feat: unified, editable frontmatter editor in publish modal
All checks were successful
CI / test (push) Successful in 16s

- Preset fields (from settings) are now seeded as editable/deletable rows in
  the modal alongside custom ones, using one consistent row style.
- The note's own merged frontmatter is shown as read-only (disabled) rows at
  the top of the list (only when 'Merge document frontmatter' is on, and only
  for keys not overridden by an editable row).
- Drops the separate read-only 'resolved' text preview; the rows are the
  WYSIWYG result.

Adds pure, unit-tested composeFrontmatter()/docFrontmatterToPairs() and removes
the superseded resolveFrontmatter(). Editable rows are authoritative: on a key
collision they override the merged document field. Presets are deep-copied so
editing rows never mutates saved settings.

Co-Authored-By: Claude
This commit is contained in:
2026-06-19 03:20:36 +00:00
parent f2a4040ae8
commit fb5d11dd6a
4 changed files with 166 additions and 59 deletions

View File

@@ -14,12 +14,26 @@ export function parseNote(text: string): { frontmatter: Record<string, unknown>;
return { frontmatter, body: stripped.slice(m[0].length) };
}
export function resolveFrontmatter(
presets: Pair[],
docFrontmatter: Record<string, unknown>,
custom: Pair[],
mergeDoc: boolean
): Pair[] {
/**
* Convert a parsed note's frontmatter object into ordered, stringified pairs
* suitable for display (read-only) and composition.
*/
export function docFrontmatterToPairs(docFrontmatter: Record<string, unknown>): Pair[] {
return Object.entries(docFrontmatter).map(([key, v]) => ({ key, value: stringifyScalar(v) }));
}
/**
* Build the final frontmatter for the post.
*
* Editable rows (the preset fields seeded from settings plus any custom rows
* the user added) are authoritative: on a key collision they win over the
* document's own merged fields, which appear first (and are shown read-only in
* the modal). Blank-key rows are dropped; duplicate editable keys keep the last
* value at the first position.
*/
export function composeFrontmatter(docPairs: Pair[], editableRows: Pair[], mergeDoc: boolean): Pair[] {
const editable = editableRows.filter((r) => r.key.trim() !== "");
const editableKeys = new Set(editable.map((r) => r.key));
const out: Pair[] = [];
const idx = new Map<string, number>();
const put = (key: string, value: string) => {
@@ -27,9 +41,8 @@ export function resolveFrontmatter(
if (at !== undefined) out[at] = { key, value };
else { idx.set(key, out.length); out.push({ key, value }); }
};
for (const p of presets) put(p.key, p.value);
if (mergeDoc) for (const [k, v] of Object.entries(docFrontmatter)) put(k, stringifyScalar(v));
for (const c of custom) put(c.key, c.value);
if (mergeDoc) for (const p of docPairs) if (!editableKeys.has(p.key)) put(p.key, p.value);
for (const r of editable) put(r.key, r.value);
return out;
}