import { SearchDocument , SearchDocumentMap , setupSearchDocumentMap } from "../document" ; import { SearchHighlightFactoryFn , setupSearchHighlighter } from "../highlighter" ; import { SearchOptions } from "../options" ; import { SearchQueryTerms , getSearchQueryTerms , parseSearchQuery } from "../query" ; /**
 * Search index configuration
 */ export interface SearchIndexConfig { lang : string [ ] ; separator : string ; } /**
 * Search index document
 */ export interface SearchIndexDocument { location : string ; title : string ; text : string ; } /**
 * Search index
 *
 * This interfaces describes the format of the `search_index.json` file which
 * is automatically built by the MkDocs search plugin.
 */ export interface SearchIndex { config : SearchIndexConfig ; docs : SearchIndexDocument [ ] ; options : SearchOptions ; } /**
 * Search metadata
 */ export interface SearchMetadata { score : number ; terms : SearchQueryTerms ; } /**
 * Search result document
 */ export type SearchResultDocument = SearchDocument & SearchMetadata ; /**
 * Search result item
 */ export type SearchResultItem = SearchResultDocument [ ] ; /**
 * Search result
 */ export interface SearchResult { items : SearchResultItem [ ] ; suggestions ? : string [ ] ; } param a param b returns function difference ( a , b ) { const [ x , y ] = [ new Set ( a ) , new Set ( b ) ] ; return [ ... new Set ( [ ... x ] . filter ( value => ! y . has ( value ) ) ) ] ; } /**
 * Search index
 */ export class Search { /**
     * Search document mapping
     *
     * A mapping of URLs (including hash fragments) to the actual articles and
     * sections of the documentation. The search document mapping must be created
     * regardless of whether the index was prebuilt or not, as Lunr.js itself
     * only stores the actual index.
     */ protected documents : SearchDocumentMap ; /**
     * Search highlight factory function
     */ protected highlight : SearchHighlightFactoryFn ; /**
     * The underlying Lunr.js search index
     */ protected index : lunr . Index ; /**
     * Search options
     */ protected options : SearchOptions ; param data public constructor ( { config , docs , options } ) { this . options = options ; this . documents = setupSearchDocumentMap ( docs ) ; this . highlight = setupSearchHighlighter ( config , false ) ; lunr . tokenizer . separator = new RegExp ( config . separator ) ; this . index = lunr ( function ( ) { if ( config . lang . length === 1 && config . lang [ 0 ] !== "en" ) { this . use ( ( lunr as any ) [ config . lang [ 0 ] ] ) ; } else if ( config . lang . length > 1 ) { this . use ( ( lunr as any ) . multiLanguage ( ... config . lang ) ) ; } const fns = difference ( [ "trimmer" , "stopWordFilter" , "stemmer" ] , options . pipeline ) ; for ( const lang of config . lang . map ( language => ( language === "en" ? lunr : ( lunr as any ) [ language ] ) ) ) { for ( const fn of fns ) { this . pipeline . remove ( lang [ fn ] ) ; this . searchPipeline . remove ( lang [ fn ] ) ; } } this . ref ( "location" ) ; this . field ( "title" , { boost : 1e3 } ) ; this . field ( "text" ) ; for ( const doc of docs ) this . add ( doc ) ; } ) ; } param query returns public search ( query ) { if ( query ) { try { const highlight = this . highlight ( query ) ; const clauses = parseSearchQuery ( query ) . filter ( clause => ( clause . presence !== lunr . Query . presence . PROHIBITED ) ) ; const groups = this . index . search ( `${ query }*` ) . reduce < SearchResultItem > ( ( item , { ref , score , matchData } ) => { const document = this . documents . get ( ref ) ; if ( typeof document !== "undefined" ) { const { location , title , text , parent } = document ; const terms = getSearchQueryTerms ( clauses , Object . keys ( matchData . metadata ) ) ; const boost = + ! parent + + Object . values ( terms ) . every ( t => t ) ; item . push ( { location , title : highlight ( title ) , text : highlight ( text ) , score : score * ( 1 + boost ) , terms } ) ; } return item ; } , [ ] ) . sort ( ( a , b ) => b . score - a . score ) . reduce ( ( items , result ) => { const document = this . documents . get ( result . location ) ; if ( typeof document !== "undefined" ) { const ref = "parent" in document ? document . parent ! . location : document . location ; items . set ( ref , [ ... items . get ( ref ) || [ ] , result ] ) ; } return items ; } , new Map < string , SearchResultItem > ( ) ) ; let suggestions ; if ( this . options . suggestions ) { const titles = this . index . query ( builder => { for ( const clause of clauses ) builder . term ( clause . term , { fields : [ "title" ] , presence : lunr . Query . presence . REQUIRED , wildcard : lunr . Query . wildcard . TRAILING } ) ; } ) ; suggestions = titles . length ? Object . keys ( titles [ 0 ] . matchData . metadata ) : [ ] ; } return { items : [ ... groups . values ( ) ] , ... typeof suggestions !== "undefined" && { suggestions } } ; } catch { console . warn ( `Invalid query: ${ query } – see https://bit.ly/2s3ChXG` ) ; } } return { items : [ ] } ; } }