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