Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.
- 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(' '); row.inputColumnElement.append(buttonInsertQuotes); row.inputColumnElement.append(' '); 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('Широта: ') .append(inputLat) .append(' Долгота: ') .append(inputLong) .append(' Точные? ') .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