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
|
||||
result += `[[${normalizedPath}|${alias}]]`;
|
||||
} else if (normalizedPath.includes("/")) {
|
||||
// Namespace exists, use the last part of the normalized path as display text
|
||||
// Tests expect the last part of the *path*, not the original text's potential multi-word form.
|
||||
const segments = normalizedPath.split("/");
|
||||
const displayText = segments[segments.length - 1];
|
||||
// Namespace exists, no explicit alias.
|
||||
// Use original matched text for display if ignoreCase is true, otherwise use the last part of the path.
|
||||
const displayText = settings.ignoreCase
|
||||
? originalMatchedText
|
||||
: normalizedPath.split("/").pop() || originalMatchedText; // Fallback to original text if split fails
|
||||
result += `[[${normalizedPath}|${displayText}]]`;
|
||||
} else {
|
||||
// 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) {
|
||||
const fallbackMatch = text.slice(i).match(FALLBACK_REGEX);
|
||||
if (fallbackMatch) {
|
||||
const word = settings.ignoreCase
|
||||
? fallbackMatch[1].toLowerCase()
|
||||
: fallbackMatch[1];
|
||||
let longestMatch: {
|
||||
word: string;
|
||||
length: number;
|
||||
key: string;
|
||||
candidateList: Array<[string, CandidateData]>;
|
||||
} | null = null;
|
||||
|
||||
// Skip date formats
|
||||
if (
|
||||
settings.ignoreDateFormats &&
|
||||
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
|
||||
// Iterate through potential multi-word sequences starting from i
|
||||
for (let k = i; k < text.length; k++) {
|
||||
const potentialMatch = text.substring(i, k + 1);
|
||||
const searchWord = settings.ignoreCase
|
||||
? word.toLowerCase()
|
||||
: word;
|
||||
const candidateList = fallbackIndex.get(searchWord);
|
||||
? potentialMatch.toLowerCase()
|
||||
: potentialMatch;
|
||||
|
||||
// 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) {
|
||||
// Filter candidates to comply with namespace restrictions
|
||||
const filteredCandidates = candidateList.filter(
|
||||
([, data]) =>
|
||||
!(
|
||||
data.restrictNamespace &&
|
||||
data.namespace !== currentNamespace
|
||||
),
|
||||
// Check word boundary at the beginning
|
||||
const prevChar = text[i - 1];
|
||||
if (!isWordBoundary(prevChar)) {
|
||||
// If the start isn't a word boundary, this isn't a valid match.
|
||||
// However, a shorter match starting later might be, so just continue the outer loop.
|
||||
// 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) {
|
||||
// Single match - straightforward case
|
||||
const candidateData = filteredCandidates[0][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;
|
||||
} 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;
|
||||
}
|
||||
// Format the link
|
||||
const originalMatchedWord = longestMatch.word; // Use the actual matched word
|
||||
if (hasAlias) {
|
||||
result += `[[${normalizedPath}|${alias}]]`;
|
||||
} else if (normalizedPath.includes("/")) {
|
||||
// Namespace exists, no explicit alias.
|
||||
// Use original matched word for display if ignoreCase is true, otherwise use the last part of the path.
|
||||
const displayText = settings.ignoreCase
|
||||
? originalMatchedWord
|
||||
: normalizedPath.split("/").pop() || originalMatchedWord; // Fallback to original word if split fails
|
||||
result += `[[${normalizedPath}|${displayText}]]`;
|
||||
} else {
|
||||
// No namespace, no explicit alias. Use the original matched word.
|
||||
result += `[[${originalMatchedWord}]]`;
|
||||
}
|
||||
|
||||
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