@dxos/react-ui-editor 0.7.5-main.9d26e3a → 0.7.5-main.b19bfc8
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/dist/lib/browser/index.mjs +1125 -1137
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +1145 -1171
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +1125 -1137
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/InputMode.stories.d.ts +3 -4
- package/dist/types/src/InputMode.stories.d.ts.map +1 -1
- package/dist/types/src/TextEditor.stories.d.ts +34 -35
- package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +3 -0
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/blocks.d.ts +18 -0
- package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/comment.d.ts +17 -0
- package/dist/types/src/components/EditorToolbar/comment.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/formatting.d.ts +18 -0
- package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/headings.d.ts +18 -0
- package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/index.d.ts +3 -0
- package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/lists.d.ts +18 -0
- package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/util.d.ts +58 -0
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -0
- package/dist/types/src/components/EditorToolbar/viewMode.d.ts +18 -0
- package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +5 -6
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +3 -4
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/editorAction.d.ts +12 -0
- package/dist/types/src/extensions/markdown/editorAction.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/formatting.d.ts +14 -12
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
- package/dist/types/src/hooks/useActionHandler.d.ts +2 -2
- package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/stack-item-content-class-names.d.ts +3 -0
- package/dist/types/src/styles/stack-item-content-class-names.d.ts.map +1 -0
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -29
- package/src/InputMode.stories.tsx +7 -10
- package/src/components/EditorToolbar/EditorToolbar.tsx +106 -0
- package/src/components/EditorToolbar/blocks.ts +41 -0
- package/src/components/EditorToolbar/comment.ts +23 -0
- package/src/components/EditorToolbar/formatting.ts +41 -0
- package/src/components/EditorToolbar/headings.ts +59 -0
- package/src/components/EditorToolbar/index.ts +6 -0
- package/src/components/EditorToolbar/lists.ts +40 -0
- package/src/components/EditorToolbar/util.ts +65 -0
- package/src/components/EditorToolbar/viewMode.ts +48 -0
- package/src/components/index.ts +1 -1
- package/src/extensions/automerge/automerge.stories.tsx +2 -2
- package/src/extensions/comments.ts +12 -19
- package/src/extensions/factories.ts +11 -5
- package/src/extensions/markdown/decorate.ts +1 -1
- package/src/extensions/markdown/{action.ts → editorAction.ts} +22 -20
- package/src/extensions/markdown/formatting.test.ts +7 -6
- package/src/extensions/markdown/formatting.ts +20 -24
- package/src/extensions/markdown/index.ts +1 -1
- package/src/extensions/markdown/styles.ts +21 -0
- package/src/hooks/useActionHandler.ts +4 -4
- package/src/index.ts +4 -0
- package/src/styles/markdown.ts +1 -1
- package/src/styles/stack-item-content-class-names.ts +17 -0
- package/src/styles/theme.ts +2 -3
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +0 -34
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +0 -1
- package/dist/types/src/components/Toolbar/index.d.ts +0 -2
- package/dist/types/src/components/Toolbar/index.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/action.d.ts +0 -9
- package/dist/types/src/extensions/markdown/action.d.ts.map +0 -1
- package/src/components/Toolbar/Toolbar.tsx +0 -522
- package/src/components/Toolbar/index.ts +0 -5
@@ -0,0 +1,106 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2024 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import React, { useCallback } from 'react';
|
6
|
+
|
7
|
+
import { type NodeArg } from '@dxos/app-graph';
|
8
|
+
import { ElevationProvider } from '@dxos/react-ui';
|
9
|
+
import {
|
10
|
+
ToolbarMenu,
|
11
|
+
MenuProvider,
|
12
|
+
type MenuActionHandler,
|
13
|
+
useMenuActions,
|
14
|
+
createGapSeparator,
|
15
|
+
} from '@dxos/react-ui-menu';
|
16
|
+
import { textBlockWidth } from '@dxos/react-ui-theme';
|
17
|
+
|
18
|
+
import { createBlocks } from './blocks';
|
19
|
+
import { createComment } from './comment';
|
20
|
+
import { createFormatting } from './formatting';
|
21
|
+
import { createHeadings } from './headings';
|
22
|
+
import { createLists } from './lists';
|
23
|
+
import {
|
24
|
+
type EditorToolbarActionGraphProps,
|
25
|
+
type EditorToolbarFeatureFlags,
|
26
|
+
type EditorToolbarProps,
|
27
|
+
editorToolbarSearch,
|
28
|
+
} from './util';
|
29
|
+
import { createViewMode } from './viewMode';
|
30
|
+
import { stackItemContentToolbarClassNames } from '../../styles/stack-item-content-class-names';
|
31
|
+
|
32
|
+
const createToolbar = ({
|
33
|
+
state,
|
34
|
+
customActions,
|
35
|
+
...features
|
36
|
+
}: EditorToolbarFeatureFlags & Pick<EditorToolbarActionGraphProps, 'state' | 'customActions'>): {
|
37
|
+
nodes: NodeArg<any>[];
|
38
|
+
edges: { source: string; target: string }[];
|
39
|
+
} => {
|
40
|
+
const nodes = [];
|
41
|
+
const edges = [];
|
42
|
+
if (features.headings ?? true) {
|
43
|
+
const headings = createHeadings(state);
|
44
|
+
nodes.push(...headings.nodes);
|
45
|
+
edges.push(...headings.edges);
|
46
|
+
}
|
47
|
+
if (features.formatting ?? true) {
|
48
|
+
const formatting = createFormatting(state);
|
49
|
+
nodes.push(...formatting.nodes);
|
50
|
+
edges.push(...formatting.edges);
|
51
|
+
}
|
52
|
+
if (features.lists ?? true) {
|
53
|
+
const lists = createLists(state);
|
54
|
+
nodes.push(...lists.nodes);
|
55
|
+
edges.push(...lists.edges);
|
56
|
+
}
|
57
|
+
if (features.blocks ?? true) {
|
58
|
+
const blocks = createBlocks(state);
|
59
|
+
nodes.push(...blocks.nodes);
|
60
|
+
edges.push(...blocks.edges);
|
61
|
+
}
|
62
|
+
if (customActions) {
|
63
|
+
const custom = customActions();
|
64
|
+
nodes.push(...custom.nodes);
|
65
|
+
edges.push(...custom.edges);
|
66
|
+
}
|
67
|
+
const editorToolbarGap = createGapSeparator();
|
68
|
+
nodes.push(...editorToolbarGap.nodes);
|
69
|
+
edges.push(...editorToolbarGap.edges);
|
70
|
+
if (features.comment ?? true) {
|
71
|
+
const comment = createComment(state);
|
72
|
+
nodes.push(...comment.nodes);
|
73
|
+
edges.push(...comment.edges);
|
74
|
+
}
|
75
|
+
if (features.search ?? true) {
|
76
|
+
nodes.push(editorToolbarSearch);
|
77
|
+
edges.push({ source: 'root', target: editorToolbarSearch.id });
|
78
|
+
}
|
79
|
+
if (features.viewMode ?? true) {
|
80
|
+
const viewMode = createViewMode(state);
|
81
|
+
nodes.push(...viewMode.nodes);
|
82
|
+
edges.push(...viewMode.edges);
|
83
|
+
}
|
84
|
+
return { nodes, edges };
|
85
|
+
};
|
86
|
+
|
87
|
+
const useEditorToolbarActionGraph = ({ onAction, ...props }: EditorToolbarProps) => {
|
88
|
+
const menuCreator = useCallback(() => createToolbar(props), [props]);
|
89
|
+
|
90
|
+
const { resolveGroupItems } = useMenuActions(menuCreator);
|
91
|
+
|
92
|
+
return { resolveGroupItems, onAction: onAction as MenuActionHandler };
|
93
|
+
};
|
94
|
+
|
95
|
+
export const EditorToolbar = ({ classNames, attendableId, role, ...props }: EditorToolbarProps) => {
|
96
|
+
const menuProps = useEditorToolbarActionGraph(props);
|
97
|
+
return (
|
98
|
+
<div role='none' className={stackItemContentToolbarClassNames(role)}>
|
99
|
+
<ElevationProvider elevation={role === 'section' ? 'positioned' : 'base'}>
|
100
|
+
<MenuProvider {...menuProps} attendableId={attendableId}>
|
101
|
+
<ToolbarMenu classNames={[textBlockWidth, '!bg-transparent', classNames]} />
|
102
|
+
</MenuProvider>
|
103
|
+
</ElevationProvider>
|
104
|
+
</div>
|
105
|
+
);
|
106
|
+
};
|
@@ -0,0 +1,41 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type NodeArg } from '@dxos/app-graph';
|
6
|
+
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
7
|
+
|
8
|
+
import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
|
9
|
+
import { type PayloadType } from '../../extensions';
|
10
|
+
|
11
|
+
const createBlockGroupAction = (value: string) =>
|
12
|
+
createEditorActionGroup('block', {
|
13
|
+
variant: 'toggleGroup',
|
14
|
+
selectCardinality: 'single',
|
15
|
+
value,
|
16
|
+
} as ToolbarMenuActionGroupProperties);
|
17
|
+
|
18
|
+
const createBlockActions = (value: string, blankLine?: boolean) =>
|
19
|
+
Object.entries({
|
20
|
+
blockquote: 'ph--quotes--regular',
|
21
|
+
codeblock: 'ph--code-block--regular',
|
22
|
+
table: 'ph--table--regular',
|
23
|
+
}).map(([type, icon]) => {
|
24
|
+
return createEditorAction(
|
25
|
+
{ type: type as PayloadType, checked: type === value, ...(type === 'table' && { disabled: !!blankLine }) },
|
26
|
+
icon,
|
27
|
+
);
|
28
|
+
});
|
29
|
+
|
30
|
+
export const createBlocks = (state: EditorToolbarState) => {
|
31
|
+
const value = state?.blockQuote ? 'blockquote' : state.blockType ?? '';
|
32
|
+
const blockGroupAction = createBlockGroupAction(value);
|
33
|
+
const blockActions = createBlockActions(value, state.blankLine);
|
34
|
+
return {
|
35
|
+
nodes: [blockGroupAction as NodeArg<any>, ...blockActions],
|
36
|
+
edges: [
|
37
|
+
{ source: 'root', target: 'block' },
|
38
|
+
...blockActions.map(({ id }) => ({ source: blockGroupAction.id, target: id })),
|
39
|
+
],
|
40
|
+
};
|
41
|
+
};
|
@@ -0,0 +1,23 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type Label } from '@dxos/react-ui';
|
6
|
+
|
7
|
+
import { createEditorAction, type EditorToolbarState } from './util';
|
8
|
+
import { translationKey } from '../../translations';
|
9
|
+
|
10
|
+
const commentLabel = (comment?: boolean, selection?: boolean) =>
|
11
|
+
comment
|
12
|
+
? 'selection overlaps existing comment label'
|
13
|
+
: selection === false
|
14
|
+
? 'select text to comment label'
|
15
|
+
: 'comment label';
|
16
|
+
|
17
|
+
const createCommentAction = (label: Label) =>
|
18
|
+
createEditorAction({ type: 'comment', testId: 'editor.toolbar.comment' }, 'ph--chat-text--regular', label);
|
19
|
+
|
20
|
+
export const createComment = (state: EditorToolbarState) => ({
|
21
|
+
nodes: [createCommentAction([commentLabel(state.comment, state.selection), { ns: translationKey }])],
|
22
|
+
edges: [{ source: 'root', target: 'comment' }],
|
23
|
+
});
|
@@ -0,0 +1,41 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type NodeArg } from '@dxos/app-graph';
|
6
|
+
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
7
|
+
|
8
|
+
import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
|
9
|
+
import { type Formatting, type PayloadType } from '../../extensions';
|
10
|
+
|
11
|
+
const formats = {
|
12
|
+
strong: 'ph--text-b--regular',
|
13
|
+
emphasis: 'ph--text-italic--regular',
|
14
|
+
strikethrough: 'ph--text-strikethrough--regular',
|
15
|
+
code: 'ph--code--regular',
|
16
|
+
link: 'ph--link--regular',
|
17
|
+
};
|
18
|
+
|
19
|
+
const createFormattingGroup = (formatting: Formatting) =>
|
20
|
+
createEditorActionGroup('formatting', {
|
21
|
+
variant: 'toggleGroup',
|
22
|
+
selectCardinality: 'multiple',
|
23
|
+
value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),
|
24
|
+
} as ToolbarMenuActionGroupProperties);
|
25
|
+
|
26
|
+
const createFormattingActions = (formatting: Formatting) =>
|
27
|
+
Object.entries(formats).map(([type, icon]) =>
|
28
|
+
createEditorAction({ type: type as PayloadType, checked: !!formatting[type as keyof Formatting] }, icon),
|
29
|
+
);
|
30
|
+
|
31
|
+
export const createFormatting = (state: EditorToolbarState) => {
|
32
|
+
const formattingGroupAction = createFormattingGroup(state);
|
33
|
+
const formattingActions = createFormattingActions(state);
|
34
|
+
return {
|
35
|
+
nodes: [formattingGroupAction as NodeArg<any>, ...formattingActions],
|
36
|
+
edges: [
|
37
|
+
{ source: 'root', target: 'formatting' },
|
38
|
+
...formattingActions.map(({ id }) => ({ source: formattingGroupAction.id, target: id })),
|
39
|
+
],
|
40
|
+
};
|
41
|
+
};
|
@@ -0,0 +1,59 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type NodeArg } from '@dxos/app-graph';
|
6
|
+
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
7
|
+
|
8
|
+
import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
|
9
|
+
import { translationKey } from '../../translations';
|
10
|
+
|
11
|
+
const createHeadingGroupAction = (value: string) =>
|
12
|
+
createEditorActionGroup(
|
13
|
+
'heading',
|
14
|
+
{
|
15
|
+
variant: 'dropdownMenu',
|
16
|
+
applyActive: true,
|
17
|
+
selectCardinality: 'single',
|
18
|
+
value,
|
19
|
+
} as ToolbarMenuActionGroupProperties,
|
20
|
+
'ph--text-h--regular',
|
21
|
+
);
|
22
|
+
|
23
|
+
const createHeadingActions = (value: string) =>
|
24
|
+
Object.entries({
|
25
|
+
'0': 'ph--paragraph--regular',
|
26
|
+
'1': 'ph--text-h-one--regular',
|
27
|
+
'2': 'ph--text-h-two--regular',
|
28
|
+
'3': 'ph--text-h-three--regular',
|
29
|
+
'4': 'ph--text-h-four--regular',
|
30
|
+
'5': 'ph--text-h-five--regular',
|
31
|
+
'6': 'ph--text-h-six--regular',
|
32
|
+
}).map(([levelStr, icon]) => {
|
33
|
+
const level = parseInt(levelStr);
|
34
|
+
return createEditorAction(
|
35
|
+
{ type: 'heading', data: level, checked: value === levelStr },
|
36
|
+
icon,
|
37
|
+
['heading level label', { count: level, ns: translationKey }],
|
38
|
+
`heading--${levelStr}`,
|
39
|
+
);
|
40
|
+
});
|
41
|
+
|
42
|
+
const computeHeadingValue = (state: EditorToolbarState) => {
|
43
|
+
const blockType = state ? state.blockType : 'paragraph';
|
44
|
+
const header = blockType && /heading(\d)/.exec(blockType);
|
45
|
+
return header ? header[1] : blockType === 'paragraph' || !blockType ? '0' : '';
|
46
|
+
};
|
47
|
+
|
48
|
+
export const createHeadings = (state: EditorToolbarState) => {
|
49
|
+
const headingValue = computeHeadingValue(state);
|
50
|
+
const headingGroupAction = createHeadingGroupAction(headingValue);
|
51
|
+
const headingActions = createHeadingActions(headingValue);
|
52
|
+
return {
|
53
|
+
nodes: [headingGroupAction as NodeArg<any>, ...headingActions],
|
54
|
+
edges: [
|
55
|
+
{ source: 'root', target: 'heading' },
|
56
|
+
...headingActions.map(({ id }) => ({ source: headingGroupAction.id, target: id })),
|
57
|
+
],
|
58
|
+
};
|
59
|
+
};
|
@@ -0,0 +1,40 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type NodeArg } from '@dxos/app-graph';
|
6
|
+
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
7
|
+
|
8
|
+
import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
|
9
|
+
import { type PayloadType } from '../../extensions';
|
10
|
+
|
11
|
+
const listStyles = {
|
12
|
+
bullet: 'ph--list-bullets--regular',
|
13
|
+
ordered: 'ph--list-numbers--regular',
|
14
|
+
task: 'ph--list-checks--regular',
|
15
|
+
};
|
16
|
+
|
17
|
+
const createListGroupAction = (value: string) =>
|
18
|
+
createEditorActionGroup('list', {
|
19
|
+
variant: 'toggleGroup',
|
20
|
+
selectCardinality: 'single',
|
21
|
+
value,
|
22
|
+
} as ToolbarMenuActionGroupProperties);
|
23
|
+
|
24
|
+
const createListActions = (value: string) =>
|
25
|
+
Object.entries(listStyles).map(([listStyle, icon]) =>
|
26
|
+
createEditorAction({ type: `list-${listStyle}` as PayloadType, checked: value === listStyle }, icon),
|
27
|
+
);
|
28
|
+
|
29
|
+
export const createLists = (state: EditorToolbarState) => {
|
30
|
+
const value = state.listStyle ?? '';
|
31
|
+
const listGroupAction = createListGroupAction(value);
|
32
|
+
const listActionsMap = createListActions(value);
|
33
|
+
return {
|
34
|
+
nodes: [listGroupAction as NodeArg<any>, ...listActionsMap],
|
35
|
+
edges: [
|
36
|
+
{ source: 'root', target: 'list' },
|
37
|
+
...listActionsMap.map(({ id }) => ({ source: listGroupAction.id, target: id })),
|
38
|
+
],
|
39
|
+
};
|
40
|
+
};
|
@@ -0,0 +1,65 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { useMemo } from 'react';
|
6
|
+
|
7
|
+
import { create, type ReactiveObject } from '@dxos/live-object';
|
8
|
+
import { type Label, type ThemedClassName } from '@dxos/react-ui';
|
9
|
+
import {
|
10
|
+
type MenuSeparator,
|
11
|
+
type MenuItemGroup,
|
12
|
+
type ToolbarMenuActionGroupProperties,
|
13
|
+
type MenuActionProperties,
|
14
|
+
createMenuAction,
|
15
|
+
createMenuItemGroup,
|
16
|
+
type ActionGraphProps,
|
17
|
+
} from '@dxos/react-ui-menu';
|
18
|
+
|
19
|
+
import type { EditorAction, EditorActionPayload, EditorViewMode, Formatting } from '../../extensions';
|
20
|
+
import { translationKey } from '../../translations';
|
21
|
+
|
22
|
+
export type EditorToolbarState = Formatting &
|
23
|
+
Partial<{ comment: boolean; viewMode: EditorViewMode; selection: boolean }>;
|
24
|
+
|
25
|
+
export const useEditorToolbarState = (initialState: Partial<EditorToolbarState> = {}) => {
|
26
|
+
return useMemo(() => create<EditorToolbarState>(initialState), []);
|
27
|
+
};
|
28
|
+
|
29
|
+
export type EditorToolbarFeatureFlags = Partial<{
|
30
|
+
headings: boolean;
|
31
|
+
formatting: boolean;
|
32
|
+
lists: boolean;
|
33
|
+
blocks: boolean;
|
34
|
+
comment: boolean;
|
35
|
+
search: boolean;
|
36
|
+
viewMode: boolean;
|
37
|
+
}>;
|
38
|
+
|
39
|
+
export type EditorToolbarActionGraphProps = {
|
40
|
+
state: ReactiveObject<EditorToolbarState>;
|
41
|
+
// TODO(wittjosiah): Control positioning.
|
42
|
+
customActions?: () => ActionGraphProps;
|
43
|
+
onAction: (action: EditorAction) => void;
|
44
|
+
};
|
45
|
+
|
46
|
+
export type EditorToolbarProps = ThemedClassName<
|
47
|
+
EditorToolbarActionGraphProps & EditorToolbarFeatureFlags & { attendableId?: string; role?: string }
|
48
|
+
>;
|
49
|
+
|
50
|
+
export type EditorToolbarItem = EditorAction | MenuItemGroup | MenuSeparator;
|
51
|
+
|
52
|
+
export const createEditorAction = (
|
53
|
+
payload: EditorActionPayload & Partial<MenuActionProperties>,
|
54
|
+
icon: string,
|
55
|
+
label: Label = [`${payload.type} label`, { ns: translationKey }],
|
56
|
+
id: string = payload.type,
|
57
|
+
) => createMenuAction(id, { icon, label, ...payload }) as EditorAction;
|
58
|
+
|
59
|
+
export const createEditorActionGroup = (
|
60
|
+
id: string,
|
61
|
+
props: Omit<ToolbarMenuActionGroupProperties, 'icon'>,
|
62
|
+
icon?: string,
|
63
|
+
) => createMenuItemGroup(id, { icon, iconOnly: true, ...props });
|
64
|
+
|
65
|
+
export const editorToolbarSearch = createEditorAction({ type: 'search' }, 'ph--magnifying-glass--regular');
|
@@ -0,0 +1,48 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type NodeArg } from '@dxos/app-graph';
|
6
|
+
import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
|
7
|
+
|
8
|
+
import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
|
9
|
+
import { translationKey } from '../../translations';
|
10
|
+
|
11
|
+
const createViewModeGroupAction = (value: string) =>
|
12
|
+
createEditorActionGroup(
|
13
|
+
'viewMode',
|
14
|
+
{
|
15
|
+
variant: 'dropdownMenu',
|
16
|
+
applyActive: true,
|
17
|
+
selectCardinality: 'single',
|
18
|
+
value,
|
19
|
+
} as ToolbarMenuActionGroupProperties,
|
20
|
+
'ph--eye--regular',
|
21
|
+
);
|
22
|
+
|
23
|
+
const createViewModeActions = (value: string) =>
|
24
|
+
Object.entries({
|
25
|
+
preview: 'ph--eye--regular',
|
26
|
+
source: 'ph--pencil-simple--regular',
|
27
|
+
readonly: 'ph--pencil-slash--regular',
|
28
|
+
}).map(([viewMode, icon]) => {
|
29
|
+
return createEditorAction(
|
30
|
+
{ type: 'view-mode', data: viewMode, checked: viewMode === value },
|
31
|
+
icon,
|
32
|
+
[`${viewMode} mode label`, { ns: translationKey }],
|
33
|
+
`view-mode--${viewMode}`,
|
34
|
+
);
|
35
|
+
});
|
36
|
+
|
37
|
+
export const createViewMode = (state: EditorToolbarState) => {
|
38
|
+
const value = state.viewMode ?? 'source';
|
39
|
+
const viewModeGroupAction = createViewModeGroupAction(value);
|
40
|
+
const viewModeActions = createViewModeActions(value);
|
41
|
+
return {
|
42
|
+
nodes: [viewModeGroupAction as NodeArg<any>, ...viewModeActions],
|
43
|
+
edges: [
|
44
|
+
{ source: 'root', target: 'viewMode' },
|
45
|
+
...viewModeActions.map(({ id }) => ({ source: viewModeGroupAction.id, target: id })),
|
46
|
+
],
|
47
|
+
};
|
48
|
+
};
|
package/src/components/index.ts
CHANGED
@@ -74,12 +74,12 @@ const Story = () => {
|
|
74
74
|
const repo1 = new Repo({ network: [new BroadcastChannelNetworkAdapter()] });
|
75
75
|
const repo2 = new Repo({ network: [new BroadcastChannelNetworkAdapter()] });
|
76
76
|
|
77
|
-
const object1 = repo1.create();
|
77
|
+
const object1 = repo1.create<TestObject>();
|
78
78
|
object1.change((doc: TestObject) => {
|
79
79
|
doc.text = initialContent;
|
80
80
|
});
|
81
81
|
|
82
|
-
const object2 = repo2.find(object1.url);
|
82
|
+
const object2 = repo2.find<TestObject>(object1.url);
|
83
83
|
await object2.whenReady();
|
84
84
|
|
85
85
|
setObject1({ handle: object1, path: ['text'] });
|
@@ -22,13 +22,15 @@ import {
|
|
22
22
|
ViewPlugin,
|
23
23
|
} from '@codemirror/view';
|
24
24
|
import sortBy from 'lodash.sortby';
|
25
|
-
import { useEffect, useMemo
|
25
|
+
import { useEffect, useMemo } from 'react';
|
26
26
|
|
27
27
|
import { debounce, type UnsubscribeCallback } from '@dxos/async';
|
28
|
+
import { type ReactiveObject } from '@dxos/live-object';
|
28
29
|
import { log } from '@dxos/log';
|
29
|
-
import {
|
30
|
+
import { isNonNullable } from '@dxos/util';
|
30
31
|
|
31
32
|
import { documentId } from './selection';
|
33
|
+
import { type EditorToolbarState } from '../components';
|
32
34
|
import { type Comment, type Range } from '../types';
|
33
35
|
import { Cursor, overlap, singleValueFacet, callbackWrapper } from '../util';
|
34
36
|
|
@@ -85,7 +87,7 @@ export const commentsState = StateField.define<CommentsState>({
|
|
85
87
|
const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
|
86
88
|
return range && { comment, range };
|
87
89
|
})
|
88
|
-
.filter(
|
90
|
+
.filter(isNonNullable);
|
89
91
|
|
90
92
|
return { ...value, comments: commentStates };
|
91
93
|
}
|
@@ -149,7 +151,7 @@ const commentsDecorations = EditorView.decorations.compute([commentsState], (sta
|
|
149
151
|
const mark = createCommentMark(comment.comment.id, comment.comment.id === current);
|
150
152
|
return mark.range(range.from, range.to);
|
151
153
|
})
|
152
|
-
.filter(
|
154
|
+
.filter(isNonNullable);
|
153
155
|
|
154
156
|
return Decoration.set(decorations);
|
155
157
|
});
|
@@ -505,7 +507,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
505
507
|
}),
|
506
508
|
|
507
509
|
options.onUpdate && trackPastedComments(options.onUpdate),
|
508
|
-
].filter(
|
510
|
+
].filter(isNonNullable);
|
509
511
|
};
|
510
512
|
|
511
513
|
//
|
@@ -608,26 +610,17 @@ export const createExternalCommentSync = (
|
|
608
610
|
},
|
609
611
|
);
|
610
612
|
|
611
|
-
export const useCommentState = (
|
612
|
-
|
613
|
-
comment: false,
|
614
|
-
selection: false,
|
615
|
-
});
|
616
|
-
|
617
|
-
const observer = useMemo(
|
613
|
+
export const useCommentState = (state: ReactiveObject<EditorToolbarState>): Extension => {
|
614
|
+
return useMemo(
|
618
615
|
() =>
|
619
616
|
EditorView.updateListener.of((update) => {
|
620
617
|
if (update.docChanged || update.selectionSet) {
|
621
|
-
|
622
|
-
|
623
|
-
selection: hasActiveSelection(update.state),
|
624
|
-
});
|
618
|
+
state.comment = selectionOverlapsComment(update.state);
|
619
|
+
state.selection = hasActiveSelection(update.state);
|
625
620
|
}
|
626
621
|
}),
|
627
|
-
[],
|
622
|
+
[state],
|
628
623
|
);
|
629
|
-
|
630
|
-
return [state, observer];
|
631
624
|
};
|
632
625
|
|
633
626
|
/**
|
@@ -27,7 +27,7 @@ import { log } from '@dxos/log';
|
|
27
27
|
import { type DocAccessor, type Space } from '@dxos/react-client/echo';
|
28
28
|
import { type Identity } from '@dxos/react-client/halo';
|
29
29
|
import { type ThemeMode } from '@dxos/react-ui';
|
30
|
-
import { type HuePalette
|
30
|
+
import { type HuePalette } from '@dxos/react-ui-theme';
|
31
31
|
import { hexToHue, isNotFalsy } from '@dxos/util';
|
32
32
|
|
33
33
|
import { automerge } from './automerge';
|
@@ -124,6 +124,13 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
124
124
|
...(props.history ? historyKeymap : []),
|
125
125
|
// https://codemirror.net/docs/ref/#search.searchKeymap
|
126
126
|
...(props.search ? searchKeymap : []),
|
127
|
+
// Disable bindings that conflict with system shortcuts.
|
128
|
+
// TODO(burdon): Catalog global shortcuts.
|
129
|
+
{
|
130
|
+
key: 'Mod-Shift-k',
|
131
|
+
preventDefault: true,
|
132
|
+
run: () => true,
|
133
|
+
},
|
127
134
|
].filter(isNotFalsy),
|
128
135
|
),
|
129
136
|
].filter(isNotFalsy);
|
@@ -194,8 +201,7 @@ export const createDataExtensions = <T>({ id, text, space, identity }: DataExten
|
|
194
201
|
|
195
202
|
if (space && identity) {
|
196
203
|
const peerId = identity?.identityKey.toHex();
|
197
|
-
const
|
198
|
-
hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
|
204
|
+
const hue = (identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0');
|
199
205
|
|
200
206
|
extensions.push(
|
201
207
|
awareness(
|
@@ -205,8 +211,8 @@ export const createDataExtensions = <T>({ id, text, space, identity }: DataExten
|
|
205
211
|
peerId: identity.identityKey.toHex(),
|
206
212
|
info: {
|
207
213
|
displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
|
208
|
-
darkColor:
|
209
|
-
lightColor:
|
214
|
+
darkColor: `var(--dx-${hue}Cursor)`,
|
215
|
+
lightColor: `var(--dx-${hue}Cursor)`,
|
210
216
|
},
|
211
217
|
}),
|
212
218
|
),
|
@@ -73,7 +73,7 @@ class CheckboxWidget extends WidgetType {
|
|
73
73
|
|
74
74
|
override toDOM(view: EditorView) {
|
75
75
|
const input = document.createElement('input');
|
76
|
-
input.className = 'cm-task-checkbox
|
76
|
+
input.className = 'cm-task-checkbox dx-checkbox';
|
77
77
|
input.type = 'checkbox';
|
78
78
|
input.tabIndex = -1;
|
79
79
|
input.checked = this._checked;
|