ultisuite-client/lib/contacts/fuzzy-search.ts
R3D347HR4Y ae54fa29e4 Hehe
2026-05-18 17:47:32 +02:00

65 lines
1.6 KiB
TypeScript

import Fuse, { type IFuseOptions } from "fuse.js"
import type { FullContact } from "./types"
function stripAccents(str: string): string {
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
}
const fuseOptions: IFuseOptions<FullContact> = {
keys: [
"firstName",
"lastName",
"middleName",
"nicknames",
"emails.value",
"phones.value",
"company",
"department",
"jobTitle",
"addresses.street",
"addresses.city",
"addresses.country",
],
isCaseSensitive: false,
threshold: 0.4,
ignoreLocation: true,
getFn: (obj: FullContact, path: string | string[]) => {
const raw = Fuse.config.getFn(obj, path)
if (Array.isArray(raw)) {
return raw.map((v) => (typeof v === "string" ? stripAccents(v) : v)) as unknown as string
}
if (typeof raw === "string") {
return stripAccents(raw)
}
return raw as unknown as string
},
}
let cachedFuse: { key: FullContact[]; fuse: Fuse<FullContact> } | null = null
function getFuse(contacts: FullContact[]): Fuse<FullContact> {
if (cachedFuse && cachedFuse.key === contacts) return cachedFuse.fuse
const fuse = new Fuse(contacts, fuseOptions)
cachedFuse = { key: contacts, fuse }
return fuse
}
export function searchContacts(
contacts: FullContact[],
query: string
): FullContact[] {
if (!query.trim()) return contacts
const fuse = getFuse(contacts)
const results = fuse.search(stripAccents(query))
const seen = new Set<string>()
const deduped: FullContact[] = []
for (const r of results) {
if (!seen.has(r.item.id)) {
seen.add(r.item.id)
deduped.push(r.item)
}
}
return deduped
}