paper-burner/js/ui/reference-manager-detail.js

1714 lines
68 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 = `
<div class="ref-panel-placeholder">
<i class="fa fa-book fa-3x"></i>
<p>暂无文献数据</p>
<button onclick="window.extractReferencesFromContent()">提取文献</button>
</div>
`;
} else {
content.innerHTML = `
<div class="ref-panel-list">
${renderPanelList(currentReferences)}
</div>
`;
}
}
/**
* 渲染面板列表
*/
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 `
<div class="ref-panel-item">
<div class="ref-panel-header">
<div class="ref-panel-number">[${idx + 1}]</div>
<div class="ref-panel-title">${ref.title || '未提取标题'}</div>
</div>
<div class="ref-panel-meta">
<div class="ref-panel-authors">${authors}</div>
${ref.year ? `<span class="ref-panel-year">${ref.year}</span>` : ''}
${ref.journal ? `<span class="ref-panel-journal">${ref.journal}</span>` : ''}
</div>
${citationCount > 0 ? `
<div class="ref-panel-citations">
<i class="fa fa-quote-left"></i> 引用 ${citationCount}
</div>
` : ''}
<div class="ref-panel-actions">
<button class="ref-panel-action-btn" onclick="window.scrollToCitationInText(${idx})" title="跳转到原文">
<i class="fa fa-arrow-up"></i> 原文
</button>
<button class="ref-panel-action-btn" onclick="window.scrollToReferenceItem(${idx})" title="查看详情">
<i class="fa fa-eye"></i> 详情
</button>
${ref.doi ? `
<a href="https://doi.org/${ref.doi}" target="_blank" class="ref-panel-action-btn" title="打开DOI">
<i class="fa fa-external-link"></i> DOI
</a>
` : ''}
</div>
</div>
`;
}).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, 25
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] 开始显示tooltiprefIndices:', 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 = `
<div class="tooltip-detail-header">
<span class="tooltip-detail-number">[${refIndex + 1}]</span>
<button class="tooltip-detail-close" onclick="document.getElementById('reference-detail-tooltip').classList.remove('show')">
<i class="fa fa-times"></i>
</button>
</div>
<div class="tooltip-detail-content">
${ref.title ? `<h4 class="tooltip-detail-title">${ref.title}</h4>` : '<h4 class="tooltip-detail-title">未提取标题</h4>'}
<div class="tooltip-detail-authors">
<i class="fa fa-user"></i> ${authors}
</div>
<div class="tooltip-detail-meta">
${ref.year ? `<span><i class="fa fa-calendar"></i> ${ref.year}</span>` : ''}
${ref.journal ? `<span><i class="fa fa-book"></i> ${ref.journal}</span>` : ''}
${ref.volume ? `<span>Vol. ${ref.volume}</span>` : ''}
</div>
${ref.abstract ? `
<div class="tooltip-detail-abstract">
<strong>摘要:</strong>
<p>${ref.abstract}</p>
</div>
` : ''}
${ref.doi ? `
<div class="tooltip-detail-doi">
<strong>DOI:</strong>
<a href="https://doi.org/${ref.doi}" target="_blank">${ref.doi} <i class="fa fa-external-link"></i></a>
</div>
` : ''}
${citationCount > 0 ? `
<div class="tooltip-detail-citations">
<i class="fa fa-quote-left"></i> 本文引用 <strong>${citationCount}</strong> 次
</div>
` : ''}
</div>
<div class="tooltip-detail-actions">
<button class="tooltip-action-btn" onclick="window.scrollToCitationInText(${refIndex}); document.getElementById('reference-detail-tooltip').classList.remove('show');">
<i class="fa fa-arrow-up"></i> 跳转引用
</button>
<button class="tooltip-action-btn" onclick="window.scrollToReferenceItem(${refIndex}); document.getElementById('reference-detail-tooltip').classList.remove('show');">
<i class="fa fa-list"></i> 查看详情
</button>
</div>
`;
} else {
// 多个文献,显示简化列表
const refNumbersDisplay = refIndices.map(i => i + 1).join(', ');
contentHTML = `
<div class="tooltip-detail-header">
<span class="tooltip-detail-number">[${refNumbersDisplay}]</span>
<button class="tooltip-detail-close" onclick="document.getElementById('reference-detail-tooltip').classList.remove('show')">
<i class="fa fa-times"></i>
</button>
</div>
<div class="tooltip-detail-content" style="padding: 12px 14px;">
<div style="margin-bottom: 8px; color: #64748b; font-size: 12px;">
<i class="fa fa-info-circle"></i> 共 ${refIndices.length} 篇文献,点击展开查看详情
</div>
<div class="tooltip-multiple-refs">
${refIndices.map(refIndex => {
const ref = currentReferences[refIndex];
if (!ref) return '';
const authors = ref.authors && ref.authors.length > 0
? ref.authors.slice(0, 2).join(', ') + (ref.authors.length > 2 ? ' 等' : '')
: '作者未知';
const allAuthors = ref.authors && ref.authors.length > 0
? ref.authors.join(', ')
: '作者未知';
const citationCount = citationLocations[refIndex] ? citationLocations[refIndex].length : 0;
return `
<div class="tooltip-ref-item" data-ref-index="${refIndex}">
<div class="tooltip-ref-header" onclick="window.toggleReferenceDetail(${refIndex})">
<div class="tooltip-ref-number">[${refIndex + 1}]</div>
<div class="tooltip-ref-info">
<div class="tooltip-ref-title">${ref.title || '未提取标题'}</div>
<div class="tooltip-ref-authors">${authors}${ref.year ? ` · ${ref.year}` : ''}</div>
</div>
<i class="fa fa-chevron-down tooltip-ref-toggle"></i>
</div>
<div class="tooltip-ref-detail" style="display: none;">
<div class="tooltip-ref-detail-section">
<strong><i class="fa fa-user"></i> 作者:</strong>
<span>${allAuthors}</span>
</div>
${ref.year ? `
<div class="tooltip-ref-detail-section">
<strong><i class="fa fa-calendar"></i> 年份:</strong>
<span>${ref.year}</span>
</div>
` : ''}
${ref.journal ? `
<div class="tooltip-ref-detail-section">
<strong><i class="fa fa-book"></i> 期刊:</strong>
<span>${ref.journal}${ref.volume ? ` Vol. ${ref.volume}` : ''}</span>
</div>
` : ''}
${ref.abstract ? `
<div class="tooltip-ref-detail-section">
<strong><i class="fa fa-file-text-o"></i> 摘要:</strong>
<p style="margin: 4px 0 0 0; line-height: 1.4; color: #475569;">${ref.abstract}</p>
</div>
` : ''}
${ref.doi ? `
<div class="tooltip-ref-detail-section">
<strong><i class="fa fa-link"></i> DOI</strong>
<a href="https://doi.org/${ref.doi}" target="_blank" style="color: #3b82f6; text-decoration: none;">
${ref.doi} <i class="fa fa-external-link" style="font-size: 10px;"></i>
</a>
</div>
` : ''}
${citationCount > 0 ? `
<div class="tooltip-ref-detail-section">
<strong><i class="fa fa-quote-left"></i> 引用:</strong>
<span>本文引用 ${citationCount} 次</span>
</div>
` : ''}
<div class="tooltip-ref-detail-actions">
<button onclick="window.scrollToCitationInText(${refIndex}); event.stopPropagation();" class="tooltip-ref-action-btn">
<i class="fa fa-arrow-up"></i> 跳转引用
</button>
<button onclick="window.scrollToReferenceItem(${refIndex}); document.getElementById('reference-detail-tooltip').classList.remove('show'); event.stopPropagation();" class="tooltip-ref-action-btn">
<i class="fa fa-list"></i> 查看原文
</button>
</div>
</div>
</div>
`;
}).join('')}
</div>
</div>
`;
}
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 = `
<div class="reference-detail-card">
<div class="reference-detail-header">
<div class="reference-detail-number">[${refIndex + 1}]</div>
<button class="reference-detail-close" onclick="document.getElementById('reference-detail-modal').classList.remove('show')">
<i class="fa fa-times"></i>
</button>
</div>
<div class="reference-detail-content">
${ref.title ? `<h3 class="reference-detail-title">${ref.title}</h3>` : '<h3 class="reference-detail-title">未提取标题</h3>'}
<div class="reference-detail-meta">
<div class="reference-detail-authors">
<i class="fa fa-user"></i> ${authors}
</div>
${ref.year ? `<div class="reference-detail-year"><i class="fa fa-calendar"></i> ${ref.year}</div>` : ''}
${ref.journal ? `<div class="reference-detail-journal"><i class="fa fa-book"></i> ${ref.journal}</div>` : ''}
</div>
${ref.volume || ref.issue || ref.pages ? `
<div class="reference-detail-publication">
${ref.volume ? `<span>Vol. ${ref.volume}</span>` : ''}
${ref.issue ? `<span>No. ${ref.issue}</span>` : ''}
${ref.pages ? `<span>pp. ${ref.pages}</span>` : ''}
</div>
` : ''}
${ref.abstract ? `
<div class="reference-detail-abstract">
<h4><i class="fa fa-file-text"></i> 摘要</h4>
<p>${ref.abstract}</p>
</div>
` : ''}
${ref.doi ? `
<div class="reference-detail-doi">
<strong>DOI:</strong>
<a href="https://doi.org/${ref.doi}" target="_blank">${ref.doi} <i class="fa fa-external-link"></i></a>
</div>
` : ''}
${citationCount > 0 ? `
<div class="reference-detail-citations">
<i class="fa fa-quote-left"></i> 本文引用此文献 <strong>${citationCount}</strong> 次
</div>
` : ''}
</div>
<div class="reference-detail-actions">
<button class="ref-detail-btn" onclick="window.scrollToCitationInText(${refIndex})">
<i class="fa fa-arrow-up"></i> 跳转到原文引用
</button>
<button class="ref-detail-btn" onclick="window.scrollToReferenceItem(${refIndex})">
<i class="fa fa-list"></i> 查看References区域
</button>
${ref.doi ? `
<a href="https://doi.org/${ref.doi}" target="_blank" class="ref-detail-btn ref-detail-btn-primary">
<i class="fa fa-external-link"></i> 打开DOI链接
</a>
` : ''}
</div>
</div>
`;
// 显示模态框
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 = `
<a href="#" class="toc-ref-link" onclick="window.toggleReferencePanel(); return false;">
<i class="fa fa-book"></i> 参考文献 (${currentReferences.length})
</a>
`;
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 = `
<div class="reference-panel-header">
<h3><i class="fa fa-book"></i> 参考文献管理</h3>
<div class="reference-panel-actions">
<button class="ref-panel-minimize" onclick="window.toggleReferencePanel()">
<i class="fa fa-minus"></i>
</button>
<button class="ref-panel-close" onclick="window.toggleReferencePanel()">
<i class="fa fa-times"></i>
</button>
</div>
</div>
<div class="reference-panel-toolbar">
<button class="ref-toolbar-btn" onclick="window.extractReferencesFromContent()">
<i class="fa fa-sync"></i> 提取
</button>
<button class="ref-toolbar-btn" onclick="window.showFullReferenceManager()">
<i class="fa fa-th"></i> 完整管理
</button>
<button class="ref-toolbar-btn" onclick="window.exportReferences()">
<i class="fa fa-download"></i> 导出
</button>
</div>
<div class="reference-panel-content" id="reference-panel-content">
<div class="ref-panel-placeholder">
<i class="fa fa-book fa-3x"></i>
<p>暂无文献数据</p>
<button onclick="window.extractReferencesFromContent()">提取文献</button>
</div>
</div>
`;
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 = `
<div class="ref-panel-placeholder">
<i class="fa fa-book fa-3x"></i>
<p>暂无文献数据</p>
<button onclick="window.extractReferencesFromContent()">提取文献</button>
</div>
`;
} else {
content.innerHTML = `
<div class="ref-panel-list">
${renderPanelList(currentReferences)}
</div>
`;
}
}
/**
* 获取当前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);