174 lines
5.7 KiB
JavaScript
174 lines
5.7 KiB
JavaScript
document.addEventListener("alpine:init", () => {
|
|
let idCounter = 0;
|
|
|
|
console.log("[toast] Alpine available:", typeof Alpine !== "undefined");
|
|
|
|
Alpine.store("toasts", {
|
|
toasts: [],
|
|
|
|
addToast(message, type) {
|
|
console.log("[toast] addToast called:", { message, type });
|
|
if (!type) type = "info";
|
|
const validTypes = ["success", "error", "info", "warning", "debug"];
|
|
if (!validTypes.includes(type)) type = "info";
|
|
|
|
if (this.toasts.length >= 3) {
|
|
console.log("[toast] max 3 toasts reached, removing oldest");
|
|
this.toasts.shift();
|
|
}
|
|
|
|
const id = ++idCounter;
|
|
console.log("[toast] toast added, count:", this.toasts.length);
|
|
this.toasts.push({ id, message, type, visible: true, timer: null, pausedAt: null });
|
|
|
|
if (type !== "error") {
|
|
const toast = this.toasts[this.toasts.length - 1];
|
|
const autoDismissDelay = type === "debug" ? 3000 : 5000;
|
|
toast.timer = setTimeout(() => {
|
|
console.log("[toast] auto-dismiss after " + (autoDismissDelay / 1000) + "s");
|
|
this.dismissToast(id);
|
|
}, autoDismissDelay);
|
|
}
|
|
},
|
|
|
|
dismissToast(id) {
|
|
console.log("[toast] dismissToast for id:", id);
|
|
const idx = this.toasts.findIndex((t) => t.id === id);
|
|
if (idx === -1) { console.log("[toast] toast not found"); return; }
|
|
|
|
const toast = this.toasts[idx];
|
|
if (toast.timer) clearTimeout(toast.timer);
|
|
toast.visible = false;
|
|
|
|
setTimeout(() => {
|
|
this.toasts = this.toasts.filter((t) => t.id !== id);
|
|
console.log("[toast] after dismiss, count:", this.toasts.length);
|
|
}, 300);
|
|
},
|
|
|
|
clearToastTimer(id) {
|
|
const toast = this.toasts.find((t) => t.id === id);
|
|
if (toast?.timer) {
|
|
console.log("[toast] pause timer for toast id:", id);
|
|
clearTimeout(toast.timer);
|
|
toast.timer = null;
|
|
toast.pausedAt = Date.now();
|
|
}
|
|
},
|
|
|
|
resumeToastTimer(id, duration) {
|
|
const toast = this.toasts.find((t) => t.id === id);
|
|
if (toast?.pausedAt && toast.timer === null) {
|
|
console.log("[toast] resume timer for toast id:", id);
|
|
toast.timer = setTimeout(() => {
|
|
this.dismissToast(id);
|
|
}, duration);
|
|
toast.pausedAt = null;
|
|
}
|
|
},
|
|
});
|
|
|
|
Alpine.data("toastStore", () => ({
|
|
init() {
|
|
console.log("[toast] toastStore.init running");
|
|
console.log("[toast] Alpine store toasts:", Alpine.store("toasts").toasts);
|
|
|
|
window.addEventListener("show-toast", (e) => {
|
|
console.log("[toast] show-toast event received:", e.detail);
|
|
if (Array.isArray(e.detail)) {
|
|
e.detail.forEach((msg) => {
|
|
Alpine.store("toasts").addToast(msg.message, msg.type);
|
|
});
|
|
} else {
|
|
Alpine.store("toasts").addToast(e.detail.message, e.detail.type);
|
|
}
|
|
});
|
|
|
|
try {
|
|
const script = document.getElementById("django-messages");
|
|
if (script) {
|
|
const msgs = JSON.parse(
|
|
script.textContent || script.innerText || "[]"
|
|
);
|
|
console.log("[toast] django-messages script found:", msgs);
|
|
if (Array.isArray(msgs)) {
|
|
msgs.forEach((msg) => {
|
|
console.log("[toast] loading django-message:", msg);
|
|
Alpine.store("toasts").addToast(msg.message, msg.type || "info");
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error("[toast] localStorage restore failed:", e);
|
|
// ignore parse errors
|
|
}
|
|
},
|
|
|
|
addToast(message, type) {
|
|
console.log("[toast] toastStore.addToast delegating:", { message, type });
|
|
Alpine.store("toasts").addToast(message, type);
|
|
},
|
|
|
|
dismissToast(id) {
|
|
console.log("[toast] toastStore.dismissToast delegating:", id);
|
|
Alpine.store("toasts").dismissToast(id);
|
|
},
|
|
}));
|
|
});
|
|
|
|
function toast(message, type) {
|
|
console.log("[toast] toast() called:", { message, type });
|
|
const evt = new CustomEvent("show-toast", {
|
|
detail: { message, type },
|
|
bubbles: true,
|
|
});
|
|
document.dispatchEvent(evt);
|
|
console.log("[toast] CustomEvent dispatched, type:", evt.type);
|
|
}
|
|
window.toast = toast;
|
|
|
|
/**
|
|
* Wrapper around fetch() that dispatches HTMX HX-Trigger events.
|
|
* Use this for any fetch() call that expects HX-Trigger headers
|
|
* (e.g., to show toasts via the HTMX middleware).
|
|
*
|
|
* @todo Migrate these call sites to hx-post + hx-on::after-request
|
|
* for HTMX-native toast handling.
|
|
*/
|
|
window.fetchWithHtmxTriggers = function fetchWithHtmxTriggers(url, options = {}) {
|
|
console.log("[fetchWithHtmxTriggers] fetching:", url);
|
|
return fetch(url, options).then(async (response) => {
|
|
console.log("[fetchWithHtmxTriggers] response status:", response.status);
|
|
const htmxTrigger = response.headers.get("HX-Trigger");
|
|
console.log("[fetchWithHtmxTriggers] HX-Trigger header:", htmxTrigger);
|
|
if (htmxTrigger) {
|
|
let triggers;
|
|
try {
|
|
triggers = JSON.parse(htmxTrigger);
|
|
console.log("[fetchWithHtmxTriggers] parsed triggers:", triggers);
|
|
} catch {
|
|
console.warn("[fetchWithHtmxTriggers] failed to parse HX-Trigger JSON");
|
|
return response;
|
|
}
|
|
// Handle both single object and array of events
|
|
const events = Array.isArray(triggers) ? triggers : [triggers];
|
|
events.forEach((triggerObj) => {
|
|
Object.entries(triggerObj).forEach(([name, detail]) => {
|
|
console.log("[fetchWithHtmxTriggers] dispatching event:", name, detail);
|
|
let parsedDetail = detail;
|
|
try {
|
|
parsedDetail = JSON.parse(detail);
|
|
} catch {
|
|
// keep as string
|
|
}
|
|
document.dispatchEvent(new CustomEvent(name, {
|
|
detail: parsedDetail,
|
|
bubbles: true,
|
|
}));
|
|
});
|
|
});
|
|
}
|
|
return response;
|
|
});
|
|
};
|