mirror of
https://github.com/kdnk/obsidian-automatic-linker.git
synced 2026-07-22 05:37:46 +00:00
fix: Fix ignore case and multi-word fallback in replace-links
This commit is contained in:
parent
368818b935
commit
e8945fa7f4
1 changed files with 118 additions and 105 deletions
|
|
@ -437,10 +437,11 @@ const processStandardText = (
|
||||||
// Use the explicit alias from the canonical path
|
// Use the explicit alias from the canonical path
|
||||||
result += `[[${normalizedPath}|${alias}]]`;
|
result += `[[${normalizedPath}|${alias}]]`;
|
||||||
} else if (normalizedPath.includes("/")) {
|
} else if (normalizedPath.includes("/")) {
|
||||||
// Namespace exists, use the last part of the normalized path as display text
|
// Namespace exists, no explicit alias.
|
||||||
// Tests expect the last part of the *path*, not the original text's potential multi-word form.
|
// Use original matched text for display if ignoreCase is true, otherwise use the last part of the path.
|
||||||
const segments = normalizedPath.split("/");
|
const displayText = settings.ignoreCase
|
||||||
const displayText = segments[segments.length - 1];
|
? originalMatchedText
|
||||||
|
: normalizedPath.split("/").pop() || originalMatchedText; // Fallback to original text if split fails
|
||||||
result += `[[${normalizedPath}|${displayText}]]`;
|
result += `[[${normalizedPath}|${displayText}]]`;
|
||||||
} else {
|
} else {
|
||||||
// No namespace, no explicit alias. Use the original matched text.
|
// No namespace, no explicit alias. Use the original matched text.
|
||||||
|
|
@ -452,115 +453,127 @@ const processStandardText = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: if no candidate was found via the trie.
|
// Fallback: if no candidate was found via the trie, try multi-word fallback lookup.
|
||||||
if (settings.namespaceResolution) {
|
if (settings.namespaceResolution) {
|
||||||
const fallbackMatch = text.slice(i).match(FALLBACK_REGEX);
|
let longestMatch: {
|
||||||
if (fallbackMatch) {
|
word: string;
|
||||||
const word = settings.ignoreCase
|
length: number;
|
||||||
? fallbackMatch[1].toLowerCase()
|
key: string;
|
||||||
: fallbackMatch[1];
|
candidateList: Array<[string, CandidateData]>;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
// Skip date formats
|
// Iterate through potential multi-word sequences starting from i
|
||||||
if (
|
for (let k = i; k < text.length; k++) {
|
||||||
settings.ignoreDateFormats &&
|
const potentialMatch = text.substring(i, k + 1);
|
||||||
DATE_FORMAT_REGEX.test(word)
|
|
||||||
) {
|
|
||||||
result += word;
|
|
||||||
i += word.length;
|
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip dates and month notes
|
|
||||||
if (/^\d{2}$/.test(word) && /\d{4}-\d{2}-$/.test(result)) {
|
|
||||||
result += text[i];
|
|
||||||
i++;
|
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMonthNote(word)) {
|
|
||||||
result += word;
|
|
||||||
i += word.length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try fallback lookup
|
|
||||||
const searchWord = settings.ignoreCase
|
const searchWord = settings.ignoreCase
|
||||||
? word.toLowerCase()
|
? potentialMatch.toLowerCase()
|
||||||
: word;
|
: potentialMatch;
|
||||||
const candidateList = fallbackIndex.get(searchWord);
|
|
||||||
|
|
||||||
|
// Basic boundary check: next char should be a boundary if not end of text
|
||||||
|
const nextChar = text[k + 1];
|
||||||
|
if (!isWordBoundary(nextChar)) {
|
||||||
|
// If the potential match itself isn't in the index, continue extending
|
||||||
|
if (!fallbackIndex.has(searchWord)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If it is in the index, but the next char isn't a boundary, it's not a valid match end here.
|
||||||
|
// But a shorter version might have been valid, so we don't break yet.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const candidateList = fallbackIndex.get(searchWord);
|
||||||
if (candidateList) {
|
if (candidateList) {
|
||||||
// Filter candidates to comply with namespace restrictions
|
// Check word boundary at the beginning
|
||||||
const filteredCandidates = candidateList.filter(
|
const prevChar = text[i - 1];
|
||||||
([, data]) =>
|
if (!isWordBoundary(prevChar)) {
|
||||||
!(
|
// If the start isn't a word boundary, this isn't a valid match.
|
||||||
data.restrictNamespace &&
|
// However, a shorter match starting later might be, so just continue the outer loop.
|
||||||
data.namespace !== currentNamespace
|
// We break the inner loop (k) because extending this further won't help.
|
||||||
),
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Skip date formats
|
||||||
|
if (
|
||||||
|
settings.ignoreDateFormats &&
|
||||||
|
DATE_FORMAT_REGEX.test(potentialMatch)
|
||||||
|
) {
|
||||||
|
continue; // Try longer match
|
||||||
|
}
|
||||||
|
// Skip month notes
|
||||||
|
if (isMonthNote(potentialMatch)) {
|
||||||
|
continue; // Try longer match
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found a potential candidate in the fallback index
|
||||||
|
longestMatch = {
|
||||||
|
word: potentialMatch,
|
||||||
|
length: potentialMatch.length,
|
||||||
|
key: searchWord,
|
||||||
|
candidateList: candidateList,
|
||||||
|
};
|
||||||
|
// Continue checking for even longer matches
|
||||||
|
} else if (longestMatch && k > i + longestMatch.length -1) {
|
||||||
|
// If we had a match but the current longer string doesn't match,
|
||||||
|
// stop extending for this starting position 'i'.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the longest valid match found
|
||||||
|
if (longestMatch) {
|
||||||
|
// Filter candidates based on namespace restrictions
|
||||||
|
const filteredCandidates = longestMatch.candidateList.filter(
|
||||||
|
([, data]) =>
|
||||||
|
!(
|
||||||
|
data.restrictNamespace &&
|
||||||
|
data.namespace !== currentNamespace
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let bestCandidateData: CandidateData | null = null;
|
||||||
|
|
||||||
|
if (filteredCandidates.length === 1) {
|
||||||
|
bestCandidateData = filteredCandidates[0][1];
|
||||||
|
} else if (filteredCandidates.length > 1) {
|
||||||
|
const bestCandidateResult = findBestCandidateInSameNamespace(
|
||||||
|
filteredCandidates,
|
||||||
|
filePath,
|
||||||
|
settings,
|
||||||
|
);
|
||||||
|
if (bestCandidateResult) {
|
||||||
|
bestCandidateData = bestCandidateResult[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestCandidateData) {
|
||||||
|
// Found a valid candidate through fallback
|
||||||
|
const { linkPath, alias, hasAlias } = extractLinkParts(
|
||||||
|
bestCandidateData.canonical,
|
||||||
|
);
|
||||||
|
const normalizedPath = normalizeCanonicalPath(
|
||||||
|
linkPath,
|
||||||
|
settings.baseDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (filteredCandidates.length === 1) {
|
// Format the link
|
||||||
// Single match - straightforward case
|
const originalMatchedWord = longestMatch.word; // Use the actual matched word
|
||||||
const candidateData = filteredCandidates[0][1];
|
if (hasAlias) {
|
||||||
const { linkPath, alias, hasAlias } = extractLinkParts(
|
result += `[[${normalizedPath}|${alias}]]`;
|
||||||
candidateData.canonical,
|
} else if (normalizedPath.includes("/")) {
|
||||||
);
|
// Namespace exists, no explicit alias.
|
||||||
const normalizedPath = normalizeCanonicalPath(
|
// Use original matched word for display if ignoreCase is true, otherwise use the last part of the path.
|
||||||
linkPath,
|
const displayText = settings.ignoreCase
|
||||||
settings.baseDir,
|
? originalMatchedWord
|
||||||
);
|
: normalizedPath.split("/").pop() || originalMatchedWord; // Fallback to original word if split fails
|
||||||
|
result += `[[${normalizedPath}|${displayText}]]`;
|
||||||
// Format the link
|
} else {
|
||||||
const originalMatchedWord = text.substring(i, i + word.length);
|
// No namespace, no explicit alias. Use the original matched word.
|
||||||
if (hasAlias) {
|
result += `[[${originalMatchedWord}]]`;
|
||||||
result += `[[${normalizedPath}|${alias}]]`;
|
|
||||||
} else if (normalizedPath.includes("/")) {
|
|
||||||
// Namespace exists, use the last part of the normalized path as display text
|
|
||||||
const segments = normalizedPath.split("/");
|
|
||||||
const displayText = segments[segments.length - 1];
|
|
||||||
result += `[[${normalizedPath}|${displayText}]]`;
|
|
||||||
} else {
|
|
||||||
// No namespace, no explicit alias. Use the original matched word.
|
|
||||||
result += `[[${originalMatchedWord}]]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
i += word.length;
|
|
||||||
continue outer;
|
|
||||||
} else if (filteredCandidates.length > 1) {
|
|
||||||
// Multiple matches - find the best candidate
|
|
||||||
const bestCandidate = findBestCandidateInSameNamespace(
|
|
||||||
filteredCandidates,
|
|
||||||
filePath,
|
|
||||||
settings,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (bestCandidate) {
|
|
||||||
const candidateData = bestCandidate[1];
|
|
||||||
const { linkPath, alias, hasAlias } =
|
|
||||||
extractLinkParts(candidateData.canonical);
|
|
||||||
const normalizedPath = normalizeCanonicalPath(
|
|
||||||
linkPath,
|
|
||||||
settings.baseDir,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Format the link
|
|
||||||
const originalMatchedWord = text.substring(i, i + word.length);
|
|
||||||
if (hasAlias) {
|
|
||||||
result += `[[${normalizedPath}|${alias}]]`;
|
|
||||||
} else if (normalizedPath.includes("/")) {
|
|
||||||
// Namespace exists, use the last part of the normalized path as display text
|
|
||||||
const segments = normalizedPath.split("/");
|
|
||||||
const displayText = segments[segments.length - 1];
|
|
||||||
result += `[[${normalizedPath}|${displayText}]]`;
|
|
||||||
} else {
|
|
||||||
// No namespace, no explicit alias. Use the original matched word.
|
|
||||||
result += `[[${originalMatchedWord}]]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
i += word.length;
|
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i += longestMatch.length; // Advance index by the length of the matched word
|
||||||
|
continue outer; // Continue processing from the new index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue