@glw907/cairn-cms 0.29.0 → 0.33.0

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 (59) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/components/AdminLayout.svelte +372 -44
  3. package/dist/components/AdminLayout.svelte.d.ts +5 -4
  4. package/dist/components/CairnLogo.svelte +28 -0
  5. package/dist/components/CairnLogo.svelte.d.ts +15 -0
  6. package/dist/components/ComponentForm.svelte +1 -1
  7. package/dist/components/ConceptList.svelte +240 -45
  8. package/dist/components/ConceptList.svelte.d.ts +12 -2
  9. package/dist/components/ConfirmPage.svelte +20 -3
  10. package/dist/components/EditPage.svelte +12 -7
  11. package/dist/components/LoginPage.svelte +27 -5
  12. package/dist/components/ManageEditors.svelte +8 -5
  13. package/dist/components/NavTree.svelte +2 -2
  14. package/dist/components/admin-icons.d.ts +13 -0
  15. package/dist/components/admin-icons.js +15 -0
  16. package/dist/components/cairn-admin.css +5516 -37
  17. package/dist/components/cairn-favicon.d.ts +2 -0
  18. package/dist/components/cairn-favicon.js +7 -0
  19. package/dist/components/chrome-guard.d.ts +9 -0
  20. package/dist/components/chrome-guard.js +55 -0
  21. package/dist/components/fonts/BricolageGrotesque-OFL.txt +93 -0
  22. package/dist/components/fonts/Figtree-OFL.txt +93 -0
  23. package/dist/components/fonts/bricolage-grotesque.woff2 +0 -0
  24. package/dist/components/fonts/figtree.woff2 +0 -0
  25. package/dist/index.d.ts +0 -2
  26. package/dist/index.js +4 -1
  27. package/dist/render/authoring.d.ts +3 -0
  28. package/dist/render/authoring.js +5 -0
  29. package/dist/render/registry.d.ts +2 -0
  30. package/dist/render/registry.js +15 -0
  31. package/dist/render/rehype-dispatch.d.ts +9 -6
  32. package/dist/render/rehype-dispatch.js +12 -6
  33. package/dist/render/remark-directives.js +1 -1
  34. package/dist/sveltekit/content-routes.d.ts +12 -1
  35. package/dist/sveltekit/content-routes.js +37 -13
  36. package/package.json +15 -2
  37. package/src/lib/components/AdminLayout.svelte +372 -44
  38. package/src/lib/components/CairnLogo.svelte +28 -0
  39. package/src/lib/components/ComponentForm.svelte +1 -1
  40. package/src/lib/components/ConceptList.svelte +240 -45
  41. package/src/lib/components/ConfirmPage.svelte +20 -3
  42. package/src/lib/components/EditPage.svelte +12 -7
  43. package/src/lib/components/LoginPage.svelte +27 -5
  44. package/src/lib/components/ManageEditors.svelte +8 -5
  45. package/src/lib/components/NavTree.svelte +2 -2
  46. package/src/lib/components/admin-icons.ts +15 -0
  47. package/src/lib/components/cairn-admin.css +162 -7
  48. package/src/lib/components/cairn-favicon.ts +9 -0
  49. package/src/lib/components/chrome-guard.ts +62 -0
  50. package/src/lib/components/fonts/BricolageGrotesque-OFL.txt +93 -0
  51. package/src/lib/components/fonts/Figtree-OFL.txt +93 -0
  52. package/src/lib/components/fonts/bricolage-grotesque.woff2 +0 -0
  53. package/src/lib/components/fonts/figtree.woff2 +0 -0
  54. package/src/lib/index.ts +4 -2
  55. package/src/lib/render/authoring.ts +7 -0
  56. package/src/lib/render/registry.ts +20 -0
  57. package/src/lib/render/rehype-dispatch.ts +13 -6
  58. package/src/lib/render/remark-directives.ts +1 -1
  59. package/src/lib/sveltekit/content-routes.ts +51 -14
@@ -22,15 +22,20 @@ export interface NavConcept {
22
22
  label: string;
23
23
  }
24
24
 
25
- /** The admin layout's data: site identity, the signed-in user, the nav, and the active path. */
25
+ /** The admin layout's data: site identity, the signed-in user, the nav, the active path, and theme. */
26
26
  export interface LayoutData {
27
27
  siteName: string;
28
- user: { displayName: string; role: Role };
28
+ user: { displayName: string; email: string; role: Role };
29
29
  concepts: NavConcept[];
30
30
  pathname: string;
31
31
  canManageEditors: boolean;
32
32
  /** The nav menu's label when the site configures one; gates the Navigation nav entry. Null otherwise. */
33
33
  navLabel: string | null;
34
+ /** The admin theme resolved for SSR: the persisted cookie choice, or the light default. */
35
+ theme: 'cairn-admin' | 'cairn-admin-dark';
36
+ /** The nav group labels the user has collapsed, from the persisted cookie. Read at SSR so a
37
+ * collapsed group renders collapsed with no flash. Empty when none are collapsed. */
38
+ collapsedNav: string[];
34
39
  }
35
40
 
36
41
  /** One row in a concept's list view. */
@@ -83,6 +88,8 @@ export interface ContentEvent {
83
88
  request: Request;
84
89
  locals: { editor?: Editor | null };
85
90
  platform?: { env?: GithubKeyEnv };
91
+ /** SvelteKit's cookie jar; the layout load reads the persisted admin theme. Optional for non-route callers. */
92
+ cookies?: { get(name: string): string | undefined };
86
93
  }
87
94
 
88
95
  /** Injectable dependencies; tests stub the token mint to avoid signing a real key. */
@@ -109,16 +116,24 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
109
116
  const mintToken =
110
117
  deps.mintToken ?? ((env: GithubKeyEnv) => cachedInstallationToken(appCredentials(runtime.backend, env)));
111
118
 
112
- /** Layout load for every admin page: the nav, the user, and the active path. */
119
+ /** Layout load for every admin page: the nav, the user, the active path, and the resolved theme. */
113
120
  function layoutLoad(event: ContentEvent): LayoutData {
114
121
  const editor = sessionOf(event);
122
+ const cookieTheme = event.cookies?.get('cairn-admin-theme');
123
+ const theme = cookieTheme === 'cairn-admin-dark' ? 'cairn-admin-dark' : 'cairn-admin';
124
+ const cookieCollapsed = event.cookies?.get('cairn-admin-nav-collapsed');
125
+ const collapsedNav = cookieCollapsed
126
+ ? cookieCollapsed.split(',').map((part) => decodeURIComponent(part)).filter(Boolean)
127
+ : [];
115
128
  return {
116
129
  siteName: runtime.siteName,
117
- user: { displayName: editor.displayName, role: editor.role },
130
+ user: { displayName: editor.displayName, email: editor.email, role: editor.role },
118
131
  concepts: runtime.concepts.map((c) => ({ id: c.id, label: c.label })),
119
132
  pathname: event.url.pathname,
120
133
  canManageEditors: editor.role === 'owner',
121
134
  navLabel: runtime.navMenu?.label ?? null,
135
+ theme,
136
+ collapsedNav,
122
137
  };
123
138
  }
124
139
 
@@ -338,14 +353,17 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
338
353
  throw redirect(303, `/admin/${concept.id}/${id}?${savedQuery}`);
339
354
  }
340
355
 
341
- /** Delete an entry. Block-until-clean: refuse while inbound links exist (naming them), else commit
342
- * the file removal and the manifest patch in one commit. The inbound recheck here is the
343
- * authoritative gate, closing the load-to-delete race. */
344
- async function deleteAction(event: ContentEvent): Promise<ReturnType<typeof fail> | never> {
345
- const editor = sessionOf(event);
346
- const concept = conceptOf(runtime, event.params);
347
- const id = event.params.id ?? '';
348
- if (!isValidId(id)) throw error(400, 'Invalid entry id');
356
+ /** The shared delete core. Block-until-clean: refuse while inbound links exist (naming them), else
357
+ * commit the file removal and the manifest patch in one commit. The inbound recheck here is the
358
+ * authoritative gate, closing the load-to-delete race. Both the editor delete (id from params) and
359
+ * the list delete (id from the form body) call this with an already-validated id, so the guard is
360
+ * enforced once. */
361
+ async function deleteEntry(
362
+ event: ContentEvent,
363
+ concept: ConceptDescriptor,
364
+ id: string,
365
+ editor: Editor,
366
+ ): Promise<ReturnType<typeof fail> | never> {
349
367
  const path = `${concept.dir}/${filenameFromId(id)}`;
350
368
  const token = await mintToken(event.platform?.env ?? {});
351
369
 
@@ -355,7 +373,7 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
355
373
  const manifest = manifestRaw === null ? emptyManifest() : parseManifest(manifestRaw);
356
374
  const inbound = inboundLinks(manifest, concept.id, id);
357
375
  if (inbound.length) {
358
- return fail(409, { inboundLinks: inbound });
376
+ return fail(409, { inboundLinks: inbound, id });
359
377
  }
360
378
 
361
379
  const nextManifest = serializeManifest(removeEntry(manifest, concept.id, id));
@@ -379,6 +397,25 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
379
397
  throw redirect(303, `/admin/${concept.id}`);
380
398
  }
381
399
 
400
+ /** Delete an entry from its editor. The id comes from the route param. */
401
+ async function deleteAction(event: ContentEvent): Promise<ReturnType<typeof fail> | never> {
402
+ const editor = sessionOf(event);
403
+ const concept = conceptOf(runtime, event.params);
404
+ const id = event.params.id ?? '';
405
+ if (!isValidId(id)) throw error(400, 'Invalid entry id');
406
+ return deleteEntry(event, concept, id, editor);
407
+ }
408
+
409
+ /** Delete an entry from the concept list. The id comes from the form body. */
410
+ async function listDeleteAction(event: ContentEvent): Promise<ReturnType<typeof fail> | never> {
411
+ const editor = sessionOf(event);
412
+ const concept = conceptOf(runtime, event.params);
413
+ const form = await event.request.formData();
414
+ const id = String(form.get('id') ?? '');
415
+ if (!isValidId(id)) throw error(400, 'Invalid entry id');
416
+ return deleteEntry(event, concept, id, editor);
417
+ }
418
+
382
419
  /** Rename an entry: change its slug, move the file, and rewrite every inbound cairn token in one
383
420
  * atomic commit, so no internal link breaks. The collision check and the inbound recompute here
384
421
  * are the authoritative gate. The same last-writer-wins manifest race as save and delete applies,
@@ -466,5 +503,5 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
466
503
  throw redirect(303, `/admin/${concept.id}/${newId}?renamed=1`);
467
504
  }
468
505
 
469
- return { layoutLoad, indexRedirect, listLoad, createAction, editLoad, saveAction, deleteAction, renameAction, mintToken };
506
+ return { layoutLoad, indexRedirect, listLoad, createAction, editLoad, saveAction, deleteAction, listDeleteAction, renameAction, mintToken };
470
507
  }