diff --git a/packages/client/src/tiptap/core/extensions/quick-insert.ts b/packages/client/src/tiptap/core/extensions/quick-insert.ts
deleted file mode 100644
index 95692f83..00000000
--- a/packages/client/src/tiptap/core/extensions/quick-insert.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { Node } from '@tiptap/core';
-import { ReactRenderer } from '@tiptap/react';
-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 { insertMenuLRUCache, QUICK_INSERT_COMMANDS, transformToCommands } from 'tiptap/core/menus/commands';
-import { MenuList } from 'tiptap/core/wrappers/menu-list';
-
-export const QuickInsertPluginKey = new PluginKey('quickInsert');
-const extensionName = 'quickInsert';
-
-export const QuickInsert = Node.create({
- name: extensionName,
-
- priority: EXTENSION_PRIORITY_HIGHEST,
-
- addOptions() {
- return {
- HTMLAttributes: {},
- suggestion: {
- char: '/',
- pluginKey: QuickInsertPluginKey,
- command: ({ editor, range, props }) => {
- const { state, dispatch } = editor.view;
- const { $head, $from, $to } = state.selection;
-
- // 删除快捷指令
- const end = $from.pos;
- const from = $head.nodeBefore
- ? end - $head.nodeBefore.text.substring($head.nodeBefore.text.indexOf('/')).length
- : $from.start();
-
- const tr = state.tr.deleteRange(from, end);
- dispatch(tr);
-
- props?.action(editor, props.user);
- insertMenuLRUCache.put(props.label);
- editor?.view?.focus();
- },
- },
- };
- },
-
- addProseMirrorPlugins() {
- return [
- Suggestion({
- editor: this.editor,
- ...this.options.suggestion,
- }),
- new Plugin({
- key: new PluginKey('evokeMenuPlaceholder'),
- }),
- ];
- },
-
- addStorage() {
- return {
- rect: {
- width: 0,
- height: 0,
- left: 0,
- top: 0,
- right: 0,
- bottom: 0,
- },
- };
- },
-}).configure({
- suggestion: {
- items: ({ 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(QUICK_INSERT_COMMANDS, recentUsed), ...restCommands].filter(
- (command) => !('title' in command) && command.label && command.label.startsWith(query)
- );
- },
- render: () => {
- let component;
- let popup;
- let isEditable;
-
- return {
- onStart: (props) => {
- isEditable = props.editor.isEditable;
- if (!isEditable) return;
-
- component = new ReactRenderer(MenuList, {
- props,
- editor: props.editor,
- });
-
- popup = tippy('body', {
- getReferenceClientRect: props.clientRect || (() => props.editor.storage[extensionName].rect),
- appendTo: () => document.body,
- content: component.element,
- showOnCreate: true,
- interactive: true,
- trigger: 'manual',
- placement: 'bottom-start',
- });
- },
-
- onUpdate(props) {
- if (!isEditable) return;
-
- component.updateProps(props);
-
- props.editor.storage[extensionName].rect = props.clientRect();
-
- popup[0].setProps({
- getReferenceClientRect: props.clientRect,
- });
- },
-
- onKeyDown(props) {
- if (!isEditable) return;
-
- if (props.event.key === 'Escape') {
- popup[0].hide();
- return true;
- }
- return component.ref?.onKeyDown(props);
- },
-
- onExit() {
- if (!isEditable) return;
- popup[0].destroy();
- component.destroy();
- },
- };
- },
- },
-});
diff --git a/packages/client/src/tiptap/core/extensions/slash.ts b/packages/client/src/tiptap/core/extensions/slash.ts
new file mode 100644
index 00000000..f380d9a1
--- /dev/null
+++ b/packages/client/src/tiptap/core/extensions/slash.ts
@@ -0,0 +1,143 @@
+import { Node } from '@tiptap/core';
+import { ReactRenderer } from '@tiptap/react';
+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 { insertMenuLRUCache, QUICK_INSERT_COMMANDS, transformToCommands } from 'tiptap/core/menus/commands';
+import { MenuList } from 'tiptap/core/wrappers/menu-list';
+
+const createSlashExtension = (char: string) => {
+ const extensionName = `quickInsert-${char}`;
+ const extensionPluginKey = new PluginKey('quickInsert');
+
+ const slashExtension = Node.create({
+ name: extensionName,
+
+ priority: EXTENSION_PRIORITY_HIGHEST,
+
+ addOptions() {
+ return {
+ HTMLAttributes: {},
+ suggestion: {
+ char: char,
+ pluginKey: extensionPluginKey,
+ command: ({ editor, range, props }) => {
+ const { state, dispatch } = editor.view;
+ const { $head, $from, $to } = state.selection;
+
+ // 删除快捷指令
+ const end = $from.pos;
+ const from = $head.nodeBefore
+ ? end - $head.nodeBefore.text.substring($head.nodeBefore.text.indexOf(char)).length
+ : $from.start();
+
+ const tr = state.tr.deleteRange(from, end);
+ dispatch(tr);
+
+ props?.action(editor, props.user);
+ insertMenuLRUCache.put(props.label);
+ editor?.view?.focus();
+ },
+ },
+ };
+ },
+
+ addProseMirrorPlugins() {
+ return [
+ Suggestion({
+ editor: this.editor,
+ ...this.options.suggestion,
+ }),
+ new Plugin({
+ key: new PluginKey('evokeMenuPlaceholder'),
+ }),
+ ];
+ },
+
+ addStorage() {
+ return {
+ rect: {
+ width: 0,
+ height: 0,
+ left: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ },
+ };
+ },
+ }).configure({
+ suggestion: {
+ items: ({ 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(QUICK_INSERT_COMMANDS, recentUsed), ...restCommands].filter(
+ (command) =>
+ !('title' in command) &&
+ ((command.label && command.label.startsWith(query)) || (command.pinyin && command.pinyin.startsWith(query)))
+ );
+ },
+ render: () => {
+ let component;
+ let popup;
+ let isEditable;
+
+ return {
+ onStart: (props) => {
+ isEditable = props.editor.isEditable;
+ if (!isEditable) return;
+
+ component = new ReactRenderer(MenuList, {
+ props,
+ editor: props.editor,
+ });
+
+ popup = tippy('body', {
+ getReferenceClientRect: props.clientRect || (() => props.editor.storage[extensionName].rect),
+ appendTo: () => document.body,
+ content: component.element,
+ showOnCreate: true,
+ interactive: true,
+ trigger: 'manual',
+ placement: 'bottom-start',
+ });
+ },
+
+ onUpdate(props) {
+ if (!isEditable) return;
+
+ component.updateProps(props);
+ props.editor.storage[extensionName].rect = props.clientRect();
+ popup[0].setProps({
+ getReferenceClientRect: props.clientRect,
+ });
+ },
+
+ onKeyDown(props) {
+ if (!isEditable) return;
+
+ if (props.event.key === 'Escape') {
+ popup[0].hide();
+ return true;
+ }
+ return component.ref?.onKeyDown(props);
+ },
+
+ onExit() {
+ if (!isEditable) return;
+ popup[0].destroy();
+ component.destroy();
+ },
+ };
+ },
+ },
+ });
+
+ return slashExtension;
+};
+
+export const EnSlashExtension = createSlashExtension('/');
+export const ZhSlashExtension = createSlashExtension('、');
diff --git a/packages/client/src/tiptap/core/menus/commands.tsx b/packages/client/src/tiptap/core/menus/commands.tsx
index 1cdd7624..b37218c8 100644
--- a/packages/client/src/tiptap/core/menus/commands.tsx
+++ b/packages/client/src/tiptap/core/menus/commands.tsx
@@ -30,6 +30,7 @@ type IBaseCommand = {
isBlock?: boolean;
icon: React.ReactNode;
label: string;
+ pinyin: string;
user?: IUser;
};
@@ -55,12 +56,14 @@ export const COMMANDS: ICommand[] = [
{
icon: ,
label: '目录',
+ pinyin: 'mulu',
action: (editor) => editor.chain().focus().setTableOfContents().run(),
},
{
isBlock: true,
icon: ,
label: '表格',
+ pinyin: 'biaoge',
custom: (editor, runCommand) => (
,
label: '布局',
+ pinyin: 'buju',
custom: (editor, runCommand) => (
,
label: '代码块',
+ pinyin: 'daimakuai',
action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
},
{
isBlock: true,
icon: ,
label: '图片',
+ pinyin: 'tupian',
action: (editor) => editor.chain().focus().setEmptyImage({ width: '100%' }).run(),
},
{
isBlock: true,
icon: ,
label: '附件',
+ pinyin: 'fujian',
action: (editor) => editor.chain().focus().setAttachment().run(),
},
{
isBlock: true,
icon: ,
label: '倒计时',
+ pinyin: 'daojishi',
action: (editor) => createCountdown(editor),
},
{
isBlock: true,
icon: ,
label: '外链',
+ pinyin: 'wailian',
action: (editor, user) =>
editor.chain().focus().setIframe({ url: '', defaultShowPicker: true, createUser: user.id }).run(),
},
@@ -163,6 +172,7 @@ export const COMMANDS: ICommand[] = [
isBlock: true,
icon: ,
label: '流程图',
+ pinyin: 'liuchengtu',
action: (editor, user) => {
editor.chain().focus().setFlow({ width: '100%', defaultShowPicker: true, createUser: user.id }).run();
},
@@ -171,6 +181,7 @@ export const COMMANDS: ICommand[] = [
isBlock: true,
icon: ,
label: '思维导图',
+ pinyin: 'siweidaotu',
action: (editor, user) => {
editor.chain().focus().setMind({ width: '100%', defaultShowPicker: true, createUser: user.id }).run();
},
@@ -179,6 +190,7 @@ export const COMMANDS: ICommand[] = [
isBlock: true,
icon: ,
label: '绘图',
+ pinyin: 'huitu',
action: (editor, user) => {
editor.chain().focus().setExcalidraw({ width: '100%', defaultShowPicker: true, createUser: user.id }).run();
},
@@ -187,17 +199,20 @@ export const COMMANDS: ICommand[] = [
isBlock: true,
icon: ,
label: '数学公式',
+ pinyin: 'shuxuegongshi',
action: (editor, user) => editor.chain().focus().setKatex({ defaultShowPicker: true, createUser: user.id }).run(),
},
{
icon: ,
label: '状态',
+ pinyin: 'zhuangtai',
action: (editor, user) => editor.chain().focus().setStatus({ defaultShowPicker: true, createUser: user.id }).run(),
},
{
isBlock: true,
icon: ,
label: '高亮块',
+ pinyin: 'gaoliangkuai',
action: (editor) => editor.chain().focus().setCallout().run(),
},
{
@@ -207,6 +222,7 @@ export const COMMANDS: ICommand[] = [
isBlock: true,
icon: ,
label: '文档',
+ pinyin: 'wendang',
action: (editor, user) =>
editor.chain().focus().setDocumentReference({ defaultShowPicker: true, createUser: user.id }).run(),
},
@@ -214,6 +230,7 @@ export const COMMANDS: ICommand[] = [
isBlock: true,
icon: ,
label: '子文档',
+ pinyin: 'ziwendang',
action: (editor) => editor.chain().focus().setDocumentChildren().run(),
},
];
@@ -223,12 +240,14 @@ export const QUICK_INSERT_COMMANDS = [
{
icon: ,
label: '表格',
+ pinyin: 'biaoge',
action: (editor: Editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
},
{
isBlock: true,
icon: ,
label: '布局',
+ pinyin: 'buju',
action: (editor) => editor.chain().focus().insertColumns({ cols: 2 }).run(),
},
...COMMANDS.slice(4),
diff --git a/packages/client/src/tiptap/editor/collaboration/kit.ts b/packages/client/src/tiptap/editor/collaboration/kit.ts
index c3f2e831..0d8573ff 100644
--- a/packages/client/src/tiptap/editor/collaboration/kit.ts
+++ b/packages/client/src/tiptap/editor/collaboration/kit.ts
@@ -47,9 +47,9 @@ import { OrderedList } from 'tiptap/core/extensions/ordered-list';
import { Paragraph } from 'tiptap/core/extensions/paragraph';
import { Paste } from 'tiptap/core/extensions/paste';
import { Placeholder } from 'tiptap/core/extensions/placeholder';
-import { QuickInsert } from 'tiptap/core/extensions/quick-insert';
import { Scroll2Cursor } from 'tiptap/core/extensions/scroll-to-cursor';
import { SearchNReplace } from 'tiptap/core/extensions/search';
+import { EnSlashExtension, ZhSlashExtension } from 'tiptap/core/extensions/slash';
import { Status } from 'tiptap/core/extensions/status';
import { Strike } from 'tiptap/core/extensions/strike';
import { Subscript } from 'tiptap/core/extensions/subscript';
@@ -176,7 +176,8 @@ export const CollaborationKit = [
Mind.configure({
getCreateUserId,
}),
- QuickInsert,
+ EnSlashExtension,
+ ZhSlashExtension,
SearchNReplace,
Status,
TableOfContents.configure({