/* * Based on github.com/visionmedia/page.js * Licensed under the MIT license * Copyright 2012 TJ Holowaychuk <tj@vision-media.ca> */ let running = false;let currentState = null;const callbacks = []; this.page = function (value, fn) { if (typeof value === "function") { page("*", value); } else if (typeof fn === "function") { const route = new Route(value); callbacks.push(route.middleware(fn)); } else if (typeof value === "string") { page.show(value, fn); } else { page.start(value); }}; page.start = function (options) { if (options == null) { options = {}; } if (!running) { running = true; addEventListener("popstate", onpopstate); addEventListener("click", onclick); page.replace(currentPath(), null, null, true); }}; page.stop = function () { if (running) { running = false; removeEventListener("click", onclick); removeEventListener("popstate", onpopstate); }}; page.show = function (path, state) { if (path === currentState?.path) { return; } const context = new Context(path, state); const previousState = currentState; currentState = context.state; const res = page.dispatch(context); if (res) { currentState = previousState; location.assign(res); } else { context.pushState(); updateCanonicalLink(); track(); } return context;}; page.replace = function (path, state, skipDispatch, init) { let result; let context = new Context(path, state || currentState); context.init = init; currentState = context.state; if (!skipDispatch) { result = page.dispatch(context); } if (result) { context = new Context(result); context.init = init; currentState = context.state; page.dispatch(context); } context.replaceState(); updateCanonicalLink(); if (!skipDispatch) { track(); } return context;}; page.dispatch = function (context) { let i = 0; const next = function () { let fn = callbacks[i++]; return fn?.(context, next); }; return next();}; page.canGoBack = () => !Context.isIntialState(currentState); page.canGoForward = () => !Context.isLastState(currentState); const currentPath = () => location.pathname + location.search + location.hash; class Context { /** * A counter tracking the largest state ID used. */ static stateId = 0; /** * The session ID to apply across all contexts. */ static sessionId = Date.now(); static isIntialState(state) { return state.id === 0; } static isLastState(state) { return state.id === Context.stateId - 1; } static isInitialPopState(state) { return state.path === this.initialPath && Context.stateId === 1; } static isSameSession(state) { return state.sessionId === Context.sessionId; } constructor(path, state) { this.initialPath = currentPath(); if (path == null) { path = "/"; } this.path = path; if (state == null) { state = {}; } this.state = state; this.pathname = this.path.replace( /(?:\?([^#]*))?(?:#(.*))?$/, (_, query, hash) => { this.query = query; this.hash = hash; return ""; }, ); if (this.state.id == null) { Context.stateId++; this.state.id = Context.stateId; } if (this.state.sessionId == null) { this.state.sessionId = Context.sessionId; } this.state.path = this.path; } pushState() { history.pushState(this.state, "", this.path); } replaceState() { try { history.replaceState(this.state, "", this.path); } catch (error) {} // NS_ERROR_FAILURE in Firefox }} class Route { constructor(path, options) { this.path = path; if (options == null) { options = {}; } this.keys = []; this.regexp = pathToRegexp(this.path, this.keys); } middleware(fn) { return (context, next) => { let params = []; if (this.match(context.pathname, params)) { context.params = params; return fn(context, next); } else { return next(); } }; } match(path, params) { const matchData = this.regexp.exec(path); if (!matchData) { return; } const iterable = matchData.slice(1); for (let i = 0; i < iterable.length; i++) { var key = this.keys[i]; var value = iterable[i]; if (typeof value === "string") { value = decodeURIComponent(value); } if (key) { params[key.name] = value; } else { params.push(value); } } return true; }} var pathToRegexp = function (path, keys) { if (path instanceof RegExp) { return path; } if (path instanceof Array) { path = `(${path.join("|")})`; } path = path .replace(/\/\(/g, "(?:/") .replace( /(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, (_, slash, format, key, capture, optional) => { if (slash == null) { slash = ""; } if (format == null) { format = ""; } keys.push({ name: key, optional: !!optional }); let str = optional ? "" : slash; str += "(?:"; if (optional) { str += slash; } str += format; str += capture || (format ? "([^/.]+?)" : "([^/]+?)"); str += ")"; if (optional) { str += optional; } return str; }, ) .replace(/([\/.])/g, "\\$1") .replace(/\*/g, "(.*)"); return new RegExp(`^${path}$`);}; var onpopstate = function (event) { if (!event.state || Context.isInitialPopState(event.state)) { return; } if (Context.isSameSession(event.state)) { page.replace(event.state.path, event.state); } else { location.reload(); }}; var onclick = function (event) { try { if ( event.which !== 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.defaultPrevented ) { return; } } catch (error) { return; } let link = $.eventTarget(event); while (link && !(link.tagName === "A" || link.tagName === "a")) { link = link.parentNode; } if (!link) return; // If the `<a>` is in an SVG, its attributes are `SVGAnimatedString`s // instead of strings let href = link.href instanceof SVGAnimatedString ? new URL(link.href.baseVal, location.href).href : link.href; let target = link.target instanceof SVGAnimatedString ? link.target.baseVal : link.target; if (!target && isSameOrigin(href)) { event.preventDefault(); let parsedHref = new URL(href); let path = parsedHref.pathname + parsedHref.search + parsedHref.hash; path = path.replace(/^\/\/+/, "/"); // IE11 bug page.show(path); }}; var isSameOrigin = (url) => url.startsWith(`${location.protocol}//${location.hostname}`); var updateCanonicalLink = function () { if (!this.canonicalLink) { this.canonicalLink = document.head.querySelector('link[rel="canonical"]'); } return this.canonicalLink.setAttribute( "href", `https://${location.host}${location.pathname}`, );}; const trackers = []; page.track = function (fn) { trackers.push(fn);}; var track = function () { if (app.config.env !== "production") { return; } if (navigator.doNotTrack === "1") { return; } if (navigator.globalPrivacyControl) { return; } const consentGiven = Cookies.get("analyticsConsent"); const consentAsked = Cookies.get("analyticsConsentAsked"); if (consentGiven === "1") { for (var tracker of trackers) { tracker.call(); } } else if (consentGiven === undefined && consentAsked === undefined) { // Only ask for consent once per browser session Cookies.set("analyticsConsentAsked", "1"); new app.views.Notif("AnalyticsConsent", { autoHide: null }); }}; this.resetAnalytics = function () { for (var cookie of document.cookie.split(/;\s?/)) { var name = cookie.split("=")[0]; if (name[0] === "_" && name[1] !== "_") { Cookies.expire(name); } }};
/*
* Based on github.com/visionmedia/page.js
* Licensed under the MIT license
* Copyright 2012 TJ Holowaychuk <tj@vision-media.ca>
*/
let running = false;
let currentState = null;
const callbacks = [];
this.page = function (value, fn) {
if (typeof value === "function") {
page("*", value);
} else if (typeof fn === "function") {
const route = new Route(value);
callbacks.push(route.middleware(fn));
} else if (typeof value === "string") {
page.show(value, fn);
} else {
page.start(value);
}
};
page.start = function (options) {
if (options == null) {
options = {};
}
if (!running) {
running = true;
addEventListener("popstate", onpopstate);
addEventListener("click", onclick);
page.replace(currentPath(), null, null, true);
}
};
page.stop = function () {
if (running) {
running = false;
removeEventListener("click", onclick);
removeEventListener("popstate", onpopstate);
}
};
page.show = function (path, state) {
if (path === currentState?.path) {
return;
}
const context = new Context(path, state);
const previousState = currentState;
currentState = context.state;
const res = page.dispatch(context);
if (res) {
currentState = previousState;
location.assign(res);
} else {
context.pushState();
updateCanonicalLink();
track();
}
return context;
};
page.replace = function (path, state, skipDispatch, init) {
let result;
let context = new Context(path, state || currentState);
context.init = init;
currentState = context.state;
if (!skipDispatch) {
result = page.dispatch(context);
}
if (result) {
context = new Context(result);
context.init = init;
currentState = context.state;
page.dispatch(context);
}
context.replaceState();
updateCanonicalLink();
if (!skipDispatch) {
track();
}
return context;
};
page.dispatch = function (context) {
let i = 0;
const next = function () {
let fn = callbacks[i++];
return fn?.(context, next);
};
return next();
};
page.canGoBack = () => !Context.isIntialState(currentState);
page.canGoForward = () => !Context.isLastState(currentState);
const currentPath = () => location.pathname + location.search + location.hash;
class Context {
/**
* A counter tracking the largest state ID used.
*/
static stateId = 0;
/**
* The session ID to apply across all contexts.
*/
static sessionId = Date.now();
static isIntialState(state) {
return state.id === 0;
}
static isLastState(state) {
return state.id === Context.stateId - 1;
}
static isInitialPopState(state) {
return state.path === this.initialPath && Context.stateId === 1;
}
static isSameSession(state) {
return state.sessionId === Context.sessionId;
}
constructor(path, state) {
this.initialPath = currentPath();
if (path == null) {
path = "/";
}
this.path = path;
if (state == null) {
state = {};
}
this.state = state;
this.pathname = this.path.replace(
/(?:\?([^#]*))?(?:#(.*))?$/,
(_, query, hash) => {
this.query = query;
this.hash = hash;
return "";
},
);
if (this.state.id == null) {
Context.stateId++;
this.state.id = Context.stateId;
}
if (this.state.sessionId == null) {
this.state.sessionId = Context.sessionId;
}
this.state.path = this.path;
}
pushState() {
history.pushState(this.state, "", this.path);
}
replaceState() {
try {
history.replaceState(this.state, "", this.path);
} catch (error) {} // NS_ERROR_FAILURE in Firefox
}
}
class Route {
constructor(path, options) {
this.path = path;
if (options == null) {
options = {};
}
this.keys = [];
this.regexp = pathToRegexp(this.path, this.keys);
}
middleware(fn) {
return (context, next) => {
let params = [];
if (this.match(context.pathname, params)) {
context.params = params;
return fn(context, next);
} else {
return next();
}
};
}
match(path, params) {
const matchData = this.regexp.exec(path);
if (!matchData) {
return;
}
const iterable = matchData.slice(1);
for (let i = 0; i < iterable.length; i++) {
var key = this.keys[i];
var value = iterable[i];
if (typeof value === "string") {
value = decodeURIComponent(value);
}
if (key) {
params[key.name] = value;
} else {
params.push(value);
}
}
return true;
}
}
var pathToRegexp = function (path, keys) {
if (path instanceof RegExp) {
return path;
}
if (path instanceof Array) {
path = `(${path.join("|")})`;
}
path = path
.replace(/\/\(/g, "(?:/")
.replace(
/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g,
(_, slash, format, key, capture, optional) => {
if (slash == null) {
slash = "";
}
if (format == null) {
format = "";
}
keys.push({ name: key, optional: !!optional });
let str = optional ? "" : slash;
str += "(?:";
if (optional) {
str += slash;
}
str += format;
str += capture || (format ? "([^/.]+?)" : "([^/]+?)");
str += ")";
if (optional) {
str += optional;
}
return str;
},
)
.replace(/([\/.])/g, "\\$1")
.replace(/\*/g, "(.*)");
return new RegExp(`^${path}$`);
};
var onpopstate = function (event) {
if (!event.state || Context.isInitialPopState(event.state)) {
return;
}
if (Context.isSameSession(event.state)) {
page.replace(event.state.path, event.state);
} else {
location.reload();
}
};
var onclick = function (event) {
try {
if (
event.which !== 1 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.defaultPrevented
) {
return;
}
} catch (error) {
return;
}
let link = $.eventTarget(event);
while (link && !(link.tagName === "A" || link.tagName === "a")) {
link = link.parentNode;
}
if (!link) return;
// If the `<a>` is in an SVG, its attributes are `SVGAnimatedString`s
// instead of strings
let href = link.href instanceof SVGAnimatedString
? new URL(link.href.baseVal, location.href).href
: link.href;
let target = link.target instanceof SVGAnimatedString
? link.target.baseVal
: link.target;
if (!target && isSameOrigin(href)) {
event.preventDefault();
let parsedHref = new URL(href);
let path = parsedHref.pathname + parsedHref.search + parsedHref.hash;
path = path.replace(/^\/\/+/, "/"); // IE11 bug
page.show(path);
}
};
var isSameOrigin = (url) =>
url.startsWith(`${location.protocol}//${location.hostname}`);
var updateCanonicalLink = function () {
if (!this.canonicalLink) {
this.canonicalLink = document.head.querySelector('link[rel="canonical"]');
}
return this.canonicalLink.setAttribute(
"href",
`https://${location.host}${location.pathname}`,
);
};
const trackers = [];
page.track = function (fn) {
trackers.push(fn);
};
var track = function () {
if (app.config.env !== "production") {
return;
}
if (navigator.doNotTrack === "1") {
return;
}
if (navigator.globalPrivacyControl) {
return;
}
const consentGiven = Cookies.get("analyticsConsent");
const consentAsked = Cookies.get("analyticsConsentAsked");
if (consentGiven === "1") {
for (var tracker of trackers) {
tracker.call();
}
} else if (consentGiven === undefined && consentAsked === undefined) {
// Only ask for consent once per browser session
Cookies.set("analyticsConsentAsked", "1");
new app.views.Notif("AnalyticsConsent", { autoHide: null });
}
};
this.resetAnalytics = function () {
for (var cookie of document.cookie.split(/;\s?/)) {
var name = cookie.split("=")[0];
if (name[0] === "_" && name[1] !== "_") {
Cookies.expire(name);
}
}
};