MediaWiki:Common.js: Difference between revisions
Appearance
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 */