@dxos/react-ui-editor 0.3.11-main.b00882f → 0.3.11-main.b3f97d8
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 +2386 -968
- 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 +1 -2
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/{yjs.stories.d.ts → hooks.stories.d.ts} +13 -2
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +29 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +25 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/index.d.ts +2 -0
- package/dist/types/src/components/Toolbar/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/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
- package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
- package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/defs.d.ts +5 -5
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/semaphore.d.ts +2 -1
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness.d.ts +2 -4
- package/dist/types/src/extensions/awareness.d.ts.map +1 -1
- package/dist/types/src/extensions/basic.d.ts +1 -1
- package/dist/types/src/extensions/basic.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +11 -3
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +0 -7
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts +2 -2
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/formatting.d.ts +57 -0
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
- package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
- package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts +8 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
- package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
- package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
- package/dist/types/src/hooks/defs.d.ts +1 -4
- package/dist/types/src/hooks/defs.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
- package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useEditorView.d.ts +17 -0
- package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextEditor.d.ts +36 -14
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +12 -0
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts +2 -2
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/package.json +39 -29
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +15 -19
- package/src/components/TextEditor/TextEditor.tsx +27 -19
- package/src/components/TextEditor/automerge.stories.tsx +1 -2
- package/src/components/TextEditor/hooks.stories.tsx +111 -0
- package/src/components/Toolbar/Toolbar.stories.tsx +102 -0
- package/src/components/Toolbar/Toolbar.tsx +245 -0
- package/src/components/Toolbar/index.ts +5 -0
- package/src/components/index.ts +1 -0
- package/src/extensions/automerge/automerge.spec.tsx +76 -0
- package/src/extensions/automerge/automerge.test.ts +13 -0
- package/src/extensions/automerge/automerge.ts +26 -11
- package/src/extensions/automerge/cursor.ts +3 -3
- package/src/extensions/automerge/defs.ts +9 -9
- package/src/extensions/automerge/semaphore.ts +17 -19
- package/src/extensions/automerge/update-automerge.ts +12 -6
- package/src/extensions/automerge/update-codemirror.ts +5 -5
- package/src/extensions/awareness.ts +21 -35
- package/src/extensions/basic.ts +15 -13
- package/src/extensions/blast.ts +4 -1
- package/src/extensions/comments.ts +126 -68
- package/src/extensions/index.ts +0 -7
- package/src/extensions/markdown/bundle.ts +9 -8
- package/src/extensions/markdown/code.ts +150 -0
- package/src/extensions/markdown/formatting.test.ts +481 -0
- package/src/extensions/markdown/formatting.ts +1198 -0
- package/src/extensions/markdown/heading.ts +59 -0
- package/src/extensions/markdown/highlight.ts +9 -15
- package/src/extensions/{hr.ts → markdown/hr.ts} +3 -4
- package/src/extensions/markdown/index.ts +8 -0
- package/src/extensions/{link.ts → markdown/link.ts} +13 -4
- package/src/extensions/{tasklist.ts → markdown/tasklist.ts} +2 -3
- package/src/hooks/awareness-provider.ts +24 -20
- package/src/hooks/defs.ts +1 -6
- package/src/hooks/index.ts +2 -2
- package/src/hooks/useActionHandler.ts +93 -0
- package/src/hooks/useEditorView.ts +29 -0
- package/src/hooks/useTextEditor.ts +143 -19
- package/src/hooks/useTextModel.ts +65 -12
- package/src/index.ts +1 -1
- package/src/styles/markdown.ts +9 -10
- package/src/themes/default.ts +11 -8
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
- package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts +0 -26
- package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/code.d.ts.map +0 -1
- package/dist/types/src/extensions/hr.d.ts.map +0 -1
- package/dist/types/src/extensions/image.d.ts.map +0 -1
- package/dist/types/src/extensions/link.d.ts.map +0 -1
- package/dist/types/src/extensions/table.d.ts.map +0 -1
- package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
- package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/index.d.ts +0 -2
- package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
- package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
- package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
- package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge.d.ts +0 -4
- package/dist/types/src/hooks/automerge.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs.d.ts +0 -28
- package/dist/types/src/hooks/yjs.d.ts.map +0 -1
- package/dist/types/src/testing/index.d.ts +0 -3
- package/dist/types/src/testing/index.d.ts.map +0 -1
- package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
- package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
- package/dist/types/src/testing/proto/index.d.ts +0 -2
- package/dist/types/src/testing/proto/index.d.ts.map +0 -1
- package/dist/types/src/testing/replicator.d.ts +0 -45
- package/dist/types/src/testing/replicator.d.ts.map +0 -1
- package/src/components/TextEditor/codemirror.stories.ts +0 -115
- package/src/components/TextEditor/comments.stories.tsx +0 -357
- package/src/components/TextEditor/yjs.stories.tsx +0 -56
- package/src/extensions/code.ts +0 -104
- package/src/extensions/yjs/cursor.ts +0 -22
- package/src/extensions/yjs/index.ts +0 -5
- package/src/extensions/yjs/yjs.test.ts +0 -35
- package/src/extensions/yjs/yjs.ts +0 -16
- package/src/hooks/automerge.ts +0 -53
- package/src/hooks/yjs.ts +0 -157
- package/src/testing/index.ts +0 -6
- package/src/testing/proto/gen/schema.ts +0 -49
- package/src/testing/proto/index.ts +0 -5
- package/src/testing/proto/schema.proto +0 -15
- package/src/testing/replicator.ts +0 -184
- /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
- /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
- /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
- /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
- /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
- /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
- /package/src/extensions/{image.ts → markdown/image.ts} +0 -0
- /package/src/extensions/{table.ts → markdown/table.ts} +0 -0
|
@@ -12,16 +12,11 @@ import {
|
|
|
12
12
|
type ViewUpdate,
|
|
13
13
|
WidgetType,
|
|
14
14
|
} from '@codemirror/view';
|
|
15
|
-
import { useEffect } from 'react';
|
|
16
15
|
|
|
17
16
|
import { Event } from '@dxos/async';
|
|
18
17
|
import { Context } from '@dxos/context';
|
|
19
|
-
import { generateName } from '@dxos/display-name';
|
|
20
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
21
|
-
import { getColorForValue } from '@dxos/react-ui-theme';
|
|
22
18
|
|
|
23
19
|
import { Cursor, type CursorConverter } from './cursor';
|
|
24
|
-
import { type EditorModel } from '../hooks';
|
|
25
20
|
|
|
26
21
|
export interface AwarenessProvider {
|
|
27
22
|
remoteStateChange: Event<void>;
|
|
@@ -29,8 +24,8 @@ export interface AwarenessProvider {
|
|
|
29
24
|
open(): void;
|
|
30
25
|
close(): void;
|
|
31
26
|
|
|
32
|
-
update(position: AwarenessPosition | undefined): void;
|
|
33
27
|
getRemoteStates(): AwarenessState[];
|
|
28
|
+
update(position: AwarenessPosition | undefined): void;
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
const dummyProvider: AwarenessProvider = {
|
|
@@ -39,11 +34,11 @@ const dummyProvider: AwarenessProvider = {
|
|
|
39
34
|
open: () => {},
|
|
40
35
|
close: () => {},
|
|
41
36
|
|
|
42
|
-
update: () => {},
|
|
43
37
|
getRemoteStates: () => [],
|
|
38
|
+
update: () => {},
|
|
44
39
|
};
|
|
45
40
|
|
|
46
|
-
export const
|
|
41
|
+
export const awarenessProvider = Facet.define<AwarenessProvider, AwarenessProvider>({
|
|
47
42
|
combine: (providers) => providers[0] ?? dummyProvider,
|
|
48
43
|
});
|
|
49
44
|
|
|
@@ -73,7 +68,7 @@ export type AwarenessState = {
|
|
|
73
68
|
*/
|
|
74
69
|
export const awareness = (provider = dummyProvider): Extension => {
|
|
75
70
|
return [
|
|
76
|
-
|
|
71
|
+
awarenessProvider.of(provider),
|
|
77
72
|
ViewPlugin.fromClass(RemoteSelectionsDecorator, {
|
|
78
73
|
decorations: (value) => value.decorations,
|
|
79
74
|
}),
|
|
@@ -81,19 +76,6 @@ export const awareness = (provider = dummyProvider): Extension => {
|
|
|
81
76
|
];
|
|
82
77
|
};
|
|
83
78
|
|
|
84
|
-
export const useAwareness = ({ awareness, peer }: EditorModel) => {
|
|
85
|
-
const { themeMode } = useThemeContext();
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (awareness && peer) {
|
|
88
|
-
awareness.setLocalStateField('user', {
|
|
89
|
-
name: peer.name ?? generateName(peer.id),
|
|
90
|
-
color: getColorForValue({ value: peer.id, type: 'color' }),
|
|
91
|
-
colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}, [awareness, peer, themeMode]);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
79
|
/**
|
|
98
80
|
* Generates selection decorations from remote peers.
|
|
99
81
|
*/
|
|
@@ -109,7 +91,7 @@ export class RemoteSelectionsDecorator implements PluginValue {
|
|
|
109
91
|
|
|
110
92
|
constructor(view: EditorView) {
|
|
111
93
|
this._cursorConverter = view.state.facet(Cursor.converter);
|
|
112
|
-
this._provider = view.state.facet(
|
|
94
|
+
this._provider = view.state.facet(awarenessProvider);
|
|
113
95
|
this._provider.open();
|
|
114
96
|
this._provider.remoteStateChange.on(this._ctx, () => {
|
|
115
97
|
view.dispatch({ annotations: [RemoteSelectionChangedAnnotation.of([])] });
|
|
@@ -225,7 +207,10 @@ export class RemoteSelectionsDecorator implements PluginValue {
|
|
|
225
207
|
}
|
|
226
208
|
|
|
227
209
|
class RemoteCaretWidget extends WidgetType {
|
|
228
|
-
constructor(
|
|
210
|
+
constructor(
|
|
211
|
+
private readonly _name: string,
|
|
212
|
+
private readonly _color: string,
|
|
213
|
+
) {
|
|
229
214
|
super();
|
|
230
215
|
}
|
|
231
216
|
|
|
@@ -268,7 +253,6 @@ class RemoteCaretWidget extends WidgetType {
|
|
|
268
253
|
}
|
|
269
254
|
}
|
|
270
255
|
|
|
271
|
-
// TODO(burdon): Mixed case?
|
|
272
256
|
const styles = EditorView.baseTheme({
|
|
273
257
|
'.cm-collab-selection': {},
|
|
274
258
|
'.cm-collab-selectionLine': {
|
|
@@ -283,38 +267,40 @@ const styles = EditorView.baseTheme({
|
|
|
283
267
|
marginRight: '-1px',
|
|
284
268
|
boxSizing: 'border-box',
|
|
285
269
|
display: 'inline',
|
|
270
|
+
cursor: 'pointer',
|
|
286
271
|
},
|
|
287
272
|
'.cm-collab-selectionCaretDot': {
|
|
288
273
|
borderRadius: '50%',
|
|
289
274
|
position: 'absolute',
|
|
290
|
-
width: '.
|
|
291
|
-
height: '.
|
|
292
|
-
top: '-.
|
|
293
|
-
left: '-.
|
|
275
|
+
width: '.5em',
|
|
276
|
+
height: '.5em',
|
|
277
|
+
top: '-.25em',
|
|
278
|
+
left: '-.25em',
|
|
294
279
|
backgroundColor: 'inherit',
|
|
295
280
|
transition: 'transform .3s ease-in-out',
|
|
296
281
|
boxSizing: 'border-box',
|
|
297
282
|
},
|
|
298
283
|
'.cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot': {
|
|
299
|
-
transformOrigin: 'bottom center',
|
|
300
284
|
transform: 'scale(0)',
|
|
285
|
+
transformOrigin: 'center',
|
|
301
286
|
},
|
|
302
287
|
'.cm-collab-selectionInfo': {
|
|
303
288
|
position: 'absolute',
|
|
304
|
-
|
|
305
|
-
|
|
289
|
+
transform: 'translate(-50%, 0)',
|
|
290
|
+
top: '-20px',
|
|
291
|
+
left: 0,
|
|
306
292
|
fontSize: '.75em',
|
|
307
|
-
fontFamily: 'serif',
|
|
293
|
+
fontFamily: 'sans-serif',
|
|
308
294
|
fontStyle: 'normal',
|
|
309
295
|
fontWeight: 'normal',
|
|
310
296
|
lineHeight: 'normal',
|
|
311
297
|
userSelect: 'none',
|
|
312
298
|
color: 'white',
|
|
313
|
-
|
|
314
|
-
paddingRight: '2px',
|
|
299
|
+
padding: '2px',
|
|
315
300
|
zIndex: 101,
|
|
316
301
|
transition: 'opacity .3s ease-in-out',
|
|
317
302
|
backgroundColor: 'inherit',
|
|
303
|
+
borderRadius: '2px',
|
|
318
304
|
// These should be separate.
|
|
319
305
|
opacity: 0,
|
|
320
306
|
transitionDelay: '0s',
|
package/src/extensions/basic.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
|
10
10
|
import { drawSelection, EditorView, highlightActiveLine, placeholder } from '@codemirror/view';
|
|
11
11
|
|
|
12
12
|
import { type ThemeMode } from '@dxos/react-ui';
|
|
13
|
+
import { isNotFalsy } from '@dxos/util';
|
|
13
14
|
|
|
14
15
|
import { type TextEditorProps } from '../components';
|
|
15
16
|
|
|
@@ -17,21 +18,22 @@ export type BasicBundleOptions = {
|
|
|
17
18
|
themeMode?: ThemeMode;
|
|
18
19
|
} & Pick<TextEditorProps, 'placeholder' | 'lineWrapping'>;
|
|
19
20
|
|
|
20
|
-
export const
|
|
21
|
+
export const createBasicBundle = ({
|
|
21
22
|
themeMode,
|
|
22
23
|
placeholder: _placeholder,
|
|
23
24
|
lineWrapping = true,
|
|
24
|
-
}: BasicBundleOptions): Extension[] =>
|
|
25
|
-
|
|
25
|
+
}: BasicBundleOptions = {}): Extension[] =>
|
|
26
|
+
[
|
|
27
|
+
lineWrapping && EditorView.lineWrapping,
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// https://codemirror.net/docs/ref/#codemirror.minimalSetup
|
|
30
|
+
bracketMatching(),
|
|
31
|
+
closeBrackets(),
|
|
32
|
+
drawSelection(),
|
|
33
|
+
highlightActiveLine(),
|
|
34
|
+
history(),
|
|
35
|
+
_placeholder && placeholder(_placeholder),
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
];
|
|
37
|
+
// https://github.com/codemirror/theme-one-dark
|
|
38
|
+
themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
|
|
39
|
+
].filter(isNotFalsy);
|
package/src/extensions/blast.ts
CHANGED
|
@@ -125,7 +125,10 @@ class Blaster {
|
|
|
125
125
|
|
|
126
126
|
_lastPoint = { x: 0, y: 0 };
|
|
127
127
|
|
|
128
|
-
constructor(
|
|
128
|
+
constructor(
|
|
129
|
+
private readonly _node: HTMLElement,
|
|
130
|
+
private readonly _options: BlastOptions,
|
|
131
|
+
) {
|
|
129
132
|
this._effect = this._options.effect === 1 ? new Effect1(_options) : new Effect2(_options);
|
|
130
133
|
}
|
|
131
134
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { invertedEffects } from '@codemirror/commands';
|
|
6
|
+
import { type Extension, Facet, StateEffect, StateField, type Text, type ChangeDesc } from '@codemirror/state';
|
|
6
7
|
import { hoverTooltip, keymap, type Command, Decoration, EditorView, type Rect } from '@codemirror/view';
|
|
7
8
|
import sortBy from 'lodash.sortby';
|
|
8
9
|
import { useEffect } from 'react';
|
|
9
10
|
|
|
10
11
|
import { debounce } from '@dxos/async';
|
|
11
|
-
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { nonNullable } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { Cursor } from './cursor';
|
|
@@ -18,18 +18,26 @@ import { callbackWrapper } from '../util';
|
|
|
18
18
|
|
|
19
19
|
// TODO(burdon): Reconcile with theme.
|
|
20
20
|
const styles = EditorView.baseTheme({
|
|
21
|
-
'& .cm-comment': {
|
|
21
|
+
'&light .cm-comment, &light .cm-comment-current': { mixBlendMode: 'darken' },
|
|
22
|
+
'&dark .cm-comment, &dark .cm-comment-current': { mixBlendMode: 'plus-lighter' },
|
|
23
|
+
'&light .cm-comment': {
|
|
22
24
|
backgroundColor: getToken('extend.colors.yellow.50'),
|
|
23
25
|
},
|
|
24
|
-
'& .cm-comment-current': {
|
|
26
|
+
'&light .cm-comment-current': {
|
|
25
27
|
backgroundColor: getToken('extend.colors.yellow.100'),
|
|
26
28
|
},
|
|
29
|
+
'&dark .cm-comment': {
|
|
30
|
+
color: getToken('extend.colors.yellow.50'),
|
|
31
|
+
backgroundColor: getToken('extend.colors.yellow.900'),
|
|
32
|
+
},
|
|
33
|
+
'&dark .cm-comment-current': {
|
|
34
|
+
color: getToken('extend.colors.yellow.100'),
|
|
35
|
+
backgroundColor: getToken('extend.colors.yellow.950'),
|
|
36
|
+
},
|
|
27
37
|
});
|
|
28
38
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
highlightActive: Decoration.mark({ class: 'cm-comment-current' }),
|
|
32
|
-
};
|
|
39
|
+
const commentMark = Decoration.mark({ class: 'cm-comment' });
|
|
40
|
+
const commentCurrentMark = Decoration.mark({ class: 'cm-comment-current' });
|
|
33
41
|
|
|
34
42
|
type CommentState = {
|
|
35
43
|
comment: Comment;
|
|
@@ -47,7 +55,7 @@ export type CommentsState = {
|
|
|
47
55
|
selection: SelectionState;
|
|
48
56
|
};
|
|
49
57
|
|
|
50
|
-
export const
|
|
58
|
+
export const focusComment = (view: EditorView, id: string, center = true) => {
|
|
51
59
|
const comment = view.state.field(commentsState).comments.find((range) => range.comment.id === id);
|
|
52
60
|
if (!comment?.comment.cursor) {
|
|
53
61
|
return;
|
|
@@ -115,7 +123,7 @@ const commentsState = StateField.define<CommentsState>({
|
|
|
115
123
|
/**
|
|
116
124
|
* Decorate ranges.
|
|
117
125
|
*/
|
|
118
|
-
const
|
|
126
|
+
const commentsDecorations = EditorView.decorations.compute([commentsState], (state) => {
|
|
119
127
|
const {
|
|
120
128
|
selection: { current },
|
|
121
129
|
comments,
|
|
@@ -130,9 +138,9 @@ const highlightDecorations = EditorView.decorations.compute([commentsState], (st
|
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
if (comment.comment.id === current) {
|
|
133
|
-
return
|
|
141
|
+
return commentCurrentMark.range(range.from, range.to);
|
|
134
142
|
} else {
|
|
135
|
-
return
|
|
143
|
+
return commentMark.range(range.from, range.to);
|
|
136
144
|
}
|
|
137
145
|
})
|
|
138
146
|
.filter(nonNullable);
|
|
@@ -148,7 +156,7 @@ export type CommentsOptions = {
|
|
|
148
156
|
/**
|
|
149
157
|
* Called to create a new thread and return the thread id.
|
|
150
158
|
*/
|
|
151
|
-
onCreate?: (cursor: string
|
|
159
|
+
onCreate?: (params: { cursor: string; from: number; location?: Rect | null }) => string | undefined;
|
|
152
160
|
/**
|
|
153
161
|
* Selection cut/deleted.
|
|
154
162
|
*/
|
|
@@ -167,11 +175,11 @@ export type CommentsOptions = {
|
|
|
167
175
|
onHover?: (el: Element, shortcut: string) => void;
|
|
168
176
|
};
|
|
169
177
|
|
|
170
|
-
|
|
178
|
+
type TrackedComment = { id: string; from: number; to: number };
|
|
179
|
+
|
|
171
180
|
const trackPastedComments = (onUpdate: NonNullable<CommentsOptions['onUpdate']>) => {
|
|
172
181
|
// Tracks indexed selections within text.
|
|
173
|
-
|
|
174
|
-
let tracked: { text: Text; comments: { id: string; from: number; to: number }[] } | null = null;
|
|
182
|
+
let tracked: { text: Text; comments: TrackedComment[] } | null = null;
|
|
175
183
|
|
|
176
184
|
// Track cut or copy (enables cut-and-paste and copy-delete-paste to restore comment selection).
|
|
177
185
|
const handleTrack = (event: Event, view: EditorView) => {
|
|
@@ -201,16 +209,48 @@ const trackPastedComments = (onUpdate: NonNullable<CommentsOptions['onUpdate']>)
|
|
|
201
209
|
copy: handleTrack,
|
|
202
210
|
}),
|
|
203
211
|
|
|
204
|
-
//
|
|
205
|
-
|
|
212
|
+
// Track deleted comments.
|
|
213
|
+
invertedEffects.of((tr) => {
|
|
214
|
+
const { comments } = tr.startState.field(commentsState);
|
|
215
|
+
const effects: StateEffect<any>[] = [];
|
|
216
|
+
tr.changes.iterChangedRanges((fromA, toA) => {
|
|
217
|
+
for (const {
|
|
218
|
+
comment: { id },
|
|
219
|
+
range: { from, to },
|
|
220
|
+
} of comments) {
|
|
221
|
+
if (from < to && from >= fromA && to <= toA) {
|
|
222
|
+
effects.push(restoreCommentEffect.of({ id, from, to }));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return effects;
|
|
228
|
+
}),
|
|
229
|
+
|
|
230
|
+
// Handle paste or the undo of comment deletion.
|
|
231
|
+
EditorView.updateListener.of((update) => {
|
|
232
|
+
const restore: TrackedComment[] = [];
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < update.transactions.length; i++) {
|
|
235
|
+
const tr = update.transactions[i];
|
|
236
|
+
for (let j = 0; j < restore.length; j++) {
|
|
237
|
+
restore[j] = mapTrackedComment(restore[j], tr.changes);
|
|
238
|
+
}
|
|
239
|
+
for (const effect of tr.effects) {
|
|
240
|
+
if (effect.is(restoreCommentEffect)) {
|
|
241
|
+
restore.push(effect.value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
206
246
|
if (tracked) {
|
|
207
|
-
const paste = transactions.find((tr) => tr.isUserEvent('input.paste'));
|
|
247
|
+
const paste = update.transactions.find((tr) => tr.isUserEvent('input.paste'));
|
|
208
248
|
if (paste) {
|
|
209
249
|
let found = -1;
|
|
210
250
|
paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
|
|
211
251
|
if (text.eq(tracked!.text)) {
|
|
212
|
-
for (let i = transactions.indexOf(paste!) + 1; i < transactions.length; i++) {
|
|
213
|
-
fromB = transactions[i].changes.mapPos(fromB);
|
|
252
|
+
for (let i = update.transactions.indexOf(paste!) + 1; i < update.transactions.length; i++) {
|
|
253
|
+
fromB = update.transactions[i].changes.mapPos(fromB);
|
|
214
254
|
}
|
|
215
255
|
|
|
216
256
|
found = fromB;
|
|
@@ -219,23 +259,78 @@ const trackPastedComments = (onUpdate: NonNullable<CommentsOptions['onUpdate']>)
|
|
|
219
259
|
|
|
220
260
|
if (found > -1) {
|
|
221
261
|
for (const moved of tracked.comments) {
|
|
222
|
-
|
|
223
|
-
from: found + moved.from,
|
|
224
|
-
to: found + moved.to,
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const cursor = Cursor.getCursorFromRange(state, range);
|
|
228
|
-
onUpdate(moved.id, cursor);
|
|
262
|
+
restore.push({ id: moved.id, from: found + moved.from, to: found + moved.to });
|
|
229
263
|
}
|
|
230
264
|
}
|
|
231
265
|
|
|
232
266
|
tracked = null;
|
|
233
267
|
}
|
|
234
268
|
}
|
|
269
|
+
|
|
270
|
+
for (const comment of restore) {
|
|
271
|
+
const { comments } = update.startState.field(commentsState);
|
|
272
|
+
const exists = comments.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
|
|
273
|
+
if (!exists) {
|
|
274
|
+
const cursor = Cursor.getCursorFromRange(update.state, comment);
|
|
275
|
+
onUpdate(comment.id, cursor);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
235
278
|
}),
|
|
236
279
|
];
|
|
237
280
|
};
|
|
238
281
|
|
|
282
|
+
const mapTrackedComment = (comment: TrackedComment, changes: ChangeDesc) => ({
|
|
283
|
+
id: comment.id,
|
|
284
|
+
from: changes.mapPos(comment.from, 1),
|
|
285
|
+
to: changes.mapPos(comment.to, 1),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// These are attached to undone/redone transactions in the editor for the purpose of restoring comments
|
|
289
|
+
// that were deleted by the original changes.
|
|
290
|
+
const restoreCommentEffect = StateEffect.define<TrackedComment>({ map: mapTrackedComment });
|
|
291
|
+
|
|
292
|
+
const optionsFacet = Facet.define<CommentsOptions, CommentsOptions>({
|
|
293
|
+
combine: (providers) => providers[0],
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Create comment thread action.
|
|
298
|
+
*/
|
|
299
|
+
export const createComment: Command = (view) => {
|
|
300
|
+
const options = view.state.facet(optionsFacet);
|
|
301
|
+
const { from, to } = view.state.selection.main;
|
|
302
|
+
if (from === to) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Don't allow selection at end of document.
|
|
307
|
+
if (to === view.state.doc.length) {
|
|
308
|
+
view.dispatch({
|
|
309
|
+
changes: {
|
|
310
|
+
from: to,
|
|
311
|
+
insert: '\n',
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const cursor = Cursor.getCursorFromRange(view.state, { from, to });
|
|
317
|
+
if (cursor) {
|
|
318
|
+
// Create thread via callback.
|
|
319
|
+
const id = options.onCreate?.({ cursor, from, location: view.coordsAtPos(from) });
|
|
320
|
+
if (id) {
|
|
321
|
+
// Update range.
|
|
322
|
+
view.dispatch({
|
|
323
|
+
effects: setSelection.of({ current: id }),
|
|
324
|
+
selection: { anchor: to },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return false;
|
|
332
|
+
};
|
|
333
|
+
|
|
239
334
|
/**
|
|
240
335
|
* Comment threads.
|
|
241
336
|
* 1). Updates the EditorModel to store relative selections for a set of comments threads.
|
|
@@ -252,47 +347,10 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
252
347
|
|
|
253
348
|
const handleSelect = debounce((state: CommentsState) => options.onSelect?.(state), 200);
|
|
254
349
|
|
|
255
|
-
/**
|
|
256
|
-
* Create comment thread action.
|
|
257
|
-
*/
|
|
258
|
-
const createCommentThread: Command = (view) => {
|
|
259
|
-
invariant(options.onCreate);
|
|
260
|
-
const { from, to } = view.state.selection.main;
|
|
261
|
-
if (from === to) {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Don't allow selection at end of document.
|
|
266
|
-
if (to === view.state.doc.length) {
|
|
267
|
-
view.dispatch({
|
|
268
|
-
changes: {
|
|
269
|
-
from: to,
|
|
270
|
-
insert: '\n',
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const cursor = Cursor.getCursorFromRange(view.state, { from, to });
|
|
276
|
-
if (cursor) {
|
|
277
|
-
// Create thread via callback.
|
|
278
|
-
const id = options.onCreate?.(cursor, view.coordsAtPos(from));
|
|
279
|
-
if (id) {
|
|
280
|
-
// Update range.
|
|
281
|
-
view.dispatch({
|
|
282
|
-
effects: setSelection.of({ current: id }),
|
|
283
|
-
selection: { anchor: from },
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return false;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
350
|
return [
|
|
351
|
+
optionsFacet.of(options),
|
|
294
352
|
commentsState,
|
|
295
|
-
|
|
353
|
+
commentsDecorations,
|
|
296
354
|
styles,
|
|
297
355
|
|
|
298
356
|
//
|
|
@@ -302,7 +360,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
|
302
360
|
? keymap.of([
|
|
303
361
|
{
|
|
304
362
|
key: shortcut,
|
|
305
|
-
run: callbackWrapper(
|
|
363
|
+
run: callbackWrapper(createComment),
|
|
306
364
|
},
|
|
307
365
|
])
|
|
308
366
|
: [],
|
package/src/extensions/index.ts
CHANGED
|
@@ -7,17 +7,10 @@ export * from './automerge';
|
|
|
7
7
|
export * from './awareness';
|
|
8
8
|
export * from './basic';
|
|
9
9
|
export * from './blast';
|
|
10
|
-
export * from './code';
|
|
11
10
|
export * from './comments';
|
|
12
11
|
export * from './cursor';
|
|
13
|
-
export * from './hr';
|
|
14
|
-
export * from './image';
|
|
15
|
-
export * from './link';
|
|
16
12
|
export * from './listener';
|
|
17
13
|
export * from './markdown';
|
|
18
14
|
export * from './mention';
|
|
19
15
|
export * from './outliner';
|
|
20
|
-
export * from './table';
|
|
21
|
-
export * from './tasklist';
|
|
22
16
|
export * from './typewriter';
|
|
23
|
-
export * from './yjs';
|
|
@@ -21,13 +21,14 @@ import {
|
|
|
21
21
|
} from '@codemirror/view';
|
|
22
22
|
|
|
23
23
|
import type { ThemeMode } from '@dxos/react-ui';
|
|
24
|
+
import { isNotFalsy } from '@dxos/util';
|
|
24
25
|
|
|
25
26
|
import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
|
|
26
27
|
import { type TextEditorProps } from '../../components';
|
|
27
28
|
|
|
28
29
|
export type MarkdownBundleOptions = {
|
|
29
30
|
themeMode?: ThemeMode;
|
|
30
|
-
} & Pick<TextEditorProps, '
|
|
31
|
+
} & Pick<TextEditorProps, 'placeholder' | 'lineWrapping'>;
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Markdown bundle.
|
|
@@ -36,15 +37,15 @@ export type MarkdownBundleOptions = {
|
|
|
36
37
|
* https://codemirror.net/docs/ref/#codemirror.basicSetup
|
|
37
38
|
*/
|
|
38
39
|
// TODO(burdon): Add Composer here: https://codemirror.net/docs/community
|
|
39
|
-
export const
|
|
40
|
-
readonly,
|
|
40
|
+
export const createMarkdownExtensions = ({
|
|
41
41
|
themeMode,
|
|
42
42
|
placeholder: _placeholder,
|
|
43
|
-
|
|
43
|
+
lineWrapping = true,
|
|
44
|
+
}: MarkdownBundleOptions = {}): Extension[] => {
|
|
44
45
|
return [
|
|
46
|
+
lineWrapping && EditorView.lineWrapping,
|
|
45
47
|
EditorState.allowMultipleSelections.of(true),
|
|
46
48
|
EditorState.tabSize.of(2),
|
|
47
|
-
EditorView.lineWrapping,
|
|
48
49
|
|
|
49
50
|
// https://github.com/codemirror/basic-setup
|
|
50
51
|
bracketMatching(),
|
|
@@ -55,7 +56,7 @@ export const markdownBundle = ({
|
|
|
55
56
|
highlightActiveLine(),
|
|
56
57
|
history(),
|
|
57
58
|
indentOnInput(),
|
|
58
|
-
_placeholder
|
|
59
|
+
_placeholder && placeholder(_placeholder),
|
|
59
60
|
|
|
60
61
|
// Main extension.
|
|
61
62
|
// https://github.com/codemirror/lang-markdown
|
|
@@ -82,7 +83,7 @@ export const markdownBundle = ({
|
|
|
82
83
|
themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
|
|
83
84
|
|
|
84
85
|
// Custom styles.
|
|
85
|
-
syntaxHighlighting(markdownHighlightStyle(
|
|
86
|
+
syntaxHighlighting(markdownHighlightStyle()),
|
|
86
87
|
|
|
87
88
|
keymap.of([
|
|
88
89
|
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
@@ -96,5 +97,5 @@ export const markdownBundle = ({
|
|
|
96
97
|
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
97
98
|
...standardKeymap,
|
|
98
99
|
]),
|
|
99
|
-
];
|
|
100
|
+
].filter(isNotFalsy);
|
|
100
101
|
};
|