feat: remove aliases

This commit is contained in:
Kodai Nakamura 2025-11-27 23:47:07 +09:00
parent 93570e49ca
commit 9ab9cc5679
5 changed files with 413 additions and 0 deletions

View file

@ -142,6 +142,7 @@ export default class AutomaticLinkerPlugin extends Plugin {
ignoreDateFormats: this.settings.ignoreDateFormats,
ignoreCase: this.settings.ignoreCase,
preventSelfLinking: this.settings.preventSelfLinking,
removeAliasInDirs: this.settings.removeAliasInDirs,
},
});
@ -203,6 +204,7 @@ export default class AutomaticLinkerPlugin extends Plugin {
ignoreDateFormats: this.settings.ignoreDateFormats,
ignoreCase: this.settings.ignoreCase,
preventSelfLinking: this.settings.preventSelfLinking,
removeAliasInDirs: this.settings.removeAliasInDirs,
},
});
cm.replaceSelection(updatedText);

View file

@ -0,0 +1,353 @@
import { describe, expect, it } from "vitest";
import { replaceLinks } from "../replace-links";
import { buildCandidateTrieForTest } from "./test-helpers";
describe("replaceLinks - directory-specific alias removal", () => {
describe("basic alias removal", () => {
it("removes alias for links in specified directory", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[dir/xxx]]");
});
it("keeps alias for links not in specified directory", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "other/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[other/xxx|xxx]]");
});
it("removes alias for links in subdirectories", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir/subdir/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[dir/subdir/xxx]]");
});
});
describe("multiple directories", () => {
it("removes alias for links in any specified directory", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [
{ path: "dir1/xxx" },
{ path: "dir2/yyy" },
{ path: "other/zzz" },
],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "xxx yyy zzz",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
removeAliasInDirs: ["dir1", "dir2"],
},
});
expect(result).toBe("[[dir1/xxx]] [[dir2/yyy]] [[other/zzz|zzz]]");
});
});
describe("with baseDir", () => {
it("removes alias based on normalized path with baseDir", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "pages/dir/xxx" }],
settings: {
restrictNamespace: false,
baseDir: "pages",
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "pages/current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
baseDir: "pages",
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[dir/xxx]]");
});
it("keeps alias when path starts with baseDir but not with specified dir", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "pages/other/xxx" }],
settings: {
restrictNamespace: false,
baseDir: "pages",
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "pages/current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
baseDir: "pages",
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[other/xxx|xxx]]");
});
});
describe("with frontmatter aliases", () => {
it("removes alias from frontmatter alias in specified directory", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir/HelloWorld", aliases: ["HW"] }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "HW",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[dir/HelloWorld]]");
});
it("keeps alias from frontmatter alias not in specified directory", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "other/HelloWorld", aliases: ["HW"] }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "HW",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[other/HelloWorld|HW]]");
});
});
describe("edge cases", () => {
it("handles empty removeAliasInDirs array", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
removeAliasInDirs: [],
},
});
expect(result).toBe("[[dir/xxx|xxx]]");
});
it("handles undefined removeAliasInDirs", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
},
});
expect(result).toBe("[[dir/xxx|xxx]]");
});
it("handles links without alias in specified directory", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "dir/xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("[[dir/xxx]]");
});
it("works with ignoreCase option", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "Dir/Xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
ignoreCase: true,
},
});
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
removeAliasInDirs: ["Dir"],
ignoreCase: true,
},
});
expect(result).toBe("[[Dir/Xxx]]");
});
});
describe("in markdown tables", () => {
it("removes alias in markdown tables", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
const result = replaceLinks({
body: "| xxx |",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
removeAliasInDirs: ["dir"],
},
});
expect(result).toBe("| [[dir/xxx]] |");
});
});
describe("performance", () => {
it("handles large number of directories efficiently", () => {
const { candidateMap, trie } = buildCandidateTrieForTest({
files: [{ path: "dir50/xxx" }],
settings: {
restrictNamespace: false,
baseDir: undefined,
},
});
// Create array of 100 directories
const manyDirs = Array.from({ length: 100 }, (_, i) => `dir${i}`);
const startTime = performance.now();
const result = replaceLinks({
body: "xxx",
linkResolverContext: {
filePath: "current",
trie,
candidateMap,
},
settings: {
namespaceResolution: true,
removeAliasInDirs: manyDirs,
},
});
const endTime = performance.now();
expect(result).toBe("[[dir50/xxx]]");
// Should complete in reasonable time (less than 10ms)
expect(endTime - startTime).toBeLessThan(10);
});
});
});

View file

@ -14,6 +14,7 @@ export interface ReplaceLinksSettings {
ignoreDateFormats?: boolean;
ignoreCase?: boolean;
preventSelfLinking?: boolean;
removeAliasInDirs?: string[];
}
export interface ReplaceLinksOptions {
@ -165,6 +166,27 @@ const isSelfLink = (
return normalizedLinkPath === normalizedCurrentPath;
};
// Helper function to check if a path should have its alias removed
const shouldRemoveAlias = (normalizedPath: string, removeAliasInDirs?: string[]): boolean => {
if (!removeAliasInDirs || removeAliasInDirs.length === 0) {
return false;
}
// Early return for paths without slashes
if (!normalizedPath.includes("/")) {
return false;
}
// Check if the normalized path starts with any of the specified directories
for (const dir of removeAliasInDirs) {
if (normalizedPath === dir || normalizedPath.startsWith(dir + "/")) {
return true;
}
}
return false;
};
// Link Content Creation
const createLinkContent = (
candidateData: CandidateData,
@ -174,11 +196,23 @@ const createLinkContent = (
const { linkPath, alias, hasAlias } = extractLinkParts(candidateData.canonical);
const normalizedPath = normalizeCanonicalPath(linkPath, settings.baseDir);
// Check if alias should be removed for this directory
const removeAlias = shouldRemoveAlias(normalizedPath, settings.removeAliasInDirs);
if (hasAlias) {
// If alias removal is enabled for this directory, return path without alias
if (removeAlias) {
return normalizedPath;
}
return `${normalizedPath}|${alias}`;
}
if (normalizedPath.includes("/")) {
// If alias removal is enabled for this directory, return path without alias
if (removeAlias) {
return normalizedPath;
}
// For paths with slashes, use the last segment as the display text
const lastSegment = normalizedPath.split("/").pop() || originalMatchedText;

View file

@ -17,6 +17,7 @@ export type AutomaticLinkerSettings = {
replaceUrlWithTitleIgnoreDomains: string[]; // List of domains to ignore when replacing URLs with titles
excludeDirsFromAutoLinking: string[]; // Optional: List of directories to exclude from auto-linking
preventSelfLinking: boolean; // Prevent linking text to its own file
removeAliasInDirs: string[]; // Remove aliases for links in specified directories
};
export const DEFAULT_SETTINGS: AutomaticLinkerSettings = {
@ -38,4 +39,5 @@ export const DEFAULT_SETTINGS: AutomaticLinkerSettings = {
replaceUrlWithTitleIgnoreDomains: [],
excludeDirsFromAutoLinking: [], // Default: no excluded directories
preventSelfLinking: false, // Default: allow self-linking (backward compatibility)
removeAliasInDirs: [], // Default: no directories for alias removal
};

View file

@ -162,6 +162,28 @@ export class AutomaticLinkerPluginSettingsTab extends PluginSettingTab {
});
});
// Remove aliases for links in specified directories
new Setting(containerEl)
.setName("Remove aliases in directories")
.setDesc(
"Directories where link aliases should be removed, one per line (e.g. 'dir' will convert [[dir/xxx|yyy]] to [[dir/xxx]]). This affects both auto-generated aliases and frontmatter aliases.",
)
.addTextArea((text) => {
text.setPlaceholder("dir1\ndir2/subdir")
.setValue(
this.plugin.settings.removeAliasInDirs.join("\n"),
)
.onChange(async (value) => {
// Split by newlines and filter out empty lines
const dirs = value
.split("\n")
.map((dir) => dir.trim())
.filter(Boolean);
this.plugin.settings.removeAliasInDirs = dirs;
await this.plugin.saveData(this.plugin.settings);
});
});
new Setting(containerEl)
.setName("URL Formatting for GitHub")
.setHeading();