1 | var createObject = Object.create;
|
2 | function createMap() {
|
3 | var map = createObject(null);
|
4 | map["__"] = undefined;
|
5 | delete map["__"];
|
6 | return map;
|
7 | }
|
8 |
|
9 | var Target = function Target(path, matcher, delegate) {
|
10 | this.path = path;
|
11 | this.matcher = matcher;
|
12 | this.delegate = delegate;
|
13 | };
|
14 | Target.prototype.to = function to (target, callback) {
|
15 | var delegate = this.delegate;
|
16 | if (delegate && delegate.willAddRoute) {
|
17 | target = delegate.willAddRoute(this.matcher.target, target);
|
18 | }
|
19 | this.matcher.add(this.path, target);
|
20 | if (callback) {
|
21 | if (callback.length === 0) {
|
22 | throw new Error("You must have an argument in the function passed to `to`");
|
23 | }
|
24 | this.matcher.addChild(this.path, target, callback, this.delegate);
|
25 | }
|
26 | };
|
27 | var Matcher = function Matcher(target) {
|
28 | this.routes = createMap();
|
29 | this.children = createMap();
|
30 | this.target = target;
|
31 | };
|
32 | Matcher.prototype.add = function add (path, target) {
|
33 | this.routes[path] = target;
|
34 | };
|
35 | Matcher.prototype.addChild = function addChild (path, target, callback, delegate) {
|
36 | var matcher = new Matcher(target);
|
37 | this.children[path] = matcher;
|
38 | var match = generateMatch(path, matcher, delegate);
|
39 | if (delegate && delegate.contextEntered) {
|
40 | delegate.contextEntered(target, match);
|
41 | }
|
42 | callback(match);
|
43 | };
|
44 | function generateMatch(startingPath, matcher, delegate) {
|
45 | function match(path, callback) {
|
46 | var fullPath = startingPath + path;
|
47 | if (callback) {
|
48 | callback(generateMatch(fullPath, matcher, delegate));
|
49 | }
|
50 | else {
|
51 | return new Target(fullPath, matcher, delegate);
|
52 | }
|
53 | }
|
54 |
|
55 | return match;
|
56 | }
|
57 | function addRoute(routeArray, path, handler) {
|
58 | var len = 0;
|
59 | for (var i = 0; i < routeArray.length; i++) {
|
60 | len += routeArray[i].path.length;
|
61 | }
|
62 | path = path.substr(len);
|
63 | var route = { path: path, handler: handler };
|
64 | routeArray.push(route);
|
65 | }
|
66 | function eachRoute(baseRoute, matcher, callback, binding) {
|
67 | var routes = matcher.routes;
|
68 | var paths = Object.keys(routes);
|
69 | for (var i = 0; i < paths.length; i++) {
|
70 | var path = paths[i];
|
71 | var routeArray = baseRoute.slice();
|
72 | addRoute(routeArray, path, routes[path]);
|
73 | var nested = matcher.children[path];
|
74 | if (nested) {
|
75 | eachRoute(routeArray, nested, callback, binding);
|
76 | }
|
77 | else {
|
78 | callback.call(binding, routeArray);
|
79 | }
|
80 | }
|
81 | }
|
82 | var map = function (callback, addRouteCallback) {
|
83 | var matcher = new Matcher();
|
84 | callback(generateMatch("", matcher, this.delegate));
|
85 | eachRoute([], matcher, function (routes) {
|
86 | if (addRouteCallback) {
|
87 | addRouteCallback(this, routes);
|
88 | }
|
89 | else {
|
90 | this.add(routes);
|
91 | }
|
92 | }, this);
|
93 | };
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | function normalizePath(path) {
|
101 | return path.split("/")
|
102 | .map(normalizeSegment)
|
103 | .join("/");
|
104 | }
|
105 |
|
106 |
|
107 |
|
108 | var SEGMENT_RESERVED_CHARS = /%|\//g;
|
109 | function normalizeSegment(segment) {
|
110 | if (segment.length < 3 || segment.indexOf("%") === -1)
|
111 | { return segment; }
|
112 | return decodeURIComponent(segment).replace(SEGMENT_RESERVED_CHARS, encodeURIComponent);
|
113 | }
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | var PATH_SEGMENT_ENCODINGS = /%(?:2(?:4|6|B|C)|3(?:B|D|A)|40)/g;
|
125 | function encodePathSegment(str) {
|
126 | return encodeURIComponent(str).replace(PATH_SEGMENT_ENCODINGS, decodeURIComponent);
|
127 | }
|
128 |
|
129 | var escapeRegex = /(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\)/g;
|
130 | var isArray = Array.isArray;
|
131 | var hasOwnProperty = Object.prototype.hasOwnProperty;
|
132 | function getParam(params, key) {
|
133 | if (typeof params !== "object" || params === null) {
|
134 | throw new Error("You must pass an object as the second argument to `generate`.");
|
135 | }
|
136 | if (!hasOwnProperty.call(params, key)) {
|
137 | throw new Error("You must provide param `" + key + "` to `generate`.");
|
138 | }
|
139 | var value = params[key];
|
140 | var str = typeof value === "string" ? value : "" + value;
|
141 | if (str.length === 0) {
|
142 | throw new Error("You must provide a param `" + key + "`.");
|
143 | }
|
144 | return str;
|
145 | }
|
146 | var eachChar = [];
|
147 | eachChar[0 ] = function (segment, currentState) {
|
148 | var state = currentState;
|
149 | var value = segment.value;
|
150 | for (var i = 0; i < value.length; i++) {
|
151 | var ch = value.charCodeAt(i);
|
152 | state = state.put(ch, false, false);
|
153 | }
|
154 | return state;
|
155 | };
|
156 | eachChar[1 ] = function (_, currentState) {
|
157 | return currentState.put(47 , true, true);
|
158 | };
|
159 | eachChar[2 ] = function (_, currentState) {
|
160 | return currentState.put(-1 , false, true);
|
161 | };
|
162 | eachChar[4 ] = function (_, currentState) {
|
163 | return currentState;
|
164 | };
|
165 | var regex = [];
|
166 | regex[0 ] = function (segment) {
|
167 | return segment.value.replace(escapeRegex, "\\$1");
|
168 | };
|
169 | regex[1 ] = function () {
|
170 | return "([^/]+)";
|
171 | };
|
172 | regex[2 ] = function () {
|
173 | return "(.+)";
|
174 | };
|
175 | regex[4 ] = function () {
|
176 | return "";
|
177 | };
|
178 | var generate = [];
|
179 | generate[0 ] = function (segment) {
|
180 | return segment.value;
|
181 | };
|
182 | generate[1 ] = function (segment, params) {
|
183 | var value = getParam(params, segment.value);
|
184 | if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {
|
185 | return encodePathSegment(value);
|
186 | }
|
187 | else {
|
188 | return value;
|
189 | }
|
190 | };
|
191 | generate[2 ] = function (segment, params) {
|
192 | return getParam(params, segment.value);
|
193 | };
|
194 | generate[4 ] = function () {
|
195 | return "";
|
196 | };
|
197 | var EmptyObject = Object.freeze({});
|
198 | var EmptyArray = Object.freeze([]);
|
199 |
|
200 |
|
201 |
|
202 | function parse(segments, route, types) {
|
203 |
|
204 |
|
205 | if (route.length > 0 && route.charCodeAt(0) === 47 ) {
|
206 | route = route.substr(1);
|
207 | }
|
208 | var parts = route.split("/");
|
209 | var names = undefined;
|
210 | var shouldDecodes = undefined;
|
211 | for (var i = 0; i < parts.length; i++) {
|
212 | var part = parts[i];
|
213 | var flags = 0;
|
214 | var type = 0;
|
215 | if (part === "") {
|
216 | type = 4 ;
|
217 | }
|
218 | else if (part.charCodeAt(0) === 58 ) {
|
219 | type = 1 ;
|
220 | }
|
221 | else if (part.charCodeAt(0) === 42 ) {
|
222 | type = 2 ;
|
223 | }
|
224 | else {
|
225 | type = 0 ;
|
226 | }
|
227 | flags = 2 << type;
|
228 | if (flags & 12 ) {
|
229 | part = part.slice(1);
|
230 | names = names || [];
|
231 | names.push(part);
|
232 | shouldDecodes = shouldDecodes || [];
|
233 | shouldDecodes.push((flags & 4 ) !== 0);
|
234 | }
|
235 | if (flags & 14 ) {
|
236 | types[type]++;
|
237 | }
|
238 | segments.push({
|
239 | type: type,
|
240 | value: normalizeSegment(part)
|
241 | });
|
242 | }
|
243 | return {
|
244 | names: names || EmptyArray,
|
245 | shouldDecodes: shouldDecodes || EmptyArray,
|
246 | };
|
247 | }
|
248 | function isEqualCharSpec(spec, char, negate) {
|
249 | return spec.char === char && spec.negate === negate;
|
250 | }
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 | var State = function State(states, id, char, negate, repeat) {
|
268 | this.states = states;
|
269 | this.id = id;
|
270 | this.char = char;
|
271 | this.negate = negate;
|
272 | this.nextStates = repeat ? id : null;
|
273 | this.pattern = "";
|
274 | this._regex = undefined;
|
275 | this.handlers = undefined;
|
276 | this.types = undefined;
|
277 | };
|
278 | State.prototype.regex = function regex$1 () {
|
279 | if (!this._regex) {
|
280 | this._regex = new RegExp(this.pattern);
|
281 | }
|
282 | return this._regex;
|
283 | };
|
284 | State.prototype.get = function get (char, negate) {
|
285 | var this$1 = this;
|
286 |
|
287 | var nextStates = this.nextStates;
|
288 | if (nextStates === null)
|
289 | { return; }
|
290 | if (isArray(nextStates)) {
|
291 | for (var i = 0; i < nextStates.length; i++) {
|
292 | var child = this$1.states[nextStates[i]];
|
293 | if (isEqualCharSpec(child, char, negate)) {
|
294 | return child;
|
295 | }
|
296 | }
|
297 | }
|
298 | else {
|
299 | var child$1 = this.states[nextStates];
|
300 | if (isEqualCharSpec(child$1, char, negate)) {
|
301 | return child$1;
|
302 | }
|
303 | }
|
304 | };
|
305 | State.prototype.put = function put (char, negate, repeat) {
|
306 | var state;
|
307 |
|
308 |
|
309 | if (state = this.get(char, negate)) {
|
310 | return state;
|
311 | }
|
312 |
|
313 | var states = this.states;
|
314 | state = new State(states, states.length, char, negate, repeat);
|
315 | states[states.length] = state;
|
316 |
|
317 | if (this.nextStates == null) {
|
318 | this.nextStates = state.id;
|
319 | }
|
320 | else if (isArray(this.nextStates)) {
|
321 | this.nextStates.push(state.id);
|
322 | }
|
323 | else {
|
324 | this.nextStates = [this.nextStates, state.id];
|
325 | }
|
326 |
|
327 | return state;
|
328 | };
|
329 |
|
330 | State.prototype.match = function match (ch) {
|
331 | var this$1 = this;
|
332 |
|
333 | var nextStates = this.nextStates;
|
334 | if (!nextStates)
|
335 | { return []; }
|
336 | var returned = [];
|
337 | if (isArray(nextStates)) {
|
338 | for (var i = 0; i < nextStates.length; i++) {
|
339 | var child = this$1.states[nextStates[i]];
|
340 | if (isMatch(child, ch)) {
|
341 | returned.push(child);
|
342 | }
|
343 | }
|
344 | }
|
345 | else {
|
346 | var child$1 = this.states[nextStates];
|
347 | if (isMatch(child$1, ch)) {
|
348 | returned.push(child$1);
|
349 | }
|
350 | }
|
351 | return returned;
|
352 | };
|
353 | function isMatch(spec, char) {
|
354 | return spec.negate ? spec.char !== char && spec.char !== -1 : spec.char === char || spec.char === -1 ;
|
355 | }
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 | function sortSolutions(states) {
|
367 | return states.sort(function (a, b) {
|
368 | var ref = a.types || [0, 0, 0];
|
369 | var astatics = ref[0];
|
370 | var adynamics = ref[1];
|
371 | var astars = ref[2];
|
372 | var ref$1 = b.types || [0, 0, 0];
|
373 | var bstatics = ref$1[0];
|
374 | var bdynamics = ref$1[1];
|
375 | var bstars = ref$1[2];
|
376 | if (astars !== bstars) {
|
377 | return astars - bstars;
|
378 | }
|
379 | if (astars) {
|
380 | if (astatics !== bstatics) {
|
381 | return bstatics - astatics;
|
382 | }
|
383 | if (adynamics !== bdynamics) {
|
384 | return bdynamics - adynamics;
|
385 | }
|
386 | }
|
387 | if (adynamics !== bdynamics) {
|
388 | return adynamics - bdynamics;
|
389 | }
|
390 | if (astatics !== bstatics) {
|
391 | return bstatics - astatics;
|
392 | }
|
393 | return 0;
|
394 | });
|
395 | }
|
396 | function recognizeChar(states, ch) {
|
397 | var nextStates = [];
|
398 | for (var i = 0, l = states.length; i < l; i++) {
|
399 | var state = states[i];
|
400 | nextStates = nextStates.concat(state.match(ch));
|
401 | }
|
402 | return nextStates;
|
403 | }
|
404 | var RecognizeResults = function RecognizeResults(queryParams) {
|
405 | this.length = 0;
|
406 | this.queryParams = queryParams || {};
|
407 | };
|
408 |
|
409 | RecognizeResults.prototype.splice = Array.prototype.splice;
|
410 | RecognizeResults.prototype.slice = Array.prototype.slice;
|
411 | RecognizeResults.prototype.push = Array.prototype.push;
|
412 | function findHandler(state, originalPath, queryParams) {
|
413 | var handlers = state.handlers;
|
414 | var regex = state.regex();
|
415 | if (!regex || !handlers)
|
416 | { throw new Error("state not initialized"); }
|
417 | var captures = originalPath.match(regex);
|
418 | var currentCapture = 1;
|
419 | var result = new RecognizeResults(queryParams);
|
420 | result.length = handlers.length;
|
421 | for (var i = 0; i < handlers.length; i++) {
|
422 | var handler = handlers[i];
|
423 | var names = handler.names;
|
424 | var shouldDecodes = handler.shouldDecodes;
|
425 | var params = EmptyObject;
|
426 | var isDynamic = false;
|
427 | if (names !== EmptyArray && shouldDecodes !== EmptyArray) {
|
428 | for (var j = 0; j < names.length; j++) {
|
429 | isDynamic = true;
|
430 | var name = names[j];
|
431 | var capture = captures && captures[currentCapture++];
|
432 | if (params === EmptyObject) {
|
433 | params = {};
|
434 | }
|
435 | if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS && shouldDecodes[j]) {
|
436 | params[name] = capture && decodeURIComponent(capture);
|
437 | }
|
438 | else {
|
439 | params[name] = capture;
|
440 | }
|
441 | }
|
442 | }
|
443 | result[i] = {
|
444 | handler: handler.handler,
|
445 | params: params,
|
446 | isDynamic: isDynamic
|
447 | };
|
448 | }
|
449 | return result;
|
450 | }
|
451 | function decodeQueryParamPart(part) {
|
452 |
|
453 | part = part.replace(/\+/gm, "%20");
|
454 | var result;
|
455 | try {
|
456 | result = decodeURIComponent(part);
|
457 | }
|
458 | catch (error) {
|
459 | result = "";
|
460 | }
|
461 | return result;
|
462 | }
|
463 | var RouteRecognizer = function RouteRecognizer() {
|
464 | this.names = createMap();
|
465 | var states = [];
|
466 | var state = new State(states, 0, -1 , true, false);
|
467 | states[0] = state;
|
468 | this.states = states;
|
469 | this.rootState = state;
|
470 | };
|
471 | RouteRecognizer.prototype.add = function add (routes, options) {
|
472 | var currentState = this.rootState;
|
473 | var pattern = "^";
|
474 | var types = [0, 0, 0];
|
475 | var handlers = new Array(routes.length);
|
476 | var allSegments = [];
|
477 | var isEmpty = true;
|
478 | var j = 0;
|
479 | for (var i = 0; i < routes.length; i++) {
|
480 | var route = routes[i];
|
481 | var ref = parse(allSegments, route.path, types);
|
482 | var names = ref.names;
|
483 | var shouldDecodes = ref.shouldDecodes;
|
484 |
|
485 | for (; j < allSegments.length; j++) {
|
486 | var segment = allSegments[j];
|
487 | if (segment.type === 4 ) {
|
488 | continue;
|
489 | }
|
490 | isEmpty = false;
|
491 |
|
492 | currentState = currentState.put(47 , false, false);
|
493 | pattern += "/";
|
494 |
|
495 | currentState = eachChar[segment.type](segment, currentState);
|
496 | pattern += regex[segment.type](segment);
|
497 | }
|
498 | handlers[i] = {
|
499 | handler: route.handler,
|
500 | names: names,
|
501 | shouldDecodes: shouldDecodes
|
502 | };
|
503 | }
|
504 | if (isEmpty) {
|
505 | currentState = currentState.put(47 , false, false);
|
506 | pattern += "/";
|
507 | }
|
508 | currentState.handlers = handlers;
|
509 | currentState.pattern = pattern + "$";
|
510 | currentState.types = types;
|
511 | var name;
|
512 | if (typeof options === "object" && options !== null && options.as) {
|
513 | name = options.as;
|
514 | }
|
515 | if (name) {
|
516 |
|
517 |
|
518 |
|
519 | this.names[name] = {
|
520 | segments: allSegments,
|
521 | handlers: handlers
|
522 | };
|
523 | }
|
524 | };
|
525 | RouteRecognizer.prototype.handlersFor = function handlersFor (name) {
|
526 | var route = this.names[name];
|
527 | if (!route) {
|
528 | throw new Error("There is no route named " + name);
|
529 | }
|
530 | var result = new Array(route.handlers.length);
|
531 | for (var i = 0; i < route.handlers.length; i++) {
|
532 | var handler = route.handlers[i];
|
533 | result[i] = handler;
|
534 | }
|
535 | return result;
|
536 | };
|
537 | RouteRecognizer.prototype.hasRoute = function hasRoute (name) {
|
538 | return !!this.names[name];
|
539 | };
|
540 | RouteRecognizer.prototype.generate = function generate$1 (name, params) {
|
541 | var route = this.names[name];
|
542 | var output = "";
|
543 | if (!route) {
|
544 | throw new Error("There is no route named " + name);
|
545 | }
|
546 | var segments = route.segments;
|
547 | for (var i = 0; i < segments.length; i++) {
|
548 | var segment = segments[i];
|
549 | if (segment.type === 4 ) {
|
550 | continue;
|
551 | }
|
552 | output += "/";
|
553 | output += generate[segment.type](segment, params);
|
554 | }
|
555 | if (output.charAt(0) !== "/") {
|
556 | output = "/" + output;
|
557 | }
|
558 | if (params && params.queryParams) {
|
559 | output += this.generateQueryString(params.queryParams);
|
560 | }
|
561 | return output;
|
562 | };
|
563 | RouteRecognizer.prototype.generateQueryString = function generateQueryString (params) {
|
564 | var pairs = [];
|
565 | var keys = Object.keys(params);
|
566 | keys.sort();
|
567 | for (var i = 0; i < keys.length; i++) {
|
568 | var key = keys[i];
|
569 | var value = params[key];
|
570 | if (value == null) {
|
571 | continue;
|
572 | }
|
573 | var pair = encodeURIComponent(key);
|
574 | if (isArray(value)) {
|
575 | for (var j = 0; j < value.length; j++) {
|
576 | var arrayPair = key + "[]" + "=" + encodeURIComponent(value[j]);
|
577 | pairs.push(arrayPair);
|
578 | }
|
579 | }
|
580 | else {
|
581 | pair += "=" + encodeURIComponent(value);
|
582 | pairs.push(pair);
|
583 | }
|
584 | }
|
585 | if (pairs.length === 0) {
|
586 | return "";
|
587 | }
|
588 | return "?" + pairs.join("&");
|
589 | };
|
590 | RouteRecognizer.prototype.parseQueryString = function parseQueryString (queryString) {
|
591 | var pairs = queryString.split("&");
|
592 | var queryParams = {};
|
593 | for (var i = 0; i < pairs.length; i++) {
|
594 | var pair = pairs[i].split("="), key = decodeQueryParamPart(pair[0]), keyLength = key.length, isArray = false, value = (void 0);
|
595 | if (pair.length === 1) {
|
596 | value = "true";
|
597 | }
|
598 | else {
|
599 |
|
600 | if (keyLength > 2 && key.slice(keyLength - 2) === "[]") {
|
601 | isArray = true;
|
602 | key = key.slice(0, keyLength - 2);
|
603 | if (!queryParams[key]) {
|
604 | queryParams[key] = [];
|
605 | }
|
606 | }
|
607 | value = pair[1] ? decodeQueryParamPart(pair[1]) : "";
|
608 | }
|
609 | if (isArray) {
|
610 | queryParams[key].push(value);
|
611 | }
|
612 | else {
|
613 | queryParams[key] = value;
|
614 | }
|
615 | }
|
616 | return queryParams;
|
617 | };
|
618 | RouteRecognizer.prototype.recognize = function recognize (path) {
|
619 | var results;
|
620 | var states = [this.rootState];
|
621 | var queryParams = {};
|
622 | var isSlashDropped = false;
|
623 | var hashStart = path.indexOf("#");
|
624 | if (hashStart !== -1) {
|
625 | path = path.substr(0, hashStart);
|
626 | }
|
627 | var queryStart = path.indexOf("?");
|
628 | if (queryStart !== -1) {
|
629 | var queryString = path.substr(queryStart + 1, path.length);
|
630 | path = path.substr(0, queryStart);
|
631 | queryParams = this.parseQueryString(queryString);
|
632 | }
|
633 | if (path.charAt(0) !== "/") {
|
634 | path = "/" + path;
|
635 | }
|
636 | var originalPath = path;
|
637 | if (RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS) {
|
638 | path = normalizePath(path);
|
639 | }
|
640 | else {
|
641 | path = decodeURI(path);
|
642 | originalPath = decodeURI(originalPath);
|
643 | }
|
644 | var pathLen = path.length;
|
645 | if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
|
646 | path = path.substr(0, pathLen - 1);
|
647 | originalPath = originalPath.substr(0, originalPath.length - 1);
|
648 | isSlashDropped = true;
|
649 | }
|
650 | for (var i = 0; i < path.length; i++) {
|
651 | states = recognizeChar(states, path.charCodeAt(i));
|
652 | if (!states.length) {
|
653 | break;
|
654 | }
|
655 | }
|
656 | var solutions = [];
|
657 | for (var i$1 = 0; i$1 < states.length; i$1++) {
|
658 | if (states[i$1].handlers) {
|
659 | solutions.push(states[i$1]);
|
660 | }
|
661 | }
|
662 | states = sortSolutions(solutions);
|
663 | var state = solutions[0];
|
664 | if (state && state.handlers) {
|
665 |
|
666 |
|
667 | if (isSlashDropped && state.pattern && state.pattern.slice(-5) === "(.+)$") {
|
668 | originalPath = originalPath + "/";
|
669 | }
|
670 | results = findHandler(state, originalPath, queryParams);
|
671 | }
|
672 | return results;
|
673 | };
|
674 | RouteRecognizer.VERSION = "0.3.4";
|
675 |
|
676 |
|
677 | RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS = true;
|
678 | RouteRecognizer.Normalizer = {
|
679 | normalizeSegment: normalizeSegment, normalizePath: normalizePath, encodePathSegment: encodePathSegment
|
680 | };
|
681 | RouteRecognizer.prototype.map = map;
|
682 |
|
683 | export default RouteRecognizer;
|
684 |
|
685 |
|