הערה: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.
- פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload) או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
- גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
- אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh) או ללחוץ על צירוף המקשים Ctrl-F5.
/****************************************************************** Listing Editor v2.1.1-he Original author: - torty3 Additional contributors: - Andyrom75 - Wrh2 - Wang Shenwei (ClockPicker) v2.1.1-he Changes: - Checkboxs for local Additional Features: * UNESCO World Heritage Sites for see & do listings. * Kosher, Vegetarian and Halal for eat listings. * Gay Friendly for drink listings. * Campfire for do listings. * Accessibility, WiFi and Tripadvisor's featured for all the types. - ClockPicker for checkin & checkout. - Type and type group with related color sample (based on dewikivoyage). - Automatic text direction for "name", "alt" and "address" fields. - Support for local types: fun, featured city and featured destination. - Support for local Facebook parameter. - Facebook & UNESCO parameters can be loaded from Wikidata. v2.1 Changes: - Wikidata & Wikipedia fields added. - The Wikidata, image, and Wikipedia fields will now autocomplete, with lookups done by searching the relevant site. - Latitude, longitude, official link, wikipedia link, and image can be populated with the values stored at Wikidata by clicking on the "Update shared fields using values from Wikidata" link. - The "image" field is now shown by default. - Several cleanups to the underlying code. v2.0 Changes: - Update the listing editor dialog UI to provide more space for field entry, and to responsively collapse from two columns to one on small screens. - Use mw.Api().postWithToken instead of $.ajax to hopefully fix session token expiration issues. - Add support for editing of multi-paragraph listings. - Do not delete non-empty unrecognized listing template values (wikipedia, phoneextra, etc) when editing if the ALLOW_UNRECOGNIZED_PARAMETERS flag is set to true. - If listing editor form submit fails, re-display the listing editor form with the content entered by the user so that work is not lost. - Replace synchronous $.ajax call with asynchronous. - Allow edit summaries and marking edits as minor when editing listings. - Move 'add listing' link within the mw-editsection block. - Automatically replace newlines in listing content with <p> tags. - Fix bug where HTML comments inside listing fields prevented editing. - Provide configuration options so that some fields can be displayed only if they have non-empty values (examples: "fax" in the default configuration). - Add basic email address validation. - A failed captcha challenge no longer causes further captcha attempts to fail. - Significant code reorganization. TODO - Do not perform listing validations when deleting a listing. - Add support for mobile devices. - Add support for non-standard listing "type" values ("go", etc). - wrapContent is breaking the expand/collapse logic on the VFD page. - populate the input-type select list from LISTING_TEMPLATES ********************************************************************/ //<nowiki> ( function ( mw, $ ) { 'use strict'; // only run on supported skins // (on mobile this breaks section collapsing) if ( mw.config.get( 'skin' ) === 'minerva' ) { return; } 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="[email protected]" 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/4/48/Gay_Pride_Flag.svg" class=features-symbol title="לחצו על התיבה אם המקום ידידותי לקהילה הגאה">' + '<label for="input-islgbt" class="listing-tooltip" title="לחצו על התיבה אם המקום ידידותי לקהילה הגאה">(גיי פרנדלי?)</label>' + '</span>' + '<span id="span-isaccessibility">' + '<input type="checkbox" id="input-isaccessibility">' + '<img src="https://upload.wikimedia.org/wikipedia/commons/5/54/Wheelchair-green3.png" class=features-symbol title="לחצו על התיבה אם המקום נגיש עבור נכים ובעלי מוגבלויות">' + '<label for="input-isaccessibility" class="listing-tooltip" title="לחצו על התיבה אם המקום נגיש עבור נכים ובעלי מוגבלויות">(נגיש?)</label>' + '</span>' + '<span id="span-iswifi">' + '<input type="checkbox" id="input-iswifi">' + '<img src="https://upload.wikimedia.org/wikipedia/commons/4/44/WIFI_icon.svg" class=features-symbol title="לחצו על התיבה אם יש במקום גישה לאינטרנט אלחוטי (בחינם/בתשלום)">' + '<label for="input-iswifi" class="listing-tooltip" title="לחצו על התיבה אם יש במקום גישה לאינטרנט אלחוטי (בחינם/בתשלום)">(WiFi?)</label>' + '</span>' + '<span id="span-iscampfire" style="display: none;">' + '<input type="checkbox" id="input-iscampfire">' + '<img src="https://upload.wikimedia.org/wikipedia/commons/7/7c/Icon-Campfire.svg" class=features-symbol title="לחצו על התיבה אם הדלקת אש מותרת במקום">' + '<label for="input-iscampfire" class="listing-tooltip" title="לחצו על התיבה אם הדלקת אש מותרת במקום">(הדלקת אש?)</label>' + '</span>' + '</td>' + '<tr id="div_status">' + '<td class="editor-label-col"><label>סטטוס</label></td>' + '<td>' + '<span id="span-closed">' + '<input type="checkbox" id="input-closed">' + '<label for="input-closed" class="listing-tooltip" title="לחצו על התיבה כדי להסיר את הרשומה אם המקום נסגר או חדל לפעול">האם למחוק את הרשומה?</label>' + '</span>' + // update the ListingEditor.Callbacks.updateLastEditDate // method if the last edit input is removed or modified '<span id="span-last-edit">' + '<input type="checkbox" id="input-last-edit" />' + '<label for="input-last-edit" class="listing-tooltip" title="לחצו על התיבה אם המידע המופיע ברשומה עדכני ומהימן, ומועד העדכון של הרשומה ישונה לתאריך הנוכחי">האם לסמן את המידע ברשומה כעדכני?</label>' + '</span>' + '</td>' + '</tr>' + '</table>' + // update the ListingEditor.Callbacks.hideEditOnlyFields method if // the summary table is removed or modified '<table class="editor-fullwidth" id="div_summary">' + '<tr><td colspan="2"><div class="listing-divider" /></td></tr>' + '<tr>' + '<td class="editor-label-col"><label for="input-summary">עריכת התקציר</label></td>' + '<td>' + '<input type="text" class="editor-partialwidth" placeholder="סיבה לשינוי הרשומה" id="input-summary">' + '<span id="span-minor"><input type="checkbox" id="input-minor"><label for="input-minor" class="listing-tooltip" title="לחצו על התיבה אם העריכה שלכם כוללת תוספות מזעריות בלבד למדריך, כמו תיקון הקלדה">האם זו עריכה משנית?</label></span>' + '</td>' + '</tr>' + '</table>' + '</form>'; // expose public members return { LANG: LANG, COMMONS_URL: COMMONS_URL, WIKIDATA_URL: WIKIDATA_URL, WIKIPEDIA_URL: WIKIPEDIA_URL, WIKIDATA_SITELINK_WIKIPEDIA: WIKIDATA_SITELINK_WIKIPEDIA, TRANSLATIONS: TRANSLATIONS, MAX_DIALOG_WIDTH: MAX_DIALOG_WIDTH, DISALLOW_ADD_LISTING_IF_PRESENT: DISALLOW_ADD_LISTING_IF_PRESENT, DEFAULT_LISTING_TEMPLATE: DEFAULT_LISTING_TEMPLATE, LISTING_TYPE_PARAMETER: LISTING_TYPE_PARAMETER, LISTING_CONTENT_PARAMETER: LISTING_CONTENT_PARAMETER, EDIT_LINK_CONTAINER_SELECTOR: EDIT_LINK_CONTAINER_SELECTOR, ALLOW_UNRECOGNIZED_PARAMETERS: ALLOW_UNRECOGNIZED_PARAMETERS, SECTION_TO_TEMPLATE_TYPE: SECTION_TO_TEMPLATE_TYPE, LISTING_ADDITIONAL_FEATURES: LISTING_ADDITIONAL_FEATURES, LISTING_TEMPLATES: LISTING_TEMPLATES, COLOR_PROPERTIES: COLOR_PROPERTIES, EDITOR_FORM_SELECTOR: EDITOR_FORM_SELECTOR, EDITOR_CLOSED_SELECTOR: EDITOR_CLOSED_SELECTOR, EDITOR_SUMMARY_SELECTOR: EDITOR_SUMMARY_SELECTOR, EDITOR_MINOR_EDIT_SELECTOR: EDITOR_MINOR_EDIT_SELECTOR, EDITOR_FORM_HTML: EDITOR_FORM_HTML }; }(); /* *********************************************************************** * ListingEditor.Callbacks implements custom functionality that may be * specific to how a Wikivoyage language version has implemented the * listing template. For example, English Wikivoyage uses a "last edit" * date that needs to be populated when the listing editor form is * submitted, and that is done via custom functionality implemented as a * SUBMIT_FORM_CALLBACK function in this module. * ***********************************************************************/ ListingEditor.Callbacks = function() { // array of functions to invoke when creating the listing editor form. // these functions will be invoked with the form DOM object as the // first element and the mode as the second element. var CREATE_FORM_CALLBACKS = []; // array of functions to invoke when submitting the listing editor // form but prior to validating the form. these functions will be // invoked with the mapping of listing attribute to value as the first // element and the mode as the second element. var SUBMIT_FORM_CALLBACKS = []; // array of validation functions to invoke when the listing editor is // submitted. these functions will be invoked with an array of // validation messages as an argument; a failed validation should add a // message to this array, and the user will be shown the messages and // the form will not be submitted if the array is not empty. var VALIDATE_FORM_CALLBACKS = []; // -------------------------------------------------------------------- // LISTING EDITOR UI INITIALIZATION CALLBACKS // -------------------------------------------------------------------- /** * Add listeners to the currency symbols so that clicking on a currency * symbol will insert it into the price input. */ var initCurrencySymbolFormFields = function(form, mode) { var CURRENCY_SIGNS_SELECTOR = '.currency-signs'; $(CURRENCY_SIGNS_SELECTOR, form).click(function() { var priceInput = $('#input-price'); var caretPos = priceInput[0].selectionStart; var oldPrice = priceInput.val(); var currencySymbol = $(this).find('a').text(); var newPrice = oldPrice.substring(0, caretPos) + currencySymbol + oldPrice.substring(caretPos); priceInput.val(newPrice); }); }; CREATE_FORM_CALLBACKS.push(initCurrencySymbolFormFields); /** * Add listeners on various fields to update the "find on map" link. */ var initFindOnMapLink = function(form, mode) { var latlngStr = '?lang=' + ListingEditor.Config.LANG; latlngStr += '&page=' + encodeURIComponent(mw.config.get('wgTitle')); // #geodata should be a hidden span added by Template:Geo // containing the lat/long coordinates of the destination if ($('#geodata').length) { var latlng = $('#geodata').text().split('; '); latlngStr += '&lat=' + latlng[0] + '&lon=' + latlng[1] + '&zoom=15'; } if ($('#input-address', form).val() !== '') { latlngStr += '&location=' + encodeURIComponent($('#input-address', form).val()); } else if ($('#input-name', form).val() !== '') { latlngStr += '&location=' + encodeURIComponent($('#input-name', form).val()); } // #geomap-link is a link in the EDITOR_FORM_HTML $('#geomap-link', form).attr('href', $('#geomap-link', form).attr('href') + latlngStr); $('#input-address', form).change( function () { var link = $('#geomap-link').attr('href'); var index = link.indexOf('&location'); if (index < 0) index = link.length; $('#geomap-link').attr('href', link.substr(0,index) + '&location=' + encodeURIComponent($('#input-address').val())); }); }; CREATE_FORM_CALLBACKS.push(initFindOnMapLink); var hideEditOnlyFields = function(form, mode) { var EDITOR_STATUS_ROW = '#div_status'; var EDITOR_SUMMARY_ROW = '#div_summary'; if (mode !== ListingEditor.Core.MODE_EDIT) { $(EDITOR_STATUS_ROW, form).hide(); $(EDITOR_SUMMARY_ROW, form).hide(); } }; CREATE_FORM_CALLBACKS.push(hideEditOnlyFields); var typeToColor = function(listingType, form) { var color = '#ffffff'; var colorsDict = ListingEditor.Config.COLOR_PROPERTIES; if (listingType && listingType in colorsDict) { color = colorsDict[listingType]; } $('#input-type', form).css( 'box-shadow', '-20px 0 0 0 ' + color + ' inset' ); }; var initColor = function(form, mode) { typeToColor( $('#input-type', form).val(), form ); $('#input-type', form).on('keydown keyup change click', function () { typeToColor(this.value, form); }); }; CREATE_FORM_CALLBACKS.push(initColor); var isRTL = function (s){ // based on https://stackoverflow.com/questions/12006095/javascript-how-to-check-if-character-is-rtl var ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF'+'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF', rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC', rtlDirCheck = new RegExp('^[^'+ltrChars+']*['+rtlChars+']'); return rtlDirCheck.test(s); }; var autoDir = function(selector) { if (selector.val() && !isRTL(selector.val())) { selector.prop('dir', 'ltr'); } selector.keyup(function() { if (isRTL(selector.val()) || !selector.val() ) { selector.prop('dir', 'rtl'); } else { selector.prop('dir', 'ltr'); } }); }; var autoDirParameters = function(form, mode) { autoDir($('#input-name', form)); autoDir($('#input-alt', form)); autoDir($('#input-address', form)); }; CREATE_FORM_CALLBACKS.push(autoDirParameters); var ClockPickerClicking = function(form, mode) { $('#input-checkout', form).clockpicker(); $('#input-checkin', form).clockpicker(); }; CREATE_FORM_CALLBACKS.push(ClockPickerClicking); var wikidataLookup = function(form, mode) { // get the display value for the pre-existing wikidata record ID var value = $("#input-wikidata-value", form).val(); if (value) { wikidataLink(form, value); var ajaxUrl = ListingEditor.SisterSite.API_WIKIDATA; var ajaxData = { action: 'wbgetentities', ids: value, languages: ListingEditor.Config.LANG, props: 'labels' }; var ajaxSuccess = function(jsonObj) { var value = $("#input-wikidata-value").val(); var label = ListingEditor.SisterSite.wikidataLabel(jsonObj, value); if (label === null) { label = ""; } $("#input-wikidata-label").val(label); }; ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); } // set up autocomplete to search for results as the user types $('#input-wikidata-label', form).autocomplete({ source: function( request, response ) { var ajaxUrl = ListingEditor.SisterSite.API_WIKIDATA; var ajaxData = { action: 'wbsearchentities', search: request.term, language: ListingEditor.Config.LANG }; var ajaxSuccess = function (jsonObj) { response(parseWikiDataResult(jsonObj)); }; ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); }, select: function(event, ui) { $("#input-wikidata-value").val(ui.item.id); wikidataLink("", ui.item.id); } }).data("ui-autocomplete")._renderItem = function(ul, item) { var label = item.label + " <small>" + item.id + "</small>"; if (item.description) { label += "<br /><small>" + item.description + "</small>"; } return $("<li>").data('ui-autocomplete-item', item).append($("<a>").html(label)).appendTo(ul); }; // add a listener to the "remove" button so that links can be deleted $('#wikidata-remove', form).click(function() { wikidataRemove(form); }); $('#input-wikidata-label', form).change(function() { if (!$(this).val()) { wikidataRemove(form); } }); var wikidataRemove = function(form) { $("#input-wikidata-value", form).val(""); $("#input-wikidata-label", form).val(""); $("#wikidata-value-display-container", form).hide(); $('#div_wikidata_update', form).hide(); }; $('#wikidata-shared', form).click(function() { var wikidataRecord = $("#input-wikidata-value", form).val(); updateWikidataSharedFields(wikidataRecord); }); var wikipediaSiteData = { apiUrl: ListingEditor.SisterSite.API_WIKIPEDIA, selector: $('#input-wikipedia', form), form: form, ajaxData: { namespace: 0 }, updateLinkFunction: wikipediaLink }; ListingEditor.SisterSite.initializeSisterSiteAutocomplete(wikipediaSiteData); var commonsSiteData = { apiUrl: ListingEditor.SisterSite.API_COMMONS, selector: $('#input-image', form), form: form, ajaxData: { namespace: 6 }, updateLinkFunction: commonsLink }; ListingEditor.SisterSite.initializeSisterSiteAutocomplete(commonsSiteData); }; var wikipediaLink = function(value, form) { var wikipediaSiteLinkData = { inputSelector: '#input-wikipedia', containerSelector: '#wikipedia-value-display-container', linkContainerSelector: '#wikipedia-value-link', href: ListingEditor.Config.WIKIPEDIA_URL + '/wiki/' + mw.util.wikiUrlencode(value), linkTitle: ListingEditor.Config.TRANSLATIONS.viewWikipediaPage }; sisterSiteLinkDisplay(wikipediaSiteLinkData, form); }; var commonsLink = function(value, form) { var commonsSiteLinkData = { inputSelector: '#input-image', containerSelector: '#image-value-display-container', linkContainerSelector: '#image-value-link', href: ListingEditor.Config.COMMONS_URL + '/wiki/' + mw.util.wikiUrlencode('File:' + value), linkTitle: ListingEditor.Config.TRANSLATIONS.viewCommonsPage }; sisterSiteLinkDisplay(commonsSiteLinkData, form); }; var sisterSiteLinkDisplay = function(siteLinkData, form) { var value = $(siteLinkData.inputSelector, form).val(); if (!value) { $(siteLinkData.containerSelector, form).hide(); } else { var link = $("<a />", { target: "_new", href: siteLinkData.href, title: siteLinkData.linkTitle, text: siteLinkData.linkTitle }); $(siteLinkData.linkContainerSelector, form).html(link); $(siteLinkData.containerSelector, form).show(); } }; var updateFieldIfNotNull = function(selector, value) { if (value) { $(selector).val(value); } }; var updateWikidataSharedFields = function(wikidataRecord) { var ajaxUrl = ListingEditor.SisterSite.API_WIKIDATA; var ajaxData = { action: 'wbgetentities', ids: wikidataRecord, languages: ListingEditor.Config.LANG }; var ajaxSuccess = function (jsonObj) { var coords = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_COORD); var link = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_LINK); var image = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_IMAGE); var facebook = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_FACEBOOK); var unesco = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.SisterSite.WIKIDATA_CLAIM_UNESCO); var wikipedia = ListingEditor.SisterSite.wikidataWikipedia(jsonObj, wikidataRecord); var msg = ''; var urlfacebook = ''; if (coords) { // trim lat/long to six decimal places coords.latitude = ListingEditor.Core.trimDecimal(coords.latitude, 6); coords.longitude = ListingEditor.Core.trimDecimal(coords.longitude, 6); msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedLatitude + ': ' + coords.latitude; msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedLongitude + ': ' + coords.longitude; } if (link) { msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedWebsite + ': ' + link; } if (facebook) { urlfacebook = 'https://www.facebook.com/' + facebook; msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedFacebook + ': ' + urlfacebook; } if (image) { msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedImage + ': ' + image; } if (wikipedia) { msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedWikipedia + ': ' + wikipedia; } if (unesco) { msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedUnesco; } if (msg) { if (confirm(ListingEditor.Config.TRANSLATIONS.wikidataShared + '\n' + msg)) { if (coords) { updateFieldIfNotNull('#input-lat', coords.latitude); updateFieldIfNotNull('#input-long', coords.longitude); } updateFieldIfNotNull('#input-url', link); updateFieldIfNotNull('#input-facebook', urlfacebook); updateFieldIfNotNull('#input-image', image); if (unesco){ $('#input-isunesco').prop( "checked", true ); } if (image) { commonsLink(image); } updateFieldIfNotNull('#input-wikipedia', wikipedia); if (wikipedia) { wikipediaLink(wikipedia); } } } else { alert(ListingEditor.Config.TRANSLATIONS.wikidataSharedNotFound); } }; ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); }; var parseWikiDataResult = function(jsonObj) { var results = []; for (var i=0; i < $(jsonObj.search).length; i++) { var result = $(jsonObj.search)[i]; var label = result.label; if (result.match && result.match.text) { label = result.match.text; } var data = { value: label, label: label, description: result.description, id: result.id }; results.push(data); } return results; }; var wikidataLink = function(form, value) { var link = $("<a />", { target: "_new", href: ListingEditor.Config.WIKIDATA_URL + '/wiki/' + mw.util.wikiUrlencode(value), title: ListingEditor.Config.TRANSLATIONS.viewWikidataPage, text: value }); $("#wikidata-value-link", form).html(link); $("#wikidata-value-display-container", form).show(); $('#div_wikidata_update', form).show(); }; CREATE_FORM_CALLBACKS.push(wikidataLookup); // -------------------------------------------------------------------- // LISTING EDITOR FORM SUBMISSION CALLBACKS // -------------------------------------------------------------------- /** * Return the current date in the format "2015-01-15". */ var currentLastEditDate = function() { var d = new Date(); var year = d.getFullYear(); // Date.getMonth() returns 0-11 var month = d.getMonth() + 1; if (month < 10) month = '0' + month; var day = d.getDate(); if (day < 10) day = '0' + day; return year + '-' + month + '-' + day; }; /** * Only update last edit date if this is a new listing or if the * "information up-to-date" box checked. */ var updateLastEditDate = function(listing, mode) { var LISTING_LAST_EDIT_PARAMETER = 'עודכן לאחרונה'; var EDITOR_LAST_EDIT_SELECTOR = '#input-last-edit'; if (mode == ListingEditor.Core.MODE_ADD || $(EDITOR_LAST_EDIT_SELECTOR).is(':checked')) { listing[LISTING_LAST_EDIT_PARAMETER] = currentLastEditDate(); } }; SUBMIT_FORM_CALLBACKS.push(updateLastEditDate); /** Checks for additional features that were added, and creates a string of templates out of the feaures */ var updateAdditionalFeatures = function(listing) { var AdditionalFeaturesStr = ''; var LISTING_ADDITIONAL_FEATURES_PARAMETER = 'מאפיינים נוספים'; for (var feature in ListingEditor.Config.LISTING_ADDITIONAL_FEATURES) { var featureInfo = ListingEditor.Config.LISTING_ADDITIONAL_FEATURES[feature]; if ($("#input-" + featureInfo.id).is(':checked')) { AdditionalFeaturesStr += '{{' + feature + "}}"; } } listing[LISTING_ADDITIONAL_FEATURES_PARAMETER] = AdditionalFeaturesStr; }; SUBMIT_FORM_CALLBACKS.push(updateAdditionalFeatures); // -------------------------------------------------------------------- // LISTING EDITOR FORM VALIDATION CALLBACKS // -------------------------------------------------------------------- /** * Verify all listings have at least a name, address or alt value. */ var validateListingHasData = function(validationFailureMessages) { if ($('#input-name').val() === '' && $('#input-address').val() === '' && $('#input-alt').val() === '') { validationFailureMessages.push(ListingEditor.Config.TRANSLATIONS.validationEmptyListing); } }; VALIDATE_FORM_CALLBACKS.push(validateListingHasData); /** * Implement SIMPLE validation on email addresses. Invalid emails can * still get through, but this method implements a minimal amount of * validation in order to catch the worst offenders. */ var validateEmail = function(validationFailureMessages) { var VALID_EMAIL_REGEX = /^[^@\s]+@[^@\s]+\.[^@\s]+$/; _validateFieldAgainstRegex(validationFailureMessages, VALID_EMAIL_REGEX, '#input-email', ListingEditor.Config.TRANSLATIONS.validationEmail); }; VALIDATE_FORM_CALLBACKS.push(validateEmail); /** * Implement SIMPLE validation on Wikipedia field to verify that the * user is entering the article title and not a URL. */ var validateWikipedia = function(validationFailureMessages) { var VALID_WIKIPEDIA_REGEX = new RegExp('^(?!https?://)', 'i'); _validateFieldAgainstRegex(validationFailureMessages, VALID_WIKIPEDIA_REGEX, '#input-wikipedia', ListingEditor.Config.TRANSLATIONS.validationWikipedia); }; VALIDATE_FORM_CALLBACKS.push(validateWikipedia); /** * Implement SIMPLE validation on the Commons field to verify that the * user has not included a "File" or "Image" namespace. */ var validateImage = function(validationFailureMessages) { var VALID_IMAGE_REGEX = new RegExp('^(?!(file|image|' + ListingEditor.Config.TRANSLATIONS.image + '):)', 'i'); _validateFieldAgainstRegex(validationFailureMessages, VALID_IMAGE_REGEX, '#input-image', ListingEditor.Config.TRANSLATIONS.validationImage); }; VALIDATE_FORM_CALLBACKS.push(validateImage); var _validateFieldAgainstRegex = function(validationFailureMessages, validationRegex, fieldPattern, failureMsg) { var fieldValue = $(fieldPattern).val().trim(); if (fieldValue !== '' && !validationRegex.test(fieldValue)) { validationFailureMessages.push(failureMsg); } }; // expose public members return { CREATE_FORM_CALLBACKS: CREATE_FORM_CALLBACKS, SUBMIT_FORM_CALLBACKS: SUBMIT_FORM_CALLBACKS, VALIDATE_FORM_CALLBACKS: VALIDATE_FORM_CALLBACKS }; }(); ListingEditor.SisterSite = function() { var API_WIKIDATA = ListingEditor.Config.WIKIDATA_URL + '/w/api.php'; var API_WIKIPEDIA = ListingEditor.Config.WIKIPEDIA_URL + '/w/api.php'; var API_COMMONS = ListingEditor.Config.COMMONS_URL + '/w/api.php'; var WIKIDATA_CLAIM_COORD = 'P625'; var WIKIDATA_CLAIM_LINK = 'P856'; var WIKIDATA_CLAIM_IMAGE = 'P18'; var WIKIDATA_CLAIM_FACEBOOK = 'P2013'; var WIKIDATA_CLAIM_UNESCO = 'P757'; var _initializeSisterSiteAutocomplete = function(siteData) { var currentValue = $(siteData.selector).val(); if (currentValue) { siteData.updateLinkFunction(currentValue, siteData.form); } $(siteData.selector).change(function() { siteData.updateLinkFunction($(siteData.selector).val(), siteData.form); }); siteData.selectFunction = function(event, ui) { siteData.updateLinkFunction(ui.item.value, siteData.form); }; var ajaxData = siteData.ajaxData; ajaxData.action = 'opensearch'; ajaxData.list = 'search'; ajaxData.limit = 10; ajaxData.redirects = 'resolve'; var parseAjaxResponse = function(jsonObj) { var results = []; var titleResults = $(jsonObj[1]); for (var i=0; i < titleResults.length; i++) { var result = titleResults[i]; var valueWithoutFileNamespace = (titleResults[i].indexOf("File:") != -1) ? titleResults[i].substring("File:".length) : titleResults[i]; var titleResult = { value: valueWithoutFileNamespace, label: titleResults[i], description: $(jsonObj[2])[i], link: $(jsonObj[3])[i] }; results.push(titleResult); } return results; }; _initializeAutocomplete(siteData, ajaxData, parseAjaxResponse); }; var _initializeAutocomplete = function(siteData, ajaxData, parseAjaxResponse) { var autocompleteOptions = { source: function(request, response) { ajaxData.search = request.term; var ajaxSuccess = function(jsonObj) { response(parseAjaxResponse(jsonObj)); }; _ajaxSisterSiteSearch(siteData.apiUrl, ajaxData, ajaxSuccess); } }; if (siteData.selectFunction) { autocompleteOptions.select = siteData.selectFunction; } siteData.selector.autocomplete(autocompleteOptions); }; // perform an ajax query of a sister site var _ajaxSisterSiteSearch = function(ajaxUrl, ajaxData, ajaxSuccess) { ajaxData.format = 'json'; $.ajax({ url: ajaxUrl, data: ajaxData, dataType: 'jsonp', success: ajaxSuccess }); }; // parse the wikidata "claim" object from the wikidata response var _wikidataClaim = function(jsonObj, value, property) { var entity = _wikidataEntity(jsonObj, value); if (!entity || !entity.claims || !entity.claims[property]) { return null; } var propertyObj = entity.claims[property]; if (!propertyObj || propertyObj.length < 1 || !propertyObj[0].mainsnak || !propertyObj[0].mainsnak.datavalue) { return null; } return propertyObj[0].mainsnak.datavalue.value; }; // parse the wikidata "entity" object from the wikidata response var _wikidataEntity = function(jsonObj, value) { if (!jsonObj || !jsonObj.entities || !jsonObj.entities[value]) { return null; } return jsonObj.entities[value]; }; // parse the wikidata display label from the wikidata response var _wikidataLabel = function(jsonObj, value) { var entityObj = _wikidataEntity(jsonObj, value); if (!entityObj || !entityObj.labels || !entityObj.labels.en) { return null; } return entityObj.labels.en.value; }; // parse the wikipedia link from the wikidata response var _wikidataWikipedia = function(jsonObj, value) { var entityObj = _wikidataEntity(jsonObj, value); if (!entityObj || !entityObj.sitelinks || !entityObj.sitelinks[ListingEditor.Config.WIKIDATA_SITELINK_WIKIPEDIA] || !entityObj.sitelinks[ListingEditor.Config.WIKIDATA_SITELINK_WIKIPEDIA].title) { return null; } return entityObj.sitelinks[ListingEditor.Config.WIKIDATA_SITELINK_WIKIPEDIA].title; }; // expose public members return { API_WIKIDATA: API_WIKIDATA, API_WIKIPEDIA: API_WIKIPEDIA, API_COMMONS: API_COMMONS, WIKIDATA_CLAIM_COORD: WIKIDATA_CLAIM_COORD, WIKIDATA_CLAIM_LINK: WIKIDATA_CLAIM_LINK, WIKIDATA_CLAIM_IMAGE: WIKIDATA_CLAIM_IMAGE, WIKIDATA_CLAIM_FACEBOOK: WIKIDATA_CLAIM_FACEBOOK, WIKIDATA_CLAIM_UNESCO: WIKIDATA_CLAIM_UNESCO, initializeSisterSiteAutocomplete: _initializeSisterSiteAutocomplete, ajaxSisterSiteSearch: _ajaxSisterSiteSearch, wikidataClaim: _wikidataClaim, wikidataWikipedia: _wikidataWikipedia, wikidataLabel: _wikidataLabel }; }(); /* *********************************************************************** * ListingEditor.Core contains code that should be shared across different * Wikivoyage languages. This code uses the custom configurations in the * ListingEditor.Config and ListingEditor.Callback modules to initialize * the listing editor and process add and update requests for listings. * ***********************************************************************/ ListingEditor.Core = function() { var api = new mw.Api(); var MODE_ADD = 'add'; var MODE_EDIT = 'edit'; // selector that identifies the edit link as created by the // addEditButtons() function var EDIT_LINK_SELECTOR = '.vcard-edit-button'; var SAVE_FORM_SELECTOR = '#progress-dialog'; var CAPTCHA_FORM_SELECTOR = '#captcha-dialog'; var sectionText, inlineListing, replacements = {}; /** * Return false if the current page should not enable the listing editor. * Examples where the listing editor should not be enabled include talk * pages, edit pages, history pages, etc. */ var listingEditorAllowedForCurrentPage = function() { var namespace = mw.config.get( 'wgNamespaceNumber' ); if (namespace !== 0 && namespace !== 2 && namespace !== 4) { return false; } if ( mw.config.get('wgAction') != 'view' || $('#mw-revision-info').length || mw.config.get('wgCurRevisionId') != mw.config.get('wgRevisionId') || $('#ca-viewsource').length ) { return false; } return true; }; /** * Generate the form UI for the listing editor. If editing an existing * listing, pre-populate the form input fields with the existing values. */ var createForm = function(mode, listingParameters, listingTemplateAsMap) { var form = $(ListingEditor.Config.EDITOR_FORM_HTML); // make sure the select dropdown includes any custom "type" values var listingType = listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER]; if (isCustomListingType(listingType)) { $('#' + listingParameters[ListingEditor.Config.LISTING_TYPE_PARAMETER].id, form).append('<option value="' + listingType + '">' + listingType + '</option>'); } // populate the empty form with existing values for (var parameter in listingParameters) { var parameterInfo = listingParameters[parameter]; if (listingTemplateAsMap[parameter]) { $('#' + parameterInfo.id, form).val(listingTemplateAsMap[parameter]); } else if (parameterInfo.hideDivIfEmpty) { $('#' + parameterInfo.hideDivIfEmpty, form).hide(); } } /** * Loading the additional features and checking the boxes according to the templates */ var additionalFeaturesArr = []; var LISTING_ADDITIONAL_FEATURES_PARAMETER = 'מאפיינים נוספים'; var additionalFeaturesString = listingTemplateAsMap[LISTING_ADDITIONAL_FEATURES_PARAMETER]; if (additionalFeaturesString) { additionalFeaturesArr = additionalFeaturesString.match(/{{[^}]+}}/g); for (var j=0; j<additionalFeaturesArr.length; j++) { additionalFeaturesArr[j] = additionalFeaturesArr[j].replace("\|", "}"); additionalFeaturesArr[j] = additionalFeaturesArr[j].substring(2,additionalFeaturesArr[j].search('}')); } if (additionalFeaturesArr.includes("רשומה מובילה")) { additionalFeaturesArr[additionalFeaturesArr.indexOf("רשומה מובילה")] = "רשומה מובילה בטריפאדוויזר"; } } for (var feature in ListingEditor.Config.LISTING_ADDITIONAL_FEATURES) { var featureInfo = ListingEditor.Config.LISTING_ADDITIONAL_FEATURES[feature]; if (additionalFeaturesArr.includes(feature)) { $('#span-' + featureInfo.id, form).show(); $('#input-' + featureInfo.id, form).prop( "checked", true ); } else if ((featureInfo.typesToShow).includes(listingType)) { $('#span-' + featureInfo.id, form).show(); } } for (var i=0; i < ListingEditor.Callbacks.CREATE_FORM_CALLBACKS.length; i++) { ListingEditor.Callbacks.CREATE_FORM_CALLBACKS[i](form, mode); } return form; }; /** * Wrap the h2/h3 heading tag and everything up to the next section * (including sub-sections) in a div to make it easier to traverse the DOM. * This change introduces the potential for code incompatibility should the * div cause any CSS or UI conflicts. */ var wrapContent = function() { $('#bodyContent h2').each(function(){ $(this).nextUntil("h1, h2").addBack().wrapAll('<div class="mw-h2section" />'); }); $('#bodyContent h3').each(function(){ $(this).nextUntil("h1, h2, h3").addBack().wrapAll('<div class="mw-h3section" />'); }); }; /** * Place an "add listing" link at the top of each section heading next to * the "edit" link in the section heading. */ var addListingButtons = function() { if ($(ListingEditor.Config.DISALLOW_ADD_LISTING_IF_PRESENT.join(',')).length > 0) { return false; } for (var sectionId in ListingEditor.Config.SECTION_TO_TEMPLATE_TYPE) { // do not search using "#id" for two reasons. one, the article might // re-use the same heading elsewhere and thus have two of the same ID. // two, unicode headings are escaped ("è" becomes ".C3.A8") and the dot // is interpreted by JQuery to indicate a child pattern unless it is // escaped var topHeading = $('h2 [id="' + sectionId + '"]'); if (topHeading.length) { insertAddListingPlaceholder(topHeading); var parentHeading = topHeading.closest('div.mw-h2section'); $('h3 .mw-headline', parentHeading).each(function() { insertAddListingPlaceholder(this); }); } } $('.listingeditor-add').click(function() { initListingEditorDialog(MODE_ADD, $(this)); }); }; /** * Utility function for appending the "add listing" link text to a heading. */ var insertAddListingPlaceholder = function(parentHeading) { var editSection = $(parentHeading).next('.mw-editsection'); editSection.append('<span class="mw-editsection-bracket">[</span><a href="javascript:" class="listingeditor-add" title="'+ ListingEditor.Config.TRANSLATIONS.addExpandedTitle + '">'+ListingEditor.Config.TRANSLATIONS.add+'</a><span class="mw-editsection-bracket">]</span>'); }; /** * Place an "edit" link next to all existing listing tags. */ var addEditButtons = function() { var editButton = $('<span class="vcard-edit-button noprint">') .html('<a href="javascript:" class="listingeditor-edit">'+ListingEditor.Config.TRANSLATIONS.edit+'</a>' ) .click(function() { initListingEditorDialog(MODE_EDIT, $(this)); }); // if there is already metadata present add a separator $(ListingEditor.Config.EDIT_LINK_CONTAINER_SELECTOR).each(function() { if (!isElementEmpty(this)) { $(this).append(' | '); } }); // 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