@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
|
@@ -3,63 +3,47 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import React, {
|
|
9
|
-
import { createRoot } from 'react-dom/client';
|
|
6
|
+
|
|
7
|
+
import defaultsDeep from 'lodash.defaultsdeep';
|
|
8
|
+
import React, { useRef, useState } from 'react';
|
|
10
9
|
|
|
11
10
|
import { TextObject } from '@dxos/echo-schema';
|
|
12
|
-
import { fixedInsetFlexLayout,
|
|
11
|
+
import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
|
|
13
12
|
import { withTheme } from '@dxos/storybook-utils';
|
|
14
13
|
|
|
15
|
-
import { TextEditor } from './TextEditor';
|
|
16
|
-
import {
|
|
14
|
+
import { defaultSlots, TextEditor, type TextEditorProps, type TextEditorRef, type TextEditorSlots } from './TextEditor';
|
|
15
|
+
import { listener } from '../../extensions';
|
|
17
16
|
import { useTextModel } from '../../hooks';
|
|
17
|
+
import { textTheme } from '../../themes';
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
'',
|
|
21
|
-
'',
|
|
22
|
-
'This is all about [DXOS](https://dxos.org); read more [here](https://docs.dxos.org/guide/getting-started.html).',
|
|
19
|
+
const initialText = [
|
|
20
|
+
'# TextEditor',
|
|
23
21
|
'',
|
|
22
|
+
'This is the basic plain text editor within minimal formatting.',
|
|
23
|
+
'You can add custom styles and create custom extensions.',
|
|
24
|
+
'Or use the MarkdownEditor to edit documents.',
|
|
24
25
|
'',
|
|
26
|
+
'https://dxos.org',
|
|
25
27
|
].join('\n');
|
|
26
28
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<p className='pr-1'>{web.origin}</p>
|
|
35
|
-
</div>
|
|
36
|
-
</StrictMode>,
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const Story = ({ automerge }: { automerge?: boolean }) => {
|
|
41
|
-
const [item] = useState({ text: new TextObject(text, undefined, undefined, { useAutomergeBackend: !!automerge }) });
|
|
29
|
+
const Story = ({
|
|
30
|
+
text,
|
|
31
|
+
automerge,
|
|
32
|
+
...props
|
|
33
|
+
}: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
|
|
34
|
+
const ref = useRef<TextEditorRef>(null);
|
|
35
|
+
const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
|
|
42
36
|
const model = useTextModel({ text: item.text });
|
|
43
37
|
if (!model) {
|
|
44
|
-
return
|
|
38
|
+
return null;
|
|
45
39
|
}
|
|
46
40
|
|
|
47
41
|
return (
|
|
48
42
|
<div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
49
|
-
<div className='flex justify-center
|
|
50
|
-
<div className='w-[800px]'>
|
|
51
|
-
<TextEditor
|
|
52
|
-
|
|
53
|
-
extensions={[
|
|
54
|
-
markdown(),
|
|
55
|
-
hyperlinkDecoration({ link: false }),
|
|
56
|
-
hyperLinkTooltip(),
|
|
57
|
-
// EditorView.domEventHandlers({
|
|
58
|
-
// mousedown: (e, view) => {},
|
|
59
|
-
// }),
|
|
60
|
-
]}
|
|
61
|
-
slots={{ root: { className: mx(inputSurface, 'p-2') } }}
|
|
62
|
-
/>
|
|
43
|
+
<div className='flex justify-center overflow-y-scroll'>
|
|
44
|
+
<div className='flex flex-col w-[800px] py-16'>
|
|
45
|
+
<TextEditor ref={ref} model={model} {...props} />
|
|
46
|
+
<div className='flex shrink-0 h-[300px]'></div>
|
|
63
47
|
</div>
|
|
64
48
|
</div>
|
|
65
49
|
</div>
|
|
@@ -67,13 +51,56 @@ const Story = ({ automerge }: { automerge?: boolean }) => {
|
|
|
67
51
|
};
|
|
68
52
|
|
|
69
53
|
export default {
|
|
54
|
+
title: 'react-ui-editor/TextEditor',
|
|
70
55
|
component: TextEditor,
|
|
71
56
|
decorators: [withTheme],
|
|
72
57
|
render: Story,
|
|
73
58
|
};
|
|
74
59
|
|
|
75
|
-
export const Default = {
|
|
60
|
+
export const Default = {
|
|
61
|
+
render: () => (
|
|
62
|
+
<Story
|
|
63
|
+
slots={defaultsDeep(
|
|
64
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
65
|
+
defaultSlots,
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const Listener = {
|
|
72
|
+
render: () => (
|
|
73
|
+
<Story
|
|
74
|
+
slots={defaultsDeep(
|
|
75
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
76
|
+
defaultSlots,
|
|
77
|
+
)}
|
|
78
|
+
extensions={[listener({ onChange: (text) => console.log(text) })]}
|
|
79
|
+
/>
|
|
80
|
+
),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const Text = {
|
|
84
|
+
render: () => (
|
|
85
|
+
<Story
|
|
86
|
+
text={initialText}
|
|
87
|
+
slots={defaultsDeep(
|
|
88
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
89
|
+
defaultSlots,
|
|
90
|
+
)}
|
|
91
|
+
/>
|
|
92
|
+
),
|
|
93
|
+
};
|
|
76
94
|
|
|
77
95
|
export const Automerge = {
|
|
78
|
-
render: () =>
|
|
96
|
+
render: () => (
|
|
97
|
+
<Story
|
|
98
|
+
automerge
|
|
99
|
+
text={initialText}
|
|
100
|
+
slots={defaultsDeep(
|
|
101
|
+
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
102
|
+
defaultSlots,
|
|
103
|
+
)}
|
|
104
|
+
/>
|
|
105
|
+
),
|
|
79
106
|
};
|
|
@@ -7,23 +7,24 @@ import { EditorView } from '@codemirror/view';
|
|
|
7
7
|
import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
8
8
|
import { vim } from '@replit/codemirror-vim';
|
|
9
9
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
10
|
-
import get from 'lodash.get';
|
|
11
10
|
import React, {
|
|
11
|
+
type ComponentProps,
|
|
12
12
|
type KeyboardEvent,
|
|
13
13
|
forwardRef,
|
|
14
14
|
useCallback,
|
|
15
15
|
useEffect,
|
|
16
16
|
useImperativeHandle,
|
|
17
17
|
useState,
|
|
18
|
-
type ComponentProps,
|
|
19
18
|
} from 'react';
|
|
20
19
|
|
|
21
|
-
import {
|
|
20
|
+
import { generateName } from '@dxos/display-name';
|
|
22
21
|
import { useThemeContext } from '@dxos/react-ui';
|
|
22
|
+
import { getColorForValue, inputSurface, mx } from '@dxos/react-ui-theme';
|
|
23
23
|
|
|
24
|
-
import { basicBundle,
|
|
25
|
-
import { type
|
|
26
|
-
import type
|
|
24
|
+
import { basicBundle, markdownBundle, setCommentRange } from '../../extensions';
|
|
25
|
+
import { type CommentRange, type EditorModel } from '../../hooks';
|
|
26
|
+
import { type ThemeStyles } from '../../styles';
|
|
27
|
+
import { defaultTheme, markdownTheme, textTheme } from '../../themes';
|
|
27
28
|
|
|
28
29
|
export const EditorModes = ['default', 'vim'] as const;
|
|
29
30
|
export type EditorMode = (typeof EditorModes)[number];
|
|
@@ -37,7 +38,7 @@ export type CursorInfo = {
|
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export type TextEditorRef = {
|
|
40
|
-
|
|
41
|
+
root: HTMLDivElement | null;
|
|
41
42
|
state?: EditorState;
|
|
42
43
|
view?: EditorView;
|
|
43
44
|
};
|
|
@@ -55,9 +56,11 @@ export type TextEditorSlots = {
|
|
|
55
56
|
|
|
56
57
|
export type TextEditorProps = {
|
|
57
58
|
model: EditorModel;
|
|
59
|
+
readonly?: boolean; // TODO(burdon): Move into model.
|
|
60
|
+
comments?: CommentRange[]; // TODO(burdon): Move into extension.
|
|
58
61
|
extensions?: Extension[];
|
|
59
|
-
slots?: TextEditorSlots;
|
|
60
62
|
editorMode?: EditorMode;
|
|
63
|
+
slots?: TextEditorSlots;
|
|
61
64
|
};
|
|
62
65
|
|
|
63
66
|
/**
|
|
@@ -65,66 +68,87 @@ export type TextEditorProps = {
|
|
|
65
68
|
* NOTE: Rather than adding properties, try to create extensions that can be reused.
|
|
66
69
|
*/
|
|
67
70
|
export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
68
|
-
({ model, extensions = [],
|
|
71
|
+
({ model, readonly, comments, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
|
|
69
72
|
const { themeMode } = useThemeContext();
|
|
70
73
|
const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
|
|
74
|
+
const [root, setRoot] = useState<HTMLDivElement | null>(null);
|
|
75
|
+
const [{ state = undefined, view = undefined } = {}, setEditor] = useState<
|
|
76
|
+
{ state?: EditorState; view?: EditorView } | undefined
|
|
77
|
+
>();
|
|
78
|
+
useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
|
|
79
|
+
|
|
80
|
+
// TODO(burdon): Factor out as extension.
|
|
81
|
+
const { awareness, peer } = model;
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (awareness && peer) {
|
|
84
|
+
awareness.setLocalStateField('user', {
|
|
85
|
+
name: peer.name ?? generateName(peer.id),
|
|
86
|
+
color: getColorForValue({ value: peer.id, type: 'color' }),
|
|
87
|
+
colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}, [awareness, peer, themeMode]);
|
|
71
91
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
view,
|
|
81
|
-
}),
|
|
82
|
-
[view, state, parent],
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// TODO(burdon): Pass in extension?
|
|
86
|
-
const collaboration = useCollaboration(model, themeMode);
|
|
92
|
+
// TODO(burdon): Factor out as extension.
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (view && comments?.length) {
|
|
95
|
+
view.dispatch({
|
|
96
|
+
effects: setCommentRange.of({ model, comments }),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}, [view, comments]);
|
|
87
100
|
|
|
88
101
|
useEffect(() => {
|
|
89
|
-
if (!
|
|
102
|
+
if (!root) {
|
|
90
103
|
return;
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
: model.content?.toString();
|
|
96
|
-
|
|
106
|
+
// TODO(burdon): Remember cursor position.
|
|
107
|
+
// https://codemirror.net/docs/ref/#state.EditorStateConfig
|
|
97
108
|
const state = EditorState.create({
|
|
98
|
-
doc:
|
|
109
|
+
doc: model.text(),
|
|
99
110
|
extensions: [
|
|
100
|
-
|
|
101
|
-
editorMode === 'vim' && vim(),
|
|
111
|
+
readonly && EditorState.readOnly.of(readonly),
|
|
102
112
|
|
|
103
|
-
//
|
|
104
|
-
|
|
113
|
+
// TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
|
|
114
|
+
editorMode === 'vim' && vim(),
|
|
105
115
|
|
|
106
116
|
// Theme.
|
|
107
|
-
|
|
117
|
+
// TODO(burdon): Make theme configurable.
|
|
118
|
+
EditorView.baseTheme(defaultTheme),
|
|
119
|
+
EditorView.theme(slots?.editor?.theme ?? {}),
|
|
120
|
+
EditorView.darkTheme.of(themeMode === 'dark'),
|
|
121
|
+
|
|
122
|
+
// Storage and replication.
|
|
123
|
+
model.extension,
|
|
108
124
|
|
|
109
125
|
// Custom.
|
|
110
126
|
...extensions,
|
|
111
127
|
].filter(Boolean) as Extension[],
|
|
112
128
|
});
|
|
113
129
|
|
|
114
|
-
setState(state);
|
|
115
|
-
|
|
116
130
|
// NOTE: This repaints the editor.
|
|
117
131
|
// If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
|
|
118
|
-
// Ideally this should not
|
|
132
|
+
// Ideally this should not happen except when changing between text objects.
|
|
119
133
|
view?.destroy();
|
|
120
|
-
|
|
134
|
+
setEditor({
|
|
135
|
+
state,
|
|
136
|
+
view: new EditorView({
|
|
137
|
+
state,
|
|
138
|
+
parent: root,
|
|
139
|
+
// NOTE: Uncomment to spy on all transactions.
|
|
140
|
+
// https://codemirror.net/docs/ref/#view.EditorView.dispatch
|
|
141
|
+
// dispatch: (transaction, view) => {
|
|
142
|
+
// view.update([transaction]);
|
|
143
|
+
// },
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
121
146
|
|
|
122
147
|
return () => {
|
|
123
148
|
view?.destroy();
|
|
124
|
-
|
|
125
|
-
setState(undefined);
|
|
149
|
+
setEditor(undefined);
|
|
126
150
|
};
|
|
127
|
-
}, [
|
|
151
|
+
}, [root, model, readonly, editorMode, themeMode]);
|
|
128
152
|
|
|
129
153
|
const handleKeyUp = useCallback(
|
|
130
154
|
(event: KeyboardEvent) => {
|
|
@@ -136,7 +160,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
136
160
|
}
|
|
137
161
|
|
|
138
162
|
case 'Escape': {
|
|
139
|
-
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) &&
|
|
163
|
+
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
|
|
140
164
|
break;
|
|
141
165
|
}
|
|
142
166
|
}
|
|
@@ -147,7 +171,7 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
147
171
|
return (
|
|
148
172
|
<div
|
|
149
173
|
key={model.id}
|
|
150
|
-
ref={
|
|
174
|
+
ref={setRoot}
|
|
151
175
|
tabIndex={0}
|
|
152
176
|
{...slots?.root}
|
|
153
177
|
{...(editorMode !== 'vim' && tabsterDOMAttribute)}
|
|
@@ -158,14 +182,15 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
158
182
|
);
|
|
159
183
|
|
|
160
184
|
export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
161
|
-
({ extensions
|
|
185
|
+
({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
162
186
|
const { themeMode } = useThemeContext();
|
|
163
|
-
const
|
|
187
|
+
const slots = defaultsDeep({}, _slots, defaultTextSlots);
|
|
164
188
|
return (
|
|
165
189
|
<BaseTextEditor
|
|
166
190
|
ref={forwardedRef}
|
|
167
|
-
|
|
168
|
-
|
|
191
|
+
readonly={readonly}
|
|
192
|
+
extensions={[basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
193
|
+
slots={slots}
|
|
169
194
|
{...props}
|
|
170
195
|
/>
|
|
171
196
|
);
|
|
@@ -173,16 +198,37 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
173
198
|
);
|
|
174
199
|
|
|
175
200
|
export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
176
|
-
({ extensions
|
|
201
|
+
({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
|
|
177
202
|
const { themeMode } = useThemeContext();
|
|
178
|
-
const
|
|
203
|
+
const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
|
|
179
204
|
return (
|
|
180
205
|
<BaseTextEditor
|
|
181
206
|
ref={forwardedRef}
|
|
182
|
-
|
|
183
|
-
|
|
207
|
+
readonly={readonly}
|
|
208
|
+
extensions={[markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
|
|
209
|
+
slots={slots}
|
|
184
210
|
{...props}
|
|
185
211
|
/>
|
|
186
212
|
);
|
|
187
213
|
},
|
|
188
214
|
);
|
|
215
|
+
|
|
216
|
+
export const defaultSlots: TextEditorSlots = {
|
|
217
|
+
root: {
|
|
218
|
+
className: mx('p-2', inputSurface),
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const defaultTextSlots: TextEditorSlots = {
|
|
223
|
+
...defaultSlots,
|
|
224
|
+
editor: {
|
|
225
|
+
theme: textTheme,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const defaultMarkdownSlots: TextEditorSlots = {
|
|
230
|
+
...defaultSlots,
|
|
231
|
+
editor: {
|
|
232
|
+
theme: markdownTheme,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
@@ -2,17 +2,29 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
|
|
6
7
|
import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel';
|
|
7
8
|
import { EditorView } from '@codemirror/view';
|
|
9
|
+
import '@preact/signals-react'; // Register react integration
|
|
8
10
|
import { basicSetup } from 'codemirror';
|
|
9
11
|
import get from 'lodash.get';
|
|
10
12
|
import React, { useEffect, useRef, useState } from 'react';
|
|
11
13
|
|
|
12
14
|
import { type Prop } from '@dxos/automerge/automerge';
|
|
13
|
-
import { type DocHandle
|
|
15
|
+
import { Repo, type DocHandle } from '@dxos/automerge/automerge-repo';
|
|
16
|
+
import { Filter, setGlobalAutomergePreference } from '@dxos/echo-schema';
|
|
17
|
+
import { type PublicKey } from '@dxos/keys';
|
|
18
|
+
import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
|
|
19
|
+
import { useIdentity } from '@dxos/react-client/halo';
|
|
20
|
+
import { ClientRepeater } from '@dxos/react-client/testing';
|
|
21
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
14
22
|
|
|
15
|
-
import {
|
|
23
|
+
import { MarkdownEditor } from './TextEditor';
|
|
24
|
+
import { type IDocHandle, automerge, awareness } from '../../extensions';
|
|
25
|
+
import { useTextModel } from '../../hooks';
|
|
26
|
+
|
|
27
|
+
// TODO(burdon): Move to components.
|
|
16
28
|
|
|
17
29
|
type EditorProps = {
|
|
18
30
|
handle: IDocHandle;
|
|
@@ -26,7 +38,7 @@ const Editor = ({ handle, path }: EditorProps) => {
|
|
|
26
38
|
useEffect(() => {
|
|
27
39
|
const view = (editorRoot.current = new EditorView({
|
|
28
40
|
doc: get(handle.docSync()!, path),
|
|
29
|
-
extensions: [basicSetup,
|
|
41
|
+
extensions: [basicSetup, automerge({ handle, path }), awareness()],
|
|
30
42
|
parent: containerRef.current as any,
|
|
31
43
|
}));
|
|
32
44
|
|
|
@@ -81,9 +93,59 @@ const Story = () => {
|
|
|
81
93
|
};
|
|
82
94
|
|
|
83
95
|
export default {
|
|
84
|
-
title: 'Automerge',
|
|
96
|
+
title: 'react-ui-editor/Automerge',
|
|
97
|
+
component: Editor,
|
|
98
|
+
render: () => <Story />,
|
|
85
99
|
};
|
|
86
100
|
|
|
87
|
-
export const
|
|
88
|
-
|
|
101
|
+
export const Default = {};
|
|
102
|
+
|
|
103
|
+
const EchoStory = ({ id, spaceKey }: { id: number; spaceKey: PublicKey }) => {
|
|
104
|
+
const identity = useIdentity();
|
|
105
|
+
const space = useSpace(spaceKey);
|
|
106
|
+
// TODO(dmaretskyi): useQuery doesn't work.
|
|
107
|
+
const [obj] = space?.db.query(Filter.from({ type: 'test' })).objects ?? [];
|
|
108
|
+
|
|
109
|
+
const model = useTextModel({
|
|
110
|
+
text: obj?.content,
|
|
111
|
+
identity,
|
|
112
|
+
space,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!model) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
// <div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
121
|
+
<div className='flex justify-center overflow-y-scroll'>
|
|
122
|
+
<div className='flex flex-col w-[800px] py-16'>
|
|
123
|
+
<MarkdownEditor model={model} />
|
|
124
|
+
<div className='flex shrink-0 h-[300px]'></div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
// </div>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const WithEcho = {
|
|
132
|
+
render: () => {
|
|
133
|
+
setGlobalAutomergePreference(true);
|
|
134
|
+
return (
|
|
135
|
+
<ClientRepeater
|
|
136
|
+
count={2}
|
|
137
|
+
createSpace
|
|
138
|
+
onCreateSpace={async (space) => {
|
|
139
|
+
space.db.add(
|
|
140
|
+
new Expando({
|
|
141
|
+
type: 'test',
|
|
142
|
+
content: new TextObject('Hello world!'),
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}}
|
|
146
|
+
Component={EchoStory}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
decorators: [withTheme],
|
|
89
151
|
};
|
|
@@ -17,7 +17,8 @@ import { type Identity } from '@dxos/react-client/halo';
|
|
|
17
17
|
import { joinCommonSpace, TestBuilder, textGenerator } from '@dxos/react-client/testing';
|
|
18
18
|
import { YText } from '@dxos/text-model';
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { YAwarenessProvider } from '../../hooks';
|
|
21
|
+
import { cursorColor } from '../../styles';
|
|
21
22
|
import { EditorDocument, types as schema } from '../../testing';
|
|
22
23
|
|
|
23
24
|
export default {
|
|
@@ -35,7 +36,7 @@ const updateEditor = async (id: number, editor: HTMLDivElement, identity: Identi
|
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const { awareness } = new
|
|
39
|
+
const { awareness } = new YAwarenessProvider({ space, doc, channel: `yjs.awareness.${text.id}` });
|
|
39
40
|
awareness.setLocalStateField('user', {
|
|
40
41
|
name: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
|
|
41
42
|
// TODO(wittjosiah): Pick colours from theme based on identity key.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// https://codemirror.net/examples/autocompletion
|
|
6
|
+
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
7
|
+
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
autocompletion,
|
|
11
|
+
completionKeymap,
|
|
12
|
+
type Completion,
|
|
13
|
+
type CompletionContext,
|
|
14
|
+
type CompletionResult,
|
|
15
|
+
} from '@codemirror/autocomplete';
|
|
16
|
+
import { keymap } from '@codemirror/view';
|
|
17
|
+
|
|
18
|
+
export type AutocompleteResult = Completion;
|
|
19
|
+
|
|
20
|
+
export type AutocompleteOptions = {
|
|
21
|
+
onSearch: (text: string) => Completion[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Autocomplete extension.
|
|
26
|
+
*/
|
|
27
|
+
export const autocomplete = ({ onSearch }: AutocompleteOptions) => {
|
|
28
|
+
return [
|
|
29
|
+
// https://codemirror.net/docs/ref/#view.keymap
|
|
30
|
+
// https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
|
|
31
|
+
keymap.of(completionKeymap),
|
|
32
|
+
|
|
33
|
+
// https://codemirror.net/examples/autocompletion
|
|
34
|
+
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
|
35
|
+
autocompletion({
|
|
36
|
+
// TODO(burdon): Set custom keymap.
|
|
37
|
+
// defaultKeymap: false,
|
|
38
|
+
|
|
39
|
+
// Don't start unless key press.
|
|
40
|
+
activateOnTyping: false,
|
|
41
|
+
|
|
42
|
+
// TODO(burdon): Option to create new page?
|
|
43
|
+
// TODO(burdon): Optional decoration via addToOptions
|
|
44
|
+
override: [
|
|
45
|
+
(context: CompletionContext): CompletionResult | null => {
|
|
46
|
+
const match = context.matchBefore(/\w*/);
|
|
47
|
+
if (!match || (match.from === match.to && !context.explicit)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
from: match.from,
|
|
53
|
+
options: onSearch(match.text.toLowerCase()),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
}),
|
|
58
|
+
];
|
|
59
|
+
};
|