Skip to content

Instantly share code, notes, and snippets.

@jdlrobson
Created July 4, 2024 01:36
Show Gist options
  • Save jdlrobson/13cd58574c4248cfec13d353269f3dcd to your computer and use it in GitHub Desktop.
Save jdlrobson/13cd58574c4248cfec13d353269f3dcd to your computer and use it in GitHub Desktop.
(function () {
const HEADING_TAGS = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ];
/** Cache of headings in page. Cleared any time wikipage.content hook is called. */
let headingsInPageCached;
mw.hook( 'wikipage.content' ).add( () => {
headingsInPageCached = null;
} );
/**
* @callback {Function} InsertActionCallback
* @param {Element} link
*
* @typedef {Object} HeadingObject
* @property {string} anchor ID of heading for use with document.getElementById
* @property {element} element of heading
* @property {string} line text of heading
* @property {string} level of heading e.g. '2' for h2.
* @property {InsertActionCallback} insertAction for adding links to heading
*/
/**
* Gets all heading elements in the page.
*
* @param {Element} heading
* @return {HeadingObject|null} null if couldn't be resolved to a heading.
*/
const getHeading = function ( heading ) {
const HEADLINE_SELECTOR = [ '.mw-headline', ...HEADING_TAGS.map( ( tag ) => `${ tag }[id]` ) ]
.map( ( sel ) => `.mw-parser-output ${ sel }` ).join( ', ' );
const headlineElement = heading.querySelector( HEADLINE_SELECTOR );
/**
* Helper function
* @ignore
* @param {string} text
* @return {Element}
*/
const insertBracket = ( text ) => {
const bracket = document.createElement( 'span' );
bracket.textContent = text;
bracket.setAttribute( 'class', 'mw-editsection-bracket' );
return bracket;
};
/**
* Helper function
* @type {InsertActionCallback}
*/
const insertAction = ( action ) => {
const node = document.createElement( 'span' );
node.setAttribute( 'class', 'mw-editsection' );
node.appendChild( insertBracket( '[' ) );
node.appendChild( action );
node.appendChild( insertBracket( ']' ) );
heading.appendChild( node );
};
// Keys should match counterparts in skins/components/SkinComponentTableOfContents.php
return headlineElement ? {
element: heading,
level: heading.tagName.replace( 'H', '' ),
anchor: headlineElement.id,
line: headlineElement.textContent,
insertAction
} : null;
};
mw.util.getHeadings = function () {
if ( headingsInPageCached ) {
return headingsInPageCached;
}
// Support two variants of heading markup: (see T13555, T358452)
// (old) <h2> <span class="mw-headline" id="...">...</span> ... </h2>
// (new) <div class="mw-heading"> <h2 id="...">...</h2> ... </div>
// [more information: https://www.mediawiki.org/wiki/Heading_HTML_changes]
const HEADING_SELECTOR = [ '.mw-heading', ...HEADING_TAGS.map( ( tag ) => `${ tag }:not([id])` ) ]
.map( ( sel ) => `.mw-parser-output ${ sel }` ).join( ', ' );
const contentElement = document.querySelector( '#mw-content-text.mw-body-content' );
if ( contentElement ) {
headingsInPageCached = Array.from(
contentElement.querySelectorAll( HEADING_SELECTOR )
).map( getHeading );
return headingsInPageCached;
} else {
return [];
}
};
}())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment