171 lines
8.1 KiB
Markdown
171 lines
8.1 KiB
Markdown
# Image Pipeline & Cover Strategy
|
||
|
||
> 对应代码核心:`src/markdown/local-file.ts`, `src/article-render.ts` 以及相关预处理逻辑。
|
||
> 若需总体结构参见 `architecture.md`,更广设计参见 `detaildesign.md`。
|
||
|
||
## 1. 目标
|
||
- 统一处理所有图片来源:Wikilink、标准 Markdown、远程 URL、Base64、生成图 (Mermaid/Excalidraw/SVG)。
|
||
- 保证上传后 HTML 使用微信可访问的正式 URL。
|
||
- 自动封面选取:未指定时从正文首图推断。
|
||
- WebP → JPG 转换保障兼容性。
|
||
- 默认封面配置:若无正文/画廊候选,使用 `defaultCoverPic` (settings)。
|
||
- 手动 frontmatter 回退:metadata 缺失时行级解析 `title/author/image`。
|
||
|
||
## 2. 图片来源与归一化
|
||
| 来源 | 输入示例 | 归一化策略 | 备注 |
|
||
|------|----------|-----------|------|
|
||
| Wikilink | `![[foo.png]]` / `![[foo.png|120x80]]` | Marked 扩展直接生成 `<img src="vault://...">` 并登记 | 支持尺寸 / 位置扩展语法 |
|
||
| Markdown | `` | (可选) 预处理转换成 `![[foo.png]]` | 依赖设置:`enableMarkdownImageToWikilink` |
|
||
| 远程 URL | `<img src="https://...">` | 解析为远程上传任务 | 跳过已是微信域名 |
|
||
| Base64 | `<img src="data:image/png;base64,...">` | 解析成 Blob 上传 | 生成虚拟 id 构造文件名 |
|
||
| Mermaid | 源码块 → SVG/DOM | 转 PNG (html-to-image) / 留 SVG | 替换占位容器 |
|
||
| Excalidraw | `![[diagram.excalidraw]]` | 远程接口取 SVG 或转 PNG | 需 AuthKey |
|
||
| SVG 文件 | `![[icon.svg]]` | 内联 SVG 内容 | 直接嵌入,不上传 |
|
||
|
||
## 3. 关键数据结构
|
||
```ts
|
||
interface ImageInfo {
|
||
resUrl: string; // 初始占位或本地资源 URL (vault path 映射)
|
||
filePath: string; // Vault 内实际文件路径
|
||
url: string | null; // 上传后微信返回 URL
|
||
media_id: string | null; // 上传后素材 ID(type=image 时)
|
||
}
|
||
```
|
||
管理容器:`Map<string, ImageInfo>`,键值为 `resUrl`(初始唯一标识)。
|
||
|
||
## 4. 收集阶段
|
||
1. Marked 扩展 (`LocalFile.markedExtension`) 在 token 遍历时识别 LocalImage。
|
||
2. 根据 wikilink / 尺寸参数解析出真实 vault 文件路径:`assetsManager.getResourcePath()`。
|
||
3. 调用 `LocalImageManager.setImage(res.resUrl, info)` 建档。
|
||
4. Markdown 图片(标准语法)若转换启用,提前转成 wikilink 进入同一逻辑;否则需未来扩展 tokenizer 直接登记。
|
||
5. Gallery:
|
||
- 短代码:`{{<gallery dir="..."/>}}{{<load-photoswipe>}}` → 目录枚举(受 `galleryPrePath` + `galleryNumPic` 影响)→ 多行 `![[...]]` 注入。
|
||
- 块级:`{{<gallery>}} ... {{</gallery>}}` 内 `figure src="..." link="..." caption="..." >` 解析 src basename 加入候选;`link` 预留后续包装;`caption` 未来映射题注。
|
||
|
||
## 5. 上传阶段
|
||
顺序:
|
||
```mermaid
|
||
graph TD
|
||
A[准备 token] --> B[cachedElementsToImages]
|
||
B --> C[uploadLocalImage]
|
||
C --> D[uploadRemoteImage]
|
||
D --> E[replaceImages]
|
||
E --> F[输出 HTML / 复制 / 发布]
|
||
```
|
||
|
||
### 5.1 本地图片 `uploadLocalImage`
|
||
流程:
|
||
- 遍历登记表,读取 vault 文件二进制。
|
||
- 若扩展名 `.webp` → wasm (`PrepareImageLib`) 转 JPG。
|
||
- 调用 `wxUploadImage(blob, filename, token, type)`。
|
||
- 更新 `ImageInfo.url` / `media_id`。
|
||
- 失败:`Notice + console.error`,继续后续项。
|
||
|
||
### 5.2 远程图片 `uploadRemoteImage`
|
||
匹配 `<img src="http...">`:
|
||
- 跳过已在微信域名(`mmbiz.qpic.cn`)。
|
||
- 下载 → Blob → (webp 转换) → 上传。
|
||
- 更新 Map,键为原始 src。
|
||
Base64:
|
||
- 解析 data URI → 生成 Blob/扩展名 → 上传。
|
||
|
||
### 5.3 替换阶段 `replaceImages`
|
||
- 遍历 DOM `<img>` → 查询 Map → 使用上传后 `url` 覆盖 `src`。
|
||
- 未登记:输出警告(潜在渲染管线遗漏)。
|
||
|
||
## 6. 自动封面策略(更新)
|
||
触发点:`getMetadata()`;若存在 `thumb_media_id` 则跳过。决策链:
|
||
1. frontmatter cover/image(非空);若 metadataCache 缺失,则手动行级解析首个 `---` 块。
|
||
2. 正文本地图候选(markdown + wikilink)。
|
||
3. gallery 展开产生的首图(短代码或块级 figure 列表)。
|
||
4. `defaultCoverPic`(settings 配置,允许相对路径 / URL / wikilink)。
|
||
5. 否则封面为空。
|
||
|
||
Edge Cases:
|
||
| 情况 | 处理 |
|
||
|------|------|
|
||
| frontmatter cover: "" | 视为未配置,继续回退 |
|
||
| 所有正文图片都是 http(s) | 跳过正文阶段,转 gallery -> defaultCoverPic |
|
||
| gallery 块无有效 figure | 不贡献候选 |
|
||
| defaultCoverPic 不存在 | 仍返回该引用;上传阶段可能失败(待校验增强) |
|
||
| markdown 图片后缀非图片 | 跳过 |
|
||
| 重复图片 | 以最先出现的为准 |
|
||
|
||
## 7. 正则索引
|
||
| 目的 | 正则 | 描述 |
|
||
|------|------|------|
|
||
| Frontmatter 删除 | `/^(---)$.+?^(---)$.+?/ims` | 首段 YAML |
|
||
| Markdown 图片 | `/!\[[^\]]*\]\(([^)\s]+)\)/g` | 捕获路径 group1 |
|
||
| Wikilink 图片 | `/!\[\[(.+?)\]\]/g` | 捕获内部资源 |
|
||
| Gallery 块 | `{{<gallery>}}([\s\S]*?){{<\/gallery>}}` | 包裹内容 |
|
||
| Gallery figure | `{{<figure\s+src=\"([^\"]+)\"[^>]*>}}` | 提取 src |
|
||
| figure link | `link=\"([^\"]+)\"` | 可选外链属性 |
|
||
| WebP 检测 | `/\.webp$/i` | 扩展判断 |
|
||
| Base64 前缀 | `^data:image/` | 判断内嵌图 |
|
||
|
||
## 8. 失败与恢复策略
|
||
| 环节 | 失败示例 | 策略 |
|
||
|------|----------|------|
|
||
| wasm 初始化 | 网络慢 / 未加载 | 继续使用原 webp 上传(失败概率增加) |
|
||
| 本地文件找不到 | 路径错误 / 移动 | 在控制台警告,图片跳过 |
|
||
| 上传 403/401 | token 失效 | 抛异常终止流程(需重新获取 token) |
|
||
| 单图 errcode !=0 | 大小/格式不合规 | Notice 提示 + 继续其他 |
|
||
| 替换未命中 | DOM 没登记 | 记录警告,建议加入补偿扫描 |
|
||
|
||
## 9. 性能优化方向
|
||
| 问题 | 现状 | 改进 |
|
||
|------|------|------|
|
||
| 上传串行 | 顺序 await | 并发池 (N=3~5) + 重试退避 |
|
||
| WebP 转换阻塞 | 单线程 | 预热 wasm + 批量并行 |
|
||
| 远程下载串行 | 每图 await | 统一收集 Promise.all 控制并发 |
|
||
| 首图扫描重复 | 重新正则两次 | 合并一次统一匹配 pipeline |
|
||
|
||
## 10. 与发布流程的接口点
|
||
| 函数 | 上下游 | 说明 |
|
||
|------|--------|------|
|
||
| `cachedElementsToImages()` | 延迟图形 → 图片 | 在上传前确保所有图形成为 <img> 可被收集 |
|
||
| `uploadLocalImage()` | 发布前 | 更新本地图片 URL/media_id |
|
||
| `uploadRemoteImage()` | 发布前 | 远程/内嵌资源统一化 |
|
||
| `replaceImages()` | 发布前 | DOM HTML 成为最终状态 |
|
||
| `getArticleContent()` | 发布 / 复制 / 导出 | 输出含最终图片 URL 的 HTML |
|
||
|
||
## 11. 未来扩展
|
||
| 需求 | 设想 |
|
||
|------|------|
|
||
| CDN 直传 | 微信前置 → 自建或 OSS 中转 |
|
||
| 图片压缩 | 上传前可选压缩 (canvas/wasm) |
|
||
| 失效重传 | 保存上传映射 + 校验缺失再补传 |
|
||
| 分块上传 | 大图分片(若接口支持) |
|
||
| 图片校验 | MD5 去重避免重复上传 |
|
||
| defaultCoverPic 校验 | 渲染时验证存在性 + Notice |
|
||
| 多默认封面池 | 数组随机选择减少视觉重复 |
|
||
| caption alias | figure caption -> wikilink alias/figcaption |
|
||
| link 包裹 | link 属性生成 `<a>` 包裹 `<img>` |
|
||
|
||
## 12. 快速检查清单 (Debug Checklist)
|
||
- 图片是否在 `LocalImageManager.images` Map 中?
|
||
- 是否执行了 `cachedElementsToImages()`(Mermaid/Excalidraw)?
|
||
- 上传后 `media_id` 是否为空?(格式/大小不合规)
|
||
- DOM 替换后 `<img src>` 是否为微信域?
|
||
- 自动封面未生效?检查:frontmatter cover / 正文本地图是否存在 / gallery 是否展开 / defaultCoverPic 是否配置。
|
||
- 重复封面日志?确认日志节流是否失效(3 秒窗口内只应出现一次)。
|
||
|
||
## 13. 示例
|
||
输入 Markdown:
|
||
```
|
||
---
|
||
title: 示例
|
||
---
|
||

|
||
正文...
|
||
![[b-second.jpg]]
|
||
```
|
||
处理:
|
||
1. frontmatter 去除后扫描:首个匹配为 Markdown 图片 a.png → 自动封面 `![[a.png]]`。
|
||
2. a.png 转 wikilink (开启转换时) → 登记;b-second.jpg 登记。
|
||
3. 上传顺序:a.png → b-second.jpg。
|
||
4. 最终 HTML `<img src="https://mmbiz.qpic.cn/...">`。
|
||
|
||
---
|
||
如需补充“并发上传样例代码”或“封面过滤策略扩展”,请提出。
|