2022-10-06 17:45:23 +00:00
|
|
|
const ACTIVE_SECTION_CLASS = 'citizen-toc__listItem--active';
|
2022-05-17 19:10:14 +00:00
|
|
|
|
|
|
|
let /** @type {HTMLElement | undefined} */ activeSection;
|
|
|
|
|
2024-05-25 10:57:02 +00:00
|
|
|
/**
|
|
|
|
* Escapes double quotes in the given HTML attribute ID.
|
|
|
|
*
|
|
|
|
* @param {string} id - The HTML attribute ID to escape double quotes from.
|
|
|
|
* @return {string} The escaped HTML attribute ID with double quotes replaced.
|
|
|
|
*/
|
|
|
|
function escapeHtmlAttributeQuotes( id ) {
|
|
|
|
// Escapes double quotes in the given id
|
|
|
|
return id.replace( /"/g, '\\"' );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds a link element in the table of contents (TOC) based on the provided ID.
|
|
|
|
*
|
|
|
|
* @param {Element} toc - The table of contents element to search within.
|
|
|
|
* @param {string} id - The ID of the section to find the link for.
|
|
|
|
* @return {Element|null} The link element corresponding to the provided ID, or null if not found.
|
|
|
|
*/
|
|
|
|
function findLinkById( toc, id ) {
|
|
|
|
const sanitizedId = escapeHtmlAttributeQuotes( id );
|
|
|
|
const linkElement = toc.querySelector( `a[href="#${ sanitizedId }"]` );
|
|
|
|
return linkElement;
|
|
|
|
}
|
|
|
|
|
2022-05-17 19:10:14 +00:00
|
|
|
/**
|
2024-05-21 22:08:28 +00:00
|
|
|
* 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}
|
2022-05-17 19:10:14 +00:00
|
|
|
*/
|
2024-05-21 22:08:28 +00:00
|
|
|
function changeActiveSection( toc, id ) {
|
2024-05-25 10:57:02 +00:00
|
|
|
const link = findLinkById( toc, id );
|
2022-05-17 19:10:14 +00:00
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
2022-05-17 19:10:14 +00:00
|
|
|
}
|
2022-05-13 04:21:08 +00:00
|
|
|
|
2020-06-17 03:16:45 +00:00
|
|
|
/**
|
2021-04-21 19:07:55 +00:00
|
|
|
* Toggle active HTML class to items in table of content based on user viewport.
|
2022-05-17 19:10:14 +00:00
|
|
|
* Based on Vector
|
2020-07-05 21:07:36 +00:00
|
|
|
*
|
2023-04-30 22:01:53 +00:00
|
|
|
* @param {HTMLElement} bodyContent
|
2021-04-21 19:07:55 +00:00
|
|
|
* @return {void}
|
2020-06-17 03:16:45 +00:00
|
|
|
*/
|
2023-04-30 22:01:53 +00:00
|
|
|
function init( bodyContent ) {
|
2024-05-21 22:08:28 +00:00
|
|
|
const toc = document.getElementById( 'mw-panel-toc' );
|
|
|
|
if ( !toc ) {
|
2023-04-30 22:01:53 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-05-17 19:35:04 +00:00
|
|
|
|
2024-05-25 10:57:02 +00:00
|
|
|
const extractIds = () => {
|
|
|
|
return Array.from( toc.querySelectorAll( '.citizen-toc__listItem' ) )
|
|
|
|
.map( ( tocListEl ) => tocListEl.id.slice( 4 ) );
|
2023-07-12 02:01:04 +00:00
|
|
|
};
|
|
|
|
|
2024-05-25 10:57:02 +00:00
|
|
|
const queryElements = ( ids ) => {
|
|
|
|
return ids.map( ( id ) => bodyContent.querySelector( '#' + CSS.escape( id ) ) )
|
|
|
|
.filter( ( element ) => element !== null && element !== undefined );
|
2022-05-17 19:35:04 +00:00
|
|
|
};
|
2022-05-17 19:10:14 +00:00
|
|
|
|
2024-05-25 10:57:02 +00:00
|
|
|
const headlines = queryElements( extractIds() );
|
2023-07-12 02:01:04 +00:00
|
|
|
|
2024-05-25 10:57:02 +00:00
|
|
|
const computedStyle = window.getComputedStyle( document.documentElement );
|
|
|
|
const scrollPaddingTop = computedStyle.getPropertyValue( 'scroll-padding-top' );
|
|
|
|
const topMargin = Number( scrollPaddingTop.slice( 0, -2 ) ) + 20;
|
|
|
|
|
|
|
|
const getTopMargin = () => {
|
|
|
|
return topMargin;
|
|
|
|
};
|
2023-07-12 02:01:04 +00:00
|
|
|
|
2022-05-17 19:10:14 +00:00
|
|
|
const initSectionObserver = require( './sectionObserver.js' ).init;
|
|
|
|
|
|
|
|
const sectionObserver = initSectionObserver( {
|
2023-07-12 02:01:04 +00:00
|
|
|
elements: headlines,
|
2022-05-17 19:35:04 +00:00
|
|
|
topMargin: getTopMargin(),
|
2023-12-13 22:10:24 +00:00
|
|
|
onIntersection: ( section ) => {
|
2024-05-21 22:08:28 +00:00
|
|
|
if ( section.id && section.id.trim() !== '' ) {
|
|
|
|
changeActiveSection( toc, section.id );
|
|
|
|
}
|
2023-12-13 22:10:24 +00:00
|
|
|
}
|
2022-05-17 19:10:14 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
sectionObserver.resume();
|
2020-02-15 22:55:31 +00:00
|
|
|
}
|
2019-12-30 14:53:22 +00:00
|
|
|
|
2022-05-12 21:18:39 +00:00
|
|
|
module.exports = {
|
2023-04-30 22:01:53 +00:00
|
|
|
init: init
|
2022-05-12 21:18:39 +00:00
|
|
|
};
|