@glw907/cairn-cms 0.4.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 (114) hide show
  1. package/README.md +4 -4
  2. package/dist/adapter.d.ts +34 -1
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/auth/capabilities.d.ts +7 -0
  5. package/dist/auth/capabilities.d.ts.map +1 -0
  6. package/dist/auth/capabilities.js +26 -0
  7. package/dist/auth/config.d.ts +9 -9
  8. package/dist/auth/config.d.ts.map +1 -1
  9. package/dist/auth/config.js +5 -5
  10. package/dist/auth/guard.d.ts +1 -1
  11. package/dist/auth/guard.d.ts.map +1 -1
  12. package/dist/auth/guard.js +2 -2
  13. package/dist/auth/index.d.ts +1 -0
  14. package/dist/auth/index.d.ts.map +1 -1
  15. package/dist/auth/index.js +1 -0
  16. package/dist/carta.d.ts +1 -1
  17. package/dist/carta.d.ts.map +1 -1
  18. package/dist/components/AdminLayout.svelte +74 -18
  19. package/dist/components/AdminLayout.svelte.d.ts +9 -0
  20. package/dist/components/AdminLayout.svelte.d.ts.map +1 -1
  21. package/dist/components/CollectionList.svelte +96 -0
  22. package/dist/components/CollectionList.svelte.d.ts +8 -0
  23. package/dist/components/CollectionList.svelte.d.ts.map +1 -0
  24. package/dist/components/ComponentPalette.svelte +34 -0
  25. package/dist/components/ComponentPalette.svelte.d.ts +9 -0
  26. package/dist/components/ComponentPalette.svelte.d.ts.map +1 -0
  27. package/dist/components/ConfirmPage.svelte +2 -2
  28. package/dist/components/EditPage.svelte +69 -31
  29. package/dist/components/EditPage.svelte.d.ts +2 -0
  30. package/dist/components/EditPage.svelte.d.ts.map +1 -1
  31. package/dist/components/LoginPage.svelte +5 -5
  32. package/dist/components/NavTree.svelte +128 -0
  33. package/dist/components/NavTree.svelte.d.ts +8 -0
  34. package/dist/components/NavTree.svelte.d.ts.map +1 -0
  35. package/dist/components/index.d.ts +3 -1
  36. package/dist/components/index.d.ts.map +1 -1
  37. package/dist/components/index.js +3 -1
  38. package/dist/editor.d.ts +25 -0
  39. package/dist/editor.d.ts.map +1 -0
  40. package/dist/editor.js +20 -0
  41. package/dist/email.js +4 -4
  42. package/dist/frontmatter.d.ts +3 -0
  43. package/dist/frontmatter.d.ts.map +1 -0
  44. package/dist/frontmatter.js +16 -0
  45. package/dist/github.d.ts +22 -2
  46. package/dist/github.d.ts.map +1 -1
  47. package/dist/github.js +40 -5
  48. package/dist/index.d.ts +3 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +3 -0
  51. package/dist/nav.d.ts +58 -0
  52. package/dist/nav.d.ts.map +1 -0
  53. package/dist/nav.js +86 -0
  54. package/dist/render/glyph.d.ts +6 -0
  55. package/dist/render/glyph.d.ts.map +1 -0
  56. package/dist/render/glyph.js +5 -0
  57. package/dist/render/index.d.ts +6 -0
  58. package/dist/render/index.d.ts.map +1 -0
  59. package/dist/render/index.js +8 -0
  60. package/dist/render/pipeline.d.ts +16 -0
  61. package/dist/render/pipeline.d.ts.map +1 -0
  62. package/dist/render/pipeline.js +29 -0
  63. package/dist/render/registry.d.ts +28 -0
  64. package/dist/render/registry.d.ts.map +1 -0
  65. package/dist/render/registry.js +11 -0
  66. package/dist/render/rehype-dispatch.d.ts +24 -0
  67. package/dist/render/rehype-dispatch.d.ts.map +1 -0
  68. package/dist/render/rehype-dispatch.js +86 -0
  69. package/dist/render/remark-directives.d.ts +4 -0
  70. package/dist/render/remark-directives.d.ts.map +1 -0
  71. package/dist/render/remark-directives.js +74 -0
  72. package/dist/slug.d.ts +7 -0
  73. package/dist/slug.d.ts.map +1 -0
  74. package/dist/slug.js +15 -0
  75. package/dist/sveltekit/index.d.ts +118 -13
  76. package/dist/sveltekit/index.d.ts.map +1 -1
  77. package/dist/sveltekit/index.js +250 -24
  78. package/dist/utils.d.ts +1 -1
  79. package/dist/utils.d.ts.map +1 -1
  80. package/dist/utils.js +2 -2
  81. package/package.json +20 -3
  82. package/src/lib/adapter.ts +37 -3
  83. package/src/lib/auth/capabilities.ts +35 -0
  84. package/src/lib/auth/config.ts +6 -6
  85. package/src/lib/auth/guard.ts +3 -3
  86. package/src/lib/auth/index.ts +1 -0
  87. package/src/lib/carta.ts +2 -2
  88. package/src/lib/components/AdminLayout.svelte +74 -18
  89. package/src/lib/components/CollectionList.svelte +96 -0
  90. package/src/lib/components/ComponentPalette.svelte +34 -0
  91. package/src/lib/components/ConfirmPage.svelte +2 -2
  92. package/src/lib/components/EditPage.svelte +69 -31
  93. package/src/lib/components/LoginPage.svelte +5 -5
  94. package/src/lib/components/NavTree.svelte +128 -0
  95. package/src/lib/components/index.ts +3 -1
  96. package/src/lib/editor.ts +38 -0
  97. package/src/lib/email.ts +4 -4
  98. package/src/lib/frontmatter.ts +17 -0
  99. package/src/lib/github.ts +38 -6
  100. package/src/lib/index.ts +3 -0
  101. package/src/lib/nav.ts +117 -0
  102. package/src/lib/render/glyph.ts +14 -0
  103. package/src/lib/render/index.ts +8 -0
  104. package/src/lib/render/pipeline.ts +37 -0
  105. package/src/lib/render/registry.ts +36 -0
  106. package/src/lib/render/rehype-dispatch.ts +97 -0
  107. package/src/lib/render/remark-directives.ts +71 -0
  108. package/src/lib/slug.ts +16 -0
  109. package/src/lib/sveltekit/index.ts +355 -37
  110. package/src/lib/utils.ts +2 -2
  111. package/dist/components/AdminList.svelte +0 -33
  112. package/dist/components/AdminList.svelte.d.ts +0 -10
  113. package/dist/components/AdminList.svelte.d.ts.map +0 -1
  114. package/src/lib/components/AdminList.svelte +0 -33
package/README.md CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  An embedded, **magic-link**, GitHub-committing CMS for SvelteKit + Cloudflare sites.
4
4
  Non-technical authors log in by email (no GitHub account, no password), edit **raw
5
- markdown** in a [Carta](https://github.com/BearToCode/carta) editor, and save which
5
+ markdown** in a [Carta](https://github.com/BearToCode/carta) editor, and save. Each save
6
6
  commits to `main` via a **GitHub App** (committer = `cairn-cms[bot]`, author = the editor)
7
7
  and auto-deploys.
8
8
 
9
9
  It is **design-agnostic**: each consumer site supplies an adapter (collections, slug
10
10
  convention, frontmatter schema, and its own `renderPreview(md)`), so the same engine drives
11
- sites with completely different markdown pipelines e.g. [ecnordic.ski](https://ecnordic.ski)
12
- (remark→rehype directive pipeline) and [907.life](https://907.life) (plain `remark-html`).
11
+ sites with completely different markdown pipelines (e.g. [ecnordic.ski](https://ecnordic.ski)
12
+ (remark→rehype directive pipeline) and [907.life](https://907.life) (plain `remark-html`)).
13
13
 
14
14
  ## Status
15
15
 
16
- **`0.4.x` auth on [better-auth](https://better-auth.com); API not yet frozen.** The core was
16
+ **`0.4.x`: auth on [better-auth](https://better-auth.com); API not yet frozen.** The core was
17
17
  built *inside ecnordic.ski first* (the richer proving ground) with the cairn-core ↔ site-adapter
18
18
  seams designed in from day one, then extracted into this package and validated on a second design
19
19
  (907.life). Editor auth runs on **better-auth (Cloudflare D1 + magic-link)** behind a scanner-safe
package/dist/adapter.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { PreviewPlugins } from './carta';
2
2
  import type { RepoRef } from './github';
3
+ import type { ComponentRegistry } from './render';
3
4
  interface FieldBase {
4
5
  /** Frontmatter key and form input name. */
5
6
  name: string;
@@ -34,6 +35,13 @@ export interface CairnCollection {
34
35
  /** Route `[type]` segment and list key, e.g. `posts`. */
35
36
  type: string;
36
37
  label: string;
38
+ /**
39
+ * Editing shape. `story` (the default when absent) is a dated feed entry; `page` is a
40
+ * navigation-placed entry with a path-like slug and no date emphasis. Drives the create
41
+ * form and the editor header. Never gates editing capability: the palette and toolbar are
42
+ * available to both. (Pass K, R4.)
43
+ */
44
+ kind?: 'page' | 'story';
37
45
  /** Repo-relative folder holding the collection's markdown files. */
38
46
  dir: string;
39
47
  /** Editor form fields, rendered in order. */
@@ -41,16 +49,41 @@ export interface CairnCollection {
41
49
  /** Validate raw frontmatter (from the form) into the on-disk object, throwing on error. */
42
50
  validate(data: Record<string, unknown>, source: string): object;
43
51
  }
52
+ /** A managed navigation menu, read from and committed to the site's YAML config file. */
53
+ export interface NavMenuConfig {
54
+ /** Repo-relative path to the site-config YAML, e.g. 'src/lib/site.config.yaml'. */
55
+ configPath: string;
56
+ /** Key within the file's `menus` map, e.g. 'primary'. */
57
+ menuName: string;
58
+ /** Sidebar/admin label for the menu. */
59
+ label: string;
60
+ /** Max nesting depth allowed in the editor (1 = flat). Defaults to 2. */
61
+ maxDepth?: number;
62
+ }
44
63
  export interface CairnAdapter {
45
64
  /** Branding + magic-link email copy. */
46
65
  siteName: string;
47
- /** From: address for magic-link email a domain-authenticated sender. */
66
+ /** From: address for magic-link email (must be a domain-authenticated sender). */
48
67
  sender: string;
49
68
  /** The repository the admin reads content from and commits to. */
50
69
  backend: RepoRef;
51
70
  /** Site plugin set for the Carta preview (parity with the live render). */
52
71
  preview: PreviewPlugins;
53
72
  collections: CairnCollection[];
73
+ /**
74
+ * The site's component registry: the single declaration of its directive
75
+ * components (R10a). Rendering parity already flows through `preview`; this
76
+ * exposes the same registry so the editor's insert-component palette can read
77
+ * `registry.defs`. Optional: a site with no rich components (e.g. 907.life) may
78
+ * omit it or supply an empty registry.
79
+ */
80
+ registry?: ComponentRegistry;
81
+ /**
82
+ * The navigation menu this site manages from `/admin/nav` (R3/Pass L2). The menu lives in the
83
+ * site's git-committed YAML config (read at build time by the layout, committed back by the
84
+ * editor). Omit to hide the nav surface, the same opt-in shape as `registry`.
85
+ */
86
+ navMenu?: NavMenuConfig;
54
87
  }
55
88
  /** Look up a collection by its route segment, or undefined if the segment is unknown. */
56
89
  export declare function findCollection(adapter: CairnAdapter, type: string): CairnCollection | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/lib/adapter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,UAAU,SAAS;IACjB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,IAAI,EAAE,SAAS,CAAC;CACjB;AACD,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AACD,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,aAAa,GACb,YAAY,GACZ,SAAS,GACT,aAAa,CAAC;AAElB,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACjE;AAED,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC;AAED,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE/F;AAED,0FAA0F;AAC1F,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAC3B,IAAI,EAAE,QAAQ,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/lib/adapter.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,UAAU,SAAS;IACjB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,IAAI,EAAE,SAAS,CAAC;CACjB;AACD,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AACD,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,aAAa,GACb,YAAY,GACZ,SAAS,GACT,aAAa,CAAC;AAElB,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACjE;AAED,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE/F;AAED,0FAA0F;AAC1F,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAC3B,IAAI,EAAE,QAAQ,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB"}
@@ -0,0 +1,7 @@
1
+ import type { CairnUser } from './guard';
2
+ export type Capability = 'story:create' | 'story:edit' | 'page:edit' | 'page:create' | 'nav:manage' | 'user:manage';
3
+ /** Does this user hold the capability? A signed-out (null) user holds nothing. */
4
+ export declare function can(user: CairnUser | null, cap: Capability): boolean;
5
+ /** Assert the capability for a route load/action: 401 when signed out, 403 when under-privileged. */
6
+ export declare function requireCapability(user: CairnUser | null, cap: Capability): CairnUser;
7
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/lib/auth/capabilities.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,UAAU,GAClB,cAAc,GACd,YAAY,GACZ,WAAW,GACX,aAAa,GACb,YAAY,GACZ,aAAa,CAAC;AASlB,kFAAkF;AAClF,wBAAgB,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,EAAE,GAAG,EAAE,UAAU,GAAG,SAAS,CAIpF"}
@@ -0,0 +1,26 @@
1
+ // cairn-core: capability checks. Management surfaces gate on a capability, not on a role name,
2
+ // so the two-tier owner/editor model can grow finer capabilities (and a future role) additively.
3
+ // Creating a page and changing the nav are structural acts, so they sit with owner; editing a
4
+ // page's content and running the story feed are everyday editor work.
5
+ import { error } from '@sveltejs/kit';
6
+ // One source of truth. `'all'` means every capability; otherwise the explicit grant list. A future
7
+ // `manager` role is one more row here, no call-site changes.
8
+ const CAPS_BY_ROLE = {
9
+ owner: 'all',
10
+ editor: ['story:create', 'story:edit', 'page:edit'],
11
+ };
12
+ /** Does this user hold the capability? A signed-out (null) user holds nothing. */
13
+ export function can(user, cap) {
14
+ if (!user)
15
+ return false;
16
+ const grants = CAPS_BY_ROLE[user.role];
17
+ return grants === 'all' || grants.includes(cap);
18
+ }
19
+ /** Assert the capability for a route load/action: 401 when signed out, 403 when under-privileged. */
20
+ export function requireCapability(user, cap) {
21
+ if (!user)
22
+ throw error(401, 'Not signed in');
23
+ if (!can(user, cap))
24
+ throw error(403, 'You do not have permission to do that');
25
+ return user;
26
+ }
@@ -17,16 +17,16 @@ export interface AuthBranding {
17
17
  /** The `From:` address used when sending magic-link emails. */
18
18
  sender: string;
19
19
  }
20
- /** The drizzle adapter result `betterAuth` consumes — the same provider/schema everywhere. */
20
+ /** The drizzle adapter result `betterAuth` consumes (same provider/schema everywhere). */
21
21
  type DrizzleDb = Parameters<typeof drizzleAdapter>[0];
22
22
  /**
23
23
  * The shared better-auth config. Kept separate from `createAuth` so the test harness can run
24
24
  * the EXACT plugin set (allowlist semantics, expiry, POST-confirm send) over an in-memory
25
- * SQLite instead of D1. `disableSignUp:true` makes the `user` table the editor allowlist
25
+ * SQLite instead of D1. `disableSignUp:true` makes the `user` table the editor allowlist:
26
26
  * magic-link never auto-creates, so the only way in is the owner-gated admin `createUser`
27
27
  * (see auth/admins.ts). `adminRoles:['owner']` lets owners (not the default `admin` role)
28
28
  * drive the admin API. Tokens are stored hashed and consumed atomically on first verify
29
- * (better-auth GHSA-hc7v-rggr-4hvx) single-use by construction (C1).
29
+ * (better-auth GHSA-hc7v-rggr-4hvx), single-use by construction (C1).
30
30
  */
31
31
  export declare function buildAuth(opts: {
32
32
  database: DrizzleDb;
@@ -185,7 +185,7 @@ export declare function buildAuth(opts: {
185
185
  userId: string;
186
186
  expiresAt: Date;
187
187
  token: string;
188
- ipAddress? /** The `From:` address used when sending magic-link emails. */: string | null | undefined;
188
+ ipAddress?: string | null | undefined;
189
189
  userAgent?: string | null | undefined;
190
190
  } & Record<string, unknown>, ctx: import("better-auth").GenericEndpointContext | null): Promise<void>;
191
191
  };
@@ -956,8 +956,8 @@ export declare function buildAuth(opts: {
956
956
  $Infer: {
957
957
  body: {
958
958
  permissions: {
959
- readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "get" | "update")[] | undefined;
960
- readonly session?: ("list" | "delete" | "revoke")[] | undefined;
959
+ readonly user?: ("delete" | "list" | "create" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "set-password" | "get" | "update")[] | undefined;
960
+ readonly session?: ("delete" | "list" | "revoke")[] | undefined;
961
961
  };
962
962
  } & {
963
963
  userId?: string | undefined;
@@ -1217,7 +1217,7 @@ export declare function createAuth(env: AuthEnv, branding: AuthBranding): import
1217
1217
  userId: string;
1218
1218
  expiresAt: Date;
1219
1219
  token: string;
1220
- ipAddress? /** The `From:` address used when sending magic-link emails. */: string | null | undefined;
1220
+ ipAddress?: string | null | undefined;
1221
1221
  userAgent?: string | null | undefined;
1222
1222
  } & Record<string, unknown>, ctx: import("better-auth").GenericEndpointContext | null): Promise<void>;
1223
1223
  };
@@ -1988,8 +1988,8 @@ export declare function createAuth(env: AuthEnv, branding: AuthBranding): import
1988
1988
  $Infer: {
1989
1989
  body: {
1990
1990
  permissions: {
1991
- readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "delete" | "set-password" | "get" | "update")[] | undefined;
1992
- readonly session?: ("list" | "delete" | "revoke")[] | undefined;
1991
+ readonly user?: ("delete" | "list" | "create" | "set-role" | "ban" | "impersonate" | "impersonate-admins" | "set-password" | "get" | "update")[] | undefined;
1992
+ readonly session?: ("delete" | "list" | "revoke")[] | undefined;
1993
1993
  };
1994
1994
  } & {
1995
1995
  userId?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/auth/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/auth/config.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  // cairn-core: the better-auth instance. Auth is engine code (engine-fat rule), so the whole
2
- // config Drizzle/D1 adapter, magic-link (POST-confirm-shaped send), admin roles — lives here.
2
+ // config lives here: Drizzle/D1 adapter, magic-link (POST-confirm-shaped send), admin roles.
3
3
  // Instantiated PER REQUEST in hooks.server.ts (the D1 binding is request-scoped); the factory
4
4
  // is cheap (no I/O at construction).
5
5
  import { betterAuth } from 'better-auth';
@@ -12,18 +12,18 @@ import { sendMagicLink } from '../email';
12
12
  import * as schema from './schema';
13
13
  // Two-tier roles on the admin plugin's access-control system: `owner` holds every admin
14
14
  // statement (manage editors, revoke sessions); `editor` holds none (content-only). `adminRoles`
15
- // must name a role defined here, so owner not the plugin's built-in `admin` is the gate.
15
+ // must name a role defined here, so owner (not the plugin's built-in `admin`) is the gate.
16
16
  const ac = createAccessControl(defaultStatements);
17
17
  const owner = ac.newRole(defaultStatements);
18
18
  const editor = ac.newRole({});
19
19
  /**
20
20
  * The shared better-auth config. Kept separate from `createAuth` so the test harness can run
21
21
  * the EXACT plugin set (allowlist semantics, expiry, POST-confirm send) over an in-memory
22
- * SQLite instead of D1. `disableSignUp:true` makes the `user` table the editor allowlist
22
+ * SQLite instead of D1. `disableSignUp:true` makes the `user` table the editor allowlist:
23
23
  * magic-link never auto-creates, so the only way in is the owner-gated admin `createUser`
24
24
  * (see auth/admins.ts). `adminRoles:['owner']` lets owners (not the default `admin` role)
25
25
  * drive the admin API. Tokens are stored hashed and consumed atomically on first verify
26
- * (better-auth GHSA-hc7v-rggr-4hvx) single-use by construction (C1).
26
+ * (better-auth GHSA-hc7v-rggr-4hvx), single-use by construction (C1).
27
27
  */
28
28
  export function buildAuth(opts) {
29
29
  return betterAuth({
@@ -40,7 +40,7 @@ export function buildAuth(opts) {
40
40
  sendMagicLink: async ({ email, token }, ctx) => {
41
41
  // Allowlist gate: better-auth always fires this callback (even for unknown emails, to
42
42
  // avoid enumeration) and only blocks user creation at verify. So gate the actual send
43
- // here never email a non-editor. The login UI shows neutral copy either way, so this
43
+ // here. Never email a non-editor. The login UI shows neutral copy either way, so this
44
44
  // leaks nothing; it just stops strangers receiving a dead link.
45
45
  const existing = await ctx?.context.internalAdapter.findUserByEmail(email);
46
46
  if (!existing?.user)
@@ -1,5 +1,5 @@
1
1
  import type { Auth } from './config';
2
- /** The session shape the whole admin reads layout, guards, content fns, manage-editors. */
2
+ /** The session shape the whole admin reads: layout, guards, content fns, manage-editors. */
3
3
  export interface CairnUser {
4
4
  id: string;
5
5
  email: string;
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/lib/auth/guard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAErC,6FAA6F;AAC7F,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC1B;AAED,gEAAgE;AAChE,wBAAsB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAKzF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAGhE;AAED,KAAK,YAAY,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,CAAC;AAE3E;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAa1E;AAED,4FAA4F;AAC5F,wBAAsB,OAAO,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQpG"}
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/lib/auth/guard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAErC,4FAA4F;AAC5F,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC1B;AAED,gEAAgE;AAChE,wBAAsB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAKzF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAGhE;AAED,KAAK,YAAY,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,CAAC;AAE3E;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAa1E;AAED,4FAA4F;AAC5F,wBAAsB,OAAO,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQpG"}
@@ -1,6 +1,6 @@
1
1
  // cairn-core: server-side auth helpers the site route shims delegate to. Each takes the
2
- // SvelteKit event (typed structurally, so the package never depends on a site's generated
3
- // `App.*` ambient types) plus the per-request `Auth` from `locals`.
2
+ // SvelteKit event, typed structurally so the package never depends on a site's generated
3
+ // `App.*` ambient types, plus the per-request `Auth` from `locals`.
4
4
  import { redirect } from '@sveltejs/kit';
5
5
  /** Read the better-auth session into a cairn user (or null). */
6
6
  export async function loadSession(auth, request) {
@@ -1,4 +1,5 @@
1
1
  export { createAuth, type Auth, type AuthEnv, type AuthBranding } from './config';
2
2
  export { loadSession, requireSession, confirmSignIn, signOut, type CairnUser } from './guard';
3
3
  export { adminsLoad, addAdmin, removeAdmin, setAdminRole, requireOwner, type AdminsData } from './admins';
4
+ export { can, requireCapability, type Capability } from './capabilities';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/auth/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/auth/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC1G,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
@@ -4,3 +4,4 @@
4
4
  export { createAuth } from './config';
5
5
  export { loadSession, requireSession, confirmSignIn, signOut } from './guard';
6
6
  export { adminsLoad, addAdmin, removeAdmin, setAdminRole, requireOwner } from './admins';
7
+ export { can, requireCapability } from './capabilities';
package/dist/carta.d.ts CHANGED
@@ -18,7 +18,7 @@ interface PreviewTransformer {
18
18
  * so this ordering reproduces render.ts exactly. Pure (no Carta) so it is unit-testable.
19
19
  */
20
20
  export declare function previewTransformers({ remarkPlugins, rehypePlugins }: PreviewPlugins): PreviewTransformer[];
21
- /** Minimal Options subset we populate avoids importing carta-md (Svelte re-exports). */
21
+ /** Minimal Options subset we populate (avoids importing carta-md, which re-exports Svelte components). */
22
22
  interface PreviewCartaOptions {
23
23
  sanitizer: false;
24
24
  rehypeOptions: {
@@ -1 +1 @@
1
- {"version":3,"file":"carta.d.ts","sourceRoot":"","sources":["../src/lib/carta.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B,oDAAoD;IACpD,aAAa,EAAE,SAAS,EAAE,CAAC;CAC5B;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,SAAS,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE,KAAK,IAAI,CAAC;CACpD;AAYD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,cAAc,GAAG,kBAAkB,EAAE,CAE1G;AAED,0FAA0F;AAC1F,UAAU,mBAAmB;IAC3B,SAAS,EAAE,KAAK,CAAC;IACjB,aAAa,EAAE;QAAE,kBAAkB,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,UAAU,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,kBAAkB,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,CAMhF"}
1
+ {"version":3,"file":"carta.d.ts","sourceRoot":"","sources":["../src/lib/carta.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B,oDAAoD;IACpD,aAAa,EAAE,SAAS,EAAE,CAAC;CAC5B;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,SAAS,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE,KAAK,IAAI,CAAC;CACpD;AAYD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,cAAc,GAAG,kBAAkB,EAAE,CAE1G;AAED,0GAA0G;AAC1G,UAAU,mBAAmB;IAC3B,SAAS,EAAE,KAAK,CAAC;IACjB,aAAa,EAAE;QAAE,kBAAkB,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,UAAU,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,kBAAkB,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,CAMhF"}
@@ -1,11 +1,7 @@
1
1
  <script lang="ts">
2
- // Neutral admin chrome, shared across sites so the tool looks identical everywhere (only the
3
- // adapter's siteName varies). When signed in it's a responsive DaisyUI drawer+navbar shell
4
- // (`drawer lg:drawer-open` sidebar pinned on desktop, slide-over + hamburger on mobile),
5
- // patterned on scosman/CMSaasStarter's `(admin)/(menu)` layout. The nav is data-driven and
6
- // role-gated, so a new surface is one entry in `nav` (plus its route + component). Signed out
7
- // (the login page lives under this layout) it falls back to a minimal centered shell.
8
- // Each site's `admin/+layout.svelte` is a one-line shim that forwards `data` + `children`.
2
+ // Neutral admin chrome shared across sites. Signed in: DaisyUI drawer+navbar shell (sidebar
3
+ // pinned on desktop, slide-over on mobile). Signed out: minimal centered shell. The
4
+ // `cairn-admin` class on both roots scopes the "Warm Stone" theme; see the style block.
9
5
  import type { Snippet } from 'svelte';
10
6
  import type { CairnUser } from '../auth';
11
7
 
@@ -13,7 +9,14 @@
13
9
  data,
14
10
  children,
15
11
  }: {
16
- data: { siteName: string; user: CairnUser | null; pathname: string };
12
+ data: {
13
+ siteName: string;
14
+ user: CairnUser | null;
15
+ pathname: string;
16
+ collections: { type: string; label: string }[];
17
+ navMenus: { name: string; label: string }[];
18
+ canManageNav: boolean;
19
+ };
17
20
  children: Snippet;
18
21
  } = $props();
19
22
 
@@ -22,17 +25,22 @@
22
25
  label: string;
23
26
  icon: Snippet;
24
27
  active: boolean;
25
- /** Owner-only surface hidden from regular editors. */
28
+ /** Owner-only surface; hidden from regular editors. */
26
29
  owner?: boolean;
27
30
  }
28
31
 
29
32
  const nav = $derived<NavItem[]>([
30
- {
31
- href: '/admin',
32
- label: 'Content',
33
+ ...data.collections.map((collection) => ({
34
+ href: `/admin/${collection.type}`,
35
+ label: collection.label,
33
36
  icon: contentIcon,
34
- active: data.pathname === '/admin' || data.pathname.startsWith('/admin/edit'),
35
- },
37
+ active:
38
+ data.pathname === `/admin/${collection.type}` ||
39
+ data.pathname.startsWith(`/admin/edit/${collection.type}/`),
40
+ })),
41
+ ...(data.canManageNav && data.navMenus.length
42
+ ? [{ href: '/admin/nav', label: 'Navigation', icon: navIcon, active: data.pathname.startsWith('/admin/nav') }]
43
+ : []),
36
44
  {
37
45
  href: '/admin/admins',
38
46
  label: 'Editors',
@@ -43,7 +51,7 @@
43
51
  ]);
44
52
  const visibleNav = $derived(nav.filter((item) => !item.owner || data.user?.role === 'owner'));
45
53
 
46
- // Close the slide-over after a nav tap on mobile (no-op on desktop where it's pinned open).
54
+ // Close the slide-over after a nav tap on mobile.
47
55
  function closeDrawer(): void {
48
56
  const toggle = document.getElementById('admin-drawer');
49
57
  if (toggle instanceof HTMLInputElement) toggle.checked = false;
@@ -64,16 +72,23 @@
64
72
  </svg>
65
73
  {/snippet}
66
74
 
75
+ {#snippet navIcon()}
76
+ <svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
77
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
78
+ d="M4 6h16M4 12h16M4 18h16" />
79
+ </svg>
80
+ {/snippet}
81
+
67
82
  <svelte:head>
68
83
  <meta name="robots" content="noindex, nofollow" />
69
84
  </svelte:head>
70
85
 
71
86
  {#if data.user}
72
- <div class="drawer min-h-screen bg-base-200 lg:drawer-open" data-pagefind-ignore>
87
+ <div class="cairn-admin drawer min-h-screen bg-base-200 lg:drawer-open" data-pagefind-ignore>
73
88
  <input id="admin-drawer" type="checkbox" class="drawer-toggle" />
74
89
 
75
90
  <div class="drawer-content">
76
- <!-- Mobile top bar the desktop sidebar replaces this at lg. -->
91
+ <!-- Mobile top bar; the desktop sidebar replaces this at lg. -->
77
92
  <div class="navbar bg-base-100 lg:hidden">
78
93
  <div class="flex-1">
79
94
  <span class="px-2 text-xl font-bold">{data.siteName} CMS</span>
@@ -122,9 +137,50 @@
122
137
  </div>
123
138
  {:else}
124
139
  <!-- Signed out (login page): no nav, just a centered surface. -->
125
- <div class="min-h-screen bg-base-200" data-pagefind-ignore>
140
+ <div class="cairn-admin min-h-screen bg-base-200" data-pagefind-ignore>
126
141
  <div class="mx-auto max-w-3xl px-4 py-8">
127
142
  {@render children()}
128
143
  </div>
129
144
  </div>
130
145
  {/if}
146
+
147
+ <style>
148
+ /* Warm Stone: a neutral, fully self-contained admin theme (R6), light-only. Overriding the
149
+ DaisyUI v5 tokens + font on this root re-skins the whole admin subtree by inheritance, so
150
+ the tool looks identical on every host regardless of the site's own theme. Values are OKLCH
151
+ (no hex/rgb, per the design-system rule). Warm-gray neutrals (hue ~75), violet accent. */
152
+ .cairn-admin {
153
+ color-scheme: light;
154
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
155
+
156
+ --color-base-100: oklch(98.5% 0.004 75);
157
+ --color-base-200: oklch(96% 0.005 75);
158
+ --color-base-300: oklch(92% 0.008 75);
159
+ --color-base-content: oklch(28% 0.012 75);
160
+
161
+ --color-primary: oklch(52% 0.20 293);
162
+ --color-primary-content: oklch(98% 0.012 293);
163
+ --color-secondary: oklch(45% 0.02 75);
164
+ --color-secondary-content: oklch(98% 0.004 75);
165
+ --color-accent: oklch(58% 0.16 300);
166
+ --color-accent-content: oklch(98% 0.012 300);
167
+ --color-neutral: oklch(32% 0.012 75);
168
+ --color-neutral-content: oklch(96% 0.004 75);
169
+
170
+ --color-info: oklch(60% 0.12 240);
171
+ --color-info-content: oklch(98% 0.01 240);
172
+ --color-success: oklch(58% 0.12 150);
173
+ --color-success-content: oklch(98% 0.01 150);
174
+ --color-warning: oklch(75% 0.15 70);
175
+ --color-warning-content: oklch(25% 0.02 70);
176
+ --color-error: oklch(58% 0.20 25);
177
+ --color-error-content: oklch(98% 0.01 25);
178
+
179
+ --radius-selector: 0.5rem;
180
+ --radius-field: 0.5rem;
181
+ --radius-box: 0.75rem;
182
+ --size-selector: 0.25rem;
183
+ --size-field: 0.25rem;
184
+ --border: 1px;
185
+ }
186
+ </style>
@@ -5,6 +5,15 @@ type $$ComponentProps = {
5
5
  siteName: string;
6
6
  user: CairnUser | null;
7
7
  pathname: string;
8
+ collections: {
9
+ type: string;
10
+ label: string;
11
+ }[];
12
+ navMenus: {
13
+ name: string;
14
+ label: string;
15
+ }[];
16
+ canManageNav: boolean;
8
17
  };
9
18
  children: Snippet;
10
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AdminLayout.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminLayout.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAWxC,KAAK,gBAAgB,GAAI;IACtB,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACrE,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAyHJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"AdminLayout.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminLayout.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAWxC,KAAK,gBAAgB,GAAI;IACtB,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC/C,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC5C,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;IACF,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAkIJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ // One collection's entries: a table (title, date, draft badge) linking into the editor, plus a
3
+ // collapsible "New entry" form. The author types a title; the slug stem derives from it (R4) and
4
+ // stays editable. A story collection also collects a date, which createEntry forwards so the new
5
+ // entry opens with its date set. Placeholders differ by kind. The shell (AdminLayout) owns the
6
+ // chrome and nav; this renders only the body.
7
+ import type { CollectionListData } from '../sveltekit';
8
+ import { slugify } from '../slug';
9
+
10
+ let { data }: { data: CollectionListData } = $props();
11
+
12
+ let title = $state('');
13
+ let slug = $state('');
14
+ let slugEdited = $state(false);
15
+
16
+ // Keep the slug in sync with the title until the author edits the slug directly.
17
+ function onTitleInput(value: string) {
18
+ title = value;
19
+ if (!slugEdited) slug = slugify(value);
20
+ }
21
+
22
+ const slugPlaceholder = $derived(data.kind === 'page' ? 'about-us' : '2026-05-my-entry');
23
+ </script>
24
+
25
+ <div class="flex items-center justify-between gap-4">
26
+ <h1 class="text-2xl font-bold">{data.label}</h1>
27
+ {#if data.canCreate}
28
+ <details class="dropdown dropdown-end">
29
+ <summary class="btn btn-primary btn-sm">New entry</summary>
30
+ <form
31
+ method="POST"
32
+ action="?/create"
33
+ class="dropdown-content z-10 mt-2 flex w-80 flex-col gap-2 rounded-box border border-base-300 bg-base-100 p-4 shadow"
34
+ >
35
+ <label class="flex flex-col gap-1">
36
+ <span class="text-sm font-medium">Title</span>
37
+ <input
38
+ type="text"
39
+ value={title}
40
+ oninput={(e) => onTitleInput(e.currentTarget.value)}
41
+ placeholder="A human title"
42
+ class="input w-full"
43
+ />
44
+ </label>
45
+
46
+ {#if data.kind === 'story'}
47
+ <label class="flex flex-col gap-1">
48
+ <span class="text-sm font-medium">Date</span>
49
+ <input type="date" name="date" class="input w-full" />
50
+ </label>
51
+ {/if}
52
+
53
+ <label class="flex flex-col gap-1">
54
+ <span class="text-sm font-medium">Slug</span>
55
+ <input
56
+ type="text"
57
+ name="id"
58
+ required
59
+ bind:value={slug}
60
+ oninput={() => (slugEdited = true)}
61
+ placeholder={slugPlaceholder}
62
+ pattern="[a-z0-9]([a-z0-9-]*[a-z0-9])?"
63
+ class="input w-full"
64
+ />
65
+ <span class="text-xs opacity-60">Lowercase letters, numbers, and hyphens. Becomes the filename.</span>
66
+ </label>
67
+
68
+ <button type="submit" class="btn btn-primary btn-sm">Create &amp; edit</button>
69
+ </form>
70
+ </details>
71
+ {/if}
72
+ </div>
73
+
74
+ {#if data.formError}
75
+ <div class="alert alert-error mt-4"><span>{data.formError}</span></div>
76
+ {/if}
77
+
78
+ {#if data.error}
79
+ <div class="alert alert-warning mt-6">Couldn't load {data.label.toLowerCase()}: {data.error}</div>
80
+ {:else if data.entries.length === 0}
81
+ <p class="mt-6 opacity-60">No entries yet.</p>
82
+ {:else}
83
+ <ul class="menu mt-6 rounded-box border border-base-300 bg-base-100 p-2">
84
+ {#each data.entries as entry (entry.path)}
85
+ <li>
86
+ <a href="/admin/edit/{data.type}/{entry.id}" class="flex items-center justify-between gap-3">
87
+ <span class="flex items-center gap-2">
88
+ <span>{entry.title}</span>
89
+ {#if entry.draft}<span class="badge badge-warning badge-sm">Draft</span>{/if}
90
+ </span>
91
+ {#if entry.date}<span class="text-xs opacity-60">{entry.date}</span>{/if}
92
+ </a>
93
+ </li>
94
+ {/each}
95
+ </ul>
96
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { CollectionListData } from '../sveltekit';
2
+ type $$ComponentProps = {
3
+ data: CollectionListData;
4
+ };
5
+ declare const CollectionList: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type CollectionList = ReturnType<typeof CollectionList>;
7
+ export default CollectionList;
8
+ //# sourceMappingURL=CollectionList.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollectionList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/CollectionList.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGtD,KAAK,gBAAgB,GAAI;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAiFvD,QAAA,MAAM,cAAc,sDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}