diff --git a/src/components/index.ts b/src/components/index.ts index b05c9c6..a498da9 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -55,6 +55,9 @@ import { Link } from "./menus/toolbar/insert/Link.ts"; import { Image } from "./menus/toolbar/insert/Image.ts"; import { Video } from "./menus/toolbar/insert/Video.ts"; import { Audio } from "./menus/toolbar/insert/Audio.ts"; +import { HardBreak } from "./menus/toolbar/insert/HardBreak.ts"; +import { Emoji } from "./menus/toolbar/insert/Emoji.ts"; +import { Symbol } from "./menus/toolbar/insert/Symbol.ts"; // 注册组件 defineCustomElement('uai-editor-header', Header); @@ -111,3 +114,6 @@ defineCustomElement('uai-editor-insert-menu-link', Link); defineCustomElement('uai-editor-insert-menu-image', Image); defineCustomElement('uai-editor-insert-menu-video', Video); defineCustomElement('uai-editor-insert-menu-audio', Audio); +defineCustomElement('uai-editor-insert-menu-hard-break', HardBreak); +defineCustomElement('uai-editor-insert-menu-emoji', Emoji); +defineCustomElement('uai-editor-insert-menu-symbol', Symbol); diff --git a/src/components/menus/toolbar/Classic.ts b/src/components/menus/toolbar/Classic.ts index 472ffd9..4bec4be 100644 --- a/src/components/menus/toolbar/Classic.ts +++ b/src/components/menus/toolbar/Classic.ts @@ -50,6 +50,9 @@ import { Link } from "./insert/Link.ts"; import { Image } from "./insert/Image.ts"; import { Video } from "./insert/Video.ts"; import { Audio } from "./insert/Audio.ts"; +import { HardBreak } from "./insert/HardBreak.ts"; +import { Emoji } from "./insert/Emoji.ts"; +import { Symbol } from "./insert/Symbol.ts"; /** * 传统菜单栏 @@ -111,6 +114,9 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { insertMenuImage!: Image; insertMenuVideo!: Video; insertMenuAudio!: Audio; + insertMenuHardBreak!: HardBreak; + insertMenuEmoji!: Emoji; + insertMenuSymbol!: Symbol; constructor(defaultToolbarMenus: Record[]) { super(); @@ -292,6 +298,15 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { this.insertMenuAudio = new Audio({ menuType: "button", enable: true, header: "classic", hideText: false }); this.eventComponents.push(this.insertMenuAudio); + + this.insertMenuHardBreak = new HardBreak({ menuType: "button", enable: true, header: "classic", hideText: false }); + this.eventComponents.push(this.insertMenuHardBreak); + + this.insertMenuEmoji = new Emoji({ menuType: "popup", enable: true, header: "classic", hideText: false }); + this.eventComponents.push(this.insertMenuEmoji); + + this.insertMenuSymbol = new Symbol({ menuType: "popup", enable: true, header: "classic", hideText: false }); + this.eventComponents.push(this.insertMenuSymbol); } /** * 创建基础菜单 @@ -373,5 +388,12 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { group1.appendChild(this.insertMenuImage); group1.appendChild(this.insertMenuVideo); group1.appendChild(this.insertMenuAudio); + + const group2 = document.createElement("div"); + group2.classList.add("uai-classic-virtual-group"); + this.classicMenuInsertGroup.appendChild(group2); + group2.appendChild(this.insertMenuHardBreak); + group2.appendChild(this.insertMenuEmoji); + group2.appendChild(this.insertMenuSymbol); } } \ No newline at end of file diff --git a/src/components/menus/toolbar/Ribbon.ts b/src/components/menus/toolbar/Ribbon.ts index 39780e4..407aecc 100644 --- a/src/components/menus/toolbar/Ribbon.ts +++ b/src/components/menus/toolbar/Ribbon.ts @@ -52,6 +52,9 @@ import { Link } from "./insert/Link.ts"; import { Image } from "./insert/Image.ts"; import { Video } from "./insert/Video.ts"; import { Audio } from "./insert/Audio.ts"; +import { HardBreak } from "./insert/HardBreak.ts"; +import { Emoji } from "./insert/Emoji.ts"; +import { Symbol } from "./insert/Symbol.ts"; /** * 经典菜单栏 @@ -114,6 +117,9 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { insertMenuImage!: Image; insertMenuVideo!: Video; insertMenuAudio!: Audio; + insertMenuHardBreak!: HardBreak; + insertMenuEmoji!: Emoji; + insertMenuSymbol!: Symbol; constructor(defaultToolbarMenus: Record[]) { super(); @@ -315,6 +321,15 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { this.insertMenuAudio = new Audio({ menuType: "button", enable: true, huge: true }); this.eventComponents.push(this.insertMenuAudio); + + this.insertMenuHardBreak = new HardBreak({ menuType: "button", enable: true, huge: true }); + this.eventComponents.push(this.insertMenuHardBreak); + + this.insertMenuEmoji = new Emoji({ menuType: "popup", enable: true, huge: true, hideText: false }); + this.eventComponents.push(this.insertMenuEmoji); + + this.insertMenuSymbol = new Symbol({ menuType: "popup", enable: true, huge: true, hideText: false }); + this.eventComponents.push(this.insertMenuSymbol); } /** @@ -470,5 +485,12 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { group1.appendChild(this.insertMenuImage); group1.appendChild(this.insertMenuVideo); group1.appendChild(this.insertMenuAudio); + + const group2 = document.createElement("div"); + group2.classList.add("uai-ribbon-virtual-group"); + this.ribbonMenuInsertGroup.appendChild(group2); + group2.appendChild(this.insertMenuHardBreak); + group2.appendChild(this.insertMenuEmoji); + group2.appendChild(this.insertMenuSymbol); } } \ No newline at end of file diff --git a/src/components/menus/toolbar/insert/Emoji.ts b/src/components/menus/toolbar/insert/Emoji.ts new file mode 100644 index 0000000..d8a38d3 --- /dev/null +++ b/src/components/menus/toolbar/insert/Emoji.ts @@ -0,0 +1,113 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts"; +import icon from "../../../../assets/icons/emoji.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; +import tippy, { Instance, Props } from "tippy.js"; + +/** + * 插入菜单:插入表情 + */ +export class Emoji extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "popup", + enable: true, + icon: icon, + hideText: true, + text: t('insert.emoji'), + tooltip: t('insert.emoji'), + } + + // 功能按钮 + menuButton: MenuButton; + // 选项提示实例 + tippyInstance!: Instance; + + constructor(options: MenuButtonOptions) { + super(); + + // 初始化功能按钮选项 + this.menuButtonOptions = { ...this.menuButtonOptions, ...options }; + + // 创建功能按钮 + this.menuButton = new MenuButton(this.menuButtonOptions); + } + + /** + * 定义创建方法 + * @param event + * @param options + */ + onCreate(event: EditorEvents["create"], options: UAIEditorOptions) { + this.menuButton.onCreate(event, options); + this.appendChild(this.menuButton); + + // 创建表情选择提示框 + if (this.menuButtonOptions.enable && this.menuButton.menuButtonArrow) { + this.tippyInstance = tippy(this.menuButton.menuButton, { + content: this.createContainer(event, options), + appendTo: 'parent', + placement: 'bottom', + trigger: 'click', + interactive: true, + arrow: false, + onShow: () => { + this.menuButton.tippyInstance?.disable(); + }, + onHidden: () => { + this.menuButton.tippyInstance?.enable(); + }, + }); + } + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { + this.menuButton.onTransaction(event, options); + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } + + /** + * 创建表情选择容器 + * @param event + * @param options + * @returns + */ + createContainer(event: EditorEvents["create"], options: UAIEditorOptions) { + const container = document.createElement("div"); + container.classList.add("uai-emojis-container"); + // 添加所有可选表情 + options.dicts?.emojis?.forEach(emoji => { + const groupTitle = document.createElement("div"); + groupTitle.classList.add("uai-emojis-group-title"); + groupTitle.innerHTML = emoji.label; + container.appendChild(groupTitle); + const groupContainer = document.createElement("div"); + groupContainer.classList.add("uai-emojis-group-container"); + container.appendChild(groupContainer); + emoji.value.toString().split(" ").forEach(item => { + const it = document.createElement("div"); + it.classList.add("uai-emojis-group-item"); + it.innerHTML = item; + groupContainer.appendChild(it); + it.addEventListener("click", () => { + event.editor.chain().focus().insertContent(item).run(); + }); + }) + }); + return container; + } +} + diff --git a/src/components/menus/toolbar/insert/HardBreak.ts b/src/components/menus/toolbar/insert/HardBreak.ts new file mode 100644 index 0000000..da0cdd5 --- /dev/null +++ b/src/components/menus/toolbar/insert/HardBreak.ts @@ -0,0 +1,72 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts"; +import icon from "../../../../assets/icons/hard-break.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 插入菜单:插入换行 + */ +export class HardBreak extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: false, + text: t('insert.hardBreak'), + tooltip: t('insert.hardBreak'), + } + + // 功能按钮 + menuButton: MenuButton; + + constructor(options: MenuButtonOptions) { + super(); + + // 初始化功能按钮选项 + this.menuButtonOptions = { ...this.menuButtonOptions, ...options }; + + // 创建功能按钮 + this.menuButton = new MenuButton(this.menuButtonOptions); + } + + /** + * 定义创建方法 + * @param event + * @param options + */ + onCreate(event: EditorEvents["create"], options: UAIEditorOptions) { + this.menuButton.onCreate(event, options); + this.appendChild(this.menuButton); + + // 定义按钮点击事件,插入换行 + this.addEventListener("click", () => { + if (this.menuButtonOptions.enable) { + event.editor.chain().focus().setHardBreak().run(); + } + }) + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { + this.menuButton.onTransaction(event, options); + if (this.menuButton.menuButton) { + var disable = event.editor.isEditable; + this.onEditableChange(disable); + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/components/menus/toolbar/insert/Symbol.ts b/src/components/menus/toolbar/insert/Symbol.ts new file mode 100644 index 0000000..ed41678 --- /dev/null +++ b/src/components/menus/toolbar/insert/Symbol.ts @@ -0,0 +1,113 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts"; +import icon from "../../../../assets/icons/symbol.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; +import tippy, { Instance, Props } from "tippy.js"; + +/** + * 插入菜单:插入符号 + */ +export class Symbol extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "popup", + enable: true, + icon: icon, + hideText: true, + text: t('insert.symbol'), + tooltip: t('insert.symbol'), + } + + // 功能按钮 + menuButton: MenuButton; + // 选项提示实例 + tippyInstance!: Instance; + + constructor(options: MenuButtonOptions) { + super(); + + // 初始化功能按钮选项 + this.menuButtonOptions = { ...this.menuButtonOptions, ...options }; + + // 创建功能按钮 + this.menuButton = new MenuButton(this.menuButtonOptions); + } + + /** + * 定义创建方法 + * @param event + * @param options + */ + onCreate(event: EditorEvents["create"], options: UAIEditorOptions) { + this.menuButton.onCreate(event, options); + this.appendChild(this.menuButton); + + // 初始化特殊符号选项提示框 + if (this.menuButtonOptions.enable && this.menuButton.menuButtonArrow) { + this.tippyInstance = tippy(this.menuButton.menuButton, { + content: this.createContainer(event, options), + appendTo: 'parent', + placement: 'bottom', + trigger: 'click', + interactive: true, + arrow: false, + onShow: () => { + this.menuButton.tippyInstance?.disable(); + }, + onHidden: () => { + this.menuButton.tippyInstance?.enable(); + }, + }); + } + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { + this.menuButton.onTransaction(event, options); + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } + + /** + * 创建特殊符号选择提示框 + * @param event + * @param options + * @returns + */ + createContainer(event: EditorEvents["create"], options: UAIEditorOptions) { + const container = document.createElement("div"); + container.classList.add("uai-emojis-container"); + // 添加所有可选的特殊符号 + options.dicts?.symbols?.forEach(symbol => { + const groupTitle = document.createElement("div"); + groupTitle.classList.add("uai-emojis-group-title"); + groupTitle.innerHTML = symbol.label; + container.appendChild(groupTitle); + const groupContainer = document.createElement("div"); + groupContainer.classList.add("uai-emojis-group-container"); + container.appendChild(groupContainer); + symbol.value.toString().split("").forEach(item => { + const it = document.createElement("div"); + it.classList.add("uai-emojis-group-item"); + it.innerHTML = item; + groupContainer.appendChild(it); + it.addEventListener("click", () => { + event.editor.chain().focus().insertContent(item).run(); + }); + }) + }); + return container; + } +} +