feat: add image.bubbleMenuItems config and optimize AbstractBubbleMenu.ts

This commit is contained in:
Michael Yang 2024-06-20 10:20:36 +08:00
parent c37a616b94
commit 8d4c2438d3
14 changed files with 138 additions and 86 deletions

View File

@ -2,6 +2,7 @@ import {AiEditorOptions, AiEditorEvent, InnerEditor} from "../core/AiEditor.ts";
import {Editor, EditorEvents} from "@tiptap/core";
import tippy, {Instance} from "tippy.js";
import {BubbleMenuItem} from "./bubbles/types.ts";
import {MenuRecord} from "./bubbles/items/MenuRecord.ts";
export abstract class AbstractBubbleMenu extends HTMLElement implements AiEditorEvent {
@ -60,6 +61,17 @@ export abstract class AbstractBubbleMenu extends HTMLElement implements AiEditor
this.tippyInstance = value;
}
protected initItemsByOptions(allMenuItems: MenuRecord, optionItems?: (string)[]) {
if (optionItems && optionItems.length > 0) {
for (let key of optionItems) {
const linkMenuItem = allMenuItems.getItem(key);
if (linkMenuItem) this.items.push(linkMenuItem);
}
} else {
this.items = allMenuItems.getAllItem()
}
}
onCreate(createEvent: EditorEvents['create'], _: AiEditorOptions) {
this.editor = createEvent.editor
}

View File

@ -1,47 +1,16 @@
import {AbstractBubbleMenu} from "../AbstractBubbleMenu.ts";
import {EditorEvents} from "@tiptap/core";
import {t} from "i18next";
import {BubbleMenuItem} from "./types.ts";
import {AiEditorOptions} from "../../core/AiEditor.ts";
import {AllImageMenuItems} from "./items/image/AllImageMenuItems.ts";
export class ImageBubbleMenu extends AbstractBubbleMenu {
constructor() {
super();
this.items = [
{
id: "left",
title: t("align-left"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 4H21V6H3V4ZM3 19H17V21H3V19ZM3 14H21V16H3V14ZM3 9H17V11H3V9Z\"></path></svg>",
},
{
id: "center",
title: t("align-center"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 4H21V6H3V4ZM5 19H19V21H5V19ZM3 14H21V16H3V14ZM5 9H19V11H5V9Z\"></path></svg>",
},
{
id: "right",
title: t("align-right"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 4H21V6H3V4ZM7 19H21V21H7V19ZM3 14H21V16H3V14ZM7 9H21V11H7V9Z\"></path></svg>"
},
{
id: "delete",
title: t("delete"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z\"></path></svg>"
}
]
}
onItemClick(item: BubbleMenuItem): void {
if (item.id != "delete") {
const attrs = this.editor?.getAttributes("image")!;
attrs.align = item.id;
this.editor?.chain().setImage(attrs as any).run();
} else {
this.editor?.commands.deleteSelection();
}
}
onTransaction(_: EditorEvents["transaction"]): void {
onCreate(event: EditorEvents["create"], options: AiEditorOptions) {
super.onCreate(event, options);
this.initItemsByOptions(AllImageMenuItems, options?.image?.bubbleMenuItems);
}
}

View File

@ -9,18 +9,9 @@ export class LinkBubbleMenu extends AbstractBubbleMenu {
super();
}
onCreate(props: EditorEvents["create"], options: AiEditorOptions) {
super.onCreate(props, options);
if (options?.link?.bubbleMenuItems && options?.link?.bubbleMenuItems.length > 0) {
for (let key of options.link.bubbleMenuItems) {
const linkMenuItem = AllLinkMenuItems[key];
if (linkMenuItem) this.items.push(linkMenuItem);
}
} else {
this.items = [
...Object.values(AllLinkMenuItems)
]
}
onCreate(event: EditorEvents["create"], options: AiEditorOptions) {
super.onCreate(event, options);
this.initItemsByOptions(AllLinkMenuItems, options?.link?.bubbleMenuItems);
}
}

View File

@ -11,20 +11,13 @@ export class TextSelectionBubbleMenu extends AbstractBubbleMenu {
super();
}
onCreate(props: EditorEvents["create"], options: AiEditorOptions) {
super.onCreate(props, options);
if (options?.textSelectionBubbleMenu?.items && options?.textSelectionBubbleMenu?.items.length > 0) {
for (let key of options.textSelectionBubbleMenu.items) {
const linkMenuItem = AllSelectionMenuItems[key];
if (linkMenuItem) this.items.push(linkMenuItem);
}
} else {
this.items = [
...Object.values(AllSelectionMenuItems)
]
if (options.ai?.bubblePanelEnable === false) {
removeIf(this.items, (item: BubbleMenuItem) => item.id === "ai")
}
onCreate(event: EditorEvents["create"], options: AiEditorOptions) {
super.onCreate(event, options);
this.initItemsByOptions(AllSelectionMenuItems, options?.textSelectionBubbleMenu?.items);
if (options.ai?.bubblePanelEnable === false) {
removeIf(this.items, (item: BubbleMenuItem) => item.id === "ai")
}
}
}

View File

@ -0,0 +1,24 @@
import {BubbleMenuItem} from "../types.ts";
export class MenuRecord {
private record: Record<string, BubbleMenuItem> = {};
constructor(menuItems: BubbleMenuItem[]) {
for (let menuItem of menuItems) {
this.push(menuItem);
}
}
public push(menuItem: BubbleMenuItem) {
this.record[menuItem.id.toLowerCase()] = menuItem;
}
public getItem(key: string) {
return this.record[key.toLowerCase()];
}
public getAllItem() {
return Object.values(this.record);
}
}

View File

@ -0,0 +1,11 @@
import {BubbleMenuItem} from "../../types.ts";
import {t} from "i18next";
export const Delete = {
id: "delete",
title: t("delete"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z\"></path></svg>",
onClick: (editor) => {
editor.commands.deleteSelection();
}
} as BubbleMenuItem;

View File

@ -0,0 +1,13 @@
import {BubbleMenuItem} from "../../types.ts";
import {t} from "i18next";
export const AlignCenter = {
id: "AlignCenter",
title: t("align-center"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 4H21V6H3V4ZM5 19H19V21H5V19ZM3 14H21V16H3V14ZM5 9H19V11H5V9Z\"></path></svg>",
onClick: (editor) => {
const attrs = editor?.getAttributes("image")!;
attrs.align = "center";
editor?.chain().setImage(attrs as any).run();
}
} as BubbleMenuItem;

View File

@ -0,0 +1,13 @@
import {BubbleMenuItem} from "../../types.ts";
import {t} from "i18next";
export const AlignLeft = {
id: "AlignLeft",
title: t("align-left"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 4H21V6H3V4ZM3 19H17V21H3V19ZM3 14H21V16H3V14ZM3 9H17V11H3V9Z\"></path></svg>",
onClick: (editor) => {
const attrs = editor?.getAttributes("image")!;
attrs.align = "left";
editor?.chain().setImage(attrs as any).run();
}
} as BubbleMenuItem;

View File

@ -0,0 +1,13 @@
import {BubbleMenuItem} from "../../types.ts";
import {t} from "i18next";
export const AlignRight = {
id: "AlignRight",
title: t("align-right"),
content: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M3 4H21V6H3V4ZM7 19H21V21H7V19ZM3 14H21V16H3V14ZM7 9H21V11H7V9Z\"></path></svg>",
onClick: (editor) => {
const attrs = editor?.getAttributes("image")!;
attrs.align = "right";
editor?.chain().setImage(attrs as any).run();
}
} as BubbleMenuItem;

View File

@ -0,0 +1,9 @@
import {AlignLeft} from "./AlignLeft.ts";
import {AlignCenter} from "./AlignCenter.ts";
import {AlignRight} from "./AlignRight.ts";
import {Delete} from "../commons/Delete.ts";
import {MenuRecord} from "../MenuRecord.ts";
export const AllImageMenuItems = new MenuRecord(
[AlignLeft, AlignCenter, AlignRight, Delete]
)

View File

@ -1,13 +1,9 @@
import {Edit} from "./Edit.ts";
import {UnLink} from "./UnLink.ts";
import {Visit} from "./Visit.ts";
import {BubbleMenuItem} from "../../types.ts";
import {MenuRecord} from "../MenuRecord.ts";
const push = (r: Record<string, any>, name: string, value: any) => {
r[name] = value;
}
export const AllLinkMenuItems = {} as Record<string, BubbleMenuItem>
export const AllLinkMenuItems = new MenuRecord(
[Edit, UnLink, Visit]
)
push(AllLinkMenuItems, Edit.id, Edit)
push(AllLinkMenuItems, UnLink.id, UnLink)
push(AllLinkMenuItems, Visit.id, Visit)

View File

@ -1,19 +1,12 @@
import {BubbleMenuItem} from "../../types.ts";
import {AI} from "./AI.ts";
import {Bold} from "./Bold.ts";
import {Underline} from "./Underline.ts";
import {Code} from "./Code.ts";
import {Strike} from "./Strike.ts";
import {Italic} from "./Italic.ts";
import {MenuRecord} from "../MenuRecord.ts";
const push = (r: Record<string, any>, name: string, value: any) => {
r[name] = value;
}
export const AllSelectionMenuItems = {} as Record<string, BubbleMenuItem>
export const AllSelectionMenuItems = new MenuRecord(
[AI, Bold, Italic, Underline, Strike, Code]
)
push(AllSelectionMenuItems, AI.id, AI)
push(AllSelectionMenuItems, Bold.id, Bold)
push(AllSelectionMenuItems, Italic.id, Italic)
push(AllSelectionMenuItems, Underline.id, Underline)
push(AllSelectionMenuItems, Strike.id, Strike)
push(AllSelectionMenuItems, Code.id, Code)

View File

@ -87,6 +87,7 @@ export type AiEditorOptions = {
uploaderEvent?: UploaderEvent,
defaultSize?: number,
allowBase64?: boolean,
bubbleMenuItems?: (string)[],
},
video?: {
customMenuInvoke?: (editor: AiEditor) => void;

View File

@ -1,4 +1,4 @@
import { AiEditor } from "./core/AiEditor.ts";
import {AiEditor} from "./core/AiEditor.ts";
// import { config } from "./spark.ts";
// import {OpenaiModelConfig} from "./ai/openai/OpenaiModelConfig.ts";
// @ts-ignore
@ -9,6 +9,20 @@ window.aiEditor = new AiEditor({
// draggable:false,
// editable:false,
content: 'AiEditor 是一个面向 AI 的下一代富文本编辑器。<p> <strong>提示:</strong> <br/>1、输入 空格 + "/" 可以快速弹出 AI 菜单 <br/> 2、输入 空格 + "@" 可以提及某人</p> ',
textSelectionBubbleMenu: {
// enable:false
//[AI, Bold, Italic, Underline, Strike, Code]
items: ["ai", "Bold", "Italic", "Underline", "Strike", "code"],
},
image: {
//[AlignLeft, AlignCenter, AlignRight, Delete]
bubbleMenuItems: ["AlignLeft", "AlignCenter", "AlignRight", "delete"]
},
link: {
//[Edit, UnLink, Visit]
bubbleMenuItems: ["Edit", "UnLink", "visit"],
},
// onSave:()=>{
// alert("保存")
// return true;
@ -21,10 +35,10 @@ window.aiEditor = new AiEditor({
// spark: {
// ...config
// },
openai:{
endpoint:"https://api.moonshot.cn",
model:"moonshot-v1-8k",
apiKey:"sk-alQ96zb******"
openai: {
endpoint: "https://api.moonshot.cn",
model: "moonshot-v1-8k",
apiKey: "sk-alQ96zb******"
}
},
// bubblePanelEnable:false,