@dxos/react-ui-editor 0.3.11-main.5a4849d → 0.3.11-main.604a65a
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 +2474 -1566
- 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/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
- 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 +9 -0
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
- 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 +5 -0
- package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/automerge/defs.d.ts +24 -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/extensions/automerge/semaphore.d.ts +15 -0
- 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.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/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/extensions/cursor.d.ts +25 -0
- package/dist/types/src/extensions/cursor.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +4 -7
- 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 +31 -0
- package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
- 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 +5 -1
- 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 -31
- 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/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 +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 -36
- 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/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +65 -6
- 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 +103 -0
- package/src/extensions/automerge/cursor.ts +52 -0
- package/src/extensions/automerge/defs.ts +41 -0
- package/src/extensions/automerge/index.ts +6 -0
- package/src/extensions/automerge/semaphore.ts +81 -0
- package/src/extensions/automerge/update-automerge.ts +49 -0
- package/src/{hooks → extensions}/automerge/update-codemirror.ts +17 -9
- package/src/extensions/awareness.ts +310 -0
- package/src/extensions/basic.ts +17 -10
- package/src/extensions/comments.ts +327 -256
- package/src/extensions/cursor.ts +50 -0
- package/src/extensions/index.ts +4 -7
- 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 +124 -0
- package/src/hooks/defs.ts +42 -0
- package/src/hooks/index.ts +6 -1
- 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 +33 -116
- package/src/index.ts +3 -0
- package/src/styles/index.ts +1 -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/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/util.d.ts +0 -5
- package/dist/types/src/extensions/util.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
- package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/index.d.ts +0 -3
- package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
- package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/semaphore.d.ts +0 -19
- package/dist/types/src/hooks/automerge/semaphore.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/update-automerge.d.ts +0 -6
- package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +0 -1
- package/dist/types/src/hooks/automerge/update-codemirror.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 +0 -27
- package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts +0 -12
- package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/yjs/yjs.test.d.ts +0 -2
- package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +0 -1
- package/dist/types/src/testing/index.d.ts +0 -3
- package/dist/types/src/testing/index.d.ts.map +0 -1
- package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
- package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
- package/dist/types/src/testing/proto/index.d.ts +0 -2
- package/dist/types/src/testing/proto/index.d.ts.map +0 -1
- package/dist/types/src/testing/replicator.d.ts +0 -45
- package/dist/types/src/testing/replicator.d.ts.map +0 -1
- package/src/components/TextEditor/codemirror.stories.ts +0 -114
- 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/util.ts +0 -18
- package/src/hooks/automerge/handle.ts +0 -14
- package/src/hooks/automerge/index.ts +0 -6
- package/src/hooks/automerge/plugin.ts +0 -132
- package/src/hooks/automerge/semaphore.ts +0 -78
- package/src/hooks/automerge/update-automerge.ts +0 -44
- package/src/hooks/yjs/index.ts +0 -6
- package/src/hooks/yjs/space-provider.ts +0 -126
- package/src/hooks/yjs/yjs.stories.tsx +0 -56
- package/src/hooks/yjs/yjs.test.ts +0 -59
- 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 -189
- /package/dist/types/src/{hooks → extensions}/automerge/update-codemirror.d.ts +0 -0
- /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
- /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
- /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
- /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
- /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
- /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
- /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
- /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
|
@@ -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?
|
|
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
|
};
|
|
@@ -2,19 +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
|
-
// TODO(burdon): Why separate imports?
|
|
13
14
|
import { type Prop } from '@dxos/automerge/automerge';
|
|
14
|
-
import { type DocHandle
|
|
15
|
+
import { Repo, type DocHandle } from '@dxos/automerge/automerge-repo';
|
|
16
|
+
import { Filter } 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';
|
|
15
22
|
|
|
16
|
-
import {
|
|
17
|
-
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.
|
|
18
28
|
|
|
19
29
|
type EditorProps = {
|
|
20
30
|
handle: IDocHandle;
|
|
@@ -28,7 +38,7 @@ const Editor = ({ handle, path }: EditorProps) => {
|
|
|
28
38
|
useEffect(() => {
|
|
29
39
|
const view = (editorRoot.current = new EditorView({
|
|
30
40
|
doc: get(handle.docSync()!, path),
|
|
31
|
-
extensions: [basicSetup,
|
|
41
|
+
extensions: [basicSetup, automerge({ handle, path }), awareness()],
|
|
32
42
|
parent: containerRef.current as any,
|
|
33
43
|
}));
|
|
34
44
|
|
|
@@ -89,3 +99,52 @@ export default {
|
|
|
89
99
|
};
|
|
90
100
|
|
|
91
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
|
+
return (
|
|
134
|
+
<ClientRepeater
|
|
135
|
+
count={2}
|
|
136
|
+
createSpace
|
|
137
|
+
onCreateSpace={async (space) => {
|
|
138
|
+
space.db.add(
|
|
139
|
+
new Expando({
|
|
140
|
+
type: 'test',
|
|
141
|
+
content: new TextObject('Hello world!'),
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
}}
|
|
145
|
+
Component={EchoStory}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
decorators: [withTheme],
|
|
150
|
+
};
|