Files
Bjorn/web/js/core/dom.js
Fabien POLLY eb20b168a6 Add RLUtils class for managing RL/AI dashboard endpoints
- Implemented methods for fetching AI stats, training history, and recent experiences.
- Added functionality to set operation mode (MANUAL, AUTO, AI) with appropriate handling.
- Included helper methods for querying the database and sending JSON responses.
- Integrated model metadata extraction for visualization purposes.
2026-02-18 22:36:10 +01:00

98 lines
3.0 KiB
JavaScript

/**
* Safe DOM utilities — avoids innerHTML with untrusted content.
*/
import { trLoose } from './i18n.js';
/**
* Create an element with attributes and children (safe, no innerHTML).
* @param {string} tag
* @param {object} attrs - className, style, data-*, event handlers (onclick, etc.)
* @param {Array} children - strings or HTMLElements
* @returns {HTMLElement}
*/
export function el(tag, attrs = {}, children = []) {
const node = document.createElement(tag);
for (const [k, v] of Object.entries(attrs)) {
if (v == null || v === false) continue;
if (k === 'class' || k === 'className') node.className = v;
else if (k === 'style' && typeof v === 'string') node.style.cssText = v;
else if (k === 'style' && typeof v === 'object') Object.assign(node.style, v);
else if (k.startsWith('on') && typeof v === 'function') {
node.addEventListener(k.slice(2).toLowerCase(), v);
}
else node.setAttribute(k, String(v));
}
for (const child of (Array.isArray(children) ? children : [children])) {
if (child == null || child === false) continue;
if (typeof child === 'string' || typeof child === 'number') {
node.appendChild(document.createTextNode(String(child)));
} else if (child instanceof Node) {
node.appendChild(child);
}
}
return node;
}
/**
* Shorthand selectors.
*/
export const $ = (s, root = document) => root.querySelector(s);
export const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
/**
* Escape HTML entities to prevent XSS when rendering untrusted text.
* @param {string} str
* @returns {string}
*/
export function escapeHtml(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
/**
* Set text content safely (never innerHTML with untrusted data).
* @param {HTMLElement} el
* @param {string} text
*/
export function setText(el, text) {
if (el) el.textContent = text;
}
/**
* Show a toast notification.
* @param {string} message - plain text (safe)
* @param {number} duration - ms
* @param {string} type - 'info' | 'success' | 'error' | 'warning'
*/
export function toast(message, duration = 2600, type = 'info') {
const container = document.getElementById('toasts');
if (!container) return;
const t = el('div', { class: `toast toast-${type}` }, [trLoose(String(message))]);
container.appendChild(t);
setTimeout(() => {
t.style.transition = 'transform .2s ease, opacity .2s';
t.style.transform = 'translateY(10px)';
t.style.opacity = '0';
setTimeout(() => t.remove(), 220);
}, duration);
}
/**
* Empty a container safely.
* @param {HTMLElement} container
*/
export function empty(container) {
while (container.firstChild) container.removeChild(container.firstChild);
}
export function confirmT(message) {
return window.confirm(trLoose(String(message)));
}
export function promptT(message, defaultValue = '') {
return window.prompt(trLoose(String(message)), defaultValue);
}