@dxos/react-ui-editor 0.3.11-main.7ff9773 → 0.3.11-main.800163e

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