const config = require( './config.json' );
const clearAddressBar = require( './clearAddressBar.js' );
const searchRoute = new RegExp( /\/search/ );
let tempInput = null;
let searchDialogOpened = false;
/**
* @param {module:mediawiki.router} router
* @param {HTMLButtonElement} trigger
* @ignore
*/
function addRoutes( router, trigger ) {
clearAddressBar( router, searchRoute );
router.addRoute( searchRoute, () => {
const searchModuleName = config.searchModule;
mw.loader.using( searchModuleName ).then( () => {
// eslint-disable-next-line security/detect-non-literal-require
const { init } = require( searchModuleName );
// If it exports an init function execute that immediately.
if ( init ) {
init();
transferFocusToRealInput();
}
} );
} );
router.on( 'route', ( ev ) => {
if ( searchDialogOpened ) {
const currentlySearchRoute = ev.path.match( searchRoute );
if ( currentlySearchRoute ) {
// Transfer focus to the real search input after the dialog has been loaded
transferFocusToRealInput();
} else {
// Return focus to the search button after exiting the search overlay
searchDialogOpened = false;
requestAnimationFrame( () => {
trigger.focus();
} );
}
}
} );
}
/**
* Create a temporary input to open the ios virtual keyboard and maintain keyboard context.
*
* @return {void}
* @ignore
*/
function createTempInput() {
if ( tempInput ) {
tempInput.remove();
}
tempInput = document.createElement( 'input' );
tempInput.type = 'text';
tempInput.style.position = 'absolute';
tempInput.style.left = '0';
tempInput.style.top = '0';
tempInput.style.fontSize = '16px'; // Prevent iOS zoom
tempInput.style.opacity = '0';
tempInput.style.pointerEvents = 'none';
tempInput.setAttribute( 'readonly', true );
document.body.appendChild( tempInput );
tempInput.focus();
}
/**
* Transfer focus from temp input to real search input after it has been loaded by Vue.
*
* @return {void}
* @ignore
*/
function transferFocusToRealInput() {
requestAnimationFrame( () => {
const realInput = document.querySelector( '.cdx-typeahead-search .cdx-text-input__input:not(.skin-minerva-search-trigger)' );
if ( realInput && tempInput ) {
realInput.focus();
tempInput.remove();
tempInput = null;
}
} );
}
/**
* Associates a given element with the display of a search
* dialog.
*
* @param {HTMLButtonElement} trigger that will launch the search dialog.
* @method enableSearchDialog
* @memberof module:mediawiki.page.ready
*/
module.exports = function ( trigger ) {
mw.loader.using( 'mediawiki.router' ).then( () => {
const router = require( 'mediawiki.router' );
addRoutes( router, trigger );
} );
// Open search dialog when search trigger is clicked
trigger.addEventListener( 'click', ( ev ) => {
ev.preventDefault();
/**
* On-screen keyboard on iOS only opens when `focus()` is called from a "user context event".
* http://stackoverflow.com/questions/6837543/show-virtual-keyboard-on-mobile-phones-in-javascript
*
* This route callback triggers the TypeaheadSearch overlay to be loaded in by Vue
* but because the search input isn't yet available, the on-screen keyboard on iOS
* can't be triggered immediately. To work around this, we create a temporary input
* to trigger the virtual keyboard and maintain the keyboard context. Then after
* TAHS is finished loading, we transfer focus to the real search input.
*/
searchDialogOpened = true;
createTempInput();
window.location.hash = '/search';
} );
};