@voithos-labs/aragonite 0.10.2 → 0.10.3
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/README.md +6 -21
- package/THIRD-PARTY-NOTICES.md +25 -0
- package/dist/a11y-strings.d.ts +18 -0
- package/dist/a11y-strings.js +18 -0
- package/dist/action-contracts.d.ts +7 -1
- package/dist/ambient/ambient-dom.js +5 -1
- package/dist/block-component.d.ts +14 -0
- package/dist/components/BlockDragHandle.svelte +35 -24
- package/dist/components/BlockHost.svelte +16 -9
- package/dist/components/Editor.svelte +381 -198
- package/dist/components/Editor.svelte.d.ts +1 -1
- package/dist/components/SelectionOverlay.svelte +17 -3
- package/dist/components/TailInsert.svelte +107 -0
- package/dist/components/TailInsert.svelte.d.ts +17 -0
- package/dist/components/block-content-selector.d.ts +6 -2
- package/dist/components/block-content-selector.js +6 -2
- package/dist/components/blocks/ThematicBreakBlock.svelte +19 -6
- package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/code/CodeBlock.svelte +211 -30
- package/dist/components/blocks/code/CodeBlockRail.svelte +686 -0
- package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
- package/dist/components/blocks/code/code-bootstrap.js +4 -0
- package/dist/components/blocks/code/code-context-actions.d.ts +1 -0
- package/dist/components/blocks/code/code-context-actions.js +24 -0
- package/dist/components/blocks/code/code-fence-exit.d.ts +15 -0
- package/dist/components/blocks/code/code-fence-exit.js +26 -0
- package/dist/components/blocks/code/code-languages.d.ts +6 -0
- package/dist/components/blocks/code/code-languages.js +11 -0
- package/dist/components/blocks/code/code-renderer.js +11 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +1 -1
- package/dist/components/blocks/editable-leaf.d.ts +37 -6
- package/dist/components/blocks/editable-leaf.js +243 -29
- package/dist/components/blocks/editable-surface.d.ts +9 -0
- package/dist/components/blocks/editable-surface.js +22 -3
- package/dist/components/blocks/list/ListItemBlock.svelte +3 -3
- package/dist/components/blocks/list/task-checkbox.d.ts +2 -0
- package/dist/components/blocks/list/task-checkbox.js +11 -2
- package/dist/components/blocks/surface-wiring.svelte.d.ts +4 -0
- package/dist/components/blocks/surface-wiring.svelte.js +8 -1
- package/dist/components/blocks/table/TableActionMenu.svelte +210 -86
- package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +5 -0
- package/dist/components/blocks/table/TableBlock.svelte +205 -186
- package/dist/components/blocks/table/TableBlock.svelte.d.ts +1 -0
- package/dist/components/blocks/table/TableCellBlock.svelte +87 -22
- package/dist/components/blocks/table/TableRowBlock.svelte +4 -18
- package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +0 -5
- package/dist/components/blocks/table/cell-clipboard.d.ts +10 -0
- package/dist/components/blocks/table/cell-clipboard.js +34 -1
- package/dist/components/blocks/table/cell-keydown-plan.d.ts +1 -1
- package/dist/components/blocks/table/cell-keydown-plan.js +3 -1
- package/dist/components/blocks/table/table-cell-paste.js +2 -1
- package/dist/components/blocks/table/table-menu-model.d.ts +35 -8
- package/dist/components/blocks/table/table-menu-model.js +36 -17
- package/dist/components/blocks/text/TextEditableBlock.svelte +58 -11
- package/dist/components/blocks/text/delimiter-autopair.d.ts +71 -0
- package/dist/components/blocks/text/delimiter-autopair.js +216 -0
- package/dist/components/blocks/text/edge-policy-dispatch.d.ts +4 -0
- package/dist/components/blocks/text/edge-policy-dispatch.js +56 -2
- package/dist/components/blocks/text/live-selection-edit.js +27 -0
- package/dist/components/blocks/text/text-keydown.d.ts +7 -1
- package/dist/components/blocks/text/text-keydown.js +11 -1
- package/dist/components/blocks/text/text-render.d.ts +1 -1
- package/dist/components/blocks/text/text-render.js +3 -1
- package/dist/components/blocks/text/widget-interaction.d.ts +3 -0
- package/dist/components/blocks/text/widget-interaction.js +144 -22
- package/dist/components/drag-handle.d.ts +35 -0
- package/dist/components/drag-handle.js +126 -0
- package/dist/components/editor-root-focus.d.ts +19 -0
- package/dist/components/editor-root-focus.js +67 -0
- package/dist/components/editor-root-geometry.d.ts +39 -0
- package/dist/components/editor-root-geometry.js +91 -0
- package/dist/components/editor-root-keydown.d.ts +1 -1
- package/dist/components/editor-root-keydown.js +12 -3
- package/dist/components/editor-root-listeners.d.ts +10 -6
- package/dist/components/editor-root-listeners.js +23 -22
- package/dist/components/editor-root-mode-flip.d.ts +36 -0
- package/dist/components/editor-root-mode-flip.js +92 -0
- package/dist/components/image/ImageOverlayHost.svelte +14 -6
- package/dist/components/image/ImageProperties.svelte +512 -70
- package/dist/components/image/ImageProperties.svelte.d.ts +6 -1
- package/dist/components/image/ImageResizeHandles.svelte +59 -34
- package/dist/components/image/image-crop.d.ts +39 -0
- package/dist/components/image/image-crop.js +74 -0
- package/dist/components/image/image-edit-commit.d.ts +1 -0
- package/dist/components/image/image-edit-commit.js +21 -5
- package/dist/components/image/image-source-bytes.js +10 -3
- package/dist/components/image/image-widget-editing.js +1 -0
- package/dist/components/image/widget-dom.js +5 -1
- package/dist/components/link-card/link-card-commit.js +1 -1
- package/dist/components/lrd-map-gate.js +1 -1
- package/dist/components/menu/BlockMenu.svelte +315 -0
- package/dist/components/menu/BlockMenu.svelte.d.ts +34 -0
- package/dist/components/menu/MenuIcon.svelte +153 -0
- package/dist/components/menu/MenuIcon.svelte.d.ts +51 -0
- package/dist/components/menu/clipboard-actions.d.ts +12 -0
- package/dist/components/menu/clipboard-actions.js +42 -0
- package/dist/components/menu/default-context-actions.d.ts +15 -0
- package/dist/components/menu/default-context-actions.js +76 -0
- package/dist/components/menu/flyout-placement.d.ts +6 -0
- package/dist/components/menu/flyout-placement.js +25 -0
- package/dist/core/inline/format-toggle.d.ts +12 -4
- package/dist/core/inline/format-toggle.js +94 -40
- package/dist/core/inline/image-dimensions.d.ts +3 -0
- package/dist/core/inline/image-dimensions.js +46 -10
- package/dist/core/inline/inline-widgets.d.ts +11 -0
- package/dist/core/inline/scan/brackets.js +1 -0
- package/dist/core/inline/scan/plugin-syntax.d.ts +8 -0
- package/dist/core/inline/scan/plugin-syntax.js +15 -1
- package/dist/core/inline-render.d.ts +6 -0
- package/dist/core/inline-render.js +32 -0
- package/dist/core/nodes.d.ts +14 -0
- package/dist/cursor/edge-affinity.js +2 -1
- package/dist/cursor/overlay-remeasure.js +8 -0
- package/dist/cursor/reveal-source.js +7 -2
- package/dist/cursor/widget-offset.d.ts +6 -0
- package/dist/cursor/widget-offset.js +61 -4
- package/dist/debug/interaction-trace.d.ts +4 -0
- package/dist/debug/interaction-trace.js +15 -0
- package/dist/decorations/decoration-state.svelte.js +1 -1
- package/dist/editor-actions/ancestry-folds.d.ts +2 -2
- package/dist/editor-actions/ancestry-folds.js +1 -1
- package/dist/editor-actions/block-edit-scope.js +1 -1
- package/dist/editor-actions/commit/text-batch.d.ts +3 -2
- package/dist/editor-actions/commit/text-batch.js +1 -1
- package/dist/editor-actions/commit/undo-controller.js +4 -2
- package/dist/editor-actions/container-edit.js +2 -1
- package/dist/editor-actions/enter-completion.d.ts +2 -0
- package/dist/editor-actions/enter-completion.js +23 -2
- package/dist/editor-actions/inline-range-commit.js +1 -1
- package/dist/editor-actions/reorder-action.js +19 -10
- package/dist/editor-actions/reorder-drag.js +29 -1
- package/dist/editor-actions/replacement-focus.d.ts +1 -1
- package/dist/editor-actions/replacement-focus.js +1 -1
- package/dist/editor-actions/search-replace.js +1 -1
- package/dist/editor-actions/table-context.d.ts +4 -1
- package/dist/editor-actions/table-context.js +57 -1
- package/dist/editor-events.d.ts +3 -0
- package/dist/editor-keys.d.ts +33 -0
- package/dist/editor-props.d.ts +21 -12
- package/dist/index.d.ts +1 -1
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +11 -0
- package/dist/plugins/latex/BlockMath.svelte +264 -29
- package/dist/plugins/latex/BlockMath.svelte.d.ts +2 -0
- package/dist/plugins/latex/index.d.ts +2 -1
- package/dist/plugins/latex/latex-kind.js +36 -3
- package/dist/plugins/latex/math-completion.js +4 -1
- package/dist/plugins/latex/math-layout.d.ts +15 -0
- package/dist/plugins/latex/math-layout.js +13 -0
- package/dist/plugins/latex/math-source.d.ts +19 -0
- package/dist/plugins/latex/math-source.js +97 -0
- package/dist/plugins/latex/register.d.ts +11 -2
- package/dist/plugins/latex/register.js +3 -1
- package/dist/plugins/latex/renderer.d.ts +3 -3
- package/dist/plugins/latex/renderer.js +13 -6
- package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
- package/dist/reactivity/list-windowing.svelte.js +49 -24
- package/dist/schema/block-completions.d.ts +8 -0
- package/dist/schema/block-completions.js +11 -0
- package/dist/schema/context-actions.d.ts +31 -0
- package/dist/schema/context-actions.js +23 -0
- package/dist/schema/fenced-code-raw.js +31 -1
- package/dist/schema/operations.d.ts +8 -1
- package/dist/schema/reserved-chords.js +25 -4
- package/dist/schema/table-cell-raw.d.ts +1 -1
- package/dist/schema/table-cell-raw.js +1 -1
- package/dist/selection/block-hit-test.js +3 -2
- package/dist/selection/char-endpoint-snap.js +1 -1
- package/dist/selection/clipboard-text.js +6 -1
- package/dist/selection/covered-block.d.ts +10 -0
- package/dist/selection/covered-block.js +24 -0
- package/dist/selection/cross-block/dispatch.d.ts +3 -0
- package/dist/selection/cross-block/dispatch.js +13 -1
- package/dist/selection/cross-block/format-range.d.ts +1 -1
- package/dist/selection/cross-block/format-range.js +4 -15
- package/dist/selection/cross-block/format-toggle.js +1 -1
- package/dist/selection/cross-block/keydown.js +1 -1
- package/dist/selection/cross-block/ops.js +1 -1
- package/dist/selection/cross-block/paste.js +32 -30
- package/dist/selection/cross-block/type-replace.d.ts +3 -2
- package/dist/selection/cross-block/type-replace.js +62 -13
- package/dist/selection/dead-space-caret.d.ts +10 -0
- package/dist/selection/dead-space-caret.js +47 -1
- package/dist/selection/double-click-trim.d.ts +17 -0
- package/dist/selection/double-click-trim.js +57 -0
- package/dist/selection/drag-pointer.d.ts +7 -2
- package/dist/selection/drag-pointer.js +61 -2
- package/dist/selection/gap-caret.js +1 -1
- package/dist/selection/keyboard-extend.js +1 -1
- package/dist/selection/path-lookup.js +1 -1
- package/dist/selection/range-delete-ceremony.js +4 -2
- package/dist/selection/range-delete-chrome.js +2 -1
- package/dist/selection/range-delete-table-coverage.js +2 -1
- package/dist/selection/range-delete-table.js +3 -2
- package/dist/selection/range-delete.js +30 -2
- package/dist/selection/selection-restore.js +1 -1
- package/dist/selection/selection-state.svelte.d.ts +6 -0
- package/dist/selection/selection-state.svelte.js +36 -1
- package/dist/selection/table-endpoint-snap.js +1 -1
- package/dist/selection/table-rect-extend.js +1 -1
- package/dist/styles/editor-theme.css +57 -28
- package/dist/styles/editor.css +217 -18
- package/dist/testing/container-conformance.js +2 -2
- package/dist/testing/inline-conformance.js +2 -1
- package/dist/tree-operations/blockquote.js +1 -1
- package/dist/tree-operations/chain-rebuild.d.ts +63 -0
- package/dist/tree-operations/chain-rebuild.js +142 -0
- package/dist/tree-operations/children.d.ts +1 -1
- package/dist/tree-operations/children.js +1 -1
- package/dist/tree-operations/cleanup.js +1 -1
- package/dist/tree-operations/content-write.d.ts +50 -0
- package/dist/tree-operations/content-write.js +263 -0
- package/dist/tree-operations/index.d.ts +8 -3
- package/dist/tree-operations/index.js +6 -2
- package/dist/tree-operations/list/exit-replacement.js +1 -1
- package/dist/tree-operations/list/unwrap-merge.js +3 -3
- package/dist/tree-operations/node-ops.d.ts +17 -234
- package/dist/tree-operations/node-ops.js +47 -1113
- package/dist/tree-operations/node-primitives.d.ts +75 -0
- package/dist/tree-operations/node-primitives.js +117 -0
- package/dist/tree-operations/paste/apply.js +1 -1
- package/dist/tree-operations/paste/body-write.d.ts +1 -1
- package/dist/tree-operations/paste/body-write.js +2 -2
- package/dist/tree-operations/paste/container-match.js +4 -2
- package/dist/tree-operations/paste/dispatch.js +2 -1
- package/dist/tree-operations/paste/find-enclosing-list.js +1 -1
- package/dist/tree-operations/paste/focus-target.d.ts +1 -1
- package/dist/tree-operations/paste/list-absorb.js +1 -1
- package/dist/tree-operations/paste/list-break-out.js +1 -1
- package/dist/tree-operations/paste/parent-scope.js +1 -1
- package/dist/tree-operations/paste/paste-replacement.js +1 -1
- package/dist/tree-operations/paste/replace-block-at-parent.js +1 -1
- package/dist/tree-operations/path-mutate.d.ts +1 -1
- package/dist/tree-operations/path-mutate.js +2 -1
- package/dist/tree-operations/reorder-unit.js +1 -1
- package/dist/tree-operations/reorder.d.ts +5 -2
- package/dist/tree-operations/reorder.js +55 -2
- package/dist/tree-operations/settle.d.ts +105 -0
- package/dist/tree-operations/settle.js +660 -0
- package/dist/tree-operations/table-grid-clipboard.d.ts +21 -0
- package/dist/tree-operations/table-grid-clipboard.js +90 -0
- package/dist/tree-operations/unshare.d.ts +15 -77
- package/dist/tree-operations/unshare.js +15 -166
- package/docs/guide/consumer-guide.md +126 -96
- package/docs/guide/plugin-api.md +31 -3
- package/docs/guide/plugin-guide.md +26 -3
- package/package.json +4 -2
- package/dist/components/blocks/code/CodeLanguageChip.svelte +0 -127
- package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +0 -14
- package/dist/components/blocks/table/TableGrip.svelte +0 -91
- package/dist/components/blocks/table/TableGrip.svelte.d.ts +0 -8
- package/dist/components/blocks/table/table-drop-target.d.ts +0 -1
- package/dist/components/blocks/table/table-drop-target.js +0 -16
- package/dist/components/blocks/table/table-reorder-drag.d.ts +0 -78
- package/dist/components/blocks/table/table-reorder-drag.js +0 -97
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the body sits inside the block's source. The fence lines carry no glyph of their own, so
|
|
3
|
+
* a point measured against the rendered equation names a place in this span and nowhere else.
|
|
4
|
+
*/
|
|
5
|
+
export declare function mathBodySpan(text: string): {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A `$$` block with no body LINE — `$$$$`, `$$\n$$`, a whitespace-only one-liner — has nowhere
|
|
11
|
+
* for a caret to sit once the fence lines hide, and Backspace has no byte it could mean. The
|
|
12
|
+
* completion every such block takes as its source is revealed: opener, one empty body line,
|
|
13
|
+
* closer, caret on that line. A block that already has a body line, blank or not, is left alone.
|
|
14
|
+
*/
|
|
15
|
+
export declare function completeBareMathSource(text: string): {
|
|
16
|
+
text: string;
|
|
17
|
+
caret: number;
|
|
18
|
+
} | null;
|
|
19
|
+
export declare function renderMathSource(text: string): DocumentFragment;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `$$` block's source as DOM, shaped like a code block's: each fence line in a
|
|
3
|
+
* `.md-fence-line` wrapper the marker-hiding modes collapse, the body as LaTeX highlight
|
|
4
|
+
* tokens. Text-preserving by construction — opener + body + closer is the input — so the
|
|
5
|
+
* offset walk and G1.28 hold. Anything not shaped like a fence paints as plain tokens.
|
|
6
|
+
*/
|
|
7
|
+
import { highlightCode } from '../../plugin';
|
|
8
|
+
const FENCE = '$$';
|
|
9
|
+
function sliceMathSource(text) {
|
|
10
|
+
if (!text.startsWith(FENCE))
|
|
11
|
+
return { opener: '', body: text, closer: '' };
|
|
12
|
+
const firstNewline = text.indexOf('\n');
|
|
13
|
+
// One-line form: `$$x^2$$`.
|
|
14
|
+
if (firstNewline === -1) {
|
|
15
|
+
if (text.length >= 4 && text.endsWith(FENCE)) {
|
|
16
|
+
return { opener: FENCE, body: text.slice(2, -2), closer: FENCE };
|
|
17
|
+
}
|
|
18
|
+
return { opener: '', body: text, closer: '' };
|
|
19
|
+
}
|
|
20
|
+
if (text.slice(0, firstNewline) !== FENCE)
|
|
21
|
+
return { opener: '', body: text, closer: '' };
|
|
22
|
+
const opener = text.slice(0, firstNewline + 1);
|
|
23
|
+
const rest = text.slice(opener.length);
|
|
24
|
+
const lastNewline = rest.lastIndexOf('\n');
|
|
25
|
+
const lastLine = lastNewline === -1 ? rest : rest.slice(lastNewline + 1);
|
|
26
|
+
if (lastLine !== FENCE)
|
|
27
|
+
return { opener, body: rest, closer: '' };
|
|
28
|
+
if (lastNewline === -1)
|
|
29
|
+
return { opener, body: '', closer: FENCE };
|
|
30
|
+
// The newline before the closer belongs to the closer's line (the code block's rule), so
|
|
31
|
+
// collapsing that line leaves no blank line at the box's edge — unless the body is blank,
|
|
32
|
+
// where that newline IS the one line the caret can sit on.
|
|
33
|
+
const bodyWithEnding = rest.slice(0, lastNewline + 1);
|
|
34
|
+
if (!/\S/.test(bodyWithEnding))
|
|
35
|
+
return { opener, body: bodyWithEnding, closer: FENCE };
|
|
36
|
+
return { opener, body: rest.slice(0, lastNewline), closer: rest.slice(lastNewline) };
|
|
37
|
+
}
|
|
38
|
+
function fenceLine(text) {
|
|
39
|
+
const line = document.createElement('span');
|
|
40
|
+
line.className = 'md-fence-line';
|
|
41
|
+
const marker = document.createElement('span');
|
|
42
|
+
marker.className = 'md-marker md-fence';
|
|
43
|
+
marker.textContent = text.replace(/\n$/, '').replace(/^\n/, '');
|
|
44
|
+
if (text.startsWith('\n'))
|
|
45
|
+
line.appendChild(document.createTextNode('\n'));
|
|
46
|
+
line.appendChild(marker);
|
|
47
|
+
if (text.endsWith('\n'))
|
|
48
|
+
line.appendChild(document.createTextNode('\n'));
|
|
49
|
+
return line;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Where the body sits inside the block's source. The fence lines carry no glyph of their own, so
|
|
53
|
+
* a point measured against the rendered equation names a place in this span and nowhere else.
|
|
54
|
+
*/
|
|
55
|
+
export function mathBodySpan(text) {
|
|
56
|
+
if (/^[ \t]*(?:`{3,}|~{3,})/.test(text)) {
|
|
57
|
+
const firstBreak = text.indexOf('\n');
|
|
58
|
+
if (firstBreak === -1)
|
|
59
|
+
return { start: text.length, end: text.length };
|
|
60
|
+
const closer = /(?:\r?\n)?[ \t]*(?:`{3,}|~{3,})[ \t]*\r?\n?$/.exec(text);
|
|
61
|
+
return { start: firstBreak + 1, end: closer ? closer.index : text.length };
|
|
62
|
+
}
|
|
63
|
+
const { opener, body } = sliceMathSource(text);
|
|
64
|
+
return { start: opener.length, end: opener.length + body.length };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A `$$` block with no body LINE — `$$$$`, `$$\n$$`, a whitespace-only one-liner — has nowhere
|
|
68
|
+
* for a caret to sit once the fence lines hide, and Backspace has no byte it could mean. The
|
|
69
|
+
* completion every such block takes as its source is revealed: opener, one empty body line,
|
|
70
|
+
* closer, caret on that line. A block that already has a body line, blank or not, is left alone.
|
|
71
|
+
*/
|
|
72
|
+
export function completeBareMathSource(text) {
|
|
73
|
+
const { opener, body, closer } = sliceMathSource(text);
|
|
74
|
+
if (!opener || !closer)
|
|
75
|
+
return null;
|
|
76
|
+
if (body.includes('\n') || body.trim() !== '')
|
|
77
|
+
return null;
|
|
78
|
+
const completed = `${FENCE}\n\n${FENCE}`;
|
|
79
|
+
return { text: completed, caret: FENCE.length + 1 };
|
|
80
|
+
}
|
|
81
|
+
export function renderMathSource(text) {
|
|
82
|
+
const { opener, body, closer } = sliceMathSource(text);
|
|
83
|
+
const frag = document.createDocumentFragment();
|
|
84
|
+
if (opener)
|
|
85
|
+
frag.appendChild(fenceLine(opener));
|
|
86
|
+
frag.appendChild(highlightCode(body, 'latex'));
|
|
87
|
+
// An empty last body line has only the hidden closer after it; the anchor gives the caret
|
|
88
|
+
// that line (see `code-renderer.ts`).
|
|
89
|
+
if (closer.startsWith('\n') && body.endsWith('\n')) {
|
|
90
|
+
const anchor = document.createElement('br');
|
|
91
|
+
anchor.dataset.caretAnchor = 'closer';
|
|
92
|
+
frag.appendChild(anchor);
|
|
93
|
+
}
|
|
94
|
+
if (closer)
|
|
95
|
+
frag.appendChild(fenceLine(closer));
|
|
96
|
+
return frag;
|
|
97
|
+
}
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type EditorPlugin } from '../../plugin';
|
|
7
7
|
import { type MathRenderer } from './math-renderer';
|
|
8
|
-
|
|
8
|
+
import { type MathBlockLayout } from './math-layout';
|
|
9
|
+
export interface LatexPluginOptions {
|
|
9
10
|
renderer: MathRenderer;
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* How a `$$` block opens for editing: `split` (source beside the preview, the default),
|
|
13
|
+
* `stacked` (preview below), or `source` (no preview). Definition-time, so it is the
|
|
14
|
+
* bare-install default; an editor's `{ plugin, options: { blockLayout } }` entry overrides it
|
|
15
|
+
* per instance. The block's own toggle cycles from whichever applies.
|
|
16
|
+
*/
|
|
17
|
+
blockLayout?: MathBlockLayout;
|
|
18
|
+
}
|
|
19
|
+
export declare function latexPlugin(options: LatexPluginOptions): EditorPlugin;
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
import { definePlugin, registerBlockComponent, defineBlockComponent, declaredPluginKind } from '../../plugin';
|
|
7
7
|
import { registerMathInline, registerMathBlock, MATH_BLOCK, MATH_FENCE } from './latex-kind';
|
|
8
8
|
import { setMathRenderer } from './math-renderer';
|
|
9
|
+
import { isMathBlockLayout } from './math-layout';
|
|
9
10
|
import BlockMath from './BlockMath.svelte';
|
|
10
11
|
export function latexPlugin(options) {
|
|
12
|
+
const blockLayout = isMathBlockLayout(options.blockLayout) ? options.blockLayout : 'split';
|
|
11
13
|
return definePlugin({
|
|
12
14
|
name: 'latex',
|
|
13
15
|
setup() {
|
|
@@ -15,7 +17,7 @@ export function latexPlugin(options) {
|
|
|
15
17
|
registerMathInline();
|
|
16
18
|
// registerMathBlock co-registers the ```math fence kind; both render through BlockMath.
|
|
17
19
|
registerMathBlock();
|
|
18
|
-
const blockMath = defineBlockComponent(BlockMath);
|
|
20
|
+
const blockMath = defineBlockComponent(BlockMath, () => ({ blockLayout }));
|
|
19
21
|
registerBlockComponent(declaredPluginKind(MATH_BLOCK), blockMath);
|
|
20
22
|
registerBlockComponent(declaredPluginKind(MATH_FENCE), blockMath);
|
|
21
23
|
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import 'katex/dist/katex.min.css';
|
|
7
7
|
import type { MathRenderer } from './math-renderer';
|
|
8
8
|
/**
|
|
9
|
-
* `throwOnError: false` keeps a malformed formula from crashing the editor
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* `throwOnError: false` keeps a malformed formula from crashing the editor; its fallback is
|
|
10
|
+
* swapped for the source painted as an error — red, in the code face, the parser's message on
|
|
11
|
+
* hover — so a broken formula reads as what the author typed, not as a sentence about it.
|
|
12
12
|
*/
|
|
13
13
|
export declare const katexRenderer: MathRenderer;
|
|
@@ -8,9 +8,9 @@ import katex from 'katex';
|
|
|
8
8
|
// a `.katex-mathml` a11y tree this CSS clips to 1px, and unloaded every equation paints twice.
|
|
9
9
|
import 'katex/dist/katex.min.css';
|
|
10
10
|
/**
|
|
11
|
-
* `throwOnError: false` keeps a malformed formula from crashing the editor
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `throwOnError: false` keeps a malformed formula from crashing the editor; its fallback is
|
|
12
|
+
* swapped for the source painted as an error — red, in the code face, the parser's message on
|
|
13
|
+
* hover — so a broken formula reads as what the author typed, not as a sentence about it.
|
|
14
14
|
*/
|
|
15
15
|
export const katexRenderer = (source, { display }) => {
|
|
16
16
|
const container = document.createElement('span');
|
|
@@ -22,13 +22,20 @@ export const katexRenderer = (source, { display }) => {
|
|
|
22
22
|
const errorSpan = container.querySelector('.katex-error');
|
|
23
23
|
if (errorSpan) {
|
|
24
24
|
const message = (errorSpan.getAttribute('title') ?? 'invalid LaTeX').replace(/^ParseError:\s*/, '');
|
|
25
|
-
return { dom: buildErrorNode(message), error: message };
|
|
25
|
+
return { dom: buildErrorNode(source, message), error: message };
|
|
26
26
|
}
|
|
27
27
|
return { dom: container };
|
|
28
28
|
};
|
|
29
|
-
function buildErrorNode(message) {
|
|
29
|
+
function buildErrorNode(source, message) {
|
|
30
30
|
const span = document.createElement('span');
|
|
31
31
|
span.className = 'math-error';
|
|
32
|
-
span.textContent =
|
|
32
|
+
span.textContent = source;
|
|
33
|
+
span.title = message;
|
|
34
|
+
// Inline style rather than a stylesheet: the span lands inside any prose block, where no
|
|
35
|
+
// plugin sheet is guaranteed to be mounted.
|
|
36
|
+
span.style.color = 'var(--color-error, #d03025)';
|
|
37
|
+
span.style.fontFamily = 'var(--font-code, ui-monospace, monospace)';
|
|
38
|
+
span.style.fontSize = '0.9em';
|
|
39
|
+
span.style.cursor = 'help';
|
|
33
40
|
return span;
|
|
34
41
|
}
|
|
@@ -63,15 +63,15 @@ export interface ListWindowing {
|
|
|
63
63
|
recordMeasuredChild(index: number, id: string, height: number): void;
|
|
64
64
|
/** A PROPAGATED child-container subtotal: oracle + model slot, addressed by index. No anchor correction. */
|
|
65
65
|
setChildSubtotal(index: number, total: number): void;
|
|
66
|
-
/** Enroll a child in this scope's batched measure pass
|
|
67
|
-
* the child's unmount. The scope reads ALL pending
|
|
68
|
-
* fling that mounts many costs one reflow. */
|
|
66
|
+
/** Enroll a child in this scope's batched measure pass, read after the flush that registered
|
|
67
|
+
* it; returns an unregister fn to call on the child's unmount. The scope reads ALL pending
|
|
68
|
+
* children before applying ANY write, so a fling that mounts many costs one reflow. */
|
|
69
69
|
registerChild(id: string, child: MeasureEntry): () => void;
|
|
70
70
|
/** Re-measure ONE registered child immediately (its content height changed on edit). */
|
|
71
71
|
measureChildNow(id: string): void;
|
|
72
|
-
/** ResizeObserver path: O(1)-gate `observedHeight` against the
|
|
73
|
-
* re-measure only on a genuine post-mount change, so the no-op mount resize
|
|
74
|
-
* costs no DOM read. */
|
|
72
|
+
/** ResizeObserver path: O(1)-gate `observedHeight` against the height this scope last
|
|
73
|
+
* applied and re-measure only on a genuine post-mount change, so the no-op mount resize
|
|
74
|
+
* on a fling costs no DOM read. */
|
|
75
75
|
measureChildOnResize(id: string, observedHeight: number): void;
|
|
76
76
|
/**
|
|
77
77
|
* True when the scroll position IS the reveal target's requested placement. Asked by
|
|
@@ -93,8 +93,8 @@ export interface ListWindowing {
|
|
|
93
93
|
* Resize-gate decision (pure, unit-tested). Keyed on the height DIFFERING from what is
|
|
94
94
|
* recorded, never on which callback delivered it: a cached remount can report the grown
|
|
95
95
|
* size in the very first callback, so a callback-order heuristic would drop it.
|
|
96
|
-
* `recorded === undefined` means the
|
|
97
|
-
* defer to
|
|
96
|
+
* `recorded === undefined` means the scope has applied no height for this block yet —
|
|
97
|
+
* defer to the batched pass rather than racing a lone read in on a fling-dirtied layout.
|
|
98
98
|
*/
|
|
99
99
|
export declare function shouldRemeasureOnResize(recorded: number | undefined, observed: number): boolean;
|
|
100
100
|
export declare function createListWindowing(deps: ListWindowingDeps): ListWindowing;
|
|
@@ -14,8 +14,8 @@ import { runMeasureBatch } from './measure-batch';
|
|
|
14
14
|
* Resize-gate decision (pure, unit-tested). Keyed on the height DIFFERING from what is
|
|
15
15
|
* recorded, never on which callback delivered it: a cached remount can report the grown
|
|
16
16
|
* size in the very first callback, so a callback-order heuristic would drop it.
|
|
17
|
-
* `recorded === undefined` means the
|
|
18
|
-
* defer to
|
|
17
|
+
* `recorded === undefined` means the scope has applied no height for this block yet —
|
|
18
|
+
* defer to the batched pass rather than racing a lone read in on a fling-dirtied layout.
|
|
19
19
|
*/
|
|
20
20
|
export function shouldRemeasureOnResize(recorded, observed) {
|
|
21
21
|
if (observed <= 0 || recorded === undefined)
|
|
@@ -59,7 +59,7 @@ export function createListWindowing(deps) {
|
|
|
59
59
|
let heightVersion = $state(0);
|
|
60
60
|
// One scope-owned batched pass rather than a per-child effect, which would interleave a
|
|
61
61
|
// layout read with the prior child's write → one forced reflow per mounted block on a
|
|
62
|
-
// fling. `pending` holds ids awaiting their first measure;
|
|
62
|
+
// fling. `pending` holds ids awaiting their first measure; registration drains it.
|
|
63
63
|
const registry = new Map();
|
|
64
64
|
const pending = new Set();
|
|
65
65
|
/**
|
|
@@ -123,6 +123,20 @@ export function createListWindowing(deps) {
|
|
|
123
123
|
placeRevealTarget();
|
|
124
124
|
return true;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Which block this correction holds still: the top-of-viewport one by default, the FOCUSED
|
|
128
|
+
* one when it sits at or below the fold. Clicking a construct that reveals its source (a
|
|
129
|
+
* math block, a paragraph holding inline math) resizes that block and folds whichever was
|
|
130
|
+
* revealed before it; anchored on the viewport top, both slide the clicked block out from
|
|
131
|
+
* under the pointer. Anchoring on the focused block holds its start offset instead, so it
|
|
132
|
+
* stays put and only the content after it reflows.
|
|
133
|
+
*/
|
|
134
|
+
function anchorIndexFor(topIndex) {
|
|
135
|
+
const pinned = pinnedIndex();
|
|
136
|
+
if (pinned === null || pinned < topIndex || pinned >= model.size)
|
|
137
|
+
return topIndex;
|
|
138
|
+
return pinned;
|
|
139
|
+
}
|
|
126
140
|
// Hold the anchor block's screen position across a height mutation that would otherwise
|
|
127
141
|
// slide the visible content (VR-2) — native `overflow-anchor` is off wherever this runs, so
|
|
128
142
|
// nothing else holds the line. The delta comes from the Fenwick model, not
|
|
@@ -134,7 +148,7 @@ export function createListWindowing(deps) {
|
|
|
134
148
|
if (reassertRevealAnchor(mutate))
|
|
135
149
|
return;
|
|
136
150
|
const port = deps.getPort();
|
|
137
|
-
const anchorIndex = model.indexAtOffset(localScrollTop());
|
|
151
|
+
const anchorIndex = anchorIndexFor(model.indexAtOffset(localScrollTop()));
|
|
138
152
|
const before = model.offsetOf(anchorIndex);
|
|
139
153
|
mutate();
|
|
140
154
|
const delta = model.offsetOf(anchorIndex) - before;
|
|
@@ -258,22 +272,23 @@ export function createListWindowing(deps) {
|
|
|
258
272
|
$effect(() => {
|
|
259
273
|
void heightVersion;
|
|
260
274
|
const el = deps.getOwnEl?.();
|
|
261
|
-
|
|
275
|
+
const report = deps.reportSelfHeight;
|
|
276
|
+
if (!el || !report)
|
|
262
277
|
return;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
278
|
+
// After the flush, like the batch: at this effect's first run the children are unrendered.
|
|
279
|
+
let live = true;
|
|
280
|
+
void tick().then(() => {
|
|
281
|
+
if (!live)
|
|
282
|
+
return;
|
|
283
|
+
const h = el.getBoundingClientRect().height;
|
|
284
|
+
if (h > 0 && Math.abs(h - reportedSelfHeight) >= 1) {
|
|
285
|
+
reportedSelfHeight = h;
|
|
286
|
+
report(h);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
return () => {
|
|
290
|
+
live = false;
|
|
291
|
+
};
|
|
277
292
|
});
|
|
278
293
|
// The UNCORRECTED batch: the caller owns the anchor correction, because nesting a second
|
|
279
294
|
// one inside an outer `mutate` double-counts the delta.
|
|
@@ -348,11 +363,21 @@ export function createListWindowing(deps) {
|
|
|
348
363
|
if (!skipWhileHostAnchors(write) && !reassertRevealAnchor(write))
|
|
349
364
|
write();
|
|
350
365
|
},
|
|
351
|
-
//
|
|
352
|
-
//
|
|
366
|
+
// Read after the flush that mounted the child, not inside it: content can land later in
|
|
367
|
+
// that flush (an inline widget's root flushes after this one), and an empty-host read is
|
|
368
|
+
// a scroll correction the observer undoes a frame later. Still ahead of paint.
|
|
353
369
|
registerChild(id, child) {
|
|
354
|
-
|
|
370
|
+
const entry = {
|
|
371
|
+
readHeight: child.readHeight,
|
|
372
|
+
applyHeight: (h) => {
|
|
373
|
+
entry.applied = h;
|
|
374
|
+
child.applyHeight(h);
|
|
375
|
+
},
|
|
376
|
+
applied: undefined
|
|
377
|
+
};
|
|
378
|
+
registry.set(id, entry);
|
|
355
379
|
pending.add(id);
|
|
380
|
+
void tick().then(flushMeasurements);
|
|
356
381
|
return () => {
|
|
357
382
|
registry.delete(id);
|
|
358
383
|
pending.delete(id);
|
|
@@ -362,11 +387,11 @@ export function createListWindowing(deps) {
|
|
|
362
387
|
measureChildNow(id) {
|
|
363
388
|
measureOne(id);
|
|
364
389
|
},
|
|
365
|
-
// ResizeObserver path for async growth. The gate reads
|
|
390
|
+
// ResizeObserver path for async growth. The gate reads what this scope last applied
|
|
366
391
|
// (O(1), no DOM), so the no-op mount resize a fling fires for every newly-mounted
|
|
367
392
|
// block returns without a rect read on the spacer-dirtied layout (VR-4).
|
|
368
393
|
measureChildOnResize(id, observedHeight) {
|
|
369
|
-
if (shouldRemeasureOnResize(
|
|
394
|
+
if (shouldRemeasureOnResize(registry.get(id)?.applied, observedHeight))
|
|
370
395
|
measureOne(id);
|
|
371
396
|
},
|
|
372
397
|
revealHoldsScroll() {
|
|
@@ -22,10 +22,18 @@ export interface CompletionResult {
|
|
|
22
22
|
export interface BlockCompleter {
|
|
23
23
|
/** Attempt to complete `line`; null declines, as does a claim whose lines would paint nothing. */
|
|
24
24
|
tryComplete(line: string): CompletionResult | null;
|
|
25
|
+
/**
|
|
26
|
+
* Also consulted as the line is TYPED, not only at Enter — for a claim that can only ever mean
|
|
27
|
+
* one thing the moment it is complete (a lone `$$`), so the block forms as a ` ``` ` fence does.
|
|
28
|
+
* Off by default: a table's header row is a prefix of a longer row the user may still be typing.
|
|
29
|
+
*/
|
|
30
|
+
onType?: boolean;
|
|
25
31
|
}
|
|
26
32
|
export declare function registerBlockCompleter(kind: AnyBlockKind, completer: BlockCompleter): void;
|
|
27
33
|
/** The first claim on `line`, or null when no registered completer takes it. */
|
|
28
34
|
export declare function completeTypedLine(line: string): CompletionResult | null;
|
|
35
|
+
/** The first claim on `line` among the completers that answer as the line is typed. */
|
|
36
|
+
export declare function completeLineOnType(line: string): CompletionResult | null;
|
|
29
37
|
/** Whether `kind` already owns a completer — the probe a registrar re-run reads before it
|
|
30
38
|
* registers, so a re-installed plugin never trips the register-once throw. */
|
|
31
39
|
export declare function isBlockCompleterRegistered(kind: string): boolean;
|
|
@@ -33,6 +33,17 @@ export function completeTypedLine(line) {
|
|
|
33
33
|
}
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
+
/** The first claim on `line` among the completers that answer as the line is typed. */
|
|
37
|
+
export function completeLineOnType(line) {
|
|
38
|
+
for (const completer of ordered()) {
|
|
39
|
+
if (!completer.onType)
|
|
40
|
+
continue;
|
|
41
|
+
const claim = completer.tryComplete(line);
|
|
42
|
+
if (claim)
|
|
43
|
+
return claim;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
36
47
|
/** Whether `kind` already owns a completer — the probe a registrar re-run reads before it
|
|
37
48
|
* registers, so a re-installed plugin never trips the register-once throw. */
|
|
38
49
|
export function isBlockCompleterRegistered(kind) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The block context menu's vocabulary: what a right-click on a block of a given kind offers.
|
|
3
|
+
* A kind's own providers come first, then the ones registered for every kind (`'*'`: the
|
|
4
|
+
* editor's copy, replace and remove rows). Providers are consulted on every open, so they read
|
|
5
|
+
* the block as it is. The defaults register at bootstrap; a plugin registers through the barrel.
|
|
6
|
+
*/
|
|
7
|
+
export declare const EVERY_KIND = "*";
|
|
8
|
+
import type { NodeView } from '../core/node-views';
|
|
9
|
+
export interface BlockActionContext {
|
|
10
|
+
node: NodeView;
|
|
11
|
+
/** The block's path from the document root. Top-level blocks only, for now. */
|
|
12
|
+
path: number[];
|
|
13
|
+
/** Remove the block. */
|
|
14
|
+
deleteBlock(): Promise<void>;
|
|
15
|
+
/** Replace the block's bytes wholesale; the result reparses to whatever those bytes are. */
|
|
16
|
+
replaceRaw(raw: string): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export interface BlockContextAction {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
/** A glyph name the menu knows (`components/menu/MenuIcon.svelte`); absent draws none. */
|
|
22
|
+
icon?: string;
|
|
23
|
+
danger?: boolean;
|
|
24
|
+
run(ctx: BlockActionContext): void | Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export type BlockContextActionProvider = (node: NodeView, path: number[]) => BlockContextAction[];
|
|
27
|
+
/** Add a provider for `kind`, or for every kind with `EVERY_KIND`. Several may stack. */
|
|
28
|
+
export declare function registerBlockContextActions(kind: string, provider: BlockContextActionProvider): void;
|
|
29
|
+
export declare function blockContextActionsFor(node: NodeView, path: number[]): BlockContextAction[];
|
|
30
|
+
/** Test-only. */
|
|
31
|
+
export declare function __resetBlockContextActionsForTests(): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The block context menu's vocabulary: what a right-click on a block of a given kind offers.
|
|
3
|
+
* A kind's own providers come first, then the ones registered for every kind (`'*'`: the
|
|
4
|
+
* editor's copy, replace and remove rows). Providers are consulted on every open, so they read
|
|
5
|
+
* the block as it is. The defaults register at bootstrap; a plugin registers through the barrel.
|
|
6
|
+
*/
|
|
7
|
+
export const EVERY_KIND = '*';
|
|
8
|
+
const providers = new Map();
|
|
9
|
+
/** Add a provider for `kind`, or for every kind with `EVERY_KIND`. Several may stack. */
|
|
10
|
+
export function registerBlockContextActions(kind, provider) {
|
|
11
|
+
const list = providers.get(kind) ?? [];
|
|
12
|
+
list.push(provider);
|
|
13
|
+
providers.set(kind, list);
|
|
14
|
+
}
|
|
15
|
+
export function blockContextActionsFor(node, path) {
|
|
16
|
+
const own = providers.get(node.kind) ?? [];
|
|
17
|
+
const every = providers.get(EVERY_KIND) ?? [];
|
|
18
|
+
return [...own, ...every].flatMap((provider) => provider(node, path));
|
|
19
|
+
}
|
|
20
|
+
/** Test-only. */
|
|
21
|
+
export function __resetBlockContextActionsForTests() {
|
|
22
|
+
providers.clear();
|
|
23
|
+
}
|
|
@@ -17,7 +17,8 @@ export function reconcileFenceWrite(input) {
|
|
|
17
17
|
const { fence, mode } = input;
|
|
18
18
|
if (!fence.closed && mode === 'authored')
|
|
19
19
|
return { display: input.display, caret: input.caret };
|
|
20
|
-
|
|
20
|
+
const unglued = separateGluedCloser(input);
|
|
21
|
+
return escalateFenceRuns({ ...input, ...sanitizeInfoString({ ...input, ...unglued }) });
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* A whole fencedCode `raw` made legal — the kind's `normalizeRawWrite`. Literal by
|
|
@@ -166,6 +167,35 @@ function countDroppedBefore(info, infoStart, caret) {
|
|
|
166
167
|
}
|
|
167
168
|
return dropped;
|
|
168
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Un-glue a closer the write landed on top of. A fence with NO body line — ```` ```\n``` ```` —
|
|
172
|
+
* has nothing between its two runs, so the body's only caret seat IS the closer's line start,
|
|
173
|
+
* and the first character typed there arrives ahead of the closer run: ```` ```\nAB``` ````,
|
|
174
|
+
* which no longer reads as closed and shows the run as body text. The fence says it is closed
|
|
175
|
+
* and the display no longer carries a closer line, so the write broke it; the missing line
|
|
176
|
+
* ending is what puts it back.
|
|
177
|
+
*/
|
|
178
|
+
function separateGluedCloser(input) {
|
|
179
|
+
const { display, caret, fence } = input;
|
|
180
|
+
if (!fence.closed)
|
|
181
|
+
return { display, caret };
|
|
182
|
+
const lines = display.split('\n');
|
|
183
|
+
// A closer still on its own line needs nothing; so does a display too short to hold one.
|
|
184
|
+
if (lines.length < 2 || lastCloserIndex(lines, fence) >= 1)
|
|
185
|
+
return { display, caret };
|
|
186
|
+
const run = new RegExp(`[${fence.marker}]{${fence.length},}[ \t]*$`).exec(lines[lines.length - 1]);
|
|
187
|
+
// `index === 0` is a bare run that some other rule owns, not a glued one.
|
|
188
|
+
if (!run || run.index === 0)
|
|
189
|
+
return { display, caret };
|
|
190
|
+
const at = display.length - run[0].length;
|
|
191
|
+
const ending = display.includes('\r\n') ? '\r\n' : '\n';
|
|
192
|
+
return {
|
|
193
|
+
display: display.slice(0, at) + ending + display.slice(at),
|
|
194
|
+
// The caret sits at the typed text's end, which is where the ending is inserted: it
|
|
195
|
+
// stays put, and only a caret already inside the closer run rides the shift.
|
|
196
|
+
caret: caret > at ? caret + ending.length : caret
|
|
197
|
+
};
|
|
198
|
+
}
|
|
169
199
|
function escalateFenceRuns(input) {
|
|
170
200
|
const { display, caret, fence } = input;
|
|
171
201
|
const lines = display.split('\n');
|
|
@@ -39,7 +39,7 @@ export interface OperationDetailMap {
|
|
|
39
39
|
parentItemIdx?: number;
|
|
40
40
|
nestedItemIdx?: number;
|
|
41
41
|
} | {
|
|
42
|
-
source: 'paste-dispatch' | 'paste-dispatch-table-cell' | 'cross-block-
|
|
42
|
+
source: 'paste-dispatch' | 'paste-dispatch-table-cell' | 'cross-block-covered-block';
|
|
43
43
|
};
|
|
44
44
|
paste: {
|
|
45
45
|
count: number;
|
|
@@ -70,6 +70,13 @@ export interface OperationDetailMap {
|
|
|
70
70
|
colIdx: number;
|
|
71
71
|
side: 'left' | 'right';
|
|
72
72
|
};
|
|
73
|
+
/** A grid pasted from `rowIdx`/`colIdx`, the table grown to hold it. */
|
|
74
|
+
tablePasteGrid: {
|
|
75
|
+
rowIdx: number;
|
|
76
|
+
colIdx: number;
|
|
77
|
+
rows: number;
|
|
78
|
+
cols: number;
|
|
79
|
+
};
|
|
73
80
|
tableDeleteColumn: {
|
|
74
81
|
colIdx: number;
|
|
75
82
|
crossBlock?: true;
|
|
@@ -15,6 +15,12 @@ export const HARDCODED_CHORD_SITES = [
|
|
|
15
15
|
keys: [],
|
|
16
16
|
note: 'Mod-click link activation — no keystroke is consumed.'
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
file: 'components/editor-root-keydown.ts',
|
|
20
|
+
chords: [],
|
|
21
|
+
keys: ['Escape'],
|
|
22
|
+
note: 'Escape closes the find bar (a reserved UI chord, enumerated). The modifier read is a REFUSAL: an unchorded character over a live range goes to the range’s text door, a chorded one to the arms below it.'
|
|
23
|
+
},
|
|
18
24
|
{
|
|
19
25
|
file: 'components/editor-root-listeners.ts',
|
|
20
26
|
chords: [],
|
|
@@ -45,8 +51,8 @@ export const HARDCODED_CHORD_SITES = [
|
|
|
45
51
|
{
|
|
46
52
|
file: 'components/blocks/editable-leaf.ts',
|
|
47
53
|
chords: [],
|
|
48
|
-
keys: ['Enter'],
|
|
49
|
-
note:
|
|
54
|
+
keys: ['Backspace', 'Enter'],
|
|
55
|
+
note: "Shift-click gate on the rendered surface — a pointer read; plain Backspace (no modifier) at the top of an empty painted source deletes the block, as the code block does. The reveal's own undo resolves its chord through the keymap, so it claims none here."
|
|
50
56
|
},
|
|
51
57
|
{
|
|
52
58
|
file: 'components/link-card/LinkCard.svelte',
|
|
@@ -54,6 +60,12 @@ export const HARDCODED_CHORD_SITES = [
|
|
|
54
60
|
keys: ['Enter', 'K', 'Tab', 'k'],
|
|
55
61
|
note: "Backwards step of the open card's focus trap, plus the entry chord swallowed as a no-op where the focus already is — the kind keymaps claim it everywhere else. Escape lives on the host, which must also close a card the document still holds the caret for."
|
|
56
62
|
},
|
|
63
|
+
{
|
|
64
|
+
file: 'components/menu/BlockMenu.svelte',
|
|
65
|
+
chords: [],
|
|
66
|
+
keys: ['ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'Enter', 'Escape'],
|
|
67
|
+
note: 'The open block menu steps and picks; the modifier read is the guard that leaves every chord to the editor.'
|
|
68
|
+
},
|
|
57
69
|
{
|
|
58
70
|
file: 'components/blocks/table/TableActionMenu.svelte',
|
|
59
71
|
chords: ['Shift+Tab'],
|
|
@@ -115,8 +127,17 @@ export const HARDCODED_CHORD_SITES = [
|
|
|
115
127
|
{
|
|
116
128
|
file: 'components/blocks/text/widget-interaction.ts',
|
|
117
129
|
chords: ['Shift+ArrowLeft', 'Shift+ArrowRight'],
|
|
118
|
-
keys: [
|
|
119
|
-
|
|
130
|
+
keys: [
|
|
131
|
+
'Arrow*',
|
|
132
|
+
'ArrowDown',
|
|
133
|
+
'ArrowLeft',
|
|
134
|
+
'ArrowRight',
|
|
135
|
+
'ArrowUp',
|
|
136
|
+
'Backspace',
|
|
137
|
+
'Delete',
|
|
138
|
+
'Escape'
|
|
139
|
+
],
|
|
140
|
+
note: 'Extends a selection into a widget. A modified arrow is also swallowed while a widget is selected, but that is a caret-coherence guard in a transient state, not a binding; a plain vertical arrow seats a caret and declines.'
|
|
120
141
|
},
|
|
121
142
|
{
|
|
122
143
|
file: 'components/image/ImageResizeHandles.svelte',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The tableCell raw-write rule, declared on the kind as `normalizeRawWrite` and applied at the
|
|
3
|
-
* write sink (`tree-operations/
|
|
3
|
+
* write sink (`tree-operations/content-write.updateNodeContent`). A cell's bytes are joined verbatim
|
|
4
4
|
* into its row, and the parser truncates a row reparsing wider than the delimiter's column
|
|
5
5
|
* count, so a bare `|` or line break reaching a cell raw deletes the last column's content.
|
|
6
6
|
* Both passes are prefix-composable, which is what makes `escapedCellOffset` an exact caret image.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The tableCell raw-write rule, declared on the kind as `normalizeRawWrite` and applied at the
|
|
3
|
-
* write sink (`tree-operations/
|
|
3
|
+
* write sink (`tree-operations/content-write.updateNodeContent`). A cell's bytes are joined verbatim
|
|
4
4
|
* into its row, and the parser truncates a row reparsing wider than the delimiter's column
|
|
5
5
|
* count, so a bare `|` or line break reaching a cell raw deletes the last column's content.
|
|
6
6
|
* Both passes are prefix-composable, which is what makes `escapedCellOffset` an exact caret image.
|
|
@@ -26,10 +26,11 @@ export function blockAtPoint(editorRoot, clientX, clientY) {
|
|
|
26
26
|
path,
|
|
27
27
|
// A cell-addressed kind's first contenteditable is one of its CELLS, so declaring
|
|
28
28
|
// the drag hook withdraws the block-level surface rather than offering that. The
|
|
29
|
-
// whole-block editing host is chrome, not characters, so it is excluded too
|
|
29
|
+
// whole-block editing host is chrome, not characters, so it is excluded too, as is
|
|
30
|
+
// the selection overlay (`contenteditable="false"`, mounted while a range is live).
|
|
30
31
|
charSurface: dragHitTest
|
|
31
32
|
? null
|
|
32
|
-
: wrapper.querySelector(`[contenteditable]:not([${WHOLE_BLOCK_INPUT_ATTR}])`),
|
|
33
|
+
: wrapper.querySelector(`[contenteditable]:not([${WHOLE_BLOCK_INPUT_ATTR}]):not(.selection-overlay)`),
|
|
33
34
|
foreignDragHitTest: dragHitTest && ((cx, cy) => dragHitTest(wrapper, cx, cy)),
|
|
34
35
|
caretTargetAtPoint: caretTarget && ((cx, cy) => caretTarget(wrapper, cx, cy))
|
|
35
36
|
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* order against the other endpoint, never the anchor/focus role, which `normalize` reorders later.
|
|
7
7
|
*/
|
|
8
8
|
import { displayLength, snapToScalarBoundary } from '../core/lines';
|
|
9
|
-
import { isBlockNode, nodeAt } from '../tree-operations/node-
|
|
9
|
+
import { isBlockNode, nodeAt } from '../tree-operations/node-primitives';
|
|
10
10
|
import { isWholeBlockUnit } from '../schema/whole-block-unit';
|
|
11
11
|
import { comparePaths } from './path-math';
|
|
12
12
|
import { isWholeBlockEndpoint } from './primitives';
|