×
Create a new article
Write your page title here:
We currently have 69 articles on Super Shiritori Wiki. Type your article name above or click on one of the titles below and start writing!



Super Shiritori Wiki

MediaWiki:Common.js: Difference between revisions

Welcome to Super-Shiritori!

Please remember:

  • No vandalism – Do not add nonsense, false info, spam, or blank pages.
  • Respect others – Be polite and assume good faith.
  • Use reliable information – Make sure edits are accurate and IMPORTANT.
  • Stay constructive – Help us grow the wiki with content, especially with the Word compendium.

⚠️ Repeated vandalism or rule-breaking may result in warnings or blocks. If you see vandalism, please revert it and notify an admin.

Add infobox gallery tab-switching and mobile infobox reordering (below lead paragraph, Wikipedia-style)
 
Fix: use event delegation for gallery tab clicks -- direct listeners were being silently dropped, likely by MultimediaViewer touching the parser output after load
 
(One intermediate revision by the same user not shown)
Line 5: Line 5:


// Clickable tab bar for multi-image infoboxes (image1/caption1,
// Clickable tab bar for multi-image infoboxes (image1/caption1,
// image2/caption2, ...). Without this, only the first <div data-ia-slide>
// image2/caption2, ...). Delegated on document instead of bound directly
// is visible -- everything still renders, it just can't be switched.
// to each tab: something else on the page (MultimediaViewer's own image
function initInfoboxGalleries() {
// handling is the prime suspect) touches the parser output shortly after
document.querySelectorAll( '.infobox-application__gallery' ).forEach( function ( gallery ) {
// load and silently drops direct listeners, so binding to a stable
var tabs = gallery.querySelectorAll( '.infobox-application__gallery-tab' );
// ancestor that's never replaced is what actually keeps working.
var slides = gallery.querySelectorAll( '.infobox-application__gallery-slide' );
function handleGalleryClick( e ) {
tabs.forEach( function ( tab ) {
var tab = e.target.closest( '.infobox-application__gallery-tab' );
tab.addEventListener( 'click', function () {
if ( !tab ) {
var index = tab.getAttribute( 'data-ia-tab' );
return;
tabs.forEach( function ( t ) {
}
t.classList.toggle( 'is-active', t === tab );
var gallery = tab.closest( '.infobox-application__gallery' );
} );
if ( !gallery ) {
slides.forEach( function ( slide ) {
return;
slide.hidden = slide.getAttribute( 'data-ia-slide' ) !== index;
}
} );
var index = tab.getAttribute( 'data-ia-tab' );
} );
gallery.querySelectorAll( '.infobox-application__gallery-tab' ).forEach( function ( t ) {
} );
t.classList.toggle( 'is-active', t === tab );
} );
gallery.querySelectorAll( '.infobox-application__gallery-slide' ).forEach( function ( slide ) {
// A plain "hidden" attribute gets stripped by MediaWiki's
// sanitizer, so a CSS class carries visibility instead.
slide.classList.toggle( 'is-inactive', slide.getAttribute( 'data-ia-slide' ) !== index );
} );
} );
}
}
Line 37: Line 42:
}
}
document.querySelectorAll( '.mw-parser-output > .infobox-application' ).forEach( function ( box ) {
document.querySelectorAll( '.mw-parser-output > .infobox-application' ).forEach( function ( box ) {
if ( box.dataset.iaRelocated ) {
return;
}
var sib = box.nextElementSibling;
var sib = box.nextElementSibling;
while ( sib && ( sib.tagName !== 'P' || !sib.textContent.trim() ) ) {
while ( sib && ( sib.tagName !== 'P' || !sib.textContent.trim() ) ) {
Line 43: Line 51:
if ( sib ) {
if ( sib ) {
sib.insertAdjacentElement( 'afterend', box );
sib.insertAdjacentElement( 'afterend', box );
box.dataset.iaRelocated = '1';
}
}
} );
} );
}
}
// Registered immediately (doesn't need the DOM to be ready) and on a
// node that's never replaced, so this survives regardless of what else
// on the page rebuilds the content area after load.
document.addEventListener( 'click', handleGalleryClick );


function init() {
function init() {
initInfoboxGalleries();
relocateInfoboxOnMobile();
relocateInfoboxOnMobile();
}
}
if ( document.readyState === 'loading' ) {
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', init );
document.addEventListener( 'DOMContentLoaded', init );
Line 57: Line 69:
init();
init();
}
}
// Belt-and-suspenders: re-run once everything (including images) has
// finished loading, in case something reflowed the lead section after
// DOMContentLoaded. The dataset guard above makes this a no-op otherwise.
window.addEventListener( 'load', relocateInfoboxOnMobile );
}() );
}() );

Latest revision as of 05:25, 5 September 2026

/* Behavior for Template:Infobox_application. Lives in Common.js (not
 * Cosmos.js) so it keeps working regardless of which skin a reader/editor
 * has selected. */
( function () {

	// Clickable tab bar for multi-image infoboxes (image1/caption1,
	// image2/caption2, ...). Delegated on document instead of bound directly
	// to each tab: something else on the page (MultimediaViewer's own image
	// handling is the prime suspect) touches the parser output shortly after
	// load and silently drops direct listeners, so binding to a stable
	// ancestor that's never replaced is what actually keeps working.
	function handleGalleryClick( e ) {
		var tab = e.target.closest( '.infobox-application__gallery-tab' );
		if ( !tab ) {
			return;
		}
		var gallery = tab.closest( '.infobox-application__gallery' );
		if ( !gallery ) {
			return;
		}
		var index = tab.getAttribute( 'data-ia-tab' );
		gallery.querySelectorAll( '.infobox-application__gallery-tab' ).forEach( function ( t ) {
			t.classList.toggle( 'is-active', t === tab );
		} );
		gallery.querySelectorAll( '.infobox-application__gallery-slide' ).forEach( function ( slide ) {
			// A plain "hidden" attribute gets stripped by MediaWiki's
			// sanitizer, so a CSS class carries visibility instead.
			slide.classList.toggle( 'is-inactive', slide.getAttribute( 'data-ia-slide' ) !== index );
		} );
	}

	// On narrow screens the infobox drops its desktop float (see
	// Template:Infobox_application/styles.css) and becomes a full-width
	// block -- but it's still first in the wikitext/DOM, so without this it
	// would render ABOVE the lead paragraph instead of below it, the way
	// Wikipedia's mobile view places infoboxes. This moves it to just after
	// the first non-empty paragraph of the article (mirrors MobileFrontend's
	// own behavior, which Cosmos doesn't have).
	function relocateInfoboxOnMobile() {
		if ( window.innerWidth > 550 ) {
			return;
		}
		document.querySelectorAll( '.mw-parser-output > .infobox-application' ).forEach( function ( box ) {
			if ( box.dataset.iaRelocated ) {
				return;
			}
			var sib = box.nextElementSibling;
			while ( sib && ( sib.tagName !== 'P' || !sib.textContent.trim() ) ) {
				sib = sib.nextElementSibling;
			}
			if ( sib ) {
				sib.insertAdjacentElement( 'afterend', box );
				box.dataset.iaRelocated = '1';
			}
		} );
	}

	// Registered immediately (doesn't need the DOM to be ready) and on a
	// node that's never replaced, so this survives regardless of what else
	// on the page rebuilds the content area after load.
	document.addEventListener( 'click', handleGalleryClick );

	function init() {
		relocateInfoboxOnMobile();
	}
	if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', init );
	} else {
		init();
	}
	// Belt-and-suspenders: re-run once everything (including images) has
	// finished loading, in case something reflowed the lead section after
	// DOMContentLoaded. The dataset guard above makes this a no-op otherwise.
	window.addEventListener( 'load', relocateInfoboxOnMobile );
}() );