refactor: improve type definitions for editor extension (#7425)

#### What type of PR is this?

/area plugin
/area editor
/milestone 2.20.x

#### What this PR does / why we need it:

Previously, editor extension related types were not exported because the type names conflicted with some UI component names, making it impossible to import extension types in plugins. This PR modifies the type names and exports them in index.ts.

#### Does this PR introduce a user-facing change?

```release-note
导出与编辑器扩展相关的类型定义
```
pull/7424/head^2
Ryan Wang 2025-05-13 17:56:06 +08:00 committed by GitHub
parent 747ca05d90
commit 796407c67d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 80 additions and 76 deletions

View File

@ -13,23 +13,23 @@ export interface ExtensionOptions {
editor,
}: {
editor: Editor;
}) => ToolbarItem | ToolbarItem[];
}) => ToolbarItemType | ToolbarItemType[];
// Slash Command 扩展
getCommandMenuItems?: () => CommandMenuItem | CommandMenuItem[];
getCommandMenuItems?: () => CommandMenuItemType | CommandMenuItemType[];
// 悬浮菜单扩展
getBubbleMenu?: ({ editor }: { editor: Editor }) => NodeBubbleMenu;
getBubbleMenu?: ({ editor }: { editor: Editor }) => NodeBubbleMenuType;
// 工具箱扩展
getToolboxItems?: ({
editor,
}: {
editor: Editor;
}) => ToolboxItem | ToolboxItem[];
}) => ToolboxItemType | ToolboxItemType[];
// 拖拽扩展
getDraggable?: ({ editor }: { editor: Editor }) => DraggableItem | boolean;
getDraggable?: ({ editor }: { editor: Editor }) => DraggableItemType | boolean;
}
```
@ -62,10 +62,10 @@ getToolbarItems?: ({
editor,
}: {
editor: Editor;
}) => ToolbarItem | ToolbarItem[];
}) => ToolbarItemType | ToolbarItemType[];
// 工具栏
export interface ToolbarItem {
export interface ToolbarItemType {
priority: number;
component: Component;
props: {
@ -76,7 +76,7 @@ export interface ToolbarItem {
title?: string;
action?: () => void;
};
children?: ToolbarItem[];
children?: ToolbarItemType[];
}
```
@ -130,9 +130,9 @@ getToolboxItems?: ({
editor,
}: {
editor: Editor;
}) => ToolboxItem | ToolboxItem[];
}) => ToolboxItemType | ToolboxItemType[];
export interface ToolboxItem {
export interface ToolboxItemType {
priority: number;
component: Component;
props: {
@ -195,9 +195,9 @@ Slash Command (斜杠命令)的扩展,可用于在当前行快捷执行功
```ts
// Slash Command 扩展
getCommandMenuItems?: () => CommandMenuItem | CommandMenuItem[];
getCommandMenuItems?: () => CommandMenuItemType | CommandMenuItemType[];
export interface CommandMenuItem {
export interface CommandMenuItemType {
priority: number;
icon: Component;
title: string;
@ -255,16 +255,16 @@ export interface CommandMenuItem {
```ts
// 悬浮菜单扩展
getBubbleMenu?: ({ editor }: { editor: Editor }) => NodeBubbleMenu;
getBubbleMenu?: ({ editor }: { editor: Editor }) => NodeBubbleMenuType;
interface BubbleMenuProps {
pluginKey?: string; // 悬浮菜单插件 Key建议命名方式 xxxBubbleMenu
editor?: Editor;
shouldShow: (props: { // 悬浮菜单显示的条件
editor: Editor;
state: EditorState;
node?: HTMLElement;
view?: EditorView;
state?: EditorState;
oldState?: EditorState;
from?: number;
to?: number;
@ -275,17 +275,17 @@ interface BubbleMenuProps {
}
// 悬浮菜单
export interface NodeBubbleMenu extends BubbleMenuProps {
export interface NodeBubbleMenuType extends BubbleMenuProps {
component?: Component; // 不使用默认的样式,与 items 二选一
items?: BubbleItem[]; // 悬浮菜单子项,使用默认的形式进行,与 items 二选一
items?: BubbleItemType[]; // 悬浮菜单子项,使用默认的形式进行,与 items 二选一
}
// 悬浮菜单子项
export interface BubbleItem {
export interface BubbleItemType {
priority: number; // 优先级,数字越小优先级越大,越靠前
component?: Component; // 完全自定义子项样式
props: {
isActive: ({ editor }: { editor: Editor }) => boolean; // 当前功能是否已经处于活动状态
props?: { // 子项属性,可选
isActive?: ({ editor }: { editor: Editor }) => boolean; // 当前功能是否已经处于活动状态
visible?: ({ editor }: { editor: Editor }) => boolean; // 是否显示当前子项
icon?: Component; // 图标
iconStyle?: string; // 图标自定义样式
@ -363,16 +363,16 @@ addOptions() {
```ts
// 拖拽扩展
getDraggable?: ({ editor }: { editor: Editor }) => DraggableItem | boolean;
getDraggable?: ({ editor }: { editor: Editor }) => DraggableItemType | boolean;
export interface DraggableItem {
export interface DraggableItemType {
getRenderContainer?: ({ // 拖拽按钮计算偏移位置的基准 DOM
dom,
view,
}: {
dom: HTMLElement;
view: EditorView;
}) => DragSelectionNode;
}) => DragSelectionNodeType;
handleDrop?: ({ // 完成拖拽功能之后的处理。返回 true 则会阻止拖拽的发生
view,
event,
@ -391,14 +391,14 @@ export interface DraggableItem {
allowPropagationDownward?: boolean; // 是否允许拖拽事件向内部传播,
}
export interface DragSelectionNode {
export interface DragSelectionNodeType {
$pos?: ResolvedPos;
node?: Node;
el: HTMLElement;
nodeOffset?: number;
dragDomOffset?: {
x: number;
y: number;
x?: number;
y?: number;
};
}
```

View File

@ -3,7 +3,7 @@ import BubbleItem from "@/components/bubble/BubbleItem.vue";
import BubbleMenu from "@/components/bubble/BubbleMenu.vue";
import type { EditorState, EditorView } from "@/tiptap/pm";
import type { AnyExtension, Editor } from "@/tiptap/vue-3";
import type { NodeBubbleMenu } from "@/types";
import type { NodeBubbleMenuType } from "@/types";
import type { PropType } from "vue";
const props = defineProps({
@ -25,7 +25,7 @@ const getBubbleMenuFromExtensions = () => {
const nodeBubbleMenu = getBubbleMenu({
editor: props.editor,
}) as NodeBubbleMenu;
}) as NodeBubbleMenuType;
if (nodeBubbleMenu.items) {
nodeBubbleMenu.items = nodeBubbleMenu.items.sort(
@ -35,7 +35,7 @@ const getBubbleMenuFromExtensions = () => {
return nodeBubbleMenu;
})
.filter(Boolean) as NodeBubbleMenu[];
.filter(Boolean) as NodeBubbleMenuType[];
};
const shouldShow = (
@ -48,7 +48,7 @@ const shouldShow = (
from?: number;
to?: number;
},
bubbleMenu: NodeBubbleMenu
bubbleMenu: NodeBubbleMenuType
) => {
if (!props.editor.isEditable) {
return false;

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { Editor, type AnyExtension } from "@/tiptap/vue-3";
import type { ToolbarItem, ToolboxItem } from "@/types";
import type { ToolbarItemType, ToolboxItemType } from "@/types";
import { Dropdown as VDropdown, Menu as VMenu } from "floating-vue";
import MdiPlusCircle from "~icons/mdi/plus-circle";
@ -14,7 +14,7 @@ const props = defineProps({
function getToolbarItemsFromExtensions() {
const extensionManager = props.editor?.extensionManager;
return extensionManager.extensions
.reduce((acc: ToolbarItem[], extension: AnyExtension) => {
.reduce((acc: ToolbarItemType[], extension: AnyExtension) => {
const { getToolbarItems } = extension.options;
if (!getToolbarItems) {
@ -37,7 +37,7 @@ function getToolbarItemsFromExtensions() {
function getToolboxItemsFromExtensions() {
const extensionManager = props.editor?.extensionManager;
return extensionManager.extensions
.reduce((acc: ToolboxItem[], extension: AnyExtension) => {
.reduce((acc: ToolboxItemType[], extension: AnyExtension) => {
const { getToolboxItems } = extension.options;
if (!getToolboxItems) {

View File

@ -11,5 +11,5 @@ export * from "./toolbar";
export * from "./toolbox";
export { default as RichTextEditor } from "./Editor.vue";
export * from "./EditorBubbleMenu.vue";
export * from "./EditorHeader.vue";
export { default as EditorBubbleMenu } from "./EditorBubbleMenu.vue";
export { default as EditorHeader } from "./EditorHeader.vue";

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { ToolbarItem } from "@/types";
import type { ToolbarItemType } from "@/types";
import { vTooltip } from "floating-vue";
import type { Component } from "vue";
import MdiMenuDown from "~icons/mdi/menu-down";
@ -11,7 +11,7 @@ withDefaults(
title?: string;
action?: () => void;
icon?: Component;
children?: ToolbarItem[];
children?: ToolbarItemType[];
}>(),
{
isActive: false,

View File

@ -12,7 +12,7 @@ import {
nodeInputRule,
type Range,
} from "@/tiptap/vue-3";
import type { ExtensionOptions, NodeBubbleMenu } from "@/types";
import type { ExtensionOptions, NodeBubbleMenuType } from "@/types";
import { deleteNode } from "@/utils";
import { markRaw } from "vue";
import MdiLinkVariant from "~icons/mdi/link-variant";
@ -171,7 +171,7 @@ const Audio = Node.create<ExtensionOptions>({
},
};
},
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenu {
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenuType {
return {
pluginKey: "audioBubbleMenu",
shouldShow: ({ state }: { state: EditorState }) => {

View File

@ -1,17 +1,17 @@
<script lang="ts" setup>
import { i18n } from "@/locales";
import type { CommandMenuItem } from "@/types";
import type { CommandMenuItemType } from "@/types";
import scrollIntoView from "scroll-into-view-if-needed";
import { ref, watch, type PropType } from "vue";
const props = defineProps({
items: {
type: Array as PropType<CommandMenuItem[]>,
type: Array as PropType<CommandMenuItemType[]>,
required: true,
},
command: {
type: Function as PropType<(item: CommandMenuItem) => void>,
type: Function as PropType<(item: CommandMenuItemType) => void>,
required: true,
},
});

View File

@ -5,7 +5,7 @@ import {
type Editor,
type Range,
} from "@/tiptap/vue-3";
import type { CommandMenuItem } from "@/types";
import type { CommandMenuItemType } from "@/types";
import Suggestion from "@tiptap/suggestion";
import type { Instance } from "tippy.js";
import tippy from "tippy.js";
@ -31,7 +31,7 @@ export default Extension.create({
}: {
editor: Editor;
range: Range;
props: CommandMenuItem;
props: CommandMenuItemType;
}) => {
props.command({ editor, range });
},
@ -104,7 +104,7 @@ export default Extension.create({
function getToolbarItemsFromExtensions(editor: Editor) {
const extensionManager = editor?.extensionManager;
return extensionManager.extensions
.reduce((acc: CommandMenuItem[], extension: AnyExtension) => {
.reduce((acc: CommandMenuItemType[], extension: AnyExtension) => {
const { getCommandMenuItems } = extension.options;
if (!getCommandMenuItems) {

View File

@ -15,7 +15,7 @@ import {
__serializeForClipboard as serializeForClipboard,
} from "@/tiptap/pm";
import { Editor, Extension } from "@/tiptap/vue-3";
import type { DraggableItem, ExtensionOptions } from "@/types";
import type { DraggableItemType, ExtensionOptions } from "@/types";
// https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API
// https://github.com/ueberdosis/tiptap/blob/7832b96afbfc58574785043259230801e179310f/demos/src/Experiments/GlobalDragHandle/Vue/DragHandle.js
@ -28,7 +28,7 @@ export interface ActiveNode {
domOffsetTop: number;
}
let draggableItem: DraggableItem | boolean | undefined = undefined;
let draggableItem: DraggableItemType | boolean | undefined = undefined;
let draggableHandleDom: HTMLElement | null = null;
let currEditorView: EditorView;
let activeNode: ActiveNode | null = null;
@ -238,7 +238,7 @@ const getExtensionDraggableItem = (editor: Editor, node: Node) => {
**/
const getRenderContainer = (
view: EditorView,
draggableItem: DraggableItem | undefined,
draggableItem: DraggableItemType | undefined,
dom: HTMLElement
): ActiveNode => {
const renderContainer = draggableItem?.getRenderContainer?.({ dom, view });
@ -291,7 +291,7 @@ const getDraggableItem = ({
dom: HTMLElement;
event?: any;
depth?: number;
}): DraggableItem | boolean | undefined => {
}): DraggableItemType | boolean | undefined => {
const parentNode = findParentNodeByDepth(view, dom, depth);
if (!parentNode) {
return;

View File

@ -13,7 +13,7 @@ import {
nodePasteRule,
type Range,
} from "@/tiptap/vue-3";
import type { ExtensionOptions, NodeBubbleMenu } from "@/types";
import type { ExtensionOptions, NodeBubbleMenuType } from "@/types";
import { deleteNode } from "@/utils";
import { isAllowedUri } from "@/utils/is-allowed-uri";
import { markRaw } from "vue";
@ -260,7 +260,7 @@ const Iframe = Node.create<ExtensionOptions>({
},
];
},
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenu {
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenuType {
return {
pluginKey: "iframeBubbleMenu",
shouldShow: ({ state }: { state: EditorState }) => {

View File

@ -9,7 +9,7 @@ import {
VueNodeViewRenderer,
type Editor,
} from "@/tiptap/vue-3";
import type { ExtensionOptions, NodeBubbleMenu } from "@/types";
import type { ExtensionOptions, NodeBubbleMenuType } from "@/types";
import { deleteNode } from "@/utils";
import type { ImageOptions } from "@tiptap/extension-image";
import TiptapImage from "@tiptap/extension-image";
@ -127,7 +127,7 @@ const Image = TiptapImage.extend<ExtensionOptions & ImageOptions>({
},
];
},
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenu {
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenuType {
return {
pluginKey: "imageBubbleMenu",
shouldShow: ({ state }: { state: EditorState }): boolean => {

View File

@ -10,7 +10,7 @@ import {
type Dispatch,
type Editor,
} from "@/tiptap";
import type { ExtensionOptions, ToolbarItem as TypeToolbarItem } from "@/types";
import type { ExtensionOptions, ToolbarItemType } from "@/types";
import { deleteNodeByPos } from "@/utils";
import { isListActive } from "@/utils/isListActive";
import { isEmpty } from "@/utils/isNodeEmpty";
@ -59,7 +59,7 @@ const Paragraph = TiptapParagraph.extend<ExtensionOptions & ParagraphOptions>({
allowPropagationDownward: true,
};
},
getToolbarItems({ editor }: { editor: Editor }): TypeToolbarItem {
getToolbarItems({ editor }: { editor: Editor }): ToolbarItemType {
return {
priority: 220,
component: markRaw(ToolbarItem),

View File

@ -17,7 +17,7 @@ import {
type Node as ProseMirrorNode,
type ViewMutationRecord,
} from "@/tiptap/pm";
import type { ExtensionOptions, NodeBubbleMenu } from "@/types";
import type { ExtensionOptions, NodeBubbleMenuType } from "@/types";
import TiptapTable, {
createColGroup,
type TableOptions,
@ -255,7 +255,7 @@ const Table = TiptapTable.extend<ExtensionOptions & TableOptions>({
},
};
},
getBubbleMenu({ editor }): NodeBubbleMenu {
getBubbleMenu({ editor }): NodeBubbleMenuType {
return {
pluginKey: "tableBubbleMenu",
shouldShow: ({ state }: { state: EditorState }): boolean => {

View File

@ -5,7 +5,7 @@ import { RangeSelection } from "@/extensions/range-selection";
import { i18n } from "@/locales";
import type { EditorState } from "@/tiptap/pm";
import { isActive, isTextSelection } from "@/tiptap/vue-3";
import type { ExtensionOptions, NodeBubbleMenu } from "@/types";
import type { ExtensionOptions, NodeBubbleMenuType } from "@/types";
import { Text as TiptapText } from "@tiptap/extension-text";
import { markRaw } from "vue";
import MdiCodeTags from "~icons/mdi/code-tags";
@ -32,7 +32,7 @@ const Text = TiptapText.extend<ExtensionOptions>({
addOptions() {
return {
...this.parent?.(),
getBubbleMenu(): NodeBubbleMenu {
getBubbleMenu(): NodeBubbleMenuType {
return {
pluginKey: "textBubbleMenu",
shouldShow: ({ state, from, to }) => {

View File

@ -12,7 +12,7 @@ import {
nodeInputRule,
type Range,
} from "@/tiptap/vue-3";
import type { ExtensionOptions, NodeBubbleMenu } from "@/types";
import type { ExtensionOptions, NodeBubbleMenuType } from "@/types";
import { deleteNode } from "@/utils";
import { markRaw } from "vue";
import MdiCogPlay from "~icons/mdi/cog-play";
@ -215,7 +215,7 @@ const Video = Node.create<ExtensionOptions>({
},
];
},
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenu {
getBubbleMenu({ editor }: { editor: Editor }): NodeBubbleMenuType {
return {
pluginKey: "videoBubbleMenu",
shouldShow: ({ state }: { state: EditorState }) => {

View File

@ -16,5 +16,5 @@ export default plugin;
export * from "./components";
export * from "./extensions";
export * from "./tiptap";
export * from "./types";
export * from "./utils";
// TODO: export * from "./types";

View File

@ -8,7 +8,7 @@ import type {
} from "@/tiptap/pm";
import type { Editor, Range } from "@/tiptap/vue-3";
import type { Component } from "vue";
export interface ToolbarItem {
export interface ToolbarItemType {
priority: number;
component: Component;
props: {
@ -19,7 +19,7 @@ export interface ToolbarItem {
title?: string;
action?: () => void;
};
children?: ToolbarItem[];
children?: ToolbarItemType[];
}
interface BubbleMenuProps {
@ -39,12 +39,12 @@ interface BubbleMenuProps {
defaultAnimation?: boolean;
}
export interface NodeBubbleMenu extends BubbleMenuProps {
export interface NodeBubbleMenuType extends BubbleMenuProps {
component?: Component;
items?: BubbleItem[];
items?: BubbleItemType[];
}
export interface BubbleItem {
export interface BubbleItemType {
priority: number;
component?: Component;
props?: {
@ -56,7 +56,7 @@ export interface BubbleItem {
action?: ({ editor }: { editor: Editor }) => Component | void;
};
}
export interface ToolboxItem {
export interface ToolboxItemType {
priority: number;
component: Component;
props: {
@ -73,22 +73,26 @@ export interface ExtensionOptions {
editor,
}: {
editor: Editor;
}) => ToolbarItem | ToolbarItem[];
}) => ToolbarItemType | ToolbarItemType[];
getCommandMenuItems?: () => CommandMenuItem | CommandMenuItem[];
getCommandMenuItems?: () => CommandMenuItemType | CommandMenuItemType[];
getBubbleMenu?: ({ editor }: { editor: Editor }) => NodeBubbleMenu;
getBubbleMenu?: ({ editor }: { editor: Editor }) => NodeBubbleMenuType;
getToolboxItems?: ({
editor,
}: {
editor: Editor;
}) => ToolboxItem | ToolboxItem[];
}) => ToolboxItemType | ToolboxItemType[];
getDraggable?: ({ editor }: { editor: Editor }) => DraggableItem | boolean;
getDraggable?: ({
editor,
}: {
editor: Editor;
}) => DraggableItemType | boolean;
}
export interface CommandMenuItem {
export interface CommandMenuItemType {
priority: number;
icon: Component;
title: string;
@ -96,7 +100,7 @@ export interface CommandMenuItem {
command: ({ editor, range }: { editor: Editor; range: Range }) => void;
}
export interface DragSelectionNode {
export interface DragSelectionNodeType {
$pos?: ResolvedPos;
node?: Node;
el: HTMLElement;
@ -107,14 +111,14 @@ export interface DragSelectionNode {
};
}
export interface DraggableItem {
export interface DraggableItemType {
getRenderContainer?: ({
dom,
view,
}: {
dom: HTMLElement;
view: EditorView;
}) => DragSelectionNode;
}) => DragSelectionNodeType;
handleDrop?: ({
view,
event,