Перейти к содержанию

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

Материал из Wikivoyage

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

  • 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 commonMessages = {
    addTitle: 'Добавить объект',
    editTitle: 'Редактировать объект',
    submitApiError: 'Во время сохранения листинга на сервере произошла ошибка, пожайлуста, попробуйте сохранить ещё раз',
    submitBlacklistError: 'Ошибка: текст содержит ссылку из чёрного списка, пожайлуста, удалите её и попробуйте сохранить снова',
    submitUnknownError: 'Ошибка: при попытке сохранить листинг произошла неизвестная ошибка, пожайлуста, попробуйте сохранить ещё раз',
    submitHttpError: 'Ошибка: сервер сообщил о HTTP ошибке, возникшей во время сохранения листинга, пожайлуста, попробуйте сохранить ещё раз',
    submitEmptyError: 'Ошибка: сервер вернул пустой ответ при попытке сохранить листинг, пожайлуста, попробуйте сохранить ещё раз',
    enterCaptcha: 'Введите CAPTCHA',
    changesSummaryAdded: 'Добавлен объект',
    changesSummaryUpdated: 'Обновлён объект',
    captchaSubmit: 'Продолжить',
    captchaCancel: 'Отмена'
};


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 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 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 ValidationUtils = {
    normalizeUrl: function normalizeUrl(url) {
        var webRegex = new RegExp('^https?://', 'i');
        if (!webRegex.test(url) && url !== '') {
            return 'http://' + url;
        } else {
            return url;
        }
    }
};


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 _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 CaptchaDialog = function () {
    function CaptchaDialog(captchaImgSrc, onCaptchaSubmit) {
        var _this = this;

        _classCallCheck(this, CaptchaDialog);

        this._captcha = $('<div id="captcha-dialog">');
        $('<img class="fancycaptcha-image">').attr('src', captchaImgSrc).appendTo(this._captcha);
        $('<input id="input-captcha" type="text">').appendTo(this._captcha);

        this._captcha.dialog({
            modal: true,
            title: commonMessages.enterCaptcha,
            buttons: [{
                text: commonMessages.captchaSubmit,
                click: function click() {
                    onCaptchaSubmit($('#input-captcha').val());
                    _this.destroy();
                }
            }, {
                text: commonMessages.captchaCancel,
                click: function click() {
                    return _this.destroy();
                }
            }]
        });
    }

    _createClass(CaptchaDialog, [{
        key: 'destroy',
        value: function destroy() {
            this._captcha.dialog('destroy').remove();
        }
    }]);

    return CaptchaDialog;
}();


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 _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 ListingEditorButtonEdit = function () {
    function ListingEditorButtonEdit(button, listingTable) {
        _classCallCheck(this, ListingEditorButtonEdit);

        this._button = button;
        this._listingTable = listingTable;
    }

    _createClass(ListingEditorButtonEdit, [{
        key: 'click',
        value: function click(handler) {
            var _this = this;

            this._button.click(function () {
                return handler(_this._listingTable);
            });
        }
    }]);

    return ListingEditorButtonEdit;
}();

var ListingEditorButtonAdd = function () {
    function ListingEditorButtonAdd(button, section) {
        _classCallCheck(this, ListingEditorButtonAdd);

        this._button = button;
        this._section = section;
    }

    _createClass(ListingEditorButtonAdd, [{
        key: 'click',
        value: function click(handler) {
            var _this2 = this;

            this._button.click(function () {
                return handler(_this2._section);
            });
        }
    }]);

    return ListingEditorButtonAdd;
}();

var ListingEditorButtons = {
    /**
     *
     * @param listingTable ListingTable
     */
    createListingEditButton: function createListingEditButton(listingTable) {
        var editListingButton = $('<span class="vcard-edit-button noprint" style="padding-left: 5px;">');
        editListingButton.html('<a href="javascript:" class="icon-pencil" title="Редактировать">Редактировать</a>');

        var nameElement = listingTable.getTableElement().find('span.monument-name').first();
        if (nameElement) {
            nameElement.append(editListingButton);
        }

        return new ListingEditorButtonEdit(editListingButton, listingTable);
    },
    createListingAddButton: function createListingAddButton(section) {
        var sectionAddButton = $('<a href="javascript:">добавить</a>');
        var bracketStart = $('<span class="mw-editsection-bracket">[</span>');
        var bracketEnd = $('<span class="mw-editsection-bracket">]</span>');
        section.getHeaderElement().append($('<span class="mw-editsection"/>').append(bracketStart).append(sectionAddButton).append(bracketEnd));

        return new ListingEditorButtonAdd(sectionAddButton, section);
    }
};


var MAX_DIALOG_WIDTH = 1200;
var LICENSE_TEXT = 'Нажимая кнопку «Сохранить», вы соглашаетесь с <a class="external" target="_blank" href="https://wikimediafoundation.org/wiki/Terms_of_Use/ru">условиями использования</a>, а также соглашаетесь на неотзывную публикацию по лицензии <a class="external" target="_blank" href="https://en.wikipedia.org/wiki/ru:Википедия:Текст_Лицензии_Creative_Commons_Attribution-ShareAlike_3.0_Unported">CC-BY-SA 3.0</a>.';

var ListingEditorDialog = {
    showDialog: function showDialog(formElement, dialogTitle, onSubmit, onCancel, onHelp) {
        var windowWidth = $(window).width();
        var dialogWidth = windowWidth > MAX_DIALOG_WIDTH ? MAX_DIALOG_WIDTH : 'auto';

        mw.loader.using(['jquery.ui'], function () {
            formElement.dialog({
                modal: true,
                height: 'auto',
                width: dialogWidth,
                title: dialogTitle,
                dialogClass: 'listing-editor-dialog',
                buttons: [{
                    text: '?',
                    id: 'listing-help',
                    click: function click() {
                        onHelp();
                    }
                }, {
                    text: "Сохранить",
                    click: function click() {
                        onSubmit();
                    }
                }, {
                    text: "Отмена",
                    click: function click() {
                        onCancel();
                    }
                }],
                create: function create() {
                    $('.ui-dialog-buttonpane').append('<div class="listing-license">' + LICENSE_TEXT + '</div>');
                }
            });
        });
    }
};


var ListingEditorFormComposer = {
    createInputFormRow: function createInputFormRow(inputElementId, labelText) {
        var rowElement = $('<tr>');
        var label = $('<label>', {
            'for': inputElementId,
            'html': labelText
        });
        var labelColumn = $('<td>', {
            'class': "editor-label-col",
            'style': "width: 200px"
        }).append(label);
        var inputColumnElement = $('<td>');
        rowElement.append(labelColumn).append(inputColumnElement);
        return {
            'rowElement': rowElement,
            'inputColumnElement': inputColumnElement
        };
    },
    createInputFormRowCheckbox: function createInputFormRowCheckbox(inputElementId, labelText) {
        var row = this.createInputFormRow(inputElementId, labelText);
        var inputElement = $('<input>', {
            'type': 'checkbox',
            'id': inputElementId
        });
        row.inputColumnElement.append(inputElement);
        return {
            'rowElement': row.rowElement,
            'inputElement': inputElement
        };
    },
    createInputFormRowSelect: function createInputFormRowSelect(inputElementId, labelText, options) {
        var row = this.createInputFormRow(inputElementId, labelText);
        var inputElement = $('<select>', {
            'id': inputElementId
        });
        options.forEach(function (option) {
            var optionElement = $('<option>', {
                'value': option.value,
                'html': option.title
            });
            inputElement.append(optionElement);
        });
        row.inputColumnElement.append(inputElement);
        return {
            'rowElement': row.rowElement,
            'inputElement': inputElement
        };
    },
    createInputFormRowText: function createInputFormRowText(inputElementId, labelText, placeholderText, partialWidth, insertSymbols) {
        if (!placeholderText) {
            placeholderText = '';
        }
        var row = this.createInputFormRow(inputElementId, labelText);
        var inputElement = $('<input>', {
            'type': 'text',
            'class': partialWidth ? 'editor-partialwidth' : 'editor-fullwidth',
            'style': insertSymbols ? 'width: 90%' : '',
            'placeholder': placeholderText,
            'id': inputElementId
        });
        row.inputColumnElement.append(inputElement);

        if (insertSymbols) {
            var buttonInsertQuotes = $('<a>', {
                'class': 'name-quotes-template',
                href: 'javascript:;',
                html: '«»'
            });
            var buttonInsertDash = $('<a>', {
                'class': 'name-dash-template',
                href: 'javascript:;',
                html: '—'
            });
            InputInsertSymbols.addDashInsertHandler(buttonInsertDash, inputElement);
            InputInsertSymbols.addQuotesInsertHandler(buttonInsertQuotes, inputElement);

            row.inputColumnElement.append('&nbsp;');
            row.inputColumnElement.append(buttonInsertQuotes);
            row.inputColumnElement.append('&nbsp;');
            row.inputColumnElement.append(buttonInsertDash);
        }

        return {
            'rowElement': row.rowElement,
            'inputElement': inputElement
        };
    },
    createRowDivider: function createRowDivider() {
        return $('<tr>').append($('<td>', { 'colspan': "2" }).append($('<div>', {
            'class': "listing-divider",
            'style': "margin: 3px 0"
        })));
    },
    createRowLink: function createRowLink(linkText) {
        var linkElement = $("<a>", {
            'href': 'javascript:;',
            'html': linkText
        });
        var rowElement = $('<tr>').append($('<td>')).append($('<td>').append(linkElement));
        return {
            'rowElement': rowElement,
            'linkElement': linkElement
        };
    },
    createChangesDescriptionRow: function createChangesDescriptionRow() {
        var inputChangesSummary = $('<input>', {
            'type': "text",
            'class': "editor-partialwidth",
            'placeholder': "что именно было изменено",
            'id': "input-summary"
        });
        var inputIsMinorChanges = $('<input>', {
            'type': "checkbox",
            'id': "input-minor"
        });
        var labelChangesSummary = $('<label>', {
            'for': "input-summary",
            'html': 'Описание изменений'
        });
        var labelIsMinorChanges = $('<label>', {
            'for': "input-minor",
            'class': "listing-tooltip",
            'title': "Установите галочку, если изменение незначительное, например, исправление опечатки",
            'html': 'незначительное изменение?'
        });
        var spanIsMinorChanges = $('<span>', { id: "span-minor" });
        spanIsMinorChanges.append(inputIsMinorChanges).append(labelIsMinorChanges);
        var row = $('<tr>');
        row.append($('<td>', { 'class': "editor-label-col", style: "width: 200px" }).append(labelChangesSummary));
        row.append($('<td>').append(inputChangesSummary).append(spanIsMinorChanges));
        return {
            'row': row,
            'inputChangesSummary': inputChangesSummary,
            'inputIsMinorChanges': inputIsMinorChanges
        };
    },
    createObjectDescriptionRow: function createObjectDescriptionRow() {
        var inputDescription = $('<textarea>', {
            'rows': "4",
            'class': "editor-fullwidth",
            'placeholder': "описание объекта",
            'id': "input-description"
        });
        var labelDescription = $('<label>', {
            'for': "input-description",
            'html': "Описание"
        });
        var row = $('<tr>');
        row.append($('<td>', { 'class': "editor-label-col", 'style': "width: 200px" }).append(labelDescription));
        row.append($('<td>').append(inputDescription));
        return {
            'row': row,
            'inputDescription': inputDescription
        };
    },
    createTableFullWidth: function createTableFullWidth() {
        var tableElement = $('<table>', {
            'class': 'editor-fullwidth'
        });
        var wrapperElement = $('<div>');
        wrapperElement.append(tableElement);
        return {
            'wrapperElement': wrapperElement,
            'tableElement': tableElement
        };
    },
    createTableTwoColumns: function createTableTwoColumns() {
        var leftTableElement = $('<table>', {
            'class': "editor-fullwidth"
        });
        var rightTableElement = $('<table>', {
            'class': "editor-fullwidth"
        });
        var wrapperElement = $('<div>');
        wrapperElement.append($('<div>', {
            'class': 'listing-col listing-span_1_of_2'
        }).append(leftTableElement));
        wrapperElement.append($('<div>', {
            'class': 'listing-col listing-span_1_of_2'
        }).append(rightTableElement));
        return {
            'wrapperElement': wrapperElement,
            'leftTableElement': leftTableElement,
            'rightTableElement': rightTableElement
        };
    },
    createForm: function createForm() {
        var formElement = $('<form id="listing-editor">');
        formElement.append($('<br>'));
        return {
            'formElement': formElement
        };
    }
};


var api = new mw.Api();

var MediaWikiPageWikitext = {
    loadSectionWikitext: function loadSectionWikitext(sectionIndex, onSuccess) {
        $.ajax({
            url: mw.util.wikiScript(''),
            data: {
                title: mw.config.get('wgPageName'),
                action: 'raw',
                section: sectionIndex
            },
            cache: false
        }).done(function (data) {
            onSuccess(data);
        }).fail(function (jqXHR, textStatus, errorThrown) {
            alert('Ошибка при получении исходного вики-текста статьи: ' + textStatus + ' ' + errorThrown);
        });
    },
    saveSectionWikitext: function saveSectionWikitext(sectionIndex, sectionWikitext, changesSummary, changesIsMinor, captchaId, captchaAnswer, onSuccess, onFailure, onCaptcha) {
        var editPayload = {
            action: "edit",
            title: mw.config.get("wgPageName"),
            section: sectionIndex,
            text: sectionWikitext,
            summary: changesSummary,
            captchaid: captchaId,
            captchaword: captchaAnswer
        };
        if (changesIsMinor) {
            $.extend(editPayload, { minor: 'true' });
        }

        api.postWithToken("csrf", editPayload).done(function (data) {
            if (data && data.edit && data.edit.result === 'Success') {
                onSuccess();
            } else if (data && data.error) {
                onFailure(commonMessages.submitApiError + ' "' + data.error.code + '": ' + data.error.info);
            } else if (data && data.edit.spamblacklist) {
                onFailure(commonMessages.submitBlacklistError + ': ' + data.edit.spamblacklist);
            } else if (data && data.edit.captcha) {
                onCaptcha(data.edit.captcha.url, data.edit.captcha.id);
            } else {
                onFailure(commonMessages.submitUnknownError);
            }
        }).fail(function (code, result) {
            if (code === "http") {
                onFailure(commonMessages.submitHttpError + ': ' + result.textStatus);
            } else if (code === "ok-but-empty") {
                onFailure(commonMessages.submitEmptyError);
            } else {
                onFailure(commonMessages.submitUnknownError + ': ' + code);
            }
        });
    },
    getSectionName: function getSectionName(sectionWikitext) {
        var HEADING_REGEX = /^=+\s*([^=]+)\s*=+\s*\n/;
        var result = HEADING_REGEX.exec(sectionWikitext);
        return result !== null ? result[1].trim() : "";
    }
};


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 Region = function () {
    function Region(id, title) {
        _classCallCheck(this, Region);

        this._id = id;
        this._title = title;
    }

    _createClass(Region, [{
        key: "getId",
        value: function getId() {
            return this._id;
        }
    }, {
        key: "getTitle",
        value: function getTitle() {
            return this._title;
        }
    }]);

    return Region;
}();

var regions = [new Region("", "не задан"), new Region("ru-ad", "Адыгея"), new Region("ru-ba", "Башкортостан"), new Region("ru-bu", "Бурятия"), new Region("ru-al", "Алтай"), new Region("ru-da", "Дагестан"), new Region("ru-in", "Ингушетия"), new Region("ru-kb", "Кабардино-Балкария"), new Region("ru-kl", "Калмыкия"), new Region("ru-kc", "Карачаево-Черкесия"), new Region("ru-krl", "Карелия"), new Region("ru-ko", "республика Коми"), new Region("ru-me", "Марий Эл"), new Region("ru-mo", "Мордовия"), new Region("ru-sa", "Якутия (Саха)"), new Region("ru-se", "Северная Осетия"), new Region("ru-ta", "Татарстан"), new Region("ru-ty", "Тува"), new Region("ru-ud", "Удмуртия"), new Region("ru-kk", "Хакасия"), new Region("ru-ce", "Чеченская республика"), new Region("ru-chv", "Чувашия"), new Region("ru-alt", "Алтайский край"), new Region("ru-kda", "Краснодарский край"), new Region("ru-kya", "Красноярский край"), new Region("ru-pri", "Приморский край"), new Region("ru-sta", "Ставропольский край"), new Region("ru-kha", "Хабаровский край"), new Region("ru-amu", "Амурская область"), new Region("ru-ark", "Архангельская область"), new Region("ru-ast", "Астраханская область"), new Region("ru-bel", "Белгородская область"), new Region("ru-bry", "Брянская область"), new Region("ru-vla", "Владимирская область"), new Region("ru-vgg", "Волгоградская область"), new Region("ru-vol", "Вологодская область"), new Region("ru-vor", "Воронежская область"), new Region("ru-iva", "Ивановская область"), new Region("ru-irk", "Иркутская область"), new Region("ru-kal", "Калининградская область"), new Region("ru-klu", "Калужская область"), new Region("ru-kam", "Камчатский край"), new Region("ru-kem", "Кемеровская область"), new Region("ru-kir", "Кировская область"), new Region("ru-kos", "Костромская область"), new Region("ru-kgn", "Курганская область"), new Region("ru-krs", "Курская область"), new Region("ru-len", "Ленинградская область"), new Region("ru-lip", "Липецкая область"), new Region("ru-mag", "Магаданская область"), new Region("ru-mos", "Московская область"), new Region("ru-mur", "Мурманская область"), new Region("ru-niz", "Нижегородская область"), new Region("ru-ngr", "Новгородская область"), new Region("ru-nvs", "Новосибирская область"), new Region("ru-oms", "Омская область"), new Region("ru-ore", "Оренбургская область"), new Region("ru-orl", "Орловская область"), new Region("ru-pnz", "Пензенская область"), new Region("ru-per", "Пермский край"), new Region("ru-psk", "Псковская область"), new Region("ru-ros", "Ростовская область"), new Region("ru-rya", "Рязанская область"), new Region("ru-sam", "Самарская область"), new Region("ru-sar", "Саратовская область"), new Region("ru-sak", "Сахалинская область"), new Region("ru-sve", "Свердловская область"), new Region("ru-smo", "Смоленская область"), new Region("ru-tam", "Тамбовская область"), new Region("ru-tve", "Тверская область"), new Region("ru-tom", "Томская область"), new Region("ru-tul", "Тульская область"), new Region("ru-tyu", "Тюменская область"), new Region("ru-uly", "Ульяновская область"), new Region("ru-che", "Челябинская область"), new Region("ru-zab", "Забайкальский край"), new Region("ru-yar", "Ярославская область"), new Region("ru-mow", "Москва"), new Region("ru-spb", "Санкт-Петербург"), new Region("ru-jew", "Еврейская автономная область"), new Region("ru-km", "Крым"), new Region("ru-nen", "Ненецкий автономный округ"), new Region("ru-khm", "Ханты-Мансийский автономный округ"), new Region("ru-chu", "Чукотский автономный округ"), new Region("ru-yam", "Ямало-Ненецкий автономный округ"), new Region("ru-sev", "Севастополь")];


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 CulturalMonumentStyle = function () {
    function CulturalMonumentStyle(value, title) {
        _classCallCheck(this, CulturalMonumentStyle);

        this._value = value;
        this._title = title;
    }

    _createClass(CulturalMonumentStyle, [{
        key: 'getTitle',
        value: function getTitle() {
            return this._title;
        }
    }, {
        key: 'getValue',
        value: function getValue() {
            return this._value;
        }
    }]);

    return CulturalMonumentStyle;
}();

var culturalMonumentStyles = [new CulturalMonumentStyle('', ''), new CulturalMonumentStyle('конструктивизм', 'конструктивизм'), new CulturalMonumentStyle('модерн', 'модерн')];


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 CulturalMonumentType = function () {
    function CulturalMonumentType(value, title) {
        _classCallCheck(this, CulturalMonumentType);

        this._value = value;
        this._title = title;
    }

    _createClass(CulturalMonumentType, [{
        key: 'getValue',
        value: function getValue() {
            return this._value;
        }
    }, {
        key: 'getTitle',
        value: function getTitle() {
            return this._title;
        }
    }]);

    return CulturalMonumentType;
}();

var culturalMonumentTypes = [new CulturalMonumentType('architecture', 'памятник архитектуры'), new CulturalMonumentType('history', 'памятник истории'), new CulturalMonumentType('monument', 'памятник монументального искусства'), new CulturalMonumentType('archeology', 'памятник археологии')];


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 Protection = function () {
    function Protection(value, title) {
        _classCallCheck(this, Protection);

        this._value = value;
        this._title = title;
    }

    _createClass(Protection, [{
        key: 'getTitle',
        value: function getTitle() {
            return this._title;
        }
    }, {
        key: 'getValue',
        value: function getValue() {
            return this._value;
        }
    }]);

    return Protection;
}();

var protections = [new Protection('', ''), new Protection('Р', 'республиканского значения'), new Protection('М', 'местного значения'), new Protection('В', 'выявленный')];


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 RegionKz = function () {
    function RegionKz(id, title) {
        _classCallCheck(this, RegionKz);

        this._id = id;
        this._title = title;
    }

    _createClass(RegionKz, [{
        key: "getId",
        value: function getId() {
            return this._id;
        }
    }, {
        key: "getTitle",
        value: function getTitle() {
            return this._title;
        }
    }]);

    return RegionKz;
}();

var regionsKz = [new RegionKz("", "не задан"), new RegionKz("kz-ala", "Алматы"), new RegionKz("kz-ast", "Астана"), new RegionKz("kz-alm", "Алматинская область"), new RegionKz("kz-akm", "Акмолинская область"), new RegionKz("kz-akt", "Актюбинская область"), new RegionKz("kz-aty", "Атырауская область"), new RegionKz("kz-vos", "Восточно-Казахстанская область"), new RegionKz("kz-zha", "Жамбылская область"), new RegionKz("kz-zap", "Западно-Казахстанская область"), new RegionKz("kz-kar", "Карагандинская область"), new RegionKz("kz-kus", "Костанайская область"), new RegionKz("kz-kzy", "Кызылординская область"), new RegionKz("kz-man", "Мангистауская область"), new RegionKz("kz-pav", "Павлодарская область"), new RegionKz("kz-sev", "Северо-Казахстанская область"), new RegionKz("kz-yuz", "Южно-Казахстанская область")];


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 CulturalHeritageKZEditorForm = function () {
    function CulturalHeritageKZEditorForm() {
        var _this = this;

        _classCallCheck(this, CulturalHeritageKZEditorForm);

        this._form = ListingEditorFormComposer.createForm();

        this._inputObjectName = ListingEditorFormComposer.createInputFormRowText('input-name', 'Название', 'название объекта', false, true);
        this._inputType = ListingEditorFormComposer.createInputFormRowSelect('input-type', 'Тип', culturalMonumentTypes.map(function (culturalMonumentType) {
            return {
                title: culturalMonumentType.getTitle(),
                value: culturalMonumentType.getValue()
            };
        }));
        this._inputProtection = ListingEditorFormComposer.createInputFormRowSelect('input-protection', 'Категория охраны', protections.map(function (protection) {
            return {
                title: protection.getTitle(),
                value: protection.getValue()
            };
        }));
        this._inputDestroyed = ListingEditorFormComposer.createInputFormRowCheckbox('input-destroyed', 'Утрачен');
        this._inputRegion = ListingEditorFormComposer.createInputFormRowSelect('input-region', 'Регион', regionsKz.map(function (regionKz) {
            return {
                title: regionKz.getTitle(),
                value: regionKz.getId()
            };
        }));
        this._inputDistrict = ListingEditorFormComposer.createInputFormRowText('input-district', 'Район');
        this._inputMunicipality = ListingEditorFormComposer.createInputFormRowText('input-municipality', 'Населённый пункт');
        this._inputBlock = ListingEditorFormComposer.createInputFormRowText('input-block', 'Квартал');
        this._inputAddress = ListingEditorFormComposer.createInputFormRowText('input-address', 'Адрес', 'улица название, номер дома');
        this._inputLat = ListingEditorFormComposer.createInputFormRowText('input-lat', 'Широта', '11.11111', true);
        this._inputLong = ListingEditorFormComposer.createInputFormRowText('input-long', 'Долгота', '111.11111', true);
        this._inputPrecise = ListingEditorFormComposer.createInputFormRowCheckbox('input-precise', 'Точные координаты');
        this._inputYear = ListingEditorFormComposer.createInputFormRowText('input-year', 'Год постройки', 'yyyy', true);
        this._inputAuthor = ListingEditorFormComposer.createInputFormRowText('input-author', 'Автор объекта', 'архитектор, скульптор, инженер и т.д.');
        this._inputStyle = ListingEditorFormComposer.createInputFormRowSelect('input-style', 'Стиль', culturalMonumentStyles.map(function (culturalMonumentStyle) {
            return {
                title: culturalMonumentStyle.getTitle(),
                value: culturalMonumentStyle.getValue()
            };
        }));
        this._inputKnid = ListingEditorFormComposer.createInputFormRowText('input-knid', '№ объекта', 'dddddddddd', true);
        this._inputComplex = ListingEditorFormComposer.createInputFormRowText('input-complex', '№ комплекса', 'dddddddddd', true);
        this._inputImage = ListingEditorFormComposer.createInputFormRowText('input-image', 'Изображение', 'изображение на Викискладе');
        this._inputWiki = ListingEditorFormComposer.createInputFormRowText('input-wiki', 'Википедия', 'статья в русской Википедии');
        this._inputWdid = ListingEditorFormComposer.createInputFormRowText('input-wdid', 'Викиданные', 'идентификатор Викиданных', true);
        this._inputCommonscat = ListingEditorFormComposer.createInputFormRowText('input-commonscat', 'Викисклад', 'категория Викисклада');
        this._inputMunid = ListingEditorFormComposer.createInputFormRowText('input-munid', 'Викиданные нас. пункта', 'идентификатор Викиданных', true);
        this._inputDocument = ListingEditorFormComposer.createInputFormRowText('input-document', 'Документ', '', true);
        this._inputLink = ListingEditorFormComposer.createInputFormRowText('input-link', 'Ссылка №1', 'внешняя ссылка с дополнительной информацией об объекте');
        this._inputLinkExtra = ListingEditorFormComposer.createInputFormRowText('input-linkextra', 'Ссылка №2', 'внешняя ссылка с дополнительной информацией об объекте');

        var selectImageLinkRow = ListingEditorFormComposer.createRowLink('выбрать изображение из галереи');
        selectImageLinkRow.linkElement.click(function () {
            CommonsImagesSelectDialog.showDialog(
            /*knidWLM=*/null, _this._inputKnid.inputElement.val(), _this._inputCommonscat.inputElement.val(), function (selectedImage) {
                _this._inputImage.inputElement.val(selectedImage);
            });
        });

        var tableObjectName = ListingEditorFormComposer.createTableFullWidth();
        tableObjectName.tableElement.append(this._inputObjectName.rowElement);
        tableObjectName.tableElement.append(ListingEditorFormComposer.createRowDivider());
        this._form.formElement.append(tableObjectName.wrapperElement);

        var tableObjectProperties = ListingEditorFormComposer.createTableTwoColumns();

        tableObjectProperties.leftTableElement.append(ListingEditorFormComposer.createRowDivider());
        tableObjectProperties.leftTableElement.append(this._inputType.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputProtection.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputDestroyed.rowElement);
        tableObjectProperties.leftTableElement.append(ListingEditorFormComposer.createRowDivider());
        tableObjectProperties.leftTableElement.append(this._inputRegion.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputDistrict.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputMunicipality.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputBlock.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputAddress.rowElement);
        tableObjectProperties.leftTableElement.append(ListingEditorFormComposer.createRowDivider());
        tableObjectProperties.leftTableElement.append(this._inputLat.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputLong.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputPrecise.rowElement);
        tableObjectProperties.leftTableElement.append(ListingEditorFormComposer.createRowDivider());
        tableObjectProperties.leftTableElement.append(this._inputYear.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputAuthor.rowElement);
        tableObjectProperties.leftTableElement.append(this._inputStyle.rowElement);

        tableObjectProperties.rightTableElement.append(this._inputKnid.rowElement);
        tableObjectProperties.rightTableElement.append(this._inputComplex.rowElement);
        tableObjectProperties.rightTableElement.append(ListingEditorFormComposer.createRowDivider());
        tableObjectProperties.rightTableElement.append(this._inputImage.rowElement);
        tableObjectProperties.rightTableElement.append(selectImageLinkRow.rowElement);
        tableObjectProperties.rightTableElement.append(this._inputWiki.rowElement);
        tableObjectProperties.rightTableElement.append(this._inputWdid.rowElement);
        tableObjectProperties.rightTableElement.append(this._inputCommonscat.rowElement);
        tableObjectProperties.rightTableElement.append(this._inputMunid.rowElement);
        tableObjectProperties.rightTableElement.append(this._inputDocument.rowElement);
        tableObjectProperties.rightTableElement.append(ListingEditorFormComposer.createRowDivider());
        tableObjectProperties.rightTableElement.append(this._inputLink.rowElement);
        tableObjectProperties.rightTableElement.append(this._inputLinkExtra.rowElement);

        this._form.formElement.append(tableObjectProperties.wrapperElement);

        var tableObjectDescription = ListingEditorFormComposer.createTableFullWidth();
        var objectDescriptionRow = ListingEditorFormComposer.createObjectDescriptionRow();
        this._inputDescription = objectDescriptionRow.inputDescription;
        tableObjectDescription.tableElement.append(objectDescriptionRow.row);
        tableObjectDescription.tableElement.append(ListingEditorFormComposer.createRowDivider());
        this._form.formElement.append(tableObjectDescription.wrapperElement);

        var tableChanges = ListingEditorFormComposer.createTableFullWidth();
        var changesDescriptionRow = ListingEditorFormComposer.createChangesDescriptionRow();
        this._inputChangesSummary = changesDescriptionRow.inputChangesSummary;
        this._inputIsMinorChanges = changesDescriptionRow.inputIsMinorChanges;
        tableChanges.tableElement.append(ListingEditorFormComposer.createRowDivider());
        tableChanges.tableElement.append(changesDescriptionRow.row);
        this._form.formElement.append(tableChanges.wrapperElement);

        this._directMappingInputs = {
            name: this._inputObjectName.inputElement,
            type: this._inputType.inputElement,
            protection: this._inputProtection.inputElement,
            region: this._inputRegion.inputElement,
            district: this._inputDistrict.inputElement,
            municipality: this._inputMunicipality.inputElement,
            block: this._inputBlock.inputElement,
            address: this._inputAddress.inputElement,
            lat: this._inputLat.inputElement,
            long: this._inputLong.inputElement,
            year: this._inputYear.inputElement,
            author: this._inputAuthor.inputElement,
            knid: this._inputKnid.inputElement,
            complex: this._inputComplex.inputElement,
            image: this._inputImage.inputElement,
            wiki: this._inputWiki.inputElement,
            wdid: this._inputWdid.inputElement,
            commonscat: this._inputCommonscat.inputElement,
            munid: this._inputMunid.inputElement,
            document: this._inputDocument.inputElement,
            link: this._inputLink.inputElement,
            linkextra: this._inputLinkExtra.inputElement,
            description: this._inputDescription
        };
    }

    _createClass(CulturalHeritageKZEditorForm, [{
        key: 'getForm',
        value: function getForm() {
            return this._form;
        }
    }, {
        key: 'setData',
        value: function setData(listingData) {
            var _this2 = this;

            Object.keys(this._directMappingInputs).forEach(function (key) {
                if (listingData[key]) {
                    _this2._directMappingInputs[key].val(listingData[key]);
                }
            });

            if (listingData['style']) {
                this._inputStyle.inputElement.val(listingData['style'].toLowerCase());
            }
            this._inputDestroyed.inputElement.attr('checked', listingData['status'] === 'destroyed');
            this._inputPrecise.inputElement.attr('checked', listingData['precise'] === 'yes');
        }
    }, {
        key: 'getData',
        value: function getData() {
            var _this3 = this;

            var data = {};
            Object.keys(this._directMappingInputs).forEach(function (key) {
                data[key] = _this3._directMappingInputs[key].val();
            });
            if (this._inputDestroyed.inputElement.is(':checked')) {
                data['status'] = 'destroyed';
            } else {
                data['status'] = '';
            }
            if (this._inputPrecise.inputElement.is(':checked')) {
                data['precise'] = 'yes';
            } else {
                data['precise'] = 'no';
            }
            data['link'] = ValidationUtils.normalizeUrl(data['link']);
            data['linkextra'] = ValidationUtils.normalizeUrl(data['linkextra']);
            data['style'] = this._inputStyle.inputElement.val();
            return data;
        }
    }, {
        key: 'getObjectName',
        value: function getObjectName() {
            return this._inputObjectName.inputElement.val();
        }
    }, {
        key: 'getChangesSummary',
        value: function getChangesSummary() {
            return this._inputChangesSummary.val();
        }
    }, {
        key: 'getChangesIsMinor',
        value: function getChangesIsMinor() {
            return this._inputIsMinorChanges.is(':checked');
        }
    }]);

    return CulturalHeritageKZEditorForm;
}();


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 CulturalEditorKZListingSerializer = function () {
    function CulturalEditorKZListingSerializer() {
        _classCallCheck(this, CulturalEditorKZListingSerializer);
    }

    _createClass(CulturalEditorKZListingSerializer, [{
        key: "serializeListingData",
        value: function serializeListingData(listingData) {
            var listingSerializer = new ListingSerializer('monument kz', listingData);
            listingSerializer.writeListingStart();
            listingSerializer.writeParametersLine(["type", "status"]);
            listingSerializer.writeParametersLine(["lat", "long", "precise"]);
            listingSerializer.writeParameterLine("name");
            listingSerializer.writeParameterLine("knid");
            listingSerializer.writeParameterLine("complex", true);
            listingSerializer.writeParametersLine(["region", "district"]);
            listingSerializer.writeParametersLine(["municipality", "munid"]);
            listingSerializer.writeParameterLine("block", true);
            listingSerializer.writeParameterLine("address");
            listingSerializer.writeParametersLine(["year", "author"]);
            listingSerializer.writeParameterLine("style", true);
            listingSerializer.writeParameterLine("description");
            listingSerializer.writeParameterLine("image");
            listingSerializer.writeParameterLine("wdid");
            listingSerializer.writeParameterLine("wiki");
            listingSerializer.writeParameterLine("commonscat");
            listingSerializer.writeParameterLine("link");
            listingSerializer.writeParameterLine("linkextra", true);
            listingSerializer.writeParameterLine("document", true);
            listingSerializer.writeParameterLine("doc", true);
            listingSerializer.writeParameterLine("style", true);
            listingSerializer.writeParameterLine("protection", true);
            listingSerializer.writeParameterLine("dismissed", true);
            listingSerializer.writeOtherNonEmptyParameters();
            listingSerializer.writeListingEnd();
            return listingSerializer.getSerializedListing();
        }
    }]);

    return CulturalEditorKZListingSerializer;
}();


var WikitextParser = {
    /**
     * Given a listing index, return the full wikitext for that listing
     * ("{{listing|key=value|...}}"). An index of 0 returns the first listing
     * template invocation, 1 returns the second, etc.
     */
    getListingWikitextBraces: function getListingWikitextBraces(wikitext, listingName, listingIndex) {
        // find the listing wikitext that matches the same index as the listing index
        var listingRegex = new RegExp('{{\\s*(' + listingName + ')(\\s|\\|)', 'ig');
        // look through all matches for "{{listing|see|do...}}" within the section
        // wikitext, returning the nth match, where 'n' is equal to the index of the
        // edit link that was clicked
        var listingSyntax = void 0,
            regexResult = void 0,
            listingMatchIndex = void 0;
        for (var i = 0; i <= listingIndex; i++) {
            regexResult = listingRegex.exec(wikitext);
            listingMatchIndex = regexResult.index;
            listingSyntax = regexResult[0];
        }
        // listings may contain nested templates, so step through all section
        // text after the matched text to find MATCHING closing braces
        // the first two braces are matched by the listing regex and already
        // captured in the listingSyntax variable
        var curlyBraceCount = 2;
        var endPos = wikitext.length;
        var startPos = listingMatchIndex + listingSyntax.length;
        var matchFound = false;
        for (var j = startPos; j < endPos; j++) {
            if (wikitext[j] === '{') {
                ++curlyBraceCount;
            } else if (wikitext[j] === '}') {
                --curlyBraceCount;
            }
            if (curlyBraceCount === 0 && j + 1 < endPos) {
                listingSyntax = wikitext.substring(listingMatchIndex, j + 1);
                matchFound = true;
                break;
            }
        }
        if (!matchFound) {
            listingSyntax = wikitext.substring(listingMatchIndex);
        }
        return StringUtils.trim(listingSyntax);
    },


    /**
     * Convert raw wiki listing syntax into a mapping of key-value pairs
     * corresponding to the listing template parameters.
     */
    wikiTextToListing: function wikiTextToListing(wikitext) {
        // remove the trailing braces if there are some
        for (var i = 0; i < 2; i++) {
            if (wikitext[wikitext.length - 1] === '}') {
                wikitext = wikitext.slice(0, -1);
            }
        }

        var listingData = {};
        var lastKey = void 0;
        var listParams = WikitextParser.listingTemplateToParamsArray(wikitext);
        for (var _i = 1; _i < listParams.length; _i++) {
            var param = listParams[_i];
            var index = param.indexOf('=');
            if (index > 0) {
                // param is of the form key=value
                var key = StringUtils.trim(param.substr(0, index));
                listingData[key] = StringUtils.trim(param.substr(index + 1));
                lastKey = key;
            } else if (listingData[lastKey].length) {
                // there was a pipe character within a param value, such as
                // "key=value1|value2", so just append to the previous param
                listingData[lastKey] += '|' + param;
            }
        }

        return listingData;
    },


    /**
     * Split the raw template wikitext into an array of params. The pipe
     * symbol delimits template params, but this method will also inspect the
     * description to deal with nested templates or wikilinks that might contain
     * pipe characters that should not be used as delimiters.
     */
    listingTemplateToParamsArray: function listingTemplateToParamsArray(wikitext) {
        var results = [];
        var paramValue = '';
        var pos = 0;
        while (pos < wikitext.length) {
            var remainingString = wikitext.substr(pos);
            // check for a nested template or wikilink
            var patternMatch = WikitextParser.findPatternMatch(remainingString, "{{", "}}");
            if (patternMatch.length === 0) {
                patternMatch = WikitextParser.findPatternMatch(remainingString, "[[", "]]");
            }
            if (patternMatch.length > 0) {
                paramValue += patternMatch;
                pos += patternMatch.length;
            } else if (wikitext.charAt(pos) === '|') {
                // delimiter - push the previous param and move on to the next
                results.push(paramValue);
                paramValue = '';
                pos++;
            } else {
                // append the character to the param value being built
                paramValue += wikitext.charAt(pos);
                pos++;
            }
        }
        if (paramValue.length > 0) {
            // append the last param value
            results.push(paramValue);
        }
        return results;
    },


    /**
     * Utility method for finding a matching end pattern for a specified start
     * pattern, including nesting.  The specified value must start with the
     * start value, otherwise an empty string will be returned.
     */
    findPatternMatch: function findPatternMatch(value, startPattern, endPattern) {
        var matchString = '';
        var startRegex = new RegExp('^' + WikitextParser.replaceSpecial(startPattern), 'i');
        if (startRegex.test(value)) {
            var endRegex = new RegExp('^' + WikitextParser.replaceSpecial(endPattern), 'i');
            var matchCount = 1;
            for (var i = startPattern.length; i < value.length; i++) {
                var remainingValue = value.substr(i);
                if (startRegex.test(remainingValue)) {
                    matchCount++;
                } else if (endRegex.test(remainingValue)) {
                    matchCount--;
                }
                if (matchCount === 0) {
                    matchString = value.substr(0, i);
                    break;
                }
            }
        }
        return matchString;
    },
    replaceSpecial: function replaceSpecial(str) {
        return str.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&");
    }
};


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 WikitextSectionEditor = function () {
    function WikitextSectionEditor(sectionText, listingName) {
        _classCallCheck(this, WikitextSectionEditor);

        this._sectionText = sectionText;
        this._listingName = listingName;
        this._commentReplacements = {};
        this._stripComments();
    }

    _createClass(WikitextSectionEditor, [{
        key: 'getListingData',
        value: function getListingData(listingIndex) {
            var listingText = WikitextParser.getListingWikitextBraces(this._sectionText, this._listingName, listingIndex);
            var listingData = WikitextParser.wikiTextToListing(listingText);
            for (var key in listingData) {
                if (!listingData.hasOwnProperty(key)) {
                    continue;
                }
                listingData[key] = this._restoreComments(listingData[key]);
            }
            return listingData;
        }
    }, {
        key: 'getSectionTextWithReplacedListing',
        value: function getSectionTextWithReplacedListing(listingIndex, newListingText) {
            var oldListingText = WikitextParser.getListingWikitextBraces(this._sectionText, this._listingName, listingIndex);
            var result = this._sectionText;
            result = result.replace(oldListingText, newListingText);
            result = this._restoreComments(result);
            return result;
        }
    }, {
        key: 'getSectionTextWithAddedListing',
        value: function getSectionTextWithAddedListing(newListingText) {
            var result = this._sectionText;

            var index = result.indexOf('{{footer');

            if (index > 0) {
                result = result.substr(0, index) + '\n' + newListingText + '\n' + result.substr(index);
            } else {
                result += '\n' + newListingText;
            }

            result = this._restoreComments(result);

            return result;
        }

        /**
         * Commented-out listings can result in the wrong listing being edited, so
         * strip out any comments and replace them with placeholders that can be
         * restored prior to saving changes.
         */

    }, {
        key: '_stripComments',
        value: function _stripComments() {
            var comments = this._sectionText.match(/<!--[\s\S]*?-->/mig);
            if (comments !== null) {
                for (var i = 0; i < comments.length; i++) {
                    var comment = comments[i];
                    var rep = '<<<COMMENT' + i + '>>>';
                    this._sectionText = this._sectionText.replace(comment, rep);
                    this._commentReplacements[rep] = comment;
                }
            }
        }

        /**
         * Search the text provided, and if it contains any text that was
         * previously stripped out for replacement purposes, restore it.
         */

    }, {
        key: '_restoreComments',
        value: function _restoreComments(sectionText) {
            for (var key in this._commentReplacements) {
                var val = this._commentReplacements[key];
                sectionText = sectionText.replace(key, val);
            }
            return sectionText;
        }
    }]);

    return WikitextSectionEditor;
}();


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 ListingSerializer = function () {
    function ListingSerializer(listingType, listingData) {
        _classCallCheck(this, ListingSerializer);

        this._listingType = listingType;
        this._listingData = listingData;
        this._serializedListing = '';
        this._serializedParameters = [];
    }

    _createClass(ListingSerializer, [{
        key: 'writeListingStart',
        value: function writeListingStart(addNewline) {
            this._serializedListing += '{{' + this._listingType;
            if (addNewline) {
                this._serializedListing += "\n";
            } else {
                this._serializedListing += ' ';
            }
        }
    }, {
        key: 'writeParameterLine',
        value: function writeParameterLine(parameterName, optional) {
            var parameterValue = this._listingData[parameterName];
            if (optional && (parameterValue === '' || parameterValue === undefined)) {
                return;
            }
            if (parameterValue === undefined) {
                parameterValue = '';
            }
            this._serializedListing += '|' + parameterName + "=" + parameterValue + "\n";
            this._serializedParameters.push(parameterName);
        }
    }, {
        key: 'writeParametersLine',
        value: function writeParametersLine(parameterNames) {
            for (var i = 0; i < parameterNames.length; i++) {
                var parameterName = parameterNames[i];
                var parameterValue = this._listingData[parameterName];
                if (parameterValue === undefined) {
                    parameterValue = '';
                }
                if (i > 0) {
                    this._serializedListing += " ";
                }
                this._serializedListing += "|" + parameterName + "=" + parameterValue;
                this._serializedParameters.push(parameterName);
            }
            this._serializedListing += "\n";
        }
    }, {
        key: 'writeOtherNonEmptyParameters',
        value: function writeOtherNonEmptyParameters() {
            for (var parameterName in this._listingData) {
                if (!this._listingData.hasOwnProperty(parameterName)) {
                    continue;
                }

                if (!ArrayUtils.hasElement(this._serializedParameters, parameterName)) {
                    var parameterValue = this._listingData[parameterName];
                    if (parameterValue !== '' && parameterValue !== undefined) {
                        this._serializedListing += '|' + parameterName + "=" + parameterValue + "\n";
                    }
                }
            }
        }
    }, {
        key: 'writeListingEnd',
        value: function writeListingEnd() {
            this._serializedListing += '}}';
        }
    }, {
        key: 'getSerializedListing',
        value: function getSerializedListing() {
            return this._serializedListing;
        }
    }]);

    return ListingSerializer;
}();


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 SavingForm = function () {
    function SavingForm() {
        _classCallCheck(this, SavingForm);

        this._progress = $('<div id="progress-dialog">' + 'Сохранение...' + '</div>');
        this._progress.dialog({
            modal: true,
            height: 100,
            width: 300,
            title: ''
        });
        $(".ui-dialog-titlebar").hide();
    }

    _createClass(SavingForm, [{
        key: 'destroy',
        value: function destroy() {
            this._progress.dialog('destroy').remove();
        }
    }]);

    return SavingForm;
}();


InputInsertSymbols = {
    addQuotesInsertHandler: function addQuotesInsertHandler(insertButton, insertToInput) {
        insertButton.click(function () {
            var selectionStart = insertToInput[0].selectionStart;
            var selectionEnd = insertToInput[0].selectionEnd;
            var oldValue = insertToInput.val();
            var newValue = oldValue.substring(0, selectionStart) + "«" + oldValue.substring(selectionStart, selectionEnd) + "»" + oldValue.substring(selectionEnd);
            insertToInput.val(newValue);
            InputInsertSymbols._selectRange(insertToInput[0], selectionStart + 1, selectionEnd + 1);
        });
    },
    addDashInsertHandler: function addDashInsertHandler(insertButton, insertToInput) {
        insertButton.click(function () {
            var caretPos = insertToInput[0].selectionStart;
            var oldValue = insertToInput.val();
            var newValue = oldValue.substring(0, caretPos) + "—" + oldValue.substring(caretPos);
            insertToInput.val(newValue);
            InputInsertSymbols._selectRange(insertToInput[0], caretPos + 1);
        });
    },
    _selectRange: function _selectRange(element, start, end) {
        if (end === undefined) {
            end = start;
        }
        element.focus();
        if ('selectionStart' in element) {
            element.selectionStart = start;
            element.selectionEnd = end;
        } else if (element.setSelectionRange) {
            element.setSelectionRange(start, end);
        } else if (element.createTextRange) {
            var range = element.createTextRange();
            range.collapse(true);
            range.moveEnd('character', end);
            range.moveStart('character', start);
            range.select();
        }
    }
};


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);
    }
};


var CommonsImagesLoader = {
    loadImagesFromWLMCategory: function loadImagesFromWLMCategory(knid, onSuccess) {
        var _this = this;

        if (!knid) {
            onSuccess([]);
        } else {
            CommonsApi.getCategoryImages('WLM/' + knid, 'max', function (images) {
                return _this.loadImages(images, 'wlm', onSuccess);
            });
        }
    },

    loadImagesFromWLECategory: function loadImagesFromWLECategory(knid, onSuccess) {
        var _this2 = this;

        if (!knid) {
            onSuccess([]);
        } else {
            CommonsApi.getCategoryImages('Protected_areas_of_Russia/' + knid, 'max', function (images) {
                return _this2.loadImages(images, 'wlm', onSuccess);
            });
        }
    },

    loadImagesFromCommonsCategory: function loadImagesFromCommonsCategory(commonsCat, onSuccess) {
        var _this3 = this;

        if (!commonsCat) {
            onSuccess([]);
        } else {
            CommonsApi.getCategoryImages(commonsCat, 'max', function (images) {
                return _this3.loadImages(images, 'commons', onSuccess);
            });
        }
    },

    loadImages: function loadImages(images, categoryType, onSuccess) {
        CommonsApi.getImagesInfo(images, onSuccess);
    }
};


var CommonsImagesSelectDialog = {
    showDialog: function showDialog(knidWLM, knidWLE, commonsCat, onImageSelected) {
        var dialogElement = $('<div>');
        dialogElement.dialog({
            modal: true,
            height: 400,
            width: 800,
            title: 'Выбор изображения из галереи'
        });

        var loadingElement = $('<div>', { 'html': 'загрузка...' });
        var contentElement = $('<div>');
        dialogElement.append(contentElement);
        dialogElement.append(loadingElement);

        function createImageElement(image) {
            var imageThumbElement = $('<img>', { 'alt': 'Image', 'src': image.thumb });
            var commonsUrl = 'https://commons.wikimedia.org/wiki/' + image.image;
            var selectLink = $('<a>', {
                href: 'javascript:;',
                html: '[выбрать]'
            });
            var viewLink = $('<a>', {
                href: commonsUrl,
                target: '_blank',
                html: '[смотреть]'
            });

            selectLink.click(function () {
                var imageName = image.image.replace(/^File:/, '').replace(' ', '_');
                onImageSelected(imageName);
                dialogElement.dialog('destroy');
            });

            var imageBlock = $('<div>', {
                style: 'padding: 5px; width: 210px; display: flex; flex-direction: column;' + 'justify-content: center; align-items: center; align-content: center;'
            });
            imageBlock.append(imageThumbElement);
            imageBlock.append(selectLink);
            imageBlock.append(viewLink);
            return imageBlock;
        }

        function createImagesBlock(blockTitle, images) {
            var block = $('<div>');
            block.append($('<h5>', { 'html': blockTitle }));

            var currentRow = null;
            var imagesInRow = 0;

            function addImage(image) {
                if (!currentRow || imagesInRow >= 4) {
                    currentRow = $('<div>', {
                        style: 'display: flex; flex-direction: row'
                    });
                    block.append(currentRow);
                    imagesInRow = 0;
                }

                currentRow.append(createImageElement(image));
                imagesInRow++;
            }

            images.forEach(function (image) {
                addImage(image);
            });

            return block;
        }

        CommonsImagesLoader.loadImagesFromWLMCategory(knidWLM, function (wlmImages) {
            if (wlmImages.length > 0) {
                contentElement.append(createImagesBlock("Изображения из категории WLM", wlmImages));
            }

            CommonsImagesLoader.loadImagesFromWLECategory(knidWLE, function (wleImages) {
                if (wleImages.length > 0) {
                    contentElement.append(createImagesBlock("Изображения из категории WLE", wleImages));
                }

                CommonsImagesLoader.loadImagesFromCommonsCategory(commonsCat, function (commonsCatImages) {
                    if (commonsCatImages.length > 0) {
                        contentElement.append(createImagesBlock("Изображения из категории Commons", commonsCatImages));
                    }
                    if (wlmImages.length === 0 && wleImages.length === 0 && commonsCatImages.length === 0) {
                        contentElement.append($('<div>', { 'html': "Для данного объекта нет ни одного изображения" }));
                    }
                    loadingElement.hide();
                });
            });
        });
    }
};


function initListingEditor(_ref) {
    var listingPageNamespace = _ref.listingPageNamespace,
        formClass = _ref.formClass,
        listingSerializerClass = _ref.listingSerializerClass,
        listingTemplateName = _ref.listingTemplateName,
        helpPageUrl = _ref.helpPageUrl;

    function isListingPage() {
        return StringUtils.contains(MediaWikiPage.getPageName(), listingPageNamespace);
    }

    if (!ListingEditorUtils.isEditablePage() || !isListingPage()) {
        return;
    }

    function showHelp() {
        window.open(helpPageUrl);
    }

    function showListingEditorDialogAdd(sectionIndex, sectionWikitext) {
        var sectionEditor = new WikitextSectionEditor(sectionWikitext, listingTemplateName);
        var form = new formClass();

        function onFormSubmit(captchaId, captchaAnswer) {
            var listingSerializer = new listingSerializerClass();
            var newListingText = listingSerializer.serializeListingData(form.getData());
            var updatedWikitext = sectionEditor.getSectionTextWithAddedListing(newListingText);
            var changesSummary = composeChangesSummary(sectionWikitext, form, commonMessages.changesSummaryAdded);
            var savingForm = new SavingForm();
            MediaWikiPageWikitext.saveSectionWikitext(sectionIndex, updatedWikitext, changesSummary, form.getChangesIsMinor(), captchaId, captchaAnswer,
            /*onSuccess=*/function () {
                window.location.reload();
            },
            /*onFailure*/function (message) {
                savingForm.destroy();
                alert(message);
            },
            /*onCaptcha*/function (captchaImgSrc, captchaId) {
                savingForm.destroy();
                new CaptchaDialog(captchaImgSrc, function (captchaAnswer) {
                    onFormSubmit(captchaId, captchaAnswer);
                });
            });
        }

        ListingEditorDialog.showDialog(form.getForm().formElement, commonMessages.addTitle,
        /*onSubmit=*/onFormSubmit,
        /*onCancel=*/function () {
            form.getForm().formElement.dialog('destroy').remove();
        },
        /*onHelp=*/showHelp);
    }

    function showListingEditorDialogEdit(sectionIndex, listingIndex, sectionWikitext) {
        var sectionEditor = new WikitextSectionEditor(sectionWikitext, listingTemplateName);
        var listingData = sectionEditor.getListingData(listingIndex);
        var form = new formClass();
        form.setData(listingData);

        function onFormSubmit(captchaId, captchaAnswer) {
            var listingSerializer = new listingSerializerClass();
            var newListingText = listingSerializer.serializeListingData(ObjectUtils.merge(listingData, form.getData()));
            var updatedWikitext = sectionEditor.getSectionTextWithReplacedListing(listingIndex, newListingText);
            var changesSummary = composeChangesSummary(sectionWikitext, form, commonMessages.changesSummaryUpdated);
            var savingForm = new SavingForm();
            MediaWikiPageWikitext.saveSectionWikitext(sectionIndex, updatedWikitext, changesSummary, form.getChangesIsMinor(), captchaId, captchaAnswer,
            /*onSuccess=*/function () {
                window.location.reload();
            },
            /*onFailure*/function (message) {
                savingForm.destroy();
                alert(message);
            },
            /*onCaptcha*/function (captchaImgSrc, captchaId) {
                savingForm.destroy();
                new CaptchaDialog(captchaImgSrc, function (captchaAnswer) {
                    onFormSubmit(captchaId, captchaAnswer);
                });
            });
        }

        ListingEditorDialog.showDialog(form.getForm().formElement, commonMessages.editTitle,
        /*onSubmit=*/onFormSubmit,
        /*onCancel=*/function () {
            form.getForm().formElement.dialog('destroy').remove();
        },
        /*onHelp=*/showHelp);
    }

    function composeChangesSummary(sectionWikitext, form, changesType) {
        var changesSummary = getChangesSummarySection(sectionWikitext);
        changesSummary += changesType + " " + form.getObjectName();
        var userChangesSummary = StringUtils.trim(form.getChangesSummary());
        if (userChangesSummary.length > 0) {
            changesSummary += " - " + userChangesSummary;
        }
        return changesSummary;
    }

    function onAddNewListing(sectionIndex) {
        MediaWikiPageWikitext.loadSectionWikitext(sectionIndex, function (wikitext) {
            return showListingEditorDialogAdd(sectionIndex, wikitext);
        });
    }

    function onEditListing(sectionIndex, listingIndex) {
        MediaWikiPageWikitext.loadSectionWikitext(sectionIndex, function (wikitext) {
            return showListingEditorDialogEdit(sectionIndex, listingIndex, wikitext);
        });
    }

    function getChangesSummarySection(sectionWikitext) {
        var sectionName = MediaWikiPageWikitext.getSectionName(sectionWikitext);
        return sectionName.length ? '/* ' + sectionName + ' */ ' : "";
    }

    var listingPageElements = ListingEditorUtils.getListingPageElements();

    var sections = listingPageElements.getSections();
    for (var i = 0; i < sections.length; i++) {
        var addButton = ListingEditorButtons.createListingAddButton(sections[i]);
        addButton.click(function (section) {
            onAddNewListing(section.getSectionIndex());
        });
    }

    var listingTables = listingPageElements.getListingTables();
    for (var _i = 0; _i < listingTables.length; _i++) {
        var editButton = ListingEditorButtons.createListingEditButton(listingTables[_i]);
        editButton.click(function (listingTable) {
            onEditListing(listingTable.getSectionIndex(), listingTable.getListingIndex());
        });
    }
}


function culturalHeritageKZEditorMain() {
    var helpPageUrl = "https://ru.wikivoyage.org/wiki/%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%9A%D1%83%D0%BB%D1%8C%D1%82%D1%83%D1%80%D0%BD%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%B8%D0%B5_%D0%9A%D0%B0%D0%B7%D0%B0%D1%85%D1%81%D1%82%D0%B0%D0%BD%D0%B0";

    initListingEditor({
        listingPageNamespace: 'Культурное_наследие_Казахстана',
        formClass: CulturalHeritageKZEditorForm,
        listingSerializerClass: CulturalEditorKZListingSerializer,
        listingTemplateName: "monument kz",
        helpPageUrl: helpPageUrl
    });
}

culturalHeritageKZEditorMain();

});