65 lines
1.6 KiB
TypeScript
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
|
|
}
|