From 6d198240ea2b9d37de3d3e84b33d30d8976c6e6d Mon Sep 17 00:00:00 2001 From: newtextdoc1111 Date: Tue, 6 May 2025 20:39:08 +0900 Subject: [PATCH] refactor: enhance tag matching logic in findCompletionCandidates function to prioritize exact matches --- web/js/autocomplete.js | 96 +++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/web/js/autocomplete.js b/web/js/autocomplete.js index 62b148d..a635ba1 100644 --- a/web/js/autocomplete.js +++ b/web/js/autocomplete.js @@ -16,7 +16,6 @@ class AutocompleteUI { this.element.id = 'autocomplete-plus-list'; this.element.style.display = 'none'; // Initially hidden this.element.style.position = 'absolute'; // Position near the input - this.element.style.zIndex = '10000'; // Ensure it's on top this.element.style.borderCollapse = 'collapse'; // Optional: for table styling this.element.style.width = 'auto'; // Adjust width automatically @@ -537,7 +536,8 @@ function findCompletionCandidates(query) { } const lowerQuery = query.toLowerCase(); - const candidates = []; + const exactMatches = []; // Array for exact matches (will be displayed first) + const partialMatches = []; // Array for partial matches (will be displayed after exact matches) const addedTags = new Set(); // Keep track of added tags to avoid duplicates // Generate Hiragana/Katakana variations if applicable @@ -554,56 +554,99 @@ function findCompletionCandidates(query) { // Search in sortedTags (already sorted by count) for (const tagData of autoCompleteData.sortedTags) { let matched = false; + let isExactMatch = false; let matchedAlias = null; - // Check primary tag against all variations + // Ensure tagData.tag is treated as lowercase for comparison + const lowerTag = tagData.tag.toLowerCase(); + + // Check primary tag against all variations for exact match first for (const variation of queryVariations) { - // Ensure tagData.tag is treated as lowercase for comparison - const lowerTag = tagData.tag.toLowerCase(); - if (lowerTag.includes(variation)) { - matched = true; - break; - } else if (lowerTag.replace(/[\-_]/g, '').includes(variation)) { - // Try to match with underscores removed + if (lowerTag === variation || lowerTag === variation.replace(/[\-_\s]/g, '')) { + isExactMatch = true; matched = true; break; } } + + // If not an exact match, check for partial matches in the tag + if (!isExactMatch) { + for (const variation of queryVariations) { + if (lowerTag.includes(variation)) { + matched = true; + break; + } else if (lowerTag.replace(/[\-_]/g, '').includes(variation)) { + // Try to match with underscores/hyphens removed + matched = true; + break; + } + } + } // If primary tag didn't match, check aliases against all variations if (!matched && tagData.alias && Array.isArray(tagData.alias) && tagData.alias.length > 0) { for (const alias of tagData.alias) { const lowerAlias = alias.toLowerCase(); + + // Check for exact matches in aliases first for (const variation of queryVariations) { - if (lowerAlias.includes(variation)) { + if (lowerAlias === variation) { + isExactMatch = true; matched = true; - matchedAlias = alias; // Store the alias that matched + matchedAlias = alias; break; } } + + // If not an exact match in alias, check for partial matches + if (!isExactMatch) { + for (const variation of queryVariations) { + if (lowerAlias.includes(variation)) { + matched = true; + matchedAlias = alias; + break; + } + } + } + if (matched) break; // Stop checking aliases for this tag if one matched } } // Add candidate if matched and not already added if (matched && !addedTags.has(tagData.tag)) { - candidates.push({ + const candidateItem = { tag: tagData.tag, count: tagData.count, - alias: tagData.alias // Add alias property only if matched via alias - }); + alias: tagData.alias + }; + + // Add to exact matches or partial matches based on match type + if (isExactMatch) { + exactMatches.push(candidateItem); + } else { + partialMatches.push(candidateItem); + } + addedTags.add(tagData.tag); - if (candidates.length >= settingValues.maxSuggestions) { - // 早期リターンする場合もログを出力 - const endTime = performance.now(); - const duration = endTime - startTime; - // console.debug(`[Autocomplete-Plus] Search for "${query}" took ${duration.toFixed(2)}ms. Found ${candidates.length} candidates (max reached).`); - return candidates; // Early exit + + // Check if we've reached the maximum suggestions limit combining both arrays + if (exactMatches.length + partialMatches.length >= settingValues.maxSuggestions) { + // Return the combined results, prioritizing exact matches + const result = [...exactMatches, ...partialMatches].slice(0, settingValues.maxSuggestions); + + // const endTime = performance.now(); + // const duration = endTime - startTime; + // console.debug(`[Autocomplete-Plus] Search for "${query}" took ${duration.toFixed(2)}ms. Found ${result.length} candidates (max reached).`); + + return result; // Early exit } } } - // 検索終了時間を記録し、コンソールに出力 + // Combine results, with exact matches first + const candidates = [...exactMatches, ...partialMatches]; + // const endTime = performance.now(); // const duration = endTime - startTime; // console.debug(`[Autocomplete-Plus] Search for "${query}" took ${duration.toFixed(2)}ms. Found ${candidates.length} candidates.`); @@ -732,13 +775,8 @@ export class AutocompleteEventHandler { handleFocus(event) { if (!settingValues.enabled) return; - // Potentially show suggestions immediately on focus? - // For now, only show on input - if (!autocompleteUI) { - autocompleteUI = new AutocompleteUI(); - } - // Maybe check if there's already text and show suggestions? - // handleInput(event); // Trigger check immediately + + // Add code if needed for focus events } handleBlur(event) {