From 8ad8af7e6bf3905569f409064c16ef281bbbda4e Mon Sep 17 00:00:00 2001 From: cirry <812852553@qq.com> Date: Mon, 6 Jul 2026 18:08:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=87=E6=8D=A2markdown=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E4=B8=BAunified?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .reasonix/desktop-topic-title-sources.json | 9 +- .reasonix/desktop-topic-titles.json | 9 +- doc/markdown-editor-design.md | 191 ++++ package.json | 12 +- pnpm-lock.yaml | 977 ++++++++++++++++----- src-tauri/tauri.conf.json | 2 +- src/App.vue | 25 +- src/components/DirectorySidebar.vue | 30 + src/components/MarkdownEditor.vue | 644 ++++++++++---- src/editor/livePreview.ts | 377 -------- 10 files changed, 1501 insertions(+), 775 deletions(-) create mode 100644 doc/markdown-editor-design.md delete mode 100644 src/editor/livePreview.ts diff --git a/.reasonix/desktop-topic-title-sources.json b/.reasonix/desktop-topic-title-sources.json index 1f320c1..52ac5d3 100644 --- a/.reasonix/desktop-topic-title-sources.json +++ b/.reasonix/desktop-topic-title-sources.json @@ -19,5 +19,12 @@ "topic_20260703-031922_1d2031ca5f2c0dd5": "auto", "topic_20260703-035328_e939286e13cc8f1b": "auto", "topic_20260703-060642_54474cc49e29104f": "auto", - "topic_20260703-065009_d937c6f780ebb5de": "auto" + "topic_20260703-065009_d937c6f780ebb5de": "auto", + "topic_20260703-071019_122507af94a4bb69": "auto", + "topic_20260703-073828_e23737acfa7578f3": "auto", + "topic_20260703-075048_605d579e54dfa8f9": "auto", + "topic_20260703-081048_20d0dd6e24fe0eca": "auto", + "topic_20260703-090137_2a285dcd68326d53": "auto", + "topic_20260703-093407_89b9baa209988cbb": "auto", + "topic_20260706-025751_d46402fb217a34e9": "auto" } \ No newline at end of file diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json index d453b60..8093ce1 100644 --- a/.reasonix/desktop-topic-titles.json +++ b/.reasonix/desktop-topic-titles.json @@ -19,5 +19,12 @@ "topic_20260703-031922_1d2031ca5f2c0dd5": "我希望第126-228行的内容,在用…", "topic_20260703-035328_e939286e13cc8f1b": "我们把整个右侧区域都给了markdo…", "topic_20260703-060642_54474cc49e29104f": "关于这个核心编辑区域,我需要实现一个…", - "topic_20260703-065009_d937c6f780ebb5de": "在功能上有问题,现在用户输入的内容删…" + "topic_20260703-065009_d937c6f780ebb5de": "在功能上有问题,现在用户输入的内容删…", + "topic_20260703-071019_122507af94a4bb69": "现在已经有了很多功能,但是很多样式还…", + "topic_20260703-073828_e23737acfa7578f3": "现在我点击md文件,怎么在编辑区域里…", + "topic_20260703-075048_605d579e54dfa8f9": "现在我点击目录中的md文件,怎么在编…", + "topic_20260703-081048_20d0dd6e24fe0eca": "在渲染的时候有几个问题需要修改,第一…", + "topic_20260703-090137_2a285dcd68326d53": "帮我检查一下这个页面为什么渲染不出来…", + "topic_20260703-093407_89b9baa209988cbb": "帮我检查一下这个页面为什么渲染不出来…", + "topic_20260706-025751_d46402fb217a34e9": "这个页面现在是用cm6实现的预览功能…" } \ No newline at end of file diff --git a/doc/markdown-editor-design.md b/doc/markdown-editor-design.md new file mode 100644 index 0000000..effb128 --- /dev/null +++ b/doc/markdown-editor-design.md @@ -0,0 +1,191 @@ +# MarkdownEditor 块级编辑设计文档 + +## 概述 + +`MarkdownEditor` 是 yurou(语柔)的核心编辑组件,支持两种编辑模式: + +| 模式 | 说明 | +|---|---| +| **双栏**(split) | 左侧 textarea 编辑源码,右侧 unified 渲染 HTML 预览,滚动同步 | +| **单栏**(wysiwyg) | 逐块渲染与编辑:点击任意块弹出其原始 markdown 源码,失焦后自动重新渲染 | + +## 技术栈 + +- **解析**:`remark-parse` + `remark-gfm`(GFM 扩展:表格、任务列表、删除线) +- **转换**:`remark-rehype`(mdast → hast) +- **安全**:`rehype-sanitize`(过滤 XSS,保留 `data-*` 属性) +- **输出**:`rehype-stringify`(hast → HTML 字符串) +- **框架**:Vue 3 Composition API + TypeScript + +## 整体架构 + +``` +modelValue (markdown 字符串) + │ + ├─── split 模式 ────────────────────────── + │ │ + │ ├── textarea (左栏,编辑源码) + │ │ 输入 → emit('update:modelValue') + │ │ + │ └── div.markdown-preview (右栏,v-html) + │ unified.processSync(md) → HTML + │ 滚动同步:textarea.scrollTop ↔ div.scrollTop + │ + └─── wysiwyg 模式 ──────────────────────── + │ + ├── remark-parse → mdast + │ 提取每个顶层子节点的 position (offset) + │ + ├── 每个 block: + │ source = md.slice(start, end) // 原始源码 + │ html = unified.processSync(source) // 渲染 HTML + │ + ├── blocks[] = [{id, source, html, startOffset, endOffset}, ...] + │ + └── 模板中 v-for: + editingBlockId === block.id + ?