mirror of
https://github.com/StarCitizenTools/mediawiki-skins-Citizen.git
synced 2024-11-17 19:32:00 +00:00
175 lines
5.2 KiB
JavaScript
175 lines
5.2 KiB
JavaScript
/* Some of the functions are based on Vector */
|
|
/* ESLint does not like having class names as const */
|
|
/* eslint-disable mediawiki/class-doc */
|
|
const SEARCH_INPUT_ID = 'searchInput',
|
|
SEARCH_LOADING_CLASS = 'search-form__loading';
|
|
|
|
/**
|
|
* Loads the search module via `mw.loader.using` on the element's
|
|
* focus event. Or, if the element is already focused, loads the
|
|
* search module immediately.
|
|
* After the search module is loaded, executes a function to remove
|
|
* the loading indicator.
|
|
*
|
|
* @param {HTMLElement} element search input.
|
|
* @param {string} moduleName resourceLoader module to load.
|
|
* @param {function(): void} afterLoadFn function to execute after search module loads.
|
|
*/
|
|
function loadSearchModule( element, moduleName, afterLoadFn ) {
|
|
const requestSearchModule = () => {
|
|
mw.loader.using( moduleName, afterLoadFn );
|
|
element.removeEventListener( 'focus', requestSearchModule );
|
|
};
|
|
|
|
if ( document.activeElement === element ) {
|
|
requestSearchModule();
|
|
} else {
|
|
element.addEventListener( 'focus', requestSearchModule );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event callback that shows or hides the loading indicator based on the event type.
|
|
* The loading indicator states are:
|
|
* 1. Show on input event (while user is typing)
|
|
* 2. Hide on focusout event (when user removes focus from the input )
|
|
* 3. Show when input is focused, if it contains a query. (in case user re-focuses on input)
|
|
*
|
|
* @param {Event} event
|
|
*/
|
|
function renderSearchLoadingIndicator( event ) {
|
|
const form = /** @type {HTMLElement} */ ( event.currentTarget ),
|
|
input = /** @type {HTMLInputElement} */ ( event.target );
|
|
|
|
if (
|
|
!( event.currentTarget instanceof HTMLElement ) ||
|
|
!( event.target instanceof HTMLInputElement ) ||
|
|
!( input.id === SEARCH_INPUT_ID ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( event.type === 'input' ) {
|
|
form.classList.add( SEARCH_LOADING_CLASS );
|
|
|
|
} else if ( event.type === 'focusout' ) {
|
|
form.classList.remove( SEARCH_LOADING_CLASS );
|
|
|
|
} else if ( event.type === 'focusin' && input.value.trim() ) {
|
|
form.classList.add( SEARCH_LOADING_CLASS );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attaches or detaches the event listeners responsible for activating
|
|
* the loading indicator.
|
|
*
|
|
* @param {HTMLElement} element
|
|
* @param {boolean} attach
|
|
* @param {function(Event): void} eventCallback
|
|
*/
|
|
function setLoadingIndicatorListeners( element, attach, eventCallback ) {
|
|
|
|
/** @type { "addEventListener" | "removeEventListener" } */
|
|
const addOrRemoveListener = ( attach ? 'addEventListener' : 'removeEventListener' );
|
|
|
|
[ 'input', 'focusin', 'focusout' ].forEach( function ( eventType ) {
|
|
element[ addOrRemoveListener ]( eventType, eventCallback );
|
|
} );
|
|
|
|
if ( !attach ) {
|
|
element.classList.remove( SEARCH_LOADING_CLASS );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Manually focus on the input field if checkbox is checked
|
|
*
|
|
* @param {HTMLInputElement} checkbox
|
|
* @param {HTMLInputElement} input
|
|
* @return {void}
|
|
*/
|
|
function focusOnChecked( checkbox, input ) {
|
|
if ( checkbox.checked ) {
|
|
input.focus();
|
|
} else {
|
|
input.blur();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Manually check the checkbox state when the button is SLASH is pressed.
|
|
*
|
|
* @param {Window} window
|
|
* @param {HTMLInputElement} checkbox
|
|
* @param {HTMLInputElement} input
|
|
* @return {void}
|
|
*/
|
|
function bindExpandOnSlash( window, checkbox, input ) {
|
|
const onExpandOnSlash = ( /** @type {KeyboardEvent} */ event ) => {
|
|
// Only handle SPACE and ENTER.
|
|
if ( event.key === '/' &&
|
|
!event.target.matches( 'input' ) &&
|
|
!event.target.matches( 'textarea' ) &&
|
|
!document.documentElement.classList.contains( 've-active' )
|
|
) {
|
|
checkbox.checked = true;
|
|
focusOnChecked( checkbox, input );
|
|
}
|
|
};
|
|
|
|
window.addEventListener( 'keyup', onExpandOnSlash, true );
|
|
}
|
|
|
|
/**
|
|
* @param {Window} window
|
|
* @param {HTMLInputElement} input
|
|
* @param {HTMLElement} target
|
|
* @return {void}
|
|
*/
|
|
function initCheckboxHack( window, input, target ) {
|
|
const checkboxHack = require( './checkboxHack.js' ),
|
|
button = document.getElementById( 'search-toggle' ),
|
|
checkbox = document.getElementById( 'search-checkbox' );
|
|
|
|
if ( checkbox instanceof HTMLInputElement && button ) {
|
|
checkboxHack.bindToggleOnClick( checkbox, button );
|
|
checkboxHack.bindUpdateAriaExpandedOnInput( checkbox, button );
|
|
checkboxHack.updateAriaExpanded( checkbox, button );
|
|
checkboxHack.bindToggleOnSpaceEnter( checkbox, button );
|
|
checkboxHack.bindDismissOnClickOutside( window, checkbox, button, target );
|
|
checkboxHack.bindDismissOnFocusLoss( window, checkbox, button, target );
|
|
checkboxHack.bindDismissOnEscape( window, checkbox );
|
|
}
|
|
|
|
bindExpandOnSlash( window, checkbox, input );
|
|
|
|
// Focus when toggled
|
|
checkbox.addEventListener( 'input', () => {
|
|
focusOnChecked( checkbox, input );
|
|
} );
|
|
}
|
|
|
|
/**
|
|
* @param {Window} window
|
|
* @return {void}
|
|
*/
|
|
function initSearch( window ) {
|
|
const searchForm = document.getElementById( 'searchform' ),
|
|
searchInput = document.getElementById( SEARCH_INPUT_ID );
|
|
|
|
initCheckboxHack( window, searchInput, searchForm );
|
|
|
|
if ( mw.config.get( 'wgCitizenEnableSearch' ) ) {
|
|
setLoadingIndicatorListeners( searchForm, true, renderSearchLoadingIndicator );
|
|
loadSearchModule( searchInput, 'skins.citizen.scripts.search', () => {
|
|
setLoadingIndicatorListeners( searchForm, false, renderSearchLoadingIndicator );
|
|
} );
|
|
} else {
|
|
loadSearchModule( searchInput, 'mediawiki.searchSuggest', () => {} );
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
init: initSearch
|
|
};
|