@@ -19,10 +19,13 @@ import "../../../../../node_modules/details-polyfill";
19
19
// Add support for URLSearchParams Web API in IE
20
20
import "../../../../../node_modules/url-search-params-polyfill" ;
21
21
22
- export function init_headers ( right_col , lang_strings ) {
22
+ // Vocab:
23
+ // TOC = table of contents
24
+ // OTP = on this page
25
+ export function init_headers ( sticky_content , lang_strings ) {
23
26
// Add on-this-page block
24
- var this_page = $ ( '<div id="this_page"></div>' ) . prependTo ( right_col ) ;
25
- this_page . append ( '<h2 >' + lang_strings ( 'On this page' ) + '</h2 >' ) ;
27
+ var this_page = $ ( '<div id="this_page"></div>' ) . prependTo ( sticky_content ) ;
28
+ this_page . append ( '<p id="otp" class="aside-heading" >' + lang_strings ( 'On this page' ) + '</p >' ) ;
26
29
var ul = $ ( '<ul></ul>' ) . appendTo ( this_page ) ;
27
30
var items = 0 ;
28
31
var baseHeadingLevel = 0 ;
@@ -57,7 +60,7 @@ export function init_headers(right_col, lang_strings) {
57
60
. remove ( ) ;
58
61
var text = title_container . html ( ) ;
59
62
const adjustedLevel = hLevel - baseHeadingLevel ;
60
- const li = '<li class="heading-level-' + adjustedLevel + '"><a href="#' + this . id + '">' + text + '</a></li>' ;
63
+ const li = '<li id="otp-text-' + i + '" class="heading-level-' + adjustedLevel + '"><a href="#' + this . id + '">' + text + '</a></li>' ;
61
64
ul . append ( li ) ;
62
65
}
63
66
}
@@ -170,6 +173,44 @@ function init_toc(lang_strings) {
170
173
} ) ;
171
174
}
172
175
176
+ // In the OTP, highlight the heading of the section that is
177
+ // currently visible on the page.
178
+ // If more than one is visible, highlight the heading for the
179
+ // section that is higher on the page.
180
+ function highlight_otp ( ) {
181
+ let visibleHeadings = [ ]
182
+ const observer = new IntersectionObserver ( entries => {
183
+ entries . forEach ( entry => {
184
+ const id = entry . target . getAttribute ( 'id' ) ;
185
+ const element = document . querySelector ( `#sticky_content #this_page a[href="#${ id } "]` ) ;
186
+ const itemId = $ ( element ) . parent ( ) . attr ( 'id' )
187
+ // All heading elements have an `entry` (even the title).
188
+ // The title does not exist in the OTP, so we must exclude it.
189
+ // Checking for the existence of `itemId` ensures we don't parse elements that don't exist.
190
+ if ( itemId ) {
191
+ const itemNumber = parseInt ( itemId . match ( / \d + / ) [ 0 ] , 10 ) ;
192
+ if ( entry . intersectionRatio > 0 ) {
193
+ visibleHeadings . push ( itemNumber ) ;
194
+ } else {
195
+ const position = visibleHeadings . indexOf ( itemNumber ) ;
196
+ visibleHeadings . splice ( position , 1 )
197
+ }
198
+ if ( visibleHeadings . length > 0 ) {
199
+ visibleHeadings . sort ( ( a , b ) => a - b )
200
+ // Remove existing active classes
201
+ $ ( 'a.active' ) . removeClass ( "active" ) ;
202
+ // Add active class to the first visible heading
203
+ $ ( '#otp-text-' + visibleHeadings [ 0 ] + ' > a' ) . addClass ( 'active' )
204
+ }
205
+ }
206
+ } )
207
+ } )
208
+
209
+ document . querySelectorAll ( '#guide a[id]' ) . forEach ( ( heading ) => {
210
+ observer . observe ( heading ) ;
211
+ } )
212
+ }
213
+
173
214
// Main function, runs on DOM ready
174
215
$ ( function ( ) {
175
216
var lang = $ ( 'section#guide[lang]' ) . attr ( 'lang' ) || 'en' ;
@@ -228,7 +269,16 @@ $(function() {
228
269
229
270
AlternativeSwitcher ( store ( ) ) ;
230
271
231
- var right_col = $ ( '#right_col' ) ; // Move rtp container to top right and make visible
272
+ // Move rtp container to top right and make visible
273
+ var sticky_content = $ ( '#sticky_content' ) ;
274
+ // Left column that contains the TOC
275
+ var left_col = $ ( '#left_col' ) ;
276
+ // Middle column that contains the main content
277
+ var middle_col = $ ( '#middle_col' ) ;
278
+ // Right column that contains the OTP and demand gen content
279
+ var right_col = $ ( '#right_col' ) ;
280
+ // Empty column below TOC on small screens so the demand gen content can be positioned under the main content
281
+ var bottom_left_col = $ ( '#bottom_left_col' ) ;
232
282
233
283
$ ( '.page_header > a[href="../current/index.html"]' ) . click ( function ( ) {
234
284
utils . get_current_page_in_version ( 'current' ) ;
@@ -271,14 +321,24 @@ $(function() {
271
321
if ( div . length == 0 && $ ( '#guide' ) . find ( 'div.article,div.book' ) . length == 0 ) {
272
322
var url = location . href . replace ( / [ ^ \/ ] + $ / , 'toc.html' ) ;
273
323
var toc = $ . get ( url , { } , function ( data ) {
274
- right_col . append ( data ) ;
324
+ left_col . append ( data ) ;
275
325
init_toc ( LangStrings ) ;
276
326
utils . open_current ( location . pathname ) ;
277
327
} ) . always ( function ( ) {
278
- init_headers ( right_col , LangStrings ) ;
328
+ init_headers ( sticky_content , LangStrings ) ;
329
+ highlight_otp ( ) ;
279
330
} ) ;
280
331
} else {
281
332
init_toc ( LangStrings ) ;
333
+ // Style book landing page (no main content, just a TOC and demand gen content)
334
+
335
+ // Set the width of the left column to zero
336
+ left_col . removeClass ( ) . addClass ( 'col-0' ) ;
337
+ bottom_left_col . removeClass ( ) . addClass ( 'col-0' ) ;
338
+ // Set the width of the middle column (containing the TOC) to 9
339
+ middle_col . removeClass ( ) . addClass ( 'col-12 col-lg-9 guide-section' ) ;
340
+ // Set the width of the demand gen content to 3
341
+ right_col . removeClass ( ) . addClass ( 'col-12 col-lg-3 sticky-top-md h-almost-full-lg' ) ;
282
342
}
283
343
284
344
PR . prettyPrint ( ) ;
@@ -299,6 +359,26 @@ $(function() {
299
359
$ ( 'a.edit_me_private' ) . show ( ) ;
300
360
}
301
361
362
+ // scroll to selected TOC element; if it doesn't exist yet, wait and try again
363
+ // window.width must match the breakpoint of `.sticky-top-md`
364
+ if ( $ ( window ) . width ( ) >= 769 ) {
365
+ var scrollToSelectedTOC = setInterval ( ( ) => {
366
+ if ( $ ( '.current_page' ) . length ) {
367
+ // Get scrollable element
368
+ var container = document . querySelector ( "#left_col" ) ;
369
+ // Get active table of contents element
370
+ var activeItem = document . querySelector ( ".current_page" )
371
+ // If the top of the active item is out of view (or in the bottom 100px of the visible portion of the TOC)
372
+ // scroll so the top of the active item is at the top of the visible portion TOC
373
+ if ( container . offsetHeight - 100 <= activeItem . offsetTop ) {
374
+ // Scroll to active item
375
+ container . scrollTop = activeItem . offsetTop
376
+ }
377
+ clearInterval ( scrollToSelectedTOC ) ;
378
+ }
379
+ } , 150 ) ;
380
+ }
381
+
302
382
// Test comment used to detect unminifed JS in tests
303
383
} ) ;
304
384
0 commit comments