@dxos/ui-editor 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/chunk-diff.mjs +935 -0
- package/dist/lib/chunk-diff.mjs.map +1 -0
- package/dist/lib/headless.mjs +2 -0
- package/dist/lib/index.mjs +9937 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/types.mjs +18 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/types/src/extensions/{bookmarks.d.ts → behavior/bookmarks.d.ts} +3 -0
- package/dist/types/src/extensions/behavior/bookmarks.d.ts.map +1 -0
- package/dist/types/src/extensions/behavior/dnd.d.ts.map +1 -0
- package/dist/types/src/extensions/behavior/index.d.ts +6 -0
- package/dist/types/src/extensions/behavior/index.d.ts.map +1 -0
- package/dist/types/src/extensions/{listener.d.ts → behavior/listener.d.ts} +3 -0
- package/dist/types/src/extensions/behavior/listener.d.ts.map +1 -0
- package/dist/types/src/extensions/{spacing.d.ts → behavior/spacing.d.ts} +3 -0
- package/dist/types/src/extensions/behavior/spacing.d.ts.map +1 -0
- package/dist/types/src/extensions/behavior/submit.d.ts.map +1 -0
- package/dist/types/src/extensions/collab/automerge/automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/collab/automerge/automerge.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{automerge → collab/automerge}/cursor.d.ts +1 -1
- package/dist/types/src/extensions/collab/automerge/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/{automerge → collab/automerge}/defs.d.ts +3 -1
- package/dist/types/src/extensions/collab/automerge/defs.d.ts.map +1 -0
- package/dist/types/src/extensions/collab/automerge/index.d.ts +3 -0
- package/dist/types/src/extensions/collab/automerge/index.d.ts.map +1 -0
- package/dist/types/src/extensions/collab/automerge/sync.d.ts.map +1 -0
- package/dist/types/src/extensions/{automerge → collab/automerge}/update-automerge.d.ts +4 -0
- package/dist/types/src/extensions/collab/automerge/update-automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/{automerge → collab/automerge}/update-codemirror.d.ts +4 -0
- package/dist/types/src/extensions/collab/automerge/update-codemirror.d.ts.map +1 -0
- package/dist/types/src/extensions/collab/awareness/awareness-provider.d.ts.map +1 -0
- package/dist/types/src/extensions/{awareness → collab/awareness}/awareness.d.ts +4 -1
- package/dist/types/src/extensions/collab/awareness/awareness.d.ts.map +1 -0
- package/dist/types/src/extensions/collab/awareness/index.d.ts.map +1 -0
- package/dist/types/src/extensions/collab/index.d.ts +3 -0
- package/dist/types/src/extensions/collab/index.d.ts.map +1 -0
- package/dist/types/src/extensions/completion/autocomplete.d.ts.map +1 -0
- package/dist/types/src/extensions/{autocomplete → completion}/index.d.ts +1 -0
- package/dist/types/src/extensions/completion/index.d.ts.map +1 -0
- package/dist/types/src/extensions/{autocomplete → completion}/match.d.ts +2 -2
- package/dist/types/src/extensions/completion/match.d.ts.map +1 -0
- package/dist/types/src/extensions/{mention.d.ts → completion/mention.d.ts} +3 -0
- package/dist/types/src/extensions/completion/mention.d.ts.map +1 -0
- package/dist/types/src/extensions/completion/placeholder.d.ts.map +1 -0
- package/dist/types/src/extensions/{autocomplete → completion}/typeahead.d.ts +2 -2
- package/dist/types/src/extensions/completion/typeahead.d.ts.map +1 -0
- package/dist/types/src/extensions/{factories.d.ts → core/factories.d.ts} +23 -2
- package/dist/types/src/extensions/core/factories.d.ts.map +1 -0
- package/dist/types/src/extensions/core/factories.test.d.ts.map +1 -0
- package/dist/types/src/extensions/core/index.d.ts +3 -0
- package/dist/types/src/extensions/core/index.d.ts.map +1 -0
- package/dist/types/src/extensions/{modes.d.ts → core/modes.d.ts} +3 -0
- package/dist/types/src/extensions/core/modes.d.ts.map +1 -0
- package/dist/types/src/extensions/{debug.d.ts → debug/debug.d.ts} +3 -0
- package/dist/types/src/extensions/debug/debug.d.ts.map +1 -0
- package/dist/types/src/extensions/debug/index.d.ts +2 -0
- package/dist/types/src/extensions/debug/index.d.ts.map +1 -0
- package/dist/types/src/extensions/{annotations.d.ts → decoration/annotations.d.ts} +1 -1
- package/dist/types/src/extensions/decoration/annotations.d.ts.map +1 -0
- package/dist/types/src/extensions/decoration/assistant.d.ts +27 -0
- package/dist/types/src/extensions/decoration/assistant.d.ts.map +1 -0
- package/dist/types/src/extensions/decoration/fader.d.ts.map +1 -0
- package/dist/types/src/extensions/decoration/hashtag.d.ts +6 -0
- package/dist/types/src/extensions/decoration/hashtag.d.ts.map +1 -0
- package/dist/types/src/extensions/decoration/index.d.ts +8 -0
- package/dist/types/src/extensions/decoration/index.d.ts.map +1 -0
- package/dist/types/src/extensions/decoration/marker.d.ts.map +1 -0
- package/dist/types/src/extensions/{pos.d.ts → decoration/pos.d.ts} +40 -14
- package/dist/types/src/extensions/decoration/pos.d.ts.map +1 -0
- package/dist/types/src/extensions/decoration/pos.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{replacer.d.ts → decoration/replacer.d.ts} +8 -8
- package/dist/types/src/extensions/decoration/replacer.d.ts.map +1 -0
- package/dist/types/src/extensions/decoration/replacer.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{blast.d.ts → demo/blast.d.ts} +3 -0
- package/dist/types/src/extensions/demo/blast.d.ts.map +1 -0
- package/dist/types/src/extensions/demo/index.d.ts +3 -0
- package/dist/types/src/extensions/demo/index.d.ts.map +1 -0
- package/dist/types/src/extensions/demo/snippets.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +10 -33
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/language/index.d.ts +5 -0
- package/dist/types/src/extensions/language/index.d.ts.map +1 -0
- package/dist/types/src/extensions/language/json/index.d.ts +2 -0
- package/dist/types/src/extensions/language/json/index.d.ts.map +1 -0
- package/dist/types/src/extensions/language/json/json.d.ts +12 -0
- package/dist/types/src/extensions/language/json/json.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/action.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/bundle.d.ts.map +1 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/changes.d.ts +1 -1
- package/dist/types/src/extensions/language/markdown/changes.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/changes.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/debug.d.ts +3 -0
- package/dist/types/src/extensions/language/markdown/debug.d.ts.map +1 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/decorate.d.ts +5 -1
- package/dist/types/src/extensions/language/markdown/decorate.d.ts.map +1 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/formatting.d.ts +11 -2
- package/dist/types/src/extensions/language/markdown/formatting.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/formatting.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/highlight.d.ts +16 -12
- package/dist/types/src/extensions/language/markdown/highlight.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/image.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/image.test.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/index.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/link.d.ts +25 -0
- package/dist/types/src/extensions/language/markdown/link.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/link.test.d.ts +2 -0
- package/dist/types/src/extensions/language/markdown/link.test.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/parser.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/styles.d.ts +3 -0
- package/dist/types/src/extensions/language/markdown/styles.d.ts.map +1 -0
- package/dist/types/src/extensions/language/markdown/table.d.ts.map +1 -0
- package/dist/types/src/extensions/language/mermaid/index.d.ts +2 -0
- package/dist/types/src/extensions/language/mermaid/index.d.ts.map +1 -0
- package/dist/types/src/extensions/language/mermaid/mermaid.d.ts +18 -0
- package/dist/types/src/extensions/language/mermaid/mermaid.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/extended-markdown.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/extended-markdown.test.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/index.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/stub.d.ts +76 -0
- package/dist/types/src/extensions/language/xml/stub.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/widgets/anchor.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/widgets/index.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/xml-block-decoration.d.ts +48 -0
- package/dist/types/src/extensions/language/xml/xml-block-decoration.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/xml-formatting.d.ts.map +1 -0
- package/dist/types/src/extensions/{tags → language/xml}/xml-tags.d.ts +12 -1
- package/dist/types/src/extensions/language/xml/xml-tags.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/xml-tags.test.d.ts +2 -0
- package/dist/types/src/extensions/language/xml/xml-tags.test.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/xml-util.d.ts.map +1 -0
- package/dist/types/src/extensions/language/xml/xml-util.test.d.ts.map +1 -0
- package/dist/types/src/extensions/review/change-bar.d.ts +29 -0
- package/dist/types/src/extensions/review/change-bar.d.ts.map +1 -0
- package/dist/types/src/extensions/{comments.d.ts → review/comments.d.ts} +56 -38
- package/dist/types/src/extensions/review/comments.d.ts.map +1 -0
- package/dist/types/src/extensions/review/comments.test.d.ts +2 -0
- package/dist/types/src/extensions/review/comments.test.d.ts.map +1 -0
- package/dist/types/src/extensions/review/diff.d.ts +131 -0
- package/dist/types/src/extensions/review/diff.d.ts.map +1 -0
- package/dist/types/src/extensions/review/diff.test.d.ts +2 -0
- package/dist/types/src/extensions/review/diff.test.d.ts.map +1 -0
- package/dist/types/src/extensions/review/index.d.ts +7 -0
- package/dist/types/src/extensions/review/index.d.ts.map +1 -0
- package/dist/types/src/extensions/review/suggest.d.ts +89 -0
- package/dist/types/src/extensions/review/suggest.d.ts.map +1 -0
- package/dist/types/src/extensions/review/suggest.test.d.ts +2 -0
- package/dist/types/src/extensions/review/suggest.test.d.ts.map +1 -0
- package/dist/types/src/extensions/review/suggestions-overlay.d.ts +25 -0
- package/dist/types/src/extensions/review/suggestions-overlay.d.ts.map +1 -0
- package/dist/types/src/extensions/review/suggestions-overlay.test.d.ts +2 -0
- package/dist/types/src/extensions/review/suggestions-overlay.test.d.ts.map +1 -0
- package/dist/types/src/extensions/review/track-changes.d.ts +27 -0
- package/dist/types/src/extensions/review/track-changes.d.ts.map +1 -0
- package/dist/types/src/extensions/review/track-changes.test.d.ts +2 -0
- package/dist/types/src/extensions/review/track-changes.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{busy.d.ts → state/busy.d.ts} +1 -0
- package/dist/types/src/extensions/state/busy.d.ts.map +1 -0
- package/dist/types/src/extensions/{focus.d.ts → state/focus.d.ts} +4 -0
- package/dist/types/src/extensions/state/focus.d.ts.map +1 -0
- package/dist/types/src/extensions/state/index.d.ts +5 -0
- package/dist/types/src/extensions/state/index.d.ts.map +1 -0
- package/dist/types/src/extensions/state/modal.d.ts.map +1 -0
- package/dist/types/src/extensions/{selection.d.ts → state/selection.d.ts} +1 -1
- package/dist/types/src/extensions/state/selection.d.ts.map +1 -0
- package/dist/types/src/extensions/state/selection.test.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/index.d.ts +4 -0
- package/dist/types/src/extensions/streaming/index.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/pending/index.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/pending/pending-text-stream.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/pending/pending-text-stream.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{pending → streaming/pending}/pending-text.d.ts +1 -0
- package/dist/types/src/extensions/streaming/pending/pending-text.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/pending/pending-text.test.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/scrolling/auto-scroll.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/scrolling/crawler.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/scrolling/index.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/scrolling/scroll-past-end.d.ts +4 -0
- package/dist/types/src/extensions/streaming/scrolling/scroll-past-end.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/scrolling/scrollbar-autohide.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/scrolling/scroller.d.ts.map +1 -0
- package/dist/types/src/extensions/{typewriter.d.ts → streaming/typewriter.d.ts} +12 -12
- package/dist/types/src/extensions/streaming/typewriter.d.ts.map +1 -0
- package/dist/types/src/extensions/streaming/typewriter.test.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/blocks/blocks.d.ts +52 -0
- package/dist/types/src/extensions/structure/blocks/blocks.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/blocks/blocks.test.d.ts +2 -0
- package/dist/types/src/extensions/structure/blocks/blocks.test.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/blocks/drag.d.ts +57 -0
- package/dist/types/src/extensions/structure/blocks/drag.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/blocks/index.d.ts +5 -0
- package/dist/types/src/extensions/structure/blocks/index.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/blocks/selection.d.ts +47 -0
- package/dist/types/src/extensions/structure/blocks/selection.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/blocks/selection.test.d.ts +2 -0
- package/dist/types/src/extensions/structure/blocks/selection.test.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/blocks/types.d.ts +17 -0
- package/dist/types/src/extensions/structure/blocks/types.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/folding/folding.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/folding/index.d.ts +3 -0
- package/dist/types/src/extensions/structure/folding/index.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/folding/turns.d.ts +55 -0
- package/dist/types/src/extensions/structure/folding/turns.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/folding/turns.test.d.ts +2 -0
- package/dist/types/src/extensions/structure/folding/turns.test.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/index.d.ts +4 -0
- package/dist/types/src/extensions/structure/index.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/commands.d.ts +31 -0
- package/dist/types/src/extensions/structure/outliner/commands.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/dnd.d.ts +43 -0
- package/dist/types/src/extensions/structure/outliner/dnd.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/dnd.test.d.ts +2 -0
- package/dist/types/src/extensions/structure/outliner/dnd.test.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/editor.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/editor.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/index.d.ts +1 -0
- package/dist/types/src/extensions/structure/outliner/index.d.ts.map +1 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/menu.d.ts +1 -0
- package/dist/types/src/extensions/structure/outliner/menu.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/outliner.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/outliner.test.d.ts.map +1 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/tree.d.ts +1 -1
- package/dist/types/src/extensions/structure/outliner/tree.d.ts.map +1 -0
- package/dist/types/src/extensions/structure/outliner/tree.test.d.ts.map +1 -0
- package/dist/types/src/headless.d.ts +4 -0
- package/dist/types/src/headless.d.ts.map +1 -0
- package/dist/types/src/styles/theme.d.ts +1 -1
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/util/util.d.ts +2 -0
- package/dist/types/src/util/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +48 -42
- package/src/defaults.ts +1 -1
- package/src/extensions/{bookmarks.ts → behavior/bookmarks.ts} +24 -14
- package/src/extensions/behavior/index.ts +9 -0
- package/src/extensions/{listener.ts → behavior/listener.ts} +4 -1
- package/src/extensions/{spacing.ts → behavior/spacing.ts} +8 -2
- package/src/extensions/{submit.ts → behavior/submit.ts} +2 -10
- package/src/extensions/{automerge → collab/automerge}/automerge.test.tsx +4 -4
- package/src/extensions/{automerge → collab/automerge}/automerge.ts +7 -4
- package/src/extensions/{automerge → collab/automerge}/cursor.ts +1 -1
- package/src/extensions/{automerge → collab/automerge}/defs.ts +4 -1
- package/src/extensions/{automerge → collab/automerge}/index.ts +1 -0
- package/src/extensions/{automerge → collab/automerge}/sync.ts +1 -2
- package/src/extensions/{automerge → collab/automerge}/update-automerge.ts +4 -0
- package/src/extensions/{automerge → collab/automerge}/update-codemirror.ts +5 -1
- package/src/extensions/{awareness → collab/awareness}/awareness-provider.ts +3 -4
- package/src/extensions/{awareness → collab/awareness}/awareness.ts +19 -16
- package/src/extensions/collab/index.ts +6 -0
- package/src/extensions/{autocomplete → completion}/autocomplete.ts +7 -5
- package/src/extensions/{autocomplete → completion}/index.ts +2 -1
- package/src/extensions/{autocomplete → completion}/match.ts +2 -2
- package/src/extensions/{mention.ts → completion/mention.ts} +3 -0
- package/src/extensions/{autocomplete → completion}/placeholder.ts +1 -1
- package/src/extensions/completion/typeahead.ts +97 -0
- package/src/extensions/{factories.ts → core/factories.ts} +35 -13
- package/src/extensions/core/index.ts +6 -0
- package/src/extensions/{modes.ts → core/modes.ts} +4 -1
- package/src/extensions/{debug.ts → debug/debug.ts} +3 -0
- package/src/extensions/debug/index.ts +5 -0
- package/src/extensions/{annotations.ts → decoration/annotations.ts} +12 -10
- package/src/extensions/decoration/assistant.ts +225 -0
- package/src/extensions/{fader.ts → decoration/fader.ts} +17 -19
- package/src/extensions/{hashtag.ts → decoration/hashtag.ts} +35 -28
- package/src/extensions/decoration/index.ts +11 -0
- package/src/extensions/{marker.ts → decoration/marker.ts} +18 -18
- package/src/extensions/{pos.test.ts → decoration/pos.test.ts} +57 -1
- package/src/extensions/{pos.ts → decoration/pos.ts} +165 -31
- package/src/extensions/{blast.ts → demo/blast.ts} +3 -0
- package/src/extensions/demo/index.ts +6 -0
- package/src/extensions/{snippets.ts → demo/snippets.ts} +5 -1
- package/src/extensions/index.ts +10 -33
- package/src/extensions/language/index.ts +8 -0
- package/src/extensions/language/json/index.ts +5 -0
- package/src/extensions/{json.ts → language/json/json.ts} +11 -6
- package/src/extensions/{markdown → language/markdown}/action.ts +1 -1
- package/src/extensions/{markdown → language/markdown}/bundle.ts +7 -1
- package/src/extensions/{markdown → language/markdown}/changes.test.ts +2 -2
- package/src/extensions/{markdown → language/markdown}/changes.ts +2 -2
- package/src/extensions/{markdown → language/markdown}/debug.ts +3 -0
- package/src/extensions/{markdown → language/markdown}/decorate.ts +96 -85
- package/src/extensions/{markdown → language/markdown}/formatting.test.ts +88 -1
- package/src/extensions/{markdown → language/markdown}/formatting.ts +193 -74
- package/src/extensions/{markdown → language/markdown}/highlight.ts +42 -41
- package/src/extensions/{markdown → language/markdown}/image.test.ts +1 -1
- package/src/extensions/{markdown → language/markdown}/image.ts +6 -6
- package/src/extensions/language/markdown/link.test.ts +58 -0
- package/src/extensions/language/markdown/link.ts +109 -0
- package/src/extensions/{markdown → language/markdown}/parser.test.ts +0 -28
- package/src/extensions/{markdown → language/markdown}/styles.ts +38 -7
- package/src/extensions/{markdown → language/markdown}/table.ts +5 -5
- package/src/extensions/language/mermaid/index.ts +5 -0
- package/src/extensions/language/mermaid/mermaid.ts +44 -0
- package/src/extensions/{tags → language/xml}/DESIGN.md +3 -3
- package/src/extensions/{tags → language/xml}/extended-markdown.test.ts +2 -1
- package/src/extensions/{tags → language/xml}/extended-markdown.ts +2 -4
- package/src/extensions/language/xml/stub.ts +233 -0
- package/src/extensions/language/xml/xml-block-decoration.ts +165 -0
- package/src/extensions/{tags → language/xml}/xml-formatting.ts +54 -38
- package/src/extensions/language/xml/xml-tags.test.ts +588 -0
- package/src/extensions/{tags → language/xml}/xml-tags.ts +165 -66
- package/src/extensions/{tags → language/xml}/xml-util.test.ts +2 -2
- package/src/extensions/review/change-bar.ts +94 -0
- package/src/extensions/review/comments.test.ts +72 -0
- package/src/extensions/{comments.ts → review/comments.ts} +395 -267
- package/src/extensions/review/diff.test.ts +405 -0
- package/src/extensions/review/diff.ts +502 -0
- package/src/extensions/review/index.ts +10 -0
- package/src/extensions/review/suggest.test.ts +170 -0
- package/src/extensions/review/suggest.ts +512 -0
- package/src/extensions/review/suggestions-overlay.test.ts +58 -0
- package/src/extensions/review/suggestions-overlay.ts +50 -0
- package/src/extensions/review/track-changes.test.ts +101 -0
- package/src/extensions/review/track-changes.ts +215 -0
- package/src/extensions/{busy.ts → state/busy.ts} +1 -0
- package/src/extensions/{focus.ts → state/focus.ts} +4 -0
- package/src/extensions/state/index.ts +8 -0
- package/src/extensions/{selection.ts → state/selection.ts} +3 -3
- package/src/extensions/streaming/index.ts +7 -0
- package/src/extensions/{pending → streaming/pending}/pending-text-stream.ts +10 -3
- package/src/extensions/{pending → streaming/pending}/pending-text.test.ts +1 -1
- package/src/extensions/{pending → streaming/pending}/pending-text.ts +20 -19
- package/src/extensions/{scrolling → streaming/scrolling}/auto-scroll.ts +9 -6
- package/src/extensions/{scrolling → streaming/scrolling}/scroll-past-end.ts +1 -0
- package/src/extensions/{scrolling → streaming/scrolling}/scrollbar-autohide.ts +13 -11
- package/src/extensions/{typewriter.ts → streaming/typewriter.ts} +46 -47
- package/src/extensions/structure/blocks/DESIGN.md +134 -0
- package/src/extensions/structure/blocks/blocks.test.ts +147 -0
- package/src/extensions/structure/blocks/blocks.ts +219 -0
- package/src/extensions/structure/blocks/drag.ts +996 -0
- package/src/extensions/structure/blocks/index.ts +8 -0
- package/src/extensions/structure/blocks/selection.test.ts +56 -0
- package/src/extensions/structure/blocks/selection.ts +279 -0
- package/src/extensions/structure/blocks/types.ts +22 -0
- package/src/extensions/{folding.ts → structure/folding/folding.ts} +12 -10
- package/src/extensions/structure/folding/index.ts +6 -0
- package/src/extensions/structure/folding/turns.test.ts +72 -0
- package/src/extensions/structure/folding/turns.ts +209 -0
- package/src/extensions/structure/index.ts +7 -0
- package/src/extensions/structure/outliner/DESIGN.md +183 -0
- package/src/extensions/structure/outliner/SPEC.mdl +305 -0
- package/src/extensions/{outliner → structure/outliner}/commands.ts +125 -42
- package/src/extensions/structure/outliner/dnd.test.ts +142 -0
- package/src/extensions/structure/outliner/dnd.ts +375 -0
- package/src/extensions/structure/outliner/editor.test.ts +85 -0
- package/src/extensions/{outliner → structure/outliner}/editor.ts +14 -9
- package/src/extensions/{outliner → structure/outliner}/index.ts +1 -0
- package/src/extensions/{outliner → structure/outliner}/menu.ts +48 -25
- package/src/extensions/structure/outliner/outliner.test.ts +193 -0
- package/src/extensions/structure/outliner/outliner.ts +183 -0
- package/src/extensions/{outliner → structure/outliner}/tree.test.ts +2 -2
- package/src/extensions/{outliner → structure/outliner}/tree.ts +1 -1
- package/src/headless.ts +13 -0
- package/src/styles/theme.ts +3 -1
- package/src/util/util.ts +3 -0
- package/dist/lib/browser/index.mjs +0 -9594
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/types/index.mjs +0 -33
- package/dist/lib/browser/types/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -9596
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/types/index.mjs +0 -35
- package/dist/lib/node-esm/types/index.mjs.map +0 -7
- package/dist/types/src/extensions/annotations.d.ts.map +0 -1
- package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +0 -1
- package/dist/types/src/extensions/autocomplete/index.d.ts.map +0 -1
- package/dist/types/src/extensions/autocomplete/match.d.ts.map +0 -1
- package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +0 -1
- package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/defs.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/index.d.ts +0 -2
- package/dist/types/src/extensions/automerge/index.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +0 -1
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +0 -1
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +0 -1
- package/dist/types/src/extensions/awareness/awareness.d.ts.map +0 -1
- package/dist/types/src/extensions/awareness/index.d.ts.map +0 -1
- package/dist/types/src/extensions/blast.d.ts.map +0 -1
- package/dist/types/src/extensions/blocks.d.ts +0 -2
- package/dist/types/src/extensions/blocks.d.ts.map +0 -1
- package/dist/types/src/extensions/bookmarks.d.ts.map +0 -1
- package/dist/types/src/extensions/busy.d.ts.map +0 -1
- package/dist/types/src/extensions/comments.d.ts.map +0 -1
- package/dist/types/src/extensions/debug.d.ts.map +0 -1
- package/dist/types/src/extensions/dnd.d.ts.map +0 -1
- package/dist/types/src/extensions/factories.d.ts.map +0 -1
- package/dist/types/src/extensions/factories.test.d.ts.map +0 -1
- package/dist/types/src/extensions/fader.d.ts.map +0 -1
- package/dist/types/src/extensions/focus.d.ts.map +0 -1
- package/dist/types/src/extensions/folding.d.ts.map +0 -1
- package/dist/types/src/extensions/hashtag.d.ts +0 -3
- package/dist/types/src/extensions/hashtag.d.ts.map +0 -1
- package/dist/types/src/extensions/json.d.ts +0 -7
- package/dist/types/src/extensions/json.d.ts.map +0 -1
- package/dist/types/src/extensions/listener.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/action.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/changes.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/changes.test.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/debug.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/image.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/image.test.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/index.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/link.d.ts +0 -7
- package/dist/types/src/extensions/markdown/link.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/parser.test.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/styles.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/table.d.ts.map +0 -1
- package/dist/types/src/extensions/marker.d.ts.map +0 -1
- package/dist/types/src/extensions/mention.d.ts.map +0 -1
- package/dist/types/src/extensions/modal.d.ts.map +0 -1
- package/dist/types/src/extensions/modes.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/commands.d.ts +0 -10
- package/dist/types/src/extensions/outliner/commands.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/editor.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/editor.test.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/index.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/menu.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/outliner.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/selection.d.ts +0 -12
- package/dist/types/src/extensions/outliner/selection.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/tree.d.ts.map +0 -1
- package/dist/types/src/extensions/outliner/tree.test.d.ts.map +0 -1
- package/dist/types/src/extensions/pending/index.d.ts.map +0 -1
- package/dist/types/src/extensions/pending/pending-text-stream.d.ts.map +0 -1
- package/dist/types/src/extensions/pending/pending-text-stream.test.d.ts.map +0 -1
- package/dist/types/src/extensions/pending/pending-text.d.ts.map +0 -1
- package/dist/types/src/extensions/pending/pending-text.test.d.ts.map +0 -1
- package/dist/types/src/extensions/pos.d.ts.map +0 -1
- package/dist/types/src/extensions/pos.test.d.ts.map +0 -1
- package/dist/types/src/extensions/replacer.d.ts.map +0 -1
- package/dist/types/src/extensions/replacer.test.d.ts.map +0 -1
- package/dist/types/src/extensions/scrolling/auto-scroll.d.ts.map +0 -1
- package/dist/types/src/extensions/scrolling/crawler.d.ts.map +0 -1
- package/dist/types/src/extensions/scrolling/index.d.ts.map +0 -1
- package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts +0 -3
- package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts.map +0 -1
- package/dist/types/src/extensions/scrolling/scrollbar-autohide.d.ts.map +0 -1
- package/dist/types/src/extensions/scrolling/scroller.d.ts.map +0 -1
- package/dist/types/src/extensions/selection.d.ts.map +0 -1
- package/dist/types/src/extensions/selection.test.d.ts.map +0 -1
- package/dist/types/src/extensions/snippets.d.ts.map +0 -1
- package/dist/types/src/extensions/spacing.d.ts.map +0 -1
- package/dist/types/src/extensions/state.d.ts +0 -2
- package/dist/types/src/extensions/state.d.ts.map +0 -1
- package/dist/types/src/extensions/submit.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/extended-markdown.test.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/index.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/stub.d.ts +0 -35
- package/dist/types/src/extensions/tags/stub.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/widgets/anchor.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/widgets/index.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +0 -31
- package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/xml-tags.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/xml-util.d.ts.map +0 -1
- package/dist/types/src/extensions/tags/xml-util.test.d.ts.map +0 -1
- package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
- package/dist/types/src/extensions/typewriter.test.d.ts.map +0 -1
- package/src/extensions/autocomplete/typeahead.ts +0 -87
- package/src/extensions/blocks.ts +0 -131
- package/src/extensions/markdown/link.ts +0 -49
- package/src/extensions/outliner/editor.test.ts +0 -33
- package/src/extensions/outliner/outliner.test.ts +0 -99
- package/src/extensions/outliner/outliner.ts +0 -166
- package/src/extensions/outliner/selection.ts +0 -50
- package/src/extensions/state.ts +0 -7
- package/src/extensions/tags/stub.ts +0 -80
- package/src/extensions/tags/xml-block-decoration.ts +0 -123
- package/dist/types/src/extensions/{dnd.d.ts → behavior/dnd.d.ts} +0 -0
- package/dist/types/src/extensions/{submit.d.ts → behavior/submit.d.ts} +0 -0
- package/dist/types/src/extensions/{automerge → collab/automerge}/automerge.d.ts +0 -0
- package/dist/types/src/extensions/{automerge → collab/automerge}/automerge.test.d.ts +0 -0
- package/dist/types/src/extensions/{automerge → collab/automerge}/sync.d.ts +0 -0
- package/dist/types/src/extensions/{awareness → collab/awareness}/awareness-provider.d.ts +0 -0
- package/dist/types/src/extensions/{awareness → collab/awareness}/index.d.ts +0 -0
- package/dist/types/src/extensions/{autocomplete → completion}/autocomplete.d.ts +0 -0
- package/dist/types/src/extensions/{autocomplete → completion}/placeholder.d.ts +0 -0
- package/dist/types/src/extensions/{factories.test.d.ts → core/factories.test.d.ts} +0 -0
- package/dist/types/src/extensions/{fader.d.ts → decoration/fader.d.ts} +0 -0
- package/dist/types/src/extensions/{marker.d.ts → decoration/marker.d.ts} +0 -0
- package/dist/types/src/extensions/{pos.test.d.ts → decoration/pos.test.d.ts} +0 -0
- package/dist/types/src/extensions/{replacer.test.d.ts → decoration/replacer.test.d.ts} +0 -0
- package/dist/types/src/extensions/{snippets.d.ts → demo/snippets.d.ts} +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/action.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/bundle.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/changes.test.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/formatting.test.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/image.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/image.test.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/index.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/parser.test.d.ts +0 -0
- package/dist/types/src/extensions/{markdown → language/markdown}/table.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/extended-markdown.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/extended-markdown.test.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/index.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/widgets/anchor.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/widgets/index.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/xml-formatting.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/xml-util.d.ts +0 -0
- package/dist/types/src/extensions/{tags → language/xml}/xml-util.test.d.ts +0 -0
- package/dist/types/src/extensions/{modal.d.ts → state/modal.d.ts} +0 -0
- package/dist/types/src/extensions/{selection.test.d.ts → state/selection.test.d.ts} +0 -0
- package/dist/types/src/extensions/{pending → streaming/pending}/index.d.ts +0 -0
- package/dist/types/src/extensions/{pending → streaming/pending}/pending-text-stream.d.ts +0 -0
- package/dist/types/src/extensions/{pending → streaming/pending}/pending-text-stream.test.d.ts +0 -0
- package/dist/types/src/extensions/{pending → streaming/pending}/pending-text.test.d.ts +0 -0
- package/dist/types/src/extensions/{scrolling → streaming/scrolling}/auto-scroll.d.ts +0 -0
- package/dist/types/src/extensions/{scrolling → streaming/scrolling}/crawler.d.ts +0 -0
- package/dist/types/src/extensions/{scrolling → streaming/scrolling}/index.d.ts +0 -0
- package/dist/types/src/extensions/{scrolling → streaming/scrolling}/scrollbar-autohide.d.ts +0 -0
- package/dist/types/src/extensions/{scrolling → streaming/scrolling}/scroller.d.ts +0 -0
- package/dist/types/src/extensions/{typewriter.test.d.ts → streaming/typewriter.test.d.ts} +0 -0
- package/dist/types/src/extensions/{folding.d.ts → structure/folding/folding.d.ts} +0 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/editor.d.ts +0 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/editor.test.d.ts +0 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/outliner.d.ts +0 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/outliner.test.d.ts +0 -0
- package/dist/types/src/extensions/{outliner → structure/outliner}/tree.test.d.ts +0 -0
- package/src/extensions/{dnd.ts → behavior/dnd.ts} +29 -29
- package/src/extensions/{awareness → collab/awareness}/index.ts +0 -0
- package/src/extensions/{factories.test.ts → core/factories.test.ts} +0 -0
- package/src/extensions/{replacer.test.ts → decoration/replacer.test.ts} +0 -0
- package/src/extensions/{replacer.ts → decoration/replacer.ts} +25 -25
- /package/src/extensions/{markdown → language/markdown}/index.ts +0 -0
- /package/src/extensions/{tags → language/xml}/index.ts +0 -0
- /package/src/extensions/{tags → language/xml}/testing/text.md +0 -0
- /package/src/extensions/{tags → language/xml}/testing/text.txt +0 -0
- /package/src/extensions/{tags → language/xml}/widgets/anchor.ts +0 -0
- /package/src/extensions/{tags → language/xml}/widgets/index.ts +0 -0
- /package/src/extensions/{tags → language/xml}/xml-util.ts +0 -0
- /package/src/extensions/{modal.ts → state/modal.ts} +0 -0
- /package/src/extensions/{selection.test.ts → state/selection.test.ts} +0 -0
- /package/src/extensions/{pending → streaming/pending}/index.ts +0 -0
- /package/src/extensions/{pending → streaming/pending}/pending-text-stream.test.ts +0 -0
- /package/src/extensions/{scrolling → streaming/scrolling}/crawler.ts +0 -0
- /package/src/extensions/{scrolling → streaming/scrolling}/index.ts +0 -0
- /package/src/extensions/{scrolling → streaming/scrolling}/scroller.ts +0 -0
- /package/src/extensions/{typewriter.test.ts → streaming/typewriter.test.ts} +0 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { getIndentUnit } from '@codemirror/language';
|
|
6
|
+
import { type EditorState, type Extension } from '@codemirror/state';
|
|
7
|
+
import { type Command, EditorView } from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
import { bulletListIndentationWidth } from '../../language/markdown/styles';
|
|
10
|
+
import {
|
|
11
|
+
type Block,
|
|
12
|
+
type BlockOps,
|
|
13
|
+
blockSelectionField,
|
|
14
|
+
createBlockDrag,
|
|
15
|
+
createBlockSelection,
|
|
16
|
+
setBlockSelection,
|
|
17
|
+
} from '../blocks';
|
|
18
|
+
import { type Item, type Tree, getRange, treeFacet } from './tree';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Flattens the tree into draggable units in document order, one per item. Each unit spans only the
|
|
22
|
+
* item's own lines (up to its first child), so the ranges are non-overlapping and every item — parent
|
|
23
|
+
* or leaf — gets its own gutter handle. Memoized per state so the gutter and drag plugin share it.
|
|
24
|
+
*/
|
|
25
|
+
const itemCache = new WeakMap<EditorState, Item[]>();
|
|
26
|
+
const flattenItems = (state: EditorState): Item[] => {
|
|
27
|
+
const cached = itemCache.get(state);
|
|
28
|
+
if (cached) {
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tree = state.facet(treeFacet);
|
|
33
|
+
const items: Item[] = [];
|
|
34
|
+
tree.traverse((item) => {
|
|
35
|
+
items.push(item);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
itemCache.set(state, items);
|
|
39
|
+
return items;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getBlocks = (state: EditorState): Block[] =>
|
|
43
|
+
flattenItems(state).map((item) => ({
|
|
44
|
+
from: item.lineRange.from,
|
|
45
|
+
// The item's own lines end just before its first child (or at its line range end for a leaf).
|
|
46
|
+
to: item.children.length > 0 ? item.children[0].lineRange.from - 1 : item.lineRange.to,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// The range a block visually occupies for selection/drag: its whole subtree (item + descendants), so
|
|
50
|
+
// selecting or dragging a parent covers its children. `getBlocks` still yields per-item own-lines.
|
|
51
|
+
// Exported for tests.
|
|
52
|
+
export const getExtent = (state: EditorState, block: Block): Block => {
|
|
53
|
+
const tree = state.facet(treeFacet);
|
|
54
|
+
const item = tree.find(block.from);
|
|
55
|
+
if (!item) {
|
|
56
|
+
return block;
|
|
57
|
+
}
|
|
58
|
+
const [from, to] = getRange(tree, item);
|
|
59
|
+
return { from, to };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Leading-whitespace width of the line at `pos` (indentation depth in characters). */
|
|
63
|
+
const indentAt = (state: EditorState, pos: number): number => {
|
|
64
|
+
const text = state.doc.lineAt(pos).text;
|
|
65
|
+
return text.length - text.trimStart().length;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolves the drop indentation for a single-item drag (see DESIGN "Drag reindent — drop level rules").
|
|
70
|
+
* The dragged subtree stays in the branch of the item above the drop slot by default, outdenting to a
|
|
71
|
+
* sibling of the (shallower) drop target only while the pointer is over that target's row. Returns the
|
|
72
|
+
* target leading-space count and the horizontal pixel offset that previews it. Exported for tests.
|
|
73
|
+
*/
|
|
74
|
+
export const getDropIndent = (
|
|
75
|
+
view: EditorView,
|
|
76
|
+
sourceIndices: number[],
|
|
77
|
+
dropIndex: number,
|
|
78
|
+
pointerY: number,
|
|
79
|
+
): { indent: number; offset: number } | null => {
|
|
80
|
+
// Multi-item group moves keep their own relative indentation (no single re-root).
|
|
81
|
+
if (sourceIndices.length !== 1) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { state } = view;
|
|
86
|
+
const items = flattenItems(state);
|
|
87
|
+
const count = items.length;
|
|
88
|
+
const unit = getIndentUnit(state);
|
|
89
|
+
const sourceItem = items[sourceIndices[0]];
|
|
90
|
+
const sourceIndent = indentAt(state, sourceItem.lineRange.from);
|
|
91
|
+
|
|
92
|
+
// The item directly above the drop slot, skipping the dragged subtree (the source's children are
|
|
93
|
+
// collapsed with it, so they cannot serve as the indent reference).
|
|
94
|
+
const [sourceFrom, sourceTo] = getRange(state.facet(treeFacet), sourceItem);
|
|
95
|
+
let aboveIndex = dropIndex - 1;
|
|
96
|
+
while (
|
|
97
|
+
aboveIndex >= 0 &&
|
|
98
|
+
items[aboveIndex].lineRange.from >= sourceFrom &&
|
|
99
|
+
items[aboveIndex].lineRange.from <= sourceTo
|
|
100
|
+
) {
|
|
101
|
+
aboveIndex--;
|
|
102
|
+
}
|
|
103
|
+
const above = aboveIndex >= 0 ? items[aboveIndex] : null;
|
|
104
|
+
const target = dropIndex < count ? items[dropIndex] : null;
|
|
105
|
+
|
|
106
|
+
const aboveIndent = above ? indentAt(state, above.lineRange.from) : 0;
|
|
107
|
+
// Valid range for the slot: a sibling of the target (shallowest) up to a child of the item above.
|
|
108
|
+
const minIndent = target ? indentAt(state, target.lineRange.from) : 0;
|
|
109
|
+
const maxIndent = above ? aboveIndent + unit : minIndent;
|
|
110
|
+
const clamp = (value: number) => Math.max(minIndent, Math.min(maxIndent, value));
|
|
111
|
+
|
|
112
|
+
// Default: keep the dragged item at its own level (staying in its branch), clamped to the slot's range.
|
|
113
|
+
let indent = clamp(sourceIndent);
|
|
114
|
+
// Outdent to a sibling of the target only at a real branch boundary and while over the target row.
|
|
115
|
+
if (target && minIndent < indent) {
|
|
116
|
+
const top = view.coordsAtPos(target.lineRange.from)?.top;
|
|
117
|
+
if (top != null && pointerY >= top) {
|
|
118
|
+
indent = minIndent;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Preview offset: one indentation width per indent unit of delta from the source's own indent.
|
|
123
|
+
const offset = ((indent - sourceIndent) / unit) * bulletListIndentationWidth;
|
|
124
|
+
return { indent, offset };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Re-roots `text` (a moved subtree) from `fromIndent` to `toIndent` by shifting every line's leading
|
|
129
|
+
* whitespace, preserving the subtree's internal relative indentation. Keeps the moved item valid at
|
|
130
|
+
* its new depth (e.g. dragging a nested task to the top level).
|
|
131
|
+
*/
|
|
132
|
+
const reindent = (text: string, fromIndent: number, toIndent: number): string => {
|
|
133
|
+
const delta = toIndent - fromIndent;
|
|
134
|
+
if (delta === 0) {
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
137
|
+
return text
|
|
138
|
+
.split('\n')
|
|
139
|
+
.map((line) => {
|
|
140
|
+
const existing = line.length - line.trimStart().length;
|
|
141
|
+
const next = Math.max(0, existing + delta);
|
|
142
|
+
return ' '.repeat(next) + line.slice(existing);
|
|
143
|
+
})
|
|
144
|
+
.join('\n');
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Moves the item at `sourceIndex` (with its whole subtree) to the slot before `dropIndex`, or to the
|
|
149
|
+
* end of the document when `dropIndex === items.length`. Expressed as a minimal delete + insert so it
|
|
150
|
+
* is a single undo step and syncs as a small edit.
|
|
151
|
+
*/
|
|
152
|
+
const moveItem = (view: EditorView, sourceIndex: number, dropIndex: number, indent?: number): void => {
|
|
153
|
+
const { state } = view;
|
|
154
|
+
const tree: Tree = state.facet(treeFacet);
|
|
155
|
+
const items = flattenItems(state);
|
|
156
|
+
const count = items.length;
|
|
157
|
+
if (sourceIndex < 0 || sourceIndex >= count || dropIndex === sourceIndex) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const source = items[sourceIndex];
|
|
162
|
+
const [from, to] = getRange(tree, source);
|
|
163
|
+
|
|
164
|
+
// Insert before the drop-target item, or append at the end.
|
|
165
|
+
const insertAt = dropIndex < count ? items[dropIndex].lineRange.from : state.doc.length;
|
|
166
|
+
// Dropping inside the item's own subtree (including immediately after its own line) is a no-op.
|
|
167
|
+
if (insertAt > from && insertAt <= to) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const sourceIndent = indentAt(state, from);
|
|
172
|
+
// The drop level resolved live during the drag (`getDropIndent`), else the drop-target's own indent.
|
|
173
|
+
const targetIndent = indent ?? (dropIndex < count ? indentAt(state, items[dropIndex].lineRange.from) : 0);
|
|
174
|
+
const text = reindent(state.doc.sliceString(from, to), sourceIndent, targetIndent);
|
|
175
|
+
|
|
176
|
+
// Remove the subtree together with one adjacent newline so no blank line is orphaned.
|
|
177
|
+
const hasFollowing = to < state.doc.length;
|
|
178
|
+
const deleteFrom = hasFollowing ? from : Math.max(0, from - 1);
|
|
179
|
+
const deleteTo = hasFollowing ? Math.min(to + 1, state.doc.length) : to;
|
|
180
|
+
const insert = dropIndex < count ? `${text}\n` : `\n${text}`;
|
|
181
|
+
|
|
182
|
+
// Sort the two changes by position (CodeMirror requires ascending, non-overlapping specs).
|
|
183
|
+
const changes =
|
|
184
|
+
insertAt <= deleteFrom
|
|
185
|
+
? [
|
|
186
|
+
{ from: insertAt, insert },
|
|
187
|
+
{ from: deleteFrom, to: deleteTo },
|
|
188
|
+
]
|
|
189
|
+
: [
|
|
190
|
+
{ from: deleteFrom, to: deleteTo },
|
|
191
|
+
{ from: insertAt, insert },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
// Caret at the end of the moved item; keep the moved item selected (its new anchor is where the moved
|
|
195
|
+
// text starts — the subtree's first line).
|
|
196
|
+
const changeSet = state.changes(changes);
|
|
197
|
+
const textStart = changeSet.mapPos(insertAt, -1) + (dropIndex < count ? 0 : 1);
|
|
198
|
+
const caret = textStart + text.length;
|
|
199
|
+
view.dispatch({
|
|
200
|
+
changes,
|
|
201
|
+
selection: { anchor: Math.min(caret, changeSet.newLength) },
|
|
202
|
+
effects: setBlockSelection.of([textStart]),
|
|
203
|
+
userEvent: 'move.item',
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Merges sorted ranges, combining any that overlap or touch. Exported for the menu commands.
|
|
208
|
+
export const mergeRanges = (ranges: { from: number; to: number }[]): { from: number; to: number }[] => {
|
|
209
|
+
const sorted = [...ranges].sort((a, b) => a.from - b.from);
|
|
210
|
+
const merged: { from: number; to: number }[] = [];
|
|
211
|
+
for (const range of sorted) {
|
|
212
|
+
const last = merged.at(-1);
|
|
213
|
+
if (last && range.from <= last.to) {
|
|
214
|
+
last.to = Math.max(last.to, range.to);
|
|
215
|
+
} else {
|
|
216
|
+
merged.push({ ...range });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return merged;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Delete ranges for the given items: each item's own lines plus its separating newline (the one below,
|
|
223
|
+
// down to the next item; for the last item the one above instead, so no trailing blank is orphaned),
|
|
224
|
+
// merged so adjacent selections form one range.
|
|
225
|
+
const deleteRanges = (state: EditorState, blocks: Block[], indices: number[]): { from: number; to: number }[] => {
|
|
226
|
+
const count = blocks.length;
|
|
227
|
+
return mergeRanges(
|
|
228
|
+
indices.map((index) => ({
|
|
229
|
+
from: index === count - 1 && index > 0 ? blocks[index - 1].to : blocks[index].from,
|
|
230
|
+
to: index + 1 < count ? blocks[index + 1].from : state.doc.length,
|
|
231
|
+
})),
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Moves the items at `sourceIndices` to the slot before `dropIndex`. A single item moves with its whole
|
|
237
|
+
* subtree (re-indented — see `moveItem`); multiple items move by their own lines as a group (a
|
|
238
|
+
* best-effort for multi-selection reorder).
|
|
239
|
+
*/
|
|
240
|
+
// Exported for tests (the drag path calls this via `outlinerBlockOps`).
|
|
241
|
+
export const moveBlocks = (view: EditorView, sourceIndices: number[], dropIndex: number, indent?: number): void => {
|
|
242
|
+
const { state } = view;
|
|
243
|
+
const items = flattenItems(state);
|
|
244
|
+
const count = items.length;
|
|
245
|
+
const sources = [...new Set(sourceIndices)].filter((index) => index >= 0 && index < count).sort((a, b) => a - b);
|
|
246
|
+
if (sources.length === 0 || sources.includes(dropIndex)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (sources.length === 1) {
|
|
250
|
+
// Single-item drags re-root the whole subtree to the drag's resolved drop level (`getDropIndent`).
|
|
251
|
+
moveItem(view, sources[0], dropIndex, indent);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const blocks = getBlocks(state);
|
|
256
|
+
const text = sources.map((index) => state.doc.sliceString(blocks[index].from, blocks[index].to)).join('\n');
|
|
257
|
+
const deletes = deleteRanges(state, blocks, sources);
|
|
258
|
+
const insertAt = dropIndex < count ? blocks[dropIndex].from : state.doc.length;
|
|
259
|
+
if (deletes.some((range) => insertAt > range.from && insertAt < range.to)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const insert = dropIndex < count ? `${text}\n` : `\n${text}`;
|
|
264
|
+
const changes = [...deletes.map((range) => ({ from: range.from, to: range.to })), { from: insertAt, insert }];
|
|
265
|
+
const changeSet = state.changes(changes);
|
|
266
|
+
const textStart = changeSet.mapPos(insertAt, -1) + (dropIndex < count ? 0 : 1);
|
|
267
|
+
// Keep the moved items selected: their new anchors are the start of each item within the inserted
|
|
268
|
+
// text, joined by a single newline (item length + 1). The caret lands at the end of the moved content.
|
|
269
|
+
const anchors: number[] = [];
|
|
270
|
+
let anchor = textStart;
|
|
271
|
+
for (const index of sources) {
|
|
272
|
+
anchors.push(anchor);
|
|
273
|
+
anchor += blocks[index].to - blocks[index].from + 1;
|
|
274
|
+
}
|
|
275
|
+
const caret = textStart + text.length;
|
|
276
|
+
view.dispatch({
|
|
277
|
+
changes,
|
|
278
|
+
selection: { anchor: Math.min(caret, changeSet.newLength) },
|
|
279
|
+
effects: setBlockSelection.of(anchors),
|
|
280
|
+
userEvent: 'move.item',
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/** Removes the items at `indices`, optionally replacing the first slot with `text` (paste). */
|
|
285
|
+
// Exported for tests (the drag/clipboard path calls this via `outlinerBlockOps`).
|
|
286
|
+
export const replaceBlocks = (view: EditorView, indices: number[], text: string | null): void => {
|
|
287
|
+
const { state } = view;
|
|
288
|
+
const blocks = getBlocks(state);
|
|
289
|
+
const count = blocks.length;
|
|
290
|
+
const sources = [...new Set(indices)].filter((index) => index >= 0 && index < count).sort((a, b) => a - b);
|
|
291
|
+
if (sources.length === 0) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const deletes = deleteRanges(state, blocks, sources);
|
|
296
|
+
const changes = deletes.map(
|
|
297
|
+
(range) => ({ from: range.from, to: range.to }) as { from: number; to: number; insert?: string },
|
|
298
|
+
);
|
|
299
|
+
let anchor = deletes[0].from;
|
|
300
|
+
if (text != null && text.length > 0) {
|
|
301
|
+
const insert = deletes[0].to >= state.doc.length ? text : `${text}\n`;
|
|
302
|
+
changes[0] = { from: deletes[0].from, to: deletes[0].to, insert };
|
|
303
|
+
anchor = deletes[0].from + text.length;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
view.dispatch({
|
|
307
|
+
changes,
|
|
308
|
+
selection: { anchor: Math.min(anchor, state.doc.length) },
|
|
309
|
+
effects: setBlockSelection.of([]),
|
|
310
|
+
userEvent: text == null ? 'delete.block' : 'input.paste',
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const outlinerBlockOps: BlockOps = {
|
|
315
|
+
getBlocks,
|
|
316
|
+
moveBlocks,
|
|
317
|
+
serialize: (state, selected) => selected.map((block) => state.doc.sliceString(block.from, block.to)).join('\n'),
|
|
318
|
+
replaceBlocks,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Block selection, drag-to-reorder, and cut/copy/paste for outliner items. Single-item drag relocates
|
|
323
|
+
* the whole subtree (re-indented); the generic block-selection and clipboard from the `blocks` extensions
|
|
324
|
+
* operate on item lines. The grip is a floating overlay pinned to the content's left edge (see
|
|
325
|
+
* `createBlockDrag`), so it needs no outliner-specific gutter styling.
|
|
326
|
+
*/
|
|
327
|
+
export const outlinerDnd = (): Extension => [
|
|
328
|
+
// The selection highlight is drawn by `outliner.ts` as a line decoration (aligned to the actual rows);
|
|
329
|
+
// the `blocks` RectangleMarker layer is skipped here because it drifts against the outliner's row CSS.
|
|
330
|
+
createBlockSelection(outlinerBlockOps),
|
|
331
|
+
createBlockDrag({ getBlocks, moveBlocks, getExtent, keepTrailingBreak: true, getDropIndent }),
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
//
|
|
335
|
+
// Selection commands over the block selection (each item's line-start anchor). Replaces the outliner's
|
|
336
|
+
// former `selectionFacet`, so keyboard selection and the grip share one model.
|
|
337
|
+
//
|
|
338
|
+
|
|
339
|
+
const itemAnchors = (state: EditorState): number[] => flattenItems(state).map((item) => item.lineRange.from);
|
|
340
|
+
|
|
341
|
+
/** Select every item; pressing again (when all are selected) clears the selection. */
|
|
342
|
+
export const selectAllItems: Command = (view) => {
|
|
343
|
+
const anchors = itemAnchors(view.state);
|
|
344
|
+
const current = view.state.field(blockSelectionField, false) ?? [];
|
|
345
|
+
view.dispatch({ effects: setBlockSelection.of(current.length === anchors.length ? [] : anchors) });
|
|
346
|
+
return true;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/** Clear the block selection. */
|
|
350
|
+
export const selectNoneItems: Command = (view) => {
|
|
351
|
+
view.dispatch({ effects: setBlockSelection.of([]) });
|
|
352
|
+
return true;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Extend the block selection by one item toward `delta`, moving the caret into the target item.
|
|
356
|
+
const extendSelection = (view: EditorView, delta: -1 | 1): boolean => {
|
|
357
|
+
const items = flattenItems(view.state);
|
|
358
|
+
const caret = view.state.selection.main.head;
|
|
359
|
+
const index = items.findIndex((item) => caret >= item.lineRange.from && caret <= item.lineRange.to);
|
|
360
|
+
if (index < 0) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const target = Math.min(items.length - 1, Math.max(0, index + delta));
|
|
364
|
+
const anchors = new Set(view.state.field(blockSelectionField, false) ?? []);
|
|
365
|
+
anchors.add(items[index].lineRange.from);
|
|
366
|
+
anchors.add(items[target].lineRange.from);
|
|
367
|
+
view.dispatch({
|
|
368
|
+
selection: { anchor: items[target].contentRange.from },
|
|
369
|
+
effects: setBlockSelection.of([...anchors].sort((a, b) => a - b)),
|
|
370
|
+
});
|
|
371
|
+
return true;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
export const selectUp: Command = (view) => extendSelection(view, -1);
|
|
375
|
+
export const selectDown: Command = (view) => extendSelection(view, 1);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
|
6
|
+
import { type ChangeSpec, EditorSelection, EditorState } from '@codemirror/state';
|
|
7
|
+
import { describe, test } from 'vitest';
|
|
8
|
+
|
|
9
|
+
import { editor } from './editor';
|
|
10
|
+
import { outlinerTree, treeFacet } from './tree';
|
|
11
|
+
|
|
12
|
+
const extensions = [markdown({ base: markdownLanguage }), outlinerTree(), editor()];
|
|
13
|
+
|
|
14
|
+
// Two items: item 1 content [6,7]='A'; line 2 starts at 8, item 2 marker [8,14), content [14,15]='B'.
|
|
15
|
+
const DOC = '- [ ] A\n- [ ] B';
|
|
16
|
+
|
|
17
|
+
// Resulting caret after moving it to `pos` (the transaction filter snaps out-of-range positions).
|
|
18
|
+
const snap = (doc: string, pos: number): number =>
|
|
19
|
+
EditorState.create({ doc, extensions }).update({ selection: EditorSelection.cursor(pos) }).state.selection.main.from;
|
|
20
|
+
|
|
21
|
+
// Resulting caret after moving from `prev` to `next` (the filter uses the delta to pick a direction).
|
|
22
|
+
const snapFrom = (doc: string, prev: number, next: number): number => {
|
|
23
|
+
const state = EditorState.create({ doc, extensions }).update({ selection: EditorSelection.cursor(prev) }).state;
|
|
24
|
+
return state.update({ selection: EditorSelection.cursor(next) }).state.selection.main.from;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Resulting document after applying `spec` with the caret at `cursor`.
|
|
28
|
+
const applyChange = (doc: string, cursor: number, spec: ChangeSpec): string => {
|
|
29
|
+
const state = EditorState.create({ doc, extensions }).update({ selection: EditorSelection.cursor(cursor) }).state;
|
|
30
|
+
return state.update({ changes: spec }).state.doc.toString();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe('editor', () => {
|
|
34
|
+
test('an empty document still has a tree', ({ expect }) => {
|
|
35
|
+
const state = EditorState.create({ extensions });
|
|
36
|
+
expect(state.facet(treeFacet)).to.exist;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('selection guard', () => {
|
|
40
|
+
test('snaps a caret placed inside the marker to the content start', ({ expect }) => {
|
|
41
|
+
for (const pos of [0, 1, 3, 5]) {
|
|
42
|
+
expect(snap('- [ ] ', pos)).to.eq(6);
|
|
43
|
+
}
|
|
44
|
+
// A caret already at the content start is left alone.
|
|
45
|
+
expect(snap('- [ ] ', 6)).to.eq(6);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('snaps within a later item too', ({ expect }) => {
|
|
49
|
+
expect(snap(DOC, 9)).to.eq(14); // Inside item 2's marker.
|
|
50
|
+
expect(snap(DOC, 10)).to.eq(14);
|
|
51
|
+
expect(snap(DOC, 14)).to.eq(14); // Content start is valid.
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('moving down into an item lands at its content start', ({ expect }) => {
|
|
55
|
+
expect(snapFrom(DOC, 7, 10)).to.eq(14);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('moving up into an item lands at its content end', ({ expect }) => {
|
|
59
|
+
expect(snapFrom(DOC, 14, 4)).to.eq(7);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('moving left across the marker goes to the previous line end', ({ expect }) => {
|
|
63
|
+
expect(snapFrom(DOC, 14, 13)).to.eq(7);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('change validation', () => {
|
|
68
|
+
test('allows deleting a whole item line', ({ expect }) => {
|
|
69
|
+
expect(applyChange(DOC, 0, { from: 0, to: 8 })).to.eq('- [ ] B');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('allows typing into the content', ({ expect }) => {
|
|
73
|
+
expect(applyChange('- [ ] ', 6, { from: 6, to: 6, insert: 'X' })).to.eq('- [ ] X');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('allows editing within the content', ({ expect }) => {
|
|
77
|
+
expect(applyChange('- [ ] A', 7, { from: 6, to: 7 })).to.eq('- [ ] ');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Task-marker atomicity (backspace on `- [ ] ` removing the whole marker, or inserting a continuation
|
|
82
|
+
// indent) is enforced by the markdown extension's keymap, which produces the specific change shapes the
|
|
83
|
+
// filter reacts to — not by raw `changes`. Covering it needs the full keymap dispatched through a view.
|
|
84
|
+
test.todo('task-marker atomicity (needs the markdown keymap, not a raw change)');
|
|
85
|
+
});
|
|
@@ -7,7 +7,6 @@ import { type EditorView, ViewPlugin } from '@codemirror/view';
|
|
|
7
7
|
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
|
|
10
|
-
import { getSelection } from './selection';
|
|
11
10
|
import { treeFacet } from './tree';
|
|
12
11
|
|
|
13
12
|
const LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
|
|
@@ -18,12 +17,17 @@ const LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
|
|
|
18
17
|
const initialize = () => {
|
|
19
18
|
return ViewPlugin.fromClass(
|
|
20
19
|
class {
|
|
20
|
+
#timer: ReturnType<typeof setTimeout> | null = null;
|
|
21
|
+
|
|
21
22
|
constructor(view: EditorView) {
|
|
22
23
|
const first = view.state.doc.lineAt(0);
|
|
23
24
|
const text = view.state.sliceDoc(first.from, first.to);
|
|
24
25
|
const match = text.match(LIST_ITEM_REGEX);
|
|
25
26
|
if (!match) {
|
|
26
|
-
|
|
27
|
+
// Deferred so the dispatch runs after the view finishes constructing; cleared on destroy so it
|
|
28
|
+
// never fires on a torn-down view.
|
|
29
|
+
this.#timer = setTimeout(() => {
|
|
30
|
+
this.#timer = null;
|
|
27
31
|
const insert = '- [ ] ';
|
|
28
32
|
view.dispatch({
|
|
29
33
|
changes: [{ from: 0, to: 0, insert }],
|
|
@@ -32,6 +36,12 @@ const initialize = () => {
|
|
|
32
36
|
});
|
|
33
37
|
}
|
|
34
38
|
}
|
|
39
|
+
|
|
40
|
+
destroy() {
|
|
41
|
+
if (this.#timer != null) {
|
|
42
|
+
clearTimeout(this.#timer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
35
45
|
},
|
|
36
46
|
);
|
|
37
47
|
};
|
|
@@ -49,7 +59,7 @@ export const editor = () => [
|
|
|
49
59
|
// Check cursor is in a valid position.
|
|
50
60
|
//
|
|
51
61
|
if (!tr.docChanged) {
|
|
52
|
-
const current =
|
|
62
|
+
const current = tr.state.selection.main.from;
|
|
53
63
|
if (current != null) {
|
|
54
64
|
const currentItem = tree.find(current);
|
|
55
65
|
if (!currentItem) {
|
|
@@ -58,7 +68,7 @@ export const editor = () => [
|
|
|
58
68
|
|
|
59
69
|
// Check if outside of editable range.
|
|
60
70
|
if (current < currentItem.contentRange.from || current > currentItem.contentRange.to) {
|
|
61
|
-
const prev =
|
|
71
|
+
const prev = tr.startState.selection.main.from;
|
|
62
72
|
const prevItem = prev != null ? tree.find(prev) : undefined;
|
|
63
73
|
if (!prevItem) {
|
|
64
74
|
return [{ selection: EditorSelection.cursor(currentItem.contentRange.from) }];
|
|
@@ -110,11 +120,6 @@ export const editor = () => [
|
|
|
110
120
|
return;
|
|
111
121
|
}
|
|
112
122
|
|
|
113
|
-
// if (!deleteLine && (!startItem || fromA < startItem.contentRange.from || toA > startItem.contentRange.to)) {
|
|
114
|
-
// cancel = true;
|
|
115
|
-
// return;
|
|
116
|
-
// }
|
|
117
|
-
|
|
118
123
|
// Check valid item.
|
|
119
124
|
const currentItem = tree.find(tr.state.selection.main.from);
|
|
120
125
|
if (!currentItem?.contentRange) {
|
|
@@ -6,6 +6,13 @@ import { type Extension } from '@codemirror/state';
|
|
|
6
6
|
import { EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
|
7
7
|
|
|
8
8
|
import { type CleanupFn, addEventListener } from '@dxos/async';
|
|
9
|
+
import { Domino } from '@dxos/ui';
|
|
10
|
+
|
|
11
|
+
import { GUTTER_WIDTH } from '../blocks';
|
|
12
|
+
|
|
13
|
+
// Square trigger size (px), matching the drag grip (`dx-button` density `xs` + `aspect-square` → `size-6`).
|
|
14
|
+
// The right-hand strip (`GUTTER_WIDTH`, shared with the grip's left strip) centers the trigger within it.
|
|
15
|
+
const TRIGGER_SIZE = 24;
|
|
9
16
|
|
|
10
17
|
export type MenuOptions = {
|
|
11
18
|
icon?: string;
|
|
@@ -13,6 +20,7 @@ export type MenuOptions = {
|
|
|
13
20
|
padding?: number;
|
|
14
21
|
};
|
|
15
22
|
|
|
23
|
+
/** Floating action button positioned beside the active outliner line. */
|
|
16
24
|
// TODO(burdon): Replace with popover.
|
|
17
25
|
export const menu = (options: MenuOptions = {}): Extension => [
|
|
18
26
|
ViewPlugin.fromClass(
|
|
@@ -31,14 +39,16 @@ export const menu = (options: MenuOptions = {}): Extension => [
|
|
|
31
39
|
container.style.position = 'relative';
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
// Outer `dx-anchor` (fires `dx-anchor-activate`, driving the popover) styled as a `dx-button`;
|
|
43
|
+
// inner element holds the phosphor glyph. Mirrors the drag grip's construction.
|
|
44
|
+
this.tag = Domino.of('dx-anchor')
|
|
45
|
+
.classNames('dx-button aspect-square cm-popover-trigger')
|
|
46
|
+
.attributes({ 'data-variant': 'ghost', 'data-density': 'xs' })
|
|
47
|
+
.append(
|
|
48
|
+
Domino.of('div')
|
|
49
|
+
.classNames('cm-popover-trigger-icon')
|
|
50
|
+
.append(Domino.svg(options.icon ?? 'ph--dots-three-vertical--regular')),
|
|
51
|
+
).root;
|
|
42
52
|
|
|
43
53
|
container.appendChild(this.tag);
|
|
44
54
|
|
|
@@ -86,14 +96,18 @@ export const menu = (options: MenuOptions = {}): Extension => [
|
|
|
86
96
|
const line = this.view.lineBlockAt(pos);
|
|
87
97
|
const coords = this.view.coordsAtPos(line.from);
|
|
88
98
|
if (!coords) {
|
|
99
|
+
// No resolvable line (e.g. the caret's item was just deleted): hide rather than leave the
|
|
100
|
+
// trigger stranded at its previous position.
|
|
101
|
+
this.tag.style.display = 'none';
|
|
89
102
|
return;
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
const lineHeight = coords.bottom - coords.top;
|
|
93
|
-
const dy = (lineHeight - (options.height ??
|
|
106
|
+
const dy = (lineHeight - (options.height ?? TRIGGER_SIZE)) / 2;
|
|
94
107
|
|
|
95
108
|
const offsetTop = coords.top + dy;
|
|
96
|
-
|
|
109
|
+
// Center the trigger within the 3rem gutter immediately right of the content (mirrors the grip).
|
|
110
|
+
const offsetLeft = x + width + GUTTER_WIDTH / 2 - TRIGGER_SIZE / 2;
|
|
97
111
|
|
|
98
112
|
this.tag.style.top = `${offsetTop}px`;
|
|
99
113
|
this.tag.style.left = `${offsetLeft}px`;
|
|
@@ -110,19 +124,28 @@ export const menu = (options: MenuOptions = {}): Extension => [
|
|
|
110
124
|
},
|
|
111
125
|
),
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
'.cm-popover-trigger': {
|
|
115
|
-
position: 'fixed',
|
|
116
|
-
padding: '0',
|
|
117
|
-
border: 'none',
|
|
118
|
-
opacity: '0',
|
|
119
|
-
display: 'grid',
|
|
120
|
-
placeContent: 'center',
|
|
121
|
-
width: '2rem',
|
|
122
|
-
height: '2rem',
|
|
123
|
-
},
|
|
124
|
-
'&:focus-within .cm-popover-trigger': {
|
|
125
|
-
opacity: '1',
|
|
126
|
-
},
|
|
127
|
-
}),
|
|
127
|
+
styles,
|
|
128
128
|
];
|
|
129
|
+
|
|
130
|
+
const styles = EditorView.theme({
|
|
131
|
+
// `dx-button` owns the box (size, hover); this only pins it and gates visibility on editor focus.
|
|
132
|
+
'.cm-popover-trigger': {
|
|
133
|
+
position: 'fixed',
|
|
134
|
+
opacity: '0',
|
|
135
|
+
cursor: 'pointer',
|
|
136
|
+
},
|
|
137
|
+
'.cm-popover-trigger-icon': {
|
|
138
|
+
display: 'grid',
|
|
139
|
+
placeContent: 'center',
|
|
140
|
+
fontSize: '16px',
|
|
141
|
+
color: 'var(--color-description, currentColor)',
|
|
142
|
+
},
|
|
143
|
+
'&:focus-within .cm-popover-trigger': {
|
|
144
|
+
opacity: '1',
|
|
145
|
+
},
|
|
146
|
+
// Hide the trigger while a block drag is in flight (the scroller carries `cm-blockDragging`).
|
|
147
|
+
'.cm-scroller.cm-blockDragging .cm-popover-trigger': {
|
|
148
|
+
opacity: '0 !important',
|
|
149
|
+
pointerEvents: 'none',
|
|
150
|
+
},
|
|
151
|
+
});
|