@dxos/react-ui-editor 0.8.2 → 0.8.3-main.7f5a14c
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 +936 -274
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +981 -314
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +936 -274
- 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/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/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.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +40 -0
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- 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/hashtag.d.ts +3 -0
- package/dist/types/src/extensions/hashtag.d.ts.map +1 -0
- 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.map +1 -1
- package/dist/types/src/extensions/markdown/debug.d.ts +2 -2
- package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/outliner.d.ts +1 -3
- package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
- package/dist/types/src/extensions/placeholder.d.ts +4 -0
- package/dist/types/src/extensions/placeholder.d.ts.map +1 -0
- 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/Command.stories.d.ts +1 -1
- package/dist/types/src/stories/Command.stories.d.ts.map +1 -1
- package/dist/types/src/stories/CommandMenu.stories.d.ts +12 -0
- package/dist/types/src/stories/CommandMenu.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Comments.stories.d.ts +1 -1
- package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Experimental.stories.d.ts +1 -1
- package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Markdown.stories.d.ts +1 -1
- package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Preview.stories.d.ts +1 -1
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
- package/dist/types/src/stories/TextEditor.stories.d.ts +1 -1
- package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/stories/components/EditorStory.d.ts +43 -0
- package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -0
- package/dist/types/src/stories/components/index.d.ts +3 -0
- package/dist/types/src/stories/components/index.d.ts.map +1 -0
- package/dist/types/src/stories/{util.d.ts → components/util.d.ts} +3 -18
- package/dist/types/src/stories/components/util.d.ts.map +1 -0
- package/package.json +31 -27
- package/src/components/Popover/CommandMenu.tsx +279 -0
- package/src/components/Popover/RefPopover.tsx +44 -22
- package/src/components/Popover/index.ts +1 -0
- package/src/defaults.ts +1 -0
- package/src/extensions/command/menu.ts +334 -23
- package/src/extensions/factories.ts +4 -1
- package/src/extensions/hashtag.tsx +68 -0
- package/src/extensions/index.ts +2 -0
- package/src/extensions/json.ts +2 -1
- package/src/extensions/markdown/debug.ts +2 -2
- package/src/extensions/outliner/outliner.ts +6 -8
- package/src/extensions/placeholder.ts +82 -0
- package/src/extensions/preview/preview.ts +3 -6
- package/src/hooks/useTextEditor.ts +11 -12
- package/src/stories/Command.stories.tsx +1 -1
- package/src/stories/CommandMenu.stories.tsx +143 -0
- package/src/stories/Comments.stories.tsx +2 -2
- package/src/stories/Experimental.stories.tsx +2 -2
- package/src/stories/Markdown.stories.tsx +2 -2
- package/src/stories/Outliner.stories.tsx +19 -7
- package/src/stories/Preview.stories.tsx +34 -32
- package/src/stories/TextEditor.stories.tsx +3 -3
- package/src/stories/components/EditorStory.tsx +135 -0
- package/src/stories/components/index.ts +6 -0
- package/src/stories/{util.tsx → components/util.tsx} +5 -100
- package/dist/types/src/stories/util.d.ts.map +0 -1
|
@@ -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
|
*/
|
|
@@ -10,7 +10,7 @@ import { Button, Icon, Input, DropdownMenu } from '@dxos/react-ui';
|
|
|
10
10
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
11
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
|
12
12
|
|
|
13
|
-
import { EditorStory } from './
|
|
13
|
+
import { EditorStory } from './components';
|
|
14
14
|
import { RefDropdownMenu } from '../components';
|
|
15
15
|
import { editorWidth } from '../defaults';
|
|
16
16
|
import { command, type Action, floatingMenu } from '../extensions';
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
coreSlashCommands,
|
|
20
|
+
filterItems,
|
|
21
|
+
RefPopover,
|
|
22
|
+
type CommandMenuGroup,
|
|
23
|
+
CommandMenu,
|
|
24
|
+
type CommandMenuItem,
|
|
25
|
+
insertAtCursor,
|
|
26
|
+
insertAtLineStart,
|
|
27
|
+
linkSlashCommands,
|
|
28
|
+
} from '../components';
|
|
29
|
+
import { useCommandMenu, type UseCommandMenuOptions } from '../extensions';
|
|
30
|
+
import { str } from '../testing';
|
|
31
|
+
|
|
32
|
+
const generator: ValueGenerator = faker as any;
|
|
33
|
+
|
|
34
|
+
type Args = Omit<UseCommandMenuOptions, 'viewRef'> & { text: string };
|
|
35
|
+
|
|
36
|
+
const Story = ({ text, ...options }: Args) => {
|
|
37
|
+
const viewRef = useRef<EditorView>();
|
|
38
|
+
const { commandMenu, groupsRef, currentItem, onSelect, ...props } = useCommandMenu({ viewRef, ...options });
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<RefPopover modal={false} {...props}>
|
|
42
|
+
<EditorStory ref={viewRef} text={text} placeholder={''} extensions={commandMenu} />
|
|
43
|
+
<CommandMenu groups={groupsRef.current} currentItem={currentItem} onSelect={onSelect} />
|
|
44
|
+
</RefPopover>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const groups: CommandMenuGroup[] = [
|
|
49
|
+
coreSlashCommands,
|
|
50
|
+
linkSlashCommands,
|
|
51
|
+
{
|
|
52
|
+
id: 'custom',
|
|
53
|
+
label: 'Custom',
|
|
54
|
+
items: [
|
|
55
|
+
{
|
|
56
|
+
id: 'custom-1',
|
|
57
|
+
label: 'Log',
|
|
58
|
+
icon: 'ph--log--regular',
|
|
59
|
+
onSelect: console.log,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const meta: Meta<Args> = {
|
|
66
|
+
title: 'ui/react-ui-editor/CommandMenu',
|
|
67
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
68
|
+
render: (args) => <Story {...args} />,
|
|
69
|
+
parameters: { layout: 'fullscreen' },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default meta;
|
|
73
|
+
|
|
74
|
+
export const Slash: StoryObj<Args> = {
|
|
75
|
+
args: {
|
|
76
|
+
trigger: '/',
|
|
77
|
+
getGroups: (query) =>
|
|
78
|
+
filterItems(groups, (item) =>
|
|
79
|
+
query ? (item.label as string).toLowerCase().includes(query.toLowerCase()) : true,
|
|
80
|
+
),
|
|
81
|
+
text: str('# Slash', '', names.join(' '), ''),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const Link: StoryObj<Args> = {
|
|
86
|
+
render: (args) => {
|
|
87
|
+
const { space } = useClientProvider();
|
|
88
|
+
const getGroups = useCallback(
|
|
89
|
+
async (trigger: string, query?: string): Promise<CommandMenuGroup[]> => {
|
|
90
|
+
if (trigger === '/') {
|
|
91
|
+
return filterItems(groups, (item) =>
|
|
92
|
+
query ? (item.label as string).toLowerCase().includes(query.toLowerCase()) : true,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!space) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const name = query?.startsWith('@') ? query.slice(1).toLowerCase() : query?.toLowerCase() ?? '';
|
|
101
|
+
const result = await space?.db.query(Query.type(Testing.Contact)).run();
|
|
102
|
+
const items = result.objects
|
|
103
|
+
.filter((object) => object.name.toLowerCase().includes(name))
|
|
104
|
+
.map(
|
|
105
|
+
(object): CommandMenuItem => ({
|
|
106
|
+
id: object.id,
|
|
107
|
+
label: object.name,
|
|
108
|
+
icon: 'ph--user--regular',
|
|
109
|
+
onSelect: (view, head) => {
|
|
110
|
+
const link = `[${object.name}][${Obj.getDXN(object)}]`;
|
|
111
|
+
if (query?.startsWith('@')) {
|
|
112
|
+
insertAtLineStart(view, head, `!${link}\n`);
|
|
113
|
+
} else {
|
|
114
|
+
insertAtCursor(view, head, `${link} `);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
return [{ id: 'echo', items }];
|
|
120
|
+
},
|
|
121
|
+
[space],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return <Story {...args} getGroups={getGroups} />;
|
|
125
|
+
},
|
|
126
|
+
args: {
|
|
127
|
+
trigger: ['/', '@'],
|
|
128
|
+
text: str('# Link', '', names.join(' '), ''),
|
|
129
|
+
},
|
|
130
|
+
decorators: [
|
|
131
|
+
withClientProvider({
|
|
132
|
+
createSpace: true,
|
|
133
|
+
onInitialized: async (client) => {
|
|
134
|
+
client.addTypes([Testing.Contact]);
|
|
135
|
+
},
|
|
136
|
+
onSpaceCreated: async ({ space }) => {
|
|
137
|
+
const createObjects = createObjectFactory(space.db, generator);
|
|
138
|
+
await createObjects([{ type: Testing.Contact, count: 10 }]);
|
|
139
|
+
await space.db.flush({ indexes: true });
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
],
|
|
143
|
+
};
|
|
@@ -12,7 +12,7 @@ import { PublicKey } from '@dxos/keys';
|
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
|
14
14
|
|
|
15
|
-
import { EditorStory, content, longText } from './
|
|
15
|
+
import { EditorStory, content, longText } from './components';
|
|
16
16
|
import { annotations, comments, createExternalCommentSync } from '../extensions';
|
|
17
17
|
import { str } from '../testing';
|
|
18
18
|
import { type Comment } from '../types';
|
|
@@ -20,8 +20,8 @@ import { createRenderer } from '../util';
|
|
|
20
20
|
|
|
21
21
|
const meta: Meta<typeof EditorStory> = {
|
|
22
22
|
title: 'ui/react-ui-editor/Comments',
|
|
23
|
+
component: EditorStory,
|
|
23
24
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
24
|
-
render: EditorStory,
|
|
25
25
|
parameters: { layout: 'fullscreen' },
|
|
26
26
|
};
|
|
27
27
|
|
|
@@ -11,14 +11,14 @@ import { log } from '@dxos/log';
|
|
|
11
11
|
import { faker } from '@dxos/random';
|
|
12
12
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
|
13
13
|
|
|
14
|
-
import { EditorStory, content } from './
|
|
14
|
+
import { EditorStory, content } from './components';
|
|
15
15
|
import { typewriter, blast, defaultOptions, dropFile } from '../extensions';
|
|
16
16
|
import { str } from '../testing';
|
|
17
17
|
|
|
18
18
|
const meta: Meta<typeof EditorStory> = {
|
|
19
19
|
title: 'ui/react-ui-editor/Experimental',
|
|
20
|
+
component: EditorStory,
|
|
20
21
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
21
|
-
render: EditorStory,
|
|
22
22
|
parameters: { layout: 'fullscreen' },
|
|
23
23
|
};
|
|
24
24
|
|
|
@@ -9,14 +9,14 @@ import React from 'react';
|
|
|
9
9
|
|
|
10
10
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
|
11
11
|
|
|
12
|
-
import { EditorStory, content, defaultExtensions, headings, renderLinkTooltip, text } from './
|
|
12
|
+
import { EditorStory, content, defaultExtensions, headings, renderLinkTooltip, text } from './components';
|
|
13
13
|
import { decorateMarkdown, image, linkTooltip, table } from '../extensions';
|
|
14
14
|
import { str } from '../testing';
|
|
15
15
|
|
|
16
16
|
const meta: Meta<typeof EditorStory> = {
|
|
17
17
|
title: 'ui/react-ui-editor/Markdown',
|
|
18
|
+
component: EditorStory,
|
|
18
19
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
19
|
-
render: EditorStory,
|
|
20
20
|
parameters: { layout: 'fullscreen' },
|
|
21
21
|
};
|
|
22
22
|
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { type EditorView } from '@codemirror/view';
|
|
8
|
+
import React, { useRef } from 'react';
|
|
8
9
|
|
|
9
10
|
import { DropdownMenu } from '@dxos/react-ui';
|
|
11
|
+
import { withAttention } from '@dxos/react-ui-attention/testing';
|
|
10
12
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
|
11
13
|
|
|
12
|
-
import { EditorStory } from './
|
|
14
|
+
import { EditorStory } from './components';
|
|
13
15
|
import { RefDropdownMenu } from '../components';
|
|
14
|
-
import { outliner, listItemToString, treeFacet } from '../extensions';
|
|
16
|
+
import { outliner, listItemToString, treeFacet, deleteItem, hashtag } from '../extensions';
|
|
15
17
|
import { str } from '../testing';
|
|
16
18
|
|
|
17
19
|
type StoryProps = {
|
|
@@ -19,11 +21,20 @@ type StoryProps = {
|
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
const DefaultStory = ({ text }: StoryProps) => {
|
|
24
|
+
const viewRef = useRef<EditorView>(null);
|
|
25
|
+
|
|
26
|
+
const handleDelete = () => {
|
|
27
|
+
if (viewRef.current) {
|
|
28
|
+
deleteItem(viewRef.current);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
22
32
|
return (
|
|
23
33
|
<RefDropdownMenu.Provider>
|
|
24
34
|
<EditorStory
|
|
35
|
+
ref={viewRef}
|
|
25
36
|
text={text}
|
|
26
|
-
extensions={[outliner()]}
|
|
37
|
+
extensions={[outliner(), hashtag()]}
|
|
27
38
|
placeholder=''
|
|
28
39
|
slots={{}}
|
|
29
40
|
debug='raw+tree'
|
|
@@ -31,13 +42,14 @@ const DefaultStory = ({ text }: StoryProps) => {
|
|
|
31
42
|
const tree = view.state.facet(treeFacet);
|
|
32
43
|
const lines: string[] = [];
|
|
33
44
|
tree.traverse((item) => lines.push(listItemToString(item)));
|
|
34
|
-
return <pre className='p-1 text-xs text-green-800 dark:text-green-200
|
|
45
|
+
return <pre className='p-1 overflow-auto text-xs text-green-800 dark:text-green-200'>{lines.join('\n')}</pre>;
|
|
35
46
|
}}
|
|
36
47
|
/>
|
|
48
|
+
|
|
37
49
|
<DropdownMenu.Portal>
|
|
38
50
|
<DropdownMenu.Content>
|
|
39
51
|
<DropdownMenu.Viewport>
|
|
40
|
-
<DropdownMenu.Item onClick={
|
|
52
|
+
<DropdownMenu.Item onClick={handleDelete}>Delete</DropdownMenu.Item>
|
|
41
53
|
</DropdownMenu.Viewport>
|
|
42
54
|
<DropdownMenu.Arrow />
|
|
43
55
|
</DropdownMenu.Content>
|
|
@@ -48,8 +60,8 @@ const DefaultStory = ({ text }: StoryProps) => {
|
|
|
48
60
|
|
|
49
61
|
const meta: Meta<StoryProps> = {
|
|
50
62
|
title: 'ui/react-ui-editor/Outliner',
|
|
51
|
-
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
52
63
|
render: DefaultStory,
|
|
64
|
+
decorators: [withAttention, withTheme, withLayout({ fullscreen: true })],
|
|
53
65
|
parameters: { layout: 'fullscreen' },
|
|
54
66
|
};
|
|
55
67
|
|
|
@@ -7,12 +7,13 @@ import '@dxos-theme';
|
|
|
7
7
|
import React, { useState, useEffect, type FC } from 'react';
|
|
8
8
|
|
|
9
9
|
import { faker } from '@dxos/random';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { Popover } from '@dxos/react-ui';
|
|
11
|
+
import { Card } from '@dxos/react-ui-stack';
|
|
12
|
+
import { hoverableControlItem, hoverableControlItemTransition, hoverableControls } from '@dxos/react-ui-theme';
|
|
12
13
|
import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
|
13
14
|
|
|
14
|
-
import { EditorStory } from './
|
|
15
|
-
import {
|
|
15
|
+
import { EditorStory } from './components';
|
|
16
|
+
import { PreviewProvider, useRefPopover } from '../components';
|
|
16
17
|
import {
|
|
17
18
|
preview,
|
|
18
19
|
image,
|
|
@@ -49,10 +50,12 @@ const PreviewCard = () => {
|
|
|
49
50
|
const { target } = useRefPopover('PreviewCard');
|
|
50
51
|
return (
|
|
51
52
|
<Popover.Portal>
|
|
52
|
-
<Popover.Content
|
|
53
|
+
<Popover.Content onOpenAutoFocus={(event) => event.preventDefault()}>
|
|
53
54
|
<Popover.Viewport>
|
|
54
|
-
<
|
|
55
|
-
|
|
55
|
+
<Card.Container role='popover'>
|
|
56
|
+
<Card.Heading>{target?.label}</Card.Heading>
|
|
57
|
+
{target && <Card.Text classNames='line-clamp-3'>{target.text}</Card.Text>}
|
|
58
|
+
</Card.Container>
|
|
56
59
|
</Popover.Viewport>
|
|
57
60
|
<Popover.Arrow />
|
|
58
61
|
</Popover.Content>
|
|
@@ -64,52 +67,51 @@ const PreviewCard = () => {
|
|
|
64
67
|
const PreviewBlock: FC<PreviewRenderProps> = ({ readonly, link, onAction, onLookup }) => {
|
|
65
68
|
const target = useRefTarget(link, onLookup);
|
|
66
69
|
return (
|
|
67
|
-
<
|
|
68
|
-
<div className='flex items-
|
|
69
|
-
<div className='grow truncate'>
|
|
70
|
-
{/* <span className='text-xs text-subdued mie-2'>Prompt</span> */}
|
|
71
|
-
{link.label}
|
|
72
|
-
</div>
|
|
70
|
+
<Card.Content classNames={hoverableControls}>
|
|
71
|
+
<div className='flex items-start'>
|
|
73
72
|
{!readonly && (
|
|
74
|
-
<
|
|
73
|
+
<Card.Toolbar classNames='is-min p-[--dx-card-spacing-inline]'>
|
|
75
74
|
{(link.suggest && (
|
|
76
75
|
<>
|
|
76
|
+
<Card.ToolbarIconButton
|
|
77
|
+
label='Discard'
|
|
78
|
+
icon={'ph--x--regular'}
|
|
79
|
+
onClick={() => onAction({ type: 'delete', link })}
|
|
80
|
+
/>
|
|
77
81
|
{target && (
|
|
78
|
-
<
|
|
79
|
-
classNames='text-
|
|
82
|
+
<Card.ToolbarIconButton
|
|
83
|
+
classNames='bg-successSurface text-successSurfaceText'
|
|
80
84
|
label='Apply'
|
|
81
|
-
icon=
|
|
85
|
+
icon='ph--check--regular'
|
|
82
86
|
onClick={() => onAction({ type: 'insert', link, target })}
|
|
83
87
|
/>
|
|
84
88
|
)}
|
|
85
|
-
<IconButton
|
|
86
|
-
classNames='text-red-500'
|
|
87
|
-
label='Cancel'
|
|
88
|
-
icon={'ph--x--regular'}
|
|
89
|
-
onClick={() => onAction({ type: 'delete', link })}
|
|
90
|
-
/>
|
|
91
89
|
</>
|
|
92
90
|
)) || (
|
|
93
|
-
<
|
|
91
|
+
<Card.ToolbarIconButton
|
|
94
92
|
iconOnly
|
|
95
93
|
label='Delete'
|
|
96
|
-
icon=
|
|
97
|
-
classNames={
|
|
94
|
+
icon='ph--x--regular'
|
|
95
|
+
classNames={[hoverableControlItem, hoverableControlItemTransition]}
|
|
98
96
|
onClick={() => onAction({ type: 'delete', link })}
|
|
99
97
|
/>
|
|
100
98
|
)}
|
|
101
|
-
</
|
|
99
|
+
</Card.Toolbar>
|
|
102
100
|
)}
|
|
101
|
+
<Card.Heading classNames='grow order-first mie-0'>
|
|
102
|
+
{/* <span className='text-xs text-subdued mie-2'>Prompt</span> */}
|
|
103
|
+
{link.label}
|
|
104
|
+
</Card.Heading>
|
|
103
105
|
</div>
|
|
104
|
-
{target && <
|
|
105
|
-
</
|
|
106
|
+
{target && <Card.Text classNames='line-clamp-3 mbs-0'>{target.text}</Card.Text>}
|
|
107
|
+
</Card.Content>
|
|
106
108
|
);
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
const meta: Meta<typeof EditorStory> = {
|
|
110
112
|
title: 'ui/react-ui-editor/Preview',
|
|
113
|
+
component: EditorStory,
|
|
111
114
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
112
|
-
render: EditorStory,
|
|
113
115
|
parameters: { layout: 'fullscreen' },
|
|
114
116
|
};
|
|
115
117
|
|
|
@@ -117,7 +119,7 @@ export default meta;
|
|
|
117
119
|
|
|
118
120
|
export const Default = {
|
|
119
121
|
render: () => (
|
|
120
|
-
<
|
|
122
|
+
<PreviewProvider onLookup={handlePreviewLookup}>
|
|
121
123
|
<EditorStory
|
|
122
124
|
text={str(
|
|
123
125
|
'# Preview',
|
|
@@ -144,6 +146,6 @@ export const Default = {
|
|
|
144
146
|
]}
|
|
145
147
|
/>
|
|
146
148
|
<PreviewCard />
|
|
147
|
-
</
|
|
149
|
+
</PreviewProvider>
|
|
148
150
|
),
|
|
149
151
|
};
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
names,
|
|
24
24
|
renderLinkButton,
|
|
25
25
|
text,
|
|
26
|
-
} from './
|
|
26
|
+
} from './components';
|
|
27
27
|
import { editorMonospace } from '../defaults';
|
|
28
28
|
import {
|
|
29
29
|
InputModeExtensions,
|
|
@@ -41,9 +41,9 @@ import { str } from '../testing';
|
|
|
41
41
|
|
|
42
42
|
const meta: Meta<typeof EditorStory> = {
|
|
43
43
|
title: 'ui/react-ui-editor/TextEditor',
|
|
44
|
-
|
|
44
|
+
component: EditorStory,
|
|
45
45
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
46
|
-
parameters: { layout: 'fullscreen' },
|
|
46
|
+
parameters: { layout: 'fullscreen', controls: { disable: true } },
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
export default meta;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type EditorView } from '@codemirror/view';
|
|
6
|
+
import React, { type ReactNode, forwardRef, useEffect, useState, useImperativeHandle, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { Expando } from '@dxos/echo-schema';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
|
+
import { PublicKey } from '@dxos/keys';
|
|
11
|
+
import { live } from '@dxos/live-object';
|
|
12
|
+
import { createDocAccessor, createObject } from '@dxos/react-client/echo';
|
|
13
|
+
import { useForwardedRef, useThemeContext } from '@dxos/react-ui';
|
|
14
|
+
import { useAttentionAttributes } from '@dxos/react-ui-attention';
|
|
15
|
+
import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
|
|
16
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
17
|
+
import { isNonNullable } from '@dxos/util';
|
|
18
|
+
|
|
19
|
+
import { editorSlots, editorGutter } from '../../defaults';
|
|
20
|
+
import {
|
|
21
|
+
type DebugNode,
|
|
22
|
+
type ThemeExtensionsOptions,
|
|
23
|
+
createDataExtensions,
|
|
24
|
+
createBasicExtensions,
|
|
25
|
+
createMarkdownExtensions,
|
|
26
|
+
createThemeExtensions,
|
|
27
|
+
debugTree,
|
|
28
|
+
} from '../../extensions';
|
|
29
|
+
import { useTextEditor, type UseTextEditorProps } from '../../hooks';
|
|
30
|
+
|
|
31
|
+
// Type definitions.
|
|
32
|
+
export type DebugMode = 'raw' | 'tree' | 'raw+tree';
|
|
33
|
+
|
|
34
|
+
const defaultId = 'editor-' + PublicKey.random().toHex().slice(0, 8);
|
|
35
|
+
|
|
36
|
+
export type StoryProps = Pick<UseTextEditorProps, 'scrollTo' | 'selection' | 'extensions'> &
|
|
37
|
+
Pick<ThemeExtensionsOptions, 'slots'> & {
|
|
38
|
+
id?: string;
|
|
39
|
+
debug?: DebugMode;
|
|
40
|
+
debugCustom?: (view: EditorView) => ReactNode;
|
|
41
|
+
text?: string;
|
|
42
|
+
object?: Expando;
|
|
43
|
+
readOnly?: boolean;
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
lineNumbers?: boolean;
|
|
46
|
+
onReady?: (view: EditorView) => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const EditorStory = forwardRef<EditorView | undefined, StoryProps>(
|
|
50
|
+
({ debug, debugCustom, text, extensions: _extensions, ...props }, forwardedRef) => {
|
|
51
|
+
const attentionAttrs = useAttentionAttributes('testing');
|
|
52
|
+
const [tree, setTree] = useState<DebugNode>();
|
|
53
|
+
const [object] = useState(createObject(live(Expando, { content: text ?? '' })));
|
|
54
|
+
const viewRef = useForwardedRef(forwardedRef);
|
|
55
|
+
const view = viewRef.current;
|
|
56
|
+
const extensions = useMemo(
|
|
57
|
+
() => (debug ? [_extensions, debugTree(setTree)].filter(isNonNullable) : _extensions),
|
|
58
|
+
[debug, _extensions],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className={mx('w-full h-full grid overflow-hidden', debug && 'grid-cols-2 lg:grid-cols-[1fr_600px]')}>
|
|
63
|
+
<EditorComponent ref={viewRef} object={object} text={text} extensions={extensions} {...props} />
|
|
64
|
+
|
|
65
|
+
{debug && (
|
|
66
|
+
<div
|
|
67
|
+
className='grid h-full auto-rows-fr border-l border-separator divide-y divide-separator overflow-hidden'
|
|
68
|
+
{...attentionAttrs}
|
|
69
|
+
>
|
|
70
|
+
{view && debugCustom?.(view)}
|
|
71
|
+
{(debug === 'raw' || debug === 'raw+tree') && (
|
|
72
|
+
<pre className='p-1 text-xs text-green-800 dark:text-green-200 overflow-auto'>
|
|
73
|
+
{view?.state.doc.toString()}
|
|
74
|
+
</pre>
|
|
75
|
+
)}
|
|
76
|
+
{(debug === 'tree' || debug === 'raw+tree') && <JsonFilter data={tree} classNames='p-1 text-xs' />}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Default story component.
|
|
86
|
+
*/
|
|
87
|
+
export const EditorComponent = forwardRef<EditorView | undefined, StoryProps>(
|
|
88
|
+
(
|
|
89
|
+
{
|
|
90
|
+
id = defaultId,
|
|
91
|
+
text,
|
|
92
|
+
object,
|
|
93
|
+
readOnly,
|
|
94
|
+
placeholder = 'New document.',
|
|
95
|
+
lineNumbers,
|
|
96
|
+
scrollTo,
|
|
97
|
+
selection,
|
|
98
|
+
extensions,
|
|
99
|
+
slots = editorSlots,
|
|
100
|
+
onReady,
|
|
101
|
+
},
|
|
102
|
+
forwardedRef,
|
|
103
|
+
) => {
|
|
104
|
+
invariant(object);
|
|
105
|
+
const { themeMode } = useThemeContext();
|
|
106
|
+
const attentionAttrs = useAttentionAttributes(id);
|
|
107
|
+
const { parentRef, focusAttributes, view } = useTextEditor(
|
|
108
|
+
() => ({
|
|
109
|
+
id,
|
|
110
|
+
scrollTo,
|
|
111
|
+
selection,
|
|
112
|
+
initialValue: text,
|
|
113
|
+
extensions: [
|
|
114
|
+
createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
|
|
115
|
+
createBasicExtensions({ readOnly, placeholder, lineNumbers, scrollPastEnd: true }),
|
|
116
|
+
createMarkdownExtensions({ themeMode }),
|
|
117
|
+
createThemeExtensions({ themeMode, syntaxHighlighting: true, slots }),
|
|
118
|
+
editorGutter,
|
|
119
|
+
extensions || [],
|
|
120
|
+
],
|
|
121
|
+
}),
|
|
122
|
+
[id, object, extensions, themeMode],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
useImperativeHandle(forwardedRef, () => view, [view]);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (view) {
|
|
129
|
+
onReady?.(view);
|
|
130
|
+
}
|
|
131
|
+
}, [view]);
|
|
132
|
+
|
|
133
|
+
return <div ref={parentRef} className='flex overflow-hidden' {...attentionAttrs} {...focusAttributes} />;
|
|
134
|
+
},
|
|
135
|
+
);
|