User:Bawolff/advancedSearch.js
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.
![]() | Documentation for this user script can be added at User:Bawolff/advancedSearch. |
/**
* 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();
});