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.
Fix: toggle is-inactive class instead of .hidden property to match the sanitizer-safe markup |
Fix: use event delegation for gallery tab clicks -- direct listeners were being silently dropped, likely by MultimediaViewer touching the parser output after load |
||
| Line 5: | Line 5: | ||
// Clickable tab bar for multi-image infoboxes (image1/caption1, | // Clickable tab bar for multi-image infoboxes (image1/caption1, | ||
// image2/caption2, ...). | // image2/caption2, ...). Delegated on document instead of bound directly | ||
// is | // to each tab: something else on the page (MultimediaViewer's own image | ||
function | // 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 ); | |||
} ); | } ); | ||
} | } | ||
| Line 39: | 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 45: | 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() { | ||
relocateInfoboxOnMobile(); | relocateInfoboxOnMobile(); | ||
} | } | ||
if ( document.readyState === 'loading' ) { | if ( document.readyState === 'loading' ) { | ||
document.addEventListener( 'DOMContentLoaded', init ); | document.addEventListener( 'DOMContentLoaded', init ); | ||
| Line 59: | 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 );
}() );
