From bd5e3855ead8689fc49c1c9cf5b3c5ff8d4e8e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?wifi=E6=AD=AAf?= <1402772884@qq.com> Date: Wed, 29 Jan 2025 13:03:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(mini-markdown-editor):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=BE=A7=E8=BE=B9=E6=A0=8F=E7=9B=AE=E5=BD=95=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs/index.md | 73 ++++++++++++--- packages/mini-markdown-editor/package.json | 2 +- .../src/EditorWrapper.tsx | 10 +- .../src/assets/styles/preview.css | 1 + .../src/common/hotkeys.ts | 2 +- .../src/components/Editor/index.tsx | 1 + .../src/components/Sidebar/Contents.tsx | 93 ++++++++++++++++++- .../src/config/toolbar/event.ts | 8 -- .../src/utils/format-contents.ts | 58 ++++++++++++ 9 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 packages/mini-markdown-editor/src/utils/format-contents.ts diff --git a/docs/docs/index.md b/docs/docs/index.md index 7aa4edb..4e8275d 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -1,69 +1,116 @@ 让我来对比一下 CSS-in-JS(以 styled-components 为例)和 CSS Modules 这两种方案的优缺点: ### CSS-in-JS 优点: + 1. 动态样式能力强 - + - 可以基于 props 动态计算样式 - 支持主题系统,通过 ThemeProvider 可以方便地实现主题切换 - 可以直接在组件中使用 JavaScript 变量和函数 + 2. 组件化程度高 - + - 样式与组件紧密绑定,提高了代码的内聚性 - 支持样式组件的继承和组合 - 样式作用域完全隔离 + 3. 开发体验好 - + - 不需要在组件和样式文件之间切换 - 支持模板字符串语法,写法接近原生 CSS - 支持自动添加浏览器前缀 + ### CSS-in-JS 缺点: + 1. 运行时开销 - + - 需要在运行时生成和注入样式 - 首次渲染性能较差 - bundle 体积会增大 + 2. 调试相对困难 - + - 生成的类名是动态的,不易于调试 - DevTools 中查看样式不如传统 CSS 直观 + ### CSS Modules 优点: + 1. 性能更好 - + - 在构建时生成样式,没有运行时开销 - bundle 体积更小 - 首次渲染更快 + 2. 更好的工具支持 - + - 完整的 CSS 语法支持 - 更好的 IDE 提示和自动补全 - 更容易与现有的 CSS 工具链集成(如 PostCSS) + 3. 调试友好 - + - 生成的类名可预测 - 在 DevTools 中更容易定位样式问题 - 支持 source map + ### CSS Modules 缺点: + 1. 动态样式能力有限 - + - 不能基于 props 直接生成样式 - 需要通过类名组合来实现动态样式 - 主题系统实现相对复杂 + 2. 开发体验略差 - + - 需要在组件和样式文件之间切换 - 类名管理需要额外注意 - 样式复用相对困难 + ### 选择建议: + 1. 选择 CSS-in-JS 的场景: - + - 需要高度动态的样式系统 - 组件库开发 - 需要主题系统的项目 - 小型项目或对性能要求不是特别高的场景 + 2. 选择 CSS Modules 的场景: - - 对性能要求较高的大型项目 - 样式相对静态的项目 - 需要与现有 CSS 工具链集成的项目 - 团队更熟悉传统 CSS 开发模式 -在实际项目中,这两种方案也可以混合使用,根据具体场景选择最适合的方案。例如,可以使用 CSS Modules 处理大部分静态样式,而在需要动态样式的特定组件中使用 CSS-in-JS。 \ No newline at end of file + 在实际项目中,这两种方案也可以混合使用,根据具体场景选择最适合的方案。例如,可以使用 CSS Modules 处理大部分静态样式,而在需要动态样式的特定组件中使用 CSS-in-JS。 + +## 目录获取渲染区dom不同步问题 + +使用 MutationObserver 的原因是为了解决 React 状态更新和 DOM 渲染的时序问题。让我解释一下: + +1. 虽然 preview 内容更新会被设置到仓库中,但是 DOM 的实际渲染可能会有延迟。这是因为: + + - React 的状态更新是异步的 + - DOM 的实际渲染会在 React 完成虚拟 DOM 的计算后才进行 + - 特别是在处理 Markdown 渲染这种复杂操作时,DOM 的更新可能会有一定延迟 + +2. 如果不使用 MutationObserver ,可能会出现以下问题: + + ```typescript + useEffect(() => { + if (!previewView) return; + + // 这时候虽然 content 更新了,但 DOM 可能还没有渲染完成 + const elements = previewView.querySelectorAll("h1, h2, h3, h4, h5, h6"); + setTitles(formatContents(elements)); // 可能会获取不到最新的标题 + }, [content, previewView]); + ``` + + ``` + + ``` + +3. 使用 MutationObserver 的好处: + - 它能够直接监听 DOM 的变化 + - 只有在 DOM 真正更新后才会触发回调 + - 确保我们获取到的是最新的 DOM 结构 + 所以,虽然内容会更新到仓库,但使用 MutationObserver 可以确保我们在正确的时机(DOM 真正更新后)获取标题列表,避免出现标题列表不同步的问题。 diff --git a/packages/mini-markdown-editor/package.json b/packages/mini-markdown-editor/package.json index 1538b5f..824cbef 100644 --- a/packages/mini-markdown-editor/package.json +++ b/packages/mini-markdown-editor/package.json @@ -8,7 +8,7 @@ "dev": "pnpm build:ast && vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview" }, "dependencies": { "@codemirror/lang-markdown": "^6.3.2", diff --git a/packages/mini-markdown-editor/src/EditorWrapper.tsx b/packages/mini-markdown-editor/src/EditorWrapper.tsx index d009c1c..497ef07 100644 --- a/packages/mini-markdown-editor/src/EditorWrapper.tsx +++ b/packages/mini-markdown-editor/src/EditorWrapper.tsx @@ -72,15 +72,15 @@ const Divider = styled.div` // 布局配置映射 const LayoutConfig = { // 只写模式 - WRITE_ONLY: { cols: [18], components: ["editor"] }, + WRITE_ONLY: { cols: [18, 0, 0], components: ["editor", "preview", "sidebar"] }, // 仅预览模式 - READ_ONLY: { cols: [18], components: ["preview"] }, + READ_ONLY: { cols: [0, 18, 0], components: ["editor", "preview", "sidebar"] }, // 读写模式 - READ_WRITE: { cols: [12, 12], components: ["editor", "preview"] }, + READ_WRITE: { cols: [12, 12, 0], components: ["editor", "preview", "sidebar"] }, // 只写+侧边栏 - WRITE_ONLY_SIDEBAR: { cols: [18, 6], components: ["editor", "sidebar"] }, + WRITE_ONLY_SIDEBAR: { cols: [18, 0, 6], components: ["editor", "preview", "sidebar"] }, // 仅预览+侧边栏 - READ_ONLY_SIDEBAR: { cols: [18, 6], components: ["preview", "sidebar"] }, + READ_ONLY_SIDEBAR: { cols: [0, 18, 6], components: ["editor", "preview", "sidebar"] }, // 读写+侧边栏 READ_WRITE_SIDEBAR: { cols: [9, 9, 6], components: ["editor", "preview", "sidebar"] }, }; diff --git a/packages/mini-markdown-editor/src/assets/styles/preview.css b/packages/mini-markdown-editor/src/assets/styles/preview.css index dd022b9..6080978 100644 --- a/packages/mini-markdown-editor/src/assets/styles/preview.css +++ b/packages/mini-markdown-editor/src/assets/styles/preview.css @@ -56,6 +56,7 @@ } .mini-md-image { + width: 100%; max-width: 100%; border: 1px solid #e6e6e6; border-radius: 3px; diff --git a/packages/mini-markdown-editor/src/common/hotkeys.ts b/packages/mini-markdown-editor/src/common/hotkeys.ts index 53735a8..f97f4da 100644 --- a/packages/mini-markdown-editor/src/common/hotkeys.ts +++ b/packages/mini-markdown-editor/src/common/hotkeys.ts @@ -12,7 +12,7 @@ export class Hotkey { SIXTH: new Hotkey("mod+6", "heaing-6"), } as const; static readonly BOLD = new Hotkey("mod+b", "bold"); // **text** - static readonly ITALIC = new Hotkey("mod+i", "italic"); // *text* + static readonly ITALIC = new Hotkey("mod+i", "italic"); // _text_ static readonly UNDERLINE = new Hotkey("mod+u", "underline"); // --text-- static readonly DELETE = new Hotkey("mod+shift+x", "delete"); // ~~text~~ static readonly BLOCKQUOTE = new Hotkey("mod+shift+9", "blockquote"); // > text diff --git a/packages/mini-markdown-editor/src/components/Editor/index.tsx b/packages/mini-markdown-editor/src/components/Editor/index.tsx index dd03154..4094e24 100644 --- a/packages/mini-markdown-editor/src/components/Editor/index.tsx +++ b/packages/mini-markdown-editor/src/components/Editor/index.tsx @@ -59,6 +59,7 @@ const Editor: FC = () => { }, [editorViewRef], ); + // 监听快捷键 useEditorShortcuts(); // 处理重加载后的光标位置 diff --git a/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx b/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx index 571b251..cc68ed1 100644 --- a/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx +++ b/packages/mini-markdown-editor/src/components/Sidebar/Contents.tsx @@ -1,7 +1,96 @@ -import { FC } from "react"; +import { formatContents, Title } from "@/utils/format-contents"; +import { Anchor } from "antd"; +import { FC, useEffect, useMemo, useState } from "react"; +import { useEditorContentStore } from "@/store/editor"; const Contents: FC = () => { - return <>Contents>; + const previewView = useEditorContentStore((state) => state.previewView); + const [titles, setTitles] = useState