@dxos/react-ui-editor 0.3.11-next.0fb359e → 0.3.11-next.ee2b64c

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 (199) hide show
  1. package/dist/lib/browser/index.mjs +1883 -1392
  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 +16 -14
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/TextEditor/hooks.stories.d.ts +23 -0
  12. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/Toolbar/Toolbar.d.ts +25 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  15. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +23 -0
  16. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/Toolbar/index.d.ts +2 -0
  18. package/dist/types/src/components/Toolbar/index.d.ts.map +1 -0
  19. package/dist/types/src/components/index.d.ts +1 -0
  20. package/dist/types/src/components/index.d.ts.map +1 -1
  21. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  24. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  25. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  26. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  28. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  29. package/dist/types/src/extensions/automerge/defs.d.ts +9 -11
  30. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  31. package/dist/types/src/extensions/automerge/semaphore.d.ts +8 -12
  32. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  33. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  34. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  35. package/dist/types/src/extensions/awareness.d.ts +2 -2
  36. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  37. package/dist/types/src/extensions/basic.d.ts +4 -5
  38. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  39. package/dist/types/src/extensions/comments.d.ts +37 -20
  40. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  41. package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
  42. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  43. package/dist/types/src/extensions/index.d.ts +2 -8
  44. package/dist/types/src/extensions/index.d.ts.map +1 -1
  45. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  46. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  48. package/dist/types/src/extensions/markdown/formatting.d.ts +13 -0
  49. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  50. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  51. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  52. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  53. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  54. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  55. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  56. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  57. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  58. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  59. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  60. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  61. package/dist/types/src/extensions/outliner.d.ts +4 -0
  62. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  63. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  64. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  65. package/dist/types/src/hooks/defs.d.ts +25 -0
  66. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  67. package/dist/types/src/hooks/index.d.ts +4 -2
  68. package/dist/types/src/hooks/index.d.ts.map +1 -1
  69. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  70. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  71. package/dist/types/src/hooks/useEditorView.d.ts +17 -0
  72. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -0
  73. package/dist/types/src/hooks/useTextEditor.d.ts +39 -0
  74. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useTextModel.d.ts +14 -32
  76. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  77. package/dist/types/src/index.d.ts +3 -0
  78. package/dist/types/src/index.d.ts.map +1 -1
  79. package/dist/types/src/styles/markdown.d.ts +2 -2
  80. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  81. package/dist/types/src/styles/tokens.d.ts +1 -0
  82. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  83. package/dist/types/src/themes/default.d.ts.map +1 -1
  84. package/dist/types/src/util.d.ts +7 -0
  85. package/dist/types/src/util.d.ts.map +1 -0
  86. package/package.json +48 -37
  87. package/src/components/TextEditor/MarkdownEditor.stories.tsx +84 -91
  88. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  89. package/src/components/TextEditor/TextEditor.tsx +135 -95
  90. package/src/components/TextEditor/automerge.stories.tsx +1 -2
  91. package/src/components/TextEditor/hooks.stories.tsx +106 -0
  92. package/src/components/Toolbar/Toolbar.stories.tsx +90 -0
  93. package/src/components/Toolbar/Toolbar.tsx +166 -0
  94. package/src/components/Toolbar/index.ts +5 -0
  95. package/src/components/index.ts +1 -0
  96. package/src/extensions/autocomplete.ts +23 -19
  97. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  98. package/src/extensions/automerge/automerge.test.ts +13 -0
  99. package/src/extensions/automerge/automerge.ts +55 -46
  100. package/src/extensions/automerge/cursor.ts +5 -4
  101. package/src/extensions/automerge/defs.ts +13 -34
  102. package/src/extensions/automerge/semaphore.ts +33 -32
  103. package/src/extensions/automerge/update-automerge.ts +14 -8
  104. package/src/extensions/automerge/update-codemirror.ts +5 -5
  105. package/src/extensions/awareness.ts +46 -49
  106. package/src/extensions/basic.ts +17 -10
  107. package/src/extensions/blast.ts +4 -1
  108. package/src/extensions/comments.ts +332 -267
  109. package/src/extensions/cursor.ts +50 -0
  110. package/src/extensions/index.ts +2 -8
  111. package/src/extensions/markdown/bundle.ts +23 -28
  112. package/src/extensions/markdown/code.ts +150 -0
  113. package/src/extensions/markdown/formatting.ts +188 -0
  114. package/src/extensions/markdown/heading.ts +59 -0
  115. package/src/extensions/markdown/highlight.ts +11 -26
  116. package/src/extensions/markdown/hr.ts +71 -0
  117. package/src/extensions/markdown/image.ts +87 -0
  118. package/src/extensions/markdown/index.ts +8 -0
  119. package/src/extensions/{link.ts → markdown/link.ts} +90 -83
  120. package/src/extensions/{table.ts → markdown/table.ts} +10 -4
  121. package/src/extensions/markdown/tasklist.ts +110 -0
  122. package/src/extensions/mention.ts +9 -2
  123. package/src/extensions/outliner.ts +12 -0
  124. package/src/hooks/awareness-provider.ts +24 -20
  125. package/src/hooks/defs.ts +42 -0
  126. package/src/hooks/index.ts +5 -2
  127. package/src/hooks/useActionHandler.ts +62 -0
  128. package/src/hooks/useEditorView.ts +29 -0
  129. package/src/hooks/useTextEditor.ts +153 -0
  130. package/src/hooks/useTextModel.ts +67 -60
  131. package/src/index.ts +3 -0
  132. package/src/styles/markdown.ts +9 -12
  133. package/src/styles/tokens.ts +3 -0
  134. package/src/themes/default.ts +59 -83
  135. package/src/util.ts +41 -0
  136. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  137. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  138. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +0 -12
  139. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  140. package/dist/types/src/extensions/code.d.ts.map +0 -1
  141. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  142. package/dist/types/src/extensions/image.d.ts.map +0 -1
  143. package/dist/types/src/extensions/link.d.ts.map +0 -1
  144. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  145. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  146. package/dist/types/src/extensions/table.d.ts.map +0 -1
  147. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  148. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  149. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  150. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  151. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  152. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  153. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  154. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  155. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  156. package/dist/types/src/hooks/automerge.d.ts +0 -3
  157. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  158. package/dist/types/src/hooks/yjs.d.ts +0 -27
  159. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  160. package/dist/types/src/testing/index.d.ts +0 -3
  161. package/dist/types/src/testing/index.d.ts.map +0 -1
  162. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  163. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  164. package/dist/types/src/testing/proto/index.d.ts +0 -2
  165. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  166. package/dist/types/src/testing/replicator.d.ts +0 -45
  167. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  168. package/dist/types/src/util/cursor.d.ts.map +0 -1
  169. package/dist/types/src/util/index.d.ts +0 -3
  170. package/dist/types/src/util/index.d.ts.map +0 -1
  171. package/dist/types/src/util/util.d.ts +0 -5
  172. package/dist/types/src/util/util.d.ts.map +0 -1
  173. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  174. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  175. package/src/extensions/code.ts +0 -99
  176. package/src/extensions/hr.ts +0 -74
  177. package/src/extensions/image.ts +0 -69
  178. package/src/extensions/mermaid.ts +0 -11
  179. package/src/extensions/tasklist.ts +0 -121
  180. package/src/extensions/yjs/cursor.ts +0 -21
  181. package/src/extensions/yjs/index.ts +0 -5
  182. package/src/extensions/yjs/yjs.test.ts +0 -35
  183. package/src/extensions/yjs/yjs.ts +0 -16
  184. package/src/hooks/automerge.ts +0 -52
  185. package/src/hooks/yjs.ts +0 -178
  186. package/src/testing/index.ts +0 -6
  187. package/src/testing/proto/gen/schema.ts +0 -49
  188. package/src/testing/proto/index.ts +0 -5
  189. package/src/testing/proto/schema.proto +0 -15
  190. package/src/testing/replicator.ts +0 -184
  191. package/src/util/cursor.ts +0 -29
  192. package/src/util/index.ts +0 -6
  193. package/src/util/util.ts +0 -18
  194. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  195. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  196. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  197. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  198. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  199. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  // packages/ui/react-ui-editor/src/index.ts
2
2
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
3
3
  import { Doc, YText, YXmlFragment } from "@dxos/text-model";
4
+ import { keymap as keymap7 } from "@codemirror/view";
4
5
 
5
6
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
7
  import { tags as tags2 } from "@lezer/highlight";
@@ -8,55 +9,59 @@ import { tags as tags2 } from "@lezer/highlight";
8
9
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
10
  import { EditorState as EditorState2 } from "@codemirror/state";
10
11
  import { EditorView as EditorView13 } from "@codemirror/view";
11
- import { useFocusableGroup } from "@fluentui/react-tabster";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
15
- import { generateName } from "@dxos/display-name";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState } from "react";
15
+ import { log as log2 } from "@dxos/log";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
- import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
17
+ import { focusRing, mx as mx3 } from "@dxos/react-ui-theme";
18
+ import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
18
19
 
19
20
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
21
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
22
+ import { markdownLanguage } from "@codemirror/lang-markdown";
21
23
  import { keymap } from "@codemirror/view";
22
24
  var autocomplete = ({ onSearch }) => {
23
25
  return [
24
26
  // https://codemirror.net/docs/ref/#view.keymap
25
27
  // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
28
+ // TODO(burdon): Set custom keymap.
26
29
  keymap.of(completionKeymap),
27
30
  // https://codemirror.net/examples/autocompletion
28
31
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
29
32
  autocompletion({
30
- // TODO(burdon): Set custom keymap.
33
+ activateOnTyping: true,
34
+ // closeOnBlur: false,
31
35
  // defaultKeymap: false,
32
- // Don't start unless key press.
33
- activateOnTyping: false,
34
- // TODO(burdon): Option to create new page?
35
- // TODO(burdon): Optional decoration via addToOptions
36
- override: [
37
- (context) => {
38
- const match = context.matchBefore(/\w*/);
39
- if (!match || match.from === match.to && !context.explicit) {
40
- return null;
41
- }
42
- return {
43
- from: match.from,
44
- options: onSearch(match.text.toLowerCase())
45
- };
36
+ // TODO(burdon): Styles/fragments.
37
+ tooltipClass: () => "shadow rounded"
38
+ }),
39
+ // TODO(burdon): Option to create new page?
40
+ // TODO(burdon): Optional decoration via addToOptions
41
+ markdownLanguage.data.of({
42
+ autocomplete: (context) => {
43
+ const match = context.matchBefore(/\w*/);
44
+ if (!match || match.from === match.to && !context.explicit) {
45
+ return null;
46
46
  }
47
- ]
47
+ return {
48
+ from: match.from,
49
+ options: onSearch(match.text.toLowerCase())
50
+ };
51
+ }
48
52
  })
49
53
  ];
50
54
  };
51
55
 
52
56
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
53
- import { Facet as Facet2, StateField } from "@codemirror/state";
54
- import { ViewPlugin } from "@codemirror/view";
55
- import { next as A } from "@dxos/automerge/automerge";
57
+ import { invertedEffects } from "@codemirror/commands";
58
+ import { StateField } from "@codemirror/state";
59
+ import { EditorView, ViewPlugin } from "@codemirror/view";
60
+ import { next as A4 } from "@dxos/automerge/automerge";
56
61
 
57
62
  // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
58
63
  import get from "lodash.get";
59
- import { next as automerge } from "@dxos/automerge/automerge";
64
+ import { next as A } from "@dxos/automerge/automerge";
60
65
  var cursorConverter = (handle, path) => ({
61
66
  // TODO(burdon): Handle assoc to associate with a previous character.
62
67
  toCursor: (pos) => {
@@ -68,7 +73,7 @@ var cursorConverter = (handle, path) => ({
68
73
  if (typeof value === "string" && value.length <= pos) {
69
74
  return "end";
70
75
  }
71
- return automerge.getCursor(doc, path.slice(), pos);
76
+ return A.getCursor(doc, path.slice(), pos);
72
77
  },
73
78
  fromCursor: (cursor) => {
74
79
  if (cursor === "") {
@@ -86,26 +91,28 @@ var cursorConverter = (handle, path) => ({
86
91
  return 0;
87
92
  }
88
93
  }
89
- return automerge.getCursorPosition(doc, path.slice(), cursor);
94
+ return A.getCursorPosition(doc, path.slice(), cursor);
90
95
  }
91
96
  });
92
97
 
93
98
  // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
94
99
  import { Annotation, StateEffect } from "@codemirror/state";
95
- var effectType = StateEffect.define({});
96
- var updateHeads = (newHeads) => effectType.of({
100
+ var getPath = (state, field) => state.field(field).path;
101
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
102
+ var updateHeadsEffect = StateEffect.define({});
103
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
97
104
  newHeads
98
105
  });
99
- var getLastHeads = (state, field) => state.field(field).lastHeads;
100
- var getPath = (state, field) => state.field(field).path;
101
- var reconcileAnnotationType = Annotation.define();
102
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
106
+ var reconcileAnnotation = Annotation.define();
107
+ var isReconcile = (tr) => {
108
+ return !!tr.annotation(reconcileAnnotation);
109
+ };
103
110
 
104
111
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
105
- import { next as automerge2 } from "@dxos/automerge/automerge";
112
+ import { next as A3 } from "@dxos/automerge/automerge";
106
113
 
107
114
  // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
108
- import { next as am } from "@dxos/automerge/automerge";
115
+ import { next as A2 } from "@dxos/automerge/automerge";
109
116
  var updateAutomerge = (field, handle, transactions, state) => {
110
117
  const { lastHeads, path } = state.field(field);
111
118
  let hasChanges = false;
@@ -118,11 +125,19 @@ var updateAutomerge = (field, handle, transactions, state) => {
118
125
  return void 0;
119
126
  }
120
127
  const newHeads = handle.changeAt(lastHeads, (doc) => {
128
+ const invertedTransactions = [];
121
129
  for (const tr of transactions) {
122
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
123
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
130
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
131
+ invertedTransactions.push({
132
+ from: fromA,
133
+ del: toA - fromA,
134
+ insert
135
+ });
124
136
  });
125
137
  }
138
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
139
+ A2.splice(doc, path.slice(), from, del, insert.toString());
140
+ });
126
141
  });
127
142
  return newHeads ?? void 0;
128
143
  };
@@ -137,13 +152,13 @@ var updateCodeMirror = (view, selection, target, patches) => {
137
152
  selection = selection.map(changeSet, 1);
138
153
  view.dispatch({
139
154
  changes: changeSet,
140
- annotations: reconcileAnnotationType.of({})
155
+ annotations: reconcileAnnotation.of(false)
141
156
  });
142
157
  }
143
158
  }
144
159
  view.dispatch({
145
160
  selection,
146
- annotations: reconcileAnnotationType.of({})
161
+ annotations: reconcileAnnotation.of(false)
147
162
  });
148
163
  };
149
164
  var handlePatch = (patch, target, state) => {
@@ -164,7 +179,7 @@ var handleInsert = (target, patch) => {
164
179
  if (index == null) {
165
180
  return [];
166
181
  }
167
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
182
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
168
183
  return [
169
184
  {
170
185
  from: index,
@@ -236,89 +251,100 @@ var charPath = (textPath, candidatePath) => {
236
251
 
237
252
  // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
238
253
  var PatchSemaphore = class {
239
- constructor(_field) {
240
- this._field = _field;
254
+ // prettier-ignore
255
+ constructor(_handle, _state) {
256
+ this._handle = _handle;
257
+ this._state = _state;
241
258
  this._inReconcile = false;
242
- this._queue = [];
243
- this.reconcile = (handle, view) => {
244
- if (this._inReconcile) {
245
- return;
246
- }
259
+ }
260
+ reconcile(view) {
261
+ if (!this._inReconcile) {
247
262
  this._inReconcile = true;
248
- const path = getPath(view.state, this._field);
249
- const oldHeads = getLastHeads(view.state, this._field);
250
- let selection = view.state.selection;
251
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
252
- const toInvert = transactions.slice().reverse();
253
- for (const tx of toInvert) {
254
- const inverted = tx.changes.invert(tx.startState.doc);
255
- selection = selection.map(inverted);
256
- view.dispatch({
257
- changes: inverted,
258
- annotations: reconcileAnnotationType.of(true)
259
- });
260
- }
261
- let newHeads = updateAutomerge(this._field, handle, transactions, view.state);
262
- if (newHeads === null || newHeads === void 0) {
263
- newHeads = automerge2.getHeads(handle.docSync());
264
- }
265
- const diff = automerge2.equals(oldHeads, newHeads) ? [] : automerge2.diff(handle.docSync(), oldHeads, newHeads);
266
- updateCodeMirror(view, selection, path, diff);
263
+ this.doReconcile(view);
264
+ this._inReconcile = false;
265
+ }
266
+ }
267
+ doReconcile(view) {
268
+ const path = getPath(view.state, this._state);
269
+ const oldHeads = getLastHeads(view.state, this._state);
270
+ let selection = view.state.selection;
271
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
272
+ const toInvert = transactions.slice().reverse();
273
+ for (const tr of toInvert) {
274
+ const inverted = tr.changes.invert(tr.startState.doc);
275
+ selection = selection.map(inverted);
267
276
  view.dispatch({
268
- effects: updateHeads(newHeads),
269
- annotations: reconcileAnnotationType.of({})
277
+ changes: inverted,
278
+ annotations: reconcileAnnotation.of(true)
270
279
  });
271
- this._inReconcile = false;
272
- };
280
+ }
281
+ let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
282
+ if (newHeads === null || newHeads === void 0) {
283
+ newHeads = A3.getHeads(this._handle.docSync());
284
+ }
285
+ const diff = A3.equals(oldHeads, newHeads) ? [] : A3.diff(this._handle.docSync(), oldHeads, newHeads);
286
+ updateCodeMirror(view, selection, path, diff);
287
+ view.dispatch({
288
+ effects: updateHeads(newHeads),
289
+ annotations: reconcileAnnotation.of(false)
290
+ });
273
291
  }
274
292
  };
275
293
 
276
- // packages/ui/react-ui-editor/src/util/cursor.ts
294
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
277
295
  import { Facet } from "@codemirror/state";
278
- var DEFAULT_CURSOR_CONVERTER = {
296
+ var defaultCursorConverter = {
279
297
  toCursor: (position) => position.toString(),
280
298
  fromCursor: (cursor) => parseInt(cursor)
281
299
  };
282
- var CursorConverter = Facet.define({
283
- combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER
284
- });
285
-
286
- // packages/ui/react-ui-editor/src/util/util.ts
287
- import { log } from "@dxos/log";
288
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/util.ts";
289
- var callbackWrapper = (fn) => (...args) => {
290
- try {
291
- return fn(...args);
292
- } catch (error) {
293
- log.catch(error, void 0, {
294
- F: __dxlog_file,
295
- L: 16,
296
- S: void 0,
297
- C: (f, a) => f(...a)
300
+ var Cursor = class _Cursor {
301
+ static {
302
+ this.converter = Facet.define({
303
+ combine: (providers) => providers[0] ?? defaultCursorConverter
298
304
  });
299
305
  }
306
+ static {
307
+ this.getCursorFromRange = (state, range) => {
308
+ const cursorConverter2 = state.facet(_Cursor.converter);
309
+ const from = cursorConverter2.toCursor(range.from);
310
+ const to = cursorConverter2.toCursor(range.to, -1);
311
+ return [
312
+ from,
313
+ to
314
+ ].join(":");
315
+ };
316
+ }
317
+ static {
318
+ this.getRangeFromCursor = (state, cursor) => {
319
+ const cursorConverter2 = state.facet(_Cursor.converter);
320
+ const parts = cursor.split(":");
321
+ const from = cursorConverter2.fromCursor(parts[0]);
322
+ const to = cursorConverter2.fromCursor(parts[1]);
323
+ return from !== void 0 && to !== void 0 ? {
324
+ from,
325
+ to
326
+ } : void 0;
327
+ };
328
+ }
300
329
  };
301
330
 
302
331
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
303
- var semaphoreFacet = Facet2.define({
304
- combine: (values) => values.at(-1)
305
- });
306
- var automerge3 = ({ handle, path }) => {
307
- const stateField = StateField.define({
332
+ var automerge = ({ handle, path }) => {
333
+ const syncState = StateField.define({
308
334
  create: () => ({
309
- lastHeads: A.getHeads(handle.docSync()),
310
- unreconciledTransactions: [],
311
- path: path.slice()
335
+ path: path.slice(),
336
+ lastHeads: A4.getHeads(handle.docSync()),
337
+ unreconciledTransactions: []
312
338
  }),
313
339
  update: (value, tr) => {
314
340
  const result = {
341
+ path: path.slice(),
315
342
  lastHeads: value.lastHeads,
316
- unreconciledTransactions: value.unreconciledTransactions.slice(),
317
- path: path.slice()
343
+ unreconciledTransactions: value.unreconciledTransactions.slice()
318
344
  };
319
345
  let clearUnreconciled = false;
320
346
  for (const effect of tr.effects) {
321
- if (effect.is(effectType)) {
347
+ if (effect.is(updateHeadsEffect)) {
322
348
  result.lastHeads = effect.value.newHeads;
323
349
  clearUnreconciled = true;
324
350
  }
@@ -326,46 +352,53 @@ var automerge3 = ({ handle, path }) => {
326
352
  if (clearUnreconciled) {
327
353
  result.unreconciledTransactions = [];
328
354
  } else {
329
- if (!isReconcileTx(tr)) {
355
+ if (!isReconcile(tr)) {
330
356
  result.unreconciledTransactions.push(tr);
331
357
  }
332
358
  }
333
359
  return result;
334
360
  }
335
361
  });
336
- const semaphore = new PatchSemaphore(stateField);
337
- const viewPlugin = ViewPlugin.fromClass(class AutomergeCodemirrorViewPlugin {
338
- constructor(_view) {
339
- this._view = _view;
340
- this._handleChange = () => {
341
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
342
- };
343
- handle.addListener("change", this._handleChange);
344
- }
345
- update(update4) {
346
- if (update4.transactions.length > 0 && update4.transactions.some((tr) => !isReconcileTx(tr))) {
347
- queueMicrotask(() => {
348
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
362
+ const semaphore = new PatchSemaphore(handle, syncState);
363
+ return [
364
+ Cursor.converter.of(cursorConverter(handle, path)),
365
+ syncState,
366
+ // Reconcile external updates.
367
+ ViewPlugin.fromClass(class {
368
+ constructor(_view) {
369
+ this._view = _view;
370
+ handle.addListener("change", this._handleChange.bind(this));
371
+ }
372
+ destroy() {
373
+ handle.removeListener("change", this._handleChange.bind(this));
374
+ }
375
+ _handleChange() {
376
+ semaphore.reconcile(this._view);
377
+ }
378
+ }),
379
+ // Reconcile local updates.
380
+ EditorView.updateListener.of(({ view, changes }) => {
381
+ if (!changes.empty) {
382
+ semaphore.reconcile(view);
383
+ }
384
+ }),
385
+ // TODO(burdon): Record undo transactions.
386
+ // See https://github.com/yjs/y-codemirror.next/tree/main
387
+ // https://codemirror.net/examples/inverted-effect
388
+ invertedEffects.of((tr) => {
389
+ const effects = [];
390
+ if (!tr.changes.empty) {
391
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
349
392
  });
350
393
  }
351
- }
352
- destroy() {
353
- handle.addListener("change", this._handleChange);
354
- }
355
- });
356
- return {
357
- extension: [
358
- CursorConverter.of(cursorConverter(handle, path)),
359
- semaphoreFacet.of(semaphore),
360
- stateField,
361
- viewPlugin
362
- ]
363
- };
394
+ return effects;
395
+ })
396
+ ];
364
397
  };
365
398
 
366
399
  // packages/ui/react-ui-editor/src/extensions/awareness.ts
367
- import { Annotation as Annotation2, Facet as Facet3, RangeSet } from "@codemirror/state";
368
- import { Decoration, EditorView, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
400
+ import { Annotation as Annotation2, Facet as Facet2, RangeSet } from "@codemirror/state";
401
+ import { Decoration, EditorView as EditorView2, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
369
402
  import { Event } from "@dxos/async";
370
403
  import { Context } from "@dxos/context";
371
404
  var dummyProvider = {
@@ -374,17 +407,17 @@ var dummyProvider = {
374
407
  },
375
408
  close: () => {
376
409
  },
410
+ getRemoteStates: () => [],
377
411
  update: () => {
378
- },
379
- getRemoteStates: () => []
412
+ }
380
413
  };
381
- var AwarenessProvider = Facet3.define({
414
+ var awarenessProvider = Facet2.define({
382
415
  combine: (providers) => providers[0] ?? dummyProvider
383
416
  });
384
417
  var RemoteSelectionChangedAnnotation = Annotation2.define();
385
418
  var awareness = (provider = dummyProvider) => {
386
419
  return [
387
- AwarenessProvider.of(provider),
420
+ awarenessProvider.of(provider),
388
421
  ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
389
422
  decorations: (value) => value.decorations
390
423
  }),
@@ -397,8 +430,8 @@ var RemoteSelectionsDecorator = class {
397
430
  this._ctx = new Context();
398
431
  this._lastAnchor = void 0;
399
432
  this._lastHead = void 0;
400
- this._cursorConverter = view.state.facet(CursorConverter);
401
- this._provider = view.state.facet(AwarenessProvider);
433
+ this._cursorConverter = view.state.facet(Cursor.converter);
434
+ this._provider = view.state.facet(awarenessProvider);
402
435
  this._provider.open();
403
436
  this._provider.remoteStateChange.on(this._ctx, () => {
404
437
  view.dispatch({
@@ -412,13 +445,13 @@ var RemoteSelectionsDecorator = class {
412
445
  void this._ctx.dispose();
413
446
  this._provider.close();
414
447
  }
415
- update(update4) {
416
- this._updateLocalSelection(update4);
417
- this._updateRemoteSelections(update4);
448
+ update(update2) {
449
+ this._updateLocalSelection(update2);
450
+ this._updateRemoteSelections(update2);
418
451
  }
419
- _updateLocalSelection(update4) {
420
- const hasFocus = update4.view.hasFocus && update4.view.dom.ownerDocument.hasFocus();
421
- const { anchor = void 0, head = void 0 } = hasFocus ? update4.state.selection.main : {};
452
+ _updateLocalSelection(update2) {
453
+ const hasFocus = update2.view.hasFocus && update2.view.dom.ownerDocument.hasFocus();
454
+ const { anchor = void 0, head = void 0 } = hasFocus ? update2.state.selection.main : {};
422
455
  if (this._lastAnchor === anchor && this._lastHead === head) {
423
456
  return;
424
457
  }
@@ -429,7 +462,7 @@ var RemoteSelectionsDecorator = class {
429
462
  head: this._cursorConverter.toCursor(head)
430
463
  } : void 0);
431
464
  }
432
- _updateRemoteSelections(update4) {
465
+ _updateRemoteSelections(update2) {
433
466
  const decorations = [];
434
467
  const awarenessStates = this._provider.getRemoteStates();
435
468
  for (const state of awarenessStates) {
@@ -438,10 +471,10 @@ var RemoteSelectionsDecorator = class {
438
471
  if (anchor == null || head == null) {
439
472
  continue;
440
473
  }
441
- const start = Math.min(Math.min(anchor, head), update4.view.state.doc.length);
442
- const end = Math.min(Math.max(anchor, head), update4.view.state.doc.length);
443
- const startLine = update4.view.state.doc.lineAt(start);
444
- const endLine = update4.view.state.doc.lineAt(end);
474
+ const start = Math.min(Math.min(anchor, head), update2.view.state.doc.length);
475
+ const end = Math.min(Math.max(anchor, head), update2.view.state.doc.length);
476
+ const startLine = update2.view.state.doc.lineAt(start);
477
+ const endLine = update2.view.state.doc.lineAt(end);
445
478
  const color = state.info.color ?? "#30bced";
446
479
  const lightColor = state.info.lightColor ?? color + "33";
447
480
  if (startLine.number === endLine.number) {
@@ -452,7 +485,7 @@ var RemoteSelectionsDecorator = class {
452
485
  attributes: {
453
486
  style: `background-color: ${lightColor}`
454
487
  },
455
- class: "cm-ySelection"
488
+ class: "cm-collab-selection"
456
489
  })
457
490
  });
458
491
  } else {
@@ -463,7 +496,7 @@ var RemoteSelectionsDecorator = class {
463
496
  attributes: {
464
497
  style: `background-color: ${color}`
465
498
  },
466
- class: "cm-ySelection"
499
+ class: "cm-collab-selection"
467
500
  })
468
501
  });
469
502
  decorations.push({
@@ -473,18 +506,18 @@ var RemoteSelectionsDecorator = class {
473
506
  attributes: {
474
507
  style: `background-color: ${color}`
475
508
  },
476
- class: "cm-ySelection"
509
+ class: "cm-collab-selection"
477
510
  })
478
511
  });
479
512
  for (let i = startLine.number + 1; i < endLine.number; i++) {
480
- const linePos = update4.view.state.doc.line(i).from;
513
+ const linePos = update2.view.state.doc.line(i).from;
481
514
  decorations.push({
482
515
  from: linePos,
483
516
  to: linePos,
484
517
  value: Decoration.line({
485
518
  attributes: {
486
519
  style: `background-color: ${color}`,
487
- class: "cm-yLineSelection"
520
+ class: "cm-collab-selectionLine"
488
521
  }
489
522
  })
490
523
  });
@@ -504,23 +537,21 @@ var RemoteSelectionsDecorator = class {
504
537
  }
505
538
  };
506
539
  var RemoteCaretWidget = class extends WidgetType {
507
- constructor(name, color) {
540
+ constructor(_name, _color) {
508
541
  super();
509
- this.name = name;
510
- this.color = color;
511
- this.name = name;
512
- this.color = color;
542
+ this._name = _name;
543
+ this._color = _color;
513
544
  }
514
545
  toDOM() {
515
546
  const span = document.createElement("span");
516
- span.className = "cm-ySelectionCaret";
517
- span.style.backgroundColor = this.color;
518
- span.style.borderColor = this.color;
547
+ span.className = "cm-collab-selectionCaret";
548
+ span.style.backgroundColor = this._color;
549
+ span.style.borderColor = this._color;
519
550
  const dot = document.createElement("div");
520
- dot.className = "cm-ySelectionCaretDot";
551
+ dot.className = "cm-collab-selectionCaretDot";
521
552
  const info = document.createElement("div");
522
- info.className = "cm-ySelectionInfo";
523
- info.innerText = this.name;
553
+ info.className = "cm-collab-selectionInfo";
554
+ info.innerText = this._name;
524
555
  span.appendChild(document.createTextNode("\u2060"));
525
556
  span.appendChild(dot);
526
557
  span.appendChild(document.createTextNode("\u2060"));
@@ -528,15 +559,12 @@ var RemoteCaretWidget = class extends WidgetType {
528
559
  span.appendChild(document.createTextNode("\u2060"));
529
560
  return span;
530
561
  }
531
- eq(widget) {
532
- return widget.color === this.color;
533
- }
534
- compare(widget) {
535
- return widget.color === this.color;
536
- }
537
562
  updateDOM() {
538
563
  return false;
539
564
  }
565
+ eq(widget) {
566
+ return widget._color === this._color;
567
+ }
540
568
  get estimatedHeight() {
541
569
  return -1;
542
570
  }
@@ -544,58 +572,60 @@ var RemoteCaretWidget = class extends WidgetType {
544
572
  return true;
545
573
  }
546
574
  };
547
- var styles = EditorView.baseTheme({
548
- ".cm-ySelection": {},
549
- ".cm-yLineSelection": {
575
+ var styles = EditorView2.baseTheme({
576
+ ".cm-collab-selection": {},
577
+ ".cm-collab-selectionLine": {
550
578
  padding: 0,
551
579
  margin: "0px 2px 0px 4px"
552
580
  },
553
- ".cm-ySelectionCaret": {
581
+ ".cm-collab-selectionCaret": {
554
582
  position: "relative",
555
583
  borderLeft: "1px solid black",
556
584
  borderRight: "1px solid black",
557
585
  marginLeft: "-1px",
558
586
  marginRight: "-1px",
559
587
  boxSizing: "border-box",
560
- display: "inline"
588
+ display: "inline",
589
+ cursor: "pointer"
561
590
  },
562
- ".cm-ySelectionCaretDot": {
591
+ ".cm-collab-selectionCaretDot": {
563
592
  borderRadius: "50%",
564
593
  position: "absolute",
565
- width: ".4em",
566
- height: ".4em",
567
- top: "-.2em",
568
- left: "-.2em",
594
+ width: ".5em",
595
+ height: ".5em",
596
+ top: "-.25em",
597
+ left: "-.25em",
569
598
  backgroundColor: "inherit",
570
599
  transition: "transform .3s ease-in-out",
571
600
  boxSizing: "border-box"
572
601
  },
573
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
574
- transformOrigin: "bottom center",
575
- transform: "scale(0)"
602
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
603
+ transform: "scale(0)",
604
+ transformOrigin: "center"
576
605
  },
577
- ".cm-ySelectionInfo": {
606
+ ".cm-collab-selectionInfo": {
578
607
  position: "absolute",
579
- top: "-1.05em",
580
- left: "-1px",
608
+ transform: "translate(-50%, 0)",
609
+ top: "-20px",
610
+ left: 0,
581
611
  fontSize: ".75em",
582
- fontFamily: "serif",
612
+ fontFamily: "sans-serif",
583
613
  fontStyle: "normal",
584
614
  fontWeight: "normal",
585
615
  lineHeight: "normal",
586
616
  userSelect: "none",
587
617
  color: "white",
588
- paddingLeft: "2px",
589
- paddingRight: "2px",
618
+ padding: "2px",
590
619
  zIndex: 101,
591
620
  transition: "opacity .3s ease-in-out",
592
621
  backgroundColor: "inherit",
593
- // these should be separate
622
+ borderRadius: "2px",
623
+ // These should be separate.
594
624
  opacity: 0,
595
625
  transitionDelay: "0s",
596
626
  whiteSpace: "nowrap"
597
627
  },
598
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
628
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
599
629
  opacity: 1,
600
630
  transitionDelay: "0s"
601
631
  }
@@ -603,26 +633,29 @@ var styles = EditorView.baseTheme({
603
633
 
604
634
  // packages/ui/react-ui-editor/src/extensions/basic.ts
605
635
  import { closeBrackets } from "@codemirror/autocomplete";
636
+ import { history } from "@codemirror/commands";
606
637
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
607
638
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
608
- import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
609
- var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
610
- EditorView2.lineWrapping,
611
- // TODO(burdon): Compare with minimalSetup.
639
+ import { drawSelection, EditorView as EditorView3, highlightActiveLine, placeholder } from "@codemirror/view";
640
+ import { isNotFalsy } from "@dxos/util";
641
+ var createBasicBundle = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => [
642
+ lineWrapping && EditorView3.lineWrapping,
612
643
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
613
644
  bracketMatching(),
614
645
  closeBrackets(),
615
646
  drawSelection(),
647
+ highlightActiveLine(),
648
+ history(),
616
649
  _placeholder && placeholder(_placeholder),
617
- // TODO(burdon): Is this required?
650
+ // https://github.com/codemirror/theme-one-dark
618
651
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
619
- ].filter(Boolean);
652
+ ].filter(isNotFalsy);
620
653
 
621
654
  // packages/ui/react-ui-editor/src/extensions/blast.ts
622
- import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
655
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
623
656
  import defaultsDeep from "lodash.defaultsdeep";
624
657
  import { invariant } from "@dxos/invariant";
625
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
658
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
626
659
  var defaultOptions = {
627
660
  effect: 2,
628
661
  maxParticles: 200,
@@ -655,10 +688,10 @@ var blast = (options = defaultOptions) => {
655
688
  const getPoint = (view, pos) => {
656
689
  const { left: x = 0, top: y = 0 } = view.coordsForChar(pos) ?? {};
657
690
  const element = document.elementFromPoint(x, y);
658
- const { offsetLeft: left = 0, offsetTop: top = 0 } = view.scrollDOM.parentElement ?? {};
691
+ const { offsetLeft: left = 0, offsetTop: top2 = 0 } = view.scrollDOM.parentElement ?? {};
659
692
  const point = {
660
693
  x: x - left,
661
- y: y - top
694
+ y: y - top2
662
695
  };
663
696
  return {
664
697
  element,
@@ -667,12 +700,12 @@ var blast = (options = defaultOptions) => {
667
700
  };
668
701
  return [
669
702
  // Cursor moved.
670
- EditorView3.updateListener.of((update4) => {
671
- if (blaster?.node !== update4.view.scrollDOM) {
703
+ EditorView4.updateListener.of((update2) => {
704
+ if (blaster?.node !== update2.view.scrollDOM) {
672
705
  if (blaster) {
673
706
  blaster.destroy();
674
707
  }
675
- blaster = new Blaster(update4.view.scrollDOM, defaultsDeep({
708
+ blaster = new Blaster(update2.view.scrollDOM, defaultsDeep({
676
709
  particleGravity: 0.2,
677
710
  particleShrinkRate: 0.995,
678
711
  color: () => [
@@ -686,10 +719,10 @@ var blast = (options = defaultOptions) => {
686
719
  } else {
687
720
  blaster.resize();
688
721
  }
689
- const current = update4.state.selection.main.head;
722
+ const current = update2.state.selection.main.head;
690
723
  if (current !== last) {
691
724
  last = current;
692
- const { element, point } = getPoint(update4.view, current - 1);
725
+ const { element, point } = getPoint(update2.view, current - 1);
693
726
  if (element && point) {
694
727
  blaster.spawn({
695
728
  element,
@@ -749,8 +782,8 @@ var Blaster = class {
749
782
  }
750
783
  initialize() {
751
784
  invariant(!this._canvas && !this._ctx, void 0, {
752
- F: __dxlog_file2,
753
- L: 138,
785
+ F: __dxlog_file,
786
+ L: 141,
754
787
  S: this,
755
788
  A: [
756
789
  "!this._canvas && !this._ctx",
@@ -786,8 +819,8 @@ var Blaster = class {
786
819
  }
787
820
  start() {
788
821
  invariant(this._canvas && this._ctx, void 0, {
789
- F: __dxlog_file2,
790
- L: 177,
822
+ F: __dxlog_file,
823
+ L: 180,
791
824
  S: this,
792
825
  A: [
793
826
  "this._canvas && this._ctx",
@@ -928,10 +961,14 @@ var random = (min, max) => {
928
961
  return min + ~~(Math.random() * (max - min + 1));
929
962
  };
930
963
 
931
- // packages/ui/react-ui-editor/src/extensions/code.ts
932
- import { ViewPlugin as ViewPlugin3, EditorView as EditorView4, Decoration as Decoration2 } from "@codemirror/view";
933
- import get2 from "lodash.get";
934
- import { mx as mx2 } from "@dxos/react-ui-theme";
964
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
965
+ import { invertedEffects as invertedEffects2 } from "@codemirror/commands";
966
+ import { Facet as Facet3, StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
967
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration2, EditorView as EditorView5 } from "@codemirror/view";
968
+ import sortBy from "lodash.sortby";
969
+ import { useEffect } from "react";
970
+ import { debounce } from "@dxos/async";
971
+ import { nonNullable } from "@dxos/util";
935
972
 
936
973
  // packages/ui/react-ui-editor/src/styles/cursors.ts
937
974
  import * as random2 from "lib0/random";
@@ -974,34 +1011,15 @@ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
974
1011
  // packages/ui/react-ui-editor/src/styles/markdown.ts
975
1012
  import { mx } from "@dxos/react-ui-theme";
976
1013
  var headings = {
977
- 1: [
978
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
979
- "-ml-[10px]"
980
- ],
981
- 2: [
982
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
983
- "-ml-[8px]"
984
- ],
985
- 3: [
986
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
987
- "-ml-[7px]"
988
- ],
989
- 4: [
990
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
991
- "-ml-[6px]"
992
- ],
993
- 5: [
994
- "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
995
- "-ml-[5px]"
996
- ],
997
- 6: [
998
- "mbs-4 mbe-2 font-medium text-inherit no-underline",
999
- "-ml-[4px]"
1000
- ]
1001
- };
1002
- var heading = (level, readonly) => {
1003
- const [style, offset] = headings[level];
1004
- return mx(style, readonly && offset);
1014
+ 1: "mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl",
1015
+ 2: "mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl",
1016
+ 3: "mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl",
1017
+ 4: "mbs-4 mbe-2 font-medium text-inherit no-underline text-xl",
1018
+ 5: "mbs-4 mbe-2 font-medium text-inherit no-underline text-lg",
1019
+ 6: "mbs-4 mbe-2 font-medium text-inherit no-underline"
1020
+ };
1021
+ var heading = (level) => {
1022
+ return headings[level];
1005
1023
  };
1006
1024
  var light = "text-neutral-200 dark:text-neutral-800";
1007
1025
  var mark = mx("!font-normal !no-underline !text-inherit opacity-40", light);
@@ -1014,158 +1032,128 @@ var inlineUrl = mx(code, "px-1");
1014
1032
  var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
1015
1033
 
1016
1034
  // packages/ui/react-ui-editor/src/styles/tokens.ts
1035
+ import get2 from "lodash.get";
1017
1036
  import { tailwindConfig } from "@dxos/react-ui-theme";
1018
1037
  var tokens = tailwindConfig({}).theme;
1038
+ var getToken = (path, defaultValue = void 0) => get2(tokens, path, defaultValue);
1019
1039
 
1020
- // packages/ui/react-ui-editor/src/extensions/code.ts
1021
- var CODE_REGEX = /```[\s\S]*?```/gs;
1022
- var styles2 = EditorView4.baseTheme({
1023
- "& .cm-codeblock": {
1024
- fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
1025
- },
1026
- '&light [aria-readonly="true"] .cm-codeblock': {
1027
- background: get2(tokens, "extend.colors.neutral.50")
1028
- },
1029
- '&dark [aria-readonly="true"] .cm-codeblock': {
1030
- background: get2(tokens, "extend.colors.neutral.850")
1031
- },
1032
- '& [aria-readonly="true"] .cm-codeblock': {
1033
- display: "block",
1034
- paddingInline: "4px !important"
1035
- },
1036
- '& [aria-readonly="true"] .cm-codeblock-first': {
1037
- paddingTop: "4px !important",
1038
- borderTopLeftRadius: "4px",
1039
- borderTopRightRadius: "4px"
1040
- },
1041
- '& [aria-readonly="true"] .cm-codeblock-last': {
1042
- paddingBottom: "4px !important",
1043
- borderBottomLeftRadius: "4px",
1044
- borderBottomRightRadius: "4px"
1040
+ // packages/ui/react-ui-editor/src/util.ts
1041
+ import { log } from "@dxos/log";
1042
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1043
+ var callbackWrapper = (fn) => (...args) => {
1044
+ try {
1045
+ return fn(...args);
1046
+ } catch (err) {
1047
+ log.catch(err, void 0, {
1048
+ F: __dxlog_file2,
1049
+ L: 18,
1050
+ S: void 0,
1051
+ C: (f, a) => f(...a)
1052
+ });
1045
1053
  }
1046
- });
1047
- var getLineRange = (lines, from, to) => {
1048
- const start = lines.findIndex((line) => line.from >= from);
1049
- const end = lines.findIndex((line) => line.to >= to);
1050
- return [
1051
- start,
1052
- end
1053
- ];
1054
1054
  };
1055
- var code2 = () => {
1056
- const getDecorations = (view) => {
1057
- const decorations = [];
1058
- if (view.state.readOnly) {
1059
- const text = view.state.doc.sliceString(0);
1060
- const matches = text.matchAll(CODE_REGEX);
1061
- const blocks = view.viewportLineBlocks;
1062
- for (const match of matches) {
1063
- const range = getLineRange(blocks, match.index, match.index + match[0].length);
1064
- for (let i = range[0]; i <= range[1]; i++) {
1065
- const block = blocks[i];
1066
- decorations.push(Decoration2.line({
1067
- class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1068
- }).range(block.from));
1069
- }
1070
- }
1055
+ var logChanges = (trs) => {
1056
+ const changes = trs.flatMap((tr) => {
1057
+ if (tr.changes.empty) {
1058
+ return void 0;
1071
1059
  }
1072
- return Decoration2.set(decorations);
1073
- };
1074
- return [
1075
- ViewPlugin3.fromClass(class {
1076
- constructor(view) {
1077
- this.decorations = getDecorations(view);
1078
- }
1079
- update(update4) {
1080
- if (update4.docChanged || update4.viewportChanged) {
1081
- this.decorations = getDecorations(update4.view);
1082
- }
1083
- }
1084
- }, {
1085
- decorations: (value) => value.decorations
1086
- }),
1087
- styles2
1088
- ];
1060
+ const changes2 = [];
1061
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
1062
+ fromA,
1063
+ toA,
1064
+ fromB,
1065
+ toB,
1066
+ inserted: inserted.toString()
1067
+ })));
1068
+ return changes2;
1069
+ }).filter(Boolean);
1070
+ if (changes.length) {
1071
+ console.log("changes", changes);
1072
+ }
1089
1073
  };
1090
1074
 
1091
1075
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1092
- import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1093
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1094
- import get3 from "lodash.get";
1095
- import sortBy from "lodash.sortby";
1096
- import { debounce } from "@dxos/async";
1097
- import { invariant as invariant2 } from "@dxos/invariant";
1098
- import { log as log2 } from "@dxos/log";
1099
- import { nonNullable } from "@dxos/util";
1100
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1101
- var styles3 = EditorView5.baseTheme({
1102
- "& .cm-bookmark": {
1103
- cursor: "pointer",
1104
- margin: "4px",
1105
- padding: "4px",
1106
- backgroundColor: "yellow"
1076
+ var styles2 = EditorView5.baseTheme({
1077
+ "&light .cm-comment, &light .cm-comment-current": {
1078
+ mixBlendMode: "darken"
1107
1079
  },
1108
- "& .cm-bookmark-selected": {
1109
- backgroundColor: "orange"
1080
+ "&dark .cm-comment, &dark .cm-comment-current": {
1081
+ mixBlendMode: "plus-lighter"
1110
1082
  },
1111
- "& .cm-comment": {
1112
- backgroundColor: get3(tokens, "extend.colors.yellow.50")
1083
+ "&light .cm-comment": {
1084
+ backgroundColor: getToken("extend.colors.yellow.50")
1113
1085
  },
1114
- "& .cm-comment-active": {
1115
- backgroundColor: get3(tokens, "extend.colors.yellow.100")
1086
+ "&light .cm-comment-current": {
1087
+ backgroundColor: getToken("extend.colors.yellow.100")
1088
+ },
1089
+ "&dark .cm-comment": {
1090
+ color: getToken("extend.colors.yellow.50"),
1091
+ backgroundColor: getToken("extend.colors.yellow.900")
1092
+ },
1093
+ "&dark .cm-comment-current": {
1094
+ color: getToken("extend.colors.yellow.100"),
1095
+ backgroundColor: getToken("extend.colors.yellow.950")
1116
1096
  }
1117
1097
  });
1118
- var marks = {
1119
- highlight: Decoration3.mark({
1120
- class: "cm-comment"
1121
- }),
1122
- highlightActive: Decoration3.mark({
1123
- class: "cm-comment-active"
1124
- })
1125
- };
1126
- var setFocus = (view, thread) => {
1127
- const range = view.state.field(commentsStateField).ranges.find((range2) => range2.id === thread);
1128
- if (!range) {
1098
+ var commentMark = Decoration2.mark({
1099
+ class: "cm-comment"
1100
+ });
1101
+ var commentCurrentMark = Decoration2.mark({
1102
+ class: "cm-comment-current"
1103
+ });
1104
+ var focusComment = (view, id, center = true) => {
1105
+ const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
1106
+ if (!comment?.comment.cursor) {
1129
1107
  return;
1130
1108
  }
1131
- view.dispatch({
1132
- effects: setSelection.of({
1133
- active: thread
1134
- }),
1135
- selection: {
1136
- anchor: range.from
1137
- },
1138
- scrollIntoView: true
1139
- });
1109
+ const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
1110
+ if (range) {
1111
+ view.dispatch({
1112
+ selection: {
1113
+ anchor: range.from
1114
+ },
1115
+ effects: [
1116
+ //
1117
+ EditorView5.scrollIntoView(range.from, center ? {
1118
+ y: "center"
1119
+ } : void 0),
1120
+ setSelection.of({
1121
+ current: id
1122
+ })
1123
+ ]
1124
+ });
1125
+ }
1140
1126
  };
1141
- var setCommentRange = StateEffect2.define();
1127
+ var setComments = StateEffect2.define();
1142
1128
  var setSelection = StateEffect2.define();
1143
1129
  var setCommentState = StateEffect2.define();
1144
- var commentsStateField = StateField2.define({
1130
+ var commentsState = StateField2.define({
1145
1131
  create: () => ({
1146
- ranges: []
1132
+ comments: [],
1133
+ selection: {}
1147
1134
  }),
1148
1135
  update: (value, tr) => {
1149
- const cursorConverter3 = tr.state.facet(CursorConverter);
1150
1136
  for (const effect of tr.effects) {
1151
1137
  if (effect.is(setSelection)) {
1152
1138
  return {
1153
1139
  ...value,
1154
- ...effect.value
1140
+ selection: effect.value
1155
1141
  };
1156
1142
  }
1157
- if (effect.is(setCommentRange)) {
1158
- const { comments: comments2 } = effect.value;
1159
- const ranges = comments2.map((comment) => {
1160
- const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1143
+ if (effect.is(setComments)) {
1144
+ const comments2 = effect.value.map((comment) => {
1145
+ if (!comment.cursor) {
1146
+ return void 0;
1147
+ }
1148
+ const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
1161
1149
  return range && {
1162
- ...comment,
1163
- ...range
1150
+ comment,
1151
+ range
1164
1152
  };
1165
1153
  }).filter(nonNullable);
1166
1154
  return {
1167
1155
  ...value,
1168
- ranges
1156
+ comments: comments2
1169
1157
  };
1170
1158
  }
1171
1159
  if (effect.is(setCommentState)) {
@@ -1175,143 +1163,174 @@ var commentsStateField = StateField2.define({
1175
1163
  return value;
1176
1164
  }
1177
1165
  });
1178
- var highlightDecorations = EditorView5.decorations.compute([
1179
- commentsStateField
1166
+ var commentsDecorations = EditorView5.decorations.compute([
1167
+ commentsState
1180
1168
  ], (state) => {
1181
- const { active, ranges } = state.field(commentsStateField);
1182
- const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1183
- if (selection.from === selection.to) {
1169
+ const { selection: { current }, comments: comments2 } = state.field(commentsState);
1170
+ const decorations = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
1171
+ const range = comment.range;
1172
+ if (!range || range.from === range.to) {
1173
+ console.warn("Invalid range:", range);
1184
1174
  return void 0;
1185
1175
  }
1186
- const range = {
1187
- from: selection.from,
1188
- to: selection.to
1189
- };
1190
- if (selection.id === active) {
1191
- return marks.highlightActive.range(range.from, range.to);
1176
+ if (comment.comment.id === current) {
1177
+ return commentCurrentMark.range(range.from, range.to);
1192
1178
  } else {
1193
- return marks.highlight.range(range.from, range.to);
1179
+ return commentMark.range(range.from, range.to);
1194
1180
  }
1195
- }).filter(nonNullable) ?? [];
1196
- return Decoration3.set(decorations);
1181
+ }).filter(nonNullable);
1182
+ return Decoration2.set(decorations);
1197
1183
  });
1198
- var BookmarkWidget = class extends WidgetType2 {
1199
- constructor(_anchor, _id, _handleClick) {
1200
- super();
1201
- this._anchor = _anchor;
1202
- this._id = _id;
1203
- this._handleClick = _handleClick;
1204
- invariant2(this._id, void 0, {
1205
- F: __dxlog_file3,
1206
- L: 169,
1207
- S: this,
1208
- A: [
1209
- "this._id",
1210
- ""
1211
- ]
1212
- });
1213
- }
1214
- eq(other) {
1215
- return this._anchor === other._anchor && this._id === other._id;
1216
- }
1217
- toDOM() {
1218
- const span = document.createElement("span");
1219
- span.className = "cm-bookmark";
1220
- span.textContent = "\u203B";
1221
- if (this._handleClick) {
1222
- span.onclick = () => this._handleClick();
1223
- }
1224
- return span;
1225
- }
1226
- };
1227
- var comments = (options = {}) => {
1228
- const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1229
- const createCommentThread = (view) => {
1230
- const cursorConverter3 = view.state.facet(CursorConverter);
1231
- invariant2(options.onCreate, void 0, {
1232
- F: __dxlog_file3,
1233
- L: 225,
1234
- S: void 0,
1235
- A: [
1236
- "options.onCreate",
1237
- ""
1238
- ]
1239
- });
1240
- const { head, from, to } = view.state.selection.main;
1241
- if (from === to) {
1242
- return false;
1184
+ var trackPastedComments = (onUpdate) => {
1185
+ let tracked = null;
1186
+ const handleTrack = (event, view) => {
1187
+ const comments2 = view.state.field(commentsState);
1188
+ const { main } = view.state.selection;
1189
+ const selectedRanges = comments2.comments.filter(({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to);
1190
+ if (!selectedRanges.length) {
1191
+ tracked = null;
1192
+ } else {
1193
+ tracked = {
1194
+ text: view.state.doc.slice(main.from, main.to),
1195
+ comments: selectedRanges.map(({ comment, range }) => ({
1196
+ id: comment.id,
1197
+ from: range.from - main.from,
1198
+ to: range.to - main.from
1199
+ }))
1200
+ };
1243
1201
  }
1244
- const relPos = getCursorFromRange(cursorConverter3, {
1245
- from,
1246
- to
1247
- });
1248
- if (relPos) {
1249
- const id = callbackWrapper(options.onCreate)(relPos);
1250
- if (id) {
1251
- view.dispatch({
1252
- effects: setSelection.of({
1253
- active: id
1254
- }),
1255
- selection: {
1256
- anchor: from
1202
+ };
1203
+ return [
1204
+ EditorView5.domEventHandlers({
1205
+ cut: handleTrack,
1206
+ copy: handleTrack
1207
+ }),
1208
+ // Track deleted comments.
1209
+ invertedEffects2.of((tr) => {
1210
+ const { comments: comments2 } = tr.startState.field(commentsState);
1211
+ const effects = [];
1212
+ tr.changes.iterChangedRanges((fromA, toA) => {
1213
+ for (const { comment: { id }, range: { from, to } } of comments2) {
1214
+ if (from < to && from >= fromA && to <= toA) {
1215
+ effects.push(restoreCommentEffect.of({
1216
+ id,
1217
+ from,
1218
+ to
1219
+ }));
1257
1220
  }
1258
- });
1259
- if (options.footnote) {
1260
- const tag = `[^${id}]`;
1261
- view.dispatch({
1262
- changes: {
1263
- from: head,
1264
- insert: tag
1265
- },
1266
- selection: {
1267
- anchor: head + tag.length
1221
+ }
1222
+ });
1223
+ return effects;
1224
+ }),
1225
+ // Handle paste or the undo of comment deletion.
1226
+ EditorView5.updateListener.of((update2) => {
1227
+ const restore = [];
1228
+ for (let i = 0; i < update2.transactions.length; i++) {
1229
+ const tr = update2.transactions[i];
1230
+ for (let j = 0; j < restore.length; j++) {
1231
+ restore[j] = mapTrackedComment(restore[j], tr.changes);
1232
+ }
1233
+ for (const effect of tr.effects) {
1234
+ if (effect.is(restoreCommentEffect)) {
1235
+ restore.push(effect.value);
1236
+ }
1237
+ }
1238
+ }
1239
+ if (tracked) {
1240
+ const paste = update2.transactions.find((tr) => tr.isUserEvent("input.paste"));
1241
+ if (paste) {
1242
+ let found = -1;
1243
+ paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {
1244
+ if (text.eq(tracked.text)) {
1245
+ for (let i = update2.transactions.indexOf(paste) + 1; i < update2.transactions.length; i++) {
1246
+ fromB = update2.transactions[i].changes.mapPos(fromB);
1247
+ }
1248
+ found = fromB;
1268
1249
  }
1269
1250
  });
1251
+ if (found > -1) {
1252
+ for (const moved of tracked.comments) {
1253
+ restore.push({
1254
+ id: moved.id,
1255
+ from: found + moved.from,
1256
+ to: found + moved.to
1257
+ });
1258
+ }
1259
+ }
1260
+ tracked = null;
1270
1261
  }
1271
- return true;
1272
1262
  }
1273
- }
1263
+ for (const comment of restore) {
1264
+ const { comments: comments2 } = update2.startState.field(commentsState);
1265
+ const exists = comments2.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
1266
+ if (!exists) {
1267
+ const cursor = Cursor.getCursorFromRange(update2.state, comment);
1268
+ onUpdate(comment.id, cursor);
1269
+ }
1270
+ }
1271
+ })
1272
+ ];
1273
+ };
1274
+ var mapTrackedComment = (comment, changes) => ({
1275
+ id: comment.id,
1276
+ from: changes.mapPos(comment.from, 1),
1277
+ to: changes.mapPos(comment.to, 1)
1278
+ });
1279
+ var restoreCommentEffect = StateEffect2.define({
1280
+ map: mapTrackedComment
1281
+ });
1282
+ var optionsFacet = Facet3.define({
1283
+ combine: (providers) => providers[0]
1284
+ });
1285
+ var createComment = (view) => {
1286
+ const options = view.state.facet(optionsFacet);
1287
+ const { from, to } = view.state.selection.main;
1288
+ if (from === to) {
1274
1289
  return false;
1275
- };
1276
- const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
1277
- static {
1278
- // Match markdown footnotes (option).
1279
- // https://www.markdownguide.org/extended-syntax/#footnotes
1280
- this.bookmarkMatcher = new MatchDecorator({
1281
- regexp: /\[\^(\w+)\]/g,
1282
- decoration: (match, view, pos) => {
1283
- const id = match[1];
1284
- return Decoration3.replace({
1285
- id,
1286
- widget: new BookmarkWidget(pos, id)
1287
- });
1290
+ }
1291
+ if (to === view.state.doc.length) {
1292
+ view.dispatch({
1293
+ changes: {
1294
+ from: to,
1295
+ insert: "\n"
1296
+ }
1297
+ });
1298
+ }
1299
+ const cursor = Cursor.getCursorFromRange(view.state, {
1300
+ from,
1301
+ to
1302
+ });
1303
+ if (cursor) {
1304
+ const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1305
+ if (id) {
1306
+ view.dispatch({
1307
+ effects: setSelection.of({
1308
+ current: id
1309
+ }),
1310
+ selection: {
1311
+ anchor: to
1288
1312
  }
1289
1313
  });
1314
+ return true;
1290
1315
  }
1291
- constructor(view) {
1292
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1293
- }
1294
- update(update4) {
1295
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
1296
- }
1297
- }, {
1298
- decorations: (instance) => instance.bookmarks,
1299
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1300
- return view.plugin(plugin)?.bookmarks || Decoration3.none;
1301
- })
1302
- });
1316
+ }
1317
+ return false;
1318
+ };
1319
+ var comments = (options = {}) => {
1320
+ const { key: shortcut = "meta-'" } = options;
1321
+ const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1303
1322
  return [
1304
- bookmarksViewPlugin,
1305
- commentsStateField,
1306
- highlightDecorations,
1307
- styles3,
1323
+ optionsFacet.of(options),
1324
+ commentsState,
1325
+ commentsDecorations,
1326
+ styles2,
1308
1327
  //
1309
1328
  // Keymap.
1310
1329
  //
1311
1330
  options.onCreate ? keymap3.of([
1312
1331
  {
1313
- key: options?.key ?? "meta-'",
1314
- run: callbackWrapper(createCommentThread)
1332
+ key: shortcut,
1333
+ run: callbackWrapper(createComment)
1315
1334
  }
1316
1335
  ]) : [],
1317
1336
  //
@@ -1327,7 +1346,7 @@ var comments = (options = {}) => {
1327
1346
  above: true,
1328
1347
  create: () => {
1329
1348
  const el = document.createElement("div");
1330
- options.onHover?.(el);
1349
+ options.onHover(el, shortcut);
1331
1350
  return {
1332
1351
  dom: el,
1333
1352
  offset: {
@@ -1340,372 +1359,128 @@ var comments = (options = {}) => {
1340
1359
  }
1341
1360
  return null;
1342
1361
  }, {
1343
- hideOnChange: true,
1362
+ // TODO(burdon): Hide on change triggered immediately?
1363
+ // hideOnChange: true,
1344
1364
  hoverTime: 1e3
1345
1365
  }) : [],
1346
1366
  //
1347
- // Monitor cursor movement and text updates.
1348
- // TODO(burdon): Is there a better (finer grained) way to do this?
1367
+ // Track deleted ranges and update ranges for decorations.
1349
1368
  //
1350
1369
  EditorView5.updateListener.of(({ view, state, changes }) => {
1351
- const cursorConverter3 = view.state.facet(CursorConverter);
1352
- {
1353
- let mod = false;
1354
- const { active, ranges } = state.field(commentsStateField);
1355
- changes.iterChanges((from, to, from2, to2) => {
1356
- ranges.forEach((range) => {
1357
- if (from2 === to2) {
1358
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1359
- if (!newRange || newRange.to - newRange.from === 0) {
1360
- log2.info("deleted comment", {
1361
- thread: range.id
1362
- }, {
1363
- F: __dxlog_file3,
1364
- L: 359,
1365
- S: void 0,
1366
- C: (f, a) => f(...a)
1367
- });
1368
- }
1369
- }
1370
- if (from <= range.to) {
1371
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1372
- Object.assign(range, newRange);
1373
- mod = true;
1370
+ let mod = false;
1371
+ const { comments: comments2, ...value } = state.field(commentsState);
1372
+ changes.iterChanges((from, to, from2, to2) => {
1373
+ comments2.forEach(({ comment, range }) => {
1374
+ if (from2 === to2) {
1375
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1376
+ if (!newRange || newRange.to - newRange.from === 0) {
1377
+ options.onDelete?.(comment.id);
1374
1378
  }
1375
- });
1379
+ }
1380
+ if (from <= range.to) {
1381
+ const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);
1382
+ Object.assign(range, newRange);
1383
+ mod = true;
1384
+ }
1385
+ });
1386
+ });
1387
+ if (mod) {
1388
+ view.dispatch({
1389
+ effects: setCommentState.of({
1390
+ comments: comments2,
1391
+ ...value
1392
+ })
1376
1393
  });
1377
- if (mod) {
1378
- view.dispatch({
1379
- effects: setCommentState.of({
1380
- active,
1381
- ranges
1382
- })
1383
- });
1384
- }
1385
1394
  }
1386
- {
1387
- const { head } = state.selection.main;
1388
- let min = Infinity;
1389
- const { active, closest, ranges } = state.field(commentsStateField);
1390
- const selected = {
1391
- active: void 0,
1392
- closest: void 0
1393
- };
1394
- ranges.forEach((comment) => {
1395
- const d = Math.min(Math.abs(head - comment.from), Math.abs(head - comment.to));
1396
- if (head >= comment.from && head <= comment.to) {
1397
- selected.active = comment.id;
1398
- }
1395
+ }),
1396
+ //
1397
+ // Track selection/proximity.
1398
+ //
1399
+ EditorView5.updateListener.of(({ view, state }) => {
1400
+ let min = Infinity;
1401
+ const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
1402
+ const { head } = state.selection.main;
1403
+ const selection = {};
1404
+ comments2.forEach(({ comment, range }) => {
1405
+ if (head >= range.from && head <= range.to) {
1406
+ selection.current = comment.id;
1407
+ selection.closest = void 0;
1408
+ }
1409
+ if (!selection.current) {
1410
+ const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));
1399
1411
  if (d < min) {
1400
- selected.closest = comment.id;
1412
+ selection.closest = comment.id;
1401
1413
  min = d;
1402
1414
  }
1403
- });
1404
- if (selected.active !== active || selected.closest !== closest) {
1405
- view.dispatch({
1406
- effects: setSelection.of(selected)
1407
- });
1408
- handleSelect({
1409
- ...selected,
1410
- ranges: ranges.map((range) => ({
1411
- ...range,
1412
- location: view.coordsAtPos(range.from)
1413
- }))
1414
- });
1415
- }
1416
- }
1417
- })
1418
- ];
1419
- };
1420
- var getCursorFromRange = (cursorConverter3, range) => {
1421
- const from = cursorConverter3.toCursor(range.from);
1422
- const to = cursorConverter3.toCursor(range.to, -1);
1423
- return [
1424
- from,
1425
- to
1426
- ].join(":");
1427
- };
1428
- var getRangeFromCursor = (cursorConverter3, cursor) => {
1429
- const parts = cursor.split(":");
1430
- const from = cursorConverter3.fromCursor(parts[0]);
1431
- const to = cursorConverter3.fromCursor(parts[1]);
1432
- return from && to ? {
1433
- from,
1434
- to
1435
- } : void 0;
1436
- };
1437
-
1438
- // packages/ui/react-ui-editor/src/extensions/hr.ts
1439
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
1440
- import get4 from "lodash.get";
1441
- var styles4 = EditorView6.baseTheme({
1442
- "& .cm-hr": {
1443
- // TODO(burdon): ???
1444
- // Note that block-level decorations should not have vertical margins,
1445
- borderBottom: `1px solid ${get4(tokens, "extend.colors.neutral.200")}`
1446
- }
1447
- });
1448
- var HorizontalRuleWidget = class extends WidgetType3 {
1449
- constructor(_pos) {
1450
- super();
1451
- this._pos = _pos;
1452
- }
1453
- eq(other) {
1454
- return this._pos === other._pos;
1455
- }
1456
- toDOM(view) {
1457
- const el = document.createElement("div");
1458
- el.className = "cm-hr";
1459
- return el;
1460
- }
1461
- };
1462
- var placeholderMatcher = new MatchDecorator2({
1463
- regexp: /^---$/g,
1464
- decoration: (match, view, pos) => Decoration4.replace({
1465
- widget: new HorizontalRuleWidget(pos)
1466
- })
1467
- });
1468
- var hr = () => [
1469
- styles4,
1470
- ViewPlugin5.fromClass(class {
1471
- constructor(view) {
1472
- this.rules = placeholderMatcher.createDeco(view);
1473
- }
1474
- update(update4) {
1475
- this.rules = placeholderMatcher.updateDeco(update4, this.rules);
1476
- }
1477
- }, {
1478
- decorations: (instance) => instance.rules,
1479
- // TODO(burdon): Is this really atomic (cursor can move into ---).
1480
- provide: (plugin) => EditorView6.atomicRanges.of((view) => {
1481
- return view.plugin(plugin)?.rules || Decoration4.none;
1482
- })
1483
- })
1484
- ];
1485
-
1486
- // packages/ui/react-ui-editor/src/extensions/image.ts
1487
- import { syntaxTree } from "@codemirror/language";
1488
- import { RangeSetBuilder, StateField as StateField3 } from "@codemirror/state";
1489
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1490
- var image = (options = {}) => {
1491
- return StateField3.define({
1492
- create: (state) => update(state, options),
1493
- update: (_, tr) => update(tr.state, options),
1494
- provide: (field) => EditorView7.decorations.from(field)
1495
- });
1496
- };
1497
- var update = (state, options) => {
1498
- const builder = new RangeSetBuilder();
1499
- const cursor = state.selection.main.head;
1500
- syntaxTree(state).iterate({
1501
- enter: (node) => {
1502
- if (node.name === "Image") {
1503
- const urlNode = node.node.getChild("URL");
1504
- if (urlNode) {
1505
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
1506
- const url = state.sliceDoc(urlNode.from, urlNode.to);
1507
- builder.add(hide ? node.from : node.to, node.to, Decoration5.replace({
1508
- block: true,
1509
- widget: new ImageWidget(url)
1510
- }));
1511
- }
1512
- }
1513
- }
1514
- });
1515
- return builder.finish();
1516
- };
1517
- var ImageWidget = class extends WidgetType4 {
1518
- constructor(_url) {
1519
- super();
1520
- this._url = _url;
1521
- }
1522
- // TODO(burdon): Does this need to be unique for each position?
1523
- eq(other) {
1524
- return this._url === other._url;
1525
- }
1526
- toDOM(view) {
1527
- const img = document.createElement("img");
1528
- img.setAttribute("src", this._url);
1529
- img.setAttribute("class", "cm-image");
1530
- return img;
1531
- }
1532
- };
1533
-
1534
- // packages/ui/react-ui-editor/src/extensions/link.ts
1535
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1536
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1537
- import { Decoration as Decoration6, EditorView as EditorView8, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2 } from "@codemirror/view";
1538
- import { tooltipContent } from "@dxos/react-ui-theme";
1539
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1540
- var link = (options = {}) => {
1541
- const extensions = [
1542
- StateField4.define({
1543
- create: (state) => update2(state, options),
1544
- update: (_, tr) => update2(tr.state, options),
1545
- provide: (field) => EditorView8.decorations.from(field)
1546
- })
1547
- ];
1548
- if (options.onHover) {
1549
- extensions.push(
1550
- // https://codemirror.net/examples/tooltip
1551
- // https://codemirror.net/docs/ref/#view.hoverTooltip
1552
- // https://github.com/codemirror/view/blob/main/src/tooltip.ts
1553
- hoverTooltip2((view, pos) => {
1554
- const { from, text } = view.state.doc.lineAt(pos);
1555
- const p = pos - from;
1556
- let idx = 0;
1557
- let match;
1558
- do {
1559
- match = text.substring(idx).match(markdownLinkRegexp);
1560
- if (!match) {
1561
- match = void 0;
1562
- break;
1563
- }
1564
- idx = text.indexOf(match[0]);
1565
- if (p >= idx && p < idx + match[0].length) {
1566
- break;
1567
- }
1568
- idx += match[0].length;
1569
- } while (true);
1570
- if (!match) {
1571
- return null;
1572
1415
  }
1573
- const [, , url] = match ?? [];
1574
- return {
1575
- pos: idx + from,
1576
- end: idx + from + match[0].length,
1577
- above: true,
1578
- create: () => {
1579
- const el = document.createElement("div");
1580
- el.className = tooltipContent({}, "pli-2 plb-1");
1581
- options.onHover(el, url);
1582
- return {
1583
- dom: el,
1584
- offset: {
1585
- x: 0,
1586
- y: 4
1587
- }
1588
- };
1589
- }
1590
- };
1591
- })
1592
- );
1593
- }
1594
- return extensions;
1595
- };
1596
- var LinkButton = class extends WidgetType5 {
1597
- constructor(_pos, _url, _onAttach) {
1598
- super();
1599
- this._pos = _pos;
1600
- this._url = _url;
1601
- this._onAttach = _onAttach;
1602
- }
1603
- eq(other) {
1604
- return this._pos === other._pos && this._url === other._url;
1605
- }
1606
- toDOM(view) {
1607
- const el = document.createElement("span");
1608
- this._onAttach(el, this._url);
1609
- return el;
1610
- }
1611
- };
1612
- var LinkText = class extends WidgetType5 {
1613
- constructor(_text, _url) {
1614
- super();
1615
- this._text = _text;
1616
- this._url = _url;
1617
- }
1618
- eq(other) {
1619
- return this._url === other._url;
1620
- }
1621
- toDOM(view) {
1622
- const link2 = document.createElement("a");
1623
- link2.setAttribute("class", "cm-link");
1624
- link2.textContent = this._text;
1625
- if (this._url) {
1626
- link2.setAttribute("rel", "noreferrer");
1627
- link2.setAttribute("target", "_blank");
1628
- link2.href = this._url;
1629
- } else {
1630
- link2.onclick = () => {
1416
+ });
1417
+ if (selection.current !== current || selection.closest !== closest) {
1631
1418
  view.dispatch({
1632
- selection: {
1633
- anchor: view.posAtDOM(link2)
1634
- }
1419
+ effects: setSelection.of(selection)
1420
+ });
1421
+ handleSelect({
1422
+ selection,
1423
+ comments: comments2.map(({ comment, range }) => ({
1424
+ comment,
1425
+ range,
1426
+ location: view.coordsAtPos(range.from)
1427
+ }))
1635
1428
  });
1636
- };
1637
- }
1638
- return link2;
1639
- }
1640
- };
1641
- var update2 = (state, options) => {
1642
- const builder = new RangeSetBuilder2();
1643
- const cursor = state.selection.main.head;
1644
- syntaxTree2(state).iterate({
1645
- enter: (node) => {
1646
- if (node.name === "Link") {
1647
- const marks2 = node.node.getChildren("LinkMark");
1648
- const text = marks2.length >= 2 ? state.sliceDoc(marks2[0].to, marks2[1].from) : "";
1649
- const urlNode = node.node.getChild("URL");
1650
- const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
1651
- if (!url) {
1652
- return false;
1653
- }
1654
- if (state.readOnly || cursor < node.from || cursor > node.to) {
1655
- builder.add(node.from, node.to, Decoration6.replace({
1656
- widget: new LinkText(text, options.link ? url : void 0)
1657
- }));
1658
- }
1659
- if (options.onRender) {
1660
- builder.add(node.to, node.to, Decoration6.replace({
1661
- widget: new LinkButton(node.from, url, options.onRender)
1662
- }));
1663
- }
1664
- return false;
1665
1429
  }
1430
+ }),
1431
+ options.onUpdate ? trackPastedComments(options.onUpdate) : []
1432
+ ];
1433
+ };
1434
+ var useComments = (view, comments2 = []) => {
1435
+ useEffect(() => {
1436
+ if (view) {
1437
+ view.dispatch({
1438
+ effects: setComments.of(comments2)
1439
+ });
1666
1440
  }
1667
- });
1668
- return builder.finish();
1441
+ }, [
1442
+ view,
1443
+ comments2
1444
+ ]);
1669
1445
  };
1670
1446
 
1671
1447
  // packages/ui/react-ui-editor/src/extensions/listener.ts
1672
- import { EditorView as EditorView9 } from "@codemirror/view";
1448
+ import { EditorView as EditorView6 } from "@codemirror/view";
1673
1449
  var listener = ({ onFocus, onChange }) => {
1674
1450
  const extensions = [];
1675
- onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1451
+ onFocus && extensions.push(EditorView6.focusChangeEffect.of((_, focused) => {
1676
1452
  onFocus(focused);
1677
1453
  return null;
1678
1454
  }));
1679
- onChange && extensions.push(EditorView9.updateListener.of((update4) => {
1680
- onChange(update4.state.doc.toString());
1455
+ onChange && extensions.push(EditorView6.updateListener.of((update2) => {
1456
+ onChange(update2.state.doc.toString());
1681
1457
  }));
1682
1458
  return extensions;
1683
1459
  };
1684
1460
 
1685
1461
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1686
1462
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1687
- import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1688
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1463
+ import { history as history2, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1464
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1689
1465
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1690
1466
  import { languages } from "@codemirror/language-data";
1691
1467
  import { searchKeymap } from "@codemirror/search";
1692
1468
  import { EditorState } from "@codemirror/state";
1693
1469
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
1694
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1470
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView7, highlightActiveLine as highlightActiveLine2, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1471
+ import { isNotFalsy as isNotFalsy2 } from "@dxos/util";
1695
1472
 
1696
1473
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1697
- import { markdownLanguage } from "@codemirror/lang-markdown";
1474
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1698
1475
  import { HighlightStyle } from "@codemirror/language";
1699
1476
  import { tags, styleTags, Tag } from "@lezer/highlight";
1700
1477
  import { Table } from "@lezer/markdown";
1701
- import get5 from "lodash.get";
1702
1478
  var markdownTags = {
1703
1479
  Blockquote: Tag.define(),
1704
1480
  CodeMark: Tag.define(),
1705
1481
  CodeText: Tag.define(),
1706
1482
  EmphasisMark: Tag.define(),
1707
1483
  HeaderMark: Tag.define(),
1708
- // HorizontalRule: Tag.define(),
1709
1484
  InlineCode: Tag.define(),
1710
1485
  LinkLabel: Tag.define(),
1711
1486
  LinkReference: Tag.define(),
@@ -1790,7 +1565,7 @@ var markdownHighlightStyle = (readonly) => {
1790
1565
  markdownTags.QuoteMark,
1791
1566
  markdownTags.EmphasisMark
1792
1567
  ],
1793
- class: readonly ? "hidden" : mark
1568
+ class: mark
1794
1569
  },
1795
1570
  // E.g., code block language (after ```).
1796
1571
  {
@@ -1798,7 +1573,7 @@ var markdownHighlightStyle = (readonly) => {
1798
1573
  tags.function(tags.variableName),
1799
1574
  tags.labelName
1800
1575
  ],
1801
- class: readonly ? "hidden" : codeMark
1576
+ class: codeMark
1802
1577
  },
1803
1578
  {
1804
1579
  tag: [
@@ -1809,27 +1584,27 @@ var markdownHighlightStyle = (readonly) => {
1809
1584
  // Headings.
1810
1585
  {
1811
1586
  tag: tags.heading1,
1812
- class: heading(1, readonly)
1587
+ class: heading(1)
1813
1588
  },
1814
1589
  {
1815
1590
  tag: tags.heading2,
1816
- class: heading(2, readonly)
1591
+ class: heading(2)
1817
1592
  },
1818
1593
  {
1819
1594
  tag: tags.heading3,
1820
- class: heading(3, readonly)
1595
+ class: heading(3)
1821
1596
  },
1822
1597
  {
1823
1598
  tag: tags.heading4,
1824
- class: heading(4, readonly)
1599
+ class: heading(4)
1825
1600
  },
1826
1601
  {
1827
1602
  tag: tags.heading5,
1828
- class: heading(5, readonly)
1603
+ class: heading(5)
1829
1604
  },
1830
1605
  {
1831
1606
  tag: tags.heading6,
1832
- class: heading(6, readonly)
1607
+ class: heading(6)
1833
1608
  },
1834
1609
  // Emphasis.
1835
1610
  {
@@ -1855,7 +1630,7 @@ var markdownHighlightStyle = (readonly) => {
1855
1630
  // However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
1856
1631
  // when a language is specified. In this case, the syntax highlighting extensions will colorize
1857
1632
  // the code, but all other CSS properties will be inherited.
1858
- // IMPORTANT: Therefore, the fenced code block will use the base editor font.
1633
+ // IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
1859
1634
  {
1860
1635
  tag: [
1861
1636
  markdownTags.CodeText,
@@ -1869,12 +1644,6 @@ var markdownHighlightStyle = (readonly) => {
1869
1644
  ],
1870
1645
  class: blockquote
1871
1646
  },
1872
- // TODO(burdon): Replace with extension.
1873
- // {
1874
- // tag: [markdownTags.HorizontalRule],
1875
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1876
- // },
1877
- // TODO(burdon): Only if being edited.
1878
1647
  {
1879
1648
  tag: [
1880
1649
  markdownTags.TableCell
@@ -1882,27 +1651,27 @@ var markdownHighlightStyle = (readonly) => {
1882
1651
  class: "font-mono"
1883
1652
  }
1884
1653
  ], {
1885
- scope: markdownLanguage,
1654
+ scope: markdownLanguage2,
1886
1655
  all: {
1887
- fontFamily: get5(tokens, "fontFamily.body", []).join(",")
1656
+ fontFamily: getToken("fontFamily.body", []).join(",")
1888
1657
  }
1889
1658
  });
1890
1659
  };
1891
1660
 
1892
1661
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1893
- var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1662
+ var createMarkdownExtensions = ({ themeMode, placeholder: _placeholder, lineWrapping = true } = {}) => {
1894
1663
  return [
1664
+ lineWrapping && EditorView7.lineWrapping,
1895
1665
  EditorState.allowMultipleSelections.of(true),
1896
1666
  EditorState.tabSize.of(2),
1897
- EditorView10.lineWrapping,
1898
- customKeymap(),
1667
+ // https://github.com/codemirror/basic-setup
1899
1668
  bracketMatching2(),
1900
1669
  closeBrackets2(),
1901
1670
  crosshairCursor(),
1902
1671
  dropCursor(),
1903
1672
  drawSelection2(),
1904
- highlightActiveLine(),
1905
- history(),
1673
+ highlightActiveLine2(),
1674
+ history2(),
1906
1675
  indentOnInput(),
1907
1676
  _placeholder && placeholder2(_placeholder),
1908
1677
  // Main extension.
@@ -1914,7 +1683,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1914
1683
  // NOTE: This extends the parser; it doesn't affect rendering.
1915
1684
  // https://github.github.com/gfm
1916
1685
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1917
- base: markdownLanguage2,
1686
+ base: markdownLanguage3,
1918
1687
  // Languages for syntax highlighting fenced code blocks.
1919
1688
  codeLanguages: languages,
1920
1689
  // Parser extensions.
@@ -1926,81 +1695,620 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1926
1695
  // https://github.com/codemirror/theme-one-dark
1927
1696
  themeMode === "dark" ? syntaxHighlighting2(oneDarkHighlightStyle2) : syntaxHighlighting2(defaultHighlightStyle2),
1928
1697
  // Custom styles.
1929
- syntaxHighlighting2(markdownHighlightStyle(readonly))
1930
- ].filter(Boolean);
1931
- };
1932
- var customKeymap = () => keymap4.of([
1933
- // https://codemirror.net/docs/ref/#commands.indentWithTab
1934
- indentWithTab,
1935
- // https://codemirror.net/docs/ref/#commands.standardKeymap
1936
- ...standardKeymap,
1937
- // https://codemirror.net/docs/ref/#commands.historyKeymap
1938
- ...historyKeymap,
1939
- // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1940
- ...closeBracketsKeymap,
1941
- // https://codemirror.net/docs/ref/#search.searchKeymap
1942
- ...searchKeymap
1943
- ]);
1944
-
1945
- // packages/ui/react-ui-editor/src/extensions/mention.ts
1946
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1947
- var mention = ({ onSearch }) => {
1948
- return autocompletion2({
1949
- closeOnBlur: false,
1950
- override: [
1951
- (context) => {
1952
- const match = context.matchBefore(/@(\w+)?/);
1953
- if (!match || match.from === match.to && !context.explicit) {
1954
- return null;
1955
- }
1956
- console.log(match);
1957
- return {
1958
- from: match.from,
1959
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
1960
- label: `@${value}`
1961
- }))
1962
- };
1963
- }
1964
- ]
1965
- });
1698
+ syntaxHighlighting2(markdownHighlightStyle()),
1699
+ keymap4.of([
1700
+ // https://codemirror.net/docs/ref/#commands.indentWithTab
1701
+ indentWithTab,
1702
+ // https://codemirror.net/docs/ref/#autocomplete.closeBracketsKeymap
1703
+ ...closeBracketsKeymap,
1704
+ // https://codemirror.net/docs/ref/#commands.historyKeymap
1705
+ ...historyKeymap,
1706
+ // https://codemirror.net/docs/ref/#search.searchKeymap
1707
+ ...searchKeymap,
1708
+ // https://codemirror.net/docs/ref/#commands.standardKeymap
1709
+ ...standardKeymap
1710
+ ])
1711
+ ].filter(isNotFalsy2);
1966
1712
  };
1967
1713
 
1968
- // packages/ui/react-ui-editor/src/extensions/mermaid.ts
1969
- var mermaid = () => {
1970
- return [];
1714
+ // packages/ui/react-ui-editor/src/extensions/markdown/code.ts
1715
+ import { syntaxTree } from "@codemirror/language";
1716
+ import { RangeSetBuilder } from "@codemirror/state";
1717
+ import { ViewPlugin as ViewPlugin3, EditorView as EditorView8, Decoration as Decoration3, WidgetType as WidgetType2 } from "@codemirror/view";
1718
+ import { mx as mx2 } from "@dxos/react-ui-theme";
1719
+ var styles3 = EditorView8.baseTheme({
1720
+ "& .cm-code": {
1721
+ fontFamily: getToken("fontFamily.mono", []).join(",")
1722
+ },
1723
+ "& .cm-codeblock": {
1724
+ width: "100%",
1725
+ paddingInline: "1rem !important",
1726
+ position: "relative",
1727
+ "&::after": {
1728
+ content: '""',
1729
+ position: "absolute",
1730
+ inset: 0
1731
+ }
1732
+ },
1733
+ "&light .cm-codeblock": {
1734
+ "&::after": {
1735
+ background: getToken("extend.semanticColors.input.light"),
1736
+ mixBlendMode: "darken"
1737
+ }
1738
+ },
1739
+ "&dark .cm-codeblock": {
1740
+ "&::after": {
1741
+ background: getToken("extend.semanticColors.input.dark"),
1742
+ mixBlendMode: "lighten"
1743
+ }
1744
+ },
1745
+ "& .cm-codeblock-first": {
1746
+ display: "inline-block",
1747
+ "&::after": {
1748
+ borderTopLeftRadius: ".5rem",
1749
+ borderTopRightRadius: ".5rem"
1750
+ }
1751
+ },
1752
+ "& .cm-codeblock-last": {
1753
+ display: "inline-block",
1754
+ "&::after": {
1755
+ borderBottomLeftRadius: ".5rem",
1756
+ borderBottomRightRadius: ".5rem"
1757
+ }
1758
+ }
1759
+ });
1760
+ var getLineRange = (lines, from, to) => {
1761
+ const start = lines.findIndex((line) => line.from >= from);
1762
+ const end = lines.findIndex((line) => line.to >= to);
1763
+ return [
1764
+ start,
1765
+ end
1766
+ ];
1971
1767
  };
1972
-
1973
- // packages/ui/react-ui-editor/src/extensions/table.ts
1974
- import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1975
- import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1976
- import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
1977
- var table = (options = {}) => {
1978
- return StateField5.define({
1979
- create: (state) => update3(state, options),
1980
- update: (_, tr) => update3(tr.state, options),
1981
- provide: (field) => EditorView11.decorations.from(field)
1982
- });
1768
+ var LineWidget = class extends WidgetType2 {
1769
+ constructor(_className) {
1770
+ super();
1771
+ this._className = _className;
1772
+ }
1773
+ toDOM() {
1774
+ const el = document.createElement("span");
1775
+ el.innerHTML = "&nbsp;";
1776
+ el.className = mx2("cm-code cm-codeblock", this._className);
1777
+ return el;
1778
+ }
1983
1779
  };
1984
- var update3 = (state, options) => {
1985
- const builder = new RangeSetBuilder3();
1780
+ var top = new LineWidget("cm-codeblock-first");
1781
+ var bottom = new LineWidget("cm-codeblock-last");
1782
+ var buildDecorations = (view) => {
1783
+ const builder = new RangeSetBuilder();
1784
+ const { state } = view;
1785
+ const blocks = view.viewportLineBlocks;
1986
1786
  const cursor = state.selection.main.head;
1987
- const tables = [];
1988
- const getTable = () => tables[tables.length - 1];
1989
- const getRow = () => {
1990
- const table2 = getTable();
1991
- return table2.rows?.[table2.rows.length - 1];
1992
- };
1993
- syntaxTree3(state).iterate({
1994
- enter: (node) => {
1995
- switch (node.name) {
1996
- case "Table": {
1997
- tables.push({
1998
- from: node.from,
1999
- to: node.to
2000
- });
2001
- break;
1787
+ for (const { from, to } of view.visibleRanges) {
1788
+ syntaxTree(state).iterate({
1789
+ enter: (node) => {
1790
+ if (node.name === "FencedCode") {
1791
+ const edit = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
1792
+ const range = getLineRange(blocks, node.from, node.to);
1793
+ for (let i = range[0]; i <= range[1]; i++) {
1794
+ const block = blocks[i];
1795
+ if (!edit && (i === range[0] || i === range[1])) {
1796
+ builder.add(block.from, block.to, Decoration3.replace({
1797
+ widget: i === range[0] ? top : bottom
1798
+ }));
1799
+ } else {
1800
+ builder.add(block.from, block.from, Decoration3.line({
1801
+ class: mx2("cm-code cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
1802
+ }));
1803
+ }
1804
+ }
2002
1805
  }
2003
- case "TableHeader": {
1806
+ },
1807
+ from,
1808
+ to
1809
+ });
1810
+ }
1811
+ return builder.finish();
1812
+ };
1813
+ var code2 = () => {
1814
+ return [
1815
+ ViewPlugin3.fromClass(class {
1816
+ constructor(view) {
1817
+ this.decorations = buildDecorations(view);
1818
+ }
1819
+ update(update2) {
1820
+ this.decorations = buildDecorations(update2.view);
1821
+ }
1822
+ }, {
1823
+ decorations: (value) => value.decorations
1824
+ }),
1825
+ styles3
1826
+ ];
1827
+ };
1828
+
1829
+ // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1830
+ import { snippet } from "@codemirror/autocomplete";
1831
+ import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1832
+ import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1833
+ import { Decoration as Decoration4, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1834
+ var setHeading = (level) => (view) => {
1835
+ const { selection: { ranges }, doc } = view.state;
1836
+ const changes = [];
1837
+ for (const range of ranges) {
1838
+ const { number } = doc.lineAt(range.anchor);
1839
+ const { from, to } = doc.line(number);
1840
+ const line = doc.sliceString(from, to);
1841
+ const [_, marks, spaces] = line.match(/(#+)(\s+)/) ?? [];
1842
+ const current = marks?.length ?? 0;
1843
+ if (level !== current) {
1844
+ changes.push({
1845
+ from,
1846
+ to: from + current + (spaces?.length ?? 0),
1847
+ insert: "#".repeat(level) + (level > 0 ? " " : "")
1848
+ });
1849
+ }
1850
+ }
1851
+ if (changes.length) {
1852
+ view.dispatch({
1853
+ changes
1854
+ });
1855
+ }
1856
+ return true;
1857
+ };
1858
+ var toggleStyle = (mark2) => (view) => {
1859
+ const { ranges } = view.state.selection;
1860
+ for (const range of ranges) {
1861
+ if (range.from === range.to) {
1862
+ return false;
1863
+ }
1864
+ view.dispatch({
1865
+ changes: [
1866
+ {
1867
+ from: range.from,
1868
+ insert: mark2
1869
+ },
1870
+ {
1871
+ from: range.to,
1872
+ insert: mark2
1873
+ }
1874
+ ]
1875
+ });
1876
+ }
1877
+ return true;
1878
+ };
1879
+ var snippets = {
1880
+ codeblock: snippet([
1881
+ "```#{lang}",
1882
+ " #{}",
1883
+ "```"
1884
+ ].join("\n")),
1885
+ table: snippet([
1886
+ "| #{col1} | #{col2} |",
1887
+ "| ---- | ---- |",
1888
+ "| #{val1} | #{val2} |",
1889
+ "| #{val3} | #{val4} |"
1890
+ ].join("\n"))
1891
+ };
1892
+ var insertCodeblock = (view) => {
1893
+ const { selection: { main }, doc } = view.state;
1894
+ const { number } = doc.lineAt(main.anchor);
1895
+ const { from } = doc.line(number);
1896
+ snippets.codeblock(view, null, from, from);
1897
+ };
1898
+ var insertTable = (view) => {
1899
+ const { selection: { main }, doc } = view.state;
1900
+ const { number } = doc.lineAt(main.anchor);
1901
+ const { from } = doc.line(number);
1902
+ snippets.table(view, null, from, from);
1903
+ };
1904
+ var toggleBold = toggleStyle("**");
1905
+ var toggleItalic = toggleStyle("_");
1906
+ var toggleStrikethrough = toggleStyle("~~");
1907
+ var toggleList = (view) => {
1908
+ };
1909
+ var formatting = (options = {}) => {
1910
+ return [
1911
+ keymap5.of([
1912
+ {
1913
+ key: "meta-b",
1914
+ run: toggleBold
1915
+ }
1916
+ ]),
1917
+ styling()
1918
+ ];
1919
+ };
1920
+ var styling = () => {
1921
+ const buildDecorations7 = (view) => {
1922
+ const builder = new RangeSetBuilder2();
1923
+ const { state } = view;
1924
+ const cursor = state.selection.main.head;
1925
+ const replace = (node, marks) => {
1926
+ if (cursor <= node.from || cursor >= node.to) {
1927
+ for (const mark2 of marks) {
1928
+ builder.add(mark2.from, mark2.to, Decoration4.replace({}));
1929
+ }
1930
+ }
1931
+ };
1932
+ for (const { from, to } of view.visibleRanges) {
1933
+ syntaxTree2(state).iterate({
1934
+ enter: (node) => {
1935
+ switch (node.name) {
1936
+ case "Emphasis":
1937
+ case "StrongEmphasis": {
1938
+ const marks = node.node.getChildren("EmphasisMark");
1939
+ replace(node, marks);
1940
+ break;
1941
+ }
1942
+ case "Strikethrough": {
1943
+ const marks = node.node.getChildren("StrikethroughMark");
1944
+ replace(node, marks);
1945
+ break;
1946
+ }
1947
+ }
1948
+ },
1949
+ from,
1950
+ to
1951
+ });
1952
+ }
1953
+ return builder.finish();
1954
+ };
1955
+ return [
1956
+ ViewPlugin4.fromClass(class {
1957
+ constructor(view) {
1958
+ this.decorations = buildDecorations7(view);
1959
+ }
1960
+ update(update2) {
1961
+ this.decorations = buildDecorations7(update2.view);
1962
+ }
1963
+ }, {
1964
+ decorations: (value) => value.decorations
1965
+ })
1966
+ ];
1967
+ };
1968
+
1969
+ // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
1970
+ import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1971
+ import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
1972
+ import { Decoration as Decoration5, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1973
+ var buildDecorations2 = (view) => {
1974
+ const builder = new RangeSetBuilder3();
1975
+ const { state } = view;
1976
+ const cursor = state.selection.main.head;
1977
+ for (const { from, to } of view.visibleRanges) {
1978
+ syntaxTree3(state).iterate({
1979
+ enter: (node) => {
1980
+ switch (node.name) {
1981
+ case "ATXHeading1":
1982
+ case "ATXHeading2":
1983
+ case "ATXHeading3":
1984
+ case "ATXHeading4":
1985
+ case "ATXHeading5":
1986
+ case "ATXHeading6": {
1987
+ const mark2 = node.node.getChild("HeaderMark");
1988
+ if (mark2) {
1989
+ if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
1990
+ builder.add(mark2.from, mark2.to + 1, Decoration5.replace({}));
1991
+ }
1992
+ }
1993
+ }
1994
+ }
1995
+ },
1996
+ from,
1997
+ to
1998
+ });
1999
+ }
2000
+ return builder.finish();
2001
+ };
2002
+ var heading2 = () => {
2003
+ return [
2004
+ ViewPlugin5.fromClass(class {
2005
+ constructor(view) {
2006
+ this.decorations = buildDecorations2(view);
2007
+ }
2008
+ update(update2) {
2009
+ this.decorations = buildDecorations2(update2.view);
2010
+ }
2011
+ }, {
2012
+ decorations: (value) => value.decorations
2013
+ })
2014
+ ];
2015
+ };
2016
+
2017
+ // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
2018
+ import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2019
+ import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2020
+ import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2021
+ var styles4 = EditorView9.baseTheme({
2022
+ "& .cm-hr": {
2023
+ // Note that block-level decorations should not have vertical margins,
2024
+ borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
2025
+ }
2026
+ });
2027
+ var HorizontalRuleWidget = class extends WidgetType3 {
2028
+ toDOM() {
2029
+ const el = document.createElement("div");
2030
+ el.className = "cm-hr";
2031
+ return el;
2032
+ }
2033
+ };
2034
+ var decoration = Decoration6.replace({
2035
+ widget: new HorizontalRuleWidget()
2036
+ });
2037
+ var buildDecorations3 = (view) => {
2038
+ const builder = new RangeSetBuilder4();
2039
+ const { state } = view;
2040
+ const cursor = state.selection.main.head;
2041
+ for (const { from, to } of view.visibleRanges) {
2042
+ syntaxTree4(state).iterate({
2043
+ enter: (node) => {
2044
+ if (node.name === "HorizontalRule") {
2045
+ if (cursor <= node.from || cursor >= node.to) {
2046
+ builder.add(node.from, node.to, decoration);
2047
+ }
2048
+ }
2049
+ },
2050
+ from,
2051
+ to
2052
+ });
2053
+ }
2054
+ return builder.finish();
2055
+ };
2056
+ var hr = () => {
2057
+ return [
2058
+ ViewPlugin6.fromClass(class {
2059
+ constructor(view) {
2060
+ this.decorations = buildDecorations3(view);
2061
+ }
2062
+ update(update2) {
2063
+ this.decorations = buildDecorations3(update2.view);
2064
+ }
2065
+ }, {
2066
+ decorations: (value) => value.decorations
2067
+ }),
2068
+ styles4
2069
+ ];
2070
+ };
2071
+
2072
+ // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
2073
+ import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2074
+ import { StateField as StateField3 } from "@codemirror/state";
2075
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType4 } from "@codemirror/view";
2076
+ var image = (options = {}) => {
2077
+ return StateField3.define({
2078
+ create: (state) => {
2079
+ return Decoration7.set(buildDecorations4(0, state.doc.length, state));
2080
+ },
2081
+ update: (value, tr) => {
2082
+ if (!tr.docChanged && !tr.selection) {
2083
+ return value;
2084
+ }
2085
+ const cursor = tr.state.selection.main.head;
2086
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
2087
+ let from = Math.min(cursor, oldCursor);
2088
+ let to = Math.max(cursor, oldCursor);
2089
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
2090
+ from = Math.min(from, fromB);
2091
+ to = Math.max(to, toB);
2092
+ });
2093
+ from = tr.state.doc.lineAt(from).from;
2094
+ to = tr.state.doc.lineAt(to).to;
2095
+ return value.map(tr.changes).update({
2096
+ filterFrom: from,
2097
+ filterTo: to,
2098
+ filter: () => false,
2099
+ add: buildDecorations4(from, to, tr.state)
2100
+ });
2101
+ },
2102
+ provide: (field) => EditorView10.decorations.from(field)
2103
+ });
2104
+ };
2105
+ var buildDecorations4 = (from, to, state) => {
2106
+ const decorations = [];
2107
+ const cursor = state.selection.main.head;
2108
+ syntaxTree5(state).iterate({
2109
+ enter: (node) => {
2110
+ if (node.name === "Image") {
2111
+ const urlNode = node.node.getChild("URL");
2112
+ if (urlNode) {
2113
+ const hide = state.readOnly || cursor < node.from || cursor > node.to;
2114
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
2115
+ decorations.push(Decoration7.replace({
2116
+ block: true,
2117
+ widget: new ImageWidget(url)
2118
+ }).range(hide ? node.from : node.to, node.to));
2119
+ }
2120
+ }
2121
+ },
2122
+ from,
2123
+ to
2124
+ });
2125
+ return decorations;
2126
+ };
2127
+ var ImageWidget = class extends WidgetType4 {
2128
+ constructor(_url) {
2129
+ super();
2130
+ this._url = _url;
2131
+ }
2132
+ eq(other) {
2133
+ return this._url === other._url;
2134
+ }
2135
+ toDOM(view) {
2136
+ const img = document.createElement("img");
2137
+ img.setAttribute("src", this._url);
2138
+ img.setAttribute("class", "cm-image");
2139
+ img.onload = () => img.classList.add("cm-loaded-image");
2140
+ return img;
2141
+ }
2142
+ };
2143
+
2144
+ // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
2145
+ import { syntaxTree as syntaxTree6 } from "@codemirror/language";
2146
+ import { RangeSetBuilder as RangeSetBuilder5 } from "@codemirror/state";
2147
+ import { hoverTooltip as hoverTooltip2, Decoration as Decoration8, ViewPlugin as ViewPlugin7, WidgetType as WidgetType5 } from "@codemirror/view";
2148
+ import { tooltipContent } from "@dxos/react-ui-theme";
2149
+ var link = (options = {}) => {
2150
+ const extensions = [
2151
+ ViewPlugin7.fromClass(class {
2152
+ constructor(view) {
2153
+ this.decorations = buildDecorations5(view, options);
2154
+ }
2155
+ update(update2) {
2156
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2157
+ this.decorations = buildDecorations5(update2.view, options);
2158
+ }
2159
+ }
2160
+ }, {
2161
+ decorations: (value) => value.decorations
2162
+ })
2163
+ ];
2164
+ if (options.onHover) {
2165
+ extensions.push(
2166
+ // https://codemirror.net/examples/tooltip
2167
+ // https://codemirror.net/docs/ref/#view.hoverTooltip
2168
+ // https://github.com/codemirror/view/blob/main/src/tooltip.ts
2169
+ hoverTooltip2((view, pos, side) => {
2170
+ const syntax = syntaxTree6(view.state).resolveInner(pos, side);
2171
+ let link2 = null;
2172
+ for (let i = 0, node = syntax; !link2 && node && i < 5; node = node.parent, i++) {
2173
+ link2 = node.name === "Link" ? node : null;
2174
+ }
2175
+ const url = link2 && link2.getChild("URL");
2176
+ if (!url || !link2) {
2177
+ return null;
2178
+ }
2179
+ const urlText = view.state.sliceDoc(url.from, url.to);
2180
+ return {
2181
+ pos: link2.from,
2182
+ end: link2.to,
2183
+ above: true,
2184
+ create: () => {
2185
+ const el = document.createElement("div");
2186
+ el.className = tooltipContent({}, "pli-2 plb-1");
2187
+ options.onHover(el, urlText);
2188
+ return {
2189
+ dom: el,
2190
+ offset: {
2191
+ x: 0,
2192
+ y: 4
2193
+ }
2194
+ };
2195
+ }
2196
+ };
2197
+ })
2198
+ );
2199
+ }
2200
+ return extensions;
2201
+ };
2202
+ var LinkButton = class extends WidgetType5 {
2203
+ constructor(_url, _onAttach) {
2204
+ super();
2205
+ this._url = _url;
2206
+ this._onAttach = _onAttach;
2207
+ }
2208
+ eq(other) {
2209
+ return this._url === other._url;
2210
+ }
2211
+ toDOM(view) {
2212
+ const el = document.createElement("span");
2213
+ this._onAttach(el, this._url);
2214
+ return el;
2215
+ }
2216
+ };
2217
+ var LinkText = class extends WidgetType5 {
2218
+ constructor(_text, _url) {
2219
+ super();
2220
+ this._text = _text;
2221
+ this._url = _url;
2222
+ }
2223
+ eq(other) {
2224
+ return this._url === other._url;
2225
+ }
2226
+ toDOM(view) {
2227
+ const link2 = document.createElement("a");
2228
+ link2.setAttribute("class", "cm-link");
2229
+ link2.textContent = this._text;
2230
+ if (this._url) {
2231
+ link2.setAttribute("rel", "noreferrer");
2232
+ link2.setAttribute("target", "_blank");
2233
+ link2.href = this._url;
2234
+ } else {
2235
+ link2.onclick = () => {
2236
+ view.dispatch({
2237
+ selection: {
2238
+ anchor: view.posAtDOM(link2)
2239
+ }
2240
+ });
2241
+ };
2242
+ }
2243
+ return link2;
2244
+ }
2245
+ };
2246
+ var buildDecorations5 = (view, options) => {
2247
+ const builder = new RangeSetBuilder5();
2248
+ const { state } = view;
2249
+ const cursor = state.selection.main.head;
2250
+ for (const { from, to } of view.visibleRanges) {
2251
+ syntaxTree6(state).iterate({
2252
+ enter: (node) => {
2253
+ if (node.name === "Link") {
2254
+ const marks = node.node.getChildren("LinkMark");
2255
+ const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : "";
2256
+ const urlNode = node.node.getChild("URL");
2257
+ const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : "";
2258
+ if (!url) {
2259
+ return false;
2260
+ }
2261
+ if (!view.hasFocus || state.readOnly || cursor < node.from || cursor > node.to) {
2262
+ builder.add(node.from, node.to, Decoration8.replace({
2263
+ widget: new LinkText(text, options.link ? url : void 0)
2264
+ }));
2265
+ }
2266
+ if (options.onRender) {
2267
+ builder.add(node.to, node.to, Decoration8.replace({
2268
+ widget: new LinkButton(url, options.onRender)
2269
+ }));
2270
+ }
2271
+ return false;
2272
+ }
2273
+ },
2274
+ from,
2275
+ to
2276
+ });
2277
+ }
2278
+ return builder.finish();
2279
+ };
2280
+
2281
+ // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
2282
+ import { syntaxTree as syntaxTree7 } from "@codemirror/language";
2283
+ import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
2284
+ import { Decoration as Decoration9, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
2285
+ var table = (options = {}) => {
2286
+ return StateField4.define({
2287
+ create: (state) => update(state, options),
2288
+ update: (_, tr) => update(tr.state, options),
2289
+ provide: (field) => EditorView11.decorations.from(field)
2290
+ });
2291
+ };
2292
+ var update = (state, options) => {
2293
+ const builder = new RangeSetBuilder6();
2294
+ const cursor = state.selection.main.head;
2295
+ const tables = [];
2296
+ const getTable = () => tables[tables.length - 1];
2297
+ const getRow = () => {
2298
+ const table2 = getTable();
2299
+ return table2.rows?.[table2.rows.length - 1];
2300
+ };
2301
+ syntaxTree7(state).iterate({
2302
+ enter: (node) => {
2303
+ switch (node.name) {
2304
+ case "Table": {
2305
+ tables.push({
2306
+ from: node.from,
2307
+ to: node.to
2308
+ });
2309
+ break;
2310
+ }
2311
+ case "TableHeader": {
2004
2312
  getTable().header = [];
2005
2313
  break;
2006
2314
  }
@@ -2022,11 +2330,10 @@ var update3 = (state, options) => {
2022
2330
  });
2023
2331
  tables.forEach((table2) => {
2024
2332
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2025
- hide && builder.add(table2.from, table2.to, Decoration7.replace({
2026
- block: true,
2333
+ hide && builder.add(table2.from, table2.to, Decoration9.replace({
2027
2334
  widget: new TableWidget(table2)
2028
2335
  }));
2029
- builder.add(table2.from, table2.to, Decoration7.mark({
2336
+ builder.add(table2.from, table2.to, Decoration9.mark({
2030
2337
  class: "cm-table"
2031
2338
  }));
2032
2339
  });
@@ -2038,7 +2345,7 @@ var TableWidget = class extends WidgetType6 {
2038
2345
  this._table = _table;
2039
2346
  }
2040
2347
  eq(other) {
2041
- return this._table.from === other?._table?.from;
2348
+ return this._table.header?.join() === other._table.header?.join() && this._table.rows?.join() === other._table.rows?.join();
2042
2349
  }
2043
2350
  toDOM(view) {
2044
2351
  const table2 = document.createElement("table");
@@ -2064,95 +2371,144 @@ var TableWidget = class extends WidgetType6 {
2064
2371
  }
2065
2372
  return table2;
2066
2373
  }
2374
+ ignoreEvent(e) {
2375
+ return !/^mouse/.test(e.type);
2376
+ }
2067
2377
  };
2068
2378
 
2069
- // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2070
- import { EditorView as EditorView12, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2379
+ // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
2380
+ import { syntaxTree as syntaxTree8 } from "@codemirror/language";
2381
+ import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
2382
+ import { EditorView as EditorView12, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
2071
2383
  var styles5 = EditorView12.baseTheme({
2072
- "& .cm-task-item": {
2073
- paddingLeft: "4px",
2074
- paddingRight: "8px"
2384
+ "& .cm-task": {
2385
+ color: getToken("extend.colors.blue.500")
2386
+ },
2387
+ "& .cm-task-checkbox": {
2388
+ marginLeft: "4px",
2389
+ marginRight: "4px"
2075
2390
  }
2076
2391
  });
2077
2392
  var CheckboxWidget = class extends WidgetType7 {
2078
- constructor(_pos, _checked, _indent, _onCheck) {
2393
+ constructor(_checked) {
2079
2394
  super();
2080
- this._pos = _pos;
2081
2395
  this._checked = _checked;
2082
- this._indent = _indent;
2083
- this._onCheck = _onCheck;
2084
2396
  }
2085
2397
  eq(other) {
2086
- return this._pos === other._pos && this._checked === other._checked && this._indent === other._indent;
2398
+ return this._checked === other._checked;
2087
2399
  }
2088
2400
  toDOM(view) {
2089
- const wrap = document.createElement("span");
2090
- wrap.className = "cm-task-item";
2091
- wrap.setAttribute("aria-hidden", "true");
2092
- wrap.style.setProperty("margin-left", this._indent * 24 + "px");
2093
- const input = wrap.appendChild(document.createElement("input"));
2401
+ const input = document.createElement("input");
2402
+ input.className = "cm-task-checkbox";
2094
2403
  input.type = "checkbox";
2095
2404
  input.checked = this._checked;
2096
- if (!this._onCheck) {
2405
+ if (view.state.readOnly) {
2097
2406
  input.setAttribute("disabled", "true");
2098
- }
2099
- if (this._onCheck) {
2100
- input.onchange = (event) => {
2101
- this._onCheck?.(event.target.checked);
2102
- return true;
2407
+ } else {
2408
+ input.onmousedown = (event) => {
2409
+ const pos = view.posAtDOM(input);
2410
+ const text = view.state.sliceDoc(pos, pos + 3);
2411
+ if (text === (this._checked ? "[x]" : "[ ]")) {
2412
+ view.dispatch({
2413
+ changes: {
2414
+ from: pos + 1,
2415
+ to: pos + 2,
2416
+ insert: this._checked ? " " : "x"
2417
+ }
2418
+ });
2419
+ event.preventDefault();
2420
+ }
2103
2421
  };
2104
2422
  }
2105
- return wrap;
2423
+ return input;
2106
2424
  }
2107
2425
  ignoreEvent() {
2108
2426
  return false;
2109
2427
  }
2110
2428
  };
2111
- var tasklist = (options = {}) => {
2112
- const taskMatcher = new MatchDecorator3({
2113
- regexp: /^(\s*)- \[([ xX])\]\s/g,
2114
- decoration: (match, view, pos) => {
2115
- const indent = Math.floor(match[1].length / 2);
2116
- const checked = match[2] === "x" || match[2] === "X";
2117
- return Decoration8.replace({
2118
- widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
2119
- const idx = pos + match[0].indexOf("[") + 1;
2120
- view.dispatch({
2121
- changes: {
2122
- from: idx,
2123
- to: idx + 1,
2124
- insert: checked2 ? "x" : " "
2125
- },
2126
- // TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
2127
- selection: {
2128
- anchor: idx + 3
2129
- }
2130
- });
2131
- })
2132
- });
2133
- }
2134
- });
2135
- const tasks = ViewPlugin6.fromClass(class {
2136
- constructor(view) {
2137
- this.tasks = taskMatcher.createDeco(view);
2138
- }
2139
- update(update4) {
2140
- this.tasks = taskMatcher.updateDeco(update4, this.tasks);
2141
- }
2142
- }, {
2143
- decorations: (value) => value.tasks,
2144
- provide: (plugin) => EditorView12.atomicRanges.of((view) => {
2145
- return view.plugin(plugin)?.tasks || Decoration8.none;
2146
- })
2147
- });
2429
+ var checkedDecoration = Decoration10.replace({
2430
+ widget: new CheckboxWidget(true)
2431
+ });
2432
+ var uncheckedDecoration = Decoration10.replace({
2433
+ widget: new CheckboxWidget(false)
2434
+ });
2435
+ var buildDecorations6 = (view) => {
2436
+ const builder = new RangeSetBuilder7();
2437
+ const { state } = view;
2438
+ const cursor = state.selection.main.head;
2439
+ for (const { from, to } of view.visibleRanges) {
2440
+ syntaxTree8(state).iterate({
2441
+ enter: (node) => {
2442
+ if (node.name === "TaskMarker") {
2443
+ if (cursor < node.from || cursor > node.to) {
2444
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
2445
+ builder.add(node.from - 2, node.from - 1, Decoration10.mark({
2446
+ class: "cm-task"
2447
+ }));
2448
+ builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
2449
+ }
2450
+ }
2451
+ },
2452
+ from,
2453
+ to
2454
+ });
2455
+ }
2456
+ return builder.finish();
2457
+ };
2458
+ var tasklist = (options = {}) => {
2148
2459
  return [
2149
- tasks,
2460
+ ViewPlugin8.fromClass(class {
2461
+ constructor(view) {
2462
+ this.decorations = buildDecorations6(view);
2463
+ }
2464
+ update(update2) {
2465
+ if (update2.docChanged || update2.viewportChanged || update2.selectionSet) {
2466
+ this.decorations = buildDecorations6(update2.view);
2467
+ }
2468
+ }
2469
+ }, {
2470
+ decorations: (value) => value.decorations
2471
+ }),
2150
2472
  styles5
2151
2473
  ];
2152
2474
  };
2153
2475
 
2476
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
2477
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
2478
+ var mention = ({ onSearch }) => {
2479
+ return autocompletion2({
2480
+ // TODO(burdon): Not working.
2481
+ activateOnTyping: true,
2482
+ // activateOnTypingDelay: 100,
2483
+ // selectOnOpen: true,
2484
+ closeOnBlur: false,
2485
+ // defaultKeymap: false,
2486
+ icons: false,
2487
+ override: [
2488
+ (context) => {
2489
+ console.log(context);
2490
+ const match = context.matchBefore(/@(\w+)?/);
2491
+ if (!match || match.from === match.to && !context.explicit) {
2492
+ return null;
2493
+ }
2494
+ return {
2495
+ from: match.from,
2496
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
2497
+ label: `@${value}`
2498
+ }))
2499
+ };
2500
+ }
2501
+ ]
2502
+ });
2503
+ };
2504
+
2505
+ // packages/ui/react-ui-editor/src/extensions/outliner.ts
2506
+ var outliner = (options = {}) => {
2507
+ return [];
2508
+ };
2509
+
2154
2510
  // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2155
- import { keymap as keymap5 } from "@codemirror/view";
2511
+ import { keymap as keymap6 } from "@codemirror/view";
2156
2512
  var defaultItems = [
2157
2513
  "hello world!",
2158
2514
  "this is a test.",
@@ -2162,7 +2518,7 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2162
2518
  let t;
2163
2519
  let idx = 0;
2164
2520
  return [
2165
- keymap5.of([
2521
+ keymap6.of([
2166
2522
  {
2167
2523
  // Reset.
2168
2524
  key: "alt-meta-'",
@@ -2208,29 +2564,8 @@ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2208
2564
  ];
2209
2565
  };
2210
2566
 
2211
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2212
- import { yCollab } from "y-codemirror.next";
2213
-
2214
- // packages/ui/react-ui-editor/src/extensions/yjs/cursor.ts
2215
- import * as Y from "yjs";
2216
- import { arrayToString, stringToArray } from "@dxos/util";
2217
- var cursorConverter2 = (text) => ({
2218
- toCursor: (index, assoc) => {
2219
- return arrayToString(Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(text, index, assoc)));
2220
- },
2221
- fromCursor: (cursor) => {
2222
- return Y.createAbsolutePositionFromRelativePosition(Y.decodeRelativePosition(stringToArray(cursor)), text.doc)?.index ?? 0;
2223
- }
2224
- });
2225
-
2226
- // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2227
- var yjs = (content, awareness2) => [
2228
- CursorConverter.of(cursorConverter2(content)),
2229
- yCollab(content, awareness2)
2230
- ];
2231
-
2232
2567
  // packages/ui/react-ui-editor/src/themes/default.ts
2233
- import get6 from "lodash.get";
2568
+ import get3 from "lodash.get";
2234
2569
  var defaultTheme = {
2235
2570
  //
2236
2571
  // Main layout:
@@ -2259,21 +2594,22 @@ var defaultTheme = {
2259
2594
  "&.cm-focused": {
2260
2595
  outline: "none"
2261
2596
  },
2262
- "& .cm-scroller": {
2263
- overflow: "visible",
2264
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2597
+ ".cm-scroller": {
2598
+ // overflow: 'visible',
2599
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(","),
2600
+ lineHeight: 1.4
2265
2601
  },
2266
- "& .cm-content": {
2602
+ ".cm-content": {
2267
2603
  padding: 0,
2268
2604
  // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
2269
2605
  fontSize: "16px"
2270
2606
  },
2271
2607
  "&light .cm-content": {
2272
- color: get6(tokens, "extend.colors.neutral.900", "black"),
2608
+ color: get3(tokens, "extend.semanticColors.base.fg.light", "black"),
2273
2609
  caretColor: "black"
2274
2610
  },
2275
2611
  "&dark .cm-content": {
2276
- color: get6(tokens, "extend.colors.neutral.100", "white"),
2612
+ color: get3(tokens, "extend.semanticColors.base.fg.dark", "white"),
2277
2613
  caretColor: "white"
2278
2614
  },
2279
2615
  //
@@ -2285,158 +2621,123 @@ var defaultTheme = {
2285
2621
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2286
2622
  borderLeft: "2px solid white"
2287
2623
  },
2288
- "& .cm-placeholder": {
2289
- fontWeight: 100
2624
+ "&light .cm-placeholder": {
2625
+ color: get3(tokens, "extend.semanticColors.description.light", "rgba(0,0,0,.2)")
2626
+ },
2627
+ "&dark .cm-placeholder": {
2628
+ color: get3(tokens, "extend.semanticColors.description.dark", "rgba(255,255,255,.2)")
2290
2629
  },
2291
2630
  //
2292
2631
  // line
2293
2632
  //
2294
- "& .cm-line": {
2633
+ ".cm-line": {
2295
2634
  paddingInline: 0
2296
2635
  },
2297
- "& .cm-activeLine": {
2298
- background: "inherit"
2636
+ ".cm-activeLine": {
2637
+ background: "transparent"
2299
2638
  },
2300
2639
  //
2301
2640
  // Selection
2302
2641
  //
2303
2642
  "&light .cm-selectionBackground": {
2304
- background: get6(tokens, "extend.colors.primary.100")
2643
+ background: get3(tokens, "extend.colors.primary.100")
2305
2644
  },
2306
2645
  "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2307
- background: get6(tokens, "extend.colors.primary.200")
2646
+ background: get3(tokens, "extend.colors.primary.200")
2308
2647
  },
2309
2648
  "&dark .cm-selectionBackground": {
2310
- background: get6(tokens, "extend.colors.primary.700")
2649
+ background: get3(tokens, "extend.colors.primary.700")
2311
2650
  },
2312
2651
  "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2313
- background: get6(tokens, "extend.colors.primary.600")
2652
+ background: get3(tokens, "extend.colors.primary.600")
2314
2653
  },
2315
2654
  //
2316
2655
  // Search
2317
2656
  //
2318
2657
  "&light .cm-searchMatch": {
2319
- backgroundColor: get6(tokens, "extend.colors.yellow.100")
2658
+ backgroundColor: get3(tokens, "extend.colors.yellow.100")
2320
2659
  },
2321
2660
  "&dark .cm-searchMatch": {
2322
- backgroundColor: get6(tokens, "extend.colors.yellow.700")
2323
- },
2324
- //
2325
- // collaboration
2326
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2327
- //
2328
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2329
- mixBlendMode: "multiply"
2330
- },
2331
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2332
- mixBlendMode: "screen"
2333
- },
2334
- "& .cm-ySelectionInfo": {
2335
- padding: "2px 4px",
2336
- marginBlockStart: "-4px"
2337
- },
2338
- "& .cm-ySelection, & .cm-selectionMatch": {
2339
- paddingBlockStart: ".15em",
2340
- paddingBlockEnd: ".15em"
2341
- },
2342
- "& .cm-ySelectionCaret": {
2343
- display: "inline-block",
2344
- insetBlockStart: ".1em",
2345
- blockSize: "1.4em",
2346
- verticalAlign: "top"
2347
- },
2348
- "& .cm-yLineSelection": {
2349
- margin: 0
2661
+ backgroundColor: get3(tokens, "extend.colors.yellow.700")
2350
2662
  },
2351
2663
  //
2352
2664
  // link
2353
2665
  //
2354
- "& .cm-link": {
2355
- color: get6(tokens, "extend.colors.primary.500"),
2666
+ ".cm-link": {
2667
+ color: get3(tokens, "extend.colors.primary.500"),
2356
2668
  textDecorationLine: "underline",
2357
2669
  textDecorationThickness: "1px",
2358
2670
  textUnderlineOffset: "2px",
2359
2671
  borderRadius: ".125rem",
2360
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2672
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2361
2673
  },
2362
2674
  //
2363
2675
  // tooltip
2364
2676
  //
2365
- "& .cm-tooltip": {
2677
+ ".cm-tooltip": {
2366
2678
  border: "none"
2367
2679
  },
2368
- "& .cm-tooltip-below": {},
2680
+ ".cm-tooltip-below": {},
2369
2681
  //
2370
2682
  // autocomplete
2371
2683
  //
2372
- "& .cm-tooltip-autocomplete": {
2684
+ ".cm-tooltip-autocomplete": {
2373
2685
  marginTop: "4px",
2374
2686
  marginLeft: "-3px"
2375
2687
  },
2376
- "& .cm-tooltip-autocomplete ul li": {},
2377
- "& .cm-tooltip-autocomplete ul li[aria-selected]": {},
2378
- "& .cm-completionIcon": {
2688
+ ".cm-tooltip-autocomplete ul li": {},
2689
+ ".cm-tooltip-autocomplete ul li[aria-selected]": {},
2690
+ ".cm-completionIcon": {
2379
2691
  display: "none"
2380
2692
  },
2381
- "& .cm-completionLabel": {
2382
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2693
+ ".cm-completionLabel": {
2694
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2383
2695
  },
2384
- "& .cm-completionMatchedText": {
2696
+ ".cm-completionMatchedText": {
2385
2697
  textDecoration: "none"
2386
2698
  },
2387
2699
  //
2388
- // widgets
2389
- //
2390
- "& .cm-widgetBuffer": {
2391
- display: "none",
2392
- height: 0
2393
- },
2394
- //
2395
2700
  // table
2396
2701
  //
2397
- "& .cm-table *": {
2398
- fontFamily: `${get6(tokens, "fontFamily.mono", []).join(",")} !important`,
2702
+ ".cm-table *": {
2703
+ fontFamily: `${get3(tokens, "fontFamily.mono", []).join(",")} !important`,
2399
2704
  textDecoration: "none !important"
2400
2705
  },
2401
- "& .cm-table-head": {
2706
+ ".cm-table-head": {
2402
2707
  padding: "2px 16px 2px 0px",
2403
- borderBottom: `1px solid ${get6(tokens, "extend.colors.neutral.500")}`,
2708
+ borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.500")}`,
2404
2709
  fontWeight: 100,
2405
2710
  textAlign: "left",
2406
- color: get6(tokens, "extend.colors.neutral.500")
2711
+ color: get3(tokens, "extend.colors.neutral.500")
2407
2712
  },
2408
- "& .cm-table-cell": {
2713
+ ".cm-table-cell": {
2409
2714
  padding: "2px 16px 2px 0px"
2410
2715
  },
2411
2716
  //
2412
2717
  // image
2413
2718
  //
2414
- "& .cm-image": {
2415
- margin: "0.5rem 0"
2719
+ ".cm-image": {
2720
+ display: "block",
2721
+ height: "0"
2722
+ },
2723
+ ".cm-image.cm-loaded-image": {
2724
+ height: "auto",
2725
+ borderTop: "0.5rem solid transparent",
2726
+ borderBottom: "0.5rem solid transparent"
2416
2727
  },
2417
2728
  //
2418
2729
  // font size
2419
- // 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.
2730
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2731
+ // but it's far more verbose than it needs to be.
2420
2732
  //
2421
- ...Object.keys(get6(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2422
- const height = get6(tokens, [
2423
- "extend",
2424
- "fontSize",
2425
- fontSize,
2426
- 1,
2427
- "lineHeight"
2428
- ]);
2429
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2430
- height
2431
- };
2432
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2433
- height
2434
- };
2435
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2436
- height
2437
- };
2438
- return acc;
2439
- }, {}),
2733
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2734
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2735
+ //
2736
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2737
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2738
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2739
+ // return acc;
2740
+ // }, {}),
2440
2741
  /**
2441
2742
  * Panels
2442
2743
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2453,144 +2754,146 @@ var defaultTheme = {
2453
2754
  * </div>
2454
2755
  * </div
2455
2756
  */
2456
- "& .cm-panels": {
2457
- border: `1px solid ${get6(tokens, "extend.colors.neutral.200")}`
2757
+ ".cm-panels": {
2758
+ border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`
2458
2759
  },
2459
- "& .cm-panel": {
2460
- background: get6(tokens, "extend.colors.neutral.50"),
2461
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2760
+ ".cm-panel": {
2761
+ background: get3(tokens, "extend.colors.neutral.50"),
2762
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2462
2763
  },
2463
- "& .cm-button": {
2764
+ ".cm-button": {
2464
2765
  margin: "4px",
2465
- fontFamily: get6(tokens, "fontFamily.body", []).join(","),
2466
- background: get6(tokens, "extend.colors.neutral.100"),
2766
+ fontFamily: get3(tokens, "fontFamily.body", []).join(","),
2767
+ background: get3(tokens, "extend.colors.neutral.100"),
2467
2768
  backgroundImage: "none",
2468
2769
  border: "none",
2469
2770
  "&:hover": {
2470
- background: get6(tokens, "extend.colors.neutral.200")
2771
+ background: get3(tokens, "extend.colors.neutral.200")
2471
2772
  },
2472
2773
  "&:active": {
2473
- background: get6(tokens, "extend.colors.neutral.300"),
2774
+ background: get3(tokens, "extend.colors.neutral.300"),
2474
2775
  backgroundImage: "none"
2475
2776
  }
2476
2777
  },
2477
- "& .cm-panel input[type=checkbox]": {
2778
+ ".cm-panel input[type=checkbox]": {
2478
2779
  marginRight: "0.4rem !important"
2479
2780
  }
2480
2781
  };
2481
2782
  var textTheme = {
2482
- "& .cm-scroller": {
2483
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2783
+ ".cm-scroller": {
2784
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2484
2785
  },
2485
- "& .cm-placeholder": {
2486
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2786
+ ".cm-placeholder": {
2787
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2487
2788
  }
2488
2789
  };
2489
2790
  var markdownTheme = {
2490
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2491
- "& .cm-placeholder": {
2492
- fontFamily: get6(tokens, "fontFamily.body", []).join(",")
2791
+ ".cm-scroller": {
2792
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2793
+ },
2794
+ ".cm-placeholder": {
2795
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2493
2796
  }
2494
2797
  };
2495
2798
  var codeTheme = {
2496
- "& .cm-scroller": {
2497
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2799
+ ".cm-scroller": {
2800
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2498
2801
  },
2499
- "& .cm-placeholder": {
2500
- fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
2802
+ ".cm-placeholder": {
2803
+ fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2501
2804
  }
2502
2805
  };
2503
2806
 
2504
2807
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
2808
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx";
2505
2809
  var EditorModes = [
2506
2810
  "default",
2507
2811
  "vim"
2508
2812
  ];
2509
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2813
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2510
2814
  const { themeMode } = useThemeContext();
2511
- const tabsterDOMAttribute = useFocusableGroup({
2512
- tabBehavior: "limited"
2513
- });
2514
- const [root, setRoot] = useState(null);
2515
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2516
- useImperativeHandle(forwardedRef, () => ({
2517
- root,
2518
- state,
2815
+ const rootRef = useRef(null);
2816
+ const [view, setView] = useState(null);
2817
+ useImperativeHandle(forwardedRef, () => view, [
2519
2818
  view
2520
- }), [
2521
- view,
2522
- state,
2523
- root
2524
- ]);
2525
- const { awareness: awareness2, peer } = model;
2526
- useEffect(() => {
2527
- if (awareness2 && peer) {
2528
- awareness2.setLocalStateField("user", {
2529
- name: peer.name ?? generateName(peer.id),
2530
- color: getColorForValue({
2531
- value: peer.id,
2532
- type: "color"
2533
- }),
2534
- colorLight: getColorForValue({
2535
- value: peer.id,
2536
- themeMode,
2537
- type: "highlight"
2538
- })
2539
- });
2540
- }
2541
- }, [
2542
- awareness2,
2543
- peer,
2544
- themeMode
2545
2819
  ]);
2546
- useEffect(() => {
2547
- if (view && comments2?.length) {
2548
- view.dispatch({
2549
- effects: setCommentRange.of({
2550
- model,
2551
- comments: comments2
2552
- })
2553
- });
2820
+ useEffect2(() => {
2821
+ if (autoFocus && !view?.hasFocus) {
2822
+ if (view?.state.selection.main?.from === 0) {
2823
+ const { to } = view.state.doc.lineAt(0);
2824
+ view?.dispatch({
2825
+ selection: {
2826
+ anchor: to
2827
+ }
2828
+ });
2829
+ }
2830
+ view?.focus();
2554
2831
  }
2555
2832
  }, [
2556
2833
  view,
2557
- comments2
2834
+ autoFocus
2558
2835
  ]);
2559
- useEffect(() => {
2560
- if (!root) {
2836
+ useEffect2(() => {
2837
+ if (!model || !rootRef.current) {
2561
2838
  return;
2562
2839
  }
2563
- const state2 = EditorState2.create({
2840
+ const state = EditorState2.create({
2564
2841
  doc: model.text(),
2842
+ selection,
2565
2843
  extensions: [
2566
- readonly && EditorState2.readOnly.of(readonly),
2567
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
2568
- editorMode === "vim" && vim(),
2844
+ // TODO(burdon): Doesn't catch errors in keymap functions.
2845
+ EditorView13.exceptionSink.of((err) => {
2846
+ log2.catch(err, void 0, {
2847
+ F: __dxlog_file3,
2848
+ L: 134,
2849
+ S: void 0,
2850
+ C: (f, a) => f(...a)
2851
+ });
2852
+ }),
2569
2853
  // Theme.
2570
- // TODO(burdon): Make theme configurable.
2854
+ // TODO(burdon): Make configurable.
2571
2855
  EditorView13.baseTheme(defaultTheme),
2572
- EditorView13.theme(slots?.editor?.theme ?? {}),
2856
+ EditorView13.theme(theme ?? {}),
2573
2857
  EditorView13.darkTheme.of(themeMode === "dark"),
2858
+ EditorView13.editorAttributes.of({
2859
+ class: slots.editor?.className ?? ""
2860
+ }),
2861
+ EditorView13.contentAttributes.of({
2862
+ class: slots.content?.className ?? ""
2863
+ }),
2864
+ // State.
2865
+ EditorView13.editable.of(!readonly),
2866
+ EditorState2.readOnly.of(!!readonly),
2574
2867
  // Storage and replication.
2868
+ // NOTE: This must come before user extensions.
2575
2869
  model.extension,
2870
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
2871
+ editorMode === "vim" && vim(),
2576
2872
  // Custom.
2577
2873
  ...extensions
2578
- ].filter(Boolean)
2874
+ ].filter(isNotFalsy3)
2579
2875
  });
2580
- view?.destroy();
2581
- setEditor({
2582
- state: state2,
2583
- view: new EditorView13({
2584
- state: state2,
2585
- parent: root
2586
- })
2876
+ const newView = new EditorView13({
2877
+ state,
2878
+ parent: rootRef.current,
2879
+ scrollTo,
2880
+ // NOTE: Uncomment to debug/monitor all transactions.
2881
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
2882
+ dispatchTransactions: (trs, view2) => {
2883
+ if (debug) {
2884
+ logChanges(trs);
2885
+ }
2886
+ view2.update(trs);
2887
+ }
2587
2888
  });
2889
+ view?.destroy();
2890
+ setView(newView);
2588
2891
  return () => {
2589
- view?.destroy();
2590
- setEditor(void 0);
2892
+ newView?.destroy();
2893
+ setView(null);
2591
2894
  };
2592
2895
  }, [
2593
- root,
2896
+ rootRef,
2594
2897
  model,
2595
2898
  readonly,
2596
2899
  editorMode,
@@ -2600,11 +2903,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2600
2903
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2601
2904
  switch (key) {
2602
2905
  case "Enter": {
2603
- view?.contentDOM.focus();
2906
+ view?.focus();
2604
2907
  break;
2605
2908
  }
2606
2909
  case "Escape": {
2607
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2910
+ if (editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey)) {
2911
+ view?.focus();
2912
+ }
2608
2913
  break;
2609
2914
  }
2610
2915
  }
@@ -2614,83 +2919,238 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2614
2919
  ]);
2615
2920
  return /* @__PURE__ */ React.createElement("div", {
2616
2921
  key: model.id,
2617
- ref: setRoot,
2922
+ role: "none",
2618
2923
  tabIndex: 0,
2619
- ...slots?.root,
2620
- ...editorMode !== "vim" && tabsterDOMAttribute,
2621
- onKeyUp: handleKeyUp
2924
+ ...slots.root,
2925
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
2926
+ onKeyUp: handleKeyUp,
2927
+ ref: rootRef
2622
2928
  });
2623
2929
  });
2624
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2930
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
2625
2931
  const { themeMode } = useThemeContext();
2626
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2932
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2627
2933
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2628
2934
  ref: forwardedRef,
2629
2935
  readonly,
2630
2936
  extensions: [
2631
- basicBundle({
2632
- readonly,
2937
+ createBasicBundle({
2633
2938
  themeMode,
2634
- placeholder: slots?.editor?.placeholder
2939
+ placeholder: placeholder3,
2940
+ lineWrapping
2635
2941
  }),
2636
2942
  ...extensions
2637
2943
  ],
2638
- slots,
2944
+ theme,
2945
+ slots: updatedSlots,
2639
2946
  ...props
2640
2947
  });
2641
2948
  });
2642
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2949
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
2643
2950
  const { themeMode } = useThemeContext();
2644
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2951
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2645
2952
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2646
2953
  ref: forwardedRef,
2647
2954
  readonly,
2648
2955
  extensions: [
2649
- markdownBundle({
2650
- readonly,
2956
+ createMarkdownExtensions({
2651
2957
  themeMode,
2652
- placeholder: slots?.editor?.placeholder
2958
+ placeholder: placeholder3
2653
2959
  }),
2654
2960
  ...extensions
2655
2961
  ],
2656
- slots,
2962
+ theme,
2963
+ slots: updatedSlots,
2657
2964
  ...props
2658
2965
  });
2659
2966
  });
2660
2967
  var defaultSlots = {
2661
2968
  root: {
2662
- className: mx3("p-2", inputSurface)
2969
+ className: mx3("grow", focusRing)
2970
+ },
2971
+ editor: {
2972
+ className: "bs-full"
2663
2973
  }
2664
2974
  };
2665
2975
  var defaultTextSlots = {
2666
- ...defaultSlots,
2667
- editor: {
2668
- theme: textTheme
2669
- }
2976
+ ...defaultSlots
2670
2977
  };
2671
2978
  var defaultMarkdownSlots = {
2672
- ...defaultSlots,
2673
- editor: {
2674
- theme: markdownTheme
2675
- }
2979
+ ...defaultSlots
2676
2980
  };
2677
2981
 
2678
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2679
- import get7 from "lodash.get";
2680
- import { getRawDoc } from "@dxos/echo-schema";
2681
- import { isNotNullOrUndefined } from "@dxos/util";
2982
+ // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
2983
+ import { At, CaretRight, ChatText, Code, Image, Link, ListBullets, ListChecks, ListNumbers, TextStrikethrough, Table as Table2, TextB, TextItalic } from "@phosphor-icons/react";
2984
+ import { createContext } from "@radix-ui/react-context";
2985
+ import React2 from "react";
2986
+ import { Button, DensityProvider, Select } from "@dxos/react-ui";
2987
+ import { getSize } from "@dxos/react-ui-theme";
2988
+ var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
2989
+ var ToolbarRoot = ({ children, onAction }) => {
2990
+ return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
2991
+ onAction
2992
+ }, /* @__PURE__ */ React2.createElement(DensityProvider, {
2993
+ density: "fine"
2994
+ }, /* @__PURE__ */ React2.createElement("div", {
2995
+ role: "toolbar",
2996
+ className: "flex w-full shrink-0 p-2 gap-4 items-center whitespace-nowrap overflow-hidden"
2997
+ }, children)));
2998
+ };
2999
+ var ToolbarButton = ({ Icon, onClick, title }) => {
3000
+ const { onAction } = useToolbarContext("ToolbarButton");
3001
+ const handleClick = () => {
3002
+ const action = onClick();
3003
+ if (action) {
3004
+ onAction?.(action);
3005
+ }
3006
+ };
3007
+ return /* @__PURE__ */ React2.createElement(Button, {
3008
+ variant: "ghost",
3009
+ classNames: "p-2",
3010
+ onClick: handleClick,
3011
+ title
3012
+ }, /* @__PURE__ */ React2.createElement(Icon, {
3013
+ className: getSize(5)
3014
+ }));
3015
+ };
3016
+ var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
3017
+ className: "grow"
3018
+ });
3019
+ var MarkdownHeading = () => {
3020
+ const { onAction } = useToolbarContext("MarkdownFormatting");
3021
+ return /* @__PURE__ */ React2.createElement(Select.Root, {
3022
+ defaultValue: "0",
3023
+ onValueChange: (value) => onAction?.({
3024
+ type: "heading",
3025
+ data: parseInt(value)
3026
+ })
3027
+ }, /* @__PURE__ */ React2.createElement(Select.TriggerButton, {
3028
+ classNames: "w-[8rem]"
3029
+ }), /* @__PURE__ */ React2.createElement(Select.Portal, null, /* @__PURE__ */ React2.createElement(Select.Content, null, /* @__PURE__ */ React2.createElement(Select.Viewport, null, /* @__PURE__ */ React2.createElement(Select.Option, {
3030
+ value: "0"
3031
+ }, "Paragraph"), [
3032
+ 1,
3033
+ 2,
3034
+ 3,
3035
+ 4,
3036
+ 5,
3037
+ 6
3038
+ ].map((level) => /* @__PURE__ */ React2.createElement(Select.Option, {
3039
+ key: level,
3040
+ value: String(level)
3041
+ }, "Heading ", level))))));
3042
+ };
3043
+ var MarkdownStyles = () => /* @__PURE__ */ React2.createElement("div", {
3044
+ role: "none"
3045
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3046
+ Icon: TextB,
3047
+ title: "Bold",
3048
+ onClick: () => ({
3049
+ type: "bold"
3050
+ })
3051
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3052
+ Icon: TextItalic,
3053
+ title: "Italic",
3054
+ onClick: () => ({
3055
+ type: "italic"
3056
+ })
3057
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3058
+ Icon: TextStrikethrough,
3059
+ title: "Strike-through",
3060
+ onClick: () => ({
3061
+ type: "strikethrough"
3062
+ })
3063
+ }));
3064
+ var MarkdownLists = () => /* @__PURE__ */ React2.createElement("div", {
3065
+ role: "none"
3066
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3067
+ Icon: ListBullets,
3068
+ title: "Bullet list",
3069
+ onClick: () => ({
3070
+ type: "list"
3071
+ })
3072
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3073
+ Icon: ListNumbers,
3074
+ title: "Numbered list",
3075
+ onClick: () => ({
3076
+ type: "list-ordered"
3077
+ })
3078
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3079
+ Icon: ListChecks,
3080
+ title: "Task list",
3081
+ onClick: () => ({
3082
+ type: "list-tasks"
3083
+ })
3084
+ }));
3085
+ var MarkdownBlocks = () => /* @__PURE__ */ React2.createElement("div", {
3086
+ role: "none"
3087
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3088
+ Icon: CaretRight,
3089
+ title: "Block quite",
3090
+ onClick: () => ({
3091
+ type: "blockquote"
3092
+ })
3093
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3094
+ Icon: Code,
3095
+ title: "Code block",
3096
+ onClick: () => ({
3097
+ type: "codeblock"
3098
+ })
3099
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3100
+ Icon: Table2,
3101
+ title: "Table",
3102
+ onClick: () => ({
3103
+ type: "table"
3104
+ })
3105
+ }));
3106
+ var MarkdownLinks = () => /* @__PURE__ */ React2.createElement("div", {
3107
+ role: "none"
3108
+ }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3109
+ Icon: Link,
3110
+ title: "Link",
3111
+ onClick: () => ({
3112
+ type: "link"
3113
+ })
3114
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3115
+ Icon: At,
3116
+ title: "Mention",
3117
+ onClick: () => ({
3118
+ type: "mention"
3119
+ })
3120
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3121
+ Icon: Image,
3122
+ title: "Image",
3123
+ onClick: () => ({
3124
+ type: "image"
3125
+ })
3126
+ }));
3127
+ var MarkdownStandard = () => /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(MarkdownHeading, null), /* @__PURE__ */ React2.createElement(MarkdownStyles, null), /* @__PURE__ */ React2.createElement(MarkdownLists, null), /* @__PURE__ */ React2.createElement(MarkdownBlocks, null), /* @__PURE__ */ React2.createElement(MarkdownLinks, null));
3128
+ var MarkdownExtended = () => /* @__PURE__ */ React2.createElement(Toolbar.Button, {
3129
+ Icon: ChatText,
3130
+ title: "Create comment",
3131
+ onClick: () => ({
3132
+ type: "comment"
3133
+ })
3134
+ });
3135
+ var Toolbar = {
3136
+ Root: ToolbarRoot,
3137
+ Button: ToolbarButton,
3138
+ Separator: ToolbarSeparator,
3139
+ Markdown: MarkdownStandard,
3140
+ Extended: MarkdownExtended
3141
+ };
2682
3142
 
2683
3143
  // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2684
3144
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2685
3145
  import { Context as Context2 } from "@dxos/context";
2686
- import { invariant as invariant3 } from "@dxos/invariant";
3146
+ import { invariant as invariant2 } from "@dxos/invariant";
2687
3147
  import { log as log3 } from "@dxos/log";
2688
3148
  var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2689
3149
  var DEBOUNCE_INTERVAL = 100;
2690
3150
  var SpaceAwarenessProvider = class {
2691
3151
  constructor(params) {
2692
- this.remoteStateChange = new Event2();
2693
3152
  this._remoteStates = /* @__PURE__ */ new Map();
3153
+ this.remoteStateChange = new Event2();
2694
3154
  this._space = params.space;
2695
3155
  this._channel = params.channel;
2696
3156
  this._peerId = params.peerId;
@@ -2707,7 +3167,7 @@ var SpaceAwarenessProvider = class {
2707
3167
  await sleep(DEBOUNCE_INTERVAL);
2708
3168
  }
2709
3169
  });
2710
- const unsubscribe = this._space.listen(this._channel, (message) => {
3170
+ this._ctx.onDispose(this._space.listen(this._channel, (message) => {
2711
3171
  switch (message.payload.kind) {
2712
3172
  case "query": {
2713
3173
  this._handleQueryMessage();
@@ -2718,8 +3178,7 @@ var SpaceAwarenessProvider = class {
2718
3178
  break;
2719
3179
  }
2720
3180
  }
2721
- });
2722
- this._ctx.onDispose(unsubscribe);
3181
+ }));
2723
3182
  void this._space.postMessage(this._channel, {
2724
3183
  kind: "query"
2725
3184
  }).catch((err) => {
@@ -2727,7 +3186,7 @@ var SpaceAwarenessProvider = class {
2727
3186
  err
2728
3187
  }, {
2729
3188
  F: __dxlog_file4,
2730
- L: 85,
3189
+ L: 89,
2731
3190
  S: this,
2732
3191
  C: (f, a) => f(...a)
2733
3192
  });
@@ -2738,10 +3197,13 @@ var SpaceAwarenessProvider = class {
2738
3197
  this._ctx = void 0;
2739
3198
  this._postTask = void 0;
2740
3199
  }
3200
+ getRemoteStates() {
3201
+ return Array.from(this._remoteStates.values());
3202
+ }
2741
3203
  update(position) {
2742
- invariant3(this._postTask, void 0, {
3204
+ invariant2(this._postTask, void 0, {
2743
3205
  F: __dxlog_file4,
2744
- L: 96,
3206
+ L: 104,
2745
3207
  S: this,
2746
3208
  A: [
2747
3209
  "this._postTask",
@@ -2755,13 +3217,10 @@ var SpaceAwarenessProvider = class {
2755
3217
  };
2756
3218
  this._postTask.schedule();
2757
3219
  }
2758
- getRemoteStates() {
2759
- return Array.from(this._remoteStates.values());
2760
- }
2761
3220
  _handleQueryMessage() {
2762
- invariant3(this._postTask, void 0, {
3221
+ invariant2(this._postTask, void 0, {
2763
3222
  F: __dxlog_file4,
2764
- L: 111,
3223
+ L: 115,
2765
3224
  S: this,
2766
3225
  A: [
2767
3226
  "this._postTask",
@@ -2771,9 +3230,9 @@ var SpaceAwarenessProvider = class {
2771
3230
  this._postTask.schedule();
2772
3231
  }
2773
3232
  _handlePostMessage(message) {
2774
- invariant3(message.kind === "post", void 0, {
3233
+ invariant2(message.kind === "post", void 0, {
2775
3234
  F: __dxlog_file4,
2776
- L: 116,
3235
+ L: 120,
2777
3236
  S: this,
2778
3237
  A: [
2779
3238
  "message.kind === 'post'",
@@ -2785,229 +3244,240 @@ var SpaceAwarenessProvider = class {
2785
3244
  }
2786
3245
  };
2787
3246
 
2788
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2789
- import { StateField as StateField6 } from "@codemirror/state";
2790
- import { useEffect as useEffect2, useState as useState2 } from "react";
2791
- import { isAutomergeObject } from "@dxos/react-client/echo";
3247
+ // packages/ui/react-ui-editor/src/hooks/defs.ts
3248
+ import { StateField as StateField5 } from "@codemirror/state";
3249
+ var modelState = StateField5.define({
3250
+ create: () => void 0,
3251
+ update: (model) => model
3252
+ });
2792
3253
 
2793
- // packages/ui/react-ui-editor/src/hooks/yjs.ts
2794
- import * as decoding from "lib0/decoding";
2795
- import * as encoding from "lib0/encoding";
2796
- import { Observable } from "lib0/observable";
2797
- import * as YP from "y-protocols/awareness";
2798
- import * as Y2 from "yjs";
2799
- import { invariant as invariant4 } from "@dxos/invariant";
2800
- import { log as log4 } from "@dxos/log";
2801
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2802
- var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2803
- var createYjsModel = ({ identity, space, text }) => {
2804
- invariant4(text?.doc && text?.content, void 0, {
2805
- F: __dxlog_file5,
2806
- L: 23,
2807
- S: void 0,
2808
- A: [
2809
- "text?.doc && text?.content",
2810
- ""
2811
- ]
2812
- });
2813
- const provider = space ? new YAwarenessProvider({
2814
- space,
2815
- doc: text.doc,
2816
- channel: `yjs.awareness.${text.id}`
2817
- }) : void 0;
2818
- const model = {
2819
- id: text.doc.guid,
2820
- content: text.content,
2821
- text: () => text.content.toString(),
2822
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2823
- // TODO(dmaretskyi): Refactor as cursor-converter.
2824
- getCursorFromRange: (range) => {
2825
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2826
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2827
- return [
2828
- arrayToString2(from),
2829
- arrayToString2(to)
2830
- ].join(":");
2831
- },
2832
- getRangeFromCursor: (cursor) => {
2833
- invariant4(text.doc, void 0, {
2834
- F: __dxlog_file5,
2835
- L: 40,
2836
- S: void 0,
2837
- A: [
2838
- "text.doc",
2839
- ""
2840
- ]
3254
+ // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
3255
+ var useActionHandler = (view) => {
3256
+ return (action) => {
3257
+ if (!view) {
3258
+ return;
3259
+ }
3260
+ switch (action.type) {
3261
+ case "heading":
3262
+ setHeading(parseInt(action.data))(view);
3263
+ break;
3264
+ case "bold":
3265
+ toggleBold(view);
3266
+ break;
3267
+ case "italic":
3268
+ toggleItalic(view);
3269
+ break;
3270
+ case "strikethrough":
3271
+ toggleStrikethrough(view);
3272
+ break;
3273
+ case "list":
3274
+ toggleList(view);
3275
+ break;
3276
+ case "codeblock":
3277
+ insertCodeblock(view);
3278
+ break;
3279
+ case "table":
3280
+ insertTable(view);
3281
+ break;
3282
+ case "comment":
3283
+ createComment(view);
3284
+ break;
3285
+ }
3286
+ setTimeout(() => {
3287
+ view.focus();
3288
+ view.dispatch({
3289
+ selection: view.state.selection
2841
3290
  });
2842
- const parts = cursor.split(":");
2843
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2844
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2845
- return from && to ? {
2846
- from: from.index,
2847
- to: to.index
2848
- } : void 0;
2849
- },
2850
- extension: [
2851
- modelState.init(() => model),
2852
- yjs(text.content, provider?.awareness)
2853
- ],
2854
- awareness: provider?.awareness,
2855
- peer: identity ? {
2856
- id: identity.identityKey.toHex(),
2857
- name: identity.profile?.displayName
2858
- } : void 0
3291
+ });
2859
3292
  };
2860
- return model;
2861
3293
  };
2862
- var messageAwareness = 1;
2863
- var messageQueryAwareness = 3;
2864
- var YAwarenessProvider = class extends Observable {
2865
- constructor({ space, doc, channel, awareness: awareness2 }) {
2866
- super();
2867
- this._space = space;
2868
- this._awareness = awareness2 ?? new YP.Awareness(doc);
2869
- this._channel = channel;
2870
- this._clientId = doc.clientID;
2871
- this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
2872
- this._space.listen(this._channel, this._handleSpaceMessage.bind(this));
2873
- if (typeof window !== "undefined") {
2874
- window.addEventListener("beforeunload", this._handleBeforeUnload.bind(this));
2875
- } else if (typeof process !== "undefined") {
2876
- process.on("exit", this._handleBeforeUnload.bind(this));
2877
- }
2878
- const encoderAwarenessQuery = encoding.createEncoder();
2879
- encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
2880
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2881
- const encoderAwarenessState = encoding.createEncoder();
2882
- encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2883
- encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2884
- this._clientId
2885
- ]));
2886
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
2887
- }
2888
- get awareness() {
2889
- return this._awareness;
2890
- }
2891
- _handleAwarenessUpdate({ added, updated, removed }, origin) {
2892
- log4("awareness update", {
2893
- added,
2894
- updated,
2895
- removed,
2896
- origin
2897
- }, {
2898
- F: __dxlog_file5,
2899
- L: 122,
2900
- S: this,
2901
- C: (f, a) => f(...a)
2902
- });
2903
- const changedClients = added.concat(updated).concat(removed);
2904
- const encoderAwareness = encoding.createEncoder();
2905
- encoding.writeVarUint(encoderAwareness, messageAwareness);
2906
- encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2907
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2908
- }
2909
- _handleSpaceMessage({ payload }) {
2910
- log4("space message", payload, {
2911
- F: __dxlog_file5,
2912
- L: 131,
2913
- S: this,
2914
- C: (f, a) => f(...a)
2915
- });
2916
- const data = new Uint8Array(Array.from(Object.values(payload)));
2917
- const encoder = this._readMessage(data);
2918
- if (encoder) {
2919
- void this._space.postMessage(this._channel, encoding.toUint8Array(encoder));
3294
+
3295
+ // packages/ui/react-ui-editor/src/hooks/useEditorView.ts
3296
+ import { useState as useState2 } from "react";
3297
+ var useEditorView = () => {
3298
+ const [view, setView] = useState2(null);
3299
+ return [
3300
+ (ref) => {
3301
+ setView(ref);
3302
+ },
3303
+ view
3304
+ ];
3305
+ };
3306
+
3307
+ // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
3308
+ import { EditorSelection, EditorState as EditorState3 } from "@codemirror/state";
3309
+ import { EditorView as EditorView14 } from "@codemirror/view";
3310
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
3311
+ import { log as log4 } from "@dxos/log";
3312
+ import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
3313
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
3314
+ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions } = {}) => {
3315
+ const onUpdate = useRef2();
3316
+ const [view, setView] = useState3();
3317
+ const parentRef = useRef2(null);
3318
+ useEffect3(() => {
3319
+ if (parentRef.current) {
3320
+ const state = EditorState3.create({
3321
+ doc,
3322
+ selection,
3323
+ extensions: [
3324
+ // TODO(burdon): Doesn't catch errors in keymap functions.
3325
+ EditorView14.exceptionSink.of((err) => {
3326
+ log4.catch(err, void 0, {
3327
+ F: __dxlog_file5,
3328
+ L: 57,
3329
+ S: void 0,
3330
+ C: (f, a) => f(...a)
3331
+ });
3332
+ }),
3333
+ extensions,
3334
+ EditorView14.updateListener.of(() => {
3335
+ onUpdate.current?.();
3336
+ })
3337
+ ].filter(isNotFalsy4)
3338
+ });
3339
+ const view2 = new EditorView14({
3340
+ parent: parentRef.current,
3341
+ scrollTo,
3342
+ state,
3343
+ // NOTE: Uncomment to debug/monitor all transactions.
3344
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
3345
+ dispatchTransactions: (trs, view3) => {
3346
+ if (debug) {
3347
+ logChanges(trs);
3348
+ }
3349
+ view3.update(trs);
3350
+ }
3351
+ });
3352
+ setView(view2);
2920
3353
  }
2921
- }
2922
- _readMessage(message) {
2923
- const decoder = decoding.createDecoder(message);
2924
- const encoder = encoding.createEncoder();
2925
- const messageType = decoding.readVarUint(decoder);
2926
- let sendReply = false;
2927
- switch (messageType) {
2928
- case messageAwareness: {
2929
- YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2930
- break;
3354
+ return () => {
3355
+ view?.destroy();
3356
+ };
3357
+ }, [
3358
+ parentRef
3359
+ ]);
3360
+ useEffect3(() => {
3361
+ if (view) {
3362
+ if (!selection && !view.state.selection.main.anchor) {
3363
+ selection = EditorSelection.single(view.state.doc.line(1).to);
2931
3364
  }
2932
- case messageQueryAwareness: {
2933
- encoding.writeVarUint(encoder, messageAwareness);
2934
- encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2935
- sendReply = true;
2936
- break;
3365
+ if (selection || scrollTo) {
3366
+ onUpdate.current = () => {
3367
+ onUpdate.current = void 0;
3368
+ view.dispatch({
3369
+ selection,
3370
+ effects: scrollTo && [
3371
+ scrollTo
3372
+ ],
3373
+ scrollIntoView: !scrollTo
3374
+ });
3375
+ };
2937
3376
  }
2938
- default: {
2939
- console.error("Invalid message:", messageType);
2940
- return encoder;
3377
+ if (autoFocus) {
3378
+ view.focus();
2941
3379
  }
2942
3380
  }
2943
- if (!sendReply) {
2944
- return null;
2945
- }
2946
- return encoder;
2947
- }
2948
- _handleBeforeUnload() {
2949
- YP.removeAwarenessStates(this._awareness, [
2950
- this._clientId
2951
- ], "window unload");
2952
- }
3381
+ }, [
3382
+ view,
3383
+ autoFocus,
3384
+ selection,
3385
+ scrollTo
3386
+ ]);
3387
+ return {
3388
+ parentRef,
3389
+ view
3390
+ };
3391
+ };
3392
+ var createDataExtensions = ({ readonly = false } = {}) => {
3393
+ return [
3394
+ //
3395
+ EditorState3.readOnly.of(readonly),
3396
+ EditorView14.editable.of(!readonly)
3397
+ ];
3398
+ };
3399
+ var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
3400
+ return [
3401
+ //
3402
+ EditorView14.baseTheme(defaultTheme),
3403
+ theme && EditorView14.theme(theme),
3404
+ EditorView14.darkTheme.of(themeMode === "dark"),
3405
+ EditorView14.editorAttributes.of({
3406
+ class: slots?.editor?.className ?? ""
3407
+ }),
3408
+ EditorView14.contentAttributes.of({
3409
+ class: slots?.content?.className ?? ""
3410
+ })
3411
+ ].filter(isNotFalsy4);
2953
3412
  };
2954
3413
 
2955
3414
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2956
- var modelState = StateField6.define({
2957
- create: () => void 0,
2958
- update: (model) => model
2959
- });
3415
+ import get4 from "lodash.get";
3416
+ import { useEffect as useEffect4, useState as useState4 } from "react";
3417
+ import { generateName } from "@dxos/display-name";
3418
+ import { getRawDoc } from "@dxos/echo-schema";
3419
+ import { invariant as invariant3 } from "@dxos/invariant";
3420
+ import { isAutomergeObject } from "@dxos/react-client/echo";
3421
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2960
3422
  var useTextModel = (props) => {
2961
3423
  const { identity, space, text } = props;
2962
- const [model, setModel] = useState2(() => createModel(props));
2963
- useEffect2(() => setModel(createModel(props)), [
3424
+ const [model, setModel] = useState4();
3425
+ useEffect4(() => setModel(createModel(props)), [
2964
3426
  identity,
2965
3427
  space,
2966
3428
  text
2967
3429
  ]);
2968
3430
  return model;
2969
3431
  };
2970
- var createModel = (props) => {
2971
- const { text } = props;
2972
- if (isAutomergeObject(text)) {
2973
- return createAutomergeModel(props);
2974
- } else if (text?.doc) {
2975
- return createYjsModel(props);
2976
- } else {
2977
- return void 0;
2978
- }
3432
+ var useInMemoryTextModel = ({ id, defaultContent }) => {
3433
+ const [content, setContent] = useState4(defaultContent ?? "");
3434
+ return {
3435
+ id,
3436
+ content,
3437
+ setContent,
3438
+ text: () => content
3439
+ };
2979
3440
  };
2980
-
2981
- // packages/ui/react-ui-editor/src/hooks/automerge.ts
2982
- var createAutomergeModel = ({ space, identity, text }) => {
3441
+ var createModel = ({ space, identity, text }) => {
3442
+ invariant3(isAutomergeObject(text), void 0, {
3443
+ F: __dxlog_file6,
3444
+ L: 50,
3445
+ S: void 0,
3446
+ A: [
3447
+ "isAutomergeObject(text)",
3448
+ ""
3449
+ ]
3450
+ });
2983
3451
  const obj = text;
2984
3452
  const doc = getRawDoc(obj, [
2985
3453
  obj.field
2986
3454
  ]);
2987
- const awarenessProvider = space && new SpaceAwarenessProvider({
3455
+ const awarenessProvider2 = space && new SpaceAwarenessProvider({
2988
3456
  space,
2989
3457
  channel: `automerge.awareness.${obj.id}`,
2990
3458
  info: {
2991
- displayName: identity?.profile?.displayName ?? "",
3459
+ displayName: identity ? identity.profile?.displayName ?? generateName(identity.identityKey.toHex()) : void 0,
2992
3460
  color: cursorColor.color,
2993
3461
  lightColor: cursorColor.light
2994
3462
  },
2995
3463
  peerId: identity?.identityKey.toHex() ?? "Anonymous"
2996
3464
  });
3465
+ const extensions = [
3466
+ //
3467
+ modelState.init(() => model),
3468
+ automerge({
3469
+ handle: doc.handle,
3470
+ path: doc.path
3471
+ })
3472
+ ];
3473
+ if (awarenessProvider2) {
3474
+ extensions.push(awareness(awarenessProvider2));
3475
+ }
2997
3476
  const model = {
2998
3477
  id: obj.id,
2999
3478
  content: doc,
3000
- text: () => get7(doc.handle.docSync(), doc.path),
3001
- // TODO(burdon): https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html
3002
- extension: [
3003
- modelState.init(() => model),
3004
- automerge3({
3005
- handle: doc.handle,
3006
- path: doc.path
3007
- }),
3008
- awarenessProvider && AwarenessProvider.of(awarenessProvider),
3009
- awareness()
3010
- ].filter(isNotNullOrUndefined),
3479
+ text: () => get4(doc.handle.docSync(), doc.path),
3480
+ extension: extensions,
3011
3481
  peer: identity ? {
3012
3482
  id: identity.identityKey.toHex(),
3013
3483
  name: identity.profile?.displayName
@@ -3016,8 +3486,8 @@ var createAutomergeModel = ({ space, identity, text }) => {
3016
3486
  return model;
3017
3487
  };
3018
3488
  export {
3019
- AwarenessProvider,
3020
3489
  BaseTextEditor,
3490
+ Cursor,
3021
3491
  Doc,
3022
3492
  EditorModes,
3023
3493
  MarkdownEditor,
@@ -3025,45 +3495,66 @@ export {
3025
3495
  SpaceAwarenessProvider,
3026
3496
  TextEditor,
3027
3497
  TextKind,
3028
- YAwarenessProvider,
3498
+ Toolbar,
3029
3499
  YText,
3030
3500
  YXmlFragment,
3031
3501
  autocomplete,
3032
- automerge3 as automerge,
3502
+ automerge,
3033
3503
  awareness,
3034
- basicBundle,
3504
+ awarenessProvider,
3035
3505
  blast,
3506
+ callbackWrapper,
3036
3507
  code2 as code,
3037
3508
  codeTheme,
3038
3509
  comments,
3039
- createAutomergeModel,
3040
- createYjsModel,
3510
+ createBasicBundle,
3511
+ createComment,
3512
+ createDataExtensions,
3513
+ createMarkdownExtensions,
3514
+ createThemeExtensions,
3041
3515
  defaultMarkdownSlots,
3042
3516
  defaultOptions,
3043
3517
  defaultSlots,
3044
3518
  defaultTextSlots,
3045
3519
  defaultTheme,
3520
+ focusComment,
3521
+ formatting,
3522
+ getToken,
3523
+ heading2 as heading,
3046
3524
  hr,
3047
3525
  image,
3526
+ insertCodeblock,
3527
+ insertTable,
3528
+ keymap7 as keymap,
3048
3529
  link,
3049
3530
  listener,
3050
- markdownBundle,
3531
+ logChanges,
3051
3532
  markdownHighlightStyle,
3052
3533
  markdownTags,
3053
3534
  markdownTagsExtensions,
3054
3535
  markdownTheme,
3055
3536
  mention,
3056
- mermaid,
3057
3537
  modelState,
3058
- setCommentRange,
3059
- setFocus,
3538
+ outliner,
3539
+ setComments,
3540
+ setHeading,
3060
3541
  setSelection,
3061
3542
  table,
3062
3543
  tags2 as tags,
3063
3544
  tasklist,
3064
3545
  textTheme,
3546
+ toggleBold,
3547
+ toggleItalic,
3548
+ toggleList,
3549
+ toggleStrikethrough,
3550
+ toggleStyle,
3065
3551
  typewriter,
3552
+ useActionHandler,
3553
+ useComments,
3554
+ useEditorView,
3555
+ useInMemoryTextModel,
3556
+ useTextEditor,
3066
3557
  useTextModel,
3067
- yjs
3558
+ useToolbarContext
3068
3559
  };
3069
3560
  //# sourceMappingURL=index.mjs.map