@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
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The editor's own formatting popover over a selection, in Notion's shape: a row of the
|
|
4
|
+
* marks, then labelled rows for what acts on the selection as a whole. Built on the same
|
|
5
|
+
* doors a host's chrome would use (`selectionChange`, the rect API, the command door), so
|
|
6
|
+
* the consumer-guide recipe and this component cannot disagree about what a selection
|
|
7
|
+
* toolbar may read.
|
|
8
|
+
*/
|
|
9
|
+
import type { EditorInstance } from '../../editor-props';
|
|
10
|
+
import type { EditorSelection } from '../../selection/primitives';
|
|
11
|
+
import { normalize as normalizeSelection } from '../../selection/primitives';
|
|
12
|
+
import { SELECTION_END } from '../../block-component';
|
|
13
|
+
import { TOOLBAR_COMMANDS } from '../../schema/commands';
|
|
14
|
+
import { SELECTION_TOOLBAR_LABEL } from '../../a11y-strings';
|
|
15
|
+
import { keepFlyoutOnScreen } from './flyout-placement';
|
|
16
|
+
import { runClipboardAction } from './clipboard-actions';
|
|
17
|
+
import MenuIcon, { type MenuIconName } from './MenuIcon.svelte';
|
|
18
|
+
|
|
19
|
+
type ToolbarEditor = Pick<
|
|
20
|
+
EditorInstance,
|
|
21
|
+
| 'getSelection'
|
|
22
|
+
| 'getRects'
|
|
23
|
+
| 'getBlockKindAt'
|
|
24
|
+
| 'runCommand'
|
|
25
|
+
| 'canRunCommand'
|
|
26
|
+
| 'isCommandActive'
|
|
27
|
+
| 'getEvents'
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
// The bar: the three marks and the link. Beneath it, as rows: the heading picker, inline code
|
|
31
|
+
// and copy. The bar sets the card's width.
|
|
32
|
+
const MARKS: readonly { icon: MenuIconName; title: string; command: string; size: number }[] = [
|
|
33
|
+
{ icon: 'bold', title: 'Bold (Ctrl/Cmd+B)', command: TOOLBAR_COMMANDS.toggleStrong, size: 16 },
|
|
34
|
+
{
|
|
35
|
+
icon: 'italic',
|
|
36
|
+
title: 'Italic (Ctrl/Cmd+I)',
|
|
37
|
+
command: TOOLBAR_COMMANDS.toggleEmphasis,
|
|
38
|
+
size: 16
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
icon: 'strikethrough',
|
|
42
|
+
title: 'Strikethrough (Ctrl/Cmd+Shift+X)',
|
|
43
|
+
command: TOOLBAR_COMMANDS.toggleStrikethrough,
|
|
44
|
+
size: 16
|
|
45
|
+
},
|
|
46
|
+
{ icon: 'link', title: 'Edit link (Ctrl/Cmd+K)', command: TOOLBAR_COMMANDS.editLink, size: 10 }
|
|
47
|
+
];
|
|
48
|
+
const ROWS: readonly { icon: MenuIconName; label: string; command: string }[] = [
|
|
49
|
+
{ icon: 'code', label: 'Inline code', command: TOOLBAR_COMMANDS.toggleCode }
|
|
50
|
+
];
|
|
51
|
+
const TURN_INTO_COMMAND = TOOLBAR_COMMANDS.setHeading;
|
|
52
|
+
const TURN_INTO: readonly { label: string; level: number }[] = [
|
|
53
|
+
{ label: 'Normal text', level: 0 },
|
|
54
|
+
{ label: 'Heading 1', level: 1 },
|
|
55
|
+
{ label: 'Heading 2', level: 2 },
|
|
56
|
+
{ label: 'Heading 3', level: 3 }
|
|
57
|
+
];
|
|
58
|
+
const BUTTONS = [...MARKS, ...ROWS, { command: TURN_INTO_COMMAND }];
|
|
59
|
+
const BAR_GAP = 6;
|
|
60
|
+
|
|
61
|
+
let { editor, root }: { editor: ToolbarEditor; root: HTMLElement | undefined } = $props();
|
|
62
|
+
|
|
63
|
+
let turnIntoOpen = $state(false);
|
|
64
|
+
let blockKind = $state<string | null>(null);
|
|
65
|
+
let placement = $state<Placement | null>(null);
|
|
66
|
+
let declined = $state<ReadonlySet<string>>(new Set());
|
|
67
|
+
let active = $state<ReadonlySet<string>>(new Set());
|
|
68
|
+
let current: EditorSelection | null = null;
|
|
69
|
+
let barEl: HTMLDivElement | undefined = $state();
|
|
70
|
+
|
|
71
|
+
// A labelled row the door declines leaves the bar, where a mark only dims: the icons keep the
|
|
72
|
+
// bar's shape, a dead row is a line of noise over a selection it cannot act on.
|
|
73
|
+
const rows = $derived(ROWS.filter((row) => !declined.has(row.command)));
|
|
74
|
+
const offersHeading = $derived(!declined.has(TURN_INTO_COMMAND));
|
|
75
|
+
|
|
76
|
+
interface Placement {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
/** The selection's first rect, for the flip above when there is no room below. */
|
|
80
|
+
flipY: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The editor root's top is the floor the flip may not cross: whatever host chrome sits
|
|
84
|
+
// above the editor ends where the root begins.
|
|
85
|
+
function topInset(): number {
|
|
86
|
+
return Math.max(0, root?.getBoundingClientRect().top ?? 0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The document's own copy, so a cross-block range lands as the Markdown Ctrl+C would write.
|
|
90
|
+
function copySelection(): void {
|
|
91
|
+
void runClipboardAction('copy', null);
|
|
92
|
+
placement = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// The bar's own size, measured once it is in the DOM, so the placement can keep it on screen:
|
|
96
|
+
// pushed left at the right edge, flipped above the selection when there is no room below.
|
|
97
|
+
let size = $state<{ w: number; h: number } | null>(null);
|
|
98
|
+
$effect(() => {
|
|
99
|
+
if (!barEl || !placement) return;
|
|
100
|
+
const rect = barEl.getBoundingClientRect();
|
|
101
|
+
size = { w: rect.width, h: rect.height };
|
|
102
|
+
});
|
|
103
|
+
const shown = $derived.by(() => {
|
|
104
|
+
if (!placement) return null;
|
|
105
|
+
if (!size) return { x: placement.x, y: placement.y };
|
|
106
|
+
const margin = 8;
|
|
107
|
+
let { x, y } = placement;
|
|
108
|
+
if (x + size.w > window.innerWidth - margin)
|
|
109
|
+
x = Math.max(margin, window.innerWidth - margin - size.w);
|
|
110
|
+
if (y + size.h > window.innerHeight - margin)
|
|
111
|
+
y = Math.max(topInset() + 4, placement.flipY - size.h - BAR_GAP);
|
|
112
|
+
return { x, y };
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
$effect(() => editor.getEvents().on('selectionChange', update));
|
|
116
|
+
|
|
117
|
+
// Only once the drag is over: a bar that appears and re-seats under a moving pointer is in the
|
|
118
|
+
// way of the very selection being made. The press is watched at the document, since the drag
|
|
119
|
+
// can end anywhere; the release places the bar from the selection that stood at that moment.
|
|
120
|
+
let pointerHeld = false;
|
|
121
|
+
$effect(() => {
|
|
122
|
+
// A press on the bar itself is a button, not a drag: arming the release here would re-place
|
|
123
|
+
// the bar (and close its flyout) under the click that follows.
|
|
124
|
+
const down = (e: PointerEvent) => {
|
|
125
|
+
if (e.button === 0 && !barEl?.contains(e.target as Node)) pointerHeld = true;
|
|
126
|
+
};
|
|
127
|
+
// Placed from the range the editor last reported; a release that still moves the range
|
|
128
|
+
// reports again through `selectionChange`, which re-places.
|
|
129
|
+
const up = () => {
|
|
130
|
+
if (!pointerHeld) return;
|
|
131
|
+
pointerHeld = false;
|
|
132
|
+
if (current) update(current);
|
|
133
|
+
};
|
|
134
|
+
document.addEventListener('pointerdown', down, true);
|
|
135
|
+
document.addEventListener('pointerup', up, true);
|
|
136
|
+
document.addEventListener('pointercancel', up, true);
|
|
137
|
+
return () => {
|
|
138
|
+
document.removeEventListener('pointerdown', down, true);
|
|
139
|
+
document.removeEventListener('pointerup', up, true);
|
|
140
|
+
document.removeEventListener('pointercancel', up, true);
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// The editor's own right-click menu takes the selection's spot; two cards over one range read
|
|
145
|
+
// as a glitch, so the bar hides while it is open and comes back where the selection still is.
|
|
146
|
+
let menuOpen = false;
|
|
147
|
+
$effect(() =>
|
|
148
|
+
editor.getEvents().on('menuChange', (open) => {
|
|
149
|
+
menuOpen = open;
|
|
150
|
+
if (open) placement = null;
|
|
151
|
+
else if (current) update(current);
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Sticky with the text: the rects are viewport coordinates, so a scroll or resize moves the
|
|
156
|
+
// selection under the bar and the bar re-measures. Scroll already arrives once per frame.
|
|
157
|
+
$effect(() => {
|
|
158
|
+
const reanchor = () => {
|
|
159
|
+
// Same gates as a selection change: a scroll mid-drag (autoscroll included) must not
|
|
160
|
+
// bring the bar out before the release does.
|
|
161
|
+
if (current) placement = shownOver(current);
|
|
162
|
+
};
|
|
163
|
+
window.addEventListener('scroll', reanchor, { capture: true, passive: true });
|
|
164
|
+
window.addEventListener('resize', reanchor, { passive: true });
|
|
165
|
+
return () => {
|
|
166
|
+
window.removeEventListener('scroll', reanchor, { capture: true });
|
|
167
|
+
window.removeEventListener('resize', reanchor);
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
function update(selection: EditorSelection | null): void {
|
|
172
|
+
current = selection;
|
|
173
|
+
turnIntoOpen = false;
|
|
174
|
+
blockKind = selection ? editor.getBlockKindAt(normalizeSelection(selection).start.path) : null;
|
|
175
|
+
// Asked per selection change, not per render: the answers are snapshots of this selection.
|
|
176
|
+
declined = new Set(
|
|
177
|
+
BUTTONS.filter((b) => !editor.canRunCommand(b.command)).map((b) => b.command)
|
|
178
|
+
);
|
|
179
|
+
active = new Set(
|
|
180
|
+
BUTTONS.filter((b) => editor.isCommandActive(b.command)).map((b) => b.command)
|
|
181
|
+
);
|
|
182
|
+
placement = selection ? shownOver(selection) : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The door decides where the bar belongs: over a code fence or an equation's source it declines
|
|
186
|
+
// every mark, and a bar of greyed buttons is worse than none.
|
|
187
|
+
function shownOver(selection: EditorSelection): Placement | null {
|
|
188
|
+
if (menuOpen || pointerHeld) return null;
|
|
189
|
+
if (MARKS.every((mark) => declined.has(mark.command))) return null;
|
|
190
|
+
return place(selection);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function place(selection: EditorSelection): Placement | null {
|
|
194
|
+
const { start, end } = normalizeSelection(selection);
|
|
195
|
+
const sameBlock = start.path.join('.') === end.path.join('.');
|
|
196
|
+
if (!sameBlock) {
|
|
197
|
+
// The bar hangs off where the selection ENDS, so the end block's rects come first. A
|
|
198
|
+
// selection that starts at the very end of one block (a drag that left the block
|
|
199
|
+
// upward, or began at the previous paragraph's end) leaves the start block's remaining
|
|
200
|
+
// range empty, so that read is a fallback, and the start block's whole box the last one.
|
|
201
|
+
const rects = editor.getRects();
|
|
202
|
+
const endRects =
|
|
203
|
+
editor.getBlockKindAt(end.path) !== 'table' && end.offset > 0
|
|
204
|
+
? rects.rangeRects(end.path, 0, end.offset)
|
|
205
|
+
: [];
|
|
206
|
+
const anchored = endRects.length
|
|
207
|
+
? endRects
|
|
208
|
+
: rects.rangeRects(start.path, start.offset, SELECTION_END);
|
|
209
|
+
const measured = anchored.length ? anchored : rects.rangeRects(start.path, 0, SELECTION_END);
|
|
210
|
+
return measured.length ? belowRight(measured) : null;
|
|
211
|
+
}
|
|
212
|
+
// An intra-table rectangle shares the table's path and carries cell indices on endpoints the
|
|
213
|
+
// flag need not mark, so the kind read is what excludes it rather than the flag.
|
|
214
|
+
if (editor.getBlockKindAt(start.path) === 'table') return null;
|
|
215
|
+
if (start.offset === end.offset) return null;
|
|
216
|
+
const rects = editor.getRects().rangeRects(start.path, start.offset, end.offset);
|
|
217
|
+
return rects.length ? belowRight(rects) : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** The bar opens like a menu: below the selection's last line and to the right of where it
|
|
221
|
+
* ends, never over the text. The clamp above pushes it left at the viewport edge and flips it
|
|
222
|
+
* above the first line when there is no room below. */
|
|
223
|
+
function belowRight(rects: DOMRect[]): Placement | null {
|
|
224
|
+
const first = rects[0];
|
|
225
|
+
const last = rects[rects.length - 1];
|
|
226
|
+
// Scrolled out from under the host's chrome or off the bottom: nothing to anchor to.
|
|
227
|
+
if (last.bottom < topInset() || first.top > window.innerHeight) return null;
|
|
228
|
+
return { x: last.right + BAR_GAP, y: last.bottom + BAR_GAP, flipY: first.top };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// The id, not a synthesized chord: a host rebind moves the shortcut and leaves the button. A
|
|
232
|
+
// declined run means the bar's premise went stale under it, so the boolean hides the bar.
|
|
233
|
+
function fire(command: string, arg?: unknown): void {
|
|
234
|
+
turnIntoOpen = false;
|
|
235
|
+
if (!editor.runCommand(command, arg)) placement = null;
|
|
236
|
+
}
|
|
237
|
+
</script>
|
|
238
|
+
|
|
239
|
+
{#if shown}
|
|
240
|
+
<div
|
|
241
|
+
bind:this={barEl}
|
|
242
|
+
class="md-menu selection-toolbar"
|
|
243
|
+
data-testid="selection-toolbar"
|
|
244
|
+
style:left="{shown.x}px"
|
|
245
|
+
style:top="{shown.y}px"
|
|
246
|
+
role="toolbar"
|
|
247
|
+
aria-label={SELECTION_TOOLBAR_LABEL}
|
|
248
|
+
tabindex="-1"
|
|
249
|
+
onpointerleave={() => (turnIntoOpen = false)}
|
|
250
|
+
>
|
|
251
|
+
<div class="selection-toolbar-marks">
|
|
252
|
+
{#each MARKS as button (button.command)}
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
class="md-menu-item selection-toolbar-mark"
|
|
256
|
+
data-testid="toolbar-{button.command}"
|
|
257
|
+
title={button.title}
|
|
258
|
+
disabled={declined.has(button.command)}
|
|
259
|
+
aria-pressed={active.has(button.command)}
|
|
260
|
+
onmousedown={(e) => e.preventDefault()}
|
|
261
|
+
onclick={() => fire(button.command)}
|
|
262
|
+
>
|
|
263
|
+
<MenuIcon name={button.icon} size={button.size} />
|
|
264
|
+
</button>
|
|
265
|
+
{/each}
|
|
266
|
+
</div>
|
|
267
|
+
<div class="md-menu-divider" aria-hidden="true"></div>
|
|
268
|
+
{#if offersHeading}
|
|
269
|
+
<div
|
|
270
|
+
class="selection-toolbar-row"
|
|
271
|
+
role="presentation"
|
|
272
|
+
onpointerenter={() => (turnIntoOpen = true)}
|
|
273
|
+
>
|
|
274
|
+
<button
|
|
275
|
+
type="button"
|
|
276
|
+
class="md-menu-item"
|
|
277
|
+
data-testid="toolbar-set-heading"
|
|
278
|
+
aria-haspopup="menu"
|
|
279
|
+
aria-expanded={turnIntoOpen}
|
|
280
|
+
onmousedown={(e) => e.preventDefault()}
|
|
281
|
+
onclick={() => (turnIntoOpen = !turnIntoOpen)}
|
|
282
|
+
>
|
|
283
|
+
<span class="md-menu-icon"><MenuIcon name="heading" size={14} /></span>
|
|
284
|
+
<span class="selection-toolbar-label">Set heading</span>
|
|
285
|
+
<span class="md-menu-icon"><MenuIcon name="chevron-right" size={13} /></span>
|
|
286
|
+
</button>
|
|
287
|
+
{#if turnIntoOpen}
|
|
288
|
+
<div class="md-menu selection-toolbar-flyout" role="menu" {@attach keepFlyoutOnScreen}>
|
|
289
|
+
{#each TURN_INTO as option (option.level)}
|
|
290
|
+
{@const chosen = option.level === 0 ? blockKind === 'paragraph' : false}
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
class="md-menu-item"
|
|
294
|
+
role="menuitemradio"
|
|
295
|
+
data-testid="toolbar-set-heading-{option.level}"
|
|
296
|
+
aria-checked={chosen}
|
|
297
|
+
onmousedown={(e) => e.preventDefault()}
|
|
298
|
+
onclick={() => fire(TURN_INTO_COMMAND, option.level)}
|
|
299
|
+
>
|
|
300
|
+
<span class="selection-toolbar-label">{option.label}</span>
|
|
301
|
+
{#if chosen}<span class="md-menu-icon"><MenuIcon name="check" size={13} /></span
|
|
302
|
+
>{/if}
|
|
303
|
+
</button>
|
|
304
|
+
{/each}
|
|
305
|
+
</div>
|
|
306
|
+
{/if}
|
|
307
|
+
</div>
|
|
308
|
+
{/if}
|
|
309
|
+
{#each rows as row (row.command)}
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
class="md-menu-item"
|
|
313
|
+
data-testid="toolbar-{row.command}"
|
|
314
|
+
aria-pressed={active.has(row.command)}
|
|
315
|
+
onmousedown={(e) => e.preventDefault()}
|
|
316
|
+
onclick={() => fire(row.command)}
|
|
317
|
+
>
|
|
318
|
+
<span class="md-menu-icon"><MenuIcon name={row.icon} size={14} /></span>
|
|
319
|
+
<span class="selection-toolbar-label">{row.label}</span>
|
|
320
|
+
{#if active.has(row.command)}<span class="md-menu-icon"
|
|
321
|
+
><MenuIcon name="check" size={13} /></span
|
|
322
|
+
>{/if}
|
|
323
|
+
</button>
|
|
324
|
+
{/each}
|
|
325
|
+
<button
|
|
326
|
+
type="button"
|
|
327
|
+
class="md-menu-item"
|
|
328
|
+
data-testid="toolbar-copy"
|
|
329
|
+
onmousedown={(e) => e.preventDefault()}
|
|
330
|
+
onclick={copySelection}
|
|
331
|
+
>
|
|
332
|
+
<span class="md-menu-icon"><MenuIcon name="copy" size={14} /></span>
|
|
333
|
+
<span>Copy</span>
|
|
334
|
+
</button>
|
|
335
|
+
</div>
|
|
336
|
+
{/if}
|
|
337
|
+
|
|
338
|
+
<style>
|
|
339
|
+
/* Surface and rows are the shared `.md-menu` family (editor.css); only the marks row and the
|
|
340
|
+
flyout hang are this popover's own. */
|
|
341
|
+
.selection-toolbar {
|
|
342
|
+
z-index: 100;
|
|
343
|
+
min-width: 0;
|
|
344
|
+
width: calc(4 * 34px + 3 * 2px + 2 * 6px);
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
}
|
|
347
|
+
.selection-toolbar-marks {
|
|
348
|
+
display: flex;
|
|
349
|
+
align-items: center;
|
|
350
|
+
gap: 2px;
|
|
351
|
+
padding: 2px;
|
|
352
|
+
}
|
|
353
|
+
.selection-toolbar-mark {
|
|
354
|
+
flex: 0 0 34px;
|
|
355
|
+
justify-content: center;
|
|
356
|
+
width: 34px;
|
|
357
|
+
height: 30px;
|
|
358
|
+
padding: 0;
|
|
359
|
+
color: var(--color-ui-muted, #8f8f89);
|
|
360
|
+
}
|
|
361
|
+
.selection-toolbar-mark:hover:not(:disabled),
|
|
362
|
+
.selection-toolbar-mark[aria-pressed='true'] {
|
|
363
|
+
color: var(--color-text-primary, #e8e8e5);
|
|
364
|
+
}
|
|
365
|
+
/* The marks are serif letters; a Lucide outline beside them needs a heavier stroke to weigh
|
|
366
|
+
the same. */
|
|
367
|
+
.selection-toolbar-mark :global(svg) {
|
|
368
|
+
stroke-width: 2.2;
|
|
369
|
+
}
|
|
370
|
+
.selection-toolbar-row {
|
|
371
|
+
position: relative;
|
|
372
|
+
}
|
|
373
|
+
.selection-toolbar-label {
|
|
374
|
+
flex: 1;
|
|
375
|
+
overflow: hidden;
|
|
376
|
+
text-overflow: ellipsis;
|
|
377
|
+
}
|
|
378
|
+
.selection-toolbar-flyout {
|
|
379
|
+
position: absolute;
|
|
380
|
+
left: 100%;
|
|
381
|
+
top: -4px;
|
|
382
|
+
margin-left: 4px;
|
|
383
|
+
min-width: 150px;
|
|
384
|
+
}
|
|
385
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's own formatting popover over a selection, in Notion's shape: a row of the
|
|
3
|
+
* marks, then labelled rows for what acts on the selection as a whole. Built on the same
|
|
4
|
+
* doors a host's chrome would use (`selectionChange`, the rect API, the command door), so
|
|
5
|
+
* the consumer-guide recipe and this component cannot disagree about what a selection
|
|
6
|
+
* toolbar may read.
|
|
7
|
+
*/
|
|
8
|
+
import type { EditorInstance } from '../../editor-props';
|
|
9
|
+
type ToolbarEditor = Pick<EditorInstance, 'getSelection' | 'getRects' | 'getBlockKindAt' | 'runCommand' | 'canRunCommand' | 'isCommandActive' | 'getEvents'>;
|
|
10
|
+
type $$ComponentProps = {
|
|
11
|
+
editor: ToolbarEditor;
|
|
12
|
+
root: HTMLElement | undefined;
|
|
13
|
+
};
|
|
14
|
+
declare const SelectionToolbar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
15
|
+
type SelectionToolbar = ReturnType<typeof SelectionToolbar>;
|
|
16
|
+
export default SelectionToolbar;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clipboard rows a menu offers where Ctrl+X/C/V would go: cut and copy through the
|
|
3
|
+
* document's own command (so the editor's copy handlers write what they always write), paste by
|
|
4
|
+
* handing the clipboard's text to the focused surface as a paste event — the same road a real
|
|
5
|
+
* Ctrl+V takes — either as it is, or escaped so Markdown syntax in it lands as literal text.
|
|
6
|
+
*/
|
|
7
|
+
export type ClipboardAction = 'cut' | 'copy' | 'paste' | 'paste-plain';
|
|
8
|
+
/** Markdown-significant bytes, escaped so pasted text reads back exactly as pasted. */
|
|
9
|
+
export declare function escapeMarkdownLiteral(text: string): string;
|
|
10
|
+
/** Dispatch `text` to `surface` as a paste, so the editor's paste pipeline handles it. */
|
|
11
|
+
export declare function pasteTextInto(surface: HTMLElement, text: string): void;
|
|
12
|
+
export declare function runClipboardAction(action: ClipboardAction, surface: HTMLElement | null): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clipboard rows a menu offers where Ctrl+X/C/V would go: cut and copy through the
|
|
3
|
+
* document's own command (so the editor's copy handlers write what they always write), paste by
|
|
4
|
+
* handing the clipboard's text to the focused surface as a paste event — the same road a real
|
|
5
|
+
* Ctrl+V takes — either as it is, or escaped so Markdown syntax in it lands as literal text.
|
|
6
|
+
*/
|
|
7
|
+
/** Markdown-significant bytes, escaped so pasted text reads back exactly as pasted. */
|
|
8
|
+
export function escapeMarkdownLiteral(text) {
|
|
9
|
+
return text
|
|
10
|
+
.replace(/[\\`*_[\]<>|~]/g, '\\$&')
|
|
11
|
+
.replace(/^([ \t]*)([#>+-])/gm, '$1\\$2')
|
|
12
|
+
.replace(/^([ \t]*)(\d+)([.)])/gm, '$1$2\\$3');
|
|
13
|
+
}
|
|
14
|
+
/** Dispatch `text` to `surface` as a paste, so the editor's paste pipeline handles it. */
|
|
15
|
+
export function pasteTextInto(surface, text) {
|
|
16
|
+
const data = new DataTransfer();
|
|
17
|
+
data.setData('text/plain', text);
|
|
18
|
+
surface.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
|
|
19
|
+
}
|
|
20
|
+
export async function runClipboardAction(action, surface) {
|
|
21
|
+
switch (action) {
|
|
22
|
+
case 'cut':
|
|
23
|
+
case 'copy':
|
|
24
|
+
document.execCommand(action);
|
|
25
|
+
return;
|
|
26
|
+
case 'paste':
|
|
27
|
+
case 'paste-plain': {
|
|
28
|
+
if (!surface)
|
|
29
|
+
return;
|
|
30
|
+
let text;
|
|
31
|
+
try {
|
|
32
|
+
text = await navigator.clipboard.readText();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!text)
|
|
38
|
+
return;
|
|
39
|
+
pasteTextInto(surface, action === 'paste-plain' ? escapeMarkdownLiteral(text) : text);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every non-prose block's context menu offers after the kind's own rows: copy it (its
|
|
3
|
+
* Markdown), replace it with the clipboard, remove it — each named for what the block is
|
|
4
|
+
* ("Remove code block", "Copy image"). Plain, not red: every one is a Ctrl+Z away. Prose blocks
|
|
5
|
+
* are the page's background and keep the browser's own menu (`Editor.svelte`).
|
|
6
|
+
*/
|
|
7
|
+
import type { NodeView } from '../../core/node-views';
|
|
8
|
+
/** The noun the menu uses for a block: a paragraph holding nothing but an image is "image". */
|
|
9
|
+
export declare function blockNoun(node: NodeView): string;
|
|
10
|
+
/** Prose is the page's background: it takes no block menu of its own. An image alone in a
|
|
11
|
+
* paragraph is a block, not prose. */
|
|
12
|
+
export declare function isProseBackground(node: NodeView): boolean;
|
|
13
|
+
/** Test-only. */
|
|
14
|
+
export declare function __resetDefaultContextActionsForTests(): void;
|
|
15
|
+
export declare function registerDefaultContextActions(): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { EVERY_KIND, registerBlockContextActions } from '../../schema/context-actions';
|
|
2
|
+
import { applyPasteTransforms } from '../../tree-operations/paste/paste-transforms';
|
|
3
|
+
const KIND_NAMES = {
|
|
4
|
+
paragraph: 'paragraph',
|
|
5
|
+
heading: 'heading',
|
|
6
|
+
setextHeading: 'heading',
|
|
7
|
+
fencedCode: 'code block',
|
|
8
|
+
indentedCode: 'code block',
|
|
9
|
+
mathBlock: 'math block',
|
|
10
|
+
mathFence: 'math block',
|
|
11
|
+
table: 'table',
|
|
12
|
+
blockquote: 'quote',
|
|
13
|
+
list: 'list',
|
|
14
|
+
thematicBreak: 'divider',
|
|
15
|
+
html: 'HTML block',
|
|
16
|
+
linkReferenceDefinition: 'link definition'
|
|
17
|
+
};
|
|
18
|
+
const IMAGE_ONLY = /^\s*!\[[^\]]*\]\([^)]*\)\s*$/;
|
|
19
|
+
const PROSE_KINDS = new Set(['paragraph', 'heading', 'setextHeading']);
|
|
20
|
+
/** The noun the menu uses for a block: a paragraph holding nothing but an image is "image". */
|
|
21
|
+
export function blockNoun(node) {
|
|
22
|
+
if (node.kind === 'paragraph' && IMAGE_ONLY.test(node.raw))
|
|
23
|
+
return 'image';
|
|
24
|
+
return KIND_NAMES[node.kind] ?? 'block';
|
|
25
|
+
}
|
|
26
|
+
/** Prose is the page's background: it takes no block menu of its own. An image alone in a
|
|
27
|
+
* paragraph is a block, not prose. */
|
|
28
|
+
export function isProseBackground(node) {
|
|
29
|
+
return PROSE_KINDS.has(node.kind) && blockNoun(node) !== 'image';
|
|
30
|
+
}
|
|
31
|
+
let registered = false;
|
|
32
|
+
/** Test-only. */
|
|
33
|
+
export function __resetDefaultContextActionsForTests() {
|
|
34
|
+
registered = false;
|
|
35
|
+
}
|
|
36
|
+
export function registerDefaultContextActions() {
|
|
37
|
+
if (registered)
|
|
38
|
+
return;
|
|
39
|
+
registered = true;
|
|
40
|
+
registerBlockContextActions(EVERY_KIND, (node) => {
|
|
41
|
+
const noun = blockNoun(node);
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
id: 'block.copy',
|
|
45
|
+
label: `Copy ${noun}`,
|
|
46
|
+
icon: 'copy',
|
|
47
|
+
run: () => navigator.clipboard.writeText(node.raw)
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'block.replace',
|
|
51
|
+
label: 'Replace with clipboard',
|
|
52
|
+
icon: 'clipboard',
|
|
53
|
+
run: async (ctx) => {
|
|
54
|
+
let text;
|
|
55
|
+
try {
|
|
56
|
+
text = await navigator.clipboard.readText();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (text.trim() === '')
|
|
62
|
+
return;
|
|
63
|
+
// The paste transforms first, as any paste gets, so a plugin's rewrite applies.
|
|
64
|
+
const md = applyPasteTransforms(text);
|
|
65
|
+
await ctx.replaceRaw(md.endsWith('\n') ? md : md + '\n');
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'block.remove',
|
|
70
|
+
label: `Remove ${noun}`,
|
|
71
|
+
icon: 'trash',
|
|
72
|
+
run: (ctx) => ctx.deleteBlock()
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte attachment for a menu flyout: measures the surface once it is in the DOM and moves it
|
|
3
|
+
* to stay inside the viewport (`flyoutPlacement`). The element's own CSS hangs it off the row's
|
|
4
|
+
* right edge; this writes the correction inline, so the rule lives in one place for every menu.
|
|
5
|
+
*/
|
|
6
|
+
export declare function keepFlyoutOnScreen(node: HTMLElement): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte attachment for a menu flyout: measures the surface once it is in the DOM and moves it
|
|
3
|
+
* to stay inside the viewport (`flyoutPlacement`). The element's own CSS hangs it off the row's
|
|
4
|
+
* right edge; this writes the correction inline, so the rule lives in one place for every menu.
|
|
5
|
+
*/
|
|
6
|
+
import { flyoutPlacement } from '../blocks/table/table-menu-model';
|
|
7
|
+
const GAP = 4;
|
|
8
|
+
export function keepFlyoutOnScreen(node) {
|
|
9
|
+
const parentMenu = node.parentElement?.closest('.md-menu');
|
|
10
|
+
if (!parentMenu)
|
|
11
|
+
return;
|
|
12
|
+
const rect = node.getBoundingClientRect();
|
|
13
|
+
const { dy, flip } = flyoutPlacement(rect, parentMenu.getBoundingClientRect(), {
|
|
14
|
+
width: window.innerWidth,
|
|
15
|
+
height: window.innerHeight
|
|
16
|
+
});
|
|
17
|
+
if (dy !== 0)
|
|
18
|
+
node.style.top = `${node.offsetTop + dy}px`;
|
|
19
|
+
if (flip) {
|
|
20
|
+
node.style.left = 'auto';
|
|
21
|
+
node.style.right = '100%';
|
|
22
|
+
node.style.marginLeft = '0';
|
|
23
|
+
node.style.marginRight = `${GAP}px`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -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 { type PresentationMode } from '../../presentation-mode';
|
|
10
11
|
import { type InlineMarkKind } from '../../schema/inline-construct-policy';
|
|
@@ -48,3 +49,10 @@ export declare function inlineFormatsCovering(edit: InlineFormatEdit, candidates
|
|
|
48
49
|
* a toolbar's buttons arrive as N separate calls, and only the caller knows they are one repaint.
|
|
49
50
|
* One slot, since the previous state is dead the moment a keystroke or the caret moves. */
|
|
50
51
|
export declare function createInlineFormatActiveMemo(): (edit: InlineFormatEdit, format: InlineMarkKind) => boolean;
|
|
52
|
+
/** A range minus its boundary whitespace, null once nothing is left. One home for the trim, so the
|
|
53
|
+
* wrap here and the cross-block decomposition that trims before it ever asks
|
|
54
|
+
* (`selection/cross-block/format-range.ts`) cannot disagree about where a run may close. */
|
|
55
|
+
export declare function withoutBoundaryWhitespace(display: string, from: number, to: number): {
|
|
56
|
+
start: number;
|
|
57
|
+
end: number;
|
|
58
|
+
} | null;
|