mirror of
https://github.com/kdnk/obsidian-automatic-linker.git
synced 2026-07-22 12:00:30 +00:00
feat: remove aliases
This commit is contained in:
parent
93570e49ca
commit
9ab9cc5679
5 changed files with 413 additions and 0 deletions
|
|
@ -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);
|
||||
|
|
|
|||
353
src/replace-links/__tests__/replace-links.remove-alias.test.ts
Normal file
353
src/replace-links/__tests__/replace-links.remove-alias.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue