MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 122: | Line 122: | ||
// Wraps each text node in a <span data-deva="original"> so that | // Wraps each text node in a <span data-deva="original"> so that | ||
// applyScript() can flip the content without touching innerHTML. | // applyScript() can flip the content without touching innerHTML. | ||
// Covers both .mw-parser-output AND the TOC sidebar so that script | |||
// switching is truly global across content and navigation. | |||
var translatableSpans = []; | var translatableSpans = []; | ||
function tagTextNodes() { | function tagTextNodes() { | ||
// Collect all roots to walk: main content + Vector 2022 TOC sidebar | |||
var roots = []; | |||
var content = document.querySelector( '.mw-parser-output' ); | var content = document.querySelector( '.mw-parser-output' ); | ||
if ( | if ( content ) roots.push( content ); | ||
var | // Vector 2022 sidebar TOC lives outside .mw-parser-output | ||
var vtoc = document.querySelector( '.vector-toc' ); | |||
if ( vtoc ) roots.push( vtoc ); | |||
nodes.forEach( function ( node ) { | // Legacy inline TOC (#toc) is inside .mw-parser-output on older skins — | ||
// the data-deva guard prevents double-wrapping if it was already covered. | |||
// Only add it as a separate root when there is no .mw-parser-output. | |||
if ( !content ) { | |||
var ltoc = document.querySelector( '#toc' ); | |||
if ( ltoc ) roots.push( ltoc ); | |||
} | |||
roots.forEach( function ( root ) { | |||
var walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT ); | |||
var nodes = []; | |||
while ( walker.nextNode() ) nodes.push( walker.currentNode ); | |||
nodes.forEach( function ( node ) { | |||
var p = node.parentNode; | |||
if ( !p ) return; | |||
if ( p.hasAttribute && p.hasAttribute( 'data-deva' ) ) return; | |||
if ( p.closest ) { | |||
if ( p.closest( '.gr-script-bar' ) || // legacy bar (harmless guard) | |||
p.closest( '.gr-controls' ) || // toolbar controls | |||
p.closest( '.mw-editsection' ) ) return; | |||
} | |||
var orig = node.textContent; | |||
if ( !orig.trim() ) return; | |||
var span = document.createElement( 'span' ); | |||
span.setAttribute( 'data-deva', orig ); | |||
span.textContent = orig; | |||
p.replaceChild( span, node ); | |||
translatableSpans.push( span ); | |||
} ); | |||
} ); | } ); | ||
} | } | ||
| Line 165: | Line 181: | ||
} | } | ||
// ── TOC active-item highlight | // ── TOC active-item highlight + lazy TOC tagging ─────────────── | ||
// MutationObserver watches Vector 2022 TOC class changes and colours | // MutationObserver watches Vector 2022 TOC class changes and colours | ||
// the active link orange. This block is not modified. | // the active link orange. This block is not modified for the highlight | ||
// logic, but we additionally tag any TOC text nodes that were not yet | |||
// in the DOM when tagTextNodes() ran at boot (Vector 2022 renders the | |||
// TOC lazily on first scroll). | |||
function watchTocActive() { | function watchTocActive() { | ||
var toc = document.querySelector( '.vector-toc' ); | var toc = document.querySelector( '.vector-toc' ); | ||
if ( !toc || toc._grObserved ) return; | if ( !toc || toc._grObserved ) return; | ||
toc._grObserved = true; | toc._grObserved = true; | ||
// Tag any TOC text nodes that appeared after initial tagTextNodes() run | |||
function tagAndApplyToc() { | |||
var walker = document.createTreeWalker( toc, NodeFilter.SHOW_TEXT ); | |||
var nodes = []; | |||
while ( walker.nextNode() ) nodes.push( walker.currentNode ); | |||
var newSpans = []; | |||
nodes.forEach( function ( node ) { | |||
var p = node.parentNode; | |||
if ( !p ) return; | |||
if ( p.hasAttribute && p.hasAttribute( 'data-deva' ) ) return; | |||
var orig = node.textContent; | |||
if ( !orig.trim() ) return; | |||
var span = document.createElement( 'span' ); | |||
span.setAttribute( 'data-deva', orig ); | |||
span.textContent = orig; | |||
p.replaceChild( span, node ); | |||
translatableSpans.push( span ); | |||
newSpans.push( span ); | |||
} ); | |||
// Apply current script to any newly tagged spans | |||
if ( currentScript !== 'deva' && newSpans.length ) { | |||
newSpans.forEach( function ( span ) { | |||
span.textContent = transliterateText( span.getAttribute( 'data-deva' ), currentScript ); | |||
} ); | |||
} | |||
} | |||
// Tag immediately (catches TOC if it was already rendered) | |||
tagAndApplyToc(); | |||
var observer = new MutationObserver( function ( mutations ) { | var observer = new MutationObserver( function ( mutations ) { | ||
mutations.forEach( function ( m ) { | mutations.forEach( function ( m ) { | ||
if ( m.attributeName | // Tag any new text nodes added to the TOC (lazy render) | ||
if ( m.addedNodes && m.addedNodes.length ) { | |||
tagAndApplyToc(); | |||
} | |||
// Highlight active item | |||
if ( m.attributeName === 'class' ) { | |||
var li = m.target; | |||
var link = li.querySelector( ':scope > .vector-toc-link' ); | |||
if ( !link ) return; | |||
if ( li.classList.contains( 'vector-toc-list-item-active' ) ) { | |||
link.style.color = '#f57c00'; | |||
link.style.fontWeight = '700'; | |||
} else { | |||
link.style.color = ''; | |||
link.style.fontWeight = ''; | |||
} | |||
} | } | ||
} ); | } ); | ||
| Line 192: | Line 247: | ||
observer.observe( li, { attributes: true, attributeFilter: [ 'class' ] } ); | observer.observe( li, { attributes: true, attributeFilter: [ 'class' ] } ); | ||
} ); | } ); | ||
// Also watch for new list items being added (TOC populated lazily) | |||
observer.observe( toc, { childList: true, subtree: true } ); | |||
} | } | ||
// ── Init ──────────────────────────────────────────────────────── | // ── Init ──────────────────────────────────────────────────────── | ||
function init() { | function init() { | ||
var content | var content = document.querySelector( '.mw-parser-output' ); | ||
var alreadyTagged = content && content.querySelector( '[data-deva]' ); | var alreadyTagged = content && content.querySelector( '[data-deva]' ); | ||
if ( !alreadyTagged ) { | if ( !alreadyTagged ) { | ||
| Line 213: | Line 271: | ||
} | } | ||
// watchTocActive handles both highlight colouring AND lazy TOC tagging | |||
watchTocActive(); | watchTocActive(); | ||
} | } | ||