UNPKG

twreporter-react

Version:

React-Redux site for The Reporter Foundation in Taiwan

291 lines (233 loc) 10 kB
'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; }; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } var _warning = require('warning'); var _warning2 = _interopRequireDefault(_warning); var _historyLibActions = require('history/lib/Actions'); var _historyLibUseQueries = require('history/lib/useQueries'); var _historyLibUseQueries2 = _interopRequireDefault(_historyLibUseQueries); var _computeChangedRoutes2 = require('./computeChangedRoutes'); var _computeChangedRoutes3 = _interopRequireDefault(_computeChangedRoutes2); var _TransitionUtils = require('./TransitionUtils'); var _isActive2 = require('./isActive'); var _isActive3 = _interopRequireDefault(_isActive2); var _getComponents = require('./getComponents'); var _getComponents2 = _interopRequireDefault(_getComponents); var _matchRoutes = require('./matchRoutes'); var _matchRoutes2 = _interopRequireDefault(_matchRoutes); function hasAnyProperties(object) { for (var p in object) { if (object.hasOwnProperty(p)) return true; }return false; } /** * Returns a new createHistory function that may be used to create * history objects that know about routing. * * Enhances history objects with the following methods: * * - listen((error, nextState) => {}) * - listenBeforeLeavingRoute(route, (nextLocation) => {}) * - match(location, (error, redirectLocation, nextState) => {}) * - isActive(pathname, query, indexOnly=false) */ function useRoutes(createHistory) { return function () { var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var routes = options.routes; var historyOptions = _objectWithoutProperties(options, ['routes']); var history = _historyLibUseQueries2['default'](createHistory)(historyOptions); var state = {}; function isActive(pathname, query) { var indexOnly = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; return _isActive3['default'](pathname, query, indexOnly, state.location, state.routes, state.params); } function createLocationFromRedirectInfo(_ref) { var pathname = _ref.pathname; var query = _ref.query; var state = _ref.state; return history.createLocation(history.createPath(pathname, query), state, _historyLibActions.REPLACE); } var partialNextState = undefined; function match(location, callback) { if (partialNextState && partialNextState.location === location) { // Continue from where we left off. finishMatch(partialNextState, callback); } else { _matchRoutes2['default'](routes, location, function (error, nextState) { if (error) { callback(error); } else if (nextState) { finishMatch(_extends({}, nextState, { location: location }), callback); } else { callback(); } }); } } function finishMatch(nextState, callback) { var _computeChangedRoutes = _computeChangedRoutes3['default'](state, nextState); var leaveRoutes = _computeChangedRoutes.leaveRoutes; var enterRoutes = _computeChangedRoutes.enterRoutes; _TransitionUtils.runLeaveHooks(leaveRoutes); _TransitionUtils.runEnterHooks(enterRoutes, nextState, function (error, redirectInfo) { if (error) { callback(error); } else if (redirectInfo) { callback(null, createLocationFromRedirectInfo(redirectInfo)); } else { // TODO: Fetch components after state is updated. _getComponents2['default'](nextState, function (error, components) { if (error) { callback(error); } else { // TODO: Make match a pure function and have some other API // for "match and update state". callback(null, null, state = _extends({}, nextState, { components: components })); } }); } }); } var RouteGuid = 1; function getRouteID(route) { return route.__id__ || (route.__id__ = RouteGuid++); } var RouteHooks = {}; function getRouteHooksForRoutes(routes) { return routes.reduce(function (hooks, route) { hooks.push.apply(hooks, RouteHooks[getRouteID(route)]); return hooks; }, []); } function transitionHook(location, callback) { _matchRoutes2['default'](routes, location, function (error, nextState) { if (nextState == null) { // TODO: We didn't actually match anything, but hang // onto error/nextState so we don't have to matchRoutes // again in the listen callback. callback(); return; } // Cache some state here so we don't have to // matchRoutes() again in the listen callback. partialNextState = _extends({}, nextState, { location: location }); var hooks = getRouteHooksForRoutes(_computeChangedRoutes3['default'](state, partialNextState).leaveRoutes); var result = undefined; for (var i = 0, len = hooks.length; result == null && i < len; ++i) { // Passing the location arg here indicates to // the user that this is a transition hook. result = hooks[i](location); } callback(result); }); } function beforeUnloadHook() { // Synchronously check to see if any route hooks want // to prevent the current window/tab from closing. if (state.routes) { var hooks = getRouteHooksForRoutes(state.routes); var message = undefined; for (var i = 0, len = hooks.length; typeof message !== 'string' && i < len; ++i) { // Passing no args indicates to the user that this is a // beforeunload hook. We don't know the next location. message = hooks[i](); } return message; } } var unlistenBefore = undefined, unlistenBeforeUnload = undefined; /** * Registers the given hook function to run before leaving the given route. * * During a normal transition, the hook function receives the next location * as its only argument and must return either a) a prompt message to show * the user, to make sure they want to leave the page or b) false, to prevent * the transition. * * During the beforeunload event (in browsers) the hook receives no arguments. * In this case it must return a prompt message to prevent the transition. * * Returns a function that may be used to unbind the listener. */ function listenBeforeLeavingRoute(route, hook) { // TODO: Warn if they register for a route that isn't currently // active. They're probably doing something wrong, like re-creating // route objects on every location change. var routeID = getRouteID(route); var hooks = RouteHooks[routeID]; if (hooks == null) { var thereWereNoRouteHooks = !hasAnyProperties(RouteHooks); hooks = RouteHooks[routeID] = [hook]; if (thereWereNoRouteHooks) { // setup transition & beforeunload hooks unlistenBefore = history.listenBefore(transitionHook); if (history.listenBeforeUnload) unlistenBeforeUnload = history.listenBeforeUnload(beforeUnloadHook); } } else if (hooks.indexOf(hook) === -1) { hooks.push(hook); } return function () { var hooks = RouteHooks[routeID]; if (hooks != null) { var newHooks = hooks.filter(function (item) { return item !== hook; }); if (newHooks.length === 0) { delete RouteHooks[routeID]; if (!hasAnyProperties(RouteHooks)) { // teardown transition & beforeunload hooks if (unlistenBefore) { unlistenBefore(); unlistenBefore = null; } if (unlistenBeforeUnload) { unlistenBeforeUnload(); unlistenBeforeUnload = null; } } } else { RouteHooks[routeID] = newHooks; } } }; } /** * This is the API for stateful environments. As the location * changes, we update state and call the listener. We can also * gracefully handle errors and redirects. */ function listen(listener) { // TODO: Only use a single history listener. Otherwise we'll // end up with multiple concurrent calls to match. return history.listen(function (location) { if (state.location === location) { listener(null, state); } else { match(location, function (error, redirectLocation, nextState) { if (error) { listener(error); } else if (redirectLocation) { history.transitionTo(redirectLocation); } else if (nextState) { listener(null, nextState); } else { process.env.NODE_ENV !== 'production' ? _warning2['default'](false, 'Location "%s" did not match any routes', location.pathname + location.search + location.hash) : undefined; } }); } }); } return _extends({}, history, { isActive: isActive, match: match, listenBeforeLeavingRoute: listenBeforeLeavingRoute, listen: listen }); }; } exports['default'] = useRoutes; module.exports = exports['default'];