Files
MajoorWaldi-ComfyUI-Majoor-…/ui/features/panel/messages/messagePopoverController.ts
T

932 lines
32 KiB
TypeScript

import { t } from "../../../app/i18n.js";
import { EVENTS } from "../../../app/events.js";
import {
PANEL_MESSAGES_EVENT,
ensurePanelMessagesReady,
getPanelLastReadAt,
getPanelUnreadCount,
listPanelMessages,
markPanelMessagesRead,
} from "./messageCenter.js";
import {
TOAST_HISTORY_EVENT,
clearToastHistory,
getToastHistoryUnreadCount,
listToastHistory,
markToastHistoryRead,
} from "./toastHistory.js";
import { createShortcutGuidePanel } from "./shortcutGuide.js";
const ALLOWED_MESSAGE_LEVELS = new Set(["info", "success", "warning", "error"]);
const AUTO_CLOSE_HISTORY_DELAY_MS = 2200;
const RUNNING_HISTORY_STATUSES = new Set(["started", "queued", "running", "pending", "in_progress"]);
const TERMINAL_HISTORY_STATUSES = new Set(["succeeded", "success", "complete", "completed", "done", "failed", "error", "cancelled", "canceled"]);
function formatMessageDate(ts: any) {
const stamp = Number(ts);
if (!Number.isFinite(stamp) || stamp <= 0) return "";
try {
return new Date(stamp).toLocaleString([], {
year: "numeric",
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
} catch {
return "";
}
}
function resolveMessageText(entry: any, keyField: any, fallbackField: any, fallbackText: any) {
const key = String(entry?.[keyField] || "").trim();
const fallback =
String(entry?.[fallbackField] || fallbackText || "").trim() || String(fallbackText || "");
return key ? t(key, fallback) : fallback;
}
function normalizeMessageLevel(value: any) {
const raw = String(value || "")
.trim()
.toLowerCase();
if (raw === "warn") return "warning";
if (raw === "danger") return "error";
return ALLOWED_MESSAGE_LEVELS.has(raw) ? raw : "info";
}
function resolveSafeActionUrl(value: any) {
const raw = String(value || "").trim();
if (!raw) return "";
try {
const baseHref = String(globalThis?.window?.location?.href || "http://127.0.0.1/");
const normalized = raw.replace(/\\/g, "/");
const docsMatch = normalized.match(/(?:^|\/)(docs\/[^?#]+\.md)([?#].*)?$/i);
const resolvedRaw = docsMatch ? `/mjr/am/${docsMatch[1]}${docsMatch[2] || ""}` : raw;
const url = new URL(resolvedRaw, baseHref);
const protocol = String(url.protocol || "").toLowerCase();
return protocol === "http:" || protocol === "https:" ? url.href : "";
} catch {
return "";
}
}
function resolveMessageEmoji(entry: any) {
const explicit = String(entry?.emoji || "").trim();
if (explicit) return explicit;
const level = normalizeMessageLevel(entry?.level);
if (level === "success") return "OK";
if (level === "warning") return "WARN";
if (level === "error") return "ERR";
return "INFO";
}
function getHistoryTypeIcon(type: any) {
switch (String(type || "").toLowerCase()) {
case "success":
return "OK";
case "warning":
return "WARN";
case "error":
return "ERR";
default:
return "Tip";
}
}
function formatHistoryDuration(entry: any) {
if (entry?.persistent) return "persistent";
const durationMs = Number(entry?.durationMs);
if (!Number.isFinite(durationMs) || durationMs <= 0) return "";
if (durationMs < 10000) return `${(durationMs / 1000).toFixed(1)}s`;
return `${Math.round(durationMs / 1000)}s`;
}
function formatHistoryStatus(status: any) {
const raw = String(status || "")
.trim()
.toLowerCase();
if (!raw) return "";
return raw.replace(/[_-]+/g, " ");
}
function formatHistoryProgressSummary(entry: any) {
const progress = entry?.progress;
if (!progress || typeof progress !== "object") return "";
const label = String(progress.label || "").trim();
const current = Number(progress.current);
const total = Number(progress.total);
const indexed = Number(progress.indexed);
const skipped = Number(progress.skipped);
const errors = Number(progress.errors);
const parts: any[] = [];
if (label) parts.push(label);
if (Number.isFinite(current) && Number.isFinite(total) && total > 0) {
parts.push(`${current}/${total}`);
}
if (Number.isFinite(indexed)) parts.push(`indexed ${indexed}`);
if (Number.isFinite(skipped)) parts.push(`skipped ${skipped}`);
if (Number.isFinite(errors) && errors > 0) parts.push(`errors ${errors}`);
return parts.join(" | ");
}
function getHistoryProgressPercent(entry: any) {
const progress = entry?.progress;
if (!progress || typeof progress !== "object") return null;
const percent = Number(progress.percent);
if (Number.isFinite(percent)) return Math.max(0, Math.min(100, Math.round(percent)));
const current = Number(progress.current);
const total = Number(progress.total);
if (Number.isFinite(current) && Number.isFinite(total) && total > 0) {
return Math.max(0, Math.min(100, Math.round((current / total) * 100)));
}
return null;
}
function historyEntryTrackKey(entry: any) {
const explicit = String(entry?.trackId || "").trim();
if (explicit) return explicit;
return [
String(entry?.operation || "").trim(),
String(entry?.source || "").trim(),
String(entry?.title || "").trim(),
]
.filter(Boolean)
.join(":");
}
function setTabState({
activeTab = "messages",
title = null,
messagePopover = null,
messageTabBtn = null,
historyTabBtn = null,
shortcutsTabBtn = null,
messageList = null,
historyPanel = null,
shortcutsPanel = null,
markReadBtn = null,
}: Record<string, any> = {}): void {
const isMessages = activeTab === "messages";
const isHistory = activeTab === "history";
const isShortcuts = activeTab === "shortcuts";
const titleText = isMessages
? t("label.messages", "Messages")
: isHistory
? t("label.toastHistory", "History")
: t("msg.shortcuts.title", "Shortcut Guide");
try {
messageTabBtn?.classList.toggle("is-active", isMessages);
messageTabBtn?.setAttribute("aria-selected", isMessages ? "true" : "false");
messageTabBtn?.setAttribute("tabindex", isMessages ? "0" : "-1");
historyTabBtn?.classList.toggle("is-active", isHistory);
historyTabBtn?.setAttribute("aria-selected", isHistory ? "true" : "false");
historyTabBtn?.setAttribute("tabindex", isHistory ? "0" : "-1");
shortcutsTabBtn?.classList.toggle("is-active", isShortcuts);
shortcutsTabBtn?.setAttribute("aria-selected", isShortcuts ? "true" : "false");
shortcutsTabBtn?.setAttribute("tabindex", isShortcuts ? "0" : "-1");
} catch (e) {
console.debug?.(e);
}
try {
if (title) title.textContent = titleText;
messagePopover?.setAttribute?.("aria-label", titleText);
if (messageList) {
messageList.hidden = !isMessages;
messageList.style.display = isMessages ? "" : "none";
messageList.setAttribute("aria-hidden", isMessages ? "false" : "true");
}
if (historyPanel) {
historyPanel.hidden = !isHistory;
historyPanel.style.display = isHistory ? "" : "none";
historyPanel.setAttribute("aria-hidden", isHistory ? "false" : "true");
}
if (shortcutsPanel) {
shortcutsPanel.hidden = !isShortcuts;
shortcutsPanel.style.display = isShortcuts ? "" : "none";
shortcutsPanel.setAttribute("aria-hidden", isShortcuts ? "false" : "true");
}
if (markReadBtn) markReadBtn.hidden = !isMessages;
} catch (e) {
console.debug?.(e);
}
}
export function bindMessagePopoverController({
messageBtn = null,
messagePopover = null,
title = null,
messageList = null,
historyTabBtn = null,
historyTabBadge = null,
historyTabCount = null,
historyPanel = null,
shortcutsPanel = null,
messageTabBtn = null,
messageTabBadge = null,
shortcutsTabBtn = null,
markReadBtn = null,
popovers = null,
onBeforeToggle = null,
signal = null,
} = {} as Record<string, any>) {
ensurePanelMessagesReady();
let activeTab = "messages";
let historyAutoOpened = false;
let autoCloseTimer: any = null;
const autoTrackedHistoryKeys = new Set();
const clearAutoCloseTimer = () => {
if (!autoCloseTimer) return;
try {
clearTimeout(autoCloseTimer);
} catch (e) {
console.debug?.(e);
}
autoCloseTimer = null;
};
const closeAutoOpenedHistoryIfComplete = () => {
if (!historyAutoOpened || activeTab !== "history") return;
if (messagePopover?.style?.display !== "block") return;
const entries = listToastHistory();
for (const entry of entries) {
const key = historyEntryTrackKey(entry);
if (!key || !autoTrackedHistoryKeys.has(key)) continue;
const status = String(entry?.status || "").trim().toLowerCase();
if (!TERMINAL_HISTORY_STATUSES.has(status)) continue;
clearAutoCloseTimer();
autoCloseTimer = setTimeout(() => {
autoCloseTimer = null;
if (!historyAutoOpened || activeTab !== "history") return;
if (messagePopover?.style?.display !== "block") return;
popovers?.close?.(messagePopover);
historyAutoOpened = false;
autoTrackedHistoryKeys.clear();
syncExpandedState();
updateMessageButtonState();
}, AUTO_CLOSE_HISTORY_DELAY_MS);
return;
}
};
const syncExpandedState = () => {
const isOpen = messagePopover?.style?.display === "block";
try {
messageBtn?.setAttribute("aria-expanded", isOpen ? "true" : "false");
} catch (e) {
console.debug?.(e);
}
try {
messagePopover?.setAttribute("aria-hidden", isOpen ? "false" : "true");
} catch (e) {
console.debug?.(e);
}
};
const renderMessagesPopover = () => {
if (!messageList) return;
const entries = listPanelMessages();
const lastReadAt = getPanelLastReadAt();
messageList.replaceChildren();
if (!entries.length) {
const empty = document.createElement("div");
empty.className = "mjr-messages-empty";
empty.textContent = t("msg.noMessages", "No messages for now.");
messageList.appendChild(empty);
return;
}
for (const entry of entries) {
const item = document.createElement("article");
item.className = "mjr-message-item";
const level = normalizeMessageLevel(entry?.level);
item.classList.add(`mjr-message-item--${level}`);
const createdAt = Number(entry?.createdAt || 0);
const unread = createdAt > Number(lastReadAt || 0);
if (unread) item.classList.add("mjr-message-item--unread");
const head = document.createElement("div");
head.className = "mjr-message-item-head";
const titleWrap = document.createElement("div");
titleWrap.className = "mjr-message-item-title-wrap";
const emoji = document.createElement("span");
emoji.className = "mjr-message-item-emoji";
emoji.textContent = resolveMessageEmoji(entry);
emoji.setAttribute("aria-hidden", "true");
const titleEl = document.createElement("div");
titleEl.className = "mjr-message-item-title";
titleEl.textContent = resolveMessageText(
entry,
"titleKey",
"title",
t("label.info", "Info"),
);
const category = document.createElement("span");
category.className = "mjr-message-item-category";
category.textContent = resolveMessageText(
entry,
"categoryKey",
"category",
t("label.info", "Info"),
);
titleWrap.appendChild(emoji);
titleWrap.appendChild(titleEl);
head.appendChild(titleWrap);
head.appendChild(category);
const meta = document.createElement("div");
meta.className = "mjr-message-item-meta";
meta.textContent = formatMessageDate(createdAt);
const body = document.createElement("div");
body.className = "mjr-message-item-body";
body.textContent = resolveMessageText(entry, "bodyKey", "body", "");
item.appendChild(head);
if (meta.textContent) item.appendChild(meta);
if (body.textContent) item.appendChild(body);
const actionUrl = resolveSafeActionUrl(entry?.actionUrl);
const actionLabel = resolveMessageText(
entry,
"actionLabelKey",
"actionLabel",
t("label.readMe", "Read Me"),
);
if (actionUrl && actionLabel) {
const action = document.createElement("a");
action.className = "mjr-message-item-action";
action.href = actionUrl;
action.target = "_blank";
action.rel = "noopener noreferrer";
action.textContent = actionLabel;
item.appendChild(action);
}
messageList.appendChild(item);
}
};
const renderHistoryPanel = () => {
if (!historyPanel) return;
const entries = listToastHistory();
// Build header with clear button (only once)
let historyList = historyPanel.querySelector(".mjr-history-list");
if (!historyList) {
const historyHead = document.createElement("div");
historyHead.className = "mjr-history-head";
const historyHeadLabel = document.createElement("span");
historyHeadLabel.className = "mjr-history-head-label";
historyHeadLabel.textContent = t("label.recentActivity", "Recent activity");
const clearBtn = document.createElement("button");
clearBtn.type = "button";
clearBtn.className = "mjr-btn mjr-history-clear-btn";
clearBtn.textContent = t("btn.clearHistory", "Clear");
clearBtn.title = t("tooltip.clearToastHistory", "Clear toast history");
clearBtn.addEventListener(
"click",
(e) => {
e.stopPropagation();
clearToastHistory();
renderHistoryPanel();
},
signal ? { signal } : undefined,
);
historyHead.appendChild(historyHeadLabel);
historyHead.appendChild(clearBtn);
historyList = document.createElement("div");
historyList.className = "mjr-history-list";
historyPanel.replaceChildren(historyHead, historyList);
}
historyList.replaceChildren();
if (!entries.length) {
const empty = document.createElement("div");
empty.className = "mjr-messages-empty";
empty.textContent = t("msg.noHistory", "No recent activity.");
historyList.appendChild(empty);
return;
}
for (const entry of entries) {
const item = document.createElement("div");
item.className = `mjr-history-item mjr-history-item--${String(entry.type || "info")}`;
const icon = document.createElement("span");
icon.className = "mjr-history-item-icon";
icon.textContent = getHistoryTypeIcon(entry.type);
icon.setAttribute("aria-hidden", "true");
const content = document.createElement("div");
content.className = "mjr-history-item-content";
const titleText = String(entry.title || "").trim();
const detailText = String(entry.detail || "").trim();
const fallbackMessage = String(entry.message || "").trim();
const primaryText = titleText || fallbackMessage;
const secondaryText =
titleText && detailText ? detailText : titleText ? "" : detailText;
const msg = document.createElement("div");
msg.className = titleText ? "mjr-history-item-title" : "mjr-history-item-msg";
msg.textContent = primaryText;
const detail = document.createElement("div");
detail.className = "mjr-history-item-detail";
detail.textContent = secondaryText;
const meta = document.createElement("div");
meta.className = "mjr-history-item-meta";
const time = document.createElement("span");
time.className = "mjr-history-item-time";
time.textContent = formatMessageDate(entry.createdAt);
const duration = document.createElement("span");
duration.className = "mjr-history-item-chip";
duration.textContent = formatHistoryDuration(entry);
const source = document.createElement("span");
source.className = "mjr-history-item-chip";
source.textContent = String(entry.source || "").trim();
const status = document.createElement("span");
status.className = "mjr-history-item-chip";
status.textContent = formatHistoryStatus(entry.status);
content.appendChild(msg);
if (detail.textContent) content.appendChild(detail);
if (time.textContent) meta.appendChild(time);
if (duration.textContent) meta.appendChild(duration);
if (status.textContent) meta.appendChild(status);
if (source.textContent) meta.appendChild(source);
if (meta.childNodes.length) content.appendChild(meta);
const progressSummaryText = formatHistoryProgressSummary(entry);
const progressPercent = getHistoryProgressPercent(entry);
if (progressSummaryText || progressPercent !== null) {
const progressWrap = document.createElement("div");
progressWrap.className = "mjr-history-item-progress";
if (progressSummaryText) {
const progressSummary = document.createElement("div");
progressSummary.className = "mjr-history-item-progress-summary";
progressSummary.textContent = progressSummaryText;
progressWrap.appendChild(progressSummary);
}
if (progressPercent !== null) {
const progressBar = document.createElement("div");
progressBar.className = "mjr-history-item-progress-bar";
const progressFill = document.createElement("div");
progressFill.className = "mjr-history-item-progress-fill";
progressFill.style.width = `${progressPercent}%`;
progressBar.appendChild(progressFill);
progressWrap.appendChild(progressBar);
}
content.appendChild(progressWrap);
}
const actionUrl = resolveSafeActionUrl(entry?.actionUrl);
const actionLabel = String(entry?.actionLabel || "").trim();
if (actionUrl && actionLabel) {
const action = document.createElement("a");
action.className = "mjr-history-item-action";
action.href = actionUrl;
action.target = "_blank";
action.rel = "noopener noreferrer";
action.textContent = actionLabel;
content.appendChild(action);
}
item.appendChild(icon);
item.appendChild(content);
historyList.appendChild(item);
}
};
const setCountBadge = (node: any, count: any) => {
if (!node) return;
const unread = Math.max(0, Number(count || 0) || 0);
if (unread <= 0) {
node.style.display = "none";
node.textContent = "";
return;
}
node.style.display = "inline-flex";
node.textContent = unread > 99 ? "99+" : String(unread);
};
const updateHistoryTabBadge = () => {
const unread = getToastHistoryUnreadCount();
if (historyTabBadge) {
historyTabBadge.style.display = unread > 0 ? "inline-block" : "none";
}
setCountBadge(historyTabCount, unread);
};
const renderShortcutsPanel = () => {
if (!shortcutsPanel) return;
if (shortcutsPanel.childNodes.length > 0) return;
shortcutsPanel.replaceChildren(createShortcutGuidePanel());
};
const updateMarkReadButtonState = () => {
if (!markReadBtn) return;
const unreadCount = Math.max(0, Number(getPanelUnreadCount() || 0) || 0);
markReadBtn.disabled = unreadCount <= 0;
if (unreadCount > 0) {
markReadBtn.title = t("tooltip.markMessagesRead", "Mark all messages as read");
return;
}
markReadBtn.title = t("tooltip.noUnreadMessages", "No unread messages");
};
const updateMessageButtonState = () => {
const unreadCount = Math.max(0, Number(getPanelUnreadCount() || 0) || 0);
if (messageBtn) {
const badge = messageBtn.querySelector(".mjr-message-badge");
const titleText =
unreadCount > 0
? t("tooltip.openMessagesUnread", "Messages ({count} unread)", {
count: unreadCount,
})
: t("tooltip.openMessages", "Messages and updates");
messageBtn.classList.toggle("mjr-message-has-unread", unreadCount > 0);
messageBtn.title = titleText;
messageBtn.setAttribute("aria-label", titleText);
if (badge) {
if (unreadCount <= 0) {
badge.style.display = "none";
badge.textContent = "";
} else {
badge.style.display = "inline-flex";
badge.textContent = unreadCount > 9 ? "9+" : String(unreadCount);
}
}
}
setCountBadge(messageTabBadge, unreadCount);
updateMarkReadButtonState();
};
const refreshMessagesUI = () => {
updateMessageButtonState();
updateHistoryTabBadge();
if (messagePopover?.style?.display === "block") {
if (activeTab === "messages") renderMessagesPopover();
if (activeTab === "history") renderHistoryPanel();
}
};
const listenerOptions = signal ? { signal } : undefined;
try {
window.addEventListener(PANEL_MESSAGES_EVENT, refreshMessagesUI, listenerOptions);
} catch (e) {
console.debug?.(e);
}
try {
window.addEventListener(
TOAST_HISTORY_EVENT,
() => {
updateHistoryTabBadge();
if (messagePopover?.style?.display === "block" && activeTab === "history") {
renderHistoryPanel();
}
if (historyAutoOpened) {
for (const entry of listToastHistory()) {
const status = String(entry?.status || "").trim().toLowerCase();
const key = historyEntryTrackKey(entry);
if (key && RUNNING_HISTORY_STATUSES.has(status)) {
autoTrackedHistoryKeys.add(key);
}
}
closeAutoOpenedHistoryIfComplete();
}
},
listenerOptions,
);
} catch (e) {
console.debug?.(e);
}
try {
if (messageBtn && messagePopover) {
if (!messagePopover.id) messagePopover.id = "mjr-messages-popover";
messageBtn.setAttribute("aria-haspopup", "dialog");
messageBtn.setAttribute("aria-controls", messagePopover.id);
}
} catch (e) {
console.debug?.(e);
}
let expandedObserver: any = null;
try {
if (typeof MutationObserver !== "undefined" && messagePopover) {
expandedObserver = new MutationObserver(syncExpandedState);
expandedObserver.observe(messagePopover, {
attributes: true,
attributeFilter: ["style"],
});
}
} catch (e) {
console.debug?.(e);
}
try {
signal?.addEventListener?.("abort", () => expandedObserver?.disconnect?.(), { once: true });
} catch (e) {
console.debug?.(e);
}
updateMessageButtonState();
updateHistoryTabBadge();
syncExpandedState();
setTabState({
activeTab,
title,
messagePopover,
messageTabBtn,
historyTabBtn,
shortcutsTabBtn,
messageList,
historyPanel,
shortcutsPanel,
markReadBtn,
});
const showMessagesTab = () => {
activeTab = "messages";
setTabState({
activeTab,
title,
messagePopover,
messageTabBtn,
historyTabBtn,
shortcutsTabBtn,
messageList,
historyPanel,
shortcutsPanel,
markReadBtn,
});
renderMessagesPopover();
markPanelMessagesRead();
updateMessageButtonState();
};
const showHistoryTab = () => {
activeTab = "history";
setTabState({
activeTab,
title,
messagePopover,
messageTabBtn,
historyTabBtn,
shortcutsTabBtn,
messageList,
historyPanel,
shortcutsPanel,
markReadBtn,
});
renderHistoryPanel();
markToastHistoryRead();
updateHistoryTabBadge();
};
const showShortcutsTab = () => {
activeTab = "shortcuts";
setTabState({
activeTab,
title,
messagePopover,
messageTabBtn,
historyTabBtn,
shortcutsTabBtn,
messageList,
historyPanel,
shortcutsPanel,
markReadBtn,
});
renderShortcutsPanel();
};
const showTabByName = (tabName: any) => {
if (tabName === "messages") {
showMessagesTab();
return;
}
if (tabName === "history") {
showHistoryTab();
return;
}
showShortcutsTab();
};
const openHistoryPopover = () => {
if (!messagePopover || !popovers || !messageBtn) return;
const isOpen = messagePopover?.style?.display === "block";
if (!isOpen) {
try {
if (typeof onBeforeToggle === "function") onBeforeToggle();
} catch (err) {
console.debug?.(err);
}
popovers.toggle(messagePopover, messageBtn);
}
showHistoryTab();
historyAutoOpened = true;
clearAutoCloseTimer();
autoTrackedHistoryKeys.clear();
for (const entry of listToastHistory()) {
const status = String(entry?.status || "").trim().toLowerCase();
const key = historyEntryTrackKey(entry);
if (key && RUNNING_HISTORY_STATUSES.has(status)) {
autoTrackedHistoryKeys.add(key);
}
}
closeAutoOpenedHistoryIfComplete();
syncExpandedState();
updateMessageButtonState();
};
try {
window.addEventListener(
EVENTS.OPEN_MESSAGE_HISTORY,
() => {
openHistoryPopover();
},
listenerOptions,
);
} catch (e) {
console.debug?.(e);
}
const tabOrder = ["messages", "history", "shortcuts"];
const onTabKeydown = (event: any, tabName: any) => {
const key = String(event?.key || "").toLowerCase();
const index = tabOrder.indexOf(String(tabName || "").toLowerCase());
if (index < 0) return;
if (key === "arrowright") {
event.preventDefault();
const next = tabOrder[(index + 1) % tabOrder.length];
showTabByName(next);
if (next === "messages") messageTabBtn?.focus?.();
if (next === "history") historyTabBtn?.focus?.();
if (next === "shortcuts") shortcutsTabBtn?.focus?.();
return;
}
if (key === "arrowleft") {
event.preventDefault();
const next = tabOrder[(index + tabOrder.length - 1) % tabOrder.length];
showTabByName(next);
if (next === "messages") messageTabBtn?.focus?.();
if (next === "history") historyTabBtn?.focus?.();
if (next === "shortcuts") shortcutsTabBtn?.focus?.();
return;
}
if (key === "home") {
event.preventDefault();
showMessagesTab();
messageTabBtn?.focus?.();
return;
}
if (key === "end") {
event.preventDefault();
showShortcutsTab();
shortcutsTabBtn?.focus?.();
return;
}
if (key === " " || key === "enter") {
event.preventDefault();
showTabByName(tabName);
}
};
messageBtn?.addEventListener(
"click",
(e: any) => {
if (!messagePopover || !popovers || !messageBtn) return;
e.stopPropagation();
historyAutoOpened = false;
autoTrackedHistoryKeys.clear();
clearAutoCloseTimer();
activeTab = "messages";
renderMessagesPopover();
renderShortcutsPanel();
setTabState({
activeTab,
title,
messagePopover,
messageTabBtn,
historyTabBtn,
shortcutsTabBtn,
messageList,
historyPanel,
shortcutsPanel,
markReadBtn,
});
try {
if (typeof onBeforeToggle === "function") onBeforeToggle();
} catch (err) {
console.debug?.(err);
}
popovers.toggle(messagePopover, messageBtn);
if (messagePopover?.style?.display === "block") {
markPanelMessagesRead();
}
syncExpandedState();
updateMessageButtonState();
},
listenerOptions,
);
messageTabBtn?.addEventListener(
"click",
(e: any) => {
e.preventDefault();
e.stopPropagation();
showMessagesTab();
},
listenerOptions,
);
messageTabBtn?.addEventListener("keydown", (e: any) => onTabKeydown(e, "messages"), listenerOptions);
historyTabBtn?.addEventListener(
"click",
(e: any) => {
e.preventDefault();
e.stopPropagation();
historyAutoOpened = false;
autoTrackedHistoryKeys.clear();
clearAutoCloseTimer();
showHistoryTab();
},
listenerOptions,
);
historyTabBtn?.addEventListener("keydown", (e: any) => onTabKeydown(e, "history"), listenerOptions);
shortcutsTabBtn?.addEventListener(
"click",
(e: any) => {
e.preventDefault();
e.stopPropagation();
showShortcutsTab();
},
listenerOptions,
);
shortcutsTabBtn?.addEventListener(
"keydown",
(e: any) => onTabKeydown(e, "shortcuts"),
listenerOptions,
);
markReadBtn?.addEventListener(
"click",
(e: any) => {
e.preventDefault();
e.stopPropagation();
markPanelMessagesRead();
renderMessagesPopover();
updateMessageButtonState();
},
listenerOptions,
);
return {
refresh: refreshMessagesUI,
render: renderMessagesPopover,
updateButtonState: updateMessageButtonState,
showMessagesTab,
showHistoryTab,
showShortcutsTab,
openHistoryPopover,
close: () => {
if (!messagePopover || !popovers) return;
popovers.close(messagePopover);
historyAutoOpened = false;
autoTrackedHistoryKeys.clear();
clearAutoCloseTimer();
syncExpandedState();
},
};
}