feat: 添加仅阅读按钮;默认进入沉浸模式;关闭沉浸模式的出口;隐藏沉浸模式侧边栏。
This commit is contained in:
parent
b646d9ef39
commit
56cd3108ed
|
|
@ -56,6 +56,8 @@
|
||||||
#immersive-toc-area.immersive-panel {
|
#immersive-toc-area.immersive-panel {
|
||||||
padding: var(--spacing-xs) var(--spacing-sm) 0 var(--spacing-sm);
|
padding: var(--spacing-xs) var(--spacing-sm) 0 var(--spacing-sm);
|
||||||
border-right: 1px solid var(--color-border-light);
|
border-right: 1px solid var(--color-border-light);
|
||||||
|
/* 隐藏沉浸模式侧边栏 */
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#immersive-chatbot-area.immersive-panel {
|
#immersive-chatbot-area.immersive-panel {
|
||||||
|
|
@ -788,6 +790,7 @@ body.immersive-active #immersive-main-content-area .container {
|
||||||
|
|
||||||
#immersive-toc-area {
|
#immersive-toc-area {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#immersive-chatbot-area {
|
#immersive-chatbot-area {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@
|
||||||
color: #94a3b8 !important; /* slate-400 */
|
color: #94a3b8 !important; /* slate-400 */
|
||||||
border: 1px solid #e2e8f0 !important;
|
border: 1px solid #e2e8f0 !important;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
|
||||||
display: flex !important;
|
/* 隐藏沉浸模式的出口 */
|
||||||
|
display: flex;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
justify-content: center !important;
|
justify-content: center !important;
|
||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
|
|
|
||||||
|
|
@ -986,6 +986,7 @@ body.immersive-dragging .immersive-resize-handle {
|
||||||
|
|
||||||
#immersive-toc-area {
|
#immersive-toc-area {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#immersive-chatbot-area {
|
#immersive-chatbot-area {
|
||||||
|
|
|
||||||
|
|
@ -1327,7 +1327,11 @@
|
||||||
<div class="flex justify-center mt-8 mb-12">
|
<div class="flex justify-center mt-8 mb-12">
|
||||||
<button id="processBtn" class="btn-primary-large px-10 py-4 text-lg font-semibold text-white rounded-2xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]" style="background: var(--color-primary);">
|
<button id="processBtn" class="btn-primary-large px-10 py-4 text-lg font-semibold text-white rounded-2xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]" style="background: var(--color-primary);">
|
||||||
<iconify-icon icon="carbon:rocket" width="24"></iconify-icon>
|
<iconify-icon icon="carbon:rocket" width="24"></iconify-icon>
|
||||||
<span>开始智能处理</span>
|
<span>开始处理</span>
|
||||||
|
</button>
|
||||||
|
<button id="onlyReadBtn" class="btn-primary-large px-10 py-4 text-lg font-semibold text-white rounded-2xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]" style="background: var(--color-primary);">
|
||||||
|
<iconify-icon icon="carbon:rocket" width="24"></iconify-icon>
|
||||||
|
<span>仅阅读</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
103
js/app.js
103
js/app.js
|
|
@ -43,6 +43,24 @@ function escapeHtml(str) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 ArrayBuffer 转换为 Base64 字符串
|
||||||
|
* @param {ArrayBuffer} buffer - 需要转换的 ArrayBuffer
|
||||||
|
* @returns {string} Base64 编码的字符串
|
||||||
|
*/
|
||||||
|
function arrayBufferToBase64(buffer) {
|
||||||
|
if (!buffer) return null;
|
||||||
|
const bytes = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : new Uint8Array(buffer.buffer || []);
|
||||||
|
if (!bytes.length) return null;
|
||||||
|
let binary = '';
|
||||||
|
const chunkSize = 0x8000;
|
||||||
|
for (let i = 0; i < bytes.length; i += chunkSize) {
|
||||||
|
const chunk = bytes.subarray(i, i + chunkSize);
|
||||||
|
binary += String.fromCharCode.apply(null, chunk);
|
||||||
|
}
|
||||||
|
return btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 全局状态变量
|
// 全局状态变量
|
||||||
// =====================
|
// =====================
|
||||||
|
|
@ -523,6 +541,7 @@ function setupEventListeners() {
|
||||||
const githubImportBtn = document.getElementById('githubImportBtn');
|
const githubImportBtn = document.getElementById('githubImportBtn');
|
||||||
const clearBtn = document.getElementById('clearFilesBtn');
|
const clearBtn = document.getElementById('clearFilesBtn');
|
||||||
const processBtn = document.getElementById('processBtn');
|
const processBtn = document.getElementById('processBtn');
|
||||||
|
const onlyReadBtn = document.getElementById('onlyReadBtn');
|
||||||
const downloadBtn = document.getElementById('downloadAllBtn');
|
const downloadBtn = document.getElementById('downloadAllBtn');
|
||||||
const formatFilterContainer = document.getElementById('fileFormatFilters');
|
const formatFilterContainer = document.getElementById('fileFormatFilters');
|
||||||
const batchToggle = document.getElementById('batchModeToggle');
|
const batchToggle = document.getElementById('batchModeToggle');
|
||||||
|
|
@ -752,6 +771,7 @@ function setupEventListeners() {
|
||||||
|
|
||||||
// 处理和下载
|
// 处理和下载
|
||||||
processBtn.addEventListener('click', handleProcessClick);
|
processBtn.addEventListener('click', handleProcessClick);
|
||||||
|
onlyReadBtn.addEventListener('click', handleReadClick);
|
||||||
downloadBtn.addEventListener('click', handleDownloadClick);
|
downloadBtn.addEventListener('click', handleDownloadClick);
|
||||||
|
|
||||||
if (typeof window.updateDeeplxTargetLangHint === 'function') {
|
if (typeof window.updateDeeplxTargetLangHint === 'function') {
|
||||||
|
|
@ -2164,6 +2184,89 @@ function handleDownloadClick() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// 仅阅读(不做处理直接跳转)
|
||||||
|
// =====================
|
||||||
|
/**
|
||||||
|
* 处理点击"仅阅读"按钮的事件。
|
||||||
|
* 如果已有处理结果,直接跳转历史详情。
|
||||||
|
* 如果没有处理结果但有上传文件,则将文件保存到历史记录后跳转。
|
||||||
|
*/
|
||||||
|
async function handleReadClick() {
|
||||||
|
// 1. 优先检查是否有已处理的结果
|
||||||
|
if (allResults.length > 0) {
|
||||||
|
const successfulResult = allResults.find(r => r && r.file && !r.error && !r.skipped);
|
||||||
|
if (successfulResult && successfulResult.file) {
|
||||||
|
const recordId = `${successfulResult.file.name}_${successfulResult.file.size}`;
|
||||||
|
window.location.href = `views/history/history_detail.html?id=${encodeURIComponent(recordId)}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否有上传的文件,直接保存到历史记录
|
||||||
|
if (pdfFiles.length > 0) {
|
||||||
|
const file = pdfFiles[0]; // 只处理第一个文件
|
||||||
|
const recordId = `${file.name}_${file.size}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 读取文件内容为 base64
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
const base64Content = arrayBufferToBase64(arrayBuffer);
|
||||||
|
|
||||||
|
// 获取文件扩展名和类型
|
||||||
|
const ext = file.name.split('.').pop().toLowerCase();
|
||||||
|
const fileType = ext;
|
||||||
|
|
||||||
|
// 创建历史记录对象
|
||||||
|
const record = {
|
||||||
|
id: recordId,
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
ocr: '',
|
||||||
|
translation: '',
|
||||||
|
images: [],
|
||||||
|
ocrChunks: [],
|
||||||
|
translatedChunks: [],
|
||||||
|
fileType: fileType,
|
||||||
|
targetLanguage: '',
|
||||||
|
originalEncoding: 'binary',
|
||||||
|
originalBinary: base64Content,
|
||||||
|
originalExtension: ext,
|
||||||
|
ocrEngine: null,
|
||||||
|
ocrSource: null,
|
||||||
|
translationModelName: 'none',
|
||||||
|
batchId: null,
|
||||||
|
batchOrder: null,
|
||||||
|
batchTotal: null,
|
||||||
|
batchTemplate: null,
|
||||||
|
batchFormats: null,
|
||||||
|
batchStartedAt: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存到数据库
|
||||||
|
if (typeof window.storageAdapter !== 'undefined' && typeof window.storageAdapter.saveResultToDB === 'function') {
|
||||||
|
await window.storageAdapter.saveResultToDB(record);
|
||||||
|
} else if (typeof saveResultToDB === 'function') {
|
||||||
|
await saveResultToDB(record);
|
||||||
|
} else {
|
||||||
|
showNotification('存储功能不可用', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转到历史详情页面
|
||||||
|
window.location.href = `views/history/history_detail.html?id=${encodeURIComponent(recordId)}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存文件到历史记录失败:', error);
|
||||||
|
showNotification(`保存失败: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showNotification('请先上传文件', 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// 内置提示模板获取
|
// 内置提示模板获取
|
||||||
// =====================
|
// =====================
|
||||||
|
|
|
||||||
|
|
@ -218,10 +218,17 @@
|
||||||
isTocDockResizing = false; // Reset flag
|
isTocDockResizing = false; // Reset flag
|
||||||
}
|
}
|
||||||
|
|
||||||
function enterImmersiveMode() {
|
function enterImmersiveMode(options = {}) {
|
||||||
|
const { silent = false } = options;
|
||||||
|
|
||||||
// 检查是否为移动端设备
|
// 检查是否为移动端设备
|
||||||
if (window.innerWidth <= 700) {
|
if (window.innerWidth <= 700) {
|
||||||
console.warn('拒绝在移动端(≤700px)进入沉浸式布局');
|
console.warn('拒绝在移动端(≤700px)进入沉浸式布局');
|
||||||
|
// 即使不进入沉浸模式,也需要显示页面
|
||||||
|
document.documentElement.classList.remove('immersive-pending');
|
||||||
|
document.documentElement.classList.add('immersive-ready');
|
||||||
|
document.body.classList.remove('immersive-pending');
|
||||||
|
document.body.classList.add('immersive-ready');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,19 +245,41 @@
|
||||||
if (!immersiveChatbotArea) missingElements.push('immersiveChatbotArea');
|
if (!immersiveChatbotArea) missingElements.push('immersiveChatbotArea');
|
||||||
if (!immersiveDockPlaceholderElement) missingElements.push('immersiveDockPlaceholderElement (logic error if this happens)');
|
if (!immersiveDockPlaceholderElement) missingElements.push('immersiveDockPlaceholderElement (logic error if this happens)');
|
||||||
|
|
||||||
|
console.log('[enterImmersiveMode] 元素检查:', {
|
||||||
|
immersiveContainer: !!immersiveContainer,
|
||||||
|
mainPageContainer: !!mainPageContainer,
|
||||||
|
tocPopupElement: !!tocPopupElement,
|
||||||
|
chatbotModalElement: !!chatbotModalElement,
|
||||||
|
dockElement: !!dockElement,
|
||||||
|
immersiveTocArea: !!immersiveTocArea,
|
||||||
|
immersiveMainArea: !!immersiveMainArea,
|
||||||
|
immersiveChatbotArea: !!immersiveChatbotArea
|
||||||
|
});
|
||||||
|
|
||||||
if (missingElements.length > 0) {
|
if (missingElements.length > 0) {
|
||||||
console.warn('Immersive mode elements not found:', missingElements.join(', '));
|
console.warn('Immersive mode elements not found:', missingElements.join(', '));
|
||||||
|
// 元素缺失时也需要显示页面
|
||||||
|
document.body.classList.remove('immersive-pending');
|
||||||
|
document.body.classList.add('immersive-ready');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isImmersiveActive = true;
|
isImmersiveActive = true;
|
||||||
storeOriginalPositions();
|
storeOriginalPositions();
|
||||||
|
|
||||||
|
// 静默模式:跳过动画,直接设置状态
|
||||||
|
if (silent) {
|
||||||
|
document.body.classList.add('immersive-active', 'no-scroll');
|
||||||
|
immersiveContainer.style.display = 'flex';
|
||||||
|
immersiveContainer.style.opacity = '1';
|
||||||
|
immersiveContainer.style.transform = 'scale(1)';
|
||||||
|
} else {
|
||||||
// 添加进入动画类
|
// 添加进入动画类
|
||||||
document.body.classList.add('immersive-entering');
|
document.body.classList.add('immersive-entering');
|
||||||
immersiveContainer.style.opacity = '0';
|
immersiveContainer.style.opacity = '0';
|
||||||
immersiveContainer.style.transform = 'scale(0.95)';
|
immersiveContainer.style.transform = 'scale(0.95)';
|
||||||
immersiveContainer.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
|
immersiveContainer.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
|
||||||
|
}
|
||||||
|
|
||||||
// Append TOC content first
|
// Append TOC content first
|
||||||
if (tocPopupElement && immersiveTocArea) {
|
if (tocPopupElement && immersiveTocArea) {
|
||||||
|
|
@ -305,6 +334,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 静默模式下已经设置了 display 和 opacity,跳过动画
|
||||||
|
if (!silent) {
|
||||||
document.body.classList.add('immersive-active', 'no-scroll');
|
document.body.classList.add('immersive-active', 'no-scroll');
|
||||||
immersiveContainer.style.display = 'flex';
|
immersiveContainer.style.display = 'flex';
|
||||||
|
|
||||||
|
|
@ -325,6 +356,13 @@
|
||||||
immersiveContainer.style.transition = '';
|
immersiveContainer.style.transition = '';
|
||||||
}, 400);
|
}, 400);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示页面(移除 pending 状态)
|
||||||
|
document.documentElement.classList.remove('immersive-pending');
|
||||||
|
document.documentElement.classList.add('immersive-ready');
|
||||||
|
document.body.classList.remove('immersive-pending');
|
||||||
|
document.body.classList.add('immersive-ready');
|
||||||
|
|
||||||
if (toggleBtn) {
|
if (toggleBtn) {
|
||||||
toggleBtn.innerHTML = '<i class="fas fa-compress-alt"></i>';
|
toggleBtn.innerHTML = '<i class="fas fa-compress-alt"></i>';
|
||||||
|
|
@ -393,7 +431,6 @@
|
||||||
localStorage.setItem(LS_IMMERSIVE_KEY, 'true');
|
localStorage.setItem(LS_IMMERSIVE_KEY, 'true');
|
||||||
document.dispatchEvent(new CustomEvent('immersiveModeEntered'));
|
document.dispatchEvent(new CustomEvent('immersiveModeEntered'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function exitImmersiveMode() {
|
function exitImmersiveMode() {
|
||||||
reQueryDynamicElements();
|
reQueryDynamicElements();
|
||||||
isImmersiveActive = false;
|
isImmersiveActive = false;
|
||||||
|
|
@ -758,21 +795,36 @@
|
||||||
|
|
||||||
// Restore immersive state from localStorage
|
// Restore immersive state from localStorage
|
||||||
const savedImmersiveState = localStorage.getItem(LS_IMMERSIVE_KEY);
|
const savedImmersiveState = localStorage.getItem(LS_IMMERSIVE_KEY);
|
||||||
if (savedImmersiveState === 'true') {
|
|
||||||
|
// 历史详情页默认进入沉浸模式,只有用户明确退出过才不进入
|
||||||
|
// 注意:null 表示首次访问,'true' 表示用户之前处于沉浸模式
|
||||||
|
// 只有 'false' 才表示用户主动退出过沉浸模式
|
||||||
|
const shouldEnterImmersive = savedImmersiveState !== 'false';
|
||||||
|
|
||||||
|
console.log('[ImmersiveLayout] savedImmersiveState:', savedImmersiveState, 'shouldEnterImmersive:', shouldEnterImmersive);
|
||||||
|
|
||||||
|
if (shouldEnterImmersive) {
|
||||||
// 检查是否为移动端,如果是则不恢复沉浸模式
|
// 检查是否为移动端,如果是则不恢复沉浸模式
|
||||||
if (window.innerWidth <= 700) {
|
if (window.innerWidth <= 700) {
|
||||||
console.log('检测到移动端设备,不恢复沉浸式布局状态');
|
console.log('检测到移动端设备,不进入沉浸式布局');
|
||||||
localStorage.setItem(LS_IMMERSIVE_KEY, 'false'); // 清除保存的状态
|
localStorage.setItem(LS_IMMERSIVE_KEY, 'false');
|
||||||
return;
|
// 显示页面
|
||||||
|
document.documentElement.classList.remove('immersive-pending');
|
||||||
|
document.documentElement.classList.add('immersive-ready');
|
||||||
|
document.body.classList.remove('immersive-pending');
|
||||||
|
document.body.classList.add('immersive-ready');
|
||||||
|
} else {
|
||||||
|
// 立即进入沉浸模式(静默模式,无动画)
|
||||||
|
console.log('[ImmersiveLayout] 准备进入沉浸模式, isImmersiveActive:', isImmersiveActive);
|
||||||
|
enterImmersiveMode({ silent: true });
|
||||||
|
console.log('[ImmersiveLayout] enterImmersiveMode 调用完成, isImmersiveActive:', isImmersiveActive);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Slight delay to ensure other initializations (like TOC, Chatbot) can occur first
|
// 不需要沉浸模式,直接显示页面
|
||||||
// especially if they also interact with elements moved by immersive mode.
|
document.documentElement.classList.remove('immersive-pending');
|
||||||
setTimeout(() => {
|
document.documentElement.classList.add('immersive-ready');
|
||||||
if (!isImmersiveActive) { // Check again in case of race conditions or manual toggle
|
document.body.classList.remove('immersive-pending');
|
||||||
enterImmersiveMode();
|
document.body.classList.add('immersive-ready');
|
||||||
}
|
|
||||||
}, 200); // Adjust delay if needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore simple immersive state from localStorage
|
// Restore simple immersive state from localStorage
|
||||||
|
|
|
||||||
|
|
@ -86,10 +86,27 @@
|
||||||
<link rel="stylesheet" href="../../css/history_detail.css" />
|
<link rel="stylesheet" href="../../css/history_detail.css" />
|
||||||
|
|
||||||
<!-- All CSS now imported via history_detail.css (Phase 9 完成) -->
|
<!-- All CSS now imported via history_detail.css (Phase 9 完成) -->
|
||||||
|
|
||||||
|
<!-- 页面初始隐藏,等待沉浸模式初始化 -->
|
||||||
|
<style>
|
||||||
|
html.immersive-pending body,
|
||||||
|
body.immersive-pending {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
html.immersive-ready body,
|
||||||
|
body.immersive-ready {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
// 在 DOM 解析前就添加类,避免闪烁
|
||||||
|
document.documentElement.classList.add('immersive-pending');
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="immersive-pending">
|
||||||
<!-- Standard Immersive Mode Toggle Button -->
|
<!-- Standard Immersive Mode Toggle Button -->
|
||||||
<button id="toggle-immersive-btn" class="tiny-round-btn" title="进入沉浸式布局">
|
<button id="toggle-immersive-btn" class="tiny-round-btn hidden" style="display: none;" title="进入沉浸式布局">
|
||||||
<i class="fas fa-expand-alt"></i>
|
<i class="fas fa-expand-alt"></i>
|
||||||
<!-- Default icon for entering immersive mode -->
|
<!-- Default icon for entering immersive mode -->
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -400,9 +417,9 @@
|
||||||
<h2 id="fileName">历史详情</h2>
|
<h2 id="fileName">历史详情</h2>
|
||||||
<div class="meta" id="fileMeta">
|
<div class="meta" id="fileMeta">
|
||||||
<div class="meta-info">
|
<div class="meta-info">
|
||||||
<span id="fileMetaTime"></span>
|
<!-- <span id="fileMetaTime"></span>
|
||||||
<span class="meta-separator">|</span>
|
<span class="meta-separator">|</span>
|
||||||
<span id="fileMetaImages"></span>
|
<span id="fileMetaImages"></span> -->
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="meta-export-trigger"
|
class="meta-export-trigger"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue