Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Citizen.js: Difference between revisions

MediaWiki interface page
Content deleted Content added
Add window-level load guards so gadget versions can take over cleanly later (via update-page on MediaWiki MCP Server)
Bot: careui phase 1 — stop loading webfonts from JS (now @font-face in Common.css); fix copy-button closure bug where every button copied the last code block - Claude
 
Line 1: Line 1:
/**
/**
* OHC Network Wiki — Citizen skin site JS (core mechanism, no Gadgets ext).
* OHC Network Wiki — Citizen skin site JS (core mechanism, no Gadgets ext).
* Features: fonts, copy-code, search hotkey, back-to-top.
* Features: copy-code, search hotkey, back-to-top.
* Guarded with window flags so the matching ohc-* gadgets (if enabled later
* Guarded with window flags so the matching ohc-* gadgets (if enabled later
* via MediaWiki:Gadgets-definition) no-op instead of double-applying.
* via MediaWiki:Gadgets-definition) no-op instead of double-applying.
⚫
*
* Fonts are NOT loaded here any more. They are declared as @font-face in
* MediaWiki:Common.css (self-hosted variable Figtree / Geist Mono), so the
* browser discovers them while parsing CSS instead of after a script round
* trip — the old mw.loader.load() approach guaranteed a flash of fallback
* text, and in practice the webfonts were often not applied at all.
*/
*/
( function () {
( function () {
'use strict';
'use strict';


/* 1. Fonts ------------------------------------------------ */
window.ohcFontsLoaded = window.ohcFontsLoaded || false;
function loadFonts() {
if ( window.ohcFontsLoaded ) { return; }
window.ohcFontsLoaded = true;
mw.loader.load(
'https://api.fontshare.com/v2/css?f%5B%5D=switzer@400,500,600,700&display=swap',
'text/css'
);
mw.loader.load(
'https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700;800&family=Geist+Mono:wght@400;500;600&display=swap',
'text/css'
);
⚫
}


/* 2. Copy-code -------------------------------------------- */
/* 1. Copy-code -------------------------------------------- */
function copyText( text, btn ) {
function copyText( text, btn ) {
var reset = function () {
var reset = function () {
Line 58: Line 50:
function addCopyButtons( container ) {
function addCopyButtons( container ) {
var pres = container.querySelectorAll( 'pre' );
var pres = container.querySelectorAll( 'pre' );
// `let`, not `var`: with `var` the click handlers all closed over the
⚫
for ( var i = 0; i < pres.length; i++ ) {
// final loop values, so every Copy button copied the last code block
⚫
var pre = pres[ i ];
// on the page.
⚫
for ( let i = 0; i < pres.length; i++ ) {
⚫
const pre = pres[ i ];
if ( pre.classList.contains( 'ohc-copy-code' ) ) {
if ( pre.classList.contains( 'ohc-copy-code' ) ) {
continue;
continue;
}
}
var btn = document.createElement( 'button' );
const btn = document.createElement( 'button' );
btn.type = 'button';
btn.type = 'button';
btn.className = 'ohc-copy-btn';
btn.className = 'ohc-copy-btn';
Line 76: Line 71:
}
}


/* 3. Search hotkey ---------------------------------------- */
/* 2. Search hotkey ---------------------------------------- */
window.ohcSearchHotkeyLoaded = window.ohcSearchHotkeyLoaded || false;
window.ohcSearchHotkeyLoaded = window.ohcSearchHotkeyLoaded || false;
function bindSearchHotkey() {
function bindSearchHotkey() {
Line 99: Line 94:
}
}


/* 4. Back to top ------------------------------------------ */
/* 3. Back to top ------------------------------------------ */
window.ohcBackToTopLoaded = window.ohcBackToTopLoaded || false;
window.ohcBackToTopLoaded = window.ohcBackToTopLoaded || false;
function initBackToTop() {
function initBackToTop() {
Line 124: Line 119:
}
}


loadFonts();
addCopyButtons( document );
addCopyButtons( document );
mw.hook( 'wikipage.content' ).add( addCopyButtons );
mw.hook( 'wikipage.content' ).add( addCopyButtons );

Latest revision as of 12:34, 12 August 2026

/**
 * OHC Network Wiki — Citizen skin site JS (core mechanism, no Gadgets ext).
 * Features: copy-code, search hotkey, back-to-top.
 * Guarded with window flags so the matching ohc-* gadgets (if enabled later
 * via MediaWiki:Gadgets-definition) no-op instead of double-applying.
 *
 * Fonts are NOT loaded here any more. They are declared as @font-face in
 * MediaWiki:Common.css (self-hosted variable Figtree / Geist Mono), so the
 * browser discovers them while parsing CSS instead of after a script round
 * trip — the old mw.loader.load() approach guaranteed a flash of fallback
 * text, and in practice the webfonts were often not applied at all.
 */
( function () {
	'use strict';


	/* 1. Copy-code -------------------------------------------- */
	function copyText( text, btn ) {
		var reset = function () {
			btn.textContent = 'Copy';
			btn.classList.remove( 'ohc-copied' );
		};
		var done = function () {
			btn.textContent = 'Copied';
			btn.classList.add( 'ohc-copied' );
			setTimeout( reset, 1600 );
		};
		var fail = function () {
			btn.textContent = 'Failed';
			setTimeout( reset, 1600 );
		};
		function fallbackCopy() {
			var ta = document.createElement( 'textarea' );
			ta.value = text;
			ta.style.cssText = 'position:fixed;left:-9999px;top:0;';
			document.body.appendChild( ta );
			ta.select();
			var ok = false;
			try { ok = document.execCommand( 'copy' ); } catch ( e ) {}
			document.body.removeChild( ta );
			if ( ok ) { done(); } else { fail(); }
		}
		if ( navigator.clipboard && navigator.clipboard.writeText ) {
			navigator.clipboard.writeText( text ).then( done, fallbackCopy );
		} else {
			fallbackCopy();
		}
	}

	function addCopyButtons( container ) {
		var pres = container.querySelectorAll( 'pre' );
		// `let`, not `var`: with `var` the click handlers all closed over the
		// final loop values, so every Copy button copied the last code block
		// on the page.
		for ( let i = 0; i < pres.length; i++ ) {
			const pre = pres[ i ];
			if ( pre.classList.contains( 'ohc-copy-code' ) ) {
				continue;
			}
			const btn = document.createElement( 'button' );
			btn.type = 'button';
			btn.className = 'ohc-copy-btn';
			btn.textContent = 'Copy';
			btn.setAttribute( 'aria-label', 'Copy code to clipboard' );
			btn.addEventListener( 'click', function () {
				copyText( pre.textContent.replace( /\n$/, '' ), btn );
			} );
			pre.classList.add( 'ohc-copy-code' );
			pre.appendChild( btn );
		}
	}

	/* 2. Search hotkey ---------------------------------------- */
	window.ohcSearchHotkeyLoaded = window.ohcSearchHotkeyLoaded || false;
	function bindSearchHotkey() {
		if ( window.ohcSearchHotkeyLoaded ) { return; }
		window.ohcSearchHotkeyLoaded = true;
		document.addEventListener( 'keydown', function ( e ) {
			if ( e.key !== '/' && e.key !== '?' ) { return; }
			var t = e.target;
			if ( t && ( t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable ) ) {
				return;
			}
			e.preventDefault();
			var input =
				document.querySelector( '#searchInput' ) ||
				document.querySelector( '.citizen-search input[type="search"]' ) ||
				document.querySelector( 'input[type="search"]' );
			if ( input ) {
				input.focus();
				if ( input.select ) { input.select(); }
			}
		} );
	}

	/* 3. Back to top ------------------------------------------ */
	window.ohcBackToTopLoaded = window.ohcBackToTopLoaded || false;
	function initBackToTop() {
		if ( window.ohcBackToTopLoaded || document.querySelector( '.ohc-to-top' ) ) { return; }
		window.ohcBackToTopLoaded = true;
		var btn = document.createElement( 'button' );
		btn.type = 'button';
		btn.className = 'ohc-to-top';
		btn.setAttribute( 'aria-label', 'Back to top' );
		btn.innerHTML = '&uarr;';
		btn.addEventListener( 'click', function () {
			window.scrollTo( { top: 0, behavior: 'smooth' } );
		} );
		document.body.appendChild( btn );
		var toggle = function () {
			if ( window.scrollY > 600 ) {
				btn.classList.add( 'ohc-to-top--show' );
			} else {
				btn.classList.remove( 'ohc-to-top--show' );
			}
		};
		toggle();
		window.addEventListener( 'scroll', toggle, { passive: true } );
	}

	addCopyButtons( document );
	mw.hook( 'wikipage.content' ).add( addCopyButtons );
	bindSearchHotkey();
	initBackToTop();
}() );