Участник:AlexeyBaturin/CulturalHeritageListingEditor.js
Внешний вид
Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.
- 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: 'памятник археологии'
}
]
},
{
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: 'выявленный объект'
}
]
},
];
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') {
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.error.code + '": ' + data.error.info );
} else if (data && data.edit.spamblacklist) {
saveFailed(TRANSLATIONS.submitBlacklistError + ': ' + data.edit.spamblacklist );
} else if (data && data.edit.captcha) {
$(SAVE_FORM_SELECTOR).dialog('destroy').remove();
captchaDialog(summary, minor, sectionNumber, data.edit.captcha.url, data.edit.captcha.id);
} else {
saveFailed(TRANSLATIONS.submitUnknownError);
}
}).fail(function(code, result) {
if (code === "http") {
saveFailed(TRANSLATIONS.submitHttpError + ': ' + result.textStatus );
} else if (code === "ok-but-empty") {
saveFailed(TRANSLATIONS.submitEmptyError);
} else {
saveFailed(TRANSLATIONS.submitUnknownError + ': ' + code );
}
});
savingForm();
};
/**
* If an error occurs while saving the form, remove the "saving" dialog,
* restore the original listing editor form (with all user description), and
* display an alert with a failure message.
*/
var saveFailed = function(msg) {
$(SAVE_FORM_SELECTOR).dialog('destroy').remove();
currentForm.formElement.dialog('open');
alert(msg);
};
/**
* If the result of an attempt to save the listing editor description is a
* Captcha challenge then display a form to allow the user to respond to
* the challenge and resubmit.
*/
var captchaDialog = function(summary, minor, sectionNumber, captchaImgSrc, captchaId) {
// if a captcha dialog is already open, get rid of it
if ($(CAPTCHA_FORM_SELECTOR).length > 0) {
$(CAPTCHA_FORM_SELECTOR).dialog('destroy').remove();
}
var captcha = $('<div id="captcha-dialog">').text(TRANSLATIONS.externalLinks);
var image = $('<img class="fancycaptcha-image">')
.attr('src', captchaImgSrc)
.appendTo(captcha);
var label = $('<label for="input-captcha">').text(TRANSLATIONS.enterCaptcha).appendTo(captcha);
var input = $('<input id="input-captcha" type="text">').appendTo(captcha);
captcha.dialog({
modal: true,
title: TRANSLATIONS.enterCaptcha,
buttons: [
{
text: TRANSLATIONS.submit, click: function() {
saveForm(summary, minor, sectionNumber, captchaId, $('#input-captcha').val());
$(this).dialog('destroy').remove();
}
},
{
text: TRANSLATIONS.cancel, click: function() {
$(this).dialog('destroy').remove();
}
}
]
});
};
/**
* Called on DOM ready, this method initializes the listing editor and
* adds the "add/edit listing" links to sections and existing listings.
*/
var initListingEditor = function() {
if (!listingEditorAllowedForCurrentPage()) {
return;
}
addButtons();
};
// expose public members
return {
MODE_ADD: MODE_ADD,
MODE_EDIT: MODE_EDIT,
init: initListingEditor
};
}();
$(document).ready(function() {
CulturalHeritageListingEditor.Core.init();
});
});
//</nowiki>