MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary Tags: Reverted Mobile edit Mobile web edit |
||
| Line 1,242: | Line 1,242: | ||
/* ── Expose storeQueryForLink for readerToolbar to call ── */ | /* ── Expose storeQueryForLink for readerToolbar to call ── */ | ||
window.grStoreSearchHL = storeQueryForLink; | window.grStoreSearchHL = storeQueryForLink; | ||
}() ); | |||
/* ═══════════════════════════════════════════════════════════════ | |||
Mobile fixes — paste at bottom of MediaWiki:Common.js | |||
Injects a <style> tag AFTER readerToolbar's inline style | |||
so it wins the cascade without touching the extension. | |||
═══════════════════════════════════════════════════════════════ */ | |||
( function () { | |||
if ( window.innerWidth > 768 ) return; | |||
/* ── 1. Inject mobile CSS after readerToolbar's <style> ─────── */ | |||
function injectMobileCSS() { | |||
if ( document.getElementById( 'gr-mobile-css' ) ) return; | |||
var s = document.createElement( 'style' ); | |||
s.id = 'gr-mobile-css'; | |||
s.textContent = [ | |||
/* Font scale */ | |||
'html { font-size: 18px !important; }', | |||
/* Full-width layout — kill x-scroll everywhere */ | |||
'html body, html body .mw-page-container, html body .vector-page-container,', | |||
'html body .mw-content-container, html body .vector-body-container,', | |||
'html body .vector-page-content, html body .mw-body,', | |||
'html body .mw-body-content, html body #mw-content-text {', | |||
' max-width: 100% !important; width: 100% !important;', | |||
' overflow-x: hidden !important; box-sizing: border-box !important;', | |||
' margin: 0 !important; padding: 0 !important; }', | |||
/* Content padding */ | |||
'html body #mw-content-text { padding: 8px 14px 48px !important; }', | |||
/* Content font */ | |||
'html body .mw-parser-output {', | |||
' font-size: 18px !important; line-height: 1.8 !important;', | |||
' max-width: 100% !important; overflow-x: hidden !important; }', | |||
/* Headings full width */ | |||
'html body .mw-parser-output h2, html body .mw-parser-output .mw-heading2 h2 {', | |||
' font-size: 1.1em !important; width: 100% !important; margin-top: 1.2em !important; }', | |||
'html body .mw-parser-output h3, html body .mw-parser-output .mw-heading3 h3 {', | |||
' font-size: 1em !important; width: 100% !important; }', | |||
/* Hide Vector sidebar TOC — replaced by overlay */ | |||
'html body .vector-column-start, html body #vector-toc,', | |||
'html body .vector-toc-landmark, html body .mw-table-of-contents-container,', | |||
'html body .vector-sticky-pinned-container,', | |||
'html body .vector-pinnable-element.vector-toc-pinnable-element {', | |||
' display: none !important; }', | |||
/* Home grid — single column, no x-scroll */ | |||
'html body .gr-home { overflow-x: hidden !important; max-width: 100% !important; }', | |||
'html body .gr-home-grid {', | |||
' flex-direction: column !important; flex-wrap: nowrap !important;', | |||
' gap: 14px !important; overflow-x: hidden !important; width: 100% !important; }', | |||
'html body .gr-home-card {', | |||
' flex: 0 0 auto !important; width: 100% !important;', | |||
' max-width: 100% !important; min-width: unset !important;', | |||
' box-sizing: border-box !important; }', | |||
/* Toolbar */ | |||
'html body #gr-static-bar {', | |||
' height: auto !important; min-height: 52px !important;', | |||
' padding: 6px 8px !important; flex-wrap: nowrap !important;', | |||
' overflow-x: auto !important; overflow-y: hidden !important;', | |||
' scrollbar-width: none !important; gap: 4px !important; }', | |||
'html body #gr-static-bar::-webkit-scrollbar { display: none !important; }', | |||
'html body .gr-controls .gr-btn {', | |||
' height: 44px !important; min-width: 44px !important;', | |||
' font-size: 15px !important; padding: 0 10px !important; flex-shrink: 0 !important; }', | |||
'html body .gr-controls .gr-icon-btn { width: 44px !important; padding: 0 !important; }', | |||
'html body .gr-script-sel {', | |||
' height: 40px !important; font-size: 14px !important;', | |||
' min-width: 90px !important; max-width: 120px !important; }', | |||
'html body .gr-controls .gr-sep { display: none !important; }', | |||
'html body .gr-btn-staging { display: none !important; }', | |||
/* TOC overlay styles */ | |||
'.gr-mob-toc-btn {', | |||
' display: flex !important; position: fixed !important;', | |||
' bottom: 148px !important; left: 16px !important; z-index: 9100 !important;', | |||
' background: #fff !important; border: 1.5px solid #b5451b !important;', | |||
' border-radius: 24px !important; padding: 10px 16px !important;', | |||
' font-size: 15px !important; font-family: system-ui, sans-serif !important;', | |||
' color: #b5451b !important; font-weight: 600 !important;', | |||
' box-shadow: 0 3px 14px rgba(0,0,0,0.15) !important;', | |||
' cursor: pointer !important; align-items: center !important;', | |||
' gap: 6px !important; white-space: nowrap !important; }', | |||
'.gr-mob-toc-panel {', | |||
' position: fixed !important; top: 0 !important; left: 0 !important;', | |||
' bottom: 0 !important; width: 82vw !important; max-width: 340px !important;', | |||
' background: #fff !important; z-index: 10400 !important;', | |||
' box-shadow: 4px 0 28px rgba(0,0,0,0.22) !important;', | |||
' overflow-y: auto !important; padding: 0 0 40px !important;', | |||
' transform: translateX(-110%) !important;', | |||
' transition: transform 0.26s cubic-bezier(0.4,0,0.2,1) !important;', | |||
' display: block !important; }', | |||
'.gr-mob-toc-panel.open { transform: translateX(0) !important; }', | |||
'.gr-mob-toc-backdrop {', | |||
' display: none !important; position: fixed !important; inset: 0 !important;', | |||
' background: rgba(0,0,0,0.4) !important; z-index: 10399 !important; }', | |||
'.gr-mob-toc-backdrop.open { display: block !important; }', | |||
'.gr-mob-toc-header {', | |||
' position: sticky !important; top: 0 !important; background: #fff !important;', | |||
' display: flex !important; align-items: center !important;', | |||
' justify-content: space-between !important;', | |||
' padding: 16px 16px 12px !important;', | |||
' border-bottom: 1px solid #f0ebe6 !important; z-index: 1 !important; }', | |||
'.gr-mob-toc-title {', | |||
' font-size: 13px !important; font-weight: 700 !important;', | |||
' text-transform: uppercase !important; letter-spacing: 0.08em !important;', | |||
' color: #b5451b !important; font-family: system-ui, sans-serif !important; }', | |||
'.gr-mob-toc-close {', | |||
' background: none !important; border: none !important;', | |||
' font-size: 22px !important; color: #999 !important;', | |||
' cursor: pointer !important; padding: 4px 8px !important; line-height: 1 !important; }', | |||
'.gr-mob-toc-body { padding: 12px 16px !important; }', | |||
'.gr-mob-toc-body a {', | |||
' display: block !important; font-size: 16px !important;', | |||
' line-height: 1.6 !important; color: #2c1810 !important;', | |||
' text-decoration: none !important; padding: 8px 0 !important;', | |||
' border-bottom: 1px solid #f5f0ed !important;', | |||
" font-family: 'Adishila','Noto Serif Devanagari',system-ui,sans-serif !important; }", | |||
'.gr-mob-toc-body .vector-toc-level-2 a {', | |||
' padding-left: 16px !important; font-size: 15px !important; color: #555 !important; }', | |||
/* Annotation toggle button position */ | |||
'html body #gra-toggle {', | |||
' bottom: 80px !important; right: 14px !important;', | |||
' width: 54px !important; height: 54px !important; }', | |||
].join( '\n' ); | |||
document.head.appendChild( s ); | |||
} | |||
/* ── 2. Mobile TOC overlay ────────────────────────────────────── */ | |||
function initMobileToc() { | |||
if ( document.getElementById( 'gr-mob-toc-btn' ) ) return; | |||
var tocList = document.querySelector( | |||
'.vector-toc-contents, .vector-toc .vector-toc-list' | |||
); | |||
if ( !tocList ) { setTimeout( initMobileToc, 500 ); return; } | |||
/* Check if page actually has TOC items */ | |||
var hasItems = tocList.querySelector( '.vector-toc-list-item' ); | |||
/* Backdrop */ | |||
var bd = document.createElement( 'div' ); | |||
bd.className = 'gr-mob-toc-backdrop'; | |||
document.body.appendChild( bd ); | |||
/* Panel */ | |||
var panel = document.createElement( 'div' ); | |||
panel.className = 'gr-mob-toc-panel'; | |||
var hdr = document.createElement( 'div' ); | |||
hdr.className = 'gr-mob-toc-header'; | |||
var ttl = document.createElement( 'div' ); | |||
ttl.className = 'gr-mob-toc-title'; | |||
ttl.textContent = 'विषयसूची'; | |||
var cls = document.createElement( 'button' ); | |||
cls.className = 'gr-mob-toc-close'; | |||
cls.textContent = '✕'; | |||
hdr.appendChild( ttl ); hdr.appendChild( cls ); | |||
panel.appendChild( hdr ); | |||
var body = document.createElement( 'div' ); | |||
body.className = 'gr-mob-toc-body'; | |||
body.appendChild( tocList.cloneNode( true ) ); | |||
panel.appendChild( body ); | |||
document.body.appendChild( panel ); | |||
/* Toggle button — only show if page has TOC */ | |||
var btn = document.createElement( 'button' ); | |||
btn.className = 'gr-mob-toc-btn'; | |||
btn.id = 'gr-mob-toc-btn'; | |||
btn.innerHTML = '☰ Contents'; | |||
if ( !hasItems ) btn.style.display = 'none'; | |||
document.body.appendChild( btn ); | |||
function open() { | |||
panel.classList.add( 'open' ); | |||
bd.classList.add( 'open' ); | |||
document.body.style.overflow = 'hidden'; | |||
} | |||
function close() { | |||
panel.classList.remove( 'open' ); | |||
bd.classList.remove( 'open' ); | |||
document.body.style.overflow = ''; | |||
} | |||
btn.addEventListener( 'click', open ); | |||
cls.addEventListener( 'click', close ); | |||
bd.addEventListener( 'click', close ); | |||
body.querySelectorAll( 'a' ).forEach( function ( a ) { | |||
a.addEventListener( 'click', close ); | |||
} ); | |||
} | |||
/* ── 3. Mobile hamburger menu ─────────────────────────────────── */ | |||
function initMobileMenu() { | |||
if ( document.getElementById( 'gr-mob-menu-btn' ) ) return; | |||
var headerStart = document.querySelector( '.vector-header-start' ); | |||
if ( !headerStart ) return; | |||
/* Hamburger button — prepend to header start */ | |||
var btn = document.createElement( 'button' ); | |||
btn.id = 'gr-mob-menu-btn'; | |||
btn.className = 'gr-mob-menu-btn'; | |||
btn.setAttribute( 'aria-label', 'Open menu' ); | |||
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>'; | |||
headerStart.insertBefore( btn, headerStart.firstChild ); | |||
/* Backdrop */ | |||
var bd = document.createElement( 'div' ); | |||
bd.className = 'gr-mob-menu-backdrop'; | |||
document.body.appendChild( bd ); | |||
/* Panel */ | |||
var panel = document.createElement( 'div' ); | |||
panel.className = 'gr-mob-menu-panel'; | |||
/* Header */ | |||
var hdr = document.createElement( 'div' ); | |||
hdr.className = 'gr-mob-menu-header'; | |||
var ttl = document.createElement( 'div' ); | |||
ttl.className = 'gr-mob-menu-header-title'; | |||
ttl.textContent = 'Anandamakaranda'; | |||
var cls = document.createElement( 'button' ); | |||
cls.className = 'gr-mob-menu-close'; | |||
cls.textContent = '✕'; | |||
hdr.appendChild( ttl ); hdr.appendChild( cls ); | |||
panel.appendChild( hdr ); | |||
/* Body — collect links from vector-header-end */ | |||
var body = document.createElement( 'div' ); | |||
body.className = 'gr-mob-menu-body'; | |||
/* Username / user page */ | |||
var userName = window.mw ? mw.config.get( 'wgUserName' ) : null; | |||
if ( userName ) { | |||
var userLink = document.createElement( 'a' ); | |||
userLink.className = 'gr-mob-menu-item gr-mob-menu-item-user'; | |||
userLink.href = '/User:' + encodeURIComponent( userName ); | |||
userLink.textContent = '👤 ' + userName; | |||
body.appendChild( userLink ); | |||
var div0 = document.createElement( 'div' ); | |||
div0.className = 'gr-mob-menu-divider'; | |||
body.appendChild( div0 ); | |||
} | |||
/* Collect links from header-end */ | |||
var headerEnd = document.querySelector( '.vector-header-end' ); | |||
if ( headerEnd ) { | |||
var links = headerEnd.querySelectorAll( 'a' ); | |||
links.forEach( function ( a ) { | |||
var item = document.createElement( 'a' ); | |||
item.className = 'gr-mob-menu-item'; | |||
item.href = a.href; | |||
item.textContent = a.textContent.trim(); | |||
if ( item.textContent ) body.appendChild( item ); | |||
} ); | |||
} | |||
/* Static links */ | |||
var statics = [ | |||
{ label: 'Help', href: '/Help:Contents' }, | |||
{ label: 'About', href: '/Anandamakaranda:About' }, | |||
]; | |||
var div1 = document.createElement( 'div' ); | |||
div1.className = 'gr-mob-menu-divider'; | |||
body.appendChild( div1 ); | |||
/* Login / logout */ | |||
var isLoggedIn = userName !== null; | |||
var authItem = document.createElement( 'a' ); | |||
authItem.className = 'gr-mob-menu-item'; | |||
if ( isLoggedIn ) { | |||
var logoutLink = document.querySelector( '#pt-logout a, a[href*="action=logout"]' ); | |||
authItem.href = logoutLink ? logoutLink.href : '/index.php?title=Special:UserLogout'; | |||
authItem.textContent = 'Log out'; | |||
} else { | |||
authItem.href = '/index.php?title=Special:UserLogin'; | |||
authItem.textContent = 'Log in'; | |||
} | |||
body.appendChild( authItem ); | |||
panel.appendChild( body ); | |||
document.body.appendChild( panel ); | |||
function open() { panel.classList.add('open'); bd.classList.add('open'); document.body.style.overflow = 'hidden'; } | |||
function close() { panel.classList.remove('open'); bd.classList.remove('open'); document.body.style.overflow = ''; } | |||
btn.addEventListener( 'click', open ); | |||
cls.addEventListener( 'click', close ); | |||
bd.addEventListener( 'click', close ); | |||
body.querySelectorAll( 'a' ).forEach( function(a){ a.addEventListener( 'click', close ); } ); | |||
} | |||
/* ── Boot ─────────────────────────────────────────────────────── */ | |||
injectMobileCSS(); /* CSS first — synchronous */ | |||
/* ── Fix Minerva header: remove top gap, ensure orange, show logo ── */ | |||
function fixMinervaHeader() { | |||
if ( !document.body.classList.contains( 'skin-minerva' ) ) return; | |||
/* Remove any top margin/padding on body — readerToolbar may have set it */ | |||
document.body.style.paddingTop = ''; | |||
document.body.style.marginTop = '0'; | |||
/* Also watch for readerToolbar setting paddingTop after us */ | |||
var _bodyObserver = new MutationObserver( function() { | |||
if ( document.body.style.paddingTop && document.body.style.paddingTop !== '0px' ) { | |||
document.body.style.paddingTop = ''; | |||
} | |||
} ); | |||
_bodyObserver.observe( document.body, { attributes: true, attributeFilter: ['style'] } ); | |||
/* Force orange header */ | |||
var headerEl = document.querySelector( 'header.header-container' ); | |||
if ( headerEl ) headerEl.style.background = '#b5451b'; | |||
/* Add quill logo before site name */ | |||
var brandingLink = document.querySelector( '.branding-box a' ); | |||
if ( brandingLink && !brandingLink.querySelector( '.gr-mob-logo' ) ) { | |||
var quillSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="28" height="28" fill="none" style="margin-right:8px;flex-shrink:0;">' + | |||
'<path stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M32 4C26 4 14 10 8 28L6 34l6-1c4-1 8-4 11-8"/>' + | |||
'<path stroke="#fff" stroke-width="1.8" stroke-linecap="round" d="M32 4C28 12 22 20 12 26"/>' + | |||
'<path stroke="#fff" stroke-width="1.8" stroke-linecap="round" d="M8 28c2-2 4-3 6-2s3 3 2 5"/>' + | |||
'<path stroke="#fff" stroke-width="1.5" stroke-linecap="round" d="M12 26L6 34" opacity="0.6"/></svg>'; | |||
var logoEl = document.createElement( 'span' ); | |||
logoEl.className = 'gr-mob-logo'; | |||
logoEl.innerHTML = quillSvg; | |||
brandingLink.style.cssText = 'display:flex;align-items:center;text-decoration:none;'; | |||
brandingLink.insertBefore( logoEl, brandingLink.firstChild ); | |||
/* Style the text span */ | |||
var textSpan = brandingLink.querySelector( 'span:not(.gr-mob-logo)' ); | |||
if ( textSpan ) { | |||
textSpan.style.cssText = 'color:#fff;font-size:17px;font-weight:700;font-family:system-ui,sans-serif;line-height:1.2;'; | |||
} | |||
} | |||
} | |||
if ( document.readyState === 'loading' ) { | |||
document.addEventListener( 'DOMContentLoaded', fixMinervaHeader ); | |||
} else { | |||
fixMinervaHeader(); | |||
} | |||
initMobileMenu(); | |||
if ( document.readyState === 'loading' ) { | |||
document.addEventListener( 'DOMContentLoaded', function () { | |||
setTimeout( initMobileToc, 600 ); | |||
} ); | |||
} else { | |||
setTimeout( initMobileToc, 600 ); | |||
} | |||
if ( window.mw ) { | |||
mw.hook( 'wikipage.content' ).add( function () { | |||
setTimeout( initMobileToc, 600 ); | |||
setTimeout( function() { expandAllSections(); watchSections(); }, 100 ); | |||
setTimeout( expandAllSections, 600 ); | |||
} ); | |||
} | |||
/* ── Force expand all Minerva collapsed sections ────────────── | |||
* MobileFrontend JS runs AFTER our code and re-collapses sections. | |||
* We use a MutationObserver to immediately re-expand anything | |||
* that gets hidden, plus run on load and on MW hooks. | |||
* ─────────────────────────────────────────────────────────── */ | |||
function expandAllSections() { | |||
if ( !document.body.classList.contains( 'skin-minerva' ) ) return; | |||
/* THE KEY FIX: MobileFrontend uses hidden="until-found" attribute | |||
* which CSS display:block cannot override. Must remove the attribute. */ | |||
document.querySelectorAll( '[hidden]' ).forEach( function(el) { | |||
var h = el.getAttribute( 'hidden' ); | |||
if ( h === 'until-found' || h === '' || h === 'hidden' ) { | |||
if ( el.closest( '[class*="mf-section-"], .collapsible-block' ) | |||
|| el.classList.contains( 'collapsible-block' ) | |||
|| /mf-section-/.test( el.className ) ) { | |||
el.removeAttribute( 'hidden' ); | |||
} | |||
} | |||
} ); | |||
/* Also remove hidden from all mf-section and collapsible-block directly */ | |||
document.querySelectorAll( | |||
'[class*="mf-section-"], .collapsible-block' | |||
).forEach( function(el) { | |||
el.removeAttribute( 'hidden' ); | |||
el.style.setProperty( 'display', 'block', 'important' ); | |||
el.style.setProperty( 'visibility', 'visible', 'important' ); | |||
el.style.setProperty( 'content-visibility', 'visible', 'important' ); | |||
el.removeAttribute( 'aria-hidden' ); | |||
} ); | |||
/* Mark headings as open, disable pointer events to prevent re-collapse */ | |||
document.querySelectorAll( '.section-heading, .collapsible-heading' ).forEach( function(el) { | |||
el.setAttribute( 'aria-expanded', 'true' ); | |||
el.style.setProperty( 'pointer-events', 'none', 'important' ); | |||
} ); | |||
/* Hide collapse indicators */ | |||
document.querySelectorAll( | |||
'.section-heading .indicator, .collapsible-heading .indicator' | |||
).forEach( function(el) { | |||
el.style.setProperty( 'display', 'none', 'important' ); | |||
} ); | |||
} | |||
/* Persistent observer — fight back when MF re-collapses */ | |||
var _sectionObserver = null; | |||
function watchSections() { | |||
if ( !document.body.classList.contains( 'skin-minerva' ) ) return; | |||
if ( _sectionObserver ) return; | |||
var _timer = null; | |||
_sectionObserver = new MutationObserver( function( mutations ) { | |||
var needExpand = mutations.some( function(m) { | |||
/* Watch for hidden attribute being added or style=display:none */ | |||
if ( m.type === 'attributes' ) { | |||
if ( m.attributeName === 'hidden' ) return true; | |||
if ( m.attributeName === 'style' && m.target.style.display === 'none' ) return true; | |||
} | |||
return false; | |||
} ); | |||
if ( needExpand ) { | |||
clearTimeout( _timer ); | |||
_timer = setTimeout( expandAllSections, 30 ); | |||
} | |||
} ); | |||
/* Observe the content area for attribute changes including hidden */ | |||
var contentEl = document.querySelector( '#mw-content-text, #mw-mf-page-center' ); | |||
if ( contentEl ) { | |||
_sectionObserver.observe( contentEl, { | |||
subtree: true, attributes: true, | |||
attributeFilter: ['style', 'class', 'hidden', 'aria-hidden'] | |||
} ); | |||
} | |||
} | |||
/* Run on load */ | |||
if ( document.readyState === 'loading' ) { | |||
document.addEventListener( 'DOMContentLoaded', function() { | |||
setTimeout( function() { expandAllSections(); watchSections(); }, 100 ); | |||
setTimeout( expandAllSections, 500 ); | |||
setTimeout( expandAllSections, 1200 ); | |||
} ); | |||
} else { | |||
setTimeout( function() { expandAllSections(); watchSections(); }, 100 ); | |||
setTimeout( expandAllSections, 500 ); | |||
setTimeout( expandAllSections, 1200 ); | |||
} | |||
}() ); | }() ); | ||