From Official Arms of God Wiki
bot: hover-infobox tooltips + comparison filter chips (+ library tab/collapsible init) |
bot: Expand all / Collapse all controls for Help catalogues |
||
| Line 1: | Line 1: | ||
/* | /* | ||
* MediaWiki:Common.js — baseline JS. | * MediaWiki:Common.js — baseline JS. | ||
| Line 158: | Line 157: | ||
* common with the active chips of that group. Groups AND together; | * common with the active chips of that group. Groups AND together; | ||
* chips within a group OR together. Reset clears everything. | * chips within a group OR together. Reset clears everything. | ||
* | |||
* 3. Catalogue expand/collapse-all: .wm-expand-all / .wm-collapse-all | |||
* buttons inside a .wm-catalogue wrapper (Help:Wiki Editing module / | |||
* template / data-schema catalogues) expand or collapse every | |||
* jquery.makeCollapsible table inside THAT wrapper only. | |||
* | * | ||
* ES5 only, jQuery + mw provided by MediaWiki. Dark styling lives in | * ES5 only, jQuery + mw provided by MediaWiki. Dark styling lives in | ||
* MediaWiki:Common.css (.wm-tipbox / .wm-chip / .wm-row-hidden). | * MediaWiki:Common.css (.wm-tipbox / .wm-chip / .wm-row-hidden / | ||
* .wm-catalogue-controls). | |||
*/ | */ | ||
( function ( $ ) { | ( function ( $ ) { | ||
| Line 168: | Line 173: | ||
initTipPopups(); | initTipPopups(); | ||
initFilterChips(); | initFilterChips(); | ||
initCatalogueControls(); | |||
} ); | } ); | ||
| Line 381: | Line 387: | ||
} | } | ||
} | } | ||
} | |||
/* ------------------------------- catalogue expand/collapse all ---- */ | |||
function initCatalogueControls() { | |||
$( document ).on( 'click', '.wm-expand-all, .wm-collapse-all', function ( e ) { | |||
e.preventDefault(); | |||
var expand = $( this ).hasClass( 'wm-expand-all' ); | |||
var $scope = $( this ).closest( '.wm-catalogue' ); | |||
if ( !$scope.length ) { | |||
return; | |||
} | |||
// makeCollapsible ships with core page rendering, but wait for | |||
// the module anyway so the data API is guaranteed initialized. | |||
mw.loader.using( 'jquery.makeCollapsible' ).then( function () { | |||
$scope.find( '.mw-collapsible' ).each( function () { | |||
var $el = $( this ); | |||
var api = $el.data( 'mw-collapsible' ); | |||
if ( api && api.expand && api.collapse ) { | |||
if ( expand ) { | |||
api.expand(); | |||
} else { | |||
api.collapse(); | |||
} | |||
return; | |||
} | |||
// Fallback: synthetic toggle click, but only when the | |||
// current collapsed state mismatches the target so it | |||
// never flips a table the wrong way. | |||
var collapsed = $el.hasClass( 'mw-collapsed' ); | |||
if ( collapsed === expand ) { | |||
$el.find( '.mw-collapsible-toggle' ).first().trigger( 'click' ); | |||
} | |||
} ); | |||
} ); | |||
} ); | |||
} | } | ||
}( jQuery ) ); | }( jQuery ) ); | ||
Latest revision as of 16:21, 10 June 2026
/*
* MediaWiki:Common.js — baseline JS.
*
* Ships unchanged across all games. No substitution slots — this file is fully
* generic. Per-game customizations layer on top by editing
* MediaWiki:Common.js/<gameslug>.js on the live wiki (manually, not pipeline-managed).
*
* SCOPE DISCIPLINE (mirrors MediaWiki:Common.css):
* - Tab init for .wm-tabs (forward-looking — Phase 12 catalogue #1 variant-rarity
* tabbed infoboxes will emit elements with these data attributes)
* - Collapsible-section toggle for .wm-collapsible (alternative to MediaWiki's
* built-in mw-collapsible when we want consistent class names)
* - NO business logic, NO third-party libs, NO modal/popup chrome
*
* Graceful degradation: every interactive piece must be functional WITHOUT JS
* (server-rendered content is what readers see; JS adds interactivity, never
* gates the content).
*
* ES5 only — MediaWiki:Common.js runs without transpilation.
*/
( function ( $ ) {
'use strict';
$( document ).ready( function () {
initTabs();
initCollapsibles();
initCarousels();
} );
/**
* Tabbed-panel init. Activates any container with:
*
* <div class="wm-tabs" data-wm-tabs>
* <div class="wm-tabs__buttons" data-wm-tabs-btns>
* <div class="wm-tabs__button" data-wm-tabs-btn="1">I</div>
* <div class="wm-tabs__button" data-wm-tabs-btn="2">II</div>
* </div>
* <div class="wm-tabs__panels" data-wm-tabs-panels>
* <div class="wm-tabs__panel" data-wm-tabs-panel="1">…</div>
* <div class="wm-tabs__panel wm-tabs__panel--hidden" data-wm-tabs-panel="2">…</div>
* </div>
* </div>
*
* Click on a button hides every panel except the matching one. First panel
* is visible by default (server-rendered); JS only swaps on click.
*
* If JS is off, the first panel shows and others stay hidden — readers
* see SOMETHING (the most common variant), just not the tabs.
*/
function initTabs() {
var $containers = $( '[data-wm-tabs]' );
if ( !$containers.length ) {
return;
}
$containers.each( function () {
var $container = $( this );
var $btns = $container.find( '[data-wm-tabs-btn]' );
var $panels = $container.find( '[data-wm-tabs-panel]' );
$btns.on( 'click', function () {
var key = $( this ).attr( 'data-wm-tabs-btn' );
$btns.removeClass( 'wm-tabs__button--active' );
$( this ).addClass( 'wm-tabs__button--active' );
$panels.addClass( 'wm-tabs__panel--hidden' );
$panels.filter( '[data-wm-tabs-panel="' + key + '"]' )
.removeClass( 'wm-tabs__panel--hidden' );
} );
} );
}
/**
* Collapsible-section toggle. Activates any element with:
*
* <div class="wm-collapsible" data-wm-collapsible>
* <div class="wm-collapsible__header" data-wm-collapsible-toggle>Section header (click to collapse)</div>
* <div class="wm-collapsible__body">…</div>
* </div>
*
* Click on the header toggles a `wm-collapsible--collapsed` class on the
* container. CSS in MediaWiki:Common.css handles the show/hide via
* `.wm-collapsible--collapsed .wm-collapsible__body { display: none; }`.
*
* If JS is off, sections stay expanded (the readable default).
*
* MediaWiki ships a built-in `mw-collapsible` that does similar work; this
* exists for the cases where we want a consistent wm-* class namespace
* across our templates (so MediaWiki:Common.css selectors stay simple).
*/
function initCollapsibles() {
$( '[data-wm-collapsible-toggle]' ).on( 'click', function () {
$( this ).closest( '[data-wm-collapsible]' )
.toggleClass( 'wm-collapsible--collapsed' );
} );
}
/**
* Carousel init. Paired with Widget:Carousel emitting.
*
* Indexing convention: data-* attributes are 0-indexed (DOM convention);
* aria-labels are 1-indexed ("Slide 1", "Slide 2" — human convention).
* Don't "fix" this — matches WAI-ARIA carousel patterns.
*
*
* <div class="wm-carousel" data-wm-carousel>
* <div data-wm-carousel-slides>
* <div data-wm-carousel-slide="0" class="wm-carousel__slide--active">…</div>
* <div data-wm-carousel-slide="1">…</div>
* </div>
* <div data-wm-carousel-btns>
* <button data-wm-carousel-btn="0" class="wm-carousel__btn--active">…</button>
* <button data-wm-carousel-btn="1">…</button>
* </div>
* </div>
*
* Click a button → opacity-fade the matching slide in, others out. First
* slide is server-rendered visible (opacity:1); JS-off readers see only that.
*
* Active-state CSS lives in MediaWiki:Common.css. JS toggles class names +
* inline opacity (the inline rule is for graceful degradation when CSS
* hasn't loaded yet).
*/
function initCarousels() {
$( '[data-wm-carousel]' ).each( function () {
var $car = $( this );
var $slides = $car.find( '[data-wm-carousel-slide]' );
var $btns = $car.find( '[data-wm-carousel-btn]' );
$btns.on( 'click', function () {
var idx = $( this ).attr( 'data-wm-carousel-btn' );
$btns.removeClass( 'wm-carousel__btn--active' ).css( 'background', 'transparent' );
$( this ).addClass( 'wm-carousel__btn--active' ).css( 'background', '#888' );
$slides.removeClass( 'wm-carousel__slide--active' ).css( 'opacity', '0' );
$slides.filter( '[data-wm-carousel-slide="' + idx + '"]' )
.addClass( 'wm-carousel__slide--active' ).css( 'opacity', '1' );
} );
} );
}
}( jQuery ) );
/*
* Arms of God — per-game interactivity block (appended after the generic
* library block by 5-build-pages/code/emit_pages.py::emit_css_js).
*
* 1. Hover-infobox tooltips: any element with class="wm-tip"
* data-tip-title="<page>" (emitted by Module:Iconbox, Module:CrossRef,
* Module:Index, Module:LinkGrid, Module:StatIndex and the comparison
* datasets) shows the target page's infobox in a dark floating popup on
* hover. The infobox HTML is fetched once per title via api.php
* action=parse and cached for the page view. No JS / touch-only device /
* page without an infobox -> plain link, no popup.
*
* 2. Comparison filter chips: .wm-filterbar bars (shipped display:none so
* no-JS readers never see inert controls) are revealed; clicking a
* .wm-chip toggles it and hides table rows whose data-<group> attribute
* (data-element / data-class / data-tier / data-type) has no value in
* common with the active chips of that group. Groups AND together;
* chips within a group OR together. Reset clears everything.
*
* 3. Catalogue expand/collapse-all: .wm-expand-all / .wm-collapse-all
* buttons inside a .wm-catalogue wrapper (Help:Wiki Editing module /
* template / data-schema catalogues) expand or collapse every
* jquery.makeCollapsible table inside THAT wrapper only.
*
* ES5 only, jQuery + mw provided by MediaWiki. Dark styling lives in
* MediaWiki:Common.css (.wm-tipbox / .wm-chip / .wm-row-hidden /
* .wm-catalogue-controls).
*/
( function ( $ ) {
'use strict';
$( document ).ready( function () {
initTipPopups();
initFilterChips();
initCatalogueControls();
} );
/* ---------------------------------------------- hover tooltips ---- */
function initTipPopups() {
// Hover-driven feature: skip entirely on touch-only devices.
if ( window.matchMedia && window.matchMedia( '(hover: none)' ).matches ) {
return;
}
var cache = {}; // title -> html string, or false (= no popup)
var timer = null;
var $box = null;
var current = null;
function apiUrl() {
return mw.config.get( 'wgScriptPath' ) + '/api.php';
}
function ensureBox() {
if ( !$box ) {
$box = $( '<div>' ).addClass( 'wm-tipbox' )
.css( {
position: 'absolute',
zIndex: 1000,
maxWidth: '360px',
background: '#1c1d22',
color: '#e6e6e6',
border: '1px solid #3a3c44',
borderRadius: '5px',
boxShadow: '0 6px 18px rgba(0,0,0,0.65)',
padding: '6px',
fontSize: '0.9em',
pointerEvents: 'none',
display: 'none'
} )
.appendTo( document.body );
}
return $box;
}
function position( $b, x, y ) {
var w = $b.outerWidth() || 360;
var winRight = $( window ).scrollLeft() + $( window ).width();
var left = ( x + 18 + w > winRight ) ? Math.max( 4, x - w - 12 ) : x + 18;
$b.css( { left: left + 'px', top: ( y + 14 ) + 'px' } );
}
function extract( html ) {
var $root = $( '<div>' ).html( html );
var $ib = $root.find( '.infobox' ).first();
if ( $ib.length ) {
// neutralise the on-page float/width inline styles
$ib.css( { float: 'none', width: 'auto', maxWidth: '340px', margin: 0 } );
return $( '<div>' ).append( $ib ).html();
}
var $p = $root.find( 'p' ).filter( function () {
return $.trim( $( this ).text() ).length > 0;
} ).first();
if ( $p.length ) {
var text = $.trim( $p.text() );
if ( text.length > 320 ) {
text = text.slice( 0, 317 ) + '…';
}
return $( '<div>' ).text( text ).html();
}
return false;
}
function show( title, x, y ) {
if ( cache[ title ] === false ) {
return;
}
if ( typeof cache[ title ] === 'string' ) {
var $b = ensureBox();
$b.html( cache[ title ] ).show();
position( $b, x, y );
return;
}
$.getJSON( apiUrl(), {
action: 'parse',
page: title,
prop: 'text',
redirects: 1,
disablelimitreport: 1,
formatversion: 2,
format: 'json'
} ).done( function ( data ) {
var html = data && data.parse && data.parse.text;
cache[ title ] = html ? extract( html ) : false;
// only show if the cursor is still on the same link
if ( current === title && cache[ title ] ) {
var $b = ensureBox();
$b.html( cache[ title ] ).show();
position( $b, x, y );
}
} ).fail( function () {
cache[ title ] = false;
} );
}
function hide() {
current = null;
if ( timer ) {
clearTimeout( timer );
timer = null;
}
if ( $box ) {
$box.hide();
}
}
$( document ).on( 'mouseenter', '.wm-tip', function ( e ) {
// no popups for links rendered inside a popup
if ( $( this ).closest( '.wm-tipbox' ).length ) {
return;
}
var title = $( this ).attr( 'data-tip-title' );
if ( !title ) {
return;
}
var x = e.pageX, y = e.pageY;
current = title;
if ( timer ) {
clearTimeout( timer );
}
timer = setTimeout( function () {
if ( current === title ) {
show( title, x, y );
}
}, 160 );
} ).on( 'mouseleave', '.wm-tip', hide );
}
/* ---------------------------------------------- filter chips ------ */
function initFilterChips() {
var $bars = $( '.wm-filterbar' );
if ( !$bars.length ) {
return;
}
var GROUPS = [ 'element', 'class', 'tier', 'type' ];
function rowSelector() {
var sel = [];
for ( var i = 0; i < GROUPS.length; i++ ) {
sel.push( 'tr[data-' + GROUPS[ i ] + ']' );
}
return sel.join( ',' );
}
function apply() {
var active = {};
$( '.wm-chip-on' ).each( function () {
var g = $( this ).attr( 'data-group' );
var v = $( this ).attr( 'data-val' );
if ( !g || !v ) {
return;
}
( active[ g ] = active[ g ] || [] ).push( v );
} );
$( rowSelector() ).each( function () {
var row = this;
var hidden = false;
for ( var g in active ) {
var rv = row.getAttribute( 'data-' + g );
if ( rv === null ) {
continue; // row not classified on this axis
}
var tokens = rv.split( ' ' );
var hit = false;
for ( var i = 0; i < tokens.length && !hit; i++ ) {
for ( var j = 0; j < active[ g ].length && !hit; j++ ) {
if ( tokens[ i ] === active[ g ][ j ] ) {
hit = true;
}
}
}
if ( !hit ) {
hidden = true;
break;
}
}
$( row ).toggleClass( 'wm-row-hidden', hidden )
.css( 'display', hidden ? 'none' : '' );
} );
}
$bars.show(); // bars ship display:none; only JS users see them
$( document ).on( 'click', '.wm-filterbar .wm-chip', function () {
var $chip = $( this );
if ( $chip.hasClass( 'wm-chip-clear' ) ) {
$chip.closest( '.wm-filterbar' ).find( '.wm-chip-on' )
.removeClass( 'wm-chip-on' )
.each( restyleChip );
apply();
return;
}
$chip.toggleClass( 'wm-chip-on' );
restyleChip.call( this );
apply();
} );
// Inline-style fallback so the active state is visible even if the
// site CSS is stale (every shipped surface styles inline-first).
function restyleChip() {
var $chip = $( this );
if ( $chip.hasClass( 'wm-chip-on' ) ) {
$chip.css( { background: '#f1e9d2', color: '#111', fontWeight: 'bold' } );
} else {
$chip.css( { background: '', color: '', fontWeight: '' } );
}
}
}
/* ------------------------------- catalogue expand/collapse all ---- */
function initCatalogueControls() {
$( document ).on( 'click', '.wm-expand-all, .wm-collapse-all', function ( e ) {
e.preventDefault();
var expand = $( this ).hasClass( 'wm-expand-all' );
var $scope = $( this ).closest( '.wm-catalogue' );
if ( !$scope.length ) {
return;
}
// makeCollapsible ships with core page rendering, but wait for
// the module anyway so the data API is guaranteed initialized.
mw.loader.using( 'jquery.makeCollapsible' ).then( function () {
$scope.find( '.mw-collapsible' ).each( function () {
var $el = $( this );
var api = $el.data( 'mw-collapsible' );
if ( api && api.expand && api.collapse ) {
if ( expand ) {
api.expand();
} else {
api.collapse();
}
return;
}
// Fallback: synthetic toggle click, but only when the
// current collapsed state mismatches the target so it
// never flips a table the wrong way.
var collapsed = $el.hasClass( 'mw-collapsed' );
if ( collapsed === expand ) {
$el.find( '.mw-collapsible-toggle' ).first().trigger( 'click' );
}
} );
} );
} );
}
}( jQuery ) );