Boards use /b/<uid> with no invite password; restore renamable columns, recordings, and seeded locale notes, with first column locked.
97 lines
2.8 KiB
JavaScript
97 lines
2.8 KiB
JavaScript
/** Shared helpers for Stork v1 + v2. */
|
|
(function (global) {
|
|
function formatError(detail) {
|
|
if (detail == null || detail === "") return "Something went wrong";
|
|
if (typeof detail === "string") {
|
|
try {
|
|
const parsed = JSON.parse(detail);
|
|
return formatError(parsed);
|
|
} catch {
|
|
return detail;
|
|
}
|
|
}
|
|
if (Array.isArray(detail)) {
|
|
return detail
|
|
.map((item) => {
|
|
if (!item || typeof item !== "object") return String(item);
|
|
const loc = Array.isArray(item.loc) ? item.loc.filter((x) => x !== "body").join(".") : "";
|
|
const msg = item.msg || "Invalid value";
|
|
if (loc === "spelling" || (item.loc && item.loc.includes("spelling"))) {
|
|
if (String(msg).toLowerCase().includes("at least 1")) return "Enter a name spelling";
|
|
return `Spelling: ${msg}`;
|
|
}
|
|
return loc ? `${loc}: ${msg}` : msg;
|
|
})
|
|
.join(". ");
|
|
}
|
|
if (typeof detail === "object" && detail.msg) return String(detail.msg);
|
|
return String(detail);
|
|
}
|
|
|
|
function showError(el, detail) {
|
|
if (!el) return;
|
|
el.textContent = formatError(detail);
|
|
el.classList.remove("hidden");
|
|
}
|
|
|
|
function clearError(el) {
|
|
if (!el) return;
|
|
el.textContent = "";
|
|
el.classList.add("hidden");
|
|
}
|
|
|
|
const THEME_KEY = "stork_theme";
|
|
|
|
function systemTheme() {
|
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
}
|
|
|
|
function currentTheme() {
|
|
return localStorage.getItem(THEME_KEY) || "system";
|
|
}
|
|
|
|
function resolvedTheme() {
|
|
const t = currentTheme();
|
|
return t === "system" ? systemTheme() : t;
|
|
}
|
|
|
|
function applyTheme() {
|
|
const resolved = resolvedTheme();
|
|
document.documentElement.setAttribute("data-theme", resolved);
|
|
document.documentElement.style.colorScheme = resolved;
|
|
document.querySelectorAll("[data-theme-toggle]").forEach((btn) => {
|
|
const mode = btn.getAttribute("data-theme-toggle");
|
|
const active = currentTheme() === mode;
|
|
btn.setAttribute("aria-pressed", active ? "true" : "false");
|
|
btn.classList.toggle("active", active);
|
|
});
|
|
}
|
|
|
|
function setTheme(mode) {
|
|
if (mode === "system") localStorage.removeItem(THEME_KEY);
|
|
else localStorage.setItem(THEME_KEY, mode);
|
|
applyTheme();
|
|
}
|
|
|
|
function initThemeControls() {
|
|
applyTheme();
|
|
document.querySelectorAll("[data-theme-toggle]").forEach((btn) => {
|
|
btn.addEventListener("click", () => setTheme(btn.getAttribute("data-theme-toggle")));
|
|
});
|
|
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
|
if (currentTheme() === "system") applyTheme();
|
|
});
|
|
}
|
|
|
|
global.StorkUI = {
|
|
formatError,
|
|
showError,
|
|
clearError,
|
|
applyTheme,
|
|
setTheme,
|
|
initThemeControls,
|
|
currentTheme,
|
|
resolvedTheme,
|
|
};
|
|
})(window);
|