update at 2025-09-22 18:54:59
This commit is contained in:
88
README.md
88
README.md
@@ -300,6 +300,94 @@ NoteToMP插件支持该语法。
|
|||||||
### 插入SVG图标
|
### 插入SVG图标
|
||||||
https://www.bilibili.com/video/BV15XWVeEEJa/
|
https://www.bilibili.com/video/BV15XWVeEEJa/
|
||||||
|
|
||||||
|
### Gallery 短代码支持
|
||||||
|
|
||||||
|
自 1.x 版本起,插件支持将形如 Hugo/Hexo 风格的短代码:
|
||||||
|
|
||||||
|
```
|
||||||
|
{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}
|
||||||
|
```
|
||||||
|
|
||||||
|
在渲染阶段自动展开为若干行图片 WikiLink:
|
||||||
|
|
||||||
|
```
|
||||||
|
![[001.jpg]]
|
||||||
|
![[002.jpg]]
|
||||||
|
```
|
||||||
|
|
||||||
|
配置项:
|
||||||
|
|
||||||
|
- Gallery 根路径(galleryPrePath):指向本地实际图片根目录,用于拼接短代码中的 dir 得到真实磁盘路径。
|
||||||
|
- Gallery 选取图片数(galleryNumPic):每个 gallery 最多展开前 N 张图片(按文件名排序)。
|
||||||
|
|
||||||
|
可在插件设置界面直接修改,无需重启。若希望随机选取或按时间排序,可后续在 issue 中反馈需求。
|
||||||
|
|
||||||
|
### Gallery 块与 figure 支持
|
||||||
|
|
||||||
|
除了带 dir 的短代码,还支持块级:
|
||||||
|
|
||||||
|
```
|
||||||
|
{{<gallery>}}
|
||||||
|
{{<figure src="/img/a.jpg" caption="说明" >}}
|
||||||
|
{{<figure link="/img/b.png" caption="说明" >}}
|
||||||
|
{{</gallery>}}
|
||||||
|
```
|
||||||
|
|
||||||
|
渲染为:
|
||||||
|
|
||||||
|
```
|
||||||
|
![[a.jpg]]
|
||||||
|
![[b.png]]
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 支持 `src` 或 `link` 属性任选其一。
|
||||||
|
- `caption` 当前忽略(可后续增强:写入 `![[file|caption]]` 或紧随段落)。
|
||||||
|
- 去重/排序策略:按出现顺序,文件名原样。
|
||||||
|
|
||||||
|
### 自定义行级语法扩展
|
||||||
|
|
||||||
|
为提升公众号排版效率,插件内置以下“轻语法”转换(发生在 Markdown 解析前):
|
||||||
|
|
||||||
|
1. 斜体标注:`[fig 一段说明 /]` → `<span style="font-style:italic;...">一段说明</span>`
|
||||||
|
2. 彩色提示块(只作用当前这一行,不跨行):
|
||||||
|
- `|| 内容` 默认灰底
|
||||||
|
- `||r 内容` 棕底白字
|
||||||
|
- `||g 内容` 黄绿色背景
|
||||||
|
- `||b 内容` 浅灰背景
|
||||||
|
- `||y 内容` 浅黄背景
|
||||||
|
|
||||||
|
这些语法不会写回原笔记,只影响发布预览。后续可加入:类名替换 + 主题化配置 + caption 支持,欢迎反馈需求。
|
||||||
|
|
||||||
|
### 无图片时的默认封面
|
||||||
|
|
||||||
|
自动封面选择优先级:
|
||||||
|
1. frontmatter: cover / image(非空)
|
||||||
|
2. 正文首图(Markdown 或 WikiLink)
|
||||||
|
3. Gallery 短代码 / 块展开得到的首图
|
||||||
|
4. 默认封面 `defaultCoverPic`(设置面板可配置,默认 `cover.png`)
|
||||||
|
|
||||||
|
配置说明:
|
||||||
|
- 若填写文件名(如 `cover.png`),会按当前笔记目录解析并包装为 `![[cover.png]]`。
|
||||||
|
- 若填写完整 `![[xxx]]` 语法或 `http(s)://` URL,将原样使用。
|
||||||
|
- 若文件不存在,不会报错(可后续增加存在性提示)。
|
||||||
|
|
||||||
|
### Frontmatter 解析回退
|
||||||
|
|
||||||
|
如果 Obsidian `metadataCache` 暂未命中(例如首次载入或缓存延迟),插件会手动对首段 `---` YAML 进行轻量行级解析,提取:
|
||||||
|
- title / author / cover(image)
|
||||||
|
|
||||||
|
避免因为缓存未就绪导致标题/作者缺失。若需复杂 YAML(数组、多行字符串)建议等待官方缓存,或后续考虑引入完整 YAML 解析库。
|
||||||
|
|
||||||
|
### 调试日志
|
||||||
|
|
||||||
|
在控制台(开发者工具)可看到:
|
||||||
|
```
|
||||||
|
[note2mp] active file path: your/file/path.md
|
||||||
|
[note2mp] use default cover: cover.png -> ![[cover.png]]
|
||||||
|
```
|
||||||
|
路径日志做了节流:同一文件 3 秒内不重复打印。后续可加“调试开关”以完全关闭。
|
||||||
|
|
||||||
### 摘要、封面裁剪、原文链接等
|
### 摘要、封面裁剪、原文链接等
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -13,19 +13,28 @@ UI / Interaction (NotePreview)
|
|||||||
|
|
||||||
Rendering & Composition (ArticleRender + MarkedParser + Extensions)
|
Rendering & Composition (ArticleRender + MarkedParser + Extensions)
|
||||||
├─ Markdown 预处理 (frontmatter 剥离 / 可选图片语法转换)
|
├─ Markdown 预处理 (frontmatter 剥离 / 可选图片语法转换)
|
||||||
|
├─ Gallery 展开 (短代码 & 块级 figure src|link → wikilink 列表)
|
||||||
├─ 语法扩展 (LocalFile: wikilink 图片、文件嵌入、Excalidraw、SVG)
|
├─ 语法扩展 (LocalFile: wikilink 图片、文件嵌入、Excalidraw、SVG)
|
||||||
|
├─ 行级轻语法 (applyCustomInlineBlocks: fig / ||r||g||b||y||)
|
||||||
├─ 延迟元素缓存 (Mermaid / Excalidraw)
|
├─ 延迟元素缓存 (Mermaid / Excalidraw)
|
||||||
└─ 样式注入 (主题 + 代码高亮 + 自定义 CSS)
|
└─ 样式注入 (主题 + 代码高亮 + 自定义 CSS)
|
||||||
|
|
||||||
Assets & Settings Layer
|
Assets & Settings Layer
|
||||||
├─ AssetsManager (themes, highlights, customCSS)
|
├─ AssetsManager (themes, highlights, customCSS)
|
||||||
└─ NMPSettings (frontmatter key 映射 / 功能开关 / 微信配置)
|
└─ NMPSettings (frontmatter key 映射 / 功能开关 / 微信配置 / galleryPrePath & galleryNumPic & defaultCoverPic)
|
||||||
|
|
||||||
Resource & Media Layer
|
Resource & Media Layer
|
||||||
├─ LocalImageManager (图片登记 / 上传 / 替换 / base64 嵌入)
|
├─ LocalImageManager (图片登记 / 上传 / 替换 / base64 嵌入)
|
||||||
├─ Image Conversion (WebP -> JPG wasm)
|
├─ Image Conversion (WebP -> JPG wasm)
|
||||||
└─ html-to-image (Mermaid/Excalidraw 转 PNG)
|
└─ html-to-image (Mermaid/Excalidraw 转 PNG)
|
||||||
|
|
||||||
|
Cover Fallback Chain (逻辑横切 Concern)
|
||||||
|
frontmatter cover/image → 正文首本地图片 → gallery 生成列表首图 → defaultCoverPic (配置) → 空
|
||||||
|
|
||||||
|
Debug Logging & Throttle
|
||||||
|
- 输出:当前文件路径、封面决策(包含 defaultCoverPic 触发)
|
||||||
|
- 节流:同路径 3 秒内不重复打印
|
||||||
|
|
||||||
WeChat Integration Layer
|
WeChat Integration Layer
|
||||||
├─ Token 代理 (wxGetToken)
|
├─ Token 代理 (wxGetToken)
|
||||||
├─ 草稿创建 (wxAddDraft / wxAddDraftImages)
|
├─ 草稿创建 (wxAddDraft / wxAddDraftImages)
|
||||||
@@ -77,10 +86,19 @@ function resolveCover(originalMarkdown: string, fmCover?: string, thumbMediaId?:
|
|||||||
if (fmCover) return fmCover; // frontmatter 指定
|
if (fmCover) return fmCover; // frontmatter 指定
|
||||||
const body = stripFrontmatter(originalMarkdown);
|
const body = stripFrontmatter(originalMarkdown);
|
||||||
const candidates = collectImageOrder(body); // wikilink + markdown
|
const candidates = collectImageOrder(body); // wikilink + markdown
|
||||||
return candidates.length ? `![[${candidates[0]}]]` : null;
|
// 若正文无本地候选再尝试 gallery 展开结果
|
||||||
|
if (!candidates.length) {
|
||||||
|
const galleryList = collectGalleryFirstImages(body);
|
||||||
|
if (galleryList.length) candidates.push(galleryList[0]);
|
||||||
|
}
|
||||||
|
if (candidates.length) return `![[${candidates[0]}]]`;
|
||||||
|
// 最终 defaultCoverPic 由外层 getMetadata 注入(若配置)
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Fallback 扩展:`getMetadata()` 在上述返回为空且存在 `defaultCoverPic` 时作为最终封面。
|
||||||
|
|
||||||
## 5. 图片收集策略统一要点
|
## 5. 图片收集策略统一要点
|
||||||
- Wikilink:解析时即登记(路径 → vault file)。
|
- Wikilink:解析时即登记(路径 → vault file)。
|
||||||
- Markdown 图片:可配置预处理转 Wikilink,以复用同一逻辑(避免重复正则后处理)。
|
- Markdown 图片:可配置预处理转 Wikilink,以复用同一逻辑(避免重复正则后处理)。
|
||||||
@@ -118,6 +136,8 @@ function resolveCover(originalMarkdown: string, fmCover?: string, thumbMediaId?:
|
|||||||
- 多管线并存(旧 ArticleRender vs 新 RenderService 占位)易导致图片未登记 → 需统一抽象。
|
- 多管线并存(旧 ArticleRender vs 新 RenderService 占位)易导致图片未登记 → 需统一抽象。
|
||||||
- 自动封面选取对远程图片/非图片扩展尚无过滤完备策略。
|
- 自动封面选取对远程图片/非图片扩展尚无过滤完备策略。
|
||||||
- 正则预处理与 Marked tokenizer 逻辑耦合度高,重构时需单测保护。
|
- 正则预处理与 Marked tokenizer 逻辑耦合度高,重构时需单测保护。
|
||||||
|
- 默认封面文件不存在导致封面缺失但用户误以为已设置 → 需未来加入存在性校验与 Notice。
|
||||||
|
- link/caption 属性当前解析未输出 → 未来转型时注意兼容老内容。
|
||||||
|
|
||||||
## 10. 索引 / 交叉引用
|
## 10. 索引 / 交叉引用
|
||||||
| 文档 | 内容概述 |
|
| 文档 | 内容概述 |
|
||||||
|
|||||||
@@ -24,6 +24,11 @@
|
|||||||
| Gallery 支持 | 将 `{{<gallery .../>}}{{<load-photoswipe>}}` 转成图片 wikilinks 列表 |
|
| Gallery 支持 | 将 `{{<gallery .../>}}{{<load-photoswipe>}}` 转成图片 wikilinks 列表 |
|
||||||
| 预处理 | 在 Markdown 渲染前执行自定义语法转 HTML |
|
| 预处理 | 在 Markdown 渲染前执行自定义语法转 HTML |
|
||||||
| 易扩展 | 提供独立函数/接口减少耦合,如 `selectGalleryImages`、`extractWeChatMeta` |
|
| 易扩展 | 提供独立函数/接口减少耦合,如 `selectGalleryImages`、`extractWeChatMeta` |
|
||||||
|
| 默认封面配置 | 无任何图片候选时使用 `defaultCoverPic` (可配置) |
|
||||||
|
| 前置回退解析 | 若 metadataCache 缺失 frontmatter,启用手动行级解析回退 |
|
||||||
|
| Gallery 块扩展 | 支持 `{{<gallery>}}` 块 + 内部 `figure src|link=` 解析 |
|
||||||
|
| 行级语法扩展 | `[fig .../]` 与 `||r`/`||g`/`||b`/`||y`/`||` 统一由 `applyCustomInlineBlocks` 处理 |
|
||||||
|
| 调试日志节流 | 输出当前文件路径与默认封面选用日志,3 秒内同路径不重复 |
|
||||||
|
|
||||||
## 3. 术语与定义
|
## 3. 术语与定义
|
||||||
- **Wikilink 图片语法**:`![[xxx.png]]`
|
- **Wikilink 图片语法**:`![[xxx.png]]`
|
||||||
@@ -39,7 +44,9 @@ Raw Markdown
|
|||||||
↓ extractWeChatMeta (保留 frontmatter 内容供分析)
|
↓ extractWeChatMeta (保留 frontmatter 内容供分析)
|
||||||
↓ 去 frontmatter
|
↓ 去 frontmatter
|
||||||
↓ transformGalleryShortcodes (gallery → ![[...]] 列表)
|
↓ transformGalleryShortcodes (gallery → ![[...]] 列表)
|
||||||
|
↓ transformGalleryBlock (gallery 块/figure → ![[...]] 列表)
|
||||||
↓ marked.parse() (图片扩展 -> LocalImage token)
|
↓ marked.parse() (图片扩展 -> LocalImage token)
|
||||||
|
↓ applyCustomInlineBlocks (fig/彩色段落 轻语法 HTML 化)
|
||||||
↓ 生成 HTML + 样式注入
|
↓ 生成 HTML + 样式注入
|
||||||
↓ setArticle()
|
↓ setArticle()
|
||||||
↓ getArticleContent() -> preprocessContent(line regex 替换) -> 最终 HTML
|
↓ getArticleContent() -> preprocessContent(line regex 替换) -> 最终 HTML
|
||||||
@@ -76,6 +83,8 @@ Raw Markdown
|
|||||||
- 回退封面:同时匹配 wikilink + markdown 图片,比较 index 取出现最早的一种。
|
- 回退封面:同时匹配 wikilink + markdown 图片,比较 index 取出现最早的一种。
|
||||||
- 返回:`{ title, author, coverLink, rawImage }`。
|
- 返回:`{ title, author, coverLink, rawImage }`。
|
||||||
- 与 `getMetadata()` 融合以补齐空缺字段。
|
- 与 `getMetadata()` 融合以补齐空缺字段。
|
||||||
|
- 若 Obsidian `metadataCache` 返回为空或缺失字段,触发手动 fallback:扫描首段 frontmatter 行(不依赖外部 YAML 包),支持 `key: value` 单行形式;空字符串的 cover/image 会被视为未提供。
|
||||||
|
- 追加默认封面逻辑:封面候选链(frontmatter cover > 正文首本地图/本地 wikilink/markdown > gallery 生成图 > defaultCoverPic)。
|
||||||
|
|
||||||
### 7.3 前置处理(`preprocessContent`)
|
### 7.3 前置处理(`preprocessContent`)
|
||||||
- `[fig .../]` → `<span>`(题注样式)。
|
- `[fig .../]` → `<span>`(题注样式)。
|
||||||
@@ -106,6 +115,44 @@ Raw Markdown
|
|||||||
- 若块内未匹配到任何 figure,保留原文本。
|
- 若块内未匹配到任何 figure,保留原文本。
|
||||||
- 正则:`/{{<gallery>}}([\s\S]*?){{<\/gallery>}}/g` 与内部 `figureRegex = /{{<figure\s+src="([^"]+)"[^>]*>}}/g`。
|
- 正则:`/{{<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. 正则清单
|
## 8. 正则清单
|
||||||
| 场景 | 正则 | 说明 |
|
| 场景 | 正则 | 说明 |
|
||||||
@@ -114,6 +161,9 @@ Raw Markdown
|
|||||||
| Wikilink 图片 | `!\[\[(.+?)\]\]` | 非贪婪 |
|
| Wikilink 图片 | `!\[\[(.+?)\]\]` | 非贪婪 |
|
||||||
| Markdown 图片 | `!\[[^\]]*\]\(([^\n\r\)]+)\)` | 不跨行 |
|
| Markdown 图片 | `!\[[^\]]*\]\(([^\n\r\)]+)\)` | 不跨行 |
|
||||||
| Gallery | `{{<gallery\s+dir=\"([^\"]+)\"(?:\s+figcaption=\"([^\"]*)\")?\s*\/>}}{{<load-photoswipe>}}` | 捕获 dir/caption |
|
| 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([^>]*?)\/]` | 题注 |
|
| fig | `\[fig([^>]*?)\/]` | 题注 |
|
||||||
| 行块 | `\|\|r (.*)` 等 | 行级匹配 |
|
| 行块 | `\|\|r (.*)` 等 | 行级匹配 |
|
||||||
|
|
||||||
@@ -134,8 +184,11 @@ Raw Markdown
|
|||||||
## 11. 配置 & 常量
|
## 11. 配置 & 常量
|
||||||
| 常量 | 说明 | 后续计划 |
|
| 常量 | 说明 | 后续计划 |
|
||||||
|------|------|----------|
|
|------|------|----------|
|
||||||
| `GALLERY_PRE_PATH` | 画廊根目录 | 移入设置面板 |
|
| `galleryPrePath` | 画廊根目录(配置项) | 未来参数化 per-block 覆盖 |
|
||||||
| `GALLERY_NUM_PIC` | 默认选图数量 | 支持短代码参数覆盖 |
|
| `galleryNumPic` | 默认选图数量(配置项) | 支持块/短代码 count 覆盖 |
|
||||||
|
| `defaultCoverPic` | 默认封面备用 | 校验存在 / 多备选随机 |
|
||||||
|
| (移除)GALLERY_PRE_PATH | (已外化) | - |
|
||||||
|
| (移除)GALLERY_NUM_PIC | (已外化) | - |
|
||||||
| 行级样式内联 | 直接 embed style | 可改 class + CSS |
|
| 行级样式内联 | 直接 embed style | 可改 class + CSS |
|
||||||
|
|
||||||
## 12. 对外接口
|
## 12. 对外接口
|
||||||
@@ -167,6 +220,11 @@ Raw Markdown
|
|||||||
| 封面策略 | 配置“frontmatter 优先 / 正文优先 / 首图随机” |
|
| 封面策略 | 配置“frontmatter 优先 / 正文优先 / 首图随机” |
|
||||||
| 图廊 HTML 模式 | 直接生成 `<figure>` 集合而非 wikilink 列表 |
|
| 图廊 HTML 模式 | 直接生成 `<figure>` 集合而非 wikilink 列表 |
|
||||||
| 样式外置 | 行级块样式改为统一 CSS class |
|
| 样式外置 | 行级块样式改为统一 CSS class |
|
||||||
|
| 默认封面池 | 支持数组随机选择 default cover |
|
||||||
|
| 默认封面校验 | 选择时校验存在性 + Notice 提示 |
|
||||||
|
| caption alias | gallery figure caption -> wikilink alias/figcaption |
|
||||||
|
| link wrap | figure link 生成 `<a>` 包裹图片 |
|
||||||
|
| debug 开关 | 设置中关闭全部调试日志 |
|
||||||
| 目录缓存 | 减少频繁 IO |
|
| 目录缓存 | 减少频繁 IO |
|
||||||
|
|
||||||
## 15. 风险与规避
|
## 15. 风险与规避
|
||||||
|
|||||||
55
diagrams.md
55
diagrams.md
@@ -19,6 +19,8 @@ classDiagram
|
|||||||
+postArticle(appid,cover?)
|
+postArticle(appid,cover?)
|
||||||
+postImages(appid)
|
+postImages(appid)
|
||||||
+exportHTML()
|
+exportHTML()
|
||||||
|
+transformGalleryBlock()
|
||||||
|
+applyCustomInlineBlocks()
|
||||||
}
|
}
|
||||||
class LocalImageManager {
|
class LocalImageManager {
|
||||||
+setImage(path,info)
|
+setImage(path,info)
|
||||||
@@ -38,6 +40,9 @@ classDiagram
|
|||||||
+wxInfo
|
+wxInfo
|
||||||
+authKey
|
+authKey
|
||||||
+enableMarkdownImageToWikilink
|
+enableMarkdownImageToWikilink
|
||||||
|
+galleryPrePath
|
||||||
|
+galleryNumPic
|
||||||
|
+defaultCoverPic
|
||||||
}
|
}
|
||||||
class WeChatAPI {
|
class WeChatAPI {
|
||||||
+wxGetToken()
|
+wxGetToken()
|
||||||
@@ -100,20 +105,38 @@ graph TD
|
|||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
A[Need Cover?] -->|No| Z[Skip]
|
A[Need Cover?] -->|No| Z[Skip]
|
||||||
A -->|Yes| B[Strip Frontmatter]
|
A -->|Yes| B[Frontmatter cover?]
|
||||||
B --> C[Scan Markdown Images]
|
B -- Yes --> H[Use frontmatter]
|
||||||
B --> D[Scan Wikilink Images]
|
B -- No --> C[Strip Frontmatter]
|
||||||
C --> E[Merge Candidates]
|
C --> D[Scan Markdown Images]
|
||||||
D --> E[Merge Candidates]
|
C --> E[Scan Wikilink Images]
|
||||||
E --> F{Any Candidate?}
|
D --> F[Collect Candidates]
|
||||||
F -- No --> Z[Cover stays empty]
|
E --> F[Collect Candidates]
|
||||||
F -- Yes --> G[Sort by index]
|
F --> G{Any Body Image?}
|
||||||
G --> H[Select first -> cover wikilink]
|
G -- Yes --> H[Use first body image]
|
||||||
|
G -- No --> I[Gallery Expanded?]
|
||||||
|
I -- Yes --> H[Use first gallery image]
|
||||||
|
I -- No --> J[defaultCoverPic Config?]
|
||||||
|
J -- Yes --> H[Use defaultCoverPic]
|
||||||
|
J -- No --> Z[Cover stays empty]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4.1 行级轻语法与日志节流 (补充)
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
M[Markdown Raw] --> P[Preprocess Gallery Shortcode]
|
||||||
|
P --> GB[Gallery Block Parse]
|
||||||
|
GB --> MD[Marked Parse]
|
||||||
|
MD --> IB[applyCustomInlineBlocks]
|
||||||
|
IB --> R[Render HTML]
|
||||||
|
R --> L{Log Throttle}
|
||||||
|
L --> R1[Path Log]
|
||||||
|
L --> R2[Cover Fallback Log]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. 未来 RenderService Pipeline 图
|
## 5. 未来 RenderService Pipeline 图
|
||||||
```mermaid
|
```mermaid
|
||||||
graph LR
|
graph TD
|
||||||
L[Loader] --> FM[Frontmatter]
|
L[Loader] --> FM[Frontmatter]
|
||||||
FM --> PP[Preprocessors]
|
FM --> PP[Preprocessors]
|
||||||
PP --> P[Parser]
|
PP --> P[Parser]
|
||||||
@@ -127,14 +150,14 @@ graph LR
|
|||||||
## 6. 并发上传示意 (未来优化)
|
## 6. 并发上传示意 (未来优化)
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
A[images[]] --> B[Partition into tasks]
|
A[Images] --> B[Partition]
|
||||||
B --> C[Promise Pool (N=4)]
|
B --> C[Pool]
|
||||||
C --> D[Upload Task]
|
C --> D[Upload]
|
||||||
D --> E{Success?}
|
D --> E{Success?}
|
||||||
E -- No --> R[Retry with backoff]
|
E -->|No| R[Retry]
|
||||||
E -- Yes --> F[Collect media_id]
|
E -->|Yes| F[Collect ids]
|
||||||
R --> C
|
R --> C
|
||||||
F --> G[All Done]
|
F --> G[Done]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. 状态机概览 (发布按钮)
|
## 7. 状态机概览 (发布按钮)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
- 保证上传后 HTML 使用微信可访问的正式 URL。
|
- 保证上传后 HTML 使用微信可访问的正式 URL。
|
||||||
- 自动封面选取:未指定时从正文首图推断。
|
- 自动封面选取:未指定时从正文首图推断。
|
||||||
- WebP → JPG 转换保障兼容性。
|
- WebP → JPG 转换保障兼容性。
|
||||||
|
- 默认封面配置:若无正文/画廊候选,使用 `defaultCoverPic` (settings)。
|
||||||
|
- 手动 frontmatter 回退:metadata 缺失时行级解析 `title/author/image`。
|
||||||
|
|
||||||
## 2. 图片来源与归一化
|
## 2. 图片来源与归一化
|
||||||
| 来源 | 输入示例 | 归一化策略 | 备注 |
|
| 来源 | 输入示例 | 归一化策略 | 备注 |
|
||||||
@@ -36,6 +38,9 @@ interface ImageInfo {
|
|||||||
2. 根据 wikilink / 尺寸参数解析出真实 vault 文件路径:`assetsManager.getResourcePath()`。
|
2. 根据 wikilink / 尺寸参数解析出真实 vault 文件路径:`assetsManager.getResourcePath()`。
|
||||||
3. 调用 `LocalImageManager.setImage(res.resUrl, info)` 建档。
|
3. 调用 `LocalImageManager.setImage(res.resUrl, info)` 建档。
|
||||||
4. Markdown 图片(标准语法)若转换启用,提前转成 wikilink 进入同一逻辑;否则需未来扩展 tokenizer 直接登记。
|
4. Markdown 图片(标准语法)若转换启用,提前转成 wikilink 进入同一逻辑;否则需未来扩展 tokenizer 直接登记。
|
||||||
|
5. Gallery:
|
||||||
|
- 短代码:`{{<gallery dir="..."/>}}{{<load-photoswipe>}}` → 目录枚举(受 `galleryPrePath` + `galleryNumPic` 影响)→ 多行 `![[...]]` 注入。
|
||||||
|
- 块级:`{{<gallery>}} ... {{</gallery>}}` 内 `figure src="..." link="..." caption="..." >` 解析 src basename 加入候选;`link` 预留后续包装;`caption` 未来映射题注。
|
||||||
|
|
||||||
## 5. 上传阶段
|
## 5. 上传阶段
|
||||||
顺序:
|
顺序:
|
||||||
@@ -68,22 +73,21 @@ Base64:
|
|||||||
- 遍历 DOM `<img>` → 查询 Map → 使用上传后 `url` 覆盖 `src`。
|
- 遍历 DOM `<img>` → 查询 Map → 使用上传后 `url` 覆盖 `src`。
|
||||||
- 未登记:输出警告(潜在渲染管线遗漏)。
|
- 未登记:输出警告(潜在渲染管线遗漏)。
|
||||||
|
|
||||||
## 6. 自动封面策略
|
## 6. 自动封面策略(更新)
|
||||||
触发点:`getMetadata()` 中,若 frontmatter 未给出封面且无 `thumb_media_id`。步骤:
|
触发点:`getMetadata()`;若存在 `thumb_media_id` 则跳过。决策链:
|
||||||
1. 使用 `originalMarkdown`(含 frontmatter 原始文本拷贝)。
|
1. frontmatter cover/image(非空);若 metadataCache 缺失,则手动行级解析首个 `---` 块。
|
||||||
2. 去除 frontmatter 块。
|
2. 正文本地图候选(markdown + wikilink)。
|
||||||
3. 分别用正则匹配:
|
3. gallery 展开产生的首图(短代码或块级 figure 列表)。
|
||||||
- Markdown:`/!\[[^\]]*\]\(([^)\s]+)\)/g`
|
4. `defaultCoverPic`(settings 配置,允许相对路径 / URL / wikilink)。
|
||||||
- Wikilink:`/!\[\[(.+?)\]\]/g`
|
5. 否则封面为空。
|
||||||
4. 过滤远程 URL(仅保留本地引用或无协议路径)。
|
|
||||||
5. 生成候选 `{ idx, basename }` 列表,按出现位置排序。
|
|
||||||
6. 第一项 → `![[basename]]` 写入 `res.cover`。
|
|
||||||
|
|
||||||
Edge Cases:
|
Edge Cases:
|
||||||
| 情况 | 处理 |
|
| 情况 | 处理 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 所有图片都是 http(s) | 不作为封面候选 |
|
| frontmatter cover: "" | 视为未配置,继续回退 |
|
||||||
| 文件名带 query/hash | 使用前切分去除 `?` `#` |
|
| 所有正文图片都是 http(s) | 跳过正文阶段,转 gallery -> defaultCoverPic |
|
||||||
|
| gallery 块无有效 figure | 不贡献候选 |
|
||||||
|
| defaultCoverPic 不存在 | 仍返回该引用;上传阶段可能失败(待校验增强) |
|
||||||
| markdown 图片后缀非图片 | 跳过 |
|
| markdown 图片后缀非图片 | 跳过 |
|
||||||
| 重复图片 | 以最先出现的为准 |
|
| 重复图片 | 以最先出现的为准 |
|
||||||
|
|
||||||
@@ -93,6 +97,9 @@ Edge Cases:
|
|||||||
| Frontmatter 删除 | `/^(---)$.+?^(---)$.+?/ims` | 首段 YAML |
|
| Frontmatter 删除 | `/^(---)$.+?^(---)$.+?/ims` | 首段 YAML |
|
||||||
| Markdown 图片 | `/!\[[^\]]*\]\(([^)\s]+)\)/g` | 捕获路径 group1 |
|
| Markdown 图片 | `/!\[[^\]]*\]\(([^)\s]+)\)/g` | 捕获路径 group1 |
|
||||||
| Wikilink 图片 | `/!\[\[(.+?)\]\]/g` | 捕获内部资源 |
|
| Wikilink 图片 | `/!\[\[(.+?)\]\]/g` | 捕获内部资源 |
|
||||||
|
| Gallery 块 | `{{<gallery>}}([\s\S]*?){{<\/gallery>}}` | 包裹内容 |
|
||||||
|
| Gallery figure | `{{<figure\s+src=\"([^\"]+)\"[^>]*>}}` | 提取 src |
|
||||||
|
| figure link | `link=\"([^\"]+)\"` | 可选外链属性 |
|
||||||
| WebP 检测 | `/\.webp$/i` | 扩展判断 |
|
| WebP 检测 | `/\.webp$/i` | 扩展判断 |
|
||||||
| Base64 前缀 | `^data:image/` | 判断内嵌图 |
|
| Base64 前缀 | `^data:image/` | 判断内嵌图 |
|
||||||
|
|
||||||
@@ -130,13 +137,18 @@ Edge Cases:
|
|||||||
| 失效重传 | 保存上传映射 + 校验缺失再补传 |
|
| 失效重传 | 保存上传映射 + 校验缺失再补传 |
|
||||||
| 分块上传 | 大图分片(若接口支持) |
|
| 分块上传 | 大图分片(若接口支持) |
|
||||||
| 图片校验 | MD5 去重避免重复上传 |
|
| 图片校验 | MD5 去重避免重复上传 |
|
||||||
|
| defaultCoverPic 校验 | 渲染时验证存在性 + Notice |
|
||||||
|
| 多默认封面池 | 数组随机选择减少视觉重复 |
|
||||||
|
| caption alias | figure caption -> wikilink alias/figcaption |
|
||||||
|
| link 包裹 | link 属性生成 `<a>` 包裹 `<img>` |
|
||||||
|
|
||||||
## 12. 快速检查清单 (Debug Checklist)
|
## 12. 快速检查清单 (Debug Checklist)
|
||||||
- 图片是否在 `LocalImageManager.images` Map 中?
|
- 图片是否在 `LocalImageManager.images` Map 中?
|
||||||
- 是否执行了 `cachedElementsToImages()`(Mermaid/Excalidraw)?
|
- 是否执行了 `cachedElementsToImages()`(Mermaid/Excalidraw)?
|
||||||
- 上传后 `media_id` 是否为空?(格式/大小不合规)
|
- 上传后 `media_id` 是否为空?(格式/大小不合规)
|
||||||
- DOM 替换后 `<img src>` 是否为微信域?
|
- DOM 替换后 `<img src>` 是否为微信域?
|
||||||
- 自动封面未生效?检查:frontmatter cover / 有无本地首图 / 正则是否截获远程 URL。
|
- 自动封面未生效?检查:frontmatter cover / 正文本地图是否存在 / gallery 是否展开 / defaultCoverPic 是否配置。
|
||||||
|
- 重复封面日志?确认日志节流是否失效(3 秒窗口内只应出现一次)。
|
||||||
|
|
||||||
## 13. 示例
|
## 13. 示例
|
||||||
输入 Markdown:
|
输入 Markdown:
|
||||||
|
|||||||
@@ -34,10 +34,78 @@ import { CardDataManager } from './markdown/code';
|
|||||||
import { debounce } from './utils';
|
import { debounce } from './utils';
|
||||||
import { PrepareImageLib, IsImageLibReady, WebpToJPG } from './imagelib';
|
import { PrepareImageLib, IsImageLibReady, WebpToJPG } from './imagelib';
|
||||||
import { toPng } from 'html-to-image';
|
import { toPng } from 'html-to-image';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { stat, readdir } from 'fs/promises';
|
||||||
|
|
||||||
|
|
||||||
const FRONT_MATTER_REGEX = /^(---)$.+?^(---)$.+?/ims;
|
const FRONT_MATTER_REGEX = /^(---)$.+?^(---)$.+?/ims;
|
||||||
|
|
||||||
|
// gallery 配置迁移到 NMPSettings(galleryPrePath, galleryNumPic)
|
||||||
|
// 匹配示例:{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}
|
||||||
|
// figcaption 可选
|
||||||
|
const GALLERY_SHORTCODE_REGEX = /{{<gallery\s+dir="([^"]+)"(?:\s+figcaption="([^"]*)")?\s*\/?>}}\s*{{<load-photoswipe>}}/g;
|
||||||
|
// 块级 gallery:
|
||||||
|
// {{<gallery>}}\n{{<figure src="/img/a.png" caption=".." >}}\n...\n{{</gallery>}}
|
||||||
|
// 需要提取所有 figure 的 src basename 生成多行 wikilink
|
||||||
|
const GALLERY_BLOCK_REGEX = /{{<gallery>}}([\s\S]*?){{<\/gallery>}}/g;
|
||||||
|
// figure 支持 src 或 link 属性,两者取其一
|
||||||
|
const FIGURE_IN_GALLERY_REGEX = /{{<figure\s+(?:src|link)="([^"]+)"[^>]*>}}/g;
|
||||||
|
|
||||||
|
async function listLocalImages(dirAbs: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const stats = await stat(dirAbs);
|
||||||
|
if (!stats.isDirectory()) return [];
|
||||||
|
} catch { return []; }
|
||||||
|
try {
|
||||||
|
const files = await readdir(dirAbs);
|
||||||
|
return files.filter(f => /(png|jpe?g|gif|bmp|webp|svg)$/i.test(f)).sort();
|
||||||
|
} catch { return []; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickImages(all: string[], limit: number): string[] {
|
||||||
|
if (all.length <= limit) return all;
|
||||||
|
// 简单:前 n;可扩展为随机
|
||||||
|
return all.slice(0, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function transformGalleryShortcodes(md: string, prePath: string, numPic: number): Promise<string> {
|
||||||
|
// 逐个替换(异步)—— 使用 replace + 手动遍历实现
|
||||||
|
const matches: { full: string; dir: string; caption?: string }[] = [];
|
||||||
|
md.replace(GALLERY_SHORTCODE_REGEX, (full, dir, caption) => {
|
||||||
|
matches.push({ full, dir, caption });
|
||||||
|
return full;
|
||||||
|
});
|
||||||
|
let result = md;
|
||||||
|
for (const m of matches) {
|
||||||
|
const absDir = path.join(prePath, m.dir.replace(/^\//, '')); // 拼接绝对路径
|
||||||
|
const imgs = await listLocalImages(absDir);
|
||||||
|
const picked = pickImages(imgs, numPic);
|
||||||
|
if (picked.length === 0) {
|
||||||
|
// 无图则清空短代码(或保留原样,这里按需求替换为空)
|
||||||
|
result = result.replace(m.full, '');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const repl = picked.map(f => `![[${f}]]`).join('\n');
|
||||||
|
result = result.replace(m.full, repl);
|
||||||
|
}
|
||||||
|
// 处理无 dir 的块级 gallery(只做本地名提取,不访问文件系统)
|
||||||
|
result = result.replace(GALLERY_BLOCK_REGEX, (full: string, inner: string) => {
|
||||||
|
const names: string[] = [];
|
||||||
|
inner.replace(FIGURE_IN_GALLERY_REGEX, (_f: string, src: string) => {
|
||||||
|
if (!src) return _f;
|
||||||
|
const clean = src.split('#')[0].split('?')[0];
|
||||||
|
const base = clean.split('/').pop();
|
||||||
|
if (base && /(png|jpe?g|gif|bmp|webp|svg)$/i.test(base)) {
|
||||||
|
names.push(base);
|
||||||
|
}
|
||||||
|
return _f;
|
||||||
|
});
|
||||||
|
if (names.length === 0) return '';
|
||||||
|
return names.map(n => `![[${n}]]`).join('\n');
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export class ArticleRender implements MDRendererCallback {
|
export class ArticleRender implements MDRendererCallback {
|
||||||
app: App;
|
app: App;
|
||||||
itemView: ItemView;
|
itemView: ItemView;
|
||||||
@@ -168,6 +236,12 @@ export class ArticleRender implements MDRendererCallback {
|
|||||||
md = md.replace(FRONT_MATTER_REGEX, '');
|
md = md.replace(FRONT_MATTER_REGEX, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 gallery 短代码 -> wikilink 图片列表
|
||||||
|
md = await transformGalleryShortcodes(md, this.settings.galleryPrePath, this.settings.galleryNumPic);
|
||||||
|
|
||||||
|
// 自定义行级语法转换: [fig .../] 以及 || 前缀块
|
||||||
|
md = this.applyCustomInlineBlocks(md);
|
||||||
|
|
||||||
// 将标准 markdown 图片语法转为 wikilink 语法,便于现有 LocalImageManager 识别
|
// 将标准 markdown 图片语法转为 wikilink 语法,便于现有 LocalImageManager 识别
|
||||||
if (this.settings.enableMarkdownImageToWikilink) {
|
if (this.settings.enableMarkdownImageToWikilink) {
|
||||||
// 匹配 ;不跨行;忽略包含空格的 URL 末尾注释
|
// 匹配 ;不跨行;忽略包含空格的 URL 末尾注释
|
||||||
@@ -195,6 +269,35 @@ export class ArticleRender implements MDRendererCallback {
|
|||||||
this.setArticle(this.errorContent(e));
|
this.setArticle(this.errorContent(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 自定义 fig 与 || 语法转换
|
||||||
|
private applyCustomInlineBlocks(md: string): string {
|
||||||
|
const figPattern = /\[fig([^\n\]]*?)\/\]/g; // [fig text/]
|
||||||
|
md = md.replace(figPattern, (_m, inner) => {
|
||||||
|
const content = inner.trim();
|
||||||
|
return `<span style="font-style: italic; font-size: 14px; background-color: #f5f5f5; padding: 2px;">${content}</span>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 颜色映射:默认|| 与 ||g, ||r, ||b, ||y
|
||||||
|
const blockStyles: Record<string, string> = {
|
||||||
|
'': "background-color:#E5E4E2;",
|
||||||
|
'r': "color:white;background-color:#6F4E37;",
|
||||||
|
'g': "background-color:#BCE954;",
|
||||||
|
'b': "background-color:#B6B6B4;",
|
||||||
|
'y': "background-color:#FFFFC2;",
|
||||||
|
};
|
||||||
|
const baseStyle = "font-family:'Microsoft YaHei',sans-serif;font-size:14px; padding:10px;border-radius:20px;line-height:30px;";
|
||||||
|
// 仅匹配行首 ||[flag]? 空格 之后的单行内容,避免吞并后续多行
|
||||||
|
md = md.split(/\n/).map(line => {
|
||||||
|
// 例如 || 内容, ||r 内容
|
||||||
|
const m = line.match(/^\|\|(r|g|b|y)?\s+(.*)$/);
|
||||||
|
if (!m) return line;
|
||||||
|
const flag = m[1] || '';
|
||||||
|
const text = m[2];
|
||||||
|
const style = baseStyle + (blockStyles[flag] || blockStyles['']);
|
||||||
|
return `<p style="${style}">${text}</p>`;
|
||||||
|
}).join('\n');
|
||||||
|
return md;
|
||||||
|
}
|
||||||
getCSS() {
|
getCSS() {
|
||||||
try {
|
try {
|
||||||
const theme = this.assetsManager.getTheme(this.currentTheme);
|
const theme = this.assetsManager.getTheme(this.currentTheme);
|
||||||
@@ -248,6 +351,17 @@ export class ArticleRender implements MDRendererCallback {
|
|||||||
}
|
}
|
||||||
const file = this.app.workspace.getActiveFile();
|
const file = this.app.workspace.getActiveFile();
|
||||||
if (!file) return res;
|
if (!file) return res;
|
||||||
|
// 避免频繁刷屏:仅当路径变化或超过 3s 再输出一次
|
||||||
|
try {
|
||||||
|
const globalAny = window as any;
|
||||||
|
const now = Date.now();
|
||||||
|
if (!globalAny.__note2mp_lastPathLog ||
|
||||||
|
globalAny.__note2mp_lastPathLog.path !== file.path ||
|
||||||
|
now - globalAny.__note2mp_lastPathLog.time > 3000) {
|
||||||
|
console.log('[note2mp] active file path:', file.path);
|
||||||
|
globalAny.__note2mp_lastPathLog = { path: file.path, time: now };
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
const metadata = this.app.metadataCache.getFileCache(file);
|
const metadata = this.app.metadataCache.getFileCache(file);
|
||||||
if (metadata?.frontmatter) {
|
if (metadata?.frontmatter) {
|
||||||
const keys = this.assetsManager.expertSettings.frontmatter;
|
const keys = this.assetsManager.expertSettings.frontmatter;
|
||||||
@@ -260,6 +374,10 @@ export class ArticleRender implements MDRendererCallback {
|
|||||||
res.digest = this.getFrontmatterValue(frontmatter, keys.digest);
|
res.digest = this.getFrontmatterValue(frontmatter, keys.digest);
|
||||||
res.content_source_url = this.getFrontmatterValue(frontmatter, keys.content_source_url);
|
res.content_source_url = this.getFrontmatterValue(frontmatter, keys.content_source_url);
|
||||||
res.cover = this.getFrontmatterValue(frontmatter, keys.cover) || frontmatter['cover'] || frontmatter['image'];
|
res.cover = this.getFrontmatterValue(frontmatter, keys.cover) || frontmatter['cover'] || frontmatter['image'];
|
||||||
|
// frontmatter 给出的 cover/image 为空字符串时视为未设置
|
||||||
|
if (typeof res.cover === 'string' && res.cover.trim() === '') {
|
||||||
|
res.cover = undefined;
|
||||||
|
}
|
||||||
res.thumb_media_id = this.getFrontmatterValue(frontmatter, keys.thumb_media_id);
|
res.thumb_media_id = this.getFrontmatterValue(frontmatter, keys.thumb_media_id);
|
||||||
res.need_open_comment = frontmatter[keys.need_open_comment] ? 1 : undefined;
|
res.need_open_comment = frontmatter[keys.need_open_comment] ? 1 : undefined;
|
||||||
res.only_fans_can_comment = frontmatter[keys.only_fans_can_comment] ? 1 : undefined;
|
res.only_fans_can_comment = frontmatter[keys.only_fans_can_comment] ? 1 : undefined;
|
||||||
@@ -274,6 +392,33 @@ export class ArticleRender implements MDRendererCallback {
|
|||||||
res.pic_crop_1_1 = '0_0.525_0.404_1';
|
res.pic_crop_1_1 = '0_0.525_0.404_1';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (this.originalMarkdown?.startsWith('---')) {
|
||||||
|
// 元数据缓存未命中时的手动轻量解析(不引入 yaml 依赖,逐行扫描直到第二个 ---)
|
||||||
|
try {
|
||||||
|
const lines = this.originalMarkdown.split(/\r?\n/);
|
||||||
|
if (lines[0].trim() === '---') {
|
||||||
|
let i = 1;
|
||||||
|
for (; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
if (line.trim() === '---') break;
|
||||||
|
const m = line.match(/^(\w[\w-]*):\s*(.*)$/); // key: value 形式
|
||||||
|
if (!m) continue;
|
||||||
|
const k = m[1].toLowerCase();
|
||||||
|
const v = m[2].trim();
|
||||||
|
if (k === 'title' && !res.title) res.title = v;
|
||||||
|
if (k === 'author' && !res.author) res.author = v;
|
||||||
|
if ((k === 'image' || k === 'cover') && !res.cover && v) {
|
||||||
|
// image 可能是路径,取 basename
|
||||||
|
const clean = v.replace(/^"|"$/g, '').split('#')[0].split('?')[0];
|
||||||
|
const base = clean.split('/').pop();
|
||||||
|
if (base) res.cover = `![[${base}]]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('fallback frontmatter parse failed', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果未显式指定封面,尝试从正文首图( markdown 或 wikilink ) 提取,按出现顺序优先
|
// 如果未显式指定封面,尝试从正文首图( markdown 或 wikilink ) 提取,按出现顺序优先
|
||||||
if (!res.cover && this.originalMarkdown) {
|
if (!res.cover && this.originalMarkdown) {
|
||||||
@@ -307,6 +452,20 @@ export class ArticleRender implements MDRendererCallback {
|
|||||||
if (candidates.length > 0) {
|
if (candidates.length > 0) {
|
||||||
candidates.sort((a,b)=> a.idx - b.idx);
|
candidates.sort((a,b)=> a.idx - b.idx);
|
||||||
res.cover = `![[${candidates[0].basename}]]`;
|
res.cover = `![[${candidates[0].basename}]]`;
|
||||||
|
} else if (!res.cover) {
|
||||||
|
// 没有找到任何图片候选,应用默认封面(如果配置了)
|
||||||
|
const def = this.settings.defaultCoverPic?.trim();
|
||||||
|
if (def) {
|
||||||
|
if (/^!\[\[.*\]\]$/.test(def) || /^https?:\/\//i.test(def)) {
|
||||||
|
res.cover = def;
|
||||||
|
} else {
|
||||||
|
const base = def.split('/').pop();
|
||||||
|
if (base) res.cover = `![[${base}]]`;
|
||||||
|
}
|
||||||
|
if (res.cover) {
|
||||||
|
try { console.log('[note2mp] use default cover:', def, '->', res.cover); } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -270,6 +270,48 @@ export class NoteToMpSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Gallery 根路径')
|
||||||
|
.setDesc('用于 {{<gallery dir="..."/>}} 短代码解析;需指向本地图片根目录')
|
||||||
|
.addText(text => {
|
||||||
|
text.setPlaceholder('例如 /Users/xxx/site/static 或 相对路径')
|
||||||
|
.setValue(this.settings.galleryPrePath || '')
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.settings.galleryPrePath = value.trim();
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
text.inputEl.setAttr('style', 'width: 360px;');
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Gallery 选取图片数')
|
||||||
|
.setDesc('每个 gallery 短代码最多替换为前 N 张图片')
|
||||||
|
.addText(text => {
|
||||||
|
text.setPlaceholder('数字 >=1')
|
||||||
|
.setValue(String(this.settings.galleryNumPic || 2))
|
||||||
|
.onChange(async (value) => {
|
||||||
|
const n = parseInt(value, 10);
|
||||||
|
if (Number.isFinite(n) && n >= 1) {
|
||||||
|
this.settings.galleryNumPic = n;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
text.inputEl.setAttr('style', 'width: 120px;');
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('默认封面图片')
|
||||||
|
.setDesc('当文章无任何图片/短代码时使用;可填 wikilink 文件名或 http(s) URL')
|
||||||
|
.addText(text => {
|
||||||
|
text.setPlaceholder('例如 cover.png 或 https://...')
|
||||||
|
.setValue(this.settings.defaultCoverPic || '')
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.settings.defaultCoverPic = value.trim();
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
text.inputEl.setAttr('style', 'width: 360px;');
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('启用空行渲染')
|
.setName('启用空行渲染')
|
||||||
.addToggle(toggle => {
|
.addToggle(toggle => {
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ export class NMPSettings {
|
|||||||
isLoaded: boolean = false;
|
isLoaded: boolean = false;
|
||||||
enableEmptyLine: boolean = false;
|
enableEmptyLine: boolean = false;
|
||||||
enableMarkdownImageToWikilink: boolean = true; // 自动将  转为 ![[file.ext]]
|
enableMarkdownImageToWikilink: boolean = true; // 自动将  转为 ![[file.ext]]
|
||||||
|
// gallery 相关配置:根目录前缀 & 选取图片数量
|
||||||
|
galleryPrePath: string;
|
||||||
|
galleryNumPic: number;
|
||||||
|
// 无图片时的默认封面(wikilink 或 URL 均可)
|
||||||
|
defaultCoverPic: string;
|
||||||
|
|
||||||
private static instance: NMPSettings;
|
private static instance: NMPSettings;
|
||||||
|
|
||||||
@@ -74,6 +79,11 @@ export class NMPSettings {
|
|||||||
this.expertSettingsNote = '';
|
this.expertSettingsNote = '';
|
||||||
this.enableEmptyLine = false;
|
this.enableEmptyLine = false;
|
||||||
this.enableMarkdownImageToWikilink = true;
|
this.enableMarkdownImageToWikilink = true;
|
||||||
|
// 默认值:用户原先硬编码路径 & 前 2 张
|
||||||
|
this.galleryPrePath = '/Users/gavin/myweb/static';
|
||||||
|
this.galleryNumPic = 2;
|
||||||
|
// 默认封面:使用当前笔记同目录下的 cover.png (若存在会被后续流程正常解析;不存在则无效但可被用户覆盖)
|
||||||
|
this.defaultCoverPic = 'cover.png';
|
||||||
}
|
}
|
||||||
|
|
||||||
resetStyelAndHighlight() {
|
resetStyelAndHighlight() {
|
||||||
@@ -104,6 +114,9 @@ export class NMPSettings {
|
|||||||
expertSettingsNote,
|
expertSettingsNote,
|
||||||
ignoreEmptyLine,
|
ignoreEmptyLine,
|
||||||
enableMarkdownImageToWikilink,
|
enableMarkdownImageToWikilink,
|
||||||
|
galleryPrePath,
|
||||||
|
galleryNumPic,
|
||||||
|
defaultCoverPic,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
const settings = NMPSettings.getInstance();
|
const settings = NMPSettings.getInstance();
|
||||||
@@ -161,6 +174,15 @@ export class NMPSettings {
|
|||||||
if (enableMarkdownImageToWikilink !== undefined) {
|
if (enableMarkdownImageToWikilink !== undefined) {
|
||||||
settings.enableMarkdownImageToWikilink = enableMarkdownImageToWikilink;
|
settings.enableMarkdownImageToWikilink = enableMarkdownImageToWikilink;
|
||||||
}
|
}
|
||||||
|
if (galleryPrePath) {
|
||||||
|
settings.galleryPrePath = galleryPrePath;
|
||||||
|
}
|
||||||
|
if (galleryNumPic !== undefined && Number.isFinite(galleryNumPic)) {
|
||||||
|
settings.galleryNumPic = Math.max(1, parseInt(galleryNumPic));
|
||||||
|
}
|
||||||
|
if (defaultCoverPic !== undefined) {
|
||||||
|
settings.defaultCoverPic = String(defaultCoverPic).trim();
|
||||||
|
}
|
||||||
settings.getExpiredDate();
|
settings.getExpiredDate();
|
||||||
settings.isLoaded = true;
|
settings.isLoaded = true;
|
||||||
}
|
}
|
||||||
@@ -186,6 +208,9 @@ export class NMPSettings {
|
|||||||
'expertSettingsNote': settings.expertSettingsNote,
|
'expertSettingsNote': settings.expertSettingsNote,
|
||||||
'ignoreEmptyLine': settings.enableEmptyLine,
|
'ignoreEmptyLine': settings.enableEmptyLine,
|
||||||
'enableMarkdownImageToWikilink': settings.enableMarkdownImageToWikilink,
|
'enableMarkdownImageToWikilink': settings.enableMarkdownImageToWikilink,
|
||||||
|
'galleryPrePath': settings.galleryPrePath,
|
||||||
|
'galleryNumPic': settings.galleryNumPic,
|
||||||
|
'defaultCoverPic': settings.defaultCoverPic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
todo.list
70
todo.list
@@ -10,6 +10,7 @@
|
|||||||
- 如果author不为空,公众号文章作者: 大童。
|
- 如果author不为空,公众号文章作者: 大童。
|
||||||
|
|
||||||
- , ![[name.ext]]不分优先级,看哪个在文章的最前面,取最前面这个作为封面图片
|
- , ![[name.ext]]不分优先级,看哪个在文章的最前面,取最前面这个作为封面图片
|
||||||
|
✅
|
||||||
|
|
||||||
3. 预处理markdown文件:
|
3. 预处理markdown文件:
|
||||||
对{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}或{{<gallery dir="/img/guanzhan/1"/>}}{{<load-photoswipe>}}
|
对{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}或{{<gallery dir="/img/guanzhan/1"/>}}{{<load-photoswipe>}}
|
||||||
@@ -20,8 +21,9 @@
|
|||||||
如n=2,取出的图片为xx.jpg,yy.png,那么把{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}替换为:
|
如n=2,取出的图片为xx.jpg,yy.png,那么把{{<gallery dir="/img/guanzhan/1" figcaption="毕业展"/>}}{{<load-photoswipe>}}替换为:
|
||||||
![[xx.jpg]]
|
![[xx.jpg]]
|
||||||
![[yy.png]]
|
![[yy.png]]
|
||||||
|
✅
|
||||||
|
|
||||||
2.
|
3.
|
||||||
对如下:
|
对如下:
|
||||||
{{<gallery>}}
|
{{<gallery>}}
|
||||||
{{<figure src="/img/晋中晋北行程.jpeg" caption="晋中晋北行程" >}}
|
{{<figure src="/img/晋中晋北行程.jpeg" caption="晋中晋北行程" >}}
|
||||||
@@ -33,48 +35,40 @@
|
|||||||
![[晋中晋北行程-2.jpeg]]
|
![[晋中晋北行程-2.jpeg]]
|
||||||
![[晋中晋北行程-3.jpeg]]
|
![[晋中晋北行程-3.jpeg]]
|
||||||
|
|
||||||
|
src可能使用link:
|
||||||
|
{{<gallery>}}
|
||||||
|
{{<figure link="/img/2025ZK12.jpg" caption="">}}
|
||||||
|
{{<figure link="/img/2025ZK12-2.jpg" caption="">}}
|
||||||
|
{{</gallery>}}
|
||||||
|
替换为
|
||||||
|
![[2025ZK12.jpg]]
|
||||||
|
![[2025ZK12-2.jpg]]
|
||||||
|
✅
|
||||||
|
|
||||||
2. 需求:没有成功❌❓
|
|
||||||
|
4.
|
||||||
|
参考以下代码,渲染[fig content/],|| content,||r content,||g content,||b content等标签:
|
||||||
|
`\[fig([^>]*?)/\]` `<span style="font-style: italic; font-size: 14px; background-color: #f5f5f5; padding: 2px;">$1</span>`
|
||||||
|
`\|\| (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#E5E4E2 ;padding:10px;border-radius:20px;line-height:30px;">$1</p>`
|
||||||
|
`\|\|r (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;color:white;background-color:#6F4E37;padding:10px;border-radius:20px;line-height:30px;">$1</p>`
|
||||||
|
`\|\|g (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#BCE954;padding:10px;border-radius:20px;line-height:30px;">$1</p>`
|
||||||
|
`\|\|b (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#B6B6B4;padding:10px;border-radius:20px;line-height:30px;">$1</p>`
|
||||||
|
`\|\|y (.*)` `<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#FFFFC2;padding:10px;border-radius:20px;line-height:30px;">$1</p>`
|
||||||
|
|
||||||
|
||连续多行只渲染第一行,举例:
|
||||||
|| content1
|
|| content1
|
||||||
content2
|
content2
|
||||||
content3
|
content3
|
||||||
|
渲染为:
|
||||||
修改代码,连续多行只渲染第一行,举例:
|
<p style="font-family:'Microsoft YaHei',sans-serif;font-size:14px; background-color:#FFFFC2;padding:10px;border-radius:20px;line-height:30px;">content1</p>
|
||||||
<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#FFFFC2;padding:20px;border-radius:20px;line-height:35px;">content1</p>
|
content2
|
||||||
|
content3
|
||||||
而不是:
|
而不是:
|
||||||
<p style="font-family:'Microsoft YaHei',sans-serif;background-color:#FFFFC2;padding:20px;border-radius:20px;line-height:35px;">content1\ncontent2\ncontent3</p>
|
<p style="font-family:'Microsoft YaHei',sans-serif;font-size:14px; background-color:#FFFFC2;padding:10px;border-radius:20px;line-height:30px;">content1
|
||||||
|
content2
|
||||||
3.
|
content3
|
||||||
读取markdown属性,如:
|
</p>
|
||||||
---
|
✅
|
||||||
layout: post
|
|
||||||
title: 6月特种兵式观展
|
|
||||||
subtitle:
|
|
||||||
description:
|
|
||||||
date: 2025-06-11 11:00:00
|
|
||||||
author: 大童
|
|
||||||
image: "/img/shufa/a.jpg"
|
|
||||||
showtoc: true
|
|
||||||
tags:
|
|
||||||
- 旅行
|
|
||||||
URL:
|
|
||||||
categories:
|
|
||||||
- live
|
|
||||||
slug: guanzhan
|
|
||||||
---
|
|
||||||
|
|
||||||
提取以下信息(忽略两端的“”):
|
|
||||||
- 公众号文章title: 6月特种兵式观展
|
|
||||||
- 公众号文章作者: 大童
|
|
||||||
- 文章封面图片:GALLERY_PRE_PATH+"/img/shufa/a.jpg",转化为![[a.jpg]]; 如image为空,封面图片取文章中第一张图片
|
|
||||||
|
|
||||||
4.
|
|
||||||
2025ZK1.md 没有正确解析,公众号标题:2025ZK1,封面图片解析也不对。
|
|
||||||
正确的应该是:
|
|
||||||
公众号标题:“2025篆刻记录-0426”
|
|
||||||
封面图片:![[2025ZK1-7.jpg]]
|
|
||||||
|
|
||||||
注意:如果我把2025ZK1.md 内容:img改成![[2025ZK1-7.jpg]],以上解析没有问题。
|
|
||||||
|
|
||||||
5. 文章没有图片,封面使用一张默认图片(设计一张)。
|
5. 文章没有图片,封面使用一张默认图片(设计一张)。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user