diff --git a/docs/en/guide/markdown/obsidian.md b/docs/en/guide/markdown/obsidian.md
index d321c219..4ac5bbcd 100644
--- a/docs/en/guide/markdown/obsidian.md
+++ b/docs/en/guide/markdown/obsidian.md
@@ -7,8 +7,7 @@ permalink: /en/guide/markdown/obsidian/
## Overview
-The theme provides compatibility support for Obsidian's official Markdown extension syntax through the `vuepress-plugin-md-power` plugin,
-enabling Obsidian users to write documentation using familiar syntax.
+The theme provides compatibility support for Obsidian's official Markdown extension syntax through the `vuepress-plugin-md-power` plugin, enabling Obsidian users to write documentation using familiar syntax.
Currently supported Obsidian extension syntax includes:
@@ -21,7 +20,7 @@ Currently supported Obsidian extension syntax includes:
## Wiki Links
-Wiki Links are syntax for linking to other notes in Obsidian.
+Wiki Links are syntax used in Obsidian for linking to other notes. Use double brackets `[[]]` to wrap content to create internal links.
### Syntax
@@ -31,6 +30,7 @@ Wiki Links are syntax for linking to other notes in Obsidian.
[[filename#heading#subheading]]
[[filename|alias]]
[[filename#heading|alias]]
+[[https://example.com|External Link]]
```
### Filename Search Rules
@@ -39,15 +39,14 @@ When using Wiki Links, filenames are matched according to the following rules:
**Match Priority:**
-1. **Page Title** - Priority matching against page titles
-2. **Full Path** - Exact match against file paths
-3. **Fuzzy Match** - Match filenames at the end of paths
+1. **Full Path** - Exact match against file paths
+2. **Fuzzy Match** - Match filenames at the end of paths, prioritizing the shortest path
**Path Resolution Rules:**
- **Relative paths** (starting with `.`): Resolved relative to the current file's directory
-- **Absolute paths** (not starting with `.`): Searched throughout the document tree, with shortest path taking precedence
-- **Directory form** (ending with `/`): Matches `README.md` or `index.html` within that directory
+- **Absolute paths** (not starting with `.`): Searched throughout the document tree, prioritizing the shortest path
+- **Directory form** (ending with `/`): Matches `README.md` in that directory
**Example:**
@@ -55,22 +54,21 @@ Assuming the following document structure:
```txt
docs/
-├── README.md (title: "Home")
+├── README.md
├── guide/
-│ ├── README.md (title: "Guide")
+│ ├── README.md
│ └── markdown/
│ └── obsidian.md
```
In `docs/guide/markdown/obsidian.md`:
-| Syntax | Match Result |
-| ------------ | ------------------------------------------------------- |
-| `[[Home]]` | Matches `docs/README.md` (via title) |
-| `[[Guide]]` | Matches `docs/guide/README.md` (via title) |
-| `[[./]]` | Matches `docs/guide/markdown/README.md` (relative path) |
-| `[[../]]` | Matches `docs/guide/README.md` (parent directory) |
-| `[[guide/]]` | Matches `docs/guide/README.md` (directory form) |
+| Syntax | Match Result |
+| ------------------ | ----------------------------------------------------------------------------------------- |
+| `[[obsidian]]` | Matches `docs/guide/markdown/obsidian.md` (matched via filename) |
+| `[[./]]` | Matches `docs/guide/markdown/README.md` (relative path) |
+| `[[../]]` | Matches `docs/guide/README.md` (parent directory) |
+| `[[guide/]]` | Matches `docs/guide/README.md` (directory form) |
### Examples
@@ -86,31 +84,26 @@ In `docs/guide/markdown/obsidian.md`:
[[https://example.com|External Link]]
----
-
**Internal Anchor Links:**
**Input:**
```md
-[[QR Code]]
[[npm-to]]
[[guide/markdown/math]]
[[#Wiki Links]]
-[[file-tree#configuration]]
+[[file-tree#Configuration]]
```
**Output:**
-[[QR Code]]
-
[[npm-to]]
[[guide/markdown/math]]
[[#Wiki Links]]
-[[file-tree#configuration]]
+[[file-tree#Configuration]]
[Obsidian Official - **Wiki Links**](https://obsidian.md/en/help/links){.readmore}
@@ -136,22 +129,38 @@ Filename search rules are the same as [Wiki Links](#filename-search-rules).
**Syntax:**
```md
-![[image.png]]
-![[image.png|300]]
-![[image.png|300x200]]
+![[image]]
+![[image|300]]
+![[image|300x200]]
```
Supported formats: `jpg`, `jpeg`, `png`, `gif`, `avif`, `webp`, `svg`, `bmp`, `ico`, `tiff`, `apng`, `jfif`, `pjpeg`, `pjp`, `xbm`
-**Input:**
+**Example:**
+
+::: demo markdown title="Basic Image" expanded
```md
![[images/custom-hero.jpg]]
```
-**Output:**
+:::
-![[images/custom-hero.jpg]]
+::: demo markdown title="Set Width" expanded
+
+```md
+![[images/custom-hero.jpg|300]]
+```
+
+:::
+
+::: demo markdown title="Set Width and Height" expanded
+
+```md
+![[images/custom-hero.jpg|300x200]]
+```
+
+:::
### PDF Embeds
@@ -162,46 +171,38 @@ Supported formats: `jpg`, `jpeg`, `png`, `gif`, `avif`, `webp`, `svg`, `bmp`, `i
```md
![[document.pdf]]
-![[document.pdf#page=1]]
-![[document.pdf#page=1#height=300]]
+![[document.pdf#page=1]]
+![[document.pdf#page=1#height=300]]
```
+Supported formats: `pdf`
+
---
### Audio Embeds
-> [!note]
-> Audio embeds require the file path to be correct and the file to exist in the document directory.
-
-**Input:**
+**Syntax:**
```md
-![[audio.mp3]]
+![[audio file]]
```
-**Output:**
-
-![[https://publish-01.obsidian.md/access/cf01a21839823cd6cbe18031acf708c0/Attachments/audio/Excerpt%20from%20Mother%20of%20All%20Demos%20(1968).ogg]]
-
Supported formats: `mp3`, `flac`, `wav`, `ogg`, `opus`, `webm`, `acc`
---
### Video Embeds
-> [!note]
+> [!NOTE]
> Video embeds require the `markdown.artPlayer` plugin to be enabled for proper functionality.
-**Input:**
+**Syntax:**
```md
-![[video.mp4]]
+![[video file]]
+![[video file#height=400]]
```
-**Output:**
-
-![[https://artplayer.org/assets/sample/video.mp4]]
-
Supported formats: `mp4`, `webm`, `mov`, etc.
---
@@ -214,12 +215,12 @@ Content fragments under a specified heading can be embedded using `#heading`:
```md
![[my-note]]
-![[my-note#heading-one]]
-![[my-note#heading-one#subheading]]
+![[my-note#Heading One]]
+![[my-note#Heading One#Subheading]]
```
-[Obsidian Official - Embeds](https://obsidian.md/en/help/embeds){.readmore}
-[Obsidian Official - File Formats](https://obsidian.md/en/help/file-formats){.readmore}
+[Obsidian Official - **Insert Files**](https://obsidian.md/en/help/embeds){.readmore}
+[Obsidian Official - **File Formats**](https://obsidian.md/en/help/file-formats){.readmore}
## Comments
@@ -280,28 +281,25 @@ Content before the comment
%%
This is a block comment.
-
-It can span multiple lines.
%%
-Content after the comment
+It can span multiple lines.
-> Related Documentation: [Obsidian Official - Comments](https://obsidian.md/en/help/syntax#%E6%B3%A8%E9%87%8B)
+[Obsidian Official - **Comments**](https://obsidian.md/en/help/syntax#%E6%B3%A8%E9%87%8B){.readmore}
## Configuration
-You can enable or disable these plugins in the theme configuration:
+Obsidian compatibility features are all enabled by default. You can selectively enable or disable them through configuration:
```ts title=".vuepress/config.ts"
export default defineUserConfig({
theme: plumeTheme({
plugins: {
mdPower: {
- // Obsidian compatibility plugin configuration
obsidian: {
wikiLink: true, // Wiki Links
- embedLink: true, // Embeds
- comment: true, // Comments
+ embedLink: true, // Embeds
+ comment: true, // Comments
},
pdf: true, // PDF embed functionality
artPlayer: true, // Video embed functionality
@@ -316,15 +314,15 @@ export default defineUserConfig({
:::: field-group
::: field name="wikiLink" type="boolean" default="true" optional
-Enable Wiki Links syntax
+Enable Wiki Links syntax.
:::
::: field name="embedLink" type="boolean" default="true" optional
-Enable embed content syntax
+Enable embed content syntax.
:::
::: field name="comment" type="boolean" default="true" optional
-Enable comment syntax
+Enable comment syntax.
:::
::::
@@ -332,7 +330,8 @@ Enable comment syntax
## Notes
- These plugins provide **compatibility support** and do not fully implement all of Obsidian's functionality
-- Some Obsidian-specific features (such as the graph view for internal links, bidirectional links, etc.) are outside the scope of this support
+- Some Obsidian-specific features (such as internal link graph views, bidirectional links, etc.) are outside the scope of this support
- When embedding content, the embedded page also participates in the theme's build process
-- PDF embeds require the `pdf` plugin to be enabled simultaneously
-- Video embeds require the `artPlayer` plugin to be enabled simultaneously
+- PDF embeds require the `markdown.pdf` plugin to be enabled simultaneously
+- Video embeds require the `markdown.artPlayer` plugin to be enabled simultaneously
+- Embed resources starting with `/` or using `./` form are loaded from the `public` directory
diff --git a/docs/guide/markdown/obsidian.md b/docs/guide/markdown/obsidian.md
index 3033a34e..828ed060 100644
--- a/docs/guide/markdown/obsidian.md
+++ b/docs/guide/markdown/obsidian.md
@@ -15,12 +15,12 @@ permalink: /guide/markdown/obsidian/
- [嵌入内容](#嵌入内容) - 将其他文件内容嵌入到当前页面
- [注释](#注释) - 添加仅在编辑时可见的注释
-::: warning 不计划对 obsidian 社区的第三方插件提供的扩展语法进行支持
+::: warning 不计划支持 Obsidian 社区第三方插件提供的扩展语法
:::
## Wiki 链接
-Wiki 链接是 Obsidian 中用于链接到其他笔记的语法。
+Wiki 链接是 Obsidian 中用于链接到其他笔记的语法。使用双括号 `[[]]` 包裹内容来创建内部链接。
### 语法
@@ -30,6 +30,7 @@ Wiki 链接是 Obsidian 中用于链接到其他笔记的语法。
[[文件名#标题#子标题]]
[[文件名|别名]]
[[文件名#标题|别名]]
+[[https://example.com|外部链接]]
```
### 文件名搜索规则
@@ -38,15 +39,14 @@ Wiki 链接是 Obsidian 中用于链接到其他笔记的语法。
**匹配优先级:**
-1. **页面标题** - 优先匹配页面的标题
-2. **完整路径** - 精确匹配文件路径
-3. **模糊匹配** - 匹配路径结尾的文件名
+1. **完整路径** - 精确匹配文件路径
+2. **模糊匹配** - 匹配路径结尾的文件名,优先匹配最短路径
**路径解析规则:**
- **相对路径**(以 `.` 开头):相对于当前文件所在目录解析
- **绝对路径**(不以 `.` 开头):在整个文档树中搜索,优先匹配最短路径
-- **目录形式**(以 `/` 结尾):匹配该目录下的 `README.md` 或 `index.html`
+- **目录形式**(以 `/` 结尾):匹配该目录下的 `README.md`
**示例:**
@@ -54,22 +54,21 @@ Wiki 链接是 Obsidian 中用于链接到其他笔记的语法。
```txt
docs/
-├── README.md (title: "首页")
+├── README.md
├── guide/
-│ ├── README.md (title: "指南")
+│ ├── README.md
│ └── markdown/
│ └── obsidian.md
```
在 `docs/guide/markdown/obsidian.md` 中:
-| 语法 | 匹配结果 |
-| ------------ | ------------------------------------------------ |
-| `[[首页]]` | 匹配 `docs/README.md`(通过标题) |
-| `[[指南]]` | 匹配 `docs/guide/README.md`(通过标题) |
-| `[[./]]` | 匹配 `docs/guide/markdown/README.md`(相对路径) |
-| `[[../]]` | 匹配 `docs/guide/README.md`(上级目录) |
-| `[[guide/]]` | 匹配 `docs/guide/README.md`(目录形式) |
+| 语法 | 匹配结果 |
+| -------------- | -------------------------------------------------------- |
+| `[[obsidian]]` | 匹配 `docs/guide/markdown/obsidian.md`(通过文件名检索) |
+| `[[./]]` | 匹配 `docs/guide/markdown/README.md`(相对路径) |
+| `[[../]]` | 匹配 `docs/guide/README.md`(上级目录) |
+| `[[guide/]]` | 匹配 `docs/guide/README.md`(目录形式) |
### 示例
@@ -90,7 +89,6 @@ docs/
**输入:**
```md
-[[二维码]]
[[npm-to]]
[[guide/markdown/math]]
[[#Wiki 链接]]
@@ -99,8 +97,6 @@ docs/
**输出:**
-[[二维码]]
-
[[npm-to]]
[[guide/markdown/math]]
@@ -133,22 +129,38 @@ docs/
**语法:**
```md
-![[image.png]]
-![[image.png|300]]
-![[image.png|300x200]]
+![[图片]]
+![[图片|宽度]]
+![[图片|宽度x高度]]
```
-支持格式:`jpg`, `jpeg`, `png`, `gif`, `avif`, `webp`, `svg`, `bmp`, `ico`, `tiff`, `apng`, `jfif`, `pjpeg`, `pjp`, `xbm`
+支持格式:`jpg`、`jpeg`、`png`、`gif`、`avif`、`webp`、`svg`、`bmp`、`ico`、`tiff`、`apng`、`jfif`、`pjpeg`、`pjp`、`xbm`
-**输入:**
+**示例:**
+
+::: demo markdown title="基础图片" expanded
```md
![[images/custom-hero.jpg]]
```
-**输出:**
+:::
-![[images/custom-hero.jpg]]
+::: demo markdown title="设置宽度" expanded
+
+```md
+![[images/custom-hero.jpg|300]]
+```
+
+:::
+
+::: demo markdown title="设置宽度和高度" expanded
+
+```md
+![[images/custom-hero.jpg|300x200]]
+```
+
+:::
### PDF 嵌入
@@ -158,48 +170,40 @@ docs/
**语法:**
```md
-![[document.pdf]]
-![[document.pdf#page=1]]
-![[document.pdf#page=1#height=300]]
+![[文档.pdf]]
+![[文档.pdf#page=1]]
+![[文档.pdf#page=1#height=300]]
```
+支持格式:`pdf`
+
---
### 音频嵌入
-> [!note]
-> 音频嵌入需要确保文件路径正确,文件存在于文档目录中。
-
-**输入:**
+**语法:**
```md
-![[audio.mp3]]
+![[音频文件]]
```
-**输出:**
-
-![[https://publish-01.obsidian.md/access/cf01a21839823cd6cbe18031acf708c0/Attachments/audio/Excerpt%20from%20Mother%20of%20All%20Demos%20(1968).ogg]]
-
-支持格式:`mp3`, `flac`, `wav`, `ogg`, `opus`, `webm`, `acc`
+支持格式:`mp3`、`flac`、`wav`、`ogg`、`opus`、`webm`、`acc`
---
### 视频嵌入
-> [!note]
+> [!NOTE]
> 视频嵌入需要启用 `markdown.artPlayer` 插件才能正常工作。
-**输入:**
+**语法:**
```md
-![[video.mp4]]
+![[视频文件]]
+![[视频文件#height=400]]
```
-**输出:**
-
-![[https://artplayer.org/assets/sample/video.mp4]]
-
-支持格式:`mp4`, `webm`, `mov` 等
+支持格式:`mp4`、`webm`、`mov` 等
---
@@ -277,28 +281,25 @@ docs/
%%
这是一个块级注释。
-
-可以跨越多行。
%%
-注释之后的内容
+可以跨越多行。
[Obsidian 官方 - 注释](https://obsidian.md/zh/help/syntax#%E6%B3%A8%E9%87%8A){.readmore}
## 配置
-你可以在主题配置中启用或禁用这些插件:
+Obsidian 兼容功能默认全部启用,你可以通过配置选择性地启用或禁用:
```ts title=".vuepress/config.ts"
export default defineUserConfig({
theme: plumeTheme({
plugins: {
mdPower: {
- // Obsidian 兼容插件配置
obsidian: {
wikiLink: true, // Wiki 链接
- embedLink: true, // 嵌入内容
- comment: true, // 注释
+ embedLink: true, // 嵌入内容
+ comment: true, // 注释
},
pdf: true, // PDF 嵌入功能
artPlayer: true, // 视频嵌入功能
@@ -313,15 +314,15 @@ export default defineUserConfig({
:::: field-group
::: field name="wikiLink" type="boolean" default="true" optional
-启用 Wiki 链接语法
+启用 Wiki 链接语法。
:::
::: field name="embedLink" type="boolean" default="true" optional
-启用嵌入内容语法
+启用嵌入内容语法。
:::
::: field name="comment" type="boolean" default="true" optional
-启用注释语法
+启用注释语法。
:::
::::
@@ -331,5 +332,6 @@ export default defineUserConfig({
- 这些插件提供的是 **兼容性支持**,并非完全实现 Obsidian 的全部功能
- 部分 Obsidian 特有的功能(如内部链接的图谱视图、双向链接等)不在支持范围内
- 嵌入内容时,被嵌入的页面也会参与主题的构建过程
-- PDF 嵌入需要同时启用 `pdf` 插件
-- 视频嵌入需要同时启用 `artPlayer` 插件
+- PDF 嵌入需要同时启用 `markdown.pdf` 插件
+- 视频嵌入需要同时启用 `markdown.artPlayer` 插件
+- 以 `/` 开头或使用 `./` 形式的嵌入资源会从 `public` 目录加载
diff --git a/plugins/plugin-md-power/__test__/obsidianEmbedLink.spec.ts b/plugins/plugin-md-power/__test__/obsidianEmbedLink.spec.ts
new file mode 100644
index 00000000..a63de2a2
--- /dev/null
+++ b/plugins/plugin-md-power/__test__/obsidianEmbedLink.spec.ts
@@ -0,0 +1,421 @@
+import type { App } from 'vuepress'
+import type { MarkdownEnv } from 'vuepress/markdown'
+import MarkdownIt from 'markdown-it'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { embedLinkPlugin } from '../src/node/obsidian/embedLink.js'
+import { initPagePaths } from '../src/node/obsidian/findFirstPage.js'
+
+const mockGlobSync = vi.fn()
+const mockReadFileSync = vi.fn()
+
+vi.mock('vuepress/utils', () => ({
+ tinyglobby: {
+ globSync: (...args: unknown[]) => mockGlobSync(...args),
+ },
+ fs: {
+ readFileSync: (...args: unknown[]) => mockReadFileSync(...args),
+ },
+ path: {
+ dirname: vi.fn((p: string) => p.split('/').slice(0, -1).join('/') || '.'),
+ extname: vi.fn((p: string) => {
+ const i = p.lastIndexOf('.')
+ return i > 0 ? p.slice(i) : ''
+ }),
+ join: vi.fn((...args: string[]) => args.join('/')),
+ },
+ hash: vi.fn((s: string) => `hash_${s.length}`),
+}))
+
+vi.mock('gray-matter', () => ({
+ default: vi.fn((content: string) => ({
+ content: content.replace(/^---[\s\S]*?---\n?/, ''),
+ data: {},
+ })),
+}))
+
+vi.mock('@vuepress/helper', () => ({
+ removeLeadingSlash: vi.fn((p: string) => p.replace(/^\//, '')),
+}))
+
+function createMockApp(pages: App['pages'] = []): App {
+ return {
+ pages,
+ options: {
+ pagePatterns: ['**/*.md'],
+ },
+ dir: {
+ source: () => '/source',
+ },
+ } as unknown as App
+}
+
+function createMockEnv(filePathRelative = 'test.md'): MarkdownEnv {
+ return {
+ filePathRelative,
+ base: '/',
+ links: [],
+ importedFiles: [],
+ }
+}
+
+function createMarkdownWithMockRules() {
+ return MarkdownIt({ html: true }).use((md) => {
+ md.block.ruler.before('code', 'import_code', () => false)
+ md.renderer.rules.import_code = () => ''
+ })
+}
+
+describe('embedLinkPlugin', () => {
+ beforeEach(() => {
+ mockGlobSync.mockReset()
+ mockReadFileSync.mockReset()
+ })
+
+ // ==================== Asset Embedding ====================
+
+ describe('asset embedding', () => {
+ beforeEach(() => {
+ mockGlobSync.mockReturnValue([])
+ })
+
+ it('should render image embed', () => {
+ const md = createMarkdownWithMockRules().use(embedLinkPlugin, createMockApp())
+ const result = md.render('![[image.png]]')
+ expect(result).toContain('
{
+ const md = createMarkdownWithMockRules().use(embedLinkPlugin, createMockApp())
+ const result = md.render('![[image.png|300]]')
+ expect(result).toContain('
{
+ const md = createMarkdownWithMockRules().use(embedLinkPlugin, createMockApp())
+ const result = md.render('![[image.png|300x200]]')
+ expect(result).toContain('
{
+ const md = createMarkdownWithMockRules().use(embedLinkPlugin, createMockApp())
+ const result = md.render('![[audio.mp3]]')
+ expect(result).toContain('