mirror of
https://github.com/kdnk/obsidian-automatic-linker.git
synced 2026-07-22 05:37:46 +00:00
fix: restore wikilink protection for URL replacement
Why: Linear URLs were being rewritten inside existing wikilinks, and protected markdown segments were not treating linear:// URLs as URLs. That regressed helper compatibility and selection safety. What: - Skip raw URL replacement matches that fall inside wikilinks while keeping the helper direct and linear-aware. - Extend protected markdown URL segmentation to include linear://. - Add regression tests for raw replacement, segmentation, and selection formatting.
This commit is contained in:
parent
3ddcf08538
commit
cf5562f0cb
5 changed files with 102 additions and 2 deletions
|
|
@ -175,4 +175,34 @@ describe("formatMarkdownSelection", () => {
|
|||
"[[notes/TypeScript|TypeScript]] https://github.com/openai/openai/issues/1",
|
||||
)
|
||||
})
|
||||
|
||||
it("leaves linear URLs unchanged when a linear note exists", () => {
|
||||
const { candidateMap, trie } = buildCandidateTrieForTest({
|
||||
files: [
|
||||
{ path: "notes/linear" },
|
||||
{ path: "notes/TypeScript" },
|
||||
],
|
||||
settings: {
|
||||
scoped: false,
|
||||
baseDir: undefined,
|
||||
ignoreCase: true,
|
||||
},
|
||||
})
|
||||
|
||||
const result = formatMarkdownSelection({
|
||||
body: "linear://workspace/issue/ACME-123",
|
||||
filePath: "current-file.md",
|
||||
settings: {
|
||||
...DEFAULT_SETTINGS,
|
||||
formatGitHubURLs: true,
|
||||
formatJiraURLs: true,
|
||||
formatLinearURLs: true,
|
||||
replaceUrlWithTitle: true,
|
||||
ignoreCase: true,
|
||||
},
|
||||
candidateIndex: { candidateMap, trie },
|
||||
})
|
||||
|
||||
expect(result).toBe("linear://workspace/issue/ACME-123")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -62,6 +62,23 @@ describe("segmentMarkdown", () => {
|
|||
])
|
||||
expect(segments.map(segment => segment.text).join("")).toBe(text)
|
||||
})
|
||||
|
||||
it("protects linear URLs when requested", () => {
|
||||
const text = "linear://workspace/issue/ACME-123"
|
||||
const segments = segmentMarkdown(text, {
|
||||
protectUrls: true,
|
||||
})
|
||||
|
||||
expect(segments).toEqual([
|
||||
{
|
||||
kind: "protected",
|
||||
protectedKind: "url",
|
||||
start: 0,
|
||||
end: text.length,
|
||||
text,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("mapMarkdownProse", () => {
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ const buildProtectedPattern = (protectUrls: boolean): RegExp => {
|
|||
]
|
||||
|
||||
if (protectUrls) {
|
||||
parts.push("https?:\\/\\/[^\\s]+")
|
||||
parts.push("(?:https?:\\/\\/|linear:\\/\\/)[^\\s]+")
|
||||
}
|
||||
|
||||
return new RegExp(`(${parts.join("|")})`, "g")
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@ describe("replace-urls", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("does not rewrite https URLs inside wikilinks", () => {
|
||||
const input = "[[https://github.com/kdnk/obsidian-automatic-linker]]"
|
||||
|
||||
expect(
|
||||
replaceURLs(input, baseSettings, formatGitHubURL),
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
it("should replace linear protocol URLs", () => {
|
||||
const input = "linear://workspace/issue/ACME-123"
|
||||
const expected
|
||||
|
|
@ -39,6 +47,21 @@ describe("replace-urls", () => {
|
|||
).toBe(expected)
|
||||
})
|
||||
|
||||
it("does not rewrite linear protocol URLs inside wikilinks", () => {
|
||||
const input = "[[linear://workspace/issue/ACME-123]]"
|
||||
|
||||
expect(
|
||||
replaceURLs(
|
||||
input,
|
||||
{
|
||||
...baseSettings,
|
||||
formatLinearURLs: true,
|
||||
},
|
||||
formatLinearURL,
|
||||
),
|
||||
).toBe(input)
|
||||
})
|
||||
|
||||
it("keeps raw helper semantics inside Markdown links and angle autolinks", () => {
|
||||
const input = [
|
||||
"[label](https://github.com/kdnk/obsidian-automatic-linker)",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,39 @@
|
|||
import { AutomaticLinkerSettings } from "../settings/settings-info"
|
||||
|
||||
const URL_PATTERN = /(?:https?:\/\/|linear:\/\/)[^\s<>\]]+/g
|
||||
const WIKILINK_PATTERN = /\[\[[\s\S]*?\]\]/g
|
||||
|
||||
const collectWikilinkRanges = (text: string): Array<{ start: number, end: number }> => {
|
||||
const ranges: Array<{ start: number, end: number }> = []
|
||||
let match: RegExpExecArray | null
|
||||
|
||||
while ((match = WIKILINK_PATTERN.exec(text)) !== null) {
|
||||
ranges.push({
|
||||
start: match.index,
|
||||
end: match.index + match[0].length,
|
||||
})
|
||||
}
|
||||
|
||||
return ranges
|
||||
}
|
||||
|
||||
const isInsideRange = (
|
||||
index: number,
|
||||
ranges: Array<{ start: number, end: number }>,
|
||||
): boolean => ranges.some(range => index >= range.start && index < range.end)
|
||||
|
||||
export const replaceURLs = (
|
||||
fileContent: string,
|
||||
settings: AutomaticLinkerSettings,
|
||||
formatter: (url: string, settings: AutomaticLinkerSettings) => string,
|
||||
) => fileContent.replace(URL_PATTERN, match => formatter(match, settings))
|
||||
) => {
|
||||
const wikilinkRanges = collectWikilinkRanges(fileContent)
|
||||
|
||||
return fileContent.replace(URL_PATTERN, (match, offset) => {
|
||||
if (isInsideRange(offset, wikilinkRanges)) {
|
||||
return match
|
||||
}
|
||||
|
||||
return formatter(match, settings)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue