mediawiki-skins-Citizen/resources/skins.citizen.scripts/tableOfContents.js

94 lines
2.5 KiB
JavaScript
Raw Normal View History

const ACTIVE_SECTION_CLASS = 'citizen-toc__listItem--active';
let /** @type {HTMLElement | undefined} */ activeSection;
/**
* Changes the active section in the table of contents based on the provided ID.
*
* @param {HTMLElement} toc - The Table of Content HTML element
* @param {string} id - The ID of the section to make active.
* @return {void}
*/
function changeActiveSection( toc, id ) {
const getLink = ( hash ) => {
const el = toc.querySelector( `a[href="#${ hash }"], a[href="#${ encodeURIComponent( hash ) }"]` );
return el;
};
const link = getLink( id );
if ( activeSection ) {
activeSection.classList.remove( ACTIVE_SECTION_CLASS );
activeSection = undefined;
}
2023-07-30 23:44:34 +00:00
if ( link ) {
activeSection = link.parentNode;
activeSection.classList.add( ACTIVE_SECTION_CLASS );
}
}
2020-06-17 03:16:45 +00:00
/**
* Toggle active HTML class to items in table of content based on user viewport.
* Based on Vector
*
* @param {HTMLElement} bodyContent
* @return {void}
2020-06-17 03:16:45 +00:00
*/
function init( bodyContent ) {
const toc = document.getElementById( 'mw-panel-toc' );
if ( !toc ) {
return;
}
const getHeadlineElements = () => {
const headlineElements = [];
Array.from( toc.querySelectorAll( '.citizen-toc__listItem' ) ).forEach( ( tocListEl ) => {
// Remove 'toc-' prefix from ID
const headlineElement = bodyContent.querySelector( '#' + CSS.escape( tocListEl.id.slice( 4 ) ) );
if ( headlineElement ) {
headlineElements.push( headlineElement );
}
} );
return headlineElements;
};
// We use scroll-padding-top to handle scrolling with fixed header
// It is better to respect that so it is consistent
const getTopMargin = () => {
const computedStyle = window.getComputedStyle( document.documentElement );
return Number(
computedStyle.getPropertyValue( 'scroll-padding-top' )
.slice( 0, -2 )
) + 20;
};
const headlines = getHeadlineElements();
// Do not continue if there are no headlines
// TODO: Need to revamp the selector so that it works better with MW 1.40,
// currently MW 1.40 has ToC on non-content pages as well
if ( !headlines ) {
return;
}
const initSectionObserver = require( './sectionObserver.js' ).init;
const sectionObserver = initSectionObserver( {
elements: headlines,
topMargin: getTopMargin(),
onIntersection: ( section ) => {
if ( section.id && section.id.trim() !== '' ) {
changeActiveSection( toc, section.id );
}
}
} );
// TODO: Pause section observer on ToC link click
sectionObserver.resume();
2020-02-15 22:55:31 +00:00
}
module.exports = {
init: init
};