@dxos/react-ui-editor 0.3.11-main.6f25445 → 0.3.11-main.73ee990

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