@dxos/react-ui-editor 0.4.8-main.3a03fb7 → 0.4.8-main.46f461d
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 +215 -86
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +1 -0
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +11 -5
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +4 -2
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -0
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +3 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +7 -4
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/image.d.ts +6 -0
- package/dist/types/src/extensions/markdown/image.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.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/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.stories.d.ts +1 -0
- package/dist/types/src/hooks/useTextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +26 -24
- package/src/components/TextEditor/TextEditor.tsx +2 -2
- package/src/components/Toolbar/Toolbar.stories.tsx +23 -24
- package/src/components/Toolbar/Toolbar.tsx +101 -35
- package/src/extensions/automerge/automerge.ts +4 -4
- package/src/extensions/comments.ts +2 -2
- package/src/extensions/factories.ts +30 -7
- package/src/extensions/markdown/bundle.ts +8 -11
- package/src/extensions/markdown/formatting.test.ts +13 -13
- package/src/extensions/markdown/formatting.ts +95 -87
- package/src/extensions/markdown/image.ts +6 -0
- package/src/extensions/modes.ts +8 -1
- package/src/hooks/useActionHandler.ts +5 -1
- package/src/hooks/useDocAccessor.ts +8 -2
- package/src/hooks/useTextEditor.ts +3 -0
- package/src/themes/default.ts +24 -8
- package/src/translations.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTextEditor.stories.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextEditor.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;AAwBpB,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;;;;mBA2Ee,UAAU
|
|
1
|
+
{"version":3,"file":"useTextEditor.stories.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextEditor.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;AAwBpB,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;;;;mBA2Ee,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;AAH3B,wBASE;AAEF,eAAO,MAAM,OAAO;;CAKnB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAQjB,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default.d.ts","sourceRoot":"","sources":["../../../../src/themes/default.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,WAAW,EAAU,MAAM,WAAW,CAAC;AAKrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"default.d.ts","sourceRoot":"","sources":["../../../../src/themes/default.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,WAAW,EAAU,MAAM,WAAW,CAAC;AAKrD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,EAAE,WAgQ1B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,oBAAoB,CAAC
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;AAEhD,wBAqBE"}
|
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.46f461d",
|
|
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,27 +30,29 @@
|
|
|
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",
|
|
36
37
|
"lodash.get": "^4.4.2",
|
|
37
38
|
"lodash.pick": "^4.4.0",
|
|
38
39
|
"lodash.sortby": "^4.7.0",
|
|
40
|
+
"react-dropzone": "^14.2.3",
|
|
39
41
|
"style-mod": "^4.1.0",
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/protocols": "0.4.8-main.
|
|
49
|
-
"@dxos/react-
|
|
50
|
-
"@dxos/react-
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/util": "0.4.8-main.
|
|
53
|
-
"@dxos/
|
|
42
|
+
"@dxos/context": "0.4.8-main.46f461d",
|
|
43
|
+
"@dxos/debug": "0.4.8-main.46f461d",
|
|
44
|
+
"@dxos/display-name": "0.4.8-main.46f461d",
|
|
45
|
+
"@dxos/echo-schema": "0.4.8-main.46f461d",
|
|
46
|
+
"@dxos/automerge": "0.4.8-main.46f461d",
|
|
47
|
+
"@dxos/invariant": "0.4.8-main.46f461d",
|
|
48
|
+
"@dxos/log": "0.4.8-main.46f461d",
|
|
49
|
+
"@dxos/async": "0.4.8-main.46f461d",
|
|
50
|
+
"@dxos/protocols": "0.4.8-main.46f461d",
|
|
51
|
+
"@dxos/react-ui": "0.4.8-main.46f461d",
|
|
52
|
+
"@dxos/react-async": "0.4.8-main.46f461d",
|
|
53
|
+
"@dxos/react-ui-theme": "0.4.8-main.46f461d",
|
|
54
|
+
"@dxos/util": "0.4.8-main.46f461d",
|
|
55
|
+
"@dxos/text-model": "0.4.8-main.46f461d"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
58
|
"@automerge/automerge-repo-network-broadcastchannel": "^1.1.1",
|
|
@@ -77,20 +79,20 @@
|
|
|
77
79
|
"vite-plugin-top-level-await": "^1.4.1",
|
|
78
80
|
"vite-plugin-wasm": "^3.3.0",
|
|
79
81
|
"vitest": "^1.3.1",
|
|
80
|
-
"@dxos/echo-signals": "0.4.8-main.
|
|
81
|
-
"@dxos/
|
|
82
|
-
"@dxos/
|
|
83
|
-
"@dxos/
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/react-
|
|
86
|
-
"@dxos/
|
|
87
|
-
"@dxos/storybook-utils": "0.4.8-main.
|
|
82
|
+
"@dxos/echo-signals": "0.4.8-main.46f461d",
|
|
83
|
+
"@dxos/echo-typegen": "0.4.8-main.46f461d",
|
|
84
|
+
"@dxos/config": "0.4.8-main.46f461d",
|
|
85
|
+
"@dxos/keyboard": "0.4.8-main.46f461d",
|
|
86
|
+
"@dxos/react-client": "0.4.8-main.46f461d",
|
|
87
|
+
"@dxos/react-ui": "0.4.8-main.46f461d",
|
|
88
|
+
"@dxos/random": "0.4.8-main.46f461d",
|
|
89
|
+
"@dxos/storybook-utils": "0.4.8-main.46f461d"
|
|
88
90
|
},
|
|
89
91
|
"peerDependencies": {
|
|
90
92
|
"@phosphor-icons/react": "^2.0.5",
|
|
91
93
|
"react": "^18.2.0",
|
|
92
94
|
"react-dom": "^18.2.0",
|
|
93
|
-
"@dxos/react-client": "0.4.8-main.
|
|
95
|
+
"@dxos/react-client": "0.4.8-main.46f461d"
|
|
94
96
|
},
|
|
95
97
|
"publishConfig": {
|
|
96
98
|
"access": "public"
|
|
@@ -85,7 +85,7 @@ export const TextEditor = forwardRef<EditorView | null, TextEditorProps>(
|
|
|
85
85
|
// Create editor state and view.
|
|
86
86
|
// The view is recreated if the model or extensions are changed.
|
|
87
87
|
useEffect(() => {
|
|
88
|
-
log('
|
|
88
|
+
log('create', { id, instanceId });
|
|
89
89
|
|
|
90
90
|
//
|
|
91
91
|
// EditorState
|
|
@@ -147,8 +147,8 @@ export const TextEditor = forwardRef<EditorView | null, TextEditorProps>(
|
|
|
147
147
|
setView(view);
|
|
148
148
|
|
|
149
149
|
return () => {
|
|
150
|
+
log('destroy', { id, instanceId });
|
|
150
151
|
view?.destroy();
|
|
151
|
-
setView(null);
|
|
152
152
|
};
|
|
153
153
|
}, [doc, selection, extensions]);
|
|
154
154
|
|
|
@@ -16,17 +16,17 @@ import { withTheme } from '@dxos/storybook-utils';
|
|
|
16
16
|
import { Toolbar } from './Toolbar';
|
|
17
17
|
import {
|
|
18
18
|
type Comment,
|
|
19
|
-
decorateMarkdown,
|
|
20
19
|
comments,
|
|
21
20
|
createBasicExtensions,
|
|
22
21
|
createDataExtensions,
|
|
22
|
+
createMarkdownExtensions,
|
|
23
23
|
createThemeExtensions,
|
|
24
|
+
decorateMarkdown,
|
|
24
25
|
formattingKeymap,
|
|
26
|
+
image,
|
|
25
27
|
table,
|
|
26
28
|
useComments,
|
|
27
29
|
useFormattingState,
|
|
28
|
-
createMarkdownExtensions,
|
|
29
|
-
image,
|
|
30
30
|
} from '../../extensions';
|
|
31
31
|
import { useActionHandler, useDocAccessor, useTextEditor } from '../../hooks';
|
|
32
32
|
import translations from '../../translations';
|
|
@@ -34,15 +34,17 @@ import translations from '../../translations';
|
|
|
34
34
|
faker.seed(101);
|
|
35
35
|
|
|
36
36
|
const Story: FC<{ content: string }> = ({ content }) => {
|
|
37
|
+
console.log('STORY');
|
|
37
38
|
const { themeMode } = useThemeContext();
|
|
38
39
|
const [item] = useState({ text: new TextObject(content) });
|
|
39
40
|
const { id, doc, accessor } = useDocAccessor(item.text);
|
|
40
41
|
const [formattingState, formattingObserver] = useFormattingState();
|
|
41
|
-
const { parentRef, view } = useTextEditor(
|
|
42
|
-
|
|
42
|
+
const { parentRef, view } = useTextEditor(() => {
|
|
43
|
+
return {
|
|
43
44
|
id,
|
|
44
45
|
doc,
|
|
45
46
|
extensions: [
|
|
47
|
+
formattingObserver,
|
|
46
48
|
createBasicExtensions(),
|
|
47
49
|
createMarkdownExtensions({ themeMode }),
|
|
48
50
|
createThemeExtensions({ themeMode, slots: { editor: { className: 'p-2' } } }),
|
|
@@ -58,39 +60,36 @@ const Story: FC<{ content: string }> = ({ content }) => {
|
|
|
58
60
|
formattingKeymap(),
|
|
59
61
|
image(),
|
|
60
62
|
table(),
|
|
61
|
-
formattingObserver,
|
|
62
63
|
],
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
};
|
|
65
|
+
}, [id, accessor, formattingObserver, themeMode]);
|
|
66
|
+
|
|
67
|
+
const handleAction = useActionHandler(view);
|
|
66
68
|
|
|
67
69
|
const [_comments, setComments] = useState<Comment[]>([]);
|
|
68
70
|
useComments(view, id, _comments);
|
|
69
71
|
|
|
70
|
-
const handleAction = useActionHandler(view);
|
|
71
|
-
|
|
72
72
|
return (
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
<Toolbar.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
<Tooltip.Provider>
|
|
74
|
+
<div role='none' className='fixed inset-0 flex flex-col'>
|
|
75
|
+
<Toolbar.Root onAction={handleAction} state={formattingState} classNames={textBlockWidth}>
|
|
76
|
+
<Toolbar.Markdown />
|
|
77
|
+
<Toolbar.Custom onUpload={async (file) => ({ url: file.name })} />
|
|
78
|
+
<Toolbar.Separator />
|
|
79
|
+
<Toolbar.Actions />
|
|
80
|
+
</Toolbar.Root>
|
|
81
|
+
<div ref={parentRef} className={textBlockWidth} />
|
|
82
|
+
</div>
|
|
83
|
+
</Tooltip.Provider>
|
|
81
84
|
);
|
|
82
85
|
};
|
|
83
86
|
|
|
84
87
|
export default {
|
|
85
88
|
title: 'react-ui-editor/Toolbar',
|
|
86
89
|
component: Toolbar,
|
|
87
|
-
render: (args: any) => (
|
|
88
|
-
<Tooltip.Provider>
|
|
89
|
-
<Story {...args} />
|
|
90
|
-
</Tooltip.Provider>
|
|
91
|
-
),
|
|
92
90
|
decorators: [withTheme],
|
|
93
91
|
parameters: { translations, layout: 'fullscreen' },
|
|
92
|
+
render: (args: any) => <Story {...args} />,
|
|
94
93
|
};
|
|
95
94
|
|
|
96
95
|
const content = [
|
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
ChatText,
|
|
8
8
|
Code,
|
|
9
9
|
CodeBlock,
|
|
10
|
+
Image,
|
|
10
11
|
Link,
|
|
11
12
|
ListBullets,
|
|
12
13
|
ListChecks,
|
|
13
14
|
ListNumbers,
|
|
14
15
|
Paragraph,
|
|
16
|
+
Quotes,
|
|
15
17
|
TextStrikethrough,
|
|
16
18
|
Table,
|
|
17
19
|
TextB,
|
|
@@ -22,19 +24,20 @@ import {
|
|
|
22
24
|
TextHFive,
|
|
23
25
|
TextHSix,
|
|
24
26
|
TextItalic,
|
|
25
|
-
Quotes,
|
|
26
27
|
} from '@phosphor-icons/react';
|
|
27
28
|
import { createContext } from '@radix-ui/react-context';
|
|
28
|
-
import React, { type PropsWithChildren, useRef, useState } from 'react';
|
|
29
|
+
import React, { type PropsWithChildren, useEffect, useRef, useState } from 'react';
|
|
30
|
+
import { useDropzone } from 'react-dropzone';
|
|
29
31
|
|
|
30
32
|
import {
|
|
31
33
|
DensityProvider,
|
|
32
34
|
ElevationProvider,
|
|
33
35
|
Select,
|
|
34
|
-
type ThemedClassName,
|
|
35
36
|
Toolbar as NaturalToolbar,
|
|
36
37
|
Tooltip,
|
|
37
|
-
type
|
|
38
|
+
type ThemedClassName,
|
|
39
|
+
type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
|
|
40
|
+
type ToolbarButtonProps as NaturalToolbarButtonProps,
|
|
38
41
|
useTranslation,
|
|
39
42
|
} from '@dxos/react-ui';
|
|
40
43
|
import { getSize } from '@dxos/react-ui-theme';
|
|
@@ -80,7 +83,7 @@ export type Action = {
|
|
|
80
83
|
|
|
81
84
|
export type ToolbarProps = ThemedClassName<
|
|
82
85
|
PropsWithChildren<{
|
|
83
|
-
state: Formatting |
|
|
86
|
+
state: Formatting | undefined;
|
|
84
87
|
onAction?: (action: Action) => void;
|
|
85
88
|
}>
|
|
86
89
|
>;
|
|
@@ -111,9 +114,9 @@ type ButtonProps = {
|
|
|
111
114
|
disabled?: (state: Formatting) => boolean;
|
|
112
115
|
};
|
|
113
116
|
|
|
114
|
-
type
|
|
117
|
+
type ToolbarToggleButtonProps = NaturalToolbarToggleGroupItemProps & { Icon: Icon };
|
|
115
118
|
|
|
116
|
-
const
|
|
119
|
+
const ToolbarToggleButton = ({ Icon, children, ...props }: ToolbarToggleButtonProps) => {
|
|
117
120
|
return (
|
|
118
121
|
<Tooltip.Root>
|
|
119
122
|
<Tooltip.Trigger asChild>
|
|
@@ -132,6 +135,27 @@ const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
|
|
|
132
135
|
);
|
|
133
136
|
};
|
|
134
137
|
|
|
138
|
+
type ToolbarButtonProps = NaturalToolbarButtonProps & { Icon: Icon };
|
|
139
|
+
|
|
140
|
+
const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
|
|
141
|
+
return (
|
|
142
|
+
<Tooltip.Root>
|
|
143
|
+
<Tooltip.Trigger asChild>
|
|
144
|
+
<NaturalToolbar.Button variant='ghost' {...props} classNames={buttonStyles}>
|
|
145
|
+
<Icon className={iconStyles} />
|
|
146
|
+
<span className='sr-only'>{children}</span>
|
|
147
|
+
</NaturalToolbar.Button>
|
|
148
|
+
</Tooltip.Trigger>
|
|
149
|
+
<Tooltip.Portal>
|
|
150
|
+
<Tooltip.Content {...tooltipProps}>
|
|
151
|
+
{children}
|
|
152
|
+
<Tooltip.Arrow />
|
|
153
|
+
</Tooltip.Content>
|
|
154
|
+
</Tooltip.Portal>
|
|
155
|
+
</Tooltip.Root>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
135
159
|
const ToolbarSeparator = () => <div role='separator' className='grow' />;
|
|
136
160
|
|
|
137
161
|
const MarkdownHeading = () => {
|
|
@@ -227,7 +251,7 @@ const MarkdownStyles = () => {
|
|
|
227
251
|
value={markdownStyles.filter(({ getState }) => state && getState(state)).map(({ type }) => type)}
|
|
228
252
|
>
|
|
229
253
|
{markdownStyles.map(({ type, getState, Icon }) => (
|
|
230
|
-
<
|
|
254
|
+
<ToolbarToggleButton
|
|
231
255
|
key={type}
|
|
232
256
|
value={type}
|
|
233
257
|
Icon={Icon}
|
|
@@ -235,7 +259,7 @@ const MarkdownStyles = () => {
|
|
|
235
259
|
onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
|
|
236
260
|
>
|
|
237
261
|
{t(`${type} label`)}
|
|
238
|
-
</
|
|
262
|
+
</ToolbarToggleButton>
|
|
239
263
|
))}
|
|
240
264
|
</NaturalToolbar.ToggleGroup>
|
|
241
265
|
);
|
|
@@ -253,14 +277,14 @@ const MarkdownLists = () => {
|
|
|
253
277
|
return (
|
|
254
278
|
<NaturalToolbar.ToggleGroup type='single' value={state?.listStyle ? `list-${state.listStyle}` : ''}>
|
|
255
279
|
{markdownLists.map(({ type, getState, Icon }) => (
|
|
256
|
-
<
|
|
280
|
+
<ToolbarToggleButton
|
|
257
281
|
key={type}
|
|
258
282
|
value={type}
|
|
259
283
|
Icon={Icon}
|
|
260
284
|
onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
|
|
261
285
|
>
|
|
262
286
|
{t(`${type} label`)}
|
|
263
|
-
</
|
|
287
|
+
</ToolbarToggleButton>
|
|
264
288
|
))}
|
|
265
289
|
</NaturalToolbar.ToggleGroup>
|
|
266
290
|
);
|
|
@@ -292,7 +316,7 @@ const MarkdownBlocks = () => {
|
|
|
292
316
|
return (
|
|
293
317
|
<NaturalToolbar.ToggleGroup type='single' value={value?.type ?? ''}>
|
|
294
318
|
{markdownBlocks.map(({ type, disabled, getState, Icon }) => (
|
|
295
|
-
<
|
|
319
|
+
<ToolbarToggleButton
|
|
296
320
|
key={type}
|
|
297
321
|
value={type}
|
|
298
322
|
Icon={Icon}
|
|
@@ -300,7 +324,7 @@ const MarkdownBlocks = () => {
|
|
|
300
324
|
onClick={state ? () => onAction?.({ type, data: !getState(state) }) : undefined}
|
|
301
325
|
>
|
|
302
326
|
{t(`${type} label`)}
|
|
303
|
-
</
|
|
327
|
+
</ToolbarToggleButton>
|
|
304
328
|
))}
|
|
305
329
|
</NaturalToolbar.ToggleGroup>
|
|
306
330
|
);
|
|
@@ -315,38 +339,80 @@ const MarkdownStandard = () => (
|
|
|
315
339
|
</>
|
|
316
340
|
);
|
|
317
341
|
|
|
318
|
-
|
|
342
|
+
// TODO(burdon): Make extensible.
|
|
343
|
+
export type MarkdownCustomOptions = {
|
|
344
|
+
onUpload?: (file: File) => Promise<{ url?: string }>;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const MarkdownCustom = ({ onUpload }: MarkdownCustomOptions = {}) => {
|
|
319
348
|
const { onAction } = useToolbarContext('MarkdownStyles');
|
|
320
349
|
const { t } = useTranslation(translationKey);
|
|
350
|
+
// https://react-dropzone.js.org/#src
|
|
351
|
+
const { acceptedFiles, getInputProps, open } = useDropzone({
|
|
352
|
+
multiple: false,
|
|
353
|
+
noDrag: true,
|
|
354
|
+
accept: {
|
|
355
|
+
'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
if (onUpload && acceptedFiles.length) {
|
|
361
|
+
setTimeout(async () => {
|
|
362
|
+
// NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
|
|
363
|
+
const f = acceptedFiles[0];
|
|
364
|
+
const file = new File([f], f.name, {
|
|
365
|
+
type: f.type,
|
|
366
|
+
lastModified: f.lastModified,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const { url } = await onUpload(file);
|
|
370
|
+
if (url) {
|
|
371
|
+
onAction?.({ type: 'image', data: url });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}, [acceptedFiles]);
|
|
376
|
+
|
|
321
377
|
return (
|
|
322
|
-
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
378
|
+
<>
|
|
379
|
+
<input {...getInputProps()} />
|
|
380
|
+
<ToolbarButton value='image' Icon={Image} onClick={() => open()}>
|
|
381
|
+
{t('image label')}
|
|
382
|
+
</ToolbarButton>
|
|
383
|
+
</>
|
|
384
|
+
);
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// TODO(burdon): Make extensible.
|
|
388
|
+
const MarkdownActions = () => {
|
|
389
|
+
const { onAction } = useToolbarContext('MarkdownStyles');
|
|
390
|
+
const { t } = useTranslation(translationKey);
|
|
391
|
+
return (
|
|
392
|
+
<>
|
|
393
|
+
{/* TODO(burdon): Toggle readonly state. */}
|
|
394
|
+
{/* <ToolbarButton value='comment' Icon={BookOpenText} onClick={() => onAction?.({ type: 'comment' })}> */}
|
|
395
|
+
{/* {t('comment label')} */}
|
|
396
|
+
{/* </ToolbarButton> */}
|
|
397
|
+
<ToolbarButton
|
|
398
|
+
value='comment'
|
|
399
|
+
Icon={ChatText}
|
|
400
|
+
data-testid='editor.toolbar.comment'
|
|
401
|
+
onClick={() => onAction?.({ type: 'comment' })}
|
|
402
|
+
>
|
|
403
|
+
{t('comment label')}
|
|
404
|
+
</ToolbarButton>
|
|
405
|
+
</>
|
|
341
406
|
);
|
|
342
407
|
};
|
|
343
408
|
|
|
344
409
|
export const Toolbar = {
|
|
345
410
|
Root: ToolbarRoot,
|
|
346
|
-
Button:
|
|
411
|
+
Button: ToolbarToggleButton,
|
|
347
412
|
Separator: ToolbarSeparator,
|
|
348
413
|
Markdown: MarkdownStandard,
|
|
349
|
-
|
|
414
|
+
Custom: MarkdownCustom,
|
|
415
|
+
Actions: MarkdownActions,
|
|
350
416
|
};
|
|
351
417
|
|
|
352
418
|
export { useToolbarContext };
|
|
@@ -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
|
|
|
@@ -508,14 +508,14 @@ export const focusComment = (view: EditorView, id: string, center = true) => {
|
|
|
508
508
|
/**
|
|
509
509
|
* Update comments state field.
|
|
510
510
|
*/
|
|
511
|
-
export const useComments = (view: EditorView | null | undefined, id: string, comments
|
|
511
|
+
export const useComments = (view: EditorView | null | undefined, id: string, comments?: Comment[]) => {
|
|
512
512
|
useEffect(() => {
|
|
513
513
|
if (view) {
|
|
514
514
|
// Check same document.
|
|
515
515
|
// NOTE: Hook might be called before editor state is updated.
|
|
516
516
|
if (id === view.state.facet(documentId)) {
|
|
517
517
|
view.dispatch({
|
|
518
|
-
effects: setComments.of({ id, comments }),
|
|
518
|
+
effects: setComments.of({ id, comments: comments ?? [] }),
|
|
519
519
|
});
|
|
520
520
|
}
|
|
521
521
|
}
|
|
@@ -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,8 @@ import {
|
|
|
12
13
|
drawSelection,
|
|
13
14
|
dropCursor,
|
|
14
15
|
highlightActiveLine,
|
|
16
|
+
highlightSpecialChars,
|
|
17
|
+
keymap,
|
|
15
18
|
lineNumbers,
|
|
16
19
|
placeholder,
|
|
17
20
|
scrollPastEnd,
|
|
@@ -32,8 +35,6 @@ import { awareness, SpaceAwarenessProvider } from './awareness';
|
|
|
32
35
|
import { type ThemeStyles } from '../styles';
|
|
33
36
|
import { defaultTheme } from '../themes';
|
|
34
37
|
|
|
35
|
-
// TODO(burdon): Move into extensions folder.
|
|
36
|
-
|
|
37
38
|
//
|
|
38
39
|
// Basic
|
|
39
40
|
//
|
|
@@ -41,8 +42,8 @@ import { defaultTheme } from '../themes';
|
|
|
41
42
|
/**
|
|
42
43
|
* https://codemirror.net/docs/extensions
|
|
43
44
|
* https://github.com/codemirror/basic-setup
|
|
45
|
+
* https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
|
|
44
46
|
*/
|
|
45
|
-
// TODO(burdon): Reconcile with createMarkdownExtensions.
|
|
46
47
|
export type BasicExtensionsOptions = {
|
|
47
48
|
allowMultipleSelections?: boolean;
|
|
48
49
|
bracketMatching?: boolean;
|
|
@@ -53,21 +54,25 @@ export type BasicExtensionsOptions = {
|
|
|
53
54
|
editable?: boolean;
|
|
54
55
|
highlightActiveLine?: boolean;
|
|
55
56
|
history?: boolean;
|
|
57
|
+
indentWithTab?: boolean;
|
|
56
58
|
lineNumbers?: boolean;
|
|
57
59
|
lineWrapping?: boolean;
|
|
58
60
|
placeholder?: string;
|
|
59
61
|
readonly?: boolean;
|
|
62
|
+
search?: boolean;
|
|
60
63
|
scrollPastEnd?: 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,
|
|
70
74
|
lineWrapping: true,
|
|
75
|
+
search: true,
|
|
71
76
|
};
|
|
72
77
|
|
|
73
78
|
export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extension => {
|
|
@@ -85,6 +90,7 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
|
85
90
|
props.dropCursor && dropCursor(),
|
|
86
91
|
props.drawSelection && drawSelection(),
|
|
87
92
|
props.highlightActiveLine && highlightActiveLine(),
|
|
93
|
+
highlightSpecialChars(),
|
|
88
94
|
props.history && history(),
|
|
89
95
|
props.lineNumbers && lineNumbers(),
|
|
90
96
|
props.lineWrapping && EditorView.lineWrapping,
|
|
@@ -92,6 +98,24 @@ 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
|
+
...standardKeymap,
|
|
107
|
+
|
|
108
|
+
// https://codemirror.net/docs/ref/#commands.indentWithTab
|
|
109
|
+
props.indentWithTab && indentWithTab,
|
|
110
|
+
|
|
111
|
+
// https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
|
|
112
|
+
...(props.closeBrackets ? closeBracketsKeymap : []),
|
|
113
|
+
// https://codemirror.net/docs/ref/#commands.historyKeymap
|
|
114
|
+
...(props.history ? historyKeymap : []),
|
|
115
|
+
// https://codemirror.net/docs/ref/#search.searchKeymap
|
|
116
|
+
...(props.search ? searchKeymap : []),
|
|
117
|
+
].filter(isNotFalsy),
|
|
118
|
+
),
|
|
95
119
|
].filter(isNotFalsy);
|
|
96
120
|
};
|
|
97
121
|
|
|
@@ -135,12 +159,11 @@ export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: Theme
|
|
|
135
159
|
|
|
136
160
|
export type DataExtensionsProps = {
|
|
137
161
|
id: string;
|
|
138
|
-
text: DocAccessor;
|
|
162
|
+
text: DocAccessor;
|
|
139
163
|
space?: Space;
|
|
140
164
|
identity?: Identity | null;
|
|
141
165
|
};
|
|
142
166
|
|
|
143
|
-
// TODO(burdon): Factor out automerge defs and extension (not hook).
|
|
144
167
|
// TODO(burdon): Move out of react-ui-editor (remove echo deps).
|
|
145
168
|
export const createDataExtensions = ({ id, text, space, identity }: DataExtensionsProps): Extension[] => {
|
|
146
169
|
const extensions: Extension[] = [automerge(text)];
|
|
@@ -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
|
};
|