[Refactor/TS] Simplify node filter logic (#3275)

This commit is contained in:
Chenlei Hu
2025-03-29 13:00:18 -04:00
committed by GitHub
parent 3922a5882b
commit fcc22f06ac
9 changed files with 211 additions and 178 deletions

View File

@@ -1,6 +1,80 @@
import Fuse, { FuseOptionKey, FuseSearchOptions, IFuseOptions } from 'fuse.js'
type SearchAuxScore = number[]
export type SearchAuxScore = number[]
export interface FuseFilterWithValue<T, O = string> {
filterDef: FuseFilter<T, O>
value: O
}
export class FuseFilter<T, O = string> {
public readonly fuseSearch: FuseSearch<O>
/** The unique identifier for the filter. */
public readonly id: string
/** The name of the filter for display purposes. */
public readonly name: string
/** The sequence of characters to invoke the filter. */
public readonly invokeSequence: string
/** A function that returns the options for the filter. */
public readonly getItemOptions: (item: T) => O[]
constructor(
data: T[],
options: {
id: string
name: string
invokeSequence: string
getItemOptions: (item: T) => O[]
fuseOptions?: IFuseOptions<O>
}
) {
this.id = options.id
this.name = options.name
this.invokeSequence = options.invokeSequence
this.getItemOptions = options.getItemOptions
this.fuseSearch = new FuseSearch(this.getAllNodeOptions(data), {
fuseOptions: options.fuseOptions
})
}
public getAllNodeOptions(data: T[]): O[] {
const options = new Set<O>()
for (const item of data) {
for (const option of this.getItemOptions(item)) {
options.add(option)
}
}
return Array.from(options)
}
public matches(
item: T,
value: O,
extraOptions: {
wildcard?: O
} = {}
): boolean {
const { wildcard } = extraOptions
if (wildcard && value === wildcard) {
return true
}
const options = this.getItemOptions(item)
return (
options.includes(value) ||
(!!wildcard && options.some((option) => option === wildcard))
)
}
}
export interface FuseSearchable {
postProcessSearchScores: (scores: SearchAuxScore) => SearchAuxScore
}
function isFuseSearchable(item: any): item is FuseSearchable {
return 'postProcessSearchScores' in item
}
/**
* A wrapper around Fuse.js that provides a more type-safe API.
@@ -56,20 +130,22 @@ export class FuseSearch<T> {
public calcAuxScores(query: string, entry: T, score: number): SearchAuxScore {
let values: string[] = []
if (!this.keys.length) values = [entry as string]
// @ts-expect-error fixme ts strict error
else values = this.keys.map((x) => entry[x])
if (typeof entry === 'string') {
values = [entry]
} else if (typeof entry === 'object' && entry !== null) {
values = this.keys
.map((x) => entry[x as keyof T])
.filter((x) => typeof x === 'string') as string[]
}
const scores = values.map((x) => this.calcAuxSingle(query, x, score))
let result = scores.sort(this.compareAux)[0]
const deprecated = values.some((x) =>
x.toLocaleLowerCase().includes('deprecated')
)
result[0] += deprecated && result[0] != 0 ? 5 : 0
// @ts-expect-error fixme ts strict error
if (entry['postProcessSearchScores']) {
// @ts-expect-error fixme ts strict error
result = entry['postProcessSearchScores'](result) as SearchAuxScore
result[0] += deprecated && result[0] !== 0 ? 5 : 0
if (isFuseSearchable(entry)) {
result = entry.postProcessSearchScores(result)
}
return result
}