Jump to content

MediaWiki:Common.js: Difference between revisions

From Elite Dangerous Wiki
Content deleted Content added
Removing customization policy violations. Moving imports to MediaWiki:ImportJS.
 
Improve Main Page browsing, article search, featured article links and independent-wiki information
 
Line 30: Line 30:


/* END - switching images for the main page navigation buttons */
/* END - switching images for the main page navigation buttons */

/* BEGIN Elite Main Page search */
if ( mw.config.get( "wgIsMainPage" ) ) {
mw.loader.using( [ "mediawiki.searchSuggest", "mediawiki.util" ] ).then( function () {
/* Main Page InputBox enhancement. Load after:
* mw.loader.using( [ 'mediawiki.searchSuggest', 'mediawiki.util' ] )
* The first dependency supplies jquery.suggestions, mediawiki.api and user.options.
* Keep the ordinary InputBox form: no-JS/error/short-query submission still works.
*/
( function ( mw, $ ) {

'use strict';
if ( !mw.config.get( 'wgIsMainPage' ) ) {
return;
}

$( function () {
// mediawiki.searchSuggest's ready callback runs first. Reconfigure its
// existing widget, rather than attaching a competing autocomplete.
mw.hook( 'wikipage.content' ).add( function () {
$( '.ed-home-search input.mw-searchInput' ).each( function () {
const input = this;
const $input = $( input );
if ( $input.data( 'elite-home-search' ) || !input.form ) {
return;
}
if ( !$.fn.suggestions || !mw.searchSuggest || !mw.Api || !mw.util ) {
return; // Leave the working form alone if dependencies are absent.
}

let request = null;
let generation = 0;
let composing = false;
const api = new mw.Api(); // Configured local wiki only, never ForeignApi.
// A stable per-input ID also permits more than one hero InputBox.
const listId = 'elite-home-suggestions-' + $( '.ed-home-search input.mw-searchInput' ).index( input );
let context;

function cancel() {
generation++;
if ( request ) {
const previous = request;
request = null;
previous.abort();
}
}

function syncAria() {
const container = context.data.$container[ 0 ];
const results = container.querySelectorAll( '.suggestions-result' );
const expanded = container.style.display !== 'none' && results.length > 0;
input.setAttribute( 'aria-expanded', String( expanded ) );
input.removeAttribute( 'aria-activedescendant' );
results.forEach( function ( result, index ) {
result.id = listId + '-' + index;
result.setAttribute( 'role', 'option' );
const selected = expanded && result.classList.contains( 'suggestions-result-current' );
result.setAttribute( 'aria-selected', String( selected ) );
if ( selected ) {
input.setAttribute( 'aria-activedescendant', result.id );
}
} );
}

function hide() {
context.data.$container.hide();
syncAria();
}

function localUrl( title ) {
// Ignore OpenSearch's URL array, deriving links through MediaWiki.
const url = new URL( mw.util.getUrl( title ), window.location.href );
return url.origin === window.location.origin ? url.href : null;
}

$input.suggestions( {
$region: $input,
delay: 250,
maxRows: 6,
maxExpandFactor: 1,
cache: false,
highlightInput: false,
submitOnClick: false,
special: {},
fetch: function ( query, response ) {
if ( !context ) {
return; // Initial native construction may fetch synchronously.
}
cancel();
const current = generation;
if ( composing || query.trim().length < 2 || query.length > 200 ) {
response( [] );
hide();
return;
}
// [0], not 0: native request() otherwise falls back to the
// user's configured namespaces because zero is falsy.
request = mw.searchSuggest.request( api, query, function ( titles ) {
if ( current !== generation || query !== input.value ||
document.activeElement !== input || !input.isConnected ) {
return;
}
const safeTitles = Array.isArray( titles ) ? titles.filter( function ( title ) {
return typeof title === 'string' && title.length > 0 &&
!/[\u0000-\u001f\u007f]/.test( title ) && localUrl( title );
} ).slice( 0, 6 ) : [];
response( safeTitles );
if ( safeTitles.length === 0 ) {
hide();
}
}, 6, [ 0 ] );
request.fail( function () {
if ( current === generation ) {
response( [] );
hide();
}
} );
},
cancel: cancel,
update: { after: function () { if ( context ) { syncAria(); } } },
result: {
render: function ( title ) {
this[ 0 ].textContent = title;
const link = document.createElement( 'a' );
link.href = localUrl( title );
link.tabIndex = -1; // Focus stays on the combobox.
link.className = 'mw-searchSuggest-link';
this.wrap( link );
},
select: function ( textbox, source ) {
if ( source === 'keyboard' ) {
const url = localUrl( this.data( 'text' ) );
if ( url ) {
cancel();
window.location.assign( url );
return false;
}
}
return true; // Mouse uses the ordinary local anchor.
}
}
} );
context = $input.data( 'suggestions-context' );
cancel(); // Abort any generic request started before reconfiguration.
const genericRequest = $.data( input, 'request' );
if ( genericRequest ) {
genericRequest.abort();
$.removeData( input, 'request' );
}
$input.data( 'elite-home-search', true );
$input.attr( {
role: 'combobox',
'aria-autocomplete': 'list',
'aria-haspopup': 'listbox',
'aria-controls': listId,
'aria-expanded': 'false'
} ); // Preserve InputBox's existing accessible label.
context.data.$container.addClass( 'ed-home-suggestions' );
const results = context.data.$container[ 0 ].querySelector( '.suggestions-results' );
results.id = listId;
results.setAttribute( 'role', 'listbox' );
results.setAttribute( 'aria-label', 'Suggested articles' );
hide();

// Native keyboard handling owns arrows/Enter/Escape. Input covers
// paste, mobile keyboards and other edits without a keypress event.
function changed() {
cancel();
hide();
if ( !composing ) {
context.data.keypressed = 0;
$input.trigger( 'keypress' ); // Native 250 ms debounce + cancel.
}
}
$input.on( 'input.eliteHomeSearch', changed )
.on( 'compositionstart.eliteHomeSearch', function () {
composing = true;
cancel();
hide();
} )
.on( 'compositionend.eliteHomeSearch', function () {
composing = false;
changed();
} );
$( input.form ).on( 'submit.eliteHomeSearch', function () { cancel(); hide(); } );
const observer = new MutationObserver( syncAria );
observer.observe( context.data.$container[ 0 ], {
subtree: true, childList: true, attributes: true,
attributeFilter: [ 'class', 'style' ]
} );
if ( document.activeElement === input && input.value.length >= 2 ) {
changed();
}
} );
} );
} );
}( mediaWiki, jQuery ) );

} );
}
/* END Elite Main Page search */

Latest revision as of 00:25, 1 November 2026

/* Any JavaScript here will be loaded for all users on every page load. */

window.AjaxRCRefreshText = 'Auto-refresh';
window.AjaxRCRefreshHoverText = 'Automatically refresh the page';
window.ajaxPages = [
    "Special:RecentChanges",
    "Special:WikiActivity",
    "Special:Watchlist",
    "Special:Log",
    "Special:Contributions"
];

/* START - switching images for the main page navigation buttons */

$("#ShipsButton img").hover(function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/9/9b/Ships_Selected.png");}, function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/1/19/Ships_Default.png");});

$("#OutfittingButton img").hover(function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/7/76/Outfitting_Selected.png");}, function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/f/fc/Outfitting_Default.png");});

$("#EngineersButton img").hover(function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/d/da/Engineers_Selected.png");}, function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/9/96/Engineers_Default.png");});

$("#RanksButton img").hover(function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/8/80/Ranks_Selected.png");}, function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/f/f3/Ranks_Default.png");});

$("#FactionsButton img").hover(function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/d/de/Factions_Selected.png");}, function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/b/bc/Factions_Default.png");});

$("#StarsButton img").hover(function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/9/9b/Stars_Selected.png");}, function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/9/90/Stars_Default.png");});

$("#PlanetsButton img").hover(function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/8/8d/Planets_Selected.png");}, function() {$(this).attr("src", "https://vignette.wikia.nocookie.net/elite-dangerous/images/a/a3/Planets_Default.png");});

$("#RolesButton img").hover(function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/0/01/Roles_Selected.png");}, function() {$(this).attr("src", "https://images.wikia.nocookie.net/elite-dangerous/images/4/4d/Roles_Default.png");});

/* END - switching images for the main page navigation buttons */

/* BEGIN Elite Main Page search */
if ( mw.config.get( "wgIsMainPage" ) ) {
mw.loader.using( [ "mediawiki.searchSuggest", "mediawiki.util" ] ).then( function () {
/* Main Page InputBox enhancement. Load after:
 * mw.loader.using( [ 'mediawiki.searchSuggest', 'mediawiki.util' ] )
 * The first dependency supplies jquery.suggestions, mediawiki.api and user.options.
 * Keep the ordinary InputBox form: no-JS/error/short-query submission still works.
 */
( function ( mw, $ ) {

	'use strict';
	if ( !mw.config.get( 'wgIsMainPage' ) ) {
		return;
	}

	$( function () {
		// mediawiki.searchSuggest's ready callback runs first. Reconfigure its
		// existing widget, rather than attaching a competing autocomplete.
		mw.hook( 'wikipage.content' ).add( function () {
			$( '.ed-home-search input.mw-searchInput' ).each( function () {
				const input = this;
				const $input = $( input );
				if ( $input.data( 'elite-home-search' ) || !input.form ) {
					return;
				}
				if ( !$.fn.suggestions || !mw.searchSuggest || !mw.Api || !mw.util ) {
					return; // Leave the working form alone if dependencies are absent.
				}

				let request = null;
				let generation = 0;
				let composing = false;
				const api = new mw.Api(); // Configured local wiki only, never ForeignApi.
				// A stable per-input ID also permits more than one hero InputBox.
				const listId = 'elite-home-suggestions-' + $( '.ed-home-search input.mw-searchInput' ).index( input );
				let context;

				function cancel() {
					generation++;
					if ( request ) {
						const previous = request;
						request = null;
						previous.abort();
					}
				}

				function syncAria() {
					const container = context.data.$container[ 0 ];
					const results = container.querySelectorAll( '.suggestions-result' );
					const expanded = container.style.display !== 'none' && results.length > 0;
					input.setAttribute( 'aria-expanded', String( expanded ) );
					input.removeAttribute( 'aria-activedescendant' );
					results.forEach( function ( result, index ) {
						result.id = listId + '-' + index;
						result.setAttribute( 'role', 'option' );
						const selected = expanded && result.classList.contains( 'suggestions-result-current' );
						result.setAttribute( 'aria-selected', String( selected ) );
						if ( selected ) {
							input.setAttribute( 'aria-activedescendant', result.id );
						}
					} );
				}

				function hide() {
					context.data.$container.hide();
					syncAria();
				}

				function localUrl( title ) {
					// Ignore OpenSearch's URL array, deriving links through MediaWiki.
					const url = new URL( mw.util.getUrl( title ), window.location.href );
					return url.origin === window.location.origin ? url.href : null;
				}

				$input.suggestions( {
					$region: $input,
					delay: 250,
					maxRows: 6,
					maxExpandFactor: 1,
					cache: false,
					highlightInput: false,
					submitOnClick: false,
					special: {},
					fetch: function ( query, response ) {
						if ( !context ) {
							return; // Initial native construction may fetch synchronously.
						}
						cancel();
						const current = generation;
						if ( composing || query.trim().length < 2 || query.length > 200 ) {
							response( [] );
							hide();
							return;
						}
						// [0], not 0: native request() otherwise falls back to the
						// user's configured namespaces because zero is falsy.
						request = mw.searchSuggest.request( api, query, function ( titles ) {
							if ( current !== generation || query !== input.value ||
								document.activeElement !== input || !input.isConnected ) {
								return;
							}
							const safeTitles = Array.isArray( titles ) ? titles.filter( function ( title ) {
								return typeof title === 'string' && title.length > 0 &&
									!/[\u0000-\u001f\u007f]/.test( title ) && localUrl( title );
							} ).slice( 0, 6 ) : [];
							response( safeTitles );
							if ( safeTitles.length === 0 ) {
								hide();
							}
						}, 6, [ 0 ] );
						request.fail( function () {
							if ( current === generation ) {
								response( [] );
								hide();
							}
						} );
					},
					cancel: cancel,
					update: { after: function () { if ( context ) { syncAria(); } } },
					result: {
						render: function ( title ) {
							this[ 0 ].textContent = title;
							const link = document.createElement( 'a' );
							link.href = localUrl( title );
							link.tabIndex = -1; // Focus stays on the combobox.
							link.className = 'mw-searchSuggest-link';
							this.wrap( link );
						},
						select: function ( textbox, source ) {
							if ( source === 'keyboard' ) {
								const url = localUrl( this.data( 'text' ) );
								if ( url ) {
									cancel();
									window.location.assign( url );
									return false;
								}
							}
							return true; // Mouse uses the ordinary local anchor.
						}
					}
				} );
				context = $input.data( 'suggestions-context' );
				cancel(); // Abort any generic request started before reconfiguration.
				const genericRequest = $.data( input, 'request' );
				if ( genericRequest ) {
					genericRequest.abort();
					$.removeData( input, 'request' );
				}
				$input.data( 'elite-home-search', true );
				$input.attr( {
					role: 'combobox',
					'aria-autocomplete': 'list',
					'aria-haspopup': 'listbox',
					'aria-controls': listId,
					'aria-expanded': 'false'
				} ); // Preserve InputBox's existing accessible label.
				context.data.$container.addClass( 'ed-home-suggestions' );
				const results = context.data.$container[ 0 ].querySelector( '.suggestions-results' );
				results.id = listId;
				results.setAttribute( 'role', 'listbox' );
				results.setAttribute( 'aria-label', 'Suggested articles' );
				hide();

				// Native keyboard handling owns arrows/Enter/Escape. Input covers
				// paste, mobile keyboards and other edits without a keypress event.
				function changed() {
					cancel();
					hide();
					if ( !composing ) {
						context.data.keypressed = 0;
						$input.trigger( 'keypress' ); // Native 250 ms debounce + cancel.
					}
				}
				$input.on( 'input.eliteHomeSearch', changed )
					.on( 'compositionstart.eliteHomeSearch', function () {
						composing = true;
						cancel();
						hide();
					} )
					.on( 'compositionend.eliteHomeSearch', function () {
						composing = false;
						changed();
					} );
				$( input.form ).on( 'submit.eliteHomeSearch', function () { cancel(); hide(); } );
				const observer = new MutationObserver( syncAria );
				observer.observe( context.data.$container[ 0 ], {
					subtree: true, childList: true, attributes: true,
					attributeFilter: [ 'class', 'style' ]
				} );
				if ( document.activeElement === input && input.value.length >= 2 ) {
					changed();
				}
			} );
		} );
	} );
}( mediaWiki, jQuery ) );

} );
}
/* END Elite Main Page search */