MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 122: | Line 122: | ||
// ── Tag all transliteratable text nodes once per page ─────────── | // ── Tag all transliteratable text nodes once per page ─────────── | ||
var translatableSpans = []; | var translatableSpans = []; | ||
function tagTextNodes() { | function tagTextNodes() { | ||
// | // ── Main article content ────────────────────────────────────── | ||
var content = document.querySelector( '.mw-parser-output' ); | var content = document.querySelector( '.mw-parser-output' ); | ||
if ( | if ( content ) { | ||
var walker = document.createTreeWalker( content, 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-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 ); | |||
} ); | |||
} | |||
// ── Sidebar TOC (.vector-toc-text spans) ───────────────────── | |||
// We target the .vector-toc-text spans that Vector itself renders, | |||
// rather than walking raw text nodes. Mutating textContent of an | |||
// existing child span is a characterData mutation — Vector's own | |||
// MutationObserver only watches for childList changes on the <li>, | |||
// so it will NOT fire, and the "unknown title" bug is avoided. | |||
document.querySelectorAll( '.vector-toc .vector-toc-text' ).forEach( function ( span ) { | |||
if ( span.hasAttribute( 'data-deva' ) ) return; // already tagged | |||
var orig = | var orig = span.textContent; | ||
if ( !orig.trim() ) return; | if ( !orig.trim() ) return; | ||
span.setAttribute( 'data-deva', orig ); | span.setAttribute( 'data-deva', orig ); | ||
translatableSpans.push( span ); | translatableSpans.push( span ); | ||
} ); | } ); | ||
| Line 176: | Line 178: | ||
} | } | ||
// ── TOC active-item highlight ─────────────────────────────────── | // ── TOC active-item highlight ─────────────────────────────────── | ||
function watchTocActive() { | function watchTocActive() { | ||
var toc = document.querySelector( '.vector-toc' ); | var toc = document.querySelector( '.vector-toc' ); | ||
| Line 196: | Line 191: | ||
var observer = new MutationObserver( function ( mutations ) { | var observer = new MutationObserver( function ( mutations ) { | ||
mutations.forEach( function ( m ) { | mutations.forEach( function ( m ) { | ||
// New list items added (lazy render) → attach highlight | // New list items added (lazy render) → attach highlight + tag for transliteration | ||
if ( m.type === 'childList' ) { | if ( m.type === 'childList' ) { | ||
m.addedNodes.forEach( function ( n ) { | m.addedNodes.forEach( function ( n ) { | ||
| Line 205: | Line 200: | ||
n.querySelectorAll && n.querySelectorAll( '.vector-toc-list-item' ) | n.querySelectorAll && n.querySelectorAll( '.vector-toc-list-item' ) | ||
.forEach( attachHighlight ); | .forEach( attachHighlight ); | ||
// Tag any newly revealed .vector-toc-text spans for transliteration | |||
var newTextSpans = []; | |||
if ( n.classList && n.classList.contains( 'vector-toc-text' ) ) { | |||
newTextSpans.push( n ); | |||
} | |||
if ( n.querySelectorAll ) { | |||
n.querySelectorAll( '.vector-toc-text' ).forEach( function ( s ) { | |||
newTextSpans.push( s ); | |||
} ); | |||
} | |||
newTextSpans.forEach( function ( span ) { | |||
if ( span.hasAttribute( 'data-deva' ) ) return; | |||
var orig = span.textContent; | |||
if ( !orig.trim() ) return; | |||
span.setAttribute( 'data-deva', orig ); | |||
if ( currentScript !== 'deva' ) { | |||
span.textContent = transliterateText( orig, currentScript ); | |||
} | |||
translatableSpans.push( span ); | |||
} ); | |||
} ); | } ); | ||
return; | return; | ||
} | } | ||
// Class change → scroll active item into view if needed. | // Class change → scroll active item into view if needed. | ||
if ( m.attributeName !== 'class' ) return; | if ( m.attributeName !== 'class' ) return; | ||
var li = m.target; | var li = m.target; | ||
| Line 231: | Line 246: | ||
observer.observe( toc, { childList: true, subtree: true } ); | observer.observe( toc, { childList: true, subtree: true } ); | ||
// On initial load: scroll to already-active item | // On initial load: scroll to already-active item | ||
setTimeout( function () { | setTimeout( function () { | ||
var active = toc.querySelector( '.vector-toc-list-item-active' ); | var active = toc.querySelector( '.vector-toc-list-item-active' ); | ||
| Line 237: | Line 252: | ||
}, 400 ); | }, 400 ); | ||
} | } | ||
// ── Init ────────────────────────────────────────────────────── | // ── Init ────────────────────────────────────────────────────── | ||
function init() { | function init() { | ||
| Line 244: | Line 260: | ||
translatableSpans = []; | translatableSpans = []; | ||
tagTextNodes(); | tagTextNodes(); | ||
} else { | |||
// Content already tagged — still tag TOC spans if not yet done | |||
document.querySelectorAll( '.vector-toc .vector-toc-text:not([data-deva])' ).forEach( function ( span ) { | |||
var orig = span.textContent; | |||
if ( !orig.trim() ) return; | |||
span.setAttribute( 'data-deva', orig ); | |||
translatableSpans.push( span ); | |||
} ); | |||
} | } | ||
| Line 256: | Line 280: | ||
} | } | ||
watchTocActive(); | watchTocActive(); | ||
} | } | ||
// ── React to toolbar dropdown changes on the same page ───────── | // ── React to toolbar dropdown changes on the same page ───────── | ||
window.addEventListener( 'gr-script-change', function ( e ) { | window.addEventListener( 'gr-script-change', function ( e ) { | ||
var script = e && e.detail && e.detail.script; | var script = e && e.detail && e.detail.script; | ||
| Line 278: | Line 298: | ||
translatableSpans = []; | translatableSpans = []; | ||
tagTextNodes(); | tagTextNodes(); | ||
} else { | |||
// Tag any untagged TOC spans after navigation | |||
document.querySelectorAll( '.vector-toc .vector-toc-text:not([data-deva])' ).forEach( function ( span ) { | |||
var orig = span.textContent; | |||
if ( !orig.trim() ) return; | |||
span.setAttribute( 'data-deva', orig ); | |||
translatableSpans.push( span ); | |||
} ); | |||
} | } | ||
// Re-apply current script to newly loaded content | // Re-apply current script to newly loaded content | ||