fix: escape Obsidian alias links in tables

Why:
- Aliased wiki links generated inside Markdown table cells used a raw pipe separator.
- Markdown table formatters interpret that raw pipe as a column delimiter, which mangles table rows after automatic linking.

What:
- Escape pipe characters in links returned by Obsidian's generateMarkdownLink API when the replacement occurs inside a table.
- Add a regression test covering an existing target file with a frontmatter alias inside a Markdown table cell.

Closes: #35
This commit is contained in:
Kodai Nakamura 2026-05-30 09:40:36 +09:00
parent b5e09f9b75
commit f821e618f6
2 changed files with 83 additions and 1 deletions

View file

@ -0,0 +1,79 @@
import { describe, expect, it, vi } from "vitest"
import { buildCandidateTrieForTest } from "../replace-links/__tests__/test-helpers"
import { DEFAULT_SETTINGS } from "../settings/settings-info"
class MockTFile {
path: string
constructor(path: string) {
this.path = path
}
}
vi.mock("obsidian", () => ({
App: class {},
Editor: class {},
getFrontMatterInfo: () => ({ contentStart: 0 }),
MarkdownView: class {},
Notice: class {},
parseFrontMatterAliases: () => [],
Plugin: class {
app: unknown
constructor(app: unknown) {
this.app = app
}
},
PluginSettingTab: class {},
request: async () => ({}),
Setting: class {
setName() { return this }
setDesc() { return this }
setHeading() { return this }
addToggle() { return this }
addTextArea() { return this }
},
TFile: MockTFile,
}))
describe("AutomaticLinkerPlugin link generator", () => {
it("escapes alias separators generated by Obsidian inside markdown tables", async () => {
const { default: AutomaticLinkerPlugin } = await import("../main")
const settings = {
scoped: false,
baseDir: undefined,
ignoreCase: true,
}
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "notes/foo", aliases: ["bar"] }],
settings,
})
const targetFile = new MockTFile("notes/foo.md")
const app = {
fileManager: {
generateMarkdownLink: vi.fn(() => "[[notes/foo|bar]]"),
},
vault: {
getAbstractFileByPath: vi.fn((path: string) => {
if (path === "notes/foo.md") return targetFile
return null
}),
getConfig: vi.fn(),
},
}
const plugin = new AutomaticLinkerPlugin(app as never, {} as never)
plugin.settings = {
...DEFAULT_SETTINGS,
formatGitHubURLs: false,
formatJiraURLs: false,
replaceUrlWithTitle: false,
respectNewFileFolderPath: false,
}
;(plugin as unknown as { trie: typeof trie }).trie = trie
;(plugin as unknown as { candidateMap: typeof candidateMap }).candidateMap = candidateMap
const result = plugin.modifyLinks("| bar | x |\n| --- | --- |\n", "current-file.md")
expect(result).toBe("| [[notes/foo\\|bar]] | x |\n| --- | --- |\n")
})
})

View file

@ -77,7 +77,10 @@ export default class AutomaticLinkerPlugin extends Plugin {
if (targetFile instanceof TFile) {
// File exists, use Obsidian's generateMarkdownLink API
try {
const link = this.app.fileManager.generateMarkdownLink(targetFile, sourcePath, "", alias || "")
let link = this.app.fileManager.generateMarkdownLink(targetFile, sourcePath, "", alias || "")
if (isInTable && link.includes("|")) {
link = link.replace(/\|/g, "\\|")
}
return link
}
catch (error) {