מדיה ויקי:Gadget-ListingEditor.js

מתוך ויקימסע
קפיצה לניווט קפיצה לחיפוש

הערה: לאחר השמירה, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.

  • פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload), או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
  • גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
  • אינטרנט אקספלורר: להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh), או ללחוץ על צירוף המקשים Ctrl-F5.
  • אופרה: לפתוח תפריט ← הגדרות (במחשב מק: Opera ← העדפות) ואז ללחוץ על פרטיות ואבטחה ← מחק היסטוריית גלישה ← Cached images and files.
/******************************************************************
   Listing Editor v2.1.1-he
     Original author:
     - torty3
     Additional contributors:
     - Andyrom75
     - Wrh2
     - Wang Shenwei (ClockPicker)
     
     v2.1.1-he Changes:
     - Checkboxs for local Additional Features:
    	* UNESCO World Heritage Sites for see & do listings.
    	* Kosher, Vegetarian and Halal for eat listings.
    	* Gay Friendly for drink listings.
    	* Campfire for do listings.
    	* Accessibility, WiFi and Tripadvisor's featured for all the types.
     - ClockPicker for checkin & checkout.
     - Type and type group with related color sample (based on dewikivoyage).
     - Automatic text direction for "name", "alt" and "address" fields.
     - Support for local types: fun, featured city and featured destination.
     - Support for local Facebook parameter.
     - Facebook & UNESCO parameters can be loaded from Wikidata.
     
     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
     - Do not perform listing validations when deleting a listing.
     - Add support for mobile devices.
     - 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_TEMPLATES
********************************************************************/
//<nowiki>

( function ( mw, $ ) {
	'use strict';
		importScript("MediaWiki:ClockPicker.js");

	/* ***********************************************************************
	 * 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 = 'he';
		var COMMONS_URL = '//commons.wikimedia.org';
		var WIKIDATA_URL = '//www.wikidata.org';
		var WIKIPEDIA_URL = '//he.wikipedia.org';
		var WIKIDATA_SITELINK_WIKIPEDIA = 'hewiki';
		var TRANSLATIONS = {
			'addTitle' : 'הוספת רשומה חדשה',
			'editTitle' : 'עריכת רשומה קיימת',
			'add': 'הוספת רשומה',
			'edit': 'עריכה',
			'saving': 'בשמירה...',
			'submit': 'פרסום',
			'cancel': 'ביטול',
			'addExpandedTitle': 'הוספת רשומה חדשה באמצעות עורך הרשימות',
			'validationEmptyListing': 'אנא הזינו שם כלשהו או כתובת',
			'validationEmail': 'אנא ודאו שכתובת המייל שהוזנה תקינה',
			'validationWikipedia': 'אנא הכניסו את שם הערך המקביל בוויקיפדיה כמו שהוא, אין להכניס כתובת URL',
			'validationImage': 'אנא הכניסו את הכותרת של התמונה בוויקישיתוף ללא קידומת',
			'image': '', //Local prefix for Image (or File)
			'added': 'הוספת הרשומה: ',
			'updated': 'עדכון הרשומה: ',
			'removed': 'הסרת הרשומה: ',
			'helpPage': 'http://he.wikivoyage.org/wiki/ויקימסע:עורך הרשומות',
			'enterCaptcha': 'הזינו CAPTCHA',
			'externalLinks': 'עריכתכם כוללת הוספת קישורים חיצוניים חדשים.',
			// license text should match MediaWiki:Wikimedia-copyrightwarning
			'licenseText': 'לחיצה על "פרסום" מהווה הסכמתך ל<a class="external" target="_blank" href="//wikimediafoundation.org/wiki/Terms_of_use">תנאי השימוש</a> ואת הסכמתך הבלתי־חוזרת לשחרר את תרומתך בכפוף לרישיון <a class="external" target="_blank" href="//en.wikivoyage.org/wiki/Wikivoyage:Full_text_of_the_Attribution-ShareAlike_3.0_license">CC-BY-SA 3.0 License</a> ולרישיון GFDL. זוהי גם הסכמתך לכך שקישור או כתובת URL הוא ייחוס מספיק בהתאם לרישיון Creative Commons.',
			'ajaxInitFailure': 'שגיאה: אתחול עורך הרשומות נכשל',
			'sharedImage': 'תמונה',
			'sharedFacebook': 'פייסבוק',
			'sharedLatitude': 'רוחב (lat)',
			'sharedLongitude': 'אורך (long)',
			'sharedWebsite': 'אתר אינטרנט',
			'sharedWikipedia': 'ויקיפדיה',
			'sharedUnesco': 'אתר מורשת עולמית: כן',
			'submitApiError': 'שגיאה: The server returned an error while attempting to save the listing, please try again',
			'submitBlacklistError': 'Error: A value in the data submitted has been blacklisted, please remove the blacklisted pattern and try again',
			'submitUnknownError': 'Error: An unknown error has been encountered while attempting to save the listing, please try again',
			'submitHttpError': 'Error: The server responded with an HTTP error while attempting to save the listing, please try again',
			'submitEmptyError': 'Error: The server returned an empty response while attempting to save the listing, please try again',
			'viewCommonsPage' : 'צפו בעמוד התמונה בוויקישיתוף',
			'viewWikidataPage' : 'צפו בפריט המקביל בוויקינתונים',
			'viewWikipediaPage' : 'צפו בערך המקביל בוויקיפדיה',
			'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_TEMPLATES 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 = {
			'מוקדי': 'מוקדי',
			'מוקדי_עניין': 'מוקדי',
			'מוקדי_העניין': 'מוקדי',
			'מוקדי_העניין_העיקריים': 'מוקדי',
			'אטרקציות': 'מוקדי',
			'פעילויות': 'פעילויות',
			'מסלולים': 'פעילויות',
			'מסלולי_הליכה': 'פעילויות',
			'אירועים_נוספים': 'פעילויות',
			'בידור': 'בידור',
			'בתי קולנוע': 'בידור',
			'תיאטראות': 'בידור',
			'בתי אופרה': 'בידור',
			'קולנוע': 'בידור',
			'קניות': 'קניות',
			'קניונים': 'קניות',
			'שווקים': 'קניות',
			'אוכל': 'אוכל',
			'שתייה': 'שתייה',
			'חיי_לילה': 'שתייה',
			'שתייה_וחיי_לילה': 'שתייה',
			'לינה': 'לינה',
			'בתי_מלון': 'לינה',
			'חשוב_לדעת': 'רשומה',
			'אוכל_ושתייה': 'אוכל'
		};
		// 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 = ['#Cities', '#Other_destinations', '#Islands', '#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
		var DEFAULT_LISTING_TEMPLATE = 'רשומה';
		var LISTING_TYPE_PARAMETER = 'סוג';
		var LISTING_CONTENT_PARAMETER = 'תיאור';
		// selector that identifies the HTML elements into which the 'edit' link
		// for each listing will be placed
		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_TEMPLATES
		// 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.

		var LISTING_TEMPLATE_PARAMETERS = {
			'סוג': { id:'input-type', hideDivIfEmpty: 'div_type', newline: true },
			'שם': { id:'input-name' },
			'שם חלופי': { id:'input-alt' },
			'האתר הרשמי': { id:'input-url' },
			'מייל': { id:'input-email', newline: true },
			'כתובת': { id:'input-address' },
			'lat': { id:'input-lat' },
			'long': { id:'input-long' },
			'הוראות': { id:'input-directions', newline: true },
			'טלפון': { id:'input-phone' },
			'שיחה בחינם': { id:'input-tollfree' },
			'פקס': { id:'input-fax', hideDivIfEmpty: 'div_fax'},
			'פייסבוק': { id:'input-facebook', newline: true },
			'שעות': { id:'input-hours' },
			"צ'ק-אין": { id:'input-checkin', hideDivIfEmpty: 'div_checkin', skipIfEmpty: true },
			"צ'ק-אאוט": { id:'input-checkout', hideDivIfEmpty: 'div_checkout', skipIfEmpty: true },
			'מחיר': { id:'input-price', newline: true },
			'ויקיפדיה': { id:'input-wikipedia', skipIfEmpty: true },
			'ויקינתונים': { id:'input-wikidata-value', skipIfEmpty: true },
			'תמונה': { id:'input-image', newline: true, skipIfEmpty: true },
			'מאפיינים נוספים': { id:'input-additionalfeatures', newline: true, skipIfEmpty: true },
			'עודכן_לאחרונה': { id:'input-lastedit', newline: true, skipIfEmpty: true },
			'תיאור': { id:'input-content', newline: true }
		};
		// override the default settings for "sleep" listings since that
		// listing type uses "checkin"/"checkout" instead of "hours"
		var SLEEP_TEMPLATE_PARAMETERS = $.extend(true, {}, LISTING_TEMPLATE_PARAMETERS, {
			'שעות': { hideDivIfEmpty: 'div_hours', skipIfEmpty: true },
			"צ'ק-אין": { hideDivIfEmpty: null, skipIfEmpty: false },
			"צ'ק-אאוט": { hideDivIfEmpty: null, skipIfEmpty: false }
		});
		
		// color samples for each type
		var COLOR_PROPERTIES = {
			'רשומה': '#228B22',
			'מוקדי': '#4682B4',
			'פעילויות': '#808080',
			'בידור': '#800000',
			'קניות': '#008080',
			'אוכל': '#D2691E',
			'שתייה': '#000000',
			'לינה': '#000080',
			'יישוב מרכזי': '#0000FF',
			'יעד מרכזי': '#0ea2f1',
			};
		
		var LISTING_ADDITIONAL_FEATURES = {
			'רשומה מובילה בטריפאדוויזר': { id:'istripadvisor', typesToShow: [] },
			'אתר מורשת עולמית': { id:'isunesco', typesToShow: ['מוקדי', 'פעילויות', 'יישוב מרכזי'] },
			'נגישות': { id:'isaccessibility', typesToShow: [''] },
			'אינטרנט אלחוטי': { id:'iswifi', typesToShow: [''] },
			'אש': { id:'iscampfire', typesToShow: ['פעילויות'] },
			'כשר': { id:'iskosher', typesToShow: ['אוכל'] },
			'צמחוני': { id:'isvegetarian' ,typesToShow: ['אוכל'] },
			'חלאל': { id:'ishalal', typesToShow: ['אוכל'] },
			'גיי פרנדלי': { id:'islgbt', typesToShow: ['שתייה'] },
		};
		
		// map the template name to configuration information needed by the listing
		// editor
		var LISTING_TEMPLATES = {
			'רשומה': LISTING_TEMPLATE_PARAMETERS,
			'מוקדי': LISTING_TEMPLATE_PARAMETERS,
			'פעילויות': LISTING_TEMPLATE_PARAMETERS,
			'בידור': LISTING_TEMPLATE_PARAMETERS,
			'קניות': LISTING_TEMPLATE_PARAMETERS,
			'אוכל': LISTING_TEMPLATE_PARAMETERS,
			'שתייה': LISTING_TEMPLATE_PARAMETERS,
			'לינה': SLEEP_TEMPLATE_PARAMETERS,
			'יישוב מרכזי': LISTING_TEMPLATE_PARAMETERS,
			'יעד מרכזי': LISTING_TEMPLATE_PARAMETERS
		};

		// --------------------------------------------------------------------
		// 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_TEMPLATES 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-fullwidth" placeholder="שם המקום" id="input-name"></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_url">' +
					'<td class="editor-label-col"><label for="input-url">האתר הרשמי<span class="wikidata-update"></span></label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="http://www.example.com" id="input-url" dir=ltr></td>' +
				'</tr>' +
				'<tr id="div_facebook">' +
					'<td class="editor-label-col"><label for="input-facebook">פייסבוק<span class="wikidata-update"></span></label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="http://www.facebook.com/example" id="input-facebook" dir=ltr></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" dir=auto></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-fullwidth" placeholder="+55 555 555-5555" id="input-phone" dir=ltr></td>' +
				'</tr>' +
				'<tr id="div_tollfree">' +
					'<td class="editor-label-col"><label for="input-tollfree">שיחה בחינם</label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="+1-800-100-1000" id="input-tollfree" dir=ltr></td>' +
				'</tr>' +
				'<tr id="div_fax">' +
					'<td class="editor-label-col"><label for="input-fax">פקס</label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="+55 555 555-555" id="input-fax" dir=ltr></td>' +
				'</tr>' +
				'<tr id="div_email">' +
					'<td class="editor-label-col"><label for="input-email">מייל</label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="hello@example.com" id="input-email" dir=ltr></td>' +
				'</tr>' +
				'<tr id="div_lastedit" style="display: none;">' +
					'<td class="editor-label-col"><label for="input-lastedit">עודכן לאחרונה</label></td>' +
					'<td><input type="text" size="10" placeholder="2015-01-15" id="input-lastedit"></td>' +
				'</tr>' +
				'<tr id="div_wikidata_update" style="display: none">' +
					'<td class="editor-label-col">&#160;</td>' +
					'<td><span class="wikidata-update"></span><a href="javascript:" id="wikidata-shared">עדכון שדות המידע על בסיס נתונים עדכניים מוויקינתונים</a></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" class="editor-partialwidth" placeholder="סוג הרשומה">' +
							'<option value="רשומה">רשומה רגילה</option>' +
							'<option value="מוקדי">מוקדי עניין</option>' +
							'<option value="פעילויות">פעילויות</option>' +
							'<option value="בידור">בידור</option>' +
							'<option value="קניות">קניות</option>' +
							'<option value="אוכל">אוכל</option>' +
							'<option value="שתייה">שתייה</option>' +
							'<option value="לינה">לינה</option>' +
							'<option value="יישוב מרכזי">יישוב מרכזי</option>' +
							'<option value="יעד מרכזי">יעד מרכזי</option>' +
						'</select>' +
					'</td>' +
				'</tr>' +
				'<tr id="div_lat">' +
					'<td class="editor-label-col"><label for="input-lat">רוחב (lat)<span class="wikidata-update"></span></label></td>' +
					'<td><input type="text" class="editor-partialwidth" placeholder="11.11111" id="input-lat" dir=ltr>' +
					// update the ListingEditor.Callbacks.initFindOnMapLink
					// method if this field is removed or modified
					'&nbsp;<a id="geomap-link" target="_blank" href="//tools.wmflabs.org/wikivoyage/w/geomap.php"><u>איתור המקום במפה</u></a></td>' +
				'</tr>' +
				'<tr id="div_long">' +
					'<td class="editor-label-col"><label for="input-long">אורך (long)<span class="wikidata-update"></span></label></td>' +
					'<td><input type="text" class="editor-partialwidth" placeholder="111.11111" id="input-long" dir=ltr></td>' +
				'</tr>' +
				'<tr id="div_hours">' +
					'<td class="editor-label-col"><label for="input-hours">שעות</label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="שעות הפתיחה והסגירה של המקום. למשל: 09:00-17:00" id="input-hours"></td>' +
				'</tr>' +
				'<tr id="div_checkin">' +
					'<td class="editor-label-col"><label for="input-checkin">צ\'ק-אין</label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="זמן הצ\'ק-אין" id="input-checkin"></td>' +
				'</tr>' +
				'<tr id="div_checkout">' +
					'<td class="editor-label-col"><label for="input-checkout">צ\'ק-אאוט</label></td>' +
					'<td><input type="text" class="editor-fullwidth" placeholder="זמן הצ\'ק-אאוט" id="input-checkout"></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" placeholder="עלות הכניסה או השירות במקום" id="input-price">' +
						'<span id="span_currency">' +
							'<span class="currency-signs"> <a href="javascript:">\u20AA</a></span>' +
							'<span class="currency-signs"> <a href="javascript:">\u0024</a></span>' +
							'<span class="currency-signs"> <a href="javascript:">\u00A3</a></span>' +
							'<span class="currency-signs"> <a href="javascript:">\u20AC</a></span>' +
							'<span class="currency-signs"> <a href="javascript:">\u00A5</a></span>' +
						'</span>' +
					'</td>' +
				'</tr>' +
				'<tr id="div_wikidata">' +
					'<td class="editor-label-col"><label for="input-wikidata-label">ויקינתונים</label></td>' +
					'<td>' +
						'<input type="text" class="editor-partialwidth" placeholder="פריט מקביל בוויקינתונים" id="input-wikidata-label">' +
						'<input type="hidden" id="input-wikidata-value">' +
						'<span id="wikidata-value-display-container" style="display:none">' +
							'<small>' +
							'&#160;<span id="wikidata-value-link"></span>' +
							'&#160;|&#160;<a href="javascript:" id="wikidata-remove" title="Delete the Wikidata entry from this listing">הסרה</a>' +
							'</small>' +
						'</span>' +
					'</td>' +
				'</tr>' +
				'<tr id="div_wikipedia">' +
					'<td class="editor-label-col"><label for="input-wikipedia">ויקיפדיה<span class="wikidata-update"></span></label></td>' +
					'<td>' +
						'<input type="text" class="editor-partialwidth" placeholder="שם הערך המקביל בוויקיפדיה" id="input-wikipedia">' +
						'<span id="wikipedia-value-display-container" style="display:none">' +
							'<small>' +
							'&#160;<span id="wikipedia-value-link"></span>' +
							'</small>' +
						'</span>' +
					'</td>' +
				'</tr>' +
				'<tr id="div_image">' +
					'<td class="editor-label-col"><label for="input-image">תמונה<span class="wikidata-update"></span></label></td>' +
					'<td>' +
						'<input type="text" class="editor-partialwidth" placeholder="תמונה של המקום" 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">' +
			'<tr id="div_content">' +
				'<td class="editor-label-col"><label for="input-content">תיאור</label></td>' +
				'<td><textarea rows="8" class="editor-fullwidth" placeholder="תיאור מפורט של המקום ושל מה שהוא מציע לנוסע. ניתן להוסיף פרטים רלוונטים נוספים שלא הוזכרו בשדות לעיל" id="input-content" style="margin-bottom: 5px;"></textarea></td>' +
			'</tr>' +
			'<tr id="div_additional_features">' +
				'<td class="editor-label-col"><label>מאפיינים נוספים</label></td>' +
				'<td>' +
					'<span id="span-istripadvisor">' +
						'<input type="checkbox" id="input-istripadvisor">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/Symbol_thumbs_up.svg" class=features-symbol title="לחצו על התיבה אם המקום הוא הפופולרי ביותר בקטגוריה זו ב-Tripadvisor">' +
						'<label for="input-istripadvisor" class="listing-tooltip" title="לחצו על התיבה אם המקום הוא הפופולרי ביותר בקטגוריה זו ב-Tripadvisor">(רשומה מובילה בטריפאדוויזר?)</label>' +
					'</span>' +
					'<span id="span-isunesco" style="display: none;">' +
						'<input type="checkbox" id="input-isunesco">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/3/34/Swedish_world_heritage_sign.PNG" class=features-symbol title="לחצו על התיבה אם המקום הוכרז כאתר מורשת עולמית של אונסק"ו>' +
						'<label for="input-isunesco" class="listing-tooltip" title="לחצו על התיבה אם המקום הוכרז כאתר מורשת עולמית של אונסק"ו>(אתר מורשת עולמית?)</label>' +
						'<span class="wikidata-update"></span>' +
					'</span>' +
					'<span id="span-iskosher" style="display: none;">' +
						'<input type="checkbox" id="input-iskosher">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/9/9c/Kosher_green_frame.png" class=features-symbol title="לחצו על התיבה אם המסעדה כשרה או מציעה תפריט עם מגוון של מנות כשרות">' +
						'<label for="input-iskosher" class="listing-tooltip" title="לחצו על התיבה אם המסעדה כשרה או מציעה תפריט עם מגוון של מנות כשרות">(כשר?)</label>' +
					'</span>' +
					'<span id="span-isvegetarian" style="display: none;">' +
						'<input type="checkbox" id="input-isvegetarian">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/a/a6/Farm-Fresh_green.png" class=features-symbol title="לחצו על התיבה אם המסעדה צמחונית או מציעה תפריט עם מגוון של מנות צמחוניות">' +
						'<label for="input-isvegetarian" class="listing-tooltip" title="לחצו על התיבה אם המסעדה צמחונית או מציעה תפריט עם מגוון של מנות צמחוניות">(צמחוני?)</label>' +
					'</span>' +
					'<span id="span-ishalal" style="display: none;">' +
						'<input type="checkbox" id="input-ishalal">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Halal_word_in_Arabic.png" class=features-symbol title="לחצו על התיבה אם המסעדה חלאל או מציעה תפריט עם מגוון של מנות חלאל">' +
						'<label for="input-ishalal" class="listing-tooltip" title="לחצו על התיבה אם המסעדה חלאל או מציעה תפריט עם מגוון של מנות חלאל">(חלאל?)</label>' +
					'</span>' +
					'<span id="span-islgbt" style="display: none;">' +
						'<input type="checkbox" id="input-islgbt">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/4/48/Gay_Pride_Flag.svg" class=features-symbol title="לחצו על התיבה אם המקום ידידותי לקהילה הגאה">' +
						'<label for="input-islgbt" class="listing-tooltip" title="לחצו על התיבה אם המקום ידידותי לקהילה הגאה">(גיי פרנדלי?)</label>' +
					'</span>' +
					'<span id="span-isaccessibility">' +
						'<input type="checkbox" id="input-isaccessibility">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/5/54/Wheelchair-green3.png" class=features-symbol title="לחצו על התיבה אם המקום נגיש עבור נכים ובעלי מוגבלויות">' +
						'<label for="input-isaccessibility" class="listing-tooltip" title="לחצו על התיבה אם המקום נגיש עבור נכים ובעלי מוגבלויות">(נגיש?)</label>' +
					'</span>' +
					'<span id="span-iswifi">' +
						'<input type="checkbox" id="input-iswifi">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/4/44/WIFI_icon.svg" class=features-symbol title="לחצו על התיבה אם יש במקום גישה לאינטרנט אלחוטי (בחינם/בתשלום)">' +
						'<label for="input-iswifi" class="listing-tooltip" title="לחצו על התיבה אם יש במקום גישה לאינטרנט אלחוטי (בחינם/בתשלום)">(WiFi?)</label>' +
					'</span>' +
					'<span id="span-iscampfire" style="display: none;">' +
						'<input type="checkbox" id="input-iscampfire">' +
						'<img src="https://upload.wikimedia.org/wikipedia/commons/7/7c/Icon-Campfire.svg" class=features-symbol title="לחצו על התיבה אם הדלקת אש מותרת במקום">' +
						'<label for="input-iscampfire" class="listing-tooltip" title="לחצו על התיבה אם הדלקת אש מותרת במקום">(הדלקת אש?)</label>' +
					'</span>' +
				'</td>' +
			'<tr id="div_status">' +
				'<td class="editor-label-col"><label>סטטוס</label></td>' +
				'<td>' +
					'<span id="span-closed">' +
						'<input type="checkbox" id="input-closed">' +
						'<label for="input-closed" class="listing-tooltip" title="לחצו על התיבה כדי להסיר את הרשומה אם המקום נסגר או חדל לפעול">האם למחוק את הרשומה?</label>' +
					'</span>' +
					// 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" />' +
						'<label for="input-last-edit" class="listing-tooltip" title="לחצו על התיבה אם המידע המופיע ברשומה עדכני ומהימן, ומועד העדכון של הרשומה ישונה לתאריך הנוכחי">האם לסמן את המידע ברשומה כעדכני?</label>' +
					'</span>' +
				'</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,
			WIKIPEDIA_URL: WIKIPEDIA_URL,
			WIKIDATA_SITELINK_WIKIPEDIA: WIKIDATA_SITELINK_WIKIPEDIA,
			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_ADDITIONAL_FEATURES: LISTING_ADDITIONAL_FEATURES,
			LISTING_TEMPLATES: LISTING_TEMPLATES,
			COLOR_PROPERTIES: COLOR_PROPERTIES,
			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
		// --------------------------------------------------------------------

		/**
		 * 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);

		/**
		 * 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) {
			var EDITOR_STATUS_ROW = '#div_status';
			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 typeToColor = function(listingType, form) {
			var color = '#ffffff';
			var colorsDict = ListingEditor.Config.COLOR_PROPERTIES;
			if (listingType && listingType in colorsDict) {
				color = colorsDict[listingType];
			}
			$('#input-type', form).css( 'box-shadow', '-20px 0 0 0 ' + color + ' inset' );
		};
		var initColor = function(form, mode) {
			typeToColor( $('#input-type', form).val(), form );
			$('#input-type', form).on('keydown keyup change click', function () {
   				typeToColor(this.value, form);
			});
		};
		CREATE_FORM_CALLBACKS.push(initColor);

		var isRTL = function (s){ // based on https://stackoverflow.com/questions/12006095/javascript-how-to-check-if-character-is-rtl
			var ltrChars    = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF'+'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF',
			rtlChars    = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC',
			rtlDirCheck = new RegExp('^[^'+ltrChars+']*['+rtlChars+']');
    		return rtlDirCheck.test(s);
		};
		var autoDir = function(selector) {
			if (selector.val() && !isRTL(selector.val())) {
				selector.prop('dir', 'ltr');
			}
			selector.keyup(function() {
			    if (isRTL(selector.val()) || !selector.val() ) {
			    	selector.prop('dir', 'rtl');
			    }
			    else {
			    	selector.prop('dir', 'ltr');
			    }
			  });
		};
		var autoDirParameters = function(form, mode) {
			autoDir($('#input-name', form));
			autoDir($('#input-alt', form));
			autoDir($('#input-address', form));
		};
		CREATE_FORM_CALLBACKS.push(autoDirParameters);

	var ClockPickerClicking = function(form, mode) {
			$('#input-checkout', form).clockpicker();
			$('#input-checkin', form).clockpicker();
		};
		CREATE_FORM_CALLBACKS.push(ClockPickerClicking);
		
		var wikidataLookup = function(form, mode) {
			// get the display value for the pre-existing wikidata record ID
			var value = $("#input-wikidata-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-wikidata-value").val();
					var label = ListingEditor.SisterSite.wikidataLabel(jsonObj, value);
					if (label === null) {
						label = "";
					}
					$("#input-wikidata-label").val(label);
				};
				ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess);
			}
			// set up autocomplete to search for results as the user types
			$('#input-wikidata-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-wikidata-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
			$('#wikidata-remove', form).click(function() {
				wikidataRemove(form);
			});
			$('#input-wikidata-label', form).change(function() {
				if (!$(this).val()) {
					wikidataRemove(form);
				}
			});
			var wikidataRemove = function(form) {
				$("#input-wikidata-value", form).val("");
				$("#input-wikidata-label", form).val("");
				$("#wikidata-value-display-container", form).hide();
				$('#div_wikidata_update', form).hide();
			};
			$('#wikidata-shared', form).click(function() {
				var wikidataRecord = $("#input-wikidata-value", form).val();
				updateWikidataSharedFields(wikidataRecord);
			});
			var wikipediaSiteData = {
				apiUrl: ListingEditor.SisterSite.API_WIKIPEDIA,
				selector: $('#input-wikipedia', form),
				form: form,
				ajaxData: {
					namespace: 0
				},
				updateLinkFunction: wikipediaLink
			};
			ListingEditor.SisterSite.initializeSisterSiteAutocomplete(wikipediaSiteData);
			var commonsSiteData = {
				apiUrl: ListingEditor.SisterSite.API_COMMONS,
				selector: $('#input-image', form),
				form: form,
				ajaxData: {
					namespace: 6
				},
				updateLinkFunction: commonsLink
			};
			ListingEditor.SisterSite.initializeSisterSiteAutocomplete(commonsSiteData);
		};
		var wikipediaLink = function(value, form) {
			var wikipediaSiteLinkData = {
				inputSelector: '#input-wikipedia',
				containerSelector: '#wikipedia-value-display-container',
				linkContainerSelector: '#wikipedia-value-link',
				href: ListingEditor.Config.WIKIPEDIA_URL + '/wiki/' + mw.util.wikiUrlencode(value),
				linkTitle: ListingEditor.Config.TRANSLATIONS.viewWikipediaPage
			};
			sisterSiteLinkDisplay(wikipediaSiteLinkData, form);
		};
		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 facebook = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_FACEBOOK);
				var unesco = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_UNESCO);
				var wikipedia = ListingEditor.SisterSite.wikidataWikipedia(jsonObj, wikidataRecord);
				var msg = '';
				var urlfacebook = '';
				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 (facebook) {
					urlfacebook = 'https://www.facebook.com/' + facebook;
					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedFacebook + ': ' + urlfacebook;
				}
				if (image) {
					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedImage + ': ' + image;
				}
				if (wikipedia) {
					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedWikipedia + ': ' + wikipedia;
				}
				if (unesco) {
					msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedUnesco;
				}
				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-facebook', urlfacebook);
						updateFieldIfNotNull('#input-image', image);
						
						if (unesco){
							$('#input-isunesco').prop( "checked", true );
						}
						
						if (image) {
							commonsLink(image);
						}
						updateFieldIfNotNull('#input-wikipedia', wikipedia);
						if (wikipedia) {
							wikipediaLink(wikipedia);
						}
					}
				} 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
			});
			$("#wikidata-value-link", form).html(link);
			$("#wikidata-value-display-container", form).show();
			$('#div_wikidata_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 = 'עודכן לאחרונה';
			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);
	
		/**
		Checks for additional features that were added, and creates a string
		of templates out of the feaures 
		*/
		var updateAdditionalFeatures = function(listing) {
			var AdditionalFeaturesStr = '';	
			var LISTING_ADDITIONAL_FEATURES_PARAMETER = 'מאפיינים נוספים';
			for (var feature in ListingEditor.Config.LISTING_ADDITIONAL_FEATURES)
				{
					var featureInfo = ListingEditor.Config.LISTING_ADDITIONAL_FEATURES[feature];
					if ($("#input-" + featureInfo.id).is(':checked')) {
						AdditionalFeaturesStr += '{{' +  feature + "}}";
					}	
				}
			listing[LISTING_ADDITIONAL_FEATURES_PARAMETER] = AdditionalFeaturesStr;
		};
		SUBMIT_FORM_CALLBACKS.push(updateAdditionalFeatures);
		

		// --------------------------------------------------------------------
		// 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 email addresses.  Invalid emails can
		 * still get through, but this method implements a minimal amount of
		 * validation in order to catch the worst offenders.
		 */
		var validateEmail = function(validationFailureMessages) {
			var VALID_EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
			_validateFieldAgainstRegex(validationFailureMessages, VALID_EMAIL_REGEX, '#input-email', ListingEditor.Config.TRANSLATIONS.validationEmail);
		};
		VALIDATE_FORM_CALLBACKS.push(validateEmail);

		/**
		 * Implement SIMPLE validation on Wikipedia field to verify that the
		 * user is entering the article title and not a URL.
		 */
		var validateWikipedia = function(validationFailureMessages) {
			var VALID_WIKIPEDIA_REGEX = new RegExp('^(?!https?://)', 'i');
			_validateFieldAgainstRegex(validationFailureMessages, VALID_WIKIPEDIA_REGEX, '#input-wikipedia', ListingEditor.Config.TRANSLATIONS.validationWikipedia);
		};
		VALIDATE_FORM_CALLBACKS.push(validateWikipedia);

		/**
		 * 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_WIKIPEDIA = ListingEditor.Config.WIKIPEDIA_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 WIKIDATA_CLAIM_FACEBOOK = 'P2013';
		var WIKIDATA_CLAIM_UNESCO =  'P757';

		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;
		};
		// parse the wikipedia link from the wikidata response
		var _wikidataWikipedia = function(jsonObj, value) {
			var entityObj = _wikidataEntity(jsonObj, value);
			if (!entityObj || !entityObj.sitelinks || !entityObj.sitelinks[ListingEditor.Config.WIKIDATA_SITELINK_WIKIPEDIA] || !entityObj.sitelinks[ListingEditor.Config.WIKIDATA_SITELINK_WIKIPEDIA].title) {
				return null;
			}
			return entityObj.sitelinks[ListingEditor.Config.WIKIDATA_SITELINK_WIKIPEDIA].title;
		};
		// expose public members
		return {
			API_WIKIDATA: API_WIKIDATA,
			API_WIKIPEDIA: API_WIKIPEDIA,
			API_COMMONS: API_COMMONS,
			WIKIDATA_CLAIM_COORD: WIKIDATA_CLAIM_COORD,
			WIKIDATA_CLAIM_LINK: WIKIDATA_CLAIM_LINK,
			WIKIDATA_CLAIM_IMAGE: WIKIDATA_CLAIM_IMAGE,
			WIKIDATA_CLAIM_FACEBOOK: WIKIDATA_CLAIM_FACEBOOK,
			WIKIDATA_CLAIM_UNESCO: WIKIDATA_CLAIM_UNESCO,
			initializeSisterSiteAutocomplete: _initializeSisterSiteAutocomplete,
			ajaxSisterSiteSearch: _ajaxSisterSiteSearch,
			wikidataClaim: _wikidataClaim,
			wikidataWikipedia: _wikidataWikipedia,
			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, listingParameters, listingTemplateAsMap) {
			var form = $(ListingEditor.Config.EDITOR_FORM_HTML);
			// make sure the select dropdown includes any custom "type" values
			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>');
			}
			
			
			// populate the empty form with existing values
			for (var parameter in listingParameters) {
				var parameterInfo = listingParameters[parameter];
				if (listingTemplateAsMap[parameter]) {
					$('#' + parameterInfo.id, form).val(listingTemplateAsMap[parameter]);
				} else if (parameterInfo.hideDivIfEmpty) {
					$('#' + parameterInfo.hideDivIfEmpty, form).hide();
				}
			}
			
			/**
			 * Loading the additional features and checking the boxes according to the templates
			 */
			var additionalFeaturesArr = [];
			var LISTING_ADDITIONAL_FEATURES_PARAMETER = 'מאפיינים נוספים';
			var additionalFeaturesString = listingTemplateAsMap[LISTING_ADDITIONAL_FEATURES_PARAMETER];
			if (additionalFeaturesString)
			{
				additionalFeaturesArr = additionalFeaturesString.match(/{{[^}]+}}/g);
				for (var j=0; j<additionalFeaturesArr.length; j++) {
					additionalFeaturesArr[j] = additionalFeaturesArr[j].replace("\|", "}");
					additionalFeaturesArr[j] = additionalFeaturesArr[j].substring(2,additionalFeaturesArr[j].search('}'));
				}
				if (additionalFeaturesArr.includes("רשומה מובילה"))
				{
					additionalFeaturesArr[additionalFeaturesArr.indexOf("רשומה מובילה")] = "רשומה מובילה בטריפאדוויזר";
				}
			}
			for (var feature in ListingEditor.Config.LISTING_ADDITIONAL_FEATURES)
			{
				var featureInfo = ListingEditor.Config.LISTING_ADDITIONAL_FEATURES[feature];
				if (additionalFeaturesArr.includes(feature))
				{
					$('#span-' + featureInfo.id, form).show();
					$('#input-' + featureInfo.id, form).prop( "checked", true );
				}
				else if ((featureInfo.typesToShow).includes(listingType))
				{
					$('#span-' + featureInfo.id, 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 h2').each(function(){
				$(this).nextUntil("h1, h2").addBack().wrapAll('<div class="mw-h2section" />');
			});
			$('#bodyContent h3').each(function(){
				$(this).nextUntil("h1, h2, h3").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) {
				// do not search using "#id" for two reasons.  one, the article might
				// re-use the same heading elsewhere and thus have two of the same ID.
				// two, unicode headings are escaped ("è" becomes ".C3.A8") and the dot
				// is interpreted by JQuery to indicate a child pattern unless it is
				// escaped
				var topHeading = $('h2 [id="' + sectionId + '"]');
				if (topHeading.length) {
					insertAddListingPlaceholder(topHeading);
					var parentHeading = topHeading.closest('div.mw-h2section');
					$('h3 .mw-headline', 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" title="'+ ListingEditor.Config.TRANSLATIONS.addExpandedTitle + '">'+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">')
				.html('<a href="javascript:" class="listingeditor-edit">'+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)) {
					$(this).append('&nbsp;|&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.
		 */
		var findListingTypeForSection = function(entry) {
			var sectionType = entry.closest('div.mw-h2section').children('h2').find('.mw-headline').attr('id');
			for (var sectionId in ListingEditor.Config.SECTION_TO_TEMPLATE_TYPE) {
				if (sectionType == sectionId) {
					return ListingEditor.Config.SECTION_TO_TEMPLATE_TYPE[sectionId];
				}
			}
			return ListingEditor.Config.DEFAULT_LISTING_TEMPLATE;
		};

		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_TEMPLATES 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_TEMPLATES) {
				regex.push(key);
			}
			return new RegExp('({{\\s*(' + regex.join('|') + ')(?:^|\\s)*)(\\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[1];
			}
			// 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();
			// convert "{{see" to {{listing|type=see"
			listingTemplateWikiSyntax = listingTemplateWikiSyntax.replace(typeRegex,'{{רשומה| ' + ListingEditor.Config.LISTING_TYPE_PARAMETER + '=$2$3');
			// 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);
			}
			if (listingTemplateAsMap[ListingEditor.Config.LISTING_CONTENT_PARAMETER]) {
				// convert paragraph tags to newlines so that the content is more
				// readable in the editor window
				listingTemplateAsMap[ListingEditor.Config.LISTING_CONTENT_PARAMETER] = listingTemplateAsMap[ListingEditor.Config.LISTING_CONTENT_PARAMETER].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_TEMPLATES) {
				if (listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER].toLowerCase() === key.toLowerCase()) {
					listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER] = 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
		 * content 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 content 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.dialog'], function () {
				var listingTemplateAsMap, listingTemplateWikiSyntax;
				if (mode == MODE_ADD) {
					listingTemplateAsMap = {};
					listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER] = listingType;
				} else {
					listingTemplateWikiSyntax = getListingWikitextBraces(listingIndex);
					listingTemplateAsMap = wikiTextToListing(listingTemplateWikiSyntax);
					listingType = listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER];
				}
				var listingParameters = getListingInfo(listingType);
				// 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, listingParameters, 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;
		};

		/**
		 * Given a listing type, return the appropriate entry from the
		 * LISTING_TEMPLATES array. This method returns the entry for the default
		 * listing template type if not enty exists for the specified type.
		 */
		var getListingInfo = function(type) {
			return (isCustomListingType(type)) ? ListingEditor.Config.LISTING_TEMPLATES[ListingEditor.Config.DEFAULT_LISTING_TEMPLATE] : ListingEditor.Config.LISTING_TEMPLATES[type];
		};

		/**
		 * Determine if the specified listing type is a custom type - for example "go"
		 * instead of "see", "do", "listing", etc.
		 */
		var isCustomListingType = function(listingType) {
			return !(listingType in ListingEditor.Config.LISTING_TEMPLATES);
		};

		/**
		 * 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 content won't render properly in lists, so
			// replace them with <p> tags
			$('#input-content').val($.trim($('#input-content').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, listingTemplateAsMap, sectionNumber) {
			var listing = listingTemplateAsMap;
			var defaultListingParameters = getListingInfo(ListingEditor.Config.DEFAULT_LISTING_TEMPLATE);
			var listingTypeInput = defaultListingParameters[ListingEditor.Config.LISTING_TYPE_PARAMETER].id;
			var listingType = $("#" + listingTypeInput).val();
			var listingParameters = getListingInfo(listingType);
			for (var parameter in listingParameters) {
				listing[parameter] = $("#" + listingParameters[parameter].id).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('====');
			}
			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 = mw.util.escapeIdForLink(getSectionName());
						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 content), 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 content 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[ListingEditor.Config.LISTING_TYPE_PARAMETER];
			var listingParameters = getListingInfo(listingType);
			var saveStr = '{{';
			// if this is a custom type (example: "go") then the type parameter must be specified explicitly
			if (isCustomListingType(listingType)) {
				saveStr += ListingEditor.Config.DEFAULT_LISTING_TEMPLATE;
				saveStr += ' | ' + ListingEditor.Config.LISTING_TYPE_PARAMETER + '=' + listingType;
			} else {
				saveStr += listingType;
			}
			if (!inlineListing && listingParameters[ListingEditor.Config.LISTING_TYPE_PARAMETER].newline) {
				saveStr += '\n';
			}
			for (var parameter in listingParameters) {
				if (parameter === ListingEditor.Config.LISTING_TYPE_PARAMETER) {
					// "type" parameter was handled previously
					continue;
				}
				if (parameter === ListingEditor.Config.LISTING_CONTENT_PARAMETER) {
					// processed last
					continue;
				}
				if (listing[parameter] !== '' || (!listingParameters[parameter].skipIfEmpty && !inlineListing)) {
					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 += '| ' + ListingEditor.Config.LISTING_CONTENT_PARAMETER + '=' + listing[ListingEditor.Config.LISTING_CONTENT_PARAMETER];
			saveStr += (inlineListing || !listingParameters[ListingEditor.Config.LISTING_CONTENT_PARAMETER].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>