@dxos/react-ui-editor 0.8.1-staging.9eaf14f → 0.8.2-main.12df754
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 +499 -371
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +67 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/node/index.cjs +515 -379
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +101 -0
- package/dist/lib/node/testing/index.cjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +499 -371
- 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 +69 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/components/EditorToolbar/util.d.ts +3 -3
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/{viewMode.d.ts → view-mode.d.ts} +1 -1
- package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -0
- package/dist/types/src/defaults.d.ts +1 -0
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/command/action.d.ts +17 -0
- package/dist/types/src/extensions/command/action.d.ts.map +1 -0
- package/dist/types/src/extensions/command/command.d.ts +5 -10
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts +4 -2
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +1 -0
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +7 -2
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +9 -11
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +9 -7
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +1 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts +4 -1
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts +2 -2
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts +4 -1
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
- package/dist/types/src/extensions/preview/index.d.ts +2 -0
- package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
- package/dist/types/src/extensions/preview/preview.d.ts +39 -0
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
- package/dist/types/src/hooks/useTextEditor.d.ts +2 -1
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/{InputMode.stories.d.ts → stories/InputMode.stories.d.ts} +1 -1
- package/dist/types/src/stories/InputMode.stories.d.ts.map +1 -0
- package/dist/types/src/{TextEditor.stories.d.ts → stories/TextEditorBasic.stories.d.ts} +2 -35
- package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +1 -0
- package/dist/types/src/stories/TextEditorComments.stories.d.ts +13 -0
- package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +1 -0
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts +13 -0
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +1 -0
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts +19 -0
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +1 -0
- package/dist/types/src/stories/story-utils.d.ts +53 -0
- package/dist/types/src/stories/story-utils.d.ts.map +1 -0
- package/dist/types/src/testing/RefPopover.d.ts +21 -0
- package/dist/types/src/testing/RefPopover.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +5 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/react.d.ts +6 -1
- package/dist/types/src/util/react.d.ts.map +1 -1
- package/package.json +33 -27
- package/src/components/EditorToolbar/EditorToolbar.tsx +2 -2
- package/src/components/EditorToolbar/util.ts +3 -3
- package/src/defaults.ts +5 -3
- package/src/extensions/automerge/automerge.stories.tsx +3 -11
- package/src/extensions/command/action.ts +49 -0
- package/src/extensions/command/command.ts +9 -27
- package/src/extensions/command/hint.ts +33 -30
- package/src/extensions/command/index.ts +1 -0
- package/src/extensions/command/menu.ts +11 -8
- package/src/extensions/command/state.ts +41 -61
- package/src/extensions/comments.ts +9 -9
- package/src/extensions/folding.tsx +1 -1
- package/src/extensions/index.ts +1 -0
- package/src/extensions/markdown/decorate.ts +4 -3
- package/src/extensions/markdown/formatting.ts +2 -2
- package/src/extensions/markdown/image.ts +12 -11
- package/src/extensions/markdown/link.ts +33 -24
- package/src/extensions/preview/index.ts +5 -0
- package/src/extensions/preview/preview.ts +271 -0
- package/src/hooks/useTextEditor.ts +4 -3
- package/src/{InputMode.stories.tsx → stories/InputMode.stories.tsx} +4 -4
- package/src/stories/TextEditorBasic.stories.tsx +289 -0
- package/src/stories/TextEditorComments.stories.tsx +99 -0
- package/src/stories/TextEditorPreview.stories.tsx +239 -0
- package/src/stories/TextEditorSpecial.stories.tsx +107 -0
- package/src/stories/story-utils.tsx +329 -0
- package/src/testing/RefPopover.tsx +74 -0
- package/src/testing/index.ts +5 -0
- package/src/types.ts +7 -0
- package/src/util/react.tsx +20 -2
- package/dist/types/src/InputMode.stories.d.ts.map +0 -1
- package/dist/types/src/TextEditor.stories.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +0 -1
- package/dist/types/src/extensions/command/preview.d.ts +0 -12
- package/dist/types/src/extensions/command/preview.d.ts.map +0 -1
- package/dist/types/src/fragments.d.ts +0 -3
- package/dist/types/src/fragments.d.ts.map +0 -1
- package/src/TextEditor.stories.tsx +0 -856
- package/src/extensions/command/preview.ts +0 -79
- package/src/fragments.ts +0 -19
- /package/src/components/EditorToolbar/{viewMode.ts → view-mode.ts} +0 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@dxos/react-ui-editor",
|
3
|
-
"version": "0.8.
|
3
|
+
"version": "0.8.2-main.12df754",
|
4
4
|
"description": "Document editing experience within a DXOS shell.",
|
5
5
|
"homepage": "https://dxos.org",
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
@@ -13,6 +13,11 @@
|
|
13
13
|
"types": "./dist/types/src/index.d.ts",
|
14
14
|
"browser": "./dist/lib/browser/index.mjs",
|
15
15
|
"node": "./dist/lib/node-esm/index.mjs"
|
16
|
+
},
|
17
|
+
"./testing": {
|
18
|
+
"types": "./dist/types/src/testing/index.d.ts",
|
19
|
+
"browser": "./dist/lib/browser/testing/index.mjs",
|
20
|
+
"node": "./dist/lib/node-esm/testing/index.mjs"
|
16
21
|
}
|
17
22
|
},
|
18
23
|
"types": "dist/types/src/index.d.ts",
|
@@ -51,20 +56,21 @@
|
|
51
56
|
"lodash.merge": "^4.6.2",
|
52
57
|
"lodash.sortby": "^4.7.0",
|
53
58
|
"style-mod": "^4.1.0",
|
54
|
-
"@dxos/app-graph": "0.8.
|
55
|
-
"@dxos/async": "0.8.
|
56
|
-
"@dxos/context": "0.8.
|
57
|
-
"@dxos/
|
58
|
-
"@dxos/
|
59
|
-
"@dxos/
|
60
|
-
"@dxos/
|
61
|
-
"@dxos/
|
62
|
-
"@dxos/
|
63
|
-
"@dxos/
|
64
|
-
"@dxos/
|
65
|
-
"@dxos/
|
66
|
-
"@dxos/react-
|
67
|
-
"@dxos/
|
59
|
+
"@dxos/app-graph": "0.8.2-main.12df754",
|
60
|
+
"@dxos/async": "0.8.2-main.12df754",
|
61
|
+
"@dxos/context": "0.8.2-main.12df754",
|
62
|
+
"@dxos/debug": "0.8.2-main.12df754",
|
63
|
+
"@dxos/display-name": "0.8.2-main.12df754",
|
64
|
+
"@dxos/echo-schema": "0.8.2-main.12df754",
|
65
|
+
"@dxos/lit-ui": "0.8.2-main.12df754",
|
66
|
+
"@dxos/invariant": "0.8.2-main.12df754",
|
67
|
+
"@dxos/automerge": "0.8.2-main.12df754",
|
68
|
+
"@dxos/log": "0.8.2-main.12df754",
|
69
|
+
"@dxos/live-object": "0.8.2-main.12df754",
|
70
|
+
"@dxos/protocols": "0.8.2-main.12df754",
|
71
|
+
"@dxos/react-hooks": "0.8.2-main.12df754",
|
72
|
+
"@dxos/react-ui-menu": "0.8.2-main.12df754",
|
73
|
+
"@dxos/util": "0.8.2-main.12df754"
|
68
74
|
},
|
69
75
|
"devDependencies": {
|
70
76
|
"@phosphor-icons/react": "^2.1.5",
|
@@ -88,23 +94,23 @@
|
|
88
94
|
"vite": "5.4.7",
|
89
95
|
"vite-plugin-top-level-await": "^1.4.1",
|
90
96
|
"vite-plugin-wasm": "^3.3.0",
|
91
|
-
"@dxos/automerge": "0.8.
|
92
|
-
"@dxos/config": "0.8.
|
93
|
-
"@dxos/echo-signals": "0.8.
|
94
|
-
"@dxos/keyboard": "0.8.
|
95
|
-
"@dxos/
|
96
|
-
"@dxos/react-
|
97
|
-
"@dxos/
|
98
|
-
"@dxos/
|
99
|
-
"@dxos/
|
97
|
+
"@dxos/automerge": "0.8.2-main.12df754",
|
98
|
+
"@dxos/config": "0.8.2-main.12df754",
|
99
|
+
"@dxos/echo-signals": "0.8.2-main.12df754",
|
100
|
+
"@dxos/keyboard": "0.8.2-main.12df754",
|
101
|
+
"@dxos/react-client": "0.8.2-main.12df754",
|
102
|
+
"@dxos/react-ui-theme": "0.8.2-main.12df754",
|
103
|
+
"@dxos/random": "0.8.2-main.12df754",
|
104
|
+
"@dxos/storybook-utils": "0.8.2-main.12df754",
|
105
|
+
"@dxos/react-ui": "0.8.2-main.12df754"
|
100
106
|
},
|
101
107
|
"peerDependencies": {
|
102
108
|
"@phosphor-icons/react": "^2.1.5",
|
103
109
|
"react": "~18.2.0",
|
104
110
|
"react-dom": "~18.2.0",
|
105
|
-
"@dxos/react-client": "0.8.
|
106
|
-
"@dxos/react-ui": "0.8.
|
107
|
-
"@dxos/react-ui-theme": "0.8.
|
111
|
+
"@dxos/react-client": "0.8.2-main.12df754",
|
112
|
+
"@dxos/react-ui": "0.8.2-main.12df754",
|
113
|
+
"@dxos/react-ui-theme": "0.8.2-main.12df754"
|
108
114
|
},
|
109
115
|
"publishConfig": {
|
110
116
|
"access": "public"
|
@@ -26,8 +26,8 @@ import {
|
|
26
26
|
type EditorToolbarProps,
|
27
27
|
editorToolbarSearch,
|
28
28
|
} from './util';
|
29
|
-
import { createViewMode } from './
|
30
|
-
import { stackItemContentToolbarClassNames } from '../../
|
29
|
+
import { createViewMode } from './view-mode';
|
30
|
+
import { stackItemContentToolbarClassNames } from '../../defaults';
|
31
31
|
|
32
32
|
const createToolbar = ({
|
33
33
|
state,
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
import { useMemo } from 'react';
|
6
6
|
|
7
|
-
import {
|
7
|
+
import { live, type Live } from '@dxos/live-object';
|
8
8
|
import { type Label, type ThemedClassName } from '@dxos/react-ui';
|
9
9
|
import {
|
10
10
|
type MenuSeparator,
|
@@ -23,7 +23,7 @@ export type EditorToolbarState = Formatting &
|
|
23
23
|
Partial<{ comment: boolean; viewMode: EditorViewMode; selection: boolean }>;
|
24
24
|
|
25
25
|
export const useEditorToolbarState = (initialState: Partial<EditorToolbarState> = {}) => {
|
26
|
-
return useMemo(() =>
|
26
|
+
return useMemo(() => live<EditorToolbarState>(initialState), []);
|
27
27
|
};
|
28
28
|
|
29
29
|
export type EditorToolbarFeatureFlags = Partial<{
|
@@ -37,7 +37,7 @@ export type EditorToolbarFeatureFlags = Partial<{
|
|
37
37
|
}>;
|
38
38
|
|
39
39
|
export type EditorToolbarActionGraphProps = {
|
40
|
-
state:
|
40
|
+
state: Live<EditorToolbarState>;
|
41
41
|
// TODO(wittjosiah): Control positioning.
|
42
42
|
customActions?: () => ActionGraphProps;
|
43
43
|
onAction: (action: EditorAction) => void;
|
package/src/defaults.ts
CHANGED
@@ -17,7 +17,9 @@ const margin = '!mt-[1rem]';
|
|
17
17
|
* NOTE: Max width - 4rem = 2rem left/right margin (or 2rem gutter plus 1rem left/right margin).
|
18
18
|
*/
|
19
19
|
// TOOD(burdon): Adjust depending on
|
20
|
-
export const
|
20
|
+
export const editorWidth = '!mli-auto is-full max-is-[min(50rem,100%-4rem)]';
|
21
|
+
|
22
|
+
export const editorContent = mx(margin, editorWidth);
|
21
23
|
|
22
24
|
/**
|
23
25
|
* Margin for numbers.
|
@@ -50,6 +52,6 @@ export const stackItemContentEditorClassNames = (role?: string) =>
|
|
50
52
|
|
51
53
|
export const stackItemContentToolbarClassNames = (role?: string) =>
|
52
54
|
mx(
|
53
|
-
'attention-surface is-full border-be !border-separator',
|
54
|
-
role === 'section' && 'sticky block-start-0
|
55
|
+
'attention-surface is-full border-be !border-separator relative z-[1]',
|
56
|
+
role === 'section' && 'sticky block-start-0 -mbe-px min-is-0',
|
55
57
|
);
|
@@ -10,15 +10,7 @@ import React, { useEffect, useState } from 'react';
|
|
10
10
|
import { Repo } from '@dxos/automerge/automerge-repo';
|
11
11
|
import { BroadcastChannelNetworkAdapter } from '@dxos/automerge/automerge-repo-network-broadcastchannel';
|
12
12
|
import { Expando } from '@dxos/echo-schema';
|
13
|
-
import {
|
14
|
-
DocAccessor,
|
15
|
-
Filter,
|
16
|
-
create,
|
17
|
-
createDocAccessor,
|
18
|
-
useQuery,
|
19
|
-
useSpace,
|
20
|
-
type Space,
|
21
|
-
} from '@dxos/react-client/echo';
|
13
|
+
import { DocAccessor, Filter, live, createDocAccessor, useQuery, useSpace, type Space } from '@dxos/react-client/echo';
|
22
14
|
import { useIdentity, type Identity } from '@dxos/react-client/halo';
|
23
15
|
import { ClientRepeater, type ClientRepeatedComponentProps } from '@dxos/react-client/testing';
|
24
16
|
import { useThemeContext } from '@dxos/react-ui';
|
@@ -139,9 +131,9 @@ export const WithEcho = {
|
|
139
131
|
createSpace
|
140
132
|
onSpaceCreated={async ({ space }) => {
|
141
133
|
space.db.add(
|
142
|
-
|
134
|
+
live({
|
143
135
|
type: 'test',
|
144
|
-
content:
|
136
|
+
content: live(Expando, { content: initialContent }),
|
145
137
|
}),
|
146
138
|
);
|
147
139
|
}}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { StateEffect } from '@codemirror/state';
|
6
|
+
import { type KeyBinding, type Command, type EditorView } from '@codemirror/view';
|
7
|
+
|
8
|
+
import { commandState } from './state';
|
9
|
+
|
10
|
+
export type Action =
|
11
|
+
| {
|
12
|
+
type: 'insert';
|
13
|
+
text: string;
|
14
|
+
}
|
15
|
+
| {
|
16
|
+
type: 'cancel';
|
17
|
+
};
|
18
|
+
|
19
|
+
export type ActionHandler = (action: Action) => void;
|
20
|
+
|
21
|
+
export const openEffect = StateEffect.define<{ pos: number; fullWidth?: boolean }>();
|
22
|
+
export const closeEffect = StateEffect.define<null>();
|
23
|
+
|
24
|
+
export const openCommand: Command = (view: EditorView) => {
|
25
|
+
if (view.state.field(commandState, false)) {
|
26
|
+
const selection = view.state.selection.main;
|
27
|
+
const line = view.state.doc.lineAt(selection.from);
|
28
|
+
if (line.from === selection.from && line.from === line.to) {
|
29
|
+
view.dispatch({ effects: openEffect.of({ pos: selection.anchor, fullWidth: true }) });
|
30
|
+
return true;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
return false;
|
35
|
+
};
|
36
|
+
|
37
|
+
export const closeCommand: Command = (view: EditorView) => {
|
38
|
+
if (view.state.field(commandState, false)) {
|
39
|
+
view.dispatch({ effects: closeEffect.of(null) });
|
40
|
+
return true;
|
41
|
+
}
|
42
|
+
|
43
|
+
return false;
|
44
|
+
};
|
45
|
+
|
46
|
+
export const commandKeyBindings: readonly KeyBinding[] = [
|
47
|
+
{ key: '/', run: openCommand },
|
48
|
+
{ key: 'Escape', run: closeCommand },
|
49
|
+
];
|
@@ -5,35 +5,25 @@
|
|
5
5
|
import { type Extension } from '@codemirror/state';
|
6
6
|
import { EditorView, keymap } from '@codemirror/view';
|
7
7
|
|
8
|
-
import {
|
9
|
-
import {
|
10
|
-
import {
|
11
|
-
import {
|
8
|
+
import { closeEffect, commandKeyBindings } from './action';
|
9
|
+
import { hintViewPlugin, type HintOptions } from './hint';
|
10
|
+
import { floatingMenu, type FloatingMenuOptions } from './menu';
|
11
|
+
import { commandConfig, commandState, type PopupOptions } from './state';
|
12
12
|
|
13
13
|
// TODO(burdon): Create knowledge base for CM notes and ideas.
|
14
14
|
// https://discuss.codemirror.net/t/inline-code-hints-like-vscode/5533/4
|
15
15
|
// https://github.com/saminzadeh/codemirror-extension-inline-suggestion
|
16
16
|
// https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/ui/components/text_editor/config.ts#L370
|
17
17
|
|
18
|
-
|
19
|
-
export type CommandAction = {
|
20
|
-
insert?: string;
|
21
|
-
};
|
22
|
-
|
23
|
-
export type CommandOptions = {
|
24
|
-
onHint: () => string | undefined;
|
25
|
-
onRenderDialog: (el: HTMLElement, cb: (action?: CommandAction) => void) => void;
|
26
|
-
onRenderMenu: (el: HTMLElement, cb: () => void) => void;
|
27
|
-
} & Pick<PreviewOptions, 'onRenderPreview'>;
|
18
|
+
export type CommandOptions = Partial<PopupOptions & FloatingMenuOptions & HintOptions>;
|
28
19
|
|
29
|
-
export const command = (options: CommandOptions): Extension => {
|
20
|
+
export const command = (options: CommandOptions = {}): Extension => {
|
30
21
|
return [
|
22
|
+
keymap.of(commandKeyBindings),
|
31
23
|
commandConfig.of(options),
|
32
24
|
commandState,
|
33
|
-
|
34
|
-
|
35
|
-
floatingMenu(options),
|
36
|
-
hintViewPlugin(options),
|
25
|
+
options.renderMenu ? floatingMenu({ renderMenu: options.renderMenu }) : [],
|
26
|
+
options.onHint ? hintViewPlugin({ onHint: options.onHint }) : [],
|
37
27
|
EditorView.focusChangeEffect.of((_, focusing) => {
|
38
28
|
return focusing ? closeEffect.of(null) : null;
|
39
29
|
}),
|
@@ -41,14 +31,6 @@ export const command = (options: CommandOptions): Extension => {
|
|
41
31
|
'.cm-tooltip': {
|
42
32
|
background: 'transparent',
|
43
33
|
},
|
44
|
-
'.cm-preview': {
|
45
|
-
marginLeft: '-1rem',
|
46
|
-
marginRight: '-1rem',
|
47
|
-
padding: '1rem',
|
48
|
-
borderRadius: '1rem',
|
49
|
-
background: 'var(--dx-modalSurface)',
|
50
|
-
border: '1px solid var(--dx-separator)',
|
51
|
-
},
|
52
34
|
}),
|
53
35
|
];
|
54
36
|
};
|
@@ -5,10 +5,42 @@
|
|
5
5
|
import { RangeSetBuilder } from '@codemirror/state';
|
6
6
|
import { Decoration, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
|
7
7
|
|
8
|
-
import { type CommandOptions } from './command';
|
9
8
|
import { commandState } from './state';
|
10
9
|
import { clientRectsFor, flattenRect } from '../../util';
|
11
10
|
|
11
|
+
export type HintOptions = {
|
12
|
+
onHint: () => string | undefined;
|
13
|
+
};
|
14
|
+
|
15
|
+
export const hintViewPlugin = ({ onHint }: HintOptions) =>
|
16
|
+
ViewPlugin.fromClass(
|
17
|
+
class {
|
18
|
+
deco = Decoration.none;
|
19
|
+
update(update: ViewUpdate) {
|
20
|
+
const builder = new RangeSetBuilder<Decoration>();
|
21
|
+
const cState = update.view.state.field(commandState, false);
|
22
|
+
if (!cState?.tooltip) {
|
23
|
+
const selection = update.view.state.selection.main;
|
24
|
+
const line = update.view.state.doc.lineAt(selection.from);
|
25
|
+
// Only show if blank line.
|
26
|
+
// TODO(burdon): Clashes with placeholder if pos === 0.
|
27
|
+
// TODO(burdon): Show after delay or if blank line above?
|
28
|
+
if (selection.from === selection.to && line.from === line.to) {
|
29
|
+
const hint = onHint();
|
30
|
+
if (hint) {
|
31
|
+
builder.add(selection.from, selection.to, Decoration.widget({ widget: new CommandHint(hint) }));
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
this.deco = builder.finish();
|
37
|
+
}
|
38
|
+
},
|
39
|
+
{
|
40
|
+
provide: (plugin) => [EditorView.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration.none)],
|
41
|
+
},
|
42
|
+
);
|
43
|
+
|
12
44
|
class CommandHint extends WidgetType {
|
13
45
|
constructor(readonly content: string | HTMLElement) {
|
14
46
|
super();
|
@@ -48,32 +80,3 @@ class CommandHint extends WidgetType {
|
|
48
80
|
return false;
|
49
81
|
}
|
50
82
|
}
|
51
|
-
|
52
|
-
export const hintViewPlugin = ({ onHint }: CommandOptions) =>
|
53
|
-
ViewPlugin.fromClass(
|
54
|
-
class {
|
55
|
-
deco = Decoration.none;
|
56
|
-
update(update: ViewUpdate) {
|
57
|
-
const builder = new RangeSetBuilder<Decoration>();
|
58
|
-
const cState = update.view.state.field(commandState, false);
|
59
|
-
if (!cState?.tooltip) {
|
60
|
-
const selection = update.view.state.selection.main;
|
61
|
-
const line = update.view.state.doc.lineAt(selection.from);
|
62
|
-
// Only show if blank line.
|
63
|
-
// TODO(burdon): Clashes with placeholder if pos === 0.
|
64
|
-
// TODO(burdon): Show after delay or if blank line above?
|
65
|
-
if (selection.from === selection.to && line.from === line.to) {
|
66
|
-
const hint = onHint();
|
67
|
-
if (hint) {
|
68
|
-
builder.add(selection.from, selection.to, Decoration.widget({ widget: new CommandHint(hint) }));
|
69
|
-
}
|
70
|
-
}
|
71
|
-
}
|
72
|
-
|
73
|
-
this.deco = builder.finish();
|
74
|
-
}
|
75
|
-
},
|
76
|
-
{
|
77
|
-
provide: (plugin) => [EditorView.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration.none)],
|
78
|
-
},
|
79
|
-
);
|
@@ -4,12 +4,16 @@
|
|
4
4
|
|
5
5
|
import { type BlockInfo, type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
6
6
|
|
7
|
-
import {
|
8
|
-
import {
|
7
|
+
import { closeEffect, openCommand, openEffect } from './action';
|
8
|
+
import { type RenderCallback } from '../../types';
|
9
|
+
|
10
|
+
export type FloatingMenuOptions = {
|
11
|
+
renderMenu: RenderCallback<{ onAction: () => void }>;
|
12
|
+
};
|
9
13
|
|
10
14
|
// TODO(burdon): Trigger completion on click.
|
11
15
|
// TODO(burdon): Hide when dialog is open.
|
12
|
-
export const floatingMenu = (options:
|
16
|
+
export const floatingMenu = (options: FloatingMenuOptions) =>
|
13
17
|
ViewPlugin.fromClass(
|
14
18
|
class {
|
15
19
|
button: HTMLElement;
|
@@ -31,13 +35,11 @@ export const floatingMenu = (options: CommandOptions) =>
|
|
31
35
|
this.button.style.zIndex = '10';
|
32
36
|
this.button.style.display = 'none';
|
33
37
|
|
34
|
-
options.
|
35
|
-
openCommand(view);
|
36
|
-
});
|
38
|
+
options.renderMenu(this.button, { onAction: () => openCommand(view) }, view);
|
37
39
|
container.appendChild(this.button);
|
38
40
|
|
39
41
|
// Listen for scroll events.
|
40
|
-
container.addEventListener('scroll', this.scheduleUpdate);
|
42
|
+
container.addEventListener('scroll', this.scheduleUpdate.bind(this));
|
41
43
|
this.scheduleUpdate();
|
42
44
|
}
|
43
45
|
|
@@ -56,7 +58,8 @@ export const floatingMenu = (options: CommandOptions) =>
|
|
56
58
|
if (this.rafId != null) {
|
57
59
|
cancelAnimationFrame(this.rafId);
|
58
60
|
}
|
59
|
-
|
61
|
+
|
62
|
+
this.rafId = requestAnimationFrame(this.updateButtonPosition.bind(this));
|
60
63
|
}
|
61
64
|
|
62
65
|
updateButtonPosition() {
|
@@ -2,25 +2,24 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
showTooltip,
|
8
|
-
type Command,
|
9
|
-
type EditorView,
|
10
|
-
type KeyBinding,
|
11
|
-
type Tooltip,
|
12
|
-
type TooltipView,
|
13
|
-
} from '@codemirror/view';
|
5
|
+
import { StateField } from '@codemirror/state';
|
6
|
+
import { showTooltip, type EditorView, type Tooltip, type TooltipView } from '@codemirror/view';
|
14
7
|
|
8
|
+
import { closeEffect, type Action, openEffect } from './action';
|
15
9
|
import { type CommandOptions } from './command';
|
10
|
+
import { type RenderCallback } from '../../types';
|
16
11
|
import { singleValueFacet } from '../../util';
|
17
12
|
|
13
|
+
export const commandConfig = singleValueFacet<CommandOptions>();
|
14
|
+
|
15
|
+
export type PopupOptions = {
|
16
|
+
renderDialog: RenderCallback<{ onAction: (action?: Action) => void }>;
|
17
|
+
};
|
18
|
+
|
18
19
|
type CommandState = {
|
19
20
|
tooltip?: Tooltip | null;
|
20
21
|
};
|
21
22
|
|
22
|
-
export const commandConfig = singleValueFacet<CommandOptions>();
|
23
|
-
|
24
23
|
export const commandState = StateField.define<CommandState>({
|
25
24
|
create: () => ({}),
|
26
25
|
update: (state, tr) => {
|
@@ -29,8 +28,8 @@ export const commandState = StateField.define<CommandState>({
|
|
29
28
|
return {};
|
30
29
|
}
|
31
30
|
|
32
|
-
|
33
|
-
|
31
|
+
const { renderDialog } = tr.state.facet(commandConfig);
|
32
|
+
if (effect.is(openEffect) && renderDialog) {
|
34
33
|
const { pos, fullWidth } = effect.value;
|
35
34
|
const tooltip: Tooltip = {
|
36
35
|
pos,
|
@@ -38,38 +37,49 @@ export const commandState = StateField.define<CommandState>({
|
|
38
37
|
arrow: false,
|
39
38
|
strictSide: true,
|
40
39
|
create: (view: EditorView) => {
|
41
|
-
const
|
40
|
+
const root = document.createElement('div');
|
41
|
+
|
42
42
|
const tooltipView: TooltipView = {
|
43
|
-
dom,
|
43
|
+
dom: root,
|
44
44
|
mount: (view: EditorView) => {
|
45
45
|
if (fullWidth) {
|
46
|
-
const parent =
|
46
|
+
const parent = root.parentElement!;
|
47
47
|
const { paddingLeft, paddingRight } = window.getComputedStyle(parent);
|
48
48
|
const widthWithoutPadding = parent.clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight);
|
49
|
-
|
49
|
+
root.style.width = `${widthWithoutPadding}px`;
|
50
50
|
}
|
51
51
|
|
52
52
|
// Render react component.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
53
|
+
renderDialog(
|
54
|
+
root,
|
55
|
+
{
|
56
|
+
onAction: (action) => {
|
57
|
+
view.dispatch({ effects: closeEffect.of(null) });
|
58
|
+
switch (action?.type) {
|
59
|
+
case 'insert': {
|
60
|
+
// Insert into editor.
|
61
|
+
const text = action.text + '\n';
|
62
|
+
view.dispatch({
|
63
|
+
changes: { from: pos, insert: text },
|
64
|
+
selection: { anchor: pos + text.length },
|
65
|
+
});
|
66
|
+
break;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
// NOTE: Truncates text if set focus immediately.
|
71
|
+
requestAnimationFrame(() => view.focus());
|
72
|
+
},
|
73
|
+
},
|
74
|
+
view,
|
75
|
+
);
|
67
76
|
},
|
68
77
|
};
|
69
78
|
|
70
79
|
return tooltipView;
|
71
80
|
},
|
72
81
|
};
|
82
|
+
|
73
83
|
return { tooltip };
|
74
84
|
}
|
75
85
|
}
|
@@ -78,33 +88,3 @@ export const commandState = StateField.define<CommandState>({
|
|
78
88
|
},
|
79
89
|
provide: (field) => [showTooltip.from(field, (value) => value.tooltip ?? null)],
|
80
90
|
});
|
81
|
-
|
82
|
-
export const openEffect = StateEffect.define<{ pos: number; fullWidth?: boolean }>();
|
83
|
-
export const closeEffect = StateEffect.define<null>();
|
84
|
-
|
85
|
-
export const openCommand: Command = (view: EditorView) => {
|
86
|
-
if (view.state.field(commandState, false)) {
|
87
|
-
const selection = view.state.selection.main;
|
88
|
-
const line = view.state.doc.lineAt(selection.from);
|
89
|
-
if (line.from === selection.from && line.from === line.to) {
|
90
|
-
view.dispatch({ effects: openEffect.of({ pos: selection.anchor, fullWidth: true }) });
|
91
|
-
return true;
|
92
|
-
}
|
93
|
-
}
|
94
|
-
|
95
|
-
return false;
|
96
|
-
};
|
97
|
-
|
98
|
-
export const closeCommand: Command = (view: EditorView) => {
|
99
|
-
if (view.state.field(commandState, false)) {
|
100
|
-
view.dispatch({ effects: closeEffect.of(null) });
|
101
|
-
return true;
|
102
|
-
}
|
103
|
-
|
104
|
-
return false;
|
105
|
-
};
|
106
|
-
|
107
|
-
export const commandKeyBindings: readonly KeyBinding[] = [
|
108
|
-
{ key: '/', run: openCommand },
|
109
|
-
{ key: 'Escape', run: closeCommand },
|
110
|
-
];
|
@@ -25,13 +25,13 @@ import sortBy from 'lodash.sortby';
|
|
25
25
|
import { useEffect, useMemo } from 'react';
|
26
26
|
|
27
27
|
import { debounce, type CleanupFn } from '@dxos/async';
|
28
|
-
import { type
|
28
|
+
import { type Live } from '@dxos/live-object';
|
29
29
|
import { log } from '@dxos/log';
|
30
30
|
import { isNonNullable } from '@dxos/util';
|
31
31
|
|
32
32
|
import { documentId } from './selection';
|
33
33
|
import { type EditorToolbarState } from '../components';
|
34
|
-
import { type Comment, type Range } from '../types';
|
34
|
+
import { type RenderCallback, type Comment, type Range } from '../types';
|
35
35
|
import { Cursor, overlap, singleValueFacet, callbackWrapper } from '../util';
|
36
36
|
|
37
37
|
//
|
@@ -345,6 +345,10 @@ export type CommentsOptions = {
|
|
345
345
|
* Key shortcut to create a new thread.
|
346
346
|
*/
|
347
347
|
key?: string;
|
348
|
+
/**
|
349
|
+
* Called to render tooltip.
|
350
|
+
*/
|
351
|
+
renderTooltip?: RenderCallback<{ shortcut: string }>;
|
348
352
|
/**
|
349
353
|
* Called to create a new thread and return the thread id.
|
350
354
|
*/
|
@@ -361,10 +365,6 @@ export type CommentsOptions = {
|
|
361
365
|
* Called to notify which thread is currently closest to the cursor.
|
362
366
|
*/
|
363
367
|
onSelect?: (state: CommentsState) => void;
|
364
|
-
/**
|
365
|
-
* Called to render tooltip.
|
366
|
-
*/
|
367
|
-
onHover?: (el: Element, shortcut: string) => void;
|
368
368
|
};
|
369
369
|
|
370
370
|
const optionsFacet = singleValueFacet<CommentsOptions>();
|
@@ -408,7 +408,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
408
408
|
// Hover tooltip (for key shortcut hints, etc.)
|
409
409
|
// TODO(burdon): Factor out to generic hints extension for current selection/line.
|
410
410
|
//
|
411
|
-
options.
|
411
|
+
options.renderTooltip &&
|
412
412
|
hoverTooltip(
|
413
413
|
(view, pos) => {
|
414
414
|
const selection = view.state.selection.main;
|
@@ -419,7 +419,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
|
|
419
419
|
above: true,
|
420
420
|
create: () => {
|
421
421
|
const el = document.createElement('div');
|
422
|
-
options.
|
422
|
+
options.renderTooltip!(el, { shortcut }, view);
|
423
423
|
return { dom: el, offset: { x: 0, y: 8 } };
|
424
424
|
},
|
425
425
|
};
|
@@ -606,7 +606,7 @@ export const createExternalCommentSync = (
|
|
606
606
|
},
|
607
607
|
);
|
608
608
|
|
609
|
-
export const useCommentState = (state:
|
609
|
+
export const useCommentState = (state: Live<EditorToolbarState>): Extension => {
|
610
610
|
return useMemo(
|
611
611
|
() =>
|
612
612
|
EditorView.updateListener.of((update) => {
|
@@ -29,7 +29,7 @@ export const folding = (_props: FoldingOptions = {}): Extension => [
|
|
29
29
|
const el = createElement('div', { className: 'flex h-full items-center' });
|
30
30
|
return renderRoot(
|
31
31
|
el,
|
32
|
-
<Icon icon='ph--caret-right--
|
32
|
+
<Icon icon='ph--caret-right--bold' size={3} classNames={['mx-3 cursor-pointer', open && 'rotate-90']} />,
|
33
33
|
);
|
34
34
|
},
|
35
35
|
}),
|