From 49981cdf6818a2a4769de9c1d4e2757c18639758 Mon Sep 17 00:00:00 2001 From: cade Date: Fri, 10 Jul 2026 12:31:03 +0800 Subject: [PATCH] Searching now bumps matches based on exact match ranking --- .../composables/ModFiltersComposable.ts | 36 +++++++++++++++++-- src/utils/SearchUtils.ts | 19 ++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/components/composables/ModFiltersComposable.ts b/src/components/composables/ModFiltersComposable.ts index 5d13e029a..e88d3fef1 100644 --- a/src/components/composables/ModFiltersComposable.ts +++ b/src/components/composables/ModFiltersComposable.ts @@ -5,7 +5,7 @@ import SortingStyle from '../../model/enums/SortingStyle'; import ThunderstoreMod from '../../model/ThunderstoreMod'; import { getStore } from '../../providers/generic/store/StoreProvider'; import { State } from '../../store'; -import SearchUtils from '../../utils/SearchUtils'; +import SearchUtils, { ExactSearchMatchRank } from '../../utils/SearchUtils'; const store = getStore(); @@ -15,7 +15,7 @@ const filteredMods = ref([]); const filteredModCount = ref(0); function runFilter() { - let result = sortedMods.value; + let result = sortedMods.value as ThunderstoreMod[]; const searchKeys = SearchUtils.makeKeys(searchFilter.value); if (searchKeys.length > 0) { @@ -44,10 +44,42 @@ function runFilter() { result = result.filter(x => filterAll.every(c => x.getCategories().includes(c))); } + result = bumpExactMatches(result, searchFilter.value.trim().toLowerCase()); + filteredMods.value = result; filteredModCount.value = result.length; } +function bumpExactMatches(mods: ThunderstoreMod[], query: string): ThunderstoreMod[] { + if (query.length === 0) { + return mods; + } + + const nameMatches: ThunderstoreMod[] = []; + const authorMatches: ThunderstoreMod[] = []; + const others: ThunderstoreMod[] = []; + + for (const mod of mods) { + const rank = SearchUtils.getExactMatchRank(query, mod.getName(), mod.getFullName(), mod.getOwner()); + switch (rank) { + case ExactSearchMatchRank.NAME: + nameMatches.push(mod); + break; + case ExactSearchMatchRank.AUTHOR: + authorMatches.push(mod); + break; + default: + others.push(mod); + break; + } + } + + if (nameMatches.length === 0 && authorMatches.length === 0) { + return mods; + } + return [...nameMatches, ...authorMatches, ...others]; +} + function runSort() { const sortDescending = store.state.modFilters.sortDirection === SortDirection.STANDARD; const sorted = [...store.state.tsMods.mods]; diff --git a/src/utils/SearchUtils.ts b/src/utils/SearchUtils.ts index 41d483966..642f817db 100644 --- a/src/utils/SearchUtils.ts +++ b/src/utils/SearchUtils.ts @@ -1,3 +1,9 @@ +export enum ExactSearchMatchRank { + NONE = 0, + AUTHOR = 1, + NAME = 2, +} + export default class SearchUtils { public static makeKeys(search: string) { return search.trim().toLowerCase().split(' '); @@ -8,4 +14,17 @@ export default class SearchUtils { description = description.toLowerCase(); return keys.every(i => name.indexOf(i) >= 0 || description.indexOf(i) >= 0); } + + public static getExactMatchRank(query: string, name: string, fullName: string, author: string): ExactSearchMatchRank { + if (query.length === 0) { + return ExactSearchMatchRank.NONE; + } + if (name.toLowerCase() === query || fullName.toLowerCase() === query) { + return ExactSearchMatchRank.NAME; + } + if (author.toLowerCase() === query) { + return ExactSearchMatchRank.AUTHOR; + } + return ExactSearchMatchRank.NONE; + } }