@dxos/react-ui-editor 0.3.11-next.0fb359e → 0.3.11-next.e28df4f
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 +1854 -1391
- 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 +16 -14
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
- package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts +23 -0
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +25 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +23 -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/autocomplete.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/cursor.d.ts +1 -1
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts +9 -11
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/semaphore.d.ts +8 -12
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness.d.ts +2 -2
- package/dist/types/src/extensions/awareness.d.ts.map +1 -1
- package/dist/types/src/extensions/basic.d.ts +4 -5
- package/dist/types/src/extensions/basic.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +37 -20
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
- package/dist/types/src/extensions/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +2 -8
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
- 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 +13 -0
- package/dist/types/src/extensions/markdown/formatting.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/extensions/mention.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner.d.ts +4 -0
- package/dist/types/src/extensions/outliner.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 +25 -0
- package/dist/types/src/hooks/defs.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -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 +39 -0
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextModel.d.ts +2 -32
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -0
- 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/styles/tokens.d.ts +1 -0
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +7 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/package.json +48 -37
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +84 -91
- package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
- package/src/components/TextEditor/TextEditor.tsx +136 -95
- package/src/components/TextEditor/automerge.stories.tsx +1 -2
- package/src/components/TextEditor/comments.stories.tsx +357 -0
- package/src/components/TextEditor/hooks.stories.tsx +106 -0
- package/src/components/Toolbar/Toolbar.stories.tsx +90 -0
- package/src/components/Toolbar/Toolbar.tsx +166 -0
- package/src/components/Toolbar/index.ts +5 -0
- package/src/components/index.ts +1 -0
- package/src/extensions/autocomplete.ts +23 -19
- package/src/extensions/automerge/automerge.spec.tsx +76 -0
- package/src/extensions/automerge/automerge.test.ts +13 -0
- package/src/extensions/automerge/automerge.ts +55 -46
- package/src/extensions/automerge/cursor.ts +5 -4
- package/src/extensions/automerge/defs.ts +13 -34
- package/src/extensions/automerge/semaphore.ts +33 -32
- package/src/extensions/automerge/update-automerge.ts +14 -8
- package/src/extensions/automerge/update-codemirror.ts +5 -5
- package/src/extensions/awareness.ts +43 -49
- package/src/extensions/basic.ts +17 -10
- package/src/extensions/comments.ts +323 -268
- package/src/extensions/cursor.ts +50 -0
- package/src/extensions/index.ts +2 -8
- package/src/extensions/markdown/bundle.ts +23 -28
- package/src/extensions/markdown/code.ts +150 -0
- package/src/extensions/markdown/formatting.ts +188 -0
- package/src/extensions/markdown/heading.ts +59 -0
- package/src/extensions/markdown/highlight.ts +11 -26
- package/src/extensions/markdown/hr.ts +71 -0
- package/src/extensions/markdown/image.ts +87 -0
- package/src/extensions/markdown/index.ts +8 -0
- package/src/extensions/{link.ts → markdown/link.ts} +87 -86
- package/src/extensions/{table.ts → markdown/table.ts} +10 -4
- package/src/extensions/markdown/tasklist.ts +110 -0
- package/src/extensions/mention.ts +9 -2
- package/src/extensions/outliner.ts +12 -0
- package/src/hooks/awareness-provider.ts +24 -20
- package/src/hooks/defs.ts +42 -0
- package/src/hooks/index.ts +5 -2
- package/src/hooks/useActionHandler.ts +62 -0
- package/src/hooks/useEditorView.ts +29 -0
- package/src/hooks/useTextEditor.ts +153 -0
- package/src/hooks/useTextModel.ts +50 -59
- package/src/index.ts +3 -0
- package/src/styles/markdown.ts +9 -12
- package/src/styles/tokens.ts +3 -0
- package/src/themes/default.ts +53 -80
- package/src/util.ts +41 -0
- 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/yjs.stories.d.ts +0 -12
- 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/mermaid.d.ts +0 -3
- package/dist/types/src/extensions/mermaid.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 -3
- package/dist/types/src/hooks/automerge.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs.d.ts +0 -27
- 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/dist/types/src/util/cursor.d.ts.map +0 -1
- package/dist/types/src/util/index.d.ts +0 -3
- package/dist/types/src/util/index.d.ts.map +0 -1
- package/dist/types/src/util/util.d.ts +0 -5
- package/dist/types/src/util/util.d.ts.map +0 -1
- package/src/components/TextEditor/codemirror.stories.ts +0 -115
- package/src/components/TextEditor/yjs.stories.tsx +0 -56
- package/src/extensions/code.ts +0 -99
- package/src/extensions/hr.ts +0 -74
- package/src/extensions/image.ts +0 -69
- package/src/extensions/mermaid.ts +0 -11
- package/src/extensions/tasklist.ts +0 -121
- package/src/extensions/yjs/cursor.ts +0 -21
- 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 -52
- package/src/hooks/yjs.ts +0 -178
- 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/src/util/cursor.ts +0 -29
- package/src/util/index.ts +0 -6
- package/src/util/util.ts +0 -18
- /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
|
@@ -4,17 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import React, { useRef, useState } from 'react';
|
|
7
|
+
import React, { useState } from 'react';
|
|
9
8
|
|
|
10
9
|
import { TextObject } from '@dxos/echo-schema';
|
|
11
10
|
import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
|
|
12
11
|
import { withTheme } from '@dxos/storybook-utils';
|
|
13
12
|
|
|
14
|
-
import {
|
|
13
|
+
import { TextEditor, type TextEditorProps } from './TextEditor';
|
|
15
14
|
import { listener } from '../../extensions';
|
|
16
15
|
import { useTextModel } from '../../hooks';
|
|
17
|
-
import { textTheme } from '../../themes';
|
|
18
16
|
|
|
19
17
|
const initialText = [
|
|
20
18
|
'# TextEditor',
|
|
@@ -30,8 +28,7 @@ const Story = ({
|
|
|
30
28
|
text,
|
|
31
29
|
automerge,
|
|
32
30
|
...props
|
|
33
|
-
}: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'slots'>) => {
|
|
34
|
-
const ref = useRef<TextEditorRef>(null);
|
|
31
|
+
}: { text?: string; automerge?: boolean } & Pick<TextEditorProps, 'extensions' | 'placeholder' | 'slots'>) => {
|
|
35
32
|
const [item] = useState({ text: new TextObject(text, undefined, undefined, { automerge }) });
|
|
36
33
|
const model = useTextModel({ text: item.text });
|
|
37
34
|
if (!model) {
|
|
@@ -40,10 +37,9 @@ const Story = ({
|
|
|
40
37
|
|
|
41
38
|
return (
|
|
42
39
|
<div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
43
|
-
<div className='flex justify-center
|
|
40
|
+
<div className='flex justify-center'>
|
|
44
41
|
<div className='flex flex-col w-[800px] py-16'>
|
|
45
|
-
<TextEditor
|
|
46
|
-
<div className='flex shrink-0 h-[300px]'></div>
|
|
42
|
+
<TextEditor model={model} {...props} />
|
|
47
43
|
</div>
|
|
48
44
|
</div>
|
|
49
45
|
</div>
|
|
@@ -58,49 +54,19 @@ export default {
|
|
|
58
54
|
};
|
|
59
55
|
|
|
60
56
|
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
|
-
),
|
|
57
|
+
render: () => <Story placeholder='Enter text...' />,
|
|
81
58
|
};
|
|
82
59
|
|
|
83
60
|
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
|
-
),
|
|
61
|
+
render: () => <Story text={initialText} placeholder='Enter text...' />,
|
|
93
62
|
};
|
|
94
63
|
|
|
95
64
|
export const Automerge = {
|
|
65
|
+
render: () => <Story text={initialText} placeholder='Enter text...' automerge />,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Listener = {
|
|
96
69
|
render: () => (
|
|
97
|
-
<Story
|
|
98
|
-
automerge
|
|
99
|
-
text={initialText}
|
|
100
|
-
slots={defaultsDeep(
|
|
101
|
-
{ editor: { theme: textTheme, placeholder: 'Enter text...' } } satisfies TextEditorSlots,
|
|
102
|
-
defaultSlots,
|
|
103
|
-
)}
|
|
104
|
-
/>
|
|
70
|
+
<Story placeholder='Enter text...' extensions={[listener({ onChange: (text) => console.log(text) })]} />
|
|
105
71
|
),
|
|
106
72
|
};
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { EditorState, type Extension } from '@codemirror/state';
|
|
5
|
+
import { EditorState, type Extension, type StateEffect } from '@codemirror/state';
|
|
6
6
|
import { EditorView } from '@codemirror/view';
|
|
7
|
-
import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
7
|
+
// import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
8
8
|
import { vim } from '@replit/codemirror-vim';
|
|
9
9
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
10
10
|
import React, {
|
|
@@ -14,18 +14,22 @@ import React, {
|
|
|
14
14
|
useCallback,
|
|
15
15
|
useEffect,
|
|
16
16
|
useImperativeHandle,
|
|
17
|
+
useRef,
|
|
17
18
|
useState,
|
|
18
19
|
} from 'react';
|
|
19
20
|
|
|
20
|
-
import {
|
|
21
|
+
import { log } from '@dxos/log';
|
|
21
22
|
import { useThemeContext } from '@dxos/react-ui';
|
|
22
|
-
import {
|
|
23
|
+
import { attentionSurface, mx } from '@dxos/react-ui-theme';
|
|
24
|
+
import { isNotFalsy } from '@dxos/util';
|
|
23
25
|
|
|
24
|
-
import {
|
|
25
|
-
import { type
|
|
26
|
+
import { createBasicBundle, createMarkdownExtensions } from '../../extensions';
|
|
27
|
+
import { type EditorModel } from '../../hooks';
|
|
26
28
|
import { type ThemeStyles } from '../../styles';
|
|
27
29
|
import { defaultTheme, markdownTheme, textTheme } from '../../themes';
|
|
30
|
+
import { logChanges } from '../../util';
|
|
28
31
|
|
|
32
|
+
// TODO(burdon): Change to enum?
|
|
29
33
|
export const EditorModes = ['default', 'vim'] as const;
|
|
30
34
|
export type EditorMode = (typeof EditorModes)[number];
|
|
31
35
|
|
|
@@ -34,133 +38,169 @@ export type CursorInfo = {
|
|
|
34
38
|
to: number;
|
|
35
39
|
line: number;
|
|
36
40
|
lines: number;
|
|
41
|
+
length: number;
|
|
37
42
|
after?: string;
|
|
38
43
|
};
|
|
39
44
|
|
|
40
|
-
export type TextEditorRef = {
|
|
41
|
-
root: HTMLDivElement | null;
|
|
42
|
-
state?: EditorState;
|
|
43
|
-
view?: EditorView;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
45
|
export type TextEditorSlots = {
|
|
47
46
|
root?: Omit<ComponentProps<'div'>, 'ref'>;
|
|
48
47
|
editor?: {
|
|
49
48
|
className?: string;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
theme?: ThemeStyles;
|
|
49
|
+
};
|
|
50
|
+
content?: {
|
|
51
|
+
className?: string;
|
|
54
52
|
};
|
|
55
53
|
};
|
|
56
54
|
|
|
55
|
+
// TODO(burdon): Spellcheck?
|
|
57
56
|
export type TextEditorProps = {
|
|
58
|
-
model: EditorModel;
|
|
57
|
+
model: EditorModel; // TODO(burdon): Optional (e.g., just provide content if readonly).
|
|
59
58
|
readonly?: boolean; // TODO(burdon): Move into model.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
autoFocus?: boolean;
|
|
60
|
+
lineWrapping?: boolean;
|
|
61
|
+
scrollTo?: StateEffect<any>; // TODO(burdon): Restore scroll position: scrollTo EditorView.scrollSnapshot().
|
|
62
|
+
selection?: { anchor: number; head?: number };
|
|
63
|
+
editorMode?: EditorMode; // TODO(burdon): Factor out.
|
|
64
|
+
placeholder?: string;
|
|
65
|
+
theme?: ThemeStyles;
|
|
63
66
|
slots?: TextEditorSlots;
|
|
67
|
+
extensions?: Extension[];
|
|
68
|
+
debug?: boolean;
|
|
64
69
|
};
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* Base text editor.
|
|
68
|
-
* NOTE: Rather than adding properties, try to create extensions that can be reused.
|
|
69
73
|
*/
|
|
70
|
-
export const BaseTextEditor = forwardRef<
|
|
71
|
-
(
|
|
74
|
+
export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
75
|
+
(
|
|
76
|
+
{
|
|
77
|
+
model,
|
|
78
|
+
readonly,
|
|
79
|
+
autoFocus,
|
|
80
|
+
scrollTo,
|
|
81
|
+
selection,
|
|
82
|
+
editorMode,
|
|
83
|
+
theme,
|
|
84
|
+
slots = defaultSlots,
|
|
85
|
+
extensions = [],
|
|
86
|
+
debug,
|
|
87
|
+
},
|
|
88
|
+
forwardedRef,
|
|
89
|
+
) => {
|
|
90
|
+
// TODO(burdon): Hook causes error even if properties are not spread into div.
|
|
91
|
+
// Uncaught TypeError: Cannot read properties of undefined (reading 'relatedTarget')
|
|
92
|
+
// Uses event.detail, which is deprecated (not event.details). At runtime the event has a property `details`.
|
|
93
|
+
// https://github.com/microsoft/tabster/blob/master/src/State/FocusedElement.ts#L348 (e.detail.relatedTarget)
|
|
94
|
+
// https://github.com/microsoft/keyborg/blob/49e49b2c3ba0a5f6cc518ac46825d7551def8109/src/FocusEvent.ts#L58
|
|
95
|
+
// const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
|
|
72
96
|
const { themeMode } = useThemeContext();
|
|
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]);
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
99
|
+
const [view, setView] = useState<EditorView | null>(null);
|
|
100
|
+
|
|
101
|
+
// The view ref can be used to focus the editor.
|
|
102
|
+
// NOTE: This does not cause the parent to re-render, so the ref is not available immediately.
|
|
103
|
+
useImperativeHandle<EditorView | null, EditorView | null>(forwardedRef, () => view, [view]);
|
|
104
|
+
|
|
105
|
+
// Set focus.
|
|
93
106
|
useEffect(() => {
|
|
94
|
-
if (
|
|
95
|
-
view.
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
if (autoFocus && !view?.hasFocus) {
|
|
108
|
+
if (view?.state.selection.main?.from === 0) {
|
|
109
|
+
// Start at end of line.
|
|
110
|
+
const { to } = view.state.doc.lineAt(0);
|
|
111
|
+
view?.dispatch({ selection: { anchor: to } });
|
|
112
|
+
}
|
|
113
|
+
view?.focus();
|
|
98
114
|
}
|
|
99
|
-
}, [view,
|
|
115
|
+
}, [view, autoFocus]);
|
|
100
116
|
|
|
117
|
+
// Create editor state and view.
|
|
118
|
+
// The view is recreated if the model or extensions are changed.
|
|
101
119
|
useEffect(() => {
|
|
102
|
-
if (!
|
|
120
|
+
if (!model || !rootRef.current) {
|
|
103
121
|
return;
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
//
|
|
124
|
+
//
|
|
125
|
+
// EditorState
|
|
107
126
|
// https://codemirror.net/docs/ref/#state.EditorStateConfig
|
|
127
|
+
//
|
|
108
128
|
const state = EditorState.create({
|
|
109
129
|
doc: model.text(),
|
|
130
|
+
selection,
|
|
110
131
|
extensions: [
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
// TODO(burdon): Doesn't catch errors in keymap functions.
|
|
133
|
+
EditorView.exceptionSink.of((err) => {
|
|
134
|
+
log.catch(err);
|
|
135
|
+
}),
|
|
115
136
|
|
|
116
137
|
// Theme.
|
|
117
|
-
// TODO(burdon): Make
|
|
138
|
+
// TODO(burdon): Make configurable.
|
|
118
139
|
EditorView.baseTheme(defaultTheme),
|
|
119
|
-
EditorView.theme(
|
|
140
|
+
EditorView.theme(theme ?? {}),
|
|
120
141
|
EditorView.darkTheme.of(themeMode === 'dark'),
|
|
142
|
+
EditorView.editorAttributes.of({ class: slots.editor?.className ?? '' }),
|
|
143
|
+
EditorView.contentAttributes.of({ class: slots.content?.className ?? '' }),
|
|
144
|
+
|
|
145
|
+
// State.
|
|
146
|
+
EditorView.editable.of(!readonly),
|
|
147
|
+
EditorState.readOnly.of(!!readonly),
|
|
121
148
|
|
|
122
149
|
// Storage and replication.
|
|
150
|
+
// NOTE: This must come before user extensions.
|
|
123
151
|
model.extension,
|
|
124
152
|
|
|
153
|
+
// TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
|
|
154
|
+
editorMode === 'vim' && vim(),
|
|
155
|
+
|
|
125
156
|
// Custom.
|
|
126
157
|
...extensions,
|
|
127
|
-
].filter(
|
|
158
|
+
].filter(isNotFalsy),
|
|
128
159
|
});
|
|
129
160
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
//
|
|
162
|
+
// EditorView
|
|
163
|
+
// https://codemirror.net/docs/ref/#view.EditorViewConfig
|
|
164
|
+
//
|
|
165
|
+
const newView = new EditorView({
|
|
135
166
|
state,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
parent: rootRef.current,
|
|
168
|
+
scrollTo,
|
|
169
|
+
// NOTE: Uncomment to debug/monitor all transactions.
|
|
170
|
+
// https://codemirror.net/docs/ref/#view.EditorView.dispatch
|
|
171
|
+
dispatchTransactions: (trs, view) => {
|
|
172
|
+
if (debug) {
|
|
173
|
+
logChanges(trs);
|
|
174
|
+
}
|
|
175
|
+
view.update(trs);
|
|
176
|
+
},
|
|
145
177
|
});
|
|
146
178
|
|
|
179
|
+
view?.destroy();
|
|
180
|
+
setView(newView);
|
|
181
|
+
|
|
147
182
|
return () => {
|
|
148
|
-
|
|
149
|
-
|
|
183
|
+
newView?.destroy();
|
|
184
|
+
setView(null);
|
|
150
185
|
};
|
|
151
|
-
}, [
|
|
186
|
+
}, [rootRef, model, readonly, editorMode, themeMode]);
|
|
152
187
|
|
|
188
|
+
// Handles tab/focus.
|
|
189
|
+
// Pressing Escape focuses the outer div (to support tab navigation); pressing Enter refocuses the editor.
|
|
190
|
+
// TODO(burdon): Convert to keymap?
|
|
153
191
|
const handleKeyUp = useCallback(
|
|
154
192
|
(event: KeyboardEvent) => {
|
|
155
193
|
const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
|
|
156
194
|
switch (key) {
|
|
157
195
|
case 'Enter': {
|
|
158
|
-
view?.
|
|
196
|
+
view?.focus();
|
|
159
197
|
break;
|
|
160
198
|
}
|
|
161
199
|
|
|
162
200
|
case 'Escape': {
|
|
163
|
-
editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey)
|
|
201
|
+
if (editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey)) {
|
|
202
|
+
view?.focus();
|
|
203
|
+
}
|
|
164
204
|
break;
|
|
165
205
|
}
|
|
166
206
|
}
|
|
@@ -171,42 +211,45 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
171
211
|
return (
|
|
172
212
|
<div
|
|
173
213
|
key={model.id}
|
|
174
|
-
|
|
214
|
+
role='none'
|
|
175
215
|
tabIndex={0}
|
|
176
|
-
{...slots
|
|
177
|
-
{...(editorMode !== 'vim' && tabsterDOMAttribute)}
|
|
216
|
+
{...slots.root}
|
|
217
|
+
// {...(editorMode !== 'vim' && tabsterDOMAttribute)}
|
|
178
218
|
onKeyUp={handleKeyUp}
|
|
219
|
+
ref={rootRef}
|
|
179
220
|
/>
|
|
180
221
|
);
|
|
181
222
|
},
|
|
182
223
|
);
|
|
183
224
|
|
|
184
|
-
export const TextEditor = forwardRef<
|
|
185
|
-
({ readonly,
|
|
225
|
+
export const TextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
226
|
+
({ readonly, placeholder, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
|
|
186
227
|
const { themeMode } = useThemeContext();
|
|
187
|
-
const
|
|
228
|
+
const updatedSlots = defaultsDeep({}, slots, defaultTextSlots);
|
|
188
229
|
return (
|
|
189
230
|
<BaseTextEditor
|
|
190
231
|
ref={forwardedRef}
|
|
191
232
|
readonly={readonly}
|
|
192
|
-
extensions={[
|
|
193
|
-
|
|
233
|
+
extensions={[createBasicBundle({ themeMode, placeholder, lineWrapping }), ...extensions]}
|
|
234
|
+
theme={theme}
|
|
235
|
+
slots={updatedSlots}
|
|
194
236
|
{...props}
|
|
195
237
|
/>
|
|
196
238
|
);
|
|
197
239
|
},
|
|
198
240
|
);
|
|
199
241
|
|
|
200
|
-
export const MarkdownEditor = forwardRef<
|
|
201
|
-
({ readonly,
|
|
242
|
+
export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
|
|
243
|
+
({ readonly, placeholder, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
|
|
202
244
|
const { themeMode } = useThemeContext();
|
|
203
|
-
const
|
|
245
|
+
const updatedSlots = defaultsDeep({}, slots, defaultMarkdownSlots);
|
|
204
246
|
return (
|
|
205
247
|
<BaseTextEditor
|
|
206
248
|
ref={forwardedRef}
|
|
207
249
|
readonly={readonly}
|
|
208
|
-
extensions={[
|
|
209
|
-
|
|
250
|
+
extensions={[createMarkdownExtensions({ themeMode, placeholder }), ...extensions]}
|
|
251
|
+
theme={theme}
|
|
252
|
+
slots={updatedSlots}
|
|
210
253
|
{...props}
|
|
211
254
|
/>
|
|
212
255
|
);
|
|
@@ -215,20 +258,18 @@ export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
215
258
|
|
|
216
259
|
export const defaultSlots: TextEditorSlots = {
|
|
217
260
|
root: {
|
|
218
|
-
|
|
261
|
+
// TODO(burdon): Add focusRing by default/as property?
|
|
262
|
+
className: mx('flex flex-col grow overflow-y-auto', attentionSurface),
|
|
263
|
+
},
|
|
264
|
+
editor: {
|
|
265
|
+
className: 'h-full p-2',
|
|
219
266
|
},
|
|
220
267
|
};
|
|
221
268
|
|
|
222
269
|
export const defaultTextSlots: TextEditorSlots = {
|
|
223
270
|
...defaultSlots,
|
|
224
|
-
editor: {
|
|
225
|
-
theme: textTheme,
|
|
226
|
-
},
|
|
227
271
|
};
|
|
228
272
|
|
|
229
273
|
export const defaultMarkdownSlots: TextEditorSlots = {
|
|
230
274
|
...defaultSlots,
|
|
231
|
-
editor: {
|
|
232
|
-
theme: markdownTheme,
|
|
233
|
-
},
|
|
234
275
|
};
|
|
@@ -13,7 +13,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
|
|
13
13
|
|
|
14
14
|
import { type Prop } from '@dxos/automerge/automerge';
|
|
15
15
|
import { Repo, type DocHandle } from '@dxos/automerge/automerge-repo';
|
|
16
|
-
import { Filter
|
|
16
|
+
import { Filter } from '@dxos/echo-schema';
|
|
17
17
|
import { type PublicKey } from '@dxos/keys';
|
|
18
18
|
import { Expando, TextObject, useSpace } from '@dxos/react-client/echo';
|
|
19
19
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
@@ -130,7 +130,6 @@ const EchoStory = ({ id, spaceKey }: { id: number; spaceKey: PublicKey }) => {
|
|
|
130
130
|
|
|
131
131
|
export const WithEcho = {
|
|
132
132
|
render: () => {
|
|
133
|
-
setGlobalAutomergePreference(true);
|
|
134
133
|
return (
|
|
135
134
|
<ClientRepeater
|
|
136
135
|
count={2}
|