mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-18 21:15:50 +00:00
dce24c9784
By default the API uses location.host as the host, however during development it is useful to test against production wikis For example to test against English Wikipedia: $wgVectorSearchHost = 'en.wikipedia.org'; Note: Links when clicked will not take the user to the target page, and instead will take the user to the search results page with a link to create the page. The following config can be used to workaround that page: $wgDisableTextSearch = true; $wgSearchForwardUrl = "/w/index.php?title=$1"; Change-Id: I5fbac7f54844d7a9d6976007bc0d0ff9938b9f2b
179 lines
5.5 KiB
JavaScript
179 lines
5.5 KiB
JavaScript
/**
|
|
* Disabling this rule as it's only necessary for
|
|
* combining multiple class names and documenting the output.
|
|
* That doesn't happen in this file but the linter still throws an error.
|
|
* https://github.com/wikimedia/eslint-plugin-mediawiki/blob/master/docs/rules/class-doc.md
|
|
*/
|
|
/* eslint-disable mediawiki/class-doc */
|
|
|
|
/** @interface VectorResourceLoaderVirtualConfig */
|
|
/** @interface MediaWikiPageReadyModule */
|
|
|
|
var /** @type {VectorResourceLoaderVirtualConfig} */
|
|
config = require( /** @type {string} */ ( './config.json' ) ),
|
|
// T251544: Collect search performance metrics to compare Vue search with
|
|
// mediawiki.searchSuggest performance.
|
|
SHOULD_TEST_SEARCH = !!(
|
|
!config.wgVectorUseCoreSearch &&
|
|
window.performance &&
|
|
performance.mark &&
|
|
performance.measure &&
|
|
performance.getEntriesByName ),
|
|
LOAD_START_MARK = 'mwVectorVueSearchLoadStart',
|
|
LOAD_END_MARK = 'mwVectorVueSearchLoadEnd',
|
|
LOAD_MEASURE = 'mwVectorVueSearchLoadStartToLoadEnd',
|
|
SEARCH_FORM_ID = 'simpleSearch',
|
|
SEARCH_INPUT_ID = 'searchInput',
|
|
SEARCH_LOADING_CLASS = 'search-form__loader';
|
|
|
|
/**
|
|
* 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 ) {
|
|
|
|
function requestSearchModule() {
|
|
if ( SHOULD_TEST_SEARCH ) {
|
|
performance.mark( LOAD_START_MARK );
|
|
}
|
|
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 ) {
|
|
|
|
var 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 ( !form.dataset.loadingMsg ) {
|
|
form.dataset.loadingMsg = mw.msg( 'vector-search-loader' );
|
|
}
|
|
|
|
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" } */
|
|
var addOrRemoveListener = ( attach ? 'addEventListener' : 'removeEventListener' );
|
|
|
|
[ 'input', 'focusin', 'focusout' ].forEach( function ( eventType ) {
|
|
element[ addOrRemoveListener ]( eventType, eventCallback );
|
|
} );
|
|
|
|
if ( !attach ) {
|
|
element.classList.remove( SEARCH_LOADING_CLASS );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Marks when the lazy load has completed.
|
|
*/
|
|
function markLoadEnd() {
|
|
if ( SHOULD_TEST_SEARCH && performance.getEntriesByName( LOAD_START_MARK ).length ) {
|
|
performance.mark( LOAD_END_MARK );
|
|
performance.measure( LOAD_MEASURE, LOAD_START_MARK, LOAD_END_MARK );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the loading of the search module as well as the loading indicator.
|
|
* Only initialize the loading indicator when not using the core search module.
|
|
*
|
|
* @param {Document} document
|
|
*/
|
|
function initSearchLoader( document ) {
|
|
var searchForm = document.getElementById( SEARCH_FORM_ID ),
|
|
searchInput = document.getElementById( SEARCH_INPUT_ID );
|
|
|
|
// Allow developers to defined $wgVectorSearchHost in LocalSettings to target different APIs
|
|
if ( config.wgVectorSearchHost ) {
|
|
mw.config.set( 'wgVectorSearchHost', config.wgVectorSearchHost );
|
|
}
|
|
|
|
if ( !searchForm || !searchInput ) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* 1. If $wgVectorUseCoreSearch is true,
|
|
* or we are in a browser that doesn't support fetch
|
|
* load the legacy searchSuggest module. The check for window.fetch
|
|
* can be removed when IE11 support is finally officially dropped.
|
|
* 2. If we're using a different search module, enable the loading indicator
|
|
* before the search module loads.
|
|
**/
|
|
if ( config.wgVectorUseCoreSearch || !window.fetch ) {
|
|
loadSearchModule( searchInput, 'mediawiki.searchSuggest', function () {} );
|
|
} else {
|
|
// Remove tooltips while Vue search is still loading
|
|
searchInput.setAttribute( 'autocomplete', 'off' );
|
|
searchInput.removeAttribute( 'title' );
|
|
setLoadingIndicatorListeners( searchForm, true, renderSearchLoadingIndicator );
|
|
loadSearchModule(
|
|
searchInput,
|
|
'skins.vector.search',
|
|
function () {
|
|
markLoadEnd();
|
|
|
|
setLoadingIndicatorListeners(
|
|
/** @type {HTMLElement} */ ( searchForm ),
|
|
false,
|
|
renderSearchLoadingIndicator
|
|
);
|
|
}
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
initSearchLoader: initSearchLoader
|
|
};
|