feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
134
modules/system/assets/js/snowboard/utilities/Cookie.js
Normal file
134
modules/system/assets/js/snowboard/utilities/Cookie.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import BaseCookie from 'js-cookie';
|
||||
import Singleton from '../abstracts/Singleton';
|
||||
|
||||
/**
|
||||
* Cookie utility.
|
||||
*
|
||||
* This utility is a thin wrapper around the "js-cookie" library.
|
||||
*
|
||||
* @see https://github.com/js-cookie/js-cookie
|
||||
* @copyright 2021 Winter.
|
||||
* @author Ben Thomson <git@alfreido.com>
|
||||
*/
|
||||
export default class Cookie extends Singleton {
|
||||
construct() {
|
||||
this.defaults = {
|
||||
expires: null,
|
||||
path: '/',
|
||||
domain: null,
|
||||
secure: false,
|
||||
sameSite: 'Lax',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default cookie parameters for all subsequent "set" and "remove" calls.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
setDefaults(options) {
|
||||
if (typeof options !== 'object') {
|
||||
throw new Error('Cookie defaults must be provided as an object');
|
||||
}
|
||||
|
||||
Object.entries(options).forEach((entry) => {
|
||||
const [key, value] = entry;
|
||||
|
||||
if (this.defaults[key] !== undefined) {
|
||||
this.defaults[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current default cookie parameters.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
getDefaults() {
|
||||
const defaults = {};
|
||||
|
||||
Object.entries(this.defaults).forEach((entry) => {
|
||||
const [key, value] = entry;
|
||||
|
||||
if (this.defaults[key] !== null) {
|
||||
defaults[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cookie by name.
|
||||
*
|
||||
* If `name` is undefined, returns all cookies as an Object.
|
||||
*
|
||||
* @param {String} name
|
||||
* @returns {Object|String}
|
||||
*/
|
||||
get(name) {
|
||||
if (name === undefined) {
|
||||
const cookies = BaseCookie.get();
|
||||
|
||||
Object.entries(cookies).forEach((entry) => {
|
||||
const [cookieName, cookieValue] = entry;
|
||||
|
||||
this.snowboard.globalEvent('cookie.get', cookieName, cookieValue, (newValue) => {
|
||||
cookies[cookieName] = newValue;
|
||||
});
|
||||
});
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
let value = BaseCookie.get(name);
|
||||
|
||||
// Allow plugins to override the gotten value
|
||||
this.snowboard.globalEvent('cookie.get', name, value, (newValue) => {
|
||||
value = newValue;
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie by name.
|
||||
*
|
||||
* You can specify additional cookie parameters through the "options" parameter.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String} value
|
||||
* @param {Object} options
|
||||
* @returns {String}
|
||||
*/
|
||||
set(name, value, options) {
|
||||
let saveValue = value;
|
||||
|
||||
// Allow plugins to override the value to save
|
||||
this.snowboard.globalEvent('cookie.set', name, value, (newValue) => {
|
||||
saveValue = newValue;
|
||||
});
|
||||
|
||||
return BaseCookie.set(name, saveValue, {
|
||||
...this.getDefaults(),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a cookie by name.
|
||||
*
|
||||
* You can specify the additional cookie parameters via the "options" parameter.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
* @returns {void}
|
||||
*/
|
||||
remove(name, options) {
|
||||
BaseCookie.remove(name, {
|
||||
...this.getDefaults(),
|
||||
...options,
|
||||
});
|
||||
}
|
||||
}
|
||||
395
modules/system/assets/js/snowboard/utilities/JsonParser.js
Normal file
395
modules/system/assets/js/snowboard/utilities/JsonParser.js
Normal file
@@ -0,0 +1,395 @@
|
||||
import Singleton from '../abstracts/Singleton';
|
||||
|
||||
/**
|
||||
* JSON Parser utility.
|
||||
*
|
||||
* This utility parses JSON-like data that does not strictly meet the JSON specifications in order to simplify development.
|
||||
* It is a safe replacement for JSON.parse(JSON.stringify(eval("({" + value + "})"))) that does not require the use of eval()
|
||||
*
|
||||
* @author Ayumi Hamasaki
|
||||
* @author Ben Thomson <git@alfreido.com>
|
||||
* @see https://github.com/octobercms/october/pull/4527
|
||||
*/
|
||||
export default class JsonParser extends Singleton {
|
||||
construct() {
|
||||
// Add to global function for backwards compatibility
|
||||
window.wnJSON = (json) => this.parse(json);
|
||||
window.ocJSON = window.wnJSON;
|
||||
}
|
||||
|
||||
parse(str) {
|
||||
const jsonString = this.parseString(str);
|
||||
return JSON.parse(jsonString);
|
||||
}
|
||||
|
||||
parseString(value) {
|
||||
let str = value.trim();
|
||||
|
||||
if (!str.length) {
|
||||
throw new Error('Broken JSON object.');
|
||||
}
|
||||
|
||||
let result = '';
|
||||
let type = null;
|
||||
let key = null;
|
||||
let body = '';
|
||||
|
||||
/*
|
||||
* the mistake ','
|
||||
*/
|
||||
while (str && str[0] === ',') {
|
||||
str = str.substr(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* string
|
||||
*/
|
||||
if (str[0] === '"' || str[0] === '\'') {
|
||||
if (str[str.length - 1] !== str[0]) {
|
||||
throw new Error('Invalid string JSON object.');
|
||||
}
|
||||
|
||||
body = '"';
|
||||
for (let i = 1; i < str.length; i += 1) {
|
||||
if (str[i] === '\\') {
|
||||
if (str[i + 1] === '\'') {
|
||||
body += str[i + 1];
|
||||
} else {
|
||||
body += str[i];
|
||||
body += str[i + 1];
|
||||
}
|
||||
i += 1;
|
||||
} else if (str[i] === str[0]) {
|
||||
body += '"';
|
||||
return body;
|
||||
} else if (str[i] === '"') {
|
||||
body += '\\"';
|
||||
} else {
|
||||
body += str[i];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Invalid string JSON object.');
|
||||
}
|
||||
|
||||
/*
|
||||
* boolean
|
||||
*/
|
||||
if (str === 'true' || str === 'false') {
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* null
|
||||
*/
|
||||
if (str === 'null') {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
/*
|
||||
* number
|
||||
*/
|
||||
const num = Number(str);
|
||||
if (!Number.isNaN(num)) {
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* object
|
||||
*/
|
||||
if (str[0] === '{') {
|
||||
type = 'needKey';
|
||||
key = null;
|
||||
result = '{';
|
||||
|
||||
for (let i = 1; i < str.length; i += 1) {
|
||||
if (this.isBlankChar(str[i])) {
|
||||
/* eslint-disable-next-line */
|
||||
continue;
|
||||
}
|
||||
if (type === 'needKey' && (str[i] === '"' || str[i] === '\'')) {
|
||||
key = this.parseKey(str, i + 1, str[i]);
|
||||
result += `"${key}"`;
|
||||
i += key.length;
|
||||
i += 1;
|
||||
type = 'afterKey';
|
||||
} else if (type === 'needKey' && this.canBeKeyHead(str[i])) {
|
||||
key = this.parseKey(str, i);
|
||||
result += '"';
|
||||
result += key;
|
||||
result += '"';
|
||||
i += key.length - 1;
|
||||
type = 'afterKey';
|
||||
} else if (type === 'afterKey' && str[i] === ':') {
|
||||
result += ':';
|
||||
type = ':';
|
||||
} else if (type === ':') {
|
||||
body = this.getBody(str, i);
|
||||
|
||||
i = i + body.originLength - 1;
|
||||
result += this.parseString(body.body);
|
||||
|
||||
type = 'afterBody';
|
||||
} else if (type === 'afterBody' || type === 'needKey') {
|
||||
let last = i;
|
||||
while (str[last] === ',' || this.isBlankChar(str[last])) {
|
||||
last += 1;
|
||||
}
|
||||
if (str[last] === '}' && last === str.length - 1) {
|
||||
while (result[result.length - 1] === ',') {
|
||||
result = result.substr(0, result.length - 1);
|
||||
}
|
||||
result += '}';
|
||||
return result;
|
||||
}
|
||||
if (last !== i && result !== '{') {
|
||||
result += ',';
|
||||
type = 'needKey';
|
||||
i = last - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON object near ${result}`);
|
||||
}
|
||||
|
||||
/*
|
||||
* array
|
||||
*/
|
||||
if (str[0] === '[') {
|
||||
result = '[';
|
||||
type = 'needBody';
|
||||
for (let i = 1; i < str.length; i += 1) {
|
||||
if (str[i] === ' ' || str[i] === '\n' || str[i] === '\t') {
|
||||
/* eslint-disable-next-line */
|
||||
continue;
|
||||
} else if (type === 'needBody') {
|
||||
if (str[i] === ',') {
|
||||
result += 'null,';
|
||||
/* eslint-disable-next-line */
|
||||
continue;
|
||||
}
|
||||
if (str[i] === ']' && i === str.length - 1) {
|
||||
if (result[result.length - 1] === ',') {
|
||||
result = result.substr(0, result.length - 1);
|
||||
}
|
||||
result += ']';
|
||||
return result;
|
||||
}
|
||||
|
||||
body = this.getBody(str, i);
|
||||
|
||||
i = i + body.originLength - 1;
|
||||
result += this.parseString(body.body);
|
||||
|
||||
type = 'afterBody';
|
||||
} else if (type === 'afterBody') {
|
||||
if (str[i] === ',') {
|
||||
result += ',';
|
||||
type = 'needBody';
|
||||
|
||||
// deal with mistake ","
|
||||
while (str[i + 1] === ',' || this.isBlankChar(str[i + 1])) {
|
||||
if (str[i + 1] === ',') {
|
||||
result += 'null,';
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
} else if (str[i] === ']' && i === str.length - 1) {
|
||||
result += ']';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON array near ${result}`);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
getBody(str, pos) {
|
||||
let body = '';
|
||||
|
||||
// parse string body
|
||||
if (str[pos] === '"' || str[pos] === '\'') {
|
||||
body = str[pos];
|
||||
|
||||
for (let i = pos + 1; i < str.length; i += 1) {
|
||||
if (str[i] === '\\') {
|
||||
body += str[i];
|
||||
if (i + 1 < str.length) {
|
||||
body += str[i + 1];
|
||||
}
|
||||
i += 1;
|
||||
} else if (str[i] === str[pos]) {
|
||||
body += str[pos];
|
||||
return {
|
||||
originLength: body.length,
|
||||
body,
|
||||
};
|
||||
} else {
|
||||
body += str[i];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON string body near ${body}`);
|
||||
}
|
||||
|
||||
// parse true / false
|
||||
if (str[pos] === 't') {
|
||||
if (str.indexOf('true', pos) === pos) {
|
||||
return {
|
||||
originLength: 'true'.length,
|
||||
body: 'true',
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON boolean body near ${str.substr(0, pos + 10)}`);
|
||||
}
|
||||
if (str[pos] === 'f') {
|
||||
if (str.indexOf('f', pos) === pos) {
|
||||
return {
|
||||
originLength: 'false'.length,
|
||||
body: 'false',
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON boolean body near ${str.substr(0, pos + 10)}`);
|
||||
}
|
||||
|
||||
// parse null
|
||||
if (str[pos] === 'n') {
|
||||
if (str.indexOf('null', pos) === pos) {
|
||||
return {
|
||||
originLength: 'null'.length,
|
||||
body: 'null',
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON boolean body near ${str.substr(0, pos + 10)}`);
|
||||
}
|
||||
|
||||
// parse number
|
||||
if (str[pos] === '-' || str[pos] === '+' || str[pos] === '.' || (str[pos] >= '0' && str[pos] <= '9')) {
|
||||
body = '';
|
||||
|
||||
for (let i = pos; i < str.length; i += 1) {
|
||||
if (str[i] === '-' || str[i] === '+' || str[i] === '.' || (str[i] >= '0' && str[i] <= '9')) {
|
||||
body += str[i];
|
||||
} else {
|
||||
return {
|
||||
originLength: body.length,
|
||||
body,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON number body near ${body}`);
|
||||
}
|
||||
|
||||
// parse object
|
||||
if (str[pos] === '{' || str[pos] === '[') {
|
||||
const stack = [
|
||||
str[pos],
|
||||
];
|
||||
body = str[pos];
|
||||
|
||||
for (let i = pos + 1; i < str.length; i += 1) {
|
||||
body += str[i];
|
||||
if (str[i] === '\\') {
|
||||
if (i + 1 < str.length) {
|
||||
body += str[i + 1];
|
||||
}
|
||||
i += 1;
|
||||
} else if (str[i] === '"') {
|
||||
if (stack[stack.length - 1] === '"') {
|
||||
stack.pop();
|
||||
} else if (stack[stack.length - 1] !== '\'') {
|
||||
stack.push(str[i]);
|
||||
}
|
||||
} else if (str[i] === '\'') {
|
||||
if (stack[stack.length - 1] === '\'') {
|
||||
stack.pop();
|
||||
} else if (stack[stack.length - 1] !== '"') {
|
||||
stack.push(str[i]);
|
||||
}
|
||||
} else if (stack[stack.length - 1] !== '"' && stack[stack.length - 1] !== '\'') {
|
||||
if (str[i] === '{') {
|
||||
stack.push('{');
|
||||
} else if (str[i] === '}') {
|
||||
if (stack[stack.length - 1] === '{') {
|
||||
stack.pop();
|
||||
} else {
|
||||
throw new Error(`Broken JSON ${(str[pos] === '{' ? 'object' : 'array')} body near ${body}`);
|
||||
}
|
||||
} else if (str[i] === '[') {
|
||||
stack.push('[');
|
||||
} else if (str[i] === ']') {
|
||||
if (stack[stack.length - 1] === '[') {
|
||||
stack.pop();
|
||||
} else {
|
||||
throw new Error(`Broken JSON ${(str[pos] === '{' ? 'object' : 'array')} body near ${body}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stack.length) {
|
||||
return {
|
||||
originLength: i - pos,
|
||||
body,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON ${(str[pos] === '{' ? 'object' : 'array')} body near ${body}`);
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON body near ${str.substr((pos - 5 >= 0) ? pos - 5 : 0, 50)}`);
|
||||
}
|
||||
|
||||
parseKey(str, pos, quote) {
|
||||
let key = '';
|
||||
|
||||
for (let i = pos; i < str.length; i += 1) {
|
||||
if (quote && quote === str[i]) {
|
||||
return key;
|
||||
}
|
||||
if (!quote && (str[i] === ' ' || str[i] === ':')) {
|
||||
return key;
|
||||
}
|
||||
|
||||
key += str[i];
|
||||
|
||||
if (str[i] === '\\' && i + 1 < str.length) {
|
||||
key += str[i + 1];
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Broken JSON syntax near ${key}`);
|
||||
}
|
||||
|
||||
canBeKeyHead(ch) {
|
||||
if (ch[0] === '\\') {
|
||||
return false;
|
||||
}
|
||||
if ((ch[0] >= 'a' && ch[0] <= 'z') || (ch[0] >= 'A' && ch[0] <= 'Z') || ch[0] === '_') {
|
||||
return true;
|
||||
}
|
||||
if (ch[0] >= '0' && ch[0] <= '9') {
|
||||
return true;
|
||||
}
|
||||
if (ch[0] === '$') {
|
||||
return true;
|
||||
}
|
||||
if (ch.charCodeAt(0) > 255) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
isBlankChar(ch) {
|
||||
return ch === ' ' || ch === '\n' || ch === '\t';
|
||||
}
|
||||
}
|
||||
64
modules/system/assets/js/snowboard/utilities/Sanitizer.js
Normal file
64
modules/system/assets/js/snowboard/utilities/Sanitizer.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import Singleton from '../abstracts/Singleton';
|
||||
|
||||
/**
|
||||
* Sanitizer utility.
|
||||
*
|
||||
* Client-side HTML sanitizer designed mostly to prevent self-XSS attacks.
|
||||
* The sanitizer utility will strip all attributes that start with `on` (usually JS event handlers as attributes, i.e. `onload` or `onerror`) or contain the `javascript:` pseudo protocol in their values.
|
||||
*
|
||||
* @author Ben Thomson <git@alfreido.com>
|
||||
*/
|
||||
export default class Sanitizer extends Singleton {
|
||||
construct() {
|
||||
// Add to global function for backwards compatibility
|
||||
window.wnSanitize = (html) => this.sanitize(html);
|
||||
window.ocSanitize = window.wnSanitize;
|
||||
}
|
||||
|
||||
sanitize(html, bodyOnly) {
|
||||
const parser = new DOMParser();
|
||||
const dom = parser.parseFromString(html, 'text/html');
|
||||
const returnBodyOnly = (bodyOnly !== undefined && typeof bodyOnly === 'boolean')
|
||||
? bodyOnly
|
||||
: true;
|
||||
|
||||
this.sanitizeNode(dom.getRootNode());
|
||||
|
||||
return (returnBodyOnly) ? dom.body.innerHTML : dom.innerHTML;
|
||||
}
|
||||
|
||||
sanitizeNode(node) {
|
||||
if (node.tagName === 'SCRIPT') {
|
||||
node.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
this.trimAttributes(node);
|
||||
|
||||
const children = Array.from(node.children);
|
||||
|
||||
children.forEach((child) => {
|
||||
this.sanitizeNode(child);
|
||||
});
|
||||
}
|
||||
|
||||
trimAttributes(node) {
|
||||
if (!node.attributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < node.attributes.length; i += 1) {
|
||||
const attrName = node.attributes.item(i).name;
|
||||
const attrValue = node.attributes.item(i).value;
|
||||
|
||||
/*
|
||||
* remove attributes where the names start with "on" (for example: onload, onerror...)
|
||||
* remove attributes where the value starts with the "javascript:" pseudo protocol (for example href="javascript:alert(1)")
|
||||
*/
|
||||
/* eslint-disable-next-line */
|
||||
if (attrName.indexOf('on') === 0 || attrValue.indexOf('javascript:') === 0) {
|
||||
node.removeAttribute(attrName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
165
modules/system/assets/js/snowboard/utilities/Url.js
Normal file
165
modules/system/assets/js/snowboard/utilities/Url.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import Singleton from '../abstracts/Singleton';
|
||||
|
||||
/**
|
||||
* URL utility.
|
||||
*
|
||||
* This utility provides URL functions.
|
||||
*
|
||||
* @copyright 2022 Winter.
|
||||
* @author Ben Thomson <git@alfreido.com>
|
||||
*/
|
||||
export default class Url extends Singleton {
|
||||
construct() {
|
||||
this.foundBaseUrl = null;
|
||||
this.foundAssetUrl = null;
|
||||
this.baseUrl();
|
||||
this.assetUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL based on a relative path.
|
||||
*
|
||||
* If an absolute URL is provided, it will be returned unchanged.
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
to(url) {
|
||||
const urlRegex = /^(?:[^:]+:\/\/)[-a-z0-9@:%._+~#=]{1,256}\b([-a-z0-9()@:%_+.~#?&//=]*)/i;
|
||||
|
||||
if (url.match(urlRegex)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const theUrl = url.replace(/^\/+/, '');
|
||||
|
||||
return `${this.baseUrl()}${theUrl}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an Asset URL based on a relative path.
|
||||
*
|
||||
* If an absolute URL is provided, it will be returned unchanged.
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
asset(url) {
|
||||
const urlRegex = /^(?:[^:]+:\/\/)[-a-z0-9@:%._+~#=]{1,256}\b([-a-z0-9()@:%_+.~#?&//=]*)/i;
|
||||
|
||||
if (url.match(urlRegex)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const theUrl = url.replace(/^\/+/, '');
|
||||
|
||||
return `${this.assetUrl()}${theUrl}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the base URL of this install.
|
||||
*
|
||||
* This determines the base URL from three sources, in order:
|
||||
* - If Snowboard is loaded via the `{% snowboard %}` tag, it will retrieve the base URL that
|
||||
* is automatically included there.
|
||||
* - If a `<base>` tag is available, it will use the URL specified in the base tag.
|
||||
* - Finally, it will take a guess from the current location. This will likely not work for sites
|
||||
* that reside in subdirectories.
|
||||
*
|
||||
* The base URL will always contain a trailing backslash.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
baseUrl() {
|
||||
if (this.foundBaseUrl !== null) {
|
||||
return this.foundBaseUrl;
|
||||
}
|
||||
|
||||
if (document.querySelector('script[data-module="snowboard-base"]') !== null) {
|
||||
this.foundBaseUrl = this.validateBaseUrl(document.querySelector('script[data-module="snowboard-base"]').dataset.baseUrl);
|
||||
return this.foundBaseUrl;
|
||||
}
|
||||
|
||||
if (document.querySelector('base') !== null) {
|
||||
this.foundBaseUrl = this.validateBaseUrl(document.querySelector('base').getAttribute('href'));
|
||||
return this.foundBaseUrl;
|
||||
}
|
||||
|
||||
const urlParts = [
|
||||
window.location.protocol,
|
||||
'//',
|
||||
window.location.host,
|
||||
'/',
|
||||
];
|
||||
this.foundBaseUrl = urlParts.join('');
|
||||
|
||||
return this.foundBaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the asset URL of this install.
|
||||
*
|
||||
* This determines the base URL from three sources, in order:
|
||||
* - If Snowboard is loaded via the `{% snowboard %}` tag, it will retrieve the asset URL that
|
||||
* is automatically included there.
|
||||
* - If a `<link rel="asset_url" href="https://example.com">` tag is available, it will use the URL specified in the link tag.
|
||||
* - Finally, it will take a guess from the current location. This will likely not work for sites
|
||||
* that reside in subdirectories.
|
||||
*
|
||||
* The asset URL will always contain a trailing backslash.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
assetUrl() {
|
||||
if (this.foundAssetUrl !== null) {
|
||||
return this.foundAssetUrl;
|
||||
}
|
||||
|
||||
if (document.querySelector('script[data-module="snowboard-base"]') !== null) {
|
||||
this.foundAssetUrl = this.validateBaseUrl(document.querySelector('script[data-module="snowboard-base"]').dataset.assetUrl);
|
||||
return this.foundAssetUrl;
|
||||
}
|
||||
|
||||
if (document.querySelector('link[rel="asset_url"]') !== null) {
|
||||
this.foundAssetUrl = this.validateBaseUrl(document.querySelector('link[rel="asset_url"]').getAttribute('href'));
|
||||
return this.foundAssetUrl;
|
||||
}
|
||||
|
||||
const urlParts = [
|
||||
window.location.protocol,
|
||||
'//',
|
||||
window.location.host,
|
||||
'/',
|
||||
];
|
||||
this.foundAssetUrl = urlParts.join('');
|
||||
|
||||
return this.foundAssetUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the base URL, ensuring it is a HTTP/HTTPs URL.
|
||||
*
|
||||
* If the Snowboard script or <base> tag on the page use a different type of URL, this will fail with
|
||||
* an error.
|
||||
*
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
validateBaseUrl(url) {
|
||||
const urlRegex = /^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/i;
|
||||
const urlParts = urlRegex.exec(url);
|
||||
const protocol = urlParts[2];
|
||||
const domain = urlParts[4];
|
||||
|
||||
if (protocol && ['http', 'https'].indexOf(protocol.toLowerCase()) === -1) {
|
||||
throw new Error('Invalid base URL detected');
|
||||
}
|
||||
if (!domain) {
|
||||
throw new Error('Invalid base URL detected');
|
||||
}
|
||||
|
||||
return (url.substr(-1) === '/')
|
||||
? url
|
||||
: `${url}/`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user