@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
|
+
);
|