@dxos/react-ui-editor 0.3.11-main.668f0ff → 0.3.11-main.68db74e
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 +1742 -1382
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +9 -8
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +12 -17
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -3
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
- package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/index.d.ts +0 -2
- package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
- package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/index.d.ts +4 -0
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
- package/dist/types/src/{hooks → extensions}/automerge/semaphore.d.ts +4 -4
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
- package/dist/types/src/extensions/awareness.d.ts +46 -0
- package/dist/types/src/extensions/awareness.d.ts.map +1 -0
- package/dist/types/src/extensions/basic.d.ts.map +1 -0
- package/dist/types/src/extensions/blast.d.ts.map +1 -0
- package/dist/types/src/extensions/code.d.ts.map +1 -0
- package/dist/types/src/{components/TextEditor/extensions → extensions}/comments.d.ts +6 -4
- package/dist/types/src/extensions/comments.d.ts.map +1 -0
- package/dist/types/src/extensions/cursor.d.ts +25 -0
- package/dist/types/src/extensions/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/hr.d.ts.map +1 -0
- package/dist/types/src/extensions/image.d.ts.map +1 -0
- package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +4 -2
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
- package/dist/types/src/extensions/link.d.ts.map +1 -0
- package/dist/types/src/extensions/listener.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
- package/dist/types/src/extensions/mention.d.ts.map +1 -0
- package/dist/types/src/extensions/table.d.ts.map +1 -0
- package/dist/types/src/extensions/tasklist.d.ts +7 -0
- package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
- package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
- package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/index.d.ts +2 -0
- package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
- package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
- package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
- package/dist/types/src/hooks/automerge.d.ts +3 -0
- package/dist/types/src/hooks/automerge.d.ts.map +1 -0
- package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
- package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +4 -8
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
- package/dist/types/src/hooks/yjs.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +5 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/cursors.d.ts.map +1 -0
- package/dist/types/src/styles/index.d.ts +1 -0
- package/dist/types/src/styles/index.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts +1 -0
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/testing/replicator.d.ts +2 -2
- package/dist/types/src/testing/replicator.d.ts.map +1 -1
- package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -0
- package/dist/types/src/themes/index.d.ts.map +1 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/package.json +24 -22
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +36 -36
- package/src/components/TextEditor/TextEditor.stories.tsx +13 -34
- package/src/components/TextEditor/TextEditor.tsx +71 -67
- package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +66 -6
- package/src/components/TextEditor/codemirror.stories.ts +3 -2
- package/src/components/TextEditor/comments.stories.tsx +315 -0
- package/src/components/TextEditor/index.ts +0 -3
- package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
- package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +18 -17
- package/src/extensions/automerge/automerge.ts +94 -0
- package/src/extensions/automerge/cursor.ts +52 -0
- package/src/extensions/automerge/defs.ts +62 -0
- package/src/extensions/automerge/index.ts +7 -0
- package/src/{hooks → extensions}/automerge/semaphore.ts +14 -12
- package/src/{hooks → extensions}/automerge/update-automerge.ts +5 -6
- package/src/{hooks → extensions}/automerge/update-codemirror.ts +13 -5
- package/src/extensions/awareness.ts +309 -0
- package/src/{components/TextEditor/extensions → extensions}/code.ts +14 -7
- package/src/{components/TextEditor/extensions → extensions}/comments.ts +49 -113
- package/src/extensions/cursor.ts +46 -0
- package/src/{components/TextEditor/extensions → extensions}/hr.ts +4 -5
- package/src/extensions/image.ts +87 -0
- package/src/{components/TextEditor/extensions → extensions}/index.ts +4 -2
- package/src/{components/TextEditor/extensions → extensions}/link.ts +116 -62
- package/src/extensions/listener.ts +35 -0
- package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +4 -13
- package/src/{components/TextEditor/extensions → extensions}/table.ts +10 -4
- package/src/extensions/tasklist.ts +95 -0
- package/src/{components/TextEditor/extensions → extensions}/typewriter.ts +1 -1
- package/src/extensions/yjs/cursor.ts +22 -0
- package/src/extensions/yjs/index.ts +5 -0
- package/src/{hooks → extensions}/yjs/yjs.test.ts +2 -26
- package/src/extensions/yjs/yjs.ts +16 -0
- package/src/hooks/automerge.ts +52 -0
- package/src/hooks/awareness-provider.ts +120 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTextModel.ts +20 -96
- package/src/hooks/{yjs/space-provider.ts → yjs.ts} +49 -19
- package/src/index.ts +5 -0
- package/src/styles/index.ts +1 -0
- package/src/styles/markdown.ts +0 -2
- package/src/styles/tokens.ts +3 -0
- package/src/testing/replicator.ts +10 -15
- package/src/{components/TextEditor/themes → themes}/default.ts +52 -79
- package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hr.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts +0 -3
- package/dist/types/src/components/TextEditor/extensions/mermaid.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts +0 -3
- package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
- package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/typewriter.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/util.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
- package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/index.d.ts +0 -3
- package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
- package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/index.d.ts +0 -3
- package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
- package/src/components/TextEditor/extensions/image.ts +0 -69
- package/src/components/TextEditor/extensions/listener.ts +0 -39
- package/src/components/TextEditor/extensions/mermaid.ts +0 -11
- package/src/components/TextEditor/extensions/tasklist.ts +0 -121
- package/src/components/TextEditor/extensions/tooltip.ts +0 -61
- package/src/hooks/automerge/handle.ts +0 -14
- package/src/hooks/automerge/index.ts +0 -6
- package/src/hooks/automerge/plugin.ts +0 -134
- package/src/hooks/yjs/index.ts +0 -6
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
- /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/basic.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/hr.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/image.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/listener.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/bundle.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/index.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/table.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions → extensions}/typewriter.d.ts +0 -0
- /package/dist/types/src/{hooks → extensions}/yjs/yjs.test.d.ts +0 -0
- /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
- /package/dist/types/src/{components/TextEditor/extensions/util.d.ts → util.d.ts} +0 -0
- /package/src/{components/TextEditor/extensions → extensions}/basic.ts +0 -0
- /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
- /package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +0 -0
- /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
- /package/src/{components/TextEditor/extensions → extensions}/mention.ts +0 -0
- /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
- /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
- /package/src/{components/TextEditor/extensions/util.ts → util.ts} +0 -0
|
@@ -3,19 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Extension, StateEffect, StateField } from '@codemirror/state';
|
|
6
|
-
import {
|
|
7
|
-
hoverTooltip,
|
|
8
|
-
keymap,
|
|
9
|
-
type Command,
|
|
10
|
-
type DecorationSet,
|
|
11
|
-
type ViewUpdate,
|
|
12
|
-
Decoration,
|
|
13
|
-
EditorView,
|
|
14
|
-
MatchDecorator,
|
|
15
|
-
ViewPlugin,
|
|
16
|
-
WidgetType,
|
|
17
|
-
type Rect,
|
|
18
|
-
} from '@codemirror/view';
|
|
6
|
+
import { hoverTooltip, keymap, type Command, Decoration, EditorView, type Rect } from '@codemirror/view';
|
|
19
7
|
import sortBy from 'lodash.sortby';
|
|
20
8
|
|
|
21
9
|
import { debounce } from '@dxos/async';
|
|
@@ -23,29 +11,21 @@ import { invariant } from '@dxos/invariant';
|
|
|
23
11
|
import { log } from '@dxos/log';
|
|
24
12
|
import { nonNullable } from '@dxos/util';
|
|
25
13
|
|
|
26
|
-
import {
|
|
27
|
-
import { type CommentRange, type EditorModel,
|
|
14
|
+
import { Cursor } from './cursor';
|
|
15
|
+
import { type CommentRange, type EditorModel, type Range } from '../hooks';
|
|
16
|
+
import { getToken } from '../styles';
|
|
17
|
+
import { callbackWrapper } from '../util';
|
|
28
18
|
|
|
29
|
-
// TODO(burdon): Handle scroll to.
|
|
30
19
|
// TODO(burdon): Handle delete, cut, copy, and paste (separately) text that includes comment range.
|
|
31
20
|
// TODO(burdon): Consider breaking into separate plugin (since not standalone)? Like mermaid?
|
|
32
21
|
|
|
33
22
|
// TODO(burdon): Reconcile with theme.
|
|
34
23
|
const styles = EditorView.baseTheme({
|
|
35
|
-
'& .cm-bookmark': {
|
|
36
|
-
cursor: 'pointer',
|
|
37
|
-
margin: '4px',
|
|
38
|
-
padding: '4px',
|
|
39
|
-
backgroundColor: 'yellow',
|
|
40
|
-
},
|
|
41
|
-
'& .cm-bookmark-selected': {
|
|
42
|
-
backgroundColor: 'orange',
|
|
43
|
-
},
|
|
44
24
|
'& .cm-comment': {
|
|
45
|
-
backgroundColor: 'yellow',
|
|
25
|
+
backgroundColor: getToken('extend.colors.yellow.50'),
|
|
46
26
|
},
|
|
47
27
|
'& .cm-comment-active': {
|
|
48
|
-
backgroundColor: '
|
|
28
|
+
backgroundColor: getToken('extend.colors.yellow.100'),
|
|
49
29
|
},
|
|
50
30
|
});
|
|
51
31
|
|
|
@@ -71,9 +51,22 @@ export type CommentsState = CommentSelected & {
|
|
|
71
51
|
ranges: ExtendedCommentRange[];
|
|
72
52
|
};
|
|
73
53
|
|
|
54
|
+
export const setFocus = (view: EditorView, thread: string) => {
|
|
55
|
+
const range = view.state.field(commentsStateField).ranges.find((range) => range.id === thread);
|
|
56
|
+
if (!range) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
view.dispatch({
|
|
61
|
+
effects: setSelection.of({ active: thread }),
|
|
62
|
+
selection: { anchor: range.from },
|
|
63
|
+
scrollIntoView: true,
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
74
67
|
export const setCommentRange = StateEffect.define<{ model: EditorModel; comments: CommentRange[] }>();
|
|
75
68
|
|
|
76
|
-
const setSelection = StateEffect.define<CommentSelected>();
|
|
69
|
+
export const setSelection = StateEffect.define<CommentSelected>();
|
|
77
70
|
|
|
78
71
|
const setCommentState = StateEffect.define<CommentsState>();
|
|
79
72
|
|
|
@@ -85,6 +78,8 @@ const setCommentState = StateEffect.define<CommentsState>();
|
|
|
85
78
|
const commentsStateField = StateField.define<CommentsState>({
|
|
86
79
|
create: () => ({ ranges: [] }),
|
|
87
80
|
update: (value, tr) => {
|
|
81
|
+
const cursorConverter = tr.state.facet(Cursor.converter);
|
|
82
|
+
|
|
88
83
|
for (const effect of tr.effects) {
|
|
89
84
|
// Update selection.
|
|
90
85
|
if (effect.is(setSelection)) {
|
|
@@ -93,11 +88,13 @@ const commentsStateField = StateField.define<CommentsState>({
|
|
|
93
88
|
|
|
94
89
|
// Update range from store.
|
|
95
90
|
if (effect.is(setCommentRange)) {
|
|
96
|
-
const {
|
|
97
|
-
const ranges: ExtendedCommentRange[] = comments
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
const { comments } = effect.value;
|
|
92
|
+
const ranges: ExtendedCommentRange[] = comments
|
|
93
|
+
.map((comment) => {
|
|
94
|
+
const range = Cursor.getRangeFromCursor(cursorConverter, comment.cursor);
|
|
95
|
+
return range && { ...comment, ...range };
|
|
96
|
+
})
|
|
97
|
+
.filter(nonNullable);
|
|
101
98
|
|
|
102
99
|
return { ...value, ranges };
|
|
103
100
|
}
|
|
@@ -137,42 +134,13 @@ const highlightDecorations = EditorView.decorations.compute([commentsStateField]
|
|
|
137
134
|
return Decoration.set(decorations);
|
|
138
135
|
});
|
|
139
136
|
|
|
140
|
-
/**
|
|
141
|
-
* Optional bookmark widget at the start/end of the selection.
|
|
142
|
-
* May correspond to a markdown bookmark.
|
|
143
|
-
*/
|
|
144
|
-
class BookmarkWidget extends WidgetType {
|
|
145
|
-
constructor(
|
|
146
|
-
private readonly _anchor: number,
|
|
147
|
-
private readonly _id: string,
|
|
148
|
-
private readonly _handleClick?: () => void,
|
|
149
|
-
) {
|
|
150
|
-
super();
|
|
151
|
-
invariant(this._id);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
override eq(other: BookmarkWidget) {
|
|
155
|
-
return this._anchor === other._anchor && this._id === other._id;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
override toDOM() {
|
|
159
|
-
const span = document.createElement('span');
|
|
160
|
-
span.className = 'cm-bookmark';
|
|
161
|
-
span.textContent = '※';
|
|
162
|
-
if (this._handleClick) {
|
|
163
|
-
span.onclick = () => this._handleClick!();
|
|
164
|
-
}
|
|
165
|
-
return span;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
137
|
export type CommentsOptions = {
|
|
170
138
|
key?: string;
|
|
171
139
|
footnote?: boolean;
|
|
172
140
|
/**
|
|
173
141
|
* Called to create a new thread and return the thread id.
|
|
174
142
|
*/
|
|
175
|
-
onCreate?: (
|
|
143
|
+
onCreate?: (cursor: string, location?: Rect | null) => string | undefined;
|
|
176
144
|
/**
|
|
177
145
|
* Called to notify which thread is currently closest to the cursor.
|
|
178
146
|
*/
|
|
@@ -180,7 +148,7 @@ export type CommentsOptions = {
|
|
|
180
148
|
/**
|
|
181
149
|
* Called to render tooltip.
|
|
182
150
|
*/
|
|
183
|
-
onHover?: (el: Element) => void;
|
|
151
|
+
onHover?: (el: Element, shortcut: string) => void;
|
|
184
152
|
};
|
|
185
153
|
|
|
186
154
|
/**
|
|
@@ -196,23 +164,26 @@ export type CommentsOptions = {
|
|
|
196
164
|
* 6). Optionally, implements a hoverTooltip to show hints when creating a selection range.
|
|
197
165
|
*/
|
|
198
166
|
export const comments = (options: CommentsOptions = {}): Extension => {
|
|
167
|
+
const { key: shortcut = "meta-'" } = options;
|
|
168
|
+
|
|
199
169
|
const handleSelect = debounce((state: CommentsState) => options.onSelect?.(state), 200);
|
|
200
170
|
|
|
201
171
|
/**
|
|
202
172
|
* Create comment thread action.
|
|
203
173
|
*/
|
|
204
174
|
const createCommentThread: Command = (view) => {
|
|
175
|
+
const cursorConverter = view.state.facet(Cursor.converter);
|
|
176
|
+
|
|
205
177
|
invariant(options.onCreate);
|
|
206
178
|
const { head, from, to } = view.state.selection.main;
|
|
207
179
|
if (from === to) {
|
|
208
180
|
return false;
|
|
209
181
|
}
|
|
210
182
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
if (relPos) {
|
|
183
|
+
const cursor = Cursor.getCursorFromRange(cursorConverter, { from, to });
|
|
184
|
+
if (cursor) {
|
|
214
185
|
// Create thread via callback.
|
|
215
|
-
const id = callbackWrapper(options.onCreate)(
|
|
186
|
+
const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
|
|
216
187
|
if (id) {
|
|
217
188
|
// Update range.
|
|
218
189
|
view.dispatch({
|
|
@@ -236,44 +207,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
236
207
|
return false;
|
|
237
208
|
};
|
|
238
209
|
|
|
239
|
-
/**
|
|
240
|
-
* Bookmark widget decoration.
|
|
241
|
-
*/
|
|
242
|
-
const bookmarksViewPlugin = ViewPlugin.fromClass(
|
|
243
|
-
class BookmarkViewPlugin {
|
|
244
|
-
// Match markdown footnotes (option).
|
|
245
|
-
// https://www.markdownguide.org/extended-syntax/#footnotes
|
|
246
|
-
static bookmarkMatcher = new MatchDecorator({
|
|
247
|
-
regexp: /\[\^(\w+)\]/g,
|
|
248
|
-
decoration: (match, view, pos) => {
|
|
249
|
-
const id = match[1];
|
|
250
|
-
return Decoration.replace({
|
|
251
|
-
id,
|
|
252
|
-
widget: new BookmarkWidget(pos, id),
|
|
253
|
-
});
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
bookmarks: DecorationSet;
|
|
258
|
-
constructor(view: EditorView) {
|
|
259
|
-
this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
update(update: ViewUpdate) {
|
|
263
|
-
this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update, this.bookmarks);
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
decorations: (instance) => instance.bookmarks,
|
|
268
|
-
provide: (plugin) =>
|
|
269
|
-
EditorView.atomicRanges.of((view) => {
|
|
270
|
-
return view.plugin(plugin)?.bookmarks || Decoration.none;
|
|
271
|
-
}),
|
|
272
|
-
},
|
|
273
|
-
);
|
|
274
|
-
|
|
275
210
|
return [
|
|
276
|
-
bookmarksViewPlugin,
|
|
277
211
|
commentsStateField,
|
|
278
212
|
highlightDecorations,
|
|
279
213
|
styles,
|
|
@@ -284,7 +218,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
284
218
|
options.onCreate
|
|
285
219
|
? keymap.of([
|
|
286
220
|
{
|
|
287
|
-
key:
|
|
221
|
+
key: shortcut,
|
|
288
222
|
run: callbackWrapper(createCommentThread),
|
|
289
223
|
},
|
|
290
224
|
])
|
|
@@ -306,7 +240,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
306
240
|
create: () => {
|
|
307
241
|
// TODO(burdon): Dispatch to react callback to render (or use SSR)?
|
|
308
242
|
const el = document.createElement('div');
|
|
309
|
-
options.onHover?.(el);
|
|
243
|
+
options.onHover?.(el, shortcut);
|
|
310
244
|
return { dom: el, offset: { x: 0, y: 8 } };
|
|
311
245
|
},
|
|
312
246
|
};
|
|
@@ -322,27 +256,27 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
322
256
|
// Monitor cursor movement and text updates.
|
|
323
257
|
// TODO(burdon): Is there a better (finer grained) way to do this?
|
|
324
258
|
//
|
|
325
|
-
EditorView.updateListener.of(({ view, state,
|
|
259
|
+
EditorView.updateListener.of(({ view, state, changes }) => {
|
|
260
|
+
const cursorConverter = view.state.facet(Cursor.converter);
|
|
261
|
+
|
|
326
262
|
//
|
|
327
263
|
// Test if need to recompute indexed range if document changes before the end of the range.
|
|
328
264
|
//
|
|
329
265
|
{
|
|
330
266
|
let mod = false;
|
|
331
|
-
const model = state.field(modelState);
|
|
332
267
|
const { active, ranges } = state.field(commentsStateField);
|
|
333
268
|
changes.iterChanges((from, to, from2, to2) => {
|
|
334
|
-
invariant(model);
|
|
335
269
|
ranges.forEach((range) => {
|
|
336
270
|
if (from2 === to2) {
|
|
337
|
-
const newRange =
|
|
338
|
-
if (newRange.to - newRange.from === 0) {
|
|
271
|
+
const newRange = Cursor.getRangeFromCursor(cursorConverter, range.cursor);
|
|
272
|
+
if (!newRange || newRange.to - newRange.from === 0) {
|
|
339
273
|
// TODO(burdon): Delete range if empty.
|
|
340
274
|
log.info('deleted comment', { thread: range.id });
|
|
341
275
|
}
|
|
342
276
|
}
|
|
343
277
|
|
|
344
278
|
if (from <= range.to) {
|
|
345
|
-
const newRange =
|
|
279
|
+
const newRange = Cursor.getRangeFromCursor(cursorConverter, range.cursor);
|
|
346
280
|
Object.assign(range, newRange);
|
|
347
281
|
mod = true;
|
|
348
282
|
}
|
|
@@ -374,6 +308,8 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
374
308
|
}
|
|
375
309
|
});
|
|
376
310
|
|
|
311
|
+
// TODO(burdon): Fire debounced callback if range selected (to show hint).
|
|
312
|
+
|
|
377
313
|
if (selected.active !== active || selected.closest !== closest) {
|
|
378
314
|
view.dispatch({ effects: setSelection.of(selected) });
|
|
379
315
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Facet } from '@codemirror/state';
|
|
6
|
+
|
|
7
|
+
import type { Range } from '../hooks';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Converts indexes into the text document into stable peer-independent cursors.
|
|
11
|
+
*
|
|
12
|
+
* See:
|
|
13
|
+
* - https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
|
|
14
|
+
* - https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
|
|
15
|
+
*
|
|
16
|
+
* @param {assoc} number Negative values will associate the cursor with the previous character
|
|
17
|
+
* while positive - with the next one.
|
|
18
|
+
*/
|
|
19
|
+
export interface CursorConverter {
|
|
20
|
+
toCursor(position: number, assoc?: -1 | 1 | undefined): string;
|
|
21
|
+
fromCursor(cursor: string): number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultCursorConverter: CursorConverter = {
|
|
25
|
+
toCursor: (position) => position.toString(),
|
|
26
|
+
fromCursor: (cursor) => parseInt(cursor),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class Cursor {
|
|
30
|
+
static readonly converter = Facet.define<CursorConverter, CursorConverter>({
|
|
31
|
+
combine: (providers) => providers[0] ?? defaultCursorConverter,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
static readonly getCursorFromRange = (cursorConverter: CursorConverter, range: Range) => {
|
|
35
|
+
const from = cursorConverter.toCursor(range.from);
|
|
36
|
+
const to = cursorConverter.toCursor(range.to, -1);
|
|
37
|
+
return [from, to].join(':');
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
static readonly getRangeFromCursor = (cursorConverter: CursorConverter, cursor: string) => {
|
|
41
|
+
const parts = cursor.split(':');
|
|
42
|
+
const from = cursorConverter.fromCursor(parts[0]);
|
|
43
|
+
const to = cursorConverter.fromCursor(parts[1]);
|
|
44
|
+
return from !== undefined && to !== undefined ? { from, to } : undefined;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -11,16 +11,15 @@ import {
|
|
|
11
11
|
type ViewUpdate,
|
|
12
12
|
WidgetType,
|
|
13
13
|
} from '@codemirror/view';
|
|
14
|
-
import get from 'lodash.get';
|
|
15
14
|
|
|
16
|
-
import {
|
|
15
|
+
import { getToken } from '../styles';
|
|
17
16
|
|
|
18
17
|
// TODO(burdon): Reconcile with theme.
|
|
19
18
|
const styles = EditorView.baseTheme({
|
|
20
19
|
'& .cm-hr': {
|
|
21
20
|
// TODO(burdon): ???
|
|
22
21
|
// Note that block-level decorations should not have vertical margins,
|
|
23
|
-
borderBottom: `1px solid ${
|
|
22
|
+
borderBottom: `1px solid ${getToken('extend.colors.neutral.200')}`,
|
|
24
23
|
},
|
|
25
24
|
});
|
|
26
25
|
|
|
@@ -29,11 +28,11 @@ class HorizontalRuleWidget extends WidgetType {
|
|
|
29
28
|
super();
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
override eq(other:
|
|
31
|
+
override eq(other: this) {
|
|
33
32
|
return this._pos === (other as any as HorizontalRuleWidget)._pos;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
toDOM(view: EditorView) {
|
|
35
|
+
override toDOM(view: EditorView) {
|
|
37
36
|
// TODO(burdon): Create <hr> element? Does this clash with markdown parser?
|
|
38
37
|
const el = document.createElement('div');
|
|
39
38
|
el.className = 'cm-hr';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import { type EditorState, type Extension, StateField, type Transaction, type Range } from '@codemirror/state';
|
|
7
|
+
import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
export type ImageOptions = {};
|
|
10
|
+
|
|
11
|
+
export const image = (options: ImageOptions = {}): Extension => {
|
|
12
|
+
return StateField.define<DecorationSet>({
|
|
13
|
+
create: (state) => {
|
|
14
|
+
return Decoration.set(buildDecorations(0, state.doc.length, state));
|
|
15
|
+
},
|
|
16
|
+
update: (value: DecorationSet, tr: Transaction) => {
|
|
17
|
+
if (!tr.docChanged && !tr.selection) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
// Find range of changes and cursor changes.
|
|
21
|
+
const cursor = tr.state.selection.main.head;
|
|
22
|
+
const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
|
|
23
|
+
let from = Math.min(cursor, oldCursor);
|
|
24
|
+
let to = Math.max(cursor, oldCursor);
|
|
25
|
+
tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
|
|
26
|
+
from = Math.min(from, fromB);
|
|
27
|
+
to = Math.max(to, toB);
|
|
28
|
+
});
|
|
29
|
+
// Expand to cover lines
|
|
30
|
+
from = tr.state.doc.lineAt(from).from;
|
|
31
|
+
to = tr.state.doc.lineAt(to).to;
|
|
32
|
+
return value.map(tr.changes).update({
|
|
33
|
+
filterFrom: from,
|
|
34
|
+
filterTo: to,
|
|
35
|
+
filter: () => false,
|
|
36
|
+
add: buildDecorations(from, to, tr.state),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
provide: (field) => EditorView.decorations.from(field),
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const buildDecorations = (from: number, to: number, state: EditorState) => {
|
|
44
|
+
const decorations: Range<Decoration>[] = [];
|
|
45
|
+
const cursor = state.selection.main.head;
|
|
46
|
+
|
|
47
|
+
syntaxTree(state).iterate({
|
|
48
|
+
enter: (node) => {
|
|
49
|
+
if (node.name === 'Image') {
|
|
50
|
+
const urlNode = node.node.getChild('URL');
|
|
51
|
+
if (urlNode) {
|
|
52
|
+
const hide = state.readOnly || cursor < node.from || cursor > node.to;
|
|
53
|
+
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
|
54
|
+
decorations.push(
|
|
55
|
+
Decoration.replace({
|
|
56
|
+
block: true, // Prevent cursor from entering.
|
|
57
|
+
widget: new ImageWidget(url),
|
|
58
|
+
}).range(hide ? node.from : node.to, node.to),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
from,
|
|
64
|
+
to,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return decorations;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
class ImageWidget extends WidgetType {
|
|
71
|
+
constructor(readonly _url: string) {
|
|
72
|
+
super();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override eq(other: this) {
|
|
76
|
+
return this._url === (other as any as ImageWidget)._url;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override toDOM(view: EditorView) {
|
|
80
|
+
const img = document.createElement('img');
|
|
81
|
+
img.setAttribute('src', this._url);
|
|
82
|
+
img.setAttribute('class', 'cm-image');
|
|
83
|
+
// Images are hidden until successfully loaded to avoid flickering effects.
|
|
84
|
+
img.onload = () => img.classList.add('cm-loaded-image');
|
|
85
|
+
return img;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -3,18 +3,20 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
export * from './autocomplete';
|
|
6
|
+
export * from './automerge';
|
|
7
|
+
export * from './awareness';
|
|
6
8
|
export * from './basic';
|
|
7
9
|
export * from './blast';
|
|
8
10
|
export * from './code';
|
|
9
11
|
export * from './comments';
|
|
12
|
+
export * from './cursor';
|
|
10
13
|
export * from './hr';
|
|
11
14
|
export * from './image';
|
|
12
15
|
export * from './link';
|
|
13
16
|
export * from './listener';
|
|
14
17
|
export * from './markdown';
|
|
15
18
|
export * from './mention';
|
|
16
|
-
export * from './mermaid';
|
|
17
19
|
export * from './table';
|
|
18
20
|
export * from './tasklist';
|
|
19
|
-
export * from './tooltip';
|
|
20
21
|
export * from './typewriter';
|
|
22
|
+
export * from './yjs';
|