משתמש:Guycn2/common.js: הבדלים בין גרסאות בדף
תוכן שנמחק תוכן שנוסף
Maintenance: mw:RL/MGU - Updated deprecated module name |
הסרת כל התוכן מהדף תגית: ריקון |
||
שורה 1: | שורה 1: | ||
/****************************************************************** |
|||
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> |
|||
mw.loader.using(['mediawiki.util', 'jquery.ui', 'mediawiki.api'], ( function ( mw, $ ) { |
|||
'use strict'; |
|||
importStylesheet('MediaWiki:NewListingEditor.css'); |
|||
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"> </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 |
|||
' <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>' + |
|||
' <span id="wikidata-value-link"></span>' + |
|||
' | <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>' + |
|||
' <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>' + |
|||
' <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/6/68/Gay_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; |
|||
} |
|||
console.log(propertyObj); |
|||
// Check for Hebrew: |
|||
for (let i = 0; i < propertyObj.length; i++) { |
|||
if ( |
|||
propertyObj[i].qualifiers && |
|||
propertyObj[i].qualifiers.P407[0] && |
|||
propertyObj[i].qualifiers.P407[0].datavalue.value.id === "Q9288" |
|||
) { |
|||
return propertyObj[i].mainsnak.datavalue.value; |
|||
} else { |
|||
if (i === propertyObj.length - 1) { |
|||
return propertyObj[0].mainsnak.datavalue.value; |
|||
} else { |
|||
continue; |
|||
} |
|||
} |
|||
} |
|||
// Check for Hebrew - END |
|||
}; |
|||
// 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(' | '); |
|||
} |
|||
}); |
|||
// 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'], 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 ( ). |
|||
*/ |
|||
var isElementEmpty = function(element) { |
|||
var text = $(element).text(); |
|||
if (!text.trim()) { |
|||
return true; |
|||
} |
|||
return (text.trim() == ' '); |
|||
}; |
|||
/** |
|||
* 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> |