@dxos/react-ui-editor 0.8.4-main.1068cf700f → 0.8.4-main.1c7ec43d41

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