MediaWiki:Citizen.js: Difference between revisions
MediaWiki interface page
More languages
More actions
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: |
* 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' |
|||
); |
|||
| ⚫ | |||
/* |
/* 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 |
|||
| ⚫ | |||
// final loop values, so every Copy button copied the last code block |
|||
| ⚫ | |||
// on the page. |
|||
| ⚫ | |||
| ⚫ | |||
if ( pre.classList.contains( 'ohc-copy-code' ) ) { |
if ( pre.classList.contains( 'ohc-copy-code' ) ) { |
||
continue; |
continue; |
||
} |
} |
||
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: | ||
} |
} |
||
/* |
/* 2. Search hotkey ---------------------------------------- */ |
||
window.ohcSearchHotkeyLoaded = window.ohcSearchHotkeyLoaded || false; |
window.ohcSearchHotkeyLoaded = window.ohcSearchHotkeyLoaded || false; |
||
function bindSearchHotkey() { |
function bindSearchHotkey() { |
||
| Line 99: | Line 94: | ||
} |
} |
||
/* |
/* 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 = '↑';
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();
}() );