@@ -44,73 +44,71 @@ export function init(blocks: Block[]) {
44
44
inited = true ;
45
45
}
46
46
47
+ const CURRENT_SECTION_BOOST = 2 ;
48
+ const EXACT_MATCH_BOOST = 10 ;
49
+ const WORD_MATCH_BOOST = 4 ;
50
+ const NEAR_MATCH_BOOST = 2 ;
51
+ const BREADCRUMB_LENGTH_BOOST = 0.2 ;
52
+
47
53
/**
48
54
* Search for a given query in the existing index
49
55
*/
50
56
export function search ( query : string , path : string ) : BlockGroup [ ] {
51
57
const escaped = query . replace ( / [ - [ \] { } ( ) * + ? . , \\ ^ $ | # \s ] / g, '\\$&' ) ;
52
- const regex = new RegExp ( `(^|\\b)${ escaped } ` , 'i' ) ;
58
+ const exact_match = new RegExp ( `^${ escaped } $` , 'i' ) ;
59
+ const word_match = new RegExp ( `(^|\\b)${ escaped } ($|\\b)` , 'i' ) ;
60
+ const near_match = new RegExp ( `(^|\\b)${ escaped } ` , 'i' ) ;
53
61
54
62
const parts = path . split ( '/' ) ;
55
63
56
64
const blocks = indexes
57
65
. flatMap ( ( index ) => index . search ( query ) )
58
66
// @ts -expect-error flexsearch types are wrong i think?
59
67
. map ( lookup )
60
- . map ( ( block , rank ) => ( { block : block as Block , rank } ) )
61
- . sort ( ( a , b ) => {
68
+ . map ( ( block ) => {
69
+ const block_parts = block . href . split ( '/' ) ;
70
+
62
71
// prioritise current section
63
- const a_parts = a . block . href . split ( '/' ) ;
64
- const b_parts = b . block . href . split ( '/' ) ;
65
-
66
- for ( let i = 0 ; i < parts . length ; i += 1 ) {
67
- const a_part_matches = a_parts [ i ] === parts [ i ] ;
68
- const b_part_matches = b_parts [ i ] === parts [ i ] ;
69
-
70
- if ( ! a_part_matches || ! b_part_matches ) {
71
- if ( a_part_matches !== b_part_matches ) {
72
- if ( i > 1 ) {
73
- console . log ( 'here' , a , b ) ;
74
- }
75
- return a_part_matches ? - 1 : 1 ;
76
- }
77
-
78
- break ;
79
- }
72
+ let score = block_parts . findIndex ( ( part , i ) => part !== parts [ i ] ) ;
73
+ if ( score === - 1 ) score = block_parts . length ;
74
+ score *= CURRENT_SECTION_BOOST ;
75
+
76
+ if ( block . breadcrumbs . some ( ( text ) => exact_match . test ( text ) ) ) {
77
+ console . log ( 'EXACT MATCH' , block . breadcrumbs ) ;
78
+ score += EXACT_MATCH_BOOST ;
79
+ } else if ( block . breadcrumbs . some ( ( text ) => word_match . test ( text ) ) ) {
80
+ score += WORD_MATCH_BOOST ;
81
+ } else if ( block . breadcrumbs . some ( ( text ) => near_match . test ( text ) ) ) {
82
+ score += NEAR_MATCH_BOOST ;
80
83
}
81
84
82
- const a_title_matches = regex . test ( a . block . breadcrumbs . at ( - 1 ) ! ) ;
83
- const b_title_matches = regex . test ( b . block . breadcrumbs . at ( - 1 ) ! ) ;
84
-
85
- // massage the order a bit, so that title matches
86
- // are given higher priority
87
- if ( a_title_matches !== b_title_matches ) {
88
- return a_title_matches ? - 1 : 1 ;
89
- }
85
+ // prioritise branches over leaves
86
+ score -= block . breadcrumbs . length * BREADCRUMB_LENGTH_BOOST ;
90
87
91
- return a . block . breadcrumbs . length - b . block . breadcrumbs . length || a . rank - b . rank ;
92
- } )
93
- . map ( ( { block } ) => block ) ;
88
+ return { block, score } ;
89
+ } ) ;
94
90
95
91
const groups : Record < string , BlockGroup > = { } ;
96
92
97
- for ( const block of blocks ) {
93
+ for ( const { score , block } of blocks ) {
98
94
const breadcrumbs = block . breadcrumbs . slice ( 0 , 2 ) ;
99
95
100
96
const group = ( groups [ breadcrumbs . join ( '::' ) ] ??= {
101
97
breadcrumbs,
102
- blocks : [ ]
98
+ blocks : [ ] ,
99
+ score : 0
103
100
} ) ;
104
101
102
+ group . score = Math . max ( score , group . score ) ;
105
103
group . blocks . push ( block ) ;
106
104
}
107
105
108
- return Object . values ( groups ) ;
106
+ return Object . values ( groups ) . sort ( ( a , b ) => b . score - a . score ) ;
109
107
}
110
108
111
109
/**
112
110
* Get a block with details by its href
113
111
*/
114
112
export function lookup ( href : string ) {
115
- return map . get ( href ) ;
113
+ return map . get ( href ) ! ;
116
114
}
0 commit comments