暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

page.js 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.page=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  2. (function (process){
  3. /* globals require, module */
  4. 'use strict';
  5. /**
  6. * Module dependencies.
  7. */
  8. var pathtoRegexp = require('path-to-regexp');
  9. /**
  10. * Module exports.
  11. */
  12. module.exports = page;
  13. /**
  14. * Detect click event
  15. */
  16. var clickEvent = ('undefined' !== typeof document) && document.ontouchstart ? 'touchstart' : 'click';
  17. /**
  18. * To work properly with the URL
  19. * history.location generated polyfill in https://github.com/devote/HTML5-History-API
  20. */
  21. var location = ('undefined' !== typeof window) && (window.history.location || window.location);
  22. /**
  23. * Perform initial dispatch.
  24. */
  25. var dispatch = true;
  26. /**
  27. * Decode URL components (query string, pathname, hash).
  28. * Accommodates both regular percent encoding and x-www-form-urlencoded format.
  29. */
  30. var decodeURLComponents = true;
  31. /**
  32. * Base path.
  33. */
  34. var base = '';
  35. /**
  36. * Running flag.
  37. */
  38. var running;
  39. /**
  40. * HashBang option
  41. */
  42. var hashbang = false;
  43. /**
  44. * Previous context, for capturing
  45. * page exit events.
  46. */
  47. var prevContext;
  48. /**
  49. * Register `path` with callback `fn()`,
  50. * or route `path`, or redirection,
  51. * or `page.start()`.
  52. *
  53. * page(fn);
  54. * page('*', fn);
  55. * page('/user/:id', load, user);
  56. * page('/user/' + user.id, { some: 'thing' });
  57. * page('/user/' + user.id);
  58. * page('/from', '/to')
  59. * page();
  60. *
  61. * @param {String|Function} path
  62. * @param {Function} fn...
  63. * @api public
  64. */
  65. function page(path, fn) {
  66. // <callback>
  67. if ('function' === typeof path) {
  68. return page('*', path);
  69. }
  70. // route <path> to <callback ...>
  71. if ('function' === typeof fn) {
  72. var route = new Route(path);
  73. for (var i = 1; i < arguments.length; ++i) {
  74. page.callbacks.push(route.middleware(arguments[i]));
  75. }
  76. // show <path> with [state]
  77. } else if ('string' === typeof path) {
  78. page['string' === typeof fn ? 'redirect' : 'show'](path, fn);
  79. // start [options]
  80. } else {
  81. page.start(path);
  82. }
  83. }
  84. /**
  85. * Callback functions.
  86. */
  87. page.callbacks = [];
  88. page.exits = [];
  89. /**
  90. * Current path being processed
  91. * @type {String}
  92. */
  93. page.current = '';
  94. /**
  95. * Number of pages navigated to.
  96. * @type {number}
  97. *
  98. * page.len == 0;
  99. * page('/login');
  100. * page.len == 1;
  101. */
  102. page.len = 0;
  103. /**
  104. * Get or set basepath to `path`.
  105. *
  106. * @param {String} path
  107. * @api public
  108. */
  109. page.base = function(path) {
  110. if (0 === arguments.length) return base;
  111. base = path;
  112. };
  113. /**
  114. * Bind with the given `options`.
  115. *
  116. * Options:
  117. *
  118. * - `click` bind to click events [true]
  119. * - `popstate` bind to popstate [true]
  120. * - `dispatch` perform initial dispatch [true]
  121. *
  122. * @param {Object} options
  123. * @api public
  124. */
  125. page.start = function(options) {
  126. options = options || {};
  127. if (running) return;
  128. running = true;
  129. if (false === options.dispatch) dispatch = false;
  130. if (false === options.decodeURLComponents) decodeURLComponents = false;
  131. if (false !== options.popstate) window.addEventListener('popstate', onpopstate, false);
  132. if (false !== options.click) {
  133. document.addEventListener(clickEvent, onclick, false);
  134. }
  135. if (true === options.hashbang) hashbang = true;
  136. if (!dispatch) return;
  137. var url = (hashbang && ~location.hash.indexOf('#!')) ? location.hash.substr(2) + location.search : location.pathname + location.search + location.hash;
  138. page.replace(url, null, true, dispatch);
  139. };
  140. /**
  141. * Unbind click and popstate event handlers.
  142. *
  143. * @api public
  144. */
  145. page.stop = function() {
  146. if (!running) return;
  147. page.current = '';
  148. page.len = 0;
  149. running = false;
  150. document.removeEventListener(clickEvent, onclick, false);
  151. window.removeEventListener('popstate', onpopstate, false);
  152. };
  153. /**
  154. * Show `path` with optional `state` object.
  155. *
  156. * @param {String} path
  157. * @param {Object} state
  158. * @param {Boolean} dispatch
  159. * @return {Context}
  160. * @api public
  161. */
  162. page.show = function(path, state, dispatch, push) {
  163. var ctx = new Context(path, state);
  164. page.current = ctx.path;
  165. if (false !== dispatch) page.dispatch(ctx);
  166. if (false !== ctx.handled && false !== push) ctx.pushState();
  167. return ctx;
  168. };
  169. /**
  170. * Goes back in the history
  171. * Back should always let the current route push state and then go back.
  172. *
  173. * @param {String} path - fallback path to go back if no more history exists, if undefined defaults to page.base
  174. * @param {Object} [state]
  175. * @api public
  176. */
  177. page.back = function(path, state) {
  178. if (page.len > 0) {
  179. // this may need more testing to see if all browsers
  180. // wait for the next tick to go back in history
  181. history.back();
  182. page.len--;
  183. } else if (path) {
  184. setTimeout(function() {
  185. page.show(path, state);
  186. });
  187. }else{
  188. setTimeout(function() {
  189. page.show(base, state);
  190. });
  191. }
  192. };
  193. /**
  194. * Register route to redirect from one path to other
  195. * or just redirect to another route
  196. *
  197. * @param {String} from - if param 'to' is undefined redirects to 'from'
  198. * @param {String} [to]
  199. * @api public
  200. */
  201. page.redirect = function(from, to) {
  202. // Define route from a path to another
  203. if ('string' === typeof from && 'string' === typeof to) {
  204. page(from, function(e) {
  205. setTimeout(function() {
  206. page.replace(to);
  207. }, 0);
  208. });
  209. }
  210. // Wait for the push state and replace it with another
  211. if ('string' === typeof from && 'undefined' === typeof to) {
  212. setTimeout(function() {
  213. page.replace(from);
  214. }, 0);
  215. }
  216. };
  217. /**
  218. * Replace `path` with optional `state` object.
  219. *
  220. * @param {String} path
  221. * @param {Object} state
  222. * @return {Context}
  223. * @api public
  224. */
  225. page.replace = function(path, state, init, dispatch) {
  226. var ctx = new Context(path, state);
  227. page.current = ctx.path;
  228. ctx.init = init;
  229. ctx.save(); // save before dispatching, which may redirect
  230. if (false !== dispatch) page.dispatch(ctx);
  231. return ctx;
  232. };
  233. /**
  234. * Dispatch the given `ctx`.
  235. *
  236. * @param {Object} ctx
  237. * @api private
  238. */
  239. page.dispatch = function(ctx) {
  240. var prev = prevContext,
  241. i = 0,
  242. j = 0;
  243. prevContext = ctx;
  244. function nextExit() {
  245. var fn = page.exits[j++];
  246. if (!fn) return nextEnter();
  247. fn(prev, nextExit);
  248. }
  249. function nextEnter() {
  250. var fn = page.callbacks[i++];
  251. if (ctx.path !== page.current) {
  252. ctx.handled = false;
  253. return;
  254. }
  255. if (!fn) return unhandled(ctx);
  256. fn(ctx, nextEnter);
  257. }
  258. if (prev) {
  259. nextExit();
  260. } else {
  261. nextEnter();
  262. }
  263. };
  264. /**
  265. * Unhandled `ctx`. When it's not the initial
  266. * popstate then redirect. If you wish to handle
  267. * 404s on your own use `page('*', callback)`.
  268. *
  269. * @param {Context} ctx
  270. * @api private
  271. */
  272. function unhandled(ctx) {
  273. if (ctx.handled) return;
  274. var current;
  275. if (hashbang) {
  276. current = base + location.hash.replace('#!', '');
  277. } else {
  278. current = location.pathname + location.search;
  279. }
  280. if (current === ctx.canonicalPath) return;
  281. page.stop();
  282. ctx.handled = false;
  283. location.href = ctx.canonicalPath;
  284. }
  285. /**
  286. * Register an exit route on `path` with
  287. * callback `fn()`, which will be called
  288. * on the previous context when a new
  289. * page is visited.
  290. */
  291. page.exit = function(path, fn) {
  292. if (typeof path === 'function') {
  293. return page.exit('*', path);
  294. }
  295. var route = new Route(path);
  296. for (var i = 1; i < arguments.length; ++i) {
  297. page.exits.push(route.middleware(arguments[i]));
  298. }
  299. };
  300. /**
  301. * Remove URL encoding from the given `str`.
  302. * Accommodates whitespace in both x-www-form-urlencoded
  303. * and regular percent-encoded form.
  304. *
  305. * @param {str} URL component to decode
  306. */
  307. function decodeURLEncodedURIComponent(val) {
  308. if (typeof val !== 'string') { return val; }
  309. return decodeURLComponents ? decodeURIComponent(val.replace(/\+/g, ' ')) : val;
  310. }
  311. /**
  312. * Initialize a new "request" `Context`
  313. * with the given `path` and optional initial `state`.
  314. *
  315. * @param {String} path
  316. * @param {Object} state
  317. * @api public
  318. */
  319. function Context(path, state) {
  320. if ('/' === path[0] && 0 !== path.indexOf(base)) path = base + (hashbang ? '#!' : '') + path;
  321. var i = path.indexOf('?');
  322. this.canonicalPath = path;
  323. this.path = path.replace(base, '') || '/';
  324. if (hashbang) this.path = this.path.replace('#!', '') || '/';
  325. this.title = document.title;
  326. this.state = state || {};
  327. this.state.path = path;
  328. this.querystring = ~i ? decodeURLEncodedURIComponent(path.slice(i + 1)) : '';
  329. this.pathname = decodeURLEncodedURIComponent(~i ? path.slice(0, i) : path);
  330. this.params = {};
  331. // fragment
  332. this.hash = '';
  333. if (!hashbang) {
  334. if (!~this.path.indexOf('#')) return;
  335. var parts = this.path.split('#');
  336. this.path = parts[0];
  337. this.hash = decodeURLEncodedURIComponent(parts[1]) || '';
  338. this.querystring = this.querystring.split('#')[0];
  339. }
  340. }
  341. /**
  342. * Expose `Context`.
  343. */
  344. page.Context = Context;
  345. /**
  346. * Push state.
  347. *
  348. * @api private
  349. */
  350. Context.prototype.pushState = function() {
  351. page.len++;
  352. history.pushState(this.state, this.title, hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
  353. };
  354. /**
  355. * Save the context state.
  356. *
  357. * @api public
  358. */
  359. Context.prototype.save = function() {
  360. history.replaceState(this.state, this.title, hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
  361. };
  362. /**
  363. * Initialize `Route` with the given HTTP `path`,
  364. * and an array of `callbacks` and `options`.
  365. *
  366. * Options:
  367. *
  368. * - `sensitive` enable case-sensitive routes
  369. * - `strict` enable strict matching for trailing slashes
  370. *
  371. * @param {String} path
  372. * @param {Object} options.
  373. * @api private
  374. */
  375. function Route(path, options) {
  376. options = options || {};
  377. this.path = (path === '*') ? '(.*)' : path;
  378. this.method = 'GET';
  379. this.regexp = pathtoRegexp(this.path,
  380. this.keys = [],
  381. options.sensitive,
  382. options.strict);
  383. }
  384. /**
  385. * Expose `Route`.
  386. */
  387. page.Route = Route;
  388. /**
  389. * Return route middleware with
  390. * the given callback `fn()`.
  391. *
  392. * @param {Function} fn
  393. * @return {Function}
  394. * @api public
  395. */
  396. Route.prototype.middleware = function(fn) {
  397. var self = this;
  398. return function(ctx, next) {
  399. if (self.match(ctx.path, ctx.params)) return fn(ctx, next);
  400. next();
  401. };
  402. };
  403. /**
  404. * Check if this route matches `path`, if so
  405. * populate `params`.
  406. *
  407. * @param {String} path
  408. * @param {Object} params
  409. * @return {Boolean}
  410. * @api private
  411. */
  412. Route.prototype.match = function(path, params) {
  413. var keys = this.keys,
  414. qsIndex = path.indexOf('?'),
  415. pathname = ~qsIndex ? path.slice(0, qsIndex) : path,
  416. m = this.regexp.exec(decodeURIComponent(pathname));
  417. if (!m) return false;
  418. for (var i = 1, len = m.length; i < len; ++i) {
  419. var key = keys[i - 1];
  420. var val = decodeURLEncodedURIComponent(m[i]);
  421. if (val !== undefined || !(hasOwnProperty.call(params, key.name))) {
  422. params[key.name] = val;
  423. }
  424. }
  425. return true;
  426. };
  427. /**
  428. * Handle "populate" events.
  429. */
  430. var onpopstate = (function () {
  431. var loaded = false;
  432. if ('undefined' === typeof window) {
  433. return;
  434. }
  435. if (document.readyState === 'complete') {
  436. loaded = true;
  437. } else {
  438. window.addEventListener('load', function() {
  439. setTimeout(function() {
  440. loaded = true;
  441. }, 0);
  442. });
  443. }
  444. return function onpopstate(e) {
  445. if (!loaded) return;
  446. if (e.state) {
  447. var path = e.state.path;
  448. page.replace(path, e.state);
  449. } else {
  450. page.show(location.pathname + location.hash, undefined, undefined, false);
  451. }
  452. };
  453. })();
  454. /**
  455. * Handle "click" events.
  456. */
  457. function onclick(e) {
  458. if (1 !== which(e)) return;
  459. if (e.metaKey || e.ctrlKey || e.shiftKey) return;
  460. if (e.defaultPrevented) return;
  461. // ensure link
  462. var el = e.target;
  463. while (el && 'A' !== el.nodeName) el = el.parentNode;
  464. if (!el || 'A' !== el.nodeName) return;
  465. // Ignore if tag has
  466. // 1. "download" attribute
  467. // 2. rel="external" attribute
  468. if (el.hasAttribute('download') || el.getAttribute('rel') === 'external') return;
  469. // ensure non-hash for the same path
  470. var link = el.getAttribute('href');
  471. if (!hashbang && el.pathname === location.pathname && (el.hash || '#' === link)) return;
  472. // Check for mailto: in the href
  473. if (link && link.indexOf('mailto:') > -1) return;
  474. // check target
  475. if (el.target) return;
  476. // x-origin
  477. if (!sameOrigin(el.href)) return;
  478. // rebuild path
  479. var path = el.pathname + el.search + (el.hash || '');
  480. // strip leading "/[drive letter]:" on NW.js on Windows
  481. if (typeof process !== 'undefined' && path.match(/^\/[a-zA-Z]:\//)) {
  482. path = path.replace(/^\/[a-zA-Z]:\//, '/');
  483. }
  484. // same page
  485. var orig = path;
  486. if (path.indexOf(base) === 0) {
  487. path = path.substr(base.length);
  488. }
  489. if (hashbang) path = path.replace('#!', '');
  490. if (base && orig === path) return;
  491. e.preventDefault();
  492. page.show(orig);
  493. }
  494. /**
  495. * Event button.
  496. */
  497. function which(e) {
  498. e = e || window.event;
  499. return null === e.which ? e.button : e.which;
  500. }
  501. /**
  502. * Check if `href` is the same origin.
  503. */
  504. function sameOrigin(href) {
  505. var origin = location.protocol + '//' + location.hostname;
  506. if (location.port) origin += ':' + location.port;
  507. return (href && (0 === href.indexOf(origin)));
  508. }
  509. page.sameOrigin = sameOrigin;
  510. }).call(this,require('_process'))
  511. },{"_process":2,"path-to-regexp":3}],2:[function(require,module,exports){
  512. // shim for using process in browser
  513. var process = module.exports = {};
  514. process.nextTick = (function () {
  515. var canSetImmediate = typeof window !== 'undefined'
  516. && window.setImmediate;
  517. var canMutationObserver = typeof window !== 'undefined'
  518. && window.MutationObserver;
  519. var canPost = typeof window !== 'undefined'
  520. && window.postMessage && window.addEventListener
  521. ;
  522. if (canSetImmediate) {
  523. return function (f) { return window.setImmediate(f) };
  524. }
  525. var queue = [];
  526. if (canMutationObserver) {
  527. var hiddenDiv = document.createElement("div");
  528. var observer = new MutationObserver(function () {
  529. var queueList = queue.slice();
  530. queue.length = 0;
  531. queueList.forEach(function (fn) {
  532. fn();
  533. });
  534. });
  535. observer.observe(hiddenDiv, { attributes: true });
  536. return function nextTick(fn) {
  537. if (!queue.length) {
  538. hiddenDiv.setAttribute('yes', 'no');
  539. }
  540. queue.push(fn);
  541. };
  542. }
  543. if (canPost) {
  544. window.addEventListener('message', function (ev) {
  545. var source = ev.source;
  546. if ((source === window || source === null) && ev.data === 'process-tick') {
  547. ev.stopPropagation();
  548. if (queue.length > 0) {
  549. var fn = queue.shift();
  550. fn();
  551. }
  552. }
  553. }, true);
  554. return function nextTick(fn) {
  555. queue.push(fn);
  556. window.postMessage('process-tick', '*');
  557. };
  558. }
  559. return function nextTick(fn) {
  560. setTimeout(fn, 0);
  561. };
  562. })();
  563. process.title = 'browser';
  564. process.browser = true;
  565. process.env = {};
  566. process.argv = [];
  567. function noop() {}
  568. process.on = noop;
  569. process.addListener = noop;
  570. process.once = noop;
  571. process.off = noop;
  572. process.removeListener = noop;
  573. process.removeAllListeners = noop;
  574. process.emit = noop;
  575. process.binding = function (name) {
  576. throw new Error('process.binding is not supported');
  577. };
  578. // TODO(shtylman)
  579. process.cwd = function () { return '/' };
  580. process.chdir = function (dir) {
  581. throw new Error('process.chdir is not supported');
  582. };
  583. },{}],3:[function(require,module,exports){
  584. var isArray = require('isarray');
  585. /**
  586. * Expose `pathToRegexp`.
  587. */
  588. module.exports = pathToRegexp;
  589. /**
  590. * The main path matching regexp utility.
  591. *
  592. * @type {RegExp}
  593. */
  594. var PATH_REGEXP = new RegExp([
  595. // Match escaped characters that would otherwise appear in future matches.
  596. // This allows the user to escape special characters that won't transform.
  597. '(\\\\.)',
  598. // Match Express-style parameters and un-named parameters with a prefix
  599. // and optional suffixes. Matches appear as:
  600. //
  601. // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"]
  602. // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
  603. '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?',
  604. // Match regexp special characters that are always escaped.
  605. '([.+*?=^!:${}()[\\]|\\/])'
  606. ].join('|'), 'g');
  607. /**
  608. * Escape the capturing group by escaping special characters and meaning.
  609. *
  610. * @param {String} group
  611. * @return {String}
  612. */
  613. function escapeGroup (group) {
  614. return group.replace(/([=!:$\/()])/g, '\\$1');
  615. }
  616. /**
  617. * Attach the keys as a property of the regexp.
  618. *
  619. * @param {RegExp} re
  620. * @param {Array} keys
  621. * @return {RegExp}
  622. */
  623. function attachKeys (re, keys) {
  624. re.keys = keys;
  625. return re;
  626. }
  627. /**
  628. * Get the flags for a regexp from the options.
  629. *
  630. * @param {Object} options
  631. * @return {String}
  632. */
  633. function flags (options) {
  634. return options.sensitive ? '' : 'i';
  635. }
  636. /**
  637. * Pull out keys from a regexp.
  638. *
  639. * @param {RegExp} path
  640. * @param {Array} keys
  641. * @return {RegExp}
  642. */
  643. function regexpToRegexp (path, keys) {
  644. // Use a negative lookahead to match only capturing groups.
  645. var groups = path.source.match(/\((?!\?)/g);
  646. if (groups) {
  647. for (var i = 0; i < groups.length; i++) {
  648. keys.push({
  649. name: i,
  650. delimiter: null,
  651. optional: false,
  652. repeat: false
  653. });
  654. }
  655. }
  656. return attachKeys(path, keys);
  657. }
  658. /**
  659. * Transform an array into a regexp.
  660. *
  661. * @param {Array} path
  662. * @param {Array} keys
  663. * @param {Object} options
  664. * @return {RegExp}
  665. */
  666. function arrayToRegexp (path, keys, options) {
  667. var parts = [];
  668. for (var i = 0; i < path.length; i++) {
  669. parts.push(pathToRegexp(path[i], keys, options).source);
  670. }
  671. var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options));
  672. return attachKeys(regexp, keys);
  673. }
  674. /**
  675. * Replace the specific tags with regexp strings.
  676. *
  677. * @param {String} path
  678. * @param {Array} keys
  679. * @return {String}
  680. */
  681. function replacePath (path, keys) {
  682. var index = 0;
  683. function replace (_, escaped, prefix, key, capture, group, suffix, escape) {
  684. if (escaped) {
  685. return escaped;
  686. }
  687. if (escape) {
  688. return '\\' + escape;
  689. }
  690. var repeat = suffix === '+' || suffix === '*';
  691. var optional = suffix === '?' || suffix === '*';
  692. keys.push({
  693. name: key || index++,
  694. delimiter: prefix || '/',
  695. optional: optional,
  696. repeat: repeat
  697. });
  698. prefix = prefix ? ('\\' + prefix) : '';
  699. capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?');
  700. if (repeat) {
  701. capture = capture + '(?:' + prefix + capture + ')*';
  702. }
  703. if (optional) {
  704. return '(?:' + prefix + '(' + capture + '))?';
  705. }
  706. // Basic parameter support.
  707. return prefix + '(' + capture + ')';
  708. }
  709. return path.replace(PATH_REGEXP, replace);
  710. }
  711. /**
  712. * Normalize the given path string, returning a regular expression.
  713. *
  714. * An empty array can be passed in for the keys, which will hold the
  715. * placeholder key descriptions. For example, using `/user/:id`, `keys` will
  716. * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
  717. *
  718. * @param {(String|RegExp|Array)} path
  719. * @param {Array} [keys]
  720. * @param {Object} [options]
  721. * @return {RegExp}
  722. */
  723. function pathToRegexp (path, keys, options) {
  724. keys = keys || [];
  725. if (!isArray(keys)) {
  726. options = keys;
  727. keys = [];
  728. } else if (!options) {
  729. options = {};
  730. }
  731. if (path instanceof RegExp) {
  732. return regexpToRegexp(path, keys, options);
  733. }
  734. if (isArray(path)) {
  735. return arrayToRegexp(path, keys, options);
  736. }
  737. var strict = options.strict;
  738. var end = options.end !== false;
  739. var route = replacePath(path, keys);
  740. var endsWithSlash = path.charAt(path.length - 1) === '/';
  741. // In non-strict mode we allow a slash at the end of match. If the path to
  742. // match already ends with a slash, we remove it for consistency. The slash
  743. // is valid at the end of a path match, not in the middle. This is important
  744. // in non-ending mode, where "/test/" shouldn't match "/test//route".
  745. if (!strict) {
  746. route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?';
  747. }
  748. if (end) {
  749. route += '$';
  750. } else {
  751. // In non-ending mode, we need the capturing groups to match as much as
  752. // possible by using a positive lookahead to the end or next path segment.
  753. route += strict && endsWithSlash ? '' : '(?=\\/|$)';
  754. }
  755. return attachKeys(new RegExp('^' + route, flags(options)), keys);
  756. }
  757. },{"isarray":4}],4:[function(require,module,exports){
  758. module.exports = Array.isArray || function (arr) {
  759. return Object.prototype.toString.call(arr) == '[object Array]';
  760. };
  761. },{}]},{},[1])(1)
  762. });