完善功能

This commit is contained in:
2026-07-10 16:07:53 +08:00
parent 0a3534a7e0
commit 78913c46b6
10 changed files with 971 additions and 31 deletions

View File

@@ -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`.` 开头自动隐藏WindowsRust 端调用 `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<u8> │
│ ├─ 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` 插件根据扩展名自动转为 `<video>` 标签。