pdf-tei-editor / app /src /modules /browser-utils.js
cmboulanger's picture
disaster-recovery deploy of pdf-tei-editor
6a49f21 verified
Raw
History Blame Contribute Delete
9.61 kB
/**
* "Mini-jquery" - monkey-patches the element identified by the selector with some convenience methods.
* @param {string} selector
* @returns {Element}
*/
export function $(selector) {
const node = document.querySelector(selector)
if (!node) {
throw new Error(`Selector "${selector} does not find any element"`)
}
return node
}
/**
* Given a selector, return all matching DOM nodes in an array
* @param {string} selector The DOM selector
* @returns {Array}
*/
export function $$(selector) {
return Array.from(document.querySelectorAll(selector))
}
export class CookieStorage {
/**
* Constructor for CookieStorage.
* @param {object} [config={}] - Configuration for cookies.
* @param {string} [config.path='/'] - The default path for cookies.
* @param {boolean} [config.secure=true] - Whether cookies should be secure.
* @param {string} [config.sameSite='Strict'] - The SameSite attribute (e.g., 'Strict', 'Lax', or 'None').
* @param {number} [config.maxAge=604800] - The default max-age for cookies (in seconds, default is 7 days).
*/
constructor(config = {}) {
this.config = {
path: config.path || '/',
secure: config.secure !== undefined ? config.secure : true,
sameSite: config.sameSite || 'Strict',
maxAge: config.maxAge !== undefined ? config.maxAge : 7 * 24 * 60 * 60 // Default: 7 days
};
}
/**
* Retrieves a value from cookies by key. If it can be converted from a
* JSON string, return the converted value.
* @param {string} key - The key of the cookie to retrieve.
* @returns {string|Object|null} The cookie value, or null if not found.
*/
get(key) {
const cookie = document.cookie
.split('; ')
.find(row => row.startsWith(`${key}=`));
let value = cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
if (value && typeof value === "string") {
try {
value = JSON.parse(value);
} catch (e) {
// just keep the old value
}
}
return value;
}
/**
* Returns true if the key exists in the cookies or false if not.
* @param {string} key The key of the hash parameter to retrieve.
* @returns {boolean}
*/
has(key) {
return this.get(key) !== null;
}
/**
* Sets a cookie with a specified key and value. If it is not a string,
* JSON-encode it before storing it.
* @param {string} key - The key of the cookie.
* @param {string|Object} value - The value of the cookie.
* @param {object} [options={}] - Optional overrides for the configuration.
*/
set(key, value, options = {}) {
if (typeof value != "string") {
value = JSON.stringify(value);
}
const { path, secure, sameSite, maxAge } = { ...this.config, ...options };
let cookie = `${key}=${encodeURIPart(value)}; path=${path}; samesite=${sameSite}`;
if (secure) cookie += '; secure';
if (maxAge !== undefined) cookie += `; max-age=${maxAge}`;
document.cookie = cookie;
}
/**
* Removes a cookie by key.
* @param {string} key - The key of the cookie to remove.
* @param {object} [options={}] - Optional overrides for the configuration.
*/
remove(key, options = {}) {
const { path, sameSite } = { ...this.config, ...options };
const cookie = `${key}=; path=${path}; samesite=${sameSite}; max-age=0`;
document.cookie = cookie;
}
}
export class UrlHash {
/**
* Sets or updates a hash parameter in the URL without reloading the page and ensures browser history is updated.
* @param {string|Object} keyOrObj - The key of the hash parameter to set, or an object with several key-value pairs
* @param {string?} value - The value of the hash parameter to set. Not used if first parameter is an object
* @param {boolean} [dispatchEvent=true] If a 'hashchange' event should be dispatched. Defaults to true
*/
static set(keyOrObj, value, dispatchEvent = true) {
const hash = new URLSearchParams(window.location.hash.slice(1));
if (typeof keyOrObj === "string" && typeof value === "string") {
hash.set(keyOrObj, value)
} else if (keyOrObj && typeof keyOrObj === "object") {
Object.entries(keyOrObj).forEach(([key, value]) => hash.set(key, value))
} else {
throw new TypeError(`Invalid parameters: ${keyOrObj}, ${value}`)
}
// Use history.replaceState to update the current history entry
history.replaceState(null, '', '#' + hash.toString());
if (dispatchEvent) {
window.dispatchEvent(new HashChangeEvent('hashchange'));
}
}
/**
* Retrieves the value of a hash parameter from the URL.
* @param {string} key - The key of the hash parameter to retrieve.
* @returns {string|null} The value of the hash parameter, or null if not found.
*/
static get(key) {
const hash = new URLSearchParams(window.location.hash.slice(1));
return hash.get(key);
}
/**
* Returns true if the key exists in the URL hash or false if not
* @param {string} key The key of the hash parameter to retrieve.
* @returns {boolean}
*/
static has(key) {
return UrlHash.get(key) !== null;
}
/**
* Removes a hash parameter from the URL without reloading the page.
* @param {string} key - The key of the hash parameter to remove.
* @param {boolean} [dispatchEvent=true] If a 'hashchange' event should be dispatched. Defaults to true
*/
static remove(key, dispatchEvent = true) {
if (!UrlHash.has(key)) return; // Do nothing if the key does not exist
const hash = new URLSearchParams(window.location.hash.slice(1));
hash.delete(key); // Remove the specified key
const updatedHash = hash.toString();
// Use history.replaceState to update the current history entry
history.replaceState(null, '', '#' + updatedHash);
if (dispatchEvent) {
window.dispatchEvent(new HashChangeEvent('hashchange'));
}
}
}
/**
* Selects the option in the selectbox of which the value property matches the given value
* @param {HTMLSelectElement} selectbox The selectbox
* @param {string} value The value to select
*/
export function selectByValue(selectbox, value) {
const index = Array.from(selectbox.options).findIndex(o => o.value === value)
if (index == -1) {
throw new Error(`No matching option with value '${value}' in selectbox with name '${selectbox.name}'`)
}
selectbox.selectedIndex = index;
}
/**
* Selects the option in the selectbox of which the given data property matches the given value
* @param {HTMLSelectElement} selectbox The selectbox
* @param {string} key The key of the dataset property
* @param {string} value The value to select
*/
export function selectByData(selectbox, key, value) {
const index = Array.from(selectbox.options).findIndex(o => o.dataset[key] === value)
if (index == -1) {
throw new Error(`No matching option with dataset.${key} value '${value}' in selectbox with name '${selectbox.name}'`)
}
selectbox.selectedIndex = index;
}
/**
* Returns the first descendant having that name. Throws an error if none can be found unless you pass noError = true
* @param {Element} node The ancestor node
* @param {string} name The name to look for
* @param {Boolean} noError If true, return null instead of throwing an error if no ancestor with that name exists
* @returns {Element|null}
*/
export function getDescendantByName(node, name, noError) {
const descendant = node.querySelector(`[name="${name}]`)
if (!descendant) {
if (noError) return null
throw new Error(`No descendant with name "${name} exists`)
}
return descendant
}
/**
* Escapes the given text to valid html
* @param {string} text
* @returns {string} The escaped text
*/
export function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Function to serialize the XML DOM back to a string (for demonstration)
export function serializeXmlToString(xmlDoc) {
// Check if xmlDoc is actually a document node before trying to serialize
if (!xmlDoc || typeof xmlDoc.serializeToString !== 'function') {
console.error("Invalid document object passed to serializeXmlToString");
// Attempt to return a string representation if possible, or indicate error
try {
if (xmlDoc.documentElement) {
return new XMLSerializer().serializeToString(xmlDoc.documentElement);
}
} catch (e) {
console.error("Could not serialize even documentElement:", e);
}
return "[Invalid XML Document]";
}
const serializer = new XMLSerializer();
return serializer.serializeToString(xmlDoc);
}
/**
* Tests if an XPath is valid
* @param {string} xpathExpression
* @param {Document} xmlDom The DOM document to test the expression on
* @param {XPathNSResolver|null} namespaceResolver
* @returns {Boolean}
*/
export function isValidXPath(xpathExpression, xmlDom, namespaceResolver = null) {
try {
// Check if the XML DOM is valid
if (!xmlDom || typeof xmlDom !== 'object' || !xmlDom.evaluate) {
console.error("Invalid XML DOM provided.");
return false;
}
// Try to evaluate the XPath expression
xmlDom.evaluate(
xpathExpression,
xmlDom,
namespaceResolver,
XPathResult.ANY_TYPE, // resultType - ANY_TYPE is generally fine for validation
null // result - reuse existing result, optional
);
return true; // If no error thrown, the XPath is valid
} catch (error) {
// An error indicates an invalid XPath expression
console.error("Invalid XPath:", String(error)); // Optionally log the error
return false;
}
}