import { EMPTY , NEVER , Observable , Subject , bufferCount , catchError , concatMap , debounceTime , distinctUntilChanged , distinctUntilKeyChanged , filter , fromEvent , map , merge , of , sample , share , skip , skipUntil , switchMap } from "rxjs" ; import { configuration , feature } from "~/_" ; import { Viewport , ViewportOffset , getElements , getOptionalElement , request , requestXML , setLocation , setLocationHash } from "~/browser" ; import { getComponentElement } from "~/components" ; import { h } from "~/utilities" ; /**
 * History state
 */ export interface HistoryState { url : URL ; offset ? : ViewportOffset ; } /**
 * Setup options
 */ interface SetupOptions { document$ : Subject < Document > ; location$ : Subject < URL > ; viewport$ : Observable < Viewport > ; } param urls returns function preprocess ( urls ) { if ( urls . length < 2 ) return urls ; const [ root , next ] = urls . sort ( ( a , b ) => a . length - b . length ) . map ( url => url . replace ( /[^/]+$/ , "" ) ) ; let index = 0 ; if ( root === next ) index = root . length ; else while ( root . charCodeAt ( index ) === next . charCodeAt ( index ) ) index ++ ; const config = configuration ( ) ; return urls . map ( url => ( url . replace ( root . slice ( 0 , index ) , config . base ) ) ) ; } param options export function setupInstantLoading ( { document$ , location$ , viewport$ } ) { const config = configuration ( ) ; if ( location . protocol === "file:" ) return ; if ( "scrollRestoration" in history ) { history . scrollRestoration = "manual" ; fromEvent ( window , "beforeunload" ) . subscribe ( ( ) => { history . scrollRestoration = "auto" ; } ) ; } const favicon = getOptionalElement < HTMLLinkElement > ( "link[rel=icon]" ) ; if ( typeof favicon !== "undefined" ) favicon . href = favicon . href ; const push$ = requestXML ( new URL ( "sitemap.xml" , config . base ) ) . pipe ( map ( sitemap => preprocess ( getElements ( "loc" , sitemap ) . map ( node => node . textContent ! ) ) ) , switchMap ( urls => fromEvent < MouseEvent > ( document . body , "click" ) . pipe ( filter ( ev => ! ev . metaKey && ! ev . ctrlKey ) , switchMap ( ev => { if ( ev . target instanceof Element ) { const el = ev . target . closest ( "a" ) ; if ( el && ! el . target ) { const url = new URL ( el . href ) ; url . search = "" ; url . hash = "" ; if ( url . pathname !== location . pathname && urls . includes ( url . toString ( ) ) ) { ev . preventDefault ( ) ; return of ( { url : new URL ( el . href ) } ) ; } } } return NEVER ; } ) ) ) , share < HistoryState > ( ) ) ; const pop$ = fromEvent < PopStateEvent > ( window , "popstate" ) . pipe ( filter ( ev => ev . state !== null ) , map ( ev => ( { url : new URL ( location . href ) , offset : ev . state } ) ) , share < HistoryState > ( ) ) ; merge ( push$ , pop$ ) . pipe ( distinctUntilChanged ( ( a , b ) => a . url . href === b . url . href ) , map ( ( { url } ) => url ) ) . subscribe ( location$ ) ; const response$ = location$ . pipe ( distinctUntilKeyChanged ( "pathname" ) , switchMap ( url => request ( url . href ) . pipe ( catchError ( ( ) => { setLocation ( url ) ; return NEVER ; } ) ) ) , share ( ) ) ; push$ . pipe ( sample ( response$ ) ) . subscribe ( ( { url } ) => { history . pushState ( { } , "" , `${ url }` ) ; } ) ; const dom = new DOMParser ( ) ; response$ . pipe ( switchMap ( res => res . text ( ) ) , map ( res => dom . parseFromString ( res , "text/html" ) ) ) . subscribe ( document$ ) ; document$ . pipe ( skip ( 1 ) ) . subscribe ( replacement => { for ( const selector of [ "title" , "link[rel=canonical]" , "meta[name=author]" , "meta[name=description]" , "[data-md-component=announce]" , "[data-md-component=container]" , "[data-md-component=header-topic]" , "[data-md-component=logo]" , "[data-md-component=skip]" , ... feature ( "navigation.tabs.sticky" ) ? [ "[data-md-component=tabs]" ] : [ ] ] ) { const source = getOptionalElement ( selector ) ; const target = getOptionalElement ( selector , replacement ) ; if ( typeof source !== "undefined" && typeof target !== "undefined" ) { source . replaceWith ( target ) ; } } } ) ; document$ . pipe ( skip ( 1 ) , map ( ( ) => getComponentElement ( "container" ) ) , switchMap ( el => of ( ... getElements ( "script" , el ) ) ) , concatMap ( el => { const script = h ( "script" ) ; if ( el . src ) { for ( const name of el . getAttributeNames ( ) ) script . setAttribute ( name , el . getAttribute ( name ) ! ) ; el . replaceWith ( script ) ; return new Observable ( observer => { script . onload = ( ) => observer . complete ( ) ; } ) ; } else { script . textContent = el . textContent ; el . replaceWith ( script ) ; return EMPTY ; } } ) ) . subscribe ( ) ; merge ( push$ , pop$ ) . pipe ( sample ( document$ ) ) . subscribe ( ( { url , offset } ) => { if ( url . hash && ! offset ) { setLocationHash ( url . hash ) ; } else { window . scrollTo ( 0 , offset ?. y || 0 ) ; } } ) ; viewport$ . pipe ( skipUntil ( push$ ) , debounceTime ( 250 ) , distinctUntilKeyChanged ( "offset" ) ) . subscribe ( ( { offset } ) => { history . replaceState ( offset , "" ) ; } ) ; merge ( push$ , pop$ ) . pipe ( bufferCount ( 2 , 1 ) , filter ( ( [ a , b ] ) => a . url . pathname === b . url . pathname ) , map ( ( [ , state ] ) => state ) ) . subscribe ( ( { offset } ) => { window . scrollTo ( 0 , offset ?. y || 0 ) ; } ) ; }