@@ -4,6 +4,8 @@ const attrsToCopy = [
44 "data-githubContributorsUrl" ,
55 "data-githubContributorsFilename" ,
66 "data-pathToRoot" ,
7+ "data-rawLocation" ,
8+ "data-dynamicSideMenu" ,
79]
810
911/**
@@ -25,7 +27,7 @@ function savePageState(doc) {
2527 }
2628 return {
2729 mainDiv : doc . querySelector ( "#main" ) ?. innerHTML ,
28- leftColumn : doc . querySelector ( "#leftColumn" ) . innerHTML ,
30+ leftColumn : dynamicSideMenu ? null : doc . querySelector ( "#leftColumn" ) . innerHTML ,
2931 title : doc . title ,
3032 attrs,
3133 } ;
@@ -38,12 +40,15 @@ function savePageState(doc) {
3840function loadPageState ( doc , saved ) {
3941 doc . title = saved . title ;
4042 doc . querySelector ( "#main" ) . innerHTML = saved . mainDiv ;
41- doc . querySelector ( "#leftColumn" ) . innerHTML = saved . leftColumn ;
43+ if ( ! dynamicSideMenu )
44+ doc . querySelector ( "#leftColumn" ) . innerHTML = saved . leftColumn ;
4245 for ( const attr of attrsToCopy ) {
4346 doc . documentElement . setAttribute ( attr , saved . attrs [ attr ] ) ;
4447 }
4548}
4649
50+ const attachedElements = new WeakSet ( )
51+
4752function attachAllListeners ( ) {
4853 if ( observer ) {
4954 observer . disconnect ( ) ;
@@ -97,19 +102,19 @@ function attachAllListeners() {
97102 }
98103 }
99104
100- document
101- . querySelectorAll ( ".documentableElement .signature" )
102- . forEach ( ( signature ) => {
103- const short = signature . querySelector ( ".signature-short" ) ;
104- const long = signature . querySelector ( ".signature-long" ) ;
105- const extender = document . createElement ( "span" ) ;
106- const extenderDots = document . createTextNode ( "..." ) ;
107- extender . appendChild ( extenderDots ) ;
108- extender . classList . add ( "extender" ) ;
109- if ( short && long && signature . children [ 1 ] . hasChildNodes ( ) ) {
110- signature . children [ 0 ] . append ( extender ) ;
111- }
112- } ) ;
105+ document
106+ . querySelectorAll ( ".documentableElement .signature" )
107+ . forEach ( ( signature ) => {
108+ const short = signature . querySelector ( ".signature-short" ) ;
109+ const long = signature . querySelector ( ".signature-long" ) ;
110+ const extender = document . createElement ( "span" ) ;
111+ const extenderDots = document . createTextNode ( "..." ) ;
112+ extender . appendChild ( extenderDots ) ;
113+ extender . classList . add ( "extender" ) ;
114+ if ( short && long && signature . children [ 1 ] . hasChildNodes ( ) ) {
115+ signature . children [ 0 ] . append ( extender ) ;
116+ }
117+ } ) ;
113118
114119 const documentableLists = document . getElementsByClassName ( "documentableList" ) ;
115120 [ ...documentableLists ] . forEach ( ( list ) => {
@@ -151,6 +156,8 @@ document
151156 return ;
152157 }
153158 const url = new URL ( href ) ;
159+ if ( attachedElements . has ( el ) ) return ;
160+ attachedElements . add ( el ) ;
154161 el . addEventListener ( "click" , ( e ) => {
155162 if (
156163 url . href . replace ( / # .* / , "" ) === window . location . href . replace ( / # .* / , "" )
@@ -166,6 +173,7 @@ document
166173 e . preventDefault ( ) ;
167174 e . stopPropagation ( ) ;
168175 $ . get ( href , function ( data ) {
176+ const oldLoc = getRawLoc ( ) ;
169177 if ( window . history . state === null ) {
170178 window . history . replaceState ( savePageState ( document ) , "" ) ;
171179 }
@@ -174,6 +182,11 @@ document
174182 const state = savePageState ( parsedDocument ) ;
175183 window . history . pushState ( state , "" , href ) ;
176184 loadPageState ( document , state ) ;
185+ const newLoc = getRawLoc ( ) ;
186+ if ( dynamicSideMenu ) {
187+ updateMenu ( oldLoc , newLoc ) ;
188+ }
189+
177190 window . dispatchEvent ( new Event ( DYNAMIC_PAGE_LOAD ) ) ;
178191 document
179192 . querySelector ( "#main" )
@@ -182,11 +195,15 @@ document
182195 } ) ;
183196 } ) ;
184197
185- $ ( ".ar" ) . on ( "click" , function ( e ) {
186- $ ( this ) . parent ( ) . parent ( ) . toggleClass ( "expanded" ) ;
187- $ ( this ) . toggleClass ( "expanded" ) ;
188- e . stopPropagation ( ) ;
189- } ) ;
198+ document . querySelectorAll ( '.ar' ) . forEach ( ( el ) => {
199+ if ( attachedElements . has ( el ) ) return ;
200+ attachedElements . add ( el ) ;
201+ el . addEventListener ( 'click' , ( e ) => {
202+ e . stopPropagation ( ) ;
203+ el . parentElement . parentElement . classList . toggle ( "expanded" ) ;
204+ el . classList . toggle ( "expanded" ) ;
205+ } )
206+ } )
190207
191208 document . querySelectorAll ( ".documentableList .ar" ) . forEach ( ( arrow ) => {
192209 arrow . addEventListener ( "click" , ( ) => {
@@ -195,7 +212,9 @@ document
195212 } ) ;
196213 } ) ;
197214
198- document . querySelectorAll ( ".nh" ) . forEach ( ( el ) =>
215+ document . querySelectorAll ( ".nh" ) . forEach ( ( el ) => {
216+ if ( attachedElements . has ( el ) ) return ;
217+ attachedElements . add ( el ) ;
199218 el . addEventListener ( "click" , ( ) => {
200219 if (
201220 el . lastChild . href . replace ( "#" , "" ) ===
@@ -206,8 +225,8 @@ document
206225 } else {
207226 el . lastChild . click ( ) ;
208227 }
209- } ) ,
210- ) ;
228+ } ) ;
229+ } ) ;
211230
212231 const toggleShowAllElem = ( element ) => {
213232 if ( element . textContent == "Show all" ) {
@@ -345,7 +364,7 @@ window.addEventListener(DYNAMIC_PAGE_LOAD, () => {
345364 attachAllListeners ( ) ;
346365} ) ;
347366
348- window . addEventListener ( "dynamicPageLoad" , ( ) => {
367+ window . addEventListener ( DYNAMIC_PAGE_LOAD , ( ) => {
349368 const sideMenuOpen = sessionStorage . getItem ( "sideMenuOpen" ) ;
350369 if ( sideMenuOpen ) {
351370 if ( document . querySelector ( "#leftColumn" ) . classList . contains ( "show" ) ) {
@@ -365,10 +384,136 @@ window.addEventListener("dynamicPageLoad", () => {
365384 }
366385} ) ;
367386
387+ let dynamicSideMenu = false ;
388+ /** @param {Element } elem @param {boolean } hide */
389+ function updatePath ( elem , hide , first = true ) {
390+ if ( elem . classList . contains ( "side-menu" ) ) return ;
391+ const span = elem . firstElementChild
392+ const btn = span . firstElementChild
393+ if ( hide ) {
394+ elem . classList . remove ( "expanded" ) ;
395+ span . classList . remove ( "h100" , "selected" , "expanded" , "cs" ) ;
396+ if ( btn ) btn . classList . remove ( "expanded" ) ;
397+ } else {
398+ elem . classList . add ( "expanded" ) ;
399+ span . classList . add ( "h100" , "expanded" , "cs" ) ;
400+ if ( btn ) btn . classList . add ( "expanded" ) ;
401+ if ( first ) span . classList . add ( "selected" ) ;
402+ }
403+ updatePath ( elem . parentElement , hide , false ) ;
404+ }
405+ let updateMenu = null ;
406+ function getRawLoc ( ) {
407+ return document . documentElement . getAttribute ( "data-rawLocation" ) ?. split ( "/" ) ?. filter ( c => c !== "" ) ;
408+ }
409+
410+ /**
411+ * @template {keyof HTMLElementTagNameMap} T
412+ * @param {T } el type of element to create
413+ * @param {{ cls?: string | null, id?: string | null, href?: string | null } } attrs element attributes
414+ * @param {Array<HTMLElement | string | null> } chldr element children
415+ * @returns {HTMLElementTagNameMap[T] }
416+ */
417+ function render ( el , { cls = null , id = null , href = null , loc = null } = { } , chldr = [ ] ) {
418+ const r = document . createElement ( el ) ;
419+ if ( cls ) cls . split ( " " ) . filter ( x => x !== "" ) . forEach ( c => r . classList . add ( c ) ) ;
420+ if ( id ) r . id = id ;
421+ if ( href ) r . href = href ;
422+ if ( loc ) r . setAttribute ( "data-loc" , loc ) ;
423+ chldr . filter ( c => c !== null ) . forEach ( c =>
424+ r . appendChild ( typeof c === "string" ? document . createTextNode ( c ) : c )
425+ ) ;
426+ return r ;
427+ }
428+ function renderDynamicSideMenu ( ) {
429+ const pathToRoot = document . documentElement . getAttribute ( "data-pathToRoot" )
430+ const path = pathToRoot + "dynamicSideMenu.json" ;
431+ const rawLocation = getRawLoc ( ) ;
432+ const baseUrl = window . location . pathname . split ( "/" ) . slice ( 0 ,
433+ - 1 - pathToRoot . split ( "/" ) . filter ( c => c != "" ) . length
434+ ) ;
435+ function linkTo ( loc ) {
436+ return `${ baseUrl } /${ loc . join ( "/" ) } .html` ;
437+ }
438+ fetch ( path ) . then ( r => r . json ( ) ) . then ( menu => {
439+ function renderNested ( item , nestLevel , prefix , isApi ) {
440+ const name = item . name ;
441+ const newName =
442+ isApi && item . kind === "package" && name . startsWith ( prefix + "." )
443+ ? name . substring ( prefix . length + 1 )
444+ : name ;
445+ const newPrefix =
446+ prefix == ""
447+ ? newName
448+ : prefix + "." + newName ;
449+ const chldr =
450+ item . children . map ( x => renderNested ( x , nestLevel + 1 , newPrefix , isApi ) ) ;
451+ const link = render ( "span" , { cls : `nh ${ isApi ? "" : "de" } ` } , [
452+ chldr . length ? render ( "button" , { cls : "ar icon-button" } ) : null ,
453+ render ( "a" , { href : linkTo ( item . location ) } , [
454+ item . kind && render ( "span" , { cls : `micon ${ item . kind . slice ( 0 , 2 ) } ` } ) ,
455+ render ( "span" , { } , [ newName ] ) ,
456+ ] ) ,
457+ ] ) ;
458+ const loc = item . location . join ( "/" ) ;
459+ const ret = render ( "div" , { cls : `ni n${ nestLevel } ` , loc : item . location . join ( "/" ) } , [ link , ...chldr ] ) ;
460+ return ret ;
461+ }
462+ const d = render ( "div" , { cls : "switcher-container" } , [
463+ menu . docs && render ( "a" , {
464+ id : "docs-nav-button" ,
465+ cls : "switcher h100" ,
466+ href : linkTo ( menu . docs . location )
467+ } , [ "Docs" ] ) ,
468+ menu . api && render ( "a" , {
469+ id : "api-nav-button" ,
470+ cls : "switcher h100" ,
471+ href : linkTo ( menu . api . location )
472+ } , [ "API" ] ) ,
473+ ] ) ;
474+ const d1 = menu . docs && render ( "nav" , { cls : "side-menu" , id : "docs-nav" } ,
475+ menu . docs . children . map ( item => renderNested ( item , 0 , "" , false ) )
476+ ) ;
477+ const d2 = menu . api && render ( "nav" , { cls : "side-menu" , id : "api-nav" } ,
478+ menu . api . children . map ( item => renderNested ( item , 0 , "" , true ) )
479+ ) ;
480+
481+ document . getElementById ( "leftColumn" ) . appendChild ( d ) ;
482+ d1 && document . getElementById ( "leftColumn" ) . appendChild ( d1 ) ;
483+ d2 && document . getElementById ( "leftColumn" ) . appendChild ( d2 ) ;
484+ updateMenu = ( oldLoc , newLoc ) => {
485+ if ( oldLoc ) {
486+ const elem = document . querySelector ( `[data-loc="${ oldLoc . join ( "/" ) } "]` ) ;
487+ if ( elem ) updatePath ( elem , true ) ;
488+ }
489+ if ( d1 && d2 ) {
490+ if ( newLoc [ 0 ] && newLoc [ 0 ] == menu . api . location [ 0 ] ) {
491+ d1 . hidden = true ;
492+ d2 . hidden = false ;
493+ } else {
494+ d1 . hidden = false ;
495+ d2 . hidden = true ;
496+ }
497+ }
498+ const elem = document . querySelector ( `[data-loc="${ newLoc . join ( "/" ) } "]` ) ;
499+ if ( elem ) updatePath ( elem , false )
500+ }
501+ updateMenu ( null , rawLocation ) ;
502+
503+ window . dispatchEvent ( new Event ( DYNAMIC_PAGE_LOAD ) ) ;
504+ } )
505+ }
506+
368507window . addEventListener ( "DOMContentLoaded" , ( ) => {
369508 hljs . registerLanguage ( "scala" , highlightDotty ) ;
370509 hljs . registerAliases ( [ "dotty" , "scala3" ] , "scala" ) ;
371- window . dispatchEvent ( new Event ( DYNAMIC_PAGE_LOAD ) ) ;
510+
511+ dynamicSideMenu = document . documentElement . getAttribute ( "data-dynamicSideMenu" ) === "true" ;
512+ if ( dynamicSideMenu ) {
513+ renderDynamicSideMenu ( ) ;
514+ } else {
515+ window . dispatchEvent ( new Event ( DYNAMIC_PAGE_LOAD ) ) ;
516+ }
372517} ) ;
373518
374519const elements = document . querySelectorAll ( ".documentableElement" ) ;
0 commit comments