1031 lines
48 KiB
JavaScript
1031 lines
48 KiB
JavaScript
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 = '<tr><td colspan="7">暂无数据或批注未加载。</td></tr>';
|
||
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 = '<tr><td colspan="7">没有符合筛选条件的批注或高亮。</td></tr>';
|
||
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();
|
||
})();
|