rosswang5 downloadsConvert Obsidian wiki-link embeds to Markdown image syntax and back. Scope the run to a folder for a pilot, or leave it empty for the whole vault. Preview every change before it is written, with automatic timestamped backups.
在 Obsidian 的双链嵌入(![[图片.png]])和标准 Markdown 图片()之间双向转换。
可以先在一个文件夹里试点,确认结果后再清空设置对整个 vault 执行。每次转换前都会弹预览窗口逐条列出改动,并自动备份原文件。
这个插件只改写嵌入的语法形式,绝不重写链接目标。路径里的每一个字符都原样保留。
![[attachments/图表 1.png]] → 
![[attachments/图表 1.png|300]] → <img src="attachments/图表 1.png" width="300">
![[attachments/图表 1.png|标题|640x480]] → <img src="attachments/图表 1.png" alt="标题" width="640" height="480">
这是刻意的选择。重写路径(比如把短名解析成 ../attachments/x.png)看起来更"规范",但它有两个不可接受的后果:链接一旦算错就永久失效,而且转换不可逆。保持路径原样,意味着转过去再转回来逐字节一致。
含空格或括号的文件名会自动包进 <>(CommonMark 与 Obsidian 都接受),所以 ![[Screenshot (2).png]] → .png>) 能原样转回来。普通的正则 \(([^)]*)\) 会在第一个 ) 处截断,这种文件名在实际 vault 里非常普遍。
标准 Markdown 没有图片尺寸语法,|300 必须有个去处。三个选项:
| 选项 | ![[a.png|300]] 变成 | 说明 |
|---|---|---|
| HTML <img> 标签(默认) | <img src="a.png" width="300"> | 通用渲染器都能显示,可逆 |
| 直接丢弃 |  | 最简洁,尺寸丢失 |
| 写入 alt 文本 |  | Obsidian 自有写法,可逆但不是通用 Markdown |
用 alt 文本那一项时,即使标题为空也会保留 |(写成 ![|300]),否则  会被读成标题"300"而无法转回来。
设置里的文件夹留空 = 整个 vault,填了就是只在该文件夹内执行。这是同一个代码路径的两种用法。
.obsidian、.trash 等点目录永远被排除不想改设置也可以直接用命令面板里的「当前笔记」/「当前文件夹」版本。
「处理哪些嵌入」决定除图片以外的东西是否参与转换:
| 选项 | 行为 |
|---|---|
| 仅图片(默认) | 只处理 png/jpg/gif/svg/webp/bmp/avif 等。唯一不改变渲染结果的范围 |
| 图片 + 附件 | 加上 PDF、音视频、canvas、压缩包、xlsx 等,但不动笔记 |
| 全部(含笔记转嵌) | 连 ![[Some Note]] 也转成普通链接 |
最后一项会改变笔记的渲染结果——转嵌是一段内联的摘录,普通链接只是一个链接。所以它默认关闭,开启后预览窗口会单独标出有多少条笔记转嵌被改。
.obsidian/plugins/embed-link-converter/backups/<时间戳>/,目录名即时间戳。备份文件夹名只认插件自己创建的格式;认不出来的目录永远不会被删除。
``` 和 ~~~)里的嵌入`![[x.png]]`、)![a][ref] —— 转成双链会丢标题,所以原样保留<img src="a.png" width="50%"> —— 50% 在 Obsidian 里没有对应写法,所以原样保留< > 的链接 —— 无法安全表达,宁可不转未闭合的
---开头按 Obsidian 的做法当作水平分割线处理,所以这种笔记里的嵌入仍然会被转换。
| 命令 | 作用 |
|---|---|
| 双链嵌入 → Markdown 图片(按设置范围) | 用设置的文件夹/全库范围,双向转换的第一个方向 |
| Markdown 图片 → 双链嵌入(按设置范围) | 反方向 |
| 当前笔记:…(两个方向) | 只处理当前打开的笔记 |
| 当前文件夹:…(两个方向) | 只处理当前笔记所在文件夹 |
| 回滚上一次转换 | 从最近的备份恢复 |
功能区图标默认执行「双链 → Markdown」。
Obsidian 的参数顺序是 先标题后尺寸:![[image.jpg|caption|300]]。这一点经两个来源交叉验证:
,嵌入写 ![[image|100x100]]obsidian-image-captions 插件 README:![[image.jpg|This is a caption|center]]插件按这个顺序写出,也按这个顺序读回;反向解析对两种顺序都容错。
cd code/obsidian-embed-link-converter
npm install
npm run build
产物是 main.js + manifest.json + styles.css。部署到 vault:
OUTDIR="/path/to/your/vault/.obsidian/plugins/embed-link-converter" npm run build
或者手动复制这三个文件到 <vault>/.obsidian/plugins/embed-link-converter/,然后在 Obsidian 里启用。
npm run typecheck # tsc --noEmit
npm test # 216 个测试
npm run dev # 监听重建
| 文件 | 职责 |
|---|---|
src/constants.ts |
扩展名分类(图片 / 附件 / 笔记) |
src/segments.ts |
把文档切成「可改」和「不可改」的区域(frontmatter、代码块、行内代码) |
src/markdown-image.ts |
Markdown 图片与 HTML <img> 的手写扫描器 |
src/wiki-embed.ts |
双链嵌入的解析与拼装 |
src/converter.ts |
双向转换的编排(纯函数,不依赖 Obsidian) |
src/scope.ts |
决定哪些文件在范围内 |
src/planner.ts |
生成可预览的改动计划 |
src/backup.ts |
时间戳备份、过期清理、回滚 |
src/previewModal.ts |
预览确认窗口 |
src/SettingsTab.ts |
设置界面 |
src/main.ts |
命令、运行流程 |
转换逻辑全部是不依赖 Obsidian 的纯函数,所以改写规则可以脱离 vault 单测——风险本来就集中在这里。
tests/
├── converter.test.ts # 双向转换规则、范围开关
├── markdown-image.test.ts # 两个扫描器与双链解析器
├── segments.test.ts # 代码块 / frontmatter / 行内代码
├── roundtrip.test.ts # 18 种语法逐条往返一致
├── scope.test.ts # 范围解析,含错误路径
├── planner.test.ts # 计划生成
├── backup.test.ts # 时间戳、过期、回滚、路径安全
├── settings.test.ts # 备份目录由 configDir 推导
├── settingsTab.test.ts # 声明式设置(1.13+ 设置搜索)
└── e2e-filesystem.test.ts # 真实目录、真实文件名、真实落盘往返
roundtrip.test.ts 是这个插件的核心承诺:每一种受支持的语法,转过去再转回来必须逐字节一致。e2e-filesystem.test.ts 在真实文件系统上验证同一件事,用的是中文路径、空格、括号、CRLF。
设置页有两套渲染,但只有一份定义。 getSettingDefinitions()(Obsidian 1.13.0+)和 display()(1.13 之前的全部版本)都读同一个 spec(),因为它们描述的是同一批设置,写两遍迟早会漂移。少实现前者不会报错——设置只是搜不到,所以 settingsTab.test.ts 断言每个持久化字段都出现在声明式定义里。
备份目录跟着 Vault#configDir 走。 .obsidian 这个名字是可以被用户改掉的;硬编码会把备份写到 vault 根本不看的地方,而回滚按钮只会安静地报告「没有找到任何备份」。绝对路径和相对路径两种形态都有测试。
MIT