// js/ui/reference-manager-detail.js
// 参考文献管理器 - 详情页专用版本
(function(global) {
'use strict';
let currentDocumentId = null;
let currentReferences = [];
let isFloatingPanelOpen = false;
let citationLocations = {}; // 记录每个文献的引用位置 {refIndex: [citationElementIds]}
let activeTooltipLinkElement = null; // 记录当前激活tooltip的链接元素
let hideTooltipTimer = null; // 记录隐藏tooltip的定时器
/**
* 初始化参考文献管理器(详情页版本)
*/
function initReferenceManagerForDetail() {
// 获取文档ID
const urlParams = new URLSearchParams(window.location.search);
currentDocumentId = urlParams.get('id');
// 如果 URL 参数丢失(如被服务器规则重写),尝试从 localStorage 恢复
if (!currentDocumentId) {
try {
currentDocumentId = localStorage.getItem('pbx_current_doc_id');
} catch (e) {}
}
if (!currentDocumentId) {
console.warn('[ReferenceManagerDetail] No document ID found');
return;
}
// 绑定dock点击事件
bindDockClickEvent();
// 监听内容渲染完成事件
document.addEventListener('contentRendered', () => {
loadAndDisplayReferences();
});
// 监听子块分割完成事件(如果存在)
document.addEventListener('subBlocksSegmented', () => {
console.log('[ReferenceManagerDetail] 子块分割完成,重新标记引用');
if (currentReferences.length > 0) {
appendReferencesToContent(currentReferences);
}
});
// 使用MutationObserver监听DOM变化,自动重新标记引用
setupDOMMutationObserver();
// 创建悬浮面板
createFloatingPanel();
// 处理页面刷新:仅在内容已就绪或数据已可用时尝试一次,
// 否则等待 contentRendered 事件再处理,避免早期取不到内容
setTimeout(() => {
const hasData = !!(window.data && (window.data.ocr || window.data.translation || (Array.isArray(window.data.ocrChunks) && window.data.ocrChunks.length > 0)));
if (window.contentReady || hasData) {
loadAndDisplayReferences();
} else {
console.log('[ReferenceManagerDetail] 等待内容渲染完成后再尝试提取参考文献');
}
}, 200);
console.log('[ReferenceManagerDetail] Initialized for document:', currentDocumentId);
}
/**
* 绑定dock点击事件
*/
function bindDockClickEvent() {
const refStat = document.querySelector('[data-stat-type="reference"]');
if (refStat) {
refStat.addEventListener('click', (e) => {
e.preventDefault();
toggleFloatingPanel();
});
refStat.style.cursor = 'pointer';
}
}
/**
* 加载并显示参考文献
*/
async function loadAndDisplayReferences() {
// 从存储加载
const data = await global.ReferenceStorage?.loadReferences(currentDocumentId);
if (data && data.references) {
currentReferences = data.references;
updateReferenceCount(currentReferences.length);
appendReferencesToContent(currentReferences);
addToTOC();
} else {
// 尝试自动提取
await autoExtractReferences();
}
}
/**
* 自动提取参考文献
*/
async function autoExtractReferences() {
const markdown = await getCurrentMarkdownContent();
if (!markdown) return;
const section = global.ReferenceDetector?.detectReferenceSection(markdown);
if (!section || section.entries.length === 0) {
console.log('[ReferenceManagerDetail] No references detected');
return;
}
console.log(`[ReferenceManagerDetail] Auto-detected ${section.entries.length} references`);
// 显示提取方式选择对话框
await showExtractionMethodDialog(section, markdown);
}
/**
* 显示提取方式选择对话框
*/
async function showExtractionMethodDialog(section, markdown) {
const message = `检测到 ${section.entries.length} 条文献\n\n` +
`请选择提取方式:\n` +
`1. 正则表达式(快速,适合标准格式)\n` +
`2. AI智能提取(准确,适合任意格式)\n` +
`3. 混合模式(推荐,先正则再AI)\n\n` +
`请输入数字 1、2 或 3(取消将不再提示):`;
const choice = prompt(message);
// 用户点击取消:保存空数组,避免反复提示
if (!choice) {
console.log('[ReferenceManagerDetail] User cancelled extraction, saving empty state');
if (global.ReferenceStorage) {
await global.ReferenceStorage.saveReferences(currentDocumentId, [], {
extractionSkipped: true,
skippedAt: new Date().toISOString()
});
console.log('[ReferenceManagerDetail] Empty state saved successfully');
}
return;
}
switch (choice.trim()) {
case '1':
extractWithRegex(section, markdown);
break;
case '2':
extractWithAI(section, markdown);
break;
case '3':
extractWithHybrid(section, markdown);
break;
default:
alert('无效的选择,请重新打开文档并输入 1、2 或 3');
}
}
/**
* 使用正则表达式提取
*/
function extractWithRegex(section, markdown) {
const extracted = global.ReferenceExtractor?.batchExtract(section.entries) || section.entries;
// 建立索引
let indexed = extracted;
if (global.ReferenceIndexer) {
indexed = global.ReferenceIndexer.buildIndex(
currentDocumentId,
markdown,
extracted
);
}
// 保存
global.ReferenceStorage?.saveReferences(
currentDocumentId,
indexed,
{
extractedAt: new Date().toISOString(),
method: 'regex'
}
);
// 显示
currentReferences = indexed;
updateReferenceCount(indexed.length);
appendReferencesToContent(indexed);
addToTOC();
alert(`正则提取完成\n成功提取 ${indexed.length} 条文献`);
}
/**
* 使用AI提取
*/
async function extractWithAI(section, markdown) {
const simpleEntries = section.entries.map((e, idx) => ({
index: idx,
rawText: e.rawText || e,
needsAIProcessing: true
}));
await processWithAI(simpleEntries, markdown);
}
/**
* 使用混合模式提取
*/
async function extractWithHybrid(section, markdown) {
// 先用正则提取
const extracted = global.ReferenceExtractor?.batchExtract(section.entries) || section.entries;
// 找出需要AI处理的
const needsAI = extracted.filter(e => e.needsAIProcessing);
if (needsAI.length > 0) {
const message = `正则提取: ${extracted.length - needsAI.length}/${extracted.length} 成功\n` +
`需要AI处理: ${needsAI.length} 条\n\n` +
`是否继续使用AI处理剩余文献?`;
if (confirm(message)) {
await processWithAI(extracted, markdown);
} else {
// 只保存正则提取的结果
saveExtractedReferences(extracted, markdown);
}
} else {
// 全部正则提取成功
saveExtractedReferences(extracted, markdown);
alert(`提取完成\n全部 ${extracted.length} 条文献已通过正则成功提取`);
}
}
/**
* 在原文中标记引用(不插入参考文献列表)
*/
function appendReferencesToContent(references) {
console.log('[appendReferencesToContent] 开始标记引用,references.length:', references.length);
if (!references || references.length === 0) {
console.warn('[appendReferencesToContent] 没有参考文献数据');
return;
}
// 查找内容容器(使用正确的ID)
const containers = ['#ocr-content-wrapper', '#translation-content-wrapper'];
containers.forEach(selector => {
const container = document.querySelector(selector);
if (!container) {
console.log('[appendReferencesToContent] 容器不存在:', selector);
return;
}
console.log('[appendReferencesToContent] 找到容器:', selector);
// 清除之前的标记(避免重复标记)
const existingCitations = container.querySelectorAll('.reference-citation');
console.log('[appendReferencesToContent] 清除现有引用:', existingCitations.length);
existingCitations.forEach(citation => {
// 将链接替换回原始文本
const text = document.createTextNode(citation.textContent);
citation.parentNode.replaceChild(text, citation);
});
// 标记原文中的引用(如[1], [2])
markCitationsInContent(container, references.length);
// 使用事件委托,在容器级别监听引用链接的鼠标事件
setupCitationEventDelegation(container);
console.log('[appendReferencesToContent] 已标记原文中的引用,不插入文献列表');
});
// 更新悬浮面板内容
updatePanelContent();
}
/**
* 设置DOM变化监听器,自动重新标记引用
*/
function setupDOMMutationObserver() {
const containers = ['#ocr-content-wrapper', '#translation-content-wrapper'];
let remarkerTimer = null;
containers.forEach(selector => {
const container = document.querySelector(selector);
if (!container) return;
const observer = new MutationObserver((mutations) => {
// 检查是否有子块被添加或修改
let needRemark = false;
for (const mutation of mutations) {
if (mutation.type === 'childList' || mutation.type === 'subtree') {
// 检查是否有新增的子块
if (mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE &&
(node.classList?.contains('sub-block') ||
node.querySelector?.('.sub-block'))) {
needRemark = true;
break;
}
}
}
}
if (needRemark) break;
}
if (needRemark && currentReferences.length > 0) {
// 防抖:延迟执行,避免频繁重新标记
if (remarkerTimer) clearTimeout(remarkerTimer);
remarkerTimer = setTimeout(() => {
console.log('[MutationObserver] 检测到DOM变化,重新标记引用');
appendReferencesToContent(currentReferences);
}, 500);
}
});
observer.observe(container, {
childList: true,
subtree: true
});
console.log('[setupDOMMutationObserver] 已设置DOM监听器:', selector);
});
}
/**
* 设置引用链接的事件委托
*/
function setupCitationEventDelegation(container) {
// 移除旧的事件监听器(如果存在)
if (container._citationEventSetup) {
return; // 已经设置过了
}
container._citationEventSetup = true;
// 使用事件委托监听mouseenter
container.addEventListener('mouseover', (e) => {
const target = e.target;
if (target.classList && target.classList.contains('reference-citation')) {
// 获取所有文献编号
const refNumbers = target.dataset.refNumbers;
if (refNumbers) {
console.log('[delegation mouseenter] 触发悬停事件,refNumbers:', refNumbers);
// 清除之前的隐藏定时器
if (hideTooltipTimer) {
clearTimeout(hideTooltipTimer);
hideTooltipTimer = null;
}
showReferenceDetailTooltip(target, refNumbers);
}
}
});
// 使用事件委托监听mouseleave
container.addEventListener('mouseout', (e) => {
const target = e.target;
if (target.classList && target.classList.contains('reference-citation')) {
// 从 dataset 获取引用编号
const refNumbers = target.dataset.refNumbers;
if (refNumbers) {
console.log('[delegation mouseleave] 触发离开事件,refNumbers:', refNumbers, 'activeElement:', activeTooltipLinkElement === target);
// 延迟隐藏,给用户时间移动到tooltip上
hideTooltipTimer = setTimeout(() => {
const tooltip = document.getElementById('reference-detail-tooltip');
const tooltipHover = tooltip ? tooltip.matches(':hover') : false;
const isActive = activeTooltipLinkElement === target;
console.log('[delegation mouseleave timer] refNumbers:', refNumbers, 'tooltip hover:', tooltipHover, 'is active:', isActive);
// 只有当鼠标既不在tooltip上且当前链接不再是活跃链接时才隐藏
if (tooltip && !tooltipHover && !isActive) {
hideReferenceDetailTooltip();
}
}, 100);
}
}
});
// 监听点击事件
container.addEventListener('click', (e) => {
const target = e.target;
if (target.classList && target.classList.contains('reference-citation')) {
e.preventDefault();
const refIndex = parseInt(target.dataset.refIndex, 10);
if (!isNaN(refIndex)) {
window.scrollToReferenceItem(refIndex);
}
}
});
console.log('[setupCitationEventDelegation] 已设置事件委托');
}
/**
* 更新面板内容
*/
function updatePanelContent() {
const content = document.getElementById('reference-panel-content');
if (!content) return;
if (currentReferences.length === 0) {
content.innerHTML = `
`;
} else {
content.innerHTML = `
${renderPanelList(currentReferences)}
`;
}
}
/**
* 渲染面板列表
*/
function renderPanelList(references) {
return references.map((ref, idx) => {
const authors = ref.authors && ref.authors.length > 0
? (ref.authors.length > 2
? `${ref.authors.slice(0, 2).join(', ')} 等`
: ref.authors.join(', '))
: '作者未知';
const citationCount = citationLocations[idx] ? citationLocations[idx].length : 0;
return `
${citationCount > 0 ? `
引用 ${citationCount} 次
` : ''}
${ref.doi ? `
DOI
` : ''}
`;
}).join('');
}
/**
* 滚动到文献详情(原References区域的具体文献条目)
*/
global.scrollToReferenceItem = function(index) {
// 查找原文中的References标题
const containers = ['#ocr-content-wrapper', '#translation-content-wrapper'];
for (const selector of containers) {
const container = document.querySelector(selector);
if (!container) continue;
const referenceHeading = findReferenceHeading(container);
if (referenceHeading) {
// 先滚动到References标题
referenceHeading.scrollIntoView({ behavior: 'smooth', block: 'start' });
// 高亮整个References区域
setTimeout(() => {
let currentElement = referenceHeading.nextElementSibling;
let highlightElements = [];
// 收集References区域的所有元素
while (currentElement) {
const isNextSection = currentElement.tagName && /^H[1-3]$/i.test(currentElement.tagName);
if (isNextSection) {
const headingText = currentElement.textContent.trim().toLowerCase();
const sectionKeywords = ['acknowledgment', 'appendix', 'supplementary', '致谢', '附录'];
const isNewSection = sectionKeywords.some(keyword => headingText.includes(keyword));
if (isNewSection) break;
}
highlightElements.push(currentElement);
currentElement = currentElement.nextElementSibling;
}
// 添加高亮
highlightElements.forEach(el => {
el.style.backgroundColor = '#fff3cd';
el.style.transition = 'background-color 0.3s';
});
// 3秒后移除高亮
setTimeout(() => {
highlightElements.forEach(el => {
el.style.backgroundColor = '';
});
}, 3000);
}, 500);
console.log('[scrollToReferenceItem] 已跳转到References区域并高亮文献', index + 1);
return;
}
}
alert('未找到References区域');
};
/**
* 查找参考文献标题元素
*/
function findReferenceHeading(container) {
if (!container) return null;
// 参考文献标题的常见关键词
const keywords = [
'references', 'reference', 'bibliography', 'works cited',
'literature cited', 'citations',
'参考文献', '引用文献', '文献引用', '参考资料'
];
// 查找所有标题元素
const headings = container.querySelectorAll('h1, h2, h3, h4, h5, h6');
for (const heading of headings) {
const text = heading.textContent.trim().toLowerCase();
// 检查是否包含参考文献关键词
for (const keyword of keywords) {
if (text === keyword || text === keyword + 's') {
return heading;
}
}
}
return null;
}
/**
* 在原文中标记引用并添加点击跳转功能
*/
function markCitationsInContent(container, refCount) {
if (!container) return;
// 重置引用位置记录
citationLocations = {};
for (let i = 0; i < refCount; i++) {
citationLocations[i] = [];
}
// 使用TreeWalker遍历文本节点
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
// 跳过参考文献区域本身
if (node.parentElement.closest('.reference-section')) {
return NodeFilter.FILTER_REJECT;
}
// 跳过已经处理过的引用链接
if (node.parentElement.classList?.contains('reference-citation')) {
return NodeFilter.FILTER_REJECT;
}
// 跳过注解系统创建的高亮span
if (node.parentElement.classList?.contains('annotated-block') ||
node.parentElement.classList?.contains('annotated-sub-block') ||
node.parentElement.classList?.contains('partial-subblock-highlight')) {
return NodeFilter.FILTER_REJECT;
}
// 跳过公式
let p = node.parentElement;
while (p) {
if (p.classList && (p.classList.contains('katex') ||
p.classList.contains('katex-display') ||
p.classList.contains('katex-inline'))) {
return NodeFilter.FILTER_REJECT;
}
p = p.parentElement;
}
return NodeFilter.FILTER_ACCEPT;
}
}
);
const textNodes = [];
let node;
while (node = walker.nextNode()) {
textNodes.push(node);
}
// 正则匹配引用标记:[1], [2,3], [1-5], [1~5] 等
// 支持的分隔符:逗号(,)、短横线(-)、en dash(–)、波浪号(~)
const citationPattern = /\[(\d+(?:\s*[-–,~]\s*\d+)*)\]/g;
textNodes.forEach(textNode => {
const text = textNode.textContent;
const matches = [];
let match;
// 收集所有匹配
while ((match = citationPattern.exec(text)) !== null) {
const numbers = parseReferenceNumbers(match[1]);
// 只处理有效的引用(编号在范围内)
const validNumbers = numbers.filter(num => num > 0 && num <= refCount);
if (validNumbers.length > 0) {
matches.push({
index: match.index,
length: match[0].length,
text: match[0],
numbers: validNumbers
});
}
}
// 如果有匹配,替换为链接
if (matches.length > 0) {
const parent = textNode.parentElement;
const fragment = document.createDocumentFragment();
let lastIndex = 0;
matches.forEach(m => {
// 添加前面的文本
if (m.index > lastIndex) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex, m.index)));
}
// 为整个引用创建一个链接(不管它包含多少个文献编号)
const firstRefNum = m.numbers[0];
const refIndex = firstRefNum - 1;
const occurrenceIndex = citationLocations[refIndex] ? citationLocations[refIndex].length : 0;
const citationId = `citation-source-${refIndex}-${occurrenceIndex}`;
// 创建引用链接
const link = document.createElement('a');
link.href = `#ref-${firstRefNum}`;
link.className = 'reference-citation';
link.id = citationId;
link.textContent = m.text; // 显示完整的引用文本,如 [1] 或 [1,2,3]
link.dataset.refIndex = refIndex;
// 保存0-based索引(用于数组访问)
link.dataset.refNumbers = m.numbers.map(num => num - 1).join(',');
// 不在这里绑定事件,而是使用事件委托
// 事件委托在setupCitationEventDelegation中设置
fragment.appendChild(link);
console.log('[markCitationsInContent] 创建链接:', citationId, '文本:', m.text, 'refIndex:', refIndex);
// 记录所有引用的文献的位置
m.numbers.forEach(num => {
const idx = num - 1;
if (!citationLocations[idx]) {
citationLocations[idx] = [];
}
citationLocations[idx].push(citationId);
});
lastIndex = m.index + m.length;
});
// 添加剩余的文本
if (lastIndex < text.length) {
fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
}
// 替换原文本节点
parent.replaceChild(fragment, textNode);
console.log('[markCitationsInContent] 替换了', matches.length, '个引用链接');
}
});
console.log('[markCitationsInContent] 已标记原文中的引用', citationLocations);
// 验证链接是否真的在DOM中
setTimeout(() => {
const allLinks = container.querySelectorAll('.reference-citation');
console.log('[markCitationsInContent] DOM中的引用链接数量:', allLinks.length);
if (allLinks.length > 0) {
console.log('[markCitationsInContent] 第一个链接样例:', allLinks[0], '文本:', allLinks[0].textContent);
console.log('[markCitationsInContent] 第一个链接的计算样式 color:', window.getComputedStyle(allLinks[0]).color);
console.log('[markCitationsInContent] 第一个链接的计算样式 cursor:', window.getComputedStyle(allLinks[0]).cursor);
}
}, 100);
}
/**
* 解析引用编号字符串
*/
function parseReferenceNumbers(numbersStr) {
const result = [];
const parts = numbersStr.split(/\s*,\s*/);
parts.forEach(part => {
// 检查是否是范围(如 2-5, 2~5, 2–5)
const rangeMatch = part.match(/(\d+)\s*[-–~]\s*(\d+)/);
if (rangeMatch) {
const start = parseInt(rangeMatch[1]);
const end = parseInt(rangeMatch[2]);
for (let i = start; i <= end; i++) {
result.push(i);
}
} else {
const num = parseInt(part);
if (!isNaN(num)) {
result.push(num);
}
}
});
return result;
}
/**
* 滚动到指定的文献条目
*/
function scrollToReferenceItem(index) {
const refItem = document.querySelector(`[data-ref-id="ref-${index + 1}"]`);
if (refItem) {
refItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 添加高亮动画
refItem.classList.add('reference-item-highlight');
setTimeout(() => {
refItem.classList.remove('reference-item-highlight');
}, 3000);
console.log('[scrollToReferenceItem] 已定位到文献:', index + 1);
} else {
console.warn('[scrollToReferenceItem] 未找到文献:', index + 1);
}
}
/**
* 显示引用详细悬浮卡片(改进版,显示更多信息)
* @param {HTMLElement} linkElement - 引用链接元素
* @param {string} refNumbersStr - 文献编号字符串,如 "0,1,2" (0-based索引)
*/
function showReferenceDetailTooltip(linkElement, refNumbersStr) {
// 解析文献编号
const refIndices = refNumbersStr.split(',').map(n => parseInt(n.trim(), 10)).filter(n => !isNaN(n));
console.log('[showReferenceDetailTooltip] 开始显示tooltip,refIndices:', refIndices, 'currentReferences.length:', currentReferences.length);
if (refIndices.length === 0) {
console.warn('[showReferenceDetailTooltip] 没有有效的文献编号');
return;
}
// 记录当前激活的链接
activeTooltipLinkElement = linkElement;
// 创建或获取tooltip元素
let tooltip = document.getElementById('reference-detail-tooltip');
if (!tooltip) {
tooltip = document.createElement('div');
tooltip.id = 'reference-detail-tooltip';
tooltip.className = 'reference-detail-tooltip';
document.body.appendChild(tooltip);
// 鼠标移出tooltip时隐藏
tooltip.addEventListener('mouseleave', () => {
console.log('[tooltip mouseleave] 触发');
hideTooltipTimer = setTimeout(() => {
console.log('[tooltip mouseleave timer] 准备隐藏');
hideReferenceDetailTooltip();
}, 100);
});
// 鼠标进入tooltip时取消隐藏
tooltip.addEventListener('mouseenter', () => {
console.log('[tooltip mouseenter] 取消隐藏定时器');
if (hideTooltipTimer) {
clearTimeout(hideTooltipTimer);
hideTooltipTimer = null;
}
});
}
// 构建多个文献的详细内容
let contentHTML = '';
if (refIndices.length === 1) {
// 单个文献,显示完整信息
const refIndex = refIndices[0];
const ref = currentReferences[refIndex];
if (!ref) {
console.warn('[showReferenceDetailTooltip] 未找到参考文献数据,refIndex:', refIndex);
return;
}
const authors = ref.authors && ref.authors.length > 0
? ref.authors.join(', ')
: '作者未知';
const citationCount = citationLocations[refIndex] ? citationLocations[refIndex].length : 0;
contentHTML = `
${ref.title ? `
` : '
'}
${authors}
${ref.year ? ` ${ref.year}` : ''}
${ref.journal ? ` ${ref.journal}` : ''}
${ref.volume ? `Vol. ${ref.volume}` : ''}
${ref.abstract ? `
` : ''}
${ref.doi ? `
` : ''}
${citationCount > 0 ? `
本文引用 ${citationCount} 次
` : ''}
`;
} else {
// 多个文献,显示简化列表
const refNumbersDisplay = refIndices.map(i => i + 1).join(', ');
contentHTML = `
共 ${refIndices.length} 篇文献,点击展开查看详情
`;
}
tooltip.innerHTML = contentHTML;
// 先移除show类(重置状态)
const wasVisible = tooltip.classList.contains('show');
tooltip.classList.remove('show');
console.log('[showReferenceDetailTooltip] 重置show类,之前是否可见:', wasVisible);
// 使用requestAnimationFrame确保布局完成后再定位
requestAnimationFrame(() => {
// 获取链接和tooltip的位置信息
const linkRect = linkElement.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
// 默认显示在链接右侧
let top = linkRect.top + window.scrollY;
let left = linkRect.right + window.scrollX + 10;
// 如果右侧空间不足,显示在左侧
if (left + tooltipRect.width > window.innerWidth - 20) {
left = linkRect.left + window.scrollX - tooltipRect.width - 10;
}
// 如果左侧也不够,显示在下方
if (left < 20) {
left = linkRect.left + window.scrollX;
top = linkRect.bottom + window.scrollY + 10;
}
// 防止tooltip超出视口顶部
if (top < window.scrollY + 20) {
top = window.scrollY + 20;
}
// 防止tooltip超出视口底部
if (top + tooltipRect.height > window.scrollY + window.innerHeight - 20) {
top = window.scrollY + window.innerHeight - tooltipRect.height - 20;
}
// 设置位置
tooltip.style.top = top + 'px';
tooltip.style.left = left + 'px';
// 下一帧添加show类,触发过渡动画
requestAnimationFrame(() => {
tooltip.classList.add('show');
});
console.log('[showReferenceDetailTooltip] 显示详细tooltip,文献编号:', refIndices.map(i => i + 1).join(','));
});
}
/**
* 隐藏详细悬浮卡片
*/
function hideReferenceDetailTooltip() {
const tooltip = document.getElementById('reference-detail-tooltip');
if (tooltip) {
tooltip.classList.remove('show');
activeTooltipLinkElement = null;
}
if (hideTooltipTimer) {
clearTimeout(hideTooltipTimer);
hideTooltipTimer = null;
}
console.log('[hideReferenceDetailTooltip] 隐藏tooltip');
}
/**
* 切换文献详情的展开/收起状态
*/
global.toggleReferenceDetail = function(refIndex) {
const item = document.querySelector(`.tooltip-ref-item[data-ref-index="${refIndex}"]`);
if (!item) return;
const detail = item.querySelector('.tooltip-ref-detail');
const toggle = item.querySelector('.tooltip-ref-toggle');
if (!detail || !toggle) return;
if (detail.style.display === 'none') {
// 展开
detail.style.display = 'block';
toggle.classList.add('expanded');
item.classList.add('expanded');
// 平滑滚动到该项
setTimeout(() => {
item.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
} else {
// 收起
detail.style.display = 'none';
toggle.classList.remove('expanded');
item.classList.remove('expanded');
}
};
/**
* 显示参考文献详细卡片(模态框)
*/
function showReferenceDetailCard(refIndex) {
if (!currentReferences[refIndex]) return;
const ref = currentReferences[refIndex];
// 先隐藏tooltip
hideReferenceTooltip();
// 创建或获取模态框
let modal = document.getElementById('reference-detail-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'reference-detail-modal';
modal.className = 'reference-detail-modal';
document.body.appendChild(modal);
// 点击背景关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('show');
}
});
}
// 构建详细内容
const authors = ref.authors && ref.authors.length > 0
? ref.authors.join(', ')
: '作者未知';
const citationCount = citationLocations[refIndex] ? citationLocations[refIndex].length : 0;
modal.innerHTML = `
${ref.title ? `
${ref.title}
` : '
未提取标题
'}
${ref.volume || ref.issue || ref.pages ? `
${ref.volume ? `Vol. ${ref.volume}` : ''}
${ref.issue ? `No. ${ref.issue}` : ''}
${ref.pages ? `pp. ${ref.pages}` : ''}
` : ''}
${ref.abstract ? `
` : ''}
${ref.doi ? `
` : ''}
${citationCount > 0 ? `
本文引用此文献 ${citationCount} 次
` : ''}
${ref.doi ? `
打开DOI链接
` : ''}
`;
// 显示模态框
requestAnimationFrame(() => {
modal.classList.add('show');
});
console.log('[showReferenceDetailCard] 显示详细卡片:', refIndex + 1);
}
/**
* 隐藏引用悬浮卡片
*/
function hideReferenceTooltip() {
const tooltip = document.getElementById('reference-citation-tooltip');
if (tooltip) {
tooltip.classList.remove('show');
}
}
/**
* 跳转到原文中的引用位置
*/
function scrollToCitationInText(refIndex) {
const citationIds = citationLocations[refIndex];
if (!citationIds || citationIds.length === 0) {
console.warn('[scrollToCitationInText] 未找到引用位置:', refIndex);
alert('未找到该文献在原文中的引用位置');
return;
}
// 跳转到第一个引用位置
const firstCitationId = citationIds[0];
const citationElement = document.getElementById(firstCitationId);
if (citationElement) {
citationElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 添加临时高亮
citationElement.style.backgroundColor = '#fff3cd';
citationElement.style.padding = '2px 4px';
citationElement.style.borderRadius = '3px';
setTimeout(() => {
citationElement.style.backgroundColor = '';
citationElement.style.padding = '';
citationElement.style.borderRadius = '';
}, 2000);
console.log('[scrollToCitationInText] 已跳转到引用位置:', firstCitationId);
} else {
console.warn('[scrollToCitationInText] 未找到引用元素:', firstCitationId);
}
}
// 暴露给全局,供HTML按钮调用
global.scrollToCitationInText = scrollToCitationInText;
/**
* 添加到TOC - 点击打开悬浮面板
*/
function addToTOC() {
const tocList = document.getElementById('toc-list');
if (!tocList) return;
// 移除已存在的文献链接
const existing = tocList.querySelector('.toc-reference-link');
if (existing) {
existing.remove();
}
// 添加新链接(点击打开悬浮面板)
const li = document.createElement('li');
li.className = 'toc-reference-link';
li.innerHTML = `
参考文献 (${currentReferences.length})
`;
tocList.appendChild(li);
}
/**
* 更新文献计数
*/
function updateReferenceCount(count) {
const countEl = document.getElementById('reference-count');
if (countEl) {
countEl.textContent = count;
} else {
// 如果元素还不存在,延迟重试
console.warn('[ReferenceManagerDetail] reference-count element not found, retrying...');
setTimeout(() => {
const retryCountEl = document.getElementById('reference-count');
if (retryCountEl) {
retryCountEl.textContent = count;
}
}, 500);
}
}
/**
* 创建悬浮面板(类似chatbot)
*/
function createFloatingPanel() {
const panel = document.createElement('div');
panel.id = 'reference-floating-panel';
panel.className = 'reference-floating-panel';
panel.style.display = 'none';
panel.innerHTML = `
`;
document.body.appendChild(panel);
// 使面板可拖拽
makePanelDraggable(panel);
}
/**
* 使面板可拖拽
*/
function makePanelDraggable(panel) {
const header = panel.querySelector('.reference-panel-header');
let isDragging = false;
let currentX, currentY, initialX, initialY;
header.addEventListener('mousedown', (e) => {
if (e.target.closest('button')) return;
isDragging = true;
// 在拖动开始前,将bottom定位转换为top定位
if (panel.style.bottom || getComputedStyle(panel).bottom !== 'auto') {
const rect = panel.getBoundingClientRect();
panel.style.top = rect.top + 'px';
panel.style.left = rect.left + 'px';
panel.style.bottom = 'auto';
panel.style.right = 'auto';
}
initialX = e.clientX - panel.offsetLeft;
initialY = e.clientY - panel.offsetTop;
header.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// 限制面板在视口内
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
currentX = Math.max(0, Math.min(currentX, maxX));
currentY = Math.max(0, Math.min(currentY, maxY));
panel.style.left = currentX + 'px';
panel.style.top = currentY + 'px';
});
document.addEventListener('mouseup', () => {
isDragging = false;
header.style.cursor = 'grab';
});
}
/**
* 切换悬浮面板
*/
function toggleFloatingPanel() {
const panel = document.getElementById('reference-floating-panel');
if (!panel) return;
if (isFloatingPanelOpen) {
panel.style.display = 'none';
isFloatingPanelOpen = false;
} else {
panel.style.display = 'flex';
isFloatingPanelOpen = true;
// 打开时重新加载数据(如果还没有加载)
if (currentReferences.length === 0) {
const data = global.ReferenceStorage?.loadReferences(currentDocumentId);
if (data && data.references) {
currentReferences = data.references;
console.log('[toggleFloatingPanel] 重新加载文献数据:', currentReferences.length);
}
}
updatePanelContent();
}
}
/**
* 更新面板内容
*/
function updatePanelContent() {
const content = document.getElementById('reference-panel-content');
if (!content) return;
if (currentReferences.length === 0) {
content.innerHTML = `
`;
} else {
content.innerHTML = `
${renderPanelList(currentReferences)}
`;
}
}
/**
* 获取当前Markdown内容
*/
async function getCurrentMarkdownContent() {
const active = window.globalCurrentContentIdentifier || window.currentVisibleTabId || 'ocr';
// 优先:根据当前可见内容获取(translation 优先使用译文)
if (window.data) {
if (active === 'translation' && typeof window.data.translation === 'string' && window.data.translation.length > 0) {
console.log('[ReferenceManagerDetail] 使用 window.data.translation,长度:', window.data.translation.length);
return window.data.translation;
}
if (typeof window.data.ocr === 'string' && window.data.ocr.length > 0) {
console.log('[ReferenceManagerDetail] 使用 window.data.ocr,长度:', window.data.ocr.length);
return window.data.ocr;
}
// 备用:从分块重建
if (Array.isArray(window.data.ocrChunks) && window.data.ocrChunks.length > 0) {
const joined = window.data.ocrChunks.filter(Boolean).join('\n\n');
if (joined && joined.trim().length > 0) {
console.log('[ReferenceManagerDetail] 使用 window.data.ocrChunks 重建内容,块数:', window.data.ocrChunks.length);
return joined;
}
}
}
// 方式:历史数据(若存在)
if (window.currentHistoryData && window.currentHistoryData.ocrResult) {
console.log('[ReferenceManagerDetail] 使用 currentHistoryData.ocrResult');
return window.currentHistoryData.ocrResult;
}
// 方式:从DOM中的文本内容获取(依据当前标签)
const selector = active === 'translation'
? '#translation-content-wrapper'
: '#tab-ocr-content, #ocr-content-wrapper';
const contentEl = document.querySelector(selector) || document.querySelector('#tabContent .markdown-body');
if (contentEl && contentEl.textContent && contentEl.textContent.trim()) {
console.log('[ReferenceManagerDetail] 使用 DOM textContent, selector:', selector);
return contentEl.textContent;
}
console.error('[ReferenceManagerDetail] 无法获取文档内容,尝试的方法:', {
active,
hasWindowData: !!window.data,
hasOcr: !!(window.data && window.data.ocr),
hasTranslation: !!(window.data && window.data.translation),
hasOcrChunks: !!(window.data && Array.isArray(window.data.ocrChunks) && window.data.ocrChunks.length > 0),
contentReady: !!window.contentReady,
hasCurrentHistoryData: !!window.currentHistoryData,
hasDOMContent: !!document.querySelector(selector) || !!document.querySelector('#tabContent .markdown-body')
});
return null;
}
/**
* 全局函数:切换面板
*/
global.toggleReferencePanel = function() {
toggleFloatingPanel();
};
/**
* 全局函数:提取文献
*/
global.extractReferencesFromContent = async function() {
const markdown = await getCurrentMarkdownContent();
if (!markdown) {
alert('无法获取文档内容');
return;
}
const section = global.ReferenceDetector?.detectReferenceSection(markdown);
if (!section) {
alert('未检测到参考文献部分');
return;
}
// 使用统一的提取方式选择对话框
await showExtractionMethodDialog(section, markdown);
};
/**
* 保存提取的文献
*/
function saveExtractedReferences(references, markdown) {
// 建立索引
let indexed = references;
if (markdown && global.ReferenceIndexer) {
indexed = global.ReferenceIndexer.buildIndex(
currentDocumentId,
markdown,
references
);
}
// 保存
global.ReferenceStorage?.saveReferences(
currentDocumentId,
indexed,
{
extractedAt: new Date().toISOString(),
method: 'hybrid'
}
);
// 显示
currentReferences = indexed;
updateReferenceCount(indexed.length);
appendReferencesToContent(indexed);
addToTOC();
updatePanelContent();
}
/**
* 使用AI处理文献
*/
async function processWithAI(extracted, markdown) {
// 获取API配置(使用与Chatbot相同的方式)
const apiConfig = await getAPIConfig();
if (!apiConfig) {
return;
}
console.log('[ReferenceManagerDetail] 开始AI批量处理,总数:', extracted.length);
try {
// 提取原始文本
const rawTexts = extracted.map(e => e.rawText || (typeof e === 'string' ? e : ''));
// 使用批量处理API
const processed = await global.ReferenceAIProcessor.batchProcessReferences(
rawTexts,
apiConfig,
'auto',
(progress) => {
const percent = Math.round((progress.processed / progress.total) * 100);
console.log(`[AI处理] ${progress.processed}/${progress.total} (${percent}%) - 批次 ${progress.batchIndex + 1}/${progress.totalBatches}`);
}
);
// 合并原始信息
const finalReferences = processed.map((ref, idx) => ({
...ref,
index: idx,
rawText: rawTexts[idx],
extractedBy: 'ai',
confidence: 0.9
}));
saveExtractedReferences(finalReferences, markdown);
alert(`AI处理完成\n共提取 ${finalReferences.length} 条文献`);
} catch (error) {
console.error('[ReferenceManagerDetail] AI处理失败:', error);
alert('AI处理失败: ' + error.message + '\n\n请检查模型配置和API Key');
}
}
/**
* 获取API配置(使用与Chatbot相同的方式)
*/
async function getAPIConfig() {
// 使用Chatbot的配置获取函数
if (typeof window.MessageSender?.getChatbotConfig === 'function') {
const config = window.MessageSender.getChatbotConfig();
if (!config || !config.apiKey) {
alert('请先配置AI模型和API Key\n\n提示:打开Chatbot设置配置模型');
return null;
}
console.log('[ReferenceManagerDetail] 获取到配置:', {
model: config.model,
hasApiKey: !!config.apiKey,
cms: config.cms
});
// 如果是自定义模型,使用Chatbot的buildCustomApiConfig
if (config.model === 'custom' || config.model.startsWith('custom_source_')) {
const endpoint = config.cms.apiEndpoint || config.cms.apiBaseUrl;
if (!endpoint || !config.cms.modelId) {
alert('自定义模型配置不完整');
return null;
}
// 使用Chatbot的buildCustomApiConfig函数(保证一致性)
if (typeof window.ApiConfigBuilder?.buildCustomApiConfig === 'function') {
const builtConfig = window.ApiConfigBuilder.buildCustomApiConfig(
config.apiKey,
endpoint,
config.cms.modelId || config.cms.preferredModelId,
config.cms.requestFormat,
parseFloat(config.cms.temperature) || 0.1,
parseInt(config.cms.max_tokens) || 4000,
{
endpointMode: config.cms.endpointMode || 'auto'
}
);
console.log('[ReferenceManagerDetail] 使用buildCustomApiConfig构建的配置:', builtConfig);
return builtConfig;
}
console.error('[ReferenceManagerDetail] buildCustomApiConfig函数不可用');
return null;
}
// 预设模型
return global.ReferenceAIProcessor.buildAPIConfig(config.model, config.apiKey);
}
alert('无法获取AI配置,请确保Chatbot模块已加载');
return null;
}
/**
* 全局函数:编辑文献
*/
global.editReference = function(index) {
// 打开完整管理器并定位到该文献
if (global.ReferenceManagerUI) {
global.ReferenceManagerUI.show(currentDocumentId);
// TODO: 定位到特定文献
}
};
/**
* 全局函数:显示完整管理器
*/
global.showFullReferenceManager = function() {
if (global.ReferenceManagerUI) {
global.ReferenceManagerUI.show(currentDocumentId);
}
};
/**
* 全局函数:导出文献
*/
global.exportReferences = function() {
const format = prompt('选择导出格式:\n1. BibTeX\n2. JSON\n3. CSV\n\n请输入数字:');
if (!format) return;
let content = '';
let filename = '';
switch (format) {
case '1':
content = global.ReferenceStorage?.exportToBibTeX(currentDocumentId) || '';
filename = 'references.bib';
break;
case '2':
content = global.ReferenceStorage?.exportToJSON(currentDocumentId) || '';
filename = 'references.json';
break;
case '3':
content = exportToCSV();
filename = 'references.csv';
break;
default:
alert('无效的选择');
return;
}
if (content) {
downloadFile(content, filename);
}
};
/**
* 导出为CSV
*/
function exportToCSV() {
const headers = ['Index', 'Authors', 'Title', 'Year', 'Journal', 'DOI'];
const rows = currentReferences.map((ref, idx) => [
idx + 1,
(ref.authors || []).join('; '),
ref.title || '',
ref.year || '',
ref.journal || '',
ref.doi || (ref.doiFallback ? '(未找到)' : '')
]);
const csv = [headers, ...rows].map(row =>
row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')
).join('\n');
return csv;
}
/**
* 下载文件
*/
function downloadFile(content, filename) {
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initReferenceManagerForDetail);
} else {
initReferenceManagerForDetail();
}
console.log('[ReferenceManagerDetail] Module loaded. v1.0.1 - Fixed refIndex error');
})(window);