@dxos/react-ui-editor 0.6.10-main.e92b5eb → 0.6.10-staging.00555f6

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.
Files changed (72) hide show
  1. package/dist/lib/browser/index.mjs +737 -719
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/InputMode.stories.d.ts +1 -0
  5. package/dist/types/src/InputMode.stories.d.ts.map +1 -1
  6. package/dist/types/src/TextEditor.stories.d.ts +19 -12
  7. package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  9. package/dist/types/src/defaults.d.ts +5 -1
  10. package/dist/types/src/defaults.d.ts.map +1 -1
  11. package/dist/types/src/extensions/autocomplete.d.ts +3 -2
  12. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  13. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -0
  14. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  15. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  16. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  17. package/dist/types/src/extensions/factories.d.ts +2 -2
  18. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  19. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  20. package/dist/types/src/extensions/markdown/action.d.ts +1 -1
  21. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -1
  22. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  23. package/dist/types/src/extensions/markdown/changes.d.ts +10 -0
  24. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -0
  25. package/dist/types/src/extensions/markdown/changes.test.d.ts +2 -0
  26. package/dist/types/src/extensions/markdown/changes.test.d.ts.map +1 -0
  27. package/dist/types/src/extensions/markdown/debug.d.ts +11 -0
  28. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -0
  29. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  30. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  31. package/dist/types/src/extensions/markdown/index.d.ts +1 -0
  32. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  33. package/dist/types/src/extensions/markdown/styles.d.ts +4 -0
  34. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -0
  35. package/dist/types/src/index.d.ts +0 -1
  36. package/dist/types/src/index.d.ts.map +1 -1
  37. package/dist/types/src/styles/theme.d.ts +1 -1
  38. package/dist/types/src/styles/theme.d.ts.map +1 -1
  39. package/dist/types/src/styles/tokens.d.ts +2 -4
  40. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  41. package/dist/types/src/translations.d.ts +1 -0
  42. package/dist/types/src/translations.d.ts.map +1 -1
  43. package/package.json +26 -27
  44. package/src/TextEditor.stories.tsx +123 -75
  45. package/src/components/Toolbar/Toolbar.tsx +91 -92
  46. package/src/defaults.ts +16 -11
  47. package/src/extensions/annotations.ts +2 -2
  48. package/src/extensions/autocomplete.ts +4 -1
  49. package/src/extensions/awareness/awareness.ts +1 -1
  50. package/src/extensions/comments.ts +11 -45
  51. package/src/extensions/dnd.ts +3 -5
  52. package/src/extensions/factories.ts +4 -4
  53. package/src/extensions/folding.tsx +3 -4
  54. package/src/extensions/markdown/action.ts +1 -0
  55. package/src/extensions/markdown/bundle.ts +0 -1
  56. package/src/extensions/markdown/{link-paste.test.ts → changes.test.ts} +2 -2
  57. package/src/extensions/markdown/changes.ts +148 -0
  58. package/src/extensions/markdown/debug.ts +44 -0
  59. package/src/extensions/markdown/decorate.ts +14 -93
  60. package/src/extensions/markdown/formatting.ts +1 -2
  61. package/src/extensions/markdown/highlight.ts +2 -2
  62. package/src/extensions/markdown/index.ts +1 -0
  63. package/src/extensions/markdown/styles.ts +103 -0
  64. package/src/index.ts +0 -2
  65. package/src/styles/theme.ts +85 -147
  66. package/src/styles/tokens.ts +5 -5
  67. package/src/translations.ts +1 -0
  68. package/dist/types/src/extensions/markdown/link-paste.d.ts +0 -9
  69. package/dist/types/src/extensions/markdown/link-paste.d.ts.map +0 -1
  70. package/dist/types/src/extensions/markdown/link-paste.test.d.ts +0 -2
  71. package/dist/types/src/extensions/markdown/link-paste.test.d.ts.map +0 -1
  72. package/src/extensions/markdown/link-paste.ts +0 -107
@@ -0,0 +1,44 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { syntaxTree } from '@codemirror/language';
6
+ import { type EditorState, StateField } from '@codemirror/state';
7
+ import { type TreeCursor } from '@lezer/common';
8
+
9
+ export const debugTree = (cb: (tree: DebugNode) => void) =>
10
+ StateField.define({
11
+ create: (state) => cb(convertTreeToJson(state)),
12
+ update: (value, tr) => cb(convertTreeToJson(tr.state)),
13
+ });
14
+
15
+ export type DebugNode = {
16
+ type: string;
17
+ from: number;
18
+ to: number;
19
+ text: string;
20
+ children: DebugNode[];
21
+ };
22
+
23
+ export const convertTreeToJson = (state: EditorState): DebugNode => {
24
+ const treeToJson = (cursor: TreeCursor): DebugNode => {
25
+ const node: DebugNode = {
26
+ type: cursor.type.name,
27
+ from: cursor.from,
28
+ to: cursor.to,
29
+ text: state.doc.slice(cursor.from, cursor.to).toString(),
30
+ children: [],
31
+ };
32
+
33
+ if (cursor.firstChild()) {
34
+ do {
35
+ node.children.push(treeToJson(cursor));
36
+ } while (cursor.nextSibling());
37
+ cursor.parent();
38
+ }
39
+
40
+ return node;
41
+ };
42
+
43
+ return treeToJson(syntaxTree(state).cursor());
44
+ };
@@ -10,10 +10,11 @@ import { type SyntaxNodeRef } from '@lezer/common';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { mx } from '@dxos/react-ui-theme';
12
12
 
13
+ import { adjustChanges } from './changes';
13
14
  import { image } from './image';
14
- import { linkPastePlugin } from './link-paste';
15
+ import { formattingStyles, bulletListIndentationWidth, orderedListIndentationWidth } from './styles';
15
16
  import { table } from './table';
16
- import { getToken, theme, type HeadingLevel } from '../../styles';
17
+ import { theme, type HeadingLevel } from '../../styles';
17
18
  import { wrapWithCatch } from '../util';
18
19
 
19
20
  //
@@ -144,9 +145,6 @@ const autoHideTags = new Set([
144
145
  */
145
146
  type NumberingLevel = { type: string; from: number; to: number; level: number; number: number };
146
147
 
147
- const bulletListIndentationWidth = 24;
148
- const orderedListIndentationWidth = 36; // TODO(burdon): Make variable length based on number of digits.
149
-
150
148
  const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boolean) => {
151
149
  const deco = new RangeSetBuilder<Decoration>();
152
150
  const atomicDeco = new RangeSetBuilder<Decoration>();
@@ -177,7 +175,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
177
175
  const leaveList = () => {
178
176
  listLevels.pop();
179
177
  };
180
- const getCurrentList = (): NumberingLevel => {
178
+ const getCurrentListLevel = (): NumberingLevel => {
181
179
  invariant(listLevels.length);
182
180
  return listLevels[listLevels.length - 1];
183
181
  };
@@ -189,8 +187,6 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
189
187
  // ATXHeading > HeaderMark > Paragraph
190
188
  // NOTE: Numbering requires processing the entire document since otherwise only the visible range will be
191
189
  // processed and the numbering will be incorrect.
192
- // TODO(burdon): Code folding (via gutter).
193
- // Modify parser to create foldable sections that can be skipped (or pre-processed).
194
190
  case 'ATXHeading1':
195
191
  case 'ATXHeading2':
196
192
  case 'ATXHeading3':
@@ -251,7 +247,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
251
247
 
252
248
  case 'ListItem': {
253
249
  // Set indentation.
254
- const list = getCurrentList();
250
+ const list = getCurrentListLevel();
255
251
  const width = list.type === 'OrderedList' ? orderedListIndentationWidth : bulletListIndentationWidth;
256
252
  const offset = ((list.level ?? 0) + 1) * width;
257
253
  const line = state.doc.lineAt(node.from);
@@ -260,7 +256,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
260
256
  return false;
261
257
  }
262
258
 
263
- // Add line decoration to indent.
259
+ // Add line decoration for continuation indent.
264
260
  deco.add(
265
261
  line.from,
266
262
  line.from,
@@ -284,19 +280,14 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
284
280
 
285
281
  case 'ListMark': {
286
282
  // Look-ahead for task marker.
283
+ // NOTE: Requires space to exist (otherwise processes as a link).
287
284
  const next = tree.resolve(node.to + 1, 1);
288
285
  if (next?.name === 'TaskMarker') {
289
286
  atomicDeco.add(node.from, node.to + 1, hide);
290
287
  break;
291
288
  }
292
289
 
293
- const list = getCurrentList();
294
-
295
- // Abort unless followed by space.
296
- const text = state.doc.sliceString(node.from, node.to + 1);
297
- if (list.type === 'BulletList' && text[1] !== ' ') {
298
- return false;
299
- }
290
+ const list = getCurrentListLevel();
300
291
 
301
292
  // TODO(burdon): Option to make hierarchical; or a), i), etc.
302
293
  const label = list.type === 'OrderedList' ? `${++list.number}.` : '•';
@@ -444,7 +435,6 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
444
435
  }
445
436
  } else {
446
437
  // NOTE: If line numbering then we must iterate from the start of document.
447
- // TODO(burdon): Same for lists?
448
438
  tree.iterate({
449
439
  enter: wrapWithCatch(enterNode),
450
440
  leave: wrapWithCatch(leaveNode),
@@ -457,6 +447,8 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
457
447
  };
458
448
  };
459
449
 
450
+ const forceUpdate = StateEffect.define<null>();
451
+
460
452
  export interface DecorateOptions {
461
453
  /**
462
454
  * Prevents triggering decorations as the cursor moves through the document.
@@ -466,8 +458,6 @@ export interface DecorateOptions {
466
458
  renderLinkButton?: (el: Element, url: string) => void;
467
459
  }
468
460
 
469
- const forceUpdate = StateEffect.define<null>();
470
-
471
461
  export const decorateMarkdown = (options: DecorateOptions = {}) => {
472
462
  return [
473
463
  ViewPlugin.fromClass(
@@ -485,7 +475,7 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
485
475
  update.docChanged ||
486
476
  update.viewportChanged ||
487
477
  update.focusChanged ||
488
- update.transactions.some((tr) => tr.effects.some((e) => e.is(forceUpdate))) ||
478
+ update.transactions.some((tr) => tr.effects.some((effect) => effect.is(forceUpdate))) ||
489
479
  (update.selectionSet && !options.selectionChangeDelay)
490
480
  ) {
491
481
  ({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations(
@@ -500,6 +490,7 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
500
490
  }
501
491
  }
502
492
 
493
+ // Defer update in case moving through the document.
503
494
  scheduleUpdate(view: EditorView) {
504
495
  this.clearUpdate();
505
496
  this.pendingUpdate = setTimeout(() => {
@@ -526,79 +517,9 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
526
517
  ],
527
518
  },
528
519
  ),
529
- formattingStyles,
530
- linkPastePlugin,
531
520
  image(),
532
521
  table(),
522
+ adjustChanges(),
523
+ formattingStyles,
533
524
  ];
534
525
  };
535
-
536
- const formattingStyles = EditorView.baseTheme({
537
- '& .cm-code': {
538
- fontFamily: getToken('fontFamily.mono'),
539
- },
540
-
541
- '& .cm-codeblock-line': {
542
- paddingInline: '1rem !important',
543
- },
544
- '& .cm-codeblock-end': {
545
- display: 'inline-block',
546
- width: '100%',
547
- position: 'relative',
548
- '&::after': {
549
- position: 'absolute',
550
- inset: 0,
551
- content: '""',
552
- },
553
- },
554
- '& .cm-codeblock-first': {
555
- borderTopLeftRadius: '.25rem',
556
- borderTopRightRadius: '.25rem',
557
- },
558
- '& .cm-codeblock-last': {
559
- borderBottomLeftRadius: '.25rem',
560
- borderBottomRightRadius: '.25rem',
561
- },
562
- '&light .cm-codeblock-line, &light .cm-activeLine.cm-codeblock-line': {
563
- background: getToken('extend.semanticColors.input.light'),
564
- mixBlendMode: 'darken',
565
- },
566
- '&dark .cm-codeblock-line, &dark .cm-activeLine.cm-codeblock-line': {
567
- background: getToken('extend.semanticColors.input.dark'), // TODO(burdon): Make darker.
568
- mixBlendMode: 'lighten',
569
- },
570
-
571
- '& .cm-hr': {
572
- display: 'inline-block',
573
- width: '100%',
574
- height: '0',
575
- verticalAlign: 'middle',
576
- borderTop: `1px solid ${getToken('extend.colors.primary.500')}`,
577
- opacity: 0.5,
578
- },
579
-
580
- '& .cm-task': {
581
- display: 'inline-block',
582
- width: `${bulletListIndentationWidth}px`,
583
- color: getToken('extend.colors.blue.500'),
584
- },
585
- '& .cm-task-checkbox': {
586
- display: 'grid',
587
- margin: '0',
588
- transform: 'translateY(2px)',
589
- },
590
-
591
- '& .cm-list-item': {},
592
- '& .cm-list-mark': {
593
- display: 'inline-block',
594
- textAlign: 'right',
595
- paddingRight: '0.5em',
596
- fontVariant: 'tabular-nums',
597
- },
598
- '& .cm-list-mark-bullet': {
599
- width: `${bulletListIndentationWidth}px`,
600
- },
601
- '& .cm-list-mark-ordered': {
602
- width: `${orderedListIndentationWidth}px`,
603
- },
604
- });
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import { snippet } from '@codemirror/autocomplete';
6
- import { indentWithTab } from '@codemirror/commands';
7
6
  import { syntaxTree } from '@codemirror/language';
8
7
  import {
9
8
  type Extension,
@@ -1016,7 +1015,6 @@ export const toggleCodeblock: StateCommand = (target) => {
1016
1015
  //
1017
1016
 
1018
1017
  export type FormattingOptions = {};
1019
- indentWithTab;
1020
1018
 
1021
1019
  export const formattingKeymap = (_options: FormattingOptions = {}): Extension => {
1022
1020
  return [
@@ -1259,6 +1257,7 @@ export const useFormattingState = (): [Formatting | undefined, Extension] => {
1259
1257
  if (!prevState || !formattingEquals(prevState, newState)) {
1260
1258
  return newState;
1261
1259
  }
1260
+
1262
1261
  return prevState;
1263
1262
  });
1264
1263
  }
@@ -7,7 +7,7 @@ import { HighlightStyle } from '@codemirror/language';
7
7
  import { tags, styleTags, Tag } from '@lezer/highlight';
8
8
  import { type MarkdownConfig, Table } from '@lezer/markdown';
9
9
 
10
- import { getToken, theme } from '../../styles';
10
+ import { fontBody, theme } from '../../styles';
11
11
 
12
12
  /**
13
13
  * Custom tags defined and processed by the GFM lezer extension.
@@ -181,7 +181,7 @@ export const markdownHighlightStyle = (_options: HighlightOptions = {}) => {
181
181
  {
182
182
  scope: markdownLanguage,
183
183
  all: {
184
- fontFamily: getToken('fontFamily.body'),
184
+ fontFamily: fontBody,
185
185
  },
186
186
  },
187
187
  );
@@ -4,6 +4,7 @@
4
4
 
5
5
  export * from './action';
6
6
  export * from './bundle';
7
+ export * from './debug';
7
8
  export * from './decorate';
8
9
  export * from './formatting';
9
10
  export * from './highlight';
@@ -0,0 +1,103 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { EditorView } from '@codemirror/view';
6
+
7
+ import { fontMono } from '../../styles';
8
+
9
+ export const bulletListIndentationWidth = 24;
10
+ export const orderedListIndentationWidth = 36; // TODO(burdon): Make variable length based on number of digits.
11
+
12
+ export const formattingStyles = EditorView.theme({
13
+ /**
14
+ * Horizontal rule.
15
+ */
16
+ '& .cm-hr': {
17
+ display: 'inline-block',
18
+ width: '100%',
19
+ height: '0',
20
+ verticalAlign: 'middle',
21
+ borderTop: '1px solid var(--dx-cmSeparator)',
22
+ opacity: 0.5,
23
+ },
24
+
25
+ /**
26
+ * Lists.
27
+ */
28
+ '& .cm-list-item': {},
29
+ '& .cm-list-mark': {
30
+ display: 'inline-block',
31
+ textAlign: 'right',
32
+ paddingRight: '0.5em',
33
+ fontVariant: 'tabular-nums',
34
+ },
35
+ '& .cm-list-mark-bullet': {
36
+ width: `${bulletListIndentationWidth}px`,
37
+ },
38
+ '& .cm-list-mark-ordered': {
39
+ width: `${orderedListIndentationWidth}px`,
40
+ },
41
+
42
+ /**
43
+ * Code and codeblocks.
44
+ */
45
+ '& .cm-code': {
46
+ fontFamily: fontMono,
47
+ },
48
+ '& .cm-codeblock-line': {
49
+ background: 'var(--dx-cmCodeblock)',
50
+ paddingInline: '1rem !important',
51
+ },
52
+ '& .cm-codeblock-first': {
53
+ borderTopLeftRadius: '.25rem',
54
+ borderTopRightRadius: '.25rem',
55
+ },
56
+ '& .cm-codeblock-last': {
57
+ borderBottomLeftRadius: '.25rem',
58
+ borderBottomRightRadius: '.25rem',
59
+ },
60
+
61
+ /**
62
+ * Task list.
63
+ */
64
+ '& .cm-task': {
65
+ display: 'inline-block',
66
+ width: `${bulletListIndentationWidth}px`,
67
+ },
68
+ '& .cm-task-checkbox': {
69
+ display: 'grid',
70
+ margin: '0',
71
+ transform: 'translateY(2px)',
72
+ },
73
+
74
+ /**
75
+ * Table.
76
+ */
77
+ '.cm-table *': {
78
+ fontFamily: fontMono,
79
+ textDecoration: 'none !important',
80
+ },
81
+ '.cm-table-head': {
82
+ padding: '2px 16px 2px 0px',
83
+ textAlign: 'left',
84
+ borderBottom: '1px solid var(--dx-cmSeparator)',
85
+ color: 'var(--dx-subdued)',
86
+ },
87
+ '.cm-table-cell': {
88
+ padding: '2px 16px 2px 0px',
89
+ },
90
+
91
+ /**
92
+ * Image.
93
+ */
94
+ '.cm-image': {
95
+ display: 'block',
96
+ height: '0',
97
+ },
98
+ '.cm-image.cm-loaded-image': {
99
+ height: 'auto',
100
+ borderTop: '0.5rem solid transparent',
101
+ borderBottom: '0.5rem solid transparent',
102
+ },
103
+ });
package/src/index.ts CHANGED
@@ -10,8 +10,6 @@ export { tags } from '@lezer/highlight';
10
10
 
11
11
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
12
12
 
13
- export { getToken } from './styles';
14
-
15
13
  export * from './components';
16
14
  export * from './defaults';
17
15
  export * from './extensions';