MediaWiki:Gadget-readerchrome.js: Difference between revisions
Created page with "/** * readerChrome.js β site-wide reader chrome (NOT part of the editor). * * Provides two always-available reader aids that used to live inside the * Editor extension's siteNav.js: * β’ [π] document navigator (bottom-right FAB + panel) * β’ [β] scroll-to-top (appears after 300px scroll) * * Why it's separate from the editor: these are reading aids, useful on every * page for every visitor β they have nothing to do with editing. Keeping th..." Β |
No edit summary Β |
||
| Line 1: | Line 1: | ||
/** | /** | ||
Β * | Β * readerLoader.js β brief themed loading overlay for content pages. | ||
Β * | Β * | ||
Β * | Β * Replaces the "blank visibility:hidden" gap (used to hide the Devanagari | ||
Β * | Β * flash-then-transliterate flip) with an intentional-looking loader. The | ||
Β * | Β * overlay covers only the content area and fades out the moment the page is | ||
Β * | Β * "ready" β either when the first transliteration pass has run, or after a | ||
Β * short safety timeout so it can never get stuck. | |||
Β * | Β * | ||
Β * | Β * How it coordinates with transliteration: | ||
Β * | Β * Β β’ This script adds class `gr-loading` to <html> immediately (before paint). | ||
Β * | Β * Β Β CSS then hides content and shows the overlay. | ||
Β * | Β * Β β’ When common.js finishes its first applyScript() (or right away if the | ||
Β * | *Β Β saved script is 'deva'), it must callΒ window.grReaderReady()Β β that | ||
*Β Β removes `gr-loading` and adds `gr-ready`, fading the overlay out and | |||
Β * Β Β revealing content. | |||
*Β β’ If grReaderReady() is never called (JS error, etc.), a 2.5s failsafe | |||
*Β Β reveals the page anyway β the reader is never left staring at a loader. | |||
Β * | Β * | ||
Β * Install: load early (top of MediaWiki:Common.js, or a Gadget with | |||
Β * "type":"general" so it's in <head>). It self-limits to content article | |||
Β * pages and skips Main Page / Special pages. | |||
Β * Install: | |||
Β * | |||
Β * | |||
Β */ | Β */ | ||
( function () { | ( function () { | ||
'use strict'; | 'use strict'; | ||
/* | /* Only show the loader on real content article pages. */ | ||
var | function isContentPage() { | ||
try { | |||
if ( !window.mw || !mw.config ) return false; | |||
var ns = mw.config.get( 'wgNamespaceNumber' ); | |||
var pn = mw.config.get( 'wgPageName' ) || ''; | |||
if ( ns !== 0 ) return false;Β Β Β Β Β Β Β Β // main namespace only | |||
if ( pn === ( mw.config.get( 'wgMainPageTitle' ) || 'Main_Page' ) ) return false; | |||
if ( pn === 'Main_Page' || pn === 'Welcome' ) return false; | |||
return true; | |||
} catch ( e ) { return false; } | |||
} | |||
if ( !isContentPage() ) return; | |||
var root = document.documentElement; | |||
root.classList.add( 'gr-loading' ); | |||
/* ββ Styles βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |||
var css = [ | |||
/* Hide the parsed content while loading, but keep layout height so the | |||
Β page doesn't jump when it reveals. */ | |||
'html.gr-loading .mw-parser-output{visibility:hidden;}', | |||
'html.gr-readyΒ .mw-parser-output{visibility:visible;}', | |||
/* Overlay sits over the content column only (not the header/toolbar). */ | |||
'#gr-reader-loader{', | |||
'Β position:absolute;left:0;right:0;top:0;', | |||
'Β min-height:60vh;height:100%;', | |||
'Β display:flex;flex-direction:column;align-items:center;justify-content:center;', | |||
'Β gap:16px;z-index:50;pointer-events:none;', | |||
'Β background:transparent;', | |||
' transition:opacity .3s ease;', | |||
'}', | |||
'html.gr-ready #gr-reader-loader{opacity:0;}', | |||
/* Spinner β a rotating ring in the site terracotta. */ | |||
'#gr-reader-loader .gr-rl-ring{', | |||
'Β width:38px;height:38px;border-radius:50%;', | |||
'Β border:3px solid rgba(181,69,27,0.18);', | |||
'Β border-top-color:#b5451b;', | |||
'Β animation:gr-rl-spin .8s linear infinite;', | |||
'}', | |||
'@keyframes gr-rl-spin{to{transform:rotate(360deg);}}', | |||
/* Label β quiet, manuscript-toned. */ | |||
'#gr-reader-loader .gr-rl-label{', | |||
'Β font-family:"Adishila","Noto Serif Devanagari",system-ui,serif;', | |||
' font-size:15px;color:#a07040;letter-spacing:.02em;', | |||
'}', | |||
/* Reduced-motion: no spin, just a gentle pulse. */ | |||
'@media (prefers-reduced-motion: reduce){', | |||
'Β #gr-reader-loader .gr-rl-ring{animation:gr-rl-pulse 1.2s ease-in-out infinite;border-top-color:rgba(181,69,27,0.18);}', | |||
'Β @keyframes gr-rl-pulse{0%,100%{opacity:.4}50%{opacity:1}}', | |||
'}' | |||
].join( '' ); | |||
var style = document.createElement( 'style' ); | |||
style.id = 'gr-reader-loader-css'; | |||
style.textContent = css; | |||
( document.head || document.documentElement ).appendChild( style ); | |||
/* ββ Overlay element (inserted into the content container) ββββββββ */ | |||
var | function mountOverlay() { | ||
. | if ( document.getElementById( 'gr-reader-loader' ) ) return; | ||
var | var host = document.getElementById( 'mw-content-text' ) || | ||
document.querySelector( '.mw-body-content' ) || | |||
document.body; | |||
if ( !host ) return; | |||
/* host needs positioning context so the absolute overlay covers it */ | |||
var pos = window.getComputedStyle( host ).position; | |||
if ( pos === 'static' ) host.style.position = 'relative'; | |||
var | var ov = document.createElement( 'div' ); | ||
ov.id = 'gr-reader-loader'; | |||
ov.setAttribute( 'aria-hidden', 'true' ); | |||
ov.innerHTML = | |||
'<div class="gr-rl-ring"></div>' + | |||
'<div class="gr-rl-label">ΰ€²ΰ₯ΰ€‘ΰ₯ΰ€―ΰ€€ΰ₯β¦</div>';Β /* "loadingβ¦" in Sanskrit */ | |||
host.appendChild( ov ); | |||
'<div class=" | } | ||
'<div class=" | |||
if ( document.readyState === 'loading' ) { | |||
document.addEventListener( 'DOMContentLoaded', mountOverlay ); | |||
} else { | |||
mountOverlay(); | |||
} | |||
/* ββ Reveal logic βββββββββββββββββββββββββββββββββββββββββββββββββ */ | |||
var revealed = false; | |||
function reveal() { | |||
if ( revealed ) return; | |||
revealed = true; | |||
root.classList.remove( 'gr-loading' ); | |||
root.classList.add( 'gr-ready' ); | |||
/* Remove the overlay node after the fade so it leaves no trace. */ | |||
setTimeout( function () { | |||
Β | var ov = document.getElementById( 'gr-reader-loader' ); | ||
if ( ov && ov.parentNode ) ov.parentNode.removeChild( ov ); | |||
}, 320 ); | |||
} | |||
Β | |||
Β | |||
Β | |||
Β | |||
Β | |||
Β | |||
/* common.js calls this after its first applyScript(). */ | |||
window.grReaderReady = reveal; | |||
if ( | /* Failsafe: never leave the loader up longer than 2.5s, even if the | ||
Β ready signal never arrives (JS error, missing hook, etc.). */ | |||
setTimeout( reveal, 2500 ); | |||
}() ); | }() ); | ||
Latest revision as of 18:10, 2 July 2026
/**
* readerLoader.js β brief themed loading overlay for content pages.
*
* Replaces the "blank visibility:hidden" gap (used to hide the Devanagari
* flash-then-transliterate flip) with an intentional-looking loader. The
* overlay covers only the content area and fades out the moment the page is
* "ready" β either when the first transliteration pass has run, or after a
* short safety timeout so it can never get stuck.
*
* How it coordinates with transliteration:
* β’ This script adds class `gr-loading` to <html> immediately (before paint).
* CSS then hides content and shows the overlay.
* β’ When common.js finishes its first applyScript() (or right away if the
* saved script is 'deva'), it must call window.grReaderReady() β that
* removes `gr-loading` and adds `gr-ready`, fading the overlay out and
* revealing content.
* β’ If grReaderReady() is never called (JS error, etc.), a 2.5s failsafe
* reveals the page anyway β the reader is never left staring at a loader.
*
* Install: load early (top of MediaWiki:Common.js, or a Gadget with
* "type":"general" so it's in <head>). It self-limits to content article
* pages and skips Main Page / Special pages.
*/
( function () {
'use strict';
/* Only show the loader on real content article pages. */
function isContentPage() {
try {
if ( !window.mw || !mw.config ) return false;
var ns = mw.config.get( 'wgNamespaceNumber' );
var pn = mw.config.get( 'wgPageName' ) || '';
if ( ns !== 0 ) return false; // main namespace only
if ( pn === ( mw.config.get( 'wgMainPageTitle' ) || 'Main_Page' ) ) return false;
if ( pn === 'Main_Page' || pn === 'Welcome' ) return false;
return true;
} catch ( e ) { return false; }
}
if ( !isContentPage() ) return;
var root = document.documentElement;
root.classList.add( 'gr-loading' );
/* ββ Styles βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
var css = [
/* Hide the parsed content while loading, but keep layout height so the
page doesn't jump when it reveals. */
'html.gr-loading .mw-parser-output{visibility:hidden;}',
'html.gr-ready .mw-parser-output{visibility:visible;}',
/* Overlay sits over the content column only (not the header/toolbar). */
'#gr-reader-loader{',
' position:absolute;left:0;right:0;top:0;',
' min-height:60vh;height:100%;',
' display:flex;flex-direction:column;align-items:center;justify-content:center;',
' gap:16px;z-index:50;pointer-events:none;',
' background:transparent;',
' transition:opacity .3s ease;',
'}',
'html.gr-ready #gr-reader-loader{opacity:0;}',
/* Spinner β a rotating ring in the site terracotta. */
'#gr-reader-loader .gr-rl-ring{',
' width:38px;height:38px;border-radius:50%;',
' border:3px solid rgba(181,69,27,0.18);',
' border-top-color:#b5451b;',
' animation:gr-rl-spin .8s linear infinite;',
'}',
'@keyframes gr-rl-spin{to{transform:rotate(360deg);}}',
/* Label β quiet, manuscript-toned. */
'#gr-reader-loader .gr-rl-label{',
' font-family:"Adishila","Noto Serif Devanagari",system-ui,serif;',
' font-size:15px;color:#a07040;letter-spacing:.02em;',
'}',
/* Reduced-motion: no spin, just a gentle pulse. */
'@media (prefers-reduced-motion: reduce){',
' #gr-reader-loader .gr-rl-ring{animation:gr-rl-pulse 1.2s ease-in-out infinite;border-top-color:rgba(181,69,27,0.18);}',
' @keyframes gr-rl-pulse{0%,100%{opacity:.4}50%{opacity:1}}',
'}'
].join( '' );
var style = document.createElement( 'style' );
style.id = 'gr-reader-loader-css';
style.textContent = css;
( document.head || document.documentElement ).appendChild( style );
/* ββ Overlay element (inserted into the content container) ββββββββ */
function mountOverlay() {
if ( document.getElementById( 'gr-reader-loader' ) ) return;
var host = document.getElementById( 'mw-content-text' ) ||
document.querySelector( '.mw-body-content' ) ||
document.body;
if ( !host ) return;
/* host needs positioning context so the absolute overlay covers it */
var pos = window.getComputedStyle( host ).position;
if ( pos === 'static' ) host.style.position = 'relative';
var ov = document.createElement( 'div' );
ov.id = 'gr-reader-loader';
ov.setAttribute( 'aria-hidden', 'true' );
ov.innerHTML =
'<div class="gr-rl-ring"></div>' +
'<div class="gr-rl-label">ΰ€²ΰ₯ΰ€‘ΰ₯ΰ€―ΰ€€ΰ₯β¦</div>'; /* "loadingβ¦" in Sanskrit */
host.appendChild( ov );
}
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', mountOverlay );
} else {
mountOverlay();
}
/* ββ Reveal logic βββββββββββββββββββββββββββββββββββββββββββββββββ */
var revealed = false;
function reveal() {
if ( revealed ) return;
revealed = true;
root.classList.remove( 'gr-loading' );
root.classList.add( 'gr-ready' );
/* Remove the overlay node after the fade so it leaves no trace. */
setTimeout( function () {
var ov = document.getElementById( 'gr-reader-loader' );
if ( ov && ov.parentNode ) ov.parentNode.removeChild( ov );
}, 320 );
}
/* common.js calls this after its first applyScript(). */
window.grReaderReady = reveal;
/* Failsafe: never leave the loader up longer than 2.5s, even if the
ready signal never arrives (JS error, missing hook, etc.). */
setTimeout( reveal, 2500 );
}() );