# 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` | | 默认封面配置 | 无任何图片候选时使用 `defaultCoverPic` (可配置) | | 前置回退解析 | 若 metadataCache 缺失 frontmatter,启用手动行级解析回退 | | Gallery 块扩展 | 支持 `{{}}` 块 + 内部 `figure src|link=` 解析 | | 行级语法扩展 | `[fig .../]` 与 `||r`/`||g`/`||b`/`||y`/`||` 统一由 `applyCustomInlineBlocks` 处理 | | 调试日志节流 | 输出当前文件路径与默认封面选用日志,3 秒内同路径不重复 | ## 3. 术语与定义 - **Wikilink 图片语法**:`![[xxx.png]]` - **标准 Markdown 图片**:`![描述](path/to/xxx.png)` - **Frontmatter**:位于首部 `---` 包裹的元数据区域。 - **Cover(封面)**:用于公众号首图上传的图片。 - **Gallery Shortcode**:`{{}}{{}}` ## 4. 系统现状概览 主要处理链路: ``` Raw Markdown ↓ extractWeChatMeta (保留 frontmatter 内容供分析) ↓ 去 frontmatter ↓ transformGalleryShortcodes (gallery → ![[...]] 列表) ↓ transformGalleryBlock (gallery 块/figure → ![[...]] 列表) ↓ marked.parse() (图片扩展 -> LocalImage token) ↓ applyCustomInlineBlocks (fig/彩色段落 轻语法 HTML 化) ↓ 生成 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()` 融合以补齐空缺字段。 - 若 Obsidian `metadataCache` 返回为空或缺失字段,触发手动 fallback:扫描首段 frontmatter 行(不依赖外部 YAML 包),支持 `key: value` 单行形式;空字符串的 cover/image 会被视为未提供。 - 追加默认封面逻辑:封面候选链(frontmatter cover > 正文首本地图/本地 wikilink/markdown > gallery 生成图 > defaultCoverPic)。 ### 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`。 - 输出顺序按出现顺序。 - `figure` 标签支持 `src="..."` 与可选 `link="..."`,当存在 link 时仍按 `src` 的 basename 作为图片候选;后续可利用 link 生成超链接包装。 #### 7.4.2 link 属性与未来 caption 计划 - 当前:`link` 仅被解析但未输出额外结构,保留在后续渲染扩展阶段使用(例如生成 `` 包裹 ``)。 - 规划:`caption` 字段可映射为 wikilink alias 或 `
`。 ### 7.5 行级轻语法扩展 (`applyCustomInlineBlocks`) - 输入:渲染后 HTML / 或预处理文本段落。 - 规则: - `[fig 内容 /]` → `内容`(当前实现可能用内联 style,后续计划换 class)。 - `||r 文本` / `||g` / `||b` / `||y` / `|| 文本` → 彩色背景段落 `

...

`。 - 节点安全:通过转义内部 HTML 以防注入(若未实现需列入风险)。 - 后续:提取公共 class + 主题 CSS。 ### 7.6 调试日志与节流 - 目的:调试封面选取与路径解析;避免刷屏。 - 机制:记录最近一次输出路径时间戳,3 秒内同路径日志抑制。 - 日志包括:当前 markdown 文件绝对路径;默认封面 fallback 触发说明;gallery 转换统计(可选)。 ### 7.7 配置项外化 (Settings 更新) - 新增:`galleryPrePath`, `galleryNumPic`, `defaultCoverPic`。 - 位置:`NMPSettings` + `SettingTab` UI 输入框。 - 迁移:移除硬编码常量 `GALLERY_PRE_PATH` / `GALLERY_NUM_PIC`。 - 默认值:`defaultCoverPic = 'cover.png'`(可为相对/绝对/网络 URL 或 wikilink 形式)。 - 风险:用户提供的默认封面不存在 → 目前不校验,可后续增加存在性检查与 Notice。 ### 7.8 封面候选决策链(更新版) 1. 若已有 `thumb_media_id`(外部指定)→ 不再上传本地封面,保持 null。 2. frontmatter cover/image(非空字符串)→ 使用其 basename 生成 wikilink。 3. 正文扫描首个本地图片(markdown / wikilink;忽略 http/https)。 4. 若正文无 → 使用 gallery 自动展开生成的第一张候选。 5. 若仍无 → 使用 `defaultCoverPic`(若配置)。 6. 若 `defaultCoverPic` 也无 → cover 为空。 Edge Cases: - frontmatter cover: "" → 视为未提供。 - defaultCoverPic 若为绝对 URL → 在上传阶段需区分远程/本地策略。 - gallery 展开后若所有图片为远程 URL(未来支持)→ 不作为本地候选,跳到 defaultCoverPic。 ## 8. 正则清单 | 场景 | 正则 | 说明 | |------|------|------| | frontmatter | `^---[\s\S]*?\n---` | 仅首段 | | Wikilink 图片 | `!\[\[(.+?)\]\]` | 非贪婪 | | Markdown 图片 | `!\[[^\]]*\]\(([^\n\r\)]+)\)` | 不跨行 | | Gallery | `{{}}{{}}` | 捕获 dir/caption | | Gallery 块 | `{{}}([\s\S]*?){{<\/gallery>}}` | 块包裹内容 | | Gallery figure | `{{]*>}}` | 提取图片 src | | Figure link 属性 | `link=\"([^\"]+)\"` | 可选外链(当前仅解析) | | fig | `\[fig([^>]*?)\/]` | 题注 | | 行块 | `\|\|r (.*)` 等 | 行级匹配 | ## 9. 错误与边界 | 情况 | 行为 | |------|------| | frontmatter 缺尾部 | 不解析,当普通正文 | | 无 image 且正文无图 | `coverLink` 为空 | | Gallery 目录缺失 | 原样保留短代码 | | WebP 转换失败 | 记录日志,使用原文件 | | 非支持图片扩展 | 忽略该文件 | ## 10. 性能 - 正则线性扫描 O(n)。 - Gallery 目录排序 O(m log m)。 - 可后续对 `_listGalleryImages` 结果加缓存。 ## 11. 配置 & 常量 | 常量 | 说明 | 后续计划 | |------|------|----------| | `galleryPrePath` | 画廊根目录(配置项) | 未来参数化 per-block 覆盖 | | `galleryNumPic` | 默认选图数量(配置项) | 支持块/短代码 count 覆盖 | | `defaultCoverPic` | 默认封面备用 | 校验存在 / 多备选随机 | | (移除)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 | | 默认封面池 | 支持数组随机选择 default cover | | 默认封面校验 | 选择时校验存在性 + Notice 提示 | | caption alias | gallery figure caption -> wikilink alias/figcaption | | link wrap | figure link 生成 `` 包裹图片 | | debug 开关 | 设置中关闭全部调试日志 | | 目录缓存 | 减少频繁 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 结果 |