`(题注样式)。
+- 行级命令:`||r / ||g / ||b / ||y / ||` → 不同背景色 ``。
+- `
` → 前缀补全 `/img/`。
+
+### 7.4 Gallery 功能
+- 短代码 Regex:`{{}}{{}}`
+- `_listGalleryImages`:读目录 + 过滤扩展 + 排序 + 截断。
+- `selectGalleryImages`:对外通用(支持未来 random / prefix / includeDirInLink)。
+- 输出:多行 `![[file]]`,并追加可选 `figcaption` div。
+
+#### 7.4.1 块级 Gallery 语法(新增)
+支持:
+```
+{{}}
+{{}}
+{{}}
+{{}}
+```
+转换:
+```
+![[foo-1.png]]
+![[foo-2.jpeg]]
+```
+规则:
+- 仅取 src 的 basename,忽略 caption(后续可扩展为题注输出)。
+- 若块内未匹配到任何 figure,保留原文本。
+- 正则:`/{{}}([\s\S]*?){{<\/gallery>}}/g` 与内部 `figureRegex = /{{]*>}}/g`。
+- 输出顺序按出现顺序。
+
+## 8. 正则清单
+| 场景 | 正则 | 说明 |
+|------|------|------|
+| frontmatter | `^---[\s\S]*?\n---` | 仅首段 |
+| Wikilink 图片 | `!\[\[(.+?)\]\]` | 非贪婪 |
+| Markdown 图片 | `!\[[^\]]*\]\(([^\n\r\)]+)\)` | 不跨行 |
+| Gallery | `{{}}{{}}` | 捕获 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 模式 | 直接生成 `` 集合而非 wikilink 列表 |
+| 样式外置 | 行级块样式改为统一 CSS class |
+| 目录缓存 | 减少频繁 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 形式写入下方 区域,便于复制。
+4. Notice 简要提示(DryRun 或 完成)。
+
+错误处理:
+- try/catch 包裹,失败写入 resultPre 文本 + Notice。
+- run 按钮在执行期间 disabled,防止重复触发。
+
+后续增强设想:
+| 项目 | 说明 |
+|------|------|
+| 进度条 | 删除大批量时显示当前进度/总数 |
+| 失败重试 | 针对 fails 列表单独重试按钮 |
+| 过滤条件 | 增加标题关键词 / 日期起止输入 |
+| 多账号选择 | 下拉列出已配置的 appid 列表 |
+| 日志导出 | 一键复制 JSON 结果 |
+
diff --git a/package-lock.json b/package-lock.json
index cd928e1..9bde4ae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "note-to-mp",
- "version": "1.3.0",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "note-to-mp",
- "version": "1.3.0",
+ "version": "1.0.0",
"license": "MIT",
"dependencies": {
"@zip.js/zip.js": "^2.7.43",
diff --git a/src/gallery/index.ts b/src/gallery/index.ts
deleted file mode 100644
index c781eb3..0000000
--- a/src/gallery/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-// [note-to-mp 重构] Gallery 模块
-import { App } from 'obsidian';
-
-export interface GalleryTransformResult {
- content: string;
- replaced: boolean;
-}
-
-// 单行 self-closing 形式: {{}}{{}}
-const GALLERY_INLINE_RE = /{{}}\s*{{}}/g;
-// 块级形式
-const GALLERY_BLOCK_RE = /{{}}([\s\S]*?){{<\/gallery>}}/g;
-const FIGURE_RE = /{{]*>}}/g;
-
-export function transformGalleryShortcodes(raw: string): GalleryTransformResult {
- let replaced = false;
- // 处理块级
- raw = raw.replace(GALLERY_BLOCK_RE, (_m, inner) => {
- const imgs: string[] = [];
- let fm: RegExpExecArray | null;
- while ((fm = FIGURE_RE.exec(inner)) !== null) {
- const src = fm[1];
- const base = src.split(/[?#]/)[0].split('/').pop();
- if (base) imgs.push(`![[${base}]]`);
- }
- if (imgs.length === 0) return _m; // 保留原文本
- replaced = true;
- return imgs.join('\n') + '\n';
- });
-
- // 处理单行自闭合形式
- raw = raw.replace(GALLERY_INLINE_RE, (_m, dir, figcaption) => {
- replaced = true;
- const comment = figcaption ? `\n` : '';
- // 暂不实际列目录;由后续 selectGalleryImages 扩展
- return comment + ``;
- });
-
- return { content: raw, replaced };
-}
-
-// 占位:真实实现可遍历 vault 目录
-export async function selectGalleryImages(app: App, dir: string, options?: { limit?: number }): Promise {
- // TODO: 遍历 app.vault.getAbstractFileByPath(dir)
- // 返回文件名数组(不含路径)
- return [];
-}
diff --git a/src/image/index.ts b/src/image/index.ts
deleted file mode 100644
index aedf3c9..0000000
--- a/src/image/index.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-// [note-to-mp 重构] 图片处理模块
-// 负责统一解析 wikilink 与 markdown 图片,并提供集中管理
-
-export interface LocalImage {
- original: string; // 原始匹配串(包括语法标记)
- basename: string; // 文件基本名(不含路径)
- alt?: string; // alt 描述(若来自 markdown 语法)
- sourceType: 'wikilink' | 'markdown';
- index: number; // 在原文中的出现顺序
-}
-
-export const LocalFileRegex = /^(?:!\[\[(.*?)\]\]|!\[[^\]]*\]\(([^\n\r\)]+)\))/;
-
-export class LocalImageManager {
- private images: LocalImage[] = [];
- private byBasename: Map = new Map();
-
- add(image: LocalImage) {
- this.images.push(image);
- const list = this.byBasename.get(image.basename) || [];
- list.push(image);
- this.byBasename.set(image.basename, list);
- }
-
- all(): LocalImage[] { return this.images.slice(); }
-
- first(): LocalImage | undefined { return this.images[0]; }
-
- findByBasename(name: string): LocalImage | undefined {
- const list = this.byBasename.get(name);
- return list && list[0];
- }
-
- clear() { this.images = []; this.byBasename.clear(); }
-}
-
-export function parseImagesFromMarkdown(markdown: string): LocalImage[] {
- // 扫描整篇,统一抽取,不做替换
- const result: LocalImage[] = [];
- const wikilinkRe = /!\[\[(.+?)\]\]/g; // 非贪婪
- const mdImgRe = /!\[([^\]]*)\]\(([^\n\r\)]+)\)/g;
- let index = 0;
-
- let m: RegExpExecArray | null;
- while ((m = wikilinkRe.exec(markdown)) !== null) {
- const full = m[0];
- const inner = m[1].trim();
- const basename = inner.split('/').pop() || inner;
- result.push({ original: full, basename, sourceType: 'wikilink', index: index++ });
- }
- while ((m = mdImgRe.exec(markdown)) !== null) {
- const full = m[0];
- const alt = m[1].trim();
- const link = m[2].trim();
- const basename = link.split(/[?#]/)[0].split('/').pop() || link;
- result.push({ original: full, basename, alt, sourceType: 'markdown', index: index++ });
- }
-
- // 按出现顺序(两个正则独立扫描会破坏顺序,重新排序 by 原始位置)
- result.sort((a, b) => markdown.indexOf(a.original) - markdown.indexOf(b.original));
- // 重排 index
- result.forEach((r, i) => r.index = i);
- return result;
-}
diff --git a/src/meta/index.ts b/src/meta/index.ts
deleted file mode 100644
index 5d12baf..0000000
--- a/src/meta/index.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-// [note-to-mp 重构] 元数据与封面模块
-import { LocalImage } from '../image';
-
-export interface WeChatMetaRaw {
- title?: string;
- author?: string;
- coverLink?: string; // frontmatter 或行内指定的图片 basename 形式
- rawImage?: string; // 原 frontmatter 中的 image 字段原始值(可包含路径)
- hasFrontmatter: boolean;
-}
-
-export interface FinalMeta {
- title: string;
- author?: string;
- coverImage?: LocalImage; // 解析到的封面图片对象
- coverLink?: string; // 决策后的封面 basename
-}
-
-const FRONTMATTER_RE = /^---[\s\S]*?\n---/;
-
-export function extractWeChatMeta(raw: string): { meta: WeChatMetaRaw; body: string } {
- const fmMatch = raw.match(FRONTMATTER_RE);
- if (!fmMatch) {
- return { meta: { hasFrontmatter: false }, body: raw };
- }
- const block = fmMatch[0];
- const lines = block.split(/\r?\n/).slice(1, -1); // 去除首尾 ---
- let title: string | undefined;
- let author: string | undefined;
- let image: string | undefined;
-
- for (const line of lines) {
- const m = line.match(/^([a-zA-Z0-9_-]+)\s*:\s*(.*)$/);
- if (!m) continue;
- const key = m[1].toLowerCase();
- let val = m[2].trim();
- // 去除包裹引号
- if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
- val = val.slice(1, -1);
- }
- if (key === 'title') title = val;
- else if (key === 'author') author = val;
- else if (key === 'image' || key === 'cover') image = val;
- }
-
- let coverLink: string | undefined;
- if (image) {
- const basename = image.split(/[?#]/)[0].split('/').pop() || image;
- coverLink = basename;
- }
-
- const body = raw.slice(block.length).replace(/^\s+/, '');
- return { meta: { title, author, coverLink, rawImage: image, hasFrontmatter: true }, body };
-}
-
-export function getMetadata(images: LocalImage[], rawMeta: WeChatMetaRaw): FinalMeta {
- // 标题回退策略:若无 frontmatter title,尝试第一行一级标题
- let title = rawMeta.title;
- if (!title) {
- // 简单取第一行 markdown 一级/二级标题
- // 实际调用方可传入 body 再做改进;这里保持接口简单
- title = '未命名文章';
- }
-
- let coverLink = rawMeta.coverLink;
- let coverImage: LocalImage | undefined;
- if (coverLink) {
- coverImage = images.find(img => img.basename === coverLink);
- }
- if (!coverImage) {
- coverImage = images[0];
- coverLink = coverImage?.basename;
- }
-
- return { title, author: rawMeta.author, coverImage, coverLink };
-}
diff --git a/src/note-preview.ts b/src/note-preview.ts
index 1029d68..2641c95 100644
--- a/src/note-preview.ts
+++ b/src/note-preview.ts
@@ -22,8 +22,6 @@
import { EventRef, ItemView, Workspace, WorkspaceLeaf, Notice, Platform, TFile, TFolder, TAbstractFile, Plugin } from 'obsidian';
import { uevent, debounce, waitForLayoutReady } from './utils';
-// [note-to-mp 重构] 引入新渲染管线
-import { RenderService, RenderedArticle } from './render';
import { NMPSettings } from './settings';
import AssetsManager from './assets';
import { MarkedParser } from './markdown/parser';
@@ -64,9 +62,6 @@ export class NotePreview extends ItemView {
_articleRender: ArticleRender | null = null;
isCancelUpload: boolean = false;
isBatchRuning: boolean = false;
- // [note-to-mp 重构] 新渲染服务实例与最近一次渲染结果
- newRenderService: RenderService | null = null;
- lastArticle?: RenderedArticle;
constructor(leaf: WorkspaceLeaf, plugin: Plugin) {
@@ -118,8 +113,6 @@ export class NotePreview extends ItemView {
}
this.buildUI();
- // [note-to-mp 重构] 初始化新渲染服务
- this.newRenderService = new RenderService(this.app);
this.listeners = [
this.workspace.on('file-open', () => {
this.update();
@@ -447,33 +440,31 @@ export class NotePreview extends ItemView {
return;
}
this.currentFile = af;
- // [note-to-mp 重构] 使用新渲染服务进行渲染
- if (this.newRenderService) {
- try {
- const article = await this.newRenderService.renderFile(af);
- this.lastArticle = article;
- if (this.articleDiv) {
- this.articleDiv.empty();
- const wrap = this.articleDiv.createDiv();
- wrap.innerHTML = article.html;
+ await this.render.renderMarkdown(af);
+ const metadata = this.render.getMetadata();
+ if (metadata.appid) {
+ this.wechatSelect.value = metadata.appid;
+ }
+ else {
+ this.wechatSelect.value = this.currentAppId;
+ }
+
+ if (metadata.theme) {
+ this.assetsManager.themes.forEach(theme => {
+ if (theme.name === metadata.theme) {
+ this.themeSelect.value = theme.className;
}
- // 元数据适配(当前新 meta 不含 appid/theme/highlight,保持现有选择状态)
- if (this.wechatSelect) {
- this.wechatSelect.value = this.currentAppId || '';
- }
- if (this.themeSelect) {
- this.themeSelect.value = this.currentTheme;
- }
- if (this.highlightSelect) {
- this.highlightSelect.value = this.currentHighlight;
- }
- } catch (e) {
- console.error('[note-to-mp 重构] 渲染失败', e);
- new Notice('渲染失败: ' + e.message);
- }
- } else {
- // 兜底:仍使用旧渲染
- await this.render.renderMarkdown(af);
+ });
+ }
+ else {
+ this.themeSelect.value = this.currentTheme;
+ }
+
+ if (metadata.highlight) {
+ this.highlightSelect.value = this.render.currentHighlight;
+ }
+ else {
+ this.highlightSelect.value = this.currentHighlight;
}
}
diff --git a/src/preprocess/index.ts b/src/preprocess/index.ts
deleted file mode 100644
index 28163de..0000000
--- a/src/preprocess/index.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// [note-to-mp 重构] 内容预处理模块
-
-// 行级颜色块语法:||r text / ||g text / ||b text / ||y text / || text
-const LINE_COLOR_RE = /^\|\|(r|g|b|y)?\s+(.*)$/;
-const FIG_RE = /\[fig([^\n]*?)\/_?]/g; // 简单题注
-
-function wrapColorLine(code: string | undefined, text: string): string {
- const colorMap: Record = {
- r: '#ffe5e5',
- g: '#e5ffe9',
- b: '#e5f1ff',
- y: '#fff7d6',
- '': '#f2f2f2'
- };
- const c = (code && colorMap[code]) || colorMap[''];
- return `${text}
`;
-}
-
-export function preprocessContent(markdown: string): string {
- const lines = markdown.split(/\r?\n/);
- const out: string[] = [];
- for (const line of lines) {
- const m = line.match(LINE_COLOR_RE);
- if (m) {
- out.push(wrapColorLine(m[1], m[2]));
- } else {
- out.push(line);
- }
- }
- let joined = out.join('\n');
- joined = joined.replace(FIG_RE, (_m, g1) => {
- const text = g1.trim();
- return `${text}`;
- });
- return joined;
-}
diff --git a/src/refactor-plan.md b/src/refactor-plan.md
deleted file mode 100644
index ab6f617..0000000
--- a/src/refactor-plan.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# note-to-mp 重构规划 (模块与接口草案)
-
-> 标记格式: // [note-to-mp 重构]
-
-## 目标概要
-- 模块化:图片处理、元数据、Gallery、内容预处理、渲染管线分离。
-- 清晰接口:对外暴露统一渲染与数据提取 API。
-- 可测试:核心逻辑函数纯函数化,最小化对 Obsidian 运行时依赖。
-
-## 模块划分
-1. 图片处理模块 (image/)
- - 统一识别 wikilink 与 markdown 图片语法
- - LocalImage 结构: { original: string; basename: string; alt?: string; sourceType: 'wikilink'|'markdown'; index: number; }
- - LocalImageManager: 收集、查询、封面候选、上传占位接口
- - 正则常量: LocalFileRegex
-
-2. 元数据与封面 (meta/)
- - extractWeChatMeta(raw: string): WeChatMetaRaw
- - getWeChatArticleMeta(): 返回最近一次渲染缓存的 meta
- - getMetadata(images: LocalImage[], metaRaw: WeChatMetaRaw): FinalMeta
- - 回退策略: frontmatter cover > metaRaw.coverLink > images[0]
-
-3. Gallery 支持 (gallery/)
- - transformGalleryShortcodes(content: string): { content: string; extracted?: GalleryInfo }
- - selectGalleryImages(dir: string, options): Promise
- - 语法: 单行 self-closing 与 块级形式
-
-4. 内容预处理 (preprocess/)
- - preprocessContent(markdown: string): string
- - 行级语法: ||r / ||g / ||b / ||y / || (默认灰)
- - figure 语法: [fig text/]
-
-5. 渲染管线 (render/)
- - renderMarkdown(file: TFile): Promise
- - 内部阶段:
- Raw -> extractWeChatMeta -> strip frontmatter -> transformGalleryShortcodes -> preprocessContent -> markdown parse (自定义 tokenizer) -> HTML + 样式注入 -> metadata 汇总
-
-6. 上传/微信接口 (weixin/)
- - 包装现有 weixin-api.ts 函数 + 错误封装
-
-## 数据结构
-```ts
-interface LocalImage { original: string; basename: string; alt?: string; sourceType: 'wikilink'|'markdown'; index: number; }
-interface WeChatMetaRaw { title?: string; author?: string; coverLink?: string; rawImage?: string; hasFrontmatter: boolean; }
-interface FinalMeta { title: string; author?: string; coverImage?: LocalImage; coverLink?: string; }
-interface RenderedArticle { html: string; css?: string; meta: FinalMeta; images: LocalImage[]; raw: string; }
-```
-
-## 关键正则
-- frontmatter: ^---[\s\S]*?\n---
-- wikilink image: !\[\[(.+?)\]\]
-- markdown image: !\[[^\]]*\]\(([^\n\r\)]+)\)
-- gallery block: /{{}}([\s\S]*?){{<\/gallery>}}/g
-- gallery figure: /{{]*>}}/g
-
-## 风险点
-- 正则误判 frontmatter
-- 图片在预处理阶段被破坏索引
-- 多次渲染缓存污染
-
-## 缓解
-- 提取后不修改原文本副本
-- 维护渲染上下文对象 (RenderContext)
-
-## 后续实现顺序
-图片处理 -> 元数据 -> Gallery -> 预处理 -> 渲染组装 -> 接口对接现有 NotePreview
-
----
-(实现过程中该文档可增补)
diff --git a/src/render/index.ts b/src/render/index.ts
deleted file mode 100644
index 62f60a9..0000000
--- a/src/render/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-// [note-to-mp 重构] 渲染管线模块
-import { App, TFile, MarkdownRenderer } from 'obsidian';
-import { parseImagesFromMarkdown, LocalImage, LocalImageManager } from '../image';
-import { extractWeChatMeta, getMetadata, FinalMeta } from '../meta';
-import { transformGalleryShortcodes } from '../gallery';
-import { preprocessContent } from '../preprocess';
-
-export interface RenderedArticle {
- html: string;
- meta: FinalMeta;
- images: LocalImage[];
- raw: string;
-}
-
-export class RenderService {
- private app: App;
- private imageManager = new LocalImageManager();
-
- constructor(app: App) {
- this.app = app;
- }
-
- async renderFile(file: TFile): Promise {
- const raw = await this.app.vault.read(file);
- return this.renderRaw(raw, file.path);
- }
-
- async renderRaw(raw: string, path?: string): Promise {
- this.imageManager.clear();
- // 1. frontmatter + 基础元数据
- const { meta: rawMeta, body } = extractWeChatMeta(raw);
- // 2. gallery 转换
- const galleryRes = transformGalleryShortcodes(body);
- // 3. 预处理行级语法
- const preprocessed = preprocessContent(galleryRes.content);
- // 4. 图片解析
- const images = parseImagesFromMarkdown(preprocessed);
- images.forEach(i => this.imageManager.add(i));
- // 5. 获取最终 meta(封面回退)
- const finalMeta = getMetadata(images, rawMeta);
- // 6. markdown -> HTML (使用 Obsidian 内部渲染管线)
- const el = document.createElement('div');
- // NOTE: 这里简化,实际应考虑自定义 tokenizer;后续可补充
- await MarkdownRenderer.renderMarkdown(preprocessed, el, path || '', this.app as any);
- // 7. 注入简单样式 (可外置)
- const style = ``;
-
- return { html: style + el.innerHTML, meta: finalMeta, images, raw };
- }
-}
diff --git a/todo.list b/todo.list
new file mode 100644
index 0000000..8567523
--- /dev/null
+++ b/todo.list
@@ -0,0 +1,68 @@
+1. 预处理markdown文件:
+对{{}}{{}}或{{}}{{}}
+ - 获取dir中的内容,如"/img/guanzhan/1",与PREPATH拼接,全局定义PRE_PATH=/Users/gavin/myweb/static
+ 图片所在路径:PREPATH+"/img/guanzhan/1",即/Users/gavin/myweb/static/img/guanzhan/1。
+ - 这个/Users/gavin/myweb/static/img/guanzhan/1路径下图片<5张,取出所有图片; >n张,任意取出n张。n=NUM_PIC作为全局定义。
+ - 比如n=1,取出的图片为xx.jpg,那么把{{}}{{}}替换为![[xx.jpg]]
+ 如n=2,取出的图片为xx.jpg,yy.png,那么把{{}}{{}}替换为:
+ ![[xx.jpg]]
+ ![[yy.png]]
+ 在main.js单独函数中处理,在预处理内容时调用。
+
+
+2.
+对如下:
+{{}}
+{{}}
+{{}}
+{{}}
+{{}}
+替换为
+![[晋中晋北行程.jpeg]]
+![[晋中晋北行程-2.jpeg]]
+![[晋中晋北行程-3.jpeg]]
+
+
+2. 需求:没有成功❌❓
+|| content1
+content2
+content3
+
+修改代码,连续多行只渲染第一行,举例:
+content1
+而不是:
+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]],以上解析没有问题。
+
+5. 文章没有图片,封面使用一张默认图片(设计一张)。