mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 14:00:56 +01:00
ad1bed136c
Summary: Fixes T7058. We weren't propagating `state` properly so some other code ended up doing the wrong thing. Test Plan: - Clicked from Home -> Anything -> Home under Quicksand, saw reloads with no double requests. - Used "back", saw back button work properly. Reviewers: btrahan, chad Reviewed By: chad Subscribers: epriestley Maniphest Tasks: T7058 Differential Revision: https://secure.phabricator.com/D12190
226 lines
6.7 KiB
JavaScript
226 lines
6.7 KiB
JavaScript
/**
|
|
* @requires javelin-stratcom
|
|
* javelin-install
|
|
* javelin-uri
|
|
* javelin-util
|
|
* @provides javelin-history
|
|
* @javelin
|
|
*/
|
|
|
|
/**
|
|
* JX.History provides a stable interface for managing the browser's history
|
|
* stack. Whenever the history stack mutates, the "history:change" event is
|
|
* invoked via JX.Stratcom.
|
|
*
|
|
* Inspired by History Manager implemented by Christoph Pojer (@cpojer)
|
|
* @see https://github.com/cpojer/mootools-history
|
|
*/
|
|
JX.install('History', {
|
|
|
|
statics : {
|
|
|
|
// Mechanisms to @{JX.History.install} with (in preferred support order).
|
|
// The default behavior is to use the best supported mechanism.
|
|
DEFAULT : Infinity,
|
|
PUSHSTATE : 3,
|
|
HASHCHANGE : 2,
|
|
POLLING : 1,
|
|
|
|
// Last path parsed from the URL fragment.
|
|
_hash : null,
|
|
|
|
// Some browsers fire an extra "popstate" on initial page load, so we keep
|
|
// track of the initial path to normalize behavior (and not fire the extra
|
|
// event).
|
|
_initialPath : null,
|
|
|
|
// Mechanism used to interface with the browser history stack.
|
|
_mechanism : null,
|
|
|
|
/**
|
|
* Starts history management. This method must be invoked first before any
|
|
* other JX.History method can be used.
|
|
*
|
|
* @param int An optional mechanism used to interface with the browser
|
|
* history stack. If it is not supported, the next supported
|
|
* mechanism will be used.
|
|
*/
|
|
install : function(mechanism) {
|
|
if (__DEV__) {
|
|
if (JX.History._installed) {
|
|
JX.$E('JX.History.install(): can only install once.');
|
|
}
|
|
JX.History._installed = true;
|
|
}
|
|
|
|
mechanism = mechanism || JX.History.DEFAULT;
|
|
|
|
if (mechanism >= JX.History.PUSHSTATE && 'pushState' in history) {
|
|
JX.History._mechanism = JX.History.PUSHSTATE;
|
|
JX.History._initialPath = JX.History._getBasePath(location.href);
|
|
JX.Stratcom.listen('popstate', null, JX.History._handleChange);
|
|
} else if (mechanism >= JX.History.HASHCHANGE &&
|
|
'onhashchange' in window) {
|
|
JX.History._mechanism = JX.History.HASHCHANGE;
|
|
JX.Stratcom.listen('hashchange', null, JX.History._handleChange);
|
|
} else {
|
|
JX.History._mechanism = JX.History.POLLING;
|
|
setInterval(JX.History._handleChange, 200);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the name of the mechanism used to interface with the browser
|
|
* history stack.
|
|
*
|
|
* @return string Mechanism, either pushstate, hashchange, or polling.
|
|
*/
|
|
getMechanism : function() {
|
|
if (__DEV__) {
|
|
if (!JX.History._installed) {
|
|
JX.$E(
|
|
'JX.History.getMechanism(): ' +
|
|
'must call JX.History.install() first.');
|
|
}
|
|
}
|
|
return JX.History._mechanism;
|
|
},
|
|
|
|
/**
|
|
* Returns the path on top of the history stack.
|
|
*
|
|
* If the HTML5 History API is unavailable and an eligible path exists in
|
|
* the current URL fragment, the fragment is parsed for a path. Otherwise,
|
|
* the current URL path is returned.
|
|
*
|
|
* @return string Path on top of the history stack.
|
|
*/
|
|
getPath : function() {
|
|
if (__DEV__) {
|
|
if (!JX.History._installed) {
|
|
JX.$E(
|
|
'JX.History.getPath(): ' +
|
|
'must call JX.History.install() first.');
|
|
}
|
|
}
|
|
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
|
|
return JX.History._getBasePath(location.href);
|
|
} else {
|
|
var parsed = JX.History._parseFragment(location.hash);
|
|
return parsed || JX.History._getBasePath(location.href);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Pushes a path onto the history stack.
|
|
*
|
|
* @param string Path.
|
|
* @param wild State object for History API.
|
|
* @return void
|
|
*/
|
|
push : function(path, state) {
|
|
if (__DEV__) {
|
|
if (!JX.History._installed) {
|
|
JX.$E(
|
|
'JX.History.push(): ' +
|
|
'must call JX.History.install() first.');
|
|
}
|
|
}
|
|
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
|
|
if (JX.History._initialPath && JX.History._initialPath !== path) {
|
|
JX.History._initialPath = null;
|
|
}
|
|
history.pushState(state || null, null, path);
|
|
JX.History._fire(path, state);
|
|
} else {
|
|
location.hash = JX.History._composeFragment(path);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Modifies the path on top of the history stack.
|
|
*
|
|
* @param string Path.
|
|
* @return void
|
|
*/
|
|
replace : function(path) {
|
|
if (__DEV__) {
|
|
if (!JX.History._installed) {
|
|
JX.$E(
|
|
'JX.History.replace(): ' +
|
|
'must call JX.History.install() first.');
|
|
}
|
|
}
|
|
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
|
|
history.replaceState(null, null, path);
|
|
JX.History._fire(path);
|
|
} else {
|
|
var uri = JX.$U(location.href);
|
|
uri.setFragment(JX.History._composeFragment(path));
|
|
// Safari bug: "location.replace" does not respect changes made via
|
|
// setting "location.hash", so use "history.replaceState" if possible.
|
|
if ('replaceState' in history) {
|
|
history.replaceState(null, null, uri.toString());
|
|
JX.History._handleChange();
|
|
} else {
|
|
location.replace(uri.toString());
|
|
}
|
|
}
|
|
},
|
|
|
|
_handleChange : function(e) {
|
|
var path = JX.History.getPath();
|
|
var state = (e && e.getRawEvent().state);
|
|
|
|
if (JX.History.getMechanism() === JX.History.PUSHSTATE) {
|
|
if (path === JX.History._initialPath) {
|
|
JX.History._initialPath = null;
|
|
} else {
|
|
JX.History._fire(path, state);
|
|
}
|
|
} else {
|
|
if (path !== JX.History._hash) {
|
|
JX.History._hash = path;
|
|
JX.History._fire(path);
|
|
}
|
|
}
|
|
},
|
|
|
|
_fire : function(path, state) {
|
|
JX.Stratcom.invoke('history:change', null, {
|
|
path: JX.History._getBasePath(path),
|
|
state: state
|
|
});
|
|
},
|
|
|
|
_getBasePath : function(href) {
|
|
return JX.$U(href).setProtocol(null).setDomain(null).toString();
|
|
},
|
|
|
|
_composeFragment : function(path) {
|
|
path = JX.History._getBasePath(path);
|
|
// If the URL fragment does not change, the new path will not get pushed
|
|
// onto the stack. So we alternate the hash prefix to force a new state.
|
|
if (JX.History.getPath() === path) {
|
|
var hash = location.hash;
|
|
if (hash && hash.charAt(1) === '!') {
|
|
return '~!' + path;
|
|
}
|
|
}
|
|
return '!' + path;
|
|
},
|
|
|
|
_parseFragment : function(fragment) {
|
|
if (fragment) {
|
|
if (fragment.charAt(1) === '!') {
|
|
return fragment.substr(2);
|
|
} else if (fragment.substr(1, 2) === '~!') {
|
|
return fragment.substr(3);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
});
|