Merge "Add licensing for extensions to Special:Version"
[lhc/web/wiklou.git] / resources / mediawiki / mediawiki.searchSuggest.js
1 /**
2 * Add search suggestions to the search form.
3 */
4 ( function ( mw, $ ) {
5 $( function () {
6 var map, resultRenderCache, searchboxesSelectors,
7 // Region where the suggestions box will appear directly below
8 // (using the same width). Can be a container element or the input
9 // itself, depending on what suits best in the environment.
10 // For Vector the suggestion box should align with the simpleSearch
11 // container's borders, in other skins it should align with the input
12 // element (not the search form, as that would leave the buttons
13 // vertically between the input and the suggestions).
14 $searchRegion = $( '#simpleSearch, #searchInput' ).first(),
15 $searchInput = $( '#searchInput' );
16
17 // Compatibility map
18 map = {
19 // SimpleSearch is broken in Opera < 9.6
20 opera: [['>=', 9.6]],
21 docomo: false,
22 blackberry: false,
23 ipod: false,
24 iphone: false
25 };
26
27 if ( !$.client.test( map ) ) {
28 return;
29 }
30
31 // Compute form data for search suggestions functionality.
32 function computeResultRenderCache( context ) {
33 var $form, formAction, baseHref, linkParams;
34
35 // Compute common parameters for links' hrefs
36 $form = context.config.$region.closest( 'form' );
37
38 formAction = $form.attr( 'action' );
39 baseHref = formAction + ( formAction.match(/\?/) ? '&' : '?' );
40
41 linkParams = {};
42 $.each( $form.serializeArray(), function ( idx, obj ) {
43 linkParams[ obj.name ] = obj.value;
44 } );
45
46 return {
47 textParam: context.data.$textbox.attr( 'name' ),
48 linkParams: linkParams,
49 baseHref: baseHref
50 };
51 }
52
53 // The function used to render the suggestions.
54 function renderFunction( text, context ) {
55 if ( !resultRenderCache ) {
56 resultRenderCache = computeResultRenderCache( context );
57 }
58
59 // linkParams object is modified and reused
60 resultRenderCache.linkParams[ resultRenderCache.textParam ] = text;
61
62 // this is the container <div>, jQueryfied
63 this
64 .append(
65 // the <span> is needed for $.autoEllipsis to work
66 $( '<span>' )
67 .css( 'whiteSpace', 'nowrap' )
68 .text( text )
69 )
70 .wrap(
71 $( '<a>' )
72 .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) )
73 .addClass( 'mw-searchSuggest-link' )
74 );
75 }
76
77 function specialRenderFunction( query, context ) {
78 var $el = this;
79
80 if ( !resultRenderCache ) {
81 resultRenderCache = computeResultRenderCache( context );
82 }
83
84 // linkParams object is modified and reused
85 resultRenderCache.linkParams[ resultRenderCache.textParam ] = query;
86
87 if ( $el.children().length === 0 ) {
88 $el
89 .append(
90 $( '<div>' )
91 .addClass( 'special-label' )
92 .text( mw.msg( 'searchsuggest-containing' ) ),
93 $( '<div>' )
94 .addClass( 'special-query' )
95 .text( query )
96 .autoEllipsis()
97 )
98 .show();
99 } else {
100 $el.find( '.special-query' )
101 .text( query )
102 .autoEllipsis();
103 }
104
105 if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) {
106 $el.parent().attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' );
107 } else {
108 $el.wrap(
109 $( '<a>' )
110 .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' )
111 .addClass( 'mw-searchSuggest-link' )
112 );
113 }
114 }
115
116 // General suggestions functionality for all search boxes
117 searchboxesSelectors = [
118 // Primary searchbox on every page in standard skins
119 '#searchInput',
120 // Special:Search
121 '#powerSearchText',
122 '#searchText',
123 // Generic selector for skins with multiple searchboxes (used by CologneBlue)
124 '.mw-searchInput'
125 ];
126 $( searchboxesSelectors.join(', ') )
127 .suggestions( {
128 fetch: function ( query ) {
129 var $el;
130
131 if ( query.length !== 0 ) {
132 $el = $( this );
133 $el.data( 'request', ( new mw.Api() ).get( {
134 action: 'opensearch',
135 search: query,
136 namespace: 0,
137 suggest: ''
138 } ).done( function ( data ) {
139 $el.suggestions( 'suggestions', data[1] );
140 } ) );
141 }
142 },
143 cancel: function () {
144 var apiPromise = $( this ).data( 'request' );
145 // If the delay setting has caused the fetch to have not even happened
146 // yet, the apiPromise object will have never been set.
147 if ( apiPromise && $.isFunction( apiPromise.abort ) ) {
148 apiPromise.abort();
149 $( this ).removeData( 'request' );
150 }
151 },
152 result: {
153 render: renderFunction,
154 select: function ( $input ) {
155 $input.closest( 'form' ).submit();
156 }
157 },
158 delay: 120,
159 highlightInput: true
160 } )
161 .bind( 'paste cut drop', function () {
162 // make sure paste and cut events from the mouse and drag&drop events
163 // trigger the keypress handler and cause the suggestions to update
164 $( this ).trigger( 'keypress' );
165 } );
166
167 // Ensure that the thing is actually present!
168 if ( $searchRegion.length === 0 ) {
169 // Don't try to set anything up if simpleSearch is disabled sitewide.
170 // The loader code loads us if the option is present, even if we're
171 // not actually enabled (anymore).
172 return;
173 }
174
175 // Special suggestions functionality for skin-provided search box
176 $searchInput.suggestions( {
177 result: {
178 render: renderFunction,
179 select: function ( $input ) {
180 $input.closest( 'form' ).submit();
181 }
182 },
183 special: {
184 render: specialRenderFunction,
185 select: function ( $input ) {
186 $input.closest( 'form' ).append(
187 $( '<input type="hidden" name="fulltext" value="1"/>' )
188 );
189 $input.closest( 'form' ).submit();
190 }
191 },
192 $region: $searchRegion
193 } );
194
195 // In most skins (at least Monobook and Vector), the font-size is messed up in <body>.
196 // (they use 2 elements to get a sane font-height). So, instead of making exceptions for
197 // each skin or adding more stylesheets, just copy it from the active element so auto-fit.
198 $searchInput
199 .data( 'suggestions-context' )
200 .data.$container
201 .css( 'fontSize', $searchInput.css( 'fontSize' ) );
202
203 } );
204
205 }( mediaWiki, jQuery ) );