@glw907/cairn-cms 0.56.1 → 0.57.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 (186) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +10 -4
  3. package/dist/components/AdminLayout.svelte +3 -0
  4. package/dist/components/CairnAdmin.svelte +8 -1
  5. package/dist/components/CairnAdmin.svelte.d.ts +2 -0
  6. package/dist/components/CairnMediaLibrary.svelte +929 -0
  7. package/dist/components/CairnMediaLibrary.svelte.d.ts +37 -0
  8. package/dist/components/ComponentForm.svelte +175 -46
  9. package/dist/components/ComponentForm.svelte.d.ts +22 -8
  10. package/dist/components/ComponentInsertDialog.svelte +379 -26
  11. package/dist/components/ComponentInsertDialog.svelte.d.ts +31 -2
  12. package/dist/components/EditPage.svelte +477 -15
  13. package/dist/components/EditPage.svelte.d.ts +2 -0
  14. package/dist/components/MarkdownEditor.svelte +358 -1
  15. package/dist/components/MarkdownEditor.svelte.d.ts +51 -1
  16. package/dist/components/MediaCaptureCard.svelte +135 -0
  17. package/dist/components/MediaCaptureCard.svelte.d.ts +40 -0
  18. package/dist/components/MediaFigureControl.svelte +247 -0
  19. package/dist/components/MediaFigureControl.svelte.d.ts +40 -0
  20. package/dist/components/MediaHeroField.svelte +569 -0
  21. package/dist/components/MediaHeroField.svelte.d.ts +67 -0
  22. package/dist/components/MediaInsertPopover.svelte +449 -0
  23. package/dist/components/MediaInsertPopover.svelte.d.ts +58 -0
  24. package/dist/components/MediaPicker.svelte +257 -0
  25. package/dist/components/MediaPicker.svelte.d.ts +41 -0
  26. package/dist/components/admin-icons.d.ts +12 -0
  27. package/dist/components/admin-icons.js +12 -0
  28. package/dist/components/cairn-admin.css +1045 -28
  29. package/dist/components/client-ingest.d.ts +142 -0
  30. package/dist/components/client-ingest.js +297 -0
  31. package/dist/components/editor-media.d.ts +11 -0
  32. package/dist/components/editor-media.js +206 -0
  33. package/dist/components/editor-placeholder.d.ts +26 -0
  34. package/dist/components/editor-placeholder.js +166 -0
  35. package/dist/components/index.d.ts +1 -0
  36. package/dist/components/index.js +1 -0
  37. package/dist/components/markdown-directives.d.ts +19 -0
  38. package/dist/components/markdown-directives.js +52 -0
  39. package/dist/components/markdown-format.d.ts +89 -0
  40. package/dist/components/markdown-format.js +255 -0
  41. package/dist/components/media-upload-outcome.d.ts +52 -0
  42. package/dist/components/media-upload-outcome.js +48 -0
  43. package/dist/content/compose.js +3 -0
  44. package/dist/content/frontmatter.js +17 -0
  45. package/dist/content/manifest.d.ts +4 -0
  46. package/dist/content/manifest.js +41 -1
  47. package/dist/content/media-refs.d.ts +7 -0
  48. package/dist/content/media-refs.js +52 -0
  49. package/dist/content/schema.d.ts +5 -2
  50. package/dist/content/schema.js +17 -0
  51. package/dist/content/types.d.ts +62 -11
  52. package/dist/content/validate.js +27 -0
  53. package/dist/delivery/public-routes.d.ts +16 -0
  54. package/dist/delivery/public-routes.js +46 -3
  55. package/dist/delivery/seo-fields.js +7 -1
  56. package/dist/delivery/seo.d.ts +2 -0
  57. package/dist/delivery/seo.js +3 -0
  58. package/dist/doctor/checks-local.d.ts +1 -0
  59. package/dist/doctor/checks-local.js +21 -0
  60. package/dist/doctor/index.d.ts +3 -1
  61. package/dist/doctor/index.js +11 -2
  62. package/dist/doctor/types.d.ts +3 -0
  63. package/dist/doctor/wrangler-config.d.ts +3 -0
  64. package/dist/doctor/wrangler-config.js +20 -0
  65. package/dist/env.d.ts +19 -0
  66. package/dist/env.js +26 -0
  67. package/dist/index.d.ts +1 -1
  68. package/dist/log/events.d.ts +1 -1
  69. package/dist/media/config.d.ts +24 -0
  70. package/dist/media/config.js +69 -0
  71. package/dist/media/delivery-bucket.d.ts +34 -0
  72. package/dist/media/delivery-bucket.js +10 -0
  73. package/dist/media/index.d.ts +6 -0
  74. package/dist/media/index.js +13 -0
  75. package/dist/media/library-entry.d.ts +30 -0
  76. package/dist/media/library-entry.js +17 -0
  77. package/dist/media/manifest.d.ts +44 -0
  78. package/dist/media/manifest.js +105 -0
  79. package/dist/media/naming.d.ts +18 -0
  80. package/dist/media/naming.js +112 -0
  81. package/dist/media/reconcile.d.ts +36 -0
  82. package/dist/media/reconcile.js +45 -0
  83. package/dist/media/reference.d.ts +12 -0
  84. package/dist/media/reference.js +33 -0
  85. package/dist/media/sniff.d.ts +18 -0
  86. package/dist/media/sniff.js +106 -0
  87. package/dist/media/store.d.ts +25 -0
  88. package/dist/media/store.js +16 -0
  89. package/dist/media/transform-url.d.ts +26 -0
  90. package/dist/media/transform-url.js +38 -0
  91. package/dist/media/usage.d.ts +48 -0
  92. package/dist/media/usage.js +90 -0
  93. package/dist/render/component-grammar.d.ts +20 -0
  94. package/dist/render/component-grammar.js +47 -3
  95. package/dist/render/component-validate.js +22 -0
  96. package/dist/render/pipeline.d.ts +2 -0
  97. package/dist/render/pipeline.js +13 -2
  98. package/dist/render/registry.d.ts +28 -0
  99. package/dist/render/registry.js +15 -0
  100. package/dist/render/remark-figure.d.ts +4 -0
  101. package/dist/render/remark-figure.js +103 -0
  102. package/dist/render/resolve-media.d.ts +34 -0
  103. package/dist/render/resolve-media.js +78 -0
  104. package/dist/render/sanitize-schema.d.ts +4 -2
  105. package/dist/render/sanitize-schema.js +5 -3
  106. package/dist/sveltekit/admin-dispatch.d.ts +2 -0
  107. package/dist/sveltekit/admin-dispatch.js +5 -0
  108. package/dist/sveltekit/cairn-admin.d.ts +8 -1
  109. package/dist/sveltekit/cairn-admin.js +10 -2
  110. package/dist/sveltekit/content-routes.d.ts +68 -2
  111. package/dist/sveltekit/content-routes.js +461 -10
  112. package/dist/sveltekit/csrf.d.ts +16 -0
  113. package/dist/sveltekit/csrf.js +18 -0
  114. package/dist/sveltekit/guard.js +10 -3
  115. package/dist/sveltekit/index.d.ts +2 -1
  116. package/dist/sveltekit/index.js +1 -0
  117. package/dist/sveltekit/media-route.d.ts +12 -0
  118. package/dist/sveltekit/media-route.js +137 -0
  119. package/dist/vite/index.d.ts +3 -0
  120. package/dist/vite/index.js +7 -2
  121. package/package.json +8 -1
  122. package/src/lib/components/AdminLayout.svelte +3 -0
  123. package/src/lib/components/CairnAdmin.svelte +8 -1
  124. package/src/lib/components/CairnMediaLibrary.svelte +929 -0
  125. package/src/lib/components/ComponentForm.svelte +175 -46
  126. package/src/lib/components/ComponentInsertDialog.svelte +379 -26
  127. package/src/lib/components/EditPage.svelte +477 -15
  128. package/src/lib/components/MarkdownEditor.svelte +358 -1
  129. package/src/lib/components/MediaCaptureCard.svelte +135 -0
  130. package/src/lib/components/MediaFigureControl.svelte +247 -0
  131. package/src/lib/components/MediaHeroField.svelte +569 -0
  132. package/src/lib/components/MediaInsertPopover.svelte +449 -0
  133. package/src/lib/components/MediaPicker.svelte +257 -0
  134. package/src/lib/components/admin-icons.ts +12 -0
  135. package/src/lib/components/cairn-admin.css +37 -0
  136. package/src/lib/components/client-ingest.ts +380 -0
  137. package/src/lib/components/editor-media.ts +248 -0
  138. package/src/lib/components/editor-placeholder.ts +213 -0
  139. package/src/lib/components/index.ts +1 -0
  140. package/src/lib/components/markdown-directives.ts +57 -0
  141. package/src/lib/components/markdown-format.ts +307 -1
  142. package/src/lib/components/media-upload-outcome.ts +83 -0
  143. package/src/lib/content/compose.ts +3 -0
  144. package/src/lib/content/frontmatter.ts +16 -1
  145. package/src/lib/content/manifest.ts +44 -1
  146. package/src/lib/content/media-refs.ts +58 -0
  147. package/src/lib/content/schema.ts +31 -7
  148. package/src/lib/content/types.ts +78 -13
  149. package/src/lib/content/validate.ts +26 -1
  150. package/src/lib/delivery/public-routes.ts +52 -3
  151. package/src/lib/delivery/seo-fields.ts +6 -1
  152. package/src/lib/delivery/seo.ts +5 -0
  153. package/src/lib/doctor/checks-local.ts +22 -0
  154. package/src/lib/doctor/index.ts +21 -3
  155. package/src/lib/doctor/types.ts +3 -0
  156. package/src/lib/doctor/wrangler-config.ts +23 -0
  157. package/src/lib/env.ts +28 -0
  158. package/src/lib/index.ts +2 -0
  159. package/src/lib/log/events.ts +8 -1
  160. package/src/lib/media/config.ts +103 -0
  161. package/src/lib/media/delivery-bucket.ts +41 -0
  162. package/src/lib/media/index.ts +22 -0
  163. package/src/lib/media/library-entry.ts +58 -0
  164. package/src/lib/media/manifest.ts +122 -0
  165. package/src/lib/media/naming.ts +130 -0
  166. package/src/lib/media/reconcile.ts +79 -0
  167. package/src/lib/media/reference.ts +40 -0
  168. package/src/lib/media/sniff.ts +114 -0
  169. package/src/lib/media/store.ts +57 -0
  170. package/src/lib/media/transform-url.ts +58 -0
  171. package/src/lib/media/usage.ts +152 -0
  172. package/src/lib/render/component-grammar.ts +59 -3
  173. package/src/lib/render/component-validate.ts +22 -1
  174. package/src/lib/render/pipeline.ts +17 -3
  175. package/src/lib/render/registry.ts +38 -0
  176. package/src/lib/render/remark-figure.ts +132 -0
  177. package/src/lib/render/resolve-media.ts +96 -0
  178. package/src/lib/render/sanitize-schema.ts +5 -3
  179. package/src/lib/sveltekit/admin-dispatch.ts +6 -1
  180. package/src/lib/sveltekit/cairn-admin.ts +13 -3
  181. package/src/lib/sveltekit/content-routes.ts +573 -12
  182. package/src/lib/sveltekit/csrf.ts +18 -0
  183. package/src/lib/sveltekit/guard.ts +12 -3
  184. package/src/lib/sveltekit/index.ts +6 -0
  185. package/src/lib/sveltekit/media-route.ts +158 -0
  186. package/src/lib/vite/index.ts +9 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,154 @@
2
2
 
3
3
  All notable changes to this project are recorded here, most recent first.
4
4
 
5
+ ## 0.57.0
6
+
7
+ Images become first-class. An editor can paste, drag, or insert an image straight into a post, and
8
+ cairn stores it, names it by its content, commits it with the entry, and serves it from the site's
9
+ own R2 bucket. This is the whole media stack landing together: the foundation that models a stored
10
+ image, the infrastructure that ingests and delivers the bytes, and the insert UI that puts it in an
11
+ editor's hands. It is additive to the public API, but it needs per-site wiring, so it is a minor.
12
+
13
+ The foundation models an image as a logical reference, not a path. Content commits a `media:` token
14
+ keyed to the first 16 hex characters of the bytes' sha256, so the same image resolves no matter where
15
+ it is stored or what it is named, and identical bytes always land at one key. A small git-committed
16
+ manifest (`media.json`) carries the human layer the bytes cannot: the display name, the alt text, the
17
+ original filename, and the pixel facts. A render-time resolver reads that manifest and rewrites each
18
+ `media:` token to its delivery URL, optionally through a Cloudflare Images transform URL when a site
19
+ turns transforms on. The adapter's `AssetConfig` grew to declare the R2 bucket binding, the URL form,
20
+ the upload limits, and the named variants.
21
+
22
+ The infrastructure ingests and serves the bytes. A locked-down `/media` delivery route, built from
23
+ `createMediaRoute`, streams content-addressed bytes from R2: it validates the hash and extension
24
+ before any read, derives the object key from the validated values alone, carries the load-bearing
25
+ security headers (nosniff, inline disposition, a `default-src 'none'; sandbox` CSP, a one-year
26
+ immutable cache), and forwards `If-None-Match` and `Range` for 304 and 206 responses. An admin
27
+ `uploadAction` takes the editor's bytes, hashes them, dedups against the manifest with a put-first
28
+ head check, and rejects a hash collision with a 409. A client ingest helper normalizes a HEIC to a
29
+ web format before upload. A save merges the editor's optimistic records into `media.json` at commit
30
+ time, and the edit load hands the admin preview a lean `mediaTargets` projection so an in-session
31
+ image renders before it is committed.
32
+
33
+ The insert UI puts it in an editor's hands. Three gestures start an insert: paste from the clipboard,
34
+ drag a file onto the editor, or the toolbar's Insert image button. A paste or drag opens an at-caret
35
+ popover on the capture card with the dropped file; the button opens a chooser with upload first and a
36
+ combobox picker below it for reusing an image already on the site. The capture card pre-fills the name
37
+ from the filename and never blocks on alt text, so an editor can insert now and describe later. The
38
+ inserted reference renders in the editor as an atomic chip (thumbnail, name, and a needs-alt marker),
39
+ and an upload still in flight shows a widget-only placeholder with a determinate progress bar that
40
+ writes no document text until it resolves. A non-blocking needs-alt notice on the edit page counts the
41
+ images still waiting for a description and jumps to each one, never blocking a save or a Publish. The
42
+ edit-page preview renders inserted images through the same resolver the live site uses.
43
+
44
+ Figures land in the same release. An inline image can carry a caption and a placement through a
45
+ cairn-reserved `:::figure` directive that wraps the image as a child node. The caption is the
46
+ directive's body text, rendered to a real `<figcaption>`, and the placement is a closed role set
47
+ (`center`, `wide`, `full`, plus the bare measure default) carried as a class on the `<figure>`. A
48
+ persistent editor control wraps a bare image, edits an existing figure's caption and role, or unwraps
49
+ it, writing the markdown source the author can read and hand-edit, and the source chip shows the
50
+ figure's role so the decoration agrees with the source. `figure` and `figcaption` join the base
51
+ sanitize floor, so a captioned figure survives on every site, and `figure` is a reserved directive
52
+ name the registry refuses to let a site component shadow. cairn ships default `.cairn-place-*` CSS in
53
+ the showcase reference, and a site restyles those classes to own the placement pixels. A guide section
54
+ covers it in [add an image](docs/guides/add-an-image.md).
55
+
56
+ Hero images land in the same release. A Post or Page carries a lead image in frontmatter as a nested
57
+ `image: { src, alt, caption }` object, where `src` is a `media:` reference, `alt` is the screen-reader
58
+ description, and `caption` is an optional line the template may show. `image` is a new built-in field
59
+ type declared through `defineFields` like `text` or `date`. The editor renders it in the details panel
60
+ as a one-row resting field that opens the same picker and capture flow the body insert uses. Alt stays
61
+ debt, and the needs-alt notice now counts a hero with an empty alt alongside the body images. One
62
+ image serves two jobs: the delivery read path resolves the frontmatter reference into a derived
63
+ `heroImage` projection the template lays out, and the SEO head reads the same resolved image as the
64
+ `og:image` and `twitter:image`. The on-disk `media:` token stays canonical, since resolution is a
65
+ separate projection that is never written back. `resolveImageUrl` now rejects a non-http(s) result, so
66
+ an unresolved `media:` token degrades to no social image rather than shipping a broken tag. The site
67
+ template owns the hero layout: cairn ships the resolved data and the social-card wiring, not a hero
68
+ render step. A required `image` field is enforced on the presence of its `src`, never on its alt.
69
+
70
+ The Media Library lands in the same release. A first-class admin screen at `/admin/media`, a peer of
71
+ Posts and Pages, browses every committed asset, shows where each one is used, edits its name and
72
+ default alt, and deletes it safely. The resting surface is a contact-sheet grid with a list-density
73
+ toggle; a non-modal detail slide-over carries the preview, the alt editor, the grouped where-used
74
+ list, and the actions. The Library computes where-used by content hash across `main` and every open
75
+ edit branch, so a not-yet-published upload still shows and a renamed slug still resolves. The content
76
+ manifest gained an additive `mediaRefs` field per entry to feed the `main` side of that index; an
77
+ existing manifest without it still parses and builds. Safe-delete rechecks usage server-side against
78
+ a fresh read at delete time, refuses an in-use asset (the in-use face names what would break and
79
+ requires typing the slug), commits the manifest row removal before deleting the R2 object, and fails
80
+ closed if it cannot verify usage. Rename and default-alt are a single `media.json` row commit with no
81
+ reference rewrite, since the resolver and route key on the hash; the default alt is the value
82
+ prefilled into the next placement, not a rewrite of alt already committed. Replace, bulk actions, and
83
+ tags are deferred.
84
+
85
+ Consumers must: bind an R2 bucket and mount the delivery route before media works. Add an
86
+ `r2_buckets` binding named `MEDIA_BUCKET` in `wrangler.jsonc`, and mount the delivery route at
87
+ `src/routes/media/[...path]/+server.ts` with `createMediaRoute(runtime.resolvedAssets)`. Declare the
88
+ adapter's `assets` block naming that binding, and regenerate nothing else; media stays off until the
89
+ `assets` block is present. Cloudflare Images transforms stay behind the `transformations: false`
90
+ default, so a site serves full-size bytes until it opts in. The wiring steps are in
91
+ [the upgrade guide](docs/guides/upgrade-cairn.md) and the
92
+ [wire the delivery surface guide](docs/guides/wire-the-delivery-surface.md); the surface is documented
93
+ in [the media reference](docs/reference/media.md) and
94
+ [the sveltekit reference](docs/reference/sveltekit.md).
95
+
96
+ Recommended, not required: regenerate the content manifest (`cairn-manifest`) and commit it so the
97
+ Media Library's `main` where-used is accurate. The `mediaRefs` field is additive, so a site builds
98
+ without it, but an un-regenerated manifest reads every published media reference as absent until it
99
+ is regenerated. Save and publish keep the field current from then on.
100
+
101
+ ## 0.56.2
102
+
103
+ The component insert picker gains a live preview and round-trip editing, and the component contract
104
+ grows the optional fields that make a good picker possible. These refine the existing
105
+ component-editing surface and are all additive, so it is a patch; existing definitions compile
106
+ unchanged with no action required.
107
+
108
+ How the design was reached. Two research arms ran first. One surveyed how comparable systems build
109
+ their insert pickers (Gutenberg, Sanity, Wagtail, Payload, Contentful, Builder, and the git-backed and
110
+ document tools). The other hunted documented complaints from both the editor and the developer, then
111
+ paired each with a correction. Five pains recur across systems that share no code, and cairn already
112
+ beats four of them by its existing architecture: a single `ComponentDef` co-locates render and schema
113
+ (no schema-render drift), content is markdown in git (no database-migration tax), and the parser reads
114
+ real directives (lossless re-edit stays reachable). The fifth pain, configuring a block without seeing
115
+ the result, no system has solved. An adversarial critique of the first mockup then caught the preview
116
+ faked with static HTML and an ironic "Untitled" placeholder, which the shipped design corrects.
117
+
118
+ What an editor gets. The picker lists components in one column, grouped under headings, each row a
119
+ glyph, a description, and a line on when to reach for it; a search box appears once a site declares
120
+ more than eight. Picking a component that declares a `preview` opens a two-pane configure step: the
121
+ fill form on the left, and on the right the configured component rendered through the site's own
122
+ pipeline, the same machinery the edit page preview uses. This is the part no comparable CMS offers,
123
+ and cairn can offer it because it already owns the render path. The preview settles on a debounce
124
+ rather than re-rendering on every keystroke, and it stays honest: a still-empty required field shows
125
+ the skeleton with the empty region called out rather than a fabricated result, and a render that
126
+ throws shows a failed-to-render surface and keeps the form. A component that declares no `preview`
127
+ keeps the single-column form. Required fields are marked and block Insert with inline messages, and
128
+ the modal collapses to one column on a narrow screen.
129
+
130
+ Round-trip editing closes the loop. With the cursor in a placed component, an Edit block control opens
131
+ it back into the same guided form, pre-filled, and Update rewrites that block in place. It is offered
132
+ only when the round-trip is provably lossless for that block: one that carries an attribute or a child
133
+ the component does not declare is left for hand-editing rather than silently rewritten, the failure
134
+ that corrupts content in the git-backed editors the research surveyed. A guided edit that does run
135
+ preserves content and normalizes formatting to the canonical serialization. A consumer site that
136
+ mounts `CairnAdmin` gets this with no change.
137
+
138
+ For consumers, the `ComponentDef` contract gains optional fields, so existing definitions compile
139
+ unchanged with no action required:
140
+
141
+ - `icon` shows a glyph from the site icon set beside the label in the picker.
142
+ - `group` puts a component under a category heading, in declaration order.
143
+ - `hidden` keeps a component out of the top-level picker (for a nested-only component).
144
+ - `preview` is a structured sample (`attributes` and `slots`) the picker seeds the form with and
145
+ renders. Declaring it is what opts a component into the two-pane preview layout.
146
+ - `pattern` and `validate` on an attribute field add inline validation, the regex case and a pure
147
+ cross-field escape hatch.
148
+ - `itemLabel` on a repeatable slot derives a row's label, so a list of items is not a column of blanks.
149
+
150
+ Round-trip editing of a placed component, a persistent catalog rail, and a slash-trigger are designed
151
+ for but deferred to a later pass.
152
+
5
153
  ## 0.56.1
6
154
 
7
155
  Test and CI reliability only; the published library is unchanged from 0.56.0. The component test job
package/README.md CHANGED
@@ -52,10 +52,16 @@ doesn't, cairn is the wrong tool and will not try to meet you halfway.
52
52
 
53
53
  ## Status
54
54
 
55
- cairn-cms runs the two production sites above. It is `0.x`, and the version position signals scale:
56
- a minor bump (`0.X.0`) carries a significant new feature and may break, while a patch (`0.X.Y`) is a
57
- safe fix, polish, or tooling change. Pin a caret range and read the [CHANGELOG](./CHANGELOG.md)
58
- before taking a minor; every breaking entry carries a "Consumers must" line. The author is still working through the core-feature
55
+ cairn-cms runs the two production sites above. It is `0.x`, and the version position signals scale.
56
+ A minor bump (`0.X.0`) is reserved for a new subsystem or public surface that did not exist before,
57
+ such as a new entry point, a new content concept, or the scaffolder, and it may break. Everything
58
+ that refines, extends, or adds an affordance to a surface that already exists (the editor, the admin,
59
+ auth, delivery) is a patch (`0.X.Y`), even when it gives an editor a new thing to do; a redesign, a
60
+ round-trip edit on an existing surface, or a new optional config field is a patch. When the call is
61
+ unclear, it is a patch. A minor release carries a `<!-- release-size: minor -->` marker in its
62
+ CHANGELOG entry, which the `check:version` gate requires, so a minor is always a deliberate,
63
+ documented choice. Pin a caret range and read the [CHANGELOG](./CHANGELOG.md) before taking a minor;
64
+ every breaking entry carries a "Consumers must" line. The author is still working through the core-feature
59
65
  [ROADMAP](./ROADMAP.md), and the project stays closely held until that core lands. A
60
66
  contributor who feels inspired is welcome to open an issue or a discussion; there is no
61
67
  formal contribution process yet, so this is not an open call for pull requests.
@@ -20,6 +20,7 @@ identical on every host regardless of the site's own theme.
20
20
  import SignpostIcon from '@lucide/svelte/icons/signpost';
21
21
  import SettingsIcon from '@lucide/svelte/icons/settings';
22
22
  import UsersIcon from '@lucide/svelte/icons/users';
23
+ import ImageIcon from '@lucide/svelte/icons/image';
23
24
  import BlocksIcon from '@lucide/svelte/icons/blocks';
24
25
  import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
25
26
  import './cairn-admin.css';
@@ -56,6 +57,8 @@ identical on every host regardless of the site's own theme.
56
57
  // the owner-only Editors.
57
58
  const coreItems: NavItem[] = $derived([
58
59
  ...data.concepts.map((c) => ({ label: c.label, icon: FileTextIcon, href: `/admin/${c.id}` })),
60
+ // Media is a content peer, immediately after the concepts.
61
+ { label: 'Media', icon: ImageIcon, href: '/admin/media' },
59
62
  ...(data.navLabel ? [{ label: data.navLabel, icon: SignpostIcon, href: '/admin/nav' }] : []),
60
63
  { label: 'Settings', icon: SettingsIcon, href: '/admin/settings' },
61
64
  ...(data.canManageEditors ? [{ label: 'Editors', icon: UsersIcon, href: '/admin/editors' }] : []),
@@ -13,11 +13,13 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
13
13
  import EditPage from './EditPage.svelte';
14
14
  import ManageEditors from './ManageEditors.svelte';
15
15
  import NavTree from './NavTree.svelte';
16
+ import CairnMediaLibrary from './CairnMediaLibrary.svelte';
16
17
  import type { AdminData } from '../sveltekit/cairn-admin.js';
17
18
  import type { ContentFormFailure } from '../sveltekit/content-routes.js';
18
19
  import type { ComponentRegistry } from '../render/registry.js';
19
20
  import type { IconSet } from '../render/glyph.js';
20
21
  import type { LinkResolve } from '../content/links.js';
22
+ import type { MediaResolve } from '../render/resolve-media.js';
21
23
 
22
24
  interface Props {
23
25
  /** The discriminated view data from `createCairnAdmin`'s load. */
@@ -33,7 +35,10 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
33
35
  })
34
36
  | null;
35
37
  /** The site's design-accurate render pipeline, for the edit view's preview pane. */
36
- render?: (md: string, opts?: { stagger?: boolean; resolve?: LinkResolve }) => string | Promise<string>;
38
+ render?: (
39
+ md: string,
40
+ opts?: { stagger?: boolean; resolve?: LinkResolve; resolveMedia?: MediaResolve },
41
+ ) => string | Promise<string>;
37
42
  /** The site's component registry, for the edit view's insert palette. */
38
43
  registry?: ComponentRegistry;
39
44
  /** The site's icon set, for the edit view's guided form fields. */
@@ -62,6 +67,8 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
62
67
  <ManageEditors data={data.page} {form} />
63
68
  {:else if data.view === 'nav'}
64
69
  <NavTree data={data.page} />
70
+ {:else if data.view === 'media'}
71
+ <CairnMediaLibrary data={data.page} {form} />
65
72
  {/if}
66
73
  </AdminLayout>
67
74
  {/if}
@@ -3,6 +3,7 @@ import type { ContentFormFailure } from '../sveltekit/content-routes.js';
3
3
  import type { ComponentRegistry } from '../render/registry.js';
4
4
  import type { IconSet } from '../render/glyph.js';
5
5
  import type { LinkResolve } from '../content/links.js';
6
+ import type { MediaResolve } from '../render/resolve-media.js';
6
7
  interface Props {
7
8
  /** The discriminated view data from `createCairnAdmin`'s load. */
8
9
  data: AdminData;
@@ -18,6 +19,7 @@ interface Props {
18
19
  render?: (md: string, opts?: {
19
20
  stagger?: boolean;
20
21
  resolve?: LinkResolve;
22
+ resolveMedia?: MediaResolve;
21
23
  }) => string | Promise<string>;
22
24
  /** The site's component registry, for the edit view's insert palette. */
23
25
  registry?: ComponentRegistry;