MediaWiki:Carte-osm.js
Note : après avoir publié vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.
- Firefox / Safari : maintenez la touche Maj (Shift) en cliquant sur le bouton Actualiser ou appuyez sur Ctrl + F5 ou Ctrl + R (⌘ + R sur un Mac).
- Google Chrome : appuyez sur Ctrl + Maj + R (⌘ + Shift + R sur un Mac).
- Edge : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl + F5.
/* Cartes OpenStreetMap de WikiColombes.
*
* Côté wikicode :
* - {{Carte OSM}} place une carte dans la page ;
* - {{Point carte|latitude|longitude|…}}, posé à côté d'un nom, dans une liste
* ou dans la première colonne d'un tableau, y ajoute ce lieu.
*
* Deux sortes de cartes :
* - la carte d'un article montre les points de cet article ; sa légende reprend
* les titres de section (niveau 3, à défaut 2) qui les précèdent ;
* - une carte d'ensemble ({{Carte OSM|sources=Article 1; Article 2}}) ne contient
* aucun point : elle lit ceux des articles cités, à chaque affichage. Rien n'est
* recopié, donc rien n'est à synchroniser ; sa légende propose un thème par
* article, nommé par le paramètre « thème » de la carte de cet article.
*
* Pastilles : pour un établissement scolaire (niveau=…), une lettre par niveau —
* M maternelle, É élémentaire, P primaire en bleu, C collège en orange, L lycée en
* vert ; pour tout autre lieu, le pictogramme du lieu (symbole=…) ou, à défaut,
* celui de son thème. Carré : établissement privé ou lieu associatif.
*
* Les utilisateurs connectés peuvent déplacer un lieu depuis sa fiche (bouton
* « Déplacer ») : le script réécrit alors les coordonnées de son {{Point carte}}
* dans l'article où il est défini, même depuis une carte d'ensemble.
*
* Bibliothèque : Leaflet 1.9.4, hébergée sur le serveur du wiki
* (/ressources/leaflet/1.9.4/) pour ne dépendre d'aucun CDN tiers.
* Fond de carte : tuiles OpenStreetMap standard, attribution obligatoire.
* Styles : bloc « Cartes OpenStreetMap » de MediaWiki:Common.css.
*/
( function () {
'use strict';
if ( mw.carteOsm ) {
return; // déjà chargé (aperçu de modification, etc.)
}
mw.carteOsm = {};
var LEAFLET = '/ressources/leaflet/1.9.4/';
var TUILES = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
var ATTRIBUTION = '© <a href="https://www.openstreetmap.org/copyright">les contributeurs d’OpenStreetMap</a>';
var NIVEAUX = {
M: { nom: 'École maternelle', famille: 'ecole' },
'É': { nom: 'École élémentaire', famille: 'ecole' },
P: { nom: 'École primaire', famille: 'ecole' },
C: { nom: 'Collège', famille: 'college' },
L: { nom: 'Lycée', famille: 'lycee' }
};
var ORDRE = [ 'M', 'É', 'P', 'C', 'L' ];
var HAUTEUR_PX = 26; // hauteur d'un marqueur (pastille de 22 px + liseré)
var MARGE_PX = 3; // écart minimal entre deux marqueurs voisins
var reduit = window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
var compteur = 0; // identifiant unique de chaque point, toutes cartes confondues
/* ---------- utilitaires ---------- */
function echapper( s ) {
return String( s ).replace( /[&<>"']/g, function ( c ) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ c ];
} );
}
function simplifier( s ) {
return String( s ).toLowerCase().normalize( 'NFD' ).replace( /[̀-ͯ]/g, '' ).replace( /[^a-z0-9]+/g, ' ' ).trim();
}
function lireNiveaux( texte ) {
var vus = [];
String( texte || '' ).toUpperCase().split( /[\s,;\/]+/ ).forEach( function ( n ) {
if ( n === 'E' ) {
n = 'É';
}
if ( NIVEAUX[ n ] && vus.indexOf( n ) === -1 ) {
vus.push( n );
}
} );
return vus.sort( function ( a, b ) {
return ORDRE.indexOf( a ) - ORDRE.indexOf( b );
} );
}
/* Les pastilles d'un point : lettres de niveau pour une école, sinon un pictogramme. */
function pucesDe( p ) {
var carre = p.statut !== 'public';
if ( p.niveaux.length ) {
return p.niveaux.map( function ( n ) {
return { texte: n, classe: 'carte-osm-' + NIVEAUX[ n ].famille, carre: carre, rang: ORDRE.indexOf( n ) };
} );
}
return [ { texte: p.symbole || '•', classe: 'carte-osm-symbole', carre: carre, rang: 10 } ];
}
function htmlPuce( u ) {
return '<span class="carte-osm-pastille ' + u.classe + ( u.carre ? ' carte-osm-prive' : '' ) +
'" aria-hidden="true">' + echapper( u.texte ) + '</span>';
}
/* Réunit des pastilles sans doublon, dans un ordre stable. */
function fusionnerPuces( listes, ignorerForme ) {
var vues = {}, res = [];
listes.forEach( function ( liste ) {
liste.forEach( function ( u ) {
var cle = u.texte + '|' + u.classe + ( ignorerForme ? '' : '|' + u.carre );
if ( !vues[ cle ] ) {
vues[ cle ] = true;
res.push( u );
}
} );
} );
return res.sort( function ( a, b ) {
return a.rang - b.rang || ( a.carre === b.carre ? 0 : a.carre ? 1 : -1 );
} );
}
function pastillesDe( p ) {
return pucesDe( p ).map( htmlPuce ).join( '' );
}
function texteTitre( h ) {
var cible = h.querySelector( '.mw-headline' ) || h;
return cible.textContent.replace( /\s*\([^)]*\)\s*$/, '' ).trim();
}
function lienValable( a ) {
return !a.closest( '.reference, .point-carte-voir, .mw-cite-backlink, sup' ) &&
!a.classList.contains( 'external' ) && !a.classList.contains( 'mw-file-description' );
}
/* Le nom d'un lieu : le lien le plus proche qui le précède dans la même cellule
(ou le même élément de liste) ; à défaut, le premier lien de la ligne ; à défaut,
le texte. Du lien, on garde le titre de la page quand il contient le texte
affiché (« Ambroise Paré » → « École maternelle Ambroise Paré »), sinon le texte
(« Médiathèque de la Marine », lien vers « Médiathèques de Colombes »). */
function lireNom( el ) {
var force = ( el.getAttribute( 'data-nom' ) || '' ).trim(),
cellule = el.closest( 'td, th, li' ) || el.parentNode,
ligne = el.closest( 'tr, li' ) || cellule,
lien = null;
Array.prototype.forEach.call( cellule.querySelectorAll( 'a' ), function ( a ) {
if ( lienValable( a ) && ( a.compareDocumentPosition( el ) & Node.DOCUMENT_POSITION_FOLLOWING ) ) {
lien = a;
}
} );
if ( !lien ) {
lien = Array.prototype.filter.call( ligne.querySelectorAll( 'a' ), lienValable )[ 0 ] || null;
}
if ( lien ) {
var texte = lien.textContent.trim(),
titre = ( lien.getAttribute( 'title' ) || texte ).replace( /\s*\(page inexistante\)$/, '' ).trim();
return {
complet: force || ( simplifier( titre ).indexOf( simplifier( texte ) ) !== -1 ? titre : texte ),
lien: lien.getAttribute( 'href' ),
rouge: lien.classList.contains( 'new' ),
force: !!force
};
}
var copie = cellule.cloneNode( true );
copie.querySelectorAll( 'sup, ul, ol, .point-carte-voir' ).forEach( function ( x ) {
x.remove();
} );
var brut = copie.textContent.replace( /\s*\(.*$/, '' ).trim();
return { complet: force || brut, lien: null, rouge: false, force: !!force };
}
/* Sans lien, « Guy de Maupassant » devient « Lycée Guy de Maupassant ». */
function nomComplet( p ) {
if ( p.nom.lien || p.nom.force || !p.niveaux.length ||
/^(École|Ecole|Collège|Lycée|Groupe|Institut|Cité)\b/i.test( p.nom.complet ) ) {
return p.nom.complet;
}
return NIVEAUX[ p.niveaux[ p.niveaux.length - 1 ] ].nom + ' ' + p.nom.complet;
}
function detail( p, ensemble ) {
var morceaux = [];
if ( ensemble && p.theme ) {
morceaux.push( p.theme.nom );
}
if ( p.niveaux.length ) {
morceaux.push( 'Enseignement ' + p.statut );
} else if ( p.statut === 'associatif' ) {
morceaux.push( 'Lieu associatif' );
} else if ( p.statut === 'privé' ) {
morceaux.push( 'Lieu privé' );
}
if ( p.adresse ) {
morceaux.push( p.adresse );
}
return morceaux.map( echapper ).join( ' · ' );
}
/* ---------- lecture des points d'une page ---------- */
/* racine : le contenu d'une page (celle qu'on lit, ou une page source chargée par
l'API) ; source : son titre, ou null pour la page affichée. Le rang d'un point
est son numéro d'ordre parmi les {{Point carte}} de sa page : c'est lui qui
désigne le modèle à réécrire quand on déplace le lieu. */
function lirePoints( racine, source, theme ) {
var points = [], sections = [], index = {}, courante = null, rang = 0;
racine.querySelectorAll( 'h2, h3, .point-carte' ).forEach( function ( el ) {
if ( !el.classList.contains( 'point-carte' ) ) {
courante = texteTitre( el );
return;
}
var rangIci = rang++,
lat = parseFloat( el.getAttribute( 'data-lat' ) ),
lon = parseFloat( el.getAttribute( 'data-lon' ) );
if ( isNaN( lat ) || isNaN( lon ) ) {
return;
}
var statut = ( el.getAttribute( 'data-statut' ) || '' ).toLowerCase(),
cle = courante || 'Autres lieux';
if ( !index[ cle ] ) {
index[ cle ] = { nom: cle, points: [], actif: true };
sections.push( index[ cle ] );
}
var p = {
id: compteur++,
lat: lat,
lon: lon,
niveaux: lireNiveaux( el.getAttribute( 'data-niveau' ) ),
statut: /priv/.test( statut ) ? 'privé' : /assoc/.test( statut ) ? 'associatif' : 'public',
adresse: el.getAttribute( 'data-adresse' ) || '',
symbole: el.getAttribute( 'data-symbole' ) || ( theme && theme.symbole ) || '',
section: index[ cle ],
theme: theme,
source: source,
el: el,
rang: rangIci
};
p.nom = lireNom( el );
index[ cle ].points.push( p );
points.push( p );
} );
return { points: points, sections: sections };
}
function themeDe( racine, titre ) {
var c = racine.querySelector( '.carte-osm' );
return {
nom: ( c && c.getAttribute( 'data-theme' ) ) || titre,
symbole: ( c && c.getAttribute( 'data-symbole' ) ) || '',
page: titre,
points: [],
actif: true
};
}
/* Charge une page source par l'API et en lit les points. */
function chargerSource( titre ) {
return mw.loader.using( 'mediawiki.api' ).then( function () {
return new mw.Api().get( {
action: 'parse',
page: titre,
prop: 'text',
redirects: 1,
disableeditsection: 1,
disablelimitreport: 1,
formatversion: 2
} );
} ).then( function ( r ) {
var doc = new DOMParser().parseFromString( r.parse.text, 'text/html' ),
theme = themeDe( doc, r.parse.title ),
lus = lirePoints( doc, r.parse.title, theme );
theme.points = lus.points;
return theme;
}, function () {
return { nom: titre, page: titre, points: [], actif: true, erreur: true };
} );
}
/* ---------- construction d'une carte ---------- */
function construire( conteneur, racine ) {
var sources = ( conteneur.getAttribute( 'data-sources' ) || '' ).split( /\s*;\s*/ ).map( function ( s ) {
return s.trim();
} ).filter( Boolean );
if ( !sources.length ) {
var themeLocal = themeDe( racine, mw.config.get( 'wgTitle' ) ),
lus = lirePoints( racine, null, themeLocal );
if ( lus.points.length ) {
dessinerCarte( conteneur, lus.points, lus.sections, false, [] );
}
return;
}
conteneur.classList.add( 'carte-osm-prete' );
$.when.apply( $, sources.map( chargerSource ) ).then( function () {
var themes = Array.prototype.slice.call( arguments ),
points = [], manquants = [];
themes.forEach( function ( t ) {
if ( t.erreur || !t.points.length ) {
manquants.push( t.page );
}
points = points.concat( t.points );
t.points.forEach( function ( p ) {
p.section = t; // dans une carte d'ensemble, la légende filtre par thème
} );
} );
themes = themes.filter( function ( t ) {
return t.points.length;
} );
if ( points.length ) {
dessinerCarte( conteneur, points, themes, true, manquants );
} else {
var repli = conteneur.querySelector( '.carte-osm-repli' );
if ( repli ) {
repli.textContent = 'Aucun lieu trouvé dans les articles sources.';
}
}
} );
}
/* points : les lieux ; filtres : les entrées de la légende (sections d'un article,
ou thèmes d'une carte d'ensemble) ; ensemble : vrai pour une carte d'ensemble. */
function dessinerCarte( conteneur, points, filtres, ensemble, manquants ) {
conteneur.classList.add( 'carte-osm-prete' );
conteneur.innerHTML = '';
var cfg = mw.config.get( [ 'wgUserName', 'wgIsProbablyEditable', 'wgAction', 'wgRevisionId', 'wgCurRevisionId', 'wgPageName', 'wgScript' ] ),
peutDeplacer = !!cfg.wgUserName && !!cfg.wgIsProbablyEditable && cfg.wgAction === 'view' &&
!!cfg.wgRevisionId && cfg.wgRevisionId === cfg.wgCurRevisionId;
/* légende et filtres, au-dessus de la carte */
var legende = document.createElement( 'div' );
legende.className = 'carte-osm-legende';
legende.setAttribute( 'role', 'group' );
legende.setAttribute( 'aria-label', 'Lieux affichés sur la carte' );
filtres.forEach( function ( s ) {
var etiquette = document.createElement( 'label' ),
caseACocher = document.createElement( 'input' ),
echantillon = ensemble ?
fusionnerPuces( s.points.map( pucesDe ), true ).slice( 0, 5 ) :
pucesDe( s.points[ 0 ] );
caseACocher.type = 'checkbox';
caseACocher.checked = true;
caseACocher.setAttribute( 'aria-label', s.nom + ' (' + s.points.length + ')' );
caseACocher.addEventListener( 'change', function () {
s.actif = caseACocher.checked;
dessiner();
} );
s.caseACocher = caseACocher;
etiquette.appendChild( caseACocher );
etiquette.insertAdjacentHTML( 'beforeend',
'<span class="carte-osm-puces">' + echantillon.map( htmlPuce ).join( '' ) + '</span>' +
'<span>' + echapper( s.nom ) + '</span>' +
'<span class="carte-osm-compte">' + s.points.length + '</span>' );
legende.appendChild( etiquette );
} );
if ( ensemble && points.some( function ( p ) {
return p.statut !== 'public';
} ) ) {
legende.insertAdjacentHTML( 'beforeend', '<span class="carte-osm-note">' +
'<span class="carte-osm-pastille carte-osm-symbole carte-osm-prive" aria-hidden="true"></span>' +
'carré : privé ou associatif</span>' );
}
if ( manquants.length ) {
legende.insertAdjacentHTML( 'beforeend', '<span class="carte-osm-note">Sans lieu ou introuvable : ' +
manquants.map( echapper ).join( ', ' ) + '</span>' );
}
var fond = document.createElement( 'div' );
fond.className = 'carte-osm-fond';
fond.setAttribute( 'aria-label', conteneur.getAttribute( 'data-titre' ) || 'Carte' );
conteneur.appendChild( legende );
conteneur.appendChild( fond );
var carte = L.map( fond, {
zoomControl: false,
scrollWheelZoom: false, // la molette fait défiler la page tant qu'on n'a pas cliqué la carte
minZoom: 12,
maxZoom: 19,
zoomAnimation: !reduit,
fadeAnimation: !reduit,
markerZoomAnimation: !reduit
} );
L.tileLayer( TUILES, { maxZoom: 19, attribution: ATTRIBUTION } ).addTo( carte );
carte.attributionControl.setPrefix( '<a href="https://leafletjs.com/">Leaflet</a>' );
L.control.zoom( { zoomInTitle: 'Zoom avant', zoomOutTitle: 'Zoom arrière' } ).addTo( carte );
var limites;
function calculerLimites() {
limites = L.latLngBounds( points.map( function ( q ) {
return [ q.lat, q.lon ];
} ) );
}
calculerLimites();
function cadrer() {
carte.fitBounds( limites, { padding: [ 24, 24 ] } );
}
var ToutVoir = L.Control.extend( {
options: { position: 'topleft' },
onAdd: function () {
var bouton = L.DomUtil.create( 'button', 'carte-osm-toutvoir' );
bouton.type = 'button';
bouton.textContent = 'Tout voir';
bouton.title = 'Revenir à la vue d’ensemble';
L.DomEvent.disableClickPropagation( bouton );
L.DomEvent.on( bouton, 'click', cadrer );
return bouton;
}
} );
new ToutVoir().addTo( carte );
carte.on( 'focus click', function () {
carte.scrollWheelZoom.enable();
} );
carte.on( 'blur mouseout', function () {
carte.scrollWheelZoom.disable();
} );
/* ---------- regroupement des lieux trop proches à l'échelle affichée ---------- */
var calque = L.layerGroup().addTo( carte ),
groupes = [];
function ordre( p ) {
return ( p.niveaux.length ? ORDRE.indexOf( p.niveaux[ 0 ] ) : 10 ) * 2 + ( p.statut === 'public' ? 0 : 1 );
}
function bulle( g ) {
var html = '<div class="carte-osm-bulle"><ul>';
g.membres.slice().sort( function ( a, b ) {
return ordre( a ) - ordre( b ) || nomComplet( a ).localeCompare( nomComplet( b ), 'fr' );
} ).forEach( function ( p ) {
var nom = echapper( nomComplet( p ) ), ligne = detail( p, ensemble );
if ( p.nom.lien ) {
nom = '<a href="' + echapper( p.nom.lien ) + '"' + ( p.nom.rouge ? ' class="new"' : '' ) + '>' + nom + '</a>';
}
html += '<li><span class="carte-osm-puces">' + pastillesDe( p ) + '</span><span>' +
'<span class="carte-osm-nom">' + nom + '</span>' +
( ligne ? '<span class="carte-osm-detail">' + ligne + '</span>' : '' ) +
( peutDeplacer ? '<button type="button" class="carte-osm-deplacer" data-id="' + p.id + '">Déplacer</button>' : '' ) +
'</span></li>';
} );
html += '</ul>';
if ( g.etendu ) {
html += '<button type="button" class="carte-osm-zoom">Zoomer sur ces ' + g.membres.length + ' lieux</button>';
}
return html + '</div>';
}
/* Un groupe = les lieux qui se chevaucheraient à l'écran. Son marqueur aligne
leurs pastilles, d'où une largeur variable : on compare donc les boîtes
réelles des marqueurs, pas seulement leurs centres, et on fusionne tant
qu'il reste un chevauchement. */
function majGroupe( g ) {
var x = 0, y = 0;
g.membres.forEach( function ( p ) {
x += p.px.x / g.membres.length;
y += p.px.y / g.membres.length;
} );
g.px = L.point( x, y );
g.puces = fusionnerPuces( g.membres.map( pucesDe ), false );
g.largeur = g.puces.length * 24 + 2;
}
function chevauchent( a, b ) {
return Math.abs( a.px.x - b.px.x ) < ( a.largeur + b.largeur ) / 2 + MARGE_PX &&
Math.abs( a.px.y - b.px.y ) < HAUTEUR_PX + MARGE_PX;
}
function dessiner() {
calque.clearLayers();
groupes = points.filter( function ( p ) {
return p.section.actif;
} ).map( function ( p ) {
p.px = carte.latLngToLayerPoint( [ p.lat, p.lon ] );
var g = { membres: [ p ] };
majGroupe( g );
return g;
} );
var i, j, fusion = true;
while ( fusion ) {
fusion = false;
for ( i = 0; i < groupes.length && !fusion; i++ ) {
for ( j = i + 1; j < groupes.length; j++ ) {
if ( chevauchent( groupes[ i ], groupes[ j ] ) ) {
groupes[ i ].membres = groupes[ i ].membres.concat( groupes[ j ].membres );
majGroupe( groupes[ i ] );
groupes.splice( j, 1 );
fusion = true;
break;
}
}
}
}
groupes.forEach( function ( g ) {
g.limites = L.latLngBounds( g.membres.map( function ( p ) {
return [ p.lat, p.lon ];
} ) );
g.etendu = g.membres.length > 1 &&
carte.distance( g.limites.getSouthWest(), g.limites.getNorthEast() ) > 5;
var noms = g.membres.map( nomComplet ).join( ', ' ),
html = '<span class="carte-osm-marque">' + g.puces.map( htmlPuce ).join( '' ) +
( g.membres.length > g.puces.length ? '<span class="carte-osm-nombre">' + g.membres.length + '</span>' : '' ) +
'</span>';
g.marque = L.marker( carte.layerPointToLatLng( g.px ), {
icon: L.divIcon( { className: 'carte-osm-icone', html: html, iconSize: null } ),
title: noms,
keyboard: true,
riseOnHover: true
} ).bindPopup( bulle( g ), { maxWidth: 300, minWidth: 200, maxHeight: 280 } ).addTo( calque );
var icone = g.marque.getElement();
if ( icone ) {
icone.setAttribute( 'aria-label', noms );
}
g.marque.on( 'popupopen', function ( e ) {
var element = e.popup.getElement(),
fermer = element.querySelector( '.leaflet-popup-close-button' ),
bouton = element.querySelector( '.carte-osm-zoom' );
if ( fermer ) {
fermer.setAttribute( 'aria-label', 'Fermer' );
fermer.title = 'Fermer';
}
if ( bouton ) {
bouton.addEventListener( 'click', function () {
carte.closePopup();
carte.fitBounds( g.limites, { padding: [ 48, 48 ], maxZoom: 18 } );
} );
}
element.querySelectorAll( '.carte-osm-deplacer' ).forEach( function ( b ) {
b.addEventListener( 'click', function () {
var vise = Number( b.getAttribute( 'data-id' ) );
g.membres.forEach( function ( p ) {
if ( p.id === vise ) {
commencerDeplacement( p );
}
} );
} );
} );
} );
} );
}
carte.on( 'zoomend', dessiner );
/* ---------- déplacer un lieu (utilisateurs connectés) ----------
« Déplacer » dans la fiche d'un lieu, puis un clic sur la carte à l'endroit
voulu ; le repère provisoire s'ajuste à la souris avant d'enregistrer.
L'enregistrement réécrit les coordonnées du n-ième {{Point carte}} de
l'article où le lieu est défini — la page affichée, ou l'article source
d'une carte d'ensemble : c'est une modification ordinaire de cet article
(historique, résumé, annulation possible). Si l'article a changé depuis son
affichage, rien n'est écrit. */
var bandeau = null, enCours = null;
function afficherBandeau( html ) {
if ( !bandeau ) {
bandeau = L.DomUtil.create( 'div', 'carte-osm-bandeau', fond );
bandeau.setAttribute( 'role', 'status' );
L.DomEvent.disableClickPropagation( bandeau );
L.DomEvent.disableScrollPropagation( bandeau );
}
bandeau.innerHTML = html;
bandeau.hidden = false;
var fermer = bandeau.querySelector( '.carte-osm-annuler' );
if ( fermer ) {
fermer.addEventListener( 'click', annuler );
}
return bandeau;
}
function arreterDeplacement() {
if ( enCours && enCours.repere ) {
carte.removeLayer( enCours.repere );
}
carte.off( 'click', viser );
fond.classList.remove( 'carte-osm-visee' );
enCours = null;
}
function annuler() {
arreterDeplacement();
if ( bandeau ) {
bandeau.hidden = true;
}
}
function commencerDeplacement( p ) {
arreterDeplacement();
carte.closePopup();
enCours = { p: p, repere: null };
fond.classList.add( 'carte-osm-visee' );
afficherBandeau( 'Cliquez sur la carte à l’emplacement de <b>' + echapper( nomComplet( p ) ) + '</b>. ' +
'<button type="button" class="carte-osm-annuler">Annuler</button>' );
carte.on( 'click', viser );
}
function viser( e ) {
if ( !enCours ) {
return;
}
carte.off( 'click', viser );
fond.classList.remove( 'carte-osm-visee' );
var p = enCours.p;
enCours.repere = L.marker( e.latlng, {
draggable: true,
zIndexOffset: 1000,
title: 'Nouvelle position : ' + nomComplet( p ),
icon: L.divIcon( {
className: 'carte-osm-icone',
html: '<span class="carte-osm-marque carte-osm-nouvelle">' + pastillesDe( p ) + '</span>',
iconSize: null
} )
} ).addTo( carte );
afficherBandeau( 'Nouvelle position de <b>' + echapper( nomComplet( p ) ) + '</b> : ' +
'<span class="carte-osm-coord"></span> ' +
'<span class="carte-osm-aide">— faites glisser le repère pour l’ajuster.</span> ' +
'<button type="button" class="carte-osm-enregistrer">Enregistrer</button> ' +
'<button type="button" class="carte-osm-annuler">Annuler</button>' );
function coord() {
var ll = enCours.repere.getLatLng();
bandeau.querySelector( '.carte-osm-coord' ).textContent = ll.lat.toFixed( 6 ) + ', ' + ll.lng.toFixed( 6 );
}
coord();
enCours.repere.on( 'drag dragend', coord );
bandeau.querySelector( '.carte-osm-enregistrer' ).addEventListener( 'click', enregistrer );
}
function enregistrer() {
var p = enCours.p,
page = p.source || cfg.wgPageName,
ll = enCours.repere.getLatLng(),
lat = ll.lat.toFixed( 6 ),
lon = ll.lng.toFixed( 6 ),
ancienLat = parseFloat( p.el.getAttribute( 'data-lat' ) ),
ancienLon = parseFloat( p.el.getAttribute( 'data-lon' ) );
enCours.repere.dragging.disable();
afficherBandeau( 'Enregistrement…' );
mw.loader.using( 'mediawiki.api' ).then( function () {
return new mw.Api().edit( page, function ( revision ) {
var n = 0, ok = false;
var texte = revision.content.replace( /(\{\{\s*[Pp]oint[ _]carte\s*\|)([^|}]*)\|([^|}]*)/g,
function ( tout, debut, la, lo ) {
if ( n++ !== p.rang ) {
return tout;
}
// garde-fou : le modèle visé doit porter les coordonnées affichées
if ( Math.abs( parseFloat( la ) - ancienLat ) > 1e-7 || Math.abs( parseFloat( lo ) - ancienLon ) > 1e-7 ) {
return tout;
}
ok = true;
return debut + lat + '|' + lon;
} );
if ( !ok ) {
return $.Deferred().reject( 'carte-decalee' ).promise();
}
return {
text: texte,
summary: 'Carte : nouvelle position pour ' + nomComplet( p ) + ' (' + lat + ', ' + lon + ')',
minor: true
};
} );
} ).then( function () {
p.lat = parseFloat( lat );
p.lon = parseFloat( lon );
p.el.setAttribute( 'data-lat', lat );
p.el.setAttribute( 'data-lon', lon );
calculerLimites();
arreterDeplacement();
dessiner();
afficherBandeau( '✓ Position de <b>' + echapper( nomComplet( p ) ) + '</b> enregistrée' +
( p.source ? ' dans « ' + echapper( p.source ) + ' »' : '' ) + '. ' +
'<a href="' + echapper( cfg.wgScript + '?title=' + encodeURIComponent( page ) + '&action=history' ) + '">Historique</a> ' +
'<button type="button" class="carte-osm-annuler">Fermer</button>' );
}, function ( code ) {
var message = code === 'carte-decalee' ?
'L’article a changé depuis son affichage : rechargez la page, puis recommencez.' :
'L’enregistrement a échoué (' + echapper( code ) + '). Les coordonnées peuvent aussi se corriger dans le wikicode.';
if ( enCours && enCours.repere ) {
enCours.repere.dragging.enable();
}
afficherBandeau( message + ' <button type="button" class="carte-osm-annuler">Fermer</button>' );
} );
}
if ( peutDeplacer ) {
document.addEventListener( 'keydown', function ( e ) {
if ( e.key === 'Escape' && enCours ) {
annuler();
}
} );
}
/* ---------- « situer sur la carte » depuis la page (points de la page seulement) ---------- */
points.forEach( function ( p ) {
if ( p.source || ( p.el.nextElementSibling && p.el.nextElementSibling.classList.contains( 'point-carte-voir' ) ) ) {
return;
}
var bouton = document.createElement( 'button' );
bouton.type = 'button';
bouton.className = 'point-carte-voir';
bouton.textContent = '📍';
bouton.title = 'Situer sur la carte';
bouton.setAttribute( 'aria-label', 'Situer ' + nomComplet( p ) + ' sur la carte' );
bouton.addEventListener( 'click', function () {
if ( !p.section.actif ) {
p.section.actif = true;
p.section.caseACocher.checked = true;
}
conteneur.scrollIntoView( { behavior: reduit ? 'auto' : 'smooth', block: 'center' } );
var cible = L.latLng( p.lat, p.lon ), zoom = Math.max( carte.getZoom(), 17 );
function ouvrir() {
dessiner();
groupes.forEach( function ( g ) {
if ( g.membres.indexOf( p ) !== -1 ) {
g.marque.openPopup();
}
} );
}
if ( carte.getZoom() === zoom && carte.getCenter().distanceTo( cible ) < 1 ) {
ouvrir();
} else {
carte.once( 'moveend', ouvrir );
carte.setView( cible, zoom, { animate: !reduit } );
}
} );
p.el.insertAdjacentElement( 'afterend', bouton );
} );
/* une carte dans une section repliée (vue mobile) naît sans dimensions */
function montrer() {
carte.invalidateSize();
cadrer();
dessiner();
}
if ( fond.offsetWidth && fond.offsetHeight ) {
montrer();
}
if ( window.ResizeObserver ) {
var avant = fond.offsetWidth;
new ResizeObserver( function () {
if ( fond.offsetWidth && !avant ) {
montrer();
} else if ( fond.offsetWidth ) {
carte.invalidateSize();
}
avant = fond.offsetWidth;
} ).observe( fond );
}
}
/* ---------- chargement de Leaflet, puis des cartes de la page ---------- */
var leaflet = null;
function chargerLeaflet() {
if ( !leaflet ) {
mw.loader.load( LEAFLET + 'leaflet.css', 'text/css' );
leaflet = window.L ? Promise.resolve() : mw.loader.getScript( LEAFLET + 'leaflet.js' );
}
return leaflet;
}
mw.hook( 'wikipage.content' ).add( function ( $contenu ) {
var racine = $contenu && $contenu[ 0 ],
cartes = racine ? racine.querySelectorAll( '.carte-osm:not(.carte-osm-prete)' ) : [];
if ( !cartes.length ) {
return;
}
chargerLeaflet().then( function () {
cartes.forEach( function ( conteneur ) {
construire( conteneur, racine );
} );
}, function () {
cartes.forEach( function ( conteneur ) {
var repli = conteneur.querySelector( '.carte-osm-repli' );
if ( repli ) {
repli.textContent = 'La carte n’a pas pu être chargée.';
}
} );
} );
} );
}() );