61 lines
1.8 KiB
JavaScript
61 lines
1.8 KiB
JavaScript
/* Common helpers shared across pages (ES module). */
|
|
|
|
/**
|
|
* Debounce a function call.
|
|
* @param {Function} fn - function to debounce
|
|
* @param {number} delay - delay in ms
|
|
*/
|
|
export function debounce(fn, delay) {
|
|
let timer = null;
|
|
return function (...args) {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format an ISO date string to YYYY-MM-DD.
|
|
* Returns empty string for falsy input.
|
|
*/
|
|
export function formatDate(isoString) {
|
|
if (!isoString) return "";
|
|
const d = new Date(isoString);
|
|
if (isNaN(d.getTime())) return String(isoString).slice(0, 10);
|
|
const y = d.getFullYear();
|
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
const day = String(d.getDate()).padStart(2, "0");
|
|
return `${y}-${m}-${day}`;
|
|
}
|
|
|
|
/** Truncate text to maxLen chars, appending ellipsis. */
|
|
export function truncate(text, maxLen) {
|
|
if (!text) return "";
|
|
const s = String(text);
|
|
return s.length > maxLen ? s.slice(0, maxLen) + "…" : s;
|
|
}
|
|
|
|
/** Escape HTML special chars to prevent XSS when injecting via innerHTML. */
|
|
export function escapeHtml(text) {
|
|
if (text === null || text === undefined) return "";
|
|
return String(text)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
/** Fetch JSON with basic error handling. */
|
|
export async function fetchJson(url) {
|
|
const resp = await fetch(url);
|
|
if (!resp.ok) {
|
|
throw new Error(`请求失败: ${resp.status} ${resp.statusText}`);
|
|
}
|
|
return resp.json();
|
|
}
|
|
|
|
/* Delegated retry: covers .js-retry-btn injected into error hints after load. */
|
|
document.addEventListener("click", (e) => {
|
|
if (e.target.closest(".js-retry-btn")) location.reload();
|
|
});
|