@dxos/react-ui-editor 0.4.8-main.ec0899a → 0.4.8-next.fff1521
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 +449 -273
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -2
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +2 -1
- 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.map +1 -1
- package/dist/types/src/extensions/index.d.ts +1 -0
- package/dist/types/src/extensions/index.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 -0
- package/dist/types/src/extensions/modes.d.ts.map +1 -1
- package/dist/types/src/extensions/state.d.ts +20 -0
- package/dist/types/src/extensions/state.d.ts.map +1 -0
- package/dist/types/src/hooks/useActionHandler.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/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +25 -27
- package/src/components/TextEditor/TextEditor.stories.tsx +16 -4
- package/src/components/TextEditor/TextEditor.tsx +13 -12
- package/src/components/Toolbar/Toolbar.stories.tsx +23 -24
- package/src/components/Toolbar/Toolbar.tsx +96 -35
- package/src/extensions/comments.ts +2 -2
- package/src/extensions/factories.ts +0 -3
- package/src/extensions/index.ts +1 -0
- package/src/extensions/markdown/formatting.test.ts +13 -13
- package/src/extensions/markdown/formatting.ts +91 -83
- package/src/extensions/markdown/image.ts +6 -0
- package/src/extensions/modes.ts +3 -1
- package/src/extensions/state.ts +90 -0
- package/src/hooks/useActionHandler.ts +5 -1
- package/src/hooks/useTextEditor.stories.tsx +3 -3
- package/src/hooks/useTextEditor.ts +3 -0
- package/src/translations.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../src/extensions/state.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,mBAAmB,CAAC;AAYhE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACtD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;CACtD,CAAC;AAGF,eAAO,MAAM,6BAA6B,EAAE,YAU3C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,4BAA4B,QAAQ,YAAY,CAAC,KAAQ,SA0C1E,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActionHandler.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useActionHandler.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAqBlD,eAAO,MAAM,gBAAgB,UAAW,UAAU,GAAG,IAAI,KAAG,YAAY,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"useActionHandler.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useActionHandler.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAqBlD,eAAO,MAAM,gBAAgB,UAAW,UAAU,GAAG,IAAI,KAAG,YAAY,CAAC,UAAU,CAqElF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTextEditor.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextEditor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAwC,MAAM,OAAO,CAAC;AAKlG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAIrD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,SAAS,CAAC,cAAc,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,eAAe,EAAE,iBAAiB,GAAG,YAAY,CAAC,CAAC;AAEzF;;GAEG;AACH,eAAO,MAAM,aAAa,QAAQ,MAAM,kBAAkB,4BAA2C,
|
|
1
|
+
{"version":3,"file":"useTextEditor.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useTextEditor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAwC,MAAM,OAAO,CAAC;AAKlG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAIrD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,SAAS,CAAC,cAAc,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,eAAe,EAAE,iBAAiB,GAAG,YAAY,CAAC,CAAC;AAEzF;;GAEG;AACH,eAAO,MAAM,aAAa,QAAQ,MAAM,kBAAkB,4BAA2C,aA4EpG,CAAC"}
|
|
@@ -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":"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-
|
|
3
|
+
"version": "0.4.8-next.fff1521",
|
|
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",
|
|
@@ -36,21 +36,22 @@
|
|
|
36
36
|
"lodash.get": "^4.4.2",
|
|
37
37
|
"lodash.pick": "^4.4.0",
|
|
38
38
|
"lodash.sortby": "^4.7.0",
|
|
39
|
+
"react-dropzone": "^14.2.3",
|
|
39
40
|
"style-mod": "^4.1.0",
|
|
40
|
-
"@dxos/async": "0.4.8-
|
|
41
|
-
"@dxos/context": "0.4.8-
|
|
42
|
-
"@dxos/debug": "0.4.8-
|
|
43
|
-
"@dxos/display-name": "0.4.8-
|
|
44
|
-
"@dxos/automerge": "0.4.8-
|
|
45
|
-
"@dxos/echo-schema": "0.4.8-
|
|
46
|
-
"@dxos/invariant": "0.4.8-
|
|
47
|
-
"@dxos/protocols": "0.4.8-
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/react-
|
|
50
|
-
"@dxos/react-ui": "0.4.8-
|
|
51
|
-
"@dxos/text-model": "0.4.8-
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/util": "0.4.8-
|
|
41
|
+
"@dxos/async": "0.4.8-next.fff1521",
|
|
42
|
+
"@dxos/context": "0.4.8-next.fff1521",
|
|
43
|
+
"@dxos/debug": "0.4.8-next.fff1521",
|
|
44
|
+
"@dxos/display-name": "0.4.8-next.fff1521",
|
|
45
|
+
"@dxos/automerge": "0.4.8-next.fff1521",
|
|
46
|
+
"@dxos/echo-schema": "0.4.8-next.fff1521",
|
|
47
|
+
"@dxos/invariant": "0.4.8-next.fff1521",
|
|
48
|
+
"@dxos/protocols": "0.4.8-next.fff1521",
|
|
49
|
+
"@dxos/react-async": "0.4.8-next.fff1521",
|
|
50
|
+
"@dxos/react-ui": "0.4.8-next.fff1521",
|
|
51
|
+
"@dxos/react-ui-theme": "0.4.8-next.fff1521",
|
|
52
|
+
"@dxos/text-model": "0.4.8-next.fff1521",
|
|
53
|
+
"@dxos/log": "0.4.8-next.fff1521",
|
|
54
|
+
"@dxos/util": "0.4.8-next.fff1521"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@automerge/automerge-repo-network-broadcastchannel": "^1.1.1",
|
|
@@ -68,7 +69,6 @@
|
|
|
68
69
|
"@types/react-test-renderer": "^17.0.2",
|
|
69
70
|
"@vitest/browser": "^1.3.1",
|
|
70
71
|
"@vitest/ui": "^1.3.1",
|
|
71
|
-
"deepsignal": "^1.5.0",
|
|
72
72
|
"happy-dom": "^13.3.1",
|
|
73
73
|
"jsdom": "^20.0.1",
|
|
74
74
|
"react": "^18.2.0",
|
|
@@ -78,22 +78,20 @@
|
|
|
78
78
|
"vite-plugin-top-level-await": "^1.4.1",
|
|
79
79
|
"vite-plugin-wasm": "^3.3.0",
|
|
80
80
|
"vitest": "^1.3.1",
|
|
81
|
-
"@dxos/config": "0.4.8-
|
|
82
|
-
"@dxos/echo-signals": "0.4.8-
|
|
83
|
-
"@dxos/
|
|
84
|
-
"@dxos/
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/react-ui": "0.4.8-
|
|
87
|
-
"@dxos/storybook-utils": "0.4.8-
|
|
88
|
-
"@dxos/
|
|
81
|
+
"@dxos/config": "0.4.8-next.fff1521",
|
|
82
|
+
"@dxos/echo-signals": "0.4.8-next.fff1521",
|
|
83
|
+
"@dxos/echo-typegen": "0.4.8-next.fff1521",
|
|
84
|
+
"@dxos/random": "0.4.8-next.fff1521",
|
|
85
|
+
"@dxos/keyboard": "0.4.8-next.fff1521",
|
|
86
|
+
"@dxos/react-ui": "0.4.8-next.fff1521",
|
|
87
|
+
"@dxos/storybook-utils": "0.4.8-next.fff1521",
|
|
88
|
+
"@dxos/react-client": "0.4.8-next.fff1521"
|
|
89
89
|
},
|
|
90
90
|
"peerDependencies": {
|
|
91
91
|
"@phosphor-icons/react": "^2.0.5",
|
|
92
|
-
"@vitest/browser": "^1.3.1",
|
|
93
|
-
"@vitest/ui": "^1.3.1",
|
|
94
92
|
"react": "^18.2.0",
|
|
95
93
|
"react-dom": "^18.2.0",
|
|
96
|
-
"@dxos/react-client": "0.4.8-
|
|
94
|
+
"@dxos/react-client": "0.4.8-next.fff1521"
|
|
97
95
|
},
|
|
98
96
|
"publishConfig": {
|
|
99
97
|
"access": "public"
|
|
@@ -20,6 +20,7 @@ import { withTheme } from '@dxos/storybook-utils';
|
|
|
20
20
|
|
|
21
21
|
import { TextEditor, type TextEditorProps } from './TextEditor';
|
|
22
22
|
import {
|
|
23
|
+
EditorModes,
|
|
23
24
|
annotations,
|
|
24
25
|
autocomplete,
|
|
25
26
|
blast,
|
|
@@ -37,14 +38,15 @@ import {
|
|
|
37
38
|
linkTooltip,
|
|
38
39
|
listener,
|
|
39
40
|
mention,
|
|
40
|
-
|
|
41
|
+
state,
|
|
41
42
|
table,
|
|
42
43
|
typewriter,
|
|
43
|
-
|
|
44
|
+
useComments,
|
|
44
45
|
type CommandAction,
|
|
45
46
|
type CommandOptions,
|
|
46
47
|
type Comment,
|
|
47
48
|
type CommentsOptions,
|
|
49
|
+
type SelectionState,
|
|
48
50
|
} from '../../extensions';
|
|
49
51
|
import { useDocAccessor } from '../../hooks';
|
|
50
52
|
import translations from '../../translations';
|
|
@@ -241,7 +243,7 @@ type StoryProps = {
|
|
|
241
243
|
} & Pick<TextEditorProps, 'extensions'>;
|
|
242
244
|
|
|
243
245
|
const Story = ({
|
|
244
|
-
id = '
|
|
246
|
+
id = 'editor-' + PublicKey.random().toHex().slice(0, 8),
|
|
245
247
|
text,
|
|
246
248
|
comments,
|
|
247
249
|
extensions: _extensions = [],
|
|
@@ -325,8 +327,18 @@ export const Empty = {
|
|
|
325
327
|
render: () => <Story />,
|
|
326
328
|
};
|
|
327
329
|
|
|
330
|
+
const global = new Map<string, SelectionState>();
|
|
331
|
+
|
|
328
332
|
export const Scrolling = {
|
|
329
|
-
render: () =>
|
|
333
|
+
render: () => (
|
|
334
|
+
<Story
|
|
335
|
+
text={str('# Large Document', '', large)}
|
|
336
|
+
extensions={state({
|
|
337
|
+
setState: (id, state) => global.set(id, state),
|
|
338
|
+
getState: (id) => global.get(id),
|
|
339
|
+
})}
|
|
340
|
+
/>
|
|
341
|
+
),
|
|
330
342
|
};
|
|
331
343
|
|
|
332
344
|
export const ScrollingWithImages = {
|
|
@@ -18,7 +18,7 @@ import React, {
|
|
|
18
18
|
import { log } from '@dxos/log';
|
|
19
19
|
import { isNotFalsy } from '@dxos/util';
|
|
20
20
|
|
|
21
|
-
import { documentId, editorMode } from '../../extensions';
|
|
21
|
+
import { documentId, editorMode, focusEvent } from '../../extensions';
|
|
22
22
|
import { logChanges } from '../../util';
|
|
23
23
|
|
|
24
24
|
export type CursorInfo = {
|
|
@@ -34,7 +34,7 @@ export type TextEditorProps = Pick<EditorStateConfig, 'doc' | 'selection' | 'ext
|
|
|
34
34
|
id?: string;
|
|
35
35
|
className?: string;
|
|
36
36
|
autoFocus?: boolean;
|
|
37
|
-
scrollTo?: StateEffect<
|
|
37
|
+
scrollTo?: StateEffect<unknown>;
|
|
38
38
|
moveToEndOfLine?: boolean;
|
|
39
39
|
debug?: boolean;
|
|
40
40
|
dataTestId?: string;
|
|
@@ -56,14 +56,14 @@ export const TextEditor = forwardRef<EditorView | null, TextEditorProps>(
|
|
|
56
56
|
extensions,
|
|
57
57
|
className,
|
|
58
58
|
autoFocus,
|
|
59
|
-
scrollTo = EditorView.scrollIntoView(0),
|
|
59
|
+
scrollTo = EditorView.scrollIntoView(0, { yMargin: 0 }),
|
|
60
60
|
moveToEndOfLine,
|
|
61
61
|
debug,
|
|
62
62
|
dataTestId,
|
|
63
63
|
},
|
|
64
64
|
forwardedRef,
|
|
65
65
|
) => {
|
|
66
|
-
//
|
|
66
|
+
// NOTE: Increments by 2 in strict mode.
|
|
67
67
|
const [instanceId] = useState(() => `text-editor-${++instanceCount}`);
|
|
68
68
|
|
|
69
69
|
// TODO(burdon): Make tabster optional.
|
|
@@ -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
|
|
@@ -104,7 +104,7 @@ export const TextEditor = forwardRef<EditorView | null, TextEditorProps>(
|
|
|
104
104
|
// Focus.
|
|
105
105
|
EditorView.updateListener.of((update) => {
|
|
106
106
|
update.transactions.forEach((transaction) => {
|
|
107
|
-
if (transaction.isUserEvent(
|
|
107
|
+
if (transaction.isUserEvent(focusEvent)) {
|
|
108
108
|
rootRef.current?.focus();
|
|
109
109
|
}
|
|
110
110
|
});
|
|
@@ -122,6 +122,7 @@ export const TextEditor = forwardRef<EditorView | null, TextEditorProps>(
|
|
|
122
122
|
state,
|
|
123
123
|
parent: rootRef.current!,
|
|
124
124
|
scrollTo,
|
|
125
|
+
|
|
125
126
|
// NOTE: Uncomment to debug/monitor all transactions.
|
|
126
127
|
// https://codemirror.net/docs/ref/#view.EditorView.dispatch
|
|
127
128
|
dispatchTransactions: (trs, view) => {
|
|
@@ -132,12 +133,10 @@ export const TextEditor = forwardRef<EditorView | null, TextEditorProps>(
|
|
|
132
133
|
},
|
|
133
134
|
});
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Position cursor at end of line.
|
|
138
|
-
if (moveToEndOfLine) {
|
|
136
|
+
// Position cursor at end of first line.
|
|
137
|
+
if (moveToEndOfLine && !(scrollTo || selection)) {
|
|
139
138
|
const { to } = view.state.doc.lineAt(0);
|
|
140
|
-
view
|
|
139
|
+
view.dispatch({ selection: { anchor: to } });
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
// Remove tabster attribute (rely on custom keymap).
|
|
@@ -145,9 +144,11 @@ export const TextEditor = forwardRef<EditorView | null, TextEditorProps>(
|
|
|
145
144
|
rootRef.current?.removeAttribute('data-tabster');
|
|
146
145
|
}
|
|
147
146
|
|
|
147
|
+
setView(view);
|
|
148
|
+
|
|
148
149
|
return () => {
|
|
150
|
+
log('destroy', { id, instanceId });
|
|
149
151
|
view?.destroy();
|
|
150
|
-
setView(null);
|
|
151
152
|
};
|
|
152
153
|
}, [doc, selection, extensions]);
|
|
153
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,75 @@ 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 value='comment' Icon={ChatText} onClick={() => onAction?.({ type: 'comment' })}>
|
|
398
|
+
{t('comment label')}
|
|
399
|
+
</ToolbarButton>
|
|
400
|
+
</>
|
|
341
401
|
);
|
|
342
402
|
};
|
|
343
403
|
|
|
344
404
|
export const Toolbar = {
|
|
345
405
|
Root: ToolbarRoot,
|
|
346
|
-
Button:
|
|
406
|
+
Button: ToolbarToggleButton,
|
|
347
407
|
Separator: ToolbarSeparator,
|
|
348
408
|
Markdown: MarkdownStandard,
|
|
349
|
-
|
|
409
|
+
Custom: MarkdownCustom,
|
|
410
|
+
Actions: MarkdownActions,
|
|
350
411
|
};
|
|
351
412
|
|
|
352
413
|
export { useToolbarContext };
|
|
@@ -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
|
}
|
|
@@ -116,9 +116,6 @@ const defaultSlots = {
|
|
|
116
116
|
editor: {
|
|
117
117
|
className: 'w-full bs-full',
|
|
118
118
|
},
|
|
119
|
-
content: {
|
|
120
|
-
className: '!p-2',
|
|
121
|
-
},
|
|
122
119
|
};
|
|
123
120
|
|
|
124
121
|
export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: ThemeExtensionsOptions = {}): Extension => {
|