@dxos/react-ui-editor 0.8.4-main.d05673bc65 → 0.8.4-main.dfabb4ec29

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 (144) hide show
  1. package/dist/lib/browser/index.mjs +754 -731
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/translations.mjs +39 -0
  5. package/dist/lib/browser/translations.mjs.map +7 -0
  6. package/dist/lib/node-esm/index.mjs +754 -731
  7. package/dist/lib/node-esm/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/meta.json +1 -1
  9. package/dist/lib/node-esm/translations.mjs +41 -0
  10. package/dist/lib/node-esm/translations.mjs.map +7 -0
  11. package/dist/types/src/components/Editor/Editor.d.ts +36 -25
  12. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  13. package/dist/types/src/components/Editor/Editor.stories.d.ts +4 -4
  14. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/{EditorContent/EditorContent.d.ts → Editor/EditorView.d.ts} +5 -5
  16. package/dist/types/src/components/Editor/EditorView.d.ts.map +1 -0
  17. package/dist/types/src/components/Editor/controller.d.ts.map +1 -0
  18. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts +1 -3
  19. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorMenuProvider/menu-presets.d.ts.map +1 -1
  21. package/dist/types/src/components/EditorMenuProvider/menu.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorMenuProvider/popover.d.ts +2 -1
  23. package/dist/types/src/components/EditorMenuProvider/popover.d.ts.map +1 -1
  24. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
  25. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts.map +1 -1
  26. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +2 -2
  27. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  28. package/dist/types/src/components/EditorToolbar/blocks.d.ts +4 -19
  29. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  30. package/dist/types/src/components/EditorToolbar/formatting.d.ts +4 -19
  31. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  32. package/dist/types/src/components/EditorToolbar/headings.d.ts +4 -19
  33. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  34. package/dist/types/src/components/EditorToolbar/image.d.ts +3 -9
  35. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -1
  36. package/dist/types/src/components/EditorToolbar/index.d.ts +1 -2
  37. package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
  38. package/dist/types/src/components/EditorToolbar/lists.d.ts +6 -0
  39. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -0
  40. package/dist/types/src/components/EditorToolbar/search.d.ts +3 -9
  41. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -1
  42. package/dist/types/src/components/EditorToolbar/types.d.ts +6 -0
  43. package/dist/types/src/components/EditorToolbar/types.d.ts.map +1 -0
  44. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +5 -20
  45. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  46. package/dist/types/src/components/index.d.ts +0 -2
  47. package/dist/types/src/components/index.d.ts.map +1 -1
  48. package/dist/types/src/extensions/Assistant.stories.d.ts +10 -0
  49. package/dist/types/src/extensions/Assistant.stories.d.ts.map +1 -0
  50. package/dist/types/src/extensions/assistant-extension.d.ts +24 -0
  51. package/dist/types/src/extensions/assistant-extension.d.ts.map +1 -0
  52. package/dist/types/src/extensions/index.d.ts +2 -0
  53. package/dist/types/src/extensions/index.d.ts.map +1 -0
  54. package/dist/types/src/hooks/index.d.ts +1 -0
  55. package/dist/types/src/hooks/index.d.ts.map +1 -1
  56. package/dist/types/src/hooks/useBasicMarkdownExtensions.d.ts +25 -0
  57. package/dist/types/src/hooks/useBasicMarkdownExtensions.d.ts.map +1 -0
  58. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  59. package/dist/types/src/index.d.ts +1 -2
  60. package/dist/types/src/index.d.ts.map +1 -1
  61. package/dist/types/src/stories/Automerge.stories.d.ts +24 -24
  62. package/dist/types/src/stories/Automerge.stories.d.ts.map +1 -1
  63. package/dist/types/src/stories/Comments.stories.d.ts +1 -1
  64. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  65. package/dist/types/src/stories/EditorToolbar.stories.d.ts +28 -26
  66. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  67. package/dist/types/src/stories/Experimental.stories.d.ts +2 -2
  68. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  69. package/dist/types/src/stories/Markdown.stories.d.ts +1 -1
  70. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
  71. package/dist/types/src/stories/Outliner.stories.d.ts +2 -2
  72. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  73. package/dist/types/src/stories/Popover.stories.d.ts +2 -2
  74. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -1
  75. package/dist/types/src/stories/Preview.stories.d.ts +1 -1
  76. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  77. package/dist/types/src/stories/Tags.stories.d.ts.map +1 -1
  78. package/dist/types/src/stories/TextEditor.stories.d.ts +1 -1
  79. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  80. package/dist/types/src/stories/Theme.stories.d.ts.map +1 -1
  81. package/dist/types/src/stories/components/EditorStory.d.ts +2 -2
  82. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  83. package/dist/types/src/stories/components/util.d.ts +2 -1
  84. package/dist/types/src/stories/components/util.d.ts.map +1 -1
  85. package/dist/types/src/translations.d.ts +24 -24
  86. package/dist/types/src/translations.d.ts.map +1 -1
  87. package/dist/types/src/util/react.d.ts +1 -1
  88. package/dist/types/src/util/react.d.ts.map +1 -1
  89. package/dist/types/tsconfig.tsbuildinfo +1 -1
  90. package/package.json +57 -48
  91. package/src/components/Editor/Editor.stories.tsx +15 -21
  92. package/src/components/Editor/Editor.tsx +54 -53
  93. package/src/components/Editor/EditorView.tsx +102 -0
  94. package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +3 -5
  95. package/src/components/EditorMenuProvider/menu-presets.ts +1 -0
  96. package/src/components/EditorMenuProvider/popover.ts +3 -1
  97. package/src/components/EditorMenuProvider/useEditorMenu.ts +2 -1
  98. package/src/components/EditorPreviewProvider/EditorPreviewProvider.tsx +1 -1
  99. package/src/components/EditorToolbar/EditorToolbar.tsx +29 -48
  100. package/src/components/EditorToolbar/blocks.ts +54 -46
  101. package/src/components/EditorToolbar/formatting.ts +44 -45
  102. package/src/components/EditorToolbar/headings.ts +44 -50
  103. package/src/components/EditorToolbar/image.ts +16 -21
  104. package/src/components/EditorToolbar/index.ts +2 -3
  105. package/src/components/EditorToolbar/lists.ts +58 -0
  106. package/src/components/EditorToolbar/search.ts +16 -21
  107. package/src/components/EditorToolbar/types.ts +8 -0
  108. package/src/components/EditorToolbar/view-mode.ts +37 -43
  109. package/src/components/index.ts +0 -3
  110. package/src/extensions/Assistant.stories.tsx +112 -0
  111. package/src/extensions/assistant-extension.tsx +223 -0
  112. package/src/extensions/index.ts +5 -0
  113. package/src/hooks/index.ts +1 -0
  114. package/src/hooks/useBasicMarkdownExtensions.ts +55 -0
  115. package/src/index.ts +1 -4
  116. package/src/stories/Automerge.stories.tsx +4 -3
  117. package/src/stories/Comments.stories.tsx +2 -2
  118. package/src/stories/EditorToolbar.stories.tsx +36 -64
  119. package/src/stories/Experimental.stories.tsx +10 -10
  120. package/src/stories/Outliner.stories.tsx +3 -4
  121. package/src/stories/Popover.stories.tsx +6 -7
  122. package/src/stories/Preview.stories.tsx +6 -7
  123. package/src/stories/Theme.stories.tsx +2 -2
  124. package/src/stories/components/EditorStory.tsx +17 -8
  125. package/src/stories/components/util.tsx +37 -35
  126. package/src/translations.ts +29 -24
  127. package/src/util/react.tsx +1 -1
  128. package/dist/types/src/components/EditorContent/EditorContent.d.ts.map +0 -1
  129. package/dist/types/src/components/EditorContent/controller.d.ts.map +0 -1
  130. package/dist/types/src/components/EditorContent/index.d.ts +0 -3
  131. package/dist/types/src/components/EditorContent/index.d.ts.map +0 -1
  132. package/dist/types/src/components/EditorToolbar/actions.d.ts +0 -25
  133. package/dist/types/src/components/EditorToolbar/actions.d.ts.map +0 -1
  134. package/dist/types/src/components/EditorToolbar/useEditorToolbar.d.ts +0 -11
  135. package/dist/types/src/components/EditorToolbar/useEditorToolbar.d.ts.map +0 -1
  136. package/dist/types/src/stories/CommandDialog.stories.d.ts +0 -14
  137. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +0 -1
  138. package/src/components/EditorContent/EditorContent.tsx +0 -83
  139. package/src/components/EditorContent/index.ts +0 -6
  140. package/src/components/EditorToolbar/actions.ts +0 -87
  141. package/src/components/EditorToolbar/useEditorToolbar.ts +0 -20
  142. package/src/stories/CommandDialog.stories.tsx +0 -81
  143. /package/dist/types/src/components/{EditorContent → Editor}/controller.d.ts +0 -0
  144. /package/src/components/{EditorContent → Editor}/controller.ts +0 -0
@@ -9,16 +9,18 @@ import React, { memo, useMemo } from 'react';
9
9
  import { type Node } from '@dxos/app-graph';
10
10
  import { ElevationProvider, type ThemedClassName } from '@dxos/react-ui';
11
11
  import { type ActionGraphProps, Menu, type MenuAction, MenuBuilder, useMenuActions } from '@dxos/react-ui-menu';
12
- import { type EditorViewMode } from '@dxos/ui-editor';
12
+ import { type EditorViewMode } from '@dxos/ui-editor/types';
13
13
 
14
- import { createLists } from './actions';
15
- import { createBlocks } from './blocks';
16
- import { createFormatting } from './formatting';
17
- import { createHeadings } from './headings';
18
- import { createImageUpload } from './image';
19
- import { createSearch } from './search';
20
- import { type EditorToolbarState } from './useEditorToolbar';
21
- import { createViewMode } from './view-mode';
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).
22
24
 
23
25
  export type EditorToolbarFeatureFlags = Partial<{
24
26
  showHeadings: boolean;
@@ -49,12 +51,12 @@ export type EditorToolbarProps = ThemedClassName<
49
51
  >;
50
52
 
51
53
  export const EditorToolbar = memo(({ classNames, role, attendableId, onAction, ...props }: EditorToolbarProps) => {
52
- const menuProps = useEditorToolbarActionGraph(props);
54
+ const menuActions = useMarkdownMenuActions(props);
53
55
 
54
56
  return (
55
57
  <ElevationProvider elevation={role === 'section' ? 'positioned' : 'base'}>
56
- <Menu.Root {...menuProps} attendableId={attendableId} onAction={onAction}>
57
- <Menu.Toolbar classNames={classNames} textBlockWidth />
58
+ <Menu.Root {...menuActions} attendableId={attendableId} onAction={onAction}>
59
+ <Menu.Toolbar classNames={classNames} />
58
60
  </Menu.Root>
59
61
  </ElevationProvider>
60
62
  );
@@ -63,13 +65,13 @@ export const EditorToolbar = memo(({ classNames, role, attendableId, onAction, .
63
65
  type ToolbarActionsProps = Pick<EditorToolbarActionGraphProps, 'state' | 'getView' | 'customActions'> &
64
66
  EditorToolbarFeatureFlags;
65
67
 
68
+ // TODO(burdon): Some actions should toggle the state (e.g., toggle bullets on/off depending on the current state).
66
69
  // TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.
67
70
  // E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.
68
71
  // This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.
69
- // TODO(burdon): Some actions should toggle the state (e.g., toggle bullets on/off depending on the current state).
70
- const useEditorToolbarActionGraph = ({ state, getView, customActions, ...features }: ToolbarActionsProps) => {
72
+ const useMarkdownMenuActions = ({ state, getView, customActions, ...features }: ToolbarActionsProps) => {
71
73
  const menuCreator = useMemo(
72
- () => createToolbarActions({ state, getView, customActions, ...features }),
74
+ () => createMarkdownActions({ state, getView, customActions, ...features }),
73
75
  [
74
76
  state,
75
77
  getView,
@@ -87,7 +89,7 @@ const useEditorToolbarActionGraph = ({ state, getView, customActions, ...feature
87
89
  return useMenuActions(menuCreator);
88
90
  };
89
91
 
90
- const createToolbarActions = ({
92
+ const createMarkdownActions = ({
91
93
  state,
92
94
  getView,
93
95
  customActions,
@@ -96,37 +98,16 @@ const createToolbarActions = ({
96
98
  return Atom.make((get) => {
97
99
  // Subscribe to state changes.
98
100
  const stateSnapshot = get(state);
99
-
100
- const builder = MenuBuilder.make();
101
-
102
- if (features?.showHeadings ?? true) {
103
- builder.subgraph(createHeadings(stateSnapshot, getView));
104
- }
105
- if (features?.showFormatting ?? true) {
106
- builder.subgraph(createFormatting(stateSnapshot, getView));
107
- }
108
- if (features?.showLists ?? true) {
109
- builder.subgraph(createLists(stateSnapshot, getView));
110
- }
111
- if (features?.showBlocks ?? true) {
112
- builder.subgraph(createBlocks(stateSnapshot, getView));
113
- }
114
- if (features?.onImageUpload) {
115
- builder.subgraph(createImageUpload(features.onImageUpload!));
116
- }
117
-
118
- builder.separator('gap');
119
-
120
- if (customActions) {
121
- builder.subgraph(get(customActions));
122
- }
123
- if (features?.showSearch ?? true) {
124
- builder.subgraph(createSearch(getView));
125
- }
126
- if (features?.onViewModeChange) {
127
- builder.subgraph(createViewMode(stateSnapshot, features.onViewModeChange!));
128
- }
129
-
130
- return builder.build();
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();
131
112
  });
132
113
  };
@@ -4,56 +4,64 @@
4
4
 
5
5
  import { type EditorView } from '@codemirror/view';
6
6
 
7
- import { type Node } from '@dxos/app-graph';
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 { createEditorAction, createEditorActionGroup } from './actions';
12
- import { type EditorToolbarState } from './useEditorToolbar';
10
+ import { translationKey } from '#translations';
13
11
 
14
- const createBlockGroupAction = (value: string) =>
15
- createEditorActionGroup('block', {
16
- variant: 'toggleGroup',
17
- selectCardinality: 'single',
18
- value,
19
- } as ToolbarMenuActionGroupProperties);
12
+ import { type EditorToolbarState } from './types';
20
13
 
21
- const createBlockActions = (value: string, getView: () => EditorView, blankLine?: boolean) =>
22
- Object.entries({
23
- blockquote: 'ph--quotes--regular',
24
- codeblock: 'ph--code-block--regular',
25
- table: 'ph--table--regular',
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
- switch (type) {
35
- case 'blockquote':
36
- checked ? removeBlockquote(view) : addBlockquote(view);
37
- break;
38
- case 'codeblock':
39
- checked ? removeCodeblock(view) : addCodeblock(view);
40
- break;
41
- case 'table':
42
- insertTable(view);
43
- break;
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
- export const createBlocks = (state: EditorToolbarState, getView: () => EditorView) => {
49
- const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');
50
- const blockGroupAction = createBlockGroupAction(value);
51
- const blockActions = createBlockActions(value, getView, state.blankLine);
52
- return {
53
- nodes: [blockGroupAction as Node.NodeArg<any>, ...blockActions],
54
- edges: [
55
- { source: 'root', target: 'block', relation: 'child' },
56
- ...blockActions.map(({ id }) => ({ source: blockGroupAction.id, target: id, relation: 'child' })),
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 Node } from '@dxos/app-graph';
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 { createEditorAction, createEditorActionGroup } from './actions';
12
- import { type EditorToolbarState } from './useEditorToolbar';
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
- const createFormattingGroup = (formatting: Formatting) =>
23
- createEditorActionGroup('formatting', {
24
- variant: 'toggleGroup',
25
- selectCardinality: 'multiple',
26
- value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),
27
- } as ToolbarMenuActionGroupProperties);
28
-
29
- const createFormattingActions = (formatting: Formatting, getView: () => EditorView) =>
30
- Object.entries(formats).map(([type, icon]) => {
31
- const checked = !!formatting[type as keyof Formatting];
32
- return createEditorAction(type, { checked, icon }, () => {
33
- const view = getView();
34
- if (!view) {
35
- return;
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
- if (type === 'link') {
39
- checked ? removeLink(view) : addLink()(view);
40
- return;
41
- }
45
+ if (type === 'link') {
46
+ checked ? removeLink(view) : addLink()(view);
47
+ return;
48
+ }
42
49
 
43
- const inlineType =
44
- type === 'strong'
45
- ? Inline.Strong
46
- : type === 'emphasis'
47
- ? Inline.Emphasis
48
- : type === 'strikethrough'
49
- ? Inline.Strikethrough
50
- : Inline.Code;
51
- setStyle(inlineType, !checked)(view);
52
- });
53
- });
54
-
55
- export const createFormatting = (state: EditorToolbarState, getView: () => EditorView) => {
56
- const formattingGroupAction = createFormattingGroup(state);
57
- const formattingActions = createFormattingActions(state, getView);
58
- return {
59
- nodes: [formattingGroupAction as Node.NodeArg<any>, ...formattingActions],
60
- edges: [
61
- { source: 'root', target: 'formatting', relation: 'child' },
62
- ...formattingActions.map(({ id }) => ({ source: formattingGroupAction.id, target: id, relation: 'child' })),
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 Node } from '@dxos/app-graph';
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 '../../translations';
10
+ import { translationKey } from '#translations';
12
11
 
13
- import { createEditorAction, createEditorActionGroup } from './actions';
14
- import { type EditorToolbarState } from './useEditorToolbar';
12
+ import { type EditorToolbarState } from './types';
15
13
 
16
- const createHeadingGroupAction = (value: string) =>
17
- createEditorActionGroup(
18
- 'heading',
19
- {
20
- variant: 'dropdownMenu',
21
- applyActive: true,
22
- selectCardinality: 'single',
23
- // TODO(wittjosiah): Remove? Not sure this does anything.
24
- value,
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
- export const createHeadings = (state: EditorToolbarState, getView: () => EditorView) => {
58
- const headingValue = computeHeadingValue(state);
59
- const headingGroupAction = createHeadingGroupAction(headingValue);
60
- const headingActions = createHeadingActions(headingValue, getView);
61
- return {
62
- nodes: [headingGroupAction as Node.NodeArg<any>, ...headingActions],
63
- edges: [
64
- { source: 'root', target: 'heading', relation: 'child' },
65
- ...headingActions.map(({ id }) => ({ source: headingGroupAction.id, target: id, relation: 'child' })),
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
- };
@@ -2,26 +2,21 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Node } from '@dxos/app-graph';
5
+ import { type ActionGroupBuilderFn } from '@dxos/react-ui-menu';
6
6
 
7
- import { createEditorAction } from './actions';
7
+ import { translationKey } from '#translations';
8
8
 
9
- const createImageUploadAction = (onImageUpload: () => void) =>
10
- createEditorAction(
11
- 'image',
12
- {
13
- testId: 'editor.toolbar.image',
14
- icon: 'ph--image-square--regular',
15
- },
16
- onImageUpload,
17
- );
18
-
19
- export const createImageUpload = (
20
- onImageUpload: () => void,
21
- ): {
22
- nodes: Node.NodeArg<any>[];
23
- edges: Array<{ source: string; target: string; relation: 'child' }>;
24
- } => ({
25
- nodes: [createImageUploadAction(onImageUpload)],
26
- edges: [{ source: 'root', target: 'image', relation: 'child' }],
27
- });
9
+ /** Add image upload action to the builder. */
10
+ export const addImageUpload =
11
+ (onImageUpload: () => void): ActionGroupBuilderFn =>
12
+ (builder) => {
13
+ builder.action(
14
+ 'image',
15
+ {
16
+ label: ['image.label', { ns: translationKey }],
17
+ testId: 'editor.toolbar.image',
18
+ icon: 'ph--image-square--regular',
19
+ },
20
+ onImageUpload,
21
+ );
22
+ };
@@ -2,7 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './EditorToolbar';
5
+ export * from './types';
6
6
 
7
- export { createEditorAction, createEditorActionGroup } from './actions';
8
- export { type EditorToolbarState, useEditorToolbar } from './useEditorToolbar';
7
+ export * from './EditorToolbar';
@@ -0,0 +1,58 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type EditorView } from '@codemirror/view';
6
+
7
+ import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
8
+ import { List, addList, removeList } from '@dxos/ui-editor';
9
+
10
+ import { translationKey } from '#translations';
11
+
12
+ import { type EditorToolbarState } from './types';
13
+
14
+ const listStyles = {
15
+ bullet: 'ph--list-bullets--regular',
16
+ ordered: 'ph--list-numbers--regular',
17
+ task: 'ph--list-checks--regular',
18
+ };
19
+
20
+ /** Add list actions to the builder. */
21
+ export const addLists =
22
+ (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
23
+ (builder) => {
24
+ const value = state.listStyle ?? '';
25
+ builder.group(
26
+ 'list',
27
+ {
28
+ label: ['list.label', { ns: translationKey }],
29
+ iconOnly: true,
30
+ variant: 'toggleGroup',
31
+ selectCardinality: 'single',
32
+ value,
33
+ } as ToolbarMenuActionGroupProperties,
34
+ (group) => {
35
+ for (const [listStyle, icon] of Object.entries(listStyles)) {
36
+ const checked = value === listStyle;
37
+ group.action(
38
+ `list-${listStyle}`,
39
+ { label: [`list.${listStyle}.label`, { ns: translationKey }], checked, icon },
40
+ () => {
41
+ const view = getView();
42
+ if (!view) {
43
+ return;
44
+ }
45
+
46
+ const listType =
47
+ listStyle === 'ordered' ? List.Ordered : listStyle === 'bullet' ? List.Bullet : List.Task;
48
+ if (checked) {
49
+ removeList(listType)(view);
50
+ } else {
51
+ addList(listType)(view);
52
+ }
53
+ },
54
+ );
55
+ }
56
+ },
57
+ );
58
+ };
@@ -5,26 +5,21 @@
5
5
  import { openSearchPanel } from '@codemirror/search';
6
6
  import { type EditorView } from '@codemirror/view';
7
7
 
8
- import { type Node } from '@dxos/app-graph';
8
+ import { type ActionGroupBuilderFn } from '@dxos/react-ui-menu';
9
9
 
10
- import { createEditorAction } from './actions';
10
+ import { translationKey } from '#translations';
11
11
 
12
- const createSearchAction = (getView: () => EditorView) =>
13
- createEditorAction(
14
- 'search',
15
- {
16
- testId: 'editor.toolbar.search',
17
- icon: 'ph--magnifying-glass--regular',
18
- },
19
- () => openSearchPanel(getView()),
20
- );
21
-
22
- export const createSearch = (
23
- getView: () => EditorView,
24
- ): {
25
- nodes: Node.NodeArg<any>[];
26
- edges: Array<{ source: string; target: string; relation: 'child' }>;
27
- } => ({
28
- nodes: [createSearchAction(getView)],
29
- edges: [{ source: 'root', target: 'search', relation: 'child' }],
30
- });
12
+ /** Add search action to the builder. */
13
+ export const addSearch =
14
+ (getView: () => EditorView): ActionGroupBuilderFn =>
15
+ (builder) => {
16
+ builder.action(
17
+ 'search',
18
+ {
19
+ label: ['search.label', { ns: translationKey }],
20
+ testId: 'editor.toolbar.search',
21
+ icon: 'ph--magnifying-glass--regular',
22
+ },
23
+ () => openSearchPanel(getView()),
24
+ );
25
+ };
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Formatting } from '@dxos/ui-editor';
6
+ import { type EditorViewMode } from '@dxos/ui-editor/types';
7
+
8
+ export type EditorToolbarState = Formatting & { viewMode?: EditorViewMode };