add order algorithm in search result
This commit is contained in:
parent
5844209bee
commit
f73dc05e25
3 changed files with 107 additions and 42 deletions
src/app
components
hooks
|
@ -64,9 +64,7 @@ export function EmoticonAutocomplete({
|
|||
}, [imagePacks]);
|
||||
|
||||
const [result, search, resetSearch] = useAsyncSearch(searchList, getEmoticonStr, SEARCH_OPTIONS);
|
||||
const autoCompleteEmoticon = (result ? result.items : recentEmoji).sort((a, b) =>
|
||||
a.shortcode.localeCompare(b.shortcode)
|
||||
);
|
||||
const autoCompleteEmoticon = result ? result.items : recentEmoji;
|
||||
|
||||
useEffect(() => {
|
||||
if (query.text) search(query.text);
|
||||
|
|
|
@ -471,36 +471,34 @@ export function SearchEmojiGroup({
|
|||
return (
|
||||
<EmojiGroup key={id} id={id} label={label}>
|
||||
{tab === EmojiBoardTab.Emoji
|
||||
? searchResult
|
||||
.sort((a, b) => a.shortcode.localeCompare(b.shortcode))
|
||||
.map((emoji) =>
|
||||
'unicode' in emoji ? (
|
||||
<EmojiItem
|
||||
key={emoji.unicode}
|
||||
label={emoji.label}
|
||||
type={EmojiType.Emoji}
|
||||
data={emoji.unicode}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
{emoji.unicode}
|
||||
</EmojiItem>
|
||||
) : (
|
||||
<EmojiItem
|
||||
key={emoji.shortcode}
|
||||
label={emoji.body || emoji.shortcode}
|
||||
type={EmojiType.CustomEmoji}
|
||||
data={emoji.url}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.CustomEmojiImg}
|
||||
alt={emoji.body || emoji.shortcode}
|
||||
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
|
||||
/>
|
||||
</EmojiItem>
|
||||
)
|
||||
? searchResult.map((emoji) =>
|
||||
'unicode' in emoji ? (
|
||||
<EmojiItem
|
||||
key={emoji.unicode}
|
||||
label={emoji.label}
|
||||
type={EmojiType.Emoji}
|
||||
data={emoji.unicode}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
{emoji.unicode}
|
||||
</EmojiItem>
|
||||
) : (
|
||||
<EmojiItem
|
||||
key={emoji.shortcode}
|
||||
label={emoji.body || emoji.shortcode}
|
||||
type={EmojiType.CustomEmoji}
|
||||
data={emoji.url}
|
||||
shortcode={emoji.shortcode}
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
className={css.CustomEmojiImg}
|
||||
alt={emoji.body || emoji.shortcode}
|
||||
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
|
||||
/>
|
||||
</EmojiItem>
|
||||
)
|
||||
)
|
||||
: searchResult.map((emoji) =>
|
||||
'unicode' in emoji ? null : (
|
||||
<StickerItem
|
||||
|
|
|
@ -28,6 +28,81 @@ export type UseAsyncSearchResult<TSearchItem extends object | string | number> =
|
|||
|
||||
export type SearchResetHandler = () => void;
|
||||
|
||||
const performMatch = (
|
||||
target: string | string[],
|
||||
query: string,
|
||||
options?: UseAsyncSearchOptions
|
||||
): string | undefined => {
|
||||
if (Array.isArray(target)) {
|
||||
const matchTarget = target.find((i) =>
|
||||
matchQuery(normalize(i, options?.normalizeOptions), query, options?.matchOptions)
|
||||
);
|
||||
return matchTarget ? normalize(matchTarget, options?.normalizeOptions) : undefined;
|
||||
}
|
||||
|
||||
const normalizedTargetStr = normalize(target, options?.normalizeOptions);
|
||||
const matches = matchQuery(normalizedTargetStr, query, options?.matchOptions);
|
||||
return matches ? normalizedTargetStr : undefined;
|
||||
};
|
||||
|
||||
export const orderSearchItems = <TSearchItem extends object | string | number>(
|
||||
query: string,
|
||||
items: TSearchItem[],
|
||||
getItemStr: SearchItemStrGetter<TSearchItem>,
|
||||
options?: UseAsyncSearchOptions
|
||||
): TSearchItem[] => {
|
||||
const orderedItems: TSearchItem[] = Array.from(items);
|
||||
|
||||
// we will consider "_" as word boundary char.
|
||||
// because in more use-cases it is used. (like: emojishortcode)
|
||||
const boundaryRegex = new RegExp(`(\\b|_)${query}`);
|
||||
const perfectBoundaryRegex = new RegExp(`(\\b|_)${query}(\\b|_)`);
|
||||
|
||||
orderedItems.sort((i1, i2) => {
|
||||
const str1 = performMatch(getItemStr(i1, query), query, options);
|
||||
const str2 = performMatch(getItemStr(i2, query), query, options);
|
||||
|
||||
if (str1 === undefined && str2 === undefined) return 0;
|
||||
if (str1 === undefined) return 1;
|
||||
if (str2 === undefined) return -1;
|
||||
|
||||
let points1 = 0;
|
||||
let points2 = 0;
|
||||
|
||||
// short string should score more
|
||||
const pointsToSmallStr = (points: number) => {
|
||||
if (str1.length < str2.length) points1 += points;
|
||||
else if (str2.length < str1.length) points2 += points;
|
||||
};
|
||||
pointsToSmallStr(1);
|
||||
|
||||
// closes query match should score more
|
||||
const indexIn1 = str1.indexOf(query);
|
||||
const indexIn2 = str2.indexOf(query);
|
||||
if (indexIn1 < indexIn2) points1 += 2;
|
||||
else if (indexIn2 < indexIn1) points2 += 2;
|
||||
else pointsToSmallStr(2);
|
||||
|
||||
// query match word start on boundary should score more
|
||||
const boundaryIn1 = str1.match(boundaryRegex);
|
||||
const boundaryIn2 = str2.match(boundaryRegex);
|
||||
if (boundaryIn1 && boundaryIn2) pointsToSmallStr(4);
|
||||
else if (boundaryIn1) points1 += 4;
|
||||
else if (boundaryIn2) points2 += 4;
|
||||
|
||||
// query match word start and end on boundary should score more
|
||||
const perfectBoundaryIn1 = str1.match(perfectBoundaryRegex);
|
||||
const perfectBoundaryIn2 = str2.match(perfectBoundaryRegex);
|
||||
if (perfectBoundaryIn1 && perfectBoundaryIn2) pointsToSmallStr(8);
|
||||
else if (perfectBoundaryIn1) points1 += 8;
|
||||
else if (perfectBoundaryIn2) points2 += 8;
|
||||
|
||||
return points2 - points1;
|
||||
});
|
||||
|
||||
return orderedItems;
|
||||
};
|
||||
|
||||
export const useAsyncSearch = <TSearchItem extends object | string | number>(
|
||||
list: TSearchItem[],
|
||||
getItemStr: SearchItemStrGetter<TSearchItem>,
|
||||
|
@ -40,21 +115,15 @@ export const useAsyncSearch = <TSearchItem extends object | string | number>(
|
|||
|
||||
const handleMatch: MatchHandler<TSearchItem> = (item, query) => {
|
||||
const itemStr = getItemStr(item, query);
|
||||
if (Array.isArray(itemStr))
|
||||
return !!itemStr.find((i) =>
|
||||
matchQuery(normalize(i, options?.normalizeOptions), query, options?.matchOptions)
|
||||
);
|
||||
return matchQuery(
|
||||
normalize(itemStr, options?.normalizeOptions),
|
||||
query,
|
||||
options?.matchOptions
|
||||
);
|
||||
|
||||
const strWithMatch = performMatch(itemStr, query, options);
|
||||
return typeof strWithMatch === 'string';
|
||||
};
|
||||
|
||||
const handleResult: ResultHandler<TSearchItem> = (results, query) =>
|
||||
setResult({
|
||||
query,
|
||||
items: [...results],
|
||||
items: orderSearchItems(query, results, getItemStr, options),
|
||||
});
|
||||
|
||||
return AsyncSearch(list, handleMatch, handleResult, options);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue