mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
Start the rebuild: note schema, vault kit and data migration
Phase 1 of the functional design plan. - Move the old site into legacy/, unchanged, for reference. It still builds from there until cutover. - Add packages/schema: the one definition of all 20 note types, with field titles and descriptions, note and vault checks, and JSON Schema export so later editing tools and the MCP server share it. - Add vault-kit: Obsidian templates generated from the schema, and the Site guide for 24.01_PersonalWebsite. - Add tools/migrate: turns the legacy JSON, collections, store data and page text into 545 Site folder notes and 84 images, and checks the result with the schema. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Field builders shared by every note type.
|
||||
*
|
||||
* Each field carries `title` and `description` metadata. The JSON Schema
|
||||
* export keeps them, so the MCP server, the admin page and any later editor
|
||||
* can render a form or explain a field without a second definition.
|
||||
*/
|
||||
import { z } from "zod";
|
||||
import { normalizeWikilinkInput, wikilinkTarget } from "./names.ts";
|
||||
|
||||
type Meta = { title: string; description?: string };
|
||||
|
||||
/** A calendar date written `YYYY-MM-DD`, as Obsidian writes dates. */
|
||||
export const isoDate = (meta: Meta) =>
|
||||
z
|
||||
.preprocess(
|
||||
(v) => (v instanceof Date ? v.toISOString().slice(0, 10) : v),
|
||||
z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use a date like 2026-10-11"),
|
||||
)
|
||||
.meta(meta);
|
||||
|
||||
/** A month written `YYYY-MM`, for resume dates. */
|
||||
export const isoMonth = (meta: Meta) =>
|
||||
z.string().regex(/^\d{4}-\d{2}$/, "Use a month like 2019-03").meta(meta);
|
||||
|
||||
/** A link to another note, written `[[Note name]]`. */
|
||||
export const wikilink = (meta: Meta) =>
|
||||
z
|
||||
.preprocess(
|
||||
normalizeWikilinkInput,
|
||||
z.string().refine((v) => wikilinkTarget(v) !== null, "Write a link as [[Note name]]"),
|
||||
)
|
||||
.meta(meta);
|
||||
|
||||
/** A list of links to other notes. */
|
||||
export const wikilinks = (meta: Meta) =>
|
||||
z
|
||||
.preprocess(
|
||||
(v) => (Array.isArray(v) ? v.map(normalizeWikilinkInput) : v),
|
||||
z.array(z.string().refine((v) => wikilinkTarget(v) !== null, "Write each link as [[Note name]]")),
|
||||
)
|
||||
.meta(meta);
|
||||
|
||||
/** An image: a vault attachment as `[[file.jpg]]`, or a full https URL. */
|
||||
export const image = (meta: Meta) =>
|
||||
z
|
||||
.preprocess(
|
||||
normalizeWikilinkInput,
|
||||
z
|
||||
.string()
|
||||
.refine(
|
||||
(v) => wikilinkTarget(v) !== null || /^https:\/\//.test(v),
|
||||
"Use a vault image as [[file.jpg]] or a full https URL",
|
||||
),
|
||||
)
|
||||
.meta(meta);
|
||||
|
||||
/** One short line of text. */
|
||||
export const line = (meta: Meta) => z.string().trim().min(1).meta(meta);
|
||||
|
||||
/** A list of short tags or names. */
|
||||
export const strings = (meta: Meta) => z.array(z.string().trim().min(1)).meta(meta);
|
||||
|
||||
/**
|
||||
* Fields every note shares. `type` is added per note type.
|
||||
*
|
||||
* `publish` defaults to false: a note goes live only when it says so.
|
||||
*/
|
||||
export const baseFields = {
|
||||
title: line({ title: "Title" }),
|
||||
slug: z
|
||||
.string()
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Lowercase words joined by hyphens")
|
||||
.meta({ title: "Slug", description: "The last part of the page address. Made from the title when left out." })
|
||||
.optional(),
|
||||
publish: z
|
||||
.boolean()
|
||||
.default(false)
|
||||
.meta({ title: "Publish", description: "Only notes set to true appear on the site." }),
|
||||
favorite: z.boolean().meta({ title: "Favorite", description: "Starred on the site." }).optional(),
|
||||
tags: strings({ title: "Tags" }).optional(),
|
||||
links: wikilinks({
|
||||
title: "Links",
|
||||
description: "Other notes this one is about. The site shows the link on both notes.",
|
||||
}).optional(),
|
||||
cover: image({ title: "Cover image" }).optional(),
|
||||
created: isoDate({ title: "Created" }).optional(),
|
||||
updated: isoDate({ title: "Updated" }).optional(),
|
||||
updatedBy: z
|
||||
.enum(["greg", "agent", "admin"])
|
||||
.meta({ title: "Last edited by", description: "Set by the content service; you never need to write it." })
|
||||
.optional(),
|
||||
agentEdits: z
|
||||
.boolean()
|
||||
.meta({ title: "Agents may edit", description: "Let agents change this note without your review." })
|
||||
.optional(),
|
||||
};
|
||||
Reference in New Issue
Block a user