diff --git a/.reasonix/desktop-topic-auto-title-meta.json b/.reasonix/desktop-topic-auto-title-meta.json index b3ae486..2811b3e 100644 --- a/.reasonix/desktop-topic-auto-title-meta.json +++ b/.reasonix/desktop-topic-auto-title-meta.json @@ -32,8 +32,8 @@ "topic_20260710-032907_600adc6b00791607": { "stage": 1, "userTurns": 1, - "basisHash": "e0e08f907071b904", - "updatedAt": 1783654440818 + "basisHash": "60a646e63a54c3bf", + "updatedAt": 1783669646165 }, "topic_20260710-034033_6432ee6566f7b432": { "stage": 1, @@ -52,5 +52,11 @@ "userTurns": 3, "basisHash": "87073c4b14751c7b", "updatedAt": 1783663776394 + }, + "topic_20260710-063053_353d579222d3b70b": { + "stage": 3, + "userTurns": 3, + "basisHash": "aaae120b1f33c2f2", + "updatedAt": 1783668572932 } } \ No newline at end of file diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json index 26ee8d6..5b316c2 100644 --- a/.reasonix/desktop-topic-titles.json +++ b/.reasonix/desktop-topic-titles.json @@ -43,9 +43,9 @@ "topic_20260708-095428_990617acd4168b40": "帮我检查一下这个应用在进行放大缩小的…", "topic_20260710-024855_5c4b1833149d66bb": "帮我给右侧的目录树添加一个功能,当用…", "topic_20260710-030936_cedad2907c3954fe": "鼠标放在tree上的时候,只有hov…", - "topic_20260710-032907_600adc6b00791607": "目录树中显示的文件夹,markdow…", + "topic_20260710-032907_600adc6b00791607": "帮我移除目录树中在空白区域点击右键可…", "topic_20260710-034033_6432ee6566f7b432": "我在点击目录树的时候新增文件夹和新建…", "topic_20260710-034724_3c6fb29d8d0a4119": "目录树中鼠标悬浮弹框dropmenu…", "topic_20260710-054543_5e7c427932aecd7b": "现在在markdown的编辑区域中…", - "topic_20260710-063053_353d579222d3b70b": "新的会话" + "topic_20260710-063053_353d579222d3b70b": "这个应用里有一个全局搜索的功能,帮我…" } \ No newline at end of file diff --git a/doc/markdown-editor-design.md b/doc/markdown-editor-design.md index effb128..6de4295 100644 --- a/doc/markdown-editor-design.md +++ b/doc/markdown-editor-design.md @@ -189,3 +189,106 @@ MarkdownEditor.vue | 块 hover | `bg-[#faf9f6]` 微暖灰,提示可点击 | | 编辑 textarea | `bg-[#faf9f6]`,`border-[#bf6a3b]`,`font-mono` | | 缩放 | `calc(1rem * var(--editor-zoom, 1))` CSS 变量,由 App.vue 的 Ctrl+滚轮 控制 | + +## 粘贴图片/文件功能 + +### 概述 + +支持在编辑区域(edit 模式和 mixed 模式)中通过 `Ctrl+V` 粘贴剪贴板中的图片或文件。媒体文件自动拷贝到与 markdown 文件同级的隐藏资源文件夹中,编辑区域插入 markdown 图片语法引用。 + +### 隐藏资源文件夹约定 + +``` +工作目录/ +├── doc.md ← markdown 文档 +└── .doc/ ← 隐藏资源文件夹(. + 文件名 stem) + ├── 20260710_140530_a1b2.png + └── 20260710_140531_c3d4.png +``` + +| 规则 | 说明 | +|---|---| +| 命名 | `{md文件名}` → `.{stem}/`(如 `test.md` → `.test/`) | +| 可见性 | macOS/Linux:`.` 开头自动隐藏;Windows:Rust 端调用 `attrib +h` 设置隐藏属性 | +| 生命周期 | 随 md 文件创建/删除/重命名自动联动 | +| 引用方式 | markdown 中使用相对路径 `![alt](.doc/xxx.png)` | + +### 整体流程 + +``` +用户 Ctrl+V 粘贴图片 + │ + ▼ +┌─────────────────────────────────────────┐ +│ textarea @paste 事件 │ +│ 检查 clipboardData.items │ +│ ├─ image/png, image/jpeg, ... │ ← 截图粘贴 +│ └─ kind === 'file' │ ← 文件管理器复制粘贴 +│ │ +│ 如有媒体项 → e.preventDefault() │ +│ 无媒体项 → 走浏览器默认文本粘贴 │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ 前端 processPaste() │ +│ ├─ FileReader.readAsDataURL(blob) │ +│ ├─ 去掉 "data:xxx;base64," 前缀 │ +│ ├─ invoke("save_media_file", { │ +│ │ path: ".doc/20260710_xxx.png", │ +│ │ data: "base64string" │ +│ │ }) │ +│ └─ 光标处插入 ![alt](.doc/xxx.png) │ +└──────────────────┬──────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Rust save_media_file │ +│ ├─ base64 decode → Vec │ +│ ├─ ensure_resources_dir() 创建目录 │ +│ │ └─ Windows: attrib +h 隐藏 │ +│ └─ fs::write() 写入文件 │ +└─────────────────────────────────────────┘ +``` + +### 前端关键函数 + +| 函数 | 位置 | 说明 | +|---|---|---| +| `processPaste()` | `MarkdownEditor.vue` | 核心粘贴处理:检测剪贴板媒体 → 生成文件名 → 调 Rust 保存 → 拼接 markdown 语法 | +| `blobToBase64()` | 同上 | FileReader 读取 blob 为去前缀 base64 字符串 | +| `generateMediaFilename()` | 同上 | `YYYYMMDD_HHmmss_随机4位.ext` | +| `imageExtFromMime()` | 同上 | MIME type → 文件扩展名映射 | +| `onEditorPaste()` | 同上 | edit 模式 textarea 的 paste handler | +| `onBlockPaste()` | 同上 | mixed 模式 block textarea 的 paste handler | + +### Rust 端关键函数 + +| 命令/函数 | 位置 | 说明 | +|---|---|---| +| `save_media_file(path, data)` | `lib.rs` | base64 解码 → 创建目录(+Windows 隐藏) → 写入文件 | +| `resources_dir_path(file_path)` | 同上 | `a/b/doc.md` → `a/b/.doc`(仅 .md 文件) | +| `ensure_resources_dir(dir_path)` | 同上 | 创建目录 + Windows `attrib +h` 隐藏 | +| `hide_windows_dir(dir_path)` | 同上 | `#[cfg(windows)]` 调用 `attrib +h` | + +### 生命周期联动 + +| 操作 | Rust 行为 | 前端行为 | +|---|---|---| +| 新建 `doc.md` | `create_file` 同时调用 `ensure_resources_dir` 创建 `.doc/` | 无额外操作 | +| 删除 `doc.md` | `delete_entry` 检查并删除 `.doc/`(如存在) | App.vue 关闭文件引用 | +| 重命名 `doc.md` → `new.md` | `rename_entry` 检查并重命名 `.doc/` → `.new/` | App.vue 更新 `currentFilePath` | +| 粘贴图片时目录被删 | `save_media_file` → `ensure_resources_dir` 自动重建 `.doc/` | 正常写入 | + +### 编辑模式兼容 + +paste 事件同时绑定在两种模式的 textarea 上: + +- **edit 模式**:`@paste="onEditorPaste"` — 操作 `props.modelValue` + `editorTextareaRef` +- **mixed 模式**:`@paste="onBlockPaste"` — 操作 `editingSource` + `blockTextareaRef`(包含空文档和 block 编辑两种场景) + +粘贴后的 markdown 语法统一为: +```markdown +![原文件名](.doc/20260710_143025_a1b2.png) +``` +多张图片粘贴时用换行分隔。视频文件使用相同的 `![alt](path)` 语法,由 `remarkLocalMedia` 插件根据扩展名自动转为 `