define([], function() { "use strict"; function page(path, fn) { if ("function" == typeof path) return page("*", path); if ("function" == typeof fn) for (var route = new Route(path), i = 1; i < arguments.length; ++i) page.callbacks.push(route.middleware(arguments[i])); else "string" == typeof path ? page["string" == typeof fn ? "redirect" : "show"](path, fn) : page.start(path) } function unhandled(ctx) { if (!ctx.handled) { var current; current = hashbang ? base + location.hash.replace("#!", "") : location.pathname + location.search, current !== ctx.canonicalPath && (page.stop(), ctx.handled = !1, location.href = ctx.canonicalPath) } } function decodeURLEncodedURIComponent(val) { return "string" != typeof val ? val : decodeURLComponents ? decodeURIComponent(val.replace(/\+/g, " ")) : val } function Context(path, state) { "/" === path[0] && 0 !== path.indexOf(base) && (path = base + (hashbang ? "#!" : "") + path); var i = path.indexOf("?"); if (this.canonicalPath = path, this.path = path.replace(base, "") || "/", hashbang && (this.path = this.path.replace("#!", "") || "/"), this.title = document.title, this.state = state || {}, this.state.path = path, this.querystring = ~i ? decodeURLEncodedURIComponent(path.slice(i + 1)) : "", this.pathname = decodeURLEncodedURIComponent(~i ? path.slice(0, i) : path), this.params = {}, this.hash = "", !hashbang) { if (!~this.path.indexOf("#")) return; var parts = this.path.split("#"); this.path = parts[0], this.hash = decodeURLEncodedURIComponent(parts[1]) || "", this.querystring = this.querystring.split("#")[0] } } function Route(path, options) { options = options || {}, this.path = "*" === path ? "(.*)" : path, this.method = "GET", this.regexp = pathToRegexp(this.path, this.keys = [], options.sensitive, options.strict) } function ignorePopState(event) { var state = event.state || {}; return !1 === previousPopState.navigate ? (previousPopState = state, !0) : (previousPopState = state, !1) } function onclick(e, checkWhich) { if ((1 === which(e) || !1 === checkWhich) && !(e.metaKey || e.ctrlKey || e.shiftKey || e.defaultPrevented)) { for (var el = e.target; el && "A" !== el.nodeName;) el = el.parentNode; if (el && "A" === el.nodeName && !el.hasAttribute("download") && "external" !== el.getAttribute("rel")) { var link = el.getAttribute("href"); if ("#" === link) return void e.preventDefault(); if ((hashbang || el.pathname !== location.pathname || !el.hash && "#" !== link) && !el.target && sameOrigin(el.href)) { var path = el.pathname + el.search + (el.hash || ""), orig = path; 0 === path.indexOf(base) && (path = path.substr(base.length)), hashbang && (path = path.replace("#!", "")), e.preventDefault(), page.show(orig) } } } } function which(e) { return e = e || window.event, null === e.which ? e.button : e.which } function sameOrigin(href) { var origin = location.protocol + "//" + location.hostname; return location.port && (origin += ":" + location.port), href && 0 === href.indexOf(origin) } function parse(str) { for (var res, tokens = [], key = 0, index = 0, path = ""; null != (res = PATH_REGEXP.exec(str));) { var m = res[0], escaped = res[1], offset = res.index; if (path += str.slice(index, offset), index = offset + m.length, escaped) path += escaped[1]; else { path && (tokens.push(path), path = ""); var prefix = res[2], name = res[3], capture = res[4], group = res[5], suffix = res[6], asterisk = res[7], repeat = "+" === suffix || "*" === suffix, optional = "?" === suffix || "*" === suffix, delimiter = prefix || "/", pattern = capture || group || (asterisk ? ".*" : "[^" + delimiter + "]+?"); tokens.push({ name: name || key++, prefix: prefix || "", delimiter: delimiter, optional: optional, repeat: repeat, pattern: escapeGroup(pattern) }) } } return index < str.length && (path += str.substr(index)), path && tokens.push(path), tokens } function escapeString(str) { return str.replace(/([.+*?=^!:${}()[\]|\/])/g, "\\$1") } function escapeGroup(group) { return group.replace(/([=!:$\/()])/g, "\\$1") } function attachKeys(re, keys) { return re.keys = keys, re } function flags(options) { return options.sensitive ? "" : "i" } function regexpToRegexp(path, keys) { var groups = path.source.match(/\((?!\?)/g); if (groups) for (var i = 0; i < groups.length; i++) keys.push({ name: i, prefix: null, delimiter: null, optional: !1, repeat: !1, pattern: null }); return attachKeys(path, keys) } function arrayToRegexp(path, keys, options) { for (var parts = [], i = 0; i < path.length; i++) parts.push(pathToRegexp(path[i], keys, options).source); return attachKeys(new RegExp("(?:" + parts.join("|") + ")", flags(options)), keys) } function stringToRegexp(path, keys, options) { for (var tokens = parse(path), re = tokensToRegExp(tokens, options), i = 0; i < tokens.length; i++) "string" != typeof tokens[i] && keys.push(tokens[i]); return attachKeys(re, keys) } function tokensToRegExp(tokens, options) { options = options || {}; for (var strict = options.strict, end = !1 !== options.end, route = "", lastToken = tokens[tokens.length - 1], endsWithSlash = "string" == typeof lastToken && /\/$/.test(lastToken), i = 0; i < tokens.length; i++) { var token = tokens[i]; if ("string" == typeof token) route += escapeString(token); else { var prefix = escapeString(token.prefix), capture = token.pattern; token.repeat && (capture += "(?:" + prefix + capture + ")*"), capture = token.optional ? prefix ? "(?:" + prefix + "(" + capture + "))?" : "(" + capture + ")?" : prefix + "(" + capture + ")", route += capture } } return strict || (route = (endsWithSlash ? route.slice(0, -2) : route) + "(?:\\/(?=$))?"), route += end ? "$" : strict && endsWithSlash ? "" : "(?=\\/|$)", new RegExp("^" + route, flags(options)) } function pathToRegexp(path, keys, options) { return keys = keys || [], isarray(keys) ? options || (options = {}) : (options = keys, keys = []), path instanceof RegExp ? regexpToRegexp(path, keys, options) : isarray(path) ? arrayToRegexp(path, keys, options) : stringToRegexp(path, keys, options) } var running, prevContext, prevPageContext, clickEvent = "undefined" != typeof document && document.ontouchstart ? "touchstart" : "click", location = "undefined" != typeof window && (window.history.location || window.location), dispatch = !0, decodeURLComponents = !0, base = "", hashbang = !1, enableHistory = !1; page.callbacks = [], page.exits = [], page.current = "", page.len = 0, page.base = function(path) { if (0 === arguments.length) return base; base = path }, page.start = function(options) { if (options = options || {}, !running && (running = !0, !1 === options.dispatch && (dispatch = !1), !1 === options.decodeURLComponents && (decodeURLComponents = !1), !1 !== options.popstate && window.addEventListener("popstate", onpopstate, !1), !1 !== options.click && document.addEventListener(clickEvent, onclick, !1), null != options.enableHistory && (enableHistory = options.enableHistory), !0 === options.hashbang && (hashbang = !0), dispatch)) { var url; if (hashbang && ~location.hash.indexOf("#!")) { url = location.hash.substr(2); var href = location.href.toString(); href.indexOf("?") >= href.indexOf("#!") && (url += location.search) } else url = location.pathname + location.search + location.hash; page.replace(url, null, !0, dispatch) } }, page.stop = function() { running && (page.current = "", page.len = 0, running = !1, document.removeEventListener(clickEvent, onclick, !1), window.removeEventListener("popstate", onpopstate, !1)) }, page.show = function(path, state, dispatch, push, isBack) { var ctx = new Context(path, state); return ctx.isBack = isBack, page.current = ctx.path, !1 !== dispatch && page.dispatch(ctx), !1 !== ctx.handled && !1 !== push && ctx.pushState(), ctx }, page.restorePreviousState = function() { prevContext = prevPageContext, page.show(prevContext.pathname, prevContext.state, !1, !0, !1) }, page.back = function(path, state) { if (enableHistory) return void history.back(); if (page.len > 0) { if (enableHistory) history.back(); else if (backStack.length > 2) { backStack.length--; var previousState = backStack[backStack.length - 1]; page.show(previousState.path, previousState.state, !0, !1, !0) } page.len-- } else path ? setTimeout(function() { page.show(path, state) }) : setTimeout(function() { page.show(base, state) }) }, page.enableNativeHistory = function() { return enableHistory }, page.canGoBack = function() { return enableHistory ? history.length > 1 : (page.len || 0) > 0 }, page.redirect = function(from, to) { "string" == typeof from && "string" == typeof to && page(from, function(e) { setTimeout(function() { page.replace(to) }, 0) }), "string" == typeof from && void 0 === to && setTimeout(function() { page.replace(from) }, 0) }, page.replace = function(path, state, init, dispatch, isBack) { var ctx = new Context(path, state); return ctx.isBack = isBack, page.current = ctx.path, ctx.init = init, ctx.save(), !1 !== dispatch && page.dispatch(ctx), ctx }, page.dispatch = function(ctx) { function nextExit() { var fn = page.exits[j++]; if (!fn) return nextEnter(); fn(prev, nextExit) } function nextEnter() { var fn = page.callbacks[i++]; return ctx.path !== page.current ? void(ctx.handled = !1) : fn ? void fn(ctx, nextEnter) : unhandled(ctx) } var prev = prevContext, i = 0, j = 0; prevPageContext = prevContext, prevContext = ctx, prev ? nextExit() : nextEnter() }, page.exit = function(path, fn) { if ("function" == typeof path) return page.exit("*", path); for (var route = new Route(path), i = 1; i < arguments.length; ++i) page.exits.push(route.middleware(arguments[i])) }, page.Context = Context; var backStack = []; Context.prototype.pushState = function() { page.len++, enableHistory ? history.pushState(this.state, this.title, hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath) : backStack.push({ state: this.state, title: this.title, url: hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath, path: this.path }) }, Context.prototype.save = function() { enableHistory ? history.replaceState(this.state, this.title, hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath) : backStack[page.len || 0] = { state: this.state, title: this.title, url: hashbang && "/" !== this.path ? "#!" + this.path : this.canonicalPath, path: this.path } }, page.Route = Route, Route.prototype.middleware = function(fn) { var self = this; return function(ctx, next) { if (self.match(ctx.path, ctx.params)) return fn(ctx, next); next() } }, Route.prototype.match = function(path, params) { var keys = this.keys, qsIndex = path.indexOf("?"), pathname = ~qsIndex ? path.slice(0, qsIndex) : path, m = this.regexp.exec(decodeURIComponent(pathname)); if (!m) return !1; for (var i = 1, len = m.length; i < len; ++i) { var key = keys[i - 1], val = decodeURLEncodedURIComponent(m[i]); void 0 === val && hasOwnProperty.call(params, key.name) || (params[key.name] = val) } return !0 }; var previousPopState = {}; page.pushState = function(state, title, url) { hashbang && (url = "#!" + url), history.pushState(state, title, url), previousPopState = state }; var onpopstate = function() { var loaded = !1; if ("undefined" != typeof window) return "complete" === document.readyState ? loaded = !0 : window.addEventListener("load", function() { setTimeout(function() { loaded = !0 }, 0) }), function(e) { if (loaded && !ignorePopState(e)) if (e.state) { var path = e.state.path; page.replace(path, e.state, null, null, !0) } else page.show(location.pathname + location.hash, void 0, void 0, !1, !0) } }(); page.handleAnchorClick = onclick, page.sameOrigin = sameOrigin; var PATH_REGEXP = new RegExp(["(\\\\.)", "([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))"].join("|"), "g"), isarray = Array.isArray || function(arr) { return "[object Array]" === Object.prototype.toString.call(arr) }; return page });