From d41eff9eccf66644c4c60f53cb516bf079937a28 Mon Sep 17 00:00:00 2001 From: aman333nolawz Date: Mon, 2 Mar 2026 21:49:13 +0530 Subject: Using astro-erudite for static site generation --- src/components/AuthorCard.astro | 62 +++ src/components/BlogCard.astro | 105 +++++ src/components/Breadcrumbs.astro | 61 +++ src/components/Callout.astro | 183 +++++++++ src/components/Favicons.astro | 6 + src/components/Footer.astro | 26 ++ src/components/Head.astro | 49 +++ src/components/Header.astro | 36 ++ src/components/Link.astro | 27 ++ src/components/PageHead.astro | 36 ++ src/components/PostHead.astro | 60 +++ src/components/PostNavigation.astro | 94 +++++ src/components/ProjectCard.astro | 70 ++++ src/components/SocialIcons.astro | 37 ++ src/components/SubpostsHeader.astro | 314 +++++++++++++++ src/components/SubpostsSidebar.astro | 267 +++++++++++++ src/components/TOCHeader.astro | 398 +++++++++++++++++++ src/components/TOCSidebar.astro | 365 +++++++++++++++++ src/components/ThemeToggle.astro | 72 ++++ src/components/ui/avatar.tsx | 74 ++++ src/components/ui/badge.tsx | 46 +++ src/components/ui/breadcrumb.tsx | 108 +++++ src/components/ui/button.tsx | 56 +++ src/components/ui/pagination.tsx | 195 +++++++++ src/components/ui/scroll-area.tsx | 56 +++ src/components/ui/separator.tsx | 26 ++ src/consts.ts | 47 +++ src/content.config.ts | 49 +++ src/content/authors/enscribe.md | 10 + src/content/blog/2023-post/banner.png | Bin 0 -> 95101 bytes src/content/blog/2023-post/index.mdx | 10 + src/content/blog/2024-post/banner.png | Bin 0 -> 95101 bytes src/content/blog/2024-post/index.mdx | 154 +++++++ src/content/blog/callouts-component/banner.png | Bin 0 -> 94431 bytes src/content/blog/callouts-component/index.mdx | 442 +++++++++++++++++++++ .../assets/new-toc-closed.png | Bin 0 -> 103019 bytes .../assets/new-toc-open.png | Bin 0 -> 109336 bytes .../mobile-nav-and-subposts/assets/old-toc.png | Bin 0 -> 112378 bytes .../assets/subpost-navigation.png | Bin 0 -> 29744 bytes .../assets/subposts-listing.png | Bin 0 -> 20432 bytes .../blog/mobile-nav-and-subposts/banner.png | Bin 0 -> 95701 bytes src/content/blog/mobile-nav-and-subposts/index.mdx | 82 ++++ .../mobile-nav-and-subposts/mobile-navigation.mdx | 152 +++++++ .../blog/mobile-nav-and-subposts/subposts.mdx | 161 ++++++++ src/content/blog/rehype-patch/banner.png | Bin 0 -> 94365 bytes src/content/blog/rehype-patch/index.mdx | 89 +++++ .../blog/the-state-of-static-blogs/banner.png | Bin 0 -> 79225 bytes .../blog/the-state-of-static-blogs/index.mdx | 246 ++++++++++++ src/content/projects/project-a.md | 9 + src/content/projects/project-b.md | 9 + src/content/projects/project-c.md | 8 + src/env.d.ts | 2 + src/layouts/Layout.astro | 41 ++ src/lib/data-utils.ts | 304 ++++++++++++++ src/lib/utils.ts | 37 ++ src/pages/404.astro | 29 ++ src/pages/about.astro | 47 +++ src/pages/authors/[...id].astro | 58 +++ src/pages/authors/index.astro | 27 ++ src/pages/blog/[...id].astro | 299 ++++++++++++++ src/pages/blog/[...page].astro | 58 +++ src/pages/index.astro | 85 ++++ src/pages/robots.txt.ts | 13 + src/pages/rss.xml.ts | 25 ++ src/pages/tags/[...id].astro | 52 +++ src/pages/tags/index.astro | 33 ++ src/styles/global.css | 77 ++++ src/styles/typography.css | 162 ++++++++ src/types.ts | 18 + 69 files changed, 5664 insertions(+) create mode 100644 src/components/AuthorCard.astro create mode 100644 src/components/BlogCard.astro create mode 100644 src/components/Breadcrumbs.astro create mode 100644 src/components/Callout.astro create mode 100644 src/components/Favicons.astro create mode 100644 src/components/Footer.astro create mode 100644 src/components/Head.astro create mode 100644 src/components/Header.astro create mode 100644 src/components/Link.astro create mode 100644 src/components/PageHead.astro create mode 100644 src/components/PostHead.astro create mode 100644 src/components/PostNavigation.astro create mode 100644 src/components/ProjectCard.astro create mode 100644 src/components/SocialIcons.astro create mode 100644 src/components/SubpostsHeader.astro create mode 100644 src/components/SubpostsSidebar.astro create mode 100644 src/components/TOCHeader.astro create mode 100644 src/components/TOCSidebar.astro create mode 100644 src/components/ThemeToggle.astro create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/breadcrumb.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/consts.ts create mode 100644 src/content.config.ts create mode 100644 src/content/authors/enscribe.md create mode 100644 src/content/blog/2023-post/banner.png create mode 100644 src/content/blog/2023-post/index.mdx create mode 100644 src/content/blog/2024-post/banner.png create mode 100644 src/content/blog/2024-post/index.mdx create mode 100644 src/content/blog/callouts-component/banner.png create mode 100644 src/content/blog/callouts-component/index.mdx create mode 100644 src/content/blog/mobile-nav-and-subposts/assets/new-toc-closed.png create mode 100644 src/content/blog/mobile-nav-and-subposts/assets/new-toc-open.png create mode 100644 src/content/blog/mobile-nav-and-subposts/assets/old-toc.png create mode 100644 src/content/blog/mobile-nav-and-subposts/assets/subpost-navigation.png create mode 100644 src/content/blog/mobile-nav-and-subposts/assets/subposts-listing.png create mode 100644 src/content/blog/mobile-nav-and-subposts/banner.png create mode 100644 src/content/blog/mobile-nav-and-subposts/index.mdx create mode 100644 src/content/blog/mobile-nav-and-subposts/mobile-navigation.mdx create mode 100644 src/content/blog/mobile-nav-and-subposts/subposts.mdx create mode 100644 src/content/blog/rehype-patch/banner.png create mode 100644 src/content/blog/rehype-patch/index.mdx create mode 100644 src/content/blog/the-state-of-static-blogs/banner.png create mode 100644 src/content/blog/the-state-of-static-blogs/index.mdx create mode 100644 src/content/projects/project-a.md create mode 100644 src/content/projects/project-b.md create mode 100644 src/content/projects/project-c.md create mode 100644 src/env.d.ts create mode 100644 src/layouts/Layout.astro create mode 100644 src/lib/data-utils.ts create mode 100644 src/lib/utils.ts create mode 100644 src/pages/404.astro create mode 100644 src/pages/about.astro create mode 100644 src/pages/authors/[...id].astro create mode 100644 src/pages/authors/index.astro create mode 100644 src/pages/blog/[...id].astro create mode 100644 src/pages/blog/[...page].astro create mode 100644 src/pages/index.astro create mode 100644 src/pages/robots.txt.ts create mode 100644 src/pages/rss.xml.ts create mode 100644 src/pages/tags/[...id].astro create mode 100644 src/pages/tags/index.astro create mode 100644 src/styles/global.css create mode 100644 src/styles/typography.css create mode 100644 src/types.ts (limited to 'src') diff --git a/src/components/AuthorCard.astro b/src/components/AuthorCard.astro new file mode 100644 index 0000000..9f178fa --- /dev/null +++ b/src/components/AuthorCard.astro @@ -0,0 +1,62 @@ +--- +import Link from '@/components/Link.astro' +import AvatarComponent from '@/components/ui/avatar' +import { cn } from '@/lib/utils' +import type { SocialLink } from '@/types' +import type { CollectionEntry } from 'astro:content' +import SocialIcons from './SocialIcons.astro' + +interface Props { + author: CollectionEntry<'authors'> +} + +const { author } = Astro.props +const { name, avatar, bio, pronouns } = author.data +const currentPath = Astro.url.pathname +const isAuthorPage = currentPath === `/authors/${author.id}` + +const socialLinks: SocialLink[] = [ + author.data.website && { href: author.data.website, label: 'Website' }, + author.data.github && { href: author.data.github, label: 'GitHub' }, + author.data.twitter && { href: author.data.twitter, label: 'Twitter' }, + author.data.linkedin && { href: author.data.linkedin, label: 'LinkedIn' }, + author.data.mail && { href: `mailto:${author.data.mail}`, label: 'Email' }, +].filter(Boolean) as SocialLink[] +--- + +
+
+ + [data-slot="avatar-fallback"]]:rounded-md', + !isAuthorPage && + 'hover:ring-primary transition-shadow duration-300 hover:cursor-pointer hover:ring-2', + )} + /> + +
+
+
+

{name}

+ { + pronouns && ( + ({pronouns}) + ) + } +
+

{bio}

+
+ +
+
+
diff --git a/src/components/BlogCard.astro b/src/components/BlogCard.astro new file mode 100644 index 0000000..1a74025 --- /dev/null +++ b/src/components/BlogCard.astro @@ -0,0 +1,105 @@ +--- +import AvatarComponent from '@/components/ui/avatar' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { + getCombinedReadingTime, + getSubpostCount, + isSubpost, + parseAuthors, +} from '@/lib/data-utils' +import { formatDate } from '@/lib/utils' +import { Icon } from 'astro-icon/components' +import { Image } from 'astro:assets' +import type { CollectionEntry } from 'astro:content' +import Link from './Link.astro' + +interface Props { + entry: CollectionEntry<'blog'> +} + +const { entry } = Astro.props +const formattedDate = formatDate(entry.data.date) +const readTime = await getCombinedReadingTime(entry.id) +const authors = await parseAuthors(entry.data.authors ?? []) +const subpostCount = !isSubpost(entry.id) ? await getSubpostCount(entry.id) : 0 +--- + +
+ + { + entry.data.image && ( +
+ {entry.data.title} +
+ ) + } + +
+

{entry.data.title}

+

{entry.data.description}

+ +
+ { + authors.length > 0 && ( + <> + {authors.map((author) => ( +
+ + {author.name} +
+ ))} + + + ) + } + {formattedDate} + + {readTime} + { + subpostCount > 0 && ( + <> + + + + {subpostCount} subpost{subpostCount === 1 ? '' : 's'} + + + ) + } +
+ + { + entry.data.tags && ( +
+ {entry.data.tags.map((tag) => ( + + + {tag} + + ))} +
+ ) + } +
+ +
diff --git a/src/components/Breadcrumbs.astro b/src/components/Breadcrumbs.astro new file mode 100644 index 0000000..7f5181f --- /dev/null +++ b/src/components/Breadcrumbs.astro @@ -0,0 +1,61 @@ +--- +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from '@/components/ui/breadcrumb' +import { Icon } from 'astro-icon/components' + +export interface BreadcrumbItem { + href?: string + label: string + icon?: string +} + +interface Props { + items: BreadcrumbItem[] +} + +const { items } = Astro.props +--- + + + + + + + + + { + items.map((item, index) => ( + <> + + + {index === items.length - 1 ? ( + + + {item.icon && ( + + )} + {item.label} + + + ) : ( + + + {item.icon && ( + + )} + {item.label} + + + )} + + + )) + } + + diff --git a/src/components/Callout.astro b/src/components/Callout.astro new file mode 100644 index 0000000..98ef07d --- /dev/null +++ b/src/components/Callout.astro @@ -0,0 +1,183 @@ +--- +import { cn } from '@/lib/utils' +import { Icon } from 'astro-icon/components' +import { cva, type VariantProps } from 'class-variance-authority' + +const calloutConfig = { + note: { + style: 'border-blue-500 dark:bg-blue-950/5', + textColor: 'text-blue-700 dark:text-blue-300', + icon: 'lucide:info', + }, + tip: { + style: 'border-green-500 dark:bg-green-950/5', + textColor: 'text-green-700 dark:text-green-300', + icon: 'lucide:lightbulb', + }, + warning: { + style: 'border-amber-500 dark:bg-amber-950/5', + textColor: 'text-amber-700 dark:text-amber-300', + icon: 'lucide:alert-triangle', + }, + danger: { + style: 'border-red-500 dark:bg-red-950/5', + textColor: 'text-red-700 dark:text-red-300', + icon: 'lucide:shield-alert', + }, + important: { + style: 'border-purple-500 dark:bg-purple-950/5', + textColor: 'text-purple-700 dark:text-purple-300', + icon: 'lucide:message-square-warning', + }, + definition: { + style: 'border-purple-500 dark:bg-purple-950/5', + textColor: 'text-purple-700 dark:text-purple-300', + icon: 'lucide:book-open', + }, + theorem: { + style: 'border-teal-500 dark:bg-teal-950/5', + textColor: 'text-teal-700 dark:text-teal-300', + icon: 'lucide:check-circle', + }, + lemma: { + style: 'border-sky-400 dark:bg-sky-950/5', + textColor: 'text-sky-700 dark:text-sky-300', + icon: 'lucide:puzzle', + }, + proof: { + style: 'border-gray-500 dark:bg-gray-950/5', + textColor: 'text-gray-700 dark:text-gray-300', + icon: 'lucide:check-square', + }, + corollary: { + style: 'border-cyan-500 dark:bg-cyan-950/5', + textColor: 'text-cyan-700 dark:text-cyan-300', + icon: 'lucide:git-branch', + }, + proposition: { + style: 'border-slate-500 dark:bg-slate-950/5', + textColor: 'text-slate-700 dark:text-slate-300', + icon: 'lucide:file-text', + }, + axiom: { + style: 'border-violet-600 dark:bg-violet-950/5', + textColor: 'text-violet-700 dark:text-violet-300', + icon: 'lucide:anchor', + }, + conjecture: { + style: 'border-pink-500 dark:bg-pink-950/5', + textColor: 'text-pink-700 dark:text-pink-300', + icon: 'lucide:help-circle', + }, + notation: { + style: 'border-slate-400 dark:bg-slate-950/5', + textColor: 'text-slate-700 dark:text-slate-300', + icon: 'lucide:pen-tool', + }, + remark: { + style: 'border-gray-400 dark:bg-gray-950/5', + textColor: 'text-gray-700 dark:text-gray-300', + icon: 'lucide:message-circle', + }, + intuition: { + style: 'border-yellow-500 dark:bg-yellow-950/5', + textColor: 'text-yellow-700 dark:text-yellow-300', + icon: 'lucide:lightbulb', + }, + recall: { + style: 'border-blue-300 dark:bg-blue-950/5', + textColor: 'text-blue-600 dark:text-blue-300', + icon: 'lucide:rotate-ccw', + }, + explanation: { + style: 'border-lime-500 dark:bg-lime-950/5', + textColor: 'text-lime-700 dark:text-lime-300', + icon: 'lucide:help-circle', + }, + example: { + style: 'border-emerald-500 dark:bg-emerald-950/5', + textColor: 'text-emerald-700 dark:text-emerald-300', + icon: 'lucide:code', + }, + exercise: { + style: 'border-indigo-500 dark:bg-indigo-950/5', + textColor: 'text-indigo-700 dark:text-indigo-300', + icon: 'lucide:dumbbell', + }, + problem: { + style: 'border-orange-600 dark:bg-orange-950/5', + textColor: 'text-orange-700 dark:text-orange-300', + icon: 'lucide:alert-circle', + }, + answer: { + style: 'border-teal-500 dark:bg-teal-950/5', + textColor: 'text-teal-700 dark:text-teal-300', + icon: 'lucide:check', + }, + solution: { + style: 'border-emerald-600 dark:bg-emerald-950/5', + textColor: 'text-emerald-700 dark:text-emerald-300', + icon: 'lucide:check-circle-2', + }, + summary: { + style: 'border-sky-500 dark:bg-sky-950/5', + textColor: 'text-sky-700 dark:text-sky-300', + icon: 'lucide:list', + }, +} as const + +const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1) + +const calloutVariants = cva('relative px-4 py-3 my-6 border-l-4 text-sm', { + variants: { + variant: Object.fromEntries( + Object.entries(calloutConfig).map(([key, config]) => [key, config.style]), + ), + }, + defaultVariants: { + variant: 'note', + }, +}) + +interface Props extends VariantProps { + title?: string + class?: string + variant?: keyof typeof calloutConfig + defaultOpen?: boolean +} + +const { title, variant = 'note', defaultOpen = true, ...rest } = Astro.props +--- + +
summary_svg:last-child]:rotate-180 [&[open]>summary]:mb-3', + )} + {...rest} + open={defaultOpen} +> + + + + {capitalize(variant)} + {title && ({title})} + + + +
+ +
+
diff --git a/src/components/Favicons.astro b/src/components/Favicons.astro new file mode 100644 index 0000000..5816730 --- /dev/null +++ b/src/components/Favicons.astro @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/Footer.astro b/src/components/Footer.astro new file mode 100644 index 0000000..7ff1e50 --- /dev/null +++ b/src/components/Footer.astro @@ -0,0 +1,26 @@ +--- +import { Separator } from '@/components/ui/separator' +import { SOCIAL_LINKS } from '@/consts' +import Link from './Link.astro' +import SocialIcons from './SocialIcons.astro' +--- + +
+
+ + © {new Date().getFullYear()} All rights reserved. + + +

+ Made with 🤍 by enscribe! +

+
+ +
diff --git a/src/components/Head.astro b/src/components/Head.astro new file mode 100644 index 0000000..8ce42cb --- /dev/null +++ b/src/components/Head.astro @@ -0,0 +1,49 @@ +--- +import { SITE } from '@/consts' +import { ClientRouter } from 'astro:transitions' +import Favicons from './Favicons.astro' +--- + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Header.astro b/src/components/Header.astro new file mode 100644 index 0000000..ff7dafe --- /dev/null +++ b/src/components/Header.astro @@ -0,0 +1,36 @@ +--- +import Link from '@/components/Link.astro' +import ThemeToggle from '@/components/ThemeToggle.astro' +import { NAV_LINKS, SITE } from '@/consts' +import { Image } from 'astro:assets' +import logo from '../../public/static/logo.svg' +--- + +
+
+ + Logo + + +
+ + +
+
+
diff --git a/src/components/Link.astro b/src/components/Link.astro new file mode 100644 index 0000000..6d434b1 --- /dev/null +++ b/src/components/Link.astro @@ -0,0 +1,27 @@ +--- +import { cn } from '@/lib/utils' + +interface Props { + href: string + external?: boolean + class?: string + underline?: boolean + [key: string]: any +} + +const { href, external, class: className, underline, ...rest } = Astro.props +--- + + + + diff --git a/src/components/PageHead.astro b/src/components/PageHead.astro new file mode 100644 index 0000000..7983fde --- /dev/null +++ b/src/components/PageHead.astro @@ -0,0 +1,36 @@ +--- +import { SITE } from '@/consts' + +interface Props { + title?: string + description?: string + noindex?: boolean +} + +const { + title = SITE.title, + description = SITE.description, + noindex = false, +} = Astro.props +const image = new URL('/static/1200x630.png', Astro.site) +--- + +{`${title} | ${SITE.title}`} + + +{noindex && } + + + + + + + + + + + + + + + diff --git a/src/components/PostHead.astro b/src/components/PostHead.astro new file mode 100644 index 0000000..911b195 --- /dev/null +++ b/src/components/PostHead.astro @@ -0,0 +1,60 @@ +--- +import { SITE } from '@/consts' +import { isSubpost } from '@/lib/data-utils' +import type { CollectionEntry } from 'astro:content' + +interface Props { + post: CollectionEntry<'blog'> +} + +const { post } = Astro.props + +const title = post.data.title || SITE.title +const description = post.data.description || SITE.description +const image = new URL('/static/1200x630.png', Astro.site) +const author = + post.data.authors && post.data.authors.length > 0 + ? post.data.authors.join(', ') + : SITE.author +--- + +{`${title} | ${SITE.title}`} + + + + +{isSubpost(post.id) && } + + + + + + + + + + + + + + + + + + +{ + post?.data.tags && + post.data.tags.map((tag: string) => { + return + }) +} diff --git a/src/components/PostNavigation.astro b/src/components/PostNavigation.astro new file mode 100644 index 0000000..8dc2db5 --- /dev/null +++ b/src/components/PostNavigation.astro @@ -0,0 +1,94 @@ +--- +import Link from '@/components/Link.astro' +import { buttonVariants } from '@/components/ui/button' +import { cn } from '@/lib/utils' +import { Icon } from 'astro-icon/components' + +const { newerPost, olderPost, parentPost } = Astro.props +const isSubpost = !!parentPost +--- + + diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro new file mode 100644 index 0000000..03449a7 --- /dev/null +++ b/src/components/ProjectCard.astro @@ -0,0 +1,70 @@ +--- +import Link from '@/components/Link.astro' +import { Badge } from '@/components/ui/badge' +import { formatDate } from '@/lib/utils' +import { Icon } from 'astro-icon/components' +import { Image } from 'astro:assets' +import type { CollectionEntry } from 'astro:content' + +interface Props { + project: CollectionEntry<'projects'> +} + +const { project } = Astro.props +--- + +
+ + { + project.data.image && ( +
+ {project.data.name} +
+ ) + } +
+

+ {project.data.name} +

+

+ {project.data.description} +

+ { + project.data.startDate && ( +

+ + + + {formatDate(project.data.startDate)} + {project.data.endDate + ? ` → ${formatDate(project.data.endDate)}` + : ' → Present'} + + +

+ ) + } + { + project.data.tags && ( +
+ {project.data.tags.map((tag: string) => ( + {tag} + ))} +
+ ) + } +
+ +
diff --git a/src/components/SocialIcons.astro b/src/components/SocialIcons.astro new file mode 100644 index 0000000..236bb6a --- /dev/null +++ b/src/components/SocialIcons.astro @@ -0,0 +1,37 @@ +--- +import Link from '@/components/Link.astro' +import { buttonVariants } from '@/components/ui/button' +import { ICON_MAP } from '@/consts' +import type { SocialLink } from '@/types' +import { Icon } from 'astro-icon/components' + +interface Props { + links: SocialLink[] +} + +const { links } = Astro.props +--- + +
    + { + links.map(({ href, label }) => ( +
  • + + + +
  • + )) + } +
diff --git a/src/components/SubpostsHeader.astro b/src/components/SubpostsHeader.astro new file mode 100644 index 0000000..0f1c597 --- /dev/null +++ b/src/components/SubpostsHeader.astro @@ -0,0 +1,314 @@ +--- +import { ScrollArea } from '@/components/ui/scroll-area' +import { + getCombinedReadingTime, + getParentId, + getParentPost, + getPostById, + getPostReadingTime, + getSubpostsForParent, + isSubpost, +} from '@/lib/data-utils' +import { Icon } from 'astro-icon/components' + +const { parentId } = Astro.props +const currentPostId = Astro.params.id as string +const isCurrentSubpost = isSubpost(currentPostId) +const rootParentId = isCurrentSubpost ? getParentId(currentPostId) : parentId + +const currentPost = !isCurrentSubpost ? await getPostById(currentPostId) : null +const subposts = await getSubpostsForParent(rootParentId) +const parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null + +const activePost = parentPost || currentPost +const isActivePost = activePost?.id === currentPostId + +const activePostReadingTime = activePost + ? await getPostReadingTime(activePost.id) + : null +const activePostCombinedReadingTime = + activePost && subposts.length > 0 + ? await getCombinedReadingTime(activePost.id) + : null +const subpostsWithReadingTime = await Promise.all( + subposts.map(async (subpost) => ({ + ...subpost, + readingTime: await getPostReadingTime(subpost.id), + })), +) + +const currentSubpostDetails = isCurrentSubpost + ? subpostsWithReadingTime.find((subpost) => subpost.id === currentPostId) + : null +--- + +{ + activePost && subposts.length > 0 && ( +
+
+ +
+
+
+
+ + {currentSubpostDetails + ? currentSubpostDetails.data.title + : activePost?.data.title} + +
+ + + +
+
+ + +
+ +
+
+
+
+ ) +} + + diff --git a/src/components/SubpostsSidebar.astro b/src/components/SubpostsSidebar.astro new file mode 100644 index 0000000..3428583 --- /dev/null +++ b/src/components/SubpostsSidebar.astro @@ -0,0 +1,267 @@ +--- +import Link from '@/components/Link.astro' +import { ScrollArea } from '@/components/ui/scroll-area' +import { + getCombinedReadingTime, + getParentId, + getParentPost, + getPostById, + getPostReadingTime, + getSubpostsForParent, + isSubpost, +} from '@/lib/data-utils' +import { Icon } from 'astro-icon/components' + +const { parentId } = Astro.props +const currentPostId = Astro.params.id as string +const isCurrentSubpost = isSubpost(currentPostId) +const rootParentId = isCurrentSubpost ? getParentId(currentPostId) : parentId + +const currentPost = !isCurrentSubpost ? await getPostById(currentPostId) : null +const subposts = await getSubpostsForParent(rootParentId) +const parentPost = isCurrentSubpost ? await getParentPost(currentPostId) : null + +const activePost = parentPost || currentPost +const isActivePost = activePost?.id === currentPostId + +const activePostReadingTime = activePost + ? await getPostReadingTime(activePost.id) + : null +const activePostCombinedReadingTime = + activePost && subposts.length > 0 + ? await getCombinedReadingTime(activePost.id) + : null +const subpostsWithReadingTime = await Promise.all( + subposts.map(async (subpost) => ({ + ...subpost, + readingTime: await getPostReadingTime(subpost.id), + })), +) +--- + + + + diff --git a/src/components/TOCHeader.astro b/src/components/TOCHeader.astro new file mode 100644 index 0000000..5500f17 --- /dev/null +++ b/src/components/TOCHeader.astro @@ -0,0 +1,398 @@ +--- +import { ScrollArea } from '@/components/ui/scroll-area' +import { cn, getHeadingMargin } from '@/lib/utils' +import type { MarkdownHeading } from 'astro' +import { Icon } from 'astro-icon/components' + +type Props = { + headings: MarkdownHeading[] +} + +const { headings } = Astro.props +--- + +{ + headings && headings.length > 0 && ( +
+
+ +
+
+ + + + +
+ + Overview + + + + +
+
+ + +
+ +
+
+
+
+ ) +} + + diff --git a/src/components/TOCSidebar.astro b/src/components/TOCSidebar.astro new file mode 100644 index 0000000..77be7c6 --- /dev/null +++ b/src/components/TOCSidebar.astro @@ -0,0 +1,365 @@ +--- +import { ScrollArea } from '@/components/ui/scroll-area' +import type { TOCSection } from '@/lib/data-utils' +import { getParentId, isSubpost } from '@/lib/data-utils' +import { cn, getHeadingMargin } from '@/lib/utils' + +type Props = { + sections: TOCSection[] + currentPostId: string +} + +const { sections, currentPostId } = Astro.props +const isCurrentSubpost = isSubpost(currentPostId) +const parentId = isCurrentSubpost ? getParentId(currentPostId) : currentPostId +--- + +{ + sections.length > 0 && ( +