@dxos/react-ui-editor 0.3.11-main.53a52d9 → 0.3.11-main.547da9d

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