feat(content): introduce projects, experience & speaking collections and support tagging, experiences & draft status across content types

This commit is contained in:
Prad Nukala
2026-06-30 12:03:14 -04:00
parent ee266a4197
commit 0c7b32d54c
16 changed files with 557 additions and 54 deletions
+22
View File
@@ -0,0 +1,22 @@
import { type CollectionEntry, getCollection } from "astro:content";
/** All experiences, most recent (by startDate) first. */
export async function getAllExperience(): Promise<CollectionEntry<"experience">[]> {
const entries = await getCollection("experience");
return entries.sort((a, b) => b.data.startDate.valueOf() - a.data.startDate.valueOf());
}
const hasExperience = (
entry: { data: { experiences: { id: string }[] } },
id: string,
): boolean => entry.data.experiences.some((ref) => ref.id === id);
/** Every project, writing & talk filed under a given experience id. */
export async function getContentForExperience(id: string) {
const [projects, writing, speaking] = await Promise.all([
getCollection("projects", (e) => hasExperience(e, id)),
getCollection("writing", (e) => (import.meta.env.PROD ? !e.data.draft : true) && hasExperience(e, id)),
getCollection("speaking", (e) => (import.meta.env.PROD ? !e.data.draft : true) && hasExperience(e, id)),
]);
return { projects, writing, speaking };
}
+4 -38
View File
@@ -1,49 +1,15 @@
import { type CollectionEntry, getCollection } from "astro:content";
/** filter out draft posts based on the environment */
export async function getAllPosts(): Promise<CollectionEntry<"post">[]> {
return await getCollection("post", ({ data }) => {
export async function getAllPosts(): Promise<CollectionEntry<"writing">[]> {
return await getCollection("writing", ({ data }) => {
return import.meta.env.PROD ? !data.draft : true;
});
}
/** Get tag metadata by tag name */
export async function getTagMeta(tag: string): Promise<CollectionEntry<"tag"> | undefined> {
const tagEntries = await getCollection("tag", (entry) => {
return entry.id === tag;
});
return tagEntries[0];
}
/** groups posts by year (based on option siteConfig.sortPostsByUpdatedDate), using the year as the key
/** groups posts by year, using the year as the key
* Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so.
*/
export function groupPostsByYear(posts: CollectionEntry<"post">[]) {
export function groupPostsByYear(posts: CollectionEntry<"writing">[]) {
return Object.groupBy(posts, (post) => post.data.publishDate.getFullYear().toString());
}
/** returns all tags created from posts (inc duplicate tags)
* Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so.
* */
export function getAllTags(posts: CollectionEntry<"post">[]) {
return posts.flatMap((post) => [...post.data.tags]);
}
/** returns all unique tags created from posts
* Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so.
* */
export function getUniqueTags(posts: CollectionEntry<"post">[]) {
return [...new Set(getAllTags(posts))];
}
/** returns a count of each unique tag - [[tagName, count], ...]
* Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so.
* */
export function getUniqueTagsWithCount(posts: CollectionEntry<"post">[]): [string, number][] {
return [
...getAllTags(posts).reduce(
(acc, t) => acc.set(t, (acc.get(t) ?? 0) + 1),
new Map<string, number>(),
),
].sort((a, b) => b[1] - a[1]);
}
+8
View File
@@ -0,0 +1,8 @@
import { type CollectionEntry, getCollection } from "astro:content";
/** filter out draft talks in production */
export async function getAllSpeaking(): Promise<CollectionEntry<"speaking">[]> {
return await getCollection("speaking", ({ data }) => {
return import.meta.env.PROD ? !data.draft : true;
});
}