1- $ ( document ) . ready ( function ( ) {
2-
3- /**
4- * TODO: Refactor with intent toward pure functions. Mutation of state can lead to bugs and difficult debugging.
5- */
6-
7- var toc = navData . toc ;
8- var flatToc = navData . flatToc . reverse ( ) ;
9-
10- function collectNodes ( tocMap ) {
11- var tocNodes = { } ;
12- tocMap . map ( function ( node , index ) {
13- var sectionNode = $ ( '#' + node . section ) ;
14- var tocSubsections = { } ;
15- tocItem = { section : sectionNode } ;
16- var subsectionNodes ;
17- if ( node . subsections ) {
18- subsectionNodes = ( collectNodes ( node . subsections ) ) ;
19- tocItem . subsections = subsectionNodes ;
20- }
21- tocNodes [ node . section ] = tocItem ;
22- } ) ;
23- return tocNodes ;
24- }
25- var tocItems = collectNodes ( toc ) ;
26-
27- function collectNodesFlat ( tocMap , obj ) {
28- var collect = obj || { } ;
29- tocMap . map ( function ( node , index ) {
30- var sectionNode = $ ( '#' + node . section ) ;
31- tocItem = { section : sectionNode } ;
32- if ( node . subsections ) {
33- subsectionNodes = ( collectNodesFlat ( node . subsections , collect ) ) ;
34- }
35- collect [ node . section ] = sectionNode ;
36- } ) ;
37- return collect ;
38- }
39- var tocFlat = collectNodesFlat ( toc ) ;
40-
41- var prevSectionToken ;
42- var prevSubsectionToken ;
43- var activeTokensObj = { } ;
44-
45- function scrollActions ( scrollPosition ) {
46- var activeSection = checkNodePositions ( toc , tocFlat , scrollPosition ) ;
47- var activeSubSection ,
48- prevL1Nav ,
49- currL1Nav ,
50- prevL2Nav ,
51- currL2Nav ;
52-
53- // No active section - return existing activeTokensObj (may be empty)
54- if ( ! activeSection ) {
55- return activeTokensObj ;
56- }
57-
58- /**
59- * This block deals with L1Nav sections
60- */
61-
62- // If no previous token, set previous to current active and show L1Nav
63- if ( ! prevSectionToken ) {
64- prevSectionToken = activeSection . token ;
65- currL1Nav = getNavNode ( activeSection . token ) ;
66- currL1Nav . show ( 'fast' ) ;
67- }
68- // If active is not the same as previous, hide previous L1Nav and show current L1Nav; set previous to current
69- else if ( activeSection . token !== prevSectionToken ) {
70- prevL1Nav = getNavNode ( prevSectionToken ) ;
71- currL1Nav = getNavNode ( activeSection . token ) ;
72- prevL1Nav . hide ( 'fast' ) ;
73- currL1Nav . show ( 'fast' ) ;
74- prevSectionToken = activeSection . token ;
75- }
76-
77- /**
78- * This block deals with L2Nav subsections
79- */
80-
81- // If there is a subsections array and it has a non-zero length, set active subsection
82- if ( activeSection . subsections && activeSection . subsections . length !== 0 ) {
83- activeSubSection = checkNodePositions ( activeSection . subsections , tocFlat , scrollPosition ) ;
84- if ( activeSubSection ) {
85- if ( ! prevSubsectionToken ) {
86- prevSubsectionToken = activeSubSection . token ;
87- currL2Nav = getNavNode ( activeSubSection . token ) ;
88- currL2Nav . show ( 'fast' ) ;
89- } else if ( activeSubSection . token !== prevSubsectionToken ) {
90- prevL2Nav = getNavNode ( prevSubsectionToken ) ;
91- currL2Nav = getNavNode ( activeSubSection . token ) ;
92- prevL2Nav . hide ( 'fast' ) ;
93- currL2Nav . show ( 'fast' ) ;
94- prevSubsectionToken = activeSubSection . token ;
95- }
96- } else {
97- prevL2Nav = getNavNode ( prevSubsectionToken ) ;
98- prevL2Nav . hide ( 'fast' ) ;
99- prevSubsectionToken = null ;
100- }
101- }
102- activeTokensObj . L1 = prevSectionToken ;
103- activeTokensObj . L2 = prevSubsectionToken ;
104- return activeTokensObj ;
1+ jQuery ( function ( $ ) {
2+ // tocItems are headings in the main content area that have a representation in the TOC
3+ // (not all headings are present in the TOC).
4+ let tocItems = $ ( '#page-content-wrapper .toc-item' ) ;
5+
6+ function getCurrentlyVisibleSection ( scrollPosition ) {
7+ // Walk the list from the bottom up and check;
8+ // each TOC item is the immediate child of a <div> that
9+ // carries the TOC item's ID.
10+ for ( let i = tocItems . length - 1 ; i >= 0 ; -- i ) {
11+ let item = $ ( tocItems . get ( i ) ) ;
12+ let offsetTop = item . offset ( ) . top - 50 ;
13+
14+ if ( scrollPosition >= offsetTop ) {
15+ return item . parent ( ) . attr ( 'id' ) ;
16+ }
10517 }
10618
107- /**
108- * Checks for active elements by scroll position
109- */
110-
111- var prevElemToken ;
112- var activeElemToken ;
113-
114- function checkActiveElement ( items , scrollPosition ) {
115- var offset = 50 ;
116- var offsetScroll = scrollPosition + offset ;
117- var visibleNode ;
118- for ( var i = 0 ; i < items . length ; i ++ ) {
119- var token = items [ i ] ;
120- var node = getHeadingNode ( token ) ;
121- if ( offsetScroll >= node . offset ( ) . top ) {
122- activeElemToken = token ;
123- }
124- }
125- if ( ! prevElemToken ) {
126- getNavElemNode ( activeElemToken ) . addClass ( 'selected' ) ;
127- prevElemToken = activeElemToken ;
128- return ;
129- }
130- if ( activeElemToken !== prevElemToken ) {
131- getNavElemNode ( prevElemToken ) . removeClass ( 'selected' ) ;
132- getNavElemNode ( activeElemToken ) . addClass ( 'selected' ) ;
133- prevElemToken = activeElemToken ;
134- }
135- return activeElemToken ;
136- }
19+ return null ;
20+ }
13721
138- function getHeadingNode ( token ) {
139- return $ ( '#' + token ) ;
140- }
22+ function updateNavigationState ( visibleSection ) {
23+ let selectedLink = $ ( '#navigation a.selected' ) ;
24+ let selectedSection = selectedLink . length === 0 ? null : selectedLink . attr ( 'href' ) . replace ( / # / , '' ) ;
14125
142- function getNavNode ( token ) {
143- return $ ( '#' + token + '-nav' ) ;
26+ // nothing to do :)
27+ if ( visibleSection === selectedSection ) {
28+ return ;
14429 }
14530
146- function getNavElemNode ( token ) {
147- return $ ( '#sidebar-wrapper > ul a[href="#' + token + '"]' ) ;
31+ // un-select whatever was previously selected
32+ if ( selectedLink . length > 0 ) {
33+ selectedLink . removeClass ( 'selected' ) ;
14834 }
14935
150- function checkNodePositions ( nodes , flatNodeMap , scrollPosition ) {
151- var activeNode ;
152- for ( var i = 0 ; i < nodes . length ; i ++ ) {
153- var item = nodes [ i ] ;
154- var node = flatNodeMap [ item . section ] ;
155- var nodeTop = node . offset ( ) . top - 50 ;
156- if ( scrollPosition >= nodeTop ) {
157- activeNode = { token : item . section , node : node } ;
158-
159- if ( item . subsections ) {
160- activeNode . subsections = item . subsections ;
161- }
162- break ;
163- }
164- }
165- return activeNode ;
36+ if ( visibleSection !== null ) {
37+ // show the leaf node in the navigation for the activeSection, plus
38+ // all nodes that lead to it (in a->b->c->d, if c is active, show
39+ // b and c because a is always shown already).
40+ let activeSectionLink = '#' + visibleSection ;
41+ let link = $ ( '#navigation a[href="' + activeSectionLink + '"]' ) ;
42+ let listItem = link . parent ( ) ;
43+
44+ link . addClass ( 'selected' ) ;
45+
46+ while ( listItem . data ( 'level' ) > 1 ) {
47+ let ul = listItem . parent ( ) ;
48+ ul . show ( 'fast' ) ;
49+ listItem = ul . parent ( ) ;
50+ }
51+
52+ // expand the currently selected item, i.e. do not just show the
53+ // parent path, but also the immediate child <ul>
54+ link . parent ( ) . children ( 'ul' ) . show ( 'fast' ) ;
16655 }
16756
168- function scrollToNav ( token ) {
169- setTimeout ( function ( ) {
170- var scrollPosition = $ ( window ) . scrollTop ( ) ;
171- var activeSectionTokens = scrollActions ( scrollPosition ) ;
172- var activeElemToken = checkActiveElement ( flatToc , scrollPosition ) ;
173- var navNode = $ ( '#sidebar-wrapper > ul a[href="#' + token + '"]' ) ;
174- $ ( '#sidebar-wrapper' ) . scrollTo ( navNode , { duration : 'fast' , axis : 'y' } ) ;
175- } , 200 ) ;
176- }
177-
178- $ ( window ) . on ( 'hashchange' , function ( event ) {
179- var scrollPosition = $ ( window ) . scrollTop ( ) ;
180- var activeSectionTokens = scrollActions ( scrollPosition ) ;
181- var activeElemToken = checkActiveElement ( flatToc , scrollPosition ) ;
182- var scrollToken = activeSectionTokens . L2 ? activeSectionTokens . L2 : activeSectionTokens . L1 ;
183- scrollToNav ( scrollToken ) ;
184- var token = location . hash . slice ( 1 ) ;
57+ // collapse all sub tree that are not required to show the currently
58+ // selected section (i.e. has no .selected in its children, is not
59+ // the direct descendant of the currently selected item (which we
60+ // just expanded) and is not the top level).
61+ $ ( '#navigation ul:visible' ) . each ( function ( ) {
62+ let list = $ ( this ) ;
63+ if ( list . find ( '.selected' ) . length > 0 ) {
64+ return ;
65+ }
66+
67+ let listItem = list . parent ( ) ;
68+ if ( listItem . data ( 'level' ) <= 1 ) {
69+ return ;
70+ }
71+
72+ let link = listItem . children ( 'a' ) ;
73+ if ( link . hasClass ( 'selected' ) ) {
74+ return ;
75+ }
76+
77+ list . hide ( 'fast' ) ;
18578 } ) ;
79+ }
18680
187- var scrollPosition = $ ( window ) . scrollTop ( ) ;
188- scrollActions ( scrollPosition ) ;
189- checkActiveElement ( flatToc , scrollPosition ) ;
190- // TODO: prevent scroll on sidebar from propagating to window
191- $ ( window ) . on ( 'scroll' , function ( event ) {
192- var scrollPosition = $ ( window ) . scrollTop ( ) ;
193- var activeSectionTokens = scrollActions ( scrollPosition ) ;
194- var activeElemToken = checkActiveElement ( flatToc , scrollPosition ) ;
195- } ) ;
196- } ) ;
81+ let navScrollTimeout ;
82+ function scrollToNav ( currentSection ) {
83+ // debounce to prevent too many scrolls in the sidebar
84+ if ( navScrollTimeout ) {
85+ clearTimeout ( navScrollTimeout ) ;
86+ }
87+
88+ navScrollTimeout = setTimeout ( function ( ) {
89+ let navNode = $ ( '#navigation a[href="#' + currentSection + '"]' ) ;
90+ $ ( '#sidebar-wrapper' ) . scrollTo ( navNode , { duration : 'fast' , axis : 'y' , over : { top : - 1 } } ) ;
91+ } , 200 ) ;
92+ }
93+
94+ function repaint ( ) {
95+ let scrollPosition = $ ( window ) . scrollTop ( ) ;
96+ let currentSection = getCurrentlyVisibleSection ( scrollPosition ) ;
97+ updateNavigationState ( currentSection ) ;
98+ scrollToNav ( currentSection ) ;
99+ }
100+
101+ // update navigation whenever scrolling happens
102+ $ ( window ) . on ( 'scroll' , repaint ) ;
103+
104+ // perform an initial update on the navigation
105+ repaint ( ) ;
106+ } ) ;
0 commit comments