@voithos-labs/aragonite 0.10.2 → 0.10.4
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 +19 -0
- package/dist/a11y-strings.js +19 -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 +37 -24
- package/dist/components/BlockHost.svelte +18 -11
- package/dist/components/Editor.svelte +434 -198
- package/dist/components/Editor.svelte.d.ts +1 -1
- package/dist/components/SelectionOverlay.svelte +25 -17
- package/dist/components/SelectionOverlay.svelte.d.ts +1 -1
- 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 +704 -0
- package/dist/components/blocks/code/CodeBlockRail.svelte.d.ts +26 -0
- package/dist/components/blocks/code/code-bootstrap.js +16 -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 +22 -3
- 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 +248 -30
- package/dist/components/blocks/editable-surface.d.ts +9 -0
- package/dist/components/blocks/editable-surface.js +22 -3
- package/dist/components/blocks/list/ListBlock.svelte +1 -0
- package/dist/components/blocks/list/ListItemBlock.svelte +14 -5
- package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +2 -0
- 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/click-snap-guard.d.ts +3 -0
- package/dist/components/blocks/text/click-snap-guard.js +8 -0
- 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 +89 -20
- package/dist/components/blocks/text/live-selection-edit.d.ts +5 -5
- package/dist/components/blocks/text/live-selection-edit.js +37 -7
- package/dist/components/blocks/text/text-clipboard.js +2 -2
- 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 +181 -37
- package/dist/components/drag-handle.d.ts +41 -0
- package/dist/components/drag-handle.js +134 -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 +3 -6
- package/dist/components/editor-root-listeners.js +3 -24
- 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 +67 -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/SelectionToolbar.svelte +385 -0
- package/dist/components/menu/SelectionToolbar.svelte.d.ts +16 -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 +19 -0
- package/dist/core/inline/inline-widgets.js +5 -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/transparency.js +3 -3
- 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/height-oracle.d.ts +2 -3
- package/dist/cursor/height-oracle.js +0 -1
- package/dist/cursor/overlay-remeasure.js +8 -0
- package/dist/cursor/reveal-source.js +7 -2
- package/dist/cursor/scroll-hold.d.ts +10 -0
- package/dist/cursor/scroll-hold.js +22 -0
- package/dist/cursor/scrollport.d.ts +9 -0
- package/dist/cursor/scrollport.js +30 -1
- package/dist/cursor/visual-lines.d.ts +5 -4
- package/dist/cursor/visual-lines.js +56 -11
- package/dist/cursor/widget-edge-snap.d.ts +28 -0
- package/dist/cursor/widget-edge-snap.js +38 -0
- package/dist/cursor/widget-offset.d.ts +9 -0
- package/dist/cursor/widget-offset.js +71 -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/decorations/reserved-attrs.js +1 -0
- 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/focus/focus-dispatch.js +7 -4
- package/dist/editor-actions/focus/focus-landing.d.ts +10 -1
- package/dist/editor-actions/focus/focus-landing.js +20 -6
- package/dist/editor-actions/inline-range-commit.js +1 -1
- package/dist/editor-actions/plugin/container.d.ts +7 -0
- package/dist/editor-actions/plugin/container.js +3 -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 +24 -12
- package/dist/index.d.ts +1 -1
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +16 -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 +60 -10
- 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 +103 -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/plugins/mermaid/MermaidBlock.svelte +19 -9
- package/dist/plugins/parrot/ParrotBlock.svelte +3 -1
- package/dist/reactivity/list-windowing.svelte.d.ts +8 -8
- package/dist/reactivity/list-windowing.svelte.js +75 -30
- package/dist/schema/block-completions.d.ts +8 -0
- package/dist/schema/block-completions.js +11 -0
- package/dist/schema/commands.d.ts +9 -5
- package/dist/schema/commands.js +12 -7
- 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 +37 -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 +3 -29
- package/dist/selection/cross-block/ops.js +1 -1
- package/dist/selection/cross-block/paste.js +31 -35
- package/dist/selection/cross-block/type-replace.d.ts +3 -2
- package/dist/selection/cross-block/type-replace.js +52 -13
- package/dist/selection/dead-space-caret.d.ts +13 -0
- package/dist/selection/dead-space-caret.js +57 -1
- package/dist/selection/drag-pointer.d.ts +26 -2
- package/dist/selection/drag-pointer.js +103 -2
- package/dist/selection/gap-caret.js +1 -1
- package/dist/selection/keyboard-extend.d.ts +3 -2
- package/dist/selection/keyboard-extend.js +4 -3
- package/dist/selection/multi-click.d.ts +42 -0
- package/dist/selection/multi-click.js +203 -0
- package/dist/selection/native-bridge.d.ts +3 -0
- package/dist/selection/native-bridge.js +31 -2
- package/dist/selection/path-lookup.d.ts +10 -2
- package/dist/selection/path-lookup.js +11 -4
- package/dist/selection/pointer-gesture.d.ts +9 -0
- package/dist/selection/pointer-gesture.js +11 -0
- package/dist/selection/primitives.d.ts +7 -0
- package/dist/selection/primitives.js +19 -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.d.ts +4 -0
- package/dist/selection/range-delete.js +31 -3
- package/dist/selection/selection-drop.d.ts +34 -0
- package/dist/selection/selection-drop.js +253 -0
- 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 +75 -31
- package/dist/styles/editor.css +268 -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.d.ts +3 -1
- package/dist/tree-operations/paste/replace-block-at-parent.js +5 -2
- package/dist/tree-operations/paste/replacement-parse.d.ts +17 -0
- package/dist/tree-operations/paste/replacement-parse.js +22 -0
- 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 +136 -104
- package/docs/guide/plugin-api.md +73 -29
- package/docs/guide/plugin-guide.md +55 -23
- package/package.json +9 -7
- 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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Toggle an inline format inside a prose block. Over a SELECTION the direction is parse coverage,
|
|
3
3
|
* not edge adjacency: a covered range unapplies (aligned strip, else split, over every covering run
|
|
4
|
-
* in turn), overlapping or abutting runs apply over their union, a bare range wraps
|
|
5
|
-
* PAINTS delimiters the strip and the wrap write
|
|
6
|
-
* verifies. At a COLLAPSED CARET: unwrap the
|
|
7
|
-
* (live-mode.md § 4.3). Every write clamps to the
|
|
4
|
+
* in turn), overlapping or abutting runs apply over their union, a bare range wraps the core its
|
|
5
|
+
* boundary whitespace leaves. Where the mode PAINTS delimiters the strip and the wrap write
|
|
6
|
+
* unverified; every other candidate declines unless it verifies. At a COLLAPSED CARET: unwrap the
|
|
7
|
+
* span, else drop the empty pair, else insert one (live-mode.md § 4.3). Every write clamps to the
|
|
8
|
+
* CONTENT range: a marker in `# ` changes the kind.
|
|
8
9
|
*/
|
|
9
10
|
import { recordFormatCoverageRead } from '../../perf/instruments';
|
|
10
11
|
import { paintsFocusedMarkers } from '../../presentation-mode';
|
|
@@ -29,29 +30,31 @@ export function toggleInlineFormat(edit, format, mode) {
|
|
|
29
30
|
// Painted delimiters are the mode's own answer, so those modes write their candidate unverified.
|
|
30
31
|
// The preview rungs paint: this seam only writes into the block those rungs reveal.
|
|
31
32
|
const paints = paintsFocusedMarkers(mode ?? 'source');
|
|
32
|
-
const covering =
|
|
33
|
+
const { from, to, covering } = coveredReading(display, inlines, start, end, format);
|
|
33
34
|
// A strip sheds ONE run, so where a second of the same kind covers the selection too, the answer
|
|
34
35
|
// is the covering arm's split: stripping there leaves the press's own read still active.
|
|
35
|
-
const sole = covering.length > 1 ? null : soleStripCandidate(display, inlines,
|
|
36
|
+
const sole = covering.length > 1 ? null : soleStripCandidate(display, inlines, from, to, format);
|
|
36
37
|
if (sole)
|
|
37
38
|
return paints || preservesScreen(sole, edit, screenOf(display, content)) ? sole : null;
|
|
38
39
|
// The flank strip rewrites bytes OUTSIDE the selection, and byte equality can mistake a nested
|
|
39
40
|
// run's delimiters for the enclosing one's, so its candidate verifies like the split's.
|
|
40
|
-
const enclosing = enclosingSpanOf(inlines,
|
|
41
|
-
if (enclosing && flanksAreItsMarkers(display,
|
|
42
|
-
const flank = firstFlipVerified([flankStrip(display,
|
|
41
|
+
const enclosing = enclosingSpanOf(inlines, from, to, format);
|
|
42
|
+
if (enclosing && flanksAreItsMarkers(display, from, to, enclosing)) {
|
|
43
|
+
const flank = firstFlipVerified([flankStrip(display, from, to, enclosing)], edit, format, 'unapply');
|
|
43
44
|
if (flank)
|
|
44
45
|
return flank;
|
|
45
46
|
}
|
|
46
47
|
if (covering.length > 0)
|
|
47
|
-
return firstFlipVerified(covering.flatMap((span) => splitCandidates(display, inlines, span,
|
|
48
|
+
return firstFlipVerified(covering.flatMap((span) => splitCandidates(display, inlines, span, from, to, format)), edit, format, 'unapply');
|
|
48
49
|
const union = formatUnionOf(inlines, start, end, format);
|
|
49
50
|
if (union)
|
|
50
|
-
return firstFlipVerified(absorbCandidates(display, inlines, union, format, mark), edit, format, 'apply');
|
|
51
|
+
return firstFlipVerified(absorbCandidates(display, inlines, union, { start, end }, format, mark), edit, format, 'apply');
|
|
51
52
|
// A wrap whose markers do not paint owes split and absorb's coverage check: bytes that re-pair
|
|
52
53
|
// against a neighbouring run leave the range unformatted with nothing on screen to say so.
|
|
53
|
-
const
|
|
54
|
-
|
|
54
|
+
const wrap = wrapCandidate(display, inlines, start, end, mark);
|
|
55
|
+
if (!wrap)
|
|
56
|
+
return null;
|
|
57
|
+
return paints ? wrap : firstFlipVerified([wrap], edit, format, 'apply');
|
|
55
58
|
}
|
|
56
59
|
/** Whether a toggle right now would UNAPPLY — the pressed-state a toolbar paints. The same arms
|
|
57
60
|
* the toggle routes by, asked without emitting: a caret inside the construct, a selection
|
|
@@ -137,7 +140,24 @@ function coverageCarries({ display, start, end, inlines, sliceNodes }, format) {
|
|
|
137
140
|
const enclosing = enclosingSpanOf(inlines, start, end, format);
|
|
138
141
|
if (enclosing && flanksAreItsMarkers(display, start, end, enclosing))
|
|
139
142
|
return true;
|
|
140
|
-
return
|
|
143
|
+
return coveredReading(display, inlines, start, end, format).covering.length > 0;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The range an unapply reads, with the runs covering it. A run closes against a word and never
|
|
147
|
+
* whitespace, so the wrap leaves a boundary space OUTSIDE the delimiters: a selection reaching
|
|
148
|
+
* past a run by whitespace alone is still that run's own, or it could not take its own mark off.
|
|
149
|
+
*/
|
|
150
|
+
function coveredReading(display, inlines, start, end, format) {
|
|
151
|
+
const covering = coveringSpansOf(inlines, start, end, format);
|
|
152
|
+
if (covering.length > 0)
|
|
153
|
+
return { from: start, to: end, covering };
|
|
154
|
+
const trimmed = withoutBoundaryWhitespace(display, start, end);
|
|
155
|
+
if (!trimmed || (trimmed.start === start && trimmed.end === end))
|
|
156
|
+
return { from: start, to: end, covering };
|
|
157
|
+
const inner = coveringSpansOf(inlines, trimmed.start, trimmed.end, format);
|
|
158
|
+
return inner.length > 0
|
|
159
|
+
? { from: trimmed.start, to: trimmed.end, covering: inner }
|
|
160
|
+
: { from: start, to: end, covering };
|
|
141
161
|
}
|
|
142
162
|
// ── Aligned unapply ──────────────────────────────────────────────────────────
|
|
143
163
|
/** The selection carries its own flanking markers (the user selected `**word**`): exactly one
|
|
@@ -247,18 +267,37 @@ function formatUnionOf(inlines, start, end, format) {
|
|
|
247
267
|
}
|
|
248
268
|
return touched ? { start: from, end: to } : null;
|
|
249
269
|
}
|
|
250
|
-
|
|
270
|
+
/** The run grows to the union, the SELECTION does not: each endpoint is carried through the
|
|
271
|
+
* strip and re-wrap, so a press over half a run leaves that half selected. An endpoint on the
|
|
272
|
+
* union's edge keeps the new marker inside the range, as a bare wrap's selection does. */
|
|
273
|
+
function absorbCandidates(display, inlines, union, selected, format, mark) {
|
|
251
274
|
if (!cutsLandCleanly(inlines, union, union))
|
|
252
275
|
return [];
|
|
253
|
-
const
|
|
276
|
+
const cuts = kindMarkerCuts(inlines, format, union.start, union.end);
|
|
277
|
+
const stripped = spliceOutCuts(display, cuts, union.start, union.end);
|
|
254
278
|
const prefix = display.slice(0, union.start);
|
|
255
279
|
const suffix = display.slice(union.end);
|
|
256
280
|
const wrapAt = (lead, core, trail) => {
|
|
257
281
|
const wrapped = wrapSlice(core, mark);
|
|
282
|
+
// The kind may pad its fence, so the marker widths are read off the wrap, not the row.
|
|
283
|
+
const found = core ? wrapped.indexOf(core) : -1;
|
|
284
|
+
const open = found >= 0 ? found : (wrapped.length - core.length) / 2;
|
|
285
|
+
const close = wrapped.length - core.length - open;
|
|
286
|
+
const coreStart = lead.length;
|
|
287
|
+
const coreEnd = lead.length + core.length;
|
|
288
|
+
const startAt = strippedOffset(cuts, union.start, selected.start);
|
|
289
|
+
const endAt = strippedOffset(cuts, union.start, selected.end);
|
|
290
|
+
const shift = (at, edgeInclusive) => {
|
|
291
|
+
if (edgeInclusive === 'start' ? at <= coreStart : at < coreStart)
|
|
292
|
+
return at;
|
|
293
|
+
if (edgeInclusive === 'start' ? at <= coreEnd : at < coreEnd)
|
|
294
|
+
return at + open;
|
|
295
|
+
return at + open + close;
|
|
296
|
+
};
|
|
258
297
|
return {
|
|
259
298
|
newDisplay: prefix + lead + wrapped + trail + suffix,
|
|
260
|
-
newSelStart: prefix.length +
|
|
261
|
-
newSelEnd: prefix.length +
|
|
299
|
+
newSelStart: prefix.length + shift(startAt, 'start'),
|
|
300
|
+
newSelEnd: prefix.length + shift(endAt, 'end')
|
|
262
301
|
};
|
|
263
302
|
};
|
|
264
303
|
const out = [wrapAt('', stripped, '')];
|
|
@@ -288,6 +327,10 @@ function cutsLandCleanly(inlines, cuts, within) {
|
|
|
288
327
|
/** The bytes of `[from, to)` with the delimiter runs of every same-format construct lying wholly
|
|
289
328
|
* inside removed; one straddling the range keeps its markers, for the verifier to judge. */
|
|
290
329
|
function stripKindMarkers(display, inlines, format, from, to) {
|
|
330
|
+
return spliceOutCuts(display, kindMarkerCuts(inlines, format, from, to), from, to);
|
|
331
|
+
}
|
|
332
|
+
/** The marker byte ranges of every `format` run lying wholly inside [from, to), in order. */
|
|
333
|
+
function kindMarkerCuts(inlines, format, from, to) {
|
|
291
334
|
const cuts = [];
|
|
292
335
|
for (const node of inlineDescendants(inlines)) {
|
|
293
336
|
if (node.kind !== format)
|
|
@@ -297,7 +340,9 @@ function stripKindMarkers(display, inlines, format, from, to) {
|
|
|
297
340
|
continue;
|
|
298
341
|
cuts.push([span.start, span.contentStart], [span.contentEnd, span.end]);
|
|
299
342
|
}
|
|
300
|
-
cuts.sort((a, b) => a[0] - b[0]);
|
|
343
|
+
return cuts.sort((a, b) => a[0] - b[0]);
|
|
344
|
+
}
|
|
345
|
+
function spliceOutCuts(display, cuts, from, to) {
|
|
301
346
|
let out = '';
|
|
302
347
|
let at = from;
|
|
303
348
|
for (const [cutFrom, cutTo] of cuts) {
|
|
@@ -306,30 +351,27 @@ function stripKindMarkers(display, inlines, format, from, to) {
|
|
|
306
351
|
}
|
|
307
352
|
return out + display.slice(at, to);
|
|
308
353
|
}
|
|
354
|
+
/** Where a display offset lands in the stripped slice: the cut bytes before it fall away, and an
|
|
355
|
+
* offset inside a marker rides to that marker's start. */
|
|
356
|
+
function strippedOffset(cuts, from, offset) {
|
|
357
|
+
let removed = 0;
|
|
358
|
+
for (const [cutFrom, cutTo] of cuts)
|
|
359
|
+
if (cutFrom < offset)
|
|
360
|
+
removed += Math.min(cutTo, offset) - cutFrom;
|
|
361
|
+
return Math.max(0, offset - from - removed);
|
|
362
|
+
}
|
|
309
363
|
// ── Wrap ─────────────────────────────────────────────────────────────────────
|
|
310
364
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
365
|
+
* The bare wrap, over the selection's trimmed core: markdown opens and closes a run against a word,
|
|
366
|
+
* never whitespace, so a boundary space goes to the plain text beside the run, as
|
|
367
|
+
* `live-split-rebalance` reads the same problem. Null where nothing survives the trim, or where an
|
|
368
|
+
* endpoint is no legal cut: markers spliced into a construct's bytes re-pair.
|
|
315
369
|
*/
|
|
316
|
-
function
|
|
317
|
-
const core =
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
.map((range) => wrapRange(display, range.start, range.end, mark));
|
|
322
|
-
}
|
|
323
|
-
/** The selection minus its boundary whitespace, or null when there is none to trim or nothing
|
|
324
|
-
* left once it goes. */
|
|
325
|
-
function trimmedRange(display, start, end) {
|
|
326
|
-
let from = start;
|
|
327
|
-
let to = end;
|
|
328
|
-
while (from < to && /\s/.test(display[from]))
|
|
329
|
-
from++;
|
|
330
|
-
while (to > from && /\s/.test(display[to - 1]))
|
|
331
|
-
to--;
|
|
332
|
-
return (from === start && to === end) || from === to ? null : { start: from, end: to };
|
|
370
|
+
function wrapCandidate(display, inlines, start, end, mark) {
|
|
371
|
+
const core = withoutBoundaryWhitespace(display, start, end);
|
|
372
|
+
if (!core || !cutsLandCleanly(inlines, core, core))
|
|
373
|
+
return null;
|
|
374
|
+
return wrapRange(display, core.start, core.end, mark);
|
|
333
375
|
}
|
|
334
376
|
function wrapRange(display, start, end, mark) {
|
|
335
377
|
const wrapped = wrapSlice(display.slice(start, end), mark);
|
|
@@ -527,6 +569,18 @@ function leadingWs(text) {
|
|
|
527
569
|
function trailingWs(text) {
|
|
528
570
|
return /\s*$/.exec(text)[0];
|
|
529
571
|
}
|
|
572
|
+
/** A range minus its boundary whitespace, null once nothing is left. One home for the trim, so the
|
|
573
|
+
* wrap here and the cross-block decomposition that trims before it ever asks
|
|
574
|
+
* (`selection/cross-block/format-range.ts`) cannot disagree about where a run may close. */
|
|
575
|
+
export function withoutBoundaryWhitespace(display, from, to) {
|
|
576
|
+
let start = from;
|
|
577
|
+
let end = to;
|
|
578
|
+
while (start < end && /\s/.test(display[start]))
|
|
579
|
+
start++;
|
|
580
|
+
while (end > start && /\s/.test(display[end - 1]))
|
|
581
|
+
end--;
|
|
582
|
+
return start === end ? null : { start, end };
|
|
583
|
+
}
|
|
530
584
|
// ── Content clamp ────────────────────────────────────────────────────────────
|
|
531
585
|
function clampToContent(offset, content) {
|
|
532
586
|
return Math.min(Math.max(offset, content.start), content.end);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { ImageCrop } from '../nodes';
|
|
2
|
+
export declare const MAX_CROP_ZOOM = 4;
|
|
1
3
|
export interface ParsedImageAlt {
|
|
2
4
|
displayAlt: string;
|
|
3
5
|
width: number | undefined;
|
|
4
6
|
height: number | undefined;
|
|
7
|
+
crop: ImageCrop | undefined;
|
|
5
8
|
}
|
|
6
9
|
export declare function parseImageDimensions(alt: string): ParsedImageAlt;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
// `|N` / `|NxM` is an Obsidian extension, not part of GFM.
|
|
1
|
+
// `|N` / `|NxM` is an Obsidian extension, not part of GFM. The `@X,Y[,Z]` tail on the `NxM`
|
|
2
|
+
// form is this editor's own: the pan and zoom of the image inside that frame.
|
|
2
3
|
const MAX_DIMENSION = 10000;
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
4
|
+
export const MAX_CROP_ZOOM = 4;
|
|
5
|
+
// The longest decodable suffix, `|10000x10000@100,100,4.00`. Bounding the pipe search to that
|
|
6
|
+
// tail keeps nested-label floods linear.
|
|
7
|
+
const MAX_SUFFIX_SEARCH = 26;
|
|
8
|
+
const NO_HINT = { width: undefined, height: undefined, crop: undefined };
|
|
6
9
|
export function parseImageDimensions(alt) {
|
|
7
10
|
const lastPipe = boundedLastPipe(alt);
|
|
8
11
|
const dims = lastPipe === -1 ? null : parseDimensionSuffix(alt.slice(lastPipe + 1));
|
|
9
12
|
if (dims === null)
|
|
10
|
-
return { displayAlt: alt,
|
|
11
|
-
return { displayAlt: alt.slice(0, lastPipe),
|
|
13
|
+
return { displayAlt: alt, ...NO_HINT };
|
|
14
|
+
return { displayAlt: alt.slice(0, lastPipe), ...dims };
|
|
12
15
|
}
|
|
13
16
|
function boundedLastPipe(alt) {
|
|
14
17
|
const floor = alt.length > MAX_SUFFIX_SEARCH ? alt.length - MAX_SUFFIX_SEARCH : 0;
|
|
@@ -24,16 +27,49 @@ function parseDimensionSuffix(s) {
|
|
|
24
27
|
const w = parseStrictInt(s);
|
|
25
28
|
if (w === null)
|
|
26
29
|
return null;
|
|
27
|
-
return { width: w, height: undefined };
|
|
30
|
+
return { width: w, height: undefined, crop: undefined };
|
|
28
31
|
}
|
|
32
|
+
const atIdx = s.indexOf('@');
|
|
33
|
+
const heightEnd = atIdx === -1 ? s.length : atIdx;
|
|
29
34
|
const w = parseStrictInt(s.slice(0, xIdx));
|
|
30
|
-
const h = parseStrictInt(s.slice(xIdx + 1));
|
|
35
|
+
const h = parseStrictInt(s.slice(xIdx + 1, heightEnd));
|
|
31
36
|
if (w === null || h === null)
|
|
32
37
|
return null;
|
|
33
|
-
|
|
38
|
+
if (atIdx === -1)
|
|
39
|
+
return { width: w, height: h, crop: undefined };
|
|
40
|
+
const crop = parseCropTail(s.slice(atIdx + 1));
|
|
41
|
+
// A malformed tail is not a hint at all: the whole suffix stays alt text.
|
|
42
|
+
if (crop === null)
|
|
43
|
+
return null;
|
|
44
|
+
return { width: w, height: h, crop };
|
|
45
|
+
}
|
|
46
|
+
/** `X,Y` or `X,Y,Z`: whole percents 0–100 and a zoom of 1–4 with up to two decimals. */
|
|
47
|
+
function parseCropTail(s) {
|
|
48
|
+
const parts = s.split(',');
|
|
49
|
+
if (parts.length < 2 || parts.length > 3)
|
|
50
|
+
return null;
|
|
51
|
+
const x = parsePercent(parts[0]);
|
|
52
|
+
const y = parsePercent(parts[1]);
|
|
53
|
+
if (x === null || y === null)
|
|
54
|
+
return null;
|
|
55
|
+
if (parts.length === 2)
|
|
56
|
+
return { x, y, z: 1 };
|
|
57
|
+
if (!/^[1-4](?:\.\d{1,2})?$/.test(parts[2]))
|
|
58
|
+
return null;
|
|
59
|
+
const z = Number(parts[2]);
|
|
60
|
+
if (z > MAX_CROP_ZOOM)
|
|
61
|
+
return null;
|
|
62
|
+
return { x, y, z };
|
|
63
|
+
}
|
|
64
|
+
function parsePercent(s) {
|
|
65
|
+
if (!/^\d{1,3}$/.test(s))
|
|
66
|
+
return null;
|
|
67
|
+
const n = Number(s);
|
|
68
|
+
return n > 100 ? null : n;
|
|
34
69
|
}
|
|
70
|
+
/** No leading zeros: a padded run is alt text, not a hint. */
|
|
35
71
|
function parseStrictInt(s) {
|
|
36
|
-
if (
|
|
72
|
+
if (!/^[1-9]\d*$/.test(s))
|
|
37
73
|
return null;
|
|
38
74
|
const n = Number(s);
|
|
39
75
|
if (n <= 0 || n > MAX_DIMENSION)
|
|
@@ -67,6 +67,22 @@ export declare const ON_EDGE_POLICIES: readonly ["select", "step-over"];
|
|
|
67
67
|
*/
|
|
68
68
|
export interface InlineWidgetEditingPolicy {
|
|
69
69
|
revealSource?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Where this kind's editable CONTENT sits inside its source span, as offsets relative to
|
|
72
|
+
* that span — `$x$` answers `{ start: 1, end: 2 }`. It bounds a caret entering the source,
|
|
73
|
+
* and `end` is where a revealing click lands when the kind maps no point of its own. Only
|
|
74
|
+
* the kind knows its own delimiters; absent, the caret keeps the leading edge.
|
|
75
|
+
*/
|
|
76
|
+
revealContentSpan?: (source: string) => {
|
|
77
|
+
start: number;
|
|
78
|
+
end: number;
|
|
79
|
+
} | null;
|
|
80
|
+
/**
|
|
81
|
+
* The offset in `source` a press on the RENDERED widget names, so a click seats the caret
|
|
82
|
+
* where it landed rather than at one edge. Only the kind can map its render back to bytes (a
|
|
83
|
+
* KaTeX island paints glyphs, not source); null declines this point and keeps the fallback.
|
|
84
|
+
*/
|
|
85
|
+
revealOffsetAtPoint?: (widgetEl: HTMLElement, source: string, clientX: number, clientY: number) => number | null;
|
|
70
86
|
deleteGranularity?: (typeof DELETE_GRANULARITIES)[number];
|
|
71
87
|
onEdge?: (typeof ON_EDGE_POLICIES)[number];
|
|
72
88
|
onSelectedKey?: (e: KeyboardEvent, ctx: InlineWidgetEditingContext) => boolean;
|
|
@@ -107,6 +123,9 @@ export declare function augmentInlineWidgetKind(kind: AnyInlineKind, editing: Pa
|
|
|
107
123
|
/** Kind-level recognition, independent of per-block render policy (renderImagesAsWidgets). */
|
|
108
124
|
export declare function isInlineWidget(node: InlineNode, raw: string): boolean;
|
|
109
125
|
export declare function getInlineWidgetEditing(kind: AnyInlineKind): InlineWidgetEditingPolicy | undefined;
|
|
126
|
+
/** A kind the caret reads as one character: it steps over in one press, carries a column of its
|
|
127
|
+
* own, and a press ON its glyph names an edge rather than selecting the island whole. */
|
|
128
|
+
export declare function isCharacterLikeWidget(kind: AnyInlineKind): boolean;
|
|
110
129
|
export declare function getInlineWidgetComponent(kind: AnyInlineKind): Component<InlineWidgetComponentProps> | undefined;
|
|
111
130
|
/**
|
|
112
131
|
* Every live widget reachable from `nodes`, in document order. Descends so a widget nested in a
|
|
@@ -74,6 +74,11 @@ export function isInlineWidget(node, raw) {
|
|
|
74
74
|
export function getInlineWidgetEditing(kind) {
|
|
75
75
|
return registry.get(kind)?.editing;
|
|
76
76
|
}
|
|
77
|
+
/** A kind the caret reads as one character: it steps over in one press, carries a column of its
|
|
78
|
+
* own, and a press ON its glyph names an edge rather than selecting the island whole. */
|
|
79
|
+
export function isCharacterLikeWidget(kind) {
|
|
80
|
+
return getInlineWidgetEditing(kind)?.onEdge === 'step-over';
|
|
81
|
+
}
|
|
77
82
|
export function getInlineWidgetComponent(kind) {
|
|
78
83
|
return registry.get(kind)?.component;
|
|
79
84
|
}
|
|
@@ -112,6 +112,7 @@ function buildImage(raw, opener, labelEnd, target, children) {
|
|
|
112
112
|
...(target.title !== undefined ? { title: target.title } : {}),
|
|
113
113
|
...(dims.width !== undefined ? { width: dims.width } : {}),
|
|
114
114
|
...(dims.height !== undefined ? { height: dims.height } : {}),
|
|
115
|
+
...(dims.crop !== undefined ? { crop: dims.crop } : {}),
|
|
115
116
|
...(target.label !== undefined ? { label: target.label } : {})
|
|
116
117
|
};
|
|
117
118
|
}
|
|
@@ -38,6 +38,12 @@ export interface InlineSyntaxOptions {
|
|
|
38
38
|
* commit's equality guard. See the plugin guide's inline section.
|
|
39
39
|
*/
|
|
40
40
|
rewriteImage?: ImageSyntaxRewriter;
|
|
41
|
+
/**
|
|
42
|
+
* The trigger is a symmetric one-byte delimiter (`$…$`) a keystroke should close at once:
|
|
43
|
+
* typing it lands its twin after the caret, so the new opener pairs with that twin rather
|
|
44
|
+
* than with a later formula's delimiter (see `delimiter-autopair.ts`). Bare triggers only.
|
|
45
|
+
*/
|
|
46
|
+
autoPair?: boolean;
|
|
41
47
|
}
|
|
42
48
|
export interface InlineRung extends InlineSyntaxClaim {
|
|
43
49
|
recognizer: InlineSyntaxRecognizer;
|
|
@@ -57,6 +63,8 @@ export declare function hasInlineSyntax(): boolean;
|
|
|
57
63
|
/** Read once per scan, so an empty set leaves `needsScan` at its pre-plugin cost. */
|
|
58
64
|
export declare function hasScanProbeRungs(): boolean;
|
|
59
65
|
export declare function isScanProbeTrigger(char: string): boolean;
|
|
66
|
+
/** Whether a plugin asked for `char` to close itself as it is typed. */
|
|
67
|
+
export declare function isAutoPairTrigger(char: string): boolean;
|
|
60
68
|
/** False costs the scan loop nothing. */
|
|
61
69
|
export declare function hasPrefixRungs(): boolean;
|
|
62
70
|
export declare function __resetInlineSyntaxForTests(): void;
|
|
@@ -36,6 +36,9 @@ const NO_RUNGS = [];
|
|
|
36
36
|
// (`default` arm) so each dispatch path reads one map and its empty gate is one `size` check.
|
|
37
37
|
const reservedRegistry = new Map();
|
|
38
38
|
const unreservedRegistry = new Map();
|
|
39
|
+
// Triggers whose keystroke closes itself (`autoPair`); the built-in backtick is not registered
|
|
40
|
+
// here, the typing seam knows it on its own.
|
|
41
|
+
const autoPairTriggers = new Set();
|
|
39
42
|
// Triggers the fast bail (`needsScan`, scan/index.ts) must visit while a rung lives on them.
|
|
40
43
|
// Maintained at registration, so a rung on a trigger `SPECIAL_CHARS` already visits costs nothing.
|
|
41
44
|
const scanProbeTriggers = new Set();
|
|
@@ -44,7 +47,7 @@ export function registerInlineSyntax(trigger, recognizer, options) {
|
|
|
44
47
|
if (trigger.length !== 1) {
|
|
45
48
|
throw new Error('registerInlineSyntax: trigger must be a single character');
|
|
46
49
|
}
|
|
47
|
-
const { prefix, priority = INLINE_PRIORITIES.plugin, rewriteImage } = options ?? {};
|
|
50
|
+
const { prefix, priority = INLINE_PRIORITIES.plugin, rewriteImage, autoPair } = options ?? {};
|
|
48
51
|
if (prefix !== undefined && (prefix.length < 2 || !prefix.startsWith(trigger))) {
|
|
49
52
|
throw new Error(`registerInlineSyntax: prefix ${JSON.stringify(prefix)} must begin with the trigger ` +
|
|
50
53
|
`${JSON.stringify(trigger)} and be at least two characters`);
|
|
@@ -69,6 +72,10 @@ export function registerInlineSyntax(trigger, recognizer, options) {
|
|
|
69
72
|
`the built-in scanner; got ${priority}`);
|
|
70
73
|
}
|
|
71
74
|
}
|
|
75
|
+
if (autoPair && prefix !== undefined) {
|
|
76
|
+
throw new Error(`registerInlineSyntax: autoPair pairs the bare trigger ${JSON.stringify(trigger)} with ` +
|
|
77
|
+
`itself, so it cannot be combined with a prefix`);
|
|
78
|
+
}
|
|
72
79
|
const effectivePrefix = prefix ?? trigger;
|
|
73
80
|
const registry = reserved ? reservedRegistry : unreservedRegistry;
|
|
74
81
|
const existing = registry.get(trigger);
|
|
@@ -84,6 +91,8 @@ export function registerInlineSyntax(trigger, recognizer, options) {
|
|
|
84
91
|
// or the recognizer is the silent no-op this seam refuses to accept.
|
|
85
92
|
if (!reserved || SCAN_PROBED_RESERVED.has(trigger))
|
|
86
93
|
scanProbeTriggers.add(trigger);
|
|
94
|
+
if (autoPair)
|
|
95
|
+
autoPairTriggers.add(trigger);
|
|
87
96
|
}, `registerInlineSyntax: ${JSON.stringify(trigger)} already registered at prefix ` +
|
|
88
97
|
`${JSON.stringify(effectivePrefix)}, priority ${priority}`);
|
|
89
98
|
}
|
|
@@ -139,6 +148,10 @@ export function hasScanProbeRungs() {
|
|
|
139
148
|
export function isScanProbeTrigger(char) {
|
|
140
149
|
return scanProbeTriggers.has(char);
|
|
141
150
|
}
|
|
151
|
+
/** Whether a plugin asked for `char` to close itself as it is typed. */
|
|
152
|
+
export function isAutoPairTrigger(char) {
|
|
153
|
+
return autoPairTriggers.has(char);
|
|
154
|
+
}
|
|
142
155
|
/** False costs the scan loop nothing. */
|
|
143
156
|
export function hasPrefixRungs() {
|
|
144
157
|
return reservedRegistry.size > 0;
|
|
@@ -147,4 +160,5 @@ export function __resetInlineSyntaxForTests() {
|
|
|
147
160
|
reservedRegistry.clear();
|
|
148
161
|
unreservedRegistry.clear();
|
|
149
162
|
scanProbeTriggers.clear();
|
|
163
|
+
autoPairTriggers.clear();
|
|
150
164
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* an unparsed block degrades to "land on it".
|
|
6
6
|
*/
|
|
7
7
|
import { getInlineContent } from './inline-cache';
|
|
8
|
-
import { isInlineWidget,
|
|
8
|
+
import { isInlineWidget, isCharacterLikeWidget } from './inline-widgets';
|
|
9
9
|
export function isVerticallyTransparentNode(node) {
|
|
10
10
|
if (!node)
|
|
11
11
|
return false;
|
|
@@ -26,8 +26,8 @@ export function isVerticallyTransparentNode(node) {
|
|
|
26
26
|
return false;
|
|
27
27
|
for (const inline of inlines) {
|
|
28
28
|
if (isInlineWidget(inline, node.raw)) {
|
|
29
|
-
// A
|
|
30
|
-
if (
|
|
29
|
+
// A character-like widget carries a column, so it reads as text.
|
|
30
|
+
if (isCharacterLikeWidget(inline.kind))
|
|
31
31
|
return false;
|
|
32
32
|
continue;
|
|
33
33
|
}
|
|
@@ -25,6 +25,12 @@ export interface RenderInlineOptions {
|
|
|
25
25
|
* as `buildImageWidget`; absent or null falls the widget back to its raw source.
|
|
26
26
|
*/
|
|
27
27
|
buildPortalWidget?: (node: InlineNode, raw: string) => HTMLElement | null;
|
|
28
|
+
/**
|
|
29
|
+
* Paint a lone backslash ending the display as the hard break it is about to become: the
|
|
30
|
+
* byte as a (hidden) marker plus two `br` anchors so the caret has a second line to sit on.
|
|
31
|
+
* Only for a mode that hides markers; the DOM stays byte-identical elsewhere.
|
|
32
|
+
*/
|
|
33
|
+
pendingBreakSeat?: boolean;
|
|
28
34
|
/**
|
|
29
35
|
* Stamp marker spans with the construct's raw range, so preview-inline's reveal trigger can
|
|
30
36
|
* address them. Attributes only, leaving textContent and the offset walk untouched. Off by
|
|
@@ -288,8 +288,40 @@ export function renderInlineNodes(nodes, raw, opts = {}) {
|
|
|
288
288
|
if (child !== null)
|
|
289
289
|
stack.push(child);
|
|
290
290
|
}
|
|
291
|
+
if (opts.pendingBreakSeat)
|
|
292
|
+
paintPendingBreak(nodes, raw, root.content);
|
|
291
293
|
return root.content;
|
|
292
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* `insertHardBreak` at the end of a block writes `\` whose line ending IS the block's trailing
|
|
297
|
+
* one, so until the next key supplies the following line CommonMark (and the scanner) read the
|
|
298
|
+
* byte as literal text. Left as text it shows as a stray backslash and the caret stays on the
|
|
299
|
+
* first line; painted like this the user sees the new line they asked for. A `br` adds no
|
|
300
|
+
* textContent, so G1.28 holds; the walk seats offset `length` after the first anchor.
|
|
301
|
+
*/
|
|
302
|
+
function paintPendingBreak(nodes, raw, frag) {
|
|
303
|
+
const last = nodes[nodes.length - 1];
|
|
304
|
+
if (!last || last.kind !== 'text' || raw[last.end - 1] !== '\\')
|
|
305
|
+
return;
|
|
306
|
+
const rest = raw.slice(last.end);
|
|
307
|
+
if (rest !== '' && rest !== '\n' && rest !== '\r\n')
|
|
308
|
+
return;
|
|
309
|
+
const tail = frag.lastChild;
|
|
310
|
+
if (!tail || tail.nodeType !== Node.TEXT_NODE || !tail.textContent?.endsWith('\\'))
|
|
311
|
+
return;
|
|
312
|
+
if (tail.textContent.length === 1)
|
|
313
|
+
tail.remove();
|
|
314
|
+
else
|
|
315
|
+
tail.textContent = tail.textContent.slice(0, -1);
|
|
316
|
+
const marker = markerSpan('\\');
|
|
317
|
+
marker.classList.add('md-break-pending');
|
|
318
|
+
frag.appendChild(marker);
|
|
319
|
+
for (let i = 0; i < 2; i++) {
|
|
320
|
+
const anchor = document.createElement('br');
|
|
321
|
+
anchor.dataset.caretAnchor = 'break';
|
|
322
|
+
frag.appendChild(anchor);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
293
325
|
/**
|
|
294
326
|
* The leaf containing `offset`, preferring the right node at a boundary; `offset === end` only
|
|
295
327
|
* matches the last node. Model-layer, touching no DOM: the DOM counterpart is
|
package/dist/core/nodes.d.ts
CHANGED
|
@@ -259,6 +259,8 @@ export interface InlineNode {
|
|
|
259
259
|
decoded?: string;
|
|
260
260
|
width?: number;
|
|
261
261
|
height?: number;
|
|
262
|
+
/** Pan/zoom inside the `|WxH` frame, read from a `@X,Y[,Z]` tail on the hint. */
|
|
263
|
+
crop?: ImageCrop;
|
|
262
264
|
/** Discriminator for `unresolvedReference` nodes: which form they would have been. */
|
|
263
265
|
refKind?: 'link' | 'image';
|
|
264
266
|
/**
|
|
@@ -271,12 +273,24 @@ export interface InlineNode {
|
|
|
271
273
|
* Optional keys are omitted rather than set to `undefined`, so a serializer can tell "no title"
|
|
272
274
|
* from "empty title" and reproduce a node that never carried one.
|
|
273
275
|
*/
|
|
276
|
+
/**
|
|
277
|
+
* How an image sits inside its `|WxH` frame: the image point (`x`%, `y`%) pinned to the same
|
|
278
|
+
* point of the frame, at `z` times the smallest scale that fills it. `{50, 50, 1}` is a plain
|
|
279
|
+
* centred cover fit; absent, the frame stretches the image the way `<img width height>` does.
|
|
280
|
+
*/
|
|
281
|
+
export interface ImageCrop {
|
|
282
|
+
x: number;
|
|
283
|
+
y: number;
|
|
284
|
+
z: number;
|
|
285
|
+
}
|
|
274
286
|
export interface ImageFields {
|
|
275
287
|
alt: string;
|
|
276
288
|
url: string;
|
|
277
289
|
title?: string;
|
|
278
290
|
width?: number;
|
|
279
291
|
height?: number;
|
|
292
|
+
/** Needs both dimensions: a crop is a window into a fixed frame. */
|
|
293
|
+
crop?: ImageCrop;
|
|
280
294
|
/**
|
|
281
295
|
* Reference-style images only (`![alt][label]`). When set, the serializer emits the reference
|
|
282
296
|
* form and writes no url/title: those live in the LRD, and inlining them would orphan it.
|
|
@@ -39,7 +39,8 @@ export function classifyArrivalKey(key, metaKey = false) {
|
|
|
39
39
|
if (metaKey && (key === 'ArrowLeft' || key === 'ArrowRight'))
|
|
40
40
|
return 'outside';
|
|
41
41
|
// A step stops on the side of the run it approached from, so one press never changes which
|
|
42
|
-
// construct the caret is in
|
|
42
|
+
// construct the caret is in (live-mode.md § 4.2); leaving a construct is a typed closer's job
|
|
43
|
+
// (delimiter-autopair.ts) or a toggle's, not the arrow's.
|
|
43
44
|
if (key === 'ArrowRight' || key === 'ArrowDown' || key === 'PageDown')
|
|
44
45
|
return 'near';
|
|
45
46
|
if (key === 'ArrowLeft' || key === 'ArrowUp' || key === 'PageUp')
|
|
@@ -16,9 +16,8 @@ export interface HeightOracle {
|
|
|
16
16
|
estimate(node: NodeView, width: number): number;
|
|
17
17
|
measured(id: string): number | undefined;
|
|
18
18
|
recordMeasured(id: string, height: number): void;
|
|
19
|
-
/** measured
|
|
20
|
-
|
|
21
|
-
/** Drop every measured height; estimates carry the model until each block re-measures. */
|
|
19
|
+
/** Drop every measured height. A live scope carries its own across a rebuild (VR-15), so
|
|
20
|
+
* estimates seed only a scope built after the drop, and any block with a fresh id. */
|
|
22
21
|
dropMeasured(): void;
|
|
23
22
|
}
|
|
24
23
|
export declare function createHeightOracle(opts: HeightOracleOptions): HeightOracle;
|
|
@@ -27,5 +27,13 @@ export function wireOverlayRemeasure(opts) {
|
|
|
27
27
|
editorRoot.addEventListener('scroll', measure, { passive: true });
|
|
28
28
|
disposers.push(() => editorRoot.removeEventListener('scroll', measure));
|
|
29
29
|
}
|
|
30
|
+
// The block's own box changing under a live range — a paragraph set to a heading, a font
|
|
31
|
+
// load, a resize — moves the text the rects were measured against. Layout-driven, so it
|
|
32
|
+
// fires after the change has painted; absent (jsdom) the scroll paths above still hold.
|
|
33
|
+
if (typeof ResizeObserver === 'function') {
|
|
34
|
+
const observer = new ResizeObserver(() => measure());
|
|
35
|
+
observer.observe(el);
|
|
36
|
+
disposers.push(() => observer.disconnect());
|
|
37
|
+
}
|
|
30
38
|
return () => disposers.forEach((dispose) => dispose());
|
|
31
39
|
}
|
|
@@ -8,13 +8,18 @@
|
|
|
8
8
|
import { tick } from 'svelte';
|
|
9
9
|
import { asRawOffset, toDomTextOffset } from './coordinate-spaces';
|
|
10
10
|
import { restoreCaretAtWalkOffset } from './focused-caret';
|
|
11
|
+
import { clampToLandableRaw } from './widget-offset';
|
|
11
12
|
import { assertInvariant } from '../assert';
|
|
12
13
|
import { checkRevealSourceLength } from '../invariants/inline-transitions';
|
|
13
14
|
export function createSourceReveal(deps) {
|
|
14
15
|
/** Places the caret at a BLOCK-source offset, converting to ambient-included walk space. */
|
|
15
16
|
function placeCaret(container, blockSourceOffset) {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
// Offset 0 of a source that opens with hidden chrome (a leaf painting `$$` fence lines) is
|
|
18
|
+
// BEFORE that chrome: typing there would land outside the fence. The seat is the nearest
|
|
19
|
+
// landable position, which is a no-op wherever nothing hides.
|
|
20
|
+
const ambient = deps.getAmbientLength();
|
|
21
|
+
const seat = clampToLandableRaw(container, blockSourceOffset, ambient);
|
|
22
|
+
restoreCaretAtWalkOffset(container, toDomTextOffset(asRawOffset(seat), ambient));
|
|
18
23
|
}
|
|
19
24
|
async function reveal(atSourceOffset = 0) {
|
|
20
25
|
assertInvariant('reveal-transition', () => checkRevealSourceLength(deps.source.length, deps.sourceStart, deps.sourceEnd));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scroll-position hold across a block's render swap. A swap that shortens a block clamps the
|
|
3
|
+
* scrollport against the transient layout on the way to its settled height, and the clamp is
|
|
4
|
+
* never given back. A reveal in flight outranks the hold and owns the position; host mode does
|
|
5
|
+
* not, since native anchoring cannot undo a max-scroll clamp. Only a measure-then-fit surface
|
|
6
|
+
* needs this, and today that is one: an editable leaf's source is content-sized at mount.
|
|
7
|
+
*/
|
|
8
|
+
/** Capture the position of whatever scrolls `el`; await the result AFTER the state flip that
|
|
9
|
+
* changes the block's height. */
|
|
10
|
+
export declare function captureScrollPosition(el: HTMLElement | null | undefined, isRevealInFlight: () => boolean): () => Promise<void>;
|