From ca01cba91e9ab76ec39baddd617527222a8ec9ed Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sat, 8 Feb 2025 16:52:19 +0800 Subject: [PATCH 1/8] =?UTF-8?q?chore(mini-editor-markdown):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=B7=A5=E5=85=B7=E9=85=8D=E7=BD=AE=E7=B1=BB=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=B7=A5=E5=85=B7=E7=B1=BB=E5=9E=8B=E5=AE=9A?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mini-markdown-editor/package.json | 1 + packages/mini-markdown-editor/src/App.tsx | 8 ++ .../src/config/toolbar/index.ts | 67 +++++++--- .../mini-markdown-editor/src/types/toolbar.ts | 116 ++++++++++++------ 4 files changed, 140 insertions(+), 52 deletions(-) diff --git a/packages/mini-markdown-editor/package.json b/packages/mini-markdown-editor/package.json index 73661eb..2c66955 100644 --- a/packages/mini-markdown-editor/package.json +++ b/packages/mini-markdown-editor/package.json @@ -39,6 +39,7 @@ "highlight.js": "^11.11.1", "html2pdf.js": "^0.10.2", "nanoid": "^5.0.9", + "immer": "^10.1.1", "react-hotkeys-hook": "^4.6.1", "zustand": "^5.0.3" }, diff --git a/packages/mini-markdown-editor/src/App.tsx b/packages/mini-markdown-editor/src/App.tsx index 2e3fc39..0e82bdc 100644 --- a/packages/mini-markdown-editor/src/App.tsx +++ b/packages/mini-markdown-editor/src/App.tsx @@ -8,6 +8,8 @@ import "highlight.js/styles/atom-one-dark.css"; // import { ViewUpdate } from "./types/code-mirror"; import { EditorView } from "@uiw/react-codemirror"; import { EditorRef } from "./types/ref"; +import { toolbarConfig } from "./config/toolbar"; +import OlIcon from "@/assets/images/ol.svg?raw"; const AppWrapper = styled.div` width: 100%; @@ -71,6 +73,12 @@ const App: FC = () => { } }, []); + toolbarConfig.addToolbar({ + type: "abc", + title: "abc", + icon: OlIcon, + }); + return ( diff --git a/packages/mini-markdown-editor/src/config/toolbar/index.ts b/packages/mini-markdown-editor/src/config/toolbar/index.ts index b317d53..7e4f7eb 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/index.ts +++ b/packages/mini-markdown-editor/src/config/toolbar/index.ts @@ -1,16 +1,19 @@ import type { ToolbarItem, ToolbarType } from "@/types/toolbar"; import { toolbar } from "./base"; +import { produce } from "immer"; class ToolbarConfig { private toolbars: ToolbarItem[]; + private readonly defaultToolbars: ToolbarItem[]; - constructor() { + constructor(initialToolbars: ToolbarItem[]) { + this.defaultToolbars = [...initialToolbars]; this.toolbars = this.initToolbars(); } - // 初始化工具栏内容 + // 初始化默认工具栏内容 private initToolbars(): ToolbarItem[] { - return toolbar; + return [...this.defaultToolbars]; } // 获取所有工具栏项 @@ -18,31 +21,63 @@ class ToolbarConfig { return this.toolbars; } + // 获取指定类型的工具栏项 + private getToolbarByType(type: ToolbarType): ToolbarItem | undefined { + return this.toolbars.find((toolbar) => toolbar.type === type); + } + + // 添加指定工具栏项 public addToolbar(toolbarItem: ToolbarItem): void { - this.toolbars.push(toolbarItem); + // 过滤掉重复的工具栏项 + if (this.getToolbarByType(toolbarItem.type)) { + throw new Error(`Toolbar type ${toolbarItem.type} already exists`); + } + this.toolbars = produce(this.toolbars, (draft) => { + draft.push(toolbarItem); + }); } + // 移除指定工具栏项 public removeToolbar(type: ToolbarType): void { - const index = this.toolbars.findIndex((toolbar) => toolbar.type === type); - if (index !== -1) { - this.toolbars.splice(index, 1); - } + this.toolbars = produce(this.toolbars, (draft) => { + const index = draft.findIndex((toolbar) => toolbar.type === type); + if (index !== -1) { + draft.splice(index, 1); + } + }); } + // 更新工具栏 public updateToolbar(type: ToolbarType, newConfig: Partial): void { - const index = this.toolbars.findIndex((toolbar) => toolbar.type === type); - if (index !== -1) { - this.toolbars[index] = { ...this.toolbars[index], ...newConfig }; - } + this.toolbars = produce(this.toolbars, (draft) => { + const index = draft.findIndex((toolbar) => toolbar.type === type); + if (index !== -1) { + draft[index] = { ...draft[index], ...newConfig }; + } + }); } // TODO: 添加重新排序工具栏顺序(例如拖拽排序??)的方法 public reorderToolbars(newOrder: ToolbarType[]): void { const toolbarMap = new Map(this.toolbars.map((toolbar) => [toolbar.type, toolbar])); - this.toolbars = newOrder - .map((type) => toolbarMap.get(type)) - .filter((toolbar): toolbar is ToolbarItem => toolbar !== undefined); + this.toolbars = produce(this.toolbars, () => + newOrder + .map((type) => toolbarMap.get(type)) + .filter((toolbar): toolbar is ToolbarItem => toolbar !== undefined), + ); + } + + public reset(): void { + this.toolbars = this.initToolbars(); + } + + public enableToolbar(type: ToolbarType): void { + this.updateToolbar(type, { disabled: false }); + } + + public disableToolbar(type: ToolbarType): void { + this.updateToolbar(type, { disabled: true }); } } -export const toolbarConfig = new ToolbarConfig(); +export const toolbarConfig = new ToolbarConfig(toolbar); diff --git a/packages/mini-markdown-editor/src/types/toolbar.ts b/packages/mini-markdown-editor/src/types/toolbar.ts index 01823b6..5c0eab0 100644 --- a/packages/mini-markdown-editor/src/types/toolbar.ts +++ b/packages/mini-markdown-editor/src/types/toolbar.ts @@ -1,59 +1,103 @@ -export interface ToolbarItem { +// 基础工具栏类型 +export enum BaseToolbarType { + HEADING = "heading", + HEADING_1 = "heading-1", + HEADING_2 = "heading-2", + HEADING_3 = "heading-3", + HEADING_4 = "heading-4", + HEADING_5 = "heading-5", + HEADING_6 = "heading-6", + BOLD = "bold", + ITALIC = "italic", + UNDERLINE = "underline", + DELETE = "delete", + LINE = "line", + BLOCKQUOTE = "blockquote", + UL = "ul", + OL = "ol", + INLINECODE = "inlinecode", + CODE = "code", + LINK = "link", + IMAGE = "image", + IMAGE_LINK = "image-link", + IMAGE_UPLOAD = "image-upload", + TABLE = "table", + UNDO = "undo", + REDO = "redo", + FULLSCREEN = "fullscreen", + WRITE = "write", + PREVIEW = "preview", + CONTENTS = "contents", + HELP = "help", + OUTPUT = "output", + EMOJI = "emoji", + SAVE = "save", +} + +// 允许用户扩展的工具栏类型 +export type ExtendedToolbarType = string; + +// 合并基础类型和扩展类型 +export type ToolbarType = BaseToolbarType | ExtendedToolbarType; + +// 基础工具栏项接口 +export interface BaseToolbarItem { type: ToolbarType; icon?: string; title?: string; description?: string; - list?: ToolbarItemListItem[]; disabled?: boolean; visible?: boolean; onClick?: () => void; component?: React.ReactNode; } +// 工具栏列表项接口 export interface ToolbarItemListItem { title: string; type: string; onClick?: (...args: any[]) => void | (() => void); } +// 完整的工具栏项接口 +export interface ToolbarItem extends BaseToolbarItem { + list?: ToolbarItemListItem[]; +} + +// 工具栏上下文值接口 export interface ToolbarContextValues { toolbars: ToolbarItem[]; addToolbar?: (toolbarItem: ToolbarItem) => void; removeToolbar?: (type: ToolbarType) => void; - updateToolbar?: (type: ToolbarType, newConfig: ToolbarItem) => void; + updateToolbar?: (type: ToolbarType, newConfig: Partial) => void; reorderToolbars?: (newOrder: ToolbarType[]) => void; } -export type ToolbarType = - | "heading" - | "heading-1" - | "heading-2" - | "heading-3" - | "heading-4" - | "heading-5" - | "heading-6" - | "bold" - | "italic" - | "underline" - | "delete" - | "line" - | "blockquote" - | "ul" - | "ol" - | "inlinecode" - | "code" - | "link" - | "image" - | "image-link" - | "image-upload" - | "table" - | "undo" - | "redo" - | "fullscreen" - | "write" - | "preview" - | "contents" - | "help" - | "output" - | "emoji" - | "save"; +// 类型保护函数 +export const isBaseToolbarType = (type: ToolbarType): type is BaseToolbarType => { + return Object.values(BaseToolbarType).includes(type as BaseToolbarType); +}; + +// 工具栏配置验证 +export interface ToolbarValidationResult { + isValid: boolean; + errors: string[]; +} + +// 工具栏配置验证函数 +export const validateToolbarItem = (item: ToolbarItem): ToolbarValidationResult => { + const errors: string[] = []; + + if (!item.type) { + errors.push("Toolbar item must have a type"); + } + + if (item.list && !Array.isArray(item.list)) { + errors.push("Toolbar item list must be an array"); + } + + return { + isValid: errors.length === 0, + errors, + }; +}; -- Gitee From f761849fc204243c9fc3cf52e181171ec3677e77 Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sat, 8 Feb 2025 19:36:47 +0800 Subject: [PATCH 2/8] =?UTF-8?q?chore(mini-markdown-editor):=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AEbase=E7=B1=BB=E5=81=9A=E4=B8=BA=E5=9F=BA=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mini-markdown-editor/src/config/base.ts | 127 ++++++++++++++++++ .../src/config/toolbar/index.ts | 7 +- 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 packages/mini-markdown-editor/src/config/base.ts diff --git a/packages/mini-markdown-editor/src/config/base.ts b/packages/mini-markdown-editor/src/config/base.ts new file mode 100644 index 0000000..f474362 --- /dev/null +++ b/packages/mini-markdown-editor/src/config/base.ts @@ -0,0 +1,127 @@ +import { EventEmitter } from "events"; + +export interface EventCallback { + (...args: any[]): void; +} + +export interface BaseConfig { + name?: string; + // 最大监听器数量 + maxListeners?: number; +} + +export abstract class BaseClass { + protected readonly config: BaseConfig; + protected readonly eventEmitter: EventEmitter; + protected readonly logger: Console; + protected isDestroyed: boolean = false; + + constructor(config: BaseConfig = {}) { + this.config = { + ...config, + }; + + this.eventEmitter = new EventEmitter(); + this.eventEmitter.setMaxListeners(this.config.maxListeners!); + this.logger = console; + } + + // 错误日志 + protected error(...args: unknown[]): void { + this.logger.error(`[${this.config.name}]`, ...args); + } + + /** + * 添加事件监听器 + * @param event 事件名称 + * @param listener 监听器回调函数 + */ + public on(event: string, listener: EventCallback): void { + if (typeof listener !== "function") { + throw new TypeError("Listener must be a function"); + } + this.eventEmitter.on(event, listener); + } + + /** + * 一次性事件监听器 + * @param event 事件名称 + * @param listener 监听器回调函数 + */ + public once(event: string, listener: EventCallback): void { + if (typeof listener !== "function") { + throw new TypeError("Listener must be a function"); + } + this.eventEmitter.once(event, listener); + } + + /** + * 移除事件监听器 + * @param event 事件名称 + * @param listener 监听器回调函数 + */ + public off(event: string, listener: EventCallback): void { + if (typeof listener !== "function") { + throw new TypeError("Listener must be a function"); + } + this.eventEmitter.off(event, listener); + } + + /** + * 触发事件 + * @param event 事件名称 + * @param args 事件参数 + */ + protected emit(event: string, ...args: unknown[]): void { + this.eventEmitter.emit(event, ...args); + } + + public isDestroy(): boolean { + return this.isDestroyed; + } + + /** + * 获取当前注册的所有事件 + */ + public getEvents(): string[] { + return this.eventEmitter.eventNames() as string[]; + } + + /** + * 获取特定事件的监听器数量 + * @param event 事件名称 + */ + public listenerCount(event: string): number { + return this.eventEmitter.listenerCount(event); + } + + // 销毁实例 + public destroy(): void { + if (this.isDestroyed) { + return; + } + + try { + // 触发销毁前事件 + this.emit("beforeDestroy"); + + // 移除所有事件监听器 + this.eventEmitter.removeAllListeners(); + + // 标记为已销毁 + this.isDestroyed = true; + + // 触发销毁完成事件 + this.emit("destroyed"); + } catch (error) { + this.error("Error during destruction:", error); + } + } + + protected checkDestroyed(): void { + if (this.isDestroyed) { + this.logger.error("ERROR ISDESTOYED"); + // TODO: 后续操作 + } + } +} diff --git a/packages/mini-markdown-editor/src/config/toolbar/index.ts b/packages/mini-markdown-editor/src/config/toolbar/index.ts index 7e4f7eb..2a6de61 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/index.ts +++ b/packages/mini-markdown-editor/src/config/toolbar/index.ts @@ -1,12 +1,17 @@ import type { ToolbarItem, ToolbarType } from "@/types/toolbar"; import { toolbar } from "./base"; import { produce } from "immer"; +import { BaseClass } from "../base"; -class ToolbarConfig { +class ToolbarConfig extends BaseClass { private toolbars: ToolbarItem[]; private readonly defaultToolbars: ToolbarItem[]; constructor(initialToolbars: ToolbarItem[]) { + super({ + name: "toolbarConfig", + maxListeners: 10, + }); this.defaultToolbars = [...initialToolbars]; this.toolbars = this.initToolbars(); } -- Gitee From b342e20d5867cb8b6c429c4c0bb0f3edd07d10da Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sat, 8 Feb 2025 21:05:06 +0800 Subject: [PATCH 3/8] =?UTF-8?q?refactor(mini-markdown-editor):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=B7=A5=E5=85=B7=E6=A0=8F=E9=83=A8=E5=88=86=E7=9A=84?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mini-markdown-editor/src/App.tsx | 14 +- .../src/common/hotkeys.ts | 11 +- .../mini-markdown-editor/src/config/base.ts | 2 +- .../src/config/toolbar/base.tsx | 20 +++ .../src/extensions/codemirror/hotkeys.ts | 169 +----------------- packages/mini-markdown-editor/src/index.tsx | 3 + .../mini-markdown-editor/src/types/toolbar.ts | 21 +++ .../src/utils/handle-hotkeys.ts | 89 +++++++++ 8 files changed, 159 insertions(+), 170 deletions(-) create mode 100644 packages/mini-markdown-editor/src/utils/handle-hotkeys.ts diff --git a/packages/mini-markdown-editor/src/App.tsx b/packages/mini-markdown-editor/src/App.tsx index 0e82bdc..041d707 100644 --- a/packages/mini-markdown-editor/src/App.tsx +++ b/packages/mini-markdown-editor/src/App.tsx @@ -73,10 +73,22 @@ const App: FC = () => { } }, []); + // 添加abc工具 toolbarConfig.addToolbar({ type: "abc", - title: "abc", + title: "我是测试abc", icon: OlIcon, + description: "我是描述abc", + hotkey: { + command: "mod+p", + description: "Make text bold", + handle: () => { + // 处理加粗逻辑 + }, + }, + onClick: () => { + console.log("我是输出abc"); + }, }); return ( diff --git a/packages/mini-markdown-editor/src/common/hotkeys.ts b/packages/mini-markdown-editor/src/common/hotkeys.ts index cde6d7f..f389853 100644 --- a/packages/mini-markdown-editor/src/common/hotkeys.ts +++ b/packages/mini-markdown-editor/src/common/hotkeys.ts @@ -30,7 +30,7 @@ export class Hotkey { // Actions static readonly SAVE = new Hotkey("mod+s", "Save"); - private constructor( + constructor( public readonly command: Command, public readonly description: Description, public readonly handle?: void | (() => void), @@ -104,4 +104,13 @@ export class Hotkey { throw new Error(`This is must!: ${command}`); } } + + // 生成配置对象的方法 + public toConfig() { + return { + command: this.codeMirrorCommand, + description: this.description, + handle: this.handle, + }; + } } diff --git a/packages/mini-markdown-editor/src/config/base.ts b/packages/mini-markdown-editor/src/config/base.ts index f474362..26934c5 100644 --- a/packages/mini-markdown-editor/src/config/base.ts +++ b/packages/mini-markdown-editor/src/config/base.ts @@ -4,7 +4,7 @@ export interface EventCallback { (...args: any[]): void; } -export interface BaseConfig { +interface BaseConfig { name?: string; // 最大监听器数量 maxListeners?: number; diff --git a/packages/mini-markdown-editor/src/config/toolbar/base.tsx b/packages/mini-markdown-editor/src/config/toolbar/base.tsx index 9be2813..679d6a3 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/base.tsx +++ b/packages/mini-markdown-editor/src/config/toolbar/base.tsx @@ -34,31 +34,37 @@ export const toolbar: ToolbarItem[] = [ { title: "H1 一级标题", type: "heading-1", + hotkey: Hotkey.TITLE.FIRST.toConfig(), onClick: () => InsertTextEvent("heading-1"), }, { title: "H2 二级标题", type: "heading-2", + hotkey: Hotkey.TITLE.SECOND.toConfig(), onClick: () => InsertTextEvent("heading-2"), }, { title: "H3 三级标题", type: "heading-3", + hotkey: Hotkey.TITLE.THIRD.toConfig(), onClick: () => InsertTextEvent("heading-3"), }, { title: "H4 四级标题", type: "heading-4", + hotkey: Hotkey.TITLE.FOURTH.toConfig(), onClick: () => InsertTextEvent("heading-4"), }, { title: "H5 五级标题", type: "heading-5", + hotkey: Hotkey.TITLE.FIFTH.toConfig(), onClick: () => InsertTextEvent("heading-5"), }, { title: "H6 六级标题", type: "heading-6", + hotkey: Hotkey.TITLE.SIXTH.toConfig(), onClick: () => InsertTextEvent("heading-6"), }, ], @@ -68,6 +74,7 @@ export const toolbar: ToolbarItem[] = [ icon: BoldIcon, title: "加粗", description: Hotkey.BOLD.readableCommand, + hotkey: Hotkey.BOLD.toConfig(), onClick: () => InsertTextEvent("bold"), }, { @@ -75,6 +82,7 @@ export const toolbar: ToolbarItem[] = [ icon: ItalicIcon, title: "斜体", description: Hotkey.ITALIC.readableCommand, + hotkey: Hotkey.ITALIC.toConfig(), onClick: () => InsertTextEvent("italic"), }, { @@ -82,6 +90,7 @@ export const toolbar: ToolbarItem[] = [ icon: UnderlineIcon, title: "下划线", description: Hotkey.UNDERLINE.readableCommand, + hotkey: Hotkey.UNDERLINE.toConfig(), onClick: () => InsertTextEvent("underline"), }, { @@ -89,6 +98,7 @@ export const toolbar: ToolbarItem[] = [ icon: DeleteIcon, title: "删除线", description: Hotkey.DELETE.readableCommand, + hotkey: Hotkey.DELETE.toConfig(), onClick: () => InsertTextEvent("delete"), }, { @@ -99,6 +109,7 @@ export const toolbar: ToolbarItem[] = [ icon: BlockquoteIcon, title: "引用", description: Hotkey.BLOCKQUOTE.readableCommand, + hotkey: Hotkey.BLOCKQUOTE.toConfig(), onClick: () => InsertTextEvent("blockquote"), }, { @@ -106,6 +117,7 @@ export const toolbar: ToolbarItem[] = [ icon: UlIcon, title: "无序列表", description: Hotkey.UNORDERED_LIST.readableCommand, + hotkey: Hotkey.UNORDERED_LIST.toConfig(), onClick: () => InsertTextEvent("ul"), }, { @@ -113,6 +125,7 @@ export const toolbar: ToolbarItem[] = [ icon: OlIcon, title: "有序列表", description: Hotkey.ORDERED_LIST.readableCommand, + hotkey: Hotkey.ORDERED_LIST.toConfig(), onClick: () => InsertTextEvent("ol"), }, { @@ -120,6 +133,7 @@ export const toolbar: ToolbarItem[] = [ icon: InlineCodeIcon, title: "行内代码", description: Hotkey.INLINE_CODE.readableCommand, + hotkey: Hotkey.INLINE_CODE.toConfig(), onClick: () => InsertTextEvent("inlinecode"), }, { @@ -127,6 +141,7 @@ export const toolbar: ToolbarItem[] = [ icon: CodeIcon, title: "代码块", description: Hotkey.CODE_BLOCK.readableCommand, + hotkey: Hotkey.CODE_BLOCK.toConfig(), onClick: () => InsertTextEvent("code"), }, { @@ -134,6 +149,7 @@ export const toolbar: ToolbarItem[] = [ icon: LinkIcon, title: "链接", description: Hotkey.LINK.readableCommand, + hotkey: Hotkey.LINK.toConfig(), onClick: () => InsertTextEvent("link"), }, { @@ -144,6 +160,7 @@ export const toolbar: ToolbarItem[] = [ { title: "添加链接", type: "image-link", + hotkey: Hotkey.LINK.toConfig(), onClick: () => InsertTextEvent("image-link"), }, { @@ -157,6 +174,7 @@ export const toolbar: ToolbarItem[] = [ icon: TableIcon, title: "表格", description: Hotkey.TABLE.readableCommand, + hotkey: Hotkey.TABLE.toConfig(), onClick: () => InsertTextEvent("table"), }, { @@ -185,10 +203,12 @@ export const toolbar: ToolbarItem[] = [ }, { type: "fullscreen", + hotkey: Hotkey.FULL_SCREEN.toConfig(), component: , }, { type: "save", + hotkey: Hotkey.SAVE.toConfig(), component: , }, { diff --git a/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts b/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts index 99fce7a..e5adae1 100644 --- a/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts +++ b/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts @@ -1,175 +1,10 @@ import { Extension } from "@codemirror/state"; import { keymap } from "@codemirror/view"; -import { Hotkey } from "@/common/hotkeys"; -import { InsertTextEvent } from "@/config/toolbar/event"; -import { ToolbarType } from "@/types/toolbar"; -import { useToolbarStore } from "@/store/toolbar"; - -// 定义默认快捷键支持 -const KEYMAP = { - // Text - [Hotkey.TITLE.FIRST.codeMirrorCommand]: { - run: () => { - Hotkey.TITLE.FIRST.handle?.(); - InsertTextEvent(Hotkey.TITLE.FIRST.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.TITLE.SECOND.codeMirrorCommand]: { - run: () => { - Hotkey.TITLE.SECOND.handle?.(); - InsertTextEvent(Hotkey.TITLE.SECOND.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.TITLE.THIRD.codeMirrorCommand]: { - run: () => { - Hotkey.TITLE.THIRD.handle?.(); - InsertTextEvent(Hotkey.TITLE.THIRD.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.TITLE.FOURTH.codeMirrorCommand]: { - run: () => { - Hotkey.TITLE.FOURTH.handle?.(); - InsertTextEvent(Hotkey.TITLE.FOURTH.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.TITLE.FIFTH.codeMirrorCommand]: { - run: () => { - Hotkey.TITLE.FIFTH.handle?.(); - InsertTextEvent(Hotkey.TITLE.FIFTH.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.TITLE.SIXTH.codeMirrorCommand]: { - run: () => { - Hotkey.TITLE.SIXTH.handle?.(); - InsertTextEvent(Hotkey.TITLE.SIXTH.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.BOLD.codeMirrorCommand]: { - run: () => { - Hotkey.BOLD.handle?.(); - InsertTextEvent(Hotkey.BOLD.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.ITALIC.codeMirrorCommand]: { - run: () => { - Hotkey.ITALIC.handle?.(); - InsertTextEvent(Hotkey.ITALIC.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.UNDERLINE.codeMirrorCommand]: { - run: () => { - Hotkey.UNDERLINE.handle?.(); - InsertTextEvent(Hotkey.UNDERLINE.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.DELETE.codeMirrorCommand]: { - run: () => { - Hotkey.DELETE.handle?.(); - InsertTextEvent(Hotkey.DELETE.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.BLOCKQUOTE.codeMirrorCommand]: { - run: () => { - Hotkey.BLOCKQUOTE.handle?.(); - InsertTextEvent(Hotkey.BLOCKQUOTE.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.UNORDERED_LIST.codeMirrorCommand]: { - run: () => { - Hotkey.UNORDERED_LIST.handle?.(); - InsertTextEvent(Hotkey.UNORDERED_LIST.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.ORDERED_LIST.codeMirrorCommand]: { - run: () => { - Hotkey.ORDERED_LIST.handle?.(); - InsertTextEvent(Hotkey.ORDERED_LIST.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.INLINE_CODE.codeMirrorCommand]: { - run: () => { - Hotkey.INLINE_CODE.handle?.(); - InsertTextEvent(Hotkey.INLINE_CODE.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.CODE_BLOCK.codeMirrorCommand]: { - run: () => { - Hotkey.CODE_BLOCK.handle?.(); - InsertTextEvent(Hotkey.CODE_BLOCK.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.LINK.codeMirrorCommand]: { - run: () => { - Hotkey.LINK.handle?.(); - InsertTextEvent(Hotkey.LINK.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - [Hotkey.TABLE.codeMirrorCommand]: { - run: () => { - Hotkey.TABLE.handle?.(); - InsertTextEvent(Hotkey.TABLE.description as ToolbarType); - return true; - }, - preventDefault: true, - }, - - // Actions - [Hotkey.FULL_SCREEN.codeMirrorCommand]: { - run: () => { - Hotkey.FULL_SCREEN.handle?.(); - const currentState = useToolbarStore.getState(); - useToolbarStore.setState({ - isFullScreen: !currentState.isFullScreen, - }); - return true; - }, - preventDefault: true, - }, - [Hotkey.SAVE.codeMirrorCommand]: { - run: () => { - Hotkey.SAVE.handle?.(); - // TODO: 添加保存事件 - return true; - }, - preventDefault: true, - }, -}; +import { handleHotkeys } from "@/utils/handle-hotkeys"; export function createHotkeysExtension(): Extension { return keymap.of([ - ...Object.entries(KEYMAP).map(([key, value]) => ({ + ...Object.entries(handleHotkeys()).map(([key, value]) => ({ key, ...value, })), diff --git a/packages/mini-markdown-editor/src/index.tsx b/packages/mini-markdown-editor/src/index.tsx index 0dccd3d..ccf7db2 100644 --- a/packages/mini-markdown-editor/src/index.tsx +++ b/packages/mini-markdown-editor/src/index.tsx @@ -5,6 +5,9 @@ import Editor from "./EditorWrapper"; // 导出组件 export { Editor }; +// 导出配置 +export { toolbarConfig as ToolbarManager } from "@/config/toolbar"; + // 导出 ts 类型 export * from "@/types/code-mirror"; export * from "@/types/global-config"; diff --git a/packages/mini-markdown-editor/src/types/toolbar.ts b/packages/mini-markdown-editor/src/types/toolbar.ts index 5c0eab0..41581d5 100644 --- a/packages/mini-markdown-editor/src/types/toolbar.ts +++ b/packages/mini-markdown-editor/src/types/toolbar.ts @@ -1,3 +1,5 @@ +import { Hotkey } from "@/common/hotkeys"; + // 基础工具栏类型 export enum BaseToolbarType { HEADING = "heading", @@ -56,12 +58,22 @@ export interface BaseToolbarItem { export interface ToolbarItemListItem { title: string; type: string; + hotkey?: { + command: string; + description: string; + handle?: void | (() => void); + }; onClick?: (...args: any[]) => void | (() => void); } // 完整的工具栏项接口 export interface ToolbarItem extends BaseToolbarItem { list?: ToolbarItemListItem[]; + hotkey?: { + command: string; + description: string; + handle?: void | (() => void); + }; } // 工具栏上下文值接口 @@ -85,6 +97,7 @@ export interface ToolbarValidationResult { } // 工具栏配置验证函数 +// 更新验证函数以包含快捷键验证 export const validateToolbarItem = (item: ToolbarItem): ToolbarValidationResult => { const errors: string[] = []; @@ -96,6 +109,14 @@ export const validateToolbarItem = (item: ToolbarItem): ToolbarValidationResult errors.push("Toolbar item list must be an array"); } + if (item.hotkey) { + try { + new Hotkey(item.hotkey.command, item.hotkey.description || "", item.hotkey.handle); + } catch (e) { + errors.push(`Invalid hotkey configuration: ${(e as Error).message}`); + } + } + return { isValid: errors.length === 0, errors, diff --git a/packages/mini-markdown-editor/src/utils/handle-hotkeys.ts b/packages/mini-markdown-editor/src/utils/handle-hotkeys.ts new file mode 100644 index 0000000..52abd67 --- /dev/null +++ b/packages/mini-markdown-editor/src/utils/handle-hotkeys.ts @@ -0,0 +1,89 @@ +import { ToolbarItem, ToolbarType } from "@/types/toolbar"; +import { useToolbarStore } from "@/store/toolbar"; +import { InsertTextEvent } from "@/config/toolbar/event"; +import { toolbar } from "@/config/toolbar/base"; + +// 定义处理器类型 +type HotkeyHandler = { + run: () => boolean; + preventDefault: boolean; +}; +type HotkeyType = { + command: string; + description: string; + handle?: void | (() => void); +}; + +// 创建文本插入类型的处理器 +const createInsertTextHandler = (hotkey: HotkeyType): HotkeyHandler => ({ + run: () => { + hotkey.handle?.(); + InsertTextEvent(hotkey.description as ToolbarType); + return true; + }, + preventDefault: true, +}); + +// 创建全屏切换处理器 +const createFullScreenHandler = (hotkey: HotkeyType): HotkeyHandler => ({ + run: () => { + hotkey.handle?.(); + const currentState = useToolbarStore.getState(); + useToolbarStore.setState({ + isFullScreen: !currentState.isFullScreen, + }); + return true; + }, + preventDefault: true, +}); + +// 创建保存处理器 +const createSaveHandler = (hotkey: HotkeyType): HotkeyHandler => ({ + run: () => { + hotkey.handle?.(); + // TODO: 添加保存事件 + return true; + }, + preventDefault: true, +}); + +// 创建自定义处理器 +export const createCustomHandler = (hotkey: HotkeyType, handler: () => boolean): HotkeyHandler => ({ + run: () => { + hotkey.handle?.(); + return handler(); + }, + preventDefault: true, +}); + +export const handleHotkeys = () => { + const result: Record = {}; + + // 处理为cm可接受hotkeys的格式 + const processItem = (item: ToolbarItem) => { + // 处理有 hotkey 的项 + if (item.hotkey) { + if (item.type === "fullscreen") { + result[item.hotkey.command] = createFullScreenHandler(item.hotkey); + } else if (item.type === "save") { + result[item.hotkey.command] = createSaveHandler(item.hotkey); + } else { + result[item.hotkey.command] = createInsertTextHandler(item.hotkey); + } + } + + // 处理子列表 + if (item.list) { + item.list.forEach((listItem) => { + if (listItem.hotkey) { + result[listItem.hotkey.command] = createInsertTextHandler(listItem.hotkey); + } + }); + } + }; + + // 处理所有工具栏项 + toolbar.forEach(processItem); + + return result; +}; -- Gitee From 290187fdde6b9ecfcb9575ccfcec3385f0d32a66 Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sat, 8 Feb 2025 21:26:39 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat(mini-markdown-editor):=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B7=A5=E5=85=B7=E9=85=8D=E7=BD=AE=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=B7=A5=E5=85=B7=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mini-markdown-editor/src/App.tsx | 6 +- .../src/common/hotkeys.ts | 2 +- .../src/config/toolbar/base.tsx | 2 +- .../src/config/toolbar/index.ts | 4 +- .../src/extensions/codemirror/hotkeys.ts | 6 +- .../mini-markdown-editor/src/types/toolbar.ts | 4 +- .../src/utils/handle-hotkeys.ts | 93 +++++++++++-------- 7 files changed, 68 insertions(+), 49 deletions(-) diff --git a/packages/mini-markdown-editor/src/App.tsx b/packages/mini-markdown-editor/src/App.tsx index 041d707..d8df29e 100644 --- a/packages/mini-markdown-editor/src/App.tsx +++ b/packages/mini-markdown-editor/src/App.tsx @@ -80,10 +80,10 @@ const App: FC = () => { icon: OlIcon, description: "我是描述abc", hotkey: { - command: "mod+p", - description: "Make text bold", + command: "Mod-p", + description: "控制台输出def", handle: () => { - // 处理加粗逻辑 + console.log("我是快捷键输出def"); }, }, onClick: () => { diff --git a/packages/mini-markdown-editor/src/common/hotkeys.ts b/packages/mini-markdown-editor/src/common/hotkeys.ts index f389853..92bcb5e 100644 --- a/packages/mini-markdown-editor/src/common/hotkeys.ts +++ b/packages/mini-markdown-editor/src/common/hotkeys.ts @@ -33,7 +33,7 @@ export class Hotkey { constructor( public readonly command: Command, public readonly description: Description, - public readonly handle?: void | (() => void), + public readonly handle?: () => void, ) { Hotkey.validateCommand(command); } diff --git a/packages/mini-markdown-editor/src/config/toolbar/base.tsx b/packages/mini-markdown-editor/src/config/toolbar/base.tsx index 679d6a3..12f1cba 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/base.tsx +++ b/packages/mini-markdown-editor/src/config/toolbar/base.tsx @@ -25,7 +25,7 @@ import Save from "@/components/Toolbar/Save"; // 快捷键描述 import { Hotkey } from "@/common/hotkeys"; -export const toolbar: ToolbarItem[] = [ +export const defaultToolbar: ToolbarItem[] = [ { type: "heading", icon: HeadingIcon, diff --git a/packages/mini-markdown-editor/src/config/toolbar/index.ts b/packages/mini-markdown-editor/src/config/toolbar/index.ts index 2a6de61..042e76e 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/index.ts +++ b/packages/mini-markdown-editor/src/config/toolbar/index.ts @@ -1,5 +1,5 @@ import type { ToolbarItem, ToolbarType } from "@/types/toolbar"; -import { toolbar } from "./base"; +import { defaultToolbar } from "./base"; import { produce } from "immer"; import { BaseClass } from "../base"; @@ -85,4 +85,4 @@ class ToolbarConfig extends BaseClass { } } -export const toolbarConfig = new ToolbarConfig(toolbar); +export const toolbarConfig = new ToolbarConfig(defaultToolbar); diff --git a/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts b/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts index e5adae1..25ae6f7 100644 --- a/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts +++ b/packages/mini-markdown-editor/src/extensions/codemirror/hotkeys.ts @@ -1,14 +1,14 @@ import { Extension } from "@codemirror/state"; import { keymap } from "@codemirror/view"; import { handleHotkeys } from "@/utils/handle-hotkeys"; +import { toolbarConfig } from "@/config/toolbar"; export function createHotkeysExtension(): Extension { + const KEY_MAP = handleHotkeys(toolbarConfig.getAllToolbars()); return keymap.of([ - ...Object.entries(handleHotkeys()).map(([key, value]) => ({ + ...Object.entries(KEY_MAP).map(([key, value]) => ({ key, ...value, })), - - //! 留空 暴露外部其他内部快捷键 ]); } diff --git a/packages/mini-markdown-editor/src/types/toolbar.ts b/packages/mini-markdown-editor/src/types/toolbar.ts index 41581d5..a917254 100644 --- a/packages/mini-markdown-editor/src/types/toolbar.ts +++ b/packages/mini-markdown-editor/src/types/toolbar.ts @@ -61,7 +61,7 @@ export interface ToolbarItemListItem { hotkey?: { command: string; description: string; - handle?: void | (() => void); + handle?: () => void; }; onClick?: (...args: any[]) => void | (() => void); } @@ -72,7 +72,7 @@ export interface ToolbarItem extends BaseToolbarItem { hotkey?: { command: string; description: string; - handle?: void | (() => void); + handle?: () => void; }; } diff --git a/packages/mini-markdown-editor/src/utils/handle-hotkeys.ts b/packages/mini-markdown-editor/src/utils/handle-hotkeys.ts index 52abd67..280cfea 100644 --- a/packages/mini-markdown-editor/src/utils/handle-hotkeys.ts +++ b/packages/mini-markdown-editor/src/utils/handle-hotkeys.ts @@ -1,44 +1,46 @@ import { ToolbarItem, ToolbarType } from "@/types/toolbar"; +import { BaseToolbarType } from "@/types/toolbar"; import { useToolbarStore } from "@/store/toolbar"; import { InsertTextEvent } from "@/config/toolbar/event"; -import { toolbar } from "@/config/toolbar/base"; // 定义处理器类型 -type HotkeyHandler = { +interface HotkeyHandler { run: () => boolean; preventDefault: boolean; -}; -type HotkeyType = { +} + +// 定义热键类型 +interface HotkeyType { command: string; description: string; - handle?: void | (() => void); -}; + handle?: () => void; +} // 创建文本插入类型的处理器 -const createInsertTextHandler = (hotkey: HotkeyType): HotkeyHandler => ({ +export const createInsertTextHandler = (hotkey: HotkeyType): HotkeyHandler => ({ run: () => { - hotkey.handle?.(); InsertTextEvent(hotkey.description as ToolbarType); + hotkey.handle?.(); return true; }, preventDefault: true, }); // 创建全屏切换处理器 -const createFullScreenHandler = (hotkey: HotkeyType): HotkeyHandler => ({ +export const createFullScreenHandler = (hotkey: HotkeyType): HotkeyHandler => ({ run: () => { - hotkey.handle?.(); const currentState = useToolbarStore.getState(); useToolbarStore.setState({ isFullScreen: !currentState.isFullScreen, }); + hotkey.handle?.(); return true; }, preventDefault: true, }); // 创建保存处理器 -const createSaveHandler = (hotkey: HotkeyType): HotkeyHandler => ({ +export const createSaveHandler = (hotkey: HotkeyType): HotkeyHandler => ({ run: () => { hotkey.handle?.(); // TODO: 添加保存事件 @@ -48,42 +50,59 @@ const createSaveHandler = (hotkey: HotkeyType): HotkeyHandler => ({ }); // 创建自定义处理器 -export const createCustomHandler = (hotkey: HotkeyType, handler: () => boolean): HotkeyHandler => ({ +export const createCustomHandler = (hotkey: HotkeyType): HotkeyHandler => ({ run: () => { hotkey.handle?.(); - return handler(); + return true; }, preventDefault: true, }); -export const handleHotkeys = () => { - const result: Record = {}; +// 判断是否为基础工具栏类型(判断是否为新添加的工具) +const isBaseToolbarType = (type: string): type is BaseToolbarType => { + return Object.values(BaseToolbarType).includes(type as BaseToolbarType); +}; + +// 创建对应类型的处理器 +const createHandler = (item: ToolbarItem): HotkeyHandler | null => { + if (!item.hotkey) return null; - // 处理为cm可接受hotkeys的格式 - const processItem = (item: ToolbarItem) => { - // 处理有 hotkey 的项 - if (item.hotkey) { - if (item.type === "fullscreen") { - result[item.hotkey.command] = createFullScreenHandler(item.hotkey); - } else if (item.type === "save") { - result[item.hotkey.command] = createSaveHandler(item.hotkey); - } else { - result[item.hotkey.command] = createInsertTextHandler(item.hotkey); + switch (item.type) { + case BaseToolbarType.FULLSCREEN: + return createFullScreenHandler(item.hotkey); + case BaseToolbarType.SAVE: + return createSaveHandler(item.hotkey); + default: + if (isBaseToolbarType(item.type)) { + return createInsertTextHandler(item.hotkey); } - } + // 对于自定义类型,使用createCustomHandler + return createCustomHandler(item.hotkey); + } +}; - // 处理子列表 - if (item.list) { - item.list.forEach((listItem) => { - if (listItem.hotkey) { - result[listItem.hotkey.command] = createInsertTextHandler(listItem.hotkey); - } - }); - } - }; +// 处理单个工具栏项 +const processToolbarItem = (result: Record, item: ToolbarItem): void => { + const handler = createHandler(item); + if (item.hotkey && handler) { + result[item.hotkey.command] = handler; + } - // 处理所有工具栏项 - toolbar.forEach(processItem); + // 处理子列表 + if (item.list?.length) { + item.list.forEach((listItem) => { + if (listItem.hotkey) { + const listItemHandler = createHandler(listItem); + if (listItemHandler) { + result[listItem.hotkey.command] = listItemHandler; + } + } + }); + } +}; +export const handleHotkeys = (toolbar: ToolbarItem[]): Record => { + const result: Record = {}; + toolbar.forEach((item) => processToolbarItem(result, item)); return result; }; -- Gitee From 63a083252e288fcf128609ea6f524f479138bbd9 Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sat, 8 Feb 2025 23:05:12 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat(mini-markdown-editor):=20=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E5=85=A8=E5=B1=80Config=E7=B1=BB=E5=9E=8B=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0cm=E7=9A=84props=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/EditorWrapper.tsx | 2 +- .../src/components/Editor/index.tsx | 15 +++++++++--- .../mini-markdown-editor/src/config/global.ts | 4 ++-- .../src/types/global-config.ts | 21 +++++++++++++++-- .../src/utils/filter-context-props.tsx | 23 +++++++++++++++++++ 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 packages/mini-markdown-editor/src/utils/filter-context-props.tsx diff --git a/packages/mini-markdown-editor/src/EditorWrapper.tsx b/packages/mini-markdown-editor/src/EditorWrapper.tsx index 047e75f..c972820 100644 --- a/packages/mini-markdown-editor/src/EditorWrapper.tsx +++ b/packages/mini-markdown-editor/src/EditorWrapper.tsx @@ -174,7 +174,7 @@ const EditorWrapper = forwardRef((config, ref) => { } + editor={} preview={} /> diff --git a/packages/mini-markdown-editor/src/components/Editor/index.tsx b/packages/mini-markdown-editor/src/components/Editor/index.tsx index 741f4da..5d9bb45 100644 --- a/packages/mini-markdown-editor/src/components/Editor/index.tsx +++ b/packages/mini-markdown-editor/src/components/Editor/index.tsx @@ -7,7 +7,8 @@ import { handleEditorScroll } from "@/utils/handle-scroll"; import { usePersistEditorContent } from "@/hooks/use-persist-editor-content"; import { ConfigContext } from "../providers/config-provider"; import { createEditorExtensions } from "@/extensions/codemirror"; -import { Callback } from "@/types/global-config"; +import { Callback, GlobalConfig } from "@/types/global-config"; +import { filterContextProps } from "@/utils/filter-context-props"; const ScrollWrapper = styled.div<{ $lineNumbers?: boolean; @@ -40,7 +41,13 @@ const ScrollWrapper = styled.div<{ } `; -const Editor: FC<{ isSyncScroll: boolean }> = ({ isSyncScroll }) => { +interface EditorProps extends GlobalConfig { + className?: string; + style?: React.CSSProperties; + isSyncScroll: boolean; +} + +const Editor: FC = (props) => { const { content, setContent, @@ -86,6 +93,8 @@ const Editor: FC<{ isSyncScroll: boolean }> = ({ isSyncScroll }) => { const { theme, lineNumbers, enableShortcuts, onChange, onDragUpload, onPatseUpload } = useContext(ConfigContext); + console.log(filterContextProps(useContext(ConfigContext), props)); + const handleChange = (val: string, editView: ViewUpdate) => { // 更新store setContent(val); @@ -103,7 +112,7 @@ const Editor: FC<{ isSyncScroll: boolean }> = ({ isSyncScroll }) => { scroll: () => { if (scrollWrapper !== "editor") return; const view = editorViewRef.current; - if (!(view && previewView && isSyncScroll)) return; + if (!(view && previewView && props.isSyncScroll)) return; handleEditorScroll({ editorView: view, previewView }); }, }); diff --git a/packages/mini-markdown-editor/src/config/global.ts b/packages/mini-markdown-editor/src/config/global.ts index 9b90b35..3126513 100644 --- a/packages/mini-markdown-editor/src/config/global.ts +++ b/packages/mini-markdown-editor/src/config/global.ts @@ -1,7 +1,7 @@ -import { GlobalConfig } from "@/types/global-config"; +import { GlobalContextConfig } from "@/types/global-config"; // 默认配置 -export const defaultGlobalConfig: GlobalConfig = { +export const defaultGlobalConfig: GlobalContextConfig = { status: true, // 底部状态栏 theme: "light", // 主题 local: true, // 是否开启本地存储 diff --git a/packages/mini-markdown-editor/src/types/global-config.ts b/packages/mini-markdown-editor/src/types/global-config.ts index 6b5a582..1e7e00a 100644 --- a/packages/mini-markdown-editor/src/types/global-config.ts +++ b/packages/mini-markdown-editor/src/types/global-config.ts @@ -1,7 +1,7 @@ -import { EditorView, ViewUpdate } from "@uiw/react-codemirror"; +import { EditorView, ReactCodeMirrorProps, ViewUpdate } from "@uiw/react-codemirror"; import { ToolbarType } from "./toolbar"; -export interface GlobalConfig { +export interface GlobalConfig extends ReactCodeMirrorProps { /** * 编辑器内容 * @type {string} @@ -66,3 +66,20 @@ export interface GlobalConfig { } export type Callback = (param: { url: string; alt?: string }) => void; + +export type GlobalContextConfig = Pick< + GlobalConfig, + | "theme" + | "initialValue" + | "toolbar" + | "status" + | "local" + | "lineNumbers" + | "enableShortcuts" + | "onUpload" + | "onDragUpload" + | "onPatseUpload" + | "onSave" + | "onChange" +>; +export type EditorConfig = Omit; diff --git a/packages/mini-markdown-editor/src/utils/filter-context-props.tsx b/packages/mini-markdown-editor/src/utils/filter-context-props.tsx new file mode 100644 index 0000000..ecfc44f --- /dev/null +++ b/packages/mini-markdown-editor/src/utils/filter-context-props.tsx @@ -0,0 +1,23 @@ +import { GlobalConfig, GlobalContextConfig } from "@/types/global-config"; + +export const filterContextProps = ( + context: Partial, + props: GlobalConfig, +): Omit => { + // 1. 获取 context 的所有键和固定要过滤的键 + const contextKeys = new Set([...Object.keys(context), "isSyncScroll"]); + + // 2. 从 props 中解构出固定要保留的属性 + const { className, style, ...restProps } = props; + + // 3. 创建新对象,过滤掉 context 的属性和 isSync + const filteredProps = Object.fromEntries( + Object.entries(restProps).filter(([key]) => !contextKeys.has(key)), + ); + + return { + ...filteredProps, + className, + style, + } as Omit; +}; -- Gitee From 1b2f1db29bf90d668aac82ba41d40db478ee38fc Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sat, 8 Feb 2025 23:10:17 +0800 Subject: [PATCH 6/8] =?UTF-8?q?fix(mini-markdown-editor):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E7=A4=BA=E4=BE=8B=E6=B7=BB=E5=8A=A0=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决工具已存在的问题 --- packages/mini-markdown-editor/src/App.tsx | 38 +++++++++++-------- .../src/utils/filter-context-props.tsx | 4 +- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/mini-markdown-editor/src/App.tsx b/packages/mini-markdown-editor/src/App.tsx index d8df29e..ca3e55f 100644 --- a/packages/mini-markdown-editor/src/App.tsx +++ b/packages/mini-markdown-editor/src/App.tsx @@ -74,22 +74,28 @@ const App: FC = () => { }, []); // 添加abc工具 - toolbarConfig.addToolbar({ - type: "abc", - title: "我是测试abc", - icon: OlIcon, - description: "我是描述abc", - hotkey: { - command: "Mod-p", - description: "控制台输出def", - handle: () => { - console.log("我是快捷键输出def"); - }, - }, - onClick: () => { - console.log("我是输出abc"); - }, - }); + useEffect(() => { + try { + toolbarConfig.addToolbar({ + type: "abc", + title: "我是测试abc", + icon: OlIcon, + description: "我是描述abc", + hotkey: { + command: "Mod-p", + description: "控制台输出def", + handle: () => { + console.log("我是快捷键输出def"); + }, + }, + onClick: () => { + console.log("我是输出abc"); + }, + }); + } catch (error) { + console.error(error); + } + }, []); return ( diff --git a/packages/mini-markdown-editor/src/utils/filter-context-props.tsx b/packages/mini-markdown-editor/src/utils/filter-context-props.tsx index ecfc44f..42eb742 100644 --- a/packages/mini-markdown-editor/src/utils/filter-context-props.tsx +++ b/packages/mini-markdown-editor/src/utils/filter-context-props.tsx @@ -4,13 +4,11 @@ export const filterContextProps = ( context: Partial, props: GlobalConfig, ): Omit => { - // 1. 获取 context 的所有键和固定要过滤的键 + // 获取 context 的所有键和固定要过滤的键 const contextKeys = new Set([...Object.keys(context), "isSyncScroll"]); - // 2. 从 props 中解构出固定要保留的属性 const { className, style, ...restProps } = props; - // 3. 创建新对象,过滤掉 context 的属性和 isSync const filteredProps = Object.fromEntries( Object.entries(restProps).filter(([key]) => !contextKeys.has(key)), ); -- Gitee From f434a1f16ae8fa8752a22ee8fe3a1ba57845bdfb Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sat, 8 Feb 2025 23:47:15 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix(mini-markdown-editor):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=BF=87=E6=BB=A4props=E4=B8=8D=E5=87=86=E7=A1=AE?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E7=A9=BA=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mini-markdown-editor/src/App.tsx | 1 + .../src/components/Editor/index.tsx | 5 ++++- .../mini-markdown-editor/src/config/global.ts | 5 +++++ ...text-props.tsx => filter-context-props.ts} | 20 +++++++++++-------- 4 files changed, 22 insertions(+), 9 deletions(-) rename packages/mini-markdown-editor/src/utils/{filter-context-props.tsx => filter-context-props.ts} (38%) diff --git a/packages/mini-markdown-editor/src/App.tsx b/packages/mini-markdown-editor/src/App.tsx index ca3e55f..31eeb4d 100644 --- a/packages/mini-markdown-editor/src/App.tsx +++ b/packages/mini-markdown-editor/src/App.tsx @@ -169,6 +169,7 @@ const App: FC = () => { 获取预览区实例 = (props) => { const { theme, lineNumbers, enableShortcuts, onChange, onDragUpload, onPatseUpload } = useContext(ConfigContext); - console.log(filterContextProps(useContext(ConfigContext), props)); + const CodeMirrorProps = useMemo(() => { + return filterContextProps(props); + }, [props]); const handleChange = (val: string, editView: ViewUpdate) => { // 更新store @@ -164,6 +166,7 @@ const Editor: FC = (props) => { style={{ height: "100%" }} onChange={handleChange} onMouseEnter={handleMouseEnter} + {...CodeMirrorProps} /> ); diff --git a/packages/mini-markdown-editor/src/config/global.ts b/packages/mini-markdown-editor/src/config/global.ts index 3126513..ed2a041 100644 --- a/packages/mini-markdown-editor/src/config/global.ts +++ b/packages/mini-markdown-editor/src/config/global.ts @@ -7,4 +7,9 @@ export const defaultGlobalConfig: GlobalContextConfig = { local: true, // 是否开启本地存储 lineNumbers: false, // 是否显示行号 enableShortcuts: true, // 是否开启快捷键 + onSave: () => {}, // 保存触发 + onChange: () => {}, // 内容改变触发 + onUpload: () => {}, // 上传图片触发 + onDragUpload: () => {}, // 拖拽上传图片触发 + onPatseUpload: () => {}, // 粘贴上传图片触发 }; diff --git a/packages/mini-markdown-editor/src/utils/filter-context-props.tsx b/packages/mini-markdown-editor/src/utils/filter-context-props.ts similarity index 38% rename from packages/mini-markdown-editor/src/utils/filter-context-props.tsx rename to packages/mini-markdown-editor/src/utils/filter-context-props.ts index 42eb742..f59f98f 100644 --- a/packages/mini-markdown-editor/src/utils/filter-context-props.tsx +++ b/packages/mini-markdown-editor/src/utils/filter-context-props.ts @@ -1,21 +1,25 @@ +import { defaultGlobalConfig } from "@/config/global"; import { GlobalConfig, GlobalContextConfig } from "@/types/global-config"; -export const filterContextProps = ( - context: Partial, - props: GlobalConfig, -): Omit => { - // 获取 context 的所有键和固定要过滤的键 - const contextKeys = new Set([...Object.keys(context), "isSyncScroll"]); +type GlobalContextKeys = keyof GlobalContextConfig | "isSyncScroll"; + +export const filterContextProps = (props: GlobalConfig): Omit => { + // 快速查找 + const contextKeys = new Set( + Object.keys(defaultGlobalConfig) as Array, + ); + + contextKeys.add("isSyncScroll" as GlobalContextKeys); const { className, style, ...restProps } = props; const filteredProps = Object.fromEntries( - Object.entries(restProps).filter(([key]) => !contextKeys.has(key)), + Object.entries(restProps).filter(([key]) => !contextKeys.has(key as GlobalContextKeys)), ); return { ...filteredProps, className, style, - } as Omit; + } as Omit; }; -- Gitee From 170d01f2e1a9de08a0bc0393060d3a49593948b0 Mon Sep 17 00:00:00 2001 From: tabzzz Date: Sun, 9 Feb 2025 00:06:12 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix(mini-markdown-editor):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=90=8C=E6=AD=A5=E6=BB=9A=E5=8A=A8=E5=A4=B1=E6=95=88?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8=E6=89=A9=E5=B1=95=E8=BF=90=E7=AE=97?= =?UTF-8?q?=E7=AC=A6=E4=BC=98=E5=8C=96=E8=A7=A3=E6=9E=90props?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Editor/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mini-markdown-editor/src/components/Editor/index.tsx b/packages/mini-markdown-editor/src/components/Editor/index.tsx index 4c8ee9d..314121c 100644 --- a/packages/mini-markdown-editor/src/components/Editor/index.tsx +++ b/packages/mini-markdown-editor/src/components/Editor/index.tsx @@ -93,10 +93,6 @@ const Editor: FC = (props) => { const { theme, lineNumbers, enableShortcuts, onChange, onDragUpload, onPatseUpload } = useContext(ConfigContext); - const CodeMirrorProps = useMemo(() => { - return filterContextProps(props); - }, [props]); - const handleChange = (val: string, editView: ViewUpdate) => { // 更新store setContent(val); @@ -119,6 +115,10 @@ const Editor: FC = (props) => { }, }); + const { className, style, ...CodeMirrorProps } = useMemo(() => { + return filterContextProps(props); + }, [props]); + const handleMouseEnter = () => { setScrollWrapper("editor"); }; @@ -148,7 +148,7 @@ const Editor: FC = (props) => { return ( = (props) => { defaultKeymap: true, }} autoFocus={true} - style={{ height: "100%" }} + style={{ height: "100%", ...(style || {}) }} onChange={handleChange} onMouseEnter={handleMouseEnter} {...CodeMirrorProps} -- Gitee