添加基础菜单

This commit is contained in:
wux_labs
2025-02-18 09:14:21 +08:00
parent e0cf01c819
commit 7c78fb9e1a
11 changed files with 1009 additions and 0 deletions
+59
View File
@@ -5,6 +5,12 @@ import { EditorEvents } from "@tiptap/core";
import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts";
import menuIcon from "../../../assets/icons/menu.svg";
import { ScrollableDiv } from "./ScrollableDiv";
import { Redo } from "./base/Redo";
import { Undo } from "./base/Undo";
import { FormatPainter } from "./base/FormatPainter";
import { ClearFormat } from "./base/ClearFormat";
/**
* 传统菜单栏
@@ -17,6 +23,16 @@ export class Classic extends HTMLElement implements UAIEditorEventListener {
classicMenu!: HTMLElement;
classicScrollableContainer!: HTMLElement;
// 基础菜单容器
classicMenuBaseScrollable!: ScrollableDiv;
classicMenuBaseGroup!: HTMLElement;
// 基础菜单
baseMenuUndo!: Undo;
baseMenuRedo!: Redo;
baseMenuFormatPainter!: FormatPainter;
baseMenuClearFormat!: ClearFormat;
constructor(defaultToolbarMenus: Record<string, any>[]) {
super();
this.defaultToolbarMenus = defaultToolbarMenus;
@@ -57,6 +73,17 @@ export class Classic extends HTMLElement implements UAIEditorEventListener {
option.value = menu.value;
selectMuenus.options.add(option);
});
// 添加事件,处理菜单分组切换、界面元素切换
selectMuenus.addEventListener("change", () => {
const menu = selectMuenus.selectedOptions[0].value;
this.classicMenuBaseScrollable.style.display = "none";
if (menu === "base") {
this.classicMenuBaseScrollable.style.display = "flex";
}
})
// 创建分组菜单
this.createBaseMenu(event, options);
this.classicMenuBaseScrollable.style.display = "flex";
}
/**
@@ -80,5 +107,37 @@ export class Classic extends HTMLElement implements UAIEditorEventListener {
* 初始化菜单
*/
initMenus() {
this.baseMenuUndo = new Undo({ menuType: "button", enable: true, hideText: false });
this.eventComponents.push(this.baseMenuUndo);
this.baseMenuRedo = new Redo({ menuType: "button", enable: true });
this.eventComponents.push(this.baseMenuRedo);
this.baseMenuFormatPainter = new FormatPainter({ menuType: "button", enable: true });
this.eventComponents.push(this.baseMenuFormatPainter);
this.baseMenuClearFormat = new ClearFormat({ menuType: "button", enable: true });
this.eventComponents.push(this.baseMenuClearFormat);
}
/**
* 创建基础菜单
* @param event
* @param options
*/
createBaseMenu(event: EditorEvents["create"], options: UAIEditorOptions) {
this.classicMenuBaseGroup = document.createElement("div");
this.classicMenuBaseGroup.style.display = "flex";
this.classicMenuBaseScrollable = new ScrollableDiv(this.classicMenuBaseGroup);
this.classicMenuBaseScrollable.style.display = "none";
this.classicMenu.appendChild(this.classicMenuBaseScrollable);
this.classicMenuBaseScrollable.onCreate(event, options);
const group1 = document.createElement("div");
group1.classList.add("uai-classic-virtual-group");
this.classicMenuBaseGroup.appendChild(group1);
group1.appendChild(this.baseMenuUndo);
group1.appendChild(this.baseMenuRedo);
group1.appendChild(this.baseMenuFormatPainter);
group1.appendChild(this.baseMenuClearFormat);
}
}
+67
View File
@@ -5,6 +5,13 @@ import { EditorEvents } from "@tiptap/core";
import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts";
import { t } from "i18next";
import { ScrollableDiv } from "./ScrollableDiv";
import { Redo } from "./base/Redo";
import { Undo } from "./base/Undo";
import { FormatPainter } from "./base/FormatPainter";
import { ClearFormat } from "./base/ClearFormat";
/**
* 经典菜单栏
@@ -18,6 +25,17 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener {
ribbonMenu!: HTMLElement;
ribbonScrollableContainer!: HTMLElement;
// 基础菜单容器
ribbonMenuBaseScrollable!: ScrollableDiv;
ribbonMenuBaseGroup!: HTMLElement;
// 基础菜单
baseMenuUndo!: Undo;
baseMenuRedo!: Redo;
baseMenuFormatPainter!: FormatPainter;
baseMenuClearFormat!: ClearFormat;
constructor(defaultToolbarMenus: Record<string, any>[]) {
super();
this.defaultToolbarMenus = defaultToolbarMenus;
@@ -54,6 +72,10 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener {
ribbonTabs.children[i].classList.remove("active");
}
tab.classList.add("active");
this.ribbonMenuBaseScrollable.style.display = "none";
if (menu.value === "base") {
this.ribbonMenuBaseScrollable.style.display = "flex";
}
})
ribbonTabs.appendChild(tab);
});
@@ -69,6 +91,10 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener {
const level = i + 1
this.headingOptions.push({ label: `${t('base.heading.text')}`.replace("{level}", `${level}`), desc: `h${level}`, value: level, element: document.createElement("div") })
}
// 创建分组菜单
this.createBaseMenu(event, options);
this.ribbonMenuBaseScrollable.style.display = "flex";
}
/**
@@ -104,5 +130,46 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener {
* 初始化菜单
*/
initMenus() {
this.baseMenuUndo = new Undo({ menuType: "button", enable: true });
this.eventComponents.push(this.baseMenuUndo);
this.baseMenuRedo = new Redo({ menuType: "button", enable: true });
this.eventComponents.push(this.baseMenuRedo);
this.baseMenuFormatPainter = new FormatPainter({ menuType: "button", enable: true });
this.eventComponents.push(this.baseMenuFormatPainter);
this.baseMenuClearFormat = new ClearFormat({ menuType: "button", enable: true });
this.eventComponents.push(this.baseMenuClearFormat);
}
/**
* 创建基础菜单
* @param event
* @param options
*/
createBaseMenu(event: EditorEvents["create"], _options: UAIEditorOptions) {
this.ribbonMenuBaseGroup = document.createElement("div");
this.ribbonMenuBaseGroup.classList.add("uai-ribbon-container");
this.ribbonMenuBaseGroup.style.display = "flex";
this.ribbonMenuBaseScrollable = new ScrollableDiv(this.ribbonMenuBaseGroup);
this.ribbonMenuBaseScrollable.style.display = "none";
this.ribbonScrollableContainer.appendChild(this.ribbonMenuBaseScrollable);
const group1 = document.createElement("div");
group1.classList.add("uai-ribbon-virtual-group");
this.ribbonMenuBaseGroup.appendChild(group1);
const group1row1 = document.createElement("div");
group1row1.classList.add("uai-ribbon-virtual-group-row");
group1.appendChild(group1row1);
group1row1.appendChild(this.baseMenuUndo);
group1row1.appendChild(this.baseMenuRedo);
const group1row2 = document.createElement("div");
group1row2.classList.add("uai-ribbon-virtual-group-row");
group1.appendChild(group1row2);
group1row2.appendChild(this.baseMenuFormatPainter);
group1row2.appendChild(this.baseMenuClearFormat);
}
}
@@ -0,0 +1,89 @@
// Copyright (c) 2024-present AI-Labs
// @ ts-nocheck
import { EditorEvents } from "@tiptap/core";
import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts";
/**
* 可滚动容器
*/
export class ScrollableDiv extends HTMLElement implements UAIEditorEventListener {
private container: HTMLElement;
scrollLeftBtn: HTMLElement;
content: HTMLElement;
scrollRightBtn: HTMLElement;
private scrollAmount: number = 50; // 每次滚动的像素数
private transform: number = 0; // 当前transform值
constructor(content: HTMLElement) {
super();
this.content = content;
this.scrollLeftBtn = document.createElement("div");
this.scrollLeftBtn.classList.add("uai-scrollable-control-button");
this.scrollLeftBtn.innerHTML = "<strong><</strong>";
this.scrollLeftBtn.style.display = "none";
this.container = document.createElement("div");
this.container.appendChild(this.content);
// this.container.style.overflow = "hidden";
// this.container.style.position = "relative";
this.scrollRightBtn = document.createElement("div");
this.scrollRightBtn.classList.add("uai-scrollable-control-button");
this.scrollRightBtn.innerHTML = "<strong>></strong>";
this.scrollRightBtn.style.display = "none";
this.scrollLeftBtn.addEventListener('click', () => {
if (this.transform < 0) {
this.transform += Math.min(this.scrollAmount, 0 - this.transform);
}
this.content.style.transform = `translateX(${this.transform}px)`;
this.scrollButtonControl();
});
this.scrollRightBtn.addEventListener('click', () => {
const scrollWidth = this.content.scrollWidth + this.transform - this.scrollRightBtn.clientWidth - this.container.clientWidth;
if (scrollWidth > 0) {
this.transform -= Math.min(this.scrollAmount, scrollWidth);
}
this.content.style.transform = `translateX(${this.transform}px)`;
this.scrollButtonControl();
});
this.classList.add("uai-classic-scrollable-menu")
this.appendChild(this.scrollLeftBtn)
this.appendChild(this.container)
this.appendChild(this.scrollRightBtn)
}
onCreate(event: EditorEvents["create"], options: UAIEditorOptions) {
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.target === this.parentElement) {
this.scrollButtonControl();
}
}
});
resizeObserver.observe(this.parentElement!);
}
onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) {
}
onEditableChange(editable: boolean) {
}
scrollButtonControl() {
if (this.transform < 0) {
this.scrollLeftBtn.style.display = "flex";
} else {
this.scrollLeftBtn.style.display = "none";
}
if (this.parentElement!.scrollWidth + this.transform - this.scrollRightBtn.clientWidth < this.container.clientWidth) {
this.scrollRightBtn.style.display = "flex";
} else {
this.scrollRightBtn.style.display = "none";
}
// 触发宽度变化事件
}
}
@@ -0,0 +1,68 @@
// Copyright (c) 2024-present AI-Labs
// @ ts-nocheck
import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts";
import icon from "../../../../assets/icons/clear-format.svg";
import { t } from "i18next";
import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts";
import { EditorEvents } from "@tiptap/core";
/**
* 基础菜单:清除格式
*/
export class ClearFormat extends HTMLElement implements UAIEditorEventListener {
// 按钮选项
menuButtonOptions: MenuButtonOptions = {
menuType: "button",
enable: true,
icon: icon,
hideText: true,
text: t('base.clearFormat'),
tooltip: t('base.clearFormat'),
}
// 功能按钮
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().unsetAllMarks().run();
}
})
}
/**
* 定义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);
}
}
@@ -0,0 +1,72 @@
// Copyright (c) 2024-present AI-Labs
// @ ts-nocheck
import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts";
import icon from "../../../../assets/icons/format-painter.svg";
import { t } from "i18next";
import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts";
import { EditorEvents } from "@tiptap/core";
/**
* 基础菜单:格式刷
*/
export class FormatPainter extends HTMLElement implements UAIEditorEventListener {
// 按钮选项
menuButtonOptions: MenuButtonOptions = {
menuType: "button",
enable: true,
icon: icon,
hideText: true,
text: t('base.formatPainter.text'),
tooltip: t('base.formatPainter.text'),
}
// 功能按钮
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) {
// TODO
}
})
}
/**
* 定义Transaction监听方法
* @param event
* @param options
*/
onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) {
this.menuButton.onTransaction(event, options);
if (this.menuButton.menuButton) {
var disable = !event.editor.state.selection.empty;
this.onEditableChange(disable);
}
}
onEditableChange(editable: boolean) {
this.menuButtonOptions.enable = editable;
this.menuButton.onEditableChange(editable);
}
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) 2024-present AI-Labs
// @ ts-nocheck
import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts";
import icon from "../../../../assets/icons/redo.svg";
import { t } from "i18next";
import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts";
import { EditorEvents } from "@tiptap/core";
/**
* 基础菜单:重做
*/
export class Redo extends HTMLElement implements UAIEditorEventListener {
// 按钮选项
menuButtonOptions: MenuButtonOptions = {
menuType: "button",
enable: true,
icon: icon,
hideText: true,
text: t('base.redo'),
tooltip: t('base.redo'),
shortcut: "Ctrl+Y / Ctrl+Shift+Z",
}
// 功能按钮
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().redo().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.can().chain().redo().run();
this.onEditableChange(disable);
}
}
onEditableChange(editable: boolean){
this.menuButtonOptions.enable = editable;
this.menuButton.onEditableChange(editable);
}
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) 2024-present AI-Labs
// @ ts-nocheck
import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts";
import icon from "../../../../assets/icons/undo.svg";
import { t } from "i18next";
import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts";
import { EditorEvents } from "@tiptap/core";
/**
* 基础菜单:撤销
*/
export class Undo extends HTMLElement implements UAIEditorEventListener {
// 按钮选项
menuButtonOptions: MenuButtonOptions = {
menuType: "button",
enable: true,
icon: icon,
hideText: true,
text: t('base.undo'),
tooltip: t('base.undo'),
shortcut: "Ctrl+Z",
}
// 功能按钮
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().undo().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.can().chain().undo().run();
this.onEditableChange(disable);
}
}
onEditableChange(editable: boolean){
this.menuButtonOptions.enable = editable;
this.menuButton.onEditableChange(editable);
}
}