Merge "filebackend: Clean up TempFSFile and fix IDEA errors"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.searchSuggest.js
1 /*!
2 * Add search suggestions to the search form.
3 */
4 ( function ( mw, $ ) {
5 mw.searchSuggest = {
6 // queries the wiki and calls response with the result
7 request: function ( api, query, response, maxRows ) {
8 return api.get( {
9 formatversion: 2,
10 action: 'opensearch',
11 search: query,
12 namespace: 0,
13 limit: maxRows,
14 suggest: true
15 } ).done( function ( data ) {
16 response( data[ 1 ] );
17 } );
18 },
19 // The name of the request api for event logging purposes
20 type: 'prefix'
21 };
22
23 $( function () {
24 var api, map, searchboxesSelectors,
25 // Region where the suggestions box will appear directly below
26 // (using the same width). Can be a container element or the input
27 // itself, depending on what suits best in the environment.
28 // For Vector the suggestion box should align with the simpleSearch
29 // container's borders, in other skins it should align with the input
30 // element (not the search form, as that would leave the buttons
31 // vertically between the input and the suggestions).
32 $searchRegion = $( '#simpleSearch, #searchInput' ).first(),
33 $searchInput = $( '#searchInput' ),
34 previousSearchText = $searchInput.val();
35
36 // Compatibility map
37 map = {
38 // SimpleSearch is broken in Opera < 9.6
39 opera: [ [ '>=', 9.6 ] ],
40 // Older Konquerors are unable to position the suggestions correctly (bug 50805)
41 konqueror: [ [ '>=', '4.11' ] ],
42 docomo: false,
43 blackberry: false,
44 // Support for iOS 6 or higher. It has not been tested on iOS 5 or lower
45 ipod: [ [ '>=', 6 ] ],
46 iphone: [ [ '>=', 6 ] ]
47 };
48
49 if ( !$.client.test( map ) ) {
50 return;
51 }
52
53 // Compute form data for search suggestions functionality.
54 function getFormData( context ) {
55 var $form, baseHref, linkParams;
56
57 if ( !context.formData ) {
58 // Compute common parameters for links' hrefs
59 $form = context.config.$region.closest( 'form' );
60
61 baseHref = $form.attr( 'action' );
62 baseHref += baseHref.indexOf( '?' ) > -1 ? '&' : '?';
63
64 linkParams = $form.serializeObject();
65
66 context.formData = {
67 textParam: context.data.$textbox.attr( 'name' ),
68 linkParams: linkParams,
69 baseHref: baseHref
70 };
71 }
72
73 return context.formData;
74 }
75
76 /**
77 * Callback that's run when the user changes the search input text
78 * 'this' is the search input box (jQuery object)
79 *
80 * @ignore
81 */
82 function onBeforeUpdate() {
83 var searchText = this.val();
84
85 if ( searchText && searchText !== previousSearchText ) {
86 mw.track( 'mediawiki.searchSuggest', {
87 action: 'session-start'
88 } );
89 }
90 previousSearchText = searchText;
91 }
92
93 /**
94 * Callback that's run when suggestions have been updated either from the cache or the API
95 * 'this' is the search input box (jQuery object)
96 *
97 * @ignore
98 */
99 function onAfterUpdate() {
100 var context = this.data( 'suggestionsContext' );
101
102 mw.track( 'mediawiki.searchSuggest', {
103 action: 'impression-results',
104 numberOfResults: context.config.suggestions.length,
105 resultSetType: mw.searchSuggest.type
106 } );
107 }
108
109 // The function used to render the suggestions.
110 function renderFunction( text, context ) {
111 var formData = getFormData( context ),
112 textboxConfig = context.data.$textbox.data( 'mw-searchsuggest' ) || {};
113
114 // linkParams object is modified and reused
115 formData.linkParams[ formData.textParam ] = text;
116
117 // this is the container <div>, jQueryfied
118 this.text( text );
119
120 // wrap only as link, if the config doesn't disallow it
121 if ( textboxConfig.wrapAsLink !== false ) {
122 this.wrap(
123 $( '<a>' )
124 .attr( 'href', formData.baseHref + $.param( formData.linkParams ) )
125 .attr( 'title', text )
126 .addClass( 'mw-searchSuggest-link' )
127 );
128 }
129 }
130
131 // The function used when the user makes a selection
132 function selectFunction( $input ) {
133 var context = $input.data( 'suggestionsContext' ),
134 text = $input.val();
135
136 mw.track( 'mediawiki.searchSuggest', {
137 action: 'click-result',
138 numberOfResults: context.config.suggestions.length,
139 clickIndex: context.config.suggestions.indexOf( text ) + 1
140 } );
141
142 // allow the form to be submitted
143 return true;
144 }
145
146 function specialRenderFunction( query, context ) {
147 var $el = this,
148 formData = getFormData( context );
149
150 // linkParams object is modified and reused
151 formData.linkParams[ formData.textParam ] = query;
152
153 if ( $el.children().length === 0 ) {
154 $el
155 .append(
156 $( '<div>' )
157 .addClass( 'special-label' )
158 .text( mw.msg( 'searchsuggest-containing' ) ),
159 $( '<div>' )
160 .addClass( 'special-query' )
161 .text( query )
162 )
163 .show();
164 } else {
165 $el.find( '.special-query' )
166 .text( query );
167 }
168
169 if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) {
170 $el.parent().attr( 'href', formData.baseHref + $.param( formData.linkParams ) + '&fulltext=1' );
171 } else {
172 $el.wrap(
173 $( '<a>' )
174 .attr( 'href', formData.baseHref + $.param( formData.linkParams ) + '&fulltext=1' )
175 .addClass( 'mw-searchSuggest-link' )
176 );
177 }
178 }
179
180 // Generic suggestions functionality for all search boxes
181 searchboxesSelectors = [
182 // Primary searchbox on every page in standard skins
183 '#searchInput',
184 // Special:Search
185 '#powerSearchText',
186 '#searchText',
187 // Generic selector for skins with multiple searchboxes (used by CologneBlue)
188 // and for MediaWiki itself (special pages with page title inputs)
189 '.mw-searchInput'
190 ];
191 $( searchboxesSelectors.join( ', ' ) )
192 .suggestions( {
193 fetch: function ( query, response, maxRows ) {
194 var node = this[ 0 ];
195
196 api = api || new mw.Api();
197
198 $.data( node, 'request', mw.searchSuggest.request( api, query, response, maxRows ) );
199 },
200 cancel: function () {
201 var node = this[ 0 ],
202 request = $.data( node, 'request' );
203
204 if ( request ) {
205 request.abort();
206 $.removeData( node, 'request' );
207 }
208 },
209 result: {
210 render: renderFunction,
211 select: function () {
212 // allow the form to be submitted
213 return true;
214 }
215 },
216 cache: true,
217 highlightInput: true
218 } )
219 .bind( 'paste cut drop', function () {
220 // make sure paste and cut events from the mouse and drag&drop events
221 // trigger the keypress handler and cause the suggestions to update
222 $( this ).trigger( 'keypress' );
223 } )
224 // In most skins (at least Monobook and Vector), the font-size is messed up in <body>.
225 // (they use 2 elements to get a sane font-height). So, instead of making exceptions for
226 // each skin or adding more stylesheets, just copy it from the active element so auto-fit.
227 .each( function () {
228 var $this = $( this );
229 $this
230 .data( 'suggestions-context' )
231 .data.$container
232 .css( 'fontSize', $this.css( 'fontSize' ) );
233 } );
234
235 // Ensure that the thing is actually present!
236 if ( $searchRegion.length === 0 ) {
237 // Don't try to set anything up if simpleSearch is disabled sitewide.
238 // The loader code loads us if the option is present, even if we're
239 // not actually enabled (anymore).
240 return;
241 }
242
243 // Special suggestions functionality and tracking for skin-provided search box
244 $searchInput.suggestions( {
245 update: {
246 before: onBeforeUpdate,
247 after: onAfterUpdate
248 },
249 result: {
250 render: renderFunction,
251 select: selectFunction
252 },
253 special: {
254 render: specialRenderFunction,
255 select: function ( $input ) {
256 $input.closest( 'form' )
257 .append( $( '<input type="hidden" name="fulltext" value="1"/>' ) );
258 return true; // allow the form to be submitted
259 }
260 },
261 $region: $searchRegion
262 } );
263
264 $searchInput.closest( 'form' )
265 // track the form submit event
266 .on( 'submit', function () {
267 var context = $searchInput.data( 'suggestionsContext' );
268 mw.track( 'mediawiki.searchSuggest', {
269 action: 'submit-form',
270 numberOfResults: context.config.suggestions.length
271 } );
272 } )
273 // If the form includes any fallback fulltext search buttons, remove them
274 .find( '.mw-fallbackSearchButton' ).remove();
275 } );
276
277 }( mediaWiki, jQuery ) );