JGirault has uploaded a new change for review. https://gerrit.wikimedia.org/r/283374
Change subject: Adds a router to Kartographer, fixes back button in fullscreen mode, and allows sharing a link that opens a map in fullscreen mode. ...................................................................... Adds a router to Kartographer, fixes back button in fullscreen mode, and allows sharing a link that opens a map in fullscreen mode. This patch includes the Router proposal made in patch: https://gerrit.wikimedia.org/r/#/c/260950 Ideally mediawiki or OO would support a Router by default. Bug: T128940 Change-Id: I456a4582a67e31533d51d5817d0f4af57528c35e --- M extension.json A lib/oo-router.js M modules/kartographer.MapDialog.js M modules/kartographer.js 4 files changed, 262 insertions(+), 6 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Kartographer refs/changes/74/283374/1 diff --git a/extension.json b/extension.json index e161a40..af7bd6a 100644 --- a/extension.json +++ b/extension.json @@ -182,6 +182,18 @@ "mobile", "desktop" ] + }, + "oo-router": { + "scripts": [ + "lib/oo-router.js" + ], + "targets": [ + "mobile", + "desktop" + ], + "dependencies": [ + "oojs" + ] } }, "ResourceFileModulePaths": { diff --git a/lib/oo-router.js b/lib/oo-router.js new file mode 100644 index 0000000..ffb10ce --- /dev/null +++ b/lib/oo-router.js @@ -0,0 +1,177 @@ +OO.Router = ( function ( $ ) { + /** + * Does hash match entry.path? + * + * @method + * @private + * @ignore + * @param {string} hash string to match + * @param {Object} entry Entry object + * @return {boolean} Whether hash matches entry.path + */ + function matchRoute( hash, entry ) { + var match = hash.match( entry.path ); + if ( match ) { + entry.callback.apply( this, match.slice( 1 ) ); + return true; + } + return false; + } + + /** + * Provides navigation routing and location information + * + * @class Router + * @mixins OO.EventEmitter + */ + function Router() { + var self = this; + OO.EventEmitter.call( this ); + // use an object instead of an array for routes so that we don't + // duplicate entries that already exist + this.routes = {}; + this.enabled = true; + this.oldHash = this.getPath(); + + $( window ).on( 'popstate', function () { + self.emit( 'popstate' ); + } ); + + $( window ).on( 'hashchange', function () { + self.emit( 'hashchange' ); + } ); + + this.on( 'hashchange', function () { + // ev.originalEvent.newURL is undefined on Android 2.x + var routeEv; + + if ( self.enabled ) { + routeEv = $.Event( 'route', { + path: self.getPath() + } ); + self.emit( 'route', routeEv ); + + if ( !routeEv.isDefaultPrevented() ) { + self.checkRoute(); + } else { + // if route was prevented, ignore the next hash change and revert the + // hash to its old value + self.enabled = false; + self.navigate( self.oldHash ); + } + } else { + self.enabled = true; + } + + self.oldHash = self.getPath(); + } ); + } + OO.mixinClass( Router, OO.EventEmitter ); + + /** + * Check the current route and run appropriate callback if it matches. + * + * @method + */ + Router.prototype.checkRoute = function () { + var hash = this.getPath(); + + $.each( this.routes, function ( id, entry ) { + return !matchRoute( hash, entry ); + } ); + }; + + /** + * Bind a specific callback to a hash-based route, e.g. + * + * @example + * route( 'alert', function () { alert( 'something' ); } ); + * route( /hi-(.*)/, function ( name ) { alert( 'Hi ' + name ) } ); + * + * @method + * @param {Object} path string or RegExp to match. + * @param {Function} callback Callback to be run when hash changes to one + * that matches. + */ + Router.prototype.route = function ( path, callback ) { + var entry = { + path: typeof path === 'string' ? new RegExp( '^' + path + '$' ) : path, + callback: callback + }; + this.routes[ entry.path ] = entry; + matchRoute( this.getPath(), entry ); + }; + + /** + * Navigate to a specific route. This is only a wrapper for changing the + * hash now. + * + * @method + * @param {string} path string with a route (hash without #). + */ + Router.prototype.navigate = function ( path ) { + window.location.hash = path; + }; + + /** + * Triggers back on the window + */ + Router.prototype.goBack = function () { + window.history.back(); + }; + + /** + * Navigate to the previous route. This is a wrapper for window.history.back + * + * @method + * @return {jQuery.Deferred} + */ + Router.prototype.back = function () { + var deferredRequest = $.Deferred(), + self = this, + timeoutID; + + this.once( 'popstate', function () { + clearTimeout( timeoutID ); + deferredRequest.resolve(); + } ); + + this.goBack(); + + // If for some reason (old browser, bug in IE/windows 8.1, etc) popstate doesn't fire, + // resolve manually. Since we don't know for sure which browsers besides IE10/11 have + // this problem, it's better to fall back this way rather than singling out browsers + // and resolving the deferred request for them individually. + // See https://connect.microsoft.com/IE/feedback/details/793618/history-back-popstate-not-working-as-expected-in-webview-control + // Give browser a few ms to update its history. + timeoutID = setTimeout( function () { + self.off( 'popstate' ); + deferredRequest.resolve(); + }, 50 ); + + return deferredRequest; + }; + + /** + * Get current path (hash). + * + * @method + * @return {string} Current path. + */ + Router.prototype.getPath = function () { + return window.location.hash.slice( 1 ); + }; + + /** + * Determine if current browser supports onhashchange event + * + * @method + * @return {boolean} + */ + Router.prototype.isSupported = function () { + return 'onhashchange' in window; + }; + + return new Router(); + +}( jQuery ) ); diff --git a/modules/kartographer.MapDialog.js b/modules/kartographer.MapDialog.js index fd69c1c..0233fdd 100644 --- a/modules/kartographer.MapDialog.js +++ b/modules/kartographer.MapDialog.js @@ -27,6 +27,22 @@ this.$body.append( this.$map ); }; +/** + * Changes the map within the map dialog. + * + * @param {Object} data The data for the new map. + */ +mw.kartographer.MapDialog.prototype.changeMap = function ( data ) { + + $( '.mw-kartographer-mapDialog-map' ).remove(); + + this.$map = $( '<div>' ).addClass( 'mw-kartographer-mapDialog-map' ); + this.map = null; + + this.$body.append( this.$map ); + this.setup.call( this, data ); +}; + mw.kartographer.MapDialog.prototype.getSetupProcess = function ( data ) { return mw.kartographer.MapDialog.super.prototype.getSetupProcess.call( this, data ) .next( function () { diff --git a/modules/kartographer.js b/modules/kartographer.js index 40f2f18..6ad426e 100644 --- a/modules/kartographer.js +++ b/modules/kartographer.js @@ -35,6 +35,13 @@ mw.kartographer = {}; + /** + * References the map containers of the page. + * + * @type {Array} + */ + mw.kartographer.maps = []; + mw.kartographer.FullScreenControl = L.Control.extend( { options: { // Do not switch for RTL because zoom also stays in place @@ -45,7 +52,7 @@ var container = L.DomUtil.create( 'div', 'leaflet-control-mapbox-share leaflet-bar' ), link = L.DomUtil.create( 'a', 'mapbox-share mapbox-icon mapbox-icon-share', container ); - link.href = '#'; + link.href = '#' + this.options.fullScreenRoute; link.title = mw.msg( 'kartographer-fullscreen-text' ); this.map = map; @@ -57,7 +64,12 @@ onShowFullScreen: function ( e ) { L.DomEvent.stop( e ); - mw.kartographer.openFullscreenMap( this.options.mapPositionData, this.map ); + + if ( OO && OO.Router && OO.Router.isSupported() ) { + OO.Router.navigate( '#' + this.options.fullScreenRoute ); + } else { + mw.kartographer.openFullscreenMap( this.options.mapPositionData, this.map ); + } } } ); @@ -92,8 +104,11 @@ map.setView( [ data.latitude, data.longitude ], data.zoom ); map.attributionControl.setPrefix( '' ); - if ( data.enableFullScreenButton ) { - map.addControl( new mw.kartographer.FullScreenControl( { mapPositionData: data } ) ); + if ( data.enableFullScreenButton && data.hasOwnProperty( 'articleMapId' ) ) { + map.addControl( new mw.kartographer.FullScreenControl( { + mapPositionData: data, + fullScreenRoute: 'map/' + data.articleMapId + } ) ); } L.tileLayer( mapServer + '/' + style + urlFormat, { @@ -211,12 +226,21 @@ } // full screen map should never show "full screen" button data.enableFullScreenButton = false; + if ( mapDialog ) { + mapDialog.changeMap( data ); + return; + } getWindowManager() .openWindow( mapDialog, data ) .then( function ( opened ) { return opened; } ) .then( function ( closing ) { if ( map ) { map.setView( mapDialog.map.getCenter(), mapDialog.map.getZoom() ); + } + windowManager = mapDialog = undefined; + + if ( OO && OO.Router && OO.Router.isSupported() ) { + OO.Router.navigate( '' ); } return closing; } ); @@ -312,22 +336,49 @@ sleepOpacity: 1 } ); - $content.find( '.mw-kartographer-interactive' ).each( function () { + $content.find( '.mw-kartographer-interactive' ).each( function ( index ) { var map, $this = $( this ), data = getMapProps( $this ); if ( data ) { + data.articleMapId = index; data.enableFullScreenButton = true; map = mw.kartographer.createMap( this, data ); + map.doubleClickZoom.disable(); + + mw.kartographer.maps[ index ] = $this; mw.hook( 'wikipage.maps' ).fire( map, false /* isFullScreen */ ); $this.on( 'dblclick', function () { - mw.kartographer.openFullscreenMap( data, map ); + if ( OO && OO.Router && OO.Router.isSupported() ) { + OO.Router.navigate( '#map/' + data.articleMapId ); + } else { + mw.kartographer.openFullscreenMap( data, map ); + } } ); } } ); + + mw.loader.using( 'oo-router' ).done( function () { + + // Opens map in full screen. Example: #map/0 + OO.Router.route( 'map/([0-9]+)', function ( mapId ) { + var $map = $( mw.kartographer.maps[ mapId ] ); + if ( $map.length ) { + mw.kartographer.openFullscreenMap( getMapProps( $map ) ); + } + } ); + + // Index route + OO.Router.route( '', function () { + if ( mapDialog ) { + mapDialog.close(); + } + } ); + + } ); } ); }( jQuery, mediaWiki ) ); -- To view, visit https://gerrit.wikimedia.org/r/283374 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I456a4582a67e31533d51d5817d0f4af57528c35e 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
