419 lines
15 KiB
JavaScript
419 lines
15 KiB
JavaScript
// clab-tooltip.js - 完全隔离事件版本(修改后的显示位置逻辑)
|
|
(function() {
|
|
// 全局变量存储从 RN.html 提取的内容
|
|
let rnContentCache = {};
|
|
let isRnContentLoaded = false;
|
|
let activeTooltip = null;
|
|
let hideTimer = null;
|
|
let isMouseOverTooltip = false;
|
|
|
|
// 从 RN.html 提取内容(保持不变)
|
|
async function loadRnContent() {
|
|
try {
|
|
console.log('加载 RN.html...');
|
|
const response = await fetch('RN.html');
|
|
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
|
|
|
|
const htmlText = await response.text();
|
|
const parser = new DOMParser();
|
|
const rnDoc = parser.parseFromString(htmlText, 'text/html');
|
|
|
|
// 查找所有包含 data-options 的元素
|
|
const optionElements = rnDoc.querySelectorAll('[data-options]');
|
|
console.log(`找到 ${optionElements.length} 个 data-options 元素`);
|
|
|
|
optionElements.forEach(element => {
|
|
const optionValue = element.getAttribute('data-options');
|
|
if (!optionValue) return;
|
|
|
|
// 向前查找最近的 blockquote
|
|
let prevElement = element.previousElementSibling;
|
|
let blockquote = null;
|
|
|
|
// 先找前一个兄弟元素
|
|
while (prevElement) {
|
|
if (prevElement.tagName === 'BLOCKQUOTE') {
|
|
blockquote = prevElement;
|
|
break;
|
|
}
|
|
prevElement = prevElement.previousElementSibling;
|
|
}
|
|
|
|
// 如果没找到,向上查找
|
|
if (!blockquote) {
|
|
let parent = element.parentElement;
|
|
while (parent) {
|
|
let sibling = parent.previousElementSibling;
|
|
while (sibling) {
|
|
if (sibling.tagName === 'BLOCKQUOTE') {
|
|
blockquote = sibling;
|
|
break;
|
|
}
|
|
sibling = sibling.previousElementSibling;
|
|
}
|
|
if (blockquote) break;
|
|
parent = parent.parentElement;
|
|
}
|
|
}
|
|
|
|
if (blockquote) {
|
|
const lis = blockquote.querySelectorAll('li');
|
|
if (lis.length > 0) {
|
|
const liContents = Array.from(lis).map(li => li.textContent.trim());
|
|
|
|
if (!rnContentCache[optionValue]) {
|
|
rnContentCache[optionValue] = [];
|
|
}
|
|
|
|
// 存储所有引用
|
|
rnContentCache[optionValue].push({
|
|
contents: liContents
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
isRnContentLoaded = true;
|
|
|
|
console.log('内容加载完成');
|
|
Object.keys(rnContentCache).forEach(key => {
|
|
console.log(`"${key}": ${rnContentCache[key].length} Questions`);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('加载失败:', error);
|
|
}
|
|
}
|
|
|
|
// 创建 tooltip(保持不变)
|
|
function createTooltip() {
|
|
const tooltip = document.createElement('div');
|
|
tooltip.className = 'clab-tooltip';
|
|
tooltip.id = 'clab-tooltip-element';
|
|
|
|
// 完全阻止所有事件冒泡
|
|
const stopAllEvents = (e) => {
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
e.preventDefault();
|
|
return false;
|
|
};
|
|
|
|
// 阻止所有可能的事件
|
|
const events = [
|
|
'mousedown', 'mouseup', 'click', 'dblclick',
|
|
'mousemove', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave',
|
|
'wheel', 'scroll',
|
|
'touchstart', 'touchmove', 'touchend', 'touchcancel',
|
|
'pointerdown', 'pointermove', 'pointerup', 'pointercancel',
|
|
'dragstart', 'dragover', 'drop'
|
|
];
|
|
|
|
events.forEach(eventType => {
|
|
tooltip.addEventListener(eventType, stopAllEvents, true); // 捕获阶段
|
|
tooltip.addEventListener(eventType, stopAllEvents, false); // 冒泡阶段
|
|
});
|
|
|
|
// 允许 wheel 事件用于滚动,但阻止冒泡
|
|
tooltip.addEventListener('wheel', function(e) {
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
// 允许默认的滚动行为
|
|
}, { passive: false });
|
|
|
|
// 允许鼠标在 tooltip 内移动
|
|
tooltip.addEventListener('mousemove', function(e) {
|
|
e.stopPropagation();
|
|
}, { passive: true });
|
|
|
|
document.body.appendChild(tooltip);
|
|
return tooltip;
|
|
}
|
|
|
|
// 显示 tooltip - 已修改位置逻辑
|
|
function showTooltip(element, tooltip) {
|
|
const rect = element.getBoundingClientRect();
|
|
const optionValue = element.getAttribute('data-options');
|
|
|
|
// 清空内容
|
|
tooltip.innerHTML = '';
|
|
|
|
if (rnContentCache[optionValue] && rnContentCache[optionValue].length > 0) {
|
|
const entries = rnContentCache[optionValue];
|
|
|
|
// 标题显示选项
|
|
const title = document.createElement('div');
|
|
title.className = 'tooltip-title';
|
|
title.textContent = optionValue;
|
|
if (entries.length > 1) {
|
|
title.textContent += ` (${entries.length} questions)`;
|
|
}
|
|
tooltip.appendChild(title);
|
|
|
|
// 显示所有引用
|
|
entries.forEach((entry, entryIndex) => {
|
|
const content = entry.contents;
|
|
|
|
if (content.length === 1) {
|
|
// 单一条目
|
|
const entryDiv = document.createElement('div');
|
|
entryDiv.className = 'entry-single';
|
|
|
|
if (entries.length >= 1) {
|
|
entryDiv.innerHTML = `<span class="q-label">Q${entryIndex + 1}:</span> ${content[0]}`;
|
|
} else {
|
|
entryDiv.textContent = content[0];
|
|
}
|
|
|
|
tooltip.appendChild(entryDiv);
|
|
} else {
|
|
// 多个条目
|
|
const entryDiv = document.createElement('div');
|
|
entryDiv.className = 'entry-multiple';
|
|
|
|
if (entries.length > 1) {
|
|
const header = document.createElement('div');
|
|
header.className = 'q-header';
|
|
header.textContent = `Q${entryIndex + 1}:`;
|
|
entryDiv.appendChild(header);
|
|
}
|
|
|
|
const ol = document.createElement('ol');
|
|
ol.className = 'q-list';
|
|
|
|
content.forEach((item, itemIndex) => {
|
|
const li = document.createElement('li');
|
|
li.innerHTML = `<span class="q-sublabel">${entryIndex + 1}.${itemIndex + 1}</span> ${item}`;
|
|
ol.appendChild(li);
|
|
});
|
|
|
|
entryDiv.appendChild(ol);
|
|
tooltip.appendChild(entryDiv);
|
|
}
|
|
});
|
|
} else {
|
|
// 没有找到内容
|
|
const noContent = document.createElement('div');
|
|
noContent.className = 'tooltip-no-content';
|
|
noContent.textContent = '未找到相关内容';
|
|
tooltip.appendChild(noContent);
|
|
}
|
|
|
|
// 确保 tooltip 有合适的尺寸
|
|
tooltip.style.position = 'fixed';
|
|
tooltip.style.zIndex = '10000';
|
|
tooltip.style.maxWidth = '400px';
|
|
tooltip.style.maxHeight = '500px';
|
|
tooltip.style.overflowY = 'auto';
|
|
tooltip.style.boxSizing = 'border-box';
|
|
|
|
// 首先隐藏并获取尺寸
|
|
tooltip.style.visibility = 'hidden';
|
|
tooltip.classList.add('active');
|
|
document.body.appendChild(tooltip);
|
|
|
|
const tooltipWidth = Math.min(tooltip.scrollWidth, 400);
|
|
const tooltipHeight = Math.min(tooltip.scrollHeight, 500);
|
|
|
|
tooltip.style.width = tooltipWidth + 'px';
|
|
tooltip.style.height = 'auto';
|
|
|
|
const padding = 5;
|
|
const minDistanceFromEdge = 20;
|
|
|
|
let left, top;
|
|
let position = 'right'; // 首选右侧
|
|
|
|
// 检查右侧空间
|
|
if (rect.right + padding + tooltipWidth <= window.innerWidth - minDistanceFromEdge) {
|
|
// 右侧有足够空间
|
|
left = rect.right + padding;
|
|
top = rect.top;
|
|
position = 'right';
|
|
} else if (rect.left - padding - tooltipWidth >= minDistanceFromEdge) {
|
|
// 左侧有足够空间
|
|
left = rect.left - padding - tooltipWidth;
|
|
top = rect.top;
|
|
position = 'left';
|
|
} else {
|
|
// 两侧都没有足够空间,显示在下方
|
|
left = Math.max(
|
|
minDistanceFromEdge,
|
|
Math.min(
|
|
rect.left + (rect.width - tooltipWidth) / 2,
|
|
window.innerWidth - tooltipWidth - minDistanceFromEdge
|
|
)
|
|
);
|
|
top = rect.bottom + padding;
|
|
position = 'bottom';
|
|
}
|
|
|
|
// 垂直边界检查 - 确保不超出屏幕
|
|
if (top + tooltipHeight > window.innerHeight - minDistanceFromEdge) {
|
|
// 如果下方空间不足,尝试显示在上方
|
|
if (rect.top - padding - tooltipHeight >= minDistanceFromEdge) {
|
|
top = rect.top - padding - tooltipHeight;
|
|
} else {
|
|
// 上下都不够,显示在中间
|
|
top = Math.max(
|
|
minDistanceFromEdge,
|
|
Math.min(
|
|
top,
|
|
window.innerHeight - tooltipHeight - minDistanceFromEdge
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
// 水平边界检查 - 确保不超出屏幕
|
|
if (position === 'right' || position === 'left') {
|
|
left = Math.max(
|
|
minDistanceFromEdge,
|
|
Math.min(left, window.innerWidth - tooltipWidth - minDistanceFromEdge)
|
|
);
|
|
}
|
|
|
|
tooltip.style.left = left + 'px';
|
|
tooltip.style.top = top + 'px';
|
|
tooltip.style.visibility = 'visible';
|
|
|
|
// 添加一个小箭头指示方向
|
|
tooltip.setAttribute('data-position', position);
|
|
|
|
activeTooltip = tooltip;
|
|
isMouseOverTooltip = true;
|
|
|
|
// 添加 overlay 防止 body 交互
|
|
addOverlay();
|
|
}
|
|
|
|
// 添加半透明 overlay(保持不变)
|
|
function addOverlay() {
|
|
// 移除现有的 overlay
|
|
removeOverlay();
|
|
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'tooltip-overlay';
|
|
overlay.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 9999;
|
|
background: transparent;
|
|
pointer-events: none;
|
|
`;
|
|
document.body.appendChild(overlay);
|
|
|
|
// 将 overlay 的 pointer-events 设为 auto,但阻止所有事件
|
|
overlay.addEventListener('mousedown', stopEvent, true);
|
|
overlay.addEventListener('mouseup', stopEvent, true);
|
|
overlay.addEventListener('click', stopEvent, true);
|
|
overlay.addEventListener('mousemove', stopEvent, true);
|
|
overlay.addEventListener('wheel', stopEvent, true);
|
|
|
|
function stopEvent(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 移除 overlay(保持不变)
|
|
function removeOverlay() {
|
|
const overlay = document.getElementById('tooltip-overlay');
|
|
if (overlay) {
|
|
overlay.remove();
|
|
}
|
|
}
|
|
|
|
// 隐藏 tooltip(保持不变)
|
|
function hideTooltip() {
|
|
if (!isMouseOverTooltip && activeTooltip) {
|
|
activeTooltip.classList.remove('active');
|
|
activeTooltip = null;
|
|
removeOverlay();
|
|
}
|
|
}
|
|
|
|
// 强制隐藏 tooltip(保持不变)
|
|
function forceHideTooltip() {
|
|
if (activeTooltip) {
|
|
activeTooltip.classList.remove('active');
|
|
activeTooltip = null;
|
|
isMouseOverTooltip = false;
|
|
removeOverlay();
|
|
}
|
|
}
|
|
|
|
// 初始化(保持不变)
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('初始化 clab-tooltip');
|
|
|
|
const clabElements = document.querySelectorAll('.clab[data-options]');
|
|
console.log(`找到 ${clabElements.length} 个 .clab 元素`);
|
|
|
|
if (clabElements.length === 0) return;
|
|
|
|
// 加载内容
|
|
loadRnContent();
|
|
|
|
// 创建 tooltip
|
|
const tooltip = createTooltip();
|
|
|
|
// tooltip 鼠标进入/离开
|
|
tooltip.addEventListener('mouseenter', function() {
|
|
isMouseOverTooltip = true;
|
|
clearTimeout(hideTimer);
|
|
});
|
|
|
|
tooltip.addEventListener('mouseleave', function() {
|
|
isMouseOverTooltip = false;
|
|
hideTimer = setTimeout(() => {
|
|
forceHideTooltip();
|
|
}, 100);
|
|
});
|
|
|
|
// 为所有 .clab 元素添加事件监听
|
|
clabElements.forEach(clab => {
|
|
clab.addEventListener('mouseenter', function() {
|
|
clearTimeout(hideTimer);
|
|
|
|
setTimeout(() => {
|
|
if (isRnContentLoaded) {
|
|
showTooltip(this, tooltip);
|
|
}
|
|
}, 100);
|
|
});
|
|
|
|
clab.addEventListener('mouseleave', function() {
|
|
hideTimer = setTimeout(() => {
|
|
hideTooltip();
|
|
}, 100);
|
|
});
|
|
});
|
|
|
|
// ESC 键隐藏 tooltip
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape' && activeTooltip) {
|
|
forceHideTooltip();
|
|
}
|
|
});
|
|
|
|
// 点击其他地方隐藏 tooltip
|
|
document.addEventListener('click', function(e) {
|
|
if (activeTooltip && !activeTooltip.contains(e.target)) {
|
|
forceHideTooltip();
|
|
}
|
|
});
|
|
|
|
// 窗口大小改变时隐藏 tooltip
|
|
window.addEventListener('resize', forceHideTooltip);
|
|
|
|
// 滚动时隐藏 tooltip
|
|
window.addEventListener('scroll', forceHideTooltip);
|
|
});
|
|
})();
|