@glw907/cairn-cms 0.26.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 (234) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/dist/auth/crypto.d.ts +0 -1
  3. package/dist/auth/store.d.ts +0 -1
  4. package/dist/auth/types.d.ts +0 -1
  5. package/dist/components/AdminLayout.svelte +372 -44
  6. package/dist/components/AdminLayout.svelte.d.ts +5 -5
  7. package/dist/components/CairnLogo.svelte +28 -0
  8. package/dist/components/CairnLogo.svelte.d.ts +15 -0
  9. package/dist/components/ComponentForm.svelte +1 -1
  10. package/dist/components/ComponentForm.svelte.d.ts +0 -1
  11. package/dist/components/ComponentInsertDialog.svelte.d.ts +0 -1
  12. package/dist/components/ConceptList.svelte +240 -45
  13. package/dist/components/ConceptList.svelte.d.ts +12 -3
  14. package/dist/components/ConfirmPage.svelte +20 -3
  15. package/dist/components/ConfirmPage.svelte.d.ts +0 -1
  16. package/dist/components/DeleteDialog.svelte.d.ts +0 -1
  17. package/dist/components/EditPage.svelte +12 -7
  18. package/dist/components/EditPage.svelte.d.ts +0 -1
  19. package/dist/components/EditorToolbar.svelte.d.ts +0 -1
  20. package/dist/components/IconPicker.svelte.d.ts +0 -1
  21. package/dist/components/LinkPicker.svelte.d.ts +0 -1
  22. package/dist/components/LoginPage.svelte +27 -5
  23. package/dist/components/LoginPage.svelte.d.ts +0 -1
  24. package/dist/components/ManageEditors.svelte +8 -5
  25. package/dist/components/ManageEditors.svelte.d.ts +0 -1
  26. package/dist/components/MarkdownEditor.svelte.d.ts +0 -1
  27. package/dist/components/NavTree.svelte +2 -2
  28. package/dist/components/NavTree.svelte.d.ts +0 -1
  29. package/dist/components/RenameDialog.svelte.d.ts +0 -1
  30. package/dist/components/admin-icons.d.ts +13 -0
  31. package/dist/components/admin-icons.js +15 -0
  32. package/dist/components/cairn-admin.css +5516 -37
  33. package/dist/components/cairn-favicon.d.ts +2 -0
  34. package/dist/components/cairn-favicon.js +7 -0
  35. package/dist/components/chrome-guard.d.ts +9 -0
  36. package/dist/components/chrome-guard.js +55 -0
  37. package/dist/components/fonts/BricolageGrotesque-OFL.txt +93 -0
  38. package/dist/components/fonts/Figtree-OFL.txt +93 -0
  39. package/dist/components/fonts/bricolage-grotesque.woff2 +0 -0
  40. package/dist/components/fonts/figtree.woff2 +0 -0
  41. package/dist/components/index.d.ts +0 -1
  42. package/dist/components/link-completion.d.ts +0 -1
  43. package/dist/components/markdown-format.d.ts +0 -1
  44. package/dist/content/adapter.d.ts +0 -1
  45. package/dist/content/compose.d.ts +1 -2
  46. package/dist/content/compose.js +2 -3
  47. package/dist/content/concepts.d.ts +7 -1
  48. package/dist/content/concepts.js +49 -1
  49. package/dist/content/frontmatter.d.ts +0 -1
  50. package/dist/content/identity.d.ts +23 -0
  51. package/dist/content/identity.js +43 -0
  52. package/dist/content/ids.d.ts +0 -1
  53. package/dist/content/links.d.ts +0 -1
  54. package/dist/content/manifest.d.ts +3 -2
  55. package/dist/content/manifest.js +6 -26
  56. package/dist/content/permalink.d.ts +0 -1
  57. package/dist/content/schema.d.ts +0 -1
  58. package/dist/content/types.d.ts +0 -1
  59. package/dist/content/validate.d.ts +0 -1
  60. package/dist/delivery/CairnHead.svelte.d.ts +0 -1
  61. package/dist/delivery/content-index.d.ts +0 -1
  62. package/dist/delivery/content-index.js +8 -25
  63. package/dist/delivery/data.d.ts +0 -1
  64. package/dist/delivery/excerpt.d.ts +0 -1
  65. package/dist/delivery/feeds.d.ts +0 -1
  66. package/dist/delivery/head.d.ts +0 -1
  67. package/dist/delivery/index.d.ts +0 -1
  68. package/dist/delivery/json-ld.d.ts +0 -1
  69. package/dist/delivery/manifest.d.ts +0 -1
  70. package/dist/delivery/paginate.d.ts +0 -1
  71. package/dist/delivery/responses.d.ts +0 -1
  72. package/dist/delivery/robots.d.ts +0 -1
  73. package/dist/delivery/seo-fields.d.ts +0 -1
  74. package/dist/delivery/seo.d.ts +0 -1
  75. package/dist/delivery/site-descriptors.d.ts +0 -1
  76. package/dist/delivery/site-descriptors.js +5 -6
  77. package/dist/delivery/site-index.d.ts +0 -1
  78. package/dist/delivery/site-indexes.d.ts +0 -1
  79. package/dist/delivery/sitemap.d.ts +0 -1
  80. package/dist/email.d.ts +0 -1
  81. package/dist/env.d.ts +0 -1
  82. package/dist/github/credentials.d.ts +0 -1
  83. package/dist/github/repo.d.ts +0 -1
  84. package/dist/github/signing.d.ts +0 -1
  85. package/dist/github/types.d.ts +0 -1
  86. package/dist/index.d.ts +0 -29
  87. package/dist/index.js +4 -23
  88. package/dist/nav/site-config.d.ts +0 -1
  89. package/dist/render/authoring.d.ts +3 -0
  90. package/dist/render/authoring.js +5 -0
  91. package/dist/render/component-grammar.d.ts +0 -1
  92. package/dist/render/component-insert.d.ts +0 -1
  93. package/dist/render/component-reference.d.ts +0 -1
  94. package/dist/render/component-validate.d.ts +0 -1
  95. package/dist/render/glyph.d.ts +0 -1
  96. package/dist/render/index.d.ts +0 -1
  97. package/dist/render/pipeline.d.ts +0 -1
  98. package/dist/render/pipeline.js +5 -1
  99. package/dist/render/registry.d.ts +2 -1
  100. package/dist/render/registry.js +15 -0
  101. package/dist/render/rehype-dispatch.d.ts +9 -7
  102. package/dist/render/rehype-dispatch.js +12 -6
  103. package/dist/render/remark-directives.d.ts +0 -1
  104. package/dist/render/remark-directives.js +1 -1
  105. package/dist/render/resolve-links.d.ts +0 -1
  106. package/dist/render/sanitize-schema.d.ts +14 -1
  107. package/dist/render/sanitize-schema.js +96 -0
  108. package/dist/sveltekit/auth-routes.d.ts +0 -1
  109. package/dist/sveltekit/content-routes.d.ts +12 -2
  110. package/dist/sveltekit/content-routes.js +37 -13
  111. package/dist/sveltekit/editors-routes.d.ts +0 -1
  112. package/dist/sveltekit/guard.d.ts +0 -1
  113. package/dist/sveltekit/health.d.ts +0 -1
  114. package/dist/sveltekit/index.d.ts +1 -3
  115. package/dist/sveltekit/index.js +0 -1
  116. package/dist/sveltekit/nav-routes.d.ts +0 -1
  117. package/dist/sveltekit/public-routes.d.ts +0 -1
  118. package/dist/sveltekit/types.d.ts +0 -1
  119. package/dist/vite/bin.d.ts +0 -1
  120. package/dist/vite/index.d.ts +0 -1
  121. package/package.json +16 -2
  122. package/src/lib/components/AdminLayout.svelte +372 -44
  123. package/src/lib/components/CairnLogo.svelte +28 -0
  124. package/src/lib/components/ComponentForm.svelte +1 -1
  125. package/src/lib/components/ConceptList.svelte +240 -45
  126. package/src/lib/components/ConfirmPage.svelte +20 -3
  127. package/src/lib/components/EditPage.svelte +12 -7
  128. package/src/lib/components/LoginPage.svelte +27 -5
  129. package/src/lib/components/ManageEditors.svelte +8 -5
  130. package/src/lib/components/NavTree.svelte +2 -2
  131. package/src/lib/components/admin-icons.ts +15 -0
  132. package/src/lib/components/cairn-admin.css +162 -7
  133. package/src/lib/components/cairn-favicon.ts +9 -0
  134. package/src/lib/components/chrome-guard.ts +62 -0
  135. package/src/lib/components/fonts/BricolageGrotesque-OFL.txt +93 -0
  136. package/src/lib/components/fonts/Figtree-OFL.txt +93 -0
  137. package/src/lib/components/fonts/bricolage-grotesque.woff2 +0 -0
  138. package/src/lib/components/fonts/figtree.woff2 +0 -0
  139. package/src/lib/content/compose.ts +3 -3
  140. package/src/lib/content/concepts.ts +61 -1
  141. package/src/lib/content/identity.ts +60 -0
  142. package/src/lib/content/manifest.ts +6 -27
  143. package/src/lib/delivery/content-index.ts +8 -27
  144. package/src/lib/delivery/site-descriptors.ts +5 -6
  145. package/src/lib/index.ts +4 -57
  146. package/src/lib/render/authoring.ts +7 -0
  147. package/src/lib/render/pipeline.ts +4 -1
  148. package/src/lib/render/registry.ts +20 -0
  149. package/src/lib/render/rehype-dispatch.ts +13 -6
  150. package/src/lib/render/remark-directives.ts +1 -1
  151. package/src/lib/render/sanitize-schema.ts +97 -0
  152. package/src/lib/sveltekit/content-routes.ts +51 -14
  153. package/src/lib/sveltekit/index.ts +2 -8
  154. package/dist/auth/crypto.d.ts.map +0 -1
  155. package/dist/auth/store.d.ts.map +0 -1
  156. package/dist/auth/types.d.ts.map +0 -1
  157. package/dist/components/AdminLayout.svelte.d.ts.map +0 -1
  158. package/dist/components/ComponentForm.svelte.d.ts.map +0 -1
  159. package/dist/components/ComponentInsertDialog.svelte.d.ts.map +0 -1
  160. package/dist/components/ConceptList.svelte.d.ts.map +0 -1
  161. package/dist/components/ConfirmPage.svelte.d.ts.map +0 -1
  162. package/dist/components/DeleteDialog.svelte.d.ts.map +0 -1
  163. package/dist/components/EditPage.svelte.d.ts.map +0 -1
  164. package/dist/components/EditorToolbar.svelte.d.ts.map +0 -1
  165. package/dist/components/IconPicker.svelte.d.ts.map +0 -1
  166. package/dist/components/LinkPicker.svelte.d.ts.map +0 -1
  167. package/dist/components/LoginPage.svelte.d.ts.map +0 -1
  168. package/dist/components/ManageEditors.svelte.d.ts.map +0 -1
  169. package/dist/components/MarkdownEditor.svelte.d.ts.map +0 -1
  170. package/dist/components/NavTree.svelte.d.ts.map +0 -1
  171. package/dist/components/RenameDialog.svelte.d.ts.map +0 -1
  172. package/dist/components/index.d.ts.map +0 -1
  173. package/dist/components/link-completion.d.ts.map +0 -1
  174. package/dist/components/markdown-format.d.ts.map +0 -1
  175. package/dist/content/adapter.d.ts.map +0 -1
  176. package/dist/content/compose.d.ts.map +0 -1
  177. package/dist/content/concepts.d.ts.map +0 -1
  178. package/dist/content/frontmatter.d.ts.map +0 -1
  179. package/dist/content/ids.d.ts.map +0 -1
  180. package/dist/content/links.d.ts.map +0 -1
  181. package/dist/content/manifest.d.ts.map +0 -1
  182. package/dist/content/permalink.d.ts.map +0 -1
  183. package/dist/content/schema.d.ts.map +0 -1
  184. package/dist/content/types.d.ts.map +0 -1
  185. package/dist/content/validate.d.ts.map +0 -1
  186. package/dist/delivery/CairnHead.svelte.d.ts.map +0 -1
  187. package/dist/delivery/content-index.d.ts.map +0 -1
  188. package/dist/delivery/data.d.ts.map +0 -1
  189. package/dist/delivery/excerpt.d.ts.map +0 -1
  190. package/dist/delivery/feeds.d.ts.map +0 -1
  191. package/dist/delivery/head.d.ts.map +0 -1
  192. package/dist/delivery/index.d.ts.map +0 -1
  193. package/dist/delivery/json-ld.d.ts.map +0 -1
  194. package/dist/delivery/manifest.d.ts.map +0 -1
  195. package/dist/delivery/paginate.d.ts.map +0 -1
  196. package/dist/delivery/responses.d.ts.map +0 -1
  197. package/dist/delivery/robots.d.ts.map +0 -1
  198. package/dist/delivery/seo-fields.d.ts.map +0 -1
  199. package/dist/delivery/seo.d.ts.map +0 -1
  200. package/dist/delivery/site-descriptors.d.ts.map +0 -1
  201. package/dist/delivery/site-index.d.ts.map +0 -1
  202. package/dist/delivery/site-indexes.d.ts.map +0 -1
  203. package/dist/delivery/sitemap.d.ts.map +0 -1
  204. package/dist/email.d.ts.map +0 -1
  205. package/dist/env.d.ts.map +0 -1
  206. package/dist/github/credentials.d.ts.map +0 -1
  207. package/dist/github/repo.d.ts.map +0 -1
  208. package/dist/github/signing.d.ts.map +0 -1
  209. package/dist/github/types.d.ts.map +0 -1
  210. package/dist/index.d.ts.map +0 -1
  211. package/dist/nav/site-config.d.ts.map +0 -1
  212. package/dist/render/component-grammar.d.ts.map +0 -1
  213. package/dist/render/component-insert.d.ts.map +0 -1
  214. package/dist/render/component-reference.d.ts.map +0 -1
  215. package/dist/render/component-validate.d.ts.map +0 -1
  216. package/dist/render/glyph.d.ts.map +0 -1
  217. package/dist/render/index.d.ts.map +0 -1
  218. package/dist/render/pipeline.d.ts.map +0 -1
  219. package/dist/render/registry.d.ts.map +0 -1
  220. package/dist/render/rehype-dispatch.d.ts.map +0 -1
  221. package/dist/render/remark-directives.d.ts.map +0 -1
  222. package/dist/render/resolve-links.d.ts.map +0 -1
  223. package/dist/render/sanitize-schema.d.ts.map +0 -1
  224. package/dist/sveltekit/auth-routes.d.ts.map +0 -1
  225. package/dist/sveltekit/content-routes.d.ts.map +0 -1
  226. package/dist/sveltekit/editors-routes.d.ts.map +0 -1
  227. package/dist/sveltekit/guard.d.ts.map +0 -1
  228. package/dist/sveltekit/health.d.ts.map +0 -1
  229. package/dist/sveltekit/index.d.ts.map +0 -1
  230. package/dist/sveltekit/nav-routes.d.ts.map +0 -1
  231. package/dist/sveltekit/public-routes.d.ts.map +0 -1
  232. package/dist/sveltekit/types.d.ts.map +0 -1
  233. package/dist/vite/bin.d.ts.map +0 -1
  234. package/dist/vite/index.d.ts.map +0 -1
@@ -56,3 +56,99 @@ export function rehypeAnchorRel(rel) {
56
56
  });
57
57
  };
58
58
  }
59
+ // URL-bearing hast properties the post-dispatch guard scheme-checks. hast camelCases attribute
60
+ // names through property-information (srcset -> srcSet, xlink:href -> xLinkHref with a capital L,
61
+ // formaction -> formAction). data is the <object data> URL attribute; data-* attributes camelCase
62
+ // to dataFoo and are not matched here.
63
+ const URL_PROPS = new Set([
64
+ 'href',
65
+ 'src',
66
+ 'srcSet',
67
+ 'xLinkHref',
68
+ 'poster',
69
+ 'formAction',
70
+ 'action',
71
+ 'data',
72
+ 'background',
73
+ ]);
74
+ // The safe URL schemes: the union of every protocol list in defaultSchema, plus cairn. The
75
+ // floor admits these and strips the rest, so deriving from the same source keeps the floor and
76
+ // this guard from drifting on what a safe scheme is. javascript:/data:/vbscript: are never in
77
+ // defaultSchema, so they are never safe.
78
+ const SAFE_SCHEMES = (() => {
79
+ const protocols = defaultSchema.protocols ?? {};
80
+ const schemes = new Set(['cairn']);
81
+ for (const list of Object.values(protocols)) {
82
+ for (const scheme of list ?? [])
83
+ schemes.add(String(scheme).toLowerCase());
84
+ }
85
+ return schemes;
86
+ })();
87
+ // Read a URL value's scheme for the safety check, defeating the whitespace and control-character
88
+ // tricks a browser ignores inside a scheme (java\tscript:, a leading space). A value with no
89
+ // scheme (relative, anchor, query) returns undefined and is always safe.
90
+ function urlScheme(value) {
91
+ const cleaned = value.replace(/[\x00-\x20]+/g, '');
92
+ const match = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);
93
+ return match ? match[1].toLowerCase() : undefined;
94
+ }
95
+ function isSafeUrl(value) {
96
+ const scheme = urlScheme(value);
97
+ return scheme === undefined || SAFE_SCHEMES.has(scheme);
98
+ }
99
+ // srcset is "url descriptor, url descriptor, …". hast may store it as a string or, because
100
+ // property-information marks it comma-separated, as a string array. One unsafe candidate makes
101
+ // the whole attribute unsafe.
102
+ function isSafeSrcset(value) {
103
+ const candidates = Array.isArray(value)
104
+ ? value.map(String)
105
+ : typeof value === 'string'
106
+ ? value.split(',')
107
+ : [];
108
+ return candidates.every((candidate) => {
109
+ const url = candidate.trim().split(/\s+/)[0];
110
+ return url === '' || isSafeUrl(url);
111
+ });
112
+ }
113
+ // Decide whether one URL-bearing property value is safe to keep. srcset has its own
114
+ // multi-candidate grammar. A non-string value carries no scheme to abuse, so the floor's own
115
+ // handling stands and the guard leaves it alone.
116
+ function isSafeUrlProp(key, value) {
117
+ if (key === 'srcSet')
118
+ return isSafeSrcset(value);
119
+ if (typeof value !== 'string')
120
+ return true;
121
+ return isSafeUrl(value);
122
+ }
123
+ /**
124
+ * Post-dispatch safety floor over the fully-built tree. The pre-dispatch rehype-sanitize floor
125
+ * cleans author content, but a component build() runs after it and can route a raw author
126
+ * attribute value into a sink. This guard runs last and neutralizes those sinks on every element
127
+ * no matter which plugin or which build() produced it: an unsafe URL scheme in a URL-bearing
128
+ * attribute, an inline on* event handler, or an inline style (stripped wholesale, matching the
129
+ * floor and cairn's class-driven styling). It is gated by the same unsafeDisableSanitize switch as
130
+ * the floor.
131
+ *
132
+ * The guard's boundary is the URL scheme check plus the on* and style strip. It does not remove a
133
+ * build()-emitted raw script, style, or iframe srcdoc element node. A build() that emits those is
134
+ * running site-developer code, and author markdown is cleaned by the pre-dispatch floor.
135
+ */
136
+ export function rehypeSinkGuard() {
137
+ return (tree) => {
138
+ visit(tree, 'element', (node) => {
139
+ const props = node.properties;
140
+ if (!props)
141
+ return;
142
+ for (const key of Object.keys(props)) {
143
+ if (/^on/i.test(key) || key === 'style') {
144
+ delete props[key];
145
+ continue;
146
+ }
147
+ if (!URL_PROPS.has(key))
148
+ continue;
149
+ if (!isSafeUrlProp(key, props[key]))
150
+ delete props[key];
151
+ }
152
+ });
153
+ };
154
+ }
@@ -20,4 +20,3 @@ export declare function createAuthRoutes(config: AuthRoutesConfig): {
20
20
  confirmAction: (event: RequestContext) => Promise<never>;
21
21
  logoutAction: (event: RequestContext) => Promise<never>;
22
22
  };
23
- //# sourceMappingURL=auth-routes.d.ts.map
@@ -8,11 +8,12 @@ export interface NavConcept {
8
8
  id: string;
9
9
  label: string;
10
10
  }
11
- /** The admin layout's data: site identity, the signed-in user, the nav, and the active path. */
11
+ /** The admin layout's data: site identity, the signed-in user, the nav, the active path, and theme. */
12
12
  export interface LayoutData {
13
13
  siteName: string;
14
14
  user: {
15
15
  displayName: string;
16
+ email: string;
16
17
  role: Role;
17
18
  };
18
19
  concepts: NavConcept[];
@@ -20,6 +21,11 @@ export interface LayoutData {
20
21
  canManageEditors: boolean;
21
22
  /** The nav menu's label when the site configures one; gates the Navigation nav entry. Null otherwise. */
22
23
  navLabel: string | null;
24
+ /** The admin theme resolved for SSR: the persisted cookie choice, or the light default. */
25
+ theme: 'cairn-admin' | 'cairn-admin-dark';
26
+ /** The nav group labels the user has collapsed, from the persisted cookie. Read at SSR so a
27
+ * collapsed group renders collapsed with no flash. Empty when none are collapsed. */
28
+ collapsedNav: string[];
23
29
  }
24
30
  /** One row in a concept's list view. */
25
31
  export interface EntrySummary {
@@ -72,6 +78,10 @@ export interface ContentEvent {
72
78
  platform?: {
73
79
  env?: GithubKeyEnv;
74
80
  };
81
+ /** SvelteKit's cookie jar; the layout load reads the persisted admin theme. Optional for non-route callers. */
82
+ cookies?: {
83
+ get(name: string): string | undefined;
84
+ };
75
85
  }
76
86
  /** Injectable dependencies; tests stub the token mint to avoid signing a real key. */
77
87
  export interface ContentRoutesDeps {
@@ -86,7 +96,7 @@ export declare function createContentRoutes(runtime: CairnRuntime, deps?: Conten
86
96
  editLoad: (event: ContentEvent) => Promise<EditData>;
87
97
  saveAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
88
98
  deleteAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
99
+ listDeleteAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
89
100
  renameAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
90
101
  mintToken: (env: GithubKeyEnv) => Promise<string>;
91
102
  };
92
- //# sourceMappingURL=content-routes.d.ts.map
@@ -29,16 +29,24 @@ function conceptOf(runtime, params) {
29
29
  }
30
30
  export function createContentRoutes(runtime, deps = {}) {
31
31
  const mintToken = deps.mintToken ?? ((env) => cachedInstallationToken(appCredentials(runtime.backend, env)));
32
- /** Layout load for every admin page: the nav, the user, and the active path. */
32
+ /** Layout load for every admin page: the nav, the user, the active path, and the resolved theme. */
33
33
  function layoutLoad(event) {
34
34
  const editor = sessionOf(event);
35
+ const cookieTheme = event.cookies?.get('cairn-admin-theme');
36
+ const theme = cookieTheme === 'cairn-admin-dark' ? 'cairn-admin-dark' : 'cairn-admin';
37
+ const cookieCollapsed = event.cookies?.get('cairn-admin-nav-collapsed');
38
+ const collapsedNav = cookieCollapsed
39
+ ? cookieCollapsed.split(',').map((part) => decodeURIComponent(part)).filter(Boolean)
40
+ : [];
35
41
  return {
36
42
  siteName: runtime.siteName,
37
- user: { displayName: editor.displayName, role: editor.role },
43
+ user: { displayName: editor.displayName, email: editor.email, role: editor.role },
38
44
  concepts: runtime.concepts.map((c) => ({ id: c.id, label: c.label })),
39
45
  pathname: event.url.pathname,
40
46
  canManageEditors: editor.role === 'owner',
41
47
  navLabel: runtime.navMenu?.label ?? null,
48
+ theme,
49
+ collapsedNav,
42
50
  };
43
51
  }
44
52
  /** Redirect /admin to the first concept's list (spec §7.6: land on the first concept). */
@@ -251,15 +259,12 @@ export function createContentRoutes(runtime, deps = {}) {
251
259
  const savedQuery = draft.length ? `saved=1&drafts=${encodeURIComponent(draft.join(','))}` : 'saved=1';
252
260
  throw redirect(303, `/admin/${concept.id}/${id}?${savedQuery}`);
253
261
  }
254
- /** Delete an entry. Block-until-clean: refuse while inbound links exist (naming them), else commit
255
- * the file removal and the manifest patch in one commit. The inbound recheck here is the
256
- * authoritative gate, closing the load-to-delete race. */
257
- async function deleteAction(event) {
258
- const editor = sessionOf(event);
259
- const concept = conceptOf(runtime, event.params);
260
- const id = event.params.id ?? '';
261
- if (!isValidId(id))
262
- throw error(400, 'Invalid entry id');
262
+ /** The shared delete core. Block-until-clean: refuse while inbound links exist (naming them), else
263
+ * commit the file removal and the manifest patch in one commit. The inbound recheck here is the
264
+ * authoritative gate, closing the load-to-delete race. Both the editor delete (id from params) and
265
+ * the list delete (id from the form body) call this with an already-validated id, so the guard is
266
+ * enforced once. */
267
+ async function deleteEntry(event, concept, id, editor) {
263
268
  const path = `${concept.dir}/${filenameFromId(id)}`;
264
269
  const token = await mintToken(event.platform?.env ?? {});
265
270
  // An absent manifest degrades the inbound gate to "allow": with no manifest there is nothing to
@@ -268,7 +273,7 @@ export function createContentRoutes(runtime, deps = {}) {
268
273
  const manifest = manifestRaw === null ? emptyManifest() : parseManifest(manifestRaw);
269
274
  const inbound = inboundLinks(manifest, concept.id, id);
270
275
  if (inbound.length) {
271
- return fail(409, { inboundLinks: inbound });
276
+ return fail(409, { inboundLinks: inbound, id });
272
277
  }
273
278
  const nextManifest = serializeManifest(removeEntry(manifest, concept.id, id));
274
279
  try {
@@ -286,6 +291,25 @@ export function createContentRoutes(runtime, deps = {}) {
286
291
  }
287
292
  throw redirect(303, `/admin/${concept.id}`);
288
293
  }
294
+ /** Delete an entry from its editor. The id comes from the route param. */
295
+ async function deleteAction(event) {
296
+ const editor = sessionOf(event);
297
+ const concept = conceptOf(runtime, event.params);
298
+ const id = event.params.id ?? '';
299
+ if (!isValidId(id))
300
+ throw error(400, 'Invalid entry id');
301
+ return deleteEntry(event, concept, id, editor);
302
+ }
303
+ /** Delete an entry from the concept list. The id comes from the form body. */
304
+ async function listDeleteAction(event) {
305
+ const editor = sessionOf(event);
306
+ const concept = conceptOf(runtime, event.params);
307
+ const form = await event.request.formData();
308
+ const id = String(form.get('id') ?? '');
309
+ if (!isValidId(id))
310
+ throw error(400, 'Invalid entry id');
311
+ return deleteEntry(event, concept, id, editor);
312
+ }
289
313
  /** Rename an entry: change its slug, move the file, and rewrite every inbound cairn token in one
290
314
  * atomic commit, so no internal link breaks. The collision check and the inbound recompute here
291
315
  * are the authoritative gate. The same last-writer-wins manifest race as save and delete applies,
@@ -364,5 +388,5 @@ export function createContentRoutes(runtime, deps = {}) {
364
388
  }
365
389
  throw redirect(303, `/admin/${concept.id}/${newId}?renamed=1`);
366
390
  }
367
- return { layoutLoad, indexRedirect, listLoad, createAction, editLoad, saveAction, deleteAction, renameAction, mintToken };
391
+ return { layoutLoad, indexRedirect, listLoad, createAction, editLoad, saveAction, deleteAction, listDeleteAction, renameAction, mintToken };
368
392
  }
@@ -21,4 +21,3 @@ export declare function createEditorRoutes(): {
21
21
  ok: true;
22
22
  }>;
23
23
  };
24
- //# sourceMappingURL=editors-routes.d.ts.map
@@ -6,4 +6,3 @@ export declare function createAuthGuard(): ({ event, resolve }: HandleInput) =>
6
6
  export declare function requireSession(event: RequestContext): Editor;
7
7
  /** For the management surface: a signed-in owner, or 403 for an editor. */
8
8
  export declare function requireOwner(event: RequestContext): Editor;
9
- //# sourceMappingURL=guard.d.ts.map
@@ -16,4 +16,3 @@ export declare function healthLoad(event: {
16
16
  env?: GithubKeyEnv;
17
17
  };
18
18
  }, runtime: CairnRuntime): Promise<HealthData>;
19
- //# sourceMappingURL=health.d.ts.map
@@ -7,6 +7,4 @@ export { createNavRoutes } from './nav-routes.js';
7
7
  export type { NavLoadData, NavPageOption, NavRoutesDeps } from './nav-routes.js';
8
8
  export { healthLoad, type HealthData } from './health.js';
9
9
  export type { RequestContext, CookieJar, HandleInput } from './types.js';
10
- export { createPublicRoutes } from './public-routes.js';
11
- export type { PublicRoutesDeps, ListData as PublicListData, TagData, TagIndexData, EntryData, } from './public-routes.js';
12
- //# sourceMappingURL=index.d.ts.map
10
+ export type { GithubKeyEnv } from '../github/credentials.js';
@@ -6,4 +6,3 @@ export { createEditorRoutes } from './editors-routes.js';
6
6
  export { createContentRoutes } from './content-routes.js';
7
7
  export { createNavRoutes } from './nav-routes.js';
8
8
  export { healthLoad } from './health.js';
9
- export { createPublicRoutes } from './public-routes.js';
@@ -27,4 +27,3 @@ export declare function createNavRoutes(runtime: CairnRuntime, deps?: NavRoutesD
27
27
  navLoad: (event: ContentEvent) => Promise<NavLoadData>;
28
28
  navSave: (event: ContentEvent) => Promise<never>;
29
29
  };
30
- //# sourceMappingURL=nav-routes.d.ts.map
@@ -64,4 +64,3 @@ export declare function createPublicRoutes(deps: PublicRoutesDeps): {
64
64
  path: string;
65
65
  }[];
66
66
  };
67
- //# sourceMappingURL=public-routes.d.ts.map
@@ -35,4 +35,3 @@ export interface HandleInput {
35
35
  event: RequestContext;
36
36
  resolve(event: RequestContext): Promise<Response> | Response;
37
37
  }
38
- //# sourceMappingURL=types.d.ts.map
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=bin.d.ts.map
@@ -30,4 +30,3 @@ export declare function cairnManifest(opts: CairnManifestOptions): Plugin;
30
30
  * resolution, and writes the serialized manifest. The cairn-manifest bin calls this; it is exported
31
31
  * so the write logic is testable apart from the CLI shell. */
32
32
  export declare function writeManifest(cwd?: string): Promise<void>;
33
- //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glw907/cairn-cms",
3
- "version": "0.26.0",
3
+ "version": "0.33.0",
4
4
  "description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -26,8 +26,10 @@
26
26
  "markdown"
27
27
  ],
28
28
  "scripts": {
29
- "package": "svelte-package && chmod +x dist/vite/bin.js",
29
+ "package": "svelte-package && node scripts/build-admin-css.mjs && chmod +x dist/vite/bin.js",
30
30
  "check:package": "npm run package && publint --strict && attw --pack . --ignore-rules no-resolution cjs-resolves-to-esm internal-resolution-error",
31
+ "check:reference": "npm run package && node scripts/reference-coverage.mjs",
32
+ "check:docs": "node scripts/docs-links.mjs",
31
33
  "prepare": "npm run package",
32
34
  "check": "svelte-check --tsconfig ./tsconfig.json",
33
35
  "test": "vitest run",
@@ -52,6 +54,11 @@
52
54
  "svelte": "./dist/components/index.js",
53
55
  "default": "./dist/components/index.js"
54
56
  },
57
+ "./render": {
58
+ "types": "./dist/render/authoring.d.ts",
59
+ "svelte": "./dist/render/authoring.js",
60
+ "default": "./dist/render/authoring.js"
61
+ },
55
62
  "./delivery": {
56
63
  "types": "./dist/delivery/index.d.ts",
57
64
  "svelte": "./dist/delivery/index.js",
@@ -92,6 +99,7 @@
92
99
  "@codemirror/language": "^6.12.3",
93
100
  "@codemirror/state": "^6.6.0",
94
101
  "@codemirror/view": "^6.43.0",
102
+ "@lucide/svelte": "^1.17.0",
95
103
  "@rodrigodagostino/svelte-sortable-list": "^2.1.17",
96
104
  "@types/hast": "^3.0.4",
97
105
  "@types/mdast": "^4.0.4",
@@ -120,13 +128,19 @@
120
128
  "@sveltejs/kit": "^2.61",
121
129
  "@sveltejs/package": "^2",
122
130
  "@sveltejs/vite-plugin-svelte": "^7.1",
131
+ "@tailwindcss/postcss": "^4.3.0",
123
132
  "@types/node": "^22.19.19",
124
133
  "@vitest/browser": "^4.1.7",
125
134
  "@vitest/browser-playwright": "^4.1.7",
135
+ "daisyui": "^5.5.23",
136
+ "lightningcss": "^1.32.0",
126
137
  "playwright": "^1.60.0",
138
+ "postcss": "^8.5.15",
139
+ "postcss-prefix-selector": "^2.1.1",
127
140
  "publint": "^0.3.21",
128
141
  "svelte": "^5.55",
129
142
  "svelte-check": "^4",
143
+ "tailwindcss": "^4.3.0",
130
144
  "typescript": "^6.0.3",
131
145
  "vite": "^8.0",
132
146
  "vitest": "^4.1",