@dxos/react-ui-editor 0.4.8-next.fff1521 → 0.4.9-main.2d20855
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 +1687 -1690
- 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 +3 -0
- 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/components/Toolbar/Toolbar.stories.d.ts +3 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +3 -0
- package/dist/types/src/extensions/automerge/automerge.stories.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/hooks/useTextEditor.stories.d.ts +3 -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 +3 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +25 -24
- package/src/components/TextEditor/TextEditor.stories.tsx +14 -2
- package/src/components/Toolbar/Toolbar.stories.tsx +0 -1
- package/src/components/Toolbar/Toolbar.tsx +46 -59
- 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/src/translations.ts +3 -0
- 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
|
@@ -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,wBAwBE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-editor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9-main.2d20855",
|
|
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.
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/echo-schema": "0.4.
|
|
47
|
-
"@dxos/invariant": "0.4.
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/react-async": "0.4.
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/react-ui
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/util": "0.4.
|
|
42
|
+
"@dxos/async": "0.4.9-main.2d20855",
|
|
43
|
+
"@dxos/debug": "0.4.9-main.2d20855",
|
|
44
|
+
"@dxos/automerge": "0.4.9-main.2d20855",
|
|
45
|
+
"@dxos/context": "0.4.9-main.2d20855",
|
|
46
|
+
"@dxos/display-name": "0.4.9-main.2d20855",
|
|
47
|
+
"@dxos/echo-schema": "0.4.9-main.2d20855",
|
|
48
|
+
"@dxos/invariant": "0.4.9-main.2d20855",
|
|
49
|
+
"@dxos/log": "0.4.9-main.2d20855",
|
|
50
|
+
"@dxos/react-async": "0.4.9-main.2d20855",
|
|
51
|
+
"@dxos/protocols": "0.4.9-main.2d20855",
|
|
52
|
+
"@dxos/react-ui": "0.4.9-main.2d20855",
|
|
53
|
+
"@dxos/react-ui-theme": "0.4.9-main.2d20855",
|
|
54
|
+
"@dxos/text-model": "0.4.9-main.2d20855",
|
|
55
|
+
"@dxos/util": "0.4.9-main.2d20855"
|
|
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.
|
|
82
|
-
"@dxos/echo-
|
|
83
|
-
"@dxos/echo-
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/react-
|
|
87
|
-
"@dxos/
|
|
88
|
-
"@dxos/
|
|
82
|
+
"@dxos/config": "0.4.9-main.2d20855",
|
|
83
|
+
"@dxos/echo-typegen": "0.4.9-main.2d20855",
|
|
84
|
+
"@dxos/echo-signals": "0.4.9-main.2d20855",
|
|
85
|
+
"@dxos/keyboard": "0.4.9-main.2d20855",
|
|
86
|
+
"@dxos/random": "0.4.9-main.2d20855",
|
|
87
|
+
"@dxos/react-client": "0.4.9-main.2d20855",
|
|
88
|
+
"@dxos/react-ui": "0.4.9-main.2d20855",
|
|
89
|
+
"@dxos/storybook-utils": "0.4.9-main.2d20855"
|
|
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.
|
|
95
|
+
"@dxos/react-client": "0.4.9-main.2d20855"
|
|
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(',');
|
|
@@ -34,7 +34,6 @@ import translations from '../../translations';
|
|
|
34
34
|
faker.seed(101);
|
|
35
35
|
|
|
36
36
|
const Story: FC<{ content: string }> = ({ content }) => {
|
|
37
|
-
console.log('STORY');
|
|
38
37
|
const { themeMode } = useThemeContext();
|
|
39
38
|
const [item] = useState({ text: new TextObject(content) });
|
|
40
39
|
const { id, doc, accessor } = useDocAccessor(item.text);
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
TextHFive,
|
|
25
25
|
TextHSix,
|
|
26
26
|
TextItalic,
|
|
27
|
+
CaretDown,
|
|
28
|
+
Check,
|
|
27
29
|
} from '@phosphor-icons/react';
|
|
28
30
|
import { createContext } from '@radix-ui/react-context';
|
|
29
31
|
import React, { type PropsWithChildren, useEffect, useRef, useState } from 'react';
|
|
@@ -32,17 +34,18 @@ import { useDropzone } from 'react-dropzone';
|
|
|
32
34
|
import {
|
|
33
35
|
DensityProvider,
|
|
34
36
|
ElevationProvider,
|
|
35
|
-
Select,
|
|
36
37
|
Toolbar as NaturalToolbar,
|
|
37
38
|
Tooltip,
|
|
38
39
|
type ThemedClassName,
|
|
39
40
|
type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
|
|
40
41
|
type ToolbarButtonProps as NaturalToolbarButtonProps,
|
|
41
42
|
useTranslation,
|
|
43
|
+
DropdownMenu,
|
|
44
|
+
Button,
|
|
42
45
|
} from '@dxos/react-ui';
|
|
43
46
|
import { getSize } from '@dxos/react-ui-theme';
|
|
44
47
|
|
|
45
|
-
import { type Formatting } from '../../extensions';
|
|
48
|
+
import { type Action, type ActionType, type Formatting } from '../../extensions';
|
|
46
49
|
import { translationKey } from '../../translations';
|
|
47
50
|
|
|
48
51
|
const tooltipProps = { side: 'top' as const, classNames: 'z-10' };
|
|
@@ -57,30 +60,6 @@ const HeadingIcons: { [key: string]: Icon } = {
|
|
|
57
60
|
'6': TextHSix,
|
|
58
61
|
};
|
|
59
62
|
|
|
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
63
|
export type ToolbarProps = ThemedClassName<
|
|
85
64
|
PropsWithChildren<{
|
|
86
65
|
state: Formatting | undefined;
|
|
@@ -180,9 +159,9 @@ const MarkdownHeading = () => {
|
|
|
180
159
|
}
|
|
181
160
|
}}
|
|
182
161
|
>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
162
|
+
{/* TODO(thure): `Select` encounters a ref error if used here (repro: select a heading, then select another
|
|
163
|
+
heading). Determine the root cause and fix or report to Radix. */}
|
|
164
|
+
<DropdownMenu.Root
|
|
186
165
|
open={selectOpen}
|
|
187
166
|
onOpenChange={(nextOpen: boolean) => {
|
|
188
167
|
if (!nextOpen) {
|
|
@@ -190,39 +169,42 @@ const MarkdownHeading = () => {
|
|
|
190
169
|
}
|
|
191
170
|
return setSelectOpen(nextOpen);
|
|
192
171
|
}}
|
|
193
|
-
onValueChange={(value) => onAction?.({ type: 'heading', data: parseInt(value) })}
|
|
194
172
|
>
|
|
195
173
|
<Tooltip.Trigger asChild>
|
|
196
174
|
<NaturalToolbar.Button asChild>
|
|
197
|
-
<
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
175
|
+
<DropdownMenu.Trigger asChild>
|
|
176
|
+
<Button variant='ghost' classNames={buttonStyles} disabled={value === null}>
|
|
177
|
+
<span className='sr-only'>{t('heading label')}</span>
|
|
178
|
+
<HeadingIcon className={iconStyles} />
|
|
179
|
+
<CaretDown />
|
|
180
|
+
</Button>
|
|
181
|
+
</DropdownMenu.Trigger>
|
|
201
182
|
</NaturalToolbar.Button>
|
|
202
183
|
</Tooltip.Trigger>
|
|
203
|
-
<
|
|
204
|
-
<
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
184
|
+
<DropdownMenu.Portal>
|
|
185
|
+
<DropdownMenu.Content classNames='is-min md:is-min' onCloseAutoFocus={(e) => e.preventDefault()}>
|
|
186
|
+
<DropdownMenu.Viewport>
|
|
187
|
+
{Object.keys(HeadingIcons).map((level) => {
|
|
188
|
+
const Icon = HeadingIcons[level];
|
|
189
|
+
return (
|
|
190
|
+
<DropdownMenu.CheckboxItem
|
|
191
|
+
key={level}
|
|
192
|
+
checked={value === level}
|
|
193
|
+
onClick={() => onAction?.({ type: 'heading', data: level })}
|
|
194
|
+
>
|
|
195
|
+
<span className='sr-only'>{t('heading level label', { count: parseInt(level) })}</span>
|
|
196
|
+
<Icon className={iconStyles} />
|
|
197
|
+
<DropdownMenu.ItemIndicator>
|
|
198
|
+
<Check />
|
|
199
|
+
</DropdownMenu.ItemIndicator>
|
|
200
|
+
</DropdownMenu.CheckboxItem>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
</DropdownMenu.Viewport>
|
|
204
|
+
<DropdownMenu.Arrow />
|
|
205
|
+
</DropdownMenu.Content>
|
|
206
|
+
</DropdownMenu.Portal>
|
|
207
|
+
</DropdownMenu.Root>
|
|
226
208
|
<Tooltip.Portal>
|
|
227
209
|
<Tooltip.Content {...tooltipProps}>
|
|
228
210
|
{t('heading label')}
|
|
@@ -358,7 +340,7 @@ const MarkdownCustom = ({ onUpload }: MarkdownCustomOptions = {}) => {
|
|
|
358
340
|
|
|
359
341
|
useEffect(() => {
|
|
360
342
|
if (onUpload && acceptedFiles.length) {
|
|
361
|
-
|
|
343
|
+
requestAnimationFrame(async () => {
|
|
362
344
|
// NOTE: Clone file since react-dropzone patches in a non-standard `path` property, which confuses IPFS.
|
|
363
345
|
const f = acceptedFiles[0];
|
|
364
346
|
const file = new File([f], f.name, {
|
|
@@ -394,7 +376,12 @@ const MarkdownActions = () => {
|
|
|
394
376
|
{/* <ToolbarButton value='comment' Icon={BookOpenText} onClick={() => onAction?.({ type: 'comment' })}> */}
|
|
395
377
|
{/* {t('comment label')} */}
|
|
396
378
|
{/* </ToolbarButton> */}
|
|
397
|
-
<ToolbarButton
|
|
379
|
+
<ToolbarButton
|
|
380
|
+
value='comment'
|
|
381
|
+
Icon={ChatText}
|
|
382
|
+
data-testid='editor.toolbar.comment'
|
|
383
|
+
onClick={() => onAction?.({ type: 'comment' })}
|
|
384
|
+
>
|
|
398
385
|
{t('comment label')}
|
|
399
386
|
</ToolbarButton>
|
|
400
387
|
</>
|
|
@@ -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
|
+
};
|