@dxos/react-ui-editor 0.8.2-main.fbd8ed0 → 0.8.2-staging.42af850
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 +1828 -961
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +3 -64
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/index.cjs +2008 -1138
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +3 -75
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +1828 -961
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +3 -64
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/util.d.ts +4 -6
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +21 -0
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +1 -0
- package/dist/types/src/{testing → components/Popover}/RefPopover.d.ts +1 -1
- package/dist/types/src/components/Popover/RefPopover.d.ts.map +1 -0
- package/dist/types/src/components/Popover/index.d.ts +3 -0
- package/dist/types/src/components/Popover/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +2 -5
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/annotations.d.ts +4 -1
- package/dist/types/src/extensions/annotations.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +1 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
- package/dist/types/src/extensions/command/command.d.ts +1 -2
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts +14 -2
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +2 -0
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +4 -14
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +1 -1
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/command/typeahead.d.ts +17 -0
- package/dist/types/src/extensions/command/typeahead.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +2 -12
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +4 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/json.d.ts +7 -0
- package/dist/types/src/extensions/json.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/{editorAction.d.ts → action.d.ts} +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts +2 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -2
- 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/extensions/outliner/commands.d.ts +10 -0
- package/dist/types/src/extensions/outliner/commands.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.d.ts +5 -0
- package/dist/types/src/extensions/outliner/editor.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/index.d.ts +4 -0
- package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts +13 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/selection.d.ts +12 -0
- package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.d.ts +79 -0
- package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts.map +1 -0
- package/dist/types/src/stories/Command.stories.d.ts +7 -0
- package/dist/types/src/stories/Command.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorComments.stories.d.ts → Comments.stories.d.ts} +3 -3
- package/dist/types/src/stories/Comments.stories.d.ts.map +1 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts +12 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorSpecial.stories.d.ts → Experimental.stories.d.ts} +3 -6
- package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Markdown.stories.d.ts +46 -0
- package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Outliner.stories.d.ts +26 -0
- package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Preview.stories.d.ts +10 -0
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorBasic.stories.d.ts → TextEditor.stories.d.ts} +9 -39
- package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{story-utils.d.ts → util.d.ts} +6 -6
- package/dist/types/src/stories/util.d.ts.map +1 -0
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -1
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts +2 -0
- package/dist/types/src/testing/util.d.ts.map +1 -0
- package/package.json +40 -34
- package/src/components/EditorToolbar/EditorToolbar.tsx +81 -57
- package/src/components/EditorToolbar/index.ts +7 -1
- package/src/components/EditorToolbar/util.ts +3 -4
- package/src/components/Popover/RefDropdownMenu.tsx +77 -0
- package/src/{testing → components/Popover}/RefPopover.tsx +5 -4
- package/src/components/Popover/index.ts +6 -0
- package/src/components/index.ts +1 -0
- package/src/defaults.ts +10 -13
- package/src/extensions/annotations.ts +41 -64
- package/src/extensions/autocomplete.ts +5 -6
- package/src/extensions/automerge/automerge.stories.tsx +2 -7
- package/src/extensions/automerge/automerge.test.tsx +3 -2
- package/src/extensions/automerge/sync.ts +3 -3
- package/src/extensions/awareness/awareness-provider.ts +4 -4
- package/src/extensions/awareness/awareness.ts +7 -7
- package/src/extensions/blast.ts +9 -9
- package/src/extensions/command/command.ts +1 -3
- package/src/extensions/command/hint.ts +7 -7
- package/src/extensions/command/index.ts +2 -0
- package/src/extensions/command/menu.ts +75 -50
- package/src/extensions/command/typeahead.ts +116 -0
- package/src/extensions/comments.ts +4 -69
- package/src/extensions/factories.ts +13 -0
- package/src/extensions/index.ts +2 -0
- package/src/extensions/json.ts +56 -0
- package/src/extensions/markdown/bundle.ts +13 -9
- package/src/extensions/markdown/decorate.ts +7 -7
- package/src/extensions/markdown/image.ts +2 -2
- package/src/extensions/markdown/index.ts +1 -2
- package/src/extensions/markdown/styles.ts +2 -1
- package/src/extensions/markdown/table.ts +3 -3
- package/src/extensions/outliner/commands.ts +270 -0
- package/src/extensions/outliner/editor.test.ts +33 -0
- package/src/extensions/outliner/editor.ts +184 -0
- package/src/extensions/outliner/index.ts +7 -0
- package/src/extensions/outliner/outliner.test.ts +99 -0
- package/src/extensions/outliner/outliner.ts +168 -0
- package/src/extensions/outliner/selection.ts +50 -0
- package/src/extensions/outliner/tree.test.ts +164 -0
- package/src/extensions/outliner/tree.ts +315 -0
- package/src/extensions/preview/preview.ts +5 -5
- package/src/stories/Command.stories.tsx +97 -0
- package/src/stories/{TextEditorComments.stories.tsx → Comments.stories.tsx} +13 -14
- package/src/{components/EditorToolbar → stories}/EditorToolbar.stories.tsx +26 -20
- package/src/stories/{TextEditorSpecial.stories.tsx → Experimental.stories.tsx} +9 -30
- package/src/stories/Markdown.stories.tsx +121 -0
- package/src/stories/Outliner.stories.tsx +108 -0
- package/src/stories/{TextEditorPreview.stories.tsx → Preview.stories.tsx} +46 -136
- package/src/stories/TextEditor.stories.tsx +256 -0
- package/src/stories/{story-utils.tsx → util.tsx} +21 -22
- package/src/styles/theme.ts +12 -5
- package/src/styles/tokens.ts +1 -2
- package/src/testing/index.ts +1 -1
- package/src/testing/util.ts +5 -0
- package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts +0 -53
- package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/comment.d.ts +0 -18
- package/dist/types/src/components/EditorToolbar/comment.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/editorAction.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/outliner.d.ts +0 -12
- package/dist/types/src/extensions/markdown/outliner.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts +0 -13
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +0 -1
- package/dist/types/src/stories/story-utils.d.ts.map +0 -1
- package/dist/types/src/testing/RefPopover.d.ts.map +0 -1
- package/src/components/EditorToolbar/comment.ts +0 -30
- package/src/extensions/markdown/outliner.ts +0 -235
- package/src/stories/TextEditorBasic.stories.tsx +0 -333
- /package/src/extensions/markdown/{editorAction.ts → action.ts} +0 -0
@@ -2,15 +2,15 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
-
import
|
5
|
+
import { Rx } from '@effect-rx/rx-react';
|
6
|
+
import React, { memo, useMemo } from 'react';
|
6
7
|
|
7
|
-
import { type NodeArg } from '@dxos/app-graph';
|
8
|
+
import { rxFromSignal, type NodeArg } from '@dxos/app-graph';
|
8
9
|
import { ElevationProvider } from '@dxos/react-ui';
|
9
10
|
import { MenuProvider, ToolbarMenu, createGapSeparator, useMenuActions } from '@dxos/react-ui-menu';
|
10
11
|
import { textBlockWidth } from '@dxos/react-ui-theme';
|
11
12
|
|
12
13
|
import { createBlocks } from './blocks';
|
13
|
-
import { createComment } from './comment';
|
14
14
|
import { createFormatting } from './formatting';
|
15
15
|
import { createHeadings } from './headings';
|
16
16
|
import { createImageUpload } from './image';
|
@@ -25,68 +25,92 @@ const createToolbar = ({
|
|
25
25
|
state,
|
26
26
|
customActions,
|
27
27
|
...features
|
28
|
-
}: EditorToolbarFeatureFlags & Pick<EditorToolbarActionGraphProps, 'getView' | 'state' | 'customActions'>): {
|
28
|
+
}: EditorToolbarFeatureFlags & Pick<EditorToolbarActionGraphProps, 'getView' | 'state' | 'customActions'>): Rx.Rx<{
|
29
29
|
nodes: NodeArg<any>[];
|
30
30
|
edges: { source: string; target: string }[];
|
31
|
-
} => {
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
const
|
61
|
-
nodes.push(...
|
62
|
-
edges.push(...
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
edges.push(...viewMode.edges);
|
81
|
-
}
|
82
|
-
return { nodes, edges };
|
31
|
+
}> => {
|
32
|
+
return Rx.make((get) => {
|
33
|
+
const nodes = [];
|
34
|
+
const edges = [];
|
35
|
+
if (features.headings ?? true) {
|
36
|
+
const headings = get(rxFromSignal(() => createHeadings(state, getView)));
|
37
|
+
nodes.push(...headings.nodes);
|
38
|
+
edges.push(...headings.edges);
|
39
|
+
}
|
40
|
+
if (features.formatting ?? true) {
|
41
|
+
const formatting = get(rxFromSignal(() => createFormatting(state, getView)));
|
42
|
+
nodes.push(...formatting.nodes);
|
43
|
+
edges.push(...formatting.edges);
|
44
|
+
}
|
45
|
+
if (features.lists ?? true) {
|
46
|
+
const lists = get(rxFromSignal(() => createLists(state, getView)));
|
47
|
+
nodes.push(...lists.nodes);
|
48
|
+
edges.push(...lists.edges);
|
49
|
+
}
|
50
|
+
if (features.blocks ?? true) {
|
51
|
+
const blocks = get(rxFromSignal(() => createBlocks(state, getView)));
|
52
|
+
nodes.push(...blocks.nodes);
|
53
|
+
edges.push(...blocks.edges);
|
54
|
+
}
|
55
|
+
if (features.image) {
|
56
|
+
const image = get(rxFromSignal(() => createImageUpload(features.image!)));
|
57
|
+
nodes.push(...image.nodes);
|
58
|
+
edges.push(...image.edges);
|
59
|
+
}
|
60
|
+
const editorToolbarGap = createGapSeparator();
|
61
|
+
nodes.push(...editorToolbarGap.nodes);
|
62
|
+
edges.push(...editorToolbarGap.edges);
|
63
|
+
if (customActions) {
|
64
|
+
const custom = get(customActions);
|
65
|
+
nodes.push(...custom.nodes);
|
66
|
+
edges.push(...custom.edges);
|
67
|
+
}
|
68
|
+
if (features.search ?? true) {
|
69
|
+
const search = get(rxFromSignal(() => createSearch(getView)));
|
70
|
+
nodes.push(...search.nodes);
|
71
|
+
edges.push(...search.edges);
|
72
|
+
}
|
73
|
+
if (features.viewMode) {
|
74
|
+
const viewMode = get(rxFromSignal(() => createViewMode(state, features.viewMode!)));
|
75
|
+
nodes.push(...viewMode.nodes);
|
76
|
+
edges.push(...viewMode.edges);
|
77
|
+
}
|
78
|
+
return { nodes, edges };
|
79
|
+
});
|
83
80
|
};
|
84
81
|
|
85
82
|
// TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.
|
86
83
|
// E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.
|
87
84
|
// This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.
|
88
85
|
const useEditorToolbarActionGraph = (props: EditorToolbarProps) => {
|
89
|
-
const menuCreator =
|
86
|
+
const menuCreator = useMemo(
|
87
|
+
() =>
|
88
|
+
createToolbar({
|
89
|
+
getView: props.getView,
|
90
|
+
state: props.state,
|
91
|
+
customActions: props.customActions,
|
92
|
+
headings: props.headings,
|
93
|
+
formatting: props.formatting,
|
94
|
+
lists: props.lists,
|
95
|
+
blocks: props.blocks,
|
96
|
+
image: props.image,
|
97
|
+
search: props.search,
|
98
|
+
viewMode: props.viewMode,
|
99
|
+
}),
|
100
|
+
[
|
101
|
+
props.getView,
|
102
|
+
props.state,
|
103
|
+
props.customActions,
|
104
|
+
props.headings,
|
105
|
+
props.formatting,
|
106
|
+
props.lists,
|
107
|
+
props.blocks,
|
108
|
+
props.image,
|
109
|
+
props.search,
|
110
|
+
props.viewMode,
|
111
|
+
],
|
112
|
+
);
|
113
|
+
|
90
114
|
return useMenuActions(menuCreator);
|
91
115
|
};
|
92
116
|
|
@@ -3,4 +3,10 @@
|
|
3
3
|
//
|
4
4
|
|
5
5
|
export * from './EditorToolbar';
|
6
|
-
export {
|
6
|
+
export {
|
7
|
+
type EditorToolbarState,
|
8
|
+
type EditorToolbarActionGraphProps,
|
9
|
+
useEditorToolbarState,
|
10
|
+
createEditorAction,
|
11
|
+
createEditorActionGroup,
|
12
|
+
} from './util';
|
@@ -3,6 +3,7 @@
|
|
3
3
|
//
|
4
4
|
|
5
5
|
import { type EditorView } from '@codemirror/view';
|
6
|
+
import { type Rx } from '@effect-rx/rx-react';
|
6
7
|
import { useMemo } from 'react';
|
7
8
|
|
8
9
|
import { type Action } from '@dxos/app-graph';
|
@@ -21,8 +22,7 @@ import {
|
|
21
22
|
import type { EditorAction, EditorViewMode, Formatting } from '../../extensions';
|
22
23
|
import { translationKey } from '../../translations';
|
23
24
|
|
24
|
-
export type EditorToolbarState = Formatting &
|
25
|
-
Partial<{ comment: boolean; viewMode: EditorViewMode; selection: boolean }>;
|
25
|
+
export type EditorToolbarState = Formatting & Partial<{ viewMode: EditorViewMode }>;
|
26
26
|
|
27
27
|
export const useEditorToolbarState = (initialState: Partial<EditorToolbarState> = {}) => {
|
28
28
|
return useMemo(() => live<EditorToolbarState>(initialState), []);
|
@@ -35,7 +35,6 @@ export type EditorToolbarFeatureFlags = Partial<{
|
|
35
35
|
blocks: boolean;
|
36
36
|
search: boolean;
|
37
37
|
// TODO(wittjosiah): Factor out. Depend on plugin-level capabilities.
|
38
|
-
comment: boolean;
|
39
38
|
image: () => void;
|
40
39
|
viewMode: (mode: EditorViewMode) => void;
|
41
40
|
}>;
|
@@ -44,7 +43,7 @@ export type EditorToolbarActionGraphProps = {
|
|
44
43
|
state: Live<EditorToolbarState>;
|
45
44
|
getView: () => EditorView;
|
46
45
|
// TODO(wittjosiah): Control positioning.
|
47
|
-
customActions?:
|
46
|
+
customActions?: Rx.Rx<ActionGraphProps>;
|
48
47
|
};
|
49
48
|
|
50
49
|
export type EditorToolbarProps = ThemedClassName<
|
@@ -0,0 +1,77 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
6
|
+
import React, { type PropsWithChildren, useRef, useState, useEffect, useCallback, type RefObject } from 'react';
|
7
|
+
|
8
|
+
import { addEventListener } from '@dxos/async';
|
9
|
+
import { type DxRefTag, type DxRefTagActivate } from '@dxos/lit-ui';
|
10
|
+
import { DropdownMenu } from '@dxos/react-ui';
|
11
|
+
|
12
|
+
import { type PreviewLinkRef, type PreviewLinkTarget, type PreviewLookup } from '../../extensions';
|
13
|
+
|
14
|
+
// TODO(burdon): Reconcile with RefPopover?
|
15
|
+
|
16
|
+
const customEventOptions = { capture: true, passive: false };
|
17
|
+
|
18
|
+
// Create a context for the dxn value.
|
19
|
+
type RefDropdownMenuValue = Partial<{ link: PreviewLinkRef; target: PreviewLinkTarget; pending: boolean }>;
|
20
|
+
|
21
|
+
const REF_DROPDOWN_MENU = 'RefDropdownMenu';
|
22
|
+
const [RefDropdownMenuContextProvider, useRefDropdownMenu] = createContext<RefDropdownMenuValue>(REF_DROPDOWN_MENU, {});
|
23
|
+
|
24
|
+
type RefDropdownMenuProviderProps = PropsWithChildren<{ onLookup?: PreviewLookup }>;
|
25
|
+
|
26
|
+
const RefDropdownMenuProvider = ({ children, onLookup }: RefDropdownMenuProviderProps) => {
|
27
|
+
const trigger = useRef<DxRefTag | null>(null);
|
28
|
+
const [value, setValue] = useState<RefDropdownMenuValue>({});
|
29
|
+
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
30
|
+
const [open, setOpen] = useState(false);
|
31
|
+
|
32
|
+
const handleDxRefTagActivate = useCallback(
|
33
|
+
(event: DxRefTagActivate) => {
|
34
|
+
const { refId, label, trigger: dxTrigger } = event;
|
35
|
+
setValue((value) => ({
|
36
|
+
...value,
|
37
|
+
link: { label, ref: refId },
|
38
|
+
pending: true,
|
39
|
+
}));
|
40
|
+
trigger.current = dxTrigger;
|
41
|
+
queueMicrotask(() => setOpen(true));
|
42
|
+
void onLookup?.({ label, ref: refId }).then((target) =>
|
43
|
+
setValue((value) => ({
|
44
|
+
...value,
|
45
|
+
target: target ?? undefined,
|
46
|
+
pending: false,
|
47
|
+
})),
|
48
|
+
);
|
49
|
+
},
|
50
|
+
[onLookup],
|
51
|
+
);
|
52
|
+
|
53
|
+
useEffect(() => {
|
54
|
+
return rootRef
|
55
|
+
? addEventListener(rootRef, 'dx-ref-tag-activate', handleDxRefTagActivate, customEventOptions)
|
56
|
+
: undefined;
|
57
|
+
}, [rootRef]);
|
58
|
+
|
59
|
+
return (
|
60
|
+
<RefDropdownMenuContextProvider pending={value.pending} link={value.link} target={value.target}>
|
61
|
+
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
|
62
|
+
<DropdownMenu.VirtualTrigger virtualRef={trigger as unknown as RefObject<HTMLButtonElement>} />
|
63
|
+
<div role='none' className='contents' ref={setRootRef}>
|
64
|
+
{children}
|
65
|
+
</div>
|
66
|
+
</DropdownMenu.Root>
|
67
|
+
</RefDropdownMenuContextProvider>
|
68
|
+
);
|
69
|
+
};
|
70
|
+
|
71
|
+
export const RefDropdownMenu = {
|
72
|
+
Provider: RefDropdownMenuProvider,
|
73
|
+
};
|
74
|
+
|
75
|
+
export { useRefDropdownMenu };
|
76
|
+
|
77
|
+
export type { RefDropdownMenuProviderProps, RefDropdownMenuValue };
|
@@ -9,12 +9,13 @@ import { addEventListener } from '@dxos/async';
|
|
9
9
|
import { type DxRefTag, type DxRefTagActivate } from '@dxos/lit-ui';
|
10
10
|
import { Popover } from '@dxos/react-ui';
|
11
11
|
|
12
|
-
import { type PreviewLinkRef, type PreviewLinkTarget, type PreviewLookup } from '
|
12
|
+
import { type PreviewLinkRef, type PreviewLinkTarget, type PreviewLookup } from '../../extensions';
|
13
13
|
|
14
14
|
const customEventOptions = { capture: true, passive: false };
|
15
15
|
|
16
16
|
// Create a context for the dxn value.
|
17
17
|
type RefPopoverValue = Partial<{ link: PreviewLinkRef; target: PreviewLinkTarget; pending: boolean }>;
|
18
|
+
|
18
19
|
const REF_POPOVER = 'RefPopover';
|
19
20
|
const [RefPopoverContextProvider, useRefPopover] = createContext<RefPopoverValue>(REF_POPOVER, {});
|
20
21
|
|
@@ -28,15 +29,15 @@ const RefPopoverProvider = ({ children, onLookup }: RefPopoverProviderProps) =>
|
|
28
29
|
|
29
30
|
const handleDxRefTagActivate = useCallback(
|
30
31
|
(event: DxRefTagActivate) => {
|
31
|
-
const {
|
32
|
+
const { refId, label, trigger: dxTrigger } = event;
|
32
33
|
setValue((value) => ({
|
33
34
|
...value,
|
34
|
-
link: { label, ref },
|
35
|
+
link: { label, ref: refId },
|
35
36
|
pending: true,
|
36
37
|
}));
|
37
38
|
trigger.current = dxTrigger;
|
38
39
|
queueMicrotask(() => setOpen(true));
|
39
|
-
void onLookup?.({ label, ref }).then((target) =>
|
40
|
+
void onLookup?.({ label, ref: refId }).then((target) =>
|
40
41
|
setValue((value) => ({
|
41
42
|
...value,
|
42
43
|
target: target ?? undefined,
|
package/src/components/index.ts
CHANGED
package/src/defaults.ts
CHANGED
@@ -6,31 +6,28 @@ import { EditorView } from '@codemirror/view';
|
|
6
6
|
|
7
7
|
import { mx } from '@dxos/react-ui-theme';
|
8
8
|
|
9
|
+
import { type ThemeExtensionsOptions } from './extensions';
|
9
10
|
import { fontMono } from './styles';
|
10
11
|
|
11
|
-
const margin = '!mt-[1rem]';
|
12
|
-
|
13
12
|
/**
|
14
13
|
* CodeMirror content width.
|
15
14
|
* 40rem = 640px. Corresponds to initial plank width (Google docs, Stashpad, etc.)
|
16
15
|
* 50rem = 800px. Maximum content width for solo mode.
|
17
16
|
* NOTE: Max width - 4rem = 2rem left/right margin (or 2rem gutter plus 1rem left/right margin).
|
18
17
|
*/
|
19
|
-
// TOOD(burdon): Adjust depending on
|
20
18
|
export const editorWidth = '!mli-auto is-full max-is-[min(50rem,100%-4rem)]';
|
21
19
|
|
22
|
-
export const
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
export const editorSlots: ThemeExtensionsOptions['slots'] = {
|
21
|
+
scroll: {
|
22
|
+
className: 'pbs-2',
|
23
|
+
},
|
24
|
+
content: {
|
25
|
+
className: editorWidth,
|
26
|
+
},
|
27
|
+
};
|
28
28
|
|
29
29
|
export const editorGutter = EditorView.theme({
|
30
|
-
// Match margin from content.
|
31
|
-
// Gutter = 2rem + 1rem margin.
|
32
30
|
'.cm-gutters': {
|
33
|
-
marginTop: '1rem',
|
34
31
|
paddingRight: '1rem',
|
35
32
|
},
|
36
33
|
});
|
@@ -52,6 +49,6 @@ export const stackItemContentEditorClassNames = (role?: string) =>
|
|
52
49
|
|
53
50
|
export const stackItemContentToolbarClassNames = (role?: string) =>
|
54
51
|
mx(
|
55
|
-
'relative z-[1] flex is-full bg-toolbarSurface border-be border-
|
52
|
+
'relative z-[1] flex is-full bg-toolbarSurface border-be border-subduedSeparator',
|
56
53
|
role === 'section' && 'sticky block-start-0 -mbe-px min-is-0',
|
57
54
|
);
|
@@ -2,77 +2,54 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
-
import { type
|
6
|
-
import { Decoration, EditorView } from '@codemirror/view';
|
5
|
+
import { type Extension, RangeSetBuilder } from '@codemirror/state';
|
6
|
+
import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
import { Cursor } from '../util';
|
11
|
-
|
12
|
-
type Annotation = {
|
13
|
-
cursor: string;
|
14
|
-
};
|
8
|
+
const annotationMark = Decoration.mark({ class: 'cm-annotation' });
|
15
9
|
|
16
10
|
export type AnnotationOptions = {
|
17
11
|
match?: RegExp; // TODO(burdon): Update via hook (e.g., for search).
|
18
12
|
};
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
// Define annotation action in prompt. E.g., extract company names. Find links, etc.
|
25
|
-
// Show popover card. A16Z chain demo. Identify, extract, research, link. Multi-agent.
|
26
|
-
const match = (state: EditorState) => {
|
27
|
-
const annotations: Annotation[] = [];
|
28
|
-
const text = state.doc.toString();
|
29
|
-
if (options.match) {
|
30
|
-
const matches = text.matchAll(options.match);
|
31
|
-
for (const match of matches) {
|
32
|
-
const from = match.index!;
|
33
|
-
const to = from + match[0].length;
|
34
|
-
const cursor = Cursor.getCursorFromRange(state, { from, to });
|
35
|
-
annotations.push({ cursor });
|
36
|
-
}
|
37
|
-
}
|
38
|
-
|
39
|
-
return annotations;
|
40
|
-
};
|
41
|
-
|
42
|
-
const annotationsState = StateField.define<Annotation[]>({
|
43
|
-
create: (state) => {
|
44
|
-
return match(state);
|
45
|
-
},
|
46
|
-
update: (value, tr) => {
|
47
|
-
if (!tr.changes.empty) {
|
48
|
-
return match(tr.state);
|
49
|
-
}
|
50
|
-
|
51
|
-
return value;
|
52
|
-
},
|
53
|
-
});
|
54
|
-
|
14
|
+
/**
|
15
|
+
*
|
16
|
+
*/
|
17
|
+
export const annotations = ({ match }: AnnotationOptions = {}): Extension => {
|
55
18
|
return [
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
19
|
+
ViewPlugin.fromClass(
|
20
|
+
class {
|
21
|
+
decorations: DecorationSet = Decoration.none;
|
22
|
+
update(update: ViewUpdate) {
|
23
|
+
const builder = new RangeSetBuilder<Decoration>();
|
24
|
+
if (match) {
|
25
|
+
// Only process visible lines.
|
26
|
+
const { from, to } = update.view.viewport;
|
27
|
+
const text = update.state.doc.sliceString(from, to);
|
28
|
+
const matches = text.matchAll(match);
|
29
|
+
for (const m of matches) {
|
30
|
+
if (m.index !== undefined) {
|
31
|
+
// Adjust match position relative to viewport.
|
32
|
+
const start = from + m.index;
|
33
|
+
const end = start + m[0].length;
|
34
|
+
builder.add(start, end, annotationMark);
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
this.decorations = builder.finish();
|
40
|
+
}
|
41
|
+
},
|
42
|
+
{
|
43
|
+
decorations: (v) => v.decorations,
|
44
|
+
},
|
45
|
+
),
|
46
|
+
|
47
|
+
EditorView.theme({
|
48
|
+
'.cm-annotation': {
|
49
|
+
textDecoration: 'underline',
|
50
|
+
textDecorationStyle: 'wavy',
|
51
|
+
textDecorationColor: 'var(--dx-errorText)',
|
52
|
+
},
|
67
53
|
}),
|
68
|
-
styles,
|
69
54
|
];
|
70
55
|
};
|
71
|
-
|
72
|
-
const styles = EditorView.theme({
|
73
|
-
'.cm-annotation': {
|
74
|
-
textDecoration: 'underline',
|
75
|
-
textDecorationStyle: 'wavy',
|
76
|
-
textDecorationColor: 'var(--dx-error)',
|
77
|
-
},
|
78
|
-
});
|
@@ -17,7 +17,6 @@ import { keymap } from '@codemirror/view';
|
|
17
17
|
export type AutocompleteResult = Completion;
|
18
18
|
|
19
19
|
export type AutocompleteOptions = {
|
20
|
-
debug?: boolean;
|
21
20
|
activateOnTyping?: boolean;
|
22
21
|
override?: CompletionSource[];
|
23
22
|
onSearch?: (text: string) => Completion[];
|
@@ -30,7 +29,7 @@ export type AutocompleteOptions = {
|
|
30
29
|
/**
|
31
30
|
* Autocomplete extension.
|
32
31
|
*/
|
33
|
-
export const autocomplete = ({
|
32
|
+
export const autocomplete = ({ activateOnTyping, override, onSearch }: AutocompleteOptions = {}): Extension => {
|
34
33
|
const extensions: Extension[] = [
|
35
34
|
// https://codemirror.net/docs/ref/#view.keymap
|
36
35
|
// https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
|
@@ -40,16 +39,16 @@ export const autocomplete = ({ debug, activateOnTyping, override, onSearch }: Au
|
|
40
39
|
// https://codemirror.net/examples/autocompletion
|
41
40
|
// https://codemirror.net/docs/ref/#autocomplete.autocompletion
|
42
41
|
autocompletion({
|
43
|
-
activateOnTyping,
|
44
42
|
override,
|
45
|
-
|
46
|
-
|
43
|
+
activateOnTyping,
|
44
|
+
// closeOnBlur: false,
|
45
|
+
// tooltipClass: () => 'rounded-be pbe-1 border-separator',
|
47
46
|
}),
|
48
47
|
];
|
49
48
|
|
50
49
|
if (onSearch) {
|
51
50
|
extensions.push(
|
52
|
-
// TODO(burdon): Optional decoration via addToOptions
|
51
|
+
// TODO(burdon): Optional decoration via addToOptions.
|
53
52
|
markdownLanguage.data.of({
|
54
53
|
autocomplete: (context: CompletionContext): CompletionResult | null => {
|
55
54
|
const match = context.matchBefore(/\w*/);
|
@@ -17,7 +17,7 @@ import { ClientRepeater, type ClientRepeatedComponentProps } from '@dxos/react-c
|
|
17
17
|
import { useThemeContext } from '@dxos/react-ui';
|
18
18
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
19
19
|
|
20
|
-
import {
|
20
|
+
import { editorSlots } from '../../defaults';
|
21
21
|
import { useTextEditor } from '../../hooks';
|
22
22
|
import translations from '../../translations';
|
23
23
|
import { createBasicExtensions, createDataExtensions, createThemeExtensions } from '../factories';
|
@@ -42,12 +42,7 @@ const Editor = ({ source, autoFocus, space, identity }: EditorProps) => {
|
|
42
42
|
initialValue: DocAccessor.getValue(source),
|
43
43
|
extensions: [
|
44
44
|
createBasicExtensions({ placeholder: 'Type here...' }),
|
45
|
-
createThemeExtensions({
|
46
|
-
themeMode,
|
47
|
-
slots: {
|
48
|
-
editor: { className: editorContent },
|
49
|
-
},
|
50
|
-
}),
|
45
|
+
createThemeExtensions({ themeMode, slots: editorSlots }),
|
51
46
|
createDataExtensions({ id: 'test', text: source, space, identity }),
|
52
47
|
],
|
53
48
|
autoFocus,
|
@@ -9,10 +9,11 @@ import { render, screen } from '@testing-library/react';
|
|
9
9
|
// TODO(wittjosiah): Move to vitest expect (and remove from package.json).
|
10
10
|
import chai, { expect } from 'chai';
|
11
11
|
import chaiDom from 'chai-dom';
|
12
|
-
import get from 'lodash.get';
|
13
12
|
import React, { type FC, useEffect, useRef, useState } from 'react';
|
14
13
|
import { describe, test } from 'vitest';
|
15
14
|
|
15
|
+
import { get } from '@dxos/util';
|
16
|
+
|
16
17
|
import { automerge } from './automerge';
|
17
18
|
|
18
19
|
type TestObject = {
|
@@ -23,7 +24,7 @@ const path = ['text'];
|
|
23
24
|
|
24
25
|
class Generator {
|
25
26
|
constructor(private readonly _handle: DocHandle<TestObject>) {}
|
26
|
-
update(text: string) {
|
27
|
+
update(text: string): void {
|
27
28
|
this._handle.change((doc: TestObject) => {
|
28
29
|
doc.text = text;
|
29
30
|
});
|
@@ -26,7 +26,7 @@ export class Syncer {
|
|
26
26
|
private readonly _state: StateField<State>
|
27
27
|
) {}
|
28
28
|
|
29
|
-
reconcile(view: EditorView, editor: boolean) {
|
29
|
+
reconcile(view: EditorView, editor: boolean): void {
|
30
30
|
// TODO(burdon): Better way to do mutex?
|
31
31
|
if (this._pending) {
|
32
32
|
return;
|
@@ -41,7 +41,7 @@ export class Syncer {
|
|
41
41
|
this._pending = false;
|
42
42
|
}
|
43
43
|
|
44
|
-
onEditorChange(view: EditorView) {
|
44
|
+
onEditorChange(view: EditorView): void {
|
45
45
|
// Apply the unreconciled transactions to the document.
|
46
46
|
const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
|
47
47
|
const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
|
@@ -54,7 +54,7 @@ export class Syncer {
|
|
54
54
|
}
|
55
55
|
}
|
56
56
|
|
57
|
-
onAutomergeChange(view: EditorView) {
|
57
|
+
onAutomergeChange(view: EditorView): void {
|
58
58
|
// Get the diff between the updated state of the document and the heads and apply that to the codemirror doc.
|
59
59
|
const oldHeads = getLastHeads(view.state, this._state);
|
60
60
|
const newHeads = A.getHeads(this._handle.doc()!);
|
@@ -53,7 +53,7 @@ export class SpaceAwarenessProvider implements AwarenessProvider {
|
|
53
53
|
this._info = params.info;
|
54
54
|
}
|
55
55
|
|
56
|
-
open() {
|
56
|
+
open(): void {
|
57
57
|
this._ctx = new Context();
|
58
58
|
this._postTask = new DeferredTask(this._ctx, async () => {
|
59
59
|
if (this._localState) {
|
@@ -92,7 +92,7 @@ export class SpaceAwarenessProvider implements AwarenessProvider {
|
|
92
92
|
});
|
93
93
|
}
|
94
94
|
|
95
|
-
close() {
|
95
|
+
close(): void {
|
96
96
|
void this._ctx?.dispose();
|
97
97
|
this._ctx = undefined;
|
98
98
|
this._postTask = undefined;
|
@@ -113,12 +113,12 @@ export class SpaceAwarenessProvider implements AwarenessProvider {
|
|
113
113
|
this._postTask.schedule();
|
114
114
|
}
|
115
115
|
|
116
|
-
private _handleQueryMessage() {
|
116
|
+
private _handleQueryMessage(): void {
|
117
117
|
invariant(this._postTask);
|
118
118
|
this._postTask.schedule();
|
119
119
|
}
|
120
120
|
|
121
|
-
private _handlePostMessage(message: ProtocolMessage) {
|
121
|
+
private _handlePostMessage(message: ProtocolMessage): void {
|
122
122
|
invariant(message.kind === 'post');
|
123
123
|
// TODO(wittjosiah): Is it helpful or confusing to show cursors for self on other devices?
|
124
124
|
this._remoteStates.set(message.state.peerId, message.state);
|