mirror of
https://github.com/prdlk/website.git
synced 2026-08-05 02:41:47 +00:00
docs(content): add documentation for content directory structure and collections
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
<!-- Parent: ../AGENTS.md -->
|
||||
<!-- Generated: 2026-06-30 | Updated: 2026-06-30 -->
|
||||
|
||||
# data
|
||||
|
||||
## Purpose
|
||||
Thin query helpers over Content Collections — draft filtering, sorting, and the experience-taxonomy aggregation.
|
||||
|
||||
## Key Files
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `post.ts` | `getAllPosts()` (returns `CollectionEntry<"writing">`, drafts filtered in prod) and `groupPostsByYear()`. **Naming quirk:** file/functions say "post" but operate on the `writing` collection. |
|
||||
| `experience.ts` | `getAllExperience()` (sorted by `startDate` desc) and `getContentForExperience(id)` which gathers all `projects`/`writing`/`speaking` entries whose `experiences` include `id`. |
|
||||
| `speaking.ts` | `getAllSpeaking()` (drafts filtered in prod). |
|
||||
|
||||
## For AI Agents
|
||||
|
||||
### Working In This Directory
|
||||
- Draft filtering uses `import.meta.env.PROD ? !data.draft : true` — drafts are visible in dev, hidden in production.
|
||||
- `getContentForExperience` matches refs via `entry.data.experiences.some((ref) => ref.id === id)`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Internal
|
||||
- `astro:content` (`getCollection`). Consumed by pages in `src/pages/` and the homepage.
|
||||
|
||||
<!-- MANUAL: -->
|
||||
@@ -0,0 +1,54 @@
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
|
||||
/** URL-safe slug for a skill/tag (tags are already lowercased by the schema). */
|
||||
export const slugifyTag = (tag: string) =>
|
||||
tag
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
|
||||
const draftFilter = ({ data }: { data: { draft?: boolean } }) =>
|
||||
import.meta.env.PROD ? !data.draft : true;
|
||||
|
||||
/** All tag-bearing content across the four collections (drafts filtered in prod). */
|
||||
export async function getTaggedCollections() {
|
||||
const [projects, experience, writing, speaking] = await Promise.all([
|
||||
getCollection("projects"),
|
||||
getCollection("experience"),
|
||||
getCollection("writing", draftFilter),
|
||||
getCollection("speaking", draftFilter),
|
||||
]);
|
||||
return { projects, experience, writing, speaking };
|
||||
}
|
||||
|
||||
export type TaggedCollections = Awaited<ReturnType<typeof getTaggedCollections>>;
|
||||
|
||||
type AnyTagged = CollectionEntry<"projects" | "experience" | "writing" | "speaking">;
|
||||
const tagsOf = (entry: AnyTagged) => entry.data.tags ?? [];
|
||||
|
||||
/** Map of slug → { label, count } across every collection, for the tag index. */
|
||||
export function getTagIndex(cols: TaggedCollections) {
|
||||
const index = new Map<string, { label: string; count: number }>();
|
||||
for (const col of Object.values(cols)) {
|
||||
for (const entry of col) {
|
||||
for (const tag of tagsOf(entry)) {
|
||||
const slug = slugifyTag(tag);
|
||||
const cur = index.get(slug) ?? { label: tag, count: 0 };
|
||||
cur.count += 1;
|
||||
index.set(slug, cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/** Every entry, grouped by collection, that carries the given tag label. */
|
||||
export function filterByTag(cols: TaggedCollections, label: string) {
|
||||
const has = (e: AnyTagged) => tagsOf(e).includes(label);
|
||||
return {
|
||||
projects: cols.projects.filter(has),
|
||||
experience: cols.experience.filter(has),
|
||||
writing: cols.writing.filter(has),
|
||||
speaking: cols.speaking.filter(has),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user