Участник:AlexeyBaturin/CulturalHeritageImagesCount.js
Внешний вид
Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.
- Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
- Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
- Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
- Opera: Нажмите Ctrl+F5.
mw.loader.using(['mediawiki.api'], function() {
var AsyncUtils = {
runSequence: function runSequence(functions, onSuccess, results) {
if (!results) {
results = [];
}
if (functions.length > 0) {
var firstFunction = functions[0];
firstFunction(function (result) {
results.push(result);
setTimeout( // hack to break recursion chain
function () {
AsyncUtils.runSequence(functions.slice(1), onSuccess, results);
}, 0);
});
} else {
onSuccess(results);
}
},
runChunks: function runChunks(runSingleChunkFunction, maxChunkSize, data, onSuccess) {
var chunkRunFunctions = [];
var _loop = function _loop(dataNumStart) {
var dataChunk = data.slice(dataNumStart, dataNumStart + maxChunkSize);
chunkRunFunctions.push(function (onSuccess) {
return runSingleChunkFunction(dataChunk, onSuccess);
});
};
for (var dataNumStart = 0; dataNumStart < data.length; dataNumStart += maxChunkSize) {
_loop(dataNumStart);
}
this.runSequence(chunkRunFunctions, function (chunkResults) {
var result = chunkResults.reduce(function (current, total) {
return total.concat(current);
}, []);
onSuccess(result);
});
}
};
var StringUtils = {
contains: function contains(string, substring) {
return string.indexOf(substring) >= 0;
},
trim: function trim(string) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
};
var ArrayUtils = {
hasElement: function hasElement(array, element) {
return array.indexOf(element) >= 0;
},
inArray: function inArray(element, array) {
return this.hasElement(array, element);
}
};
var ObjectUtils = {
merge: function merge(obj1, obj2) {
var result = {};
for (var prop in obj1) {
if (obj1.hasOwnProperty(prop)) {
result[prop] = obj1[prop];
}
}
for (var _prop in obj2) {
if (obj2.hasOwnProperty(_prop)) {
result[_prop] = obj2[_prop];
}
}
return result;
}
};
var MediaWikiPage = {
getPageName: function getPageName() {
return mw.config.get('wgPageName');
},
isDiffMode: function isDiffMode() {
return $('table.diff').length > 0;
},
isLastRevision: function isLastRevision() {
return mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId');
},
isViewAction: function isViewAction() {
return mw.config.get('wgAction') === 'view';
},
isViewSourceMode: function isViewSourceMode() {
return $('#ca-viewsource').length > 0;
},
isViewSpecificRevisionMode: function isViewSpecificRevisionMode() {
return $('#mw-revision-info').length > 0;
},
isRegularNamespace: function isRegularNamespace() {
var namespace = mw.config.get('wgNamespaceNumber');
return namespace === 0 || namespace === 2 || namespace === 4;
}
};
var CommonsApi = {
baseUrl: 'https://commons.wikimedia.org/w/api.php',
executeRequest: function executeRequest(parameters, onSuccess) {
$.ajax({
url: this.baseUrl,
data: parameters,
crossDomain: true,
dataType: 'jsonp'
}).done(function (data) {
onSuccess(data);
});
},
getCategoryFiles: function getCategoryFiles(category, limit, onSuccess) {
var self = this;
self.executeRequest({
'action': 'query',
'list': 'categorymembers',
'cmtype': 'file',
'cmtitle': 'Category:' + category,
'cmlimit': 'max',
'format': 'json'
}, function (data) {
if (data.query && data.query.categorymembers) {
var files = [];
data.query.categorymembers.forEach(function (member) {
if (member.title) {
files.push(member.title);
}
});
onSuccess(files);
}
});
},
getCategoryImages: function getCategoryImages(category, limit, onSucess) {
this.getCategoryFiles(category, limit, function (files) {
var images = [];
files.forEach(function (file) {
var extension = file.toLowerCase().substr(file.length - 4);
if (extension === '.jpg' || extension === '.png' || extension === '.gif') {
images.push(file);
}
});
onSucess(images);
});
},
getImageInfo: function getImageInfo(image, onSuccess) {
var self = this;
self.executeRequest({
'action': 'query',
'titles': image,
'prop': 'imageinfo|revisions',
'iiprop': 'url',
'iiurlwidth': '200',
'iiurlheight': '200',
'rvprop': 'content',
'rvlimit': '1',
'format': 'json'
}, function (data) {
if (!data.query || !data.query.pages) {
return;
}
var pages = data.query.pages;
var firstPage = pages[Object.keys(pages)[0]];
if (!firstPage || !firstPage.imageinfo || firstPage.imageinfo.length <= 0) {
return;
}
var text = '';
if (firstPage.revisions && firstPage.revisions.length > 0) {
var revision = firstPage.revisions[0];
if (revision['*']) {
text = revision['*'];
}
}
var imageInfo = firstPage.imageinfo[0];
onSuccess({
'image': image,
'thumb': imageInfo.thumburl,
'text': text,
'url': imageInfo.url
});
});
},
getImagesInfo: function getImagesInfo(images, onSuccess) {
var self = this;
AsyncUtils.runSequence(images.map(function (image) {
return function (onSuccess) {
self.getImageInfo(image, onSuccess);
};
}), function (imageInfos) {
onSuccess(imageInfos);
});
},
/**
*
* @param categories list of category titles, e.g. ['Novosibirsk', 'Tomsk', 'Culture_of_Novosibirsk']
* @param onSuccess function which accepts single argument - list which has category
* titles for each category which has at least one file, e.g.
* ['Novosibirsk': 'Culture_of_Novosibirsk']
*/
hasCategoriesFiles: function hasCategoriesFiles(categories, onSuccess) {
var _this = this;
var maxChunkSize = 30;
AsyncUtils.runChunks(function (categoriesChunk, onSuccess) {
_this.executeRequest({
action: 'query',
titles: categoriesChunk.join("|"),
prop: 'categoryinfo',
format: 'json'
}, function (data) {
var result = [];
if (!data || !data.query || !data.query.pages) {
return;
}
Object.keys(data.query.pages).forEach(function (key) {
var pageInfo = data.query.pages[key];
if (pageInfo.title && pageInfo.categoryinfo && pageInfo.categoryinfo.files && pageInfo.categoryinfo.files > 0) {
result.push(pageInfo.title);
}
});
onSuccess(result);
});
}, maxChunkSize, categories, onSuccess);
},
/**
*
* @param categories list of category titles, e.g. ['Novosibirsk', 'Tomsk', 'Culture_of_Novosibirsk']
* @param onSuccess function which accepts single argument - list where each item has category
* title and files count, e.g. [
* {category: 'Novosibirsk', files: 51},
* {category: 'Tomsk', files: 42}
* {category: 'Culture_of_Novosibirsk', files: 48}
* ]
*/
countCategoriesFiles: function countCategoriesFiles(categories, onSuccess) {
var _this2 = this;
var maxChunkSize = 30;
AsyncUtils.runChunks(function (categoriesChunk, onSuccess) {
_this2.executeRequest({
action: 'query',
titles: categoriesChunk.join("|"),
prop: 'categoryinfo',
format: 'json'
}, function (data) {
var result = [];
if (!data || !data.query || !data.query.pages) {
return;
}
Object.keys(data.query.pages).forEach(function (key) {
var pageInfo = data.query.pages[key];
if (pageInfo.title) {
var filesCount = pageInfo.categoryinfo && pageInfo.categoryinfo.files ? pageInfo.categoryinfo.files : 0;
result.push({
category: pageInfo.title,
files: filesCount
});
}
});
onSuccess(result);
});
}, maxChunkSize, categories, onSuccess);
}
};
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var ListingTableHtml = function () {
function ListingTableHtml(listingTableElement) {
_classCallCheck(this, ListingTableHtml);
this._listingTableElement = listingTableElement;
}
_createClass(ListingTableHtml, [{
key: 'hasListingPhoto',
value: function hasListingPhoto() {
var hasPhoto = true;
this._listingTableElement.find('a').each(function () {
var aElement = $(this);
if (aElement.text() === 'Нет фото' || aElement.attr('title') === 'Нет фото') {
hasPhoto = false;
return false;
}
});
return hasPhoto;
}
}, {
key: 'addWarning',
value: function addWarning(warningText) {
var nameElement = this._listingTableElement.find('span.monument-name').first();
if (!nameElement) {
return;
}
var warningElement = $('<span>', { html: ' [' + warningText + ']', style: 'color: red;' });
warningElement.insertAfter(nameElement);
}
}, {
key: 'addGalleryFilesCount',
value: function addGalleryFilesCount(filesCount) {
this._listingTableElement.find('a.extiw').each(function () {
var linkElem = $(this);
if (linkElem.attr('href').indexOf('https://commons.wikimedia.org') !== 0) return;
linkElem.text(linkElem.text() + " (" + filesCount + ")");
});
}
}, {
key: 'findCommonsCategory',
value: function findCommonsCategory(parentCategory) {
var commonsCategory = null;
this._listingTableElement.find('a.extiw').each(function () {
var linkElem = $(this);
var href = linkElem.attr('href');
var parentCategoryHtmlName = parentCategory.replace(/ /g, '_');
if (!href) {
return;
}
if (href.indexOf('https://commons.wikimedia.org/wiki/Category:' + parentCategoryHtmlName + '/') === 0) {
commonsCategory = href.replace(/https:\/\/commons\.wikimedia\.org\/wiki\//, '');
return false;
}
});
return commonsCategory.replace(/_/g, ' ');
}
}]);
return ListingTableHtml;
}();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var ListingSection = function () {
function ListingSection(headerElement, sectionIndex) {
_classCallCheck(this, ListingSection);
this._headerElement = headerElement;
this._sectionIndex = sectionIndex;
}
_createClass(ListingSection, [{
key: 'getHeaderElement',
value: function getHeaderElement() {
return this._headerElement;
}
}, {
key: 'getSectionIndex',
value: function getSectionIndex() {
return this._sectionIndex;
}
}]);
return ListingSection;
}();
var ListingTable = function () {
function ListingTable(tableElement, sectionIndex, listingIndex) {
_classCallCheck(this, ListingTable);
this._tableElement = tableElement;
this._sectionIndex = sectionIndex;
this._listingIndex = listingIndex;
}
_createClass(ListingTable, [{
key: 'getTableElement',
value: function getTableElement() {
return this._tableElement;
}
}, {
key: 'getSectionIndex',
value: function getSectionIndex() {
return this._sectionIndex;
}
}, {
key: 'getListingIndex',
value: function getListingIndex() {
return this._listingIndex;
}
}]);
return ListingTable;
}();
var ListingPageElements = function () {
function ListingPageElements(sections, listingTables) {
_classCallCheck(this, ListingPageElements);
this._sections = sections;
this._listingTables = listingTables;
}
/**
* @returns {ListingSection[]}
*/
_createClass(ListingPageElements, [{
key: 'getSections',
value: function getSections() {
return this._sections;
}
}, {
key: 'getListingTables',
value: function getListingTables() {
return this._listingTables;
}
}]);
return ListingPageElements;
}();
var ListingEditorUtils = {
isEditablePage: function isEditablePage() {
return MediaWikiPage.isRegularNamespace() && MediaWikiPage.isViewAction() && MediaWikiPage.isLastRevision() && !MediaWikiPage.isDiffMode() && !MediaWikiPage.isViewSpecificRevisionMode() && !MediaWikiPage.isViewSourceMode();
},
/**
* @returns {ListingPageElements}
*/
getListingPageElements: function getListingPageElements() {
var pageBodyContentElement = $('#bodyContent');
var currentSectionIndex = 0;
var currentListingIndex = 0;
var sections = [];
var listingTables = [];
function isTableOfContentsHeader(headerElement) {
return headerElement.parents('.toc').length > 0;
}
// Here we add buttons to:
// - add new listing - for each section header
// - edit existing listing - for each existing listing
//
// - section index, to which we are going to add new listing
// - section index and listing index (within a section) for listing which we are going to edit
// To calculate section index and listing index, we iterate over all section header and listing
// table elements sequentially (in the same order as we have them in HTML).
// When we meet header - we consider that new section is started and increase current section index,
// and reset current listing index (listings are enumerated within section). All listings belong
// to that section until we meet the next header.
// When we meet listing table - we increase current listing index.
pageBodyContentElement.find('h1, h2, h3, h4, h5, h6, table.monument').each(function () {
if (ArrayUtils.inArray(this.tagName, ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'])) {
var headerElement = $(this);
if (!isTableOfContentsHeader(headerElement)) {
currentSectionIndex++;
currentListingIndex = 0;
sections.push(new ListingSection(headerElement, currentSectionIndex));
}
} else if (this.tagName === 'TABLE') {
var listingTable = $(this);
listingTables.push(new ListingTable(listingTable, currentSectionIndex, currentListingIndex));
currentListingIndex++;
}
});
return new ListingPageElements(sections, listingTables);
}
};
var pageTypes = [{
galleryTitle: "WLE",
parentCategoryName: "Protected areas of Russia",
pageNamespace: "Природные_памятники"
}, {
galleryTitle: "WLM",
parentCategoryName: "WLM",
pageNamespace: "Культурное_наследие"
}];
pageTypes.forEach(function (pageType) {
if (!ListingEditorUtils.isEditablePage() || !StringUtils.contains(MediaWikiPage.getPageName(), pageType.pageNamespace)) {
return;
}
var listingPageElements = ListingEditorUtils.getListingPageElements();
var listingTables = listingPageElements.getListingTables();
var listingCommonsCategory = [];
listingTables.forEach(function (listingTable) {
var listingTableElement = $(listingTable.getTableElement());
var listingTableHtml = new ListingTableHtml(listingTableElement);
var commonsCategory = listingTableHtml.findCommonsCategory(pageType.parentCategoryName);
if (!commonsCategory) {
return;
}
listingCommonsCategory.push({
listingTableHtml: listingTableHtml,
category: commonsCategory
});
});
CommonsApi.countCategoriesFiles(listingCommonsCategory.map(function (item) {
return item.category.replace(/_/g, ' ');
}), function (categoriesWithImages) {
var filesCountByCategory = {};
categoriesWithImages.forEach(function (item) {
filesCountByCategory[item.category] = item.files;
});
listingCommonsCategory.forEach(function (listingItem) {
var filesCount = filesCountByCategory[listingItem.category.replace(/_'/g, ' ')];
if (filesCount !== undefined) {
listingItem.listingTableHtml.addGalleryFilesCount(filesCount);
}
});
});
});
});