MediaWiki:Common.js: Difference between revisions
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 1,515: | Line 1,515: | ||
}); | }); | ||
} | } | ||
}() ); | |||
/** | |||
* grantha-mobile-fixes.js | |||
* Add this to MediaWiki:Common.js (or load as a separate gadget) | |||
* | |||
* Fixes: | |||
* 1. Patches Minerva's ToggleList to guard against null parentNode | |||
* (prevents "Cannot read properties of null" crash on Main_Page) | |||
* 2. Keeps #gr-home-toggle visible below #gr-static-bar on mobile | |||
* 3. Wires the grantha/author tab toggle if it hasn't been wired yet | |||
*/ | |||
( function () { | |||
'use strict'; | |||
/* ══════════════════════════════════════════════════════════════ | |||
* 1. TOGGLELIST NULL GUARD | |||
* Minerva's initMobile.js runs ToggleList.init() which calls | |||
* el.parentNode on every .collapsible-block it finds. | |||
* On Main_Page, some of these are dynamically injected and may | |||
* not yet be in the live DOM when ToggleList runs. | |||
* We patch by removing any element that has no parentNode | |||
* before ToggleList.init() fires. | |||
* ══════════════════════════════════════════════════════════════ */ | |||
mw.hook( 'wikipage.content' ).add( function () { | |||
/* Wait one tick so Minerva's own DOMReady handlers run first */ | |||
setTimeout( function () { | |||
var blocks = document.querySelectorAll( '.collapsible-block, .toggle-list' ); | |||
Array.prototype.forEach.call( blocks, function ( el ) { | |||
/* If the element has no parentNode it's detached — remove it | |||
before ToggleList tries to call parentNode on it */ | |||
if ( !el.parentNode ) { | |||
try { el.remove(); } catch(e) {} | |||
} | |||
} ); | |||
}, 0 ); | |||
} ); | |||
/* ══════════════════════════════════════════════════════════════ | |||
* 2. MAIN PAGE: keep #gr-home-toggle below the reader bar | |||
* ══════════════════════════════════════════════════════════════ */ | |||
if ( mw.config.get( 'wgPageName' ) !== 'Main_Page' ) return; | |||
mw.loader.using( 'mediawiki.util' ).done( function () { | |||
$( function () { | |||
applyHomeToggleOffset(); | |||
/* Re-apply when bar height might change (orientation change, etc.) */ | |||
window.addEventListener( 'resize', applyHomeToggleOffset, { passive: true } ); | |||
/* Re-apply after a short delay in case the bar hasn't rendered yet */ | |||
setTimeout( applyHomeToggleOffset, 300 ); | |||
setTimeout( applyHomeToggleOffset, 800 ); | |||
} ); | |||
} ); | |||
function applyHomeToggleOffset() { | |||
var bar = document.getElementById( 'gr-static-bar' ); | |||
if ( !bar ) return; | |||
var barRect = bar.getBoundingClientRect(); | |||
/* barRect.bottom = distance from viewport top to bar bottom. | |||
On load (before any scroll) this equals the bar's actual top offset | |||
plus its height. After scrolling (Minerva header gone) it's just | |||
the bar height since bar.top → 0. */ | |||
var barBottom = Math.round( barRect.bottom ); | |||
/* #gr-home is the outermost container */ | |||
var homeEl = document.getElementById( 'gr-home' ); | |||
/* #gr-home-toggle is the tab switcher row */ | |||
var toggleEl = document.getElementById( 'gr-home-toggle' ); | |||
/* Also target the mw-parser-output first child as a fallback */ | |||
var firstChild = document.querySelector( | |||
'#mw-content-text .mw-parser-output > .gr-home, ' + | |||
'#mw-content-text .mw-parser-output > *:first-child' | |||
); | |||
/* scroll-margin-top: so that if someone scrolls-to-top the toggle | |||
is visible below the bar (not behind it) */ | |||
[ homeEl, toggleEl, firstChild ].forEach( function ( el ) { | |||
if ( el ) el.style.scrollMarginTop = ( barBottom + 4 ) + 'px'; | |||
} ); | |||
/* The body already has padding-top set by readerToolbar.js. | |||
But on Main_Page the toggle might STILL be hidden if the | |||
Minerva header is taller. Force the content area's top | |||
padding to be at least barBottom. */ | |||
var contentText = document.getElementById( 'mw-content-text' ); | |||
if ( contentText ) { | |||
/* Only add extra padding if we're on mobile */ | |||
var isMob = window.innerWidth < 768 || !!document.getElementById( 'mw-mf-viewport' ); | |||
if ( isMob ) { | |||
/* Check if the toggle is actually obscured */ | |||
if ( toggleEl ) { | |||
var toggleRect = toggleEl.getBoundingClientRect(); | |||
/* If toggle top < barBottom, the bar is covering it */ | |||
if ( toggleRect.top < barBottom ) { | |||
var currentPT = parseInt( window.getComputedStyle( document.body ).paddingTop, 10 ) || 0; | |||
var needed = currentPT + ( barBottom - toggleRect.top ) + 4; | |||
document.body.style.paddingTop = needed + 'px'; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
/* ══════════════════════════════════════════════════════════════ | |||
* 3. WIRE THE GRANTHA / AUTHOR TAB TOGGLE | |||
* In case the toggle script hasn't loaded yet or failed | |||
* ══════════════════════════════════════════════════════════════ */ | |||
$( function () { | |||
var $toggle = $( '#gr-home-toggle' ); | |||
var $viewG = $( '#gr-view-grantha' ); | |||
var $viewA = $( '#gr-view-author' ); | |||
var $btnG = $( '#gr-toggle-grantha' ); | |||
var $btnA = $( '#gr-toggle-author' ); | |||
if ( !$toggle.length || !$viewG.length || !$viewA.length ) return; | |||
/* Only wire if not already wired */ | |||
if ( $toggle.data( 'gr-wired' ) ) return; | |||
$toggle.data( 'gr-wired', true ); | |||
function showView( which ) { | |||
if ( which === 'grantha' ) { | |||
$viewG.show(); $viewA.hide(); | |||
$btnG.addClass( 'gr-toggle-active' ); | |||
$btnA.removeClass( 'gr-toggle-active' ); | |||
} else { | |||
$viewA.show(); $viewG.hide(); | |||
$btnA.addClass( 'gr-toggle-active' ); | |||
$btnG.removeClass( 'gr-toggle-active' ); | |||
} | |||
try { localStorage.setItem( 'grantha_home_tab', which ); } catch(e){} | |||
} | |||
$btnG.on( 'click keydown', function(e){ | |||
if ( e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ' ) return; | |||
showView( 'grantha' ); | |||
}); | |||
$btnA.on( 'click keydown', function(e){ | |||
if ( e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ' ) return; | |||
showView( 'author' ); | |||
}); | |||
/* Restore last-used tab */ | |||
try { | |||
var saved = localStorage.getItem( 'grantha_home_tab' ); | |||
if ( saved === 'author' ) showView( 'author' ); | |||
else showView( 'grantha' ); | |||
} catch(e) { showView( 'grantha' ); } | |||
} ); | |||
}() ); | }() ); | ||