迁移在线名单提示气泡脚本
This commit is contained in:
@@ -249,6 +249,9 @@ export {
|
||||
updateRedPacketClaimsUI,
|
||||
} from "./chat-room/red-packet-panel.js";
|
||||
export { createMessageQueue } from "./chat-room/message-queue.js";
|
||||
export {
|
||||
bindInstantHoverTooltip,
|
||||
} from "./chat-room/hover-tooltip.js";
|
||||
export {
|
||||
isExpiredChatImageMessage,
|
||||
localClearScreen,
|
||||
@@ -448,6 +451,9 @@ import {
|
||||
updateRedPacketClaimsUI,
|
||||
} from "./chat-room/red-packet-panel.js";
|
||||
import { createMessageQueue } from "./chat-room/message-queue.js";
|
||||
import {
|
||||
bindInstantHoverTooltip,
|
||||
} from "./chat-room/hover-tooltip.js";
|
||||
import {
|
||||
isExpiredChatImageMessage,
|
||||
localClearScreen,
|
||||
@@ -457,6 +463,8 @@ import {
|
||||
} from "./chat-room/message-utils.js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
bindInstantHoverTooltip();
|
||||
|
||||
// 保留聚合入口,给新迁移模块、测试和仍在 Blade 内的存量脚本统一读取工具。
|
||||
window.ChatRoomTools = {
|
||||
escapeHtml,
|
||||
@@ -680,6 +688,7 @@ if (typeof window !== "undefined") {
|
||||
showRedPacketModal,
|
||||
updateRedPacketClaimsUI,
|
||||
createMessageQueue,
|
||||
bindInstantHoverTooltip,
|
||||
isExpiredChatImageMessage,
|
||||
localClearScreen,
|
||||
scrollChatToBottom,
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// 在线名单徽章即时提示工具,负责动态徽章的悬浮气泡定位和显示。
|
||||
|
||||
const TOOLTIP_SELECTOR = ".user-badge-icon[data-instant-tooltip]";
|
||||
|
||||
/**
|
||||
* 绑定在线名单徽章的即时悬浮提示。
|
||||
* Vite 模块脚本通常晚于 Blade 经典脚本执行,这里由模块加载后主动绑定,避免依赖 Blade 调用时序。
|
||||
*
|
||||
* @param {HTMLElement|null} tooltip 提示气泡容器
|
||||
* @param {Document|HTMLElement} root 事件委托根节点
|
||||
* @returns {{hide: () => void, destroy: () => void}|null}
|
||||
*/
|
||||
export function bindInstantHoverTooltip(
|
||||
tooltip = document.getElementById("chat-hover-tooltip"),
|
||||
root = document,
|
||||
) {
|
||||
if (!tooltip || tooltip.dataset.instantTooltipBound === "1") {
|
||||
return null;
|
||||
}
|
||||
|
||||
tooltip.dataset.instantTooltipBound = "1";
|
||||
let activeTrigger = null;
|
||||
|
||||
/**
|
||||
* 从事件目标解析可显示提示的徽章节点。
|
||||
*
|
||||
* @param {EventTarget|null} target 事件目标
|
||||
* @returns {HTMLElement|null}
|
||||
*/
|
||||
const resolveTrigger = (target) => {
|
||||
if (!(target instanceof Element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return target.closest(TOOLTIP_SELECTOR);
|
||||
};
|
||||
|
||||
/**
|
||||
* 把提示气泡定位到徽章旁边,同时避免超出视口边缘。
|
||||
*
|
||||
* @param {HTMLElement|null} trigger 当前悬浮徽章
|
||||
* @returns {void}
|
||||
*/
|
||||
const position = (trigger) => {
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = 10;
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
const tooltipWidth = tooltip.offsetWidth;
|
||||
const tooltipHeight = tooltip.offsetHeight;
|
||||
const fitsRight = rect.right + offset + tooltipWidth <= window.innerWidth - 8;
|
||||
const side = fitsRight ? "right" : "left";
|
||||
const nextLeft = fitsRight ? rect.right + offset : Math.max(8, rect.left - tooltipWidth - offset);
|
||||
const nextTop = Math.min(
|
||||
Math.max(8, rect.top + (rect.height - tooltipHeight) / 2),
|
||||
window.innerHeight - tooltipHeight - 8,
|
||||
);
|
||||
|
||||
tooltip.dataset.side = side;
|
||||
tooltip.style.left = `${nextLeft}px`;
|
||||
tooltip.style.top = `${nextTop}px`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示当前徽章对应的提示文字。
|
||||
*
|
||||
* @param {HTMLElement|null} trigger 当前悬浮徽章
|
||||
* @returns {void}
|
||||
*/
|
||||
const show = (trigger) => {
|
||||
const tooltipText = trigger?.dataset.instantTooltip || "";
|
||||
|
||||
if (!trigger || !tooltipText) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeTrigger = trigger;
|
||||
tooltip.textContent = tooltipText;
|
||||
tooltip.style.display = "block";
|
||||
position(trigger);
|
||||
};
|
||||
|
||||
/**
|
||||
* 隐藏提示气泡并清理方向状态。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const hide = () => {
|
||||
activeTrigger = null;
|
||||
tooltip.style.display = "none";
|
||||
tooltip.textContent = "";
|
||||
delete tooltip.dataset.side;
|
||||
};
|
||||
|
||||
const handleMouseOver = (event) => {
|
||||
const trigger = resolveTrigger(event.target);
|
||||
|
||||
if (trigger) {
|
||||
show(trigger);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseOut = (event) => {
|
||||
const trigger = resolveTrigger(event.target);
|
||||
|
||||
if (!trigger || trigger !== activeTrigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.relatedTarget && trigger.contains(event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
hide();
|
||||
};
|
||||
|
||||
const reposition = () => {
|
||||
if (activeTrigger) {
|
||||
position(activeTrigger);
|
||||
}
|
||||
};
|
||||
|
||||
root.addEventListener("mouseover", handleMouseOver);
|
||||
root.addEventListener("mouseout", handleMouseOut);
|
||||
window.addEventListener("scroll", reposition, true);
|
||||
window.addEventListener("resize", reposition);
|
||||
|
||||
return {
|
||||
hide,
|
||||
destroy() {
|
||||
root.removeEventListener("mouseover", handleMouseOver);
|
||||
root.removeEventListener("mouseout", handleMouseOut);
|
||||
window.removeEventListener("scroll", reposition, true);
|
||||
window.removeEventListener("resize", reposition);
|
||||
delete tooltip.dataset.instantTooltipBound;
|
||||
hide();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -40,8 +40,6 @@
|
||||
const BLOCKED_SYSTEM_SENDERS_STORAGE_KEY = window.ChatRoomTools?.BLOCKED_SYSTEM_SENDERS_STORAGE_KEY || 'chat_blocked_system_senders';
|
||||
const CHAT_SOUND_MUTED_STORAGE_KEY = window.ChatRoomTools?.CHAT_SOUND_MUTED_STORAGE_KEY || 'chat_sound_muted';
|
||||
const BLOCKABLE_SYSTEM_SENDERS = window.ChatRoomTools?.BLOCKABLE_SYSTEM_SENDERS || ['钓鱼播报', '星海小博士', '百家乐', '跑马', '神秘箱子'];
|
||||
const hoverTooltip = document.getElementById('chat-hover-tooltip');
|
||||
let activeTooltipTrigger = null;
|
||||
|
||||
let onlineUsers = {};
|
||||
// 暂时暴露给已迁移的手机抽屉 Vite 模块读取,后续在线名单整体迁移后可移除。
|
||||
@@ -1780,105 +1778,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把小气泡提示定位到图标旁边,不侵入右侧名单结构。
|
||||
*
|
||||
* @param {HTMLElement} trigger 当前悬浮的图标元素
|
||||
*/
|
||||
function positionHoverTooltip(trigger) {
|
||||
if (!hoverTooltip || !trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = 10;
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
const tooltipWidth = hoverTooltip.offsetWidth;
|
||||
const tooltipHeight = hoverTooltip.offsetHeight;
|
||||
const fitsRight = rect.right + offset + tooltipWidth <= window.innerWidth - 8;
|
||||
const side = fitsRight ? 'right' : 'left';
|
||||
const nextLeft = fitsRight
|
||||
? rect.right + offset
|
||||
: Math.max(8, rect.left - tooltipWidth - offset);
|
||||
const nextTop = Math.min(
|
||||
Math.max(8, rect.top + (rect.height - tooltipHeight) / 2),
|
||||
window.innerHeight - tooltipHeight - 8
|
||||
);
|
||||
|
||||
hoverTooltip.dataset.side = side;
|
||||
hoverTooltip.style.left = `${nextLeft}px`;
|
||||
hoverTooltip.style.top = `${nextTop}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示图标旁边的小气泡文字提示。
|
||||
*
|
||||
* @param {HTMLElement|null} trigger 当前悬浮的图标元素
|
||||
*/
|
||||
function showHoverTooltip(trigger) {
|
||||
if (!hoverTooltip || !trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tooltipText = trigger.dataset.instantTooltip || '';
|
||||
if (!tooltipText) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeTooltipTrigger = trigger;
|
||||
hoverTooltip.textContent = tooltipText;
|
||||
hoverTooltip.style.display = 'block';
|
||||
positionHoverTooltip(trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏图标提示气泡。
|
||||
*/
|
||||
function hideHoverTooltip() {
|
||||
if (!hoverTooltip) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeTooltipTrigger = null;
|
||||
hoverTooltip.style.display = 'none';
|
||||
hoverTooltip.textContent = '';
|
||||
delete hoverTooltip.dataset.side;
|
||||
}
|
||||
|
||||
// 通过事件委托处理动态生成的徽章提示,确保 hover 后立刻显示。
|
||||
document.addEventListener('mouseover', (event) => {
|
||||
const trigger = event.target.closest('.user-badge-icon[data-instant-tooltip]');
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
showHoverTooltip(trigger);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseout', (event) => {
|
||||
const trigger = event.target.closest('.user-badge-icon[data-instant-tooltip]');
|
||||
if (!trigger || trigger !== activeTooltipTrigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.relatedTarget && trigger.contains(event.relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideHoverTooltip();
|
||||
});
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (activeTooltipTrigger) {
|
||||
positionHoverTooltip(activeTooltipTrigger);
|
||||
}
|
||||
}, true);
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (activeTooltipTrigger) {
|
||||
positionHoverTooltip(activeTooltipTrigger);
|
||||
}
|
||||
});
|
||||
|
||||
// ── 渲染在线人员列表(支持排序) ──────────────────
|
||||
/**
|
||||
* 核心渲染函数:将在线用户渲染到指定容器(桌面端名单区和手机端抽屉共用)
|
||||
|
||||
Reference in New Issue
Block a user