Files
plainleaf/client/lib/fuse_search.ts
T

96 lines
2.5 KiB
TypeScript

import Fuse from "fuse.js";
import type { FilterOption } from "@silverbulletmd/silverbullet/type/client";
import { fileName } from "@silverbulletmd/silverbullet/lib/resolve";
type FuseOption = FilterOption & {
baseName: string;
};
/**
* Compare orderIds; aspiring pages (Infinity) sort last. Avoids NaN when both are Infinity.
* @returns -1 if a is less than b, 1 if a is greater than b, 0 if they are equal
*/
function compareOrderId(a: number | undefined, b: number | undefined): number {
const aOrder = a ?? 0;
const bOrder = b ?? 0;
if (aOrder === Infinity && bOrder === Infinity) return 0;
if (aOrder === Infinity) return 1;
if (bOrder === Infinity) return -1;
return aOrder - bOrder;
}
export const fuzzySearchAndSort = (
arr: FilterOption[],
searchPhrase: string,
): FilterOption[] => {
if (!searchPhrase) {
return arr.sort((a, b) => compareOrderId(a.orderId, b.orderId));
}
const enrichedArr: FuseOption[] = arr.map((item) => {
return {
...item,
// Only relevant for pages and or documents and not commands
baseName: fileName(item.name),
displayName: item?.meta?.displayName,
aliases: item?.meta?.aliases?.join(" "),
};
});
const fuse = new Fuse(enrichedArr, {
keys: [
{
name: "name",
weight: 2,
},
{
name: "baseName",
weight: 3,
},
{
name: "displayName",
weight: 2,
},
{
name: "aliases",
weight: 2,
},
{
name: "description",
weight: 1,
},
],
isCaseSensitive: false,
ignoreDiacritics: true,
shouldSort: true,
threshold: 0.3,
includeScore: true,
sortFn: (a, b): number => {
const aItem = enrichedArr[a.idx];
const bItem = enrichedArr[b.idx];
// If either orderId is Infinity (aspiring page), put it last (compareOrderId avoids NaN)
if (aItem.orderId === Infinity || bItem.orderId === Infinity) {
return compareOrderId(aItem.orderId, bItem.orderId);
}
// If scores are the same, use orderId for sorting
if (a.score === b.score) {
const aOrder = aItem.orderId ?? 0;
const bOrder = bItem.orderId ?? 0;
if (aOrder !== bOrder) {
return aOrder - bOrder;
}
}
return a.score - b.score;
},
});
const results = fuse.search(searchPhrase);
const enhancedResults = results.map((r) => ({
...r.item,
fuseScore: r.score,
}));
return enhancedResults;
};