@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
|
@@ -7,8 +7,10 @@ export interface Range {
|
|
|
7
7
|
from: number;
|
|
8
8
|
to: number;
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
10
|
+
/**
|
|
11
|
+
* A word extracted for lookup: the lowercased form the Worker checks, and its absolute range so a
|
|
12
|
+
* verdict maps straight back to an underline.
|
|
13
|
+
*/
|
|
12
14
|
export interface ExtractedWord {
|
|
13
15
|
/** The lowercased word, as the engine's case-insensitive lookup expects. */
|
|
14
16
|
text: string;
|
|
@@ -22,8 +24,10 @@ export interface ExtractedWord {
|
|
|
22
24
|
* precedence live in {@link skipRanges}.
|
|
23
25
|
*/
|
|
24
26
|
export declare function classifyProse(text: string, tree: Tree, from: number, to: number): Range[];
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
+
/**
|
|
28
|
+
* The prose ranges worth checking across the whole document. The lint source narrows this to the
|
|
29
|
+
* visible window; the unit test reads the whole-document set.
|
|
30
|
+
*/
|
|
27
31
|
export declare function spellcheckRanges(text: string, tree: Tree): Range[];
|
|
28
32
|
/**
|
|
29
33
|
* The checkable words inside [from, to), each lowercased for lookup with its absolute range recorded
|
|
@@ -31,8 +35,10 @@ export declare function spellcheckRanges(text: string, tree: Tree): Range[];
|
|
|
31
35
|
* all-caps tokens are dropped.
|
|
32
36
|
*/
|
|
33
37
|
export declare function extractWords(text: string, from: number, to: number): ExtractedWord[];
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
38
|
+
/**
|
|
39
|
+
* The callbacks the management actions invoke. The lint source supplies these so the pure builder
|
|
40
|
+
* never touches the Worker or the re-lint mechanism: it only wires the buttons to these handlers.
|
|
41
|
+
*/
|
|
36
42
|
export interface SpellDiagnosticActions {
|
|
37
43
|
/** Add the word to the personal dictionary (posts addWord, records the pending addition, re-lints). */
|
|
38
44
|
onAddWord(word: string): void;
|
|
@@ -40,7 +46,7 @@ export interface SpellDiagnosticActions {
|
|
|
40
46
|
onIgnoreWord(word: string): void;
|
|
41
47
|
}
|
|
42
48
|
/**
|
|
43
|
-
* Build the correction popover for one misspelled word, as a
|
|
49
|
+
* Build the correction popover for one misspelled word, as a `@codemirror/lint` Diagnostic whose
|
|
44
50
|
* `actions` CodeMirror renders as tooltip buttons (no custom popover code). The actions, in order:
|
|
45
51
|
* up to five ranked suggestions (each replaces the word's range with one transaction), then "Add to
|
|
46
52
|
* dictionary", then "Ignore". The severity is `info` so the underline is quiet, and the message names
|
|
@@ -64,7 +70,7 @@ export interface SeqArbiter {
|
|
|
64
70
|
/** Build a fresh {@link SeqArbiter}. */
|
|
65
71
|
export declare function arbitrateChecked(): SeqArbiter;
|
|
66
72
|
/**
|
|
67
|
-
* Build the quick-fix popover for one objective-error finding, as a
|
|
73
|
+
* Build the quick-fix popover for one objective-error finding, as a `@codemirror/lint` Diagnostic whose
|
|
68
74
|
* one `actions` entry applies the finding's deterministic fix. The severity is `info` so the underline
|
|
69
75
|
* shares the spellcheck surface and the locked amber color (an editor reads spelling and these
|
|
70
76
|
* mechanical errors as one "spellcheck" layer). The fix range is recomputed from the live diagnostic
|
|
@@ -73,58 +79,80 @@ export declare function arbitrateChecked(): SeqArbiter;
|
|
|
73
79
|
* diagnostic without a browser.
|
|
74
80
|
*/
|
|
75
81
|
export declare function buildObjectiveDiagnostic(error: ObjectiveError): Diagnostic;
|
|
76
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* The narrow Worker surface the lint source drives: it posts check, suggest, addWord, and ignoreWord
|
|
77
84
|
* messages and listens for the answers. A `suggest` answer is a one-shot, so the source removes its
|
|
78
|
-
* own listener once it lands. A test injects a fake; production injects a real Worker.
|
|
85
|
+
* own listener once it lands. A test injects a fake; production injects a real Worker.
|
|
86
|
+
*/
|
|
79
87
|
export interface SpellWorker {
|
|
80
88
|
postMessage(message: unknown): void;
|
|
81
89
|
addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
82
90
|
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
83
91
|
}
|
|
84
|
-
/**
|
|
85
|
-
*
|
|
92
|
+
/**
|
|
93
|
+
* Construct the real spellcheck Worker, the spike's delivery shape. Kept behind the seam so the
|
|
94
|
+
* lint source never references `Worker` at module scope and a test can swap it.
|
|
95
|
+
*/
|
|
86
96
|
export declare function createSpellWorker(): SpellWorker;
|
|
87
97
|
/** The real wasm asset URL, resolved module-relative the same way the worker is. */
|
|
88
98
|
export declare function resolveWasmUrl(): string;
|
|
89
|
-
/**
|
|
90
|
-
*
|
|
99
|
+
/**
|
|
100
|
+
* The real dictionary asset URL for a dictionary filename, resolved module-relative. The caller
|
|
101
|
+
* passes the dialect-resolved filename (default `dictionary-en-us.txt`). `dictionaryFileForDialect`
|
|
102
|
+
* already collapses an unknown dialect to the default, so an unmapped name falls back the same way
|
|
103
|
+
* rather than pointing at an asset that does not ship.
|
|
104
|
+
*/
|
|
91
105
|
export declare function resolveDictionaryUrl(dictionaryFile: string): string;
|
|
92
|
-
/**
|
|
93
|
-
*
|
|
106
|
+
/**
|
|
107
|
+
* Options for {@link cairnSpellcheck}, so the unit and component layers can inject a fake Worker
|
|
108
|
+
* factory in place of the real `new Worker(...)`.
|
|
109
|
+
*/
|
|
94
110
|
export interface SpellcheckOptions {
|
|
95
111
|
/** The Worker factory; defaults to {@link createSpellWorker}. Created lazily on the first lint. */
|
|
96
112
|
createWorker?: () => SpellWorker;
|
|
97
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* The pending personal-dictionary additions, owned by the caller. When an author chooses "Add to
|
|
98
115
|
* dictionary" the source posts addWord to the Worker (the underline clears at once) and records the
|
|
99
116
|
* word here. The set is the seam Task 9 commits to the git-backed dictionary file; this source only
|
|
100
|
-
* fills it and never persists. A caller that does not pass one gets a fresh internal set.
|
|
117
|
+
* fills it and never persists. A caller that does not pass one gets a fresh internal set.
|
|
118
|
+
*/
|
|
101
119
|
pendingAdditions?: Set<string>;
|
|
102
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* The committed personal-dictionary words (spec 1.6) the source seeds the Worker's personal layer
|
|
103
122
|
* with, posted as one batch `addWord` right after `init`. The git-backed site dictionary is the
|
|
104
123
|
* durable layer; the editor reads it at load (EditData.siteDictionary) and hands it here, so a word
|
|
105
|
-
* another editor committed answers correct from the first lint. Empty by default (dialect-only).
|
|
124
|
+
* another editor committed answers correct from the first lint. Empty by default (dialect-only).
|
|
125
|
+
*/
|
|
106
126
|
siteWords?: ReadonlyArray<string>;
|
|
107
|
-
/**
|
|
108
|
-
*
|
|
127
|
+
/**
|
|
128
|
+
* The dialect-resolved dictionary filename, e.g. "dictionary-en-us.txt". The source resolves it to
|
|
129
|
+
* a real asset URL and posts it in the Worker's `init`. Defaults to US English.
|
|
130
|
+
*/
|
|
109
131
|
dictionaryFile?: string;
|
|
110
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Override the resolved wasm and dictionary URLs the source posts in `init`. The real resolution
|
|
111
134
|
* uses {@link resolveWasmUrl}/{@link resolveDictionaryUrl} (module-relative `import.meta.url`); a
|
|
112
135
|
* component test that injects a fake Worker can pass canned URLs so it never touches the asset
|
|
113
|
-
* resolver.
|
|
136
|
+
* resolver.
|
|
137
|
+
*/
|
|
114
138
|
assetUrls?: {
|
|
115
139
|
wasmUrl: string;
|
|
116
140
|
dictionaryUrl: string;
|
|
117
141
|
};
|
|
118
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Treat the Worker as ready without waiting for a `ready` message. The production path is strict
|
|
119
144
|
* (it posts `init` and waits for `ready` before painting); a fake Worker in a test that does not
|
|
120
|
-
* answer `ready` can set this so a lint run is not held back. Defaults to false.
|
|
145
|
+
* answer `ready` can set this so a lint run is not held back. Defaults to false.
|
|
146
|
+
*/
|
|
121
147
|
assumeReady?: boolean;
|
|
122
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* The already-loaded CodeMirror modules to reuse instead of importing them again. The editor
|
|
123
150
|
* component loads `@codemirror/view`/`@codemirror/language` for its own extensions, so passing them
|
|
124
151
|
* here keeps the lint source on the SAME module instances; a second dynamic import can resolve to a
|
|
125
152
|
* separate copy (the test bundler's dedup quirk), and CodeMirror's instanceof checks then reject the
|
|
126
153
|
* extension. When omitted, the source imports them itself (the standalone path). `@codemirror/lint`
|
|
127
|
-
* is loaded here when not supplied, since the editor does not otherwise need it.
|
|
154
|
+
* is loaded here when not supplied, since the editor does not otherwise need it.
|
|
155
|
+
*/
|
|
128
156
|
modules?: {
|
|
129
157
|
lint?: typeof import('@codemirror/lint');
|
|
130
158
|
language?: typeof import('@codemirror/language');
|
|
@@ -133,7 +161,7 @@ export interface SpellcheckOptions {
|
|
|
133
161
|
};
|
|
134
162
|
}
|
|
135
163
|
/**
|
|
136
|
-
* The
|
|
164
|
+
* The `@codemirror/lint` linter() source, made markdown-aware by the Lezer tree. It runs over the
|
|
137
165
|
* visible viewport plus a margin (not the whole document), extracts the checkable words via the pure
|
|
138
166
|
* classifier, posts them to the Worker keyed by a monotonic latest-wins seq, and maps the
|
|
139
167
|
* `correct: false` answers back to ranges. Each wrong word becomes a correction popover: the source
|
|
@@ -40,8 +40,10 @@ const SKIP_NODES = new Set([
|
|
|
40
40
|
// surrounding whitespace). A token inside an image is already caught by the URL skip; this catches
|
|
41
41
|
// the form authors type directly in prose, so it is never split into "media" plus a flagged hash.
|
|
42
42
|
const MEDIA_TOKEN = /media:[\w.-]+/g;
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
43
|
+
/**
|
|
44
|
+
* Merge overlapping or touching ranges into a sorted, disjoint set, so the keep-span computation
|
|
45
|
+
* subtracts one clean list of skip regions.
|
|
46
|
+
*/
|
|
45
47
|
function mergeRanges(ranges) {
|
|
46
48
|
if (ranges.length === 0)
|
|
47
49
|
return [];
|
|
@@ -57,9 +59,11 @@ function mergeRanges(ranges) {
|
|
|
57
59
|
}
|
|
58
60
|
return out;
|
|
59
61
|
}
|
|
60
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Every absolute skip range in the document, from all three mechanisms, merged. This is the single
|
|
61
64
|
* skip authority the spec calls for: the tree decides node kind, frontmatterSpan covers the `---`
|
|
62
|
-
* region, and fenceTokens covers the directive machinery the tree parses as plain text.
|
|
65
|
+
* region, and fenceTokens covers the directive machinery the tree parses as plain text.
|
|
66
|
+
*/
|
|
63
67
|
function skipRanges(text, tree) {
|
|
64
68
|
const skips = [];
|
|
65
69
|
// 1. The Lezer tree: the single authority for node-kind skips.
|
|
@@ -131,8 +135,10 @@ export function classifyProse(text, tree, from, to) {
|
|
|
131
135
|
out.push({ from: cursor, to });
|
|
132
136
|
return out;
|
|
133
137
|
}
|
|
134
|
-
/**
|
|
135
|
-
*
|
|
138
|
+
/**
|
|
139
|
+
* The prose ranges worth checking across the whole document. The lint source narrows this to the
|
|
140
|
+
* visible window; the unit test reads the whole-document set.
|
|
141
|
+
*/
|
|
136
142
|
export function spellcheckRanges(text, tree) {
|
|
137
143
|
return classifyProse(text, tree, 0, text.length);
|
|
138
144
|
}
|
|
@@ -141,8 +147,10 @@ export function spellcheckRanges(text, tree) {
|
|
|
141
147
|
// with the inner apostrophe (straight or curly) and hyphen allowed only between word characters.
|
|
142
148
|
const WORD = /[\p{L}\p{N}]+(?:[-'’][\p{L}\p{N}]+)*/gu;
|
|
143
149
|
const ALL_DIGITS = /^\p{N}+$/u;
|
|
144
|
-
/**
|
|
145
|
-
*
|
|
150
|
+
/**
|
|
151
|
+
* Whether a word is worth a lookup. Words under three characters, pure numbers, and all-caps tokens
|
|
152
|
+
* are skipped to cut false positives (the conservative posture VSCode's spell checker takes).
|
|
153
|
+
*/
|
|
146
154
|
function isCheckable(word) {
|
|
147
155
|
if (word.length < 3)
|
|
148
156
|
return false;
|
|
@@ -175,7 +183,7 @@ export function extractWords(text, from, to) {
|
|
|
175
183
|
// wall of near-ties.
|
|
176
184
|
const MAX_SUGGESTIONS = 5;
|
|
177
185
|
/**
|
|
178
|
-
* Build the correction popover for one misspelled word, as a
|
|
186
|
+
* Build the correction popover for one misspelled word, as a `@codemirror/lint` Diagnostic whose
|
|
179
187
|
* `actions` CodeMirror renders as tooltip buttons (no custom popover code). The actions, in order:
|
|
180
188
|
* up to five ranked suggestions (each replaces the word's range with one transaction), then "Add to
|
|
181
189
|
* dictionary", then "Ignore". The severity is `info` so the underline is quiet, and the message names
|
|
@@ -227,7 +235,7 @@ export function arbitrateChecked() {
|
|
|
227
235
|
};
|
|
228
236
|
}
|
|
229
237
|
/**
|
|
230
|
-
* Build the quick-fix popover for one objective-error finding, as a
|
|
238
|
+
* Build the quick-fix popover for one objective-error finding, as a `@codemirror/lint` Diagnostic whose
|
|
231
239
|
* one `actions` entry applies the finding's deterministic fix. The severity is `info` so the underline
|
|
232
240
|
* shares the spellcheck surface and the locked amber color (an editor reads spelling and these
|
|
233
241
|
* mechanical errors as one "spellcheck" layer). The fix range is recomputed from the live diagnostic
|
|
@@ -271,8 +279,10 @@ let lintMod = null;
|
|
|
271
279
|
let langMod = null;
|
|
272
280
|
let viewMod = null;
|
|
273
281
|
let stateMod = null;
|
|
274
|
-
/**
|
|
275
|
-
*
|
|
282
|
+
/**
|
|
283
|
+
* Construct the real spellcheck Worker, the spike's delivery shape. Kept behind the seam so the
|
|
284
|
+
* lint source never references `Worker` at module scope and a test can swap it.
|
|
285
|
+
*/
|
|
276
286
|
export function createSpellWorker() {
|
|
277
287
|
return new Worker(new URL('./spellcheck-worker.js', import.meta.url), {
|
|
278
288
|
type: 'module',
|
|
@@ -290,10 +300,26 @@ export function createSpellWorker() {
|
|
|
290
300
|
export function resolveWasmUrl() {
|
|
291
301
|
return new URL('./spellcheck-assets/spellchecker-wasm.wasm', import.meta.url).href;
|
|
292
302
|
}
|
|
293
|
-
/**
|
|
294
|
-
*
|
|
303
|
+
/**
|
|
304
|
+
* Each shipped dictionary, mapped to a resolver that builds its asset URL with a LITERAL
|
|
305
|
+
* `new URL(..., import.meta.url)`. The literal path is load-bearing. A templated `new URL` makes Vite
|
|
306
|
+
* and rolldown treat the directory as a glob and parse every sibling module to build it, including the
|
|
307
|
+
* `.svelte` components that still carry `lang="ts"` in `dist`, and the glob parser chokes on the TS
|
|
308
|
+
* syntax and breaks the consumer build. This set mirrors the dialect map in `nav/site-config.ts`; add
|
|
309
|
+
* one line per new shipped dialect dictionary.
|
|
310
|
+
*/
|
|
311
|
+
const DICTIONARY_URLS = {
|
|
312
|
+
'dictionary-en-us.txt': () => new URL('./spellcheck-assets/dictionary-en-us.txt', import.meta.url).href,
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* The real dictionary asset URL for a dictionary filename, resolved module-relative. The caller
|
|
316
|
+
* passes the dialect-resolved filename (default `dictionary-en-us.txt`). `dictionaryFileForDialect`
|
|
317
|
+
* already collapses an unknown dialect to the default, so an unmapped name falls back the same way
|
|
318
|
+
* rather than pointing at an asset that does not ship.
|
|
319
|
+
*/
|
|
295
320
|
export function resolveDictionaryUrl(dictionaryFile) {
|
|
296
|
-
|
|
321
|
+
const resolve = DICTIONARY_URLS[dictionaryFile] ?? DICTIONARY_URLS['dictionary-en-us.txt'];
|
|
322
|
+
return resolve();
|
|
297
323
|
}
|
|
298
324
|
/** How far past the visible viewport to lint, so a small scroll does not re-lint from scratch. */
|
|
299
325
|
const VIEWPORT_MARGIN = 1000;
|
|
@@ -333,7 +359,7 @@ function lockedUnderlineTheme(EditorViewMod) {
|
|
|
333
359
|
});
|
|
334
360
|
}
|
|
335
361
|
/**
|
|
336
|
-
* The
|
|
362
|
+
* The `@codemirror/lint` linter() source, made markdown-aware by the Lezer tree. It runs over the
|
|
337
363
|
* visible viewport plus a margin (not the whole document), extracts the checkable words via the pure
|
|
338
364
|
* classifier, posts them to the Worker keyed by a monotonic latest-wins seq, and maps the
|
|
339
365
|
* `correct: false` answers back to ranges. Each wrong word becomes a correction popover: the source
|
|
@@ -445,10 +471,12 @@ export async function cairnSpellcheck(options = {}) {
|
|
|
445
471
|
worker.postMessage({ type: 'addWord', words: siteWords });
|
|
446
472
|
return worker;
|
|
447
473
|
}
|
|
448
|
-
/**
|
|
474
|
+
/**
|
|
475
|
+
* Fetch a single word's ranked suggestions over the Worker, a one-shot listener removed on the
|
|
449
476
|
* answer. The suggest path is independent of the check seq, so a slow suggest never blocks a fresh
|
|
450
477
|
* check; an empty list (the engine returned nothing) still yields a popover with the two
|
|
451
|
-
* management actions.
|
|
478
|
+
* management actions.
|
|
479
|
+
*/
|
|
452
480
|
function fetchSuggestions(w, word) {
|
|
453
481
|
suggestSeq += 1;
|
|
454
482
|
const seq = suggestSeq;
|
|
@@ -464,8 +492,10 @@ export async function cairnSpellcheck(options = {}) {
|
|
|
464
492
|
w.postMessage({ type: 'suggest', seq, word });
|
|
465
493
|
});
|
|
466
494
|
}
|
|
467
|
-
/**
|
|
468
|
-
*
|
|
495
|
+
/**
|
|
496
|
+
* Turn the wrong words into correction popovers, each carrying its ranked suggestions and the two
|
|
497
|
+
* management actions.
|
|
498
|
+
*/
|
|
469
499
|
async function buildDiagnostics(wrong) {
|
|
470
500
|
const w = ensureWorker();
|
|
471
501
|
const callbacks = {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Change } from './tidy-diff.js';
|
|
2
2
|
import type { TidyConventions } from '../nav/site-config.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* A change's locally-inferred category. The first four are objective (safe to sweep); `normalization`
|
|
4
5
|
* and `grammar` are judgment (held undecided, never swept by Accept-fixes). `normalization` carries
|
|
5
|
-
* the convention key that authorized it, so the surface can name the setting and label the badge.
|
|
6
|
+
* the convention key that authorized it, so the surface can name the setting and label the badge.
|
|
7
|
+
*/
|
|
6
8
|
export type TidyCategory = {
|
|
7
9
|
kind: 'spelling';
|
|
8
10
|
} | {
|
|
@@ -17,13 +19,17 @@ export type TidyCategory = {
|
|
|
17
19
|
} | {
|
|
18
20
|
kind: 'grammar';
|
|
19
21
|
};
|
|
20
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* True for the objective categories: the safe, pre-kept, Accept-fixes-swept rank. A judgment
|
|
21
24
|
* category (`normalization` or `grammar`) returns false. The bulk action and the surface both read
|
|
22
|
-
* this, so the safety rank is one source of truth.
|
|
25
|
+
* this, so the safety rank is one source of truth.
|
|
26
|
+
*/
|
|
23
27
|
export declare function isObjective(category: TidyCategory): boolean;
|
|
24
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The enabled-convention keys a normalization can be attributed to. Each maps to one config field on
|
|
25
30
|
* TidyConventions and to a because-line. A change is only ever labelled a normalization when it matches
|
|
26
|
-
* one of these AND the config has the matching variant enabled; otherwise it is never a normalization.
|
|
31
|
+
* one of these AND the config has the matching variant enabled; otherwise it is never a normalization.
|
|
32
|
+
*/
|
|
27
33
|
export type NormalizationKey = 'oxfordComma' | 'numberStyle' | 'measurements' | 'percent' | 'emDash' | 'enDashRanges' | 'ellipsis' | 'timeFormat' | 'smartQuotes';
|
|
28
34
|
/**
|
|
29
35
|
* Categorize one change against the captured original and the resolved conventions. The rules are
|
|
@@ -39,11 +45,13 @@ export type NormalizationKey = 'oxfordComma' | 'numberStyle' | 'measurements' |
|
|
|
39
45
|
* offers a normalization that cannot cite an enabled setting.
|
|
40
46
|
*/
|
|
41
47
|
export declare function categorize(change: Change, original: string, conventions: TidyConventions): TidyCategory;
|
|
42
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The because-line data for a hunk: the convention's display name and the variant phrasing, both pure
|
|
43
50
|
* strings derived from the config. The surface renders "Your <label> setting is <variant>, ..." from
|
|
44
51
|
* these. Only a normalization carries a because-line; an objective or grammar hunk returns null (a
|
|
45
52
|
* grammar hunk's rationale, when shown, is the local subject-verb note the surface composes, not a
|
|
46
|
-
* config citation).
|
|
53
|
+
* config citation).
|
|
54
|
+
*/
|
|
47
55
|
export interface BecauseLine {
|
|
48
56
|
/** The convention's display label, e.g. "Oxford-comma". */
|
|
49
57
|
label: string;
|
|
@@ -61,7 +69,9 @@ export interface BecauseLine {
|
|
|
61
69
|
* null when the convention is somehow not enabled (defensive: categorize never produces such a hunk).
|
|
62
70
|
*/
|
|
63
71
|
export declare function buildBecause(key: NormalizationKey, conventions: TidyConventions): BecauseLine | null;
|
|
64
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* The human badge label for a category, the word shown in the hunk's category pill. A normalization's
|
|
65
74
|
* label is the convention's display name (its comma style, its time format), never "consistency" and
|
|
66
|
-
* never a count.
|
|
75
|
+
* never a count.
|
|
76
|
+
*/
|
|
67
77
|
export declare function categoryLabel(category: TidyCategory): string;
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
// quiet and are swept by Accept-fixes. Judgment categories (a declared normalization, or a grammar fix
|
|
10
10
|
// that reworded more than one token) carry the review-this treatment and are never swept until the
|
|
11
11
|
// author confirms each. The category alone decides the rank, so the surface and the bulk action agree.
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* True for the objective categories: the safe, pre-kept, Accept-fixes-swept rank. A judgment
|
|
13
14
|
* category (`normalization` or `grammar`) returns false. The bulk action and the surface both read
|
|
14
|
-
* this, so the safety rank is one source of truth.
|
|
15
|
+
* this, so the safety rank is one source of truth.
|
|
16
|
+
*/
|
|
15
17
|
export function isObjective(category) {
|
|
16
18
|
return (category.kind === 'spelling' ||
|
|
17
19
|
category.kind === 'typo' ||
|
|
@@ -94,9 +96,11 @@ function isWhitespaceOnly(text) {
|
|
|
94
96
|
function isPunctuationOnly(text) {
|
|
95
97
|
return text.length > 0 && /^[^A-Za-z0-9_\s]+$/.test(text);
|
|
96
98
|
}
|
|
97
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* The word ending immediately before `offset` in `text`, skipping any whitespace just before the
|
|
98
101
|
* offset, or null when none. The doubled-word rule reads it to confirm the deleted word repeats the
|
|
99
|
-
* one before it. Pure text inspection, never a count.
|
|
102
|
+
* one before it. Pure text inspection, never a count.
|
|
103
|
+
*/
|
|
100
104
|
function precedingWord(text, offset) {
|
|
101
105
|
let i = offset;
|
|
102
106
|
while (i > 0 && /\s/.test(text[i - 1]))
|
|
@@ -106,8 +110,10 @@ function precedingWord(text, offset) {
|
|
|
106
110
|
j--;
|
|
107
111
|
return j < i ? text.slice(j, i) : null;
|
|
108
112
|
}
|
|
109
|
-
/**
|
|
110
|
-
*
|
|
113
|
+
/**
|
|
114
|
+
* The word starting immediately after `offset` in `text`, skipping any whitespace just after the
|
|
115
|
+
* offset, or null when none. The doubled-word rule reads it as the other half of the look-around.
|
|
116
|
+
*/
|
|
111
117
|
function followingWord(text, offset) {
|
|
112
118
|
let i = offset;
|
|
113
119
|
while (i < text.length && /\s/.test(text[i]))
|
|
@@ -349,9 +355,11 @@ export function buildBecause(key, conventions) {
|
|
|
349
355
|
}
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
|
-
/**
|
|
358
|
+
/**
|
|
359
|
+
* The human badge label for a category, the word shown in the hunk's category pill. A normalization's
|
|
353
360
|
* label is the convention's display name (its comma style, its time format), never "consistency" and
|
|
354
|
-
* never a count.
|
|
361
|
+
* never a count.
|
|
362
|
+
*/
|
|
355
363
|
export function categoryLabel(category) {
|
|
356
364
|
switch (category.kind) {
|
|
357
365
|
case 'spelling':
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import type { Change } from './tidy-diff.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* The reason a tidy result was rejected. Task 14 branches on this; every value maps to the one
|
|
3
4
|
* honest author-facing message, so the reason is for logging and tests, not the user surface.
|
|
4
5
|
* - `structure`: a directive opener/closer sequence, a heading count or level, or a fenced-code
|
|
5
6
|
* count diverged (the result restructured the document).
|
|
6
7
|
* - `frontmatter`: the frontmatter block is not byte-for-byte equal.
|
|
7
8
|
* - `media`: the multiset of `media:` hashes differs (a hash was altered, dropped, or invented).
|
|
8
9
|
* - `code`: a code span or fenced code block was edited.
|
|
9
|
-
* - `divergence`: the changed-token amount exceeds the length-aware bound (a wholesale rewrite).
|
|
10
|
+
* - `divergence`: the changed-token amount exceeds the length-aware bound (a wholesale rewrite).
|
|
11
|
+
*/
|
|
10
12
|
export type TidyRejectionReason = 'structure' | 'frontmatter' | 'media' | 'code' | 'divergence';
|
|
11
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The honest author-facing message a rejection maps to. The same message for every reason, by
|
|
12
15
|
* design: an author does not need the validator's internal taxonomy, only that the result was
|
|
13
|
-
* discarded and their text is safe.
|
|
16
|
+
* discarded and their text is safe.
|
|
17
|
+
*/
|
|
14
18
|
export declare const TIDY_REJECTION_MESSAGE = "Tidy returned a result that changed more than the wording, so it was discarded. Your text is unchanged.";
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
19
|
+
/**
|
|
20
|
+
* The outcome of validating a tidy result. On success it carries the Task 12 change set the review
|
|
21
|
+
* surface accepts and rejects against; on failure it carries the typed reason and the message.
|
|
22
|
+
*/
|
|
17
23
|
export type TidyValidation = {
|
|
18
24
|
ok: true;
|
|
19
25
|
changes: Change[];
|
|
@@ -16,9 +16,11 @@ import { visit } from 'unist-util-visit';
|
|
|
16
16
|
import { fenceScan, frontmatterSpan } from './markdown-directives.js';
|
|
17
17
|
import { parseMediaToken } from '../media/reference.js';
|
|
18
18
|
import { diffTokens, diffChanges } from './tidy-diff.js';
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* The honest author-facing message a rejection maps to. The same message for every reason, by
|
|
20
21
|
* design: an author does not need the validator's internal taxonomy, only that the result was
|
|
21
|
-
* discarded and their text is safe.
|
|
22
|
+
* discarded and their text is safe.
|
|
23
|
+
*/
|
|
22
24
|
export const TIDY_REJECTION_MESSAGE = 'Tidy returned a result that changed more than the wording, so it was discarded. Your text is unchanged.';
|
|
23
25
|
// The divergence bound. The floor allows a fixed number of changed tokens regardless of fraction so
|
|
24
26
|
// a legitimate heavy proofread of a SHORT input is not penalized: a short paragraph with a typo in
|
|
@@ -38,10 +40,12 @@ const DIVERGENCE_FRACTION = 0.5;
|
|
|
38
40
|
// site, which the validator otherwise has no reason to know. A token mangled inside a code fence is
|
|
39
41
|
// caught here too, redundantly with the code check, which is the right posture for a backstop.
|
|
40
42
|
const MEDIA_TOKEN = /media:[A-Za-z0-9.-]+/g;
|
|
41
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The sorted multiset of valid media hashes in the text. Each `media:` occurrence is parsed; a
|
|
42
45
|
* malformed token (a broken hash, an illegal slug) parses to null and is dropped, so a tidy that
|
|
43
46
|
* CORRUPTED a hash drops it from the multiset and the comparison fails. Sorted so two multisets
|
|
44
|
-
* compare by value, order-independent.
|
|
47
|
+
* compare by value, order-independent.
|
|
48
|
+
*/
|
|
45
49
|
function mediaHashes(text) {
|
|
46
50
|
const hashes = [];
|
|
47
51
|
for (const m of text.matchAll(MEDIA_TOKEN)) {
|
|
@@ -51,11 +55,13 @@ function mediaHashes(text) {
|
|
|
51
55
|
}
|
|
52
56
|
return hashes.sort();
|
|
53
57
|
}
|
|
54
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* The directive structure signature: each opener or closer in document order, paired with the depth
|
|
55
60
|
* the fence scan assigned it. Two texts share a directive structure when these signatures are equal,
|
|
56
61
|
* so an added, removed, or relevelled container fails the comparison. A fence-shaped line inside a
|
|
57
62
|
* code block is already disowned by the scan (its role is null), so a documented `:::` example does
|
|
58
|
-
* not enter the signature.
|
|
63
|
+
* not enter the signature.
|
|
64
|
+
*/
|
|
59
65
|
function directiveSignature(text) {
|
|
60
66
|
const { depths, roles } = fenceScan(text.split('\n'));
|
|
61
67
|
const parts = [];
|
|
@@ -65,10 +71,12 @@ function directiveSignature(text) {
|
|
|
65
71
|
}
|
|
66
72
|
return parts.join(',');
|
|
67
73
|
}
|
|
68
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The heading signature: every ATX heading's level in document order. Parsed as mdast so a `#`
|
|
69
76
|
* inside a code block or an escaped one is never counted, and the level is the parser's own depth.
|
|
70
77
|
* Two texts share a heading structure when these are equal, so an added, removed, or relevelled
|
|
71
|
-
* heading fails the comparison.
|
|
78
|
+
* heading fails the comparison.
|
|
79
|
+
*/
|
|
72
80
|
function headingSignature(text) {
|
|
73
81
|
const tree = unified().use(remarkParse).use(remarkGfm).parse(text);
|
|
74
82
|
const levels = [];
|
|
@@ -78,11 +86,13 @@ function headingSignature(text) {
|
|
|
78
86
|
});
|
|
79
87
|
return levels.join(',');
|
|
80
88
|
}
|
|
81
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Every code span and fenced or indented code block in the text, as a sorted multiset of values.
|
|
82
91
|
* Parsed as mdast so the comparison sees exactly what the parser treats as code, the same authority
|
|
83
92
|
* the media body scan uses. Sorted so the comparison is order-independent: the divergence and
|
|
84
93
|
* structure checks own ordering, this check owns the contents. A `code` node is a block, an
|
|
85
|
-
* `inlineCode` node is a span.
|
|
94
|
+
* `inlineCode` node is a span.
|
|
95
|
+
*/
|
|
86
96
|
function codeContents(text) {
|
|
87
97
|
const tree = unified().use(remarkParse).use(remarkGfm).parse(text);
|
|
88
98
|
const values = [];
|
|
@@ -2,9 +2,11 @@ import { type Snippet } from 'svelte';
|
|
|
2
2
|
/** The shared holder: the desk snippet a document registers, or null on the office routes. */
|
|
3
3
|
export interface TopbarHolder {
|
|
4
4
|
desk: Snippet | null;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* True while the document is in zen: AdminLayout drops the whole topbar element so the band
|
|
6
7
|
* slides away (the desk's three clusters include AdminLayout-owned chrome, the drawer toggle and
|
|
7
|
-
* breadcrumb, that must vanish with it). EditPage sets this; the office routes leave it false.
|
|
8
|
+
* breadcrumb, that must vanish with it). EditPage sets this; the office routes leave it false.
|
|
9
|
+
*/
|
|
8
10
|
zen: boolean;
|
|
9
11
|
}
|
|
10
12
|
/** Called by AdminLayout once: creates the holder, provides it on context, returns it to render. */
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ConceptDescriptor } from './types.js';
|
|
2
|
+
import type { RepoRef } from '../github/types.js';
|
|
3
|
+
import type { Manifest } from './manifest.js';
|
|
4
|
+
/** One action an advisory offers, as a label and an optional link target. */
|
|
5
|
+
export interface AdvisoryAction {
|
|
6
|
+
/** The action's button or link label. */
|
|
7
|
+
label: string;
|
|
8
|
+
/** The link target, when the action navigates. */
|
|
9
|
+
href?: string;
|
|
10
|
+
}
|
|
11
|
+
/** A non-blocking editor notice, serializable so it can ride EditData across the SSR boundary. */
|
|
12
|
+
export interface AdvisoryNotice {
|
|
13
|
+
/** The notice kind, e.g. "address-collision". */
|
|
14
|
+
kind: string;
|
|
15
|
+
/** The advisory severity; warn-and-allow, never a gate. */
|
|
16
|
+
severity: 'warn';
|
|
17
|
+
/** The notice text shown to the editor. */
|
|
18
|
+
message: string;
|
|
19
|
+
/** The notice's offered actions, when any. */
|
|
20
|
+
actions?: AdvisoryAction[];
|
|
21
|
+
}
|
|
22
|
+
/** One entry that resolves to an address, in a shape the collision check and the message read. */
|
|
23
|
+
export interface AddressEntry {
|
|
24
|
+
/** The concept id, e.g. "pages". */
|
|
25
|
+
concept: string;
|
|
26
|
+
/** The entry id (its filename stem). */
|
|
27
|
+
id: string;
|
|
28
|
+
/** The entry title for display, from the manifest (main) or frontmatter (branch). */
|
|
29
|
+
title: string;
|
|
30
|
+
/** The published corpus on main, or an open cairn/* edit branch. */
|
|
31
|
+
source: 'main' | 'branch';
|
|
32
|
+
}
|
|
33
|
+
/** Permalink to the distinct entries that resolve to it, across main and every open branch. */
|
|
34
|
+
export type AddressIndex = Map<string, AddressEntry[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Build the permalink-keyed address index over main (from each manifest entry's resolved permalink)
|
|
37
|
+
* plus every open cairn/* branch (resolved from its edited markdown).
|
|
38
|
+
*
|
|
39
|
+
* The build fails open: a branch read that throws and a permalink that cannot resolve are both caught
|
|
40
|
+
* and skipped, so a transient failure degrades to a thinner index, never a thrown editor or a blocked
|
|
41
|
+
* publish. The branches are read in one Promise.all, the way buildUsageIndex reads them.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildAddressIndex(repo: RepoRef, token: string, concepts: ConceptDescriptor[], manifest: Manifest): Promise<AddressIndex>;
|
|
44
|
+
/**
|
|
45
|
+
* Find the first other entry that already resolves to an address, or null when the address is free
|
|
46
|
+
* or holds only the entry itself. The self entry is identified by its concept and id together.
|
|
47
|
+
*/
|
|
48
|
+
export declare function addressCollision(index: AddressIndex, self: {
|
|
49
|
+
concept: string;
|
|
50
|
+
id: string;
|
|
51
|
+
}, address: string): AddressEntry | null;
|