Участник:AlexeyBaturin/CulturalHeritageImagesCount.js

Материал из Wikivoyage

Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.

  • Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
  • Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
  • Internet Explorer / 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: '&nbsp;[' + 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);
            }
        });
    });
});

});