MediaWiki:MarkerTooltip.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>
/***************************************************************************
 * MarkerTooltip.js v1.4, 2018-11-27
 * Displays an extended marker tooltip on mouse over on desktops
 * or on click on smartphones
 * Displays tooltip for abbreviations on smartphones
 * Original author: Roland Unger
 * Support of both desktop and mobile views
 * Documentation: https://de.wikivoyage.org/wiki/Wikivoyage:MarkerTooltip.js
 * License: GPL-2.0+, CC-by-sa 3.0
 ***************************************************************************/

( function ( $ ) {
	'use strict';

	var mkTooltip = function() {
		var wmflabsToolsURL = 'https://tools.wmflabs.org/geohack/geohack.php?';
		var wvToolsURL = '/w/index.php?title=Special%3AMapsources';
		var maxZoomLevel = 19; // see also getScaleFromZoom

		var mapHint = '<b>Click auf den Marker öffnet die Karte direkt.</b>';
		var decLabel = 'Dezimal';
		var decTitle = 'Es folgt die Koordinate in Dezimalform. Über den nebenstehenden Geo-URI-Link kann eine Karten-Anwendung gestartet werden.';
		var geoUriTitle = 'Über diesen Link startet der Browser eine Karten-Anwendung, z.&nbsp;B. Google Maps. Auf vielen Android-Smartphones bereits eingerichtet.';
		var hexLabel = 'GMS';
		var hexTitle = 'Es folgt die Koordinate in der Form Grad-Minuten-Sekunden.';
		var plusLabel = 'Plus Code';
		var plusTitle = 'Es folgt die Koordinate als Plus Code.';
		var ch1903Label = 'CH1903';
		var ch1903Title = 'Es folgt die Koordinate in der Form der Schweizer Landeskoordinaten.';
		var linkTo = 'Link zu';
		var wvTools = 'Wikivoyage-Kartenwerkzeugen';
		var wvToolsTitle = 'Öffnet eine Wikivoyage-eigene Internetseite, die zahlreiche Kartenquellen und -werkzeuge auflistet.';
		var wmflabsTools = 'WMF-Labs-Kartenwerkzeugen';
		var wmflabsToolsTitle = 'Öffnet eine Internetseite von WMF Labs, die zahlreiche Kartenquellen und -werkzeuge auflistet.';
		var copyTitle = 'Kopiert die nebenstehende Angabe in die Zwischenablage. Insbesondere ältere Browser unterstützten diese Funktion leider nicht.';
		var clipboard = 'Ablage';
		var NS = 'NS';
		var EW = 'OW'; // international: 'EW'

		var isMobile = false;
		var timeouts = [];

		// Only n digits
		var round = function( coord, n ) {
			var m = Math.pow( 10, n );
			return Math.round( coord * m ) / m;
		};

		// Converting decimal to DMS coordinates
		var toDMS = function( dec, letters ) {
			var deg, min, sec, letter;
			if ( dec < 0 ) letter = letters.charAt( 1 );
			else letter = letters.charAt( 0 );

			var angle = Math.abs( dec );
			deg = Math.floor( angle );
			min = ( angle - deg ) * 60;
			sec = Math.round( ( min - Math.floor( min ) ) * 60 );
			min = Math.floor( min );
			if ( sec >= 60 ) {
				sec -= 60;
				min += 1;
			}
			if ( min >= 60 ) {
				min -= 60;
				deg += 1;
			}
			return deg + '° ' + min + '′ ' + sec + '″ ' + letter;
		};

		// Converting decimal to CH1903 coordinates
		// see: https://de.wikipedia.org/wiki/Schweizer_Landeskoordinaten
		var toCH1903 = function( lat, lon ) {
			var ch1903 = {
				easting: 0,
				northing: 0,
				error: true
			};
			if ( lat < 45.5 || lat > 48 || lon < 5.0 || lon > 11 ) return ch1903;

			var phi = ( lat * 3600 - 169028.66 ) / 10000;
			var phi2 = phi * phi;
			var lambda = ( lon * 3600 - 26782.5 ) / 10000;
			var lambda2 = lambda * lambda;

			ch1903.northing = Math.round( 200147.07 + 308807.95 * phi + 3745.25 * lambda2 +
				76.63 * phi2 - 194.56 * lambda2 * phi + 119.79 * phi2 * phi );

			ch1903.easting = Math.round( 600072.37 + 211455.93 * lambda - 10938.51 * lambda * phi -
				0.36 * lambda * phi2 - 44.54 * lambda2 * lambda );

			ch1903.error = false;
			return ch1903;
		};

		// Converting decimal to Open Location Code (Plus Code)
		// see: https://en.wikipedia.org/wiki/Open_Location_Code
		var toPlusCode = function( lat, lon ) {
			var codeChars = '23456789CFGHJMPQRVWX';
			var resolutions = [ 20.0, 1.0, 0.05, 0.0025, 0.000125 ];
			var code = '';

			var modLat = lat;
			modLat = Math.max( -90, modLat );
			modLat = Math.min( modLat, 90 - 0.000025 );
			// 0.000025 = resolutions[ 4 ] / 5 [rows]
			modLat += 90; // starting from 0
			
			var modLon = lon;
			while ( modLon < -180 ) modLon += 360;
			while ( modLon >= 180 ) modLon -= 360;
			modLon += 180; // starting from 0

			// first 10 + 1 digits
			var col, digit, i, latRes, lonRes, res, row;
			for ( i = 0; i < 5; i++ ) {
				res = resolutions[ i ];

				digit = Math.floor( modLat / res );
				modLat -= digit * res;
				code += codeChars.charAt( digit );

				digit = Math.floor( modLon / res );
				modLon -= digit * res;
				code += codeChars.charAt( digit );

				if ( i === 3 ) code += '+';
			}

			// last digit
			latRes = res; // resolutions[ 4 ]
			lonRes = res;

			latRes /= 5;
			lonRes /= 4;
			row = Math.floor( modLat / latRes );
			col = Math.floor( modLon / lonRes );
			code += codeChars.charAt( 4 * row + col );

			return code;
		};
		
		// zoom level 19 -> 1:1000, 0 -> 500000000
		var getScaleFromZoom = function( zoom ) {
			var scales = [ 1000, 2000, 4000, 8000, 15000, 35000, 70000, 150000, 250000,
				500000, 1000000, 2000000, 4000000, 10000000, 15000000, 35000000, 70000000,
				150000000, 250000000, 500000000 ];
			if ( zoom >= maxZoomLevel ) return scales[ 0 ];
			if ( zoom <= 0 ) return scales[ scales.length - 1 ];
			return scales[ maxZoomLevel - Math.round( zoom ) ];
		};

		var copyToClipboard = function( selector, container ) {
			var clipboard = $( '<textarea id="mkClipboard"></textarea>' )
				.css( { 'width': 1, 'border': 'none', 'opacity': 0 } );
			$( 'body' ).append( clipboard );
			clipboard.val( $( selector, container ).text() )
				.select();
			document.execCommand( 'copy' );
			clipboard.remove();
		};

		var clipboardLink = function( aClass ) {
			return '[ <a href="javascript:" class="' + aClass + '" title="'
				+ copyTitle + '">' + clipboard + '</a> ]';
		};

		var makeTableRow = function( label, title, clipClass, buttonClass, text ) {
			return '<tr><td>'
				+ '<span title="' + title + '">' + label + ':</span> '
				+ '<span class="' + clipClass + '">' + text + '</span></td>'
				+ '<td>' + clipboardLink( buttonClass ) + '</td></tr>';
		};

		var makeContent = function( $origin ) {
			var link = $( '.mw-kartographer-maplink', $origin ).first();
			var lat = round( link.attr( 'data-lat' ), 6 );
			var latStr = toDMS( lat, NS );
			var lon = round( link.attr( 'data-lon' ), 6 );
			var lonStr = toDMS( lon, EW );
			var zoom = link.attr( 'data-zoom' );

			var wrapper = $origin.closest( '.vcard' );
			var color = wrapper.attr( 'data-color' );
			var lang = wrapper.attr( 'data-wikilang' );
			var region = wrapper.attr( 'data-region' );
			if ( region === undefined ) region = '';

			var name = wrapper.attr( 'data-name' );
			if ( name === undefined ) {
				name = $( '.listing-name', wrapper ).first();
				var wikiLink = $( 'a', name ).first();
				if ( wikiLink.length === 0 ) name = name.text();
				else name = wikiLink.text();
			}
			name = encodeURI( name.replace( /\s/g, '+' ) ).replace( /&/g, '%26' );

			var params = '&params=';
			if ( lat < 0 ) params += Math.abs( lat ) + '_S_';
			else params += lat + '_N_';
			if ( lon < 0 ) params += Math.abs( lon ) + '_W';
			else params += lon + '_E';
			params += '_scale%3A' + getScaleFromZoom( zoom )
				+ '_type%3Alandmark_globe%3Aearth';
			if ( region !== '' ) params += '_region%3A' + region;

			var ch1903 = toCH1903( lat, lon );
			var plusCode = toPlusCode( lat, lon );

			var table = '<table>'
				+ makeTableRow( hexLabel, hexTitle, 'mkClip1', 'mkButton1',
					latStr + ' ' + lonStr )
				+ makeTableRow( decLabel, decTitle, 'mkClip2', 'mkButton2',
					'<a href="geo:' + lat + ',' + lon + '" title="'
					+ geoUriTitle + '">' + lat + ', ' + lon + '</a>' )
				+ makeTableRow( plusLabel, plusTitle, 'mkClip3', 'mkButton3',
					'<span class="plusCode">' + plusCode.substr( 0, 4 ) + '</span>'
					+ plusCode.substr( 4 ) );
			if ( !ch1903.error ) table += makeTableRow( ch1903Label, ch1903Title,
				'mkClip4', 'mkButton4', '<span title="CH1903 easting">'
				+ ch1903.easting + '</span> / <span title="CH1903 northing">'
				+ ch1903.northing + '</span>' );
			table += '</table>';

			return $( '<div class="mkTooltipInner"></div>' )
				.css( 'border-left-color', color )
				.append( $( '<div>' + mapHint + '</div>' )
					.css( { 'margin-bottom': '0.5em' } ) )
				.append( $( table ) )
				.append( $( '<div>' + linkTo + ' <a href="' + wvToolsURL + params
					+ '&locname=' + name + '" title="' + wvToolsTitle + '">'
					+ wvTools + '</a></div>' ) )
				.append( $( '<div>' + linkTo + ' <a href="' + wmflabsToolsURL
					+ 'pagename=' + name + '&language=' + lang + params + '" title="'
					+ wmflabsToolsTitle + '">' + wmflabsTools + '</a></div>' ) )
				.append( $( '<div class="mkTooltipTail"></div>' ) );
		};

		// setting tooltip position
		var setTooltipPosition = function( e, tooltip, $this ) {
			var tail = $( '.mkTooltipTail', tooltip ), left, offset, right, width;
			var winWidth = $( window ).width();
			
			if ( e.clientY < $( window ).height() / 2 ) 
				tooltip.css( 'top', $this.innerHeight() - 4 )
					.addClass('mkBelow');
			else
				tooltip.css( 'bottom', $this.innerHeight() - 4 )
					.addClass('mkAbove');
			if ( e.clientX < winWidth / 2 ) {
				tooltip.css( 'left', $this.innerWidth() / 2 - 16 )
					.addClass('mkLeft');
				if ( isMobile ) {
					offset = tooltip.offset();
					right = offset.left + tooltip.outerWidth();
					if ( right > winWidth - 1 ) {
						left = offset.left - ( right - winWidth ) - 2;
						if ( left < 2 ) left = 2;
						width = tooltip.innerWidth();
						tooltip.offset( { top: offset.top, left: left } );
						tooltip.innerWidth( width );
						width = offset.left - left;
						offset = tail.offset();
						offset.left += width;
						tail.offset( offset );
					}
				}
			}
			else {
				tooltip.css( 'right', $this.innerWidth() / 2 - 13 )
					.addClass('mkRight');
				if ( isMobile ) {
					offset = tooltip.offset();
					left = offset.left;
					if ( left < 2 ) {
						width = tooltip.innerWidth();
						tooltip.offset( { top: offset.top, left: 2 } );
						tooltip.innerWidth( width );
						offset = tail.offset();
						offset.left += left;
						tail.offset( offset );
					}
				}
			}
		};

		var showTooltip = function( e ) {
			var $this = $( e.target ).closest( '.listing-tooltip' );
			var id = $this.attr( 'data-id' );
			var $origin = $this;
			if ( $this.hasClass( 'copy-marker' ) ) {
				// getting from original marker
				var attr = $this.attr( 'data-copy-marker-attribute' );
				var content = $this.attr( 'data-copy-marker-content' );
				$origin = $( '*[' + attr + '="' + content + '"]' ).first();
			}

			var tooltip = $( '<div class="mkTooltip" role="tooltip"></div>' )
				.append( makeContent( $origin ) );
			if ( !isMobile ) tooltip.hide(); // later fade-in
			else tooltip.addClass( 'mkTooltipMobile' );
			$this.append( tooltip );
			setTooltipPosition( e, tooltip, $this );

			$( '.mkButton1', tooltip )
				.click( function() { copyToClipboard( '.mkClip1', tooltip ); } );
			$( '.mkButton2', tooltip )
				.click( function() { copyToClipboard( '.mkClip2', tooltip ); } );
			$( '.mkButton3', tooltip )
				.click( function() { copyToClipboard( '.mkClip3', tooltip ); } );
			$( '.mkButton4', tooltip )
				.click( function() { copyToClipboard( '.mkClip4', tooltip ); } );

			if ( isMobile )
				// removing tooltip after 10 sec in mobile mode
				timeouts[ id ] =
					setTimeout( function() { removeTooltip( $this ) }, 10000 );
			else
				// fading-in hidden tooltip in desktop mode
				setTimeout( function() { tooltip.fadeIn( 500 ); }, 300 );

			return tooltip;
		};

		var removeTooltip = function( marker ) {
			$( '.listing-tooltip-button', marker ).text( '▼' );
			$( '.mkTooltip', marker ).remove();
			var id = marker.attr( 'data-id' );
			clearTimeout( timeouts[ id ] );
		};

		var showMobileTooltip = function( e ) {
			var $this = $( e.target ).closest( '.listing-tooltip-button' );
			if ( $this.text() === '▼' ) {
				$this.text( '▲' );
				showTooltip( e );
			}
			else {
				var marker = $( e.target ).closest( '.listing-tooltip' );
				removeTooltip( marker );
			}
		};

		var initMarkerTooltip = function() {
			$( '.listing-map' ).addClass( 'listing-tooltip' );
			$( '.copy-marker' ).addClass( 'listing-tooltip' ); // Marker-Kopie

			var markers = $( '.listing-tooltip' )
				.attr( 'title', '' )
				.css( { 'position': 'relative', 'cursor': 'default' } );
			var id = 0;
			// setting id for timeout handler
			markers.each( function() {
				$( this ).attr( 'data-id', 'tt' + id );
				id += 1;
			} );
				
			if ( isMobile ) {
				var mobileMarker = $( '<span class="listing-tooltip-button">▼</span>' )
					.click( function( e ) { showMobileTooltip( e ) });
				markers.append( mobileMarker );
			}
			else {
				markers.mouseenter( function( e ) {
					showTooltip( e );
				})
				.mouseleave( function( e ) {
					$( '.mkTooltip' ).remove();
				});
			}
		};

		var initAbbrTooltip = function() {
			var abbr = $( 'abbr' )
				.css( { 'position': 'relative', 'cursor': 'pointer' } );

			var id = 0;
			// setting id for timeout handler
			abbr.each( function() {
				$( this ).attr( 'data-id', 'at' + id );
				id += 1;
			} );

			abbr.click( function( e ) {
				var $this = $( e.target ).closest( 'abbr' );
				var id = $this.attr( 'data-id' );
				var tooltip = $( '.mkTooltip', $this );
				var div, title;
				if ( tooltip.length > 0 ) removeTooltip( $this );
				else {
					title = $this.attr( 'title' );
					if ( title ) {
						div = $( '<div class="mkTooltipInner mkTooltipMaxWidth">'
							+ title + '</div>' )
							.append( $( '<div class="mkTooltipTail"></div>' ) );
						tooltip = $( '<div class="mkTooltip mkTooltipMobile" role="tooltip"></div>' )
							.append( div );
						$this.append( tooltip );
						setTooltipPosition( e, tooltip, $this );
						timeouts[ id ] =
							setTimeout( function() { removeTooltip( $this ) }, 10000 );
					}
				}
			} );
		};

		var init = function() {
			isMobile = ( /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test( navigator.userAgent.toLowerCase() ) );
//			isMobile = true;

			initMarkerTooltip();
			if ( isMobile ) initAbbrTooltip();
		};

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

} ( jQuery ) );

//</nowiki>