@dxos/react-ui-editor 0.8.2-main.f11618f → 0.8.2-staging.7ac8446
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 +371 -499
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +379 -515
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +371 -499
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/{stories/InputMode.stories.d.ts → InputMode.stories.d.ts} +1 -1
- package/dist/types/src/InputMode.stories.d.ts.map +1 -0
- package/dist/types/src/{stories/TextEditorBasic.stories.d.ts → TextEditor.stories.d.ts} +35 -2
- package/dist/types/src/TextEditor.stories.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/util.d.ts +3 -3
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/{view-mode.d.ts → viewMode.d.ts} +1 -1
- package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +1 -0
- package/dist/types/src/defaults.d.ts +0 -1
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/command/command.d.ts +10 -5
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts +2 -4
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +0 -1
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +2 -7
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/preview.d.ts +12 -0
- package/dist/types/src/extensions/command/preview.d.ts.map +1 -0
- package/dist/types/src/extensions/command/state.d.ts +11 -9
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +7 -9
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +0 -1
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts +1 -4
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +2 -2
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts +1 -4
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
- package/dist/types/src/fragments.d.ts +3 -0
- package/dist/types/src/fragments.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextEditor.d.ts +1 -2
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +0 -5
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/react.d.ts +1 -6
- package/dist/types/src/util/react.d.ts.map +1 -1
- package/package.json +27 -33
- package/src/{stories/InputMode.stories.tsx → InputMode.stories.tsx} +4 -4
- package/src/TextEditor.stories.tsx +856 -0
- package/src/components/EditorToolbar/EditorToolbar.tsx +2 -2
- package/src/components/EditorToolbar/util.ts +3 -3
- package/src/defaults.ts +3 -5
- package/src/extensions/automerge/automerge.stories.tsx +11 -3
- package/src/extensions/command/command.ts +27 -9
- package/src/extensions/command/hint.ts +30 -33
- package/src/extensions/command/index.ts +0 -1
- package/src/extensions/command/menu.ts +8 -11
- package/src/extensions/command/preview.ts +79 -0
- package/src/extensions/command/state.ts +61 -41
- package/src/extensions/comments.ts +9 -9
- package/src/extensions/folding.tsx +1 -1
- package/src/extensions/index.ts +0 -1
- package/src/extensions/markdown/decorate.ts +3 -4
- package/src/extensions/markdown/formatting.ts +2 -2
- package/src/extensions/markdown/image.ts +11 -12
- package/src/extensions/markdown/link.ts +24 -33
- package/src/fragments.ts +19 -0
- package/src/hooks/useTextEditor.ts +3 -4
- package/src/types.ts +0 -7
- package/src/util/react.tsx +2 -20
- package/dist/lib/browser/testing/index.mjs +0 -67
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node/testing/index.cjs +0 -101
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/testing/index.mjs +0 -69
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +0 -1
- package/dist/types/src/extensions/command/action.d.ts +0 -17
- package/dist/types/src/extensions/command/action.d.ts.map +0 -1
- package/dist/types/src/extensions/preview/index.d.ts +0 -2
- package/dist/types/src/extensions/preview/index.d.ts.map +0 -1
- package/dist/types/src/extensions/preview/preview.d.ts +0 -39
- package/dist/types/src/extensions/preview/preview.d.ts.map +0 -1
- package/dist/types/src/stories/InputMode.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorComments.stories.d.ts +0 -13
- package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts +0 -13
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts +0 -19
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +0 -1
- package/dist/types/src/stories/story-utils.d.ts +0 -53
- package/dist/types/src/stories/story-utils.d.ts.map +0 -1
- package/dist/types/src/testing/RefPopover.d.ts +0 -21
- package/dist/types/src/testing/RefPopover.d.ts.map +0 -1
- package/dist/types/src/testing/index.d.ts +0 -2
- package/dist/types/src/testing/index.d.ts.map +0 -1
- package/src/extensions/command/action.ts +0 -49
- package/src/extensions/preview/index.ts +0 -5
- package/src/extensions/preview/preview.ts +0 -271
- package/src/stories/TextEditorBasic.stories.tsx +0 -289
- package/src/stories/TextEditorComments.stories.tsx +0 -99
- package/src/stories/TextEditorPreview.stories.tsx +0 -239
- package/src/stories/TextEditorSpecial.stories.tsx +0 -107
- package/src/stories/story-utils.tsx +0 -329
- package/src/testing/RefPopover.tsx +0 -74
- package/src/testing/index.ts +0 -5
- /package/src/components/EditorToolbar/{view-mode.ts → viewMode.ts} +0 -0
@@ -0,0 +1,856 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import '@dxos-theme';
|
6
|
+
|
7
|
+
import { type Completion } from '@codemirror/autocomplete';
|
8
|
+
import { javascript } from '@codemirror/lang-javascript';
|
9
|
+
import { markdown } from '@codemirror/lang-markdown';
|
10
|
+
import { openSearchPanel } from '@codemirror/search';
|
11
|
+
import { type Extension } from '@codemirror/state';
|
12
|
+
import { type EditorView } from '@codemirror/view';
|
13
|
+
import { X } from '@phosphor-icons/react';
|
14
|
+
import { effect, useSignal } from '@preact/signals-react';
|
15
|
+
import defaultsDeep from 'lodash.defaultsdeep';
|
16
|
+
import React, { useEffect, useState, type FC, type KeyboardEvent } from 'react';
|
17
|
+
import { createRoot } from 'react-dom/client';
|
18
|
+
|
19
|
+
import { Expando } from '@dxos/echo-schema';
|
20
|
+
import { keySymbols, parseShortcut } from '@dxos/keyboard';
|
21
|
+
import { PublicKey } from '@dxos/keys';
|
22
|
+
import { create } from '@dxos/live-object';
|
23
|
+
import { log } from '@dxos/log';
|
24
|
+
import { faker } from '@dxos/random';
|
25
|
+
import { createDocAccessor, createObject } from '@dxos/react-client/echo';
|
26
|
+
import { Button, Icon, IconButton, Input, ThemeProvider, useThemeContext } from '@dxos/react-ui';
|
27
|
+
import { baseSurface, defaultTx, getSize, mx } from '@dxos/react-ui-theme';
|
28
|
+
import { type Meta, withLayout, withTheme } from '@dxos/storybook-utils';
|
29
|
+
|
30
|
+
import { editorContent, editorGutter, editorMonospace } from './defaults';
|
31
|
+
import {
|
32
|
+
annotations,
|
33
|
+
autocomplete,
|
34
|
+
blast,
|
35
|
+
command,
|
36
|
+
comments,
|
37
|
+
createBasicExtensions,
|
38
|
+
createDataExtensions,
|
39
|
+
createExternalCommentSync,
|
40
|
+
createMarkdownExtensions,
|
41
|
+
createThemeExtensions,
|
42
|
+
debugTree,
|
43
|
+
decorateMarkdown,
|
44
|
+
defaultOptions,
|
45
|
+
dropFile,
|
46
|
+
folding,
|
47
|
+
formattingKeymap,
|
48
|
+
image,
|
49
|
+
InputModeExtensions,
|
50
|
+
linkTooltip,
|
51
|
+
listener,
|
52
|
+
mention,
|
53
|
+
selectionState,
|
54
|
+
table,
|
55
|
+
typewriter,
|
56
|
+
type CommandAction,
|
57
|
+
type CommentsOptions,
|
58
|
+
type DebugNode,
|
59
|
+
type EditorSelectionState,
|
60
|
+
} from './extensions';
|
61
|
+
import { useTextEditor, type UseTextEditorProps } from './hooks';
|
62
|
+
import translations from './translations';
|
63
|
+
import { type Comment } from './types';
|
64
|
+
import { renderRoot } from './util';
|
65
|
+
|
66
|
+
faker.seed(101);
|
67
|
+
|
68
|
+
const str = (...lines: string[]) => lines.join('\n');
|
69
|
+
|
70
|
+
const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
|
71
|
+
|
72
|
+
const img = '';
|
73
|
+
|
74
|
+
const code = str(
|
75
|
+
'// Code',
|
76
|
+
'const Component = () => {',
|
77
|
+
' const x = 100;',
|
78
|
+
'',
|
79
|
+
' return () => <div>Test</div>;',
|
80
|
+
'};',
|
81
|
+
);
|
82
|
+
|
83
|
+
const content = {
|
84
|
+
tasks: str(
|
85
|
+
//
|
86
|
+
'### TaskList',
|
87
|
+
'',
|
88
|
+
`- [x] ${faker.lorem.sentences()}`,
|
89
|
+
`- [ ] ${faker.lorem.sentences()}`,
|
90
|
+
` - [ ] ${faker.lorem.sentences()}`,
|
91
|
+
` - [ ] ${faker.lorem.sentences()}`,
|
92
|
+
` - [x] ${faker.lorem.sentences()}`,
|
93
|
+
'',
|
94
|
+
),
|
95
|
+
|
96
|
+
bullets: str(
|
97
|
+
//
|
98
|
+
'### BulletList',
|
99
|
+
'',
|
100
|
+
`- ${faker.lorem.sentences()}`,
|
101
|
+
`- ${faker.lorem.sentences()}`,
|
102
|
+
` - ${faker.lorem.sentences()}`,
|
103
|
+
` - ${faker.lorem.sentences()}`,
|
104
|
+
`- ${faker.lorem.sentences()}`,
|
105
|
+
'',
|
106
|
+
),
|
107
|
+
|
108
|
+
numbered: str(
|
109
|
+
//
|
110
|
+
'### OrderedList (part 1)',
|
111
|
+
'',
|
112
|
+
`1. ${faker.lorem.sentences()}`,
|
113
|
+
`1. ${faker.lorem.sentences()}`,
|
114
|
+
`1. ${faker.lorem.sentences()}`,
|
115
|
+
` 1. ${faker.lorem.sentences()}`,
|
116
|
+
` 1. ${faker.lorem.sentences()}`,
|
117
|
+
` 1. ${faker.lorem.sentences()}`,
|
118
|
+
`1. ${faker.lorem.sentences()}`,
|
119
|
+
'',
|
120
|
+
'### OrderedList (part 2)',
|
121
|
+
'',
|
122
|
+
`1. ${faker.lorem.sentences()}`,
|
123
|
+
'',
|
124
|
+
),
|
125
|
+
|
126
|
+
typescript: code,
|
127
|
+
|
128
|
+
codeblocks: str('### Code', '', '```bash', '$ ls -las', '```', '', '```tsx', code, '```', ''),
|
129
|
+
|
130
|
+
comment: str('<!--', 'A comment', '-->', '', 'No comment.', 'Partial comment. <!-- comment. -->'),
|
131
|
+
|
132
|
+
links: str(
|
133
|
+
'### Links',
|
134
|
+
'',
|
135
|
+
'This is a naked link https://dxos.org within a sentence.',
|
136
|
+
'',
|
137
|
+
'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
|
138
|
+
'',
|
139
|
+
'This is all about https://dxos.org and related technologies.',
|
140
|
+
'',
|
141
|
+
),
|
142
|
+
|
143
|
+
table: str(
|
144
|
+
'### Tables',
|
145
|
+
'',
|
146
|
+
`| ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} |`,
|
147
|
+
`|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|`,
|
148
|
+
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
149
|
+
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
150
|
+
`| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
|
151
|
+
'',
|
152
|
+
),
|
153
|
+
|
154
|
+
image: str('### Image', '', img),
|
155
|
+
|
156
|
+
headings: str(
|
157
|
+
...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
|
158
|
+
),
|
159
|
+
|
160
|
+
formatting: str('### Formatting', '', 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.', ''),
|
161
|
+
|
162
|
+
blockquotes: str(
|
163
|
+
'### Blockquotes',
|
164
|
+
'',
|
165
|
+
'> This is a block quote.',
|
166
|
+
'',
|
167
|
+
'> 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.',
|
168
|
+
'',
|
169
|
+
'> This is ...',
|
170
|
+
'... a multi-line ...',
|
171
|
+
'block quote.',
|
172
|
+
'',
|
173
|
+
),
|
174
|
+
|
175
|
+
paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
|
176
|
+
|
177
|
+
footer: str('', '', '', '', ''),
|
178
|
+
};
|
179
|
+
|
180
|
+
const text = str(
|
181
|
+
'# Markdown',
|
182
|
+
'Composer Markdown Editor',
|
183
|
+
'',
|
184
|
+
|
185
|
+
'---',
|
186
|
+
'## Basics',
|
187
|
+
content.blockquotes,
|
188
|
+
content.formatting,
|
189
|
+
content.links,
|
190
|
+
|
191
|
+
'---',
|
192
|
+
'## Lists',
|
193
|
+
content.bullets,
|
194
|
+
content.tasks,
|
195
|
+
content.numbered,
|
196
|
+
|
197
|
+
'---',
|
198
|
+
'## Misc',
|
199
|
+
content.codeblocks,
|
200
|
+
content.table,
|
201
|
+
content.image,
|
202
|
+
content.footer,
|
203
|
+
'=== LAST LINE ===',
|
204
|
+
);
|
205
|
+
|
206
|
+
const links: Completion[] = [
|
207
|
+
{ label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
|
208
|
+
{ label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
|
209
|
+
{ label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
|
210
|
+
{ label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
|
211
|
+
{ label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
|
212
|
+
];
|
213
|
+
|
214
|
+
const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
|
215
|
+
|
216
|
+
const hover =
|
217
|
+
'rounded-sm text-baseText text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
|
218
|
+
|
219
|
+
const Key: FC<{ char: string }> = ({ char }) => (
|
220
|
+
<span className='flex justify-center items-center w-[24px] h-[24px] rounded text-xs bg-neutral-200 text-black'>
|
221
|
+
{char}
|
222
|
+
</span>
|
223
|
+
);
|
224
|
+
|
225
|
+
const onCommentsHover: CommentsOptions['onHover'] = (el, shortcut) => {
|
226
|
+
createRoot(el).render(
|
227
|
+
<div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
|
228
|
+
<div>Create comment</div>
|
229
|
+
<div className='flex gap-1'>
|
230
|
+
{keySymbols(parseShortcut(shortcut)).map((char) => (
|
231
|
+
<Key key={char} char={char} />
|
232
|
+
))}
|
233
|
+
</div>
|
234
|
+
</div>,
|
235
|
+
);
|
236
|
+
};
|
237
|
+
|
238
|
+
const renderLinkTooltip = (el: Element, url: string) => {
|
239
|
+
const web = new URL(url);
|
240
|
+
createRoot(el).render(
|
241
|
+
<ThemeProvider tx={defaultTx}>
|
242
|
+
<a href={url} target='_blank' rel='noreferrer' className={mx(hover, 'flex items-center gap-2')}>
|
243
|
+
{web.origin}
|
244
|
+
<Icon icon='ph--arrow-square-out--regular' size={4} />
|
245
|
+
</a>
|
246
|
+
</ThemeProvider>,
|
247
|
+
);
|
248
|
+
};
|
249
|
+
|
250
|
+
const renderLinkButton = (el: Element, url: string) => {
|
251
|
+
createRoot(el).render(
|
252
|
+
<ThemeProvider tx={defaultTx}>
|
253
|
+
<a href={url} target='_blank' rel='noreferrer' className={mx(hover)}>
|
254
|
+
<Icon icon='ph--arrow-square-out--regular' size={4} classNames='inline-block mis-1 mb-[3px]' />
|
255
|
+
</a>
|
256
|
+
</ThemeProvider>,
|
257
|
+
);
|
258
|
+
};
|
259
|
+
|
260
|
+
//
|
261
|
+
// Story
|
262
|
+
//
|
263
|
+
|
264
|
+
type DebugMode = 'raw' | 'tree' | 'raw+tree';
|
265
|
+
|
266
|
+
type StoryProps = {
|
267
|
+
id?: string;
|
268
|
+
debug?: DebugMode;
|
269
|
+
text?: string;
|
270
|
+
readOnly?: boolean;
|
271
|
+
placeholder?: string;
|
272
|
+
lineNumbers?: boolean;
|
273
|
+
onReady?: (view: EditorView) => void;
|
274
|
+
} & Pick<UseTextEditorProps, 'scrollTo' | 'selection' | 'extensions'>;
|
275
|
+
|
276
|
+
const DefaultStory = ({
|
277
|
+
id = 'editor-' + PublicKey.random().toHex().slice(0, 8),
|
278
|
+
debug,
|
279
|
+
text,
|
280
|
+
extensions,
|
281
|
+
readOnly,
|
282
|
+
placeholder = 'New document.',
|
283
|
+
scrollTo,
|
284
|
+
selection,
|
285
|
+
lineNumbers,
|
286
|
+
onReady,
|
287
|
+
}: StoryProps) => {
|
288
|
+
const [object] = useState(createObject(create(Expando, { content: text ?? '' })));
|
289
|
+
const { themeMode } = useThemeContext();
|
290
|
+
const [tree, setTree] = useState<DebugNode>();
|
291
|
+
const { parentRef, focusAttributes, view } = useTextEditor(
|
292
|
+
() => ({
|
293
|
+
id,
|
294
|
+
initialValue: text,
|
295
|
+
extensions: [
|
296
|
+
createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
|
297
|
+
createBasicExtensions({ readOnly, placeholder, lineNumbers, scrollPastEnd: true }),
|
298
|
+
createMarkdownExtensions({ themeMode }),
|
299
|
+
createThemeExtensions({
|
300
|
+
themeMode,
|
301
|
+
syntaxHighlighting: true,
|
302
|
+
slots: {
|
303
|
+
content: {
|
304
|
+
className: editorContent,
|
305
|
+
},
|
306
|
+
},
|
307
|
+
}),
|
308
|
+
editorGutter,
|
309
|
+
extensions || [],
|
310
|
+
debug ? debugTree(setTree) : [],
|
311
|
+
],
|
312
|
+
scrollTo,
|
313
|
+
selection,
|
314
|
+
}),
|
315
|
+
[object, extensions, themeMode],
|
316
|
+
);
|
317
|
+
|
318
|
+
useEffect(() => {
|
319
|
+
if (view) {
|
320
|
+
onReady?.(view);
|
321
|
+
}
|
322
|
+
}, [view]);
|
323
|
+
|
324
|
+
return (
|
325
|
+
<div className='flex w-full'>
|
326
|
+
<div role='none' className='flex w-full overflow-hidden' ref={parentRef} {...focusAttributes} />
|
327
|
+
{debug && (
|
328
|
+
<div className='flex flex-col w-[800px] border-l border-separator divide-y divide-separator overflow-auto'>
|
329
|
+
{(debug === 'raw' || debug === 'raw+tree') && (
|
330
|
+
<pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>{view?.state.doc.toString()}</pre>
|
331
|
+
)}
|
332
|
+
{(debug === 'tree' || debug === 'raw+tree') && (
|
333
|
+
<pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>
|
334
|
+
{JSON.stringify(tree, null, 2)}
|
335
|
+
</pre>
|
336
|
+
)}
|
337
|
+
</div>
|
338
|
+
)}
|
339
|
+
</div>
|
340
|
+
);
|
341
|
+
};
|
342
|
+
|
343
|
+
const meta: Meta<typeof DefaultStory> = {
|
344
|
+
title: 'ui/react-ui-editor/TextEditor',
|
345
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
346
|
+
render: DefaultStory,
|
347
|
+
parameters: { translations, layout: 'fullscreen' },
|
348
|
+
};
|
349
|
+
|
350
|
+
export default meta;
|
351
|
+
|
352
|
+
const defaultExtensions: Extension[] = [
|
353
|
+
decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100 }),
|
354
|
+
formattingKeymap(),
|
355
|
+
linkTooltip(renderLinkTooltip),
|
356
|
+
];
|
357
|
+
|
358
|
+
const allExtensions: Extension[] = [
|
359
|
+
decorateMarkdown({ numberedHeadings: { from: 2, to: 4 }, renderLinkButton, selectionChangeDelay: 100 }),
|
360
|
+
formattingKeymap(),
|
361
|
+
linkTooltip(renderLinkTooltip),
|
362
|
+
image(),
|
363
|
+
table(),
|
364
|
+
folding(),
|
365
|
+
];
|
366
|
+
|
367
|
+
//
|
368
|
+
// Default
|
369
|
+
//
|
370
|
+
|
371
|
+
export const Default = {
|
372
|
+
render: () => <DefaultStory text={text} extensions={defaultExtensions} />,
|
373
|
+
};
|
374
|
+
|
375
|
+
//
|
376
|
+
// Everything
|
377
|
+
//
|
378
|
+
|
379
|
+
export const Everything = {
|
380
|
+
render: () => <DefaultStory text={text} extensions={allExtensions} selection={{ anchor: 99, head: 110 }} />,
|
381
|
+
};
|
382
|
+
|
383
|
+
//
|
384
|
+
// Empty
|
385
|
+
//
|
386
|
+
|
387
|
+
export const Empty = {
|
388
|
+
render: () => <DefaultStory extensions={defaultExtensions} />,
|
389
|
+
};
|
390
|
+
|
391
|
+
//
|
392
|
+
// Readonly
|
393
|
+
//
|
394
|
+
|
395
|
+
export const Readonly = {
|
396
|
+
render: () => <DefaultStory text={text} extensions={defaultExtensions} readOnly />,
|
397
|
+
};
|
398
|
+
|
399
|
+
//
|
400
|
+
// No Extensions
|
401
|
+
//
|
402
|
+
|
403
|
+
export const NoExtensions = {
|
404
|
+
render: () => <DefaultStory text={text} />,
|
405
|
+
};
|
406
|
+
|
407
|
+
//
|
408
|
+
// Vim
|
409
|
+
//
|
410
|
+
|
411
|
+
export const Vim = {
|
412
|
+
render: () => (
|
413
|
+
<DefaultStory
|
414
|
+
text={str('# Vim Mode', '', 'The distant future. The year 2000.', '', content.paragraphs)}
|
415
|
+
extensions={[defaultExtensions, InputModeExtensions.vim]}
|
416
|
+
/>
|
417
|
+
),
|
418
|
+
};
|
419
|
+
|
420
|
+
//
|
421
|
+
// Scrolling
|
422
|
+
//
|
423
|
+
|
424
|
+
const longText = faker.helpers.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 }).join('\n\n');
|
425
|
+
|
426
|
+
const largeWithImages = faker.helpers
|
427
|
+
.multiple(() => [faker.lorem.paragraph({ min: 12, max: 16 }), img], { count: 20 })
|
428
|
+
.flatMap((x) => x)
|
429
|
+
.join('\n\n');
|
430
|
+
|
431
|
+
const headings = str(
|
432
|
+
...[1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 2, 3, 3, 2, 2, 6, 1]
|
433
|
+
.map((level) => ['#'.repeat(level) + ' ' + faker.lorem.sentence(3), faker.lorem.sentences(), ''])
|
434
|
+
.flat(),
|
435
|
+
);
|
436
|
+
|
437
|
+
const global = new Map<string, EditorSelectionState>();
|
438
|
+
|
439
|
+
export const Folding = {
|
440
|
+
render: () => <DefaultStory text={text} extensions={[folding()]} />,
|
441
|
+
};
|
442
|
+
|
443
|
+
export const Scrolling = {
|
444
|
+
render: () => (
|
445
|
+
<DefaultStory
|
446
|
+
text={str('# Large Document', '', longText)}
|
447
|
+
extensions={selectionState({
|
448
|
+
setState: (id, state) => global.set(id, state),
|
449
|
+
getState: (id) => global.get(id),
|
450
|
+
})}
|
451
|
+
/>
|
452
|
+
),
|
453
|
+
};
|
454
|
+
|
455
|
+
export const ScrollingWithImages = {
|
456
|
+
render: () => (
|
457
|
+
<DefaultStory text={str('# Large Document', '', largeWithImages)} extensions={[decorateMarkdown(), image()]} />
|
458
|
+
),
|
459
|
+
};
|
460
|
+
|
461
|
+
export const ScrollTo = {
|
462
|
+
render: () => {
|
463
|
+
// NOTE: Selection won't appear if text is reformatted.
|
464
|
+
const word = 'Scroll to here...';
|
465
|
+
const text = str('# Scroll To', longText, '', word, '', longText);
|
466
|
+
const idx = text.indexOf(word);
|
467
|
+
return (
|
468
|
+
<DefaultStory
|
469
|
+
text={text}
|
470
|
+
extensions={defaultExtensions}
|
471
|
+
scrollTo={idx}
|
472
|
+
selection={{ anchor: idx, head: idx + word.length }}
|
473
|
+
/>
|
474
|
+
);
|
475
|
+
},
|
476
|
+
};
|
477
|
+
|
478
|
+
//
|
479
|
+
// Markdown
|
480
|
+
//
|
481
|
+
|
482
|
+
export const Blockquote = {
|
483
|
+
render: () => (
|
484
|
+
<DefaultStory
|
485
|
+
text={str('> Blockquote', 'continuation', content.footer)}
|
486
|
+
extensions={decorateMarkdown()}
|
487
|
+
debug='raw'
|
488
|
+
/>
|
489
|
+
),
|
490
|
+
};
|
491
|
+
|
492
|
+
export const Headings = {
|
493
|
+
render: () => (
|
494
|
+
<DefaultStory text={headings} extensions={decorateMarkdown({ numberedHeadings: { from: 2, to: 4 } })} />
|
495
|
+
),
|
496
|
+
};
|
497
|
+
|
498
|
+
export const Links = {
|
499
|
+
render: () => (
|
500
|
+
<DefaultStory text={str(content.links, content.footer)} extensions={[linkTooltip(renderLinkTooltip)]} />
|
501
|
+
),
|
502
|
+
};
|
503
|
+
|
504
|
+
export const Image = {
|
505
|
+
render: () => <DefaultStory text={str(content.image, content.footer)} extensions={[image()]} />,
|
506
|
+
};
|
507
|
+
|
508
|
+
export const Code = {
|
509
|
+
render: () => <DefaultStory text={str(content.codeblocks, content.footer)} extensions={[decorateMarkdown()]} />,
|
510
|
+
};
|
511
|
+
|
512
|
+
export const Lists = {
|
513
|
+
render: () => (
|
514
|
+
<DefaultStory
|
515
|
+
text={str(content.tasks, '', content.bullets, '', content.numbered, content.footer)}
|
516
|
+
extensions={[decorateMarkdown()]}
|
517
|
+
/>
|
518
|
+
),
|
519
|
+
};
|
520
|
+
|
521
|
+
export const BulletList = {
|
522
|
+
render: () => <DefaultStory text={str(content.bullets, content.footer)} extensions={[decorateMarkdown()]} />,
|
523
|
+
};
|
524
|
+
|
525
|
+
export const OrderedList = {
|
526
|
+
render: () => <DefaultStory text={str(content.numbered, content.footer)} extensions={[decorateMarkdown()]} />,
|
527
|
+
};
|
528
|
+
|
529
|
+
export const TaskList = {
|
530
|
+
render: () => (
|
531
|
+
<DefaultStory text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} debug='raw+tree' />
|
532
|
+
),
|
533
|
+
};
|
534
|
+
|
535
|
+
export const Table = {
|
536
|
+
render: () => <DefaultStory text={str(content.table, content.footer)} extensions={[decorateMarkdown(), table()]} />,
|
537
|
+
};
|
538
|
+
|
539
|
+
//
|
540
|
+
// Commented out
|
541
|
+
//
|
542
|
+
|
543
|
+
export const CommentedOut = {
|
544
|
+
render: () => (
|
545
|
+
<DefaultStory
|
546
|
+
text={str('# Commented out', '', content.comment, content.footer)}
|
547
|
+
extensions={[
|
548
|
+
decorateMarkdown(),
|
549
|
+
markdown(),
|
550
|
+
// commentBlock()
|
551
|
+
]}
|
552
|
+
/>
|
553
|
+
),
|
554
|
+
};
|
555
|
+
|
556
|
+
//
|
557
|
+
// Typescript
|
558
|
+
//
|
559
|
+
|
560
|
+
export const Typescript = {
|
561
|
+
render: () => (
|
562
|
+
<DefaultStory
|
563
|
+
text={content.typescript}
|
564
|
+
lineNumbers
|
565
|
+
extensions={[editorMonospace, javascript({ typescript: true })]}
|
566
|
+
/>
|
567
|
+
),
|
568
|
+
};
|
569
|
+
|
570
|
+
//
|
571
|
+
// Custom
|
572
|
+
//
|
573
|
+
|
574
|
+
export const Autocomplete = {
|
575
|
+
render: () => (
|
576
|
+
<DefaultStory
|
577
|
+
text={str('# Autocomplete', '', 'Press Ctrl-Space...', content.footer)}
|
578
|
+
extensions={[
|
579
|
+
decorateMarkdown({ renderLinkButton }),
|
580
|
+
autocomplete({
|
581
|
+
onSearch: (text) => {
|
582
|
+
return links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase()));
|
583
|
+
},
|
584
|
+
}),
|
585
|
+
]}
|
586
|
+
/>
|
587
|
+
),
|
588
|
+
};
|
589
|
+
|
590
|
+
//
|
591
|
+
// Mention
|
592
|
+
//
|
593
|
+
|
594
|
+
export const Mention = {
|
595
|
+
render: () => (
|
596
|
+
<DefaultStory
|
597
|
+
text={str('# Mention', '', 'Type @...', content.footer)}
|
598
|
+
extensions={[
|
599
|
+
mention({
|
600
|
+
onSearch: (text) => names.filter((name) => name.toLowerCase().startsWith(text.toLowerCase())),
|
601
|
+
}),
|
602
|
+
]}
|
603
|
+
/>
|
604
|
+
),
|
605
|
+
};
|
606
|
+
|
607
|
+
//
|
608
|
+
// Search
|
609
|
+
//
|
610
|
+
|
611
|
+
export const Search = {
|
612
|
+
render: () => (
|
613
|
+
<DefaultStory
|
614
|
+
text={str('# Search', text)}
|
615
|
+
extensions={defaultExtensions}
|
616
|
+
onReady={(view) => openSearchPanel(view)}
|
617
|
+
/>
|
618
|
+
),
|
619
|
+
};
|
620
|
+
|
621
|
+
//
|
622
|
+
// Command
|
623
|
+
//
|
624
|
+
|
625
|
+
export const Command = {
|
626
|
+
render: () => (
|
627
|
+
<DefaultStory
|
628
|
+
text={str('# Command', '', '', '[Test](dxn:queue:data:123)', '', '', '')}
|
629
|
+
extensions={[
|
630
|
+
command({
|
631
|
+
onHint: () => 'Press / for commands.',
|
632
|
+
onRenderMenu: (el, onClick) => {
|
633
|
+
renderRoot(
|
634
|
+
el,
|
635
|
+
<ThemeProvider tx={defaultTx}>
|
636
|
+
<Button classNames='p-1 aspect-square' onClick={onClick}>
|
637
|
+
<Icon icon={'ph--sparkle--regular'} size={5} />
|
638
|
+
</Button>
|
639
|
+
</ThemeProvider>,
|
640
|
+
);
|
641
|
+
},
|
642
|
+
onRenderDialog: (el, onClose) => {
|
643
|
+
renderRoot(el, <CommandDialog onClose={onClose} />);
|
644
|
+
},
|
645
|
+
onRenderPreview: (el, { text }) => {
|
646
|
+
faker.seed(text.length);
|
647
|
+
const data = Array.from({ length: 2 }, () => faker.lorem.sentences(2));
|
648
|
+
renderRoot(
|
649
|
+
el,
|
650
|
+
<ThemeProvider tx={defaultTx}>
|
651
|
+
<div className='flex flex-col gap-2'>
|
652
|
+
<div className='flex items-center gap-4'>
|
653
|
+
<div className='grow truncate'>
|
654
|
+
<span className='text-xs text-subdued mie-2'>Prompt</span>
|
655
|
+
{text}
|
656
|
+
</div>
|
657
|
+
<div className='flex gap-1'>
|
658
|
+
<IconButton classNames='text-green-500' label='Apply' icon={'ph--check--regular'} />
|
659
|
+
<IconButton classNames='text-red-500' label='Cancel' icon={'ph--x--regular'} />
|
660
|
+
</div>
|
661
|
+
</div>
|
662
|
+
<div>{data.join('\n\n')}</div>
|
663
|
+
</div>
|
664
|
+
</ThemeProvider>,
|
665
|
+
);
|
666
|
+
},
|
667
|
+
}),
|
668
|
+
]}
|
669
|
+
/>
|
670
|
+
),
|
671
|
+
};
|
672
|
+
|
673
|
+
const CommandDialog = ({ onClose }: { onClose: (action?: CommandAction) => void }) => {
|
674
|
+
const [text, setText] = useState('');
|
675
|
+
const handleInsert = () => {
|
676
|
+
// TODO(burdon): Use queue ref.
|
677
|
+
const link = `[${text}](dxn:queue:data:123)`;
|
678
|
+
console.log({ link });
|
679
|
+
onClose(text.length ? { insert: link } : undefined);
|
680
|
+
};
|
681
|
+
|
682
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
683
|
+
switch (event.key) {
|
684
|
+
case 'Enter': {
|
685
|
+
handleInsert();
|
686
|
+
break;
|
687
|
+
}
|
688
|
+
case 'Escape': {
|
689
|
+
onClose();
|
690
|
+
break;
|
691
|
+
}
|
692
|
+
}
|
693
|
+
};
|
694
|
+
|
695
|
+
return (
|
696
|
+
<div className='flex w-full justify-center'>
|
697
|
+
<div
|
698
|
+
className={mx(
|
699
|
+
'flex w-full p-2 gap-2 items-center border border-separator rounded-md',
|
700
|
+
editorContent,
|
701
|
+
baseSurface,
|
702
|
+
)}
|
703
|
+
>
|
704
|
+
<Input.Root>
|
705
|
+
<Input.TextInput
|
706
|
+
autoFocus={true}
|
707
|
+
placeholder='Ask a question...'
|
708
|
+
value={text}
|
709
|
+
onChange={({ target: { value } }) => setText(value)}
|
710
|
+
onKeyDown={handleKeyDown}
|
711
|
+
/>
|
712
|
+
</Input.Root>
|
713
|
+
<Button variant='ghost' classNames='pli-0' onClick={() => onClose()}>
|
714
|
+
<X className={getSize(5)} />
|
715
|
+
</Button>
|
716
|
+
</div>
|
717
|
+
</div>
|
718
|
+
);
|
719
|
+
};
|
720
|
+
|
721
|
+
//
|
722
|
+
// Comments
|
723
|
+
//
|
724
|
+
|
725
|
+
export const Comments = {
|
726
|
+
render: () => {
|
727
|
+
const _comments = useSignal<Comment[]>([]);
|
728
|
+
return (
|
729
|
+
<DefaultStory
|
730
|
+
text={str('# Comments', '', content.paragraphs, content.footer)}
|
731
|
+
extensions={[
|
732
|
+
createExternalCommentSync(
|
733
|
+
'test',
|
734
|
+
(sink) => effect(() => sink()),
|
735
|
+
() => _comments.value,
|
736
|
+
),
|
737
|
+
comments({
|
738
|
+
id: 'test',
|
739
|
+
onHover: onCommentsHover,
|
740
|
+
onCreate: ({ cursor }) => {
|
741
|
+
const id = PublicKey.random().toHex();
|
742
|
+
_comments.value = [..._comments.value, { id, cursor }];
|
743
|
+
return id;
|
744
|
+
},
|
745
|
+
onSelect: (state) => {
|
746
|
+
const debug = false;
|
747
|
+
if (debug) {
|
748
|
+
console.log(
|
749
|
+
'update',
|
750
|
+
JSON.stringify({
|
751
|
+
comments: state.comments.length,
|
752
|
+
active: state.selection.current?.slice(0, 8),
|
753
|
+
closest: state.selection.closest?.slice(0, 8),
|
754
|
+
}),
|
755
|
+
);
|
756
|
+
}
|
757
|
+
},
|
758
|
+
}),
|
759
|
+
]}
|
760
|
+
/>
|
761
|
+
);
|
762
|
+
},
|
763
|
+
};
|
764
|
+
|
765
|
+
//
|
766
|
+
// Annotations
|
767
|
+
//
|
768
|
+
|
769
|
+
export const Annotations = {
|
770
|
+
render: () => (
|
771
|
+
<DefaultStory text={str('# Annotations', '', longText)} extensions={[annotations({ match: /volup/gi })]} />
|
772
|
+
),
|
773
|
+
};
|
774
|
+
|
775
|
+
//
|
776
|
+
// DND
|
777
|
+
//
|
778
|
+
|
779
|
+
export const DND = {
|
780
|
+
render: () => (
|
781
|
+
<DefaultStory
|
782
|
+
text={str('# DND', '')}
|
783
|
+
extensions={[
|
784
|
+
dropFile({
|
785
|
+
onDrop: (view, event) => {
|
786
|
+
log.info('drop', event);
|
787
|
+
},
|
788
|
+
}),
|
789
|
+
]}
|
790
|
+
/>
|
791
|
+
),
|
792
|
+
};
|
793
|
+
|
794
|
+
//
|
795
|
+
// Listener
|
796
|
+
//
|
797
|
+
|
798
|
+
export const Listener = {
|
799
|
+
render: () => (
|
800
|
+
<DefaultStory
|
801
|
+
text={str('# Listener', '', content.footer)}
|
802
|
+
extensions={[
|
803
|
+
listener({
|
804
|
+
onFocus: (focusing) => {
|
805
|
+
console.log({ focusing });
|
806
|
+
},
|
807
|
+
onChange: (text) => {
|
808
|
+
console.log({ text });
|
809
|
+
},
|
810
|
+
}),
|
811
|
+
]}
|
812
|
+
/>
|
813
|
+
),
|
814
|
+
};
|
815
|
+
|
816
|
+
//
|
817
|
+
// Typewriter
|
818
|
+
//
|
819
|
+
|
820
|
+
const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
|
821
|
+
|
822
|
+
export const Typewriter = {
|
823
|
+
render: () => (
|
824
|
+
<DefaultStory
|
825
|
+
text={str('# Typewriter', '', content.paragraphs, content.footer)}
|
826
|
+
extensions={[typewriter({ items: typewriterItems })]}
|
827
|
+
/>
|
828
|
+
),
|
829
|
+
};
|
830
|
+
|
831
|
+
//
|
832
|
+
// Blast
|
833
|
+
//
|
834
|
+
|
835
|
+
export const Blast = {
|
836
|
+
render: () => (
|
837
|
+
<DefaultStory
|
838
|
+
text={str('# Blast', '', content.paragraphs, content.codeblocks, content.paragraphs)}
|
839
|
+
extensions={[
|
840
|
+
typewriter({ items: typewriterItems }),
|
841
|
+
blast(
|
842
|
+
defaultsDeep(
|
843
|
+
{
|
844
|
+
effect: 2,
|
845
|
+
particleGravity: 0.2,
|
846
|
+
particleShrinkRate: 0.995,
|
847
|
+
color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
|
848
|
+
// color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
|
849
|
+
},
|
850
|
+
defaultOptions,
|
851
|
+
),
|
852
|
+
),
|
853
|
+
]}
|
854
|
+
/>
|
855
|
+
),
|
856
|
+
};
|