@dxos/react-ui-editor 0.3.10 → 0.3.11-main.0e40963

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 (141) hide show
  1. package/dist/lib/browser/index.mjs +1971 -1744
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +14 -10
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +3 -2
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  9. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  10. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  11. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  12. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  13. package/dist/types/src/extensions/code.d.ts.map +1 -0
  14. package/dist/types/src/extensions/comments.d.ts +48 -0
  15. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  16. package/dist/types/src/extensions/hr.d.ts +2 -0
  17. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  18. package/dist/types/src/extensions/image.d.ts.map +1 -0
  19. package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +3 -3
  20. package/dist/types/src/extensions/index.d.ts.map +1 -0
  21. package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
  22. package/dist/types/src/extensions/link.d.ts.map +1 -0
  23. package/dist/types/src/extensions/listener.d.ts +10 -0
  24. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  25. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  26. package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -1
  27. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  28. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  29. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  30. package/dist/types/src/extensions/mermaid.d.ts +3 -0
  31. package/dist/types/src/extensions/mermaid.d.ts.map +1 -0
  32. package/dist/types/src/extensions/table.d.ts.map +1 -0
  33. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  34. package/dist/types/src/{components/TextEditor/extensions/demo.d.ts → extensions/typewriter.d.ts} +2 -3
  35. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  36. package/dist/types/src/extensions/util.d.ts +5 -0
  37. package/dist/types/src/extensions/util.d.ts.map +1 -0
  38. package/dist/types/src/hooks/automerge/automerge.stories.d.ts +2 -3
  39. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +1 -1
  40. package/dist/types/src/hooks/automerge/plugin.d.ts.map +1 -1
  41. package/dist/types/src/hooks/automerge/{PatchSemaphore.d.ts → semaphore.d.ts} +1 -1
  42. package/dist/types/src/hooks/automerge/semaphore.d.ts.map +1 -0
  43. package/dist/types/src/hooks/automerge/update-automerge.d.ts +6 -0
  44. package/dist/types/src/hooks/automerge/update-automerge.d.ts.map +1 -0
  45. package/dist/types/src/hooks/automerge/update-codemirror.d.ts +5 -0
  46. package/dist/types/src/hooks/automerge/update-codemirror.d.ts.map +1 -0
  47. package/dist/types/src/hooks/useTextModel.d.ts +14 -6
  48. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  49. package/dist/types/src/hooks/yjs/yjs.test.d.ts +2 -0
  50. package/dist/types/src/hooks/yjs/yjs.test.d.ts.map +1 -0
  51. package/dist/types/src/index.d.ts +3 -0
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  54. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +5 -13
  55. package/dist/types/src/themes/default.d.ts.map +1 -0
  56. package/dist/types/src/themes/index.d.ts.map +1 -0
  57. package/package.json +24 -20
  58. package/src/components/TextEditor/MarkdownEditor.stories.tsx +130 -71
  59. package/src/components/TextEditor/TextEditor.stories.tsx +2 -2
  60. package/src/components/TextEditor/TextEditor.tsx +29 -25
  61. package/src/components/TextEditor/index.ts +0 -3
  62. package/src/{components/TextEditor/extensions → extensions}/code.ts +13 -13
  63. package/src/extensions/comments.ts +391 -0
  64. package/src/extensions/hr.ts +74 -0
  65. package/src/{components/TextEditor/extensions → extensions}/image.ts +3 -1
  66. package/src/{components/TextEditor/extensions → extensions}/index.ts +3 -3
  67. package/src/{components/TextEditor/extensions → extensions}/link.ts +66 -10
  68. package/src/extensions/listener.ts +35 -0
  69. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +6 -9
  70. package/src/extensions/mermaid.ts +11 -0
  71. package/src/{components/TextEditor/extensions → extensions}/table.ts +14 -1
  72. package/src/{components/TextEditor/extensions/demo.ts → extensions/typewriter.ts} +2 -3
  73. package/src/extensions/util.ts +18 -0
  74. package/src/hooks/automerge/automerge.stories.tsx +2 -3
  75. package/src/hooks/automerge/plugin.ts +1 -1
  76. package/src/hooks/automerge/{PatchSemaphore.ts → semaphore.ts} +4 -3
  77. package/src/hooks/automerge/{translation/codemirror-to-automerge.ts → update-automerge.ts} +3 -3
  78. package/src/hooks/automerge/{translation/automerge-to-codemirror.ts → update-codemirror.ts} +2 -7
  79. package/src/hooks/useTextModel.ts +60 -22
  80. package/src/hooks/yjs/yjs.test.ts +59 -0
  81. package/src/index.ts +3 -0
  82. package/src/testing/replicator.ts +0 -1
  83. package/src/{components/TextEditor/themes → themes}/default.ts +61 -38
  84. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  85. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  86. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  87. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  88. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -18
  89. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  90. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +0 -1
  91. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +0 -10
  92. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +0 -1
  93. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts +0 -24
  94. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts.map +0 -1
  95. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  96. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  97. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  98. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  99. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  100. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  101. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  102. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  103. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  104. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  105. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  106. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  107. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  108. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  109. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  110. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +0 -1
  111. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +0 -5
  112. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +0 -1
  113. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +0 -6
  114. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +0 -1
  115. package/dist/types/src/hooks/automerge/translation/index.d.ts +0 -3
  116. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +0 -1
  117. package/src/components/TextEditor/extensions/comments.ts +0 -173
  118. package/src/components/TextEditor/extensions/experimental.tsx +0 -114
  119. package/src/components/TextEditor/extensions/highlight.ts +0 -128
  120. package/src/components/TextEditor/extensions/listener.ts +0 -23
  121. package/src/components/TextEditor/extensions/tooltip.tsx +0 -61
  122. package/src/hooks/automerge/translation/index.ts +0 -6
  123. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  124. /package/dist/types/src/{components/TextEditor/extensions → extensions}/basic.d.ts +0 -0
  125. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  126. /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.d.ts +0 -0
  127. /package/dist/types/src/{components/TextEditor/extensions → extensions}/image.d.ts +0 -0
  128. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/bundle.d.ts +0 -0
  129. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/index.d.ts +0 -0
  130. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  131. /package/dist/types/src/{components/TextEditor/extensions → extensions}/table.d.ts +0 -0
  132. /package/dist/types/src/{components/TextEditor/extensions → extensions}/tasklist.d.ts +0 -0
  133. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  134. /package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +0 -0
  135. /package/src/{components/TextEditor/extensions → extensions}/basic.ts +0 -0
  136. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  137. /package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +0 -0
  138. /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
  139. /package/src/{components/TextEditor/extensions → extensions}/mention.ts +0 -0
  140. /package/src/{components/TextEditor/extensions → extensions}/tasklist.ts +0 -0
  141. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -5,7 +5,18 @@ import { Doc, YText, YXmlFragment } from "@dxos/text-model";
5
5
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
6
  import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
8
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
+ import { EditorState as EditorState2 } from "@codemirror/state";
10
+ import { EditorView as EditorView12 } from "@codemirror/view";
11
+ import { useFocusableGroup } from "@fluentui/react-tabster";
12
+ import { vim } from "@replit/codemirror-vim";
13
+ import defaultsDeep2 from "lodash.defaultsdeep";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
15
+ import { generateName } from "@dxos/display-name";
16
+ import { useThemeContext } from "@dxos/react-ui";
17
+ import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
+
19
+ // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
9
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
10
21
  import { keymap } from "@codemirror/view";
11
22
  var autocomplete = ({ onSearch }) => {
@@ -38,7 +49,7 @@ var autocomplete = ({ onSearch }) => {
38
49
  ];
39
50
  };
40
51
 
41
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic.ts
52
+ // packages/ui/react-ui-editor/src/extensions/basic.ts
42
53
  import { closeBrackets } from "@codemirror/autocomplete";
43
54
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
44
55
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
@@ -55,11 +66,11 @@ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
55
66
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
56
67
  ].filter(Boolean);
57
68
 
58
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts
69
+ // packages/ui/react-ui-editor/src/extensions/blast.ts
59
70
  import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
60
71
  import defaultsDeep from "lodash.defaultsdeep";
61
72
  import { invariant } from "@dxos/invariant";
62
- var __dxlog_file = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts";
73
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
63
74
  var defaultOptions = {
64
75
  effect: 2,
65
76
  maxParticles: 200,
@@ -365,7 +376,7 @@ var random = (min, max) => {
365
376
  return min + ~~(Math.random() * (max - min + 1));
366
377
  };
367
378
 
368
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
379
+ // packages/ui/react-ui-editor/src/extensions/code.ts
369
380
  import { ViewPlugin, EditorView as EditorView3, Decoration } from "@codemirror/view";
370
381
  import get from "lodash.get";
371
382
  import { mx as mx2 } from "@dxos/react-ui-theme";
@@ -411,31 +422,33 @@ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
411
422
  var codeMark = "font-mono text-primary-500";
412
423
  var inlineUrl = mx(code, "px-1");
413
424
  var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
414
- var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800";
415
425
 
416
426
  // packages/ui/react-ui-editor/src/styles/tokens.ts
417
427
  import { tailwindConfig } from "@dxos/react-ui-theme";
418
428
  var tokens = tailwindConfig({}).theme;
419
429
 
420
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
430
+ // packages/ui/react-ui-editor/src/extensions/code.ts
421
431
  var CODE_REGEX = /```[\s\S]*?```/gs;
422
432
  var styles = EditorView3.baseTheme({
423
- "& .cm-code-block": {
424
- display: "block",
425
- paddingInline: "4px !important"
433
+ "& .cm-codeblock": {
434
+ fontFamily: get(tokens, "fontFamily.mono", []).join(",")
426
435
  },
427
- "&light .cm-code-block": {
436
+ '&light [aria-readonly="true"] .cm-codeblock': {
428
437
  background: get(tokens, "extend.colors.neutral.50")
429
438
  },
430
- "&dark .cm-code-block": {
439
+ '&dark [aria-readonly="true"] .cm-codeblock': {
431
440
  background: get(tokens, "extend.colors.neutral.850")
432
441
  },
433
- "& .cm-code-block-first": {
442
+ '& [aria-readonly="true"] .cm-codeblock': {
443
+ display: "block",
444
+ paddingInline: "4px !important"
445
+ },
446
+ '& [aria-readonly="true"] .cm-codeblock-first': {
434
447
  paddingTop: "4px !important",
435
448
  borderTopLeftRadius: "4px",
436
449
  borderTopRightRadius: "4px"
437
450
  },
438
- "& .cm-code-block-last": {
451
+ '& [aria-readonly="true"] .cm-codeblock-last': {
439
452
  paddingBottom: "4px !important",
440
453
  borderBottomLeftRadius: "4px",
441
454
  borderBottomRightRadius: "4px"
@@ -461,7 +474,7 @@ var code2 = () => {
461
474
  for (let i = range[0]; i <= range[1]; i++) {
462
475
  const block = blocks[i];
463
476
  decorations.push(Decoration.line({
464
- class: mx2("cm-code-block", i === range[0] && "cm-code-block-first", i === range[1] && "cm-code-block-last")
477
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
465
478
  }).range(block.from));
466
479
  }
467
480
  }
@@ -485,1853 +498,2068 @@ var code2 = () => {
485
498
  ];
486
499
  };
487
500
 
488
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/demo.ts
489
- import { keymap as keymap3 } from "@codemirror/view";
490
- var defaultItems = [
491
- "hello world!",
492
- "this is a test.",
493
- "this is [DXOS](https://dxos.org)"
494
- ];
495
- var demo = ({ delay = 75, items = defaultItems } = {}) => {
496
- let t;
497
- let idx = 0;
498
- return [
499
- keymap3.of([
500
- {
501
- // Reset.
502
- key: "alt-meta-'",
503
- run: (view) => {
504
- clearTimeout(t);
505
- idx = 0;
506
- return true;
507
- }
508
- },
509
- {
510
- // Next prompt.
511
- // TODO(burdon): Press 1-9 to select prompt?
512
- key: "shift-meta-'",
513
- run: (view) => {
514
- clearTimeout(t);
515
- const text = items[idx++];
516
- if (idx === items?.length) {
517
- idx = 0;
518
- }
519
- let i = 0;
520
- const insert = (d = 0) => {
521
- t = setTimeout(() => {
522
- const pos = view.state.selection.main.head;
523
- view.dispatch({
524
- changes: {
525
- from: pos,
526
- insert: text[i++]
527
- },
528
- selection: {
529
- anchor: pos + 1
530
- }
531
- });
532
- if (i < text.length) {
533
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
534
- }
535
- }, d);
536
- };
537
- insert();
538
- return true;
539
- }
540
- }
541
- ])
542
- ];
543
- };
544
-
545
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
546
- import { keymap as keymap4, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
501
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
502
+ import { StateEffect as StateEffect2, StateField as StateField3 } from "@codemirror/state";
503
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin3, WidgetType } from "@codemirror/view";
504
+ import sortBy from "lodash.sortby";
547
505
  import { debounce } from "@dxos/async";
548
- import { invariant as invariant2 } from "@dxos/invariant";
549
- var __dxlog_file2 = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
550
- var styles2 = EditorView4.baseTheme({
551
- "& .cm-bookmark": {
552
- cursor: "pointer",
553
- margin: "4px",
554
- padding: "4px",
555
- backgroundColor: "yellow"
556
- },
557
- "& .cm-bookmark-selected": {
558
- backgroundColor: "orange"
559
- }
560
- });
561
- var BookmarkWidget = class extends WidgetType {
562
- constructor(_pos, _id, _handleClick) {
563
- super();
564
- this._pos = _pos;
565
- this._id = _id;
566
- this._handleClick = _handleClick;
567
- invariant2(this._id, void 0, {
506
+ import { invariant as invariant3 } from "@dxos/invariant";
507
+ import { log as log3 } from "@dxos/log";
508
+ import { nonNullable } from "@dxos/util";
509
+
510
+ // packages/ui/react-ui-editor/src/extensions/util.ts
511
+ import { log } from "@dxos/log";
512
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/util.ts";
513
+ var callbackWrapper = (fn) => (...args) => {
514
+ try {
515
+ return fn(...args);
516
+ } catch (error) {
517
+ log.catch(error, void 0, {
568
518
  F: __dxlog_file2,
569
- L: 37,
570
- S: this,
571
- A: [
572
- "this._id",
573
- ""
574
- ]
519
+ L: 16,
520
+ S: void 0,
521
+ C: (f, a) => f(...a)
575
522
  });
576
523
  }
577
- eq(other) {
578
- return this._id === other._id;
524
+ };
525
+
526
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
527
+ import { StateField as StateField2 } from "@codemirror/state";
528
+ import get2 from "lodash.get";
529
+ import { useEffect, useState } from "react";
530
+ import { yCollab } from "y-codemirror.next";
531
+ import * as Y from "yjs";
532
+ import { invariant as invariant2 } from "@dxos/invariant";
533
+ import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
534
+ import { arrayToString, stringToArray } from "@dxos/util";
535
+
536
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
537
+ import { Annotation, Facet, StateEffect, StateField } from "@codemirror/state";
538
+ import { ViewPlugin as ViewPlugin2 } from "@codemirror/view";
539
+ import * as automerge2 from "@dxos/automerge/automerge";
540
+
541
+ // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
542
+ import { next as automerge } from "@dxos/automerge/automerge";
543
+
544
+ // packages/ui/react-ui-editor/src/hooks/automerge/update-automerge.ts
545
+ import { next as am } from "@dxos/automerge/automerge";
546
+ var updateAutomerge = (field, handle, transactions, state) => {
547
+ const { lastHeads, path } = state.field(field);
548
+ let hasChanges = false;
549
+ for (const tr of transactions) {
550
+ tr.changes.iterChanges(() => {
551
+ hasChanges = true;
552
+ });
579
553
  }
580
- toDOM() {
581
- const span = document.createElement("span");
582
- span.className = "cm-bookmark";
583
- span.textContent = "\xA7";
584
- span.onclick = () => this._handleClick();
585
- return span;
554
+ if (!hasChanges) {
555
+ return void 0;
586
556
  }
587
- };
588
- var comments = (options = {}) => {
589
- let active;
590
- const bookmarkMatcher = new MatchDecorator({
591
- regexp: /\[\^(\w+)\]/g,
592
- decoration: (match, view, pos) => {
593
- const id = match[1];
594
- return Decoration2.replace({
595
- id,
596
- widget: new BookmarkWidget(pos, id, () => handleUpdate({
597
- active: id
598
- }))
557
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
558
+ for (const tr of transactions) {
559
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
560
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
599
561
  });
600
562
  }
601
563
  });
602
- const bookmarks = ViewPlugin2.fromClass(class {
603
- constructor(view) {
604
- this.bookmarks = bookmarkMatcher.createDeco(view);
605
- }
606
- update(update4) {
607
- this.bookmarks = bookmarkMatcher.updateDeco(update4, this.bookmarks);
608
- }
609
- }, {
610
- decorations: (instance) => instance.bookmarks,
611
- provide: (plugin) => EditorView4.atomicRanges.of((view) => {
612
- return view.plugin(plugin)?.bookmarks || Decoration2.none;
613
- })
614
- });
615
- const handleUpdate = debounce((data) => {
616
- options.onUpdate?.(data);
617
- }, 200);
618
- return [
619
- keymap4.of([
620
- {
621
- key: options?.key ?? "shift-meta-c",
622
- run: (view) => {
623
- const { from, to, head } = view.state.selection.main;
624
- const id = options.onCreate?.(from, to);
625
- if (id) {
626
- const tag = `[^${id}]`;
627
- view.dispatch({
628
- changes: {
629
- from: head,
630
- insert: tag
631
- },
632
- selection: {
633
- anchor: head + tag.length
634
- }
635
- });
636
- return true;
637
- }
638
- return false;
639
- }
640
- }
641
- ]),
642
- bookmarks,
643
- // Monitor cursor movement.
644
- EditorView4.updateListener.of((update4) => {
645
- active = void 0;
646
- if (!options.onUpdate) {
647
- return;
648
- }
649
- const { view, state } = update4;
650
- const pos = state.selection.main.head;
651
- const decorations = [];
652
- const rangeSet = view.plugin(bookmarks)?.bookmarks;
653
- let closest = Infinity;
654
- const { from, to } = (
655
- /* view.visibleRanges?.[0] ?? */
656
- {
657
- from: 0,
658
- to: state.doc.length
659
- }
660
- );
661
- rangeSet?.between(from, to, (from2, to2, value) => {
662
- decorations.push({
663
- from: from2,
664
- to: to2,
665
- value
666
- });
667
- const d = Math.min(Math.abs(pos - from2), Math.abs(pos - to2));
668
- if (d < closest) {
669
- active = value.spec.id;
670
- closest = d;
671
- }
672
- });
673
- if (decorations.length) {
674
- handleUpdate({
675
- active,
676
- items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
677
- id,
678
- pos: from2,
679
- location: view.coordsAtPos(from2)
680
- }))
681
- });
682
- }
683
- }),
684
- styles2
685
- ];
564
+ return newHeads ?? void 0;
686
565
  };
687
566
 
688
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/highlight.ts
689
- import { StateEffect, StateField } from "@codemirror/state";
690
- import { Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
691
- import sortBy from "lodash.sortby";
692
- import { useEffect } from "react";
693
- var styles3 = EditorView5.baseTheme({
694
- "& .cm-highlight": {
695
- backgroundColor: "yellow"
696
- },
697
- "& .cm-highlight::before": {
698
- content: "]"
699
- },
700
- "& .cm-highlight-active": {
701
- backgroundColor: "lime"
567
+ // packages/ui/react-ui-editor/src/hooks/automerge/update-codemirror.ts
568
+ import { ChangeSet } from "@codemirror/state";
569
+ var updateCodeMirror = (view, selection, target, patches) => {
570
+ for (const patch of patches) {
571
+ const changeSpec = handlePatch(patch, target, view.state);
572
+ if (changeSpec != null) {
573
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
574
+ selection = selection.map(changeSet, 1);
575
+ view.dispatch({
576
+ changes: changeSet,
577
+ annotations: reconcileAnnotationType.of({})
578
+ });
579
+ }
702
580
  }
703
- });
704
- var marks = {
705
- highlight: Decoration3.mark({
706
- class: "cm-highlight"
707
- }),
708
- HighlightActive: Decoration3.mark({
709
- class: "cm-highlight-active"
710
- })
581
+ view.dispatch({
582
+ selection,
583
+ annotations: reconcileAnnotationType.of({})
584
+ });
711
585
  };
712
- var setHighlights = StateEffect.define();
713
- var useHighlights = (view, ranges = []) => {
714
- useEffect(() => {
715
- view?.dispatch({
716
- effects: setHighlights.of(ranges)
717
- });
718
- }, [
719
- view,
720
- ranges
721
- ]);
586
+ var handlePatch = (patch, target, state) => {
587
+ if (patch.action === "insert") {
588
+ return handleInsert(target, patch);
589
+ } else if (patch.action === "splice") {
590
+ return handleSplice(target, patch);
591
+ } else if (patch.action === "del") {
592
+ return handleDel(target, patch);
593
+ } else if (patch.action === "put") {
594
+ return handlePut(target, patch, state);
595
+ } else {
596
+ return null;
597
+ }
722
598
  };
723
- var highlightStateField = StateField.define({
724
- create: () => [],
725
- update: (threads, tr) => {
726
- for (const effect of tr.effects) {
727
- if (effect.is(setHighlights)) {
728
- return effect.value;
729
- }
730
- }
731
- return threads;
599
+ var handleInsert = (target, patch) => {
600
+ const index = charPath(target, patch.path);
601
+ if (index == null) {
602
+ return [];
732
603
  }
733
- });
734
- var highlightDecorations = EditorView5.decorations.compute([
735
- highlightStateField
736
- ], (state) => {
737
- const selectionRanges = state.field(highlightStateField);
738
- const decorations = sortBy(selectionRanges ?? [], (selection) => selection.from)?.flatMap((selection) => {
739
- const range = {
740
- from: selection.from,
741
- to: selection.to + 1
742
- };
743
- if (selection.to > selection.from) {
744
- if (selection.active) {
745
- return marks.HighlightActive.range(range.from, range.to);
746
- } else {
747
- return marks.highlight.range(range.from, range.to);
748
- }
749
- } else {
750
- return [];
604
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
605
+ return [
606
+ {
607
+ from: index,
608
+ to: index,
609
+ insert: text
751
610
  }
752
- }) ?? [];
753
- return Decoration3.set(decorations);
754
- });
755
- var listener = (options) => {
756
- return EditorView5.updateListener.of((update4) => {
757
- const { view, state } = update4;
758
- const { head } = state.selection.main;
759
- const ranges = state.field(highlightStateField);
760
- let mod = false;
761
- const newRanges = ranges.map((range) => {
762
- const active = head >= range.from && head <= range.to;
763
- if (active !== range.active) {
764
- mod = true;
765
- return {
766
- ...range,
767
- active
768
- };
769
- } else {
770
- return range;
771
- }
772
- });
773
- if (mod) {
774
- view.dispatch({
775
- effects: setHighlights.of(newRanges)
776
- });
777
- options?.onChange?.(newRanges.find((range) => range.active)?.id);
611
+ ];
612
+ };
613
+ var handleSplice = (target, patch) => {
614
+ const index = charPath(target, patch.path);
615
+ if (index == null) {
616
+ return [];
617
+ }
618
+ return [
619
+ {
620
+ from: index,
621
+ insert: patch.value
778
622
  }
779
- });
623
+ ];
780
624
  };
781
- var highlight = (options = {}) => {
625
+ var handleDel = (target, patch) => {
626
+ const index = charPath(target, patch.path);
627
+ if (index == null) {
628
+ return [];
629
+ }
630
+ const length = patch.length || 1;
782
631
  return [
783
- highlightStateField,
784
- highlightDecorations,
785
- listener(options),
786
- styles3
632
+ {
633
+ from: index,
634
+ to: index + length
635
+ }
787
636
  ];
788
637
  };
789
-
790
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
791
- import { syntaxTree } from "@codemirror/language";
792
- import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
793
- import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType2 } from "@codemirror/view";
794
- var image = (options = {}) => {
795
- return StateField2.define({
796
- create: (state) => update(state, options),
797
- update: (_, tr) => update(tr.state, options),
798
- provide: (field) => EditorView6.decorations.from(field)
799
- });
800
- };
801
- var update = (state, options) => {
802
- const builder = new RangeSetBuilder();
803
- const cursor = state.selection.main.head;
804
- syntaxTree(state).iterate({
805
- enter: (node) => {
806
- if (node.name === "Image") {
807
- const urlNode = node.node.getChild("URL");
808
- if (urlNode) {
809
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
810
- const url = state.sliceDoc(urlNode.from, urlNode.to);
811
- builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
812
- widget: new ImageWidget(url)
813
- }));
814
- }
815
- }
816
- }
817
- });
818
- return builder.finish();
819
- };
820
- var ImageWidget = class extends WidgetType2 {
821
- constructor(_url) {
822
- super();
823
- this._url = _url;
824
- }
825
- eq(other) {
826
- return this._url === other?._url;
638
+ var handlePut = (target, patch, state) => {
639
+ const index = charPath(target, [
640
+ ...patch.path,
641
+ 0
642
+ ]);
643
+ if (index == null) {
644
+ return [];
827
645
  }
828
- toDOM(view) {
829
- const img = document.createElement("img");
830
- img.setAttribute("src", this._url);
831
- img.setAttribute("class", "cm-image");
832
- return img;
646
+ const length = state.doc.length;
647
+ if (typeof patch.value !== "string") {
648
+ return [];
833
649
  }
650
+ return [
651
+ {
652
+ from: 0,
653
+ to: length,
654
+ insert: patch.value
655
+ }
656
+ ];
834
657
  };
835
-
836
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
837
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
838
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
839
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
840
- var link = (options = {}) => {
841
- return StateField3.define({
842
- create: (state) => update2(state, options),
843
- update: (_, tr) => update2(tr.state, options),
844
- provide: (field) => EditorView7.decorations.from(field)
845
- });
846
- };
847
- var LinkButton = class extends WidgetType3 {
848
- constructor(_pos, _url, _onAttach) {
849
- super();
850
- this._pos = _pos;
851
- this._url = _url;
852
- this._onAttach = _onAttach;
658
+ var charPath = (textPath, candidatePath) => {
659
+ if (candidatePath.length !== textPath.length + 1) {
660
+ return null;
853
661
  }
854
- eq(other) {
855
- return this._pos === other._pos && this._url === other._url;
662
+ for (let i = 0; i < textPath.length; i++) {
663
+ if (textPath[i] !== candidatePath[i]) {
664
+ return null;
665
+ }
856
666
  }
857
- toDOM(view) {
858
- const el = document.createElement("span");
859
- this._onAttach(el, this._url);
860
- return el;
667
+ const index = candidatePath[candidatePath.length - 1];
668
+ if (typeof index === "number") {
669
+ return index;
861
670
  }
671
+ return null;
862
672
  };
863
- var LinkText = class extends WidgetType3 {
864
- constructor(_pos, _text, _url) {
865
- super();
866
- this._pos = _pos;
867
- this._text = _text;
868
- this._url = _url;
869
- }
870
- eq(other) {
871
- return this._pos === other._pos && this._url === other._url;
872
- }
873
- toDOM(view) {
874
- const link2 = document.createElement("a");
875
- link2.setAttribute("class", "cm-link");
876
- link2.textContent = this._text;
877
- if (this._url) {
878
- link2.setAttribute("rel", "noreferrer");
879
- link2.setAttribute("target", "_blank");
880
- link2.href = this._url;
881
- } else {
882
- link2.onclick = () => {
673
+
674
+ // packages/ui/react-ui-editor/src/hooks/automerge/semaphore.ts
675
+ var PatchSemaphore = class {
676
+ constructor(field) {
677
+ this._inReconcile = false;
678
+ this._queue = [];
679
+ this.reconcile = (handle, view) => {
680
+ if (this._inReconcile) {
681
+ return;
682
+ }
683
+ this._inReconcile = true;
684
+ const path = getPath(view.state, this._field);
685
+ const oldHeads = getLastHeads(view.state, this._field);
686
+ let selection = view.state.selection;
687
+ const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
688
+ const toInvert = transactions.slice().reverse();
689
+ for (const tx of toInvert) {
690
+ const inverted = tx.changes.invert(tx.startState.doc);
691
+ selection = selection.map(inverted);
883
692
  view.dispatch({
884
- selection: {
885
- anchor: this._pos
886
- }
693
+ changes: inverted,
694
+ annotations: reconcileAnnotationType.of(true)
887
695
  });
888
- };
696
+ }
697
+ let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
698
+ if (newHeads === null || newHeads === void 0) {
699
+ newHeads = automerge.getHeads(handle.docSync());
700
+ }
701
+ const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
702
+ updateCodeMirror(view, selection, path, diff);
703
+ view.dispatch({
704
+ effects: updateHeads(newHeads),
705
+ annotations: reconcileAnnotationType.of({})
706
+ });
707
+ this._inReconcile = false;
708
+ };
709
+ if (field !== void 0) {
710
+ this._field = field;
889
711
  }
890
- return link2;
891
712
  }
892
713
  };
893
- var update2 = (state, options) => {
894
- const builder = new RangeSetBuilder2();
895
- const cursor = state.selection.main.head;
896
- syntaxTree2(state).iterate({
897
- enter: (node) => {
898
- if (node.name === "Link") {
899
- const marks2 = node.node.getChildren("LinkMark");
900
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
901
- const urlNode = node.node.getChild("URL");
902
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
903
- if (!url) {
904
- return false;
905
- }
906
- if (state.readOnly || cursor < node.from || cursor > node.to) {
907
- builder.add(node.from, node.to, Decoration5.replace({
908
- widget: new LinkText(node.from, text, options.link ? url : void 0)
909
- }));
714
+
715
+ // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
716
+ var effectType = StateEffect.define({});
717
+ var updateHeads = (newHeads) => effectType.of({
718
+ newHeads
719
+ });
720
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
721
+ var getPath = (state, field) => state.field(field).path;
722
+ var semaphoreFacet = Facet.define({
723
+ combine: (values) => values.at(-1)
724
+ });
725
+ var automergePlugin = (handle, path) => {
726
+ const stateField = StateField.define({
727
+ create: () => ({
728
+ lastHeads: automerge2.getHeads(handle.docSync()),
729
+ unreconciledTransactions: [],
730
+ path: path.slice()
731
+ }),
732
+ update: (value, tr) => {
733
+ const result = {
734
+ lastHeads: value.lastHeads,
735
+ unreconciledTransactions: value.unreconciledTransactions.slice(),
736
+ path: path.slice()
737
+ };
738
+ let clearUnreconciled = false;
739
+ for (const effect of tr.effects) {
740
+ if (effect.is(effectType)) {
741
+ result.lastHeads = effect.value.newHeads;
742
+ clearUnreconciled = true;
910
743
  }
911
- if (options.onRender) {
912
- builder.add(node.to, node.to, Decoration5.replace({
913
- widget: new LinkButton(node.from, url, options.onRender)
914
- }));
744
+ }
745
+ if (clearUnreconciled) {
746
+ result.unreconciledTransactions = [];
747
+ } else {
748
+ if (!isReconcileTx(tr)) {
749
+ result.unreconciledTransactions.push(tr);
915
750
  }
916
- return false;
917
751
  }
752
+ return result;
918
753
  }
919
754
  });
920
- return builder.finish();
921
- };
922
-
923
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
924
- import { StateField as StateField4 } from "@codemirror/state";
925
- var listener2 = ({ onChange }) => {
926
- return StateField4.define({
927
- create: () => null,
928
- update: (_value, transaction) => {
929
- if (transaction.docChanged && onChange) {
930
- onChange(transaction.newDoc.toString());
755
+ const semaphore = new PatchSemaphore(stateField);
756
+ const viewPlugin = ViewPlugin2.fromClass(class AutomergeCodemirrorViewPlugin {
757
+ constructor(view) {
758
+ this._handleChange = () => {
759
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
760
+ };
761
+ this._view = view;
762
+ handle.addListener("change", this._handleChange);
763
+ }
764
+ update(update4) {
765
+ if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
766
+ queueMicrotask(() => {
767
+ this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
768
+ });
931
769
  }
932
- return null;
770
+ }
771
+ destroy() {
772
+ handle.addListener("change", this._handleChange);
933
773
  }
934
774
  });
775
+ return {
776
+ extension: [
777
+ stateField,
778
+ semaphoreFacet.of(semaphore),
779
+ viewPlugin
780
+ ]
781
+ };
935
782
  };
783
+ var reconcileAnnotationType = Annotation.define();
784
+ var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
936
785
 
937
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
938
- import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
939
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
940
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
941
- import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
942
- import { languages } from "@codemirror/language-data";
943
- import { searchKeymap } from "@codemirror/search";
944
- import { EditorState } from "@codemirror/state";
945
- import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
946
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView8, highlightActiveLine, keymap as keymap5, placeholder as placeholder2 } from "@codemirror/view";
947
-
948
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
949
- import { markdownLanguage } from "@codemirror/lang-markdown";
950
- import { HighlightStyle } from "@codemirror/language";
951
- import { tags, styleTags, Tag } from "@lezer/highlight";
952
- import { Table } from "@lezer/markdown";
953
- import get2 from "lodash.get";
954
- import { mx as mx3 } from "@dxos/react-ui-theme";
955
- var markdownTags = {
956
- Blockquote: Tag.define(),
957
- CodeMark: Tag.define(),
958
- CodeText: Tag.define(),
959
- EmphasisMark: Tag.define(),
960
- HeaderMark: Tag.define(),
961
- HorizontalRule: Tag.define(),
962
- InlineCode: Tag.define(),
963
- LinkLabel: Tag.define(),
964
- LinkReference: Tag.define(),
965
- ListMark: Tag.define(),
966
- QuoteMark: Tag.define(),
967
- URL: Tag.define(),
968
- // Custom.
969
- TableCell: Tag.define()
970
- };
971
- Table.defineNodes?.forEach((node) => {
972
- switch (node?.name) {
973
- case "TableCell": {
974
- node.style = markdownTags.TableCell;
975
- break;
976
- }
977
- }
978
- });
979
- var markdownTagsExtensions = [
980
- Table,
981
- {
982
- props: [
983
- styleTags(markdownTags)
984
- ]
985
- }
986
- ];
987
- var markdownHighlightStyle = (readonly) => {
988
- return HighlightStyle.define([
989
- {
990
- tag: [
991
- tags.keyword,
992
- tags.name,
993
- tags.deleted,
994
- tags.character,
995
- tags.propertyName,
996
- tags.macroName,
997
- tags.color,
998
- tags.constant(tags.name),
999
- tags.standard(tags.name),
1000
- tags.definition(tags.name),
1001
- tags.separator,
1002
- tags.typeName,
1003
- tags.className,
1004
- tags.number,
1005
- tags.changed,
1006
- tags.annotation,
1007
- tags.modifier,
1008
- tags.self,
1009
- tags.namespace,
1010
- tags.operator,
1011
- tags.operatorKeyword,
1012
- tags.escape,
1013
- tags.regexp,
1014
- tags.special(tags.string),
1015
- tags.meta,
1016
- tags.comment,
1017
- tags.atom,
1018
- tags.bool,
1019
- tags.special(tags.variableName),
1020
- tags.processingInstruction,
1021
- tags.string,
1022
- tags.inserted,
1023
- tags.invalid
1024
- ],
1025
- color: "inherit !important"
1026
- },
1027
- // Markdown marks.
1028
- {
1029
- tag: [
1030
- tags.meta,
1031
- tags.processingInstruction,
1032
- markdownTags.LinkLabel,
1033
- markdownTags.LinkReference,
1034
- markdownTags.ListMark
1035
- ],
1036
- class: mark
1037
- },
1038
- // Markdown marks.
1039
- {
1040
- tag: [
1041
- markdownTags.CodeMark,
1042
- markdownTags.HeaderMark,
1043
- markdownTags.QuoteMark,
1044
- markdownTags.EmphasisMark
1045
- ],
1046
- class: readonly ? "hidden" : mark
1047
- },
1048
- // E.g., code block language (after ```).
1049
- {
1050
- tag: [
1051
- tags.function(tags.variableName),
1052
- tags.labelName
1053
- ],
1054
- class: readonly ? "hidden" : codeMark
1055
- },
1056
- {
1057
- tag: [
1058
- tags.monospace
1059
- ],
1060
- class: "font-mono"
1061
- },
1062
- // Headings.
1063
- {
1064
- tag: tags.heading1,
1065
- class: heading(1, readonly)
1066
- },
1067
- {
1068
- tag: tags.heading2,
1069
- class: heading(2, readonly)
1070
- },
1071
- {
1072
- tag: tags.heading3,
1073
- class: heading(3, readonly)
1074
- },
1075
- {
1076
- tag: tags.heading4,
1077
- class: heading(4, readonly)
1078
- },
1079
- {
1080
- tag: tags.heading5,
1081
- class: heading(5, readonly)
1082
- },
1083
- {
1084
- tag: tags.heading6,
1085
- class: heading(6, readonly)
1086
- },
1087
- // Emphasis.
1088
- {
1089
- tag: tags.emphasis,
1090
- class: italic
1091
- },
1092
- {
1093
- tag: tags.strong,
1094
- class: bold
1095
- },
1096
- {
1097
- tag: tags.strikethrough,
1098
- class: strikethrough
1099
- },
1100
- // Naked URLs.
1101
- {
1102
- tag: [
1103
- markdownTags.URL
1104
- ],
1105
- class: inlineUrl
1106
- },
1107
- // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
1108
- // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1109
- // when a language is specified. In this case, the syntax highlighting extensions will colorize
1110
- // the code, but all other CSS properties will be inherited.
1111
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1112
- {
1113
- tag: [
1114
- markdownTags.CodeText,
1115
- markdownTags.InlineCode
1116
- ],
1117
- class: code
1118
- },
1119
- {
1120
- tag: [
1121
- markdownTags.QuoteMark
1122
- ],
1123
- class: blockquote
1124
- },
1125
- // TODO(burdon): Replace with extension.
1126
- {
1127
- tag: [
1128
- markdownTags.HorizontalRule
1129
- ],
1130
- class: mx3(horizontalRule, readonly && "-indent-[100rem]")
1131
- },
1132
- // TODO(burdon): Only if being edited.
1133
- {
1134
- tag: [
1135
- markdownTags.TableCell
1136
- ],
1137
- class: "font-mono"
1138
- }
1139
- ], {
1140
- scope: markdownLanguage,
1141
- all: {
1142
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1143
- }
1144
- });
1145
- };
1146
-
1147
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1148
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1149
- return [
1150
- EditorState.allowMultipleSelections.of(true),
1151
- EditorState.tabSize.of(2),
1152
- EditorView8.lineWrapping,
1153
- customKeymap(),
1154
- bracketMatching2(),
1155
- closeBrackets2(),
1156
- crosshairCursor(),
1157
- dropCursor(),
1158
- drawSelection2(),
1159
- highlightActiveLine(),
1160
- history(),
1161
- indentOnInput(),
1162
- _placeholder && placeholder2(_placeholder),
1163
- // Main extension.
1164
- // https://github.com/codemirror/lang-markdown
1165
- // https://codemirror.net/5/mode/markdown/index.html (demo).
1166
- markdown({
1167
- // GRM by default (vs strict CommonMark):
1168
- // Table, TaskList, Strikethrough, and Autolink.
1169
- // NOTE: This extends the parser; it doesn't affect rendering.
1170
- // https://github.github.com/gfm
1171
- // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1172
- base: markdownLanguage2,
1173
- // Languages for syntax highlighting fenced code blocks.
1174
- codeLanguages: languages,
1175
- // Parser extensions.
1176
- extensions: [
1177
- // GFM provided by default.
1178
- markdownTagsExtensions
1179
- ]
1180
- }),
1181
- // https://github.com/codemirror/theme-one-dark
1182
- themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1183
- // Custom styles.
1184
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1185
- ].filter(Boolean);
1186
- };
1187
- var customKeymap = () => keymap5.of([
1188
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1189
- indentWithTab,
1190
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1191
- ...standardKeymap,
1192
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1193
- ...historyKeymap,
1194
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1195
- ...closeBracketsKeymap,
1196
- // https://codemirror.net/docs/ref/#search.searchKeymap
1197
- ...searchKeymap
1198
- ]);
1199
-
1200
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1201
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1202
- var mention = ({ onSearch }) => {
1203
- return autocompletion2({
1204
- closeOnBlur: false,
1205
- override: [
1206
- (context) => {
1207
- const match = context.matchBefore(/@(\w+)?/);
1208
- if (!match || match.from === match.to && !context.explicit) {
1209
- return null;
1210
- }
1211
- console.log(match);
1212
- return {
1213
- from: match.from,
1214
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1215
- label: `@${value}`
1216
- }))
1217
- };
1218
- }
1219
- ]
1220
- });
1221
- };
1222
-
1223
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1224
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1225
- import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1226
- import { Decoration as Decoration6, EditorView as EditorView9, WidgetType as WidgetType4 } from "@codemirror/view";
1227
- var table = (options = {}) => {
1228
- return StateField5.define({
1229
- create: (state) => update3(state, options),
1230
- update: (_, tr) => update3(tr.state, options),
1231
- provide: (field) => EditorView9.decorations.from(field)
1232
- });
1233
- };
1234
- var update3 = (state, options) => {
1235
- const builder = new RangeSetBuilder3();
1236
- const cursor = state.selection.main.head;
1237
- const tables = [];
1238
- const getTable = () => tables[tables.length - 1];
1239
- const getRow = () => {
1240
- const table2 = getTable();
1241
- return table2.rows?.[table2.rows.length - 1];
1242
- };
1243
- syntaxTree3(state).iterate({
1244
- enter: (node) => {
1245
- switch (node.name) {
1246
- case "Table": {
1247
- tables.push({
1248
- from: node.from,
1249
- to: node.to
1250
- });
1251
- break;
1252
- }
1253
- case "TableHeader": {
1254
- getTable().header = [];
1255
- break;
1256
- }
1257
- case "TableRow": {
1258
- (getTable().rows ??= []).push([]);
1259
- break;
1260
- }
1261
- case "TableCell": {
1262
- const row = getRow();
1263
- if (row) {
1264
- row.push(state.sliceDoc(node.from, node.to));
1265
- } else {
1266
- getTable().header?.push(state.sliceDoc(node.from, node.to));
1267
- }
1268
- break;
1269
- }
1270
- }
1271
- }
1272
- });
1273
- tables.forEach((table2) => {
1274
- const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1275
- hide && builder.add(table2.from, table2.to, Decoration6.replace({
1276
- widget: new TableWidget(table2)
1277
- }));
1278
- builder.add(table2.from, table2.to, Decoration6.mark({
1279
- class: "cm-table"
1280
- }));
1281
- });
1282
- return builder.finish();
1283
- };
1284
- var TableWidget = class extends WidgetType4 {
1285
- constructor(_table) {
1286
- super();
1287
- this._table = _table;
1288
- }
1289
- eq(other) {
1290
- return this._table.from === other?._table?.from;
1291
- }
1292
- toDOM(view) {
1293
- const table2 = document.createElement("table");
1294
- {
1295
- const header = table2.appendChild(document.createElement("thead"));
1296
- const tr = header.appendChild(document.createElement("tr"));
1297
- this._table.header?.forEach((cell) => {
1298
- const th = document.createElement("th");
1299
- th.setAttribute("class", "cm-table-head");
1300
- tr.appendChild(th).textContent = cell;
1301
- });
1302
- }
1303
- {
1304
- const body = table2.appendChild(document.createElement("tbody"));
1305
- this._table.rows?.forEach((row) => {
1306
- const tr = body.appendChild(document.createElement("tr"));
1307
- row.forEach((cell) => {
1308
- const td = document.createElement("td");
1309
- td.setAttribute("class", "cm-table-cell");
1310
- tr.appendChild(td).textContent = cell;
1311
- });
1312
- });
1313
- }
1314
- return table2;
1315
- }
1316
- };
1317
-
1318
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
1319
- import { EditorView as EditorView10, Decoration as Decoration7, WidgetType as WidgetType5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin3 } from "@codemirror/view";
1320
- var styles4 = EditorView10.baseTheme({
1321
- "& .cm-task-item": {
1322
- paddingLeft: "4px",
1323
- paddingRight: "8px"
1324
- }
1325
- });
1326
- var CheckboxWidget = class extends WidgetType5 {
1327
- constructor(_pos, _checked, _indent, _onCheck) {
1328
- super();
1329
- this._pos = _pos;
1330
- this._checked = _checked;
1331
- this._indent = _indent;
1332
- this._onCheck = _onCheck;
1333
- }
1334
- eq(other) {
1335
- return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
1336
- }
1337
- toDOM(view) {
1338
- const wrap = document.createElement("span");
1339
- wrap.className = "cm-task-item";
1340
- wrap.setAttribute("aria-hidden", "true");
1341
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
1342
- const input = wrap.appendChild(document.createElement("input"));
1343
- input.type = "checkbox";
1344
- input.checked = this._checked;
1345
- if (!this._onCheck) {
1346
- input.setAttribute("disabled", "true");
1347
- }
1348
- if (this._onCheck) {
1349
- input.onchange = (event) => {
1350
- this._onCheck?.(event.target.checked);
1351
- return true;
1352
- };
1353
- }
1354
- return wrap;
1355
- }
1356
- ignoreEvent() {
1357
- return false;
1358
- }
1359
- };
1360
- var tasklist = (options = {}) => {
1361
- const taskMatcher = new MatchDecorator2({
1362
- regexp: /^(\s*)- \[([ xX])\]\s/g,
1363
- decoration: (match, view, pos) => {
1364
- const indent = Math.floor(match[1].length / 2);
1365
- const checked = match[2] === "x" || match[2] === "X";
1366
- return Decoration7.replace({
1367
- widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
1368
- const idx = pos + match[0].indexOf("[") + 1;
1369
- view.dispatch({
1370
- changes: {
1371
- from: idx,
1372
- to: idx + 1,
1373
- insert: checked2 ? "x" : " "
1374
- },
1375
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
1376
- selection: {
1377
- anchor: idx + 3
1378
- }
1379
- });
1380
- })
1381
- });
1382
- }
1383
- });
1384
- const tasks = ViewPlugin3.fromClass(class {
1385
- constructor(view) {
1386
- this.tasks = taskMatcher.createDeco(view);
1387
- }
1388
- update(update4) {
1389
- this.tasks = taskMatcher.updateDeco(update4, this.tasks);
1390
- }
1391
- }, {
1392
- decorations: (value) => value.tasks,
1393
- provide: (plugin) => EditorView10.atomicRanges.of((view) => {
1394
- return view.plugin(plugin)?.tasks || Decoration7.none;
1395
- })
1396
- });
1397
- return [
1398
- tasks,
1399
- styles4
1400
- ];
1401
- };
1402
-
1403
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
1404
- import { hoverTooltip } from "@codemirror/view";
1405
- import { tooltipContent } from "@dxos/react-ui-theme";
1406
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1407
- var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
1408
- const { from, text } = view.state.doc.lineAt(pos);
1409
- const p = pos - from;
1410
- let idx = 0;
1411
- let match;
1412
- do {
1413
- match = text.substring(idx).match(regexp);
1414
- if (!match) {
1415
- match = void 0;
1416
- break;
1417
- }
1418
- idx = text.indexOf(match[0]);
1419
- if (p >= idx && p < idx + match[0].length) {
1420
- break;
1421
- }
1422
- idx += match[0].length;
1423
- } while (true);
1424
- if (!match) {
1425
- return null;
1426
- }
1427
- const [, , url] = match ?? [];
1428
- return {
1429
- pos: idx + from,
1430
- end: idx + from + match[0].length,
1431
- above: true,
1432
- create: () => {
1433
- const el = document.createElement("div");
1434
- el.className = tooltipContent({}, "pli-2 plb-1");
1435
- onHover(el, url);
1436
- return {
1437
- dom: el,
1438
- offset: {
1439
- x: 0,
1440
- y: 12
1441
- }
1442
- };
1443
- }
1444
- };
1445
- });
1446
-
1447
- // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
1448
- import get3 from "lodash.get";
1449
- var defaultTheme = {
1450
- //
1451
- // Main layout:
1452
- // <div class=".cm-editor .cm-focused">
1453
- // <div class=".cm-scroller">
1454
- // <div class=".cm-content" role="textbox" contenteditable="true">
1455
- // <div class=".cm-line" />
1456
- // </div>
1457
- // </div>
1458
- // </div>
1459
- //
1460
- "&": {},
1461
- "&.cm-focused": {
1462
- outline: "none"
1463
- },
1464
- "& .cm-scroller": {
1465
- overflow: "visible",
1466
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1467
- },
1468
- "& .cm-content": {
1469
- padding: 0,
1470
- // Base font size (otherwise defined by HTML tag, which might be different for storybook).
1471
- fontSize: "16px"
1472
- },
1473
- "&light .cm-content": {
1474
- color: get3(tokens, "extend.colors.neutral.900", "black"),
1475
- caretColor: "black"
1476
- },
1477
- "&dark .cm-content": {
1478
- color: get3(tokens, "extend.colors.neutral.100", "white"),
1479
- caretColor: "white"
1480
- },
1481
- //
1482
- // Cursor
1483
- //
1484
- "&light .cm-cursor, &light .cm-dropCursor": {
1485
- borderLeft: "2px solid black"
1486
- },
1487
- "&dark .cm-cursor, &dark .cm-dropCursor": {
1488
- borderLeft: "2px solid white"
1489
- },
1490
- "& .cm-placeholder": {
1491
- fontWeight: 100
1492
- },
1493
- //
1494
- // line
1495
- //
1496
- "& .cm-line": {
1497
- paddingInline: 0
1498
- },
1499
- "& .cm-line *": {},
1500
- "& .cm-activeLine": {
1501
- backgroundColor: "transparent"
1502
- },
1503
- //
1504
- // selection
1505
- //
1506
- "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
1507
- background: get3(tokens, "extend.colors.primary.150", "#00ffff")
1508
- },
1509
- "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
1510
- background: get3(tokens, "extend.colors.primary.850", "#00ffff")
1511
- },
1512
- "&light .cm-selectionMatch": {
1513
- background: get3(tokens, "extend.colors.primary.250", "#00ffff") + "44"
1514
- },
1515
- "&dark .cm-selectionMatch": {
1516
- background: get3(tokens, "extend.colors.primary.600", "#00ffff") + "44"
1517
- },
1518
- //
1519
- // collaboration
1520
- // TODO(burdon): Review classnames (YJS dependent?)
1521
- //
1522
- "&light .cm-ySelection, &light .cm-yLineSelection": {
1523
- mixBlendMode: "multiply"
1524
- },
1525
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
1526
- mixBlendMode: "screen"
1527
- },
1528
- "& .cm-ySelectionInfo": {
1529
- padding: "2px 4px",
1530
- marginBlockStart: "-4px"
1531
- },
1532
- "& .cm-ySelection, & .cm-selectionMatch": {
1533
- paddingBlockStart: ".15em",
1534
- paddingBlockEnd: ".15em"
1535
- },
1536
- "& .cm-ySelectionCaret": {
1537
- display: "inline-block",
1538
- insetBlockStart: ".1em",
1539
- blockSize: "1.4em",
1540
- verticalAlign: "top"
1541
- },
1542
- "& .cm-yLineSelection": {
1543
- margin: 0
1544
- },
1545
- //
1546
- // link
1547
- //
1548
- "& .cm-link": {
1549
- color: get3(tokens, "extend.colors.primary.500"),
1550
- textDecorationLine: "underline",
1551
- textDecorationThickness: "1px",
1552
- textUnderlineOffset: "2px",
1553
- borderRadius: ".125rem",
1554
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1555
- },
1556
- //
1557
- // tooltip
1558
- //
1559
- "& .cm-tooltip": {
1560
- border: "none"
1561
- },
1562
- "& .cm-tooltip-below": {},
1563
- //
1564
- // autocomplete
1565
- //
1566
- "& .cm-tooltip-autocomplete": {
1567
- marginTop: "4px",
1568
- marginLeft: "-3px"
1569
- },
1570
- "& .cm-tooltip-autocomplete ul li": {},
1571
- "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
1572
- "& .cm-completionIcon": {
1573
- display: "none"
1574
- },
1575
- "& .cm-completionLabel": {
1576
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
1577
- },
1578
- "& .cm-completionMatchedText": {
1579
- textDecoration: "none"
1580
- },
1581
- //
1582
- // widgets
1583
- //
1584
- "& .cm-widgetBuffer": {
1585
- display: "none",
1586
- height: 0
1587
- },
1588
- //
1589
- // table
1590
- //
1591
- "& .cm-table *": {
1592
- fontFamily: `${get3(tokens, "fontFamily.mono", []).join(",")} !important`,
1593
- textDecoration: "none !important"
1594
- },
1595
- "& .cm-table-head": {
1596
- padding: "2px 16px 2px 0px",
1597
- borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.500")}`,
1598
- fontWeight: 100,
1599
- textAlign: "left",
1600
- color: get3(tokens, "extend.colors.neutral.500")
1601
- },
1602
- "& .cm-table-cell": {
1603
- padding: "2px 16px 2px 0px"
1604
- },
1605
- //
1606
- // image
1607
- //
1608
- "& .cm-image": {
1609
- margin: "0.5rem 0"
1610
- },
1611
- //
1612
- // font size
1613
- // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
1614
- //
1615
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
1616
- const height = get3(tokens, [
1617
- "extend",
1618
- "fontSize",
1619
- fontSize,
1620
- 1,
1621
- "lineHeight"
1622
- ]);
1623
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
1624
- height
1625
- };
1626
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
1627
- height
1628
- };
1629
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
1630
- height
1631
- };
1632
- return acc;
1633
- }, {}),
1634
- /**
1635
- * Panels
1636
- * https://github.com/codemirror/search/blob/main/src/search.ts#L745
1637
- *
1638
- * Find/replace panel.
1639
- * <div class="cm-announced">...</div>
1640
- * <div class="cm-scroller">...</div>
1641
- * <div class="cm-panels cm-panels-bottom">
1642
- * <div class="cm-search cm-panel">
1643
- * <input class="cm-textfield" />
1644
- * <button class="cm-button">...</button>
1645
- * <label><input type="checkbox" />...</label>
1646
- * </div>
1647
- * </div
1648
- */
1649
- "& .cm-panels": {
1650
- border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`,
1651
- borderBottom: "none"
786
+ // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
787
+ import * as random2 from "lib0/random";
788
+ var cursorColors = [
789
+ {
790
+ color: "#30bced",
791
+ light: "#30bced33"
1652
792
  },
1653
- "& .cm-panel": {
1654
- background: get3(tokens, "extend.colors.neutral.50"),
1655
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
793
+ {
794
+ color: "#6eeb83",
795
+ light: "#6eeb8333"
1656
796
  },
1657
- "& .cm-button": {
1658
- margin: "4px",
1659
- fontFamily: get3(tokens, "fontFamily.body", []).join(","),
1660
- background: get3(tokens, "extend.colors.neutral.100"),
1661
- border: "none"
797
+ {
798
+ color: "#ffbc42",
799
+ light: "#ffbc4233"
1662
800
  },
1663
- "& .cm-searchMatch": {
1664
- backgroundColor: get3(tokens, "extend.colors.yellow.100")
801
+ {
802
+ color: "#ecd444",
803
+ light: "#ecd44433"
1665
804
  },
1666
- "& .cm-searchMatch.cm-searchMatch-selected": {
1667
- backgroundColor: get3(tokens, "extend.colors.primary.100")
1668
- }
1669
- };
1670
- var textTheme = {
1671
- "& .cm-scroller": {
1672
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
805
+ {
806
+ color: "#ee6352",
807
+ light: "#ee635233"
1673
808
  },
1674
- "& .cm-placeholder": {
1675
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
809
+ {
810
+ color: "#9ac2c9",
811
+ light: "#9ac2c933"
812
+ },
813
+ {
814
+ color: "#8acb88",
815
+ light: "#8acb8833"
816
+ },
817
+ {
818
+ color: "#1be7ff",
819
+ light: "#1be7ff33"
820
+ }
821
+ ];
822
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
823
+
824
+ // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
825
+ import * as decoding from "lib0/decoding";
826
+ import * as encoding from "lib0/encoding";
827
+ import { Observable } from "lib0/observable";
828
+ import * as awarenessProtocol from "y-protocols/awareness";
829
+ import { log as log2 } from "@dxos/log";
830
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
831
+ var messageAwareness = 1;
832
+ var messageQueryAwareness = 3;
833
+ var SpaceAwarenessProvider = class extends Observable {
834
+ constructor({ space, doc, channel, awareness }) {
835
+ super();
836
+ this._space = space;
837
+ this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
838
+ this._channel = channel;
839
+ this._clientId = doc.clientID;
840
+ this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
841
+ this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
842
+ if (typeof window !== "undefined") {
843
+ window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
844
+ } else if (typeof process !== "undefined") {
845
+ process.on("exit", this._handleBeforeUnload.bind(this));
846
+ }
847
+ const encoderAwarenessQuery = encoding.createEncoder();
848
+ encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
849
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
850
+ const encoderAwarenessState = encoding.createEncoder();
851
+ encoding.writeVarUint(encoderAwarenessState, messageAwareness);
852
+ encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
853
+ this._clientId
854
+ ]));
855
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
856
+ }
857
+ get awareness() {
858
+ return this._awareness;
859
+ }
860
+ _handleAwarenessUpdate({ added, updated, removed }, origin) {
861
+ log2("awareness update", {
862
+ added,
863
+ updated,
864
+ removed,
865
+ origin
866
+ }, {
867
+ F: __dxlog_file3,
868
+ L: 67,
869
+ S: this,
870
+ C: (f, a) => f(...a)
871
+ });
872
+ const changedClients = added.concat(updated).concat(removed);
873
+ const encoderAwareness = encoding.createEncoder();
874
+ encoding.writeVarUint(encoderAwareness, messageAwareness);
875
+ encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
876
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
877
+ }
878
+ _handleSpaceMessage({ payload }) {
879
+ log2("space message", payload, {
880
+ F: __dxlog_file3,
881
+ L: 79,
882
+ S: this,
883
+ C: (f, a) => f(...a)
884
+ });
885
+ const data = new Uint8Array(Array.from(Object.values(payload)));
886
+ const encoder = this._readMessage(data);
887
+ if (encoder) {
888
+ void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
889
+ }
890
+ }
891
+ _readMessage(message) {
892
+ const decoder = decoding.createDecoder(message);
893
+ const encoder = encoding.createEncoder();
894
+ const messageType = decoding.readVarUint(decoder);
895
+ let sendReply = false;
896
+ switch (messageType) {
897
+ case messageAwareness: {
898
+ awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
899
+ break;
900
+ }
901
+ case messageQueryAwareness: {
902
+ encoding.writeVarUint(encoder, messageAwareness);
903
+ encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
904
+ sendReply = true;
905
+ break;
906
+ }
907
+ default: {
908
+ console.error("Invalid message:", messageType);
909
+ return encoder;
910
+ }
911
+ }
912
+ if (!sendReply) {
913
+ return null;
914
+ }
915
+ return encoder;
916
+ }
917
+ _handleBeforeUnload() {
918
+ awarenessProtocol.removeAwarenessStates(this._awareness, [
919
+ this._clientId
920
+ ], "window unload");
1676
921
  }
1677
922
  };
1678
- var markdownTheme = {
1679
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
1680
- "& .cm-placeholder": {
1681
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
923
+
924
+ // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
925
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
926
+ var modelState = StateField2.define({
927
+ create: () => void 0,
928
+ update: (model) => model
929
+ });
930
+ var useTextModel = (props) => {
931
+ const { identity, space, text } = props;
932
+ const [model, setModel] = useState(() => createModel(props));
933
+ useEffect(() => setModel(createModel(props)), [
934
+ identity,
935
+ space,
936
+ text
937
+ ]);
938
+ return model;
939
+ };
940
+ var createModel = (props) => {
941
+ const { text } = props;
942
+ if (isActualAutomergeObject(text)) {
943
+ return createAutomergeModel(props);
944
+ } else {
945
+ if (!text?.doc) {
946
+ return void 0;
947
+ }
948
+ return createYjsModel(props);
1682
949
  }
1683
950
  };
1684
- var codeTheme = {
1685
- "& .cm-scroller": {
1686
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
951
+ var createYjsModel = ({ identity, space, text }) => {
952
+ invariant2(text?.doc && text?.content, void 0, {
953
+ F: __dxlog_file4,
954
+ L: 97,
955
+ S: void 0,
956
+ A: [
957
+ "text?.doc && text?.content",
958
+ ""
959
+ ]
960
+ });
961
+ const provider = space ? new SpaceAwarenessProvider({
962
+ space,
963
+ doc: text.doc,
964
+ channel: `yjs.awareness.${text.id}`
965
+ }) : void 0;
966
+ const model = {
967
+ id: text.doc.guid,
968
+ content: text.content,
969
+ text: () => text.content.toString(),
970
+ // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
971
+ getCursorFromRange: (range) => {
972
+ const from = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.from));
973
+ const to = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text.content, range.to, -1));
974
+ return [
975
+ arrayToString(from),
976
+ arrayToString(to)
977
+ ].join(":");
978
+ },
979
+ getRangeFromCursor: (cursor) => {
980
+ invariant2(text.doc, void 0, {
981
+ F: __dxlog_file4,
982
+ L: 113,
983
+ S: void 0,
984
+ A: [
985
+ "text.doc",
986
+ ""
987
+ ]
988
+ });
989
+ const parts = cursor.split(":");
990
+ const from = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[0])), text.doc);
991
+ const to = Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(parts[1])), text.doc);
992
+ return from && to ? {
993
+ from: from.index,
994
+ to: to.index
995
+ } : void 0;
996
+ },
997
+ extension: [
998
+ yCollab(text.content, provider?.awareness),
999
+ modelState.init(() => model)
1000
+ ],
1001
+ awareness: provider?.awareness,
1002
+ peer: identity ? {
1003
+ id: identity.identityKey.toHex(),
1004
+ name: identity.profile?.displayName
1005
+ } : void 0
1006
+ };
1007
+ return model;
1008
+ };
1009
+ var createAutomergeModel = ({ identity, text }) => {
1010
+ const obj = text;
1011
+ const doc = getRawDoc(obj, [
1012
+ obj.field
1013
+ ]);
1014
+ const model = {
1015
+ id: obj.id,
1016
+ content: doc,
1017
+ text: () => get2(doc.handle.docSync(), doc.path),
1018
+ // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
1019
+ extension: [
1020
+ automergePlugin(doc.handle, doc.path),
1021
+ modelState.init(() => model)
1022
+ ],
1023
+ peer: identity ? {
1024
+ id: identity.identityKey.toHex(),
1025
+ name: identity.profile?.displayName
1026
+ } : void 0
1027
+ };
1028
+ return model;
1029
+ };
1030
+
1031
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1032
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1033
+ var styles2 = EditorView4.baseTheme({
1034
+ "& .cm-bookmark": {
1035
+ cursor: "pointer",
1036
+ margin: "4px",
1037
+ padding: "4px",
1038
+ backgroundColor: "yellow"
1687
1039
  },
1688
- "& .cm-placeholder": {
1689
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
1040
+ "& .cm-bookmark-selected": {
1041
+ backgroundColor: "orange"
1042
+ },
1043
+ "& .cm-comment": {
1044
+ backgroundColor: "yellow"
1045
+ },
1046
+ "& .cm-comment-active": {
1047
+ backgroundColor: "orange"
1048
+ }
1049
+ });
1050
+ var marks = {
1051
+ highlight: Decoration2.mark({
1052
+ class: "cm-comment"
1053
+ }),
1054
+ highlightActive: Decoration2.mark({
1055
+ class: "cm-comment-active"
1056
+ })
1057
+ };
1058
+ var setCommentRange = StateEffect2.define();
1059
+ var setSelection = StateEffect2.define();
1060
+ var setCommentState = StateEffect2.define();
1061
+ var commentsStateField = StateField3.define({
1062
+ create: () => ({
1063
+ ranges: []
1064
+ }),
1065
+ update: (value, tr) => {
1066
+ for (const effect of tr.effects) {
1067
+ if (effect.is(setSelection)) {
1068
+ return {
1069
+ ...value,
1070
+ ...effect.value
1071
+ };
1072
+ }
1073
+ if (effect.is(setCommentRange)) {
1074
+ const { model, comments: comments2 } = effect.value;
1075
+ const ranges = comments2.map((comment) => {
1076
+ const range = model.getRangeFromCursor(comment.cursor);
1077
+ return range && {
1078
+ ...comment,
1079
+ ...range
1080
+ };
1081
+ }).filter(nonNullable);
1082
+ return {
1083
+ ...value,
1084
+ ranges
1085
+ };
1086
+ }
1087
+ if (effect.is(setCommentState)) {
1088
+ return effect.value;
1089
+ }
1090
+ }
1091
+ return value;
1092
+ }
1093
+ });
1094
+ var highlightDecorations = EditorView4.decorations.compute([
1095
+ commentsStateField
1096
+ ], (state) => {
1097
+ const { active, ranges } = state.field(commentsStateField);
1098
+ const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1099
+ if (selection.from === selection.to) {
1100
+ return void 0;
1101
+ }
1102
+ const range = {
1103
+ from: selection.from,
1104
+ to: selection.to
1105
+ };
1106
+ if (selection.id === active) {
1107
+ return marks.highlightActive.range(range.from, range.to);
1108
+ } else {
1109
+ return marks.highlight.range(range.from, range.to);
1110
+ }
1111
+ }).filter(nonNullable) ?? [];
1112
+ return Decoration2.set(decorations);
1113
+ });
1114
+ var BookmarkWidget = class extends WidgetType {
1115
+ constructor(_anchor, _id, _handleClick) {
1116
+ super();
1117
+ this._anchor = _anchor;
1118
+ this._id = _id;
1119
+ this._handleClick = _handleClick;
1120
+ invariant3(this._id, void 0, {
1121
+ F: __dxlog_file5,
1122
+ L: 153,
1123
+ S: this,
1124
+ A: [
1125
+ "this._id",
1126
+ ""
1127
+ ]
1128
+ });
1690
1129
  }
1691
- };
1692
-
1693
- // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
1694
- import { EditorState as EditorState2 } from "@codemirror/state";
1695
- import { EditorView as EditorView11 } from "@codemirror/view";
1696
- import { useFocusableGroup } from "@fluentui/react-tabster";
1697
- import { vim } from "@replit/codemirror-vim";
1698
- import defaultsDeep2 from "lodash.defaultsdeep";
1699
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
1700
- import { generateName } from "@dxos/display-name";
1701
- import { useThemeContext } from "@dxos/react-ui";
1702
- import { getColorForValue, inputSurface, mx as mx4 } from "@dxos/react-ui-theme";
1703
- var EditorModes = [
1704
- "default",
1705
- "vim"
1706
- ];
1707
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
1708
- const { themeMode } = useThemeContext();
1709
- const tabsterDOMAttribute = useFocusableGroup({
1710
- tabBehavior: "limited"
1711
- });
1712
- const [root, setRoot] = useState(null);
1713
- const [state, setState] = useState();
1714
- const [view, setView] = useState();
1715
- useImperativeHandle(forwardedRef, () => ({
1716
- root,
1717
- state,
1718
- view
1719
- }), [
1720
- view,
1721
- state,
1722
- root
1723
- ]);
1724
- const { awareness, peer } = model;
1725
- useEffect2(() => {
1726
- if (awareness && peer) {
1727
- awareness.setLocalStateField("user", {
1728
- name: peer.name ?? generateName(peer.id),
1729
- color: getColorForValue({
1730
- value: peer.id,
1731
- type: "color"
1732
- }),
1733
- colorLight: getColorForValue({
1734
- value: peer.id,
1735
- themeMode,
1736
- type: "highlight"
1737
- })
1738
- });
1130
+ eq(other) {
1131
+ return this._anchor === other._anchor && this._id === other._id;
1132
+ }
1133
+ toDOM() {
1134
+ const span = document.createElement("span");
1135
+ span.className = "cm-bookmark";
1136
+ span.textContent = "\u203B";
1137
+ if (this._handleClick) {
1138
+ span.onclick = () => this._handleClick();
1739
1139
  }
1740
- }, [
1741
- awareness,
1742
- peer,
1743
- themeMode
1744
- ]);
1745
- useEffect2(() => {
1746
- if (!root) {
1747
- return;
1140
+ return span;
1141
+ }
1142
+ };
1143
+ var comments = (options = {}) => {
1144
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1145
+ const createCommentThread = (view) => {
1146
+ invariant3(options.onCreate, void 0, {
1147
+ F: __dxlog_file5,
1148
+ L: 207,
1149
+ S: void 0,
1150
+ A: [
1151
+ "options.onCreate",
1152
+ ""
1153
+ ]
1154
+ });
1155
+ const { head, from, to } = view.state.selection.main;
1156
+ if (from === to) {
1157
+ return false;
1748
1158
  }
1749
- const state2 = EditorState2.create({
1750
- doc: model.text(),
1751
- extensions: [
1752
- readonly && EditorState2.readOnly.of(readonly),
1753
- // TODO(burdon): Factor out VIM mode?
1754
- editorMode === "vim" && vim(),
1755
- // Theme.
1756
- // TODO(burdon): Make optional.
1757
- EditorView11.baseTheme(defaultTheme),
1758
- EditorView11.theme(slots?.editor?.theme ?? {}),
1759
- // TODO(burdon): themeMode doesn't change in storybooks.
1760
- EditorView11.darkTheme.of(themeMode === "dark"),
1761
- // Storage and replication.
1762
- model.extension,
1763
- // Custom.
1764
- ...extensions
1765
- ].filter(Boolean)
1159
+ const model = view.state.field(modelState);
1160
+ const relPos = model?.getCursorFromRange?.({
1161
+ from,
1162
+ to
1766
1163
  });
1767
- view?.destroy();
1768
- setView(new EditorView11({
1769
- state: state2,
1770
- parent: root
1771
- }));
1772
- setState(state2);
1773
- return () => {
1774
- view?.destroy();
1775
- setView(void 0);
1776
- setState(void 0);
1777
- };
1778
- }, [
1779
- root,
1780
- model,
1781
- themeMode,
1782
- readonly,
1783
- editorMode
1784
- ]);
1785
- const handleKeyUp = useCallback((event) => {
1786
- const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
1787
- switch (key) {
1788
- case "Enter": {
1789
- view?.contentDOM.focus();
1790
- break;
1791
- }
1792
- case "Escape": {
1793
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
1794
- break;
1164
+ if (relPos) {
1165
+ const id = callbackWrapper(options.onCreate)(relPos);
1166
+ if (id) {
1167
+ view.dispatch({
1168
+ effects: setSelection.of({
1169
+ active: id
1170
+ }),
1171
+ selection: {
1172
+ anchor: from
1173
+ }
1174
+ });
1175
+ if (options.footnote) {
1176
+ const tag = `[^${id}]`;
1177
+ view.dispatch({
1178
+ changes: {
1179
+ from: head,
1180
+ insert: tag
1181
+ },
1182
+ selection: {
1183
+ anchor: head + tag.length
1184
+ }
1185
+ });
1186
+ }
1187
+ return true;
1795
1188
  }
1796
1189
  }
1797
- }, [
1798
- view,
1799
- editorMode
1800
- ]);
1801
- return /* @__PURE__ */ React.createElement("div", {
1802
- key: model.id,
1803
- ref: setRoot,
1804
- tabIndex: 0,
1805
- ...slots?.root,
1806
- ...editorMode !== "vim" && tabsterDOMAttribute,
1807
- onKeyUp: handleKeyUp
1190
+ return false;
1191
+ };
1192
+ const bookmarksViewPlugin = ViewPlugin3.fromClass(class BookmarkViewPlugin {
1193
+ static {
1194
+ // Match markdown footnotes (option).
1195
+ // https://www.markdownguide.org/extended-syntax/#footnotes
1196
+ this.bookmarkMatcher = new MatchDecorator({
1197
+ regexp: /\[\^(\w+)\]/g,
1198
+ decoration: (match, view, pos) => {
1199
+ const id = match[1];
1200
+ return Decoration2.replace({
1201
+ id,
1202
+ widget: new BookmarkWidget(pos, id)
1203
+ });
1204
+ }
1205
+ });
1206
+ }
1207
+ constructor(view) {
1208
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1209
+ }
1210
+ update(update4) {
1211
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
1212
+ }
1213
+ }, {
1214
+ decorations: (instance) => instance.bookmarks,
1215
+ provide: (plugin) => EditorView4.atomicRanges.of((view) => {
1216
+ return view.plugin(plugin)?.bookmarks || Decoration2.none;
1217
+ })
1808
1218
  });
1219
+ return [
1220
+ bookmarksViewPlugin,
1221
+ commentsStateField,
1222
+ highlightDecorations,
1223
+ styles2,
1224
+ //
1225
+ // Keymap.
1226
+ //
1227
+ options.onCreate ? keymap3.of([
1228
+ {
1229
+ key: options?.key ?? "meta-'",
1230
+ run: callbackWrapper(createCommentThread)
1231
+ }
1232
+ ]) : [],
1233
+ //
1234
+ // Hover tooltip (for key shortcut hints, etc.)
1235
+ // TODO(burdon): Factor out to generic hints extension for current selection/line.
1236
+ //
1237
+ options.onHover ? hoverTooltip((view, pos) => {
1238
+ const selection = view.state.selection.main;
1239
+ if (selection && pos >= selection.from && pos <= selection.to) {
1240
+ return {
1241
+ pos: selection.from,
1242
+ end: selection.to,
1243
+ above: true,
1244
+ create: () => {
1245
+ const el = document.createElement("div");
1246
+ options.onHover?.(el);
1247
+ return {
1248
+ dom: el,
1249
+ offset: {
1250
+ x: 0,
1251
+ y: 8
1252
+ }
1253
+ };
1254
+ }
1255
+ };
1256
+ }
1257
+ return null;
1258
+ }, {
1259
+ hideOnChange: true,
1260
+ hoverTime: 1e3
1261
+ }) : [],
1262
+ //
1263
+ // Monitor cursor movement and text updates.
1264
+ // TODO(burdon): Is there a better (finer grained) way to do this?
1265
+ //
1266
+ EditorView4.updateListener.of(({ view, state, startState, changes }) => {
1267
+ {
1268
+ let mod = false;
1269
+ const model = state.field(modelState);
1270
+ const { active, ranges } = state.field(commentsStateField);
1271
+ changes.iterChanges((from, to, from2, to2) => {
1272
+ invariant3(model, void 0, {
1273
+ F: __dxlog_file5,
1274
+ L: 336,
1275
+ S: void 0,
1276
+ A: [
1277
+ "model",
1278
+ ""
1279
+ ]
1280
+ });
1281
+ ranges.forEach((range) => {
1282
+ if (from2 === to2) {
1283
+ const newRange = model.getRangeFromCursor(range.cursor);
1284
+ if (!newRange || newRange.to - newRange.from === 0) {
1285
+ log3.info("deleted comment", {
1286
+ thread: range.id
1287
+ }, {
1288
+ F: __dxlog_file5,
1289
+ L: 342,
1290
+ S: void 0,
1291
+ C: (f, a) => f(...a)
1292
+ });
1293
+ }
1294
+ }
1295
+ if (from <= range.to) {
1296
+ const newRange = model.getRangeFromCursor(range.cursor);
1297
+ Object.assign(range, newRange);
1298
+ mod = true;
1299
+ }
1300
+ });
1301
+ });
1302
+ if (mod) {
1303
+ view.dispatch({
1304
+ effects: setCommentState.of({
1305
+ active,
1306
+ ranges
1307
+ })
1308
+ });
1309
+ }
1310
+ }
1311
+ {
1312
+ const { head } = state.selection.main;
1313
+ let min = Infinity;
1314
+ const { active, closest, ranges } = state.field(commentsStateField);
1315
+ const selected = {
1316
+ active: void 0,
1317
+ closest: void 0
1318
+ };
1319
+ ranges.forEach((comment) => {
1320
+ const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1321
+ if (head >= comment.from && head <= comment.to) {
1322
+ selected.active = comment.id;
1323
+ }
1324
+ if (d < min) {
1325
+ selected.closest = comment.id;
1326
+ min = d;
1327
+ }
1328
+ });
1329
+ if (selected.active !== active || selected.closest !== closest) {
1330
+ view.dispatch({
1331
+ effects: setSelection.of(selected)
1332
+ });
1333
+ handleSelect({
1334
+ ...selected,
1335
+ ranges: ranges.map((range) => ({
1336
+ ...range,
1337
+ location: view.coordsAtPos(range.from)
1338
+ }))
1339
+ });
1340
+ }
1341
+ }
1342
+ })
1343
+ ];
1344
+ };
1345
+
1346
+ // packages/ui/react-ui-editor/src/extensions/hr.ts
1347
+ import { Decoration as Decoration3, EditorView as EditorView5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1348
+ import get3 from "lodash.get";
1349
+ var styles3 = EditorView5.baseTheme({
1350
+ "& .cm-hr": {
1351
+ // TODO(burdon): ???
1352
+ // Note that block-level decorations should not have vertical margins,
1353
+ borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`
1354
+ }
1809
1355
  });
1810
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1811
- const { themeMode } = useThemeContext();
1812
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
1813
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1814
- ref: forwardedRef,
1815
- readonly,
1816
- extensions: [
1817
- basicBundle({
1818
- readonly,
1819
- themeMode,
1820
- placeholder: slots?.editor?.placeholder
1821
- }),
1822
- ...extensions
1823
- ],
1824
- slots,
1825
- ...props
1826
- });
1356
+ var HorizontalRuleWidget = class extends WidgetType2 {
1357
+ constructor(_pos) {
1358
+ super();
1359
+ this._pos = _pos;
1360
+ }
1361
+ eq(other) {
1362
+ return this._pos === other._pos;
1363
+ }
1364
+ toDOM(view) {
1365
+ const el = document.createElement("div");
1366
+ el.className = "cm-hr";
1367
+ return el;
1368
+ }
1369
+ };
1370
+ var placeholderMatcher = new MatchDecorator2({
1371
+ regexp: /^---$/g,
1372
+ decoration: (match, view, pos) => Decoration3.replace({
1373
+ widget: new HorizontalRuleWidget(pos)
1374
+ })
1827
1375
  });
1828
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
1829
- const { themeMode } = useThemeContext();
1830
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
1831
- return /* @__PURE__ */ React.createElement(BaseTextEditor, {
1832
- ref: forwardedRef,
1833
- readonly,
1834
- extensions: [
1835
- markdownBundle({
1836
- readonly,
1837
- themeMode,
1838
- placeholder: slots?.editor?.placeholder
1839
- }),
1840
- ...extensions
1841
- ],
1842
- slots,
1843
- ...props
1376
+ var hr = () => [
1377
+ styles3,
1378
+ ViewPlugin4.fromClass(class {
1379
+ constructor(view) {
1380
+ this.rules = placeholderMatcher.createDeco(view);
1381
+ }
1382
+ update(update4) {
1383
+ this.rules = placeholderMatcher.updateDeco(update4, this.rules);
1384
+ }
1385
+ }, {
1386
+ decorations: (instance) => instance.rules,
1387
+ // TODO(burdon): Is this really atomic (cursor can move into ---).
1388
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1389
+ return view.plugin(plugin)?.rules || Decoration3.none;
1390
+ })
1391
+ })
1392
+ ];
1393
+
1394
+ // packages/ui/react-ui-editor/src/extensions/image.ts
1395
+ import { syntaxTree } from "@codemirror/language";
1396
+ import { RangeSetBuilder, StateField as StateField4 } from "@codemirror/state";
1397
+ import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType3 } from "@codemirror/view";
1398
+ var image = (options = {}) => {
1399
+ return StateField4.define({
1400
+ create: (state) => update(state, options),
1401
+ update: (_, tr) => update(tr.state, options),
1402
+ provide: (field) => EditorView6.decorations.from(field)
1844
1403
  });
1845
- });
1846
- var defaultSlots = {
1847
- root: {
1848
- className: mx4("p-2", inputSurface)
1404
+ };
1405
+ var update = (state, options) => {
1406
+ const builder = new RangeSetBuilder();
1407
+ const cursor = state.selection.main.head;
1408
+ syntaxTree(state).iterate({
1409
+ enter: (node) => {
1410
+ if (node.name === "Image") {
1411
+ const urlNode = node.node.getChild("URL");
1412
+ if (urlNode) {
1413
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
1414
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
1415
+ builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
1416
+ block: true,
1417
+ widget: new ImageWidget(url)
1418
+ }));
1419
+ }
1420
+ }
1421
+ }
1422
+ });
1423
+ return builder.finish();
1424
+ };
1425
+ var ImageWidget = class extends WidgetType3 {
1426
+ constructor(_url) {
1427
+ super();
1428
+ this._url = _url;
1429
+ }
1430
+ // TODO(burdon): Does this need to be unique for each position?
1431
+ eq(other) {
1432
+ return this._url === other._url;
1433
+ }
1434
+ toDOM(view) {
1435
+ const img = document.createElement("img");
1436
+ img.setAttribute("src", this._url);
1437
+ img.setAttribute("class", "cm-image");
1438
+ return img;
1849
1439
  }
1850
1440
  };
1851
- var defaultTextSlots = {
1852
- ...defaultSlots,
1853
- editor: {
1854
- theme: textTheme
1441
+
1442
+ // packages/ui/react-ui-editor/src/extensions/link.ts
1443
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1444
+ import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField5 } from "@codemirror/state";
1445
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2 } from "@codemirror/view";
1446
+ import { tooltipContent } from "@dxos/react-ui-theme";
1447
+ var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1448
+ var link = (options = {}) => {
1449
+ const extensions = [
1450
+ StateField5.define({
1451
+ create: (state) => update2(state, options),
1452
+ update: (_, tr) => update2(tr.state, options),
1453
+ provide: (field) => EditorView7.decorations.from(field)
1454
+ })
1455
+ ];
1456
+ if (options.onHover) {
1457
+ extensions.push(
1458
+ // https://codemirror.net/examples/tooltip
1459
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
1460
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1461
+ hoverTooltip2((view, pos) => {
1462
+ const { from, text } = view.state.doc.lineAt(pos);
1463
+ const p = pos - from;
1464
+ let idx = 0;
1465
+ let match;
1466
+ do {
1467
+ match = text.substring(idx).match(markdownLinkRegexp);
1468
+ if (!match) {
1469
+ match = void 0;
1470
+ break;
1471
+ }
1472
+ idx = text.indexOf(match[0]);
1473
+ if (p >= idx && p < idx + match[0].length) {
1474
+ break;
1475
+ }
1476
+ idx += match[0].length;
1477
+ } while (true);
1478
+ if (!match) {
1479
+ return null;
1480
+ }
1481
+ const [, , url] = match ?? [];
1482
+ return {
1483
+ pos: idx + from,
1484
+ end: idx + from + match[0].length,
1485
+ above: true,
1486
+ create: () => {
1487
+ const el = document.createElement("div");
1488
+ el.className = tooltipContent({}, "pli-2 plb-1");
1489
+ options.onHover(el, url);
1490
+ return {
1491
+ dom: el,
1492
+ offset: {
1493
+ x: 0,
1494
+ y: 4
1495
+ }
1496
+ };
1497
+ }
1498
+ };
1499
+ })
1500
+ );
1855
1501
  }
1502
+ return extensions;
1856
1503
  };
1857
- var defaultMarkdownSlots = {
1858
- ...defaultSlots,
1859
- editor: {
1860
- theme: markdownTheme
1504
+ var LinkButton = class extends WidgetType4 {
1505
+ constructor(_pos, _url, _onAttach) {
1506
+ super();
1507
+ this._pos = _pos;
1508
+ this._url = _url;
1509
+ this._onAttach = _onAttach;
1510
+ }
1511
+ eq(other) {
1512
+ return this._pos === other._pos && this._url === other._url;
1513
+ }
1514
+ toDOM(view) {
1515
+ const el = document.createElement("span");
1516
+ this._onAttach(el, this._url);
1517
+ return el;
1861
1518
  }
1862
1519
  };
1520
+ var LinkText = class extends WidgetType4 {
1521
+ constructor(_text, _url) {
1522
+ super();
1523
+ this._text = _text;
1524
+ this._url = _url;
1525
+ }
1526
+ eq(other) {
1527
+ return this._url === other._url;
1528
+ }
1529
+ toDOM(view) {
1530
+ const link2 = document.createElement("a");
1531
+ link2.setAttribute("class", "cm-link");
1532
+ link2.textContent = this._text;
1533
+ if (this._url) {
1534
+ link2.setAttribute("rel", "noreferrer");
1535
+ link2.setAttribute("target", "_blank");
1536
+ link2.href = this._url;
1537
+ } else {
1538
+ link2.onclick = () => {
1539
+ view.dispatch({
1540
+ selection: {
1541
+ anchor: view.posAtDOM(link2)
1542
+ }
1543
+ });
1544
+ };
1545
+ }
1546
+ return link2;
1547
+ }
1548
+ };
1549
+ var update2 = (state, options) => {
1550
+ const builder = new RangeSetBuilder2();
1551
+ const cursor = state.selection.main.head;
1552
+ syntaxTree2(state).iterate({
1553
+ enter: (node) => {
1554
+ if (node.name === "Link") {
1555
+ const marks2 = node.node.getChildren("LinkMark");
1556
+ const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1557
+ const urlNode = node.node.getChild("URL");
1558
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1559
+ if (!url) {
1560
+ return false;
1561
+ }
1562
+ if (state.readOnly || cursor < node.from || cursor > node.to) {
1563
+ builder.add(node.from, node.to, Decoration5.replace({
1564
+ widget: new LinkText(text, options.link ? url : void 0)
1565
+ }));
1566
+ }
1567
+ if (options.onRender) {
1568
+ builder.add(node.to, node.to, Decoration5.replace({
1569
+ widget: new LinkButton(node.from, url, options.onRender)
1570
+ }));
1571
+ }
1572
+ return false;
1573
+ }
1574
+ }
1575
+ });
1576
+ return builder.finish();
1577
+ };
1863
1578
 
1864
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1865
- import get4 from "lodash.get";
1866
- import { useEffect as useEffect3, useState as useState2 } from "react";
1867
- import { yCollab } from "y-codemirror.next";
1868
- import { invariant as invariant3 } from "@dxos/invariant";
1869
- import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
1870
-
1871
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
1872
- import { Annotation, Facet, StateEffect as StateEffect2, StateField as StateField6 } from "@codemirror/state";
1873
- import { ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1874
- import * as automerge2 from "@dxos/automerge/automerge";
1579
+ // packages/ui/react-ui-editor/src/extensions/listener.ts
1580
+ import { EditorView as EditorView8 } from "@codemirror/view";
1581
+ var listener = ({ onFocus, onChange }) => {
1582
+ const extensions = [];
1583
+ onFocus && extensions.push(EditorView8.focusChangeEffect.of((_, focused) => {
1584
+ onFocus(focused);
1585
+ return null;
1586
+ }));
1587
+ onChange && extensions.push(EditorView8.updateListener.of((update4) => {
1588
+ onChange(update4.state.doc.toString());
1589
+ }));
1590
+ return extensions;
1591
+ };
1875
1592
 
1876
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
1877
- import { next as automerge } from "@dxos/automerge/automerge";
1593
+ // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1594
+ import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1595
+ import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1596
+ import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1597
+ import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1598
+ import { languages } from "@codemirror/language-data";
1599
+ import { searchKeymap } from "@codemirror/search";
1600
+ import { EditorState } from "@codemirror/state";
1601
+ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1602
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1878
1603
 
1879
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/automerge-to-codemirror.ts
1880
- import { ChangeSet } from "@codemirror/state";
1881
- var automergeToCodemirror = (view, selection, target, patches) => {
1882
- for (const patch of patches) {
1883
- const changeSpec = handlePatch(patch, target, view.state);
1884
- if (changeSpec != null) {
1885
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
1886
- selection = selection.map(changeSet, 1);
1887
- view.dispatch({
1888
- changes: changeSet,
1889
- annotations: reconcileAnnotationType.of({})
1890
- });
1604
+ // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1605
+ import { markdownLanguage } from "@codemirror/lang-markdown";
1606
+ import { HighlightStyle } from "@codemirror/language";
1607
+ import { tags, styleTags, Tag } from "@lezer/highlight";
1608
+ import { Table } from "@lezer/markdown";
1609
+ import get4 from "lodash.get";
1610
+ var markdownTags = {
1611
+ Blockquote: Tag.define(),
1612
+ CodeMark: Tag.define(),
1613
+ CodeText: Tag.define(),
1614
+ EmphasisMark: Tag.define(),
1615
+ HeaderMark: Tag.define(),
1616
+ // HorizontalRule: Tag.define(),
1617
+ InlineCode: Tag.define(),
1618
+ LinkLabel: Tag.define(),
1619
+ LinkReference: Tag.define(),
1620
+ ListMark: Tag.define(),
1621
+ QuoteMark: Tag.define(),
1622
+ URL: Tag.define(),
1623
+ // Custom.
1624
+ TableCell: Tag.define()
1625
+ };
1626
+ Table.defineNodes?.forEach((node) => {
1627
+ switch (node?.name) {
1628
+ case "TableCell": {
1629
+ node.style = markdownTags.TableCell;
1630
+ break;
1631
+ }
1632
+ }
1633
+ });
1634
+ var markdownTagsExtensions = [
1635
+ Table,
1636
+ {
1637
+ props: [
1638
+ styleTags(markdownTags)
1639
+ ]
1640
+ }
1641
+ ];
1642
+ var markdownHighlightStyle = (readonly) => {
1643
+ return HighlightStyle.define([
1644
+ {
1645
+ tag: [
1646
+ tags.keyword,
1647
+ tags.name,
1648
+ tags.deleted,
1649
+ tags.character,
1650
+ tags.propertyName,
1651
+ tags.macroName,
1652
+ tags.color,
1653
+ tags.constant(tags.name),
1654
+ tags.standard(tags.name),
1655
+ tags.definition(tags.name),
1656
+ tags.separator,
1657
+ tags.typeName,
1658
+ tags.className,
1659
+ tags.number,
1660
+ tags.changed,
1661
+ tags.annotation,
1662
+ tags.modifier,
1663
+ tags.self,
1664
+ tags.namespace,
1665
+ tags.operator,
1666
+ tags.operatorKeyword,
1667
+ tags.escape,
1668
+ tags.regexp,
1669
+ tags.special(tags.string),
1670
+ tags.meta,
1671
+ tags.comment,
1672
+ tags.atom,
1673
+ tags.bool,
1674
+ tags.special(tags.variableName),
1675
+ tags.processingInstruction,
1676
+ tags.string,
1677
+ tags.inserted,
1678
+ tags.invalid
1679
+ ],
1680
+ color: "inherit !important"
1681
+ },
1682
+ // Markdown marks.
1683
+ {
1684
+ tag: [
1685
+ tags.meta,
1686
+ tags.processingInstruction,
1687
+ markdownTags.LinkLabel,
1688
+ markdownTags.LinkReference,
1689
+ markdownTags.ListMark
1690
+ ],
1691
+ class: mark
1692
+ },
1693
+ // Markdown marks.
1694
+ {
1695
+ tag: [
1696
+ markdownTags.CodeMark,
1697
+ markdownTags.HeaderMark,
1698
+ markdownTags.QuoteMark,
1699
+ markdownTags.EmphasisMark
1700
+ ],
1701
+ class: readonly ? "hidden" : mark
1702
+ },
1703
+ // E.g., code block language (after ```).
1704
+ {
1705
+ tag: [
1706
+ tags.function(tags.variableName),
1707
+ tags.labelName
1708
+ ],
1709
+ class: readonly ? "hidden" : codeMark
1710
+ },
1711
+ {
1712
+ tag: [
1713
+ tags.monospace
1714
+ ],
1715
+ class: "font-mono"
1716
+ },
1717
+ // Headings.
1718
+ {
1719
+ tag: tags.heading1,
1720
+ class: heading(1, readonly)
1721
+ },
1722
+ {
1723
+ tag: tags.heading2,
1724
+ class: heading(2, readonly)
1725
+ },
1726
+ {
1727
+ tag: tags.heading3,
1728
+ class: heading(3, readonly)
1729
+ },
1730
+ {
1731
+ tag: tags.heading4,
1732
+ class: heading(4, readonly)
1733
+ },
1734
+ {
1735
+ tag: tags.heading5,
1736
+ class: heading(5, readonly)
1737
+ },
1738
+ {
1739
+ tag: tags.heading6,
1740
+ class: heading(6, readonly)
1741
+ },
1742
+ // Emphasis.
1743
+ {
1744
+ tag: tags.emphasis,
1745
+ class: italic
1746
+ },
1747
+ {
1748
+ tag: tags.strong,
1749
+ class: bold
1750
+ },
1751
+ {
1752
+ tag: tags.strikethrough,
1753
+ class: strikethrough
1754
+ },
1755
+ // Naked URLs.
1756
+ {
1757
+ tag: [
1758
+ markdownTags.URL
1759
+ ],
1760
+ class: inlineUrl
1761
+ },
1762
+ // NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
1763
+ // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1764
+ // when a language is specified. In this case, the syntax highlighting extensions will colorize
1765
+ // the code, but all other CSS properties will be inherited.
1766
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1767
+ {
1768
+ tag: [
1769
+ markdownTags.CodeText,
1770
+ markdownTags.InlineCode
1771
+ ],
1772
+ class: code
1773
+ },
1774
+ {
1775
+ tag: [
1776
+ markdownTags.QuoteMark
1777
+ ],
1778
+ class: blockquote
1779
+ },
1780
+ // TODO(burdon): Replace with extension.
1781
+ // {
1782
+ // tag: [markdownTags.HorizontalRule],
1783
+ // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1784
+ // },
1785
+ // TODO(burdon): Only if being edited.
1786
+ {
1787
+ tag: [
1788
+ markdownTags.TableCell
1789
+ ],
1790
+ class: "font-mono"
1791
+ }
1792
+ ], {
1793
+ scope: markdownLanguage,
1794
+ all: {
1795
+ fontFamily: get4(tokens, "fontFamily.body", []).join(",")
1891
1796
  }
1892
- }
1893
- view.dispatch({
1894
- selection,
1895
- annotations: reconcileAnnotationType.of({})
1896
1797
  });
1897
1798
  };
1898
- var handlePatch = (patch, target, state) => {
1899
- if (patch.action === "insert") {
1900
- return handleInsert(target, patch);
1901
- } else if (patch.action === "splice") {
1902
- return handleSplice(target, patch);
1903
- } else if (patch.action === "del") {
1904
- return handleDel(target, patch);
1905
- } else if (patch.action === "put") {
1906
- return handlePut(target, patch, state);
1907
- } else {
1908
- return null;
1909
- }
1910
- };
1911
- var handleInsert = (target, patch) => {
1912
- const index = charPath(target, patch.path);
1913
- if (index == null) {
1914
- return [];
1915
- }
1916
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
1799
+
1800
+ // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1801
+ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1917
1802
  return [
1918
- {
1919
- from: index,
1920
- to: index,
1921
- insert: text
1922
- }
1923
- ];
1803
+ EditorState.allowMultipleSelections.of(true),
1804
+ EditorState.tabSize.of(2),
1805
+ EditorView9.lineWrapping,
1806
+ customKeymap(),
1807
+ bracketMatching2(),
1808
+ closeBrackets2(),
1809
+ crosshairCursor(),
1810
+ dropCursor(),
1811
+ drawSelection2(),
1812
+ highlightActiveLine(),
1813
+ history(),
1814
+ indentOnInput(),
1815
+ _placeholder && placeholder2(_placeholder),
1816
+ // Main extension.
1817
+ // https://github.com/codemirror/lang-markdown
1818
+ // https://codemirror.net/5/mode/markdown/index.html (demo).
1819
+ markdown({
1820
+ // GRM by default (vs strict CommonMark):
1821
+ // Table, TaskList, Strikethrough, and Autolink.
1822
+ // NOTE: This extends the parser; it doesn't affect rendering.
1823
+ // https://github.github.com/gfm
1824
+ // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1825
+ base: markdownLanguage2,
1826
+ // Languages for syntax highlighting fenced code blocks.
1827
+ codeLanguages: languages,
1828
+ // Parser extensions.
1829
+ extensions: [
1830
+ // GFM provided by default.
1831
+ markdownTagsExtensions
1832
+ ]
1833
+ }),
1834
+ // https://github.com/codemirror/theme-one-dark
1835
+ themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1836
+ // Custom styles.
1837
+ syntaxHighlighting2(markdownHighlightStyle(readonly))
1838
+ ].filter(Boolean);
1924
1839
  };
1925
- var handleSplice = (target, patch) => {
1926
- const index = charPath(target, patch.path);
1927
- if (index == null) {
1928
- return [];
1929
- }
1930
- return [
1931
- {
1932
- from: index,
1933
- insert: patch.value
1934
- }
1935
- ];
1840
+ var customKeymap = () => keymap4.of([
1841
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1842
+ indentWithTab,
1843
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1844
+ ...standardKeymap,
1845
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1846
+ ...historyKeymap,
1847
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1848
+ ...closeBracketsKeymap,
1849
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1850
+ ...searchKeymap
1851
+ ]);
1852
+
1853
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
1854
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1855
+ var mention = ({ onSearch }) => {
1856
+ return autocompletion2({
1857
+ closeOnBlur: false,
1858
+ override: [
1859
+ (context) => {
1860
+ const match = context.matchBefore(/@(\w+)?/);
1861
+ if (!match || match.from === match.to && !context.explicit) {
1862
+ return null;
1863
+ }
1864
+ console.log(match);
1865
+ return {
1866
+ from: match.from,
1867
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1868
+ label: `@${value}`
1869
+ }))
1870
+ };
1871
+ }
1872
+ ]
1873
+ });
1936
1874
  };
1937
- var handleDel = (target, patch) => {
1938
- const index = charPath(target, patch.path);
1939
- if (index == null) {
1940
- return [];
1941
- }
1942
- const length = patch.length || 1;
1943
- return [
1944
- {
1945
- from: index,
1946
- to: index + length
1947
- }
1948
- ];
1875
+
1876
+ // packages/ui/react-ui-editor/src/extensions/mermaid.ts
1877
+ var mermaid = () => {
1878
+ return [];
1949
1879
  };
1950
- var handlePut = (target, patch, state) => {
1951
- const index = charPath(target, [
1952
- ...patch.path,
1953
- 0
1954
- ]);
1955
- if (index == null) {
1956
- return [];
1957
- }
1958
- const length = state.doc.length;
1959
- if (typeof patch.value !== "string") {
1960
- return [];
1961
- }
1962
- return [
1963
- {
1964
- from: 0,
1965
- to: length,
1966
- insert: patch.value
1967
- }
1968
- ];
1880
+
1881
+ // packages/ui/react-ui-editor/src/extensions/table.ts
1882
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1883
+ import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField6 } from "@codemirror/state";
1884
+ import { Decoration as Decoration6, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1885
+ var table = (options = {}) => {
1886
+ return StateField6.define({
1887
+ create: (state) => update3(state, options),
1888
+ update: (_, tr) => update3(tr.state, options),
1889
+ provide: (field) => EditorView10.decorations.from(field)
1890
+ });
1969
1891
  };
1970
- var charPath = (textPath, candidatePath) => {
1971
- if (candidatePath.length !== textPath.length + 1) {
1972
- return null;
1973
- }
1974
- for (let i = 0; i < textPath.length; i++) {
1975
- if (textPath[i] !== candidatePath[i]) {
1976
- return null;
1892
+ var update3 = (state, options) => {
1893
+ const builder = new RangeSetBuilder3();
1894
+ const cursor = state.selection.main.head;
1895
+ const tables = [];
1896
+ const getTable = () => tables[tables.length - 1];
1897
+ const getRow = () => {
1898
+ const table2 = getTable();
1899
+ return table2.rows?.[table2.rows.length - 1];
1900
+ };
1901
+ syntaxTree3(state).iterate({
1902
+ enter: (node) => {
1903
+ switch (node.name) {
1904
+ case "Table": {
1905
+ tables.push({
1906
+ from: node.from,
1907
+ to: node.to
1908
+ });
1909
+ break;
1910
+ }
1911
+ case "TableHeader": {
1912
+ getTable().header = [];
1913
+ break;
1914
+ }
1915
+ case "TableRow": {
1916
+ (getTable().rows ??= []).push([]);
1917
+ break;
1918
+ }
1919
+ case "TableCell": {
1920
+ const row = getRow();
1921
+ if (row) {
1922
+ row.push(state.sliceDoc(node.from, node.to));
1923
+ } else {
1924
+ getTable().header?.push(state.sliceDoc(node.from, node.to));
1925
+ }
1926
+ break;
1927
+ }
1928
+ }
1977
1929
  }
1978
- }
1979
- const index = candidatePath[candidatePath.length - 1];
1980
- if (typeof index === "number") {
1981
- return index;
1982
- }
1983
- return null;
1930
+ });
1931
+ tables.forEach((table2) => {
1932
+ const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1933
+ hide && builder.add(table2.from, table2.to, Decoration6.replace({
1934
+ block: true,
1935
+ widget: new TableWidget(table2)
1936
+ }));
1937
+ builder.add(table2.from, table2.to, Decoration6.mark({
1938
+ class: "cm-table"
1939
+ }));
1940
+ });
1941
+ return builder.finish();
1984
1942
  };
1985
-
1986
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/codemirror-to-automerge.ts
1987
- import { next as am } from "@dxos/automerge/automerge";
1988
- var codemirrorToAutomerge = (field, handle, transactions, state) => {
1989
- const { lastHeads, path } = state.field(field);
1990
- let hasChanges = false;
1991
- for (const tr of transactions) {
1992
- tr.changes.iterChanges(() => {
1993
- hasChanges = true;
1994
- });
1943
+ var TableWidget = class extends WidgetType5 {
1944
+ constructor(_table) {
1945
+ super();
1946
+ this._table = _table;
1995
1947
  }
1996
- if (!hasChanges) {
1997
- return void 0;
1948
+ eq(other) {
1949
+ return this._table.from === other?._table?.from;
1998
1950
  }
1999
- const newHeads = handle.changeAt(lastHeads, (doc) => {
2000
- for (const tr of transactions) {
2001
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
2002
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
1951
+ toDOM(view) {
1952
+ const table2 = document.createElement("table");
1953
+ {
1954
+ const header = table2.appendChild(document.createElement("thead"));
1955
+ const tr = header.appendChild(document.createElement("tr"));
1956
+ this._table.header?.forEach((cell) => {
1957
+ const th = document.createElement("th");
1958
+ th.setAttribute("class", "cm-table-head");
1959
+ tr.appendChild(th).textContent = cell;
2003
1960
  });
2004
1961
  }
2005
- });
2006
- return newHeads ?? void 0;
2007
- };
2008
-
2009
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
2010
- var PatchSemaphore = class {
2011
- constructor(field) {
2012
- this._inReconcile = false;
2013
- this._queue = [];
2014
- this.reconcile = (handle, view) => {
2015
- if (this._inReconcile) {
2016
- return;
2017
- }
2018
- this._inReconcile = true;
2019
- const path = getPath(view.state, this._field);
2020
- const oldHeads = getLastHeads(view.state, this._field);
2021
- let selection = view.state.selection;
2022
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
2023
- const toInvert = transactions.slice().reverse();
2024
- for (const tx of toInvert) {
2025
- const inverted = tx.changes.invert(tx.startState.doc);
2026
- selection = selection.map(inverted);
2027
- view.dispatch({
2028
- changes: inverted,
2029
- annotations: reconcileAnnotationType.of(true)
1962
+ {
1963
+ const body = table2.appendChild(document.createElement("tbody"));
1964
+ this._table.rows?.forEach((row) => {
1965
+ const tr = body.appendChild(document.createElement("tr"));
1966
+ row.forEach((cell) => {
1967
+ const td = document.createElement("td");
1968
+ td.setAttribute("class", "cm-table-cell");
1969
+ tr.appendChild(td).textContent = cell;
2030
1970
  });
2031
- }
2032
- let newHeads = codemirrorToAutomerge(this._field, handle, transactions, view.state);
2033
- if (newHeads === null || newHeads === void 0) {
2034
- newHeads = automerge.getHeads(handle.docSync());
2035
- }
2036
- const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
2037
- automergeToCodemirror(view, selection, path, diff);
2038
- view.dispatch({
2039
- effects: updateHeads(newHeads),
2040
- annotations: reconcileAnnotationType.of({})
2041
1971
  });
2042
- this._inReconcile = false;
2043
- };
2044
- if (field !== void 0) {
2045
- this._field = field;
2046
1972
  }
1973
+ return table2;
2047
1974
  }
2048
1975
  };
2049
1976
 
2050
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
2051
- var effectType = StateEffect2.define({});
2052
- var updateHeads = (newHeads) => effectType.of({
2053
- newHeads
2054
- });
2055
- var getLastHeads = (state, field) => state.field(field).lastHeads;
2056
- var getPath = (state, field) => state.field(field).path;
2057
- var semaphoreFacet = Facet.define({
2058
- combine: (values) => values.at(-1)
1977
+ // packages/ui/react-ui-editor/src/extensions/tasklist.ts
1978
+ import { EditorView as EditorView11, Decoration as Decoration7, WidgetType as WidgetType6, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1979
+ var styles4 = EditorView11.baseTheme({
1980
+ "& .cm-task-item": {
1981
+ paddingLeft: "4px",
1982
+ paddingRight: "8px"
1983
+ }
2059
1984
  });
2060
- var automergePlugin = (handle, path) => {
2061
- const stateField = StateField6.define({
2062
- create: () => ({
2063
- lastHeads: automerge2.getHeads(handle.docSync()),
2064
- unreconciledTransactions: [],
2065
- path: path.slice()
2066
- }),
2067
- update: (value, tr) => {
2068
- const result = {
2069
- lastHeads: value.lastHeads,
2070
- unreconciledTransactions: value.unreconciledTransactions.slice(),
2071
- path: path.slice()
1985
+ var CheckboxWidget = class extends WidgetType6 {
1986
+ constructor(_pos, _checked, _indent, _onCheck) {
1987
+ super();
1988
+ this._pos = _pos;
1989
+ this._checked = _checked;
1990
+ this._indent = _indent;
1991
+ this._onCheck = _onCheck;
1992
+ }
1993
+ eq(other) {
1994
+ return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
1995
+ }
1996
+ toDOM(view) {
1997
+ const wrap = document.createElement("span");
1998
+ wrap.className = "cm-task-item";
1999
+ wrap.setAttribute("aria-hidden", "true");
2000
+ wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2001
+ const input = wrap.appendChild(document.createElement("input"));
2002
+ input.type = "checkbox";
2003
+ input.checked = this._checked;
2004
+ if (!this._onCheck) {
2005
+ input.setAttribute("disabled", "true");
2006
+ }
2007
+ if (this._onCheck) {
2008
+ input.onchange = (event) => {
2009
+ this._onCheck?.(event.target.checked);
2010
+ return true;
2072
2011
  };
2073
- let clearUnreconciled = false;
2074
- for (const effect of tr.effects) {
2075
- if (effect.is(effectType)) {
2076
- result.lastHeads = effect.value.newHeads;
2077
- clearUnreconciled = true;
2078
- }
2079
- }
2080
- if (clearUnreconciled) {
2081
- result.unreconciledTransactions = [];
2082
- } else {
2083
- if (!isReconcileTx(tr)) {
2084
- result.unreconciledTransactions.push(tr);
2085
- }
2086
- }
2087
- return result;
2012
+ }
2013
+ return wrap;
2014
+ }
2015
+ ignoreEvent() {
2016
+ return false;
2017
+ }
2018
+ };
2019
+ var tasklist = (options = {}) => {
2020
+ const taskMatcher = new MatchDecorator3({
2021
+ regexp: /^(\s*)- \[([ xX])\]\s/g,
2022
+ decoration: (match, view, pos) => {
2023
+ const indent = Math.floor(match[1].length / 2);
2024
+ const checked = match[2] === "x" || match[2] === "X";
2025
+ return Decoration7.replace({
2026
+ widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2027
+ const idx = pos + match[0].indexOf("[") + 1;
2028
+ view.dispatch({
2029
+ changes: {
2030
+ from: idx,
2031
+ to: idx + 1,
2032
+ insert: checked2 ? "x" : " "
2033
+ },
2034
+ // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2035
+ selection: {
2036
+ anchor: idx + 3
2037
+ }
2038
+ });
2039
+ })
2040
+ });
2088
2041
  }
2089
2042
  });
2090
- const semaphore = new PatchSemaphore(stateField);
2091
- const viewPlugin = ViewPlugin4.fromClass(class AutomergeCodemirrorViewPlugin {
2043
+ const tasks = ViewPlugin5.fromClass(class {
2092
2044
  constructor(view) {
2093
- this._handleChange = () => {
2094
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2095
- };
2096
- this._view = view;
2097
- handle.addListener("change", this._handleChange);
2045
+ this.tasks = taskMatcher.createDeco(view);
2098
2046
  }
2099
2047
  update(update4) {
2100
- if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
2101
- queueMicrotask(() => {
2102
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2103
- });
2104
- }
2105
- }
2106
- destroy() {
2107
- handle.addListener("change", this._handleChange);
2048
+ this.tasks = taskMatcher.updateDeco(update4, this.tasks);
2108
2049
  }
2050
+ }, {
2051
+ decorations: (value) => value.tasks,
2052
+ provide: (plugin) => EditorView11.atomicRanges.of((view) => {
2053
+ return view.plugin(plugin)?.tasks || Decoration7.none;
2054
+ })
2109
2055
  });
2110
- return {
2111
- extension: [
2112
- stateField,
2113
- semaphoreFacet.of(semaphore),
2114
- viewPlugin
2115
- ]
2116
- };
2056
+ return [
2057
+ tasks,
2058
+ styles4
2059
+ ];
2060
+ };
2061
+
2062
+ // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2063
+ import { keymap as keymap5 } from "@codemirror/view";
2064
+ var defaultItems = [
2065
+ "hello world!",
2066
+ "this is a test.",
2067
+ "this is [DXOS](https://dxos.org)"
2068
+ ];
2069
+ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2070
+ let t;
2071
+ let idx = 0;
2072
+ return [
2073
+ keymap5.of([
2074
+ {
2075
+ // Reset.
2076
+ key: "alt-meta-'",
2077
+ run: (view) => {
2078
+ clearTimeout(t);
2079
+ idx = 0;
2080
+ return true;
2081
+ }
2082
+ },
2083
+ {
2084
+ // Next prompt.
2085
+ // TODO(burdon): Press 1-9 to select prompt?
2086
+ key: "shift-meta-'",
2087
+ run: (view) => {
2088
+ clearTimeout(t);
2089
+ const text = items[idx++];
2090
+ if (idx === items?.length) {
2091
+ idx = 0;
2092
+ }
2093
+ let i = 0;
2094
+ const insert = (d = 0) => {
2095
+ t = setTimeout(() => {
2096
+ const pos = view.state.selection.main.head;
2097
+ view.dispatch({
2098
+ changes: {
2099
+ from: pos,
2100
+ insert: text[i++]
2101
+ },
2102
+ selection: {
2103
+ anchor: pos + 1
2104
+ }
2105
+ });
2106
+ if (i < text.length) {
2107
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
2108
+ }
2109
+ }, d);
2110
+ };
2111
+ insert();
2112
+ return true;
2113
+ }
2114
+ }
2115
+ ])
2116
+ ];
2117
2117
  };
2118
- var reconcileAnnotationType = Annotation.define();
2119
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
2120
2118
 
2121
- // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
2122
- import * as random2 from "lib0/random";
2123
- var cursorColors = [
2124
- {
2125
- color: "#30bced",
2126
- light: "#30bced33"
2119
+ // packages/ui/react-ui-editor/src/themes/default.ts
2120
+ import get5 from "lodash.get";
2121
+ var defaultTheme = {
2122
+ //
2123
+ // Main layout:
2124
+ // https://codemirror.net/examples/styling
2125
+ //
2126
+ // <div class="cm-editor [cm-focused] [generated classes]">
2127
+ // <div class="cm-scroller">
2128
+ // <div class="cm-gutters">
2129
+ // <div class="cm-gutter [...]">
2130
+ // <div class="cm-gutterElement">...</div>
2131
+ // </div>
2132
+ // </div>
2133
+ // <div class="cm-content" role="textbox" contenteditable="true">
2134
+ // <div class="cm-line"></div>
2135
+ // </div>
2136
+ // <div class="cm-selectionLayer">
2137
+ // <div class="cm-selectionBackground"></div>
2138
+ // </div>
2139
+ // <div class="cm-cursorLayer">
2140
+ // <div class="cm-cursor"></div>
2141
+ // </div>
2142
+ // </div>
2143
+ // </div>
2144
+ //
2145
+ "&": {},
2146
+ "&.cm-focused": {
2147
+ outline: "none"
2127
2148
  },
2128
- {
2129
- color: "#6eeb83",
2130
- light: "#6eeb8333"
2149
+ "& .cm-scroller": {
2150
+ overflow: "visible",
2151
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2131
2152
  },
2132
- {
2133
- color: "#ffbc42",
2134
- light: "#ffbc4233"
2153
+ "& .cm-content": {
2154
+ padding: 0,
2155
+ // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
2156
+ fontSize: "16px"
2135
2157
  },
2136
- {
2137
- color: "#ecd444",
2138
- light: "#ecd44433"
2158
+ "&light .cm-content": {
2159
+ color: get5(tokens, "extend.colors.neutral.900", "black"),
2160
+ caretColor: "black"
2139
2161
  },
2140
- {
2141
- color: "#ee6352",
2142
- light: "#ee635233"
2162
+ "&dark .cm-content": {
2163
+ color: get5(tokens, "extend.colors.neutral.100", "white"),
2164
+ caretColor: "white"
2143
2165
  },
2144
- {
2145
- color: "#9ac2c9",
2146
- light: "#9ac2c933"
2166
+ //
2167
+ // Cursor
2168
+ //
2169
+ "&light .cm-cursor, &light .cm-dropCursor": {
2170
+ borderLeft: "2px solid black"
2147
2171
  },
2148
- {
2149
- color: "#8acb88",
2150
- light: "#8acb8833"
2172
+ "&dark .cm-cursor, &dark .cm-dropCursor": {
2173
+ borderLeft: "2px solid white"
2151
2174
  },
2152
- {
2153
- color: "#1be7ff",
2154
- light: "#1be7ff33"
2155
- }
2156
- ];
2157
- var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
2158
-
2159
- // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
2160
- import * as decoding from "lib0/decoding";
2161
- import * as encoding from "lib0/encoding";
2162
- import { Observable } from "lib0/observable";
2163
- import * as awarenessProtocol from "y-protocols/awareness";
2164
- import { log } from "@dxos/log";
2165
- var __dxlog_file3 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
2166
- var messageAwareness = 1;
2167
- var messageQueryAwareness = 3;
2168
- var SpaceAwarenessProvider = class extends Observable {
2169
- constructor({ space, doc, channel, awareness }) {
2170
- super();
2171
- this._space = space;
2172
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
2173
- this._channel = channel;
2174
- this._clientId = doc.clientID;
2175
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2176
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2177
- if (typeof window !== "undefined") {
2178
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2179
- } else if (typeof process !== "undefined") {
2180
- process.on("exit", this._handleBeforeUnload.bind(this));
2175
+ "& .cm-placeholder": {
2176
+ fontWeight: 100
2177
+ },
2178
+ //
2179
+ // line
2180
+ //
2181
+ "& .cm-line": {
2182
+ paddingInline: 0
2183
+ },
2184
+ "& .cm-activeLine": {
2185
+ background: "inherit"
2186
+ },
2187
+ //
2188
+ // Selection
2189
+ //
2190
+ "&light .cm-selectionBackground": {
2191
+ background: get5(tokens, "extend.colors.primary.100")
2192
+ },
2193
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2194
+ background: get5(tokens, "extend.colors.primary.200")
2195
+ },
2196
+ "&dark .cm-selectionBackground": {
2197
+ background: get5(tokens, "extend.colors.primary.700")
2198
+ },
2199
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2200
+ background: get5(tokens, "extend.colors.primary.600")
2201
+ },
2202
+ //
2203
+ // Search
2204
+ //
2205
+ "&light .cm-searchMatch": {
2206
+ backgroundColor: get5(tokens, "extend.colors.yellow.100")
2207
+ },
2208
+ "&dark .cm-searchMatch": {
2209
+ backgroundColor: get5(tokens, "extend.colors.yellow.700")
2210
+ },
2211
+ //
2212
+ // collaboration
2213
+ // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2214
+ //
2215
+ "&light .cm-ySelection, &light .cm-yLineSelection": {
2216
+ mixBlendMode: "multiply"
2217
+ },
2218
+ "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2219
+ mixBlendMode: "screen"
2220
+ },
2221
+ "& .cm-ySelectionInfo": {
2222
+ padding: "2px 4px",
2223
+ marginBlockStart: "-4px"
2224
+ },
2225
+ "& .cm-ySelection, & .cm-selectionMatch": {
2226
+ paddingBlockStart: ".15em",
2227
+ paddingBlockEnd: ".15em"
2228
+ },
2229
+ "& .cm-ySelectionCaret": {
2230
+ display: "inline-block",
2231
+ insetBlockStart: ".1em",
2232
+ blockSize: "1.4em",
2233
+ verticalAlign: "top"
2234
+ },
2235
+ "& .cm-yLineSelection": {
2236
+ margin: 0
2237
+ },
2238
+ //
2239
+ // link
2240
+ //
2241
+ "& .cm-link": {
2242
+ color: get5(tokens, "extend.colors.primary.500"),
2243
+ textDecorationLine: "underline",
2244
+ textDecorationThickness: "1px",
2245
+ textUnderlineOffset: "2px",
2246
+ borderRadius: ".125rem",
2247
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2248
+ },
2249
+ //
2250
+ // tooltip
2251
+ //
2252
+ "& .cm-tooltip": {
2253
+ border: "none"
2254
+ },
2255
+ "& .cm-tooltip-below": {},
2256
+ //
2257
+ // autocomplete
2258
+ //
2259
+ "& .cm-tooltip-autocomplete": {
2260
+ marginTop: "4px",
2261
+ marginLeft: "-3px"
2262
+ },
2263
+ "& .cm-tooltip-autocomplete ul li": {},
2264
+ "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
2265
+ "& .cm-completionIcon": {
2266
+ display: "none"
2267
+ },
2268
+ "& .cm-completionLabel": {
2269
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2270
+ },
2271
+ "& .cm-completionMatchedText": {
2272
+ textDecoration: "none"
2273
+ },
2274
+ //
2275
+ // widgets
2276
+ //
2277
+ "& .cm-widgetBuffer": {
2278
+ display: "none",
2279
+ height: 0
2280
+ },
2281
+ //
2282
+ // table
2283
+ //
2284
+ "& .cm-table *": {
2285
+ fontFamily: `${get5(tokens, "fontFamily.mono", []).join(",")} !important`,
2286
+ textDecoration: "none !important"
2287
+ },
2288
+ "& .cm-table-head": {
2289
+ padding: "2px 16px 2px 0px",
2290
+ borderBottom: `1px solid ${get5(tokens, "extend.colors.neutral.500")}`,
2291
+ fontWeight: 100,
2292
+ textAlign: "left",
2293
+ color: get5(tokens, "extend.colors.neutral.500")
2294
+ },
2295
+ "& .cm-table-cell": {
2296
+ padding: "2px 16px 2px 0px"
2297
+ },
2298
+ //
2299
+ // image
2300
+ //
2301
+ "& .cm-image": {
2302
+ margin: "0.5rem 0"
2303
+ },
2304
+ //
2305
+ // font size
2306
+ // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
2307
+ //
2308
+ ...Object.keys(get5(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2309
+ const height = get5(tokens, [
2310
+ "extend",
2311
+ "fontSize",
2312
+ fontSize,
2313
+ 1,
2314
+ "lineHeight"
2315
+ ]);
2316
+ acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2317
+ height
2318
+ };
2319
+ acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2320
+ height
2321
+ };
2322
+ acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2323
+ height
2324
+ };
2325
+ return acc;
2326
+ }, {}),
2327
+ /**
2328
+ * Panels
2329
+ * TODO(burdon): Needs styling attention (esp. dark mode).
2330
+ * https://github.com/codemirror/search/blob/main/src/search.ts#L745
2331
+ *
2332
+ * Find/replace panel.
2333
+ * <div class="cm-announced">...</div>
2334
+ * <div class="cm-scroller">...</div>
2335
+ * <div class="cm-panels cm-panels-bottom">
2336
+ * <div class="cm-search cm-panel">
2337
+ * <input class="cm-textfield" />
2338
+ * <button class="cm-button">...</button>
2339
+ * <label><input type="checkbox" />...</label>
2340
+ * </div>
2341
+ * </div
2342
+ */
2343
+ "& .cm-panels": {
2344
+ border: `1px solid ${get5(tokens, "extend.colors.neutral.200")}`
2345
+ },
2346
+ "& .cm-panel": {
2347
+ background: get5(tokens, "extend.colors.neutral.50"),
2348
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2349
+ },
2350
+ "& .cm-button": {
2351
+ margin: "4px",
2352
+ fontFamily: get5(tokens, "fontFamily.body", []).join(","),
2353
+ background: get5(tokens, "extend.colors.neutral.100"),
2354
+ backgroundImage: "none",
2355
+ border: "none",
2356
+ "&:hover": {
2357
+ background: get5(tokens, "extend.colors.neutral.200")
2358
+ },
2359
+ "&:active": {
2360
+ background: get5(tokens, "extend.colors.neutral.300"),
2361
+ backgroundImage: "none"
2181
2362
  }
2182
- const encoderAwarenessQuery = encoding.createEncoder();
2183
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2184
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2185
- const encoderAwarenessState = encoding.createEncoder();
2186
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2187
- encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
2188
- this._clientId
2189
- ]));
2190
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2363
+ },
2364
+ "& .cm-panel input[type=checkbox]": {
2365
+ marginRight: "0.4rem !important"
2366
+ }
2367
+ };
2368
+ var textTheme = {
2369
+ "& .cm-scroller": {
2370
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2371
+ },
2372
+ "& .cm-placeholder": {
2373
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2374
+ }
2375
+ };
2376
+ var markdownTheme = {
2377
+ // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2378
+ "& .cm-placeholder": {
2379
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
2191
2380
  }
2192
- get awareness() {
2193
- return this._awareness;
2381
+ };
2382
+ var codeTheme = {
2383
+ "& .cm-scroller": {
2384
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2385
+ },
2386
+ "& .cm-placeholder": {
2387
+ fontFamily: get5(tokens, "fontFamily.mono", []).join(",")
2194
2388
  }
2195
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2196
- log("awareness update", {
2197
- added,
2198
- updated,
2199
- removed,
2200
- origin
2201
- }, {
2202
- F: __dxlog_file3,
2203
- L: 67,
2204
- S: this,
2205
- C: (f, a) => f(...a)
2389
+ };
2390
+
2391
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2392
+ var EditorModes = [
2393
+ "default",
2394
+ "vim"
2395
+ ];
2396
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2397
+ const { themeMode } = useThemeContext();
2398
+ const tabsterDOMAttribute = useFocusableGroup({
2399
+ tabBehavior: "limited"
2400
+ });
2401
+ const [root, setRoot] = useState2(null);
2402
+ const [{ state = void 0, view = void 0 } = {}, setEditor] = useState2();
2403
+ useImperativeHandle(forwardedRef, () => ({
2404
+ root,
2405
+ state,
2406
+ view
2407
+ }), [
2408
+ view,
2409
+ state,
2410
+ root
2411
+ ]);
2412
+ const { awareness, peer } = model;
2413
+ useEffect2(() => {
2414
+ if (awareness && peer) {
2415
+ awareness.setLocalStateField("user", {
2416
+ name: peer.name ?? generateName(peer.id),
2417
+ color: getColorForValue({
2418
+ value: peer.id,
2419
+ type: "color"
2420
+ }),
2421
+ colorLight: getColorForValue({
2422
+ value: peer.id,
2423
+ themeMode,
2424
+ type: "highlight"
2425
+ })
2426
+ });
2427
+ }
2428
+ }, [
2429
+ awareness,
2430
+ peer,
2431
+ themeMode
2432
+ ]);
2433
+ useEffect2(() => {
2434
+ if (view && comments2?.length) {
2435
+ view.dispatch({
2436
+ effects: setCommentRange.of({
2437
+ model,
2438
+ comments: comments2
2439
+ })
2440
+ });
2441
+ }
2442
+ }, [
2443
+ view,
2444
+ comments2
2445
+ ]);
2446
+ useEffect2(() => {
2447
+ if (!root) {
2448
+ return;
2449
+ }
2450
+ const state2 = EditorState2.create({
2451
+ doc: model.text(),
2452
+ extensions: [
2453
+ readonly && EditorState2.readOnly.of(readonly),
2454
+ // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2455
+ editorMode === "vim" && vim(),
2456
+ // Theme.
2457
+ // TODO(burdon): Make theme configurable.
2458
+ EditorView12.baseTheme(defaultTheme),
2459
+ EditorView12.theme(slots?.editor?.theme ?? {}),
2460
+ EditorView12.darkTheme.of(themeMode === "dark"),
2461
+ // Storage and replication.
2462
+ model.extension,
2463
+ // Custom.
2464
+ ...extensions
2465
+ ].filter(Boolean)
2206
2466
  });
2207
- const changedClients = added.concat(updated).concat(removed);
2208
- const encoderAwareness = encoding.createEncoder();
2209
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2210
- encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
2211
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2212
- }
2213
- _handleSpaceMessage({ payload }) {
2214
- log("space message", payload, {
2215
- F: __dxlog_file3,
2216
- L: 79,
2217
- S: this,
2218
- C: (f, a) => f(...a)
2467
+ view?.destroy();
2468
+ setEditor({
2469
+ state: state2,
2470
+ view: new EditorView12({
2471
+ state: state2,
2472
+ parent: root
2473
+ })
2219
2474
  });
2220
- const data = new Uint8Array(Array.from(Object.values(payload)));
2221
- const encoder = this._readMessage(data);
2222
- if (encoder) {
2223
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
2224
- }
2225
- }
2226
- _readMessage(message) {
2227
- const decoder = decoding.createDecoder(message);
2228
- const encoder = encoding.createEncoder();
2229
- const messageType = decoding.readVarUint(decoder);
2230
- let sendReply = false;
2231
- switch (messageType) {
2232
- case messageAwareness: {
2233
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2475
+ return () => {
2476
+ view?.destroy();
2477
+ setEditor(void 0);
2478
+ };
2479
+ }, [
2480
+ root,
2481
+ model,
2482
+ readonly,
2483
+ editorMode,
2484
+ themeMode
2485
+ ]);
2486
+ const handleKeyUp = useCallback((event) => {
2487
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2488
+ switch (key) {
2489
+ case "Enter": {
2490
+ view?.contentDOM.focus();
2234
2491
  break;
2235
2492
  }
2236
- case messageQueryAwareness: {
2237
- encoding.writeVarUint(encoder, messageAwareness);
2238
- encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2239
- sendReply = true;
2493
+ case "Escape": {
2494
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2240
2495
  break;
2241
2496
  }
2242
- default: {
2243
- console.error("Invalid message:", messageType);
2244
- return encoder;
2245
- }
2246
- }
2247
- if (!sendReply) {
2248
- return null;
2249
2497
  }
2250
- return encoder;
2251
- }
2252
- _handleBeforeUnload() {
2253
- awarenessProtocol.removeAwarenessStates(this._awareness, [
2254
- this._clientId
2255
- ], "window unload");
2256
- }
2257
- };
2258
-
2259
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2260
- var __dxlog_file4 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2261
- var useTextModel = ({ identity, space, text }) => {
2262
- const [model, setModel] = useState2(() => createModel({
2263
- identity,
2264
- space,
2265
- text
2266
- }));
2267
- useEffect3(() => {
2268
- setModel(createModel({
2269
- identity,
2270
- space,
2271
- text
2272
- }));
2273
2498
  }, [
2274
- identity,
2275
- space,
2276
- text
2499
+ view,
2500
+ editorMode
2277
2501
  ]);
2278
- return model;
2279
- };
2280
- var createModel = (options) => {
2281
- const { text } = options;
2282
- if (isActualAutomergeObject(text)) {
2283
- return createAutomergeModel(options);
2284
- } else {
2285
- if (!text?.doc) {
2286
- return void 0;
2287
- }
2288
- return createYjsModel(options);
2502
+ return /* @__PURE__ */ React.createElement("div", {
2503
+ key: model.id,
2504
+ ref: setRoot,
2505
+ tabIndex: 0,
2506
+ ...slots?.root,
2507
+ ...editorMode !== "vim" && tabsterDOMAttribute,
2508
+ onKeyUp: handleKeyUp
2509
+ });
2510
+ });
2511
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2512
+ const { themeMode } = useThemeContext();
2513
+ const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2514
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2515
+ ref: forwardedRef,
2516
+ readonly,
2517
+ extensions: [
2518
+ basicBundle({
2519
+ readonly,
2520
+ themeMode,
2521
+ placeholder: slots?.editor?.placeholder
2522
+ }),
2523
+ ...extensions
2524
+ ],
2525
+ slots,
2526
+ ...props
2527
+ });
2528
+ });
2529
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2530
+ const { themeMode } = useThemeContext();
2531
+ const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2532
+ return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2533
+ ref: forwardedRef,
2534
+ readonly,
2535
+ extensions: [
2536
+ markdownBundle({
2537
+ readonly,
2538
+ themeMode,
2539
+ placeholder: slots?.editor?.placeholder
2540
+ }),
2541
+ ...extensions
2542
+ ],
2543
+ slots,
2544
+ ...props
2545
+ });
2546
+ });
2547
+ var defaultSlots = {
2548
+ root: {
2549
+ className: mx3("p-2", inputSurface)
2289
2550
  }
2290
2551
  };
2291
- var createYjsModel = ({ identity, space, text }) => {
2292
- invariant3(text?.doc && text?.content, void 0, {
2293
- F: __dxlog_file4,
2294
- L: 81,
2295
- S: void 0,
2296
- A: [
2297
- "text?.doc && text?.content",
2298
- ""
2299
- ]
2300
- });
2301
- const provider = space ? new SpaceAwarenessProvider({
2302
- space,
2303
- doc: text.doc,
2304
- channel: `yjs.awareness.${text.id}`
2305
- }) : void 0;
2306
- return {
2307
- id: text.doc.guid,
2308
- content: text.content,
2309
- text: () => text.content.toString(),
2310
- ranges: [],
2311
- extension: yCollab(text.content, provider?.awareness),
2312
- awareness: provider?.awareness,
2313
- peer: identity ? {
2314
- id: identity.identityKey.toHex(),
2315
- name: identity.profile?.displayName
2316
- } : void 0
2317
- };
2552
+ var defaultTextSlots = {
2553
+ ...defaultSlots,
2554
+ editor: {
2555
+ theme: textTheme
2556
+ }
2318
2557
  };
2319
- var createAutomergeModel = ({ identity, text }) => {
2320
- const obj = text;
2321
- const doc = getRawDoc(obj, [
2322
- obj.field
2323
- ]);
2324
- return {
2325
- id: obj.id,
2326
- content: doc,
2327
- text: () => get4(doc.handle.docSync(), doc.path),
2328
- ranges: [],
2329
- extension: automergePlugin(doc.handle, doc.path),
2330
- peer: identity ? {
2331
- id: identity.identityKey.toHex(),
2332
- name: identity.profile?.displayName
2333
- } : void 0
2334
- };
2558
+ var defaultMarkdownSlots = {
2559
+ ...defaultSlots,
2560
+ editor: {
2561
+ theme: markdownTheme
2562
+ }
2335
2563
  };
2336
2564
  export {
2337
2565
  BaseTextEditor,
@@ -2355,25 +2583,24 @@ export {
2355
2583
  defaultSlots,
2356
2584
  defaultTextSlots,
2357
2585
  defaultTheme,
2358
- demo,
2359
- highlight,
2360
- highlightDecorations,
2361
- highlightStateField,
2586
+ hr,
2362
2587
  image,
2363
2588
  link,
2364
- listener2 as listener,
2589
+ listener,
2365
2590
  markdownBundle,
2366
2591
  markdownHighlightStyle,
2367
2592
  markdownTags,
2368
2593
  markdownTagsExtensions,
2369
2594
  markdownTheme,
2370
2595
  mention,
2596
+ mermaid,
2597
+ modelState,
2598
+ setCommentRange,
2371
2599
  table,
2372
2600
  tags2 as tags,
2373
2601
  tasklist,
2374
2602
  textTheme,
2375
- tooltip,
2376
- useHighlights,
2603
+ typewriter,
2377
2604
  useTextModel
2378
2605
  };
2379
2606
  //# sourceMappingURL=index.mjs.map