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

  • Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
  • Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
  • Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
  • Opera: Нажмите Ctrl+F5.
/******************************************************************    Listing Editor v2.1      Original author:      - torty3      Additional contributors:      - Andyrom75      - Wrh2      v2.1 Changes:      - Wikidata & Wikipedia fields added.      - The Wikidata, image, and Wikipedia fields will now autocomplete, with        lookups done by searching the relevant site.      - Latitude, longitude, official link, wikipedia link, and image can be        populated with the values stored at Wikidata by clicking on the "Update        shared fields using values from Wikidata" link.      - The "image" field is now shown by default.      - Several cleanups to the underlying code.      v2.0 Changes:      - Update the listing editor dialog UI to provide more space for field        entry, and to responsively collapse from two columns to one on small        screens.      - Use mw.Api().postWithToken instead of $.ajax to hopefully fix session        token expiration issues.      - Add support for editing of multi-paragraph listings.      - Do not delete non-empty unrecognized listing template values (wikipedia,        phoneextra, etc) when editing if the ALLOW_UNRECOGNIZED_PARAMETERS flag        is set to true.      - If listing editor form submit fails, re-display the listing editor form        with the content entered by the user so that work is not lost.      - Replace synchronous $.ajax call with asynchronous.      - Allow edit summaries and marking edits as minor when editing listings.      - Move 'add listing' link within the mw-editsection block.      - Automatically replace newlines in listing content with <p> tags.      - Fix bug where HTML comments inside listing fields prevented editing.      - Provide configuration options so that some fields can be displayed only        if they have non-empty values (examples: "fax" in the default        configuration).      - Add basic email address validation.      - A failed captcha challenge no longer causes further captcha attempts to        fail.      - Significant code reorganization.      TODO      - Add support for mobile devices.      - Handle "=" in listing parameter values ("[[File:XYZ.jpg|link=ABC]])"      - Add support for non-standard listing "type" values ("go", etc).      - wrapContent is breaking the expand/collapse logic on the VFD page.      - populate the input-type select list from LISTING_TEMPLATE_PARAMETERS ********************************************************************/ //<nowiki>  ( function ( mw, $ ) { 	'use strict'; 	if ( mw.config.get( 'skin' ) === 'minerva' ) { 		return; 	}  	/* *********************************************************************** 	 * CUSTOMIZATION INSTRUCTIONS: 	 * 	 * Different Wikivoyage language versions have different implementations of 	 * the listing template, so this module must be customized for each.  The 	 * ListingEditor.Config and ListingEditor.Callbacks modules should be the 	 * ONLY code that requires customization - ListingEditor.Core should be 	 * shared across all language versions.  If for some reason the Core module 	 * must be modified, ideally the module should be modified for all language 	 * versions so that the code can stay in sync. 	 * ***********************************************************************/  	var ListingEditor = {};  	// see http://toddmotto.com/mastering-the-module-pattern/ for an overview 	// of the module design pattern being used in this gadget  	/* *********************************************************************** 	 * ListingEditor.Config contains properties that will likely need to be 	 * modified for each Wikivoyage language version.  Properties in this 	 * module will be referenced from the other ListingEditor modules. 	 * ***********************************************************************/ 	ListingEditor.Config = function() {  		// -------------------------------------------------------------------- 		// TRANSLATE THE FOLLOWING BASED ON THE WIKIVOYAGE LANGUAGE IN USE 		// --------------------------------------------------------------------  		var LANG = 'ru'; 		var COMMONS_URL = '//commons.wikimedia.org'; 		var WIKIDATA_URL = '//www.wikidata.org'; 		var TRANSLATIONS = { 			'addTitle' : 'Добавить объект', 			'editTitle' : 'Редактировать объект', 			'add': 'добавить объект', 			'edit': 'редактировать', 			'saving': 'Сохранение…', 			'submit': 'Сохранить', 			'cancel': 'Отмена', 			'validationEmptyListing': 'Пожалуйста, укажите название или адрес', 			'validationImage': 'Пожалуйста, введите имя файла без каких-либо префиксов', 			'image': 'Файл',  			'added': 'Добавлен объект ', 			'updated': 'Обновлён объект ', 			'removed': 'Удалён объект ', 			'helpPage': '//ru.wikivoyage.org/wiki/Обсуждение_MediaWiki:Gadget-ListingEditor.js', 			'enterCaptcha': 'Введите CAPTCHA', 			'externalLinks': 'Введённый текст содержит внешние ссылки.', 			// license text should match MediaWiki:Wikimedia-copyrightwarning 			'licenseText': 'Нажимая кнопку «Сохранить», вы соглашаетесь с <a class="external" target="_blank" href="https://foundation.wikimedia.org/wiki/Terms_of_Use/ru">условиями использования</a>, а также соглашаетесь на неотзывную публикацию по лицензии <a class="external" target="_blank" href="https://creativecommons.org/licenses/by-sa/3.0/deed.ru">CC-BY-SA 3.0</a>.', 			'ajaxInitFailure': 'Ошибка: невозможно инициализировать Listing Editor', 			'sharedImage': 'Изображение', 			'sharedLatitude': 'Широта', 			'sharedLongitude': 'Долгота', 			'sharedWebsite': 'Веб-сайт', 			'submitApiError': 'Во время сохранения листинга на сервере произошла ошибка, пожайлуста, попробуйте сохранить ещё раз', 			'submitBlacklistError': 'Ошибка: текст содержит ссылку из чёрного списка, пожайлуста, удалите её и попробуйте сохранить снова', 			'submitUnknownError': 'Ошибка: при попытке сохранить листинг произошла неизвестная ошибка, пожайлуста, попробуйте сохранить ещё раз', 			'submitHttpError': 'Ошибка: сервер сообщил о HTTP ошибке, возникшей во время сохранения листинга, пожайлуста, попробуйте сохранить ещё раз', 			'submitEmptyError': 'Ошибка: сервер вернул пустой ответ при попытке сохранить листинг, пожайлуста, попробуйте сохранить ещё раз', 			'viewCommonsPage' : 'открыть изображение на новой странице', 			'viewWikidataPage' : 'посмотреть запись Викиданных', 			'wikidataShared': 'Следующая информация доступна в Викиданных. Обновить эти данные в листинге?', 			'wikidataSharedNotFound': 'На Викиданных информации, возможной к импортированию, не найдено.' 		};  		// -------------------------------------------------------------------- 		// CONFIGURE THE FOLLOWING BASED ON WIKIVOYAGE COMMUNITY PREFERENCES 		// --------------------------------------------------------------------  		// 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; 		// set this flag to false if the listing editor should strip away any 		// listing template parameters that are not explicitly configured in the 		// LISTING_TEMPLATE_PARAMETERS parameter arrays (such as wikipedia, phoneextra, etc). 		// if the flag is set to true then unrecognized parameters will be allowed 		// as long as they have a non-empty value. 		var ALLOW_UNRECOGNIZED_PARAMETERS = true;  		// -------------------------------------------------------------------- 		// UPDATE THE FOLLOWING TO MATCH WIKIVOYAGE ARTICLE SECTION NAMES 		// --------------------------------------------------------------------  		// map section heading ID to the listing template to use for that section 		var SECTION_TO_TEMPLATE_TYPE = { 			'Как добраться': 'go', 			'Достопримечательности': 'see', 			'Чем заняться': 'do', 			'Покупки': 'buy', 			'Еда': 'eat', 			'Ночная жизнь': 'drink', 			'Где остановиться': 'sleep', 			'Связь': 'other', 			'Окрестности':'vicinity' 		}; 		// If any of these patterns are present on a page then no 'add listing' 		// buttons will be added to the page 		var DISALLOW_ADD_LISTING_IF_PRESENT = ['#Города', '#Областные центры', '#Курорты', '#Острова', '#Малые города', '#Другие места назначения', '#print-districts'];  		// -------------------------------------------------------------------- 		// CONFIGURE THE FOLLOWING TO MATCH THE LISTING TEMPLATE PARAMS & OUTPUT 		// --------------------------------------------------------------------  		// name of the generic listing template to use when a more specific 		// template ("see", "do", etc) is not appropriate 		//**RUS** commented out 		//var DEFAULT_LISTING_TEMPLATE = 'listing'; 		//var LISTING_TYPE_PARAMETER = 'type'; 		//var LISTING_CONTENT_PARAMETER = 'description'; 		// selector that identifies the HTML elements into which the 'edit' link 		// for each listing will be placed 		//**RUS** changed 'span.listing-metadata-items' to '.mw-addhere' 		var EDIT_LINK_CONTAINER_SELECTOR = 'span.listing-metadata-items'; 		// The arrays below must include entries for each listing template 		// parameter in use for each Wikivoyage language version - for example 		// "name", "address", "phone", etc.  If all listing template types use 		// the same parameters then a single configuration array is sufficient, 		// but if listing templates use different parameters or have different 		// rules about which parameters are required then the differences must 		// be configured - for example, English Wikivoyage uses "checkin" and 		// "checkout" in the "sleep" template, so a separate 		// SLEEP_TEMPLATE_PARAMETERS array has been created below to define the 		// different requirements for that listing template type. 		// 		// Once arrays of parameters are defined, the LISTING_TEMPLATE_PARAMETERS 		// mapping is used to link the configuration to the listing template 		// type, so in the English Wikivoyage example all listing template 		// types use the LISTING_TEMPLATE_PARAMETERS configuration EXCEPT for 		// "sleep" listings, which use the SLEEP_TEMPLATE_PARAMETERS 		// configuration. 		// 		// Fields that can used in the configuration array(s): 		//   - id: HTML input ID in the EDITOR_FORM_HTML for this element. 		//   - hideDivIfEmpty: id of a <div> in the EDITOR_FORM_HTML for this 		//     element that should be hidden if the corresponding template 		//     parameter has no value. For example, the "fax" field is 		//     little-used and is not shown by default in the editor form if it 		//     does not already have a value. 		//   - skipIfEmpty: Do not include the parameter in the wiki template 		//     syntax that is saved to the article if the parameter has no 		//     value. For example, the "image" tag is not included by default 		//     in the listing template syntax unless it has a value. 		//   - newline: Append a newline after the parameter in the listing 		//     template syntax when the article is saved. 		//**RUS** removed 'skipIfEmpty', SLEEP_TEMPLATE_PARAMETERS. Template parameters: "checkin"/"checkout", 'tollfree', 'fax', 'wikipedia', globally changed 'content'->'description' 		var LISTING_TEMPLATE_PARAMETERS = { 			'type': { id:'input-type' }, 			'lat': { id:'input-lat' }, 			'long': { id:'input-long' }, 			'wdid': { id:'input-wdid-value', newline: true }, 			'name': { id:'input-name' }, 			'alt': { id:'input-alt' }, 			'image': { id:'input-image', newline: true }, 			'address': { id:'input-address' }, 			'directions': { id:'input-directions', newline: true }, 			'url': { id:'input-url' }, 			'facebook': { id:'input-face-book' }, 			'vkontakte': { id:'input-vkontakte', newline: true }, 			'phone': { id:'input-phone', newline: true }, 			'email': { id:'input-email', newline: true }, 			'hours': { id:'input-hours' }, 			'price': { id:'input-price', newline: true }, 			'lastedit': { id:'input-lastedit', newline: true }, 			'description': { id:'input-description', newline: true }, 			'star': { id:'input-star', newline: true}, 			'unesco': { id:'input-unesco', newline: true }, 			'format': { id:'input-format', newline: true } 		};  		// -------------------------------------------------------------------- 		// CONFIGURE THE FOLLOWING TO IMPLEMENT THE UI FOR THE LISTING EDITOR 		// --------------------------------------------------------------------  		// these selectors should match a value defined in the EDITOR_FORM_HTML 		// if the selector refers to a field that is not used by a Wikivoyage 		// language version the variable should still be defined, but the 		// corresponding element in EDITOR_FORM_HTML can be removed and thus 		// the selector will not match anything and the functionality tied to 		// the selector will never execute. 		var EDITOR_FORM_SELECTOR = '#listing-editor'; 		var EDITOR_CLOSED_SELECTOR = '#input-closed'; 		var EDITOR_SUMMARY_SELECTOR = '#input-summary'; 		var EDITOR_MINOR_EDIT_SELECTOR = '#input-minor'; 		// the below HTML is the UI that will be loaded into the listing editor 		// dialog box when a listing is added or edited.  EACH WIKIVOYAGE 		// LANGUAGE SITE CAN CUSTOMIZE THIS HTML - fields can be removed, 		// added, displayed differently, etc.  Note that it is important that 		// any changes to the HTML structure are also made to the 		// LISTING_TEMPLATE_PARAMETERS parameter arrays since that array provides the 		// mapping between the editor HTML and the listing template fields. 		var EDITOR_FORM_HTML = '<form id="listing-editor">' + 			'<div class="listing-col listing-span_1_of_2">' + 				'<table class="editor-fullwidth">' + 				'<tr id="div_name">' + 					'<td class="editor-label-col"><label for="input-name">Название</label></td>' + 					'<td>' +  						'<input type="text" class="editor-partialwidth-75" placeholder="название объекта" id="input-name">&nbsp;' + 						'<a id="name-quotes-template" href="javascript:">«»</a>&nbsp;' + 						'<a id="name-dash-template" href="javascript:">—</a>' + 					'</td>' + 				'</tr>' + 				'<tr id="div_alt">' + 					'<td class="editor-label-col"><label for="input-alt">Или</label></td>' + 					'<td><input type="text" class="editor-fullwidth" placeholder="другое название или пояснение" id="input-alt"></td>' + 				'</tr>' + 				'<tr id="div_address">' + 					'<td class="editor-label-col"><label for="input-address">Адрес</label></td>' + 					'<td><input type="text" class="editor-fullwidth" placeholder="адрес объекта" id="input-address"></td>' + 				'</tr>' + 				'<tr id="div_directions">' + 					'<td class="editor-label-col"><label for="input-directions">Пояснения</label></td>' + 					'<td><input type="text" class="editor-fullwidth" placeholder="пояснения, в том числе транспорт" id="input-directions"></td>' + 				'</tr>' + 				'<tr id="div_phone">' + 					'<td class="editor-label-col"><label for="input-phone">Телефон</label></td>' + 					'<td>' +  						'<input type="text" class="editor-partialwidth-75" placeholder="+55 555 555-5555" id="input-phone">&nbsp;' +  						'<a id="phone-template" href="javascript:">{{phone|||}}</a>' + 					'</td>' + 				'</tr>' + 				'<tr id="div_hours">' + 					'<td class="editor-label-col"><label for="input-hours">Часы работы</label></td>' + 					'<td>' + 						'<input type="text" class="editor-partialwidth-75" placeholder="09:00-18:00" id="input-hours">&nbsp;' + 						'<a id="hours-template" href="javascript:">{{hours|||}}</a>' + 					'</td>' + 				'</tr>' + 				'<tr id="div_price">' + 					'<td class="editor-label-col"><label for="input-price">Стоимость</label></td>' + 					'<td>' + 						// update the ListingEditor.Callbacks.initCurrencySymbolFormFields 						// method if the currency symbols are removed or modified 						'<input type="text" class="editor-partialwidth-75" placeholder="100 руб" id="input-price">&nbsp;' + 						'<span id="span_currency">' + 							'<span class="currency-signs"> <a href="javascript:">\u20AC</a></span>' + 						'</span>' + 					'</td>' + 				'</tr>' + 				'<tr id="div_url">' + 					'<td class="editor-label-col"><label for="input-url">Веб-сайт<span class="wdid-update"></span></label></td>' + 					'<td><input type="text" class="editor-fullwidth" placeholder="http://www.example.com" id="input-url"></td>' + 				'</tr>' + 				'<tr id="div_email" style="display: none;">' + 					'<td class="editor-label-col"><label for="input-email">E-mail</label></td>' + 					'<td><input type="text" class="editor-fullwidth" placeholder="[email protected]" id="input-email"></td>' + 				'</tr>' + 				'<tr id="div_vkontakte">' + 					'<td class="editor-label-col"><label for="input-vkontakte">ВКонтакте</label></td>' + 					'<td><input type="text" class="editor-partialwidth-75" placeholder="http://vk.com/example" id="input-vkontakte"></td>' + 				'</tr>' + 				'<tr id="div-face-book">' + 					'<td class="editor-label-col"><label for="input-face-book">Facebook</label></td>' + 					'<td><input type="text" class="editor-partialwidth-75" placeholder="https://www.facebook.com/example" id="input-face-book"></td>' + 				'</tr>' + 				'<tr id="div_lastedit" style="display: none;">' + 					'<td class="editor-label-col"><label for="input-lastedit">Last Updated</label></td>' + 					'<td><input type="text" size="10" placeholder="2016-01-15" id="input-lastedit"></td>' + 				'</tr>' + 				'</table>' + 			'</div>' + 			'<div class="listing-col listing-span_1_of_2">' + 				'<table class="editor-fullwidth">' + 				'<tr id="div_type">' + 					'<td class="editor-label-col"><label for="input-type">Тип</label></td>' + 					'<td>' + 						'<select id="input-type" placeholder="тип объекта, определяющий цвет значка на карте">' + 							'<option value="go">Как добраться</option>' + 							'<option value="see">Достопримечательности</option>' + 							'<option value="do">Чем заняться</option>' + 							'<option value="buy">Покупки</option>' + 							'<option value="eat">Еда</option>' + 							'<option value="drink">Ночная жизнь</option>' + 							'<option value="sleep">Где остановиться</option>' + 							'<option value="other">Связь</option>' + 							'<option value="vicinity">Окрестности</option>' + 						'</select>' + 					'</td>' + 				'</tr>' + 				'<tr id="div_format" style="display: none;">' + 					'<td class="editor-label-col"><label for="input-format">Форматирование</label></td>' + 					'<td><input type="text" size="10" placeholder="" id="input-format"></td>' + 				'</tr>' + 				'<tr id="div_lat">' + 					'<td class="editor-label-col"><label for="input-lat">Широта<span class="wdid-update"></span></label></td>' + 					'<td><input type="text" class="editor-partialwidth" placeholder="11.11111" id="input-lat">' + 					// update the ListingEditor.Callbacks.initFindOnMapLink 					// method if this field is removed or modified 					'&nbsp;<a id="geomap-link" target="_blank" href="https://wikivoyage.toolforge.org/w/geomap.php">Найти на карте</a></td>' + 				'</tr>' + 				'<tr id="div_long">' + 					'<td class="editor-label-col"><label for="input-long">Долгота<span class="wdid-update"></span></label></td>' + 					'<td><input type="text" class="editor-partialwidth" placeholder="111.11111" id="input-long"></td>' + 				'</tr>' + 				'<tr id="div_wdid">' + 					'<td class="editor-label-col"><label for="input-wdid-label">Викиданные</label></td>' + 					'<td>' + 						'<input type="text" class="editor-partialwidth" placeholder="Qxxxxx" id="input-wdid-label">' + 						'<input type="hidden" id="input-wdid-value">' + 						'<span id="wdid-value-display-container" style="display:none">' + 							'<small>' + 							'&#160;<span id="wdid-value-link"></span>' + 							'&#160;|&#160;<a href="javascript:" id="wdid-remove" title="Удалить запись Викиданных из этого листинга">удалить</a>' + 							'</small>' + 						'</span>' + 					'</td>' + 				'</tr>' + 				'<tr id="div_wdid_update" style="display: none">' + 					'<td class="editor-label-col">&#160;</td>' + 					'<td><span class="wdid-update"></span><a href="javascript:" id="wdid-shared">Импортировать информацию со страницы Викиданных</a></td>' + 				'</tr>' + 				'<tr id="div_image">' + 					'<td class="editor-label-col"><label for="input-image">Изображение<span class="wdid-update"></span></label></td>' + 					'<td>' + 						'<input type="text" class="editor-partialwidth" placeholder="имя файла на Commons" id="input-image">' + 						'<span id="image-value-display-container" style="display:none">' + 							'<small>' + 							'&#160;<span id="image-value-link"></span>' + 							'</small>' + 						'</span>' + 					'</td>' + 				'</tr>' + 				'</table>' + 			'</div>' + 			'<table class="editor-fullwidth">' + 			// update the ListingEditor.Callbacks.hideEditOnlyFields method if 			// the status row is removed or modified 			'<tr id="div_status">' + 				'<td class="editor-label-col"><label>Статус</label></td>' + 				'<td>' + 					// update the ListingEditor.Callbacks.updateLastEditDate 					// method if the last edit input is removed or modified 					'<span id="span-last-edit">' + 						'<input type="checkbox" id="input-last-edit" checked="checked"/>' + 						'<label for="input-last-edit" class="listing-tooltip" title="Установите галочку, если вся информация об объекте достоверна и актуальна, и дате последнего обновления будет присвоено сегодняшнее число">Информация актуальна на сегодняшнюю дату?</label>' + 					'</span>' + 					'<span id="span-star">' + 						'<input type="checkbox" id="input-star">' + 						'<label for="input-star" class="listing-tooltip" title="Установите галочку, если объект является ключевым при посещении данного населённого пункта">Важный?</label>' + 					'</span>' + 					'<span id="span-unesco">' + 						'<input type="checkbox" id="input-unesco">' + 						'<label for="input-unesco" class="listing-tooltip" title="Установите галочку, если данный объект включён в список всемирного наследия ЮНЕСКО">ЮНЕСКО?</label>' + 					'</span>' + 					'<span id="span-closed">' + 						'<input type="checkbox" id="input-closed">' + 						'<label for="input-closed" class="listing-tooltip" title="Установите галочку, если данный объект больше не функционирует, и он будет удалён из статьи">Удалить этот объект?</label>' + 					'</span>' + 				'</td>' + 			'</tr>' + 			'<tr id="div_description">' + 				'<td class="editor-label-col"><label for="input-description">Описание</label></td>' + 				'<td><textarea rows="8" class="editor-fullwidth" placeholder="описание объекта" id="input-description"></textarea></td>' + 			'</tr>' + 			'</table>' + 			// update the ListingEditor.Callbacks.hideEditOnlyFields method if 			// the summary table is removed or modified 			'<table class="editor-fullwidth" id="div_summary">' + 			'<tr><td colspan="2"><div class="listing-divider" /></td></tr>' + 			'<tr>' + 				'<td class="editor-label-col"><label for="input-summary">Описание изменений</label></td>' + 				'<td>' + 					'<input type="text" class="editor-partialwidth" placeholder="что именно было изменено" id="input-summary">' + 					'<span id="span-minor"><input type="checkbox" id="input-minor"><label for="input-minor" class="listing-tooltip" title="Установите галочку, если изменение незначительное, например, исправление опечатки">незначительное изменение?</label></span>' + 				'</td>' + 			'</tr>' + 			'</table>' + 			'</form>';  		// expose public members 		return { 			LANG: LANG, 			COMMONS_URL: COMMONS_URL, 			WIKIDATA_URL: WIKIDATA_URL, 			TRANSLATIONS: TRANSLATIONS, 			MAX_DIALOG_WIDTH: MAX_DIALOG_WIDTH, 			DISALLOW_ADD_LISTING_IF_PRESENT: DISALLOW_ADD_LISTING_IF_PRESENT, 			//DEFAULT_LISTING_TEMPLATE: DEFAULT_LISTING_TEMPLATE, 			//LISTING_TYPE_PARAMETER: LISTING_TYPE_PARAMETER, 			//LISTING_CONTENT_PARAMETER: LISTING_CONTENT_PARAMETER, 			EDIT_LINK_CONTAINER_SELECTOR: EDIT_LINK_CONTAINER_SELECTOR, 			ALLOW_UNRECOGNIZED_PARAMETERS: ALLOW_UNRECOGNIZED_PARAMETERS, 			SECTION_TO_TEMPLATE_TYPE: SECTION_TO_TEMPLATE_TYPE, 			LISTING_TEMPLATE_PARAMETERS: LISTING_TEMPLATE_PARAMETERS, 			EDITOR_FORM_SELECTOR: EDITOR_FORM_SELECTOR, 			EDITOR_CLOSED_SELECTOR: EDITOR_CLOSED_SELECTOR, 			EDITOR_SUMMARY_SELECTOR: EDITOR_SUMMARY_SELECTOR, 			EDITOR_MINOR_EDIT_SELECTOR: EDITOR_MINOR_EDIT_SELECTOR, 			EDITOR_FORM_HTML: EDITOR_FORM_HTML 		}; 	}();  	/* *********************************************************************** 	 * ListingEditor.Callbacks implements custom functionality that may be 	 * specific to how a Wikivoyage language version has implemented the 	 * listing template.  For example, English Wikivoyage uses a "last edit" 	 * date that needs to be populated when the listing editor form is 	 * submitted, and that is done via custom functionality implemented as a 	 * SUBMIT_FORM_CALLBACK function in this module. 	 * ***********************************************************************/ 	ListingEditor.Callbacks = function() { 		// array of functions to invoke when creating the listing editor form. 		// these functions will be invoked with the form DOM object as the 		// first element and the mode as the second element. 		var CREATE_FORM_CALLBACKS = []; 		// array of functions to invoke when submitting the listing editor 		// form but prior to validating the form. these functions will be 		// invoked with the mapping of listing attribute to value as the first 		// element and the mode as the second element. 		var SUBMIT_FORM_CALLBACKS = []; 		// array of validation functions to invoke when the listing editor is 		// submitted. these functions will be invoked with an array of 		// validation messages as an argument; a failed validation should add a 		// message to this array, and the user will be shown the messages and 		// the form will not be submitted if the array is not empty. 		var VALIDATE_FORM_CALLBACKS = [];  		// -------------------------------------------------------------------- 		// LISTING EDITOR UI INITIALIZATION CALLBACKS 		// -------------------------------------------------------------------- 		 		//**RUS** added the next method 		var initQuotesInsert = function(form, mode) { 			$('#name-quotes-template', form).click(function() { 				var input = $('#input-name'); 				var selectionStart = input[0].selectionStart; 				var selectionEnd = input[0].selectionEnd; 				var oldValue = input.val(); 				var newValue = oldValue.substring(0, selectionStart) + "«" + oldValue.substring(selectionStart, selectionEnd) + "»" + oldValue.substring(selectionEnd); 				input.val(newValue); 			}); 		}; 		CREATE_FORM_CALLBACKS.push(initQuotesInsert); 		 		//**RUS** added the next method 		var initQuotesInsert = function(form, mode) { 			$('#name-dash-template', form).click(function() { 				var input = $('#input-name'); 				var caretPos = input[0].selectionStart; 				var oldValue = input.val(); 				var newValue = oldValue.substring(0, caretPos) + "—" + oldValue.substring(caretPos); 				input.val(newValue); 			}); 		}; 		CREATE_FORM_CALLBACKS.push(initQuotesInsert);  		/** 		 * Add listeners to the currency symbols so that clicking on a currency 		 * symbol will insert it into the price input. 		 */ 		var initCurrencySymbolFormFields = function(form, mode) { 			var CURRENCY_SIGNS_SELECTOR = '.currency-signs'; 			$(CURRENCY_SIGNS_SELECTOR, form).click(function() { 				var priceInput = $('#input-price'); 				var caretPos = priceInput[0].selectionStart; 				var oldPrice = priceInput.val(); 				var currencySymbol = $(this).find('a').text(); 				var newPrice = oldPrice.substring(0, caretPos) + currencySymbol + oldPrice.substring(caretPos); 				priceInput.val(newPrice); 			}); 		}; 		CREATE_FORM_CALLBACKS.push(initCurrencySymbolFormFields); 		 		//**RUS** added the next method 		var initPhoneTemplateInsert = function(form, mode) { 			$('#phone-template', form).click(function() { 				var input = $('#input-phone'); 				var caretPos = input[0].selectionStart; 				var oldValue = input.val(); 				var newValue = oldValue.substring(0, caretPos) + "{{phone|||}}" + oldValue.substring(caretPos); 				input.val(newValue); 			}); 		}; 		CREATE_FORM_CALLBACKS.push(initPhoneTemplateInsert); 		 		//**RUS** added the next method 		var initHoursTemplateInsert = function(form, mode) { 			$('#hours-template', form).click(function() { 				var input = $('#input-hours'); 				var caretPos = input[0].selectionStart; 				var oldValue = input.val(); 				var newValue = oldValue.substring(0, caretPos) + "{{hours|день1|день2|час1|мин1|час2|мин2}}" + oldValue.substring(caretPos); 				input.val(newValue); 			}); 		}; 		CREATE_FORM_CALLBACKS.push(initHoursTemplateInsert);  		/** 		 * Add listeners on various fields to update the "find on map" link. 		 */ 		var initFindOnMapLink = function(form, mode) { 			var latlngStr = '?lang=' + ListingEditor.Config.LANG; 			latlngStr += '&page=' + encodeURIComponent(mw.config.get('wgTitle')); 			// #geodata should be a hidden span added by Template:Geo 			// containing the lat/long coordinates of the destination 			if ($('#geodata').length) { 				var latlng = $('#geodata').text().split('; '); 				latlngStr += '&lat=' + latlng[0] + '&lon=' + latlng[1] + '&zoom=15'; 			} 			if ($('#input-address', form).val() !== '') { 				latlngStr += '&location=' + encodeURIComponent($('#input-address', form).val()); 			} else if ($('#input-name', form).val() !== '') { 				latlngStr += '&location=' + encodeURIComponent($('#input-name', form).val()); 			} 			// #geomap-link is a link in the EDITOR_FORM_HTML 			$('#geomap-link', form).attr('href', $('#geomap-link', form).attr('href') + latlngStr); 			$('#input-address', form).change( function () { 				var link = $('#geomap-link').attr('href'); 				var index = link.indexOf('&location'); 				if (index < 0) index = link.length; 				$('#geomap-link').attr('href', link.substr(0,index) + '&location=' 						+ encodeURIComponent($('#input-address').val())); 			}); 		}; 		CREATE_FORM_CALLBACKS.push(initFindOnMapLink);  		var hideEditOnlyFields = function(form, mode) { 			//**RUS** modified 1 line 			var EDITOR_STATUS_ROW = '#span-last-edit,#span-closed'; 			var EDITOR_SUMMARY_ROW = '#div_summary'; 			if (mode !== ListingEditor.Core.MODE_EDIT) { 				$(EDITOR_STATUS_ROW, form).hide(); 				$(EDITOR_SUMMARY_ROW, form).hide(); 			} 		}; 		CREATE_FORM_CALLBACKS.push(hideEditOnlyFields);  		var wikidataLookup = function(form, mode) { 			// get the display value for the pre-existing wikidata record ID 			var value = $("#input-wdid-value", form).val(); 			if (value) { 				wikidataLink(form, value); 				var ajaxUrl = ListingEditor.SisterSite.API_WIKIDATA; 				var ajaxData = { 					action: 'wbgetentities', 					ids: value, 					languages: ListingEditor.Config.LANG, 					props: 'labels' 				}; 				var ajaxSuccess = function(jsonObj) { 					var value = $("#input-wdid-value").val(); 					var label = ListingEditor.SisterSite.wikidataLabel(jsonObj, value); 					if (label === null) { 						label = ""; 					} 					$("#input-wdid-label").val(label); 				}; 				ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); 			} 			// set up autocomplete to search for results as the user types 			$('#input-wdid-label', form).autocomplete({ 				source: function( request, response ) { 					var ajaxUrl = ListingEditor.SisterSite.API_WIKIDATA; 					var ajaxData = { 						action: 'wbsearchentities', 						search: request.term, 						language: ListingEditor.Config.LANG 					}; 					var ajaxSuccess = function (jsonObj) { 						response(parseWikiDataResult(jsonObj)); 					}; 					ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); 				}, 				select: function(event, ui) { 					$("#input-wdid-value").val(ui.item.id); 					wikidataLink("", ui.item.id); 				} 			}).data("ui-autocomplete")._renderItem = function(ul, item) { 				var label = item.label + " <small>" + item.id + "</small>"; 				if (item.description) { 					label += "<br /><small>" + item.description + "</small>"; 				} 				return $("<li>").data('ui-autocomplete-item', item).append($("<a>").html(label)).appendTo(ul); 			}; 			// add a listener to the "remove" button so that links can be deleted 			$('#wdid-remove', form).click(function() { 				wikidataRemove(form); 			}); 			$('#input-wdid-label', form).change(function() { 				if (!$(this).val()) { 					wikidataRemove(form); 				} 			}); 			var wikidataRemove = function(form) { 				$("#input-wdid-value", form).val(""); 				$("#input-wdid-label", form).val(""); 				$("#wdid-value-display-container", form).hide(); 				$('#div_wdid_update', form).hide(); 			}; 			$('#wdid-shared', form).click(function() { 				var wikidataRecord = $("#input-wdid-value", form).val(); 				updateWikidataSharedFields(wikidataRecord); 			}); 			var commonsSiteData = { 				apiUrl: ListingEditor.SisterSite.API_COMMONS, 				selector: $('#input-image', form), 				form: form, 				ajaxData: { 					namespace: 6 				}, 				updateLinkFunction: commonsLink 			}; 			ListingEditor.SisterSite.initializeSisterSiteAutocomplete(commonsSiteData); 		}; 		var commonsLink = function(value, form) { 			var commonsSiteLinkData = { 				inputSelector: '#input-image', 				containerSelector: '#image-value-display-container', 				linkContainerSelector: '#image-value-link', 				href: ListingEditor.Config.COMMONS_URL + '/wiki/' + mw.util.wikiUrlencode('File:' + value), 				linkTitle: ListingEditor.Config.TRANSLATIONS.viewCommonsPage 			}; 			sisterSiteLinkDisplay(commonsSiteLinkData, form); 		}; 		var sisterSiteLinkDisplay = function(siteLinkData, form) { 			var value = $(siteLinkData.inputSelector, form).val(); 			if (!value) { 				$(siteLinkData.containerSelector, form).hide(); 			} else { 				var link = $("<a />", { 					target: "_new", 					href: siteLinkData.href, 					title: siteLinkData.linkTitle, 					text: siteLinkData.linkTitle 				}); 				$(siteLinkData.linkContainerSelector, form).html(link); 				$(siteLinkData.containerSelector, form).show(); 			} 		}; 		var updateFieldIfNotNull = function(selector, value) { 			if (value) { 				$(selector).val(value); 			} 		}; 		var updateWikidataSharedFields = function(wikidataRecord) { 			var ajaxUrl = ListingEditor.SisterSite.API_WIKIDATA; 			var ajaxData = { 				action: 'wbgetentities', 				ids: wikidataRecord, 				languages: ListingEditor.Config.LANG 			}; 			var ajaxSuccess = function (jsonObj) { 				var coords = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_COORD); 				var link = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_LINK); 				var image = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_IMAGE); 				var msg = ''; 				if (coords) { 					// trim lat/long to six decimal places 					coords.latitude = ListingEditor.Core.trimDecimal(coords.latitude, 6); 					coords.longitude = ListingEditor.Core.trimDecimal(coords.longitude, 6); 					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedLatitude + ': ' + coords.latitude; 					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedLongitude + ': ' + coords.longitude; 				} 				if (link) { 					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedWebsite + ': ' + link; 				} 				if (image) { 					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedImage + ': ' + image; 				} 				if (msg) { 					if (confirm(ListingEditor.Config.TRANSLATIONS.wikidataShared + '\n' + msg)) { 						if (coords) { 							updateFieldIfNotNull('#input-lat', coords.latitude); 							updateFieldIfNotNull('#input-long', coords.longitude); 						} 						updateFieldIfNotNull('#input-url', link); 						updateFieldIfNotNull('#input-image', image); 						if (image) { 							commonsLink(image); 						} 					} 				} else { 					alert(ListingEditor.Config.TRANSLATIONS.wikidataSharedNotFound); 				} 			}; 			ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); 		}; 		var parseWikiDataResult = function(jsonObj) { 			var results = []; 			for (var i=0; i < $(jsonObj.search).length; i++) { 				var result = $(jsonObj.search)[i]; 				var label = result.label; 				if (result.match && result.match.text) { 					label = result.match.text; 				} 				var data = { 					value: label, 					label: label, 					description: result.description, 					id: result.id 				}; 				results.push(data); 			} 			return results; 		}; 		var wikidataLink = function(form, value) { 			var link = $("<a />", { 				target: "_new", 				href: ListingEditor.Config.WIKIDATA_URL + '/wiki/' + mw.util.wikiUrlencode(value), 				title: ListingEditor.Config.TRANSLATIONS.viewWikidataPage, 				text: value 			}); 			$("#wdid-value-link", form).html(link); 			$("#wdid-value-display-container", form).show(); 			$('#div_wdid_update', form).show(); 		}; 		CREATE_FORM_CALLBACKS.push(wikidataLookup);  		// -------------------------------------------------------------------- 		// LISTING EDITOR FORM SUBMISSION CALLBACKS 		// --------------------------------------------------------------------  		/** 		 * Return the current date in the format "2015-01-15". 		 */ 		var currentLastEditDate = function() { 			var d = new Date(); 			var year = d.getFullYear(); 			// Date.getMonth() returns 0-11 			var month = d.getMonth() + 1; 			if (month < 10) month = '0' + month; 			var day = d.getDate(); 			if (day < 10) day = '0' + day; 			return year + '-' + month + '-' + day; 		};  		/** 		 * Only update last edit date if this is a new listing or if the 		 * "information up-to-date" box checked. 		 */ 		var updateLastEditDate = function(listing, mode) { 			var LISTING_LAST_EDIT_PARAMETER = 'lastedit'; 			var EDITOR_LAST_EDIT_SELECTOR = '#input-last-edit'; 			if (mode == ListingEditor.Core.MODE_ADD || $(EDITOR_LAST_EDIT_SELECTOR).is(':checked')) { 				listing[LISTING_LAST_EDIT_PARAMETER] = currentLastEditDate(); 			} 		}; 		SUBMIT_FORM_CALLBACKS.push(updateLastEditDate);  		// -------------------------------------------------------------------- 		// LISTING EDITOR FORM VALIDATION CALLBACKS 		// --------------------------------------------------------------------  		/** 		 * Verify all listings have at least a name, address or alt value. 		 */ 		var validateListingHasData = function(validationFailureMessages) { 			if ($('#input-name').val() === '' && $('#input-address').val() === '' && $('#input-alt').val() === '') { 				validationFailureMessages.push(ListingEditor.Config.TRANSLATIONS.validationEmptyListing); 			} 		}; 		VALIDATE_FORM_CALLBACKS.push(validateListingHasData);  		/** 		 * Implement SIMPLE validation on the Commons field to verify that the 		 * user has not included a "File" or "Image" namespace. 		 */ 		var validateImage = function(validationFailureMessages) { 			var VALID_IMAGE_REGEX = new RegExp('^(?!(file|image|' + ListingEditor.Config.TRANSLATIONS.image + '):)', 'i'); 			_validateFieldAgainstRegex(validationFailureMessages, VALID_IMAGE_REGEX, '#input-image', ListingEditor.Config.TRANSLATIONS.validationImage); 		}; 		VALIDATE_FORM_CALLBACKS.push(validateImage);  		var _validateFieldAgainstRegex = function(validationFailureMessages, validationRegex, fieldPattern, failureMsg) { 			var fieldValue = $(fieldPattern).val().trim(); 			if (fieldValue !== '' && !validationRegex.test(fieldValue)) { 				validationFailureMessages.push(failureMsg); 			} 		};  		// expose public members 		return { 			CREATE_FORM_CALLBACKS: CREATE_FORM_CALLBACKS, 			SUBMIT_FORM_CALLBACKS: SUBMIT_FORM_CALLBACKS, 			VALIDATE_FORM_CALLBACKS: VALIDATE_FORM_CALLBACKS 		}; 	}();  	ListingEditor.SisterSite = function() { 		var API_WIKIDATA = ListingEditor.Config.WIKIDATA_URL + '/w/api.php'; 		var API_COMMONS = ListingEditor.Config.COMMONS_URL + '/w/api.php'; 		var WIKIDATA_CLAIM_COORD = 'P625'; 		var WIKIDATA_CLAIM_LINK = 'P856'; 		var WIKIDATA_CLAIM_IMAGE = 'P18';  		var _initializeSisterSiteAutocomplete = function(siteData) { 			var currentValue = $(siteData.selector).val(); 			if (currentValue) { 				siteData.updateLinkFunction(currentValue, siteData.form); 			} 			$(siteData.selector).change(function() { 				siteData.updateLinkFunction($(siteData.selector).val(), siteData.form); 			}); 			siteData.selectFunction = function(event, ui) { 				siteData.updateLinkFunction(ui.item.value, siteData.form); 			}; 			var ajaxData = siteData.ajaxData; 			ajaxData.action = 'opensearch'; 			ajaxData.list = 'search'; 			ajaxData.limit = 10; 			ajaxData.redirects = 'resolve'; 			var parseAjaxResponse = function(jsonObj) { 				var results = []; 				var titleResults = $(jsonObj[1]); 				for (var i=0; i < titleResults.length; i++) { 					var result = titleResults[i]; 					var valueWithoutFileNamespace = (titleResults[i].indexOf("File:") != -1) ? titleResults[i].substring("File:".length) : titleResults[i]; 					var titleResult = { value: valueWithoutFileNamespace, label: titleResults[i], description: $(jsonObj[2])[i], link: $(jsonObj[3])[i] }; 					results.push(titleResult); 				} 				return results; 			}; 			_initializeAutocomplete(siteData, ajaxData, parseAjaxResponse); 		}; 		var _initializeAutocomplete = function(siteData, ajaxData, parseAjaxResponse) { 			var autocompleteOptions = { 				source: function(request, response) { 					ajaxData.search = request.term; 					var ajaxSuccess = function(jsonObj) { 						response(parseAjaxResponse(jsonObj)); 					}; 					_ajaxSisterSiteSearch(siteData.apiUrl, ajaxData, ajaxSuccess); 				} 			}; 			if (siteData.selectFunction) { 				autocompleteOptions.select = siteData.selectFunction; 			} 			siteData.selector.autocomplete(autocompleteOptions); 		}; 		// perform an ajax query of a sister site 		var _ajaxSisterSiteSearch = function(ajaxUrl, ajaxData, ajaxSuccess) { 			ajaxData.format = 'json'; 			$.ajax({ 				url: ajaxUrl, 				data: ajaxData, 				dataType: 'jsonp', 				success: ajaxSuccess 			}); 		}; 		// parse the wikidata "claim" object from the wikidata response 		var _wikidataClaim = function(jsonObj, value, property) { 			var entity = _wikidataEntity(jsonObj, value); 			if (!entity || !entity.claims || !entity.claims[property]) { 				return null; 			} 			var propertyObj = entity.claims[property]; 			if (!propertyObj || propertyObj.length < 1 || !propertyObj[0].mainsnak || !propertyObj[0].mainsnak.datavalue) { 				return null; 			} 			return propertyObj[0].mainsnak.datavalue.value; 		}; 		// parse the wikidata "entity" object from the wikidata response 		var _wikidataEntity = function(jsonObj, value) { 			if (!jsonObj || !jsonObj.entities || !jsonObj.entities[value]) { 				return null; 			} 			return jsonObj.entities[value]; 		}; 		// parse the wikidata display label from the wikidata response 		var _wikidataLabel = function(jsonObj, value) { 			var entityObj = _wikidataEntity(jsonObj, value); 			if (!entityObj || !entityObj.labels || !entityObj.labels.en) { 				return null; 			} 			return entityObj.labels.en.value; 		}; 		// expose public members 		return { 			API_WIKIDATA: API_WIKIDATA, 			API_COMMONS: API_COMMONS, 			WIKIDATA_CLAIM_COORD: WIKIDATA_CLAIM_COORD, 			WIKIDATA_CLAIM_LINK: WIKIDATA_CLAIM_LINK, 			WIKIDATA_CLAIM_IMAGE: WIKIDATA_CLAIM_IMAGE, 			initializeSisterSiteAutocomplete: _initializeSisterSiteAutocomplete, 			ajaxSisterSiteSearch: _ajaxSisterSiteSearch, 			wikidataClaim: _wikidataClaim, 			wikidataLabel: _wikidataLabel 		}; 	}();  	/* *********************************************************************** 	 * ListingEditor.Core contains code that should be shared across different 	 * Wikivoyage languages.  This code uses the custom configurations in the 	 * ListingEditor.Config and ListingEditor.Callback modules to initialize 	 * the listing editor and process add and update requests for listings. 	 * ***********************************************************************/ 	ListingEditor.Core = function() { 		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 EDIT_LINK_SELECTOR = '.vcard-edit-button'; 		var SAVE_FORM_SELECTOR = '#progress-dialog'; 		var CAPTCHA_FORM_SELECTOR = '#captcha-dialog'; 		var sectionText, inlineListing, 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. 		 */ 		var listingEditorAllowedForCurrentPage = function() { 			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 true; 		};  		/** 		 * Generate the form UI for the listing editor.  If editing an existing 		 * listing, pre-populate the form input fields with the existing values. 		 */ 		var createForm = function(mode, listingTemplateAsMap) { 			var form = $(ListingEditor.Config.EDITOR_FORM_HTML); 			// make sure the select dropdown includes any custom "type" values 			//**RUS** commented out 			//var listingType = listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER]; 			//if (isCustomListingType(listingType)) { 				//$('#' + listingParameters[ListingEditor.Config.LISTING_TYPE_PARAMETER].id, form).append('<option value="' + listingType + '">' + listingType + '</option>'); 			//} 			 			//**RUS** modified 			var listingParameters = ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS; 			// populate the empty form with existing values 			for (var parameter in ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS) { 				var paramId = listingParameters[parameter].id; 				var input = $('#' + paramId, form); 				 				var paramValue = listingTemplateAsMap[parameter]; 				if (paramValue) { 					if (parameter == "star" || parameter == "unesco") { 						if (paramValue == "yes") { 							input.prop('checked', true); 						} 	 					} else { 						input.val(paramValue); 					} 					 					if (parameter == "format") { 						$('#div_format', form).show();  					} if (parameter == "email") { 						$('#div_email', form).show(); 					} 				} 			} 			for (var i=0; i < ListingEditor.Callbacks.CREATE_FORM_CALLBACKS.length; i++) { 				ListingEditor.Callbacks.CREATE_FORM_CALLBACKS[i](form, mode); 			} 			return form; 		};  		/** 		 * Wrap the h2/h3 heading tag and everything up to the next section 		 * (including sub-sections) in a div to make it easier to traverse the DOM. 		 * This change introduces the potential for code incompatibility should the 		 * div cause any CSS or UI conflicts. 		 */ 		var wrapContent = function() { 			$('#bodyContent').find('.mw-heading2').each(function(){ 				$(this).nextUntil(".mw-heading1, .mw-heading2").addBack().wrapAll('<div class="mw-h2section" />'); 			}); 			$('#bodyContent').find('.mw-heading3').each(function(){ 				$(this).nextUntil(".mw-heading1,.mw-heading2,.mw-heading3").addBack().wrapAll('<div class="mw-h3section" />'); 			}); 		};  		/** 		 * Place an "add listing" link at the top of each section heading next to 		 * the "edit" link in the section heading. 		 */ 		var addListingButtons = function() { 			if ($(ListingEditor.Config.DISALLOW_ADD_LISTING_IF_PRESENT.join(',')).length > 0) { 				return false; 			} 			for (var sectionId in ListingEditor.Config.SECTION_TO_TEMPLATE_TYPE) { 				sectionId = sectionId.replace(/ /g,'_'); 				// use a pattern rather than searching by ID directly in case 				// the article uses top-level headings in sub-sections 				//**RUS** changed # to [id= 				var topHeading = $('h2[id="' + sectionId + '"]'); 				if (topHeading.length) { 					insertAddListingPlaceholder(topHeading); 					var parentHeading = topHeading.closest('div.mw-h2section'); 					$('h3', parentHeading).each(function() { 						insertAddListingPlaceholder(this); 					}); 				} 			} 			$('.listingeditor-add').click(function() { 				initListingEditorDialog(MODE_ADD, $(this)); 			}); 		};  		/** 		 * Utility function for appending the "add listing" link text to a heading. 		 */ 		var insertAddListingPlaceholder = function(parentHeading) { 			var editSection = $(parentHeading).next('.mw-editsection'); 			editSection.append('<span class="mw-editsection-bracket">[</span><a href="javascript:" class="listingeditor-add">'+ListingEditor.Config.TRANSLATIONS.add+'</a><span class="mw-editsection-bracket">]</span>'); 		};  		/** 		 * Place an "edit" link next to all existing listing tags. 		 */ 		var addEditButtons = function() { 			var editButton = $('<span class="vcard-edit-button noprint">') 				//**RUS** added class and title parameters 				.html('<a href="javascript:" class="icon-pencil" title="' + ListingEditor.Config.TRANSLATIONS.editTitle + '">'+ListingEditor.Config.TRANSLATIONS.edit+'</a>' ) 				.click(function() { 					initListingEditorDialog(MODE_EDIT, $(this)); 				}); 			// if there is already metadata present add a separator 			$(ListingEditor.Config.EDIT_LINK_CONTAINER_SELECTOR).each(function() { 				if (!isElementEmpty(this)) { 					//**RUS** removed '|' sign 	                 $(this).append('&nbsp;'); 				} 			}); 			// append the edit link 			$(ListingEditor.Config.EDIT_LINK_CONTAINER_SELECTOR).append( editButton ); 		};  		/** 		 * Determine whether a listing entry is within a paragraph rather than 		 * an entry in a list; inline listings will be formatted slightly 		 * differently than entries in lists (no newlines in the template syntax, 		 * skip empty fields). 		 */ 		var isInline = function(entry) { 			// if the edit link clicked is within a paragraph AND, since 			// newlines in a listing description will cause the Mediawiki parser 			// to close an HTML list (thus triggering the "is edit link within a 			// paragraph" test condition), also verify that the listing is 			// within the expected listing template span tag and thus hasn't 			// been incorrectly split due to newlines. 			return (entry.closest('p').length !== 0 && entry.closest('span.vcard').length !== 0); 		};  		/** 		 * Given a DOM element, find the nearest editable section (h2 or h3) that 		 * it is contained within. 		 */ 		var findSectionHeading = function(element) { 			return element.closest('div.mw-h3section, div.mw-h2section'); 		};  		/** 		 * Given an editable heading, examine it to determine what section index 		 * the heading represents.  First heading is 1, second is 2, etc. 		 */ 		var findSectionIndex = function(heading) { 			if (heading === undefined) { 				return 0; 			} 			var link = heading.find('.mw-editsection a').attr('href'); 			return (link !== undefined) ? link.split('=').pop() : 0; 		};  		/** 		 * Given an edit link that was clicked for a listing, determine what index 		 * that listing is within a section.  First listing is 0, second is 1, etc. 		 */ 		var findListingIndex = function(sectionHeading, clicked) { 			var count = 0; 			$(EDIT_LINK_SELECTOR, sectionHeading).each(function() { 				if (clicked.is($(this))) { 					return false; 				} 				count++; 			}); 			return count; 		};  		/** 		 * Return the listing template type appropriate for the section that 		 * contains the provided DOM element (example: "see" for "See" sections, 		 * etc).  If no matching type is found then the default listing template 		 * type is returned. 		 * from https://en.wikivoyage.org/wiki/MediaWiki:Gadget-ListingEditor2023Main.js 		 */ 		var findListingTypeForSection = function(entry) { 			var closestSection = entry.closest('div.mw-h2section, section'); 			while (closestSection.is('section') && closestSection.parents('section').length) { 				// check it's the top level section. 				closestSection = closestSection.parent(); 			} 			var closestHeading = closestSection.find('.mw-heading2 h2, h2 .mw-headline'); 			var sectionType = closestHeading.attr('id'); 			//**RUS** added the following line 			sectionType = decodeURIComponent(sectionType.replace(/\./g, '%').replace(/_/g, '%20')); 			for (var sectionId in ListingEditor.Config.SECTION_TO_TEMPLATE_TYPE) { 				if (sectionType == sectionId) { 					return ListingEditor.Config.SECTION_TO_TEMPLATE_TYPE[sectionId]; 				} 			} 			//**RUS** changed 'listing' to 'other' 			return "other"; 			 		};  		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() { 			var regex = []; 			for (var key in ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS) { 				regex.push(key); 			} 			//**RUS** modified regex.join('|') to listing 			return new RegExp('{{\\s*(listing)\\b','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) { 			var typeRegex = getListingTypesRegex(); 			// remove the trailing braces 			listingTemplateWikiSyntax = listingTemplateWikiSyntax.slice(0,-2); 			// convert "{{see" to {{listing|type=see" 			listingTemplateWikiSyntax = listingTemplateWikiSyntax.replace(typeRegex,'{{listing|type=$1'); 			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); 			} 			if (listingTemplateAsMap["description"]) { 				// convert paragraph tags to newlines so that the description is more 				// readable in the editor window 				listingTemplateAsMap["description"] = listingTemplateAsMap["description"].replace(/\s*<p>\s*/g, '\n\n'); 			} 			// sanitize the listing type param to match the configured values, so 			// if the listing contained "Do" it will still match the configured "do" 			for (var key in ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS) { 				if (listingTemplateAsMap["type"].toLowerCase() === key.toLowerCase()) { 					listingTemplateAsMap["type"] = key; 					break; 				} 			} 			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; 		};  		/** 		 * 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, clicked) { 			var listingType; 			if (mode === MODE_ADD) { 				listingType = findListingTypeForSection(clicked); 			} 			var sectionHeading = findSectionHeading(clicked); 			var sectionIndex = findSectionIndex(sectionHeading); 			var listingIndex = (mode === MODE_ADD) ? -1 : findListingIndex(sectionHeading, clicked); 			inlineListing = (mode === MODE_EDIT && isInline(clicked)); 			$.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, listingType); 			}).fail(function(jqXHR, textStatus, errorThrown) { 				alert(ListingEditor.Config.TRANSLATIONS.ajaxInitFailure + ': ' + textStatus + ' ' + errorThrown); 			}); 		};  		/** 		 * 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, listingType) { 			sectionText = stripComments(sectionText); 			mw.loader.using( ['jquery.ui'], function () { 				var listingTemplateAsMap, listingTemplateWikiSyntax; 				if (mode == MODE_ADD) { 					listingTemplateAsMap = {}; 					listingTemplateAsMap["type"] = listingType; 				} else { 					listingTemplateWikiSyntax = getListingWikitextBraces(listingIndex); 					listingTemplateAsMap = wikiTextToListing(listingTemplateWikiSyntax); 					listingType = listingTemplateAsMap["type"]; 				} 				// if a listing editor dialog is already open, get rid of it 				if ($(ListingEditor.Config.EDITOR_FORM_SELECTOR).length > 0) { 					$(ListingEditor.Config.EDITOR_FORM_SELECTOR).dialog('destroy').remove(); 				} 				var form = $(createForm(mode, listingTemplateAsMap)); 				// wide dialogs on huge screens look terrible 				var windowWidth = $(window).width(); 				var dialogWidth = (windowWidth > ListingEditor.Config.MAX_DIALOG_WIDTH) ? ListingEditor.Config.MAX_DIALOG_WIDTH : 'auto'; 				// modal form - must submit or cancel 				form.dialog({ 					modal: true, 					height: 'auto', 					width: dialogWidth, 					title: (mode == MODE_ADD) ? ListingEditor.Config.TRANSLATIONS.addTitle : ListingEditor.Config.TRANSLATIONS.editTitle, 					dialogClass: 'listing-editor-dialog', 					buttons: [ 					{ 						text: '?', 						id: 'listing-help', 						click: function() { window.open(ListingEditor.Config.TRANSLATIONS.helpPage);} 					}, 					{ 						text: ListingEditor.Config.TRANSLATIONS.submit, click: function() { 							if (validateForm()) { 								formToText(mode, listingTemplateWikiSyntax, listingTemplateAsMap, sectionNumber); 								$(this).dialog('close'); 							} 						} 					}, 					{ 						text: ListingEditor.Config.TRANSLATIONS.cancel, 						click: function() { 							$(this).dialog('destroy').remove(); 						} 					} 					], 					create: function() { 						$('.ui-dialog-buttonpane').append('<div class="listing-license">' + ListingEditor.Config.TRANSLATIONS.licenseText + '</div>'); 					} 				}); 			}); 		};  		/** 		 * 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; 		};  		/** 		 * Determine if the specified listing type is a custom type - for example "go" 		 * instead of "see", "do", "listing", etc. 		 */ 		//**RUS** no need - commented out 		//var isCustomListingType = function(listingType) { 			//return !(listingType in ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS); 		//};  		/** 		 * Logic invoked on form submit to analyze the values entered into the 		 * editor form and to block submission if any fatal errors are found. 		 */ 		var validateForm = function() { 			var validationFailureMessages = []; 			for (var i=0; i < ListingEditor.Callbacks.VALIDATE_FORM_CALLBACKS.length; i++) { 				ListingEditor.Callbacks.VALIDATE_FORM_CALLBACKS[i](validationFailureMessages); 			} 			if (validationFailureMessages.length > 0) { 				alert(validationFailureMessages.join('\n')); 				return false; 			} 			// newlines in listing description won't render properly in lists, so 			// replace them with <p> tags 			$('#input-description').val($.trim($('#input-description').val()).replace(/\n+/g, '<p>')); 			var webRegex = new RegExp('^https?://', 'i'); 			var url = $('#input-url').val(); 			if (!webRegex.test(url) && url !== '') { 				$('#input-url').val('http://' + url); 			} 			return true; 		};  		/** 		 * 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) {  			for (var parameter in ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS) { 				var input = $("#" + ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS[parameter].id); 				//**RUS** added the following condition 				if (parameter == 'star' || parameter == 'unesco') { 					listing[parameter]= input.is(':checked') ? 'yes' : ''; 				} else { 					listing[parameter] = input.val(); 				} 				 			} 			for (var i=0; i < ListingEditor.Callbacks.SUBMIT_FORM_CALLBACKS.length; i++) { 				ListingEditor.Callbacks.SUBMIT_FORM_CALLBACKS[i](listing, mode); 			} 			var text = listingToStr(listing); 			var summary = editSummarySection(); 			if (mode == MODE_ADD) { 				summary = updateSectionTextWithAddedListing(summary, text, listing); 			} else { 				summary = updateSectionTextWithEditedListing(summary, text, listingTemplateWikiSyntax); 			} 			summary += $("#input-name").val(); 			if ($(ListingEditor.Config.EDITOR_SUMMARY_SELECTOR).val() !== '') { 				summary += ' - ' + $(ListingEditor.Config.EDITOR_SUMMARY_SELECTOR).val(); 			} 			var minor = $(ListingEditor.Config.EDITOR_MINOR_EDIT_SELECTOR).is(':checked') ? true : false; 			saveForm(summary, minor, sectionNumber, '', ''); 			return; 		};  		/** 		 * 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 += ListingEditor.Config.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 === -1) { 				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; 			if ($(ListingEditor.Config.EDITOR_CLOSED_SELECTOR).is(':checked')) { 				listingWikiText = ''; 				summary += ListingEditor.Config.TRANSLATIONS.removed; 				var listRegex = new RegExp('(\\n+[\\:\\*\\#]*)?\\s*' + replaceSpecial(listingTemplateWikiSyntax)); 				sectionText = sectionText.replace(listRegex, listingWikiText); 			} else { 				summary += ListingEditor.Config.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">' + ListingEditor.Config.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(ListingEditor.Config.TRANSLATIONS.submitApiError + ' "' + data.error.code + '": ' + data.error.info ); 				} else if (data && data.edit.spamblacklist) { 					saveFailed(ListingEditor.Config.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(ListingEditor.Config.TRANSLATIONS.submitUnknownError); 				} 			}).fail(function(code, result) { 				if (code === "http") { 					saveFailed(ListingEditor.Config.TRANSLATIONS.submitHttpError + ': ' + result.textStatus ); 				} else if (code === "ok-but-empty") { 					saveFailed(ListingEditor.Config.TRANSLATIONS.submitEmptyError); 				} else { 					saveFailed(ListingEditor.Config.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(); 			$(ListingEditor.Config.EDITOR_FORM_SELECTOR).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(ListingEditor.Config.TRANSLATIONS.externalLinks); 			var image = $('<img class="fancycaptcha-image">') 					.attr('src', captchaImgSrc) 					.appendTo(captcha); 			var label = $('<label for="input-captcha">').text(ListingEditor.Config.TRANSLATIONS.enterCaptcha).appendTo(captcha); 			var input = $('<input id="input-captcha" type="text">').appendTo(captcha); 			captcha.dialog({ 				modal: true, 				title: ListingEditor.Config.TRANSLATIONS.enterCaptcha, 				buttons: [ 					{ 						text: ListingEditor.Config.TRANSLATIONS.submit, click: function() { 							saveForm(summary, minor, sectionNumber, captchaId, $('#input-captcha').val()); 							$(this).dialog('destroy').remove(); 						} 					}, 					{ 						text: ListingEditor.Config.TRANSLATIONS.cancel, click: function() { 							$(this).dialog('destroy').remove(); 						} 					} 				] 			}); 		};  		/** 		 * Convert the listing map back to a wiki text string. 		 */ 		var listingToStr = function(listing) { 			var listingType = listing["type"]; 			var listingParameters = ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS; 			var saveStr = '{{'; 			 			//**RUS** modified 			saveStr += 'listing|type=' + listingType;  			if (!inlineListing && listingParameters["type"].newline) { 				saveStr += '\n'; 			} 			for (var parameter in listingParameters) { 				if (parameter === "type") { 					// "type" parameter was handled previously 					continue; 				} 				if (parameter === "description") { 					// processed last 					continue; 				} 				 				// **RUS** if format, unesco or star parameter is empty - don't include it in the listing 		        if (((parameter == 'format') || (parameter == 'unesco') || (parameter == 'star')) && listing[parameter] == '') {  		            continue; 		        } 				 				saveStr += '| ' + parameter + '=' + listing[parameter]; 				 				if (!saveStr.match(/\n$/)) { 					if (!inlineListing && listingParameters[parameter].newline) { 						saveStr = rtrim(saveStr) + '\n'; 					} else if (!saveStr.match(/ $/)) { 						saveStr += ' '; 					} 				} 			} 			if (ListingEditor.Config.ALLOW_UNRECOGNIZED_PARAMETERS) { 				// append any unexpected values 				for (var key in listing) { 					if (listingParameters[key]) { 						// this is a known field 						continue; 					} 					if (listing[key] === '') { 						// skip unrecognized fields without values 						continue; 					} 					saveStr += '| ' + key + '=' + listing[key]; 					saveStr += (inlineListing) ? ' ' : '\n'; 				} 			} 			saveStr += '| description=' + listing["description"]; 			saveStr += (inlineListing || !listingParameters["description"].newline) ? ' ' : '\n'; 			saveStr += '}}'; 			return saveStr; 		};  		/** 		 * Determine if the specified DOM element contains only whitespace or 		 * whitespace HTML characters (&nbsp;). 		 */ 		var isElementEmpty = function(element) { 			var text = $(element).text(); 			if (!text.trim()) { 				return true; 			} 			return (text.trim() == '&nbsp;'); 		};  		/** 		 * Trim whitespace at the end of a string. 		 */ 		var rtrim = function(str) { 			return str.replace(/\s+$/, ''); 		};  		/** 		 * Trim decimal precision if it exceeds the specified number of 		 * decimal places. 		 */ 		var trimDecimal = function(value, precision) { 			if ($.isNumeric(value) && value.toString().length > value.toFixed(precision).toString().length) { 				value = value.toFixed(precision); 			} 			return value; 		};  		/** 		 * 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; 			} 			wrapContent(); 			mw.hook( 'wikipage.content' ).add( addListingButtons.bind( this ) ); 			addEditButtons(); 		};  		// expose public members 		return { 			MODE_ADD: MODE_ADD, 			MODE_EDIT: MODE_EDIT, 			trimDecimal: trimDecimal, 			init: initListingEditor 		}; 	}();  	$(document).ready(function() { 		ListingEditor.Core.init(); 	});  } ( mediaWiki, jQuery ) );  //</nowiki>