mirror of
https://github.com/wekan/wekan.git
synced 2026-01-02 07:38:49 +01:00
Include to Wekan packages directory contents, so that meteor command would build all directly.
This also simplifies build scripts. Thanks to xet7 !
This commit is contained in:
parent
6117097a93
commit
73e265d8fd
354 changed files with 36977 additions and 106 deletions
587
packages/kadira-flow-router/client/router.js
Normal file
587
packages/kadira-flow-router/client/router.js
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
Router = function () {
|
||||
var self = this;
|
||||
this.globals = [];
|
||||
this.subscriptions = Function.prototype;
|
||||
|
||||
this._tracker = this._buildTracker();
|
||||
this._current = {};
|
||||
|
||||
// tracks the current path change
|
||||
this._onEveryPath = new Tracker.Dependency();
|
||||
|
||||
this._globalRoute = new Route(this);
|
||||
|
||||
// holds onRoute callbacks
|
||||
this._onRouteCallbacks = [];
|
||||
|
||||
// if _askedToWait is true. We don't automatically start the router
|
||||
// in Meteor.startup callback. (see client/_init.js)
|
||||
// Instead user need to call `.initialize()
|
||||
this._askedToWait = false;
|
||||
this._initialized = false;
|
||||
this._triggersEnter = [];
|
||||
this._triggersExit = [];
|
||||
this._routes = [];
|
||||
this._routesMap = {};
|
||||
this._updateCallbacks();
|
||||
this.notFound = this.notfound = null;
|
||||
// indicate it's okay (or not okay) to run the tracker
|
||||
// when doing subscriptions
|
||||
// using a number and increment it help us to support FlowRouter.go()
|
||||
// and legitimate reruns inside tracker on the same event loop.
|
||||
// this is a solution for #145
|
||||
this.safeToRun = 0;
|
||||
|
||||
// Meteor exposes to the client the path prefix that was defined using the
|
||||
// ROOT_URL environement variable on the server using the global runtime
|
||||
// configuration. See #315.
|
||||
this._basePath = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '';
|
||||
|
||||
// this is a chain contains a list of old routes
|
||||
// most of the time, there is only one old route
|
||||
// but when it's the time for a trigger redirect we've a chain
|
||||
this._oldRouteChain = [];
|
||||
|
||||
this.env = {
|
||||
replaceState: new Meteor.EnvironmentVariable(),
|
||||
reload: new Meteor.EnvironmentVariable(),
|
||||
trailingSlash: new Meteor.EnvironmentVariable()
|
||||
};
|
||||
|
||||
// redirect function used inside triggers
|
||||
this._redirectFn = function(pathDef, fields, queryParams) {
|
||||
if (/^http(s)?:\/\//.test(pathDef)) {
|
||||
var message = "Redirects to URLs outside of the app are not supported in this version of Flow Router. Use 'window.location = yourUrl' instead";
|
||||
throw new Error(message);
|
||||
}
|
||||
self.withReplaceState(function() {
|
||||
var path = FlowRouter.path(pathDef, fields, queryParams);
|
||||
self._page.redirect(path);
|
||||
});
|
||||
};
|
||||
this._initTriggersAPI();
|
||||
};
|
||||
|
||||
Router.prototype.route = function(pathDef, options, group) {
|
||||
if (!/^\/.*/.test(pathDef)) {
|
||||
var message = "route's path must start with '/'";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var route = new Route(this, pathDef, options, group);
|
||||
|
||||
// calls when the page route being activates
|
||||
route._actionHandle = function (context, next) {
|
||||
var oldRoute = self._current.route;
|
||||
self._oldRouteChain.push(oldRoute);
|
||||
|
||||
var queryParams = self._qs.parse(context.querystring);
|
||||
// _qs.parse() gives us a object without prototypes,
|
||||
// created with Object.create(null)
|
||||
// Meteor's check doesn't play nice with it.
|
||||
// So, we need to fix it by cloning it.
|
||||
// see more: https://github.com/meteorhacks/flow-router/issues/164
|
||||
queryParams = JSON.parse(JSON.stringify(queryParams));
|
||||
|
||||
self._current = {
|
||||
path: context.path,
|
||||
context: context,
|
||||
params: context.params,
|
||||
queryParams: queryParams,
|
||||
route: route,
|
||||
oldRoute: oldRoute
|
||||
};
|
||||
|
||||
// we need to invalidate if all the triggers have been completed
|
||||
// if not that means, we've been redirected to another path
|
||||
// then we don't need to invalidate
|
||||
var afterAllTriggersRan = function() {
|
||||
self._invalidateTracker();
|
||||
};
|
||||
|
||||
var triggers = self._triggersEnter.concat(route._triggersEnter);
|
||||
Triggers.runTriggers(
|
||||
triggers,
|
||||
self._current,
|
||||
self._redirectFn,
|
||||
afterAllTriggersRan
|
||||
);
|
||||
};
|
||||
|
||||
// calls when you exit from the page js route
|
||||
route._exitHandle = function(context, next) {
|
||||
var triggers = self._triggersExit.concat(route._triggersExit);
|
||||
Triggers.runTriggers(
|
||||
triggers,
|
||||
self._current,
|
||||
self._redirectFn,
|
||||
next
|
||||
);
|
||||
};
|
||||
|
||||
this._routes.push(route);
|
||||
if (options.name) {
|
||||
this._routesMap[options.name] = route;
|
||||
}
|
||||
|
||||
this._updateCallbacks();
|
||||
this._triggerRouteRegister(route);
|
||||
|
||||
return route;
|
||||
};
|
||||
|
||||
Router.prototype.group = function(options) {
|
||||
return new Group(this, options);
|
||||
};
|
||||
|
||||
Router.prototype.path = function(pathDef, fields, queryParams) {
|
||||
if (this._routesMap[pathDef]) {
|
||||
pathDef = this._routesMap[pathDef].pathDef;
|
||||
}
|
||||
|
||||
var path = "";
|
||||
|
||||
// Prefix the path with the router global prefix
|
||||
if (this._basePath) {
|
||||
path += "/" + this._basePath + "/";
|
||||
}
|
||||
|
||||
fields = fields || {};
|
||||
var regExp = /(:[\w\(\)\\\+\*\.\?]+)+/g;
|
||||
path += pathDef.replace(regExp, function(key) {
|
||||
var firstRegexpChar = key.indexOf("(");
|
||||
// get the content behind : and (\\d+/)
|
||||
key = key.substring(1, (firstRegexpChar > 0)? firstRegexpChar: undefined);
|
||||
// remove +?*
|
||||
key = key.replace(/[\+\*\?]+/g, "");
|
||||
|
||||
// this is to allow page js to keep the custom characters as it is
|
||||
// we need to encode 2 times otherwise "/" char does not work properly
|
||||
// So, in that case, when I includes "/" it will think it's a part of the
|
||||
// route. encoding 2times fixes it
|
||||
return encodeURIComponent(encodeURIComponent(fields[key] || ""));
|
||||
});
|
||||
|
||||
// Replace multiple slashes with single slash
|
||||
path = path.replace(/\/\/+/g, "/");
|
||||
|
||||
// remove trailing slash
|
||||
// but keep the root slash if it's the only one
|
||||
path = path.match(/^\/{1}$/) ? path: path.replace(/\/$/, "");
|
||||
|
||||
// explictly asked to add a trailing slash
|
||||
if(this.env.trailingSlash.get() && _.last(path) !== "/") {
|
||||
path += "/";
|
||||
}
|
||||
|
||||
var strQueryParams = this._qs.stringify(queryParams || {});
|
||||
if(strQueryParams) {
|
||||
path += "?" + strQueryParams;
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
Router.prototype.go = function(pathDef, fields, queryParams) {
|
||||
var path = this.path(pathDef, fields, queryParams);
|
||||
|
||||
var useReplaceState = this.env.replaceState.get();
|
||||
if(useReplaceState) {
|
||||
this._page.replace(path);
|
||||
} else {
|
||||
this._page(path);
|
||||
}
|
||||
};
|
||||
|
||||
Router.prototype.reload = function() {
|
||||
var self = this;
|
||||
|
||||
self.env.reload.withValue(true, function() {
|
||||
self._page.replace(self._current.path);
|
||||
});
|
||||
};
|
||||
|
||||
Router.prototype.redirect = function(path) {
|
||||
this._page.redirect(path);
|
||||
};
|
||||
|
||||
Router.prototype.setParams = function(newParams) {
|
||||
if(!this._current.route) {return false;}
|
||||
|
||||
var pathDef = this._current.route.pathDef;
|
||||
var existingParams = this._current.params;
|
||||
var params = {};
|
||||
_.each(_.keys(existingParams), function(key) {
|
||||
params[key] = existingParams[key];
|
||||
});
|
||||
|
||||
params = _.extend(params, newParams);
|
||||
var queryParams = this._current.queryParams;
|
||||
|
||||
this.go(pathDef, params, queryParams);
|
||||
return true;
|
||||
};
|
||||
|
||||
Router.prototype.setQueryParams = function(newParams) {
|
||||
if(!this._current.route) {return false;}
|
||||
|
||||
var queryParams = _.clone(this._current.queryParams);
|
||||
_.extend(queryParams, newParams);
|
||||
|
||||
for (var k in queryParams) {
|
||||
if (queryParams[k] === null || queryParams[k] === undefined) {
|
||||
delete queryParams[k];
|
||||
}
|
||||
}
|
||||
|
||||
var pathDef = this._current.route.pathDef;
|
||||
var params = this._current.params;
|
||||
this.go(pathDef, params, queryParams);
|
||||
return true;
|
||||
};
|
||||
|
||||
// .current is not reactive
|
||||
// This is by design. use .getParam() instead
|
||||
// If you really need to watch the path change, use .watchPathChange()
|
||||
Router.prototype.current = function() {
|
||||
// We can't trust outside, that's why we clone this
|
||||
// Anyway, we can't clone the whole object since it has non-jsonable values
|
||||
// That's why we clone what's really needed.
|
||||
var current = _.clone(this._current);
|
||||
current.queryParams = EJSON.clone(current.queryParams);
|
||||
current.params = EJSON.clone(current.params);
|
||||
return current;
|
||||
};
|
||||
|
||||
// Implementing Reactive APIs
|
||||
var reactiveApis = [
|
||||
'getParam', 'getQueryParam',
|
||||
'getRouteName', 'watchPathChange'
|
||||
];
|
||||
reactiveApis.forEach(function(api) {
|
||||
Router.prototype[api] = function(arg1) {
|
||||
// when this is calling, there may not be any route initiated
|
||||
// so we need to handle it
|
||||
var currentRoute = this._current.route;
|
||||
if(!currentRoute) {
|
||||
this._onEveryPath.depend();
|
||||
return;
|
||||
}
|
||||
|
||||
// currently, there is only one argument. If we've more let's add more args
|
||||
// this is not clean code, but better in performance
|
||||
return currentRoute[api].call(currentRoute, arg1);
|
||||
};
|
||||
});
|
||||
|
||||
Router.prototype.subsReady = function() {
|
||||
var callback = null;
|
||||
var args = _.toArray(arguments);
|
||||
|
||||
if (typeof _.last(args) === "function") {
|
||||
callback = args.pop();
|
||||
}
|
||||
|
||||
var currentRoute = this.current().route;
|
||||
var globalRoute = this._globalRoute;
|
||||
|
||||
// we need to depend for every route change and
|
||||
// rerun subscriptions to check the ready state
|
||||
this._onEveryPath.depend();
|
||||
|
||||
if(!currentRoute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var subscriptions;
|
||||
if(args.length === 0) {
|
||||
subscriptions = _.values(globalRoute.getAllSubscriptions());
|
||||
subscriptions = subscriptions.concat(_.values(currentRoute.getAllSubscriptions()));
|
||||
} else {
|
||||
subscriptions = _.map(args, function(subName) {
|
||||
return globalRoute.getSubscription(subName) || currentRoute.getSubscription(subName);
|
||||
});
|
||||
}
|
||||
|
||||
var isReady = function() {
|
||||
var ready = _.every(subscriptions, function(sub) {
|
||||
return sub && sub.ready();
|
||||
});
|
||||
|
||||
return ready;
|
||||
};
|
||||
|
||||
if (callback) {
|
||||
Tracker.autorun(function(c) {
|
||||
if (isReady()) {
|
||||
callback();
|
||||
c.stop();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return isReady();
|
||||
}
|
||||
};
|
||||
|
||||
Router.prototype.withReplaceState = function(fn) {
|
||||
return this.env.replaceState.withValue(true, fn);
|
||||
};
|
||||
|
||||
Router.prototype.withTrailingSlash = function(fn) {
|
||||
return this.env.trailingSlash.withValue(true, fn);
|
||||
};
|
||||
|
||||
Router.prototype._notfoundRoute = function(context) {
|
||||
this._current = {
|
||||
path: context.path,
|
||||
context: context,
|
||||
params: [],
|
||||
queryParams: {},
|
||||
};
|
||||
|
||||
// XXX this.notfound kept for backwards compatibility
|
||||
this.notFound = this.notFound || this.notfound;
|
||||
if(!this.notFound) {
|
||||
console.error("There is no route for the path:", context.path);
|
||||
return;
|
||||
}
|
||||
|
||||
this._current.route = new Route(this, "*", this.notFound);
|
||||
this._invalidateTracker();
|
||||
};
|
||||
|
||||
Router.prototype.initialize = function(options) {
|
||||
options = options || {};
|
||||
|
||||
if(this._initialized) {
|
||||
throw new Error("FlowRouter is already initialized");
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this._updateCallbacks();
|
||||
|
||||
// Implementing idempotent routing
|
||||
// by overriding page.js`s "show" method.
|
||||
// Why?
|
||||
// It is impossible to bypass exit triggers,
|
||||
// because they execute before the handler and
|
||||
// can not know what the next path is, inside exit trigger.
|
||||
//
|
||||
// we need override both show, replace to make this work
|
||||
// since we use redirect when we are talking about withReplaceState
|
||||
_.each(['show', 'replace'], function(fnName) {
|
||||
var original = self._page[fnName];
|
||||
self._page[fnName] = function(path, state, dispatch, push) {
|
||||
var reload = self.env.reload.get();
|
||||
if (!reload && self._current.path === path) {
|
||||
return;
|
||||
}
|
||||
|
||||
original.call(this, path, state, dispatch, push);
|
||||
};
|
||||
});
|
||||
|
||||
// this is very ugly part of pagejs and it does decoding few times
|
||||
// in unpredicatable manner. See #168
|
||||
// this is the default behaviour and we need keep it like that
|
||||
// we are doing a hack. see .path()
|
||||
this._page.base(this._basePath);
|
||||
this._page({
|
||||
decodeURLComponents: true,
|
||||
hashbang: !!options.hashbang
|
||||
});
|
||||
|
||||
this._initialized = true;
|
||||
};
|
||||
|
||||
Router.prototype._buildTracker = function() {
|
||||
var self = this;
|
||||
|
||||
// main autorun function
|
||||
var tracker = Tracker.autorun(function () {
|
||||
if(!self._current || !self._current.route) {
|
||||
return;
|
||||
}
|
||||
|
||||
// see the definition of `this._processingContexts`
|
||||
var currentContext = self._current;
|
||||
var route = currentContext.route;
|
||||
var path = currentContext.path;
|
||||
|
||||
if(self.safeToRun === 0) {
|
||||
var message =
|
||||
"You can't use reactive data sources like Session" +
|
||||
" inside the `.subscriptions` method!";
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
// We need to run subscriptions inside a Tracker
|
||||
// to stop subs when switching between routes
|
||||
// But we don't need to run this tracker with
|
||||
// other reactive changes inside the .subscription method
|
||||
// We tackle this with the `safeToRun` variable
|
||||
self._globalRoute.clearSubscriptions();
|
||||
self.subscriptions.call(self._globalRoute, path);
|
||||
route.callSubscriptions(currentContext);
|
||||
|
||||
// otherwise, computations inside action will trigger to re-run
|
||||
// this computation. which we do not need.
|
||||
Tracker.nonreactive(function() {
|
||||
var isRouteChange = currentContext.oldRoute !== currentContext.route;
|
||||
var isFirstRoute = !currentContext.oldRoute;
|
||||
// first route is not a route change
|
||||
if(isFirstRoute) {
|
||||
isRouteChange = false;
|
||||
}
|
||||
|
||||
// Clear oldRouteChain just before calling the action
|
||||
// We still need to get a copy of the oldestRoute first
|
||||
// It's very important to get the oldest route and registerRouteClose() it
|
||||
// See: https://github.com/kadirahq/flow-router/issues/314
|
||||
var oldestRoute = self._oldRouteChain[0];
|
||||
self._oldRouteChain = [];
|
||||
|
||||
currentContext.route.registerRouteChange(currentContext, isRouteChange);
|
||||
route.callAction(currentContext);
|
||||
|
||||
Tracker.afterFlush(function() {
|
||||
self._onEveryPath.changed();
|
||||
if(isRouteChange) {
|
||||
// We need to trigger that route (definition itself) has changed.
|
||||
// So, we need to re-run all the register callbacks to current route
|
||||
// This is pretty important, otherwise tracker
|
||||
// can't identify new route's items
|
||||
|
||||
// We also need to afterFlush, otherwise this will re-run
|
||||
// helpers on templates which are marked for destroying
|
||||
if(oldestRoute) {
|
||||
oldestRoute.registerRouteClose();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.safeToRun--;
|
||||
});
|
||||
|
||||
return tracker;
|
||||
};
|
||||
|
||||
Router.prototype._invalidateTracker = function() {
|
||||
var self = this;
|
||||
this.safeToRun++;
|
||||
this._tracker.invalidate();
|
||||
// After the invalidation we need to flush to make changes imediately
|
||||
// otherwise, we have face some issues context mix-maches and so on.
|
||||
// But there are some cases we can't flush. So we need to ready for that.
|
||||
|
||||
// we clearly know, we can't flush inside an autorun
|
||||
// this may leads some issues on flow-routing
|
||||
// we may need to do some warning
|
||||
if(!Tracker.currentComputation) {
|
||||
// Still there are some cases where we can't flush
|
||||
// eg:- when there is a flush currently
|
||||
// But we've no public API or hacks to get that state
|
||||
// So, this is the only solution
|
||||
try {
|
||||
Tracker.flush();
|
||||
} catch(ex) {
|
||||
// only handling "while flushing" errors
|
||||
if(!/Tracker\.flush while flushing/.test(ex.message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX: fix this with a proper solution by removing subscription mgt.
|
||||
// from the router. Then we don't need to run invalidate using a tracker
|
||||
|
||||
// this happens when we are trying to invoke a route change
|
||||
// with inside a route chnage. (eg:- Template.onCreated)
|
||||
// Since we use page.js and tracker, we don't have much control
|
||||
// over this process.
|
||||
// only solution is to defer route execution.
|
||||
|
||||
// It's possible to have more than one path want to defer
|
||||
// But, we only need to pick the last one.
|
||||
// self._nextPath = self._current.path;
|
||||
Meteor.defer(function() {
|
||||
var path = self._nextPath;
|
||||
if(!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete self._nextPath;
|
||||
self.env.reload.withValue(true, function() {
|
||||
self.go(path);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Router.prototype._updateCallbacks = function () {
|
||||
var self = this;
|
||||
|
||||
self._page.callbacks = [];
|
||||
self._page.exits = [];
|
||||
|
||||
_.each(self._routes, function(route) {
|
||||
self._page(route.pathDef, route._actionHandle);
|
||||
self._page.exit(route.pathDef, route._exitHandle);
|
||||
});
|
||||
|
||||
self._page("*", function(context) {
|
||||
self._notfoundRoute(context);
|
||||
});
|
||||
};
|
||||
|
||||
Router.prototype._initTriggersAPI = function() {
|
||||
var self = this;
|
||||
this.triggers = {
|
||||
enter: function(triggers, filter) {
|
||||
triggers = Triggers.applyFilters(triggers, filter);
|
||||
if(triggers.length) {
|
||||
self._triggersEnter = self._triggersEnter.concat(triggers);
|
||||
}
|
||||
},
|
||||
|
||||
exit: function(triggers, filter) {
|
||||
triggers = Triggers.applyFilters(triggers, filter);
|
||||
if(triggers.length) {
|
||||
self._triggersExit = self._triggersExit.concat(triggers);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Router.prototype.wait = function() {
|
||||
if(this._initialized) {
|
||||
throw new Error("can't wait after FlowRouter has been initialized");
|
||||
}
|
||||
|
||||
this._askedToWait = true;
|
||||
};
|
||||
|
||||
Router.prototype.onRouteRegister = function(cb) {
|
||||
this._onRouteCallbacks.push(cb);
|
||||
};
|
||||
|
||||
Router.prototype._triggerRouteRegister = function(currentRoute) {
|
||||
// We should only need to send a safe set of fields on the route
|
||||
// object.
|
||||
// This is not to hide what's inside the route object, but to show
|
||||
// these are the public APIs
|
||||
var routePublicApi = _.pick(currentRoute, 'name', 'pathDef', 'path');
|
||||
var omittingOptionFields = [
|
||||
'triggersEnter', 'triggersExit', 'action', 'subscriptions', 'name'
|
||||
];
|
||||
routePublicApi.options = _.omit(currentRoute.options, omittingOptionFields);
|
||||
|
||||
_.each(this._onRouteCallbacks, function(cb) {
|
||||
cb(routePublicApi);
|
||||
});
|
||||
};
|
||||
|
||||
Router.prototype._page = page;
|
||||
Router.prototype._qs = qs;
|
||||
Loading…
Add table
Add a link
Reference in a new issue