MediaWiki:Common.js: Difference between revisions
From LinuxizeNet
(Correct viewport comment: $wgVectorResponsive does not affect Vector legacy) |
(Mount the theme toggle in Timeless's header (#personal-extra)) |
||
| Line 82: | Line 82: | ||
function buildThemeToggle() { | function buildThemeToggle() { | ||
var host = document.querySelector( '#p-personal .vector-menu-content-list' ); | /* Mount points differ per skin, in preference order: | ||
* - Timeless: #personal-extra is the small icon strip in the header, | |||
* always visible. #p-personal's list is inside the user dropdown, | |||
* so a toggle there would be hidden behind a click. | |||
* - Vector legacy: the personal links row. | |||
* Until this existed, the toggle simply never rendered under Timeless | |||
* and dark mode was reachable only via prefers-color-scheme. */ | |||
var host = document.querySelector( '#personal-extra ul' ) || | |||
document.querySelector( '#p-personal .vector-menu-content-list' ) || | |||
document.querySelector( '#p-personal ul' ); | |||
if ( !host ) { | if ( !host || host.querySelector( '.lz-theme-toggle' ) ) { | ||
return; | return; | ||
} | } | ||
Revision as of 20:28, 9 August 2026
/**
* MediaWiki:Common.js — wiki.linuxize.net
*
* Three jobs, in priority order:
* 1. Patch the viewport meta so the responsive CSS in Common.css can fire.
* This is a workaround. The correct fix is $wgVectorResponsive = true;
* in LocalSettings.php — delete section 1 once that is set.
* 2. Add a language chip + copy button to every SyntaxHighlight block.
* 3. Add a light/dark toggle that persists and matches the CSS tokens.
*/
( function () {
'use strict';
/* ---------------------------------------------------------------
* 1. Viewport — PERMANENT. Do not delete.
*
* Vector legacy emits <meta name="viewport" content="width=1000">,
* which makes phones render a zoomed-out desktop page and prevents
* every max-width media query from ever matching.
*
* $wgVectorResponsive does NOT fix this. Tested 2026-08-08 on
* MediaWiki 1.41.5 with Vector 1.0.0: with the setting enabled and
* placed after wfLoadSkin( 'Vector' ), ?useskin=vector still returns
* width=1000, while vector-2022, timeless, monobook and minerva all
* return width=device-width. The config does not affect the legacy
* skin in this version.
*
* So this patch is the only thing making the site usable on a phone
* while the default skin is Vector legacy. It can only be removed by
* switching the default skin — and that also means reworking the
* chrome selectors in Common.css section 2 and 3, which target the
* legacy DOM.
*
* Runs at parse time rather than on DOM ready, to minimise the
* reflow flash.
* ------------------------------------------------------------- */
var viewport = document.querySelector( 'meta[name="viewport"]' );
if ( viewport ) {
viewport.setAttribute( 'content', 'width=device-width, initial-scale=1' );
} else {
viewport = document.createElement( 'meta' );
viewport.name = 'viewport';
viewport.content = 'width=device-width, initial-scale=1';
document.head.appendChild( viewport );
}
/* ---------------------------------------------------------------
* 2. Theme
* Applied before first paint so there is no light flash on a dark
* device. 'auto' stores nothing and lets prefers-color-scheme win.
* ------------------------------------------------------------- */
var STORAGE_KEY = 'lz-theme';
function readTheme() {
try {
return window.localStorage.getItem( STORAGE_KEY );
} catch ( e ) {
return null;
}
}
function applyTheme( theme ) {
if ( theme === 'dark' || theme === 'light' ) {
document.documentElement.setAttribute( 'data-theme', theme );
} else {
document.documentElement.removeAttribute( 'data-theme' );
}
}
applyTheme( readTheme() );
function currentTheme() {
var stamped = document.documentElement.getAttribute( 'data-theme' );
if ( stamped ) {
return stamped;
}
return window.matchMedia( '(prefers-color-scheme: dark)' ).matches ? 'dark' : 'light';
}
function buildThemeToggle() {
/* Mount points differ per skin, in preference order:
* - Timeless: #personal-extra is the small icon strip in the header,
* always visible. #p-personal's list is inside the user dropdown,
* so a toggle there would be hidden behind a click.
* - Vector legacy: the personal links row.
* Until this existed, the toggle simply never rendered under Timeless
* and dark mode was reachable only via prefers-color-scheme. */
var host = document.querySelector( '#personal-extra ul' ) ||
document.querySelector( '#p-personal .vector-menu-content-list' ) ||
document.querySelector( '#p-personal ul' );
if ( !host || host.querySelector( '.lz-theme-toggle' ) ) {
return;
}
var li = document.createElement( 'li' );
var btn = document.createElement( 'button' );
btn.type = 'button';
btn.className = 'lz-theme-toggle';
btn.setAttribute( 'aria-live', 'polite' );
function label() {
btn.textContent = currentTheme() === 'dark' ? 'light' : 'dark';
btn.setAttribute( 'aria-label', 'Switch to ' + btn.textContent + ' theme' );
}
label();
btn.addEventListener( 'click', function () {
var next = currentTheme() === 'dark' ? 'light' : 'dark';
applyTheme( next );
try {
window.localStorage.setItem( STORAGE_KEY, next );
} catch ( e ) {
/* private browsing — the choice just will not persist */
}
label();
} );
li.appendChild( btn );
host.appendChild( li );
}
/* ---------------------------------------------------------------
* 3. Code block chrome
* SyntaxHighlight emits:
* <div class="mw-highlight mw-highlight-lang-bash"><pre>…</pre></div>
* so the language is recoverable from the class name.
* ------------------------------------------------------------- */
function buildCodeChrome() {
var blocks = document.querySelectorAll( '#mw-content-text .mw-highlight' );
Array.prototype.forEach.call( blocks, function ( block ) {
if ( block.querySelector( '.lz-chead' ) ) {
return;
}
var pre = block.querySelector( 'pre' );
if ( !pre ) {
return;
}
var match = /mw-highlight-lang-([a-z0-9+#-]+)/i.exec( block.className );
var head = document.createElement( 'div' );
var lang = document.createElement( 'span' );
var copy = document.createElement( 'button' );
head.className = 'lz-chead';
lang.className = 'lz-lang';
lang.textContent = match ? match[ 1 ] : 'text';
copy.type = 'button';
copy.className = 'lz-copy';
copy.textContent = 'Copy';
copy.addEventListener( 'click', function () {
var text = pre.innerText;
function done() {
copy.textContent = 'Copied';
copy.classList.add( 'lz-done' );
window.setTimeout( function () {
copy.textContent = 'Copy';
copy.classList.remove( 'lz-done' );
}, 1600 );
}
if ( navigator.clipboard && window.isSecureContext ) {
navigator.clipboard.writeText( text ).then( done, function () {
copy.textContent = 'Failed';
} );
return;
}
// Fallback for non-secure contexts.
var ta = document.createElement( 'textarea' );
ta.value = text;
ta.setAttribute( 'readonly', '' );
ta.style.position = 'absolute';
ta.style.left = '-9999px';
document.body.appendChild( ta );
ta.select();
try {
document.execCommand( 'copy' );
done();
} catch ( e ) {
copy.textContent = 'Failed';
}
document.body.removeChild( ta );
} );
head.appendChild( lang );
head.appendChild( copy );
block.insertBefore( head, pre );
} );
}
/* ------------------------------------------------------------- */
if ( window.mw && mw.hook ) {
mw.hook( 'wikipage.content' ).add( buildCodeChrome );
$( buildThemeToggle );
} else {
document.addEventListener( 'DOMContentLoaded', function () {
buildCodeChrome();
buildThemeToggle();
} );
}
}() );