@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.
- package/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/browser/index.mjs +796 -755
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +39 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +796 -755
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +41 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/Editor/Editor.d.ts +36 -25
- package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
- package/dist/types/src/components/Editor/Editor.stories.d.ts +4 -4
- package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -1
- package/dist/types/src/components/{EditorContent/EditorContent.d.ts → Editor/EditorView.d.ts} +5 -5
- package/dist/types/src/components/Editor/EditorView.d.ts.map +1 -0
- package/dist/types/src/components/Editor/controller.d.ts.map +1 -0
- package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts +1 -3
- package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts.map +1 -1
- package/dist/types/src/components/EditorMenuProvider/menu-presets.d.ts.map +1 -1
- package/dist/types/src/components/EditorMenuProvider/menu.d.ts.map +1 -1
- package/dist/types/src/components/EditorMenuProvider/popover.d.ts +2 -1
- package/dist/types/src/components/EditorMenuProvider/popover.d.ts.map +1 -1
- package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
- package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +2 -2
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/blocks.d.ts +4 -18
- package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/formatting.d.ts +4 -18
- package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/headings.d.ts +4 -18
- package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/image.d.ts +3 -8
- package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts +1 -2
- package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/lists.d.ts +6 -0
- package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/search.d.ts +3 -8
- package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/types.d.ts +6 -0
- package/dist/types/src/components/EditorToolbar/types.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/view-mode.d.ts +5 -19
- package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/extensions/Assistant.stories.d.ts +10 -0
- package/dist/types/src/extensions/Assistant.stories.d.ts.map +1 -0
- package/dist/types/src/extensions/assistant-extension.d.ts +24 -0
- package/dist/types/src/extensions/assistant-extension.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useBasicMarkdownExtensions.d.ts +25 -0
- package/dist/types/src/hooks/useBasicMarkdownExtensions.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/stories/Automerge.stories.d.ts +25 -24
- package/dist/types/src/stories/Automerge.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Comments.stories.d.ts +2 -2
- package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
- package/dist/types/src/stories/EditorToolbar.stories.d.ts +28 -26
- package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Experimental.stories.d.ts +3 -3
- package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Markdown.stories.d.ts +2 -2
- package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Outliner.stories.d.ts +2 -2
- package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Popover.stories.d.ts +2 -2
- package/dist/types/src/stories/Popover.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Preview.stories.d.ts +2 -2
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Tags.stories.d.ts.map +1 -1
- package/dist/types/src/stories/TextEditor.stories.d.ts +2 -2
- package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Theme.stories.d.ts.map +1 -1
- package/dist/types/src/stories/components/EditorStory.d.ts +4 -4
- package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
- package/dist/types/src/stories/components/util.d.ts +3 -2
- package/dist/types/src/stories/components/util.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +24 -24
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/react.d.ts +2 -5
- package/dist/types/src/util/react.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +77 -68
- package/src/components/Editor/Editor.stories.tsx +15 -21
- package/src/components/Editor/Editor.tsx +54 -53
- package/src/components/Editor/EditorView.tsx +102 -0
- package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +19 -24
- package/src/components/EditorMenuProvider/menu-presets.ts +1 -0
- package/src/components/EditorMenuProvider/popover.ts +3 -1
- package/src/components/EditorMenuProvider/useEditorMenu.ts +8 -1
- package/src/components/EditorPreviewProvider/EditorPreviewProvider.tsx +1 -1
- package/src/components/EditorToolbar/EditorToolbar.tsx +31 -65
- package/src/components/EditorToolbar/blocks.ts +54 -46
- package/src/components/EditorToolbar/formatting.ts +44 -45
- package/src/components/EditorToolbar/headings.ts +44 -50
- package/src/components/EditorToolbar/image.ts +16 -21
- package/src/components/EditorToolbar/index.ts +2 -3
- package/src/components/EditorToolbar/lists.ts +58 -0
- package/src/components/EditorToolbar/search.ts +16 -21
- package/src/components/EditorToolbar/types.ts +8 -0
- package/src/components/EditorToolbar/view-mode.ts +37 -43
- package/src/components/index.ts +0 -3
- package/src/extensions/Assistant.stories.tsx +112 -0
- package/src/extensions/assistant-extension.tsx +223 -0
- package/src/extensions/index.ts +5 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useBasicMarkdownExtensions.ts +55 -0
- package/src/index.ts +1 -4
- package/src/stories/Automerge.stories.tsx +18 -16
- package/src/stories/Comments.stories.tsx +6 -6
- package/src/stories/EditorToolbar.stories.tsx +37 -65
- package/src/stories/Experimental.stories.tsx +12 -12
- package/src/stories/Markdown.stories.tsx +2 -2
- package/src/stories/Outliner.stories.tsx +4 -5
- package/src/stories/Popover.stories.tsx +10 -11
- package/src/stories/Preview.stories.tsx +51 -43
- package/src/stories/Tags.stories.tsx +5 -5
- package/src/stories/TextEditor.stories.tsx +2 -2
- package/src/stories/Theme.stories.tsx +4 -4
- package/src/stories/components/EditorStory.tsx +19 -12
- package/src/stories/components/util.tsx +49 -50
- package/src/translations.ts +29 -24
- package/src/util/react.tsx +4 -13
- package/dist/types/src/components/EditorContent/EditorContent.d.ts.map +0 -1
- package/dist/types/src/components/EditorContent/controller.d.ts.map +0 -1
- package/dist/types/src/components/EditorContent/index.d.ts +0 -3
- package/dist/types/src/components/EditorContent/index.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/actions.d.ts +0 -24
- package/dist/types/src/components/EditorToolbar/actions.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/useEditorToolbar.d.ts +0 -11
- package/dist/types/src/components/EditorToolbar/useEditorToolbar.d.ts.map +0 -1
- package/dist/types/src/stories/CommandDialog.stories.d.ts +0 -14
- package/dist/types/src/stories/CommandDialog.stories.d.ts.map +0 -1
- package/src/components/EditorContent/EditorContent.tsx +0 -83
- package/src/components/EditorContent/index.ts +0 -6
- package/src/components/EditorToolbar/actions.ts +0 -87
- package/src/components/EditorToolbar/useEditorToolbar.ts +0 -20
- package/src/stories/CommandDialog.stories.tsx +0 -81
- /package/dist/types/src/components/{EditorContent → Editor}/controller.d.ts +0 -0
- /package/src/components/{EditorContent → Editor}/controller.ts +0 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type DxAnchorActivate,
|
|
14
14
|
Icon,
|
|
15
15
|
Popover,
|
|
16
|
+
ScrollArea,
|
|
16
17
|
toLocalizedString,
|
|
17
18
|
useDynamicRef,
|
|
18
19
|
useThemeContext,
|
|
@@ -35,9 +36,7 @@ export type EditorMenuProviderProps = PropsWithChildren<{
|
|
|
35
36
|
}>;
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
* Implements the Popover and listens for the `dx-anchor-activate` event from the
|
|
39
|
-
* `popover` extension's decoration.
|
|
40
|
-
*
|
|
39
|
+
* Implements the Popover and listens for the `dx-anchor-activate` event from the `popover` extension's decoration.
|
|
41
40
|
* NOTE: We don't use DropdownMenu because the command menu needs to manage focus explicitly.
|
|
42
41
|
* I.e., focus must remain in the editor while displaying the menu (for type-ahead).
|
|
43
42
|
*/
|
|
@@ -113,32 +112,32 @@ export const EditorMenuProvider = ({
|
|
|
113
112
|
<Popover.Portal>
|
|
114
113
|
<Popover.Content
|
|
115
114
|
align='start'
|
|
116
|
-
classNames={
|
|
117
|
-
'overflow-y-auto',
|
|
118
|
-
!menuGroups.length && 'hidden',
|
|
119
|
-
])}
|
|
115
|
+
classNames={['flex flex-col', !menuGroups.length && 'hidden']}
|
|
120
116
|
style={{
|
|
121
117
|
maxBlockSize: 36 * numItems + 10,
|
|
122
118
|
}}
|
|
123
|
-
|
|
124
|
-
* NOTE: We keep the focus in the editor, but Radix routes escape key.
|
|
125
|
-
*/
|
|
119
|
+
// NOTE: We keep the focus in the editor, but Radix routes escape key.
|
|
126
120
|
onEscapeKeyDown={() => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
121
|
+
const currentView = viewRef.current;
|
|
122
|
+
if (currentView) {
|
|
123
|
+
onCancel?.({ view: currentView });
|
|
124
|
+
}
|
|
130
125
|
}}
|
|
131
126
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
132
127
|
>
|
|
133
|
-
<Popover.Viewport classNames=
|
|
134
|
-
<
|
|
128
|
+
<Popover.Viewport asChild classNames='dx-container'>
|
|
129
|
+
<ScrollArea.Root thin>
|
|
130
|
+
<ScrollArea.Viewport>
|
|
131
|
+
<Menu groups={menuGroups} currentItem={currentItem} onSelect={handleSelect} />
|
|
132
|
+
</ScrollArea.Viewport>
|
|
133
|
+
</ScrollArea.Root>
|
|
135
134
|
</Popover.Viewport>
|
|
136
135
|
<Popover.Arrow />
|
|
137
136
|
</Popover.Content>
|
|
138
137
|
</Popover.Portal>
|
|
139
138
|
|
|
140
139
|
{/* Content */}
|
|
141
|
-
<div ref={setRoot}
|
|
140
|
+
<div className='contents' ref={setRoot}>
|
|
142
141
|
{children}
|
|
143
142
|
</div>
|
|
144
143
|
</Popover.Root>
|
|
@@ -160,7 +159,7 @@ const Menu = ({ groups, currentItem, onSelect }: MenuProps) => {
|
|
|
160
159
|
{groups.map((group, index) => (
|
|
161
160
|
<Fragment key={group.id}>
|
|
162
161
|
<MenuGroup group={group} currentItem={currentItem} onSelect={onSelect} />
|
|
163
|
-
{index < groups.length - 1 && <div className={tx('menu.separator',
|
|
162
|
+
{index < groups.length - 1 && <div className={tx('menu.separator', {})} />}
|
|
164
163
|
</Fragment>
|
|
165
164
|
))}
|
|
166
165
|
</ul>
|
|
@@ -183,7 +182,7 @@ const MenuGroup = ({ group, currentItem, onSelect }: MenuGroupProps) => {
|
|
|
183
182
|
return (
|
|
184
183
|
<>
|
|
185
184
|
{group.label && (
|
|
186
|
-
<div className={tx('menu.groupLabel',
|
|
185
|
+
<div className={tx('menu.groupLabel', {})}>
|
|
187
186
|
<span>{toLocalizedString(group.label, t)}</span>
|
|
188
187
|
</div>
|
|
189
188
|
)}
|
|
@@ -219,12 +218,8 @@ const MenuItem = ({ item, current, onSelect }: MenuItemProps) => {
|
|
|
219
218
|
const handleSelect = useCallback(() => onSelect?.(item), [item, onSelect]);
|
|
220
219
|
|
|
221
220
|
return (
|
|
222
|
-
<li
|
|
223
|
-
|
|
224
|
-
className={tx('menu.item', 'menu__item--exotic-unfocusable', {}, [current && 'bg-hoverSurface'])}
|
|
225
|
-
onClick={handleSelect}
|
|
226
|
-
>
|
|
227
|
-
{item.icon && <Icon icon={item.icon} size={5} />}
|
|
221
|
+
<li ref={listRef} className={tx('menu.item', {}, [current && 'bg-hover-surface'])} onClick={handleSelect}>
|
|
222
|
+
{item.icon && <Icon icon={item.icon} />}
|
|
228
223
|
<span className='grow truncate'>{toLocalizedString(item.label, t)}</span>
|
|
229
224
|
</li>
|
|
230
225
|
);
|
|
@@ -110,6 +110,7 @@ export const linkSlashCommands: EditorMenuGroup = {
|
|
|
110
110
|
label: 'Block embed',
|
|
111
111
|
icon: 'ph--lego--regular',
|
|
112
112
|
onSelect: ({ view, head }) => {
|
|
113
|
+
// Seed the same query shape as typing "@@" manually.
|
|
113
114
|
view.dispatch({
|
|
114
115
|
changes: { from: head, insert: '@@' },
|
|
115
116
|
selection: { anchor: head + 2, head: head + 2 },
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
keymap,
|
|
14
14
|
} from '@codemirror/view';
|
|
15
15
|
|
|
16
|
-
import { type PlaceholderOptions,
|
|
16
|
+
import { type PlaceholderOptions, modalStateField, placeholder } from '@dxos/ui-editor';
|
|
17
|
+
import { type Range } from '@dxos/ui-editor/types';
|
|
17
18
|
import { isNonNullable, isTruthy } from '@dxos/util';
|
|
18
19
|
|
|
19
20
|
const DELIMITERS = [' ', ':'];
|
|
@@ -52,6 +53,7 @@ export const popover = (options: PopoverOptions = {}): Extension => {
|
|
|
52
53
|
placeholder({
|
|
53
54
|
// TODO(burdon): Translations.
|
|
54
55
|
content: `Press '${Array.isArray(options.trigger) ? options.trigger[0] : options.trigger}' for commands`,
|
|
56
|
+
focusOnly: true,
|
|
55
57
|
...options.placeholder,
|
|
56
58
|
}),
|
|
57
59
|
].filter(isTruthy);
|
|
@@ -62,7 +62,8 @@ export const useEditorMenu = ({
|
|
|
62
62
|
const getMenuOptions = useCallback<NonNullable<UseEditorMenuProps['getMenu']>>(
|
|
63
63
|
async ({ text, trigger, ...props }) => {
|
|
64
64
|
const groups = (await getMenu?.({ text, trigger, ...props })) ?? [];
|
|
65
|
-
|
|
65
|
+
// The "@" menu can use "@@" as syntax for block embeds, so it owns its own query filtering.
|
|
66
|
+
return filter && trigger !== '@'
|
|
66
67
|
? filterMenuGroups(groups, (item) =>
|
|
67
68
|
text ? (item.label as string).toLowerCase().startsWith(text.toLowerCase()) : true,
|
|
68
69
|
)
|
|
@@ -108,7 +109,13 @@ export const useEditorMenu = ({
|
|
|
108
109
|
);
|
|
109
110
|
|
|
110
111
|
const handleSelect = useCallback<NonNullable<UseEditorMenu['onSelect']>>(({ view, item }) => {
|
|
112
|
+
// Delete trigger range (e.g., "/" and any typed filter text).
|
|
113
|
+
const { range } = view.state.field(popoverStateField) ?? {};
|
|
114
|
+
if (range) {
|
|
115
|
+
view.dispatch({ changes: { from: range.from, to: range.to, insert: '' } });
|
|
116
|
+
}
|
|
111
117
|
void item.onSelect?.({ view, head: view.state.selection.main.head });
|
|
118
|
+
view.focus();
|
|
112
119
|
}, []);
|
|
113
120
|
|
|
114
121
|
const handleCancel = useCallback<NonNullable<UseEditorMenu['onCancel']>>(({ view }) => {
|
|
@@ -68,7 +68,7 @@ export const EditorPreviewProvider = ({ children, onLookup }: EditorPreviewProvi
|
|
|
68
68
|
<EditorPreviewContextProvider pending={value.pending} link={value.link} target={value.target}>
|
|
69
69
|
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
70
70
|
<Popover.VirtualTrigger virtualRef={triggerRef as unknown as RefObject<HTMLButtonElement>} />
|
|
71
|
-
<div
|
|
71
|
+
<div className='contents' ref={setRoot}>
|
|
72
72
|
{children}
|
|
73
73
|
</div>
|
|
74
74
|
</Popover.Root>
|
|
@@ -8,24 +8,19 @@ import React, { memo, useMemo } from 'react';
|
|
|
8
8
|
|
|
9
9
|
import { type Node } from '@dxos/app-graph';
|
|
10
10
|
import { ElevationProvider, type ThemedClassName } from '@dxos/react-ui';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
type MenuAction,
|
|
14
|
-
MenuProvider,
|
|
15
|
-
ToolbarMenu,
|
|
16
|
-
createGapSeparator,
|
|
17
|
-
useMenuActions,
|
|
18
|
-
} from '@dxos/react-ui-menu';
|
|
19
|
-
import { type EditorViewMode } from '@dxos/ui-editor';
|
|
11
|
+
import { type ActionGraphProps, Menu, type MenuAction, MenuBuilder, useMenuActions } from '@dxos/react-ui-menu';
|
|
12
|
+
import { type EditorViewMode } from '@dxos/ui-editor/types';
|
|
20
13
|
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { type EditorToolbarState } from './
|
|
28
|
-
import {
|
|
14
|
+
import { addBlocks } from './blocks';
|
|
15
|
+
import { addFormatting } from './formatting';
|
|
16
|
+
import { addHeadings } from './headings';
|
|
17
|
+
import { addImageUpload } from './image';
|
|
18
|
+
import { addLists } from './lists';
|
|
19
|
+
import { addSearch } from './search';
|
|
20
|
+
import { type EditorToolbarState } from './types';
|
|
21
|
+
import { addViewMode } from './view-mode';
|
|
22
|
+
|
|
23
|
+
// TODO(burdon): Enable toolbar variants (e.g., markdown, code).
|
|
29
24
|
|
|
30
25
|
export type EditorToolbarFeatureFlags = Partial<{
|
|
31
26
|
showHeadings: boolean;
|
|
@@ -56,13 +51,13 @@ export type EditorToolbarProps = ThemedClassName<
|
|
|
56
51
|
>;
|
|
57
52
|
|
|
58
53
|
export const EditorToolbar = memo(({ classNames, role, attendableId, onAction, ...props }: EditorToolbarProps) => {
|
|
59
|
-
const
|
|
54
|
+
const menuActions = useMarkdownMenuActions(props);
|
|
60
55
|
|
|
61
56
|
return (
|
|
62
57
|
<ElevationProvider elevation={role === 'section' ? 'positioned' : 'base'}>
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
</
|
|
58
|
+
<Menu.Root {...menuActions} attendableId={attendableId} onAction={onAction}>
|
|
59
|
+
<Menu.Toolbar classNames={classNames} />
|
|
60
|
+
</Menu.Root>
|
|
66
61
|
</ElevationProvider>
|
|
67
62
|
);
|
|
68
63
|
});
|
|
@@ -70,12 +65,13 @@ export const EditorToolbar = memo(({ classNames, role, attendableId, onAction, .
|
|
|
70
65
|
type ToolbarActionsProps = Pick<EditorToolbarActionGraphProps, 'state' | 'getView' | 'customActions'> &
|
|
71
66
|
EditorToolbarFeatureFlags;
|
|
72
67
|
|
|
68
|
+
// TODO(burdon): Some actions should toggle the state (e.g., toggle bullets on/off depending on the current state).
|
|
73
69
|
// TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.
|
|
74
70
|
// E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.
|
|
75
71
|
// This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.
|
|
76
|
-
const
|
|
72
|
+
const useMarkdownMenuActions = ({ state, getView, customActions, ...features }: ToolbarActionsProps) => {
|
|
77
73
|
const menuCreator = useMemo(
|
|
78
|
-
() =>
|
|
74
|
+
() => createMarkdownActions({ state, getView, customActions, ...features }),
|
|
79
75
|
[
|
|
80
76
|
state,
|
|
81
77
|
getView,
|
|
@@ -93,55 +89,25 @@ const useEditorToolbarActionGraph = ({ state, getView, customActions, ...feature
|
|
|
93
89
|
return useMenuActions(menuCreator);
|
|
94
90
|
};
|
|
95
91
|
|
|
96
|
-
const
|
|
92
|
+
const createMarkdownActions = ({
|
|
97
93
|
state,
|
|
98
94
|
getView,
|
|
99
95
|
customActions,
|
|
100
96
|
...features
|
|
101
97
|
}: ToolbarActionsProps): Atom.Atom<ActionGraphProps> => {
|
|
102
98
|
return Atom.make((get) => {
|
|
103
|
-
const graph: ActionGraphProps = {
|
|
104
|
-
nodes: [],
|
|
105
|
-
edges: [],
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// TODO(burdon): Builder pattern?
|
|
109
|
-
const addSubGraph = (graph: ActionGraphProps, subGraph: ActionGraphProps) => {
|
|
110
|
-
graph.nodes.push(...subGraph.nodes);
|
|
111
|
-
graph.edges.push(...subGraph.edges);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
99
|
// Subscribe to state changes.
|
|
115
100
|
const stateSnapshot = get(state);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
addSubGraph(graph, createBlocks(stateSnapshot, getView));
|
|
128
|
-
}
|
|
129
|
-
if (features?.onImageUpload) {
|
|
130
|
-
addSubGraph(graph, createImageUpload(features.onImageUpload!));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
addSubGraph(graph, createGapSeparator());
|
|
134
|
-
|
|
135
|
-
if (customActions) {
|
|
136
|
-
addSubGraph(graph, get(customActions));
|
|
137
|
-
}
|
|
138
|
-
if (features?.showSearch ?? true) {
|
|
139
|
-
addSubGraph(graph, createSearch(getView));
|
|
140
|
-
}
|
|
141
|
-
if (features?.onViewModeChange) {
|
|
142
|
-
addSubGraph(graph, createViewMode(stateSnapshot, features.onViewModeChange!));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return graph;
|
|
101
|
+
return MenuBuilder.make()
|
|
102
|
+
.subgraph(features?.showHeadings !== false && addHeadings(stateSnapshot, getView))
|
|
103
|
+
.subgraph(features?.showFormatting !== false && addFormatting(stateSnapshot, getView))
|
|
104
|
+
.subgraph(features?.showLists !== false && addLists(stateSnapshot, getView))
|
|
105
|
+
.subgraph(features?.showBlocks !== false && addBlocks(stateSnapshot, getView))
|
|
106
|
+
.subgraph(features?.onImageUpload && addImageUpload(features.onImageUpload))
|
|
107
|
+
.separator('gap')
|
|
108
|
+
.subgraph(customActions && get(customActions))
|
|
109
|
+
.subgraph(features?.showSearch !== false && addSearch(getView))
|
|
110
|
+
.subgraph(features?.onViewModeChange && addViewMode(stateSnapshot, features.onViewModeChange))
|
|
111
|
+
.build();
|
|
146
112
|
});
|
|
147
113
|
};
|
|
@@ -4,56 +4,64 @@
|
|
|
4
4
|
|
|
5
5
|
import { type EditorView } from '@codemirror/view';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
8
|
-
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
|
7
|
+
import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
|
9
8
|
import { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from '@dxos/ui-editor';
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
import { type EditorToolbarState } from './useEditorToolbar';
|
|
10
|
+
import { translationKey } from '#translations';
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
createEditorActionGroup('block', {
|
|
16
|
-
variant: 'toggleGroup',
|
|
17
|
-
selectCardinality: 'single',
|
|
18
|
-
value,
|
|
19
|
-
} as ToolbarMenuActionGroupProperties);
|
|
12
|
+
import { type EditorToolbarState } from './types';
|
|
20
13
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}).map(([type, icon]) => {
|
|
27
|
-
const checked = type === value;
|
|
28
|
-
return createEditorAction(type, { checked, ...(type === 'table' && { disabled: !!blankLine }), icon }, () => {
|
|
29
|
-
const view = getView();
|
|
30
|
-
if (!view) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
14
|
+
const blockTypes = {
|
|
15
|
+
blockquote: 'ph--quotes--regular',
|
|
16
|
+
codeblock: 'ph--code-block--regular',
|
|
17
|
+
table: 'ph--table--regular',
|
|
18
|
+
};
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
20
|
+
/** Add block actions to the builder. */
|
|
21
|
+
export const addBlocks =
|
|
22
|
+
(state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
|
|
23
|
+
(builder) => {
|
|
24
|
+
const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');
|
|
25
|
+
builder.group(
|
|
26
|
+
'block',
|
|
27
|
+
{
|
|
28
|
+
label: ['block.label', { ns: translationKey }],
|
|
29
|
+
iconOnly: true,
|
|
30
|
+
variant: 'toggleGroup',
|
|
31
|
+
selectCardinality: 'single',
|
|
32
|
+
value,
|
|
33
|
+
} as ToolbarMenuActionGroupProperties,
|
|
34
|
+
(group) => {
|
|
35
|
+
for (const [type, icon] of Object.entries(blockTypes)) {
|
|
36
|
+
const checked = type === value;
|
|
37
|
+
group.action(
|
|
38
|
+
type,
|
|
39
|
+
{
|
|
40
|
+
label: [`block.${type}.label`, { ns: translationKey }],
|
|
41
|
+
checked,
|
|
42
|
+
...(type === 'table' && { disabled: !!state.blankLine }),
|
|
43
|
+
icon,
|
|
44
|
+
},
|
|
45
|
+
() => {
|
|
46
|
+
const view = getView();
|
|
47
|
+
if (!view) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
switch (type) {
|
|
52
|
+
case 'blockquote':
|
|
53
|
+
checked ? removeBlockquote(view) : addBlockquote(view);
|
|
54
|
+
break;
|
|
55
|
+
case 'codeblock':
|
|
56
|
+
checked ? removeCodeblock(view) : addCodeblock(view);
|
|
57
|
+
break;
|
|
58
|
+
case 'table':
|
|
59
|
+
insertTable(view);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
);
|
|
58
67
|
};
|
|
59
|
-
};
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import { type EditorView } from '@codemirror/view';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
8
|
-
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
|
7
|
+
import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
|
9
8
|
import { type Formatting, Inline, addLink, removeLink, setStyle } from '@dxos/ui-editor';
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
10
|
+
import { translationKey } from '#translations';
|
|
11
|
+
|
|
12
|
+
import { type EditorToolbarState } from './types';
|
|
13
13
|
|
|
14
14
|
const formats = {
|
|
15
15
|
strong: 'ph--text-b--regular',
|
|
@@ -19,47 +19,46 @@ const formats = {
|
|
|
19
19
|
link: 'ph--link--regular',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
/** Add formatting actions to the builder. */
|
|
23
|
+
export const addFormatting =
|
|
24
|
+
(state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
|
|
25
|
+
(builder) => {
|
|
26
|
+
const formatting: Formatting = state;
|
|
27
|
+
builder.group(
|
|
28
|
+
'formatting',
|
|
29
|
+
{
|
|
30
|
+
label: ['formatting.label', { ns: translationKey }],
|
|
31
|
+
iconOnly: true,
|
|
32
|
+
variant: 'toggleGroup',
|
|
33
|
+
selectCardinality: 'multiple',
|
|
34
|
+
value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),
|
|
35
|
+
} as ToolbarMenuActionGroupProperties,
|
|
36
|
+
(group) => {
|
|
37
|
+
for (const [type, icon] of Object.entries(formats)) {
|
|
38
|
+
const checked = !!formatting[type as keyof Formatting];
|
|
39
|
+
group.action(type, { label: [`formatting.${type}.label`, { ns: translationKey }], checked, icon }, () => {
|
|
40
|
+
const view = getView();
|
|
41
|
+
if (!view) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
if (type === 'link') {
|
|
46
|
+
checked ? removeLink(view) : addLink()(view);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const formattingActions = createFormattingActions(state, getView);
|
|
58
|
-
return {
|
|
59
|
-
nodes: [formattingGroupAction as Node.NodeArg<any>, ...formattingActions],
|
|
60
|
-
edges: [
|
|
61
|
-
{ source: 'root', target: 'formatting' },
|
|
62
|
-
...formattingActions.map(({ id }) => ({ source: formattingGroupAction.id, target: id })),
|
|
63
|
-
],
|
|
50
|
+
setStyle(
|
|
51
|
+
type === 'strong'
|
|
52
|
+
? Inline.Strong
|
|
53
|
+
: type === 'emphasis'
|
|
54
|
+
? Inline.Emphasis
|
|
55
|
+
: type === 'strikethrough'
|
|
56
|
+
? Inline.Strikethrough
|
|
57
|
+
: Inline.Code,
|
|
58
|
+
!checked,
|
|
59
|
+
)(view);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
64
|
};
|
|
65
|
-
};
|
|
@@ -4,49 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
import { type EditorView } from '@codemirror/view';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
8
|
-
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
|
7
|
+
import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
|
9
8
|
import { setHeading } from '@dxos/ui-editor';
|
|
10
9
|
|
|
11
|
-
import { translationKey } from '
|
|
10
|
+
import { translationKey } from '#translations';
|
|
12
11
|
|
|
13
|
-
import {
|
|
14
|
-
import { type EditorToolbarState } from './useEditorToolbar';
|
|
12
|
+
import { type EditorToolbarState } from './types';
|
|
15
13
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} as ToolbarMenuActionGroupProperties,
|
|
26
|
-
'ph--text-h--regular',
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const createHeadingActions = (currentLevel: string, getView: () => EditorView) =>
|
|
30
|
-
Object.entries({
|
|
31
|
-
'0': 'ph--paragraph--regular',
|
|
32
|
-
'1': 'ph--text-h-one--regular',
|
|
33
|
-
'2': 'ph--text-h-two--regular',
|
|
34
|
-
'3': 'ph--text-h-three--regular',
|
|
35
|
-
'4': 'ph--text-h-four--regular',
|
|
36
|
-
'5': 'ph--text-h-five--regular',
|
|
37
|
-
'6': 'ph--text-h-six--regular',
|
|
38
|
-
}).map(([levelStr, icon]) => {
|
|
39
|
-
const level = parseInt(levelStr);
|
|
40
|
-
return createEditorAction(
|
|
41
|
-
`heading--${levelStr}`,
|
|
42
|
-
{
|
|
43
|
-
label: ['heading level label', { count: level, ns: translationKey }],
|
|
44
|
-
icon,
|
|
45
|
-
checked: levelStr === currentLevel,
|
|
46
|
-
},
|
|
47
|
-
() => setHeading(level)(getView()),
|
|
48
|
-
);
|
|
49
|
-
});
|
|
14
|
+
const headingIcons: Record<string, string> = {
|
|
15
|
+
0: 'ph--paragraph--regular',
|
|
16
|
+
1: 'ph--text-h-one--regular',
|
|
17
|
+
2: 'ph--text-h-two--regular',
|
|
18
|
+
3: 'ph--text-h-three--regular',
|
|
19
|
+
4: 'ph--text-h-four--regular',
|
|
20
|
+
5: 'ph--text-h-five--regular',
|
|
21
|
+
6: 'ph--text-h-six--regular',
|
|
22
|
+
};
|
|
50
23
|
|
|
51
24
|
const computeHeadingValue = (state: EditorToolbarState) => {
|
|
52
25
|
const blockType = state ? state.blockType : 'paragraph';
|
|
@@ -54,15 +27,36 @@ const computeHeadingValue = (state: EditorToolbarState) => {
|
|
|
54
27
|
return heading ? heading[1] : blockType === 'paragraph' || !blockType ? '0' : '';
|
|
55
28
|
};
|
|
56
29
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
/** Add heading actions to the builder. */
|
|
31
|
+
export const addHeadings =
|
|
32
|
+
(state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
|
|
33
|
+
(builder) => {
|
|
34
|
+
const headingValue = computeHeadingValue(state);
|
|
35
|
+
builder.group(
|
|
36
|
+
'heading',
|
|
37
|
+
{
|
|
38
|
+
label: ['heading.label', { ns: translationKey }],
|
|
39
|
+
icon: 'ph--text-h--regular',
|
|
40
|
+
iconOnly: true,
|
|
41
|
+
variant: 'dropdownMenu',
|
|
42
|
+
applyActive: true,
|
|
43
|
+
selectCardinality: 'single',
|
|
44
|
+
// TODO(wittjosiah): Remove? Not sure this does anything.
|
|
45
|
+
value: headingValue,
|
|
46
|
+
} as ToolbarMenuActionGroupProperties,
|
|
47
|
+
(group) => {
|
|
48
|
+
for (const [levelStr, icon] of Object.entries(headingIcons)) {
|
|
49
|
+
const level = parseInt(levelStr);
|
|
50
|
+
group.action(
|
|
51
|
+
`heading--${levelStr}`,
|
|
52
|
+
{
|
|
53
|
+
label: ['heading-level.label', { count: level, ns: translationKey }],
|
|
54
|
+
icon,
|
|
55
|
+
checked: levelStr === headingValue,
|
|
56
|
+
},
|
|
57
|
+
() => setHeading(level)(getView()),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
);
|
|
67
62
|
};
|
|
68
|
-
};
|