|
@@ -192,20 +192,47 @@
|
|
|
min-height: 0;
|
|
min-height: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- #readerControls.collapsed {
|
|
|
|
|
- padding: 10px 8px;
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ #readerControls.collapsed {
|
|
|
|
|
+ padding: 10px 8px;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
body.reader-position-left #readerControls.collapsed,
|
|
body.reader-position-left #readerControls.collapsed,
|
|
|
body.reader-position-right #readerControls.collapsed {
|
|
body.reader-position-right #readerControls.collapsed {
|
|
|
- width: calc(var(--reader-controls-size) + var(--reader-controls-padding) * 2);
|
|
|
|
|
|
|
+ width: 0;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ box-shadow: none;
|
|
|
|
|
+ overflow: visible;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #readerControls.collapsed .reader-tool:not(.reader-tool-collapse),
|
|
|
|
|
+ #readerControls.collapsed .reader-settings-launcher {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #readerControls.collapsed #readerControlsSpacer,
|
|
|
|
|
+ #readerControls.collapsed .reader-tools,
|
|
|
|
|
+ #readerControls.collapsed .reader-settings-anchor {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #readerControls.collapsed .reader-tool-collapse {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ z-index: 30;
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ body.reader-controls-collapsed #sidebarContainer {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ body.reader-controls-collapsed #outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode) {
|
|
|
|
|
+ inset-inline-start: 0;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- #readerControls.collapsed .reader-tool:not(.reader-tool-collapse),
|
|
|
|
|
- #readerControls.collapsed .reader-settings-launcher {
|
|
|
|
|
- display: none;
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
.reader-tools {
|
|
.reader-tools {
|
|
|
display: flex;
|
|
display: flex;
|
|
@@ -312,8 +339,13 @@
|
|
|
order: 2;
|
|
order: 2;
|
|
|
border-right: none;
|
|
border-right: none;
|
|
|
border-left: 1px solid var(--reader-controls-border);
|
|
border-left: 1px solid var(--reader-controls-border);
|
|
|
- box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.7);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.7);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ body.reader-position-right #readerControls.collapsed .reader-tool-collapse {
|
|
|
|
|
+ left: auto;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
.reader-settings-panel {
|
|
.reader-settings-panel {
|
|
|
display: none;
|
|
display: none;
|
|
@@ -1140,24 +1172,30 @@
|
|
|
<span class="reader-tool-label">整页</span>
|
|
<span class="reader-tool-label">整页</span>
|
|
|
</span>
|
|
</span>
|
|
|
</button>
|
|
</button>
|
|
|
- <button id="pause-play-button" class="reader-tool" type="button" title="暂停播放" aria-label="暂停播放">
|
|
|
|
|
- <span class="reader-tool-stack">
|
|
|
|
|
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5v14"/><path d="M15 5v14"/></svg>
|
|
|
|
|
- <span class="reader-tool-label">暂停</span>
|
|
|
|
|
- </span>
|
|
|
|
|
- </button>
|
|
|
|
|
- <button id="stop-play-button" class="reader-tool reader-tool-danger" type="button" title="停止播放" aria-label="停止播放">
|
|
|
|
|
- <span class="reader-tool-stack">
|
|
|
|
|
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 7h10v10H7z"/></svg>
|
|
|
|
|
- <span class="reader-tool-label">停止</span>
|
|
|
|
|
- </span>
|
|
|
|
|
- </button>
|
|
|
|
|
- <button id="toggle-text-select" class="reader-tool reader-tool-accent" type="button" title="文本选择" aria-label="文本选择">
|
|
|
|
|
- <span class="reader-tool-stack">
|
|
|
|
|
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7V4h3"/><path d="M20 7V4h-3"/><path d="M4 17v3h3"/><path d="M20 17v3h-3"/><path d="M8 12h8"/></svg>
|
|
|
|
|
- <span class="reader-tool-label">选读</span>
|
|
|
|
|
- </span>
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <button id="pause-play-button" class="reader-tool" type="button" title="暂停播放" aria-label="暂停播放">
|
|
|
|
|
+ <span class="reader-tool-stack">
|
|
|
|
|
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5v14"/><path d="M15 5v14"/></svg>
|
|
|
|
|
+ <span class="reader-tool-label">暂停</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button id="stop-play-button" class="reader-tool reader-tool-danger" type="button" title="停止播放" aria-label="停止播放">
|
|
|
|
|
+ <span class="reader-tool-stack">
|
|
|
|
|
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 7h10v10H7z"/></svg>
|
|
|
|
|
+ <span class="reader-tool-label">停止</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button id="toggle-text-select" class="reader-tool reader-tool-accent" type="button" title="文本选择" aria-label="文本选择">
|
|
|
|
|
+ <span class="reader-tool-stack">
|
|
|
|
|
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 5h12"/><path d="M12 5v14"/></svg>
|
|
|
|
|
+ <span class="reader-tool-label">选读</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button id="toggle-browser-fullscreen" class="reader-tool" type="button" title="浏览器全屏" aria-label="浏览器全屏">
|
|
|
|
|
+ <span class="reader-tool-stack">
|
|
|
|
|
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7V4h3"/><path d="M20 7V4h-3"/><path d="M4 17v3h3"/><path d="M20 17v3h-3"/><path d="M8 12h8"/></svg>
|
|
|
|
|
+ <span class="reader-tool-label">全屏</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
<div id="readerControlsSpacer"></div>
|
|
<div id="readerControlsSpacer"></div>
|
|
|
<div class="reader-settings-anchor">
|
|
<div class="reader-settings-anchor">
|
|
@@ -1522,22 +1560,25 @@
|
|
|
const userMenuTrigger = document.getElementById('userMenuTrigger');
|
|
const userMenuTrigger = document.getElementById('userMenuTrigger');
|
|
|
const userMenuPanel = document.getElementById('userMenuPanel');
|
|
const userMenuPanel = document.getElementById('userMenuPanel');
|
|
|
const userLogoutBtn = document.getElementById('userLogoutBtn');
|
|
const userLogoutBtn = document.getElementById('userLogoutBtn');
|
|
|
- const userAdminBtn = document.getElementById('userAdminBtn');
|
|
|
|
|
- const readerControls = document.getElementById('readerControls');
|
|
|
|
|
- const readerControlsCollapse = document.getElementById('readerControlsCollapse');
|
|
|
|
|
- const readerSettingsButton = document.getElementById('readerSettingsButton');
|
|
|
|
|
- const readerSettingsPanel = document.getElementById('readerSettingsPanel');
|
|
|
|
|
-
|
|
|
|
|
- let selectedFile = null;
|
|
|
|
|
- let currentPage = 1;
|
|
|
|
|
- const itemsPerPage = 10;
|
|
|
|
|
- let allFiles = [];
|
|
|
|
|
- let selectedStartContext = null;
|
|
|
|
|
- const currentFilePath = new URLSearchParams(window.location.search).get('file') || '';
|
|
|
|
|
- let isRestoringProgress = false;
|
|
|
|
|
- const NIGHT_MODE_STORAGE_KEY = 'reader_pro.night_mode';
|
|
|
|
|
- const READER_SIZE_STORAGE_KEY = 'reader_pro.controls.size';
|
|
|
|
|
- const READER_COLLAPSED_STORAGE_KEY = 'reader_pro.controls.collapsed';
|
|
|
|
|
|
|
+ const userAdminBtn = document.getElementById('userAdminBtn');
|
|
|
|
|
+ const readerControls = document.getElementById('readerControls');
|
|
|
|
|
+ const readerControlsCollapse = document.getElementById('readerControlsCollapse');
|
|
|
|
|
+ const readerSettingsButton = document.getElementById('readerSettingsButton');
|
|
|
|
|
+ const readerSettingsPanel = document.getElementById('readerSettingsPanel');
|
|
|
|
|
+ const sidebarToggleButton = document.getElementById('sidebarToggleButton');
|
|
|
|
|
+ const browserFullscreenButton = document.getElementById('toggle-browser-fullscreen');
|
|
|
|
|
+
|
|
|
|
|
+ let selectedFile = null;
|
|
|
|
|
+ let currentPage = 1;
|
|
|
|
|
+ const itemsPerPage = 10;
|
|
|
|
|
+ let allFiles = [];
|
|
|
|
|
+ let selectedStartContext = null;
|
|
|
|
|
+ let sidebarWasOpenBeforeReaderCollapse = false;
|
|
|
|
|
+ const currentFilePath = new URLSearchParams(window.location.search).get('file') || '';
|
|
|
|
|
+ let isRestoringProgress = false;
|
|
|
|
|
+ const NIGHT_MODE_STORAGE_KEY = 'reader_pro.night_mode';
|
|
|
|
|
+ const READER_SIZE_STORAGE_KEY = 'reader_pro.controls.size';
|
|
|
|
|
+ const READER_COLLAPSED_STORAGE_KEY = 'reader_pro.controls.collapsed';
|
|
|
|
|
|
|
|
function updateReaderToggleIcon() {
|
|
function updateReaderToggleIcon() {
|
|
|
const icon = readerControlsCollapse.querySelector('svg path');
|
|
const icon = readerControlsCollapse.querySelector('svg path');
|
|
@@ -1552,12 +1593,24 @@
|
|
|
icon.setAttribute('d', directionMap[position] || directionMap.left);
|
|
icon.setAttribute('d', directionMap[position] || directionMap.left);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function setReaderControlsCollapsed(collapsed) {
|
|
|
|
|
- readerControls.classList.toggle('collapsed', collapsed);
|
|
|
|
|
- readerControlsCollapse.setAttribute('aria-expanded', String(!collapsed));
|
|
|
|
|
- localStorage.setItem(READER_COLLAPSED_STORAGE_KEY, collapsed ? '1' : '0');
|
|
|
|
|
- updateReaderToggleIcon();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ function setReaderControlsCollapsed(collapsed) {
|
|
|
|
|
+ readerControls.classList.toggle('collapsed', collapsed);
|
|
|
|
|
+ readerControlsCollapse.setAttribute('aria-expanded', String(!collapsed));
|
|
|
|
|
+ document.body.classList.toggle('reader-controls-collapsed', collapsed);
|
|
|
|
|
+
|
|
|
|
|
+ if (collapsed) {
|
|
|
|
|
+ sidebarWasOpenBeforeReaderCollapse = sidebarToggleButton.getAttribute('aria-expanded') === 'true';
|
|
|
|
|
+ if (sidebarWasOpenBeforeReaderCollapse) {
|
|
|
|
|
+ sidebarToggleButton.click();
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (sidebarWasOpenBeforeReaderCollapse &&
|
|
|
|
|
+ sidebarToggleButton.getAttribute('aria-expanded') !== 'true') {
|
|
|
|
|
+ sidebarToggleButton.click();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ localStorage.setItem(READER_COLLAPSED_STORAGE_KEY, collapsed ? '1' : '0');
|
|
|
|
|
+ updateReaderToggleIcon();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
function setReaderControlsPosition(position) {
|
|
function setReaderControlsPosition(position) {
|
|
|
const normalized = ['left', 'right'].includes(position) ? position : 'left';
|
|
const normalized = ['left', 'right'].includes(position) ? position : 'left';
|
|
@@ -1613,10 +1666,61 @@
|
|
|
localStorage.setItem(pdfjsPreferencesKey, JSON.stringify(preferences));
|
|
localStorage.setItem(pdfjsPreferencesKey, JSON.stringify(preferences));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function initNightMode() {
|
|
|
|
|
- const enabled = localStorage.getItem(NIGHT_MODE_STORAGE_KEY) === '1';
|
|
|
|
|
- setNightMode(enabled);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ function initNightMode() {
|
|
|
|
|
+ const enabled = localStorage.getItem(NIGHT_MODE_STORAGE_KEY) === '1';
|
|
|
|
|
+ setNightMode(enabled);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function getFullscreenElement() {
|
|
|
|
|
+ return document.fullscreenElement ||
|
|
|
|
|
+ document.webkitFullscreenElement ||
|
|
|
|
|
+ document.mozFullScreenElement ||
|
|
|
|
|
+ document.msFullscreenElement ||
|
|
|
|
|
+ null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function updateBrowserFullscreenButton() {
|
|
|
|
|
+ const isFullscreen = !!getFullscreenElement();
|
|
|
|
|
+ browserFullscreenButton.classList.toggle('is-active', isFullscreen);
|
|
|
|
|
+ browserFullscreenButton.title = isFullscreen ? '退出浏览器全屏' : '浏览器全屏';
|
|
|
|
|
+ browserFullscreenButton.setAttribute('aria-label', isFullscreen ? '退出浏览器全屏' : '浏览器全屏');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function toggleBrowserFullscreen() {
|
|
|
|
|
+ const target = document.documentElement;
|
|
|
|
|
+ const isFullscreen = !!getFullscreenElement();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (isFullscreen) {
|
|
|
|
|
+ if (document.exitFullscreen) {
|
|
|
|
|
+ await document.exitFullscreen();
|
|
|
|
|
+ } else if (document.webkitExitFullscreen) {
|
|
|
|
|
+ document.webkitExitFullscreen();
|
|
|
|
|
+ } else if (document.mozCancelFullScreen) {
|
|
|
|
|
+ document.mozCancelFullScreen();
|
|
|
|
|
+ } else if (document.msExitFullscreen) {
|
|
|
|
|
+ document.msExitFullscreen();
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (target.requestFullscreen) {
|
|
|
|
|
+ await target.requestFullscreen({ navigationUI: 'hide' });
|
|
|
|
|
+ } else if (target.webkitRequestFullscreen) {
|
|
|
|
|
+ target.webkitRequestFullscreen();
|
|
|
|
|
+ } else if (target.mozRequestFullScreen) {
|
|
|
|
|
+ target.mozRequestFullScreen();
|
|
|
|
|
+ } else if (target.msRequestFullscreen) {
|
|
|
|
|
+ target.msRequestFullscreen();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert('当前浏览器不支持网页全屏');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.warn('切换浏览器全屏失败:', error);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setTimeout(updateBrowserFullscreenButton, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
async function ensureLoggedIn() {
|
|
async function ensureLoggedIn() {
|
|
|
try {
|
|
try {
|
|
@@ -1658,16 +1762,20 @@
|
|
|
userAdminBtn.addEventListener('click', function () {
|
|
userAdminBtn.addEventListener('click', function () {
|
|
|
window.location.href = '/admin';
|
|
window.location.href = '/admin';
|
|
|
});
|
|
});
|
|
|
- initNightMode();
|
|
|
|
|
- initReaderControls();
|
|
|
|
|
- nightModeButton.addEventListener('click', function () {
|
|
|
|
|
- const nextEnabled = !document.body.classList.contains('night-reading-mode');
|
|
|
|
|
- setNightMode(nextEnabled);
|
|
|
|
|
- window.location.reload();
|
|
|
|
|
- });
|
|
|
|
|
- readerControlsCollapse.addEventListener('click', () => {
|
|
|
|
|
- setReaderControlsCollapsed(!readerControls.classList.contains('collapsed'));
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ initNightMode();
|
|
|
|
|
+ initReaderControls();
|
|
|
|
|
+ updateBrowserFullscreenButton();
|
|
|
|
|
+ nightModeButton.addEventListener('click', function () {
|
|
|
|
|
+ const nextEnabled = !document.body.classList.contains('night-reading-mode');
|
|
|
|
|
+ setNightMode(nextEnabled);
|
|
|
|
|
+ window.location.reload();
|
|
|
|
|
+ });
|
|
|
|
|
+ browserFullscreenButton.addEventListener('click', () => {
|
|
|
|
|
+ toggleBrowserFullscreen();
|
|
|
|
|
+ });
|
|
|
|
|
+ readerControlsCollapse.addEventListener('click', () => {
|
|
|
|
|
+ setReaderControlsCollapsed(!readerControls.classList.contains('collapsed'));
|
|
|
|
|
+ });
|
|
|
readerSettingsButton.addEventListener('click', event => {
|
|
readerSettingsButton.addEventListener('click', event => {
|
|
|
event.stopPropagation();
|
|
event.stopPropagation();
|
|
|
const nextOpen = !readerControls.classList.contains('settings-open');
|
|
const nextOpen = !readerControls.classList.contains('settings-open');
|
|
@@ -1680,9 +1788,13 @@
|
|
|
readerSettingsButton.setAttribute('aria-expanded', 'false');
|
|
readerSettingsButton.setAttribute('aria-expanded', 'false');
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
- readerSettingsPanel.querySelectorAll('[data-size]').forEach(button => {
|
|
|
|
|
- button.addEventListener('click', () => setReaderControlsSize(button.dataset.size));
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ readerSettingsPanel.querySelectorAll('[data-size]').forEach(button => {
|
|
|
|
|
+ button.addEventListener('click', () => setReaderControlsSize(button.dataset.size));
|
|
|
|
|
+ });
|
|
|
|
|
+ document.addEventListener('fullscreenchange', updateBrowserFullscreenButton);
|
|
|
|
|
+ document.addEventListener('webkitfullscreenchange', updateBrowserFullscreenButton);
|
|
|
|
|
+ document.addEventListener('mozfullscreenchange', updateBrowserFullscreenButton);
|
|
|
|
|
+ document.addEventListener('MSFullscreenChange', updateBrowserFullscreenButton);
|
|
|
|
|
|
|
|
async function saveReadingProgress(pageNumber) {
|
|
async function saveReadingProgress(pageNumber) {
|
|
|
if (!currentFilePath || !Number.isInteger(pageNumber) || pageNumber < 1) return;
|
|
if (!currentFilePath || !Number.isInteger(pageNumber) || pageNumber < 1) return;
|