MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 240: | Line 240: | ||
* position. */ | * position. */ | ||
if ( isActive ) { | if ( isActive ) { | ||
/* ── Auto-expand parent section in TOC ──────────────────────── | |||
* Vector collapses child sections under a parent li. When a child | |||
* becomes active we must expand its parent by removing the | |||
* vector-toc-list-item-collapsed class, mirroring what Vector's | |||
* own JS does on click. Walk up to find collapsed ancestors. */ | |||
var ancestor = li.parentNode; | |||
while ( ancestor && ancestor !== document.body ) { | |||
if ( ancestor.classList && | |||
ancestor.classList.contains( 'vector-toc-list-item' ) && | |||
ancestor.classList.contains( 'vector-toc-list-item-collapsed' ) ) { | |||
ancestor.classList.remove( 'vector-toc-list-item-collapsed' ); | |||
} | |||
ancestor = ancestor.parentNode; | |||
} | |||
/* ── Scroll active item into view within the TOC ────────────── | |||
* Use getBoundingClientRect for both the item and the scroll | |||
* container so the measurement is always in viewport coords — | |||
* avoids the offsetTop-relative-to-offsetParent mismatch. */ | |||
var container = document.querySelector( '.vector-sticky-pinned-container' ); | var container = document.querySelector( '.vector-sticky-pinned-container' ); | ||
if ( container ) { | if ( container ) { | ||
var tocVisible = container.offsetHeight > 0 && | var tocVisible = container.offsetHeight > 0 && | ||
container.offsetParent !== null && | container.offsetParent !== null && | ||
| Line 251: | Line 268: | ||
var liRect = li.getBoundingClientRect(); | var liRect = li.getBoundingClientRect(); | ||
var cRect = container.getBoundingClientRect(); | var cRect = container.getBoundingClientRect(); | ||
if ( liRect.top < cRect.top + | /* Only scroll if the item is outside the visible container area */ | ||
/* | if ( liRect.top < cRect.top + 4 || liRect.bottom > cRect.bottom - 4 ) { | ||
/* Walk up to find the actual scrollable ancestor */ | |||
var scrollHost = null; | var scrollHost = null; | ||
var | var node = li.parentNode; | ||
while ( | while ( node && node !== document.body ) { | ||
if ( | var overflow = window.getComputedStyle( node ).overflowY; | ||
scrollHost = | if ( ( overflow === 'auto' || overflow === 'scroll' ) && | ||
node.scrollHeight > node.clientHeight ) { | |||
scrollHost = node; | |||
break; | break; | ||
} | } | ||
node = node.parentNode; | |||
} | |||
/* Fallback: use container itself if no scrollable ancestor found */ | |||
if ( !scrollHost && container.scrollHeight > container.clientHeight ) { | |||
scrollHost = container; | |||
} | } | ||
if ( scrollHost ) { | if ( scrollHost ) { | ||
var | /* Convert li position to scrollHost-relative using rects */ | ||
scrollHost.scrollTop = Math.max( 0, | var hostRect = scrollHost.getBoundingClientRect(); | ||
var currentScroll = scrollHost.scrollTop; | |||
var liTop = liRect.top - hostRect.top + currentScroll; | |||
var target = liTop - ( scrollHost.clientHeight / 2 ) + ( li.offsetHeight / 2 ); | |||
scrollHost.scrollTop = Math.max( 0, target ); | |||
} | } | ||
} | } | ||
| Line 329: | Line 355: | ||
* Vector injects some of these elements via its own JS after DOMContentLoaded, | * Vector injects some of these elements via its own JS after DOMContentLoaded, | ||
* so we use a MutationObserver to catch them whenever they appear. */ | * so we use a MutationObserver to catch them whenever they appear. */ | ||
var | /* Remove appearance panel elements by their stable IDs only. | ||
' | * We deliberately avoid class-based selectors like | ||
' | * .mw-portlet-vector-user-menu-overflow because on some Vector versions | ||
' | * that class name is shared with the user-menu portlet (which contains | ||
* the login/logout/preferences links) and removing it hides the profile | |||
* dropdown. ID-based removal is safe and precise. */ | |||
var HIDE_IDS = [ | |||
'vector-appearance', | |||
'vector-appearance-pinned-container', | |||
'vector-appearance-unpinned-container' | |||
]; | ]; | ||
function removeHiddenEls() { | function removeHiddenEls() { | ||
HIDE_IDS.forEach( function ( id ) { | |||
document. | var el = document.getElementById( id ); | ||
if ( el && el.parentNode ) el.parentNode.removeChild( el ); | |||
} ); | } ); | ||
/* Also remove the appearance toggle button by its aria-controls attribute. | |||
* Scope the search to page-tools area only — never touch the header user-links. */ | |||
var pageTools = document.getElementById( 'vector-page-tools' ) || | |||
document.querySelector( '.vector-page-tools-pinned-container' ); | |||
if ( pageTools ) { | |||
pageTools.querySelectorAll( '[aria-controls="vector-appearance"]' ) | |||
.forEach( function ( el ) { | |||
if ( el.parentNode ) el.parentNode.removeChild( el ); | |||
} ); | |||
} | |||
} | } | ||
removeHiddenEls(); | removeHiddenEls(); | ||
/* ── Teeka view-mode detection ──────────────────────────────────────── | |||
* Teeka pages carry <div class="gr-teeka-page" data-primary="X" data-slug="Y"> | |||
* | |||
* Two modes: | |||
* gr-standalone — user opened the teeka URL directly, or ?ref=0 | |||
* → plain reading view, no coloured containers | |||
* gr-ref-mode — user navigated here from the main doc, or ?ref=1 | |||
* → coloured teeka-block containers (the default styled view) | |||
* | |||
* Detection order: ?ref= query param > document.referrer > default (standalone) | |||
* ─────────────────────────────────────────────────────────────────── */ | |||
( function detectTeekaMode() { | |||
var teekaPage = document.querySelector( '.gr-teeka-page' ); | |||
if ( !teekaPage ) return; // not a teeka page — nothing to do | |||
var primary = teekaPage.getAttribute( 'data-primary' ) || ''; | |||
var artPath = ( window.mw && mw.config.get( 'wgArticlePath' ) ) || '/wiki/$1'; | |||
var mainUrl = artPath.replace( '$1', primary ); | |||
// 1. Query param override: ?ref=1 or ?ref=0 | |||
var qs = window.location.search; | |||
var refParam = qs.match( /[?&]ref=([01])/ ); | |||
if ( refParam ) { | |||
document.body.classList.add( refParam[1] === '1' ? 'gr-ref-mode' : 'gr-standalone' ); | |||
return; | |||
} | |||
// 2. Referrer check — did we come from the main doc? | |||
var ref = document.referrer || ''; | |||
var fromMain = ref && primary && ref.indexOf( mainUrl ) !== -1; | |||
document.body.classList.add( fromMain ? 'gr-ref-mode' : 'gr-standalone' ); | |||
}() ); | |||
if ( window.MutationObserver ) { | if ( window.MutationObserver ) { | ||
var hideObserver = new MutationObserver( function ( mutations ) { | var hideObserver = new MutationObserver( function ( mutations ) { | ||
| Line 359: | Line 431: | ||
* liObserver that handles active highlight colouring. */ | * liObserver that handles active highlight colouring. */ | ||
hideObserver.observe( document.body, { childList: true, subtree: false } ); | hideObserver.observe( document.body, { childList: true, subtree: false } ); | ||
/* Stop observing after 6s — Vector will have finished by then */ | |||
setTimeout( function () { hideObserver.disconnect(); }, 6000 ); | |||
/* Stop observing after | |||
setTimeout( function () { hideObserver.disconnect(); }, | |||
} | } | ||