MediaWiki:Gadget-advanced-search.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/**
* Improve advanced search by adding
* options suitable for Commons
*
* Maintainer: [[User:Bawolff]]
* Created in 2014
* Version 1.1 [Jan 2018]
*/
/* eslint-disable vars-on-top, one-var, no-use-before-define, no-jquery/no-global-selector */
mw.loader.using( [ 'mw.Api', 'jquery.suggestions' ], function () {
	'use strict';
	if ( mw.config.get( 'wgCanonicalSpecialPageName' ) !== 'Search' ) {
		return;
	}

	var i18n = {
			en: {
				'header-cat': 'Search by category:',
				'header-file': 'File options:',
				'cat-any': 'In any of these categories (comma separated):',
				'cat-all': 'In all of these categories:',
				'cat-none': 'In none of these categories:',
				'cat-depth': 'Include subcategories up to a depth of:',
				'file-type': 'Only include the following types of files:',
				'file-license': 'Copyright status:',
				'file-type-all': 'All',
				'file-type-multimedia': 'Multimedia',
				'file-type-jpeg': 'Photographs (JPEG)',
				'file-type-vector': 'Vector images (SVG)',
				'file-type-drawing': 'Drawing',
				'file-pd': 'Do absolutely anything with it (Public domain)',
				'file-cc': 'Freely re-usable under a Creative Commons license',
				'file-any': 'Show all (All files on commons can be re-used under certain conditions)'
			},
			ru: {
				'header-cat': 'Поиск по категориям:',
				'header-file': 'Типы файлов:',
				'cat-any': 'В любой из этих категорий:',
				'cat-all': 'Во всех этих категориях:',
				'cat-none': 'Ни в одной из этих категорий:',
				'cat-depth': 'Включать подкатегории до глубины:',
				'file-type': 'Включать только следующие типы файлов:',
				'file-license': 'Правовой статус:',
				'file-type-all': 'Все',
				'file-type-multimedia': 'Медиафайлы',
				'file-type-jpeg': 'Фотографии (JPEG)',
				'file-type-vector': 'Векторные изображения (SVG)',
				'file-type-drawing': 'Рисунки и чертежи',
				'file-pd': 'Абсолютно никаких ограничений (общественное достояние)',
				'file-cc': 'Допускается свободное повторное использование по лицензии Creative Commons',
				'file-any': 'Показать все (все файлы на Викискладе могут быть повторно использованы при определённых условиях)'
			}
		},
		lookup = function ( key ) {
			var lang = mw.config.get( 'wgUserLanguage' );
			if ( i18n[ lang ] && i18n[ lang ][ key ] ) {
				return i18n[ lang ][ key ];
			}
			return i18n.en[ key ];
		},

		expand = function ( str ) {
			return str.replace( /\$([a-z-]+)/g, function ( match, p1 ) {
				return lookup( p1 );
			} );
		},

		renderSearchSuggest = function ( suggestion ) {
			var split = splitField( suggestion ),
				lastBit;
			if ( split.length >= 2 ) {
				lastBit = split[ split.length - 2 ];
			} else {
				mw.log( 'No commas in search suggestion?' );
				lastBit = suggestion;
			}
			$( this ).text( lastBit );
		},
		fetchSearchQuery = function ( input, callback ) {
			// http://commons.wikimedia.org/w/api.php?action=query&generator=allpages&gapnamespace=14&gaplimit=7&gapprefix=Cat&prop=categoryinfo|links|templates&tllimit=max&pllimit=max&tltemplates=Template:Category%20redirect&plnamespace=14
			var lastInput = splitField( input );
			lastInput = lastInput ? lastInput[ lastInput.length - 1 ] : '';
			if ( lastInput.match( /^\s*$/ ) ) {
				callback( [] );
				return;
			}
			var api = new mw.Api();
			api.get( {
				action: 'query',
				generator: 'allpages',
				gapnamespace: 14,
				gaplimit: 5,
				gapprefix: lastInput,
				prop: 'categoryinfo|links|templates',
				tllimit: 'max',
				pllimit: 'max',
				tltemplates: 'Template:Category_redirect',
				plnamespace: 14,
				format: 'json'
			} ).done( function ( data1 ) {
				// This would be much more efficient if we did at same time.
				api.get( {
					action: 'query',
					generator: 'search',
					gsrnamespace: 14,
					gsrlimit: 5,
					gsrsearch: lastInput,
					prop: 'categoryinfo|links|templates',
					tllimit: 'max',
					pllimit: 'max',
					tltemplates: 'Template:Category_redirect',
					plnamespace: 14,
					format: 'json'
				} ).done( function ( data2 ) {
					var suggestions = [],
						formatSuggestion = function ( item ) {
							// Kill the namespace, add the comma separator as a hint to user.
							// Needs to be full search text. renderSearchSuggest will cause only part to be displayed.
							if ( input.match( /,\s/ ) ) {
								return input.replace( /,\s[^,]*$/, '' ) + ', ' + item.replace( /^[^:]*:/, '' ) + ', ';
							} else {
								return item.replace( /^[^:]*:/, '' ) + ', ';
							}
						};
					if ( !( data1 && data1.query && data1.query.pages && data2 && data2.query && data2.query.pages ) ) {
						mw.log( 'error fetching data' );
						return;
					}
					var combinedResults = $.extend( {}, data1.query.pages, data2.query.pages );
					for ( var i in combinedResults ) {
						// XXX does not do de-duplication in regards to cat redirects.
						if ( combinedResults[ i ].templates !== undefined ) {
							for ( var j in combinedResults[ i ].links ) {
								suggestions[ suggestions.length ] = formatSuggestion( combinedResults[ i ].links[ j ].title );
								if ( j > 5 ) {
									break;
								}
							}
						} else if ( combinedResults[ i ].categoryinfo.size > 5 ) {
							// Realistically, most people won't want super small categories.
							suggestions[ suggestions.length ] = formatSuggestion( combinedResults[ i ].title );
						}
					}
					callback( suggestions );
				} );
			} );
		},

		add = function () {
			if ( !$( '#mw-searchoptions' ).length ) {
				return;
			}
			// This doesn't convert numbers...
			var html = '<div class="sr-category"><h4>$header-cat </h4><div class="divider"/><table border="0"><tbody><tr><td><label for="search-cat-any">$cat-any </label></td><td><input id="search-cat-any" name="search-cat-any" size="50"></input></td></tr><tr><td><label for="search-cat-all">$cat-all </label></td><td><input id="search-cat-all" name="search-cat-all" size="50"></input></td></tr><tr><td><label for="search-cat-none">$cat-none </label></td><td><input id="search-cat-none" name="search-cat-none" size="50"></input></td></tr><!--<tr><td><label for="search-cat-depth">$cat-depth </label></td><td><select id="search-cat-depth" name="search-cat-depth"><option value="1">1</option><option value="2" selected>2</option><option value="3">3</option></select></td></tr>--></tbody></table></div><div class="sr-file" style="clear:both"><h4>$header-file </h4><div class="divider"><table border="0"><tr><td>$file-license </td><td><select id="search-file-copyright" name="search-file-copyright"><option value="any">$file-any </option><option value="pd">$file-pd</option><option value="by">$file-cc</option></select></td></tr><tr><td>$file-type</td><td><select id="search-file-type" name="search-file-type"><option value="">$file-type-all</option><!--<option value="ogv oga ogg webm mid midi flac wav gif">$file-type-multimedia</option>--><option value="webm">WebM</option><option value="pdf">PDF</option><option value="png">PNG</option><option value="gif">GIF</option><option value="jpg">$file-type-jpeg</option><option value="svg">$file-type-vector</option></select></td></tr></table><input type="hidden" name="search-orig-query" value="" id="search-orig-query"/></div><br style="clear:both">';

			$( '#mw-searchoptions' ).prepend( $( expand( html ) ) );

			restoreDefaults( [
				'search-cat-any', 'search-cat-all', 'search-cat-none', 'search-cat-depth', 'search-file-copyright', 'search-file-type'
			] );
			if ( mw.util.getParamValue( 'search-orig-query' ) !== null ) {
				OO.ui.infuse( $( '#searchText' ) ).setValue( mw.util.getParamValue( 'search-orig-query' ) );
			}

			$( '#powersearch' ).on( 'submit', fixForm );

			$( '#search-cat-all, #search-cat-any, #search-cat-none' ).suggestions( {
				fetch: fetchSearchQuery,
				cache: true,
				result: {
					render: renderSearchSuggest
				}
			} );

		},

		fixForm = function () {
			var altCat, cats,
				searchBox = OO.ui.infuse( $( '#searchText' ) ),
				catsAll = $( '#search-cat-all' )[ 0 ],
				catsAny = $( '#search-cat-any' )[ 0 ],
				catNone = $( '#search-cat-none' )[ 0 ],
				addedText = ' ',
				trimField = function ( input ) {
					if ( input === null || input === undefined ) {
						input = '';
					}
					input = String( input ); // FIXME why was I casting to string in first place?
					input = input.replace( /^\s*/, '' ).replace( /\s*$/, '' );
					return input;
				};

			if ( catsAny ) {
				altCat = splitField( catsAny.value );
				cats = altCat.join( '|' ).replace( /\|$/, '' );
			}
			
			if ( cats ) {
				addedText += ' incategory:"' + cats + '"';
			}
			var anyCat = catsAll ? splitField( catsAll.value ) : [];
			anyCat.forEach( function () {
				var field = trimField( this );
				if ( field === '' ) {
					return;
				}
				addedText += ' incategory:"' + field + '"';
			} );

			var noCat = catNone ? splitField( catNone.value ) : [];
			noCat.forEach( function () {
				var field = trimField( this );
				if ( field === '' ) {
					return;
				}
				addedText += ' -incategory:"' + field + '"';
			} );

			var fileTypes = ( String( $( '#search-file-type' ).val() ) ).split( ' ' );
			fileTypes.forEach( function () {
				var field = trimField( this );
				if ( field === '' ) {
					return;
				}
				addedText += ' intitle:' + field;
			} );

			var copyright = $( '#search-file-copyright' ).val();
			if ( copyright === 'pd' ) {
				addedText += ' hastemplate:PD-Layout';
			}
			if ( copyright === 'cc' ) {
				addedText += ' hastemplate:CC-Layout';
			}

			$( '#search-orig-query' ).val( searchBox.getValue() );
			searchBox.setValue( searchBox.getValue() + addedText );
		},

		splitField = function ( text ) {
			var res = ( String( text ) ).split( /,\s+/ );
			if ( res.length === 1 && res[ 0 ].match( /^\s*$/ ) !== null ) {
				return [];
			}
			return res;
		},

		// Security note: The elements of list should not come from user input.
		restoreDefaults = function ( list ) {
			list.forEach( function ( item ) {
				var val = mw.util.getParamValue( item );
				if ( val === null || val === '' ) {
					return;
				}
				$( '#' + item ).val( val );
			} );
		};

	add();

} );