@dxos/react-ui-editor 0.8.3-main.672df60 → 0.8.3-staging.0fa589b
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 +981 -377
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +1025 -420
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +981 -377
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/util.d.ts +2 -2
- package/dist/types/src/components/Popover/CommandMenu.d.ts +34 -0
- package/dist/types/src/components/Popover/CommandMenu.d.ts.map +1 -0
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Popover/RefPopover.d.ts +19 -6
- package/dist/types/src/components/Popover/RefPopover.d.ts.map +1 -1
- package/dist/types/src/components/Popover/index.d.ts +1 -0
- package/dist/types/src/components/Popover/index.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +0 -1
- 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.map +1 -1
- package/dist/types/src/extensions/command/command-menu.d.ts +20 -0
- package/dist/types/src/extensions/command/command-menu.d.ts.map +1 -0
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/{menu.d.ts → floating-menu.d.ts} +1 -1
- package/dist/types/src/extensions/command/floating-menu.d.ts.map +1 -0
- package/dist/types/src/extensions/command/hint.d.ts +5 -2
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +3 -1
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/placeholder.d.ts +10 -0
- package/dist/types/src/extensions/command/placeholder.d.ts.map +1 -0
- 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/useCommandMenu.d.ts +26 -0
- package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +1 -0
- package/dist/types/src/extensions/factories.d.ts +1 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
- package/dist/types/src/extensions/preview/preview.d.ts +12 -19
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts +8 -9
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/stories/CommandMenu.stories.d.ts +13 -0
- package/dist/types/src/stories/CommandMenu.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
- package/dist/types/src/util/dom.d.ts +5 -0
- package/dist/types/src/util/dom.d.ts.map +1 -1
- package/dist/types/src/util/react.d.ts +2 -4
- package/dist/types/src/util/react.d.ts.map +1 -1
- package/package.json +31 -28
- package/src/components/EditorToolbar/EditorToolbar.tsx +5 -9
- package/src/components/Popover/CommandMenu.tsx +279 -0
- package/src/components/Popover/RefDropdownMenu.tsx +5 -3
- package/src/components/Popover/RefPopover.tsx +46 -22
- package/src/components/Popover/index.ts +1 -0
- package/src/defaults.ts +1 -6
- package/src/extensions/automerge/automerge.stories.tsx +5 -5
- package/src/extensions/command/action.ts +9 -2
- package/src/extensions/command/command-menu.ts +210 -0
- package/src/extensions/command/command.ts +8 -8
- package/src/extensions/command/{menu.ts → floating-menu.ts} +0 -4
- package/src/extensions/command/hint.ts +29 -9
- package/src/extensions/command/index.ts +3 -1
- package/src/extensions/command/placeholder.ts +113 -0
- package/src/extensions/command/state.ts +1 -2
- package/src/extensions/command/useCommandMenu.ts +118 -0
- package/src/extensions/factories.ts +4 -1
- package/src/extensions/markdown/bundle.ts +0 -2
- package/src/extensions/outliner/outliner.ts +0 -3
- package/src/extensions/outliner/tree.test.ts +13 -10
- package/src/extensions/outliner/tree.ts +5 -3
- package/src/extensions/preview/preview.ts +11 -89
- package/src/hooks/useTextEditor.ts +11 -12
- package/src/stories/Command.stories.tsx +1 -1
- package/src/stories/CommandMenu.stories.tsx +159 -0
- package/src/stories/Preview.stories.tsx +157 -78
- package/src/stories/components/util.tsx +2 -2
- package/src/util/dom.ts +20 -0
- package/src/util/react.tsx +3 -20
- package/dist/types/src/extensions/command/menu.d.ts.map +0 -1
@@ -16,8 +16,6 @@ import {
|
|
16
16
|
import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
|
17
17
|
import { type SyntaxNode } from '@lezer/common';
|
18
18
|
|
19
|
-
import { type RenderCallback } from '../../types';
|
20
|
-
|
21
19
|
export type PreviewLinkRef = {
|
22
20
|
suggest?: boolean;
|
23
21
|
block?: boolean;
|
@@ -31,32 +29,13 @@ export type PreviewLinkTarget = {
|
|
31
29
|
object?: any;
|
32
30
|
};
|
33
31
|
|
34
|
-
|
35
|
-
| {
|
36
|
-
type: 'insert';
|
37
|
-
link: PreviewLinkRef;
|
38
|
-
target: PreviewLinkTarget;
|
39
|
-
}
|
40
|
-
| {
|
41
|
-
type: 'delete';
|
42
|
-
link: PreviewLinkRef;
|
43
|
-
};
|
44
|
-
|
32
|
+
// TODO(wittjosiah): Remove.
|
45
33
|
// TODO(burdon): Handle error.
|
46
34
|
export type PreviewLookup = (link: PreviewLinkRef) => Promise<PreviewLinkTarget | null | undefined>;
|
47
35
|
|
48
|
-
export type PreviewActionHandler = (action: PreviewAction) => void;
|
49
|
-
|
50
|
-
export type PreviewRenderProps = {
|
51
|
-
readonly: boolean;
|
52
|
-
link: PreviewLinkRef;
|
53
|
-
onAction: PreviewActionHandler;
|
54
|
-
onLookup?: PreviewLookup;
|
55
|
-
};
|
56
|
-
|
57
36
|
export type PreviewOptions = {
|
58
|
-
|
59
|
-
|
37
|
+
addBlockContainer?: (link: PreviewLinkRef, el: HTMLElement) => void;
|
38
|
+
removeBlockContainer?: (link: PreviewLinkRef) => void;
|
60
39
|
};
|
61
40
|
|
62
41
|
/**
|
@@ -74,17 +53,6 @@ export const preview = (options: PreviewOptions = {}): Extension => {
|
|
74
53
|
EditorView.atomicRanges.of((view) => view.state.field(field)),
|
75
54
|
],
|
76
55
|
}),
|
77
|
-
|
78
|
-
EditorView.theme({
|
79
|
-
'.cm-preview-block': {
|
80
|
-
marginLeft: '-1rem',
|
81
|
-
marginRight: '-1rem',
|
82
|
-
padding: '1rem',
|
83
|
-
borderRadius: '0.5rem',
|
84
|
-
background: 'var(--dx-modalSurface)',
|
85
|
-
border: '1px solid var(--dx-separator)',
|
86
|
-
},
|
87
|
-
}),
|
88
56
|
];
|
89
57
|
};
|
90
58
|
|
@@ -95,7 +63,7 @@ export const preview = (options: PreviewOptions = {}): Extension => {
|
|
95
63
|
* ![Label][dxn:echo:123] Block reference
|
96
64
|
* ![Label][?dxn:echo:123] Suggestion
|
97
65
|
*/
|
98
|
-
const getLinkRef = (state: EditorState, node: SyntaxNode): PreviewLinkRef | undefined => {
|
66
|
+
export const getLinkRef = (state: EditorState, node: SyntaxNode): PreviewLinkRef | undefined => {
|
99
67
|
const mark = node.getChild('LinkMark');
|
100
68
|
const label = node.getChild('LinkLabel');
|
101
69
|
if (mark && label) {
|
@@ -144,7 +112,7 @@ const buildDecorations = (state: EditorState, options: PreviewOptions) => {
|
|
144
112
|
//
|
145
113
|
case 'Image': {
|
146
114
|
const link = getLinkRef(state, node.node);
|
147
|
-
if (options.
|
115
|
+
if (options.addBlockContainer && options.removeBlockContainer && link) {
|
148
116
|
builder.add(
|
149
117
|
node.from,
|
150
118
|
node.to,
|
@@ -214,58 +182,12 @@ class PreviewBlockWidget extends WidgetType {
|
|
214
182
|
|
215
183
|
override toDOM(view: EditorView): HTMLDivElement {
|
216
184
|
const root = document.createElement('div');
|
217
|
-
root.classList.add('cm-preview-block');
|
218
|
-
|
219
|
-
// TODO(burdon): Inject handler.
|
220
|
-
const handleAction: PreviewActionHandler = (action) => {
|
221
|
-
const pos = view.posAtDOM(root);
|
222
|
-
const node = syntaxTree(view.state).resolve(pos + 1).node.parent;
|
223
|
-
if (!node) {
|
224
|
-
return;
|
225
|
-
}
|
226
|
-
|
227
|
-
const link = getLinkRef(view.state, node);
|
228
|
-
if (link?.ref !== action.link.ref) {
|
229
|
-
return;
|
230
|
-
}
|
231
|
-
|
232
|
-
switch (action.type) {
|
233
|
-
// TODO(burdon): Should we dispatch to the view or mutate the document? (i.e., handle externally?)
|
234
|
-
// Insert ref text.
|
235
|
-
case 'insert': {
|
236
|
-
view.dispatch({
|
237
|
-
changes: {
|
238
|
-
from: node.from,
|
239
|
-
to: node.to,
|
240
|
-
insert: action.target.text,
|
241
|
-
},
|
242
|
-
});
|
243
|
-
break;
|
244
|
-
}
|
245
|
-
// Remove ref.
|
246
|
-
case 'delete': {
|
247
|
-
view.dispatch({
|
248
|
-
changes: {
|
249
|
-
from: node.from,
|
250
|
-
to: node.to,
|
251
|
-
},
|
252
|
-
});
|
253
|
-
break;
|
254
|
-
}
|
255
|
-
}
|
256
|
-
};
|
257
|
-
|
258
|
-
this._options.renderBlock!(
|
259
|
-
root,
|
260
|
-
{
|
261
|
-
readonly: view.state.readOnly,
|
262
|
-
link: this._link,
|
263
|
-
onAction: handleAction,
|
264
|
-
onLookup: this._options.onLookup,
|
265
|
-
},
|
266
|
-
view,
|
267
|
-
);
|
268
|
-
|
185
|
+
root.classList.add('cm-preview-block', 'density-coarse');
|
186
|
+
this._options.addBlockContainer?.(this._link, root);
|
269
187
|
return root;
|
270
188
|
}
|
189
|
+
|
190
|
+
override destroy() {
|
191
|
+
this._options.removeBlockContainer?.(this._link);
|
192
|
+
}
|
271
193
|
}
|
@@ -22,15 +22,7 @@ import { getProviderValue, isNotFalsy, type MaybeProvider } from '@dxos/util';
|
|
22
22
|
import { type EditorSelection, documentId, createEditorStateTransaction, editorInputMode } from '../extensions';
|
23
23
|
import { debugDispatcher } from '../util';
|
24
24
|
|
25
|
-
|
26
|
-
// TODO(burdon): Rename.
|
27
|
-
parentRef: RefObject<HTMLDivElement>;
|
28
|
-
view?: EditorView;
|
29
|
-
focusAttributes?: TabsterTypes.TabsterDOMAttribute & {
|
30
|
-
tabIndex: 0;
|
31
|
-
onKeyUp: KeyboardEventHandler<HTMLDivElement>;
|
32
|
-
};
|
33
|
-
};
|
25
|
+
let instanceCount = 0;
|
34
26
|
|
35
27
|
export type CursorInfo = {
|
36
28
|
from: number;
|
@@ -41,11 +33,20 @@ export type CursorInfo = {
|
|
41
33
|
after?: string;
|
42
34
|
};
|
43
35
|
|
36
|
+
export type UseTextEditor = {
|
37
|
+
// TODO(burdon): Rename.
|
38
|
+
parentRef: RefObject<HTMLDivElement>;
|
39
|
+
view?: EditorView;
|
40
|
+
focusAttributes?: TabsterTypes.TabsterDOMAttribute & {
|
41
|
+
tabIndex: 0;
|
42
|
+
onKeyUp: KeyboardEventHandler<HTMLDivElement>;
|
43
|
+
};
|
44
|
+
};
|
45
|
+
|
44
46
|
export type UseTextEditorProps = Pick<EditorStateConfig, 'extensions'> & {
|
45
47
|
id?: string;
|
46
48
|
doc?: Text;
|
47
49
|
initialValue?: string;
|
48
|
-
className?: string;
|
49
50
|
autoFocus?: boolean;
|
50
51
|
scrollTo?: number;
|
51
52
|
selection?: EditorSelection;
|
@@ -53,8 +54,6 @@ export type UseTextEditorProps = Pick<EditorStateConfig, 'extensions'> & {
|
|
53
54
|
debug?: boolean;
|
54
55
|
};
|
55
56
|
|
56
|
-
let instanceCount = 0;
|
57
|
-
|
58
57
|
/**
|
59
58
|
* Creates codemirror text editor.
|
60
59
|
*/
|
@@ -0,0 +1,159 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2023 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import '@dxos-theme';
|
6
|
+
|
7
|
+
import { type EditorView } from '@codemirror/view';
|
8
|
+
import { type StoryObj } from '@storybook/react';
|
9
|
+
import React, { useCallback, useRef } from 'react';
|
10
|
+
|
11
|
+
import { Obj, Query } from '@dxos/echo';
|
12
|
+
import { faker } from '@dxos/random';
|
13
|
+
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
14
|
+
import { createObjectFactory, Testing, type ValueGenerator } from '@dxos/schema/testing';
|
15
|
+
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
16
|
+
|
17
|
+
import { EditorStory, names } from './components';
|
18
|
+
import {
|
19
|
+
CommandMenu,
|
20
|
+
type CommandMenuGroup,
|
21
|
+
type CommandMenuItem,
|
22
|
+
RefPopover,
|
23
|
+
coreSlashCommands,
|
24
|
+
filterItems,
|
25
|
+
insertAtCursor,
|
26
|
+
insertAtLineStart,
|
27
|
+
linkSlashCommands,
|
28
|
+
} from '../components';
|
29
|
+
import { useCommandMenu, type UseCommandMenuOptions } from '../extensions';
|
30
|
+
import { str } from '../testing';
|
31
|
+
import { createElement } from '../util';
|
32
|
+
|
33
|
+
const generator: ValueGenerator = faker as any;
|
34
|
+
|
35
|
+
type StoryProps = Omit<UseCommandMenuOptions, 'viewRef'> & { text: string };
|
36
|
+
|
37
|
+
const DefaultStory = ({ text, ...options }: StoryProps) => {
|
38
|
+
const viewRef = useRef<EditorView>();
|
39
|
+
const { commandMenu, groupsRef, currentItem, onSelect, ...props } = useCommandMenu({ viewRef, ...options });
|
40
|
+
|
41
|
+
return (
|
42
|
+
<RefPopover modal={false} {...props}>
|
43
|
+
<EditorStory ref={viewRef} text={text} placeholder={''} extensions={commandMenu} />
|
44
|
+
<CommandMenu groups={groupsRef.current} currentItem={currentItem} onSelect={onSelect} />
|
45
|
+
</RefPopover>
|
46
|
+
);
|
47
|
+
};
|
48
|
+
|
49
|
+
const groups: CommandMenuGroup[] = [
|
50
|
+
coreSlashCommands,
|
51
|
+
linkSlashCommands,
|
52
|
+
{
|
53
|
+
id: 'custom',
|
54
|
+
label: 'Custom',
|
55
|
+
items: [
|
56
|
+
{
|
57
|
+
id: 'custom-1',
|
58
|
+
label: 'Log',
|
59
|
+
icon: 'ph--log--regular',
|
60
|
+
onSelect: console.log,
|
61
|
+
},
|
62
|
+
],
|
63
|
+
},
|
64
|
+
];
|
65
|
+
|
66
|
+
const meta: Meta<StoryProps> = {
|
67
|
+
title: 'ui/react-ui-editor/CommandMenu',
|
68
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
69
|
+
render: (args) => <DefaultStory {...args} />,
|
70
|
+
parameters: {
|
71
|
+
layout: 'fullscreen',
|
72
|
+
},
|
73
|
+
};
|
74
|
+
|
75
|
+
export default meta;
|
76
|
+
|
77
|
+
type Story = StoryObj<StoryProps>;
|
78
|
+
|
79
|
+
// TODO(burdon): Not working.
|
80
|
+
export const Slash: Story = {
|
81
|
+
args: {
|
82
|
+
text: str('# Slash', '', names.join(' '), ''),
|
83
|
+
trigger: '/',
|
84
|
+
placeholder: {
|
85
|
+
content: () => {
|
86
|
+
return createElement('div', undefined, [
|
87
|
+
createElement('span', { text: 'Press' }),
|
88
|
+
createElement('span', { className: 'border border-separator rounded-sm mx-1 px-1', text: '/' }),
|
89
|
+
createElement('span', { text: 'for commands' }),
|
90
|
+
]);
|
91
|
+
},
|
92
|
+
},
|
93
|
+
getMenu: (text) => {
|
94
|
+
return filterItems(groups, (item) =>
|
95
|
+
text ? (item.label as string).toLowerCase().includes(text.toLowerCase()) : true,
|
96
|
+
);
|
97
|
+
},
|
98
|
+
},
|
99
|
+
};
|
100
|
+
|
101
|
+
export const Link: Story = {
|
102
|
+
render: (args) => {
|
103
|
+
const { space } = useClientProvider();
|
104
|
+
const getMenu = useCallback(
|
105
|
+
async (trigger: string, query?: string): Promise<CommandMenuGroup[]> => {
|
106
|
+
if (trigger === '/') {
|
107
|
+
return filterItems(groups, (item) =>
|
108
|
+
query ? (item.label as string).toLowerCase().includes(query.toLowerCase()) : true,
|
109
|
+
);
|
110
|
+
}
|
111
|
+
|
112
|
+
if (!space) {
|
113
|
+
return [];
|
114
|
+
}
|
115
|
+
|
116
|
+
const name = query?.startsWith('@') ? query.slice(1).toLowerCase() : query?.toLowerCase() ?? '';
|
117
|
+
const result = await space?.db.query(Query.type(Testing.Contact)).run();
|
118
|
+
const items = result.objects
|
119
|
+
.filter((object) => object.name.toLowerCase().includes(name))
|
120
|
+
.map(
|
121
|
+
(object): CommandMenuItem => ({
|
122
|
+
id: object.id,
|
123
|
+
label: object.name,
|
124
|
+
icon: 'ph--user--regular',
|
125
|
+
onSelect: (view, head) => {
|
126
|
+
const link = `[${object.name}][${Obj.getDXN(object)}]`;
|
127
|
+
if (query?.startsWith('@')) {
|
128
|
+
insertAtLineStart(view, head, `!${link}\n`);
|
129
|
+
} else {
|
130
|
+
insertAtCursor(view, head, `${link} `);
|
131
|
+
}
|
132
|
+
},
|
133
|
+
}),
|
134
|
+
);
|
135
|
+
return [{ id: 'echo', items }];
|
136
|
+
},
|
137
|
+
[space],
|
138
|
+
);
|
139
|
+
|
140
|
+
return <DefaultStory {...args} getMenu={getMenu} />;
|
141
|
+
},
|
142
|
+
args: {
|
143
|
+
text: str('# Link', '', names.join(' '), ''),
|
144
|
+
trigger: ['/', '@'],
|
145
|
+
},
|
146
|
+
decorators: [
|
147
|
+
withClientProvider({
|
148
|
+
createSpace: true,
|
149
|
+
onInitialized: async (client) => {
|
150
|
+
client.addTypes([Testing.Contact]);
|
151
|
+
},
|
152
|
+
onSpaceCreated: async ({ space }) => {
|
153
|
+
const createObjects = createObjectFactory(space.db, generator);
|
154
|
+
await createObjects([{ type: Testing.Contact, count: 10 }]);
|
155
|
+
await space.db.flush({ indexes: true });
|
156
|
+
},
|
157
|
+
}),
|
158
|
+
],
|
159
|
+
};
|
@@ -4,25 +4,22 @@
|
|
4
4
|
|
5
5
|
import '@dxos-theme';
|
6
6
|
|
7
|
-
import
|
7
|
+
import { syntaxTree } from '@codemirror/language';
|
8
|
+
import { type EditorView } from '@codemirror/view';
|
9
|
+
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
10
|
+
import { createPortal } from 'react-dom';
|
8
11
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
9
13
|
import { faker } from '@dxos/random';
|
10
|
-
import {
|
11
|
-
import {
|
14
|
+
import { Popover } from '@dxos/react-ui';
|
15
|
+
import { Card } from '@dxos/react-ui-stack';
|
16
|
+
import { hoverableControlItem, hoverableControlItemTransition, hoverableControls } from '@dxos/react-ui-theme';
|
12
17
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
13
18
|
|
14
19
|
import { EditorStory } from './components';
|
15
|
-
import {
|
16
|
-
import {
|
17
|
-
preview,
|
18
|
-
image,
|
19
|
-
type PreviewOptions,
|
20
|
-
type PreviewLinkRef,
|
21
|
-
type PreviewLinkTarget,
|
22
|
-
type PreviewRenderProps,
|
23
|
-
} from '../extensions';
|
20
|
+
import { PreviewProvider, useRefPopover } from '../components';
|
21
|
+
import { preview, image, type PreviewLinkRef, type PreviewLinkTarget, getLinkRef } from '../extensions';
|
24
22
|
import { str } from '../testing';
|
25
|
-
import { createRenderer } from '../util';
|
26
23
|
|
27
24
|
const handlePreviewLookup = async ({ label, ref }: PreviewLinkRef): Promise<PreviewLinkTarget> => {
|
28
25
|
// Random text.
|
@@ -36,11 +33,11 @@ const handlePreviewLookup = async ({ label, ref }: PreviewLinkRef): Promise<Prev
|
|
36
33
|
|
37
34
|
// Async lookup.
|
38
35
|
// TODO(burdon): Handle errors.
|
39
|
-
const useRefTarget = (link: PreviewLinkRef
|
36
|
+
const useRefTarget = (link: PreviewLinkRef): PreviewLinkTarget | undefined => {
|
40
37
|
const [target, setTarget] = useState<PreviewLinkTarget | undefined>();
|
41
38
|
useEffect(() => {
|
42
|
-
void
|
43
|
-
}, [link
|
39
|
+
void handlePreviewLookup(link).then((target) => setTarget(target ?? undefined));
|
40
|
+
}, [link]);
|
44
41
|
|
45
42
|
return target;
|
46
43
|
};
|
@@ -49,10 +46,12 @@ const PreviewCard = () => {
|
|
49
46
|
const { target } = useRefPopover('PreviewCard');
|
50
47
|
return (
|
51
48
|
<Popover.Portal>
|
52
|
-
<Popover.Content
|
49
|
+
<Popover.Content onOpenAutoFocus={(event) => event.preventDefault()}>
|
53
50
|
<Popover.Viewport>
|
54
|
-
<
|
55
|
-
|
51
|
+
<Card.Container role='popover'>
|
52
|
+
<Card.Heading>{target?.label}</Card.Heading>
|
53
|
+
{target && <Card.Text classNames='line-clamp-3'>{target.text}</Card.Text>}
|
54
|
+
</Card.Container>
|
56
55
|
</Popover.Viewport>
|
57
56
|
<Popover.Arrow />
|
58
57
|
</Popover.Content>
|
@@ -60,49 +59,108 @@ const PreviewCard = () => {
|
|
60
59
|
);
|
61
60
|
};
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
62
|
+
type PreviewAction =
|
63
|
+
| {
|
64
|
+
type: 'insert';
|
65
|
+
link: PreviewLinkRef;
|
66
|
+
target: PreviewLinkTarget;
|
67
|
+
}
|
68
|
+
| {
|
69
|
+
type: 'delete';
|
70
|
+
link: PreviewLinkRef;
|
71
|
+
};
|
72
|
+
|
73
|
+
const PreviewBlock = ({ link, el, view }: { link: PreviewLinkRef; el: HTMLElement; view?: EditorView }) => {
|
74
|
+
const target = useRefTarget(link);
|
75
|
+
|
76
|
+
const handleAction = useCallback(
|
77
|
+
(action: PreviewAction) => {
|
78
|
+
invariant(view, 'View not found');
|
79
|
+
const pos = view.posAtDOM(el);
|
80
|
+
const node = syntaxTree(view.state).resolve(pos + 1).node.parent;
|
81
|
+
if (!node) {
|
82
|
+
return;
|
83
|
+
}
|
84
|
+
|
85
|
+
const link = getLinkRef(view.state, node);
|
86
|
+
if (link?.ref !== action.link.ref) {
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
|
90
|
+
switch (action.type) {
|
91
|
+
// TODO(burdon): Should we dispatch to the view or mutate the document? (i.e., handle externally?)
|
92
|
+
// Insert ref text.
|
93
|
+
case 'insert': {
|
94
|
+
view.dispatch({
|
95
|
+
changes: {
|
96
|
+
from: node.from,
|
97
|
+
to: node.to,
|
98
|
+
insert: action.target.text,
|
99
|
+
},
|
100
|
+
});
|
101
|
+
break;
|
102
|
+
}
|
103
|
+
// Remove ref.
|
104
|
+
case 'delete': {
|
105
|
+
view.dispatch({
|
106
|
+
changes: {
|
107
|
+
from: node.from,
|
108
|
+
to: node.to,
|
109
|
+
},
|
110
|
+
});
|
111
|
+
break;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
},
|
115
|
+
[view, el],
|
116
|
+
);
|
117
|
+
|
118
|
+
const handleDelete = useCallback(() => {
|
119
|
+
handleAction({ type: 'delete', link });
|
120
|
+
}, [handleAction, link]);
|
121
|
+
|
122
|
+
const handleInsert = useCallback(() => {
|
123
|
+
if (target) {
|
124
|
+
handleAction({ type: 'insert', link, target });
|
125
|
+
}
|
126
|
+
}, [handleAction, link, target]);
|
127
|
+
|
128
|
+
return createPortal(
|
129
|
+
<Card.Content classNames={hoverableControls}>
|
130
|
+
<div className='flex items-start'>
|
131
|
+
{!view?.state.readOnly && (
|
132
|
+
<Card.Toolbar classNames='is-min p-[--dx-cardSpacingInline]'>
|
75
133
|
{(link.suggest && (
|
76
134
|
<>
|
135
|
+
<Card.ToolbarIconButton label='Discard' icon={'ph--x--regular'} onClick={handleDelete} />
|
77
136
|
{target && (
|
78
|
-
<
|
79
|
-
classNames='text-
|
137
|
+
<Card.ToolbarIconButton
|
138
|
+
classNames='bg-successSurface text-successSurfaceText'
|
80
139
|
label='Apply'
|
81
|
-
icon=
|
82
|
-
onClick={
|
140
|
+
icon='ph--check--regular'
|
141
|
+
onClick={handleInsert}
|
83
142
|
/>
|
84
143
|
)}
|
85
|
-
<IconButton
|
86
|
-
classNames='text-red-500'
|
87
|
-
label='Cancel'
|
88
|
-
icon={'ph--x--regular'}
|
89
|
-
onClick={() => onAction({ type: 'delete', link })}
|
90
|
-
/>
|
91
144
|
</>
|
92
145
|
)) || (
|
93
|
-
<
|
146
|
+
<Card.ToolbarIconButton
|
94
147
|
iconOnly
|
95
148
|
label='Delete'
|
96
|
-
icon=
|
97
|
-
classNames={
|
98
|
-
onClick={
|
149
|
+
icon='ph--x--regular'
|
150
|
+
classNames={[hoverableControlItem, hoverableControlItemTransition]}
|
151
|
+
onClick={handleDelete}
|
99
152
|
/>
|
100
153
|
)}
|
101
|
-
</
|
154
|
+
</Card.Toolbar>
|
102
155
|
)}
|
156
|
+
<Card.Heading classNames='grow order-first mie-0'>
|
157
|
+
{/* <span className='text-xs text-subdued mie-2'>Prompt</span> */}
|
158
|
+
{link.label}
|
159
|
+
</Card.Heading>
|
103
160
|
</div>
|
104
|
-
{target && <
|
105
|
-
</
|
161
|
+
{target && <Card.Text classNames='line-clamp-3 mbs-0'>{target.text}</Card.Text>}
|
162
|
+
</Card.Content>,
|
163
|
+
el,
|
106
164
|
);
|
107
165
|
};
|
108
166
|
|
@@ -116,34 +174,55 @@ const meta: Meta<typeof EditorStory> = {
|
|
116
174
|
export default meta;
|
117
175
|
|
118
176
|
export const Default = {
|
119
|
-
render: () =>
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
177
|
+
render: () => {
|
178
|
+
const [view, setView] = useState<EditorView>();
|
179
|
+
const [previewBlocks, setPreviewBlocks] = useState<{ link: PreviewLinkRef; el: HTMLElement }[]>([]);
|
180
|
+
|
181
|
+
const extensions = useMemo(() => {
|
182
|
+
return [
|
183
|
+
image(),
|
184
|
+
preview({
|
185
|
+
addBlockContainer: (link, el) => {
|
186
|
+
setPreviewBlocks((prev) => [...prev, { link, el }]);
|
187
|
+
},
|
188
|
+
removeBlockContainer: (link) => {
|
189
|
+
setPreviewBlocks((prev) => prev.filter(({ link: prevLink }) => prevLink.ref !== link.ref));
|
190
|
+
},
|
191
|
+
}),
|
192
|
+
];
|
193
|
+
}, []);
|
194
|
+
|
195
|
+
const handleViewRef = useCallback((instance?: EditorView | null) => {
|
196
|
+
setView(instance ?? undefined);
|
197
|
+
}, []);
|
198
|
+
|
199
|
+
return (
|
200
|
+
<PreviewProvider onLookup={handlePreviewLookup}>
|
201
|
+
<EditorStory
|
202
|
+
ref={handleViewRef}
|
203
|
+
text={str(
|
204
|
+
'# Preview',
|
205
|
+
'',
|
206
|
+
'This project is part of the [DXOS][dxn:queue:data:123] SDK.',
|
207
|
+
'',
|
208
|
+
'![DXOS][?dxn:queue:data:123]',
|
209
|
+
'',
|
210
|
+
'It consists of [ECHO][dxn:queue:data:echo], [HALO][dxn:queue:data:halo], and [MESH][dxn:queue:data:mesh].',
|
211
|
+
'',
|
212
|
+
'## Deep dive',
|
213
|
+
'',
|
214
|
+
'![ECHO][dxn:queue:data:echo]',
|
215
|
+
'',
|
216
|
+
'',
|
217
|
+
'',
|
218
|
+
)}
|
219
|
+
extensions={extensions}
|
220
|
+
/>
|
221
|
+
<PreviewCard />
|
222
|
+
{previewBlocks.map(({ link, el }) => (
|
223
|
+
<PreviewBlock key={link.ref} link={link} el={el} view={view} />
|
224
|
+
))}
|
225
|
+
</PreviewProvider>
|
226
|
+
);
|
227
|
+
},
|
149
228
|
};
|
@@ -196,7 +196,7 @@ const LinkButton: FC<{ url: string }> = ({ url }) => {
|
|
196
196
|
|
197
197
|
export const renderLinkButton = createRenderer(LinkButton);
|
198
198
|
|
199
|
-
// Shared extensions
|
199
|
+
// Shared extensions.
|
200
200
|
export const defaultExtensions: Extension[] = [
|
201
201
|
decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100 }),
|
202
202
|
formattingKeymap(),
|
@@ -212,7 +212,7 @@ export const allExtensions: Extension[] = [
|
|
212
212
|
folding(),
|
213
213
|
];
|
214
214
|
|
215
|
-
// Long text for scrolling stories
|
215
|
+
// Long text for scrolling stories.
|
216
216
|
export const longText = faker.helpers
|
217
217
|
.multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 })
|
218
218
|
.join('\n\n');
|