diff options
Diffstat (limited to 'www/wiki/resources/src/mediawiki/mediawiki.searchSuggest.js')
-rw-r--r-- | www/wiki/resources/src/mediawiki/mediawiki.searchSuggest.js | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/www/wiki/resources/src/mediawiki/mediawiki.searchSuggest.js b/www/wiki/resources/src/mediawiki/mediawiki.searchSuggest.js new file mode 100644 index 00000000..e7859cf7 --- /dev/null +++ b/www/wiki/resources/src/mediawiki/mediawiki.searchSuggest.js @@ -0,0 +1,322 @@ +/*! + * Add search suggestions to the search form. + */ +( function ( mw, $ ) { + var searchNS = $.map( mw.config.get( 'wgFormattedNamespaces' ), function ( nsName, nsID ) { + if ( nsID >= 0 && mw.user.options.get( 'searchNs' + nsID ) ) { + // Cast string key to number + return Number( nsID ); + } + } ); + mw.searchSuggest = { + // queries the wiki and calls response with the result + request: function ( api, query, response, maxRows, namespace ) { + return api.get( { + formatversion: 2, + action: 'opensearch', + search: query, + namespace: namespace || searchNS, + limit: maxRows, + suggest: true + } ).done( function ( data, jqXHR ) { + response( data[ 1 ], { + type: jqXHR.getResponseHeader( 'X-OpenSearch-Type' ), + query: query + } ); + } ); + } + }; + + $( function () { + var api, searchboxesSelectors, + // Region where the suggestions box will appear directly below + // (using the same width). Can be a container element or the input + // itself, depending on what suits best in the environment. + // For Vector the suggestion box should align with the simpleSearch + // container's borders, in other skins it should align with the input + // element (not the search form, as that would leave the buttons + // vertically between the input and the suggestions). + $searchRegion = $( '#simpleSearch, #searchInput' ).first(), + $searchInput = $( '#searchInput' ), + previousSearchText = $searchInput.val(); + + // Compute form data for search suggestions functionality. + function getFormData( context ) { + var $form, baseHref, linkParams; + + if ( !context.formData ) { + // Compute common parameters for links' hrefs + $form = context.config.$region.closest( 'form' ); + + baseHref = $form.attr( 'action' ); + baseHref += baseHref.indexOf( '?' ) > -1 ? '&' : '?'; + + linkParams = $form.serializeObject(); + + context.formData = { + textParam: context.data.$textbox.attr( 'name' ), + linkParams: linkParams, + baseHref: baseHref + }; + } + + return context.formData; + } + + /** + * Callback that's run when the user changes the search input text + * 'this' is the search input box (jQuery object) + * + * @ignore + */ + function onBeforeUpdate() { + var searchText = this.val(); + + if ( searchText && searchText !== previousSearchText ) { + mw.track( 'mediawiki.searchSuggest', { + action: 'session-start' + } ); + } + previousSearchText = searchText; + } + + /** + * Defines the location of autocomplete. Typically either + * header, which is in the top right of vector (for example) + * and content which identifies the main search bar on + * Special:Search. Defaults to header for skins that don't set + * explicitly. + * + * @ignore + * @param {Object} context + * @return {string} + */ + function getInputLocation( context ) { + return context.config.$region + .closest( 'form' ) + .find( '[data-search-loc]' ) + .data( 'search-loc' ) || 'header'; + } + + /** + * Callback that's run when suggestions have been updated either from the cache or the API + * 'this' is the search input box (jQuery object) + * + * @ignore + * @param {Object} metadata + */ + function onAfterUpdate( metadata ) { + var context = this.data( 'suggestionsContext' ); + + mw.track( 'mediawiki.searchSuggest', { + action: 'impression-results', + numberOfResults: context.config.suggestions.length, + resultSetType: metadata.type || 'unknown', + query: metadata.query, + inputLocation: getInputLocation( context ) + } ); + } + + // The function used to render the suggestions. + function renderFunction( text, context ) { + var formData = getFormData( context ), + textboxConfig = context.data.$textbox.data( 'mw-searchsuggest' ) || {}; + + // linkParams object is modified and reused + formData.linkParams[ formData.textParam ] = text; + + // Allow trackers to attach tracking information, such + // as wprov, to clicked links. + mw.track( 'mediawiki.searchSuggest', { + action: 'render-one', + formData: formData, + index: context.config.suggestions.indexOf( text ) + } ); + + // this is the container <div>, jQueryfied + this.text( text ); + + // wrap only as link, if the config doesn't disallow it + if ( textboxConfig.wrapAsLink !== false ) { + this.wrap( + $( '<a>' ) + .attr( 'href', formData.baseHref + $.param( formData.linkParams ) ) + .attr( 'title', text ) + .addClass( 'mw-searchSuggest-link' ) + ); + } + } + + // The function used when the user makes a selection + function selectFunction( $input, source ) { + var context = $input.data( 'suggestionsContext' ), + text = $input.val(); + + // Selecting via keyboard triggers a form submission. That will fire + // the submit-form event in addition to this click-result event. + if ( source !== 'keyboard' ) { + mw.track( 'mediawiki.searchSuggest', { + action: 'click-result', + numberOfResults: context.config.suggestions.length, + index: context.config.suggestions.indexOf( text ) + } ); + } + + // allow the form to be submitted + return true; + } + + function specialRenderFunction( query, context ) { + var $el = this, + formData = getFormData( context ); + + // linkParams object is modified and reused + formData.linkParams[ formData.textParam ] = query; + + mw.track( 'mediawiki.searchSuggest', { + action: 'render-one', + formData: formData, + index: context.config.suggestions.indexOf( query ) + } ); + + if ( $el.children().length === 0 ) { + $el + .append( + $( '<div>' ) + .addClass( 'special-label' ) + .text( mw.msg( 'searchsuggest-containing' ) ), + $( '<div>' ) + .addClass( 'special-query' ) + .text( query ) + ) + .show(); + } else { + $el.find( '.special-query' ) + .text( query ); + } + + if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) { + $el.parent().attr( 'href', formData.baseHref + $.param( formData.linkParams ) + '&fulltext=1' ); + } else { + $el.wrap( + $( '<a>' ) + .attr( 'href', formData.baseHref + $.param( formData.linkParams ) + '&fulltext=1' ) + .addClass( 'mw-searchSuggest-link' ) + ); + } + } + + // Generic suggestions functionality for all search boxes + searchboxesSelectors = [ + // Primary searchbox on every page in standard skins + '#searchInput', + // Generic selector for skins with multiple searchboxes (used by CologneBlue) + // and for MediaWiki itself (special pages with page title inputs) + '.mw-searchInput' + ]; + $( searchboxesSelectors.join( ', ' ) ) + .suggestions( { + fetch: function ( query, response, maxRows ) { + var node = this[ 0 ]; + + api = api || new mw.Api(); + + $.data( node, 'request', mw.searchSuggest.request( api, query, response, maxRows ) ); + }, + cancel: function () { + var node = this[ 0 ], + request = $.data( node, 'request' ); + + if ( request ) { + request.abort(); + $.removeData( node, 'request' ); + } + }, + result: { + render: renderFunction, + select: function () { + // allow the form to be submitted + return true; + } + }, + update: { + before: onBeforeUpdate, + after: onAfterUpdate + }, + cache: true, + highlightInput: true + } ) + .on( 'paste cut drop', function () { + // make sure paste and cut events from the mouse and drag&drop events + // trigger the keypress handler and cause the suggestions to update + $( this ).trigger( 'keypress' ); + } ) + // In most skins (at least Monobook and Vector), the font-size is messed up in <body>. + // (they use 2 elements to get a sane font-height). So, instead of making exceptions for + // each skin or adding more stylesheets, just copy it from the active element so auto-fit. + .each( function () { + var $this = $( this ); + $this + .data( 'suggestions-context' ) + .data.$container.css( 'fontSize', $this.css( 'fontSize' ) ); + } ); + + // Ensure that the thing is actually present! + if ( $searchRegion.length === 0 ) { + // Don't try to set anything up if simpleSearch is disabled sitewide. + // The loader code loads us if the option is present, even if we're + // not actually enabled (anymore). + return; + } + + // Special suggestions functionality and tracking for skin-provided search box + $searchInput.suggestions( { + update: { + before: onBeforeUpdate, + after: onAfterUpdate + }, + result: { + render: renderFunction, + select: selectFunction + }, + special: { + render: specialRenderFunction, + select: function ( $input, source ) { + var context = $input.data( 'suggestionsContext' ), + text = $input.val(); + if ( source === 'mouse' ) { + // mouse click won't trigger form submission, so we need to send a click event + mw.track( 'mediawiki.searchSuggest', { + action: 'click-result', + numberOfResults: context.config.suggestions.length, + index: context.config.suggestions.indexOf( text ) + } ); + } else { + $input.closest( 'form' ) + .append( $( '<input type="hidden" name="fulltext" value="1"/>' ) ); + } + return true; // allow the form to be submitted + } + }, + $region: $searchRegion + } ); + + $searchInput.closest( 'form' ) + // track the form submit event + .on( 'submit', function () { + var context = $searchInput.data( 'suggestionsContext' ); + mw.track( 'mediawiki.searchSuggest', { + action: 'submit-form', + numberOfResults: context.config.suggestions.length, + $form: context.config.$region.closest( 'form' ), + inputLocation: getInputLocation( context ), + index: context.config.suggestions.indexOf( + context.data.$textbox.val() + ) + } ); + } ) + // If the form includes any fallback fulltext search buttons, remove them + .find( '.mw-fallbackSearchButton' ).remove(); + } ); + +}( mediaWiki, jQuery ) ); |