Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.
- 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); } }); }); }); });