mirror of https://github.com/fantasticit/think.git
tiptap: use shared commands
This commit is contained in:
parent
bfddd4cc6e
commit
a589db9978
|
@ -4,7 +4,7 @@ import Suggestion from '@tiptap/suggestion';
|
|||
import { Plugin, PluginKey } from 'prosemirror-state';
|
||||
import tippy from 'tippy.js';
|
||||
import { EXTENSION_PRIORITY_HIGHEST } from 'tiptap/core/constants';
|
||||
import { QUICK_INSERT_ITEMS } from 'tiptap/core/menus/quick-insert';
|
||||
import { insertMenuLRUCache, QUICK_INSERT_COMMANDS, transformToCommands } from 'tiptap/core/menus/commands';
|
||||
import { MenuList } from 'tiptap/core/wrappers/menu-list';
|
||||
|
||||
export const QuickInsertPluginKey = new PluginKey('quickInsert');
|
||||
|
@ -25,7 +25,8 @@ export const QuickInsert = Node.create({
|
|||
const $from = state.selection.$from;
|
||||
const tr = state.tr.deleteRange($from.start(), $from.pos);
|
||||
dispatch(tr);
|
||||
props?.command(editor, props.user);
|
||||
props?.action(editor, props.user);
|
||||
insertMenuLRUCache.put(props.label);
|
||||
editor?.view?.focus();
|
||||
},
|
||||
},
|
||||
|
@ -33,57 +34,26 @@ export const QuickInsert = Node.create({
|
|||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
const { editor } = this;
|
||||
|
||||
return [
|
||||
Suggestion({
|
||||
editor: this.editor,
|
||||
...this.options.suggestion,
|
||||
}),
|
||||
|
||||
new Plugin({
|
||||
key: new PluginKey('evokeMenuPlaceholder'),
|
||||
props: {
|
||||
// decorations: (state) => {
|
||||
// if (!editor.isEditable) return;
|
||||
// const parent = findParentNode((node) => node.type.name === 'paragraph')(state.selection);
|
||||
// if (!parent) {
|
||||
// return;
|
||||
// }
|
||||
// const decorations: Decoration[] = [];
|
||||
// const isEmpty = parent && parent.node.content.size === 0;
|
||||
// const isSlash = parent && parent.node.textContent === '/';
|
||||
// const isTopLevel = state.selection.$from.depth === 1;
|
||||
// const hasOtherChildren = parent && parent.node.content.childCount > 1;
|
||||
// if (isTopLevel) {
|
||||
// if (isEmpty) {
|
||||
// decorations.push(
|
||||
// Decoration.node(parent.pos, parent.pos + parent.node.nodeSize, {
|
||||
// 'class': 'is-empty',
|
||||
// 'data-placeholder': '输入 / 唤起更多',
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
// if (isSlash && !hasOtherChildren) {
|
||||
// decorations.push(
|
||||
// Decoration.node(parent.pos, parent.pos + parent.node.nodeSize, {
|
||||
// 'class': 'is-empty',
|
||||
// 'data-placeholder': ` 继续输入进行过滤`,
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
// return DecorationSet.create(state.doc, decorations);
|
||||
// }
|
||||
// return null;
|
||||
// },
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
}).configure({
|
||||
suggestion: {
|
||||
items: ({ query }) => {
|
||||
return QUICK_INSERT_ITEMS.filter((command) => command.key.startsWith(query));
|
||||
const recentUsed = insertMenuLRUCache.get() as string[];
|
||||
const restCommands = QUICK_INSERT_COMMANDS.filter((command) => {
|
||||
return !('title' in command) && !('custom' in command) && !recentUsed.includes(command.label);
|
||||
});
|
||||
return [...transformToCommands(recentUsed), ...restCommands].filter(
|
||||
(command) => !('title' in command) && command.label && command.label.startsWith(query)
|
||||
);
|
||||
},
|
||||
render: () => {
|
||||
let component;
|
||||
|
@ -130,9 +100,8 @@ export const QuickInsert = Node.create({
|
|||
return component.ref?.onKeyDown(props);
|
||||
},
|
||||
|
||||
onExit(props) {
|
||||
onExit() {
|
||||
if (!isEditable) return;
|
||||
|
||||
popup[0].destroy();
|
||||
component.destroy();
|
||||
},
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
import { Dropdown, Popover } from '@douyinfe/semi-ui';
|
||||
import { IUser } from '@think/domains';
|
||||
import { GridSelect } from 'components/grid-select';
|
||||
import {
|
||||
IconAttachment,
|
||||
IconCallout,
|
||||
IconCodeBlock,
|
||||
IconCountdown,
|
||||
IconDocument,
|
||||
IconFlow,
|
||||
IconImage,
|
||||
IconLink,
|
||||
IconMath,
|
||||
IconMind,
|
||||
IconStatus,
|
||||
IconTable,
|
||||
IconTableOfContents,
|
||||
} from 'components/icons';
|
||||
import { createKeysLocalStorageLRUCache } from 'helpers/lru-cache';
|
||||
import { Editor } from 'tiptap/core';
|
||||
|
||||
import { createCountdown } from './countdown/service';
|
||||
|
||||
export type ITitle = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
type IBaseCommand = {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
user?: IUser;
|
||||
};
|
||||
|
||||
type IAction = (editor: Editor, user?: IUser) => void;
|
||||
|
||||
export type ILabelRenderCommand = IBaseCommand & {
|
||||
action: IAction;
|
||||
};
|
||||
|
||||
type ICustomRenderCommand = IBaseCommand & {
|
||||
custom: (editor: Editor, runCommand: (arg: { label: string; action: IAction }) => any) => React.ReactNode;
|
||||
};
|
||||
|
||||
export type ICommand = ITitle | ILabelRenderCommand | ICustomRenderCommand;
|
||||
|
||||
export const insertMenuLRUCache = createKeysLocalStorageLRUCache('TIPTAP_INSERT_MENU', 3);
|
||||
|
||||
export const COMMANDS: ICommand[] = [
|
||||
{
|
||||
title: '通用',
|
||||
},
|
||||
{
|
||||
icon: <IconTableOfContents />,
|
||||
label: '目录',
|
||||
action: (editor) => editor.chain().focus().setTableOfContents().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconTable />,
|
||||
label: '表格',
|
||||
custom: (editor, runCommand) => (
|
||||
<Popover
|
||||
key="table"
|
||||
showArrow
|
||||
position="rightTop"
|
||||
zIndex={10000}
|
||||
content={
|
||||
<div style={{ padding: 0 }}>
|
||||
<GridSelect
|
||||
onSelect={({ rows, cols }) => {
|
||||
return runCommand({
|
||||
label: '表格',
|
||||
action: () => editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run(),
|
||||
})();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Dropdown.Item>
|
||||
<IconTable />
|
||||
表格
|
||||
</Dropdown.Item>
|
||||
</Popover>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <IconCodeBlock />,
|
||||
label: '代码块',
|
||||
action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconImage />,
|
||||
label: '图片',
|
||||
// @ts-ignore
|
||||
action: (editor) => editor.chain().focus().setEmptyImage({ width: '100%' }).run(),
|
||||
},
|
||||
{
|
||||
icon: <IconAttachment />,
|
||||
label: '附件',
|
||||
action: (editor) => editor.chain().focus().setAttachment().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconCountdown />,
|
||||
label: '倒计时',
|
||||
action: (editor) => createCountdown(editor),
|
||||
},
|
||||
{
|
||||
icon: <IconLink />,
|
||||
label: '外链',
|
||||
action: (editor) => editor.chain().focus().setIframe({ url: '' }).run(),
|
||||
},
|
||||
{
|
||||
title: '卡片',
|
||||
},
|
||||
{
|
||||
icon: <IconFlow />,
|
||||
label: '流程图',
|
||||
action: (editor) => {
|
||||
editor.chain().focus().setFlow({ width: '100%' }).run();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <IconMind />,
|
||||
label: '思维导图',
|
||||
action: (editor) => {
|
||||
// @ts-ignore
|
||||
editor.chain().focus().setMind({ width: '100%' }).run();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <IconMath />,
|
||||
label: '数学公式',
|
||||
action: (editor, user) => editor.chain().focus().setKatex({ defaultShowPicker: true, createUser: user.name }).run(),
|
||||
},
|
||||
{
|
||||
icon: <IconStatus />,
|
||||
label: '状态',
|
||||
action: (editor, user) =>
|
||||
editor.chain().focus().setStatus({ defaultShowPicker: true, createUser: user.name }).run(),
|
||||
},
|
||||
{
|
||||
icon: <IconCallout />,
|
||||
label: '高亮块',
|
||||
action: (editor) => editor.chain().focus().setCallout().run(),
|
||||
},
|
||||
{
|
||||
title: '内容引用',
|
||||
},
|
||||
{
|
||||
icon: <IconDocument />,
|
||||
label: '文档',
|
||||
action: (editor) => editor.chain().focus().setDocumentReference().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconDocument />,
|
||||
label: '子文档',
|
||||
action: (editor) => editor.chain().focus().setDocumentChildren().run(),
|
||||
},
|
||||
];
|
||||
|
||||
export const QUICK_INSERT_COMMANDS = [
|
||||
...COMMANDS.slice(0, 1),
|
||||
{
|
||||
icon: <IconTable />,
|
||||
label: '表格',
|
||||
action: (editor: Editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
|
||||
},
|
||||
...COMMANDS.slice(3),
|
||||
];
|
||||
|
||||
export const transformToCommands = (data: string[]) => {
|
||||
return data
|
||||
.map((label) => {
|
||||
return COMMANDS.find((command) => {
|
||||
if ('title' in command) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return command.label === label;
|
||||
});
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
|
@ -1,146 +1,14 @@
|
|||
import { IconPlus } from '@douyinfe/semi-icons';
|
||||
import { Button, Dropdown, Popover } from '@douyinfe/semi-ui';
|
||||
import { GridSelect } from 'components/grid-select';
|
||||
import {
|
||||
IconAttachment,
|
||||
IconCallout,
|
||||
IconCodeBlock,
|
||||
IconCountdown,
|
||||
IconDocument,
|
||||
IconFlow,
|
||||
IconImage,
|
||||
IconLink,
|
||||
IconMath,
|
||||
IconMind,
|
||||
IconStatus,
|
||||
IconTable,
|
||||
IconTableOfContents,
|
||||
} from 'components/icons';
|
||||
import { Button, Dropdown } from '@douyinfe/semi-ui';
|
||||
import { Tooltip } from 'components/tooltip';
|
||||
import { useUser } from 'data/user';
|
||||
import { createKeysLocalStorageLRUCache } from 'helpers/lru-cache';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Editor } from 'tiptap/core';
|
||||
import { Title } from 'tiptap/core/extensions/title';
|
||||
import { useActive } from 'tiptap/core/hooks/use-active';
|
||||
|
||||
import { createCountdown } from '../countdown/service';
|
||||
|
||||
const insertMenuLRUCache = createKeysLocalStorageLRUCache('TIPTAP_INSERT_MENU', 3);
|
||||
|
||||
const COMMANDS = [
|
||||
{
|
||||
title: '通用',
|
||||
},
|
||||
{
|
||||
icon: <IconTableOfContents />,
|
||||
label: '目录',
|
||||
action: (editor) => editor.chain().focus().setTableOfContents().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconTable />,
|
||||
label: '表格',
|
||||
custom: (editor, runCommand) => (
|
||||
<Popover
|
||||
key="table"
|
||||
showArrow
|
||||
position="rightTop"
|
||||
zIndex={10000}
|
||||
content={
|
||||
<div style={{ padding: 0 }}>
|
||||
<GridSelect
|
||||
onSelect={({ rows, cols }) => {
|
||||
return runCommand({
|
||||
label: '表格',
|
||||
action: () => editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run(),
|
||||
})();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Dropdown.Item>
|
||||
<IconTable />
|
||||
表格
|
||||
</Dropdown.Item>
|
||||
</Popover>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <IconCodeBlock />,
|
||||
label: '代码块',
|
||||
action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconImage />,
|
||||
label: '图片',
|
||||
action: (editor) => {
|
||||
editor.chain().focus().setEmptyImage({ width: '100%' }).run();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <IconAttachment />,
|
||||
label: '附件',
|
||||
action: (editor) => editor.chain().focus().setAttachment().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconCountdown />,
|
||||
label: '倒计时',
|
||||
action: (editor) => createCountdown(editor),
|
||||
},
|
||||
{
|
||||
icon: <IconLink />,
|
||||
label: '外链',
|
||||
action: (editor) => editor.chain().focus().setIframe({ url: '' }).run(),
|
||||
},
|
||||
{
|
||||
title: '卡片',
|
||||
},
|
||||
{
|
||||
icon: <IconFlow />,
|
||||
label: '流程图',
|
||||
action: (editor) => {
|
||||
editor.chain().focus().setFlow({ width: '100%' }).run();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <IconMind />,
|
||||
label: '思维导图',
|
||||
action: (editor) => {
|
||||
editor.chain().focus().setMind({ width: '100%' }).run();
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <IconMath />,
|
||||
label: '数学公式',
|
||||
action: (editor, user) => editor.chain().focus().setKatex({ defaultShowPicker: true, createUser: user.name }).run(),
|
||||
},
|
||||
{
|
||||
icon: <IconStatus />,
|
||||
label: '状态',
|
||||
action: (editor, user) =>
|
||||
editor.chain().focus().setStatus({ defaultShowPicker: true, createUser: user.name }).run(),
|
||||
},
|
||||
{
|
||||
icon: <IconCallout />,
|
||||
label: '高亮块',
|
||||
action: (editor) => editor.chain().focus().setCallout().run(),
|
||||
},
|
||||
{
|
||||
title: '内容引用',
|
||||
},
|
||||
{
|
||||
icon: <IconDocument />,
|
||||
label: '文档',
|
||||
action: (editor) => editor.chain().focus().setDocumentReference().run(),
|
||||
},
|
||||
{
|
||||
icon: <IconDocument />,
|
||||
label: '子文档',
|
||||
action: (editor) => editor.chain().focus().setDocumentChildren().run(),
|
||||
},
|
||||
];
|
||||
import { COMMANDS, insertMenuLRUCache, transformToCommands } from '../commands';
|
||||
|
||||
export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||
const { user } = useUser();
|
||||
|
@ -153,14 +21,6 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
|
|||
[recentUsed]
|
||||
);
|
||||
|
||||
const transformToCommands = useCallback((data: string[]) => {
|
||||
return data
|
||||
.map((label) => {
|
||||
return COMMANDS.find((command) => command.label && command.label === label);
|
||||
})
|
||||
.filter(Boolean);
|
||||
}, []);
|
||||
|
||||
const runCommand = useCallback(
|
||||
(command) => {
|
||||
return () => {
|
||||
|
@ -170,14 +30,14 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
|
|||
toggleVisible(false);
|
||||
};
|
||||
},
|
||||
[editor, toggleVisible, transformToCommands, user]
|
||||
[editor, toggleVisible, user]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
insertMenuLRUCache.syncFromStorage();
|
||||
setRecentUsed(transformToCommands(insertMenuLRUCache.get() as string[]));
|
||||
}, [visible, transformToCommands]);
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
|
|
|
@ -1,305 +0,0 @@
|
|||
import { IconList, IconOrderedList } from '@douyinfe/semi-icons';
|
||||
import { Space } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconAttachment,
|
||||
IconCallout,
|
||||
IconCodeBlock,
|
||||
IconCountdown,
|
||||
IconDocument,
|
||||
IconFlow,
|
||||
IconHeading1,
|
||||
IconHeading2,
|
||||
IconHeading3,
|
||||
IconHorizontalRule,
|
||||
IconImage,
|
||||
IconLink,
|
||||
IconMath,
|
||||
IconMind,
|
||||
IconQuote,
|
||||
IconStatus,
|
||||
IconTable,
|
||||
IconTableOfContents,
|
||||
IconTask,
|
||||
} from 'components/icons';
|
||||
import { Editor } from 'tiptap/core';
|
||||
|
||||
import { createCountdown } from './countdown/service';
|
||||
import { createOrToggleLink } from './link/service';
|
||||
|
||||
export const QUICK_INSERT_ITEMS = [
|
||||
{
|
||||
key: '标题1',
|
||||
label: (
|
||||
<Space>
|
||||
<IconHeading1 />
|
||||
标题1
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 1 }).run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '标题2',
|
||||
label: (
|
||||
<Space>
|
||||
<IconHeading2 />
|
||||
标题2
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '标题1',
|
||||
label: (
|
||||
<Space>
|
||||
<IconHeading3 />
|
||||
标题3
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '无序列表',
|
||||
label: (
|
||||
<Space>
|
||||
<IconList />
|
||||
无序列表
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleBulletList().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '有序列表',
|
||||
label: (
|
||||
<Space>
|
||||
<IconOrderedList />
|
||||
有序列表
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleOrderedList().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '任务列表',
|
||||
label: (
|
||||
<Space>
|
||||
<IconTask />
|
||||
任务列表
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleTaskList().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '链接',
|
||||
label: (
|
||||
<Space>
|
||||
<IconLink />
|
||||
链接
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => createOrToggleLink(editor),
|
||||
},
|
||||
|
||||
{
|
||||
key: '引用',
|
||||
label: (
|
||||
<Space>
|
||||
<IconQuote />
|
||||
引用
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleBlockquote().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '分割线',
|
||||
label: (
|
||||
<Space>
|
||||
<IconHorizontalRule />
|
||||
分割线
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setHorizontalRule().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '目录',
|
||||
label: (
|
||||
<Space>
|
||||
<IconTableOfContents />
|
||||
目录
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setTableOfContents().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '表格',
|
||||
label: (
|
||||
<Space>
|
||||
<IconTable />
|
||||
表格
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '代码块',
|
||||
label: (
|
||||
<Space>
|
||||
<IconCodeBlock />
|
||||
代码块
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().toggleCodeBlock().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '图片',
|
||||
label: () => (
|
||||
<Space>
|
||||
<IconImage />
|
||||
图片
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setEmptyImage().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '附件',
|
||||
label: () => (
|
||||
<Space>
|
||||
<IconAttachment />
|
||||
附件
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setAttachment().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '倒计时',
|
||||
label: () => (
|
||||
<Space>
|
||||
<IconCountdown />
|
||||
倒计时
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => createCountdown(editor),
|
||||
},
|
||||
|
||||
{
|
||||
key: '外链',
|
||||
label: (
|
||||
<Space>
|
||||
<IconLink />
|
||||
外链
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setIframe({ url: '' }).run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '流程图',
|
||||
label: (
|
||||
<Space>
|
||||
<IconFlow />
|
||||
流程图
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setFlow({ width: '100%' }).run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '思维导图',
|
||||
label: (
|
||||
<Space>
|
||||
<IconMind />
|
||||
思维导图
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setMind().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '数学公式',
|
||||
label: (
|
||||
<Space>
|
||||
<IconMath />
|
||||
数学公式
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor, user) => {
|
||||
console.log('user', user);
|
||||
if (!user) return;
|
||||
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setKatex({
|
||||
defaultShowPicker: true,
|
||||
createUser: user.name,
|
||||
})
|
||||
.run();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
key: '状态',
|
||||
label: (
|
||||
<Space>
|
||||
<IconStatus />
|
||||
状态
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor, user) => {
|
||||
if (!user) return;
|
||||
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setStatus({
|
||||
defaultShowPicker: true,
|
||||
createUser: user.name,
|
||||
})
|
||||
.run();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
key: '高亮块',
|
||||
label: (
|
||||
<Space>
|
||||
<IconCallout />
|
||||
高亮块
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setCallout().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '文档',
|
||||
label: (
|
||||
<Space>
|
||||
<IconDocument />
|
||||
文档
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setDocumentReference().run(),
|
||||
},
|
||||
|
||||
{
|
||||
key: '子文档',
|
||||
label: (
|
||||
<Space>
|
||||
<IconDocument />
|
||||
子文档
|
||||
</Space>
|
||||
),
|
||||
command: (editor: Editor) => editor.chain().focus().setDocumentChildren().run(),
|
||||
},
|
||||
];
|
|
@ -1,15 +1,17 @@
|
|||
import { Space } from '@douyinfe/semi-ui';
|
||||
import { Editor } from '@tiptap/core';
|
||||
import cls from 'classnames';
|
||||
import { useUser } from 'data/user';
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import { ILabelRenderCommand } from 'tiptap/core/menus/commands';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
interface IProps {
|
||||
editor: Editor;
|
||||
items: Array<{ label: React.ReactNode | ((editor: Editor) => React.ReactNode) }>;
|
||||
command: any;
|
||||
items: ILabelRenderCommand[];
|
||||
command: (command: ILabelRenderCommand) => void;
|
||||
}
|
||||
|
||||
export const MenuList: React.FC<IProps> = forwardRef((props, ref) => {
|
||||
|
@ -18,12 +20,12 @@ export const MenuList: React.FC<IProps> = forwardRef((props, ref) => {
|
|||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const selectItem = (index) => {
|
||||
const item = props.items[index];
|
||||
const command = props.items[index];
|
||||
|
||||
if (item) {
|
||||
// @ts-ignore
|
||||
item.user = user; // 注入用户信息
|
||||
props.command(item);
|
||||
if (command) {
|
||||
// 注入用户信息
|
||||
command.user = user;
|
||||
props.command(command);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -72,15 +74,20 @@ export const MenuList: React.FC<IProps> = forwardRef((props, ref) => {
|
|||
<div className={styles.items}>
|
||||
<div ref={$container}>
|
||||
{props.items.length ? (
|
||||
props.items.map((item, index) => (
|
||||
<span
|
||||
className={cls(styles.item, index === selectedIndex ? styles['is-selected'] : '')}
|
||||
key={index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
{typeof item.label === 'function' ? item.label(props.editor) : item.label}
|
||||
</span>
|
||||
))
|
||||
props.items.map((item, index) => {
|
||||
return (
|
||||
<span
|
||||
className={cls(styles.item, index === selectedIndex ? styles['is-selected'] : '')}
|
||||
key={index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
<Space>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</Space>
|
||||
</span>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className={styles.item}>没有找到结果</div>
|
||||
)}
|
||||
|
|
Loading…
Reference in New Issue