/** * Toast-уведомления зоотех-страниц (нижний pill-toast, стиль Яндекс Музыки). */ (function (global) { const HOST_ID = "wespZtToastHost"; const DEFAULT_DURATION_MS = 4000; const EXIT_MS = 220; const UM = () => global.WespUserMessages || {}; const userFacingMessage = (message, fallback) => { const api = UM(); if (typeof api.userFacingMessage === "function") { return api.userFacingMessage(message, fallback); } const text = message == null ? "" : String(message).trim(); return text || fallback || "Не удалось выполнить операцию. Попробуйте ещё раз."; }; let hostEl = null; let activeToast = null; let dismissTimer = null; function ensureHost() { if (hostEl && hostEl.isConnected) return hostEl; hostEl = document.getElementById(HOST_ID); if (!hostEl) { hostEl = document.createElement("div"); hostEl.id = HOST_ID; hostEl.className = "wesp-zt-toast-host"; hostEl.setAttribute("aria-live", "polite"); hostEl.setAttribute("aria-atomic", "true"); document.body.appendChild(hostEl); } return hostEl; } function clearDismissTimer() { if (dismissTimer) { clearTimeout(dismissTimer); dismissTimer = null; } } function normalizeType(type) { if (type === "error" || type === "warning") return type; return "success"; } function escapeHtml(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function normalizeDisplayMessage(message) { if (message && typeof message === "object") { if (Array.isArray(message.parts)) return message; if (message.text != null) message = message.text; } const text = message == null ? "" : String(message).trim(); if (/^Сохранено\b/i.test(text)) { return "Сохранено"; } return text; } function renderMessageContent(message, opts) { if (message && typeof message === "object") { if (Array.isArray(message.parts)) { return message.parts .map((part) => { const text = escapeHtml(part && part.text != null ? part.text : ""); return part && part.bold ? `${text}` : text; }) .join(""); } if (message.text != null) { return escapeHtml(message.text); } } const text = message == null ? "" : String(message); if (opts && Array.isArray(opts.parts)) { return opts.parts .map((part) => { const chunk = escapeHtml(part && part.text != null ? part.text : ""); return part && part.bold ? `${chunk}` : chunk; }) .join(""); } return escapeHtml(text); } function removeToast(toastEl, immediate) { if (!toastEl || !toastEl.isConnected) return Promise.resolve(); clearDismissTimer(); if (immediate) { toastEl.remove(); if (activeToast === toastEl) activeToast = null; return Promise.resolve(); } return new Promise((resolve) => { toastEl.classList.add("is-exiting"); const onEnd = () => { toastEl.removeEventListener("animationend", onEnd); toastEl.remove(); if (activeToast === toastEl) activeToast = null; resolve(); }; toastEl.addEventListener("animationend", onEnd); setTimeout(onEnd, EXIT_MS + 40); }); } function dismissAll() { clearDismissTimer(); if (!activeToast) return; const toast = activeToast; activeToast = null; return removeToast(toast, false); } function plainTextFromMessage(message) { const api = UM(); if (typeof api.plainText === "function") return api.plainText(message); if (message && typeof message === "object") { if (message instanceof Error) return ""; if (Array.isArray(message.parts)) { return message.parts.map((p) => (p && p.text != null ? String(p.text) : "")).join(""); } if (message.text != null) return String(message.text); } return message == null ? "" : String(message); } function persistNotification(type, message, opts) { if (opts && opts.skipRecord) return; const plain = plainTextFromMessage( message && typeof message === "object" && Array.isArray(message.parts) ? message : normalizeDisplayMessage(message) ); if (!plain.trim() && !(opts && opts.record)) return; const builder = global.WespZootechNotificationMessages; const payload = builder ? builder.buildNotificationRecord(type, plain, opts) : { title: plain.slice(0, 200) || "Уведомление", detail: plain, kind: type, category: "general", page: "general", }; fetch("/api/notifications", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }) .then(() => { global.WespZootechNotificationCenter?.refreshBadge?.(); }) .catch(() => { /* fire-and-forget */ }); } function show(type, message, opts) { const normalizedType = normalizeType(type); const duration = opts && Number.isFinite(opts.duration) ? opts.duration : DEFAULT_DURATION_MS; const displayMessage = message && typeof message === "object" && Array.isArray(message.parts) ? message : normalizeDisplayMessage(message); const html = renderMessageContent(displayMessage, opts); const plainText = displayMessage && typeof displayMessage === "object" ? "" : String(displayMessage || ""); const isMultiline = plainText.length > 42 || /\n/.test(plainText); const run = async () => { if (activeToast) { await removeToast(activeToast, false); } const root = ensureHost(); const toast = document.createElement("div"); toast.className = `wesp-zt-toast wesp-zt-toast--${normalizedType}${ isMultiline ? " wesp-zt-toast--multiline" : "" }`; toast.setAttribute("role", "status"); const accent = document.createElement("span"); accent.className = "wesp-zt-toast__accent"; accent.setAttribute("aria-hidden", "true"); const messageEl = document.createElement("p"); messageEl.className = "wesp-zt-toast__message"; messageEl.innerHTML = html; const closeBtn = document.createElement("button"); closeBtn.type = "button"; closeBtn.className = "wesp-zt-toast__close"; closeBtn.setAttribute("aria-label", "Закрыть уведомление"); closeBtn.textContent = "×"; closeBtn.addEventListener("click", () => { dismissAll(); }); toast.append(accent, messageEl, closeBtn); root.appendChild(toast); activeToast = toast; if (duration > 0) { dismissTimer = setTimeout(() => { dismissAll(); }, duration); } }; persistNotification(normalizedType, displayMessage, opts); run(); } function success(message, opts) { show("success", message, opts); } function error(message, opts) { const safe = userFacingMessage(message, opts && opts.fallback); show("error", safe, opts); } function warning(message, opts) { const safe = userFacingMessage(message, opts && opts.fallback); show("warning", safe, opts); } function open(payload) { const data = payload && typeof payload === "object" ? payload : {}; show(data.type || "success", data.message, data); } function createAdapter() { return { success, error, warning, open, dismissAll, }; } global.WespZootechNotify = { success, error, warning, open, dismissAll, createAdapter, userFacingMessage, messageFromResponseBody: (body, fallback) => { const api = UM(); if (typeof api.messageFromResponseBody === "function") { return api.messageFromResponseBody(body, fallback); } return userFacingMessage(null, fallback); }, isTechnicalMessage: (message) => { const api = UM(); if (typeof api.isTechnicalMessage === "function") return api.isTechnicalMessage(message); return false; }, DEFAULT_ERROR_MESSAGE: UM().DEFAULT_ERROR_MESSAGE || "Не удалось выполнить операцию. Попробуйте ещё раз.", get instance() { return createAdapter(); }, }; })(typeof window !== "undefined" ? window : globalThis);