MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
/* MediaWiki:Common.js — grantha.io | /* MediaWiki:Common.js — grantha.io (v6) | ||
* | * | ||
* | * Changes vs v5: | ||
* 1. | * 1. TOC active highlight fix: Vector 2022 sets the active class on the | ||
* | * <li.vector-toc-list-item>, but Common.js wraps all text nodes inside | ||
* | * .vector-toc-text spans in <span data-deva="…">. The CSS colour rule | ||
* | * was targeting .vector-toc-list-item-active > a which never matched | ||
* because the <a> sits deeper and its text is now inside a <span>. | |||
* | * Fix: MutationObserver watches the <li> for class changes and directly | ||
* | * applies/removes the orange colour via inline style on the .vector-toc-link | ||
* | * ancestor, bypassing the CSS specificity war entirely. | ||
* 2. All other behaviour identical to v5. | |||
* the | |||
* | |||
*/ | */ | ||
| Line 125: | Line 122: | ||
function tagTextNodes() { | function tagTextNodes() { | ||
var content = document.querySelector( '.mw-parser-output' ); | var content = document.querySelector( '.mw-parser-output' ); | ||
if ( content ) { | if ( content ) { | ||
| Line 137: | Line 133: | ||
if ( p.hasAttribute && p.hasAttribute( 'data-deva' ) ) return; | if ( p.hasAttribute && p.hasAttribute( 'data-deva' ) ) return; | ||
if ( p.closest ) { | if ( p.closest ) { | ||
if ( p.closest( '.gr-controls' ) | if ( p.closest( '.gr-controls' ) || p.closest( '.mw-editsection' ) ) return; | ||
} | } | ||
var orig = node.textContent; | var orig = node.textContent; | ||
| Line 150: | Line 145: | ||
} | } | ||
// | // Tag .vector-toc-text spans for TOC transliteration | ||
document.querySelectorAll( '.vector-toc .vector-toc-text' ).forEach( function ( span ) { | document.querySelectorAll( '.vector-toc .vector-toc-text' ).forEach( function ( span ) { | ||
if ( span.hasAttribute( 'data-deva' ) ) return; | if ( span.hasAttribute( 'data-deva' ) ) return; | ||
var orig = span.textContent; | var orig = span.textContent; | ||
if ( !orig.trim() ) return; | if ( !orig.trim() ) return; | ||
| Line 169: | Line 159: | ||
currentScript = script; | currentScript = script; | ||
translatableSpans.forEach( function ( span ) { | translatableSpans.forEach( function ( span ) { | ||
if ( !span.parentNode ) return; | if ( !span.parentNode ) return; | ||
var orig = span.getAttribute( 'data-deva' ); | var orig = span.getAttribute( 'data-deva' ); | ||
if ( !orig ) return; | if ( !orig ) return; | ||
| Line 239: | Line 229: | ||
setLinkActive( li, isActive ); | setLinkActive( li, isActive ); | ||
/* Scroll active item into view | /* Scroll active item into view within the TOC container — | ||
* but ONLY when the TOC sidebar is actually visible and expanded. | |||
* If the TOC is collapsed or hidden, scrollIntoView scrolls the | |||
* whole page instead of just the TOC, which hijacks the reading | |||
* position. */ | |||
if ( isActive ) { | if ( isActive ) { | ||
var container = document.querySelector( '.vector-sticky-pinned-container' ); | var container = document.querySelector( '.vector-sticky-pinned-container' ); | ||
if ( container ) { | if ( container ) { | ||
var liRect | /* Check TOC is visible: the container must have nonzero height | ||
* and must not be hidden by the Vector "pinned/unpinned" toggle */ | |||
var tocVisible = container.offsetHeight > 0 && | |||
container.offsetParent !== null && | |||
window.getComputedStyle( container ).display !== 'none' && | |||
window.getComputedStyle( container ).visibility !== 'hidden'; | |||
if ( tocVisible ) { | |||
var liRect = li.getBoundingClientRect(); | |||
var cRect = container.getBoundingClientRect(); | |||
if ( liRect.top < cRect.top + 8 || liRect.bottom > cRect.bottom - 8 ) { | |||
/* Scroll only inside the TOC container, not the page */ | |||
var scrollParent = container.querySelector( '.vector-toc' ) || container; | |||
var offset = liRect.top - cRect.top; | |||
if ( offset < 8 || offset > cRect.height - 48 ) { | |||
scrollParent.scrollTop += offset - ( cRect.height / 2 ); | |||
} | |||
} | |||
} | } | ||
} | } | ||
| Line 289: | Line 296: | ||
structObserver.observe( toc, { childList: true, subtree: true } ); | structObserver.observe( toc, { childList: true, subtree: true } ); | ||
/* On initial load, | /* On initial load, colour the already-active item. | ||
* scrollIntoView is intentionally skipped here — calling it while the | |||
* TOC sidebar might be collapsed causes the PAGE to scroll to the element | |||
* rather than scrolling within the TOC container. The liObserver handles | |||
* scrolling the TOC as the user scrolls the page content. */ | |||
setTimeout( function () { | setTimeout( function () { | ||
var active = toc.querySelector( '.vector-toc-list-item-active' ); | var active = toc.querySelector( '.vector-toc-list-item-active' ); | ||
if ( active ) { | if ( active ) { | ||
setLinkActive( active, true ); | setLinkActive( active, true ); | ||
} | } | ||
}, 300 ); | }, 300 ); | ||
| Line 366: | Line 376: | ||
}() ); | }() ); | ||
// ── Main page: by-Grantha / by-Author toggle ────────────────── | // ── Main page: by-Grantha / by-Author toggle ────────────────── | ||
( function () { | ( function () { | ||
function grHomeView( v ) { | function grHomeView( v ) { | ||
var gView | var gView = document.getElementById( 'gr-view-grantha' ); | ||
var aView | var aView = document.getElementById( 'gr-view-author' ); | ||
var gBtn | var gBtn = document.getElementById( 'gr-toggle-grantha' ); | ||
var aBtn | var aBtn = document.getElementById( 'gr-toggle-author' ); | ||
if ( !gView || !aView || !gBtn || !aBtn ) return; | if ( !gView || !aView || !gBtn || !aBtn ) return; | ||
gView.style.display = ( v === 'grantha' ) ? '' : 'none'; | gView.style.display = ( v === 'grantha' ) ? '' : 'none'; | ||
aView.style.display = ( v === 'author' ) ? '' : 'none'; | aView.style.display = ( v === 'author' ) ? '' : 'none'; | ||
| Line 385: | Line 393: | ||
try { localStorage.setItem( 'gr_home_view', v ); } catch ( e ) {} | try { localStorage.setItem( 'gr_home_view', v ); } catch ( e ) {} | ||
} | } | ||
function initHomeToggle() { | function initHomeToggle() { | ||
var gBtn = document.getElementById( 'gr-toggle-grantha' ); | var gBtn = document.getElementById( 'gr-toggle-grantha' ); | ||
var aBtn = document.getElementById( 'gr-toggle-author' ); | var aBtn = document.getElementById( 'gr-toggle-author' ); | ||
if ( !gBtn || !aBtn ) return; | if ( !gBtn || !aBtn ) return; | ||
gBtn.addEventListener( 'click', function () { grHomeView( 'grantha' ); } ); | gBtn.addEventListener( 'click', function () { grHomeView( 'grantha' ); } ); | ||
aBtn.addEventListener( 'click', function () { grHomeView( 'author' ); } ); | aBtn.addEventListener( 'click', function () { grHomeView( 'author' ); } ); | ||
[ gBtn, aBtn ].forEach( function ( btn ) { | [ gBtn, aBtn ].forEach( function ( btn ) { | ||
btn.addEventListener( 'keydown', function ( e ) { | btn.addEventListener( 'keydown', function ( e ) { | ||
| Line 399: | Line 406: | ||
} ); | } ); | ||
} ); | } ); | ||
var saved; | var saved; | ||
try { saved = localStorage.getItem( 'gr_home_view' ); } catch ( e ) {} | try { saved = localStorage.getItem( 'gr_home_view' ); } catch ( e ) {} | ||
if ( saved === 'author' ) grHomeView( 'author' ); | if ( saved === 'author' ) grHomeView( 'author' ); | ||
} | } | ||
if ( document.readyState === 'loading' ) { | if ( document.readyState === 'loading' ) { | ||
document.addEventListener( 'DOMContentLoaded', initHomeToggle ); | document.addEventListener( 'DOMContentLoaded', initHomeToggle ); | ||