@glw907/cairn-cms 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/adapter.d.ts +24 -0
  2. package/dist/adapter.d.ts.map +1 -1
  3. package/dist/auth/capabilities.d.ts +7 -0
  4. package/dist/auth/capabilities.d.ts.map +1 -0
  5. package/dist/auth/capabilities.js +26 -0
  6. package/dist/auth/index.d.ts +1 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +1 -0
  9. package/dist/components/AdminLayout.svelte +72 -16
  10. package/dist/components/AdminLayout.svelte.d.ts +9 -0
  11. package/dist/components/AdminLayout.svelte.d.ts.map +1 -1
  12. package/dist/components/CollectionList.svelte +96 -0
  13. package/dist/components/CollectionList.svelte.d.ts +8 -0
  14. package/dist/components/CollectionList.svelte.d.ts.map +1 -0
  15. package/dist/components/ComponentPalette.svelte +34 -0
  16. package/dist/components/ComponentPalette.svelte.d.ts +9 -0
  17. package/dist/components/ComponentPalette.svelte.d.ts.map +1 -0
  18. package/dist/components/EditPage.svelte +66 -28
  19. package/dist/components/EditPage.svelte.d.ts +2 -0
  20. package/dist/components/EditPage.svelte.d.ts.map +1 -1
  21. package/dist/components/NavTree.svelte +128 -0
  22. package/dist/components/NavTree.svelte.d.ts +8 -0
  23. package/dist/components/NavTree.svelte.d.ts.map +1 -0
  24. package/dist/components/index.d.ts +3 -1
  25. package/dist/components/index.d.ts.map +1 -1
  26. package/dist/components/index.js +3 -1
  27. package/dist/editor.d.ts +25 -0
  28. package/dist/editor.d.ts.map +1 -0
  29. package/dist/editor.js +20 -0
  30. package/dist/frontmatter.d.ts +3 -0
  31. package/dist/frontmatter.d.ts.map +1 -0
  32. package/dist/frontmatter.js +16 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -0
  36. package/dist/nav.d.ts +58 -0
  37. package/dist/nav.d.ts.map +1 -0
  38. package/dist/nav.js +86 -0
  39. package/dist/slug.d.ts +7 -0
  40. package/dist/slug.d.ts.map +1 -0
  41. package/dist/slug.js +15 -0
  42. package/dist/sveltekit/index.d.ts +102 -12
  43. package/dist/sveltekit/index.d.ts.map +1 -1
  44. package/dist/sveltekit/index.js +219 -20
  45. package/package.json +7 -2
  46. package/src/lib/adapter.ts +25 -0
  47. package/src/lib/auth/capabilities.ts +35 -0
  48. package/src/lib/auth/index.ts +1 -0
  49. package/src/lib/components/AdminLayout.svelte +72 -16
  50. package/src/lib/components/CollectionList.svelte +96 -0
  51. package/src/lib/components/ComponentPalette.svelte +34 -0
  52. package/src/lib/components/EditPage.svelte +66 -28
  53. package/src/lib/components/NavTree.svelte +128 -0
  54. package/src/lib/components/index.ts +3 -1
  55. package/src/lib/editor.ts +38 -0
  56. package/src/lib/frontmatter.ts +17 -0
  57. package/src/lib/index.ts +2 -0
  58. package/src/lib/nav.ts +117 -0
  59. package/src/lib/slug.ts +16 -0
  60. package/src/lib/sveltekit/index.ts +303 -26
  61. package/dist/components/AdminList.svelte +0 -33
  62. package/dist/components/AdminList.svelte.d.ts +0 -10
  63. package/dist/components/AdminList.svelte.d.ts.map +0 -1
  64. package/src/lib/components/AdminList.svelte +0 -33
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ // The navigation tree editor (Pass L). Edits a local copy of the menu tree and posts the whole
3
+ // tree as JSON to the `save` action. DaisyUI primitives under the Warm Stone admin theme. Drag a
4
+ // row up or down to reorder within its level; use Indent/Outdent to nest under the previous
5
+ // sibling or promote a level (capped at the menu's maxDepth). The engine validates on save.
6
+ import { untrack } from 'svelte';
7
+ import type { NavLoadData } from '../sveltekit';
8
+ import type { NavNode } from '../nav';
9
+
10
+ let { data }: { data: NavLoadData } = $props();
11
+
12
+ // A flat, ordered working model is far simpler to drag-edit than a recursive one: each row
13
+ // carries an explicit depth, and the tree is rebuilt from (order + depth) only at submit time.
14
+ interface Row {
15
+ id: number;
16
+ depth: number;
17
+ label: string;
18
+ url: string;
19
+ }
20
+
21
+ let nextId = 1;
22
+ function flatten(nodes: NavNode[], depth: number, out: Row[]): Row[] {
23
+ for (const n of nodes) {
24
+ out.push({ id: nextId++, depth, label: n.label, url: n.url ?? '' });
25
+ if (n.children?.length) flatten(n.children, depth + 1, out);
26
+ }
27
+ return out;
28
+ }
29
+
30
+ let rows = $state<Row[]>(untrack(() => flatten(data.tree, 0, [])));
31
+ const maxDepthIndex = $derived(data.menu.maxDepth - 1); // depth is 0-based here
32
+
33
+ // Rebuild the nested tree from the flat rows by depth, then serialize for the hidden field.
34
+ function toTree(list: Row[]): NavNode[] {
35
+ const root: NavNode[] = [];
36
+ const stack: { depth: number; node: NavNode }[] = [];
37
+ for (const r of list) {
38
+ const node: NavNode = { label: r.label.trim() };
39
+ if (r.url.trim()) node.url = r.url.trim();
40
+ while (stack.length && stack[stack.length - 1].depth >= r.depth) stack.pop();
41
+ if (stack.length) (stack[stack.length - 1].node.children ??= []).push(node);
42
+ else root.push(node);
43
+ stack.push({ depth: r.depth, node });
44
+ }
45
+ return root;
46
+ }
47
+
48
+ const treeJson = $derived(JSON.stringify(toTree(rows)));
49
+
50
+ function addRow() {
51
+ rows = [...rows, { id: nextId++, depth: 0, label: 'New item', url: '' }];
52
+ }
53
+ function removeRow(id: number) {
54
+ rows = rows.filter((r) => r.id !== id);
55
+ }
56
+ function indent(i: number) {
57
+ // A row may nest at most one level deeper than the row above it, and never past the cap.
58
+ if (i === 0) return;
59
+ const ceiling = Math.min(rows[i - 1].depth + 1, maxDepthIndex);
60
+ if (rows[i].depth < ceiling) rows[i].depth += 1;
61
+ }
62
+ function outdent(i: number) {
63
+ if (rows[i].depth > 0) rows[i].depth -= 1;
64
+ }
65
+
66
+ let dragFrom = $state<number | null>(null);
67
+ function onDrop(to: number) {
68
+ if (dragFrom === null || dragFrom === to) return;
69
+ const next = [...rows];
70
+ const [moved] = next.splice(dragFrom, 1);
71
+ next.splice(to, 0, moved);
72
+ rows = next;
73
+ dragFrom = null;
74
+ }
75
+ </script>
76
+
77
+ <div class="cairn-admin">
78
+ <div class="flex items-center justify-between">
79
+ <h1 class="text-xl font-semibold">{data.menu.label}</h1>
80
+ <button type="button" class="btn btn-sm" onclick={addRow}>Add item</button>
81
+ </div>
82
+
83
+ {#if data.saved}
84
+ <div class="alert alert-success mt-3">Navigation saved.</div>
85
+ {/if}
86
+ {#if data.error}
87
+ <div class="alert alert-error mt-3">{data.error}</div>
88
+ {/if}
89
+
90
+ <form method="POST" action="?/save" class="mt-4">
91
+ <input type="hidden" name="tree" value={treeJson} />
92
+ <ul class="menu w-full gap-1">
93
+ {#each rows as row, i (row.id)}
94
+ <li
95
+ draggable="true"
96
+ ondragstart={() => (dragFrom = i)}
97
+ ondragover={(e) => e.preventDefault()}
98
+ ondrop={() => onDrop(i)}
99
+ style={`margin-left:${row.depth * 1.5}rem`}
100
+ >
101
+ <div class="flex items-center gap-2 p-2">
102
+ <span class="cursor-grab opacity-40" aria-hidden="true">&#x283F;</span>
103
+ <input class="input input-sm input-bordered flex-1" placeholder="Label" bind:value={row.label} />
104
+ <input
105
+ class="input input-sm input-bordered flex-1"
106
+ placeholder="/path or https://…"
107
+ list="cairn-nav-pages"
108
+ bind:value={row.url}
109
+ />
110
+ <button type="button" class="btn btn-xs btn-ghost" onclick={() => outdent(i)} aria-label="Outdent">&larr;</button>
111
+ <button type="button" class="btn btn-xs btn-ghost" onclick={() => indent(i)} aria-label="Indent">&rarr;</button>
112
+ <button type="button" class="btn btn-xs btn-ghost text-error" onclick={() => removeRow(row.id)} aria-label="Remove">&times;</button>
113
+ </div>
114
+ </li>
115
+ {/each}
116
+ </ul>
117
+
118
+ <datalist id="cairn-nav-pages">
119
+ {#each data.pages as p (p.url)}
120
+ <option value={p.url}>{p.label}</option>
121
+ {/each}
122
+ </datalist>
123
+
124
+ <div class="mt-4">
125
+ <button type="submit" class="btn btn-primary btn-sm">Save navigation</button>
126
+ </div>
127
+ </form>
128
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { NavLoadData } from '../sveltekit';
2
+ type $$ComponentProps = {
3
+ data: NavLoadData;
4
+ };
5
+ declare const NavTree: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type NavTree = ReturnType<typeof NavTree>;
7
+ export default NavTree;
8
+ //# sourceMappingURL=NavTree.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavTree.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/NavTree.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAY/C,KAAK,gBAAgB,GAAI;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAiHhD,QAAA,MAAM,OAAO,sDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -1,7 +1,9 @@
1
1
  export { default as AdminLayout } from './AdminLayout.svelte';
2
- export { default as AdminList } from './AdminList.svelte';
2
+ export { default as CollectionList } from './CollectionList.svelte';
3
3
  export { default as LoginPage } from './LoginPage.svelte';
4
4
  export { default as ConfirmPage } from './ConfirmPage.svelte';
5
5
  export { default as EditPage } from './EditPage.svelte';
6
6
  export { default as ManageAdmins } from './ManageAdmins.svelte';
7
+ export { default as ComponentPalette } from './ComponentPalette.svelte';
8
+ export { default as NavTree } from './NavTree.svelte';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/components/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/components/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,8 +1,10 @@
1
1
  // cairn-cms admin UI shell. Consumers import from 'cairn-cms/components'; each site's
2
2
  // admin route `.svelte` files are one-line shims around these.
3
3
  export { default as AdminLayout } from './AdminLayout.svelte';
4
- export { default as AdminList } from './AdminList.svelte';
4
+ export { default as CollectionList } from './CollectionList.svelte';
5
5
  export { default as LoginPage } from './LoginPage.svelte';
6
6
  export { default as ConfirmPage } from './ConfirmPage.svelte';
7
7
  export { default as EditPage } from './EditPage.svelte';
8
8
  export { default as ManageAdmins } from './ManageAdmins.svelte';
9
+ export { default as ComponentPalette } from './ComponentPalette.svelte';
10
+ export { default as NavTree } from './NavTree.svelte';
@@ -0,0 +1,25 @@
1
+ interface CartaInput {
2
+ getSelection(): {
3
+ start: number;
4
+ end: number;
5
+ direction: string;
6
+ slice: string;
7
+ };
8
+ insertAt(position: number, text: string): void;
9
+ }
10
+ interface CartaLike {
11
+ input?: CartaInput;
12
+ }
13
+ /** The programmatic editing surface the admin relies on. */
14
+ export interface MarkdownEditor {
15
+ /** Insert a component or template at the current cursor position. */
16
+ insertComponent(template: string): void;
17
+ }
18
+ /**
19
+ * Wrap a Carta instance as a MarkdownEditor. Takes a getter (not the instance) because the
20
+ * EditPage component creates the Carta instance once and `carta.input` is only populated after
21
+ * the editor mounts; reading it lazily at call time avoids capturing an undefined `input`.
22
+ */
23
+ export declare function cartaEditor(getCarta: () => CartaLike): MarkdownEditor;
24
+ export {};
25
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/lib/editor.ts"],"names":[],"mappings":"AAQA,UAAU,UAAU;IAClB,YAAY,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAChD;AAED,UAAU,SAAS;IACjB,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,SAAS,GAAG,cAAc,CASrE"}
package/dist/editor.js ADDED
@@ -0,0 +1,20 @@
1
+ // cairn-core: the editor cursor seam (decision P3). The component palette and any later insert
2
+ // control talk to MarkdownEditor, never to Carta directly, so a swap to a different editing
3
+ // engine is contained to this file. Verified against carta-md@4.11: `input.getSelection()` and
4
+ // `input.insertAt(pos, text)` are public on the InputEnhancer.
5
+ /**
6
+ * Wrap a Carta instance as a MarkdownEditor. Takes a getter (not the instance) because the
7
+ * EditPage component creates the Carta instance once and `carta.input` is only populated after
8
+ * the editor mounts; reading it lazily at call time avoids capturing an undefined `input`.
9
+ */
10
+ export function cartaEditor(getCarta) {
11
+ return {
12
+ insertComponent(template) {
13
+ const input = getCarta().input;
14
+ if (!input)
15
+ return; // editor not mounted yet; nothing to insert into
16
+ const { start } = input.getSelection();
17
+ input.insertAt(start, template);
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,3 @@
1
+ /** A frontmatter date value (Date or string) to the `YYYY-MM-DD` an `<input type="date">` expects. */
2
+ export declare function dateInputValue(value: unknown): string;
3
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../src/lib/frontmatter.ts"],"names":[],"mappings":"AAMA,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASrD"}
@@ -0,0 +1,16 @@
1
+ // cairn-core: coerce a frontmatter value to the YYYY-MM-DD string an <input type="date"> wants.
2
+ // gray-matter parses an unquoted YAML date (date: 2026-05-14) into a JS Date, so a string-only
3
+ // read leaves the date input empty and drops the date on save. This normalizes a Date or an
4
+ // ISO-ish string to YYYY-MM-DD. A parsed YAML date is UTC midnight, so slicing the ISO string
5
+ // avoids a local-timezone shift. Internal (not re-exported from the barrel), like utils.ts.
6
+ /** A frontmatter date value (Date or string) to the `YYYY-MM-DD` an `<input type="date">` expects. */
7
+ export function dateInputValue(value) {
8
+ if (value instanceof Date) {
9
+ return Number.isNaN(value.getTime()) ? '' : value.toISOString().slice(0, 10);
10
+ }
11
+ if (typeof value === 'string') {
12
+ const match = value.match(/^\d{4}-\d{2}-\d{2}/);
13
+ return match ? match[0] : '';
14
+ }
15
+ return '';
16
+ }
package/dist/index.d.ts CHANGED
@@ -3,5 +3,7 @@ export * from './github';
3
3
  export * from './carta';
4
4
  export * from './content';
5
5
  export * from './adapter';
6
+ export * from './slug';
6
7
  export * from './render';
8
+ export * from './nav';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAEA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAEA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC"}
package/dist/index.js CHANGED
@@ -5,4 +5,6 @@ export * from './github';
5
5
  export * from './carta';
6
6
  export * from './content';
7
7
  export * from './adapter';
8
+ export * from './slug';
8
9
  export * from './render';
10
+ export * from './nav';
package/dist/nav.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ /** One navigation node. `url` omitted/empty is a label-only grouping header; `children` omitted is a leaf. */
2
+ export interface NavNode {
3
+ label: string;
4
+ url?: string;
5
+ children?: NavNode[];
6
+ }
7
+ /** Total node cap across the whole tree, a guard against a runaway payload. */
8
+ export declare const MAX_NAV_NODES = 200;
9
+ export declare class NavValidationError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ /**
13
+ * Validate and normalize an untrusted value into a NavNode[]: arrays only, non-empty labels,
14
+ * depth within `maxDepth` (1 = flat), bounded node count, and only the three known keys kept.
15
+ * Throws NavValidationError on any violation. Used by `navSave` before writing.
16
+ */
17
+ export declare function validateNavTree(value: unknown, maxDepth: number): NavNode[];
18
+ /**
19
+ * Shape of the YAML site-config file. Unknown keys are ignored so the file can grow without
20
+ * an engine change. Read at build time by the public site.
21
+ */
22
+ export interface SiteConfig {
23
+ siteName: string;
24
+ description?: string;
25
+ author?: string;
26
+ url?: string;
27
+ locale?: string;
28
+ /** Named navigation menus, each a NavNode[] (normalized by extractMenu). */
29
+ menus?: Record<string, unknown>;
30
+ email?: {
31
+ sender?: string;
32
+ senderName?: string;
33
+ };
34
+ footer?: {
35
+ copyrightName?: string;
36
+ };
37
+ settings?: {
38
+ feedMaxItems?: number;
39
+ homepageFeaturedCount?: number;
40
+ postTags?: string[];
41
+ [key: string]: unknown;
42
+ };
43
+ }
44
+ export declare class SiteConfigError extends Error {
45
+ constructor(message: string);
46
+ }
47
+ /** Parse the YAML site-config text into a typed object. Throws SiteConfigError on a malformed root. */
48
+ export declare function parseSiteConfig(raw: string): SiteConfig;
49
+ /** Extract one named menu from a parsed config and validate it. Returns [] when the menu is absent. */
50
+ export declare function extractMenu(config: SiteConfig, name: string, maxDepth: number): NavNode[];
51
+ /**
52
+ * Replace one named menu in the YAML site-config text and re-serialize, preserving every other
53
+ * top-level key (siteName, other menus, settings, ...). The `/admin/nav` editor commits the result.
54
+ * Parses into a Document so the rest of the file round-trips; YAML comments are not preserved
55
+ * (an accepted trade), but data keys are. A leaf node serializes without `url`/`children` keys.
56
+ */
57
+ export declare function setMenu(raw: string, name: string, tree: NavNode[]): string;
58
+ //# sourceMappingURL=nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../src/lib/nav.ts"],"names":[],"mappings":"AAOA,8GAA8G;AAC9G,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAuB3E;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,QAAQ,CAAC,EAAE;QACT,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,uGAAuG;AACvG,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAUvD;AAED,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAIzF;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAO1E"}
package/dist/nav.js ADDED
@@ -0,0 +1,86 @@
1
+ // cairn-core: the navigation tree. A menu lives in the site's git-committed `site.config.yaml`
2
+ // under `menus.<name>`, read at build time by the public layout and edited from `/admin/nav`,
3
+ // which commits the file back through the GitHub-App pipeline. The engine returns data only; each
4
+ // site renders the tree with its own header markup.
5
+ import { parse as parseYaml, parseDocument } from 'yaml';
6
+ /** Total node cap across the whole tree, a guard against a runaway payload. */
7
+ export const MAX_NAV_NODES = 200;
8
+ export class NavValidationError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'NavValidationError';
12
+ }
13
+ }
14
+ /**
15
+ * Validate and normalize an untrusted value into a NavNode[]: arrays only, non-empty labels,
16
+ * depth within `maxDepth` (1 = flat), bounded node count, and only the three known keys kept.
17
+ * Throws NavValidationError on any violation. Used by `navSave` before writing.
18
+ */
19
+ export function validateNavTree(value, maxDepth) {
20
+ let count = 0;
21
+ function walk(nodes, depth) {
22
+ if (!Array.isArray(nodes))
23
+ throw new NavValidationError('Navigation must be a list of items');
24
+ if (depth > maxDepth)
25
+ throw new NavValidationError(`Navigation is nested deeper than ${maxDepth} levels`);
26
+ return nodes.map((raw) => {
27
+ if (typeof raw !== 'object' || raw === null)
28
+ throw new NavValidationError('Each item must be an object');
29
+ const item = raw;
30
+ const label = typeof item.label === 'string' ? item.label.trim() : '';
31
+ if (!label)
32
+ throw new NavValidationError('Each item needs a label');
33
+ if (++count > MAX_NAV_NODES)
34
+ throw new NavValidationError('Too many navigation items');
35
+ const node = { label };
36
+ if (typeof item.url === 'string' && item.url.trim())
37
+ node.url = item.url.trim();
38
+ if (item.children !== undefined) {
39
+ const children = walk(item.children, depth + 1);
40
+ if (children.length)
41
+ node.children = children;
42
+ }
43
+ return node;
44
+ });
45
+ }
46
+ return walk(value, 1);
47
+ }
48
+ export class SiteConfigError extends Error {
49
+ constructor(message) {
50
+ super(message);
51
+ this.name = 'SiteConfigError';
52
+ }
53
+ }
54
+ /** Parse the YAML site-config text into a typed object. Throws SiteConfigError on a malformed root. */
55
+ export function parseSiteConfig(raw) {
56
+ const parsed = parseYaml(raw);
57
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
58
+ throw new SiteConfigError('Site config must be a YAML mapping');
59
+ }
60
+ const { siteName } = parsed;
61
+ if (typeof siteName !== 'string' || !siteName.trim()) {
62
+ throw new SiteConfigError('Site config needs a siteName');
63
+ }
64
+ return parsed;
65
+ }
66
+ /** Extract one named menu from a parsed config and validate it. Returns [] when the menu is absent. */
67
+ export function extractMenu(config, name, maxDepth) {
68
+ const menu = config.menus?.[name];
69
+ if (menu === undefined)
70
+ return [];
71
+ return validateNavTree(menu, maxDepth);
72
+ }
73
+ /**
74
+ * Replace one named menu in the YAML site-config text and re-serialize, preserving every other
75
+ * top-level key (siteName, other menus, settings, ...). The `/admin/nav` editor commits the result.
76
+ * Parses into a Document so the rest of the file round-trips; YAML comments are not preserved
77
+ * (an accepted trade), but data keys are. A leaf node serializes without `url`/`children` keys.
78
+ */
79
+ export function setMenu(raw, name, tree) {
80
+ const doc = parseDocument(raw);
81
+ if (doc.get('siteName') === undefined) {
82
+ throw new SiteConfigError('Site config must be a mapping with a siteName');
83
+ }
84
+ doc.setIn(['menus', name], tree);
85
+ return doc.toString();
86
+ }
package/dist/slug.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Lowercase a title into a filename-safe slug stem.
3
+ * Apostrophes are dropped so "Geoff's" becomes "geoffs" (no spurious hyphen).
4
+ * All other non-alphanumeric runs become a single hyphen; leading/trailing hyphens are trimmed.
5
+ */
6
+ export declare function slugify(title: string): string;
7
+ //# sourceMappingURL=slug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../src/lib/slug.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7C"}
package/dist/slug.js ADDED
@@ -0,0 +1,15 @@
1
+ // cairn-core: derive a filename-safe slug stem from a human title, for the create-entry form.
2
+ // The admin is filename-based (Pass E): this produces the editable stem an author can adjust,
3
+ // matching the server-side SLUG_RE (lowercase alphanumerics and internal hyphens). Pure.
4
+ /**
5
+ * Lowercase a title into a filename-safe slug stem.
6
+ * Apostrophes are dropped so "Geoff's" becomes "geoffs" (no spurious hyphen).
7
+ * All other non-alphanumeric runs become a single hyphen; leading/trailing hyphens are trimmed.
8
+ */
9
+ export function slugify(title) {
10
+ return title
11
+ .toLowerCase()
12
+ .replace(/'/g, '')
13
+ .replace(/[^a-z0-9]+/g, '-')
14
+ .replace(/^-+|-+$/g, '');
15
+ }
@@ -1,6 +1,6 @@
1
1
  import type { CairnUser } from '../auth/guard';
2
- import { type RepoFile } from '../github';
3
2
  import { type CairnAdapter, type CairnField } from '../adapter';
3
+ import { type NavNode } from '../nav';
4
4
  /** The `platform.env` bindings the content routes read. All optional; the handlers guard. */
5
5
  export interface AdminEnv {
6
6
  GITHUB_APP_ID?: string;
@@ -12,17 +12,31 @@ interface PlatformEvent {
12
12
  env?: AdminEnv;
13
13
  };
14
14
  }
15
+ /** A collection reduced to what the sidebar nav needs (no plugin graph crosses to the client). */
16
+ export interface NavCollection {
17
+ type: string;
18
+ label: string;
19
+ }
15
20
  export interface AdminLayoutData {
16
21
  user: CairnUser | null;
17
22
  siteName: string;
18
23
  pathname: string;
24
+ collections: NavCollection[];
25
+ /** Managed menus (name+label only) so the shell can show a Navigation entry. */
26
+ navMenus: {
27
+ name: string;
28
+ label: string;
29
+ }[];
30
+ /** Whether the viewer may manage navigation (gates the Navigation nav entry). */
31
+ canManageNav: boolean;
19
32
  }
20
33
  /**
21
- * Branding + session for every admin page. `siteName` flows from the adapter without pulling
22
- * its plugin graph into client bundles; the import stays server-side in the layout load.
23
- * `pathname` lets the shared shell highlight the active nav item without a `$app/*` import
24
- * (those kit virtual modules have no types outside a kit app, so they can't live in the
25
- * package); reading `event.url` here also opts the layout load into rerunning on navigation.
34
+ * Branding, session, and collection nav for every admin page. `siteName` and the collection
35
+ * list flow from the adapter without pulling its plugin graph into client bundles (the import
36
+ * stays server-side in the layout load; only `{type,label}` crosses). `pathname` lets the
37
+ * shared shell highlight the active nav item without a `$app/*` import (those kit virtual
38
+ * modules have no types outside a kit app); reading `event.url` also opts the layout load into
39
+ * rerunning on navigation, keeping the active class correct.
26
40
  */
27
41
  export declare function adminLayoutLoad(event: {
28
42
  locals: {
@@ -30,20 +44,66 @@ export declare function adminLayoutLoad(event: {
30
44
  };
31
45
  url: URL;
32
46
  }, adapter: CairnAdapter): AdminLayoutData;
33
- export interface AdminCollectionList {
47
+ /**
48
+ * The `/admin` index has no content of its own now that each collection is its own page; send
49
+ * the editor straight to the first collection's entries list (a Sveltia-style landing).
50
+ */
51
+ export declare function adminIndexRedirect(adapter: CairnAdapter): never;
52
+ /** One entry row: id (filename stem), display title, optional date, draft flag. */
53
+ export interface CollectionEntry {
54
+ id: string;
55
+ path: string;
56
+ title: string;
57
+ date: string | null;
58
+ draft: boolean;
59
+ }
60
+ export interface CollectionListData {
34
61
  type: string;
35
62
  label: string;
36
- files: RepoFile[];
63
+ kind: 'page' | 'story';
64
+ entries: CollectionEntry[];
65
+ /** Set when the directory listing itself failed (rate limit, network). */
37
66
  error?: string;
67
+ /** A create-flow error bounced back via `?error=` (an invalid or taken slug). */
68
+ formError: string | null;
69
+ /** Whether the viewer may create an entry in this collection (page-create is owner-only). */
70
+ canCreate: boolean;
38
71
  }
39
- /** List every collection's markdown files. A failed listing degrades to an inline error. */
40
- export declare function adminListLoad(event: PlatformEvent, adapter: CairnAdapter): Promise<{
41
- collections: AdminCollectionList[];
42
- }>;
72
+ /**
73
+ * List one collection's entries, reading each file's frontmatter for the display title, date,
74
+ * and draft badge. Reads run in parallel; a single failed read degrades that row to the slug
75
+ * (rather than failing the page), and a failed directory listing returns an inline `error`.
76
+ * Collections are small here; the 1,000-entry / Git-Trees sharding concern is risk #11, deferred.
77
+ */
78
+ export declare function collectionListLoad(event: PlatformEvent & {
79
+ params: {
80
+ collection: string;
81
+ };
82
+ url: URL;
83
+ locals: {
84
+ user: CairnUser | null;
85
+ };
86
+ }, adapter: CairnAdapter): Promise<CollectionListData>;
87
+ /**
88
+ * The "New entry" form action. Validates the requested slug, rejects one that already exists,
89
+ * then redirects into the editor in create mode (`?new=1`, where `editLoad` serves a blank
90
+ * document and `saveCommit`'s create path commits a new file). cairn is filename-based, so the
91
+ * slug is the filename stem the author types; a title-driven auto-slug is a later (Pass K) concern.
92
+ */
93
+ export declare function createEntry(event: PlatformEvent & {
94
+ params: {
95
+ collection: string;
96
+ };
97
+ locals: {
98
+ user: CairnUser | null;
99
+ };
100
+ request: Request;
101
+ }, adapter: CairnAdapter): Promise<never>;
43
102
  export interface EditData {
44
103
  type: string;
45
104
  id: string;
46
105
  label: string;
106
+ kind: 'page' | 'story';
47
107
  fields: CairnField[];
48
108
  path: string;
49
109
  body: string;
@@ -51,6 +111,8 @@ export interface EditData {
51
111
  title: string;
52
112
  saved: boolean;
53
113
  error: string | null;
114
+ /** True when editing a not-yet-committed new entry (reached via `?new=1`). */
115
+ isNew: boolean;
54
116
  }
55
117
  export declare function editLoad(event: PlatformEvent & {
56
118
  params: {
@@ -65,6 +127,34 @@ export declare function saveCommit(event: PlatformEvent & {
65
127
  user: CairnUser | null;
66
128
  };
67
129
  }, adapter: CairnAdapter): Promise<never>;
130
+ /** A page the picker can insert: its display label and the URL the nav item points at. */
131
+ export interface NavPageOption {
132
+ label: string;
133
+ url: string;
134
+ }
135
+ export interface NavLoadData {
136
+ menu: {
137
+ name: string;
138
+ label: string;
139
+ maxDepth: number;
140
+ };
141
+ tree: NavNode[];
142
+ pages: NavPageOption[];
143
+ saved: boolean;
144
+ error: string | null;
145
+ }
146
+ export declare function navLoad(event: PlatformEvent & {
147
+ locals: {
148
+ user: CairnUser | null;
149
+ };
150
+ url: URL;
151
+ }, adapter: CairnAdapter): Promise<NavLoadData>;
152
+ export declare function navSave(event: PlatformEvent & {
153
+ locals: {
154
+ user: CairnUser | null;
155
+ };
156
+ request: Request;
157
+ }, adapter: CairnAdapter): Promise<never>;
68
158
  export interface HealthData {
69
159
  ok: boolean;
70
160
  checks: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAOL,KAAK,QAAQ,EACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAuC,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAErG,6FAA6F;AAC7F,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,QAAQ,CAAA;KAAE,CAAC;CAC/B;AA2BD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,EACvD,OAAO,EAAE,YAAY,GACpB,eAAe,CAEjB;AAID,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4FAA4F;AAC5F,wBAAsB,aAAa,CACjC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,WAAW,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC,CAajD;AAID,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,aAAa,GAAG;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,EACzE,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,QAAQ,CAAC,CAyBnB;AAID,wBAAsB,UAAU,CAC9B,KAAK,EAAE,aAAa,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,EAC/E,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,KAAK,CAAC,CAoDhB;AAID,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE;QAAE,gBAAgB,EAAE;YAAE,EAAE,EAAE,OAAO,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CAChE;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAS1E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/index.ts"],"names":[],"mappings":"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"}