diff --git a/packages/mini-markdown-editor/src/App.tsx b/packages/mini-markdown-editor/src/App.tsx index ce6f37db427dd1a0bca834e336d28b27a9693e03..cb660e01263982955d89ab0ecf3edc668349f760 100644 --- a/packages/mini-markdown-editor/src/App.tsx +++ b/packages/mini-markdown-editor/src/App.tsx @@ -10,8 +10,8 @@ import { EditorView } from "@uiw/react-codemirror"; import { EditorRef } from "./types/ref"; // import { toolbarConfig as toolbarManager } from "./config/toolbar"; import UlIcon from "@/assets/images/ul.svg?raw"; -import { toolbarConfig } from "./config/toolbar"; -import { insertContent } from "./utils/insert-content"; +// import { toolbarConfig } from "./config/toolbar"; +// import { insertContent } from "./utils/insert-content"; // import { insertContent } from "@/utils/insert-content"; // import { toolbarConfig } from "./config/toolbar"; // import { ToolbarEvents, ToolbarItem } from "./types/toolbar"; @@ -78,56 +78,47 @@ const App: FC = () => { } }, []); - useEffect(() => { - // 先检查工具栏是否存在 - const allToolbars = toolbarConfig.getAllToolbars(); - console.log("当前所有工具栏:", allToolbars); + // useEffect(() => { + // try { + // // toolbarConfig.addToolItem({ + // // type: "abc", + // // title: "我是测试abc", + // // icon: UlIcon, + // // description: "我是描述abc", + // // hotkey: { + // // command: "Mod-l", + // // description: "控制台输出def", + // // handle: () => { + // // console.log("我是快捷键输出def"); + // // }, + // // }, + // // onClick: () => { + // // console.log("我是输出abc"); + // // // InsertText("123", {anchor: 1, head: 1}); + // // insertContent.insertTextAtCursor("123"); + // // }, + // // }); + // console.log(toolbarConfig.getAllToolbarsOrder()); - // 检查特定工具栏是否存在 - const boldToolbar = allToolbars.find((t) => t.type === "bold"); - console.log("bold工具栏:", boldToolbar); + // // 单个顺序修改测试 + // // toolbarConfig.setToolbarOrder("bold", 0); - try { - toolbarConfig.updateToolbarItem("bold", { - onClick: () => { - console.log("我是粗体"); - }, - hotkey: { - command: "Mod-b", - description: "控制台输出粗体", - handle: () => { - console.log("我是快捷键粗体"); - }, - }, - }); - } catch (error) { - console.error("更新工具栏失败:", error); - } + // // 批量设置顺序 + // // toolbarConfig.setToolbarsOrder({ + // // bold: 0, + // // italic: 1, + // // underline: 2, + // // }); - try { - toolbarConfig.addToolItem({ - type: "abc", - title: "我是测试abc", - icon: UlIcon, - description: "我是描述abc", - hotkey: { - command: "Mod-l", - description: "控制台输出def", - handle: () => { - console.log("我是快捷键输出def"); - }, - }, - onClick: () => { - console.log("我是输出abc"); - // InsertText("123", {anchor: 1, head: 1}); - insertContent.insertTextAtCursor("123"); - }, - }); - console.log("添加工具栏成功"); - } catch (error) { - console.error("添加工具栏失败:", error); - } - }, []); + // // 完全重排序 + // // 此方法需要所有工具栏都传入,否则会报错 + // // toolbarConfig.reorderToolbar([""]) + + // toolbarConfig.swapToolbarsPosition("heading", "bold"); + // } catch (e) { + // console.log(e); + // } + // }, []); return ( @@ -202,6 +193,7 @@ const App: FC = () => { {/* */} { onPatseUpload={handlePatseUpload} ref={editorRef} toolbars={{ - excludeTools: ["italic"], + // excludeTools: ["italic"], addTools: [ { type: "123", @@ -233,6 +225,7 @@ const App: FC = () => { }, }, ], + orderTools: [{ type: "123", order: 0 }], }} value="## Hello World." /> diff --git a/packages/mini-markdown-editor/src/EditorWrapper.tsx b/packages/mini-markdown-editor/src/EditorWrapper.tsx index 2f7078e9a6b8cc5165826539449706ce200f1168..1231822700f0168b1a194eb362aa8c2b2d3230a6 100644 --- a/packages/mini-markdown-editor/src/EditorWrapper.tsx +++ b/packages/mini-markdown-editor/src/EditorWrapper.tsx @@ -1,4 +1,4 @@ -import { FC, forwardRef, Fragment, useDeferredValue } from "react"; +import { FC, forwardRef, Fragment, useDeferredValue, useEffect } from "react"; import styled from "styled-components"; import { useEditorContentStore } from "@/store/editor"; import Toolbar from "@/components/Toolbar"; @@ -15,6 +15,7 @@ import GlobalTheme from "./theme/global-theme"; import { EditorRef } from "./types/ref"; import { useExposeHandle } from "./hooks/use-expose-handle"; import { defaultGlobalConfig } from "./config/global"; +import { setCurrentLocale } from "./locales/index2"; const Container = styled.div` width: 100%; @@ -155,6 +156,11 @@ const EditorWrapper = forwardRef((config, ref) => { const isFullScreen = useToolbarStore((state) => state.isFullScreen); const { isSyncScroll, updateSyncScrollStatus } = useInitSyncScrollStatus(); + // 语言初始化 + useEffect(() => { + setCurrentLocale(config.locale); + }, [config.locale]); + // 外部ref使用的方法 useExposeHandle(ref); diff --git a/packages/mini-markdown-editor/src/components/Toolbar/ToolbarItem.tsx b/packages/mini-markdown-editor/src/components/Toolbar/ToolbarItem.tsx index 32ee71be533a47df0bb799781616c111aca4d095..83cb92eb4db4a88fb42a048abf00446ded47e6c7 100644 --- a/packages/mini-markdown-editor/src/components/Toolbar/ToolbarItem.tsx +++ b/packages/mini-markdown-editor/src/components/Toolbar/ToolbarItem.tsx @@ -3,6 +3,8 @@ import { DropDownMenu } from "../base/DropDownMenu"; import IconTooltip from "../base/IconTooltip"; import { FC, memo } from "react"; import type { ToolbarItem as ToolbarItemType } from "@/types/toolbar"; +import { useGlobalConfig } from "@/hooks/use-global-config"; +import { createTranslator, TRANSLATION_KEYS } from "@/locales"; const ToolbarItemWrapper = styled.div` width: 16px; @@ -48,10 +50,13 @@ const ToolbarItemRender: FC = ({ onClick, component, }) => { + const { locale } = useGlobalConfig(); + const t = createTranslator(locale); + if (list && list.length > 0) { return ( <> - + {/* {title} */} {icon &&
}
@@ -62,7 +67,11 @@ const ToolbarItemRender: FC = ({ } else { return ( <> - + {/* {title} */} {icon &&
}
diff --git a/packages/mini-markdown-editor/src/components/base/DropDownMenu.tsx b/packages/mini-markdown-editor/src/components/base/DropDownMenu.tsx index a82bb2d152c04b9bf0f5ac1cddfeddc7de48554a..cd98e49bdca49ab621bea3474acb387a5b4d7560 100644 --- a/packages/mini-markdown-editor/src/components/base/DropDownMenu.tsx +++ b/packages/mini-markdown-editor/src/components/base/DropDownMenu.tsx @@ -3,14 +3,16 @@ import { Dropdown } from "antd"; import type { MenuProps } from "antd"; import { ToolbarItemListItem } from "@/types/toolbar"; import { render, renderKey } from "@/config/toolbar/base"; +import { TRANSLATION_KEYS } from "@/locales"; interface DropDownMenuProps { children: React.ReactNode; list?: ToolbarItemListItem[]; + t?: (key: TRANSLATION_KEYS) => string; dropdownRender?: () => React.ReactNode; } -export const DropDownMenu = memo(({ children, list, dropdownRender }: DropDownMenuProps) => { +export const DropDownMenu = memo(({ children, list, dropdownRender, t }: DropDownMenuProps) => { // list const items: MenuProps["items"] = useMemo(() => { if (!list) return undefined; @@ -18,10 +20,10 @@ export const DropDownMenu = memo(({ children, list, dropdownRender }: DropDownMe const renderKeys = Object.keys(render) as renderKey[]; return list.map((item) => { return { - key: item.title, + key: item.title!, label: renderKeys.includes(item.type as renderKey) ? render[item.type as renderKey] - : item.title, + : t?.(item.title! as TRANSLATION_KEYS) || item.title!, }; }); }, [list]); diff --git a/packages/mini-markdown-editor/src/components/providers/toolbar-provider.tsx b/packages/mini-markdown-editor/src/components/providers/toolbar-provider.tsx index 37bf5f54a433d2e4c5dac0d7fa9619cec1e862a9..f84915dc6f6b2f8e9a34bf97050a6f9a76b4604a 100644 --- a/packages/mini-markdown-editor/src/components/providers/toolbar-provider.tsx +++ b/packages/mini-markdown-editor/src/components/providers/toolbar-provider.tsx @@ -9,45 +9,70 @@ interface ToolbarProviderProps { toolbarConfig?: { addTools?: ToolbarItem[]; excludeTools?: string[]; + orderTools?: { + type: string; + order: number; + }[]; }; } -// 工具栏处理(针对是否有过滤工具) +// 处理工具栏的函数(过滤、添加和排序) const processToolbars = ( defaultToolbars: ToolbarItem[], additionalTools: ToolbarItem[] = [], excludeTools?: string[], + toolsOrder?: { type: string; order: number }[], ): ToolbarItem[] => { + let processedToolbars = [...defaultToolbars]; + + // 过滤工具 if (excludeTools?.length) { - return [ - ...defaultToolbars.filter((item) => !excludeTools.includes(item.type)), - ...additionalTools, - ]; + processedToolbars = processedToolbars.filter((item) => !excludeTools.includes(item.type)); + } + + // 添加工具 + if (additionalTools.length) { + processedToolbars = [...processedToolbars, ...additionalTools]; } - return [...defaultToolbars, ...additionalTools]; + + // 判断并处理工具顺序 + if (toolsOrder?.length) { + const orderMap = new Map(toolsOrder.map((item) => [item.type, item.order])); + processedToolbars = [...processedToolbars].sort((a, b) => { + const orderA = orderMap.get(a.type) ?? Number.MAX_SAFE_INTEGER; + const orderB = orderMap.get(b.type) ?? Number.MAX_SAFE_INTEGER; + return orderA - orderB; + }); + } + + return processedToolbars; }; export const ToolbarProvider = React.memo(({ children, toolbarConfig }) => { const [toolbars, setToolbars] = useState(() => { - // 初始化工具栏 const defaultToolbars = toolbarManager.getDefaultToolbar(); - return processToolbars(defaultToolbars, toolbarConfig?.addTools, toolbarConfig?.excludeTools); + return processToolbars( + defaultToolbars, + toolbarConfig?.addTools, + toolbarConfig?.excludeTools, + toolbarConfig?.orderTools, + ); }); - // 更新工具栏 const updateToolbars = useCallback(() => { const defaultToolbars = toolbarManager.getDefaultToolbar(); const newToolbars = processToolbars( defaultToolbars, toolbarConfig?.addTools, toolbarConfig?.excludeTools, + toolbarConfig?.orderTools, ); setToolbars(newToolbars); toolbarManager.updateToolbars(newToolbars); - }, [toolbarConfig?.addTools, toolbarConfig?.excludeTools]); + }, [toolbarConfig?.addTools, toolbarConfig?.excludeTools, toolbarConfig?.orderTools]); - // 监听配置变化 + // 更新工具栏 useEffect(() => { updateToolbars(); }, [updateToolbars]); diff --git a/packages/mini-markdown-editor/src/config/toolbar/base.tsx b/packages/mini-markdown-editor/src/config/toolbar/base.tsx index 12f1cba22e1e42b71bf11d25222e0d95c32e9027..4901655053d937da3d805819b03f6027970ebf8e 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/base.tsx +++ b/packages/mini-markdown-editor/src/config/toolbar/base.tsx @@ -24,45 +24,47 @@ import Emoji from "@/components/Toolbar/Emoji"; import Save from "@/components/Toolbar/Save"; // 快捷键描述 import { Hotkey } from "@/common/hotkeys"; +// 多语言支持 +import { TOOLBAR_KEYS } from "@/locales/keys"; export const defaultToolbar: ToolbarItem[] = [ { type: "heading", icon: HeadingIcon, - title: "标题", + title: TOOLBAR_KEYS.TOOLBAR.heading, list: [ { - title: "H1 一级标题", + title: TOOLBAR_KEYS.TOOLBAR.HEADING_ITEMS["heading-1"], type: "heading-1", hotkey: Hotkey.TITLE.FIRST.toConfig(), onClick: () => InsertTextEvent("heading-1"), }, { - title: "H2 二级标题", + title: TOOLBAR_KEYS.TOOLBAR.HEADING_ITEMS["heading-2"], type: "heading-2", hotkey: Hotkey.TITLE.SECOND.toConfig(), onClick: () => InsertTextEvent("heading-2"), }, { - title: "H3 三级标题", + title: TOOLBAR_KEYS.TOOLBAR.HEADING_ITEMS["heading-3"], type: "heading-3", hotkey: Hotkey.TITLE.THIRD.toConfig(), onClick: () => InsertTextEvent("heading-3"), }, { - title: "H4 四级标题", + title: TOOLBAR_KEYS.TOOLBAR.HEADING_ITEMS["heading-4"], type: "heading-4", hotkey: Hotkey.TITLE.FOURTH.toConfig(), onClick: () => InsertTextEvent("heading-4"), }, { - title: "H5 五级标题", + title: TOOLBAR_KEYS.TOOLBAR.HEADING_ITEMS["heading-5"], type: "heading-5", hotkey: Hotkey.TITLE.FIFTH.toConfig(), onClick: () => InsertTextEvent("heading-5"), }, { - title: "H6 六级标题", + title: TOOLBAR_KEYS.TOOLBAR.HEADING_ITEMS["heading-6"], type: "heading-6", hotkey: Hotkey.TITLE.SIXTH.toConfig(), onClick: () => InsertTextEvent("heading-6"), @@ -72,7 +74,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "bold", icon: BoldIcon, - title: "加粗", + title: TOOLBAR_KEYS.TOOLBAR.bold, description: Hotkey.BOLD.readableCommand, hotkey: Hotkey.BOLD.toConfig(), onClick: () => InsertTextEvent("bold"), @@ -80,7 +82,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "italic", icon: ItalicIcon, - title: "斜体", + title: TOOLBAR_KEYS.TOOLBAR.italic, description: Hotkey.ITALIC.readableCommand, hotkey: Hotkey.ITALIC.toConfig(), onClick: () => InsertTextEvent("italic"), @@ -88,7 +90,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "underline", icon: UnderlineIcon, - title: "下划线", + title: TOOLBAR_KEYS.TOOLBAR.underline, description: Hotkey.UNDERLINE.readableCommand, hotkey: Hotkey.UNDERLINE.toConfig(), onClick: () => InsertTextEvent("underline"), @@ -96,7 +98,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "delete", icon: DeleteIcon, - title: "删除线", + title: TOOLBAR_KEYS.TOOLBAR.delete, description: Hotkey.DELETE.readableCommand, hotkey: Hotkey.DELETE.toConfig(), onClick: () => InsertTextEvent("delete"), @@ -107,7 +109,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "blockquote", icon: BlockquoteIcon, - title: "引用", + title: TOOLBAR_KEYS.TOOLBAR.blockquote, description: Hotkey.BLOCKQUOTE.readableCommand, hotkey: Hotkey.BLOCKQUOTE.toConfig(), onClick: () => InsertTextEvent("blockquote"), @@ -115,7 +117,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "ul", icon: UlIcon, - title: "无序列表", + title: TOOLBAR_KEYS.TOOLBAR.ul, description: Hotkey.UNORDERED_LIST.readableCommand, hotkey: Hotkey.UNORDERED_LIST.toConfig(), onClick: () => InsertTextEvent("ul"), @@ -123,7 +125,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "ol", icon: OlIcon, - title: "有序列表", + title: TOOLBAR_KEYS.TOOLBAR.ol, description: Hotkey.ORDERED_LIST.readableCommand, hotkey: Hotkey.ORDERED_LIST.toConfig(), onClick: () => InsertTextEvent("ol"), @@ -131,7 +133,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "inlinecode", icon: InlineCodeIcon, - title: "行内代码", + title: TOOLBAR_KEYS.TOOLBAR.inlinecode, description: Hotkey.INLINE_CODE.readableCommand, hotkey: Hotkey.INLINE_CODE.toConfig(), onClick: () => InsertTextEvent("inlinecode"), @@ -139,7 +141,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "code", icon: CodeIcon, - title: "代码块", + title: TOOLBAR_KEYS.TOOLBAR.code, description: Hotkey.CODE_BLOCK.readableCommand, hotkey: Hotkey.CODE_BLOCK.toConfig(), onClick: () => InsertTextEvent("code"), @@ -147,7 +149,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "link", icon: LinkIcon, - title: "链接", + title: TOOLBAR_KEYS.TOOLBAR.link, description: Hotkey.LINK.readableCommand, hotkey: Hotkey.LINK.toConfig(), onClick: () => InsertTextEvent("link"), @@ -158,13 +160,13 @@ export const defaultToolbar: ToolbarItem[] = [ title: "图片", list: [ { - title: "添加链接", + title: TOOLBAR_KEYS.TOOLBAR.IMAGE_ITEMS["image-link"], type: "image-link", hotkey: Hotkey.LINK.toConfig(), onClick: () => InsertTextEvent("image-link"), }, { - title: "上传图片", + title: TOOLBAR_KEYS.TOOLBAR.IMAGE_ITEMS["image-upload"], type: "image-upload", }, ], @@ -172,7 +174,7 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "table", icon: TableIcon, - title: "表格", + title: TOOLBAR_KEYS.TOOLBAR.table, description: Hotkey.TABLE.readableCommand, hotkey: Hotkey.TABLE.toConfig(), onClick: () => InsertTextEvent("table"), @@ -187,14 +189,14 @@ export const defaultToolbar: ToolbarItem[] = [ { type: "undo", icon: UndoIcon, - title: "撤销", + title: TOOLBAR_KEYS.TOOLBAR.undo, description: Hotkey.UNDO.readableCommand, onClick: () => UndoEvent(), }, { type: "redo", icon: RedoIcon, - title: "重做", + title: TOOLBAR_KEYS.TOOLBAR.redo, description: Hotkey.REDO.readableCommand, onClick: () => RedoEvent(), }, diff --git a/packages/mini-markdown-editor/src/config/toolbar/index.ts b/packages/mini-markdown-editor/src/config/toolbar/index.ts index 5823937de10c7b59ef2626176762a006f1425cd0..206e87abb610dd3f4108e2c3cc6ff6ac740196a9 100644 --- a/packages/mini-markdown-editor/src/config/toolbar/index.ts +++ b/packages/mini-markdown-editor/src/config/toolbar/index.ts @@ -7,6 +7,7 @@ import { ToolbarEvents } from "@/types/toolbar"; class ToolbarConfig extends BaseClass { private toolbars: ToolbarItem[]; private readonly defaultToolbars: ToolbarItem[]; + private toolbarOrderMap: Map; private initialized: boolean = false; constructor(initialToolbars: ToolbarItem[]) { @@ -14,25 +15,56 @@ class ToolbarConfig extends BaseClass { name: "toolbarConfig", maxListeners: 10, }); + this.toolbarOrderMap = new Map(); this.defaultToolbars = [...initialToolbars]; this.toolbars = this.initToolbar(); + this.initToolbarOrderMap(); this.initialized = true; this.emit(ToolbarEvents.TOOLBAR_RESET, this.toolbars); } + // ---------------------初始化函数----------------------- + // 初始化默认工具栏内容 private initToolbar(): ToolbarItem[] { return [...this.defaultToolbars]; } + // 初始化顺序映射 + private initToolbarOrderMap(): void { + this.toolbars.forEach((toolbar, index) => { + this.toolbarOrderMap.set(toolbar.type, index); + }); + } + + // ---------------------获取(get)函数----------------------- + // 获取默认工具栏内容 public getDefaultToolbar(): ToolbarItem[] { return this.defaultToolbars; } - // 更新工具栏内容 - public updateToolbars(newToolbars: ToolbarItem[]) { - this.toolbars = newToolbars; + // 获取工具项的当前顺序 + public getToolbarOrder(type: ToolbarType): number { + const order = this.toolbarOrderMap.get(type); + return order !== undefined ? order : -1; + } + + // 获取所有工具栏顺序 + public getAllToolbarsOrder(): { type: ToolbarType; order: number }[] { + try { + this.checkDestroyed(); + + return Array.from(this.toolbarOrderMap.entries()) + .map(([type, order]) => ({ + type, + order, + })) + .sort((a, b) => a.order - b.order); + } catch (error) { + this.error("Error getting toolbars order:", error); + throw error; + } } // 获取所有工具栏项 @@ -45,62 +77,100 @@ class ToolbarConfig extends BaseClass { return this.toolbars.find((toolbar) => toolbar.type === type); } - // 修改特定工具栏项的功能 - public updateToolbarItem(type: ToolbarType, partialToolbarItem: Partial): void { + // ---------------------验证/校验函数----------------------- + + // 验证顺序的有效性 + private validateOrder(order: number): number { + const maxOrder = this.toolbars.length - 1; + return Math.max(0, Math.min(order, maxOrder)); + } + + // ---------------------操作(工具)函数----------------------- + + // 添加工具项方法 + public addToolItem(toolbarItem: ToolbarItem): void { try { this.checkDestroyed(); - const existingToolbar = this.getToolbarByType(type); - if (!existingToolbar) { - const error = `Toolbar type ${type} does not exist`; + if (!this.initialized) { + throw new Error("ToolbarConfig is not initialized yet"); + } + + if (this.getToolbarByType(toolbarItem.type)) { + const error = `Toolbar type ${toolbarItem.type} already exists`; this.emit(ToolbarEvents.TOOLBAR_ERROR, error); throw new Error(error); } - // 合并现有配置和新配置,保证未提及的项使用默认值 - const updatedToolbar: ToolbarItem = { - ...existingToolbar, - ...partialToolbarItem, - type: existingToolbar.type, - }; - this.toolbars = produce(this.toolbars, (draft) => { - const index = draft.findIndex((toolbar) => toolbar.type === type); - if (index !== -1) { - draft[index] = updatedToolbar; - } + draft.push(toolbarItem); }); - this.emit(ToolbarEvents.TOOLBAR_UPDATED, updatedToolbar); + this.toolbarOrderMap.set(toolbarItem.type, this.toolbars.length - 1); + + this.emit(ToolbarEvents.TOOLBAR_ADDED, toolbarItem); } catch (error) { - this.error("Error updating toolbar:", error); + this.error("Error adding toolbar:", error); throw error; } } - // 添加指定工具栏项 - public addToolItem(toolbarItem: ToolbarItem): void { + // 设置单个工具项的顺序 + public setToolbarOrder(type: ToolbarType, newOrder: number): void { try { this.checkDestroyed(); - if (!this.initialized) { - throw new Error("ToolbarConfig is not initialized yet"); + const toolbar = this.getToolbarByType(type); + if (!toolbar) { + throw new Error(`Toolbar type ${type} does not exist`); } - if (this.getToolbarByType(toolbarItem.type)) { - const error = `Toolbar type ${toolbarItem.type} already exists`; - this.emit(ToolbarEvents.TOOLBAR_ERROR, error); - throw new Error(error); - } + const currentOrder = this.getToolbarOrder(type); + const validOrder = this.validateOrder(newOrder); + + if (currentOrder === validOrder) return; this.toolbars = produce(this.toolbars, (draft) => { - draft.push(toolbarItem); + const item = draft.splice(currentOrder, 1)[0]; + draft.splice(validOrder, 0, item); }); - // 发送工具栏添加事件 - this.emit(ToolbarEvents.TOOLBAR_ADDED, toolbarItem); + this.initToolbarOrderMap(); + + this.emit(ToolbarEvents.TOOLBAR_REORDERED, this.toolbars); } catch (error) { - this.error("Error adding toolbar:", error); + this.error("Error setting toolbar order:", error); + throw error; + } + } + + // 批量设置工具项顺序 + public setToolbarsOrder(orders: Record): void { + try { + this.checkDestroyed(); + + const sortedItems = [...this.toolbars]; + const processedTypes = new Set(); + + // 遍历所有工具项,按照传入的顺序进行排序 + Object.entries(orders).forEach(([type, order]) => { + const toolbarType = type as ToolbarType; + const validOrder = this.validateOrder(order); + const currentIndex = sortedItems.findIndex((item) => item.type === toolbarType); + + if (currentIndex !== -1) { + const [item] = sortedItems.splice(currentIndex, 1); + sortedItems.splice(validOrder, 0, item); + processedTypes.add(toolbarType); + } + }); + + this.updateToolbars(sortedItems); + this.initToolbarOrderMap(); + + this.emit(ToolbarEvents.TOOLBAR_REORDERED, this.toolbars); + } catch (error) { + this.error("Error setting toolbars order:", error); throw error; } } @@ -113,6 +183,8 @@ class ToolbarConfig extends BaseClass { const toolbarToRemove = this.getToolbarByType(type); if (!toolbarToRemove) return; + const removedOrder = this.getToolbarOrder(type); + this.toolbars = produce(this.toolbars, (draft) => { const index = draft.findIndex((toolbar) => toolbar.type === type); if (index !== -1) { @@ -120,6 +192,14 @@ class ToolbarConfig extends BaseClass { } }); + this.toolbarOrderMap.delete(type); + this.toolbars.forEach((toolbar) => { + const currentOrder = this.toolbarOrderMap.get(toolbar.type); + if (currentOrder !== undefined && currentOrder > removedOrder) { + this.toolbarOrderMap.set(toolbar.type, currentOrder - 1); + } + }); + this.emit(ToolbarEvents.TOOLBAR_REMOVED, type); } catch (error) { this.error("Error removing toolbar:", error); @@ -127,16 +207,70 @@ class ToolbarConfig extends BaseClass { } } - // 重置工具栏内容 + // 更新工具栏内容 + public updateToolbars(newToolbars: ToolbarItem[]) { + this.toolbars = newToolbars; + } + + // 修改特定工具栏项的功能 + public updateToolbarItem(type: ToolbarType, partialToolbarItem: Partial): void { + try { + this.checkDestroyed(); + + const existingToolbar = this.getToolbarByType(type); + if (!existingToolbar) { + const error = `Toolbar type ${type} does not exist`; + this.emit(ToolbarEvents.TOOLBAR_ERROR, error); + throw new Error(error); + } + + // 合并现有配置和新配置,保证未提及的项使用默认值 + const updatedToolbar: ToolbarItem = { + ...existingToolbar, + ...partialToolbarItem, + type: existingToolbar.type, + }; + + this.toolbars = produce(this.toolbars, (draft) => { + const index = draft.findIndex((toolbar) => toolbar.type === type); + if (index !== -1) { + draft[index] = updatedToolbar; + } + }); + + this.emit(ToolbarEvents.TOOLBAR_UPDATED, updatedToolbar); + } catch (error) { + this.error("Error updating toolbar:", error); + throw error; + } + } + + // 重置工具栏顺序 public reset(): void { this.toolbars = this.initToolbar(); + this.initToolbarOrderMap(); + this.emit(ToolbarEvents.TOOLBAR_RESET, this.toolbars); } - // TODO: 添加重新排序工具栏顺序(例如拖拽排序??)的方法 + // 工具栏顺序重排序 public reorderToolbar(newOrder: ToolbarType[]): void { try { this.checkDestroyed(); + // 验证新顺序是否包含所有现有工具项 + const currentTypes = new Set(this.toolbars.map((t) => t.type)); + const newTypes = new Set(newOrder); + + if (currentTypes.size !== newTypes.size) { + throw new Error("New order must include all existing toolbar items"); + } + + currentTypes.forEach((type) => { + if (!newTypes.has(type)) { + throw new Error(`Missing toolbar type: ${type}`); + } + }); + const toolbarMap = new Map(this.toolbars.map((toolbar) => [toolbar.type, toolbar])); this.toolbars = produce(this.toolbars, () => newOrder @@ -144,12 +278,47 @@ class ToolbarConfig extends BaseClass { .filter((toolbar): toolbar is ToolbarItem => toolbar !== undefined), ); + this.initToolbarOrderMap(); + this.emit(ToolbarEvents.TOOLBAR_REORDERED, this.toolbars); } catch (error) { this.error("Error reordering toolbars:", error); throw error; } } + + // 交换工具栏位置(一对一) + public swapToolbarsPosition(firstType: ToolbarType, secondType: ToolbarType): void { + try { + this.checkDestroyed(); + + const firstIndex = this.toolbars.findIndex((t) => t.type === firstType); + const secondIndex = this.toolbars.findIndex((t) => t.type === secondType); + + // 验证工具是否存在 + if (firstIndex === -1) { + throw new Error(`Toolbar type ${firstType} does not exist`); + } + if (secondIndex === -1) { + throw new Error(`Toolbar type ${secondType} does not exist`); + } + + if (firstIndex === secondIndex) { + return; + } + + this.toolbars = produce(this.toolbars, (draft) => { + [draft[firstIndex], draft[secondIndex]] = [draft[secondIndex], draft[firstIndex]]; + }); + + this.initToolbarOrderMap(); + + this.emit(ToolbarEvents.TOOLBAR_REORDERED, this.toolbars); + } catch (error) { + this.error("Error swapping toolbar positions:", error); + throw error; + } + } } export const toolbarConfig = new ToolbarConfig(defaultToolbar); diff --git a/packages/mini-markdown-editor/src/locales/cn.ts b/packages/mini-markdown-editor/src/locales/cn.ts new file mode 100644 index 0000000000000000000000000000000000000000..d74c316e74391c06d4dc722db82cb88b6f3c7957 --- /dev/null +++ b/packages/mini-markdown-editor/src/locales/cn.ts @@ -0,0 +1,25 @@ +export const cn = { + "TOOLBAR.HEADING": "标题", + "TOOLBAR.BOLD": "加粗", + "TOOLBAR.ITALIC": "斜体", + "TOOLBAR.UNDERLINE": "下划线", + "TOOLBAR.DELETE": "删除线", + "TOOLBAR.BLOCKQUOTE": "引用", + "TOOLBAR.UL": "无序列表", + "TOOLBAR.OL": "有序列表", + "TOOLBAR.INLINE_CODE": "行内代码", + "TOOLBAR.CODE": "代码块", + "TOOLBAR.LINK": "链接", + "TOOLBAR.IMAGE": "图片", + "TOOLBAR.TABLE": "表格", + "TOOLBAR.UNDO": "撤销", + "TOOLBAR.REDO": "重做", + "TOOLBAR.HEADING_ITEMS.H1": "H1 一级标题", + "TOOLBAR.HEADING_ITEMS.H2": "H2 二级标题", + "TOOLBAR.HEADING_ITEMS.H3": "H3 三级标题", + "TOOLBAR.HEADING_ITEMS.H4": "H4 四级标题", + "TOOLBAR.HEADING_ITEMS.H5": "H5 五级标题", + "TOOLBAR.HEADING_ITEMS.H6": "H6 六级标题", + "TOOLBAR.IMAGE_ITEMS.LINK": "添加链接", + "TOOLBAR.IMAGE_ITEMS.UPLOAD": "上传图片", +}; diff --git a/packages/mini-markdown-editor/src/locales/en.ts b/packages/mini-markdown-editor/src/locales/en.ts new file mode 100644 index 0000000000000000000000000000000000000000..79c8426133cf8c79cdd2229b7203db85dc9a86b2 --- /dev/null +++ b/packages/mini-markdown-editor/src/locales/en.ts @@ -0,0 +1,25 @@ +export const en = { + "TOOLBAR.HEADING": "Heading", + "TOOLBAR.BOLD": "Bold", + "TOOLBAR.ITALIC": "Italic", + "TOOLBAR.UNDERLINE": "Underline", + "TOOLBAR.DELETE": "Strikethrough", + "TOOLBAR.BLOCKQUOTE": "Quote", + "TOOLBAR.UL": "Unordered List", + "TOOLBAR.OL": "Ordered List", + "TOOLBAR.INLINE_CODE": "Inline Code", + "TOOLBAR.CODE": "Code Block", + "TOOLBAR.LINK": "Link", + "TOOLBAR.IMAGE": "Image", + "TOOLBAR.TABLE": "Table", + "TOOLBAR.UNDO": "Undo", + "TOOLBAR.REDO": "Redo", + "TOOLBAR.HEADING_ITEMS.H1": "H1 Heading 1", + "TOOLBAR.HEADING_ITEMS.H2": "H2 Heading 2", + "TOOLBAR.HEADING_ITEMS.H3": "H3 Heading 3", + "TOOLBAR.HEADING_ITEMS.H4": "H4 Heading 4", + "TOOLBAR.HEADING_ITEMS.H5": "H5 Heading 5", + "TOOLBAR.HEADING_ITEMS.H6": "H6 Heading 6", + "TOOLBAR.IMAGE_ITEMS.LINK": "Add Link", + "TOOLBAR.IMAGE_ITEMS.UPLOAD": "Upload Image", +}; diff --git a/packages/mini-markdown-editor/src/locales/index.ts b/packages/mini-markdown-editor/src/locales/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..053166408a8fe4409f2fecf9ed73017eca6df717 --- /dev/null +++ b/packages/mini-markdown-editor/src/locales/index.ts @@ -0,0 +1,15 @@ +import { en } from "./en"; +import { cn } from "./cn"; + +type Language = "en" | "cn"; +const locales = { en, cn } as const; + +export type TRANSLATION_KEYS = keyof typeof en; + +// 创建翻译器 +//? 可以进行优化 +export const createTranslator = (locale: Language = "en") => { + const currentTranslations = locales[locale] || locales.en; + + return (key: TRANSLATION_KEYS) => currentTranslations[key] || locales.en[key] || key; +}; diff --git a/packages/mini-markdown-editor/src/locales/index1.tsx b/packages/mini-markdown-editor/src/locales/index1.tsx new file mode 100644 index 0000000000000000000000000000000000000000..851c8914ad7680ff692eb6f15f63189f2b3603ba --- /dev/null +++ b/packages/mini-markdown-editor/src/locales/index1.tsx @@ -0,0 +1,48 @@ +import React, { createContext, useState, useContext, ReactNode } from "react"; +import { en } from "./en"; +import { cn } from "./cn"; + +type Language = "en" | "cn"; +type LocaleMessages = typeof en; + +interface LocaleContextType { + locale: Language; + messages: LocaleMessages; + changeLocale: (locale: Language) => void; +} + +const LocaleContext = createContext(undefined); + +// 语言包映射 +const locales: Record = { + en, + cn, +}; + +export const LocaleProvider: React.FC<{ + children: ReactNode; + localeConfig?: Language; +}> = ({ children, localeConfig = "cn" }) => { + const [locale, setLocale] = useState(localeConfig); + + //? 提供切换语言 + const changeLocale = (newLocale: Language) => { + setLocale(newLocale); + }; + + const value = { + locale, + messages: locales[locale], + changeLocale, + }; + + return {children}; +}; + +export const useLocale = () => { + const context = useContext(LocaleContext); + if (!context) { + throw new Error("Locale init error"); + } + return context; +}; diff --git a/packages/mini-markdown-editor/src/locales/index2.ts b/packages/mini-markdown-editor/src/locales/index2.ts new file mode 100644 index 0000000000000000000000000000000000000000..781d8e7d24353e727e0260917fc3cc0a52b36e1c --- /dev/null +++ b/packages/mini-markdown-editor/src/locales/index2.ts @@ -0,0 +1,61 @@ +import { en } from "./en"; +import { cn } from "./cn"; + +export type Language = "en" | "cn"; +type LocaleMessages = typeof en; + +const locales: Record = { + en, + cn, +}; + +let currentLocale: Language = "cn"; + +// 设置当前语言 +export const setCurrentLocale = (locale: Language | undefined) => { + if (locale) { + currentLocale = locale; + } +}; + +// 获取当前语言 +export const getCurrentLocale = (): Language => { + return currentLocale; +}; + +// 翻译工具函数 +export const t = (path: string, locale?: Language): string => { + const targetLocale = locale || currentLocale; + const messages = locales[targetLocale]; + + return path.split(".").reduce((obj, key) => obj?.[key], messages as any) || path; +}; + +// 获取特定语言的配置 +export const getLocaleConfig = (config: T, locale?: Language): T => { + // 获取目标语言 + const targetLocale = locale || currentLocale; + + //* 递归翻译对象 + const translateObject = (obj: any): any => { + if (typeof obj === "string") { + return t(obj, targetLocale); + } + + if (Array.isArray(obj)) { + return obj.map((item) => translateObject(item)); + } + + if (typeof obj === "object" && obj !== null) { + const result: any = {}; + for (const key in obj) { + result[key] = translateObject(obj[key]); + } + return result; + } + + return obj; + }; + + return translateObject(config); +}; diff --git a/packages/mini-markdown-editor/src/locales/keys.ts b/packages/mini-markdown-editor/src/locales/keys.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ff0bf4929191a27fda04c8767dd9d14823413f3 --- /dev/null +++ b/packages/mini-markdown-editor/src/locales/keys.ts @@ -0,0 +1,79 @@ +// // 映射表 +// export const TRANSLATION_KEYS = { +// TOOLBAR: { +// HEADING: "TOOLBAR.HEADING", +// BOLD: "TOOLBAR.BOLD", +// ITALIC: "TOOLBAR.ITALIC", +// UNDERLINE: "TOOLBAR.UNDERLINE", +// DELETE: "TOOLBAR.DELETE", +// BLOCKQUOTE: "TOOLBAR.BLOCKQUOTE", +// UL: "TOOLBAR.UL", +// OL: "TOOLBAR.OL", +// INLINE_CODE: "TOOLBAR.INLINE_CODE", +// CODE: "TOOLBAR.CODE", +// LINK: "TOOLBAR.LINK", +// IMAGE: "TOOLBAR.IMAGE", +// TABLE: "TOOLBAR.TABLE", +// UNDO: "TOOLBAR.UNDO", +// REDO: "TOOLBAR.REDO", +// HEADING_ITEMS: { +// H1: "TOOLBAR.HEADING_ITEMS.H1", +// H2: "TOOLBAR.HEADING_ITEMS.H2", +// H3: "TOOLBAR.HEADING_ITEMS.H3", +// H4: "TOOLBAR.HEADING_ITEMS.H4", +// H5: "TOOLBAR.HEADING_ITEMS.H5", +// H6: "TOOLBAR.HEADING_ITEMS.H6", +// }, +// IMAGE_ITEMS: { +// LINK: "TOOLBAR.IMAGE_ITEMS.LINK", +// UPLOAD: "TOOLBAR.IMAGE_ITEMS.UPLOAD", +// }, +// }, +// } as const; + +import { BaseToolbarType } from "@/types/toolbar"; + +export const TOOLBAR_KEYS = { + TOOLBAR: { + // 基础工具栏项 + [BaseToolbarType.HEADING]: "TOOLBAR.HEADING", + [BaseToolbarType.BOLD]: "TOOLBAR.BOLD", + [BaseToolbarType.ITALIC]: "TOOLBAR.ITALIC", + [BaseToolbarType.UNDERLINE]: "TOOLBAR.UNDERLINE", + [BaseToolbarType.DELETE]: "TOOLBAR.DELETE", + [BaseToolbarType.BLOCKQUOTE]: "TOOLBAR.BLOCKQUOTE", + [BaseToolbarType.UL]: "TOOLBAR.UL", + [BaseToolbarType.OL]: "TOOLBAR.OL", + [BaseToolbarType.INLINECODE]: "TOOLBAR.INLINE_CODE", + [BaseToolbarType.CODE]: "TOOLBAR.CODE", + [BaseToolbarType.LINK]: "TOOLBAR.LINK", + [BaseToolbarType.IMAGE]: "TOOLBAR.IMAGE", + [BaseToolbarType.TABLE]: "TOOLBAR.TABLE", + [BaseToolbarType.UNDO]: "TOOLBAR.UNDO", + [BaseToolbarType.REDO]: "TOOLBAR.REDO", + [BaseToolbarType.FULLSCREEN]: "TOOLBAR.FULLSCREEN", + [BaseToolbarType.WRITE]: "TOOLBAR.WRITE", + [BaseToolbarType.PREVIEW]: "TOOLBAR.PREVIEW", + [BaseToolbarType.CONTENTS]: "TOOLBAR.CONTENTS", + [BaseToolbarType.HELP]: "TOOLBAR.HELP", + [BaseToolbarType.OUTPUT]: "TOOLBAR.OUTPUT", + [BaseToolbarType.EMOJI]: "TOOLBAR.EMOJI", + [BaseToolbarType.SAVE]: "TOOLBAR.SAVE", + + // 标题子项 + HEADING_ITEMS: { + [BaseToolbarType.HEADING_1]: "TOOLBAR.HEADING_ITEMS.H1", + [BaseToolbarType.HEADING_2]: "TOOLBAR.HEADING_ITEMS.H2", + [BaseToolbarType.HEADING_3]: "TOOLBAR.HEADING_ITEMS.H3", + [BaseToolbarType.HEADING_4]: "TOOLBAR.HEADING_ITEMS.H4", + [BaseToolbarType.HEADING_5]: "TOOLBAR.HEADING_ITEMS.H5", + [BaseToolbarType.HEADING_6]: "TOOLBAR.HEADING_ITEMS.H6", + }, + + // 图片相关 + IMAGE_ITEMS: { + [BaseToolbarType.IMAGE_LINK]: "TOOLBAR.IMAGE_ITEMS.LINK", + [BaseToolbarType.IMAGE_UPLOAD]: "TOOLBAR.IMAGE_ITEMS.UPLOAD", + }, + }, +} as const; diff --git a/packages/mini-markdown-editor/src/types/global-config.ts b/packages/mini-markdown-editor/src/types/global-config.ts index 181084edcf71d96d333eaa6e72bb06967a71a7f2..bb5482a9947507657d181639c18be4ad433fb894 100644 --- a/packages/mini-markdown-editor/src/types/global-config.ts +++ b/packages/mini-markdown-editor/src/types/global-config.ts @@ -9,10 +9,14 @@ export interface GlobalConfig extends ReactCodeMirrorProps { value?: string; /** * 配置工具栏 - * @type {{ addTools?: ToolbarItem[]; excludeTools?: string[] }} + * @type {{ addTools?: ToolbarItem[]; excludeTools?: string[], orderTools?: { type: string; order: number }[]; }} * 添加工具; 排除工具 */ - toolbars?: { addTools?: ToolbarItem[]; excludeTools?: string[] }; + toolbars?: { + addTools?: ToolbarItem[]; + excludeTools?: string[]; + orderTools?: { type: string; order: number }[]; + }; /** * 底部状态栏是否显示,默认显示 * @type {boolean} @@ -23,6 +27,11 @@ export interface GlobalConfig extends ReactCodeMirrorProps { * @type {"light" | "dark"} */ theme?: "light" | "dark"; + /** + * 语言 + * @type {"en" | "cn" | "tw"} + */ + locale?: "en" | "cn"; /** * 是否开启本地存储 * @type {boolean} @@ -70,6 +79,7 @@ export type Callback = (param: { url: string; alt?: string }) => void; export type GlobalContextConfig = Pick< GlobalConfig, | "theme" + | "locale" | "value" | "toolbars" | "status" diff --git a/packages/mini-markdown-editor/src/types/toolbar.ts b/packages/mini-markdown-editor/src/types/toolbar.ts index d843945fb7612de880c53a0b4191365045c4f668..783e904b0edf34da63a7bcd8c678a029418d1f55 100644 --- a/packages/mini-markdown-editor/src/types/toolbar.ts +++ b/packages/mini-markdown-editor/src/types/toolbar.ts @@ -56,7 +56,7 @@ export interface BaseToolbarItem { // 工具栏列表项接口 export interface ToolbarItemListItem { - title: string; + title?: string; type: string; hotkey?: { command: string; @@ -89,6 +89,7 @@ export enum ToolbarEvents { TOOLBAR_ADDED = "toolbar:added", TOOLBAR_REMOVED = "toolbar:removed", TOOLBAR_UPDATED = "toolbar:updated", + // 重排序 TOOLBAR_REORDERED = "toolbar:reordered", TOOLBAR_RESET = "toolbar:reset", TOOLBAR_ERROR = "toolbar:error",