@dxos/react-ui-editor 0.8.4-main.ef1bc66f44 → 0.8.4-main.effb148878

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