fix: support multi-segment partial paths in shortest link resolution

The 'shortest' strategy in transformLink() only matched by filename (last
segment), causing wiki-links like [[Species/Elf/Wood]] to fail resolution
when the target lived under a deeper path (e.g. Compendium/Species/Elf/Wood).

Add suffix-based matching for multi-segment partial paths and handle the
/index variant for folder targets whose suffix gets stripped by
transformInternalLink().
This commit is contained in:
saberzero1 2026-03-22 11:51:41 +01:00
parent 1bc315eb6e
commit 1ad4e4f2f6
No known key found for this signature in database
2 changed files with 104 additions and 0 deletions

View file

@ -237,7 +237,21 @@ export function transformLink(src: FullSlug, target: string, opts: TransformOpti
const [targetCanonical, targetAnchor] = splitAnchor(canonicalSlug);
if (opts.strategy === "shortest") {
const isMultiSegment = targetCanonical.includes("/");
const isFolderTarget = isFolderPath(targetSlug);
const matchingFileNames = opts.allSlugs.filter((slug) => {
if (isMultiSegment) {
// Multi-segment partial path: match by suffix
if (slug === targetCanonical || slug.endsWith("/" + targetCanonical)) {
return true;
}
// Folder targets have "/index" stripped — also match the "/index" variant
if (isFolderTarget) {
const withIndex = targetCanonical + "/index";
return slug === withIndex || slug.endsWith("/" + withIndex);
}
return false;
}
const parts = slug.split("/");
const fileName = parts.at(-1);
return targetCanonical === fileName;

View file

@ -247,6 +247,96 @@ describe("transformLink", () => {
});
});
describe("multi-segment partial path (shortest)", () => {
const multiSegSlugs = [
"Compendium/Species/Elf/Wood",
"Compendium/Species/Elf/index",
"Compendium/Species/Elf/Eladrin",
"Compendium/Spells/index",
"Compendium/Spells/Bane",
"Campaigns/Unnamed/People/index",
"index",
] as FullSlug[];
const opts: TransformOptions = { strategy: "shortest", allSlugs: multiSegSlugs };
it("resolves multi-segment path Species/Elf/Wood to unique match", () => {
const cur = "Campaigns/Unnamed/People/index" as FullSlug;
expect(transformLink(cur, "Species/Elf/Wood", opts)).toBe(
"../../../Compendium/Species/Elf/Wood",
);
});
it("resolves multi-segment path Species/Elf/index to folder page", () => {
const cur = "Campaigns/Unnamed/People/index" as FullSlug;
expect(transformLink(cur, "Species/Elf/index", opts)).toBe(
"../../../Compendium/Species/Elf/",
);
});
it("falls back to absolute when multi-segment path has no match", () => {
const cur = "index" as FullSlug;
expect(transformLink(cur, "Species/Elf/NoSuchPage", opts)).toBe("./Species/Elf/NoSuchPage");
});
it("resolves single-segment name that is unique", () => {
const cur = "index" as FullSlug;
expect(transformLink(cur, "Bane", opts)).toBe("./Compendium/Spells/Bane");
});
it("resolves exact full path as multi-segment match", () => {
const cur = "index" as FullSlug;
expect(transformLink(cur, "Compendium/Species/Elf/Eladrin", opts)).toBe(
"./Compendium/Species/Elf/Eladrin",
);
});
});
describe("multi-segment partial path (shortest)", () => {
const multiSegSlugs = [
"Compendium/Species/Elf/Wood",
"Compendium/Species/Elf/index",
"Compendium/Species/Elf/Eladrin",
"Compendium/Spells/index",
"Compendium/Spells/Bane",
"Campaigns/Unnamed/People/index",
"index",
] as FullSlug[];
const opts: TransformOptions = { strategy: "shortest", allSlugs: multiSegSlugs };
it("resolves multi-segment path Species/Elf/Wood to unique match", () => {
const cur = "Campaigns/Unnamed/People/index" as FullSlug;
expect(transformLink(cur, "Species/Elf/Wood", opts)).toBe(
"../../../Compendium/Species/Elf/Wood",
);
});
it("resolves multi-segment path Species/Elf/index to folder page", () => {
const cur = "Campaigns/Unnamed/People/index" as FullSlug;
expect(transformLink(cur, "Species/Elf/index", opts)).toBe(
"../../../Compendium/Species/Elf/",
);
});
it("falls back to absolute when multi-segment path has no match", () => {
const cur = "index" as FullSlug;
expect(transformLink(cur, "Species/Elf/NoSuchPage", opts)).toBe("./Species/Elf/NoSuchPage");
});
it("resolves single-segment name that is unique", () => {
const cur = "index" as FullSlug;
expect(transformLink(cur, "Bane", opts)).toBe("./Compendium/Spells/Bane");
});
it("resolves exact full path as multi-segment match", () => {
const cur = "index" as FullSlug;
expect(transformLink(cur, "Compendium/Species/Elf/Eladrin", opts)).toBe(
"./Compendium/Species/Elf/Eladrin",
);
});
});
describe("folder page bug regression", () => {
const spellSlugs = [
"Compendium/Spells/index",