paper-burner/js/history/history_detail_globals.js

595 lines
29 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.

// ---- 一键屏蔽非错误 console.log ----
const PRODUCTION_MODE = false; // 设置为 true 来屏蔽 console.log, false 则不屏蔽
if (PRODUCTION_MODE) {
const originalConsoleLog = console.log;
console.log = function() {
// 可以选择在这里什么都不做,或者记录到一个备用日志系统
// originalConsoleLog.apply(console, arguments); // 如果需要,可以取消注释来保留原始日志
};
}
// ========== 新增:防止 showTab 重复渲染的锁 ==========
let renderingTab = null;
// =====================================================
let isOriginalFirstInChunkCompare = true; // 状态变量:原文是否在左侧
let docIdForLocalStorage = null; // To store the current document ID for localStorage keys
window.docIdForLocalStorage = null; // 将变量挂载到 window 对象上,使其成为全局变量
var currentVisibleTabId = null; // To store the ID of the currently visible tab
window.currentVisibleTabId = null; // 将变量挂载到 window 对象上,使其成为全局变量
// let progressPercentageSpan = null; // MOVED to dock_logic.js
// let progressPercentageVerboseSpan = null; // MOVED to dock_logic.js
// let dockElement = null; // MOVED to dock_logic.js
// NEW FUNCTION DEFINITION
function getCurrentScrollableElementForHistoryDetail() {
// console.log(`[getCurrentScrollableElementForHistoryDetail] 开始查找可滚动元素, 沉浸模式=${window.ImmersiveLayout && window.ImmersiveLayout.isActive() ? '是' : '否'}`);
if (window.ImmersiveLayout && window.ImmersiveLayout.isActive()) {
const immersiveMainArea = document.getElementById('immersive-main-content-area');
// console.log(`[getCurrentScrollableElementForHistoryDetail] 找到immersiveMainArea:`, immersiveMainArea ? true : false);
if (immersiveMainArea) {
// Try to find the specific, scrollable tab content area first
const tabContentScroller = immersiveMainArea.querySelector('.tab-content[style*="overflow-y: auto"], .tab-content[style*="overflow: auto"]');
// console.log(`[getCurrentScrollableElementForHistoryDetail] 尝试查找tabContentScroller:`, tabContentScroller ? {
// id: tabContentScroller.id || '无ID',
// className: tabContentScroller.className || '无类名',
// overflowY: tabContentScroller.style.overflowY,
// overflow: tabContentScroller.style.overflow,
// computedOverflowY: window.getComputedStyle(tabContentScroller).overflowY
// } : '未找到');
if (tabContentScroller) return tabContentScroller;
// Fallback: look for a .content-wrapper or .chunk-compare-container within .tab-content
const activeTabContentBlock = immersiveMainArea.querySelector('.tab-content .content-wrapper, .tab-content .chunk-compare-container');
// console.log(`[getCurrentScrollableElementForHistoryDetail] 尝试查找activeTabContentBlock:`, activeTabContentBlock ? {
// id: activeTabContentBlock.id || '无ID',
// className: activeTabContentBlock.className || '无类名'
// } : '未找到');
if (activeTabContentBlock) {
// It might be that the .tab-content itself is the designated scroller
const parentTabContent = activeTabContentBlock.closest('.tab-content');
// console.log(`[getCurrentScrollableElementForHistoryDetail] 尝试查找parentTabContent:`, parentTabContent ? {
// id: parentTabContent.id || '无ID',
// className: parentTabContent.className || '无类名',
// overflowY: parentTabContent.style.overflowY,
// overflow: parentTabContent.style.overflow,
// computedOverflowY: window.getComputedStyle(parentTabContent).overflowY
// } : '未找到');
if (parentTabContent && (parentTabContent.style.overflowY === 'auto' || parentTabContent.style.overflow === 'auto' ||
window.getComputedStyle(parentTabContent).overflowY === 'auto')) {
// console.log(`[getCurrentScrollableElementForHistoryDetail] 返回parentTabContent作为滚动元素`);
return parentTabContent;
}
// console.log(`[getCurrentScrollableElementForHistoryDetail] 返回activeTabContentBlock作为滚动元素`);
return activeTabContentBlock; // Fallback to the content wrapper itself if .tab-content isn't the scroller
}
// Fallback to the general .container if present inside immersive main area
const containerInImmersive = immersiveMainArea.querySelector('.container');
// console.log(`[getCurrentScrollableElementForHistoryDetail] 尝试查找containerInImmersive:`, containerInImmersive ? {
// id: containerInImmersive.id || '无ID',
// className: containerInImmersive.className || '无类名'
// } : '未找到');
if(containerInImmersive) {
// console.log(`[getCurrentScrollableElementForHistoryDetail] 返回containerInImmersive作为滚动元素`);
return containerInImmersive;
}
// console.log(`[getCurrentScrollableElementForHistoryDetail] 返回immersiveMainArea作为滚动元素`);
return immersiveMainArea; // Last fallback for immersive mode
}
}
// 非沉浸模式:使用 .app-main侧边栏布局中的主内容区
const appMain = document.querySelector('.app-main');
if (appMain) {
// console.log(`[getCurrentScrollableElementForHistoryDetail] 返回.app-main作为滚动元素`);
return appMain;
}
// 最终回退到 document.documentElement旧布局或特殊情况
// console.log(`[getCurrentScrollableElementForHistoryDetail] 返回document.documentElement作为滚动元素`);
return document.documentElement;
}
function adjustLongHeadingsToParagraphs(parentElement) {
if (!parentElement) return;
// console.log('[adjustLongHeadingsToParagraphs] 开始处理,父元素:', parentElement);
// 创建一个数组来存储所有需要处理的markdown-body元素
let markdownBodies = [];
// 检查父元素本身是否有markdown-body类
if (parentElement.classList && parentElement.classList.contains('markdown-body')) {
markdownBodies.push(parentElement);
// console.log('[adjustLongHeadingsToParagraphs] 父元素本身是markdown-body');
}
// 查找父元素内的所有markdown-body元素
const childMarkdownBodies = parentElement.querySelectorAll('.markdown-body');
childMarkdownBodies.forEach(el => {
if (!markdownBodies.includes(el)) { // 避免重复
markdownBodies.push(el);
}
});
// console.log('[adjustLongHeadingsToParagraphs] 找到 markdown-body 元素数量:', markdownBodies.length);
markdownBodies.forEach((markdownBody, mbIdx) => {
const headings = Array.from(markdownBody.querySelectorAll('h1, h2, h3, h4, h5, h6'));
//console.log(`[adjustLongHeadingsToParagraphs] markdown-body #${mbIdx} 中找到标题元素数量:`, headings.length);
// 逆序遍历防止DOM结构变化影响
for (let hIdx = headings.length - 1; hIdx >= 0; hIdx--) {
const heading = headings[hIdx];
// Skip the main fileName heading if it's somehow caught by this logic
if (heading.id === 'fileName') {
// console.log('[adjustLongHeadingsToParagraphs] 跳过 fileName 标题');
continue;
}
// 补丁:如果没有 id分配唯一 id
if (!heading.id) heading.id = 'auto-hx-' + hIdx;
const textContent = heading.textContent || "";
//console.log(`[adjustLongHeadingsToParagraphs] 标题 #${hIdx} (${heading.tagName}), ID: ${heading.id}, 文本长度: ${textContent.length}, 文本: "${textContent.substring(0, 50)}${textContent.length > 50 ? '...' : ''}"`);
if (textContent.length > 30) {
//console.log(`[adjustLongHeadingsToParagraphs] 标题 #${hIdx} 文本长度 > 30准备转换为段落`);
try {
const p = document.createElement('p');
const originalTagName = heading.tagName.toLowerCase(); // 存储原始标签名
// 递归移动所有子节点,彻底保留结构
while (heading.firstChild) {
p.appendChild(heading.firstChild);
}
// Copy all attributes from heading to p
for (let i = 0; i < heading.attributes.length; i++) {
const attr = heading.attributes[i];
p.setAttribute(attr.name, attr.value);
}
// Add a class to indicate this was a converted heading,
// in case specific styling is needed later.
p.classList.add('converted-from-heading');
p.dataset.originalTag = originalTagName; // 将原始标签名存储到 data-* 属性
p.style.fontWeight = 'bold'; // 直接设置字体加粗
heading.parentNode.replaceChild(p, heading);
//console.log(`[adjustLongHeadingsToParagraphs] 成功将标题 #${hIdx} 转换为段落(递归移动子节点)`);
} catch (error) {
console.error(`[adjustLongHeadingsToParagraphs] 转换标题 #${hIdx} 时出错:`, error);
}
}
}
});
// console.log('[adjustLongHeadingsToParagraphs] 处理完成');
}
function debounce(func, delay) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), delay);
};
}
function saveScrollPosition() {
if (!docIdForLocalStorage || !currentVisibleTabId) return;
const scrollableElement = getCurrentScrollableElementForHistoryDetail();
const isImmersive = window.ImmersiveLayout && window.ImmersiveLayout.isActive();
const modePrefix = isImmersive ? 'immersive_' : 'normal_';
if (scrollableElement) {
const scrollKey = `scrollPos_${modePrefix}${docIdForLocalStorage}_${currentVisibleTabId}`;
localStorage.setItem(scrollKey, scrollableElement.scrollTop);
let bestAnchorId = null;
if (typeof window.getTocNodes === 'function') {
const tocElements = window.getTocNodes(); // 获取TOC节点
let minPositiveTop = Infinity;
let lastVisibleAboveFoldId = null;
for (const el of tocElements) {
if (!el || !el.id) continue;
const rect = el.getBoundingClientRect();
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
if (rect.top >= 0 && rect.top < viewportHeight / 3) { // 在视口顶部三分之一
if (rect.top < minPositiveTop) {
minPositiveTop = rect.top;
bestAnchorId = el.id;
}
} else if (rect.bottom < 0) { // 完全滚出视口上方
lastVisibleAboveFoldId = el.id; // 记录最后一个滚出上方的
}
}
if (!bestAnchorId && lastVisibleAboveFoldId) { // 如果顶部三分之一没有,用最后一个滚出上方的
bestAnchorId = lastVisibleAboveFoldId;
}
}
if (bestAnchorId) {
const anchorKey = `scrollAnchorId_${modePrefix}${docIdForLocalStorage}_${currentVisibleTabId}`;
localStorage.setItem(anchorKey, bestAnchorId);
// console.log(`[saveScrollPosition] 保存滚动锚点: ${anchorKey} = ${bestAnchorId}`);
} else {
const anchorKey = `scrollAnchorId_${modePrefix}${docIdForLocalStorage}_${currentVisibleTabId}`;
localStorage.removeItem(anchorKey); // 如果没有合适的锚点,清除旧的
// console.log(`[saveScrollPosition] 未找到合适锚点,清除旧锚点 (如有): ${anchorKey}`);
}
// console.log(`[saveScrollPosition] 保存滚动位置: ${scrollKey} = ${scrollableElement.scrollTop}, 元素: ${scrollableElement.tagName}, 模式: ${isImmersive ? '沉浸式' : '普通'}`, {
// 元素ID: scrollableElement.id || '无ID',
// 元素类名: scrollableElement.className || '无类名',
// 元素标签: scrollableElement.tagName,
// scrollTop: scrollableElement.scrollTop,
// scrollHeight: scrollableElement.scrollHeight,
// clientHeight: scrollableElement.clientHeight,
// maxScrollTop: scrollableElement.scrollHeight - scrollableElement.clientHeight,
// scrollPercent: ((scrollableElement.scrollTop / (scrollableElement.scrollHeight - scrollableElement.clientHeight)) * 100).toFixed(2) + '%',
// 路径: getElementPath(scrollableElement),
// 锚点ID: bestAnchorId
// });
} else {
console.warn(`[saveScrollPosition] 未找到可滚动元素,无法保存滚动位置`);
}
}
// 使用debounce函数创建一个防抖动的保存滚动位置函数
const debouncedSaveScrollPosition = debounce(saveScrollPosition, 200);
// 动态绑定/解绑滚动事件到当前滚动容器
let lastScrollableElementForSave = null;
function bindScrollForSavePosition() {
// 解绑旧的滚动监听
if (lastScrollableElementForSave) {
lastScrollableElementForSave.removeEventListener('scroll', debouncedSaveScrollPosition);
lastScrollableElementForSave = null;
}
// 获取当前滚动元素
const el = getCurrentScrollableElementForHistoryDetail();
if (el) {
console.log(`[bindScrollForSavePosition] 绑定滚动事件到元素:`, el.id || el.className || el.tagName);
el.addEventListener('scroll', debouncedSaveScrollPosition);
lastScrollableElementForSave = el;
} else {
console.warn(`[bindScrollForSavePosition] 未找到可滚动元素,无法绑定滚动事件`);
}
}
function unbindScrollForSavePosition() {
if (lastScrollableElementForSave) {
lastScrollableElementForSave.removeEventListener('scroll', debouncedSaveScrollPosition);
lastScrollableElementForSave = null;
}
}
// 辅助函数获取元素的DOM路径
function getElementPath(element) {
if (!element) return "null";
let path = [];
while (element && element.nodeType === Node.ELEMENT_NODE) {
let selector = element.nodeName.toLowerCase();
if (element.id) {
selector += '#' + element.id;
path.unshift(selector);
break;
} else {
let sibling = element;
let siblingIndex = 1;
while (sibling = sibling.previousElementSibling) {
if (sibling.nodeName.toLowerCase() === selector) siblingIndex++;
}
if (siblingIndex > 1) selector += ':nth-of-type(' + siblingIndex + ')';
}
path.unshift(selector);
element = element.parentNode;
}
return path.join(' > ');
}
function saveChatbotStateOnUnload() {
if (docIdForLocalStorage && typeof window.isChatbotOpen !== 'undefined') {
localStorage.setItem(`chatbotOpenState_${docIdForLocalStorage}`, window.isChatbotOpen);
// console.log(`Saved chatbot state on beforeunload for ${docIdForLocalStorage}: ${window.isChatbotOpen}`);
}
}
// MOVED to dock_logic.js: function updateReadingProgress() { ... }
// MOVED to dock_logic.js: const debouncedUpdateReadingProgress = debounce(updateReadingProgress, 100);
// MOVED to dock_logic.js: function updateHighlightSummary() { ... }
// MOVED to dock_logic.js: function updateAnnotationSummary() { ... }
// MOVED to dock_logic.js: function updateImageCount() { ... }
// MOVED to dock_logic.js: function updateTableCount(contentElement) { ... }
// MOVED to dock_logic.js: function updateFormulaCount(contentElement) { ... }
// MOVED to dock_logic.js: function updateWordCount(contentElement) { ... }
// MOVED to dock_logic.js: function updateAllDockStats() { ... }
/**
* 预处理 Markdown 文本,以安全地渲染图片、自定义语法(如上下标)并兼容 KaTeX。
* - 将 Markdown 中的本地图片引用 (e.g., `![alt](images/img-1.jpeg.png)`) 替换为 Base64 嵌入式图片。
* - 解析自定义的上下标语法 (e.g., `${base}^{sup}$`, `${base}_{sub}$`) 并转换为 HTML `<sup>` 和 `<sub>` 标签。
* - 其他如 `$formula$` 和 `$$block formula$$` 的 LaTeX 标记会保留,交由后续的 `renderWithKatexFailback` 处理。
*
* @param {string} md -输入的 Markdown 文本。
* @param {Array<Object>} images -一个包含图片对象的数组,每个对象应有 `name` 或 `id` (用于匹配) 和 `data` (Base64 图片数据或其前缀)。
* @returns {string} 处理后的 Markdown 文本,其中图片被替换,自定义语法被转换。
*/
// MOVED to js/markdown_processor.js: function safeMarkdown(md, images) { ... }
/**
* 使用 KaTeX 渲染 Markdown 文本中的数学公式,并提供降级处理。
* 它会按以下顺序处理:
* 1. 将长度较短 (<=10字符) 的块级公式 `$$...$$` 转换为行内公式 `$...\$`。
* 2. 尝试使用 KaTeX 渲染行内公式 `$...\$`。如果渲染失败,则将公式内容包裹在 `<code>` 标签中显示。
* 3. 尝试使用 KaTeX 渲染剩余的(通常是多行的)块级公式 `$$...$$`。如果渲染失败,则同样包裹在 `<code>` 标签中。
* 4. 对处理完公式的文本,使用 `marked.parse()` 将其余 Markdown 内容转换为 HTML。
*
* @param {string} md - 经过 `safeMarkdown` 处理的 Markdown 文本。
* @param {Function} customRenderer - 自定义的 Markdown 渲染器函数,用于处理特殊内容。
* @returns {string} 包含渲染后公式和其余 Markdown 内容的 HTML 字符串。
*/
// MOVED to js/markdown_processor.js: function renderWithKatexFailback(md, customRenderer) { ... }
/**
* 从当前页面的 URL 中获取指定查询参数的值。
* @param {string} name - 要获取的查询参数的名称。
* @returns {string|null} 查询参数的值,如果不存在则返回 null。
*/
function getQueryParam(name) {
const url = new URL(window.location.href);
let val = url.searchParams.get(name);
if (!val && name === 'id') {
try {
val = localStorage.getItem('pbx_current_doc_id');
if (val) {
console.log('[getQueryParam] Recovered document ID from localStorage:', val);
}
} catch (e) {
console.warn('[getQueryParam] Failed to read doc id from localStorage', e);
}
}
return val;
}
/**
* @type {Object|null}
* @description 存储从 IndexedDB 加载的当前历史记录的详细数据。
*/
let data = null;
/**
* 将块级元素内容按标点分割成子块 (span.sub-block)。
* @param {HTMLElement} blockElement - 要分割的块级元素 (如 p, h1-h6)。
* @param {string|number} parentBlockIndex - 父块的索引。
*/
// MOVED to js/sub_block_segmenter.js: function segmentBlockIntoSubBlocks(blockElement, parentBlockIndex) { ... }
function restoreScrollPositionForCurrentTab() {
if (docIdForLocalStorage && currentVisibleTabId) {
const isImmersive = window.ImmersiveLayout && window.ImmersiveLayout.isActive();
const modePrefix = isImmersive ? 'immersive_' : 'normal_';
const anchorKey = `scrollAnchorId_${modePrefix}${docIdForLocalStorage}_${currentVisibleTabId}`;
const savedAnchorId = localStorage.getItem(anchorKey);
console.log(`[restoreScrollPosition] 模式: ${modePrefix}, 尝试恢复锚点: ${anchorKey}, 保存的锚点ID: ${savedAnchorId}`);
if (savedAnchorId) {
const targetElement = document.getElementById(savedAnchorId);
if (targetElement) {
console.log(`[restoreScrollPosition] 找到锚点元素 ${savedAnchorId}, 滚动到该元素。`);
requestAnimationFrame(() => {
if (localStorage.getItem(`activeTab_${docIdForLocalStorage}`) === currentVisibleTabId) {
targetElement.scrollIntoView({ behavior: 'auto', block: 'start' }); // 'auto' for instant jump
// 立即保存一次当前scrollTop因为scrollIntoView后可能需要微调
setTimeout(saveScrollPosition, 50);
}
});
// 新增:每次恢复滚动后都绑定 scroll 事件到正确容器
if (window.DockLogic && typeof window.DockLogic.bindScrollForCurrentScrollable === 'function') {
window.DockLogic.bindScrollForCurrentScrollable();
}
return; // 成功通过锚点恢复,直接返回
}
console.warn(`[restoreScrollPosition] 未找到ID为 ${savedAnchorId} 的锚点元素。回退到scrollTop恢复。`);
}
// 如果锚点恢复失败或没有锚点则回退到scrollTop恢复
const scrollKey = `scrollPos_${modePrefix}${docIdForLocalStorage}_${currentVisibleTabId}`;
const savedScrollTop = localStorage.getItem(scrollKey);
console.log(`[restoreScrollPosition] (回退)尝试恢复scrollTop: ${scrollKey}, 保存的值: ${savedScrollTop}`);
if (savedScrollTop !== null && !isNaN(parseInt(savedScrollTop, 10))) {
const scrollableElement = getCurrentScrollableElementForHistoryDetail();
if (scrollableElement) {
console.log(`[restoreScrollPosition] (回退)找到可滚动元素:`, {
元素ID: scrollableElement.id || '无ID',
元素类名: scrollableElement.className || '无类名',
元素标签: scrollableElement.tagName,
当前scrollTop: scrollableElement.scrollTop,
将要设置的scrollTop: parseInt(savedScrollTop, 10)
});
requestAnimationFrame(() => {
if (localStorage.getItem(`activeTab_${docIdForLocalStorage}`) === currentVisibleTabId) {
const scrollTopToSet = parseInt(savedScrollTop, 10);
scrollableElement.scrollTop = scrollTopToSet;
setTimeout(() => {
if (Math.abs(scrollTopToSet - scrollableElement.scrollTop) > 5) {
console.warn(`[restoreScrollPosition] (回退)警告: scrollTop设置可能未生效! 尝试再次设置...`);
scrollableElement.scrollTop = scrollTopToSet;
}
// 即使是scrollTop恢复也保存一次这可能会更新锚点如果之前没有
saveScrollPosition();
}, 100);
}
});
} else {
console.warn(`[restoreScrollPosition] (回退)未找到可滚动元素无法恢复scrollTop`);
}
} else {
console.log(`[restoreScrollPosition] (回退)没有保存的scrollTop或值无效: ${savedScrollTop}`);
}
} else {
console.log(`[restoreScrollPosition] 缺少必要参数: docId=${docIdForLocalStorage}, tabId=${currentVisibleTabId}`);
}
// 新增:每次恢复滚动后都绑定 scroll 事件到正确容器
if (window.DockLogic && typeof window.DockLogic.bindScrollForCurrentScrollable === 'function') {
window.DockLogic.bindScrollForCurrentScrollable();
}
}
// Listen for immersive mode changes
document.addEventListener('immersiveModeEntered', function() {
setTimeout(() => {
console.log("[HistoryDetail] 进入沉浸模式事件触发");
const normalModeAnchorKey = `scrollAnchorId_normal_${docIdForLocalStorage}_${currentVisibleTabId}`;
const savedNormalAnchorId = localStorage.getItem(normalModeAnchorKey);
let restoredByAnchor = false;
if (savedNormalAnchorId) {
const targetElement = document.getElementById(savedNormalAnchorId);
if (targetElement && document.body.contains(targetElement)) { // 确保元素在当前DOM中
const immersiveScrollableElement = getCurrentScrollableElementForHistoryDetail();
if(immersiveScrollableElement && immersiveScrollableElement.contains(targetElement)){
console.log(`[HistoryDetail-ImmersiveEnter] 应用普通模式锚点 ${savedNormalAnchorId} 到沉浸式元素`);
targetElement.scrollIntoView({ behavior: 'auto', block: 'start' });
restoredByAnchor = true;
} else {
console.warn("[HistoryDetail-ImmersiveEnter] 锚点元素不在当前沉浸模式滚动容器内,无法精确恢复。");
}
} else {
console.warn("[HistoryDetail-ImmersiveEnter] 普通模式锚点 ${savedNormalAnchorId} 在沉浸模式DOM中未找到。");
}
}
if (restoredByAnchor) {
// 锚点恢复成功立即为沉浸模式保存当前状态包括scrollTop和新计算的锚点
console.log("[HistoryDetail-ImmersiveEnter] 通过锚点恢复成功,立即为沉浸模式保存状态");
saveScrollPosition();
} else {
console.log("[HistoryDetail-ImmersiveEnter] 普通模式锚点恢复失败或无锚点,尝试恢复上次沉浸式位置(锚点优先后scrollTop)");
restoreScrollPositionForCurrentTab(); // 会尝试恢复 immersive_ 锚点,然后 immersive_ scrollTop
}
if (window.DockLogic) {
if (typeof window.DockLogic.forceUpdateReadingProgress === 'function') {
window.DockLogic.forceUpdateReadingProgress();
}
if (typeof window.DockLogic.bindScrollForCurrentScrollable === 'function') {
window.DockLogic.bindScrollForCurrentScrollable();
}
}
// 重新绑定滚动事件以保存滚动位置
if (typeof bindScrollForSavePosition === 'function') {
bindScrollForSavePosition();
}
}, 450); // 增加延迟确保DOM和TOC更新
});
document.addEventListener('immersiveModeExited', function() {
setTimeout(() => {
console.log("[HistoryDetail] 退出沉浸模式事件触发");
const immersiveModeAnchorKey = `scrollAnchorId_immersive_${docIdForLocalStorage}_${currentVisibleTabId}`;
const savedImmersiveAnchorId = localStorage.getItem(immersiveModeAnchorKey);
let restoredByAnchor = false;
if (savedImmersiveAnchorId) {
const targetElement = document.getElementById(savedImmersiveAnchorId);
// 退出沉浸模式后,内容移回主容器,检查 targetElement 是否在 document.body (或其他主内容区) 中
if (targetElement && document.body.contains(targetElement)) {
const normalScrollableElement = getCurrentScrollableElementForHistoryDetail();
if(normalScrollableElement && (normalScrollableElement === document.documentElement || normalScrollableElement.contains(targetElement))){
console.log(`[HistoryDetail-ImmersiveExit] 应用沉浸模式锚点 ${savedImmersiveAnchorId} 到普通模式元素`);
targetElement.scrollIntoView({ behavior: 'auto', block: 'start' });
restoredByAnchor = true;
} else {
console.warn("[HistoryDetail-ImmersiveExit] 锚点元素不在当前普通模式滚动容器内,无法精确恢复。");
}
} else {
console.warn("[HistoryDetail-ImmersiveExit] 沉浸模式锚点 ${savedImmersiveAnchorId} 在普通模式DOM中未找到。");
}
}
if (restoredByAnchor) {
// 锚点恢复成功,立即为普通模式保存当前状态
console.log("[HistoryDetail-ImmersiveExit] 通过锚点恢复成功,立即为普通模式保存状态");
saveScrollPosition();
} else {
console.log("[HistoryDetail-ImmersiveExit] 沉浸模式锚点恢复失败或无锚点,尝试恢复上次普通模式位置(锚点优先后scrollTop)");
restoreScrollPositionForCurrentTab(); // 会尝试恢复 normal_ 锚点,然后 normal_ scrollTop
}
if (window.DockLogic) {
if (typeof window.DockLogic.forceUpdateReadingProgress === 'function') {
window.DockLogic.forceUpdateReadingProgress();
}
if (typeof window.DockLogic.bindScrollForCurrentScrollable === 'function') {
window.DockLogic.bindScrollForCurrentScrollable();
}
}
// 重新绑定滚动事件以保存滚动位置
if (typeof bindScrollForSavePosition === 'function') {
bindScrollForSavePosition();
}
}, 450); // 增加延迟
});
window.addEventListener('beforeunload', function cleanupLargeRefs() {
try {
if (window.ChunkCompareOptimizer && window.ChunkCompareOptimizer.instance && typeof window.ChunkCompareOptimizer.instance.cleanup === 'function') {
window.ChunkCompareOptimizer.instance.cleanup();
}
} catch {}
try {
if (window.chunkParseCache) {
Object.keys(window.chunkParseCache).forEach(k => delete window.chunkParseCache[k]);
}
} catch {}
try {
if (typeof window.__lastChunkCompareTotalBlocks === 'number') {
for (let i = 0; i <= window.__lastChunkCompareTotalBlocks; i++) {
delete window[`blockRawContent_${i}`];
}
}
} catch {}
window.largeDocumentData = null;
});
// 自动恢复 globalCurrentContentIdentifier
(function restoreContentIdentifierFromLocalStorage() {
try {
// 优先用 docIdForLocalStorage如果没有则尝试从URL获取
let docId = window.docIdForLocalStorage;
if (!docId && typeof getQueryParam === 'function') {
docId = getQueryParam('id');
}
if (!docId) return;
const savedTabKey = `activeTab_${docId}`;
const savedTab = localStorage.getItem(savedTabKey);
if (savedTab === 'ocr' || savedTab === 'translation') {
window.globalCurrentContentIdentifier = savedTab;
console.log('[auto-restore] 恢复 globalCurrentContentIdentifier =', savedTab);
}
} catch (e) {
// 忽略
}
})();