# note-to-mp 设计文档 (Detail Design) > 拆分文档索引: > - 架构总览:`architecture.md` > - 图片管线:`image-pipeline.md` > - 渲染服务蓝图:`render-service-blueprint.md` > - 图示 (Mermaid):`diagrams.md` > 本文件保留全量细节,增量演进请同步上述子文档。 ## 1. 背景 为满足从 Obsidian 笔记到微信公众号文章的高质量发布需求,插件需要: - 支持多种图片书写形式(Wikilink 与标准 Markdown)。 - 统一图片处理与上传(包括 WebP 转换、水印、封面选择)。 - 自动抽取文章元数据(标题、作者、封面图)。 - 支持自定义短代码(`gallery`)与行级语法扩展(`||` 样式块、`[fig .../]` 等)。 - 提供灵活的封面回退逻辑(frontmatter 指定优先,缺省取正文第一图)。 ## 2. 目标 | 目标 | 说明 | |------|------| | 图片语法统一 | `![[file.png]]` 与 `![alt](path/file.png)` 最终统一进入 LocalImage 管线 | | 元数据抽取 | 自动获取标题、作者、封面图(可回退)供后续上传逻辑使用 | | 封面回退 | 未显式指定封面时,自动决策第一张图片 | | Gallery 支持 | 将 `{{}}{{}}` 转成图片 wikilinks 列表 | | 预处理 | 在 Markdown 渲染前执行自定义语法转 HTML | | 易扩展 | 提供独立函数/接口减少耦合,如 `selectGalleryImages`、`extractWeChatMeta` | ## 3. 术语与定义 - **Wikilink 图片语法**:`![[xxx.png]]` - **标准 Markdown 图片**:`![描述](path/to/xxx.png)` - **Frontmatter**:位于首部 `---` 包裹的元数据区域。 - **Cover(封面)**:用于公众号首图上传的图片。 - **Gallery Shortcode**:`{{}}{{}}` ## 4. 系统现状概览 主要处理链路: ``` Raw Markdown ↓ extractWeChatMeta (保留 frontmatter 内容供分析) ↓ 去 frontmatter ↓ transformGalleryShortcodes (gallery → ![[...]] 列表) ↓ marked.parse() (图片扩展 -> LocalImage token) ↓ 生成 HTML + 样式注入 ↓ setArticle() ↓ getArticleContent() -> preprocessContent(line regex 替换) -> 最终 HTML ``` ## 5. 架构模块划分 | 模块 | 关键函数/变量 | 作用 | |------|---------------|------| | 内容预处理 | `preprocessContent()` | 行级 Regex 转 HTML(图片路径修正、`||` 块、`[fig .../]`) | | 图片统一解析 | `LocalFileRegex`、MarkdownImage tokenizer | 标准化所有图片为 LocalImage token | | 图片资源管理 | `LocalImageManager` | 记录本地图片、上传、替换 URL、Base64 嵌入 | | Gallery | `_listGalleryImages` / `selectGalleryImages` / `transformGalleryShortcodes` | 短代码 → wikilink 列表(可扩展 figcaption) | | 元数据抽取 | `extractWeChatMeta` / `getWeChatArticleMeta` | 标题 / 作者 / 封面图计算 | | 封面自动补全 | `getMetadata()` 尾部逻辑 | 无 frontmatter cover 时回填 | | 图片上传 | `uploadLocalImage` / `uploadCover` | WebP→JPG、加水印、水印依赖 wasm | | WebP 支持 | `PrepareImageLib` + wasm | 转换后再上传 | | 渲染管线 | `renderMarkdown` | 串联以上逻辑 | ## 6. 数据流示意 参见第 4 节图。每个阶段保证产物单向流入下一层,避免循环依赖。 ## 7. 关键算法与实现细节 ### 7.1 图片统一转换 - Regex:`LocalFileRegex = /^(?:!\[\[(.*?)\]\]|!\[[^\]]*\]\(([^\n\r\)]+)\))/` - Markdown 标准图片 tokenizer: 1. 匹配 `![alt](path)` → `matches[0]`。 2. 取 basename → 构造 `![[basename]]` 语义(内部直接建 LocalImage token,不再二次正则回匹配)。 3. 避免原先多余 `-2.png)` 残留问题。 ### 7.2 元数据抽取(`extractWeChatMeta`) - 捕获 frontmatter 简易块(首个 `---` 区间)。 - 解析 `title / author / image` 单行 KV。 - `image` → 取 basename → `![[basename]]`。 - 回退封面:同时匹配 wikilink + markdown 图片,比较 index 取出现最早的一种。 - 返回:`{ title, author, coverLink, rawImage }`。 - 与 `getMetadata()` 融合以补齐空缺字段。 ### 7.3 前置处理(`preprocessContent`) - `[fig .../]` → ``(题注样式)。 - 行级命令:`||r / ||g / ||b / ||y / ||` → 不同背景色 `

`。 - `` → 前缀补全 `/img/`。 ### 7.4 Gallery 功能 - 短代码 Regex:`{{}}{{}}` - `_listGalleryImages`:读目录 + 过滤扩展 + 排序 + 截断。 - `selectGalleryImages`:对外通用(支持未来 random / prefix / includeDirInLink)。 - 输出:多行 `![[file]]`,并追加可选 `figcaption` div。 #### 7.4.1 块级 Gallery 语法(新增) 支持: ``` {{}} {{

}} {{
}} {{}} ``` 转换: ``` ![[foo-1.png]] ![[foo-2.jpeg]] ``` 规则: - 仅取 src 的 basename,忽略 caption(后续可扩展为题注输出)。 - 若块内未匹配到任何 figure,保留原文本。 - 正则:`/{{}}([\s\S]*?){{<\/gallery>}}/g` 与内部 `figureRegex = /{{]*>}}/g`。 - 输出顺序按出现顺序。 ## 8. 正则清单 | 场景 | 正则 | 说明 | |------|------|------| | frontmatter | `^---[\s\S]*?\n---` | 仅首段 | | Wikilink 图片 | `!\[\[(.+?)\]\]` | 非贪婪 | | Markdown 图片 | `!\[[^\]]*\]\(([^\n\r\)]+)\)` | 不跨行 | | Gallery | `{{}}{{}}` | 捕获 dir/caption | | fig | `\[fig([^>]*?)\/]` | 题注 | | 行块 | `\|\|r (.*)` 等 | 行级匹配 | ## 9. 错误与边界 | 情况 | 行为 | |------|------| | frontmatter 缺尾部 | 不解析,当普通正文 | | 无 image 且正文无图 | `coverLink` 为空 | | Gallery 目录缺失 | 原样保留短代码 | | WebP 转换失败 | 记录日志,使用原文件 | | 非支持图片扩展 | 忽略该文件 | ## 10. 性能 - 正则线性扫描 O(n)。 - Gallery 目录排序 O(m log m)。 - 可后续对 `_listGalleryImages` 结果加缓存。 ## 11. 配置 & 常量 | 常量 | 说明 | 后续计划 | |------|------|----------| | `GALLERY_PRE_PATH` | 画廊根目录 | 移入设置面板 | | `GALLERY_NUM_PIC` | 默认选图数量 | 支持短代码参数覆盖 | | 行级样式内联 | 直接 embed style | 可改 class + CSS | ## 12. 对外接口 | 方法 | 描述 | |------|------| | `getWeChatArticleMeta()` | 获取最近一次渲染抽取的 meta | | `getMetadata()` | 微信上传所需聚合元数据,含封面补回 | | `uploadCover()` | 上传封面,含 webp 处理 | | `uploadLocalImage()` | 上传正文图片 | | `renderMarkdown()` | 触发整个渲染链路 | ## 13. 测试建议 | 测试项 | 用例 | |--------|------| | frontmatter | 正常/缺尾部/缺字段/中文标题 | | 首图回退 | wikilink 与 markdown 顺序交错 | | Gallery | 有/无目录;含 caption;空目录 | | 图片文件名 | 中文/空格/连字符/数字/大小写扩展 | | 行级语法 | 多种颜色并存/与普通段落混排 | | WebP | 可转换/未准备 wasm | | 覆盖逻辑 | frontmatter 不同组合(仅 author、仅 title 等) | ## 14. 可扩展点 | 方向 | 说明 | |------|------| | 更完整 YAML | 使用 `js-yaml` 支持多行、列表、复杂类型 | | tags/categories | 抽取为数组并暴露接口 | | Gallery 参数 | 支持 `count=`、`random=`、`includeDir=` 等 | | 封面策略 | 配置“frontmatter 优先 / 正文优先 / 首图随机” | | 图廊 HTML 模式 | 直接生成 `
` 集合而非 wikilink 列表 | | 样式外置 | 行级块样式改为统一 CSS class | | 目录缓存 | 减少频繁 IO | ## 15. 风险与规避 | 风险 | 缓解 | |------|------| | 简化 frontmatter 误判 | 提示限制 + 计划引入 YAML 解析 | | 正则误伤 | 增加单元测试覆盖边界字符 | | Gallery IO 阻塞 | 后续异步 + loading 占位 | | 移动端缺 fs | try/catch + 环境判断 | | 样式散落行内 | 后续集中到主题 CSS | ## 16. 示例复盘 示例: ``` --- title: 6月特种兵式观展 author: 大童 image: "/img/shufa/a.jpg" --- 前言 ![首图](img/b-first.png) ![[c-second.png]] ``` 结果: - 封面:`![[a.jpg]]`(frontmatter 优先) - 若删去 image 行 → 封面:`![[b-first.png]]`(首图) ## 17. 迭代优先级建议 | 优先级 | 项目 | |--------|------| | 高 | 封面 UI 选择确认 | | 中 | YAML 解析器集成 | | 中 | Gallery 参数化(count/random) | | 中 | tags/categories 抽取 | | 低 | 图廊 HTML figure 模式 | ## 18. 关键函数索引 | 函数 | 作用 | |------|------| | `extractWeChatMeta` | 抽取标题/作者/封面回退 | | `transformGalleryShortcodes` | gallery 短代码 → wikilinks | | `selectGalleryImages` | 画廊图片选择封装 | | `preprocessContent` | 行级语法 HTML 化 | | `getWeChatArticleMeta` | 获取抽取的 meta | | `getMetadata` | 最终上传元数据(含封面回填) | | `MarkdownImage.tokenizer` | 标准图片转 LocalImage token | | `LocalFileRegex` | 统一匹配图片语法 | ## 19. 总结 通过“标准化 → 抽取 → 预处理 → 渲染 → 上传”分层设计,确保各功能模块低耦合并可独立演进。当前设计已满足基础运营发布需求,后续可按优先级增强 YAML 解析、封面配置、多图策略与 gallery 表现力。 --- *若需我继续实现 tags/categories 抽取或 gallery 参数扩展,请直接提出。* ## 附录 A. 草稿箱清空功能 ### A.1 背景 运营过程中测试/多次上传会堆积大量“草稿”,需要一键清理能力,并具备安全保护与预览模式。 ### A.2 接口 | 方法 | 说明 | |------|------| | `clearAllDrafts(appid, { confirm, batchSize=20, retainLatest=0, dryRun=false })` | 批量列出并删除草稿;需 `confirm:true` 才执行实际删除 | ### A.3 选项说明 | 选项 | 类型 | 说明 | |------|------|------| | confirm | boolean | 必须显式 true,否则抛错中止 | | batchSize | number | 分页拉取条数(默认 20,受微信接口限制) | | retainLatest | number | 保留最新 N 条(按接口返回顺序) | | dryRun | boolean | 仅统计将删除的数量,不执行删除 | ### A.4 返回结构 ``` { total: number, // 收集到的全部 media_id 数 skip: number, // 被保留的数量(= retainLatest 实际保留) success: number, // 实际删除成功数(dryRun= true 时恒 0) fail: number, // 删除失败数 fails: Array<{ media_id, status? , errcode?, errmsg?, text? }>, dryRun: boolean } ``` ### A.5 安全措施 1. `confirm` 必须为 true。 2. 可设置 `retainLatest` 防止误删全部。 3. `dryRun` 先预览再正式执行。 4. 删除逐条执行,可在失败时保留失败列表审计。 ### A.6 未来增强 | 方向 | 说明 | |------|------| | 并发删除 | Promise pool 控制并发提升速度 | | 过滤条件 | 按标题关键词/日期范围选择性删除 | | 进度通知 | 分批实时进度 Notice / 状态栏 | | UI 集成 | 命令面板 + 二次确认弹窗 | | 时间排序校验 | 根据返回 `update_time` 明确排序而非假设 | ### A.7 命令面板入口 已添加命令:`清空微信草稿箱 (危险)` (id: `note-to-mp-clear-drafts`) 流程: 1. 首次 confirm:提示风险。 2. 询问是否 dryRun(输入 y 仅预览)。 3. 若非 dryRun,再询问保留最近 N 条。 4. 二次 confirm 再次确认删除范围。 5. 调用 `clearAllDrafts(null, { confirm:true, dryRun, retainLatest })`。 失败处理:捕获异常并 Notice 显示;控制台输出详细错误。 ### A.8 可视化操作面板 (Modal) 新增 `ClearDraftsModal`:提供表单而非多级 confirm/prompt。 表单字段: - appid (可留空自动从当前文章 frontmatter 获取) - 保留最近 N 条(number,默认 0) - DryRun 复选框(默认勾选) 交互流程: 1. 打开命令 → 弹出 Modal。 2. 用户填写/确认参数,首次点“执行”→ 若为真实删除且非 dryRun,会再弹出 confirm。 3. 结果以 JSON 形式写入下方
 区域,便于复制。
4. Notice 简要提示(DryRun 或 完成)。

错误处理:
- try/catch 包裹,失败写入 resultPre 文本 + Notice。
- run 按钮在执行期间 disabled,防止重复触发。

后续增强设想:
| 项目 | 说明 |
|------|------|
| 进度条 | 删除大批量时显示当前进度/总数 |
| 失败重试 | 针对 fails 列表单独重试按钮 |
| 过滤条件 | 增加标题关键词 / 日期起止输入 |
| 多账号选择 | 下拉列出已配置的 appid 列表 |
| 日志导出 | 一键复制 JSON 结果 |