@dxos/react-ui-editor 0.4.8-main.4d33a73 → 0.4.8-main.4f9d99e
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 +1437 -1438
- 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 +4 -0
- 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 +2 -26
- package/src/extensions/command/state.ts +2 -2
- package/src/extensions/dnd.ts +31 -9
- package/src/extensions/factories.ts +28 -7
- 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-main.
|
|
3
|
+
"version": "0.4.8-main.4f9d99e",
|
|
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-main.
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/context": "0.4.8-main.
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/display-name": "0.4.8-main.
|
|
46
|
-
"@dxos/echo-schema": "0.4.8-main.
|
|
47
|
-
"@dxos/invariant": "0.4.8-main.
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/react-async": "0.4.8-main.
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/react-ui-theme": "0.4.8-main.
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/text-model": "0.4.8-main.
|
|
54
|
-
"@dxos/util": "0.4.8-main.
|
|
42
|
+
"@dxos/async": "0.4.8-main.4f9d99e",
|
|
43
|
+
"@dxos/automerge": "0.4.8-main.4f9d99e",
|
|
44
|
+
"@dxos/context": "0.4.8-main.4f9d99e",
|
|
45
|
+
"@dxos/debug": "0.4.8-main.4f9d99e",
|
|
46
|
+
"@dxos/display-name": "0.4.8-main.4f9d99e",
|
|
47
|
+
"@dxos/echo-schema": "0.4.8-main.4f9d99e",
|
|
48
|
+
"@dxos/invariant": "0.4.8-main.4f9d99e",
|
|
49
|
+
"@dxos/log": "0.4.8-main.4f9d99e",
|
|
50
|
+
"@dxos/react-async": "0.4.8-main.4f9d99e",
|
|
51
|
+
"@dxos/react-ui": "0.4.8-main.4f9d99e",
|
|
52
|
+
"@dxos/react-ui-theme": "0.4.8-main.4f9d99e",
|
|
53
|
+
"@dxos/protocols": "0.4.8-main.4f9d99e",
|
|
54
|
+
"@dxos/text-model": "0.4.8-main.4f9d99e",
|
|
55
|
+
"@dxos/util": "0.4.8-main.4f9d99e"
|
|
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-main.
|
|
82
|
-
"@dxos/echo-signals": "0.4.8-main.
|
|
83
|
-
"@dxos/
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/react-
|
|
87
|
-
"@dxos/
|
|
88
|
-
"@dxos/
|
|
82
|
+
"@dxos/config": "0.4.8-main.4f9d99e",
|
|
83
|
+
"@dxos/echo-signals": "0.4.8-main.4f9d99e",
|
|
84
|
+
"@dxos/echo-typegen": "0.4.8-main.4f9d99e",
|
|
85
|
+
"@dxos/keyboard": "0.4.8-main.4f9d99e",
|
|
86
|
+
"@dxos/react-client": "0.4.8-main.4f9d99e",
|
|
87
|
+
"@dxos/react-ui": "0.4.8-main.4f9d99e",
|
|
88
|
+
"@dxos/random": "0.4.8-main.4f9d99e",
|
|
89
|
+
"@dxos/storybook-utils": "0.4.8-main.4f9d99e"
|
|
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-main.
|
|
95
|
+
"@dxos/react-client": "0.4.8-main.4f9d99e"
|
|
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, {
|
|
@@ -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,9 +2,10 @@
|
|
|
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 { 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,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
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,8 +41,8 @@ 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;
|
|
@@ -53,21 +53,27 @@ export type BasicExtensionsOptions = {
|
|
|
53
53
|
editable?: boolean;
|
|
54
54
|
highlightActiveLine?: boolean;
|
|
55
55
|
history?: boolean;
|
|
56
|
+
indentWithTab?: boolean;
|
|
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
67
|
const defaults: 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
|
+
standardKeymap: true,
|
|
70
75
|
lineWrapping: true,
|
|
76
|
+
search: true,
|
|
71
77
|
};
|
|
72
78
|
|
|
73
79
|
export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
|
|
@@ -92,6 +98,22 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
|
92
98
|
props.readonly && [EditorState.readOnly.of(true), EditorView.editable.of(false)],
|
|
93
99
|
props.scrollPastEnd && scrollPastEnd(),
|
|
94
100
|
props.tabSize && EditorState.tabSize.of(props.tabSize),
|
|
101
|
+
|
|
102
|
+
// https://codemirror.net/docs/ref/#view.KeyBinding
|
|
103
|
+
keymap.of(
|
|
104
|
+
[
|
|
105
|
+
// https://codemirror.net/docs/ref/#commands.standardKeymap
|
|
106
|
+
...(props.standardKeymap ? standardKeymap : []),
|
|
107
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
108
|
+
...(props.indentWithTab ? [indentWithTab] : []),
|
|
109
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
110
|
+
...(props.closeBrackets ? closeBracketsKeymap : []),
|
|
111
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
112
|
+
...(props.history ? historyKeymap : []),
|
|
113
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
114
|
+
...(props.search ? searchKeymap : []),
|
|
115
|
+
].filter(isNotFalsy),
|
|
116
|
+
),
|
|
95
117
|
].filter(isNotFalsy);
|
|
96
118
|
};
|
|
97
119
|
|
|
@@ -135,12 +157,11 @@ export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: Theme
|
|
|
135
157
|
|
|
136
158
|
export type DataExtensionsProps = {
|
|
137
159
|
id: string;
|
|
138
|
-
text: DocAccessor;
|
|
160
|
+
text: DocAccessor;
|
|
139
161
|
space?: Space;
|
|
140
162
|
identity?: Identity | null;
|
|
141
163
|
};
|
|
142
164
|
|
|
143
|
-
// TODO(burdon): Factor out automerge defs and extension (not hook).
|
|
144
165
|
// TODO(burdon): Move out of react-ui-editor (remove echo deps).
|
|
145
166
|
export const createDataExtensions = ({ id, text, space, identity }: DataExtensionsProps): Extension[] => {
|
|
146
167
|
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([
|
|
@@ -2,96 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type
|
|
5
|
+
import { type EditorView } from '@codemirror/view';
|
|
6
6
|
|
|
7
|
-
import type
|
|
8
|
-
import {
|
|
9
|
-
Inline,
|
|
10
|
-
List,
|
|
11
|
-
addBlockquote,
|
|
12
|
-
addCodeblock,
|
|
13
|
-
addLink,
|
|
14
|
-
addList,
|
|
15
|
-
createComment,
|
|
16
|
-
insertTable,
|
|
17
|
-
removeBlockquote,
|
|
18
|
-
removeCodeblock,
|
|
19
|
-
removeLink,
|
|
20
|
-
removeList,
|
|
21
|
-
setHeading,
|
|
22
|
-
setStyle,
|
|
23
|
-
toggleBlockquote,
|
|
24
|
-
toggleList,
|
|
25
|
-
toggleStyle,
|
|
26
|
-
} from '../extensions';
|
|
7
|
+
import { type ToolbarProps } from '../components';
|
|
8
|
+
import { processAction } from '../extensions';
|
|
27
9
|
|
|
28
10
|
export const useActionHandler = (view?: EditorView | null): ToolbarProps['onAction'] => {
|
|
29
|
-
return (action) =>
|
|
30
|
-
if (!view) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let inlineType, listType;
|
|
35
|
-
switch (action.type) {
|
|
36
|
-
case 'heading':
|
|
37
|
-
setHeading(parseInt(action.data))(view);
|
|
38
|
-
break;
|
|
39
|
-
|
|
40
|
-
case 'strong':
|
|
41
|
-
case 'emphasis':
|
|
42
|
-
case 'strikethrough':
|
|
43
|
-
case 'code':
|
|
44
|
-
inlineType =
|
|
45
|
-
action.type === 'strong'
|
|
46
|
-
? Inline.Strong
|
|
47
|
-
: action.type === 'emphasis'
|
|
48
|
-
? Inline.Emphasis
|
|
49
|
-
: action.type === 'strikethrough'
|
|
50
|
-
? Inline.Strikethrough
|
|
51
|
-
: Inline.Code;
|
|
52
|
-
(typeof action.data === 'boolean' ? setStyle(inlineType, action.data) : toggleStyle(inlineType))(view);
|
|
53
|
-
break;
|
|
54
|
-
|
|
55
|
-
case 'list-ordered':
|
|
56
|
-
case 'list-bullet':
|
|
57
|
-
case 'list-task':
|
|
58
|
-
listType =
|
|
59
|
-
action.type === 'list-ordered' ? List.Ordered : action.type === 'list-bullet' ? List.Bullet : List.Task;
|
|
60
|
-
(action.data === false
|
|
61
|
-
? removeList(listType)
|
|
62
|
-
: action.data === true
|
|
63
|
-
? addList(listType)
|
|
64
|
-
: toggleList(listType))(view);
|
|
65
|
-
break;
|
|
66
|
-
|
|
67
|
-
case 'blockquote':
|
|
68
|
-
(action.data === false ? removeBlockquote : action.data === true ? addBlockquote : toggleBlockquote)(view);
|
|
69
|
-
break;
|
|
70
|
-
case 'codeblock':
|
|
71
|
-
(action.data === false ? removeCodeblock : addCodeblock)(view);
|
|
72
|
-
break;
|
|
73
|
-
case 'table':
|
|
74
|
-
insertTable(view);
|
|
75
|
-
break;
|
|
76
|
-
|
|
77
|
-
case 'link':
|
|
78
|
-
(action.data === false ? removeLink : addLink())(view);
|
|
79
|
-
break;
|
|
80
|
-
|
|
81
|
-
case 'image':
|
|
82
|
-
addLink({ url: action.data, image: true })(view);
|
|
83
|
-
break;
|
|
84
|
-
|
|
85
|
-
case 'comment':
|
|
86
|
-
createComment(view);
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// TODO(burdon): Hack otherwise remains on heading selector.
|
|
91
|
-
setTimeout(() => {
|
|
92
|
-
if (!view.hasFocus) {
|
|
93
|
-
view.focus();
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
};
|
|
11
|
+
return (action) => view && processAction(view, action);
|
|
97
12
|
};
|