@dxos/react-ui-editor 0.8.4-main.e098934 → 0.8.4-main.ead640a

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 (205) hide show
  1. package/dist/lib/browser/{chunk-22UMM3QJ.mjs → chunk-HL3YF6WC.mjs} +2 -2
  2. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3555 -3484
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs.map +2 -2
  7. package/dist/lib/browser/types/index.mjs +1 -1
  8. package/dist/lib/node-esm/{chunk-YXYQPV6R.mjs → chunk-YJZGD3LY.mjs} +2 -2
  9. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +3555 -3484
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  14. package/dist/lib/node-esm/types/index.mjs +1 -1
  15. package/dist/types/src/components/Editor/Editor.d.ts +24 -9
  16. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  17. package/dist/types/src/components/Editor/Editor.stories.d.ts +27 -0
  18. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorToolbar/util.d.ts +1 -1
  21. package/dist/types/src/components/index.d.ts +0 -1
  22. package/dist/types/src/components/index.d.ts.map +1 -1
  23. package/dist/types/src/extensions/{autocomplete.d.ts → autocomplete/autocomplete.d.ts} +1 -1
  24. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
  25. package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
  26. package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
  27. package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
  28. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
  29. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +20 -0
  30. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
  31. package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
  32. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  34. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  35. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
  36. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  37. package/dist/types/src/extensions/automerge/sync.d.ts +2 -2
  38. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  39. package/dist/types/src/extensions/autoscroll.d.ts +2 -2
  40. package/dist/types/src/extensions/autoscroll.d.ts.map +1 -1
  41. package/dist/types/src/extensions/factories.d.ts +7 -2
  42. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  43. package/dist/types/src/extensions/focus.d.ts.map +1 -1
  44. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  45. package/dist/types/src/extensions/index.d.ts +2 -1
  46. package/dist/types/src/extensions/index.d.ts.map +1 -1
  47. package/dist/types/src/extensions/json.d.ts +1 -1
  48. package/dist/types/src/extensions/json.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  51. package/dist/types/src/extensions/modes.d.ts +1 -1
  52. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  53. package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
  54. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
  55. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts +36 -0
  56. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts.map +1 -0
  57. package/dist/types/src/extensions/popover/index.d.ts +8 -0
  58. package/dist/types/src/extensions/popover/index.d.ts.map +1 -0
  59. package/dist/types/src/extensions/popover/menu-presets.d.ts +4 -0
  60. package/dist/types/src/extensions/popover/menu-presets.d.ts.map +1 -0
  61. package/dist/types/src/extensions/popover/menu.d.ts +24 -0
  62. package/dist/types/src/extensions/popover/menu.d.ts.map +1 -0
  63. package/dist/types/src/extensions/popover/modal.d.ts +7 -0
  64. package/dist/types/src/extensions/popover/modal.d.ts.map +1 -0
  65. package/dist/types/src/extensions/popover/popover.d.ts +47 -0
  66. package/dist/types/src/extensions/popover/popover.d.ts.map +1 -0
  67. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts +34 -0
  68. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts.map +1 -0
  69. package/dist/types/src/extensions/popover/util.d.ts +8 -0
  70. package/dist/types/src/extensions/popover/util.d.ts.map +1 -0
  71. package/dist/types/src/extensions/preview/preview.d.ts +0 -1
  72. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  73. package/dist/types/src/extensions/state.d.ts +2 -0
  74. package/dist/types/src/extensions/state.d.ts.map +1 -0
  75. package/dist/types/src/extensions/tags/streamer.d.ts.map +1 -1
  76. package/dist/types/src/extensions/tags/xml-tags.d.ts +1 -0
  77. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  78. package/dist/types/src/hooks/useTextEditor.d.ts +4 -8
  79. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  80. package/dist/types/src/stories/{Command.stories.d.ts → CommandDialog.stories.d.ts} +2 -3
  81. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +1 -0
  82. package/dist/types/src/stories/Comments.stories.d.ts +3 -4
  83. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  84. package/dist/types/src/stories/EditorToolbar.stories.d.ts +1 -2
  85. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  86. package/dist/types/src/stories/Experimental.stories.d.ts +3 -4
  87. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  88. package/dist/types/src/stories/Markdown.stories.d.ts +3 -4
  89. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
  90. package/dist/types/src/stories/Outliner.stories.d.ts +0 -1
  91. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  92. package/dist/types/src/stories/{CommandMenu.stories.d.ts → Popover.stories.d.ts} +6 -6
  93. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -0
  94. package/dist/types/src/stories/Preview.stories.d.ts +3 -4
  95. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  96. package/dist/types/src/stories/Tags.stories.d.ts +0 -1
  97. package/dist/types/src/stories/Tags.stories.d.ts.map +1 -1
  98. package/dist/types/src/stories/TextEditor.stories.d.ts +3 -5
  99. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  100. package/dist/types/src/stories/components/EditorStory.d.ts +5 -5
  101. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  102. package/dist/types/src/styles/theme.d.ts.map +1 -1
  103. package/dist/types/src/testing/PreviewPopover.d.ts.map +1 -1
  104. package/dist/types/src/types/types.d.ts +1 -1
  105. package/dist/types/src/types/types.d.ts.map +1 -1
  106. package/dist/types/src/util/index.d.ts +0 -1
  107. package/dist/types/src/util/index.d.ts.map +1 -1
  108. package/dist/types/tsconfig.tsbuildinfo +1 -1
  109. package/package.json +54 -51
  110. package/src/components/Editor/Editor.stories.tsx +69 -0
  111. package/src/components/Editor/Editor.tsx +57 -14
  112. package/src/components/EditorToolbar/EditorToolbar.tsx +1 -0
  113. package/src/components/index.ts +0 -1
  114. package/src/extensions/{autocomplete.ts → autocomplete/autocomplete.ts} +2 -1
  115. package/src/extensions/autocomplete/index.ts +8 -0
  116. package/src/extensions/autocomplete/match.ts +46 -0
  117. package/src/extensions/{command → autocomplete}/placeholder.ts +21 -17
  118. package/src/extensions/{command → autocomplete}/typeahead.ts +6 -48
  119. package/src/extensions/automerge/automerge.stories.tsx +8 -8
  120. package/src/extensions/automerge/automerge.ts +28 -9
  121. package/src/extensions/automerge/sync.ts +7 -3
  122. package/src/extensions/autoscroll.ts +29 -29
  123. package/src/extensions/factories.ts +41 -12
  124. package/src/extensions/focus.ts +5 -4
  125. package/src/extensions/folding.tsx +4 -6
  126. package/src/extensions/hashtag.tsx +2 -2
  127. package/src/extensions/index.ts +2 -1
  128. package/src/extensions/json.ts +1 -1
  129. package/src/extensions/markdown/bundle.ts +16 -4
  130. package/src/extensions/markdown/decorate.ts +1 -0
  131. package/src/extensions/modes.ts +2 -2
  132. package/src/extensions/{command/floating-menu.ts → outliner/menu.ts} +9 -9
  133. package/src/extensions/outliner/outliner.ts +2 -2
  134. package/src/extensions/popover/PopoverMenuProvider.tsx +221 -0
  135. package/src/extensions/popover/index.ts +12 -0
  136. package/src/extensions/popover/menu-presets.ts +124 -0
  137. package/src/extensions/popover/menu.ts +67 -0
  138. package/src/extensions/popover/modal.ts +24 -0
  139. package/src/extensions/popover/popover.ts +291 -0
  140. package/src/extensions/popover/usePopoverMenu.ts +173 -0
  141. package/src/extensions/popover/util.ts +29 -0
  142. package/src/extensions/preview/index.ts +1 -1
  143. package/src/extensions/preview/preview.ts +0 -2
  144. package/src/extensions/selection.ts +2 -2
  145. package/src/extensions/state.ts +7 -0
  146. package/src/extensions/tags/streamer.ts +4 -5
  147. package/src/extensions/tags/xml-tags.ts +59 -1
  148. package/src/hooks/useTextEditor.ts +27 -27
  149. package/src/stories/{Command.stories.tsx → CommandDialog.stories.tsx} +10 -22
  150. package/src/stories/Comments.stories.tsx +5 -5
  151. package/src/stories/EditorToolbar.stories.tsx +6 -5
  152. package/src/stories/Experimental.stories.tsx +6 -6
  153. package/src/stories/Markdown.stories.tsx +5 -5
  154. package/src/stories/Outliner.stories.tsx +21 -14
  155. package/src/stories/Popover.stories.tsx +163 -0
  156. package/src/stories/Preview.stories.tsx +5 -5
  157. package/src/stories/Tags.stories.tsx +5 -5
  158. package/src/stories/TextEditor.stories.tsx +7 -32
  159. package/src/stories/components/EditorStory.tsx +7 -5
  160. package/src/styles/theme.ts +12 -10
  161. package/src/testing/PreviewPopover.tsx +2 -0
  162. package/src/types/types.ts +1 -1
  163. package/src/util/index.ts +0 -1
  164. package/dist/lib/browser/chunk-22UMM3QJ.mjs.map +0 -7
  165. package/dist/lib/node-esm/chunk-YXYQPV6R.mjs.map +0 -7
  166. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts +0 -38
  167. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts.map +0 -1
  168. package/dist/types/src/components/CommandMenu/index.d.ts +0 -2
  169. package/dist/types/src/components/CommandMenu/index.d.ts.map +0 -1
  170. package/dist/types/src/extensions/autocomplete.d.ts.map +0 -1
  171. package/dist/types/src/extensions/command/action.d.ts +0 -17
  172. package/dist/types/src/extensions/command/action.d.ts.map +0 -1
  173. package/dist/types/src/extensions/command/command-menu.d.ts +0 -20
  174. package/dist/types/src/extensions/command/command-menu.d.ts.map +0 -1
  175. package/dist/types/src/extensions/command/command.d.ts +0 -6
  176. package/dist/types/src/extensions/command/command.d.ts.map +0 -1
  177. package/dist/types/src/extensions/command/floating-menu.d.ts +0 -7
  178. package/dist/types/src/extensions/command/floating-menu.d.ts.map +0 -1
  179. package/dist/types/src/extensions/command/hint.d.ts +0 -19
  180. package/dist/types/src/extensions/command/hint.d.ts.map +0 -1
  181. package/dist/types/src/extensions/command/index.d.ts +0 -7
  182. package/dist/types/src/extensions/command/index.d.ts.map +0 -1
  183. package/dist/types/src/extensions/command/placeholder.d.ts +0 -10
  184. package/dist/types/src/extensions/command/placeholder.d.ts.map +0 -1
  185. package/dist/types/src/extensions/command/state.d.ts +0 -16
  186. package/dist/types/src/extensions/command/state.d.ts.map +0 -1
  187. package/dist/types/src/extensions/command/typeahead.d.ts +0 -22
  188. package/dist/types/src/extensions/command/typeahead.d.ts.map +0 -1
  189. package/dist/types/src/extensions/command/useCommandMenu.d.ts +0 -25
  190. package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +0 -1
  191. package/dist/types/src/stories/Command.stories.d.ts.map +0 -1
  192. package/dist/types/src/stories/CommandMenu.stories.d.ts.map +0 -1
  193. package/dist/types/src/util/domino.d.ts +0 -18
  194. package/dist/types/src/util/domino.d.ts.map +0 -1
  195. package/src/components/CommandMenu/CommandMenu.tsx +0 -346
  196. package/src/components/CommandMenu/index.ts +0 -5
  197. package/src/extensions/command/action.ts +0 -55
  198. package/src/extensions/command/command-menu.ts +0 -211
  199. package/src/extensions/command/command.ts +0 -34
  200. package/src/extensions/command/hint.ts +0 -103
  201. package/src/extensions/command/index.ts +0 -10
  202. package/src/extensions/command/state.ts +0 -90
  203. package/src/extensions/command/useCommandMenu.ts +0 -115
  204. package/src/stories/CommandMenu.stories.tsx +0 -158
  205. package/src/util/domino.ts +0 -51
@@ -0,0 +1,221 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type EditorView } from '@codemirror/view';
6
+ import { useControllableState } from '@radix-ui/react-use-controllable-state';
7
+ import React, { Fragment, type PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
8
+
9
+ import { addEventListener } from '@dxos/async';
10
+ import { invariant } from '@dxos/invariant';
11
+ import {
12
+ type DxAnchorActivate,
13
+ Icon,
14
+ Popover,
15
+ toLocalizedString,
16
+ useDynamicRef,
17
+ useThemeContext,
18
+ useTranslation,
19
+ } from '@dxos/react-ui';
20
+
21
+ import { type PopoverMenuGroup, type PopoverMenuItem } from './menu';
22
+
23
+ export type PopoverMenuProviderProps = PropsWithChildren<{
24
+ view?: EditorView | null;
25
+ groups: PopoverMenuGroup[];
26
+ currentItem?: string;
27
+ open?: boolean;
28
+ defaultOpen?: boolean;
29
+ numItems?: number;
30
+ onOpenChange?: (event: { view: EditorView; open: boolean; trigger?: string }) => void;
31
+ onActivate?: (event: { view: EditorView; trigger?: string }) => void;
32
+ onSelect?: (event: { view: EditorView; item: PopoverMenuItem }) => void;
33
+ onCancel?: (event: { view: EditorView }) => void;
34
+ }>;
35
+
36
+ /**
37
+ * Implements the Popover and listens for the `dx-anchor-activate` event from the
38
+ * `popover` extension's decoration.
39
+ *
40
+ * NOTE: We don't use DropdownMenu because the command menu needs to manage focus explicitly.
41
+ * I.e., focus must remain in the editor while displaying the menu (for type-ahead).
42
+ */
43
+ export const PopoverMenuProvider = ({
44
+ children,
45
+ view,
46
+ groups,
47
+ currentItem,
48
+ open: openParam,
49
+ defaultOpen,
50
+ numItems = 8,
51
+ onOpenChange,
52
+ onActivate,
53
+ onSelect,
54
+ onCancel,
55
+ }: PopoverMenuProviderProps) => {
56
+ const { tx } = useThemeContext();
57
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
58
+ const [root, setRoot] = useState<HTMLDivElement | null>(null);
59
+ const viewRef = useDynamicRef(view);
60
+ const [open, setOpen] = useControllableState({
61
+ prop: openParam,
62
+ defaultProp: defaultOpen,
63
+ onChange: (open) => {
64
+ invariant(viewRef.current);
65
+ onOpenChange?.({ view: viewRef.current, open });
66
+ },
67
+ });
68
+
69
+ useEffect(() => {
70
+ if (!root) {
71
+ return;
72
+ }
73
+
74
+ // Listen for trigger.
75
+ return addEventListener(
76
+ root,
77
+ 'dx-anchor-activate' as any,
78
+ (event: DxAnchorActivate) => {
79
+ const { trigger, refId } = event;
80
+ console.log('update', trigger, refId);
81
+
82
+ // If this has a `refId`, then it’s probably a URL or DXN and out of scope for this component.
83
+ if (!refId) {
84
+ triggerRef.current = trigger as HTMLButtonElement;
85
+ if (onActivate) {
86
+ onActivate({ view: viewRef.current!, trigger: trigger.getAttribute('data-trigger') ?? undefined });
87
+ } else {
88
+ requestAnimationFrame(() => setOpen(true));
89
+ }
90
+ }
91
+ },
92
+ {
93
+ capture: true,
94
+ passive: false,
95
+ },
96
+ );
97
+ }, [root, onActivate]);
98
+
99
+ const handleSelect = useCallback<NonNullable<MenuProps['onSelect']>>(
100
+ (item) => {
101
+ invariant(viewRef.current);
102
+ onSelect?.({ view: viewRef.current, item });
103
+ },
104
+ [viewRef, onSelect],
105
+ );
106
+
107
+ const menuGroups = groups.filter((group) => group.items.length > 0);
108
+
109
+ return (
110
+ <Popover.Root modal={false} open={open} onOpenChange={setOpen}>
111
+ <Popover.VirtualTrigger virtualRef={triggerRef} />
112
+ <Popover.Portal>
113
+ <Popover.Content
114
+ align='start'
115
+ classNames={tx('menu.content', 'menu--exotic-unfocusable', { elevation: 'positioned' }, [
116
+ 'overflow-y-auto',
117
+ !menuGroups.length && 'hidden',
118
+ ])}
119
+ style={{
120
+ maxBlockSize: 36 * numItems + 10,
121
+ }}
122
+ /**
123
+ * NOTE: We keep the focus in the editor, but Radix routes escape key.
124
+ */
125
+ onEscapeKeyDown={() => {
126
+ // NOTE: Able to cancel if not in valid state.
127
+ // event.preventDefault();
128
+ onCancel?.({ view: view! });
129
+ }}
130
+ onOpenAutoFocus={(event) => event.preventDefault()}
131
+ >
132
+ <Popover.Viewport classNames={tx('menu.viewport', 'menu__viewport--exotic-unfocusable', {})}>
133
+ <Menu groups={menuGroups} currentItem={currentItem} onSelect={handleSelect} />
134
+ </Popover.Viewport>
135
+ <Popover.Arrow />
136
+ </Popover.Content>
137
+ </Popover.Portal>
138
+
139
+ <div ref={setRoot} role='none' className='contents'>
140
+ {children}
141
+ </div>
142
+ </Popover.Root>
143
+ );
144
+ };
145
+
146
+ //
147
+ // Menu
148
+ //
149
+
150
+ type MenuProps = {
151
+ groups: PopoverMenuGroup[];
152
+ } & Pick<MenuGroupProps, 'currentItem' | 'onSelect'>;
153
+
154
+ const Menu = ({ groups, currentItem, onSelect }: MenuProps) => {
155
+ const { tx } = useThemeContext();
156
+ return (
157
+ <ul>
158
+ {groups.map((group, index) => (
159
+ <Fragment key={group.id}>
160
+ <MenuGroup group={group} currentItem={currentItem} onSelect={onSelect} />
161
+ {index < groups.length - 1 && <div className={tx('menu.separator', 'menu__item', {})} />}
162
+ </Fragment>
163
+ ))}
164
+ </ul>
165
+ );
166
+ };
167
+
168
+ type MenuGroupProps = {
169
+ group: PopoverMenuGroup;
170
+ currentItem?: string;
171
+ } & Pick<MenuItemProps, 'onSelect'>;
172
+
173
+ const MenuGroup = ({ group, currentItem, onSelect }: MenuGroupProps) => {
174
+ const { tx } = useThemeContext();
175
+ const { t } = useTranslation();
176
+
177
+ return (
178
+ <>
179
+ {group.label && (
180
+ <div className={tx('menu.groupLabel', 'menu__group__label', {})}>
181
+ <span>{toLocalizedString(group.label, t)}</span>
182
+ </div>
183
+ )}
184
+
185
+ {group.items.map((item) => (
186
+ <MenuItem key={item.id} item={item} current={currentItem === item.id} onSelect={onSelect} />
187
+ ))}
188
+ </>
189
+ );
190
+ };
191
+
192
+ type MenuItemProps = {
193
+ item: PopoverMenuItem;
194
+ current: boolean;
195
+ onSelect?: (item: PopoverMenuItem) => void;
196
+ };
197
+
198
+ const MenuItem = ({ item, current, onSelect }: MenuItemProps) => {
199
+ const { tx } = useThemeContext();
200
+ const { t } = useTranslation();
201
+
202
+ const listRef = useRef<HTMLLIElement>(null);
203
+ useEffect(() => {
204
+ if (current && listRef.current) {
205
+ listRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
206
+ }
207
+ }, [current]);
208
+
209
+ const handleSelect = useCallback(() => onSelect?.(item), [item, onSelect]);
210
+
211
+ return (
212
+ <li
213
+ ref={listRef}
214
+ className={tx('menu.item', 'menu__item--exotic-unfocusable', {}, [current && 'bg-hoverSurface'])}
215
+ onClick={handleSelect}
216
+ >
217
+ {item.icon && <Icon icon={item.icon} size={5} />}
218
+ <span className='grow truncate'>{toLocalizedString(item.label, t)}</span>
219
+ </li>
220
+ );
221
+ };
@@ -0,0 +1,12 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './menu';
6
+ export * from './menu-presets';
7
+ export * from './modal';
8
+ export * from './popover';
9
+ export * from './util';
10
+
11
+ export * from './PopoverMenuProvider';
12
+ export * from './usePopoverMenu';
@@ -0,0 +1,124 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { popoverRangeEffect } from '../../extensions';
6
+
7
+ import { type PopoverMenuGroup } from './menu';
8
+ import { insertAtLineStart } from './util';
9
+
10
+ export const formattingCommands: PopoverMenuGroup = {
11
+ id: 'markdown',
12
+ label: 'Markdown',
13
+ items: [
14
+ {
15
+ id: 'heading-1',
16
+ label: 'Heading 1',
17
+ icon: 'ph--text-h-one--regular',
18
+ onSelect: (view, head) => insertAtLineStart(view, head, '# '),
19
+ },
20
+ {
21
+ id: 'heading-2',
22
+ label: 'Heading 2',
23
+ icon: 'ph--text-h-two--regular',
24
+ onSelect: (view, head) => insertAtLineStart(view, head, '## '),
25
+ },
26
+ {
27
+ id: 'heading-3',
28
+ label: 'Heading 3',
29
+ icon: 'ph--text-h-three--regular',
30
+ onSelect: (view, head) => insertAtLineStart(view, head, '### '),
31
+ },
32
+ {
33
+ id: 'heading-4',
34
+ label: 'Heading 4',
35
+ icon: 'ph--text-h-four--regular',
36
+ onSelect: (view, head) => insertAtLineStart(view, head, '#### '),
37
+ },
38
+ {
39
+ id: 'heading-5',
40
+ label: 'Heading 5',
41
+ icon: 'ph--text-h-five--regular',
42
+ onSelect: (view, head) => insertAtLineStart(view, head, '##### '),
43
+ },
44
+ {
45
+ id: 'heading-6',
46
+ label: 'Heading 6',
47
+ icon: 'ph--text-h-six--regular',
48
+ onSelect: (view, head) => insertAtLineStart(view, head, '###### '),
49
+ },
50
+ {
51
+ id: 'bullet-list',
52
+ label: 'Bullet List',
53
+ icon: 'ph--list-bullets--regular',
54
+ onSelect: (view, head) => insertAtLineStart(view, head, '- '),
55
+ },
56
+ {
57
+ id: 'numbered-list',
58
+ label: 'Numbered List',
59
+ icon: 'ph--list-numbers--regular',
60
+ onSelect: (view, head) => insertAtLineStart(view, head, '1. '),
61
+ },
62
+ {
63
+ id: 'task-list',
64
+ label: 'Task List',
65
+ icon: 'ph--list-checks--regular',
66
+ onSelect: (view, head) => insertAtLineStart(view, head, '- [ ] '),
67
+ },
68
+ {
69
+ id: 'quote',
70
+ label: 'Quote',
71
+ icon: 'ph--quotes--regular',
72
+ onSelect: (view, head) => insertAtLineStart(view, head, '> '),
73
+ },
74
+ {
75
+ id: 'code-block',
76
+ label: 'Code Block',
77
+ icon: 'ph--code-block--regular',
78
+ onSelect: (view, head) => insertAtLineStart(view, head, '```\n\n```'),
79
+ },
80
+ {
81
+ id: 'table',
82
+ label: 'Table',
83
+ icon: 'ph--table--regular',
84
+ onSelect: (view, head) => insertAtLineStart(view, head, '| | | |\n|---|---|---|\n| | | |'),
85
+ },
86
+ ],
87
+ };
88
+
89
+ export const linkSlashCommands: PopoverMenuGroup = {
90
+ id: 'link',
91
+ label: 'Link',
92
+ items: [
93
+ {
94
+ id: 'inline-link',
95
+ label: 'Inline link',
96
+ icon: 'ph--link--regular',
97
+ onSelect: (view, head) => {
98
+ view.dispatch({
99
+ changes: { from: head, insert: '@' },
100
+ selection: { anchor: head + 1, head: head + 1 },
101
+ effects: popoverRangeEffect.of({
102
+ trigger: '@',
103
+ range: { from: head, to: head + 1 },
104
+ }),
105
+ });
106
+ },
107
+ },
108
+ {
109
+ id: 'block-embed',
110
+ label: 'Block embed',
111
+ icon: 'ph--lego--regular',
112
+ onSelect: (view, head) => {
113
+ view.dispatch({
114
+ changes: { from: head, insert: '@@' },
115
+ selection: { anchor: head + 2, head: head + 2 },
116
+ effects: popoverRangeEffect.of({
117
+ trigger: '@',
118
+ range: { from: head, to: head + 2 },
119
+ }),
120
+ });
121
+ },
122
+ },
123
+ ],
124
+ };
@@ -0,0 +1,67 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type EditorView } from '@codemirror/view';
6
+
7
+ import { type Label } from '@dxos/react-ui';
8
+ import { type MaybePromise } from '@dxos/util';
9
+
10
+ import { insertAtCursor } from './util';
11
+
12
+ export type PopoverMenuGroup = {
13
+ id: string;
14
+ label?: Label;
15
+ items: PopoverMenuItem[];
16
+ };
17
+
18
+ export type PopoverMenuItem = {
19
+ id: string;
20
+ label: Label;
21
+ icon?: string;
22
+ onSelect?: (view: EditorView, head: number) => MaybePromise<void>;
23
+ };
24
+
25
+ export const getMenuItem = (groups: PopoverMenuGroup[], id?: string): PopoverMenuItem | undefined => {
26
+ return groups.flatMap((group) => group.items).find((item) => item.id === id);
27
+ };
28
+
29
+ export const getNextMenuItem = (groups: PopoverMenuGroup[], id?: string): PopoverMenuItem => {
30
+ const items = groups.flatMap((group) => group.items);
31
+ const index = items.findIndex((item) => item.id === id);
32
+ return index < items.length - 1 ? items[index + 1] : items[index];
33
+ };
34
+
35
+ export const getPreviousMenuItem = (groups: PopoverMenuGroup[], id?: string): PopoverMenuItem => {
36
+ const items = groups.flatMap((group) => group.items);
37
+ const index = items.findIndex((item) => item.id === id);
38
+ return index > 0 ? items[index - 1] : items[index];
39
+ };
40
+
41
+ export const createMenuGroup = ({
42
+ id = 'menu',
43
+ label,
44
+ items,
45
+ }: {
46
+ id?: string;
47
+ label?: Label;
48
+ items: string[];
49
+ }): PopoverMenuGroup => ({
50
+ id,
51
+ label,
52
+ items: items.map((item, i) => ({
53
+ id: `${id}-${i}`,
54
+ label: item,
55
+ onSelect: (view, head) => insertAtCursor(view, head, item),
56
+ })),
57
+ });
58
+
59
+ export const filterMenuGroups = (
60
+ groups: PopoverMenuGroup[],
61
+ filter: (item: PopoverMenuItem) => boolean,
62
+ ): PopoverMenuGroup[] => {
63
+ return groups.map((group) => ({
64
+ ...group,
65
+ items: group.items.filter(filter),
66
+ }));
67
+ };
@@ -0,0 +1,24 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { StateEffect, StateField } from '@codemirror/state';
6
+
7
+ export const modalStateEffect = StateEffect.define<boolean>();
8
+
9
+ /**
10
+ * Determines if a modal dialog (e.g., popover) is active.
11
+ */
12
+ export const modalStateField = StateField.define<boolean>({
13
+ create: () => false,
14
+ update: (value, tr) => {
15
+ let newValue = value;
16
+ for (const effect of tr.effects) {
17
+ if (effect.is(modalStateEffect)) {
18
+ newValue = effect.value;
19
+ }
20
+ }
21
+
22
+ return newValue;
23
+ },
24
+ });