@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.
Files changed (75) hide show
  1. package/dist/lib/browser/index.mjs +759 -732
  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 +2 -1
  5. package/dist/types/src/InputMode.stories.d.ts.map +1 -1
  6. package/dist/types/src/TextEditor.stories.d.ts +20 -13
  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 +2 -1
  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 -5
  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/InputMode.stories.tsx +1 -1
  45. package/src/TextEditor.stories.tsx +125 -77
  46. package/src/components/Toolbar/Toolbar.tsx +91 -92
  47. package/src/defaults.ts +16 -11
  48. package/src/extensions/annotations.ts +2 -2
  49. package/src/extensions/autocomplete.ts +4 -1
  50. package/src/extensions/automerge/automerge.stories.tsx +1 -1
  51. package/src/extensions/awareness/awareness.ts +1 -1
  52. package/src/extensions/comments.ts +11 -45
  53. package/src/extensions/dnd.ts +3 -5
  54. package/src/extensions/factories.ts +8 -8
  55. package/src/extensions/folding.tsx +3 -4
  56. package/src/extensions/markdown/action.ts +1 -0
  57. package/src/extensions/markdown/bundle.ts +3 -1
  58. package/src/extensions/markdown/{link-paste.test.ts → changes.test.ts} +2 -2
  59. package/src/extensions/markdown/changes.ts +148 -0
  60. package/src/extensions/markdown/debug.ts +44 -0
  61. package/src/extensions/markdown/decorate.ts +35 -108
  62. package/src/extensions/markdown/formatting.ts +1 -2
  63. package/src/extensions/markdown/highlight.ts +2 -2
  64. package/src/extensions/markdown/index.ts +1 -0
  65. package/src/extensions/markdown/parser.test.ts +29 -0
  66. package/src/extensions/markdown/styles.ts +103 -0
  67. package/src/index.ts +0 -2
  68. package/src/styles/theme.ts +85 -147
  69. package/src/styles/tokens.ts +6 -6
  70. package/src/translations.ts +1 -0
  71. package/dist/types/src/extensions/markdown/link-paste.d.ts +0 -9
  72. package/dist/types/src/extensions/markdown/link-paste.d.ts.map +0 -1
  73. package/dist/types/src/extensions/markdown/link-paste.test.d.ts +0 -2
  74. package/dist/types/src/extensions/markdown/link-paste.test.d.ts.map +0 -1
  75. package/src/extensions/markdown/link-paste.ts +0 -107
@@ -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 = 32; // 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,19 +175,18 @@ 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
  };
184
182
 
183
+ // const count = 0;
185
184
  const enterNode = (node: SyntaxNodeRef) => {
186
- // console.log('##', { node: node.name, from: node.from, to: node.to });
185
+ // console.log(`[${count++}]`, { node: node.name, from: node.from, to: node.to });
187
186
  switch (node.name) {
188
187
  // ATXHeading > HeaderMark > Paragraph
189
188
  // NOTE: Numbering requires processing the entire document since otherwise only the visible range will be
190
189
  // processed and the numbering will be incorrect.
191
- // TODO(burdon): Code folding (via gutter).
192
- // Modify parser to create foldable sections that can be skipped (or pre-processed).
193
190
  case 'ATXHeading1':
194
191
  case 'ATXHeading2':
195
192
  case 'ATXHeading3':
@@ -250,53 +247,53 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
250
247
 
251
248
  case 'ListItem': {
252
249
  // Set indentation.
253
- const list = getCurrentList();
250
+ const list = getCurrentListLevel();
254
251
  const width = list.type === 'OrderedList' ? orderedListIndentationWidth : bulletListIndentationWidth;
255
252
  const offset = ((list.level ?? 0) + 1) * width;
256
- const start = state.doc.lineAt(node.from);
253
+ const line = state.doc.lineAt(node.from);
254
+ if (node.from === line.to - 1) {
255
+ // Abort if only the hyphen is typed.
256
+ return false;
257
+ }
257
258
 
259
+ // Add line decoration for continuation indent.
258
260
  deco.add(
259
- start.from,
260
- start.from,
261
+ line.from,
262
+ line.from,
261
263
  Decoration.line({
262
264
  class: 'cm-list-item',
263
265
  attributes: {
264
- // Subtract 0.25em to account for the space CM adds to Paragraph nodes following the ListItem.
265
- // Note: This makes the cursor appear to be left of the margin.
266
- style: `padding-left: ${offset}px; text-indent: calc(-${width}px - 0.25em);`,
266
+ style: `padding-left: ${offset}px; text-indent: -${width}px;`,
267
267
  },
268
268
  }),
269
269
  );
270
270
 
271
271
  // Remove indentation spaces.
272
- // TODO(burdon): Replace whitespace with atomic block. Parse ListMark inline.
273
- const line = state.doc.sliceString(start.from, node.to);
274
- const whitespace = line.match(/^ */)?.[0].length ?? 0;
272
+ const text = state.doc.sliceString(line.from, node.to);
273
+ const whitespace = text.match(/^ */)?.[0].length ?? 0;
275
274
  if (whitespace) {
276
- atomicDeco.add(start.from, start.from + whitespace, hide);
275
+ atomicDeco.add(line.from, line.from + whitespace, hide);
277
276
  }
278
277
 
279
- // const mark = node.node.firstChild!;
280
- // console.log(mark?.name);
281
- // if (mark?.name === 'ListMark') {}
282
278
  break;
283
279
  }
284
280
 
285
281
  case 'ListMark': {
286
282
  // Look-ahead for task marker.
287
- const task = tree.resolve(node.to + 1, 1).name === 'TaskMarker';
288
- if (task) {
289
- atomicDeco.add(node.from, node.to, hide);
283
+ // NOTE: Requires space to exist (otherwise processes as a link).
284
+ const next = tree.resolve(node.to + 1, 1);
285
+ if (next?.name === 'TaskMarker') {
286
+ atomicDeco.add(node.from, node.to + 1, hide);
290
287
  break;
291
288
  }
292
289
 
293
- // TODO(burdon): Cursor stops for 1 character when moving back into number (but not dashes).
294
- // TODO(burdon): Option to make hierarchical; or a, b, c. etc.
295
- const list = getCurrentList();
296
- const label = list.type === 'OrderedList' ? `${++list.number}.` : '-';
290
+ const list = getCurrentListLevel();
291
+
292
+ // TODO(burdon): Option to make hierarchical; or a), i), etc.
293
+ const label = list.type === 'OrderedList' ? `${++list.number}.` : '';
297
294
  atomicDeco.add(
298
295
  node.from,
299
- node.to,
296
+ node.to + 1,
300
297
  Decoration.replace({
301
298
  widget: new TextWidget(
302
299
  label,
@@ -310,8 +307,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
310
307
  case 'TaskMarker': {
311
308
  if (!editingRange(state, node, focus)) {
312
309
  const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
313
- atomicDeco.add(node.from - 2, node.from - 1, Decoration.mark({ class: 'cm-task-checkbox' }));
314
- atomicDeco.add(node.from, node.to, checked ? checkedTask : uncheckedTask);
310
+ atomicDeco.add(node.from, node.to + 1, checked ? checkedTask : uncheckedTask);
315
311
  }
316
312
  break;
317
313
  }
@@ -439,7 +435,6 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
439
435
  }
440
436
  } else {
441
437
  // NOTE: If line numbering then we must iterate from the start of document.
442
- // TODO(burdon): Same for lists?
443
438
  tree.iterate({
444
439
  enter: wrapWithCatch(enterNode),
445
440
  leave: wrapWithCatch(leaveNode),
@@ -452,6 +447,8 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
452
447
  };
453
448
  };
454
449
 
450
+ const forceUpdate = StateEffect.define<null>();
451
+
455
452
  export interface DecorateOptions {
456
453
  /**
457
454
  * Prevents triggering decorations as the cursor moves through the document.
@@ -461,8 +458,6 @@ export interface DecorateOptions {
461
458
  renderLinkButton?: (el: Element, url: string) => void;
462
459
  }
463
460
 
464
- const forceUpdate = StateEffect.define<null>();
465
-
466
461
  export const decorateMarkdown = (options: DecorateOptions = {}) => {
467
462
  return [
468
463
  ViewPlugin.fromClass(
@@ -480,7 +475,7 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
480
475
  update.docChanged ||
481
476
  update.viewportChanged ||
482
477
  update.focusChanged ||
483
- update.transactions.some((tr) => tr.effects.some((e) => e.is(forceUpdate))) ||
478
+ update.transactions.some((tr) => tr.effects.some((effect) => effect.is(forceUpdate))) ||
484
479
  (update.selectionSet && !options.selectionChangeDelay)
485
480
  ) {
486
481
  ({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations(
@@ -495,6 +490,7 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
495
490
  }
496
491
  }
497
492
 
493
+ // Defer update in case moving through the document.
498
494
  scheduleUpdate(view: EditorView) {
499
495
  this.clearUpdate();
500
496
  this.pendingUpdate = setTimeout(() => {
@@ -521,78 +517,9 @@ export const decorateMarkdown = (options: DecorateOptions = {}) => {
521
517
  ],
522
518
  },
523
519
  ),
524
- formattingStyles,
525
- linkPastePlugin,
526
520
  image(),
527
521
  table(),
522
+ adjustChanges(),
523
+ formattingStyles,
528
524
  ];
529
525
  };
530
-
531
- const formattingStyles = EditorView.baseTheme({
532
- '& .cm-code': {
533
- fontFamily: getToken('fontFamily.mono'),
534
- },
535
-
536
- '& .cm-codeblock-line': {
537
- paddingInline: '1rem !important',
538
- },
539
- '& .cm-codeblock-end': {
540
- display: 'inline-block',
541
- width: '100%',
542
- position: 'relative',
543
- '&::after': {
544
- position: 'absolute',
545
- inset: 0,
546
- content: '""',
547
- },
548
- },
549
- '& .cm-codeblock-first': {
550
- borderTopLeftRadius: '.25rem',
551
- borderTopRightRadius: '.25rem',
552
- },
553
- '& .cm-codeblock-last': {
554
- borderBottomLeftRadius: '.25rem',
555
- borderBottomRightRadius: '.25rem',
556
- },
557
- '&light .cm-codeblock-line, &light .cm-activeLine.cm-codeblock-line': {
558
- background: getToken('extend.semanticColors.input.light'),
559
- mixBlendMode: 'darken',
560
- },
561
- '&dark .cm-codeblock-line, &dark .cm-activeLine.cm-codeblock-line': {
562
- background: getToken('extend.semanticColors.input.dark'), // TODO(burdon): Make darker.
563
- mixBlendMode: 'lighten',
564
- },
565
-
566
- '& .cm-hr': {
567
- display: 'inline-block',
568
- width: '100%',
569
- height: '0',
570
- verticalAlign: 'middle',
571
- borderTop: `1px solid ${getToken('extend.colors.primary.500')}`,
572
- opacity: 0.5,
573
- },
574
-
575
- '& .cm-task': {
576
- display: 'inline-block',
577
- width: `calc(${bulletListIndentationWidth}px - 0.25em)`,
578
- color: getToken('extend.colors.blue.500'),
579
- },
580
- '& .cm-task-checkbox': {
581
- display: 'grid',
582
- margin: '0',
583
- transform: 'translateY(2px)',
584
- },
585
-
586
- '& .cm-list-item': {},
587
- '& .cm-list-mark': {
588
- display: 'inline-block',
589
- textAlign: 'right',
590
- fontVariant: 'tabular-nums',
591
- },
592
- '& .cm-list-mark-bullet': {
593
- width: `${bulletListIndentationWidth}px`,
594
- },
595
- '& .cm-list-mark-ordered': {
596
- width: `${orderedListIndentationWidth}px`,
597
- },
598
- });
@@ -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';
@@ -9,6 +9,34 @@ import { parser } from '@lezer/markdown';
9
9
  import { describe, test } from '@dxos/test';
10
10
 
11
11
  describe('parser', () => {
12
+ // test.only('list-mark', () => {
13
+ // const newParser = parser.configure({
14
+ // parseBlock: [
15
+ // {
16
+ // name: 'ListItem',
17
+ // parse: (cx, line) => {
18
+ // console.log(`[${line.text}]`, cx.lineStart, line.text.length);
19
+ // // line.skipSpace(1);
20
+ // return true;
21
+ // },
22
+ // },
23
+ // ],
24
+ // });
25
+ //
26
+ // {
27
+ // const result = newParser.parse(' - ');
28
+ // testTree(result, 'Document(BulletList(ListItem(ListMark)))');
29
+ // }
30
+ // {
31
+ // const result = newParser.parse('-x');
32
+ // testTree(result, 'Document(Paragraph)');
33
+ // }
34
+ // {
35
+ // const result = newParser.parse('- x');
36
+ // testTree(result, 'Document(BulletList(ListItem(ListMark,Paragraph)))');
37
+ // }
38
+ // });
39
+
12
40
  // https://www.markdownguide.org/basic-syntax/#lists-1
13
41
  test('lists', () => {
14
42
  // Indented list must have 4 spaces.
@@ -25,6 +53,7 @@ describe('parser', () => {
25
53
  '1. one',
26
54
  ].join('\n'),
27
55
  );
56
+
28
57
  testTree(
29
58
  result,
30
59
  [
@@ -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';