@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
|
@@ -11,22 +11,28 @@ import type { UploadResult } from '../sveltekit/content-routes.js';
|
|
|
11
11
|
import type { IngestFailureKind } from './client-ingest.js';
|
|
12
12
|
import { mediaToken } from '../media/reference.js';
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
14
|
+
/**
|
|
15
|
+
* A failure the card surfaces. The ingest taxonomy plus a `generic` catch-all for a refuse reason
|
|
16
|
+
* with no specific author-facing card (a binding-missing, a length-required, a parse miss).
|
|
17
|
+
*/
|
|
16
18
|
export type UploadFailureKind = IngestFailureKind | 'generic';
|
|
17
19
|
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* The outcome the popover acts on. `inserted` swaps the placeholder for the reference and records
|
|
19
22
|
* the entry; `failed` cancels the placeholder and shows the typed card; `session-expired` cancels
|
|
20
|
-
* the placeholder and tells the author to sign in again.
|
|
23
|
+
* the placeholder and tells the author to sign in again.
|
|
24
|
+
*/
|
|
21
25
|
export type UploadOutcome =
|
|
22
26
|
| { kind: 'inserted'; reference: string; record: MediaEntry; reused: boolean }
|
|
23
27
|
| { kind: 'failed'; failure: UploadFailureKind }
|
|
24
28
|
| { kind: 'session-expired' };
|
|
25
29
|
|
|
26
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* The shape the popover hands in: either a parsed SvelteKit action result (success or failure) or a
|
|
27
32
|
* bare response signal for the redirect and network-error cases. The popover deserializes the body
|
|
28
33
|
* for the success and failure cases and passes the raw `response.type`/`response.status` for the
|
|
29
|
-
* redirect case, so this one mapper covers every branch.
|
|
34
|
+
* redirect case, so this one mapper covers every branch.
|
|
35
|
+
*/
|
|
30
36
|
export type UploadEnvelope =
|
|
31
37
|
| { type: 'success'; status?: number; data: UploadResult }
|
|
32
38
|
| { type: 'failure'; status?: number; data?: { error?: string } }
|
|
@@ -12,16 +12,20 @@ import type { Range } from './spellcheck.js';
|
|
|
12
12
|
/** The three objective-error kinds, each its own check. */
|
|
13
13
|
export type ObjectiveErrorKind = 'doubled-word' | 'double-space' | 'repeated-punct';
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
15
|
+
/**
|
|
16
|
+
* A single deterministic edit that resolves one finding: replace [from, to) with `insert`. The lint
|
|
17
|
+
* source turns this into the diagnostic's quick-fix action.
|
|
18
|
+
*/
|
|
17
19
|
export interface ObjectiveFix {
|
|
18
20
|
from: number;
|
|
19
21
|
to: number;
|
|
20
22
|
insert: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
+
/**
|
|
26
|
+
* One objective-error finding: the flagged range a reader sees underlined, the error kind, a plain
|
|
27
|
+
* message, and the one-edit fix.
|
|
28
|
+
*/
|
|
25
29
|
export interface ObjectiveError {
|
|
26
30
|
kind: ObjectiveErrorKind;
|
|
27
31
|
/** The flagged range (absolute document offsets), the span the underline covers. */
|
|
@@ -59,15 +63,19 @@ const DOUBLE_SPACE = /[^\s] ( +)/g;
|
|
|
59
63
|
// flagged because it is a legitimate construction; only a run of one identical mark counts.
|
|
60
64
|
const REPEATED_PUNCT = /([!?,])\1+/g;
|
|
61
65
|
|
|
62
|
-
/**
|
|
63
|
-
*
|
|
66
|
+
/**
|
|
67
|
+
* Whether two matched word strings are the same word, case-insensitively. Both are already plain
|
|
68
|
+
* word runs from the same WORD pattern, so a locale-insensitive lowercase compare is enough.
|
|
69
|
+
*/
|
|
64
70
|
function sameWord(a: string, b: string): boolean {
|
|
65
71
|
return a.toLowerCase() === b.toLowerCase();
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Run the three objective checks over one prose span [from, to), returning every finding with an
|
|
69
76
|
* absolute range and fix. The doubled-word check is bounded to this span so a repeat that straddles
|
|
70
|
-
* a skipped region is never matched.
|
|
77
|
+
* a skipped region is never matched.
|
|
78
|
+
*/
|
|
71
79
|
function checkSpan(text: string, from: number, to: number): ObjectiveError[] {
|
|
72
80
|
const out: ObjectiveError[] = [];
|
|
73
81
|
const slice = text.slice(from, to);
|
|
@@ -32,8 +32,10 @@ export function previewDevice(id: PreviewDeviceId): PreviewDevice {
|
|
|
32
32
|
return previewDevices.find((d) => d.id === id) ?? previewDevices[0];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
35
|
+
/**
|
|
36
|
+
* A device's user-facing text, shared by the toolbar's menu items and the frame caption: the
|
|
37
|
+
* label with its width when one is fixed, so the value reaches assistive tech at pick time.
|
|
38
|
+
*/
|
|
37
39
|
export function deviceLabel(d: PreviewDevice): string {
|
|
38
40
|
return d.width === null ? d.label : `${d.label} · ${d.width} px`;
|
|
39
41
|
}
|
|
@@ -23,8 +23,10 @@ export interface Range {
|
|
|
23
23
|
to: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
26
|
+
/**
|
|
27
|
+
* A word extracted for lookup: the lowercased form the Worker checks, and its absolute range so a
|
|
28
|
+
* verdict maps straight back to an underline.
|
|
29
|
+
*/
|
|
28
30
|
export interface ExtractedWord {
|
|
29
31
|
/** The lowercased word, as the engine's case-insensitive lookup expects. */
|
|
30
32
|
text: string;
|
|
@@ -73,8 +75,10 @@ const SKIP_NODES = new Set<string>([
|
|
|
73
75
|
// the form authors type directly in prose, so it is never split into "media" plus a flagged hash.
|
|
74
76
|
const MEDIA_TOKEN = /media:[\w.-]+/g;
|
|
75
77
|
|
|
76
|
-
/**
|
|
77
|
-
*
|
|
78
|
+
/**
|
|
79
|
+
* Merge overlapping or touching ranges into a sorted, disjoint set, so the keep-span computation
|
|
80
|
+
* subtracts one clean list of skip regions.
|
|
81
|
+
*/
|
|
78
82
|
function mergeRanges(ranges: Range[]): Range[] {
|
|
79
83
|
if (ranges.length === 0) return [];
|
|
80
84
|
const sorted = [...ranges].sort((a, b) => a.from - b.from || a.to - b.to);
|
|
@@ -88,9 +92,11 @@ function mergeRanges(ranges: Range[]): Range[] {
|
|
|
88
92
|
return out;
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Every absolute skip range in the document, from all three mechanisms, merged. This is the single
|
|
92
97
|
* skip authority the spec calls for: the tree decides node kind, frontmatterSpan covers the `---`
|
|
93
|
-
* region, and fenceTokens covers the directive machinery the tree parses as plain text.
|
|
98
|
+
* region, and fenceTokens covers the directive machinery the tree parses as plain text.
|
|
99
|
+
*/
|
|
94
100
|
function skipRanges(text: string, tree: Tree): Range[] {
|
|
95
101
|
const skips: Range[] = [];
|
|
96
102
|
|
|
@@ -163,8 +169,10 @@ export function classifyProse(text: string, tree: Tree, from: number, to: number
|
|
|
163
169
|
return out;
|
|
164
170
|
}
|
|
165
171
|
|
|
166
|
-
/**
|
|
167
|
-
*
|
|
172
|
+
/**
|
|
173
|
+
* The prose ranges worth checking across the whole document. The lint source narrows this to the
|
|
174
|
+
* visible window; the unit test reads the whole-document set.
|
|
175
|
+
*/
|
|
168
176
|
export function spellcheckRanges(text: string, tree: Tree): Range[] {
|
|
169
177
|
return classifyProse(text, tree, 0, text.length);
|
|
170
178
|
}
|
|
@@ -175,8 +183,10 @@ export function spellcheckRanges(text: string, tree: Tree): Range[] {
|
|
|
175
183
|
const WORD = /[\p{L}\p{N}]+(?:[-'’][\p{L}\p{N}]+)*/gu;
|
|
176
184
|
const ALL_DIGITS = /^\p{N}+$/u;
|
|
177
185
|
|
|
178
|
-
/**
|
|
179
|
-
*
|
|
186
|
+
/**
|
|
187
|
+
* Whether a word is worth a lookup. Words under three characters, pure numbers, and all-caps tokens
|
|
188
|
+
* are skipped to cut false positives (the conservative posture VSCode's spell checker takes).
|
|
189
|
+
*/
|
|
180
190
|
function isCheckable(word: string): boolean {
|
|
181
191
|
if (word.length < 3) return false;
|
|
182
192
|
if (ALL_DIGITS.test(word)) return false;
|
|
@@ -207,8 +217,10 @@ export function extractWords(text: string, from: number, to: number): ExtractedW
|
|
|
207
217
|
// wall of near-ties.
|
|
208
218
|
const MAX_SUGGESTIONS = 5;
|
|
209
219
|
|
|
210
|
-
/**
|
|
211
|
-
*
|
|
220
|
+
/**
|
|
221
|
+
* The callbacks the management actions invoke. The lint source supplies these so the pure builder
|
|
222
|
+
* never touches the Worker or the re-lint mechanism: it only wires the buttons to these handlers.
|
|
223
|
+
*/
|
|
212
224
|
export interface SpellDiagnosticActions {
|
|
213
225
|
/** Add the word to the personal dictionary (posts addWord, records the pending addition, re-lints). */
|
|
214
226
|
onAddWord(word: string): void;
|
|
@@ -217,7 +229,7 @@ export interface SpellDiagnosticActions {
|
|
|
217
229
|
}
|
|
218
230
|
|
|
219
231
|
/**
|
|
220
|
-
* Build the correction popover for one misspelled word, as a
|
|
232
|
+
* Build the correction popover for one misspelled word, as a `@codemirror/lint` Diagnostic whose
|
|
221
233
|
* `actions` CodeMirror renders as tooltip buttons (no custom popover code). The actions, in order:
|
|
222
234
|
* up to five ranked suggestions (each replaces the word's range with one transaction), then "Add to
|
|
223
235
|
* dictionary", then "Ignore". The severity is `info` so the underline is quiet, and the message names
|
|
@@ -290,7 +302,7 @@ export function arbitrateChecked(): SeqArbiter {
|
|
|
290
302
|
}
|
|
291
303
|
|
|
292
304
|
/**
|
|
293
|
-
* Build the quick-fix popover for one objective-error finding, as a
|
|
305
|
+
* Build the quick-fix popover for one objective-error finding, as a `@codemirror/lint` Diagnostic whose
|
|
294
306
|
* one `actions` entry applies the finding's deterministic fix. The severity is `info` so the underline
|
|
295
307
|
* shares the spellcheck surface and the locked amber color (an editor reads spelling and these
|
|
296
308
|
* mechanical errors as one "spellcheck" layer). The fix range is recomputed from the live diagnostic
|
|
@@ -337,17 +349,21 @@ let langMod: typeof import('@codemirror/language') | null = null;
|
|
|
337
349
|
let viewMod: typeof import('@codemirror/view') | null = null;
|
|
338
350
|
let stateMod: typeof import('@codemirror/state') | null = null;
|
|
339
351
|
|
|
340
|
-
/**
|
|
352
|
+
/**
|
|
353
|
+
* The narrow Worker surface the lint source drives: it posts check, suggest, addWord, and ignoreWord
|
|
341
354
|
* messages and listens for the answers. A `suggest` answer is a one-shot, so the source removes its
|
|
342
|
-
* own listener once it lands. A test injects a fake; production injects a real Worker.
|
|
355
|
+
* own listener once it lands. A test injects a fake; production injects a real Worker.
|
|
356
|
+
*/
|
|
343
357
|
export interface SpellWorker {
|
|
344
358
|
postMessage(message: unknown): void;
|
|
345
359
|
addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
346
360
|
removeEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
347
361
|
}
|
|
348
362
|
|
|
349
|
-
/**
|
|
350
|
-
*
|
|
363
|
+
/**
|
|
364
|
+
* Construct the real spellcheck Worker, the spike's delivery shape. Kept behind the seam so the
|
|
365
|
+
* lint source never references `Worker` at module scope and a test can swap it.
|
|
366
|
+
*/
|
|
351
367
|
export function createSpellWorker(): SpellWorker {
|
|
352
368
|
return new Worker(new URL('./spellcheck-worker.js', import.meta.url), {
|
|
353
369
|
type: 'module',
|
|
@@ -368,48 +384,80 @@ export function resolveWasmUrl(): string {
|
|
|
368
384
|
return new URL('./spellcheck-assets/spellchecker-wasm.wasm', import.meta.url).href;
|
|
369
385
|
}
|
|
370
386
|
|
|
371
|
-
/**
|
|
372
|
-
*
|
|
387
|
+
/**
|
|
388
|
+
* Each shipped dictionary, mapped to a resolver that builds its asset URL with a LITERAL
|
|
389
|
+
* `new URL(..., import.meta.url)`. The literal path is load-bearing. A templated `new URL` makes Vite
|
|
390
|
+
* and rolldown treat the directory as a glob and parse every sibling module to build it, including the
|
|
391
|
+
* `.svelte` components that still carry `lang="ts"` in `dist`, and the glob parser chokes on the TS
|
|
392
|
+
* syntax and breaks the consumer build. This set mirrors the dialect map in `nav/site-config.ts`; add
|
|
393
|
+
* one line per new shipped dialect dictionary.
|
|
394
|
+
*/
|
|
395
|
+
const DICTIONARY_URLS: Record<string, () => string> = {
|
|
396
|
+
'dictionary-en-us.txt': () =>
|
|
397
|
+
new URL('./spellcheck-assets/dictionary-en-us.txt', import.meta.url).href,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* The real dictionary asset URL for a dictionary filename, resolved module-relative. The caller
|
|
402
|
+
* passes the dialect-resolved filename (default `dictionary-en-us.txt`). `dictionaryFileForDialect`
|
|
403
|
+
* already collapses an unknown dialect to the default, so an unmapped name falls back the same way
|
|
404
|
+
* rather than pointing at an asset that does not ship.
|
|
405
|
+
*/
|
|
373
406
|
export function resolveDictionaryUrl(dictionaryFile: string): string {
|
|
374
|
-
|
|
407
|
+
const resolve = DICTIONARY_URLS[dictionaryFile] ?? DICTIONARY_URLS['dictionary-en-us.txt'];
|
|
408
|
+
return resolve();
|
|
375
409
|
}
|
|
376
410
|
|
|
377
411
|
/** How far past the visible viewport to lint, so a small scroll does not re-lint from scratch. */
|
|
378
412
|
const VIEWPORT_MARGIN = 1000;
|
|
379
413
|
|
|
380
|
-
/**
|
|
381
|
-
*
|
|
414
|
+
/**
|
|
415
|
+
* Options for {@link cairnSpellcheck}, so the unit and component layers can inject a fake Worker
|
|
416
|
+
* factory in place of the real `new Worker(...)`.
|
|
417
|
+
*/
|
|
382
418
|
export interface SpellcheckOptions {
|
|
383
419
|
/** The Worker factory; defaults to {@link createSpellWorker}. Created lazily on the first lint. */
|
|
384
420
|
createWorker?: () => SpellWorker;
|
|
385
|
-
/**
|
|
421
|
+
/**
|
|
422
|
+
* The pending personal-dictionary additions, owned by the caller. When an author chooses "Add to
|
|
386
423
|
* dictionary" the source posts addWord to the Worker (the underline clears at once) and records the
|
|
387
424
|
* word here. The set is the seam Task 9 commits to the git-backed dictionary file; this source only
|
|
388
|
-
* fills it and never persists. A caller that does not pass one gets a fresh internal set.
|
|
425
|
+
* fills it and never persists. A caller that does not pass one gets a fresh internal set.
|
|
426
|
+
*/
|
|
389
427
|
pendingAdditions?: Set<string>;
|
|
390
|
-
/**
|
|
428
|
+
/**
|
|
429
|
+
* The committed personal-dictionary words (spec 1.6) the source seeds the Worker's personal layer
|
|
391
430
|
* with, posted as one batch `addWord` right after `init`. The git-backed site dictionary is the
|
|
392
431
|
* durable layer; the editor reads it at load (EditData.siteDictionary) and hands it here, so a word
|
|
393
|
-
* another editor committed answers correct from the first lint. Empty by default (dialect-only).
|
|
432
|
+
* another editor committed answers correct from the first lint. Empty by default (dialect-only).
|
|
433
|
+
*/
|
|
394
434
|
siteWords?: ReadonlyArray<string>;
|
|
395
|
-
/**
|
|
396
|
-
*
|
|
435
|
+
/**
|
|
436
|
+
* The dialect-resolved dictionary filename, e.g. "dictionary-en-us.txt". The source resolves it to
|
|
437
|
+
* a real asset URL and posts it in the Worker's `init`. Defaults to US English.
|
|
438
|
+
*/
|
|
397
439
|
dictionaryFile?: string;
|
|
398
|
-
/**
|
|
440
|
+
/**
|
|
441
|
+
* Override the resolved wasm and dictionary URLs the source posts in `init`. The real resolution
|
|
399
442
|
* uses {@link resolveWasmUrl}/{@link resolveDictionaryUrl} (module-relative `import.meta.url`); a
|
|
400
443
|
* component test that injects a fake Worker can pass canned URLs so it never touches the asset
|
|
401
|
-
* resolver.
|
|
444
|
+
* resolver.
|
|
445
|
+
*/
|
|
402
446
|
assetUrls?: { wasmUrl: string; dictionaryUrl: string };
|
|
403
|
-
/**
|
|
447
|
+
/**
|
|
448
|
+
* Treat the Worker as ready without waiting for a `ready` message. The production path is strict
|
|
404
449
|
* (it posts `init` and waits for `ready` before painting); a fake Worker in a test that does not
|
|
405
|
-
* answer `ready` can set this so a lint run is not held back. Defaults to false.
|
|
450
|
+
* answer `ready` can set this so a lint run is not held back. Defaults to false.
|
|
451
|
+
*/
|
|
406
452
|
assumeReady?: boolean;
|
|
407
|
-
/**
|
|
453
|
+
/**
|
|
454
|
+
* The already-loaded CodeMirror modules to reuse instead of importing them again. The editor
|
|
408
455
|
* component loads `@codemirror/view`/`@codemirror/language` for its own extensions, so passing them
|
|
409
456
|
* here keeps the lint source on the SAME module instances; a second dynamic import can resolve to a
|
|
410
457
|
* separate copy (the test bundler's dedup quirk), and CodeMirror's instanceof checks then reject the
|
|
411
458
|
* extension. When omitted, the source imports them itself (the standalone path). `@codemirror/lint`
|
|
412
|
-
* is loaded here when not supplied, since the editor does not otherwise need it.
|
|
459
|
+
* is loaded here when not supplied, since the editor does not otherwise need it.
|
|
460
|
+
*/
|
|
413
461
|
modules?: {
|
|
414
462
|
lint?: typeof import('@codemirror/lint');
|
|
415
463
|
language?: typeof import('@codemirror/language');
|
|
@@ -455,7 +503,7 @@ function lockedUnderlineTheme(EditorViewMod: typeof import('@codemirror/view').E
|
|
|
455
503
|
}
|
|
456
504
|
|
|
457
505
|
/**
|
|
458
|
-
* The
|
|
506
|
+
* The `@codemirror/lint` linter() source, made markdown-aware by the Lezer tree. It runs over the
|
|
459
507
|
* visible viewport plus a margin (not the whole document), extracts the checkable words via the pure
|
|
460
508
|
* classifier, posts them to the Worker keyed by a monotonic latest-wins seq, and maps the
|
|
461
509
|
* `correct: false` answers back to ranges. Each wrong word becomes a correction popover: the source
|
|
@@ -576,10 +624,12 @@ export async function cairnSpellcheck(options: SpellcheckOptions = {}): Promise<
|
|
|
576
624
|
return worker;
|
|
577
625
|
}
|
|
578
626
|
|
|
579
|
-
/**
|
|
627
|
+
/**
|
|
628
|
+
* Fetch a single word's ranked suggestions over the Worker, a one-shot listener removed on the
|
|
580
629
|
* answer. The suggest path is independent of the check seq, so a slow suggest never blocks a fresh
|
|
581
630
|
* check; an empty list (the engine returned nothing) still yields a popover with the two
|
|
582
|
-
* management actions.
|
|
631
|
+
* management actions.
|
|
632
|
+
*/
|
|
583
633
|
function fetchSuggestions(w: SpellWorker, word: string): Promise<string[]> {
|
|
584
634
|
suggestSeq += 1;
|
|
585
635
|
const seq = suggestSeq;
|
|
@@ -595,8 +645,10 @@ export async function cairnSpellcheck(options: SpellcheckOptions = {}): Promise<
|
|
|
595
645
|
});
|
|
596
646
|
}
|
|
597
647
|
|
|
598
|
-
/**
|
|
599
|
-
*
|
|
648
|
+
/**
|
|
649
|
+
* Turn the wrong words into correction popovers, each carrying its ranked suggestions and the two
|
|
650
|
+
* management actions.
|
|
651
|
+
*/
|
|
600
652
|
async function buildDiagnostics(wrong: ExtractedWord[]): Promise<Diagnostic[]> {
|
|
601
653
|
const w = ensureWorker();
|
|
602
654
|
const callbacks: SpellDiagnosticActions = {
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
import type { Change } from './tidy-diff.js';
|
|
14
14
|
import type { TidyConventions } from '../nav/site-config.js';
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* A change's locally-inferred category. The first four are objective (safe to sweep); `normalization`
|
|
17
18
|
* and `grammar` are judgment (held undecided, never swept by Accept-fixes). `normalization` carries
|
|
18
|
-
* the convention key that authorized it, so the surface can name the setting and label the badge.
|
|
19
|
+
* the convention key that authorized it, so the surface can name the setting and label the badge.
|
|
20
|
+
*/
|
|
19
21
|
export type TidyCategory =
|
|
20
22
|
| { kind: 'spelling' }
|
|
21
23
|
| { kind: 'typo' }
|
|
@@ -24,9 +26,11 @@ export type TidyCategory =
|
|
|
24
26
|
| { kind: 'normalization'; convention: NormalizationKey }
|
|
25
27
|
| { kind: 'grammar' };
|
|
26
28
|
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* True for the objective categories: the safe, pre-kept, Accept-fixes-swept rank. A judgment
|
|
28
31
|
* category (`normalization` or `grammar`) returns false. The bulk action and the surface both read
|
|
29
|
-
* this, so the safety rank is one source of truth.
|
|
32
|
+
* this, so the safety rank is one source of truth.
|
|
33
|
+
*/
|
|
30
34
|
export function isObjective(category: TidyCategory): boolean {
|
|
31
35
|
return (
|
|
32
36
|
category.kind === 'spelling' ||
|
|
@@ -36,9 +40,11 @@ export function isObjective(category: TidyCategory): boolean {
|
|
|
36
40
|
);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The enabled-convention keys a normalization can be attributed to. Each maps to one config field on
|
|
40
45
|
* TidyConventions and to a because-line. A change is only ever labelled a normalization when it matches
|
|
41
|
-
* one of these AND the config has the matching variant enabled; otherwise it is never a normalization.
|
|
46
|
+
* one of these AND the config has the matching variant enabled; otherwise it is never a normalization.
|
|
47
|
+
*/
|
|
42
48
|
export type NormalizationKey =
|
|
43
49
|
| 'oxfordComma'
|
|
44
50
|
| 'numberStyle'
|
|
@@ -133,9 +139,11 @@ function isPunctuationOnly(text: string): boolean {
|
|
|
133
139
|
return text.length > 0 && /^[^A-Za-z0-9_\s]+$/.test(text);
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* The word ending immediately before `offset` in `text`, skipping any whitespace just before the
|
|
137
144
|
* offset, or null when none. The doubled-word rule reads it to confirm the deleted word repeats the
|
|
138
|
-
* one before it. Pure text inspection, never a count.
|
|
145
|
+
* one before it. Pure text inspection, never a count.
|
|
146
|
+
*/
|
|
139
147
|
function precedingWord(text: string, offset: number): string | null {
|
|
140
148
|
let i = offset;
|
|
141
149
|
while (i > 0 && /\s/.test(text[i - 1])) i--;
|
|
@@ -144,8 +152,10 @@ function precedingWord(text: string, offset: number): string | null {
|
|
|
144
152
|
return j < i ? text.slice(j, i) : null;
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
/**
|
|
148
|
-
*
|
|
155
|
+
/**
|
|
156
|
+
* The word starting immediately after `offset` in `text`, skipping any whitespace just after the
|
|
157
|
+
* offset, or null when none. The doubled-word rule reads it as the other half of the look-around.
|
|
158
|
+
*/
|
|
149
159
|
function followingWord(text: string, offset: number): string | null {
|
|
150
160
|
let i = offset;
|
|
151
161
|
while (i < text.length && /\s/.test(text[i])) i++;
|
|
@@ -335,11 +345,13 @@ function matchNormalization(
|
|
|
335
345
|
return null;
|
|
336
346
|
}
|
|
337
347
|
|
|
338
|
-
/**
|
|
348
|
+
/**
|
|
349
|
+
* The because-line data for a hunk: the convention's display name and the variant phrasing, both pure
|
|
339
350
|
* strings derived from the config. The surface renders "Your <label> setting is <variant>, ..." from
|
|
340
351
|
* these. Only a normalization carries a because-line; an objective or grammar hunk returns null (a
|
|
341
352
|
* grammar hunk's rationale, when shown, is the local subject-verb note the surface composes, not a
|
|
342
|
-
* config citation).
|
|
353
|
+
* config citation).
|
|
354
|
+
*/
|
|
343
355
|
export interface BecauseLine {
|
|
344
356
|
/** The convention's display label, e.g. "Oxford-comma". */
|
|
345
357
|
label: string;
|
|
@@ -416,9 +428,11 @@ export function buildBecause(key: NormalizationKey, conventions: TidyConventions
|
|
|
416
428
|
}
|
|
417
429
|
}
|
|
418
430
|
|
|
419
|
-
/**
|
|
431
|
+
/**
|
|
432
|
+
* The human badge label for a category, the word shown in the hunk's category pill. A normalization's
|
|
420
433
|
* label is the convention's display name (its comma style, its time format), never "consistency" and
|
|
421
|
-
* never a count.
|
|
434
|
+
* never a count.
|
|
435
|
+
*/
|
|
422
436
|
export function categoryLabel(category: TidyCategory): string {
|
|
423
437
|
switch (category.kind) {
|
|
424
438
|
case 'spelling':
|
|
@@ -19,24 +19,30 @@ import { parseMediaToken } from '../media/reference.js';
|
|
|
19
19
|
import { diffTokens, diffChanges } from './tidy-diff.js';
|
|
20
20
|
import type { Change } from './tidy-diff.js';
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The reason a tidy result was rejected. Task 14 branches on this; every value maps to the one
|
|
23
24
|
* honest author-facing message, so the reason is for logging and tests, not the user surface.
|
|
24
25
|
* - `structure`: a directive opener/closer sequence, a heading count or level, or a fenced-code
|
|
25
26
|
* count diverged (the result restructured the document).
|
|
26
27
|
* - `frontmatter`: the frontmatter block is not byte-for-byte equal.
|
|
27
28
|
* - `media`: the multiset of `media:` hashes differs (a hash was altered, dropped, or invented).
|
|
28
29
|
* - `code`: a code span or fenced code block was edited.
|
|
29
|
-
* - `divergence`: the changed-token amount exceeds the length-aware bound (a wholesale rewrite).
|
|
30
|
+
* - `divergence`: the changed-token amount exceeds the length-aware bound (a wholesale rewrite).
|
|
31
|
+
*/
|
|
30
32
|
export type TidyRejectionReason = 'structure' | 'frontmatter' | 'media' | 'code' | 'divergence';
|
|
31
33
|
|
|
32
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* The honest author-facing message a rejection maps to. The same message for every reason, by
|
|
33
36
|
* design: an author does not need the validator's internal taxonomy, only that the result was
|
|
34
|
-
* discarded and their text is safe.
|
|
37
|
+
* discarded and their text is safe.
|
|
38
|
+
*/
|
|
35
39
|
export const TIDY_REJECTION_MESSAGE =
|
|
36
40
|
'Tidy returned a result that changed more than the wording, so it was discarded. Your text is unchanged.';
|
|
37
41
|
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
42
|
+
/**
|
|
43
|
+
* The outcome of validating a tidy result. On success it carries the Task 12 change set the review
|
|
44
|
+
* surface accepts and rejects against; on failure it carries the typed reason and the message.
|
|
45
|
+
*/
|
|
40
46
|
export type TidyValidation =
|
|
41
47
|
| { ok: true; changes: Change[] }
|
|
42
48
|
| { ok: false; reason: TidyRejectionReason; message: string };
|
|
@@ -61,10 +67,12 @@ const DIVERGENCE_FRACTION = 0.5;
|
|
|
61
67
|
// caught here too, redundantly with the code check, which is the right posture for a backstop.
|
|
62
68
|
const MEDIA_TOKEN = /media:[A-Za-z0-9.-]+/g;
|
|
63
69
|
|
|
64
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* The sorted multiset of valid media hashes in the text. Each `media:` occurrence is parsed; a
|
|
65
72
|
* malformed token (a broken hash, an illegal slug) parses to null and is dropped, so a tidy that
|
|
66
73
|
* CORRUPTED a hash drops it from the multiset and the comparison fails. Sorted so two multisets
|
|
67
|
-
* compare by value, order-independent.
|
|
74
|
+
* compare by value, order-independent.
|
|
75
|
+
*/
|
|
68
76
|
function mediaHashes(text: string): string[] {
|
|
69
77
|
const hashes: string[] = [];
|
|
70
78
|
for (const m of text.matchAll(MEDIA_TOKEN)) {
|
|
@@ -74,11 +82,13 @@ function mediaHashes(text: string): string[] {
|
|
|
74
82
|
return hashes.sort();
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* The directive structure signature: each opener or closer in document order, paired with the depth
|
|
78
87
|
* the fence scan assigned it. Two texts share a directive structure when these signatures are equal,
|
|
79
88
|
* so an added, removed, or relevelled container fails the comparison. A fence-shaped line inside a
|
|
80
89
|
* code block is already disowned by the scan (its role is null), so a documented `:::` example does
|
|
81
|
-
* not enter the signature.
|
|
90
|
+
* not enter the signature.
|
|
91
|
+
*/
|
|
82
92
|
function directiveSignature(text: string): string {
|
|
83
93
|
const { depths, roles } = fenceScan(text.split('\n'));
|
|
84
94
|
const parts: string[] = [];
|
|
@@ -88,10 +98,12 @@ function directiveSignature(text: string): string {
|
|
|
88
98
|
return parts.join(',');
|
|
89
99
|
}
|
|
90
100
|
|
|
91
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* The heading signature: every ATX heading's level in document order. Parsed as mdast so a `#`
|
|
92
103
|
* inside a code block or an escaped one is never counted, and the level is the parser's own depth.
|
|
93
104
|
* Two texts share a heading structure when these are equal, so an added, removed, or relevelled
|
|
94
|
-
* heading fails the comparison.
|
|
105
|
+
* heading fails the comparison.
|
|
106
|
+
*/
|
|
95
107
|
function headingSignature(text: string): string {
|
|
96
108
|
const tree = unified().use(remarkParse).use(remarkGfm).parse(text);
|
|
97
109
|
const levels: number[] = [];
|
|
@@ -101,11 +113,13 @@ function headingSignature(text: string): string {
|
|
|
101
113
|
return levels.join(',');
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Every code span and fenced or indented code block in the text, as a sorted multiset of values.
|
|
105
118
|
* Parsed as mdast so the comparison sees exactly what the parser treats as code, the same authority
|
|
106
119
|
* the media body scan uses. Sorted so the comparison is order-independent: the divergence and
|
|
107
120
|
* structure checks own ordering, this check owns the contents. A `code` node is a block, an
|
|
108
|
-
* `inlineCode` node is a span.
|
|
121
|
+
* `inlineCode` node is a span.
|
|
122
|
+
*/
|
|
109
123
|
function codeContents(text: string): string[] {
|
|
110
124
|
const tree = unified().use(remarkParse).use(remarkGfm).parse(text);
|
|
111
125
|
const values: string[] = [];
|
|
@@ -12,9 +12,11 @@ const TOPBAR_CONTEXT_KEY = Symbol('cairn-topbar');
|
|
|
12
12
|
/** The shared holder: the desk snippet a document registers, or null on the office routes. */
|
|
13
13
|
export interface TopbarHolder {
|
|
14
14
|
desk: Snippet | null;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* True while the document is in zen: AdminLayout drops the whole topbar element so the band
|
|
16
17
|
* slides away (the desk's three clusters include AdminLayout-owned chrome, the drawer toggle and
|
|
17
|
-
* breadcrumb, that must vanish with it). EditPage sets this; the office routes leave it false.
|
|
18
|
+
* breadcrumb, that must vanish with it). EditPage sets this; the office routes leave it false.
|
|
19
|
+
*/
|
|
18
20
|
zen: boolean;
|
|
19
21
|
}
|
|
20
22
|
|