@dxos/react-ui-editor 0.3.11-main.dd122a6 → 0.3.11-main.e0dc42b
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 +93 -86
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +6 -2
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +10 -10
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -2
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/index.d.ts +1 -0
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
- package/dist/types/src/extensions/code.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/package.json +23 -22
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +12 -8
- package/src/components/TextEditor/TextEditor.stories.tsx +3 -4
- package/src/components/TextEditor/TextEditor.tsx +59 -45
- package/src/extensions/autocomplete.ts +18 -17
- package/src/extensions/automerge/index.ts +1 -0
- package/src/extensions/awareness.ts +14 -15
- package/src/extensions/code.ts +10 -2
- package/src/extensions/comments.ts +41 -42
- package/src/extensions/hr.ts +2 -2
- package/src/extensions/image.ts +2 -2
- package/src/extensions/link.ts +4 -4
- package/src/extensions/table.ts +6 -3
- package/src/extensions/tasklist.ts +2 -2
- package/src/hooks/useTextModel.ts +1 -1
- package/src/index.ts +2 -0
|
@@ -15,6 +15,7 @@ import React, {
|
|
|
15
15
|
useEffect,
|
|
16
16
|
useImperativeHandle,
|
|
17
17
|
useState,
|
|
18
|
+
useRef,
|
|
18
19
|
} from 'react';
|
|
19
20
|
|
|
20
21
|
import { generateName } from '@dxos/display-name';
|
|
@@ -34,15 +35,10 @@ export type CursorInfo = {
|
|
|
34
35
|
to: number;
|
|
35
36
|
line: number;
|
|
36
37
|
lines: number;
|
|
38
|
+
length: number;
|
|
37
39
|
after?: string;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
|
-
export type TextEditorRef = {
|
|
41
|
-
root: HTMLDivElement | null;
|
|
42
|
-
state?: EditorState;
|
|
43
|
-
view?: EditorView;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
42
|
export type TextEditorSlots = {
|
|
47
43
|
root?: Omit<ComponentProps<'div'>, 'ref'>;
|
|
48
44
|
editor?: {
|
|
@@ -56,6 +52,8 @@ export type TextEditorSlots = {
|
|
|
56
52
|
|
|
57
53
|
export type TextEditorProps = {
|
|
58
54
|
model: EditorModel;
|
|
55
|
+
focus?: boolean;
|
|
56
|
+
selection?: { anchor: number; head?: number };
|
|
59
57
|
readonly?: boolean; // TODO(burdon): Move into model.
|
|
60
58
|
comments?: CommentRange[]; // TODO(burdon): Move into extension.
|
|
61
59
|
extensions?: Extension[];
|
|
@@ -65,17 +63,26 @@ export type TextEditorProps = {
|
|
|
65
63
|
|
|
66
64
|
/**
|
|
67
65
|
* Base text editor.
|
|
68
|
-
* NOTE: Rather than adding properties, try to create extensions that can be reused.
|
|
69
66
|
*/
|
|
70
|
-
export const BaseTextEditor = forwardRef<
|
|
71
|
-
(
|
|
72
|
-
|
|
67
|
+
export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
68
|
+
(
|
|
69
|
+
{ model, focus, selection, readonly, comments, extensions = [], editorMode, slots = defaultSlots },
|
|
70
|
+
forwardedRef,
|
|
71
|
+
) => {
|
|
73
72
|
const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
>();
|
|
78
|
-
|
|
73
|
+
const { themeMode } = useThemeContext();
|
|
74
|
+
|
|
75
|
+
// The editor view ref should only be used as an escape hatch.
|
|
76
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
77
|
+
const [view, setView] = useState<EditorView | null>(null);
|
|
78
|
+
useImperativeHandle<EditorView | null, EditorView | null>(forwardedRef, () => view, [view]);
|
|
79
|
+
|
|
80
|
+
// Focus.
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (view && focus) {
|
|
83
|
+
view.focus();
|
|
84
|
+
}
|
|
85
|
+
}, [view, focus]);
|
|
79
86
|
|
|
80
87
|
// TODO(burdon): Factor out as extension.
|
|
81
88
|
const { awareness, peer } = model;
|
|
@@ -99,7 +106,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
99
106
|
}, [view, comments]);
|
|
100
107
|
|
|
101
108
|
useEffect(() => {
|
|
102
|
-
if (!
|
|
109
|
+
if (!model || !rootRef.current) {
|
|
103
110
|
return;
|
|
104
111
|
}
|
|
105
112
|
|
|
@@ -107,6 +114,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
107
114
|
// https://codemirror.net/docs/ref/#state.EditorStateConfig
|
|
108
115
|
const state = EditorState.create({
|
|
109
116
|
doc: model.text(),
|
|
117
|
+
selection,
|
|
110
118
|
extensions: [
|
|
111
119
|
readonly && EditorState.readOnly.of(readonly),
|
|
112
120
|
|
|
@@ -131,36 +139,35 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
131
139
|
// If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
|
|
132
140
|
// Ideally this should not happen except when changing between text objects.
|
|
133
141
|
view?.destroy();
|
|
134
|
-
|
|
142
|
+
const newView = new EditorView({
|
|
143
|
+
parent: rootRef.current,
|
|
135
144
|
state,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// dispatch: (transaction, view) => {
|
|
142
|
-
// view.update([transaction]);
|
|
143
|
-
// },
|
|
144
|
-
}),
|
|
145
|
+
// NOTE: Uncomment to spy on all transactions.
|
|
146
|
+
// https://codemirror.net/docs/ref/#view.EditorView.dispatch
|
|
147
|
+
// dispatch: (transaction, view) => {
|
|
148
|
+
// view.update([transaction]);
|
|
149
|
+
// },
|
|
145
150
|
});
|
|
146
151
|
|
|
152
|
+
setView(newView);
|
|
147
153
|
return () => {
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
newView?.destroy();
|
|
155
|
+
setView(null);
|
|
150
156
|
};
|
|
151
|
-
}, [
|
|
157
|
+
}, [rootRef, model, readonly, editorMode, themeMode]);
|
|
152
158
|
|
|
153
159
|
const handleKeyUp = useCallback(
|
|
154
160
|
(event: KeyboardEvent) => {
|
|
155
161
|
const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
|
|
156
162
|
switch (key) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
// TODO(burdon): ???
|
|
164
|
+
// case 'Enter': {
|
|
165
|
+
// view?.contentDOM.focus();
|
|
166
|
+
// break;
|
|
167
|
+
// }
|
|
161
168
|
|
|
162
169
|
case 'Escape': {
|
|
163
|
-
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) &&
|
|
170
|
+
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
|
|
164
171
|
break;
|
|
165
172
|
}
|
|
166
173
|
}
|
|
@@ -171,7 +178,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
171
178
|
return (
|
|
172
179
|
<div
|
|
173
180
|
key={model.id}
|
|
174
|
-
ref={
|
|
181
|
+
ref={rootRef}
|
|
175
182
|
tabIndex={0}
|
|
176
183
|
{...slots?.root}
|
|
177
184
|
{...(editorMode !== 'vim' && tabsterDOMAttribute)}
|
|
@@ -181,32 +188,39 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
181
188
|
},
|
|
182
189
|
);
|
|
183
190
|
|
|
184
|
-
export const TextEditor = forwardRef<
|
|
185
|
-
({ readonly, extensions = [], slots
|
|
191
|
+
export const TextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
192
|
+
({ readonly, extensions = [], slots, ...props }, forwardedRef) => {
|
|
186
193
|
const { themeMode } = useThemeContext();
|
|
187
|
-
const
|
|
194
|
+
const updatedSlots = defaultsDeep({}, slots, defaultTextSlots);
|
|
188
195
|
return (
|
|
189
196
|
<BaseTextEditor
|
|
190
197
|
ref={forwardedRef}
|
|
191
198
|
readonly={readonly}
|
|
192
|
-
extensions={[
|
|
193
|
-
|
|
199
|
+
extensions={[
|
|
200
|
+
basicBundle({ readonly, themeMode, placeholder: updatedSlots?.editor?.placeholder }),
|
|
201
|
+
...extensions,
|
|
202
|
+
]}
|
|
203
|
+
slots={updatedSlots}
|
|
194
204
|
{...props}
|
|
195
205
|
/>
|
|
196
206
|
);
|
|
197
207
|
},
|
|
198
208
|
);
|
|
199
209
|
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
// TODO(burdon): Remove (Just provide bundle, slots).
|
|
211
|
+
export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
|
|
212
|
+
({ readonly, extensions = [], slots, ...props }, forwardedRef) => {
|
|
202
213
|
const { themeMode } = useThemeContext();
|
|
203
|
-
const
|
|
214
|
+
const updatedSlots = defaultsDeep({}, slots, defaultMarkdownSlots);
|
|
204
215
|
return (
|
|
205
216
|
<BaseTextEditor
|
|
206
217
|
ref={forwardedRef}
|
|
207
218
|
readonly={readonly}
|
|
208
|
-
extensions={[
|
|
209
|
-
|
|
219
|
+
extensions={[
|
|
220
|
+
markdownBundle({ readonly, themeMode, placeholder: updatedSlots?.editor?.placeholder }),
|
|
221
|
+
...extensions,
|
|
222
|
+
]}
|
|
223
|
+
slots={updatedSlots}
|
|
210
224
|
{...props}
|
|
211
225
|
/>
|
|
212
226
|
);
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type CompletionContext,
|
|
14
14
|
type CompletionResult,
|
|
15
15
|
} from '@codemirror/autocomplete';
|
|
16
|
+
import { markdownLanguage } from '@codemirror/lang-markdown';
|
|
16
17
|
import { keymap } from '@codemirror/view';
|
|
17
18
|
|
|
18
19
|
export type AutocompleteResult = Completion;
|
|
@@ -28,32 +29,32 @@ export const autocomplete = ({ onSearch }: AutocompleteOptions) => {
|
|
|
28
29
|
return [
|
|
29
30
|
// https://codemirror.net/docs/ref/#view.keymap
|
|
30
31
|
// https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
|
|
32
|
+
// TODO(burdon): Set custom keymap.
|
|
31
33
|
keymap.of(completionKeymap),
|
|
32
34
|
|
|
33
35
|
// https://codemirror.net/examples/autocompletion
|
|
34
36
|
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
35
37
|
autocompletion({
|
|
36
|
-
|
|
37
|
-
// defaultKeymap: false,
|
|
38
|
+
defaultKeymap: false,
|
|
38
39
|
|
|
39
40
|
// Don't start unless key press.
|
|
40
41
|
activateOnTyping: false,
|
|
42
|
+
}),
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
],
|
|
44
|
+
// TODO(burdon): Option to create new page?
|
|
45
|
+
// TODO(burdon): Optional decoration via addToOptions
|
|
46
|
+
markdownLanguage.data.of({
|
|
47
|
+
autocomplete: (context: CompletionContext): CompletionResult | null => {
|
|
48
|
+
const match = context.matchBefore(/\w*/);
|
|
49
|
+
if (!match || (match.from === match.to && !context.explicit)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
from: match.from,
|
|
55
|
+
options: onSearch(match.text.toLowerCase()),
|
|
56
|
+
};
|
|
57
|
+
},
|
|
57
58
|
}),
|
|
58
59
|
];
|
|
59
60
|
};
|
|
@@ -208,24 +208,22 @@ export class RemoteSelectionsDecorator implements PluginValue {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
class RemoteCaretWidget extends WidgetType {
|
|
211
|
-
constructor(
|
|
211
|
+
constructor(private readonly _name: string, private readonly _color: string) {
|
|
212
212
|
super();
|
|
213
|
-
this.name = name;
|
|
214
|
-
this.color = color;
|
|
215
213
|
}
|
|
216
214
|
|
|
217
|
-
toDOM(): HTMLElement {
|
|
215
|
+
override toDOM(): HTMLElement {
|
|
218
216
|
const span = document.createElement('span');
|
|
219
217
|
span.className = 'cm-ySelectionCaret';
|
|
220
|
-
span.style.backgroundColor = this.
|
|
221
|
-
span.style.borderColor = this.
|
|
218
|
+
span.style.backgroundColor = this._color;
|
|
219
|
+
span.style.borderColor = this._color;
|
|
222
220
|
|
|
223
221
|
const dot = document.createElement('div');
|
|
224
222
|
dot.className = 'cm-ySelectionCaretDot';
|
|
225
223
|
|
|
226
224
|
const info = document.createElement('div');
|
|
227
225
|
info.className = 'cm-ySelectionInfo';
|
|
228
|
-
info.innerText = this.
|
|
226
|
+
info.innerText = this._name;
|
|
229
227
|
|
|
230
228
|
span.appendChild(document.createTextNode('\u2060'));
|
|
231
229
|
span.appendChild(dot);
|
|
@@ -236,18 +234,14 @@ class RemoteCaretWidget extends WidgetType {
|
|
|
236
234
|
return span;
|
|
237
235
|
}
|
|
238
236
|
|
|
239
|
-
override eq(widget: this) {
|
|
240
|
-
return widget.color === this.color;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
compare(widget: this) {
|
|
244
|
-
return widget.color === this.color;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
237
|
override updateDOM() {
|
|
248
238
|
return false;
|
|
249
239
|
}
|
|
250
240
|
|
|
241
|
+
override eq(widget: this) {
|
|
242
|
+
return widget._color === this._color;
|
|
243
|
+
}
|
|
244
|
+
|
|
251
245
|
override get estimatedHeight() {
|
|
252
246
|
return -1;
|
|
253
247
|
}
|
|
@@ -255,6 +249,11 @@ class RemoteCaretWidget extends WidgetType {
|
|
|
255
249
|
override ignoreEvent() {
|
|
256
250
|
return true;
|
|
257
251
|
}
|
|
252
|
+
|
|
253
|
+
// TODO(burdon): Not used?
|
|
254
|
+
compare(widget: this) {
|
|
255
|
+
return widget._color === this._color;
|
|
256
|
+
}
|
|
258
257
|
}
|
|
259
258
|
|
|
260
259
|
// TODO(burdon): Rename prefix (y for yjs?)
|
package/src/extensions/code.ts
CHANGED
|
@@ -55,7 +55,7 @@ const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
|
|
|
55
55
|
export const code = () => {
|
|
56
56
|
const getDecorations = (view: EditorView) => {
|
|
57
57
|
const decorations: Range<Decoration>[] = [];
|
|
58
|
-
if (view.state.readOnly) {
|
|
58
|
+
if (!view.hasFocus || view.state.readOnly) {
|
|
59
59
|
const text = view.state.doc.sliceString(0);
|
|
60
60
|
const matches = text.matchAll(CODE_REGEX);
|
|
61
61
|
const blocks = view.viewportLineBlocks;
|
|
@@ -78,13 +78,21 @@ export const code = () => {
|
|
|
78
78
|
return [
|
|
79
79
|
ViewPlugin.fromClass(
|
|
80
80
|
class {
|
|
81
|
+
hasFocus = false;
|
|
81
82
|
decorations: DecorationSet;
|
|
82
83
|
constructor(view: EditorView) {
|
|
83
84
|
this.decorations = getDecorations(view);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
update(update: ViewUpdate) {
|
|
87
|
-
if (
|
|
88
|
+
if (
|
|
89
|
+
// TODO(burdon): Generalize for other extensions.
|
|
90
|
+
this.hasFocus !== update.view.hasFocus ||
|
|
91
|
+
update.startState.readOnly !== update.view.state.readOnly ||
|
|
92
|
+
update.docChanged ||
|
|
93
|
+
update.viewportChanged
|
|
94
|
+
) {
|
|
95
|
+
this.hasFocus = update.view.hasFocus;
|
|
88
96
|
this.decorations = getDecorations(update.view);
|
|
89
97
|
}
|
|
90
98
|
}
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
hoverTooltip,
|
|
8
8
|
keymap,
|
|
9
9
|
type Command,
|
|
10
|
-
type DecorationSet,
|
|
11
|
-
type ViewUpdate,
|
|
12
10
|
Decoration,
|
|
11
|
+
type DecorationSet,
|
|
13
12
|
EditorView,
|
|
14
13
|
MatchDecorator,
|
|
14
|
+
type Rect,
|
|
15
|
+
type ViewUpdate,
|
|
15
16
|
ViewPlugin,
|
|
16
17
|
WidgetType,
|
|
17
|
-
type Rect,
|
|
18
18
|
} from '@codemirror/view';
|
|
19
19
|
import sortBy from 'lodash.sortby';
|
|
20
20
|
|
|
@@ -159,17 +159,13 @@ const highlightDecorations = EditorView.decorations.compute([commentsStateField]
|
|
|
159
159
|
* May correspond to a markdown bookmark.
|
|
160
160
|
*/
|
|
161
161
|
class BookmarkWidget extends WidgetType {
|
|
162
|
-
constructor(
|
|
163
|
-
private readonly _anchor: number,
|
|
164
|
-
private readonly _id: string,
|
|
165
|
-
private readonly _handleClick?: () => void,
|
|
166
|
-
) {
|
|
162
|
+
constructor(private readonly _id: string, private readonly _handleClick?: () => void) {
|
|
167
163
|
super();
|
|
168
164
|
invariant(this._id);
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
override eq(other:
|
|
172
|
-
return this.
|
|
167
|
+
override eq(other: this) {
|
|
168
|
+
return this._id === other._id;
|
|
173
169
|
}
|
|
174
170
|
|
|
175
171
|
override toDOM() {
|
|
@@ -197,7 +193,7 @@ export type CommentsOptions = {
|
|
|
197
193
|
/**
|
|
198
194
|
* Called to render tooltip.
|
|
199
195
|
*/
|
|
200
|
-
onHover?: (el: Element) => void;
|
|
196
|
+
onHover?: (el: Element, shortcut: string) => void;
|
|
201
197
|
};
|
|
202
198
|
|
|
203
199
|
/**
|
|
@@ -213,6 +209,8 @@ export type CommentsOptions = {
|
|
|
213
209
|
* 6). Optionally, implements a hoverTooltip to show hints when creating a selection range.
|
|
214
210
|
*/
|
|
215
211
|
export const comments = (options: CommentsOptions = {}): Extension => {
|
|
212
|
+
const { key: shortcut = "meta-'" } = options;
|
|
213
|
+
|
|
216
214
|
const handleSelect = debounce((state: CommentsState) => options.onSelect?.(state), 200);
|
|
217
215
|
|
|
218
216
|
/**
|
|
@@ -257,38 +255,37 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
257
255
|
/**
|
|
258
256
|
* Bookmark widget decoration.
|
|
259
257
|
*/
|
|
260
|
-
const bookmarksViewPlugin =
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
258
|
+
const bookmarksViewPlugin = options.footnote
|
|
259
|
+
? ViewPlugin.fromClass(
|
|
260
|
+
class BookmarkViewPlugin {
|
|
261
|
+
// Match markdown footnotes (option).
|
|
262
|
+
// https://www.markdownguide.org/extended-syntax/#footnotes
|
|
263
|
+
static bookmarkMatcher = new MatchDecorator({
|
|
264
|
+
regexp: /\[\^(\w+)\]/g,
|
|
265
|
+
decoration: (match) => {
|
|
266
|
+
const id = match[1];
|
|
267
|
+
return Decoration.replace({ id, widget: new BookmarkWidget(id) });
|
|
268
|
+
},
|
|
271
269
|
});
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
270
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
bookmarks: DecorationSet;
|
|
272
|
+
constructor(view: EditorView) {
|
|
273
|
+
this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
|
|
274
|
+
}
|
|
279
275
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
276
|
+
update(update: ViewUpdate) {
|
|
277
|
+
this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update, this.bookmarks);
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
decorations: (instance) => instance.bookmarks,
|
|
282
|
+
provide: (plugin) =>
|
|
283
|
+
EditorView.atomicRanges.of((view) => {
|
|
284
|
+
return view.plugin(plugin)?.bookmarks || Decoration.none;
|
|
285
|
+
}),
|
|
286
|
+
},
|
|
287
|
+
)
|
|
288
|
+
: [];
|
|
292
289
|
|
|
293
290
|
return [
|
|
294
291
|
bookmarksViewPlugin,
|
|
@@ -302,7 +299,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
302
299
|
options.onCreate
|
|
303
300
|
? keymap.of([
|
|
304
301
|
{
|
|
305
|
-
key:
|
|
302
|
+
key: shortcut,
|
|
306
303
|
run: callbackWrapper(createCommentThread),
|
|
307
304
|
},
|
|
308
305
|
])
|
|
@@ -324,7 +321,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
324
321
|
create: () => {
|
|
325
322
|
// TODO(burdon): Dispatch to react callback to render (or use SSR)?
|
|
326
323
|
const el = document.createElement('div');
|
|
327
|
-
options.onHover?.(el);
|
|
324
|
+
options.onHover?.(el, shortcut);
|
|
328
325
|
return { dom: el, offset: { x: 0, y: 8 } };
|
|
329
326
|
},
|
|
330
327
|
};
|
|
@@ -392,6 +389,8 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
392
389
|
}
|
|
393
390
|
});
|
|
394
391
|
|
|
392
|
+
// TODO(burdon): Fire debounced callback if range selected (to show hint).
|
|
393
|
+
|
|
395
394
|
if (selected.active !== active || selected.closest !== closest) {
|
|
396
395
|
view.dispatch({ effects: setSelection.of(selected) });
|
|
397
396
|
|
package/src/extensions/hr.ts
CHANGED
|
@@ -28,11 +28,11 @@ class HorizontalRuleWidget extends WidgetType {
|
|
|
28
28
|
super();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
override eq(other:
|
|
31
|
+
override eq(other: this) {
|
|
32
32
|
return this._pos === (other as any as HorizontalRuleWidget)._pos;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
toDOM(view: EditorView) {
|
|
35
|
+
override toDOM(view: EditorView) {
|
|
36
36
|
// TODO(burdon): Create <hr> element? Does this clash with markdown parser?
|
|
37
37
|
const el = document.createElement('div');
|
|
38
38
|
el.className = 'cm-hr';
|
package/src/extensions/image.ts
CHANGED
|
@@ -72,11 +72,11 @@ class ImageWidget extends WidgetType {
|
|
|
72
72
|
super();
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
override eq(other:
|
|
75
|
+
override eq(other: this) {
|
|
76
76
|
return this._url === (other as any as ImageWidget)._url;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
toDOM(view: EditorView) {
|
|
79
|
+
override toDOM(view: EditorView) {
|
|
80
80
|
const img = document.createElement('img');
|
|
81
81
|
img.setAttribute('src', this._url);
|
|
82
82
|
img.setAttribute('class', 'cm-image');
|
package/src/extensions/link.ts
CHANGED
|
@@ -93,11 +93,11 @@ class LinkButton extends WidgetType {
|
|
|
93
93
|
super();
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
override eq(other:
|
|
96
|
+
override eq(other: this) {
|
|
97
97
|
return this._url === other._url;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
toDOM(view: EditorView) {
|
|
100
|
+
override toDOM(view: EditorView) {
|
|
101
101
|
const el = document.createElement('span');
|
|
102
102
|
this._onAttach!(el, this._url);
|
|
103
103
|
return el;
|
|
@@ -109,11 +109,11 @@ class LinkText extends WidgetType {
|
|
|
109
109
|
super();
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
override eq(other:
|
|
112
|
+
override eq(other: this) {
|
|
113
113
|
return this._url === other._url;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
toDOM(view: EditorView) {
|
|
116
|
+
override toDOM(view: EditorView) {
|
|
117
117
|
const link = document.createElement('a');
|
|
118
118
|
link.setAttribute('class', 'cm-link');
|
|
119
119
|
link.textContent = this._text;
|
package/src/extensions/table.ts
CHANGED
|
@@ -81,7 +81,6 @@ const update = (state: EditorState, options: TableOptions) => {
|
|
|
81
81
|
table.from,
|
|
82
82
|
table.to,
|
|
83
83
|
Decoration.replace({
|
|
84
|
-
block: true,
|
|
85
84
|
widget: new TableWidget(table),
|
|
86
85
|
}),
|
|
87
86
|
);
|
|
@@ -98,14 +97,14 @@ class TableWidget extends WidgetType {
|
|
|
98
97
|
super();
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
override eq(other:
|
|
100
|
+
override eq(other: this) {
|
|
102
101
|
return (
|
|
103
102
|
this._table.header?.join() === other._table.header?.join() &&
|
|
104
103
|
this._table.rows?.join() === other._table.rows?.join()
|
|
105
104
|
);
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
toDOM(view: EditorView) {
|
|
107
|
+
override toDOM(view: EditorView) {
|
|
109
108
|
const table = document.createElement('table');
|
|
110
109
|
|
|
111
110
|
{
|
|
@@ -132,4 +131,8 @@ class TableWidget extends WidgetType {
|
|
|
132
131
|
|
|
133
132
|
return table;
|
|
134
133
|
}
|
|
134
|
+
|
|
135
|
+
override ignoreEvent(e: Event) {
|
|
136
|
+
return !/^mouse/.test(e.type);
|
|
137
|
+
}
|
|
135
138
|
}
|
|
@@ -29,11 +29,11 @@ class CheckboxWidget extends WidgetType {
|
|
|
29
29
|
super();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
override eq(other:
|
|
32
|
+
override eq(other: this) {
|
|
33
33
|
return this._checked === other._checked && this._indent === other._indent;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
toDOM(view: EditorView) {
|
|
36
|
+
override toDOM(view: EditorView) {
|
|
37
37
|
const wrap = document.createElement('span');
|
|
38
38
|
wrap.className = 'cm-task-item';
|
|
39
39
|
wrap.setAttribute('aria-hidden', 'true');
|
|
@@ -66,7 +66,7 @@ export type UseTextModelProps = {
|
|
|
66
66
|
// TODO(wittjosiah): Factor out to common package? @dxos/react-client?
|
|
67
67
|
export const useTextModel = (props: UseTextModelProps): EditorModel | undefined => {
|
|
68
68
|
const { identity, space, text } = props;
|
|
69
|
-
const [model, setModel] = useState<EditorModel | undefined>(
|
|
69
|
+
const [model, setModel] = useState<EditorModel | undefined>();
|
|
70
70
|
useEffect(() => setModel(createModel(props)), [identity, space, text]);
|
|
71
71
|
return model;
|
|
72
72
|
};
|
package/src/index.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
|
|
6
6
|
export { Doc, YText, YXmlFragment } from '@dxos/text-model';
|
|
7
7
|
export { type Extension } from '@codemirror/state';
|
|
8
|
+
export { type EditorView } from '@codemirror/view';
|
|
8
9
|
|
|
9
10
|
export * from './components';
|
|
10
11
|
export * from './extensions';
|
|
11
12
|
export * from './hooks';
|
|
12
13
|
export { getToken } from './styles';
|
|
13
14
|
export * from './themes';
|
|
15
|
+
export * from './util';
|