@dxos/react-ui-editor 0.4.7-main.85d9ee7 → 0.4.7-main.b1ae49b
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 +563 -522
- 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 +3 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +0 -9
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/index.d.ts +0 -2
- package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/{components/TextEditor → extensions/automerge}/automerge.stories.d.ts +4 -2
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -0
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +1 -1
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -1
- package/dist/types/src/extensions/dnd.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +0 -1
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/listener.d.ts +1 -1
- package/dist/types/src/extensions/listener.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts +2 -2
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts +3 -0
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/mention.d.ts.map +1 -1
- package/dist/types/src/hooks/defs.d.ts +5 -11
- package/dist/types/src/hooks/defs.d.ts.map +1 -1
- package/dist/types/src/hooks/useEditorView.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts +20 -5
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/{components/TextEditor/hooks.stories.d.ts → hooks/useTextEditor.stories.d.ts} +1 -3
- package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/package.json +30 -30
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +25 -6
- package/src/components/TextEditor/TextEditor.stories.tsx +0 -15
- package/src/components/TextEditor/TextEditor.tsx +11 -6
- package/src/components/TextEditor/index.ts +0 -3
- package/src/components/Toolbar/Toolbar.stories.tsx +2 -1
- package/src/{components/TextEditor → extensions/automerge}/automerge.stories.tsx +32 -37
- package/src/extensions/command/command.ts +4 -7
- package/src/extensions/command/hint.ts +1 -0
- package/src/extensions/command/state.ts +2 -2
- package/src/extensions/comments.ts +2 -1
- package/src/extensions/cursor-line-margin.ts +5 -2
- package/src/extensions/dnd.ts +3 -1
- package/src/extensions/index.ts +0 -1
- package/src/extensions/listener.ts +3 -3
- package/src/extensions/markdown/bundle.ts +4 -14
- package/src/extensions/markdown/decorate.ts +21 -14
- package/src/extensions/mention.ts +3 -1
- package/src/hooks/defs.ts +6 -20
- package/src/hooks/useActionHandler.ts +11 -11
- package/src/hooks/useEditorView.ts +0 -1
- package/src/{components/TextEditor/hooks.stories.tsx → hooks/useTextEditor.stories.tsx} +8 -8
- package/src/hooks/useTextEditor.ts +69 -14
- package/src/hooks/useTextModel.ts +11 -16
- package/src/index.ts +2 -0
- package/src/themes/default.ts +1 -0
- package/src/util.ts +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/basic.d.ts +0 -9
- package/dist/types/src/extensions/basic.d.ts.map +0 -1
- package/src/extensions/basic.ts +0 -41
|
@@ -6,7 +6,7 @@ import { type Extension } from '@codemirror/state';
|
|
|
6
6
|
import { EditorView } from '@codemirror/view';
|
|
7
7
|
|
|
8
8
|
export type ListenerOptions = {
|
|
9
|
-
onFocus?: (
|
|
9
|
+
onFocus?: (focusing: boolean) => void;
|
|
10
10
|
onChange?: (text: string) => void;
|
|
11
11
|
};
|
|
12
12
|
|
|
@@ -18,8 +18,8 @@ export const listener = ({ onFocus, onChange }: ListenerOptions): Extension => {
|
|
|
18
18
|
|
|
19
19
|
onFocus &&
|
|
20
20
|
extensions.push(
|
|
21
|
-
EditorView.focusChangeEffect.of((_,
|
|
22
|
-
onFocus(
|
|
21
|
+
EditorView.focusChangeEffect.of((_, focusing) => {
|
|
22
|
+
onFocus(focusing);
|
|
23
23
|
return null;
|
|
24
24
|
}),
|
|
25
25
|
);
|
|
@@ -10,17 +10,9 @@ import { languages } from '@codemirror/language-data';
|
|
|
10
10
|
import { searchKeymap } from '@codemirror/search';
|
|
11
11
|
import { EditorState, type Extension } from '@codemirror/state';
|
|
12
12
|
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
13
|
-
import {
|
|
14
|
-
crosshairCursor,
|
|
15
|
-
drawSelection,
|
|
16
|
-
dropCursor,
|
|
17
|
-
EditorView,
|
|
18
|
-
highlightActiveLine,
|
|
19
|
-
keymap,
|
|
20
|
-
placeholder,
|
|
21
|
-
} from '@codemirror/view';
|
|
13
|
+
import { EditorView, drawSelection, dropCursor, keymap, placeholder } from '@codemirror/view';
|
|
22
14
|
|
|
23
|
-
import type
|
|
15
|
+
import { type ThemeMode } from '@dxos/react-ui';
|
|
24
16
|
import { isNotFalsy } from '@dxos/util';
|
|
25
17
|
|
|
26
18
|
import { markdownHighlightStyle, markdownTagsExtensions } from './highlight';
|
|
@@ -28,7 +20,7 @@ import { type TextEditorProps } from '../../components';
|
|
|
28
20
|
|
|
29
21
|
export type MarkdownBundleOptions = {
|
|
30
22
|
themeMode?: ThemeMode;
|
|
31
|
-
} & Pick<TextEditorProps, '
|
|
23
|
+
} & Pick<TextEditorProps, 'lineWrapping' | 'placeholder'>;
|
|
32
24
|
|
|
33
25
|
/**
|
|
34
26
|
* Markdown bundle.
|
|
@@ -42,17 +34,15 @@ export const createMarkdownExtensions = ({
|
|
|
42
34
|
lineWrapping = true,
|
|
43
35
|
}: MarkdownBundleOptions = {}): Extension[] => {
|
|
44
36
|
return [
|
|
37
|
+
// TODO(burdon): Reconcile with createBasicExtensions.
|
|
45
38
|
lineWrapping && EditorView.lineWrapping,
|
|
46
39
|
EditorState.allowMultipleSelections.of(true),
|
|
47
40
|
EditorState.tabSize.of(2),
|
|
48
41
|
|
|
49
|
-
// https://github.com/codemirror/basic-setup
|
|
50
42
|
bracketMatching(),
|
|
51
43
|
closeBrackets(),
|
|
52
|
-
crosshairCursor(),
|
|
53
44
|
dropCursor(),
|
|
54
45
|
drawSelection(),
|
|
55
|
-
highlightActiveLine(),
|
|
56
46
|
history(),
|
|
57
47
|
indentOnInput(),
|
|
58
48
|
_placeholder && placeholder(_placeholder),
|
|
@@ -88,7 +88,7 @@ const editingRange = (state: EditorState, range: { from: number; to: number }, f
|
|
|
88
88
|
main: { head },
|
|
89
89
|
},
|
|
90
90
|
} = state;
|
|
91
|
-
return focus && !readOnly && head >= range.from && head
|
|
91
|
+
return focus && !readOnly && head >= range.from && head <= range.to;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
const MarksByParent = new Set(['CodeMark', 'EmphasisMark', 'StrikethroughMark', 'SubscriptMark', 'SuperscriptMark']);
|
|
@@ -114,7 +114,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
|
114
114
|
break;
|
|
115
115
|
}
|
|
116
116
|
const first = block.from <= node.from;
|
|
117
|
-
const last = block.to >= node.to;
|
|
117
|
+
const last = block.to >= node.to && /^(\s>)*```$/.test(state.doc.sliceString(block.from, block.to));
|
|
118
118
|
deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
|
|
119
119
|
if (!editing && (first || last)) {
|
|
120
120
|
atomicDeco.add(block.from, block.to, hide);
|
|
@@ -186,6 +186,9 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
|
|
|
186
186
|
|
|
187
187
|
export interface DecorateOptions {
|
|
188
188
|
renderLinkButton?: (el: Element, url: string) => void;
|
|
189
|
+
/**
|
|
190
|
+
* Prevents triggering decorations as the cursor moves through the document.
|
|
191
|
+
*/
|
|
189
192
|
selectionChangeDelay?: number;
|
|
190
193
|
}
|
|
191
194
|
|
|
@@ -195,9 +198,9 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
|
|
|
195
198
|
return [
|
|
196
199
|
ViewPlugin.fromClass(
|
|
197
200
|
class {
|
|
198
|
-
pendingUpdate = -1;
|
|
199
201
|
deco: DecorationSet;
|
|
200
202
|
atomicDeco: DecorationSet;
|
|
203
|
+
pendingUpdate?: NodeJS.Timeout;
|
|
201
204
|
|
|
202
205
|
constructor(view: EditorView) {
|
|
203
206
|
({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations(view, options, view.hasFocus));
|
|
@@ -208,31 +211,35 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
|
|
|
208
211
|
update.docChanged ||
|
|
209
212
|
update.viewportChanged ||
|
|
210
213
|
update.focusChanged ||
|
|
211
|
-
update.transactions.some((tr) => tr.effects.some((e) => e.is(forceUpdate)))
|
|
214
|
+
update.transactions.some((tr) => tr.effects.some((e) => e.is(forceUpdate))) ||
|
|
215
|
+
(update.selectionSet && !options.selectionChangeDelay)
|
|
212
216
|
) {
|
|
213
217
|
({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations(
|
|
214
218
|
update.view,
|
|
215
219
|
options,
|
|
216
220
|
update.view.hasFocus,
|
|
217
221
|
));
|
|
222
|
+
|
|
218
223
|
this.clearUpdate();
|
|
219
224
|
} else if (update.selectionSet) {
|
|
220
225
|
this.scheduleUpdate(update.view);
|
|
221
226
|
}
|
|
222
227
|
}
|
|
223
228
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
window.clearTimeout(this.pendingUpdate);
|
|
227
|
-
this.pendingUpdate = -1;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
229
|
+
// TODO(burdon): BUG: If the cursor is at the end of a link at the end of a line,
|
|
230
|
+
// the cursor will float in space or be in the wrong position after the decoration is applied.
|
|
231
231
|
scheduleUpdate(view: EditorView) {
|
|
232
232
|
this.clearUpdate();
|
|
233
|
-
this.pendingUpdate =
|
|
233
|
+
this.pendingUpdate = setTimeout(() => {
|
|
234
234
|
view.dispatch({ effects: forceUpdate.of(null) });
|
|
235
|
-
}, options.selectionChangeDelay
|
|
235
|
+
}, options.selectionChangeDelay);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
clearUpdate() {
|
|
239
|
+
if (this.pendingUpdate) {
|
|
240
|
+
clearTimeout(this.pendingUpdate);
|
|
241
|
+
this.pendingUpdate = undefined;
|
|
242
|
+
}
|
|
236
243
|
}
|
|
237
244
|
|
|
238
245
|
destroy() {
|
|
@@ -242,8 +249,8 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
|
|
|
242
249
|
{
|
|
243
250
|
provide: (plugin) => [
|
|
244
251
|
EditorView.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration.none),
|
|
245
|
-
EditorView.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration.none),
|
|
246
252
|
EditorView.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration.none),
|
|
253
|
+
EditorView.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration.none),
|
|
247
254
|
],
|
|
248
255
|
},
|
|
249
256
|
),
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { autocompletion, type CompletionContext, type CompletionResult } from '@codemirror/autocomplete';
|
|
6
6
|
import type { Extension } from '@codemirror/state';
|
|
7
7
|
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
|
|
8
10
|
export type MentionOptions = {
|
|
9
11
|
onSearch: (text: string) => string[];
|
|
10
12
|
};
|
|
@@ -21,7 +23,7 @@ export const mention = ({ onSearch }: MentionOptions): Extension => {
|
|
|
21
23
|
icons: false,
|
|
22
24
|
override: [
|
|
23
25
|
(context: CompletionContext): CompletionResult | null => {
|
|
24
|
-
|
|
26
|
+
log.info('completion context', { context });
|
|
25
27
|
|
|
26
28
|
const match = context.matchBefore(/@(\w+)?/);
|
|
27
29
|
if (!match || (match.from === match.to && !context.explicit)) {
|
package/src/hooks/defs.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Extension
|
|
5
|
+
import { type Extension } from '@codemirror/state';
|
|
6
6
|
|
|
7
7
|
import { type DocAccessor } from '@dxos/echo-schema';
|
|
8
8
|
|
|
@@ -18,27 +18,13 @@ export type Comment = {
|
|
|
18
18
|
cursor?: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated
|
|
23
|
+
*/
|
|
24
|
+
// TODO(burdon): Remove.
|
|
21
25
|
export type EditorModel = {
|
|
22
26
|
id: string;
|
|
23
27
|
text: () => string;
|
|
24
|
-
extension?: Extension;
|
|
25
|
-
|
|
26
|
-
// TODO(burdon): Remove.
|
|
27
28
|
content: string | DocAccessor;
|
|
28
|
-
|
|
29
|
-
// TODO(burdon): Remove.
|
|
30
|
-
peer?: {
|
|
31
|
-
id: string;
|
|
32
|
-
name?: string;
|
|
33
|
-
};
|
|
29
|
+
extension?: Extension;
|
|
34
30
|
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* State field makes the model available to other extensions.
|
|
38
|
-
* @deprecated
|
|
39
|
-
*/
|
|
40
|
-
// TODO(burdon): Remove.
|
|
41
|
-
export const modelState = StateField.define<EditorModel | undefined>({
|
|
42
|
-
create: () => undefined,
|
|
43
|
-
update: (model) => model,
|
|
44
|
-
});
|
|
@@ -6,23 +6,23 @@ import type { EditorView } from '@codemirror/view';
|
|
|
6
6
|
|
|
7
7
|
import type { ToolbarProps } from '../components';
|
|
8
8
|
import {
|
|
9
|
-
createComment,
|
|
10
|
-
insertTable,
|
|
11
|
-
setHeading,
|
|
12
|
-
setStyle,
|
|
13
|
-
toggleStyle,
|
|
14
|
-
addLink,
|
|
15
|
-
removeLink,
|
|
16
9
|
Inline,
|
|
17
10
|
List,
|
|
18
|
-
addList,
|
|
19
|
-
removeList,
|
|
20
|
-
toggleList,
|
|
21
11
|
addBlockquote,
|
|
22
|
-
removeBlockquote,
|
|
23
12
|
addCodeblock,
|
|
13
|
+
addLink,
|
|
14
|
+
addList,
|
|
15
|
+
createComment,
|
|
16
|
+
insertTable,
|
|
17
|
+
removeBlockquote,
|
|
24
18
|
removeCodeblock,
|
|
19
|
+
removeLink,
|
|
20
|
+
removeList,
|
|
21
|
+
setHeading,
|
|
22
|
+
setStyle,
|
|
25
23
|
toggleBlockquote,
|
|
24
|
+
toggleList,
|
|
25
|
+
toggleStyle,
|
|
26
26
|
} from '../extensions';
|
|
27
27
|
|
|
28
28
|
export const useActionHandler = (view?: EditorView | null): ToolbarProps['onAction'] => {
|
|
@@ -21,7 +21,6 @@ import { type MutableRefObject, useRef, useState } from 'react';
|
|
|
21
21
|
* };
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
// TODO(burdon): Replace with useTextEditor.
|
|
25
24
|
export const useEditorView = (id: string): [MutableRefObject<EditorView | null>, boolean] => {
|
|
26
25
|
const editorRef = useRef<EditorView | null>(null);
|
|
27
26
|
const [prev, setPrev] = useState<[EditorView | null, string]>([null, '']);
|
|
@@ -10,7 +10,9 @@ import { Toolbar as NaturalToolbar, Select, useThemeContext, Tooltip } from '@dx
|
|
|
10
10
|
import { attentionSurface, mx, textBlockWidth } from '@dxos/react-ui-theme';
|
|
11
11
|
import { withTheme } from '@dxos/storybook-utils';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { useActionHandler } from './useActionHandler';
|
|
14
|
+
import { createBasicExtensions, createThemeExtensions, useTextEditor } from './useTextEditor';
|
|
15
|
+
import { Toolbar } from '../components';
|
|
14
16
|
import {
|
|
15
17
|
type EditorMode,
|
|
16
18
|
EditorModes,
|
|
@@ -20,11 +22,9 @@ import {
|
|
|
20
22
|
image,
|
|
21
23
|
table,
|
|
22
24
|
useFormattingState,
|
|
23
|
-
} from '
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
26
|
-
import translations from '../../translations';
|
|
27
|
-
import { Toolbar } from '../Toolbar';
|
|
25
|
+
} from '../extensions';
|
|
26
|
+
import { markdownTheme } from '../themes';
|
|
27
|
+
import translations from '../translations';
|
|
28
28
|
|
|
29
29
|
// TODO(burdon): Demo toolbar with hooks.
|
|
30
30
|
// TODO(burdon): Build components from hooks and adapters for model/extensions, etc.
|
|
@@ -46,7 +46,7 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
|
|
|
46
46
|
const extensions = useMemo(
|
|
47
47
|
() => [
|
|
48
48
|
editorMode ? EditorModes[editorMode] : [],
|
|
49
|
-
|
|
49
|
+
createBasicExtensions({ readonly }),
|
|
50
50
|
createThemeExtensions({
|
|
51
51
|
themeMode,
|
|
52
52
|
theme: markdownTheme,
|
|
@@ -116,7 +116,7 @@ const EditorModeToolbar = ({
|
|
|
116
116
|
|
|
117
117
|
export default {
|
|
118
118
|
title: 'react-ui-editor/useTextEditor',
|
|
119
|
-
component: TextEditor,
|
|
119
|
+
// component: TextEditor,
|
|
120
120
|
decorators: [withTheme],
|
|
121
121
|
render: (args: StoryProps) => (
|
|
122
122
|
<Tooltip.Provider>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { closeBrackets } from '@codemirror/autocomplete';
|
|
6
|
+
import { history } from '@codemirror/commands';
|
|
7
|
+
import { bracketMatching } from '@codemirror/language';
|
|
5
8
|
import {
|
|
6
9
|
EditorSelection,
|
|
7
10
|
EditorState,
|
|
@@ -9,7 +12,17 @@ import {
|
|
|
9
12
|
type Extension,
|
|
10
13
|
type StateEffect,
|
|
11
14
|
} from '@codemirror/state';
|
|
12
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
crosshairCursor,
|
|
17
|
+
drawSelection,
|
|
18
|
+
dropCursor,
|
|
19
|
+
EditorView,
|
|
20
|
+
highlightActiveLine,
|
|
21
|
+
lineNumbers,
|
|
22
|
+
placeholder,
|
|
23
|
+
scrollPastEnd,
|
|
24
|
+
} from '@codemirror/view';
|
|
25
|
+
import defaultsDeep from 'lodash.defaultsdeep';
|
|
13
26
|
import { type DependencyList, type RefObject, useEffect, useRef, useState } from 'react';
|
|
14
27
|
|
|
15
28
|
import { log } from '@dxos/log';
|
|
@@ -38,7 +51,7 @@ export type UseTextEditor = {
|
|
|
38
51
|
// TODO(wittjosiah): Does not work in strict mode.
|
|
39
52
|
export const useTextEditor = (
|
|
40
53
|
{ autoFocus, scrollTo, debug, doc, selection, extensions }: UseTextEditorOptions = {},
|
|
41
|
-
deps
|
|
54
|
+
deps: DependencyList = [],
|
|
42
55
|
): UseTextEditor => {
|
|
43
56
|
const onUpdate = useRef<() => void>();
|
|
44
57
|
const [view, setView] = useState<EditorView>();
|
|
@@ -114,23 +127,66 @@ export const useTextEditor = (
|
|
|
114
127
|
|
|
115
128
|
// TODO(burdon): Factor out extension factories.
|
|
116
129
|
|
|
117
|
-
|
|
130
|
+
/**
|
|
131
|
+
* https://codemirror.net/docs/extensions
|
|
132
|
+
* https://github.com/codemirror/basic-setup
|
|
133
|
+
*/
|
|
134
|
+
// TODO(burdon): Reconcile with createMarkdownExtensions.
|
|
135
|
+
export type BasicExtensionsOptions = {
|
|
136
|
+
allowMultipleSelections?: boolean;
|
|
137
|
+
bracketMatching?: boolean;
|
|
138
|
+
closeBrackets?: boolean;
|
|
139
|
+
crosshairCursor?: boolean;
|
|
140
|
+
dropCursor?: boolean;
|
|
141
|
+
drawSelection?: boolean;
|
|
142
|
+
editable?: boolean;
|
|
143
|
+
highlightActiveLine?: boolean;
|
|
144
|
+
history?: boolean;
|
|
145
|
+
lineNumbers?: boolean;
|
|
146
|
+
lineWrapping?: boolean;
|
|
147
|
+
placeholder?: string;
|
|
118
148
|
readonly?: boolean;
|
|
149
|
+
scrollPastEnd?: boolean;
|
|
150
|
+
tabSize?: number;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const defaults: BasicExtensionsOptions = {
|
|
154
|
+
bracketMatching: true,
|
|
155
|
+
closeBrackets: true,
|
|
156
|
+
drawSelection: true,
|
|
157
|
+
editable: true,
|
|
158
|
+
history: true,
|
|
159
|
+
lineWrapping: true,
|
|
119
160
|
};
|
|
120
161
|
|
|
121
|
-
|
|
122
|
-
|
|
162
|
+
export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
|
|
163
|
+
const props: BasicExtensionsOptions = defaultsDeep({}, _props, defaults);
|
|
123
164
|
return [
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
165
|
+
// TODO(burdon): Doesn't catch errors in keymap functions.
|
|
166
|
+
EditorView.exceptionSink.of((err) => {
|
|
167
|
+
log.catch(err);
|
|
168
|
+
}),
|
|
169
|
+
|
|
170
|
+
props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
|
|
171
|
+
props.bracketMatching && bracketMatching(),
|
|
172
|
+
props.closeBrackets && closeBrackets(),
|
|
173
|
+
props.crosshairCursor && crosshairCursor(),
|
|
174
|
+
props.dropCursor && dropCursor(),
|
|
175
|
+
props.drawSelection && drawSelection(),
|
|
176
|
+
props.highlightActiveLine && highlightActiveLine(),
|
|
177
|
+
props.history && history(),
|
|
178
|
+
props.lineNumbers && lineNumbers(),
|
|
179
|
+
props.lineWrapping && EditorView.lineWrapping,
|
|
180
|
+
props.placeholder && placeholder(props.placeholder),
|
|
181
|
+
props.readonly && [EditorState.readOnly.of(true), EditorView.editable.of(false)],
|
|
182
|
+
props.scrollPastEnd && scrollPastEnd(),
|
|
183
|
+
props.tabSize && EditorState.tabSize.of(props.tabSize),
|
|
184
|
+
].filter(isNotFalsy);
|
|
128
185
|
};
|
|
129
186
|
|
|
130
187
|
export type ThemeExtensionsOptions = {
|
|
131
188
|
theme?: ThemeStyles;
|
|
132
189
|
themeMode?: ThemeMode;
|
|
133
|
-
lineWrap?: boolean;
|
|
134
190
|
slots?: {
|
|
135
191
|
editor?: {
|
|
136
192
|
className?: string;
|
|
@@ -143,11 +199,10 @@ export type ThemeExtensionsOptions = {
|
|
|
143
199
|
|
|
144
200
|
export const createThemeExtensions = ({ theme, themeMode, slots }: ThemeExtensionsOptions = {}): Extension => {
|
|
145
201
|
return [
|
|
146
|
-
//
|
|
147
202
|
EditorView.baseTheme(defaultTheme),
|
|
148
|
-
theme && EditorView.theme(theme),
|
|
149
203
|
EditorView.darkTheme.of(themeMode === 'dark'),
|
|
150
|
-
EditorView.
|
|
151
|
-
EditorView.
|
|
204
|
+
theme && EditorView.theme(theme),
|
|
205
|
+
slots?.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
|
|
206
|
+
slots?.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
|
|
152
207
|
].filter(isNotFalsy);
|
|
153
208
|
};
|
|
@@ -11,11 +11,11 @@ import { type AutomergeTextCompat, getRawDoc } from '@dxos/echo-schema';
|
|
|
11
11
|
import { invariant } from '@dxos/invariant';
|
|
12
12
|
import { isAutomergeObject, type Space, type TextObject } from '@dxos/react-client/echo';
|
|
13
13
|
import { type Identity } from '@dxos/react-client/halo';
|
|
14
|
-
import { hueTokens } from '@dxos/react-ui-theme';
|
|
14
|
+
import { type HuePalette, hueTokens } from '@dxos/react-ui-theme';
|
|
15
15
|
import { hexToHue } from '@dxos/util';
|
|
16
16
|
|
|
17
17
|
import { SpaceAwarenessProvider } from './awareness-provider';
|
|
18
|
-
import { type EditorModel
|
|
18
|
+
import { type EditorModel } from './defs';
|
|
19
19
|
import { automerge, awareness } from '../extensions';
|
|
20
20
|
import { type DocAccessor } from '../extensions/automerge/defs';
|
|
21
21
|
|
|
@@ -31,7 +31,8 @@ export const useTextExtensions = ({ id, text, space, identity }: useTextExtensio
|
|
|
31
31
|
const extensions: Extension[] = [automerge(text)];
|
|
32
32
|
|
|
33
33
|
const peerId = identity?.identityKey.toHex();
|
|
34
|
-
const { cursorLightValue, cursorDarkValue } =
|
|
34
|
+
const { cursorLightValue, cursorDarkValue } =
|
|
35
|
+
hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
|
|
35
36
|
|
|
36
37
|
if (space && identity) {
|
|
37
38
|
const awarenessProvider = new SpaceAwarenessProvider({
|
|
@@ -68,6 +69,7 @@ export const useTextModel = (props: UseTextModelProps): EditorModel | undefined
|
|
|
68
69
|
* For use primarily in stories & tests so the dependence on TextObject can be avoided.
|
|
69
70
|
* @deprecated
|
|
70
71
|
*/
|
|
72
|
+
// TODO(burdon): Remove.
|
|
71
73
|
export const useInMemoryTextModel = ({
|
|
72
74
|
id,
|
|
73
75
|
defaultContent,
|
|
@@ -76,10 +78,10 @@ export const useInMemoryTextModel = ({
|
|
|
76
78
|
defaultContent?: string;
|
|
77
79
|
}): EditorModel & { setContent: Dispatch<SetStateAction<string>> } => {
|
|
78
80
|
const [content, setContent] = useState(defaultContent ?? '');
|
|
79
|
-
return { id, content,
|
|
81
|
+
return { id, content, text: () => content, setContent };
|
|
80
82
|
};
|
|
81
83
|
|
|
82
|
-
const createModel = ({ space, identity, text }: UseTextModelProps) => {
|
|
84
|
+
const createModel = ({ space, identity, text }: UseTextModelProps): EditorModel | undefined => {
|
|
83
85
|
if (!text) {
|
|
84
86
|
return undefined;
|
|
85
87
|
}
|
|
@@ -89,7 +91,8 @@ const createModel = ({ space, identity, text }: UseTextModelProps) => {
|
|
|
89
91
|
const doc = getRawDoc(obj, [obj.field]);
|
|
90
92
|
|
|
91
93
|
const peerId = identity?.identityKey.toHex();
|
|
92
|
-
const { cursorLightValue, cursorDarkValue } =
|
|
94
|
+
const { cursorLightValue, cursorDarkValue } =
|
|
95
|
+
hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
|
|
93
96
|
|
|
94
97
|
const awarenessProvider =
|
|
95
98
|
space &&
|
|
@@ -104,23 +107,15 @@ const createModel = ({ space, identity, text }: UseTextModelProps) => {
|
|
|
104
107
|
peerId: identity?.identityKey.toHex() ?? 'Anonymous',
|
|
105
108
|
});
|
|
106
109
|
|
|
107
|
-
const extensions = [
|
|
110
|
+
const extensions = [automerge({ handle: doc.handle, path: doc.path })];
|
|
108
111
|
if (awarenessProvider) {
|
|
109
112
|
extensions.push(awareness(awarenessProvider));
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
return {
|
|
113
116
|
id: obj.id,
|
|
114
117
|
content: doc,
|
|
115
118
|
text: () => get(doc.handle.docSync(), doc.path),
|
|
116
119
|
extension: extensions,
|
|
117
|
-
peer: identity
|
|
118
|
-
? {
|
|
119
|
-
id: identity.identityKey.toHex(),
|
|
120
|
-
name: identity.profile?.displayName,
|
|
121
|
-
}
|
|
122
|
-
: undefined,
|
|
123
120
|
};
|
|
124
|
-
|
|
125
|
-
return model;
|
|
126
121
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
|
+
|
|
4
5
|
import translations from './translations';
|
|
5
6
|
|
|
6
7
|
export { type Extension, type EditorState } from '@codemirror/state';
|
|
7
8
|
export { type EditorView, keymap } from '@codemirror/view';
|
|
9
|
+
export { tags } from '@lezer/highlight';
|
|
8
10
|
|
|
9
11
|
export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
|
|
10
12
|
export { Doc, YText, YXmlFragment } from '@dxos/text-model';
|
package/src/themes/default.ts
CHANGED
package/src/util.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"automerge.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/TextEditor/automerge.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;AAGpB,OAAO,uBAAuB,CAAC;AAc/B,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAW9D,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DF,wBAKE;AAEF,eAAO,MAAM,OAAO,IAAK,CAAC;AAoB1B,eAAO,MAAM,QAAQ;;;CAmBpB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.stories.d.ts","sourceRoot":"","sources":["../../../../../src/components/TextEditor/hooks.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;AAEpB,OAAO,KAA4B,MAAM,OAAO,CAAC;AA4BjD,KAAK,UAAU,GAAG;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;;;;;mBAiFe,UAAU;;;;;;;;;;;;;;;;;;;;;;;;AAJ3B,wBAUE;AAEF,eAAO,MAAM,OAAO;;;;;;CAMnB,CAAC"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type Extension } from '@codemirror/state';
|
|
2
|
-
import { type ThemeMode } from '@dxos/react-ui';
|
|
3
|
-
import { type TextEditorProps } from '../components';
|
|
4
|
-
export type BasicBundleOptions = {
|
|
5
|
-
themeMode?: ThemeMode;
|
|
6
|
-
bracketMatching?: boolean;
|
|
7
|
-
} & Pick<TextEditorProps, 'placeholder' | 'lineWrapping'>;
|
|
8
|
-
export declare const createBasicBundle: ({ themeMode, placeholder: _placeholder, lineWrapping, bracketMatching: _bracketMatching, }?: BasicBundleOptions) => Extension[];
|
|
9
|
-
//# sourceMappingURL=basic.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["../../../../src/extensions/basic.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,IAAI,CAAC,eAAe,EAAE,aAAa,GAAG,cAAc,CAAC,CAAC;AAE1D,eAAO,MAAM,iBAAiB,gGAK3B,kBAAkB,KAAQ,SAAS,EAchB,CAAC"}
|
package/src/extensions/basic.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { closeBrackets } from '@codemirror/autocomplete';
|
|
6
|
-
import { history } from '@codemirror/commands';
|
|
7
|
-
import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
8
|
-
import { type Extension } from '@codemirror/state';
|
|
9
|
-
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
10
|
-
import { drawSelection, EditorView, highlightActiveLine, placeholder } from '@codemirror/view';
|
|
11
|
-
|
|
12
|
-
import { type ThemeMode } from '@dxos/react-ui';
|
|
13
|
-
import { isNotFalsy } from '@dxos/util';
|
|
14
|
-
|
|
15
|
-
import { type TextEditorProps } from '../components';
|
|
16
|
-
|
|
17
|
-
export type BasicBundleOptions = {
|
|
18
|
-
themeMode?: ThemeMode;
|
|
19
|
-
bracketMatching?: boolean;
|
|
20
|
-
} & Pick<TextEditorProps, 'placeholder' | 'lineWrapping'>;
|
|
21
|
-
|
|
22
|
-
export const createBasicBundle = ({
|
|
23
|
-
themeMode,
|
|
24
|
-
placeholder: _placeholder,
|
|
25
|
-
lineWrapping = true,
|
|
26
|
-
bracketMatching: _bracketMatching = true,
|
|
27
|
-
}: BasicBundleOptions = {}): Extension[] =>
|
|
28
|
-
[
|
|
29
|
-
lineWrapping && EditorView.lineWrapping,
|
|
30
|
-
|
|
31
|
-
// https://codemirror.net/docs/ref/#codemirror.minimalSetup
|
|
32
|
-
_bracketMatching && bracketMatching(),
|
|
33
|
-
closeBrackets(),
|
|
34
|
-
drawSelection(),
|
|
35
|
-
highlightActiveLine(),
|
|
36
|
-
history(),
|
|
37
|
-
_placeholder && placeholder(_placeholder),
|
|
38
|
-
|
|
39
|
-
// https://github.com/codemirror/theme-one-dark
|
|
40
|
-
themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle),
|
|
41
|
-
].filter(isNotFalsy);
|