UI / dev tooling - /find/ index: sort by file birthtime desc (sub-day chronology even when many finds share the same frontmatter date); render the captured-at on its own line under the host (~0.65rem mono muted) - New dev-only filter at the top of /find/: All / Not in lists, persisted in localStorage; "not in lists" hides finds referenced by any superpost - New editorial-tags chip strip on each find card (dev-only): selected tags visible by default, full picker on hover/focus-within with a "…" button for arbitrary tag entry; clicks copy /edit-find-tag <slug> <tag> to the clipboard via the existing EditConfirm singleton - New optional editorialTags: string[] field on the find collection - New src/data/editorial-tags.json — master list of available tags (initial set: ["delete"]); display order follows file order /grid/ now uses the JS Masonry component instead of CSS column-count, so the middle column no longer "dips"; tile hover updated to match the home page's accent-glow / no-translate style Content - New review: apex-markdown-processor (graduated from find of same slug; hero reused from the find folder) - New review: sindre-sorhus-older-mac-apps (graduated; intentionally hero-less per the source page's text-only design) - New review: superkey (added by user; hero wired in src/lib/hero.ts) - 24 new finds captured by /daily-finds across travel, jokes, quotes, product picks, and indie tools - 2 new finds superposts: 2026-05-04-a-small-atlas, 2026-05-04-off-until-needed Misc - daily-finds.log.md and candidate-sources.md updated with the day's runs - build-finds skill prompt iterated by user
81 lines
2.0 KiB
TypeScript
81 lines
2.0 KiB
TypeScript
import { defineCollection, z } from 'astro:content';
|
|
import { glob } from 'astro/loaders';
|
|
|
|
const stripIndex = ({ entry }: { entry: string }) =>
|
|
entry.replace(/\/index\.mdx$/, '');
|
|
|
|
const reviews = defineCollection({
|
|
loader: glob({
|
|
pattern: '**/index.mdx',
|
|
base: './src/content/reviews',
|
|
generateId: stripIndex,
|
|
}),
|
|
schema: z.object({
|
|
name: z.string(),
|
|
subtitle: z.string(),
|
|
date: z.coerce.date(),
|
|
category: z.string(),
|
|
tags: z.array(z.string()).default([]),
|
|
link: z.string().url().optional(),
|
|
linkText: z.string().optional(),
|
|
description: z.string().optional(),
|
|
source: z.string().optional(),
|
|
fromFind: z.string().optional(),
|
|
}),
|
|
});
|
|
|
|
const posts = defineCollection({
|
|
loader: glob({
|
|
pattern: '**/index.mdx',
|
|
base: './src/content/posts',
|
|
generateId: stripIndex,
|
|
}),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
date: z.coerce.date(),
|
|
category: z.string(),
|
|
tags: z.array(z.string()).default([]),
|
|
description: z.string().optional(),
|
|
}),
|
|
});
|
|
|
|
const find = defineCollection({
|
|
loader: glob({
|
|
pattern: '**/index.mdx',
|
|
base: './src/content/find',
|
|
generateId: stripIndex,
|
|
}),
|
|
schema: z.object({
|
|
name: z.string(),
|
|
subtitle: z.string().optional(),
|
|
date: z.coerce.date(),
|
|
link: z.string().url(),
|
|
linkText: z.string().optional(),
|
|
source: z.string(),
|
|
topics: z.array(z.string()).default([]),
|
|
tags: z.array(z.string()).default([]),
|
|
description: z.string().optional(),
|
|
promotedTo: z.string().optional(),
|
|
editorialTags: z.array(z.string()).default([]),
|
|
}),
|
|
});
|
|
|
|
const finds = defineCollection({
|
|
loader: glob({
|
|
pattern: '**/index.mdx',
|
|
base: './src/content/finds',
|
|
generateId: stripIndex,
|
|
}),
|
|
schema: z.object({
|
|
title: z.string(),
|
|
date: z.coerce.date(),
|
|
category: z.string().default('finds'),
|
|
tags: z.array(z.string()).default([]),
|
|
description: z.string().optional(),
|
|
blurb: z.string().optional(),
|
|
items: z.array(z.string()).min(1),
|
|
}),
|
|
});
|
|
|
|
export const collections = { reviews, posts, find, finds };
|