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/lib/data-utils.ts | 304 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 src/lib/data-utils.ts (limited to 'src/lib/data-utils.ts') diff --git a/src/lib/data-utils.ts b/src/lib/data-utils.ts new file mode 100644 index 0000000..f6e566a --- /dev/null +++ b/src/lib/data-utils.ts @@ -0,0 +1,304 @@ +import { getCollection, render, type CollectionEntry } from 'astro:content' +import { readingTime, calculateWordCountFromHtml } from '@/lib/utils' + +export async function getAllAuthors(): Promise[]> { + return await getCollection('authors') +} + +export async function getAllPosts(): Promise[]> { + const posts = await getCollection('blog') + return posts + .filter((post) => !post.data.draft && !isSubpost(post.id)) + .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) +} + +export async function getAllPostsAndSubposts(): Promise< + CollectionEntry<'blog'>[] +> { + const posts = await getCollection('blog') + return posts + .filter((post) => !post.data.draft) + .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) +} + +export async function getAllProjects(): Promise[]> { + const projects = await getCollection('projects') + return projects.sort((a, b) => { + const dateA = a.data.startDate?.getTime() || 0 + const dateB = b.data.startDate?.getTime() || 0 + return dateB - dateA + }) +} + +export async function getAllTags(): Promise> { + const posts = await getAllPosts() + return posts.reduce((acc, post) => { + post.data.tags?.forEach((tag) => { + acc.set(tag, (acc.get(tag) || 0) + 1) + }) + return acc + }, new Map()) +} + +export async function getAdjacentPosts(currentId: string): Promise<{ + newer: CollectionEntry<'blog'> | null + older: CollectionEntry<'blog'> | null + parent: CollectionEntry<'blog'> | null +}> { + const allPosts = await getAllPosts() + + if (isSubpost(currentId)) { + const parentId = getParentId(currentId) + const allPosts = await getAllPosts() + const parent = allPosts.find((post) => post.id === parentId) || null + + const posts = await getCollection('blog') + const subposts = posts + .filter( + (post) => + isSubpost(post.id) && + getParentId(post.id) === parentId && + !post.data.draft, + ) + .sort((a, b) => { + const dateDiff = a.data.date.valueOf() - b.data.date.valueOf() + if (dateDiff !== 0) return dateDiff + + const orderA = a.data.order ?? 0 + const orderB = b.data.order ?? 0 + return orderA - orderB + }) + + const currentIndex = subposts.findIndex((post) => post.id === currentId) + if (currentIndex === -1) { + return { newer: null, older: null, parent } + } + + return { + newer: + currentIndex < subposts.length - 1 ? subposts[currentIndex + 1] : null, + older: currentIndex > 0 ? subposts[currentIndex - 1] : null, + parent, + } + } + + const parentPosts = allPosts.filter((post) => !isSubpost(post.id)) + const currentIndex = parentPosts.findIndex((post) => post.id === currentId) + + if (currentIndex === -1) { + return { newer: null, older: null, parent: null } + } + + return { + newer: currentIndex > 0 ? parentPosts[currentIndex - 1] : null, + older: + currentIndex < parentPosts.length - 1 + ? parentPosts[currentIndex + 1] + : null, + parent: null, + } +} + +export async function getPostsByAuthor( + authorId: string, +): Promise[]> { + const posts = await getAllPosts() + return posts.filter((post) => post.data.authors?.includes(authorId)) +} + +export async function getPostsByTag( + tag: string, +): Promise[]> { + const posts = await getAllPosts() + return posts.filter((post) => post.data.tags?.includes(tag)) +} + +export async function getRecentPosts( + count: number, +): Promise[]> { + const posts = await getAllPosts() + return posts.slice(0, count) +} + +export async function getSortedTags(): Promise< + { tag: string; count: number }[] +> { + const tagCounts = await getAllTags() + return [...tagCounts.entries()] + .map(([tag, count]) => ({ tag, count })) + .sort((a, b) => { + const countDiff = b.count - a.count + return countDiff !== 0 ? countDiff : a.tag.localeCompare(b.tag) + }) +} + +export function getParentId(subpostId: string): string { + return subpostId.split('/')[0] +} + +export async function getSubpostsForParent( + parentId: string, +): Promise[]> { + const posts = await getCollection('blog') + return posts + .filter( + (post) => + !post.data.draft && + isSubpost(post.id) && + getParentId(post.id) === parentId, + ) + .sort((a, b) => { + const dateDiff = a.data.date.valueOf() - b.data.date.valueOf() + if (dateDiff !== 0) return dateDiff + + const orderA = a.data.order ?? 0 + const orderB = b.data.order ?? 0 + return orderA - orderB + }) +} + +export function groupPostsByYear( + posts: CollectionEntry<'blog'>[], +): Record[]> { + return posts.reduce( + (acc: Record[]>, post) => { + const year = post.data.date.getFullYear().toString() + ;(acc[year] ??= []).push(post) + return acc + }, + {}, + ) +} + +export async function hasSubposts(postId: string): Promise { + const subposts = await getSubpostsForParent(postId) + return subposts.length > 0 +} + +export function isSubpost(postId: string): boolean { + return postId.includes('/') +} + +export async function getParentPost( + subpostId: string, +): Promise | null> { + if (!isSubpost(subpostId)) { + return null + } + + const parentId = getParentId(subpostId) + const allPosts = await getAllPosts() + return allPosts.find((post) => post.id === parentId) || null +} + +export async function parseAuthors(authorIds: string[] = []) { + if (!authorIds.length) return [] + + const allAuthors = await getAllAuthors() + const authorMap = new Map(allAuthors.map((author) => [author.id, author])) + + return authorIds.map((id) => { + const author = authorMap.get(id) + return { + id, + name: author?.data?.name || id, + avatar: author?.data?.avatar || '/static/logo.png', + isRegistered: !!author, + } + }) +} + +export async function getPostById( + postId: string, +): Promise | null> { + const allPosts = await getAllPostsAndSubposts() + return allPosts.find((post) => post.id === postId) || null +} + +export async function getSubpostCount(parentId: string): Promise { + const subposts = await getSubpostsForParent(parentId) + return subposts.length +} + +export async function getCombinedReadingTime(postId: string): Promise { + const post = await getPostById(postId) + if (!post) return readingTime(0) + + let totalWords = calculateWordCountFromHtml(post.body) + + if (!isSubpost(postId)) { + const subposts = await getSubpostsForParent(postId) + for (const subpost of subposts) { + totalWords += calculateWordCountFromHtml(subpost.body) + } + } + + return readingTime(totalWords) +} + +export async function getPostReadingTime(postId: string): Promise { + const post = await getPostById(postId) + if (!post) return readingTime(0) + + const wordCount = calculateWordCountFromHtml(post.body) + return readingTime(wordCount) +} + +export type TOCHeading = { + slug: string + text: string + depth: number + isSubpostTitle?: boolean +} + +export type TOCSection = { + type: 'parent' | 'subpost' + title: string + headings: TOCHeading[] + subpostId?: string +} + +export async function getTOCSections(postId: string): Promise { + const post = await getPostById(postId) + if (!post) return [] + + const parentId = isSubpost(postId) ? getParentId(postId) : postId + const parentPost = isSubpost(postId) ? await getPostById(parentId) : post + + if (!parentPost) return [] + + const sections: TOCSection[] = [] + + const { headings: parentHeadings } = await render(parentPost) + if (parentHeadings.length > 0) { + sections.push({ + type: 'parent', + title: 'Overview', + headings: parentHeadings.map((heading) => ({ + slug: heading.slug, + text: heading.text, + depth: heading.depth, + })), + }) + } + + const subposts = await getSubpostsForParent(parentId) + for (const subpost of subposts) { + const { headings: subpostHeadings } = await render(subpost) + if (subpostHeadings.length > 0) { + sections.push({ + type: 'subpost', + title: subpost.data.title, + headings: subpostHeadings.map((heading, index) => ({ + slug: heading.slug, + text: heading.text, + depth: heading.depth, + isSubpostTitle: index === 0, + })), + subpostId: subpost.id, + }) + } + } + + return sections +} -- cgit v1.2.3