@@ -5,31 +5,71 @@ import { select as d3_select } from 'd3-selection';
55import { geoSphericalDistance } from '../geo' ;
66import { modeBrowse } from '../modes/browse' ;
77import { modeSelect , modeSelectNote } from '../modes' ;
8- import { utilObjectOmit , utilQsString , utilStringQs } from '../util' ;
8+ import { utilQsString , utilStringQs } from '../util' ;
99import { utilArrayIdentical } from '../util/array' ;
1010import { utilDisplayLabel } from '../util/utilDisplayLabel' ;
1111import { localizer , t } from '../core/localizer' ;
1212import { prefs } from '../core/preferences' ;
1313
14+ function getNewHash ( arg ) {
15+ const original = utilStringQs ( window . location . hash ) ;
16+ const update = typeof arg === 'function' ? arg ( original ) : arg ;
17+ if ( ! update || typeof update !== 'object' ) return ;
18+
19+ const updated = { ...original , ...update } ;
20+ Object . keys ( update )
21+ . filter ( key => update [ key ] === null || update [ key ] === undefined )
22+ . forEach ( key => delete updated [ key ] ) ;
23+
24+ return '#' + utilQsString ( updated , true ) ;
25+ }
26+
27+ /**
28+ * Updates the URL hash by applying a partial patch.
29+ *
30+ * Keys with nullish values will be removed from the hash.
31+ *
32+ * @param {(?Object<string, any>|function (Object<string, any>): Object<string, any>) } updater Either
33+ * - a plain object of key/value pairs to merge into the hash, or
34+ * - a function `(currentHash) => patchObject` that returns such an object.
35+ * @returns {boolean } Whether the hash was updated.
36+ */
37+ export function patchHash ( updater ) {
38+ if ( ! updater || ! [ 'function' , 'object' ] . includes ( typeof updater ) ) return false ;
39+
40+ const latestHash = getNewHash ( updater ) ;
41+ if ( ! latestHash || window . location . hash === latestHash ) return false ;
42+
43+ // Update the URL hash without affecting the browser navigation stack,
44+ // though unavoidably creating a browser history entry
45+ window . history . replaceState ( null , '' , latestHash ) ;
46+
47+ // save last used map location for future
48+ const { map } = utilStringQs ( latestHash ) ;
49+ if ( map ) prefs ( 'map-location' , map ) ;
50+ return true ;
51+ }
1452
1553export function behaviorHash ( context ) {
1654
17- // cached window.location.hash
18- var _cachedHash = null ;
1955 // allowable latitude range
2056 var _latitudeLimit = 90 - 1e-8 ;
2157
22- function computedHashParameters ( ) {
58+ function computeHashUpdate ( ) {
59+ if ( context . inIntro ( ) ) return null ;
60+
2361 var map = context . map ( ) ;
2462 var center = map . center ( ) ;
2563 var zoom = map . zoom ( ) ;
2664 var precision = Math . max ( 0 , Math . ceil ( Math . log ( zoom ) / Math . LN2 ) ) ;
27- var oldParams = utilObjectOmit ( utilStringQs ( window . location . hash ) ,
28- [ 'comment' , 'source' , 'hashtags' , 'walkthrough' ]
29- ) ;
30- var newParams = { } ;
65+ const newParams = {
66+ comment : null ,
67+ source : null ,
68+ hashtags : null ,
69+ walkthrough : null ,
70+ id : null
71+ } ;
3172
32- delete oldParams . id ;
3373 var selected = context . selectedIDs ( ) . filter ( function ( id ) {
3474 return context . hasEntity ( id ) ;
3575 } ) ;
@@ -43,11 +83,7 @@ export function behaviorHash(context) {
4383 '/' + center [ 1 ] . toFixed ( precision ) +
4484 '/' + center [ 0 ] . toFixed ( precision ) ;
4585
46- return Object . assign ( oldParams , newParams ) ;
47- }
48-
49- function computedHash ( ) {
50- return '#' + utilQsString ( computedHashParameters ( ) , true ) ;
86+ return newParams ;
5187 }
5288
5389 function computedTitle ( includeChangeCount ) {
@@ -91,7 +127,7 @@ export function behaviorHash(context) {
91127 return baseTitle ;
92128 }
93129
94- function updateTitle ( includeChangeCount ) {
130+ function updateTitle ( includeChangeCount = true ) {
95131 if ( ! context . setsDocumentTitle ( ) ) return ;
96132
97133 var newTitle = computedTitle ( includeChangeCount ) ;
@@ -100,40 +136,14 @@ export function behaviorHash(context) {
100136 }
101137 }
102138
103- function updateHashIfNeeded ( ) {
104- if ( context . inIntro ( ) ) return ;
105-
106- var latestHash = computedHash ( ) ;
107- if ( _cachedHash !== latestHash ) {
108- _cachedHash = latestHash ;
109-
110- // Update the URL hash without affecting the browser navigation stack,
111- // though unavoidably creating a browser history entry
112- window . history . replaceState ( null , '' , latestHash ) ;
113-
114- // set the title we want displayed for the browser tab/window
115- updateTitle ( true /* includeChangeCount */ ) ;
116-
117- // save last used map location for future
118- const q = utilStringQs ( latestHash ) ;
119- if ( q . map ) {
120- prefs ( 'map-location' , q . map ) ;
121- }
122- }
123- }
124-
125- var _throttledUpdate = throttle ( updateHashIfNeeded , 500 ) ;
126- var _throttledUpdateTitle = throttle ( function ( ) {
127- updateTitle ( true /* includeChangeCount */ ) ;
139+ var _throttledUpdate = throttle ( ( ) => {
140+ patchHash ( computeHashUpdate ) ;
141+ updateTitle ( ) ;
128142 } , 500 ) ;
143+ var _throttledUpdateTitle = throttle ( updateTitle , 500 ) ;
129144
130145 function hashchange ( ) {
131- // ignore spurious hashchange events
132- if ( window . location . hash === _cachedHash ) return ;
133-
134- _cachedHash = window . location . hash ;
135-
136- var q = utilStringQs ( _cachedHash ) ;
146+ var q = utilStringQs ( window . location . hash ) ;
137147
138148 if ( q . theme ) {
139149 context . theme ( q . theme ) ;
@@ -147,11 +157,11 @@ export function behaviorHash(context) {
147157 var mapArgs = ( q . map || '' ) . split ( '/' ) . map ( Number ) ;
148158 if ( mapArgs . length < 3 || mapArgs . some ( isNaN ) ) {
149159 // replace bogus hash
150- updateHashIfNeeded ( ) ;
151-
160+ patchHash ( computeHashUpdate ) ;
161+ updateTitle ( ) ;
152162 } else {
153163 // don't update if the new hash already reflects the state of iD
154- if ( _cachedHash === computedHash ( ) ) return ;
164+ if ( window . location . hash === getNewHash ( computeHashUpdate ) ) return ;
155165
156166 var mode = context . mode ( ) ;
157167
@@ -224,14 +234,14 @@ export function behaviorHash(context) {
224234 const mapArgs = prefs ( 'map-location' ) . split ( '/' ) . map ( Number ) ;
225235 context . map ( ) . centerZoom ( [ mapArgs [ 2 ] , Math . min ( _latitudeLimit , Math . max ( - _latitudeLimit , mapArgs [ 1 ] ) ) ] , mapArgs [ 0 ] ) ;
226236
227- updateHashIfNeeded ( ) ;
237+ patchHash ( computeHashUpdate ) ;
228238
229239 behavior . hadLocation = true ;
230240 }
231241
232242 hashchange ( ) ;
233243
234- updateTitle ( false ) ;
244+ updateTitle ( false /* includeChangeCount */ ) ;
235245 }
236246
237247 behavior . off = function ( ) {
0 commit comments