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

  • Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
  • Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
  • Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
  • Opera: Нажмите Ctrl+F5.
/******************************************************************  Russian Cultural Heritage Listing Editor.   Forked from "Listing Editor v2.1"  - Original author: torty3  - Additional contributors: Andyrom75, Wrh2, AlexeyBaturin  ********************************************************************/ //<nowiki> mw.loader.using(['mediawiki.api'], function() {     'use strict';      var CulturalHeritageListingEditor = {};      var StringUtils = {         contains: function(string, substring) {             return string.indexOf(substring) >= 0;         }     };      var monumentListingParameterDescriptors = [         {             id: 'name'         },         {             id: 'type',             possibleValues: [                 {                     value: 'architecture',                     title: 'памятник архитектуры'                 },                 {                     value: 'history',                     title: 'памятник истории'                 },                 {                     value: 'monument',                     title: 'памятник монументального искусства'                 },                 {                     value: 'archeology',                     title: 'памятник археологии'                 },                 {                     value: 'settlement',                     title: 'историческое поселение'                 }             ]         },         {             id: 'style',             possibleValues: [                 {                     value: '',                     title: ''                 },                 {                     value: 'конструктивизм',                     title: 'конструктивизм'                 },                 {                     value: 'модерн',                     title: 'модерн'                 }             ]         },         {             id: 'status'         },         {             id: 'region',             possibleValues: [                 {                     value: "",                     title: "не задан"                 },                 {                     value: "ru-ad",                     title: "Адыгея"                 },                 {                     value: "ru-ba",                     title: "Башкортостан"                 },                 {                     value: "ru-bu",                     title: "Бурятия"                 },                 {                     value: "ru-al",                     title: "Алтай"                 },                 {                     value: "ru-da",                     title: "Дагестан"                 },                 {                     value: "ru-in",                     title: "Ингушетия"                 },                 {                     value: "ru-kb",                     title: "Кабардино-Балкария"                 },                 {                     value: "ru-kl",                     title: "Калмыкия"                 },                 {                     value: "ru-kc",                     title: "Карачаево-Черкесия"                 },                 {                     value: "ru-krl",                     title: "Карелия"                 },                 {                     value: "ru-ko",                     title: "республика Коми"                 },                 {                     value: "ru-me",                     title: "Марий Эл"                 },                 {                     value: "ru-mo",                     title: "Мордовия"                 },                 {                     value: "ru-sa",                     title: "Якутия (Саха)"                 },                 {                     value: "ru-se",                     title: "Северная Осетия"                 },                 {                     value: "ru-ta",                     title: "Татарстан"                 },                 {                     value: "ru-ty",                     title: "Тува"                 },                 {                     value: "ru-ud",                     title: "Удмуртия"                 },                 {                     value: "ru-kk",                     title: "Хакасия"                 },                 {                     value: "ru-ce",                     title: "Чеченская республика"                 },                 {                     value: "ru-chv",                     title: "Чувашия"                 },                 {                     value: "ru-alt",                     title: "Алтайский край"                 },                 {                     value: "ru-kda",                     title: "Краснодарский край"                 },                 {                     value: "ru-kya",                     title: "Красноярский край"                 },                 {                     value: "ru-pri",                     title: "Приморский край"                 },                 {                     value: "ru-sta",                     title: "Ставропольский край"                 },                 {                     value: "ru-kha",                     title: "Хабаровский край"                 },                 {                     value: "ru-amu",                     title: "Амурская область"                 },                 {                     value: "ru-ark",                     title: "Архангельская область"                 },                 {                     value: "ru-ast",                     title: "Астраханская область"                 },                 {                     value: "ru-bel",                     title: "Белгородская область"                 },                 {                     value: "ru-bry",                     title: "Брянская область"                 },                 {                     value: "ru-vla",                     title: "Владимирская область"                 },                 {                     value: "ru-vgg",                     title: "Волгоградская область"                 },                 {                     value: "ru-vol",                     title: "Вологодская область"                 },                 {                     value: "ru-vor",                     title: "Воронежская область"                 },                 {                     value: "ru-iva",                     title: "Ивановская область"                 },                 {                     value: "ru-irk",                     title: "Иркутская область"                 },                 {                     value: "ru-kal",                     title: "Калининградская область"                 },                 {                     value: "ru-klu",                     title: "Калужская область"                 },                 {                     value: "ru-kam",                     title: "Камчатский край"                 },                 {                     value: "ru-kem",                     title: "Кемеровская область"                 },                 {                     value: "ru-kir",                     title: "Кировская область"                 },                 {                     value: "ru-kos",                     title: "Костромская область"                 },                 {                     value: "ru-kgn",                     title: "Курганская область"                 },                 {                     value: "ru-krs",                     title: "Курская область"                 },                 {                     value: "ru-len",                     title: "Ленинградская область"                 },                 {                     value: "ru-lip",                     title: "Липецкая область"                 },                 {                     value: "ru-mag",                     title: "Магаданская область"                 },                 {                     value: "ru-mos",                     title: "Московская область"                 },                 {                     value: "ru-mur",                     title: "Мурманская область"                 },                 {                     value: "ru-niz",                     title: "Нижегородская область"                 },                 {                     value: "ru-ngr",                     title: "Новгородская область"                 },                 {                     value: "ru-nvs",                     title: "Новосибирская область"                 },                 {                     value: "ru-oms",                     title: "Омская область"                 },                 {                     value: "ru-ore",                     title: "Оренбургская область"                 },                 {                     value: "ru-orl",                     title: "Орловская область"                 },                 {                     value: "ru-pnz",                     title: "Пензенская область"                 },                 {                     value: "ru-per",                     title: "Пермский край"                 },                 {                     value: "ru-psk",                     title: "Псковская область"                 },                 {                     value: "ru-ros",                     title: "Ростовская область"                 },                 {                     value: "ru-rya",                     title: "Рязанская область"                 },                 {                     value: "ru-sam",                     title: "Самарская область"                 },                 {                     value: "ru-sar",                     title: "Саратовская область"                 },                 {                     value: "ru-sak",                     title: "Сахалинская область"                 },                 {                     value: "ru-sve",                     title: "Свердловская область"                 },                 {                     value: "ru-smo",                     title: "Смоленская область"                 },                 {                     value: "ru-tam",                     title: "Тамбовская область"                 },                 {                     value: "ru-tve",                     title: "Тверская область"                 },                 {                     value: "ru-tom",                     title: "Томская область"                 },                 {                     value: "ru-tul",                     title: "Тульская область"                 },                 {                     value: "ru-tyu",                     title: "Тюменская область"                 },                 {                     value: "ru-uly",                     title: "Ульяновская область"                 },                 {                     value: "ru-che",                     title: "Челябинская область"                 },                 {                     value: "ru-zab",                     title: "Забайкальский край"                 },                 {                     value: "ru-yar",                     title: "Ярославская область"                 },                 {                     value: "ru-mow",                     title: "Москва"                 },                 {                     value: "ru-spb",                     title: "Санкт-Петербург"                 },                 {                     value: "ru-jew",                     title: "Еврейская автономная область"                 },                 {                     value: "ru-km",                     title: "Крым"                 },                 {                     value: "ru-nen",                     title: "Ненецкий автономный округ"                 },                 {                     value: "ru-khm",                     title: "Ханты-Мансийский автономный округ"                 },                 {                     value: "ru-chu",                     title: "Чукотский автономный округ"                 },                 {                     value: "ru-yam",                     title: "Ямало-Ненецкий автономный округ"                 },                 {                     value: "ru-sev",                     title: "Севастополь"                 }             ]         },         {             id: 'district'         },         {             id: 'municipality'         },         {             id: 'block'         },         {             id: 'address'         },         {             id: 'lat'         },         {             id: 'long'         },         {             id: 'precise'         },         {             id: 'year'         },         {             id: 'author'         },         {             id: 'knid'         },         {             id: 'complex'         },         {             id: 'knid-new'         },         {             id: 'uid'         },         {             id: 'image'         },         {             id: 'wiki'         },         {             id: 'wdid'         },         {             id: 'commonscat'         },         {             id: 'munid'         },         {             id: 'document'         },         {             id: 'link'         },         {             id: 'linkextra'         },         {             id: 'description'         },         {             id: 'protection',             possibleValues: [                 {                     value: '',                     title: ''                 },                 {                     value: 'Ф',                     title: 'федеральная'                 },                 {                     value: 'Р',                     title: 'региональная'                 },                 {                     value: 'М',                     title: 'местная'                 },                 {                     value: 'В',                     title: 'выявленный объект'                 },                 {                     value: 'Ц',                     title: 'градоформирующий объект'                 },                 {                     value: 'Н',                     title: 'не охраняется'                 }             ]         },     ];      function createTemplateParameters(parameterDescriptors) {         var parametersById = {};         var parameterIds = [];          for (var i = 0; i < parameterDescriptors.length; i++) {             var parameterData = parameterDescriptors[i];             var parameterId = parameterData.id;             parametersById [parameterId] = parameterData;             parameterIds.push(parameterId);         }          return {             _parametersById: parametersById,             _parameterIds: parameterIds,              getParameter: function (parameterId) {                 return this._parametersById[parameterId];             }         };     }      var monumentListingParameters = createTemplateParameters(monumentListingParameterDescriptors);      function arrayHasElement(array, element) {         return array.indexOf(element) >= 0;     }      function inArray(element, array) {         return arrayHasElement(array, element);     }      function createListingSerializer(listingType, listingParameters, listingData) {         return {             _data: '',             _serializedParameters: [],              writeListingStart: function(addNewline) {                 this._data += '{{' + listingType;                 if (addNewline) {                     this._data += "\n";                 } else {                     this._data += ' ';                 }             },              writeParameterLine: function(parameterName, optional) {                 var parameterValue = listingData[parameterName];                 if (optional && (parameterValue === '' || parameterValue === undefined)) {                     return;                 }                 if (parameterValue === undefined) {                     parameterValue = '';                 }                 this._data += '|' + parameterName + "= " + parameterValue + "\n";                 this._serializedParameters.push(parameterName);             },              writeParametersLine: function(parameterNames, optionalParameters) {                 for (var i = 0; i < parameterNames.length; i++) {                     var parameterName = parameterNames[i];                     var parameterValue = listingData[parameterName];                     var optional = optionalParameters && inArray(parameterName, optionalParameters);                     if (optional && (parameterValue === '' || parameterValue === undefined)) {                         continue;                     }                      if (parameterValue === undefined) {                         parameterValue = '';                     }                     if (i > 0) {                         this._data += " ";                     }                     this._data += "|" + parameterName + "= " + parameterValue;                     this._serializedParameters.push(parameterName);                 }                 this._data += "\n";             },              writeOtherNonEmptyParameters: function() {                 for (var parameterName in listingData) {                     if (listingData.hasOwnProperty(parameterName)) {                         if (!arrayHasElement(this._serializedParameters, parameterName)) {                             var parameterValue = listingData[parameterName];                             if (parameterValue !== '' && parameterValue !== undefined) {                                 this._data += '|' + parameterName + "= " + parameterValue + "\n"                             }                         }                     }                 }             },              writeListingEnd: function() {                 this._data += '}}';             },              getSerializedListing: function() {                 return this._data;             }         };     }      function serializeMonumentListing(listingData) {         var serializer = createListingSerializer("monument", monumentListingParameterDescriptors, listingData);         serializer.writeListingStart();         serializer.writeParametersLine(["type", "status"]);         serializer.writeParametersLine(["lat", "long", "precise"]);         serializer.writeParameterLine("name");         serializer.writeParametersLine(["knid", "complex"], ["complex"]);         serializer.writeParameterLine("knid-new");         serializer.writeParameterLine("uid", true);         serializer.writeParametersLine(["region", "district"]);         serializer.writeParametersLine(["municipality", "munid"]);         serializer.writeParameterLine("block", true);         serializer.writeParameterLine("address");         serializer.writeParametersLine(["year", "author"]);         serializer.writeParameterLine("style", true);         serializer.writeParameterLine("description");         serializer.writeParameterLine("image");         serializer.writeParameterLine("wdid");         serializer.writeParameterLine("wiki");         serializer.writeParametersLine(["commonscat", "protection"], ["protection"]);         serializer.writeParameterLine("link");         serializer.writeParameterLine("linkextra", true);         serializer.writeParameterLine("sobory", true);         serializer.writeParameterLine("temples", true);         serializer.writeParameterLine("document", true);         serializer.writeParameterLine("doc", true);         serializer.writeParameterLine("dismissed", true);         serializer.writeOtherNonEmptyParameters();         serializer.writeListingEnd();          return serializer.getSerializedListing();     }      var InputInsertSymbols = {         addQuotesInsertHandler: function(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(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(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();             }         }     };      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() {                         runSequence(functions.slice(1), onSuccess, results)                     },                     0                 );             });         } else {             onSuccess(results);         }     }      var CommonsApi = {         baseUrl: 'https://commons.wikimedia.org/w/api.php',          executeRequest: function(parameters, onSuccess) {             $.ajax({                 url: this.baseUrl,                 data: parameters,                 crossDomain: true,                 dataType: 'jsonp'             }).done(function(data) {                 onSuccess(data);             });         },          getCategoryFiles: function(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(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' || extension === '.tif' || extension === 'tiff' || extension === 'webp') {                         images.push(file);                     }                 });                 onSucess(images);             })         },          getImageInfo: function(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(images, onSuccess) {             var self = this;             runSequence(                 images.map(function(image) {                     return function(onSuccess) {                         self.getImageInfo(image, onSuccess);                     }                 }),                 function(imageInfos) {                     onSuccess(imageInfos);                 }             );         }     };      var ListingEditorFormComposer = {         createInputFormRow: function(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(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(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(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() {             return $('<tr>').append(                 $('<td>', {colspan: "2"}).append(                     $('<div>', {                         'class': "listing-divider",                         style: "margin: 3px 0"                     })                 )             );         },         createRowLink: function(linkText) {             var linkElement = $("<a>", {                 href: 'javascript:;',                 html: linkText             });             var rowElement = $('<tr>').append($('<td>')).append($('<td>').append(linkElement));             return {                 rowElement: rowElement,                 linkElement: linkElement             }         },         createChangesDescriptionRow: function () {             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() {             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()         {             var tableElement = $('<table>', {                 'class': 'editor-fullwidth'             });             var wrapperElement = $('<div>');             wrapperElement.append(tableElement);             return {                 wrapperElement: wrapperElement,                 tableElement: tableElement             };         },         createTableTwoColumns: function()         {             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() {             var formElement = $('<form id="listing-editor">');             formElement.append($('<br>'));             return {                 formElement: formElement             }         }     };      var CommonsImagesLoader = {         loadImagesFromWLMCategory: function(knid, onSuccess) {             var self = this;             if (!knid) {                 onSuccess([]);             } else {                 CommonsApi.getCategoryImages(                     'WLM/' + knid, 'max',                     function (images) {                         self.loadImages(images, 'wlm', onSuccess);                     }                 );             }         },          loadImagesFromCommonsCategory: function(commonsCat, onSuccess) {             var self = this;             if (!commonsCat) {                 onSuccess([]);             } else {                 CommonsApi.getCategoryImages(                     commonsCat, 'max',                     function (images) {                         self.loadImages(images, 'commons', onSuccess);                     }                 );             }         },          loadImages: function (images, categoryType, onSuccess) {             var self = this;             CommonsApi.getImagesInfo(images, function (imagesInfo) {                 onSuccess(imagesInfo);             });         }     };      var CommonsImagesSelectDialog = {         showDialog: function(knid, 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(knid, function(wlmImages) {                 if (wlmImages.length > 0) {                     contentElement.append(createImagesBlock(                         "Изображения из категории WLM", wlmImages                     ));                 }                  CommonsImagesLoader.loadImagesFromCommonsCategory(commonsCat, function (commonsCatImages) {                     if (commonsCatImages.length > 0) {                         contentElement.append(createImagesBlock(                             "Изображения из категории Commons", commonsCatImages                         ));                     }                     if (wlmImages.length === 0 && commonsCatImages.length === 0) {                         contentElement.append(                             $('<div>', {'html': "Для данного объекта нет ни одного изображения"})                         );                     }                     loadingElement.hide();                 });             });         }     };      var MonumentListingEditorFormComposer = {         createForm: function() {             var editorForm = ListingEditorFormComposer.createForm();              var inputObjectName = ListingEditorFormComposer.createInputFormRowText(                 'input-name', 'Название', 'название объекта', false, true             );              var tableObjectName = ListingEditorFormComposer.createTableFullWidth();             tableObjectName.tableElement.append(inputObjectName.rowElement);             tableObjectName.tableElement.append(ListingEditorFormComposer.createRowDivider());             editorForm.formElement.append(tableObjectName.wrapperElement);              var inputType = ListingEditorFormComposer.createInputFormRowSelect(                 'input-type', 'Тип', monumentListingParameters.getParameter('type').possibleValues             );             var inputDestroyed = ListingEditorFormComposer.createInputFormRowCheckbox(                 'input-destroyed', 'Утрачен'             );             var inputRegion = ListingEditorFormComposer.createInputFormRowSelect(                 'input-region', 'Регион', monumentListingParameters.getParameter('region').possibleValues             );             var inputDistrict = ListingEditorFormComposer.createInputFormRowText(                 'input-district', 'Район'             );             var inputMunicipality = ListingEditorFormComposer.createInputFormRowText(                 'input-municipality', 'Населённый пункт'             );             var inputBlock = ListingEditorFormComposer.createInputFormRowText(                 'input-block', 'Квартал'             );             var inputAddress = ListingEditorFormComposer.createInputFormRowText(                 'input-address', 'Адрес', 'улица название, номер дома'             );             var inputYear = ListingEditorFormComposer.createInputFormRowText(                 'input-year', 'Год постройки', 'yyyy', true             );             var inputAuthor = ListingEditorFormComposer.createInputFormRowText(                 'input-author', 'Автор объекта', 'архитектор, скульптор, инженер и т.д.'             );             var inputStyle = ListingEditorFormComposer.createInputFormRowSelect(                 'input-style', 'Стиль', monumentListingParameters.getParameter('style').possibleValues             );             var inputKnid = ListingEditorFormComposer.createInputFormRowText(                 'input-knid', '10-значный № объекта', 'dddddddddd', true             );             var inputComplex = ListingEditorFormComposer.createInputFormRowText(                 'input-complex', '10-значный № комплекса', 'dddddddddd', true             );             var inputKnidNew = ListingEditorFormComposer.createInputFormRowText(                 'input-knid-new', '15-значный № объекта', 'ddddddddddddddd', true             );             var inputUid = ListingEditorFormComposer.createInputFormRowText(                 'input-uid', 'Украинский номер', 'dd-ddd-dddd', true             );             var inputImage = ListingEditorFormComposer.createInputFormRowText(                 'input-image', 'Изображение', 'изображение на Викискладе'             );             var inputWiki = ListingEditorFormComposer.createInputFormRowText(                 'input-wiki', 'Википедия', 'статья в русской Википедии'             );             var inputWdid = ListingEditorFormComposer.createInputFormRowText(                 'input-wdid', 'Викиданные', 'идентификатор Викиданных', true             );             var inputCommonscat = ListingEditorFormComposer.createInputFormRowText(                 'input-commonscat', 'Викисклад', 'категория Викисклада'             );             var inputMunid = ListingEditorFormComposer.createInputFormRowText(                 'input-munid', 'Викиданные нас. пункта', 'идентификатор Викиданных', true             );             var inputDocument = ListingEditorFormComposer.createInputFormRowText(                 'input-document', 'Код документа', 'dDDMMYYYY', true             );             var inputLink = ListingEditorFormComposer.createInputFormRowText(                 'input-link', 'Ссылка №1', 'внешняя ссылка с дополнительной информацией об объекте'             );             var inputLinkExtra = ListingEditorFormComposer.createInputFormRowText(                 'input-linkextra', 'Ссылка №2', 'внешняя ссылка с дополнительной информацией об объекте'             );             var inputSobory = ListingEditorFormComposer.createInputFormRowText(                 'input-sobory', 'sobory.ru', 'идентификатор на sobory.ru'             );             var inputTemples = ListingEditorFormComposer.createInputFormRowText(                 'input-temples', 'temples.ru', 'идентификатор на temples.ru'             );             var inputProtection = ListingEditorFormComposer.createInputFormRowSelect(                 'input-type', 'Категория охраны', monumentListingParameters.getParameter('protection').possibleValues             );              var tableObjectProperties = ListingEditorFormComposer.createTableTwoColumns();              var inputLat = $('<input id="input-lat" placeholder="11.11111" style="width: 80px;">');             var inputLong = $('<input id="input-long" placeholder="111.11111" style="width: 80px;">');             var inputPrecise = $('<input id="input-precise" type="checkbox">');             var coordinatesRow = $('<tr>').append(                 $('<td style="text-align: center;">')                     .attr('colspan', '2')                     .append('Широта:&nbsp;')                     .append(inputLat)                     .append('&nbsp;Долгота:&nbsp;')                     .append(inputLong)                     .append('&nbsp;Точные?&nbsp;')                     .append(inputPrecise)             );               tableObjectProperties.leftTableElement.append(inputType.rowElement);             tableObjectProperties.leftTableElement.append(inputDestroyed.rowElement);             tableObjectProperties.leftTableElement.append(ListingEditorFormComposer.createRowDivider());             tableObjectProperties.leftTableElement.append(inputDistrict.rowElement);             tableObjectProperties.leftTableElement.append(inputMunicipality.rowElement);             tableObjectProperties.leftTableElement.append(inputAddress.rowElement);             tableObjectProperties.leftTableElement.append(coordinatesRow);             tableObjectProperties.leftTableElement.append(ListingEditorFormComposer.createRowDivider());             tableObjectProperties.leftTableElement.append(inputYear.rowElement);             tableObjectProperties.leftTableElement.append(inputAuthor.rowElement);             tableObjectProperties.leftTableElement.append(inputStyle.rowElement);              var selectImageLinkRow = ListingEditorFormComposer.createRowLink('выбрать изображение из галереи');             selectImageLinkRow.linkElement.click(function() {                 CommonsImagesSelectDialog.showDialog(                     inputKnid.inputElement.val(),                     inputCommonscat.inputElement.val(),                     function (selectedImage) {                         inputImage.inputElement.val(selectedImage);                     }                 )             });              tableObjectProperties.rightTableElement.append(inputImage.rowElement);             tableObjectProperties.rightTableElement.append(selectImageLinkRow.rowElement);             tableObjectProperties.rightTableElement.append(inputWiki.rowElement);             tableObjectProperties.rightTableElement.append(inputCommonscat.rowElement);             tableObjectProperties.rightTableElement.append(ListingEditorFormComposer.createRowDivider());             tableObjectProperties.rightTableElement.append(inputLink.rowElement);             tableObjectProperties.rightTableElement.append(inputLinkExtra.rowElement);             tableObjectProperties.rightTableElement.append(inputSobory.rowElement);             tableObjectProperties.rightTableElement.append(inputTemples.rowElement);             tableObjectProperties.rightTableElement.append(ListingEditorFormComposer.createRowDivider());             tableObjectProperties.rightTableElement.append(inputProtection.rowElement);              var showAdditionalParametersLink = $('<a>').text('Показать дополнительные параметры');             var hideAdditionalParametersLink = $('<a>').text('Скрыть дополнительные параметры').hide();             var additionParametersToggleRow = $('<tr>').append(                 $('<td>')                     .attr('colspan', '2')                     .append(showAdditionalParametersLink)                     .append(hideAdditionalParametersLink)             );              tableObjectProperties.rightTableElement.append(ListingEditorFormComposer.createRowDivider());             tableObjectProperties.rightTableElement.append(additionParametersToggleRow);              // Additional parameters             var additionalRows = [                 inputKnid.rowElement,                 inputComplex.rowElement,                 inputKnidNew.rowElement,                 inputUid.rowElement,                 inputWdid.rowElement,                 inputMunid.rowElement,                 inputDocument.rowElement,                 inputRegion.rowElement,                 inputBlock.rowElement             ];              additionalRows.forEach(function(additionalRow) {                 additionalRow.hide();                 tableObjectProperties.rightTableElement.append(additionalRow);             });              function toggleAdditionalParams() {                 additionalRows.forEach(function(additionalRow) {                     additionalRow.toggle();                 });                 showAdditionalParametersLink.toggle();                 hideAdditionalParametersLink.toggle();             }              showAdditionalParametersLink.click(toggleAdditionalParams);             hideAdditionalParametersLink.click(toggleAdditionalParams);              editorForm.formElement.append(tableObjectProperties.wrapperElement);              var tableObjectDescription = ListingEditorFormComposer.createTableFullWidth();              var objectDescriptionRow = ListingEditorFormComposer.createObjectDescriptionRow();             tableObjectDescription.tableElement.append(objectDescriptionRow.row);             tableObjectDescription.tableElement.append(ListingEditorFormComposer.createRowDivider());              editorForm.formElement.append(tableObjectDescription.wrapperElement);              var tableChanges = ListingEditorFormComposer.createTableFullWidth();              var changesDescriptionRow = ListingEditorFormComposer.createChangesDescriptionRow();             tableChanges.tableElement.append(ListingEditorFormComposer.createRowDivider());             tableChanges.tableElement.append(changesDescriptionRow.row);              editorForm.formElement.append(tableChanges.wrapperElement);              var directMappingInputs = {                 name: inputObjectName.inputElement,                 type: inputType.inputElement,                 region: inputRegion.inputElement,                 district: inputDistrict.inputElement,                 municipality: inputMunicipality.inputElement,                 block: inputBlock.inputElement,                 address: inputAddress.inputElement,                 lat: inputLat,                 long: inputLong,                 year: inputYear.inputElement,                 author: inputAuthor.inputElement,                 knid: inputKnid.inputElement,                 complex: inputComplex.inputElement,                 'knid-new': inputKnidNew.inputElement,                 uid: inputUid.inputElement,                 image: inputImage.inputElement,                 wiki: inputWiki.inputElement,                 wdid: inputWdid.inputElement,                 commonscat: inputCommonscat.inputElement,                 munid: inputMunid.inputElement,                 document: inputDocument.inputElement,                 link: inputLink.inputElement,                 linkextra: inputLinkExtra.inputElement,                 sobory: inputSobory.inputElement,                 temples: inputTemples.inputElement,                 description: objectDescriptionRow.inputDescription,                 protection: inputProtection.inputElement,             };              return {                 formElement: editorForm.formElement,                  setValues: function(listing) {                     Object.keys(directMappingInputs).forEach(function(key) {                         if (listing[key]) {                             directMappingInputs[key].val(listing[key]);                         }                     });                     if (listing['style']) {                         inputStyle.inputElement.val(listing['style'].toLowerCase());                     }                     inputDestroyed.inputElement.attr('checked', listing['status'] === 'destroyed');                     inputPrecise.attr('checked', listing['precise'] === 'yes');                 },                  getValues: function() {                     var listingData = {};                     Object.keys(directMappingInputs).forEach(function(key) {                         listingData[key] = directMappingInputs[key].val();                     });                     if (inputDestroyed.inputElement.is(':checked')) {                         listingData['status'] = 'destroyed';                     } else {                         listingData['status'] = '';                     }                     if (inputPrecise.is(':checked')) {                         listingData['precise'] = 'yes';                     } else {                         listingData['precise'] = 'no';                     }                     listingData['link'] = this._normalizeUrl(listingData['link']);                     listingData['linkextra'] = this._normalizeUrl(listingData['linkextra']);                     listingData['style'] = inputStyle.inputElement.val();                     return listingData;                 },                  getObjectName: function() {                     return inputObjectName.inputElement.val();                 },                  getChangesSummary: function() {                     return changesDescriptionRow.inputChangesSummary.val();                 },                  getChangesIsMinor: function() {                     return changesDescriptionRow.inputIsMinorChanges.is(':checked');                 },                  _normalizeUrl: function(url) {                     var webRegex = new RegExp('^https?://', 'i');                     if (!webRegex.test(url) && url !== '') {                         return 'http://' + url;                     } else {                         return url;                     }                 }             };         }     };      CulturalHeritageListingEditor.Core = function() {         var TRANSLATIONS = {             addTitle: 'Добавить объект',             editTitle: 'Редактировать объект',             saving: 'Сохранение...',             submit: 'Сохранить',             cancel: 'Отмена',             added: 'Добавлен объект ',             updated: 'Обновлён объект ',             helpPage: '//ru.wikivoyage.org/wiki/%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%A0%D0%BE%D1%81%D1%81%D0%B8%D0%B8',             enterCaptcha: 'Введите CAPTCHA',             externalLinks: 'Введённый текст содержит внешние ссылки.',             licenseText: 'Нажимая кнопку «Сохранить», вы соглашаетесь с <a class="external" target="_blank" href="https://foundation.wikimedia.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>.',             submitApiError: 'Во время сохранения листинга на сервере произошла ошибка, пожайлуста, попробуйте сохранить ещё раз',             submitBlacklistError: 'Ошибка: текст содержит ссылку из чёрного списка, пожайлуста, удалите её и попробуйте сохранить снова',             submitUnknownError: 'Ошибка: при попытке сохранить листинг произошла неизвестная ошибка, пожайлуста, попробуйте сохранить ещё раз',             submitHttpError: 'Ошибка: сервер сообщил о HTTP ошибке, возникшей во время сохранения листинга, пожайлуста, попробуйте сохранить ещё раз',             submitEmptyError: 'Ошибка: сервер вернул пустой ответ при попытке сохранить листинг, пожайлуста, попробуйте сохранить ещё раз'         };          // if the browser window width is less than MAX_DIALOG_WIDTH (pixels), the         // listing editor dialog will fill the available space, otherwise it will         // be limited to the specified width         var MAX_DIALOG_WIDTH = 1200;          var currentForm = null;          var api = new mw.Api();         var MODE_ADD = 'add';         var MODE_EDIT = 'edit';         // selector that identifies the edit link as created by the         // addEditButtons() function         var SAVE_FORM_SELECTOR = '#progress-dialog';         var CAPTCHA_FORM_SELECTOR = '#captcha-dialog';         var sectionText, replacements = {};          /**          * Return false if the current page should not enable the listing editor.          * Examples where the listing editor should not be enabled include talk          * pages, edit pages, history pages, etc.          */         function listingEditorAllowedForCurrentPage() {             var namespace = mw.config.get( 'wgNamespaceNumber' );             if (namespace !== 0 && namespace !== 2 && namespace !== 4) {                 return false;             }             if (                 mw.config.get('wgAction') !== 'view' ||                 $('#mw-revision-info').length ||                 mw.config.get('wgCurRevisionId') !== mw.config.get('wgRevisionId') ||                 $('#ca-viewsource').length             ) {                 return false;             }              return isCulturalHeritagePage() && !isDiffMode();         }          function getCurrentPageName()         {             return mw.config.get('wgPageName');         }          /**          * Whether we are viewing page in "diff" mode.          *          * @returns {boolean}          */         function isDiffMode()         {             return $('table.diff').length > 0;         }          /**          * Whether current page is related to Cultural Heritage.          *          * @returns {boolean}          */         function isCulturalHeritagePage()         {             // We do not use "Культурное_наследие_России/" here as Crimea is outside of that space             // and is located at "Культурное_наследие/" space.             return StringUtils.contains(getCurrentPageName(), 'Культурное_наследие') && !StringUtils.contains(getCurrentPageName(), 'Культурное_наследие_Казахстана');         }          /**          * Place an "add listing" link at the top of each section heading next to          * the "edit" link in the section heading.          */         var addButtons = function() {             var pageBodyContentElement = $('.mw-parser-output');              var currentSectionIndex = 0;             var currentListingIndex = 0;              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             //             // It is required to know:             // - 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 (inArray(this.tagName, ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'])) {                     var headerElement = $(this);                      if (!isTableOfContentsHeader(headerElement)) {                         currentSectionIndex++;                         currentListingIndex = 0;                         addAddNewListingButton(headerElement, currentSectionIndex);                     }                 } else if (this.tagName === 'TABLE') {                     var listingTable = $(this);                     addEditExistingListingButton(listingTable, currentSectionIndex, currentListingIndex);                     currentListingIndex++;                 }             });         };          function addEditExistingListingButton(listingTable, sectionIndex, listingIndex)         {             var editListingButton = $('<span class="vcard-edit-button noprint" style="padding-left: 5px;">')                 .html('<a href="javascript:" class="icon-pencil" title="Редактировать">Редактировать</a>' )                 .click(function() {                     initListingEditorDialog(MODE_EDIT, sectionIndex, listingIndex);                 });             var nameElement = listingTable.find('span.monument-name').first();             if (nameElement) {                 nameElement.append(editListingButton);             }         }          function addAddNewListingButton(headerElement, sectionIndex)         {             var sectionEditLink = $('<a href="javascript:">добавить</a>');             var bracketStart = $('<span class="mw-editsection-bracket">[</span>');             var bracketEnd = $('<span class="mw-editsection-bracket">]</span>');             headerElement.append(                 $('<span class="mw-editsection"/>').append(bracketStart).append(sectionEditLink).append(bracketEnd)             );             sectionEditLink.click(function() {                 initListingEditorDialog(MODE_ADD, sectionIndex);             });         }          /**          * This method is invoked when an "add" or "edit" listing button is          * clicked and will execute an Ajax request to retrieve all of the raw wiki          * syntax contained within the specified section.  This wiki text will          * later be modified via the listing editor and re-submitted as a section          * edit.          */         var initListingEditorDialog = function(mode, sectionIndex, listingIndex) {             $.ajax({                 url: mw.util.wikiScript(''),                 data: { title: mw.config.get('wgPageName'), action: 'raw', section: sectionIndex },                 cache: false // required             }).done(function(data, textStatus, jqXHR) {                 sectionText = data;                 openListingEditorDialog(mode, sectionIndex, listingIndex);             }).fail(function(jqXHR, textStatus, errorThrown) {                 alert('Ошибка при получении исходного вики-текста статьи: ' + textStatus + ' ' + errorThrown);             });         };          var closeCurrentForm = function() {             if (currentForm && currentForm.formElement.dialog("isOpen")) {                 currentForm.formElement.dialog("destroy");                 currentForm = null;             }         };          /**          * This method is called asynchronously after the initListingEditorDialog()          * method has retrieved the existing wiki section description that the          * listing is being added to (and that contains the listing wiki syntax          * when editing).          */         var openListingEditorDialog = function(mode, sectionNumber, listingIndex) {             sectionText = stripComments(sectionText);             mw.loader.using( ['jquery.ui'], function () {                 var listingTemplateAsMap, listingTemplateWikiSyntax;                 if (mode === MODE_ADD) {                     listingTemplateAsMap = {};                 } else {                     listingTemplateWikiSyntax = getListingWikitextBraces(listingIndex);                     listingTemplateAsMap = wikiTextToListing(listingTemplateWikiSyntax);                 }                 // if a listing editor dialog is already open, get rid of it                 closeCurrentForm();                  currentForm = MonumentListingEditorFormComposer.createForm();                 // populate the empty form with existing values                 currentForm.setValues(listingTemplateAsMap);                  // wide dialogs on huge screens look terrible                 var windowWidth = $(window).width();                 var dialogWidth = (windowWidth > MAX_DIALOG_WIDTH) ? MAX_DIALOG_WIDTH : 'auto';                 // modal form - must submit or cancel                 currentForm.formElement.dialog({                     modal: true,                     height: 'auto',                     width: dialogWidth,                     title: (mode === MODE_ADD) ? TRANSLATIONS.addTitle : TRANSLATIONS.editTitle,                     dialogClass: 'listing-editor-dialog',                     buttons: [                         {                             text: '?',                             id: 'listing-help',                             click: function() { window.open(TRANSLATIONS.helpPage);}                         },                         {                             text: TRANSLATIONS.submit, click: function() {                                 formToText(mode, listingTemplateWikiSyntax, listingTemplateAsMap, sectionNumber);                                 closeCurrentForm();                             }                         },                         {                             text: TRANSLATIONS.cancel,                             click: function() {                                 closeCurrentForm();                             }                         }                     ],                     create: function() {                         $('.ui-dialog-buttonpane').append('<div class="listing-license">' + TRANSLATIONS.licenseText + '</div>');                     }                 });             });         };          var replaceSpecial = function(str) {             return str.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&");         };          /**          * Return a regular expression that can be used to find all listing          * template invocations (as configured via the LISTING_TEMPLATE_PARAMETERS map)          * within a section of wikitext.  Note that the returned regex simply          * matches the start of the template ("{{listing") and not the full          * template ("{{listing|key=value|...}}").          */         var getListingTypesRegex = function() {             return new RegExp('{{\\s*(monument)(\\s|\\|)','ig');         };          /**          * 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.          */         var getListingWikitextBraces = function(listingIndex) {             sectionText = sectionText.replace(/[^\S\n]+/g,' ');             // find the listing wikitext that matches the same index as the listing index             var listingRegex = getListingTypesRegex();             // 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, regexResult, listingMatchIndex;             for (var i = 0; i <= listingIndex; i++) {                 regexResult = listingRegex.exec(sectionText);                 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 = sectionText.length;             var startPos = listingMatchIndex + listingSyntax.length;             var matchFound = false;             for (var j = startPos; j < endPos; j++) {                 if (sectionText[j] === '{') {                     ++curlyBraceCount;                 } else if (sectionText[j] === '}') {                     --curlyBraceCount;                 }                 if (curlyBraceCount === 0 && (j + 1) < endPos) {                     listingSyntax = sectionText.substring(listingMatchIndex, j + 1);                     matchFound = true;                     break;                 }             }             if (!matchFound) {                 listingSyntax = sectionText.substring(listingMatchIndex);             }             return $.trim(listingSyntax);         };          /**          * Convert raw wiki listing syntax into a mapping of key-value pairs          * corresponding to the listing template parameters.          */         var wikiTextToListing = function(listingTemplateWikiSyntax) {             // remove the trailing braces             listingTemplateWikiSyntax = listingTemplateWikiSyntax.slice(0,-2);             var listingTemplateAsMap = {};             var lastKey;             var listParams = listingTemplateToParamsArray(listingTemplateWikiSyntax);             for (var j=1; j < listParams.length; j++) {                 var param = listParams[j];                 var index = param.indexOf('=');                 if (index > 0) {                     // param is of the form key=value                     var key = $.trim(param.substr(0, index));                     var value = $.trim(param.substr(index+1));                     listingTemplateAsMap[key] = value;                     lastKey = key;                 } else if (listingTemplateAsMap[lastKey].length) {                     // there was a pipe character within a param value, such as                     // "key=value1|value2", so just append to the previous param                     listingTemplateAsMap[lastKey] += '|' + param;                 }             }             for (var key in listingTemplateAsMap) {                 // if the template value contains an HTML comment that was                 // previously converted to a placehold then it needs to be                 // converted back to a comment so that the placeholder is not                 // displayed in the edit form                 listingTemplateAsMap[key] = restoreComments(listingTemplateAsMap[key], false);             }             return listingTemplateAsMap;         };          /**          * 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.          */         var listingTemplateToParamsArray = function(listingTemplateWikiSyntax) {             var results = [];             var paramValue = '';             var pos = 0;             while (pos < listingTemplateWikiSyntax.length) {                 var remainingString = listingTemplateWikiSyntax.substr(pos);                 // check for a nested template or wikilink                 var patternMatch = findPatternMatch(remainingString, "{{", "}}");                 if (patternMatch.length === 0) {                     patternMatch = findPatternMatch(remainingString, "[[", "]]");                 }                 if (patternMatch.length > 0) {                     paramValue += patternMatch;                     pos += patternMatch.length;                 } else if (listingTemplateWikiSyntax.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 += listingTemplateWikiSyntax.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.          */         var findPatternMatch = function(value, startPattern, endPattern) {             var matchString = '';             var startRegex = new RegExp('^' + replaceSpecial(startPattern), 'i');             if (startRegex.test(value)) {                 var endRegex = new RegExp('^' + 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;         };          /**          * 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.          */         var stripComments = function(text) {             var comments = text.match(/<!--[\s\S]*?-->/mig);             if (comments !== null ) {                 for (var i = 0; i < comments.length; i++) {                     var comment = comments[i];                     var rep = '<<<COMMENT' + i + '>>>';                     text = text.replace(comment, rep);                     replacements[rep] = comment;                 }             }             return text;         };          /**          * Search the text provided, and if it contains any text that was          * previously stripped out for replacement purposes, restore it.          */         var restoreComments = function(text, resetReplacements) {             for (var key in replacements) {                 var val = replacements[key];                 text = text.replace(key, val);             }             if (resetReplacements) {                 replacements = {};             }             return text;         };          /**          * Convert the listing editor form entry fields into wiki text.  This          * method converts the form entry fields into a listing template string,          * replaces the original template string in the section text with the          * updated entry, and then submits the section text to be saved on the          * server.          */         var formToText = function(mode, listingTemplateWikiSyntax, listing, sectionNumber) {             var formData = currentForm.getValues();             Object.keys(formData).forEach(function(key) {                 listing[key] = formData[key];             });              var text = serializeMonumentListing(listing);              var summary = editSummarySection();             if (mode === MODE_ADD) {                 summary = updateSectionTextWithAddedListing(summary, text, listing);             } else {                 summary = updateSectionTextWithEditedListing(summary, text, listingTemplateWikiSyntax);             }             summary += currentForm.getObjectName();             var formSummary = currentForm.getChangesSummary();              if (formSummary !== '') {                 summary += ' - ' + formSummary;             }              var minor = currentForm.getChangesIsMinor();              saveForm(summary, minor, sectionNumber, '', '');         };          /**          * Begin building the edit summary by trying to find the section name.          */         var editSummarySection = function() {             var sectionName = getSectionName();             return (sectionName.length) ? '/* ' + sectionName + ' */ ' : "";         };          var getSectionName = function() {             var HEADING_REGEX = /^=+\s*([^=]+)\s*=+\s*\n/;             var result = HEADING_REGEX.exec(sectionText);             return (result !== null) ? result[1].trim() : "";         };          /**          * After the listing has been converted to a string, add additional          * processing required for adds (as opposed to edits), returning an          * appropriate edit summary string.          */         var updateSectionTextWithAddedListing = function(originalEditSummary, listingWikiText, listing) {             var summary = originalEditSummary;             summary += TRANSLATIONS.added;             // add the new listing to the end of the section.  if there are             // sub-sections, add it prior to the start of the sub-sections.             var index = sectionText.indexOf('===');             if (index === 0) {                 index = sectionText.indexOf('====');             }             //**RUS** add condition to make sure listingWikiText gets inserted before the footer             if (index === 0) {                 index = sectionText.indexOf('{{footer');             }             if (index > 0) {                 sectionText = sectionText.substr(0, index) + listingWikiText                     + '\n' + sectionText.substr(index);             } else {                 sectionText += '\n' + listingWikiText;             }             sectionText = restoreComments(sectionText, true);             return summary;         };          /**          * After the listing has been converted to a string, add additional          * processing required for edits (as opposed to adds), returning an          * appropriate edit summary string.          */         var updateSectionTextWithEditedListing = function(originalEditSummary, listingWikiText, listingTemplateWikiSyntax) {             var summary = originalEditSummary;             summary += TRANSLATIONS.updated;             sectionText = sectionText.replace(listingTemplateWikiSyntax, listingWikiText);             sectionText = restoreComments(sectionText, true);             return summary;         };          /**          * Render a dialog that notifies the user that the listing editor changes          * are being saved.          */         var savingForm = function() {             // if a progress dialog is already open, get rid of it             if ($(SAVE_FORM_SELECTOR).length > 0) {                 $(SAVE_FORM_SELECTOR).dialog('destroy').remove();             }             var progress = $('<div id="progress-dialog">' + TRANSLATIONS.saving + '</div>');             progress.dialog({                 modal: true,                 height: 100,                 width: 300,                 title: ''             });             $(".ui-dialog-titlebar").hide();         };          /**          * Execute the logic to post listing editor changes to the server so that          * they are saved.  After saving the page is refreshed to show the updated          * article.          */         var saveForm = function(summary, minor, sectionNumber, cid, answer) {             var editPayload = {                 action: "edit",                 title: mw.config.get( "wgPageName" ),                 section: sectionNumber,                 text: sectionText,                 summary: summary,                 captchaid: cid,                 captchaword: answer             };             if (minor) {                 $.extend( editPayload, { minor: 'true' } );             }             api.postWithToken(                 "csrf",                 editPayload             ).done(function(data, jqXHR) {                 if (data && data.edit && data.edit.result == 'Success') {                     // since the listing editor can be used on diff pages, redirect                     // to the canonical URL if it is different from the current URL                     var canonicalUrl = $("link[rel='canonical']").attr("href");                     var currentUrlWithoutHash = window.location.href.replace(window.location.hash, "");                     if (canonicalUrl && currentUrlWithoutHash != canonicalUrl) {                         var sectionName = encodeURIComponent(getSectionName()).replace(/%20/g,'_').replace(/%/g,'.');                         if (sectionName.length) {                             canonicalUrl += "#" + sectionName;                         }                         window.location.href = canonicalUrl;                     } else {                         window.location.reload();                     }                 } else if (data && data.error) {                     saveFailed(TRANSLATIONS.submitApiError + ' "' + data