@dxos/react-ui-editor 0.4.6-main.268e405 → 0.4.6-main.2c76dca
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 +99 -94
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts +3 -5
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts +2 -7
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts +2 -3
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts +9 -2
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/hooks/defs.d.ts +1 -0
- package/dist/types/src/hooks/defs.d.ts.map +1 -1
- package/dist/types/src/hooks/useEditorView.d.ts +1 -0
- package/dist/types/src/hooks/useEditorView.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts +2 -2
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +12 -4
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/components/TextEditor/automerge.stories.tsx +15 -16
- package/src/extensions/automerge/automerge.ts +10 -15
- package/src/extensions/automerge/cursor.ts +2 -3
- package/src/extensions/automerge/defs.ts +15 -2
- package/src/extensions/markdown/decorate.ts +13 -49
- package/src/extensions/markdown/image.ts +11 -0
- package/src/hooks/defs.ts +2 -0
- package/src/hooks/useEditorView.ts +2 -0
- package/src/hooks/useTextEditor.ts +6 -10
- package/src/hooks/useTextModel.ts +34 -5
- package/src/themes/default.ts +7 -0
|
@@ -7,11 +7,9 @@ import '@dxosTheme';
|
|
|
7
7
|
import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel';
|
|
8
8
|
import '@preact/signals-react';
|
|
9
9
|
import { EditorView } from '@codemirror/view'; // Register react integration.
|
|
10
|
-
import get from 'lodash.get';
|
|
11
10
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
12
11
|
|
|
13
|
-
import {
|
|
14
|
-
import { Repo, type DocHandle } from '@dxos/automerge/automerge-repo';
|
|
12
|
+
import { Repo } from '@dxos/automerge/automerge-repo';
|
|
15
13
|
import { Filter } from '@dxos/echo-schema';
|
|
16
14
|
import { type PublicKey } from '@dxos/keys';
|
|
17
15
|
import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
|
|
@@ -20,7 +18,8 @@ import { ClientRepeater } from '@dxos/react-client/testing';
|
|
|
20
18
|
import { withTheme } from '@dxos/storybook-utils';
|
|
21
19
|
|
|
22
20
|
import { MarkdownEditor } from './TextEditor';
|
|
23
|
-
import {
|
|
21
|
+
import { automerge, awareness, createBasicBundle } from '../../extensions';
|
|
22
|
+
import { DocAccessor } from '../../extensions/automerge/defs';
|
|
24
23
|
import { useTextEditor, useTextModel } from '../../hooks';
|
|
25
24
|
import { defaultTheme } from '../../themes';
|
|
26
25
|
import translations from '../../translations';
|
|
@@ -32,24 +31,24 @@ type TestObject = {
|
|
|
32
31
|
};
|
|
33
32
|
|
|
34
33
|
type EditorProps = {
|
|
35
|
-
|
|
36
|
-
path?: Prop[];
|
|
34
|
+
source: DocAccessor;
|
|
37
35
|
};
|
|
38
36
|
|
|
39
|
-
const Editor = ({
|
|
37
|
+
const Editor = ({ source }: EditorProps) => {
|
|
40
38
|
const extensions = useMemo(
|
|
41
39
|
() => [
|
|
42
40
|
EditorView.baseTheme(defaultTheme),
|
|
43
41
|
EditorView.editorAttributes.of({ class: 'p-2 bg-white' }),
|
|
44
42
|
createBasicBundle({ placeholder: 'Type here...' }),
|
|
45
|
-
automerge(
|
|
43
|
+
automerge(source),
|
|
46
44
|
awareness(),
|
|
47
45
|
],
|
|
48
|
-
[
|
|
46
|
+
[source],
|
|
49
47
|
);
|
|
48
|
+
|
|
50
49
|
const { parentRef } = useTextEditor({
|
|
51
50
|
autoFocus: true,
|
|
52
|
-
doc:
|
|
51
|
+
doc: DocAccessor.getValue(source),
|
|
53
52
|
extensions,
|
|
54
53
|
});
|
|
55
54
|
|
|
@@ -57,8 +56,8 @@ const Editor = ({ handle, path = ['text'] }: EditorProps) => {
|
|
|
57
56
|
};
|
|
58
57
|
|
|
59
58
|
const Story = () => {
|
|
60
|
-
const [object1, setObject1] = useState<
|
|
61
|
-
const [object2, setObject2] = useState<
|
|
59
|
+
const [object1, setObject1] = useState<DocAccessor<TestObject>>();
|
|
60
|
+
const [object2, setObject2] = useState<DocAccessor<TestObject>>();
|
|
62
61
|
|
|
63
62
|
useEffect(() => {
|
|
64
63
|
queueMicrotask(async () => {
|
|
@@ -77,8 +76,8 @@ const Story = () => {
|
|
|
77
76
|
const object2 = repo2.find(object1.url);
|
|
78
77
|
await object2.whenReady();
|
|
79
78
|
|
|
80
|
-
setObject1(object1);
|
|
81
|
-
setObject2(object2);
|
|
79
|
+
setObject1({ handle: object1, path: ['text'] });
|
|
80
|
+
setObject2({ handle: object2, path: ['text'] });
|
|
82
81
|
});
|
|
83
82
|
}, []);
|
|
84
83
|
|
|
@@ -88,8 +87,8 @@ const Story = () => {
|
|
|
88
87
|
|
|
89
88
|
return (
|
|
90
89
|
<div role='none' className='grid grid-cols-2 bs-full is-full gap-2'>
|
|
91
|
-
<Editor
|
|
92
|
-
<Editor
|
|
90
|
+
<Editor source={object1} />
|
|
91
|
+
<Editor source={object2} />
|
|
93
92
|
</div>
|
|
94
93
|
);
|
|
95
94
|
};
|
|
@@ -7,29 +7,24 @@
|
|
|
7
7
|
import { StateField, type Extension } from '@codemirror/state';
|
|
8
8
|
import { EditorView, ViewPlugin } from '@codemirror/view';
|
|
9
9
|
|
|
10
|
-
import { next as A
|
|
10
|
+
import { next as A } from '@dxos/automerge/automerge';
|
|
11
11
|
|
|
12
12
|
import { cursorConverter } from './cursor';
|
|
13
|
-
import { updateHeadsEffect,
|
|
13
|
+
import { updateHeadsEffect, isReconcile, type State, type DocAccessor } from './defs';
|
|
14
14
|
import { Syncer } from './sync';
|
|
15
15
|
import { Cursor } from '../cursor';
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
handle: IDocHandle;
|
|
19
|
-
path: Prop[];
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
17
|
+
export const automerge = (accessor: DocAccessor): Extension => {
|
|
23
18
|
const syncState = StateField.define<State>({
|
|
24
19
|
create: () => ({
|
|
25
|
-
path: path.slice(),
|
|
26
|
-
lastHeads: A.getHeads(handle.docSync()!),
|
|
20
|
+
path: accessor.path.slice(),
|
|
21
|
+
lastHeads: A.getHeads(accessor.handle.docSync()!),
|
|
27
22
|
unreconciledTransactions: [],
|
|
28
23
|
}),
|
|
29
24
|
|
|
30
25
|
update: (value, tr) => {
|
|
31
26
|
const result: State = {
|
|
32
|
-
path: path.slice(),
|
|
27
|
+
path: accessor.path.slice(),
|
|
33
28
|
lastHeads: value.lastHeads,
|
|
34
29
|
unreconciledTransactions: value.unreconciledTransactions.slice(),
|
|
35
30
|
};
|
|
@@ -54,21 +49,21 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
54
49
|
},
|
|
55
50
|
});
|
|
56
51
|
|
|
57
|
-
const syncer = new Syncer(handle, syncState);
|
|
52
|
+
const syncer = new Syncer(accessor.handle, syncState);
|
|
58
53
|
|
|
59
54
|
return [
|
|
60
|
-
Cursor.converter.of(cursorConverter(
|
|
55
|
+
Cursor.converter.of(cursorConverter(accessor)),
|
|
61
56
|
syncState,
|
|
62
57
|
|
|
63
58
|
// Reconcile external updates.
|
|
64
59
|
ViewPlugin.fromClass(
|
|
65
60
|
class {
|
|
66
61
|
constructor(private readonly _view: EditorView) {
|
|
67
|
-
handle.addListener('change', this._handleChange.bind(this));
|
|
62
|
+
accessor.handle.addListener('change', this._handleChange.bind(this));
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
destroy() {
|
|
71
|
-
handle.removeListener('change', this._handleChange.bind(this));
|
|
66
|
+
accessor.handle.removeListener('change', this._handleChange.bind(this));
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
_handleChange() {
|
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import get from 'lodash.get';
|
|
6
6
|
|
|
7
|
-
import type { Prop } from '@dxos/automerge/automerge';
|
|
8
7
|
import { next as A } from '@dxos/automerge/automerge';
|
|
9
8
|
import { log } from '@dxos/log';
|
|
10
9
|
|
|
11
|
-
import { type
|
|
10
|
+
import { type DocAccessor } from './defs';
|
|
12
11
|
import { type CursorConverter } from '../cursor';
|
|
13
12
|
|
|
14
|
-
export const cursorConverter = (handle
|
|
13
|
+
export const cursorConverter = ({ handle, path }: DocAccessor): CursorConverter => ({
|
|
15
14
|
// TODO(burdon): Handle assoc to associate with a previous character.
|
|
16
15
|
toCursor: (pos) => {
|
|
17
16
|
const doc = handle.docSync();
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
|
|
7
7
|
import { Annotation, StateEffect, type StateField, type EditorState, type Transaction } from '@codemirror/state';
|
|
8
|
+
import get from 'lodash.get';
|
|
8
9
|
|
|
9
10
|
import { type ChangeFn, type ChangeOptions, type Doc, type Heads, type Prop } from '@dxos/automerge/automerge';
|
|
10
11
|
|
|
@@ -31,10 +32,22 @@ export const isReconcile = (tr: Transaction): boolean => {
|
|
|
31
32
|
return !!tr.annotation(reconcileAnnotation);
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
// TODO(burdon): Reconcile with echo.
|
|
36
|
+
export interface DocAccessor<T = any> {
|
|
37
|
+
handle: IDocHandle<T>;
|
|
38
|
+
get path(): string[]; // TODO(burdon): Getter or prop?
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// TODO(burdon): Reconcile with echo.
|
|
42
|
+
export const DocAccessor = {
|
|
43
|
+
getValue: (accessor: DocAccessor) => get(accessor.handle, accessor.path),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// TODO(burdon): Reconcile with echo.
|
|
47
|
+
export interface IDocHandle<T = any> {
|
|
35
48
|
docSync(): Doc<T> | undefined;
|
|
36
49
|
change(callback: ChangeFn<T>, options?: ChangeOptions<T>): void;
|
|
37
50
|
changeAt(heads: Heads, callback: ChangeFn<T>, options?: ChangeOptions<T>): string[] | undefined;
|
|
38
51
|
addListener(event: 'change', listener: () => void): void;
|
|
39
52
|
removeListener(event: 'change', listener: () => void): void;
|
|
40
|
-
}
|
|
53
|
+
}
|
|
@@ -10,23 +10,6 @@ import { mx } from '@dxos/react-ui-theme';
|
|
|
10
10
|
|
|
11
11
|
import { getToken } from '../../styles';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Widget for first/last line of code block (read-only).
|
|
15
|
-
*/
|
|
16
|
-
// TODO(burdon): Add copy-to-clipboard button.
|
|
17
|
-
class FencedCodeBoundary extends WidgetType {
|
|
18
|
-
constructor(private readonly _className: string) {
|
|
19
|
-
super();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
override toDOM() {
|
|
23
|
-
const el = document.createElement('span');
|
|
24
|
-
el.innerHTML = ' ';
|
|
25
|
-
el.className = mx('cm-code cm-codeblock', this._className);
|
|
26
|
-
return el;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
13
|
class HorizontalRuleWidget extends WidgetType {
|
|
31
14
|
override toDOM() {
|
|
32
15
|
const el = document.createElement('span');
|
|
@@ -91,8 +74,9 @@ class CheckboxWidget extends WidgetType {
|
|
|
91
74
|
}
|
|
92
75
|
|
|
93
76
|
const hide = Decoration.replace({});
|
|
94
|
-
const
|
|
95
|
-
const
|
|
77
|
+
const fencedCodeLine = Decoration.line({ class: mx('cm-code cm-codeblock-line') });
|
|
78
|
+
const fencedCodeLineFirst = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-first') });
|
|
79
|
+
const fencedCodeLineLast = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-last') });
|
|
96
80
|
const horizontalRule = Decoration.replace({ widget: new HorizontalRuleWidget() });
|
|
97
81
|
const checkedTask = Decoration.replace({ widget: new CheckboxWidget(true) });
|
|
98
82
|
const uncheckedTask = Decoration.replace({ widget: new CheckboxWidget(false) });
|
|
@@ -130,16 +114,13 @@ const buildDecorations = (view: EditorView, options: DecorateOptions): Decoratio
|
|
|
130
114
|
}
|
|
131
115
|
const first = block.from <= node.from;
|
|
132
116
|
const last = block.to >= node.to;
|
|
117
|
+
builder.add(
|
|
118
|
+
block.from,
|
|
119
|
+
block.from,
|
|
120
|
+
first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine,
|
|
121
|
+
);
|
|
133
122
|
if (!editing && (first || last)) {
|
|
134
|
-
builder.add(block.from, block.to,
|
|
135
|
-
} else {
|
|
136
|
-
builder.add(
|
|
137
|
-
block.from,
|
|
138
|
-
block.from,
|
|
139
|
-
Decoration.line({
|
|
140
|
-
class: mx('cm-code cm-codeblock-line', first && 'cm-codeblock-first', last && 'cm-codeblock-last'),
|
|
141
|
-
}),
|
|
142
|
-
);
|
|
123
|
+
builder.add(block.from, block.to, hide);
|
|
143
124
|
}
|
|
144
125
|
}
|
|
145
126
|
return false;
|
|
@@ -252,18 +233,13 @@ const formattingStyles = EditorView.baseTheme({
|
|
|
252
233
|
},
|
|
253
234
|
},
|
|
254
235
|
'& .cm-codeblock-first': {
|
|
255
|
-
'
|
|
256
|
-
|
|
257
|
-
borderTopRightRadius: '.5rem',
|
|
258
|
-
},
|
|
236
|
+
borderTopLeftRadius: '.5rem',
|
|
237
|
+
borderTopRightRadius: '.5rem',
|
|
259
238
|
},
|
|
260
239
|
'& .cm-codeblock-last': {
|
|
261
|
-
'
|
|
262
|
-
|
|
263
|
-
borderBottomRightRadius: '.5rem',
|
|
264
|
-
},
|
|
240
|
+
borderBottomLeftRadius: '.5rem',
|
|
241
|
+
borderBottomRightRadius: '.5rem',
|
|
265
242
|
},
|
|
266
|
-
|
|
267
243
|
'&light .cm-codeblock-line, &light .cm-activeLine.cm-codeblock-line': {
|
|
268
244
|
background: getToken('extend.semanticColors.input.light'),
|
|
269
245
|
mixBlendMode: 'darken',
|
|
@@ -272,18 +248,6 @@ const formattingStyles = EditorView.baseTheme({
|
|
|
272
248
|
background: getToken('extend.semanticColors.input.dark'),
|
|
273
249
|
mixBlendMode: 'lighten',
|
|
274
250
|
},
|
|
275
|
-
'&light .cm-codeblock-first, &light .cm-codeblock-last': {
|
|
276
|
-
mixBlendMode: 'darken',
|
|
277
|
-
'&::after': {
|
|
278
|
-
background: getToken('extend.semanticColors.input.light'),
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
'&dark .cm-codeblock-first, &dark .cm-codeblock-last': {
|
|
282
|
-
mixBlendMode: 'lighten',
|
|
283
|
-
'&::after': {
|
|
284
|
-
background: getToken('extend.semanticColors.input.dark'),
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
251
|
'& .cm-hr': {
|
|
288
252
|
display: 'inline-block',
|
|
289
253
|
width: '100%',
|
|
@@ -40,6 +40,16 @@ export const image = (options: ImageOptions = {}): Extension => {
|
|
|
40
40
|
});
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
const preloaded = new Set<string>();
|
|
44
|
+
|
|
45
|
+
const preloadImage = (url: string) => {
|
|
46
|
+
if (!preloaded.has(url)) {
|
|
47
|
+
const img = document.createElement('img');
|
|
48
|
+
img.src = url;
|
|
49
|
+
preloaded.add(url);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
43
53
|
const buildDecorations = (from: number, to: number, state: EditorState) => {
|
|
44
54
|
const decorations: Range<Decoration>[] = [];
|
|
45
55
|
const cursor = state.selection.main.head;
|
|
@@ -51,6 +61,7 @@ const buildDecorations = (from: number, to: number, state: EditorState) => {
|
|
|
51
61
|
if (urlNode) {
|
|
52
62
|
const hide = state.readOnly || cursor < node.from || cursor > node.to;
|
|
53
63
|
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
|
64
|
+
preloadImage(url);
|
|
54
65
|
decorations.push(
|
|
55
66
|
Decoration.replace({
|
|
56
67
|
block: true, // Prevent cursor from entering.
|
package/src/hooks/defs.ts
CHANGED
|
@@ -35,7 +35,9 @@ export type EditorModel = {
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* State field makes the model available to other extensions.
|
|
38
|
+
* @deprecated
|
|
38
39
|
*/
|
|
40
|
+
// TODO(burdon): Remove.
|
|
39
41
|
export const modelState = StateField.define<EditorModel | undefined>({
|
|
40
42
|
create: () => undefined,
|
|
41
43
|
update: (model) => model,
|
|
@@ -7,6 +7,7 @@ import { type MutableRefObject, useRef, useState } from 'react';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Hook for accessing the editor view.
|
|
10
|
+
* @deprecated
|
|
10
11
|
*
|
|
11
12
|
* ```tsx
|
|
12
13
|
* const Test = () => {
|
|
@@ -20,6 +21,7 @@ import { type MutableRefObject, useRef, useState } from 'react';
|
|
|
20
21
|
* };
|
|
21
22
|
* ```
|
|
22
23
|
*/
|
|
24
|
+
// TODO(burdon): Replace with useTextEditor.
|
|
23
25
|
export const useEditorView = (id: string): [MutableRefObject<EditorView | null>, boolean] => {
|
|
24
26
|
const editorRef = useRef<EditorView | null>(null);
|
|
25
27
|
const [prev, setPrev] = useState<[EditorView | null, string]>([null, '']);
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type StateEffect,
|
|
11
11
|
} from '@codemirror/state';
|
|
12
12
|
import { EditorView } from '@codemirror/view';
|
|
13
|
-
import { type RefObject, useEffect, useRef, useState } from 'react';
|
|
13
|
+
import { type DependencyList, type RefObject, useEffect, useRef, useState } from 'react';
|
|
14
14
|
|
|
15
15
|
import { log } from '@dxos/log';
|
|
16
16
|
import { type ThemeMode } from '@dxos/react-ui';
|
|
@@ -36,14 +36,10 @@ export type UseTextEditor = {
|
|
|
36
36
|
* Hook for creating editor.
|
|
37
37
|
*/
|
|
38
38
|
// TODO(wittjosiah): Does not work in strict mode.
|
|
39
|
-
export const useTextEditor = (
|
|
40
|
-
autoFocus,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
doc,
|
|
44
|
-
selection,
|
|
45
|
-
extensions,
|
|
46
|
-
}: UseTextEditorOptions = {}): UseTextEditor => {
|
|
39
|
+
export const useTextEditor = (
|
|
40
|
+
{ autoFocus, scrollTo, debug, doc, selection, extensions }: UseTextEditorOptions = {},
|
|
41
|
+
deps?: DependencyList,
|
|
42
|
+
): UseTextEditor => {
|
|
47
43
|
const onUpdate = useRef<() => void>();
|
|
48
44
|
const [view, setView] = useState<EditorView>();
|
|
49
45
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
@@ -88,7 +84,7 @@ export const useTextEditor = ({
|
|
|
88
84
|
return () => {
|
|
89
85
|
view?.destroy();
|
|
90
86
|
};
|
|
91
|
-
},
|
|
87
|
+
}, deps);
|
|
92
88
|
|
|
93
89
|
useEffect(() => {
|
|
94
90
|
if (view) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Extension } from '@codemirror/state';
|
|
5
6
|
import get from 'lodash.get';
|
|
6
7
|
import { type Dispatch, type SetStateAction, useState, useMemo } from 'react';
|
|
7
8
|
|
|
@@ -14,13 +15,42 @@ import { type Identity } from '@dxos/react-client/halo';
|
|
|
14
15
|
import { SpaceAwarenessProvider } from './awareness-provider';
|
|
15
16
|
import { type EditorModel, modelState } from './defs';
|
|
16
17
|
import { automerge, awareness } from '../extensions';
|
|
18
|
+
import { type DocAccessor } from '../extensions/automerge/defs';
|
|
17
19
|
import { cursorColor } from '../styles';
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
export type useTextExtensionsProps = {
|
|
22
|
+
id: string;
|
|
23
|
+
text: DocAccessor;
|
|
22
24
|
space?: Space;
|
|
25
|
+
identity?: Identity;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// TODO(burdon): Factor out automerge defs and extension (not hook).
|
|
29
|
+
export const useTextExtensions = ({ id, text, space, identity }: useTextExtensionsProps): Extension[] => {
|
|
30
|
+
const extensions: Extension[] = [automerge(text)];
|
|
31
|
+
|
|
32
|
+
if (space && identity) {
|
|
33
|
+
const awarenessProvider = new SpaceAwarenessProvider({
|
|
34
|
+
space,
|
|
35
|
+
channel: `awareness.${id}`,
|
|
36
|
+
peerId: identity.identityKey.toHex(),
|
|
37
|
+
info: {
|
|
38
|
+
displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
|
|
39
|
+
color: cursorColor.color,
|
|
40
|
+
lightColor: cursorColor.light,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
extensions.push(awareness(awarenessProvider));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return extensions;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type UseTextModelProps = {
|
|
23
51
|
text?: TextObject;
|
|
52
|
+
space?: Space;
|
|
53
|
+
identity?: Identity | null;
|
|
24
54
|
};
|
|
25
55
|
|
|
26
56
|
/**
|
|
@@ -32,8 +62,7 @@ export const useTextModel = (props: UseTextModelProps): EditorModel | undefined
|
|
|
32
62
|
|
|
33
63
|
/**
|
|
34
64
|
* For use primarily in stories & tests so the dependence on TextObject can be avoided.
|
|
35
|
-
* @
|
|
36
|
-
* @param defaultContent
|
|
65
|
+
* @deprecated
|
|
37
66
|
*/
|
|
38
67
|
export const useInMemoryTextModel = ({
|
|
39
68
|
id,
|