...
`。 +- 节点安全:通过转义内部 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. 正则清单 | 场景 | 正则 | 说明 | @@ -114,6 +161,9 @@ Raw Markdown | Wikilink 图片 | `!\[\[(.+?)\]\]` | 非贪婪 | | Markdown 图片 | `!\[[^\]]*\]\(([^\n\r\)]+)\)` | 不跨行 | | Gallery | `{{${text}
`; + }).join('\n'); + return md; + } getCSS() { try { const theme = this.assetsManager.getTheme(this.currentTheme); @@ -248,6 +351,17 @@ export class ArticleRender implements MDRendererCallback { } const file = this.app.workspace.getActiveFile(); 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); if (metadata?.frontmatter) { const keys = this.assetsManager.expertSettings.frontmatter; @@ -260,6 +374,10 @@ export class ArticleRender implements MDRendererCallback { res.digest = this.getFrontmatterValue(frontmatter, keys.digest); res.content_source_url = this.getFrontmatterValue(frontmatter, keys.content_source_url); 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.need_open_comment = frontmatter[keys.need_open_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'; } } + 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 ) 提取,按出现顺序优先 if (!res.cover && this.originalMarkdown) { @@ -307,6 +452,20 @@ export class ArticleRender implements MDRendererCallback { if (candidates.length > 0) { candidates.sort((a,b)=> a.idx - b.idx); 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; diff --git a/src/setting-tab.ts b/src/setting-tab.ts index 4a3ff01..5c1c4d1 100644 --- a/src/setting-tab.ts +++ b/src/setting-tab.ts @@ -270,6 +270,48 @@ export class NoteToMpSettingTab extends PluginSettingTab { }); }) + new Setting(containerEl) + .setName('Gallery 根路径') + .setDesc('用于 {{$1
` + `\|\|r (.*)` `$1
` + `\|\|g (.*)` `$1
` + `\|\|b (.*)` `$1
` + `\|\|y (.*)` `$1
` + +||连续多行只渲染第一行,举例: || content1 content2 content3 - -修改代码,连续多行只渲染第一行,举例: -content1
+渲染为: +content1
+content2 +content3 而不是: -content1\ncontent2\ncontent3
- -3. -读取markdown属性,如: ---- -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]],以上解析没有问题。 +content1 +content2 +content3 +
+✅ 5. 文章没有图片,封面使用一张默认图片(设计一张)。