@lobehub/lobehub 2.0.0-next.254 → 2.0.0-next.256
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/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/zh-CN/plugin.json +1 -0
- package/package.json +1 -1
- package/packages/builtin-tool-notebook/src/client/Placeholder/CreateDocument.tsx +6 -6
- package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +23 -10
- package/packages/builtin-tool-notebook/src/client/Streaming/CreateDocument/index.tsx +1 -1
- package/packages/database/src/models/__tests__/agent.test.ts +91 -4
- package/packages/database/src/models/agent.ts +15 -7
- package/packages/editor-runtime/src/EditorRuntime.ts +1 -1
- package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +65 -4
- package/packages/editor-runtime/src/__tests__/__snapshots__/EditorRuntime.real.test.ts.snap +108 -17
- package/packages/editor-runtime/src/__tests__/fixtures/remove-then-add.json +636 -0
- package/packages/editor-runtime/src/__tests__/fixtures/remove.json +1 -0
- package/packages/types/src/agent/agentConfig.ts +8 -8
- package/src/app/[variants]/(main)/chat/features/Portal/_layout/Mobile.tsx +2 -1
- package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +2 -1
- package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +2 -2
- package/src/app/[variants]/(main)/page/{features/PageTitle → PageTitle}/index.tsx +0 -1
- package/src/app/[variants]/(main)/page/[id]/index.tsx +43 -1
- package/src/app/[variants]/(main)/page/_layout/Body/AllPagesDrawer/Content.tsx +15 -15
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Editing.tsx +3 -3
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +7 -12
- package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +5 -5
- package/src/app/[variants]/(main)/page/_layout/Body/List/index.tsx +7 -7
- package/src/app/[variants]/(main)/page/_layout/Body/index.tsx +15 -9
- package/src/app/[variants]/(main)/page/_layout/Body/useDropdownMenu.tsx +3 -3
- package/src/app/[variants]/(main)/page/_layout/DataSync.tsx +15 -0
- package/src/app/[variants]/(main)/page/_layout/Header/AddButton.tsx +2 -2
- package/src/app/[variants]/(main)/page/_layout/index.tsx +2 -0
- package/src/app/[variants]/(main)/page/index.tsx +3 -7
- package/src/components/Editor/AutoSaveHint.tsx +1 -1
- package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/Intervention/ApprovalActions.tsx +2 -2
- package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +18 -15
- package/src/features/Conversation/Messages/AssistantGroup/components/Group.tsx +3 -3
- package/src/features/Conversation/Messages/Contexts/MessageAggregationContext.ts +15 -0
- package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +3 -3
- package/src/features/Conversation/Messages/User/Actions/index.tsx +5 -1
- package/src/features/EditorCanvas/AutoSaveHint.tsx +37 -0
- package/src/features/{PageEditor → EditorCanvas}/DiffAllToolbar.tsx +57 -16
- package/src/features/EditorCanvas/DocumentIdMode.tsx +111 -0
- package/src/features/EditorCanvas/EditorCanvas.tsx +148 -0
- package/src/features/EditorCanvas/EditorDataMode.tsx +64 -0
- package/src/features/EditorCanvas/ErrorBoundary.tsx +66 -0
- package/src/features/EditorCanvas/InlineToolbar.tsx +245 -0
- package/src/features/EditorCanvas/InternalEditor.tsx +134 -0
- package/src/features/{PageEditor/EditorCanvas → EditorCanvas}/actions.ts +10 -8
- package/src/features/EditorCanvas/index.ts +9 -0
- package/src/features/PageEditor/EditorCanvas/index.tsx +14 -111
- package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +95 -0
- package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +1 -1
- package/src/features/PageEditor/Header/Breadcrumb.tsx +2 -2
- package/src/features/PageEditor/Header/index.tsx +15 -18
- package/src/features/PageEditor/Header/useMenu.tsx +12 -9
- package/src/features/PageEditor/PageEditor.tsx +45 -21
- package/src/features/PageEditor/PageEditorProvider.tsx +13 -1
- package/src/features/PageEditor/PageTitle/index.tsx +2 -2
- package/src/features/PageEditor/StoreUpdater.tsx +35 -308
- package/src/features/PageEditor/{Body/Title.tsx → TitleSection.tsx} +16 -16
- package/src/features/PageEditor/store/action.ts +96 -188
- package/src/features/PageEditor/store/initialState.ts +16 -21
- package/src/features/PageEditor/store/selectors.ts +3 -4
- package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +22 -14
- package/src/features/PageExplorer/index.tsx +34 -67
- package/src/features/Portal/Artifacts/index.ts +0 -2
- package/src/features/Portal/Document/AutoSaveHint.tsx +7 -6
- package/src/features/Portal/Document/Body.tsx +1 -3
- package/src/features/Portal/Document/EditorCanvas.tsx +7 -50
- package/src/features/Portal/Document/Header.tsx +13 -10
- package/src/features/Portal/Document/TodoList.tsx +6 -4
- package/src/features/Portal/Document/Wrapper.tsx +3 -11
- package/src/features/Portal/Document/index.ts +0 -2
- package/src/features/Portal/FilePreview/index.ts +0 -2
- package/src/features/Portal/GroupThread/index.ts +0 -3
- package/src/features/Portal/MessageDetail/index.ts +0 -2
- package/src/features/Portal/Notebook/index.ts +0 -2
- package/src/features/Portal/Plugins/index.ts +0 -2
- package/src/features/Portal/Thread/index.ts +0 -3
- package/src/features/Portal/components/Header.tsx +18 -6
- package/src/features/Portal/router.tsx +34 -97
- package/src/features/Portal/type.ts +0 -2
- package/src/libs/next/config/define-config.ts +4 -1
- package/src/locales/default/plugin.ts +1 -0
- package/src/store/chat/slices/portal/action.test.ts +218 -15
- package/src/store/chat/slices/portal/action.ts +194 -41
- package/src/store/chat/slices/portal/initialState.ts +40 -1
- package/src/store/chat/slices/portal/selectors/thread.ts +44 -3
- package/src/store/chat/slices/portal/selectors.test.ts +119 -17
- package/src/store/chat/slices/portal/selectors.ts +117 -36
- package/src/store/document/index.ts +17 -5
- package/src/store/document/slices/document/action.ts +209 -0
- package/src/store/document/slices/document/index.ts +6 -0
- package/src/store/document/slices/editor/action.test.ts +340 -0
- package/src/store/document/slices/editor/action.ts +133 -149
- package/src/store/document/slices/editor/index.ts +9 -2
- package/src/store/document/slices/editor/initialState.ts +66 -29
- package/src/store/document/slices/editor/reducer.test.ts +217 -0
- package/src/store/document/slices/editor/reducer.ts +67 -0
- package/src/store/document/slices/editor/selectors.test.ts +395 -0
- package/src/store/document/slices/editor/selectors.ts +107 -5
- package/src/store/document/store.ts +12 -13
- package/src/store/file/slices/document/action.ts +19 -188
- package/src/store/file/slices/document/initialState.ts +0 -30
- package/src/store/file/slices/document/selectors.ts +25 -59
- package/src/store/notebook/index.ts +5 -4
- package/src/store/page/index.ts +2 -0
- package/src/store/page/initialState.ts +92 -0
- package/src/store/page/selectors.ts +5 -0
- package/src/store/page/slices/crud/action.ts +477 -0
- package/src/store/page/slices/crud/index.ts +2 -0
- package/src/store/page/slices/crud/initialState.ts +7 -0
- package/src/store/page/slices/internal/action.ts +32 -0
- package/src/store/page/slices/internal/index.ts +2 -0
- package/src/store/page/slices/internal/reducer.ts +105 -0
- package/src/store/page/slices/list/action.ts +206 -0
- package/src/store/page/slices/list/index.ts +3 -0
- package/src/store/page/slices/list/initialState.ts +29 -0
- package/src/store/page/slices/list/selectors.ts +90 -0
- package/src/store/page/slices/selection/action.ts +67 -0
- package/src/store/page/slices/selection/index.ts +2 -0
- package/src/store/page/slices/selection/initialState.ts +11 -0
- package/src/store/page/store.ts +29 -0
- package/src/store/tool/slices/lobehubSkillStore/selectors.ts +10 -20
- package/src/utils/identifier.ts +8 -2
- package/src/features/Conversation/Messages/AssistantGroup/components/GroupContext.ts +0 -15
- package/src/features/Conversation/Messages/Supervisor/components/GroupContext.ts +0 -15
- package/src/features/PageEditor/Body/index.tsx +0 -68
- package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +0 -316
- package/src/features/PageEditor/Header/AutoSaveHint.tsx +0 -27
- package/src/features/Portal/Artifacts/useEnable.ts +0 -4
- package/src/features/Portal/Document/DocumentEditorProvider.tsx +0 -34
- package/src/features/Portal/Document/StoreUpdater.tsx +0 -80
- package/src/features/Portal/Document/Title.tsx +0 -54
- package/src/features/Portal/Document/store/action.ts +0 -114
- package/src/features/Portal/Document/store/index.ts +0 -21
- package/src/features/Portal/Document/store/initialState.ts +0 -24
- package/src/features/Portal/Document/useEnable.ts +0 -8
- package/src/features/Portal/FilePreview/useEnable.ts +0 -6
- package/src/features/Portal/GroupThread/hook.ts +0 -9
- package/src/features/Portal/MessageDetail/useEnable.ts +0 -4
- package/src/features/Portal/Notebook/useEnable.ts +0 -6
- package/src/features/Portal/Plugins/useEnable.ts +0 -6
- package/src/features/Portal/Thread/hook.ts +0 -8
- package/src/store/document/slices/notebook/action.ts +0 -119
- package/src/store/document/slices/notebook/index.ts +0 -3
- package/src/store/document/slices/notebook/initialState.ts +0 -12
- package/src/store/document/slices/notebook/selectors.ts +0 -26
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
HotkeyEnum,
|
|
5
|
+
INSERT_HEADING_COMMAND,
|
|
6
|
+
getHotkeyById,
|
|
7
|
+
type IEditor,
|
|
8
|
+
} from '@lobehub/editor';
|
|
9
|
+
import {
|
|
10
|
+
ChatInputActions,
|
|
11
|
+
type ChatInputActionsProps,
|
|
12
|
+
type EditorState,
|
|
13
|
+
FloatActions,
|
|
14
|
+
} from '@lobehub/editor/react';
|
|
15
|
+
import { Block } from '@lobehub/ui';
|
|
16
|
+
import { cssVar } from 'antd-style';
|
|
17
|
+
import {
|
|
18
|
+
BoldIcon,
|
|
19
|
+
CodeXmlIcon,
|
|
20
|
+
Heading1Icon,
|
|
21
|
+
Heading2Icon,
|
|
22
|
+
Heading3Icon,
|
|
23
|
+
ItalicIcon,
|
|
24
|
+
LinkIcon,
|
|
25
|
+
ListIcon,
|
|
26
|
+
ListOrderedIcon,
|
|
27
|
+
ListTodoIcon,
|
|
28
|
+
MessageSquareQuote,
|
|
29
|
+
Redo2Icon,
|
|
30
|
+
SigmaIcon,
|
|
31
|
+
SquareDashedBottomCodeIcon,
|
|
32
|
+
StrikethroughIcon,
|
|
33
|
+
UnderlineIcon,
|
|
34
|
+
Undo2Icon,
|
|
35
|
+
} from 'lucide-react';
|
|
36
|
+
import { type CSSProperties, memo, useMemo } from 'react';
|
|
37
|
+
import { useTranslation } from 'react-i18next';
|
|
38
|
+
|
|
39
|
+
export interface InlineToolbarProps {
|
|
40
|
+
className?: string;
|
|
41
|
+
editor?: IEditor;
|
|
42
|
+
editorState?: EditorState;
|
|
43
|
+
/**
|
|
44
|
+
* Extra items to prepend to the toolbar (e.g., "Ask Copilot" button)
|
|
45
|
+
*/
|
|
46
|
+
extraItems?: ChatInputActionsProps['items'];
|
|
47
|
+
floating?: boolean;
|
|
48
|
+
style?: CSSProperties;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const InlineToolbar = memo<InlineToolbarProps>(
|
|
52
|
+
({ floating, style, className, editor, editorState, extraItems }) => {
|
|
53
|
+
const { t } = useTranslation('editor');
|
|
54
|
+
|
|
55
|
+
const items: ChatInputActionsProps['items'] = useMemo(() => {
|
|
56
|
+
if (!editorState) return [];
|
|
57
|
+
|
|
58
|
+
const baseItems = [
|
|
59
|
+
// Extra items (like "Ask Copilot") come first
|
|
60
|
+
...(extraItems || []),
|
|
61
|
+
extraItems?.length ? { type: 'divider' as const } : null,
|
|
62
|
+
!floating && {
|
|
63
|
+
disabled: !editorState.canUndo,
|
|
64
|
+
icon: Undo2Icon,
|
|
65
|
+
key: 'undo',
|
|
66
|
+
label: t('typobar.undo', 'Undo'),
|
|
67
|
+
onClick: editorState.undo,
|
|
68
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Undo).keys },
|
|
69
|
+
},
|
|
70
|
+
!floating && {
|
|
71
|
+
disabled: !editorState.canRedo,
|
|
72
|
+
icon: Redo2Icon,
|
|
73
|
+
key: 'redo',
|
|
74
|
+
label: t('typobar.redo', 'Redo'),
|
|
75
|
+
onClick: editorState.redo,
|
|
76
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Redo).keys },
|
|
77
|
+
},
|
|
78
|
+
!floating && {
|
|
79
|
+
type: 'divider',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
active: editorState.isBold,
|
|
83
|
+
icon: BoldIcon,
|
|
84
|
+
key: 'bold',
|
|
85
|
+
label: t('typobar.bold'),
|
|
86
|
+
onClick: editorState.bold,
|
|
87
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Bold).keys },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
active: editorState.isItalic,
|
|
91
|
+
icon: ItalicIcon,
|
|
92
|
+
key: 'italic',
|
|
93
|
+
label: t('typobar.italic'),
|
|
94
|
+
onClick: editorState.italic,
|
|
95
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Italic).keys },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
active: editorState.isUnderline,
|
|
99
|
+
icon: UnderlineIcon,
|
|
100
|
+
key: 'underline',
|
|
101
|
+
label: t('typobar.underline'),
|
|
102
|
+
onClick: editorState.underline,
|
|
103
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Underline).keys },
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
active: editorState.isStrikethrough,
|
|
107
|
+
icon: StrikethroughIcon,
|
|
108
|
+
key: 'strikethrough',
|
|
109
|
+
label: t('typobar.strikethrough'),
|
|
110
|
+
onClick: editorState.strikethrough,
|
|
111
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Strikethrough).keys },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'divider',
|
|
115
|
+
},
|
|
116
|
+
!floating && {
|
|
117
|
+
icon: Heading1Icon,
|
|
118
|
+
key: 'h1',
|
|
119
|
+
label: t('slash.h1'),
|
|
120
|
+
onClick: () => {
|
|
121
|
+
if (editor) {
|
|
122
|
+
editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h1' });
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
!floating && {
|
|
127
|
+
icon: Heading2Icon,
|
|
128
|
+
key: 'h2',
|
|
129
|
+
label: t('slash.h2'),
|
|
130
|
+
onClick: () => {
|
|
131
|
+
if (editor) {
|
|
132
|
+
editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h2' });
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
!floating && {
|
|
137
|
+
icon: Heading3Icon,
|
|
138
|
+
key: 'h3',
|
|
139
|
+
label: t('slash.h3'),
|
|
140
|
+
onClick: () => {
|
|
141
|
+
if (editor) {
|
|
142
|
+
editor.dispatchCommand(INSERT_HEADING_COMMAND, { tag: 'h3' });
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
!floating && {
|
|
147
|
+
type: 'divider',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
icon: ListIcon,
|
|
151
|
+
key: 'bulletList',
|
|
152
|
+
label: t('typobar.bulletList'),
|
|
153
|
+
onClick: editorState.bulletList,
|
|
154
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.BulletList).keys },
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
icon: ListOrderedIcon,
|
|
158
|
+
key: 'numberlist',
|
|
159
|
+
label: t('typobar.numberList'),
|
|
160
|
+
onClick: editorState.numberList,
|
|
161
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.NumberList).keys },
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
icon: ListTodoIcon,
|
|
165
|
+
key: 'tasklist',
|
|
166
|
+
label: t('typobar.taskList'),
|
|
167
|
+
onClick: editorState.checkList,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
type: 'divider',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
active: editorState.isBlockquote,
|
|
174
|
+
icon: MessageSquareQuote,
|
|
175
|
+
key: 'blockquote',
|
|
176
|
+
label: t('typobar.blockquote'),
|
|
177
|
+
onClick: editorState.blockquote,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
icon: LinkIcon,
|
|
181
|
+
key: 'link',
|
|
182
|
+
label: t('typobar.link'),
|
|
183
|
+
onClick: editorState.insertLink,
|
|
184
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.Link).keys },
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
icon: SigmaIcon,
|
|
188
|
+
key: 'math',
|
|
189
|
+
label: t('typobar.tex'),
|
|
190
|
+
onClick: editorState.insertMath,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
type: 'divider',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
active: editorState.isCode,
|
|
197
|
+
icon: CodeXmlIcon,
|
|
198
|
+
key: 'code',
|
|
199
|
+
label: t('typobar.code'),
|
|
200
|
+
onClick: editorState.code,
|
|
201
|
+
tooltipProps: { hotkey: getHotkeyById(HotkeyEnum.CodeInline).keys },
|
|
202
|
+
},
|
|
203
|
+
!floating && {
|
|
204
|
+
icon: SquareDashedBottomCodeIcon,
|
|
205
|
+
key: 'codeblock',
|
|
206
|
+
label: t('typobar.codeblock'),
|
|
207
|
+
onClick: editorState.codeblock,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
return baseItems.filter(Boolean) as ChatInputActionsProps['items'];
|
|
212
|
+
}, [editor, editorState, extraItems, floating, t]);
|
|
213
|
+
|
|
214
|
+
if (!editorState) return null;
|
|
215
|
+
|
|
216
|
+
// Floating toolbar - just return the actions
|
|
217
|
+
if (floating) return <FloatActions className={className} items={items} style={style} />;
|
|
218
|
+
|
|
219
|
+
// Fixed toolbar - wrap in a styled container
|
|
220
|
+
return (
|
|
221
|
+
<Block
|
|
222
|
+
className={className}
|
|
223
|
+
padding={4}
|
|
224
|
+
shadow
|
|
225
|
+
style={{
|
|
226
|
+
background: cssVar.colorBgElevated,
|
|
227
|
+
borderRadius: 8,
|
|
228
|
+
marginBottom: 16,
|
|
229
|
+
marginTop: 16,
|
|
230
|
+
position: 'sticky',
|
|
231
|
+
top: 12,
|
|
232
|
+
zIndex: 10,
|
|
233
|
+
...style,
|
|
234
|
+
}}
|
|
235
|
+
variant={'outlined'}
|
|
236
|
+
>
|
|
237
|
+
<ChatInputActions items={items} />
|
|
238
|
+
</Block>
|
|
239
|
+
);
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
InlineToolbar.displayName = 'InlineToolbar';
|
|
244
|
+
|
|
245
|
+
export default InlineToolbar;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type IEditor,
|
|
5
|
+
ReactCodePlugin,
|
|
6
|
+
ReactCodemirrorPlugin,
|
|
7
|
+
ReactHRPlugin,
|
|
8
|
+
ReactImagePlugin,
|
|
9
|
+
ReactLinkPlugin,
|
|
10
|
+
ReactListPlugin,
|
|
11
|
+
ReactLiteXmlPlugin,
|
|
12
|
+
ReactMathPlugin,
|
|
13
|
+
ReactTablePlugin,
|
|
14
|
+
ReactToolbarPlugin,
|
|
15
|
+
} from '@lobehub/editor';
|
|
16
|
+
import { Editor, useEditorState } from '@lobehub/editor/react';
|
|
17
|
+
import { memo, useEffect, useMemo } from 'react';
|
|
18
|
+
import { useTranslation } from 'react-i18next';
|
|
19
|
+
|
|
20
|
+
import type { EditorCanvasProps } from './EditorCanvas';
|
|
21
|
+
import InlineToolbar from './InlineToolbar';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base plugins for the editor (without toolbar)
|
|
25
|
+
*/
|
|
26
|
+
const BASE_PLUGINS = [
|
|
27
|
+
ReactLiteXmlPlugin,
|
|
28
|
+
ReactListPlugin,
|
|
29
|
+
ReactCodePlugin,
|
|
30
|
+
ReactCodemirrorPlugin,
|
|
31
|
+
ReactHRPlugin,
|
|
32
|
+
ReactLinkPlugin,
|
|
33
|
+
ReactTablePlugin,
|
|
34
|
+
ReactMathPlugin,
|
|
35
|
+
Editor.withProps(ReactImagePlugin, {
|
|
36
|
+
defaultBlockImage: true,
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export interface InternalEditorProps extends EditorCanvasProps {
|
|
41
|
+
/**
|
|
42
|
+
* Editor instance (required)
|
|
43
|
+
*/
|
|
44
|
+
editor: IEditor;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Internal EditorCanvas component that requires editor instance
|
|
49
|
+
*/
|
|
50
|
+
const InternalEditor = memo<InternalEditorProps>(
|
|
51
|
+
({
|
|
52
|
+
editor,
|
|
53
|
+
extraPlugins,
|
|
54
|
+
floatingToolbar = true,
|
|
55
|
+
onContentChange,
|
|
56
|
+
onInit,
|
|
57
|
+
placeholder,
|
|
58
|
+
plugins: customPlugins,
|
|
59
|
+
slashItems,
|
|
60
|
+
style,
|
|
61
|
+
toolbarExtraItems,
|
|
62
|
+
}) => {
|
|
63
|
+
const { t } = useTranslation('file');
|
|
64
|
+
const editorState = useEditorState(editor);
|
|
65
|
+
|
|
66
|
+
const finalPlaceholder = placeholder || t('pageEditor.editorPlaceholder');
|
|
67
|
+
|
|
68
|
+
// Build plugins array
|
|
69
|
+
const plugins = useMemo(() => {
|
|
70
|
+
// If custom plugins provided, use them directly
|
|
71
|
+
if (customPlugins) return customPlugins;
|
|
72
|
+
|
|
73
|
+
// Build base plugins with optional extra plugins prepended
|
|
74
|
+
const basePlugins = extraPlugins ? [...extraPlugins, ...BASE_PLUGINS] : BASE_PLUGINS;
|
|
75
|
+
|
|
76
|
+
// Add toolbar if enabled
|
|
77
|
+
if (floatingToolbar) {
|
|
78
|
+
return [
|
|
79
|
+
...basePlugins,
|
|
80
|
+
Editor.withProps(ReactToolbarPlugin, {
|
|
81
|
+
children: (
|
|
82
|
+
<InlineToolbar
|
|
83
|
+
editor={editor}
|
|
84
|
+
editorState={editorState}
|
|
85
|
+
extraItems={toolbarExtraItems}
|
|
86
|
+
floating
|
|
87
|
+
/>
|
|
88
|
+
),
|
|
89
|
+
}),
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return basePlugins;
|
|
94
|
+
}, [customPlugins, editor, editorState, extraPlugins, floatingToolbar, toolbarExtraItems]);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
// for easier debug, mount editor instance to window
|
|
98
|
+
if (editor) window.__editor = editor;
|
|
99
|
+
|
|
100
|
+
return () => {
|
|
101
|
+
window.__editor = undefined;
|
|
102
|
+
};
|
|
103
|
+
}, [editor]);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
onClick={(e) => {
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Editor
|
|
113
|
+
content={''}
|
|
114
|
+
editor={editor}
|
|
115
|
+
lineEmptyPlaceholder={finalPlaceholder}
|
|
116
|
+
onInit={onInit}
|
|
117
|
+
onTextChange={onContentChange}
|
|
118
|
+
placeholder={finalPlaceholder}
|
|
119
|
+
plugins={plugins}
|
|
120
|
+
slashOption={slashItems ? { items: slashItems } : undefined}
|
|
121
|
+
style={{
|
|
122
|
+
paddingBottom: 64,
|
|
123
|
+
...style,
|
|
124
|
+
}}
|
|
125
|
+
type={'text'}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
InternalEditor.displayName = 'InternalEditor';
|
|
133
|
+
|
|
134
|
+
export default InternalEditor;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opens a native file selector dialog
|
|
3
|
+
* @param handleFiles - Callback function to handle selected files
|
|
4
|
+
* @param accept - MIME type filter for accepted files (default: all files)
|
|
5
|
+
*/
|
|
1
6
|
export function openFileSelector(handleFiles: (files: FileList) => void, accept = '*/*') {
|
|
2
7
|
// Skip on server side
|
|
3
8
|
if (typeof document === 'undefined') {
|
|
@@ -7,19 +12,16 @@ export function openFileSelector(handleFiles: (files: FileList) => void, accept
|
|
|
7
12
|
// Create a hidden input element
|
|
8
13
|
const input = document.createElement('input');
|
|
9
14
|
input.type = 'file';
|
|
10
|
-
input.accept = accept;
|
|
11
|
-
input.multiple = false;
|
|
15
|
+
input.accept = accept;
|
|
16
|
+
input.multiple = false;
|
|
12
17
|
|
|
13
18
|
// Listen for file selection events
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// @ts-expect-error not error
|
|
17
|
-
const files = event.target?.files;
|
|
19
|
+
input.addEventListener('change', (event) => {
|
|
20
|
+
const files = (event.target as HTMLInputElement)?.files;
|
|
18
21
|
if (files && files.length > 0) {
|
|
19
|
-
// Handle selected files
|
|
20
22
|
handleFiles(files);
|
|
21
23
|
}
|
|
22
|
-
};
|
|
24
|
+
});
|
|
23
25
|
|
|
24
26
|
// Trigger file selector
|
|
25
27
|
input.click();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { openFileSelector } from './actions';
|
|
2
|
+
export { default as AutoSaveHint, type AutoSaveHintProps } from './AutoSaveHint';
|
|
3
|
+
export {
|
|
4
|
+
EditorCanvas,
|
|
5
|
+
type EditorCanvasProps,
|
|
6
|
+
type EditorCanvasWithEditorProps,
|
|
7
|
+
} from './EditorCanvas';
|
|
8
|
+
export { EditorErrorBoundary } from './ErrorBoundary';
|
|
9
|
+
export { default as InlineToolbar, type InlineToolbarProps } from './InlineToolbar';
|
|
@@ -1,76 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
ReactCodePlugin,
|
|
5
|
-
ReactCodemirrorPlugin,
|
|
6
|
-
ReactHRPlugin,
|
|
7
|
-
ReactImagePlugin,
|
|
8
|
-
ReactLinkPlugin,
|
|
9
|
-
ReactListPlugin,
|
|
10
|
-
ReactLiteXmlPlugin,
|
|
11
|
-
ReactMathPlugin,
|
|
12
|
-
ReactTablePlugin,
|
|
13
|
-
ReactToolbarPlugin,
|
|
14
|
-
} from '@lobehub/editor';
|
|
15
|
-
import { Editor } from '@lobehub/editor/react';
|
|
16
|
-
import { Alert } from '@lobehub/ui';
|
|
17
|
-
import { type CSSProperties, Component, type ErrorInfo, type ReactNode, memo } from 'react';
|
|
3
|
+
import { type CSSProperties, memo } from 'react';
|
|
18
4
|
import { useTranslation } from 'react-i18next';
|
|
19
5
|
|
|
6
|
+
import { EditorCanvas as SharedEditorCanvas } from '@/features/EditorCanvas';
|
|
7
|
+
|
|
20
8
|
import { usePageEditorStore } from '../store';
|
|
21
|
-
import
|
|
9
|
+
import { useAskCopilotItem } from './useAskCopilotItem';
|
|
22
10
|
import { useSlashItems } from './useSlashItems';
|
|
23
11
|
|
|
24
|
-
interface EditorErrorBoundaryState {
|
|
25
|
-
error: Error | null;
|
|
26
|
-
hasError: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* ErrorBoundary for EditorCanvas component.
|
|
31
|
-
* Catches rendering errors in the editor and displays a fallback error UI
|
|
32
|
-
* instead of crashing the entire page.
|
|
33
|
-
*/
|
|
34
|
-
class EditorErrorBoundary extends Component<{ children: ReactNode }, EditorErrorBoundaryState> {
|
|
35
|
-
public state: EditorErrorBoundaryState = {
|
|
36
|
-
error: null,
|
|
37
|
-
hasError: false,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
public static getDerivedStateFromError(error: Error): Partial<EditorErrorBoundaryState> {
|
|
41
|
-
return { error, hasError: true };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
45
|
-
console.error('[EditorErrorBoundary] Caught error in editor render:', {
|
|
46
|
-
componentStack: errorInfo.componentStack,
|
|
47
|
-
error: error.message,
|
|
48
|
-
stack: error.stack,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public render() {
|
|
53
|
-
if (this.state.hasError) {
|
|
54
|
-
return (
|
|
55
|
-
<Alert
|
|
56
|
-
message={this.state.error?.message || 'An unknown error occurred in the editor'}
|
|
57
|
-
showIcon
|
|
58
|
-
style={{
|
|
59
|
-
margin: 16,
|
|
60
|
-
overflow: 'hidden',
|
|
61
|
-
position: 'relative',
|
|
62
|
-
width: '100%',
|
|
63
|
-
}}
|
|
64
|
-
title="Editor Error"
|
|
65
|
-
type="error"
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return this.props.children;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
12
|
interface EditorCanvasProps {
|
|
75
13
|
placeholder?: string;
|
|
76
14
|
style?: CSSProperties;
|
|
@@ -80,55 +18,20 @@ const EditorCanvas = memo<EditorCanvasProps>(({ placeholder, style }) => {
|
|
|
80
18
|
const { t } = useTranslation(['file', 'editor']);
|
|
81
19
|
|
|
82
20
|
const editor = usePageEditorStore((s) => s.editor);
|
|
83
|
-
const
|
|
84
|
-
const onEditorInit = usePageEditorStore((s) => s.onEditorInit);
|
|
21
|
+
const documentId = usePageEditorStore((s) => s.documentId);
|
|
85
22
|
|
|
86
23
|
const slashItems = useSlashItems(editor);
|
|
87
|
-
|
|
88
|
-
if (!editor) return null;
|
|
24
|
+
const askCopilotItem = useAskCopilotItem(editor);
|
|
89
25
|
|
|
90
26
|
return (
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
content={''}
|
|
100
|
-
editor={editor!}
|
|
101
|
-
lineEmptyPlaceholder={placeholder || t('pageEditor.editorPlaceholder')}
|
|
102
|
-
onInit={onEditorInit}
|
|
103
|
-
onTextChange={handleContentChange}
|
|
104
|
-
placeholder={placeholder || t('pageEditor.editorPlaceholder')}
|
|
105
|
-
plugins={[
|
|
106
|
-
ReactLiteXmlPlugin,
|
|
107
|
-
ReactListPlugin,
|
|
108
|
-
ReactCodePlugin,
|
|
109
|
-
ReactCodemirrorPlugin,
|
|
110
|
-
ReactHRPlugin,
|
|
111
|
-
ReactLinkPlugin,
|
|
112
|
-
ReactTablePlugin,
|
|
113
|
-
ReactMathPlugin,
|
|
114
|
-
Editor.withProps(ReactImagePlugin, {
|
|
115
|
-
defaultBlockImage: true,
|
|
116
|
-
}),
|
|
117
|
-
Editor.withProps(ReactToolbarPlugin, {
|
|
118
|
-
children: <InlineToolbar floating />,
|
|
119
|
-
}),
|
|
120
|
-
]}
|
|
121
|
-
slashOption={{
|
|
122
|
-
items: slashItems,
|
|
123
|
-
}}
|
|
124
|
-
style={{
|
|
125
|
-
paddingBottom: 64,
|
|
126
|
-
...style,
|
|
127
|
-
}}
|
|
128
|
-
type={'text'}
|
|
129
|
-
/>
|
|
130
|
-
</div>
|
|
131
|
-
</EditorErrorBoundary>
|
|
27
|
+
<SharedEditorCanvas
|
|
28
|
+
documentId={documentId}
|
|
29
|
+
editor={editor}
|
|
30
|
+
placeholder={placeholder || t('pageEditor.editorPlaceholder')}
|
|
31
|
+
slashItems={slashItems}
|
|
32
|
+
style={style}
|
|
33
|
+
toolbarExtraItems={askCopilotItem}
|
|
34
|
+
/>
|
|
132
35
|
);
|
|
133
36
|
});
|
|
134
37
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { nanoid } from '@lobechat/utils';
|
|
4
|
+
import { HIDE_TOOLBAR_COMMAND, type IEditor } from '@lobehub/editor';
|
|
5
|
+
import { type ChatInputActionsProps } from '@lobehub/editor/react';
|
|
6
|
+
import { Block } from '@lobehub/ui';
|
|
7
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
8
|
+
import { BotIcon } from 'lucide-react';
|
|
9
|
+
import { useMemo } from 'react';
|
|
10
|
+
|
|
11
|
+
import { useFileStore } from '@/store/file';
|
|
12
|
+
import { useGlobalStore } from '@/store/global';
|
|
13
|
+
|
|
14
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
15
|
+
askCopilot: css`
|
|
16
|
+
border-radius: 6px;
|
|
17
|
+
color: ${cssVar.colorTextDescription};
|
|
18
|
+
|
|
19
|
+
&:hover {
|
|
20
|
+
color: ${cssVar.colorTextSecondary};
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
export const useAskCopilotItem = (editor: IEditor | undefined): ChatInputActionsProps['items'] => {
|
|
26
|
+
const addSelectionContext = useFileStore((s) => s.addChatContextSelection);
|
|
27
|
+
|
|
28
|
+
return useMemo(() => {
|
|
29
|
+
if (!editor) return [];
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
children: (
|
|
34
|
+
<Block
|
|
35
|
+
align="center"
|
|
36
|
+
className={styles.askCopilot}
|
|
37
|
+
clickable
|
|
38
|
+
gap={8}
|
|
39
|
+
horizontal
|
|
40
|
+
onClick={() => {
|
|
41
|
+
const xml = (editor.getSelectionDocument?.('litexml') as string) || '';
|
|
42
|
+
const plainText = (editor.getSelectionDocument?.('text') as string) || '';
|
|
43
|
+
const content = xml.trim() || plainText.trim();
|
|
44
|
+
|
|
45
|
+
if (!content) return;
|
|
46
|
+
|
|
47
|
+
const format = xml.trim() ? 'xml' : 'text';
|
|
48
|
+
const preview =
|
|
49
|
+
(plainText || xml)
|
|
50
|
+
.replaceAll(/<[^>]*>/g, ' ')
|
|
51
|
+
.replaceAll(/\s+/g, ' ')
|
|
52
|
+
.trim() || undefined;
|
|
53
|
+
|
|
54
|
+
// Store action handles deduplication
|
|
55
|
+
addSelectionContext({
|
|
56
|
+
content,
|
|
57
|
+
format,
|
|
58
|
+
id: `selection-${nanoid(6)}`,
|
|
59
|
+
preview,
|
|
60
|
+
title: 'Selection',
|
|
61
|
+
type: 'text',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Open right panel if not opened
|
|
65
|
+
useGlobalStore.getState().toggleRightPanel(true);
|
|
66
|
+
|
|
67
|
+
// Focus on chat input after a short delay to ensure panel is opened
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
// Find the chat input editor within the right panel
|
|
70
|
+
// Query all lexical editors and get the last one (which should be the chat input)
|
|
71
|
+
const allEditors = [...document.querySelectorAll('[data-lexical-editor="true"]')];
|
|
72
|
+
const chatInputEditor = allEditors.at(-1) as HTMLElement;
|
|
73
|
+
if (chatInputEditor) {
|
|
74
|
+
chatInputEditor.focus();
|
|
75
|
+
}
|
|
76
|
+
}, 300);
|
|
77
|
+
|
|
78
|
+
editor.dispatchCommand(HIDE_TOOLBAR_COMMAND, undefined);
|
|
79
|
+
editor.blur();
|
|
80
|
+
}}
|
|
81
|
+
paddingBlock={6}
|
|
82
|
+
paddingInline={12}
|
|
83
|
+
variant="borderless"
|
|
84
|
+
>
|
|
85
|
+
<BotIcon />
|
|
86
|
+
<span>Ask Copilot</span>
|
|
87
|
+
</Block>
|
|
88
|
+
),
|
|
89
|
+
key: 'ask-copilot',
|
|
90
|
+
label: 'Ask Copilot',
|
|
91
|
+
onClick: () => {},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
}, [addSelectionContext, editor]);
|
|
95
|
+
};
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { useMemo } from 'react';
|
|
29
29
|
import { useTranslation } from 'react-i18next';
|
|
30
30
|
|
|
31
|
-
import { openFileSelector } from '@/features/
|
|
31
|
+
import { openFileSelector } from '@/features/EditorCanvas';
|
|
32
32
|
import { useFileStore } from '@/store/file';
|
|
33
33
|
|
|
34
34
|
export const useSlashItems = (editor: IEditor | undefined): SlashOptions['items'] => {
|
|
@@ -40,7 +40,7 @@ interface FolderCrumb {
|
|
|
40
40
|
const Breadcrumb = memo(() => {
|
|
41
41
|
const { t } = useTranslation('file');
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const title = usePageEditorStore((s) => s.title);
|
|
44
44
|
const knowledgeBaseId = usePageEditorStore((s) => s.knowledgeBaseId);
|
|
45
45
|
const parentId = usePageEditorStore((s) => s.parentId);
|
|
46
46
|
|
|
@@ -61,7 +61,7 @@ const Breadcrumb = memo(() => {
|
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const documentTitle =
|
|
64
|
+
const documentTitle = title || t('pageEditor.titlePlaceholder');
|
|
65
65
|
|
|
66
66
|
return (
|
|
67
67
|
<Flexbox align={'center'} className={styles.breadcrumb} flex={1} gap={0} horizontal>
|