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

283 lines
7.6 KiB

// TODO: This file was created by bulk-decaffeinate.
// Sanity-check the conversion and remove this comment.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
/*
* 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) {
let res;
if (path === (currentState != null ? currentState.path : undefined)) { return; }
const context = new Context(path, state);
const previousState = currentState;
currentState = context.state;
if (res = page.dispatch(context)) {
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;
var next = function() {
let fn, res;
if (fn = callbacks[i++]) { res = fn(context, next); }
return res;
};
return next();
};
page.canGoBack = () => !Context.isIntialState(currentState);
page.canGoForward = () => !Context.isLastState(currentState);
var currentPath = () => location.pathname + location.search + location.hash;
class Context {
static initClass() {
this.initialPath = currentPath();
this.sessionId = Date.now();
this.stateId = 0;
}
static isIntialState(state) {
return state.id === 0;
}
static isLastState(state) {
return state.id === (this.stateId - 1);
}
static isInitialPopState(state) {
return (state.path === this.initialPath) && (this.stateId === 1);
}
static isSameSession(state) {
return state.sessionId === this.sessionId;
}
constructor(path, state) {
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) { this.state.id = this.constructor.stateId++; }
if (this.state.sessionId == null) { this.state.sessionId = this.constructor.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
}
}
Context.initClass();
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) {
let matchData;
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];
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) {
if (path instanceof RegExp) { return path; }
if (path instanceof Array) { path = `(${path.join('|')})`; }
path = path
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, 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 = link.parentNode; }
if (link && !link.target && isSameOrigin(link.href)) {
event.preventDefault();
let path = link.pathname + link.search + link.hash;
path = path.replace(/^\/\/+/, '/'); // IE11 bug
page.show(path);
}
};
var isSameOrigin = url => url.indexOf(`${location.protocol}//${location.hostname}`) === 0;
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 Array.from(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 Array.from(document.cookie.split(/;\s?/))) {
var name = cookie.split('=')[0];
if ((name[0] === '_') && (name[1] !== '_')) {
Cookies.expire(name);
}
}
};