96 lines
2.5 KiB
TypeScript
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;
|
|
};
|