@dxos/react-ui-editor 0.4.8-next.fff1521 → 0.4.8
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 +1453 -1449
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +1 -6
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/extensions/dnd.d.ts +7 -1
- package/dist/types/src/extensions/dnd.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +5 -1
- package/dist/types/src/extensions/factories.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/markdown/action.d.ts +9 -0
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/modes.d.ts +1 -1
- package/dist/types/src/extensions/modes.d.ts.map +1 -1
- package/dist/types/src/hooks/useActionHandler.d.ts +2 -2
- package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useDocAccessor.d.ts +4 -2
- package/dist/types/src/hooks/useDocAccessor.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/package.json +25 -24
- package/src/components/TextEditor/TextEditor.stories.tsx +14 -2
- package/src/components/Toolbar/Toolbar.tsx +8 -27
- package/src/extensions/automerge/automerge.ts +4 -4
- package/src/extensions/command/state.ts +2 -2
- package/src/extensions/dnd.ts +31 -9
- package/src/extensions/factories.ts +41 -15
- package/src/extensions/index.ts +0 -1
- package/src/extensions/markdown/action.ts +112 -0
- package/src/extensions/markdown/bundle.ts +8 -11
- package/src/extensions/markdown/formatting.ts +11 -11
- package/src/extensions/markdown/index.ts +1 -0
- package/src/extensions/modes.ts +8 -1
- package/src/hooks/useActionHandler.ts +4 -89
- package/src/hooks/useDocAccessor.ts +8 -2
- package/src/themes/default.ts +24 -8
- package/dist/types/src/extensions/cursor-line-margin.d.ts +0 -7
- package/dist/types/src/extensions/cursor-line-margin.d.ts.map +0 -1
- package/src/extensions/cursor-line-margin.ts +0 -94
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-editor",
|
|
3
|
-
"version": "0.4.8
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Document editing experience within a DXOS shell.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@lezer/markdown": "^1.2.0",
|
|
31
31
|
"@radix-ui/react-context": "^1.0.0",
|
|
32
32
|
"@replit/codemirror-vim": "^6.0.14",
|
|
33
|
+
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
|
33
34
|
"codemirror": "^6.0.1",
|
|
34
35
|
"lib0": "^0.2.65",
|
|
35
36
|
"lodash.defaultsdeep": "^4.6.1",
|
|
@@ -38,20 +39,20 @@
|
|
|
38
39
|
"lodash.sortby": "^4.7.0",
|
|
39
40
|
"react-dropzone": "^14.2.3",
|
|
40
41
|
"style-mod": "^4.1.0",
|
|
41
|
-
"@dxos/async": "0.4.8
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/display-name": "0.4.8
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/echo-schema": "0.4.8
|
|
47
|
-
"@dxos/invariant": "0.4.8
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/react-
|
|
51
|
-
"@dxos/react-ui
|
|
52
|
-
"@dxos/text-model": "0.4.8
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
42
|
+
"@dxos/async": "0.4.8",
|
|
43
|
+
"@dxos/automerge": "0.4.8",
|
|
44
|
+
"@dxos/context": "0.4.8",
|
|
45
|
+
"@dxos/display-name": "0.4.8",
|
|
46
|
+
"@dxos/debug": "0.4.8",
|
|
47
|
+
"@dxos/echo-schema": "0.4.8",
|
|
48
|
+
"@dxos/invariant": "0.4.8",
|
|
49
|
+
"@dxos/log": "0.4.8",
|
|
50
|
+
"@dxos/protocols": "0.4.8",
|
|
51
|
+
"@dxos/react-async": "0.4.8",
|
|
52
|
+
"@dxos/react-ui": "0.4.8",
|
|
53
|
+
"@dxos/text-model": "0.4.8",
|
|
54
|
+
"@dxos/util": "0.4.8",
|
|
55
|
+
"@dxos/react-ui-theme": "0.4.8"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@automerge/automerge-repo-network-broadcastchannel": "^1.1.1",
|
|
@@ -78,20 +79,20 @@
|
|
|
78
79
|
"vite-plugin-top-level-await": "^1.4.1",
|
|
79
80
|
"vite-plugin-wasm": "^3.3.0",
|
|
80
81
|
"vitest": "^1.3.1",
|
|
81
|
-
"@dxos/config": "0.4.8
|
|
82
|
-
"@dxos/echo-signals": "0.4.8
|
|
83
|
-
"@dxos/echo-typegen": "0.4.8
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/react-
|
|
87
|
-
"@dxos/
|
|
88
|
-
"@dxos/
|
|
82
|
+
"@dxos/config": "0.4.8",
|
|
83
|
+
"@dxos/echo-signals": "0.4.8",
|
|
84
|
+
"@dxos/echo-typegen": "0.4.8",
|
|
85
|
+
"@dxos/keyboard": "0.4.8",
|
|
86
|
+
"@dxos/random": "0.4.8",
|
|
87
|
+
"@dxos/react-client": "0.4.8",
|
|
88
|
+
"@dxos/react-ui": "0.4.8",
|
|
89
|
+
"@dxos/storybook-utils": "0.4.8"
|
|
89
90
|
},
|
|
90
91
|
"peerDependencies": {
|
|
91
92
|
"@phosphor-icons/react": "^2.0.5",
|
|
92
93
|
"react": "^18.2.0",
|
|
93
94
|
"react-dom": "^18.2.0",
|
|
94
|
-
"@dxos/react-client": "0.4.8
|
|
95
|
+
"@dxos/react-client": "0.4.8"
|
|
95
96
|
},
|
|
96
97
|
"publishConfig": {
|
|
97
98
|
"access": "public"
|
|
@@ -13,6 +13,7 @@ import { createRoot } from 'react-dom/client';
|
|
|
13
13
|
import { TextObject } from '@dxos/echo-schema';
|
|
14
14
|
import { keySymbols, parseShortcut } from '@dxos/keyboard';
|
|
15
15
|
import { PublicKey } from '@dxos/keys';
|
|
16
|
+
import { log } from '@dxos/log';
|
|
16
17
|
import { faker } from '@dxos/random';
|
|
17
18
|
import { Button, DensityProvider, Input, ThemeProvider, useThemeContext } from '@dxos/react-ui';
|
|
18
19
|
import { baseSurface, defaultTx, getSize, mx, textBlockWidth } from '@dxos/react-ui-theme';
|
|
@@ -32,7 +33,7 @@ import {
|
|
|
32
33
|
createThemeExtensions,
|
|
33
34
|
decorateMarkdown,
|
|
34
35
|
defaultOptions,
|
|
35
|
-
|
|
36
|
+
dropFile,
|
|
36
37
|
formattingKeymap,
|
|
37
38
|
image,
|
|
38
39
|
linkTooltip,
|
|
@@ -501,7 +502,18 @@ export const Annotations = {
|
|
|
501
502
|
};
|
|
502
503
|
|
|
503
504
|
export const DND = {
|
|
504
|
-
render: () =>
|
|
505
|
+
render: () => (
|
|
506
|
+
<Story
|
|
507
|
+
text={str('# DND', '')}
|
|
508
|
+
extensions={[
|
|
509
|
+
dropFile({
|
|
510
|
+
onDrop: (view, event) => {
|
|
511
|
+
log.info('drop', event);
|
|
512
|
+
},
|
|
513
|
+
}),
|
|
514
|
+
]}
|
|
515
|
+
/>
|
|
516
|
+
),
|
|
505
517
|
};
|
|
506
518
|
|
|
507
519
|
const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
} from '@dxos/react-ui';
|
|
43
43
|
import { getSize } from '@dxos/react-ui-theme';
|
|
44
44
|
|
|
45
|
-
import { type Formatting } from '../../extensions';
|
|
45
|
+
import { type Action, type ActionType, type Formatting } from '../../extensions';
|
|
46
46
|
import { translationKey } from '../../translations';
|
|
47
47
|
|
|
48
48
|
const tooltipProps = { side: 'top' as const, classNames: 'z-10' };
|
|
@@ -57,30 +57,6 @@ const HeadingIcons: { [key: string]: Icon } = {
|
|
|
57
57
|
'6': TextHSix,
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
// TODO(burdon): Revert to string to make extensible?
|
|
61
|
-
export type ActionType =
|
|
62
|
-
| 'blockquote'
|
|
63
|
-
| 'strong'
|
|
64
|
-
| 'codeblock'
|
|
65
|
-
| 'comment'
|
|
66
|
-
| 'heading'
|
|
67
|
-
| 'image'
|
|
68
|
-
| 'emphasis'
|
|
69
|
-
| 'code'
|
|
70
|
-
| 'link'
|
|
71
|
-
| 'list-bullet'
|
|
72
|
-
| 'list-ordered'
|
|
73
|
-
| 'list-task'
|
|
74
|
-
| 'mention'
|
|
75
|
-
| 'prompt'
|
|
76
|
-
| 'strikethrough'
|
|
77
|
-
| 'table';
|
|
78
|
-
|
|
79
|
-
export type Action = {
|
|
80
|
-
type: ActionType;
|
|
81
|
-
data?: any;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
60
|
export type ToolbarProps = ThemedClassName<
|
|
85
61
|
PropsWithChildren<{
|
|
86
62
|
state: Formatting | undefined;
|
|
@@ -358,7 +334,7 @@ const MarkdownCustom = ({ onUpload }: MarkdownCustomOptions = {}) => {
|
|
|
358
334
|
|
|
359
335
|
useEffect(() => {
|
|
360
336
|
if (onUpload && acceptedFiles.length) {
|
|
361
|
-
|
|
337
|
+
requestAnimationFrame(async () => {
|
|
362
338
|
// NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
|
|
363
339
|
const f = acceptedFiles[0];
|
|
364
340
|
const file = new File([f], f.name, {
|
|
@@ -394,7 +370,12 @@ const MarkdownActions = () => {
|
|
|
394
370
|
{/* <ToolbarButton value='comment' Icon={BookOpenText} onClick={() => onAction?.({ type: 'comment' })}> */}
|
|
395
371
|
{/* {t('comment label')} */}
|
|
396
372
|
{/* </ToolbarButton> */}
|
|
397
|
-
<ToolbarButton
|
|
373
|
+
<ToolbarButton
|
|
374
|
+
value='comment'
|
|
375
|
+
Icon={ChatText}
|
|
376
|
+
data-testid='editor.toolbar.comment'
|
|
377
|
+
onClick={() => onAction?.({ type: 'comment' })}
|
|
378
|
+
>
|
|
398
379
|
{t('comment label')}
|
|
399
380
|
</ToolbarButton>
|
|
400
381
|
</>
|
|
@@ -60,16 +60,16 @@ export const automerge = (accessor: DocAccessor): Extension => {
|
|
|
60
60
|
ViewPlugin.fromClass(
|
|
61
61
|
class {
|
|
62
62
|
constructor(private readonly _view: EditorView) {
|
|
63
|
-
accessor.handle.addListener('change', this._handleChange
|
|
63
|
+
accessor.handle.addListener('change', this._handleChange);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
destroy() {
|
|
67
|
-
accessor.handle.removeListener('change', this._handleChange
|
|
67
|
+
accessor.handle.removeListener('change', this._handleChange);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
_handleChange() {
|
|
70
|
+
readonly _handleChange = () => {
|
|
71
71
|
syncer.reconcile(this._view, false);
|
|
72
|
-
}
|
|
72
|
+
};
|
|
73
73
|
},
|
|
74
74
|
),
|
|
75
75
|
|
|
@@ -59,8 +59,8 @@ export const commandState = StateField.define<CommandState>({
|
|
|
59
59
|
selection: { anchor: pos + action.insert.length },
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
//
|
|
63
|
-
|
|
62
|
+
// NOTE: Truncates text if set focus immediately.
|
|
63
|
+
requestAnimationFrame(() => view.focus());
|
|
64
64
|
});
|
|
65
65
|
},
|
|
66
66
|
};
|
package/src/extensions/dnd.ts
CHANGED
|
@@ -3,15 +3,37 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import type { Extension } from '@codemirror/state';
|
|
6
|
-
import { EditorView } from '@codemirror/view';
|
|
6
|
+
import { dropCursor, EditorView } from '@codemirror/view';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { getToken } from '../styles';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
export type DNDOptions = { onDrop?: (view: EditorView, event: { files: FileList }) => void };
|
|
11
|
+
|
|
12
|
+
const styles = EditorView.baseTheme({
|
|
13
|
+
'.cm-dropCursor': {
|
|
14
|
+
borderLeft: `2px solid ${getToken('extend.colors.primary.500')}`,
|
|
15
|
+
color: getToken('extend.colors.primary.500'),
|
|
16
|
+
padding: '0 4px',
|
|
17
|
+
},
|
|
18
|
+
'.cm-dropCursor:after': {
|
|
19
|
+
content: '"←"',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const dropFile = (options: DNDOptions = {}): Extension => {
|
|
24
|
+
return [
|
|
25
|
+
styles,
|
|
26
|
+
dropCursor(),
|
|
27
|
+
EditorView.domEventHandlers({
|
|
28
|
+
drop: (event, view) => {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
const files = event.dataTransfer?.files;
|
|
31
|
+
const pos = view.posAtCoords(event);
|
|
32
|
+
if (files?.length && pos !== null) {
|
|
33
|
+
view.dispatch({ selection: { anchor: pos } });
|
|
34
|
+
options.onDrop?.(view, { files });
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
17
39
|
};
|
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { closeBrackets } from '@codemirror/autocomplete';
|
|
6
|
-
import { history } from '@codemirror/commands';
|
|
5
|
+
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
|
6
|
+
import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap } from '@codemirror/commands';
|
|
7
7
|
import { bracketMatching } from '@codemirror/language';
|
|
8
|
+
import { searchKeymap } from '@codemirror/search';
|
|
8
9
|
import { EditorState, type Extension } from '@codemirror/state';
|
|
9
10
|
import {
|
|
10
11
|
EditorView,
|
|
11
|
-
|
|
12
|
+
type KeyBinding,
|
|
12
13
|
drawSelection,
|
|
13
14
|
dropCursor,
|
|
14
15
|
highlightActiveLine,
|
|
16
|
+
keymap,
|
|
15
17
|
lineNumbers,
|
|
16
18
|
placeholder,
|
|
17
19
|
scrollPastEnd,
|
|
@@ -32,8 +34,6 @@ import { awareness, SpaceAwarenessProvider } from './awareness';
|
|
|
32
34
|
import { type ThemeStyles } from '../styles';
|
|
33
35
|
import { defaultTheme } from '../themes';
|
|
34
36
|
|
|
35
|
-
// TODO(burdon): Move into extensions folder.
|
|
36
|
-
|
|
37
37
|
//
|
|
38
38
|
// Basic
|
|
39
39
|
//
|
|
@@ -41,39 +41,52 @@ import { defaultTheme } from '../themes';
|
|
|
41
41
|
/**
|
|
42
42
|
* https://codemirror.net/docs/extensions
|
|
43
43
|
* https://github.com/codemirror/basic-setup
|
|
44
|
+
* https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
|
|
44
45
|
*/
|
|
45
|
-
// TODO(burdon): Reconcile with createMarkdownExtensions.
|
|
46
46
|
export type BasicExtensionsOptions = {
|
|
47
47
|
allowMultipleSelections?: boolean;
|
|
48
48
|
bracketMatching?: boolean;
|
|
49
49
|
closeBrackets?: boolean;
|
|
50
|
-
crosshairCursor?: boolean;
|
|
51
50
|
dropCursor?: boolean;
|
|
52
51
|
drawSelection?: boolean;
|
|
53
52
|
editable?: boolean;
|
|
54
53
|
highlightActiveLine?: boolean;
|
|
55
54
|
history?: boolean;
|
|
55
|
+
indentWithTab?: boolean;
|
|
56
|
+
keymap?: null | 'default' | 'standard';
|
|
56
57
|
lineNumbers?: boolean;
|
|
57
58
|
lineWrapping?: boolean;
|
|
58
59
|
placeholder?: string;
|
|
59
60
|
readonly?: boolean;
|
|
61
|
+
search?: boolean;
|
|
60
62
|
scrollPastEnd?: boolean;
|
|
63
|
+
standardKeymap?: boolean;
|
|
61
64
|
tabSize?: number;
|
|
62
65
|
};
|
|
63
66
|
|
|
64
|
-
const
|
|
67
|
+
const defaultBasicOptions: BasicExtensionsOptions = {
|
|
68
|
+
allowMultipleSelections: true,
|
|
65
69
|
bracketMatching: true,
|
|
66
70
|
closeBrackets: true,
|
|
67
71
|
drawSelection: true,
|
|
68
72
|
editable: true,
|
|
69
73
|
history: true,
|
|
74
|
+
keymap: 'standard',
|
|
70
75
|
lineWrapping: true,
|
|
76
|
+
search: true,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const keymaps: { [key: string]: readonly KeyBinding[] } = {
|
|
80
|
+
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
81
|
+
standard: standardKeymap,
|
|
82
|
+
// https://codemirror.net/docs/ref/#commands.defaultKeymap
|
|
83
|
+
default: defaultKeymap,
|
|
71
84
|
};
|
|
72
85
|
|
|
73
86
|
export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
|
|
74
|
-
const props: BasicExtensionsOptions = defaultsDeep({}, _props,
|
|
87
|
+
const props: BasicExtensionsOptions = defaultsDeep({}, _props, defaultBasicOptions);
|
|
75
88
|
return [
|
|
76
|
-
//
|
|
89
|
+
// NOTE: Doesn't catch errors in keymap functions.
|
|
77
90
|
EditorView.exceptionSink.of((err) => {
|
|
78
91
|
log.catch(err);
|
|
79
92
|
}),
|
|
@@ -81,7 +94,6 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
|
81
94
|
props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
|
|
82
95
|
props.bracketMatching && bracketMatching(),
|
|
83
96
|
props.closeBrackets && closeBrackets(),
|
|
84
|
-
props.crosshairCursor && crosshairCursor(),
|
|
85
97
|
props.dropCursor && dropCursor(),
|
|
86
98
|
props.drawSelection && drawSelection(),
|
|
87
99
|
props.highlightActiveLine && highlightActiveLine(),
|
|
@@ -92,6 +104,21 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
|
92
104
|
props.readonly && [EditorState.readOnly.of(true), EditorView.editable.of(false)],
|
|
93
105
|
props.scrollPastEnd && scrollPastEnd(),
|
|
94
106
|
props.tabSize && EditorState.tabSize.of(props.tabSize),
|
|
107
|
+
|
|
108
|
+
// https://codemirror.net/docs/ref/#view.KeyBinding
|
|
109
|
+
keymap.of(
|
|
110
|
+
[
|
|
111
|
+
...((props.keymap && keymaps[props.keymap]) ?? []),
|
|
112
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
113
|
+
...(props.indentWithTab ? [indentWithTab] : []),
|
|
114
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
115
|
+
...(props.closeBrackets ? closeBracketsKeymap : []),
|
|
116
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
117
|
+
...(props.history ? historyKeymap : []),
|
|
118
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
119
|
+
...(props.search ? searchKeymap : []),
|
|
120
|
+
].filter(isNotFalsy),
|
|
121
|
+
),
|
|
95
122
|
].filter(isNotFalsy);
|
|
96
123
|
};
|
|
97
124
|
|
|
@@ -112,14 +139,14 @@ export type ThemeExtensionsOptions = {
|
|
|
112
139
|
};
|
|
113
140
|
};
|
|
114
141
|
|
|
115
|
-
const
|
|
142
|
+
const defaultThemeSlots = {
|
|
116
143
|
editor: {
|
|
117
144
|
className: 'w-full bs-full',
|
|
118
145
|
},
|
|
119
146
|
};
|
|
120
147
|
|
|
121
148
|
export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: ThemeExtensionsOptions = {}): Extension => {
|
|
122
|
-
const slots = defaultsDeep({}, _slots,
|
|
149
|
+
const slots = defaultsDeep({}, _slots, defaultThemeSlots);
|
|
123
150
|
return [
|
|
124
151
|
EditorView.baseTheme(defaultTheme),
|
|
125
152
|
EditorView.darkTheme.of(themeMode === 'dark'),
|
|
@@ -135,12 +162,11 @@ export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: Theme
|
|
|
135
162
|
|
|
136
163
|
export type DataExtensionsProps = {
|
|
137
164
|
id: string;
|
|
138
|
-
text: DocAccessor;
|
|
165
|
+
text: DocAccessor;
|
|
139
166
|
space?: Space;
|
|
140
167
|
identity?: Identity | null;
|
|
141
168
|
};
|
|
142
169
|
|
|
143
|
-
// TODO(burdon): Factor out automerge defs and extension (not hook).
|
|
144
170
|
// TODO(burdon): Move out of react-ui-editor (remove echo deps).
|
|
145
171
|
export const createDataExtensions = ({ id, text, space, identity }: DataExtensionsProps): Extension[] => {
|
|
146
172
|
const extensions: Extension[] = [automerge(text)];
|
package/src/extensions/index.ts
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type EditorView } from '@codemirror/view';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Inline,
|
|
9
|
+
List,
|
|
10
|
+
addBlockquote,
|
|
11
|
+
addCodeblock,
|
|
12
|
+
addLink,
|
|
13
|
+
addList,
|
|
14
|
+
insertTable,
|
|
15
|
+
removeBlockquote,
|
|
16
|
+
removeCodeblock,
|
|
17
|
+
removeLink,
|
|
18
|
+
removeList,
|
|
19
|
+
setHeading,
|
|
20
|
+
setStyle,
|
|
21
|
+
toggleBlockquote,
|
|
22
|
+
toggleList,
|
|
23
|
+
toggleStyle,
|
|
24
|
+
} from './formatting';
|
|
25
|
+
import { createComment } from '../comments';
|
|
26
|
+
|
|
27
|
+
export type ActionType =
|
|
28
|
+
| 'blockquote'
|
|
29
|
+
| 'strong'
|
|
30
|
+
| 'codeblock'
|
|
31
|
+
| 'comment'
|
|
32
|
+
| 'heading'
|
|
33
|
+
| 'image'
|
|
34
|
+
| 'emphasis'
|
|
35
|
+
| 'code'
|
|
36
|
+
| 'link'
|
|
37
|
+
| 'list-bullet'
|
|
38
|
+
| 'list-ordered'
|
|
39
|
+
| 'list-task'
|
|
40
|
+
| 'mention'
|
|
41
|
+
| 'prompt'
|
|
42
|
+
| 'strikethrough'
|
|
43
|
+
| 'table';
|
|
44
|
+
|
|
45
|
+
export type Action = {
|
|
46
|
+
type: ActionType;
|
|
47
|
+
data?: any;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ActionHandler = (view: EditorView, action: Action) => void;
|
|
51
|
+
|
|
52
|
+
export const processAction: ActionHandler = (view, action) => {
|
|
53
|
+
let inlineType, listType;
|
|
54
|
+
switch (action.type) {
|
|
55
|
+
case 'heading':
|
|
56
|
+
setHeading(parseInt(action.data))(view);
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case 'strong':
|
|
60
|
+
case 'emphasis':
|
|
61
|
+
case 'strikethrough':
|
|
62
|
+
case 'code':
|
|
63
|
+
inlineType =
|
|
64
|
+
action.type === 'strong'
|
|
65
|
+
? Inline.Strong
|
|
66
|
+
: action.type === 'emphasis'
|
|
67
|
+
? Inline.Emphasis
|
|
68
|
+
: action.type === 'strikethrough'
|
|
69
|
+
? Inline.Strikethrough
|
|
70
|
+
: Inline.Code;
|
|
71
|
+
(typeof action.data === 'boolean' ? setStyle(inlineType, action.data) : toggleStyle(inlineType))(view);
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 'list-ordered':
|
|
75
|
+
case 'list-bullet':
|
|
76
|
+
case 'list-task':
|
|
77
|
+
listType =
|
|
78
|
+
action.type === 'list-ordered' ? List.Ordered : action.type === 'list-bullet' ? List.Bullet : List.Task;
|
|
79
|
+
(action.data === false ? removeList(listType) : action.data === true ? addList(listType) : toggleList(listType))(
|
|
80
|
+
view,
|
|
81
|
+
);
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
case 'blockquote':
|
|
85
|
+
(action.data === false ? removeBlockquote : action.data === true ? addBlockquote : toggleBlockquote)(view);
|
|
86
|
+
break;
|
|
87
|
+
case 'codeblock':
|
|
88
|
+
(action.data === false ? removeCodeblock : addCodeblock)(view);
|
|
89
|
+
break;
|
|
90
|
+
case 'table':
|
|
91
|
+
insertTable(view);
|
|
92
|
+
break;
|
|
93
|
+
|
|
94
|
+
case 'link':
|
|
95
|
+
(action.data === false ? removeLink : addLink())(view);
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case 'image':
|
|
99
|
+
addLink({ url: action.data, image: true })(view);
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
case 'comment':
|
|
103
|
+
createComment(view);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
if (!view.hasFocus) {
|
|
109
|
+
view.focus();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
};
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { completionKeymap } from '@codemirror/autocomplete';
|
|
6
|
+
import { defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
7
7
|
import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
|
|
8
8
|
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
9
9
|
import { languages } from '@codemirror/language-data';
|
|
10
|
-
import {
|
|
10
|
+
import { lintKeymap } from '@codemirror/lint';
|
|
11
11
|
import { type Extension } from '@codemirror/state';
|
|
12
12
|
import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
13
13
|
import { keymap } from '@codemirror/view';
|
|
@@ -60,14 +60,11 @@ export const createMarkdownExtensions = ({ themeMode }: MarkdownBundleOptions =
|
|
|
60
60
|
keymap.of([
|
|
61
61
|
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
62
62
|
indentWithTab,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
...
|
|
67
|
-
|
|
68
|
-
...searchKeymap,
|
|
69
|
-
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
70
|
-
...standardKeymap,
|
|
63
|
+
|
|
64
|
+
// https://codemirror.net/docs/ref/#commands.defaultKeymap
|
|
65
|
+
...defaultKeymap,
|
|
66
|
+
...completionKeymap,
|
|
67
|
+
...lintKeymap,
|
|
71
68
|
]),
|
|
72
69
|
];
|
|
73
70
|
};
|
|
@@ -15,9 +15,7 @@ import {
|
|
|
15
15
|
} from '@codemirror/state';
|
|
16
16
|
import { EditorView, keymap } from '@codemirror/view';
|
|
17
17
|
import { type SyntaxNodeRef, type SyntaxNode } from '@lezer/common';
|
|
18
|
-
import { useMemo } from 'react';
|
|
19
|
-
|
|
20
|
-
import { useStateRef } from '@dxos/react-async';
|
|
18
|
+
import { useMemo, useState } from 'react';
|
|
21
19
|
|
|
22
20
|
// Markdown refs:
|
|
23
21
|
// https://github.github.com/gfm
|
|
@@ -1225,20 +1223,22 @@ export const getFormatting = (state: EditorState): Formatting => {
|
|
|
1225
1223
|
* Hook provides an extension to compute the current formatting state.
|
|
1226
1224
|
*/
|
|
1227
1225
|
export const useFormattingState = (): [Formatting | undefined, Extension] => {
|
|
1228
|
-
const [state, setState
|
|
1226
|
+
const [state, setState] = useState<Formatting>();
|
|
1227
|
+
|
|
1229
1228
|
const observer = useMemo(
|
|
1230
1229
|
() =>
|
|
1231
1230
|
EditorView.updateListener.of((update) => {
|
|
1232
1231
|
if (update.docChanged || update.selectionSet) {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1232
|
+
setState((prevState) => {
|
|
1233
|
+
const newState = getFormatting(update.state);
|
|
1234
|
+
if (!prevState || !formattingEquals(prevState, newState)) {
|
|
1235
|
+
return newState;
|
|
1236
|
+
}
|
|
1237
|
+
return prevState;
|
|
1238
|
+
});
|
|
1239
1239
|
}
|
|
1240
1240
|
}),
|
|
1241
|
-
[],
|
|
1241
|
+
[setState],
|
|
1242
1242
|
);
|
|
1243
1243
|
|
|
1244
1244
|
return [state, observer];
|
package/src/extensions/modes.ts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
import { type Extension, Facet } from '@codemirror/state';
|
|
6
6
|
import { keymap } from '@codemirror/view';
|
|
7
7
|
import { vim } from '@replit/codemirror-vim';
|
|
8
|
+
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
|
8
9
|
|
|
9
10
|
export const focusEvent = 'focus.container';
|
|
10
11
|
|
|
11
|
-
export type EditorMode = 'default' | 'vim' | undefined;
|
|
12
|
+
export type EditorMode = 'default' | 'vim' | 'vscode' | undefined;
|
|
12
13
|
|
|
13
14
|
export type EditorConfig = {
|
|
14
15
|
type: string;
|
|
@@ -21,7 +22,13 @@ export const editorMode = Facet.define<EditorConfig, EditorConfig>({
|
|
|
21
22
|
|
|
22
23
|
export const EditorModes: { [mode: string]: Extension } = {
|
|
23
24
|
default: [],
|
|
25
|
+
vscode: [
|
|
26
|
+
// https://github.com/replit/codemirror-vscode-keymap
|
|
27
|
+
editorMode.of({ type: 'vscode' }),
|
|
28
|
+
keymap.of(vscodeKeymap),
|
|
29
|
+
],
|
|
24
30
|
vim: [
|
|
31
|
+
// https://github.com/replit/codemirror-vim
|
|
25
32
|
vim(),
|
|
26
33
|
editorMode.of({ type: 'vim', noTabster: true }),
|
|
27
34
|
keymap.of([
|