MediaWiki:ListingInfo.js

Aus Wikivoyage
Zur Navigation springen Zur Suche springen

Hinweis: Leere nach dem Speichern 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
  • Internet Explorer: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Gehe zu Menü → Einstellungen (Opera → Einstellungen auf dem Mac) und dann auf Datenschutz & Sicherheit → Browserdaten löschen → Gespeicherte Bilder und Dateien.
//<nowiki>
/*******************************************************************************
 * ListingInfo v1.2
 * Presents a dialog showing characteristics of a single listing
 * Original author: Roland Unger
 * Support of desktop and mobile views
 * Documentation: https://de.wikivoyage.org/wiki/Wikivoyage:ListingInfo.js
 ******************************************************************************/

( function ( $, mw ) {
	'use strict';

	var listingPopup = function() {

		/******************* Internationalization *****************************/
		// see also listingInit definitions

		var listingSelector = '.vCard';

		var headers = {
			booking: 'Buchung und Bewertung',
			contact: 'Kontakt',
			credit: 'Kreditkarten',
			features: 'Ausstattung',
			figure: 'Bild',
			hours: 'Zeiten',
			map: 'Lagekarte'
		};
		
		var actionButtons = {
			bookingImg: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Edit_font_awesome.svg/20px-Edit_font_awesome.svg.png',
			bookingTooltip: 'Buchungslinks anzeigen',

			cancelImg: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/Black_close_x.svg/20px-Black_close_x.svg.png',
			cancelTooltip: 'Fenster schließen',

			contactImg: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Phone_font_awesome.svg/20px-Phone_font_awesome.svg.png',
			contactTooltip: 'Kontakt anzeigen',

			featuresImg: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Black_check.svg/20px-Black_check.svg.png',
			featuresTooltip: 'Ausstattung anzeigen',

			figureImg: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/High-contrast-image-x-generic.svg/20px-High-contrast-image-x-generic.svg.png',
			figureTooltip: 'Abbildung anzeigen',

			mapImg: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Antu_tag-places-black.svg/20px-Antu_tag-places-black.svg.png',
			mapTooltip: 'Karte anzeigen',

			taxiImg: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Q82650_noun_7191_ccJensT%C3%A4rning_taxi.svg/20px-Q82650_noun_7191_ccJensT%C3%A4rning_taxi.svg.png',
			taxiTooltip: 'Bringen Sie mich zu',
		};

		var	webImg = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Erioll_world_2.svg/24px-Erioll_world_2.svg.png';
		var	webTooltip = 'Website der Einrichtung';

		var contact = {
			phone: 'Tel.',
			mobile: 'Mobil',
			tollfree: 'Tel. gebührenfrei',
			fax: 'Fax',
			skype: 'Skype',
			email: 'Email',
			web: 'Internet'
		};

		// dialog move
		var mousePos = {};
		var dialogPos = {};

		// map support
		var aMap = null;
		var aType = null;
		var lat = 0;
		var lon = 0;
		var color = '';
		var title = '';
		var thumb = '';

		var classPrefix = 'wv-listinginfo-';

		var sites = [
			{ data: 'data-agoda-com', site: 'Agoda.com', title: 'Hotel auf Agoda.com', formatter: 'https://www.agoda.com/de-de/$1.html', grClass: 'group1' },
			{ data: 'data-booking-com', site: 'Booking.com', title: 'Hotel auf Booking.com', formatter: 'https://www.booking.com/hotel/$1.de.html', grClass: 'group1' },
			{ data: 'data-expedia-com', site: 'Expedia.com', title: 'Hotel auf Expedia.com', formatter: 'https://www.expedia.com/$1.Hotel-Information', grClass: 'group1' },
			{ data: 'data-historic-hotels-america', site: 'HistoricHotels.org', title: 'Hotel auf HistoricHotels.org', formatter: 'https://www.historichotels.org/hotels-resorts/$1', grClass: 'group1' },
			{ data: 'data-historic-hotels-europe', site: 'HistoricHotelsOfEurope.com', title: 'Hotel auf HistoricHotelsOfEurope.com', formatter: 'https://www.historichotelsofeurope.com/property-details.html/$1', grClass: 'group1' },
			{ data: 'data-historic-hotels-worldwide', site: 'HistoricHotelsWorldwide.com', title: 'Hotel auf HistoricHotelsWorldwide.com', formatter: 'http://www.historichotelsworldwide.com/hotels-resorts/$1', grClass: 'group1' },
			{ data: 'data-hotels-com', site: 'Hotels.com', title: 'Hotel auf Hotels.com', formatter: 'https://de.hotels.com/$1/', grClass: 'group1' },
			{ data: 'data-leading-hotels', site: 'LHW.com', title: 'Hotel auf Leading Hotels of the World', formatter: 'https://www.lhw.com/hotel/$1', grClass: 'group1' },
			{ data: 'data-preferred-hotels', site: 'PreferredHotels.com', title: 'Hotel auf PreferredHotels.com', formatter: 'https://preferredhotels.com/destinations/$1', grClass: 'group1' },
			{ data: 'data-recreation-gov', site: 'Recreation.gov facility', title: 'Einrichtung auf Recreation.gov', formatter: 'https://www.recreation.gov/recreationalAreaDetails.do?facilityId=$1', grClass: 'group1' },
			{ data: 'data-relais-chateaux', site: 'RelaisChateaux.com', title: 'Einrichtung auf RelaisChateaux.com', formatter: 'https://www.relaischateaux.com/us/wd/$1', grClass: 'group1' },
			{ data: 'data-tripadvisor-com', site: 'Tripadvisor.com', title: 'Einrichtung auf Tripadvisor.com', formatter: 'https://www.tripadvisor.com/$1', grClass: 'group1' },

			{ data: 'data-alpenverein-de', site: 'Alpenverein.de', title: 'Schutzhütte auf Alpenverein.de', formatter: 'https://www.alpenverein.de/DAV-Services/Huettensuche/wd/$1', grClass: 'group1' },
			{ data: 'data-alpenverein-at', site: 'Alpenverein.at', title: 'Schutzhütte auf Alpenverein.at', formatter: 'https://www.alpenverein.at/huetten/index.php?huette_nr=$1', grClass: 'group1' },
			{ data: 'data-pzs-si', site: 'PZS.si', title: 'Schutzhütte auf im Verzeichnis des Alpenvereins Sloweniens', formatter: 'https://en.pzs.si/koce.php?pid=$1', grClass: 'group1' },
			{ data: 'data-sac-cas-ch', site: 'SAC-CAS.ch', title: 'Schutzhütte und Gipfel auf im Verzeichnis des Schweizer Alpen-Clubs', formatter: 'https://beta.sac-cas.ch/de/huetten-und-touren/tourenportal/$1/', grClass: 'group1' },

			{ data: 'data-foursquare-id', site: 'Foursquare.com', title: 'Einrichtung auf Foursquare.com', formatter: 'https://www.foursquare.com/v/$1', grClass: 'group2' },
			{ data: 'data-google-maps-cid', site: 'Maps.google.com', title: 'Einrichtung auf Google Maps', formatter: 'https://maps.google.com/?cid=$1', grClass: 'group2' },
		];

		var translations = {
			de: { takeRequest: 'Bitte bringen Sie mich zu…',
				name: 'Name', comment: 'Kommentar', address: 'Anschrift', directions: 'Wegbeschreibung' },
			en: { takeRequest: 'Please take me to…',
				name: 'Name', comment: 'Comment', address: 'Address', directions: 'Directions' },
			af: { takeRequest: 'Neem my asseblief na …',
				name: 'Naam', comment: 'Opmerking', address: 'Adres', directions: 'Hoe om daar te kom' }, // native speaker
			ar: { takeRequest: 'من فضلك ، أريد الذهاب إلى…',
				name: 'الاسم', comment: 'التعليق', address: 'العنوان', directions: 'الموقع والاتجاه' }, // native speaker
			az: { takeRequest: 'Xahiş edirəm məni … aparın',
				name: 'Ad', comment: 'Şərh', address: 'Ünvan', directions: 'Yer və necə getməli' }, // native speaker
			be: { takeRequest: 'Калі ласка, вазьміце мяне да …' }, // Weißrussisch
			bg: { takeRequest: 'Моля, вземете ме до …' ,
				name: 'Име', comment: 'Коментар', address: 'Адрес', directions: 'Местоположение и пристигане' }, // native speaker
			bn: { takeRequest: 'আমাকে নিয়ে যান …',
				name: 'নাম', comment: 'মন্তব্য', address: 'ঠিকানা', directions: 'দিকনির্দেশ' },
			bs: { takeRequest: 'Molim te, vodi me do …',
				name: 'Ime', comment: 'Komentar', address: 'Adresa', directions: 'Mjesto i odredište' }, // native speaker
			ca: { takeRequest: 'Porta’m a …' },
			cs: { takeRequest: 'Vezměte mě prosím na toto místo:',
				name: 'Jméno', comment: 'Komentář', address: 'Adresa', directions: 'Cíl a cesta' }, // native speaker
			da: { takeRequest: 'Jeg vil gerne til …',
				name: 'Navn', comment: 'Kommentar', address: 'Adresse', directions: 'Kørselsvejledning' }, // native speaker
			el: { takeRequest: 'Παρακαλώ να με πάτε στο(ν)/στη(ν) …',
				name: 'Όνομα', comment: 'Σχόλιο', address: 'Διεύθυνση', directions: 'Τοποθεσία και άφιξη' }, // native speaker
			es: { takeRequest: 'Por favor, lléveme a…',
				name: 'Nombre', comment: 'Comentario', address: 'Dirección', directions: 'Indicaciones' }, // native speaker
			et: { takeRequest: 'Palun viige mind …',
				name: 'Nimi', comment: 'Kommentaar', address: 'Aadress', directions: 'Asukoht ja saabumine' }, // native speaker
			fa: { takeRequest: 'لطفا من را ببر به…',
				name: 'نام', comment: 'یادداشت', address: 'نشانی', directions: 'مسیرها' }, // native speaker
			fi: { takeRequest: 'Vie minut …',
				name: 'Nimi', comment: 'Kommentti', address: 'Osoite', directions: 'Ohjeet' }, // native speaker
			fr: { takeRequest: 'Pouvez-vous m’emmenez à/au…',
				name: 'Nom', comment: 'Commentaire', address: 'Adresse', directions: 'Direction' }, // native speaker
			ga: { takeRequest: 'Tabhair dom go …' }, // Irisch
			gd: { takeRequest: 'Thoir dhomh gu …' }, // Schottisch-gälisch
			gu: { takeRequest: 'કીરપા કરકે મૈના લે જો …'    , // Gujarati provided by an Indian guy
				name: 'નાબ', comment: 'પાસ', address: 'પતા', directions: 'ઢીશા' },
			he: { takeRequest: 'בבקשה תיקח אותי ל…',
				name: 'שם', comment: 'תגובה', address: 'כתובת', directions: 'הוראות' },
			hi: { takeRequest: 'कृपया मुझे वहाँ ले जाएं…' ,
				name: 'नाम', comment: 'पास', address: 'पता', directions: 'दीशा' }, //Hindi provided by an Indian guy
			hr: { takeRequest: 'Molim Vas, odvedi me …' },
			hu: { takeRequest: 'Kérem, vigyen a/az …-hoz/hez/höz',
				name: 'Név', comment: 'Megjegyzés', address: 'Cím', directions: 'Odajutás' }, // native speaker
			hy: { takeRequest: 'Խնդրում եմ ինձ տանել …',
				name: 'Անուն', comment: 'Բացատրություն', address: 'Հասցե', directions: 'Վայր եւ ցուցումներ' }, // Armenisch, native speaker
			id: { takeRequest: 'Tolong hantarkan saya ke …',
				name: 'Nama', comment: 'Komen', address: 'Alamat', directions: 'Arah' }, // Indonesian: same like malay (statement of a Sabahan native Malay speaker)
			is: { takeRequest: 'Vinsamlegast taktu mig til …' },
			it: { takeRequest: 'Per favore, mi porti a…',
				name: 'Nome', comment: 'Commento', address: 'Indirizzo', directions: 'Posizione e arrivo' }, // Italian native speaker
			ja: { takeRequest: '… までお願いします。',
				name: '場所', comment: 'コメント', address: '住所', directions: 'アクセス' }, // Japanese native speaker
			ka: { takeRequest: 'გთხოვთ მიმიყვანოთ…',
				name: 'დასახელება', comment: 'კომენტარი', address: 'მისამართი', directions: 'დანიშნულების ადგილი' }, // Georgisch, native speaker
			kk: { takeRequest: 'Маған …' }, // Kasachisch
			km: { takeRequest: 'សូមជូនខ្ញុំទៅ …',
				name: 'ឈ្មោះ', comment: 'នែនាំ', address: 'អាសយដ្ឋាន', directions: 'ទិសដៅ' }, // Khmer: by native speaker from Phnom Penh
			ko: { takeRequest: '… 로 가주세요',
				name: '이름', comment: '호텔 안내', address: '주소', directions: '찾아가는 길' }, // Korean: verified by a native speaker from Seoul
			ky: { takeRequest: 'Суранам, мени алып…' }, // Kirgisisch
			lb: { takeRequest: 'Huelt mech op …' },
			lt: { takeRequest: 'Prašau, paimk mane …' },
			lv: { takeRequest: 'Lūdzu, aizvediet mani uz …',
				name: 'Nosaukums', comment: 'Komentars', address: 'Adrese', directions: 'Virzieni' }, // Lettisch -- native speaker
			mk: { takeRequest: 'Ве молам, земи ме …' }, // Mazedonisch
			mn: { takeRequest: 'Намайг аваарай …' },
			ms: { takeRequest: 'Tolong hantarkan saya ke …',
				name: 'Nama', comment: 'Komen', address: 'Alamat', directions: 'Arah' }, // Malay: verified by a Sabahan native speaker
			mt: { takeRequest: 'Jekk jogħġbok ħudni …' },
			my: { takeRequest: 'ငါ့ကိုအယူကို ကျေးဇူးပြု. ...' }, // Birmanisch
			nan: { takeRequest: '請帶我去 …',
				name: '姓名', comment: '評語', address: '住址', directions: '抵達方式' }, // Taiwanesisch: verified by a Taiwanese native speaker
			nb: { takeRequest: 'Kan du kjøre meg til …',
				name: 'Navn', comment: 'Kommentar', address: 'Adresse', directions: 'Sted og tid' }, // Bokmål, native speaker
			ne: { takeRequest: 'कृपया मुझे वहाँ ले जाएं…' ,
				name: 'नाम', comment: 'पास', address: 'पता', directions: 'दीशा' }, // Nepalese provided by an Indian guy
			nl: { takeRequest: 'Breng me alstublieft naar…',
				name: 'Naam', comment: 'Commentaar', address: 'Adres', directions: 'Route' }, // native speaker
			no: { takeRequest: 'Kan du kjøre meg til …',
				name: 'Navn', comment: 'Kommentar', address: 'Adresse', directions: 'Sted og tid' }, // Bokmål, native speaker
			pl: { takeRequest: 'Proszę mnie zabrać do…',
				name: 'Nazwa', comment: 'Komentarz', address: 'Adres', directions: 'Wskazówki dojuzdu' }, // native speaker
			pt: { takeRequest: 'Por favor, leve-me para …',
				name: 'Nome', comment: 'Comentário', address: 'Endereço', directions: 'Direções' }, // native speaker
			pu: { takeRequest: 'ਕਿਰਪਾ ਕਰਕੇ ਮੈਨੂ ਲੈ ਚਲੌं…' ,
				name: 'ਨਾਮ', comment: 'ਢੇ ਕੋਲ', address: 'ਪਤਾ', directions: 'ਵਲ' }, //Punjabie provided by an Indian guy
			ro: { takeRequest: 'Te rog să mă dai la …',
				name: 'Nume', comment: 'Comentariu', address: 'Adresă', directions: 'Indicații' },
			ru: { takeRequest: 'Пожалуйста, отвезите меня в…',
				name: 'Название', comment: 'Комментарий', address: 'Адрес', directions: 'Пояснения' }, // Russian native speaker
			sk: { takeRequest: 'Prosím, môžete ma vziať na miesto …',
				name: 'Menom', comment: 'Komentár', address: 'Adresa', directions: 'v oblasti' }, // native spaker
			sl: { takeRequest: 'Prosim, vzemite me …' },
			sq: { takeRequest: 'Ju lutem më dërgoni në …',
				name: 'Emri', comment: 'Komenti', address: 'Adresa', directions: 'Vendndodhja dhe Mbërritja' }, // Albanisch, native speaker
			sr: { takeRequest: 'Молим те, води ме до …',
				name: 'Име', comment: 'Коментар', address: 'Адреса', directions: 'Место и одредиште' }, // native speaker
			sv: { takeRequest: 'Snälla ta mig till …',
				name: 'Namn', comment: 'Kommentar', address: 'Adress', directions: 'Vägbeskrivning' }, // native speaker
			tg: { takeRequest: 'Лутфан маро ба …' },
			th: { takeRequest: 'กรุณาพาฉันไปที่ …' ,
				name: 'ชื่อ', comment: 'แนะนำ', address: 'ที่อยู่', directions: 'สถานที่ตั้ง' }, //Thai: by native speakerfrom Chiang Mai, North Thailand
			tl: { takeRequest: 'Pakiusap dalhin mo ako sa …',
				name: 'Pangalan', comment: 'Komento', address: 'Address', directions: 'Paano pumunta doon' }, // Tagalog (Filipino) provided by a native speaker from anywhere in Luzon
			tr: { takeRequest: 'Lütfen beni … götür',
				name: 'Ad', comment: 'Özellik', address: 'Adres', directions: 'Yer ve varış noktası' }, // native speaker
			uk: { takeRequest: 'Будь ласка, відвезіть мене до …',
				name: 'Назва', comment: 'Коментар', address: 'Адреса', directions: 'Як дістатись' }, // native speaker
			uz: { takeRequest: 'Iltimos, meni olib boring …' },
			vi: { takeRequest: 'Xin hãy đưa tôi đến …',
				name: 'Tên', comment: 'Chú thích', address: 'Địa chỉ', directions: 'Chỉ đường' },
			yue: { takeRequest: '请带我去……',
				name: '名字', comment: '评论', address: '地址', directions: '方向' }, // Cantonese: from a native speaker from city of Guangzhou
			zh: { takeRequest: '您好,请您带我去……',
				name: '名称', comment: '评价与备注', address: '地址', directions: '如何到达酒店' }, // Mandarin: from a native speaker from city of Hefei
		};

		/************************** Dialog ************************************/

		// Opening the dialog
		var open = function() {
			close();

			var width = 400;
			var height = 300;
			var left = (document.body.scrollWidth - width) / 2 + $(document).scrollLeft();
			if ( left < 0 ) left = 0;
			var top = (window.innerHeight - height) / 2 + $(document).scrollTop();
			if ( top < 0 ) top = 0;
			var isMobile = ( /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test( navigator.userAgent.toLowerCase() ) );
//			isMobile = true;

			$( 'body' ).append( $( '<div class="wv-dialog-background"></div>' ) );
			var aDialog = $( '<div id="wv-listing-info" class="wv-dialog" role="dialog"></div>' )
				.css( { 'width': width, 'height': height, 'display': 'flex',
					'left': left, 'top': top } )

				// Handle TAB and ESC keycodes
				.keydown( function( e ) { handleKeyCodes( e ); } );
			if ( isMobile ) aDialog.addClass( 'wv-dialog-mobile' );
			$( 'body' ).append( aDialog );

			return aDialog;
		};

		// Key code event handler for TAB and ESC keys
		var handleKeyCodes = function( event ) {
			switch( event.keyCode ) {
				case 9: // TAB
					var tabbables = $( event.delegateTarget ).find( ':visible' ).filter( 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]' ),
						first = tabbables.filter( ':first' ),
						last = tabbables.filter( ':last' );
					if ( event.target === last[0] && !event.shiftKey ) {
						event.preventDefault();
						first.focus();
					} else if ( event.target === first[0] && event.shiftKey ) {
						event.preventDefault();
						last.focus();
					}
    	    		break;
	        	case 27: // ESC
					event.preventDefault();
        			close();
        			break;
			}
		};

		// Closing the dialog
		 var close = function() {
			if ( $( '.wv-dialog-background' ).length > 0 ) $( '.wv-dialog-background' ).remove();
			if ( $( '.wv-dialog' ).length > 0 ) $( '.wv-dialog' ).remove();
		};

		// Creating header with the opportunity to use it as a move tool
		var makeHeader = function( text, move ) {
			var header = $( '<h2 style="flex: none;">' + text + '</h2>' );
		
			if ( move )
				header.css('cursor', 'move')
				.mouseup(function(e) {
					$(this).off( 'mousemove', dialogMove ).css('cursor', 'move');
					focusNextButton();
				})
				.mouseout(function(e) {
					$(this).off( 'mousemove', dialogMove ).css('cursor', 'move');
					focusNextButton();
				})
				.mousedown(function(e) {
					var dialog = $( '#wv-listing-info' );
					$(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') );
				});
			return header;
		};

		// Event handler for mouse move
		var dialogMove = function( e ) {
			var dialog = $( '#wv-listing-info' );
			dialog.css('left', dialogPos.X - mousePos.X + e.clientX)
				.css('top', dialogPos.Y - mousePos.Y + e.clientY);
		};
	
		/***************** Info panes and selection ***************************/

		// Making buttons with event handlers
		// Image name is used as an id, too
		// cancel = true means it is the cancel button
		var makeButton = function( img, tooltip, cancel ) {
			var image = $( '<img></img>' ).attr( {
				src: actionButtons[img],
				title: actionButtons[tooltip]
				} )
				.css( { 'vertical-align': 'middle' } );
			var button = $( '<button class="wv-dialog-button"></button>' )
				.attr( { 'title': actionButtons[tooltip], 'id': img } )
				.css( { 'clear': 'right', 'float': 'right' } )
				.append( image );
			if ( cancel ) button.click(function() { close() });
			else button.click(function( e ) { changeInfoPane( e ) });
			return button;
		};
	
		// Make an info pane distinguishable by id
		var makeInfoPane = function( id ) {
			return $( '<div class="wv-infoPane"></div>' )
				.css( { 'width': 340, 'display': 'none' } )
				.attr( 'id', id);
		};

		// Select an info pane by id and disable its button
		var selectInfoPane = function( id ) {
			var infoPanes = $( '.wv-infoPane' );
			var buttons = $( '.wv-dialog-button' );

			infoPanes.each( function() {
				if ( $( this ).attr( 'id' ) === id )
					$( this ).css( { 'display': 'flex', 'flex-direction': 'column' } );
				else $( this ).css( 'display', 'none' );
			});

			id = id.replace( 'p-', '' );

			buttons.each( function() {
				if ( $( this ).attr( 'id' ) === id )
					$( this ).prop( 'disabled', true );
				else $( this ).prop( 'disabled', false );
			});
		
			setTimeout(function () { focusNextButton(); }, 100);
			if ( id === 'mapImg' ) createMap();
		};


		// Focus the button of the following info pane
		var focusNextButton = function() {
			var infoPanes = $( '.wv-infoPane' );
			var paneFound = false, id;

			infoPanes.each( function() {
				if ( paneFound ) {
					paneFound = false;
					id = $( this ).attr( 'id' ).replace( 'p-', '' );
					$( '#' + id ).focus();
				}
				if ( $( this ).css( 'display' ) !== 'none' ) paneFound = true;
			});
			if ( paneFound ) $( '#cancelImg' ).focus();
		};

		// Event handler for info-pane change triggered by clicking on its
		// belonging buttons or button child images
		var changeInfoPane = function ( e ) {
			var id = '';
			var button = $( e.target ).closest( '.wv-dialog-button' );
			if ( button.length === 1 ) id = button.attr( 'id' );
			if ( id !== '' ) selectInfoPane( 'p-' + id );
		};

		/********************** Helper function *******************************/

		// Specifying language of a text and wrap it with right-to-left mark
		// if necessary
		var langSpan = function( text, lang ) {
			if ( text === '' ) return '';
			var r2l = {
				ar: '',
				dv: '',
				fa: '',
				he: '',
				ms: '',
				ur: '',
			};
			var t = '<span lang="' + lang + '" xml:lang="' + lang + '"';
			if ( lang in r2l ) t += ' dir="rtl"';
			t += '>' + text + '</span>';
			if ( lang in r2l ) t = '&rlm;' + t + '&lrm;';
			return t;
		};
	
		// Getting HTML text from a wrapper tag specified by aClass.
		// context is the dialog itself
		var getHTML = function( aClass, context ) {
			var span = $( '.' + aClass, context );
			if ( span.length === 0 ) return '';
			return span.first().html();
		};
	
		// Creating a Kartographer map
		var createMap = function () {
			// see also: https://www.mediawiki.org/wiki/Help:Extension:Kartographer/Developer_guide
			if ( aMap !== null ) return;

			mw.loader.using( [ 'ext.kartographer.box' ], function () {
				var kartoBox = mw.loader.require( 'ext.kartographer.box' );

				aMap = kartoBox.map( {
					container: $( '#p-map' )[ 0 ],
					center: [ lat, lon ],
					zoom: 15,
					allowFullScreen: true,
					alwaysInteractive: true,
					data: [ {
						"type": "Feature",
						"properties": {
							"marker-color": color,
							"marker-size": "medium",
							"marker-symbol": getTypeSymbol(),
							"title": title,
							"description": thumb
						},
						"geometry": {
							"type": "Point",
							"coordinates": [
								lon, lat
							]
						}
					} ]
				} );
			} );
		};

		// Making a header with wikilang and country lang identifiers
		var translate = function( lang, wikiLang, id, delimiter ) {
			var s = '';
			if ( wikiLang === lang )
				s = translations[wikiLang][id];
			else {
				if ( lang !== '' && lang in translations && id in translations[lang] )
					s = langSpan( translations[lang][id], lang);
				else {
					if ( wikiLang !== 'en' ) s = translations.en[id];
				}
				if ( s === '' )	s = translations[wikiLang][id];
				else s = translations[wikiLang][id] + delimiter + s;
			}
			return s;
		};

		var getTypeSymbol = function() {
			var symbols = {
				'around':    'circle-stroked',
				'buy':       'shop',
				'city':      'town',
				'do':        'swimming',
				'drink':     'bar',
				'eat':       'restaurant',
				'error':     'cross',
				'go':        'suitcase',
				'other':     'star-stroked',
				'see':       'town-hall',
				'sleep':     'lodging',
				'target':    'star-stroked',
				'vicinity':  'land-use',
				'view':      'camera',
			};
			if ( aType in symbols ) return symbols[aType];
			return '';
		};

		/**************** Making and filling dialog ***************************/

		// Creating the dialog
		// Event handler called by listingPopup.init
		var dialog = function( element ) {
			var listing = element.closest( listingSelector );
			var wikiLang = mw.config.get( 'wgPageContentLanguage' );
			var userLang = mw.config.get( 'wgUserLanguage' );
			if ( userLang && translations[ userLang ] ) wikiLang = userLang ;
			var lang = listing.attr( 'data-lang' );
			if ( lang.indexOf('-') >-1 ) lang = lang.substring(0, 2);
			aType = listing.attr( 'data-group' ); // other wikis: 'data-type'
			var link, s, t, infoPane;

			var dialog = open();

			var button;
			var buttonPane = $( '<div class="wv-buttonPane"></div>' );

			/****** General ******/

			var url = listing.attr( 'data-url' );
			if ( !url ) url = '';

			var name = listing.attr( 'data-name' );
			var listingName = $( '.listing-name', listing ).first();
			var nameHTML = listingName.html();
			if ( !name ) {
				link = $( 'a', listingName ).first();
				if ( link.length === 0 ) name = listingName.text();
				else name = link.text();
			}
			title = name;
			var nameLocal = '';
			s = listing.attr( 'data-name-local' );
			if ( s ) nameLocal = langSpan( s, lang );
			if ( nameLocal === '' ) nameLocal = getHTML( 'listing-alt', listing );

			/****** Bring me to dialog ******/
		
			var comment = getHTML( 'listing-comment', listing );
			var address = getHTML( 'listing-address', listing );
			var addressLocal = '';
			s = listing.attr( 'data-address-local' );
			if ( s ) addressLocal = langSpan( s, lang );
			var directions = getHTML( 'listing-directions', listing );
			var directionsLocal = '';
			s = listing.attr( 'data-directions-local' );
			if ( s ) directionsLocal = langSpan( s, lang );

			infoPane = makeInfoPane( 'p-taxiImg' );
			s = translate( lang, wikiLang, 'takeRequest', '<br />' );
			infoPane.append( makeHeader( s, true ) );
			var container = $( '<div class="wv-container"></div>' );
			infoPane.append( container );

			s = translate( lang, wikiLang, 'name', ' / ' );
			t = nameLocal;
			if ( t === '' ) t = name;
			else t += '<br />' + name;
			container.append( '<dl><dt>' + s + '</dt><dd>' + t + '</dd></dl>' );

			if ( comment !== '' ) {
				s = translate( lang, wikiLang, 'comment', ' / ' );
				container.append( '<dl><dt>' + s + '</dt><dd>' + comment + '</dd></dl>' );
			}

			if ( address !== '' || addressLocal !=='' ) {
				s = translate( lang, wikiLang, 'address', ' / ' );
				t = addressLocal;
				if ( address !== '' ) {
					if ( t === '' ) t = address;
					else t += '<br />' + address;
				}
				container.append( '<dl><dt>' + s + '</dt><dd>' + t + '</dd></dl>' );
			}

			if ( directions !== '' || directionsLocal !=='' ) {
				s = translate( lang, wikiLang, 'directions', ' / ' );
				t = directionsLocal;
				if ( directions !== '' ) {
					if ( t === '' ) t = directions;
					else t += '<br />' + directions;
				}
				container.append( '<dl><dt>' + s + '</dt><dd>' + t + '</dd></dl>' );
			}

			dialog.append( infoPane );
			button = makeButton( 'taxiImg', 'taxiTooltip', false );
			buttonPane.append( button );

			/****** Image dialog ******/

			s = nameHTML;
			if ( nameLocal !== '' ) s += '<br />' + nameLocal;

			var image = getHTML( 'listing-image', listing );
			if ( image !== '' ) {
				// for mobile view: show image from noscript instead of placeholder
				image = image.replace( '<noscript>', '' ).replace( '</noscript>', '' );
				thumb = image;
				infoPane = makeInfoPane( 'p-figureImg' );
				infoPane.append( '<div class="wv-Image">' + image + '</div>' );
				infoPane.append( '<p><strong>' + s + '</strong> '
					+ getHTML( 'listing-sister-commons', listing ) + '</p>' );
				dialog.append( infoPane );

				button = makeButton( 'figureImg', 'figureTooltip', false );
				buttonPane.append( button );			
			}

			/****** Map dialog ******/

			aMap = null;
			link = $( '.mw-kartographer-maplink', listing ).first();
			if ( link.length > 0 ) {
				lat = link.attr( 'data-lat' );
				lon = link.attr( 'data-lon' );
				color = listing.attr( 'data-color' );

				infoPane = makeInfoPane( 'p-mapImg' );
				infoPane.append( $( '<div id="p-map"></div>' )
					.css( { 'border': '1px solid #c8ccd1', 'height': 225 } ));
				dialog.append( infoPane );

				// map is created later if dialog is visible

				infoPane.append( '<p><strong>' + s + '</strong></p>' );
				button = makeButton( 'mapImg', 'mapTooltip', false );
				buttonPane.append( button );
			}

			/****** Contact dialog ******/

			var phone = getHTML( 'listing-landline', listing );
			var tollfree = getHTML( 'listing-tollfree', listing );
			var mobile = getHTML( 'listing-mobile', listing );
			var fax = getHTML( 'listing-fax', listing );
			var email = getHTML( 'listing-email', listing );
			var skype = getHTML( 'listing-skype', listing );
			var socialMedia = $( '.listing-social-media', listing );
			t = '';
			if ( socialMedia.length !== 0 )
				socialMedia.each( function() {
					if ( t !== '' ) t += ' ';
					t += $( this ).html();
				} );
			socialMedia = t;
		
			if ( phone !== '' || tollfree !== '' || mobile !== '' || fax !== '' ||
				email !=='' || skype !== '' || url !== '' || socialMedia !== '' ) {
				infoPane = makeInfoPane( 'p-contactImg' );
				infoPane.append( makeHeader( headers.contact, true ) );
				container = $( '<div class="wv-container"></div>' );
				infoPane.append( container );

				if ( phone !== '' ) container.append( '<p><strong>'
					+ contact.phone + ':</strong> ' + phone + '</p>' );
				if ( mobile !== '' ) container.append( '<p><strong>'
					+ contact.mobile + ':</strong> ' + mobile + '</p>' );
				if ( tollfree !== '' ) container.append( '<p><strong>'
					+ contact.tollfree + ':</strong> ' + tollfree + '</p>' );
				if ( fax !== '' ) container.append( '<p><strong>'
					+ contact.fax + ':</strong> ' + fax + '</p>' );
				if ( email !== '' ) container.append( '<p><strong>'
					+ contact.email + ':</strong> ' + email + '</p>' );
				if ( skype !== '' ) container.append( '<p><strong>'
					+ contact.skype + ':</strong> ' + skype + '</p>' );
				if ( url === '' ) s = '';
				else s = '<a target="_blank" class="external text" href="' + url + '"><img width="16" height="16"'
					+ ' style="vertical-align: text-bottom;" src="'
					+ webImg + '" title="' + webTooltip + '" /></a> ';
				if ( socialMedia !== '' ) s += socialMedia;
				if ( s !== '' ) container.append( '<p><strong>'
					+ contact.web + ':</strong> ' + s + '</p>' );

				dialog.append( infoPane );
				button = makeButton( 'contactImg', 'contactTooltip', false );
				buttonPane.append( button );
			}

			/****** Features dialog ******/
		
			var features = getHTML( 'listing-subtype', listing );
			var hours = getHTML( 'listing-hours', listing );
			var checkin = getHTML( 'listing-checkin', listing );
			var checkout = getHTML( 'listing-checkout', listing );
			var credit = getHTML( 'listing-credit', listing );

			if ( features !== '' || hours !== '' || checkin !== '' || checkout !== '' ||
				credit !== '' ) {
				infoPane = makeInfoPane( 'p-featuresImg' )
					.css( { 'overflow-y': 'auto' } );

				var move = true;
				if ( features !== '' ) {
					infoPane.append( makeHeader( headers.features, move ) );
					move = false;
					infoPane.append( '<p>' + features + '</p>' );
				}
				if ( credit !== '' ) {
					infoPane.append( makeHeader( headers.credit, move ) );
					move = false;
					infoPane.append( '<p>' + credit + '</p>' );
				}
				if ( hours !== '' || checkin !== '' || checkout !== '' ) {
					infoPane.append( makeHeader( headers.hours, move ) );
					if ( hours !== '' ) infoPane.append( '<p>' + hours + '</p>' );
					if ( checkin !== '' ) infoPane.append( '<p>' + checkin + '</p>' );
					if ( checkout !== '' ) infoPane.append( '<p>' + checkout + '</p>' );
				}

				dialog.append( infoPane );
				button = makeButton( 'featuresImg', 'featuresTooltip', false );
				buttonPane.append( button );
			}

			/****** Booking, comparison, and rating dialog ******/

			var booking = []; // results array

			for (var i = 0; i < sites.length; i++) {
				s = listing.attr( sites[i].data );
				if ( s ) {
					s = sites[i].formatter.replace( '$1', s );
					booking.push( '<li class="' + classPrefix + sites[i].grClass
						+ '"><a target="_blank" class="external text" href="' + s
						+ '" title="' + sites[i].title + '">' + sites[i].site + '</a></li>' );
				}
			}
	
			if ( booking.length > 0 ) {
				infoPane = makeInfoPane( 'p-bookingImg' );
				infoPane.append( makeHeader( headers.booking, true ) );
				container = $( '<div class="wv-container"></div>' );
				infoPane.append( container );

				s = nameHTML;
				if ( nameLocal !== '' ) s += '<br />' + nameLocal;
				container.append( '<p>' + s + '</p>' );
				s = $( '<ul class="wv-listing-booking"></ul>' );
				container.append( s );

				for (i = 0; i < booking.length; i++) s.append( booking[i] );

				dialog.append( infoPane );
				button = makeButton( 'bookingImg', 'bookingTooltip', false );
				buttonPane.append( button );			
			}

			/****** Final ******/

			dialog.append( buttonPane );

			if ( $( '.wv-dialog-button' ).length < 2 ) buttonPane.empty();
			button = makeButton( 'cancelImg', 'cancelTooltip', true );
			buttonPane.append( button );
			selectInfoPane( 'p-taxiImg' );
		};

		return { dialog: dialog	};
	} ();

	/************************ Initialization **********************************/

	var listingInit = function() {
		var button = 'info';
		var buttonTooltip = 'Zeigt ein Popup-Fenster mit den wichtigsten vCard-Daten und teilweise mit Buchungsmöglichkeiten an';
		var editSelector = 'span.listing-metadata-items';
		var imagesToPreload = [
			'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Edit_font_awesome.svg/20px-Edit_font_awesome.svg.png',
			'https://upload.wikimedia.org/wikipedia/commons/thumb/c/ce/Black_close_x.svg/20px-Black_close_x.svg.png',
			'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Phone_font_awesome.svg/20px-Phone_font_awesome.svg.png',
			'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Black_check.svg/20px-Black_check.svg.png',
			'https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/High-contrast-image-x-generic.svg/20px-High-contrast-image-x-generic.svg.png',
			'https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Antu_tag-places-black.svg/20px-Antu_tag-places-black.svg.png',
			'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Q82650_noun_7191_ccJensT%C3%A4rning_taxi.svg/20px-Q82650_noun_7191_ccJensT%C3%A4rning_taxi.svg.png',
			'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Erioll_world_2.svg/24px-Erioll_world_2.svg.png'
		];

		// Check if namespace and action is allowed
		var checkIfAllowed = function() {
			var namespace = mw.config.get( 'wgNamespaceNumber' );
			if (namespace !== 0 && namespace !== 2) return false;
			return mw.config.get( 'wgAction' ) === 'view';
		};

		// Check if a last-edit indicator is given
		var isElementEmpty = function( element ) {
			var text = $( element ).text();
			if ( !text.trim() ) return true;
			return (text.trim() == '&nbsp;');
		};

		// Adding "info" buttons and event handlers after vCard text
		var init = function() {
			if ( !checkIfAllowed() ) return;

			// preloading button images
			var images = [];
			for ( var i = 0; i < imagesToPreload.length; i++ ) {
				images[i] = new Image();
				images[i].src = imagesToPreload[i];
			}

			var popupButton = $( '<a href="javascript:" class="listing-popup">' + button + '</a>' )
				.attr( 'title', buttonTooltip )
				.click( function() { listingPopup.dialog( $(this) ); });
			var popupButtonSpan = $( '<span class="listing-metadata-item vcard-popup-button noprint">' )
				.append( popupButton );

			var metadata = $( editSelector );
			// maybe ListingInfo.js is called before ListingEditor.js
			metadata.each( function() {
				if ( isElementEmpty( this ) ) {
					$( this ).empty();
					$( this ).parent().prepend('<span class="noprint"> (</span>')
						.append('<span class="noprint">)</span>');
				}
			});
			metadata.append( popupButtonSpan );
		};

		return { init: init };
	} ();
	
	$( listingInit.init );

} ( jQuery, mediaWiki ) );

//</nowiki>