MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 129: | Line 129: | ||
function tagTextNodes() { | function tagTextNodes() { | ||
// | // Walk only .mw-parser-output — the main article content. | ||
// | |||
// We deliberately EXCLUDE .vector-toc (the sidebar TOC): | |||
// Vector 2022 has its own MutationObserver on the TOC that rebuilds | |||
// list items when text nodes change. If we transliterate TOC text nodes, | |||
// Vector re-reads the changed text, tries to match it to a heading anchor, | |||
// fails (because anchors use the original Devanagari), and renders | |||
// "unknown title" for those entries. Keeping the TOC in Devanagari | |||
// preserves correct anchor navigation regardless of script selection. | |||
var content = document.querySelector( '.mw-parser-output' ); | var content = document.querySelector( '.mw-parser-output' ); | ||
if ( content ) | if ( !content ) return; | ||
var walker = document.createTreeWalker( content, NodeFilter.SHOW_TEXT ); | |||
var | 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 ); | |||
} ); | } ); | ||
} | } | ||
| Line 177: | Line 169: | ||
if ( !span.parentNode ) return; // detached node | if ( !span.parentNode ) return; // detached node | ||
var orig = span.getAttribute( 'data-deva' ); | var orig = span.getAttribute( 'data-deva' ); | ||
if ( !orig ) return; | |||
span.textContent = ( script === 'deva' ) | span.textContent = ( script === 'deva' ) | ||
? orig | ? orig | ||
| Line 185: | Line 178: | ||
// ── TOC active-item highlight + lazy TOC tagging ─────────────── | // ── TOC active-item highlight + lazy TOC tagging ─────────────── | ||
// ── TOC active-item highlight + lazy TOC tagging ─────────────── | // ── TOC active-item highlight + lazy TOC tagging ─────────────── | ||
// ── TOC active-item highlight ─────────────────────────────────── | |||
// Watches class changes on TOC list items and colours the active | |||
// link orange. We do NOT transliterate TOC text nodes — doing so | |||
// triggers Vector's own TOC observer which rebuilds items using the | |||
// new text as anchor text, failing to match the original heading | |||
// IDs and showing "unknown title". | |||
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; | ||
if ( !window.MutationObserver ) return; | |||
function attachHighlight( li ) { | |||
function | observer.observe( li, { attributes: true, attributeFilter: [ 'class' ] } ); | ||
} | } | ||
// | var observer = new MutationObserver( function ( mutations ) { | ||
mutations.forEach( function ( m ) { | |||
// New list items added (lazy render) → attach highlight to them | |||
if ( m.type === 'childList' ) { | |||
m.addedNodes.forEach( function ( n ) { | |||
if ( n.nodeType !== 1 ) return; | |||
if ( n.classList.contains( 'vector-toc-list-item' ) ) { | |||
attachHighlight( n ); | |||
} | |||
n.querySelectorAll && n.querySelectorAll( '.vector-toc-list-item' ) | |||
.forEach( attachHighlight ); | |||
} ); | |||
return; | |||
} | |||
// Class change → scroll active item into view if needed. | |||
// Colour is handled entirely by CSS — no inline style manipulation | |||
// which could conflict with Vector's own stylesheet. | |||
if ( m.attributeName !== 'class' ) return; | |||
var li = m.target; | |||
if ( !li.classList.contains( 'vector-toc-list-item-active' ) ) return; | |||
var container = document.querySelector( '.vector-sticky-pinned-container' ); | |||
if ( !container ) return; | |||
var liRect = li.getBoundingClientRect(); | |||
var cRect = container.getBoundingClientRect(); | |||
var isAbove = liRect.top < cRect.top + 8; | |||
var isBelow = liRect.bottom > cRect.bottom - 8; | |||
if ( isAbove || isBelow ) { | |||
li.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); | |||
} | |||
} ); | |||
} ); | |||
// Observe existing list items for class changes | |||
toc.querySelectorAll( '.vector-toc-list-item' ).forEach( attachHighlight ); | |||
// Watch for new items (lazy TOC population) | |||
observer.observe( toc, { childList: true, subtree: true } ); | |||
// On initial load: scroll to already-active item (colour handled by CSS) | |||
setTimeout( function () { | |||
var active = toc.querySelector( '.vector-toc-list-item-active' ); | |||
if ( active ) active.scrollIntoView({ block: 'nearest', behavior: 'auto' }); | |||
}, 400 ); | |||
} | } | ||
// ── Init ────────────────────────────────────────────────────── | // ── Init ────────────────────────────────────────────────────── | ||