Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.
- Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
- Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
- Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
/****************************************************************** Listing Editor v2.3-de Further development at: Benutzer:RolandUnger/ListingEditor.js Original author: ausgehe - torty3 - Wrh2 - RolandUnger TODO (WV-en): - Add support for mobile devices. - wrapContent is breaking the expand/collapse logic on the VFD page. TODO (WV-de v2.3 and above) - Substitution of jquery.ui Dialog and jquery.ui Autocomplete modules - Remove control characters from input - Check field data entries (URLs, phone numbers, emails, skype, coords, image, show, subtypes, last edit). - Check and convert coordinates (hex/dec). - Show errors with red border. - Get type from Wikidata (WD). - Getting country-specific data from article entity (language, currency, country calling code). - Show at beginning: Loading... - Add support for mobile devices. - Change type list to autocomplete combobox. - Improved for show and subtype parameters with comboboxes with multiple selections. - Picking up gps position from external tools. v2.3-de Changes: - ... v2.2-de Changes: - Adaption to Wikivoyage/de (translations etc.). - Getting placeholders from Wikidata. - Getting Address, Phone, Fax, Email, etc. from Wikidata. - Populate input-type select list from LISTING_TYPE_OPTIONS. - Add additional vCard fields. - Handle templates only in inline mode. - Handle missing type parameter and vCard template (no type substitution). - Remove Wikipedia field. - Preview button for template preview. - Add mouse cursor back to price after clicking currency sign. - Extended checks if article can be edited. - Type dependend show / hide changes. - Table layout replaced by div layout. - Gadget: suppress listing editor. - Label titles added. - Type and type group with related color sample. - Accept comma-separated "lat, long" in lat field. - Do not perform listing validations when deleting a listing. - Pipe delimiter before edit button will not be printed. - Adding listing-empty-input, listing-default-placeholder, listing-wikidata-placeholder classes. - Several cleanups to the underlying code. 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. ********************************************************************/ //<nowiki> ( function ( mw, $ ) { 'use strict'; /* *********************************************************************** * 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 --------------------------- /* *********************************************************************** * 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 = 'de'; // vCard specific var COMMONS_URL = '//commons.wikimedia.org'; var WIKIDATA_URL = '//www.wikidata.org'; var WIKIPEDIA_URL = '//de.wikipedia.org'; // vCard specific var WIKIDATA_SITELINK_WIKIPEDIA = 'dewiki'; // vCard specific var TRANSLATIONS = { // vCard specific 'addTitle' : 'Hinzufügen einer neuen vCard', 'editTitle' : 'Bearbeitung einer vorhanden vCard', 'add': 'vCard hinzufügen', 'edit': 'bearbeiten', 'saving': 'Speichern…', 'submit': 'Übernehmen', 'cancel': 'Abbruch', 'helpTitle': 'Hilfe zum vCard-Editor', 'submitTitle': 'Änderungen abspeichern', 'cancelTitle': 'Änderungen verwerfen', 'preview': 'Vorschau', 'previewTitle': 'Vorschau der vCard. Bitte vor dem Speichern benutzen!', 'previewOff': 'Vorschau aus', 'previewOffTitle': 'Vorschau ausschalten.', 'refresh': '↺', // \ue031 not yet working 'refreshTitle': 'Vorschau aktualisieren', 'validationEmptyListing': 'Bitte geben Sie entweder einen Namen oder eine Anschrift ein.', 'validationEmail': 'Bitte vergewissern Sie sich, dass die Email fehlerfrei ist.', 'validationImage': 'Bitte geben Sie den Bilddateinamen ohne Präfix ein.', 'image': 'Datei|datei|Bild|bild', //Local prefix for Image (or File) 'added': 'Hinzugefügte vCard für ', 'updated': 'Geänderte vCard für ', 'removed': 'Gelöschte vCard für ', 'mobileEdit': 'Mobile Bearbeitung', 'helpPage': '//de.wikivoyage.org/wiki/Hilfe:vCard-Editor', 'enterCaptcha': 'Eingabe des CAPTCHA', 'externalLinks': 'Ihre Bearbeitung enthält neue externe Links.', // license text should match MediaWiki:Wikimedia-copyrightwarning 'licenseText': 'Mit dem Speichern von Änderungen erklärst du dich mit den <a class="external" target="_blank" href="//wikimediafoundation.org/wiki/Terms_of_Use/de">Nutzungsbedingungen</a> einverstanden und lizenzierst deine Bearbeitung unwiderruflich unter der Lizenz <a class="external" target="_blank" href="https://creativecommons.org/licenses/by-sa/3.0/deed.de"><i>Creative-Commons</i> „Namensnennung – Weitergabe unter gleichen Bedingungen 3.0“</a> und der <a class="external" target="_blank" href="https://de.wikipedia.org/wiki/Wikipedia:GNU_Free_Documentation_License">GFDL</a>. Du stimmst zu, dass ein Hyperlink oder eine URL zur Seite für die notwendige Zuschreibung gemäß der <i>Creative-Commons</i>-Lizenz ausreichend ist.', 'ajaxInitFailure': 'Fehler: vCard-Editor kann nicht vorausgefüllt werden.', 'sharedName': 'Name', 'sharedImage': 'Bild', 'sharedLatitude': 'Breite', 'sharedLongitude': 'Länge', 'sharedWebsite': 'Website', 'submitApiError': 'Fehler: Der Server lieferte eine Fehlermeldung beim Versuch der Speicherung der vCard. Bitte versuchen Sie es erneut.', 'submitBlacklistError': 'Fehler: Eine Angabe wurde als schwarzgelistet ermittel. Bitte entfernen Sieden Eintrag und versuchen Sie es erneut.', 'submitUnknownError': 'Fehler: Ein unbekannter Fehler ist beim Versuch der Speicherung der vCard aufgetreten. Bitte versuchen Sie es erneut.', 'submitHttpError': 'Fehler: Der Server lieferte einen HTTP-Fehler beim Versuch der Speicherung der vCard. Bitte versuchen Sie es erneut.', 'submitEmptyError': 'Fehler: Der Server lieferte eine leere Antwort beim Versuch der Speicherung der vCard. Bitte versuchen Sie es erneut.', 'viewCommonsPage' : 'Ansicht der Commons-Seite', 'viewWikidataPage' : 'Ansicht des Wikidata-Datensatzes', 'wikidataShared': 'Nachfolgende Daten wurden in der Wikidata-Datensatz gefunden. Sollen die gefundenen Werte übernommen werden?', 'wikidataSharedNotFound': 'Keine Daten in der Wikidata-Datenbank gefunden.' }; var YES_ARRAY = { 'y': '', 'yes': '', 'j': '', 'ja': '', 'true': '' }; var NO_ARRAY = { 'n': '', 'no': '', 'nein': '', 'false': '' }; var WIKIDATA_CLAIMS = { 'coord': { 'p': 'P625', 'label': 'Koordinate' }, 'url': { 'p': 'P856', 'label': 'Website' }, // link 'image': { 'p': 'P18', 'label': 'Bild' }, 'address': { 'p': 'P969', 'label': 'Anschrift' }, 'phone': { 'p': 'P1329', 'label': 'Telefon' }, 'fax': { 'p': 'P2900', 'label': 'Fax' }, 'email': { 'p': 'P968', 'label': 'Emails' }, 'skype': { 'p': 'P2893', 'label': 'Skype' }, 'facebook': { 'p': 'P2013', 'label': 'Facebook' }, 'flickr': { 'p': 'P3267', 'label': 'Flickr' }, 'google': { 'p': 'P2847', 'label': 'Google+' }, 'twitter': { 'p': 'P2002', 'label': 'Twitter' }, 'youtube': { 'p': 'P2397', 'label': 'Youtube' } }; // -------------------------------------------------------------------- // 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 = { // vCard specific 'Anreise': 'go', 'Mobilit.C3.A4t': 'go', 'Sehensw.C3.BCrdigkeiten': 'see', 'Aktivit.C3.A4ten': 'do', 'Einkaufen': 'buy', 'K.C3.BCche': 'eat', 'Nachtleben': 'drink', 'Unterkunft': 'sleep', 'Lernen': 'education', 'Arbeiten': 'administration', 'Sicherheit': 'administration', 'Gesundheit': 'health', 'Praktische_Hinweise': 'other' }; // 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 = ['#Orte', '#Weitere_Ziele', '#St.C3.A4dte', '#Regionen', '#Inseln', '#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 = 'vCard'; // vCard specific var DEFAULT_TEMPLATE_NO_TYPE = true; var LISTING_TYPE_PARAMETER = 'type'; var LISTING_CONTENT_PARAMETER = 'description'; // vCard specific // 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. // Default skipIfEmpty = true // - newline: Append a newline after the parameter in the listing // template syntax when the article is saved. var SKIP_DEFAULT = true; var LISTING_TEMPLATE_PARAMETERS = { 'type': { id: 'input-type' }, 'name': { id: 'input-name' }, 'alt': { id: 'input-alt' }, 'comment': { id: 'input-comment' }, 'name-local': { id: 'input-name-local' }, 'name-latin': { id: 'input-name-latin' }, 'address': { id: 'input-address' }, 'address-local': { id: 'input-address-local' }, 'directions': { id: 'input-directions' }, 'directions-local': { id: 'input-directions-local' }, 'wikidata': { id: 'input-wikidata-value' }, 'auto': { id: 'input-auto' }, 'url': { id: 'input-url' }, 'facebook': { id: 'input-facebook' }, 'google': { id: 'input-google' }, 'twitter': { id: 'input-twitter' }, 'flickr': { id: 'input-flickr' }, 'youtube': { id: 'input-youtube' }, 'lat': { id: 'input-lat' }, 'long': { id: 'input-long' }, 'image': { id: 'input-image' }, 'group': { id: 'input-group' }, 'intl-area-code': { id: 'input-intl-area-code' }, 'phone': { id: 'input-phone' }, 'tollfree': { id: 'input-tollfree' }, 'mobile': { id: 'input-mobile' }, 'fax': { id: 'input-fax' }, 'email': { id: 'input-email' }, 'skype': { id: 'input-skype' }, 'hours': { id: 'input-hours' }, 'checkin': { id: 'input-checkin' }, 'checkout': { id: 'input-checkout' }, 'price': { id: 'input-price' }, 'lastedit': { id: 'input-lastedit', hideDivIfEmpty: 'div_lastedit' }, 'credit-cards': { id: 'input-credit-cards' }, 'subtype': { id: 'input-subtype' }, 'show': { id: 'input-show' }, 'before': { id: 'input-before' }, 'description': { id: 'input-content', skipIfEmpty: false } }; // Type dependent hide /show var HIDE_AND_SHOW = { 'sleep': { hide: ['div_hours'], show: ['div_checkin', 'div_checkout'] }, 'default': { hide: ['div_checkin', 'div_checkout'], show: ['div_hours'] } }; // map the template name to configuration information needed by the listing // editor var LISTING_TEMPLATES = { // vCard specific 'listing': '', 'see': '', 'do': '', 'buy': '', 'eat': '', 'drink': '', 'sleep': '', 'vCard': '' }; // -------------------------------------------------------------------- // 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'; // -------------------------------------------------------------------- // ADDING TYPE OPTIONS and DEFAULT_PLACEHOLDERS // -------------------------------------------------------------------- var DEFAULT_PLACEHOLDERS = [ { p: ' Bezeichnung der Einrichtung', s: 'input-name' }, { p: ' Auch bekannt als', s: 'input-alt' }, { p: ' Beispiel: http://www.beispiel.de', s: 'input-url' }, { p: ' Anschrift der Einrichtung', s: 'input-address' }, { p: ' Angaben zur Lage der Einrichtung', s: 'input-directions' }, { p: ' Beispiel: +55', s: 'input-intl-area-code' }, { p: ' Beispiel: +55 555 555-5555', s: 'input-phone' }, { p: ' Beispiel: +49 800 100-1000', s: 'input-tollfree' }, { p: ' Beispiel: +55 123 555-555', s: 'input-mobile' }, { p: ' Beispiel: +55 555 555-555', s: 'input-fax' }, { p: ' Beispiel: [email protected]', s: 'input-email' }, { p: ' Hinweis zur Bezeichnung', s: 'input-comment' }, { p: ' Beispiel: myskype', s: 'input-skype' }, { p: ' Beispiele: myfacebook, https://www.facebook.com/myfacebook', s: 'input-facebook' }, { p: ' Beispiel: myflickr', s: 'input-flickr' }, { p: ' Beispiel: mytwitter', s: 'input-twitter' }, { p: ' Beispiel: myyoutube', s: 'input-youtube' }, { p: ' Beispiel: Master, Visa, Amex', s: 'input-credit-cards' }, { p: ' Beispiel: 2015-01-15', s: 'input-lastedit' }, { p: 'Type der Einrichtung', s: 'input-type' }, { p: 'Alternative Typ-Gruppe', s: 'input-group' }, { p: ' Wikidata-Bezeichnung', s: 'input-wikidata-label' }, { p: 'Automatischer Bezug aus Wikidata', s: 'input-auto' }, { p: ' Beispiel: 11.11111', s: 'input-lat' }, { p: ' Beispiel: 111.11111', s: 'input-long' }, { p: ' Beispiel: Mo-Fr 9-18 Uhr', s: 'input-hours' }, { p: ' Checkin-Zeit', s: 'input-checkin' }, { p: ' Checkout-Zeit', s: 'input-checkout' }, { p: ' Eintritts- oder Dienstleistungspreis', s: 'input-price' }, { p: ' Abbildung der Einrichtung', s: 'input-image' }, { p: 'Automatischer Bezug aus Wikidata' }, { p: 'Untergruppen', s: 'input-subtype' }, { p: ' Beispiel: المتحف المصري', s: 'input-name-local' }, { p: ' Beispiel: al-Matḥaf al-Miṣrī', s: 'input-name-latin' }, { p: ' Beispiel: ميدان التحرير', s: 'input-address-local' }, { p: ' Beispiel: بوسط البلد', s: 'input-directions-local' }, { p: ' Beispiel: [[Datei:Sternchen.jpg]]', s: 'input-before' }, ]; var GROUP_PROPERTIES = [ { group: 'do', label: 'Aktivität', color: '#808080' }, // grey { group: 'other', label: 'Anderes', color: '#228B22' }, // forestgreen { group: 'go', label: 'Anreise', color: '#A52A2A' }, // brown { group: 'view', label: 'Aussicht', color: '#4169E1' }, // royalblue { group: 'buy', label: 'Einkaufen', color: '#008080' }, // teal { group: 'eat', label: 'Essen', color: '#D2691E' }, // chocolate { group: 'vicinity', label: 'Nahe Umgebung', color: '#800000' }, // maroon { group: 'see', label: 'Sehenswürdigkeit', color: '#4682B4' }, // steelblue { group: 'populated', label: 'Bewohntes Gebiet', color: '#0000FF' }, // blue { group: 'drink', label: 'Trinken', color: '#000000' }, //black { group: 'around', label: 'Umgebung', color: '#800080' }, // purple { group: 'sleep', label: 'Unterkunft', color: '#000080' }, // dark blue { group: 'target', label: 'Ziel', color: '#FFCCCC' }, //pink { group: 'mediumaquamarine', label: 'Aquamarinblau', color: '#66CDAA' }, { group: 'blue', label: 'Blau', color: '#0000FF' }, { group: 'teal', label: 'Blaugrün', color: '#008080' }, { group: 'brown', label: 'Braun', color: '#A52A2A' }, { group: 'gold', label: 'Gold', color: '#FFD700' }, { group: 'grey', label: 'Grau', color: '#808080' }, { group: 'lime', label: 'Hellgrün', color: '#00FF00' }, { group: 'maroon', label: 'Kastanienbraun', color: '#800000' }, { group: 'royalblue', label: 'Königsblau', color: '#4169E1' }, { group: 'magenta', label: 'Magentarot', color: '#FF00FF' }, { group: 'navy', label: 'Marineblau', color: '#000080' }, { group: 'orange', label: 'Orange', color: '#FFA500' }, { group: 'plum', label: 'Pflaumenblau', color: '#DDA0DD' }, { group: 'fuchsia', label: 'Purpurrot', color: '#FF00FF' }, { group: 'red', label: 'Rot', color: '#FF0000' }, { group: 'chocolate', label: 'Schokobraun', color: '#D2691E' }, { group: 'black', label: 'Schwarz', color: '#000000' }, { group: 'silver', label: 'Silber', color: '#C0C0C0' }, { group: 'steelblue', label: 'Stahlblau', color: '#4682B4' }, { group: 'purple', label: 'Violett', color: '#800080' }, { group: 'forestgreen', label: 'Waldgrün', color: '#228B22' } ]; var LISTING_TYPE_OPTIONS = [ // journey and mobility { 'type': 'aerialway', group: 'go', label: 'Seilbahn' }, { 'type': 'airline', group: 'go', label: 'Fluggesellschaft' }, { 'type': 'airport', group: 'go', label: 'Flughafen' }, { 'type': 'airport_service', group: 'go', label: 'Flughafendienste' }, { 'type': 'bicycle_rental', group: 'go', label: 'Fahrradverleih' }, { 'type': 'bicycle_parking', group: 'go', label: 'Fahrradparkplatz' }, { 'type': 'boat_rental', group: 'go', label: 'Bootsverleih' }, { 'type': 'boat_sharing', group: 'go', label: 'Boot-Sharing/Charter' }, { 'type': 'border_control', group: 'go', label: 'Grenzkontrolle' }, { 'type': 'bus', group: 'go', label: 'Bus' }, { 'type': 'car_rental', group: 'go', label: 'Autoverleih' }, { 'type': 'car_repair', group: 'go', label: 'Autowerkstatt' }, { 'type': 'car_sharing', group: 'go', label: 'Car Sharing' }, { 'type': 'ferry', group: 'go', label: 'Fähre' }, { 'type': 'fuel', group: 'go', label: 'Tankstelle' }, { 'type': 'marina', group: 'go', label: 'Yachthafen' }, { 'type': 'motorbike_rental', group: 'go', label: 'Motorradverleih' }, { 'type': 'parking', group: 'go', label: 'Parkplatz' }, { 'type': 'port', group: 'go', label: 'Hafen' }, { 'type': 'public_transport', group: 'go', label: 'Öffentliche Verkehrsmittel' }, { 'type': 'shipping_company', group: 'go', label: 'Schiffahrtsgesellschaft' }, { 'type': 'shuttle_bus_service', group: 'go', label: 'Shuttlebus-Service' }, { 'type': 'station', group: 'go', label: 'Station' }, { 'type': 'subway', group: 'go', label: 'U-Bahn' }, { 'type': 'taxi', group: 'go', label: 'Taxistand' }, { 'type': 'terminal', group: 'go', label: 'Terminal' }, { 'type': 'train', group: 'go', label: 'Bahnhof, Haltepunkt' }, { 'type': 'tram', group: 'go', label: 'Straßenbahn' }, { 'type': 'waypoint', group: 'go', label: 'Wegpunkt' }, { 'type': 'go', group: 'go', label: 'Anreise' }, // sights { 'type': 'aquarium', group: 'see', label: 'Aquarium' }, { 'type': 'archaeological_site', group: 'see', label: 'Archäologische Stätte' }, { 'type': 'artwork', group: 'see', label: 'Kunstwerk' }, { 'type': 'bay', group: 'see', label: 'Bucht' }, { 'type': 'botanical_garden', group: 'see', label: 'Botanischer Garten' }, { 'type': 'bridge', group: 'see', label: 'Brücke' }, { 'type': 'building', group: 'see', label: 'Gebäude' }, { 'type': 'bunker', group: 'see', label: 'Bunker' }, { 'type': 'canal', group: 'see', label: 'Kanal' }, { 'type': 'castle', group: 'see', label: 'Schloss' }, { 'type': 'cathedral', group: 'see', label: 'Kathedrale' }, { 'type': 'cave', group: 'see', label: 'Höhle' }, { 'type': 'cemetery', group: 'see', label: 'Friedhof' }, { 'type': 'church', group: 'see', label: 'Kirche' }, { 'type': 'crocodile_farm', group: 'see', label: 'Krokodilfarm' }, { 'type': 'farm', group: 'see', label: 'Bauernhof' }, { 'type': 'forest', group: 'see', label: 'Wald' }, { 'type': 'fort', group: 'see', label: 'Festung' }, { 'type': 'gallery', group: 'see', label: 'Galerie' }, { 'type': 'garden', group: 'see', label: 'Garten' }, { 'type': 'gate', group: 'see', label: 'Tor' }, { 'type': 'habitat', group: 'see', label: 'Habitat' }, { 'type': 'island', group: 'see', label: 'Insel' }, { 'type': 'lake', group: 'see', label: 'See' }, { 'type': 'landmark', group: 'see', label: 'Wahrzeichen' }, { 'type': 'landscape', group: 'see', label: 'Landschaft' }, { 'type': 'lighthouse', group: 'see', label: 'Leuchtturm' }, { 'type': 'mausoleum', group: 'see', label: 'Mausoleum' }, { 'type': 'memorial', group: 'see', label: 'Denkmal' }, { 'type': 'mill', group: 'see', label: 'Mühle' }, { 'type': 'mine', group: 'see', label: 'Bergwerk' }, // including surface mining { 'type': 'mining_museum', group: 'see', label: 'Bergbaumuseum' }, { 'type': 'monastery', group: 'see', label: 'Kloster' }, { 'type': 'monument', group: 'see', label: 'Monument' }, { 'type': 'mosque', group: 'see', label: 'Moschee' }, { 'type': 'museum', group: 'see', label: 'Museum' }, { 'type': 'national_park', group: 'see', label: 'Nationalpark' }, { 'type': 'nature_reserve', group: 'see', label: 'Naturschutzgebiet' }, { 'type': 'nunnery', group: 'see', label: 'Nonnenkloster' }, { 'type': 'observatory', group: 'see', label: 'Sternwarte' }, { 'type': 'open_air_museum', group: 'see', label: 'Freilichtmuseum' }, { 'type': 'pagoda', group: 'see', label: 'Pagode' }, { 'type': 'palace', group: 'see', label: 'Palast' }, { 'type': 'palaeontological_site', group: 'see', label: 'Paläontologische Stätte' }, { 'type': 'park', group: 'see', label: 'Park' }, { 'type': 'pedestrian', group: 'see', label: 'Fußgängerzone' }, { 'type': 'peninsula', group: 'see', label: 'Halbinsel' }, { 'type': 'planetarium', group: 'see', label: 'Planetarium' }, { 'type': 'plantation', group: 'see', label: 'Plantage' }, { 'type': 'power', group: 'see', label: 'Kraftwerk, Energie' }, // power station, transformers, towers etc. { 'type': 'pyramid', group: 'see', label: 'Pyramide' }, { 'type': 'quarry', group: 'see', label: 'Steinbruch' }, { 'type': 'road', group: 'see', label: 'Straße' }, // including street and lane { 'type': 'religious_site', group: 'see', label: 'Religiöse Stätte' }, { 'type': 'spring', group: 'see', label: 'Quelle' }, { 'type': 'square', group: 'see', label: 'Platz' }, { 'type': 'stud', group: 'see', label: 'Gestüt' }, { 'type': 'synagogue', group: 'see', label: 'Synagoge' }, { 'type': 'temple', group: 'see', label: 'Tempel' }, { 'type': 'tomb', group: 'see', label: 'Grab' }, { 'type': 'tower', group: 'see', label: 'Turm' }, { 'type': 'town_hall', group: 'see', label: 'Rathaus' }, { 'type': 'tumulus', group: 'see', label: 'Tumulus' }, { 'type': 'viewpoint', group: 'see', label: 'Aussichtspunkt' }, { 'type': 'war_grave', group: 'see', label: 'Kriegsgrab' }, { 'type': 'waterfall', group: 'see', label: 'Wasserfall' }, { 'type': 'well', group: 'see', label: 'Brunnen' }, { 'type': 'wetland', group: 'see', label: 'Feuchtgebiet' }, { 'type': 'zoo', group: 'see', label: 'Zoo, Tierpark' }, { 'type': 'see', group: 'see', label: 'Sehenswürdigkeit' }, // culture { 'type': 'arts_centre', group: 'do', label: 'Kulturzentrum' }, { 'type': 'cabaret', group: 'do', label: 'Cabaret' }, { 'type': 'cinema', group: 'do', label: 'Kino' }, { 'type': 'circus', group: 'do', label: 'Zirkus' }, { 'type': 'edutainment', group: 'do', label: 'Edutainment' }, { 'type': 'fair', group: 'do', label: 'Messe' }, { 'type': 'festival', group: 'do', label: 'Festspiele' }, { 'type': 'music', group: 'do', label: 'Musikdarbietung' }, // different types of music theaters { 'type': 'opera_house', group: 'do', label: 'Opernhaus' }, { 'type': 'puppet_theater', group: 'do', label: 'Puppentheater' }, { 'type': 'theater', group: 'do', label: 'Theater' }, // speech theater, theater in general // recreation { 'type': 'amusement_park', group: 'do', label: 'Vergnügungspark' }, { 'type': 'attraction', group: 'do', label: 'Attraktion' }, { 'type': 'ballooning', group: 'do', label: 'Ballonfahren' }, { 'type': 'casino', group: 'do', label: 'Spielbank, Casino' }, { 'type': 'dolphinarium', group: 'do', label: 'Delfinarium' }, { 'type': 'entertainment', group: 'do', label: 'Unterhaltung' }, { 'type': 'horsecar', group: 'do', label: 'Pferdewagen' }, { 'type': 'massage', group: 'do', label: 'Massagesalon' }, { 'type': 'playground', group: 'do', label: 'Spielplatz' }, { 'type': 'ropes_course', group: 'do', label: 'Seilgarten' }, { 'type': 'sauna', group: 'do', label: 'Sauna' }, { 'type': 'spa', group: 'do', label: 'Heilbad' }, { 'type': 'theme_park', group: 'do', label: 'Themenpark' }, // sports and fitness { 'type': '9pin', group: 'do', label: 'Kegeln' }, { 'type': '10pin', group: 'do', label: 'Bowling' }, { 'type': 'badminton', group: 'do', label: 'Badminton' }, { 'type': 'beach', group: 'do', label: 'Strand' }, { 'type': 'beachvolleyball', group: 'do', label: 'Beachvolleyball' }, { 'type': 'billiards', group: 'do', label: 'Billiard' }, { 'type': 'bmx', group: 'do', label: 'BMX' }, { 'type': 'bobsleigh', group: 'do', label: 'Bobsport' }, { 'type': 'boules', group: 'do', label: 'Boules' }, { 'type': 'bungee_jumping', group: 'do', label: 'Bungeespringen' }, { 'type': 'canoe', group: 'do', label: 'Bootssport' }, // Kanu, Kayak { 'type': 'canyoning', group: 'do', label: 'Canyoning' }, { 'type': 'climbing', group: 'do', label: 'Bergsteigen' }, { 'type': 'cricket', group: 'do', label: 'Cricket' }, { 'type': 'dive_center', group: 'do', label: 'Tauchzentrum' }, { 'type': 'downhill', group: 'do', label: 'Schi Alpin, Abfahrt' }, { 'type': 'equestrian', group: 'do', label: 'Reitsport' }, { 'type': 'extreme_sports', group: 'do', label: 'Extremsport' }, { 'type': 'fishing', group: 'do', label: 'Fischen, Angeln' }, { 'type': 'fitness_centre', group: 'do', label: 'Fitnessstudio' }, // including gym { 'type': 'gliding', group: 'do', label: 'Segelflug' }, { 'type': 'golf', group: 'do', label: 'Golf' }, { 'type': 'hiking', group: 'do', label: 'Wandern' }, // including trekking { 'type': 'hillwalking', group: 'do', label: 'Bergwandern' }, { 'type': 'horse_racing', group: 'do', label: 'Pferderennen' }, { 'type': 'hunting', group: 'do', label: 'Jagd' }, { 'type': 'ice_hockey', group: 'do', label: 'Eishockey' }, { 'type': 'ice_skating', group: 'do', label: 'Eislauf' }, { 'type': 'karting', group: 'do', label: 'Kartsport' }, { 'type': 'kitesurfing', group: 'do', label: 'Kitesurfen' }, { 'type': 'minigolf', group: 'do', label: 'Minigolf' }, { 'type': 'motocross', group: 'do', label: 'Motocross' }, { 'type': 'mountain', group: 'do', label: 'Berg' }, { 'type': 'nordic', group: 'do', label: 'Schi Nordisch' }, // nordic/cross country ski trail { 'type': 'orienteering', group: 'do', label: 'Orientierungslauf' }, { 'type': 'parachuting', group: 'do', label: 'Fallschirmspringen' }, // including skydiving { 'type': 'paragliding', group: 'do', label: 'Paragliding' }, { 'type': 'polo', group: 'do', label: 'Polo' }, { 'type': 'quad_bike', group: 'do', label: 'Quad-Bike' }, { 'type': 'racetrack', group: 'do', label: 'Rennbahn' }, { 'type': 'rafting', group: 'do', label: 'Rafting' }, // including whitewater rafting { 'type': 'roller_skating', group: 'do', label: 'Rollsport' }, // including inline roller skating { 'type': 'rowing', group: 'do', label: 'Rudern' }, { 'type': 'sailing', group: 'do', label: 'Segeln' }, { 'type': 'sandboarding', group: 'do', label: 'Sandboarding' }, { 'type': 'scuba_diving', group: 'do', label: 'Tauchen' }, { 'type': 'ski_jumping', group: 'do', label: 'Schi-Springen' }, { 'type': 'ski_school', group: 'do', label: 'Schischule' }, { 'type': 'skitour', group: 'do', label: 'Schi-Touren' }, { 'type': 'sled', group: 'do', label: 'Schlittensport' }, { 'type': 'snow_park', group: 'do', label: 'Snowpark' }, { 'type': 'soccer', group: 'do', label: 'Fußball' }, { 'type': 'sports', group: 'do', label: 'Sport' }, { 'type': 'stadium', group: 'do', label: 'Stadion' }, { 'type': 'summit', group: 'do', label: 'Gipfel' }, { 'type': 'surfing', group: 'do', label: 'Surfen' }, { 'type': 'swimming', group: 'do', label: 'Schwimmen' }, { 'type': 'swimming_pool', group: 'do', label: 'Schwimmbad' }, { 'type': 'tennis', group: 'do', label: 'Tennis' }, { 'type': 'triathlon', group: 'do', label: 'Triathlon' }, { 'type': 'wakeboarding', group: 'do', label: 'Wakeboarding' }, { 'type': 'water_ski', group: 'do', label: 'Wasserski' }, { 'type': 'water_sports', group: 'do', label: 'Wassersport' }, { 'type': 'winter_sports', group: 'do', label: 'Wintersport' }, { 'type': 'do', group: 'do', label: 'Aktivität' }, // shopping { 'type': 'antiquarian', group: 'buy', label: 'Antiquariat' }, { 'type': 'atm', group: 'buy', label: 'Geldautomat' }, { 'type': 'bakery', group: 'buy', label: 'Bäckerei' }, { 'type': 'bank', group: 'buy', label: 'Bank, Geldwesen' }, { 'type': 'beverages', group: 'buy', label: 'Getränkegeschäft' }, { 'type': 'bike_shop', group: 'buy', label: 'Fahrradladen' }, { 'type': 'book_seller', group: 'buy', label: 'Buchladen' }, { 'type': 'boutique', group: 'buy', label: 'Boutique' }, { 'type': 'butchery', group: 'buy', label: 'Metzgerei, Fleischerei' }, { 'type': 'carpet_shop', group: 'buy', label: 'Teppichgeschäft' }, { 'type': 'chemist', group: 'buy', label: 'Drogerie' }, { 'type': 'collector_shop', group: 'buy', label: 'Sammlergeschäft' }, { 'type': 'cosmetics_shop', group: 'buy', label: 'Kosmetik' }, { 'type': 'crafts_shop', group: 'buy', label: 'Handwerksgeschäft' }, { 'type': 'delicatessen', group: 'buy', label: 'Spezialitätengeschäft' }, { 'type': 'department_store', group: 'buy', label: 'Kaufhaus' }, { 'type': 'duty-free_shop', group: 'buy', label: 'Duty-Free-Geschäft' }, { 'type': 'fashion_store', group: 'buy', label: 'Modegeschäft' }, { 'type': 'flea_market', group: 'buy', label: 'Flohmarkt' }, { 'type': 'hairdresser', group: 'buy', label: 'Friseur' }, { 'type': 'jewellery', group: 'buy', label: 'Juwelier' }, { 'type': 'kiosk', group: 'buy', label: 'Kiosk' }, { 'type': 'mall', group: 'buy', label: 'Einkaufszentrum, Mall' }, { 'type': 'market', group: 'buy', label: 'Markt' }, { 'type': 'music_shop', group: 'buy', label: 'Musikgeschäft' }, { 'type': 'optician', group: 'buy', label: 'Optiker' }, { 'type': 'outdoor_retailer', group: 'buy', label: 'Outdoor-Händler' }, { 'type': 'pastry_shop', group: 'buy', label: 'Konditorei' }, { 'type': 'photo_store', group: 'buy', label: 'Fotogeschäft' }, { 'type': 'second_hand', group: 'buy', label: 'Gebrauchtwarenhändler' }, { 'type': 'shop', group: 'buy', label: 'Geschäft' }, { 'type': 'souvenir_shop', group: 'buy', label: 'Souvenirgeschäft' }, { 'type': 'sports_shop', group: 'buy', label: 'Sportgeschäft' }, { 'type': 'supermarket', group: 'buy', label: 'Supermarkt' }, { 'type': 'watersports_shop', group: 'buy', label: 'Wassersportgeschäft' }, { 'type': 'buy', group: 'buy', label: 'Einkaufen' }, { 'type': 'bistro', group: 'eat', label: 'Bistro' }, { 'type': 'brasserie', group: 'eat', label: 'Brasserie' }, { 'type': 'brewery', group: 'eat', label: 'Brauerei' }, { 'type': 'cafe', group: 'eat', label: 'Café' }, { 'type': 'cafeteria', group: 'eat', label: 'Cafeteria' }, { 'type': 'canteen', group: 'eat', label: 'Kantine' }, // auch für Mensa { 'type': 'coffee_shop', group: 'eat', label: 'Kaffeegeschäft' }, { 'type': 'fast_food', group: 'eat', label: 'Fastfood' }, { 'type': 'ice_cream', group: 'eat', label: 'Eisdiele' }, { 'type': 'restaurant', group: 'eat', label: 'Restaurant' }, { 'type': 'restaurant_and_bar', group: 'eat', label: 'Restaurant und Bar' }, { 'type': 'snack_bar', group: 'eat', label: 'Imbiss' }, { 'type': 'steak_house', group: 'eat', label: 'Steakhaus' }, { 'type': 'eat', group: 'eat', label: 'Küche, Essen' }, { 'type': 'bar', group: 'drink', label: 'Bar' }, { 'type': 'beer_garden', group: 'drink', label: 'Biergarten' }, { 'type': 'club', group: 'drink', label: 'Club' }, { 'type': 'discotheque', group: 'drink', label: 'Diskothek' }, { 'type': 'distillery', group: 'drink', label: 'Brennerei' }, { 'type': 'nightclub', group: 'drink', label: 'Nachtclub' }, { 'type': 'pub', group: 'drink', label: 'Kneipe' }, { 'type': 'vineyard', group: 'drink', label: 'Weingut' }, { 'type': 'drink', group: 'drink', label: 'Trinken' }, { 'type': 'appartment', group: 'sleep', label: 'Appartements' }, { 'type': 'alpine_hut', group: 'sleep', label: 'Berghütte' }, { 'type': 'bed_and_bike', group: 'sleep', label: 'Bett und Bike' }, { 'type': 'boarding_house', group: 'sleep', label: 'Pension' }, { 'type': 'campsite', group: 'sleep', label: 'Campingplatz' }, { 'type': 'caravan_site', group: 'sleep', label: 'Caravan-Campingplatz' }, { 'type': 'chalet', group: 'sleep', label: 'Chalet' }, { 'type': 'guest_house', group: 'sleep', label: 'Gästehaus' }, { 'type': 'holiday_flat', group: 'sleep', label: 'Ferienwohnung' }, { 'type': 'hostel', group: 'sleep', label: 'Herberge' }, { 'type': 'hotel', group: 'sleep', label: 'Hotel' }, { 'type': 'hotel_garni', group: 'sleep', label: 'Hotel Garni' }, { 'type': 'motel', group: 'sleep', label: 'Motel' }, { 'type': 'resort', group: 'sleep', label: 'Ressort' }, { 'type': 'wilderness_hut', group: 'sleep', label: 'Wildnishütte' }, { 'type': 'youth_hostel', group: 'sleep', label: 'Jugendherberge' }, { 'type': 'sleep', group: 'sleep', label: 'Unterkunft' }, // health { 'type': 'clinic', group: 'other', label: 'Klinik' }, { 'type': 'dentist', group: 'other', label: 'Zahnarzt' }, { 'type': 'health', group: 'other', label: 'Gesundheit' }, { 'type': 'health_centre', group: 'other', label: 'Ärztehaus' }, { 'type': 'hospital', group: 'other', label: 'Krankenhaus' }, { 'type': 'laboratory', group: 'other', label: 'Labor' }, { 'type': 'naturopathy', group: 'other', label: 'Naturheilkunde' }, { 'type': 'nursing_home', group: 'other', label: 'Pflegeheim' }, { 'type': 'pharmacy', group: 'other', label: 'Apotheke' }, { 'type': 'practice', group: 'other', label: 'Praxis' }, { 'type': 'rehabilitation', group: 'other', label: 'Rehabilitation' }, { 'type': 'surgery', group: 'other', label: 'Arztpraxis' }, { 'type': 'veterinary', group: 'other', label: 'Tierarzt' }, // teaching & learning { 'type': 'academy', group: 'other', label: 'Akademie' }, { 'type': 'bookcase', group: 'other', label: 'Bücherschrank' }, { 'type': 'college', group: 'other', label: 'Hochschule' }, { 'type': 'cooking_class', group: 'other', label: 'Kochkurse' }, { 'type': 'education', group: 'other', label: 'Ausbildung' }, { 'type': 'kindergarten', group: 'other', label: 'Kindergarten' }, { 'type': 'language_school', group: 'other', label: 'Sprachschule' }, { 'type': 'library', group: 'other', label: 'Bücherei' }, { 'type': 'nursery', group: 'other', label: 'Kinderkrippe' }, { 'type': 'school', group: 'other', label: 'Schule' }, { 'type': 'university', group: 'other', label: 'Universität' }, // administration and uncategorized { 'type': 'administration', group: 'other', label: 'Verwaltung' }, { 'type': 'company', group: 'other', label: 'Unternehmen' }, { 'type': 'consulate', group: 'other', label: 'Konsulat' }, { 'type': 'customs', group: 'other', label: 'Zoll' }, { 'type': 'cultural_organisation', group: 'other', label: 'Kulturorganisation' }, { 'type': 'embassy', group: 'other', label: 'Botschaft' }, { 'type': 'fire_brigade', group: 'other', label: 'Feuerwehr' }, { 'type': 'government', group: 'other', label: 'Regierungsgebäude' }, { 'type': 'guide', group: 'other', label: 'Fremdenführer' }, { 'type': 'internet_cafe', group: 'other', label: 'Internetcafé' }, { 'type': 'laundry', group: 'other', label: 'Wäscherei' }, { 'type': 'listing', group: 'other', label: 'Listung' }, { 'type': 'lost_and_found', group: 'other', label: 'Fundbüro' }, { 'type': 'mobile_telephony', group: 'other', label: 'Mobilfunkgeschäft' }, { 'type': 'office', group: 'other', label: 'Büro' }, { 'type': 'organisation', group: 'other', label: 'Organisation' }, { 'type': 'phone', group: 'other', label: 'Telefon' }, { 'type': 'police', group: 'other', label: 'Polizei' }, { 'type': 'post', group: 'other', label: 'Post' }, { 'type': 'relief_organisation', group: 'other', label: 'Wohltätigkeitsorganisation' }, { 'type': 'shelter', group: 'other', label: 'Unterstand' }, { 'type': 'toilet', group: 'other', label: 'Toiletten' }, { 'type': 'tour_operator', group: 'other', label: 'Ausflugsveranstalter' }, { 'type': 'tourism_authority', group: 'other', label: 'Tourismusbehörde' }, { 'type': 'tourist_information', group: 'other', label: 'Touristinformation' }, { 'type': 'travel_agency', group: 'other', label: 'Reisebüro' }, { 'type': 'other', group: 'other', label: 'Anderes' }, // settlements { 'type': 'city', group: 'populated', label: 'Stadt' }, { 'type': 'holiday_resort', group: 'populated', label: 'Feriensiedlung' }, { 'type': 'municipality', group: 'populated', label: 'Gemeinde' }, { 'type': 'quarter', group: 'populated', label: 'Stadtteil' }, { 'type': 'settlement', group: 'populated', label: 'Siedlung' }, { 'type': 'town', group: 'populated', label: 'Kleinstadt' }, { 'type': 'village', group: 'populated', label: 'Dorf' }, // view { 'type': 'scenic_view', group: 'view', label: 'Bildmotiv' }, ]; LISTING_TYPE_OPTIONS.sort( function(a, b) { return a.label.localeCompare(b.label); }); // 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-container">' + '<div class="listing-col listing-span_1_of_2">' + '<div id="div_name" class="editor-row">' + '<div><label for="input-name" title="Bezeichnung der Einrichtung">Name</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Bezeichnung der Einrichtung" id="input-name"></div>' + '</div>' + '<div id="div_alt" class="editor-row">' + '<div><label for="input-alt" title="Alternative aktuelle Bezeichnung der Einrichtung">Alternative</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Auch bekannt als" id="input-alt"></div>' + '</div>' + '<div id="div_comment" class="editor-row">' + '<div><label for="input-comment" title="Anmerkung zum Namen oder zur Einrichtung, die nicht Namensbestandteil ist. Zum Beispiel frühere Namen.">Kommentar</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Hinweis zur Bezeichnung" id="input-comment"></div>' + '</div>' + '<div id="div_url" class="editor-row">' + '<div><label for="input-url" class="wikidata-update" title="Webadresse der Einrichtung">Website</label></div>' + '<div><input type="url" class="editor-fullwidth" placeholder=" Beispiel: http://www.beispiel.de" id="input-url"></div>' + '</div>' + '<div id="div_address" class="editor-row">' + '<div><label for="input-address" class="wikidata-update" title="Anschrift der Einrichtung">Anschrift</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Anschrift der Einrichtung" id="input-address"></div>' + '</div>' + '<div id="div_directions" class="editor-row">' + '<div><label for="input-directions" title="Angaben zur Lage der Einrichtung">Lage</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Angaben zur Lage der Einrichtung" id="input-directions"></div>' + '</div>' + '<div id="div_intl-area-code" class="editor-row">' + '<div><label for="input-intl-area-code" title="Ländervorwahl für alle Telefonnummern">Intl. Vorwahl</label></div>' + '<div><input type="text" class="editor-partialwidth" placeholder=" Beispiel: +55" id="input-intl-area-code"></div>' + '</div>' + '<div id="div_phone" class="editor-row">' + '<div><label for="input-phone" class="wikidata-update" title="Telefonnummern der Einrichtung, mit Komma getrennt">Telefon</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: +55 555 555-5555" id="input-phone"></div>' + '</div>' + '<div id="div_tollfree" class="editor-row">' + '<div><label for="input-tollfree" title="Gebührenfreie Telefonnummern der Einrichtung, mit Komma getrennt">Gebührenfrei</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: +49 800 100-1000" id="input-tollfree"></div>' + '</div>' + '<div id="div_mobile" class="editor-row">' + '<div><label for="input-mobile" title="Mobil-Telefonnummern der Einrichtung, mit Komma getrennt">Handy</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: +55 123 555-555" id="input-mobile"></div>' + '</div>' + '<div id="div_fax" class="editor-row">' + '<div><label for="input-fax" class="wikidata-update" title="Fax-Telefonnummern der Einrichtung, mit Komma getrennt">Fax</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: +55 555 555-555" id="input-fax"></div>' + '</div>' + '<div id="div_email" class="editor-row">' + '<div><label for="input-email" class="wikidata-update" title="E-Mail-Adressen der Einrichtung, mit Komma getrennt">E-Mails</label></div>' + '<div><input type="email" multiple="" class="editor-fullwidth" placeholder=" Beispiel: [email protected]" id="input-email"></div>' + '</div>' + '<div id="div_skype" class="editor-row">' + '<div><label for="input-skype" class="wikidata-update" title="Skype-Benutzername der Einrichtung">Skype-Name</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: myskype" id="input-skype"></div>' + '</div>' + '<div id="div_facebook" class="editor-row">' + '<div><label for="input-facebook" class="wikidata-update" title="Facebook-Webadresse oder Facebook-Profil-ID der Einrichtung">Facebook-URL</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiele: myfacebook, https://www.facebook.com/myfacebook" id="input-facebook"></div>' + '</div>' + '<div id="div_google" class="editor-row">' + '<div><label for="input-google" class="wikidata-update" title="Google+-Webadresse der Einrichtung">Google+-URL</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: mygoogle" id="input-google"></div>' + '</div>' + '<div id="twitter" class="editor-row">' + '<div><label for="input-twitter" class="wikidata-update" title="Twitter-Webadresse der Einrichtung">Twitter-URL</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: mytwitter" id="input-twitter"></div>' + '</div>' + '<div id="div_flickr" class="editor-row">' + '<div><label for="input-flickr" class="wikidata-update" title="Name der flickr-Gruppe">flickr-Gruppe</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: myflickr" id="input-flickr"></div>' + '</div>' + '<div id="youtube" class="editor-row">' + '<div><label for="input-youtube" class="wikidata-update" title="Webadresse des YouTube-Kanals">YouTube-Kanal</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: myyoutube" id="input-youtube"></div>' + '</div>' + '</div>' + '<div class="listing-col listing-span_1_of_2">' + '<div id="div_type" class="editor-row">' + '<div><label for="input-type" class="wikidata-update" title="Typ der Einrichtung">Typ</label></div>' + '<div>' + '<select id="input-type" placeholder="Type der Einrichtung">' + '<option value=""></option>'; var anOptionObj; for (var i = 0; i < LISTING_TYPE_OPTIONS.length; i++) { anOptionObj = LISTING_TYPE_OPTIONS[i]; EDITOR_FORM_HTML += '<option value="' + anOptionObj.type + '">' + anOptionObj.label + '</option>'; } EDITOR_FORM_HTML += '</select>' + ' <label for="input-group" title="Überschreibt die automatisch ermittelte Gruppenzugehörigkeit z. B. mit buy (Einkaufen), do (Aktivitäten), drink (Ausgehen), eat (Küche), go (Anreise), see (Sehenswürdigkeiten) und sleep (Unterkunft)">Typ-Gruppe</label> ' + '<select id="input-group" placeholder="Alternative Typ-Gruppe">' + '<option value=""></option>'; for (i = 0; i < GROUP_PROPERTIES.length; i++) { anOptionObj = GROUP_PROPERTIES[i]; EDITOR_FORM_HTML += '<option value="' + anOptionObj.group + '">' + anOptionObj.label + '</option>'; } EDITOR_FORM_HTML += '</select>' + '</div>' + '</div>' + '<div id="div_wikidata" class="editor-row">' + '<div><label for="input-wikidata-label" title="Bezeichnung des Wikidata-Datenobjekts">Wikidata</label></div>' + '<div>' + '<input type="text" class="editor-partialwidth" placeholder=" Wikidata-Bezeichnung" 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="Lösche Wikidata-Eintrag aus der vCard">löschen</a>' + '</small>' + '</span>' + '</div>' + '</div>' + '<div id="div_auto" class="editor-row" style="display: none">' + '<div><label for="input-auto" title="Automatischer Bezug aller Angaben aus Wikidata">Auto</label></div>' + '<div><select id="input-auto" placeholder="Automatischer Bezug aus Wikidata">' + '<option value=""></option>' + '<option value="y">ja</option>' + '<option value="n">nein</option>' + '</select> <span id="div_wikidata_update" style="display: none"><a href="javascript:" id="wikidata-shared">Aktualisiere Felder aus Wikidata</a></span></div>' + '</div>' + '<div id="div_lat" class="editor-row">' + '<div><label for="input-lat" class="wikidata-update" title="Geografische Breite der Position der Einrichtung">Breite</label></div>' + '<div><input type="text" class="editor-partialwidth" placeholder=" Beispiel: 11.11111" id="input-lat">' + // update the ListingEditor.Callbacks.initFindOnMapLink // method if this field is removed or modified ' <a id="geomap-link" target="_blank" href="http://maps.wikivoyage-ev.org/w/geomap.php">Suche auf einer Karte</a></div>' + '</div>' + '<div id="div_long" class="editor-row">' + '<div><label for="input-long" class="wikidata-update" title="Geografische Länge der Position der Einrichtung">Länge</label></div>' + '<div><input type="text" class="editor-partialwidth" placeholder=" Beispiel: 111.11111" id="input-long"></div>' + '</div>' + '<div id="div_hours" class="editor-row">' + '<div><label for="input-hours" title="Öffnungszeiten der Einrichtung">Geöffnet</label></div>' + '<div><input type="text" size="255" class="editor-fullwidth" placeholder=" Beispiel: Mo-Fr 9-18 Uhr" id="input-hours"></div>' + '</div>' + '<div id="div_checkin" class="editor-row">' + '<div><label for="input-checkin" title="Früheste Checkin-Zeit">Checkin</label></div>' + '<div><input type="text" size="255" class="editor-fullwidth" placeholder=" Checkin-Zeit" id="input-checkin"></div>' + '</div>' + '<div id="div_checkout" class="editor-row">' + '<div><label for="input-checkout" title="Späteste Checkout-Zeit">Checkout</label></div>' + '<div><input type="text" size="255" class="editor-fullwidth" placeholder=" Checkout-Zeit" id="input-checkout"></div>' + '</div>' + '<div id="div_price" class="editor-row">' + '<div><label for="input-price" title="Eintritts- oder Übernachtungspreise der Einrichtung">Preis</label></div>' + '<div>' + // update the ListingEditor.Callbacks.initCurrencySymbolFormFields // method if the currency symbols are removed or modified '<input type="text" class="editor-partialwidth" placeholder=" Eintritts- oder Dienstleistungspreis" id="input-price">' + '<span id="span_currency">' + '<span class="currency-signs"> <a href="javascript:">\u20AC</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:">\u00A5</a></span>' + '<span class="currency-signs"> <a href="javascript:">\u20A9</a></span>' + '</span>' + '</div>' + '</div>' + '<div id="credit-cards" class="editor-row">' + '<div><label for="input-credit-cards" title="Von der Einrichtung akzeptierte Kreditkarten">Kreditkarten</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: Master, Visa, Amex" id="input-credit-cards"></div>' + '</div>' + '<div id="div_image" class="editor-row">' + '<div><label for="input-image" class="wikidata-update" title="Bild, das beim Anklicken der Nummer auf der Karte eingeblendet werden soll">Bild</label></div>' + '<div>' + '<input type="text" class="editor-partialwidth" placeholder=" Abbildung der Einrichtung" id="input-image">' + '<span id="image-value-display-container" style="display:none">' + '<small>' + ' <span id="image-value-link"></span>' + '</small>' + '</span>' + '</div>' + '</div>' + '<div id="div_show" class="editor-row">' + '<div><label for="input-show" title="Legt die Anzeige von POIs und Koordinaten fest. Nur nötig, wenn der Anzeigemodus vom Standard (meist „nur Poi“) abweicht">Anzeigemodus</label></div>' + '<div><select id="input-show" placeholder="Automatischer Bezug aus Wikidata">' + '<option value=""></option>' + '<option value="all">POI und Koordinaten</option>' + '<option value="poi">nur POI</option>' + '<option value="coord">nur Koordinaten</option>' + '<option value="none">gar nichts</option>' + '</select></div>' + '</div>' + '<div id="div_subtype" class="editor-row">' + '<div><label for="input-subtype" title="Preislage der Einrichtung">Untertyp</label></div>' + '<div><select id="input-subtype" placeholder="Untergruppen">' + '<option value=""></option>' + '<option value="budget">günstig</option>' + '<option value="midrange">mittel</option>' + '<option value="upmarket">gehoben</option>' + '</select></div>' + '</div>' + '<div id="div_name-local" class="editor-row">' + '<div><label for="input-name-local" class="wikidata-update" title="Bezeichnung der Einrichtung in der Landessprache. Zusätzlich zu Name">Name in Landessprache</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: المتحف المصري" id="input-name-local"></div>' + '</div>' + '<div id="div_name-latin" class="editor-row">' + '<div><label for="input-name-latin" title="Bezeichnung der Einrichtung in der Umschrift der Landessprache">Name in Umschrift</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: al-Matḥaf al-Miṣrī" id="input-name-latin"></div>' + '</div>' + '<div id="div_address-local" class="editor-row">' + '<div><label for="input-address-local" class="wikidata-update" title="Anschrift der Einrichtung in der Landessprache. Zusätzlich zur Anschrift">Anschrift in Landessprache</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: ميدان التحرير" id="input-address-local"></div>' + '</div>' + '<div id="div_directions-local" class="editor-row">' + '<div><label for="input-directions-local" title="Angaben zur Lage der Einrichtung in der Landessprache. Zusätzlich zur Lage">Lage in Landessprache</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: بوسط البلد" id="input-directions-local"></div>' + '</div>' + '<div id="div_before" class="editor-row">' + '<div><label for="input-before" title="An den Anfang gestellte Symbole usw.">Voranstellung</label></div>' + '<div><input type="text" class="editor-fullwidth" placeholder=" Beispiel: [[Datei:Sternchen.jpg]]" id="input-before"></div>' + '</div>' + '</div>' + '</div>' + '<div id="div_content" class="editor-row">' + '<div><label for="input-content" title="Beschreibung der Einrichtung">Beschreibung</label></div>' + '<div><textarea rows="8" class="editor-fullwidth" placeholder="Beschreibung der Einrichtung" id="input-content"></textarea></div>' + '</div>' + // update the ListingEditor.Callbacks.hideEditOnlyFields method if // the status row is removed or modified '<div id="div_status" class="editor-row">' + '<div><label title="Angaben zum Artikelstatus wie Löschung oder Aktualisierung">Status</label></div>' + '<div>' + '<span id="span-closed">' + '<input type="checkbox" id="input-closed">' + '<label for="input-closed" class="listing-tooltip" title="Markiere diese Option, falls diese Einrichtung nicht mehr besteht oder falls der Eintrag aus einem anderen Grund gelöscht werden soll. Der Eintrag wird dann aus dem Artikel entfernt.">Diesen Eintrag löschen?</label>' + '</span>' + // update the ListingEditor.Callbacks.updateLastEditDate // method if the last edit input is removed or modified '<span id="div_lastedit">' + '<label for="input-lastedit" title="Datum der letzten Bearbeitung in der Form jjjj-mm-tt, sog. ISO-Datum. Bei leer gelassenem Feld wird beim Abspeichern das heutige Datum eingetragen.">Letzte Aktualisierung</label> ' + '<input type="text" size="10" placeholder="2015-01-15" id="input-lastedit">' + '</span>' + '<span id="span-last-edit">' + '<input type="checkbox" id="input-last-edit" />' + '<label for="input-last-edit" class="listing-tooltip" title="Markiere diese Option, falls die Angaben als aktuell und fehlerfrei überprüft wurden. Als Update-Datum wird der Eintrag oder das Datum des heutigen Tags verwendet.">Diesen Eintrag als aktuell markieren?</label>' + '</span>' + '</div>' + '</div>' + // update the ListingEditor.Callbacks.hideEditOnlyFields method if // the summary table is removed or modified '<div id="div_summary">'+ '<div class="listing-divider"></div>' + '<div class="editor-row">' + '<div><label for="input-summary" title="Kurze Zusammenfassung zum Grund der Änderung">Zusammenfassung</label></div>' + '<div>' + '<input type="text" class="editor-partialwidth" placeholder="Grund der Änderung der vCard" id="input-summary">' + '<span id="span-minor"><input type="checkbox" id="input-minor"><label for="input-minor" class="listing-tooltip" title="Markiere diese Option, falls nur geringfügige Änderungen wie z. B. Schreibfehler ausgeführt wurden.">Nur Kleinigkeiten wurden verändert</label></span>' + '</div>' + '</div>' + '</div>' + '<div id="listing-preview" style="display: none;">' + '<div class="listing-divider"></div>' + '<div class="editor-row">' + '<div title="Vorschau der vCard mit den aktuellen Formulardaten">Vorschau</div>' + '<div><div id="listing-preview-text"></div></div>' + '</div>' + '</div>' + '</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, WIKIDATA_CLAIMS: WIKIDATA_CLAIMS, TRANSLATIONS: TRANSLATIONS, MAX_DIALOG_WIDTH: MAX_DIALOG_WIDTH, DISALLOW_ADD_LISTING_IF_PRESENT: DISALLOW_ADD_LISTING_IF_PRESENT, DEFAULT_LISTING_TEMPLATE: DEFAULT_LISTING_TEMPLATE, DEFAULT_TEMPLATE_NO_TYPE: DEFAULT_TEMPLATE_NO_TYPE, LISTING_TYPE_PARAMETER: LISTING_TYPE_PARAMETER, LISTING_CONTENT_PARAMETER: LISTING_CONTENT_PARAMETER, DEFAULT_PLACEHOLDERS: DEFAULT_PLACEHOLDERS, HIDE_AND_SHOW: HIDE_AND_SHOW, EDIT_LINK_CONTAINER_SELECTOR: EDIT_LINK_CONTAINER_SELECTOR, ALLOW_UNRECOGNIZED_PARAMETERS: ALLOW_UNRECOGNIZED_PARAMETERS, SECTION_TO_TEMPLATE_TYPE: SECTION_TO_TEMPLATE_TYPE, LISTING_TEMPLATES: LISTING_TEMPLATES, LISTING_TEMPLATE_PARAMETERS: LISTING_TEMPLATE_PARAMETERS, EDITOR_FORM_SELECTOR: EDITOR_FORM_SELECTOR, EDITOR_CLOSED_SELECTOR: EDITOR_CLOSED_SELECTOR, EDITOR_SUMMARY_SELECTOR: EDITOR_SUMMARY_SELECTOR, EDITOR_MINOR_EDIT_SELECTOR: EDITOR_MINOR_EDIT_SELECTOR, LISTING_TYPE_OPTIONS: LISTING_TYPE_OPTIONS, GROUP_PROPERTIES: GROUP_PROPERTIES, EDITOR_FORM_HTML: EDITOR_FORM_HTML, YES_ARRAY: YES_ARRAY, NO_ARRAY: NO_ARRAY }; }(); // -------------------------- ListingEditor.Callbacks-------------------------- /* *********************************************************************** * 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); priceInput.select(); // now setting the cursor behind the currency symbol inserted priceInput[0].setSelectionRange(caretPos + 1, caretPos + 1); }); }; 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); /** * Add listeners on type selector field. */ var showAndHideFields = function(valueSelected, form) { var group = ''; var anOptionObj; var i; for (i = 0; i < ListingEditor.Config.LISTING_TYPE_OPTIONS.length; i++) { anOptionObj = ListingEditor.Config.LISTING_TYPE_OPTIONS[i]; if (anOptionObj.type === valueSelected) { group = anOptionObj.group; break; } } anOptionObj = ListingEditor.Config.HIDE_AND_SHOW[group]; if (!anOptionObj) anOptionObj = ListingEditor.Config.HIDE_AND_SHOW.default; if (anOptionObj && anOptionObj.show.length > 0) { for(i = 0; i < anOptionObj.show.length; i++) { $('#' + anOptionObj.show[i], form).show(); } } if (anOptionObj && anOptionObj.hide.length > 0) { for(i = 0; i < anOptionObj.hide.length; i++) { if ($('#' + anOptionObj.hide[i] + ' input', form).val() === '') $('#' + anOptionObj.hide[i], form).hide(); } } // set input shadow var color = '#ffffff'; for (i = 0; i < ListingEditor.Config.GROUP_PROPERTIES.length; i++) { anOptionObj = ListingEditor.Config.GROUP_PROPERTIES[i]; if (anOptionObj.group === group) { color = anOptionObj.color; break; } } $('#input-type', form).css( 'box-shadow', '20px 0 0 0 ' + color + ' inset' ); }; var initTypeSelector = function(form, mode) { showAndHideFields( $('#input-type', form).val(), form ); $('#input-type', form).change(function () { var optionSelected = $('option:selected', this); var valueSelected = this.value; showAndHideFields(valueSelected, form); }); }; CREATE_FORM_CALLBACKS.push(initTypeSelector); var setGroupColor = function(group, form) { var anOptionObj; var i; var color = '#ffffff'; for (i = 0; i < ListingEditor.Config.GROUP_PROPERTIES.length; i++) { anOptionObj = ListingEditor.Config.GROUP_PROPERTIES[i]; if (anOptionObj.group === group) { color = anOptionObj.color; break; } } $('#input-group', form).css( 'box-shadow', '20px 0 0 0 ' + color + ' inset' ); }; var initGroupSelector = function(form, mode) { setGroupColor( $('#input-group', form).val(), form ); $('#input-group', form).on('keydown keyup change', function () { /* var optionSelected = $('option:selected', this); var valueSelected = this.value; setGroupColor(valueSelected, form); */ var box = $(this); setTimeout(function() { setGroupColor(box.val(), form); }, 0); }); }; CREATE_FORM_CALLBACKS.push(initGroupSelector); var initLastEditCheckBox = function(form, mode) { $('#input-last-edit', form).change(function () { if (this.checked && $('#div_lastedit', form).is(':visible')) $('#input-lastedit', form).val( currentLastEditDate() ); }); }; CREATE_FORM_CALLBACKS.push(initLastEditCheckBox); 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 checkForSplit = function(value, form) { if ($('#input-long', form).val() !== '') return; value = value.toUpperCase(); var coords = value.split(/[,;]/); if (coords.length === 2) { $('#input-lat', form).val( coords[0].trim() ); $('#input-long', form).val( coords[1].trim() ); return; } var dir = ['N', 'S']; for (var i = 0; i < dir.length; i++ ) { coords = value.split(dir[i]); if (coords.length === 2) { $('#input-lat', form).val( coords[0].trim() + ' ' + dir[i]); $('#input-long', form).val( coords[1].trim() ); return; } } }; var splitCoordinate = function(form, mode) { checkForSplit( $('#input-lat', form).val(), form ); $('#input-lat', form).blur(function() { checkForSplit( $('#input-lat', form).val(), form ); }); }; CREATE_FORM_CALLBACKS.push(splitCoordinate); var addInputClass = function(form) { $('input', form).each( function() { if ($( this ).val() === '') $( this ).addClass( "listing-empty-input" ); else $( this ).removeClass( "listing-empty-input" ); }); }; var checkEmptyInput = function (form, mode) { addInputClass(form); $('input', form).blur(function() { addInputClass( form ); }); }; CREATE_FORM_CALLBACKS.push(checkEmptyInput); var setDefaultPlaceholders = function() { var obj; for (var i = 0; i < ListingEditor.Config.DEFAULT_PLACEHOLDERS.length; i++) { obj = ListingEditor.Config.DEFAULT_PLACEHOLDERS[i]; $("#" + obj.s).attr('placeholder', obj.p); $("#" + obj.s).addClass('listing-default-placeholder'); $("#" + obj.s).removeClass('listing-wikidata-placeholder'); } }; var updatePlaceholder = function(selector, value) { if (value) { $(selector).attr('placeholder', value); $(selector).addClass('listing-wikidata-placeholder'); $(selector).removeClass('listing-default-placeholder'); } }; var updatePlaceholders = function(value) { setDefaultPlaceholders(); var ajaxUrl = ListingEditor.SisterSite.API_WIKIDATA; var ajaxData = { action: 'wbgetentities', ids: value, languages: ListingEditor.Config.LANG, }; var ajaxSuccess = function (jsonObj) { var res; for (var key in ListingEditor.Config.WIKIDATA_CLAIMS) { res = ListingEditor.SisterSite.wikidataClaim(jsonObj, value, ListingEditor.Config.WIKIDATA_CLAIMS[key].p); if (res) { if (key === 'coord') { res.latitude = ListingEditor.Core.trimDecimal(res.latitude, 6); res.longitude = ListingEditor.Core.trimDecimal(res.longitude, 6); updatePlaceholder('#input-lat', res.latitude); updatePlaceholder('#input-long', res.longitude); } else if (key === 'email') { res = res.replace('mailto:', ''); updatePlaceholder('#input-' + key, res); } else updatePlaceholder('#input-' + key, res); } res = ListingEditor.SisterSite.wikidataLabel(jsonObj, value); updatePlaceholder('#input-wikidata-label', res); $('#input-wikidata-label').removeClass('listing-wikidata-placeholder'); $('#input-wikidata-label').addClass('listing-default-placeholder'); updatePlaceholder('#input-name', res); } }; ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); }; 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 = ""; } if (label === '') $("#input-wikidata-label").addClass(' listing-empty-input'); else $("#input-wikidata-label").removeClass(' listing-empty-input'); $("#input-wikidata-label").val(label); }; ListingEditor.SisterSite.ajaxSisterSiteSearch(ajaxUrl, ajaxData, ajaxSuccess); updatePlaceholders(value); } // 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); updatePlaceholders(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(); $('#div_auto', form).hide(); $('#input-auto').val(''); // vCard specific setDefaultPlaceholders(); }; $('#wikidata-shared', form).click(function() { var wikidataRecord = $("#input-wikidata-value", form).val(); updateWikidataSharedFields(wikidataRecord); }); var commonsSiteData = { apiUrl: ListingEditor.SisterSite.API_COMMONS, selector: $('#input-image', form), form: form, ajaxData: { namespace: 6 }, updateLinkFunction: commonsLink }; ListingEditor.SisterSite.initializeSisterSiteAutocomplete(commonsSiteData); }; var commonsLink = function(value, form) { var commonsSiteLinkData = { inputSelector: '#input-image', containerSelector: '#image-value-display-container', linkContainerSelector: '#image-value-link', href: ListingEditor.Config.COMMONS_URL + '/wiki/' + mw.util.wikiUrlencode('File:' + value), linkTitle: ListingEditor.Config.TRANSLATIONS.viewCommonsPage }; sisterSiteLinkDisplay(commonsSiteLinkData, form); }; var sisterSiteLinkDisplay = function(siteLinkData, form) { var value = $(siteLinkData.inputSelector, form).val(); if (!value) { $(siteLinkData.containerSelector, form).hide(); } else { var link = $("<a />", { target: "_new", href: siteLinkData.href, title: siteLinkData.linkTitle, text: siteLinkData.linkTitle }); $(siteLinkData.linkContainerSelector, form).html(link); $(siteLinkData.containerSelector, form).show(); } }; var updateFieldIfNotNull = function(selector, value) { if (value) { $(selector).val(value); $(selector).removeClass('listing-empty-input'); } }; 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 msg = ''; var label = ListingEditor.SisterSite.wikidataLabel(jsonObj, wikidataRecord); if (label !== null) msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedName + ': ' + label; var res = {}; for (var key in ListingEditor.Config.WIKIDATA_CLAIMS) { res[key] = ListingEditor.SisterSite.wikidataClaim(jsonObj, wikidataRecord, ListingEditor.Config.WIKIDATA_CLAIMS[key].p); if (res[key]) { if (key === 'coord') { res[key].latitude = ListingEditor.Core.trimDecimal(res[key].latitude, 6); res[key].longitude = ListingEditor.Core.trimDecimal(res[key].longitude, 6); msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedLatitude + ': ' + res[key].latitude; msg += '\n' + ListingEditor.Config.TRANSLATIONS.sharedLongitude + ': ' + res[key].longitude; } else if (key === 'email') { res[key] = res[key].replace('mailto:', ''); msg += '\n' + ListingEditor.Config.WIKIDATA_CLAIMS[key].label + ': ' + res[key]; } else msg += '\n' + ListingEditor.Config.WIKIDATA_CLAIMS[key].label + ': ' + res[key]; } } if (msg) { if (confirm(ListingEditor.Config.TRANSLATIONS.wikidataShared + '\n' + msg)) { if (label !== null) updateFieldIfNotNull('#input-name', label); for (key in res) { if (res[key]) { if (key === 'coord') { updateFieldIfNotNull('#input-lat', res[key].latitude); updateFieldIfNotNull('#input-long', res[key].longitude); } else if (key === 'image') { updateFieldIfNotNull('#input-image', res[key]); commonsLink(res[key]); } else { updateFieldIfNotNull('#input-' + key, res[key]); } } $('#input-auto').val(''); // vCard specific } } } 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(); $('#input-auto').val('y'); // vCard specific $('#div_wikidata_update', form).show(); $('#div_auto', form).show(); }; CREATE_FORM_CALLBACKS.push(wikidataLookup); // -------------------------------------------------------------------- // LISTING EDITOR FORM SUBMISSION CALLBACKS // -------------------------------------------------------------------- /** * Return the current date in the format "2015-01-15". */ var currentLastEditDate = function() { var d = new Date(); var year = d.getFullYear(); // Date.getMonth() returns 0-11 var month = d.getMonth() + 1; if (month < 10) month = '0' + month; var day = d.getDate(); if (day < 10) day = '0' + day; return year + '-' + month + '-' + day; }; /** * Only update last edit date if this is a new listing or if the * "information up-to-date" box checked. */ var updateLastEditDate = function(listing, mode) { var LISTING_LAST_EDIT_PARAMETER = 'lastedit'; var EDITOR_LAST_EDIT_SELECTOR = '#input-last-edit'; if (mode == ListingEditor.Core.MODE_ADD || $(EDITOR_LAST_EDIT_SELECTOR).is(':checked')) { listing[LISTING_LAST_EDIT_PARAMETER] = currentLastEditDate(); } }; SUBMIT_FORM_CALLBACKS.push(updateLastEditDate); // -------------------------------------------------------------------- // LISTING EDITOR FORM VALIDATION CALLBACKS // -------------------------------------------------------------------- /** * Verify all listings have at least a name, address or alt value. */ var validateListingHasData = function(validationFailureMessages) { if ($('#input-name').val() === '' && $('#input-address').val() === '' && $('#input-alt').val() === '' && $('#input-wikidata-value').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 = /^([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+,*[\W]*)+$/; _validateFieldAgainstRegex(validationFailureMessages, VALID_EMAIL_REGEX, '#input-email', ListingEditor.Config.TRANSLATIONS.validationEmail); }; VALIDATE_FORM_CALLBACKS.push(validateEmail); /** * 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 _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[ListingEditor.Config.LANG]) { /* mod */ return null; } return entityObj.labels[ListingEditor.Config.LANG].value; }; // expose public members return { API_WIKIDATA: API_WIKIDATA, API_WIKIPEDIA: API_WIKIPEDIA, API_COMMONS: API_COMMONS, initializeSisterSiteAutocomplete: _initializeSisterSiteAutocomplete, ajaxSisterSiteSearch: _ajaxSisterSiteSearch, wikidataClaim: _wikidataClaim, wikidataLabel: _wikidataLabel }; }(); // ---------------------------- ListingEditor.Core ---------------------------- /* *********************************************************************** * 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') || !mw.config.get('wgRelevantPageIsProbablyEditable') || $('#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 listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER] = listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER].toLowerCase().replace(" ", "_"); 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(); } } 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)); }) .attr('title', ListingEditor.Config.TRANSLATIONS.addTitle); }; /** * Utility function for appending the "add listing" link text to a heading. */ var insertAddListingPlaceholder = function(parentHeading) { var editSection = $(parentHeading).next('.mw-editsection'); if (editSection.length > 0) editSection.append('<span class="mw-editsection-bracket">[</span><a href="javascript:" class="listingeditor-add">'+ListingEditor.Config.TRANSLATIONS.add+'</a><span class="mw-editsection-bracket">]</span>'); else { // Mobile view: Minerva support editSection = $(parentHeading).next('span'); editSection.after('<span><a href="javascript:" class="mw-ui-icon mw-ui-icon-element listingeditor-add">'+ListingEditor.Config.TRANSLATIONS.add+'</a></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)); }) .attr('title', ListingEditor.Config.TRANSLATIONS.editTitle); // if there is already metadata present add a separator $(ListingEditor.Config.EDIT_LINK_CONTAINER_SELECTOR).each(function() { if (!isElementEmpty(this)) { $(this).append('<span class="noprint"> | </span>'); } else { $(this).append('<span class="noprint">(</span>'); $(this).parent().append('<span class="listing-metadata noprint">)</span>'); } }); // 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); return true; }; /** * 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; } // Vector etc. skins var link = heading.find('.mw-editsection a').attr('href'); var section = (link !== undefined) ? link.split('=').pop() : 0; if (section > 0) return section; // Mobile view: Minerva support link = heading.find('.in-block 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('|') + ')\\b)(\\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); }; var checkYesNo = function(value) { var v = value.toLowerCase(); if (ListingEditor.Config.YES_ARRAY[v] === '') return 'y'; else if (ListingEditor.Config.NO_ARRAY[v] === '') return 'n'; else return ''; }; /** * 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" // but not for vCard template if ( !(ListingEditor.Config.DEFAULT_TEMPLATE_NO_TYPE && (listingTemplateWikiSyntax.toLowerCase().indexOf(ListingEditor.Config.DEFAULT_LISTING_TEMPLATE.toLowerCase()) >= 0) ) ) listingTemplateWikiSyntax = listingTemplateWikiSyntax.replace(typeRegex,'{{vCard| ' + ListingEditor.Config.LISTING_TYPE_PARAMETER + '=$2$3'); /* mod */ // remove the trailing braces listingTemplateWikiSyntax = listingTemplateWikiSyntax.slice(0,-2); var listingTemplateAsMap = {}; var lastKey; var listParams = listingTemplateToParamsArray(listingTemplateWikiSyntax); var key; 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 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 (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" if (!listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER]) listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER] = ''; for (key in ListingEditor.Config.LISTING_TEMPLATES) { if (listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER].toLowerCase() === key.toLowerCase()) { listingTemplateAsMap[ListingEditor.Config.LISTING_TYPE_PARAMETER] = key; break; } } for (key in listingTemplateAsMap) { var c = checkYesNo( listingTemplateAsMap[key] ); if (c !== '') listingTemplateAsMap[key] = c; } 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); if (mode === MODE_EDIT) inlineListing = isInline(clicked); else inlineListing = true; $.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); 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(); // if a listing editor dialog is already open, get rid of it wvDialog.destroy(); // destroy if existent // $(ListingEditor.Config.EDITOR_FORM_SELECTOR).remove(); // $('.wv-dialog-background').remove(); var windowWidth = $(window).width(); var dialogWidth = (windowWidth > ListingEditor.Config.MAX_DIALOG_WIDTH) ? ListingEditor.Config.MAX_DIALOG_WIDTH : 'auto'; var leElement; var leDialog = wvDialog.create({ width: dialogWidth, title: (mode == MODE_ADD) ? ListingEditor.Config.TRANSLATIONS.addTitle : ListingEditor.Config.TRANSLATIONS.editTitle, dialogClass: 'listing-editor-dialog', id: 'listing-editor-dialog', cancelTitle: ListingEditor.Config.TRANSLATIONS.cancelTitle }); var leBody = wvDialog.getElement( 'body' ); leDialog.append( leBody ); var form = $(createForm(mode, listingParameters, listingTemplateAsMap)); leBody.append( form ); var leButtons = wvDialog.getElement( 'button-pane' ); leElement = wvDialog.getElement( 'button', 'listing-help' ) .text('?') .attr('title', ListingEditor.Config.TRANSLATIONS.helpTitle) .click(function() { window.open(ListingEditor.Config.TRANSLATIONS.helpPage); }); leButtons.append( leElement ); leElement = wvDialog.getElement( 'button' ) .text(ListingEditor.Config.TRANSLATIONS.submit) .attr('title', ListingEditor.Config.TRANSLATIONS.submitTitle) .click(function() { if ($(ListingEditor.Config.EDITOR_CLOSED_SELECTOR).is(':checked')) { // no validation formToText(mode, listingTemplateWikiSyntax, listingTemplateAsMap, sectionNumber); wvDialog.destroy(); } else if (validateForm()) { formToText(mode, listingTemplateWikiSyntax, listingTemplateAsMap, sectionNumber); wvDialog.destroy(); } }); leButtons.append( leElement ); leElement = wvDialog.getElement( 'button', 'listing-preview-button' ) .text(ListingEditor.Config.TRANSLATIONS.preview) .attr('title', ListingEditor.Config.TRANSLATIONS.previewTitle) .click(function() { formToPreview(listingTemplateAsMap); }); leButtons.append( leElement ); leElement = wvDialog.getElement( 'button', 'listing-previewOff' ) .text(ListingEditor.Config.TRANSLATIONS.previewOff) .attr('title', ListingEditor.Config.TRANSLATIONS.previewOffTitle) .attr('style', 'display: none') .click(function() { hidePreview(); }); leButtons.append( leElement ); leElement = wvDialog.getElement( 'button', 'listing-refresh' ) .text(ListingEditor.Config.TRANSLATIONS.refresh) .attr('title', ListingEditor.Config.TRANSLATIONS.refreshTitle) .attr('style', 'display: none') .click(function() { refreshPreview(listingTemplateAsMap); }); leButtons.append( leElement ); leElement = wvDialog.getElement( 'button' ) .text(ListingEditor.Config.TRANSLATIONS.cancel) .attr('title', ListingEditor.Config.TRANSLATIONS.cancelTitle) .click(function() { wvDialog.destroy() }); leButtons.append( leElement ); leElement = wvDialog.getElement( 'footer' ).append(leButtons); leElement.append( wvDialog.getElement( 'listing-license' ) .html( ListingEditor.Config.TRANSLATIONS.licenseText) ); leDialog.append( leElement ); wvDialog.centerDialog( leDialog ); var testdata = [ 'abc', 'def', 'ghi', 'jkl', 'mno', 'ActionScript', 'AppleScript', 'Asp', 'BASIC', 'C', 'C++', 'Clojure', 'COBOL', 'ColdFusion', 'Erlang', 'Fortran', 'Groovy', 'Haskell', 'Java', 'JavaScript', 'Lisp', 'Perl', 'PHP', 'Python', 'Ruby', { value: 'Scala', label: 'ScalaTest' }, 'Scheme']; wvAutocomplete.init({ selector: $('#input-wikidata-label'), source: testdata }); wvAutocomplete.init({ selector: $('#input-image'), source: ['abc', 'aab', 'aac', 'def', 'ghi', 'jkl', 'mno'] // function ( request, response ) // { response = ['abc', 'aab', 'aac', 'def', 'ghi', 'jkl', 'mno'] } }); }; /** * 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() { // vCard specific return ListingEditor.Config.LISTING_TEMPLATE_PARAMETERS; }; /** * Determine if the specified listing type is a custom type - for example "go" * instead of "see", "do", "listing", etc. */ var isCustomListingType = function(listingType) { // vCard specific for (var i = 0; i < ListingEditor.Config.LISTING_TYPE_OPTIONS.length; i++) { if (ListingEditor.Config.LISTING_TYPE_OPTIONS[i].type === listingType) return false; } return true; }; /** * 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 listingParameters = getListingInfo(); var listingTypeInput = listingParameters[ListingEditor.Config.LISTING_TYPE_PARAMETER].id; var listingType = $("#" + listingTypeInput).val(); 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(); } if ( mw.config.get('skin') === 'minerva' ) { summary += ' – ' + ListingEditor.Config.TRANSLATIONS.mobileEdit; } var minor = $(ListingEditor.Config.EDITOR_MINOR_EDIT_SELECTOR).is(':checked') ? true : false; saveForm(summary, minor, sectionNumber, '', ''); return; }; var showPreview = function(listingTemplateAsMap) { var listing = listingTemplateAsMap; var listingParameters = getListingInfo(); var listingTypeInput = listingParameters[ListingEditor.Config.LISTING_TYPE_PARAMETER].id; var listingType = $("#" + listingTypeInput).val(); for (var parameter in listingParameters) { listing[parameter] = $("#" + listingParameters[parameter].id).val(); } var text = listingToStr(listing); $.ajax ({ url: mw.config.get('wgScriptPath') + '/api.php?' + $.param({ action: 'parse', prop: 'text', contentmodel: 'wikitext', format: 'json', 'text': text, }), error: function (jqXHR, txt) { $('#listing-preview').hide(); }, success: function (data) { $('#listing-preview-text').html(data.parse.text['*']); }, }); }; var formToPreview = function(listingTemplateAsMap) { if ( !$('#listing-preview').is(':visible') ) { $('#listing-preview').show(); $('#listing-refresh').show(); $('#listing-preview-button').hide(); $('#listing-previewOff').show(); showPreview(listingTemplateAsMap); } else { $('#listing-preview').hide(); $('#listing-refresh').hide(); $('#listing-previewOff').hide(); $('#listing-preview-button').show(); } }; var refreshPreview = function(listingTemplateAsMap) { if ( $('#listing-preview').is(':visible') ) { showPreview(listingTemplateAsMap); } }; var hidePreview = function() { $('#listing-preview').hide(); $('#listing-previewOff').hide(); $('#listing-refresh').hide(); $('#listing-preview-button').show(); }; /** * 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 // Problem: Minerva /* 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.escapeId(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) { // Problem: Minerva /* $(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) { // Problem: Minerva // $(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 // Problem: Minerva /* 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(); var saveStr = '{{'; saveStr += ListingEditor.Config.DEFAULT_LISTING_TEMPLATE; saveStr += ' | ' + ListingEditor.Config.LISTING_TYPE_PARAMETER + ' = ' + listingType.replace("_", " ") + ' '; if (!inlineListing && listingParameters[ListingEditor.Config.LISTING_TYPE_PARAMETER].newline) { saveStr += '\n'; } var skipIt; 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; } skipIt = listingParameters[parameter].skipIfEmpty; if (typeof(skipIt) === "undefined") skipIt = ListingEditor.Config.SKIP_DEFAULT; if (listing[parameter] !== '' || (!skipIt && !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 }; }(); // -------------------------- jquery.ui dialog replacement --------------------------- /* *********************************************************************** * jquery.ui replacement. * ***********************************************************************/ var wvDialog = function() { var create = function( options ) { var classes = 'wv-dialog'; if (options.dialogClass) classes += ' ' + options.dialogClass; classes = ' class="' + classes + '"'; var id = ''; if (options.id) id = ' id="' + options.id + '"'; var width = 'auto'; if (options.width) width = options.width; var height = 'auto'; if (options.height) height = options.height; var draggable = true; if (typeof(options.draggable) === 'boolean') draggable = options.draggable; var resizable = true; if (typeof(options.resizable) === 'boolean') resizable = options.resizable; var closeOnEscape = true; if (typeof(options.closeOnEscape) === 'boolean') closeOnEscape = options.closeOnEscape; $('body').append( $('<div class="wv-dialog-background"></div>') ); var aDialog = $('<div' + classes + id + '></div>') .css('width', width) .css('height', height); $('body').append( aDialog ); if (options.title) appendHeader( aDialog, options.title, options.cancelTitle, draggable ); if (closeOnEscape) $(document).keyup(function(e) { if ( e.keyCode === 27 ) destroy() } ); return aDialog; }; var dialog; var mousePos = {}; var dialogPos = {}; var dialogMove = function(e) { dialog.css('left', dialogPos.X - mousePos.X + e.clientX) .css('top', dialogPos.Y - mousePos.Y + e.clientY); }; var appendHeader = function( aDialog, aTitle, cancelTitle, draggable ) { if ( !aDialog || !aTitle || (aTitle === '') ) return; var cTitle = 'Abort dialog'; if ( cancelTitle && (cancelTitle !== '') ) cTitle = cancelTitle; var anElement = $( '<div class="wv-dialog-close"></div>' ) .attr('title', cTitle) .click(function() { wvDialog.destroy() }); aDialog.append( $( anElement ) ); anElement = $( '<div class="wv-dialog-header"></div>' ) .text( aTitle ); if (draggable) anElement .css('cursor', 'move') .mouseup(function(e) { $(this).off( 'mousemove', dialogMove ).css('cursor', 'move'); }) .mouseout(function(e) { $(this).off( 'mousemove', dialogMove ).css('cursor', 'move'); }) .mousedown(function(e) { dialog = $(this).parent(); $(this).on( 'mousemove', dialogMove ).css('cursor', 'grabbing'); mousePos.X = e.clientX; mousePos.Y = e.clientY; dialogPos.X = parseInt( dialog.css('left') ); dialogPos.Y = parseInt( dialog.css('top') ); }); aDialog.append( anElement ); }; var centerDialog = function( aDialog ) { if ( !aDialog) return; aDialog.css('left', (document.body.scrollWidth - aDialog.width()) / 2 + $(document).scrollLeft() ) .css('top', (window.innerHeight - aDialog.height()) / 2 + $(document).scrollTop() ); }; var getElement = function( aType, anId ) { if (!aType) return; var tag = 'div'; var classes = ''; var id = ''; if (anId) id = ' id="' + anId + '"'; switch( aType ) { case 'body': classes = 'wv-dialog-body'; break; case 'footer': classes = 'wv-dialog-footer'; break; case 'button-pane': classes = 'wv-dialog-button-pane'; break; case 'button': classes = 'wv-dialog-button'; tag = 'button'; break; default: classes = aType; } classes = ' class="' + classes + '"'; return $( '<' + tag + classes + id +'></' + tag + '>' ); }; var destroy = function() { if ( $('.wv-dialog-background').length > 0 ) $('.wv-dialog-background').remove(); if ( $('.wv-dialog').length > 0 ) $('.wv-dialog').remove(); }; return { appendHeader: appendHeader, centerDialog: centerDialog, create: create, destroy: destroy, getElement: getElement }; }(); // -------------------- jquery.ui autocomplete replacement -------------------- /* *********************************************************************** * jquery.ui replacement. * ***********************************************************************/ var wvAutocomplete = function() { var opts = {}; var init = function( options ) { var selector = options.selector; selector.keyup(function(e) { keyevent( selector, e ); }) .focusout(function(e) { closeList( selector ) }); var id = selector.attr('id'); if (!opts[id]) { opts[id] = { data: null, classes: '', source: null, select: null, _renderItem: function( list, item ) { return $( '<li></li>') .append( item.label ) .appendTo( list ); }, matches: [], focus: -1, lastValue: '' }; } if ( $.isArray( options.source ) ) { opts[id].data = options.source; } else if ( typeof options.source === 'string' ) { // not supported } else { opts[id].source = options.source; } if (options.select) opts[id].select = options.select; if (options.classes) opts[id].classes = ' ' + options.classes; }; var keyevent = function( thisInput, e ) { var input = thisInput.val(); if (input === '') { closeList( thisInput ); return; } var list = $('#wvac-' + thisInput.attr('id')); var listExists = list.length > 0; var isEsc = false; var id = thisInput.attr('id'); switch(e.keyCode) { case 13: // enter closeList( thisInput ); break; case 27: // escape if (listExists) { closeList( thisInput ); e.preventDefault(); e.stopPropagation(); } isEsc = true; break; case 38: // up updateFocus( thisInput, -1 ); break; case 40: // down updateFocus( thisInput, 1 ); break; default: if ( opts[id].lastValue !== input) { closeList( thisInput ); listExists = false; } } if ( !listExists && !isEsc) search( thisInput ); }; var setValue = function( thisInput, v ) { thisInput.val( v ); var id = thisInput.attr('id'); var ui = { item: {} } ; if ( (opts[id].select !== null) && (typeof(opts[id].select) === "function") ) { var event = null; ui.item.value = v; opts[id].select( event, ui ); } }; var setFocus = function( thisInput, f ) { var id = thisInput.attr('id'); if ( (f < -1) || (f > opts[id].matches.length) ) return; opts[id].focus = f; var list = $('#wvac-' + id).children(); var i = 0; var newValue = opts[id].lastValue; list.each(function() { if (i == f) { $(this).addClass('wv-ac-focused'); newValue = opts[id].matches[i].value; } else $(this).removeClass('wv-ac-focused'); i++; }); setValue( thisInput, newValue ); }; var updateFocus = function( thisInput, diff ) { var id = thisInput.attr('id'); setFocus( thisInput, opts[id].focus + diff ); }; var setMouseFocus = function( thisInput, e ) { var list = $('#wvac-' + thisInput.attr('id')).children(); var i = 0; list.each(function() { if ( $(this).is(e.target) ) setFocus( thisInput, i ); i++; }); }; var defaultFilter = function( testStr, array ) { var pattern = new RegExp(testStr, 'i'); return $.grep( array, function( v ) { return pattern.test( v.label || v.value || v ); }); }; var search = function( thisInput ) { var id = thisInput.attr('id'); opts[id].lastValue = thisInput.val(); opts[id].matches = []; if ( opts[id].data !== null ) { for (var i = 0; i < opts[id].data.length; i++) { if ( typeof(opts[id].data[i]) !== 'object' ) opts[id].data[i] = { value: opts[id].data[i], label: opts[id].data[i] }; } opts[id].matches = defaultFilter( opts[id].lastValue, opts[id].data ); } openList( thisInput, opts[id].matches ); }; var openList = function( thisInput, matches ) { var id = thisInput.attr('id'); var listId = 'wvac-' + id; var list = $('#' + listId); if ( list.length > 0 ) list.remove(); if ( matches.length > 0 ) { list = $( '<ul class="wv-ac-list' + opts[id].classes + '" id="' + listId + '"></ul>' ) .css('left', thisInput.offset().left) .css('top', thisInput.offset().top + thisInput.outerHeight()); $('body').append( list ); opts[id].focus = -1; var item; for (var i = 0; i < matches.length; i++) { item = opts[id]._renderItem( list, matches[i] ) .attr( 'data-value', matches[i].value ) .mousedown(function(e) { setMouseFocus( thisInput, e ) } ) .mouseover(function(e) { setFocus( thisInput, -1 ) } ); } } }; var closeList = function( thisInput ){ var id = thisInput.attr('id'); var list = $('#wvac-' + id); if ( list.length !== 0 ) list.remove(); opts[id].lastValue = ''; }; return { init: init, }; }(); // ------------------------------ Initialization ------------------------------ /* *********************************************************************** * Editor initialization * ***********************************************************************/ var suppressLE = false; if (typeof window.suppressListingEditorBeta !== 'undefined') { suppressLE = window.suppressListingEditorBeta; } if (!suppressLE) { mw.loader.using(['mediawiki.util', 'jquery.ui' ]); // mw.loader.using(['mediawiki.util', 'jquery.ui' ]).then( function () { $(document).ready(function() { ListingEditor.Core.init(); }); // }); } } ( mediaWiki, jQuery ) ); //</nowiki>