@dxos/react-ui-editor 0.6.7 → 0.6.8-main.3be982f
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 +1019 -796
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/{hooks/InputMode.stories.d.ts → InputMode.stories.d.ts} +6 -4
- package/dist/types/src/InputMode.stories.d.ts.map +1 -0
- package/dist/types/src/{hooks/TextEditor.stories.d.ts → TextEditor.stories.d.ts} +43 -26
- package/dist/types/src/TextEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +9 -9
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +10 -0
- package/dist/types/src/defaults.d.ts.map +1 -0
- package/dist/types/src/extensions/autocomplete.d.ts +2 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +6 -4
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/blast.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +1 -1
- package/dist/types/src/extensions/command/state.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/folding.d.ts +7 -0
- package/dist/types/src/extensions/folding.d.ts.map +1 -0
- 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/decorate.d.ts +5 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +9 -9
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/image.d.ts +1 -1
- package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link-paste.d.ts +6 -0
- package/dist/types/src/extensions/markdown/link-paste.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/link-paste.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/link-paste.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/parser.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/parser.test.d.ts.map +1 -0
- package/dist/types/src/extensions/state.d.ts.map +1 -1
- package/dist/types/src/extensions/util/error.d.ts +2 -0
- package/dist/types/src/extensions/util/error.d.ts.map +1 -0
- package/dist/types/src/extensions/util/index.d.ts +3 -1
- package/dist/types/src/extensions/util/index.d.ts.map +1 -1
- package/dist/types/src/extensions/util/overlap.d.ts.map +1 -1
- package/dist/types/src/extensions/util/react.d.ts +3 -0
- package/dist/types/src/extensions/util/react.d.ts.map +1 -0
- package/dist/types/src/hooks/useActionHandler.d.ts +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/index.d.ts +1 -1
- package/dist/types/src/styles/index.d.ts.map +1 -1
- package/dist/types/src/styles/markdown.d.ts +0 -1
- package/dist/types/src/styles/markdown.d.ts.map +1 -1
- package/dist/types/src/styles/theme.d.ts +36 -0
- package/dist/types/src/styles/theme.d.ts.map +1 -0
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +26 -29
- package/src/{hooks/InputMode.stories.tsx → InputMode.stories.tsx} +6 -11
- package/src/{hooks/TextEditor.stories.tsx → TextEditor.stories.tsx} +139 -92
- package/src/components/Toolbar/Toolbar.tsx +17 -9
- package/src/defaults.ts +28 -0
- package/src/extensions/autocomplete.ts +24 -18
- package/src/extensions/automerge/automerge.stories.tsx +4 -6
- package/src/extensions/comments.ts +4 -0
- package/src/extensions/factories.ts +3 -2
- package/src/extensions/folding.tsx +34 -0
- package/src/extensions/index.ts +1 -0
- package/src/extensions/markdown/bundle.ts +1 -1
- package/src/extensions/markdown/decorate.ts +359 -129
- package/src/extensions/markdown/formatting.ts +10 -12
- package/src/extensions/markdown/image.ts +3 -1
- package/src/extensions/markdown/link-paste.test.ts +28 -0
- package/src/extensions/markdown/link-paste.ts +104 -0
- package/src/extensions/markdown/parser.test.ts +47 -0
- package/src/extensions/markdown/table.ts +21 -24
- package/src/extensions/util/error.ts +15 -0
- package/src/extensions/util/index.ts +3 -1
- package/src/extensions/util/overlap.ts +1 -0
- package/src/extensions/util/react.tsx +15 -0
- package/src/hooks/useTextEditor.ts +1 -1
- package/src/index.ts +2 -2
- package/src/styles/index.ts +1 -1
- package/src/styles/markdown.ts +4 -3
- package/src/{themes/default.ts → styles/theme.ts} +51 -43
- package/src/styles/tokens.ts +0 -1
- package/src/translations.ts +2 -0
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +0 -57
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/linkPaste.d.ts +0 -16
- package/dist/types/src/extensions/markdown/linkPaste.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/linkPaste.test.d.ts +0 -2
- package/dist/types/src/extensions/markdown/linkPaste.test.d.ts.map +0 -1
- package/dist/types/src/hooks/InputMode.stories.d.ts.map +0 -1
- package/dist/types/src/hooks/TextEditor.stories.d.ts.map +0 -1
- package/dist/types/src/styles/layout.d.ts +0 -4
- package/dist/types/src/styles/layout.d.ts.map +0 -1
- package/dist/types/src/themes/default.d.ts +0 -14
- package/dist/types/src/themes/default.d.ts.map +0 -1
- package/dist/types/src/themes/index.d.ts +0 -2
- package/dist/types/src/themes/index.d.ts.map +0 -1
- package/src/components/Toolbar/Toolbar.stories.tsx +0 -119
- package/src/extensions/markdown/linkPaste.test.ts +0 -45
- package/src/extensions/markdown/linkPaste.ts +0 -113
- package/src/styles/layout.ts +0 -9
- package/src/themes/index.ts +0 -5
|
@@ -3,32 +3,31 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
|
+
|
|
6
7
|
import { markdown } from '@codemirror/lang-markdown';
|
|
7
8
|
import { ArrowSquareOut, X } from '@phosphor-icons/react';
|
|
8
9
|
import { effect, useSignal } from '@preact/signals-react';
|
|
9
10
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
10
|
-
import React, { type FC, type KeyboardEvent, StrictMode,
|
|
11
|
+
import React, { type FC, type KeyboardEvent, StrictMode, useState } from 'react';
|
|
11
12
|
import { createRoot } from 'react-dom/client';
|
|
12
13
|
|
|
13
|
-
import {
|
|
14
|
-
import { create } from '@dxos/echo-schema';
|
|
14
|
+
import { create, Expando } from '@dxos/echo-schema';
|
|
15
15
|
import { keySymbols, parseShortcut } from '@dxos/keyboard';
|
|
16
16
|
import { PublicKey } from '@dxos/keys';
|
|
17
17
|
import { log } from '@dxos/log';
|
|
18
18
|
import { faker } from '@dxos/random';
|
|
19
19
|
import { createDocAccessor, createEchoObject } from '@dxos/react-client/echo';
|
|
20
20
|
import { Button, DensityProvider, Input, ThemeProvider, useThemeContext } from '@dxos/react-ui';
|
|
21
|
-
import { baseSurface, defaultTx,
|
|
22
|
-
import { withTheme } from '@dxos/storybook-utils';
|
|
21
|
+
import { baseSurface, defaultTx, mx, getSize } from '@dxos/react-ui-theme';
|
|
22
|
+
import { withFullscreen, withTheme } from '@dxos/storybook-utils';
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import { editorContent } from './defaults';
|
|
25
25
|
import {
|
|
26
26
|
InputModeExtensions,
|
|
27
27
|
annotations,
|
|
28
28
|
autocomplete,
|
|
29
29
|
blast,
|
|
30
30
|
command,
|
|
31
|
-
// commentBlock,
|
|
32
31
|
comments,
|
|
33
32
|
createBasicExtensions,
|
|
34
33
|
createDataExtensions,
|
|
@@ -38,6 +37,7 @@ import {
|
|
|
38
37
|
decorateMarkdown,
|
|
39
38
|
defaultOptions,
|
|
40
39
|
dropFile,
|
|
40
|
+
folding,
|
|
41
41
|
formattingKeymap,
|
|
42
42
|
image,
|
|
43
43
|
linkTooltip,
|
|
@@ -51,8 +51,9 @@ import {
|
|
|
51
51
|
type Comment,
|
|
52
52
|
type CommentsOptions,
|
|
53
53
|
type SelectionState,
|
|
54
|
-
} from '
|
|
55
|
-
import
|
|
54
|
+
} from './extensions';
|
|
55
|
+
import { useTextEditor, type UseTextEditorProps } from './hooks';
|
|
56
|
+
import translations from './translations';
|
|
56
57
|
|
|
57
58
|
faker.seed(101);
|
|
58
59
|
|
|
@@ -62,10 +63,10 @@ const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
|
|
|
62
63
|
|
|
63
64
|
const img = '';
|
|
64
65
|
|
|
65
|
-
const
|
|
66
|
+
const content = {
|
|
66
67
|
tasks: str(
|
|
67
68
|
//
|
|
68
|
-
'
|
|
69
|
+
'### Task List',
|
|
69
70
|
'',
|
|
70
71
|
`- [x] ${faker.lorem.sentences()}`,
|
|
71
72
|
`- [ ] ${faker.lorem.sentences()}`,
|
|
@@ -75,28 +76,38 @@ const text = {
|
|
|
75
76
|
'',
|
|
76
77
|
),
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
bullets: str(
|
|
79
80
|
//
|
|
80
|
-
'
|
|
81
|
+
'### BulletList',
|
|
81
82
|
'',
|
|
82
83
|
`- ${faker.lorem.sentences()}`,
|
|
83
84
|
`- ${faker.lorem.sentences()}`,
|
|
85
|
+
` - ${faker.lorem.sentences()}`,
|
|
86
|
+
` - ${faker.lorem.sentences()}`,
|
|
84
87
|
`- ${faker.lorem.sentences()}`,
|
|
85
88
|
'',
|
|
86
89
|
),
|
|
87
90
|
|
|
88
91
|
numbered: str(
|
|
89
92
|
//
|
|
90
|
-
'
|
|
93
|
+
'### OrderedList (part 1)',
|
|
94
|
+
'',
|
|
95
|
+
`1. ${faker.lorem.sentences()}`,
|
|
96
|
+
`1. ${faker.lorem.sentences()}`,
|
|
97
|
+
`1. ${faker.lorem.sentences()}`,
|
|
98
|
+
` 1. ${faker.lorem.sentences()}`,
|
|
99
|
+
` 1. ${faker.lorem.sentences()}`,
|
|
100
|
+
` 1. ${faker.lorem.sentences()}`,
|
|
101
|
+
`1. ${faker.lorem.sentences()}`,
|
|
102
|
+
'',
|
|
103
|
+
'### OrderedList (part 2)',
|
|
91
104
|
'',
|
|
92
105
|
`1. ${faker.lorem.sentences()}`,
|
|
93
|
-
`2. ${faker.lorem.sentences()}`,
|
|
94
|
-
`3. ${faker.lorem.sentences()}`,
|
|
95
106
|
'',
|
|
96
107
|
),
|
|
97
108
|
|
|
98
109
|
code: str(
|
|
99
|
-
'
|
|
110
|
+
'### Code',
|
|
100
111
|
'',
|
|
101
112
|
'```',
|
|
102
113
|
'$ ls -las',
|
|
@@ -115,16 +126,18 @@ const text = {
|
|
|
115
126
|
comment: str('<!--', 'A comment', '-->', '', 'No comment.', 'Partial comment. <!-- comment. -->'),
|
|
116
127
|
|
|
117
128
|
links: str(
|
|
118
|
-
'
|
|
129
|
+
'### Links',
|
|
119
130
|
'',
|
|
120
131
|
'This is a naked link https://dxos.org within a sentence.',
|
|
121
132
|
'',
|
|
122
133
|
'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
|
|
123
134
|
'',
|
|
135
|
+
'This is all about https://dxos.org and related technologies.',
|
|
136
|
+
'',
|
|
124
137
|
),
|
|
125
138
|
|
|
126
139
|
table: str(
|
|
127
|
-
'
|
|
140
|
+
'### Tables',
|
|
128
141
|
'',
|
|
129
142
|
`| ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} |`,
|
|
130
143
|
`|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|`,
|
|
@@ -134,49 +147,56 @@ const text = {
|
|
|
134
147
|
'',
|
|
135
148
|
),
|
|
136
149
|
|
|
137
|
-
image: str('
|
|
150
|
+
image: str('### Image', '', img),
|
|
138
151
|
|
|
139
152
|
headings: str(
|
|
140
153
|
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
|
141
154
|
),
|
|
142
155
|
|
|
156
|
+
formatting: str('### Formatting', 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.'),
|
|
157
|
+
|
|
158
|
+
blockquotes: str(
|
|
159
|
+
'### Blockquotes',
|
|
160
|
+
'> This is a block quote.',
|
|
161
|
+
'',
|
|
162
|
+
'> This is a long wrapping block quote. Neque reiciendis ullam quae error labore sit, at, et, nulla, aut at nostrum omnis quas nostrum, at consectetur vitae eos asperiores non omnis ullam in beatae at vitae deserunt asperiores sapiente.',
|
|
163
|
+
'',
|
|
164
|
+
'> This is',
|
|
165
|
+
'> a multi-line',
|
|
166
|
+
'> block quote.',
|
|
167
|
+
),
|
|
168
|
+
|
|
143
169
|
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
|
144
170
|
|
|
145
171
|
footer: str('', '', '', '', ''),
|
|
146
172
|
};
|
|
147
173
|
|
|
148
|
-
const
|
|
174
|
+
const text = str(
|
|
149
175
|
'# Markdown',
|
|
176
|
+
'Composer Markdown Editor',
|
|
150
177
|
'',
|
|
151
|
-
|
|
152
|
-
'',
|
|
153
|
-
'> This is a long wrapping block quote. Neque reiciendis ullam quae error labore sit, at, et, nulla, aut at nostrum omnis quas nostrum, at consectetur vitae eos asperiores non omnis ullam in beatae at vitae deserunt asperiores sapiente.',
|
|
154
|
-
'',
|
|
155
|
-
'> This is',
|
|
156
|
-
'> a multi-line',
|
|
157
|
-
'> block quote.',
|
|
158
|
-
'',
|
|
159
|
-
'This is all about https://dxos.org and related technologies.',
|
|
160
|
-
'',
|
|
161
|
-
'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.',
|
|
162
|
-
'',
|
|
163
|
-
'---',
|
|
164
|
-
text.links,
|
|
165
|
-
'---',
|
|
166
|
-
text.list,
|
|
167
|
-
'---',
|
|
168
|
-
text.tasks,
|
|
169
|
-
'---',
|
|
170
|
-
text.numbered,
|
|
178
|
+
|
|
171
179
|
'---',
|
|
172
|
-
|
|
180
|
+
'## Basics',
|
|
181
|
+
content.blockquotes,
|
|
182
|
+
content.formatting,
|
|
183
|
+
content.links,
|
|
184
|
+
|
|
173
185
|
'---',
|
|
174
|
-
|
|
186
|
+
'## Lists',
|
|
187
|
+
content.bullets,
|
|
188
|
+
content.tasks,
|
|
189
|
+
content.numbered,
|
|
190
|
+
|
|
175
191
|
'---',
|
|
176
|
-
|
|
192
|
+
content.headings,
|
|
193
|
+
|
|
177
194
|
'---',
|
|
178
|
-
|
|
179
|
-
|
|
195
|
+
'## Misc',
|
|
196
|
+
content.code,
|
|
197
|
+
content.table,
|
|
198
|
+
content.image,
|
|
199
|
+
content.footer,
|
|
180
200
|
);
|
|
181
201
|
|
|
182
202
|
const links = [
|
|
@@ -249,67 +269,73 @@ type StoryProps = {
|
|
|
249
269
|
const Story = ({
|
|
250
270
|
id = 'editor-' + PublicKey.random().toHex().slice(0, 8),
|
|
251
271
|
text,
|
|
252
|
-
extensions
|
|
272
|
+
extensions,
|
|
253
273
|
readonly,
|
|
254
274
|
placeholder = 'New document.',
|
|
255
275
|
selection,
|
|
256
276
|
}: StoryProps) => {
|
|
257
|
-
const [object] = useState(createEchoObject(create(
|
|
277
|
+
const [object] = useState(createEchoObject(create(Expando, { content: text ?? '' })));
|
|
258
278
|
const { themeMode } = useThemeContext();
|
|
259
|
-
const extensions = useMemo(
|
|
260
|
-
() => [
|
|
261
|
-
createBasicExtensions({ readonly, placeholder }),
|
|
262
|
-
createMarkdownExtensions({ themeMode }),
|
|
263
|
-
createThemeExtensions({
|
|
264
|
-
themeMode,
|
|
265
|
-
slots: {
|
|
266
|
-
editor: { className: 'min-bs-dvh px-8 bg-white dark:bg-black' },
|
|
267
|
-
},
|
|
268
|
-
}),
|
|
269
|
-
createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
|
|
270
|
-
_extensions,
|
|
271
|
-
],
|
|
272
|
-
[_extensions, object],
|
|
273
|
-
);
|
|
274
|
-
|
|
275
279
|
const { parentRef, focusAttributes } = useTextEditor(
|
|
276
|
-
() => ({
|
|
277
|
-
|
|
280
|
+
() => ({
|
|
281
|
+
id,
|
|
282
|
+
initialValue: text,
|
|
283
|
+
extensions: [
|
|
284
|
+
createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
|
|
285
|
+
createBasicExtensions({ readonly, placeholder }),
|
|
286
|
+
createMarkdownExtensions({ themeMode }),
|
|
287
|
+
createThemeExtensions({
|
|
288
|
+
themeMode,
|
|
289
|
+
slots: {
|
|
290
|
+
content: {
|
|
291
|
+
className: editorContent,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
extensions || [],
|
|
296
|
+
],
|
|
297
|
+
selection,
|
|
298
|
+
}),
|
|
299
|
+
[object, extensions, themeMode],
|
|
278
300
|
);
|
|
279
301
|
|
|
280
|
-
return <div role='none'
|
|
302
|
+
return <div role='none' className='flex w-full overflow-hidden' ref={parentRef} {...focusAttributes} />;
|
|
281
303
|
};
|
|
282
304
|
|
|
283
305
|
export default {
|
|
284
|
-
title: 'react-ui-editor/
|
|
285
|
-
decorators: [withTheme],
|
|
306
|
+
title: 'react-ui-editor/TextEditor',
|
|
307
|
+
decorators: [withTheme, withFullscreen()],
|
|
286
308
|
render: Story,
|
|
287
309
|
parameters: { translations, layout: 'fullscreen' },
|
|
288
310
|
};
|
|
289
311
|
|
|
290
|
-
// TODO(burdon): Test invalid inputs (e.g., selection).
|
|
291
|
-
|
|
292
312
|
const defaults = [
|
|
293
313
|
autocomplete({
|
|
294
314
|
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
295
315
|
}),
|
|
296
|
-
decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100 }),
|
|
316
|
+
decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100, numberedHeadings: { from: 1, to: 4 } }),
|
|
297
317
|
formattingKeymap(),
|
|
298
|
-
image(),
|
|
299
|
-
table(),
|
|
300
318
|
linkTooltip(renderLinkTooltip),
|
|
301
319
|
];
|
|
302
320
|
|
|
303
321
|
export const Default = {
|
|
304
|
-
render: () => <Story text={
|
|
322
|
+
render: () => <Story text={text} extensions={defaults} />,
|
|
305
323
|
};
|
|
306
324
|
|
|
307
325
|
export const Readonly = {
|
|
308
|
-
render: () => <Story text={
|
|
326
|
+
render: () => <Story text={text} extensions={defaults} readonly />,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export const Empty = {
|
|
330
|
+
render: () => <Story extensions={defaults} />,
|
|
309
331
|
};
|
|
310
332
|
|
|
311
333
|
export const NoExtensions = {
|
|
312
|
-
render: () => <Story text={
|
|
334
|
+
render: () => <Story text={text} />,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export const Folding = {
|
|
338
|
+
render: () => <Story text={text} extensions={[folding()]} />,
|
|
313
339
|
};
|
|
314
340
|
|
|
315
341
|
const large = faker.helpers.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 }).join('\n\n');
|
|
@@ -319,8 +345,14 @@ const largeWithImages = faker.helpers
|
|
|
319
345
|
.flatMap((x) => x)
|
|
320
346
|
.join('\n\n');
|
|
321
347
|
|
|
322
|
-
|
|
323
|
-
|
|
348
|
+
const headings = str(
|
|
349
|
+
...[1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 2, 3, 3, 2, 2, 6, 1]
|
|
350
|
+
.map((level) => ['#'.repeat(level) + ' ' + faker.lorem.sentence(3), faker.lorem.sentences(), ''])
|
|
351
|
+
.flat(),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
export const Headings = {
|
|
355
|
+
render: () => <Story text={headings} extensions={decorateMarkdown({ numberedHeadings: { from: 2, to: 4 } })} />,
|
|
324
356
|
};
|
|
325
357
|
|
|
326
358
|
const global = new Map<string, SelectionState>();
|
|
@@ -342,31 +374,46 @@ export const ScrollingWithImages = {
|
|
|
342
374
|
};
|
|
343
375
|
|
|
344
376
|
export const Links = {
|
|
345
|
-
render: () => <Story text={str(
|
|
377
|
+
render: () => <Story text={str(content.links, content.footer)} extensions={[linkTooltip(renderLinkTooltip)]} />,
|
|
346
378
|
};
|
|
347
379
|
|
|
348
380
|
export const Image = {
|
|
349
|
-
render: () => <Story text={str(
|
|
381
|
+
render: () => <Story text={str(content.image, content.footer)} extensions={[image()]} />,
|
|
350
382
|
};
|
|
351
383
|
|
|
352
384
|
export const Code = {
|
|
353
|
-
render: () => <Story text={str(
|
|
385
|
+
render: () => <Story text={str(content.code, content.footer)} extensions={[decorateMarkdown()]} />,
|
|
354
386
|
};
|
|
355
387
|
|
|
356
388
|
export const Lists = {
|
|
357
389
|
render: () => (
|
|
358
|
-
<Story
|
|
390
|
+
<Story
|
|
391
|
+
text={str(content.tasks, '', content.bullets, '', content.numbered, content.footer)}
|
|
392
|
+
extensions={[decorateMarkdown()]}
|
|
393
|
+
/>
|
|
359
394
|
),
|
|
360
395
|
};
|
|
361
396
|
|
|
397
|
+
export const BulletList = {
|
|
398
|
+
render: () => <Story text={str(content.bullets, content.footer)} extensions={[decorateMarkdown()]} />,
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
export const OrderedList = {
|
|
402
|
+
render: () => <Story text={str(content.numbered, content.footer)} extensions={[decorateMarkdown()]} />,
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export const TaskList = {
|
|
406
|
+
render: () => <Story text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} />,
|
|
407
|
+
};
|
|
408
|
+
|
|
362
409
|
export const Table = {
|
|
363
|
-
render: () => <Story text={str(
|
|
410
|
+
render: () => <Story text={str(content.table, content.footer)} extensions={[table()]} />,
|
|
364
411
|
};
|
|
365
412
|
|
|
366
413
|
export const Autocomplete = {
|
|
367
414
|
render: () => (
|
|
368
415
|
<Story
|
|
369
|
-
text={str('# Autocomplete', '', 'Press Ctrl-Space...',
|
|
416
|
+
text={str('# Autocomplete', '', 'Press Ctrl-Space...', content.footer)}
|
|
370
417
|
extensions={[
|
|
371
418
|
decorateMarkdown({ renderLinkButton }),
|
|
372
419
|
autocomplete({
|
|
@@ -380,7 +427,7 @@ export const Autocomplete = {
|
|
|
380
427
|
export const CommentedOut = {
|
|
381
428
|
render: () => (
|
|
382
429
|
<Story
|
|
383
|
-
text={str('# Commented out', '',
|
|
430
|
+
text={str('# Commented out', '', content.comment, content.footer)}
|
|
384
431
|
extensions={[
|
|
385
432
|
decorateMarkdown(),
|
|
386
433
|
markdown(),
|
|
@@ -393,7 +440,7 @@ export const CommentedOut = {
|
|
|
393
440
|
export const Mention = {
|
|
394
441
|
render: () => (
|
|
395
442
|
<Story
|
|
396
|
-
text={str('# Mention', '', 'Type @...',
|
|
443
|
+
text={str('# Mention', '', 'Type @...', content.footer)}
|
|
397
444
|
extensions={[
|
|
398
445
|
mention({
|
|
399
446
|
onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
|
|
@@ -466,7 +513,7 @@ export const Comments = {
|
|
|
466
513
|
const _comments = useSignal<Comment[]>([]);
|
|
467
514
|
return (
|
|
468
515
|
<Story
|
|
469
|
-
text={str('# Comments', '',
|
|
516
|
+
text={str('# Comments', '', content.paragraphs, content.footer)}
|
|
470
517
|
extensions={[
|
|
471
518
|
createExternalCommentSync(
|
|
472
519
|
'test',
|
|
@@ -504,7 +551,7 @@ export const Comments = {
|
|
|
504
551
|
export const Vim = {
|
|
505
552
|
render: () => (
|
|
506
553
|
<Story
|
|
507
|
-
text={str('# Vim Mode', '', 'The distant future. The year 2000.', '',
|
|
554
|
+
text={str('# Vim Mode', '', 'The distant future. The year 2000.', '', content.paragraphs)}
|
|
508
555
|
extensions={[defaults, InputModeExtensions.vim]}
|
|
509
556
|
/>
|
|
510
557
|
),
|
|
@@ -534,7 +581,7 @@ const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewrite
|
|
|
534
581
|
export const Listener = {
|
|
535
582
|
render: () => (
|
|
536
583
|
<Story
|
|
537
|
-
text={str('# Listener', '',
|
|
584
|
+
text={str('# Listener', '', content.footer)}
|
|
538
585
|
extensions={[
|
|
539
586
|
listener({
|
|
540
587
|
onFocus: (focusing) => {
|
|
@@ -552,7 +599,7 @@ export const Listener = {
|
|
|
552
599
|
export const Typewriter = {
|
|
553
600
|
render: () => (
|
|
554
601
|
<Story
|
|
555
|
-
text={str('# Typewriter', '',
|
|
602
|
+
text={str('# Typewriter', '', content.paragraphs, content.footer)}
|
|
556
603
|
extensions={[typewriter({ items: typewriterItems })]}
|
|
557
604
|
/>
|
|
558
605
|
),
|
|
@@ -561,7 +608,7 @@ export const Typewriter = {
|
|
|
561
608
|
export const Blast = {
|
|
562
609
|
render: () => (
|
|
563
610
|
<Story
|
|
564
|
-
text={str('# Blast', '',
|
|
611
|
+
text={str('# Blast', '', content.paragraphs, content.code, content.paragraphs)}
|
|
565
612
|
extensions={[
|
|
566
613
|
typewriter({ items: typewriterItems }),
|
|
567
614
|
blast(
|
|
@@ -61,6 +61,8 @@ const ToolbarSeparator = () => <div role='separator' className='grow' />;
|
|
|
61
61
|
// Root
|
|
62
62
|
//
|
|
63
63
|
|
|
64
|
+
const [ToolbarContextProvider, useToolbarContext] = createContext<ToolbarProps>('Toolbar');
|
|
65
|
+
|
|
64
66
|
export type ToolbarProps = ThemedClassName<
|
|
65
67
|
PropsWithChildren<{
|
|
66
68
|
state: (Formatting & { comment?: boolean; mode?: EditorViewMode; selection?: boolean }) | undefined;
|
|
@@ -68,8 +70,6 @@ export type ToolbarProps = ThemedClassName<
|
|
|
68
70
|
}>
|
|
69
71
|
>;
|
|
70
72
|
|
|
71
|
-
const [ToolbarContextProvider, useToolbarContext] = createContext<ToolbarProps>('Toolbar');
|
|
72
|
-
|
|
73
73
|
const ToolbarRoot = ({ children, onAction, classNames, state }: ToolbarProps) => {
|
|
74
74
|
return (
|
|
75
75
|
<ToolbarContextProvider onAction={onAction} state={state}>
|
|
@@ -320,11 +320,11 @@ const MarkdownHeading = () => {
|
|
|
320
320
|
//
|
|
321
321
|
|
|
322
322
|
const markdownStyles: ButtonProps[] = [
|
|
323
|
-
{ type: 'strong', Icon: TextB, getState: (state) => state
|
|
324
|
-
{ type: 'emphasis', Icon: TextItalic, getState: (state) => state
|
|
325
|
-
{ type: 'strikethrough', Icon: TextStrikethrough, getState: (state) => state
|
|
326
|
-
{ type: 'code', Icon: Code, getState: (state) => state
|
|
327
|
-
{ type: 'link', Icon: Link, getState: (state) => state
|
|
323
|
+
{ type: 'strong', Icon: TextB, getState: (state) => !!state?.strong },
|
|
324
|
+
{ type: 'emphasis', Icon: TextItalic, getState: (state) => !!state?.emphasis },
|
|
325
|
+
{ type: 'strikethrough', Icon: TextStrikethrough, getState: (state) => !!state?.strikethrough },
|
|
326
|
+
{ type: 'code', Icon: Code, getState: (state) => !!state?.code },
|
|
327
|
+
{ type: 'link', Icon: Link, getState: (state) => !!state?.link },
|
|
328
328
|
];
|
|
329
329
|
|
|
330
330
|
const MarkdownStyles = () => {
|
|
@@ -380,7 +380,7 @@ const markdownBlocks: ButtonProps[] = [
|
|
|
380
380
|
{
|
|
381
381
|
type: 'blockquote',
|
|
382
382
|
Icon: Quotes,
|
|
383
|
-
getState: (state) => state
|
|
383
|
+
getState: (state) => !!state?.blockQuote,
|
|
384
384
|
},
|
|
385
385
|
{
|
|
386
386
|
type: 'codeblock',
|
|
@@ -482,6 +482,14 @@ const MarkdownCustom = ({ onUpload }: MarkdownCustomOptions = {}) => {
|
|
|
482
482
|
const MarkdownActions = () => {
|
|
483
483
|
const { onAction, state } = useToolbarContext('MarkdownActions');
|
|
484
484
|
const { t } = useTranslation(translationKey);
|
|
485
|
+
|
|
486
|
+
let toolTipKey = 'comment label';
|
|
487
|
+
if (state?.comment) {
|
|
488
|
+
toolTipKey = 'selection overlaps existing comment label';
|
|
489
|
+
} else if (state?.selection === false) {
|
|
490
|
+
toolTipKey = 'select text to comment label';
|
|
491
|
+
}
|
|
492
|
+
|
|
485
493
|
return (
|
|
486
494
|
<>
|
|
487
495
|
{/* TODO(burdon): Toggle readonly state. */}
|
|
@@ -495,7 +503,7 @@ const MarkdownActions = () => {
|
|
|
495
503
|
onClick={() => onAction?.({ type: 'comment' })}
|
|
496
504
|
disabled={!state || state.comment || !state.selection}
|
|
497
505
|
>
|
|
498
|
-
{t(
|
|
506
|
+
{t(toolTipKey)}
|
|
499
507
|
</ToolbarButton>
|
|
500
508
|
</>
|
|
501
509
|
);
|
package/src/defaults.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { EditorView } from '@codemirror/view';
|
|
6
|
+
|
|
7
|
+
export { getToken } from './styles';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* CodeMirror content width.
|
|
11
|
+
* 40rem = 640px. Corresponds to initial plank width (Google docs, Stashpad, etc.)
|
|
12
|
+
* 50rem = 800px. Maximum content width for solo mode.
|
|
13
|
+
*/
|
|
14
|
+
export const editorContent = '!mt-[16px] !mli-auto w-full max-w-[min(50rem,100%-4rem)]';
|
|
15
|
+
|
|
16
|
+
export const editorWithToolbarLayout =
|
|
17
|
+
'grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden';
|
|
18
|
+
|
|
19
|
+
export const editorGutter = EditorView.baseTheme({
|
|
20
|
+
'.cm-gutters': {
|
|
21
|
+
// Match margin from content.
|
|
22
|
+
marginTop: '16px',
|
|
23
|
+
// Inside within content margin.
|
|
24
|
+
marginRight: '-32px',
|
|
25
|
+
width: '32px',
|
|
26
|
+
backgroundColor: 'transparent !important',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -20,14 +20,14 @@ export type AutocompleteResult = Completion;
|
|
|
20
20
|
|
|
21
21
|
export type AutocompleteOptions = {
|
|
22
22
|
activateOnTyping?: boolean;
|
|
23
|
-
onSearch
|
|
23
|
+
onSearch?: (text: string) => Completion[];
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Autocomplete extension.
|
|
28
28
|
*/
|
|
29
|
-
export const autocomplete = ({ activateOnTyping, onSearch }: AutocompleteOptions) => {
|
|
30
|
-
|
|
29
|
+
export const autocomplete = ({ activateOnTyping, onSearch }: AutocompleteOptions = {}) => {
|
|
30
|
+
const extentions = [
|
|
31
31
|
// https://codemirror.net/docs/ref/#view.keymap
|
|
32
32
|
// https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
|
|
33
33
|
// TODO(burdon): Set custom keymap.
|
|
@@ -44,20 +44,26 @@ export const autocomplete = ({ activateOnTyping, onSearch }: AutocompleteOptions
|
|
|
44
44
|
// TODO(burdon): Styles/fragments.
|
|
45
45
|
tooltipClass: () => 'shadow rounded',
|
|
46
46
|
}),
|
|
47
|
-
|
|
48
|
-
// TODO(burdon): Optional decoration via addToOptions
|
|
49
|
-
markdownLanguage.data.of({
|
|
50
|
-
autocomplete: (context: CompletionContext): CompletionResult | null => {
|
|
51
|
-
const match = context.matchBefore(/\w*/);
|
|
52
|
-
if (!match || (match.from === match.to && !context.explicit)) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
from: match.from,
|
|
58
|
-
options: onSearch(match.text.toLowerCase()),
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
}),
|
|
62
47
|
];
|
|
48
|
+
|
|
49
|
+
if (onSearch) {
|
|
50
|
+
extentions.push(
|
|
51
|
+
// TODO(burdon): Optional decoration via addToOptions
|
|
52
|
+
markdownLanguage.data.of({
|
|
53
|
+
autocomplete: (context: CompletionContext): CompletionResult | null => {
|
|
54
|
+
const match = context.matchBefore(/\w*/);
|
|
55
|
+
if (!match || (match.from === match.to && !context.explicit)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
from: match.from,
|
|
61
|
+
options: onSearch(match.text.toLowerCase()),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return extentions;
|
|
63
69
|
};
|
|
@@ -7,10 +7,9 @@ import '@dxosTheme';
|
|
|
7
7
|
import '@preact/signals-react';
|
|
8
8
|
import React, { useEffect, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import { TextType } from '@braneframe/types';
|
|
11
10
|
import { Repo } from '@dxos/automerge/automerge-repo';
|
|
12
11
|
import { BroadcastChannelNetworkAdapter } from '@dxos/automerge/automerge-repo-network-broadcastchannel';
|
|
13
|
-
import {
|
|
12
|
+
import { Expando, create } from '@dxos/echo-schema';
|
|
14
13
|
import { type PublicKey } from '@dxos/keys';
|
|
15
14
|
import { Filter, DocAccessor, createDocAccessor, useSpace, useQuery, type Space } from '@dxos/react-client/echo';
|
|
16
15
|
import { useIdentity, type Identity } from '@dxos/react-client/halo';
|
|
@@ -18,6 +17,7 @@ import { ClientRepeater } from '@dxos/react-client/testing';
|
|
|
18
17
|
import { useThemeContext } from '@dxos/react-ui';
|
|
19
18
|
import { withTheme } from '@dxos/storybook-utils';
|
|
20
19
|
|
|
20
|
+
import { editorContent } from '../../defaults';
|
|
21
21
|
import { useTextEditor } from '../../hooks';
|
|
22
22
|
import translations from '../../translations';
|
|
23
23
|
import { createBasicExtensions, createDataExtensions, createThemeExtensions } from '../factories';
|
|
@@ -45,9 +45,7 @@ const Editor = ({ source, autoFocus, space, identity }: EditorProps) => {
|
|
|
45
45
|
createThemeExtensions({
|
|
46
46
|
themeMode,
|
|
47
47
|
slots: {
|
|
48
|
-
editor: { className:
|
|
49
|
-
// TODO(burdon): Sufficient padding so indicator isn't clipped.
|
|
50
|
-
content: { className: '!m-8' },
|
|
48
|
+
editor: { className: editorContent },
|
|
51
49
|
},
|
|
52
50
|
}),
|
|
53
51
|
createDataExtensions({ id: 'test', text: source, space, identity }),
|
|
@@ -136,7 +134,7 @@ export const WithEcho = {
|
|
|
136
134
|
space.db.add(
|
|
137
135
|
create({
|
|
138
136
|
type: 'test',
|
|
139
|
-
content: create(
|
|
137
|
+
content: create(Expando, { content: initialContent }),
|
|
140
138
|
}),
|
|
141
139
|
);
|
|
142
140
|
}}
|
|
@@ -611,6 +611,10 @@ const hasActiveSelection = (state: EditorState): boolean => {
|
|
|
611
611
|
return state.selection.ranges.some((range) => !range.empty);
|
|
612
612
|
};
|
|
613
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Manages external comment synchronization for the editor.
|
|
616
|
+
* This class subscribes to external comment updates and applies them to the editor view.
|
|
617
|
+
*/
|
|
614
618
|
class ExternalCommentSync implements PluginValue {
|
|
615
619
|
private readonly unsubscribe: () => void;
|
|
616
620
|
|
|
@@ -30,8 +30,7 @@ import { hexToHue, isNotFalsy } from '@dxos/util';
|
|
|
30
30
|
|
|
31
31
|
import { automerge } from './automerge';
|
|
32
32
|
import { awareness, SpaceAwarenessProvider } from './awareness';
|
|
33
|
-
import { type ThemeStyles } from '../styles';
|
|
34
|
-
import { defaultTheme } from '../themes';
|
|
33
|
+
import { type ThemeStyles, defaultTheme } from '../styles';
|
|
35
34
|
|
|
36
35
|
//
|
|
37
36
|
// Basic
|
|
@@ -146,6 +145,8 @@ const defaultThemeSlots = {
|
|
|
146
145
|
},
|
|
147
146
|
};
|
|
148
147
|
|
|
148
|
+
// TODO(burdon): Should only have one baseTheme?
|
|
149
|
+
// https://codemirror.net/examples/styling
|
|
149
150
|
export const createThemeExtensions = ({ theme, themeMode, slots: _slots }: ThemeExtensionsOptions = {}): Extension => {
|
|
150
151
|
const slots = defaultsDeep({}, _slots, defaultThemeSlots);
|
|
151
152
|
return [
|