diff --git a/src/components/index.ts b/src/components/index.ts index 2ac7942..e40a6fb 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -12,6 +12,12 @@ import { ScrollableDiv } from "./menus/toolbar/ScrollableDiv.ts"; import { MenuButton } from "./menus/MenuButton.ts"; import { ColorPicker } from "./popups/ColorPicker.ts"; +import { AlignLeft } from "./menus/common/AlignLeft.ts"; +import { AlignCenter } from "./menus/common/AlignCenter.ts"; +import { AlignRight } from "./menus/common/AlignRight.ts"; +import { AlignJustify } from "./menus/common/AlignJustify.ts"; +import { AlignDistributed } from "./menus/common/AlignDistributed.ts"; + import { FontSizeIncrease } from "./menus/common/FontSizeIncrease.ts"; import { FontSizeDecrease } from "./menus/common/FontSizeDecrease.ts"; @@ -40,6 +46,9 @@ import { Indent } from "./menus/toolbar/base/Indent.ts"; import { Outdent } from "./menus/toolbar/base/Outdent.ts"; import { LineHeight } from "./menus/toolbar/base/LineHeight.ts"; +import { BlockQuote } from "./menus/toolbar/base/BlockQuote.ts"; +import { CodeBlock } from "./menus/toolbar/base/CodeBlock.ts"; + // 注册组件 defineCustomElement('uai-editor-header', Header); defineCustomElement('uai-editor-editor', Editor); @@ -52,6 +61,12 @@ defineCustomElement('uai-editor-scrollable-div', ScrollableDiv); defineCustomElement('uai-editor-menu-button', MenuButton); defineCustomElement('uai-editor-popup-color-picker', ColorPicker); +defineCustomElement('uai-editor-common-menu-align-left', AlignLeft); +defineCustomElement('uai-editor-common-menu-align-center', AlignCenter); +defineCustomElement('uai-editor-common-menu-align-right', AlignRight); +defineCustomElement('uai-editor-common-menu-align-justify', AlignJustify); +defineCustomElement('uai-editor-common-menu-align-distributed', AlignDistributed); + defineCustomElement('uai-editor-common-menu-font-size-increase', FontSizeIncrease); defineCustomElement('uai-editor-common-menu-font-size-decrease', FontSizeDecrease); @@ -79,3 +94,6 @@ defineCustomElement('uai-editor-base-menu-task-list', TaskList); defineCustomElement('uai-editor-base-menu-indent', Indent); defineCustomElement('uai-editor-base-menu-outdent', Outdent); defineCustomElement('uai-editor-base-menu-lineheight', LineHeight); + +defineCustomElement('uai-editor-base-menu-blockquote', BlockQuote); +defineCustomElement('uai-editor-base-menu-codeblock', CodeBlock); diff --git a/src/components/menus/common/AlignCenter.ts b/src/components/menus/common/AlignCenter.ts new file mode 100644 index 0000000..e6b98e6 --- /dev/null +++ b/src/components/menus/common/AlignCenter.ts @@ -0,0 +1,81 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../MenuButton.ts"; +import icon from "../../../assets/icons/align-center.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 公共菜单:居中对齐 + */ +export class AlignCenter extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: true, + text: t('base.align.center'), + tooltip: t('base.align.center'), + shortcut: "Ctrl+Shift+E", + } + + // 功能按钮 + 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) { + if (event.editor.can().chain().focus().setTextAlign('center').run()) { + event.editor.chain().focus().setTextAlign('center').run(); + } + event.editor.chain().focus().setNodeAlign('center').run(); + } + }) + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { + this.menuButton.onTransaction(event, options); + if (this.menuButton.menuButton && this.menuButtonOptions.enable) { + // 如果所选内容是居中的,居中按钮成激活状态,否则是非激活状态 + var active = event.editor.isActive({ textAlign: 'center' }); + if (active) { + this.menuButton.menuButton.classList.add("active"); + } else { + this.menuButton.menuButton.classList.remove("active"); + } + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/components/menus/common/AlignDistributed.ts b/src/components/menus/common/AlignDistributed.ts new file mode 100644 index 0000000..77422d1 --- /dev/null +++ b/src/components/menus/common/AlignDistributed.ts @@ -0,0 +1,84 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../MenuButton.ts"; +import icon from "../../../assets/icons/align-distributed.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 公共菜单:分散对齐 + */ +export class AlignDistributed extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: true, + text: t('base.align.distributed'), + tooltip: t('base.align.distributed'), + shortcut: "Ctrl+Shift+D", + } + + // 功能按钮 + 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().setTextAlign('distributed').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().focus().setTextAlign('distributed').run(); + this.onEditableChange(disable); + + if (this.menuButtonOptions.enable) { + // 如果所选内容是分散对齐的,分散对齐按钮成激活状态,否则是非激活状态 + var active = event.editor.isActive({ textAlign: 'distributed' }); + if (active) { + this.menuButton.menuButton.classList.add("active"); + } else { + this.menuButton.menuButton.classList.remove("active"); + } + } + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/components/menus/common/AlignJustify.ts b/src/components/menus/common/AlignJustify.ts new file mode 100644 index 0000000..7ee3f01 --- /dev/null +++ b/src/components/menus/common/AlignJustify.ts @@ -0,0 +1,78 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../MenuButton.ts"; +import icon from "../../../assets/icons/align-justify.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 公共菜单:两端对齐 + */ +export class AlignJustify extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: true, + text: t('base.align.justify'), + tooltip: t('base.align.justify'), + shortcut: "Ctrl+Shift+J", + } + + // 功能按钮 + 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().setTextAlign('justify').run(); + } + }) + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { + this.menuButton.onTransaction(event, options); + if (this.menuButton.menuButton && this.menuButtonOptions.enable) { + // 如果所选内容是两端对齐的,两端对齐按钮成激活状态,否则是非激活状态 + var active = event.editor.isActive({ textAlign: 'justify' }); + if (active) { + this.menuButton.menuButton.classList.add("active"); + } else { + this.menuButton.menuButton.classList.remove("active"); + } + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/components/menus/common/AlignLeft.ts b/src/components/menus/common/AlignLeft.ts new file mode 100644 index 0000000..220ed6a --- /dev/null +++ b/src/components/menus/common/AlignLeft.ts @@ -0,0 +1,81 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../MenuButton.ts"; +import icon from "../../../assets/icons/align-left.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 公共菜单:左对齐 + */ +export class AlignLeft extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: true, + text: t('base.align.left'), + tooltip: t('base.align.left'), + shortcut: "Ctrl+Shift+L", + } + + // 功能按钮 + 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) { + if (event.editor.can().chain().focus().setTextAlign('left').run()) { + event.editor.chain().focus().setTextAlign('left').run(); + } + event.editor.chain().focus().setNodeAlign('flex-start').run(); + } + }) + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { + this.menuButton.onTransaction(event, options); + if (this.menuButton.menuButton && this.menuButtonOptions.enable) { + // 如果所选内容是左对齐的,左对齐按钮成激活状态,否则是非激活状态 + var active = event.editor.isActive({ textAlign: 'left' }); + if (active) { + this.menuButton.menuButton.classList.add("active"); + } else { + this.menuButton.menuButton.classList.remove("active"); + } + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/components/menus/common/AlignRight.ts b/src/components/menus/common/AlignRight.ts new file mode 100644 index 0000000..2748db4 --- /dev/null +++ b/src/components/menus/common/AlignRight.ts @@ -0,0 +1,81 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../MenuButton.ts"; +import icon from "../../../assets/icons/align-right.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 公共菜单:右对齐 + */ +export class AlignRight extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: true, + text: t('base.align.right'), + tooltip: t('base.align.right'), + shortcut: "Ctrl+Shift+R", + } + + // 功能按钮 + 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) { + if (event.editor.can().chain().focus().setTextAlign('right').run()) { + event.editor.chain().focus().setTextAlign('right').run(); + } + event.editor.chain().focus().setNodeAlign('flex-end').run(); + } + }) + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { + this.menuButton.onTransaction(event, options); + if (this.menuButton.menuButton && this.menuButtonOptions.enable) { + // 如果所选内容是右对齐的,右对齐按钮成激活状态,否则是非激活状态 + var active = event.editor.isActive({ textAlign: 'right' }); + if (active) { + this.menuButton.menuButton.classList.add("active"); + } else { + this.menuButton.menuButton.classList.remove("active"); + } + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/components/menus/toolbar/Classic.ts b/src/components/menus/toolbar/Classic.ts index 4ebc2f7..8303739 100644 --- a/src/components/menus/toolbar/Classic.ts +++ b/src/components/menus/toolbar/Classic.ts @@ -20,6 +20,12 @@ import { Superscript } from "../common/Superscript.ts"; import { FontColor } from "../common/FontColor.ts"; import { Highlight } from "../common/Highlight.ts"; +import { AlignLeft } from "../common/AlignLeft.ts"; +import { AlignCenter } from "../common/AlignCenter.ts"; +import { AlignRight } from "../common/AlignRight.ts"; +import { AlignJustify } from "../common/AlignJustify.ts"; +import { AlignDistributed } from "../common/AlignDistributed.ts"; + import { Redo } from "./base/Redo.ts"; import { Undo } from "./base/Undo.ts"; @@ -36,6 +42,9 @@ import { Indent } from "./base/Indent.ts"; import { Outdent } from "./base/Outdent.ts"; import { LineHeight } from "./base/LineHeight.ts"; +import { BlockQuote } from "./base/BlockQuote.ts"; +import { CodeBlock } from "./base/CodeBlock.ts"; + /** * 传统菜单栏 */ @@ -78,6 +87,14 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { baseMenuOutdent!: Outdent; baseMenuLineHeight!: LineHeight; + baseMenuAlignLeft!: AlignLeft; + baseMenuAlignCenter!: AlignCenter; + baseMenuAlignRight!: AlignRight; + baseMenuAlignJustify!: AlignJustify; + baseMenuAlignDistributed!: AlignDistributed; + baseMenuBlockQuote!: BlockQuote; + baseMenuCodeBlock!: CodeBlock; + constructor(defaultToolbarMenus: Record[]) { super(); this.defaultToolbarMenus = defaultToolbarMenus; @@ -217,6 +234,27 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { this.baseMenuLineHeight = new LineHeight({ menuType: "popup", enable: true }); this.eventComponents.push(this.baseMenuLineHeight); + + this.baseMenuAlignLeft = new AlignLeft({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignLeft); + + this.baseMenuAlignCenter = new AlignCenter({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignCenter); + + this.baseMenuAlignRight = new AlignRight({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignRight); + + this.baseMenuAlignJustify = new AlignJustify({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignJustify); + + this.baseMenuAlignDistributed = new AlignDistributed({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignDistributed); + + this.baseMenuBlockQuote = new BlockQuote({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuBlockQuote); + + this.baseMenuCodeBlock = new CodeBlock({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuCodeBlock); } /** * 创建基础菜单 @@ -264,5 +302,12 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { group3.appendChild(this.baseMenuIndent); group3.appendChild(this.baseMenuOutdent); group3.appendChild(this.baseMenuLineHeight); + group3.appendChild(this.baseMenuAlignLeft); + group3.appendChild(this.baseMenuAlignCenter); + group3.appendChild(this.baseMenuAlignRight); + group3.appendChild(this.baseMenuAlignJustify); + group3.appendChild(this.baseMenuAlignDistributed); + group3.appendChild(this.baseMenuBlockQuote); + group3.appendChild(this.baseMenuCodeBlock); } } \ No newline at end of file diff --git a/src/components/menus/toolbar/Ribbon.ts b/src/components/menus/toolbar/Ribbon.ts index 9eb2b81..4404210 100644 --- a/src/components/menus/toolbar/Ribbon.ts +++ b/src/components/menus/toolbar/Ribbon.ts @@ -17,6 +17,12 @@ import { Strike } from "../common/Strike.ts"; import { Subscript } from "../common/Subscript.ts"; import { Superscript } from "../common/Superscript.ts"; +import { AlignLeft } from "../common/AlignLeft.ts"; +import { AlignCenter } from "../common/AlignCenter.ts"; +import { AlignRight } from "../common/AlignRight.ts"; +import { AlignJustify } from "../common/AlignJustify.ts"; +import { AlignDistributed } from "../common/AlignDistributed.ts"; + import { FontColor } from "../common/FontColor.ts"; import { Highlight } from "../common/Highlight.ts"; @@ -36,6 +42,9 @@ import { Indent } from "./base/Indent.ts"; import { Outdent } from "./base/Outdent.ts"; import { LineHeight } from "./base/LineHeight.ts"; +import { BlockQuote } from "./base/BlockQuote.ts"; +import { CodeBlock } from "./base/CodeBlock.ts"; + /** * 经典菜单栏 */ @@ -79,6 +88,14 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { baseMenuOutdent!: Outdent; baseMenuLineHeight!: LineHeight; + baseMenuAlignLeft!: AlignLeft; + baseMenuAlignCenter!: AlignCenter; + baseMenuAlignRight!: AlignRight; + baseMenuAlignJustify!: AlignJustify; + baseMenuAlignDistributed!: AlignDistributed; + baseMenuBlockQuote!: BlockQuote; + baseMenuCodeBlock!: CodeBlock; + constructor(defaultToolbarMenus: Record[]) { super(); this.defaultToolbarMenus = defaultToolbarMenus; @@ -238,6 +255,27 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { this.baseMenuLineHeight = new LineHeight({ menuType: "popup", enable: true }); this.eventComponents.push(this.baseMenuLineHeight); + + this.baseMenuAlignLeft = new AlignLeft({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignLeft); + + this.baseMenuAlignCenter = new AlignCenter({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignCenter); + + this.baseMenuAlignRight = new AlignRight({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignRight); + + this.baseMenuAlignJustify = new AlignJustify({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignJustify); + + this.baseMenuAlignDistributed = new AlignDistributed({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuAlignDistributed); + + this.baseMenuBlockQuote = new BlockQuote({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuBlockQuote); + + this.baseMenuCodeBlock = new CodeBlock({ menuType: "button", enable: true }); + this.eventComponents.push(this.baseMenuCodeBlock); } /** @@ -306,5 +344,16 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { group3row1.appendChild(this.baseMenuIndent); group3row1.appendChild(this.baseMenuOutdent); group3row1.appendChild(this.baseMenuLineHeight); + + const group3row2 = document.createElement("div"); + group3row2.classList.add("uai-ribbon-virtual-group-row"); + group3.appendChild(group3row2); + group3row2.appendChild(this.baseMenuAlignLeft); + group3row2.appendChild(this.baseMenuAlignCenter); + group3row2.appendChild(this.baseMenuAlignRight); + group3row2.appendChild(this.baseMenuAlignJustify); + group3row2.appendChild(this.baseMenuAlignDistributed); + group3row2.appendChild(this.baseMenuBlockQuote); + group3row2.appendChild(this.baseMenuCodeBlock); } } \ No newline at end of file diff --git a/src/components/menus/toolbar/base/BlockQuote.ts b/src/components/menus/toolbar/base/BlockQuote.ts new file mode 100644 index 0000000..6f196f0 --- /dev/null +++ b/src/components/menus/toolbar/base/BlockQuote.ts @@ -0,0 +1,85 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts"; +import icon from "../../../../assets/icons/blockquote.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 基础菜单:引用 + */ +export class BlockQuote extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: true, + text: t('base.blockquote'), + tooltip: t('base.blockquote'), + shortcut: "Ctrl+Shift+B", + } + + // 功能按钮 + 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().toggleBlockquote().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().toggleBlockquote().run(); + this.onEditableChange(disable); + + if (this.menuButtonOptions.enable) { + // 判断当前内容是否是引用格式 + var active = event.editor.isActive('blockquote'); + if (active) { + // 如果是,则按钮是激活状态 + this.menuButton.menuButton.classList.add("active"); + } else { + // 如果不是,则按钮是非激活状态 + this.menuButton.menuButton.classList.remove("active"); + } + } + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/components/menus/toolbar/base/CodeBlock.ts b/src/components/menus/toolbar/base/CodeBlock.ts new file mode 100644 index 0000000..ff2b823 --- /dev/null +++ b/src/components/menus/toolbar/base/CodeBlock.ts @@ -0,0 +1,85 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts"; +import icon from "../../../../assets/icons/codeblock.svg"; +import { t } from "i18next"; +import { UAIEditorEventListener, UAIEditorOptions } from "../../../../core/UAIEditor.ts"; +import { EditorEvents } from "@tiptap/core"; + +/** + * 基础菜单:设置代码块 + */ +export class CodeBlock extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: true, + text: t('base.codeblock'), + tooltip: t('base.codeblock'), + shortcut: "Ctrl+E", + } + + // 功能按钮 + 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().toggleCode().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().toggleCode().run(); + this.onEditableChange(disable); + + if (this.menuButtonOptions.enable) { + // 判断当前内容是否是代码块 + var active = event.editor.isActive('code'); + if (active) { + // 如果是,则按钮是激活状态 + this.menuButton.menuButton.classList.add("active"); + } else { + // 如果不是,则按钮是非激活状态 + this.menuButton.menuButton.classList.remove("active"); + } + } + } + } + + onEditableChange(editable: boolean) { + this.menuButtonOptions.enable = editable; + this.menuButton.onEditableChange(editable); + } +} + diff --git a/src/core/UAIExtensions.ts b/src/core/UAIExtensions.ts index 2191585..c046c6b 100644 --- a/src/core/UAIExtensions.ts +++ b/src/core/UAIExtensions.ts @@ -12,13 +12,15 @@ import { Subscript } from "@tiptap/extension-subscript"; import { Superscript } from "@tiptap/extension-superscript"; import { TaskItem } from "@tiptap/extension-task-item"; import { TaskList } from "@tiptap/extension-task-list"; +import { TextAlign } from "@tiptap/extension-text-align"; import { TextStyle } from "@tiptap/extension-text-style"; import { Underline } from "@tiptap/extension-underline"; - +; +import BulletList from "../extensions/BulletList.ts"; import FontSize from "../extensions/FontSize.ts"; import Indent from "../extensions/Indent.ts"; -import LineHeight from "../extensions/LineHeight.ts"; -import BulletList from "../extensions/BulletList.ts"; +import LineHeight from "../extensions/LineHeight.ts" +import NodeAlign from "../extensions/NodeAlign.ts"; import OrderedList from "../extensions/OrderedList.ts"; /** @@ -44,6 +46,7 @@ export const allExtensions = (uaiEditor: UAIEditor, _options: UAIEditorOptions): }), Indent, LineHeight, + NodeAlign, OrderedList, Subscript, Superscript, @@ -51,6 +54,9 @@ export const allExtensions = (uaiEditor: UAIEditor, _options: UAIEditorOptions): TaskItem.configure({ nested: true, }), + TextAlign.configure({ + types: ['heading', 'paragraph'], + }), TextStyle, Underline, ]; diff --git a/src/extensions/NodeAlign.ts b/src/extensions/NodeAlign.ts new file mode 100644 index 0000000..020d285 --- /dev/null +++ b/src/extensions/NodeAlign.ts @@ -0,0 +1,81 @@ +// Copyright (c) 2024-present AI-Labs + +import { Extension } from '@tiptap/core' + +declare module '@tiptap/core' { + interface Commands { + setNodeAlign: { + /** + * 设置节点对齐方式 + * @param options + * @returns + */ + setNodeAlign: (options: any) => ReturnType + } + unsetNodeAlign: { + /** + * 取消设置节点对齐方式 + * @returns + */ + unsetNodeAlign: () => ReturnType + } + } +} + +/** + * 定义节点对齐扩展 + */ +export default Extension.create({ + name: 'nodeAlign', + addOptions() { + return { + defaultAlignment: 'center', + alignments: ['flex-start', 'center', 'flex-end'], + types: ['image', 'video', 'audio', 'iframe', 'file'], + } + }, + addGlobalAttributes() { + return [ + { + types: this.options.types, + attributes: { + nodeAlign: { + default: this.options.defaultAlignment, + parseHTML: (element) => { + return ( + element.style.justifyContent || this.options.defaultAlignment + ) + }, + renderHTML: (attributes) => { + if (attributes.nodeAlign === this.options.defaultAlignment) { + return {} + } + return { style: `justify-content: ${attributes.nodeAlign}` } + }, + }, + }, + }, + ] + }, + addCommands() { + return { + setNodeAlign: + (alignment) => + ({ commands }) => { + if (!this.options.alignments.includes(alignment)) { + return false + } + return this.options.types.forEach((type: string) => { + commands.updateAttributes(type, { nodeAlign: alignment }) + }) + }, + unsetNodeAlign: + () => + ({ commands }) => { + return this.options.types.every((type: string) => + commands.resetAttributes(type, 'nodeAlign'), + ) + }, + } + }, +})