JGirault has uploaded a new change for review.
https://gerrit.wikimedia.org/r/295599
Change subject: Split the JS codebase into several modules.
......................................................................
Split the JS codebase into several modules.
Previously, the module/resource "ext.kartographer.live"
contained all the dependencies, and was loaded for both
MapLink and MapFrame tags.
With this refactor:
* MapLink tag only loads "ext.kartographer.maplink"
* MapFrame tag only loads "ext.kartographer.mapframe"
* These two resources define the dependencies for each
tag.
* The dependencies for displaying an interactive map
are contained in "ext.kartographer.live"
* The dependencies for displaying a map in full screen
mode are contained in "ext.kartographer.fullscreen"
Bug: T134079
Change-Id: Ifdeb529c86709ae0890d4445afce972fa26c9521
---
M extension.json
M includes/Tag/MapFrame.php
M includes/Tag/MapLink.php
A modules/fullscreen/CloseControl.js
A modules/fullscreen/MapDialog.js
A modules/fullscreen/fullscreen.js
D modules/kartographer.MapDialog.js
M modules/kartographer.js
A modules/live/FullScreenControl.js
A modules/live/live.js
A modules/mapframe/mapframe.js
A modules/maplink/maplink.js
A modules/settings/settings.js
13 files changed, 904 insertions(+), 762 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Kartographer
refs/changes/99/295599/1
diff --git a/extension.json b/extension.json
index 9431ee6..321c337 100644
--- a/extension.json
+++ b/extension.json
@@ -129,18 +129,70 @@
"desktop"
]
},
- "ext.kartographer.live": {
+ "ext.kartographer.init": {
"dependencies": [
- "mapbox",
"ext.kartographer",
- "ext.kartographer.site",
- "mediawiki.jqueryMsg",
- "oojs-ui.styles.icons-media",
+ "mediawiki.jqueryMsg"
+ ],
+ "scripts": [
+ "modules/kartographer.js"
+ ],
+ "targets": [
+ "mobile",
+ "desktop"
+ ]
+ },
+ "ext.kartographer.maplink": {
+ "dependencies": [
+ "ext.kartographer.init",
"mediawiki.router"
],
"scripts": [
+ "modules/maplink/maplink.js"
+ ],
+ "targets": [
+ "mobile",
+ "desktop"
+ ]
+ },
+ "ext.kartographer.settings": {
+ "dependencies": [
+ "mapbox"
+ ],
+ "scripts": [
+ "modules/settings/settings.js"
+ ],
+ "targets": [
+ "mobile",
+ "desktop"
+ ]
+ },
+ "ext.kartographer.mapframe": {
+ "dependencies": [
+ "mapbox",
+ "ext.kartographer.init",
+ "mediawiki.router",
+ "ext.kartographer.live"
+ ],
+ "scripts": [
+ "modules/mapframe/mapframe.js"
+ ],
+ "targets": [
+ "mobile",
+ "desktop"
+ ]
+ },
+ "ext.kartographer.live": {
+ "dependencies": [
+ "mapbox",
+ "ext.kartographer.settings",
+ "mediawiki.router",
+ "oojs-ui.styles.icons-media"
+ ],
+ "scripts": [
"lib/leaflet.sleep.js",
- "modules/kartographer.js"
+ "modules/live/FullScreenControl.js",
+ "modules/live/live.js"
],
"messages": [
"kartographer-attribution"
@@ -152,11 +204,16 @@
},
"ext.kartographer.fullscreen": {
"dependencies": [
+ "ext.kartographer.init",
"ext.kartographer.site",
+ "ext.kartographer.live",
+ "mediawiki.router",
"oojs-ui-windows"
],
"scripts": [
- "modules/kartographer.MapDialog.js"
+ "modules/fullscreen/fullscreen.js",
+ "modules/fullscreen/CloseControl.js",
+ "modules/fullscreen/MapDialog.js"
],
"messages": [
"kartographer-fullscreen-close",
diff --git a/includes/Tag/MapFrame.php b/includes/Tag/MapFrame.php
index a7f1022..53d40e8 100644
--- a/includes/Tag/MapFrame.php
+++ b/includes/Tag/MapFrame.php
@@ -78,7 +78,7 @@
*/
case 'interactive':
- $output->addModules( 'ext.kartographer.live' );
+ $output->addModules(
'ext.kartographer.mapframe' );
$width = is_numeric( $this->width ) ?
"{$this->width}px" : $this->width;
$attrs = [
diff --git a/includes/Tag/MapLink.php b/includes/Tag/MapLink.php
index cab6e18..63ad0b5 100644
--- a/includes/Tag/MapLink.php
+++ b/includes/Tag/MapLink.php
@@ -21,7 +21,7 @@
protected function render() {
$output = $this->parser->getOutput();
- $output->addModules( 'ext.kartographer.live' );
+ $output->addModules( 'ext.kartographer.maplink' );
$interact = $output->getExtensionData( 'kartographer_interact'
);
if ( $interact === null ) {
$output->setExtensionData( 'kartographer_interact', []
);
diff --git a/modules/fullscreen/CloseControl.js
b/modules/fullscreen/CloseControl.js
new file mode 100644
index 0000000..3ab008f
--- /dev/null
+++ b/modules/fullscreen/CloseControl.js
@@ -0,0 +1,28 @@
+/*jshint unused:false*/
+/**
+ * Close control on full screen mode.
+ */
+var FullscreenCloseControl = L.Control.extend( {
+ options: {
+ position: 'topright'
+ },
+
+ onAdd: function () {
+ var container = L.DomUtil.create( 'div', 'leaflet-bar' ),
+ link = L.DomUtil.create( 'a', 'oo-ui-icon-close',
container );
+
+ this.href = '#';
+ link.title = mw.msg( 'kartographer-fullscreen-close' );
+
+ L.DomEvent.addListener( link, 'click', this.onClick, this );
+ L.DomEvent.disableClickPropagation( container );
+
+ return container;
+ },
+
+ onClick: function ( e ) {
+ L.DomEvent.stop( e );
+
+ this.options.dialog.executeAction( '' );
+ }
+} );
diff --git a/modules/fullscreen/MapDialog.js b/modules/fullscreen/MapDialog.js
new file mode 100644
index 0000000..2ca02e8
--- /dev/null
+++ b/modules/fullscreen/MapDialog.js
@@ -0,0 +1,186 @@
+/* globals require, module */
+/* globals FullscreenCloseControl */
+( function ( $, mw, CloseControl ) {
+
+ var kartoLive = require( 'ext.kartographer.live' ),
+ MwKartographerMapDialog;
+
+ /**
+ * Dialog for full screen maps
+ *
+ * @class
+ * @extends OO.ui.Dialog
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+ mw.kartographer.MapDialog = MwKartographerMapDialog = function () {
+ // Parent method
+ mw.kartographer.MapDialog.super.apply( this, arguments );
+ };
+
+ /* Inheritance */
+
+ OO.inheritClass( mw.kartographer.MapDialog, OO.ui.Dialog );
+
+ /* Static Properties */
+
+ mw.kartographer.MapDialog.static.size = 'full';
+
+ /* Methods */
+
+ mw.kartographer.MapDialog.prototype.initialize = function () {
+ // Parent method
+ mw.kartographer.MapDialog.super.prototype.initialize.apply(
this, arguments );
+
+ this.map = null;
+ this.mapData = null;
+ this.$map = null;
+ };
+
+ /**
+ * Changes the map within the map dialog.
+ *
+ * If the new map is the same at the previous map, we reuse the same map
+ * object and simply update the zoom and the center of the map.
+ *
+ * If the new map is different, we keep the dialog open and simply
+ * replace the map object with a new one.
+ *
+ * @param {Object} mapData The data for the new map.
+ * @param {Object} [mapData.fullScreenState] Optional full screen
position in
+ * which to open the map.
+ * @param {number} [mapData.fullScreenState.zoom]
+ * @param {number} [mapData.fullScreenState.latitude]
+ * @param {number} [mapData.fullScreenState.longitude]
+ */
+ mw.kartographer.MapDialog.prototype.changeMap = function ( mapData ) {
+ var fullScreenState, extendedData,
+ existing = this.mapData;
+
+ // Check whether it is the same map.
+ if ( existing &&
+ typeof existing.maptagId === 'number' &&
+ existing.maptagId === mapData.maptagId ) {
+
+ fullScreenState = mapData.fullScreenState;
+ extendedData = {};
+
+ // override with full screen state
+ $.extend( extendedData, mapData, fullScreenState );
+
+ // Use this boolean to stop listening to `moveend`
event while we're
+ // manually moving the map.
+ this.movingMap = true;
+ this.map.setView( new L.LatLng( extendedData.latitude,
extendedData.longitude ), extendedData.zoom );
+ this.map.mapData = mapData;
+ this.movingMap = false;
+ return;
+ }
+
+ this.setup.call( this, mapData );
+ };
+
+ mw.kartographer.MapDialog.prototype.getActionProcess = function (
action ) {
+ var dialog = this;
+ if ( !action ) {
+ return new OO.ui.Process( function () {
+ var router = mw.loader.require(
'mediawiki.router' );
+ if ( router.getPath() !== '' ) {
+ router.navigate( '' );
+ } else {
+ // force close
+ dialog.close( { action: action } );
+ }
+ } );
+ }
+ return
mw.kartographer.MapDialog.super.prototype.getActionProcess.call( this, action );
+ };
+
+ /**
+ * Tells the router to navigate to the current full screen map route.
+ */
+ mw.kartographer.MapDialog.prototype.updateHash = function () {
+ var router = mw.loader.require( 'mediawiki.router' ),
+ hash = mw.kartographer.getMapHash( this.mapData,
this.map );
+
+ // Avoid extra operations
+ if ( this.lastHash !== hash ) {
+ router.navigate( hash );
+ this.lastHash = hash;
+ }
+ };
+
+ /**
+ * Listens to `moveend` event and calls {@link #updateHash}.
+ *
+ * This method is throttled, meaning the method will be called at most
once per
+ * every 100 milliseconds.
+ */
+ mw.kartographer.MapDialog.prototype.onMapMove = OO.ui.throttle(
function () {
+ // Stop listening to `moveend` event while we're
+ // manually moving the map (updating from a hash),
+ // or if the map is not yet loaded.
+ /*jscs:disable disallowDanglingUnderscores */
+ if ( this.movingMap || !this.map || !this.map._loaded ) {
+ return false;
+ }
+ /*jscs:enable disallowDanglingUnderscores */
+ this.updateHash();
+ }, 100 );
+
+ mw.kartographer.MapDialog.prototype.getSetupProcess = function (
mapData ) {
+ return
mw.kartographer.MapDialog.super.prototype.getSetupProcess.call( this, mapData )
+ .next( function () {
+ var fullScreenState = mapData.fullScreenState,
+ extendedData = {};
+
+ if ( this.map ) {
+ this.map.remove();
+ this.$map.remove();
+ }
+
+ this.$map = $( '<div>' )
+ .addClass(
'mw-kartographer-mapDialog-map' )
+ .appendTo( this.$body );
+
+ this.map = kartoLive.createMap( this.$map[ 0 ],
mapData );
+ this.map.addControl( new CloseControl( {
dialog: this } ) );
+
+ // copy of the initial settings
+ this.mapData = mapData;
+
+ if ( fullScreenState ) {
+ // override with full screen state
+ $.extend( extendedData, mapData,
fullScreenState );
+ this.map.setView( new L.LatLng(
extendedData.latitude, extendedData.longitude ), extendedData.zoom, true );
+ }
+
+ if ( typeof mapData.maptagId === 'number' ) {
+ this.map.on( 'moveend', this.onMapMove,
this );
+ }
+
+ mw.hook( 'wikipage.maps' ).fire( this.map, true
/* isFullScreen */ );
+ }, this );
+ };
+
+ mw.kartographer.MapDialog.prototype.getReadyProcess = function ( data )
{
+ return
mw.kartographer.MapDialog.super.prototype.getReadyProcess.call( this, data )
+ .next( function () {
+ this.map.invalidateSize();
+ }, this );
+ };
+
+ mw.kartographer.MapDialog.prototype.getTeardownProcess = function (
data ) {
+ return
mw.kartographer.MapDialog.super.prototype.getTeardownProcess.call( this, data )
+ .next( function () {
+ this.map.remove();
+ this.$map.remove();
+ this.map = null;
+ this.mapData = null;
+ this.$map = null;
+ }, this );
+ };
+
+ module.exports = MwKartographerMapDialog;
+}( jQuery, mediaWiki, FullscreenCloseControl ) );
diff --git a/modules/fullscreen/fullscreen.js b/modules/fullscreen/fullscreen.js
new file mode 100644
index 0000000..15b4cbf
--- /dev/null
+++ b/modules/fullscreen/fullscreen.js
@@ -0,0 +1,78 @@
+/* globals require */
+( function ( $, mw ) {
+
+ var kartographer = require( 'ext.kartographer.init' ),
+ router = require( 'mediawiki.router' );
+
+ /**
+ * Get "editable" geojson layer for the map.
+ *
+ * If a layer doesn't exist, create and attach one.
+ *
+ * @param {L.mapbox.Map} map Map to get layers from
+ * @param {L.mapbox.FeatureLayer} map.kartographerLayer show
tag-specific info in this layer
+ * @return {L.mapbox.FeatureLayer|null} GeoJSON layer, if present
+ */
+ mw.kartographer.getKartographerLayer = function ( map ) {
+ if ( !map.kartographerLayer ) {
+ map.kartographerLayer = L.mapbox.featureLayer().addTo(
map );
+ }
+ return map.kartographerLayer;
+ };
+
+ /**
+ * Updates "editable" GeoJSON layer from a string.
+ *
+ * Validates the GeoJSON against the `sanitize-mapdata` api
+ * before executing it.
+ *
+ * The deferred object will be resolved with a `boolean` flag
+ * indicating whether the GeoJSON was valid and was applied.
+ *
+ * @param {L.mapbox.Map} map Map to set the GeoJSON for
+ * @param {string} geoJsonString GeoJSON data, empty string to clear
+ * @return {jQuery.Promise} Promise which resolves when the GeoJSON is
updated, and rejects if there was an error
+ */
+ mw.kartographer.updateKartographerLayer = function ( map, geoJsonString
) {
+ var deferred = $.Deferred();
+
+ if ( geoJsonString === '' ) {
+ return deferred.resolve().promise();
+ }
+
+ new mw.Api().post( {
+ action: 'sanitize-mapdata',
+ text: geoJsonString,
+ title: mw.config.get( 'wgPageName' )
+ } ).done( function ( resp ) {
+ var geoJson, layer,
+ data = resp[ 'sanitize-mapdata' ];
+
+ geoJsonString = data && data.sanitized;
+
+ if ( geoJsonString && !data.error ) {
+ try {
+ geoJson = JSON.parse( geoJsonString );
+ layer =
mw.kartographer.getKartographerLayer( map );
+ layer.setGeoJSON( geoJson );
+ deferred.resolve();
+ } catch ( e ) {
+ deferred.reject( e );
+ }
+ } else {
+ deferred.reject();
+ }
+ } );
+
+ return deferred.promise();
+ };
+
+ // Add index route.
+ router.route( '', function () {
+ // TODO: mapDialog is undefined
+ if ( kartographer.getMapDialog() ) {
+ kartographer.getMapDialog().close();
+ }
+ } );
+
+}( jQuery, mediaWiki ) );
diff --git a/modules/kartographer.MapDialog.js
b/modules/kartographer.MapDialog.js
deleted file mode 100644
index 2bb774c..0000000
--- a/modules/kartographer.MapDialog.js
+++ /dev/null
@@ -1,204 +0,0 @@
-/**
- * Dialog for full screen maps
- *
- * @class
- * @extends OO.ui.Dialog
- *
- * @constructor
- * @param {Object} [config] Configuration options
- */
-mw.kartographer.MapDialog = function MwKartographerMapDialog() {
- // Parent method
- mw.kartographer.MapDialog.super.apply( this, arguments );
-};
-
-/* Inheritance */
-
-OO.inheritClass( mw.kartographer.MapDialog, OO.ui.Dialog );
-
-/* Static Properties */
-
-mw.kartographer.MapDialog.static.size = 'full';
-
-/* Methods */
-
-mw.kartographer.MapDialog.prototype.initialize = function () {
- // Parent method
- mw.kartographer.MapDialog.super.prototype.initialize.apply( this,
arguments );
-
- this.map = null;
- this.mapData = null;
- this.$map = null;
-};
-
-/**
- * Changes the map within the map dialog.
- *
- * If the new map is the same at the previous map, we reuse the same map
- * object and simply update the zoom and the center of the map.
- *
- * If the new map is different, we keep the dialog open and simply
- * replace the map object with a new one.
- *
- * @param {Object} mapData The data for the new map.
- * @param {Object} [mapData.fullScreenState] Optional full screen position in
- * which to open the map.
- * @param {number} [mapData.fullScreenState.zoom]
- * @param {number} [mapData.fullScreenState.latitude]
- * @param {number} [mapData.fullScreenState.longitude]
- */
-mw.kartographer.MapDialog.prototype.changeMap = function ( mapData ) {
- var fullScreenState, extendedData,
- existing = this.mapData;
-
- // Check whether it is the same map.
- if ( existing &&
- typeof existing.maptagId === 'number' &&
- existing.maptagId === mapData.maptagId ) {
-
- fullScreenState = mapData.fullScreenState;
- extendedData = {};
-
- // override with full screen state
- $.extend( extendedData, mapData, fullScreenState );
-
- // Use this boolean to stop listening to `moveend` event while
we're
- // manually moving the map.
- this.movingMap = true;
- this.map.setView( new L.LatLng( extendedData.latitude,
extendedData.longitude ), extendedData.zoom );
- this.map.mapData = mapData;
- this.movingMap = false;
- return;
- }
-
- this.setup.call( this, mapData );
-};
-
-mw.kartographer.MapDialog.prototype.getActionProcess = function ( action ) {
- var dialog = this;
- if ( !action ) {
- return new OO.ui.Process( function () {
- var router = mw.loader.require( 'mediawiki.router' );
- if ( router.getPath() !== '' ) {
- router.navigate( '' );
- } else {
- // force close
- dialog.close( { action: action } );
- }
- } );
- }
- return mw.kartographer.MapDialog.super.prototype.getActionProcess.call(
this, action );
-};
-
-/**
- * Tells the router to navigate to the current full screen map route.
- */
-mw.kartographer.MapDialog.prototype.updateHash = function () {
- var router = mw.loader.require( 'mediawiki.router' ),
- hash = mw.kartographer.getMapHash( this.mapData, this.map );
-
- // Avoid extra operations
- if ( this.lastHash !== hash ) {
- router.navigate( hash );
- this.lastHash = hash;
- }
-};
-
-/**
- * Listens to `moveend` event and calls {@link #updateHash}.
- *
- * This method is throttled, meaning the method will be called at most once per
- * every 100 milliseconds.
- */
-mw.kartographer.MapDialog.prototype.onMapMove = OO.ui.throttle( function () {
- // Stop listening to `moveend` event while we're
- // manually moving the map (updating from a hash),
- // or if the map is not yet loaded.
- /*jscs:disable disallowDanglingUnderscores */
- if ( this.movingMap || !this.map || !this.map._loaded ) {
- return false;
- }
- /*jscs:enable disallowDanglingUnderscores */
- this.updateHash();
-}, 100 );
-
-mw.kartographer.MapDialog.prototype.getSetupProcess = function ( mapData ) {
- return mw.kartographer.MapDialog.super.prototype.getSetupProcess.call(
this, mapData )
- .next( function () {
- var fullScreenState = mapData.fullScreenState,
- extendedData = {};
-
- if ( this.map ) {
- this.map.remove();
- this.$map.remove();
- }
-
- this.$map = $( '<div>' )
- .addClass( 'mw-kartographer-mapDialog-map' )
- .appendTo( this.$body );
-
- this.map = mw.kartographer.createMap( this.$map[ 0 ],
mapData );
- this.map.addControl( new
mw.kartographer.MapDialogCloseControl( { dialog: this } ) );
-
- // copy of the initial settings
- this.mapData = mapData;
-
- if ( fullScreenState ) {
- // override with full screen state
- $.extend( extendedData, mapData,
fullScreenState );
- this.map.setView( new L.LatLng(
extendedData.latitude, extendedData.longitude ), extendedData.zoom, true );
- }
-
- if ( typeof mapData.maptagId === 'number' ) {
- this.map.on( 'moveend', this.onMapMove, this );
- }
-
- mw.hook( 'wikipage.maps' ).fire( this.map, true /*
isFullScreen */ );
- }, this );
-};
-
-mw.kartographer.MapDialog.prototype.getReadyProcess = function ( data ) {
- return mw.kartographer.MapDialog.super.prototype.getReadyProcess.call(
this, data )
- .next( function () {
- this.map.invalidateSize();
- }, this );
-};
-
-mw.kartographer.MapDialog.prototype.getTeardownProcess = function ( data ) {
- return
mw.kartographer.MapDialog.super.prototype.getTeardownProcess.call( this, data )
- .next( function () {
- this.map.remove();
- this.$map.remove();
- this.map = null;
- this.mapData = null;
- this.$map = null;
- }, this );
-};
-
-/**
- * Close control on full screen mode.
- */
-mw.kartographer.MapDialogCloseControl = L.Control.extend( {
- options: {
- position: 'topright'
- },
-
- onAdd: function () {
- var container = L.DomUtil.create( 'div', 'leaflet-bar' ),
- link = L.DomUtil.create( 'a', 'oo-ui-icon-close',
container );
-
- this.href = '#';
- link.title = mw.msg( 'kartographer-fullscreen-close' );
-
- L.DomEvent.addListener( link, 'click', this.onClick, this );
- L.DomEvent.disableClickPropagation( container );
-
- return container;
- },
-
- onClick: function ( e ) {
- L.DomEvent.stop( e );
-
- this.options.dialog.executeAction( '' );
- }
-} );
diff --git a/modules/kartographer.js b/modules/kartographer.js
index fca4daf..f3df0fa 100644
--- a/modules/kartographer.js
+++ b/modules/kartographer.js
@@ -1,333 +1,16 @@
+/* globals module */
( function ( $, mw ) {
- // Load this script after lib/mapbox-lib.js
- var scale, urlFormat, windowManager, mapDialog,
- mapServer = mw.config.get( 'wgKartographerMapServer' ),
- forceHttps = mapServer[ 4 ] === 's',
- config = L.mapbox.config,
- router = mw.loader.require( 'mediawiki.router' ),
- worldLatLng = new L.LatLngBounds( [ -90, -180 ], [ 90, 180 ] );
+ var windowManager, mapDialog;
- config.REQUIRE_ACCESS_TOKEN = false;
- config.FORCE_HTTPS = forceHttps;
- config.HTTP_URL = forceHttps ? false : mapServer;
- config.HTTPS_URL = !forceHttps ? false : mapServer;
-
- function bracketDevicePixelRatio() {
- var i, scale,
- brackets = mw.config.get( 'wgKartographerSrcsetScales'
),
- baseRatio = window.devicePixelRatio || 1;
- if ( !brackets ) {
- return 1;
- }
- brackets.unshift( 1 );
- for ( i = 0; i < brackets.length; i++ ) {
- scale = brackets[ i ];
- if ( scale >= baseRatio || ( baseRatio - scale ) < 0.1
) {
- return scale;
- }
- }
- return brackets[ brackets.length - 1 ];
- }
-
- scale = bracketDevicePixelRatio();
- scale = ( scale === 1 ) ? '' : ( '@' + scale + 'x' );
- urlFormat = '/{z}/{x}/{y}' + scale + '.png';
-
- mw.kartographer = {};
-
- /**
- * References the map containers of the page.
- *
- * @type {HTMLElement[]}
- */
- mw.kartographer.maps = [];
-
- /**
- * References the maplinks of the page.
- *
- * @type {HTMLElement[]}
- */
- mw.kartographer.maplinks = [];
-
- mw.kartographer.FullScreenControl = L.Control.extend( {
- options: {
- // Do not switch for RTL because zoom also stays in
place
- position: 'topright'
- },
-
- onAdd: function ( map ) {
- var container = L.DomUtil.create( 'div', 'leaflet-bar'
);
-
- this.link = L.DomUtil.create( 'a',
'oo-ui-icon-fullScreen', container );
- this.link.title = mw.msg(
'kartographer-fullscreen-text' );
- this.map = map;
-
- this.map.on( 'moveend', this.onMapMove, this );
- if ( !router.isSupported() ) {
- L.DomEvent.addListener( this.link, 'click',
this.onShowFullScreen, this );
- }
- L.DomEvent.disableClickPropagation( container );
- this.updateHash();
-
- return container;
- },
-
- onMapMove: function () {
- /*jscs:disable disallowDanglingUnderscores */
- if ( !this.map._loaded ) {
- return false;
- }
- /*jscs:enable disallowDanglingUnderscores */
- this.updateHash();
- },
-
- updateHash: function () {
- var hash = mw.kartographer.getMapHash(
this.options.mapData, this.map );
- this.link.href = '#' + hash;
- },
-
- onShowFullScreen: function ( e ) {
- L.DomEvent.stop( e );
- mw.kartographer.openFullscreenMap( this.map,
getMapPosition( this.map ) );
- }
- } );
- /**
- * Gets the valid bounds of a map/layer.
- *
- * @param {L.Map|L.Layer} layer
- * @return {L.LatLngBounds} Extended bounds
- * @private
- */
- function getValidBounds( layer ) {
- var layerBounds = new L.LatLngBounds();
- if ( typeof layer.eachLayer === 'function' ) {
- layer.eachLayer( function ( child ) {
- layerBounds.extend( getValidBounds( child ) );
- } );
- } else {
- layerBounds.extend( validateBounds( layer ) );
- }
- return layerBounds;
- }
-
- /**
- * Validate that the bounds contain no outlier.
- *
- * An outlier is a layer whom bounds do not fit into the world,
- * i.e. `-180 <= longitude <= 180 && -90 <= latitude <= 90`
- *
- * @param {L.Layer} layer Layer to get and validate the bounds.
- * @return {L.LatLng|boolean} Bounds if valid.
- * @private
- */
- function validateBounds( layer ) {
- var bounds = ( typeof layer.getBounds === 'function' ) &&
layer.getBounds();
-
- bounds = bounds || ( typeof layer.getLatLng === 'function' ) &&
layer.getLatLng();
-
- if ( bounds && worldLatLng.contains( bounds ) ) {
- return bounds;
- }
- return false;
- }
-
- /**
- * Create a new interactive map
- *
- * @param {HTMLElement} container Map container
- * @param {Object} data Map data
- * @param {number} data.latitude Latitude
- * @param {number} data.longitude Longitude
- * @param {number} data.zoom Zoom
- * @param {string} [data.style] Map style
- * @param {string[]} [data.overlays] Names of overlay groups to show
- * @param {boolean} [data.enableFullScreenButton] add zoom
- * @return {L.mapbox.Map} Map object
- */
- mw.kartographer.createMap = function ( container, data ) {
- var map,
- $container = $( container ),
- style = data.style || mw.config.get(
'wgKartographerDfltStyle' ),
- width, height,
- maxBounds;
-
- $container.addClass( 'mw-kartographer-map' );
-
- map = L.map( container );
-
- if ( !container.clientWidth ) {
- // Get `max` properties in case the container was
wrapped
- // with {@link #responsiveContainerWrap}.
- width = $container.css( 'max-width' );
- height = $container.css( 'max-height' );
- width = ( !width || width === 'none' ) ?
$container.width() : width;
- height = ( !height || height === 'none' ) ?
$container.height() : height;
-
- // HACK: If the container is not naturally measurable,
try jQuery
- // which will pick up CSS dimensions. T125263
- /*jscs:disable disallowDanglingUnderscores */
- map._size = new L.Point( width, height );
- /*jscs:enable disallowDanglingUnderscores */
- }
-
- /**
- * @property {L.TileLayer} Reference to `Wikimedia` tile layer.
- */
- map.wikimediaLayer = L.tileLayer( mapServer + '/' + style +
urlFormat, {
- maxZoom: 18,
- attribution: mw.message( 'kartographer-attribution'
).parse()
- } ).addTo( map );
-
- /**
- * @property {Object} Hash map of data groups and their
corresponding
- * {@link L.mapbox.FeatureLayer layers}.
- */
- map.dataLayers = {};
-
- if ( data.overlays ) {
-
- getMapGroupData( data.overlays ).done( function (
mapData ) {
- $.each( data.overlays, function ( index, group
) {
- if ( !$.isEmptyObject( mapData[ group ]
) ) {
- map.dataLayers[ group ] =
mw.kartographer.addDataLayer( map, mapData[ group ] );
- } else {
- mw.log.warn( 'Layer not found
or contains no data: "' + group + '"' );
- }
- } );
- } );
-
- }
-
- // Position the map
- if ( isNaN( data.longitude ) && isNaN( data.latitude ) ) {
- // Determines best center of the map
- maxBounds = getValidBounds( map );
- if ( maxBounds.isValid() ) {
- map.fitBounds( maxBounds );
- } else {
- map.fitWorld();
- }
- // (Re-)Applies expected zoom
- if ( !isNaN( data.zoom ) ) {
- map.setZoom( data.zoom );
- }
- // Updates map data.
- data.zoom = map.getZoom();
- data.longitude = map.getCenter().lng;
- data.latitude = map.getCenter().lat;
- // Updates container's data attributes to avoid `NaN`
errors
- $( map.getContainer() ).closest(
'.mw-kartographer-interactive' ).data( {
- zoom: data.zoom,
- lon: data.longitude,
- lat: data.latitude
- } );
- } else {
- map.setView( [ data.latitude, data.longitude ],
data.zoom, true );
- }
-
- map.attributionControl.setPrefix( '' );
-
- if ( data.enableFullScreenButton ) {
- map.addControl( new mw.kartographer.FullScreenControl( {
- mapData: data
- } ) );
- }
-
- return map;
- };
-
- mw.kartographer.dataLayerOpts = {
- // Disable double-sanitization by mapbox's internal sanitizer
- // because geojson has already passed through the MW internal
sanitizer
- sanitizer: function ( v ) {
- return v;
- }
- };
-
- /**
- * Create a new GeoJSON layer and add it to map.
- *
- * @param {L.mapbox.Map} map Map to get layers from
- * @param {Object} geoJson
- */
- mw.kartographer.addDataLayer = function ( map, geoJson ) {
- try {
- return L.mapbox.featureLayer( geoJson,
mw.kartographer.dataLayerOpts ).addTo( map );
- } catch ( e ) {
- mw.log( e );
- }
- };
-
- /**
- * Get "editable" geojson layer for the map.
- *
- * If a layer doesn't exist, create and attach one.
- *
- * @param {L.mapbox.Map} map Map to get layers from
- * @param {L.mapbox.FeatureLayer} map.kartographerLayer show
tag-specific info in this layer
- * @return {L.mapbox.FeatureLayer|null} GeoJSON layer, if present
- */
- mw.kartographer.getKartographerLayer = function ( map ) {
- if ( !map.kartographerLayer ) {
- map.kartographerLayer = L.mapbox.featureLayer().addTo(
map );
- }
- return map.kartographerLayer;
- };
-
- /**
- * Updates "editable" GeoJSON layer from a string.
- *
- * Validates the GeoJSON against the `sanitize-mapdata` api
- * before executing it.
- *
- * The deferred object will be resolved with a `boolean` flag
- * indicating whether the GeoJSON was valid and was applied.
- *
- * @param {L.mapbox.Map} map Map to set the GeoJSON for
- * @param {string} geoJsonString GeoJSON data, empty string to clear
- * @return {jQuery.Promise} Promise which resolves when the GeoJSON is
updated, and rejects if there was an error
- */
- mw.kartographer.updateKartographerLayer = function ( map, geoJsonString
) {
- var deferred = $.Deferred();
-
- if ( geoJsonString === '' ) {
- return deferred.resolve().promise();
- }
-
- new mw.Api().post( {
- action: 'sanitize-mapdata',
- text: geoJsonString,
- title: mw.config.get( 'wgPageName' )
- } ).done( function ( resp ) {
- var geoJson, layer,
- data = resp[ 'sanitize-mapdata' ];
-
- geoJsonString = data && data.sanitized;
-
- if ( geoJsonString && !data.error ) {
- try {
- geoJson = JSON.parse( geoJsonString );
- layer =
mw.kartographer.getKartographerLayer( map );
- layer.setGeoJSON( geoJson );
- deferred.resolve();
- } catch ( e ) {
- deferred.reject( e );
- }
- } else {
- deferred.reject();
- }
- } );
-
- return deferred.promise();
- };
+ mw.kartographer = mw.kartographer || {};
function getWindowManager() {
if ( !windowManager ) {
windowManager = new OO.ui.WindowManager();
- mapDialog = new mw.kartographer.MapDialog();
+ setMapDialog( new mw.kartographer.MapDialog() );
$( 'body' ).append( windowManager.$element );
- windowManager.addWindows( [ mapDialog ] );
+ windowManager.addWindows( [ getMapDialog() ] );
}
return windowManager;
}
@@ -374,21 +57,25 @@
enableFullScreenButton: false
} );
- if ( mapDialog ) {
- mapDialog.changeMap( dialogData );
+ if ( getMapDialog() ) {
+ getMapDialog().changeMap( dialogData );
return;
}
getWindowManager()
- .openWindow( mapDialog, dialogData )
- .then( function ( opened ) { return
opened; } )
+ .openWindow( getMapDialog(), dialogData
)
+ .then( function ( opened ) {
+ return opened;
+ } )
.then( function ( closing ) {
+ var dialog = getMapDialog();
if ( map ) {
map.setView(
-
mapDialog.map.getCenter(),
-
mapDialog.map.getZoom()
+
dialog.map.getCenter(),
+
dialog.map.getZoom()
);
}
- windowManager = mapDialog =
null;
+ setMapDialog( null );
+ windowManager = null;
return closing;
} );
} );
@@ -521,229 +208,22 @@
return obj;
}
- /**
- * Returns the map data for the page.
- *
- * If the data is not already loaded (`wgKartographerLiveData`), an
- * asynchronous request will be made to fetch the missing groups.
- * The new data is then added to `wgKartographerLiveData`.
- *
- * @param {string[]} overlays Overlay group names
- * @return {jQuery.Promise} Promise which resolves with the group data,
an object keyed by group name
- * @private
- */
- function getMapGroupData( overlays ) {
- var deferred = $.Deferred(),
- groupsLoaded = mw.config.get( 'wgKartographerLiveData'
) || {},
- groupsToLoad = [];
-
- $( overlays ).each( function ( key, value ) {
- if ( !( value in groupsLoaded ) ) {
- groupsToLoad.push( value );
- }
- } );
-
- if ( !groupsToLoad.length ) {
- return deferred.resolve( groupsLoaded ).promise();
- }
-
- new mw.Api().get( {
- action: 'query',
- formatversion: '2',
- titles: mw.config.get( 'wgPageName' ),
- prop: 'mapdata',
- mpdgroups: groupsToLoad.join( '|' )
- } ).done( function ( data ) {
- var rawMapData = data.query.pages[ 0 ].mapdata,
- mapData = rawMapData && JSON.parse( rawMapData
) || {};
-
- $.extend( groupsLoaded, mapData );
- mw.config.set( 'wgKartographerLiveData', groupsLoaded );
-
- deferred.resolve( groupsLoaded );
- } );
-
- return deferred.promise();
+ function getMapDialog() {
+ return mapDialog;
}
- /**
- * Wraps a map container to make it (and its map) responsive on
- * mobile (MobileFrontend).
- *
- * The initial `mapContainer`:
- *
- * <div class="mw-kartographer-interactive" style="height: Y;
width: X;">
- * <!-- this is the component carrying Leaflet.Map -->
- * </div>
- *
- * Becomes :
- *
- * <div class="mw-kartographer-interactive
mw-kartographer-responsive" style="max-height: Y; max-width: X;">
- * <div class="mw-kartographer-responder"
style="padding-bottom: (100*Y/X)%">
- * <div>
- * <!-- this is the component carrying Leaflet.Map -->
- * </div>
- * </div>
- * </div>
- *
- * **Note:** the container that carries the map data remains the initial
- * `mapContainer` passed in arguments. Its selector remains
`.mw-kartographer-interactive`.
- * However it is now a sub-child that carries the map.
- *
- * **Note 2:** the CSS applied to these elements vary whether the map
width
- * is absolute (px) or relative (%). The example above describes the
absolute
- * width case.
- *
- * @param {HTMLElement} mapContainer Initial component to carry the map.
- * @return {HTMLElement} New map container to carry the map.
- */
- function responsiveContainerWrap( mapContainer ) {
- var $container = $( mapContainer ),
- $responder, $map,
- width = mapContainer.style.width,
- isRelativeWidth = width.slice( -1 ) === '%',
- height = +( mapContainer.style.height.slice( 0, -2 ) ),
- containerCss, responderCss;
-
- // Convert the value to a string.
- width = isRelativeWidth ? width : +( width.slice( 0, -2 ) );
-
- if ( isRelativeWidth ) {
- containerCss = {};
- responderCss = {
- // The inner container must occupy the full
height
- height: height
- };
- } else {
- containerCss = {
- // Remove explicitly set dimensions
- width: '',
- height: '',
- // Prevent over-sizing
- 'max-width': width,
- 'max-height': height
- };
- responderCss = {
- // Use padding-bottom trick to maintain
original aspect ratio
- 'padding-bottom': ( 100 * height / width ) + '%'
- };
- }
- $container.addClass( 'mw-kartographer-responsive' ).css(
containerCss );
- $responder = $( '<div>' ).addClass( 'mw-kartographer-responder'
).css( responderCss );
-
- $map = $( '<div>' );
- $container.append( $responder.append( $map ) );
- return $map[ 0 ];
+ function setMapDialog( dialog ) {
+ mapDialog = dialog;
+ return mapDialog;
}
- /**
- * This code will be executed once the article is rendered and ready.
- */
- mw.hook( 'wikipage.content' ).add( function ( $content ) {
- var mapsInArticle = [],
- isMobile = mw.config.get( 'skin' ) === 'minerva';
+ module.exports = {
+ getMapHash: mw.kartographer.getMapHash,
+ openFullscreenMap: mw.kartographer.openFullscreenMap,
+ getMapData: getMapData,
+ getMapPosition: getMapPosition,
+ getFullScreenState: getFullScreenState,
+ getMapDialog: getMapDialog
+ };
- // Some links might be displayed outside of $content, so we
need to
- // search outside. This is an anti-pattern and should be
improved...
- // Meanwhile #content is better than searching the full
document.
- $( '.mw-kartographer-link', '#content' ).each( function ( index
) {
- mw.kartographer.maplinks[ index ] = this;
-
- $( this ).data( 'maptag-id', index );
- this.href = '#' + '/maplink/' + index;
- } );
-
- L.Map.mergeOptions( {
- sleepTime: 250,
- wakeTime: 1000,
- sleepNote: false,
- sleepOpacity: 1
- } );
-
- $content.find( '.mw-kartographer-interactive' ).each( function
( index ) {
- var map, data,
- container = this,
- $container = $( this );
-
- $container.data( 'maptag-id', index );
- data = getMapData( container );
-
- if ( data ) {
- data.enableFullScreenButton = true;
-
- if ( isMobile ) {
- container = responsiveContainerWrap(
container );
- }
-
- map = mw.kartographer.createMap( container,
data );
- map.doubleClickZoom.disable();
-
- mapsInArticle.push( map );
- mw.kartographer.maps[ index ] = map;
-
- $container.on( 'dblclick', function () {
- if ( router.isSupported() ) {
- router.navigate(
mw.kartographer.getMapHash( data, map ) );
- } else {
-
mw.kartographer.openFullscreenMap( map, getMapPosition( map ) );
- }
- } );
-
- // Special case for collapsible maps.
- // When the container is hidden Leaflet is not
able to
- // calculate the expected size when visible. We
need to force
- // updating the map to the new container size
on `expand`.
- if ( !$container.is( ':visible' ) ) {
- $container.closest( '.mw-collapsible' )
- .on(
'afterExpand.mw-collapsible', function () {
- map.invalidateSize();
- } );
- }
- }
- } );
-
- // Allow customizations of interactive maps in article.
- mw.hook( 'wikipage.maps' ).fire( mapsInArticle, false /*
isFullScreen */ );
-
- // Opens a map in full screen.
#/map(/:zoom)(/:latitude)(/:longitude)
- // Examples:
- // #/map/0
- // #/map/0/5
- // #/map/0/16/-122.4006/37.7873
- router.route(
/map\/([0-9]+)(?:\/([0-9]+))?(?:\/([\-\+]?\d+\.?\d{0,5})?\/([\-\+]?\d+\.?\d{0,5})?)?/,
function ( maptagId, zoom, latitude, longitude ) {
- var map = mw.kartographer.maps[ maptagId ];
- if ( !map ) {
- router.navigate( '' );
- return;
- }
- mw.kartographer.openFullscreenMap( map,
getFullScreenState( zoom, latitude, longitude ) );
- } );
-
- // Opens a maplink in full screen.
#/maplink(/:zoom)(/:latitude)(/:longitude)
- // Examples:
- // #/maplink/0
- // #/maplink/0/5
- // #/maplink/0/16/-122.4006/37.7873
- router.route(
/maplink\/([0-9]+)(?:\/([0-9]+))?(?:\/([\-\+]?\d+\.?\d{0,5})?\/([\-\+]?\d+\.?\d{0,5})?)?/,
function ( maptagId, zoom, latitude, longitude ) {
- var link = mw.kartographer.maplinks[ maptagId ],
- data;
-
- if ( !link ) {
- router.navigate( '' );
- return;
- }
- data = getMapData( link );
- mw.kartographer.openFullscreenMap( data,
getFullScreenState( zoom, latitude, longitude ) );
- } );
-
- // Check if we need to open a map in full screen.
- router.checkRoute();
-
- // Add index route.
- router.route( '', function () {
- if ( mapDialog ) {
- mapDialog.close();
- }
- } );
- } );
}( jQuery, mediaWiki ) );
diff --git a/modules/live/FullScreenControl.js
b/modules/live/FullScreenControl.js
new file mode 100644
index 0000000..f2bcf9c
--- /dev/null
+++ b/modules/live/FullScreenControl.js
@@ -0,0 +1,46 @@
+var router = mw.loader.require( 'mediawiki.router' ),
+ kartographer = mw.loader.require( 'ext.kartographer.init' ),
+ LiveFullScreenControl;
+
+LiveFullScreenControl = L.Control.extend( {
+ options: {
+ // Do not switch for RTL because zoom also stays in place
+ position: 'topright'
+ },
+
+ onAdd: function ( map ) {
+ var container = L.DomUtil.create( 'div', 'leaflet-bar' );
+
+ this.link = L.DomUtil.create( 'a', 'oo-ui-icon-fullScreen',
container );
+ this.link.title = mw.msg( 'kartographer-fullscreen-text' );
+ this.map = map;
+
+ this.map.on( 'moveend', this.onMapMove, this );
+ if ( !router.isSupported() ) {
+ L.DomEvent.addListener( this.link, 'click',
this.onShowFullScreen, this );
+ }
+ L.DomEvent.disableClickPropagation( container );
+ this.updateHash();
+
+ return container;
+ },
+
+ onMapMove: function () {
+ /*jscs:disable disallowDanglingUnderscores */
+ if ( !this.map._loaded ) {
+ return false;
+ }
+ /*jscs:enable disallowDanglingUnderscores */
+ this.updateHash();
+ },
+
+ updateHash: function () {
+ var hash = mw.kartographer.getMapHash( this.options.mapData,
this.map );
+ this.link.href = '#' + hash;
+ },
+
+ onShowFullScreen: function ( e ) {
+ L.DomEvent.stop( e );
+ mw.kartographer.openFullscreenMap( this.map,
kartographer.getMapPosition( this.map ) );
+ }
+} );
diff --git a/modules/live/live.js b/modules/live/live.js
new file mode 100644
index 0000000..4aee580
--- /dev/null
+++ b/modules/live/live.js
@@ -0,0 +1,255 @@
+/* globals module */
+/* globals LiveFullScreenControl */
+( function ( $, mw, FullScreenControl ) {
+
+ var scale, urlFormat,
+ mapServer = mw.config.get( 'wgKartographerMapServer' ),
+ createMap,
+ worldLatLng = new L.LatLngBounds( [ -90, -180 ], [ 90, 180 ] );
+
+ function bracketDevicePixelRatio() {
+ var i, scale,
+ brackets = mw.config.get( 'wgKartographerSrcsetScales'
),
+ baseRatio = window.devicePixelRatio || 1;
+ if ( !brackets ) {
+ return 1;
+ }
+ brackets.unshift( 1 );
+ for ( i = 0; i < brackets.length; i++ ) {
+ scale = brackets[ i ];
+ if ( scale >= baseRatio || ( baseRatio - scale ) < 0.1
) {
+ return scale;
+ }
+ }
+ return brackets[ brackets.length - 1 ];
+ }
+
+ scale = bracketDevicePixelRatio();
+ scale = ( scale === 1 ) ? '' : ( '@' + scale + 'x' );
+ urlFormat = '/{z}/{x}/{y}' + scale + '.png';
+
+ L.Map.mergeOptions( {
+ sleepTime: 250,
+ wakeTime: 1000,
+ sleepNote: false,
+ sleepOpacity: 1
+ } );
+
+ /**
+ * Create a new interactive map
+ *
+ * @param {HTMLElement} container Map container
+ * @param {Object} data Map data
+ * @param {number} data.latitude Latitude
+ * @param {number} data.longitude Longitude
+ * @param {number} data.zoom Zoom
+ * @param {string} [data.style] Map style
+ * @param {string[]} [data.overlays] Names of overlay groups to show
+ * @param {boolean} [data.enableFullScreenButton] add zoom
+ * @return {L.mapbox.Map} Map object
+ */
+ createMap = function ( container, data ) {
+ var map,
+ $container = $( container ),
+ style = data.style || mw.config.get(
'wgKartographerDfltStyle' ),
+ width, height,
+ maxBounds;
+
+ $container.addClass( 'mw-kartographer-map' );
+
+ map = L.map( container );
+
+ if ( !container.clientWidth ) {
+ // Get `max` properties in case the container was
wrapped
+ // with {@link #responsiveContainerWrap}.
+ width = $container.css( 'max-width' );
+ height = $container.css( 'max-height' );
+ width = ( !width || width === 'none' ) ?
$container.width() : width;
+ height = ( !height || height === 'none' ) ?
$container.height() : height;
+
+ // HACK: If the container is not naturally measurable,
try jQuery
+ // which will pick up CSS dimensions. T125263
+ /*jscs:disable disallowDanglingUnderscores */
+ map._size = new L.Point( width, height );
+ /*jscs:enable disallowDanglingUnderscores */
+ }
+
+ /**
+ * @property {L.TileLayer} Reference to `Wikimedia` tile layer.
+ */
+ map.wikimediaLayer = L.tileLayer( mapServer + '/' + style +
urlFormat, {
+ maxZoom: 18,
+ attribution: mw.message( 'kartographer-attribution'
).parse()
+ } ).addTo( map );
+
+ /**
+ * @property {Object} Hash map of data groups and their
corresponding
+ * {@link L.mapbox.FeatureLayer layers}.
+ */
+ map.dataLayers = {};
+
+ if ( data.overlays ) {
+
+ getMapGroupData( data.overlays ).done( function (
mapData ) {
+ $.each( data.overlays, function ( index, group
) {
+ if ( !$.isEmptyObject( mapData[ group ]
) ) {
+ map.dataLayers[ group ] =
mw.kartographer.addDataLayer( map, mapData[ group ] );
+ } else {
+ mw.log.warn( 'Layer not found
or contains no data: "' + group + '"' );
+ }
+ } );
+ } );
+
+ }
+
+ // Position the map
+ if ( isNaN( data.longitude ) && isNaN( data.latitude ) ) {
+ // Determines best center of the map
+ maxBounds = getValidBounds( map );
+ if ( maxBounds.isValid() ) {
+ map.fitBounds( maxBounds );
+ } else {
+ map.fitWorld();
+ }
+ // (Re-)Applies expected zoom
+ if ( !isNaN( data.zoom ) ) {
+ map.setZoom( data.zoom );
+ }
+ // Updates map data.
+ data.zoom = map.getZoom();
+ data.longitude = map.getCenter().lng;
+ data.latitude = map.getCenter().lat;
+ // Updates container's data attributes to avoid `NaN`
errors
+ $( map.getContainer() ).closest(
'.mw-kartographer-interactive' ).data( {
+ zoom: data.zoom,
+ lon: data.longitude,
+ lat: data.latitude
+ } );
+ } else {
+ map.setView( [ data.latitude, data.longitude ],
data.zoom, true );
+ }
+
+ map.attributionControl.setPrefix( '' );
+
+ if ( data.enableFullScreenButton ) {
+ map.addControl( new FullScreenControl( {
+ mapData: data
+ } ) );
+ }
+
+ return map;
+ };
+
+ mw.kartographer.dataLayerOpts = {
+ // Disable double-sanitization by mapbox's internal sanitizer
+ // because geojson has already passed through the MW internal
sanitizer
+ sanitizer: function ( v ) {
+ return v;
+ }
+ };
+
+ /**
+ * Create a new GeoJSON layer and add it to map.
+ *
+ * @param {L.mapbox.Map} map Map to get layers from
+ * @param {Object} geoJson
+ */
+ mw.kartographer.addDataLayer = function ( map, geoJson ) {
+ try {
+ return L.mapbox.featureLayer( geoJson,
mw.kartographer.dataLayerOpts ).addTo( map );
+ } catch ( e ) {
+ mw.log( e );
+ }
+ };
+
+ /**
+ * Returns the map data for the page.
+ *
+ * If the data is not already loaded (`wgKartographerLiveData`), an
+ * asynchronous request will be made to fetch the missing groups.
+ * The new data is then added to `wgKartographerLiveData`.
+ *
+ * @param {string[]} overlays Overlay group names
+ * @return {jQuery.Promise} Promise which resolves with the group data,
an object keyed by group name
+ * @private
+ */
+ function getMapGroupData( overlays ) {
+ var deferred = $.Deferred(),
+ groupsLoaded = mw.config.get( 'wgKartographerLiveData'
) || {},
+ groupsToLoad = [];
+
+ $( overlays ).each( function ( key, value ) {
+ if ( !( value in groupsLoaded ) ) {
+ groupsToLoad.push( value );
+ }
+ } );
+
+ if ( !groupsToLoad.length ) {
+ return deferred.resolve( groupsLoaded ).promise();
+ }
+
+ new mw.Api().get( {
+ action: 'query',
+ formatversion: '2',
+ titles: mw.config.get( 'wgPageName' ),
+ prop: 'mapdata',
+ mpdgroups: groupsToLoad.join( '|' )
+ } ).done( function ( data ) {
+ var rawMapData = data.query.pages[ 0 ].mapdata,
+ mapData = rawMapData && JSON.parse( rawMapData
) || {};
+
+ $.extend( groupsLoaded, mapData );
+ mw.config.set( 'wgKartographerLiveData', groupsLoaded );
+
+ deferred.resolve( groupsLoaded );
+ } );
+
+ return deferred.promise();
+ }
+
+ /**
+ * Gets the valid bounds of a map/layer.
+ *
+ * @param {L.Map|L.Layer} layer
+ * @return {L.LatLngBounds} Extended bounds
+ * @private
+ */
+ function getValidBounds( layer ) {
+ var layerBounds = new L.LatLngBounds();
+ if ( typeof layer.eachLayer === 'function' ) {
+ layer.eachLayer( function ( child ) {
+ layerBounds.extend( getValidBounds( child ) );
+ } );
+ } else {
+ layerBounds.extend( validateBounds( layer ) );
+ }
+ return layerBounds;
+ }
+
+ /**
+ * Validate that the bounds contain no outlier.
+ *
+ * An outlier is a layer whom bounds do not fit into the world,
+ * i.e. `-180 <= longitude <= 180 && -90 <= latitude <= 90`
+ *
+ * @param {L.Layer} layer Layer to get and validate the bounds.
+ * @return {L.LatLng|boolean} Bounds if valid.
+ * @private
+ */
+ function validateBounds( layer ) {
+ var bounds = ( typeof layer.getBounds === 'function' ) &&
layer.getBounds();
+
+ bounds = bounds || ( typeof layer.getLatLng === 'function' ) &&
layer.getLatLng();
+
+ if ( bounds && worldLatLng.contains( bounds ) ) {
+ return bounds;
+ }
+ return false;
+ }
+
+ module.exports = {
+ FullScreenControl: FullScreenControl,
+ createMap: createMap
+ };
+
+}( jQuery, mediaWiki, LiveFullScreenControl ) );
diff --git a/modules/mapframe/mapframe.js b/modules/mapframe/mapframe.js
new file mode 100644
index 0000000..b41adb2
--- /dev/null
+++ b/modules/mapframe/mapframe.js
@@ -0,0 +1,155 @@
+/* globals require */
+( function ( $, mw ) {
+
+ var router = require( 'mediawiki.router' ),
+ kartographer = require( 'ext.kartographer.init' ),
+ kartoLive = require( 'ext.kartographer.live' );
+
+ /**
+ * References the map containers of the page.
+ *
+ * @type {HTMLElement[]}
+ */
+ mw.kartographer.maps = [];
+
+ /**
+ * Wraps a map container to make it (and its map) responsive on
+ * mobile (MobileFrontend).
+ *
+ * The initial `mapContainer`:
+ *
+ * <div class="mw-kartographer-interactive" style="height: Y;
width: X;">
+ * <!-- this is the component carrying Leaflet.Map -->
+ * </div>
+ *
+ * Becomes :
+ *
+ * <div class="mw-kartographer-interactive
mw-kartographer-responsive" style="max-height: Y; max-width: X;">
+ * <div class="mw-kartographer-responder"
style="padding-bottom: (100*Y/X)%">
+ * <div>
+ * <!-- this is the component carrying Leaflet.Map -->
+ * </div>
+ * </div>
+ * </div>
+ *
+ * **Note:** the container that carries the map data remains the initial
+ * `mapContainer` passed in arguments. Its selector remains
`.mw-kartographer-interactive`.
+ * However it is now a sub-child that carries the map.
+ *
+ * **Note 2:** the CSS applied to these elements vary whether the map
width
+ * is absolute (px) or relative (%). The example above describes the
absolute
+ * width case.
+ *
+ * @param {HTMLElement} mapContainer Initial component to carry the map.
+ * @return {HTMLElement} New map container to carry the map.
+ */
+ function responsiveContainerWrap( mapContainer ) {
+ var $container = $( mapContainer ),
+ $responder, $map,
+ width = mapContainer.style.width,
+ isRelativeWidth = width.slice( -1 ) === '%',
+ height = +( mapContainer.style.height.slice( 0, -2 ) ),
+ containerCss, responderCss;
+
+ // Convert the value to a string.
+ width = isRelativeWidth ? width : +( width.slice( 0, -2 ) );
+
+ if ( isRelativeWidth ) {
+ containerCss = {};
+ responderCss = {
+ // The inner container must occupy the full
height
+ height: height
+ };
+ } else {
+ containerCss = {
+ // Remove explicitly set dimensions
+ width: '',
+ height: '',
+ // Prevent over-sizing
+ 'max-width': width,
+ 'max-height': height
+ };
+ responderCss = {
+ // Use padding-bottom trick to maintain
original aspect ratio
+ 'padding-bottom': ( 100 * height / width ) + '%'
+ };
+ }
+ $container.addClass( 'mw-kartographer-responsive' ).css(
containerCss );
+ $responder = $( '<div>' ).addClass( 'mw-kartographer-responder'
).css( responderCss );
+
+ $map = $( '<div>' );
+ $container.append( $responder.append( $map ) );
+ return $map[ 0 ];
+ }
+
+ /**
+ * This code will be executed once the article is rendered and ready.
+ */
+ mw.hook( 'wikipage.content' ).add( function ( $content ) {
+ var mapsInArticle = [],
+ isMobile = mw.config.get( 'skin' ) === 'minerva';
+
+ $content.find( '.mw-kartographer-interactive' ).each( function
( index ) {
+ var map, data,
+ container = this,
+ $container = $( this );
+
+ $container.data( 'maptag-id', index );
+ data = kartographer.getMapData( container );
+
+ if ( data ) {
+ data.enableFullScreenButton = true;
+
+ if ( isMobile ) {
+ container = responsiveContainerWrap(
container );
+ }
+
+ map = kartoLive.createMap( container, data );
+ map.doubleClickZoom.disable();
+
+ mapsInArticle.push( map );
+ mw.kartographer.maps[ index ] = map;
+
+ $container.on( 'dblclick', function () {
+ if ( router.isSupported() ) {
+ router.navigate(
kartographer.getMapHash( data, map ) );
+ } else {
+ kartographer.openFullscreenMap(
map, kartographer.getMapPosition( map ) );
+ }
+ } );
+
+ // Special case for collapsible maps.
+ // When the container is hidden Leaflet is not
able to
+ // calculate the expected size when visible. We
need to force
+ // updating the map to the new container size
on `expand`.
+ if ( !$container.is( ':visible' ) ) {
+ $container.closest( '.mw-collapsible' )
+ .on(
'afterExpand.mw-collapsible', function () {
+ map.invalidateSize();
+ } );
+ }
+ }
+ } );
+
+ // Allow customizations of interactive maps in article.
+ mw.hook( 'wikipage.maps' ).fire( mapsInArticle, false /*
isFullScreen */ );
+
+ // Opens a map in full screen.
#/map(/:zoom)(/:latitude)(/:longitude)
+ // Examples:
+ // #/map/0
+ // #/map/0/5
+ // #/map/0/16/-122.4006/37.7873
+ router.route(
/map\/([0-9]+)(?:\/([0-9]+))?(?:\/([\-\+]?\d+\.?\d{0,5})?\/([\-\+]?\d+\.?\d{0,5})?)?/,
function ( maptagId, zoom, latitude, longitude ) {
+ var map = mw.kartographer.maps[ maptagId ];
+ if ( !map ) {
+ router.navigate( '' );
+ return;
+ }
+
+ mw.kartographer.openFullscreenMap( map,
kartographer.getFullScreenState( zoom, latitude, longitude ) );
+ } );
+
+ // Check if we need to open a map in full screen.
+ router.checkRoute();
+ } );
+}( jQuery, mediaWiki ) );
diff --git a/modules/maplink/maplink.js b/modules/maplink/maplink.js
new file mode 100644
index 0000000..a3395c4
--- /dev/null
+++ b/modules/maplink/maplink.js
@@ -0,0 +1,49 @@
+/* globals require */
+( function ( $, mw ) {
+
+ var kartographer = require( 'ext.kartographer.init' ),
+ router = require( 'mediawiki.router' );
+
+ /**
+ * References the maplinks of the page.
+ *
+ * @type {HTMLElement[]}
+ */
+ mw.kartographer.maplinks = [];
+
+ /**
+ * This code will be executed once the article is rendered and ready.
+ */
+ mw.hook( 'wikipage.content' ).add( function ( ) {
+
+ // Some links might be displayed outside of $content, so we
need to
+ // search outside. This is an anti-pattern and should be
improved...
+ // Meanwhile #content is better than searching the full
document.
+ $( '.mw-kartographer-link', '#content' ).each( function ( index
) {
+ mw.kartographer.maplinks[ index ] = this;
+
+ $( this ).data( 'maptag-id', index );
+ this.href = '#' + '/maplink/' + index;
+ } );
+
+ // Opens a maplink in full screen.
#/maplink(/:zoom)(/:latitude)(/:longitude)
+ // Examples:
+ // #/maplink/0
+ // #/maplink/0/5
+ // #/maplink/0/16/-122.4006/37.7873
+ router.route(
/maplink\/([0-9]+)(?:\/([0-9]+))?(?:\/([\-\+]?\d+\.?\d{0,5})?\/([\-\+]?\d+\.?\d{0,5})?)?/,
function ( maptagId, zoom, latitude, longitude ) {
+ var link = mw.kartographer.maplinks[ maptagId ],
+ data;
+
+ if ( !link ) {
+ router.navigate( '' );
+ return;
+ }
+ data = kartographer.getMapData( link );
+ mw.kartographer.openFullscreenMap( data,
kartographer.getFullScreenState( zoom, latitude, longitude ) );
+ } );
+
+ // Check if we need to open a map in full screen.
+ router.checkRoute();
+ } );
+}( jQuery, mediaWiki ) );
diff --git a/modules/settings/settings.js b/modules/settings/settings.js
new file mode 100644
index 0000000..f97f220
--- /dev/null
+++ b/modules/settings/settings.js
@@ -0,0 +1,12 @@
+( function ( $, mw ) {
+
+ var mapServer = mw.config.get( 'wgKartographerMapServer' ),
+ forceHttps = mapServer[ 4 ] === 's',
+ config = L.mapbox.config;
+
+ config.REQUIRE_ACCESS_TOKEN = false;
+ config.FORCE_HTTPS = forceHttps;
+ config.HTTP_URL = forceHttps ? false : mapServer;
+ config.HTTPS_URL = !forceHttps ? false : mapServer;
+
+}( jQuery, mediaWiki ) );
--
To view, visit https://gerrit.wikimedia.org/r/295599
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ifdeb529c86709ae0890d4445afce972fa26c9521
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Kartographer
Gerrit-Branch: master
Gerrit-Owner: JGirault <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits