@glw907/cairn-cms 0.60.0 → 0.62.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.
- package/CHANGELOG.md +82 -0
- package/dist/components/AdminLayout.svelte +152 -229
- package/dist/components/CairnAdmin.svelte +13 -42
- package/dist/components/CairnLogo.svelte +1 -6
- package/dist/components/CairnMediaLibrary.svelte +821 -1210
- package/dist/components/CairnTidySettings.svelte +194 -261
- package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
- package/dist/components/ComponentForm.svelte +110 -185
- package/dist/components/ComponentInsertDialog.svelte +163 -283
- package/dist/components/ConceptList.svelte +111 -191
- package/dist/components/ConfirmPage.svelte +5 -12
- package/dist/components/CsrfField.svelte +5 -11
- package/dist/components/DeleteDialog.svelte +15 -42
- package/dist/components/EditPage.svelte +781 -1205
- package/dist/components/EditorToolbar.svelte +108 -170
- package/dist/components/HelpHome.svelte +824 -0
- package/dist/components/HelpHome.svelte.d.ts +22 -0
- package/dist/components/IconPicker.svelte +23 -53
- package/dist/components/LinkPicker.svelte +34 -58
- package/dist/components/LoginPage.svelte +14 -27
- package/dist/components/ManageEditors.svelte +3 -15
- package/dist/components/MarkdownEditor.svelte +689 -957
- package/dist/components/MarkdownHelpDialog.svelte +12 -27
- package/dist/components/MediaCaptureCard.svelte +18 -57
- package/dist/components/MediaFigureControl.svelte +32 -71
- package/dist/components/MediaHeroField.svelte +210 -329
- package/dist/components/MediaInsertPopover.svelte +156 -283
- package/dist/components/MediaPicker.svelte +67 -131
- package/dist/components/NavTree.svelte +46 -78
- package/dist/components/RenameDialog.svelte +16 -43
- package/dist/components/ShortcutsDialog.svelte +9 -13
- package/dist/components/ShortcutsGrid.svelte +1 -2
- package/dist/components/TidyReview.svelte +140 -248
- package/dist/components/WebLinkDialog.svelte +19 -40
- package/dist/components/cairn-admin.css +4 -0
- package/dist/components/client-ingest.d.ts +16 -8
- package/dist/components/client-ingest.js +12 -6
- package/dist/components/editor-media.js +16 -8
- package/dist/components/editor-placeholder.d.ts +4 -2
- package/dist/components/editor-tidy.d.ts +24 -12
- package/dist/components/editor-tidy.js +8 -4
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/link-completion.d.ts +12 -6
- package/dist/components/link-completion.js +12 -6
- package/dist/components/markdown-directives.d.ts +9 -6
- package/dist/components/markdown-directives.js +9 -6
- package/dist/components/markdown-format.d.ts +7 -2
- package/dist/components/markdown-format.js +59 -28
- package/dist/components/markdown-reference.d.ts +8 -0
- package/dist/components/markdown-reference.js +22 -0
- package/dist/components/media-upload-outcome.d.ts +12 -6
- package/dist/components/objective-errors.d.ts +8 -4
- package/dist/components/objective-errors.js +8 -4
- package/dist/components/preview-doc.d.ts +4 -2
- package/dist/components/preview-doc.js +4 -2
- package/dist/components/spellcheck.d.ts +57 -29
- package/dist/components/spellcheck.js +50 -20
- package/dist/components/tidy-categorize.d.ts +20 -10
- package/dist/components/tidy-categorize.js +16 -8
- package/dist/components/tidy-validate.d.ts +12 -6
- package/dist/components/tidy-validate.js +20 -10
- package/dist/components/topbar-context.d.ts +4 -2
- package/dist/content/advisories.d.ts +51 -0
- package/dist/content/advisories.js +79 -0
- package/dist/content/compose.d.ts +4 -2
- package/dist/content/compose.js +1 -0
- package/dist/content/excerpt.js +4 -2
- package/dist/content/getting-started.d.ts +18 -0
- package/dist/content/getting-started.js +12 -0
- package/dist/content/links.d.ts +16 -8
- package/dist/content/links.js +12 -6
- package/dist/content/manifest.d.ts +36 -18
- package/dist/content/manifest.js +32 -16
- package/dist/content/media-refs.d.ts +4 -2
- package/dist/content/media-refs.js +4 -2
- package/dist/content/media-rewrite.d.ts +8 -4
- package/dist/content/media-rewrite.js +76 -38
- package/dist/content/schema.d.ts +20 -10
- package/dist/content/site-dictionary.d.ts +4 -2
- package/dist/content/site-dictionary.js +8 -4
- package/dist/content/types.d.ts +97 -42
- package/dist/delivery/CairnHead.svelte +8 -11
- package/dist/delivery/content-index.d.ts +16 -8
- package/dist/delivery/feeds.js +4 -2
- package/dist/delivery/json-ld.d.ts +3 -0
- package/dist/delivery/json-ld.js +3 -0
- package/dist/delivery/manifest.d.ts +4 -2
- package/dist/delivery/manifest.js +4 -2
- package/dist/delivery/public-routes.d.ts +12 -6
- package/dist/delivery/public-routes.js +4 -2
- package/dist/delivery/seo-fields.d.ts +12 -6
- package/dist/delivery/seo-fields.js +8 -4
- package/dist/delivery/site-indexes.d.ts +4 -2
- package/dist/delivery/site-resolver.d.ts +4 -2
- package/dist/delivery/site-resolver.js +4 -2
- package/dist/doctor/cloudflare-api.d.ts +6 -0
- package/dist/doctor/cloudflare-api.js +6 -0
- package/dist/doctor/index.d.ts +12 -6
- package/dist/doctor/report.d.ts +3 -0
- package/dist/doctor/report.js +3 -0
- package/dist/doctor/run.d.ts +3 -0
- package/dist/doctor/run.js +3 -0
- package/dist/doctor/types.d.ts +10 -2
- package/dist/doctor/types.js +6 -0
- package/dist/doctor/wrangler-config.d.ts +7 -2
- package/dist/doctor/wrangler-config.js +3 -0
- package/dist/email.d.ts +4 -2
- package/dist/env.d.ts +0 -3
- package/dist/env.js +0 -3
- package/dist/github/branches.d.ts +4 -2
- package/dist/github/branches.js +4 -2
- package/dist/github/signing.d.ts +1 -1
- package/dist/github/signing.js +2 -2
- package/dist/log/events.d.ts +1 -1
- package/dist/media/bulk-delete-plan.d.ts +8 -4
- package/dist/media/config.d.ts +12 -6
- package/dist/media/config.js +16 -8
- package/dist/media/delivery-bucket.d.ts +4 -2
- package/dist/media/library-entry.d.ts +4 -2
- package/dist/media/library-entry.js +4 -2
- package/dist/media/manifest.d.ts +29 -15
- package/dist/media/manifest.js +29 -16
- package/dist/media/naming.d.ts +12 -6
- package/dist/media/naming.js +24 -12
- package/dist/media/orphan-scan.d.ts +4 -2
- package/dist/media/reconcile.d.ts +21 -11
- package/dist/media/reconcile.js +12 -6
- package/dist/media/reference.d.ts +8 -4
- package/dist/media/reference.js +12 -6
- package/dist/media/rewrite-plan.d.ts +12 -6
- package/dist/media/sniff.d.ts +4 -2
- package/dist/media/sniff.js +28 -14
- package/dist/media/store.d.ts +16 -8
- package/dist/media/store.js +4 -2
- package/dist/media/transform-url.d.ts +12 -6
- package/dist/media/transform-url.js +8 -4
- package/dist/media/usage.d.ts +8 -4
- package/dist/nav/site-config.d.ts +16 -8
- package/dist/render/component-grammar.d.ts +23 -10
- package/dist/render/component-grammar.js +19 -8
- package/dist/render/component-insert.d.ts +8 -4
- package/dist/render/component-insert.js +4 -2
- package/dist/render/component-reference.d.ts +4 -2
- package/dist/render/component-reference.js +4 -2
- package/dist/render/component-validate.d.ts +3 -0
- package/dist/render/component-validate.js +3 -0
- package/dist/render/glyph.d.ts +4 -2
- package/dist/render/glyph.js +4 -2
- package/dist/render/pipeline.d.ts +20 -10
- package/dist/render/pipeline.js +4 -2
- package/dist/render/registry.d.ts +40 -20
- package/dist/render/registry.js +16 -8
- package/dist/render/rehype-dispatch.d.ts +22 -8
- package/dist/render/rehype-dispatch.js +22 -8
- package/dist/render/remark-directives.d.ts +3 -0
- package/dist/render/remark-directives.js +3 -0
- package/dist/render/remark-figure.d.ts +4 -2
- package/dist/render/remark-figure.js +4 -2
- package/dist/render/resolve-links.d.ts +4 -2
- package/dist/render/resolve-links.js +4 -2
- package/dist/render/resolve-media.d.ts +16 -8
- package/dist/render/resolve-media.js +12 -6
- package/dist/sveltekit/admin-dispatch.d.ts +2 -0
- package/dist/sveltekit/admin-dispatch.js +9 -3
- package/dist/sveltekit/auth-routes.d.ts +3 -0
- package/dist/sveltekit/auth-routes.js +3 -0
- package/dist/sveltekit/cairn-admin.d.ts +16 -5
- package/dist/sveltekit/cairn-admin.js +26 -10
- package/dist/sveltekit/content-routes.d.ts +191 -86
- package/dist/sveltekit/content-routes.js +295 -107
- package/dist/sveltekit/editors-routes.d.ts +3 -0
- package/dist/sveltekit/editors-routes.js +3 -0
- package/dist/sveltekit/guard.d.ts +4 -2
- package/dist/sveltekit/guard.js +4 -2
- package/dist/sveltekit/https-required-page.d.ts +1 -1
- package/dist/sveltekit/https-required-page.js +1 -1
- package/dist/sveltekit/index.d.ts +1 -1
- package/dist/sveltekit/media-route.d.ts +1 -2
- package/dist/sveltekit/media-route.js +13 -8
- package/dist/sveltekit/nav-routes.d.ts +7 -2
- package/dist/sveltekit/nav-routes.js +3 -0
- package/dist/sveltekit/types.d.ts +4 -2
- package/dist/vite/index.d.ts +32 -16
- package/dist/vite/index.js +52 -26
- package/dist/vite/resolve-root.d.ts +8 -4
- package/dist/vite/resolve-root.js +4 -2
- package/package.json +8 -2
- package/src/lib/components/AdminLayout.svelte +22 -0
- package/src/lib/components/CairnAdmin.svelte +3 -0
- package/src/lib/components/CairnTidySettings.svelte +2 -2
- package/src/lib/components/ComponentForm.svelte +0 -1
- package/src/lib/components/EditPage.svelte +133 -41
- package/src/lib/components/HelpHome.svelte +850 -0
- package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
- package/src/lib/components/client-ingest.ts +20 -10
- package/src/lib/components/editor-media.ts +20 -10
- package/src/lib/components/editor-placeholder.ts +12 -6
- package/src/lib/components/editor-tidy.ts +28 -14
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/link-completion.ts +12 -6
- package/src/lib/components/markdown-directives.ts +13 -8
- package/src/lib/components/markdown-format.ts +63 -30
- package/src/lib/components/markdown-reference.ts +30 -0
- package/src/lib/components/media-upload-outcome.ts +12 -6
- package/src/lib/components/objective-errors.ts +16 -8
- package/src/lib/components/preview-doc.ts +4 -2
- package/src/lib/components/spellcheck.ts +92 -40
- package/src/lib/components/tidy-categorize.ts +28 -14
- package/src/lib/components/tidy-validate.ts +28 -14
- package/src/lib/components/topbar-context.ts +4 -2
- package/src/lib/content/advisories.ts +141 -0
- package/src/lib/content/compose.ts +5 -2
- package/src/lib/content/excerpt.ts +4 -2
- package/src/lib/content/getting-started.ts +31 -0
- package/src/lib/content/links.ts +16 -8
- package/src/lib/content/manifest.ts +36 -18
- package/src/lib/content/media-refs.ts +4 -2
- package/src/lib/content/media-rewrite.ts +100 -50
- package/src/lib/content/schema.ts +20 -10
- package/src/lib/content/site-dictionary.ts +8 -4
- package/src/lib/content/types.ts +97 -42
- package/src/lib/delivery/content-index.ts +16 -8
- package/src/lib/delivery/feeds.ts +4 -2
- package/src/lib/delivery/json-ld.ts +3 -0
- package/src/lib/delivery/manifest.ts +4 -2
- package/src/lib/delivery/public-routes.ts +16 -8
- package/src/lib/delivery/seo-fields.ts +12 -6
- package/src/lib/delivery/site-indexes.ts +4 -2
- package/src/lib/delivery/site-resolver.ts +4 -2
- package/src/lib/doctor/cloudflare-api.ts +6 -0
- package/src/lib/doctor/index.ts +12 -6
- package/src/lib/doctor/report.ts +3 -0
- package/src/lib/doctor/run.ts +3 -0
- package/src/lib/doctor/types.ts +10 -2
- package/src/lib/doctor/wrangler-config.ts +7 -2
- package/src/lib/email.ts +4 -2
- package/src/lib/env.ts +0 -3
- package/src/lib/github/branches.ts +4 -2
- package/src/lib/github/signing.ts +2 -2
- package/src/lib/log/events.ts +1 -0
- package/src/lib/media/bulk-delete-plan.ts +8 -4
- package/src/lib/media/config.ts +24 -12
- package/src/lib/media/delivery-bucket.ts +4 -2
- package/src/lib/media/library-entry.ts +4 -2
- package/src/lib/media/manifest.ts +33 -18
- package/src/lib/media/naming.ts +24 -12
- package/src/lib/media/orphan-scan.ts +4 -2
- package/src/lib/media/reconcile.ts +21 -11
- package/src/lib/media/reference.ts +12 -6
- package/src/lib/media/rewrite-plan.ts +12 -6
- package/src/lib/media/sniff.ts +28 -14
- package/src/lib/media/store.ts +16 -8
- package/src/lib/media/transform-url.ts +12 -6
- package/src/lib/media/usage.ts +8 -4
- package/src/lib/nav/site-config.ts +16 -8
- package/src/lib/render/component-grammar.ts +23 -10
- package/src/lib/render/component-insert.ts +8 -4
- package/src/lib/render/component-reference.ts +4 -2
- package/src/lib/render/component-validate.ts +3 -0
- package/src/lib/render/glyph.ts +4 -2
- package/src/lib/render/pipeline.ts +20 -10
- package/src/lib/render/registry.ts +44 -22
- package/src/lib/render/rehype-dispatch.ts +22 -8
- package/src/lib/render/remark-directives.ts +3 -0
- package/src/lib/render/remark-figure.ts +4 -2
- package/src/lib/render/resolve-links.ts +4 -2
- package/src/lib/render/resolve-media.ts +16 -8
- package/src/lib/sveltekit/admin-dispatch.ts +10 -4
- package/src/lib/sveltekit/auth-routes.ts +3 -0
- package/src/lib/sveltekit/cairn-admin.ts +37 -15
- package/src/lib/sveltekit/content-routes.ts +492 -197
- package/src/lib/sveltekit/editors-routes.ts +3 -0
- package/src/lib/sveltekit/guard.ts +4 -2
- package/src/lib/sveltekit/https-required-page.ts +1 -1
- package/src/lib/sveltekit/index.ts +3 -0
- package/src/lib/sveltekit/media-route.ts +13 -8
- package/src/lib/sveltekit/nav-routes.ts +7 -2
- package/src/lib/sveltekit/types.ts +4 -2
- package/src/lib/vite/index.ts +60 -30
- package/src/lib/vite/resolve-root.ts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,88 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are recorded here, most recent first.
|
|
4
4
|
|
|
5
|
+
## 0.62.1
|
|
6
|
+
|
|
7
|
+
The entry editor gains an advisory channel and its first notice: a cross-branch address-collision
|
|
8
|
+
warning. When another entry already resolves to the same public address, the editor shows a
|
|
9
|
+
non-blocking warning that names that entry and links to it. The warning never blocks Publish. It makes
|
|
10
|
+
the last-write-wins outcome visible instead of silent, since publishing replaces whatever currently
|
|
11
|
+
lives at that address.
|
|
12
|
+
|
|
13
|
+
The check runs at edit-load across `main` and every open `cairn/<concept>/<id>` branch. A publish
|
|
14
|
+
re-checks the address and emits a `publish.address_collision` log event (level `warn`, fields `editor`,
|
|
15
|
+
`address`, `displacedConcept`, `displacedId`) when it overrides one. The existing needs-alt notice now
|
|
16
|
+
renders through the same advisory region, with its live count and per-row actions unchanged.
|
|
17
|
+
|
|
18
|
+
This adds two exported types on `/sveltekit`, `AdvisoryNotice` and `AdvisoryAction`, the shape
|
|
19
|
+
`EditData.advisories` carries. No consumer action is required.
|
|
20
|
+
|
|
21
|
+
## 0.62.0
|
|
22
|
+
|
|
23
|
+
<!-- release-size: minor -->
|
|
24
|
+
|
|
25
|
+
The admin gains a Help home, the pull half of the in-admin editor help. It is a standing screen at
|
|
26
|
+
`/admin/help`, reached from a labeled Help home pinned at the foot of the office sidebar (and from the
|
|
27
|
+
Ctrl+K command palette).
|
|
28
|
+
|
|
29
|
+
The screen carries three sections. A getting-started checklist derives its progress from what is
|
|
30
|
+
really on the site: writing a post, publishing one, and creating a page. The count is never stored, so
|
|
31
|
+
it always reflects the corpus, and the whole section drops away once all three steps are done. A hide
|
|
32
|
+
control tucks it away per device. A formatting reference promotes the editor's Ctrl+/ cheat sheet to a
|
|
33
|
+
standing two-column table. A support hand-off points a stuck author at the site's `supportContact`,
|
|
34
|
+
shaped to the contact (an email opens a `mailto`, a URL opens a link, anything else shows as a note),
|
|
35
|
+
and it renders only when the adapter sets one.
|
|
36
|
+
|
|
37
|
+
This adds two exports: the `HelpHome` component on the `/components` subpath and the `HelpData` type on
|
|
38
|
+
`/sveltekit`. The new `/admin/help` route is additive.
|
|
39
|
+
|
|
40
|
+
No consumer action is required. A site that sets no `supportContact` sees the Help home with a
|
|
41
|
+
self-serve line in place of the contact hand-off.
|
|
42
|
+
|
|
43
|
+
This release also fixes the admin-copy prose gate (`check:prose`): a component whose `@component` doc
|
|
44
|
+
comment wrote the literal `<style>` tag had its whole markup silently skipped, so its copy was never
|
|
45
|
+
scanned. The gate now strips comments before the script and style blocks.
|
|
46
|
+
|
|
47
|
+
## 0.61.0
|
|
48
|
+
|
|
49
|
+
<!-- release-size: minor -->
|
|
50
|
+
|
|
51
|
+
The editor gains the groundwork for in-admin help. This pass adds the engine seams and one built-in
|
|
52
|
+
clarity default the help layer will build on.
|
|
53
|
+
|
|
54
|
+
A frontmatter field can now carry a `description`: one author-facing sentence shown under the field in
|
|
55
|
+
the editor's Details panel and tied to the input with `aria-describedby`. Set it on any field in a
|
|
56
|
+
concept's `defineFields` schema.
|
|
57
|
+
|
|
58
|
+
The `date` field ships a built-in publish-clarity hint ("Sets the date for this post. Publishing is a
|
|
59
|
+
separate step you choose.") when the field sets no `description`, so a new site gets the reassurance
|
|
60
|
+
without writing per-field copy. A field-level `description` overrides it; the hint cannot be turned
|
|
61
|
+
off, only replaced.
|
|
62
|
+
|
|
63
|
+
The adapter gains an optional `supportContact`: an email, a URL, or a name and instruction the
|
|
64
|
+
in-admin help points a stuck editor to. It passes through to the runtime untouched, and the help
|
|
65
|
+
renders the hand-off only when it is set, so there is never a button to a blank contact.
|
|
66
|
+
|
|
67
|
+
The admin design system documents the recipes the help shell will follow, including the non-modal help
|
|
68
|
+
region, the single right-slide-over slot, the disclosure-button ARIA contract, the getting-started
|
|
69
|
+
progress checklist, and the empty-state starter slot.
|
|
70
|
+
|
|
71
|
+
No consumer action is required. Every change is additive: the new field and adapter members are
|
|
72
|
+
optional, and a site that sets neither sees only the date field's new default hint.
|
|
73
|
+
|
|
74
|
+
## 0.60.1
|
|
75
|
+
|
|
76
|
+
A packaging fix so the library bundles cleanly in a Vite 8 consumer. It supersedes `0.60.0`, whose
|
|
77
|
+
consumer build failed on Vite 8 / Rolldown. `svelte-package` ships `.svelte` with `<script lang="ts">`
|
|
78
|
+
and the TypeScript intact, and Rolldown parses that `<script>` as JavaScript before the Svelte plugin
|
|
79
|
+
compiles the file, failing on a TypeScript optional parameter (`registry?: T` loses its type but keeps
|
|
80
|
+
the `?`). The shipped `.svelte` now carry a plain-JavaScript `<script>` body. The `lang="ts"` tag
|
|
81
|
+
stays, because the component markup still uses TypeScript that the Svelte compiler reads (typed
|
|
82
|
+
`{#snippet}` parameters and `{@const x = y as T}` casts).
|
|
83
|
+
|
|
84
|
+
No consumer action is required. The change is to the published `dist` only; the public API and the
|
|
85
|
+
types are unchanged.
|
|
86
|
+
|
|
5
87
|
## 0.60.0
|
|
6
88
|
|
|
7
89
|
<!-- release-size: minor -->
|
|
@@ -6,239 +6,142 @@ root sets `data-theme` to the resolved light or dark theme (seeded from the SSR'
|
|
|
6
6
|
flipped by the topbar toggle) and imports the self-contained Warm Stone theme, so the admin looks
|
|
7
7
|
identical on every host regardless of the site's own theme.
|
|
8
8
|
-->
|
|
9
|
-
<script lang="ts">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{ name: 'Marketing', items: [
|
|
72
|
-
{ label: 'Campaigns', icon: BlocksIcon },
|
|
73
|
-
{ label: 'Audiences', icon: BlocksIcon },
|
|
74
|
-
] },
|
|
75
|
-
{ name: 'Shop', items: [
|
|
76
|
-
{ label: 'Products', icon: BlocksIcon },
|
|
77
|
-
{ label: 'Orders', icon: BlocksIcon },
|
|
78
|
-
] },
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
// Up to two uppercase initials from the display name, falling back to '?' for an empty name.
|
|
82
|
-
function initialsOf(displayName: string): string {
|
|
83
|
-
const letters = displayName
|
|
84
|
-
.split(/\s+/)
|
|
85
|
-
.filter(Boolean)
|
|
86
|
-
.slice(0, 2)
|
|
87
|
-
.map((word) => word[0]?.toUpperCase() ?? '')
|
|
88
|
-
.join('');
|
|
89
|
-
return letters || '?';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const initials = $derived(initialsOf(data.user.displayName));
|
|
93
|
-
|
|
94
|
-
function isActive(href: string): boolean {
|
|
95
|
-
return data.pathname === href || data.pathname.startsWith(`${href}/`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Which nav groups are collapsed. Seeded once from the SSR'd cookie (so a collapsed group renders
|
|
99
|
-
// collapsed with no flash), then owned by the toggle below, which mirrors each change to the cookie.
|
|
100
|
-
let collapsed = $state(new Set(untrack(() => data.collapsedNav)));
|
|
101
|
-
|
|
102
|
-
function onToggleSection(label: string, open: boolean) {
|
|
103
|
-
const next = new Set(collapsed);
|
|
104
|
-
if (open) next.delete(label);
|
|
105
|
-
else next.add(label);
|
|
106
|
-
collapsed = next;
|
|
107
|
-
const value = [...next].map((entry) => encodeURIComponent(entry)).join(',');
|
|
108
|
-
writeAdminCookie('cairn-admin-nav-collapsed', value);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let drawerOpen = $state(false);
|
|
112
|
-
|
|
113
|
-
function onKeydown(e: KeyboardEvent) {
|
|
114
|
-
if (e.key.toLowerCase() === 'b' && (e.metaKey || e.ctrlKey)) {
|
|
115
|
-
e.preventDefault();
|
|
116
|
-
drawerOpen = !drawerOpen;
|
|
117
|
-
}
|
|
118
|
-
if (e.key.toLowerCase() === 'k' && (e.metaKey || e.ctrlKey)) {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
openPalette();
|
|
121
|
-
}
|
|
9
|
+
<script lang="ts">import { onMount, setContext, untrack } from "svelte";
|
|
10
|
+
import CsrfField from "./CsrfField.svelte";
|
|
11
|
+
import { CSRF_CONTEXT_KEY } from "./csrf-context.js";
|
|
12
|
+
import { provideTopbar } from "./topbar-context.js";
|
|
13
|
+
import { MenuIcon, LogOutIcon, SunIcon, MoonIcon, ChevronRightIcon, SearchIcon } from "./admin-icons.js";
|
|
14
|
+
import CairnLogo from "./CairnLogo.svelte";
|
|
15
|
+
import { cairnFaviconHref } from "./cairn-favicon.js";
|
|
16
|
+
import { warnIfChromeWrapped } from "./chrome-guard.js";
|
|
17
|
+
import FileTextIcon from "@lucide/svelte/icons/file-text";
|
|
18
|
+
import SignpostIcon from "@lucide/svelte/icons/signpost";
|
|
19
|
+
import SettingsIcon from "@lucide/svelte/icons/settings";
|
|
20
|
+
import UsersIcon from "@lucide/svelte/icons/users";
|
|
21
|
+
import ImageIcon from "@lucide/svelte/icons/image";
|
|
22
|
+
import BlocksIcon from "@lucide/svelte/icons/blocks";
|
|
23
|
+
import ExternalLinkIcon from "@lucide/svelte/icons/external-link";
|
|
24
|
+
import HelpCircleIcon from "@lucide/svelte/icons/circle-help";
|
|
25
|
+
import "./cairn-admin.css";
|
|
26
|
+
let { data, children } = $props();
|
|
27
|
+
setContext(CSRF_CONTEXT_KEY, () => data.csrf);
|
|
28
|
+
function writeAdminCookie(name, value) {
|
|
29
|
+
document.cookie = `${name}=${value}; path=/admin; max-age=31536000; samesite=lax`;
|
|
30
|
+
}
|
|
31
|
+
const coreItems = $derived([
|
|
32
|
+
...data.concepts.map((c) => ({ label: c.label, icon: FileTextIcon, href: `/admin/${c.id}` })),
|
|
33
|
+
// Media is a content peer, immediately after the concepts.
|
|
34
|
+
{ label: "Media", icon: ImageIcon, href: "/admin/media" },
|
|
35
|
+
...data.navLabel ? [{ label: data.navLabel, icon: SignpostIcon, href: "/admin/nav" }] : [],
|
|
36
|
+
{ label: "Settings", icon: SettingsIcon, href: "/admin/settings" },
|
|
37
|
+
...data.canManageEditors ? [{ label: "Editors", icon: UsersIcon, href: "/admin/editors" }] : []
|
|
38
|
+
]);
|
|
39
|
+
const extensionGroups = [
|
|
40
|
+
{ name: "Marketing", items: [
|
|
41
|
+
{ label: "Campaigns", icon: BlocksIcon },
|
|
42
|
+
{ label: "Audiences", icon: BlocksIcon }
|
|
43
|
+
] },
|
|
44
|
+
{ name: "Shop", items: [
|
|
45
|
+
{ label: "Products", icon: BlocksIcon },
|
|
46
|
+
{ label: "Orders", icon: BlocksIcon }
|
|
47
|
+
] }
|
|
48
|
+
];
|
|
49
|
+
function initialsOf(displayName) {
|
|
50
|
+
const letters = displayName.split(/\s+/).filter(Boolean).slice(0, 2).map((word) => word[0]?.toUpperCase() ?? "").join("");
|
|
51
|
+
return letters || "?";
|
|
52
|
+
}
|
|
53
|
+
const initials = $derived(initialsOf(data.user.displayName));
|
|
54
|
+
function isActive(href) {
|
|
55
|
+
return data.pathname === href || data.pathname.startsWith(`${href}/`);
|
|
56
|
+
}
|
|
57
|
+
let collapsed = $state(new Set(untrack(() => data.collapsedNav)));
|
|
58
|
+
function onToggleSection(label, open) {
|
|
59
|
+
const next = new Set(collapsed);
|
|
60
|
+
if (open) next.delete(label);
|
|
61
|
+
else next.add(label);
|
|
62
|
+
collapsed = next;
|
|
63
|
+
const value = [...next].map((entry) => encodeURIComponent(entry)).join(",");
|
|
64
|
+
writeAdminCookie("cairn-admin-nav-collapsed", value);
|
|
65
|
+
}
|
|
66
|
+
let drawerOpen = $state(false);
|
|
67
|
+
function onKeydown(e) {
|
|
68
|
+
if (e.key.toLowerCase() === "b" && (e.metaKey || e.ctrlKey)) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
drawerOpen = !drawerOpen;
|
|
122
71
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// close() against a result link's own navigation, which would cancel it.
|
|
127
|
-
$effect(() => {
|
|
128
|
-
data.pathname;
|
|
129
|
-
drawerOpen = false;
|
|
130
|
-
paletteDialog?.close();
|
|
131
|
-
publishAllDialog?.close();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Seed from the SSR'd theme once. The live theme is owned by this state and the toggle, so the
|
|
135
|
-
// initial read of data.theme is intentional and untracked to keep it out of any reactive graph.
|
|
136
|
-
let theme = $state<'cairn-admin' | 'cairn-admin-dark'>(untrack(() => data.theme));
|
|
137
|
-
|
|
138
|
-
// First mount with no persisted choice follows the OS preference. A returning user's cookie was
|
|
139
|
-
// already honored by the layout load (data.theme), so this only fires on a first-ever visit.
|
|
140
|
-
$effect(() => {
|
|
141
|
-
const hasCookie = document.cookie.split('; ').some((c) => c.startsWith('cairn-admin-theme='));
|
|
142
|
-
if (!hasCookie && window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
|
|
143
|
-
theme = 'cairn-admin-dark';
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
function toggleTheme() {
|
|
148
|
-
theme = theme === 'cairn-admin' ? 'cairn-admin-dark' : 'cairn-admin';
|
|
149
|
-
writeAdminCookie('cairn-admin-theme', theme);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// The command palette: a quick jump-to over the admin's destinations plus a couple of actions, so
|
|
153
|
-
// the topbar carries something productive. Opened by the topbar trigger or Cmd/Ctrl+K.
|
|
154
|
-
interface Command {
|
|
155
|
-
label: string;
|
|
156
|
-
icon: Component;
|
|
157
|
-
href?: string;
|
|
158
|
-
external?: boolean;
|
|
159
|
-
action?: () => void;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let paletteDialog = $state<HTMLDialogElement>();
|
|
163
|
-
let paletteList = $state<HTMLUListElement>();
|
|
164
|
-
let paletteQuery = $state('');
|
|
165
|
-
|
|
166
|
-
// The site-wide publish action. The trigger and its confirm render only while entries are
|
|
167
|
-
// pending; a null pendingEntries (GitHub unreachable) hides them rather than showing a stale count.
|
|
168
|
-
let publishAllDialog = $state<HTMLDialogElement>();
|
|
169
|
-
const pendingCount = $derived(data.pendingEntries?.length ?? 0);
|
|
170
|
-
// The pending ids grouped under their concept's nav label, in first-seen order. A ref whose
|
|
171
|
-
// concept is not in the nav (an unconfigured key) falls back to the raw key.
|
|
172
|
-
const pendingGroups = $derived.by(() => {
|
|
173
|
-
const groups = new Map<string, string[]>();
|
|
174
|
-
for (const entry of data.pendingEntries ?? []) {
|
|
175
|
-
const label = data.concepts.find((c) => c.id === entry.concept)?.label ?? entry.concept;
|
|
176
|
-
groups.set(label, [...(groups.get(label) ?? []), entry.id]);
|
|
177
|
-
}
|
|
178
|
-
return [...groups.entries()].map(([label, ids]) => ({ label, ids }));
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// The bare data-theme wrapper is the admin root the dev chrome-guard measures from.
|
|
182
|
-
let rootEl = $state<HTMLElement>();
|
|
183
|
-
onMount(() => {
|
|
184
|
-
if (rootEl) warnIfChromeWrapped(rootEl);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const paletteCommands = $derived<Command[]>([
|
|
188
|
-
...coreItems.map((item) => ({ label: item.label, icon: item.icon, href: item.href })),
|
|
189
|
-
{ label: 'View the live site', icon: ExternalLinkIcon, href: '/', external: true },
|
|
190
|
-
theme === 'cairn-admin'
|
|
191
|
-
? { label: 'Switch to dark mode', icon: MoonIcon, action: toggleTheme }
|
|
192
|
-
: { label: 'Switch to light mode', icon: SunIcon, action: toggleTheme },
|
|
193
|
-
]);
|
|
194
|
-
const paletteResults = $derived(
|
|
195
|
-
paletteCommands.filter((c) => c.label.toLowerCase().includes(paletteQuery.trim().toLowerCase())),
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
function openPalette() {
|
|
199
|
-
if (paletteDialog?.open) return; // showModal throws on an already-open dialog
|
|
200
|
-
paletteQuery = '';
|
|
201
|
-
paletteDialog?.showModal();
|
|
72
|
+
if (e.key.toLowerCase() === "k" && (e.metaKey || e.ctrlKey)) {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
openPalette();
|
|
202
75
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
76
|
+
}
|
|
77
|
+
$effect(() => {
|
|
78
|
+
data.pathname;
|
|
79
|
+
drawerOpen = false;
|
|
80
|
+
paletteDialog?.close();
|
|
81
|
+
publishAllDialog?.close();
|
|
82
|
+
});
|
|
83
|
+
let theme = $state(untrack(() => data.theme));
|
|
84
|
+
$effect(() => {
|
|
85
|
+
const hasCookie = document.cookie.split("; ").some((c) => c.startsWith("cairn-admin-theme="));
|
|
86
|
+
if (!hasCookie && window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
|
|
87
|
+
theme = "cairn-admin-dark";
|
|
208
88
|
}
|
|
209
|
-
|
|
210
|
-
|
|
89
|
+
});
|
|
90
|
+
function toggleTheme() {
|
|
91
|
+
theme = theme === "cairn-admin" ? "cairn-admin-dark" : "cairn-admin";
|
|
92
|
+
writeAdminCookie("cairn-admin-theme", theme);
|
|
93
|
+
}
|
|
94
|
+
let paletteDialog = $state();
|
|
95
|
+
let paletteList = $state();
|
|
96
|
+
let paletteQuery = $state("");
|
|
97
|
+
let publishAllDialog = $state();
|
|
98
|
+
const pendingCount = $derived(data.pendingEntries?.length ?? 0);
|
|
99
|
+
const pendingGroups = $derived.by(() => {
|
|
100
|
+
const groups = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const entry of data.pendingEntries ?? []) {
|
|
102
|
+
const label = data.concepts.find((c) => c.id === entry.concept)?.label ?? entry.concept;
|
|
103
|
+
groups.set(label, [...groups.get(label) ?? [], entry.id]);
|
|
211
104
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
105
|
+
return [...groups.entries()].map(([label, ids]) => ({ label, ids }));
|
|
106
|
+
});
|
|
107
|
+
let rootEl = $state();
|
|
108
|
+
onMount(() => {
|
|
109
|
+
if (rootEl) warnIfChromeWrapped(rootEl);
|
|
110
|
+
});
|
|
111
|
+
const paletteCommands = $derived([
|
|
112
|
+
...coreItems.map((item) => ({ label: item.label, icon: item.icon, href: item.href })),
|
|
113
|
+
{ label: "Help", icon: HelpCircleIcon, href: "/admin/help" },
|
|
114
|
+
{ label: "View the live site", icon: ExternalLinkIcon, href: "/", external: true },
|
|
115
|
+
theme === "cairn-admin" ? { label: "Switch to dark mode", icon: MoonIcon, action: toggleTheme } : { label: "Switch to light mode", icon: SunIcon, action: toggleTheme }
|
|
116
|
+
]);
|
|
117
|
+
const paletteResults = $derived(
|
|
118
|
+
paletteCommands.filter((c) => c.label.toLowerCase().includes(paletteQuery.trim().toLowerCase()))
|
|
119
|
+
);
|
|
120
|
+
function openPalette() {
|
|
121
|
+
if (paletteDialog?.open) return;
|
|
122
|
+
paletteQuery = "";
|
|
123
|
+
paletteDialog?.showModal();
|
|
124
|
+
}
|
|
125
|
+
function runCommand(cmd) {
|
|
126
|
+
paletteDialog?.close();
|
|
127
|
+
cmd.action?.();
|
|
128
|
+
}
|
|
129
|
+
function submitPalette() {
|
|
130
|
+
paletteList?.querySelector("a, button")?.click();
|
|
131
|
+
}
|
|
132
|
+
const crumbs = $derived.by(() => {
|
|
133
|
+
const segs = data.pathname.split("/").filter(Boolean);
|
|
134
|
+
if (segs.length < 2 || segs[0] !== "admin") return [];
|
|
135
|
+
const conceptId = segs[1];
|
|
136
|
+
const concept = data.concepts.find((c) => c.id === conceptId);
|
|
137
|
+
const out = [{ label: concept?.label ?? conceptId, href: `/admin/${conceptId}` }];
|
|
138
|
+
if (segs[2]) out.push({ label: decodeURIComponent(segs[2]) });
|
|
139
|
+
return out;
|
|
140
|
+
});
|
|
141
|
+
const pageTitle = $derived(crumbs.length ? crumbs[crumbs.length - 1].label : "Admin");
|
|
142
|
+
const isDeskRoute = $derived(data.pathname.split("/").filter(Boolean).length > 2);
|
|
143
|
+
let topbar = $state({ desk: null, zen: false });
|
|
144
|
+
provideTopbar(topbar);
|
|
242
145
|
</script>
|
|
243
146
|
|
|
244
147
|
<svelte:head>
|
|
@@ -478,6 +381,26 @@ identical on every host regardless of the site's own theme.
|
|
|
478
381
|
{/each}
|
|
479
382
|
</div>
|
|
480
383
|
|
|
384
|
+
<!-- Help is a standing utility destination, pinned at the foot of the nav and set apart from
|
|
385
|
+
the content concepts by a top hairline. It is always present, labeled in plain text, and
|
|
386
|
+
styled as a peer of the nav items above it. -->
|
|
387
|
+
<div class="flex-none border-t border-[var(--cairn-card-border)] px-2 py-2">
|
|
388
|
+
<ul class="menu menu-sm w-full gap-0.5 p-0">
|
|
389
|
+
<li>
|
|
390
|
+
<a
|
|
391
|
+
href="/admin/help"
|
|
392
|
+
class={isActive('/admin/help')
|
|
393
|
+
? 'bg-primary/10 font-semibold text-primary'
|
|
394
|
+
: 'font-medium text-[var(--color-subtle)]'}
|
|
395
|
+
aria-current={isActive('/admin/help') ? 'page' : undefined}
|
|
396
|
+
>
|
|
397
|
+
<HelpCircleIcon class="h-4 w-4" aria-hidden="true" />
|
|
398
|
+
Help
|
|
399
|
+
</a>
|
|
400
|
+
</li>
|
|
401
|
+
</ul>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
481
404
|
<div class="flex-none border-t border-[var(--cairn-card-border)] px-5 py-4">
|
|
482
405
|
<div class="flex items-center gap-3">
|
|
483
406
|
<div class="avatar avatar-placeholder">
|
|
@@ -5,48 +5,17 @@ component for every admin view, feeding it the discriminated `AdminData` from `c
|
|
|
5
5
|
load. It is a pure switcher on `data.view`: the public auth pages mount bare, and the authed views
|
|
6
6
|
mount inside `AdminLayout`. No styling or wrapper elements of its own.
|
|
7
7
|
-->
|
|
8
|
-
<script lang="ts">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import type { ContentFormFailure } from '../sveltekit/content-routes.js';
|
|
20
|
-
import type { ComponentRegistry } from '../render/registry.js';
|
|
21
|
-
import type { IconSet } from '../render/glyph.js';
|
|
22
|
-
import type { LinkResolve } from '../content/links.js';
|
|
23
|
-
import type { MediaResolve } from '../render/resolve-media.js';
|
|
24
|
-
|
|
25
|
-
interface Props {
|
|
26
|
-
/** The discriminated view data from `createCairnAdmin`'s load. */
|
|
27
|
-
data: AdminData;
|
|
28
|
-
/** The last action's result, forwarded to whichever view rendered: the shared content-action
|
|
29
|
-
* failure family (every failure carries `error`), merged with the auth and editors results,
|
|
30
|
-
* so the route's one `form` export covers every view. */
|
|
31
|
-
form?:
|
|
32
|
-
| (ContentFormFailure & {
|
|
33
|
-
sent?: boolean;
|
|
34
|
-
status?: 'sent' | 'send_error' | 'throttled';
|
|
35
|
-
ok?: boolean;
|
|
36
|
-
})
|
|
37
|
-
| null;
|
|
38
|
-
/** The site's design-accurate render pipeline, for the edit view's preview pane. */
|
|
39
|
-
render?: (
|
|
40
|
-
md: string,
|
|
41
|
-
opts?: { stagger?: boolean; resolve?: LinkResolve; resolveMedia?: MediaResolve },
|
|
42
|
-
) => string | Promise<string>;
|
|
43
|
-
/** The site's component registry, for the edit view's insert palette. */
|
|
44
|
-
registry?: ComponentRegistry;
|
|
45
|
-
/** The site's icon set, for the edit view's guided form fields. */
|
|
46
|
-
icons?: IconSet;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let { data, form = null, render, registry, icons }: Props = $props();
|
|
8
|
+
<script lang="ts">import AdminLayout from "./AdminLayout.svelte";
|
|
9
|
+
import LoginPage from "./LoginPage.svelte";
|
|
10
|
+
import ConfirmPage from "./ConfirmPage.svelte";
|
|
11
|
+
import ConceptList from "./ConceptList.svelte";
|
|
12
|
+
import EditPage from "./EditPage.svelte";
|
|
13
|
+
import ManageEditors from "./ManageEditors.svelte";
|
|
14
|
+
import NavTree from "./NavTree.svelte";
|
|
15
|
+
import CairnMediaLibrary from "./CairnMediaLibrary.svelte";
|
|
16
|
+
import CairnTidySettings from "./CairnTidySettings.svelte";
|
|
17
|
+
import HelpHome from "./HelpHome.svelte";
|
|
18
|
+
let { data, form = null, render, registry, icons } = $props();
|
|
50
19
|
</script>
|
|
51
20
|
|
|
52
21
|
{#if data.view === 'login'}
|
|
@@ -72,6 +41,8 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
|
|
|
72
41
|
<CairnMediaLibrary data={data.page} {form} />
|
|
73
42
|
{:else if data.view === 'settings'}
|
|
74
43
|
<CairnTidySettings data={data.page} />
|
|
44
|
+
{:else if data.view === 'help'}
|
|
45
|
+
<HelpHome data={data.page} />
|
|
75
46
|
{/if}
|
|
76
47
|
</AdminLayout>
|
|
77
48
|
{/if}
|
|
@@ -7,12 +7,7 @@ accessible name.
|
|
|
7
7
|
Artwork: the "cairn" icon from the Temaki icon set (https://github.com/ideditor/temaki), released into
|
|
8
8
|
the public domain under CC0 1.0 (no attribution required). Recorded here as provenance.
|
|
9
9
|
-->
|
|
10
|
-
<script lang="ts">
|
|
11
|
-
interface Props {
|
|
12
|
-
/** Utility classes for sizing and color, e.g. `h-7 w-7 text-primary`. */
|
|
13
|
-
class?: string;
|
|
14
|
-
}
|
|
15
|
-
let { class: className = '' }: Props = $props();
|
|
10
|
+
<script lang="ts">let { class: className = "" } = $props();
|
|
16
11
|
</script>
|
|
17
12
|
|
|
18
13
|
<svg
|