You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devdocs/assets/javascripts/lib/page.js

339 lines
7.4 KiB

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