MediaWiki:Gadget-site.js: Difference between revisions
From TibiaCraft
Jump to navigationJump to search
Created page with "a" |
No edit summary |
||
| Line 1: | Line 1: | ||
a | /** | ||
* Scripts placed here are loaded on both desktop and mobile views. | |||
* | |||
* Desktop-only scripts should go in [[MediaWiki:Common.js]] | |||
* Mobile-only scripts should go in [[MediaWiki:Minerva.js]]. | |||
*/ | |||
( function() { | |||
'use strict'; | |||
/* Variables for interface text used throughout the script, for ease of translating */ | |||
var i18n = { | |||
// Collapsible elements and page loader | |||
hideText: 'hide', | |||
showText: 'show', | |||
loadErrorTitle: 'An error occurred loading the content' | |||
}; | |||
/* Fired whenever wiki content is added. (#mw-content-text, live preview, load page, etc.) */ | |||
mw.hook( 'wikipage.content' ).add( function( $wikipageContent ) { | |||
/** | |||
* Collapsible elements | |||
* | |||
* Add the "collapsible" class to an element and the child element with class "collapsible-content" | |||
* (or everything but the header row if a table) will be hidden when the element is collapsed. | |||
* | |||
* * Add the class "collapsed" to the element to make it start out collapsed. | |||
* * Add either "collapsetoggle-left" or "collapsetoggle-inline" to the element to choose the collapse | |||
* toggle alignment (defaults to right). | |||
* * Add an ID in the format of "collapsible-<x>" to the element to make any element with the class | |||
* "collapsetoggle-custom" and a matching class in the format of "collapsible-<x>-toggle" control | |||
* the collapsing instead of the standard button. | |||
* If the custom toggle contains an element with the "jslink" class, only that will be clickable. | |||
*/ | |||
( function() { | |||
var $collapsibles = $wikipageContent.find( '.collapsible' ); | |||
if ( !$collapsibles.length ) { | |||
return; | |||
} | |||
var $toggleTemplate = $( '<span>' ).addClass( 'collapsetoggle' ).append( | |||
'[', $( '<span>' ).addClass( 'jslink' ), ']' | |||
); | |||
$collapsibles.each( function() { | |||
var $collapsible = $( this ); | |||
if ( $collapsible.data( 'made-collapsible' ) ) { | |||
return true; | |||
} | |||
var $children = $collapsible.children(); | |||
var showText = $collapsible.data( 'expandtext' ) || i18n.showText; | |||
var hideText = $collapsible.data( 'collapsetext' ) || i18n.hideText; | |||
// If there is no content area, add it | |||
if ( !$collapsible.is( 'table' ) && !$children.filter( '.collapsible-content' ).length ) { | |||
if ( $collapsible.is( 'tr.collapsible-rows' ) ) { | |||
var $collapsibleRows = $collapsible.nextUntil( '.collapsible-rows' ); | |||
if ( $collapsibleRows.hasClass( 'collapsible-row' ) ) { | |||
$collapsibleRows = $collapsibleRows.filter( 'collapsible-row' ); | |||
} else { | |||
$collapsibleRows.addClass( 'collapsible-row' ); | |||
} | |||
if ( $collapsible.hasClass( 'collapsed' ) && !$collapsibleRows.hasClass( 'collapsed' ) ) { | |||
$collapsibleRows.addClass( 'collapsed' ); | |||
} | |||
$collapsible = $collapsible.add( $collapsibleRows ); | |||
} else if ( $collapsible.is( 'tr' ) ) { | |||
$children.addClass( 'collapsible-content' ); | |||
} else { | |||
$collapsible.wrapInner( '<div class="collapsible-content">' ); | |||
$children = $collapsible.children(); | |||
} | |||
} | |||
var $toggle; | |||
var id = $collapsible.attr( 'id' ); | |||
if ( id && id.match( /^collapsible-./ ) ) { | |||
$toggle = $( $wikipageContent[0].getElementsByClassName( id + '-toggle' ) ) | |||
.filter( '.collapsetoggle-custom' ).css( 'visibility', 'visible' ); | |||
} | |||
// Create and insert the toggle button if there is no custom one | |||
if ( !$toggle || !$toggle.length ) { | |||
var $toggleContainer; | |||
if ( $collapsible.is( 'table' ) ) { | |||
var $rows = $children.filter( 'thead' ).children(); | |||
if ( !$rows.length ) { | |||
$rows = $children.filter( 'tbody' ).first().children(); | |||
if ( !$rows.length ) { | |||
$rows = $children.filter( 'tr' ); | |||
} | |||
} | |||
$toggleContainer = $rows.first().children().last(); | |||
} else { | |||
$toggleContainer = $children.first(); | |||
if ( $toggleContainer.hasClass( 'collapsible-content' ) ) { | |||
$toggleContainer = $collapsible; | |||
} | |||
} | |||
$toggle = $toggleTemplate.clone(); | |||
if ( | |||
$toggleContainer !== $collapsible && ( | |||
$collapsible.hasClass( 'collapsetoggle-inline' ) || | |||
$collapsible.hasClass( 'collapse-button-none' ) | |||
) ) { | |||
$toggleContainer.append( $toggle ); | |||
} else { | |||
$toggleContainer.prepend( $toggle ); | |||
} | |||
} | |||
var $toggleLink = $toggle.find( '.jslink' ); | |||
if ( !$toggleLink.length ) { | |||
$toggleLink = $toggle; | |||
} | |||
$toggleLink.attr( 'tabindex', 0 ).text( hideText ); | |||
// Find max toggle size, and set its min-width to it | |||
var hideWidth = $toggle.width(); | |||
$toggleLink.text( showText ); | |||
var showWidth = $toggle.width(); | |||
if ( hideWidth !== showWidth ) { | |||
$toggle.css( 'min-width', hideWidth > showWidth ? hideWidth : showWidth ); | |||
} | |||
// Set the text back to hide if it's not collapsed to begin with | |||
if ( !$collapsible.first().hasClass( 'collapsed' ) ) { | |||
$toggleLink.text( hideText ); | |||
} | |||
$toggleLink.on( 'click keydown', function( e ) { | |||
// Only trigger on enter press | |||
if ( e.keyCode && e.keyCode !== 13 ) { | |||
return; | |||
} | |||
// Don't toggle when clicking buttons or links inside the toggle | |||
var $target = $( e.target ); | |||
if ( $target.is( 'button' ) || $target.is( 'a' ) ) { | |||
return; | |||
} | |||
$collapsible.toggleClass( 'collapsed' ); | |||
if ( $collapsible.first().hasClass( 'collapsed' ) ) { | |||
$toggleLink.text( showText ); | |||
} else { | |||
$toggleLink.text( hideText ); | |||
} | |||
// Stop table sorting activating when clicking the link | |||
e.stopPropagation(); | |||
} ); | |||
$collapsible.data( 'made-collapsible', true ); | |||
} ); | |||
}() ); | |||
/** | |||
* Set minimum height for animations to prevent moving the page if the frames | |||
* differ in height | |||
*/ | |||
( function() { | |||
// Set frames to be visible for measuring height | |||
var $animated = $wikipageContent.find( '.animated' ).addClass( 'animated-visible' ); | |||
// Group frames per animation | |||
var animateds = []; | |||
$animated.each( function() { | |||
animateds.push( { | |||
$: $( this ).find( '> .animated-subframe' ).addBack() | |||
.find( '> *:not(.animated-subframe)' ), | |||
} ); | |||
} ); | |||
// Get highest frame for each animation (if heights differ) | |||
$.each( animateds, function() { | |||
var minHeight = 0, differentHeights; | |||
this.$.each( function() { | |||
var height = this.offsetHeight; | |||
differentHeights = differentHeights || minHeight && height !== minHeight; | |||
minHeight = Math.max( height, minHeight ); | |||
} ); | |||
if ( differentHeights ) { | |||
this.height = minHeight; | |||
} | |||
} ); | |||
// Set animation to be at least as tall as the tallest frame, | |||
// and set the non-active frames to be hidden again | |||
$animated.each( function( i ) { | |||
$( this ).css( 'min-height', animateds[i].height ); | |||
} ).removeClass( 'animated-visible' ); | |||
// Load all images in the animation as soon as the animated content becomes visible | |||
var animatedObserver = new IntersectionObserver( function( entries ) { | |||
entries.forEach( function( entry ) { | |||
if ( entry.isIntersecting && !entry.target.classList.contains( 'animated-lazyloaded' ) ) { | |||
$( entry.target ).find( 'img' ).attr( 'loading', 'eager' ); | |||
entry.target.classList.add( 'animated-lazyloaded' ); | |||
animatedObserver.unobserve( entry.target ); | |||
} | |||
} ); | |||
} ); | |||
// Observe every animation | |||
$animated.each( function() { | |||
animatedObserver.observe(this); | |||
} ); | |||
}() ); | |||
} ); | |||
/* End wiki content hook */ | |||
/* Fires when DOM is ready */ | |||
$( function() { | |||
/** | |||
* Element animator | |||
* | |||
* Cycles through a set of elements (or "frames") on a 2 second timer per frame | |||
* Add the "animated" class to the frame containing the elements to animate. | |||
* Optionally, add the "animated-active" class to the frame to display first. | |||
* Optionally, add the "animated-subframe" class to a frame, and the | |||
* "animated-active" class to a subframe within, in order to designate a set of | |||
* subframes which will only be cycled every time the parent frame is displayed. | |||
* Animations with the "animated-paused" class will be skipped each interval. | |||
* | |||
* Requires some styling from [[MediaWiki:Gadget-site-styles.css]]. | |||
*/ | |||
( function() { | |||
var $content = $( '#mw-content-text' ); | |||
var advanceFrame = function( parentElem, parentSelector ) { | |||
var curFrame = parentElem.querySelector( parentSelector + ' > .animated-active' ); | |||
$( curFrame ).removeClass( 'animated-active' ); | |||
var $nextFrame = $( curFrame && curFrame.nextElementSibling || parentElem.firstElementChild ); | |||
return $nextFrame.addClass( 'animated-active' ); | |||
}; | |||
// Set the name of the hidden property | |||
var hidden; | |||
if ( typeof document.hidden !== 'undefined' ) { | |||
hidden = 'hidden'; | |||
} else if ( typeof document.msHidden !== 'undefined' ) { | |||
hidden = 'msHidden'; | |||
} else if ( typeof document.webkitHidden !== 'undefined' ) { | |||
hidden = 'webkitHidden'; | |||
} | |||
setInterval( function() { | |||
if ( hidden && document[hidden] ) { | |||
return; | |||
} | |||
$content.find( '.animated' ).each( function() { | |||
if ( $( this ).hasClass( 'animated-paused' ) ) { | |||
return; | |||
} | |||
var $nextFrame = advanceFrame( this, '.animated' ); | |||
if ( $nextFrame.hasClass( 'animated-subframe' ) ) { | |||
advanceFrame( $nextFrame[0], '.animated-subframe' ); | |||
} | |||
} ); | |||
}, 2000 ); | |||
}() ); | |||
} ); | |||
/* End DOM ready */ | |||
}() ); | |||
Latest revision as of 23:38, 6 October 2025
/**
* Scripts placed here are loaded on both desktop and mobile views.
*
* Desktop-only scripts should go in [[MediaWiki:Common.js]]
* Mobile-only scripts should go in [[MediaWiki:Minerva.js]].
*/
( function() {
'use strict';
/* Variables for interface text used throughout the script, for ease of translating */
var i18n = {
// Collapsible elements and page loader
hideText: 'hide',
showText: 'show',
loadErrorTitle: 'An error occurred loading the content'
};
/* Fired whenever wiki content is added. (#mw-content-text, live preview, load page, etc.) */
mw.hook( 'wikipage.content' ).add( function( $wikipageContent ) {
/**
* Collapsible elements
*
* Add the "collapsible" class to an element and the child element with class "collapsible-content"
* (or everything but the header row if a table) will be hidden when the element is collapsed.
*
* * Add the class "collapsed" to the element to make it start out collapsed.
* * Add either "collapsetoggle-left" or "collapsetoggle-inline" to the element to choose the collapse
* toggle alignment (defaults to right).
* * Add an ID in the format of "collapsible-<x>" to the element to make any element with the class
* "collapsetoggle-custom" and a matching class in the format of "collapsible-<x>-toggle" control
* the collapsing instead of the standard button.
* If the custom toggle contains an element with the "jslink" class, only that will be clickable.
*/
( function() {
var $collapsibles = $wikipageContent.find( '.collapsible' );
if ( !$collapsibles.length ) {
return;
}
var $toggleTemplate = $( '<span>' ).addClass( 'collapsetoggle' ).append(
'[', $( '<span>' ).addClass( 'jslink' ), ']'
);
$collapsibles.each( function() {
var $collapsible = $( this );
if ( $collapsible.data( 'made-collapsible' ) ) {
return true;
}
var $children = $collapsible.children();
var showText = $collapsible.data( 'expandtext' ) || i18n.showText;
var hideText = $collapsible.data( 'collapsetext' ) || i18n.hideText;
// If there is no content area, add it
if ( !$collapsible.is( 'table' ) && !$children.filter( '.collapsible-content' ).length ) {
if ( $collapsible.is( 'tr.collapsible-rows' ) ) {
var $collapsibleRows = $collapsible.nextUntil( '.collapsible-rows' );
if ( $collapsibleRows.hasClass( 'collapsible-row' ) ) {
$collapsibleRows = $collapsibleRows.filter( 'collapsible-row' );
} else {
$collapsibleRows.addClass( 'collapsible-row' );
}
if ( $collapsible.hasClass( 'collapsed' ) && !$collapsibleRows.hasClass( 'collapsed' ) ) {
$collapsibleRows.addClass( 'collapsed' );
}
$collapsible = $collapsible.add( $collapsibleRows );
} else if ( $collapsible.is( 'tr' ) ) {
$children.addClass( 'collapsible-content' );
} else {
$collapsible.wrapInner( '<div class="collapsible-content">' );
$children = $collapsible.children();
}
}
var $toggle;
var id = $collapsible.attr( 'id' );
if ( id && id.match( /^collapsible-./ ) ) {
$toggle = $( $wikipageContent[0].getElementsByClassName( id + '-toggle' ) )
.filter( '.collapsetoggle-custom' ).css( 'visibility', 'visible' );
}
// Create and insert the toggle button if there is no custom one
if ( !$toggle || !$toggle.length ) {
var $toggleContainer;
if ( $collapsible.is( 'table' ) ) {
var $rows = $children.filter( 'thead' ).children();
if ( !$rows.length ) {
$rows = $children.filter( 'tbody' ).first().children();
if ( !$rows.length ) {
$rows = $children.filter( 'tr' );
}
}
$toggleContainer = $rows.first().children().last();
} else {
$toggleContainer = $children.first();
if ( $toggleContainer.hasClass( 'collapsible-content' ) ) {
$toggleContainer = $collapsible;
}
}
$toggle = $toggleTemplate.clone();
if (
$toggleContainer !== $collapsible && (
$collapsible.hasClass( 'collapsetoggle-inline' ) ||
$collapsible.hasClass( 'collapse-button-none' )
) ) {
$toggleContainer.append( $toggle );
} else {
$toggleContainer.prepend( $toggle );
}
}
var $toggleLink = $toggle.find( '.jslink' );
if ( !$toggleLink.length ) {
$toggleLink = $toggle;
}
$toggleLink.attr( 'tabindex', 0 ).text( hideText );
// Find max toggle size, and set its min-width to it
var hideWidth = $toggle.width();
$toggleLink.text( showText );
var showWidth = $toggle.width();
if ( hideWidth !== showWidth ) {
$toggle.css( 'min-width', hideWidth > showWidth ? hideWidth : showWidth );
}
// Set the text back to hide if it's not collapsed to begin with
if ( !$collapsible.first().hasClass( 'collapsed' ) ) {
$toggleLink.text( hideText );
}
$toggleLink.on( 'click keydown', function( e ) {
// Only trigger on enter press
if ( e.keyCode && e.keyCode !== 13 ) {
return;
}
// Don't toggle when clicking buttons or links inside the toggle
var $target = $( e.target );
if ( $target.is( 'button' ) || $target.is( 'a' ) ) {
return;
}
$collapsible.toggleClass( 'collapsed' );
if ( $collapsible.first().hasClass( 'collapsed' ) ) {
$toggleLink.text( showText );
} else {
$toggleLink.text( hideText );
}
// Stop table sorting activating when clicking the link
e.stopPropagation();
} );
$collapsible.data( 'made-collapsible', true );
} );
}() );
/**
* Set minimum height for animations to prevent moving the page if the frames
* differ in height
*/
( function() {
// Set frames to be visible for measuring height
var $animated = $wikipageContent.find( '.animated' ).addClass( 'animated-visible' );
// Group frames per animation
var animateds = [];
$animated.each( function() {
animateds.push( {
$: $( this ).find( '> .animated-subframe' ).addBack()
.find( '> *:not(.animated-subframe)' ),
} );
} );
// Get highest frame for each animation (if heights differ)
$.each( animateds, function() {
var minHeight = 0, differentHeights;
this.$.each( function() {
var height = this.offsetHeight;
differentHeights = differentHeights || minHeight && height !== minHeight;
minHeight = Math.max( height, minHeight );
} );
if ( differentHeights ) {
this.height = minHeight;
}
} );
// Set animation to be at least as tall as the tallest frame,
// and set the non-active frames to be hidden again
$animated.each( function( i ) {
$( this ).css( 'min-height', animateds[i].height );
} ).removeClass( 'animated-visible' );
// Load all images in the animation as soon as the animated content becomes visible
var animatedObserver = new IntersectionObserver( function( entries ) {
entries.forEach( function( entry ) {
if ( entry.isIntersecting && !entry.target.classList.contains( 'animated-lazyloaded' ) ) {
$( entry.target ).find( 'img' ).attr( 'loading', 'eager' );
entry.target.classList.add( 'animated-lazyloaded' );
animatedObserver.unobserve( entry.target );
}
} );
} );
// Observe every animation
$animated.each( function() {
animatedObserver.observe(this);
} );
}() );
} );
/* End wiki content hook */
/* Fires when DOM is ready */
$( function() {
/**
* Element animator
*
* Cycles through a set of elements (or "frames") on a 2 second timer per frame
* Add the "animated" class to the frame containing the elements to animate.
* Optionally, add the "animated-active" class to the frame to display first.
* Optionally, add the "animated-subframe" class to a frame, and the
* "animated-active" class to a subframe within, in order to designate a set of
* subframes which will only be cycled every time the parent frame is displayed.
* Animations with the "animated-paused" class will be skipped each interval.
*
* Requires some styling from [[MediaWiki:Gadget-site-styles.css]].
*/
( function() {
var $content = $( '#mw-content-text' );
var advanceFrame = function( parentElem, parentSelector ) {
var curFrame = parentElem.querySelector( parentSelector + ' > .animated-active' );
$( curFrame ).removeClass( 'animated-active' );
var $nextFrame = $( curFrame && curFrame.nextElementSibling || parentElem.firstElementChild );
return $nextFrame.addClass( 'animated-active' );
};
// Set the name of the hidden property
var hidden;
if ( typeof document.hidden !== 'undefined' ) {
hidden = 'hidden';
} else if ( typeof document.msHidden !== 'undefined' ) {
hidden = 'msHidden';
} else if ( typeof document.webkitHidden !== 'undefined' ) {
hidden = 'webkitHidden';
}
setInterval( function() {
if ( hidden && document[hidden] ) {
return;
}
$content.find( '.animated' ).each( function() {
if ( $( this ).hasClass( 'animated-paused' ) ) {
return;
}
var $nextFrame = advanceFrame( this, '.animated' );
if ( $nextFrame.hasClass( 'animated-subframe' ) ) {
advanceFrame( $nextFrame[0], '.animated-subframe' );
}
} );
}, 2000 );
}() );
} );
/* End DOM ready */
}() );