@dxos/react-ui-editor 0.4.8-main.beadd4b → 0.4.8-main.c0be0d1
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 +52 -26
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +3 -3
- package/dist/types/src/extensions/markdown/formatting.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/package.json +24 -24
- package/src/components/TextEditor/TextEditor.tsx +2 -2
- package/src/components/Toolbar/Toolbar.stories.tsx +23 -25
- package/src/components/Toolbar/Toolbar.tsx +18 -6
- package/src/extensions/automerge/automerge.ts +4 -4
- package/src/extensions/comments.ts +2 -2
- package/src/extensions/markdown/formatting.ts +11 -7
- package/src/hooks/useDocAccessor.ts +8 -2
- package/src/hooks/useTextEditor.ts +3 -0
|
@@ -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,40 +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
|
-
|
|
81
|
-
|
|
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>
|
|
82
84
|
);
|
|
83
85
|
};
|
|
84
86
|
|
|
85
87
|
export default {
|
|
86
88
|
title: 'react-ui-editor/Toolbar',
|
|
87
89
|
component: Toolbar,
|
|
88
|
-
render: (args: any) => (
|
|
89
|
-
<Tooltip.Provider>
|
|
90
|
-
<Story {...args} />
|
|
91
|
-
</Tooltip.Provider>
|
|
92
|
-
),
|
|
93
90
|
decorators: [withTheme],
|
|
94
91
|
parameters: { translations, layout: 'fullscreen' },
|
|
92
|
+
render: (args: any) => <Story {...args} />,
|
|
95
93
|
};
|
|
96
94
|
|
|
97
95
|
const content = [
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ListChecks,
|
|
14
14
|
ListNumbers,
|
|
15
15
|
Paragraph,
|
|
16
|
+
Quotes,
|
|
16
17
|
TextStrikethrough,
|
|
17
18
|
Table,
|
|
18
19
|
TextB,
|
|
@@ -23,7 +24,6 @@ import {
|
|
|
23
24
|
TextHFive,
|
|
24
25
|
TextHSix,
|
|
25
26
|
TextItalic,
|
|
26
|
-
Quotes,
|
|
27
27
|
} from '@phosphor-icons/react';
|
|
28
28
|
import { createContext } from '@radix-ui/react-context';
|
|
29
29
|
import React, { type PropsWithChildren, useEffect, useRef, useState } from 'react';
|
|
@@ -33,9 +33,9 @@ import {
|
|
|
33
33
|
DensityProvider,
|
|
34
34
|
ElevationProvider,
|
|
35
35
|
Select,
|
|
36
|
-
type ThemedClassName,
|
|
37
36
|
Toolbar as NaturalToolbar,
|
|
38
37
|
Tooltip,
|
|
38
|
+
type ThemedClassName,
|
|
39
39
|
type ToolbarToggleGroupItemProps as NaturalToolbarToggleGroupItemProps,
|
|
40
40
|
type ToolbarButtonProps as NaturalToolbarButtonProps,
|
|
41
41
|
useTranslation,
|
|
@@ -83,7 +83,7 @@ export type Action = {
|
|
|
83
83
|
|
|
84
84
|
export type ToolbarProps = ThemedClassName<
|
|
85
85
|
PropsWithChildren<{
|
|
86
|
-
state: Formatting |
|
|
86
|
+
state: Formatting | undefined;
|
|
87
87
|
onAction?: (action: Action) => void;
|
|
88
88
|
}>
|
|
89
89
|
>;
|
|
@@ -355,6 +355,7 @@ const MarkdownCustom = ({ onUpload }: MarkdownCustomOptions = {}) => {
|
|
|
355
355
|
'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
|
|
356
356
|
},
|
|
357
357
|
});
|
|
358
|
+
|
|
358
359
|
useEffect(() => {
|
|
359
360
|
if (onUpload && acceptedFiles.length) {
|
|
360
361
|
setTimeout(async () => {
|
|
@@ -388,9 +389,20 @@ const MarkdownActions = () => {
|
|
|
388
389
|
const { onAction } = useToolbarContext('MarkdownStyles');
|
|
389
390
|
const { t } = useTranslation(translationKey);
|
|
390
391
|
return (
|
|
391
|
-
|
|
392
|
-
{
|
|
393
|
-
|
|
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
|
+
</>
|
|
394
406
|
);
|
|
395
407
|
};
|
|
396
408
|
|
|
@@ -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
|
}
|
|
@@ -15,7 +15,9 @@ 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 {
|
|
18
|
+
import { useMemo } from 'react';
|
|
19
|
+
|
|
20
|
+
import { useStateRef } from '@dxos/react-async';
|
|
19
21
|
|
|
20
22
|
// Markdown refs:
|
|
21
23
|
// https://github.github.com/gfm
|
|
@@ -56,7 +58,7 @@ export type Formatting = {
|
|
|
56
58
|
listStyle: null | 'ordered' | 'bullet' | 'task';
|
|
57
59
|
};
|
|
58
60
|
|
|
59
|
-
export const
|
|
61
|
+
export const formattingEquals = (a: Formatting, b: Formatting) =>
|
|
60
62
|
a.blockType === b.blockType &&
|
|
61
63
|
a.strong === b.strong &&
|
|
62
64
|
a.emphasis === b.emphasis &&
|
|
@@ -1061,7 +1063,7 @@ export const getFormatting = (state: EditorState): Formatting => {
|
|
|
1061
1063
|
const inline: (boolean | null)[] = [null, null, null, null];
|
|
1062
1064
|
let link: boolean = false;
|
|
1063
1065
|
let blockQuote: boolean | null = null;
|
|
1064
|
-
// False indicates mixed list styles
|
|
1066
|
+
// False indicates mixed list styles.
|
|
1065
1067
|
let listStyle: Formatting['listStyle'] | null | false = null;
|
|
1066
1068
|
|
|
1067
1069
|
// Track block context for list/blockquote handling.
|
|
@@ -1220,16 +1222,18 @@ export const getFormatting = (state: EditorState): Formatting => {
|
|
|
1220
1222
|
};
|
|
1221
1223
|
|
|
1222
1224
|
/**
|
|
1223
|
-
* Hook
|
|
1225
|
+
* Hook provides an extension to compute the current formatting state.
|
|
1224
1226
|
*/
|
|
1225
|
-
export const useFormattingState = (): [Formatting |
|
|
1226
|
-
const [state, setState] =
|
|
1227
|
+
export const useFormattingState = (): [Formatting | undefined, Extension] => {
|
|
1228
|
+
const [state, setState, stateRef] = useStateRef<Formatting>();
|
|
1227
1229
|
const observer = useMemo(
|
|
1228
1230
|
() =>
|
|
1229
1231
|
EditorView.updateListener.of((update) => {
|
|
1230
1232
|
if (update.docChanged || update.selectionSet) {
|
|
1231
1233
|
const newState = getFormatting(update.state);
|
|
1232
|
-
if (!
|
|
1234
|
+
if (!stateRef.current || !formattingEquals(stateRef.current, newState)) {
|
|
1235
|
+
// TODO(burdon): Error on first click on toolbar.
|
|
1236
|
+
// Warning: React has detected a change in the order of Hooks called by ForwardRef. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
|
|
1233
1237
|
setState(newState);
|
|
1234
1238
|
}
|
|
1235
1239
|
}
|
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
createDocAccessor,
|
|
9
|
+
type DocAccessor,
|
|
10
|
+
getTextContent,
|
|
11
|
+
type TextObject,
|
|
12
|
+
type EchoReactiveObject,
|
|
13
|
+
} from '@dxos/echo-schema';
|
|
8
14
|
|
|
9
15
|
// TODO(burdon): Factor out.
|
|
10
16
|
export const useDocAccessor = <T = any>(
|
|
11
|
-
text: TextObject
|
|
17
|
+
text: TextObject | EchoReactiveObject<{ content: string }>,
|
|
12
18
|
): { id: string; doc: string | undefined; accessor: DocAccessor<T> } => {
|
|
13
19
|
return useMemo(
|
|
14
20
|
() => ({
|
|
@@ -33,6 +33,8 @@ export const useTextEditor = (cb: () => UseTextEditorProps = () => ({}), deps: D
|
|
|
33
33
|
useEffect(() => {
|
|
34
34
|
let view: EditorView;
|
|
35
35
|
if (parentRef.current) {
|
|
36
|
+
log('create', { id });
|
|
37
|
+
|
|
36
38
|
// https://codemirror.net/docs/ref/#state.EditorStateConfig
|
|
37
39
|
const state = EditorState.create({
|
|
38
40
|
doc,
|
|
@@ -69,6 +71,7 @@ export const useTextEditor = (cb: () => UseTextEditorProps = () => ({}), deps: D
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
return () => {
|
|
74
|
+
log('destroy', { id });
|
|
72
75
|
view?.destroy();
|
|
73
76
|
};
|
|
74
77
|
}, deps);
|