import { NEVER , Observable , ObservableInput , filter , merge , mergeWith , sample , take } from "rxjs" ; import { configuration } from "~/_" ; import { Keyboard , getActiveElement , getElements , setToggle } from "~/browser" ; import { SearchIndex , SearchResult , isSearchQueryMessage , isSearchReadyMessage , setupSearchWorker } from "~/integrations" ; import { Component , getComponentElement , getComponentElements } from "../../_" ; import { SearchQuery , mountSearchQuery } from "../query" ; import { mountSearchResult } from "../result" ; import { SearchShare , mountSearchShare } from "../share" ; import { SearchSuggest , mountSearchSuggest } from "../suggest" ; /**
 * Search
 */ export type Search = SearchQuery | SearchResult | SearchShare | SearchSuggest ; /**
 * Mount options
 */ interface MountOptions { index$ : ObservableInput < SearchIndex > ; keyboard$ : Observable < Keyboard > ; } param el param options returns export function mountSearch ( el , { index$ , keyboard$ } ) { const config = configuration ( ) ; try { const url = __search ?. worker || config . search ; const worker = setupSearchWorker ( url , index$ ) ; const query = getComponentElement ( "search-query" , el ) ; const result = getComponentElement ( "search-result" , el ) ; const { tx$ , rx$ } = worker ; tx$ . pipe ( filter ( isSearchQueryMessage ) , sample ( rx$ . pipe ( filter ( isSearchReadyMessage ) ) ) , take ( 1 ) ) . subscribe ( tx$ . next . bind ( tx$ ) ) ; keyboard$ . pipe ( filter ( ( { mode } ) => mode === "search" ) ) . subscribe ( key => { const active = getActiveElement ( ) ; switch ( key . type ) { case "Enter" : if ( active === query ) { const anchors = new Map < HTMLAnchorElement , number > ( ) ; for ( const anchor of getElements < HTMLAnchorElement > ( ":first-child [href]" , result ) ) { const article = anchor . firstElementChild ! ; anchors . set ( anchor , parseFloat ( article . getAttribute ( "data-md-score" ) ! ) ) ; } if ( anchors . size ) { const [ [ best ] ] = [ ... anchors ] . sort ( ( [ , a ] , [ , b ] ) => b - a ) ; best . click ( ) ; } key . claim ( ) ; } break ; case "Escape" : case "Tab" : setToggle ( "search" , false ) ; query . blur ( ) ; break ; case "ArrowUp" : case "ArrowDown" : if ( typeof active === "undefined" ) { query . focus ( ) ; } else { const els = [ query , ... getElements ( ":not(details) > [href], summary, details[open] [href]" , result ) ] ; const i = Math . max ( 0 , ( Math . max ( 0 , els . indexOf ( active ) ) + els . length + ( key . type === "ArrowUp" ? - 1 : + 1 ) ) % els . length ) ; els [ i ] . focus ( ) ; } key . claim ( ) ; break ; default : if ( query !== getActiveElement ( ) ) query . focus ( ) ; } } ) ; keyboard$ . pipe ( filter ( ( { mode } ) => mode === "global" ) ) . subscribe ( key => { switch ( key . type ) { case "f" : case "s" : case "/" : query . focus ( ) ; query . select ( ) ; key . claim ( ) ; break ; } } ) ; const query$ = mountSearchQuery ( query , worker ) ; const result$ = mountSearchResult ( result , worker , { query$ } ) ; return merge ( query$ , result$ ) . pipe ( mergeWith ( ... getComponentElements ( "search-share" , el ) . map ( child => mountSearchShare ( child , { query$ } ) ) , ... getComponentElements ( "search-suggest" , el ) . map ( child => mountSearchSuggest ( child , worker , { keyboard$ } ) ) ) ) ; } catch ( err ) { el . hidden = true ; return NEVER ; } }