// 聊天室右侧名单/房间面板事件绑定,逐步替代 Blade 内联事件。 let rightPanelEventsBound = false; // 用户名单由 Blade 主脚本动态重建,点击/双击状态必须放在模块层避免 DOM 重建后残留闭包。 const userListClickTimers = new WeakMap(); const userListLastTapTimes = new WeakMap(); const messageUserClickTimers = new WeakMap(); const messageUserLastTapTimes = new WeakMap(); const USER_LIST_CLICK_DELAY = 250; const USER_LIST_DOUBLE_TAP_DELAY = 300; const MESSAGE_USER_CLICK_DELAY = 250; const MESSAGE_USER_DOUBLE_TAP_DELAY = 300; /** * 选择聊天目标并聚焦输入框。 * * @param {string} username 目标用户名 * @returns {void} */ function selectChatTarget(username) { const toUserSelect = document.getElementById("to_user"); if (toUserSelect instanceof HTMLSelectElement) { toUserSelect.value = username; } // 手机抽屉里的名单点击后需要收起抽屉,桌面端没有该函数时自然跳过。 window.closeMobileDrawer?.(); document.getElementById("content")?.focus(); } /** * 打开用户资料卡并取消待执行的单击选择。 * * @param {HTMLElement} item 用户列表项 * @returns {void} */ function openUserCardFromList(item) { const username = item.dataset.username || ""; const clickTimer = userListClickTimers.get(item); if (!username) { return; } if (clickTimer) { window.clearTimeout(clickTimer); userListClickTimers.delete(item); } window.closeMobileDrawer?.(); window.openUserCard?.(username); } /** * 读取消息区用户名,过滤掉系统播报和游戏标签等不可交互内容。 * * @param {HTMLElement} item 消息区用户名节点 * @returns {string} */ function getMessageUsername(item) { return (item.dataset.u || "").trim(); } /** * 消息区用户名单击选择聊天目标,保留旧 switchTarget 的离线用户兜底逻辑。 * * @param {string} username 目标用户名 * @returns {void} */ function selectMessageChatTarget(username) { if (!username) { return; } if (typeof window.switchTarget === "function") { window.switchTarget(username); return; } selectChatTarget(username); } /** * 消息区用户名双击打开名片,并取消待执行的单击选择。 * * @param {HTMLElement} item 消息区用户名节点 * @returns {void} */ function openUserCardFromMessage(item) { const username = getMessageUsername(item); const clickTimer = messageUserClickTimers.get(item); if (!username) { return; } if (clickTimer) { window.clearTimeout(clickTimer); messageUserClickTimers.delete(item); } window.openUserCard?.(username); } /** * 调度消息区用户名单击选择,延迟执行用于和双击打开名片互斥。 * * @param {HTMLElement} item 消息区用户名节点 * @returns {void} */ function scheduleMessageUserSelection(item) { const username = getMessageUsername(item); if (!username || messageUserClickTimers.has(item)) { return; } const clickTimer = window.setTimeout(() => { messageUserClickTimers.delete(item); selectMessageChatTarget(username); }, MESSAGE_USER_CLICK_DELAY); messageUserClickTimers.set(item, clickTimer); } /** * 调度用户列表单击选择,延迟执行用于和双击打开名片互斥。 * * @param {HTMLElement} item 用户列表项 * @returns {void} */ function scheduleUserListSelection(item) { const username = item.dataset.username || ""; if (!username || userListClickTimers.has(item)) { return; } const clickTimer = window.setTimeout(() => { userListClickTimers.delete(item); selectChatTarget(username); }, USER_LIST_CLICK_DELAY); userListClickTimers.set(item, clickTimer); } /** * 绑定右侧在线面板的 tab、刷新、排序、搜索与用户列表选择事件。 * * @returns {void} */ export function bindChatRightPanelControls() { if (rightPanelEventsBound || typeof document === "undefined") { return; } rightPanelEventsBound = true; document.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } // 右侧名单和房间 tab 仍由 Blade 主脚本维护状态,这里只转发点击入口。 const tabButton = event.target.closest("[data-chat-right-tab]"); if (tabButton) { event.preventDefault(); window.switchTab?.(tabButton.dataset.chatRightTab); return; } // 在线名单刷新函数仍在主聊天脚本中,后续渲染整体迁移后再收口到模块内。 const refreshButton = event.target.closest("[data-chat-user-list-refresh]"); if (refreshButton) { event.preventDefault(); window.renderUserList?.(); return; } const everyoneItem = event.target.closest("[data-user-list-everyone]"); if (everyoneItem) { event.preventDefault(); selectChatTarget("大家"); return; } const userItem = event.target.closest("[data-user-list-entry]"); if (userItem instanceof HTMLElement) { event.preventDefault(); scheduleUserListSelection(userItem); return; } const messageUser = event.target.closest("[data-chat-message-user]"); if (messageUser instanceof HTMLElement) { event.preventDefault(); scheduleMessageUserSelection(messageUser); } }); document.addEventListener("dblclick", (event) => { if (!(event.target instanceof Element)) { return; } const userItem = event.target.closest("[data-user-list-entry]"); if (userItem instanceof HTMLElement) { event.preventDefault(); openUserCardFromList(userItem); return; } const messageUser = event.target.closest("[data-chat-message-user]"); if (messageUser instanceof HTMLElement) { event.preventDefault(); openUserCardFromMessage(messageUser); } }); document.addEventListener("touchend", (event) => { if (!(event.target instanceof Element)) { return; } const userItem = event.target.closest("[data-user-list-entry]"); if (!(userItem instanceof HTMLElement)) { const messageUser = event.target.closest("[data-chat-message-user]"); if (!(messageUser instanceof HTMLElement)) { return; } // 消息区用户名由 Blade 渲染,移动端双触发统一在 Vite 模块内处理。 const now = Date.now(); const lastTapTime = messageUserLastTapTimes.get(messageUser) || 0; if (now - lastTapTime < MESSAGE_USER_DOUBLE_TAP_DELAY) { event.preventDefault(); messageUserLastTapTimes.delete(messageUser); openUserCardFromMessage(messageUser); return; } messageUserLastTapTimes.set(messageUser, now); return; } // 移动端双触发沿用旧的 300ms 口径,单触摸仍交给 click 事件选择聊天目标。 const now = Date.now(); const lastTapTime = userListLastTapTimes.get(userItem) || 0; if (now - lastTapTime < USER_LIST_DOUBLE_TAP_DELAY) { event.preventDefault(); userListLastTapTimes.delete(userItem); openUserCardFromList(userItem); return; } userListLastTapTimes.set(userItem, now); }, { passive: false }); document.addEventListener("change", (event) => { if (!(event.target instanceof HTMLSelectElement) || event.target.id !== "user-sort-select") { return; } window.renderUserList?.(); }); document.addEventListener("input", (event) => { if (!(event.target instanceof HTMLInputElement) || event.target.id !== "user-search-input") { return; } // 搜索输入高频触发,继续复用主脚本里的 RAF 节流渲染。 window.scheduleFilterUserList?.(); }); }