@dxos/react-ui-editor 0.6.9 → 0.6.10-main.3cfcc89
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 +759 -732
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/InputMode.stories.d.ts +2 -1
- package/dist/types/src/InputMode.stories.d.ts.map +1 -1
- package/dist/types/src/TextEditor.stories.d.ts +20 -13
- package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +5 -1
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +3 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +2 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/dnd.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +2 -2
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/folding.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/changes.d.ts +10 -0
- package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/changes.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/changes.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/debug.d.ts +11 -0
- package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/styles.d.ts +4 -0
- package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/theme.d.ts +1 -1
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts +2 -5
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +26 -27
- package/src/InputMode.stories.tsx +1 -1
- package/src/TextEditor.stories.tsx +125 -77
- package/src/components/Toolbar/Toolbar.tsx +91 -92
- package/src/defaults.ts +16 -11
- package/src/extensions/annotations.ts +2 -2
- package/src/extensions/autocomplete.ts +4 -1
- package/src/extensions/automerge/automerge.stories.tsx +1 -1
- package/src/extensions/awareness/awareness.ts +1 -1
- package/src/extensions/comments.ts +11 -45
- package/src/extensions/dnd.ts +3 -5
- package/src/extensions/factories.ts +8 -8
- package/src/extensions/folding.tsx +3 -4
- package/src/extensions/markdown/action.ts +1 -0
- package/src/extensions/markdown/bundle.ts +3 -1
- package/src/extensions/markdown/{link-paste.test.ts → changes.test.ts} +2 -2
- package/src/extensions/markdown/changes.ts +148 -0
- package/src/extensions/markdown/debug.ts +44 -0
- package/src/extensions/markdown/decorate.ts +35 -108
- package/src/extensions/markdown/formatting.ts +1 -2
- package/src/extensions/markdown/highlight.ts +2 -2
- package/src/extensions/markdown/index.ts +1 -0
- package/src/extensions/markdown/parser.test.ts +29 -0
- package/src/extensions/markdown/styles.ts +103 -0
- package/src/index.ts +0 -2
- package/src/styles/theme.ts +85 -147
- package/src/styles/tokens.ts +6 -6
- package/src/translations.ts +1 -0
- package/dist/types/src/extensions/markdown/link-paste.d.ts +0 -9
- package/dist/types/src/extensions/markdown/link-paste.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/link-paste.test.d.ts +0 -2
- package/dist/types/src/extensions/markdown/link-paste.test.d.ts.map +0 -1
- package/src/extensions/markdown/link-paste.ts +0 -107
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
5
|
+
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { markdown } from '@codemirror/lang-markdown';
|
|
8
|
+
import { openSearchPanel } from '@codemirror/search';
|
|
9
|
+
import { type Extension } from '@codemirror/state';
|
|
10
|
+
import { type EditorView } from '@codemirror/view';
|
|
8
11
|
import { ArrowSquareOut, X } from '@phosphor-icons/react';
|
|
9
12
|
import { effect, useSignal } from '@preact/signals-react';
|
|
10
13
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
11
|
-
import React, { type FC, type KeyboardEvent,
|
|
14
|
+
import React, { type FC, type KeyboardEvent, useEffect, useState } from 'react';
|
|
12
15
|
import { createRoot } from 'react-dom/client';
|
|
13
16
|
|
|
14
17
|
import { create, Expando } from '@dxos/echo-schema';
|
|
@@ -50,6 +53,8 @@ import {
|
|
|
50
53
|
type Comment,
|
|
51
54
|
type CommentsOptions,
|
|
52
55
|
type EditorSelectionState,
|
|
56
|
+
debugTree,
|
|
57
|
+
type DebugNode,
|
|
53
58
|
} from './extensions';
|
|
54
59
|
import { renderRoot } from './extensions/util';
|
|
55
60
|
import { useTextEditor, type UseTextEditorProps } from './hooks';
|
|
@@ -66,7 +71,7 @@ const img = '}`,
|
|
72
77
|
`- [ ] ${faker.lorem.sentences()}`,
|
|
@@ -109,7 +114,7 @@ const content = {
|
|
|
109
114
|
code: str(
|
|
110
115
|
'### Code',
|
|
111
116
|
'',
|
|
112
|
-
'```',
|
|
117
|
+
'```bash',
|
|
113
118
|
'$ ls -las',
|
|
114
119
|
'```',
|
|
115
120
|
'',
|
|
@@ -199,6 +204,7 @@ const text = str(
|
|
|
199
204
|
content.table,
|
|
200
205
|
content.image,
|
|
201
206
|
content.footer,
|
|
207
|
+
'=== LAST LINE ===',
|
|
202
208
|
);
|
|
203
209
|
|
|
204
210
|
const links = [
|
|
@@ -212,17 +218,15 @@ const links = [
|
|
|
212
218
|
const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
|
|
213
219
|
|
|
214
220
|
const hover =
|
|
215
|
-
'rounded-sm text-
|
|
221
|
+
'rounded-sm text-baseText text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
|
|
216
222
|
|
|
217
223
|
const renderLinkTooltip = (el: Element, url: string) => {
|
|
218
224
|
const web = new URL(url);
|
|
219
225
|
createRoot(el).render(
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
</a>
|
|
225
|
-
</StrictMode>,
|
|
226
|
+
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
227
|
+
{web.origin}
|
|
228
|
+
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1')} />
|
|
229
|
+
</a>,
|
|
226
230
|
);
|
|
227
231
|
};
|
|
228
232
|
|
|
@@ -234,26 +238,22 @@ const Key: FC<{ char: string }> = ({ char }) => (
|
|
|
234
238
|
|
|
235
239
|
const onCommentsHover: CommentsOptions['onHover'] = (el, shortcut) => {
|
|
236
240
|
createRoot(el).render(
|
|
237
|
-
<
|
|
238
|
-
<div
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
))}
|
|
244
|
-
</div>
|
|
241
|
+
<div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded'>
|
|
242
|
+
<div>Create comment</div>
|
|
243
|
+
<div className='flex gap-1'>
|
|
244
|
+
{keySymbols(parseShortcut(shortcut)).map((char) => (
|
|
245
|
+
<Key key={char} char={char} />
|
|
246
|
+
))}
|
|
245
247
|
</div>
|
|
246
|
-
</
|
|
248
|
+
</div>,
|
|
247
249
|
);
|
|
248
250
|
};
|
|
249
251
|
|
|
250
252
|
const renderLinkButton = (el: Element, url: string) => {
|
|
251
253
|
createRoot(el).render(
|
|
252
|
-
<
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
</a>
|
|
256
|
-
</StrictMode>,
|
|
254
|
+
<a href={url} target='_blank' rel='noreferrer' className={hover}>
|
|
255
|
+
<ArrowSquareOut weight='bold' className={mx(getSize(4), 'inline-block leading-none mis-1 mb-[2px]')} />
|
|
256
|
+
</a>,
|
|
257
257
|
);
|
|
258
258
|
};
|
|
259
259
|
|
|
@@ -263,29 +263,34 @@ const renderLinkButton = (el: Element, url: string) => {
|
|
|
263
263
|
|
|
264
264
|
type StoryProps = {
|
|
265
265
|
id?: string;
|
|
266
|
+
debug?: boolean;
|
|
266
267
|
text?: string;
|
|
267
268
|
readonly?: boolean;
|
|
268
269
|
placeholder?: string;
|
|
270
|
+
onReady?: (view: EditorView) => void;
|
|
269
271
|
} & Pick<UseTextEditorProps, 'scrollTo' | 'selection' | 'extensions'>;
|
|
270
272
|
|
|
271
273
|
const Story = ({
|
|
272
274
|
id = 'editor-' + PublicKey.random().toHex().slice(0, 8),
|
|
275
|
+
debug,
|
|
273
276
|
text,
|
|
274
277
|
extensions,
|
|
275
278
|
readonly,
|
|
276
279
|
placeholder = 'New document.',
|
|
277
280
|
scrollTo,
|
|
278
281
|
selection,
|
|
282
|
+
onReady,
|
|
279
283
|
}: StoryProps) => {
|
|
280
284
|
const [object] = useState(createEchoObject(create(Expando, { content: text ?? '' })));
|
|
281
285
|
const { themeMode } = useThemeContext();
|
|
282
|
-
const
|
|
286
|
+
const [tree, setTree] = useState<DebugNode>();
|
|
287
|
+
const { parentRef, focusAttributes, view } = useTextEditor(
|
|
283
288
|
() => ({
|
|
284
289
|
id,
|
|
285
290
|
initialValue: text,
|
|
286
291
|
extensions: [
|
|
287
292
|
createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
|
|
288
|
-
createBasicExtensions({ readonly, placeholder }),
|
|
293
|
+
createBasicExtensions({ readonly, placeholder, scrollPastEnd: true }),
|
|
289
294
|
createMarkdownExtensions({ themeMode }),
|
|
290
295
|
createThemeExtensions({
|
|
291
296
|
themeMode,
|
|
@@ -296,6 +301,7 @@ const Story = ({
|
|
|
296
301
|
},
|
|
297
302
|
}),
|
|
298
303
|
extensions || [],
|
|
304
|
+
debug ? debugTree(setTree) : [],
|
|
299
305
|
],
|
|
300
306
|
scrollTo,
|
|
301
307
|
selection,
|
|
@@ -303,7 +309,24 @@ const Story = ({
|
|
|
303
309
|
[object, extensions, themeMode],
|
|
304
310
|
);
|
|
305
311
|
|
|
306
|
-
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
if (view) {
|
|
314
|
+
onReady?.(view);
|
|
315
|
+
}
|
|
316
|
+
}, [view]);
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div className='flex w-full'>
|
|
320
|
+
<div role='none' className='flex w-full overflow-hidden' ref={parentRef} {...focusAttributes} />
|
|
321
|
+
{debug && (
|
|
322
|
+
<div className='w-[800px] border-l border-separator overflow-auto'>
|
|
323
|
+
<pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>
|
|
324
|
+
{JSON.stringify(tree, null, 2)}
|
|
325
|
+
</pre>
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
307
330
|
};
|
|
308
331
|
|
|
309
332
|
export default {
|
|
@@ -313,7 +336,7 @@ export default {
|
|
|
313
336
|
parameters: { translations, layout: 'fullscreen' },
|
|
314
337
|
};
|
|
315
338
|
|
|
316
|
-
const
|
|
339
|
+
const defaultExtensions: Extension[] = [
|
|
317
340
|
autocomplete({
|
|
318
341
|
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
319
342
|
}),
|
|
@@ -323,37 +346,34 @@ const defaults = [
|
|
|
323
346
|
];
|
|
324
347
|
|
|
325
348
|
export const Default = {
|
|
326
|
-
render: () => <Story text={text} extensions={
|
|
349
|
+
render: () => <Story text={text} extensions={defaultExtensions} selection={{ anchor: 99, head: 110 }} />,
|
|
327
350
|
};
|
|
328
351
|
|
|
329
|
-
export const
|
|
330
|
-
render: () => {
|
|
331
|
-
// NOTE: Selection won't appear if text is reformatted.
|
|
332
|
-
const word = 'Scroll to here...';
|
|
333
|
-
const text = str('# Scroll To', longText, '', word, '', longText);
|
|
334
|
-
const idx = text.indexOf(word);
|
|
335
|
-
return (
|
|
336
|
-
<Story text={text} extensions={defaults} scrollTo={idx} selection={{ anchor: idx, head: idx + word.length }} />
|
|
337
|
-
);
|
|
338
|
-
},
|
|
352
|
+
export const Empty = {
|
|
353
|
+
render: () => <Story extensions={defaultExtensions} />,
|
|
339
354
|
};
|
|
340
355
|
|
|
341
356
|
export const Readonly = {
|
|
342
|
-
render: () => <Story text={text} extensions={
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
export const Empty = {
|
|
346
|
-
render: () => <Story extensions={defaults} />,
|
|
357
|
+
render: () => <Story text={text} extensions={defaultExtensions} readonly />,
|
|
347
358
|
};
|
|
348
359
|
|
|
349
360
|
export const NoExtensions = {
|
|
350
361
|
render: () => <Story text={text} />,
|
|
351
362
|
};
|
|
352
363
|
|
|
353
|
-
export const
|
|
354
|
-
render: () =>
|
|
364
|
+
export const Vim = {
|
|
365
|
+
render: () => (
|
|
366
|
+
<Story
|
|
367
|
+
text={str('# Vim Mode', '', 'The distant future. The year 2000.', '', content.paragraphs)}
|
|
368
|
+
extensions={[defaultExtensions, InputModeExtensions.vim]}
|
|
369
|
+
/>
|
|
370
|
+
),
|
|
355
371
|
};
|
|
356
372
|
|
|
373
|
+
//
|
|
374
|
+
// Scrolling
|
|
375
|
+
//
|
|
376
|
+
|
|
357
377
|
const longText = faker.helpers.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 }).join('\n\n');
|
|
358
378
|
|
|
359
379
|
const largeWithImages = faker.helpers
|
|
@@ -367,12 +387,12 @@ const headings = str(
|
|
|
367
387
|
.flat(),
|
|
368
388
|
);
|
|
369
389
|
|
|
370
|
-
export const Headings = {
|
|
371
|
-
render: () => <Story text={headings} extensions={decorateMarkdown({ numberedHeadings: { from: 2, to: 4 } })} />,
|
|
372
|
-
};
|
|
373
|
-
|
|
374
390
|
const global = new Map<string, EditorSelectionState>();
|
|
375
391
|
|
|
392
|
+
export const Folding = {
|
|
393
|
+
render: () => <Story text={text} extensions={[editorGutter, folding()]} />,
|
|
394
|
+
};
|
|
395
|
+
|
|
376
396
|
export const Scrolling = {
|
|
377
397
|
render: () => (
|
|
378
398
|
<Story
|
|
@@ -386,7 +406,34 @@ export const Scrolling = {
|
|
|
386
406
|
};
|
|
387
407
|
|
|
388
408
|
export const ScrollingWithImages = {
|
|
389
|
-
render: () =>
|
|
409
|
+
render: () => (
|
|
410
|
+
<Story text={str('# Large Document', '', largeWithImages)} extensions={[decorateMarkdown(), image()]} />
|
|
411
|
+
),
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
export const ScrollTo = {
|
|
415
|
+
render: () => {
|
|
416
|
+
// NOTE: Selection won't appear if text is reformatted.
|
|
417
|
+
const word = 'Scroll to here...';
|
|
418
|
+
const text = str('# Scroll To', longText, '', word, '', longText);
|
|
419
|
+
const idx = text.indexOf(word);
|
|
420
|
+
return (
|
|
421
|
+
<Story
|
|
422
|
+
text={text}
|
|
423
|
+
extensions={defaultExtensions}
|
|
424
|
+
scrollTo={idx}
|
|
425
|
+
selection={{ anchor: idx, head: idx + word.length }}
|
|
426
|
+
/>
|
|
427
|
+
);
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
//
|
|
432
|
+
// Markdown
|
|
433
|
+
//
|
|
434
|
+
|
|
435
|
+
export const Headings = {
|
|
436
|
+
render: () => <Story text={headings} extensions={decorateMarkdown({ numberedHeadings: { from: 2, to: 4 } })} />,
|
|
390
437
|
};
|
|
391
438
|
|
|
392
439
|
export const Links = {
|
|
@@ -419,35 +466,39 @@ export const OrderedList = {
|
|
|
419
466
|
};
|
|
420
467
|
|
|
421
468
|
export const TaskList = {
|
|
422
|
-
render: () => <Story text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} />,
|
|
469
|
+
render: () => <Story text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} debug />,
|
|
423
470
|
};
|
|
424
471
|
|
|
425
472
|
export const Table = {
|
|
426
|
-
render: () => <Story text={str(content.table, content.footer)} extensions={[table()]} />,
|
|
473
|
+
render: () => <Story text={str(content.table, content.footer)} extensions={[decorateMarkdown(), table()]} />,
|
|
427
474
|
};
|
|
428
475
|
|
|
429
|
-
export const
|
|
476
|
+
export const CommentedOut = {
|
|
430
477
|
render: () => (
|
|
431
478
|
<Story
|
|
432
|
-
text={str('#
|
|
479
|
+
text={str('# Commented out', '', content.comment, content.footer)}
|
|
433
480
|
extensions={[
|
|
434
|
-
decorateMarkdown(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}),
|
|
481
|
+
decorateMarkdown(),
|
|
482
|
+
markdown(),
|
|
483
|
+
// commentBlock()
|
|
438
484
|
]}
|
|
439
485
|
/>
|
|
440
486
|
),
|
|
441
487
|
};
|
|
442
488
|
|
|
443
|
-
|
|
489
|
+
//
|
|
490
|
+
// Custom
|
|
491
|
+
//
|
|
492
|
+
|
|
493
|
+
export const Autocomplete = {
|
|
444
494
|
render: () => (
|
|
445
495
|
<Story
|
|
446
|
-
text={str('#
|
|
496
|
+
text={str('# Autocomplete', '', 'Press Ctrl-Space...', content.footer)}
|
|
447
497
|
extensions={[
|
|
448
|
-
decorateMarkdown(),
|
|
449
|
-
|
|
450
|
-
|
|
498
|
+
decorateMarkdown({ renderLinkButton }),
|
|
499
|
+
autocomplete({
|
|
500
|
+
onSearch: (text) => links.filter(({ label }) => label.toLowerCase().includes(text.toLowerCase())),
|
|
501
|
+
}),
|
|
451
502
|
]}
|
|
452
503
|
/>
|
|
453
504
|
),
|
|
@@ -466,7 +517,13 @@ export const Mention = {
|
|
|
466
517
|
),
|
|
467
518
|
};
|
|
468
519
|
|
|
469
|
-
const
|
|
520
|
+
export const Search = {
|
|
521
|
+
render: () => (
|
|
522
|
+
<Story text={str('# Search', text)} extensions={defaultExtensions} onReady={(view) => openSearchPanel(view)} />
|
|
523
|
+
),
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const CommandDialog = ({ onClose }: { onClose: (action?: CommandAction) => void }) => {
|
|
470
527
|
const [text, setText] = useState('');
|
|
471
528
|
const handleInsert = () => {
|
|
472
529
|
onClose(text.length ? { insert: text + '\n' } : undefined);
|
|
@@ -561,15 +618,6 @@ export const Comments = {
|
|
|
561
618
|
},
|
|
562
619
|
};
|
|
563
620
|
|
|
564
|
-
export const Vim = {
|
|
565
|
-
render: () => (
|
|
566
|
-
<Story
|
|
567
|
-
text={str('# Vim Mode', '', 'The distant future. The year 2000.', '', content.paragraphs)}
|
|
568
|
-
extensions={[defaults, InputModeExtensions.vim]}
|
|
569
|
-
/>
|
|
570
|
-
),
|
|
571
|
-
};
|
|
572
|
-
|
|
573
621
|
export const Annotations = {
|
|
574
622
|
render: () => <Story text={str('# Annotations', '', longText)} extensions={[annotations({ match: /volup/gi })]} />,
|
|
575
623
|
};
|
|
@@ -589,8 +637,6 @@ export const DND = {
|
|
|
589
637
|
),
|
|
590
638
|
};
|
|
591
639
|
|
|
592
|
-
const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
|
|
593
|
-
|
|
594
640
|
export const Listener = {
|
|
595
641
|
render: () => (
|
|
596
642
|
<Story
|
|
@@ -609,6 +655,8 @@ export const Listener = {
|
|
|
609
655
|
),
|
|
610
656
|
};
|
|
611
657
|
|
|
658
|
+
const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
|
|
659
|
+
|
|
612
660
|
export const Typewriter = {
|
|
613
661
|
render: () => (
|
|
614
662
|
<Story
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ListBullets,
|
|
13
13
|
ListChecks,
|
|
14
14
|
ListNumbers,
|
|
15
|
+
MagnifyingGlass,
|
|
15
16
|
Paragraph,
|
|
16
17
|
Quotes,
|
|
17
18
|
TextStrikethrough,
|
|
@@ -140,89 +141,6 @@ const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
|
|
|
140
141
|
);
|
|
141
142
|
};
|
|
142
143
|
|
|
143
|
-
//
|
|
144
|
-
// View Mode
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
const ViewModeIcons: Record<EditorViewMode, Icon> = {
|
|
148
|
-
preview: PencilSimple,
|
|
149
|
-
readonly: PencilSimpleSlash,
|
|
150
|
-
source: MarkdownLogo,
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const MarkdownView = ({ mode }: { mode: EditorViewMode }) => {
|
|
154
|
-
const { t } = useTranslation(translationKey);
|
|
155
|
-
const { onAction } = useToolbarContext('ViewMode');
|
|
156
|
-
const ModeIcon = ViewModeIcons[mode ?? 'preview'];
|
|
157
|
-
const suppressNextTooltip = useRef<boolean>(false);
|
|
158
|
-
const [tooltipOpen, setTooltipOpen] = useState<boolean>(false);
|
|
159
|
-
const [selectOpen, setSelectOpen] = useState<boolean>(false);
|
|
160
|
-
return (
|
|
161
|
-
<Tooltip.Root
|
|
162
|
-
open={tooltipOpen}
|
|
163
|
-
onOpenChange={(nextOpen) => {
|
|
164
|
-
if (nextOpen && suppressNextTooltip.current) {
|
|
165
|
-
suppressNextTooltip.current = false;
|
|
166
|
-
return setTooltipOpen(false);
|
|
167
|
-
} else {
|
|
168
|
-
return setTooltipOpen(nextOpen);
|
|
169
|
-
}
|
|
170
|
-
}}
|
|
171
|
-
>
|
|
172
|
-
{/* TODO(thure): `Select` encounters a ref error if used here (repro: select a heading, then select another
|
|
173
|
-
heading). Determine the root cause and fix or report to Radix. */}
|
|
174
|
-
<DropdownMenu.Root
|
|
175
|
-
open={selectOpen}
|
|
176
|
-
onOpenChange={(nextOpen: boolean) => {
|
|
177
|
-
if (!nextOpen) {
|
|
178
|
-
suppressNextTooltip.current = true;
|
|
179
|
-
}
|
|
180
|
-
return setSelectOpen(nextOpen);
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
<Tooltip.Trigger asChild>
|
|
184
|
-
<NaturalToolbar.Button asChild>
|
|
185
|
-
<DropdownMenu.Trigger asChild>
|
|
186
|
-
<Button variant='ghost' classNames={buttonStyles}>
|
|
187
|
-
<span className='sr-only'>{t('mode label')}</span>
|
|
188
|
-
<ModeIcon className={iconStyles} />
|
|
189
|
-
<CaretDown />
|
|
190
|
-
</Button>
|
|
191
|
-
</DropdownMenu.Trigger>
|
|
192
|
-
</NaturalToolbar.Button>
|
|
193
|
-
</Tooltip.Trigger>
|
|
194
|
-
<DropdownMenu.Portal>
|
|
195
|
-
<DropdownMenu.Content classNames='is-min md:is-min' onCloseAutoFocus={(e) => e.preventDefault()}>
|
|
196
|
-
<DropdownMenu.Viewport>
|
|
197
|
-
{EditorViewModes.map((value) => {
|
|
198
|
-
const Icon = ViewModeIcons[value];
|
|
199
|
-
return (
|
|
200
|
-
<DropdownMenu.CheckboxItem
|
|
201
|
-
key={value}
|
|
202
|
-
checked={value === mode}
|
|
203
|
-
onClick={() => onAction?.({ type: 'view-mode', data: value })}
|
|
204
|
-
>
|
|
205
|
-
<Icon className={iconStyles} />
|
|
206
|
-
<span className='whitespace-nowrap grow'>{t(`${value} mode label`)}</span>
|
|
207
|
-
<Check className={value === mode ? 'visible' : 'invisible'} />
|
|
208
|
-
</DropdownMenu.CheckboxItem>
|
|
209
|
-
);
|
|
210
|
-
})}
|
|
211
|
-
</DropdownMenu.Viewport>
|
|
212
|
-
<DropdownMenu.Arrow />
|
|
213
|
-
</DropdownMenu.Content>
|
|
214
|
-
</DropdownMenu.Portal>
|
|
215
|
-
</DropdownMenu.Root>
|
|
216
|
-
<Tooltip.Portal>
|
|
217
|
-
<Tooltip.Content {...tooltipProps}>
|
|
218
|
-
{t('view mode label')}
|
|
219
|
-
<Tooltip.Arrow />
|
|
220
|
-
</Tooltip.Content>
|
|
221
|
-
</Tooltip.Portal>
|
|
222
|
-
</Tooltip.Root>
|
|
223
|
-
);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
144
|
//
|
|
227
145
|
// Heading
|
|
228
146
|
//
|
|
@@ -474,28 +392,109 @@ const MarkdownCustom = ({ onUpload }: MarkdownCustomOptions = {}) => {
|
|
|
474
392
|
);
|
|
475
393
|
};
|
|
476
394
|
|
|
395
|
+
//
|
|
396
|
+
// View Mode
|
|
397
|
+
//
|
|
398
|
+
|
|
399
|
+
const ViewModeIcons: Record<EditorViewMode, Icon> = {
|
|
400
|
+
preview: PencilSimple,
|
|
401
|
+
readonly: PencilSimpleSlash,
|
|
402
|
+
source: MarkdownLogo,
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const MarkdownView = ({ mode }: { mode: EditorViewMode }) => {
|
|
406
|
+
const { t } = useTranslation(translationKey);
|
|
407
|
+
const { onAction } = useToolbarContext('ViewMode');
|
|
408
|
+
const ModeIcon = ViewModeIcons[mode ?? 'preview'];
|
|
409
|
+
const suppressNextTooltip = useRef<boolean>(false);
|
|
410
|
+
const [tooltipOpen, setTooltipOpen] = useState<boolean>(false);
|
|
411
|
+
const [selectOpen, setSelectOpen] = useState<boolean>(false);
|
|
412
|
+
return (
|
|
413
|
+
<Tooltip.Root
|
|
414
|
+
open={tooltipOpen}
|
|
415
|
+
onOpenChange={(nextOpen) => {
|
|
416
|
+
if (nextOpen && suppressNextTooltip.current) {
|
|
417
|
+
suppressNextTooltip.current = false;
|
|
418
|
+
return setTooltipOpen(false);
|
|
419
|
+
} else {
|
|
420
|
+
return setTooltipOpen(nextOpen);
|
|
421
|
+
}
|
|
422
|
+
}}
|
|
423
|
+
>
|
|
424
|
+
{/* TODO(thure): `Select` encounters a ref error if used here (repro: select a heading, then select another
|
|
425
|
+
heading). Determine the root cause and fix or report to Radix. */}
|
|
426
|
+
<DropdownMenu.Root
|
|
427
|
+
open={selectOpen}
|
|
428
|
+
onOpenChange={(nextOpen: boolean) => {
|
|
429
|
+
if (!nextOpen) {
|
|
430
|
+
suppressNextTooltip.current = true;
|
|
431
|
+
}
|
|
432
|
+
return setSelectOpen(nextOpen);
|
|
433
|
+
}}
|
|
434
|
+
>
|
|
435
|
+
<Tooltip.Trigger asChild>
|
|
436
|
+
<NaturalToolbar.Button asChild>
|
|
437
|
+
<DropdownMenu.Trigger asChild>
|
|
438
|
+
<Button variant='ghost' classNames={buttonStyles}>
|
|
439
|
+
<span className='sr-only'>{t('mode label')}</span>
|
|
440
|
+
<ModeIcon className={iconStyles} />
|
|
441
|
+
<CaretDown />
|
|
442
|
+
</Button>
|
|
443
|
+
</DropdownMenu.Trigger>
|
|
444
|
+
</NaturalToolbar.Button>
|
|
445
|
+
</Tooltip.Trigger>
|
|
446
|
+
<DropdownMenu.Portal>
|
|
447
|
+
<DropdownMenu.Content classNames='is-min md:is-min' onCloseAutoFocus={(e) => e.preventDefault()}>
|
|
448
|
+
<DropdownMenu.Viewport>
|
|
449
|
+
{EditorViewModes.map((value) => {
|
|
450
|
+
const Icon = ViewModeIcons[value];
|
|
451
|
+
return (
|
|
452
|
+
<DropdownMenu.CheckboxItem
|
|
453
|
+
key={value}
|
|
454
|
+
checked={value === mode}
|
|
455
|
+
onClick={() => onAction?.({ type: 'view-mode', data: value })}
|
|
456
|
+
>
|
|
457
|
+
<Icon className={iconStyles} />
|
|
458
|
+
<span className='whitespace-nowrap grow'>{t(`${value} mode label`)}</span>
|
|
459
|
+
<Check className={value === mode ? 'visible' : 'invisible'} />
|
|
460
|
+
</DropdownMenu.CheckboxItem>
|
|
461
|
+
);
|
|
462
|
+
})}
|
|
463
|
+
</DropdownMenu.Viewport>
|
|
464
|
+
<DropdownMenu.Arrow />
|
|
465
|
+
</DropdownMenu.Content>
|
|
466
|
+
</DropdownMenu.Portal>
|
|
467
|
+
</DropdownMenu.Root>
|
|
468
|
+
<Tooltip.Portal>
|
|
469
|
+
<Tooltip.Content {...tooltipProps}>
|
|
470
|
+
{t('view mode label')}
|
|
471
|
+
<Tooltip.Arrow />
|
|
472
|
+
</Tooltip.Content>
|
|
473
|
+
</Tooltip.Portal>
|
|
474
|
+
</Tooltip.Root>
|
|
475
|
+
);
|
|
476
|
+
};
|
|
477
|
+
|
|
477
478
|
//
|
|
478
479
|
// Actions
|
|
479
480
|
//
|
|
480
481
|
|
|
481
|
-
// TODO(burdon): Make extensible.
|
|
482
482
|
const MarkdownActions = () => {
|
|
483
483
|
const { onAction, state } = useToolbarContext('MarkdownActions');
|
|
484
484
|
const { t } = useTranslation(translationKey);
|
|
485
485
|
|
|
486
|
-
let
|
|
486
|
+
let commentToolTipKey = 'comment label';
|
|
487
487
|
if (state?.comment) {
|
|
488
|
-
|
|
488
|
+
commentToolTipKey = 'selection overlaps existing comment label';
|
|
489
489
|
} else if (state?.selection === false) {
|
|
490
|
-
|
|
490
|
+
commentToolTipKey = 'select text to comment label';
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
return (
|
|
494
494
|
<>
|
|
495
|
-
{
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
{/* </ToolbarButton> */}
|
|
495
|
+
<ToolbarButton value='search' Icon={MagnifyingGlass} onClick={() => onAction?.({ type: 'search' })}>
|
|
496
|
+
{t('search label')}
|
|
497
|
+
</ToolbarButton>
|
|
499
498
|
<ToolbarButton
|
|
500
499
|
value='comment'
|
|
501
500
|
Icon={ChatText}
|
|
@@ -503,7 +502,7 @@ const MarkdownActions = () => {
|
|
|
503
502
|
onClick={() => onAction?.({ type: 'comment' })}
|
|
504
503
|
disabled={!state || state.comment || !state.selection}
|
|
505
504
|
>
|
|
506
|
-
{t(
|
|
505
|
+
{t(commentToolTipKey)}
|
|
507
506
|
</ToolbarButton>
|
|
508
507
|
</>
|
|
509
508
|
);
|
package/src/defaults.ts
CHANGED
|
@@ -4,32 +4,37 @@
|
|
|
4
4
|
|
|
5
5
|
import { EditorView } from '@codemirror/view';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
8
|
+
|
|
9
|
+
import { fontMono } from './styles';
|
|
10
|
+
|
|
11
|
+
const margin = '!mt-[16px]';
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* CodeMirror content width.
|
|
11
15
|
* 40rem = 640px. Corresponds to initial plank width (Google docs, Stashpad, etc.)
|
|
12
16
|
* 50rem = 800px. Maximum content width for solo mode.
|
|
13
17
|
*/
|
|
14
|
-
export const editorContent = '!
|
|
18
|
+
export const editorContent = mx(margin, '!mli-auto w-full max-w-[min(50rem,100%-2rem)]');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Margin for numbers.
|
|
22
|
+
*/
|
|
23
|
+
export const editorFullWidth = mx(margin, '!ml-[3rem]');
|
|
15
24
|
|
|
16
25
|
export const editorWithToolbarLayout =
|
|
17
26
|
'grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden';
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
// TODO(burdon): Define scrollMargins for fixed gutter.
|
|
29
|
+
export const editorGutter = EditorView.theme({
|
|
30
|
+
// Match margin from content.
|
|
20
31
|
'.cm-gutters': {
|
|
21
|
-
// Match margin from content.
|
|
22
32
|
marginTop: '16px',
|
|
23
|
-
marginBottom: '16px',
|
|
24
|
-
// Inside within content margin.
|
|
25
|
-
marginRight: '-32px',
|
|
26
|
-
width: '32px',
|
|
27
|
-
backgroundColor: 'transparent !important',
|
|
28
33
|
},
|
|
29
34
|
});
|
|
30
35
|
|
|
31
|
-
export const editorMonospace = EditorView.
|
|
36
|
+
export const editorMonospace = EditorView.theme({
|
|
32
37
|
'.cm-content': {
|
|
33
|
-
fontFamily:
|
|
38
|
+
fontFamily: fontMono,
|
|
34
39
|
},
|
|
35
40
|
});
|
|
@@ -69,10 +69,10 @@ export const annotations = (options: AnnotationOptions = {}): Extension => {
|
|
|
69
69
|
];
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const styles = EditorView.
|
|
72
|
+
const styles = EditorView.theme({
|
|
73
73
|
'.cm-annotation': {
|
|
74
74
|
textDecoration: 'underline',
|
|
75
75
|
textDecorationStyle: 'wavy',
|
|
76
|
-
textDecorationColor: '
|
|
76
|
+
textDecorationColor: 'var(--dx-error)',
|
|
77
77
|
},
|
|
78
78
|
});
|