/** * Hoover — helpers.js * * Shared utilities: text escaping, attribute escaping, DOM value helpers, * zone parsing, form utilities. */ /** * Escape text for safe HTML output. * Appends the string to a temporary div and reads innerHTML, * which safely escapes all HTML special characters. */ export function esc(s) { const d = document.createElement('div'); d.append(String(s ?? '')); return d.innerHTML; } /** * Escape a string for safe use in HTML attributes. */ export function att_esc(s) { return String(s ?? '') .replace(/&/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') .replace(//g, '>'); } /** * URL-encode a string. */ export const enc = encodeURIComponent; /** * Get the value of a DOM element by ID. */ export function $val(id) { return document.getElementById(id)?.value; } /** * Parse zone data from various API response shapes into a flat string array. */ export function parseZones(data) { let z = data?.active || data?.zones || []; if (typeof z === 'object' && !Array.isArray(z)) z = Object.values(z).map(i => i?.name || i); return Array.isArray(z) ? z : []; } /** * Trigger a browser file download from a Blob. * * @param {Blob} blob * @param {string} filename */ /** * Format bytes to human-readable string. * @param {number} bytes */ export function fmtBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return (bytes / Math.pow(k, i)).toFixed(i > 0 ? 1 : 0) + ' ' + sizes[i]; } /** * Split a comma-separated string into trimmed, non-empty values. * @param {string} [value] * @returns {string[]} */ export function csvToArr(value) { if (!value || !value.trim()) return []; return value.split(',').map(s => s.trim()).filter(Boolean); } export function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }