@@ -13,18 +13,19 @@ const dataset: DataSet = {
1313
1414const groupSearchOptions : IFuseOptions < Group > = {
1515 keys : [
16- { name : "groupInfo.names.stage" , weight : 2.5 } ,
17- { name : "groupInfo.names.korean" , weight : 2 } ,
18- { name : "groupInfo.names.japanese" , weight : 1.5 } ,
19- { name : "groupInfo.names.chinese" , weight : 1.5 } ,
16+ { name : "groupInfo.names.stage" , weight : 10 } ,
17+ { name : "groupInfo.names.korean" , weight : 3 } ,
18+ { name : "groupInfo.names.japanese" , weight : 2 } ,
19+ { name : "groupInfo.names.chinese" , weight : 2 } ,
2020 { name : "groupInfo.fandomName" , weight : 0.5 } ,
2121 { name : "company.current" , weight : 0.3 } ,
22- { name : "memberHistory.currentMembers.name" , weight : 0.3 } ,
22+ { name : "memberHistory.currentMembers.name" , weight : 0.2 } ,
2323 ] ,
2424 includeScore : true ,
25- threshold : 0.4 ,
25+ threshold : 0.3 ,
2626 ignoreLocation : true ,
2727 minMatchCharLength : 2 ,
28+ shouldSort : true ,
2829} ;
2930
3031const idolSearchOptions : IFuseOptions < Idol > = {
@@ -35,10 +36,7 @@ const idolSearchOptions: IFuseOptions<Idol> = {
3536 { name : "names.korean" , weight : 2 } ,
3637 { name : "names.japanese" , weight : 1.5 } ,
3738 { name : "names.chinese" , weight : 1.5 } ,
38- {
39- name : "groups.name" ,
40- weight : 1 ,
41- } ,
39+ { name : "groups.name" , weight : 1 } ,
4240 ] ,
4341 includeScore : true ,
4442 threshold : 0.3 ,
@@ -66,7 +64,11 @@ export function search(
6664 } = { } ,
6765) {
6866 const { type = "all" , limit = 10 , threshold } = options ;
69- const results : { item : Idol | Group ; type : "idol" | "group" } [ ] = [ ] ;
67+ let results : {
68+ item : Idol | Group ;
69+ type : "idol" | "group" ;
70+ score ?: number ;
71+ } [ ] = [ ] ;
7072
7173 // Split query into words for better matching
7274 const words = query . toLowerCase ( ) . trim ( ) . split ( / \s + / ) ;
@@ -93,70 +95,94 @@ export function search(
9395 : idolSearcher ;
9496
9597 if ( hasMultipleWords ) {
98+ // Split search into individual words
9699 const [ firstWord , ...restWords ] = words ;
97- if ( ! firstWord ) return [ ] ;
98- const restWordsStr = restWords . join ( " " ) ;
99-
100- // Search for idols matching the first word
101- const potentialIdols = idolSearcherInstance . search ( firstWord ) ;
102-
103- // Add matches where idol belongs to the specified group
104- for ( const idolResult of potentialIdols ) {
105- const idol = idolResult . item ;
106- if ( idol . groups ?. some ( ( g ) => g . name . toLowerCase ( ) === restWordsStr ) ) {
107- results . push ( {
108- item : idol ,
109- type : "idol" ,
110- } ) ;
111- }
112- }
113100
114- // Try reverse order (group first, then idol name)
115- const reversePotentialIdols = idolSearcherInstance . search ( restWordsStr ) ;
116- for ( const idolResult of reversePotentialIdols ) {
117- const idol = idolResult . item ;
118- if ( idol . groups ?. some ( ( g ) => g . name . toLowerCase ( ) === firstWord ) ) {
119- results . push ( {
120- item : idol ,
121- type : "idol" ,
122- } ) ;
123- }
124- }
101+ // Search for the first word
102+ const firstWordResults = firstWord ? search ( firstWord , { limit : 50 } ) : [ ] ;
103+
104+ // Filter results that match all words
105+ results = firstWordResults . filter ( ( result ) => {
106+ const textToSearch =
107+ result . type === "group"
108+ ? [
109+ ( result . item as Group ) . groupInfo . names . stage ,
110+ ...( ( result . item as Group ) . memberHistory ?. currentMembers ?. map (
111+ ( m ) => m . name ,
112+ ) || [ ] ) ,
113+ ]
114+ : [
115+ ( result . item as Idol ) . names . stage ,
116+ ( result . item as Idol ) . names . full ,
117+ ...( ( result . item as Idol ) . groups ?. map ( ( g ) => g . name ) || [ ] ) ,
118+ ] ;
119+
120+ return restWords . every ( ( word ) =>
121+ textToSearch . some ( ( text ) => text ?. toLowerCase ( ) . includes ( word ) ) ,
122+ ) ;
123+ } ) ;
124+ } else {
125+ const normalizedQuery = query . toLowerCase ( ) . trim ( ) ;
126+
127+ // First check for exact group matches if we're not specifically searching for idols
128+ if ( type !== "idol" ) {
129+ const exactGroupMatches = [
130+ ...dataset . girlGroups ,
131+ ...dataset . boyGroups ,
132+ ...dataset . coedGroups ,
133+ ]
134+ . filter (
135+ ( group ) =>
136+ group . groupInfo . names . stage ?. toLowerCase ( ) === normalizedQuery ,
137+ )
138+ . map ( ( group ) => ( {
139+ item : group ,
140+ type : "group" as const ,
141+ score : 0 , // Give exact matches the highest priority
142+ } ) ) ;
143+
144+ if ( exactGroupMatches . length > 0 ) {
145+ results . push ( ...exactGroupMatches ) ;
125146
126- // If no exact matches found, fall back to fuzzy search
127- if ( results . length === 0 ) {
128- for ( const idolResult of potentialIdols ) {
129- const idol = idolResult . item ;
130- if (
131- idol . groups ?. some ( ( g ) => g . name . toLowerCase ( ) . includes ( restWordsStr ) )
132- ) {
133- results . push ( {
134- item : idol ,
135- type : "idol" ,
136- } ) ;
147+ // If we only want groups, return here
148+ if ( type === "group" ) {
149+ return exactGroupMatches . slice ( 0 , limit ) ;
137150 }
138151 }
139152 }
140- } else {
153+
154+ // If no exact matches or we want all results, proceed with fuzzy search
155+ if ( type === "all" || type === "group" ) {
156+ const groupResults = groupSearcherInstance . search ( query ) ;
157+ results . push (
158+ ...groupResults
159+ . filter (
160+ ( result ) =>
161+ // Exclude exact matches we already added
162+ result . item . groupInfo . names . stage ?. toLowerCase ( ) !==
163+ normalizedQuery ,
164+ )
165+ . map ( ( result ) => ( {
166+ item : result . item ,
167+ type : "group" as const ,
168+ score : result . score || 1 ,
169+ } ) ) ,
170+ ) ;
171+ }
172+
141173 if ( type === "all" || type === "idol" ) {
142174 const idolResults = idolSearcherInstance . search ( query ) ;
143175 results . push (
144176 ...idolResults . map ( ( result ) => ( {
145177 item : result . item ,
146178 type : "idol" as const ,
179+ score : result . score || 1 ,
147180 } ) ) ,
148181 ) ;
149182 }
150183
151- if ( type === "all" || type === "group" ) {
152- const groupResults = groupSearcherInstance . search ( query ) ;
153- results . push (
154- ...groupResults . map ( ( result ) => ( {
155- item : result . item ,
156- type : "group" as const ,
157- } ) ) ,
158- ) ;
159- }
184+ // Sort results by score (lower is better)
185+ results = results . sort ( ( a , b ) => ( a . score || 1 ) - ( b . score || 1 ) ) ;
160186 }
161187
162188 // Remove duplicates
@@ -168,21 +194,4 @@ export function search(
168194 return uniqueResults . slice ( 0 , limit ) ;
169195}
170196
171- export type { Idol , Group } ;
172- export function getItemById (
173- id : string ,
174- ) : { item : Idol | Group ; type : "idol" | "group" } | null {
175- const idol = [ ...dataset . femaleIdols , ...dataset . maleIdols ] . find (
176- ( i ) => i . id === id ,
177- ) ;
178- if ( idol ) return { item : idol , type : "idol" } ;
179-
180- const group = [
181- ...dataset . girlGroups ,
182- ...dataset . boyGroups ,
183- ...dataset . coedGroups ,
184- ] . find ( ( g ) => g . id === id ) ;
185- if ( group ) return { item : group , type : "group" } ;
186-
187- return null ;
188- }
197+ export type { Idol , Group , DataSet , GroupsData , IdolsData } ;
0 commit comments