324 lines
9.8 KiB
JavaScript
324 lines
9.8 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
exports.__esModule = true;
|
||
|
|
|
||
|
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||
|
|
|
||
|
|
var _warning = require('warning');
|
||
|
|
|
||
|
|
var _warning2 = _interopRequireDefault(_warning);
|
||
|
|
|
||
|
|
var _invariant = require('invariant');
|
||
|
|
|
||
|
|
var _invariant2 = _interopRequireDefault(_invariant);
|
||
|
|
|
||
|
|
var _LocationUtils = require('./LocationUtils');
|
||
|
|
|
||
|
|
var _PathUtils = require('./PathUtils');
|
||
|
|
|
||
|
|
var _createTransitionManager = require('./createTransitionManager');
|
||
|
|
|
||
|
|
var _createTransitionManager2 = _interopRequireDefault(_createTransitionManager);
|
||
|
|
|
||
|
|
var _DOMUtils = require('./DOMUtils');
|
||
|
|
|
||
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
|
|
||
|
|
var HashChangeEvent = 'hashchange';
|
||
|
|
|
||
|
|
var HashPathCoders = {
|
||
|
|
hashbang: {
|
||
|
|
encodePath: function encodePath(path) {
|
||
|
|
return path.charAt(0) === '!' ? path : '!/' + (0, _PathUtils.stripLeadingSlash)(path);
|
||
|
|
},
|
||
|
|
decodePath: function decodePath(path) {
|
||
|
|
return path.charAt(0) === '!' ? path.substr(1) : path;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
noslash: {
|
||
|
|
encodePath: _PathUtils.stripLeadingSlash,
|
||
|
|
decodePath: _PathUtils.addLeadingSlash
|
||
|
|
},
|
||
|
|
slash: {
|
||
|
|
encodePath: _PathUtils.addLeadingSlash,
|
||
|
|
decodePath: _PathUtils.addLeadingSlash
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var getHashPath = function getHashPath() {
|
||
|
|
// We can't use window.location.hash here because it's not
|
||
|
|
// consistent across browsers - Firefox will pre-decode it!
|
||
|
|
var href = window.location.href;
|
||
|
|
var hashIndex = href.indexOf('#');
|
||
|
|
return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
|
||
|
|
};
|
||
|
|
|
||
|
|
var pushHashPath = function pushHashPath(path) {
|
||
|
|
return window.location.hash = path;
|
||
|
|
};
|
||
|
|
|
||
|
|
var replaceHashPath = function replaceHashPath(path) {
|
||
|
|
var hashIndex = window.location.href.indexOf('#');
|
||
|
|
|
||
|
|
window.location.replace(window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + '#' + path);
|
||
|
|
};
|
||
|
|
|
||
|
|
var createHashHistory = function createHashHistory() {
|
||
|
|
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||
|
|
|
||
|
|
(0, _invariant2.default)(_DOMUtils.canUseDOM, 'Hash history needs a DOM');
|
||
|
|
|
||
|
|
var globalHistory = window.history;
|
||
|
|
var canGoWithoutReload = (0, _DOMUtils.supportsGoWithoutReloadUsingHash)();
|
||
|
|
|
||
|
|
var _props$getUserConfirm = props.getUserConfirmation,
|
||
|
|
getUserConfirmation = _props$getUserConfirm === undefined ? _DOMUtils.getConfirmation : _props$getUserConfirm,
|
||
|
|
_props$hashType = props.hashType,
|
||
|
|
hashType = _props$hashType === undefined ? 'slash' : _props$hashType;
|
||
|
|
|
||
|
|
var basename = props.basename ? (0, _PathUtils.stripTrailingSlash)((0, _PathUtils.addLeadingSlash)(props.basename)) : '';
|
||
|
|
|
||
|
|
var _HashPathCoders$hashT = HashPathCoders[hashType],
|
||
|
|
encodePath = _HashPathCoders$hashT.encodePath,
|
||
|
|
decodePath = _HashPathCoders$hashT.decodePath;
|
||
|
|
|
||
|
|
|
||
|
|
var getDOMLocation = function getDOMLocation() {
|
||
|
|
var path = decodePath(getHashPath());
|
||
|
|
|
||
|
|
(0, _warning2.default)(!basename || (0, _PathUtils.hasBasename)(path, basename), 'You are attempting to use a basename on a page whose URL path does not begin ' + 'with the basename. Expected path "' + path + '" to begin with "' + basename + '".');
|
||
|
|
|
||
|
|
if (basename) path = (0, _PathUtils.stripBasename)(path, basename);
|
||
|
|
|
||
|
|
return (0, _LocationUtils.createLocation)(path);
|
||
|
|
};
|
||
|
|
|
||
|
|
var transitionManager = (0, _createTransitionManager2.default)();
|
||
|
|
|
||
|
|
var setState = function setState(nextState) {
|
||
|
|
_extends(history, nextState);
|
||
|
|
|
||
|
|
history.length = globalHistory.length;
|
||
|
|
|
||
|
|
transitionManager.notifyListeners(history.location, history.action);
|
||
|
|
};
|
||
|
|
|
||
|
|
var forceNextPop = false;
|
||
|
|
var ignorePath = null;
|
||
|
|
|
||
|
|
var handleHashChange = function handleHashChange() {
|
||
|
|
var path = getHashPath();
|
||
|
|
var encodedPath = encodePath(path);
|
||
|
|
|
||
|
|
if (path !== encodedPath) {
|
||
|
|
// Ensure we always have a properly-encoded hash.
|
||
|
|
replaceHashPath(encodedPath);
|
||
|
|
} else {
|
||
|
|
var location = getDOMLocation();
|
||
|
|
var prevLocation = history.location;
|
||
|
|
|
||
|
|
if (!forceNextPop && (0, _LocationUtils.locationsAreEqual)(prevLocation, location)) return; // A hashchange doesn't always == location change.
|
||
|
|
|
||
|
|
if (ignorePath === (0, _PathUtils.createPath)(location)) return; // Ignore this change; we already setState in push/replace.
|
||
|
|
|
||
|
|
ignorePath = null;
|
||
|
|
|
||
|
|
handlePop(location);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var handlePop = function handlePop(location) {
|
||
|
|
if (forceNextPop) {
|
||
|
|
forceNextPop = false;
|
||
|
|
setState();
|
||
|
|
} else {
|
||
|
|
var action = 'POP';
|
||
|
|
|
||
|
|
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
|
||
|
|
if (ok) {
|
||
|
|
setState({ action: action, location: location });
|
||
|
|
} else {
|
||
|
|
revertPop(location);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var revertPop = function revertPop(fromLocation) {
|
||
|
|
var toLocation = history.location;
|
||
|
|
|
||
|
|
// TODO: We could probably make this more reliable by
|
||
|
|
// keeping a list of paths we've seen in sessionStorage.
|
||
|
|
// Instead, we just default to 0 for paths we don't know.
|
||
|
|
|
||
|
|
var toIndex = allPaths.lastIndexOf((0, _PathUtils.createPath)(toLocation));
|
||
|
|
|
||
|
|
if (toIndex === -1) toIndex = 0;
|
||
|
|
|
||
|
|
var fromIndex = allPaths.lastIndexOf((0, _PathUtils.createPath)(fromLocation));
|
||
|
|
|
||
|
|
if (fromIndex === -1) fromIndex = 0;
|
||
|
|
|
||
|
|
var delta = toIndex - fromIndex;
|
||
|
|
|
||
|
|
if (delta) {
|
||
|
|
forceNextPop = true;
|
||
|
|
go(delta);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Ensure the hash is encoded properly before doing anything else.
|
||
|
|
var path = getHashPath();
|
||
|
|
var encodedPath = encodePath(path);
|
||
|
|
|
||
|
|
if (path !== encodedPath) replaceHashPath(encodedPath);
|
||
|
|
|
||
|
|
var initialLocation = getDOMLocation();
|
||
|
|
var allPaths = [(0, _PathUtils.createPath)(initialLocation)];
|
||
|
|
|
||
|
|
// Public interface
|
||
|
|
|
||
|
|
var createHref = function createHref(location) {
|
||
|
|
return '#' + encodePath(basename + (0, _PathUtils.createPath)(location));
|
||
|
|
};
|
||
|
|
|
||
|
|
var push = function push(path, state) {
|
||
|
|
(0, _warning2.default)(state === undefined, 'Hash history cannot push state; it is ignored');
|
||
|
|
|
||
|
|
var action = 'PUSH';
|
||
|
|
var location = (0, _LocationUtils.createLocation)(path, undefined, undefined, history.location);
|
||
|
|
|
||
|
|
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
|
||
|
|
if (!ok) return;
|
||
|
|
|
||
|
|
var path = (0, _PathUtils.createPath)(location);
|
||
|
|
var encodedPath = encodePath(basename + path);
|
||
|
|
var hashChanged = getHashPath() !== encodedPath;
|
||
|
|
|
||
|
|
if (hashChanged) {
|
||
|
|
// We cannot tell if a hashchange was caused by a PUSH, so we'd
|
||
|
|
// rather setState here and ignore the hashchange. The caveat here
|
||
|
|
// is that other hash histories in the page will consider it a POP.
|
||
|
|
ignorePath = path;
|
||
|
|
pushHashPath(encodedPath);
|
||
|
|
|
||
|
|
var prevIndex = allPaths.lastIndexOf((0, _PathUtils.createPath)(history.location));
|
||
|
|
var nextPaths = allPaths.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
|
||
|
|
|
||
|
|
nextPaths.push(path);
|
||
|
|
allPaths = nextPaths;
|
||
|
|
|
||
|
|
setState({ action: action, location: location });
|
||
|
|
} else {
|
||
|
|
(0, _warning2.default)(false, 'Hash history cannot PUSH the same path; a new entry will not be added to the history stack');
|
||
|
|
|
||
|
|
setState();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
var replace = function replace(path, state) {
|
||
|
|
(0, _warning2.default)(state === undefined, 'Hash history cannot replace state; it is ignored');
|
||
|
|
|
||
|
|
var action = 'REPLACE';
|
||
|
|
var location = (0, _LocationUtils.createLocation)(path, undefined, undefined, history.location);
|
||
|
|
|
||
|
|
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
|
||
|
|
if (!ok) return;
|
||
|
|
|
||
|
|
var path = (0, _PathUtils.createPath)(location);
|
||
|
|
var encodedPath = encodePath(basename + path);
|
||
|
|
var hashChanged = getHashPath() !== encodedPath;
|
||
|
|
|
||
|
|
if (hashChanged) {
|
||
|
|
// We cannot tell if a hashchange was caused by a REPLACE, so we'd
|
||
|
|
// rather setState here and ignore the hashchange. The caveat here
|
||
|
|
// is that other hash histories in the page will consider it a POP.
|
||
|
|
ignorePath = path;
|
||
|
|
replaceHashPath(encodedPath);
|
||
|
|
}
|
||
|
|
|
||
|
|
var prevIndex = allPaths.indexOf((0, _PathUtils.createPath)(history.location));
|
||
|
|
|
||
|
|
if (prevIndex !== -1) allPaths[prevIndex] = path;
|
||
|
|
|
||
|
|
setState({ action: action, location: location });
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
var go = function go(n) {
|
||
|
|
(0, _warning2.default)(canGoWithoutReload, 'Hash history go(n) causes a full page reload in this browser');
|
||
|
|
|
||
|
|
globalHistory.go(n);
|
||
|
|
};
|
||
|
|
|
||
|
|
var goBack = function goBack() {
|
||
|
|
return go(-1);
|
||
|
|
};
|
||
|
|
|
||
|
|
var goForward = function goForward() {
|
||
|
|
return go(1);
|
||
|
|
};
|
||
|
|
|
||
|
|
var listenerCount = 0;
|
||
|
|
|
||
|
|
var checkDOMListeners = function checkDOMListeners(delta) {
|
||
|
|
listenerCount += delta;
|
||
|
|
|
||
|
|
if (listenerCount === 1) {
|
||
|
|
(0, _DOMUtils.addEventListener)(window, HashChangeEvent, handleHashChange);
|
||
|
|
} else if (listenerCount === 0) {
|
||
|
|
(0, _DOMUtils.removeEventListener)(window, HashChangeEvent, handleHashChange);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
var isBlocked = false;
|
||
|
|
|
||
|
|
var block = function block() {
|
||
|
|
var prompt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||
|
|
|
||
|
|
var unblock = transitionManager.setPrompt(prompt);
|
||
|
|
|
||
|
|
if (!isBlocked) {
|
||
|
|
checkDOMListeners(1);
|
||
|
|
isBlocked = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return function () {
|
||
|
|
if (isBlocked) {
|
||
|
|
isBlocked = false;
|
||
|
|
checkDOMListeners(-1);
|
||
|
|
}
|
||
|
|
|
||
|
|
return unblock();
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
var listen = function listen(listener) {
|
||
|
|
var unlisten = transitionManager.appendListener(listener);
|
||
|
|
checkDOMListeners(1);
|
||
|
|
|
||
|
|
return function () {
|
||
|
|
checkDOMListeners(-1);
|
||
|
|
unlisten();
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
var history = {
|
||
|
|
length: globalHistory.length,
|
||
|
|
action: 'POP',
|
||
|
|
location: initialLocation,
|
||
|
|
createHref: createHref,
|
||
|
|
push: push,
|
||
|
|
replace: replace,
|
||
|
|
go: go,
|
||
|
|
goBack: goBack,
|
||
|
|
goForward: goForward,
|
||
|
|
block: block,
|
||
|
|
listen: listen
|
||
|
|
};
|
||
|
|
|
||
|
|
return history;
|
||
|
|
};
|
||
|
|
|
||
|
|
exports.default = createHashHistory;
|