mediawiki-extensions-Syntax.../modules/pygments.linenumbers.js
Ed Sanders d6cc861de7 Allow line ranges to be selected using shift+click
Change-Id: I224de2d2d72525b61608d7b6d0f13ce84a699ef7
2024-03-06 22:30:51 +00:00

116 lines
3 KiB
JavaScript

$( function () {
/**
* Parse a line ID, e.g. "L-18"
*
* @param {string} id Line ID fragment
* @return {Object} Object with a string prefix and number
*/
function parseId( id ) {
const matches = id.match( /(.*)-([0-9]+)/ );
return {
prefix: matches[ 1 ],
number: +matches[ 2 ]
};
}
/**
* Build a line ID from a parsed ID
*
* @param {Object} parsedId See #parseId
* @return {string} ID fragment
*/
function buildId( parsedId ) {
return parsedId.prefix + '-' + parsedId.number;
}
/**
* Get a line element from an ID
*
* @param {string} id ID
* @return {HTMLElement|null} Line element, or null if not found (or the element is not a line)
*/
function getLineElement( id ) {
const line = mw.util.getTargetFromFragment( id );
// Support IE 11, Edge 14, Safari 7: Can't use unprefixed Element.matches('… *') yet.
if ( !$( line ).closest( '.mw-highlight' ).length ) {
// Element not in a highlight block
return null;
}
return line;
}
let lastLines, lastAnchorLine;
/**
* Handle hash change events
*
* @param {boolean} scrollIntoView Scroll the selected lines into view
*/
function onHashChange( scrollIntoView ) {
const hash = location.hash.slice( 1 );
const lines = [];
let anchorLine, focusLine;
const parts = hash.split( '--' );
if ( parts.length === 2 ) {
anchorLine = getLineElement( parts[ 0 ] );
focusLine = getLineElement( parts[ 1 ] );
if ( anchorLine && focusLine ) {
const anchorId = parseId( parts[ 0 ] );
const focusId = parseId( parts[ 1 ] );
if ( anchorId.prefix === focusId.prefix ) {
for ( let i = Math.min( anchorId.number, focusId.number ); i <= Math.max( anchorId.number, focusId.number ); i++ ) {
lines.push( mw.util.getTargetFromFragment( buildId( { prefix: anchorId.prefix, number: i } ) ) );
}
if ( scrollIntoView ) {
// A line range will not automatically scroll into view
lines[ 0 ].scrollIntoView();
}
}
}
} else {
anchorLine = getLineElement();
if ( anchorLine ) {
lines.push( anchorLine );
}
}
lastAnchorLine = anchorLine;
if ( lastLines ) {
lastLines.forEach( ( line ) => line.classList.remove( 'hll' ) );
}
lines.forEach( ( line ) => line.classList.add( 'hll' ) );
lastLines = lines;
}
window.addEventListener( 'hashchange', onHashChange );
$( document.body ).on( 'click', '.mw-highlight .linenos', ( e ) => {
e.preventDefault();
const targetUrl = new URL( e.target.parentNode.href );
if ( e.shiftKey && lastAnchorLine ) {
const anchorId = parseId( lastAnchorLine.getAttribute( 'id' ) );
const focusId = parseId( targetUrl.hash.slice( 1 ) );
if ( anchorId.prefix === focusId.prefix ) {
const hash = buildId( anchorId ) + '--' + buildId( focusId );
history.replaceState( null, '', '#' + hash );
} else {
history.replaceState( null, '', targetUrl );
}
} else {
history.replaceState( null, '', targetUrl );
}
onHashChange();
} );
// Check hash on load
onHashChange( true );
}() );