const annotationsSummaryModal = document.getElementById('annotations-summary-modal'); const annotationsSummaryCloseBtn = document.getElementById('annotations-summary-close-btn'); const annotationsFilterTypeSelect = document.getElementById('annotations-filter-type'); const annotationsFilterContentSelect = document.getElementById('annotations-filter-content'); const annotationsSummaryTableBody = document.getElementById('annotations-summary-table-body'); const annotationsSummaryColorFilter = document.getElementById('annotations-summary-color-filter'); // 获取所有出现过的高亮颜色 function getAllHighlightColors() { if (!window.data || !window.data.annotations) return []; const colorSet = new Set(); window.data.annotations.forEach(ann => { if (ann.highlightColor) colorSet.add(ann.highlightColor); }); return Array.from(colorSet); } // 渲染颜色多选区 function renderColorFilter(selectedColors) { const allColors = getAllHighlightColors(); annotationsSummaryColorFilter.innerHTML = ''; if (allColors.length === 0) return; allColors.forEach(color => { const label = document.createElement('label'); label.className = 'annotations-summary-color-checkbox'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.value = color; checkbox.checked = selectedColors.includes(color); checkbox.addEventListener('change', () => { // 重新渲染表格 const checkedColors = Array.from(annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); populateAnnotationsSummaryTable( annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, checkedColors ); }); const swatch = document.createElement('span'); swatch.className = 'annotations-summary-color-swatch'; swatch.style.backgroundColor = typeof getHighlightColor === 'function' ? getHighlightColor(color) : color; label.appendChild(checkbox); label.appendChild(swatch); label.appendChild(document.createTextNode(' ')); annotationsSummaryColorFilter.appendChild(label); }); } // 主表格渲染函数,增加颜色筛选参数 function populateAnnotationsSummaryTable(typeFilter = 'all', contentFilter = 'all', colorFilter = null) { console.log('[调试] populateAnnotationsSummaryTable called, data:', window.data, 'tocStructure:', (typeof window.getCurrentTocStructure === 'function') ? window.getCurrentTocStructure() : null); if (!window.data || !window.data.annotations || !annotationsSummaryTableBody) { annotationsSummaryTableBody.innerHTML = '暂无数据或批注未加载。'; return; } // 默认全选所有颜色 let allColors = getAllHighlightColors(); if (!colorFilter) colorFilter = allColors; renderColorFilter(colorFilter); // 清空表格内容,防止重复 annotationsSummaryTableBody.innerHTML = ''; // 对批注数据进行去重处理 // 创建一个 Map 用于检测重复的批注 const uniqueAnnotations = new Map(); // 遍历所有批注,按照目标标识符和内容类型进行去重 window.data.annotations.forEach(ann => { // 获取批注的唯一标识 let targetSelector = ann.target && ann.target.selector && ann.target.selector[0]; let uniqueKey = ''; if (targetSelector) { // 使用 subBlockId 或 blockIndex 作为唯一标识的一部分 if (targetSelector.subBlockId) { uniqueKey = `${ann.targetType}_subBlock_${targetSelector.subBlockId}`; } else if (targetSelector.blockIndex !== undefined) { uniqueKey = `${ann.targetType}_block_${targetSelector.blockIndex}`; } } // 如果有有效的唯一标识,则添加到 Map 中 if (uniqueKey) { // 如果是相同位置的批注,保留最新的(假设 id 越大越新) if (!uniqueAnnotations.has(uniqueKey) || (ann.id && uniqueAnnotations.get(uniqueKey).id && ann.id > uniqueAnnotations.get(uniqueKey).id)) { uniqueAnnotations.set(uniqueKey, ann); } } else { // 对于没有有效唯一标识的批注,使用 id 作为键 uniqueAnnotations.set(ann.id || _page_generateUUID(), ann); } }); // 将去重后的批注转换为数组并应用筛选条件 const filteredAnnotations = Array.from(uniqueAnnotations.values()).filter(ann => { const typeMatch = typeFilter === 'all' || ann.motivation === typeFilter; const contentMatch = contentFilter === 'all' || ann.targetType === contentFilter; const colorMatch = !ann.highlightColor || colorFilter.includes(ann.highlightColor); return typeMatch && contentMatch && colorMatch; }); // 按照位置排序(先按内容类型,再按块索引或子块ID) filteredAnnotations.sort((a, b) => { // 先按内容类型排序 if (a.targetType !== b.targetType) { return a.targetType === 'ocr' ? -1 : 1; } // 再按块索引或子块ID排序 const aSel = a.target && a.target.selector && a.target.selector[0]; const bSel = b.target && b.target.selector && b.target.selector[0]; if (aSel && bSel) { // 如果都有 blockIndex,按 blockIndex 排序 if (aSel.blockIndex !== undefined && bSel.blockIndex !== undefined) { return aSel.blockIndex - bSel.blockIndex; } // 如果都有 subBlockId,按 subBlockId 排序 if (aSel.subBlockId && bSel.subBlockId) { const aIds = aSel.subBlockId.split('.'); const bIds = bSel.subBlockId.split('.'); // 先比较主块索引 const aMainBlock = parseInt(aIds[0]); const bMainBlock = parseInt(bIds[0]); if (aMainBlock !== bMainBlock) { return aMainBlock - bMainBlock; } // 再比较子块索引 if (aIds.length > 1 && bIds.length > 1) { return parseInt(aIds[1]) - parseInt(bIds[1]); } } } // 默认按 id 排序(如果有) return (a.id || '') < (b.id || '') ? -1 : 1; }); // ========== TOC 分组渲染 =========== let tocStructure = (typeof window.getCurrentTocStructure === 'function') ? window.getCurrentTocStructure() : null; let grouped = {}; let ungrouped = []; // 辅助:递归查找批注属于哪个 TOC 节点,并返回完整路径 function findTocPathForAnnotation(tocNodes, blockIndex, subBlockId, path = []) { let found = null; for (let node of tocNodes) { // 判断 blockIndex 是否在本节点范围内 let inRange = false; if (blockIndex !== null && node.startBlockIndex !== undefined) { if (node.endBlockIndex !== null && node.endBlockIndex !== undefined) { inRange = blockIndex >= node.startBlockIndex && blockIndex <= node.endBlockIndex; } else { inRange = blockIndex >= node.startBlockIndex; } } // 判断 subBlockId 是否属于本节点(可扩展) // 这里只做 blockIndex 匹配,subBlockId 可按需扩展 if (inRange) { // 递归查找更小的子节点 let childFound = null; if (node.children && node.children.length > 0) { childFound = findTocPathForAnnotation(node.children, blockIndex, subBlockId, path.concat([node])); } if (childFound) return childFound; return path.concat([node]); } } return null; } if (tocStructure && tocStructure.children && tocStructure.children.length > 0) { console.log('[批注分组] TOC结构节点数:', tocStructure.children.length); filteredAnnotations.forEach(ann => { let targetSelector = ann.target && ann.target.selector && ann.target.selector[0]; let blockIndex = targetSelector && targetSelector.blockIndex !== undefined ? parseInt(targetSelector.blockIndex, 10) : null; let subBlockId = targetSelector && targetSelector.subBlockId ? String(targetSelector.subBlockId) : null; // 新增:如果 blockIndex 为空但 subBlockId 存在,自动用 subBlockId 的前半部分 if (blockIndex === null && subBlockId) { const parts = subBlockId.split('.'); if (parts.length > 0 && !isNaN(parseInt(parts[0], 10))) { blockIndex = parseInt(parts[0], 10); } } let tocPath = findTocPathForAnnotation(tocStructure.children, blockIndex, subBlockId); if (tocPath && tocPath.length > 0) { let groupKey = tocPath.map(n => n.text).join(' > '); if (!grouped[groupKey]) grouped[groupKey] = { path: tocPath, items: [] }; grouped[groupKey].items.push(ann); console.log(`[批注分组] blockIndex: ${blockIndex}, subBlockId: ${subBlockId}, 分组到: ${groupKey}`); } else { ungrouped.push(ann); console.log(`[批注分组] blockIndex: ${blockIndex}, subBlockId: ${subBlockId}, 未分组`); } }); } else { ungrouped = filteredAnnotations; } // ========== 渲染分组 =========== let groupCount = 0; for (let key in grouped) { if (!grouped[key].items.length) continue; groupCount += grouped[key].items.length; const groupRow = document.createElement('tr'); groupRow.className = 'toc-group'; // XSS 防护:使用 textContent 而不是 innerHTML // key 来自文档标题,可能包含恶意 HTML const td = document.createElement('td'); td.setAttribute('colspan', '7'); td.textContent = `${key}(${grouped[key].items.length})`; groupRow.appendChild(td); annotationsSummaryTableBody.appendChild(groupRow); grouped[key].items.forEach(ann => { const row = annotationsSummaryTableBody.insertRow(); row.insertCell().textContent = ann.motivation === 'commenting' ? '批注' : (ann.motivation === 'highlighting' ? '高亮' : ann.motivation); row.insertCell().textContent = ann.targetType ? ann.targetType.toUpperCase() : 'N/A'; let identifierText = 'N/A'; let targetSelector = ann.target && ann.target.selector && ann.target.selector[0]; if (targetSelector) { if (targetSelector.subBlockId) { identifierText = `子块: ${targetSelector.subBlockId}`; } else if (targetSelector.blockIndex !== undefined) { identifierText = `块: ${targetSelector.blockIndex}`; } } row.insertCell().textContent = identifierText; // 增强文本片段预览(优先原始 Markdown,再回退 exact) const textCell = row.insertCell(); let textSnippet = '[无文本片段]'; if (targetSelector) { // 优先:以所属块的原始 Markdown 作为预览 let preferredBlockIndex = null; if (targetSelector.blockIndex !== undefined && targetSelector.blockIndex !== null) { preferredBlockIndex = parseInt(targetSelector.blockIndex, 10); } else if (targetSelector.subBlockId) { const parts = String(targetSelector.subBlockId).split('.'); if (parts.length > 0 && !isNaN(parseInt(parts[0], 10))) preferredBlockIndex = parseInt(parts[0], 10); } if (preferredBlockIndex !== null && window.currentBlockTokensForCopy && window.currentBlockTokensForCopy[ann.targetType] && window.currentBlockTokensForCopy[ann.targetType][preferredBlockIndex] && typeof window.currentBlockTokensForCopy[ann.targetType][preferredBlockIndex].raw === 'string') { textSnippet = window.currentBlockTokensForCopy[ann.targetType][preferredBlockIndex].raw; } else if (targetSelector.exact) { // 回退:展示 exact(渲染后的文本片段) textSnippet = targetSelector.exact; } } // 限制显示长度,但保留更多内容 if (textSnippet && textSnippet.length > 150) { textCell.textContent = textSnippet.substring(0, 150) + '...'; textCell.title = textSnippet; // 鼠标悬停时显示完整内容 } else { textCell.textContent = textSnippet || '[无文本片段]'; } // 添加查看完整内容的功能 if (textSnippet && textSnippet.length > 50) { textCell.style.cursor = 'pointer'; textCell.addEventListener('click', function() { const fullTextDialog = document.createElement('div'); fullTextDialog.className = 'full-text-dialog'; fullTextDialog.style.position = 'fixed'; fullTextDialog.style.top = '50%'; fullTextDialog.style.left = '50%'; fullTextDialog.style.transform = 'translate(-50%, -50%)'; fullTextDialog.style.maxWidth = '80%'; fullTextDialog.style.maxHeight = '80%'; fullTextDialog.style.backgroundColor = 'white'; fullTextDialog.style.padding = '20px'; fullTextDialog.style.borderRadius = '8px'; fullTextDialog.style.boxShadow = '0 0 20px rgba(0,0,0,0.3)'; fullTextDialog.style.zIndex = '10000'; fullTextDialog.style.overflow = 'auto'; const closeBtn = document.createElement('button'); closeBtn.textContent = '关闭'; closeBtn.style.position = 'absolute'; closeBtn.style.top = '10px'; closeBtn.style.right = '10px'; closeBtn.style.padding = '5px 10px'; closeBtn.style.cursor = 'pointer'; const content = document.createElement('pre'); content.style.whiteSpace = 'pre-wrap'; content.style.wordBreak = 'break-word'; content.style.margin = '10px 0'; content.style.padding = '10px'; content.style.backgroundColor = '#f5f5f5'; content.style.border = '1px solid #ddd'; content.style.borderRadius = '4px'; content.textContent = textSnippet; fullTextDialog.appendChild(closeBtn); fullTextDialog.appendChild(content); document.body.appendChild(fullTextDialog); closeBtn.onclick = function() { if (fullTextDialog.parentNode === document.body) { document.body.removeChild(fullTextDialog); } }; // 点击对话框外部关闭 document.addEventListener('click', function closeDialog(e) { if (!fullTextDialog.contains(e.target) && e.target !== textCell) { // 添加检查确保对话框仍然存在于文档中 if (fullTextDialog.parentNode === document.body) { document.body.removeChild(fullTextDialog); } document.removeEventListener('click', closeDialog); } }); }); } // ======= 可编辑笔记列 ======= const noteCell = row.insertCell(); const noteText = ann.body && ann.body.length > 0 && ann.body[0].value ? ann.body[0].value : ''; noteCell.textContent = noteText; // 带批注类型 或 仅高亮类型都可编辑/添加 if (ann.motivation === 'commenting' || ann.motivation === 'highlighting') { noteCell.style.cursor = 'pointer'; noteCell.title = noteText ? '点击编辑批注' : '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') { noteCell.style.color = '#aaa'; noteCell.textContent = '点击添加批注'; } noteCell.addEventListener('click', function onEditNoteCell(e) { if (noteCell.querySelector('textarea')) return; // 已经在编辑 const textarea = document.createElement('textarea'); textarea.value = noteText; textarea.style.width = '98%'; textarea.style.minHeight = '40px'; textarea.style.fontSize = '13px'; textarea.style.fontFamily = 'inherit'; textarea.style.resize = 'vertical'; textarea.autofocus = true; noteCell.innerHTML = ''; noteCell.appendChild(textarea); textarea.focus(); // 保存函数 const saveNote = async () => { const newVal = textarea.value.trim(); if (newVal === noteText || (!newVal && !noteText)) { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; return; } if (!newVal) { noteCell.textContent = '内容不能为空'; setTimeout(() => { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; }, 1200); return; } noteCell.textContent = '保存中...'; try { ann.body = [{ type: 'TextualBody', value: newVal, format: 'text/plain', purpose: 'commenting' }]; ann.modified = new Date().toISOString(); ann.motivation = 'commenting'; // 升级为批注 // 检查 updateAnnotationInDB 函数是否可用 if (typeof updateAnnotationInDB !== 'function') { console.error('updateAnnotationInDB 函数未定义,请确保 storage.js 已正确加载'); throw new Error('保存批注的函数未定义'); } await updateAnnotationInDB(ann); // 更新window.data.annotations const idx = window.data.annotations.findIndex(a => a.id === ann.id); if (idx > -1) window.data.annotations[idx] = { ...ann }; // 刷新表格,保持筛选 const checkedColors = Array.from(annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); populateAnnotationsSummaryTable(annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, checkedColors); } catch (err) { console.error('保存批注失败:', err); noteCell.textContent = '[保存失败]'; setTimeout(() => { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; }, 1500); } }; textarea.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); saveNote(); } else if (e.key === 'Escape') { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; } }); textarea.addEventListener('blur', saveNote); }); } // ======= 可编辑颜色列 ======= const colorCell = row.insertCell(); if (ann.highlightColor) { const swatch = document.createElement('span'); swatch.className = 'color-swatch'; swatch.style.backgroundColor = typeof getHighlightColor === 'function' ? getHighlightColor(ann.highlightColor) : ann.highlightColor; swatch.style.cursor = 'pointer'; swatch.title = '点击更改高亮颜色'; colorCell.appendChild(swatch); // 点击弹出下拉 swatch.addEventListener('click', function onEditColor(e) { if (colorCell.querySelector('select')) return; // 颜色选项:常用色+所有已用色 const commonColors = ['yellow','pink','lightblue','lightgreen','purple','orange','red','cyan','blue','green']; const allColors = Array.from(new Set([...commonColors, ...getAllHighlightColors()])); const select = document.createElement('select'); select.style.marginLeft = '6px'; allColors.forEach(c => { const opt = document.createElement('option'); opt.value = c; opt.textContent = c; opt.style.backgroundColor = typeof getHighlightColor === 'function' ? getHighlightColor(c) : c; if (c === ann.highlightColor) opt.selected = true; select.appendChild(opt); }); colorCell.appendChild(select); select.focus(); // 保存颜色 const saveColor = async () => { const newColor = select.value; if (newColor === ann.highlightColor) { colorCell.innerHTML = ''; colorCell.appendChild(swatch); return; } colorCell.textContent = '保存中...'; try { ann.highlightColor = newColor; ann.modified = new Date().toISOString(); if (ann.motivation !== 'commenting') ann.motivation = 'highlighting'; // 检查 updateAnnotationInDB 函数是否可用 if (typeof updateAnnotationInDB !== 'function') { console.error('updateAnnotationInDB 函数未定义,请确保 storage.js 已正确加载'); throw new Error('保存批注颜色的函数未定义'); } await updateAnnotationInDB(ann); // 更新window.data.annotations const idx = window.data.annotations.findIndex(a => a.id === ann.id); if (idx > -1) window.data.annotations[idx] = { ...ann }; // 刷新表格,保持筛选 const checkedColors = Array.from(annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); populateAnnotationsSummaryTable(annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, checkedColors); } catch (err) { console.error('保存批注颜色失败:', err); colorCell.textContent = '[保存失败]'; setTimeout(() => { colorCell.innerHTML = ''; colorCell.appendChild(swatch); }, 1500); } }; select.addEventListener('change', saveColor); select.addEventListener('blur', saveColor); }); } else { colorCell.textContent = '-'; } const actionsCell = row.insertCell(); const jumpButton = document.createElement('button'); jumpButton.textContent = '跳转'; jumpButton.className = 'action-btn'; jumpButton.dataset.annotationId = ann.id; jumpButton.dataset.targetType = ann.targetType; // 为跨子块批注提供回退子块ID与块索引 let dataBlockIndex = (targetSelector && targetSelector.blockIndex !== undefined) ? String(targetSelector.blockIndex) : ''; let dataSubBlockId = (targetSelector && targetSelector.subBlockId !== undefined) ? String(targetSelector.subBlockId) : ''; if (!dataSubBlockId && targetSelector && targetSelector.type === 'CrossBlockRangeSelector') { if (Array.isArray(targetSelector.affectedSubBlocks) && targetSelector.affectedSubBlocks.length > 0) { dataSubBlockId = String(targetSelector.affectedSubBlocks[0]); } else if (targetSelector.startSubBlockId) { dataSubBlockId = String(targetSelector.startSubBlockId); } } if (!dataBlockIndex && dataSubBlockId) { const parts = dataSubBlockId.split('.'); if (parts.length > 0 && parts[0]) dataBlockIndex = parts[0]; } jumpButton.dataset.blockIndex = dataBlockIndex; jumpButton.dataset.subBlockId = dataSubBlockId; // 仅在分块对比模式禁用跳转;其余场景即使缺少定位信息也尝试按 annotationId 跳转 if (window.currentVisibleTabId === 'chunk-compare') { jumpButton.disabled = true; jumpButton.title = '分块对比模式下禁用跳转'; } jumpButton.onclick = async function() { closeAnnotationsSummaryModal(); // Close modal before jumping const targetType = this.dataset.targetType; const blockIndex = this.dataset.blockIndex; const subBlockId = this.dataset.subBlockId; if (!targetType) return; // Switch tab if necessary if (window.currentVisibleTabId !== targetType && window.currentVisibleTabId !== `${targetType}-content-wrapper`) { // Check against common tab ID patterns if (typeof showTab === 'function') { console.log(`Switching to tab: ${targetType} for jump action.`); await Promise.resolve(showTab(targetType)); // Ensure tab switch completes // Wait a brief moment for DOM updates after tab switch, if showTab involves async rendering. await new Promise(resolve => setTimeout(resolve, 200)); } else { alert('无法切换标签页。'); return; } } // 优先:等待目标高亮/元素出现后再跳转(处理分批渲染/分块延时) if (typeof window.scrollToAnnotationAsync === 'function') { const okAsync = await window.scrollToAnnotationAsync(this.dataset.annotationId, { targetType, subBlockId, blockIndex, timeoutMs: 5000, pollIntervalMs: 120 }); if (okAsync) return; } else if (typeof window.scrollToAnnotation === 'function') { const ok = window.scrollToAnnotation(this.dataset.annotationId, true); if (ok) return; } // 回退:按 subBlockId 或 blockIndex 定位 let elementToJump = null; const contentWrapperId = `${targetType}-content-wrapper`; const contentWrapper = document.getElementById(contentWrapperId); if (contentWrapper) { if (subBlockId && subBlockId !== 'undefined' && subBlockId !== '') { elementToJump = contentWrapper.querySelector(`.sub-block[data-sub-block-id="${subBlockId}"]`); } else if (blockIndex !== '') { elementToJump = contentWrapper.querySelector(`[data-block-index="${blockIndex}"]`); } } if (elementToJump) { elementToJump.scrollIntoView({ behavior: 'smooth', block: 'center' }); elementToJump.classList.add('jump-to-highlight-effect'); setTimeout(() => elementToJump.classList.remove('jump-to-highlight-effect'), 2500); } else { alert('在当前视图中未找到目标元素。它可能已被过滤或不存在。'); console.warn('Element not found for jump:', {targetType, blockIndex, subBlockId}); } }; actionsCell.appendChild(jumpButton); }); } if (ungrouped.length) { const groupRow = document.createElement('tr'); groupRow.className = 'toc-group'; // XSS 防护:使用 textContent(虽然 length 是数字,但保持一致性) const td = document.createElement('td'); td.setAttribute('colspan', '7'); td.textContent = `未分组(${ungrouped.length})`; groupRow.appendChild(td); annotationsSummaryTableBody.appendChild(groupRow); ungrouped.forEach(ann => { const row = annotationsSummaryTableBody.insertRow(); row.insertCell().textContent = ann.motivation === 'commenting' ? '批注' : (ann.motivation === 'highlighting' ? '高亮' : ann.motivation); row.insertCell().textContent = ann.targetType ? ann.targetType.toUpperCase() : 'N/A'; let identifierText = 'N/A'; let targetSelector = ann.target && ann.target.selector && ann.target.selector[0]; if (targetSelector) { if (targetSelector.subBlockId) { identifierText = `子块: ${targetSelector.subBlockId}`; } else if (targetSelector.blockIndex !== undefined) { identifierText = `块: ${targetSelector.blockIndex}`; } } row.insertCell().textContent = identifierText; // 增强文本片段预览(优先原始 Markdown,再回退 exact) const textCell = row.insertCell(); let textSnippet = '[无文本片段]'; if (targetSelector) { // 优先:以所属块的原始 Markdown 作为预览 let preferredBlockIndex = null; if (targetSelector.blockIndex !== undefined && targetSelector.blockIndex !== null) { preferredBlockIndex = parseInt(targetSelector.blockIndex, 10); } else if (targetSelector.subBlockId) { const parts = String(targetSelector.subBlockId).split('.'); if (parts.length > 0 && !isNaN(parseInt(parts[0], 10))) preferredBlockIndex = parseInt(parts[0], 10); } if (preferredBlockIndex !== null && window.currentBlockTokensForCopy && window.currentBlockTokensForCopy[ann.targetType] && window.currentBlockTokensForCopy[ann.targetType][preferredBlockIndex] && typeof window.currentBlockTokensForCopy[ann.targetType][preferredBlockIndex].raw === 'string') { textSnippet = window.currentBlockTokensForCopy[ann.targetType][preferredBlockIndex].raw; } else if (targetSelector.exact) { // 回退:展示 exact(渲染后的文本片段) textSnippet = targetSelector.exact; } } // 限制显示长度,但保留更多内容 if (textSnippet && textSnippet.length > 150) { textCell.textContent = textSnippet.substring(0, 150) + '...'; textCell.title = textSnippet; // 鼠标悬停时显示完整内容 } else { textCell.textContent = textSnippet || '[无文本片段]'; } // 添加查看完整内容的功能 if (textSnippet && textSnippet.length > 50) { textCell.style.cursor = 'pointer'; textCell.addEventListener('click', function() { const fullTextDialog = document.createElement('div'); fullTextDialog.className = 'full-text-dialog'; fullTextDialog.style.position = 'fixed'; fullTextDialog.style.top = '50%'; fullTextDialog.style.left = '50%'; fullTextDialog.style.transform = 'translate(-50%, -50%)'; fullTextDialog.style.maxWidth = '80%'; fullTextDialog.style.maxHeight = '80%'; fullTextDialog.style.backgroundColor = 'white'; fullTextDialog.style.padding = '20px'; fullTextDialog.style.borderRadius = '8px'; fullTextDialog.style.boxShadow = '0 0 20px rgba(0,0,0,0.3)'; fullTextDialog.style.zIndex = '10000'; fullTextDialog.style.overflow = 'auto'; const closeBtn = document.createElement('button'); closeBtn.textContent = '关闭'; closeBtn.style.position = 'absolute'; closeBtn.style.top = '10px'; closeBtn.style.right = '10px'; closeBtn.style.padding = '5px 10px'; closeBtn.style.cursor = 'pointer'; const content = document.createElement('pre'); content.style.whiteSpace = 'pre-wrap'; content.style.wordBreak = 'break-word'; content.style.margin = '10px 0'; content.style.padding = '10px'; content.style.backgroundColor = '#f5f5f5'; content.style.border = '1px solid #ddd'; content.style.borderRadius = '4px'; content.textContent = textSnippet; fullTextDialog.appendChild(closeBtn); fullTextDialog.appendChild(content); document.body.appendChild(fullTextDialog); closeBtn.onclick = function() { if (fullTextDialog.parentNode === document.body) { document.body.removeChild(fullTextDialog); } }; // 点击对话框外部关闭 document.addEventListener('click', function closeDialog(e) { if (!fullTextDialog.contains(e.target) && e.target !== textCell) { // 添加检查确保对话框仍然存在于文档中 if (fullTextDialog.parentNode === document.body) { document.body.removeChild(fullTextDialog); } document.removeEventListener('click', closeDialog); } }); }); } // ======= 可编辑笔记列 ======= const noteCell = row.insertCell(); const noteText = ann.body && ann.body.length > 0 && ann.body[0].value ? ann.body[0].value : ''; noteCell.textContent = noteText; // 带批注类型 或 仅高亮类型都可编辑/添加 if (ann.motivation === 'commenting' || ann.motivation === 'highlighting') { noteCell.style.cursor = 'pointer'; noteCell.title = noteText ? '点击编辑批注' : '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') { noteCell.style.color = '#aaa'; noteCell.textContent = '点击添加批注'; } noteCell.addEventListener('click', function onEditNoteCell(e) { if (noteCell.querySelector('textarea')) return; // 已经在编辑 const textarea = document.createElement('textarea'); textarea.value = noteText; textarea.style.width = '98%'; textarea.style.minHeight = '40px'; textarea.style.fontSize = '13px'; textarea.style.fontFamily = 'inherit'; textarea.style.resize = 'vertical'; textarea.autofocus = true; noteCell.innerHTML = ''; noteCell.appendChild(textarea); textarea.focus(); // 保存函数 const saveNote = async () => { const newVal = textarea.value.trim(); if (newVal === noteText || (!newVal && !noteText)) { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; return; } if (!newVal) { noteCell.textContent = '内容不能为空'; setTimeout(() => { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; }, 1200); return; } noteCell.textContent = '保存中...'; try { ann.body = [{ type: 'TextualBody', value: newVal, format: 'text/plain', purpose: 'commenting' }]; ann.modified = new Date().toISOString(); ann.motivation = 'commenting'; // 升级为批注 // 检查 updateAnnotationInDB 函数是否可用 if (typeof updateAnnotationInDB !== 'function') { console.error('updateAnnotationInDB 函数未定义,请确保 storage.js 已正确加载'); throw new Error('保存批注的函数未定义'); } await updateAnnotationInDB(ann); // 更新window.data.annotations const idx = window.data.annotations.findIndex(a => a.id === ann.id); if (idx > -1) window.data.annotations[idx] = { ...ann }; // 刷新表格,保持筛选 const checkedColors = Array.from(annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); populateAnnotationsSummaryTable(annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, checkedColors); } catch (err) { console.error('保存批注失败:', err); noteCell.textContent = '[保存失败]'; setTimeout(() => { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; }, 1500); } }; textarea.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); saveNote(); } else if (e.key === 'Escape') { noteCell.textContent = noteText || '点击添加批注'; if (!noteText && ann.motivation === 'highlighting') noteCell.style.color = '#aaa'; } }); textarea.addEventListener('blur', saveNote); }); } // ======= 可编辑颜色列 ======= const colorCell = row.insertCell(); if (ann.highlightColor) { const swatch = document.createElement('span'); swatch.className = 'color-swatch'; swatch.style.backgroundColor = typeof getHighlightColor === 'function' ? getHighlightColor(ann.highlightColor) : ann.highlightColor; swatch.style.cursor = 'pointer'; swatch.title = '点击更改高亮颜色'; colorCell.appendChild(swatch); // 点击弹出下拉 swatch.addEventListener('click', function onEditColor(e) { if (colorCell.querySelector('select')) return; // 颜色选项:常用色+所有已用色 const commonColors = ['yellow','pink','lightblue','lightgreen','purple','orange','red','cyan','blue','green']; const allColors = Array.from(new Set([...commonColors, ...getAllHighlightColors()])); const select = document.createElement('select'); select.style.marginLeft = '6px'; allColors.forEach(c => { const opt = document.createElement('option'); opt.value = c; opt.textContent = c; opt.style.backgroundColor = typeof getHighlightColor === 'function' ? getHighlightColor(c) : c; if (c === ann.highlightColor) opt.selected = true; select.appendChild(opt); }); colorCell.appendChild(select); select.focus(); // 保存颜色 const saveColor = async () => { const newColor = select.value; if (newColor === ann.highlightColor) { colorCell.innerHTML = ''; colorCell.appendChild(swatch); return; } colorCell.textContent = '保存中...'; try { ann.highlightColor = newColor; ann.modified = new Date().toISOString(); if (ann.motivation !== 'commenting') ann.motivation = 'highlighting'; // 检查 updateAnnotationInDB 函数是否可用 if (typeof updateAnnotationInDB !== 'function') { console.error('updateAnnotationInDB 函数未定义,请确保 storage.js 已正确加载'); throw new Error('保存批注颜色的函数未定义'); } await updateAnnotationInDB(ann); // 更新window.data.annotations const idx = window.data.annotations.findIndex(a => a.id === ann.id); if (idx > -1) window.data.annotations[idx] = { ...ann }; // 刷新表格,保持筛选 const checkedColors = Array.from(annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); populateAnnotationsSummaryTable(annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, checkedColors); } catch (err) { console.error('保存批注颜色失败:', err); colorCell.textContent = '[保存失败]'; setTimeout(() => { colorCell.innerHTML = ''; colorCell.appendChild(swatch); }, 1500); } }; select.addEventListener('change', saveColor); select.addEventListener('blur', saveColor); }); } else { colorCell.textContent = '-'; } const actionsCell = row.insertCell(); const jumpButton = document.createElement('button'); jumpButton.textContent = '跳转'; jumpButton.className = 'action-btn'; jumpButton.dataset.annotationId = ann.id; jumpButton.dataset.targetType = ann.targetType; // 为跨子块批注提供回退子块ID与块索引(未分组场景) let dataBlockIndex2 = (targetSelector && targetSelector.blockIndex !== undefined) ? String(targetSelector.blockIndex) : ''; let dataSubBlockId2 = (targetSelector && targetSelector.subBlockId !== undefined) ? String(targetSelector.subBlockId) : ''; if (!dataSubBlockId2 && targetSelector && targetSelector.type === 'CrossBlockRangeSelector') { if (Array.isArray(targetSelector.affectedSubBlocks) && targetSelector.affectedSubBlocks.length > 0) { dataSubBlockId2 = String(targetSelector.affectedSubBlocks[0]); } else if (targetSelector.startSubBlockId) { dataSubBlockId2 = String(targetSelector.startSubBlockId); } } if (!dataBlockIndex2 && dataSubBlockId2) { const parts = dataSubBlockId2.split('.'); if (parts.length > 0 && parts[0]) dataBlockIndex2 = parts[0]; } jumpButton.dataset.blockIndex = dataBlockIndex2; jumpButton.dataset.subBlockId = dataSubBlockId2; // 仅在分块对比模式禁用跳转 if (window.currentVisibleTabId === 'chunk-compare') { jumpButton.disabled = true; jumpButton.title = '分块对比模式下禁用跳转'; } jumpButton.onclick = async function() { closeAnnotationsSummaryModal(); // Close modal before jumping const targetType = this.dataset.targetType; const blockIndex = this.dataset.blockIndex; const subBlockId = this.dataset.subBlockId; if (!targetType) return; // Switch tab if necessary if (window.currentVisibleTabId !== targetType && window.currentVisibleTabId !== `${targetType}-content-wrapper`) { // Check against common tab ID patterns if (typeof showTab === 'function') { console.log(`Switching to tab: ${targetType} for jump action.`); await Promise.resolve(showTab(targetType)); // Ensure tab switch completes // Wait a brief moment for DOM updates after tab switch, if showTab involves async rendering. await new Promise(resolve => setTimeout(resolve, 200)); } else { alert('无法切换标签页。'); return; } } // 优先:等待目标高亮/元素出现后再跳转(处理分批渲染/分块延时) if (typeof window.scrollToAnnotationAsync === 'function') { const okAsync = await window.scrollToAnnotationAsync(this.dataset.annotationId, { targetType, subBlockId, blockIndex, timeoutMs: 5000, pollIntervalMs: 120 }); if (okAsync) return; } else if (typeof window.scrollToAnnotation === 'function') { const ok = window.scrollToAnnotation(this.dataset.annotationId, true); if (ok) return; } // 回退:按 subBlockId 或 blockIndex 定位 let elementToJump = null; const contentWrapperId = `${targetType}-content-wrapper`; const contentWrapper = document.getElementById(contentWrapperId); if (contentWrapper) { if (subBlockId && subBlockId !== 'undefined' && subBlockId !== '') { elementToJump = contentWrapper.querySelector(`.sub-block[data-sub-block-id="${subBlockId}"]`); } else if (blockIndex !== '') { elementToJump = contentWrapper.querySelector(`[data-block-index="${blockIndex}"]`); } } if (elementToJump) { elementToJump.scrollIntoView({ behavior: 'smooth', block: 'center' }); elementToJump.classList.add('jump-to-highlight-effect'); setTimeout(() => elementToJump.classList.remove('jump-to-highlight-effect'), 2500); } else { alert('在当前视图中未找到目标元素。它可能已被过滤或不存在。'); console.warn('Element not found for jump:', {targetType, blockIndex, subBlockId}); } }; actionsCell.appendChild(jumpButton); }); } if (!groupCount && !ungrouped.length) { annotationsSummaryTableBody.innerHTML = '没有符合筛选条件的批注或高亮。'; return; } } window.openAnnotationsSummaryModal = function(filterByType = 'all', filterByContent = 'all') { if (!annotationsSummaryModal) return; // Set initial filter values from parameters annotationsFilterTypeSelect.value = filterByType; annotationsFilterContentSelect.value = filterByContent; // 默认全选所有颜色 const allColors = getAllHighlightColors(); populateAnnotationsSummaryTable(filterByType, filterByContent, allColors); annotationsSummaryModal.classList.add('visible'); }; function closeAnnotationsSummaryModal() { if (annotationsSummaryModal) { annotationsSummaryModal.classList.remove('visible'); } } if (annotationsSummaryCloseBtn) { annotationsSummaryCloseBtn.onclick = closeAnnotationsSummaryModal; } if (annotationsSummaryModal) { annotationsSummaryModal.addEventListener('click', function(event) { if (event.target === annotationsSummaryModal) { // Click on overlay closeAnnotationsSummaryModal(); } }); } if (annotationsFilterTypeSelect && annotationsFilterContentSelect) { annotationsFilterTypeSelect.addEventListener('change', () => { // 颜色多选保持当前选中 const checkedColors = Array.from(annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); populateAnnotationsSummaryTable(annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, checkedColors); }); annotationsFilterContentSelect.addEventListener('change', () => { const checkedColors = Array.from(annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); populateAnnotationsSummaryTable(annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, checkedColors); }); } // 存储上次看到的颜色集合,用于检测新颜色 let lastSeenColors = new Set(); // 定期检查是否有新颜色出现 function checkForNewColors() { const currentColors = new Set(getAllHighlightColors()); // 找出新增的颜色 const newColors = Array.from(currentColors).filter(color => !lastSeenColors.has(color)); // 如果有新颜色且模态框当前可见 if (newColors.length > 0 && annotationsSummaryModal && annotationsSummaryModal.classList.contains('visible')) { console.log('检测到新颜色:', newColors); // 获取当前已选中的颜色 const currentlyCheckedColors = Array.from( annotationsSummaryColorFilter.querySelectorAll('input[type="checkbox"]:checked') ).map(cb => cb.value); // 合并当前选中的颜色和新颜色 const updatedSelection = [...currentlyCheckedColors, ...newColors]; // 重新渲染表格,确保新颜色被选中 populateAnnotationsSummaryTable( annotationsFilterTypeSelect.value, annotationsFilterContentSelect.value, updatedSelection ); } // 更新已知颜色集合 lastSeenColors = currentColors; } // 初始化已知颜色集合 lastSeenColors = new Set(getAllHighlightColors()); // 每秒检查一次新颜色 // 性能优化:页面隐藏时暂停轮询 (function() { let timerId = null; let isActive = false; function poll() { if (!isActive) return; if (!document.hidden) { checkForNewColors(); } timerId = setTimeout(poll, 1000); } function start() { if (isActive) return; isActive = true; poll(); } function stop() { isActive = false; if (timerId) { clearTimeout(timerId); timerId = null; } } document.addEventListener('visibilitychange', () => { // 页面隐藏/显示时无需额外操作,poll 函数会自动跳过 }); window.addEventListener('beforeunload', stop); start(); })();