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 +++ 26 files changed, 2829 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 (limited to 'src/components') 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 && ( +