@dxos/react-ui-editor 0.3.11-main.1b2c8cb → 0.3.11-main.1b488db

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