Files
note2any/detaildesign.md
2025-09-22 16:49:33 +08:00

12 KiB
Raw Blame History

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 支持 {{<gallery .../>}}{{<load-photoswipe>}} 转成图片 wikilinks 列表
预处理 在 Markdown 渲染前执行自定义语法转 HTML
易扩展 提供独立函数/接口减少耦合,如 selectGalleryImagesextractWeChatMeta

3. 术语与定义

  • Wikilink 图片语法![[xxx.png]]
  • 标准 Markdown 图片![描述](path/to/xxx.png)
  • Frontmatter:位于首部 --- 包裹的元数据区域。
  • Cover封面:用于公众号首图上传的图片。
  • Gallery Shortcode{{<gallery dir="/img/foo" figcaption="说明"/>}}{{<load-photoswipe>}}

4. 系统现状概览

主要处理链路:

Raw Markdown
  ↓ extractWeChatMeta (保留 frontmatter 内容供分析)
  ↓ 去 frontmatter
  ↓ transformGalleryShortcodes (gallery → ![[...]] 列表)
  ↓ marked.parse() (图片扩展 -> LocalImage token
  ↓ 生成 HTML + 样式注入
  ↓ setArticle()
  ↓ getArticleContent() -> preprocessContent(line regex 替换) -> 最终 HTML

5. 架构模块划分

模块 关键函数/变量 作用
内容预处理 preprocessContent() 行级 Regex 转 HTML图片路径修正、`
图片统一解析 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 图片统一转换

  • RegexLocalFileRegex = /^(?:!\[\[(.*?)\]\]|!\[[^\]]*\]\(([^\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 .../]<span>(题注样式)。
  • 行级命令:||r / ||g / ||b / ||y / || → 不同背景色 <p>
  • <img src="img/..."> → 前缀补全 /img/
  • 短代码 Regex{{<gallery dir="..."( figcaption="...")?/ >}}{{<load-photoswipe>}}
  • _listGalleryImages:读目录 + 过滤扩展 + 排序 + 截断。
  • selectGalleryImages:对外通用(支持未来 random / prefix / includeDirInLink
  • 输出:多行 ![[file]],并追加可选 figcaption div。

支持:

{{<gallery>}}
{{<figure src="/img/foo-1.png" caption="说明" >}}
{{<figure src="/img/foo-2.jpeg" caption="说明2" >}}
{{</gallery>}}

转换:

![[foo-1.png]]
![[foo-2.jpeg]]

规则:

  • 仅取 src 的 basename忽略 caption后续可扩展为题注输出
  • 若块内未匹配到任何 figure保留原文本。
  • 正则:/{{<gallery>}}([\s\S]*?){{<\/gallery>}}/g 与内部 figureRegex = /{{<figure\s+src="([^"]+)"[^>]*>}}/g
  • 输出顺序按出现顺序。

8. 正则清单

场景 正则 说明
frontmatter ^---[\s\S]*?\n--- 仅首段
Wikilink 图片 !\[\[(.+?)\]\] 非贪婪
Markdown 图片 !\[[^\]]*\]\(([^\n\r\)]+)\) 不跨行
Gallery {{<gallery\s+dir=\"([^\"]+)\"(?:\s+figcaption=\"([^\"]*)\")?\s*\/>}}{{<load-photoswipe>}} 捕获 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 模式 直接生成 <figure> 集合而非 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 结果