edytor 0.0.11 → 0.1.0-next.16
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 +77 -197
- package/dist/block/block.svelte.d.ts +156 -69
- package/dist/block/block.svelte.js +324 -355
- package/dist/block/block.utils.d.ts +191 -24
- package/dist/block/block.utils.js +327 -460
- package/dist/block/contentRange.d.ts +4 -0
- package/dist/block/contentRange.js +20 -0
- package/dist/block/inlineBlock.svelte.d.ts +26 -19
- package/dist/block/inlineBlock.svelte.js +91 -39
- package/dist/clipboard/clipboard.d.ts +4 -0
- package/dist/clipboard/clipboard.js +4 -0
- package/dist/clipboard/createClipboardFragment.d.ts +3 -0
- package/dist/clipboard/createClipboardFragment.js +103 -0
- package/dist/clipboard/fragmentData.d.ts +6 -0
- package/dist/clipboard/fragmentData.js +74 -0
- package/dist/clipboard/htmlFlow.d.ts +22 -0
- package/dist/clipboard/htmlFlow.js +167 -0
- package/dist/clipboard/insertClipboardFragment.d.ts +18 -0
- package/dist/clipboard/insertClipboardFragment.js +48 -0
- package/dist/clipboard/serializeClipboardFragment.d.ts +7 -0
- package/dist/clipboard/serializeClipboardFragment.js +53 -0
- package/dist/clipboard/textBlocks.d.ts +23 -0
- package/dist/clipboard/textBlocks.js +103 -0
- package/dist/clipboard/types.d.ts +20 -0
- package/dist/clipboard/types.js +2 -0
- package/dist/cloudflare/DocumentRoom.d.ts +586 -0
- package/dist/cloudflare/DocumentRoom.js +1706 -0
- package/dist/cloudflare/index.d.ts +8 -0
- package/dist/cloudflare/index.js +8 -0
- package/dist/cloudflare/routeDocumentSocket.d.ts +33 -0
- package/dist/cloudflare/routeDocumentSocket.js +51 -0
- package/dist/collaboration/RemoteSelections.svelte +94 -0
- package/dist/collaboration/RemoteSelections.svelte.d.ts +7 -0
- package/dist/collaboration/awarenessSelection.d.ts +127 -0
- package/dist/collaboration/awarenessSelection.js +199 -0
- package/dist/collaboration/documentSync.d.ts +20 -0
- package/dist/collaboration/documentSync.js +13 -0
- package/dist/collaboration/index.d.ts +3 -0
- package/dist/collaboration/index.js +3 -0
- package/dist/collaboration/providers.d.ts +13 -0
- package/dist/collaboration/providers.js +21 -0
- package/dist/collaboration/remoteSelection.d.ts +21 -0
- package/dist/collaboration/remoteSelection.js +159 -0
- package/dist/components/Block.svelte +223 -15
- package/dist/components/Block.svelte.d.ts +4 -2
- package/dist/components/Content.svelte +128 -32
- package/dist/components/Content.svelte.d.ts +6 -2
- package/dist/components/Edytor.svelte +308 -110
- package/dist/components/Edytor.svelte.d.ts +64 -20
- package/dist/components/InlineBlock.svelte +38 -7
- package/dist/components/InlineBlock.svelte.d.ts +5 -2
- package/dist/components/Mark.svelte +29 -13
- package/dist/components/Mark.svelte.d.ts +4 -4
- package/dist/components/Suggestion.svelte +48 -0
- package/dist/components/Suggestion.svelte.d.ts +7 -0
- package/dist/components/SuggestionRanges.svelte +58 -0
- package/dist/components/SuggestionRanges.svelte.d.ts +7 -0
- package/dist/components/Text.svelte +107 -14
- package/dist/components/Text.svelte.d.ts +8 -1
- package/dist/constants.d.ts +0 -2
- package/dist/constants.js +0 -2
- package/dist/crdt/admission.d.ts +141 -0
- package/dist/crdt/admission.js +147 -0
- package/dist/crdt/anchors.d.ts +47 -0
- package/dist/crdt/anchors.js +42 -0
- package/dist/crdt/attribution/attribution.d.ts +169 -0
- package/dist/crdt/attribution/attribution.js +136 -0
- package/dist/crdt/attribution/block.d.ts +200 -0
- package/dist/crdt/attribution/block.js +327 -0
- package/dist/crdt/attribution/index.d.ts +9 -0
- package/dist/crdt/attribution/index.js +9 -0
- package/dist/crdt/data.d.ts +81 -0
- package/dist/crdt/data.js +383 -0
- package/dist/crdt/document.d.ts +590 -0
- package/dist/crdt/document.js +1160 -0
- package/dist/crdt/edytor-doc.d.ts +752 -0
- package/dist/crdt/edytor-doc.js +2382 -0
- package/dist/crdt/engine-api.d.ts +153 -0
- package/dist/crdt/engine.d.ts +37 -0
- package/dist/crdt/engine.js +50 -0
- package/dist/crdt/flow.d.ts +104 -0
- package/dist/crdt/flow.js +229 -0
- package/dist/crdt/index.d.ts +630 -0
- package/dist/crdt/index.js +168 -0
- package/dist/crdt/migration/legacy-schema.d.ts +61 -0
- package/dist/crdt/migration/legacy-schema.js +149 -0
- package/dist/crdt/migration/migrate.d.ts +67 -0
- package/dist/crdt/migration/migrate.js +219 -0
- package/dist/crdt/nodes.d.ts +120 -0
- package/dist/crdt/nodes.js +56 -0
- package/dist/crdt/placement/model.d.ts +383 -0
- package/dist/crdt/placement/model.js +811 -0
- package/dist/crdt/placement/rank.d.ts +57 -0
- package/dist/crdt/placement/rank.js +209 -0
- package/dist/crdt/protocols/auth.d.ts +28 -0
- package/dist/crdt/protocols/auth.js +43 -0
- package/dist/crdt/protocols/awareness.d.ts +87 -0
- package/dist/crdt/protocols/awareness.js +271 -0
- package/dist/crdt/protocols/envelope.d.ts +75 -0
- package/dist/crdt/protocols/envelope.js +89 -0
- package/dist/crdt/protocols/observable.d.ts +19 -0
- package/dist/crdt/protocols/observable.js +26 -0
- package/dist/crdt/protocols/sync.d.ts +82 -0
- package/dist/crdt/protocols/sync.js +288 -0
- package/dist/crdt/providers/container.d.ts +23 -0
- package/dist/crdt/providers/container.js +64 -0
- package/dist/crdt/providers/index.d.ts +282 -0
- package/dist/crdt/providers/index.js +118 -0
- package/dist/crdt/providers/indexeddb.d.ts +196 -0
- package/dist/crdt/providers/indexeddb.js +332 -0
- package/dist/crdt/providers/room.d.ts +193 -0
- package/dist/crdt/providers/room.js +444 -0
- package/dist/crdt/providers/websocket.d.ts +223 -0
- package/dist/crdt/providers/websocket.js +601 -0
- package/dist/crdt/rand.d.ts +22 -0
- package/dist/crdt/rand.js +22 -0
- package/dist/crdt/rangeDelete.d.ts +85 -0
- package/dist/crdt/rangeDelete.js +126 -0
- package/dist/crdt/schema.d.ts +96 -0
- package/dist/crdt/schema.js +79 -0
- package/dist/crdt/semantics.d.ts +81 -0
- package/dist/crdt/semantics.js +57 -0
- package/dist/crdt/structs.d.ts +106 -0
- package/dist/crdt/structs.js +119 -0
- package/dist/crdt/text/deletes.d.ts +23 -0
- package/dist/crdt/text/deletes.js +608 -0
- package/dist/crdt/text/model.d.ts +280 -0
- package/dist/crdt/text/model.js +554 -0
- package/dist/crdt/text/runs.d.ts +212 -0
- package/dist/crdt/text/runs.js +1142 -0
- package/dist/crdt/vendor/yjs/API-NOTES.md +59 -0
- package/dist/crdt/vendor/yjs/LICENSE +23 -0
- package/dist/crdt/vendor/yjs/UPSTREAM.md +653 -0
- package/dist/crdt/vendor/yjs/dts/global.d.ts +52 -0
- package/dist/crdt/vendor/yjs/dts/index.d.ts +25 -0
- package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +38 -0
- package/dist/crdt/vendor/yjs/dts/structs/GC.d.ts +25 -0
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +748 -0
- package/dist/crdt/vendor/yjs/dts/structs/Skip.d.ts +30 -0
- package/dist/crdt/vendor/yjs/dts/utils/BlockSet.d.ts +34 -0
- package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +198 -0
- package/dist/crdt/vendor/yjs/dts/utils/EventHandler.d.ts +17 -0
- package/dist/crdt/vendor/yjs/dts/utils/ID.d.ts +20 -0
- package/dist/crdt/vendor/yjs/dts/utils/RangeCursor.d.ts +198 -0
- package/dist/crdt/vendor/yjs/dts/utils/RelativePosition.d.ts +80 -0
- package/dist/crdt/vendor/yjs/dts/utils/StructStore.d.ts +61 -0
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +129 -0
- package/dist/crdt/vendor/yjs/dts/utils/UndoManager.d.ts +205 -0
- package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +170 -0
- package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +164 -0
- package/dist/crdt/vendor/yjs/dts/utils/YEvent.d.ts +93 -0
- package/dist/crdt/vendor/yjs/dts/utils/content-helper.d.ts +1 -0
- package/dist/crdt/vendor/yjs/dts/utils/encoding-helpers.d.ts +5 -0
- package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +26 -0
- package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +407 -0
- package/dist/crdt/vendor/yjs/dts/utils/isParentOf.d.ts +1 -0
- package/dist/crdt/vendor/yjs/dts/utils/meta.d.ts +25 -0
- package/dist/crdt/vendor/yjs/dts/utils/renderer-helpers.d.ts +98 -0
- package/dist/crdt/vendor/yjs/dts/utils/schemas.d.ts +2 -0
- package/dist/crdt/vendor/yjs/dts/utils/transaction-helpers.d.ts +15 -0
- package/dist/crdt/vendor/yjs/dts/utils/updates.d.ts +70 -0
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +655 -0
- package/dist/crdt/vendor/yjs/global.d.ts +52 -0
- package/dist/crdt/vendor/yjs/src/index.js +64 -0
- package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +64 -0
- package/dist/crdt/vendor/yjs/src/structs/GC.js +75 -0
- package/dist/crdt/vendor/yjs/src/structs/Item.js +1538 -0
- package/dist/crdt/vendor/yjs/src/structs/Skip.js +72 -0
- package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +291 -0
- package/dist/crdt/vendor/yjs/src/utils/Doc.js +255 -0
- package/dist/crdt/vendor/yjs/src/utils/EventHandler.js +75 -0
- package/dist/crdt/vendor/yjs/src/utils/ID.js +59 -0
- package/dist/crdt/vendor/yjs/src/utils/RangeCursor.js +407 -0
- package/dist/crdt/vendor/yjs/src/utils/RelativePosition.js +301 -0
- package/dist/crdt/vendor/yjs/src/utils/StructStore.js +134 -0
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +458 -0
- package/dist/crdt/vendor/yjs/src/utils/UndoManager.js +554 -0
- package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +284 -0
- package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +323 -0
- package/dist/crdt/vendor/yjs/src/utils/YEvent.js +158 -0
- package/dist/crdt/vendor/yjs/src/utils/content-helper.js +0 -0
- package/dist/crdt/vendor/yjs/src/utils/encoding-helpers.js +110 -0
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +662 -0
- package/dist/crdt/vendor/yjs/src/utils/ids.js +1504 -0
- package/dist/crdt/vendor/yjs/src/utils/isParentOf.js +19 -0
- package/dist/crdt/vendor/yjs/src/utils/meta.js +74 -0
- package/dist/crdt/vendor/yjs/src/utils/renderer-helpers.js +186 -0
- package/dist/crdt/vendor/yjs/src/utils/schemas.js +3 -0
- package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +391 -0
- package/dist/crdt/vendor/yjs/src/utils/updates.js +264 -0
- package/dist/crdt/vendor/yjs/src/ynode.js +2897 -0
- package/dist/dnd/pragmatic.d.ts +22 -0
- package/dist/dnd/pragmatic.js +26 -0
- package/dist/edytor.svelte.d.ts +323 -54
- package/dist/edytor.svelte.js +771 -197
- package/dist/edytor.utils.d.ts +49 -2
- package/dist/edytor.utils.js +65 -76
- package/dist/events/beforeInputCommands.d.ts +21 -0
- package/dist/events/beforeInputCommands.js +328 -0
- package/dist/events/beforeInputDeleteCommands.d.ts +3 -0
- package/dist/events/beforeInputDeleteCommands.js +245 -0
- package/dist/events/dataTransferPayload.d.ts +6 -0
- package/dist/events/dataTransferPayload.js +9 -0
- package/dist/events/domTextOffset.d.ts +12 -0
- package/dist/events/domTextOffset.js +52 -0
- package/dist/events/events.utils.d.ts +3 -0
- package/dist/events/events.utils.js +13 -0
- package/dist/events/nativeInteractiveControl.d.ts +31 -0
- package/dist/events/nativeInteractiveControl.js +98 -0
- package/dist/events/onBeforeInput.d.ts +10 -1
- package/dist/events/onBeforeInput.js +449 -330
- package/dist/events/onCopy.d.ts +7 -0
- package/dist/events/onCopy.js +30 -0
- package/dist/events/onCut.d.ts +8 -0
- package/dist/events/onCut.js +13 -0
- package/dist/events/onDrop.d.ts +16 -0
- package/dist/events/onDrop.js +158 -0
- package/dist/events/onFocus.d.ts +4 -0
- package/dist/events/onFocus.js +166 -0
- package/dist/events/onInput.d.ts +20 -0
- package/dist/events/onInput.js +249 -0
- package/dist/events/onKeyDown.js +191 -11
- package/dist/events/onPaste.d.ts +3 -0
- package/dist/events/onPaste.js +108 -0
- package/dist/events/wordBoundary.d.ts +2 -0
- package/dist/events/wordBoundary.js +58 -0
- package/dist/index.d.ts +38 -1
- package/dist/index.js +36 -1
- package/dist/kinds.d.ts +100 -0
- package/dist/kinds.js +198 -0
- package/dist/plugins/arrowMove/arrowMove.d.ts +1 -1
- package/dist/plugins/arrowMove/arrowMove.js +30 -65
- package/dist/plugins/blockHandles/BlockHandle.svelte +152 -0
- package/dist/plugins/blockHandles/BlockHandle.svelte.d.ts +12 -0
- package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +208 -0
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +823 -0
- package/dist/plugins/blockHandles/BlockHandles.svelte +151 -0
- package/dist/plugins/blockHandles/BlockHandles.svelte.d.ts +20 -0
- package/dist/plugins/blockHandles/autoScroll.d.ts +8 -0
- package/dist/plugins/blockHandles/autoScroll.js +25 -0
- package/dist/plugins/blockHandles/blockHandlesPlugin.d.ts +37 -0
- package/dist/plugins/blockHandles/blockHandlesPlugin.js +100 -0
- package/dist/plugins/blockHandles/dragPreview.d.ts +33 -0
- package/dist/plugins/blockHandles/dragPreview.js +228 -0
- package/dist/plugins/blockMenu/BlockMenu.svelte +301 -0
- package/dist/plugins/blockMenu/BlockMenu.svelte.d.ts +9 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +102 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.js +245 -0
- package/dist/plugins/blockMenu/blockMenuPlugin.d.ts +11 -0
- package/dist/plugins/blockMenu/blockMenuPlugin.js +89 -0
- package/dist/plugins/code/CodePlugin.svelte +210 -163
- package/dist/plugins/code/CodePlugin.svelte.d.ts +1 -3
- package/dist/plugins/code/code.css +92 -0
- package/dist/plugins/icons.d.ts +2 -0
- package/dist/plugins/icons.js +31 -0
- package/dist/plugins/image/ImageEmpty.svelte +69 -0
- package/dist/plugins/image/ImageEmpty.svelte.d.ts +8 -0
- package/dist/plugins/image/ImagePlugin.svelte +74 -12
- package/dist/plugins/image/ImagePlugin.svelte.d.ts +15 -0
- package/dist/plugins/image/image.d.ts +2 -0
- package/dist/plugins/image/image.js +7 -0
- package/dist/plugins/index.d.ts +14 -1
- package/dist/plugins/index.js +10 -1
- package/dist/plugins/keepInView.d.ts +4 -0
- package/dist/plugins/keepInView.js +6 -0
- package/dist/plugins/markdownShortcuts.d.ts +3 -0
- package/dist/plugins/markdownShortcuts.js +104 -0
- package/dist/plugins/mention/MentionPlugin.svelte +28 -31
- package/dist/plugins/richtext/RichTextPlugin.svelte +411 -200
- package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +8 -7
- package/dist/plugins/richtext/richTextOperations.d.ts +53 -0
- package/dist/plugins/richtext/richTextOperations.js +200 -0
- package/dist/plugins/slashMenu/SlashMenu.svelte +297 -0
- package/dist/plugins/slashMenu/SlashMenu.svelte.d.ts +11 -0
- package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +56 -0
- package/dist/plugins/slashMenu/SlashMenuController.svelte.js +275 -0
- package/dist/plugins/slashMenu/slashMenuPlugin.d.ts +25 -0
- package/dist/plugins/slashMenu/slashMenuPlugin.js +105 -0
- package/dist/plugins/suggestions/SuggestionBars.svelte +142 -0
- package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +10 -0
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +28 -0
- package/dist/plugins/suggestions/suggestionsPlugin.js +53 -0
- package/dist/plugins/toolbar/Toolbar.svelte +341 -0
- package/dist/plugins/toolbar/Toolbar.svelte.d.ts +9 -0
- package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +85 -0
- package/dist/plugins/toolbar/ToolbarController.svelte.js +150 -0
- package/dist/plugins/toolbar/toolbarPlugin.d.ts +16 -0
- package/dist/plugins/toolbar/toolbarPlugin.js +40 -0
- package/dist/plugins.d.ts +249 -40
- package/dist/plugins.js +11 -5
- package/dist/selection/domSelection.d.ts +29 -0
- package/dist/selection/domSelection.js +230 -0
- package/dist/selection/replaceSelection.d.ts +143 -0
- package/dist/selection/replaceSelection.js +316 -0
- package/dist/selection/selection.svelte.d.ts +275 -37
- package/dist/selection/selection.svelte.js +1162 -331
- package/dist/selection/selection.utils.d.ts +35 -4
- package/dist/selection/selection.utils.js +309 -100
- package/dist/selection/visibility.d.ts +88 -0
- package/dist/selection/visibility.js +140 -0
- package/dist/session/attempt.d.ts +190 -0
- package/dist/session/attempt.js +188 -0
- package/dist/session/bindings.d.ts +16 -0
- package/dist/session/bindings.js +262 -0
- package/dist/session/commands.d.ts +158 -0
- package/dist/session/commands.js +601 -0
- package/dist/session/composition.svelte.d.ts +114 -0
- package/dist/session/composition.svelte.js +489 -0
- package/dist/session/editing/text.d.ts +33 -0
- package/dist/session/editing/text.js +36 -0
- package/dist/session/handles.d.ts +63 -0
- package/dist/session/handles.js +123 -0
- package/dist/session/history.d.ts +49 -0
- package/dist/session/history.js +131 -0
- package/dist/session/keymap.d.ts +42 -0
- package/dist/session/keymap.js +144 -0
- package/dist/session/moves.d.ts +32 -0
- package/dist/session/moves.js +116 -0
- package/dist/session/navigation.d.ts +51 -0
- package/dist/session/navigation.js +312 -0
- package/dist/session/props.d.ts +28 -0
- package/dist/session/props.js +162 -0
- package/dist/session/selection.d.ts +126 -0
- package/dist/session/selection.js +227 -0
- package/dist/session/suggestions.svelte.d.ts +125 -0
- package/dist/session/suggestions.svelte.js +329 -0
- package/dist/session/virtual.d.ts +33 -0
- package/dist/session/virtual.js +182 -0
- package/dist/surface/attributes.d.ts +36 -0
- package/dist/surface/attributes.js +143 -0
- package/dist/surface/cells.d.ts +103 -0
- package/dist/surface/cells.js +215 -0
- package/dist/surface/observer.svelte.d.ts +70 -0
- package/dist/surface/observer.svelte.js +969 -0
- package/dist/surface/overlay.d.ts +33 -0
- package/dist/surface/overlay.js +91 -0
- package/dist/surface/pin.svelte.d.ts +56 -0
- package/dist/surface/pin.svelte.js +108 -0
- package/dist/surface/projector.svelte.d.ts +69 -0
- package/dist/surface/projector.svelte.js +486 -0
- package/dist/text/deltas.d.ts +14 -3
- package/dist/text/deltas.js +27 -90
- package/dist/text/text.svelte.d.ts +43 -31
- package/dist/text/text.svelte.js +88 -69
- package/dist/text/text.utils.d.ts +7 -2
- package/dist/text/text.utils.js +121 -97
- package/dist/themes/notion.css +473 -0
- package/dist/utils/diffText.d.ts +15 -0
- package/dist/utils/diffText.js +26 -0
- package/dist/utils/json.d.ts +90 -3
- package/dist/utils/json.js +369 -2
- package/dist/utils.d.ts +3 -1
- package/dist/utils.js +2 -0
- package/package.json +112 -50
- package/dist/block/content.svelte.d.ts +0 -14
- package/dist/block/content.svelte.js +0 -40
- package/dist/components/ReadonlyEditor.svelte +0 -46
- package/dist/components/ReadonlyEditor.svelte.d.ts +0 -4
- package/dist/components/readonlyElements.svelte.d.ts +0 -82
- package/dist/components/readonlyElements.svelte.js +0 -144
- package/dist/dnd.svelte.d.ts +0 -17
- package/dist/dnd.svelte.js +0 -125
- package/dist/hotkeys.d.ts +0 -27
- package/dist/hotkeys.js +0 -282
- package/dist/localProvider.d.ts +0 -82
- package/dist/localProvider.js +0 -267
- package/dist/plugins/code/prism.css +0 -141
- package/dist/utils/jsx.d.ts +0 -25
- package/dist/utils/jsx.js +0 -66
- package/dist/utils/serialize.js +0 -1
- /package/dist/{utils/serialize.d.ts → crdt/engine-api.js} +0 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
import { autoScrollFor } from './autoScroll.js';
|
|
2
|
+
import { noSelection, project } from '../../session/selection.js';
|
|
3
|
+
import { attachInstruction, draggable, dropTargetForElements, extractInstruction, setCustomNativeDragPreview } from '../../dnd/pragmatic.js';
|
|
4
|
+
import { HIDDEN, hidden } from '../../selection/visibility.js';
|
|
5
|
+
import { getSelectionBlocks, movable, outermost, selectMoved, shownText } from '../../selection/replaceSelection.js';
|
|
6
|
+
import { dragPreview } from './dragPreview.js';
|
|
7
|
+
const blockDragMimeType = 'application/x-edytor-block-id';
|
|
8
|
+
/** The DOM event a handle click dispatches on the editor when no `onActivate` is set. */
|
|
9
|
+
export const BLOCK_ACTIVATE_EVENT = 'edytor-block-activate';
|
|
10
|
+
/**
|
|
11
|
+
* The DOM event the `+` dispatches on the editor. A menu answers it with
|
|
12
|
+
* `preventDefault()` (the slash menu does), adds nothing until a row is
|
|
13
|
+
* picked, then calls `insert(then)`; unanswered, the `+` inserts at once.
|
|
14
|
+
*/
|
|
15
|
+
export const BLOCK_ADD_EVENT = 'edytor-block-add';
|
|
16
|
+
/** Alt+arrow on a handle: one relative step. */
|
|
17
|
+
const keyMoves = {
|
|
18
|
+
ArrowUp: 'up',
|
|
19
|
+
ArrowDown: 'down',
|
|
20
|
+
ArrowRight: 'in',
|
|
21
|
+
ArrowLeft: 'out'
|
|
22
|
+
};
|
|
23
|
+
/** The drop target data key of the row the hitbox measured (`rowAt`). */
|
|
24
|
+
const ROW = 'edytorRow';
|
|
25
|
+
const getOwnRowBottom = (node) => {
|
|
26
|
+
const rect = node.getBoundingClientRect();
|
|
27
|
+
// Block nodes wrap their children in the DOM; the parent's placement
|
|
28
|
+
// bands belong to its own row, ending where the first child begins.
|
|
29
|
+
const firstChild = node.querySelector('[data-edytor-block="true"]');
|
|
30
|
+
const childRect = firstChild?.getBoundingClientRect();
|
|
31
|
+
return childRect &&
|
|
32
|
+
childRect.height > 0 &&
|
|
33
|
+
childRect.top >= rect.top &&
|
|
34
|
+
childRect.top < rect.bottom
|
|
35
|
+
? childRect.top
|
|
36
|
+
: rect.bottom;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Whether the pointer is over the own row of a block other than `node` and
|
|
40
|
+
* its descendants. A container (a list) has no row of its own: its padding
|
|
41
|
+
* above its first item is none (`hasOwnRow`).
|
|
42
|
+
*/
|
|
43
|
+
const overOtherRow = (node, input, hasOwnRow) => {
|
|
44
|
+
const hit = node.ownerDocument.elementFromPoint?.(input.clientX, input.clientY);
|
|
45
|
+
const over = hit?.closest('[data-edytor-block="true"]');
|
|
46
|
+
if (!over || node.contains(over) || !hasOwnRow(over))
|
|
47
|
+
return false;
|
|
48
|
+
return input.clientY < getOwnRowBottom(over);
|
|
49
|
+
};
|
|
50
|
+
/** Whether no block follows `block` at any level: below it, the document ends. */
|
|
51
|
+
const endsDocument = (block) => {
|
|
52
|
+
for (let level = block; level && !level.isRoot; level = level.parent)
|
|
53
|
+
if (level.nextBlock)
|
|
54
|
+
return false;
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
57
|
+
/** A block's own row: its box down to where its first child begins. */
|
|
58
|
+
const ownRow = (node) => {
|
|
59
|
+
const rect = node.getBoundingClientRect();
|
|
60
|
+
return new DOMRect(rect.left, rect.top, rect.width, Math.max(0, getOwnRowBottom(node) - rect.top));
|
|
61
|
+
};
|
|
62
|
+
/** The first line of a block's own text (not a child's), or the block's box. */
|
|
63
|
+
const ownTextRow = (node) => {
|
|
64
|
+
const text = Array.from(node.querySelectorAll('[data-edytor-text="true"]')).find((element) => element.closest('[data-edytor-block="true"]') === node);
|
|
65
|
+
const row = text?.getClientRects()[0];
|
|
66
|
+
return row && row.height > 0 ? row : node.getBoundingClientRect();
|
|
67
|
+
};
|
|
68
|
+
/** Where a block's own text starts: its level's column. */
|
|
69
|
+
const column = (block) => ownTextRow(block.node).left;
|
|
70
|
+
/**
|
|
71
|
+
* One nesting step, a nested child's indent when the target has no visible
|
|
72
|
+
* child to measure: the `--edytor-nest-indent` the children container is
|
|
73
|
+
* indented by (in px), else its default.
|
|
74
|
+
*/
|
|
75
|
+
const NEST_INDENT = 24;
|
|
76
|
+
const nestIndent = (node) => {
|
|
77
|
+
const value = node.ownerDocument.defaultView
|
|
78
|
+
?.getComputedStyle(node)
|
|
79
|
+
.getPropertyValue('--edytor-nest-indent')
|
|
80
|
+
.trim();
|
|
81
|
+
return value?.endsWith('px') ? parseFloat(value) : NEST_INDENT;
|
|
82
|
+
};
|
|
83
|
+
/** Notion's drop bar: 4px of translucent blue. */
|
|
84
|
+
const DROP_INDICATOR_COLOR = 'rgba(35, 131, 226, 0.43)';
|
|
85
|
+
/** The nest backdrop's theming variable (its defaults are in `BlockHandle.svelte`). */
|
|
86
|
+
const BACKDROP_COLOR = '--edytor-drop-backdrop-color';
|
|
87
|
+
const BAR = 4;
|
|
88
|
+
/**
|
|
89
|
+
* Notion's nest backdrop: a soft rounded tint over the future parent's own
|
|
90
|
+
* row, drawn by the overlay layer under the drop line — never a style on the
|
|
91
|
+
* block, so rounded block styles and nesting cannot bend it. One per drag,
|
|
92
|
+
* made at its start so it fades in (`data-shown`) and out.
|
|
93
|
+
*/
|
|
94
|
+
const backdrop = (document) => {
|
|
95
|
+
const node = document.createElement('div');
|
|
96
|
+
node.dataset.edytorDropBackdrop = 'true';
|
|
97
|
+
node.setAttribute('aria-hidden', 'true');
|
|
98
|
+
return node;
|
|
99
|
+
};
|
|
100
|
+
/** How many blocks move: the badge on the drag preview and the drop indicator. */
|
|
101
|
+
const countBadge = (document, count) => {
|
|
102
|
+
const badge = document.createElement('span');
|
|
103
|
+
badge.dataset.edytorDragCount = 'true';
|
|
104
|
+
badge.textContent = String(count);
|
|
105
|
+
Object.assign(badge.style, {
|
|
106
|
+
position: 'absolute',
|
|
107
|
+
top: '-8px',
|
|
108
|
+
right: '-8px',
|
|
109
|
+
minWidth: '18px',
|
|
110
|
+
height: '18px',
|
|
111
|
+
padding: '0 5px',
|
|
112
|
+
boxSizing: 'border-box',
|
|
113
|
+
borderRadius: '9px',
|
|
114
|
+
background: 'rgb(35, 131, 226)',
|
|
115
|
+
color: 'white',
|
|
116
|
+
font: '600 11px/18px system-ui, sans-serif',
|
|
117
|
+
textAlign: 'center'
|
|
118
|
+
});
|
|
119
|
+
return badge;
|
|
120
|
+
};
|
|
121
|
+
export class BlockHandleController {
|
|
122
|
+
edytor;
|
|
123
|
+
options;
|
|
124
|
+
owner = {};
|
|
125
|
+
indicatorNode = null;
|
|
126
|
+
indicatorOverlay = null;
|
|
127
|
+
activeDropTarget = null;
|
|
128
|
+
activePlacement = null;
|
|
129
|
+
/** The indicator's measure in the overlay, while one is shown. */
|
|
130
|
+
offIndicator = null;
|
|
131
|
+
/**
|
|
132
|
+
* The nest backdrop: one per drag, in the overlay; `data-shown` while the
|
|
133
|
+
* placement nests (it fades out in place, then goes with the drag).
|
|
134
|
+
*/
|
|
135
|
+
backdrop = null;
|
|
136
|
+
/** Candidate drop targets; registered with the drag library only during our drag. */
|
|
137
|
+
targets = new Map();
|
|
138
|
+
registered = new Map();
|
|
139
|
+
/** The block whose handle is the source of the drag in progress (its handle stays mounted). */
|
|
140
|
+
dragging = $state(null);
|
|
141
|
+
/** Releases this drag's auto-scroll registrations (window and scrolling ancestors). */
|
|
142
|
+
scrolling = null;
|
|
143
|
+
/** The blocks the drag in progress moves (`dragBlocks` when it started). */
|
|
144
|
+
group = [];
|
|
145
|
+
/** The selection the drag in progress replaced when it started: its undo step restores it. */
|
|
146
|
+
held = null;
|
|
147
|
+
/** The selection a grip click replaced (`before`), while the one it made (`after`) stands. */
|
|
148
|
+
gripped = null;
|
|
149
|
+
constructor(edytor, options = { draggable: true }) {
|
|
150
|
+
this.edytor = edytor;
|
|
151
|
+
this.options = options;
|
|
152
|
+
}
|
|
153
|
+
get readonly() {
|
|
154
|
+
return this.edytor.readonly;
|
|
155
|
+
}
|
|
156
|
+
get draggable() {
|
|
157
|
+
return this.options.draggable;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Make `node` the block's grip: dragging (when draggable), a click that
|
|
161
|
+
* selects the block and opens its menu, and the Alt+arrow moves.
|
|
162
|
+
*/
|
|
163
|
+
grip = (block) => (node) => {
|
|
164
|
+
const click = (event) => {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
this.activateBlock(block, node);
|
|
168
|
+
};
|
|
169
|
+
const pointerdown = (event) => event.stopPropagation();
|
|
170
|
+
const keydown = (event) => this.handleKeyDown(event, block);
|
|
171
|
+
node.addEventListener('click', click);
|
|
172
|
+
node.addEventListener('pointerdown', pointerdown);
|
|
173
|
+
node.addEventListener('keydown', keydown);
|
|
174
|
+
const unregister = this.registerHandle(node, block);
|
|
175
|
+
return {
|
|
176
|
+
destroy: () => {
|
|
177
|
+
node.removeEventListener('click', click);
|
|
178
|
+
node.removeEventListener('pointerdown', pointerdown);
|
|
179
|
+
node.removeEventListener('keydown', keydown);
|
|
180
|
+
unregister();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Select the block — a block selection holding it stays; over a text
|
|
186
|
+
* range across blocks, the blocks it covers (`select`) — and open its
|
|
187
|
+
* menu.
|
|
188
|
+
*/
|
|
189
|
+
activateBlock(block, anchor) {
|
|
190
|
+
if (this.edytor.readonly || !block.movable)
|
|
191
|
+
return;
|
|
192
|
+
const { selection } = this.edytor;
|
|
193
|
+
const before = selection.value;
|
|
194
|
+
if (!selection.selectedBlocks.has(block))
|
|
195
|
+
this.select(block);
|
|
196
|
+
this.gripped = { before, after: selection.value };
|
|
197
|
+
if (this.options.onActivate)
|
|
198
|
+
this.options.onActivate({ block, anchor });
|
|
199
|
+
// Without a callback, a block menu plugin may answer the activation.
|
|
200
|
+
else
|
|
201
|
+
this.edytor.node?.dispatchEvent(new CustomEvent(BLOCK_ACTIVATE_EVENT, { detail: { block, anchor } }));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The `+`: offers the new block to a menu (`BLOCK_ADD_EVENT`), which adds
|
|
205
|
+
* it once the user picks what to insert; with none answering, adds it now
|
|
206
|
+
* (`BlockAddition.insert`). A user command: its own undo step, however
|
|
207
|
+
* soon it follows typing.
|
|
208
|
+
*/
|
|
209
|
+
addBlock(block, above = false, anchor = block.node ?? null) {
|
|
210
|
+
const { edytor } = this;
|
|
211
|
+
if (edytor.readonly || !block.parent)
|
|
212
|
+
return;
|
|
213
|
+
this.ungrip();
|
|
214
|
+
const insert = (then) => {
|
|
215
|
+
const { dispatcher, facade, selection, undoManager: um } = edytor;
|
|
216
|
+
const held = selection.value;
|
|
217
|
+
let refused;
|
|
218
|
+
edytor.expectInternalFocus();
|
|
219
|
+
edytor.node?.focus({ preventScroll: true });
|
|
220
|
+
// The addition and `then` are one transaction: one update, one undo
|
|
221
|
+
// step. Refused with nothing applied, the addition is taken back inside
|
|
222
|
+
// it, and the history captures nothing (its undo and redo steps stay).
|
|
223
|
+
const capture = um?.captureTransaction;
|
|
224
|
+
if (um && capture)
|
|
225
|
+
um.captureTransaction = (tr) => !refused && capture(tr);
|
|
226
|
+
let out;
|
|
227
|
+
try {
|
|
228
|
+
out = dispatcher.run('insertBlock', () => edytor.transact(() => {
|
|
229
|
+
const type = block.parent && edytor.defaultChild(block.parent);
|
|
230
|
+
if (!type)
|
|
231
|
+
return;
|
|
232
|
+
const spec = { block: { type } };
|
|
233
|
+
const reused = block.type === type && block.isEmpty;
|
|
234
|
+
const target = reused
|
|
235
|
+
? block
|
|
236
|
+
: above
|
|
237
|
+
? block.insertBlockBefore(spec)
|
|
238
|
+
: block.insertBlockAfter(spec);
|
|
239
|
+
// The caret is the model's: the projector shows it once the block mounts.
|
|
240
|
+
if (target?.firstText)
|
|
241
|
+
dispatcher.caret(target.firstText, 0);
|
|
242
|
+
if (!target || !then)
|
|
243
|
+
return;
|
|
244
|
+
const [added, before] = [facade.version, dispatcher.last];
|
|
245
|
+
const result = then();
|
|
246
|
+
// `then`'s own refusal (not one left from before), with nothing applied.
|
|
247
|
+
const last = dispatcher.last !== before ? dispatcher.last : null;
|
|
248
|
+
if (facade.version !== added || (result !== false && last?.status !== 'refused'))
|
|
249
|
+
return result;
|
|
250
|
+
refused =
|
|
251
|
+
last?.status === 'refused' ? last : { operation: 'insertBlock', status: 'refused' };
|
|
252
|
+
// The take-back of this command's own uncommitted write, not a user command.
|
|
253
|
+
if (!reused)
|
|
254
|
+
facade.apply(facade.prepare.deleteBlocks([target.id]));
|
|
255
|
+
// Still answered: the command scope settles a vetoed async command's rejection.
|
|
256
|
+
return result;
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
if (um && capture)
|
|
261
|
+
um.captureTransaction = capture;
|
|
262
|
+
}
|
|
263
|
+
if (!refused)
|
|
264
|
+
return out;
|
|
265
|
+
selection.select(held);
|
|
266
|
+
dispatcher.last = refused;
|
|
267
|
+
return false;
|
|
268
|
+
};
|
|
269
|
+
const detail = { block, anchor, insert };
|
|
270
|
+
const event = new CustomEvent(BLOCK_ADD_EVENT, { cancelable: true, detail });
|
|
271
|
+
if (edytor.node?.dispatchEvent(event) !== false)
|
|
272
|
+
insert();
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* A `+` while the block selection a grip click made still stands (its
|
|
276
|
+
* menu open, or closed leaving it, a divider's): that selection was the
|
|
277
|
+
* menu's, so the selection held before the grip comes back — for none,
|
|
278
|
+
* or a caret whose place is gone, a caret at the start of the grip
|
|
279
|
+
* block's first line — and the `+` holds that one (its menu's Escape
|
|
280
|
+
* gives it back).
|
|
281
|
+
*/
|
|
282
|
+
ungrip() {
|
|
283
|
+
const { gripped } = this;
|
|
284
|
+
this.gripped = null;
|
|
285
|
+
const { selection, facade } = this.edytor;
|
|
286
|
+
// Only while the grip's own selection value stands (any other selection write replaced it).
|
|
287
|
+
if (!gripped || selection.value !== gripped.after || gripped.after.kind !== 'blocks')
|
|
288
|
+
return;
|
|
289
|
+
const { before, after } = gripped;
|
|
290
|
+
if (before === after)
|
|
291
|
+
return;
|
|
292
|
+
if (before.kind !== 'none' && project(before, facade).start)
|
|
293
|
+
return selection.select(before);
|
|
294
|
+
const grip = this.edytor.idToBlock.get(after.ids[0]);
|
|
295
|
+
const text = grip && shownText(grip, 'first');
|
|
296
|
+
// A block with no text (a divider): no selection, as before the grip.
|
|
297
|
+
if (text)
|
|
298
|
+
this.edytor.dispatcher.caret(text, 0);
|
|
299
|
+
else
|
|
300
|
+
selection.select(noSelection);
|
|
301
|
+
}
|
|
302
|
+
registerHandle(element, block) {
|
|
303
|
+
if (!this.options.draggable)
|
|
304
|
+
return () => { };
|
|
305
|
+
return draggable({
|
|
306
|
+
element,
|
|
307
|
+
canDrag: () => !this.edytor.readonly && block.movable,
|
|
308
|
+
getInitialData: () => ({ owner: this.owner, blockId: block.id }),
|
|
309
|
+
getInitialDataForExternal: () => ({ [blockDragMimeType]: block.id }),
|
|
310
|
+
// The moved blocks are known, and drop targets exist, from the start of
|
|
311
|
+
// our drag (before any target is looked up).
|
|
312
|
+
onGenerateDragPreview: ({ nativeSetDragImage, location }) => {
|
|
313
|
+
this.dragging = block.id;
|
|
314
|
+
const group = (this.group = this.dragBlocks(block));
|
|
315
|
+
// A ghost of the moved blocks as they look (Notion), cloned now and
|
|
316
|
+
// removed with the drag library's container once the drag starts.
|
|
317
|
+
const root = this.edytor.node;
|
|
318
|
+
const nodes = group.flatMap((moved) => (moved.node ? [moved.node] : []));
|
|
319
|
+
if (nativeSetDragImage && root && nodes.length) {
|
|
320
|
+
const count = group.length;
|
|
321
|
+
const badge = count > 1 ? countBadge(element.ownerDocument, count) : undefined;
|
|
322
|
+
const preview = dragPreview(root, nodes, location.current.input, badge);
|
|
323
|
+
setCustomNativeDragPreview({
|
|
324
|
+
nativeSetDragImage,
|
|
325
|
+
getOffset: preview.offset,
|
|
326
|
+
render: ({ container }) => container.append(preview.element)
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
for (const [node, target] of this.targets)
|
|
330
|
+
this.registerDropTarget(node, target);
|
|
331
|
+
this.scrolling?.();
|
|
332
|
+
this.scrolling = root ? autoScrollFor(root, (data) => data.owner === this.owner) : null;
|
|
333
|
+
this.backdrop?.remove();
|
|
334
|
+
this.backdrop = backdrop(element.ownerDocument);
|
|
335
|
+
this.edytor.overlay.layer?.prepend(this.backdrop);
|
|
336
|
+
},
|
|
337
|
+
// The moved blocks show selected while they move (a text range becomes their
|
|
338
|
+
// block selection); the drop's undo step restores the selection held before.
|
|
339
|
+
onDragStart: () => {
|
|
340
|
+
const { selectedBlocks, value } = this.edytor.selection;
|
|
341
|
+
if (this.group.every((moved) => selectedBlocks.has(moved)))
|
|
342
|
+
return;
|
|
343
|
+
this.held = value;
|
|
344
|
+
this.select(block);
|
|
345
|
+
},
|
|
346
|
+
// PDD notifies the source before drop targets. Keep the shown
|
|
347
|
+
// placement and the targets until the target has committed its move.
|
|
348
|
+
onDrop: () => queueMicrotask(() => {
|
|
349
|
+
this.clearIndicator();
|
|
350
|
+
this.backdrop?.remove();
|
|
351
|
+
this.backdrop = null;
|
|
352
|
+
this.scrolling?.();
|
|
353
|
+
this.scrolling = null;
|
|
354
|
+
for (const off of this.registered.values())
|
|
355
|
+
off();
|
|
356
|
+
this.registered.clear();
|
|
357
|
+
this.dragging = null;
|
|
358
|
+
this.group = [];
|
|
359
|
+
this.held = null;
|
|
360
|
+
})
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/** A candidate drop target; registered at once when our drag is in progress. */
|
|
364
|
+
addDropTarget(node, target) {
|
|
365
|
+
if (!this.options.draggable)
|
|
366
|
+
return () => { };
|
|
367
|
+
this.targets.set(node, target);
|
|
368
|
+
if (this.dragging)
|
|
369
|
+
this.registerDropTarget(node, target);
|
|
370
|
+
return () => {
|
|
371
|
+
if (this.targets.get(node) === target)
|
|
372
|
+
this.targets.delete(node);
|
|
373
|
+
this.registered.get(node)?.();
|
|
374
|
+
this.registered.delete(node);
|
|
375
|
+
if (this.activeDropTarget === node)
|
|
376
|
+
this.clearIndicator();
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
registerDropTarget(node, target) {
|
|
380
|
+
if (this.registered.has(node))
|
|
381
|
+
return;
|
|
382
|
+
const cleanup = dropTargetForElements({
|
|
383
|
+
element: node,
|
|
384
|
+
canDrop: ({ source, input }) => {
|
|
385
|
+
const row = this.rowAt(source.data, target, node, input);
|
|
386
|
+
return Object.values(this.operations(source.data, row, input)).includes('available');
|
|
387
|
+
},
|
|
388
|
+
// Atlassian's list-item hitbox splits the row the pointer is on into its
|
|
389
|
+
// halves (`operations`); nesting is no band of it (combine is
|
|
390
|
+
// not-available): the pointer's x decides it (`zones`).
|
|
391
|
+
getData: ({ source, input }) => {
|
|
392
|
+
const row = this.rowAt(source.data, target, node, input);
|
|
393
|
+
return attachInstruction({ [ROW]: row.id }, {
|
|
394
|
+
input,
|
|
395
|
+
// The hitbox reads only this box: the row's own, not its subtree's.
|
|
396
|
+
element: { getBoundingClientRect: () => ownRow(row.node) },
|
|
397
|
+
operations: this.operations(source.data, row, input)
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
// Held while it or a target inside it holds the placement (the drag library
|
|
401
|
+
// keeps an inner target only while every outer one of its chain sticks),
|
|
402
|
+
// across a small gap beside the block — below the document's last block, all
|
|
403
|
+
// the way down (Notion) — never over another block's own row (its parent's,
|
|
404
|
+
// which the drag library would otherwise let it keep).
|
|
405
|
+
getIsSticky: ({ source, input }) => {
|
|
406
|
+
const dragSource = this.getDragSource(source.data);
|
|
407
|
+
const active = this.activeDropTarget;
|
|
408
|
+
if (!dragSource || this.edytor.readonly || !active || !node.contains(active))
|
|
409
|
+
return false;
|
|
410
|
+
const rect = node.getBoundingClientRect();
|
|
411
|
+
return (input.clientX >= rect.left - 20 &&
|
|
412
|
+
input.clientX <= rect.right + 20 &&
|
|
413
|
+
input.clientY >= rect.top - 24 &&
|
|
414
|
+
(input.clientY <= rect.bottom + 24 || endsDocument(target)) &&
|
|
415
|
+
!overOtherRow(node, input, (over) => !this.edytor.idToBlock.get(over.dataset.edytorId ?? '')?.isContainer));
|
|
416
|
+
},
|
|
417
|
+
onDragEnter: ({ location, source }) => this.showIndicator(node, location, source.data),
|
|
418
|
+
onDrag: ({ location, source }) => this.showIndicator(node, location, source.data),
|
|
419
|
+
// The target left hands the placement to the innermost one still current (an
|
|
420
|
+
// outer target of its chain that sticks), before the next move asks it to stick.
|
|
421
|
+
onDragLeave: ({ location, source }) => {
|
|
422
|
+
if (this.activeDropTarget !== node)
|
|
423
|
+
return;
|
|
424
|
+
this.clearIndicator();
|
|
425
|
+
const [current] = location.current.dropTargets;
|
|
426
|
+
if (current)
|
|
427
|
+
this.showIndicator(current.element, location, source.data);
|
|
428
|
+
},
|
|
429
|
+
onDrop: ({ location, source }) => {
|
|
430
|
+
const [current] = location.current.dropTargets;
|
|
431
|
+
if (current?.element !== node)
|
|
432
|
+
return;
|
|
433
|
+
const dragSource = this.getDragSource(source.data);
|
|
434
|
+
const placement = this.activeDropTarget === node && this.activePlacement
|
|
435
|
+
? this.activePlacement
|
|
436
|
+
: this.placement(current, source.data, location.current.input);
|
|
437
|
+
this.clearIndicator();
|
|
438
|
+
if (dragSource &&
|
|
439
|
+
placement &&
|
|
440
|
+
this.canDrop(dragSource, placement.target, placement.position)) {
|
|
441
|
+
const moved = this.moveAndSelect({
|
|
442
|
+
blocks: this.moving(dragSource),
|
|
443
|
+
target: placement.target,
|
|
444
|
+
position: placement.position
|
|
445
|
+
});
|
|
446
|
+
if (moved.length && this.held)
|
|
447
|
+
this.edytor.history.began(this.held);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
this.registered.set(node, cleanup);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Alt+arrow: one step for the blocks a drag of this handle moves. A text
|
|
455
|
+
* range over them stays, as Mod+Shift+arrow keeps it; else they end
|
|
456
|
+
* selected as after a drop (`moveAndSelect`).
|
|
457
|
+
*/
|
|
458
|
+
handleKeyDown(event, block) {
|
|
459
|
+
const direction = event.altKey ? keyMoves[event.key] : undefined;
|
|
460
|
+
if (this.edytor.readonly || !direction)
|
|
461
|
+
return;
|
|
462
|
+
event.preventDefault();
|
|
463
|
+
event.stopPropagation();
|
|
464
|
+
const request = { blocks: this.dragBlocks(block), direction };
|
|
465
|
+
if (this.inRange(block))
|
|
466
|
+
this.edytor.moveBlocks(request);
|
|
467
|
+
else
|
|
468
|
+
this.moveAndSelect(request);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Move, then select what moved — the drag's rule (`onDragStart`): a group
|
|
472
|
+
* the block selection holds keeps it (a moved block's selected children
|
|
473
|
+
* too); any other group ends selected alone.
|
|
474
|
+
*/
|
|
475
|
+
moveAndSelect(request) {
|
|
476
|
+
const { selectedBlocks } = this.edytor.selection;
|
|
477
|
+
const holds = request.blocks.every((block) => selectedBlocks.has(block));
|
|
478
|
+
const before = holds ? [...selectedBlocks] : [];
|
|
479
|
+
const moved = this.edytor.moveBlocks(request);
|
|
480
|
+
if (moved.length)
|
|
481
|
+
selectMoved(this.edytor, moved, before);
|
|
482
|
+
return moved;
|
|
483
|
+
}
|
|
484
|
+
canDrop(source, target, position) {
|
|
485
|
+
return this.edytor.canMoveBlocks({ blocks: this.moving(source), target, position });
|
|
486
|
+
}
|
|
487
|
+
/** The blocks a drop of `source` moves: the drag's own group, fixed when it started. */
|
|
488
|
+
moving(source) {
|
|
489
|
+
// A member a collaborator deleted meanwhile is left out; the others still move.
|
|
490
|
+
const group = this.dragging === source.id ? this.group.filter((block) => block.isInTree) : [];
|
|
491
|
+
return group.length ? group : this.dragBlocks(source);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* The blocks the selection covers — a block selection's blocks as
|
|
495
|
+
* clicked (a selected list is one block: its item's handle drags the
|
|
496
|
+
* item), or the blocks a text range spans (`getSelectionBlocks`, the
|
|
497
|
+
* resolver Tab and Turn into read), a code line as its code block
|
|
498
|
+
* (`movable`) — when `source` is one of them; else `source` alone.
|
|
499
|
+
*/
|
|
500
|
+
covered(source) {
|
|
501
|
+
const { selectedBlocks, state } = this.edytor.selection;
|
|
502
|
+
if (!selectedBlocks.size && state.isCollapsed)
|
|
503
|
+
return [source];
|
|
504
|
+
const covered = movable(selectedBlocks.size ? selectedBlocks : getSelectionBlocks(this.edytor));
|
|
505
|
+
return covered.includes(source) ? covered : [source];
|
|
506
|
+
}
|
|
507
|
+
/** Whether a text range (no block selection) covers `source` (`covered`). */
|
|
508
|
+
inRange(source) {
|
|
509
|
+
const { selectedBlocks, state } = this.edytor.selection;
|
|
510
|
+
return (!selectedBlocks.size &&
|
|
511
|
+
!state.isCollapsed &&
|
|
512
|
+
movable(getSelectionBlocks(this.edytor)).includes(source));
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* The blocks a drag (or an Alt+arrow) from `source`'s handle moves
|
|
516
|
+
* (Notion): the outermost of those the selection covers (`covered`), in
|
|
517
|
+
* document order — a block's children and a closed toggle's hidden body
|
|
518
|
+
* travel with it; else `source` alone.
|
|
519
|
+
*/
|
|
520
|
+
dragBlocks(source) {
|
|
521
|
+
return outermost(this.covered(source)).sort(this.edytor.compareBlocks);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Select for a gesture on `source`'s handle: over a text range across
|
|
525
|
+
* blocks, every block it covers (children too, as Turn into reads them);
|
|
526
|
+
* else `source`.
|
|
527
|
+
*/
|
|
528
|
+
select(source) {
|
|
529
|
+
const { selection } = this.edytor;
|
|
530
|
+
selection.selectBlocks(...(selection.selectedBlocks.size ? [source] : this.covered(source)));
|
|
531
|
+
}
|
|
532
|
+
/** Whether the drag of `source` may land at a placement. */
|
|
533
|
+
fits = (source) => (placement) => this.canDrop(source, placement.target, placement.position);
|
|
534
|
+
/**
|
|
535
|
+
* The hitbox's operations over `row` for our drag: reorder-before and
|
|
536
|
+
* reorder-after, each `blocked` when the document refuses every
|
|
537
|
+
* placement of that half (`zones`), none outside our drag.
|
|
538
|
+
*/
|
|
539
|
+
operations(data, row, input) {
|
|
540
|
+
const source = this.getDragSource(data);
|
|
541
|
+
if (!source || this.edytor.readonly)
|
|
542
|
+
return {};
|
|
543
|
+
const { before, after } = this.zones(source, row, input);
|
|
544
|
+
const offer = (half) => half.some(this.fits(source)) ? 'available' : 'blocked';
|
|
545
|
+
return { 'reorder-before': offer(before), 'reorder-after': offer(after) };
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* The block whose row the pointer is on over `node`: the last shown drop
|
|
549
|
+
* target in it whose row starts at or above the pointer (beside a nested
|
|
550
|
+
* block, over its ancestors' indent, the pointer is on that block's
|
|
551
|
+
* row), else `target`, over its own row (`emptied`).
|
|
552
|
+
*/
|
|
553
|
+
rowAt(data, target, node, { clientY }) {
|
|
554
|
+
const source = this.getDragSource(data);
|
|
555
|
+
const lift = (row) => (source ? this.emptied(source, row) : row);
|
|
556
|
+
if (clientY < getOwnRowBottom(node))
|
|
557
|
+
return lift(target);
|
|
558
|
+
let row = target;
|
|
559
|
+
// In document order, rows start lower and lower: measure none below the
|
|
560
|
+
// pointer, and none a closed toggle hides (each read would force a layout).
|
|
561
|
+
for (const inner of node.querySelectorAll('[data-edytor-block="true"]')) {
|
|
562
|
+
const block = this.targets.get(inner);
|
|
563
|
+
if (!block || inner.closest(HIDDEN))
|
|
564
|
+
continue;
|
|
565
|
+
const rect = inner.getBoundingClientRect();
|
|
566
|
+
if (rect.top > clientY)
|
|
567
|
+
break;
|
|
568
|
+
if (rect.height > 0)
|
|
569
|
+
row = block;
|
|
570
|
+
}
|
|
571
|
+
return lift(row);
|
|
572
|
+
}
|
|
573
|
+
/** The children a block shows: none for a closed toggle's, or a container's items. */
|
|
574
|
+
shown(block) {
|
|
575
|
+
return block.isContainer
|
|
576
|
+
? []
|
|
577
|
+
: block.children.filter((child) => child.node && this.targets.has(child.node) && !hidden(child));
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Over a dragged block, the parent it leaves when that parent shows no
|
|
581
|
+
* other child: the gap under the parent's own row, which reparents as
|
|
582
|
+
* the parent's lower half does (`zones`). Else `row`.
|
|
583
|
+
*/
|
|
584
|
+
emptied(source, row) {
|
|
585
|
+
const group = this.moving(source);
|
|
586
|
+
const parent = group.find((moved) => moved === row || row.isChildOf(moved))?.parent;
|
|
587
|
+
if (!parent?.node || parent.isRoot || !this.targets.has(parent.node))
|
|
588
|
+
return row;
|
|
589
|
+
const shown = this.shown(parent);
|
|
590
|
+
return shown.length && shown.every((child) => group.includes(child)) ? parent : row;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* The placements over `row`, each half's in preference order (a refused
|
|
594
|
+
* one gives way to the next, then to the other half's):
|
|
595
|
+
* - the top half: before it;
|
|
596
|
+
* - the bottom half of a block whose children show (not a closed toggle):
|
|
597
|
+
* its first child's slot. Pragmatic drag and drop's tree rule: an
|
|
598
|
+
* expanded item offers no "after" on its own row;
|
|
599
|
+
* - else, after it at the level whose text column is nearest the pointer:
|
|
600
|
+
* the last block of a nested group reparents to any ancestor it ends,
|
|
601
|
+
* down to the root. Past one nesting step right of its text start,
|
|
602
|
+
* inside it (its last child) first. A block every shown child of which
|
|
603
|
+
* is dragged is not expanded: its levels, never inside it when the drag
|
|
604
|
+
* moves only its children (they would stay where they are).
|
|
605
|
+
* None over a dragged block's own row or its subtree: released there, the
|
|
606
|
+
* drag changes nothing (its levels would outdent it) — unless it empties
|
|
607
|
+
* its parent's shown children (`emptied`): that parent's row.
|
|
608
|
+
*/
|
|
609
|
+
zones(source, row, { clientX }) {
|
|
610
|
+
const group = this.moving(source);
|
|
611
|
+
if (group.some((moved) => moved === row || row.isChildOf(moved)))
|
|
612
|
+
return { before: [], after: [] };
|
|
613
|
+
const at = (target, position) => target?.node ? [{ target, node: target.node, position }] : [];
|
|
614
|
+
const before = at(row, 'before');
|
|
615
|
+
const shown = this.shown(row);
|
|
616
|
+
const first = shown.find((child) => !group.includes(child));
|
|
617
|
+
if (first)
|
|
618
|
+
return { before, after: at(first, 'before') };
|
|
619
|
+
// A group moved out from under its parent leaves the block before it last.
|
|
620
|
+
const next = (block) => {
|
|
621
|
+
let next = block.nextBlock;
|
|
622
|
+
while (next && group.includes(next))
|
|
623
|
+
next = next.nextBlock;
|
|
624
|
+
return next;
|
|
625
|
+
};
|
|
626
|
+
const levels = [row];
|
|
627
|
+
for (let level = row; !next(level) && level.parent?.node && !level.parent.isRoot;)
|
|
628
|
+
levels.push((level = level.parent));
|
|
629
|
+
const distance = (level) => Math.abs(column(level) - clientX);
|
|
630
|
+
const after = levels
|
|
631
|
+
.sort((a, b) => distance(a) - distance(b))
|
|
632
|
+
.flatMap((level) => at(level, 'after'));
|
|
633
|
+
const stays = shown.length > 0 && group.every((moved) => moved.parent === row);
|
|
634
|
+
const inside = stays ? [] : at(row, 'inside');
|
|
635
|
+
const nests = clientX > column(row) + nestIndent(row.node);
|
|
636
|
+
return { before, after: nests ? [...inside, ...after] : [...after, ...inside] };
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* The placement a drop target record offers: the row and the half the
|
|
640
|
+
* hitbox attached (kept while the target is sticky), the pointer's x live.
|
|
641
|
+
*/
|
|
642
|
+
placement(record, data, input) {
|
|
643
|
+
const source = this.getDragSource(data);
|
|
644
|
+
const instruction = extractInstruction(record.data);
|
|
645
|
+
const row = this.edytor.idToBlock.get(record.data[ROW]);
|
|
646
|
+
if (!source || !instruction || !row?.node)
|
|
647
|
+
return null;
|
|
648
|
+
const { before, after } = this.zones(source, row, input);
|
|
649
|
+
const halves = instruction.operation === 'reorder-before' ? [before, after] : [after, before];
|
|
650
|
+
const placement = halves.flat().find(this.fits(source));
|
|
651
|
+
return placement ? { ...placement, blocked: instruction.blocked } : null;
|
|
652
|
+
}
|
|
653
|
+
getDragSource(data) {
|
|
654
|
+
if (data.owner !== this.owner || typeof data.blockId !== 'string')
|
|
655
|
+
return null;
|
|
656
|
+
return this.edytor.idToBlock.get(data.blockId) ?? null;
|
|
657
|
+
}
|
|
658
|
+
showIndicator(node, location, data) {
|
|
659
|
+
const [current] = location.current.dropTargets;
|
|
660
|
+
if (current?.element !== node)
|
|
661
|
+
return;
|
|
662
|
+
const placement = this.placement(current, data, location.current.input);
|
|
663
|
+
if (!placement)
|
|
664
|
+
return;
|
|
665
|
+
const shown = this.activePlacement;
|
|
666
|
+
if (this.activeDropTarget === node &&
|
|
667
|
+
shown?.target === placement.target &&
|
|
668
|
+
shown.position === placement.position &&
|
|
669
|
+
shown.blocked === placement.blocked) {
|
|
670
|
+
this.edytor.overlay.invalidate();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
this.clearIndicator();
|
|
674
|
+
this.activeDropTarget = node;
|
|
675
|
+
this.activePlacement = placement;
|
|
676
|
+
this.indicatorNode = placement.node;
|
|
677
|
+
placement.node.dataset.edytorBlockDropPosition = placement.position;
|
|
678
|
+
this.showOverlay(placement);
|
|
679
|
+
}
|
|
680
|
+
showOverlay(placement) {
|
|
681
|
+
const document = placement.node.ownerDocument;
|
|
682
|
+
const overlay = document.createElement('div');
|
|
683
|
+
overlay.dataset.edytorDropIndicator = 'true';
|
|
684
|
+
overlay.dataset.position = placement.position;
|
|
685
|
+
if (placement.blocked)
|
|
686
|
+
overlay.dataset.blocked = 'true';
|
|
687
|
+
overlay.setAttribute('aria-hidden', 'true');
|
|
688
|
+
const count = this.group.length;
|
|
689
|
+
if (count > 1) {
|
|
690
|
+
// The count sits in the gutter at the bar's start, where the handles are.
|
|
691
|
+
overlay.dataset.count = String(count);
|
|
692
|
+
const badge = countBadge(document, count);
|
|
693
|
+
Object.assign(badge.style, { top: `${BAR / 2 - 9}px`, right: 'auto', left: '-26px' });
|
|
694
|
+
overlay.append(badge);
|
|
695
|
+
}
|
|
696
|
+
const color = document.defaultView
|
|
697
|
+
?.getComputedStyle(placement.node)
|
|
698
|
+
.getPropertyValue('--edytor-drop-indicator-color')
|
|
699
|
+
.trim();
|
|
700
|
+
overlay.style.setProperty('--edytor-drop-indicator-color', color || DROP_INDICATOR_COLOR);
|
|
701
|
+
const layer = this.edytor.overlay.layer;
|
|
702
|
+
layer?.append(overlay);
|
|
703
|
+
this.indicatorOverlay = overlay;
|
|
704
|
+
const measure = (origin) => {
|
|
705
|
+
const writes = [this.positionIndicator(origin), this.positionBackdrop(placement, origin)];
|
|
706
|
+
return () => writes.forEach((write) => write?.());
|
|
707
|
+
};
|
|
708
|
+
// Placed at once (it must not show at the layer's origin for a frame), then per frame.
|
|
709
|
+
if (layer)
|
|
710
|
+
measure(layer.getBoundingClientRect())();
|
|
711
|
+
this.offIndicator = this.edytor.overlay.add(measure);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* The block a placement makes the dragged blocks' parent when the drop
|
|
715
|
+
* nests them: for `inside`, the target, or the item a container nests
|
|
716
|
+
* them under (`nestParent`, as the move does); for before/after, the
|
|
717
|
+
* target's parent unless it already holds them all (a plain reorder) or
|
|
718
|
+
* is the root. A container shows no row of its own (a list): none.
|
|
719
|
+
*/
|
|
720
|
+
nestParent({ target, position }) {
|
|
721
|
+
const { edytor, group } = this;
|
|
722
|
+
const ids = group.map((block) => block.id);
|
|
723
|
+
const parent = position === 'inside'
|
|
724
|
+
? (edytor.idToBlock.get(edytor.facade.nestParent(ids, target.id)) ?? target)
|
|
725
|
+
: target.parent;
|
|
726
|
+
if (!parent?.node || parent.isRoot || parent.isContainer)
|
|
727
|
+
return null;
|
|
728
|
+
if (position !== 'inside' && group.every((block) => block.parent === parent))
|
|
729
|
+
return null;
|
|
730
|
+
return parent;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Layer-relative geometry of the nest backdrop (`backdrop`): the future
|
|
734
|
+
* parent's box (`nestParent`, read per frame: a peer's change may change
|
|
735
|
+
* it), down to where its first child begins; hidden when the placement
|
|
736
|
+
* does not nest.
|
|
737
|
+
*/
|
|
738
|
+
positionBackdrop(placement, origin) {
|
|
739
|
+
const { backdrop } = this;
|
|
740
|
+
if (!backdrop)
|
|
741
|
+
return;
|
|
742
|
+
const parent = this.nestParent(placement);
|
|
743
|
+
const node = parent?.node;
|
|
744
|
+
if (!parent || !node?.isConnected)
|
|
745
|
+
return () => delete backdrop.dataset.shown;
|
|
746
|
+
const rect = ownRow(node);
|
|
747
|
+
const color = node.ownerDocument.defaultView
|
|
748
|
+
?.getComputedStyle(node)
|
|
749
|
+
.getPropertyValue(BACKDROP_COLOR)
|
|
750
|
+
.trim();
|
|
751
|
+
return () => {
|
|
752
|
+
backdrop.dataset.blockId = parent.id;
|
|
753
|
+
if (color)
|
|
754
|
+
backdrop.style.setProperty(BACKDROP_COLOR, color);
|
|
755
|
+
else
|
|
756
|
+
backdrop.style.removeProperty(BACKDROP_COLOR);
|
|
757
|
+
Object.assign(backdrop.style, {
|
|
758
|
+
left: `${rect.left - origin.left}px`,
|
|
759
|
+
top: `${rect.top - origin.top}px`,
|
|
760
|
+
width: `${rect.width}px`,
|
|
761
|
+
height: `${rect.height}px`
|
|
762
|
+
});
|
|
763
|
+
backdrop.dataset.shown = 'true';
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Layer-relative geometry (R11) of Notion's plain bar: centered between the
|
|
768
|
+
* two siblings of the slot, or — inside the target — where the child lands,
|
|
769
|
+
* indented to the child's column.
|
|
770
|
+
*/
|
|
771
|
+
positionIndicator(origin) {
|
|
772
|
+
const placement = this.activePlacement;
|
|
773
|
+
const overlay = this.indicatorOverlay;
|
|
774
|
+
if (!placement || !overlay)
|
|
775
|
+
return;
|
|
776
|
+
const rect = placement.node.getBoundingClientRect();
|
|
777
|
+
const place = (left, width, center) => () => {
|
|
778
|
+
overlay.style.left = `${left - origin.left}px`;
|
|
779
|
+
overlay.style.width = `${width}px`;
|
|
780
|
+
overlay.style.top = `${center - BAR / 2 - origin.top}px`;
|
|
781
|
+
};
|
|
782
|
+
if (placement.position === 'inside') {
|
|
783
|
+
// Where the child lands (the gap after the last visible child, else after
|
|
784
|
+
// the target's own row), from the child's indent to the target's right edge.
|
|
785
|
+
const row = ownTextRow(placement.node);
|
|
786
|
+
const { target } = placement;
|
|
787
|
+
const lastNode = target.children.at(-1)?.node;
|
|
788
|
+
const last = lastNode?.getBoundingClientRect();
|
|
789
|
+
const shown = last && last.height > 0 ? last : null;
|
|
790
|
+
const next = target.parent?.children[target.index + 1]?.node?.getBoundingClientRect();
|
|
791
|
+
const end = shown ? shown.bottom : getOwnRowBottom(placement.node);
|
|
792
|
+
const land = next && next.top >= end ? (end + next.top) / 2 : end + 4;
|
|
793
|
+
const left = shown && lastNode ? ownTextRow(lastNode).left : row.left + nestIndent(placement.node);
|
|
794
|
+
return place(left, Math.max(16, rect.right - left), land);
|
|
795
|
+
}
|
|
796
|
+
const siblings = placement.target.parent?.children;
|
|
797
|
+
const index = placement.target.index + (placement.position === 'after' ? 1 : 0);
|
|
798
|
+
const previous = siblings?.[index - 1]?.node?.getBoundingClientRect();
|
|
799
|
+
const next = siblings?.[index]?.node?.getBoundingClientRect();
|
|
800
|
+
if (previous && next && previous.bottom <= next.top) {
|
|
801
|
+
const left = Math.min(previous.left, next.left);
|
|
802
|
+
const width = Math.max(previous.right, next.right) - left;
|
|
803
|
+
return place(left, width, (previous.bottom + next.top) / 2);
|
|
804
|
+
}
|
|
805
|
+
const edge = placement.position === 'before' ? rect.top : rect.bottom;
|
|
806
|
+
return place(rect.left, rect.width, edge);
|
|
807
|
+
}
|
|
808
|
+
clearIndicator() {
|
|
809
|
+
if (this.indicatorNode) {
|
|
810
|
+
delete this.indicatorNode.dataset.edytorBlockDropPosition;
|
|
811
|
+
this.indicatorNode = null;
|
|
812
|
+
}
|
|
813
|
+
this.offIndicator?.();
|
|
814
|
+
this.offIndicator = null;
|
|
815
|
+
// Fades out where it was; the next nesting placement shows it again.
|
|
816
|
+
if (this.backdrop)
|
|
817
|
+
delete this.backdrop.dataset.shown;
|
|
818
|
+
this.indicatorOverlay?.remove();
|
|
819
|
+
this.indicatorOverlay = null;
|
|
820
|
+
this.activeDropTarget = null;
|
|
821
|
+
this.activePlacement = null;
|
|
822
|
+
}
|
|
823
|
+
}
|