@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
|
@@ -23,10 +23,12 @@ import remarkDirective from 'remark-directive';
|
|
|
23
23
|
import { visit } from 'unist-util-visit';
|
|
24
24
|
import { parseMediaToken } from '../media/reference.js';
|
|
25
25
|
import { escapeLinkText } from './links.js';
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Drop any span that overlaps a span already kept, in source order. A final safety net so two
|
|
27
28
|
* splices can never target the same or overlapping bytes and clobber each other into a corrupt
|
|
28
29
|
* result, no matter how the locating arms behaved. A pure-insert span (`start === end`) overlaps
|
|
29
|
-
* another span only when it sits strictly inside it, so adjacent inserts and edits are kept.
|
|
30
|
+
* another span only when it sits strictly inside it, so adjacent inserts and edits are kept.
|
|
31
|
+
*/
|
|
30
32
|
function dropOverlappingEdits(edits) {
|
|
31
33
|
const kept = [];
|
|
32
34
|
for (const e of edits) {
|
|
@@ -36,29 +38,37 @@ function dropOverlappingEdits(edits) {
|
|
|
36
38
|
}
|
|
37
39
|
return kept;
|
|
38
40
|
}
|
|
39
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* A locating scan for candidate `media:` token substrings. Deliberately broad (it accepts
|
|
40
43
|
* uppercase and other out-of-grammar characters) so a malformed token is still found and then
|
|
41
44
|
* rejected by parseMediaToken, never silently skipped by the locator. The character class stops at
|
|
42
45
|
* whitespace, a quote, or any YAML or markdown delimiter, so a frontmatter value or an image
|
|
43
|
-
* destination ends the candidate.
|
|
46
|
+
* destination ends the candidate.
|
|
47
|
+
*/
|
|
44
48
|
const MEDIA_TOKEN_SCAN = /media:[A-Za-z0-9._-]+/g;
|
|
45
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Split a leading frontmatter block off the markdown. `fmBlock` is the `---` fenced block including
|
|
46
51
|
* both fences and the trailing newline (empty when there is none); `body` is everything after it.
|
|
47
52
|
* The block leads the document, so a frontmatter offset is already absolute and a body offset needs
|
|
48
|
-
* `fmBlock.length` added. Shared by every arm so they agree on the boundary.
|
|
53
|
+
* `fmBlock.length` added. Shared by every arm so they agree on the boundary.
|
|
54
|
+
*/
|
|
49
55
|
function splitFrontmatter(markdown) {
|
|
50
56
|
const m = markdown.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|
|
51
57
|
const fmBlock = m ? m[0] : '';
|
|
52
58
|
return { fmBlock, body: markdown.slice(fmBlock.length) };
|
|
53
59
|
}
|
|
54
|
-
/**
|
|
55
|
-
*
|
|
60
|
+
/**
|
|
61
|
+
* Parse a doc with the figure-aware pipeline, so the body arm agrees with what remarkFigure renders
|
|
62
|
+
* and can see the enclosing `:::figure` container. Mirrors parseFigureDoc in markdown-format.ts.
|
|
63
|
+
*/
|
|
56
64
|
function parseFigureDoc(doc) {
|
|
57
65
|
return unified().use(remarkParse).use(remarkGfm).use(remarkDirective).parse(doc);
|
|
58
66
|
}
|
|
59
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Whether `target` sits inside a `figure`-named container directive. Walks the tree to find the
|
|
60
69
|
* ancestor, since unist-util-visit's per-call ancestors are not retained across the traversal.
|
|
61
|
-
* Mirrors enclosingFigure in markdown-format.ts, reduced to a boolean.
|
|
70
|
+
* Mirrors enclosingFigure in markdown-format.ts, reduced to a boolean.
|
|
71
|
+
*/
|
|
62
72
|
function inFigure(tree, target) {
|
|
63
73
|
let found = false;
|
|
64
74
|
visit(tree, 'containerDirective', (dir) => {
|
|
@@ -71,8 +81,10 @@ function inFigure(tree, target) {
|
|
|
71
81
|
});
|
|
72
82
|
return found;
|
|
73
83
|
}
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
84
|
+
/**
|
|
85
|
+
* Split fmBlock into lines once, so the locator helpers walk a shared structure instead of
|
|
86
|
+
* re-scanning the block per call.
|
|
87
|
+
*/
|
|
76
88
|
function fmLines(fmBlock) {
|
|
77
89
|
const lines = [];
|
|
78
90
|
let pos = 0;
|
|
@@ -86,12 +98,14 @@ function fmLines(fmBlock) {
|
|
|
86
98
|
}
|
|
87
99
|
return lines;
|
|
88
100
|
}
|
|
89
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* The inclusive line-index range `[lo, hi]` of the block-style mapping a top-level key opens: the
|
|
90
103
|
* line `^<key>:` at indent 0 through the last line before the next top-level key (or the document
|
|
91
104
|
* end). A flow-style value (`key: { ... }` all on one line) yields a single-line range. Returns null
|
|
92
105
|
* when the key has no top-level line, which a malformed or non-canonical block can cause. Scoping the
|
|
93
106
|
* per-key search to this range is what lets two image fields that share one hash, or an image field
|
|
94
|
-
* whose hash also appears in a sibling text value, resolve to distinct, correct spans.
|
|
107
|
+
* whose hash also appears in a sibling text value, resolve to distinct, correct spans.
|
|
108
|
+
*/
|
|
95
109
|
function frontmatterKeyRange(lines, fmBlock, key) {
|
|
96
110
|
const opener = new RegExp(`^${escapeForRegExp(key)}:`);
|
|
97
111
|
const topLevelKey = /^[^\s#][^:]*:/;
|
|
@@ -118,10 +132,12 @@ function frontmatterKeyRange(lines, fmBlock, key) {
|
|
|
118
132
|
}
|
|
119
133
|
return [lo, hi];
|
|
120
134
|
}
|
|
121
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Find the block-style `src:` line within `[lo, hi]` whose value token parses to `hash`. The token
|
|
122
137
|
* is located by the broad scan and validated through parseMediaToken (matching on hash), so a
|
|
123
138
|
* malformed token is found then rejected. Returns null for a flow-style value (no own `src:` line),
|
|
124
|
-
* which leaves that shape unanchorable rather than splicing a guessed span.
|
|
139
|
+
* which leaves that shape unanchorable rather than splicing a guessed span.
|
|
140
|
+
*/
|
|
125
141
|
function findSrcLineInRange(lines, fmBlock, range, hash) {
|
|
126
142
|
const srcKeyRe = /^(\s*)src:[ \t]?/;
|
|
127
143
|
for (let i = range[0]; i <= range[1]; i += 1) {
|
|
@@ -149,11 +165,13 @@ function findSrcLineInRange(lines, fmBlock, range, hash) {
|
|
|
149
165
|
}
|
|
150
166
|
return null;
|
|
151
167
|
}
|
|
152
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* The image-like top-level frontmatter keys whose `src` parses to `hash`, in source order. A key is
|
|
153
170
|
* image-like when its value is an object carrying a string `src`; this is the same shape
|
|
154
171
|
* extractMediaRefs reads, so a token in a plain-text value (a `title:`/`note:`) is never treated as a
|
|
155
172
|
* reference. The bucket-classifying data comes from gray-matter (which handles every quoting form);
|
|
156
|
-
* the byte edit is located structurally by the caller, keyed back to this key name.
|
|
173
|
+
* the byte edit is located structurally by the caller, keyed back to this key name.
|
|
174
|
+
*/
|
|
157
175
|
function imageFieldKeys(data, hash) {
|
|
158
176
|
const out = [];
|
|
159
177
|
for (const [key, value] of Object.entries(data)) {
|
|
@@ -169,11 +187,13 @@ function imageFieldKeys(data, hash) {
|
|
|
169
187
|
}
|
|
170
188
|
return out;
|
|
171
189
|
}
|
|
172
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Collect hero src-token edits inside the frontmatter block. Only an image-field `src:` line is
|
|
173
192
|
* rewritten: the structure is read via gray-matter (image-like keys), and each key's `src:` line is
|
|
174
193
|
* located structurally within that key's block. A `media:` token sitting in a plain-text value (a
|
|
175
194
|
* `title:` or `description:`) is on no `src:` line, so it is left untouched, keeping the byte-exact
|
|
176
|
-
* contract and agreeing with extractMediaRefs. A flow-style hero has no `src:` line and is skipped.
|
|
195
|
+
* contract and agreeing with extractMediaRefs. A flow-style hero has no `src:` line and is skipped.
|
|
196
|
+
*/
|
|
177
197
|
function frontmatterEdits(markdown, fmBlock, oldHash) {
|
|
178
198
|
if (fmBlock === '')
|
|
179
199
|
return [];
|
|
@@ -191,10 +211,12 @@ function frontmatterEdits(markdown, fmBlock, oldHash) {
|
|
|
191
211
|
}
|
|
192
212
|
return edits;
|
|
193
213
|
}
|
|
194
|
-
/**
|
|
214
|
+
/**
|
|
215
|
+
* Locate the exact `media:` token substring inside one image node's source span. The destination
|
|
195
216
|
* begins at the `](` that follows the alt text, so the search starts there to avoid a false match on
|
|
196
217
|
* a `media:`-like string inside the alt. Returns null when the token cannot be located, which leaves
|
|
197
|
-
* the image untouched rather than splicing a guessed range.
|
|
218
|
+
* the image untouched rather than splicing a guessed range.
|
|
219
|
+
*/
|
|
198
220
|
function locateImageToken(span, url) {
|
|
199
221
|
const destStart = span.indexOf('](');
|
|
200
222
|
const from = destStart === -1 ? 0 : destStart + 2;
|
|
@@ -203,9 +225,11 @@ function locateImageToken(span, url) {
|
|
|
203
225
|
return null;
|
|
204
226
|
return { start: at, end: at + url.length };
|
|
205
227
|
}
|
|
206
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* Find every body image whose url parses to `hash`, in source order, with absolute offsets. Parses
|
|
207
230
|
* with the figure-aware pipeline, so a `media:` token inside a code span or fence is not an image
|
|
208
|
-
* node and is correctly skipped, matching extractMediaRefs.
|
|
231
|
+
* node and is correctly skipped, matching extractMediaRefs.
|
|
232
|
+
*/
|
|
209
233
|
function matchedBodyImages(body, blockLength, hash) {
|
|
210
234
|
const tree = parseFigureDoc(body);
|
|
211
235
|
const hits = [];
|
|
@@ -226,9 +250,11 @@ function matchedBodyImages(body, blockLength, hash) {
|
|
|
226
250
|
});
|
|
227
251
|
return hits;
|
|
228
252
|
}
|
|
229
|
-
/**
|
|
253
|
+
/**
|
|
254
|
+
* Collect body edits over the body slice. Each matching image is located within its own source span
|
|
230
255
|
* and recorded with an absolute offset. The kind is 'figure' when the image is inside a `:::figure`,
|
|
231
|
-
* else 'body'.
|
|
256
|
+
* else 'body'.
|
|
257
|
+
*/
|
|
232
258
|
function bodyEdits(body, blockLength, oldHash) {
|
|
233
259
|
const edits = [];
|
|
234
260
|
for (const hit of matchedBodyImages(body, blockLength, oldHash)) {
|
|
@@ -276,14 +302,18 @@ export function repointMediaRef(markdown, oldHash, newToken) {
|
|
|
276
302
|
}
|
|
277
303
|
return { markdown: out, placements };
|
|
278
304
|
}
|
|
279
|
-
/**
|
|
305
|
+
/**
|
|
306
|
+
* Classify an existing alt into its non-decorative bucket: an empty (or whitespace-only) alt is
|
|
280
307
|
* filled, a non-empty alt is a custom alt the caller may opt in to overwrite. Mirrors the empty-alt
|
|
281
|
-
* test findMediaImagesNeedingAlt uses.
|
|
308
|
+
* test findMediaImagesNeedingAlt uses.
|
|
309
|
+
*/
|
|
282
310
|
function classifyAlt(existing) {
|
|
283
311
|
return existing.trim() === '' ? 'will-fill' : 'customized';
|
|
284
312
|
}
|
|
285
|
-
/**
|
|
286
|
-
*
|
|
313
|
+
/**
|
|
314
|
+
* Whether a bucket plus the overwrite choice means the alt text is actually rewritten. A will-fill
|
|
315
|
+
* always writes; a customized alt writes only on opt-in; a decorative hero never writes.
|
|
316
|
+
*/
|
|
287
317
|
function altIsEdited(bucket, overwrite) {
|
|
288
318
|
if (bucket === 'will-fill')
|
|
289
319
|
return true;
|
|
@@ -291,10 +321,12 @@ function altIsEdited(bucket, overwrite) {
|
|
|
291
321
|
return overwrite;
|
|
292
322
|
return false;
|
|
293
323
|
}
|
|
294
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* Collect the body and figure alt edits over the body slice. The alt source span sits between ` is spliced there. The existing alt is the parser's already
|
|
297
|
-
* unescaped `node.alt`. A body image has no decorative slot, so an empty alt is always will-fill.
|
|
328
|
+
* unescaped `node.alt`. A body image has no decorative slot, so an empty alt is always will-fill.
|
|
329
|
+
*/
|
|
298
330
|
function bodyAltEdits(body, blockLength, hash, defaultAlt, overwrite) {
|
|
299
331
|
const edits = [];
|
|
300
332
|
for (const hit of matchedBodyImages(body, blockLength, hash)) {
|
|
@@ -333,17 +365,21 @@ function bodyAltEdits(body, blockLength, hash, defaultAlt, overwrite) {
|
|
|
333
365
|
}
|
|
334
366
|
return edits;
|
|
335
367
|
}
|
|
336
|
-
/**
|
|
337
|
-
*
|
|
368
|
+
/**
|
|
369
|
+
* Escape a literal string for safe interpolation into a RegExp source. A key name or an indent is
|
|
370
|
+
* matched literally, so its characters must not act as metacharacters.
|
|
371
|
+
*/
|
|
338
372
|
function escapeForRegExp(literal) {
|
|
339
373
|
return literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
340
374
|
}
|
|
341
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* Find a sibling key line (`alt:` or `decorative:`) at exactly `indent` within the inclusive
|
|
342
377
|
* line-index range `[lo, hi]` of one mapping. The range is the mapping's own block, so the search
|
|
343
378
|
* spans the whole mapping rather than a same-indent contiguous run: a blank line or a deeper-nested
|
|
344
379
|
* child between `src:` and `alt:` no longer hides the existing key (which would otherwise insert a
|
|
345
380
|
* duplicate key and break the YAML). Returns the key line's value span (after the key and its space,
|
|
346
|
-
* to end of line) or null when the mapping has no such key at that indent.
|
|
381
|
+
* to end of line) or null when the mapping has no such key at that indent.
|
|
382
|
+
*/
|
|
347
383
|
function findSiblingKeyValue(lines, fmBlock, range, indent, key) {
|
|
348
384
|
const keyRe = new RegExp(`^${escapeForRegExp(indent)}${escapeForRegExp(key)}:[ \\t]?`);
|
|
349
385
|
for (let i = range[0]; i <= range[1]; i += 1) {
|
|
@@ -354,7 +390,8 @@ function findSiblingKeyValue(lines, fmBlock, range, indent, key) {
|
|
|
354
390
|
}
|
|
355
391
|
return null;
|
|
356
392
|
}
|
|
357
|
-
/**
|
|
393
|
+
/**
|
|
394
|
+
* Collect the hero alt edits inside the frontmatter block. The image-field objects (and their
|
|
358
395
|
* decorative and alt values) are read via gray-matter to classify the bucket robustly across quoting
|
|
359
396
|
* forms; the byte edit is then located structurally, scoped to each field's own mapping block, keyed
|
|
360
397
|
* back by the top-level field name. Iterating the fields in source order keeps the hero placements in
|
|
@@ -363,7 +400,8 @@ function findSiblingKeyValue(lines, fmBlock, range, indent, key) {
|
|
|
363
400
|
* blank line or a nested child) has its value replaced; an absent one is inserted right after the
|
|
364
401
|
* `src:` line at the same indent. The new value is a JSON-quoted scalar, valid YAML that handles a
|
|
365
402
|
* colon, a quote, or an empty string. A flow-style hero (`image: { ... }`, no own `src:` line) is
|
|
366
|
-
* unanchorable, so it is reported from the gray-matter read but never spliced.
|
|
403
|
+
* unanchorable, so it is reported from the gray-matter read but never spliced.
|
|
404
|
+
*/
|
|
367
405
|
function heroAltEdits(markdown, fmBlock, hash, defaultAlt, overwrite) {
|
|
368
406
|
if (fmBlock === '')
|
|
369
407
|
return [];
|
package/dist/content/schema.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ export interface StandardInput {
|
|
|
4
4
|
frontmatter: Record<string, unknown>;
|
|
5
5
|
body: string;
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
7
|
+
/**
|
|
8
|
+
* A minimal local copy of the Standard Schema v1 interface (https://standardschema.dev), so the
|
|
9
|
+
* schema is a drop-in where the ecosystem accepts a validator, with no runtime dependency.
|
|
10
|
+
*/
|
|
9
11
|
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
10
12
|
readonly '~standard': {
|
|
11
13
|
readonly version: 1;
|
|
@@ -26,9 +28,11 @@ type StandardResult<Output> = {
|
|
|
26
28
|
readonly path?: ReadonlyArray<PropertyKey>;
|
|
27
29
|
}>;
|
|
28
30
|
};
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Map one field descriptor to the TS type of its normalized value. text, textarea, and date
|
|
30
33
|
* normalize to a string; a closed-vocabulary `tags` field to the option-union array; an `image`
|
|
31
|
-
* field to its nested object.
|
|
34
|
+
* field to its nested object.
|
|
35
|
+
*/
|
|
32
36
|
type FieldValue<K extends FrontmatterField> = K extends {
|
|
33
37
|
type: 'boolean';
|
|
34
38
|
} ? boolean : K extends {
|
|
@@ -43,8 +47,10 @@ type FieldValue<K extends FrontmatterField> = K extends {
|
|
|
43
47
|
type Prettify<T> = {
|
|
44
48
|
[K in keyof T]: T[K];
|
|
45
49
|
} & {};
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
50
|
+
/**
|
|
51
|
+
* The normalized frontmatter type inferred from a field tuple. A field declared
|
|
52
|
+
* `required: true` is a required key; every other field is optional.
|
|
53
|
+
*/
|
|
48
54
|
export type InferFields<F extends readonly FrontmatterField[]> = Prettify<{
|
|
49
55
|
[K in F[number] as K extends {
|
|
50
56
|
required: true;
|
|
@@ -54,8 +60,10 @@ export type InferFields<F extends readonly FrontmatterField[]> = Prettify<{
|
|
|
54
60
|
required: true;
|
|
55
61
|
} ? never : K['name']]?: FieldValue<K>;
|
|
56
62
|
}>;
|
|
57
|
-
/**
|
|
58
|
-
*
|
|
63
|
+
/**
|
|
64
|
+
* A concept's schema: the plain-data field projection, the generated validator, and the
|
|
65
|
+
* Standard Schema conformance property.
|
|
66
|
+
*/
|
|
59
67
|
export interface ConceptSchema<F extends readonly FrontmatterField[] = readonly FrontmatterField[]> {
|
|
60
68
|
/** The declared fields as plain serializable data, for the editor form. */
|
|
61
69
|
readonly fields: FrontmatterField[];
|
|
@@ -66,9 +74,11 @@ export interface ConceptSchema<F extends readonly FrontmatterField[] = readonly
|
|
|
66
74
|
}
|
|
67
75
|
/** Extract the inferred frontmatter type from a `ConceptSchema`. */
|
|
68
76
|
export type Infer<S> = S extends ConceptSchema<infer F> ? InferFields<F> : never;
|
|
69
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Options for `defineFields`. `refine` runs after the per-field rules pass, for cross-field and
|
|
70
79
|
* body-dependent checks. It is validation-only: it returns field-keyed errors to merge, or
|
|
71
|
-
* nothing, and never transforms the data.
|
|
80
|
+
* nothing, and never transforms the data.
|
|
81
|
+
*/
|
|
72
82
|
export interface DefineFieldsOptions<F extends readonly FrontmatterField[]> {
|
|
73
83
|
refine?: (data: InferFields<F>, body: string) => Record<string, string> | undefined;
|
|
74
84
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* True when a word is a single valid dictionary line (no whitespace, no control characters, non-empty
|
|
2
3
|
* and within the length bound). A leading "#" is rejected: parseDictionary re-reads such a line as a
|
|
3
4
|
* comment, so committing it would silently drop the word on the next read. The action uses this to
|
|
4
5
|
* reject untrusted input before the merge, so a newline or a control byte can never inject an extra
|
|
5
|
-
* line into the committed file.
|
|
6
|
+
* line into the committed file.
|
|
7
|
+
*/
|
|
6
8
|
export declare function isValidDictionaryWord(word: string, maxLength?: number): boolean;
|
|
7
9
|
/**
|
|
8
10
|
* Parse the committed dictionary file text into its word list. Comment lines (a `#` after optional
|
|
@@ -16,11 +16,13 @@ const HEADER = '# cairn personal dictionary: one word per line, sorted, kept in
|
|
|
16
16
|
// the test is for whitespace and control bytes rather than an allow-list of letters. The action runs
|
|
17
17
|
// inbound words through this before a merge.
|
|
18
18
|
const WORD_RE = /^[^\s\p{Cc}]+$/u;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* True when a word is a single valid dictionary line (no whitespace, no control characters, non-empty
|
|
20
21
|
* and within the length bound). A leading "#" is rejected: parseDictionary re-reads such a line as a
|
|
21
22
|
* comment, so committing it would silently drop the word on the next read. The action uses this to
|
|
22
23
|
* reject untrusted input before the merge, so a newline or a control byte can never inject an extra
|
|
23
|
-
* line into the committed file.
|
|
24
|
+
* line into the committed file.
|
|
25
|
+
*/
|
|
24
26
|
export function isValidDictionaryWord(word, maxLength = 64) {
|
|
25
27
|
if (word.startsWith('#'))
|
|
26
28
|
return false;
|
|
@@ -45,8 +47,10 @@ export function parseDictionary(text) {
|
|
|
45
47
|
}
|
|
46
48
|
return words;
|
|
47
49
|
}
|
|
48
|
-
/**
|
|
49
|
-
*
|
|
50
|
+
/**
|
|
51
|
+
* Case-insensitive, locale-stable comparator for the canonical sort. Words are compared lowercased
|
|
52
|
+
* so "Cairn" and "cairn" collapse to one entry, the same case-folding the Worker's merged set uses.
|
|
53
|
+
*/
|
|
50
54
|
function byWord(a, b) {
|
|
51
55
|
return a.toLowerCase().localeCompare(b.toLowerCase());
|
|
52
56
|
}
|
package/dist/content/types.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ interface FieldBase {
|
|
|
12
12
|
label: string;
|
|
13
13
|
/** A required field fails validation when empty (spec §7.4). */
|
|
14
14
|
required?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* One author-facing sentence shown under the field in the editor, in plain end-user language.
|
|
17
|
+
* Optional; render nothing when absent. Not a validation rule.
|
|
18
|
+
*/
|
|
19
|
+
description?: string;
|
|
15
20
|
}
|
|
16
21
|
/** A single-line text input. */
|
|
17
22
|
export interface TextField extends FieldBase {
|
|
@@ -22,8 +27,10 @@ export interface TextField extends FieldBase {
|
|
|
22
27
|
max?: number;
|
|
23
28
|
/** Exact required character length. */
|
|
24
29
|
length?: number;
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
30
|
+
/**
|
|
31
|
+
* A regular-expression source string the value must match. Stored as a string so the field
|
|
32
|
+
* list stays plain serializable data; the validator compiles it.
|
|
33
|
+
*/
|
|
27
34
|
pattern?: string;
|
|
28
35
|
}
|
|
29
36
|
/** A multi-line text input. */
|
|
@@ -83,8 +90,10 @@ export interface ImageField extends FieldBase {
|
|
|
83
90
|
* arm in `schema.ts`, since its value is a nested object rather than a single string.
|
|
84
91
|
*/
|
|
85
92
|
export type FrontmatterField = TextField | TextareaField | DateField | BooleanField | TagsField | FreeTagsField | ImageField;
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
93
|
+
/**
|
|
94
|
+
* The stored value of an `image` field: a `media:` reference, a screen-reader description, and an
|
|
95
|
+
* optional caption.
|
|
96
|
+
*/
|
|
88
97
|
export interface ImageValue {
|
|
89
98
|
src: string;
|
|
90
99
|
alt: string;
|
|
@@ -120,8 +129,10 @@ export interface ConceptConfig<S extends ConceptSchema = ConceptSchema> {
|
|
|
120
129
|
singular?: string;
|
|
121
130
|
/** The concept's schema: the form projection, the generated validator, and the inferred type. */
|
|
122
131
|
schema: S;
|
|
123
|
-
/**
|
|
124
|
-
*
|
|
132
|
+
/**
|
|
133
|
+
* Frontmatter keys to surface on each `ContentSummary.fields`, so a list card reads an authored
|
|
134
|
+
* field without a per-entry detail read. Each key should also be declared in `schema`.
|
|
135
|
+
*/
|
|
125
136
|
summaryFields?: string[];
|
|
126
137
|
}
|
|
127
138
|
/**
|
|
@@ -168,29 +179,39 @@ export interface NavMenuConfig {
|
|
|
168
179
|
* stylesheet.
|
|
169
180
|
*/
|
|
170
181
|
export interface PreviewConfig {
|
|
171
|
-
/**
|
|
172
|
-
*
|
|
182
|
+
/**
|
|
183
|
+
* Absolute or root-relative URLs of the site's compiled stylesheets, linked inside the
|
|
184
|
+
* preview document. A Vite `?url` import of the site's CSS resolves the hashed asset URL.
|
|
185
|
+
*/
|
|
173
186
|
stylesheets: string[];
|
|
174
187
|
/** Class list applied to the preview document's body, for theme or typography roots. */
|
|
175
188
|
bodyClass?: string;
|
|
176
|
-
/**
|
|
177
|
-
*
|
|
189
|
+
/**
|
|
190
|
+
* Class list for a wrapper element around the rendered content, reproducing the site's
|
|
191
|
+
* content container (a prose or measure class). Omitted renders the content bare.
|
|
192
|
+
*/
|
|
178
193
|
containerClass?: string;
|
|
179
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Per-concept overrides of bodyClass and containerClass, keyed by concept id. An entry's
|
|
180
196
|
* preview resolves the override for its concept over the top-level values; stylesheets are
|
|
181
|
-
* always shared.
|
|
197
|
+
* always shared.
|
|
198
|
+
*/
|
|
182
199
|
byConcept?: Record<string, {
|
|
183
200
|
bodyClass?: string;
|
|
184
201
|
containerClass?: string;
|
|
185
202
|
}>;
|
|
186
203
|
}
|
|
187
|
-
/**
|
|
188
|
-
*
|
|
204
|
+
/**
|
|
205
|
+
* The flat preview shape `editLoad` ships to the edit page: the top-level `PreviewConfig`
|
|
206
|
+
* values with the entry's concept override applied, and no `byConcept` map.
|
|
207
|
+
*/
|
|
189
208
|
export type ResolvedPreview = Omit<PreviewConfig, 'byConcept'>;
|
|
190
|
-
/**
|
|
209
|
+
/**
|
|
210
|
+
* A site's media configuration (seam 4). A site sets this to turn on R2-backed media: uploads,
|
|
191
211
|
* content-addressed storage, and Cloudflare Images variants. Omitting it leaves media off. The
|
|
192
212
|
* engine normalizes this into a `ResolvedAssetConfig` and merges the named variants over the
|
|
193
|
-
* built-in thumb, inline, card, and hero presets.
|
|
213
|
+
* built-in thumb, inline, card, and hero presets.
|
|
214
|
+
*/
|
|
194
215
|
export interface AssetConfig {
|
|
195
216
|
/** The R2 bucket binding name on the Worker, e.g. "MEDIA_BUCKET". Required when a site declares media. */
|
|
196
217
|
bucketBinding: string;
|
|
@@ -204,10 +225,12 @@ export interface AssetConfig {
|
|
|
204
225
|
allowedTypes?: string[];
|
|
205
226
|
/** Named transform presets, merged over the built-in thumb/inline/card/hero presets. */
|
|
206
227
|
variants?: Record<string, VariantSpec>;
|
|
207
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* Whether Cloudflare Image Transformations are enabled for the zone (default false). The feature
|
|
208
230
|
* is a per-zone setting that the dashboard or API turns on; it cannot be flipped from a Worker. With
|
|
209
231
|
* it off, the media resolver serves the bare full-size delivery path and ignores any preset, so
|
|
210
|
-
* thumbnails stay correct (full-size-but-correct) rather than pointing at a dead /cdn-cgi/image URL.
|
|
232
|
+
* thumbnails stay correct (full-size-but-correct) rather than pointing at a dead /cdn-cgi/image URL.
|
|
233
|
+
*/
|
|
211
234
|
transformations?: boolean;
|
|
212
235
|
}
|
|
213
236
|
/** The single seam the engine consumes. A site implements this at `src/lib/cairn.config.ts`. */
|
|
@@ -223,33 +246,49 @@ export interface CairnAdapter {
|
|
|
223
246
|
};
|
|
224
247
|
backend: BackendConfig;
|
|
225
248
|
sender: SenderConfig;
|
|
226
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* Optional contact a stuck editor is pointed to from the in-admin help (an email address, a URL,
|
|
251
|
+
* or a name and instruction). The help renders the hand-off only when this is set. Plain string,
|
|
252
|
+
* passed through verbatim.
|
|
253
|
+
*/
|
|
254
|
+
supportContact?: string;
|
|
255
|
+
/**
|
|
256
|
+
* The site's one renderer: the editor preview and every public page call it (design decision 4).
|
|
227
257
|
* `resolve` rewrites cairn: links to live permalinks; the build passes a site-resolver-backed
|
|
228
258
|
* one, the preview a manifest one. The trailing `resolveMedia` is additive and optional: the build
|
|
229
|
-
* passes a site-resolver-backed media resolver, the preview a manifest-backed one.
|
|
259
|
+
* passes a site-resolver-backed media resolver, the preview a manifest-backed one.
|
|
260
|
+
*/
|
|
230
261
|
render(md: string, opts?: {
|
|
231
262
|
stagger?: boolean;
|
|
232
263
|
resolve?: LinkResolve;
|
|
233
264
|
resolveMedia?: import('../render/resolve-media.js').MediaResolve;
|
|
234
265
|
}): string | Promise<string>;
|
|
235
|
-
/**
|
|
236
|
-
*
|
|
266
|
+
/**
|
|
267
|
+
* Repo-relative path to the committed content manifest. Defaults to src/content/.cairn/index.json
|
|
268
|
+
* in composeRuntime. It sits outside any concept directory, so content enumeration never globs it.
|
|
269
|
+
*/
|
|
237
270
|
manifestPath?: string;
|
|
238
|
-
/**
|
|
239
|
-
*
|
|
271
|
+
/**
|
|
272
|
+
* Repo-relative path to the committed media manifest. Defaults to src/content/.cairn/media.json,
|
|
273
|
+
* applied in composeRuntime. Sits outside any concept directory, like the content manifest.
|
|
274
|
+
*/
|
|
240
275
|
mediaManifestPath?: string;
|
|
241
|
-
/**
|
|
276
|
+
/**
|
|
277
|
+
* Repo-relative path to the committed personal dictionary file. Defaults to
|
|
242
278
|
* src/content/.cairn/dictionary.txt, applied in composeRuntime: the same `.cairn/` content root the
|
|
243
279
|
* manifests use, so the spec's `content/.cairn/dictionary.txt` resolves the same configurable way the
|
|
244
|
-
* manifest paths do. One word per line, sorted, comment lines allowed (see site-dictionary.ts).
|
|
280
|
+
* manifest paths do. One word per line, sorted, comment lines allowed (see site-dictionary.ts).
|
|
281
|
+
*/
|
|
245
282
|
dictionaryPath?: string;
|
|
246
283
|
/** Directive component registry; the renderer and the future palette derive from it (seam 3). */
|
|
247
284
|
registry?: ComponentRegistry;
|
|
248
285
|
/** The site's glyph name to SVG path-data map, for the admin icon picker and the renderer. */
|
|
249
286
|
icons?: IconSet;
|
|
250
287
|
navMenu?: NavMenuConfig;
|
|
251
|
-
/**
|
|
252
|
-
*
|
|
288
|
+
/**
|
|
289
|
+
* The live site's content styling for the preview frame. The admin's chrome isolation keeps
|
|
290
|
+
* the site's CSS out of the admin document, so the preview frame links these instead.
|
|
291
|
+
*/
|
|
253
292
|
preview?: PreviewConfig;
|
|
254
293
|
assets?: AssetConfig;
|
|
255
294
|
}
|
|
@@ -273,8 +312,10 @@ export interface ConceptDescriptor {
|
|
|
273
312
|
/** Concept id, the key under `content`, e.g. "posts". */
|
|
274
313
|
id: string;
|
|
275
314
|
label: string;
|
|
276
|
-
/**
|
|
277
|
-
*
|
|
315
|
+
/**
|
|
316
|
+
* The singular noun for the create affordances ("New post"); resolved from `ConceptConfig.singular`,
|
|
317
|
+
* defaulting to `label` when the config omits it.
|
|
318
|
+
*/
|
|
278
319
|
singular: string;
|
|
279
320
|
dir: string;
|
|
280
321
|
routing: RoutingRule;
|
|
@@ -283,8 +324,10 @@ export interface ConceptDescriptor {
|
|
|
283
324
|
/** Filename date-prefix granularity for a dated concept; resolved by `normalizeConcepts`. */
|
|
284
325
|
datePrefix: DatePrefix;
|
|
285
326
|
fields: FrontmatterField[];
|
|
286
|
-
/**
|
|
287
|
-
*
|
|
327
|
+
/**
|
|
328
|
+
* Frontmatter keys the index copies onto each summary's `fields` record. `normalizeConcepts`
|
|
329
|
+
* resolves it to `[]` when a concept omits `summaryFields`.
|
|
330
|
+
*/
|
|
288
331
|
summaryFields: string[];
|
|
289
332
|
validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
|
|
290
333
|
}
|
|
@@ -339,9 +382,13 @@ export interface CairnRuntime {
|
|
|
339
382
|
concepts: ConceptDescriptor[];
|
|
340
383
|
backend: BackendConfig;
|
|
341
384
|
sender: SenderConfig;
|
|
342
|
-
/** The
|
|
385
|
+
/** The support contact passed through from the adapter; the in-admin help reads it. Optional. */
|
|
386
|
+
supportContact?: string;
|
|
387
|
+
/**
|
|
388
|
+
* The site's one renderer: the editor preview and every public page call it (design decision 4).
|
|
343
389
|
* The trailing `resolveMedia` is additive and optional: the build passes a site-resolver-backed
|
|
344
|
-
* media resolver, the preview a manifest-backed one.
|
|
390
|
+
* media resolver, the preview a manifest-backed one.
|
|
391
|
+
*/
|
|
345
392
|
render(md: string, opts?: {
|
|
346
393
|
stagger?: boolean;
|
|
347
394
|
resolve?: LinkResolve;
|
|
@@ -350,15 +397,19 @@ export interface CairnRuntime {
|
|
|
350
397
|
manifestPath: string;
|
|
351
398
|
/** The repo-relative path to the committed media manifest, defaulted in composeRuntime. */
|
|
352
399
|
mediaManifestPath: string;
|
|
353
|
-
/**
|
|
400
|
+
/**
|
|
401
|
+
* The repo-relative path to the committed personal dictionary file (one word per line, sorted),
|
|
354
402
|
* defaulted in composeRuntime to src/content/.cairn/dictionary.txt: the same `.cairn/` content root
|
|
355
403
|
* the manifests use. The edit load reads it and threads its words onto EditData; the
|
|
356
404
|
* addDictionaryWord action reads, merges, and commits it. Optional on the runtime so a hand-built
|
|
357
405
|
* runtime need not set it; composeRuntime always fills it, and the edit load and the action default
|
|
358
|
-
* a missing value to the same content-root path.
|
|
406
|
+
* a missing value to the same content-root path.
|
|
407
|
+
*/
|
|
359
408
|
dictionaryPath?: string;
|
|
360
|
-
/**
|
|
361
|
-
*
|
|
409
|
+
/**
|
|
410
|
+
* The adapter's asset config resolved once at compose: `{ enabled: false }` for a no-media site,
|
|
411
|
+
* otherwise the filled config the upload, storage, delivery, and resolver paths read.
|
|
412
|
+
*/
|
|
362
413
|
resolvedAssets: import('../media/config.js').ResolvedAssetConfig;
|
|
363
414
|
registry?: ComponentRegistry;
|
|
364
415
|
/** The site's glyph name to SVG path-data map, for the admin icon picker and the renderer. */
|
|
@@ -367,18 +418,22 @@ export interface CairnRuntime {
|
|
|
367
418
|
/** The live site's content styling for the preview frame; passed through from the adapter. */
|
|
368
419
|
preview?: PreviewConfig;
|
|
369
420
|
assets?: AssetConfig;
|
|
370
|
-
/**
|
|
421
|
+
/**
|
|
422
|
+
* The editor's spellcheck dictionary file, resolved once at compose from the site config's
|
|
371
423
|
* `spellcheck.dialect` (defaulting to US English). The edit load threads it onto EditData and the
|
|
372
424
|
* editor resolves it to a real asset URL on the main thread, so the Worker receives the URL and
|
|
373
425
|
* never reads config. Just the filename, e.g. "dictionary-en-us.txt". Optional on the runtime so a
|
|
374
426
|
* hand-built runtime need not set it; composeRuntime always fills it, and the edit load defaults a
|
|
375
|
-
* missing value to the US English dictionary.
|
|
427
|
+
* missing value to the US English dictionary.
|
|
428
|
+
*/
|
|
376
429
|
spellcheckDictionary?: string;
|
|
377
|
-
/**
|
|
430
|
+
/**
|
|
431
|
+
* The editor tidy (LLM copy-edit) settings, passed through from the site config. Optional on the
|
|
378
432
|
* runtime so a hand-built runtime need not set it; composeRuntime threads it from
|
|
379
433
|
* `siteConfig.tidy`. The tidy action reads `enabled` and `model` at call time, and builds its prompt
|
|
380
434
|
* from `conventions`. Absent (or `enabled` false) means tidy is off, and the action refuses with a
|
|
381
|
-
* fail(503) before any model call.
|
|
435
|
+
* fail(503) before any model call.
|
|
436
|
+
*/
|
|
382
437
|
tidy?: import('../nav/site-config.js').TidyConfig;
|
|
383
438
|
/** Admin panels contributed by extensions (Mode 2). Empty until Plan 09 wires the dispatch route. */
|
|
384
439
|
adminPanels?: AdminPanel[];
|