@dxos/react-ui-editor 0.3.10 → 0.3.11-main.008b7c6

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 (211) hide show
  1. package/dist/lib/browser/index.mjs +1537 -847
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +14 -10
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +3 -2
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +3 -0
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/{hooks/automerge → components/TextEditor}/automerge.stories.d.ts +6 -2
  11. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/TextEditor/index.d.ts +0 -2
  14. package/dist/types/src/components/TextEditor/index.d.ts.map +1 -1
  15. package/dist/types/src/{hooks/yjs → components/TextEditor}/yjs.stories.d.ts +1 -1
  16. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +1 -0
  17. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -0
  18. package/dist/types/src/extensions/automerge/automerge.d.ts +9 -0
  19. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/cursor.d.ts +5 -0
  21. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/defs.d.ts +26 -0
  23. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  24. package/dist/types/src/extensions/automerge/index.d.ts +3 -0
  25. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  26. package/dist/types/src/{hooks/automerge/PatchSemaphore.d.ts → extensions/automerge/semaphore.d.ts} +5 -5
  27. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
  28. package/dist/types/src/extensions/automerge/update-automerge.d.ts +5 -0
  29. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  30. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
  31. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  32. package/dist/types/src/extensions/awareness.d.ts +46 -0
  33. package/dist/types/src/extensions/awareness.d.ts.map +1 -0
  34. package/dist/types/src/extensions/basic.d.ts.map +1 -0
  35. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  36. package/dist/types/src/extensions/code.d.ts.map +1 -0
  37. package/dist/types/src/extensions/comments.d.ts +50 -0
  38. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  39. package/dist/types/src/extensions/hr.d.ts +2 -0
  40. package/dist/types/src/extensions/hr.d.ts.map +1 -0
  41. package/dist/types/src/extensions/image.d.ts.map +1 -0
  42. package/dist/types/src/{components/TextEditor/extensions → extensions}/index.d.ts +6 -3
  43. package/dist/types/src/extensions/index.d.ts.map +1 -0
  44. package/dist/types/src/{components/TextEditor/extensions → extensions}/link.d.ts +1 -0
  45. package/dist/types/src/extensions/link.d.ts.map +1 -0
  46. package/dist/types/src/extensions/listener.d.ts +10 -0
  47. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  48. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  49. package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/highlight.d.ts +0 -1
  50. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  51. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  52. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  53. package/dist/types/src/extensions/mermaid.d.ts +3 -0
  54. package/dist/types/src/extensions/mermaid.d.ts.map +1 -0
  55. package/dist/types/src/extensions/table.d.ts.map +1 -0
  56. package/dist/types/src/extensions/tasklist.d.ts.map +1 -0
  57. package/dist/types/src/{components/TextEditor/extensions/demo.d.ts → extensions/typewriter.d.ts} +2 -3
  58. package/dist/types/src/extensions/typewriter.d.ts.map +1 -0
  59. package/dist/types/src/extensions/yjs/cursor.d.ts +4 -0
  60. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -0
  61. package/dist/types/src/extensions/yjs/index.d.ts +2 -0
  62. package/dist/types/src/extensions/yjs/index.d.ts.map +1 -0
  63. package/dist/types/src/extensions/yjs/yjs.d.ts +4 -0
  64. package/dist/types/src/extensions/yjs/yjs.d.ts.map +1 -0
  65. package/dist/types/src/extensions/yjs/yjs.test.d.ts +2 -0
  66. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +1 -0
  67. package/dist/types/src/hooks/automerge.d.ts +3 -0
  68. package/dist/types/src/hooks/automerge.d.ts.map +1 -0
  69. package/dist/types/src/hooks/awareness-provider.d.ts +28 -0
  70. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -0
  71. package/dist/types/src/hooks/index.d.ts +2 -0
  72. package/dist/types/src/hooks/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useTextModel.d.ts +20 -11
  74. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  75. package/dist/types/src/hooks/{yjs/space-provider.d.ts → yjs.d.ts} +8 -8
  76. package/dist/types/src/hooks/yjs.d.ts.map +1 -0
  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/cursors.d.ts.map +1 -0
  80. package/dist/types/src/styles/index.d.ts +1 -0
  81. package/dist/types/src/styles/index.d.ts.map +1 -1
  82. package/dist/types/src/testing/replicator.d.ts +2 -2
  83. package/dist/types/src/testing/replicator.d.ts.map +1 -1
  84. package/dist/types/src/{components/TextEditor/themes → themes}/default.d.ts +5 -13
  85. package/dist/types/src/themes/default.d.ts.map +1 -0
  86. package/dist/types/src/themes/index.d.ts.map +1 -0
  87. package/dist/types/src/util/cursor.d.ts +17 -0
  88. package/dist/types/src/util/cursor.d.ts.map +1 -0
  89. package/dist/types/src/util/index.d.ts +3 -0
  90. package/dist/types/src/util/index.d.ts.map +1 -0
  91. package/dist/types/src/util/util.d.ts +5 -0
  92. package/dist/types/src/util/util.d.ts.map +1 -0
  93. package/package.json +25 -20
  94. package/src/components/TextEditor/MarkdownEditor.stories.tsx +130 -71
  95. package/src/components/TextEditor/TextEditor.stories.tsx +15 -2
  96. package/src/components/TextEditor/TextEditor.tsx +30 -26
  97. package/src/{hooks/automerge → components/TextEditor}/automerge.stories.tsx +67 -8
  98. package/src/components/TextEditor/codemirror.stories.ts +3 -2
  99. package/src/components/TextEditor/index.ts +0 -3
  100. package/src/{hooks/yjs → components/TextEditor}/yjs.stories.tsx +1 -1
  101. package/src/extensions/automerge/automerge.ts +94 -0
  102. package/src/extensions/automerge/cursor.ts +51 -0
  103. package/src/extensions/automerge/defs.ts +62 -0
  104. package/src/extensions/automerge/index.ts +6 -0
  105. package/src/{hooks/automerge/PatchSemaphore.ts → extensions/automerge/semaphore.ts} +18 -15
  106. package/src/{hooks/automerge/translation/codemirror-to-automerge.ts → extensions/automerge/update-automerge.ts} +6 -7
  107. package/src/{hooks/automerge/translation/automerge-to-codemirror.ts → extensions/automerge/update-codemirror.ts} +14 -11
  108. package/src/extensions/awareness.ts +316 -0
  109. package/src/{components/TextEditor/extensions → extensions}/code.ts +13 -13
  110. package/src/extensions/comments.ts +421 -0
  111. package/src/extensions/hr.ts +74 -0
  112. package/src/{components/TextEditor/extensions → extensions}/image.ts +3 -1
  113. package/src/{components/TextEditor/extensions → extensions}/index.ts +6 -3
  114. package/src/{components/TextEditor/extensions → extensions}/link.ts +66 -10
  115. package/src/extensions/listener.ts +35 -0
  116. package/src/{components/TextEditor/extensions → extensions}/markdown/highlight.ts +6 -9
  117. package/src/extensions/mermaid.ts +11 -0
  118. package/src/{components/TextEditor/extensions → extensions}/table.ts +14 -1
  119. package/src/{components/TextEditor/extensions/demo.ts → extensions/typewriter.ts} +2 -3
  120. package/src/extensions/yjs/cursor.ts +21 -0
  121. package/src/extensions/yjs/index.ts +5 -0
  122. package/src/extensions/yjs/yjs.test.ts +35 -0
  123. package/src/extensions/yjs/yjs.ts +16 -0
  124. package/src/hooks/automerge.ts +52 -0
  125. package/src/hooks/awareness-provider.ts +120 -0
  126. package/src/hooks/index.ts +2 -0
  127. package/src/hooks/useTextModel.ts +43 -79
  128. package/src/hooks/{yjs/space-provider.ts → yjs.ts} +71 -19
  129. package/src/index.ts +3 -0
  130. package/src/styles/index.ts +1 -0
  131. package/src/testing/replicator.ts +10 -16
  132. package/src/{components/TextEditor/themes → themes}/default.ts +61 -38
  133. package/src/util/cursor.ts +29 -0
  134. package/src/util/index.ts +6 -0
  135. package/src/util/util.ts +18 -0
  136. package/dist/types/src/components/TextEditor/extensions/autocomplete.d.ts.map +0 -1
  137. package/dist/types/src/components/TextEditor/extensions/basic.d.ts.map +0 -1
  138. package/dist/types/src/components/TextEditor/extensions/blast.d.ts.map +0 -1
  139. package/dist/types/src/components/TextEditor/extensions/code.d.ts.map +0 -1
  140. package/dist/types/src/components/TextEditor/extensions/comments.d.ts +0 -18
  141. package/dist/types/src/components/TextEditor/extensions/comments.d.ts.map +0 -1
  142. package/dist/types/src/components/TextEditor/extensions/demo.d.ts.map +0 -1
  143. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts +0 -10
  144. package/dist/types/src/components/TextEditor/extensions/experimental.d.ts.map +0 -1
  145. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts +0 -24
  146. package/dist/types/src/components/TextEditor/extensions/highlight.d.ts.map +0 -1
  147. package/dist/types/src/components/TextEditor/extensions/image.d.ts.map +0 -1
  148. package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +0 -1
  149. package/dist/types/src/components/TextEditor/extensions/link.d.ts.map +0 -1
  150. package/dist/types/src/components/TextEditor/extensions/listener.d.ts +0 -9
  151. package/dist/types/src/components/TextEditor/extensions/listener.d.ts.map +0 -1
  152. package/dist/types/src/components/TextEditor/extensions/markdown/bundle.d.ts.map +0 -1
  153. package/dist/types/src/components/TextEditor/extensions/markdown/highlight.d.ts.map +0 -1
  154. package/dist/types/src/components/TextEditor/extensions/markdown/index.d.ts.map +0 -1
  155. package/dist/types/src/components/TextEditor/extensions/mention.d.ts.map +0 -1
  156. package/dist/types/src/components/TextEditor/extensions/table.d.ts.map +0 -1
  157. package/dist/types/src/components/TextEditor/extensions/tasklist.d.ts.map +0 -1
  158. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts +0 -12
  159. package/dist/types/src/components/TextEditor/extensions/tooltip.d.ts.map +0 -1
  160. package/dist/types/src/components/TextEditor/themes/default.d.ts.map +0 -1
  161. package/dist/types/src/components/TextEditor/themes/index.d.ts.map +0 -1
  162. package/dist/types/src/hooks/automerge/PatchSemaphore.d.ts.map +0 -1
  163. package/dist/types/src/hooks/automerge/automerge.stories.d.ts.map +0 -1
  164. package/dist/types/src/hooks/automerge/handle.d.ts +0 -9
  165. package/dist/types/src/hooks/automerge/handle.d.ts.map +0 -1
  166. package/dist/types/src/hooks/automerge/index.d.ts +0 -3
  167. package/dist/types/src/hooks/automerge/index.d.ts.map +0 -1
  168. package/dist/types/src/hooks/automerge/plugin.d.ts +0 -26
  169. package/dist/types/src/hooks/automerge/plugin.d.ts.map +0 -1
  170. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts +0 -5
  171. package/dist/types/src/hooks/automerge/translation/automerge-to-codemirror.d.ts.map +0 -1
  172. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts +0 -6
  173. package/dist/types/src/hooks/automerge/translation/codemirror-to-automerge.d.ts.map +0 -1
  174. package/dist/types/src/hooks/automerge/translation/index.d.ts +0 -3
  175. package/dist/types/src/hooks/automerge/translation/index.d.ts.map +0 -1
  176. package/dist/types/src/hooks/yjs/cursors.d.ts.map +0 -1
  177. package/dist/types/src/hooks/yjs/index.d.ts +0 -3
  178. package/dist/types/src/hooks/yjs/index.d.ts.map +0 -1
  179. package/dist/types/src/hooks/yjs/space-provider.d.ts.map +0 -1
  180. package/dist/types/src/hooks/yjs/yjs.stories.d.ts.map +0 -1
  181. package/src/components/TextEditor/extensions/comments.ts +0 -173
  182. package/src/components/TextEditor/extensions/experimental.tsx +0 -114
  183. package/src/components/TextEditor/extensions/highlight.ts +0 -128
  184. package/src/components/TextEditor/extensions/listener.ts +0 -23
  185. package/src/components/TextEditor/extensions/tooltip.tsx +0 -61
  186. package/src/hooks/automerge/handle.ts +0 -14
  187. package/src/hooks/automerge/index.ts +0 -6
  188. package/src/hooks/automerge/plugin.ts +0 -134
  189. package/src/hooks/automerge/translation/index.ts +0 -6
  190. package/src/hooks/yjs/index.ts +0 -6
  191. /package/dist/types/src/{components/TextEditor/extensions → extensions}/autocomplete.d.ts +0 -0
  192. /package/dist/types/src/{components/TextEditor/extensions → extensions}/basic.d.ts +0 -0
  193. /package/dist/types/src/{components/TextEditor/extensions → extensions}/blast.d.ts +0 -0
  194. /package/dist/types/src/{components/TextEditor/extensions → extensions}/code.d.ts +0 -0
  195. /package/dist/types/src/{components/TextEditor/extensions → extensions}/image.d.ts +0 -0
  196. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/bundle.d.ts +0 -0
  197. /package/dist/types/src/{components/TextEditor/extensions → extensions}/markdown/index.d.ts +0 -0
  198. /package/dist/types/src/{components/TextEditor/extensions → extensions}/mention.d.ts +0 -0
  199. /package/dist/types/src/{components/TextEditor/extensions → extensions}/table.d.ts +0 -0
  200. /package/dist/types/src/{components/TextEditor/extensions → extensions}/tasklist.d.ts +0 -0
  201. /package/dist/types/src/{hooks/yjs → styles}/cursors.d.ts +0 -0
  202. /package/dist/types/src/{components/TextEditor/themes → themes}/index.d.ts +0 -0
  203. /package/src/{components/TextEditor/extensions → extensions}/autocomplete.ts +0 -0
  204. /package/src/{components/TextEditor/extensions → extensions}/basic.ts +0 -0
  205. /package/src/{components/TextEditor/extensions → extensions}/blast.ts +0 -0
  206. /package/src/{components/TextEditor/extensions → extensions}/markdown/bundle.ts +0 -0
  207. /package/src/{components/TextEditor/extensions → extensions}/markdown/index.ts +0 -0
  208. /package/src/{components/TextEditor/extensions → extensions}/mention.ts +0 -0
  209. /package/src/{components/TextEditor/extensions → extensions}/tasklist.ts +0 -0
  210. /package/src/{hooks/yjs → styles}/cursors.ts +0 -0
  211. /package/src/{components/TextEditor/themes → themes}/index.ts +0 -0
@@ -5,7 +5,18 @@ import { Doc, YText, YXmlFragment } from "@dxos/text-model";
5
5
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
6
  import { tags as tags2 } from "@lezer/highlight";
7
7
 
8
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/autocomplete.ts
8
+ // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
+ import { EditorState as EditorState2 } from "@codemirror/state";
10
+ import { EditorView as EditorView13 } from "@codemirror/view";
11
+ import { useFocusableGroup } from "@fluentui/react-tabster";
12
+ import { vim } from "@replit/codemirror-vim";
13
+ import defaultsDeep2 from "lodash.defaultsdeep";
14
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
15
+ import { generateName } from "@dxos/display-name";
16
+ import { useThemeContext } from "@dxos/react-ui";
17
+ import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
+
19
+ // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
9
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
10
21
  import { keymap } from "@codemirror/view";
11
22
  var autocomplete = ({ onSearch }) => {
@@ -38,13 +49,565 @@ var autocomplete = ({ onSearch }) => {
38
49
  ];
39
50
  };
40
51
 
41
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/basic.ts
52
+ // packages/ui/react-ui-editor/src/extensions/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";
56
+
57
+ // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
58
+ import get from "lodash.get";
59
+ import { next as automerge } from "@dxos/automerge/automerge";
60
+ var cursorConverter = (handle, path) => ({
61
+ // TODO(burdon): Handle assoc to associate with a previous character.
62
+ toCursor: (pos) => {
63
+ const doc = handle.docSync();
64
+ if (!doc) {
65
+ return "";
66
+ }
67
+ const value = get(doc, path);
68
+ if (typeof value === "string" && value.length <= pos) {
69
+ return "end";
70
+ }
71
+ return automerge.getCursor(doc, path.slice(), pos);
72
+ },
73
+ fromCursor: (cursor) => {
74
+ if (cursor === "") {
75
+ return 0;
76
+ }
77
+ const doc = handle.docSync();
78
+ if (!doc) {
79
+ return 0;
80
+ }
81
+ if (cursor === "end") {
82
+ const value = get(doc, path);
83
+ if (typeof value === "string") {
84
+ return value.length;
85
+ } else {
86
+ return 0;
87
+ }
88
+ }
89
+ return automerge.getCursorPosition(doc, path.slice(), cursor);
90
+ }
91
+ });
92
+
93
+ // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
94
+ import { Annotation, StateEffect } from "@codemirror/state";
95
+ var effectType = StateEffect.define({});
96
+ var updateHeads = (newHeads) => effectType.of({
97
+ newHeads
98
+ });
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);
103
+
104
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
105
+ import { next as automerge2 } from "@dxos/automerge/automerge";
106
+
107
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
108
+ import { next as am } from "@dxos/automerge/automerge";
109
+ var updateAutomerge = (field, handle, transactions, state) => {
110
+ const { lastHeads, path } = state.field(field);
111
+ let hasChanges = false;
112
+ for (const tr of transactions) {
113
+ tr.changes.iterChanges(() => {
114
+ hasChanges = true;
115
+ });
116
+ }
117
+ if (!hasChanges) {
118
+ return void 0;
119
+ }
120
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
121
+ for (const tr of transactions) {
122
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
123
+ am.splice(doc, path, fromA, toA - fromA, inserted.toString());
124
+ });
125
+ }
126
+ });
127
+ return newHeads ?? void 0;
128
+ };
129
+
130
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
131
+ import { ChangeSet } from "@codemirror/state";
132
+ var updateCodeMirror = (view, selection, target, patches) => {
133
+ for (const patch of patches) {
134
+ const changeSpec = handlePatch(patch, target, view.state);
135
+ if (changeSpec != null) {
136
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
137
+ selection = selection.map(changeSet, 1);
138
+ view.dispatch({
139
+ changes: changeSet,
140
+ annotations: reconcileAnnotationType.of({})
141
+ });
142
+ }
143
+ }
144
+ view.dispatch({
145
+ selection,
146
+ annotations: reconcileAnnotationType.of({})
147
+ });
148
+ };
149
+ var handlePatch = (patch, target, state) => {
150
+ if (patch.action === "insert") {
151
+ return handleInsert(target, patch);
152
+ } else if (patch.action === "splice") {
153
+ return handleSplice(target, patch);
154
+ } else if (patch.action === "del") {
155
+ return handleDel(target, patch);
156
+ } else if (patch.action === "put") {
157
+ return handlePut(target, patch, state);
158
+ } else {
159
+ return null;
160
+ }
161
+ };
162
+ var handleInsert = (target, patch) => {
163
+ const index = charPath(target, patch.path);
164
+ if (index == null) {
165
+ return [];
166
+ }
167
+ const text = patch.values.map((v) => v ? v.toString() : "").join("");
168
+ return [
169
+ {
170
+ from: index,
171
+ to: index,
172
+ insert: text
173
+ }
174
+ ];
175
+ };
176
+ var handleSplice = (target, patch) => {
177
+ const index = charPath(target, patch.path);
178
+ if (index == null) {
179
+ return [];
180
+ }
181
+ return [
182
+ {
183
+ from: index,
184
+ insert: patch.value
185
+ }
186
+ ];
187
+ };
188
+ var handleDel = (target, patch) => {
189
+ const index = charPath(target, patch.path);
190
+ if (index == null) {
191
+ return [];
192
+ }
193
+ const length = patch.length || 1;
194
+ return [
195
+ {
196
+ from: index,
197
+ to: index + length
198
+ }
199
+ ];
200
+ };
201
+ var handlePut = (target, patch, state) => {
202
+ const index = charPath(target, [
203
+ ...patch.path,
204
+ 0
205
+ ]);
206
+ if (index == null) {
207
+ return [];
208
+ }
209
+ const length = state.doc.length;
210
+ if (typeof patch.value !== "string") {
211
+ return [];
212
+ }
213
+ return [
214
+ {
215
+ from: 0,
216
+ to: length,
217
+ insert: patch.value
218
+ }
219
+ ];
220
+ };
221
+ var charPath = (textPath, candidatePath) => {
222
+ if (candidatePath.length !== textPath.length + 1) {
223
+ return null;
224
+ }
225
+ for (let i = 0; i < textPath.length; i++) {
226
+ if (textPath[i] !== candidatePath[i]) {
227
+ return null;
228
+ }
229
+ }
230
+ const index = candidatePath[candidatePath.length - 1];
231
+ if (typeof index === "number") {
232
+ return index;
233
+ }
234
+ return null;
235
+ };
236
+
237
+ // packages/ui/react-ui-editor/src/extensions/automerge/semaphore.ts
238
+ var PatchSemaphore = class {
239
+ constructor(_field) {
240
+ this._field = _field;
241
+ this._inReconcile = false;
242
+ this._queue = [];
243
+ this.reconcile = (handle, view) => {
244
+ if (this._inReconcile) {
245
+ return;
246
+ }
247
+ 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);
267
+ view.dispatch({
268
+ effects: updateHeads(newHeads),
269
+ annotations: reconcileAnnotationType.of({})
270
+ });
271
+ this._inReconcile = false;
272
+ };
273
+ }
274
+ };
275
+
276
+ // packages/ui/react-ui-editor/src/util/cursor.ts
277
+ import { Facet } from "@codemirror/state";
278
+ var DEFAULT_CURSOR_CONVERTER = {
279
+ toCursor: (position) => position.toString(),
280
+ fromCursor: (cursor) => parseInt(cursor)
281
+ };
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)
298
+ });
299
+ }
300
+ };
301
+
302
+ // 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({
308
+ create: () => ({
309
+ lastHeads: A.getHeads(handle.docSync()),
310
+ unreconciledTransactions: [],
311
+ path: path.slice()
312
+ }),
313
+ update: (value, tr) => {
314
+ const result = {
315
+ lastHeads: value.lastHeads,
316
+ unreconciledTransactions: value.unreconciledTransactions.slice(),
317
+ path: path.slice()
318
+ };
319
+ let clearUnreconciled = false;
320
+ for (const effect of tr.effects) {
321
+ if (effect.is(effectType)) {
322
+ result.lastHeads = effect.value.newHeads;
323
+ clearUnreconciled = true;
324
+ }
325
+ }
326
+ if (clearUnreconciled) {
327
+ result.unreconciledTransactions = [];
328
+ } else {
329
+ if (!isReconcileTx(tr)) {
330
+ result.unreconciledTransactions.push(tr);
331
+ }
332
+ }
333
+ return result;
334
+ }
335
+ });
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);
349
+ });
350
+ }
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
+ };
364
+ };
365
+
366
+ // 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";
369
+ import { Event } from "@dxos/async";
370
+ import { Context } from "@dxos/context";
371
+ var dummyProvider = {
372
+ remoteStateChange: new Event(),
373
+ open: () => {
374
+ },
375
+ close: () => {
376
+ },
377
+ update: () => {
378
+ },
379
+ getRemoteStates: () => []
380
+ };
381
+ var AwarenessProvider = Facet3.define({
382
+ combine: (providers) => providers[0] ?? dummyProvider
383
+ });
384
+ var RemoteSelectionChangedAnnotation = Annotation2.define();
385
+ var awareness = (provider = dummyProvider) => {
386
+ return [
387
+ AwarenessProvider.of(provider),
388
+ ViewPlugin2.fromClass(RemoteSelectionsDecorator, {
389
+ decorations: (value) => value.decorations
390
+ }),
391
+ styles
392
+ ];
393
+ };
394
+ var RemoteSelectionsDecorator = class {
395
+ constructor(view) {
396
+ this.decorations = RangeSet.of([]);
397
+ this._ctx = new Context();
398
+ this._lastAnchor = void 0;
399
+ this._lastHead = void 0;
400
+ this._cursorConverter = view.state.facet(CursorConverter);
401
+ this._provider = view.state.facet(AwarenessProvider);
402
+ this._provider.open();
403
+ this._provider.remoteStateChange.on(this._ctx, () => {
404
+ view.dispatch({
405
+ annotations: [
406
+ RemoteSelectionChangedAnnotation.of([])
407
+ ]
408
+ });
409
+ });
410
+ }
411
+ destroy() {
412
+ void this._ctx.dispose();
413
+ this._provider.close();
414
+ }
415
+ update(update4) {
416
+ this._updateLocalSelection(update4);
417
+ this._updateRemoteSelections(update4);
418
+ }
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 : {};
422
+ if (this._lastAnchor === anchor && this._lastHead === head) {
423
+ return;
424
+ }
425
+ this._lastAnchor = anchor;
426
+ this._lastHead = head;
427
+ this._provider.update(anchor !== void 0 && head !== void 0 ? {
428
+ anchor: this._cursorConverter.toCursor(anchor),
429
+ head: this._cursorConverter.toCursor(head)
430
+ } : void 0);
431
+ }
432
+ _updateRemoteSelections(update4) {
433
+ const decorations = [];
434
+ const awarenessStates = this._provider.getRemoteStates();
435
+ for (const state of awarenessStates) {
436
+ const anchor = state.position?.anchor ? this._cursorConverter.fromCursor(state.position.anchor) : null;
437
+ const head = state.position?.head ? this._cursorConverter.fromCursor(state.position.head) : null;
438
+ if (anchor == null || head == null) {
439
+ continue;
440
+ }
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);
445
+ const color = state.info.color ?? "#30bced";
446
+ const lightColor = state.info.lightColor ?? color + "33";
447
+ if (startLine.number === endLine.number) {
448
+ decorations.push({
449
+ from: start,
450
+ to: end,
451
+ value: Decoration.mark({
452
+ attributes: {
453
+ style: `background-color: ${lightColor}`
454
+ },
455
+ class: "cm-ySelection"
456
+ })
457
+ });
458
+ } else {
459
+ decorations.push({
460
+ from: start,
461
+ to: startLine.from + startLine.length,
462
+ value: Decoration.mark({
463
+ attributes: {
464
+ style: `background-color: ${color}`
465
+ },
466
+ class: "cm-ySelection"
467
+ })
468
+ });
469
+ decorations.push({
470
+ from: endLine.from,
471
+ to: end,
472
+ value: Decoration.mark({
473
+ attributes: {
474
+ style: `background-color: ${color}`
475
+ },
476
+ class: "cm-ySelection"
477
+ })
478
+ });
479
+ for (let i = startLine.number + 1; i < endLine.number; i++) {
480
+ const linePos = update4.view.state.doc.line(i).from;
481
+ decorations.push({
482
+ from: linePos,
483
+ to: linePos,
484
+ value: Decoration.line({
485
+ attributes: {
486
+ style: `background-color: ${color}`,
487
+ class: "cm-yLineSelection"
488
+ }
489
+ })
490
+ });
491
+ }
492
+ }
493
+ decorations.push({
494
+ from: head,
495
+ to: head,
496
+ value: Decoration.widget({
497
+ side: head - anchor > 0 ? -1 : 1,
498
+ block: false,
499
+ widget: new RemoteCaretWidget(state.info.displayName ?? "Anonymous", color)
500
+ })
501
+ });
502
+ }
503
+ this.decorations = Decoration.set(decorations, true);
504
+ }
505
+ };
506
+ var RemoteCaretWidget = class extends WidgetType {
507
+ constructor(name, color) {
508
+ super();
509
+ this.name = name;
510
+ this.color = color;
511
+ this.name = name;
512
+ this.color = color;
513
+ }
514
+ toDOM() {
515
+ const span = document.createElement("span");
516
+ span.className = "cm-ySelectionCaret";
517
+ span.style.backgroundColor = this.color;
518
+ span.style.borderColor = this.color;
519
+ const dot = document.createElement("div");
520
+ dot.className = "cm-ySelectionCaretDot";
521
+ const info = document.createElement("div");
522
+ info.className = "cm-ySelectionInfo";
523
+ info.innerText = this.name;
524
+ span.appendChild(document.createTextNode("\u2060"));
525
+ span.appendChild(dot);
526
+ span.appendChild(document.createTextNode("\u2060"));
527
+ span.appendChild(info);
528
+ span.appendChild(document.createTextNode("\u2060"));
529
+ return span;
530
+ }
531
+ eq(widget) {
532
+ return widget.color === this.color;
533
+ }
534
+ compare(widget) {
535
+ return widget.color === this.color;
536
+ }
537
+ updateDOM() {
538
+ return false;
539
+ }
540
+ get estimatedHeight() {
541
+ return -1;
542
+ }
543
+ ignoreEvent() {
544
+ return true;
545
+ }
546
+ };
547
+ var styles = EditorView.baseTheme({
548
+ ".cm-ySelection": {},
549
+ ".cm-yLineSelection": {
550
+ padding: 0,
551
+ margin: "0px 2px 0px 4px"
552
+ },
553
+ ".cm-ySelectionCaret": {
554
+ position: "relative",
555
+ borderLeft: "1px solid black",
556
+ borderRight: "1px solid black",
557
+ marginLeft: "-1px",
558
+ marginRight: "-1px",
559
+ boxSizing: "border-box",
560
+ display: "inline"
561
+ },
562
+ ".cm-ySelectionCaretDot": {
563
+ borderRadius: "50%",
564
+ position: "absolute",
565
+ width: ".4em",
566
+ height: ".4em",
567
+ top: "-.2em",
568
+ left: "-.2em",
569
+ backgroundColor: "inherit",
570
+ transition: "transform .3s ease-in-out",
571
+ boxSizing: "border-box"
572
+ },
573
+ ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
574
+ transformOrigin: "bottom center",
575
+ transform: "scale(0)"
576
+ },
577
+ ".cm-ySelectionInfo": {
578
+ position: "absolute",
579
+ top: "-1.05em",
580
+ left: "-1px",
581
+ fontSize: ".75em",
582
+ fontFamily: "serif",
583
+ fontStyle: "normal",
584
+ fontWeight: "normal",
585
+ lineHeight: "normal",
586
+ userSelect: "none",
587
+ color: "white",
588
+ paddingLeft: "2px",
589
+ paddingRight: "2px",
590
+ zIndex: 101,
591
+ transition: "opacity .3s ease-in-out",
592
+ backgroundColor: "inherit",
593
+ // these should be separate
594
+ opacity: 0,
595
+ transitionDelay: "0s",
596
+ whiteSpace: "nowrap"
597
+ },
598
+ ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
599
+ opacity: 1,
600
+ transitionDelay: "0s"
601
+ }
602
+ });
603
+
604
+ // packages/ui/react-ui-editor/src/extensions/basic.ts
42
605
  import { closeBrackets } from "@codemirror/autocomplete";
43
606
  import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
44
607
  import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
45
- import { drawSelection, EditorView, placeholder } from "@codemirror/view";
608
+ import { drawSelection, EditorView as EditorView2, placeholder } from "@codemirror/view";
46
609
  var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
47
- EditorView.lineWrapping,
610
+ EditorView2.lineWrapping,
48
611
  // TODO(burdon): Compare with minimalSetup.
49
612
  // https://codemirror.net/docs/ref/#codemirror.minimalSetup
50
613
  bracketMatching(),
@@ -55,11 +618,11 @@ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
55
618
  themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)
56
619
  ].filter(Boolean);
57
620
 
58
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts
59
- import { EditorView as EditorView2, keymap as keymap2 } from "@codemirror/view";
621
+ // packages/ui/react-ui-editor/src/extensions/blast.ts
622
+ import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
60
623
  import defaultsDeep from "lodash.defaultsdeep";
61
624
  import { invariant } from "@dxos/invariant";
62
- var __dxlog_file = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/blast.ts";
625
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
63
626
  var defaultOptions = {
64
627
  effect: 2,
65
628
  maxParticles: 200,
@@ -104,7 +667,7 @@ var blast = (options = defaultOptions) => {
104
667
  };
105
668
  return [
106
669
  // Cursor moved.
107
- EditorView2.updateListener.of((update4) => {
670
+ EditorView3.updateListener.of((update4) => {
108
671
  if (blaster?.node !== update4.view.scrollDOM) {
109
672
  if (blaster) {
110
673
  blaster.destroy();
@@ -186,7 +749,7 @@ var Blaster = class {
186
749
  }
187
750
  initialize() {
188
751
  invariant(!this._canvas && !this._ctx, void 0, {
189
- F: __dxlog_file,
752
+ F: __dxlog_file2,
190
753
  L: 138,
191
754
  S: this,
192
755
  A: [
@@ -223,7 +786,7 @@ var Blaster = class {
223
786
  }
224
787
  start() {
225
788
  invariant(this._canvas && this._ctx, void 0, {
226
- F: __dxlog_file,
789
+ F: __dxlog_file2,
227
790
  L: 177,
228
791
  S: this,
229
792
  A: [
@@ -365,11 +928,49 @@ var random = (min, max) => {
365
928
  return min + ~~(Math.random() * (max - min + 1));
366
929
  };
367
930
 
368
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
369
- import { ViewPlugin, EditorView as EditorView3, Decoration } from "@codemirror/view";
370
- import get from "lodash.get";
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";
371
934
  import { mx as mx2 } from "@dxos/react-ui-theme";
372
935
 
936
+ // packages/ui/react-ui-editor/src/styles/cursors.ts
937
+ import * as random2 from "lib0/random";
938
+ var cursorColors = [
939
+ {
940
+ color: "#30bced",
941
+ light: "#30bced33"
942
+ },
943
+ {
944
+ color: "#6eeb83",
945
+ light: "#6eeb8333"
946
+ },
947
+ {
948
+ color: "#ffbc42",
949
+ light: "#ffbc4233"
950
+ },
951
+ {
952
+ color: "#ecd444",
953
+ light: "#ecd44433"
954
+ },
955
+ {
956
+ color: "#ee6352",
957
+ light: "#ee635233"
958
+ },
959
+ {
960
+ color: "#9ac2c9",
961
+ light: "#9ac2c933"
962
+ },
963
+ {
964
+ color: "#8acb88",
965
+ light: "#8acb8833"
966
+ },
967
+ {
968
+ color: "#1be7ff",
969
+ light: "#1be7ff33"
970
+ }
971
+ ];
972
+ var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
973
+
373
974
  // packages/ui/react-ui-editor/src/styles/markdown.ts
374
975
  import { mx } from "@dxos/react-ui-theme";
375
976
  var headings = {
@@ -411,31 +1012,33 @@ var code = "font-mono !no-underline text-neutral-700 dark:text-neutral-300";
411
1012
  var codeMark = "font-mono text-primary-500";
412
1013
  var inlineUrl = mx(code, "px-1");
413
1014
  var blockquote = mx("pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30", light);
414
- var horizontalRule = "flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800";
415
1015
 
416
1016
  // packages/ui/react-ui-editor/src/styles/tokens.ts
417
1017
  import { tailwindConfig } from "@dxos/react-ui-theme";
418
1018
  var tokens = tailwindConfig({}).theme;
419
1019
 
420
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/code.ts
1020
+ // packages/ui/react-ui-editor/src/extensions/code.ts
421
1021
  var CODE_REGEX = /```[\s\S]*?```/gs;
422
- var styles = EditorView3.baseTheme({
423
- "& .cm-code-block": {
424
- display: "block",
425
- paddingInline: "4px !important"
1022
+ var styles2 = EditorView4.baseTheme({
1023
+ "& .cm-codeblock": {
1024
+ fontFamily: get2(tokens, "fontFamily.mono", []).join(",")
426
1025
  },
427
- "&light .cm-code-block": {
428
- background: get(tokens, "extend.colors.neutral.50")
1026
+ '&light [aria-readonly="true"] .cm-codeblock': {
1027
+ background: get2(tokens, "extend.colors.neutral.50")
429
1028
  },
430
- "&dark .cm-code-block": {
431
- background: get(tokens, "extend.colors.neutral.850")
1029
+ '&dark [aria-readonly="true"] .cm-codeblock': {
1030
+ background: get2(tokens, "extend.colors.neutral.850")
432
1031
  },
433
- "& .cm-code-block-first": {
1032
+ '& [aria-readonly="true"] .cm-codeblock': {
1033
+ display: "block",
1034
+ paddingInline: "4px !important"
1035
+ },
1036
+ '& [aria-readonly="true"] .cm-codeblock-first': {
434
1037
  paddingTop: "4px !important",
435
1038
  borderTopLeftRadius: "4px",
436
1039
  borderTopRightRadius: "4px"
437
1040
  },
438
- "& .cm-code-block-last": {
1041
+ '& [aria-readonly="true"] .cm-codeblock-last': {
439
1042
  paddingBottom: "4px !important",
440
1043
  borderBottomLeftRadius: "4px",
441
1044
  borderBottomRightRadius: "4px"
@@ -460,16 +1063,16 @@ var code2 = () => {
460
1063
  const range = getLineRange(blocks, match.index, match.index + match[0].length);
461
1064
  for (let i = range[0]; i <= range[1]; i++) {
462
1065
  const block = blocks[i];
463
- decorations.push(Decoration.line({
464
- class: mx2("cm-code-block", i === range[0] && "cm-code-block-first", i === range[1] && "cm-code-block-last")
1066
+ decorations.push(Decoration2.line({
1067
+ class: mx2("cm-codeblock", i === range[0] && "cm-codeblock-first", i === range[1] && "cm-codeblock-last")
465
1068
  }).range(block.from));
466
1069
  }
467
1070
  }
468
1071
  }
469
- return Decoration.set(decorations);
1072
+ return Decoration2.set(decorations);
470
1073
  };
471
1074
  return [
472
- ViewPlugin.fromClass(class {
1075
+ ViewPlugin3.fromClass(class {
473
1076
  constructor(view) {
474
1077
  this.decorations = getDecorations(view);
475
1078
  }
@@ -481,73 +1084,21 @@ var code2 = () => {
481
1084
  }, {
482
1085
  decorations: (value) => value.decorations
483
1086
  }),
484
- styles
485
- ];
486
- };
487
-
488
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/demo.ts
489
- import { keymap as keymap3 } from "@codemirror/view";
490
- var defaultItems = [
491
- "hello world!",
492
- "this is a test.",
493
- "this is [DXOS](https://dxos.org)"
494
- ];
495
- var demo = ({ delay = 75, items = defaultItems } = {}) => {
496
- let t;
497
- let idx = 0;
498
- return [
499
- keymap3.of([
500
- {
501
- // Reset.
502
- key: "alt-meta-'",
503
- run: (view) => {
504
- clearTimeout(t);
505
- idx = 0;
506
- return true;
507
- }
508
- },
509
- {
510
- // Next prompt.
511
- // TODO(burdon): Press 1-9 to select prompt?
512
- key: "shift-meta-'",
513
- run: (view) => {
514
- clearTimeout(t);
515
- const text = items[idx++];
516
- if (idx === items?.length) {
517
- idx = 0;
518
- }
519
- let i = 0;
520
- const insert = (d = 0) => {
521
- t = setTimeout(() => {
522
- const pos = view.state.selection.main.head;
523
- view.dispatch({
524
- changes: {
525
- from: pos,
526
- insert: text[i++]
527
- },
528
- selection: {
529
- anchor: pos + 1
530
- }
531
- });
532
- if (i < text.length) {
533
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
534
- }
535
- }, d);
536
- };
537
- insert();
538
- return true;
539
- }
540
- }
541
- ])
1087
+ styles2
542
1088
  ];
543
1089
  };
544
1090
 
545
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts
546
- import { keymap as keymap4, Decoration as Decoration2, EditorView as EditorView4, MatchDecorator, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
1091
+ // 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";
547
1096
  import { debounce } from "@dxos/async";
548
1097
  import { invariant as invariant2 } from "@dxos/invariant";
549
- var __dxlog_file2 = "/home/circleci/project/packages/ui/react-ui-editor/src/components/TextEditor/extensions/comments.ts";
550
- var styles2 = EditorView4.baseTheme({
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({
551
1102
  "& .cm-bookmark": {
552
1103
  cursor: "pointer",
553
1104
  margin: "4px",
@@ -556,17 +1107,103 @@ var styles2 = EditorView4.baseTheme({
556
1107
  },
557
1108
  "& .cm-bookmark-selected": {
558
1109
  backgroundColor: "orange"
1110
+ },
1111
+ "& .cm-comment": {
1112
+ backgroundColor: get3(tokens, "extend.colors.yellow.50")
1113
+ },
1114
+ "& .cm-comment-active": {
1115
+ backgroundColor: get3(tokens, "extend.colors.yellow.100")
1116
+ }
1117
+ });
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) {
1129
+ return;
1130
+ }
1131
+ view.dispatch({
1132
+ effects: setSelection.of({
1133
+ active: thread
1134
+ }),
1135
+ selection: {
1136
+ anchor: range.from
1137
+ },
1138
+ scrollIntoView: true
1139
+ });
1140
+ };
1141
+ var setCommentRange = StateEffect2.define();
1142
+ var setSelection = StateEffect2.define();
1143
+ var setCommentState = StateEffect2.define();
1144
+ var commentsStateField = StateField2.define({
1145
+ create: () => ({
1146
+ ranges: []
1147
+ }),
1148
+ update: (value, tr) => {
1149
+ const cursorConverter3 = tr.state.facet(CursorConverter);
1150
+ for (const effect of tr.effects) {
1151
+ if (effect.is(setSelection)) {
1152
+ return {
1153
+ ...value,
1154
+ ...effect.value
1155
+ };
1156
+ }
1157
+ if (effect.is(setCommentRange)) {
1158
+ const { comments: comments2 } = effect.value;
1159
+ const ranges = comments2.map((comment) => {
1160
+ const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1161
+ return range && {
1162
+ ...comment,
1163
+ ...range
1164
+ };
1165
+ }).filter(nonNullable);
1166
+ return {
1167
+ ...value,
1168
+ ranges
1169
+ };
1170
+ }
1171
+ if (effect.is(setCommentState)) {
1172
+ return effect.value;
1173
+ }
1174
+ }
1175
+ return value;
559
1176
  }
560
1177
  });
561
- var BookmarkWidget = class extends WidgetType {
562
- constructor(_pos, _id, _handleClick) {
1178
+ var highlightDecorations = EditorView5.decorations.compute([
1179
+ commentsStateField
1180
+ ], (state) => {
1181
+ const { active, ranges } = state.field(commentsStateField);
1182
+ const decorations = sortBy(ranges ?? [], (range) => range.from)?.flatMap((selection) => {
1183
+ if (selection.from === selection.to) {
1184
+ return void 0;
1185
+ }
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);
1192
+ } else {
1193
+ return marks.highlight.range(range.from, range.to);
1194
+ }
1195
+ }).filter(nonNullable) ?? [];
1196
+ return Decoration3.set(decorations);
1197
+ });
1198
+ var BookmarkWidget = class extends WidgetType2 {
1199
+ constructor(_anchor, _id, _handleClick) {
563
1200
  super();
564
- this._pos = _pos;
1201
+ this._anchor = _anchor;
565
1202
  this._id = _id;
566
1203
  this._handleClick = _handleClick;
567
1204
  invariant2(this._id, void 0, {
568
- F: __dxlog_file2,
569
- L: 37,
1205
+ F: __dxlog_file3,
1206
+ L: 169,
570
1207
  S: this,
571
1208
  A: [
572
1209
  "this._id",
@@ -575,227 +1212,286 @@ var BookmarkWidget = class extends WidgetType {
575
1212
  });
576
1213
  }
577
1214
  eq(other) {
578
- return this._id === other._id;
1215
+ return this._anchor === other._anchor && this._id === other._id;
579
1216
  }
580
1217
  toDOM() {
581
1218
  const span = document.createElement("span");
582
1219
  span.className = "cm-bookmark";
583
- span.textContent = "\xA7";
584
- span.onclick = () => this._handleClick();
1220
+ span.textContent = "\u203B";
1221
+ if (this._handleClick) {
1222
+ span.onclick = () => this._handleClick();
1223
+ }
585
1224
  return span;
586
1225
  }
587
1226
  };
588
1227
  var comments = (options = {}) => {
589
- let active;
590
- const bookmarkMatcher = new MatchDecorator({
591
- regexp: /\[\^(\w+)\]/g,
592
- decoration: (match, view, pos) => {
593
- const id = match[1];
594
- return Decoration2.replace({
595
- id,
596
- widget: new BookmarkWidget(pos, id, () => handleUpdate({
597
- active: id
598
- }))
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;
1243
+ }
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
1257
+ }
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
1268
+ }
1269
+ });
1270
+ }
1271
+ return true;
1272
+ }
1273
+ }
1274
+ 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
+ });
1288
+ }
599
1289
  });
600
1290
  }
601
- });
602
- const bookmarks = ViewPlugin2.fromClass(class {
603
1291
  constructor(view) {
604
- this.bookmarks = bookmarkMatcher.createDeco(view);
1292
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
605
1293
  }
606
1294
  update(update4) {
607
- this.bookmarks = bookmarkMatcher.updateDeco(update4, this.bookmarks);
1295
+ this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update4, this.bookmarks);
608
1296
  }
609
1297
  }, {
610
1298
  decorations: (instance) => instance.bookmarks,
611
- provide: (plugin) => EditorView4.atomicRanges.of((view) => {
612
- return view.plugin(plugin)?.bookmarks || Decoration2.none;
1299
+ provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1300
+ return view.plugin(plugin)?.bookmarks || Decoration3.none;
613
1301
  })
614
1302
  });
615
- const handleUpdate = debounce((data) => {
616
- options.onUpdate?.(data);
617
- }, 200);
618
1303
  return [
619
- keymap4.of([
1304
+ bookmarksViewPlugin,
1305
+ commentsStateField,
1306
+ highlightDecorations,
1307
+ styles3,
1308
+ //
1309
+ // Keymap.
1310
+ //
1311
+ options.onCreate ? keymap3.of([
620
1312
  {
621
- key: options?.key ?? "shift-meta-c",
622
- run: (view) => {
623
- const { from, to, head } = view.state.selection.main;
624
- const id = options.onCreate?.(from, to);
625
- if (id) {
626
- const tag = `[^${id}]`;
627
- view.dispatch({
628
- changes: {
629
- from: head,
630
- insert: tag
631
- },
632
- selection: {
633
- anchor: head + tag.length
1313
+ key: options?.key ?? "meta-'",
1314
+ run: callbackWrapper(createCommentThread)
1315
+ }
1316
+ ]) : [],
1317
+ //
1318
+ // Hover tooltip (for key shortcut hints, etc.)
1319
+ // TODO(burdon): Factor out to generic hints extension for current selection/line.
1320
+ //
1321
+ options.onHover ? hoverTooltip((view, pos) => {
1322
+ const selection = view.state.selection.main;
1323
+ if (selection && pos >= selection.from && pos <= selection.to) {
1324
+ return {
1325
+ pos: selection.from,
1326
+ end: selection.to,
1327
+ above: true,
1328
+ create: () => {
1329
+ const el = document.createElement("div");
1330
+ options.onHover?.(el);
1331
+ return {
1332
+ dom: el,
1333
+ offset: {
1334
+ x: 0,
1335
+ y: 8
634
1336
  }
635
- });
636
- return true;
1337
+ };
637
1338
  }
638
- return false;
639
- }
640
- }
641
- ]),
642
- bookmarks,
643
- // Monitor cursor movement.
644
- EditorView4.updateListener.of((update4) => {
645
- active = void 0;
646
- if (!options.onUpdate) {
647
- return;
1339
+ };
648
1340
  }
649
- const { view, state } = update4;
650
- const pos = state.selection.main.head;
651
- const decorations = [];
652
- const rangeSet = view.plugin(bookmarks)?.bookmarks;
653
- let closest = Infinity;
654
- const { from, to } = (
655
- /* view.visibleRanges?.[0] ?? */
656
- {
657
- from: 0,
658
- to: state.doc.length
659
- }
660
- );
661
- rangeSet?.between(from, to, (from2, to2, value) => {
662
- decorations.push({
663
- from: from2,
664
- to: to2,
665
- value
1341
+ return null;
1342
+ }, {
1343
+ hideOnChange: true,
1344
+ hoverTime: 1e3
1345
+ }) : [],
1346
+ //
1347
+ // Monitor cursor movement and text updates.
1348
+ // TODO(burdon): Is there a better (finer grained) way to do this?
1349
+ //
1350
+ 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;
1374
+ }
1375
+ });
666
1376
  });
667
- const d = Math.min(Math.abs(pos - from2), Math.abs(pos - to2));
668
- if (d < closest) {
669
- active = value.spec.id;
670
- closest = d;
1377
+ if (mod) {
1378
+ view.dispatch({
1379
+ effects: setCommentState.of({
1380
+ active,
1381
+ ranges
1382
+ })
1383
+ });
671
1384
  }
672
- });
673
- if (decorations.length) {
674
- handleUpdate({
675
- active,
676
- items: decorations.map(({ from: from2, value: { spec: { id } } }) => ({
677
- id,
678
- pos: from2,
679
- location: view.coordsAtPos(from2)
680
- }))
1385
+ }
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
+ }
1399
+ if (d < min) {
1400
+ selected.closest = comment.id;
1401
+ min = d;
1402
+ }
681
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
+ }
682
1416
  }
683
- }),
684
- styles2
1417
+ })
685
1418
  ];
686
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
+ };
687
1437
 
688
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/highlight.ts
689
- import { StateEffect, StateField } from "@codemirror/state";
690
- import { Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
691
- import sortBy from "lodash.sortby";
692
- import { useEffect } from "react";
693
- var styles3 = EditorView5.baseTheme({
694
- "& .cm-highlight": {
695
- backgroundColor: "yellow"
696
- },
697
- "& .cm-highlight::before": {
698
- content: "]"
699
- },
700
- "& .cm-highlight-active": {
701
- backgroundColor: "lime"
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")}`
702
1446
  }
703
1447
  });
704
- var marks = {
705
- highlight: Decoration3.mark({
706
- class: "cm-highlight"
707
- }),
708
- HighlightActive: Decoration3.mark({
709
- class: "cm-highlight-active"
710
- })
711
- };
712
- var setHighlights = StateEffect.define();
713
- var useHighlights = (view, ranges = []) => {
714
- useEffect(() => {
715
- view?.dispatch({
716
- effects: setHighlights.of(ranges)
717
- });
718
- }, [
719
- view,
720
- ranges
721
- ]);
722
- };
723
- var highlightStateField = StateField.define({
724
- create: () => [],
725
- update: (threads, tr) => {
726
- for (const effect of tr.effects) {
727
- if (effect.is(setHighlights)) {
728
- return effect.value;
729
- }
730
- }
731
- return threads;
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;
732
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
+ })
733
1467
  });
734
- var highlightDecorations = EditorView5.decorations.compute([
735
- highlightStateField
736
- ], (state) => {
737
- const selectionRanges = state.field(highlightStateField);
738
- const decorations = sortBy(selectionRanges ?? [], (selection) => selection.from)?.flatMap((selection) => {
739
- const range = {
740
- from: selection.from,
741
- to: selection.to + 1
742
- };
743
- if (selection.to > selection.from) {
744
- if (selection.active) {
745
- return marks.HighlightActive.range(range.from, range.to);
746
- } else {
747
- return marks.highlight.range(range.from, range.to);
748
- }
749
- } else {
750
- return [];
1468
+ var hr = () => [
1469
+ styles4,
1470
+ ViewPlugin5.fromClass(class {
1471
+ constructor(view) {
1472
+ this.rules = placeholderMatcher.createDeco(view);
751
1473
  }
752
- }) ?? [];
753
- return Decoration3.set(decorations);
754
- });
755
- var listener = (options) => {
756
- return EditorView5.updateListener.of((update4) => {
757
- const { view, state } = update4;
758
- const { head } = state.selection.main;
759
- const ranges = state.field(highlightStateField);
760
- let mod = false;
761
- const newRanges = ranges.map((range) => {
762
- const active = head >= range.from && head <= range.to;
763
- if (active !== range.active) {
764
- mod = true;
765
- return {
766
- ...range,
767
- active
768
- };
769
- } else {
770
- return range;
771
- }
772
- });
773
- if (mod) {
774
- view.dispatch({
775
- effects: setHighlights.of(newRanges)
776
- });
777
- options?.onChange?.(newRanges.find((range) => range.active)?.id);
1474
+ update(update4) {
1475
+ this.rules = placeholderMatcher.updateDeco(update4, this.rules);
778
1476
  }
779
- });
780
- };
781
- var highlight = (options = {}) => {
782
- return [
783
- highlightStateField,
784
- highlightDecorations,
785
- listener(options),
786
- styles3
787
- ];
788
- };
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
+ ];
789
1485
 
790
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/image.ts
1486
+ // packages/ui/react-ui-editor/src/extensions/image.ts
791
1487
  import { syntaxTree } from "@codemirror/language";
792
- import { RangeSetBuilder, StateField as StateField2 } from "@codemirror/state";
793
- import { Decoration as Decoration4, EditorView as EditorView6, WidgetType as WidgetType2 } from "@codemirror/view";
1488
+ import { RangeSetBuilder, StateField as StateField3 } from "@codemirror/state";
1489
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
794
1490
  var image = (options = {}) => {
795
- return StateField2.define({
1491
+ return StateField3.define({
796
1492
  create: (state) => update(state, options),
797
1493
  update: (_, tr) => update(tr.state, options),
798
- provide: (field) => EditorView6.decorations.from(field)
1494
+ provide: (field) => EditorView7.decorations.from(field)
799
1495
  });
800
1496
  };
801
1497
  var update = (state, options) => {
@@ -808,7 +1504,8 @@ var update = (state, options) => {
808
1504
  if (urlNode) {
809
1505
  const hide = state.readOnly || cursor < node.from || cursor > node.to;
810
1506
  const url = state.sliceDoc(urlNode.from, urlNode.to);
811
- builder.add(hide ? node.from : node.to, node.to, Decoration4.replace({
1507
+ builder.add(hide ? node.from : node.to, node.to, Decoration5.replace({
1508
+ block: true,
812
1509
  widget: new ImageWidget(url)
813
1510
  }));
814
1511
  }
@@ -817,13 +1514,14 @@ var update = (state, options) => {
817
1514
  });
818
1515
  return builder.finish();
819
1516
  };
820
- var ImageWidget = class extends WidgetType2 {
1517
+ var ImageWidget = class extends WidgetType4 {
821
1518
  constructor(_url) {
822
1519
  super();
823
1520
  this._url = _url;
824
1521
  }
1522
+ // TODO(burdon): Does this need to be unique for each position?
825
1523
  eq(other) {
826
- return this._url === other?._url;
1524
+ return this._url === other._url;
827
1525
  }
828
1526
  toDOM(view) {
829
1527
  const img = document.createElement("img");
@@ -832,19 +1530,70 @@ var ImageWidget = class extends WidgetType2 {
832
1530
  return img;
833
1531
  }
834
1532
  };
835
-
836
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/link.ts
837
- import { syntaxTree as syntaxTree2 } from "@codemirror/language";
838
- import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField3 } from "@codemirror/state";
839
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
840
- var link = (options = {}) => {
841
- return StateField3.define({
842
- create: (state) => update2(state, options),
843
- update: (_, tr) => update2(tr.state, options),
844
- provide: (field) => EditorView7.decorations.from(field)
845
- });
846
- };
847
- var LinkButton = class extends WidgetType3 {
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
+ }
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 {
848
1597
  constructor(_pos, _url, _onAttach) {
849
1598
  super();
850
1599
  this._pos = _pos;
@@ -860,15 +1609,14 @@ var LinkButton = class extends WidgetType3 {
860
1609
  return el;
861
1610
  }
862
1611
  };
863
- var LinkText = class extends WidgetType3 {
864
- constructor(_pos, _text, _url) {
1612
+ var LinkText = class extends WidgetType5 {
1613
+ constructor(_text, _url) {
865
1614
  super();
866
- this._pos = _pos;
867
1615
  this._text = _text;
868
1616
  this._url = _url;
869
1617
  }
870
1618
  eq(other) {
871
- return this._pos === other._pos && this._url === other._url;
1619
+ return this._url === other._url;
872
1620
  }
873
1621
  toDOM(view) {
874
1622
  const link2 = document.createElement("a");
@@ -882,7 +1630,7 @@ var LinkText = class extends WidgetType3 {
882
1630
  link2.onclick = () => {
883
1631
  view.dispatch({
884
1632
  selection: {
885
- anchor: this._pos
1633
+ anchor: view.posAtDOM(link2)
886
1634
  }
887
1635
  });
888
1636
  };
@@ -904,12 +1652,12 @@ var update2 = (state, options) => {
904
1652
  return false;
905
1653
  }
906
1654
  if (state.readOnly || cursor < node.from || cursor > node.to) {
907
- builder.add(node.from, node.to, Decoration5.replace({
908
- widget: new LinkText(node.from, text, options.link ? url : void 0)
1655
+ builder.add(node.from, node.to, Decoration6.replace({
1656
+ widget: new LinkText(text, options.link ? url : void 0)
909
1657
  }));
910
1658
  }
911
1659
  if (options.onRender) {
912
- builder.add(node.to, node.to, Decoration5.replace({
1660
+ builder.add(node.to, node.to, Decoration6.replace({
913
1661
  widget: new LinkButton(node.from, url, options.onRender)
914
1662
  }));
915
1663
  }
@@ -920,21 +1668,21 @@ var update2 = (state, options) => {
920
1668
  return builder.finish();
921
1669
  };
922
1670
 
923
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/listener.ts
924
- import { StateField as StateField4 } from "@codemirror/state";
925
- var listener2 = ({ onChange }) => {
926
- return StateField4.define({
927
- create: () => null,
928
- update: (_value, transaction) => {
929
- if (transaction.docChanged && onChange) {
930
- onChange(transaction.newDoc.toString());
931
- }
932
- return null;
933
- }
934
- });
1671
+ // packages/ui/react-ui-editor/src/extensions/listener.ts
1672
+ import { EditorView as EditorView9 } from "@codemirror/view";
1673
+ var listener = ({ onFocus, onChange }) => {
1674
+ const extensions = [];
1675
+ onFocus && extensions.push(EditorView9.focusChangeEffect.of((_, focused) => {
1676
+ onFocus(focused);
1677
+ return null;
1678
+ }));
1679
+ onChange && extensions.push(EditorView9.updateListener.of((update4) => {
1680
+ onChange(update4.state.doc.toString());
1681
+ }));
1682
+ return extensions;
935
1683
  };
936
1684
 
937
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1685
+ // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
938
1686
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
939
1687
  import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
940
1688
  import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
@@ -943,22 +1691,21 @@ import { languages } from "@codemirror/language-data";
943
1691
  import { searchKeymap } from "@codemirror/search";
944
1692
  import { EditorState } from "@codemirror/state";
945
1693
  import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/theme-one-dark";
946
- import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView8, highlightActiveLine, keymap as keymap5, placeholder as placeholder2 } from "@codemirror/view";
1694
+ import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView10, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
947
1695
 
948
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/highlight.ts
1696
+ // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
949
1697
  import { markdownLanguage } from "@codemirror/lang-markdown";
950
1698
  import { HighlightStyle } from "@codemirror/language";
951
1699
  import { tags, styleTags, Tag } from "@lezer/highlight";
952
1700
  import { Table } from "@lezer/markdown";
953
- import get2 from "lodash.get";
954
- import { mx as mx3 } from "@dxos/react-ui-theme";
1701
+ import get5 from "lodash.get";
955
1702
  var markdownTags = {
956
1703
  Blockquote: Tag.define(),
957
1704
  CodeMark: Tag.define(),
958
1705
  CodeText: Tag.define(),
959
1706
  EmphasisMark: Tag.define(),
960
1707
  HeaderMark: Tag.define(),
961
- HorizontalRule: Tag.define(),
1708
+ // HorizontalRule: Tag.define(),
962
1709
  InlineCode: Tag.define(),
963
1710
  LinkLabel: Tag.define(),
964
1711
  LinkReference: Tag.define(),
@@ -1123,12 +1870,10 @@ var markdownHighlightStyle = (readonly) => {
1123
1870
  class: blockquote
1124
1871
  },
1125
1872
  // TODO(burdon): Replace with extension.
1126
- {
1127
- tag: [
1128
- markdownTags.HorizontalRule
1129
- ],
1130
- class: mx3(horizontalRule, readonly && "-indent-[100rem]")
1131
- },
1873
+ // {
1874
+ // tag: [markdownTags.HorizontalRule],
1875
+ // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1876
+ // },
1132
1877
  // TODO(burdon): Only if being edited.
1133
1878
  {
1134
1879
  tag: [
@@ -1139,17 +1884,17 @@ var markdownHighlightStyle = (readonly) => {
1139
1884
  ], {
1140
1885
  scope: markdownLanguage,
1141
1886
  all: {
1142
- fontFamily: get2(tokens, "fontFamily.body", []).join(",")
1887
+ fontFamily: get5(tokens, "fontFamily.body", []).join(",")
1143
1888
  }
1144
1889
  });
1145
1890
  };
1146
1891
 
1147
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/markdown/bundle.ts
1892
+ // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1148
1893
  var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1149
1894
  return [
1150
1895
  EditorState.allowMultipleSelections.of(true),
1151
1896
  EditorState.tabSize.of(2),
1152
- EditorView8.lineWrapping,
1897
+ EditorView10.lineWrapping,
1153
1898
  customKeymap(),
1154
1899
  bracketMatching2(),
1155
1900
  closeBrackets2(),
@@ -1184,7 +1929,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1184
1929
  syntaxHighlighting2(markdownHighlightStyle(readonly))
1185
1930
  ].filter(Boolean);
1186
1931
  };
1187
- var customKeymap = () => keymap5.of([
1932
+ var customKeymap = () => keymap4.of([
1188
1933
  // https://codemirror.net/docs/ref/#commands.indentWithTab
1189
1934
  indentWithTab,
1190
1935
  // https://codemirror.net/docs/ref/#commands.standardKeymap
@@ -1197,7 +1942,7 @@ var customKeymap = () => keymap5.of([
1197
1942
  ...searchKeymap
1198
1943
  ]);
1199
1944
 
1200
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/mention.ts
1945
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
1201
1946
  import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
1202
1947
  var mention = ({ onSearch }) => {
1203
1948
  return autocompletion2({
@@ -1220,15 +1965,20 @@ var mention = ({ onSearch }) => {
1220
1965
  });
1221
1966
  };
1222
1967
 
1223
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/table.ts
1968
+ // packages/ui/react-ui-editor/src/extensions/mermaid.ts
1969
+ var mermaid = () => {
1970
+ return [];
1971
+ };
1972
+
1973
+ // packages/ui/react-ui-editor/src/extensions/table.ts
1224
1974
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1225
1975
  import { RangeSetBuilder as RangeSetBuilder3, StateField as StateField5 } from "@codemirror/state";
1226
- import { Decoration as Decoration6, EditorView as EditorView9, WidgetType as WidgetType4 } from "@codemirror/view";
1976
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
1227
1977
  var table = (options = {}) => {
1228
1978
  return StateField5.define({
1229
1979
  create: (state) => update3(state, options),
1230
1980
  update: (_, tr) => update3(tr.state, options),
1231
- provide: (field) => EditorView9.decorations.from(field)
1981
+ provide: (field) => EditorView11.decorations.from(field)
1232
1982
  });
1233
1983
  };
1234
1984
  var update3 = (state, options) => {
@@ -1272,16 +2022,17 @@ var update3 = (state, options) => {
1272
2022
  });
1273
2023
  tables.forEach((table2) => {
1274
2024
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
1275
- hide && builder.add(table2.from, table2.to, Decoration6.replace({
2025
+ hide && builder.add(table2.from, table2.to, Decoration7.replace({
2026
+ block: true,
1276
2027
  widget: new TableWidget(table2)
1277
2028
  }));
1278
- builder.add(table2.from, table2.to, Decoration6.mark({
2029
+ builder.add(table2.from, table2.to, Decoration7.mark({
1279
2030
  class: "cm-table"
1280
2031
  }));
1281
2032
  });
1282
2033
  return builder.finish();
1283
2034
  };
1284
- var TableWidget = class extends WidgetType4 {
2035
+ var TableWidget = class extends WidgetType6 {
1285
2036
  constructor(_table) {
1286
2037
  super();
1287
2038
  this._table = _table;
@@ -1315,15 +2066,15 @@ var TableWidget = class extends WidgetType4 {
1315
2066
  }
1316
2067
  };
1317
2068
 
1318
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tasklist.ts
1319
- import { EditorView as EditorView10, Decoration as Decoration7, WidgetType as WidgetType5, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin3 } from "@codemirror/view";
1320
- var styles4 = EditorView10.baseTheme({
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";
2071
+ var styles5 = EditorView12.baseTheme({
1321
2072
  "& .cm-task-item": {
1322
2073
  paddingLeft: "4px",
1323
2074
  paddingRight: "8px"
1324
2075
  }
1325
2076
  });
1326
- var CheckboxWidget = class extends WidgetType5 {
2077
+ var CheckboxWidget = class extends WidgetType7 {
1327
2078
  constructor(_pos, _checked, _indent, _onCheck) {
1328
2079
  super();
1329
2080
  this._pos = _pos;
@@ -1358,12 +2109,12 @@ var CheckboxWidget = class extends WidgetType5 {
1358
2109
  }
1359
2110
  };
1360
2111
  var tasklist = (options = {}) => {
1361
- const taskMatcher = new MatchDecorator2({
2112
+ const taskMatcher = new MatchDecorator3({
1362
2113
  regexp: /^(\s*)- \[([ xX])\]\s/g,
1363
2114
  decoration: (match, view, pos) => {
1364
2115
  const indent = Math.floor(match[1].length / 2);
1365
2116
  const checked = match[2] === "x" || match[2] === "X";
1366
- return Decoration7.replace({
2117
+ return Decoration8.replace({
1367
2118
  widget: new CheckboxWidget(pos, checked, indent, view.state.readOnly ? void 0 : (checked2) => {
1368
2119
  const idx = pos + match[0].indexOf("[") + 1;
1369
2120
  view.dispatch({
@@ -1381,7 +2132,7 @@ var tasklist = (options = {}) => {
1381
2132
  });
1382
2133
  }
1383
2134
  });
1384
- const tasks = ViewPlugin3.fromClass(class {
2135
+ const tasks = ViewPlugin6.fromClass(class {
1385
2136
  constructor(view) {
1386
2137
  this.tasks = taskMatcher.createDeco(view);
1387
2138
  }
@@ -1390,69 +2141,116 @@ var tasklist = (options = {}) => {
1390
2141
  }
1391
2142
  }, {
1392
2143
  decorations: (value) => value.tasks,
1393
- provide: (plugin) => EditorView10.atomicRanges.of((view) => {
1394
- return view.plugin(plugin)?.tasks || Decoration7.none;
2144
+ provide: (plugin) => EditorView12.atomicRanges.of((view) => {
2145
+ return view.plugin(plugin)?.tasks || Decoration8.none;
1395
2146
  })
1396
2147
  });
1397
2148
  return [
1398
2149
  tasks,
1399
- styles4
2150
+ styles5
1400
2151
  ];
1401
2152
  };
1402
2153
 
1403
- // packages/ui/react-ui-editor/src/components/TextEditor/extensions/tooltip.tsx
1404
- import { hoverTooltip } from "@codemirror/view";
1405
- import { tooltipContent } from "@dxos/react-ui-theme";
1406
- var markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)/;
1407
- var tooltip = ({ onHover, regexp = markdownLinkRegexp }) => hoverTooltip((view, pos) => {
1408
- const { from, text } = view.state.doc.lineAt(pos);
1409
- const p = pos - from;
2154
+ // packages/ui/react-ui-editor/src/extensions/typewriter.ts
2155
+ import { keymap as keymap5 } from "@codemirror/view";
2156
+ var defaultItems = [
2157
+ "hello world!",
2158
+ "this is a test.",
2159
+ "this is [DXOS](https://dxos.org)"
2160
+ ];
2161
+ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
2162
+ let t;
1410
2163
  let idx = 0;
1411
- let match;
1412
- do {
1413
- match = text.substring(idx).match(regexp);
1414
- if (!match) {
1415
- match = void 0;
1416
- break;
1417
- }
1418
- idx = text.indexOf(match[0]);
1419
- if (p >= idx && p < idx + match[0].length) {
1420
- break;
1421
- }
1422
- idx += match[0].length;
1423
- } while (true);
1424
- if (!match) {
1425
- return null;
1426
- }
1427
- const [, , url] = match ?? [];
1428
- return {
1429
- pos: idx + from,
1430
- end: idx + from + match[0].length,
1431
- above: true,
1432
- create: () => {
1433
- const el = document.createElement("div");
1434
- el.className = tooltipContent({}, "pli-2 plb-1");
1435
- onHover(el, url);
1436
- return {
1437
- dom: el,
1438
- offset: {
1439
- x: 0,
1440
- y: 12
2164
+ return [
2165
+ keymap5.of([
2166
+ {
2167
+ // Reset.
2168
+ key: "alt-meta-'",
2169
+ run: (view) => {
2170
+ clearTimeout(t);
2171
+ idx = 0;
2172
+ return true;
1441
2173
  }
1442
- };
1443
- }
1444
- };
2174
+ },
2175
+ {
2176
+ // Next prompt.
2177
+ // TODO(burdon): Press 1-9 to select prompt?
2178
+ key: "shift-meta-'",
2179
+ run: (view) => {
2180
+ clearTimeout(t);
2181
+ const text = items[idx++];
2182
+ if (idx === items?.length) {
2183
+ idx = 0;
2184
+ }
2185
+ let i = 0;
2186
+ const insert = (d = 0) => {
2187
+ t = setTimeout(() => {
2188
+ const pos = view.state.selection.main.head;
2189
+ view.dispatch({
2190
+ changes: {
2191
+ from: pos,
2192
+ insert: text[i++]
2193
+ },
2194
+ selection: {
2195
+ anchor: pos + 1
2196
+ }
2197
+ });
2198
+ if (i < text.length) {
2199
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
2200
+ }
2201
+ }, d);
2202
+ };
2203
+ insert();
2204
+ return true;
2205
+ }
2206
+ }
2207
+ ])
2208
+ ];
2209
+ };
2210
+
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
+ }
1445
2224
  });
1446
2225
 
1447
- // packages/ui/react-ui-editor/src/components/TextEditor/themes/default.ts
1448
- import get3 from "lodash.get";
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
+ // packages/ui/react-ui-editor/src/themes/default.ts
2233
+ import get6 from "lodash.get";
1449
2234
  var defaultTheme = {
1450
2235
  //
1451
2236
  // Main layout:
1452
- // <div class=".cm-editor .cm-focused">
1453
- // <div class=".cm-scroller">
1454
- // <div class=".cm-content" role="textbox" contenteditable="true">
1455
- // <div class=".cm-line" />
2237
+ // https://codemirror.net/examples/styling
2238
+ //
2239
+ // <div class="cm-editor [cm-focused] [generated classes]">
2240
+ // <div class="cm-scroller">
2241
+ // <div class="cm-gutters">
2242
+ // <div class="cm-gutter [...]">
2243
+ // <div class="cm-gutterElement">...</div>
2244
+ // </div>
2245
+ // </div>
2246
+ // <div class="cm-content" role="textbox" contenteditable="true">
2247
+ // <div class="cm-line"></div>
2248
+ // </div>
2249
+ // <div class="cm-selectionLayer">
2250
+ // <div class="cm-selectionBackground"></div>
2251
+ // </div>
2252
+ // <div class="cm-cursorLayer">
2253
+ // <div class="cm-cursor"></div>
1456
2254
  // </div>
1457
2255
  // </div>
1458
2256
  // </div>
@@ -1463,19 +2261,19 @@ var defaultTheme = {
1463
2261
  },
1464
2262
  "& .cm-scroller": {
1465
2263
  overflow: "visible",
1466
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2264
+ fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
1467
2265
  },
1468
2266
  "& .cm-content": {
1469
2267
  padding: 0,
1470
- // Base font size (otherwise defined by HTML tag, which might be different for storybook).
2268
+ // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
1471
2269
  fontSize: "16px"
1472
2270
  },
1473
2271
  "&light .cm-content": {
1474
- color: get3(tokens, "extend.colors.neutral.900", "black"),
2272
+ color: get6(tokens, "extend.colors.neutral.900", "black"),
1475
2273
  caretColor: "black"
1476
2274
  },
1477
2275
  "&dark .cm-content": {
1478
- color: get3(tokens, "extend.colors.neutral.100", "white"),
2276
+ color: get6(tokens, "extend.colors.neutral.100", "white"),
1479
2277
  caretColor: "white"
1480
2278
  },
1481
2279
  //
@@ -1496,28 +2294,36 @@ var defaultTheme = {
1496
2294
  "& .cm-line": {
1497
2295
  paddingInline: 0
1498
2296
  },
1499
- "& .cm-line *": {},
1500
2297
  "& .cm-activeLine": {
1501
- backgroundColor: "transparent"
2298
+ background: "inherit"
1502
2299
  },
1503
2300
  //
1504
- // selection
2301
+ // Selection
1505
2302
  //
1506
- "&light .cm-selectionBackground, &light.cm-focused .cm-selectionBackground": {
1507
- background: get3(tokens, "extend.colors.primary.150", "#00ffff")
2303
+ "&light .cm-selectionBackground": {
2304
+ background: get6(tokens, "extend.colors.primary.100")
2305
+ },
2306
+ "&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2307
+ background: get6(tokens, "extend.colors.primary.200")
2308
+ },
2309
+ "&dark .cm-selectionBackground": {
2310
+ background: get6(tokens, "extend.colors.primary.700")
1508
2311
  },
1509
- "&dark .cm-selectionBackground, &dark.cm-focused .cm-selectionBackground": {
1510
- background: get3(tokens, "extend.colors.primary.850", "#00ffff")
2312
+ "&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2313
+ background: get6(tokens, "extend.colors.primary.600")
1511
2314
  },
1512
- "&light .cm-selectionMatch": {
1513
- background: get3(tokens, "extend.colors.primary.250", "#00ffff") + "44"
2315
+ //
2316
+ // Search
2317
+ //
2318
+ "&light .cm-searchMatch": {
2319
+ backgroundColor: get6(tokens, "extend.colors.yellow.100")
1514
2320
  },
1515
- "&dark .cm-selectionMatch": {
1516
- background: get3(tokens, "extend.colors.primary.600", "#00ffff") + "44"
2321
+ "&dark .cm-searchMatch": {
2322
+ backgroundColor: get6(tokens, "extend.colors.yellow.700")
1517
2323
  },
1518
2324
  //
1519
2325
  // collaboration
1520
- // TODO(burdon): Review classnames (YJS dependent?)
2326
+ // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
1521
2327
  //
1522
2328
  "&light .cm-ySelection, &light .cm-yLineSelection": {
1523
2329
  mixBlendMode: "multiply"
@@ -1546,12 +2352,12 @@ var defaultTheme = {
1546
2352
  // link
1547
2353
  //
1548
2354
  "& .cm-link": {
1549
- color: get3(tokens, "extend.colors.primary.500"),
2355
+ color: get6(tokens, "extend.colors.primary.500"),
1550
2356
  textDecorationLine: "underline",
1551
2357
  textDecorationThickness: "1px",
1552
2358
  textUnderlineOffset: "2px",
1553
2359
  borderRadius: ".125rem",
1554
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2360
+ fontFamily: get6(tokens, "fontFamily.body", []).join(",")
1555
2361
  },
1556
2362
  //
1557
2363
  // tooltip
@@ -1573,7 +2379,7 @@ var defaultTheme = {
1573
2379
  display: "none"
1574
2380
  },
1575
2381
  "& .cm-completionLabel": {
1576
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2382
+ fontFamily: get6(tokens, "fontFamily.body", []).join(",")
1577
2383
  },
1578
2384
  "& .cm-completionMatchedText": {
1579
2385
  textDecoration: "none"
@@ -1589,15 +2395,15 @@ var defaultTheme = {
1589
2395
  // table
1590
2396
  //
1591
2397
  "& .cm-table *": {
1592
- fontFamily: `${get3(tokens, "fontFamily.mono", []).join(",")} !important`,
2398
+ fontFamily: `${get6(tokens, "fontFamily.mono", []).join(",")} !important`,
1593
2399
  textDecoration: "none !important"
1594
2400
  },
1595
2401
  "& .cm-table-head": {
1596
2402
  padding: "2px 16px 2px 0px",
1597
- borderBottom: `1px solid ${get3(tokens, "extend.colors.neutral.500")}`,
2403
+ borderBottom: `1px solid ${get6(tokens, "extend.colors.neutral.500")}`,
1598
2404
  fontWeight: 100,
1599
2405
  textAlign: "left",
1600
- color: get3(tokens, "extend.colors.neutral.500")
2406
+ color: get6(tokens, "extend.colors.neutral.500")
1601
2407
  },
1602
2408
  "& .cm-table-cell": {
1603
2409
  padding: "2px 16px 2px 0px"
@@ -1612,8 +2418,8 @@ var defaultTheme = {
1612
2418
  // font size
1613
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.
1614
2420
  //
1615
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
1616
- const height = get3(tokens, [
2421
+ ...Object.keys(get6(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2422
+ const height = get6(tokens, [
1617
2423
  "extend",
1618
2424
  "fontSize",
1619
2425
  fontSize,
@@ -1633,6 +2439,7 @@ var defaultTheme = {
1633
2439
  }, {}),
1634
2440
  /**
1635
2441
  * Panels
2442
+ * TODO(burdon): Needs styling attention (esp. dark mode).
1636
2443
  * https://github.com/codemirror/search/blob/main/src/search.ts#L745
1637
2444
  *
1638
2445
  * Find/replace panel.
@@ -1647,71 +2454,65 @@ var defaultTheme = {
1647
2454
  * </div
1648
2455
  */
1649
2456
  "& .cm-panels": {
1650
- border: `1px solid ${get3(tokens, "extend.colors.neutral.200")}`,
1651
- borderBottom: "none"
2457
+ border: `1px solid ${get6(tokens, "extend.colors.neutral.200")}`
1652
2458
  },
1653
2459
  "& .cm-panel": {
1654
- background: get3(tokens, "extend.colors.neutral.50"),
1655
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2460
+ background: get6(tokens, "extend.colors.neutral.50"),
2461
+ fontFamily: get6(tokens, "fontFamily.body", []).join(",")
1656
2462
  },
1657
2463
  "& .cm-button": {
1658
2464
  margin: "4px",
1659
- fontFamily: get3(tokens, "fontFamily.body", []).join(","),
1660
- background: get3(tokens, "extend.colors.neutral.100"),
1661
- border: "none"
1662
- },
1663
- "& .cm-searchMatch": {
1664
- backgroundColor: get3(tokens, "extend.colors.yellow.100")
2465
+ fontFamily: get6(tokens, "fontFamily.body", []).join(","),
2466
+ background: get6(tokens, "extend.colors.neutral.100"),
2467
+ backgroundImage: "none",
2468
+ border: "none",
2469
+ "&:hover": {
2470
+ background: get6(tokens, "extend.colors.neutral.200")
2471
+ },
2472
+ "&:active": {
2473
+ background: get6(tokens, "extend.colors.neutral.300"),
2474
+ backgroundImage: "none"
2475
+ }
1665
2476
  },
1666
- "& .cm-searchMatch.cm-searchMatch-selected": {
1667
- backgroundColor: get3(tokens, "extend.colors.primary.100")
2477
+ "& .cm-panel input[type=checkbox]": {
2478
+ marginRight: "0.4rem !important"
1668
2479
  }
1669
2480
  };
1670
2481
  var textTheme = {
1671
2482
  "& .cm-scroller": {
1672
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2483
+ fontFamily: get6(tokens, "fontFamily.body", []).join(",")
1673
2484
  },
1674
2485
  "& .cm-placeholder": {
1675
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2486
+ fontFamily: get6(tokens, "fontFamily.body", []).join(",")
1676
2487
  }
1677
2488
  };
1678
2489
  var markdownTheme = {
1679
2490
  // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
1680
2491
  "& .cm-placeholder": {
1681
- fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2492
+ fontFamily: get6(tokens, "fontFamily.body", []).join(",")
1682
2493
  }
1683
2494
  };
1684
2495
  var codeTheme = {
1685
2496
  "& .cm-scroller": {
1686
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2497
+ fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
1687
2498
  },
1688
2499
  "& .cm-placeholder": {
1689
- fontFamily: get3(tokens, "fontFamily.mono", []).join(",")
2500
+ fontFamily: get6(tokens, "fontFamily.mono", []).join(",")
1690
2501
  }
1691
2502
  };
1692
2503
 
1693
2504
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
1694
- import { EditorState as EditorState2 } from "@codemirror/state";
1695
- import { EditorView as EditorView11 } from "@codemirror/view";
1696
- import { useFocusableGroup } from "@fluentui/react-tabster";
1697
- import { vim } from "@replit/codemirror-vim";
1698
- import defaultsDeep2 from "lodash.defaultsdeep";
1699
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useState } from "react";
1700
- import { generateName } from "@dxos/display-name";
1701
- import { useThemeContext } from "@dxos/react-ui";
1702
- import { getColorForValue, inputSurface, mx as mx4 } from "@dxos/react-ui-theme";
1703
2505
  var EditorModes = [
1704
2506
  "default",
1705
2507
  "vim"
1706
2508
  ];
1707
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode, extensions = [], slots = defaultSlots }, forwardedRef) => {
2509
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
1708
2510
  const { themeMode } = useThemeContext();
1709
2511
  const tabsterDOMAttribute = useFocusableGroup({
1710
2512
  tabBehavior: "limited"
1711
2513
  });
1712
2514
  const [root, setRoot] = useState(null);
1713
- const [state, setState] = useState();
1714
- const [view, setView] = useState();
2515
+ const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
1715
2516
  useImperativeHandle(forwardedRef, () => ({
1716
2517
  root,
1717
2518
  state,
@@ -1721,10 +2522,10 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode,
1721
2522
  state,
1722
2523
  root
1723
2524
  ]);
1724
- const { awareness, peer } = model;
1725
- useEffect2(() => {
1726
- if (awareness && peer) {
1727
- awareness.setLocalStateField("user", {
2525
+ const { awareness: awareness2, peer } = model;
2526
+ useEffect(() => {
2527
+ if (awareness2 && peer) {
2528
+ awareness2.setLocalStateField("user", {
1728
2529
  name: peer.name ?? generateName(peer.id),
1729
2530
  color: getColorForValue({
1730
2531
  value: peer.id,
@@ -1738,11 +2539,24 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode,
1738
2539
  });
1739
2540
  }
1740
2541
  }, [
1741
- awareness,
2542
+ awareness2,
1742
2543
  peer,
1743
2544
  themeMode
1744
2545
  ]);
1745
- useEffect2(() => {
2546
+ useEffect(() => {
2547
+ if (view && comments2?.length) {
2548
+ view.dispatch({
2549
+ effects: setCommentRange.of({
2550
+ model,
2551
+ comments: comments2
2552
+ })
2553
+ });
2554
+ }
2555
+ }, [
2556
+ view,
2557
+ comments2
2558
+ ]);
2559
+ useEffect(() => {
1746
2560
  if (!root) {
1747
2561
  return;
1748
2562
  }
@@ -1750,14 +2564,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode,
1750
2564
  doc: model.text(),
1751
2565
  extensions: [
1752
2566
  readonly && EditorState2.readOnly.of(readonly),
1753
- // TODO(burdon): Factor out VIM mode?
2567
+ // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
1754
2568
  editorMode === "vim" && vim(),
1755
2569
  // Theme.
1756
- // TODO(burdon): Make optional.
1757
- EditorView11.baseTheme(defaultTheme),
1758
- EditorView11.theme(slots?.editor?.theme ?? {}),
1759
- // TODO(burdon): themeMode doesn't change in storybooks.
1760
- EditorView11.darkTheme.of(themeMode === "dark"),
2570
+ // TODO(burdon): Make theme configurable.
2571
+ EditorView13.baseTheme(defaultTheme),
2572
+ EditorView13.theme(slots?.editor?.theme ?? {}),
2573
+ EditorView13.darkTheme.of(themeMode === "dark"),
1761
2574
  // Storage and replication.
1762
2575
  model.extension,
1763
2576
  // Custom.
@@ -1765,22 +2578,23 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, editorMode,
1765
2578
  ].filter(Boolean)
1766
2579
  });
1767
2580
  view?.destroy();
1768
- setView(new EditorView11({
2581
+ setEditor({
1769
2582
  state: state2,
1770
- parent: root
1771
- }));
1772
- setState(state2);
2583
+ view: new EditorView13({
2584
+ state: state2,
2585
+ parent: root
2586
+ })
2587
+ });
1773
2588
  return () => {
1774
2589
  view?.destroy();
1775
- setView(void 0);
1776
- setState(void 0);
2590
+ setEditor(void 0);
1777
2591
  };
1778
2592
  }, [
1779
2593
  root,
1780
2594
  model,
1781
- themeMode,
1782
2595
  readonly,
1783
- editorMode
2596
+ editorMode,
2597
+ themeMode
1784
2598
  ]);
1785
2599
  const handleKeyUp = useCallback((event) => {
1786
2600
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
@@ -1840,336 +2654,218 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], sl
1840
2654
  ...extensions
1841
2655
  ],
1842
2656
  slots,
1843
- ...props
1844
- });
1845
- });
1846
- var defaultSlots = {
1847
- root: {
1848
- className: mx4("p-2", inputSurface)
1849
- }
1850
- };
1851
- var defaultTextSlots = {
1852
- ...defaultSlots,
1853
- editor: {
1854
- theme: textTheme
1855
- }
1856
- };
1857
- var defaultMarkdownSlots = {
1858
- ...defaultSlots,
1859
- editor: {
1860
- theme: markdownTheme
1861
- }
1862
- };
1863
-
1864
- // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
1865
- import get4 from "lodash.get";
1866
- import { useEffect as useEffect3, useState as useState2 } from "react";
1867
- import { yCollab } from "y-codemirror.next";
1868
- import { invariant as invariant3 } from "@dxos/invariant";
1869
- import { getRawDoc, isActualAutomergeObject } from "@dxos/react-client/echo";
1870
-
1871
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
1872
- import { Annotation, Facet, StateEffect as StateEffect2, StateField as StateField6 } from "@codemirror/state";
1873
- import { ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1874
- import * as automerge2 from "@dxos/automerge/automerge";
1875
-
1876
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
1877
- import { next as automerge } from "@dxos/automerge/automerge";
1878
-
1879
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/automerge-to-codemirror.ts
1880
- import { ChangeSet } from "@codemirror/state";
1881
- var automergeToCodemirror = (view, selection, target, patches) => {
1882
- for (const patch of patches) {
1883
- const changeSpec = handlePatch(patch, target, view.state);
1884
- if (changeSpec != null) {
1885
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
1886
- selection = selection.map(changeSet, 1);
1887
- view.dispatch({
1888
- changes: changeSet,
1889
- annotations: reconcileAnnotationType.of({})
1890
- });
1891
- }
1892
- }
1893
- view.dispatch({
1894
- selection,
1895
- annotations: reconcileAnnotationType.of({})
1896
- });
1897
- };
1898
- var handlePatch = (patch, target, state) => {
1899
- if (patch.action === "insert") {
1900
- return handleInsert(target, patch);
1901
- } else if (patch.action === "splice") {
1902
- return handleSplice(target, patch);
1903
- } else if (patch.action === "del") {
1904
- return handleDel(target, patch);
1905
- } else if (patch.action === "put") {
1906
- return handlePut(target, patch, state);
1907
- } else {
1908
- return null;
1909
- }
1910
- };
1911
- var handleInsert = (target, patch) => {
1912
- const index = charPath(target, patch.path);
1913
- if (index == null) {
1914
- return [];
1915
- }
1916
- const text = patch.values.map((v) => v ? v.toString() : "").join("");
1917
- return [
1918
- {
1919
- from: index,
1920
- to: index,
1921
- insert: text
1922
- }
1923
- ];
1924
- };
1925
- var handleSplice = (target, patch) => {
1926
- const index = charPath(target, patch.path);
1927
- if (index == null) {
1928
- return [];
1929
- }
1930
- return [
1931
- {
1932
- from: index,
1933
- insert: patch.value
1934
- }
1935
- ];
1936
- };
1937
- var handleDel = (target, patch) => {
1938
- const index = charPath(target, patch.path);
1939
- if (index == null) {
1940
- return [];
1941
- }
1942
- const length = patch.length || 1;
1943
- return [
1944
- {
1945
- from: index,
1946
- to: index + length
1947
- }
1948
- ];
1949
- };
1950
- var handlePut = (target, patch, state) => {
1951
- const index = charPath(target, [
1952
- ...patch.path,
1953
- 0
1954
- ]);
1955
- if (index == null) {
1956
- return [];
1957
- }
1958
- const length = state.doc.length;
1959
- if (typeof patch.value !== "string") {
1960
- return [];
1961
- }
1962
- return [
1963
- {
1964
- from: 0,
1965
- to: length,
1966
- insert: patch.value
1967
- }
1968
- ];
1969
- };
1970
- var charPath = (textPath, candidatePath) => {
1971
- if (candidatePath.length !== textPath.length + 1) {
1972
- return null;
1973
- }
1974
- for (let i = 0; i < textPath.length; i++) {
1975
- if (textPath[i] !== candidatePath[i]) {
1976
- return null;
1977
- }
1978
- }
1979
- const index = candidatePath[candidatePath.length - 1];
1980
- if (typeof index === "number") {
1981
- return index;
2657
+ ...props
2658
+ });
2659
+ });
2660
+ var defaultSlots = {
2661
+ root: {
2662
+ className: mx3("p-2", inputSurface)
1982
2663
  }
1983
- return null;
1984
2664
  };
1985
-
1986
- // packages/ui/react-ui-editor/src/hooks/automerge/translation/codemirror-to-automerge.ts
1987
- import { next as am } from "@dxos/automerge/automerge";
1988
- var codemirrorToAutomerge = (field, handle, transactions, state) => {
1989
- const { lastHeads, path } = state.field(field);
1990
- let hasChanges = false;
1991
- for (const tr of transactions) {
1992
- tr.changes.iterChanges(() => {
1993
- hasChanges = true;
1994
- });
2665
+ var defaultTextSlots = {
2666
+ ...defaultSlots,
2667
+ editor: {
2668
+ theme: textTheme
1995
2669
  }
1996
- if (!hasChanges) {
1997
- return void 0;
2670
+ };
2671
+ var defaultMarkdownSlots = {
2672
+ ...defaultSlots,
2673
+ editor: {
2674
+ theme: markdownTheme
1998
2675
  }
1999
- const newHeads = handle.changeAt(lastHeads, (doc) => {
2000
- for (const tr of transactions) {
2001
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
2002
- am.splice(doc, path, fromA, toA - fromA, inserted.toString());
2003
- });
2004
- }
2005
- });
2006
- return newHeads ?? void 0;
2007
2676
  };
2008
2677
 
2009
- // packages/ui/react-ui-editor/src/hooks/automerge/PatchSemaphore.ts
2010
- var PatchSemaphore = class {
2011
- constructor(field) {
2012
- this._inReconcile = false;
2013
- this._queue = [];
2014
- this.reconcile = (handle, view) => {
2015
- if (this._inReconcile) {
2016
- return;
2017
- }
2018
- this._inReconcile = true;
2019
- const path = getPath(view.state, this._field);
2020
- const oldHeads = getLastHeads(view.state, this._field);
2021
- let selection = view.state.selection;
2022
- const transactions = view.state.field(this._field).unreconciledTransactions.filter((tx) => !isReconcileTx(tx));
2023
- const toInvert = transactions.slice().reverse();
2024
- for (const tx of toInvert) {
2025
- const inverted = tx.changes.invert(tx.startState.doc);
2026
- selection = selection.map(inverted);
2027
- view.dispatch({
2028
- changes: inverted,
2029
- annotations: reconcileAnnotationType.of(true)
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";
2682
+
2683
+ // packages/ui/react-ui-editor/src/hooks/awareness-provider.ts
2684
+ import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
2685
+ import { Context as Context2 } from "@dxos/context";
2686
+ import { invariant as invariant3 } from "@dxos/invariant";
2687
+ import { log as log3 } from "@dxos/log";
2688
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/awareness-provider.ts";
2689
+ var DEBOUNCE_INTERVAL = 100;
2690
+ var SpaceAwarenessProvider = class {
2691
+ constructor(params) {
2692
+ this.remoteStateChange = new Event2();
2693
+ this._remoteStates = /* @__PURE__ */ new Map();
2694
+ this._space = params.space;
2695
+ this._channel = params.channel;
2696
+ this._peerId = params.peerId;
2697
+ this._info = params.info;
2698
+ }
2699
+ open() {
2700
+ this._ctx = new Context2();
2701
+ this._postTask = new DeferredTask(this._ctx, async () => {
2702
+ if (this._localState) {
2703
+ await this._space.postMessage(this._channel, {
2704
+ kind: "post",
2705
+ state: this._localState
2030
2706
  });
2707
+ await sleep(DEBOUNCE_INTERVAL);
2031
2708
  }
2032
- let newHeads = codemirrorToAutomerge(this._field, handle, transactions, view.state);
2033
- if (newHeads === null || newHeads === void 0) {
2034
- newHeads = automerge.getHeads(handle.docSync());
2709
+ });
2710
+ const unsubscribe = this._space.listen(this._channel, (message) => {
2711
+ switch (message.payload.kind) {
2712
+ case "query": {
2713
+ this._handleQueryMessage();
2714
+ break;
2715
+ }
2716
+ case "post": {
2717
+ this._handlePostMessage(message.payload);
2718
+ break;
2719
+ }
2035
2720
  }
2036
- const diff = automerge.equals(oldHeads, newHeads) ? [] : automerge.diff(handle.docSync(), oldHeads, newHeads);
2037
- automergeToCodemirror(view, selection, path, diff);
2038
- view.dispatch({
2039
- effects: updateHeads(newHeads),
2040
- annotations: reconcileAnnotationType.of({})
2721
+ });
2722
+ this._ctx.onDispose(unsubscribe);
2723
+ void this._space.postMessage(this._channel, {
2724
+ kind: "query"
2725
+ }).catch((err) => {
2726
+ log3.debug("failed to query awareness", {
2727
+ err
2728
+ }, {
2729
+ F: __dxlog_file4,
2730
+ L: 85,
2731
+ S: this,
2732
+ C: (f, a) => f(...a)
2041
2733
  });
2042
- this._inReconcile = false;
2734
+ });
2735
+ }
2736
+ close() {
2737
+ void this._ctx?.dispose();
2738
+ this._ctx = void 0;
2739
+ this._postTask = void 0;
2740
+ }
2741
+ update(position) {
2742
+ invariant3(this._postTask, void 0, {
2743
+ F: __dxlog_file4,
2744
+ L: 96,
2745
+ S: this,
2746
+ A: [
2747
+ "this._postTask",
2748
+ ""
2749
+ ]
2750
+ });
2751
+ this._localState = {
2752
+ peerId: this._peerId,
2753
+ position,
2754
+ info: this._info
2043
2755
  };
2044
- if (field !== void 0) {
2045
- this._field = field;
2046
- }
2756
+ this._postTask.schedule();
2757
+ }
2758
+ getRemoteStates() {
2759
+ return Array.from(this._remoteStates.values());
2760
+ }
2761
+ _handleQueryMessage() {
2762
+ invariant3(this._postTask, void 0, {
2763
+ F: __dxlog_file4,
2764
+ L: 111,
2765
+ S: this,
2766
+ A: [
2767
+ "this._postTask",
2768
+ ""
2769
+ ]
2770
+ });
2771
+ this._postTask.schedule();
2772
+ }
2773
+ _handlePostMessage(message) {
2774
+ invariant3(message.kind === "post", void 0, {
2775
+ F: __dxlog_file4,
2776
+ L: 116,
2777
+ S: this,
2778
+ A: [
2779
+ "message.kind === 'post'",
2780
+ ""
2781
+ ]
2782
+ });
2783
+ this._remoteStates.set(message.state.peerId, message.state);
2784
+ this.remoteStateChange.emit();
2047
2785
  }
2048
2786
  };
2049
2787
 
2050
- // packages/ui/react-ui-editor/src/hooks/automerge/plugin.ts
2051
- var effectType = StateEffect2.define({});
2052
- var updateHeads = (newHeads) => effectType.of({
2053
- newHeads
2054
- });
2055
- var getLastHeads = (state, field) => state.field(field).lastHeads;
2056
- var getPath = (state, field) => state.field(field).path;
2057
- var semaphoreFacet = Facet.define({
2058
- combine: (values) => values.at(-1)
2059
- });
2060
- var automergePlugin = (handle, path) => {
2061
- const stateField = StateField6.define({
2062
- create: () => ({
2063
- lastHeads: automerge2.getHeads(handle.docSync()),
2064
- unreconciledTransactions: [],
2065
- path: path.slice()
2066
- }),
2067
- update: (value, tr) => {
2068
- const result = {
2069
- lastHeads: value.lastHeads,
2070
- unreconciledTransactions: value.unreconciledTransactions.slice(),
2071
- path: path.slice()
2072
- };
2073
- let clearUnreconciled = false;
2074
- for (const effect of tr.effects) {
2075
- if (effect.is(effectType)) {
2076
- result.lastHeads = effect.value.newHeads;
2077
- clearUnreconciled = true;
2078
- }
2079
- }
2080
- if (clearUnreconciled) {
2081
- result.unreconciledTransactions = [];
2082
- } else {
2083
- if (!isReconcileTx(tr)) {
2084
- result.unreconciledTransactions.push(tr);
2085
- }
2086
- }
2087
- return result;
2088
- }
2089
- });
2090
- const semaphore = new PatchSemaphore(stateField);
2091
- const viewPlugin = ViewPlugin4.fromClass(class AutomergeCodemirrorViewPlugin {
2092
- constructor(view) {
2093
- this._handleChange = () => {
2094
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2095
- };
2096
- this._view = view;
2097
- handle.addListener("change", this._handleChange);
2098
- }
2099
- update(update4) {
2100
- if (update4.transactions.length > 0 && update4.transactions.some((t) => !isReconcileTx(t))) {
2101
- queueMicrotask(() => {
2102
- this._view.state.facet(semaphoreFacet).reconcile(handle, this._view);
2103
- });
2104
- }
2105
- }
2106
- destroy() {
2107
- handle.addListener("change", this._handleChange);
2108
- }
2109
- });
2110
- return {
2111
- extension: [
2112
- stateField,
2113
- semaphoreFacet.of(semaphore),
2114
- viewPlugin
2115
- ]
2116
- };
2117
- };
2118
- var reconcileAnnotationType = Annotation.define();
2119
- var isReconcileTx = (tr) => !!tr.annotation(reconcileAnnotationType);
2120
-
2121
- // packages/ui/react-ui-editor/src/hooks/yjs/cursors.ts
2122
- import * as random2 from "lib0/random";
2123
- var cursorColors = [
2124
- {
2125
- color: "#30bced",
2126
- light: "#30bced33"
2127
- },
2128
- {
2129
- color: "#6eeb83",
2130
- light: "#6eeb8333"
2131
- },
2132
- {
2133
- color: "#ffbc42",
2134
- light: "#ffbc4233"
2135
- },
2136
- {
2137
- color: "#ecd444",
2138
- light: "#ecd44433"
2139
- },
2140
- {
2141
- color: "#ee6352",
2142
- light: "#ee635233"
2143
- },
2144
- {
2145
- color: "#9ac2c9",
2146
- light: "#9ac2c933"
2147
- },
2148
- {
2149
- color: "#8acb88",
2150
- light: "#8acb8833"
2151
- },
2152
- {
2153
- color: "#1be7ff",
2154
- light: "#1be7ff33"
2155
- }
2156
- ];
2157
- var cursorColor = cursorColors[random2.uint32() % cursorColors.length];
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";
2158
2792
 
2159
- // packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts
2793
+ // packages/ui/react-ui-editor/src/hooks/yjs.ts
2160
2794
  import * as decoding from "lib0/decoding";
2161
2795
  import * as encoding from "lib0/encoding";
2162
2796
  import { Observable } from "lib0/observable";
2163
- import * as awarenessProtocol from "y-protocols/awareness";
2164
- import { log } from "@dxos/log";
2165
- var __dxlog_file3 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/yjs/space-provider.ts";
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
+ ]
2841
+ });
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
2859
+ };
2860
+ return model;
2861
+ };
2166
2862
  var messageAwareness = 1;
2167
2863
  var messageQueryAwareness = 3;
2168
- var SpaceAwarenessProvider = class extends Observable {
2169
- constructor({ space, doc, channel, awareness }) {
2864
+ var YAwarenessProvider = class extends Observable {
2865
+ constructor({ space, doc, channel, awareness: awareness2 }) {
2170
2866
  super();
2171
2867
  this._space = space;
2172
- this._awareness = awareness ?? new awarenessProtocol.Awareness(doc);
2868
+ this._awareness = awareness2 ?? new YP.Awareness(doc);
2173
2869
  this._channel = channel;
2174
2870
  this._clientId = doc.clientID;
2175
2871
  this._awareness.on("update", this._handleAwarenessUpdate.bind(this));
@@ -2184,7 +2880,7 @@ var SpaceAwarenessProvider = class extends Observable {
2184
2880
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessQuery));
2185
2881
  const encoderAwarenessState = encoding.createEncoder();
2186
2882
  encoding.writeVarUint(encoderAwarenessState, messageAwareness);
2187
- encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [
2883
+ encoding.writeVarUint8Array(encoderAwarenessState, YP.encodeAwarenessUpdate(this.awareness, [
2188
2884
  this._clientId
2189
2885
  ]));
2190
2886
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwarenessState));
@@ -2193,27 +2889,27 @@ var SpaceAwarenessProvider = class extends Observable {
2193
2889
  return this._awareness;
2194
2890
  }
2195
2891
  _handleAwarenessUpdate({ added, updated, removed }, origin) {
2196
- log("awareness update", {
2892
+ log4("awareness update", {
2197
2893
  added,
2198
2894
  updated,
2199
2895
  removed,
2200
2896
  origin
2201
2897
  }, {
2202
- F: __dxlog_file3,
2203
- L: 67,
2898
+ F: __dxlog_file5,
2899
+ L: 122,
2204
2900
  S: this,
2205
2901
  C: (f, a) => f(...a)
2206
2902
  });
2207
2903
  const changedClients = added.concat(updated).concat(removed);
2208
2904
  const encoderAwareness = encoding.createEncoder();
2209
2905
  encoding.writeVarUint(encoderAwareness, messageAwareness);
2210
- encoding.writeVarUint8Array(encoderAwareness, awarenessProtocol.encodeAwarenessUpdate(this._awareness, changedClients));
2906
+ encoding.writeVarUint8Array(encoderAwareness, YP.encodeAwarenessUpdate(this._awareness, changedClients));
2211
2907
  void this._space.postMessage(this._channel, encoding.toUint8Array(encoderAwareness));
2212
2908
  }
2213
2909
  _handleSpaceMessage({ payload }) {
2214
- log("space message", payload, {
2215
- F: __dxlog_file3,
2216
- L: 79,
2910
+ log4("space message", payload, {
2911
+ F: __dxlog_file5,
2912
+ L: 131,
2217
2913
  S: this,
2218
2914
  C: (f, a) => f(...a)
2219
2915
  });
@@ -2230,12 +2926,12 @@ var SpaceAwarenessProvider = class extends Observable {
2230
2926
  let sendReply = false;
2231
2927
  switch (messageType) {
2232
2928
  case messageAwareness: {
2233
- awarenessProtocol.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2929
+ YP.applyAwarenessUpdate(this._awareness, decoding.readVarUint8Array(decoder), this);
2234
2930
  break;
2235
2931
  }
2236
2932
  case messageQueryAwareness: {
2237
2933
  encoding.writeVarUint(encoder, messageAwareness);
2238
- encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2934
+ encoding.writeVarUint8Array(encoder, YP.encodeAwarenessUpdate(this._awareness, Array.from(this._awareness.getStates().keys())));
2239
2935
  sendReply = true;
2240
2936
  break;
2241
2937
  }
@@ -2250,130 +2946,124 @@ var SpaceAwarenessProvider = class extends Observable {
2250
2946
  return encoder;
2251
2947
  }
2252
2948
  _handleBeforeUnload() {
2253
- awarenessProtocol.removeAwarenessStates(this._awareness, [
2949
+ YP.removeAwarenessStates(this._awareness, [
2254
2950
  this._clientId
2255
2951
  ], "window unload");
2256
2952
  }
2257
2953
  };
2258
2954
 
2259
2955
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
2260
- var __dxlog_file4 = "/home/circleci/project/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
2261
- var useTextModel = ({ identity, space, text }) => {
2262
- const [model, setModel] = useState2(() => createModel({
2263
- identity,
2264
- space,
2265
- text
2266
- }));
2267
- useEffect3(() => {
2268
- setModel(createModel({
2269
- identity,
2270
- space,
2271
- text
2272
- }));
2273
- }, [
2956
+ var modelState = StateField6.define({
2957
+ create: () => void 0,
2958
+ update: (model) => model
2959
+ });
2960
+ var useTextModel = (props) => {
2961
+ const { identity, space, text } = props;
2962
+ const [model, setModel] = useState2(() => createModel(props));
2963
+ useEffect2(() => setModel(createModel(props)), [
2274
2964
  identity,
2275
2965
  space,
2276
2966
  text
2277
2967
  ]);
2278
2968
  return model;
2279
2969
  };
2280
- var createModel = (options) => {
2281
- const { text } = options;
2282
- if (isActualAutomergeObject(text)) {
2283
- return createAutomergeModel(options);
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);
2284
2976
  } else {
2285
- if (!text?.doc) {
2286
- return void 0;
2287
- }
2288
- return createYjsModel(options);
2977
+ return void 0;
2289
2978
  }
2290
2979
  };
2291
- var createYjsModel = ({ identity, space, text }) => {
2292
- invariant3(text?.doc && text?.content, void 0, {
2293
- F: __dxlog_file4,
2294
- L: 81,
2295
- S: void 0,
2296
- A: [
2297
- "text?.doc && text?.content",
2298
- ""
2299
- ]
2300
- });
2301
- const provider = space ? new SpaceAwarenessProvider({
2302
- space,
2303
- doc: text.doc,
2304
- channel: `yjs.awareness.${text.id}`
2305
- }) : void 0;
2306
- return {
2307
- id: text.doc.guid,
2308
- content: text.content,
2309
- text: () => text.content.toString(),
2310
- ranges: [],
2311
- extension: yCollab(text.content, provider?.awareness),
2312
- awareness: provider?.awareness,
2313
- peer: identity ? {
2314
- id: identity.identityKey.toHex(),
2315
- name: identity.profile?.displayName
2316
- } : void 0
2317
- };
2318
- };
2319
- var createAutomergeModel = ({ identity, text }) => {
2980
+
2981
+ // packages/ui/react-ui-editor/src/hooks/automerge.ts
2982
+ var createAutomergeModel = ({ space, identity, text }) => {
2320
2983
  const obj = text;
2321
2984
  const doc = getRawDoc(obj, [
2322
2985
  obj.field
2323
2986
  ]);
2324
- return {
2987
+ const awarenessProvider = space && new SpaceAwarenessProvider({
2988
+ space,
2989
+ channel: `automerge.awareness.${obj.id}`,
2990
+ info: {
2991
+ displayName: identity?.profile?.displayName ?? "",
2992
+ color: cursorColor.color,
2993
+ lightColor: cursorColor.light
2994
+ },
2995
+ peerId: identity?.identityKey.toHex() ?? "Anonymous"
2996
+ });
2997
+ const model = {
2325
2998
  id: obj.id,
2326
2999
  content: doc,
2327
- text: () => get4(doc.handle.docSync(), doc.path),
2328
- ranges: [],
2329
- extension: automergePlugin(doc.handle, doc.path),
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),
2330
3011
  peer: identity ? {
2331
3012
  id: identity.identityKey.toHex(),
2332
3013
  name: identity.profile?.displayName
2333
3014
  } : void 0
2334
3015
  };
3016
+ return model;
2335
3017
  };
2336
3018
  export {
3019
+ AwarenessProvider,
2337
3020
  BaseTextEditor,
2338
3021
  Doc,
2339
3022
  EditorModes,
2340
3023
  MarkdownEditor,
3024
+ RemoteSelectionsDecorator,
2341
3025
  SpaceAwarenessProvider,
2342
3026
  TextEditor,
2343
3027
  TextKind,
3028
+ YAwarenessProvider,
2344
3029
  YText,
2345
3030
  YXmlFragment,
2346
3031
  autocomplete,
3032
+ automerge3 as automerge,
3033
+ awareness,
2347
3034
  basicBundle,
2348
3035
  blast,
2349
3036
  code2 as code,
2350
3037
  codeTheme,
2351
3038
  comments,
2352
- cursorColor,
3039
+ createAutomergeModel,
3040
+ createYjsModel,
2353
3041
  defaultMarkdownSlots,
2354
3042
  defaultOptions,
2355
3043
  defaultSlots,
2356
3044
  defaultTextSlots,
2357
3045
  defaultTheme,
2358
- demo,
2359
- highlight,
2360
- highlightDecorations,
2361
- highlightStateField,
3046
+ hr,
2362
3047
  image,
2363
3048
  link,
2364
- listener2 as listener,
3049
+ listener,
2365
3050
  markdownBundle,
2366
3051
  markdownHighlightStyle,
2367
3052
  markdownTags,
2368
3053
  markdownTagsExtensions,
2369
3054
  markdownTheme,
2370
3055
  mention,
3056
+ mermaid,
3057
+ modelState,
3058
+ setCommentRange,
3059
+ setFocus,
3060
+ setSelection,
2371
3061
  table,
2372
3062
  tags2 as tags,
2373
3063
  tasklist,
2374
3064
  textTheme,
2375
- tooltip,
2376
- useHighlights,
2377
- useTextModel
3065
+ typewriter,
3066
+ useTextModel,
3067
+ yjs
2378
3068
  };
2379
3069
  //# sourceMappingURL=index.mjs.map