@dxos/react-ui-editor 0.6.13-main.548ca8d → 0.6.13-staging.1e988a3

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 (123) hide show
  1. package/dist/lib/browser/index.mjs +345 -227
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/TextEditor.stories.d.ts +1 -4
  5. package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/defaults.d.ts.map +1 -1
  7. package/dist/types/src/extensions/autocomplete.d.ts +1 -2
  8. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  9. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  10. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  11. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -1
  12. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  13. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  14. package/dist/types/src/extensions/awareness/awareness.d.ts +2 -2
  15. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  16. package/dist/types/src/extensions/command/state.d.ts +2 -2
  17. package/dist/types/src/extensions/command/state.d.ts.map +1 -1
  18. package/dist/types/src/extensions/comments.d.ts +1 -1
  19. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  20. package/dist/types/src/{state → extensions}/cursor.d.ts +2 -2
  21. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  22. package/dist/types/src/extensions/debug.d.ts +2 -2
  23. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  24. package/dist/types/src/extensions/doc.d.ts +6 -0
  25. package/dist/types/src/extensions/doc.d.ts.map +1 -0
  26. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  27. package/dist/types/src/extensions/index.d.ts +4 -0
  28. package/dist/types/src/extensions/index.d.ts.map +1 -1
  29. package/dist/types/src/extensions/listener.d.ts +0 -1
  30. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  31. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  32. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -1
  33. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  34. package/dist/types/src/extensions/markdown/link.d.ts +1 -1
  35. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  36. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
  37. package/dist/types/src/extensions/modes.d.ts +3 -3
  38. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  39. package/dist/types/src/{state → extensions}/state.d.ts +2 -2
  40. package/dist/types/src/extensions/state.d.ts.map +1 -0
  41. package/dist/types/src/extensions/types.d.ts.map +1 -0
  42. package/dist/types/src/extensions/util/overlap.d.ts +1 -1
  43. package/dist/types/src/extensions/util/overlap.d.ts.map +1 -1
  44. package/dist/types/src/extensions/util/react.d.ts +1 -1
  45. package/dist/types/src/extensions/util/react.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useTextEditor.d.ts +1 -1
  47. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  48. package/dist/types/src/index.d.ts +0 -1
  49. package/dist/types/src/index.d.ts.map +1 -1
  50. package/dist/types/src/styles/markdown.d.ts +2 -1
  51. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  52. package/dist/types/src/styles/theme.d.ts.map +1 -1
  53. package/dist/types/src/util.d.ts +0 -6
  54. package/dist/types/src/util.d.ts.map +1 -1
  55. package/package.json +38 -59
  56. package/src/TextEditor.stories.tsx +17 -22
  57. package/src/defaults.ts +2 -0
  58. package/src/extensions/annotations.ts +1 -1
  59. package/src/extensions/autocomplete.ts +8 -9
  60. package/src/extensions/automerge/{automerge.test.tsx → automerge.spec.tsx} +0 -1
  61. package/src/extensions/automerge/automerge.stories.tsx +1 -1
  62. package/src/extensions/automerge/automerge.test.ts +13 -0
  63. package/src/extensions/automerge/automerge.ts +2 -2
  64. package/src/extensions/automerge/cursor.ts +1 -1
  65. package/src/extensions/awareness/awareness.ts +5 -3
  66. package/src/extensions/command/hint.ts +1 -1
  67. package/src/extensions/command/state.ts +4 -3
  68. package/src/extensions/comments.ts +45 -44
  69. package/src/{state → extensions}/cursor.ts +6 -3
  70. package/src/extensions/debug.ts +2 -2
  71. package/src/extensions/doc.ts +17 -0
  72. package/src/extensions/folding.tsx +2 -4
  73. package/src/extensions/index.ts +4 -0
  74. package/src/extensions/listener.ts +0 -1
  75. package/src/extensions/markdown/changes.test.ts +3 -1
  76. package/src/extensions/markdown/decorate.ts +6 -49
  77. package/src/extensions/markdown/formatting.test.ts +3 -1
  78. package/src/extensions/markdown/highlight.ts +5 -0
  79. package/src/extensions/markdown/link.ts +2 -3
  80. package/src/extensions/markdown/parser.test.ts +2 -1
  81. package/src/extensions/markdown/styles.ts +0 -10
  82. package/src/extensions/markdown/table.ts +3 -3
  83. package/src/extensions/modes.ts +5 -6
  84. package/src/{state → extensions}/state.ts +3 -7
  85. package/src/extensions/util/overlap.ts +1 -1
  86. package/src/extensions/util/react.tsx +1 -5
  87. package/src/hooks/useTextEditor.ts +31 -30
  88. package/src/index.ts +0 -1
  89. package/src/styles/markdown.ts +3 -1
  90. package/src/styles/theme.ts +1 -3
  91. package/src/util.ts +0 -10
  92. package/dist/lib/browser/chunk-CIQSMP7K.mjs +0 -148
  93. package/dist/lib/browser/chunk-CIQSMP7K.mjs.map +0 -7
  94. package/dist/lib/browser/state/index.mjs +0 -17
  95. package/dist/lib/browser/state/index.mjs.map +0 -7
  96. package/dist/lib/node/chunk-GZWIENFM.cjs +0 -169
  97. package/dist/lib/node/chunk-GZWIENFM.cjs.map +0 -7
  98. package/dist/lib/node/index.cjs +0 -5480
  99. package/dist/lib/node/index.cjs.map +0 -7
  100. package/dist/lib/node/meta.json +0 -1
  101. package/dist/lib/node/state/index.cjs +0 -39
  102. package/dist/lib/node/state/index.cjs.map +0 -7
  103. package/dist/lib/node-esm/chunk-GP5RCZ3X.mjs +0 -150
  104. package/dist/lib/node-esm/chunk-GP5RCZ3X.mjs.map +0 -7
  105. package/dist/lib/node-esm/index.mjs +0 -5471
  106. package/dist/lib/node-esm/index.mjs.map +0 -7
  107. package/dist/lib/node-esm/meta.json +0 -1
  108. package/dist/lib/node-esm/state/index.mjs +0 -18
  109. package/dist/lib/node-esm/state/index.mjs.map +0 -7
  110. package/dist/types/src/state/cursor.d.ts.map +0 -1
  111. package/dist/types/src/state/doc.d.ts +0 -5
  112. package/dist/types/src/state/doc.d.ts.map +0 -1
  113. package/dist/types/src/state/index.d.ts +0 -6
  114. package/dist/types/src/state/index.d.ts.map +0 -1
  115. package/dist/types/src/state/state.d.ts.map +0 -1
  116. package/dist/types/src/state/types.d.ts.map +0 -1
  117. package/dist/types/src/state/util.d.ts +0 -3
  118. package/dist/types/src/state/util.d.ts.map +0 -1
  119. package/src/state/doc.ts +0 -10
  120. package/src/state/index.ts +0 -11
  121. package/src/state/util.ts +0 -13
  122. /package/dist/types/src/{state → extensions}/types.d.ts +0 -0
  123. /package/src/{state → extensions}/types.ts +0 -0
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { StateEffect, StateField } from '@codemirror/state';
5
+ import { Facet, StateEffect, StateField } from '@codemirror/state';
6
6
  import {
7
7
  type Command,
8
8
  type EditorView,
@@ -13,13 +13,14 @@ import {
13
13
  } from '@codemirror/view';
14
14
 
15
15
  import { type CommandOptions } from './command';
16
- import { singleValueFacet } from '../../state';
17
16
 
18
17
  type CommandState = {
19
18
  tooltip?: Tooltip | null;
20
19
  };
21
20
 
22
- export const commandConfig = singleValueFacet<CommandOptions>();
21
+ export const commandConfig = Facet.define<CommandOptions, Required<CommandOptions>>({
22
+ combine: (providers) => providers[0],
23
+ });
23
24
 
24
25
  export const commandState = StateField.define<CommandState>({
25
26
  create: () => ({}),
@@ -5,6 +5,7 @@
5
5
  import { invertedEffects } from '@codemirror/commands';
6
6
  import {
7
7
  type Extension,
8
+ Facet,
8
9
  StateEffect,
9
10
  StateField,
10
11
  type Text,
@@ -28,15 +29,18 @@ import { debounce, type UnsubscribeCallback } from '@dxos/async';
28
29
  import { log } from '@dxos/log';
29
30
  import { nonNullable } from '@dxos/util';
30
31
 
32
+ import { Cursor } from './cursor';
33
+ import { type Comment, type Range } from './types';
31
34
  import { overlap } from './util';
32
- import { Cursor, documentId, singleValueFacet } from '../state';
33
- import { type Comment, type Range } from '../state';
34
35
  import { callbackWrapper } from '../util';
35
36
 
36
37
  //
37
38
  // State management.
38
39
  //
39
40
 
41
+ // TODO(wittjosiah): Factor out, not comments-specific.
42
+ const documentId = Facet.define<string | undefined, string | undefined>({ combine: (values) => values[0] });
43
+
40
44
  type CommentState = {
41
45
  comment: Comment;
42
46
  range: Range;
@@ -365,7 +369,7 @@ export type CommentsOptions = {
365
369
  onHover?: (el: Element, shortcut: string) => void;
366
370
  };
367
371
 
368
- const optionsFacet = singleValueFacet<CommentsOptions>();
372
+ const optionsFacet = Facet.define<CommentsOptions, CommentsOptions>({ combine: (providers) => providers[0] });
369
373
 
370
374
  /**
371
375
  * Comment threads.
@@ -385,7 +389,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
385
389
 
386
390
  return [
387
391
  optionsFacet.of(options),
388
- options.id ? documentId.of(options.id) : undefined,
392
+ documentId.of(options.id),
389
393
  commentsState,
390
394
  commentsDecorations,
391
395
  handleCommentClick,
@@ -394,43 +398,45 @@ export const comments = (options: CommentsOptions = {}): Extension => {
394
398
  //
395
399
  // Keymap.
396
400
  //
397
- options.onCreate &&
398
- keymap.of([
399
- {
400
- key: shortcut,
401
- run: callbackWrapper(createComment),
402
- },
403
- ]),
401
+ options.onCreate
402
+ ? keymap.of([
403
+ {
404
+ key: shortcut,
405
+ run: callbackWrapper(createComment),
406
+ },
407
+ ])
408
+ : [],
404
409
 
405
410
  //
406
411
  // Hover tooltip (for key shortcut hints, etc.)
407
412
  // TODO(burdon): Factor out to generic hints extension for current selection/line.
408
413
  //
409
- options.onHover &&
410
- hoverTooltip(
411
- (view, pos) => {
412
- const selection = view.state.selection.main;
413
- if (selection && pos >= selection.from && pos <= selection.to) {
414
- return {
415
- pos: selection.from,
416
- end: selection.to,
417
- above: true,
418
- create: () => {
419
- const el = document.createElement('div');
420
- options.onHover!(el, shortcut);
421
- return { dom: el, offset: { x: 0, y: 8 } };
422
- },
423
- };
424
- }
414
+ options.onHover
415
+ ? hoverTooltip(
416
+ (view, pos) => {
417
+ const selection = view.state.selection.main;
418
+ if (selection && pos >= selection.from && pos <= selection.to) {
419
+ return {
420
+ pos: selection.from,
421
+ end: selection.to,
422
+ above: true,
423
+ create: () => {
424
+ const el = document.createElement('div');
425
+ options.onHover!(el, shortcut);
426
+ return { dom: el, offset: { x: 0, y: 8 } };
427
+ },
428
+ };
429
+ }
425
430
 
426
- return null;
427
- },
428
- {
429
- // TODO(burdon): Hide on change triggered immediately?
430
- // hideOnChange: true,
431
- hoverTime: 1_000,
432
- },
433
- ),
431
+ return null;
432
+ },
433
+ {
434
+ // TODO(burdon): Hide on change triggered immediately?
435
+ // hideOnChange: true,
436
+ hoverTime: 1_000,
437
+ },
438
+ )
439
+ : [],
434
440
 
435
441
  //
436
442
  // Track deleted ranges and update ranges for decorations.
@@ -505,8 +511,8 @@ export const comments = (options: CommentsOptions = {}): Extension => {
505
511
  }
506
512
  }),
507
513
 
508
- options.onUpdate && trackPastedComments(options.onUpdate),
509
- ].filter(nonNullable);
514
+ options.onUpdate ? trackPastedComments(options.onUpdate) : [],
515
+ ];
510
516
  };
511
517
 
512
518
  //
@@ -547,13 +553,9 @@ export const scrollThreadIntoView = (view: EditorView, id: string, center = true
547
553
  * Query the editor state for the active formatting at the selection.
548
554
  */
549
555
  export const selectionOverlapsComment = (state: EditorState): boolean => {
550
- // May not be defined if thread plugin not installed.
551
- const commentState = state.field(commentsState, false);
552
- if (commentState === undefined) {
553
- return false;
554
- }
555
-
556
556
  const { selection } = state;
557
+ const commentState = state.field(commentsState);
558
+
557
559
  for (const range of selection.ranges) {
558
560
  if (commentState.comments.some(({ range: commentRange }) => overlap(commentRange, range))) {
559
561
  return true;
@@ -595,7 +597,6 @@ class ExternalCommentSync implements PluginValue {
595
597
  };
596
598
  }
597
599
 
598
- // TODO(burdon): Needs comment.
599
600
  export const createExternalCommentSync = (
600
601
  id: string,
601
602
  subscribe: (sink: () => void) => UnsubscribeCallback,
@@ -2,10 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type EditorState } from '@codemirror/state';
5
+ import { type EditorState, Facet } from '@codemirror/state';
6
6
 
7
7
  import { type Range } from './types';
8
- import { singleValueFacet } from './util';
9
8
 
10
9
  /**
11
10
  * Converts indexes into the text document into stable peer-independent cursors.
@@ -28,7 +27,11 @@ const defaultCursorConverter: CursorConverter = {
28
27
  };
29
28
 
30
29
  export class Cursor {
31
- static readonly converter = singleValueFacet(defaultCursorConverter);
30
+ static readonly converter = Facet.define<CursorConverter, CursorConverter>({
31
+ combine: (providers) => {
32
+ return providers[0] ?? defaultCursorConverter;
33
+ },
34
+ });
32
35
 
33
36
  static readonly getCursorFromRange = (state: EditorState, range: Range) => {
34
37
  const cursorConverter = state.facet(Cursor.converter);
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import { syntaxTree } from '@codemirror/language';
6
- import { type EditorState, type Extension, type RangeSet, StateField, type Transaction } from '@codemirror/state';
6
+ import { type EditorState, type RangeSet, StateField, type Transaction } from '@codemirror/state';
7
7
 
8
8
  // eslint-disable-next-line no-console
9
- export const debugNodeLogger = (log: (...args: any[]) => void = console.log): Extension => {
9
+ export const debugNodeLogger = (log: (...args: any[]) => void = console.log) => {
10
10
  const logTokens = (state: EditorState) => syntaxTree(state).iterate({ enter: (node) => log(node.type) });
11
11
  return StateField.define<any>({
12
12
  create: (state) => logTokens(state),
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Facet } from '@codemirror/state';
6
+
7
+ import { invariant } from '@dxos/invariant';
8
+
9
+ /**
10
+ * Currently edited document id as FQ string.
11
+ */
12
+ export const documentId = Facet.define<string, string>({
13
+ combine: (providers) => {
14
+ invariant(providers.length <= 1);
15
+ return providers[0];
16
+ },
17
+ });
@@ -17,7 +17,7 @@ export type FoldingOptions = {};
17
17
  /**
18
18
  * https://codemirror.net/examples/gutter
19
19
  */
20
- // TODO(burdon): Remember folding state (to state).
20
+ // TODO(burdon): Remember folding state.
21
21
  export const folding = (_props: FoldingOptions = {}): Extension => [
22
22
  codeFolding({
23
23
  placeholderDOM: () => {
@@ -26,10 +26,8 @@ export const folding = (_props: FoldingOptions = {}): Extension => [
26
26
  }),
27
27
  foldGutter({
28
28
  markerDOM: (open) => {
29
- // TODO(burdon): Use sprite directly.
30
- const el = createElement('div', { className: 'flex h-full items-center' });
31
29
  return renderRoot(
32
- el,
30
+ createElement('div', { className: 'flex h-full items-center' }),
33
31
  <Icon icon='ph--caret-right--regular' classNames={[getSize(3), 'mx-3 cursor-pointer', open && 'rotate-90']} />,
34
32
  );
35
33
  },
@@ -9,7 +9,9 @@ export * from './awareness';
9
9
  export * from './blast';
10
10
  export * from './command';
11
11
  export * from './comments';
12
+ export * from './cursor';
12
13
  export * from './debug';
14
+ export * from './doc';
13
15
  export * from './dnd';
14
16
  export * from './factories';
15
17
  export * from './folding';
@@ -17,4 +19,6 @@ export * from './listener';
17
19
  export * from './markdown';
18
20
  export * from './mention';
19
21
  export * from './modes';
22
+ export * from './state';
23
+ export * from './types';
20
24
  export * from './typewriter';
@@ -12,7 +12,6 @@ export type ListenerOptions = {
12
12
 
13
13
  /**
14
14
  * Event listener.
15
- * @deprecated Use EditorView.updateListener and listen for specific update events.
16
15
  */
17
16
  export const listener = ({ onFocus, onChange }: ListenerOptions): Extension => {
18
17
  const extensions: Extension[] = [];
@@ -2,7 +2,9 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { describe, expect, test } from 'vitest';
5
+ import { expect } from 'chai';
6
+
7
+ import { describe, test } from '@dxos/test';
6
8
 
7
9
  import { createLinkLabel } from './changes';
8
10
 
@@ -45,7 +45,7 @@ class HorizontalRuleWidget extends WidgetType {
45
45
  class LinkButton extends WidgetType {
46
46
  constructor(
47
47
  private readonly url: string,
48
- private readonly render: (el: HTMLElement, url: string) => void,
48
+ private readonly render: (el: Element, url: string) => void,
49
49
  ) {
50
50
  super();
51
51
  }
@@ -54,7 +54,6 @@ class LinkButton extends WidgetType {
54
54
  return this.url === other.url;
55
55
  }
56
56
 
57
- // TODO(burdon): Create icon and link directly without react?
58
57
  override toDOM(view: EditorView) {
59
58
  const el = document.createElement('span');
60
59
  this.render(el, this.url);
@@ -128,7 +127,6 @@ class TextWidget extends WidgetType {
128
127
  }
129
128
 
130
129
  const hide = Decoration.replace({});
131
- const blockQuote = Decoration.line({ class: mx('cm-blockquote') });
132
130
  const fencedCodeLine = Decoration.line({ class: mx('cm-code cm-codeblock-line') });
133
131
  const fencedCodeLineFirst = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-first') });
134
132
  const fencedCodeLineLast = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-last') });
@@ -201,7 +199,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
201
199
  return listLevels[listLevels.length - 1];
202
200
  };
203
201
 
204
- // let count = 0;
202
+ // const count = 0;
205
203
  const enterNode = (node: SyntaxNodeRef) => {
206
204
  // console.log(`[${count++}]`, { node: node.name, from: node.from, to: node.to });
207
205
  switch (node.name) {
@@ -331,36 +329,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
331
329
  break;
332
330
  }
333
331
 
334
- //
335
- // Blockquote > QuoteMark > Paragraph
336
- //
337
-
338
- case 'Blockquote': {
339
- const editing = editingRange(state, node, focus);
340
- const quoteMark = node.node.getChild('QuoteMark');
341
- const paragraph = node.node.getChild('Paragraph');
342
- if (!editing && quoteMark && paragraph) {
343
- atomicDeco.add(quoteMark.from, paragraph.from, hide);
344
- }
345
-
346
- for (const block of view.viewportLineBlocks) {
347
- if (block.to < node.from) {
348
- continue;
349
- }
350
- if (block.from > node.to) {
351
- break;
352
- }
353
-
354
- deco.add(block.from, block.from, blockQuote);
355
- }
356
-
357
- break;
358
- }
359
-
360
- //
361
332
  // CommentBlock
362
- //
363
-
364
333
  case 'CommentBlock': {
365
334
  const editing = editingRange(state, node, focus);
366
335
  for (const block of view.viewportLineBlocks) {
@@ -370,27 +339,21 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
370
339
  if (block.from > node.to) {
371
340
  break;
372
341
  }
373
-
374
- const isFirst = block.from <= node.from;
375
- const isLast = block.to >= node.to && /^(\s>)*-->$/.test(state.doc.sliceString(block.from, block.to));
376
-
342
+ const first = block.from <= node.from;
343
+ const last = block.to >= node.to && /^(\s>)*-->$/.test(state.doc.sliceString(block.from, block.to));
377
344
  deco.add(
378
345
  block.from,
379
346
  block.from,
380
- isFirst ? commentBlockLineFirst : isLast ? commentBlockLineLast : commentBlockLine,
347
+ first ? commentBlockLineFirst : last ? commentBlockLineLast : commentBlockLine,
381
348
  );
382
-
383
- if (!editing && (isFirst || isLast)) {
349
+ if (!editing && (first || last)) {
384
350
  atomicDeco.add(block.from, block.to, hide);
385
351
  }
386
352
  }
387
353
  break;
388
354
  }
389
355
 
390
- //
391
356
  // FencedCode > CodeMark > [CodeInfo] > CodeText > CodeMark
392
- //
393
-
394
357
  case 'FencedCode': {
395
358
  for (const block of view.viewportLineBlocks) {
396
359
  if (block.to < node.from) {
@@ -412,10 +375,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
412
375
  return false;
413
376
  }
414
377
 
415
- //
416
378
  // Link > [LinkMark, URL]
417
- //
418
-
419
379
  case 'Link': {
420
380
  const marks = node.node.getChildren('LinkMark');
421
381
  const urlNode = node.node.getChild('URL');
@@ -452,10 +412,7 @@ const buildDecorations = (view: EditorView, options: DecorateOptions, focus: boo
452
412
  break;
453
413
  }
454
414
 
455
- //
456
415
  // HR
457
- //
458
-
459
416
  case 'HorizontalRule': {
460
417
  if (!editingRange(state, node, focus)) {
461
418
  deco.add(node.from, node.to, horizontalRule);
@@ -4,7 +4,9 @@
4
4
 
5
5
  import { markdownLanguage } from '@codemirror/lang-markdown';
6
6
  import { EditorState, type StateCommand } from '@codemirror/state';
7
- import { describe, expect, test } from 'vitest';
7
+ import { expect } from 'chai';
8
+
9
+ import { describe, test } from '@dxos/test';
8
10
 
9
11
  import {
10
12
  addBlockquote,
@@ -168,6 +168,11 @@ export const markdownHighlightStyle = (_options: HighlightOptions = {}) => {
168
168
  class: theme.code,
169
169
  },
170
170
 
171
+ {
172
+ tag: [markdownTags.QuoteMark],
173
+ class: theme.blockquote,
174
+ },
175
+
171
176
  {
172
177
  tag: [markdownTags.TableCell],
173
178
  class: 'font-mono',
@@ -9,8 +9,8 @@ import { type SyntaxNode } from '@lezer/common';
9
9
 
10
10
  import { tooltipContent } from '@dxos/react-ui-theme';
11
11
 
12
- export const linkTooltip = (render: (el: HTMLElement, url: string) => void) => {
13
- return hoverTooltip((view, pos, side) => {
12
+ export const linkTooltip = (render: (el: Element, url: string) => void) =>
13
+ hoverTooltip((view, pos, side) => {
14
14
  const syntax = syntaxTree(view.state).resolveInner(pos, side);
15
15
  let link = null;
16
16
  for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
@@ -35,4 +35,3 @@ export const linkTooltip = (render: (el: HTMLElement, url: string) => void) => {
35
35
  },
36
36
  };
37
37
  });
38
- };
@@ -5,7 +5,8 @@
5
5
  // @ts-ignore
6
6
  import { testTree } from '@lezer/generator/test';
7
7
  import { parser } from '@lezer/markdown';
8
- import { describe, test } from 'vitest';
8
+
9
+ import { describe, test } from '@dxos/test';
9
10
 
10
11
  describe('parser', () => {
11
12
  // test.only('list-mark', () => {
@@ -39,16 +39,6 @@ export const formattingStyles = EditorView.theme({
39
39
  width: `${orderedListIndentationWidth}px`,
40
40
  },
41
41
 
42
- /**
43
- * Blockquote.
44
- */
45
- '& .cm-blockquote': {
46
- background: 'var(--dx-cmCodeblock)',
47
- borderLeft: '2px solid var(--dx-cmSeparator)',
48
- paddingLeft: '1rem',
49
- margin: '0',
50
- },
51
-
52
42
  /**
53
43
  * Code and codeblocks.
54
44
  */
@@ -25,9 +25,9 @@ export type TableOptions = {};
25
25
  * https://github.github.com/gfm/#tables-extension
26
26
  */
27
27
  export const table = (options: TableOptions = {}): Extension => {
28
- return StateField.define<RangeSet<Decoration>>({
28
+ return StateField.define<RangeSet<any>>({
29
29
  create: (state) => update(state, options),
30
- update: (_: RangeSet<Decoration>, tr: Transaction) => update(tr.state, options),
30
+ update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
31
31
  provide: (field) => EditorView.decorations.from(field),
32
32
  });
33
33
  };
@@ -40,7 +40,7 @@ type Table = {
40
40
  };
41
41
 
42
42
  const update = (state: EditorState, _options: TableOptions) => {
43
- const builder = new RangeSetBuilder<Decoration>();
43
+ const builder = new RangeSetBuilder();
44
44
  const cursor = state.selection.main.head;
45
45
 
46
46
  const tables: Table[] = [];
@@ -2,27 +2,26 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Extension } from '@codemirror/state';
5
+ import { type Extension, Facet } from '@codemirror/state';
6
6
  import { keymap } from '@codemirror/view';
7
7
  import { vim } from '@replit/codemirror-vim';
8
8
  import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
9
9
 
10
- import { singleValueFacet } from '../state';
11
-
12
10
  export const focusEvent = 'focus.container';
13
11
 
14
12
  export const EditorViewModes = ['preview', 'readonly', 'source'] as const;
15
13
  export type EditorViewMode = (typeof EditorViewModes)[number];
16
-
17
14
  export const EditorInputModes = ['default', 'vim', 'vscode'] as const;
18
15
  export type EditorInputMode = (typeof EditorInputModes)[number];
19
16
 
20
17
  export type EditorInputConfig = {
21
- type?: string;
18
+ type: string;
22
19
  noTabster?: boolean;
23
20
  };
24
21
 
25
- export const editorInputMode = singleValueFacet<EditorInputConfig>({});
22
+ export const editorInputMode = Facet.define<EditorInputConfig, EditorInputConfig>({
23
+ combine: (modes) => modes[0] ?? {},
24
+ });
26
25
 
27
26
  export const InputModeExtensions: { [mode: string]: Extension } = {
28
27
  default: [],
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type EditorState, type Extension, Transaction, type TransactionSpec } from '@codemirror/state';
5
+ import { type Extension, Transaction, type TransactionSpec } from '@codemirror/state';
6
6
  import { EditorView, keymap } from '@codemirror/view';
7
7
 
8
8
  import { debounce } from '@dxos/async';
@@ -42,10 +42,7 @@ export const localStorageStateStoreAdapter: EditorStateOptions = {
42
42
  },
43
43
  };
44
44
 
45
- export const createEditorStateTransaction = (
46
- state: EditorState,
47
- { scrollTo, selection }: EditorSelectionState,
48
- ): TransactionSpec => {
45
+ export const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {
49
46
  return {
50
47
  selection,
51
48
  scrollIntoView: !scrollTo,
@@ -57,7 +54,6 @@ export const createEditorStateTransaction = (
57
54
  /**
58
55
  * Track scrolling and selection state to be restored when switching to document.
59
56
  */
60
- // TODO(burdon): Rename.
61
57
  export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}): Extension => {
62
58
  const setStateDebounced = debounce(setState!, 1_000);
63
59
 
@@ -90,7 +86,7 @@ export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}):
90
86
  run: (view) => {
91
87
  const state = getState(view.state.facet(documentId));
92
88
  if (state) {
93
- view.dispatch(createEditorStateTransaction(view.state, state));
89
+ view.dispatch(createEditorStateTransaction(state));
94
90
  }
95
91
  return true;
96
92
  },
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Range } from '../../state';
5
+ import { type Range } from '../types';
6
6
 
7
7
  /**
8
8
  * Determines if two ranges overlap.
@@ -8,8 +8,6 @@ import { createRoot } from 'react-dom/client';
8
8
  import { ThemeProvider } from '@dxos/react-ui';
9
9
  import { defaultTx } from '@dxos/react-ui-theme';
10
10
 
11
- // TODO(burdon): Factor out.
12
-
13
11
  export type ElementOptions = {
14
12
  className?: string;
15
13
  };
@@ -25,9 +23,7 @@ export const createElement = (tag: string, options?: ElementOptions, children?:
25
23
  return el;
26
24
  };
27
25
 
28
- // TODO(burdon): Remove react rendering; use DOM directly.
29
- // NOTE: CM seems to remove/detach/overwrite portals that are attached to the DOM it control.s
30
- export const renderRoot = <T extends Element>(root: T, node: ReactNode): T => {
26
+ export const renderRoot = (root: HTMLElement, node: ReactNode): HTMLElement => {
31
27
  createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
32
28
  return root;
33
29
  };