by rosswang
Convert 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.
Embed Link Converter
在 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"而无法转回来。
范围:试点 vs 全库
设置里的文件夹留空 = 整个 vault,填了就是只在该文件夹内执行。这是同一个代码路径的两种用法。
- 建议流程:先填一个子文件夹试点 → 看预览里的真实 diff → 确认无误后清空这一栏跑全库
- 「包含子文件夹」控制是否递归
.obsidian、.trash等点目录永远被排除- 把路径填成某个文件时只处理该文件;填一个不存在的文件夹会直接报错中止,不会悄悄扩大到全库
不想改设置也可以直接用命令面板里的「当前笔记」/「当前文件夹」版本。
非图片嵌入
「处理哪些嵌入」决定除图片以外的东西是否参与转换:
| 选项 | 行为 |
|---|---|
| 仅图片(默认) | 只处理 png/jpg/gif/svg/webp/bmp/avif 等。唯一不改变渲染结果的范围 |
| 图片 + 附件 | 加上 PDF、音视频、canvas、压缩包、xlsx 等,但不动笔记 |
| 全部(含笔记转嵌) | 连 ![[Some Note]] 也转成普通链接 |
最后一项会改变笔记的渲染结果——转嵌是一段内联的摘录,普通链接只是一个链接。所以它默认关闭,开启后预览窗口会单独标出有多少条笔记转嵌被改。
安全
- 预览:写入前弹窗,逐文件列出真实的 before → after 差异,可以展开查看每一处。
- 备份:写入前把每个待改文件的原文件复制到
.obsidian/plugins/embed-link-converter/backups/<时间戳>/,目录名即时间戳。 - 保留期:超过设定天数的备份在每次转换后自动删除(默认 7 天,设为 0 永久保留)。
- 回滚:设置里的「回滚上一次转换」用最近一次备份覆盖回来。时间戳在目录名里,所以最新的备份排序就是最新的,不需要插件自己维护指针。
- 备份失败即中止:如果备份写不成功,转换直接放弃,一个文件都不改。
备份文件夹名只认插件自己创建的格式;认不出来的目录永远不会被删除。
不会碰的东西
- 代码块(
``` 和~~~)里的嵌入 - 行内代码
`![[x.png]]` - YAML frontmatter 里的字段
- 远程图片(
、) - 引用式图片
![a][ref] - 带标题的图片
—— 转成双链会丢标题,所以原样保留 <img src="a.png" width="50%">——50%在 Obsidian 里没有对应写法,所以原样保留- 路径里含
<>的链接 —— 无法安全表达,宁可不转
未闭合的
---开头按 Obsidian 的做法当作水平分割线处理,所以这种笔记里的嵌入仍然会被转换。
命令
| 命令 | 作用 |
|---|---|
| 双链嵌入 → Markdown 图片(按设置范围) | 用设置的文件夹/全库范围,双向转换的第一个方向 |
| Markdown 图片 → 双链嵌入(按设置范围) | 反方向 |
| 当前笔记:…(两个方向) | 只处理当前打开的笔记 |
| 当前文件夹:…(两个方向) | 只处理当前笔记所在文件夹 |
| 回滚上一次转换 | 从最近的备份恢复 |
功能区图标默认执行「双链 → Markdown」。
参数顺序说明
Obsidian 的参数顺序是 先标题后尺寸:![[image.jpg|caption|300]]。这一点经两个来源交叉验证:
- Obsidian 论坛引用的 0.9.4 更新日志:Markdown 图片写
,嵌入写![[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
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.