/* eslint-disable */ /** * Based on https://gerrit.wikimedia.org/g/wikimedia/portals/+/refs/heads/master * See T219590 for more details */ /** * Below are additional dependency extracted from polyfills.js * TODO: Optimize and clear unneeded code */ /** * Detects reported or approximate device pixel ratio. * * 1.0 means 1 CSS pixel is 1 hardware pixel * * 2.0 means 1 CSS pixel is 2 hardware pixels * * etc. * * Uses window.devicePixelRatio if available, or CSS media queries on IE. * * @return {number} Device pixel ratio */ function getDevicePixelRatio() { if ( window.devicePixelRatio !== undefined ) { // Most web browsers: // * WebKit (Safari, Chrome, Android browser, etc) // * Opera // * Firefox 18+ return window.devicePixelRatio; } else if ( window.msMatchMedia !== undefined ) { // Windows 8 desktops / tablets, probably Windows Phone 8 // // IE 10 doesn't report pixel ratio directly, but we can get the // screen DPI and divide by 96. We'll bracket to [1, 1.5, 2.0] for // simplicity, but you may get different values depending on zoom // factor, size of screen and orientation in Metro IE. if ( window.msMatchMedia( '(min-resolution: 192dpi)' ).matches ) { return 2; } else if ( window.msMatchMedia( '(min-resolution: 144dpi)' ).matches ) { return 1.5; } else { return 1; } } else { // Legacy browsers... // Assume 1 if unknown. return 1; } } function addEvent( obj, evt, fn ) { if ( !obj ) { return; } if ( obj.addEventListener ) { obj.addEventListener( evt, fn, false ); } else if ( obj.attachEvent ) { obj.attachedEvents.push( [ obj, evt, fn ] ); obj.attachEvent( 'on' + evt, fn ); } } /** * WMTypeAhead. * Displays search suggestions with thumbnail and description * as user types into an input field. * * @constructor * @param {string} appendTo - ID of a container element that the suggestions will be appended to. * @param {string} searchInput - ID of a search input whose value will be used to generate * search suggestions. * * @return {Object} Returns an object with the following properties: * @return {HTMLElement} return.typeAheadEl The type-ahead DOM object. * @return {Function} return.query A function that loads the type-ahead suggestions. * * @example * var typeAhead = new WMTypeAhead('containerID', 'inputID'); * typeAhead.query('search string', 'en'); * */ window.WMTypeAhead = function ( appendTo, searchInput ) { let typeAheadID = 'typeahead-suggestions', typeAheadEl = document.getElementById( typeAheadID ), // Type-ahead DOM element. appendEl = document.getElementById( appendTo ), searchEl = document.getElementById( searchInput ), server = mw.config.get( 'wgServer' ), articleurl = server + mw.config.get( 'wgArticlePath' ).replace( '$1', '' ), searchurl = server + mw.config.get( 'wgScriptPath' ) + '/index.php?title=Special%3ASearch&search=', thumbnailSize = Math.round( getDevicePixelRatio() * 80 ), descriptionSource = mw.config.get( 'wgCitizenSearchDescriptionSource' ), maxSearchResults = mw.config.get( 'wgCitizenMaxSearchResults' ), searchString, typeAheadItems, activeItem, ssActiveIndex, api = new mw.Api(); // Only create typeAheadEl once on page. if ( !typeAheadEl ) { typeAheadEl = document.createElement( 'div' ); typeAheadEl.id = typeAheadID; appendEl.appendChild( typeAheadEl ); } /** * Keeps track of the search query callbacks. Consists of an array of * callback functions and an index that keeps track of the order of requests. * Callbacks are deleted by replacing the callback function with a no-op. */ window.callbackStack = { queue: {}, index: -1, incrementIndex: function () { this.index += 1; return this.index; }, addCallback: function ( func ) { const index = this.incrementIndex(); this.queue[ index ] = func( index ); return index; }, deleteSelfFromQueue: function ( i ) { delete this.queue[ i ]; }, deletePrevCallbacks: function ( j ) { let callback; this.deleteSelfFromQueue( j ); for ( callback in this.queue ) { if ( callback < j ) { this.queue[ callback ] = this.deleteSelfFromQueue.bind( window.callbackStack, callback ); } } } }; /** * Maintains the 'active' state on search suggestions. * Makes sure the 'active' element is synchronized between mouse and keyboard usage, * and cleared when new search suggestions appear. */ ssActiveIndex = { index: -1, max: maxSearchResults, setMax: function ( x ) { this.max = x; }, increment: function ( i ) { this.index += i; if ( this.index < 0 ) { this.setIndex( this.max - 1 ); } // Index reaches top if ( this.index === this.max ) { this.setIndex( 0 ); } // Index reaches bottom return this.index; }, setIndex: function ( i ) { if ( i <= this.max - 1 ) { this.index = i; } return this.index; }, clear: function () { this.setIndex( -1 ); } }; /** * Removed the actual child nodes from typeAheadEl * @see {typeAheadEl} */ function clearTypeAheadElements() { if ( typeof typeAheadEl === 'undefined' ) { return; } while ( typeAheadEl.firstChild !== null ) { typeAheadEl.removeChild( typeAheadEl.firstChild ); } } /** * Removes the type-ahead suggestions from the DOM. * Reason for timeout: The typeahead is set to clear on input blur. * When a user clicks on a search suggestion, they triggers the input blur * and remove the typeahead before a click event is registered. * The timeout makes it so a click on the search suggestion is registered before * an input blur. * 300ms is used to account for the click delay on mobile devices. * */ function clearTypeAhead() { setTimeout( function () { clearTypeAheadElements(); ssActiveIndex.clear(); }, 300 ); } /** * Manually redirects the page to the href of a given element. * * For Chrome on Android to solve T221628. * When search suggestions below the fold are clicked, the blur event * on the search input is triggered and the page scrolls the search input * into view. However, the originating click event does not redirect * the page. * * @param {Event} e */ function forceLinkFollow( e ) { const el = e.relatedTarget; if ( el && /suggestion-link/.test( el.className ) ) { window.location = el.href; } } /** * Card displayed if no results could be found * @param {string} searchString - The search string. * @return {string} */ function getNoResultsIndicator( searchString ) { const titlemsg = mw.message( 'citizen-search-no-results-title', searchString ).text(), descmsg = mw.message( 'citizen-search-no-results-desc', searchString ).text(); return '
' + descmsg + '
' + '