@glw907/cairn-cms 0.59.0 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/components/CairnAdmin.svelte +3 -0
- package/dist/components/CairnTidySettings.svelte +553 -0
- package/dist/components/CairnTidySettings.svelte.d.ts +32 -0
- package/dist/components/EditPage.svelte +371 -2
- package/dist/components/MarkdownEditor.svelte +168 -1
- package/dist/components/MarkdownEditor.svelte.d.ts +44 -0
- package/dist/components/TidyReview.svelte +463 -0
- package/dist/components/TidyReview.svelte.d.ts +47 -0
- package/dist/components/cairn-admin.css +764 -0
- package/dist/components/editor-tidy.d.ts +31 -0
- package/dist/components/editor-tidy.js +199 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/markdown-directives.d.ts +16 -0
- package/dist/components/markdown-directives.js +34 -0
- package/dist/components/objective-errors.d.ts +30 -0
- package/dist/components/objective-errors.js +113 -0
- package/dist/components/spellcheck-assets/dictionary-en-us.txt +104743 -0
- package/dist/components/spellcheck-assets/spellchecker-wasm-LICENSE.txt +21 -0
- package/dist/components/spellcheck-assets/spellchecker-wasm.wasm +0 -0
- package/dist/components/spellcheck-worker.d.ts +80 -0
- package/dist/components/spellcheck-worker.js +161 -0
- package/dist/components/spellcheck.d.ts +146 -0
- package/dist/components/spellcheck.js +541 -0
- package/dist/components/tidy-categorize.d.ts +67 -0
- package/dist/components/tidy-categorize.js +392 -0
- package/dist/components/tidy-diff.d.ts +60 -0
- package/dist/components/tidy-diff.js +147 -0
- package/dist/components/tidy-validate.d.ts +37 -0
- package/dist/components/tidy-validate.js +174 -0
- package/dist/content/compose.d.ts +1 -1
- package/dist/content/compose.js +11 -0
- package/dist/content/site-dictionary.d.ts +31 -0
- package/dist/content/site-dictionary.js +82 -0
- package/dist/content/types.d.ts +25 -0
- package/dist/doctor/checks-local.d.ts +1 -0
- package/dist/doctor/checks-local.js +55 -6
- package/dist/doctor/index.js +2 -1
- package/dist/log/events.d.ts +1 -1
- package/dist/nav/site-config.d.ts +98 -0
- package/dist/nav/site-config.js +132 -0
- package/dist/sveltekit/admin-dispatch.d.ts +2 -0
- package/dist/sveltekit/admin-dispatch.js +6 -2
- package/dist/sveltekit/cairn-admin.d.ts +13 -1
- package/dist/sveltekit/cairn-admin.js +22 -3
- package/dist/sveltekit/content-routes.d.ts +135 -1
- package/dist/sveltekit/content-routes.js +351 -3
- package/dist/sveltekit/tidy-prompt.d.ts +11 -0
- package/dist/sveltekit/tidy-prompt.js +118 -0
- package/package.json +10 -1
- package/src/lib/components/CairnAdmin.svelte +3 -0
- package/src/lib/components/CairnTidySettings.svelte +553 -0
- package/src/lib/components/EditPage.svelte +371 -2
- package/src/lib/components/MarkdownEditor.svelte +168 -1
- package/src/lib/components/TidyReview.svelte +463 -0
- package/src/lib/components/cairn-admin.css +25 -0
- package/src/lib/components/editor-tidy.ts +241 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/markdown-directives.ts +35 -0
- package/src/lib/components/objective-errors.ts +155 -0
- package/src/lib/components/spellcheck-assets/dictionary-en-us.txt +104743 -0
- package/src/lib/components/spellcheck-assets/spellchecker-wasm-LICENSE.txt +21 -0
- package/src/lib/components/spellcheck-assets/spellchecker-wasm.wasm +0 -0
- package/src/lib/components/spellcheck-worker.ts +279 -0
- package/src/lib/components/spellcheck.ts +679 -0
- package/src/lib/components/tidy-categorize.ts +460 -0
- package/src/lib/components/tidy-diff.ts +196 -0
- package/src/lib/components/tidy-validate.ts +202 -0
- package/src/lib/content/compose.ts +11 -1
- package/src/lib/content/site-dictionary.ts +84 -0
- package/src/lib/content/types.ts +25 -0
- package/src/lib/doctor/checks-local.ts +59 -5
- package/src/lib/doctor/index.ts +2 -0
- package/src/lib/log/events.ts +7 -1
- package/src/lib/nav/site-config.ts +197 -0
- package/src/lib/sveltekit/admin-dispatch.ts +7 -3
- package/src/lib/sveltekit/cairn-admin.ts +32 -4
- package/src/lib/sveltekit/content-routes.ts +504 -4
- package/src/lib/sveltekit/tidy-prompt.ts +153 -0
|
@@ -58,6 +58,17 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
58
58
|
registerImagePlaceholders?: (api: import('./editor-placeholder.js').ImagePlaceholderApi) => void;
|
|
59
59
|
/** Receives a `() => string` returning the selected text; the web link dialog reads it. */
|
|
60
60
|
registerGetSelection?: (get: () => string) => void;
|
|
61
|
+
/** Receives a `() => { from, to } | null` returning the selection's document offsets, or null when
|
|
62
|
+
* the selection is empty (a bare caret). The tidy host reads it so a selection tidy maps onto the
|
|
63
|
+
* exact selected span, never an identical-looking passage earlier in the document. */
|
|
64
|
+
registerGetSelectionRange?: (get: () => { from: number; to: number } | null) => void;
|
|
65
|
+
/** Receives the tidy apply api (spec 2.5): the review surface drives the in-buffer decorations and
|
|
66
|
+
* the accept/reject state machine through it. The author's original stays in the buffer until an
|
|
67
|
+
* accept writes; a reject or reject-all leaves it byte-identical. */
|
|
68
|
+
registerTidy?: (api: import('./editor-tidy.js').TidyApi) => void;
|
|
69
|
+
/** Receives a `() => void` that undoes the last editor transaction; the "Undo tidy" chip calls it
|
|
70
|
+
* to take the whole applied tidy back in one move (the apply lands as one history entry). */
|
|
71
|
+
registerUndo?: (undo: () => void) => void;
|
|
61
72
|
/** Receives a `(kind) => void` that transforms the current selection; the host's toolbar calls it. */
|
|
62
73
|
registerFormat?: (format: (kind: FormatKind) => void) => void;
|
|
63
74
|
/** Reports the directive container at the caret (or null when outside any container) whenever
|
|
@@ -84,6 +95,36 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
84
95
|
/** The surface posture. Prose is the writing instrument (72ch measure, larger type, looser
|
|
85
96
|
* leading); markup is the working surface (fills the card, denser). Prose by default. */
|
|
86
97
|
surface?: 'prose' | 'markup';
|
|
98
|
+
/** Spellcheck and the objective-error layer: the markdown-aware lint underlines. On by default;
|
|
99
|
+
* when off the lint compartment reconfigures to empty (the underlines vanish, the Worker stays
|
|
100
|
+
* idle). The footer toggle drives this. */
|
|
101
|
+
spellcheck?: boolean;
|
|
102
|
+
/** The dialect-resolved dictionary filename, e.g. "dictionary-en-us.txt", from EditData. The
|
|
103
|
+
* source resolves it to a real asset URL and hands it to the spellcheck Worker's init. Defaults to
|
|
104
|
+
* US English. */
|
|
105
|
+
spellcheckDictionary?: string;
|
|
106
|
+
/** The committed personal-dictionary words (spec 1.6), from EditData.siteDictionary. The lint
|
|
107
|
+
* source seeds the spellcheck Worker's personal layer with these at init, so a word another editor
|
|
108
|
+
* committed answers correct from the first lint. Empty by default (dialect-only). */
|
|
109
|
+
siteDictionary?: ReadonlyArray<string>;
|
|
110
|
+
/** The caller-owned pending personal-dictionary additions. When an author chooses "Add to
|
|
111
|
+
* dictionary" the lint source adds the lowercased word here (the underline clears at once); the
|
|
112
|
+
* host (EditPage) commits this set through the addDictionaryWord action at save time and reconciles
|
|
113
|
+
* it against the merged response. A fresh set by default. */
|
|
114
|
+
pendingAdditions?: Set<string>;
|
|
115
|
+
/** Test-only seam for the spellcheck Worker. The real wasm and dictionary assets are resolved with
|
|
116
|
+
* `import.meta.url` and do not load under the vitest browser dev server, so the component test
|
|
117
|
+
* injects a deterministic fake Worker factory and asks the lint source to skip the `ready` wait.
|
|
118
|
+
* When this is absent the production path is untouched: the real `new Worker(...)` and the real
|
|
119
|
+
* asset resolution. Never set this outside a test. */
|
|
120
|
+
spellcheckTest?: {
|
|
121
|
+
createWorker?: () => import('./spellcheck.js').SpellWorker;
|
|
122
|
+
assumeReady?: boolean;
|
|
123
|
+
};
|
|
124
|
+
/** Tidy mode: while a tidy review is open the surface is read-only the way Preview disables the
|
|
125
|
+
* toolbar, so the author cannot edit underneath a pending review. The host sets this when it opens
|
|
126
|
+
* the review and clears it on apply or cancel. Off by default. */
|
|
127
|
+
tidyMode?: boolean;
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
let {
|
|
@@ -98,6 +139,9 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
98
139
|
registerFocusEditor,
|
|
99
140
|
registerImagePlaceholders,
|
|
100
141
|
registerGetSelection,
|
|
142
|
+
registerGetSelectionRange,
|
|
143
|
+
registerTidy,
|
|
144
|
+
registerUndo,
|
|
101
145
|
registerFormat,
|
|
102
146
|
onComponentAtCaret,
|
|
103
147
|
onMediaImageAtCaret,
|
|
@@ -107,6 +151,12 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
107
151
|
focusMode = false,
|
|
108
152
|
typewriter = false,
|
|
109
153
|
surface = 'prose',
|
|
154
|
+
spellcheck = true,
|
|
155
|
+
spellcheckDictionary = 'dictionary-en-us.txt',
|
|
156
|
+
siteDictionary = [],
|
|
157
|
+
pendingAdditions = new Set<string>(),
|
|
158
|
+
spellcheckTest,
|
|
159
|
+
tidyMode = false,
|
|
110
160
|
}: Props = $props();
|
|
111
161
|
|
|
112
162
|
let host = $state<HTMLDivElement | null>(null);
|
|
@@ -127,6 +177,21 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
127
177
|
// module loads with the other dynamic editor modules in onMount.
|
|
128
178
|
let mediaCompartment: import('@codemirror/state').Compartment | null = null;
|
|
129
179
|
let mediaMod: typeof import('./editor-media.js') | null = null;
|
|
180
|
+
// The spellcheck lint source (and the objective-error layer it bundles) live in their own
|
|
181
|
+
// compartment, reconfigured to empty when the footer toggle turns spellcheck off. Both surfaces ride
|
|
182
|
+
// the one extension cairnSpellcheck returns, so one compartment gates both. The extension is built
|
|
183
|
+
// asynchronously (it lazy-imports CodeMirror and the lint modules), so it is held here once resolved
|
|
184
|
+
// and the on/off effect reconfigures against it.
|
|
185
|
+
let spellcheckCompartment: import('@codemirror/state').Compartment | null = null;
|
|
186
|
+
let spellcheckExt: import('@codemirror/state').Extension | null = null;
|
|
187
|
+
// The tidy decoration field lives in its own compartment (entering and leaving tidy is a reconfigure,
|
|
188
|
+
// not a rebuild) beside the media and fold decorations. A second compartment carries the read-only +
|
|
189
|
+
// edit-disable extension while a review is open, so the author cannot edit underneath a pending
|
|
190
|
+
// review (the same posture Preview takes on the toolbar). Both load with the other editor modules.
|
|
191
|
+
let tidyMod: typeof import('./editor-tidy.js') | null = null;
|
|
192
|
+
let tidyCompartment: import('@codemirror/state').Compartment | null = null;
|
|
193
|
+
let tidyReadonlyCompartment: import('@codemirror/state').Compartment | null = null;
|
|
194
|
+
let tidyReadonlyExt: import('@codemirror/state').Extension | null = null;
|
|
130
195
|
// The posture themes, swapped through the surface compartment. Each owns its type step and
|
|
131
196
|
// leading (the base theme deliberately sets neither on the content node, so the postures never
|
|
132
197
|
// contest it on adoption order). Built in onMount beside the base theme.
|
|
@@ -139,12 +204,15 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
139
204
|
const markdownMod = await import('@codemirror/lang-markdown');
|
|
140
205
|
const commandsMod = await import('@codemirror/commands');
|
|
141
206
|
const languageMod = await import('@codemirror/language');
|
|
207
|
+
const lintMod = await import('@codemirror/lint');
|
|
142
208
|
const autocompleteMod = await import('@codemirror/autocomplete');
|
|
143
209
|
const highlightMod = await import('./editor-highlight.js');
|
|
144
210
|
const modesMod = await import('./editor-modes.js');
|
|
145
211
|
const foldingMod = await import('./editor-folding.js');
|
|
146
212
|
const placeholderMod = await import('./editor-placeholder.js');
|
|
147
213
|
mediaMod = await import('./editor-media.js');
|
|
214
|
+
tidyMod = await import('./editor-tidy.js');
|
|
215
|
+
const spellcheckMod = await import('./spellcheck.js');
|
|
148
216
|
|
|
149
217
|
if (!host) return;
|
|
150
218
|
|
|
@@ -494,6 +562,37 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
494
562
|
'--cairn-directive-rail-3': 'var(--cairn-focus-dim-rail-3, 32%)',
|
|
495
563
|
'--cairn-directive-rail-active': 'var(--cairn-focus-dim-rail-active, 36%)',
|
|
496
564
|
},
|
|
565
|
+
// Tidy review decorations (spec 2.5). The author's original stays in the buffer; a deletion run
|
|
566
|
+
// strikes through in --cairn-error-ink (reserved for tidy deletions) and the proposed insertion
|
|
567
|
+
// shows as decoration content in --color-positive-ink (the locked addition token). The two are
|
|
568
|
+
// a locked pair: deletion red and insertion green never speak the same color, so the author sees
|
|
569
|
+
// exactly what tidy removes and what it adds. Both carry a non-color cue (the strike-through and
|
|
570
|
+
// the leading marker) so the change reads without hue alone.
|
|
571
|
+
'.cm-cairn-tidy-del': {
|
|
572
|
+
color: 'var(--cairn-error-ink, oklch(50% 0.19 25))',
|
|
573
|
+
textDecoration: 'line-through',
|
|
574
|
+
textDecorationThickness: '1px',
|
|
575
|
+
backgroundColor: 'color-mix(in oklab, var(--cairn-error-ink, oklch(50% 0.19 25)) 12%, transparent)',
|
|
576
|
+
borderRadius: '2px',
|
|
577
|
+
},
|
|
578
|
+
'.cm-cairn-tidy-del-marker': {
|
|
579
|
+
// A small leading wedge in the deletion ink, the non-color marker that pairs with the red.
|
|
580
|
+
display: 'inline-block',
|
|
581
|
+
width: '0',
|
|
582
|
+
borderLeft: '2px solid var(--cairn-error-ink, oklch(50% 0.19 25))',
|
|
583
|
+
height: '1em',
|
|
584
|
+
verticalAlign: '-0.15em',
|
|
585
|
+
marginRight: '1px',
|
|
586
|
+
},
|
|
587
|
+
'.cm-cairn-tidy-ins': {
|
|
588
|
+
color: 'var(--color-positive-ink, oklch(48% 0.12 150))',
|
|
589
|
+
backgroundColor: 'color-mix(in oklab, var(--color-positive-ink, oklch(48% 0.12 150)) 16%, transparent)',
|
|
590
|
+
borderRadius: '2px',
|
|
591
|
+
padding: '0 1px',
|
|
592
|
+
marginLeft: '2px',
|
|
593
|
+
// The non-color cue for an insertion: a leading caret glyph in the addition ink.
|
|
594
|
+
'&::before': { content: '"+"', fontSize: '0.8em', opacity: '0.7', marginRight: '1px' },
|
|
595
|
+
},
|
|
497
596
|
},
|
|
498
597
|
{ dark: isDark },
|
|
499
598
|
);
|
|
@@ -517,6 +616,30 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
517
616
|
typewriterCompartment = new stateMod.Compartment();
|
|
518
617
|
surfaceCompartment = new stateMod.Compartment();
|
|
519
618
|
mediaCompartment = new stateMod.Compartment();
|
|
619
|
+
spellcheckCompartment = new stateMod.Compartment();
|
|
620
|
+
tidyCompartment = new stateMod.Compartment();
|
|
621
|
+
tidyReadonlyCompartment = new stateMod.Compartment();
|
|
622
|
+
// The read-only posture while a tidy review is open: EditorState.readOnly bars edits, and
|
|
623
|
+
// editable: false drops the contenteditable so the surface is inert under the review (the same
|
|
624
|
+
// posture Preview takes). The compartment starts empty and the tidyMode effect swaps this in.
|
|
625
|
+
tidyReadonlyExt = [stateMod.EditorState.readOnly.of(true), viewMod.EditorView.editable.of(false)];
|
|
626
|
+
// Build the spellcheck extension once: the lint source resolves the dictionary asset URL from the
|
|
627
|
+
// dialect-resolved filename and posts it to the Worker's init. The compartment starts with the
|
|
628
|
+
// extension only when spellcheck is on, so a site that opens with it off never spins up the Worker.
|
|
629
|
+
spellcheckExt = await spellcheckMod.cairnSpellcheck({
|
|
630
|
+
dictionaryFile: spellcheckDictionary,
|
|
631
|
+
// Seed the Worker's personal layer from the committed site dictionary, and share the host's
|
|
632
|
+
// pending-additions set so an add-to-dictionary choice records here for the host to commit.
|
|
633
|
+
siteWords: siteDictionary,
|
|
634
|
+
pendingAdditions,
|
|
635
|
+
// Hand the lint source the editor's own CodeMirror module instances so its extension lands on the
|
|
636
|
+
// same copies; a separate dynamic import can resolve to a different instance and break instanceof.
|
|
637
|
+
modules: { lint: lintMod, language: languageMod, view: viewMod, state: stateMod },
|
|
638
|
+
// The test seam: a deterministic fake Worker and the skip-ready flag, both straight through to the
|
|
639
|
+
// lint source. Absent in production, where the real Worker and real asset resolution run.
|
|
640
|
+
createWorker: spellcheckTest?.createWorker,
|
|
641
|
+
assumeReady: spellcheckTest?.assumeReady,
|
|
642
|
+
});
|
|
520
643
|
|
|
521
644
|
view = new EditorView({
|
|
522
645
|
parent: host,
|
|
@@ -552,6 +675,14 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
552
675
|
// reconfigures it without rebuilding the editor. The chip and the atomic ranges read the
|
|
553
676
|
// library; an empty library decorates nothing.
|
|
554
677
|
mediaCompartment.of(mediaMod.cairnMediaDecorations(mediaLibrary)),
|
|
678
|
+
// The spellcheck and objective-error lint sources plus the locked amber underline theme, in
|
|
679
|
+
// their own compartment so the footer toggle gates both surfaces at once. Empty when off.
|
|
680
|
+
spellcheckCompartment.of(spellcheck ? spellcheckExt : []),
|
|
681
|
+
// The tidy decoration field, in its own compartment so entering and leaving a review is a
|
|
682
|
+
// reconfigure beside the media and fold decorations. The api the host drives is built below.
|
|
683
|
+
tidyCompartment.of(tidyMod.cairnTidy()),
|
|
684
|
+
// The read-only posture while a review is open, empty until the host sets tidyMode.
|
|
685
|
+
tidyReadonlyCompartment.of(tidyMode ? tidyReadonlyExt : []),
|
|
555
686
|
// Paste and drop ingest: an image carried by either gesture is preventDefault'd and handed
|
|
556
687
|
// to onImageIngest (the host opens the capture card with the bytes); a gesture carrying no
|
|
557
688
|
// image falls through to CodeMirror's default. 2b is single-file per gesture (open risk 3),
|
|
@@ -581,7 +712,11 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
581
712
|
return true;
|
|
582
713
|
},
|
|
583
714
|
}),
|
|
584
|
-
|
|
715
|
+
// No native text-correction override here (Task 7). The old `spellcheck: 'true'` is gone, so
|
|
716
|
+
// the content node falls back to CodeMirror's own defaults: spellcheck "false", autocorrect
|
|
717
|
+
// "off", autocapitalize "off". The cairn lint source replaces the browser's spellcheck
|
|
718
|
+
// (running both would double-underline), and autocorrect/autocapitalize stay off so a browser
|
|
719
|
+
// never silently rewrites a `media:` token, a directive name, or frontmatter.
|
|
585
720
|
theme,
|
|
586
721
|
surfaceCompartment.of(surface === 'prose' ? proseTheme : markupTheme),
|
|
587
722
|
EditorView.updateListener.of((update) => {
|
|
@@ -606,6 +741,11 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
606
741
|
registerFocusEditor?.(focusEditor);
|
|
607
742
|
registerImagePlaceholders?.(placeholderMod.imagePlaceholderApi(view));
|
|
608
743
|
registerGetSelection?.(selectedText);
|
|
744
|
+
registerGetSelectionRange?.(selectedRange);
|
|
745
|
+
registerTidy?.(tidyMod.tidyApi(view));
|
|
746
|
+
registerUndo?.(() => {
|
|
747
|
+
if (view) commandsMod.undo(view);
|
|
748
|
+
});
|
|
609
749
|
registerFormat?.(applyFormat);
|
|
610
750
|
registerReplaceRange?.(replaceRange);
|
|
611
751
|
registerSelectRange?.(selectRange);
|
|
@@ -654,6 +794,25 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
654
794
|
view.dispatch({ effects: mediaCompartment.reconfigure(mediaMod.cairnMediaDecorations(library)) });
|
|
655
795
|
});
|
|
656
796
|
|
|
797
|
+
// Reconfigure the spellcheck compartment when the footer toggle flips. On restores the bundled
|
|
798
|
+
// extension (both lint sources and the theme); off swaps in an empty extension, so the underlines
|
|
799
|
+
// vanish and the Worker goes idle. Reading the prop tracks it; the guard waits for the mounted
|
|
800
|
+
// editor and the resolved extension.
|
|
801
|
+
$effect(() => {
|
|
802
|
+
const on = spellcheck;
|
|
803
|
+
if (!mounted || !view || !spellcheckCompartment || !spellcheckExt) return;
|
|
804
|
+
view.dispatch({ effects: spellcheckCompartment.reconfigure(on ? spellcheckExt : []) });
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// Reconfigure the read-only posture when tidyMode flips. On makes the surface inert under the open
|
|
808
|
+
// review (no edits beneath a pending review); off restores editing on apply or cancel. Reading the
|
|
809
|
+
// prop tracks it; the guard waits for the mounted editor and the resolved extension.
|
|
810
|
+
$effect(() => {
|
|
811
|
+
const on = tidyMode;
|
|
812
|
+
if (!mounted || !view || !tidyReadonlyCompartment || !tidyReadonlyExt) return;
|
|
813
|
+
view.dispatch({ effects: tidyReadonlyCompartment.reconfigure(on ? tidyReadonlyExt : []) });
|
|
814
|
+
});
|
|
815
|
+
|
|
657
816
|
// The last value handed to onComponentAtCaret, so the reporter fires only on a change. The
|
|
658
817
|
// identity compared is name + markdown + from + to. A pure caret move within one block leaves all
|
|
659
818
|
// four unchanged, so it does not refire; an edit inside the block changes the markdown even when
|
|
@@ -792,6 +951,14 @@ through the adapter's render. Swapping the editor stays a one-file change.
|
|
|
792
951
|
return view.state.sliceDoc(from, to);
|
|
793
952
|
}
|
|
794
953
|
|
|
954
|
+
// The selection's document offsets, for the tidy host to scope a selection tidy to the exact span.
|
|
955
|
+
// Null when the selection is empty (a bare caret), which the host reads as document scope.
|
|
956
|
+
function selectedRange(): { from: number; to: number } | null {
|
|
957
|
+
if (!view) return null;
|
|
958
|
+
const { from, to } = view.state.selection.main;
|
|
959
|
+
return from === to ? null : { from, to };
|
|
960
|
+
}
|
|
961
|
+
|
|
795
962
|
// The caret's viewport coordinates, for the insert popover to anchor itself to the cursor. Null
|
|
796
963
|
// before the editor mounts or when the caret has no measurable position (an unrendered line).
|
|
797
964
|
function caretCoords(): { left: number; right: number; top: number; bottom: number } | null {
|
|
@@ -46,6 +46,20 @@ interface Props {
|
|
|
46
46
|
registerImagePlaceholders?: (api: import('./editor-placeholder.js').ImagePlaceholderApi) => void;
|
|
47
47
|
/** Receives a `() => string` returning the selected text; the web link dialog reads it. */
|
|
48
48
|
registerGetSelection?: (get: () => string) => void;
|
|
49
|
+
/** Receives a `() => { from, to } | null` returning the selection's document offsets, or null when
|
|
50
|
+
* the selection is empty (a bare caret). The tidy host reads it so a selection tidy maps onto the
|
|
51
|
+
* exact selected span, never an identical-looking passage earlier in the document. */
|
|
52
|
+
registerGetSelectionRange?: (get: () => {
|
|
53
|
+
from: number;
|
|
54
|
+
to: number;
|
|
55
|
+
} | null) => void;
|
|
56
|
+
/** Receives the tidy apply api (spec 2.5): the review surface drives the in-buffer decorations and
|
|
57
|
+
* the accept/reject state machine through it. The author's original stays in the buffer until an
|
|
58
|
+
* accept writes; a reject or reject-all leaves it byte-identical. */
|
|
59
|
+
registerTidy?: (api: import('./editor-tidy.js').TidyApi) => void;
|
|
60
|
+
/** Receives a `() => void` that undoes the last editor transaction; the "Undo tidy" chip calls it
|
|
61
|
+
* to take the whole applied tidy back in one move (the apply lands as one history entry). */
|
|
62
|
+
registerUndo?: (undo: () => void) => void;
|
|
49
63
|
/** Receives a `(kind) => void` that transforms the current selection; the host's toolbar calls it. */
|
|
50
64
|
registerFormat?: (format: (kind: FormatKind) => void) => void;
|
|
51
65
|
/** Reports the directive container at the caret (or null when outside any container) whenever
|
|
@@ -72,6 +86,36 @@ interface Props {
|
|
|
72
86
|
/** The surface posture. Prose is the writing instrument (72ch measure, larger type, looser
|
|
73
87
|
* leading); markup is the working surface (fills the card, denser). Prose by default. */
|
|
74
88
|
surface?: 'prose' | 'markup';
|
|
89
|
+
/** Spellcheck and the objective-error layer: the markdown-aware lint underlines. On by default;
|
|
90
|
+
* when off the lint compartment reconfigures to empty (the underlines vanish, the Worker stays
|
|
91
|
+
* idle). The footer toggle drives this. */
|
|
92
|
+
spellcheck?: boolean;
|
|
93
|
+
/** The dialect-resolved dictionary filename, e.g. "dictionary-en-us.txt", from EditData. The
|
|
94
|
+
* source resolves it to a real asset URL and hands it to the spellcheck Worker's init. Defaults to
|
|
95
|
+
* US English. */
|
|
96
|
+
spellcheckDictionary?: string;
|
|
97
|
+
/** The committed personal-dictionary words (spec 1.6), from EditData.siteDictionary. The lint
|
|
98
|
+
* source seeds the spellcheck Worker's personal layer with these at init, so a word another editor
|
|
99
|
+
* committed answers correct from the first lint. Empty by default (dialect-only). */
|
|
100
|
+
siteDictionary?: ReadonlyArray<string>;
|
|
101
|
+
/** The caller-owned pending personal-dictionary additions. When an author chooses "Add to
|
|
102
|
+
* dictionary" the lint source adds the lowercased word here (the underline clears at once); the
|
|
103
|
+
* host (EditPage) commits this set through the addDictionaryWord action at save time and reconciles
|
|
104
|
+
* it against the merged response. A fresh set by default. */
|
|
105
|
+
pendingAdditions?: Set<string>;
|
|
106
|
+
/** Test-only seam for the spellcheck Worker. The real wasm and dictionary assets are resolved with
|
|
107
|
+
* `import.meta.url` and do not load under the vitest browser dev server, so the component test
|
|
108
|
+
* injects a deterministic fake Worker factory and asks the lint source to skip the `ready` wait.
|
|
109
|
+
* When this is absent the production path is untouched: the real `new Worker(...)` and the real
|
|
110
|
+
* asset resolution. Never set this outside a test. */
|
|
111
|
+
spellcheckTest?: {
|
|
112
|
+
createWorker?: () => import('./spellcheck.js').SpellWorker;
|
|
113
|
+
assumeReady?: boolean;
|
|
114
|
+
};
|
|
115
|
+
/** Tidy mode: while a tidy review is open the surface is read-only the way Preview disables the
|
|
116
|
+
* toolbar, so the author cannot edit underneath a pending review. The host sets this when it opens
|
|
117
|
+
* the review and clears it on apply or cancel. Off by default. */
|
|
118
|
+
tidyMode?: boolean;
|
|
75
119
|
}
|
|
76
120
|
/**
|
|
77
121
|
* The `MarkdownEditor` seam (spec §6, seam 5): a thin wrapper over CodeMirror 6 exposing a bindable
|