@dxos/react-ui-editor 0.3.11-main.fc97a54 → 0.3.11-main.ff3a851
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 +163 -1196
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +4 -8
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +3 -5
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +1 -5
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +10 -55
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +3 -3
- package/src/components/TextEditor/hooks.stories.tsx +1 -6
- package/src/components/Toolbar/Toolbar.stories.tsx +4 -16
- package/src/components/Toolbar/Toolbar.tsx +30 -128
- package/src/extensions/comments.ts +4 -14
- package/src/extensions/markdown/formatting.ts +69 -1118
- package/src/hooks/useActionHandler.ts +16 -47
- package/src/themes/default.ts +2 -2
- package/dist/types/src/extensions/markdown/formatting.test.d.ts +0 -3
- package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +0 -1
- package/src/extensions/markdown/formatting.test.ts +0 -482
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
link,
|
|
22
22
|
table,
|
|
23
23
|
tasklist,
|
|
24
|
-
useFormattingState,
|
|
25
24
|
} from '../../extensions';
|
|
26
25
|
import { createDataExtensions, createThemeExtensions, useActionHandler, useTextEditor } from '../../hooks';
|
|
27
26
|
import { markdownTheme } from '../../themes';
|
|
@@ -42,7 +41,6 @@ type StoryProps = {
|
|
|
42
41
|
|
|
43
42
|
const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
|
|
44
43
|
const { themeMode } = useThemeContext();
|
|
45
|
-
const [formattingState, trackFormatting] = useFormattingState();
|
|
46
44
|
const { parentRef, view } = useTextEditor({
|
|
47
45
|
autoFocus,
|
|
48
46
|
doc,
|
|
@@ -71,19 +69,16 @@ const Story = ({ autoFocus, placeholder, doc, readonly }: StoryProps) => {
|
|
|
71
69
|
link(),
|
|
72
70
|
table(),
|
|
73
71
|
tasklist(),
|
|
74
|
-
trackFormatting,
|
|
75
72
|
],
|
|
76
73
|
});
|
|
77
74
|
|
|
78
75
|
const handleAction = useActionHandler(view);
|
|
79
76
|
|
|
80
|
-
// FIXME This doesn't update the state on view changes. Also not
|
|
81
|
-
// sure if view is even guaranteed to exist at this point.
|
|
82
77
|
return (
|
|
83
78
|
<div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
84
79
|
<div className='flex h-full justify-center'>
|
|
85
80
|
<div className='flex flex-col h-full w-[800px]'>
|
|
86
|
-
<Toolbar.Root onAction={handleAction}
|
|
81
|
+
<Toolbar.Root onAction={handleAction}>
|
|
87
82
|
<Toolbar.Markdown />
|
|
88
83
|
</Toolbar.Root>
|
|
89
84
|
|
|
@@ -4,26 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
6
|
|
|
7
|
+
import { faker } from '@faker-js/faker';
|
|
7
8
|
import React, { type FC, useMemo, useState } from 'react';
|
|
8
9
|
|
|
9
10
|
import { TextObject } from '@dxos/echo-schema';
|
|
10
11
|
import { PublicKey } from '@dxos/keys';
|
|
11
|
-
import { faker } from '@dxos/random';
|
|
12
12
|
import { fixedInsetFlexLayout, groupSurface, mx } from '@dxos/react-ui-theme';
|
|
13
13
|
import { withTheme } from '@dxos/storybook-utils';
|
|
14
14
|
|
|
15
15
|
import { Toolbar } from './Toolbar';
|
|
16
|
-
import {
|
|
17
|
-
code,
|
|
18
|
-
comments,
|
|
19
|
-
formatting,
|
|
20
|
-
heading,
|
|
21
|
-
image,
|
|
22
|
-
table,
|
|
23
|
-
tasklist,
|
|
24
|
-
useComments,
|
|
25
|
-
useFormattingState,
|
|
26
|
-
} from '../../extensions';
|
|
16
|
+
import { code, comments, formatting, heading, image, table, tasklist, useComments } from '../../extensions';
|
|
27
17
|
import { type Comment, useActionHandler, useEditorView, useTextModel } from '../../hooks';
|
|
28
18
|
import { MarkdownEditor } from '../TextEditor';
|
|
29
19
|
|
|
@@ -34,12 +24,11 @@ const Story: FC<{ content: string }> = ({ content }) => {
|
|
|
34
24
|
const [_comments, setComments] = useState<Comment[]>([]);
|
|
35
25
|
const [editorRef, editorView] = useEditorView();
|
|
36
26
|
const model = useTextModel({ text: item.text });
|
|
37
|
-
const [formattingState, formattingObserver] = useFormattingState();
|
|
38
27
|
const extensions = useMemo(
|
|
39
28
|
() => [
|
|
40
29
|
code(),
|
|
41
30
|
comments({
|
|
42
|
-
onCreate: (
|
|
31
|
+
onCreate: (cursor) => {
|
|
43
32
|
const id = PublicKey.random().toHex();
|
|
44
33
|
setComments((comments) => [...comments, { id, cursor }]);
|
|
45
34
|
return id;
|
|
@@ -50,7 +39,6 @@ const Story: FC<{ content: string }> = ({ content }) => {
|
|
|
50
39
|
image(),
|
|
51
40
|
table(),
|
|
52
41
|
tasklist(),
|
|
53
|
-
formattingObserver,
|
|
54
42
|
],
|
|
55
43
|
[],
|
|
56
44
|
);
|
|
@@ -66,7 +54,7 @@ const Story: FC<{ content: string }> = ({ content }) => {
|
|
|
66
54
|
<div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
67
55
|
<div className='flex h-full justify-center'>
|
|
68
56
|
<div className='flex flex-col h-full w-[800px]'>
|
|
69
|
-
<Toolbar.Root onAction={handleAction}
|
|
57
|
+
<Toolbar.Root onAction={handleAction}>
|
|
70
58
|
<Toolbar.Markdown />
|
|
71
59
|
<Toolbar.Separator />
|
|
72
60
|
<Toolbar.Extended />
|
|
@@ -4,56 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
type Icon,
|
|
7
|
+
At,
|
|
7
8
|
CaretRight,
|
|
8
9
|
ChatText,
|
|
9
10
|
Code,
|
|
10
|
-
|
|
11
|
+
Image,
|
|
11
12
|
Link,
|
|
12
13
|
ListBullets,
|
|
13
14
|
ListChecks,
|
|
14
15
|
ListNumbers,
|
|
15
|
-
Paragraph,
|
|
16
16
|
TextStrikethrough,
|
|
17
17
|
Table,
|
|
18
18
|
TextB,
|
|
19
|
-
TextHOne,
|
|
20
|
-
TextHTwo,
|
|
21
|
-
TextHThree,
|
|
22
|
-
TextHFour,
|
|
23
|
-
TextHFive,
|
|
24
|
-
TextHSix,
|
|
25
19
|
TextItalic,
|
|
26
20
|
} from '@phosphor-icons/react';
|
|
27
21
|
import { createContext } from '@radix-ui/react-context';
|
|
28
22
|
import React, { type PropsWithChildren } from 'react';
|
|
29
23
|
|
|
30
24
|
import { Button, type ButtonProps, DensityProvider, Select } from '@dxos/react-ui';
|
|
31
|
-
import { getSize
|
|
32
|
-
|
|
33
|
-
import { type Formatting } from '../../extensions';
|
|
34
|
-
|
|
35
|
-
const HeadingIcons: { [key: string]: Icon } = {
|
|
36
|
-
'0': Paragraph,
|
|
37
|
-
'1': TextHOne,
|
|
38
|
-
'2': TextHTwo,
|
|
39
|
-
'3': TextHThree,
|
|
40
|
-
'4': TextHFour,
|
|
41
|
-
'5': TextHFive,
|
|
42
|
-
'6': TextHSix,
|
|
43
|
-
};
|
|
25
|
+
import { getSize } from '@dxos/react-ui-theme';
|
|
44
26
|
|
|
45
27
|
// TODO(burdon): Revert to string to make extensible?
|
|
46
28
|
export type ActionType =
|
|
47
29
|
| 'blockquote'
|
|
48
|
-
| '
|
|
30
|
+
| 'bold'
|
|
49
31
|
| 'codeblock'
|
|
50
32
|
| 'comment'
|
|
51
33
|
| 'heading'
|
|
52
34
|
| 'image'
|
|
53
|
-
| '
|
|
54
|
-
| 'code'
|
|
35
|
+
| 'italic'
|
|
55
36
|
| 'link'
|
|
56
|
-
| 'list
|
|
37
|
+
| 'list'
|
|
57
38
|
| 'list-ordered'
|
|
58
39
|
| 'list-tasks'
|
|
59
40
|
| 'mention'
|
|
@@ -67,17 +48,16 @@ export type Action = {
|
|
|
67
48
|
};
|
|
68
49
|
|
|
69
50
|
export type ToolbarProps = PropsWithChildren<{
|
|
70
|
-
state: Formatting | null;
|
|
71
51
|
onAction?: (action: Action) => void;
|
|
72
52
|
}>;
|
|
73
53
|
|
|
74
54
|
const [ToolbarContextProvider, useToolbarContext] = createContext<ToolbarProps>('Toolbar');
|
|
75
55
|
|
|
76
|
-
const ToolbarRoot = ({ children, onAction
|
|
56
|
+
const ToolbarRoot = ({ children, onAction }: ToolbarProps) => {
|
|
77
57
|
return (
|
|
78
|
-
<ToolbarContextProvider onAction={onAction}
|
|
58
|
+
<ToolbarContextProvider onAction={onAction}>
|
|
79
59
|
<DensityProvider density='fine'>
|
|
80
|
-
<div role='toolbar' className='flex w-full shrink-0 p-
|
|
60
|
+
<div role='toolbar' className='flex w-full shrink-0 p-2 gap-4 items-center whitespace-nowrap overflow-hidden'>
|
|
81
61
|
{children}
|
|
82
62
|
</div>
|
|
83
63
|
</DensityProvider>
|
|
@@ -87,32 +67,20 @@ const ToolbarRoot = ({ children, onAction, state }: ToolbarProps) => {
|
|
|
87
67
|
|
|
88
68
|
type ToolbarButtonProps = {
|
|
89
69
|
Icon: Icon;
|
|
90
|
-
|
|
91
|
-
getState?: (state: Formatting) => boolean;
|
|
92
|
-
onClick: (state: Formatting | null) => Action | undefined;
|
|
70
|
+
onClick: () => Action | undefined;
|
|
93
71
|
} & NonNullable<Pick<ButtonProps, 'title'>>;
|
|
94
72
|
|
|
95
|
-
const ToolbarButton = ({ Icon, onClick, title
|
|
96
|
-
const { onAction
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const handleClick = (event: React.MouseEvent) => {
|
|
101
|
-
const action = onClick(state);
|
|
73
|
+
const ToolbarButton = ({ Icon, onClick, title }: ToolbarButtonProps) => {
|
|
74
|
+
const { onAction } = useToolbarContext('ToolbarButton');
|
|
75
|
+
const handleClick = () => {
|
|
76
|
+
const action = onClick();
|
|
102
77
|
if (action) {
|
|
103
78
|
onAction?.(action);
|
|
104
|
-
event.preventDefault();
|
|
105
79
|
}
|
|
106
80
|
};
|
|
107
81
|
|
|
108
82
|
return (
|
|
109
|
-
<Button
|
|
110
|
-
variant='ghost'
|
|
111
|
-
classNames={mx('p-2', active && 'ring-[1px]')}
|
|
112
|
-
onMouseDown={handleClick}
|
|
113
|
-
title={title}
|
|
114
|
-
disabled={disabled}
|
|
115
|
-
>
|
|
83
|
+
<Button variant='ghost' classNames='p-2' onClick={handleClick} title={title}>
|
|
116
84
|
<Icon className={getSize(5)} />
|
|
117
85
|
</Button>
|
|
118
86
|
);
|
|
@@ -121,20 +89,10 @@ const ToolbarButton = ({ Icon, onClick, title, getState, disable }: ToolbarButto
|
|
|
121
89
|
const ToolbarSeparator = () => <div className='grow' />;
|
|
122
90
|
|
|
123
91
|
const MarkdownHeading = () => {
|
|
124
|
-
const { onAction
|
|
125
|
-
const blockType = state ? state.blockType : 'paragraph';
|
|
126
|
-
const header = blockType && /heading(\d)/.exec(blockType);
|
|
127
|
-
const value = header ? header[1] : blockType === 'paragraph' || !blockType ? '0' : undefined;
|
|
128
|
-
const HeadingIcon = HeadingIcons[value ?? '0'];
|
|
92
|
+
const { onAction } = useToolbarContext('MarkdownFormatting');
|
|
129
93
|
return (
|
|
130
|
-
<Select.Root
|
|
131
|
-
|
|
132
|
-
value={value ?? '0'}
|
|
133
|
-
onValueChange={(value) => onAction?.({ type: 'heading', data: parseInt(value) })}
|
|
134
|
-
>
|
|
135
|
-
<Select.TriggerButton>
|
|
136
|
-
<HeadingIcon className={getSize(5)} />
|
|
137
|
-
</Select.TriggerButton>
|
|
94
|
+
<Select.Root defaultValue='0' onValueChange={(value) => onAction?.({ type: 'heading', data: parseInt(value) })}>
|
|
95
|
+
<Select.TriggerButton classNames='w-[8rem]' />
|
|
138
96
|
<Select.Portal>
|
|
139
97
|
<Select.Content>
|
|
140
98
|
<Select.Viewport>
|
|
@@ -153,89 +111,33 @@ const MarkdownHeading = () => {
|
|
|
153
111
|
|
|
154
112
|
const MarkdownStyles = () => (
|
|
155
113
|
<div role='none'>
|
|
156
|
-
<ToolbarButton
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
disable={(s) => s.blockType === 'codeblock'}
|
|
160
|
-
getState={(s) => s.strong}
|
|
161
|
-
onClick={(s) => ({ type: 'strong', data: s ? !s.strong : null })}
|
|
162
|
-
/>
|
|
163
|
-
<ToolbarButton
|
|
164
|
-
Icon={TextItalic}
|
|
165
|
-
title='Emphasis'
|
|
166
|
-
disable={(s) => s.blockType === 'codeblock'}
|
|
167
|
-
getState={(s) => s.emphasis}
|
|
168
|
-
onClick={(s) => ({ type: 'emphasis', data: s ? !s.emphasis : null })}
|
|
169
|
-
/>
|
|
170
|
-
<ToolbarButton
|
|
171
|
-
Icon={TextStrikethrough}
|
|
172
|
-
title='Strike-through'
|
|
173
|
-
disable={(s) => s.blockType === 'codeblock'}
|
|
174
|
-
getState={(s) => s.strikethrough}
|
|
175
|
-
onClick={(s) => ({ type: 'strikethrough', data: s ? !s.strikethrough : null })}
|
|
176
|
-
/>
|
|
177
|
-
<ToolbarButton
|
|
178
|
-
Icon={Code}
|
|
179
|
-
title='Inline code'
|
|
180
|
-
disable={(s) => s.blockType === 'codeblock'}
|
|
181
|
-
getState={(s) => s.code}
|
|
182
|
-
onClick={(s) => ({ type: 'code', data: s ? !s.code : null })}
|
|
183
|
-
/>
|
|
114
|
+
<ToolbarButton Icon={TextB} title='Bold' onClick={() => ({ type: 'bold' })} />
|
|
115
|
+
<ToolbarButton Icon={TextItalic} title='Italic' onClick={() => ({ type: 'italic' })} />
|
|
116
|
+
<ToolbarButton Icon={TextStrikethrough} title='Strike-through' onClick={() => ({ type: 'strikethrough' })} />
|
|
184
117
|
</div>
|
|
185
118
|
);
|
|
186
119
|
|
|
187
120
|
const MarkdownLists = () => (
|
|
188
121
|
<div role='none'>
|
|
189
|
-
<ToolbarButton
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
getState={(s) => s.listStyle === 'bullet'}
|
|
193
|
-
onClick={(s) => ({ type: 'list-bullet', data: s ? s.listStyle !== 'bullet' : null })}
|
|
194
|
-
/>
|
|
195
|
-
<ToolbarButton
|
|
196
|
-
Icon={ListNumbers}
|
|
197
|
-
title='Numbered list'
|
|
198
|
-
getState={(s) => s.listStyle === 'ordered'}
|
|
199
|
-
onClick={(s) => ({ type: 'list-ordered', data: s ? s.listStyle !== 'ordered' : null })}
|
|
200
|
-
/>
|
|
201
|
-
<ToolbarButton
|
|
202
|
-
Icon={ListChecks}
|
|
203
|
-
title='Task list'
|
|
204
|
-
getState={(s) => s.listStyle === 'task'}
|
|
205
|
-
onClick={(s) => ({ type: 'list-tasks', data: s ? s.listStyle !== 'task' : null })}
|
|
206
|
-
/>
|
|
122
|
+
<ToolbarButton Icon={ListBullets} title='Bullet list' onClick={() => ({ type: 'list' })} />
|
|
123
|
+
<ToolbarButton Icon={ListNumbers} title='Numbered list' onClick={() => ({ type: 'list-ordered' })} />
|
|
124
|
+
<ToolbarButton Icon={ListChecks} title='Task list' onClick={() => ({ type: 'list-tasks' })} />
|
|
207
125
|
</div>
|
|
208
126
|
);
|
|
209
127
|
|
|
210
128
|
const MarkdownBlocks = () => (
|
|
211
129
|
<div role='none'>
|
|
212
|
-
<ToolbarButton
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
getState={(s) => s.blockQuote}
|
|
216
|
-
onClick={(s) => ({ type: 'blockquote', data: s ? !s.blockQuote : null })}
|
|
217
|
-
/>
|
|
218
|
-
<ToolbarButton
|
|
219
|
-
Icon={CodeBlock}
|
|
220
|
-
title='Code block'
|
|
221
|
-
disable={(s) => !s.blankLine}
|
|
222
|
-
getState={(s) => s.blockType === 'codeblock'}
|
|
223
|
-
onClick={(s) => ({ type: 'codeblock', data: s ? s.blockType !== 'codeblock' : null })}
|
|
224
|
-
/>
|
|
225
|
-
<ToolbarButton Icon={Table} title='Table' disable={(s) => !s.blankLine} onClick={() => ({ type: 'table' })} />
|
|
130
|
+
<ToolbarButton Icon={CaretRight} title='Block quite' onClick={() => ({ type: 'blockquote' })} />
|
|
131
|
+
<ToolbarButton Icon={Code} title='Code block' onClick={() => ({ type: 'codeblock' })} />
|
|
132
|
+
<ToolbarButton Icon={Table} title='Table' onClick={() => ({ type: 'table' })} />
|
|
226
133
|
</div>
|
|
227
134
|
);
|
|
228
135
|
|
|
229
136
|
const MarkdownLinks = () => (
|
|
230
137
|
<div role='none'>
|
|
231
|
-
<ToolbarButton
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
getState={(s) => s.link}
|
|
235
|
-
onClick={(s) => ({ type: 'link', data: s ? !s.link : null })}
|
|
236
|
-
/>
|
|
237
|
-
{/* <ToolbarButton Icon={At} title='Mention' onClick={() => ({ type: 'mention' })} /> */}
|
|
238
|
-
{/* <ToolbarButton Icon={Image} title='Image' onClick={() => ({ type: 'image' })} /> */}
|
|
138
|
+
<ToolbarButton Icon={Link} title='Link' onClick={() => ({ type: 'link' })} />
|
|
139
|
+
<ToolbarButton Icon={At} title='Mention' onClick={() => ({ type: 'mention' })} />
|
|
140
|
+
<ToolbarButton Icon={Image} title='Image' onClick={() => ({ type: 'image' })} />
|
|
239
141
|
</div>
|
|
240
142
|
);
|
|
241
143
|
|
|
@@ -18,22 +18,12 @@ import { callbackWrapper } from '../util';
|
|
|
18
18
|
|
|
19
19
|
// TODO(burdon): Reconcile with theme.
|
|
20
20
|
const styles = EditorView.baseTheme({
|
|
21
|
-
'&
|
|
22
|
-
'&dark .cm-comment, &dark .cm-comment-current': { mixBlendMode: 'plus-lighter' },
|
|
23
|
-
'&light .cm-comment': {
|
|
21
|
+
'& .cm-comment': {
|
|
24
22
|
backgroundColor: getToken('extend.colors.yellow.50'),
|
|
25
23
|
},
|
|
26
|
-
'&
|
|
24
|
+
'& .cm-comment-current': {
|
|
27
25
|
backgroundColor: getToken('extend.colors.yellow.100'),
|
|
28
26
|
},
|
|
29
|
-
'&dark .cm-comment': {
|
|
30
|
-
color: getToken('extend.colors.yellow.50'),
|
|
31
|
-
backgroundColor: getToken('extend.colors.yellow.900'),
|
|
32
|
-
},
|
|
33
|
-
'&dark .cm-comment-current': {
|
|
34
|
-
color: getToken('extend.colors.yellow.100'),
|
|
35
|
-
backgroundColor: getToken('extend.colors.yellow.950'),
|
|
36
|
-
},
|
|
37
27
|
});
|
|
38
28
|
|
|
39
29
|
const commentMark = Decoration.mark({ class: 'cm-comment' });
|
|
@@ -156,7 +146,7 @@ export type CommentsOptions = {
|
|
|
156
146
|
/**
|
|
157
147
|
* Called to create a new thread and return the thread id.
|
|
158
148
|
*/
|
|
159
|
-
onCreate?: (
|
|
149
|
+
onCreate?: (cursor: string, location?: Rect | null) => string | undefined;
|
|
160
150
|
/**
|
|
161
151
|
* Selection cut/deleted.
|
|
162
152
|
*/
|
|
@@ -316,7 +306,7 @@ export const createComment: Command = (view) => {
|
|
|
316
306
|
const cursor = Cursor.getCursorFromRange(view.state, { from, to });
|
|
317
307
|
if (cursor) {
|
|
318
308
|
// Create thread via callback.
|
|
319
|
-
const id = options.onCreate?.(
|
|
309
|
+
const id = options.onCreate?.(cursor, view.coordsAtPos(from));
|
|
320
310
|
if (id) {
|
|
321
311
|
// Update range.
|
|
322
312
|
view.dispatch({
|