import { Observable , Subject , bufferCount , combineLatest , debounceTime , defer , distinctUntilChanged , distinctUntilKeyChanged , finalize , map , of , scan , startWith , switchMap , takeLast , takeUntil , tap , withLatestFrom } from "rxjs" ; import { feature } from "~/_" ; import { Viewport , getElements , getLocation , getOptionalElement , watchElementSize } from "~/browser" ; import { Component } from "../_" ; import { Header } from "../header" ; /**
 * Table of contents
 */ export interface TableOfContents { prev : HTMLAnchorElement [ ] [ ] ; next : HTMLAnchorElement [ ] [ ] ; } /**
 * Watch options
 */ interface WatchOptions { viewport$ : Observable < Viewport > ; header$ : Observable < Header > ; } /**
 * Mount options
 */ interface MountOptions { viewport$ : Observable < Viewport > ; header$ : Observable < Header > ; } param el param options returns export function watchTableOfContents ( el , { viewport$ , header$ } ) { const table = new Map < HTMLAnchorElement , HTMLElement > ( ) ; const anchors = getElements < HTMLAnchorElement > ( "[href^=\\#]" , el ) ; for ( const anchor of anchors ) { const id = decodeURIComponent ( anchor . hash . substring ( 1 ) ) ; const target = getOptionalElement ( `[id="${ id }"]` ) ; if ( typeof target !== "undefined" ) table . set ( anchor , target ) ; } const adjust$ = header$ . pipe ( map ( header => 24 + header . height ) ) ; const partition$ = watchElementSize ( document . body ) . pipe ( distinctUntilKeyChanged ( "height" ) , switchMap ( body => defer ( ( ) => { let path = [ ] ; return of ( [ ... table ] . reduce ( ( index , [ anchor , target ] ) => { while ( path . length ) { const last = table . get ( path [ path . length - 1 ] ) ! ; if ( last . tagName >= target . tagName ) { path . pop ( ) ; } else { break ; } } let offset = target . offsetTop ; while ( ! offset && target . parentElement ) { target = target . parentElement ; offset = target . offsetTop ; } return index . set ( [ ... path = [ ... path , anchor ] ] . reverse ( ) , offset ) ; } , new Map < HTMLAnchorElement [ ] , number > ( ) ) ) ; } ) . pipe ( map ( index => new Map ( [ ... index ] . sort ( ( [ , a ] , [ , b ] ) => a - b ) ) ) , switchMap ( index => combineLatest ( [ viewport$ , adjust$ ] ) . pipe ( scan ( ( [ prev , next ] , [ { offset : { y } , size } , adjust ] ) => { const last = y + size . height >= Math . floor ( body . height ) ; while ( next . length ) { const [ , offset ] = next [ 0 ] ; if ( offset - adjust < y || last ) { prev = [ ... prev , next . shift ( ) ! ] ; } else { break ; } } while ( prev . length ) { const [ , offset ] = prev [ prev . length - 1 ] ; if ( offset - adjust >= y && ! last ) { next = [ prev . pop ( ) ! , ... next ] ; } else { break ; } } return [ prev , next ] ; } , [ [ ] , [ ... index ] ] ) , distinctUntilChanged ( ( a , b ) => ( a [ 0 ] === b [ 0 ] && a [ 1 ] === b [ 1 ] ) ) ) ) ) ) ) ; return partition$ . pipe ( map ( ( [ prev , next ] ) => ( { prev : prev . map ( ( [ path ] ) => path ) , next : next . map ( ( [ path ] ) => path ) } ) ) , startWith ( { prev : [ ] , next : [ ] } ) , bufferCount ( 2 , 1 ) , map ( ( [ a , b ] ) => { if ( a . prev . length < b . prev . length ) { return { prev : b . prev . slice ( Math . max ( 0 , a . prev . length - 1 ) , b . prev . length ) , next : [ ] } ; } else { return { prev : b . prev . slice ( - 1 ) , next : b . next . slice ( 0 , b . next . length - a . next . length ) } ; } } ) ) ; } param el param options returns export function mountTableOfContents ( el , { viewport$ , header$ } ) { return defer ( ( ) => { const push$ = new Subject < TableOfContents > ( ) ; push$ . subscribe ( ( { prev , next } ) => { for ( const [ anchor ] of next ) { anchor . removeAttribute ( "data-md-state" ) ; anchor . classList . remove ( "md-nav__link--active" ) ; } for ( const [ index , [ anchor ] ] of prev . entries ( ) ) { anchor . setAttribute ( "data-md-state" , "blur" ) ; anchor . classList . toggle ( "md-nav__link--active" , index === prev . length - 1 ) ; } } ) ; if ( feature ( "navigation.tracking" ) ) viewport$ . pipe ( takeUntil ( push$ . pipe ( takeLast ( 1 ) ) ) , distinctUntilKeyChanged ( "offset" ) , debounceTime ( 250 ) , withLatestFrom ( push$ ) ) . subscribe ( ( [ , { prev } ] ) => { const url = getLocation ( ) ; const anchor = prev [ prev . length - 1 ] ; if ( anchor && anchor . length ) { const [ active ] = anchor ; const { hash } = new URL ( active . href ) ; if ( url . hash !== hash ) { url . hash = hash ; history . replaceState ( { } , "" , `${ url }` ) ; } } else { url . hash = "" ; history . replaceState ( { } , "" , `${ url }` ) ; } } ) ; return watchTableOfContents ( el , { viewport$ , header$ } ) . pipe ( tap ( state => push$ . next ( state ) ) , finalize ( ( ) => push$ . complete ( ) ) , map ( state => ( { ref : el , ... state } ) ) ) ; } ) ; }