@dxos/react-ui-editor 0.8.2-main.5885341 → 0.8.2-main.600d381

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 (94) hide show
  1. package/dist/lib/browser/index.mjs +1874 -1806
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +1386 -1319
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +1874 -1806
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +1 -1
  11. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  12. package/dist/types/src/{stories/InputMode.stories.d.ts → components/EditorToolbar/EditorToolbar.stories.d.ts} +3 -7
  13. package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts.map +1 -0
  14. package/dist/types/src/components/EditorToolbar/blocks.d.ts +4 -3
  15. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  16. package/dist/types/src/components/EditorToolbar/comment.d.ts +4 -3
  17. package/dist/types/src/components/EditorToolbar/comment.d.ts.map +1 -1
  18. package/dist/types/src/components/EditorToolbar/formatting.d.ts +4 -3
  19. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorToolbar/headings.d.ts +4 -3
  21. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorToolbar/image.d.ts +16 -0
  23. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -0
  24. package/dist/types/src/components/EditorToolbar/lists.d.ts +4 -3
  25. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -1
  26. package/dist/types/src/components/EditorToolbar/search.d.ts +17 -0
  27. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -0
  28. package/dist/types/src/components/EditorToolbar/util.d.ts +11 -17
  29. package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
  30. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +4 -3
  31. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  32. package/dist/types/src/defaults.d.ts.map +1 -1
  33. package/dist/types/src/extensions/annotations.d.ts.map +1 -1
  34. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  35. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  36. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  37. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  38. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  39. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  40. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  41. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  42. package/dist/types/src/extensions/command/command.d.ts.map +1 -1
  43. package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
  44. package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
  45. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  46. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  47. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  48. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  49. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  50. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  51. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  53. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  54. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  55. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  56. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  57. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  58. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  59. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  60. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  61. package/dist/types/src/extensions/modes.d.ts +5 -5
  62. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  63. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  64. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  65. package/dist/types/src/extensions/typewriter.d.ts.map +1 -1
  66. package/dist/types/src/hooks/index.d.ts +0 -1
  67. package/dist/types/src/hooks/index.d.ts.map +1 -1
  68. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  69. package/dist/types/src/stories/story-utils.d.ts.map +1 -1
  70. package/dist/types/src/util/cursor.d.ts.map +1 -1
  71. package/dist/types/src/util/debug.d.ts.map +1 -1
  72. package/dist/types/src/util/dom.d.ts.map +1 -1
  73. package/dist/types/src/util/facet.d.ts.map +1 -1
  74. package/dist/types/src/util/react.d.ts.map +1 -1
  75. package/dist/types/tsconfig.tsbuildinfo +1 -1
  76. package/package.json +30 -28
  77. package/src/components/EditorToolbar/EditorToolbar.stories.tsx +90 -0
  78. package/src/components/EditorToolbar/EditorToolbar.tsx +30 -31
  79. package/src/components/EditorToolbar/blocks.ts +27 -6
  80. package/src/components/EditorToolbar/comment.ts +11 -4
  81. package/src/components/EditorToolbar/formatting.ts +34 -7
  82. package/src/components/EditorToolbar/headings.ts +9 -8
  83. package/src/components/EditorToolbar/image.ts +16 -0
  84. package/src/components/EditorToolbar/lists.ts +26 -7
  85. package/src/components/EditorToolbar/search.ts +19 -0
  86. package/src/components/EditorToolbar/util.ts +14 -14
  87. package/src/components/EditorToolbar/view-mode.ts +9 -8
  88. package/src/extensions/modes.ts +5 -6
  89. package/src/hooks/index.ts +0 -1
  90. package/dist/types/src/hooks/useActionHandler.d.ts +0 -4
  91. package/dist/types/src/hooks/useActionHandler.d.ts.map +0 -1
  92. package/dist/types/src/stories/InputMode.stories.d.ts.map +0 -1
  93. package/src/hooks/useActionHandler.ts +0 -12
  94. package/src/stories/InputMode.stories.tsx +0 -124
@@ -42,7 +42,7 @@ import { tags as tags2 } from "@lezer/highlight";
42
42
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
43
43
 
44
44
  // packages/ui/react-ui-editor/src/components/EditorToolbar/EditorToolbar.tsx
45
- import React, { useCallback } from "react";
45
+ import React3, { memo, useCallback } from "react";
46
46
  import { ElevationProvider } from "@dxos/react-ui";
47
47
  import { MenuProvider, ToolbarMenu, createGapSeparator, useMenuActions } from "@dxos/react-ui-menu";
48
48
  import { textBlockWidth } from "@dxos/react-ui-theme";
@@ -54,1176 +54,594 @@ import { createMenuAction, createMenuItemGroup } from "@dxos/react-ui-menu";
54
54
  var useEditorToolbarState = (initialState = {}) => {
55
55
  return useMemo(() => live(initialState), []);
56
56
  };
57
- var createEditorAction = (payload, icon, label = [
58
- `${payload.type} label`,
59
- {
60
- ns: translationKey
61
- }
62
- ], id = payload.type) => createMenuAction(id, {
63
- icon,
64
- label,
65
- ...payload
66
- });
57
+ var createEditorAction = (id, invoke, properties) => {
58
+ const { label = [
59
+ `${id} label`,
60
+ {
61
+ ns: translationKey
62
+ }
63
+ ], ...rest } = properties;
64
+ return createMenuAction(id, invoke, {
65
+ label,
66
+ ...rest
67
+ });
68
+ };
67
69
  var createEditorActionGroup = (id, props, icon) => createMenuItemGroup(id, {
68
70
  icon,
69
71
  iconOnly: true,
70
72
  ...props
71
73
  });
72
- var editorToolbarSearch = createEditorAction({
73
- type: "search"
74
- }, "ph--magnifying-glass--regular");
75
74
 
76
- // packages/ui/react-ui-editor/src/components/EditorToolbar/blocks.ts
77
- var createBlockGroupAction = (value) => createEditorActionGroup("block", {
78
- variant: "toggleGroup",
79
- selectCardinality: "single",
80
- value
81
- });
82
- var createBlockActions = (value, blankLine) => Object.entries({
83
- blockquote: "ph--quotes--regular",
84
- codeblock: "ph--code-block--regular",
85
- table: "ph--table--regular"
86
- }).map(([type, icon]) => {
87
- return createEditorAction({
88
- type,
89
- checked: type === value,
90
- ...type === "table" && {
91
- disabled: !!blankLine
92
- }
93
- }, icon);
94
- });
95
- var createBlocks = (state) => {
96
- const value = state?.blockQuote ? "blockquote" : state.blockType ?? "";
97
- const blockGroupAction = createBlockGroupAction(value);
98
- const blockActions = createBlockActions(value, state.blankLine);
99
- return {
100
- nodes: [
101
- blockGroupAction,
102
- ...blockActions
103
- ],
104
- edges: [
105
- {
106
- source: "root",
107
- target: "block"
108
- },
109
- ...blockActions.map(({ id }) => ({
110
- source: blockGroupAction.id,
111
- target: id
112
- }))
113
- ]
114
- };
115
- };
75
+ // packages/ui/react-ui-editor/src/extensions/annotations.ts
76
+ import { StateField } from "@codemirror/state";
77
+ import { Decoration, EditorView } from "@codemirror/view";
78
+ import { isNotFalsy } from "@dxos/util";
116
79
 
117
- // packages/ui/react-ui-editor/src/components/EditorToolbar/comment.ts
118
- var commentLabel = (comment, selection) => comment ? "selection overlaps existing comment label" : selection === false ? "select text to comment label" : "comment label";
119
- var createCommentAction = (label) => createEditorAction({
120
- type: "comment",
121
- testId: "editor.toolbar.comment"
122
- }, "ph--chat-text--regular", label);
123
- var createComment = (state) => ({
124
- nodes: [
125
- createCommentAction([
126
- commentLabel(state.comment, state.selection),
127
- {
128
- ns: translationKey
129
- }
130
- ])
131
- ],
132
- edges: [
133
- {
134
- source: "root",
135
- target: "comment"
136
- }
137
- ]
80
+ // packages/ui/react-ui-editor/src/util/facet.ts
81
+ import { Facet } from "@codemirror/state";
82
+ var singleValueFacet = (defaultValue) => Facet.define({
83
+ // Called immediately.
84
+ combine: (providers) => {
85
+ return providers[0] ?? defaultValue;
86
+ }
138
87
  });
139
88
 
140
- // packages/ui/react-ui-editor/src/components/EditorToolbar/formatting.ts
141
- var formats = {
142
- strong: "ph--text-b--regular",
143
- emphasis: "ph--text-italic--regular",
144
- strikethrough: "ph--text-strikethrough--regular",
145
- code: "ph--code--regular",
146
- link: "ph--link--regular"
89
+ // packages/ui/react-ui-editor/src/util/cursor.ts
90
+ var overlap = (a, b) => a.from <= b.to && a.to >= b.from;
91
+ var defaultCursorConverter = {
92
+ toCursor: (position) => position.toString(),
93
+ fromCursor: (cursor) => parseInt(cursor)
147
94
  };
148
- var createFormattingGroup = (formatting) => createEditorActionGroup("formatting", {
149
- variant: "toggleGroup",
150
- selectCardinality: "multiple",
151
- value: Object.keys(formats).filter((key) => !!formatting[key])
152
- });
153
- var createFormattingActions = (formatting) => Object.entries(formats).map(([type, icon]) => createEditorAction({
154
- type,
155
- checked: !!formatting[type]
156
- }, icon));
157
- var createFormatting = (state) => {
158
- const formattingGroupAction = createFormattingGroup(state);
159
- const formattingActions = createFormattingActions(state);
160
- return {
161
- nodes: [
162
- formattingGroupAction,
163
- ...formattingActions
164
- ],
165
- edges: [
166
- {
167
- source: "root",
168
- target: "formatting"
169
- },
170
- ...formattingActions.map(({ id }) => ({
171
- source: formattingGroupAction.id,
172
- target: id
173
- }))
174
- ]
175
- };
95
+ var Cursor = class _Cursor {
96
+ static {
97
+ this.converter = singleValueFacet(defaultCursorConverter);
98
+ }
99
+ static {
100
+ this.getCursorFromRange = (state, range) => {
101
+ const cursorConverter2 = state.facet(_Cursor.converter);
102
+ const from = cursorConverter2.toCursor(range.from);
103
+ const to = cursorConverter2.toCursor(range.to, -1);
104
+ return [
105
+ from,
106
+ to
107
+ ].join(":");
108
+ };
109
+ }
110
+ static {
111
+ this.getRangeFromCursor = (state, cursor) => {
112
+ const cursorConverter2 = state.facet(_Cursor.converter);
113
+ const parts = cursor.split(":");
114
+ const from = cursorConverter2.fromCursor(parts[0]);
115
+ const to = cursorConverter2.fromCursor(parts[1]);
116
+ return from !== void 0 && to !== void 0 ? {
117
+ from,
118
+ to
119
+ } : void 0;
120
+ };
121
+ }
176
122
  };
177
123
 
178
- // packages/ui/react-ui-editor/src/components/EditorToolbar/headings.ts
179
- var createHeadingGroupAction = (value) => createEditorActionGroup("heading", {
180
- variant: "dropdownMenu",
181
- applyActive: true,
182
- selectCardinality: "single",
183
- value
184
- }, "ph--text-h--regular");
185
- var createHeadingActions = (value) => Object.entries({
186
- "0": "ph--paragraph--regular",
187
- "1": "ph--text-h-one--regular",
188
- "2": "ph--text-h-two--regular",
189
- "3": "ph--text-h-three--regular",
190
- "4": "ph--text-h-four--regular",
191
- "5": "ph--text-h-five--regular",
192
- "6": "ph--text-h-six--regular"
193
- }).map(([levelStr, icon]) => {
194
- const level = parseInt(levelStr);
195
- return createEditorAction({
196
- type: "heading",
197
- data: level,
198
- checked: value === levelStr
199
- }, icon, [
200
- "heading level label",
201
- {
202
- count: level,
203
- ns: translationKey
124
+ // packages/ui/react-ui-editor/src/util/debug.ts
125
+ import { log } from "@dxos/log";
126
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/debug.ts";
127
+ var wrapWithCatch = (fn) => {
128
+ return (...args) => {
129
+ try {
130
+ return fn(...args);
131
+ } catch (err) {
132
+ log.catch(err, void 0, {
133
+ F: __dxlog_file,
134
+ L: 15,
135
+ S: void 0,
136
+ C: (f, a) => f(...a)
137
+ });
204
138
  }
205
- ], `heading--${levelStr}`);
206
- });
207
- var computeHeadingValue = (state) => {
208
- const blockType = state ? state.blockType : "paragraph";
209
- const header = blockType && /heading(\d)/.exec(blockType);
210
- return header ? header[1] : blockType === "paragraph" || !blockType ? "0" : "";
211
- };
212
- var createHeadings = (state) => {
213
- const headingValue = computeHeadingValue(state);
214
- const headingGroupAction = createHeadingGroupAction(headingValue);
215
- const headingActions = createHeadingActions(headingValue);
216
- return {
217
- nodes: [
218
- headingGroupAction,
219
- ...headingActions
220
- ],
221
- edges: [
222
- {
223
- source: "root",
224
- target: "heading"
225
- },
226
- ...headingActions.map(({ id }) => ({
227
- source: headingGroupAction.id,
228
- target: id
229
- }))
230
- ]
231
139
  };
232
140
  };
233
-
234
- // packages/ui/react-ui-editor/src/components/EditorToolbar/lists.ts
235
- var listStyles = {
236
- bullet: "ph--list-bullets--regular",
237
- ordered: "ph--list-numbers--regular",
238
- task: "ph--list-checks--regular"
141
+ var callbackWrapper = (fn) => (...args) => {
142
+ try {
143
+ return fn(...args);
144
+ } catch (err) {
145
+ log.catch(err, void 0, {
146
+ F: __dxlog_file,
147
+ L: 29,
148
+ S: void 0,
149
+ C: (f, a) => f(...a)
150
+ });
151
+ }
239
152
  };
240
- var createListGroupAction = (value) => createEditorActionGroup("list", {
241
- variant: "toggleGroup",
242
- selectCardinality: "single",
243
- value
244
- });
245
- var createListActions = (value) => Object.entries(listStyles).map(([listStyle, icon]) => createEditorAction({
246
- type: `list-${listStyle}`,
247
- checked: value === listStyle
248
- }, icon));
249
- var createLists = (state) => {
250
- const value = state.listStyle ?? "";
251
- const listGroupAction = createListGroupAction(value);
252
- const listActionsMap = createListActions(value);
253
- return {
254
- nodes: [
255
- listGroupAction,
256
- ...listActionsMap
257
- ],
258
- edges: [
259
- {
260
- source: "root",
261
- target: "list"
262
- },
263
- ...listActionsMap.map(({ id }) => ({
264
- source: listGroupAction.id,
265
- target: id
266
- }))
267
- ]
268
- };
153
+ var debugDispatcher = (trs, view) => {
154
+ logChanges(trs);
155
+ view.update(trs);
269
156
  };
270
-
271
- // packages/ui/react-ui-editor/src/components/EditorToolbar/view-mode.ts
272
- var createViewModeGroupAction = (value) => createEditorActionGroup("viewMode", {
273
- variant: "dropdownMenu",
274
- applyActive: true,
275
- selectCardinality: "single",
276
- value
277
- }, "ph--eye--regular");
278
- var createViewModeActions = (value) => Object.entries({
279
- preview: "ph--eye--regular",
280
- source: "ph--pencil-simple--regular",
281
- readonly: "ph--pencil-slash--regular"
282
- }).map(([viewMode, icon]) => {
283
- return createEditorAction({
284
- type: "view-mode",
285
- data: viewMode,
286
- checked: viewMode === value
287
- }, icon, [
288
- `${viewMode} mode label`,
289
- {
290
- ns: translationKey
157
+ var logChanges = (trs) => {
158
+ const changes = trs.flatMap((tr) => {
159
+ if (tr.changes.empty) {
160
+ return void 0;
291
161
  }
292
- ], `view-mode--${viewMode}`);
293
- });
294
- var createViewMode = (state) => {
295
- const value = state.viewMode ?? "source";
296
- const viewModeGroupAction = createViewModeGroupAction(value);
297
- const viewModeActions = createViewModeActions(value);
298
- return {
299
- nodes: [
300
- viewModeGroupAction,
301
- ...viewModeActions
302
- ],
303
- edges: [
304
- {
305
- source: "root",
306
- target: "viewMode"
307
- },
308
- ...viewModeActions.map(({ id }) => ({
309
- source: viewModeGroupAction.id,
310
- target: id
311
- }))
312
- ]
162
+ const changes2 = [];
163
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
164
+ fromA,
165
+ toA,
166
+ fromB,
167
+ toB,
168
+ inserted: inserted.toString()
169
+ })));
170
+ return changes2;
171
+ }).filter(Boolean);
172
+ if (changes.length) {
173
+ log("changes", {
174
+ changes
175
+ }, {
176
+ F: __dxlog_file,
177
+ L: 62,
178
+ S: void 0,
179
+ C: (f, a) => f(...a)
180
+ });
181
+ }
182
+ };
183
+
184
+ // packages/ui/react-ui-editor/src/util/dom.ts
185
+ var flattenRect = (rect, left) => {
186
+ const x = left ? rect.left : rect.right;
187
+ return {
188
+ left: x,
189
+ right: x,
190
+ top: rect.top,
191
+ bottom: rect.bottom
313
192
  };
314
193
  };
194
+ var scratchRange;
195
+ var textRange = (node, from, to = from) => {
196
+ const range = scratchRange || (scratchRange = document.createRange());
197
+ range.setEnd(node, to);
198
+ range.setStart(node, from);
199
+ return range;
200
+ };
201
+ var clientRectsFor = (dom) => {
202
+ if (dom.nodeType === 3) {
203
+ return textRange(dom, 0, dom.nodeValue.length).getClientRects();
204
+ } else if (dom.nodeType === 1) {
205
+ return dom.getClientRects();
206
+ } else {
207
+ return [];
208
+ }
209
+ };
315
210
 
316
- // packages/ui/react-ui-editor/src/defaults.ts
317
- import { EditorView } from "@codemirror/view";
318
- import { mx as mx2 } from "@dxos/react-ui-theme";
211
+ // packages/ui/react-ui-editor/src/util/react.tsx
212
+ import React from "react";
213
+ import { createRoot } from "react-dom/client";
214
+ import { ThemeProvider, Tooltip } from "@dxos/react-ui";
215
+ import { defaultTx } from "@dxos/react-ui-theme";
216
+ var createElement = (tag, options, children) => {
217
+ const el = document.createElement(tag);
218
+ if (options?.className) {
219
+ el.className = options.className;
220
+ }
221
+ if (children) {
222
+ el.append(...Array.isArray(children) ? children : [
223
+ children
224
+ ]);
225
+ }
226
+ return el;
227
+ };
228
+ var renderRoot = (root, node) => {
229
+ createRoot(root).render(/* @__PURE__ */ React.createElement(ThemeProvider, {
230
+ tx: defaultTx
231
+ }, node));
232
+ return root;
233
+ };
234
+ var createRenderer = (Component) => (el, props) => {
235
+ renderRoot(el, /* @__PURE__ */ React.createElement(ThemeProvider, {
236
+ tx: defaultTx
237
+ }, /* @__PURE__ */ React.createElement(Tooltip.Provider, null, /* @__PURE__ */ React.createElement(Component, props))));
238
+ };
319
239
 
320
- // packages/ui/react-ui-editor/src/styles/markdown.ts
321
- import { mx } from "@dxos/react-ui-theme";
322
- var headings = {
323
- 1: "text-4xl",
324
- 2: "text-3xl",
325
- 3: "text-2xl",
326
- 4: "text-xl",
327
- 5: "text-lg",
328
- 6: ""
240
+ // packages/ui/react-ui-editor/src/extensions/annotations.ts
241
+ var annotationMark = Decoration.mark({
242
+ class: "cm-annotation"
243
+ });
244
+ var annotations = (options = {}) => {
245
+ const match = (state) => {
246
+ const annotations2 = [];
247
+ const text = state.doc.toString();
248
+ if (options.match) {
249
+ const matches = text.matchAll(options.match);
250
+ for (const match2 of matches) {
251
+ const from = match2.index;
252
+ const to = from + match2[0].length;
253
+ const cursor = Cursor.getCursorFromRange(state, {
254
+ from,
255
+ to
256
+ });
257
+ annotations2.push({
258
+ cursor
259
+ });
260
+ }
261
+ }
262
+ return annotations2;
263
+ };
264
+ const annotationsState = StateField.define({
265
+ create: (state) => {
266
+ return match(state);
267
+ },
268
+ update: (value, tr) => {
269
+ if (!tr.changes.empty) {
270
+ return match(tr.state);
271
+ }
272
+ return value;
273
+ }
274
+ });
275
+ return [
276
+ annotationsState,
277
+ EditorView.decorations.compute([
278
+ annotationsState
279
+ ], (state) => {
280
+ const annotations2 = state.field(annotationsState);
281
+ const decorations = annotations2.map((annotation) => {
282
+ const range = Cursor.getRangeFromCursor(state, annotation.cursor);
283
+ return range && annotationMark.range(range.from, range.to);
284
+ }).filter(isNotFalsy);
285
+ return Decoration.set(decorations);
286
+ }),
287
+ styles
288
+ ];
329
289
  };
330
- var theme = {
331
- code: "font-mono !no-underline text-neutral-700 dark:text-neutral-300",
332
- codeMark: "font-mono text-primary-500",
333
- mark: "opacity-50",
334
- heading: (level) => {
335
- return mx(headings[level], "dark:text-primary-400");
290
+ var styles = EditorView.theme({
291
+ ".cm-annotation": {
292
+ textDecoration: "underline",
293
+ textDecorationStyle: "wavy",
294
+ textDecorationColor: "var(--dx-error)"
336
295
  }
337
- };
296
+ });
338
297
 
339
- // packages/ui/react-ui-editor/src/styles/tokens.ts
340
- import get from "lodash.get";
341
- import { tokens } from "@dxos/react-ui-theme";
342
- var getToken = (path, defaultValue) => {
343
- const value = get(tokens, path, defaultValue);
344
- return value?.toString() ?? "";
298
+ // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
299
+ import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
300
+ import { markdownLanguage } from "@codemirror/lang-markdown";
301
+ import { keymap } from "@codemirror/view";
302
+ var autocomplete = ({ debug, activateOnTyping, override, onSearch } = {}) => {
303
+ const extensions = [
304
+ // https://codemirror.net/docs/ref/#view.keymap
305
+ // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
306
+ // TODO(burdon): Set custom keymap.
307
+ keymap.of(completionKeymap),
308
+ // https://codemirror.net/examples/autocompletion
309
+ // https://codemirror.net/docs/ref/#autocomplete.autocompletion
310
+ autocompletion({
311
+ activateOnTyping,
312
+ override,
313
+ closeOnBlur: !debug,
314
+ tooltipClass: () => "shadow rounded"
315
+ })
316
+ ];
317
+ if (onSearch) {
318
+ extensions.push(
319
+ // TODO(burdon): Optional decoration via addToOptions
320
+ markdownLanguage.data.of({
321
+ autocomplete: (context) => {
322
+ const match = context.matchBefore(/\w*/);
323
+ if (!match || match.from === match.to && !context.explicit) {
324
+ return null;
325
+ }
326
+ return {
327
+ from: match.from,
328
+ options: onSearch(match.text.toLowerCase())
329
+ };
330
+ }
331
+ })
332
+ );
333
+ }
334
+ return extensions;
345
335
  };
346
- var fontBody = getToken("fontFamily.body");
347
- var fontMono = getToken("fontFamily.mono");
348
336
 
349
- // packages/ui/react-ui-editor/src/styles/theme.ts
350
- var defaultTheme = {
351
- "&": {},
352
- "&.cm-focused": {
353
- outline: "none"
354
- },
355
- /**
356
- * Scroller
357
- */
358
- ".cm-scroller": {
359
- overflowY: "auto"
360
- },
361
- /**
362
- * Content
363
- * NOTE: Apply margins to content so that scrollbar is at the edge of the container.
364
- */
365
- ".cm-content": {
366
- padding: "unset",
367
- fontFamily: fontBody,
368
- // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
369
- fontSize: "16px",
370
- lineHeight: 1.5,
371
- color: "unset"
372
- },
373
- /**
374
- * Gutters
375
- * NOTE: Gutters should have the same top margin as the content.
376
- */
377
- ".cm-gutters": {
378
- borderRight: "none",
379
- background: "transparent"
380
- },
381
- ".cm-gutter": {},
382
- ".cm-gutter.cm-lineNumbers": {
383
- paddingRight: "4px",
384
- borderRight: "1px solid var(--dx-separator)"
385
- },
386
- ".cm-gutter.cm-lineNumbers .cm-gutterElement": {
387
- minWidth: "40px",
388
- alignContent: "center"
337
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
338
+ import { StateField as StateField2 } from "@codemirror/state";
339
+ import { EditorView as EditorView2, ViewPlugin } from "@codemirror/view";
340
+ import { next as A3 } from "@dxos/automerge/automerge";
341
+
342
+ // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
343
+ import { log as log2 } from "@dxos/log";
344
+ import { fromCursor, toCursor } from "@dxos/react-client/echo";
345
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts";
346
+ var cursorConverter = (accessor) => ({
347
+ toCursor: (pos, assoc) => {
348
+ try {
349
+ return toCursor(accessor, pos, assoc);
350
+ } catch (err) {
351
+ log2.catch(err, void 0, {
352
+ F: __dxlog_file2,
353
+ L: 15,
354
+ S: void 0,
355
+ C: (f, a) => f(...a)
356
+ });
357
+ return "";
358
+ }
389
359
  },
390
- /**
391
- * Height is set to match the corresponding line.
392
- */
393
- ".cm-gutterElement": {
394
- alignItems: "center",
395
- fontSize: "12px"
396
- },
397
- /**
398
- * Line.
399
- */
400
- ".cm-line": {
401
- paddingInline: 0
402
- },
403
- ".cm-activeLine": {
404
- background: "var(--dx-cmActiveLine)"
405
- },
406
- /**
407
- * Cursor (layer).
408
- */
409
- ".cm-cursor, .cm-dropCursor": {
410
- borderLeft: "2px solid var(--dx-cmCursor)"
411
- },
412
- ".cm-placeholder": {
413
- color: "var(--dx-subdued)"
414
- },
415
- /**
416
- * Selection (layer).
417
- */
418
- ".cm-selectionBackground": {
419
- background: "var(--dx-cmSelection)"
420
- },
421
- /**
422
- * Search.
423
- * NOTE: Matches comment.
424
- */
425
- ".cm-searchMatch": {
426
- margin: "0 -3px",
427
- padding: "3px",
428
- borderRadius: "3px",
429
- background: "var(--dx-cmHighlightSurface)",
430
- color: "var(--dx-cmHighlight)"
431
- },
432
- ".cm-searchMatch-selected": {
433
- textDecoration: "underline"
434
- },
435
- /**
436
- * Link.
437
- */
438
- ".cm-link": {
439
- textDecorationLine: "underline",
440
- textDecorationThickness: "1px",
441
- textDecorationColor: "var(--dx-separator)",
442
- textUnderlineOffset: "2px",
443
- borderRadius: ".125rem"
444
- },
445
- ".cm-link > span": {
446
- color: "var(--dx-accentText)"
447
- },
448
- /**
449
- * Tooltip.
450
- */
451
- ".cm-tooltip": {
452
- background: "var(--dx-baseSurface)"
453
- },
454
- ".cm-tooltip-below": {},
455
- /**
456
- * Autocomplete.
457
- * https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
458
- */
459
- ".cm-tooltip.cm-tooltip-autocomplete": {
460
- marginTop: "4px",
461
- marginLeft: "-3px"
462
- },
463
- ".cm-tooltip.cm-tooltip-autocomplete > ul": {
464
- maxHeight: "20em"
465
- },
466
- ".cm-tooltip.cm-tooltip-autocomplete > ul > li": {},
467
- ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {},
468
- ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
469
- paddingLeft: "4px !important",
470
- borderBottom: "none !important",
471
- color: "var(--dx-accentText)"
472
- },
473
- ".cm-tooltip.cm-completionInfo": {
474
- width: "360px !important",
475
- margin: "-10px 1px 0 1px",
476
- padding: "8px !important",
477
- borderColor: "var(--dx-separator)"
478
- },
479
- ".cm-completionIcon": {
480
- display: "none"
481
- },
482
- ".cm-completionLabel": {
483
- fontFamily: fontBody
484
- },
485
- ".cm-completionMatchedText": {
486
- textDecoration: "none !important",
487
- opacity: 0.5
488
- },
489
- /**
490
- * Panels
491
- * https://github.com/codemirror/search/blob/main/src/search.ts#L745
492
- *
493
- * Find/replace panel.
494
- * <div class="cm-announced">...</div>
495
- * <div class="cm-scroller">...</div>
496
- * <div class="cm-panels cm-panels-bottom">
497
- * <div class="cm-search cm-panel">
498
- * <input class="cm-textfield" />
499
- * <button class="cm-button">...</button>
500
- * <label><input type="checkbox" />...</label>
501
- * </div>
502
- * </div
503
- */
504
- // TODO(burdon): Implement custom panel (with icon buttons).
505
- ".cm-panels": {},
506
- ".cm-panel": {
507
- fontFamily: fontBody,
508
- backgroundColor: "var(--surface-bg)"
509
- },
510
- ".cm-panel input, .cm-panel button, .cm-panel label": {
511
- color: "var(--dx-subdued)",
512
- fontFamily: fontBody,
513
- fontSize: "14px",
514
- all: "unset",
515
- margin: "3px !important",
516
- padding: "2px 6px !important",
517
- outline: "1px solid transparent"
518
- },
519
- ".cm-panel input, .cm-panel button": {
520
- backgroundColor: "var(--dx-input)"
521
- },
522
- ".cm-panel input:focus, .cm-panel button:focus": {
523
- outline: "1px solid var(--dx-accentFocusIndicator)"
524
- },
525
- ".cm-panel label": {
526
- display: "inline-flex",
527
- alignItems: "center",
528
- cursor: "pointer"
529
- },
530
- ".cm-panel input.cm-textfield": {},
531
- ".cm-panel input[type=checkbox]": {
532
- width: "8px",
533
- height: "8px",
534
- marginRight: "6px !important",
535
- padding: "2px !important",
536
- color: "var(--dx-accentFocusIndicator)"
537
- },
538
- ".cm-panel button": {
539
- "&:hover": {
540
- backgroundColor: "var(--dx-accentSurfaceHover) !important"
541
- },
542
- "&:active": {
543
- backgroundColor: "var(--dx-accentSurfaceHover)"
360
+ fromCursor: (cursor) => {
361
+ try {
362
+ return fromCursor(accessor, cursor);
363
+ } catch (err) {
364
+ log2.catch(err, void 0, {
365
+ F: __dxlog_file2,
366
+ L: 24,
367
+ S: void 0,
368
+ C: (f, a) => f(...a)
369
+ });
370
+ return 0;
544
371
  }
545
- },
546
- ".cm-panel.cm-search": {
547
- padding: "4px",
548
- borderTop: "1px solid var(--dx-separator)"
549
- }
550
- };
551
-
552
- // packages/ui/react-ui-editor/src/defaults.ts
553
- var margin = "!mt-[1rem]";
554
- var editorWidth = "!mli-auto is-full max-is-[min(50rem,100%-4rem)]";
555
- var editorContent = mx2(margin, editorWidth);
556
- var editorFullWidth = mx2(margin);
557
- var editorGutter = EditorView.theme({
558
- // Match margin from content.
559
- // Gutter = 2rem + 1rem margin.
560
- ".cm-gutters": {
561
- marginTop: "1rem",
562
- paddingRight: "1rem"
563
372
  }
564
373
  });
565
- var editorMonospace = EditorView.theme({
566
- ".cm-content": {
567
- fontFamily: fontMono
568
- }
374
+
375
+ // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
376
+ import { Annotation, StateEffect } from "@codemirror/state";
377
+ var getPath = (state, field) => state.field(field).path;
378
+ var getLastHeads = (state, field) => state.field(field).lastHeads;
379
+ var updateHeadsEffect = StateEffect.define({});
380
+ var updateHeads = (newHeads) => updateHeadsEffect.of({
381
+ newHeads
569
382
  });
570
- var editorWithToolbarLayout = "grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden";
571
- var stackItemContentEditorClassNames = (role) => mx2("attention-surface dx-focus-ring-inset data-[toolbar=disabled]:pbs-2", role === "section" ? "[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-bs-24" : "min-bs-0");
572
- var stackItemContentToolbarClassNames = (role) => mx2("attention-surface is-full border-be !border-separator relative z-[1]", role === "section" && "sticky block-start-0 -mbe-px min-is-0");
383
+ var reconcileAnnotation = Annotation.define();
384
+ var isReconcile = (tr) => {
385
+ return !!tr.annotation(reconcileAnnotation);
386
+ };
573
387
 
574
- // packages/ui/react-ui-editor/src/components/EditorToolbar/EditorToolbar.tsx
575
- var createToolbar = ({ state, customActions, ...features }) => {
576
- const nodes = [];
577
- const edges = [];
578
- if (features.headings ?? true) {
579
- const headings2 = createHeadings(state);
580
- nodes.push(...headings2.nodes);
581
- edges.push(...headings2.edges);
582
- }
583
- if (features.formatting ?? true) {
584
- const formatting = createFormatting(state);
585
- nodes.push(...formatting.nodes);
586
- edges.push(...formatting.edges);
587
- }
588
- if (features.lists ?? true) {
589
- const lists = createLists(state);
590
- nodes.push(...lists.nodes);
591
- edges.push(...lists.edges);
592
- }
593
- if (features.blocks ?? true) {
594
- const blocks = createBlocks(state);
595
- nodes.push(...blocks.nodes);
596
- edges.push(...blocks.edges);
597
- }
598
- if (customActions) {
599
- const custom = customActions();
600
- nodes.push(...custom.nodes);
601
- edges.push(...custom.edges);
602
- }
603
- const editorToolbarGap = createGapSeparator();
604
- nodes.push(...editorToolbarGap.nodes);
605
- edges.push(...editorToolbarGap.edges);
606
- if (features.comment ?? true) {
607
- const comment = createComment(state);
608
- nodes.push(...comment.nodes);
609
- edges.push(...comment.edges);
610
- }
611
- if (features.search ?? true) {
612
- nodes.push(editorToolbarSearch);
613
- edges.push({
614
- source: "root",
615
- target: editorToolbarSearch.id
388
+ // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
389
+ import { next as A2 } from "@dxos/automerge/automerge";
390
+
391
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
392
+ import { next as A } from "@dxos/automerge/automerge";
393
+ var updateAutomerge = (field, handle, transactions, state) => {
394
+ const { lastHeads, path } = state.field(field);
395
+ let hasChanges = false;
396
+ for (const tr of transactions) {
397
+ tr.changes.iterChanges(() => {
398
+ hasChanges = true;
616
399
  });
617
400
  }
618
- if (features.viewMode ?? true) {
619
- const viewMode = createViewMode(state);
620
- nodes.push(...viewMode.nodes);
621
- edges.push(...viewMode.edges);
401
+ if (!hasChanges) {
402
+ return void 0;
622
403
  }
623
- return {
624
- nodes,
625
- edges
626
- };
627
- };
628
- var useEditorToolbarActionGraph = ({ onAction, ...props }) => {
629
- const menuCreator = useCallback(() => createToolbar(props), [
630
- props
631
- ]);
632
- const { resolveGroupItems } = useMenuActions(menuCreator);
633
- return {
634
- resolveGroupItems,
635
- onAction
636
- };
637
- };
638
- var EditorToolbar = ({ classNames, attendableId, role, ...props }) => {
639
- const menuProps = useEditorToolbarActionGraph(props);
640
- return /* @__PURE__ */ React.createElement("div", {
641
- role: "none",
642
- className: stackItemContentToolbarClassNames(role)
643
- }, /* @__PURE__ */ React.createElement(ElevationProvider, {
644
- elevation: role === "section" ? "positioned" : "base"
645
- }, /* @__PURE__ */ React.createElement(MenuProvider, {
646
- ...menuProps,
647
- attendableId
648
- }, /* @__PURE__ */ React.createElement(ToolbarMenu, {
649
- classNames: [
650
- textBlockWidth,
651
- "!bg-transparent",
652
- classNames
653
- ]
654
- }))));
404
+ const newHeads = handle.changeAt(lastHeads, (doc) => {
405
+ const invertedTransactions = [];
406
+ for (const tr of transactions) {
407
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
408
+ invertedTransactions.push({
409
+ from: fromA,
410
+ del: toA - fromA,
411
+ insert
412
+ });
413
+ });
414
+ }
415
+ invertedTransactions.reverse().forEach(({ from, del, insert }) => {
416
+ A.splice(doc, path.slice(), from, del, insert.toString());
417
+ });
418
+ });
419
+ return newHeads ?? void 0;
655
420
  };
656
421
 
657
- // packages/ui/react-ui-editor/src/extensions/annotations.ts
658
- import { StateField } from "@codemirror/state";
659
- import { Decoration, EditorView as EditorView2 } from "@codemirror/view";
660
- import { isNotFalsy } from "@dxos/util";
661
-
662
- // packages/ui/react-ui-editor/src/util/facet.ts
663
- import { Facet } from "@codemirror/state";
664
- var singleValueFacet = (defaultValue) => Facet.define({
665
- // Called immediately.
666
- combine: (providers) => {
667
- return providers[0] ?? defaultValue;
422
+ // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
423
+ import { ChangeSet } from "@codemirror/state";
424
+ var updateCodeMirror = (view, selection, target, patches) => {
425
+ for (const patch of patches) {
426
+ const changeSpec = handlePatch(patch, target, view.state);
427
+ if (changeSpec != null) {
428
+ const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
429
+ selection = selection.map(changeSet, 1);
430
+ view.dispatch({
431
+ changes: changeSet,
432
+ annotations: reconcileAnnotation.of(false)
433
+ });
434
+ }
668
435
  }
669
- });
670
-
671
- // packages/ui/react-ui-editor/src/util/cursor.ts
672
- var overlap = (a, b) => a.from <= b.to && a.to >= b.from;
673
- var defaultCursorConverter = {
674
- toCursor: (position) => position.toString(),
675
- fromCursor: (cursor) => parseInt(cursor)
436
+ view.dispatch({
437
+ selection,
438
+ annotations: reconcileAnnotation.of(false)
439
+ });
676
440
  };
677
- var Cursor = class _Cursor {
678
- static {
679
- this.converter = singleValueFacet(defaultCursorConverter);
680
- }
681
- static {
682
- this.getCursorFromRange = (state, range) => {
683
- const cursorConverter2 = state.facet(_Cursor.converter);
684
- const from = cursorConverter2.toCursor(range.from);
685
- const to = cursorConverter2.toCursor(range.to, -1);
686
- return [
687
- from,
688
- to
689
- ].join(":");
690
- };
691
- }
692
- static {
693
- this.getRangeFromCursor = (state, cursor) => {
694
- const cursorConverter2 = state.facet(_Cursor.converter);
695
- const parts = cursor.split(":");
696
- const from = cursorConverter2.fromCursor(parts[0]);
697
- const to = cursorConverter2.fromCursor(parts[1]);
698
- return from !== void 0 && to !== void 0 ? {
699
- from,
700
- to
701
- } : void 0;
702
- };
441
+ var handlePatch = (patch, target, state) => {
442
+ if (patch.action === "insert") {
443
+ return handleInsert(target, patch);
444
+ } else if (patch.action === "splice") {
445
+ return handleSplice(target, patch);
446
+ } else if (patch.action === "del") {
447
+ return handleDel(target, patch);
448
+ } else if (patch.action === "put") {
449
+ return handlePut(target, patch, state);
450
+ } else {
451
+ return null;
703
452
  }
704
453
  };
705
-
706
- // packages/ui/react-ui-editor/src/util/debug.ts
707
- import { log } from "@dxos/log";
708
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/debug.ts";
709
- var wrapWithCatch = (fn) => {
710
- return (...args) => {
711
- try {
712
- return fn(...args);
713
- } catch (err) {
714
- log.catch(err, void 0, {
715
- F: __dxlog_file,
716
- L: 15,
717
- S: void 0,
718
- C: (f, a) => f(...a)
719
- });
454
+ var handleInsert = (target, patch) => {
455
+ const index = charPath(target, patch.path);
456
+ if (index == null) {
457
+ return [];
458
+ }
459
+ const text = patch.values.map((value) => value ? value.toString() : "").join("");
460
+ return [
461
+ {
462
+ from: index,
463
+ to: index,
464
+ insert: text
720
465
  }
721
- };
466
+ ];
722
467
  };
723
- var callbackWrapper = (fn) => (...args) => {
724
- try {
725
- return fn(...args);
726
- } catch (err) {
727
- log.catch(err, void 0, {
728
- F: __dxlog_file,
729
- L: 29,
730
- S: void 0,
731
- C: (f, a) => f(...a)
732
- });
468
+ var handleSplice = (target, patch) => {
469
+ const index = charPath(target, patch.path);
470
+ if (index == null) {
471
+ return [];
733
472
  }
734
- };
735
- var debugDispatcher = (trs, view) => {
736
- logChanges(trs);
737
- view.update(trs);
738
- };
739
- var logChanges = (trs) => {
740
- const changes = trs.flatMap((tr) => {
741
- if (tr.changes.empty) {
742
- return void 0;
473
+ return [
474
+ {
475
+ from: index,
476
+ insert: patch.value
743
477
  }
744
- const changes2 = [];
745
- tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => changes2.push(JSON.stringify({
746
- fromA,
747
- toA,
748
- fromB,
749
- toB,
750
- inserted: inserted.toString()
751
- })));
752
- return changes2;
753
- }).filter(Boolean);
754
- if (changes.length) {
755
- log("changes", {
756
- changes
757
- }, {
758
- F: __dxlog_file,
759
- L: 62,
760
- S: void 0,
761
- C: (f, a) => f(...a)
762
- });
763
- }
764
- };
765
-
766
- // packages/ui/react-ui-editor/src/util/dom.ts
767
- var flattenRect = (rect, left) => {
768
- const x = left ? rect.left : rect.right;
769
- return {
770
- left: x,
771
- right: x,
772
- top: rect.top,
773
- bottom: rect.bottom
774
- };
775
- };
776
- var scratchRange;
777
- var textRange = (node, from, to = from) => {
778
- const range = scratchRange || (scratchRange = document.createRange());
779
- range.setEnd(node, to);
780
- range.setStart(node, from);
781
- return range;
478
+ ];
782
479
  };
783
- var clientRectsFor = (dom) => {
784
- if (dom.nodeType === 3) {
785
- return textRange(dom, 0, dom.nodeValue.length).getClientRects();
786
- } else if (dom.nodeType === 1) {
787
- return dom.getClientRects();
788
- } else {
480
+ var handleDel = (target, patch) => {
481
+ const index = charPath(target, patch.path);
482
+ if (index == null) {
789
483
  return [];
790
484
  }
485
+ const length = patch.length || 1;
486
+ return [
487
+ {
488
+ from: index,
489
+ to: index + length
490
+ }
491
+ ];
791
492
  };
792
-
793
- // packages/ui/react-ui-editor/src/util/react.tsx
794
- import React2 from "react";
795
- import { createRoot } from "react-dom/client";
796
- import { ThemeProvider, Tooltip } from "@dxos/react-ui";
797
- import { defaultTx } from "@dxos/react-ui-theme";
798
- var createElement = (tag, options, children) => {
799
- const el = document.createElement(tag);
800
- if (options?.className) {
801
- el.className = options.className;
493
+ var handlePut = (target, patch, state) => {
494
+ const index = charPath(target, [
495
+ ...patch.path,
496
+ 0
497
+ ]);
498
+ if (index == null) {
499
+ return [];
802
500
  }
803
- if (children) {
804
- el.append(...Array.isArray(children) ? children : [
805
- children
806
- ]);
501
+ const length = state.doc.length;
502
+ if (typeof patch.value !== "string") {
503
+ return [];
807
504
  }
808
- return el;
505
+ return [
506
+ {
507
+ from: 0,
508
+ to: length,
509
+ insert: patch.value
510
+ }
511
+ ];
809
512
  };
810
- var renderRoot = (root, node) => {
811
- createRoot(root).render(/* @__PURE__ */ React2.createElement(ThemeProvider, {
812
- tx: defaultTx
813
- }, node));
814
- return root;
815
- };
816
- var createRenderer = (Component) => (el, props) => {
817
- renderRoot(el, /* @__PURE__ */ React2.createElement(ThemeProvider, {
818
- tx: defaultTx
819
- }, /* @__PURE__ */ React2.createElement(Tooltip.Provider, null, /* @__PURE__ */ React2.createElement(Component, props))));
513
+ var charPath = (textPath, candidatePath) => {
514
+ if (candidatePath.length !== textPath.length + 1) {
515
+ return null;
516
+ }
517
+ for (let i = 0; i < textPath.length; i++) {
518
+ if (textPath[i] !== candidatePath[i]) {
519
+ return null;
520
+ }
521
+ }
522
+ const index = candidatePath[candidatePath.length - 1];
523
+ if (typeof index === "number") {
524
+ return index;
525
+ }
526
+ return null;
820
527
  };
821
528
 
822
- // packages/ui/react-ui-editor/src/extensions/annotations.ts
823
- var annotationMark = Decoration.mark({
824
- class: "cm-annotation"
825
- });
826
- var annotations = (options = {}) => {
827
- const match = (state) => {
828
- const annotations2 = [];
829
- const text = state.doc.toString();
830
- if (options.match) {
831
- const matches = text.matchAll(options.match);
832
- for (const match2 of matches) {
833
- const from = match2.index;
834
- const to = from + match2[0].length;
835
- const cursor = Cursor.getCursorFromRange(state, {
836
- from,
837
- to
838
- });
839
- annotations2.push({
840
- cursor
841
- });
842
- }
529
+ // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
530
+ var Syncer = class {
531
+ // prettier-ignore
532
+ constructor(_handle, _state) {
533
+ this._handle = _handle;
534
+ this._state = _state;
535
+ this._pending = false;
536
+ }
537
+ reconcile(view, editor) {
538
+ if (this._pending) {
539
+ return;
843
540
  }
844
- return annotations2;
845
- };
846
- const annotationsState = StateField.define({
847
- create: (state) => {
848
- return match(state);
849
- },
541
+ this._pending = true;
542
+ if (editor) {
543
+ this.onEditorChange(view);
544
+ } else {
545
+ this.onAutomergeChange(view);
546
+ }
547
+ this._pending = false;
548
+ }
549
+ onEditorChange(view) {
550
+ const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
551
+ const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
552
+ if (newHeads) {
553
+ view.dispatch({
554
+ effects: updateHeads(newHeads),
555
+ annotations: reconcileAnnotation.of(false)
556
+ });
557
+ }
558
+ }
559
+ onAutomergeChange(view) {
560
+ const oldHeads = getLastHeads(view.state, this._state);
561
+ const newHeads = A2.getHeads(this._handle.docSync());
562
+ const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.docSync(), oldHeads, newHeads);
563
+ const selection = view.state.selection;
564
+ const path = getPath(view.state, this._state);
565
+ updateCodeMirror(view, selection, path, diff);
566
+ view.dispatch({
567
+ effects: updateHeads(newHeads),
568
+ annotations: reconcileAnnotation.of(false)
569
+ });
570
+ }
571
+ };
572
+
573
+ // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
574
+ var automerge = (accessor) => {
575
+ const syncState = StateField2.define({
576
+ create: () => ({
577
+ path: accessor.path.slice(),
578
+ lastHeads: A3.getHeads(accessor.handle.docSync()),
579
+ unreconciledTransactions: []
580
+ }),
850
581
  update: (value, tr) => {
851
- if (!tr.changes.empty) {
852
- return match(tr.state);
582
+ const result = {
583
+ path: accessor.path.slice(),
584
+ lastHeads: value.lastHeads,
585
+ unreconciledTransactions: value.unreconciledTransactions.slice()
586
+ };
587
+ let clearUnreconciled = false;
588
+ for (const effect of tr.effects) {
589
+ if (effect.is(updateHeadsEffect)) {
590
+ result.lastHeads = effect.value.newHeads;
591
+ clearUnreconciled = true;
592
+ }
853
593
  }
854
- return value;
594
+ if (clearUnreconciled) {
595
+ result.unreconciledTransactions = [];
596
+ } else {
597
+ if (!isReconcile(tr)) {
598
+ result.unreconciledTransactions.push(tr);
599
+ }
600
+ }
601
+ return result;
855
602
  }
856
603
  });
604
+ const syncer = new Syncer(accessor.handle, syncState);
857
605
  return [
858
- annotationsState,
859
- EditorView2.decorations.compute([
860
- annotationsState
861
- ], (state) => {
862
- const annotations2 = state.field(annotationsState);
863
- const decorations = annotations2.map((annotation) => {
864
- const range = Cursor.getRangeFromCursor(state, annotation.cursor);
865
- return range && annotationMark.range(range.from, range.to);
866
- }).filter(isNotFalsy);
867
- return Decoration.set(decorations);
606
+ Cursor.converter.of(cursorConverter(accessor)),
607
+ // Track heads.
608
+ syncState,
609
+ // Reconcile external updates.
610
+ ViewPlugin.fromClass(class {
611
+ constructor(_view) {
612
+ this._view = _view;
613
+ this._handleChange = () => {
614
+ syncer.reconcile(this._view, false);
615
+ };
616
+ accessor.handle.addListener("change", this._handleChange);
617
+ }
618
+ destroy() {
619
+ accessor.handle.removeListener("change", this._handleChange);
620
+ }
868
621
  }),
869
- styles
870
- ];
871
- };
872
- var styles = EditorView2.theme({
873
- ".cm-annotation": {
874
- textDecoration: "underline",
875
- textDecorationStyle: "wavy",
876
- textDecorationColor: "var(--dx-error)"
877
- }
878
- });
879
-
880
- // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
881
- import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
882
- import { markdownLanguage } from "@codemirror/lang-markdown";
883
- import { keymap } from "@codemirror/view";
884
- var autocomplete = ({ debug, activateOnTyping, override, onSearch } = {}) => {
885
- const extensions = [
886
- // https://codemirror.net/docs/ref/#view.keymap
887
- // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
888
- // TODO(burdon): Set custom keymap.
889
- keymap.of(completionKeymap),
890
- // https://codemirror.net/examples/autocompletion
891
- // https://codemirror.net/docs/ref/#autocomplete.autocompletion
892
- autocompletion({
893
- activateOnTyping,
894
- override,
895
- closeOnBlur: !debug,
896
- tooltipClass: () => "shadow rounded"
622
+ // Reconcile local updates.
623
+ EditorView2.updateListener.of(({ view, changes }) => {
624
+ if (!changes.empty) {
625
+ syncer.reconcile(view, true);
626
+ }
897
627
  })
898
628
  ];
899
- if (onSearch) {
900
- extensions.push(
901
- // TODO(burdon): Optional decoration via addToOptions
902
- markdownLanguage.data.of({
903
- autocomplete: (context) => {
904
- const match = context.matchBefore(/\w*/);
905
- if (!match || match.from === match.to && !context.explicit) {
906
- return null;
907
- }
908
- return {
909
- from: match.from,
910
- options: onSearch(match.text.toLowerCase())
911
- };
912
- }
913
- })
914
- );
915
- }
916
- return extensions;
917
629
  };
918
630
 
919
- // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
920
- import { StateField as StateField2 } from "@codemirror/state";
921
- import { EditorView as EditorView3, ViewPlugin } from "@codemirror/view";
922
- import { next as A3 } from "@dxos/automerge/automerge";
923
-
924
- // packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts
925
- import { log as log2 } from "@dxos/log";
926
- import { fromCursor, toCursor } from "@dxos/react-client/echo";
927
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/automerge/cursor.ts";
928
- var cursorConverter = (accessor) => ({
929
- toCursor: (pos, assoc) => {
930
- try {
931
- return toCursor(accessor, pos, assoc);
932
- } catch (err) {
933
- log2.catch(err, void 0, {
934
- F: __dxlog_file2,
935
- L: 15,
936
- S: void 0,
937
- C: (f, a) => f(...a)
938
- });
939
- return "";
940
- }
631
+ // packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts
632
+ import { Annotation as Annotation2, RangeSet } from "@codemirror/state";
633
+ import { Decoration as Decoration2, EditorView as EditorView3, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
634
+ import { Event } from "@dxos/async";
635
+ import { Context } from "@dxos/context";
636
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts";
637
+ var dummyProvider = {
638
+ remoteStateChange: new Event(),
639
+ open: () => {
941
640
  },
942
- fromCursor: (cursor) => {
943
- try {
944
- return fromCursor(accessor, cursor);
945
- } catch (err) {
946
- log2.catch(err, void 0, {
947
- F: __dxlog_file2,
948
- L: 24,
949
- S: void 0,
950
- C: (f, a) => f(...a)
951
- });
952
- return 0;
953
- }
954
- }
955
- });
956
-
957
- // packages/ui/react-ui-editor/src/extensions/automerge/defs.ts
958
- import { Annotation, StateEffect } from "@codemirror/state";
959
- var getPath = (state, field) => state.field(field).path;
960
- var getLastHeads = (state, field) => state.field(field).lastHeads;
961
- var updateHeadsEffect = StateEffect.define({});
962
- var updateHeads = (newHeads) => updateHeadsEffect.of({
963
- newHeads
964
- });
965
- var reconcileAnnotation = Annotation.define();
966
- var isReconcile = (tr) => {
967
- return !!tr.annotation(reconcileAnnotation);
968
- };
969
-
970
- // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
971
- import { next as A2 } from "@dxos/automerge/automerge";
972
-
973
- // packages/ui/react-ui-editor/src/extensions/automerge/update-automerge.ts
974
- import { next as A } from "@dxos/automerge/automerge";
975
- var updateAutomerge = (field, handle, transactions, state) => {
976
- const { lastHeads, path } = state.field(field);
977
- let hasChanges = false;
978
- for (const tr of transactions) {
979
- tr.changes.iterChanges(() => {
980
- hasChanges = true;
981
- });
982
- }
983
- if (!hasChanges) {
984
- return void 0;
985
- }
986
- const newHeads = handle.changeAt(lastHeads, (doc) => {
987
- const invertedTransactions = [];
988
- for (const tr of transactions) {
989
- tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
990
- invertedTransactions.push({
991
- from: fromA,
992
- del: toA - fromA,
993
- insert
994
- });
995
- });
996
- }
997
- invertedTransactions.reverse().forEach(({ from, del, insert }) => {
998
- A.splice(doc, path.slice(), from, del, insert.toString());
999
- });
1000
- });
1001
- return newHeads ?? void 0;
1002
- };
1003
-
1004
- // packages/ui/react-ui-editor/src/extensions/automerge/update-codemirror.ts
1005
- import { ChangeSet } from "@codemirror/state";
1006
- var updateCodeMirror = (view, selection, target, patches) => {
1007
- for (const patch of patches) {
1008
- const changeSpec = handlePatch(patch, target, view.state);
1009
- if (changeSpec != null) {
1010
- const changeSet = ChangeSet.of(changeSpec, view.state.doc.length, "\n");
1011
- selection = selection.map(changeSet, 1);
1012
- view.dispatch({
1013
- changes: changeSet,
1014
- annotations: reconcileAnnotation.of(false)
1015
- });
1016
- }
1017
- }
1018
- view.dispatch({
1019
- selection,
1020
- annotations: reconcileAnnotation.of(false)
1021
- });
1022
- };
1023
- var handlePatch = (patch, target, state) => {
1024
- if (patch.action === "insert") {
1025
- return handleInsert(target, patch);
1026
- } else if (patch.action === "splice") {
1027
- return handleSplice(target, patch);
1028
- } else if (patch.action === "del") {
1029
- return handleDel(target, patch);
1030
- } else if (patch.action === "put") {
1031
- return handlePut(target, patch, state);
1032
- } else {
1033
- return null;
1034
- }
1035
- };
1036
- var handleInsert = (target, patch) => {
1037
- const index = charPath(target, patch.path);
1038
- if (index == null) {
1039
- return [];
1040
- }
1041
- const text = patch.values.map((value) => value ? value.toString() : "").join("");
1042
- return [
1043
- {
1044
- from: index,
1045
- to: index,
1046
- insert: text
1047
- }
1048
- ];
1049
- };
1050
- var handleSplice = (target, patch) => {
1051
- const index = charPath(target, patch.path);
1052
- if (index == null) {
1053
- return [];
1054
- }
1055
- return [
1056
- {
1057
- from: index,
1058
- insert: patch.value
1059
- }
1060
- ];
1061
- };
1062
- var handleDel = (target, patch) => {
1063
- const index = charPath(target, patch.path);
1064
- if (index == null) {
1065
- return [];
1066
- }
1067
- const length = patch.length || 1;
1068
- return [
1069
- {
1070
- from: index,
1071
- to: index + length
1072
- }
1073
- ];
1074
- };
1075
- var handlePut = (target, patch, state) => {
1076
- const index = charPath(target, [
1077
- ...patch.path,
1078
- 0
1079
- ]);
1080
- if (index == null) {
1081
- return [];
1082
- }
1083
- const length = state.doc.length;
1084
- if (typeof patch.value !== "string") {
1085
- return [];
1086
- }
1087
- return [
1088
- {
1089
- from: 0,
1090
- to: length,
1091
- insert: patch.value
1092
- }
1093
- ];
1094
- };
1095
- var charPath = (textPath, candidatePath) => {
1096
- if (candidatePath.length !== textPath.length + 1) {
1097
- return null;
1098
- }
1099
- for (let i = 0; i < textPath.length; i++) {
1100
- if (textPath[i] !== candidatePath[i]) {
1101
- return null;
1102
- }
1103
- }
1104
- const index = candidatePath[candidatePath.length - 1];
1105
- if (typeof index === "number") {
1106
- return index;
1107
- }
1108
- return null;
1109
- };
1110
-
1111
- // packages/ui/react-ui-editor/src/extensions/automerge/sync.ts
1112
- var Syncer = class {
1113
- // prettier-ignore
1114
- constructor(_handle, _state) {
1115
- this._handle = _handle;
1116
- this._state = _state;
1117
- this._pending = false;
1118
- }
1119
- reconcile(view, editor) {
1120
- if (this._pending) {
1121
- return;
1122
- }
1123
- this._pending = true;
1124
- if (editor) {
1125
- this.onEditorChange(view);
1126
- } else {
1127
- this.onAutomergeChange(view);
1128
- }
1129
- this._pending = false;
1130
- }
1131
- onEditorChange(view) {
1132
- const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
1133
- const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
1134
- if (newHeads) {
1135
- view.dispatch({
1136
- effects: updateHeads(newHeads),
1137
- annotations: reconcileAnnotation.of(false)
1138
- });
1139
- }
1140
- }
1141
- onAutomergeChange(view) {
1142
- const oldHeads = getLastHeads(view.state, this._state);
1143
- const newHeads = A2.getHeads(this._handle.docSync());
1144
- const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.docSync(), oldHeads, newHeads);
1145
- const selection = view.state.selection;
1146
- const path = getPath(view.state, this._state);
1147
- updateCodeMirror(view, selection, path, diff);
1148
- view.dispatch({
1149
- effects: updateHeads(newHeads),
1150
- annotations: reconcileAnnotation.of(false)
1151
- });
1152
- }
1153
- };
1154
-
1155
- // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
1156
- var automerge = (accessor) => {
1157
- const syncState = StateField2.define({
1158
- create: () => ({
1159
- path: accessor.path.slice(),
1160
- lastHeads: A3.getHeads(accessor.handle.docSync()),
1161
- unreconciledTransactions: []
1162
- }),
1163
- update: (value, tr) => {
1164
- const result = {
1165
- path: accessor.path.slice(),
1166
- lastHeads: value.lastHeads,
1167
- unreconciledTransactions: value.unreconciledTransactions.slice()
1168
- };
1169
- let clearUnreconciled = false;
1170
- for (const effect of tr.effects) {
1171
- if (effect.is(updateHeadsEffect)) {
1172
- result.lastHeads = effect.value.newHeads;
1173
- clearUnreconciled = true;
1174
- }
1175
- }
1176
- if (clearUnreconciled) {
1177
- result.unreconciledTransactions = [];
1178
- } else {
1179
- if (!isReconcile(tr)) {
1180
- result.unreconciledTransactions.push(tr);
1181
- }
1182
- }
1183
- return result;
1184
- }
1185
- });
1186
- const syncer = new Syncer(accessor.handle, syncState);
1187
- return [
1188
- Cursor.converter.of(cursorConverter(accessor)),
1189
- // Track heads.
1190
- syncState,
1191
- // Reconcile external updates.
1192
- ViewPlugin.fromClass(class {
1193
- constructor(_view) {
1194
- this._view = _view;
1195
- this._handleChange = () => {
1196
- syncer.reconcile(this._view, false);
1197
- };
1198
- accessor.handle.addListener("change", this._handleChange);
1199
- }
1200
- destroy() {
1201
- accessor.handle.removeListener("change", this._handleChange);
1202
- }
1203
- }),
1204
- // Reconcile local updates.
1205
- EditorView3.updateListener.of(({ view, changes }) => {
1206
- if (!changes.empty) {
1207
- syncer.reconcile(view, true);
1208
- }
1209
- })
1210
- ];
1211
- };
1212
-
1213
- // packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts
1214
- import { Annotation as Annotation2, RangeSet } from "@codemirror/state";
1215
- import { Decoration as Decoration2, EditorView as EditorView4, ViewPlugin as ViewPlugin2, WidgetType } from "@codemirror/view";
1216
- import { Event } from "@dxos/async";
1217
- import { Context } from "@dxos/context";
1218
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/awareness/awareness.ts";
1219
- var dummyProvider = {
1220
- remoteStateChange: new Event(),
1221
- open: () => {
1222
- },
1223
- close: () => {
1224
- },
1225
- getRemoteStates: () => [],
1226
- update: () => {
641
+ close: () => {
642
+ },
643
+ getRemoteStates: () => [],
644
+ update: () => {
1227
645
  }
1228
646
  };
1229
647
  var awarenessProvider = singleValueFacet(dummyProvider);
@@ -1386,7 +804,7 @@ var RemoteCaretWidget = class extends WidgetType {
1386
804
  return true;
1387
805
  }
1388
806
  };
1389
- var styles2 = EditorView4.theme({
807
+ var styles2 = EditorView3.theme({
1390
808
  ".cm-collab-selection": {},
1391
809
  ".cm-collab-selectionLine": {
1392
810
  padding: 0,
@@ -1552,7 +970,7 @@ var SpaceAwarenessProvider = class {
1552
970
  };
1553
971
 
1554
972
  // packages/ui/react-ui-editor/src/extensions/blast.ts
1555
- import { EditorView as EditorView5, keymap as keymap2 } from "@codemirror/view";
973
+ import { EditorView as EditorView4, keymap as keymap2 } from "@codemirror/view";
1556
974
  import defaultsDeep from "lodash.defaultsdeep";
1557
975
  import { invariant as invariant2 } from "@dxos/invariant";
1558
976
  var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
@@ -1600,7 +1018,7 @@ var blast = (options = defaultOptions) => {
1600
1018
  };
1601
1019
  return [
1602
1020
  // Cursor moved.
1603
- EditorView5.updateListener.of((update2) => {
1021
+ EditorView4.updateListener.of((update2) => {
1604
1022
  if (blaster?.node !== update2.view.scrollDOM) {
1605
1023
  if (blaster) {
1606
1024
  blaster.destroy();
@@ -1974,11 +1392,11 @@ var commandKeyBindings = [
1974
1392
  ];
1975
1393
 
1976
1394
  // packages/ui/react-ui-editor/src/extensions/command/command.ts
1977
- import { EditorView as EditorView7, keymap as keymap3 } from "@codemirror/view";
1395
+ import { EditorView as EditorView6, keymap as keymap3 } from "@codemirror/view";
1978
1396
 
1979
1397
  // packages/ui/react-ui-editor/src/extensions/command/hint.ts
1980
1398
  import { RangeSetBuilder } from "@codemirror/state";
1981
- import { Decoration as Decoration3, EditorView as EditorView6, ViewPlugin as ViewPlugin3, WidgetType as WidgetType2 } from "@codemirror/view";
1399
+ import { Decoration as Decoration3, EditorView as EditorView5, ViewPlugin as ViewPlugin3, WidgetType as WidgetType2 } from "@codemirror/view";
1982
1400
  var hintViewPlugin = ({ onHint }) => ViewPlugin3.fromClass(class {
1983
1401
  constructor() {
1984
1402
  this.deco = Decoration3.none;
@@ -2002,7 +1420,7 @@ var hintViewPlugin = ({ onHint }) => ViewPlugin3.fromClass(class {
2002
1420
  }
2003
1421
  }, {
2004
1422
  provide: (plugin) => [
2005
- EditorView6.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration3.none)
1423
+ EditorView5.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration3.none)
2006
1424
  ]
2007
1425
  });
2008
1426
  var CommandHint = class extends WidgetType2 {
@@ -2121,10 +1539,10 @@ var command = (options = {}) => {
2121
1539
  options.onHint ? hintViewPlugin({
2122
1540
  onHint: options.onHint
2123
1541
  }) : [],
2124
- EditorView7.focusChangeEffect.of((_, focusing) => {
1542
+ EditorView6.focusChangeEffect.of((_, focusing) => {
2125
1543
  return focusing ? closeEffect.of(null) : null;
2126
1544
  }),
2127
- EditorView7.theme({
1545
+ EditorView6.theme({
2128
1546
  ".cm-tooltip": {
2129
1547
  background: "transparent"
2130
1548
  }
@@ -2135,7 +1553,7 @@ var command = (options = {}) => {
2135
1553
  // packages/ui/react-ui-editor/src/extensions/comments.ts
2136
1554
  import { invertedEffects } from "@codemirror/commands";
2137
1555
  import { StateEffect as StateEffect3, StateField as StateField4 } from "@codemirror/state";
2138
- import { hoverTooltip, keymap as keymap5, Decoration as Decoration4, EditorView as EditorView9, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1556
+ import { hoverTooltip, keymap as keymap5, Decoration as Decoration4, EditorView as EditorView8, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
2139
1557
  import sortBy from "lodash.sortby";
2140
1558
  import { useEffect, useMemo as useMemo2 } from "react";
2141
1559
  import { debounce as debounce2 } from "@dxos/async";
@@ -2144,7 +1562,7 @@ import { isNonNullable } from "@dxos/util";
2144
1562
 
2145
1563
  // packages/ui/react-ui-editor/src/extensions/selection.ts
2146
1564
  import { Transaction } from "@codemirror/state";
2147
- import { EditorView as EditorView8, keymap as keymap4 } from "@codemirror/view";
1565
+ import { EditorView as EditorView7, keymap as keymap4 } from "@codemirror/view";
2148
1566
  import { debounce } from "@dxos/async";
2149
1567
  import { invariant as invariant3 } from "@dxos/invariant";
2150
1568
  import { isNotFalsy as isNotFalsy2 } from "@dxos/util";
@@ -2155,7 +1573,7 @@ var createEditorStateTransaction = ({ scrollTo, selection }) => {
2155
1573
  return {
2156
1574
  selection,
2157
1575
  scrollIntoView: !scrollTo,
2158
- effects: scrollTo ? EditorView8.scrollIntoView(scrollTo, {
1576
+ effects: scrollTo ? EditorView7.scrollIntoView(scrollTo, {
2159
1577
  yMargin: 96
2160
1578
  }) : void 0,
2161
1579
  annotations: Transaction.userEvent.of(stateRestoreAnnotation)
@@ -2197,7 +1615,7 @@ var selectionState = ({ getState, setState } = {}) => {
2197
1615
  // setStateDebounced(id, {});
2198
1616
  // },
2199
1617
  // }),
2200
- EditorView8.updateListener.of(({ view, transactions }) => {
1618
+ EditorView7.updateListener.of(({ view, transactions }) => {
2201
1619
  const id = view.state.facet(documentId);
2202
1620
  if (!id || transactions.some((tr) => tr.isUserEvent(stateRestoreAnnotation))) {
2203
1621
  return;
@@ -2278,7 +1696,7 @@ var commentsState = StateField4.define({
2278
1696
  return value;
2279
1697
  }
2280
1698
  });
2281
- var styles3 = EditorView9.theme({
1699
+ var styles3 = EditorView8.theme({
2282
1700
  ".cm-comment, .cm-comment-current": {
2283
1701
  margin: "0 -3px",
2284
1702
  padding: "3px",
@@ -2298,7 +1716,7 @@ var createCommentMark = (id, isCurrent) => Decoration4.mark({
2298
1716
  "data-comment-id": id
2299
1717
  }
2300
1718
  });
2301
- var commentsDecorations = EditorView9.decorations.compute([
1719
+ var commentsDecorations = EditorView8.decorations.compute([
2302
1720
  commentsState
2303
1721
  ], (state) => {
2304
1722
  const { selection: { current }, comments: comments2 } = state.field(commentsState);
@@ -2321,7 +1739,7 @@ var commentsDecorations = EditorView9.decorations.compute([
2321
1739
  return Decoration4.set(decorations);
2322
1740
  });
2323
1741
  var commentClickedEffect = StateEffect3.define();
2324
- var handleCommentClick = EditorView9.domEventHandlers({
1742
+ var handleCommentClick = EditorView8.domEventHandlers({
2325
1743
  click: (event, view) => {
2326
1744
  let target = event.target;
2327
1745
  const editorRoot = view.dom;
@@ -2360,7 +1778,7 @@ var trackPastedComments = (onUpdate) => {
2360
1778
  }
2361
1779
  };
2362
1780
  return [
2363
- EditorView9.domEventHandlers({
1781
+ EditorView8.domEventHandlers({
2364
1782
  cut: handleTrack,
2365
1783
  copy: handleTrack
2366
1784
  }),
@@ -2382,7 +1800,7 @@ var trackPastedComments = (onUpdate) => {
2382
1800
  return effects;
2383
1801
  }),
2384
1802
  // Handle paste or the undo of comment deletion.
2385
- EditorView9.updateListener.of((update2) => {
1803
+ EditorView8.updateListener.of((update2) => {
2386
1804
  const restore = [];
2387
1805
  for (let i = 0; i < update2.transactions.length; i++) {
2388
1806
  const tr = update2.transactions[i];
@@ -2441,7 +1859,7 @@ var mapTrackedComment = (comment, changes) => ({
2441
1859
  var restoreCommentEffect = StateEffect3.define({
2442
1860
  map: mapTrackedComment
2443
1861
  });
2444
- var createComment2 = (view) => {
1862
+ var createComment = (view) => {
2445
1863
  const options = view.state.facet(optionsFacet);
2446
1864
  const { from, to } = view.state.selection.main;
2447
1865
  if (from === to) {
@@ -2486,7 +1904,7 @@ var comments = (options = {}) => {
2486
1904
  options.onCreate && keymap5.of([
2487
1905
  {
2488
1906
  key: shortcut,
2489
- run: callbackWrapper(createComment2)
1907
+ run: callbackWrapper(createComment)
2490
1908
  }
2491
1909
  ]),
2492
1910
  //
@@ -2524,7 +1942,7 @@ var comments = (options = {}) => {
2524
1942
  //
2525
1943
  // Track deleted ranges and update ranges for decorations.
2526
1944
  //
2527
- EditorView9.updateListener.of(({ view, state, changes }) => {
1945
+ EditorView8.updateListener.of(({ view, state, changes }) => {
2528
1946
  let mod = false;
2529
1947
  const { comments: comments2, ...value } = state.field(commentsState);
2530
1948
  changes.iterChanges((from, to, from2, to2) => {
@@ -2556,7 +1974,7 @@ var comments = (options = {}) => {
2556
1974
  //
2557
1975
  // Track selection/proximity.
2558
1976
  //
2559
- EditorView9.updateListener.of(({ view, state }) => {
1977
+ EditorView8.updateListener.of(({ view, state }) => {
2560
1978
  let min = Infinity;
2561
1979
  const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
2562
1980
  const { head } = state.selection.main;
@@ -2610,7 +2028,7 @@ var scrollThreadIntoView = (view, id, center = true) => {
2610
2028
  anchor: range.from
2611
2029
  } : void 0,
2612
2030
  effects: [
2613
- needsScroll ? EditorView9.scrollIntoView(range.from, center ? {
2031
+ needsScroll ? EditorView8.scrollIntoView(range.from, center ? {
2614
2032
  y: "center"
2615
2033
  } : void 0) : [],
2616
2034
  needsSelectionUpdate ? setSelection.of({
@@ -2662,7 +2080,7 @@ var createExternalCommentSync = (id, subscribe, getComments) => ViewPlugin5.from
2662
2080
  }
2663
2081
  });
2664
2082
  var useCommentState = (state) => {
2665
- return useMemo2(() => EditorView9.updateListener.of((update2) => {
2083
+ return useMemo2(() => EditorView8.updateListener.of((update2) => {
2666
2084
  if (update2.docChanged || update2.selectionSet) {
2667
2085
  state.comment = selectionOverlapsComment(update2.state);
2668
2086
  state.selection = hasActiveSelection(update2.state);
@@ -2683,116 +2101,348 @@ var useComments = (view, id, comments2) => {
2683
2101
  });
2684
2102
  }
2685
2103
  }
2686
- });
2687
- };
2688
- var useCommentClickListener = (onCommentClick) => {
2689
- return useMemo2(() => EditorView9.updateListener.of((update2) => {
2690
- update2.transactions.forEach((transaction) => {
2691
- transaction.effects.forEach((effect) => {
2692
- if (effect.is(commentClickedEffect)) {
2693
- onCommentClick(effect.value);
2694
- }
2695
- });
2696
- });
2697
- }), [
2698
- onCommentClick
2699
- ]);
2700
- };
2701
-
2702
- // packages/ui/react-ui-editor/src/extensions/debug.ts
2703
- import { syntaxTree } from "@codemirror/language";
2704
- import { StateField as StateField5 } from "@codemirror/state";
2705
- var debugNodeLogger = (log8 = console.log) => {
2706
- const logTokens = (state) => syntaxTree(state).iterate({
2707
- enter: (node) => log8(node.type)
2708
- });
2709
- return StateField5.define({
2710
- create: (state) => logTokens(state),
2711
- update: (_, tr) => logTokens(tr.state)
2712
- });
2713
- };
2714
-
2715
- // packages/ui/react-ui-editor/src/extensions/dnd.ts
2716
- import { dropCursor, EditorView as EditorView10 } from "@codemirror/view";
2717
- var styles4 = EditorView10.theme({
2718
- ".cm-dropCursor": {
2719
- borderLeft: "2px solid var(--dx-accentText)",
2720
- color: "var(--dx-accentText)",
2721
- padding: "0 4px"
2104
+ });
2105
+ };
2106
+ var useCommentClickListener = (onCommentClick) => {
2107
+ return useMemo2(() => EditorView8.updateListener.of((update2) => {
2108
+ update2.transactions.forEach((transaction) => {
2109
+ transaction.effects.forEach((effect) => {
2110
+ if (effect.is(commentClickedEffect)) {
2111
+ onCommentClick(effect.value);
2112
+ }
2113
+ });
2114
+ });
2115
+ }), [
2116
+ onCommentClick
2117
+ ]);
2118
+ };
2119
+
2120
+ // packages/ui/react-ui-editor/src/extensions/debug.ts
2121
+ import { syntaxTree } from "@codemirror/language";
2122
+ import { StateField as StateField5 } from "@codemirror/state";
2123
+ var debugNodeLogger = (log8 = console.log) => {
2124
+ const logTokens = (state) => syntaxTree(state).iterate({
2125
+ enter: (node) => log8(node.type)
2126
+ });
2127
+ return StateField5.define({
2128
+ create: (state) => logTokens(state),
2129
+ update: (_, tr) => logTokens(tr.state)
2130
+ });
2131
+ };
2132
+
2133
+ // packages/ui/react-ui-editor/src/extensions/dnd.ts
2134
+ import { dropCursor, EditorView as EditorView9 } from "@codemirror/view";
2135
+ var styles4 = EditorView9.theme({
2136
+ ".cm-dropCursor": {
2137
+ borderLeft: "2px solid var(--dx-accentText)",
2138
+ color: "var(--dx-accentText)",
2139
+ padding: "0 4px"
2140
+ },
2141
+ ".cm-dropCursor:after": {
2142
+ content: '"\u2190"'
2143
+ }
2144
+ });
2145
+ var dropFile = (options = {}) => {
2146
+ return [
2147
+ styles4,
2148
+ dropCursor(),
2149
+ EditorView9.domEventHandlers({
2150
+ drop: (event, view) => {
2151
+ event.preventDefault();
2152
+ const files = event.dataTransfer?.files;
2153
+ const pos = view.posAtCoords(event);
2154
+ if (files?.length && pos !== null) {
2155
+ view.dispatch({
2156
+ selection: {
2157
+ anchor: pos
2158
+ }
2159
+ });
2160
+ options.onDrop?.(view, {
2161
+ files
2162
+ });
2163
+ }
2164
+ }
2165
+ })
2166
+ ];
2167
+ };
2168
+
2169
+ // packages/ui/react-ui-editor/src/extensions/factories.ts
2170
+ import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
2171
+ import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
2172
+ import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
2173
+ import { searchKeymap } from "@codemirror/search";
2174
+ import { EditorState } from "@codemirror/state";
2175
+ import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
2176
+ import { EditorView as EditorView11, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap6, lineNumbers, placeholder, scrollPastEnd } from "@codemirror/view";
2177
+ import defaultsDeep2 from "lodash.defaultsdeep";
2178
+ import merge from "lodash.merge";
2179
+ import { generateName } from "@dxos/display-name";
2180
+ import { log as log5 } from "@dxos/log";
2181
+ import { hexToHue, isNotFalsy as isNotFalsy3 } from "@dxos/util";
2182
+
2183
+ // packages/ui/react-ui-editor/src/extensions/focus.ts
2184
+ import { StateEffect as StateEffect4, StateField as StateField6 } from "@codemirror/state";
2185
+ import { EditorView as EditorView10 } from "@codemirror/view";
2186
+ var focusEffect = StateEffect4.define();
2187
+ var focusField = StateField6.define({
2188
+ create: () => false,
2189
+ update: (value, tr) => {
2190
+ for (const effect of tr.effects) {
2191
+ if (effect.is(focusEffect)) {
2192
+ return effect.value;
2193
+ }
2194
+ }
2195
+ return value;
2196
+ }
2197
+ });
2198
+ var focus = [
2199
+ focusField,
2200
+ EditorView10.domEventHandlers({
2201
+ focus: (event, view) => {
2202
+ setTimeout(() => view.dispatch({
2203
+ effects: focusEffect.of(true)
2204
+ }));
2205
+ },
2206
+ blur: (event, view) => {
2207
+ setTimeout(() => view.dispatch({
2208
+ effects: focusEffect.of(false)
2209
+ }));
2210
+ }
2211
+ })
2212
+ ];
2213
+
2214
+ // packages/ui/react-ui-editor/src/styles/markdown.ts
2215
+ import { mx } from "@dxos/react-ui-theme";
2216
+ var headings = {
2217
+ 1: "text-4xl",
2218
+ 2: "text-3xl",
2219
+ 3: "text-2xl",
2220
+ 4: "text-xl",
2221
+ 5: "text-lg",
2222
+ 6: ""
2223
+ };
2224
+ var theme = {
2225
+ code: "font-mono !no-underline text-neutral-700 dark:text-neutral-300",
2226
+ codeMark: "font-mono text-primary-500",
2227
+ mark: "opacity-50",
2228
+ heading: (level) => {
2229
+ return mx(headings[level], "dark:text-primary-400");
2230
+ }
2231
+ };
2232
+
2233
+ // packages/ui/react-ui-editor/src/styles/tokens.ts
2234
+ import get from "lodash.get";
2235
+ import { tokens } from "@dxos/react-ui-theme";
2236
+ var getToken = (path, defaultValue) => {
2237
+ const value = get(tokens, path, defaultValue);
2238
+ return value?.toString() ?? "";
2239
+ };
2240
+ var fontBody = getToken("fontFamily.body");
2241
+ var fontMono = getToken("fontFamily.mono");
2242
+
2243
+ // packages/ui/react-ui-editor/src/styles/theme.ts
2244
+ var defaultTheme = {
2245
+ "&": {},
2246
+ "&.cm-focused": {
2247
+ outline: "none"
2248
+ },
2249
+ /**
2250
+ * Scroller
2251
+ */
2252
+ ".cm-scroller": {
2253
+ overflowY: "auto"
2254
+ },
2255
+ /**
2256
+ * Content
2257
+ * NOTE: Apply margins to content so that scrollbar is at the edge of the container.
2258
+ */
2259
+ ".cm-content": {
2260
+ padding: "unset",
2261
+ fontFamily: fontBody,
2262
+ // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
2263
+ fontSize: "16px",
2264
+ lineHeight: 1.5,
2265
+ color: "unset"
2266
+ },
2267
+ /**
2268
+ * Gutters
2269
+ * NOTE: Gutters should have the same top margin as the content.
2270
+ */
2271
+ ".cm-gutters": {
2272
+ borderRight: "none",
2273
+ background: "transparent"
2274
+ },
2275
+ ".cm-gutter": {},
2276
+ ".cm-gutter.cm-lineNumbers": {
2277
+ paddingRight: "4px",
2278
+ borderRight: "1px solid var(--dx-separator)"
2279
+ },
2280
+ ".cm-gutter.cm-lineNumbers .cm-gutterElement": {
2281
+ minWidth: "40px",
2282
+ alignContent: "center"
2283
+ },
2284
+ /**
2285
+ * Height is set to match the corresponding line.
2286
+ */
2287
+ ".cm-gutterElement": {
2288
+ alignItems: "center",
2289
+ fontSize: "12px"
2290
+ },
2291
+ /**
2292
+ * Line.
2293
+ */
2294
+ ".cm-line": {
2295
+ paddingInline: 0
2296
+ },
2297
+ ".cm-activeLine": {
2298
+ background: "var(--dx-cmActiveLine)"
2299
+ },
2300
+ /**
2301
+ * Cursor (layer).
2302
+ */
2303
+ ".cm-cursor, .cm-dropCursor": {
2304
+ borderLeft: "2px solid var(--dx-cmCursor)"
2305
+ },
2306
+ ".cm-placeholder": {
2307
+ color: "var(--dx-subdued)"
2308
+ },
2309
+ /**
2310
+ * Selection (layer).
2311
+ */
2312
+ ".cm-selectionBackground": {
2313
+ background: "var(--dx-cmSelection)"
2314
+ },
2315
+ /**
2316
+ * Search.
2317
+ * NOTE: Matches comment.
2318
+ */
2319
+ ".cm-searchMatch": {
2320
+ margin: "0 -3px",
2321
+ padding: "3px",
2322
+ borderRadius: "3px",
2323
+ background: "var(--dx-cmHighlightSurface)",
2324
+ color: "var(--dx-cmHighlight)"
2325
+ },
2326
+ ".cm-searchMatch-selected": {
2327
+ textDecoration: "underline"
2328
+ },
2329
+ /**
2330
+ * Link.
2331
+ */
2332
+ ".cm-link": {
2333
+ textDecorationLine: "underline",
2334
+ textDecorationThickness: "1px",
2335
+ textDecorationColor: "var(--dx-separator)",
2336
+ textUnderlineOffset: "2px",
2337
+ borderRadius: ".125rem"
2338
+ },
2339
+ ".cm-link > span": {
2340
+ color: "var(--dx-accentText)"
2341
+ },
2342
+ /**
2343
+ * Tooltip.
2344
+ */
2345
+ ".cm-tooltip": {
2346
+ background: "var(--dx-baseSurface)"
2347
+ },
2348
+ ".cm-tooltip-below": {},
2349
+ /**
2350
+ * Autocomplete.
2351
+ * https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
2352
+ */
2353
+ ".cm-tooltip.cm-tooltip-autocomplete": {
2354
+ marginTop: "4px",
2355
+ marginLeft: "-3px"
2356
+ },
2357
+ ".cm-tooltip.cm-tooltip-autocomplete > ul": {
2358
+ maxHeight: "20em"
2359
+ },
2360
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li": {},
2361
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {},
2362
+ ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
2363
+ paddingLeft: "4px !important",
2364
+ borderBottom: "none !important",
2365
+ color: "var(--dx-accentText)"
2366
+ },
2367
+ ".cm-tooltip.cm-completionInfo": {
2368
+ width: "360px !important",
2369
+ margin: "-10px 1px 0 1px",
2370
+ padding: "8px !important",
2371
+ borderColor: "var(--dx-separator)"
2372
+ },
2373
+ ".cm-completionIcon": {
2374
+ display: "none"
2375
+ },
2376
+ ".cm-completionLabel": {
2377
+ fontFamily: fontBody
2378
+ },
2379
+ ".cm-completionMatchedText": {
2380
+ textDecoration: "none !important",
2381
+ opacity: 0.5
2382
+ },
2383
+ /**
2384
+ * Panels
2385
+ * https://github.com/codemirror/search/blob/main/src/search.ts#L745
2386
+ *
2387
+ * Find/replace panel.
2388
+ * <div class="cm-announced">...</div>
2389
+ * <div class="cm-scroller">...</div>
2390
+ * <div class="cm-panels cm-panels-bottom">
2391
+ * <div class="cm-search cm-panel">
2392
+ * <input class="cm-textfield" />
2393
+ * <button class="cm-button">...</button>
2394
+ * <label><input type="checkbox" />...</label>
2395
+ * </div>
2396
+ * </div
2397
+ */
2398
+ // TODO(burdon): Implement custom panel (with icon buttons).
2399
+ ".cm-panels": {},
2400
+ ".cm-panel": {
2401
+ fontFamily: fontBody,
2402
+ backgroundColor: "var(--surface-bg)"
2403
+ },
2404
+ ".cm-panel input, .cm-panel button, .cm-panel label": {
2405
+ color: "var(--dx-subdued)",
2406
+ fontFamily: fontBody,
2407
+ fontSize: "14px",
2408
+ all: "unset",
2409
+ margin: "3px !important",
2410
+ padding: "2px 6px !important",
2411
+ outline: "1px solid transparent"
2412
+ },
2413
+ ".cm-panel input, .cm-panel button": {
2414
+ backgroundColor: "var(--dx-input)"
2415
+ },
2416
+ ".cm-panel input:focus, .cm-panel button:focus": {
2417
+ outline: "1px solid var(--dx-accentFocusIndicator)"
2418
+ },
2419
+ ".cm-panel label": {
2420
+ display: "inline-flex",
2421
+ alignItems: "center",
2422
+ cursor: "pointer"
2423
+ },
2424
+ ".cm-panel input.cm-textfield": {},
2425
+ ".cm-panel input[type=checkbox]": {
2426
+ width: "8px",
2427
+ height: "8px",
2428
+ marginRight: "6px !important",
2429
+ padding: "2px !important",
2430
+ color: "var(--dx-accentFocusIndicator)"
2431
+ },
2432
+ ".cm-panel button": {
2433
+ "&:hover": {
2434
+ backgroundColor: "var(--dx-accentSurfaceHover) !important"
2435
+ },
2436
+ "&:active": {
2437
+ backgroundColor: "var(--dx-accentSurfaceHover)"
2438
+ }
2722
2439
  },
2723
- ".cm-dropCursor:after": {
2724
- content: '"\u2190"'
2440
+ ".cm-panel.cm-search": {
2441
+ padding: "4px",
2442
+ borderTop: "1px solid var(--dx-separator)"
2725
2443
  }
2726
- });
2727
- var dropFile = (options = {}) => {
2728
- return [
2729
- styles4,
2730
- dropCursor(),
2731
- EditorView10.domEventHandlers({
2732
- drop: (event, view) => {
2733
- event.preventDefault();
2734
- const files = event.dataTransfer?.files;
2735
- const pos = view.posAtCoords(event);
2736
- if (files?.length && pos !== null) {
2737
- view.dispatch({
2738
- selection: {
2739
- anchor: pos
2740
- }
2741
- });
2742
- options.onDrop?.(view, {
2743
- files
2744
- });
2745
- }
2746
- }
2747
- })
2748
- ];
2749
2444
  };
2750
2445
 
2751
- // packages/ui/react-ui-editor/src/extensions/factories.ts
2752
- import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
2753
- import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
2754
- import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
2755
- import { searchKeymap } from "@codemirror/search";
2756
- import { EditorState } from "@codemirror/state";
2757
- import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
2758
- import { EditorView as EditorView12, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap6, lineNumbers, placeholder, scrollPastEnd } from "@codemirror/view";
2759
- import defaultsDeep2 from "lodash.defaultsdeep";
2760
- import merge from "lodash.merge";
2761
- import { generateName } from "@dxos/display-name";
2762
- import { log as log5 } from "@dxos/log";
2763
- import { hexToHue, isNotFalsy as isNotFalsy3 } from "@dxos/util";
2764
-
2765
- // packages/ui/react-ui-editor/src/extensions/focus.ts
2766
- import { StateEffect as StateEffect4, StateField as StateField6 } from "@codemirror/state";
2767
- import { EditorView as EditorView11 } from "@codemirror/view";
2768
- var focusEffect = StateEffect4.define();
2769
- var focusField = StateField6.define({
2770
- create: () => false,
2771
- update: (value, tr) => {
2772
- for (const effect of tr.effects) {
2773
- if (effect.is(focusEffect)) {
2774
- return effect.value;
2775
- }
2776
- }
2777
- return value;
2778
- }
2779
- });
2780
- var focus = [
2781
- focusField,
2782
- EditorView11.domEventHandlers({
2783
- focus: (event, view) => {
2784
- setTimeout(() => view.dispatch({
2785
- effects: focusEffect.of(true)
2786
- }));
2787
- },
2788
- blur: (event, view) => {
2789
- setTimeout(() => view.dispatch({
2790
- effects: focusEffect.of(false)
2791
- }));
2792
- }
2793
- })
2794
- ];
2795
-
2796
2446
  // packages/ui/react-ui-editor/src/extensions/factories.ts
2797
2447
  var __dxlog_file8 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/factories.ts";
2798
2448
  var preventNewline = EditorState.transactionFilter.of((tr) => tr.newDoc.lines > 1 ? [] : tr);
@@ -2817,7 +2467,7 @@ var createBasicExtensions = (_props) => {
2817
2467
  const props = defaultsDeep2({}, _props, defaultBasicOptions);
2818
2468
  return [
2819
2469
  // NOTE: Doesn't catch errors in keymap functions.
2820
- EditorView12.exceptionSink.of((err) => {
2470
+ EditorView11.exceptionSink.of((err) => {
2821
2471
  log5.catch(err, void 0, {
2822
2472
  F: __dxlog_file8,
2823
2473
  L: 96,
@@ -2832,12 +2482,12 @@ var createBasicExtensions = (_props) => {
2832
2482
  props.drawSelection && drawSelection({
2833
2483
  cursorBlinkRate: 1200
2834
2484
  }),
2835
- props.editable !== void 0 && EditorView12.editable.of(props.editable),
2485
+ props.editable !== void 0 && EditorView11.editable.of(props.editable),
2836
2486
  props.focus && focus,
2837
2487
  props.highlightActiveLine && highlightActiveLine(),
2838
2488
  props.history && history(),
2839
2489
  props.lineNumbers && lineNumbers(),
2840
- props.lineWrapping && EditorView12.lineWrapping,
2490
+ props.lineWrapping && EditorView11.lineWrapping,
2841
2491
  props.placeholder && placeholder(props.placeholder),
2842
2492
  props.readOnly !== void 0 && EditorState.readOnly.of(props.readOnly),
2843
2493
  props.scrollPastEnd && scrollPastEnd(),
@@ -2874,14 +2524,14 @@ var defaultThemeSlots = {
2874
2524
  var createThemeExtensions = ({ themeMode, styles: styles5, syntaxHighlighting: _syntaxHighlighting, slots: _slots } = {}) => {
2875
2525
  const slots = defaultsDeep2({}, _slots, defaultThemeSlots);
2876
2526
  return [
2877
- EditorView12.darkTheme.of(themeMode === "dark"),
2878
- EditorView12.baseTheme(styles5 ? merge({}, defaultTheme, styles5) : defaultTheme),
2527
+ EditorView11.darkTheme.of(themeMode === "dark"),
2528
+ EditorView11.baseTheme(styles5 ? merge({}, defaultTheme, styles5) : defaultTheme),
2879
2529
  // https://github.com/codemirror/theme-one-dark
2880
2530
  _syntaxHighlighting && (themeMode === "dark" ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)),
2881
- slots.editor?.className && EditorView12.editorAttributes.of({
2531
+ slots.editor?.className && EditorView11.editorAttributes.of({
2882
2532
  class: slots.editor.className
2883
2533
  }),
2884
- slots.content?.className && EditorView12.contentAttributes.of({
2534
+ slots.content?.className && EditorView11.contentAttributes.of({
2885
2535
  class: slots.content.className
2886
2536
  })
2887
2537
  ].filter(isNotFalsy3);
@@ -2910,8 +2560,8 @@ var createDataExtensions = ({ id, text, space, identity }) => {
2910
2560
 
2911
2561
  // packages/ui/react-ui-editor/src/extensions/folding.tsx
2912
2562
  import { codeFolding, foldGutter } from "@codemirror/language";
2913
- import { EditorView as EditorView13 } from "@codemirror/view";
2914
- import React3 from "react";
2563
+ import { EditorView as EditorView12 } from "@codemirror/view";
2564
+ import React2 from "react";
2915
2565
  import { Icon } from "@dxos/react-ui";
2916
2566
  var folding = (_props = {}) => [
2917
2567
  codeFolding({
@@ -2924,7 +2574,7 @@ var folding = (_props = {}) => [
2924
2574
  const el = createElement("div", {
2925
2575
  className: "flex h-full items-center"
2926
2576
  });
2927
- return renderRoot(el, /* @__PURE__ */ React3.createElement(Icon, {
2577
+ return renderRoot(el, /* @__PURE__ */ React2.createElement(Icon, {
2928
2578
  icon: "ph--caret-right--bold",
2929
2579
  size: 3,
2930
2580
  classNames: [
@@ -2934,7 +2584,7 @@ var folding = (_props = {}) => [
2934
2584
  }));
2935
2585
  }
2936
2586
  }),
2937
- EditorView13.theme({
2587
+ EditorView12.theme({
2938
2588
  ".cm-foldGutter": {
2939
2589
  opacity: 0.3,
2940
2590
  transition: "opacity 0.3s",
@@ -2947,14 +2597,14 @@ var folding = (_props = {}) => [
2947
2597
  ];
2948
2598
 
2949
2599
  // packages/ui/react-ui-editor/src/extensions/listener.ts
2950
- import { EditorView as EditorView14 } from "@codemirror/view";
2600
+ import { EditorView as EditorView13 } from "@codemirror/view";
2951
2601
  var listener = ({ onFocus, onChange }) => {
2952
2602
  const extensions = [];
2953
- onFocus && extensions.push(EditorView14.focusChangeEffect.of((_, focusing) => {
2603
+ onFocus && extensions.push(EditorView13.focusChangeEffect.of((_, focusing) => {
2954
2604
  onFocus(focusing);
2955
2605
  return null;
2956
2606
  }));
2957
- onChange && extensions.push(EditorView14.updateListener.of((update2) => {
2607
+ onChange && extensions.push(EditorView13.updateListener.of((update2) => {
2958
2608
  onChange(update2.state.doc.toString(), update2.state.facet(documentId));
2959
2609
  }));
2960
2610
  return extensions;
@@ -2964,7 +2614,7 @@ var listener = ({ onFocus, onChange }) => {
2964
2614
  import { snippet } from "@codemirror/autocomplete";
2965
2615
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
2966
2616
  import { EditorSelection } from "@codemirror/state";
2967
- import { EditorView as EditorView15, keymap as keymap7 } from "@codemirror/view";
2617
+ import { EditorView as EditorView14, keymap as keymap7 } from "@codemirror/view";
2968
2618
  import { useMemo as useMemo3 } from "react";
2969
2619
  var formattingEquals = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockQuote === b.blockQuote;
2970
2620
  var Inline;
@@ -4053,7 +3703,7 @@ var getFormatting = (state) => {
4053
3703
  };
4054
3704
  };
4055
3705
  var useFormattingState = (state) => {
4056
- return useMemo3(() => EditorView15.updateListener.of((update2) => {
3706
+ return useMemo3(() => EditorView14.updateListener.of((update2) => {
4057
3707
  if (update2.docChanged || update2.selectionSet) {
4058
3708
  Object.entries(getFormatting(update2.state)).forEach(([key, active]) => {
4059
3709
  state[key] = active;
@@ -4101,7 +3751,7 @@ var processEditorPayload = (view, { type, data }) => {
4101
3751
  })(view);
4102
3752
  break;
4103
3753
  case "comment":
4104
- createComment2(view);
3754
+ createComment(view);
4105
3755
  break;
4106
3756
  }
4107
3757
  requestAnimationFrame(() => {
@@ -4365,9 +4015,9 @@ var convertTreeToJson = (state) => {
4365
4015
  // packages/ui/react-ui-editor/src/extensions/markdown/decorate.ts
4366
4016
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
4367
4017
  import { RangeSetBuilder as RangeSetBuilder3, StateEffect as StateEffect5 } from "@codemirror/state";
4368
- import { EditorView as EditorView19, Decoration as Decoration7, WidgetType as WidgetType5, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
4018
+ import { EditorView as EditorView18, Decoration as Decoration7, WidgetType as WidgetType5, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
4369
4019
  import { invariant as invariant4 } from "@dxos/invariant";
4370
- import { mx as mx3 } from "@dxos/react-ui-theme";
4020
+ import { mx as mx2 } from "@dxos/react-ui-theme";
4371
4021
 
4372
4022
  // packages/ui/react-ui-editor/src/extensions/markdown/changes.ts
4373
4023
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
@@ -4516,7 +4166,7 @@ var getValidUrl = (str) => {
4516
4166
  // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
4517
4167
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
4518
4168
  import { StateField as StateField8 } from "@codemirror/state";
4519
- import { Decoration as Decoration5, EditorView as EditorView16, WidgetType as WidgetType3 } from "@codemirror/view";
4169
+ import { Decoration as Decoration5, EditorView as EditorView15, WidgetType as WidgetType3 } from "@codemirror/view";
4520
4170
  var image = (_options = {}) => {
4521
4171
  return [
4522
4172
  StateField8.define({
@@ -4544,7 +4194,7 @@ var image = (_options = {}) => {
4544
4194
  add: buildDecorations(from, to, tr.state)
4545
4195
  });
4546
4196
  },
4547
- provide: (field) => EditorView16.decorations.from(field)
4197
+ provide: (field) => EditorView15.decorations.from(field)
4548
4198
  })
4549
4199
  ];
4550
4200
  };
@@ -4604,10 +4254,10 @@ var ImageWidget = class extends WidgetType3 {
4604
4254
  };
4605
4255
 
4606
4256
  // packages/ui/react-ui-editor/src/extensions/markdown/styles.ts
4607
- import { EditorView as EditorView17 } from "@codemirror/view";
4257
+ import { EditorView as EditorView16 } from "@codemirror/view";
4608
4258
  var bulletListIndentationWidth = 24;
4609
4259
  var orderedListIndentationWidth = 36;
4610
- var formattingStyles = EditorView17.theme({
4260
+ var formattingStyles = EditorView16.theme({
4611
4261
  /**
4612
4262
  * Horizontal rule.
4613
4263
  */
@@ -4728,12 +4378,12 @@ var formattingStyles = EditorView17.theme({
4728
4378
  // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
4729
4379
  import { syntaxTree as syntaxTree6 } from "@codemirror/language";
4730
4380
  import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField9 } from "@codemirror/state";
4731
- import { Decoration as Decoration6, EditorView as EditorView18, WidgetType as WidgetType4 } from "@codemirror/view";
4381
+ import { Decoration as Decoration6, EditorView as EditorView17, WidgetType as WidgetType4 } from "@codemirror/view";
4732
4382
  var table = (options = {}) => {
4733
4383
  return StateField9.define({
4734
4384
  create: (state) => update(state, options),
4735
4385
  update: (_, tr) => update(tr.state, options),
4736
- provide: (field) => EditorView18.decorations.from(field)
4386
+ provide: (field) => EditorView17.decorations.from(field)
4737
4387
  });
4738
4388
  };
4739
4389
  var update = (state, _options) => {
@@ -4919,16 +4569,16 @@ var TextWidget = class extends WidgetType5 {
4919
4569
  };
4920
4570
  var hide = Decoration7.replace({});
4921
4571
  var blockQuote = Decoration7.line({
4922
- class: mx3("cm-blockquote")
4572
+ class: mx2("cm-blockquote")
4923
4573
  });
4924
4574
  var fencedCodeLine = Decoration7.line({
4925
- class: mx3("cm-code cm-codeblock-line")
4575
+ class: mx2("cm-code cm-codeblock-line")
4926
4576
  });
4927
4577
  var fencedCodeLineFirst = Decoration7.line({
4928
- class: mx3("cm-code cm-codeblock-line", "cm-codeblock-first")
4578
+ class: mx2("cm-code cm-codeblock-line", "cm-codeblock-first")
4929
4579
  });
4930
4580
  var fencedCodeLineLast = Decoration7.line({
4931
- class: mx3("cm-code cm-codeblock-line", "cm-codeblock-last")
4581
+ class: mx2("cm-code cm-codeblock-line", "cm-codeblock-last")
4932
4582
  });
4933
4583
  var commentBlockLine = fencedCodeLine;
4934
4584
  var commentBlockLineFirst = fencedCodeLineFirst;
@@ -5030,615 +4680,1034 @@ var buildDecorations2 = (view, options, focus2) => {
5030
4680
  }
5031
4681
  }
5032
4682
  const editing = editingRange(state, node, focus2);
5033
- if (editing) {
5034
- break;
4683
+ if (editing) {
4684
+ break;
4685
+ }
4686
+ const mark = node.node.firstChild;
4687
+ if (mark?.name === "HeaderMark") {
4688
+ const { from, to = 6 } = options.numberedHeadings ?? {};
4689
+ const text = view.state.sliceDoc(node.from, node.to);
4690
+ const len = text.match(/[#\s]+/)[0].length;
4691
+ if (!from || level < from || level > to) {
4692
+ atomicDeco.add(mark.from, mark.from + len, hide);
4693
+ } else {
4694
+ const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + " ";
4695
+ if (num.length) {
4696
+ atomicDeco.add(mark.from, mark.from + len, Decoration7.replace({
4697
+ widget: new TextWidget(num, theme.heading(level))
4698
+ }));
4699
+ }
4700
+ }
4701
+ }
4702
+ return false;
4703
+ }
4704
+ //
4705
+ // Lists.
4706
+ // [BulletList | OrderedList] > (ListItem > ListMark) > (Task > TaskMarker)?
4707
+ //
4708
+ case "BulletList":
4709
+ case "OrderedList": {
4710
+ enterList(node);
4711
+ break;
4712
+ }
4713
+ case "ListItem": {
4714
+ const line = state.doc.lineAt(node.from);
4715
+ const list = getCurrentListLevel();
4716
+ const width = list.type === "OrderedList" ? orderedListIndentationWidth : bulletListIndentationWidth;
4717
+ const offset = ((list.level ?? 0) + 1) * width;
4718
+ if (node.from === line.to - 1) {
4719
+ return false;
4720
+ }
4721
+ deco.add(line.from, line.from, Decoration7.line({
4722
+ class: "cm-list-item",
4723
+ attributes: {
4724
+ style: `padding-left: ${offset}px; text-indent: -${width}px;`
4725
+ }
4726
+ }));
4727
+ break;
4728
+ }
4729
+ case "ListMark": {
4730
+ const list = getCurrentListLevel();
4731
+ const next = tree.resolve(node.to + 1, 1);
4732
+ if (next?.name === "TaskMarker") {
4733
+ break;
4734
+ }
4735
+ const label = list.type === "OrderedList" ? `${++list.number}.` : Unicode.bulletSmall;
4736
+ const line = state.doc.lineAt(node.from);
4737
+ const to = state.doc.sliceString(node.to, node.to + 1) === " " ? node.to + 1 : node.to;
4738
+ atomicDeco.add(line.from, to, Decoration7.replace({
4739
+ widget: new TextWidget(label, list.type === "OrderedList" ? "cm-list-mark cm-list-mark-ordered" : "cm-list-mark cm-list-mark-bullet")
4740
+ }));
4741
+ break;
4742
+ }
4743
+ case "TaskMarker": {
4744
+ const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
4745
+ const line = state.doc.lineAt(node.from);
4746
+ const to = state.doc.sliceString(node.to, node.to + 1) === " " ? node.to + 1 : node.to;
4747
+ atomicDeco.add(line.from, to, checked ? checkedTask : uncheckedTask);
4748
+ break;
4749
+ }
4750
+ //
4751
+ // Blockquote > QuoteMark > Paragraph
4752
+ //
4753
+ case "Blockquote": {
4754
+ const editing = editingRange(state, node, focus2);
4755
+ const quoteMark = node.node.getChild("QuoteMark");
4756
+ const paragraph = node.node.getChild("Paragraph");
4757
+ if (!editing && quoteMark && paragraph) {
4758
+ atomicDeco.add(quoteMark.from, paragraph.from, hide);
4759
+ }
4760
+ for (const block of view.viewportLineBlocks) {
4761
+ if (block.to < node.from) {
4762
+ continue;
4763
+ }
4764
+ if (block.from > node.to) {
4765
+ break;
4766
+ }
4767
+ deco.add(block.from, block.from, blockQuote);
4768
+ }
4769
+ break;
4770
+ }
4771
+ //
4772
+ // CommentBlock
4773
+ //
4774
+ case "CommentBlock": {
4775
+ const editing = editingRange(state, node, focus2);
4776
+ for (const block of view.viewportLineBlocks) {
4777
+ if (block.to < node.from) {
4778
+ continue;
4779
+ }
4780
+ if (block.from > node.to) {
4781
+ break;
4782
+ }
4783
+ const isFirst = block.from <= node.from;
4784
+ const isLast = block.to >= node.to && /^(\s>)*-->$/.test(state.doc.sliceString(block.from, block.to));
4785
+ deco.add(block.from, block.from, isFirst ? commentBlockLineFirst : isLast ? commentBlockLineLast : commentBlockLine);
4786
+ if (!editing && (isFirst || isLast)) {
4787
+ atomicDeco.add(block.from, block.to, hide);
4788
+ }
5035
4789
  }
5036
- const mark = node.node.firstChild;
5037
- if (mark?.name === "HeaderMark") {
5038
- const { from, to = 6 } = options.numberedHeadings ?? {};
5039
- const text = view.state.sliceDoc(node.from, node.to);
5040
- const len = text.match(/[#\s]+/)[0].length;
5041
- if (!from || level < from || level > to) {
5042
- atomicDeco.add(mark.from, mark.from + len, hide);
5043
- } else {
5044
- const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + " ";
5045
- if (num.length) {
5046
- atomicDeco.add(mark.from, mark.from + len, Decoration7.replace({
5047
- widget: new TextWidget(num, theme.heading(level))
5048
- }));
5049
- }
4790
+ break;
4791
+ }
4792
+ //
4793
+ // FencedCode > CodeMark > [CodeInfo] > CodeText > CodeMark
4794
+ //
4795
+ case "FencedCode": {
4796
+ for (const block of view.viewportLineBlocks) {
4797
+ if (block.to < node.from) {
4798
+ continue;
4799
+ }
4800
+ if (block.from > node.to) {
4801
+ break;
4802
+ }
4803
+ const first = block.from <= node.from;
4804
+ const last = block.to >= node.to && /^(\s>)*```$/.test(state.doc.sliceString(block.from, block.to));
4805
+ deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
4806
+ const editing = editingRange(state, node, focus2);
4807
+ if (!editing && (first || last)) {
4808
+ atomicDeco.add(block.from, block.to, hide);
5050
4809
  }
5051
4810
  }
5052
4811
  return false;
5053
4812
  }
5054
4813
  //
5055
- // Lists.
5056
- // [BulletList | OrderedList] > (ListItem > ListMark) > (Task > TaskMarker)?
4814
+ // Link > [LinkMark, URL]
4815
+ //
4816
+ case "Link": {
4817
+ const marks = node.node.getChildren("LinkMark");
4818
+ const urlNode = node.node.getChild("URL");
4819
+ const editing = editingRange(state, node, focus2);
4820
+ if (urlNode && marks.length >= 2) {
4821
+ const url = state.sliceDoc(urlNode.from, urlNode.to);
4822
+ if (!editing) {
4823
+ atomicDeco.add(node.from, marks[0].to, hide);
4824
+ }
4825
+ deco.add(marks[0].to, marks[1].from, Decoration7.mark({
4826
+ tagName: "a",
4827
+ attributes: {
4828
+ class: "cm-link",
4829
+ href: url,
4830
+ rel: "noreferrer",
4831
+ target: "_blank"
4832
+ }
4833
+ }));
4834
+ if (!editing) {
4835
+ atomicDeco.add(marks[1].from, node.to, options.renderLinkButton ? Decoration7.replace({
4836
+ widget: new LinkButton(url, options.renderLinkButton)
4837
+ }) : hide);
4838
+ }
4839
+ }
4840
+ break;
4841
+ }
4842
+ //
4843
+ // HR
5057
4844
  //
4845
+ case "HorizontalRule": {
4846
+ if (!editingRange(state, node, focus2)) {
4847
+ deco.add(node.from, node.to, horizontalRule);
4848
+ }
4849
+ break;
4850
+ }
4851
+ default: {
4852
+ if (autoHideTags.has(node.name)) {
4853
+ if (!editingRange(state, node.node.parent, focus2)) {
4854
+ atomicDeco.add(node.from, node.to, hide);
4855
+ }
4856
+ }
4857
+ }
4858
+ }
4859
+ };
4860
+ const leaveNode = (node) => {
4861
+ switch (node.name) {
5058
4862
  case "BulletList":
5059
4863
  case "OrderedList": {
5060
- enterList(node);
4864
+ leaveList();
5061
4865
  break;
5062
4866
  }
5063
- case "ListItem": {
5064
- const line = state.doc.lineAt(node.from);
5065
- const list = getCurrentListLevel();
5066
- const width = list.type === "OrderedList" ? orderedListIndentationWidth : bulletListIndentationWidth;
5067
- const offset = ((list.level ?? 0) + 1) * width;
5068
- if (node.from === line.to - 1) {
5069
- return false;
4867
+ }
4868
+ };
4869
+ const tree = syntaxTree7(state);
4870
+ if (options.numberedHeadings?.from === void 0) {
4871
+ for (const { from, to } of view.visibleRanges) {
4872
+ tree.iterate({
4873
+ from,
4874
+ to,
4875
+ enter: wrapWithCatch(enterNode),
4876
+ leave: wrapWithCatch(leaveNode)
4877
+ });
4878
+ }
4879
+ } else {
4880
+ tree.iterate({
4881
+ enter: wrapWithCatch(enterNode),
4882
+ leave: wrapWithCatch(leaveNode)
4883
+ });
4884
+ }
4885
+ return {
4886
+ deco: deco.finish(),
4887
+ atomicDeco: atomicDeco.finish()
4888
+ };
4889
+ };
4890
+ var forceUpdate = StateEffect5.define();
4891
+ var decorateMarkdown = (options = {}) => {
4892
+ return [
4893
+ ViewPlugin7.fromClass(class {
4894
+ constructor(view) {
4895
+ ({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations2(view, options, view.hasFocus));
4896
+ }
4897
+ update(update2) {
4898
+ if (update2.docChanged || update2.viewportChanged || update2.focusChanged || update2.transactions.some((tr) => tr.effects.some((effect) => effect.is(forceUpdate))) || update2.selectionSet && !options.selectionChangeDelay) {
4899
+ ({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations2(update2.view, options, update2.view.hasFocus));
4900
+ this.clearUpdate();
4901
+ } else if (update2.selectionSet) {
4902
+ this.scheduleUpdate(update2.view);
5070
4903
  }
5071
- deco.add(line.from, line.from, Decoration7.line({
5072
- class: "cm-list-item",
5073
- attributes: {
5074
- style: `padding-left: ${offset}px; text-indent: -${width}px;`
4904
+ }
4905
+ // Defer update in case moving through the document.
4906
+ scheduleUpdate(view) {
4907
+ this.clearUpdate();
4908
+ this.pendingUpdate = setTimeout(() => {
4909
+ view.dispatch({
4910
+ effects: forceUpdate.of(null)
4911
+ });
4912
+ }, options.selectionChangeDelay);
4913
+ }
4914
+ clearUpdate() {
4915
+ if (this.pendingUpdate) {
4916
+ clearTimeout(this.pendingUpdate);
4917
+ this.pendingUpdate = void 0;
4918
+ }
4919
+ }
4920
+ destroy() {
4921
+ this.clearUpdate();
4922
+ }
4923
+ }, {
4924
+ provide: (plugin) => [
4925
+ EditorView18.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration7.none),
4926
+ EditorView18.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration7.none),
4927
+ EditorView18.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration7.none)
4928
+ ]
4929
+ }),
4930
+ image(),
4931
+ table(),
4932
+ adjustChanges(),
4933
+ formattingStyles
4934
+ ];
4935
+ };
4936
+
4937
+ // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
4938
+ import { syntaxTree as syntaxTree8 } from "@codemirror/language";
4939
+ import { hoverTooltip as hoverTooltip2 } from "@codemirror/view";
4940
+ import { tooltipContent } from "@dxos/react-ui-theme";
4941
+ var linkTooltip = (renderTooltip) => {
4942
+ return hoverTooltip2((view, pos, side) => {
4943
+ const syntax = syntaxTree8(view.state).resolveInner(pos, side);
4944
+ let link = null;
4945
+ for (let i = 0, node = syntax; !link && node && i < 5; node = node.parent, i++) {
4946
+ link = node.name === "Link" ? node : null;
4947
+ }
4948
+ const url = link && link.getChild("URL");
4949
+ if (!url || !link) {
4950
+ return null;
4951
+ }
4952
+ const urlText = view.state.sliceDoc(url.from, url.to);
4953
+ return {
4954
+ pos: link.from,
4955
+ end: link.to,
4956
+ // NOTE: Forcing above causes the tooltip to flicker.
4957
+ // above: true,
4958
+ create: () => {
4959
+ const el = document.createElement("div");
4960
+ el.className = tooltipContent({});
4961
+ renderTooltip(el, {
4962
+ url: urlText
4963
+ }, view);
4964
+ return {
4965
+ dom: el,
4966
+ offset: {
4967
+ x: 0,
4968
+ y: 4
5075
4969
  }
5076
- }));
5077
- break;
4970
+ };
5078
4971
  }
5079
- case "ListMark": {
5080
- const list = getCurrentListLevel();
5081
- const next = tree.resolve(node.to + 1, 1);
5082
- if (next?.name === "TaskMarker") {
5083
- break;
4972
+ };
4973
+ }, {
4974
+ // NOTE: 0 = default of 300ms.
4975
+ hoverTime: 1
4976
+ });
4977
+ };
4978
+
4979
+ // packages/ui/react-ui-editor/src/extensions/mention.ts
4980
+ import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
4981
+ import { log as log6 } from "@dxos/log";
4982
+ var __dxlog_file10 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/mention.ts";
4983
+ var mention = ({ debug, onSearch }) => {
4984
+ return autocompletion2({
4985
+ // TODO(burdon): Not working.
4986
+ activateOnTyping: true,
4987
+ // activateOnTypingDelay: 100,
4988
+ // selectOnOpen: true,
4989
+ closeOnBlur: !debug,
4990
+ // defaultKeymap: false,
4991
+ icons: false,
4992
+ override: [
4993
+ (context) => {
4994
+ log6.info("completion context", {
4995
+ context
4996
+ }, {
4997
+ F: __dxlog_file10,
4998
+ L: 27,
4999
+ S: void 0,
5000
+ C: (f, a) => f(...a)
5001
+ });
5002
+ const match = context.matchBefore(/@(\w+)?/);
5003
+ if (!match || match.from === match.to && !context.explicit) {
5004
+ return null;
5084
5005
  }
5085
- const label = list.type === "OrderedList" ? `${++list.number}.` : Unicode.bulletSmall;
5086
- const line = state.doc.lineAt(node.from);
5087
- const to = state.doc.sliceString(node.to, node.to + 1) === " " ? node.to + 1 : node.to;
5088
- atomicDeco.add(line.from, to, Decoration7.replace({
5089
- widget: new TextWidget(label, list.type === "OrderedList" ? "cm-list-mark cm-list-mark-ordered" : "cm-list-mark cm-list-mark-bullet")
5090
- }));
5091
- break;
5092
- }
5093
- case "TaskMarker": {
5094
- const checked = state.doc.sliceString(node.from + 1, node.to - 1) === "x";
5095
- const line = state.doc.lineAt(node.from);
5096
- const to = state.doc.sliceString(node.to, node.to + 1) === " " ? node.to + 1 : node.to;
5097
- atomicDeco.add(line.from, to, checked ? checkedTask : uncheckedTask);
5098
- break;
5006
+ return {
5007
+ from: match.from,
5008
+ options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
5009
+ label: `@${value}`
5010
+ }))
5011
+ };
5099
5012
  }
5100
- //
5101
- // Blockquote > QuoteMark > Paragraph
5102
- //
5103
- case "Blockquote": {
5104
- const editing = editingRange(state, node, focus2);
5105
- const quoteMark = node.node.getChild("QuoteMark");
5106
- const paragraph = node.node.getChild("Paragraph");
5107
- if (!editing && quoteMark && paragraph) {
5108
- atomicDeco.add(quoteMark.from, paragraph.from, hide);
5109
- }
5110
- for (const block of view.viewportLineBlocks) {
5111
- if (block.to < node.from) {
5112
- continue;
5113
- }
5114
- if (block.from > node.to) {
5115
- break;
5116
- }
5117
- deco.add(block.from, block.from, blockQuote);
5013
+ ]
5014
+ });
5015
+ };
5016
+
5017
+ // packages/ui/react-ui-editor/src/extensions/modes.ts
5018
+ import { keymap as keymap9 } from "@codemirror/view";
5019
+ import { vim } from "@replit/codemirror-vim";
5020
+ import { vscodeKeymap } from "@replit/codemirror-vscode-keymap";
5021
+ import { Schema } from "effect";
5022
+ var EditorViewModes = [
5023
+ "preview",
5024
+ "readonly",
5025
+ "source"
5026
+ ];
5027
+ var EditorViewMode = Schema.Union(...EditorViewModes.map((mode) => Schema.Literal(mode)));
5028
+ var EditorInputModes = [
5029
+ "default",
5030
+ "vim",
5031
+ "vscode"
5032
+ ];
5033
+ var EditorInputMode = Schema.Union(...EditorInputModes.map((mode) => Schema.Literal(mode)));
5034
+ var editorInputMode = singleValueFacet({});
5035
+ var InputModeExtensions = {
5036
+ default: [],
5037
+ vscode: [
5038
+ // https://github.com/replit/codemirror-vscode-keymap
5039
+ editorInputMode.of({
5040
+ type: "vscode"
5041
+ }),
5042
+ keymap9.of(vscodeKeymap)
5043
+ ],
5044
+ vim: [
5045
+ // https://github.com/replit/codemirror-vim
5046
+ vim(),
5047
+ editorInputMode.of({
5048
+ type: "vim",
5049
+ noTabster: true
5050
+ }),
5051
+ keymap9.of([
5052
+ {
5053
+ key: "Alt-Escape",
5054
+ run: (view) => {
5055
+ view.dom.parentElement?.focus();
5056
+ return true;
5118
5057
  }
5119
- break;
5120
5058
  }
5121
- //
5122
- // CommentBlock
5123
- //
5124
- case "CommentBlock": {
5125
- const editing = editingRange(state, node, focus2);
5126
- for (const block of view.viewportLineBlocks) {
5127
- if (block.to < node.from) {
5128
- continue;
5129
- }
5130
- if (block.from > node.to) {
5131
- break;
5132
- }
5133
- const isFirst = block.from <= node.from;
5134
- const isLast = block.to >= node.to && /^(\s>)*-->$/.test(state.doc.sliceString(block.from, block.to));
5135
- deco.add(block.from, block.from, isFirst ? commentBlockLineFirst : isLast ? commentBlockLineLast : commentBlockLine);
5136
- if (!editing && (isFirst || isLast)) {
5137
- atomicDeco.add(block.from, block.to, hide);
5138
- }
5139
- }
5140
- break;
5059
+ ])
5060
+ ]
5061
+ };
5062
+
5063
+ // packages/ui/react-ui-editor/src/extensions/preview/preview.ts
5064
+ import "@dxos/lit-ui/dx-ref-tag.pcss";
5065
+ import { syntaxTree as syntaxTree9 } from "@codemirror/language";
5066
+ import { RangeSetBuilder as RangeSetBuilder4, StateField as StateField10 } from "@codemirror/state";
5067
+ import { Decoration as Decoration8, EditorView as EditorView19, WidgetType as WidgetType6 } from "@codemirror/view";
5068
+ var preview = (options = {}) => {
5069
+ return [
5070
+ // NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
5071
+ // "Block decorations may not be specified via plugins"
5072
+ StateField10.define({
5073
+ create: (state) => buildDecorations3(state, options),
5074
+ update: (_, tr) => buildDecorations3(tr.state, options),
5075
+ provide: (field) => [
5076
+ EditorView19.decorations.from(field),
5077
+ EditorView19.atomicRanges.of((view) => view.state.field(field))
5078
+ ]
5079
+ }),
5080
+ EditorView19.theme({
5081
+ ".cm-preview-block": {
5082
+ marginLeft: "-1rem",
5083
+ marginRight: "-1rem",
5084
+ padding: "1rem",
5085
+ borderRadius: "0.5rem",
5086
+ background: "var(--dx-modalSurface)",
5087
+ border: "1px solid var(--dx-separator)"
5141
5088
  }
5142
- //
5143
- // FencedCode > CodeMark > [CodeInfo] > CodeText > CodeMark
5144
- //
5145
- case "FencedCode": {
5146
- for (const block of view.viewportLineBlocks) {
5147
- if (block.to < node.from) {
5148
- continue;
5149
- }
5150
- if (block.from > node.to) {
5151
- break;
5152
- }
5153
- const first = block.from <= node.from;
5154
- const last = block.to >= node.to && /^(\s>)*```$/.test(state.doc.sliceString(block.from, block.to));
5155
- deco.add(block.from, block.from, first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine);
5156
- const editing = editingRange(state, node, focus2);
5157
- if (!editing && (first || last)) {
5158
- atomicDeco.add(block.from, block.to, hide);
5089
+ })
5090
+ ];
5091
+ };
5092
+ var getLinkRef = (state, node) => {
5093
+ const mark = node.getChild("LinkMark");
5094
+ const label = node.getChild("LinkLabel");
5095
+ if (mark && label) {
5096
+ const ref = state.sliceDoc(label.from + 1, label.to - 1);
5097
+ return {
5098
+ suggest: ref.startsWith("?"),
5099
+ block: state.sliceDoc(mark.from, mark.from + 1) === "!",
5100
+ label: state.sliceDoc(mark.to, label.from - 1),
5101
+ ref: ref.startsWith("?") ? ref.slice(1) : ref
5102
+ };
5103
+ }
5104
+ };
5105
+ var buildDecorations3 = (state, options) => {
5106
+ const builder = new RangeSetBuilder4();
5107
+ syntaxTree9(state).iterate({
5108
+ enter: (node) => {
5109
+ switch (node.name) {
5110
+ //
5111
+ // Decoration.
5112
+ // [Label][dxn:echo:123]
5113
+ //
5114
+ case "Link": {
5115
+ const link = getLinkRef(state, node.node);
5116
+ if (link) {
5117
+ builder.add(node.from, node.to, Decoration8.replace({
5118
+ widget: new PreviewInlineWidget(options, link)
5119
+ }));
5159
5120
  }
5121
+ break;
5160
5122
  }
5161
- return false;
5162
- }
5163
- //
5164
- // Link > [LinkMark, URL]
5165
- //
5166
- case "Link": {
5167
- const marks = node.node.getChildren("LinkMark");
5168
- const urlNode = node.node.getChild("URL");
5169
- const editing = editingRange(state, node, focus2);
5170
- if (urlNode && marks.length >= 2) {
5171
- const url = state.sliceDoc(urlNode.from, urlNode.to);
5172
- if (!editing) {
5173
- atomicDeco.add(node.from, marks[0].to, hide);
5174
- }
5175
- deco.add(marks[0].to, marks[1].from, Decoration7.mark({
5176
- tagName: "a",
5177
- attributes: {
5178
- class: "cm-link",
5179
- href: url,
5180
- rel: "noreferrer",
5181
- target: "_blank"
5182
- }
5183
- }));
5184
- if (!editing) {
5185
- atomicDeco.add(marks[1].from, node.to, options.renderLinkButton ? Decoration7.replace({
5186
- widget: new LinkButton(url, options.renderLinkButton)
5187
- }) : hide);
5123
+ //
5124
+ // Block widget.
5125
+ // ![Label][dxn:echo:123]
5126
+ //
5127
+ case "Image": {
5128
+ const link = getLinkRef(state, node.node);
5129
+ if (options.renderBlock && link) {
5130
+ builder.add(node.from, node.to, Decoration8.replace({
5131
+ block: true,
5132
+ // atomic: true,
5133
+ widget: new PreviewBlockWidget(options, link)
5134
+ }));
5188
5135
  }
5136
+ break;
5189
5137
  }
5190
- break;
5191
5138
  }
5192
- //
5193
- // HR
5194
- //
5195
- case "HorizontalRule": {
5196
- if (!editingRange(state, node, focus2)) {
5197
- deco.add(node.from, node.to, horizontalRule);
5198
- }
5199
- break;
5139
+ }
5140
+ });
5141
+ return builder.finish();
5142
+ };
5143
+ var PreviewInlineWidget = class extends WidgetType6 {
5144
+ constructor(_options, _link) {
5145
+ super();
5146
+ this._options = _options;
5147
+ this._link = _link;
5148
+ }
5149
+ // override ignoreEvent() {
5150
+ // return false;
5151
+ // }
5152
+ eq(other) {
5153
+ return this._link.ref === other._link.ref && this._link.label === other._link.label;
5154
+ }
5155
+ toDOM(view) {
5156
+ const root = document.createElement("dx-ref-tag");
5157
+ root.textContent = this._link.label;
5158
+ root.setAttribute("ref", this._link.ref);
5159
+ return root;
5160
+ }
5161
+ };
5162
+ var PreviewBlockWidget = class extends WidgetType6 {
5163
+ constructor(_options, _link) {
5164
+ super();
5165
+ this._options = _options;
5166
+ this._link = _link;
5167
+ }
5168
+ // override ignoreEvent() {
5169
+ // return true;
5170
+ // }
5171
+ eq(other) {
5172
+ return this._link.ref === other._link.ref;
5173
+ }
5174
+ toDOM(view) {
5175
+ const root = document.createElement("div");
5176
+ root.classList.add("cm-preview-block");
5177
+ const handleAction = (action) => {
5178
+ const pos = view.posAtDOM(root);
5179
+ const node = syntaxTree9(view.state).resolve(pos + 1).node.parent;
5180
+ if (!node) {
5181
+ return;
5182
+ }
5183
+ const link = getLinkRef(view.state, node);
5184
+ if (link?.ref !== action.link.ref) {
5185
+ return;
5200
5186
  }
5201
- default: {
5202
- if (autoHideTags.has(node.name)) {
5203
- if (!editingRange(state, node.node.parent, focus2)) {
5204
- atomicDeco.add(node.from, node.to, hide);
5205
- }
5187
+ switch (action.type) {
5188
+ // TODO(burdon): Should we dispatch to the view or mutate the document? (i.e., handle externally?)
5189
+ // Insert ref text.
5190
+ case "insert": {
5191
+ view.dispatch({
5192
+ changes: {
5193
+ from: node.from,
5194
+ to: node.to,
5195
+ insert: action.target.text
5196
+ }
5197
+ });
5198
+ break;
5199
+ }
5200
+ // Remove ref.
5201
+ case "delete": {
5202
+ view.dispatch({
5203
+ changes: {
5204
+ from: node.from,
5205
+ to: node.to
5206
+ }
5207
+ });
5208
+ break;
5206
5209
  }
5207
5210
  }
5208
- }
5209
- };
5210
- const leaveNode = (node) => {
5211
- switch (node.name) {
5212
- case "BulletList":
5213
- case "OrderedList": {
5214
- leaveList();
5215
- break;
5216
- }
5217
- }
5218
- };
5219
- const tree = syntaxTree7(state);
5220
- if (options.numberedHeadings?.from === void 0) {
5221
- for (const { from, to } of view.visibleRanges) {
5222
- tree.iterate({
5223
- from,
5224
- to,
5225
- enter: wrapWithCatch(enterNode),
5226
- leave: wrapWithCatch(leaveNode)
5227
- });
5228
- }
5229
- } else {
5230
- tree.iterate({
5231
- enter: wrapWithCatch(enterNode),
5232
- leave: wrapWithCatch(leaveNode)
5233
- });
5211
+ };
5212
+ this._options.renderBlock(root, {
5213
+ readonly: view.state.readOnly,
5214
+ link: this._link,
5215
+ onAction: handleAction,
5216
+ onLookup: this._options.onLookup
5217
+ }, view);
5218
+ return root;
5234
5219
  }
5235
- return {
5236
- deco: deco.finish(),
5237
- atomicDeco: atomicDeco.finish()
5238
- };
5239
5220
  };
5240
- var forceUpdate = StateEffect5.define();
5241
- var decorateMarkdown = (options = {}) => {
5221
+
5222
+ // packages/ui/react-ui-editor/src/extensions/typewriter.ts
5223
+ import { keymap as keymap10 } from "@codemirror/view";
5224
+ var defaultItems = [
5225
+ "hello world!",
5226
+ "this is a test.",
5227
+ "this is [DXOS](https://dxos.org)"
5228
+ ];
5229
+ var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
5230
+ let t;
5231
+ let idx = 0;
5242
5232
  return [
5243
- ViewPlugin7.fromClass(class {
5244
- constructor(view) {
5245
- ({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations2(view, options, view.hasFocus));
5246
- }
5247
- update(update2) {
5248
- if (update2.docChanged || update2.viewportChanged || update2.focusChanged || update2.transactions.some((tr) => tr.effects.some((effect) => effect.is(forceUpdate))) || update2.selectionSet && !options.selectionChangeDelay) {
5249
- ({ deco: this.deco, atomicDeco: this.atomicDeco } = buildDecorations2(update2.view, options, update2.view.hasFocus));
5250
- this.clearUpdate();
5251
- } else if (update2.selectionSet) {
5252
- this.scheduleUpdate(update2.view);
5233
+ keymap10.of([
5234
+ {
5235
+ // Reset.
5236
+ key: "alt-meta-'",
5237
+ run: (view) => {
5238
+ clearTimeout(t);
5239
+ idx = 0;
5240
+ return true;
5253
5241
  }
5254
- }
5255
- // Defer update in case moving through the document.
5256
- scheduleUpdate(view) {
5257
- this.clearUpdate();
5258
- this.pendingUpdate = setTimeout(() => {
5259
- view.dispatch({
5260
- effects: forceUpdate.of(null)
5261
- });
5262
- }, options.selectionChangeDelay);
5263
- }
5264
- clearUpdate() {
5265
- if (this.pendingUpdate) {
5266
- clearTimeout(this.pendingUpdate);
5267
- this.pendingUpdate = void 0;
5242
+ },
5243
+ {
5244
+ // Next prompt.
5245
+ // TODO(burdon): Press 1-9 to select prompt?
5246
+ key: "shift-meta-'",
5247
+ run: (view) => {
5248
+ clearTimeout(t);
5249
+ const text = items[idx++];
5250
+ if (idx === items?.length) {
5251
+ idx = 0;
5252
+ }
5253
+ let i = 0;
5254
+ const insert = (d = 0) => {
5255
+ t = setTimeout(() => {
5256
+ const pos = view.state.selection.main.head;
5257
+ view.dispatch({
5258
+ changes: {
5259
+ from: pos,
5260
+ insert: text[i++]
5261
+ },
5262
+ selection: {
5263
+ anchor: pos + 1
5264
+ }
5265
+ });
5266
+ if (i < text.length) {
5267
+ insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
5268
+ }
5269
+ }, d);
5270
+ };
5271
+ insert();
5272
+ return true;
5268
5273
  }
5269
5274
  }
5270
- destroy() {
5271
- this.clearUpdate();
5272
- }
5273
- }, {
5274
- provide: (plugin) => [
5275
- EditorView19.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration7.none),
5276
- EditorView19.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration7.none),
5277
- EditorView19.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration7.none)
5278
- ]
5279
- }),
5280
- image(),
5281
- table(),
5282
- adjustChanges(),
5283
- formattingStyles
5275
+ ])
5284
5276
  ];
5285
5277
  };
5286
5278
 
5287
- // packages/ui/react-ui-editor/src/extensions/markdown/link.ts
5288
- import { syntaxTree as syntaxTree8 } from "@codemirror/language";
5289
- import { hoverTooltip as hoverTooltip2 } from "@codemirror/view";
5290
- import { tooltipContent } from "@dxos/react-ui-theme";
5291
- var linkTooltip = (renderTooltip) => {
5292
- return hoverTooltip2((view, pos, side) => {
5293
- const syntax = syntaxTree8(view.state).resolveInner(pos, side);
5294
- let link = null;
5295
- for (let i = 0, node = syntax; !link && node && i < 5; node = node.parent, i++) {
5296
- link = node.name === "Link" ? node : null;
5279
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/blocks.ts
5280
+ var createBlockGroupAction = (value) => createEditorActionGroup("block", {
5281
+ variant: "toggleGroup",
5282
+ selectCardinality: "single",
5283
+ value
5284
+ });
5285
+ var createBlockActions = (value, getView, blankLine) => Object.entries({
5286
+ blockquote: "ph--quotes--regular",
5287
+ codeblock: "ph--code-block--regular",
5288
+ table: "ph--table--regular"
5289
+ }).map(([type, icon]) => {
5290
+ const checked = type === value;
5291
+ return createEditorAction(type, () => {
5292
+ const view = getView();
5293
+ if (!view) {
5294
+ return;
5297
5295
  }
5298
- const url = link && link.getChild("URL");
5299
- if (!url || !link) {
5300
- return null;
5296
+ switch (type) {
5297
+ case "blockquote":
5298
+ checked ? removeBlockquote(view) : addBlockquote(view);
5299
+ break;
5300
+ case "codeblock":
5301
+ checked ? removeCodeblock(view) : addCodeblock(view);
5302
+ break;
5303
+ case "table":
5304
+ insertTable(view);
5305
+ break;
5301
5306
  }
5302
- const urlText = view.state.sliceDoc(url.from, url.to);
5303
- return {
5304
- pos: link.from,
5305
- end: link.to,
5306
- // NOTE: Forcing above causes the tooltip to flicker.
5307
- // above: true,
5308
- create: () => {
5309
- const el = document.createElement("div");
5310
- el.className = tooltipContent({});
5311
- renderTooltip(el, {
5312
- url: urlText
5313
- }, view);
5314
- return {
5315
- dom: el,
5316
- offset: {
5317
- x: 0,
5318
- y: 4
5319
- }
5320
- };
5307
+ }, {
5308
+ checked,
5309
+ ...type === "table" && {
5310
+ disabled: !!blankLine
5311
+ },
5312
+ icon
5313
+ });
5314
+ });
5315
+ var createBlocks = (state, getView) => {
5316
+ const value = state?.blockQuote ? "blockquote" : state.blockType ?? "";
5317
+ const blockGroupAction = createBlockGroupAction(value);
5318
+ const blockActions = createBlockActions(value, getView, state.blankLine);
5319
+ return {
5320
+ nodes: [
5321
+ blockGroupAction,
5322
+ ...blockActions
5323
+ ],
5324
+ edges: [
5325
+ {
5326
+ source: "root",
5327
+ target: "block"
5328
+ },
5329
+ ...blockActions.map(({ id }) => ({
5330
+ source: blockGroupAction.id,
5331
+ target: id
5332
+ }))
5333
+ ]
5334
+ };
5335
+ };
5336
+
5337
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/comment.ts
5338
+ var commentLabel = (comment, selection) => comment ? "selection overlaps existing comment label" : selection === false ? "select text to comment label" : "comment label";
5339
+ var createCommentAction = (label, getView) => createEditorAction("comment", () => createComment(getView()), {
5340
+ testId: "editor.toolbar.comment",
5341
+ icon: "ph--chat-text--regular",
5342
+ label
5343
+ });
5344
+ var createComment2 = (state, getView) => ({
5345
+ nodes: [
5346
+ createCommentAction([
5347
+ commentLabel(state.comment, state.selection),
5348
+ {
5349
+ ns: translationKey
5321
5350
  }
5322
- };
5351
+ ], getView)
5352
+ ],
5353
+ edges: [
5354
+ {
5355
+ source: "root",
5356
+ target: "comment"
5357
+ }
5358
+ ]
5359
+ });
5360
+
5361
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/formatting.ts
5362
+ var formats = {
5363
+ strong: "ph--text-b--regular",
5364
+ emphasis: "ph--text-italic--regular",
5365
+ strikethrough: "ph--text-strikethrough--regular",
5366
+ code: "ph--code--regular",
5367
+ link: "ph--link--regular"
5368
+ };
5369
+ var createFormattingGroup = (formatting) => createEditorActionGroup("formatting", {
5370
+ variant: "toggleGroup",
5371
+ selectCardinality: "multiple",
5372
+ value: Object.keys(formats).filter((key) => !!formatting[key])
5373
+ });
5374
+ var createFormattingActions = (formatting, getView) => Object.entries(formats).map(([type, icon]) => {
5375
+ const checked = !!formatting[type];
5376
+ return createEditorAction(type, () => {
5377
+ const view = getView();
5378
+ if (!view) {
5379
+ return;
5380
+ }
5381
+ if (type === "link") {
5382
+ checked ? removeLink(view) : addLink()(view);
5383
+ return;
5384
+ }
5385
+ const inlineType = type === "strong" ? Inline.Strong : type === "emphasis" ? Inline.Emphasis : type === "strikethrough" ? Inline.Strikethrough : Inline.Code;
5386
+ setStyle(inlineType, !checked)(view);
5323
5387
  }, {
5324
- // NOTE: 0 = default of 300ms.
5325
- hoverTime: 1
5388
+ checked,
5389
+ icon
5326
5390
  });
5391
+ });
5392
+ var createFormatting = (state, getView) => {
5393
+ const formattingGroupAction = createFormattingGroup(state);
5394
+ const formattingActions = createFormattingActions(state, getView);
5395
+ return {
5396
+ nodes: [
5397
+ formattingGroupAction,
5398
+ ...formattingActions
5399
+ ],
5400
+ edges: [
5401
+ {
5402
+ source: "root",
5403
+ target: "formatting"
5404
+ },
5405
+ ...formattingActions.map(({ id }) => ({
5406
+ source: formattingGroupAction.id,
5407
+ target: id
5408
+ }))
5409
+ ]
5410
+ };
5327
5411
  };
5328
5412
 
5329
- // packages/ui/react-ui-editor/src/extensions/mention.ts
5330
- import { autocompletion as autocompletion2 } from "@codemirror/autocomplete";
5331
- import { log as log6 } from "@dxos/log";
5332
- var __dxlog_file10 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/mention.ts";
5333
- var mention = ({ debug, onSearch }) => {
5334
- return autocompletion2({
5335
- // TODO(burdon): Not working.
5336
- activateOnTyping: true,
5337
- // activateOnTypingDelay: 100,
5338
- // selectOnOpen: true,
5339
- closeOnBlur: !debug,
5340
- // defaultKeymap: false,
5341
- icons: false,
5342
- override: [
5343
- (context) => {
5344
- log6.info("completion context", {
5345
- context
5346
- }, {
5347
- F: __dxlog_file10,
5348
- L: 27,
5349
- S: void 0,
5350
- C: (f, a) => f(...a)
5351
- });
5352
- const match = context.matchBefore(/@(\w+)?/);
5353
- if (!match || match.from === match.to && !context.explicit) {
5354
- return null;
5355
- }
5356
- return {
5357
- from: match.from,
5358
- options: onSearch(match.text.slice(1).toLowerCase()).map((value) => ({
5359
- label: `@${value}`
5360
- }))
5361
- };
5413
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/headings.ts
5414
+ var createHeadingGroupAction = (value) => createEditorActionGroup("heading", {
5415
+ variant: "dropdownMenu",
5416
+ applyActive: true,
5417
+ selectCardinality: "single",
5418
+ value
5419
+ }, "ph--text-h--regular");
5420
+ var createHeadingActions = (getView) => Object.entries({
5421
+ "0": "ph--paragraph--regular",
5422
+ "1": "ph--text-h-one--regular",
5423
+ "2": "ph--text-h-two--regular",
5424
+ "3": "ph--text-h-three--regular",
5425
+ "4": "ph--text-h-four--regular",
5426
+ "5": "ph--text-h-five--regular",
5427
+ "6": "ph--text-h-six--regular"
5428
+ }).map(([levelStr, icon]) => {
5429
+ const level = parseInt(levelStr);
5430
+ return createEditorAction(`heading--${levelStr}`, () => setHeading(level)(getView()), {
5431
+ label: [
5432
+ "heading level label",
5433
+ {
5434
+ count: level,
5435
+ ns: translationKey
5362
5436
  }
5363
- ]
5437
+ ],
5438
+ icon
5364
5439
  });
5440
+ });
5441
+ var computeHeadingValue = (state) => {
5442
+ const blockType = state ? state.blockType : "paragraph";
5443
+ const header = blockType && /heading(\d)/.exec(blockType);
5444
+ return header ? header[1] : blockType === "paragraph" || !blockType ? "0" : "";
5445
+ };
5446
+ var createHeadings = (state, getView) => {
5447
+ const headingValue = computeHeadingValue(state);
5448
+ const headingGroupAction = createHeadingGroupAction(headingValue);
5449
+ const headingActions = createHeadingActions(getView);
5450
+ return {
5451
+ nodes: [
5452
+ headingGroupAction,
5453
+ ...headingActions
5454
+ ],
5455
+ edges: [
5456
+ {
5457
+ source: "root",
5458
+ target: "heading"
5459
+ },
5460
+ ...headingActions.map(({ id }) => ({
5461
+ source: headingGroupAction.id,
5462
+ target: id
5463
+ }))
5464
+ ]
5465
+ };
5365
5466
  };
5366
5467
 
5367
- // packages/ui/react-ui-editor/src/extensions/modes.ts
5368
- import { keymap as keymap9 } from "@codemirror/view";
5369
- import { vim } from "@replit/codemirror-vim";
5370
- import { vscodeKeymap } from "@replit/codemirror-vscode-keymap";
5371
- import { S } from "@dxos/echo-schema";
5372
- var EditorViewModes = [
5373
- "preview",
5374
- "readonly",
5375
- "source"
5376
- ];
5377
- var EditorViewMode = S.Union(...EditorViewModes.map((mode) => S.Literal(mode)));
5378
- var EditorInputModes = [
5379
- "default",
5380
- "vim",
5381
- "vscode"
5382
- ];
5383
- var EditorInputMode = S.Union(...EditorInputModes.map((mode) => S.Literal(mode)));
5384
- var editorInputMode = singleValueFacet({});
5385
- var InputModeExtensions = {
5386
- default: [],
5387
- vscode: [
5388
- // https://github.com/replit/codemirror-vscode-keymap
5389
- editorInputMode.of({
5390
- type: "vscode"
5391
- }),
5392
- keymap9.of(vscodeKeymap)
5468
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/image.ts
5469
+ var createImageUploadAction = (onImageUpload) => createEditorAction("image", onImageUpload, {
5470
+ testId: "editor.toolbar.image",
5471
+ icon: "ph--image-square--regular"
5472
+ });
5473
+ var createImageUpload = (onImageUpload) => ({
5474
+ nodes: [
5475
+ createImageUploadAction(onImageUpload)
5393
5476
  ],
5394
- vim: [
5395
- // https://github.com/replit/codemirror-vim
5396
- vim(),
5397
- editorInputMode.of({
5398
- type: "vim",
5399
- noTabster: true
5400
- }),
5401
- keymap9.of([
5402
- {
5403
- key: "Alt-Escape",
5404
- run: (view) => {
5405
- view.dom.parentElement?.focus();
5406
- return true;
5407
- }
5408
- }
5409
- ])
5477
+ edges: [
5478
+ {
5479
+ source: "root",
5480
+ target: "image"
5481
+ }
5410
5482
  ]
5411
- };
5483
+ });
5412
5484
 
5413
- // packages/ui/react-ui-editor/src/extensions/preview/preview.ts
5414
- import "@dxos/lit-ui/dx-ref-tag.pcss";
5415
- import { syntaxTree as syntaxTree9 } from "@codemirror/language";
5416
- import { RangeSetBuilder as RangeSetBuilder4, StateField as StateField10 } from "@codemirror/state";
5417
- import { Decoration as Decoration8, EditorView as EditorView20, WidgetType as WidgetType6 } from "@codemirror/view";
5418
- var preview = (options = {}) => {
5419
- return [
5420
- // NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
5421
- // "Block decorations may not be specified via plugins"
5422
- StateField10.define({
5423
- create: (state) => buildDecorations3(state, options),
5424
- update: (_, tr) => buildDecorations3(tr.state, options),
5425
- provide: (field) => [
5426
- EditorView20.decorations.from(field),
5427
- EditorView20.atomicRanges.of((view) => view.state.field(field))
5428
- ]
5429
- }),
5430
- EditorView20.theme({
5431
- ".cm-preview-block": {
5432
- marginLeft: "-1rem",
5433
- marginRight: "-1rem",
5434
- padding: "1rem",
5435
- borderRadius: "0.5rem",
5436
- background: "var(--dx-modalSurface)",
5437
- border: "1px solid var(--dx-separator)"
5438
- }
5439
- })
5440
- ];
5441
- };
5442
- var getLinkRef = (state, node) => {
5443
- const mark = node.getChild("LinkMark");
5444
- const label = node.getChild("LinkLabel");
5445
- if (mark && label) {
5446
- const ref = state.sliceDoc(label.from + 1, label.to - 1);
5447
- return {
5448
- suggest: ref.startsWith("?"),
5449
- block: state.sliceDoc(mark.from, mark.from + 1) === "!",
5450
- label: state.sliceDoc(mark.to, label.from - 1),
5451
- ref: ref.startsWith("?") ? ref.slice(1) : ref
5452
- };
5453
- }
5485
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/lists.ts
5486
+ var listStyles = {
5487
+ bullet: "ph--list-bullets--regular",
5488
+ ordered: "ph--list-numbers--regular",
5489
+ task: "ph--list-checks--regular"
5454
5490
  };
5455
- var buildDecorations3 = (state, options) => {
5456
- const builder = new RangeSetBuilder4();
5457
- syntaxTree9(state).iterate({
5458
- enter: (node) => {
5459
- switch (node.name) {
5460
- //
5461
- // Decoration.
5462
- // [Label][dxn:echo:123]
5463
- //
5464
- case "Link": {
5465
- const link = getLinkRef(state, node.node);
5466
- if (link) {
5467
- builder.add(node.from, node.to, Decoration8.replace({
5468
- widget: new PreviewInlineWidget(options, link)
5469
- }));
5470
- }
5471
- break;
5472
- }
5473
- //
5474
- // Block widget.
5475
- // ![Label][dxn:echo:123]
5476
- //
5477
- case "Image": {
5478
- const link = getLinkRef(state, node.node);
5479
- if (options.renderBlock && link) {
5480
- builder.add(node.from, node.to, Decoration8.replace({
5481
- block: true,
5482
- // atomic: true,
5483
- widget: new PreviewBlockWidget(options, link)
5484
- }));
5485
- }
5486
- break;
5487
- }
5488
- }
5491
+ var createListGroupAction = (value) => createEditorActionGroup("list", {
5492
+ variant: "toggleGroup",
5493
+ selectCardinality: "single",
5494
+ value
5495
+ });
5496
+ var createListActions = (value, getView) => Object.entries(listStyles).map(([listStyle, icon]) => {
5497
+ const checked = value === listStyle;
5498
+ return createEditorAction(`list-${listStyle}`, () => {
5499
+ const view = getView();
5500
+ if (!view) {
5501
+ return;
5502
+ }
5503
+ const listType = listStyle === "ordered" ? List.Ordered : listStyle === "bullet" ? List.Bullet : List.Task;
5504
+ if (checked) {
5505
+ removeList(listType)(view);
5506
+ } else {
5507
+ addList(listType)(view);
5508
+ }
5509
+ }, {
5510
+ checked,
5511
+ icon
5512
+ });
5513
+ });
5514
+ var createLists = (state, getView) => {
5515
+ const value = state.listStyle ?? "";
5516
+ const listGroupAction = createListGroupAction(value);
5517
+ const listActionsMap = createListActions(value, getView);
5518
+ return {
5519
+ nodes: [
5520
+ listGroupAction,
5521
+ ...listActionsMap
5522
+ ],
5523
+ edges: [
5524
+ {
5525
+ source: "root",
5526
+ target: "list"
5527
+ },
5528
+ ...listActionsMap.map(({ id }) => ({
5529
+ source: listGroupAction.id,
5530
+ target: id
5531
+ }))
5532
+ ]
5533
+ };
5534
+ };
5535
+
5536
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/search.ts
5537
+ import { openSearchPanel } from "@codemirror/search";
5538
+ var createSearchAction = (getView) => createEditorAction("search", () => openSearchPanel(getView()), {
5539
+ testId: "editor.toolbar.search",
5540
+ icon: "ph--magnifying-glass--regular"
5541
+ });
5542
+ var createSearch = (getView) => ({
5543
+ nodes: [
5544
+ createSearchAction(getView)
5545
+ ],
5546
+ edges: [
5547
+ {
5548
+ source: "root",
5549
+ target: "search"
5489
5550
  }
5551
+ ]
5552
+ });
5553
+
5554
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/view-mode.ts
5555
+ var createViewModeGroupAction = (value) => createEditorActionGroup("viewMode", {
5556
+ variant: "dropdownMenu",
5557
+ applyActive: true,
5558
+ selectCardinality: "single",
5559
+ value
5560
+ }, "ph--eye--regular");
5561
+ var createViewModeActions = (value, onViewModeChange) => Object.entries({
5562
+ preview: "ph--eye--regular",
5563
+ source: "ph--pencil-simple--regular",
5564
+ readonly: "ph--pencil-slash--regular"
5565
+ }).map(([viewMode, icon]) => {
5566
+ const checked = viewMode === value;
5567
+ return createEditorAction(`view-mode--${viewMode}`, () => onViewModeChange(viewMode), {
5568
+ label: [
5569
+ `${viewMode} mode label`,
5570
+ {
5571
+ ns: translationKey
5572
+ }
5573
+ ],
5574
+ checked,
5575
+ icon
5490
5576
  });
5491
- return builder.finish();
5577
+ });
5578
+ var createViewMode = (state, onViewModeChange) => {
5579
+ const value = state.viewMode ?? "source";
5580
+ const viewModeGroupAction = createViewModeGroupAction(value);
5581
+ const viewModeActions = createViewModeActions(value, onViewModeChange);
5582
+ return {
5583
+ nodes: [
5584
+ viewModeGroupAction,
5585
+ ...viewModeActions
5586
+ ],
5587
+ edges: [
5588
+ {
5589
+ source: "root",
5590
+ target: "viewMode"
5591
+ },
5592
+ ...viewModeActions.map(({ id }) => ({
5593
+ source: viewModeGroupAction.id,
5594
+ target: id
5595
+ }))
5596
+ ]
5597
+ };
5492
5598
  };
5493
- var PreviewInlineWidget = class extends WidgetType6 {
5494
- constructor(_options, _link) {
5495
- super();
5496
- this._options = _options;
5497
- this._link = _link;
5599
+
5600
+ // packages/ui/react-ui-editor/src/defaults.ts
5601
+ import { EditorView as EditorView20 } from "@codemirror/view";
5602
+ import { mx as mx3 } from "@dxos/react-ui-theme";
5603
+ var margin = "!mt-[1rem]";
5604
+ var editorWidth = "!mli-auto is-full max-is-[min(50rem,100%-4rem)]";
5605
+ var editorContent = mx3(margin, editorWidth);
5606
+ var editorFullWidth = mx3(margin);
5607
+ var editorGutter = EditorView20.theme({
5608
+ // Match margin from content.
5609
+ // Gutter = 2rem + 1rem margin.
5610
+ ".cm-gutters": {
5611
+ marginTop: "1rem",
5612
+ paddingRight: "1rem"
5498
5613
  }
5499
- // override ignoreEvent() {
5500
- // return false;
5501
- // }
5502
- eq(other) {
5503
- return this._link.ref === other._link.ref && this._link.label === other._link.label;
5614
+ });
5615
+ var editorMonospace = EditorView20.theme({
5616
+ ".cm-content": {
5617
+ fontFamily: fontMono
5504
5618
  }
5505
- toDOM(view) {
5506
- const root = document.createElement("dx-ref-tag");
5507
- root.textContent = this._link.label;
5508
- root.setAttribute("ref", this._link.ref);
5509
- return root;
5619
+ });
5620
+ var editorWithToolbarLayout = "grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden";
5621
+ var stackItemContentEditorClassNames = (role) => mx3("attention-surface dx-focus-ring-inset data-[toolbar=disabled]:pbs-2", role === "section" ? "[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-bs-24" : "min-bs-0");
5622
+ var stackItemContentToolbarClassNames = (role) => mx3("attention-surface is-full border-be !border-separator relative z-[1]", role === "section" && "sticky block-start-0 -mbe-px min-is-0");
5623
+
5624
+ // packages/ui/react-ui-editor/src/components/EditorToolbar/EditorToolbar.tsx
5625
+ var createToolbar = ({ getView, state, customActions, ...features }) => {
5626
+ const nodes = [];
5627
+ const edges = [];
5628
+ if (features.headings ?? true) {
5629
+ const headings2 = createHeadings(state, getView);
5630
+ nodes.push(...headings2.nodes);
5631
+ edges.push(...headings2.edges);
5510
5632
  }
5511
- };
5512
- var PreviewBlockWidget = class extends WidgetType6 {
5513
- constructor(_options, _link) {
5514
- super();
5515
- this._options = _options;
5516
- this._link = _link;
5633
+ if (features.formatting ?? true) {
5634
+ const formatting = createFormatting(state, getView);
5635
+ nodes.push(...formatting.nodes);
5636
+ edges.push(...formatting.edges);
5517
5637
  }
5518
- // override ignoreEvent() {
5519
- // return true;
5520
- // }
5521
- eq(other) {
5522
- return this._link.ref === other._link.ref;
5638
+ if (features.lists ?? true) {
5639
+ const lists = createLists(state, getView);
5640
+ nodes.push(...lists.nodes);
5641
+ edges.push(...lists.edges);
5523
5642
  }
5524
- toDOM(view) {
5525
- const root = document.createElement("div");
5526
- root.classList.add("cm-preview-block");
5527
- const handleAction = (action) => {
5528
- const pos = view.posAtDOM(root);
5529
- const node = syntaxTree9(view.state).resolve(pos + 1).node.parent;
5530
- if (!node) {
5531
- return;
5532
- }
5533
- const link = getLinkRef(view.state, node);
5534
- if (link?.ref !== action.link.ref) {
5535
- return;
5536
- }
5537
- switch (action.type) {
5538
- // TODO(burdon): Should we dispatch to the view or mutate the document? (i.e., handle externally?)
5539
- // Insert ref text.
5540
- case "insert": {
5541
- view.dispatch({
5542
- changes: {
5543
- from: node.from,
5544
- to: node.to,
5545
- insert: action.target.text
5546
- }
5547
- });
5548
- break;
5549
- }
5550
- // Remove ref.
5551
- case "delete": {
5552
- view.dispatch({
5553
- changes: {
5554
- from: node.from,
5555
- to: node.to
5556
- }
5557
- });
5558
- break;
5559
- }
5560
- }
5561
- };
5562
- this._options.renderBlock(root, {
5563
- readonly: view.state.readOnly,
5564
- link: this._link,
5565
- onAction: handleAction,
5566
- onLookup: this._options.onLookup
5567
- }, view);
5568
- return root;
5643
+ if (features.blocks ?? true) {
5644
+ const blocks = createBlocks(state, getView);
5645
+ nodes.push(...blocks.nodes);
5646
+ edges.push(...blocks.edges);
5569
5647
  }
5648
+ if (features.image) {
5649
+ const image2 = createImageUpload(features.image);
5650
+ nodes.push(...image2.nodes);
5651
+ edges.push(...image2.edges);
5652
+ }
5653
+ if (customActions) {
5654
+ const custom = customActions();
5655
+ nodes.push(...custom.nodes);
5656
+ edges.push(...custom.edges);
5657
+ }
5658
+ const editorToolbarGap = createGapSeparator();
5659
+ nodes.push(...editorToolbarGap.nodes);
5660
+ edges.push(...editorToolbarGap.edges);
5661
+ if (features.comment) {
5662
+ const comment = createComment2(state, getView);
5663
+ nodes.push(...comment.nodes);
5664
+ edges.push(...comment.edges);
5665
+ }
5666
+ if (features.search ?? true) {
5667
+ const search = createSearch(getView);
5668
+ nodes.push(...search.nodes);
5669
+ edges.push(...search.edges);
5670
+ }
5671
+ if (features.viewMode) {
5672
+ const viewMode = createViewMode(state, features.viewMode);
5673
+ nodes.push(...viewMode.nodes);
5674
+ edges.push(...viewMode.edges);
5675
+ }
5676
+ return {
5677
+ nodes,
5678
+ edges
5679
+ };
5570
5680
  };
5571
-
5572
- // packages/ui/react-ui-editor/src/extensions/typewriter.ts
5573
- import { keymap as keymap10 } from "@codemirror/view";
5574
- var defaultItems = [
5575
- "hello world!",
5576
- "this is a test.",
5577
- "this is [DXOS](https://dxos.org)"
5578
- ];
5579
- var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
5580
- let t;
5581
- let idx = 0;
5582
- return [
5583
- keymap10.of([
5584
- {
5585
- // Reset.
5586
- key: "alt-meta-'",
5587
- run: (view) => {
5588
- clearTimeout(t);
5589
- idx = 0;
5590
- return true;
5591
- }
5592
- },
5593
- {
5594
- // Next prompt.
5595
- // TODO(burdon): Press 1-9 to select prompt?
5596
- key: "shift-meta-'",
5597
- run: (view) => {
5598
- clearTimeout(t);
5599
- const text = items[idx++];
5600
- if (idx === items?.length) {
5601
- idx = 0;
5602
- }
5603
- let i = 0;
5604
- const insert = (d = 0) => {
5605
- t = setTimeout(() => {
5606
- const pos = view.state.selection.main.head;
5607
- view.dispatch({
5608
- changes: {
5609
- from: pos,
5610
- insert: text[i++]
5611
- },
5612
- selection: {
5613
- anchor: pos + 1
5614
- }
5615
- });
5616
- if (i < text.length) {
5617
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
5618
- }
5619
- }, d);
5620
- };
5621
- insert();
5622
- return true;
5623
- }
5624
- }
5625
- ])
5626
- ];
5627
- };
5628
-
5629
- // packages/ui/react-ui-editor/src/hooks/useActionHandler.ts
5630
- import { useCallback as useCallback2 } from "react";
5631
- var useActionHandler = (view) => {
5632
- return useCallback2((action) => view && processEditorPayload(view, action.properties), [
5633
- view
5681
+ var useEditorToolbarActionGraph = (props) => {
5682
+ const menuCreator = useCallback(() => createToolbar(props), [
5683
+ props
5634
5684
  ]);
5685
+ return useMenuActions(menuCreator);
5635
5686
  };
5687
+ var EditorToolbar = /* @__PURE__ */ memo(({ classNames, attendableId, role, ...props }) => {
5688
+ const menuProps = useEditorToolbarActionGraph(props);
5689
+ return /* @__PURE__ */ React3.createElement("div", {
5690
+ role: "none",
5691
+ className: stackItemContentToolbarClassNames(role)
5692
+ }, /* @__PURE__ */ React3.createElement(ElevationProvider, {
5693
+ elevation: role === "section" ? "positioned" : "base"
5694
+ }, /* @__PURE__ */ React3.createElement(MenuProvider, {
5695
+ ...menuProps,
5696
+ attendableId
5697
+ }, /* @__PURE__ */ React3.createElement(ToolbarMenu, {
5698
+ classNames: [
5699
+ textBlockWidth,
5700
+ "!bg-transparent",
5701
+ classNames
5702
+ ]
5703
+ }))));
5704
+ });
5636
5705
 
5637
5706
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
5638
5707
  import { EditorState as EditorState2 } from "@codemirror/state";
5639
5708
  import { EditorView as EditorView21 } from "@codemirror/view";
5640
5709
  import { useFocusableGroup } from "@fluentui/react-tabster";
5641
- import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo4, useRef, useState } from "react";
5710
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo4, useRef, useState } from "react";
5642
5711
  import { log as log7 } from "@dxos/log";
5643
5712
  import { getProviderValue, isNotFalsy as isNotFalsy4 } from "@dxos/util";
5644
5713
  var __dxlog_file11 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
@@ -5763,7 +5832,7 @@ var useTextEditor = (props = {}, deps = []) => {
5763
5832
  Escape: view?.state.facet(editorInputMode).noTabster
5764
5833
  }
5765
5834
  });
5766
- const handleKeyUp = useCallback3((event) => {
5835
+ const handleKeyUp = useCallback2((event) => {
5767
5836
  const { key, target, currentTarget } = event;
5768
5837
  if (target === currentTarget) {
5769
5838
  switch (key) {
@@ -5822,7 +5891,7 @@ export {
5822
5891
  commentsState,
5823
5892
  convertTreeToJson,
5824
5893
  createBasicExtensions,
5825
- createComment2 as createComment,
5894
+ createComment,
5826
5895
  createDataExtensions,
5827
5896
  createEditorAction,
5828
5897
  createEditorActionGroup,
@@ -5900,7 +5969,6 @@ export {
5900
5969
  toggleStyle,
5901
5970
  translations_default as translations,
5902
5971
  typewriter,
5903
- useActionHandler,
5904
5972
  useCommentClickListener,
5905
5973
  useCommentState,
5906
5974
  useComments,