@dxos/react-ui-editor 0.8.1-staging.9eaf14f → 0.8.2-main.10c050d
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/browser/index.mjs +4152 -2852
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +6 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/node/index.cjs +3318 -2009
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +29 -0
- package/dist/lib/node/testing/index.cjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +4152 -2852
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +8 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +1 -1
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/blocks.d.ts +4 -3
- package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/formatting.d.ts +4 -3
- package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/headings.d.ts +4 -3
- package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/{comment.d.ts → image.d.ts} +4 -5
- package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/index.d.ts +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/lists.d.ts +4 -3
- package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/search.d.ts +17 -0
- package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/util.d.ts +17 -25
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/{viewMode.d.ts → view-mode.d.ts} +5 -4
- package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -0
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +21 -0
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +1 -0
- package/dist/types/src/components/Popover/RefPopover.d.ts +21 -0
- package/dist/types/src/components/Popover/RefPopover.d.ts.map +1 -0
- package/dist/types/src/components/Popover/index.d.ts +3 -0
- package/dist/types/src/components/Popover/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +3 -5
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/annotations.d.ts +4 -1
- package/dist/types/src/extensions/annotations.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +1 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts +1 -1
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
- package/dist/types/src/extensions/blast.d.ts.map +1 -1
- package/dist/types/src/extensions/command/action.d.ts +17 -0
- package/dist/types/src/extensions/command/action.d.ts.map +1 -0
- package/dist/types/src/extensions/command/command.d.ts +4 -10
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts +18 -4
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +3 -0
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +6 -11
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +9 -11
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/command/typeahead.d.ts +17 -0
- package/dist/types/src/extensions/command/typeahead.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +9 -17
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/debug.d.ts.map +1 -1
- package/dist/types/src/extensions/dnd.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +4 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/folding.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +3 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/json.d.ts +7 -0
- package/dist/types/src/extensions/json.d.ts.map +1 -0
- package/dist/types/src/extensions/listener.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/{editorAction.d.ts → action.d.ts} +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts +2 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts +5 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +3 -3
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts +4 -1
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
- package/dist/types/src/extensions/mention.d.ts.map +1 -1
- package/dist/types/src/extensions/modes.d.ts +5 -5
- package/dist/types/src/extensions/modes.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/commands.d.ts +10 -0
- package/dist/types/src/extensions/outliner/commands.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.d.ts +5 -0
- package/dist/types/src/extensions/outliner/editor.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/index.d.ts +4 -0
- package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts +13 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/selection.d.ts +12 -0
- package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.d.ts +79 -0
- package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts.map +1 -0
- package/dist/types/src/extensions/preview/index.d.ts +2 -0
- package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
- package/dist/types/src/extensions/preview/preview.d.ts +39 -0
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
- package/dist/types/src/extensions/selection.d.ts.map +1 -1
- package/dist/types/src/extensions/typewriter.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +0 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts +2 -1
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/stories/Command.stories.d.ts +7 -0
- package/dist/types/src/stories/Command.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Comments.stories.d.ts +13 -0
- package/dist/types/src/stories/Comments.stories.d.ts.map +1 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts +12 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Experimental.stories.d.ts +16 -0
- package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Markdown.stories.d.ts +46 -0
- package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Outliner.stories.d.ts +26 -0
- package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Preview.stories.d.ts +10 -0
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -0
- package/dist/types/src/stories/TextEditor.stories.d.ts +55 -0
- package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -0
- package/dist/types/src/stories/util.d.ts +53 -0
- package/dist/types/src/stories/util.d.ts.map +1 -0
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/util.d.ts +2 -0
- package/dist/types/src/testing/util.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +5 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/cursor.d.ts.map +1 -1
- package/dist/types/src/util/debug.d.ts.map +1 -1
- package/dist/types/src/util/dom.d.ts.map +1 -1
- package/dist/types/src/util/facet.d.ts.map +1 -1
- package/dist/types/src/util/react.d.ts +6 -1
- package/dist/types/src/util/react.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +46 -30
- package/src/components/EditorToolbar/EditorToolbar.tsx +95 -72
- package/src/components/EditorToolbar/blocks.ts +27 -6
- package/src/components/EditorToolbar/formatting.ts +34 -7
- package/src/components/EditorToolbar/headings.ts +9 -8
- package/src/components/EditorToolbar/image.ts +16 -0
- package/src/components/EditorToolbar/index.ts +7 -1
- package/src/components/EditorToolbar/lists.ts +26 -7
- package/src/components/EditorToolbar/search.ts +19 -0
- package/src/components/EditorToolbar/util.ts +19 -20
- package/src/components/EditorToolbar/{viewMode.ts → view-mode.ts} +9 -8
- package/src/components/Popover/RefDropdownMenu.tsx +77 -0
- package/src/components/Popover/RefPopover.tsx +75 -0
- package/src/components/Popover/index.ts +6 -0
- package/src/components/index.ts +1 -0
- package/src/defaults.ts +12 -13
- package/src/extensions/annotations.ts +41 -64
- package/src/extensions/autocomplete.ts +5 -6
- package/src/extensions/automerge/automerge.stories.tsx +13 -24
- package/src/extensions/automerge/automerge.test.tsx +6 -5
- package/src/extensions/automerge/automerge.ts +2 -2
- package/src/extensions/automerge/defs.ts +1 -2
- package/src/extensions/automerge/sync.ts +7 -7
- package/src/extensions/automerge/update-automerge.ts +1 -1
- package/src/extensions/automerge/update-codemirror.ts +3 -4
- package/src/extensions/awareness/awareness-provider.ts +4 -4
- package/src/extensions/awareness/awareness.ts +7 -7
- package/src/extensions/blast.ts +9 -9
- package/src/extensions/command/action.ts +49 -0
- package/src/extensions/command/command.ts +7 -27
- package/src/extensions/command/hint.ts +36 -33
- package/src/extensions/command/index.ts +3 -0
- package/src/extensions/command/menu.ts +79 -51
- package/src/extensions/command/state.ts +41 -61
- package/src/extensions/command/typeahead.ts +116 -0
- package/src/extensions/comments.ts +11 -76
- package/src/extensions/factories.ts +13 -0
- package/src/extensions/folding.tsx +1 -1
- package/src/extensions/index.ts +3 -0
- package/src/extensions/json.ts +56 -0
- package/src/extensions/markdown/bundle.ts +13 -9
- package/src/extensions/markdown/changes.ts +3 -2
- package/src/extensions/markdown/decorate.ts +19 -17
- package/src/extensions/markdown/formatting.ts +6 -6
- package/src/extensions/markdown/image.ts +14 -13
- package/src/extensions/markdown/index.ts +1 -1
- package/src/extensions/markdown/link.ts +33 -24
- package/src/extensions/markdown/styles.ts +4 -3
- package/src/extensions/markdown/table.ts +3 -3
- package/src/extensions/modes.ts +5 -6
- package/src/extensions/outliner/commands.ts +270 -0
- package/src/extensions/outliner/editor.test.ts +33 -0
- package/src/extensions/outliner/editor.ts +184 -0
- package/src/extensions/outliner/index.ts +7 -0
- package/src/extensions/outliner/outliner.test.ts +99 -0
- package/src/extensions/outliner/outliner.ts +168 -0
- package/src/extensions/outliner/selection.ts +50 -0
- package/src/extensions/outliner/tree.test.ts +164 -0
- package/src/extensions/outliner/tree.ts +315 -0
- package/src/extensions/preview/index.ts +5 -0
- package/src/extensions/preview/preview.ts +271 -0
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useTextEditor.ts +4 -3
- package/src/stories/Command.stories.tsx +97 -0
- package/src/stories/Comments.stories.tsx +98 -0
- package/src/stories/EditorToolbar.stories.tsx +96 -0
- package/src/stories/Experimental.stories.tsx +86 -0
- package/src/stories/Markdown.stories.tsx +121 -0
- package/src/stories/Outliner.stories.tsx +108 -0
- package/src/stories/Preview.stories.tsx +149 -0
- package/src/stories/TextEditor.stories.tsx +256 -0
- package/src/stories/util.tsx +326 -0
- package/src/styles/theme.ts +15 -5
- package/src/styles/tokens.ts +1 -2
- package/src/testing/index.ts +5 -0
- package/src/testing/util.ts +5 -0
- package/src/types.ts +7 -0
- package/src/util/react.tsx +20 -2
- package/dist/types/src/InputMode.stories.d.ts +0 -57
- package/dist/types/src/InputMode.stories.d.ts.map +0 -1
- package/dist/types/src/TextEditor.stories.d.ts +0 -115
- package/dist/types/src/TextEditor.stories.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/comment.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +0 -1
- package/dist/types/src/extensions/command/preview.d.ts +0 -12
- package/dist/types/src/extensions/command/preview.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/editorAction.d.ts.map +0 -1
- package/dist/types/src/fragments.d.ts +0 -3
- package/dist/types/src/fragments.d.ts.map +0 -1
- package/dist/types/src/hooks/useActionHandler.d.ts +0 -4
- package/dist/types/src/hooks/useActionHandler.d.ts.map +0 -1
- package/src/InputMode.stories.tsx +0 -124
- package/src/TextEditor.stories.tsx +0 -856
- package/src/components/EditorToolbar/comment.ts +0 -23
- package/src/extensions/command/preview.ts +0 -79
- package/src/fragments.ts +0 -19
- package/src/hooks/useActionHandler.ts +0 -12
- /package/src/extensions/markdown/{editorAction.ts → action.ts} +0 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { EditorSelection, Prec, RangeSetBuilder, type Extension } from '@codemirror/state';
|
6
|
+
import {
|
7
|
+
type Command,
|
8
|
+
Decoration,
|
9
|
+
type DecorationSet,
|
10
|
+
type EditorView,
|
11
|
+
keymap,
|
12
|
+
ViewPlugin,
|
13
|
+
type ViewUpdate,
|
14
|
+
} from '@codemirror/view';
|
15
|
+
|
16
|
+
import { Hint } from './hint';
|
17
|
+
|
18
|
+
export type TypeaheadContext = { line: string };
|
19
|
+
|
20
|
+
// TODO(burdon): Option to complete only at end of line?
|
21
|
+
export type TypeaheadOptions = {
|
22
|
+
onComplete?: (context: TypeaheadContext) => string | undefined;
|
23
|
+
};
|
24
|
+
|
25
|
+
/**
|
26
|
+
* CodeMirror extension for typeahead completion.
|
27
|
+
*/
|
28
|
+
export const typeahead = ({ onComplete }: TypeaheadOptions = {}): Extension => {
|
29
|
+
let hint: string | undefined;
|
30
|
+
|
31
|
+
const complete: Command = (view: EditorView) => {
|
32
|
+
if (!hint) {
|
33
|
+
return false;
|
34
|
+
}
|
35
|
+
|
36
|
+
const selection = view.state.selection.main;
|
37
|
+
view.dispatch({
|
38
|
+
changes: [{ from: selection.from, to: selection.to, insert: hint }],
|
39
|
+
selection: EditorSelection.cursor(selection.from + hint.length),
|
40
|
+
});
|
41
|
+
|
42
|
+
return true;
|
43
|
+
};
|
44
|
+
|
45
|
+
return [
|
46
|
+
ViewPlugin.fromClass(
|
47
|
+
class {
|
48
|
+
decorations: DecorationSet = Decoration.none;
|
49
|
+
update(update: ViewUpdate) {
|
50
|
+
const builder = new RangeSetBuilder<Decoration>();
|
51
|
+
const selection = update.view.state.selection.main;
|
52
|
+
const line = update.view.state.doc.lineAt(selection.from);
|
53
|
+
|
54
|
+
// TODO(burdon): Check at end of line and matches start of previous word.
|
55
|
+
// TODO(burdon): Context grammar.
|
56
|
+
if (selection.from === selection.to && selection.from === line.to) {
|
57
|
+
const str = update.state.sliceDoc(line.from, selection.from);
|
58
|
+
hint = onComplete?.({ line: str });
|
59
|
+
if (hint) {
|
60
|
+
builder.add(selection.from, selection.to, Decoration.widget({ widget: new Hint(hint) }));
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
this.decorations = builder.finish();
|
65
|
+
}
|
66
|
+
},
|
67
|
+
{
|
68
|
+
decorations: (v) => v.decorations,
|
69
|
+
},
|
70
|
+
),
|
71
|
+
|
72
|
+
// Keys.
|
73
|
+
Prec.highest(
|
74
|
+
keymap.of([
|
75
|
+
{
|
76
|
+
key: 'Tab',
|
77
|
+
preventDefault: true,
|
78
|
+
run: complete,
|
79
|
+
},
|
80
|
+
{
|
81
|
+
key: 'ArrowRight',
|
82
|
+
preventDefault: true,
|
83
|
+
run: complete,
|
84
|
+
},
|
85
|
+
]),
|
86
|
+
),
|
87
|
+
];
|
88
|
+
};
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Util to match current line to a static list of completions.
|
92
|
+
*/
|
93
|
+
export const staticCompletion =
|
94
|
+
(completions: string[], defaultCompletion?: string) =>
|
95
|
+
({ line }: TypeaheadContext) => {
|
96
|
+
if (line.length === 0 && defaultCompletion) {
|
97
|
+
return defaultCompletion;
|
98
|
+
}
|
99
|
+
|
100
|
+
const words = line.split(/\s+/).filter(Boolean);
|
101
|
+
if (words.length) {
|
102
|
+
const word = words.at(-1)!;
|
103
|
+
for (const completion of completions) {
|
104
|
+
const match = matchCompletion(completion, word);
|
105
|
+
if (match) {
|
106
|
+
return match;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
};
|
111
|
+
|
112
|
+
export const matchCompletion = (completion: string, word: string): string | undefined => {
|
113
|
+
if (completion.length > word.length && completion.startsWith(word)) {
|
114
|
+
return completion.slice(word.length);
|
115
|
+
}
|
116
|
+
};
|
@@ -3,14 +3,7 @@
|
|
3
3
|
//
|
4
4
|
|
5
5
|
import { invertedEffects } from '@codemirror/commands';
|
6
|
-
import {
|
7
|
-
type ChangeDesc,
|
8
|
-
type EditorState,
|
9
|
-
type Extension,
|
10
|
-
StateEffect,
|
11
|
-
StateField,
|
12
|
-
type Text,
|
13
|
-
} from '@codemirror/state';
|
6
|
+
import { type ChangeDesc, type Extension, StateEffect, StateField, type Text } from '@codemirror/state';
|
14
7
|
import {
|
15
8
|
hoverTooltip,
|
16
9
|
keymap,
|
@@ -22,17 +15,15 @@ import {
|
|
22
15
|
ViewPlugin,
|
23
16
|
} from '@codemirror/view';
|
24
17
|
import sortBy from 'lodash.sortby';
|
25
|
-
import { useEffect
|
18
|
+
import { useEffect } from 'react';
|
26
19
|
|
27
20
|
import { debounce, type CleanupFn } from '@dxos/async';
|
28
|
-
import { type ReactiveObject } from '@dxos/live-object';
|
29
21
|
import { log } from '@dxos/log';
|
30
22
|
import { isNonNullable } from '@dxos/util';
|
31
23
|
|
32
24
|
import { documentId } from './selection';
|
33
|
-
import { type
|
34
|
-
import {
|
35
|
-
import { Cursor, overlap, singleValueFacet, callbackWrapper } from '../util';
|
25
|
+
import { type RenderCallback, type Comment, type Range } from '../types';
|
26
|
+
import { Cursor, singleValueFacet, callbackWrapper } from '../util';
|
36
27
|
|
37
28
|
//
|
38
29
|
// State management.
|
@@ -156,7 +147,7 @@ const commentsDecorations = EditorView.decorations.compute([commentsState], (sta
|
|
156
147
|
return Decoration.set(decorations);
|
157
148
|
});
|
158
149
|
|
159
|
-
const commentClickedEffect = StateEffect.define<string>();
|
150
|
+
export const commentClickedEffect = StateEffect.define<string>();
|
160
151
|
|
161
152
|
const handleCommentClick = EditorView.domEventHandlers({
|
162
153
|
click: (event, view) => {
|
@@ -345,6 +336,10 @@ export type CommentsOptions = {
|
|
345
336
|
* Key shortcut to create a new thread.
|
346
337
|
*/
|
347
338
|
key?: string;
|
339
|
+
/**
|
340
|
+
* Called to render tooltip.
|
341
|
+
*/
|
342
|
+
renderTooltip?: RenderCallback<{ shortcut: string }>;
|
348
343
|
/**
|
349
344
|
* Called to create a new thread and return the thread id.
|
350
345
|
*/
|
@@ -361,10 +356,6 @@ export type CommentsOptions = {
|
|
361
356
|
* Called to notify which thread is currently closest to the cursor.
|
362
357
|
*/
|
363
358
|
onSelect?: (state: CommentsState) => void;
|
364
|
-
/**
|
365
|
-
* Called to render tooltip.
|
366
|
-
*/
|
367
|
-
onHover?: (el: Element, shortcut: string) => void;
|
368
359
|
};
|
369
360
|
|
370
361
|
const optionsFacet = singleValueFacet<CommentsOptions>();
|
@@ -408,7 +399,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
408
399
|
// Hover tooltip (for key shortcut hints, etc.)
|
409
400
|
// TODO(burdon): Factor out to generic hints extension for current selection/line.
|
410
401
|
//
|
411
|
-
options.
|
402
|
+
options.renderTooltip &&
|
412
403
|
hoverTooltip(
|
413
404
|
(view, pos) => {
|
414
405
|
const selection = view.state.selection.main;
|
@@ -419,7 +410,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
419
410
|
above: true,
|
420
411
|
create: () => {
|
421
412
|
const el = document.createElement('div');
|
422
|
-
options.
|
413
|
+
options.renderTooltip!(el, { shortcut }, view);
|
423
414
|
return { dom: el, offset: { x: 0, y: 8 } };
|
424
415
|
},
|
425
416
|
};
|
@@ -545,30 +536,6 @@ export const scrollThreadIntoView = (view: EditorView, id: string, center = true
|
|
545
536
|
}
|
546
537
|
};
|
547
538
|
|
548
|
-
/**
|
549
|
-
* Query the editor state for the active formatting at the selection.
|
550
|
-
*/
|
551
|
-
export const selectionOverlapsComment = (state: EditorState): boolean => {
|
552
|
-
// May not be defined if thread plugin not installed.
|
553
|
-
const commentState = state.field(commentsState, false);
|
554
|
-
if (commentState === undefined) {
|
555
|
-
return false;
|
556
|
-
}
|
557
|
-
|
558
|
-
const { selection } = state;
|
559
|
-
for (const range of selection.ranges) {
|
560
|
-
if (commentState.comments.some(({ range: commentRange }) => overlap(commentRange, range))) {
|
561
|
-
return true;
|
562
|
-
}
|
563
|
-
}
|
564
|
-
|
565
|
-
return false;
|
566
|
-
};
|
567
|
-
|
568
|
-
const hasActiveSelection = (state: EditorState): boolean => {
|
569
|
-
return state.selection.ranges.some((range) => !range.empty);
|
570
|
-
};
|
571
|
-
|
572
539
|
/**
|
573
540
|
* Manages external comment synchronization for the editor.
|
574
541
|
* This class subscribes to external comment updates and applies them to the editor view.
|
@@ -606,19 +573,6 @@ export const createExternalCommentSync = (
|
|
606
573
|
},
|
607
574
|
);
|
608
575
|
|
609
|
-
export const useCommentState = (state: ReactiveObject<EditorToolbarState>): Extension => {
|
610
|
-
return useMemo(
|
611
|
-
() =>
|
612
|
-
EditorView.updateListener.of((update) => {
|
613
|
-
if (update.docChanged || update.selectionSet) {
|
614
|
-
state.comment = selectionOverlapsComment(update.state);
|
615
|
-
state.selection = hasActiveSelection(update.state);
|
616
|
-
}
|
617
|
-
}),
|
618
|
-
[state],
|
619
|
-
);
|
620
|
-
};
|
621
|
-
|
622
576
|
/**
|
623
577
|
* @deprecated This hook will be removed in future versions. Use the new comment sync extension instead.
|
624
578
|
* Update comments state field.
|
@@ -636,22 +590,3 @@ export const useComments = (view: EditorView | null | undefined, id: string, com
|
|
636
590
|
}
|
637
591
|
});
|
638
592
|
};
|
639
|
-
|
640
|
-
/**
|
641
|
-
* Hook provides an extension to listen for comment clicks and invoke a handler.
|
642
|
-
*/
|
643
|
-
export const useCommentClickListener = (onCommentClick: (commentId: string) => void): Extension => {
|
644
|
-
return useMemo(
|
645
|
-
() =>
|
646
|
-
EditorView.updateListener.of((update) => {
|
647
|
-
update.transactions.forEach((transaction) => {
|
648
|
-
transaction.effects.forEach((effect) => {
|
649
|
-
if (effect.is(commentClickedEffect)) {
|
650
|
-
onCommentClick(effect.value);
|
651
|
-
}
|
652
|
-
});
|
653
|
-
});
|
654
|
-
}),
|
655
|
-
[onCommentClick],
|
656
|
-
);
|
657
|
-
};
|
@@ -11,6 +11,7 @@ import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
11
11
|
import {
|
12
12
|
EditorView,
|
13
13
|
type KeyBinding,
|
14
|
+
ViewPlugin,
|
14
15
|
drawSelection,
|
15
16
|
dropCursor,
|
16
17
|
highlightActiveLine,
|
@@ -59,6 +60,7 @@ export type BasicExtensionsOptions = {
|
|
59
60
|
indentWithTab?: boolean;
|
60
61
|
keymap?: null | 'default' | 'standard';
|
61
62
|
lineNumbers?: boolean;
|
63
|
+
/** If false then do not set a max-width or side margin on the editor. */
|
62
64
|
lineWrapping?: boolean;
|
63
65
|
placeholder?: string;
|
64
66
|
/** If true user cannot edit the text, but they can still select and copy it. */
|
@@ -149,6 +151,9 @@ export type ThemeExtensionsOptions = {
|
|
149
151
|
editor?: {
|
150
152
|
className?: string;
|
151
153
|
};
|
154
|
+
scroll?: {
|
155
|
+
className?: string;
|
156
|
+
};
|
152
157
|
content?: {
|
153
158
|
className?: string;
|
154
159
|
};
|
@@ -179,6 +184,14 @@ export const createThemeExtensions = ({
|
|
179
184
|
(themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)),
|
180
185
|
slots.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
|
181
186
|
slots.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
|
187
|
+
slots.scroll?.className &&
|
188
|
+
ViewPlugin.fromClass(
|
189
|
+
class {
|
190
|
+
constructor(view: EditorView) {
|
191
|
+
view.scrollDOM.classList.add(slots.scroll.className);
|
192
|
+
}
|
193
|
+
},
|
194
|
+
),
|
182
195
|
].filter(isNotFalsy);
|
183
196
|
};
|
184
197
|
|
@@ -29,7 +29,7 @@ export const folding = (_props: FoldingOptions = {}): Extension => [
|
|
29
29
|
const el = createElement('div', { className: 'flex h-full items-center' });
|
30
30
|
return renderRoot(
|
31
31
|
el,
|
32
|
-
<Icon icon='ph--caret-right--
|
32
|
+
<Icon icon='ph--caret-right--bold' size={3} classNames={['mx-3 cursor-pointer', open && 'rotate-90']} />,
|
33
33
|
);
|
34
34
|
},
|
35
35
|
}),
|
package/src/extensions/index.ts
CHANGED
@@ -14,9 +14,12 @@ export * from './dnd';
|
|
14
14
|
export * from './factories';
|
15
15
|
export * from './focus';
|
16
16
|
export * from './folding';
|
17
|
+
export * from './json';
|
17
18
|
export * from './listener';
|
18
19
|
export * from './markdown';
|
19
20
|
export * from './mention';
|
20
21
|
export * from './modes';
|
22
|
+
export * from './outliner';
|
23
|
+
export * from './preview';
|
21
24
|
export * from './selection';
|
22
25
|
export * from './typewriter';
|
@@ -0,0 +1,56 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { json, jsonParseLinter } from '@codemirror/lang-json';
|
6
|
+
import { type LintSource, linter } from '@codemirror/lint';
|
7
|
+
import { type Extension } from '@codemirror/state';
|
8
|
+
import Ajv, { type ValidateFunction } from 'ajv';
|
9
|
+
|
10
|
+
import { type JsonSchemaType } from '@dxos/echo-schema';
|
11
|
+
|
12
|
+
export type JsonExtensionsOptions = {
|
13
|
+
schema?: JsonSchemaType;
|
14
|
+
};
|
15
|
+
|
16
|
+
export const createJsonExtensions = ({ schema }: JsonExtensionsOptions = {}): Extension => {
|
17
|
+
let lintSource: LintSource = jsonParseLinter();
|
18
|
+
if (schema) {
|
19
|
+
const ajv = new Ajv({ allErrors: false });
|
20
|
+
const validate = ajv.compile(schema);
|
21
|
+
lintSource = schemaLinter(validate);
|
22
|
+
}
|
23
|
+
|
24
|
+
return [json(), linter(lintSource)];
|
25
|
+
};
|
26
|
+
|
27
|
+
const schemaLinter =
|
28
|
+
(validate: ValidateFunction): LintSource =>
|
29
|
+
(view) => {
|
30
|
+
try {
|
31
|
+
const jsonText = view.state.doc.toString();
|
32
|
+
const jsonData = JSON.parse(jsonText);
|
33
|
+
const valid = validate(jsonData);
|
34
|
+
if (valid) {
|
35
|
+
return [];
|
36
|
+
}
|
37
|
+
|
38
|
+
return (
|
39
|
+
validate.errors?.map((err: any) => ({
|
40
|
+
from: 0,
|
41
|
+
to: jsonText.length,
|
42
|
+
severity: 'error',
|
43
|
+
message: `${err.instancePath || '(root)'} ${err.message}`,
|
44
|
+
})) ?? []
|
45
|
+
);
|
46
|
+
} catch (err: unknown) {
|
47
|
+
return [
|
48
|
+
{
|
49
|
+
from: 0,
|
50
|
+
to: view.state.doc.length,
|
51
|
+
severity: 'error',
|
52
|
+
message: 'Invalid JSON: ' + (err as Error).message,
|
53
|
+
},
|
54
|
+
];
|
55
|
+
}
|
56
|
+
};
|
@@ -12,11 +12,13 @@ import { type Extension } from '@codemirror/state';
|
|
12
12
|
import { keymap } from '@codemirror/view';
|
13
13
|
|
14
14
|
import { type ThemeMode } from '@dxos/react-ui';
|
15
|
+
import { isNotFalsy } from '@dxos/util';
|
15
16
|
|
16
17
|
import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
|
17
18
|
|
18
19
|
export type MarkdownBundleOptions = {
|
19
20
|
themeMode?: ThemeMode;
|
21
|
+
indentWithTab?: boolean;
|
20
22
|
};
|
21
23
|
|
22
24
|
/**
|
@@ -27,7 +29,7 @@ export type MarkdownBundleOptions = {
|
|
27
29
|
* https://codemirror.net/docs/community
|
28
30
|
* https://codemirror.net/docs/ref/#codemirror.basicSetup
|
29
31
|
*/
|
30
|
-
export const createMarkdownExtensions = (
|
32
|
+
export const createMarkdownExtensions = (options: MarkdownBundleOptions = {}): Extension[] => {
|
31
33
|
return [
|
32
34
|
// Main extension.
|
33
35
|
// https://github.com/codemirror/lang-markdown
|
@@ -56,14 +58,16 @@ export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions =
|
|
56
58
|
// Custom styles.
|
57
59
|
syntaxHighlighting(markdownHighlightStyle()),
|
58
60
|
|
59
|
-
keymap.of(
|
60
|
-
|
61
|
-
|
61
|
+
keymap.of(
|
62
|
+
[
|
63
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
64
|
+
options.indentWithTab !== false && indentWithTab,
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
// https://codemirror.net/docs/ref/#commands.defaultKeymap
|
67
|
+
...defaultKeymap,
|
68
|
+
...completionKeymap,
|
69
|
+
...lintKeymap,
|
70
|
+
].filter(isNotFalsy),
|
71
|
+
),
|
68
72
|
];
|
69
73
|
};
|
@@ -56,8 +56,9 @@ export const adjustChanges = () => {
|
|
56
56
|
// Check for URL.
|
57
57
|
const url = getValidUrl(update.view.state.sliceDoc(fromB, toB));
|
58
58
|
if (url) {
|
59
|
+
// Check if pasting inside existing link.
|
59
60
|
const node = tree.resolveInner(fromA, -1);
|
60
|
-
const invalidPositions = new Set(['
|
61
|
+
const invalidPositions = new Set(['Code', 'CodeText', 'FencedCode', 'Link', 'LinkMark', 'URL']);
|
61
62
|
if (!invalidPositions.has(node?.name)) {
|
62
63
|
const replacedText = tr.startState.sliceDoc(fromA, toA);
|
63
64
|
adjustments.push({ from: fromA, to: toB, insert: createLink(url, replacedText) });
|
@@ -84,7 +85,7 @@ export const adjustChanges = () => {
|
|
84
85
|
}
|
85
86
|
}
|
86
87
|
|
87
|
-
// TODO(burdon): Is this the right way to augment changes?
|
88
|
+
// TODO(burdon): Is this the right way to augment changes? Alt: EditorState.transactionFilter
|
88
89
|
if (adjustments.length) {
|
89
90
|
setTimeout(() => {
|
90
91
|
update.view.dispatch(
|
@@ -15,6 +15,7 @@ import { image } from './image';
|
|
15
15
|
import { formattingStyles, bulletListIndentationWidth, orderedListIndentationWidth } from './styles';
|
16
16
|
import { table } from './table';
|
17
17
|
import { theme, type HeadingLevel } from '../../styles';
|
18
|
+
import { type RenderCallback } from '../../types';
|
18
19
|
import { wrapWithCatch } from '../../util';
|
19
20
|
|
20
21
|
/**
|
@@ -35,7 +36,7 @@ const Unicode = {
|
|
35
36
|
//
|
36
37
|
|
37
38
|
class HorizontalRuleWidget extends WidgetType {
|
38
|
-
override toDOM() {
|
39
|
+
override toDOM(): HTMLSpanElement {
|
39
40
|
const el = document.createElement('span');
|
40
41
|
el.className = 'cm-hr';
|
41
42
|
return el;
|
@@ -45,19 +46,19 @@ class HorizontalRuleWidget extends WidgetType {
|
|
45
46
|
class LinkButton extends WidgetType {
|
46
47
|
constructor(
|
47
48
|
private readonly url: string,
|
48
|
-
private readonly render:
|
49
|
+
private readonly render: RenderCallback<{ url: string }>,
|
49
50
|
) {
|
50
51
|
super();
|
51
52
|
}
|
52
53
|
|
53
|
-
override eq(other: this) {
|
54
|
+
override eq(other: this): boolean {
|
54
55
|
return this.url === other.url;
|
55
56
|
}
|
56
57
|
|
57
58
|
// TODO(burdon): Create icon and link directly without react?
|
58
|
-
override toDOM(view: EditorView) {
|
59
|
+
override toDOM(view: EditorView): HTMLSpanElement {
|
59
60
|
const el = document.createElement('span');
|
60
|
-
this.render(el, this.url);
|
61
|
+
this.render(el, { url: this.url }, view);
|
61
62
|
return el;
|
62
63
|
}
|
63
64
|
}
|
@@ -67,11 +68,11 @@ class CheckboxWidget extends WidgetType {
|
|
67
68
|
super();
|
68
69
|
}
|
69
70
|
|
70
|
-
override eq(other: this) {
|
71
|
+
override eq(other: this): boolean {
|
71
72
|
return this._checked === other._checked;
|
72
73
|
}
|
73
74
|
|
74
|
-
override toDOM(view: EditorView) {
|
75
|
+
override toDOM(view: EditorView): HTMLSpanElement {
|
75
76
|
const input = document.createElement('input');
|
76
77
|
input.className = 'cm-task-checkbox dx-checkbox';
|
77
78
|
input.type = 'checkbox';
|
@@ -104,7 +105,7 @@ class CheckboxWidget extends WidgetType {
|
|
104
105
|
return span;
|
105
106
|
}
|
106
107
|
|
107
|
-
override ignoreEvent() {
|
108
|
+
override ignoreEvent(): boolean {
|
108
109
|
return false;
|
109
110
|
}
|
110
111
|
}
|
@@ -117,7 +118,7 @@ class TextWidget extends WidgetType {
|
|
117
118
|
super();
|
118
119
|
}
|
119
120
|
|
120
|
-
override toDOM() {
|
121
|
+
override toDOM(): HTMLSpanElement {
|
121
122
|
const el = document.createElement('span');
|
122
123
|
if (this.className) {
|
123
124
|
el.className = this.className;
|
@@ -128,10 +129,10 @@ class TextWidget extends WidgetType {
|
|
128
129
|
}
|
129
130
|
|
130
131
|
const hide = Decoration.replace({});
|
131
|
-
const blockQuote = Decoration.line({ class:
|
132
|
-
const fencedCodeLine = Decoration.line({ class:
|
133
|
-
const fencedCodeLineFirst = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-
|
134
|
-
const fencedCodeLineLast = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-
|
132
|
+
const blockQuote = Decoration.line({ class: 'cm-blockquote' });
|
133
|
+
const fencedCodeLine = Decoration.line({ class: 'cm-code cm-codeblock-line' });
|
134
|
+
const fencedCodeLineFirst = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-start') });
|
135
|
+
const fencedCodeLineLast = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-end') });
|
135
136
|
const commentBlockLine = fencedCodeLine;
|
136
137
|
const commentBlockLineFirst = fencedCodeLineFirst;
|
137
138
|
const commentBlockLineLast = fencedCodeLineLast;
|
@@ -276,7 +277,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
276
277
|
// Set indentation.
|
277
278
|
const list = getCurrentListLevel();
|
278
279
|
const width = list.type === 'OrderedList' ? orderedListIndentationWidth : bulletListIndentationWidth;
|
279
|
-
const offset = ((list.level ?? 0) + 1) * width;
|
280
|
+
const offset = (options?.listPaddingLeft ?? 0) + ((list.level ?? 0) + 1) * width;
|
280
281
|
if (node.from === line.to - 1) {
|
281
282
|
// Abort if only the hyphen is typed.
|
282
283
|
return false;
|
@@ -284,7 +285,6 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
284
285
|
|
285
286
|
// Add line decoration for the continuation indent.
|
286
287
|
// TODO(burdon): Bug if indentation is more than one indentation unit (e.g., 4 spaces) from the previous line.
|
287
|
-
|
288
288
|
deco.add(
|
289
289
|
line.from,
|
290
290
|
line.from,
|
@@ -405,7 +405,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
405
405
|
}
|
406
406
|
|
407
407
|
const first = block.from <= node.from;
|
408
|
-
const last = block.to >= node.to &&
|
408
|
+
const last = block.to >= node.to && /```$/.test(state.doc.sliceString(block.from, block.to));
|
409
409
|
deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
|
410
410
|
|
411
411
|
const editing = editingRange(state, node, focus);
|
@@ -519,7 +519,9 @@ export interface DecorateOptions {
|
|
519
519
|
*/
|
520
520
|
selectionChangeDelay?: number;
|
521
521
|
numberedHeadings?: { from: number; to?: number };
|
522
|
-
renderLinkButton?:
|
522
|
+
renderLinkButton?: RenderCallback<{ url: string }>;
|
523
|
+
// TODO(burdon): Additional padding for each line.
|
524
|
+
listPaddingLeft?: number;
|
523
525
|
}
|
524
526
|
|
525
527
|
export const decorateMarkdown = (options: DecorateOptions = {}) => {
|
@@ -5,19 +5,19 @@
|
|
5
5
|
import { snippet } from '@codemirror/autocomplete';
|
6
6
|
import { syntaxTree } from '@codemirror/language';
|
7
7
|
import {
|
8
|
-
type Extension,
|
9
|
-
type StateCommand,
|
10
|
-
type EditorState,
|
11
8
|
type ChangeSpec,
|
12
|
-
type Text,
|
13
9
|
EditorSelection,
|
10
|
+
type Extension,
|
11
|
+
type EditorState,
|
14
12
|
type Line,
|
13
|
+
type StateCommand,
|
14
|
+
type Text,
|
15
15
|
} from '@codemirror/state';
|
16
16
|
import { EditorView, keymap } from '@codemirror/view';
|
17
17
|
import { type SyntaxNodeRef, type SyntaxNode } from '@lezer/common';
|
18
18
|
import { useMemo } from 'react';
|
19
19
|
|
20
|
-
import { type
|
20
|
+
import { type Live } from '@dxos/live-object';
|
21
21
|
|
22
22
|
import { type EditorToolbarState } from '../../components';
|
23
23
|
|
@@ -1250,7 +1250,7 @@ export const getFormatting = (state: EditorState): Formatting => {
|
|
1250
1250
|
/**
|
1251
1251
|
* Hook provides an extension to compute the current formatting state.
|
1252
1252
|
*/
|
1253
|
-
export const useFormattingState = (state:
|
1253
|
+
export const useFormattingState = (state: Live<EditorToolbarState>): Extension => {
|
1254
1254
|
return useMemo(
|
1255
1255
|
() =>
|
1256
1256
|
EditorView.updateListener.of((update) => {
|
@@ -51,16 +51,6 @@ export const image = (_options: ImageOptions = {}): Extension => {
|
|
51
51
|
];
|
52
52
|
};
|
53
53
|
|
54
|
-
const preloaded = new Set<string>();
|
55
|
-
|
56
|
-
const preloadImage = (url: string) => {
|
57
|
-
if (!preloaded.has(url)) {
|
58
|
-
const img = document.createElement('img');
|
59
|
-
img.src = url;
|
60
|
-
preloaded.add(url);
|
61
|
-
}
|
62
|
-
};
|
63
|
-
|
64
54
|
const buildDecorations = (from: number, to: number, state: EditorState) => {
|
65
55
|
const decorations: Range<Decoration>[] = [];
|
66
56
|
const cursor = state.selection.main.head;
|
@@ -94,16 +84,26 @@ const buildDecorations = (from: number, to: number, state: EditorState) => {
|
|
94
84
|
return decorations;
|
95
85
|
};
|
96
86
|
|
87
|
+
const preloaded = new Set<string>();
|
88
|
+
|
89
|
+
const preloadImage = (url: string) => {
|
90
|
+
if (!preloaded.has(url)) {
|
91
|
+
const img = document.createElement('img');
|
92
|
+
img.src = url;
|
93
|
+
preloaded.add(url);
|
94
|
+
}
|
95
|
+
};
|
96
|
+
|
97
97
|
class ImageWidget extends WidgetType {
|
98
98
|
constructor(readonly _url: string) {
|
99
99
|
super();
|
100
100
|
}
|
101
101
|
|
102
|
-
override eq(other: this) {
|
103
|
-
return this._url ===
|
102
|
+
override eq(other: this): boolean {
|
103
|
+
return this._url === other._url;
|
104
104
|
}
|
105
105
|
|
106
|
-
override toDOM(view: EditorView) {
|
106
|
+
override toDOM(view: EditorView): HTMLImageElement {
|
107
107
|
const img = document.createElement('img');
|
108
108
|
img.setAttribute('src', this._url);
|
109
109
|
img.setAttribute('class', 'cm-image');
|
@@ -113,6 +113,7 @@ class ImageWidget extends WidgetType {
|
|
113
113
|
} else {
|
114
114
|
img.classList.add('cm-loaded-image');
|
115
115
|
}
|
116
|
+
|
116
117
|
return img;
|
117
118
|
}
|
118
119
|
}
|