update at 2025-10-09 12:39:24
This commit is contained in:
365
docs/detaildesign.md
Normal file
365
docs/detaildesign.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# 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]]` 与 `` 最终统一进入 LocalImage 管线 |
|
||||
| 元数据抽取 | 自动获取标题、作者、封面图(可回退)供后续上传逻辑使用 |
|
||||
| 封面回退 | 未显式指定封面时,自动决策第一张图片 |
|
||||
| Gallery 支持 | 将 `{{<gallery .../>}}{{<load-photoswipe>}}` 转成图片 wikilinks 列表 |
|
||||
| 预处理 | 在 Markdown 渲染前执行自定义语法转 HTML |
|
||||
| 易扩展 | 提供独立函数/接口减少耦合,如 `selectGalleryImages`、`extractWeChatMeta` |
|
||||
| 默认封面配置 | 无任何图片候选时使用 `defaultCoverPic` (可配置) |
|
||||
| 前置回退解析 | 若 metadataCache 缺失 frontmatter,启用手动行级解析回退 |
|
||||
| Gallery 块扩展 | 支持 `{{<gallery>}}` 块 + 内部 `figure src|link=` 解析 |
|
||||
| 行级语法扩展 | `[fig .../]` 与 `||r`/`||g`/`||b`/`||y`/`||` 统一由 `applyCustomInlineBlocks` 处理 |
|
||||
| 调试日志节流 | 输出当前文件路径与默认封面选用日志,3 秒内同路径不重复 |
|
||||
|
||||
## 3. 术语与定义
|
||||
- **Wikilink 图片语法**:`![[xxx.png]]`
|
||||
- **标准 Markdown 图片**:``
|
||||
- **Frontmatter**:位于首部 `---` 包裹的元数据区域。
|
||||
- **Cover(封面)**:用于公众号首图上传的图片。
|
||||
- **Gallery Shortcode**:`{{<gallery dir="/img/foo" figcaption="说明"/>}}{{<load-photoswipe>}}`
|
||||
|
||||
## 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. 匹配 `` → `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 .../]` → `<span>`(题注样式)。
|
||||
- 行级命令:`||r / ||g / ||b / ||y / ||` → 不同背景色 `<p>`。
|
||||
- `<img src="img/...">` → 前缀补全 `/img/`。
|
||||
|
||||
### 7.4 Gallery 功能
|
||||
- 短代码 Regex:`{{<gallery dir="..."( figcaption="...")?/ >}}{{<load-photoswipe>}}`
|
||||
- `_listGalleryImages`:读目录 + 过滤扩展 + 排序 + 截断。
|
||||
- `selectGalleryImages`:对外通用(支持未来 random / prefix / includeDirInLink)。
|
||||
- 输出:多行 `![[file]]`,并追加可选 `figcaption` div。
|
||||
|
||||
#### 7.4.1 块级 Gallery 语法(新增)
|
||||
支持:
|
||||
```
|
||||
{{<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`。
|
||||
- 输出顺序按出现顺序。
|
||||
- `figure` 标签支持 `src="..."` 与可选 `link="..."`,当存在 link 时仍按 `src` 的 basename 作为图片候选;后续可利用 link 生成超链接包装。
|
||||
|
||||
#### 7.4.2 link 属性与未来 caption 计划
|
||||
- 当前:`link` 仅被解析但未输出额外结构,保留在后续渲染扩展阶段使用(例如生成 `<a>` 包裹 `<img>`)。
|
||||
- 规划:`caption` 字段可映射为 wikilink alias 或 `<figcaption>`。
|
||||
|
||||
### 7.5 行级轻语法扩展 (`applyCustomInlineBlocks`)
|
||||
- 输入:渲染后 HTML / 或预处理文本段落。
|
||||
- 规则:
|
||||
- `[fig 内容 /]` → `<span class="n2m-fig">内容</span>`(当前实现可能用内联 style,后续计划换 class)。
|
||||
- `||r 文本` / `||g` / `||b` / `||y` / `|| 文本` → 彩色背景段落 `<p style>...</p>`。
|
||||
- 节点安全:通过转义内部 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 | `{{<gallery\s+dir=\"([^\"]+)\"(?:\s+figcaption=\"([^\"]*)\")?\s*\/>}}{{<load-photoswipe>}}` | 捕获 dir/caption |
|
||||
| Gallery 块 | `{{<gallery>}}([\s\S]*?){{<\/gallery>}}` | 块包裹内容 |
|
||||
| Gallery figure | `{{<figure\s+src=\"([^\"]+)\"[^>]*>}}` | 提取图片 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 模式 | 直接生成 `<figure>` 集合而非 wikilink 列表 |
|
||||
| 样式外置 | 行级块样式改为统一 CSS class |
|
||||
| 默认封面池 | 支持数组随机选择 default cover |
|
||||
| 默认封面校验 | 选择时校验存在性 + Notice 提示 |
|
||||
| caption alias | gallery figure caption -> wikilink alias/figcaption |
|
||||
| link wrap | figure link 生成 `<a>` 包裹图片 |
|
||||
| debug 开关 | 设置中关闭全部调试日志 |
|
||||
| 目录缓存 | 减少频繁 IO |
|
||||
|
||||
## 15. 风险与规避
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| 简化 frontmatter 误判 | 提示限制 + 计划引入 YAML 解析 |
|
||||
| 正则误伤 | 增加单元测试覆盖边界字符 |
|
||||
| Gallery IO 阻塞 | 后续异步 + loading 占位 |
|
||||
| 移动端缺 fs | try/catch + 环境判断 |
|
||||
| 样式散落行内 | 后续集中到主题 CSS |
|
||||
|
||||
## 16. 示例复盘
|
||||
示例:
|
||||
```
|
||||
---
|
||||
title: 6月特种兵式观展
|
||||
author: 大童
|
||||
image: "/img/shufa/a.jpg"
|
||||
---
|
||||
前言
|
||||

|
||||
![[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 形式写入下方 <pre> 区域,便于复制。
|
||||
4. Notice 简要提示(DryRun 或 完成)。
|
||||
|
||||
错误处理:
|
||||
- try/catch 包裹,失败写入 resultPre 文本 + Notice。
|
||||
- run 按钮在执行期间 disabled,防止重复触发。
|
||||
|
||||
后续增强设想:
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 进度条 | 删除大批量时显示当前进度/总数 |
|
||||
| 失败重试 | 针对 fails 列表单独重试按钮 |
|
||||
| 过滤条件 | 增加标题关键词 / 日期起止输入 |
|
||||
| 多账号选择 | 下拉列出已配置的 appid 列表 |
|
||||
| 日志导出 | 一键复制 JSON 结果 |
|
||||
|
||||
Reference in New Issue
Block a user