* Calculate match score for an item
(
item: T,
queryWords: string[],
fields: string[]
)
| 75 | * Calculate match score for an item |
| 76 | */ |
| 77 | private calculateScore( |
| 78 | item: T, |
| 79 | queryWords: string[], |
| 80 | fields: string[] |
| 81 | ): number { |
| 82 | let totalScore = 0; |
| 83 | |
| 84 | for (const word of queryWords) { |
| 85 | let wordScore = 0; |
| 86 | |
| 87 | for (const field of fields) { |
| 88 | const value = (item as Record<string, unknown>)[field]; |
| 89 | if (!value) continue; |
| 90 | |
| 91 | const normalizedValue = String(value).toLowerCase(); |
| 92 | |
| 93 | // Exact match in title gets highest score |
| 94 | if (field === "title" && normalizedValue === word) { |
| 95 | wordScore = Math.max(wordScore, 100); |
| 96 | } |
| 97 | // Title starts with word |
| 98 | else if (field === "title" && normalizedValue.startsWith(word)) { |
| 99 | wordScore = Math.max(wordScore, 80); |
| 100 | } |
| 101 | // Title contains word |
| 102 | else if (field === "title" && normalizedValue.includes(word)) { |
| 103 | wordScore = Math.max(wordScore, 60); |
| 104 | } |
| 105 | // Description contains word |
| 106 | else if (field === "description" && normalizedValue.includes(word)) { |
| 107 | wordScore = Math.max(wordScore, 30); |
| 108 | } |
| 109 | // searchText (includes tags, tools, etc) contains word |
| 110 | else if (field === "searchText" && normalizedValue.includes(word)) { |
| 111 | wordScore = Math.max(wordScore, 20); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | totalScore += wordScore; |
| 116 | } |
| 117 | |
| 118 | // Bonus for matching all words |
| 119 | const matchesAllWords = queryWords.every((word) => |
| 120 | fields.some((field) => { |
| 121 | const value = (item as Record<string, unknown>)[field]; |
| 122 | return value && String(value).toLowerCase().includes(word); |
| 123 | }) |
| 124 | ); |
| 125 | |
| 126 | if (matchesAllWords && queryWords.length > 1) { |
| 127 | totalScore *= 1.5; |
| 128 | } |
| 129 | |
| 130 | return totalScore; |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Highlight matching text in a string |