From a2b9c2960b272a7874bf2deee63c5273fa32a81d Mon Sep 17 00:00:00 2001 From: wux_labs Date: Mon, 6 Jan 2025 21:54:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AD=97=E6=95=B0=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Footer.ts | 20 ++++ src/components/index.ts | 4 + .../menus/statusbar/CharacterCount.ts | 96 +++++++++++++++++++ src/core/UAIExtensions.ts | 2 + 4 files changed, 122 insertions(+) create mode 100644 src/components/menus/statusbar/CharacterCount.ts diff --git a/src/components/Footer.ts b/src/components/Footer.ts index a2af5de..e721c19 100644 --- a/src/components/Footer.ts +++ b/src/components/Footer.ts @@ -5,6 +5,7 @@ import { EditorEvents } from "@tiptap/core"; import { UAIEditorEventListener, UAIEditorOptions } from "../core/UAIEditor.ts"; import { ToggleToc } from "./menus/toolbar/page/ToggleToc"; +import { CharacterCount } from "./menus/statusbar/CharacterCount.ts"; /** * 编辑器底部状态栏 @@ -15,11 +16,15 @@ export class Footer extends HTMLElement implements UAIEditorEventListener { // 文档大纲 toggleToc!: ToggleToc; + // 字数统计 + characterCount!: CharacterCount; + constructor() { super(); // 创建状态栏菜单 this.toggleToc = new ToggleToc({ menuType: "button", enable: true }); + this.characterCount = new CharacterCount(); } /** @@ -30,6 +35,7 @@ export class Footer extends HTMLElement implements UAIEditorEventListener { onCreate(event: EditorEvents["create"], options: UAIEditorOptions) { // 初始化状态栏菜单 this.toggleToc.onCreate(event, options); + this.characterCount.onCreate(event, options); this.container = document.createElement("div"); this.container.classList.add("uai-footer"); @@ -54,6 +60,18 @@ export class Footer extends HTMLElement implements UAIEditorEventListener { // 状态栏中添加功能菜单按钮 statusBarLeft.appendChild(this.toggleToc); + statusBarLeft.appendChild(this.createSplit()); + statusBarLeft.appendChild(this.characterCount); + } + + /** + * 创建菜单分隔线 + * @returns + */ + createSplit() { + const split = document.createElement("div"); + split.classList.add("uai-status-bar-split"); + return split; } /** @@ -63,9 +81,11 @@ export class Footer extends HTMLElement implements UAIEditorEventListener { */ onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) { this.toggleToc.onTransaction(event, options); + this.characterCount.onTransaction(event, options); } onEditableChange(editable: boolean) { this.toggleToc.onEditableChange(editable); + this.characterCount.onEditableChange(editable); } } \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index a1329ef..8b1146d 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -96,6 +96,8 @@ import { VideoBubbleMenu } from "./menus/bubble/VideoBubbleMenu.ts"; import { AudioBubbleMenu } from "./menus/bubble/AudioBubbleMenu.ts"; +import { CharacterCount } from "./menus/statusbar/CharacterCount.ts"; + // 注册组件 defineCustomElement('uai-editor-header', Header); defineCustomElement('uai-editor-editor', Editor); @@ -182,6 +184,8 @@ defineCustomElement('uai-editor-export-menu-pdf', ExportPdf); defineCustomElement('uai-editor-export-menu-markdown', ExportMarkdown); defineCustomElement('uai-editor-export-menu-image', ExportImage); +defineCustomElement('uai-editor-statusbar-menu-character-count', CharacterCount); + defineCustomElement('uai-editor-bubble-menu-text-selection', TextSelectionBubbleMenu); defineCustomElement('uai-editor-bubble-menu-image', ImageBubbleMenu); diff --git a/src/components/menus/statusbar/CharacterCount.ts b/src/components/menus/statusbar/CharacterCount.ts new file mode 100644 index 0000000..416de08 --- /dev/null +++ b/src/components/menus/statusbar/CharacterCount.ts @@ -0,0 +1,96 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +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 CharacterCount extends HTMLElement implements UAIEditorEventListener { + // 字数统计容器 + container!: HTMLDivElement; + // 提示框内容 + tipsContent!: HTMLDivElement; + // 提示框实例 + tippyInstance!: Instance; + + constructor() { + super(); + // 创建字数统计容器 + this.container = document.createElement("div"); + this.container.classList.add("uai-menu-button-wrap"); + this.container.classList.add("uai-menu-button"); + this.container.style.cursor = "pointer"; + this.appendChild(this.container); + // 创建字数统计内容容器 + this.tipsContent = document.createElement("div"); + this.tipsContent.classList.add("uai-word-count-detail"); + // 提示框实例化 + this.tippyInstance = tippy(this.container, { + appendTo: "parent", + content: this.tipsContent, + theme: 'uai-tips', + placement: "top", + // trigger: "click", + // arrow: false, + // interactive: true, + }); + } + + /** + * 定义创建方法 + * @param event + * @param options + */ + onCreate(_event: EditorEvents["create"], _options: UAIEditorOptions) { + this.addEventListener("click", () => { + }) + } + + /** + * 定义Transaction监听方法 + * @param event + * @param options + */ + onTransaction(event: EditorEvents["transaction"], _options: UAIEditorOptions) { + const { selection } = event.editor.state; + // 获取当前选中的内容 + const text = event.editor.state.doc.textBetween( + selection.from, + selection.to, + '', + ); + + // 根据当前选定的内容,获取相应的字数 + if (text.length > 0) { + this.container.innerHTML = `  ${text.length} / ${event.editor.storage.characterCount.characters()} 字符  `; + } else { + this.container.innerHTML = `  ${event.editor.storage.characterCount.characters()} 字符  `; + } + + // 提示框内容 + this.tipsContent.innerHTML = ` +
${t('wordCount.title')}
+ + + ` + } + + onEditableChange(_editable: boolean) { + } +} + diff --git a/src/core/UAIExtensions.ts b/src/core/UAIExtensions.ts index 15a7c7d..6f46c33 100644 --- a/src/core/UAIExtensions.ts +++ b/src/core/UAIExtensions.ts @@ -5,6 +5,7 @@ import { Extension, Extensions, getTextBetween } from "@tiptap/core"; import { UAIEditor, UAIEditorOptions } from "./UAIEditor"; import { StarterKit } from "@tiptap/starter-kit"; +import { CharacterCount } from "@tiptap/extension-character-count"; import { Color } from "@tiptap/extension-color"; import { FontFamily } from "@tiptap/extension-font-family"; import { Highlight } from "@tiptap/extension-highlight"; @@ -204,6 +205,7 @@ export const allExtensions = (uaiEditor: UAIEditor, _options: UAIEditorOptions): }), Audio, BulletList, + CharacterCount, Color, Export.configure({ appId: process.env.TIPTAP_APP_ID,