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
//<nowiki> /*************************************************************************** * MarkerTooltip.js v1.6, 2025-01-02 * 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:Gadget-MarkerTooltip.js * License: GPL-2.0+, CC-by-sa 3.0 ***************************************************************************/ /* eslint-disable mediawiki/class-doc */ ( function ( $ ) { 'use strict'; var mkTooltip = function() { const strings = { de: { hint: 'Click auf den Marker öffnet die Karte direkt.', hint2: 'Bitte nutzen Sie zur Anzeige die Kartendienste.', ch1903: 'CH1903', ch1903Title: 'Es folgt die Koordinate in der Form der Schweizer Landeskoordinaten.', dec: 'Dezimal', decTitle: 'Es folgt die Koordinate in Dezimalform. Über den nebenstehenden Geo-URI-Link kann eine Karten-Anwendung gestartet werden.', geoUriTitle: 'Über diesen Link startet der Browser eine Karten-Anwendung, z. B. Google Maps. Auf vielen Smartphones bereits eingerichtet.', hex: 'GMS', hexTitle: 'Es folgt die Koordinate in der Form Grad-Minuten-Sekunden.', plus: 'Plus Code', plusTitle: 'Es folgt die Koordinate als Plus Code.', anchor: 'Anker', anchorTitle: 'Zeigt den Namen des Vorlagen-Ankers an.', anchorText: 'Der Name des Vorlagen-Ankers lautet:\n\n', clipboard: 'Ablage', clipboardTitle:'Kopiert die nebenstehende Angabe in die Zwischenablage. Insbesondere ältere Browser unterstützten diese Funktion leider nicht.', mapSources: 'Kartendienste', mapSourcesTitle: 'Es folgen verschiedene Listen mit Kartenquellen und -diensten', tools: 'Werkzeuge', toolsTitle: 'Es folgen verschiedene Vorlagen-Werkzeuge', voy: 'Wikivoyage', voyTitle: 'Öffnet eine Wikivoyage-eigene Internetseite, die zahlreiche Kartenquellen und -dienste auflistet.', voyURL: '/w/index.php?title=Special%3AMapsources', wmflabs: 'WMF-Labs', wmflabsTitle: 'Öffnet eine Internetseite von WMF Labs, die zahlreiche Kartenquellen und -dienste auflistet.', wmflabsURL: 'https://tools.wmflabs.org/geohack/geohack.php', EW: 'OW', // international: 'EW' NS: 'NS' }, en: { hint: 'Clicking on the marker directly opens the map.', hint2: 'Please use the map tools for display.', ch1903: 'CH1903', ch1903Title: 'Coordinates are shown in the form of the Swiss national coordinates.', dec: 'Decimal', decTitle: 'Coordinates are shown as decimal values. A map application can be started via the adjacent Geo-URI link.', geoUriTitle: 'The browser starts a map application using this link, e. g. Google Maps. Already set up on many smartphones.', hex: 'DMS', hexTitle: 'Coordinates are shown as degree-minutes-seconds.', plus: 'Plus Code', plusTitle: 'Coordinates are shown as Plus Code.', anchor: 'Anchor', anchorTitle: 'Shows the name of the template anchor.', anchorText: 'The name of the template anchor is:\n\n', clipboard: 'Clipboard', clipboardTitle:'Copies the adjacent information to the clipboard. Unfortunately, older browsers do not support this feature.', mapSources: 'Map tools', mapSourcesTitle: 'The following lists with map sources and services are available', tools: 'Tools', toolsTitle: 'The following tools are available', voy: 'Wikivoyage', voyTitle: 'Opens Wikivoyage’s own webpage which lists numerous map sources and services.', voyURL: '/w/index.php?title=Special%3AMapsources', wmflabs: 'WMF Labs', wmflabsTitle: 'Opens a WMF Labs webpage which lists numerous map sources and services.', wmflabsURL: 'https://tools.wmflabs.org/geohack/geohack.php', EW: 'EW', NS: 'NS' }, es: { hint: 'Haga clic aquí para abrir el mapa.', hint2: 'Utilice los servicios de mapas para visualizar.', ch1903: 'CH1903', ch1903Title: 'A esto le sigue la coordenada en forma de coordenadas nacionales suizas.', dec: 'Decimal', decTitle: 'La coordenada sigue en forma decimal. Se puede iniciar una aplicación de mapas a través del enlace Geo-URI adyacente.', geoUriTitle: 'El navegador inicia una aplicación de mapas a través de este enlace, p. ej. mapas de Google. Ya está configurado en muchos teléfonos inteligentes Android.', hex: 'GMS', hexTitle: 'La coordenada sigue en forma de grados-minutos-segundos.', plus: 'Plus Code', plusTitle: 'La coordenada sigue como un código de ubicación abierto.', anchor: 'Ancla', anchorTitle: 'Muestra el nombre del ancla de la plantilla.', anchorText: 'El nombre del ancla de la plantilla es:\n\n', clipboard: 'Portapapeles', clipboardTitle:'Copia la información adyacente al portapapeles. Lamentablemente, los navegadores más antiguos no admiten esta función.', mapSources: 'Instrumentos de mapas', mapSourcesTitle: 'Las siguientes listas con fuentes y instrumentos de mapas están disponibles.', tools: 'Instrumentos', toolsTitle: 'Los siguientes instrumentos están disponibles.', voy: 'Wikiviajes', voyTitle: 'Abre un sitio web de Wikiviajes que enumera numerosas fuentes y instrumentos de mapas.', voyURL: '/w/index.php?title=Special%3AMapsources', wmflabs: 'WMF Labs', wmflabsTitle: 'Abre un sitio web de WMF Labs que enumera numerosas fuentes y instrumentos de mapas.', wmflabsURL: 'https://tools.wmflabs.org/geohack/geohack.php', EW: 'EO', // international: 'EW' NS: 'NS' } }; const fallbackLang = 'en', maxZoomLevel = 19; // see also getScaleFromZoom const options = { plusCode: false, ch1903: true }; const version = "2024-12-31"; const classes = { copyMarker: 'voy-copy-marker', idRegex: /vCard_.+/, // see [[Module:Marker_utilities/i18n]], texts.anchor listingTooltip: 'listing-tooltip', listingTooltipMobile: 'listing-tooltip-mobile', withoutMarker: 'voy-without-marker' }; const selectors = { kartographerLink: '.mw-kartographer-maplink', latitude: '.p-latitude', longitude: '.p-longitude', lCoordinates: '.listing-coordinates', lEditButton: '.listing-edit-button button', lInfoButton: '.listing-info-button button', lMap: '.listing-map, .listing-without-marker', lName: '.listing-name', vcard: '.vcard' }; const data = { color: 'data-color', id: 'data-tooltip-id', lat: 'data-lat', lon: 'data-lon', mAttribute: 'data-copy-marker-attribute', mContent: 'data-copy-marker-content', name: 'data-name', region: 'data-region', wikilang: 'data-wikilang', zoom: 'data-zoom', version: 'data-version' }; // internal use const pageLang = mw.config.get( 'wgPageContentLanguage' ), userLang = mw.config.get( 'wgUserLanguage' ), // isMobile = window.matchMedia( '(any-pointer: coarse)' ).matches && // has touch screen or similar // !window.matchMedia( '(any-pointer: fine)' ).matches, // has mouse isMobile = ( /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test( navigator.userAgent.toLowerCase() ) ), timeouts = []; var messages = {}; function addMessages( str, chain ) { for ( var i = chain.length - 1; i >= 0; i-- ) { if ( str.hasOwnProperty( chain[ i ] ) ) { $.extend( messages, strings[ chain[ i ] ] ); } } } function setupMessages() { const chain = ( userLang == pageLang ) ? [ pageLang, fallbackLang ] : [ userLang, pageLang, fallbackLang ]; addMessages( strings, chain ); } // Only n digits function round( coord, n ) { const m = Math.pow( 10, n ); return Math.round( coord * m ) / m; } // Converting decimal to DMS coordinates function toDMS( dec, letters ) { const letter = letters.charAt( ( dec >= 0 ) ? 0 : 1 ); const angle = Math.abs( dec ); var deg = Math.floor( angle ); var min = ( angle - deg ) * 60; var 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 function toCH1903( lat, lon ) { const ch1903 = { easting: 0, northing: 0, error: true }; if ( lat < 45.5 || lat > 48 || lon < 5.0 || lon > 11 ) { return ch1903; } const phi = ( lat * 3600 - 169028.66 ) / 10000; const phi2 = phi * phi; const lambda = ( lon * 3600 - 26782.5 ) / 10000; const 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 function toPlusCode( lat, lon ) { const codeChars = '23456789CFGHJMPQRVWX'; const 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 for ( var i = 0; i < 5; i++ ) { const res = resolutions[ i ]; var 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 const row = Math.floor( 5 * modLat / resolutions[ 4 ] ); const col = Math.floor( 4 * modLon / resolutions[ 4 ] ); code += codeChars.charAt( 4 * row + col ); return code; } function shortenCoordinate( coord ) { return Math.round( coord * 1E5 ) / 1E5; // only 5 decimals } // zoom level 19 -> 1:1000, 0 -> 500000000 function getScaleFromZoom( zoom ) { const 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 ]; } else if ( zoom <= 0 ) { return scales[ scales.length - 1 ]; } return scales[ maxZoomLevel - Math.round( zoom ) ]; } function copyToClipboard( selector, container ) { const clipboard = $( '<textarea id="mkClipboard"></textarea>' ) .css( { 'width': 1, 'border': 'none', 'opacity': 0 } ); $( 'body' ).append( clipboard ); const text = ( selector !== '.mkClip5' ) ? $( selector, container ).text() : $( '#anchorId', container ).text(); clipboard.val( text ).select(); document.execCommand( 'copy' ); clipboard.remove(); } function clipboardLink( aClass ) { return aClass ? `[ <a href="javascript:" class="${aClass}" title="${messages.clipboardTitle}">${messages.clipboard}</a> ]` : ''; } function makeTableRow( label, title, clipClass, buttonClass, text ) { return `<tr><td><span title="${title}">${label}:</span> <span class="${clipClass}">${text}</span></td><td>${clipboardLink( buttonClass )}</td></tr>`; } function makeContent( $origin ) { var link = $( selectors.kartographerLink, $origin ).first(); var lat, lon, withMarker, zoom; if ( link.length ) { lat = round( link.attr( data.lat ), 6 ); lon = round( link.attr( data.lon ), 6 ); zoom = link.attr( data.zoom ); withMarker = true; } else { link = $origin.closest( selectors.vcard ).find( selectors.lCoordinates ); lat = round( $( selectors.latitude, link ).text(), 6 ); lon = round( $( selectors.longitude, link ).text(), 6 ); zoom = 16; withMarker = false; } const latStr = toDMS( lat, messages.NS ); const lonStr = toDMS( lon, messages.EW ); const wrapper = $origin.closest( selectors.vcard ); const color = wrapper.attr( data.color ); const lang = wrapper.attr( data.wikilang ); var region = wrapper.attr( data.region ); if ( !region ) { region = ''; } var name = wrapper.attr( data.name ); if ( !name ) { name = $( selectors.lName, wrapper ).first(); var wikiLink = $( 'a', name ).first(); name = ( wikiLink.length ) ? wikiLink.text() : name = name.text(); } name = encodeURI( name.replace( /\s/g, '+' ) ).replace( /&/g, '%26' ); var params = '¶ms=' + Math.abs( lat ) + ( lat < 0 ? '_S_' : '_N_' ) + Math.abs( lon ) + ( lon < 0 ? '_W' : '_E' ) + `_scale%3A${getScaleFromZoom( zoom )}_type%3Alandmark_globe%3Aearth`; if ( region !== '' ) { params += '_region%3A' + region; } const ch1903 = toCH1903( lat, lon ), plusCode = toPlusCode( lat, lon ), latDec = shortenCoordinate( lat ), lonDec = shortenCoordinate( lon ); var table = '<table>' + makeTableRow( messages.hex, messages.hexTitle, 'mkClip1', 'mkButton1', latStr + ' ' + lonStr ) + makeTableRow( messages.dec, messages.decTitle, 'mkClip2', 'mkButton2', `<a href="geo:${latDec},${lonDec}" title="${messages.geoUriTitle}">${latDec}, ${lonDec}</a>` ); if ( options.plusCode ) { table += makeTableRow( messages.plus, messages.plusTitle, 'mkClip3', 'mkButton3', `<span class="voy-mkTooltipPlusCode">${plusCode.substr( 0, 4 ) }</span>` + plusCode.substr( 4 ) ); } if ( options.ch1903 && !ch1903.error ) { table += makeTableRow( messages.ch1903, messages.ch1903Title, 'mkClip4', 'mkButton4', `<span title="CH1903 easting">${ch1903.easting}</span> / <span title="CH1903 northing">${ch1903.northing}</span>` ); } const html = [], infobutton = $( selectors.lInfoButton, wrapper ).prop( 'outerHTML' ) || ''; if ( infobutton !== '' ) { html.push( `<span id="infobutton">${infobutton}</span>` ); } const editbutton = $( selectors.lEditButton, wrapper ).prop( 'outerHTML' ) || ''; if ( editbutton !== '' ) { html.push( `<span id="editbutton">${editbutton}</span>` ); } let id = wrapper.attr( 'id' ); id = id && id.match( classes.idRegex ); if ( id ) { var anchor = `<a href="#" id="anchorIdLink" title="${messages.anchorTitle}">${messages.anchor}</a>` + `<span id="anchorId" style="display: none">${id}</span>`; html.push( anchor ); } if ( html.length ) { table += makeTableRow( messages.tools, messages.toolsTitle, 'mkClip5', id ? 'mkButton5' : null, html.join( ' | ' ) ); } table += '</table>'; var mapSources = `<div title="${messages.mapSourcesTitle}">${messages.mapSources}: ` + `<a href="${messages.voyURL + params}&locname=${name}" title="${messages.voyTitle}" target="_blank" rel="noopener">${messages.voy}</a> | ` + `<a href="${messages.wmflabsURL}?pagename=${name}&language=${lang + params}" title="${messages.wmflabsTitle}" target="_blank" rel="noopener">${messages.wmflabs}</a>` + '</div>'; return $( '<div class="voy-mkTooltipInner"></div>' ) .css( 'border-left-color', color ) .append( $( '<div class="voy-mkTooltipHint">' + (withMarker ? messages.hint : messages.hint2 ) + '</div>' ) .css( { 'margin-bottom': '0.5em' } ) ) .append( $( table ) ) .append( $( mapSources ) ) .append( $( '<div class="voy-mkTooltipTail"></div>' ) ); } // setting tooltip position function setTooltipPosition( e, tooltip, $this ) { const tail = $( '.voy-mkTooltipTail', tooltip ); const winWidth = $( window ).width(); var left, offset, right, width; if ( e.clientY < $( window ).height() / 2 ) { tooltip.css( 'top', $this.innerHeight() - 4 ) .addClass('voy-mkBelow'); } else { tooltip.css( 'bottom', $this.innerHeight() - 4 ) .addClass('voy-mkAbove'); } if ( e.clientX < winWidth / 2 ) { tooltip.css( 'left', $this.innerWidth() / 2 - 16 ) .addClass('voy-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('voy-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 ); } } } } function showMarkerTooltip( e ) { const $this = $( e.target ).closest( '.' + classes.listingTooltip ), id = $this.attr( data.id ), isCopyMarker = $this.hasClass( classes.copyMarker ), wrapper = $this.closest( selectors.vcard ), withoutMarker = wrapper.hasClass( classes.withoutMarker ), triggerWrapper = withoutMarker || !isCopyMarker; var $origin = $this; e.stopPropagation(); if ( isCopyMarker ) { // getting from original marker const attr = $this.attr( data.mAttribute ); const content = $this.attr( data.mContent ); $origin = $( `*[${attr}="${content}"]` ).first(); } const tooltip = $( `<div class="voy-mkTooltip" role="tooltip" ${data.version}="${version}"/>` ) .append( makeContent( $origin ) ); if ( isMobile ) { tooltip.addClass( 'voy-mkTooltipMobile' ); } else { tooltip.hide(); // later fade-in; } $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 ); } ); $( '.mkButton5', tooltip ) .click( function() { copyToClipboard( '.mkClip5', tooltip ); } ); $( '#anchorIdLink', tooltip ) .click( function() { var alertText = messages.anchorText + $( '#anchorId', tooltip ).text(); removeAllTooltips(); alert( alertText ); } ); $( '#infobutton', tooltip ) .click( function( e ) { e.stopImmediatePropagation(); $( selectors.lInfoButton, triggerWrapper ? wrapper : $origin ).trigger( 'click' ); removeAllTooltips(); } ); $( '#editbutton', tooltip ) .click( function( e ) { e.stopImmediatePropagation(); $( selectors.lEditButton, triggerWrapper ? wrapper : $origin ).trigger( 'click' ); removeAllTooltips(); } ); if ( isMobile ) { // removing tooltip after 10 sec in mobile mode timeouts[ id ] = setTimeout( function() { removeTooltip( $this ); }, 10000 ); $( 'body' ).click( handleOutsideClick ); } else { // fading-in hidden tooltip in desktop mode setTimeout( function() { tooltip.fadeIn( 500 ); }, 300 ); } return; } // Click event handler if clicked outside any tooltip function handleOutsideClick( event ) { if ( !$( event.target ).closest( '.voy-mkTooltip' ).length && $( '.voy-mkTooltip' ).is( ':visible' ) ) { removeAllTooltips(); } } function removeTooltip( marker ) { const id = marker.attr( data.id ); if ( id ) { clearTimeout( timeouts[ id ] ); } $( '.voy-mkTooltip', marker ).remove(); $( '.voy-mkTooltipButton', marker ).text( '▼' ); } function removeAllTooltips() { if ( isMobile ) { $( 'body' ).off( 'click', handleOutsideClick ); } var markers = $( '.' + classes.listingTooltip ).add( $( 'abbr' ) ); markers.each( function() { removeTooltip( $( this ) ); }); } function showMobileMarker( e ) { const $this = $( e.target ).closest( '.voy-mkTooltipButton' ); const text = $this.text(); removeAllTooltips(); if ( text === '▼' ) { $this.text( '▲' ); showMarkerTooltip( e ); } } function initMarkerTooltip() { $( selectors.lMap ).addClass( classes.listingTooltip ) .css( { position: 'relative' } ); if ( isMobile ) { $( selectors.lMap ).addClass( classes.listingTooltipMobile ); } const markers = $( '.' + classes.listingTooltip ) .attr( 'title', '' ); var id = 0; // setting id for timeout handler markers.each( function() { $( this ).attr( data.id, 'tt' + id ); id += 1; } ); if ( isMobile ) { const mobileMarker = $( '<span class="voy-mkTooltipButton">▼</span>' ) .click( function( e ) { showMobileMarker( e ); }); markers.append( mobileMarker ); } else { markers.mouseenter( function( e ) { showMarkerTooltip( e ); }) .mouseleave( function( e ) { $( '.voy-mkTooltip' ).remove(); }); } } function initAbbrTooltip() { const 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 ) { e.stopPropagation(); const $this = $( e.target ).closest( 'abbr' ); const id = $this.attr( data.id ); var tooltip = $( '.voy-mkTooltip', $this ); removeAllTooltips(); if ( tooltip.length === 0 ) { const title = $this.attr( 'title' ); if ( title ) { const div = $( `<div class="voy-mkTooltipInner voy-mkTooltipMaxWidth">${title}</div>` ) .append( $( '<div class="voy-mkTooltipTail"></div>' ) ); tooltip = $( '<div class="voy-mkTooltip voy-mkTooltipMobile" role="tooltip"></div>' ) .append( div ); $this.append( tooltip ); setTooltipPosition( e, tooltip, $this ); timeouts[ id ] = setTimeout( function() { removeTooltip( $this ); }, 10000 ); $( 'body' ).click( handleOutsideClick ); } } } ); } function init() { setupMessages(); initMarkerTooltip(); if ( isMobile ) { initAbbrTooltip(); } } return { init: init }; } (); $( mkTooltip.init ); } ( jQuery ) ); //</nowiki>