feat: add image.bubbleMenuItems config and optimize AbstractBubbleMenu.ts
This commit is contained in:
parent
c37a616b94
commit
8d4c2438d3
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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]
|
||||
)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -87,6 +87,7 @@ export type AiEditorOptions = {
|
|||
uploaderEvent?: UploaderEvent,
|
||||
defaultSize?: number,
|
||||
allowBase64?: boolean,
|
||||
bubbleMenuItems?: (string)[],
|
||||
},
|
||||
video?: {
|
||||
customMenuInvoke?: (editor: AiEditor) => void;
|
||||
|
|
24
src/main.ts
24
src/main.ts
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue