@dxos/react-ui-editor 0.3.10-next.ef70620 → 0.3.11-main.008b7c6
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 +2814 -1096
- 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 +63 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +9 -4
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +15 -4
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts +20 -0
- 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/index.d.ts +1 -1
- package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -0
- package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +10 -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 +3 -0
- package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
- package/dist/types/src/{automerge/automerge-plugin/PatchSemaphore.d.ts → extensions/automerge/semaphore.d.ts} +8 -5
- 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 +5 -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/{components/TextEditor/extensions/basic/bundle.d.ts → extensions/basic.d.ts} +3 -2
- package/dist/types/src/extensions/basic.d.ts.map +1 -0
- package/dist/types/src/extensions/blast.d.ts +25 -0
- package/dist/types/src/extensions/blast.d.ts.map +1 -0
- package/dist/types/src/extensions/code.d.ts +2 -0
- package/dist/types/src/extensions/code.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +50 -0
- package/dist/types/src/extensions/comments.d.ts.map +1 -0
- package/dist/types/src/extensions/hr.d.ts +2 -0
- package/dist/types/src/extensions/hr.d.ts.map +1 -0
- package/dist/types/src/extensions/image.d.ts +4 -0
- package/dist/types/src/extensions/image.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +19 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/extensions/link.d.ts +12 -0
- package/dist/types/src/extensions/link.d.ts.map +1 -0
- package/dist/types/src/extensions/listener.d.ts +10 -0
- package/dist/types/src/extensions/listener.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts +17 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts +36 -0
- package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts +3 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
- package/dist/types/src/extensions/mention.d.ts +6 -0
- package/dist/types/src/extensions/mention.d.ts.map +1 -0
- package/dist/types/src/extensions/mermaid.d.ts +3 -0
- package/dist/types/src/extensions/mermaid.d.ts.map +1 -0
- package/dist/types/src/extensions/table.d.ts +8 -0
- package/dist/types/src/extensions/table.d.ts.map +1 -0
- package/dist/types/src/extensions/tasklist.d.ts +3 -0
- package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
- package/dist/types/src/extensions/typewriter.d.ts +10 -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 +2 -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 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextModel.d.ts +23 -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 +3 -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 +14 -11
- package/dist/types/src/styles/markdown.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/themes/default.d.ts +17 -0
- package/dist/types/src/themes/default.d.ts.map +1 -0
- package/dist/types/src/themes/index.d.ts +2 -0
- package/dist/types/src/themes/index.d.ts.map +1 -0
- package/dist/types/src/util/cursor.d.ts +17 -0
- package/dist/types/src/util/cursor.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +3 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/src/util/util.d.ts +5 -0
- package/dist/types/src/util/util.d.ts.map +1 -0
- package/package.json +29 -33
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +426 -0
- package/src/components/TextEditor/TextEditor.stories.tsx +70 -43
- package/src/components/TextEditor/TextEditor.tsx +98 -52
- package/src/{automerge → components/TextEditor}/automerge.stories.tsx +69 -7
- package/src/components/TextEditor/codemirror.stories.ts +3 -2
- package/src/components/TextEditor/index.ts +1 -1
- package/src/components/TextEditor/yjs.stories.tsx +1 -0
- package/src/extensions/autocomplete.ts +59 -0
- package/src/extensions/automerge/automerge.ts +94 -0
- package/src/extensions/automerge/cursor.ts +51 -0
- package/src/extensions/automerge/defs.ts +62 -0
- package/src/extensions/automerge/index.ts +6 -0
- package/src/{automerge/automerge-plugin/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +24 -18
- package/src/{automerge/automerge-plugin/codeMirrorToAm.ts → extensions/automerge/update-automerge.ts} +8 -6
- package/src/{automerge/automerge-plugin/amToCodemirror.ts → extensions/automerge/update-codemirror.ts} +15 -4
- package/src/extensions/awareness.ts +316 -0
- package/src/{components/TextEditor/extensions/basic/bundle.ts → extensions/basic.ts} +8 -4
- package/src/extensions/blast.ts +373 -0
- package/src/extensions/code.ts +99 -0
- package/src/extensions/comments.ts +421 -0
- package/src/extensions/hr.ts +74 -0
- package/src/extensions/image.ts +69 -0
- package/src/extensions/index.ts +22 -0
- package/src/extensions/link.ts +192 -0
- package/src/extensions/listener.ts +35 -0
- package/src/extensions/markdown/bundle.ts +106 -0
- package/src/extensions/markdown/highlight.ts +204 -0
- package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +1 -1
- package/src/extensions/mention.ts +31 -0
- package/src/extensions/mermaid.ts +11 -0
- package/src/extensions/table.ts +132 -0
- package/src/extensions/tasklist.ts +121 -0
- package/src/extensions/typewriter.ts +68 -0
- package/src/extensions/yjs/cursor.ts +21 -0
- package/src/extensions/yjs/index.ts +5 -0
- package/src/extensions/yjs/yjs.test.ts +35 -0
- 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 -1
- package/src/hooks/useTextModel.ts +53 -67
- package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
- package/src/index.ts +3 -0
- package/src/styles/index.ts +1 -0
- package/src/styles/markdown.ts +33 -24
- package/src/testing/replicator.ts +16 -21
- package/src/themes/default.ts +302 -0
- package/src/themes/index.ts +5 -0
- package/src/util/cursor.ts +29 -0
- package/src/util/index.ts +6 -0
- package/src/util/util.ts +18 -0
- package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts +0 -6
- package/dist/types/src/automerge/automerge-plugin/amToCodemirror.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +0 -7
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts +0 -9
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/index.d.ts +0 -3
- package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts +0 -26
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +0 -1
- package/dist/types/src/automerge/automerge.stories.d.ts +0 -9
- package/dist/types/src/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/bundle.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts +0 -3
- package/dist/types/src/components/TextEditor/extensions/basic/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts +0 -10
- package/dist/types/src/components/TextEditor/extensions/basic/theme.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/change.d.ts +0 -7
- package/dist/types/src/components/TextEditor/extensions/change.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts +0 -11
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts +0 -8
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts +0 -10
- package/dist/types/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts +0 -4
- package/dist/types/src/components/TextEditor/extensions/hyperlink/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +0 -5
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts +0 -8
- package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts +0 -3
- package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts +0 -17
- package/dist/types/src/components/TextEditor/extensions/markdown/tags.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts +0 -21
- package/dist/types/src/components/TextEditor/extensions/markdown/theme.d.ts.map +0 -1
- package/dist/types/src/hooks/useCollaboration.d.ts +0 -9
- package/dist/types/src/hooks/useCollaboration.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/src/automerge/automerge-plugin/handle.ts +0 -14
- package/src/automerge/automerge-plugin/index.ts +0 -6
- package/src/automerge/automerge-plugin/plugin.ts +0 -133
- package/src/components/TextEditor/extensions/basic/index.ts +0 -6
- package/src/components/TextEditor/extensions/basic/theme.ts +0 -49
- package/src/components/TextEditor/extensions/change.ts +0 -22
- package/src/components/TextEditor/extensions/hyperlink/hyperlinkDecoration.ts +0 -99
- package/src/components/TextEditor/extensions/hyperlink/hyperlinkTooltip.tsx +0 -61
- package/src/components/TextEditor/extensions/hyperlink/hyperlinkWidget.tsx +0 -119
- package/src/components/TextEditor/extensions/hyperlink/index.ts +0 -7
- package/src/components/TextEditor/extensions/index.ts +0 -8
- package/src/components/TextEditor/extensions/markdown/bundle.ts +0 -99
- package/src/components/TextEditor/extensions/markdown/tags.ts +0 -38
- package/src/components/TextEditor/extensions/markdown/theme.ts +0 -265
- package/src/hooks/useCollaboration.ts +0 -45
- package/src/hooks/yjs/index.ts +0 -6
- /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
- /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
|
@@ -2,96 +2,82 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { StateField, type Extension } from '@codemirror/state';
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
7
|
+
import type * as YP from 'y-protocols/awareness';
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
-
type DocAccessor,
|
|
10
|
-
type Space,
|
|
11
|
-
type TextObject,
|
|
12
|
-
type AutomergeTextCompat,
|
|
13
|
-
getRawDoc,
|
|
14
|
-
isActualAutomergeObject,
|
|
15
|
-
} from '@dxos/react-client/echo';
|
|
9
|
+
import { isAutomergeObject, type DocAccessor, type Space, type TextObject } from '@dxos/react-client/echo';
|
|
16
10
|
import { type Identity } from '@dxos/react-client/halo';
|
|
17
11
|
import type { YText, YXmlFragment } from '@dxos/text-model';
|
|
18
12
|
|
|
19
|
-
import {
|
|
13
|
+
import { createAutomergeModel } from './automerge';
|
|
14
|
+
import { createYjsModel } from './yjs';
|
|
20
15
|
|
|
21
|
-
// TODO(burdon):
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
// TODO(burdon): Factor out defs.
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* State field makes the model available to other extensions.
|
|
20
|
+
*/
|
|
21
|
+
export const modelState = StateField.define<EditorModel | undefined>({
|
|
22
|
+
create: () => undefined,
|
|
23
|
+
update: (model) => model,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type Range = {
|
|
27
|
+
from: number;
|
|
28
|
+
to: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type CommentRange = {
|
|
32
|
+
id: string;
|
|
33
|
+
cursor: string;
|
|
34
|
+
};
|
|
24
35
|
|
|
25
36
|
// TODO(wittjosiah): Factor out to common package? @dxos/react-client?
|
|
26
37
|
export type EditorModel = {
|
|
27
38
|
id: string;
|
|
39
|
+
text: () => string;
|
|
40
|
+
extension?: Extension;
|
|
41
|
+
|
|
42
|
+
// TODO(burdon): Remove.
|
|
28
43
|
content: string | YText | YXmlFragment | DocAccessor;
|
|
29
|
-
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated Use CursorConverter.
|
|
46
|
+
*/
|
|
47
|
+
getCursorFromRange?: (value: Range) => string;
|
|
48
|
+
getRangeFromCursor?: (cursor: string) => Range | undefined;
|
|
49
|
+
// TODO(burdon): Move into extension.
|
|
50
|
+
awareness?: YP.Awareness;
|
|
51
|
+
// TODO(burdon): Remove.
|
|
30
52
|
peer?: {
|
|
31
53
|
id: string;
|
|
32
54
|
name?: string;
|
|
33
55
|
};
|
|
34
56
|
};
|
|
35
57
|
|
|
36
|
-
|
|
58
|
+
// TODO(burdon): Remove space/identity dependency. Define interface for the framework re content and presence.
|
|
59
|
+
export type UseTextModelProps = {
|
|
37
60
|
identity?: Identity | null;
|
|
38
61
|
space?: Space;
|
|
39
62
|
text?: TextObject;
|
|
40
63
|
};
|
|
41
64
|
|
|
42
|
-
// TODO(burdon):
|
|
43
|
-
// TODO(burdon): Decouple space (make Editor less dependent on entire stack)?
|
|
65
|
+
// TODO(burdon): Remove YJS/Automerge deps (from UI component -- create abstraction; incl. all ECHO/Space deps).
|
|
44
66
|
// TODO(wittjosiah): Factor out to common package? @dxos/react-client?
|
|
45
|
-
export const useTextModel = (
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!space || !text?.doc) {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return new SpaceAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` });
|
|
56
|
-
}, [identity, space, text, text?.doc]);
|
|
57
|
-
|
|
58
|
-
const content = useMemo(() => {
|
|
59
|
-
if (isActualAutomergeObject(text)) {
|
|
60
|
-
const obj = text as any as AutomergeTextCompat;
|
|
61
|
-
return getRawDoc(obj, [obj.field]);
|
|
62
|
-
} else {
|
|
63
|
-
return text?.content;
|
|
64
|
-
}
|
|
65
|
-
}, [text]);
|
|
66
|
-
|
|
67
|
-
if (isActualAutomergeObject(text)) {
|
|
68
|
-
const obj = text as any as AutomergeTextCompat;
|
|
69
|
-
return {
|
|
70
|
-
id: obj.id,
|
|
71
|
-
content: content!,
|
|
72
|
-
provider: undefined,
|
|
73
|
-
peer: identity
|
|
74
|
-
? {
|
|
75
|
-
id: identity.identityKey.toHex(),
|
|
76
|
-
name: identity.profile?.displayName,
|
|
77
|
-
}
|
|
78
|
-
: undefined,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
67
|
+
export const useTextModel = (props: UseTextModelProps): EditorModel | undefined => {
|
|
68
|
+
const { identity, space, text } = props;
|
|
69
|
+
const [model, setModel] = useState<EditorModel | undefined>(() => createModel(props));
|
|
70
|
+
useEffect(() => setModel(createModel(props)), [identity, space, text]);
|
|
71
|
+
return model;
|
|
72
|
+
};
|
|
81
73
|
|
|
82
|
-
|
|
74
|
+
const createModel = (props: UseTextModelProps) => {
|
|
75
|
+
const { text } = props;
|
|
76
|
+
if (isAutomergeObject(text)) {
|
|
77
|
+
return createAutomergeModel(props);
|
|
78
|
+
} else if (text?.doc) {
|
|
79
|
+
return createYjsModel(props);
|
|
80
|
+
} else {
|
|
83
81
|
return undefined;
|
|
84
82
|
}
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
id: text.doc.guid,
|
|
88
|
-
content: text.content,
|
|
89
|
-
provider,
|
|
90
|
-
peer: identity
|
|
91
|
-
? {
|
|
92
|
-
id: identity.identityKey.toHex(),
|
|
93
|
-
name: identity.profile?.displayName,
|
|
94
|
-
}
|
|
95
|
-
: undefined,
|
|
96
|
-
};
|
|
97
83
|
};
|
|
@@ -5,14 +5,62 @@
|
|
|
5
5
|
import * as decoding from 'lib0/decoding';
|
|
6
6
|
import * as encoding from 'lib0/encoding';
|
|
7
7
|
import { Observable } from 'lib0/observable';
|
|
8
|
-
import * as
|
|
8
|
+
import * as YP from 'y-protocols/awareness';
|
|
9
|
+
import * as Y from 'yjs';
|
|
9
10
|
import { type Doc } from 'yjs';
|
|
10
11
|
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
11
13
|
import { log } from '@dxos/log';
|
|
12
14
|
import { type Space } from '@dxos/react-client/echo';
|
|
13
15
|
import { type GossipMessage } from '@dxos/react-client/mesh';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
import type { YText } from '@dxos/text-model';
|
|
17
|
+
import { arrayToString, stringToArray } from '@dxos/util';
|
|
18
|
+
|
|
19
|
+
import { type EditorModel, modelState, type Range, type UseTextModelProps } from './useTextModel';
|
|
20
|
+
import { yjs } from '../extensions';
|
|
21
|
+
|
|
22
|
+
export const createYjsModel = ({ identity, space, text }: UseTextModelProps): EditorModel => {
|
|
23
|
+
invariant(text?.doc && text?.content);
|
|
24
|
+
const provider = space
|
|
25
|
+
? new YAwarenessProvider({ space, doc: text.doc, channel: `yjs.awareness.${text.id}` })
|
|
26
|
+
: undefined;
|
|
27
|
+
|
|
28
|
+
const model: EditorModel = {
|
|
29
|
+
id: text.doc.guid,
|
|
30
|
+
content: text.content,
|
|
31
|
+
text: () => text.content!.toString(),
|
|
32
|
+
// https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
|
|
33
|
+
// TODO(dmaretskyi): Refactor as cursor-converter.
|
|
34
|
+
getCursorFromRange: (range: Range) => {
|
|
35
|
+
const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.from));
|
|
36
|
+
const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content as YText, range.to, -1));
|
|
37
|
+
return [arrayToString(from), arrayToString(to)].join(':');
|
|
38
|
+
},
|
|
39
|
+
getRangeFromCursor: (cursor: string) => {
|
|
40
|
+
invariant(text.doc);
|
|
41
|
+
const parts = cursor.split(':');
|
|
42
|
+
const from = Y.createAbsolutePositionFromRelativePosition(
|
|
43
|
+
Y.decodeRelativePosition(stringToArray(parts[0])),
|
|
44
|
+
text.doc,
|
|
45
|
+
);
|
|
46
|
+
const to = Y.createAbsolutePositionFromRelativePosition(
|
|
47
|
+
Y.decodeRelativePosition(stringToArray(parts[1])),
|
|
48
|
+
text.doc,
|
|
49
|
+
);
|
|
50
|
+
return from && to ? { from: from.index, to: to.index } : undefined;
|
|
51
|
+
},
|
|
52
|
+
extension: [modelState.init(() => model), yjs(text.content as YText, provider?.awareness)],
|
|
53
|
+
awareness: provider?.awareness,
|
|
54
|
+
peer: identity
|
|
55
|
+
? {
|
|
56
|
+
id: identity.identityKey.toHex(),
|
|
57
|
+
name: identity.profile?.displayName,
|
|
58
|
+
}
|
|
59
|
+
: undefined,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return model;
|
|
63
|
+
};
|
|
16
64
|
|
|
17
65
|
// Based on https://github.com/yjs/y-webrtc/blob/88baab2/src/y-webrtc.js.
|
|
18
66
|
|
|
@@ -22,16 +70,26 @@ const messageQueryAwareness = 3;
|
|
|
22
70
|
/**
|
|
23
71
|
* Yjs awareness provider on top of a DXOS space.
|
|
24
72
|
*/
|
|
25
|
-
export class
|
|
73
|
+
export class YAwarenessProvider extends Observable<any> {
|
|
26
74
|
private readonly _space: Space;
|
|
27
|
-
private readonly _awareness: Awareness;
|
|
75
|
+
private readonly _awareness: YP.Awareness;
|
|
28
76
|
private readonly _clientId: number;
|
|
29
77
|
private readonly _channel: string;
|
|
30
78
|
|
|
31
|
-
constructor({
|
|
79
|
+
constructor({
|
|
80
|
+
space,
|
|
81
|
+
doc,
|
|
82
|
+
channel,
|
|
83
|
+
awareness,
|
|
84
|
+
}: {
|
|
85
|
+
space: Space;
|
|
86
|
+
doc: Doc;
|
|
87
|
+
channel: string;
|
|
88
|
+
awareness?: YP.Awareness;
|
|
89
|
+
}) {
|
|
32
90
|
super();
|
|
33
91
|
this._space = space;
|
|
34
|
-
this._awareness = awareness ?? new
|
|
92
|
+
this._awareness = awareness ?? new YP.Awareness(doc);
|
|
35
93
|
this._channel = channel;
|
|
36
94
|
this._clientId = doc.clientID;
|
|
37
95
|
|
|
@@ -52,14 +110,11 @@ export class SpaceAwarenessProvider extends Observable<any> {
|
|
|
52
110
|
// Post local awareness state.
|
|
53
111
|
const encoderAwarenessState = encoding.createEncoder();
|
|
54
112
|
encoding.writeVarUint(encoderAwarenessState, messageAwareness);
|
|
55
|
-
encoding.writeVarUint8Array(
|
|
56
|
-
encoderAwarenessState,
|
|
57
|
-
awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this._clientId]),
|
|
58
|
-
);
|
|
113
|
+
encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [this._clientId]));
|
|
59
114
|
void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
|
|
60
115
|
}
|
|
61
116
|
|
|
62
|
-
get awareness(): Awareness {
|
|
117
|
+
get awareness(): YP.Awareness {
|
|
63
118
|
return this._awareness;
|
|
64
119
|
}
|
|
65
120
|
|
|
@@ -68,10 +123,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
|
|
|
68
123
|
const changedClients = added.concat(updated).concat(removed);
|
|
69
124
|
const encoderAwareness = encoding.createEncoder();
|
|
70
125
|
encoding.writeVarUint(encoderAwareness, messageAwareness);
|
|
71
|
-
encoding.writeVarUint8Array(
|
|
72
|
-
encoderAwareness,
|
|
73
|
-
awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients),
|
|
74
|
-
);
|
|
126
|
+
encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
|
|
75
127
|
void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
|
|
76
128
|
}
|
|
77
129
|
|
|
@@ -92,7 +144,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
|
|
|
92
144
|
let sendReply = false;
|
|
93
145
|
switch (messageType) {
|
|
94
146
|
case messageAwareness: {
|
|
95
|
-
|
|
147
|
+
YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
|
|
96
148
|
break;
|
|
97
149
|
}
|
|
98
150
|
|
|
@@ -100,7 +152,7 @@ export class SpaceAwarenessProvider extends Observable<any> {
|
|
|
100
152
|
encoding.writeVarUint(encoder, messageAwareness);
|
|
101
153
|
encoding.writeVarUint8Array(
|
|
102
154
|
encoder,
|
|
103
|
-
|
|
155
|
+
YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
|
|
104
156
|
);
|
|
105
157
|
sendReply = true;
|
|
106
158
|
break;
|
|
@@ -121,6 +173,6 @@ export class SpaceAwarenessProvider extends Observable<any> {
|
|
|
121
173
|
}
|
|
122
174
|
|
|
123
175
|
private _handleBeforeUnload() {
|
|
124
|
-
|
|
176
|
+
YP.removeAwarenessStates(this._awareness, [this._clientId], 'window unload');
|
|
125
177
|
}
|
|
126
178
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
|
|
6
6
|
export { Doc, YText, YXmlFragment } from '@dxos/text-model';
|
|
7
|
+
export { type Extension } from '@codemirror/state';
|
|
7
8
|
|
|
8
9
|
export * from './components';
|
|
10
|
+
export * from './extensions';
|
|
9
11
|
export * from './hooks';
|
|
12
|
+
export * from './themes';
|
package/src/styles/index.ts
CHANGED
package/src/styles/markdown.ts
CHANGED
|
@@ -6,41 +6,50 @@ import { mx } from '@dxos/react-ui-theme';
|
|
|
6
6
|
|
|
7
7
|
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
// TODO(burdon): Better way to align vertically than negative margin? Font-specific?
|
|
10
|
+
// https://tailwindcss.com/docs/font-weight
|
|
11
|
+
export const headings: Record<HeadingLevel, string[]> = {
|
|
12
|
+
1: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl', '-ml-[10px]'],
|
|
13
|
+
2: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl', '-ml-[8px]'],
|
|
14
|
+
3: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl', '-ml-[7px]'],
|
|
15
|
+
4: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-xl', '-ml-[6px]'],
|
|
16
|
+
5: ['mbs-4 mbe-2 font-medium text-inherit no-underline text-lg', '-ml-[5px]'],
|
|
17
|
+
6: ['mbs-4 mbe-2 font-medium text-inherit no-underline', '-ml-[4px]'],
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
export const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[attr(marker)] [&>li:before]:mlb-1 [&>li:before]:mie-2';
|
|
23
|
-
export const orderedList =
|
|
24
|
-
'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[counters(section,_".")_"._"] [counter-reset:section] [&>li:before]:mlb-1';
|
|
20
|
+
export const heading = (level: HeadingLevel, readonly?: boolean) => {
|
|
21
|
+
const [style, offset] = headings[level];
|
|
22
|
+
return mx(style, readonly && offset);
|
|
23
|
+
};
|
|
25
24
|
|
|
26
|
-
export const
|
|
25
|
+
export const text = 'text-neutral-800 dark:text-neutral-200';
|
|
26
|
+
export const light = 'text-neutral-200 dark:text-neutral-800';
|
|
27
27
|
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
export const horizontalRule = 'mlb-4 border-neutral-500/50';
|
|
28
|
+
export const mark = mx('!font-normal !no-underline !text-inherit opacity-40', light);
|
|
31
29
|
|
|
32
30
|
export const paragraph = 'mlb-1';
|
|
33
31
|
|
|
34
32
|
export const bold = 'font-bold';
|
|
33
|
+
export const italic = 'italic';
|
|
34
|
+
export const strikethrough = 'line-through';
|
|
35
35
|
|
|
36
|
-
export const code = 'font-mono
|
|
36
|
+
export const code = 'font-mono !no-underline text-neutral-700 dark:text-neutral-300';
|
|
37
|
+
export const codeMark = 'font-mono text-primary-500';
|
|
38
|
+
export const codeBlock = 'mlb-2 font-mono bg-neutral-500/10 p-3 rounded';
|
|
37
39
|
|
|
38
|
-
export const
|
|
40
|
+
export const inlineUrl = mx(code, 'px-1');
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
// TODO(burdon): Replace with widget.
|
|
43
|
+
export const blockquote = mx('pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30', light);
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
// TODO(burdon): Replace with widget.
|
|
46
|
+
export const horizontalRule =
|
|
47
|
+
'flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800';
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
// TODO(thure): Tailwind was not seeing `[&>li:before]:content-["•"]` as a utility class, but it would work if instead of `"•"` it was `"X"`… why?
|
|
50
|
+
export const unorderedList =
|
|
51
|
+
'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[attr(marker)] [&>li:before]:mlb-1 [&>li:before]:mie-2';
|
|
52
|
+
export const orderedList =
|
|
53
|
+
'mlb-2 grid grid-cols-[min-content_1fr] [&>li:before]:content-[counters(section,_".")_"._"] [counter-reset:section] [&>li:before]:mlb-1';
|
|
45
54
|
|
|
46
|
-
export const
|
|
55
|
+
export const listItem = 'contents before:[counter-increment:section]';
|
|
@@ -7,7 +7,7 @@ import * as decoding from 'lib0/decoding';
|
|
|
7
7
|
import * as encoding from 'lib0/encoding';
|
|
8
8
|
import { Observable } from 'lib0/observable';
|
|
9
9
|
import { useMemo } from 'react';
|
|
10
|
-
import * as
|
|
10
|
+
import * as YP from 'y-protocols/awareness';
|
|
11
11
|
import * as syncProtocol from 'y-protocols/sync';
|
|
12
12
|
import { Doc } from 'yjs';
|
|
13
13
|
|
|
@@ -15,9 +15,10 @@ import { Event } from '@dxos/async';
|
|
|
15
15
|
import { log } from '@dxos/log';
|
|
16
16
|
import { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { type EditorModel } from '../hooks';
|
|
19
|
+
import { cursorColor } from '../styles';
|
|
19
20
|
|
|
20
|
-
type Awareness =
|
|
21
|
+
type Awareness = YP.Awareness;
|
|
21
22
|
|
|
22
23
|
const messageSync = 0;
|
|
23
24
|
const messageAwareness = 1;
|
|
@@ -35,7 +36,7 @@ export class AwarenessProvider extends Observable<any> {
|
|
|
35
36
|
super();
|
|
36
37
|
this._remoteUpdate = update;
|
|
37
38
|
this._doc = doc;
|
|
38
|
-
this._awareness = awareness ?? new
|
|
39
|
+
this._awareness = awareness ?? new YP.Awareness(doc);
|
|
39
40
|
|
|
40
41
|
this._doc.on('update', this._handleDocUpdate.bind(this));
|
|
41
42
|
this._awareness.on('update', this._handleAwarenessUpdate.bind(this));
|
|
@@ -55,10 +56,7 @@ export class AwarenessProvider extends Observable<any> {
|
|
|
55
56
|
// Post local awareness state.
|
|
56
57
|
const encoderAwarenessState = encoding.createEncoder();
|
|
57
58
|
encoding.writeVarUint(encoderAwarenessState, messageAwareness);
|
|
58
|
-
encoding.writeVarUint8Array(
|
|
59
|
-
encoderAwarenessState,
|
|
60
|
-
awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this._doc.clientID]),
|
|
61
|
-
);
|
|
59
|
+
encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [this._doc.clientID]));
|
|
62
60
|
void this._remoteUpdate.emit(encoding.toUint8Array(encoderAwarenessState));
|
|
63
61
|
}
|
|
64
62
|
|
|
@@ -79,10 +77,7 @@ export class AwarenessProvider extends Observable<any> {
|
|
|
79
77
|
const changedClients = added.concat(updated).concat(removed);
|
|
80
78
|
const encoderAwareness = encoding.createEncoder();
|
|
81
79
|
encoding.writeVarUint(encoderAwareness, messageAwareness);
|
|
82
|
-
encoding.writeVarUint8Array(
|
|
83
|
-
encoderAwareness,
|
|
84
|
-
awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients),
|
|
85
|
-
);
|
|
80
|
+
encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
|
|
86
81
|
this._remoteUpdate.emit(encoding.toUint8Array(encoderAwareness));
|
|
87
82
|
}
|
|
88
83
|
|
|
@@ -109,7 +104,7 @@ export class AwarenessProvider extends Observable<any> {
|
|
|
109
104
|
}
|
|
110
105
|
|
|
111
106
|
case messageAwareness: {
|
|
112
|
-
|
|
107
|
+
YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
|
|
113
108
|
break;
|
|
114
109
|
}
|
|
115
110
|
|
|
@@ -117,7 +112,7 @@ export class AwarenessProvider extends Observable<any> {
|
|
|
117
112
|
encoding.writeVarUint(encoder, messageAwareness);
|
|
118
113
|
encoding.writeVarUint8Array(
|
|
119
114
|
encoder,
|
|
120
|
-
|
|
115
|
+
YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())),
|
|
121
116
|
);
|
|
122
117
|
sendReply = true;
|
|
123
118
|
break;
|
|
@@ -138,7 +133,7 @@ export class AwarenessProvider extends Observable<any> {
|
|
|
138
133
|
}
|
|
139
134
|
|
|
140
135
|
private _handleBeforeUnload() {
|
|
141
|
-
|
|
136
|
+
YP.removeAwarenessStates(this._awareness, [this._doc.clientID], 'window unload');
|
|
142
137
|
}
|
|
143
138
|
}
|
|
144
139
|
|
|
@@ -163,16 +158,17 @@ export class Replicator {
|
|
|
163
158
|
colorLight: cursorColor.light,
|
|
164
159
|
});
|
|
165
160
|
|
|
166
|
-
|
|
161
|
+
const field = 'content';
|
|
162
|
+
const content = this._kind === TextKind.PLAIN ? doc.getText(field) : doc.getXmlFragment(field);
|
|
167
163
|
const model: EditorModel = {
|
|
168
164
|
id: doc.guid,
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
text: () => content.toString(),
|
|
166
|
+
content,
|
|
167
|
+
awareness: provider.awareness,
|
|
171
168
|
peer: { id },
|
|
172
169
|
};
|
|
173
170
|
|
|
174
171
|
this._peers.push(model);
|
|
175
|
-
|
|
176
172
|
return model;
|
|
177
173
|
};
|
|
178
174
|
}
|
|
@@ -184,6 +180,5 @@ export type UseYjsModelOptions = {
|
|
|
184
180
|
};
|
|
185
181
|
|
|
186
182
|
export const useYjsModel = ({ replicator, id, doc }: UseYjsModelOptions): EditorModel => {
|
|
187
|
-
|
|
188
|
-
return peer;
|
|
183
|
+
return useMemo(() => replicator.createPeer(id, doc), [doc]);
|
|
189
184
|
};
|