export function humanFileSize(size: number) { if (size < 1024) return `${size} B`; const i = Math.min(4, Math.floor(Math.log(size) / Math.log(1024))); return `${(size / 1024 ** i).toFixed(2)} ${["B", "KiB", "MiB", "GiB", "TiB"][i]}`; } // TextDecoder-based UTF-8 check that works in both Bun and the browser. export function isValidUTF8(buf: ArrayBuffer | ArrayBufferView): boolean { try { new TextDecoder("utf-8", { fatal: true }).decode(buf); return true; } catch (_e) { return false; } } export type Theme = "auto" | "light" | "dark"; // Reads the persisted theme choice from a raw Cookie request header, defaulting // to "auto" (follow the OS) when unset or unrecognized. Values are plain // (auto/light/dark), so no decoding is needed. export function getTheme(cookieHeader: string | undefined): Theme { const value = cookieHeader?.match(/(?:^|;\s*)zbin-theme=([^;]+)/)?.[1]; return value === "light" || value === "dark" ? value : "auto"; } // Inline SVG for the toggle's current state, drawn in `currentColor` so it picks // up the button's text color. Shared by the server (initial render) and // client-theme.js (on toggle): light = sun, dark = crescent, auto = a half-filled // disc suggesting "follows the system". Injected as raw markup (not escaped). export function themeIcon(theme: Theme): string { const open = '`; } if (theme === "dark") { return `${open}`; } return `${open}`; } const MODE_STORAGE_KEY = "zbin-mode"; // Enables the (HTML-disabled) client-side radio now that JS is running, applies // the saved client/server preference (defaulting to client since JS is here), // and persists changes. Shared by the index (encrypt_mode) and show (decrypt_mode) pages. export function setupModeRadios(name: string) { const radios = document.querySelectorAll( `input[name="${name}"]`, ); if (radios.length === 0) return; const saved = localStorage.getItem(MODE_STORAGE_KEY) ?? "client"; for (const radio of radios) { radio.disabled = false; radio.checked = radio.value === saved; radio.addEventListener("change", () => { if (radio.checked) localStorage.setItem(MODE_STORAGE_KEY, radio.value); }); } } export function getMode(name: string): string { const checked = document.querySelector( `input[name="${name}"]:checked`, ); return checked?.value ?? "server"; }