@dxos/react-ui-editor 0.10.0 → 0.11.0

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 (113) hide show
  1. package/dist/lib/index.mjs +1477 -0
  2. package/dist/lib/index.mjs.map +1 -0
  3. package/dist/lib/translations.mjs +32 -0
  4. package/dist/lib/translations.mjs.map +1 -0
  5. package/dist/types/src/components/Editor/Editor.d.ts +22 -17
  6. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  7. package/dist/types/src/components/Editor/EditorContext.d.ts +20 -0
  8. package/dist/types/src/components/Editor/EditorContext.d.ts.map +1 -0
  9. package/dist/types/src/components/Editor/EditorView.d.ts +1 -2
  10. package/dist/types/src/components/Editor/EditorView.d.ts.map +1 -1
  11. package/dist/types/src/components/Editor/index.d.ts +2 -0
  12. package/dist/types/src/components/Editor/index.d.ts.map +1 -1
  13. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts +7 -1
  14. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts.map +1 -1
  15. package/dist/types/src/components/EditorMenuProvider/menu.d.ts +4 -2
  16. package/dist/types/src/components/EditorMenuProvider/menu.d.ts.map +1 -1
  17. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts +7 -2
  18. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
  19. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewContext.d.ts +18 -0
  20. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewContext.d.ts.map +1 -0
  21. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts +0 -6
  22. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts.map +1 -1
  23. package/dist/types/src/components/EditorPreviewProvider/index.d.ts +1 -0
  24. package/dist/types/src/components/EditorPreviewProvider/index.d.ts.map +1 -1
  25. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +4 -1
  26. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  27. package/dist/types/src/components/EditorToolbar/index.d.ts +1 -0
  28. package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
  29. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -1
  30. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +27 -2
  31. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  32. package/dist/types/src/components/index.d.ts +1 -0
  33. package/dist/types/src/components/index.d.ts.map +1 -1
  34. package/dist/types/src/index.d.ts +0 -1
  35. package/dist/types/src/index.d.ts.map +1 -1
  36. package/dist/types/src/stories/Assistant.stories.d.ts.map +1 -0
  37. package/dist/types/src/stories/Automerge.stories.d.ts +16 -39
  38. package/dist/types/src/stories/Automerge.stories.d.ts.map +1 -1
  39. package/dist/types/src/stories/Blocks.stories.d.ts +10 -0
  40. package/dist/types/src/stories/Blocks.stories.d.ts.map +1 -0
  41. package/dist/types/src/stories/Comments.stories.d.ts +53 -14
  42. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  43. package/dist/types/src/stories/Diff.stories.d.ts +32 -0
  44. package/dist/types/src/stories/Diff.stories.d.ts.map +1 -0
  45. package/dist/types/src/stories/Experimental.stories.d.ts +1 -0
  46. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  47. package/dist/types/src/stories/Folding.stories.d.ts +18 -0
  48. package/dist/types/src/stories/Folding.stories.d.ts.map +1 -0
  49. package/dist/types/src/stories/Outliner.stories.d.ts +4 -4
  50. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  51. package/dist/types/src/stories/Popover.stories.d.ts +3 -3
  52. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -1
  53. package/dist/types/src/stories/Suggest.stories.d.ts +27 -0
  54. package/dist/types/src/stories/Suggest.stories.d.ts.map +1 -0
  55. package/dist/types/src/stories/TrackChanges.stories.d.ts +44 -0
  56. package/dist/types/src/stories/TrackChanges.stories.d.ts.map +1 -0
  57. package/dist/types/src/stories/Widgets.stories.d.ts +12 -0
  58. package/dist/types/src/stories/Widgets.stories.d.ts.map +1 -1
  59. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  60. package/dist/types/src/stories/components/util.d.ts +16 -0
  61. package/dist/types/src/stories/components/util.d.ts.map +1 -1
  62. package/dist/types/tsconfig.tsbuildinfo +1 -1
  63. package/package.json +40 -83
  64. package/src/components/Editor/Editor.tsx +142 -35
  65. package/src/components/Editor/EditorContext.ts +34 -0
  66. package/src/components/Editor/EditorView.tsx +9 -6
  67. package/src/components/Editor/index.ts +3 -0
  68. package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +98 -8
  69. package/src/components/EditorMenuProvider/menu.ts +4 -2
  70. package/src/components/EditorMenuProvider/useEditorMenu.ts +114 -29
  71. package/src/components/EditorPreviewProvider/EditorPreviewContext.ts +21 -0
  72. package/src/components/EditorPreviewProvider/EditorPreviewProvider.tsx +1 -10
  73. package/src/components/EditorPreviewProvider/index.ts +2 -0
  74. package/src/components/EditorToolbar/EditorToolbar.tsx +6 -3
  75. package/src/components/EditorToolbar/index.ts +1 -0
  76. package/src/components/EditorToolbar/lists.ts +2 -6
  77. package/src/components/EditorToolbar/view-mode.ts +41 -12
  78. package/src/components/index.ts +1 -0
  79. package/src/index.ts +0 -1
  80. package/src/{extensions → stories}/Assistant.stories.tsx +7 -2
  81. package/src/stories/Automerge.stories.tsx +99 -78
  82. package/src/stories/Blocks.stories.tsx +83 -0
  83. package/src/stories/Comments.stories.tsx +273 -74
  84. package/src/stories/Diff.stories.tsx +48 -0
  85. package/src/stories/EditorToolbar.stories.tsx +2 -2
  86. package/src/stories/Experimental.stories.tsx +13 -2
  87. package/src/stories/Folding.stories.tsx +120 -0
  88. package/src/stories/Markdown.stories.tsx +1 -1
  89. package/src/stories/Outliner.stories.tsx +18 -7
  90. package/src/stories/Popover.stories.tsx +27 -5
  91. package/src/stories/Suggest.stories.tsx +199 -0
  92. package/src/stories/TrackChanges.stories.tsx +226 -0
  93. package/src/stories/Widgets.stories.tsx +162 -10
  94. package/src/stories/components/EditorStory.tsx +15 -6
  95. package/src/stories/components/util.tsx +40 -2
  96. package/dist/lib/browser/index.mjs +0 -1676
  97. package/dist/lib/browser/index.mjs.map +0 -7
  98. package/dist/lib/browser/meta.json +0 -1
  99. package/dist/lib/browser/translations.mjs +0 -39
  100. package/dist/lib/browser/translations.mjs.map +0 -7
  101. package/dist/lib/node-esm/index.mjs +0 -1678
  102. package/dist/lib/node-esm/index.mjs.map +0 -7
  103. package/dist/lib/node-esm/meta.json +0 -1
  104. package/dist/lib/node-esm/translations.mjs +0 -41
  105. package/dist/lib/node-esm/translations.mjs.map +0 -7
  106. package/dist/types/src/extensions/Assistant.stories.d.ts.map +0 -1
  107. package/dist/types/src/extensions/assistant-extension.d.ts +0 -24
  108. package/dist/types/src/extensions/assistant-extension.d.ts.map +0 -1
  109. package/dist/types/src/extensions/index.d.ts +0 -2
  110. package/dist/types/src/extensions/index.d.ts.map +0 -1
  111. package/src/extensions/assistant-extension.tsx +0 -223
  112. package/src/extensions/index.ts +0 -5
  113. /package/dist/types/src/{extensions → stories}/Assistant.stories.d.ts +0 -0
@@ -1,1678 +0,0 @@
1
- import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
-
3
- // src/components/Editor/Editor.tsx
4
- import { Atom as Atom2 } from "@effect-atom/atom-react";
5
- import { createContext } from "@radix-ui/react-context";
6
- import React4, { forwardRef as forwardRef2, useCallback as useCallback4, useImperativeHandle as useImperativeHandle2, useMemo as useMemo5, useState as useState4 } from "react";
7
- import { invariant as invariant3 } from "@dxos/invariant";
8
- import { mx as mx2 } from "@dxos/ui-theme";
9
- import { isNonNullable as isNonNullable2 } from "@dxos/util";
10
-
11
- // src/components/EditorMenuProvider/menu.ts
12
- import { insertAtCursor } from "@dxos/ui-editor";
13
- var getMenuItem = (groups, id) => {
14
- return groups.flatMap((group) => group.items).find((item) => item.id === id);
15
- };
16
- var getNextMenuItem = (groups, id) => {
17
- const items = groups.flatMap((group) => group.items);
18
- const index = items.findIndex((item) => item.id === id);
19
- return index < items.length - 1 ? items[index + 1] : items[index];
20
- };
21
- var getPreviousMenuItem = (groups, id) => {
22
- const items = groups.flatMap((group) => group.items);
23
- const index = items.findIndex((item) => item.id === id);
24
- return index > 0 ? items[index - 1] : items[index];
25
- };
26
- var createMenuGroup = ({ id = "menu", label, filter, items }) => ({
27
- id,
28
- label,
29
- items: items.filter((item) => !filter || item.toLowerCase().includes(filter.toLowerCase())).map((item, i) => ({
30
- id: `${id}-${i}`,
31
- label: item,
32
- onSelect: ({ view, head }) => insertAtCursor(view, head, item)
33
- }))
34
- });
35
- var filterMenuGroups = (groups, filter) => {
36
- return groups.map((group) => ({
37
- ...group,
38
- items: group.items.filter(filter)
39
- }));
40
- };
41
-
42
- // src/components/EditorMenuProvider/menu-presets.ts
43
- import { insertAtLineStart } from "@dxos/ui-editor";
44
-
45
- // src/components/EditorMenuProvider/popover.ts
46
- import { Prec, RangeSetBuilder, StateEffect, StateField } from "@codemirror/state";
47
- import { Decoration, EditorView, ViewPlugin, keymap } from "@codemirror/view";
48
- import { modalStateField, placeholder } from "@dxos/ui-editor";
49
- import { isNonNullable, isTruthy } from "@dxos/util";
50
- var DELIMITERS = [
51
- " ",
52
- ":"
53
- ];
54
- var popover = (options = {}) => {
55
- return [
56
- Prec.highest(popoverKeymap(options)),
57
- popoverStateField,
58
- popoverTriggerListener(options),
59
- options.activateOnTyping && popoverAutoActivate(options),
60
- popoverAnchorDecoration(options),
61
- modalStateField,
62
- options.trigger && placeholder({
63
- // TODO(burdon): Translations.
64
- content: `Press '${Array.isArray(options.trigger) ? options.trigger[0] : options.trigger}' for commands`,
65
- focusOnly: true,
66
- ...options.placeholder
67
- })
68
- ].filter(isTruthy);
69
- };
70
- var popoverTriggerListener = (options) => EditorView.updateListener.of(({ view, docChanged }) => {
71
- const { range: activeRange, trigger } = view.state.field(popoverStateField) ?? {};
72
- if (!activeRange) {
73
- return;
74
- }
75
- const text = view.state.doc.sliceString(activeRange.from, activeRange.to);
76
- const selection = view.state.selection.main;
77
- const shouldClose = (
78
- // Trigger deleted.
79
- (trigger ? trigger !== text[0] : false) || // Whitespace in text.
80
- /\s/.test(trigger ? text.slice(1) : text) || // Cursor moved before the range.
81
- selection.head < activeRange.from || // Cursor moved after the range (+1 to handle selection changing before doc).
82
- selection.head > activeRange.to + 1
83
- );
84
- const nextRange = shouldClose ? null : docChanged ? {
85
- from: activeRange.from,
86
- to: selection.head
87
- } : activeRange;
88
- if (nextRange !== activeRange) {
89
- view.dispatch({
90
- effects: popoverRangeEffect.of(nextRange ? {
91
- range: nextRange,
92
- trigger
93
- } : null)
94
- });
95
- }
96
- if (shouldClose) {
97
- options.onClose?.({
98
- view
99
- });
100
- }
101
- });
102
- var popoverAutoActivate = (options) => EditorView.updateListener.of(({ view, docChanged, transactions }) => {
103
- if (!docChanged || !transactions.some((tr) => tr.isUserEvent("input")) || view.state.field(popoverStateField)) {
104
- return;
105
- }
106
- const selection = view.state.selection.main;
107
- if (!selection.empty) {
108
- return;
109
- }
110
- const line = view.state.doc.lineAt(selection.head);
111
- const before = line.text.slice(0, selection.head - line.from);
112
- const idx = getLastIndexOf(before, options.delimiters ?? DELIMITERS);
113
- const token = before.slice(idx + 1);
114
- if (token.length === 0) {
115
- return;
116
- }
117
- view.dispatch({
118
- effects: popoverRangeEffect.of({
119
- range: {
120
- from: line.from + idx + 1,
121
- to: selection.head
122
- }
123
- })
124
- });
125
- });
126
- var popoverKeymap = (options) => {
127
- const triggers = Array.isArray(options.trigger) ? options.trigger : [
128
- options.trigger
129
- ];
130
- return keymap.of([
131
- // Prefix triggers.
132
- ...triggers.filter(isNonNullable).map((trigger) => ({
133
- key: trigger,
134
- run: (view) => {
135
- const selection = view.state.selection.main;
136
- const line = view.state.doc.lineAt(selection.head);
137
- if (line.text.trim() === "" || selection.head === line.from || selection.head > line.from && line.text[selection.head - line.from - 1] === " ") {
138
- view.dispatch({
139
- changes: {
140
- from: selection.head,
141
- insert: trigger
142
- },
143
- selection: {
144
- anchor: selection.head + 1,
145
- head: selection.head + 1
146
- },
147
- effects: popoverRangeEffect.of({
148
- trigger,
149
- range: {
150
- from: selection.head,
151
- to: selection.head + 1
152
- }
153
- })
154
- });
155
- return true;
156
- }
157
- return false;
158
- }
159
- })),
160
- //
161
- // Custom trigger.
162
- //
163
- options.triggerKey && {
164
- key: options.triggerKey,
165
- run: (view) => {
166
- const selection = view.state.selection.main;
167
- const line = view.state.doc.lineAt(selection.head);
168
- let str = line.text.slice(0, selection.head - line.from);
169
- const idx = getLastIndexOf(str, options.delimiters ?? DELIMITERS);
170
- if (idx !== -1) {
171
- str = str.slice(idx + 1);
172
- }
173
- const from = line.from + idx;
174
- view.dispatch({
175
- effects: popoverRangeEffect.of({
176
- range: {
177
- from: from + 1,
178
- to: selection.head
179
- }
180
- }),
181
- changes: selection.head === view.state.doc.length ? {
182
- from: from + 1,
183
- to: selection.head,
184
- insert: " "
185
- } : void 0
186
- });
187
- return true;
188
- }
189
- },
190
- //
191
- // Nav keys.
192
- //
193
- {
194
- key: "ArrowUp",
195
- run: (view) => {
196
- const range = view.state.field(popoverStateField)?.range;
197
- if (range) {
198
- options.onArrowUp?.({
199
- view
200
- });
201
- return true;
202
- }
203
- return false;
204
- }
205
- },
206
- {
207
- key: "ArrowDown",
208
- run: (view) => {
209
- const range = view.state.field(popoverStateField)?.range;
210
- if (range) {
211
- options.onArrowDown?.({
212
- view
213
- });
214
- return true;
215
- }
216
- return false;
217
- }
218
- },
219
- {
220
- key: "Enter",
221
- run: (view) => {
222
- const range = view.state.field(popoverStateField)?.range;
223
- if (range) {
224
- return options.onEnter?.({
225
- view
226
- }) ?? false;
227
- }
228
- return false;
229
- }
230
- }
231
- ].filter(isTruthy));
232
- };
233
- var popoverAnchorDecoration = (options) => {
234
- return ViewPlugin.fromClass(class {
235
- view;
236
- _decorations = Decoration.none;
237
- constructor(view) {
238
- this.view = view;
239
- }
240
- // TODO(wittjosiah): The decorations are repainted on every update, this occasionally causes menu to flicker.
241
- update({ view, transactions }) {
242
- const builder = new RangeSetBuilder();
243
- const { range, trigger } = view.state.field(popoverStateField) ?? {};
244
- if (range) {
245
- const selection = view.state.selection.main;
246
- const showWidget = selection.head >= range.from && selection.head <= range.to;
247
- if (showWidget) {
248
- builder.add(range.from, range.to + 1, Decoration.mark({
249
- tagName: "dx-anchor",
250
- class: "cm-popover-trigger",
251
- attributes: {
252
- "data-visible-focus": "false",
253
- "data-auto-trigger": "true",
254
- "data-trigger": trigger ?? options.triggerKey ?? ""
255
- }
256
- }));
257
- }
258
- const rangeChanged = transactions.some((tr) => tr.effects.some((effect) => effect.is(popoverRangeEffect)));
259
- if (rangeChanged) {
260
- const content = view.state.sliceDoc(range.from + (trigger ? trigger.length : 0), range.to);
261
- options.onTextChange?.({
262
- view,
263
- pos: selection.head,
264
- text: content,
265
- trigger
266
- });
267
- }
268
- }
269
- this._decorations = builder.finish();
270
- }
271
- }, {
272
- decorations: (v) => v._decorations
273
- });
274
- };
275
- var popoverRangeEffect = StateEffect.define();
276
- var popoverStateField = StateField.define({
277
- create: () => null,
278
- update: (value, tr) => {
279
- let newValue = value;
280
- for (const effect of tr.effects) {
281
- if (effect.is(popoverRangeEffect)) {
282
- newValue = effect.value;
283
- }
284
- }
285
- return newValue;
286
- }
287
- });
288
- var getLastIndexOf = (str, delimiters) => Math.max(...delimiters.map((delim) => str.lastIndexOf(delim)));
289
-
290
- // src/components/EditorMenuProvider/menu-presets.ts
291
- var openLinkQuery = (view, insert) => {
292
- requestAnimationFrame(() => {
293
- const { head } = view.state.selection.main;
294
- const to = head + insert.length;
295
- view.dispatch({
296
- changes: {
297
- from: head,
298
- insert
299
- },
300
- selection: {
301
- anchor: to,
302
- head: to
303
- },
304
- // The popover trigger is the single "@"; a second "@" lives in the query range and switches to block mode.
305
- effects: popoverRangeEffect.of({
306
- trigger: "@",
307
- range: {
308
- from: head,
309
- to
310
- }
311
- })
312
- });
313
- });
314
- };
315
- var formattingCommands = {
316
- id: "markdown",
317
- label: "Markdown",
318
- items: [
319
- {
320
- id: "heading-1",
321
- label: "Heading 1",
322
- icon: "ph--text-h-one--regular",
323
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "# ")
324
- },
325
- {
326
- id: "heading-2",
327
- label: "Heading 2",
328
- icon: "ph--text-h-two--regular",
329
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "## ")
330
- },
331
- {
332
- id: "heading-3",
333
- label: "Heading 3",
334
- icon: "ph--text-h-three--regular",
335
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "### ")
336
- },
337
- {
338
- id: "heading-4",
339
- label: "Heading 4",
340
- icon: "ph--text-h-four--regular",
341
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "#### ")
342
- },
343
- {
344
- id: "heading-5",
345
- label: "Heading 5",
346
- icon: "ph--text-h-five--regular",
347
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "##### ")
348
- },
349
- {
350
- id: "heading-6",
351
- label: "Heading 6",
352
- icon: "ph--text-h-six--regular",
353
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "###### ")
354
- },
355
- {
356
- id: "bullet-list",
357
- label: "Bullet List",
358
- icon: "ph--list-bullets--regular",
359
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "- ")
360
- },
361
- {
362
- id: "numbered-list",
363
- label: "Numbered List",
364
- icon: "ph--list-numbers--regular",
365
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "1. ")
366
- },
367
- {
368
- id: "task-list",
369
- label: "Task List",
370
- icon: "ph--list-checks--regular",
371
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "- [ ] ")
372
- },
373
- {
374
- id: "quote",
375
- label: "Quote",
376
- icon: "ph--quotes--regular",
377
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "> ")
378
- },
379
- {
380
- id: "code-block",
381
- label: "Code Block",
382
- icon: "ph--code-block--regular",
383
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "```\n\n```")
384
- },
385
- {
386
- id: "table",
387
- label: "Table",
388
- icon: "ph--table--regular",
389
- onSelect: ({ view, head }) => insertAtLineStart(view, head, "| | | |\n|---|---|---|\n| | | |")
390
- }
391
- ]
392
- };
393
- var linkSlashCommands = {
394
- id: "link",
395
- label: "Link",
396
- items: [
397
- {
398
- id: "inline-link",
399
- label: "Inline link",
400
- icon: "ph--link--regular",
401
- onSelect: ({ view }) => openLinkQuery(view, "@")
402
- },
403
- {
404
- id: "inline-object",
405
- label: "Inline object",
406
- icon: "ph--lego--regular",
407
- onSelect: ({ view }) => openLinkQuery(view, "@@")
408
- }
409
- ]
410
- };
411
-
412
- // src/components/EditorMenuProvider/EditorMenuProvider.tsx
413
- import { useControllableState } from "@radix-ui/react-use-controllable-state";
414
- import React, { Fragment, useCallback, useEffect, useRef, useState } from "react";
415
- import { addEventListener } from "@dxos/async";
416
- import { invariant } from "@dxos/invariant";
417
- import { DX_ANCHOR_ACTIVATE, Icon, Popover, ScrollArea, toLocalizedString, useDynamicRef, useThemeContext, useTranslation } from "@dxos/react-ui";
418
- var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/EditorMenuProvider/EditorMenuProvider.tsx";
419
- var EditorMenuProvider = ({ children, getView, groups, currentItem, open: openProp, defaultOpen, numItems = 8, onOpenChange, onActivate, onSelect, onCancel }) => {
420
- const { tx } = useThemeContext();
421
- const triggerRef = useRef(null);
422
- const getViewRef = useDynamicRef(getView);
423
- const [open, setOpen] = useControllableState({
424
- prop: openProp,
425
- defaultProp: defaultOpen,
426
- onChange: (open2) => {
427
- const view = getViewRef.current?.();
428
- invariant(view, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 23, S: void 0, A: ["view", ""] });
429
- onOpenChange?.({
430
- view,
431
- open: open2
432
- });
433
- }
434
- });
435
- const [root, setRoot] = useState(null);
436
- useEffect(() => {
437
- if (!root) {
438
- return;
439
- }
440
- return addEventListener(root, DX_ANCHOR_ACTIVATE, (event) => {
441
- const { trigger, dxn } = event;
442
- if (!dxn) {
443
- triggerRef.current = trigger;
444
- if (onActivate) {
445
- const view = getViewRef.current?.();
446
- if (view) {
447
- onActivate({
448
- view,
449
- trigger: trigger.getAttribute("data-trigger") ?? void 0
450
- });
451
- }
452
- } else {
453
- requestAnimationFrame(() => setOpen(true));
454
- }
455
- }
456
- }, {
457
- capture: true,
458
- passive: false
459
- });
460
- }, [
461
- root,
462
- onActivate
463
- ]);
464
- const handleSelect = useCallback((item) => {
465
- const view = getViewRef.current?.();
466
- invariant(view, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 62, S: void 0, A: ["view", ""] });
467
- onSelect?.({
468
- view,
469
- item
470
- });
471
- }, [
472
- getViewRef,
473
- onSelect
474
- ]);
475
- const menuGroups = groups?.filter((group) => group.items.length > 0) ?? [];
476
- return /* @__PURE__ */ React.createElement(Popover.Root, {
477
- modal: false,
478
- open,
479
- onOpenChange: setOpen
480
- }, /* @__PURE__ */ React.createElement(Popover.VirtualTrigger, {
481
- virtualRef: triggerRef
482
- }), /* @__PURE__ */ React.createElement(Popover.Portal, null, /* @__PURE__ */ React.createElement(Popover.Content, {
483
- align: "start",
484
- classNames: [
485
- "flex flex-col",
486
- !menuGroups.length && "hidden"
487
- ],
488
- style: {
489
- maxBlockSize: 36 * numItems + 10
490
- },
491
- // NOTE: We keep the focus in the editor, but Radix routes escape key.
492
- onEscapeKeyDown: () => {
493
- const currentView = getViewRef.current?.();
494
- if (currentView) {
495
- onCancel?.({
496
- view: currentView
497
- });
498
- }
499
- },
500
- onOpenAutoFocus: (event) => event.preventDefault()
501
- }, /* @__PURE__ */ React.createElement(Popover.Viewport, {
502
- asChild: true,
503
- classNames: "dx-container"
504
- }, /* @__PURE__ */ React.createElement(ScrollArea.Root, {
505
- thin: true
506
- }, /* @__PURE__ */ React.createElement(ScrollArea.Viewport, null, /* @__PURE__ */ React.createElement(Menu, {
507
- groups: menuGroups,
508
- currentItem,
509
- onSelect: handleSelect
510
- })))), /* @__PURE__ */ React.createElement(Popover.Arrow, null))), /* @__PURE__ */ React.createElement("div", {
511
- className: "contents",
512
- ref: setRoot
513
- }, children));
514
- };
515
- var Menu = ({ groups, currentItem, onSelect }) => {
516
- const { tx } = useThemeContext();
517
- return /* @__PURE__ */ React.createElement("ul", null, groups.map((group, index) => /* @__PURE__ */ React.createElement(Fragment, {
518
- key: group.id
519
- }, /* @__PURE__ */ React.createElement(MenuGroup, {
520
- group,
521
- currentItem,
522
- onSelect
523
- }), index < groups.length - 1 && /* @__PURE__ */ React.createElement("div", {
524
- className: tx("menu.separator", {})
525
- }))));
526
- };
527
- var MenuGroup = ({ group, currentItem, onSelect }) => {
528
- const { tx } = useThemeContext();
529
- const { t } = useTranslation();
530
- return /* @__PURE__ */ React.createElement(React.Fragment, null, group.label && /* @__PURE__ */ React.createElement("div", {
531
- className: tx("menu.groupLabel", {})
532
- }, /* @__PURE__ */ React.createElement("span", null, toLocalizedString(group.label, t))), group.items.map((item) => /* @__PURE__ */ React.createElement(MenuItem, {
533
- key: item.id,
534
- item,
535
- current: currentItem === item.id,
536
- onSelect
537
- })));
538
- };
539
- var MenuItem = ({ item, current, onSelect }) => {
540
- const { tx } = useThemeContext();
541
- const { t } = useTranslation();
542
- const listRef = useRef(null);
543
- useEffect(() => {
544
- if (current && listRef.current) {
545
- listRef.current.scrollIntoView({
546
- behavior: "smooth",
547
- block: "nearest"
548
- });
549
- }
550
- }, [
551
- current
552
- ]);
553
- const handleSelect = useCallback(() => onSelect?.(item), [
554
- item,
555
- onSelect
556
- ]);
557
- return /* @__PURE__ */ React.createElement("li", {
558
- ref: listRef,
559
- className: tx("menu.item", {}, [
560
- current && "bg-hover-surface"
561
- ]),
562
- onClick: handleSelect
563
- }, item.icon && /* @__PURE__ */ React.createElement(Icon, {
564
- icon: item.icon
565
- }), /* @__PURE__ */ React.createElement("span", {
566
- className: "grow truncate"
567
- }, toLocalizedString(item.label, t)));
568
- };
569
-
570
- // src/components/EditorMenuProvider/useEditorMenu.ts
571
- import { useCallback as useCallback2, useMemo, useRef as useRef2, useState as useState2 } from "react";
572
- import { invariant as invariant2 } from "@dxos/invariant";
573
- import { modalStateEffect } from "@dxos/ui-editor";
574
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/EditorMenuProvider/useEditorMenu.ts";
575
- var useEditorMenu = ({ trigger, triggerKey, placeholder: placeholder2, activateOnTyping, filter = true, getMenu }) => {
576
- const groupsRef = useRef2([]);
577
- const currentRef = useRef2(null);
578
- const [currentItem, setCurrentItem] = useState2();
579
- const [open, setOpen] = useState2(false);
580
- const [_, refresh] = useState2({});
581
- const getMenuOptions = useCallback2(async ({ text, trigger: trigger2, ...props }) => {
582
- const groups = await getMenu?.({
583
- text,
584
- trigger: trigger2,
585
- ...props
586
- }) ?? [];
587
- return filter && trigger2 !== "@" ? filterMenuGroups(groups, (item) => text ? item.label.toLowerCase().startsWith(text.toLowerCase()) : true) : groups;
588
- }, [
589
- getMenu,
590
- filter
591
- ]);
592
- const handleOpenChange = useCallback2(async ({ view, open: open2 }) => {
593
- invariant2(view, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 40, S: void 0, A: ["view", ""] });
594
- setOpen(open2);
595
- if (!open2) {
596
- setCurrentItem(void 0);
597
- view.dispatch({
598
- effects: [
599
- popoverRangeEffect.of(null)
600
- ]
601
- });
602
- }
603
- requestAnimationFrame(() => {
604
- view.dispatch({
605
- effects: [
606
- modalStateEffect.of(open2)
607
- ]
608
- });
609
- });
610
- }, [
611
- getMenuOptions
612
- ]);
613
- const handleActivate = useCallback2(async ({ view, trigger: trigger2 }) => {
614
- const item = getMenuItem(groupsRef.current, currentItem);
615
- if (item) {
616
- currentRef.current = item;
617
- }
618
- if (!open) {
619
- handleOpenChange({
620
- view,
621
- open: true,
622
- trigger: trigger2
623
- });
624
- }
625
- }, [
626
- open,
627
- handleOpenChange
628
- ]);
629
- const handleSelect = useCallback2(({ view, item }) => {
630
- const { range } = view.state.field(popoverStateField) ?? {};
631
- if (range) {
632
- view.dispatch({
633
- changes: {
634
- from: range.from,
635
- to: range.to,
636
- insert: ""
637
- }
638
- });
639
- }
640
- void item.onSelect?.({
641
- view,
642
- head: view.state.selection.main.head
643
- });
644
- view.focus();
645
- }, []);
646
- const handleCancel = useCallback2(({ view }) => {
647
- const { range, trigger: trigger2 } = view.state.field(popoverStateField) ?? {};
648
- if (range && trigger2) {
649
- view.dispatch({
650
- changes: {
651
- ...range,
652
- insert: ""
653
- }
654
- });
655
- }
656
- }, []);
657
- const serializedTrigger = Array.isArray(trigger) ? trigger.join(",") : trigger;
658
- const extension = useMemo(() => {
659
- return popover({
660
- trigger,
661
- triggerKey,
662
- placeholder: placeholder2,
663
- activateOnTyping,
664
- onClose: ({ view }) => handleOpenChange({
665
- view,
666
- open: false
667
- }),
668
- onEnter: ({ view }) => {
669
- if (currentRef.current) {
670
- handleSelect({
671
- view,
672
- item: currentRef.current
673
- });
674
- return true;
675
- }
676
- return false;
677
- },
678
- onArrowUp: () => {
679
- setCurrentItem((currentItem2) => {
680
- const previous = getPreviousMenuItem(groupsRef.current, currentItem2);
681
- currentRef.current = previous;
682
- return previous.id;
683
- });
684
- },
685
- onArrowDown: () => {
686
- setCurrentItem((currentItem2) => {
687
- const next = getNextMenuItem(groupsRef.current, currentItem2);
688
- currentRef.current = next;
689
- return next.id;
690
- });
691
- },
692
- onTextChange: async ({ view, pos, text, trigger: trigger2 }) => {
693
- groupsRef.current = await getMenuOptions({
694
- state: view.state,
695
- pos,
696
- text,
697
- trigger: trigger2
698
- }) ?? [];
699
- const firstItem = groupsRef.current.filter((group) => group.items.length > 0)[0]?.items[0];
700
- if (firstItem) {
701
- setCurrentItem(firstItem.id);
702
- currentRef.current = firstItem;
703
- } else {
704
- setCurrentItem(void 0);
705
- currentRef.current = null;
706
- }
707
- refresh({});
708
- }
709
- });
710
- }, [
711
- handleOpenChange,
712
- getMenuOptions,
713
- serializedTrigger,
714
- placeholder2,
715
- activateOnTyping
716
- ]);
717
- return {
718
- groupsRef,
719
- extension,
720
- currentItem,
721
- open,
722
- onOpenChange: handleOpenChange,
723
- onActivate: handleActivate,
724
- onSelect: handleSelect,
725
- onCancel: handleCancel
726
- };
727
- };
728
-
729
- // src/components/EditorToolbar/EditorToolbar.tsx
730
- import { Atom } from "@effect-atom/atom-react";
731
- import React2, { memo, useMemo as useMemo2 } from "react";
732
- import { ElevationProvider } from "@dxos/react-ui";
733
- import { Menu as Menu2, MenuBuilder, useMenuActions } from "@dxos/react-ui-menu";
734
-
735
- // src/components/EditorToolbar/blocks.ts
736
- import { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from "@dxos/ui-editor";
737
- import { translationKey } from "#translations";
738
- var blockTypes = {
739
- blockquote: "ph--quotes--regular",
740
- codeblock: "ph--code-block--regular",
741
- table: "ph--table--regular"
742
- };
743
- var addBlocks = (state, getView) => (builder) => {
744
- const value = state?.blockQuote ? "blockquote" : state.blockType ?? "";
745
- builder.group("block", {
746
- label: [
747
- "block.label",
748
- {
749
- ns: translationKey
750
- }
751
- ],
752
- iconOnly: true,
753
- variant: "toggleGroup",
754
- selectCardinality: "single",
755
- value
756
- }, (group) => {
757
- for (const [type, icon] of Object.entries(blockTypes)) {
758
- const checked = type === value;
759
- group.action(type, {
760
- label: [
761
- `block.${type}.label`,
762
- {
763
- ns: translationKey
764
- }
765
- ],
766
- checked,
767
- ...type === "table" && {
768
- disabled: !!state.blankLine
769
- },
770
- icon
771
- }, () => {
772
- const view = getView();
773
- if (!view) {
774
- return;
775
- }
776
- switch (type) {
777
- case "blockquote":
778
- checked ? removeBlockquote(view) : addBlockquote(view);
779
- break;
780
- case "codeblock":
781
- checked ? removeCodeblock(view) : addCodeblock(view);
782
- break;
783
- case "table":
784
- insertTable(view);
785
- break;
786
- }
787
- });
788
- }
789
- });
790
- };
791
-
792
- // src/components/EditorToolbar/formatting.ts
793
- import { Inline, addLink, removeLink, setStyle } from "@dxos/ui-editor";
794
- import { translationKey as translationKey2 } from "#translations";
795
- var formats = {
796
- strong: "ph--text-b--regular",
797
- emphasis: "ph--text-italic--regular",
798
- strikethrough: "ph--text-strikethrough--regular",
799
- code: "ph--code--regular",
800
- link: "ph--link--regular"
801
- };
802
- var addFormatting = (state, getView) => (builder) => {
803
- const formatting = state;
804
- builder.group("formatting", {
805
- label: [
806
- "formatting.label",
807
- {
808
- ns: translationKey2
809
- }
810
- ],
811
- iconOnly: true,
812
- variant: "toggleGroup",
813
- selectCardinality: "multiple",
814
- value: Object.keys(formats).filter((key) => !!formatting[key])
815
- }, (group) => {
816
- for (const [type, icon] of Object.entries(formats)) {
817
- const checked = !!formatting[type];
818
- group.action(type, {
819
- label: [
820
- `formatting.${type}.label`,
821
- {
822
- ns: translationKey2
823
- }
824
- ],
825
- checked,
826
- icon
827
- }, () => {
828
- const view = getView();
829
- if (!view) {
830
- return;
831
- }
832
- if (type === "link") {
833
- checked ? removeLink(view) : addLink()(view);
834
- return;
835
- }
836
- setStyle(type === "strong" ? Inline.Strong : type === "emphasis" ? Inline.Emphasis : type === "strikethrough" ? Inline.Strikethrough : Inline.Code, !checked)(view);
837
- });
838
- }
839
- });
840
- };
841
-
842
- // src/components/EditorToolbar/headings.ts
843
- import { setHeading } from "@dxos/ui-editor";
844
- import { translationKey as translationKey3 } from "#translations";
845
- var headingIcons = {
846
- 0: "ph--paragraph--regular",
847
- 1: "ph--text-h-one--regular",
848
- 2: "ph--text-h-two--regular",
849
- 3: "ph--text-h-three--regular",
850
- 4: "ph--text-h-four--regular",
851
- 5: "ph--text-h-five--regular",
852
- 6: "ph--text-h-six--regular"
853
- };
854
- var computeHeadingValue = (state) => {
855
- const blockType = state ? state.blockType : "paragraph";
856
- const heading = blockType && /heading(\d)/.exec(blockType);
857
- return heading ? heading[1] : blockType === "paragraph" || !blockType ? "0" : "";
858
- };
859
- var addHeadings = (state, getView) => (builder) => {
860
- const headingValue = computeHeadingValue(state);
861
- builder.group("heading", {
862
- label: [
863
- "heading.label",
864
- {
865
- ns: translationKey3
866
- }
867
- ],
868
- icon: "ph--text-h--regular",
869
- iconOnly: true,
870
- variant: "dropdownMenu",
871
- applyActive: true,
872
- selectCardinality: "single",
873
- // TODO(wittjosiah): Remove? Not sure this does anything.
874
- value: headingValue
875
- }, (group) => {
876
- for (const [levelStr, icon] of Object.entries(headingIcons)) {
877
- const level = parseInt(levelStr);
878
- group.action(`heading--${levelStr}`, {
879
- label: [
880
- "heading-level.label",
881
- {
882
- count: level,
883
- ns: translationKey3
884
- }
885
- ],
886
- icon,
887
- checked: levelStr === headingValue
888
- }, () => setHeading(level)(getView()));
889
- }
890
- });
891
- };
892
-
893
- // src/components/EditorToolbar/image.ts
894
- import { translationKey as translationKey4 } from "#translations";
895
- var addImageUpload = (onImageUpload) => (builder) => {
896
- builder.action("image", {
897
- label: [
898
- "image.label",
899
- {
900
- ns: translationKey4
901
- }
902
- ],
903
- testId: "editor.toolbar.image",
904
- icon: "ph--image-square--regular"
905
- }, onImageUpload);
906
- };
907
-
908
- // src/components/EditorToolbar/lists.ts
909
- import { List, addList, removeList } from "@dxos/ui-editor";
910
- import { translationKey as translationKey5 } from "#translations";
911
- var listStyles = {
912
- bullet: "ph--list-bullets--regular",
913
- ordered: "ph--list-numbers--regular",
914
- task: "ph--list-checks--regular"
915
- };
916
- var addLists = (state, getView) => (builder) => {
917
- const value = state.listStyle ?? "";
918
- builder.group("list", {
919
- label: [
920
- "list.label",
921
- {
922
- ns: translationKey5
923
- }
924
- ],
925
- iconOnly: true,
926
- variant: "toggleGroup",
927
- selectCardinality: "single",
928
- value
929
- }, (group) => {
930
- for (const [listStyle, icon] of Object.entries(listStyles)) {
931
- const checked = value === listStyle;
932
- group.action(`list-${listStyle}`, {
933
- label: [
934
- `list.${listStyle}.label`,
935
- {
936
- ns: translationKey5
937
- }
938
- ],
939
- checked,
940
- icon
941
- }, () => {
942
- const view = getView();
943
- if (!view) {
944
- return;
945
- }
946
- const listType = listStyle === "ordered" ? List.Ordered : listStyle === "bullet" ? List.Bullet : List.Task;
947
- if (checked) {
948
- removeList(listType)(view);
949
- } else {
950
- addList(listType)(view);
951
- }
952
- });
953
- }
954
- });
955
- };
956
-
957
- // src/components/EditorToolbar/search.ts
958
- import { openSearchPanel } from "@codemirror/search";
959
- import { translationKey as translationKey6 } from "#translations";
960
- var addSearch = (getView) => (builder) => {
961
- builder.action("search", {
962
- label: [
963
- "search.label",
964
- {
965
- ns: translationKey6
966
- }
967
- ],
968
- testId: "editor.toolbar.search",
969
- icon: "ph--magnifying-glass--regular"
970
- }, () => openSearchPanel(getView()));
971
- };
972
-
973
- // src/components/EditorToolbar/view-mode.ts
974
- import { translationKey as translationKey7 } from "#translations";
975
- var viewModes = {
976
- preview: "ph--eye--regular",
977
- source: "ph--pencil-simple--regular",
978
- readonly: "ph--pencil-slash--regular"
979
- };
980
- var addViewMode = (state, onViewModeChange) => (builder) => {
981
- const value = state.viewMode ?? "source";
982
- builder.group("viewMode", {
983
- label: [
984
- "view-mode.label",
985
- {
986
- ns: translationKey7
987
- }
988
- ],
989
- icon: "ph--eye--regular",
990
- iconOnly: true,
991
- variant: "dropdownMenu",
992
- applyActive: true,
993
- selectCardinality: "single",
994
- value
995
- }, (group) => {
996
- for (const [viewMode, icon] of Object.entries(viewModes)) {
997
- const checked = viewMode === value;
998
- group.action(`view-mode--${viewMode}`, {
999
- label: [
1000
- `view-mode.${viewMode}.label`,
1001
- {
1002
- ns: translationKey7
1003
- }
1004
- ],
1005
- checked,
1006
- icon
1007
- }, () => onViewModeChange(viewMode));
1008
- }
1009
- });
1010
- };
1011
-
1012
- // src/components/EditorToolbar/EditorToolbar.tsx
1013
- var SECTION_ROLE = "org.dxos.role.section";
1014
- var EditorToolbar = /* @__PURE__ */ memo(({ classNames, role, attendableId, onAction, ...props }) => {
1015
- const menuActions = useMarkdownMenuActions(props);
1016
- return /* @__PURE__ */ React2.createElement(ElevationProvider, {
1017
- elevation: role === SECTION_ROLE ? "positioned" : "base"
1018
- }, /* @__PURE__ */ React2.createElement(Menu2.Root, {
1019
- ...menuActions,
1020
- attendableId,
1021
- onAction
1022
- }, /* @__PURE__ */ React2.createElement(Menu2.Toolbar, {
1023
- classNames
1024
- })));
1025
- });
1026
- var useMarkdownMenuActions = ({ state, getView, customActions, ...features }) => {
1027
- const menuCreator = useMemo2(() => createMarkdownActions({
1028
- state,
1029
- getView,
1030
- customActions,
1031
- ...features
1032
- }), [
1033
- state,
1034
- getView,
1035
- customActions,
1036
- features?.showHeadings,
1037
- features?.showFormatting,
1038
- features?.showLists,
1039
- features?.showBlocks,
1040
- features?.showSearch,
1041
- features?.onImageUpload,
1042
- features?.onViewModeChange
1043
- ]);
1044
- return useMenuActions(menuCreator);
1045
- };
1046
- var createMarkdownActions = ({ state, getView, customActions, ...features }) => {
1047
- return Atom.make((get) => {
1048
- const stateSnapshot = get(state);
1049
- return MenuBuilder.make().subgraph(features?.showHeadings !== false && addHeadings(stateSnapshot, getView)).subgraph(features?.showFormatting !== false && addFormatting(stateSnapshot, getView)).subgraph(features?.showLists !== false && addLists(stateSnapshot, getView)).subgraph(features?.showBlocks !== false && addBlocks(stateSnapshot, getView)).subgraph(features?.onImageUpload && addImageUpload(features.onImageUpload)).separator("gap").subgraph(customActions && get(customActions)).subgraph(features?.showSearch !== false && addSearch(getView)).subgraph(features?.onViewModeChange && addViewMode(stateSnapshot, features.onViewModeChange)).build();
1050
- });
1051
- };
1052
-
1053
- // src/components/Editor/EditorView.tsx
1054
- import { Transaction } from "@codemirror/state";
1055
- import { EditorView as NaturalEditorView } from "@codemirror/view";
1056
- import React3, { forwardRef, useEffect as useEffect3, useImperativeHandle, useRef as useRef4 } from "react";
1057
- import { initialSync } from "@dxos/ui-editor";
1058
- import { mx } from "@dxos/ui-theme";
1059
-
1060
- // src/hooks/useBasicMarkdownExtensions.ts
1061
- import { useMemo as useMemo3 } from "react";
1062
- import { useThemeContext as useThemeContext2 } from "@dxos/react-ui";
1063
- import { createBasicExtensions, createMarkdownExtensions, createThemeExtensions, decorateMarkdown } from "@dxos/ui-editor";
1064
- var useBasicMarkdownExtensions = ({ placeholder: placeholder2, decorate = true, readonly = false, extensions } = {}) => {
1065
- const { themeMode } = useThemeContext2();
1066
- return useMemo3(() => [
1067
- createBasicExtensions({
1068
- placeholder: placeholder2,
1069
- readOnly: readonly
1070
- }),
1071
- createThemeExtensions({
1072
- syntaxHighlighting: true,
1073
- themeMode
1074
- }),
1075
- createMarkdownExtensions(),
1076
- ...decorate ? [
1077
- decorateMarkdown()
1078
- ] : [],
1079
- ...extensions ?? []
1080
- ], [
1081
- placeholder2,
1082
- themeMode,
1083
- decorate,
1084
- readonly,
1085
- extensions
1086
- ]);
1087
- };
1088
-
1089
- // src/hooks/useTextEditor.ts
1090
- import { EditorState } from "@codemirror/state";
1091
- import { EditorView as EditorView2 } from "@codemirror/view";
1092
- import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
1093
- import { log } from "@dxos/log";
1094
- import { createEditorStateTransaction, debugDispatcher, documentId, modalStateField as modalStateField2 } from "@dxos/ui-editor";
1095
- import { getProviderValue, isTruthy as isTruthy2 } from "@dxos/util";
1096
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
1097
- var instanceCount = 0;
1098
- var useTextEditor = (props = {}, deps = []) => {
1099
- const { id, doc, initialValue, extensions, autoFocus, scrollTo, selection, selectionEnd, debug } = useMemo4(() => getProviderValue(props), deps ?? []);
1100
- const [instanceId] = useState3(() => `text-editor-${++instanceCount}`);
1101
- const [view, setView] = useState3(null);
1102
- const parentRef = useRef3(null);
1103
- useEffect2(() => {
1104
- let view2 = null;
1105
- if (parentRef.current) {
1106
- log("create", {
1107
- id,
1108
- instanceId,
1109
- doc: initialValue?.length ?? 0
1110
- }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 22, S: void 0 });
1111
- let initialSelection;
1112
- if (selection?.anchor && initialValue?.length) {
1113
- if (selection.anchor <= initialValue.length && (selection?.head ?? 0) <= initialValue.length) {
1114
- initialSelection = selection;
1115
- }
1116
- } else if (selectionEnd && selection === void 0) {
1117
- const index = initialValue?.indexOf("\n");
1118
- const anchor = !index || index === -1 ? 0 : index;
1119
- initialSelection = {
1120
- anchor
1121
- };
1122
- }
1123
- const state = EditorState.create({
1124
- doc: doc ?? initialValue,
1125
- // selection: initialSelection,
1126
- extensions: [
1127
- id && documentId.of(id),
1128
- extensions,
1129
- // NOTE: This doesn't catch errors in keymap functions.
1130
- EditorView2.exceptionSink.of((err) => {
1131
- log.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 48, S: void 0 });
1132
- })
1133
- ].filter(isTruthy2)
1134
- });
1135
- view2 = new EditorView2({
1136
- parent: parentRef.current,
1137
- state,
1138
- scrollTo: scrollTo ? EditorView2.scrollIntoView(scrollTo, {
1139
- yMargin: 96
1140
- }) : void 0,
1141
- dispatchTransactions: debug ? debugDispatcher : void 0
1142
- });
1143
- if (selectionEnd && !initialSelection) {
1144
- const { to } = view2.state.doc.lineAt(0);
1145
- if (to) {
1146
- view2.dispatch({
1147
- selection: {
1148
- anchor: to
1149
- }
1150
- });
1151
- }
1152
- }
1153
- setView(view2);
1154
- }
1155
- return () => {
1156
- log("destroy", {
1157
- id
1158
- }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 75, S: void 0 });
1159
- view2?.destroy();
1160
- };
1161
- }, deps);
1162
- useEffect2(() => {
1163
- if (view) {
1164
- if (scrollTo || selection) {
1165
- if (selection && selection.anchor > view.state.doc.length) {
1166
- log.warn("invalid selection", {
1167
- length: view.state.doc.length,
1168
- scrollTo,
1169
- selection
1170
- }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 85, S: void 0 });
1171
- return;
1172
- }
1173
- view.dispatch(createEditorStateTransaction({
1174
- scrollTo,
1175
- selection
1176
- }));
1177
- }
1178
- }
1179
- }, [
1180
- view,
1181
- scrollTo,
1182
- selection
1183
- ]);
1184
- useEffect2(() => {
1185
- if (view && autoFocus) {
1186
- view.focus();
1187
- }
1188
- }, [
1189
- autoFocus,
1190
- view
1191
- ]);
1192
- const handleKeyDown = useCallback3((event) => {
1193
- const { key, target, currentTarget } = event;
1194
- switch (key) {
1195
- case "Escape": {
1196
- const modal = view?.state.field(modalStateField2, false);
1197
- if (modal) {
1198
- return;
1199
- }
1200
- const element = view?.contentDOM.closest('[tabindex="0"]');
1201
- element?.focus();
1202
- break;
1203
- }
1204
- case "Enter": {
1205
- event.preventDefault();
1206
- if (target === currentTarget) {
1207
- view?.focus();
1208
- }
1209
- break;
1210
- }
1211
- }
1212
- }, [
1213
- view
1214
- ]);
1215
- return {
1216
- parentRef,
1217
- view,
1218
- focusAttributes: {
1219
- tabIndex: 0,
1220
- onKeyDown: handleKeyDown
1221
- }
1222
- };
1223
- };
1224
-
1225
- // src/components/Editor/controller.ts
1226
- var withHiddenView = (controller) => {
1227
- Object.defineProperty(controller, "view", {
1228
- enumerable: false
1229
- });
1230
- return controller;
1231
- };
1232
- var noopController = withHiddenView({
1233
- get view() {
1234
- return null;
1235
- },
1236
- getText: () => "",
1237
- setText: () => {
1238
- },
1239
- focus: () => {
1240
- }
1241
- });
1242
- var createEditorController = (view) => {
1243
- return withHiddenView({
1244
- get view() {
1245
- return view;
1246
- },
1247
- getText: () => {
1248
- return view?.state.doc.toString() ?? "";
1249
- },
1250
- setText: (text, focus) => {
1251
- view?.dispatch({
1252
- changes: {
1253
- from: 0,
1254
- to: view?.state.doc.length ?? 0,
1255
- insert: text
1256
- },
1257
- selection: {
1258
- anchor: text.length,
1259
- head: text.length
1260
- }
1261
- });
1262
- if (focus) {
1263
- view?.focus();
1264
- }
1265
- },
1266
- focus: () => view?.focus()
1267
- });
1268
- };
1269
-
1270
- // src/components/Editor/EditorView.tsx
1271
- var EditorView3 = /* @__PURE__ */ forwardRef(({ classNames, id, extensions, selectionEnd, focusable = true, value, onChange, ...props }, forwardedRef) => {
1272
- const onChangeRef = useRef4(onChange);
1273
- onChangeRef.current = onChange;
1274
- const { parentRef, focusAttributes, view } = useTextEditor(() => ({
1275
- id,
1276
- initialValue: value,
1277
- selectionEnd,
1278
- extensions: [
1279
- extensions ?? [],
1280
- NaturalEditorView.updateListener.of(({ view: view2, docChanged, transactions }) => {
1281
- const isInitialSync = transactions.some((tr) => tr.annotation(Transaction.userEvent) === initialSync.value);
1282
- if (!isInitialSync && docChanged) {
1283
- onChangeRef.current?.(view2.state.doc.toString());
1284
- }
1285
- })
1286
- ],
1287
- ...props
1288
- }), [
1289
- id,
1290
- extensions,
1291
- selectionEnd
1292
- ]);
1293
- useImperativeHandle(forwardedRef, () => {
1294
- return createEditorController(view);
1295
- }, [
1296
- id,
1297
- view
1298
- ]);
1299
- useEffect3(() => {
1300
- if (!view) {
1301
- return;
1302
- }
1303
- const next = value ?? "";
1304
- if (view.state.doc.toString() === next) {
1305
- return;
1306
- }
1307
- requestAnimationFrame(() => {
1308
- if (view.state.doc.toString() === next) {
1309
- return;
1310
- }
1311
- view.dispatch({
1312
- annotations: initialSync,
1313
- changes: [
1314
- {
1315
- from: 0,
1316
- to: view.state.doc.length,
1317
- insert: next
1318
- }
1319
- ],
1320
- selection: selectionEnd ? {
1321
- anchor: next.length
1322
- } : void 0
1323
- });
1324
- if (selectionEnd) {
1325
- view.focus();
1326
- }
1327
- });
1328
- }, [
1329
- view,
1330
- value,
1331
- selectionEnd
1332
- ]);
1333
- return /* @__PURE__ */ React3.createElement("div", {
1334
- className: mx("w-full outline-hidden focus:border-accent-bg focus-within:border-focus-ring-subtle", classNames),
1335
- ...focusable ? focusAttributes : {},
1336
- ref: parentRef
1337
- });
1338
- });
1339
-
1340
- // src/components/Editor/Editor.tsx
1341
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/Editor/Editor.tsx";
1342
- var [EditorContextProvider, useEditorContext] = createContext("Editor");
1343
- var EditorRoot = /* @__PURE__ */ forwardRef2(({ children, extensions: extensionsProp, viewMode, numItems, ...props }, forwardedRef) => {
1344
- const state = useMemo5(() => Atom2.make({
1345
- viewMode
1346
- }), [
1347
- viewMode
1348
- ]);
1349
- const { groupsRef, extension, ...menuProps } = useEditorMenu(props);
1350
- const extensions = useMemo5(() => [
1351
- extension,
1352
- extensionsProp
1353
- ].filter(isNonNullable2).flat(), [
1354
- extension,
1355
- extensionsProp
1356
- ]);
1357
- const [controller, setController] = useState4(noopController);
1358
- useImperativeHandle2(forwardedRef, () => controller, [
1359
- controller
1360
- ]);
1361
- const getView = useCallback4(() => controller?.view ?? null, [
1362
- controller
1363
- ]);
1364
- return /* @__PURE__ */ React4.createElement(EditorContextProvider, {
1365
- controller,
1366
- setController,
1367
- extensions,
1368
- state
1369
- }, /* @__PURE__ */ React4.createElement(EditorMenuProvider, {
1370
- getView,
1371
- groups: groupsRef.current,
1372
- numItems,
1373
- ...menuProps
1374
- }, children));
1375
- });
1376
- EditorRoot.displayName = "Editor.Root";
1377
- var EDITOR_CONTENT_NAME = "Editor.Content";
1378
- var EditorContent = ({ classNames, children }) => {
1379
- return /* @__PURE__ */ React4.createElement("div", {
1380
- className: mx2("grid grid-rows-[min-content_1fr] h-full overflow-hidden", classNames)
1381
- }, children);
1382
- };
1383
- EditorContent.displayName = EDITOR_CONTENT_NAME;
1384
- var EDITOR_VIEW_NAME = "Editor.View";
1385
- var EditorView4 = ({ extensions: providedExtensions, ...props }) => {
1386
- const { extensions: additionalExtensions = [], setController } = useEditorContext(EDITOR_VIEW_NAME);
1387
- const extensions = useMemo5(() => [
1388
- additionalExtensions,
1389
- providedExtensions
1390
- ].filter(isNonNullable2).flat(), [
1391
- providedExtensions,
1392
- additionalExtensions
1393
- ]);
1394
- return /* @__PURE__ */ React4.createElement(EditorView3, {
1395
- ...props,
1396
- extensions,
1397
- ref: setController
1398
- });
1399
- };
1400
- EditorView4.displayName = EDITOR_VIEW_NAME;
1401
- var EDITOR_TOOLBAR_NAME = "Editor.Toolbar";
1402
- var EditorToolbar2 = (props) => {
1403
- const { controller, state } = useEditorContext(EDITOR_TOOLBAR_NAME);
1404
- const getView = useCallback4(() => {
1405
- invariant3(controller?.view, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 102, S: void 0, A: ["controller?.view", ""] });
1406
- return controller?.view;
1407
- }, [
1408
- controller
1409
- ]);
1410
- return /* @__PURE__ */ React4.createElement(EditorToolbar, {
1411
- ...props,
1412
- getView,
1413
- state
1414
- });
1415
- };
1416
- EditorToolbar2.displayName = EDITOR_TOOLBAR_NAME;
1417
- var Editor = {
1418
- Root: EditorRoot,
1419
- Toolbar: EditorToolbar2,
1420
- Content: EditorContent,
1421
- View: EditorView4
1422
- };
1423
-
1424
- // src/components/EditorPreviewProvider/EditorPreviewProvider.tsx
1425
- import { createContext as createContext2 } from "@radix-ui/react-context";
1426
- import React5, { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
1427
- import { addEventListener as addEventListener2 } from "@dxos/async";
1428
- import { DX_ANCHOR_ACTIVATE as DX_ANCHOR_ACTIVATE2, Popover as Popover2 } from "@dxos/react-ui";
1429
- var [EditorPreviewContextProvider, useEditorPreview] = createContext2("PreviewPopover", {});
1430
- var EditorPreviewProvider = ({ children, onLookup }) => {
1431
- const triggerRef = useRef5(null);
1432
- const [value, setValue] = useState5({});
1433
- const [open, setOpen] = useState5(false);
1434
- const handleActivate = useCallback5((event) => {
1435
- const { dxn, label, trigger } = event;
1436
- setValue((value2) => ({
1437
- ...value2,
1438
- link: {
1439
- label,
1440
- dxn
1441
- },
1442
- pending: true
1443
- }));
1444
- triggerRef.current = trigger;
1445
- queueMicrotask(() => setOpen(true));
1446
- void onLookup?.({
1447
- label,
1448
- dxn
1449
- }).then((target) => setValue((value2) => ({
1450
- ...value2,
1451
- target: target ?? void 0,
1452
- pending: false
1453
- })));
1454
- }, [
1455
- onLookup
1456
- ]);
1457
- const [root, setRoot] = useState5(null);
1458
- useEffect4(() => {
1459
- if (!root || !handleActivate) {
1460
- return;
1461
- }
1462
- return addEventListener2(root, DX_ANCHOR_ACTIVATE2, handleActivate, {
1463
- capture: true,
1464
- passive: false
1465
- });
1466
- }, [
1467
- root,
1468
- handleActivate
1469
- ]);
1470
- return /* @__PURE__ */ React5.createElement(EditorPreviewContextProvider, {
1471
- pending: value.pending,
1472
- link: value.link,
1473
- target: value.target
1474
- }, /* @__PURE__ */ React5.createElement(Popover2.Root, {
1475
- open,
1476
- onOpenChange: setOpen
1477
- }, /* @__PURE__ */ React5.createElement(Popover2.VirtualTrigger, {
1478
- virtualRef: triggerRef
1479
- }), /* @__PURE__ */ React5.createElement("div", {
1480
- className: "contents",
1481
- ref: setRoot
1482
- }, children)));
1483
- };
1484
-
1485
- // src/extensions/assistant-extension.tsx
1486
- import { forEachDiagnostic, linter, setDiagnostics } from "@codemirror/lint";
1487
- import { ChangeSet } from "@codemirror/state";
1488
- import { EditorView as EditorView5 } from "@codemirror/view";
1489
- import { log as log2 } from "@dxos/log";
1490
- import { safeParseJson, trim } from "@dxos/util";
1491
- var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/extensions/assistant-extension.tsx";
1492
- var DEFAULT_INSTRUCTIONS = trim`
1493
- Proofread the input text below.
1494
- Identify typos and grammatical errors.
1495
- Return ONLY a valid JSON array of objects with fields: "original" (string), "replacement" (string), "context" (string, 3-5 words around match).
1496
- --
1497
- `;
1498
- var underline = (color) => {
1499
- const svg = trim`
1500
- <svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">
1501
- <path d="m0 3 l2 -2 l1 0 l2 2 l1 0" stroke="${color}" fill="none" stroke-width="1"/>
1502
- </svg>
1503
- `;
1504
- return `url('data:image/svg+xml;base64,${btoa(svg)}') !important`;
1505
- };
1506
- var assistant = (options) => {
1507
- const styles = getComputedStyle(document.documentElement);
1508
- const style = {
1509
- info: styles.getPropertyValue("--color-green-500").trim(),
1510
- warning: styles.getPropertyValue("--color-orange-500").trim(),
1511
- error: styles.getPropertyValue("--color-rose-500").trim()
1512
- };
1513
- return [
1514
- assistantLinter(options),
1515
- EditorView5.baseTheme({
1516
- ".cm-lintRange-info": {
1517
- backgroundImage: underline(style.info)
1518
- },
1519
- ".cm-lintRange-warning": {
1520
- backgroundImage: underline(style.warning)
1521
- },
1522
- ".cm-lintRange-error": {
1523
- backgroundImage: underline(style.error)
1524
- },
1525
- ".cm-panels-bottom": {
1526
- borderTop: "1px solid var(--color-separator) !important"
1527
- },
1528
- ".cm-panel-lint .cm-panel": {
1529
- outline: "none !important"
1530
- },
1531
- /** @apply dx-button */
1532
- ".cm-panel button": {
1533
- color: "var(--color-base-fg) !important"
1534
- },
1535
- ".cm-panel.cm-panel-lint ul": {
1536
- color: "var(--color-base-fg) !important",
1537
- backgroundColor: "var(--color-base-surface) !important",
1538
- marginRight: "2rem !important"
1539
- },
1540
- ".cm-panel.cm-panel-lint ul [aria-selected]": {
1541
- color: "var(--color-base-fg) !important",
1542
- backgroundColor: "var(--color-base-surface) !important"
1543
- },
1544
- ".cm-panel.cm-panel-lint ul li": {
1545
- display: "grid",
1546
- gridTemplateColumns: "1fr auto",
1547
- alignItems: "center"
1548
- },
1549
- ".cm-panel.cm-panel-lint ul li .cm-diagnosticText": {
1550
- paddingRight: "8px !important"
1551
- },
1552
- ".cm-panel.cm-panel-lint ul li button.cm-diagnosticAction": {
1553
- margin: "none !important"
1554
- },
1555
- ".cm-diagnostic": {
1556
- padding: "0px 8px !important",
1557
- whiteSpace: "pre-wrap !important"
1558
- },
1559
- ".cm-diagnostic-info": {
1560
- border: "none !important"
1561
- }
1562
- })
1563
- ];
1564
- };
1565
- var isSuggestion = (value) => typeof value === "object" && value !== null && typeof value.original === "string" && typeof value.replacement === "string" && typeof value.context === "string";
1566
- var findSuggestionIndex = (content, suggestion) => {
1567
- const firstIdx = content.indexOf(suggestion.original);
1568
- if (firstIdx === -1) {
1569
- return -1;
1570
- }
1571
- const secondIdx = content.indexOf(suggestion.original, firstIdx + 1);
1572
- if (secondIdx === -1) {
1573
- return firstIdx;
1574
- }
1575
- const contextIdx = content.indexOf(suggestion.context);
1576
- if (contextIdx !== -1) {
1577
- const contextEnd = contextIdx + suggestion.context.length;
1578
- if (secondIdx >= contextIdx && secondIdx <= contextEnd) {
1579
- return secondIdx;
1580
- }
1581
- }
1582
- return firstIdx;
1583
- };
1584
- var replaceTextAndDropLintAtRange = (view, from, to, insert) => {
1585
- const kept = [];
1586
- forEachDiagnostic(view.state, (diagnostic, diagnosticFrom, diagnosticTo) => {
1587
- if (diagnosticFrom < to && diagnosticTo > from) {
1588
- return;
1589
- }
1590
- kept.push({
1591
- ...diagnostic,
1592
- from: diagnosticFrom,
1593
- to: diagnosticTo
1594
- });
1595
- });
1596
- const changeSet = ChangeSet.of({
1597
- from,
1598
- to,
1599
- insert
1600
- }, view.state.doc.length);
1601
- const next = kept.map((d) => ({
1602
- ...d,
1603
- from: changeSet.mapPos(d.from, 1),
1604
- to: changeSet.mapPos(d.to, -1)
1605
- }));
1606
- view.dispatch({
1607
- changes: {
1608
- from,
1609
- to,
1610
- insert
1611
- },
1612
- ...setDiagnostics(view.state, next)
1613
- });
1614
- };
1615
- var assistantLinter = ({ generate, instructions = DEFAULT_INSTRUCTIONS, autoPanel = true, delay = 2e3 }) => linter(async (view) => {
1616
- try {
1617
- const content = view.state.doc.toString();
1618
- const result = await generate({
1619
- instructions,
1620
- content
1621
- });
1622
- const [match] = result.match(/\[.*\]/s) ?? [];
1623
- const parsed = match ? safeParseJson(match, []) : [];
1624
- const suggestions = Array.isArray(parsed) ? parsed.filter(isSuggestion) : [];
1625
- if (suggestions && suggestions.length > 0) {
1626
- const diagnostics = [];
1627
- for (const suggestion of suggestions) {
1628
- const idx = findSuggestionIndex(content, suggestion);
1629
- if (idx !== -1) {
1630
- diagnostics.push({
1631
- from: idx,
1632
- to: idx + suggestion.original.length,
1633
- severity: "info",
1634
- message: `Suggestion: ${suggestion.replacement}`,
1635
- actions: [
1636
- {
1637
- name: "Apply",
1638
- apply: (view2, from, to) => {
1639
- replaceTextAndDropLintAtRange(view2, from, to, suggestion.replacement);
1640
- }
1641
- }
1642
- ]
1643
- });
1644
- }
1645
- }
1646
- return diagnostics;
1647
- }
1648
- } catch (err) {
1649
- log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 169, S: void 0 });
1650
- }
1651
- return [];
1652
- }, {
1653
- delay,
1654
- autoPanel
1655
- });
1656
- export {
1657
- Editor,
1658
- EditorMenuProvider,
1659
- EditorPreviewProvider,
1660
- assistant,
1661
- createEditorController,
1662
- createMenuGroup,
1663
- filterMenuGroups,
1664
- formattingCommands,
1665
- getMenuItem,
1666
- getNextMenuItem,
1667
- getPreviousMenuItem,
1668
- linkSlashCommands,
1669
- popover,
1670
- popoverRangeEffect,
1671
- popoverStateField,
1672
- useBasicMarkdownExtensions,
1673
- useEditorContext,
1674
- useEditorMenu,
1675
- useEditorPreview,
1676
- useTextEditor
1677
- };
1678
- //# sourceMappingURL=index.mjs.map