User:Bawolff/advancedSearch.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]
*/
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': 'Показать все (все файлы на Викискладе могут быть повторно использованы при определённых условиях)',
	}
};
var lookup = function (key) {
	var lang = mw.config.get('wgUserLanguage');
	if (i18n[lang] && i18n[lang][key]) {
		return i18n[lang][key];
	}
	return i18n.en[key];
};

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

var 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);
};
var 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 = [];
			var 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);
		});
	});
};

var 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').submit(fixForm);

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

};

var fixForm = function () {
	var searchBox = OO.ui.infuse('searchText');
	var addedText = ' ';
	var trimField = function (input) {
		if (input === null || input === undefined) {
			input = '';
		}
		input = input + ''; // FIXME why was I casting to string in first place?
		input = input.replace(/^\s*/, '').replace(/\s*$/, '');
		return input;
	};

	var altCat = splitField($('#search-cat-any')[0].value);
	var cats = altCat.join( '|' ).replace( /\|$/, '' );
	if ( cats ) {
		addedText += ' incategory:"' + cats + '"';
	}
	var anyCat = splitField($('#search-cat-all')[0].value);
	$.each(anyCat, function () {
		var field = trimField(this);
		if (field === '') return;
		addedText += ' incategory:"' + field + '"';
	});

	var noCat = splitField($('#search-cat-none')[0].value);
	$.each(noCat, function () {
		var field = trimField(this);
		if (field === '') return;
		addedText += ' -incategory:"' + field + '"';
	});

	var fileTypes = ($('#search-file-type').val()+ '').split(' ');
	$.each(fileTypes, 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 );
};



var splitField = function (text) {
	var res = (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.
var restoreDefaults = function (list) {
	$.each(list, function () {
		var val = mw.util.getParamValue(this);
		if (val === null || val === '') return;
		$('#' + this).val(val);
	});
};

add();

});