- // ==UserScript==
- // @name Enhanced Exhentai Record (Optimized)
- // @namespace http://tampermonkey.net/
- // @version 4.1
- // @description 增強型 Exhentai 記錄腳本,優化加載進度和閱讀體驗,支持後台加載
- // @author You
- // @match https://exhentai.org/watched*
- // @icon https://www.google.com/s2/favicons?domain=exhentai.org
- // @grant none
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // 配置選項
- const CONFIG = {
- autoHideRecorded: true, // 自動隱藏已記錄項目
- loadDelay: 800, // 加載下一頁的延遲(毫秒)
- toastDuration: 3000, // Toast 顯示時間
- storageKey: 'exhentai_record',// 本地存儲鍵名
- continueInBackground: true // 切換頁面時繼續加載
- };
-
- // DOM 元素引用
- let DOM = {
- progressBar: null,
- progressText: null,
- readingProgressBar: null,
- statusArea: null,
- totalCountElem: null,
- pageRecordedElem: null,
- pageUnrecordedElem: null,
- pageHiddenElem: null
- };
-
- // 統計數據
- const STATS = {
- totalProcessed: 0,
- totalAdded: 0,
- totalFiltered: 0,
- currentPage: 1,
- estimatedTotalPages: 0,
- readingProgress: 0
- };
-
- // 加載狀態
- const LOADING_STATE = {
- userPaused: false, // 用戶手動暫停
- backgroundPaused: false, // 因切換到後台而暫停
- processing: false // 正在處理
- };
-
- // SVG 圖標定義
- const ICONS = {
- record: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>',
- toggle: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>',
- download: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>',
- upload: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>',
- loadAll: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="8 17 12 21 16 17"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"></path></svg>',
- info: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>',
- data: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>',
- check: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>',
- uncheck: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line></svg>',
- hidden: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>',
- stop: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/></svg>',
- pause: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>',
- play: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>'
- };
-
- // 樣式定義
- const STYLES = `
- /* 主控制面板 */
- .ex-record-toolbar {
- position: sticky;
- top: 0;
- margin: 0 auto;
- padding: 15px;
- background-color: #333;
- border-radius: 5px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
- box-shadow: 0 3px 10px rgba(0,0,0,0.3);
- z-index: 1000;
- margin-bottom: 15px;
- border: 1px solid #444;
- max-width: 95%;
- }
-
- /* 按鈕樣式 */
- .ex-record-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- margin: 5px;
- padding: 8px 15px;
- background-color: #444;
- color: #eee;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.3s;
- border: none;
- font-weight: bold;
- font-size: 14px;
- min-width: 120px;
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
- }
-
- .ex-record-btn svg {
- margin-right: 8px;
- }
-
- .ex-record-btn:hover {
- background-color: #555;
- transform: translateY(-2px);
- box-shadow: 0 4px 8px rgba(0,0,0,0.3);
- }
-
- .ex-record-btn:active {
- transform: translateY(0);
- box-shadow: 0 1px 3px rgba(0,0,0,0.2);
- }
-
- /* 不同類型的按鈕顏色 */
- .ex-record-add {
- background-color: #1a73e8;
- }
-
- .ex-record-add:hover {
- background-color: #1967d2;
- }
-
- .ex-record-toggle {
- background-color: #34a853;
- }
-
- .ex-record-toggle:hover {
- background-color: #2d9247;
- }
-
- .ex-record-export {
- background-color: #ea4335;
- }
-
- .ex-record-export:hover {
- background-color: #d33426;
- }
-
- .ex-record-import {
- background-color: #fbbc05;
- color: #333;
- }
-
- .ex-record-import:hover {
- background-color: #f0b400;
- }
-
- .ex-record-stop {
- background-color: #ea4335;
- }
-
- .ex-record-stop:hover {
- background-color: #d33426;
- }
-
- /* 信息顯示 */
- .ex-record-info {
- display: inline-flex;
- align-items: center;
- padding: 8px 12px;
- margin: 5px;
- border-radius: 4px;
- background-color: #444;
- color: #eee;
- font-weight: bold;
- border: 1px solid #555;
- box-shadow: 0 1px 3px rgba(0,0,0,0.2);
- }
-
- .ex-record-info svg {
- margin-right: 8px;
- color: #aaa;
- }
-
- /* 標記已記錄項目 */
- .ex-record-highlighted {
- background-color: rgba(26, 115, 232, 0.15) !important;
- border-left: 4px solid #1a73e8 !important;
- }
-
- /* 記錄時間顯示 */
- .ex-record-time {
- font-size: 12px;
- color: #aaa;
- margin-left: 8px;
- display: inline-block;
- padding: 3px 6px;
- background-color: rgba(0, 0, 0, 0.2);
- border-radius: 3px;
- }
-
- /* Toast 消息 */
- .ex-record-toast {
- position: fixed;
- top: 20px;
- right: 20px;
- padding: 12px 20px;
- background-color: rgba(50, 50, 50, 0.9);
- color: #fff;
- border-radius: 4px;
- z-index: 10000;
- animation: ex-record-fadeInOut 3s ease-in-out forwards;
- box-shadow: 0 4px 10px rgba(0,0,0,0.3);
- border-left: 4px solid #1a73e8;
- max-width: 300px;
- }
-
- @keyframes ex-record-fadeInOut {
- 0% { opacity: 0; transform: translateY(-20px); }
- 10% { opacity: 1; transform: translateY(0); }
- 80% { opacity: 1; transform: translateY(0); }
- 100% { opacity: 0; transform: translateY(-20px); }
- }
-
- /* 模態對話框 */
- .ex-record-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.7);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 10001;
- }
-
- .ex-record-modal-content {
- background-color: #333;
- padding: 20px;
- border-radius: 8px;
- width: 80%;
- max-width: 600px;
- box-shadow: 0 5px 15px rgba(0,0,0,0.5);
- color: #eee;
- border: 1px solid #444;
- }
-
- .ex-record-modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- border-bottom: 1px solid #444;
- padding-bottom: 10px;
- }
-
- .ex-record-modal-close {
- background: none;
- border: none;
- font-size: 24px;
- cursor: pointer;
- color: #aaa;
- }
-
- .ex-record-modal-close:hover {
- color: #fff;
- }
-
- .ex-record-modal-body {
- margin-bottom: 15px;
- }
-
- .ex-record-modal textarea {
- width: 100%;
- height: 200px;
- background-color: #222;
- color: #eee;
- border: 1px solid #444;
- padding: 10px;
- border-radius: 4px;
- resize: vertical;
- font-family: monospace;
- }
-
- .ex-record-modal-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
-
- .ex-record-modal-btn {
- padding: 8px 15px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-weight: bold;
- min-width: 80px;
- }
-
- .ex-record-modal-btn-primary {
- background-color: #1a73e8;
- color: white;
- }
-
- .ex-record-modal-btn-primary:hover {
- background-color: #1967d2;
- }
-
- .ex-record-modal-btn-secondary {
- background-color: #444;
- color: #eee;
- }
-
- .ex-record-modal-btn-secondary:hover {
- background-color: #555;
- }
-
- /* 控制面板內的區域 */
- .ex-record-toolbar {
- flex-direction: column;
- padding: 12px 15px;
- }
-
- .ex-record-controls-row {
- display: flex;
- width: 100%;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- }
-
- .ex-record-controls-row:last-child {
- margin-bottom: 0;
- }
-
- .ex-record-controls-left, .ex-record-controls-center, .ex-record-controls-right {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- }
-
- .ex-record-controls-stats {
- flex: 1;
- display: flex;
- flex-wrap: wrap;
- justify-content: flex-start;
- }
-
- .ex-record-controls-center {
- flex-grow: 1;
- justify-content: center;
- margin: 0 10px;
- }
-
- .ex-record-controls-buttons {
- flex: 1;
- display: flex;
- justify-content: center;
- }
-
- .ex-record-controls-data {
- display: flex;
- justify-content: flex-end;
- }
-
- /* 進度條樣式 */
- .ex-record-progress-container {
- position: fixed;
- bottom: 20px;
- right: 20px;
- width: 300px;
- background-color: #333;
- border-radius: 5px;
- padding: 12px;
- box-shadow: 0 3px 10px rgba(0,0,0,0.3);
- border: 1px solid #444;
- z-index: 1000;
- transition: opacity 0.3s ease;
- }
-
- .ex-record-progress-container.hidden {
- opacity: 0;
- pointer-events: none;
- }
-
- .ex-record-progress-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- }
-
- .ex-record-progress-title {
- font-weight: bold;
- color: #eee;
- }
-
- .ex-record-progress-controls {
- display: flex;
- gap: 5px;
- }
-
- .ex-record-progress-btn {
- background: none;
- border: none;
- color: #aaa;
- cursor: pointer;
- padding: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border-radius: 50%;
- transition: all 0.2s;
- }
-
- .ex-record-progress-btn:hover {
- background-color: #444;
- color: #fff;
- }
-
- .ex-record-progress {
- width: 100%;
- height: 6px;
- background-color: #444;
- border-radius: 3px;
- margin: 5px 0;
- overflow: hidden;
- }
-
- .ex-record-progress-bar {
- height: 100%;
- background-color: #1a73e8;
- width: 0%;
- transition: width 0.3s ease;
- }
-
- .ex-record-reading-progress {
- width: 100%;
- height: 6px;
- background-color: #444;
- border-radius: 3px;
- margin: 8px 0 5px 0;
- overflow: hidden;
- }
-
- .ex-record-reading-progress-bar {
- height: 100%;
- background-color: #34a853;
- width: 0%;
- transition: width 0.3s ease;
- }
-
- .ex-record-progress-stats {
- display: flex;
- justify-content: space-between;
- color: #aaa;
- font-size: 12px;
- margin-top: 5px;
- }
-
- .ex-record-progress-text {
- color: #eee;
- font-size: 13px;
- margin: 8px 0;
- }
-
- /* 數據管理下拉選單 */
- .ex-record-controls-right {
- position: relative;
- }
-
- .ex-record-data-buttons {
- position: absolute;
- right: 0;
- top: 100%;
- background-color: #333;
- border-radius: 4px;
- padding: 5px;
- display: none;
- flex-direction: column;
- z-index: 2000;
- box-shadow: 0 3px 8px rgba(0,0,0,0.3);
- border: 1px solid #444;
- min-width: 120px;
- }
-
- .ex-record-controls-right:hover .ex-record-data-buttons {
- display: flex;
- }
-
- .ex-record-data-toggle {
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #444;
- color: #eee;
- padding: 8px 15px;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.3s;
- font-weight: bold;
- font-size: 14px;
- border: none;
- }
-
- .ex-record-data-toggle svg {
- margin-right: 8px;
- }
-
- .ex-record-data-toggle:hover {
- background-color: #555;
- }
- `;
-
- // 工具函數
- const Utils = {
- // 從 localStorage 獲取記錄
- getRecords() {
- try {
- const recordStr = localStorage.getItem(CONFIG.storageKey);
- return recordStr ? JSON.parse(recordStr) : {};
- } catch (e) {
- console.error('解析記錄失敗:', e);
- return {};
- }
- },
-
- // 保存記錄到 localStorage
- saveRecords(records) {
- try {
- localStorage.setItem(CONFIG.storageKey, JSON.stringify(records));
- return true;
- } catch (e) {
- console.error('保存記錄失敗:', e);
- UI.showToast('保存記錄失敗: ' + e.message);
- return false;
- }
- },
-
- // 格式化時間
- formatDate(dateString) {
- try {
- const date = new Date(dateString);
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- const hours = String(date.getHours()).padStart(2, '0');
- const minutes = String(date.getMinutes()).padStart(2, '0');
-
- return `${year}-${month}-${day} ${hours}:${minutes}`;
- } catch (e) {
- return '未知時間';
- }
- },
-
- // 獲取表格主體
- getTableBody() {
- const table = document.querySelector('.itg.glte');
- return table && table.tBodies.length > 0 ? table.tBodies[0] : null;
- },
-
- // 獲取頁面中的所有項目 ID
- getPageItems() {
- const tableBody = this.getTableBody();
- if (!tableBody) return [];
-
- return Array.from(tableBody.rows)
- .map(row => {
- const link = row.querySelector('a');
- if (!link) return null;
- const url = link.href.split("/").filter(i => i !== '');
- return url[url.length - 1] + url[url.length - 2];
- })
- .filter(id => id !== null);
- },
-
- // 從URL獲取項目ID
- getIdFromUrl(url) {
- const parts = url.split("/").filter(i => i !== '');
- return parts[parts.length - 1] + parts[parts.length - 2];
- },
-
- // 估算總頁數
- estimateTotalPages() {
- // 嘗試從分頁器中獲取頁數
- const pager = document.querySelector('.ptt');
- if (pager) {
- const lastPageLink = Array.from(pager.querySelectorAll('a')).pop();
- if (lastPageLink && lastPageLink.textContent) {
- const pageNum = parseInt(lastPageLink.textContent);
- if (!isNaN(pageNum)) {
- return pageNum;
- }
- }
- }
- // 如果無法從頁面獲取,返回預設值
- return 10;
- },
-
- // 獲取當前頁碼
- getCurrentPage() {
- const pager = document.querySelector('.ptt');
- if (pager) {
- const currentPageElement = pager.querySelector('td.ptds');
- if (currentPageElement && currentPageElement.textContent) {
- const pageNum = parseInt(currentPageElement.textContent);
- if (!isNaN(pageNum)) {
- return pageNum;
- }
- }
- }
- return 1;
- },
-
- // 動態調整閱讀進度
- updateReadingProgress() {
- // 計算閱讀進度百分比
- const tableBody = this.getTableBody();
- if (!tableBody) return 0;
-
- const totalItems = tableBody.rows.length;
- if (totalItems === 0) return 0;
-
- // 通過檢測可見區域來判斷閱讀進度
- const viewportHeight = window.innerHeight;
- const viewportTop = window.scrollY;
- const viewportBottom = viewportTop + viewportHeight;
-
- let visibleCount = 0;
-
- Array.from(tableBody.rows).forEach(row => {
- const rect = row.getBoundingClientRect();
- const rowTop = rect.top + viewportTop;
- const rowBottom = rect.bottom + viewportTop;
-
- // 行完全可見或部分可見
- if ((rowTop >= viewportTop && rowTop <= viewportBottom) ||
- (rowBottom >= viewportTop && rowBottom <= viewportBottom) ||
- (rowTop <= viewportTop && rowBottom >= viewportBottom)) {
- visibleCount++;
- }
- // 已經滾動過的行
- else if (rowBottom < viewportTop) {
- visibleCount++;
- }
- });
-
- const progress = Math.min(100, Math.round((visibleCount / totalItems) * 100));
-
- if (DOM.readingProgressBar) {
- DOM.readingProgressBar.style.width = `${progress}%`;
- }
-
- return progress;
- },
-
- // 延時執行函數
- debounce(func, wait) {
- let timeout;
- return function(...args) {
- clearTimeout(timeout);
- timeout = setTimeout(() => func.apply(this, args), wait);
- };
- },
-
- // 可靠的延時函數,即使在後台也能工作
- reliableDelay(ms) {
- return new Promise(resolve => {
- const startTime = Date.now();
- const checkTime = () => {
- const elapsedTime = Date.now() - startTime;
- if (elapsedTime >= ms) {
- resolve();
- } else {
- setTimeout(checkTime, Math.min(100, ms - elapsedTime));
- }
- };
- setTimeout(checkTime, Math.min(100, ms));
- });
- },
-
- // 記錄到控制台
- log(message) {
- console.log(`[ExRecord] ${message}`);
- }
- };
-
- // UI 操作相關
- const UI = {
- // 顯示 Toast 消息
- showToast(message, duration = CONFIG.toastDuration) {
- const toast = document.createElement('div');
- toast.className = 'ex-record-toast';
- toast.textContent = message;
- document.body.appendChild(toast);
-
- setTimeout(() => {
- if (toast.parentNode) {
- document.body.removeChild(toast);
- }
- }, duration);
- },
-
- // 創建進度顯示容器
- createProgressContainer() {
- const container = document.createElement('div');
- container.className = 'ex-record-progress-container';
- container.id = 'ex-record-progress-container';
- container.innerHTML = `
- <div class="ex-record-progress-header">
- <div class="ex-record-progress-title">加載進度</div>
- <div class="ex-record-progress-controls">
- <button class="ex-record-progress-btn" id="ex-record-pause-btn" title="暫停/繼續加載">
- ${ICONS.pause}
- </button>
- <button class="ex-record-progress-btn" id="ex-record-stop-btn" title="停止加載">
- ${ICONS.stop}
- </button>
- </div>
- </div>
- <div class="ex-record-progress-text" id="ex-record-progress-text">準備加載...</div>
- <div class="ex-record-progress">
- <div class="ex-record-progress-bar" id="ex-record-progress-bar"></div>
- </div>
- <div class="ex-record-progress-stats">
- <span id="ex-record-progress-page">頁面: 0/0</span>
- <span id="ex-record-progress-items">已加載: 0</span>
- </div>
- <div class="ex-record-progress-text">閱讀進度</div>
- <div class="ex-record-reading-progress">
- <div class="ex-record-reading-progress-bar" id="ex-record-reading-progress-bar"></div>
- </div>
- <div class="ex-record-progress-stats">
- <span id="ex-record-reading-percent">0%</span>
- <span id="ex-record-new-items">新項目: 0</span>
- </div>
- `;
-
- document.body.appendChild(container);
-
- // 獲取DOM引用
- DOM.progressBar = document.getElementById('ex-record-progress-bar');
- DOM.progressText = document.getElementById('ex-record-progress-text');
- DOM.readingProgressBar = document.getElementById('ex-record-reading-progress-bar');
-
- // 設置暫停/停止按鈕事件
- document.getElementById('ex-record-pause-btn').addEventListener('click', () => {
- this.toggleLoadingPause();
- });
-
- document.getElementById('ex-record-stop-btn').addEventListener('click', () => {
- this.stopLoading();
- });
-
- return container;
- },
-
- // 更新暫停按鈕圖標
- updatePauseButtonIcon(isPaused) {
- const pauseBtn = document.getElementById('ex-record-pause-btn');
- if (pauseBtn) {
- pauseBtn.innerHTML = isPaused ? ICONS.play : ICONS.pause;
- pauseBtn.title = isPaused ? "繼續加載" : "暫停加載";
- }
- },
-
- // 更新加載進度
- updateProgress(percent, currentPage, totalPages, loadedItems) {
- if (DOM.progressBar) {
- DOM.progressBar.style.width = `${percent}%`;
- }
-
- // 更新頁面計數
- const pageCountElement = document.getElementById('ex-record-progress-page');
- if (pageCountElement) {
- pageCountElement.textContent = `頁面: ${currentPage}/${totalPages || '?'}`;
- }
-
- // 更新已加載項目數
- const itemsCountElement = document.getElementById('ex-record-progress-items');
- if (itemsCountElement) {
- itemsCountElement.textContent = `已加載: ${loadedItems}`;
- }
-
- // 更新新項目數
- const newItemsElement = document.getElementById('ex-record-new-items');
- if (newItemsElement) {
- newItemsElement.textContent = `新項目: ${STATS.totalAdded}`;
- }
-
- // 更新閱讀百分比
- const readingPercentElement = document.getElementById('ex-record-reading-percent');
- if (readingPercentElement) {
- const readingPercent = Utils.updateReadingProgress();
- STATS.readingProgress = readingPercent;
- readingPercentElement.textContent = `${readingPercent}%`;
- }
- },
-
- // 更新加載狀態文本
- updateProgressText(text) {
- if (DOM.progressText) {
- DOM.progressText.textContent = text;
- }
- },
-
- // 顯示/隱藏進度容器
- toggleProgressContainer(show = true) {
- const container = document.getElementById('ex-record-progress-container');
- if (container) {
- container.className = show
- ? 'ex-record-progress-container'
- : 'ex-record-progress-container hidden';
- }
- },
-
- // 暫停/繼續加載
- toggleLoadingPause() {
- const loader = PageLoader;
-
- if (LOADING_STATE.userPaused) {
- // 如果是用戶暫停,則恢復
- LOADING_STATE.userPaused = false;
- this.updatePauseButtonIcon(false);
-
- if (!LOADING_STATE.backgroundPaused) {
- // 如果不是因為背景暫停,則恢復加載
- loader.processNextItem();
- this.updateProgressText('繼續加載中...');
- this.showToast('繼續加載');
- } else {
- this.updateProgressText('頁面處於後台,將在返回前台時繼續加載');
- this.showToast('已設置為繼續加載,將在返回前台時恢復');
- }
- } else {
- // 暫停加載
- LOADING_STATE.userPaused = true;
- this.updatePauseButtonIcon(true);
- this.updateProgressText('加載已暫停(用戶手動)');
- this.showToast('加載已暫停');
- }
- },
-
- // 停止加載
- stopLoading() {
- PageLoader.stopLoading();
- LOADING_STATE.userPaused = false;
- LOADING_STATE.backgroundPaused = false;
- this.updatePauseButtonIcon(false);
- this.updateProgressText('加載已停止');
- this.showToast('加載已停止');
-
- // 3秒後隱藏進度條
- setTimeout(() => {
- this.toggleProgressContainer(false);
- }, 3000);
- },
-
- // 創建控制面板
- createControlPanel() {
- const controlPanel = document.createElement('div');
- controlPanel.className = 'ex-record-toolbar';
-
- // 構建控制面板HTML - 分為上下兩行
- controlPanel.innerHTML = `
- <!-- 第一行:數據統計 -->
- <div class="ex-record-controls-row">
- <div class="ex-record-controls-stats">
- <div class="ex-record-info" id="ex-record-total-count">
- ${ICONS.info}總記錄: 0 筆
- </div>
- <div class="ex-record-info" id="ex-record-page-recorded">
- ${ICONS.check}本頁已記錄: 0 筆
- </div>
- <div class="ex-record-info" id="ex-record-page-unrecorded">
- ${ICONS.uncheck}本頁未記錄: 0 筆
- </div>
- <div class="ex-record-info" id="ex-record-page-hidden">
- ${ICONS.hidden}本頁隱藏: 0 筆
- </div>
- </div>
- </div>
-
- <!-- 第二行:操作按鈕 -->
- <div class="ex-record-controls-row">
- <!-- 中間按鈕區域 -->
- <div class="ex-record-controls-buttons">
- <button class="ex-record-btn ex-record-add" id="ex-record-add-btn">
- ${ICONS.record}記錄此頁
- </button>
- <button class="ex-record-btn ex-record-toggle" id="ex-record-toggle-btn">
- ${ICONS.toggle}隱藏/顯示
- </button>
- <button class="ex-record-btn ex-record-add" id="ex-record-load-all-btn">
- ${ICONS.loadAll}加載所有頁面
- </button>
- </div>
-
- <!-- 右側數據管理按鈕 -->
- <div class="ex-record-controls-data">
- <div class="ex-record-controls-right">
- <button class="ex-record-data-toggle" id="ex-record-data-toggle">
- ${ICONS.data}數據管理
- </button>
- <div class="ex-record-data-buttons">
- <button class="ex-record-btn ex-record-export" id="ex-record-export-btn">
- ${ICONS.download}匯出記錄
- </button>
- <button class="ex-record-btn ex-record-import" id="ex-record-import-btn">
- ${ICONS.upload}匯入記錄
- </button>
- </div>
- </div>
- </div>
- </div>
- `;
-
- // 插入到頁面中
- const target = document.querySelector('.searchnav');
- if (target && target.parentNode) {
- target.parentNode.insertBefore(controlPanel, target);
- } else {
- const searchtext = document.querySelector('.searchtext');
- if (searchtext && searchtext.parentNode) {
- searchtext.parentNode.insertBefore(controlPanel, searchtext.nextSibling);
- } else {
- document.body.insertBefore(controlPanel, document.body.firstChild);
- }
- }
-
- // 保存DOM引用
- DOM.totalCountElem = document.getElementById('ex-record-total-count');
- DOM.pageRecordedElem = document.getElementById('ex-record-page-recorded');
- DOM.pageUnrecordedElem = document.getElementById('ex-record-page-unrecorded');
- DOM.pageHiddenElem = document.getElementById('ex-record-page-hidden');
-
- // 綁定按鈕事件
- document.getElementById('ex-record-add-btn').addEventListener('click', () => Record.recordCurrentPage());
- document.getElementById('ex-record-toggle-btn').addEventListener('click', () => Record.toggleRecordedItems());
- document.getElementById('ex-record-load-all-btn').addEventListener('click', () => PageLoader.loadAllPages());
- document.getElementById('ex-record-export-btn').addEventListener('click', () => DataManager.exportRecords());
- document.getElementById('ex-record-import-btn').addEventListener('click', () => DataManager.importRecords());
-
- return controlPanel;
- },
-
- // 更新統計信息顯示
- updateStatsDisplay() {
- const records = Utils.getRecords();
- const recordsCount = Object.keys(records).length;
-
- // 更新記錄總數
- if (DOM.totalCountElem) {
- DOM.totalCountElem.innerHTML = `${ICONS.info}總記錄: ${recordsCount} 筆`;
- }
-
- // 計算並更新當前頁面統計
- const pageItems = Utils.getPageItems();
- const pageRecorded = pageItems.filter(id => records[id]).length;
- const pageUnrecorded = pageItems.length - pageRecorded;
-
- if (DOM.pageRecordedElem) {
- DOM.pageRecordedElem.innerHTML = `${ICONS.check}本頁已記錄: ${pageRecorded} 筆`;
- }
-
- if (DOM.pageUnrecordedElem) {
- DOM.pageUnrecordedElem.innerHTML = `${ICONS.uncheck}本頁未記錄: ${pageUnrecorded} 筆`;
- }
-
- // 統計隱藏數量
- let hiddenCount = 0;
- const tableBody = Utils.getTableBody();
- if (tableBody) {
- Array.from(tableBody.rows).forEach(row => {
- if (row.style.display === "none") {
- hiddenCount++;
- }
- });
- }
-
- if (DOM.pageHiddenElem) {
- DOM.pageHiddenElem.innerHTML = `${ICONS.hidden}本頁隱藏: ${hiddenCount} 筆`;
- }
- },
-
- // 添加樣式到頁面
- addStyles() {
- const styleElement = document.createElement('style');
- styleElement.textContent = STYLES;
- document.head.appendChild(styleElement);
- },
-
- // 創建模態對話框
- createModal(title, content, buttons) {
- const modal = document.createElement('div');
- modal.className = 'ex-record-modal';
- modal.innerHTML = `
- <div class="ex-record-modal-content">
- <div class="ex-record-modal-header">
- <h3>${title}</h3>
- <button class="ex-record-modal-close">×</button>
- </div>
- <div class="ex-record-modal-body">
- ${content}
- </div>
- <div class="ex-record-modal-footer">
- ${buttons.map(btn => `
- <button class="ex-record-modal-btn ${btn.primary ? 'ex-record-modal-btn-primary' : 'ex-record-modal-btn-secondary'}"
- id="${btn.id}">${btn.text}</button>
- `).join('')}
- </div>
- </div>
- `;
-
- document.body.appendChild(modal);
-
- // 綁定關閉按鈕
- const closeBtn = modal.querySelector('.ex-record-modal-close');
- if (closeBtn) {
- closeBtn.addEventListener('click', () => document.body.removeChild(modal));
- }
-
- // 返回modal以供後續處理
- return modal;
- }
- };
-
- // 記錄操作相關
- const Record = {
- // 標記已記錄的項目
- highlightRecorded() {
- const tableBody = Utils.getTableBody();
- if (!tableBody) return;
-
- const records = Utils.getRecords();
-
- Array.from(tableBody.rows).forEach(row => {
- const link = row.querySelector('a');
- if (!link) return;
-
- const url = link.href.split("/").filter(i => i !== '');
- const id = url[url.length - 1] + url[url.length - 2];
-
- if (records[id]) {
- row.classList.add('ex-record-highlighted');
-
- // 添加記錄時間
- const titleElement = row.querySelector('.gl4e');
- if (titleElement && !titleElement.querySelector('.ex-record-time')) {
- const timeSpan = document.createElement('span');
- timeSpan.className = 'ex-record-time';
- // 兼容新舊記錄格式
- const timestamp = records[id].timestamp || records[id].t || '';
- timeSpan.textContent = timestamp ? `記錄於: ${Utils.formatDate(timestamp)}` : '已記錄';
- titleElement.appendChild(timeSpan);
- }
- } else {
- row.classList.remove('ex-record-highlighted');
-
- // 移除記錄時間
- const timeSpan = row.querySelector('.ex-record-time');
- if (timeSpan && timeSpan.parentNode) {
- timeSpan.parentNode.removeChild(timeSpan);
- }
- }
- });
- },
-
- // 切換顯示/隱藏已記錄的項目
- toggleRecordedItems() {
- const tableBody = Utils.getTableBody();
- if (!tableBody) return;
-
- const records = Utils.getRecords();
- let hiddenCount = 0;
- let shownCount = 0;
-
- Array.from(tableBody.rows).forEach(row => {
- const link = row.querySelector('a');
- if (!link) return;
-
- const url = link.href.split("/").filter(i => i !== '');
- const id = url[url.length - 1] + url[url.length - 2];
-
- if (records[id]) {
- if (row.style.display === "none") {
- row.style.display = "table-row";
- shownCount++;
- } else {
- row.style.display = "none";
- hiddenCount++;
- }
- }
- });
-
- if (hiddenCount > 0) {
- UI.showToast(`已隱藏 ${hiddenCount} 筆已記錄的內容`);
- } else if (shownCount > 0) {
- UI.showToast(`已顯示 ${shownCount} 筆已記錄的內容`);
- } else {
- UI.showToast('本頁沒有已記錄的內容');
- }
-
- UI.updateStatsDisplay();
- },
-
- // 隱藏已記錄的項目
- hideRecordedItems() {
- const tableBody = Utils.getTableBody();
- if (!tableBody) return 0;
-
- const records = Utils.getRecords();
- let hiddenCount = 0;
-
- Array.from(tableBody.rows).forEach(row => {
- const link = row.querySelector('a');
- if (!link) return;
-
- const url = link.href.split("/").filter(i => i !== '');
- const id = url[url.length - 1] + url[url.length - 2];
-
- if (records[id]) {
- row.style.display = "none";
- hiddenCount++;
- }
- });
-
- UI.updateStatsDisplay();
- return hiddenCount;
- },
-
- // 記錄當前頁面的所有項目
- recordCurrentPage() {
- const tableBody = Utils.getTableBody();
- if (!tableBody) return;
-
- const records = Utils.getRecords();
- const now = new Date().toISOString();
- let newCount = 0;
-
- Array.from(tableBody.rows).forEach(row => {
- if (row.style.display === "none") return; // 跳過已隱藏的行
-
- const link = row.querySelector('a');
- if (!link) return;
-
- const url = link.href.split("/").filter(i => i !== '');
- const id = url[url.length - 1] + url[url.length - 2];
-
- if (!records[id]) {
- // 使用簡化的數據結構以節省空間
- records[id] = { t: now };
- newCount++;
- }
- });
-
- if (newCount > 0) {
- if (Utils.saveRecords(records)) {
- this.highlightRecorded();
- UI.updateStatsDisplay();
- UI.showToast(`已記錄 ${newCount} 筆新內容`);
- } else {
- UI.showToast('記錄失敗:可能超出存儲限制');
- }
- } else {
- UI.showToast('沒有新內容可記錄');
- }
- }
- };
-
- // 頁面加載器
- const PageLoader = {
- loadQueue: [], // 加載隊列
- isLoading: false, // 是否正在加載
- isStopped: false, // 是否已停止
-
- // 初始化加載器
- init() {
- STATS.currentPage = Utils.getCurrentPage();
- STATS.estimatedTotalPages = Utils.estimateTotalPages();
-
- // 設置頁面可見性變化監聽
- this.setupVisibilityHandler();
- },
-
- // 監聽頁面可見性變化
- setupVisibilityHandler() {
- document.addEventListener('visibilitychange', () => {
- if (document.visibilityState === 'hidden') {
- // 頁面進入後台
- Utils.log('頁面進入後台');
- if (!CONFIG.continueInBackground && !LOADING_STATE.userPaused && this.isLoading) {
- // 如果不允許在後台加載且沒有用戶手動暫停,則暫停加載
- LOADING_STATE.backgroundPaused = true;
- UI.updateProgressText('頁面處於後台,加載已暫停');
- Utils.log('自動暫停加載');
- }
- } else if (document.visibilityState === 'visible') {
- // 頁面回到前台
- Utils.log('頁面回到前台');
- if (LOADING_STATE.backgroundPaused && !LOADING_STATE.userPaused) {
- // 如果因為後台而暫停且沒有用戶手動暫停,則恢復加載
- LOADING_STATE.backgroundPaused = false;
- UI.updateProgressText('頁面回到前台,繼續加載...');
- Utils.log('自動恢復加載');
- this.processNextItem();
- }
- }
- });
- },
-
- // 加載所有頁面
- loadAllPages() {
- if (this.isLoading) {
- UI.showToast('正在加載中,請等待...');
- return;
- }
-
- // 初始化進度顯示
- UI.toggleProgressContainer(true);
- UI.updateProgressText('準備加載所有頁面...');
- UI.updatePauseButtonIcon(false);
-
- this.isLoading = true;
- this.isStopped = false;
- this.loadQueue = [];
-
- // 重設加載狀態
- LOADING_STATE.userPaused = false;
- LOADING_STATE.backgroundPaused = false;
- LOADING_STATE.processing = false;
-
- // 重設統計
- STATS.totalProcessed = 0;
- STATS.totalAdded = 0;
- STATS.totalFiltered = 0;
-
- // 查找下一頁鏈接
- const nextPageLink = document.querySelector('#unext');
- if (!nextPageLink || nextPageLink.href === "javascript:void(0)") {
- UI.updateProgressText('已經是最後一頁');
- UI.showToast('已經是最後一頁');
- this.isLoading = false;
-
- // 3秒後隱藏進度條
- setTimeout(() => {
- UI.toggleProgressContainer(false);
- }, 3000);
-
- return;
- }
-
- // 添加第一個頁面到隊列
- this.addPageToQueue(nextPageLink.href, true);
-
- // 開始處理隊列
- this.processNextItem();
- },
-
- // 添加頁面到隊列
- addPageToQueue(pageUrl, recursive = false) {
- this.loadQueue.push({
- type: 'page',
- url: pageUrl,
- recursive: recursive
- });
- Utils.log(`頁面已添加到隊列: ${pageUrl}`);
- },
-
- // 添加行項目到隊列
- addRowsToQueue(params) {
- this.loadQueue.push({
- type: 'rows',
- ...params
- });
- Utils.log(`${params.rows.length} 行已添加到隊列`);
- },
-
- // 處理隊列中的下一個項目
- async processNextItem() {
- // 如果已停止或沒有正在加載,則退出
- if (this.isStopped || !this.isLoading) {
- return;
- }
-
- // 如果用戶暫停或後台暫停,則退出
- if (LOADING_STATE.userPaused || (LOADING_STATE.backgroundPaused && !CONFIG.continueInBackground)) {
- return;
- }
-
- // 如果正在處理項目,則退出
- if (LOADING_STATE.processing) {
- return;
- }
-
- // 如果隊列為空,則完成加載
- if (this.loadQueue.length === 0) {
- this.completeLoading();
- return;
- }
-
- // 獲取隊列中的下一個項目
- const nextItem = this.loadQueue.shift();
-
- // 設置處理標記
- LOADING_STATE.processing = true;
-
- try {
- if (nextItem.type === 'page') {
- // 處理頁面項目
- await this.processPageItem(nextItem);
- } else if (nextItem.type === 'rows') {
- // 處理行項目
- await this.processRowsItem(nextItem);
- }
- } catch (error) {
- console.error('處理項目失敗:', error);
- UI.updateProgressText(`處理失敗: ${error.message}`);
- UI.showToast(`處理失敗: ${error.message}`);
- // 發生錯誤時仍然繼續處理其他項目
- LOADING_STATE.processing = false;
- this.processNextItem();
- }
- },
-
- // 處理頁面項目
- async processPageItem(item) {
- const { url, recursive } = item;
-
- STATS.currentPage++;
- UI.updateProgressText(`正在加載第 ${STATS.currentPage} 頁...`);
-
- try {
- // 獲取頁面內容
- const response = await fetch(url);
- const html = await response.text();
-
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
-
- // 獲取下一頁的表格
- const nextPageTableBody = doc.querySelector('.itg.glte tbody');
- if (!nextPageTableBody) {
- throw new Error('無法解析頁面內容');
- }
-
- // 獲取下一頁中的行
- const nextPageRows = Array.from(nextPageTableBody.rows);
-
- // 獲取當前表格
- const tableBody = Utils.getTableBody();
- if (!tableBody) {
- throw new Error('無法找到當前頁面的表格');
- }
-
- // 添加行項目到隊列
- this.addRowsToQueue({
- rows: nextPageRows,
- tableBody: tableBody,
- totalToProcess: nextPageRows.length,
- processed: 0,
- filtered: 0
- });
-
- // 檢查是否有下一頁
- const nextPageUrl = this.getNextPageUrlFromDoc(doc);
- if (nextPageUrl && recursive) {
- this.addPageToQueue(nextPageUrl, true);
- }
-
- // 處理完成
- LOADING_STATE.processing = false;
- this.processNextItem();
- } catch (error) {
- LOADING_STATE.processing = false;
- throw error;
- }
- },
-
- // 處理行項目
- async processRowsItem(item) {
- const { rows, tableBody, totalToProcess } = item;
- const records = Utils.getRecords();
-
- let addedCount = item.processed || 0;
- let filteredCount = item.filtered || 0;
-
- try {
- // 處理每一行
- for (let i = 0; i < rows.length; i++) {
- // 檢查是否已停止
- if (this.isStopped) {
- LOADING_STATE.processing = false;
- this.isLoading = false;
- return;
- }
-
- // 檢查是否暫停
- if (LOADING_STATE.userPaused || (LOADING_STATE.backgroundPaused && !CONFIG.continueInBackground)) {
- // 如果暫停,則將剩餘行重新加入隊列
- const remainingRows = rows.slice(i);
- this.loadQueue.unshift({
- type: 'rows',
- rows: remainingRows,
- tableBody: tableBody,
- totalToProcess: totalToProcess,
- processed: addedCount,
- filtered: filteredCount
- });
- LOADING_STATE.processing = false;
- return;
- }
-
- const row = rows[i];
-
- // 解析 ID
- const link = row.querySelector('a');
- if (!link) continue;
-
- const id = Utils.getIdFromUrl(link.href);
-
- // 檢查是否已記錄
- const isAlreadyRecorded = records[id];
-
- // 複製行並添加到表格
- const clonedRow = row.cloneNode(true);
- tableBody.appendChild(clonedRow);
- addedCount++;
- STATS.totalProcessed++;
- STATS.totalAdded++;
-
- // 如果是已記錄項目,設置高亮並可能隱藏
- if (isAlreadyRecorded) {
- clonedRow.classList.add('ex-record-highlighted');
-
- // 添加記錄時間
- const titleElement = clonedRow.querySelector('.gl4e');
- if (titleElement && !titleElement.querySelector('.ex-record-time')) {
- const timeSpan = document.createElement('span');
- timeSpan.className = 'ex-record-time';
- const timestamp = records[id].timestamp || records[id].t || '';
- timeSpan.textContent = timestamp ? `記錄於: ${Utils.formatDate(timestamp)}` : '已記錄';
- titleElement.appendChild(timeSpan);
- }
-
- // 根據當前狀態決定是否隱藏
- if (CONFIG.autoHideRecorded) {
- clonedRow.style.display = 'none';
- filteredCount++;
- STATS.totalFiltered++;
- }
- }
-
- // 更新進度顯示
- const percent = Math.round((i + 1) / totalToProcess * 100);
- UI.updateProgress(
- percent,
- STATS.currentPage,
- STATS.estimatedTotalPages,
- STATS.totalProcessed
- );
-
- // 更新統計顯示
- UI.updateStatsDisplay();
-
- // 適當延遲以避免頁面凍結
- if (i < rows.length - 1 && i % 10 === 0) {
- await Utils.reliableDelay(10);
- }
- }
-
- // 更新進度文本
- UI.updateProgressText(`第 ${STATS.currentPage} 頁完成,已加載 ${addedCount} 項`);
-
- // 添加延遲以避免請求過快
- await Utils.reliableDelay(CONFIG.loadDelay);
-
- // 處理完成
- LOADING_STATE.processing = false;
- this.processNextItem();
- } catch (error) {
- LOADING_STATE.processing = false;
- throw error;
- }
- },
-
- // 完成加載
- completeLoading() {
- this.isLoading = false;
- UI.updateProgressText(`加載完成,共處理 ${STATS.totalProcessed} 項,新增 ${STATS.totalAdded} 項`);
- UI.showToast(`加載完成,共處理 ${STATS.totalProcessed} 項,新增 ${STATS.totalAdded} 項`);
-
- // 3秒後隱藏進度條
- setTimeout(() => {
- UI.toggleProgressContainer(false);
- }, 3000);
- },
-
- // 從文檔中獲取下一頁URL
- getNextPageUrlFromDoc(doc) {
- const nextPageLink = doc.querySelector('#unext');
- if (nextPageLink && nextPageLink.href && nextPageLink.href !== "javascript:void(0)") {
- return nextPageLink.href;
- }
- return null;
- },
-
- // 停止加載
- stopLoading() {
- this.isStopped = true;
- this.isLoading = false;
- this.loadQueue = [];
- LOADING_STATE.processing = false;
- LOADING_STATE.userPaused = false;
- LOADING_STATE.backgroundPaused = false;
- }
- };
-
- // 數據管理
- const DataManager = {
- // 匯出記錄
- exportRecords() {
- const records = Utils.getRecords();
- const exportData = JSON.stringify(records, null, 2);
-
- const modalContent = `
- <p>以下是您的記錄資料,請複製並保存:</p>
- <textarea readonly>${exportData}</textarea>
- `;
-
- const buttons = [
- { id: 'ex-record-copy-btn', text: '複製', primary: true },
- { id: 'ex-record-modal-close-btn', text: '關閉', primary: false }
- ];
-
- const modal = UI.createModal('匯出記錄', modalContent, buttons);
-
- document.getElementById('ex-record-copy-btn').addEventListener('click', () => {
- const textarea = modal.querySelector('textarea');
- if (textarea) {
- textarea.select();
- document.execCommand('copy');
- UI.showToast('已複製到剪貼簿');
- }
- });
-
- document.getElementById('ex-record-modal-close-btn').addEventListener('click', () => {
- document.body.removeChild(modal);
- });
- },
-
- // 匯入記錄
- importRecords() {
- const modalContent = `
- <p>請貼上之前匯出的記錄資料:</p>
- <textarea placeholder="在這裡貼上 JSON 格式的記錄資料..."></textarea>
- `;
-
- const buttons = [
- { id: 'ex-record-import-btn', text: '匯入', primary: true },
- { id: 'ex-record-modal-close-btn', text: '取消', primary: false }
- ];
-
- const modal = UI.createModal('匯入記錄', modalContent, buttons);
-
- document.getElementById('ex-record-import-btn').addEventListener('click', () => {
- const textarea = modal.querySelector('textarea');
- if (!textarea) return;
-
- try {
- const importData = JSON.parse(textarea.value);
- const currentRecords = Utils.getRecords();
-
- // 合併記錄
- const mergedRecords = { ...currentRecords, ...importData };
-
- if (Utils.saveRecords(mergedRecords)) {
- Record.highlightRecorded();
- UI.updateStatsDisplay();
- UI.showToast(`匯入成功,共 ${Object.keys(mergedRecords).length} 筆記錄`);
- } else {
- UI.showToast('匯入失敗:保存記錄時出錯');
- }
-
- document.body.removeChild(modal);
- } catch (error) {
- UI.showToast(`匯入失敗:${error.message}`);
- }
- });
-
- document.getElementById('ex-record-modal-close-btn').addEventListener('click', () => {
- document.body.removeChild(modal);
- });
- }
- };
-
- // 檢查舊數據格式並轉換
- function migrateOldData() {
- const oldRecordStr = localStorage.getItem("record");
- if (oldRecordStr) {
- try {
- const oldIds = oldRecordStr.split(",").filter(id => id.trim() !== '');
- const newRecords = Utils.getRecords();
- const now = new Date().toISOString();
-
- for (let i = 0; i < oldIds.length; i++) {
- const id = oldIds[i];
- if (id && !newRecords[id]) {
- newRecords[id] = { t: now };
- }
- }
-
- Utils.saveRecords(newRecords);
- localStorage.removeItem("record");
- UI.showToast("已轉換舊格式記錄");
- } catch (e) {
- console.error('轉換舊記錄失敗:', e);
- }
- }
- }
-
- // 初始化函數
- function init() {
- console.log('初始化 Enhanced Exhentai Record 腳本...');
-
- // 添加樣式
- UI.addStyles();
-
- // 轉換舊數據
- migrateOldData();
-
- if (Utils.getTableBody()) {
- // 創建控制面板
- UI.createControlPanel();
-
- // 創建進度容器
- UI.createProgressContainer();
- UI.toggleProgressContainer(false); // 默認隱藏
-
- // 標記已記錄的項目
- Record.highlightRecorded();
-
- // 更新統計信息
- UI.updateStatsDisplay();
-
- // 初始化頁面加載器
- PageLoader.init();
-
- // 默認隱藏已記錄的項目
- if (CONFIG.autoHideRecorded) {
- const hiddenCount = Record.hideRecordedItems();
- if (hiddenCount > 0) {
- UI.showToast(`已隱藏 ${hiddenCount} 筆已記錄的內容`);
- }
- }
-
- // 添加滾動事件來監控閱讀進度
- window.addEventListener('scroll', Utils.debounce(() => {
- Utils.updateReadingProgress();
- }, 200));
- } else {
- console.log('找不到作品表格,可能不在正確的頁面');
- }
- }
-
- // 確保頁面載入完成後執行初始化
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
- setTimeout(init, 1000);
- } else {
- document.addEventListener('DOMContentLoaded', () => {
- setTimeout(init, 1000);
- });
- }
-
- // 確保初始化執行
- setTimeout(() => {
- if (!document.querySelector('.ex-record-toolbar')) {
- init();
- }
- }, 2000);
- })();