Files
blog-app/static/js/utils.js
2026-08-17 17:11:27 +08:00

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, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
/** 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();
});