您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在YUC.Wiki上标记番剧为已阅或删除状态,支持显示/隐藏标记番剧,一键复制动画名称
// ==UserScript== // @name YUC.Wiki 番剧标记助手 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 在YUC.Wiki上标记番剧为已阅或删除状态,支持显示/隐藏标记番剧,一键复制动画名称 // @author Cylirix // @match https://yuc.wiki/* // @exclude https://yuc.wiki/new/* // @exclude https://yuc.wiki/ // @license MIT license // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // 配置存储键名 const STORAGE_KEY = 'YUC_ANIME_TRACKER_STATE'; const SETTINGS_KEY = 'YUC_TRACKER_SETTINGS'; const DETAILS_STATE_KEY = 'YUC_DETAILS_STATE'; // PASS状态基础滤镜 const BASE_PASS_FILTER = 'sepia(100%) hue-rotate(-50deg) saturate(500%) brightness(0.7)'; // 默认设置 const defaultSettings = { readOpacity: 0.5, buttonOpacity: 1.0, showRead: true, showPass: true, panelOpen: false, effectsEnabled: true, passOpacity: 0.3, hoverPanelOpen: false, keepPanelOpen: false }; // 加载设置 function loadSettings() { const saved = GM_getValue(SETTINGS_KEY, JSON.stringify(defaultSettings)); return {...defaultSettings, ...JSON.parse(saved)}; } // 保存设置 function saveSettings(settings) { GM_setValue(SETTINGS_KEY, JSON.stringify(settings)); } // 加载动画状态 function loadAnimeState() { const saved = GM_getValue(STORAGE_KEY, '{}'); return JSON.parse(saved); } // 保存动画状态 function saveAnimeState(state) { GM_setValue(STORAGE_KEY, JSON.stringify(state)); } // 加载详情展开状态 function loadDetailsState() { const saved = GM_getValue(DETAILS_STATE_KEY, '{}'); return JSON.parse(saved); } // 保存详情展开状态 function saveDetailsState(state) { GM_setValue(DETAILS_STATE_KEY, JSON.stringify(state)); } // 统计当前页面状态数量 function countStates() { let readCount = 0; let passCount = 0; document.querySelectorAll('.anime-item').forEach(item => { const title = item.querySelector('.anime-title').textContent; const state = animeState[title]; if (state === 'read') readCount++; else if (state === 'pass') passCount++; }); return { readCount, passCount }; } // 更新设置面板中的数量显示 function updateCountDisplay() { const counts = countStates(); if (readCounter) { readCounter.textContent = `(${counts.readCount})`; } if (passCounter) { passCounter.textContent = `(${counts.passCount})`; } } // 创建控制面板 function createControlPanel() { let panel = document.getElementById('anime-tracker-control'); if (!panel) { panel = document.createElement('div'); panel.id = 'anime-tracker-control'; panel.style.cssText = ` position: fixed; bottom: 118px; /* 在悬浮球上方**px处 */ right: 20px; background: rgba(255, 255, 255, 0.55); /* 设置区域透明度 */ padding: 18px; border-radius: 20px; box-shadow: 0 0 15px rgba(0,0,0,0.2); z-index: 9999; font-family: 'Microsoft YaHei', sans-serif; max-width: 300px; border: 1px solid #ddd; transition: transform 0.3s ease; transform: translateY(${settings.hoverPanelOpen ? '0' : '20px'}); `; // 标题栏 const header = document.createElement('div'); header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; header.style.marginBottom = '8px'; const title = document.createElement('h3'); title.textContent = 'YUC.WIKI 番剧标记助手';//设置面板标题 title.style.margin = '0'; title.style.fontSize = '16px'; const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.cssText = ` background: none; border: none; font-size: 20px; cursor: pointer; color: #999; padding: 0; line-height: 1; `; closeBtn.addEventListener('click', () => { settings.panelOpen = false; settings.hoverPanelOpen = false; saveSettings(settings); panel.style.display = 'none'; toggleFloatingBall(true); }); header.appendChild(title); header.appendChild(closeBtn); panel.appendChild(header); // 已阅透明度控制 const opacityRow = document.createElement('div'); opacityRow.style.display = 'flex'; opacityRow.style.alignItems = 'center'; opacityRow.style.marginBottom = '12px'; const opacityLabel = document.createElement('label'); opacityLabel.textContent = '已阅(状态)透明度:'; opacityLabel.style.marginRight = '8px'; opacityLabel.style.flex = '0 0 auto'; opacityLabel.style.fontSize = '14px'; const opacitySlider = document.createElement('input'); opacitySlider.type = 'range'; opacitySlider.min = '0.1'; opacitySlider.max = '1.0'; opacitySlider.step = '0.05'; opacitySlider.value = settings.readOpacity; opacitySlider.style.flex = '1'; opacitySlider.style.height = '16px'; const opacityValue = document.createElement('div'); opacityValue.textContent = Math.round(settings.readOpacity * 100) + '%'; opacityValue.style.flex = '0 0 40px'; opacityValue.style.textAlign = 'right'; opacityValue.style.paddingLeft = '8px'; opacityValue.style.fontSize = '14px'; opacitySlider.addEventListener('input', () => { settings.readOpacity = parseFloat(opacitySlider.value); opacityValue.textContent = Math.round(settings.readOpacity * 100) + '%'; applyStates(); updateCountDisplay(); }); opacityRow.appendChild(opacityLabel); opacityRow.appendChild(opacitySlider); opacityRow.appendChild(opacityValue); panel.appendChild(opacityRow); // 删除透明度控制 const passOpacityRow = document.createElement('div'); passOpacityRow.style.display = 'flex'; passOpacityRow.style.alignItems = 'center'; passOpacityRow.style.marginBottom = '12px'; const passOpacityLabel = document.createElement('label'); passOpacityLabel.textContent = '删除(状态)透明度:'; passOpacityLabel.style.marginRight = '8px'; passOpacityLabel.style.flex = '0 0 auto'; passOpacityLabel.style.fontSize = '14px'; const passOpacitySlider = document.createElement('input'); passOpacitySlider.type = 'range'; passOpacitySlider.min = '0'; passOpacitySlider.max = '1'; passOpacitySlider.step = '0.05'; passOpacitySlider.value = settings.passOpacity; passOpacitySlider.style.flex = '1'; passOpacitySlider.style.height = '16px'; const passOpacityValue = document.createElement('div'); passOpacityValue.textContent = Math.round(settings.passOpacity * 100) + '%'; passOpacityValue.style.flex = '0 0 40px'; passOpacityValue.style.textAlign = 'right'; passOpacityValue.style.paddingLeft = '8px'; passOpacityValue.style.fontSize = '14px'; passOpacitySlider.addEventListener('input', () => { settings.passOpacity = parseFloat(passOpacitySlider.value); passOpacityValue.textContent = Math.round(settings.passOpacity * 100) + '%'; applyStates(); updateCountDisplay(); }); passOpacityRow.appendChild(passOpacityLabel); passOpacityRow.appendChild(passOpacitySlider); passOpacityRow.appendChild(passOpacityValue); panel.appendChild(passOpacityRow); // 按钮可视度控制 const buttonOpacityRow = document.createElement('div'); buttonOpacityRow.style.display = 'flex'; buttonOpacityRow.style.alignItems = 'center'; buttonOpacityRow.style.marginBottom = '12px'; const buttonOpacityLabel = document.createElement('label'); buttonOpacityLabel.textContent = '按钮透明度:'; buttonOpacityLabel.style.marginRight = '45px'; buttonOpacityLabel.style.flex = '0 0 auto'; buttonOpacityLabel.style.fontSize = '14px'; const buttonOpacitySlider = document.createElement('input'); buttonOpacitySlider.type = 'range'; buttonOpacitySlider.min = '0.0'; buttonOpacitySlider.max = '1.0'; buttonOpacitySlider.step = '0.05'; buttonOpacitySlider.value = settings.buttonOpacity; buttonOpacitySlider.style.flex = '1'; buttonOpacitySlider.style.height = '16px'; const buttonOpacityValue = document.createElement('div'); buttonOpacityValue.textContent = Math.round(settings.buttonOpacity * 100) + '%'; buttonOpacityValue.style.flex = '0 0 40px'; buttonOpacityValue.style.textAlign = 'right'; buttonOpacityValue.style.paddingLeft = '8px'; buttonOpacityValue.style.fontSize = '14px'; buttonOpacitySlider.addEventListener('input', () => { settings.buttonOpacity = parseFloat(buttonOpacitySlider.value); buttonOpacityValue.textContent = Math.round(settings.buttonOpacity * 100) + '%'; applyButtonOpacity(); }); buttonOpacityRow.appendChild(buttonOpacityLabel); buttonOpacityRow.appendChild(buttonOpacitySlider); buttonOpacityRow.appendChild(buttonOpacityValue); panel.appendChild(buttonOpacityRow); // 显示已阅和显示删除放在同一行 const showReadPassRow = document.createElement('div'); showReadPassRow.style.display = 'flex'; showReadPassRow.style.justifyContent = 'space-between'; showReadPassRow.style.marginBottom = '12px'; // 显示已阅开关 const showReadContainer = document.createElement('div'); showReadContainer.style.display = 'flex'; showReadContainer.style.alignItems = 'center'; showReadContainer.style.flex = '1'; showReadContainer.style.marginRight = '10px'; const showReadLabel = document.createElement('label'); showReadLabel.textContent = '显示已阅:'; showReadLabel.style.marginRight = '8px'; showReadLabel.style.flex = '0 0 auto'; showReadLabel.style.fontSize = '14px'; const showReadToggle = document.createElement('div'); showReadToggle.className = 'toggle-switch'; showReadToggle.style.cssText = ` width: 40px; height: 20px; background-color: ${settings.showRead ? '#4CAF50' : '#cccccc'}; border-radius: 10px; position: relative; cursor: pointer; transition: background-color 0.3s; `; const showReadSlider = document.createElement('div'); showReadSlider.style.cssText = ` position: absolute; top: 2px; left: ${settings.showRead ? '22px' : '2px'}; width: 16px; height: 16px; background-color: white; border-radius: 50%; transition: left 0.3s; `; showReadToggle.appendChild(showReadSlider); showReadToggle.addEventListener('click', () => { settings.showRead = !settings.showRead; showReadToggle.style.backgroundColor = settings.showRead ? '#4CAF50' : '#cccccc'; showReadSlider.style.left = settings.showRead ? '22px' : '2px'; saveSettings(settings); applyStates(); updateCountDisplay(); }); showReadContainer.appendChild(showReadLabel); showReadContainer.appendChild(showReadToggle); // 数量计数器 readCounter = document.createElement('span'); readCounter.className = 'counter'; readCounter.textContent = '(0)'; readCounter.style.marginLeft = '8px'; readCounter.style.fontSize = '14px'; showReadContainer.appendChild(readCounter); // 显示删除开关 const showPassContainer = document.createElement('div'); showPassContainer.style.display = 'flex'; showPassContainer.style.alignItems = 'center'; showPassContainer.style.flex = '1'; const showPassLabel = document.createElement('label'); showPassLabel.textContent = '显示删除:'; showPassLabel.style.marginRight = '8px'; showPassLabel.style.flex = '0 0 auto'; showPassLabel.style.fontSize = '14px'; const showPassToggle = document.createElement('div'); showPassToggle.className = 'toggle-switch'; showPassToggle.style.cssText = ` width: 40px; height: 20px; background-color: ${settings.showPass ? '#4CAF50' : '#cccccc'}; border-radius: 10px; position: relative; cursor: pointer; transition: background-color 0.3s; `; const showPassSlider = document.createElement('div'); showPassSlider.style.cssText = ` position: absolute; top: 2px; left: ${settings.showPass ? '22px' : '2px'}; width: 16px; height: 16px; background-color: white; border-radius: 50%; transition: left 0.3s; `; showPassToggle.appendChild(showPassSlider); showPassToggle.addEventListener('click', () => { settings.showPass = !settings.showPass; showPassToggle.style.backgroundColor = settings.showPass ? '#4CAF50' : '#cccccc'; showPassSlider.style.left = settings.showPass ? '22px' : '2px'; saveSettings(settings); applyStates(); updateCountDisplay(); }); showPassContainer.appendChild(showPassLabel); showPassContainer.appendChild(showPassToggle); // 数量计数器 passCounter = document.createElement('span'); passCounter.className = 'counter'; passCounter.textContent = '(0)'; passCounter.style.marginLeft = '8px'; passCounter.style.fontSize = '14px'; showPassContainer.appendChild(passCounter); showReadPassRow.appendChild(showReadContainer); showReadPassRow.appendChild(showPassContainer); panel.appendChild(showReadPassRow); // 全局效果和保持菜单放在同一行 const effectsKeepRow = document.createElement('div'); effectsKeepRow.style.display = 'flex'; effectsKeepRow.style.justifyContent = 'space-between'; effectsKeepRow.style.marginBottom = '12px'; // 全局效果开关 const effectsContainer = document.createElement('div'); effectsContainer.style.display = 'flex'; effectsContainer.style.alignItems = 'center'; effectsContainer.style.flex = '1'; effectsContainer.style.marginRight = '10px'; const effectsLabel = document.createElement('label'); effectsLabel.textContent = '全局效果:'; effectsLabel.style.marginRight = '8px'; effectsLabel.style.flex = '0 0 auto'; effectsLabel.style.fontSize = '14px'; const effectsToggle = document.createElement('div'); effectsToggle.className = 'toggle-switch'; effectsToggle.style.cssText = ` width: 40px; height: 20px; background-color: ${settings.effectsEnabled ? '#4CAF50' : '#cccccc'}; border-radius: 10px; position: relative; cursor: pointer; transition: background-color 0.3s; `; const toggleSlider = document.createElement('div'); toggleSlider.style.cssText = ` position: absolute; top: 2px; left: ${settings.effectsEnabled ? '22px' : '2px'}; width: 16px; height: 16px; background-color: white; border-radius: 50%; transition: left 0.3s; `; effectsToggle.appendChild(toggleSlider); effectsToggle.addEventListener('click', () => { settings.effectsEnabled = !settings.effectsEnabled; effectsToggle.style.backgroundColor = settings.effectsEnabled ? '#4CAF50' : '#cccccc'; toggleSlider.style.left = settings.effectsEnabled ? '22px' : '2px'; saveSettings(settings); applyStates(); }); effectsContainer.appendChild(effectsLabel); effectsContainer.appendChild(effectsToggle); // 保持菜单打开开关 const keepOpenContainer = document.createElement('div'); keepOpenContainer.style.display = 'flex'; keepOpenContainer.style.alignItems = 'center'; keepOpenContainer.style.flex = '1'; const keepOpenLabel = document.createElement('label'); keepOpenLabel.textContent = '保持菜单:'; keepOpenLabel.style.marginRight = '8px'; keepOpenLabel.style.flex = '0 0 auto'; keepOpenLabel.style.fontSize = '14px'; const keepOpenToggle = document.createElement('div'); keepOpenToggle.className = 'toggle-switch'; keepOpenToggle.style.cssText = ` width: 40px; height: 20px; background-color: ${settings.keepPanelOpen ? '#4CAF50' : '#cccccc'}; border-radius: 10px; position: relative; cursor: pointer; transition: background-color 0.3s; `; const keepOpenSlider = document.createElement('div'); keepOpenSlider.style.cssText = ` position: absolute; top: 2px; left: ${settings.keepPanelOpen ? '22px' : '2px'}; width: 16px; height: 16px; background-color: white; border-radius: 50%; transition: left 0.3s; `; keepOpenToggle.appendChild(keepOpenSlider); keepOpenToggle.addEventListener('click', () => { settings.keepPanelOpen = !settings.keepPanelOpen; keepOpenToggle.style.backgroundColor = settings.keepPanelOpen ? '#4CAF50' : '#cccccc'; keepOpenSlider.style.left = settings.keepPanelOpen ? '22px' : '2px'; saveSettings(settings); }); keepOpenContainer.appendChild(keepOpenLabel); keepOpenContainer.appendChild(keepOpenToggle); effectsKeepRow.appendChild(effectsContainer); effectsKeepRow.appendChild(keepOpenContainer); panel.appendChild(effectsKeepRow); // 重置按钮容器 const resetButtonsContainer = document.createElement('div'); resetButtonsContainer.style.cssText = ` display: flex; gap: 20px; margin-top: 12px; `; // 仅重置当前页面标记按钮 const resetCurrentBtn = document.createElement('button'); resetCurrentBtn.textContent = '重置当前页标记'; resetCurrentBtn.style.cssText = ` flex: 1; padding: 6px; background-color: #ff9800; color: white; border: none; border-radius: 10px; cursor: pointer; font-weight: bold; font-size: 14px; `; resetCurrentBtn.addEventListener('click', () => { if (confirm('确定要重置当前页面的番剧标记吗?')) { // 获取当前页面所有标题 const currentPageTitles = []; document.querySelectorAll('.anime-item').forEach(item => { const title = item.querySelector('.anime-title').textContent; currentPageTitles.push(title); }); // 从状态中移除当前页面的标记 const newState = {...animeState}; currentPageTitles.forEach(title => { delete newState[title]; }); // 保存新状态并刷新 saveAnimeState(newState); location.reload(); } }); resetButtonsContainer.appendChild(resetCurrentBtn); // 重置所有标记按钮 const resetAllBtn = document.createElement('button'); resetAllBtn.textContent = '重置所有标记'; resetAllBtn.style.cssText = ` flex: 1; padding: 6px; background-color: #f44336; color: white; border: none; border-radius: 10px; cursor: pointer; font-weight: bold; font-size: 14px; `; resetAllBtn.addEventListener('click', () => { if (confirm('确定要重置所有番剧标记吗?')) { GM_setValue(STORAGE_KEY, '{}'); location.reload(); } }); resetButtonsContainer.appendChild(resetAllBtn); panel.appendChild(resetButtonsContainer); document.body.appendChild(panel); } panel.style.display = settings.panelOpen || settings.hoverPanelOpen ? 'block' : 'none'; toggleFloatingBall(true); updateCountDisplay(); } // 创建悬浮球 function createFloatingBall() { let ball = document.getElementById('anime-tracker-ball'); if (!ball) { ball = document.createElement('div'); ball.id = 'anime-tracker-ball'; ball.textContent = '⚙️'; ball.style.cssText = ` position: fixed; bottom: 60px; right: 24px; width: 36px; height: 36px; background: rgba(255, 255, 255, 0.95); border-radius: 50%; box-shadow: 0 0 10px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; font-size: 18px; cursor: pointer; z-index: 9998; transition: transform 0.3s ease; /* 新增的禁止选中和拖动的样式 */ user-select: none; -webkit-user-select: none; /* Safari/Chrome */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* IE/Edge */ -webkit-touch-callout: none; /* iOS Safari */ /* 防止点击时出现高亮 */ -webkit-tap-highlight-color: transparent; outline: none; /* 防止拖动鼠标时选择文本 */ -webkit-user-drag: none; -khtml-user-drag: none; -moz-user-drag: none; -o-user-drag: none; user-drag: none; `; ball.addEventListener('mouseenter', () => { settings.hoverPanelOpen = true; saveSettings(settings); createControlPanel(); }); // 修改点击事件:切换菜单开关状态 ball.addEventListener('click', () => { settings.panelOpen = !settings.panelOpen; settings.hoverPanelOpen = false; // 确保关闭悬浮状态 saveSettings(settings); createControlPanel(); }); document.body.appendChild(ball); } // 添加额外的焦点处理以防止键盘选中 ball.addEventListener('focus', () => { ball.blur(); }); } function toggleFloatingBall(show) { const ball = document.getElementById('anime-tracker-ball'); if (ball) ball.style.display = show ? 'flex' : 'none'; } // 应用按钮透明度 function applyButtonOpacity() { saveSettings(settings); const shouldShow = settings.buttonOpacity > 0; document.querySelectorAll('.btn-container').forEach(container => { container.style.display = shouldShow ? 'flex' : 'none'; container.style.opacity = settings.buttonOpacity; }); } // 应用状态到动画条目 function applyStates() { saveSettings(settings); // 应用每个番剧项的样式 document.querySelectorAll('.anime-item').forEach(item => { const title = item.querySelector('.anime-title').textContent; const state = animeState[title] || 'none'; // 重置样式 const elementsToStyle = [ '.div_date, .div_date_', 'table', '.date_title, .date_title_, .date_title1, .date_title2', '.copyright', '.div_sp', '.sp_title, .sp_title_' ]; elementsToStyle.forEach(sel => { const el = item.querySelector(sel); if (el) { el.style.opacity = ''; el.style.filter = ''; el.style.position = ''; el.style.zIndex = ''; el.querySelectorAll('.pass-overlay').forEach(o => o.remove()); } }); item.style.display = ''; // 应用效果(如果全局效果启用) if (settings.effectsEnabled) { if (state === 'read') { if (settings.showRead) { elementsToStyle.forEach(sel => { const el = item.querySelector(sel); if (el) { el.style.opacity = settings.readOpacity; el.style.filter = 'grayscale(100%)'; } }); } else { item.style.display = 'none'; } } else if (state === 'pass') { if (settings.showPass) { elementsToStyle.forEach(sel => { const el = item.querySelector(sel); if (el) { // 使用删除透明度设置 el.style.filter = BASE_PASS_FILTER; el.style.opacity = settings.passOpacity; } }); } else { item.style.display = 'none'; } } } // 应用按钮透明度 applyButtonOpacity(); }); // 更新星期标题显示状态 updateWeekHeaders(); // 确保复制按钮在所有状态下都显示 setupCopyButtons(); } // 更新星期标题显示状态 function updateWeekHeaders() { // 找到所有星期标题行 const weekHeaders = document.querySelectorAll('table > tr > td.date2'); weekHeaders.forEach(header => { const tableRow = header.closest('tr'); const table = tableRow.closest('table'); const container = table.closest('div'); // 检查该星期分组下是否有可见的番剧 let hasVisibleItems = false; let next = container.nextElementSibling; while (next && !next.querySelector('td.date2')) { if (next.style.display !== 'none' && window.getComputedStyle(next).display !== 'none') { hasVisibleItems = true; break; } next = next.nextElementSibling; } // 如果没有可见番剧,隐藏星期标题行并添加占位符 if (!hasVisibleItems) { table.style.display = 'none'; // 添加占位符 if (!table.nextElementSibling || !table.nextElementSibling.classList.contains('week-placeholder')) { const placeholder = document.createElement('div'); placeholder.className = 'week-placeholder'; placeholder.style.height = '30px'; // 占位高度 table.parentNode.insertBefore(placeholder, table.nextSibling); } } else { table.style.display = ''; // 移除占位符 if (table.nextElementSibling && table.nextElementSibling.classList.contains('week-placeholder')) { table.nextElementSibling.remove(); } } }); } // 获取动画标题文本 function getAnimeTitle(item) { // 尝试多种选择器以适应不同页面 const titleSelectors = [ '.date_title', '.date_title_', '.date_title1', '.date_title2', '.sp_title', '.sp_title_', '.date_title2' ]; for (const selector of titleSelectors) { const titleEl = item.querySelector(selector); if (titleEl) { return titleEl.textContent.replace(/\n/g, ' ').trim(); } } // 作为备选方案,尝试查找包含标题文本的元素 const possibleElements = item.querySelectorAll('td[class*="title"], div[class*="title"]'); for (const el of possibleElements) { if (el.textContent.trim().length > 5) { // 假设标题至少5个字符 return el.textContent.replace(/\n/g, ' ').trim(); } } console.warn('无法找到标题元素:', item); return 'Unknown'; } // 设置动画条目和标记按钮 function setupAnimeItems() { // 尝试多种选择器以适应不同页面 const animeItems = document.querySelectorAll('div[style*="float:left"]:not(.anime-item)'); if (!animeItems.length) return; animeItems.forEach(item => { const title = getAnimeTitle(item); if (title === 'Unknown') { console.warn('跳过未知标题的条目:', item); return; } item.classList.add('anime-item'); const titleSpan = document.createElement('span'); titleSpan.className = 'anime-title'; titleSpan.textContent = title; titleSpan.style.display = 'none'; item.appendChild(titleSpan); // 检查是否已经添加过按钮 if (item.querySelector('.btn-container')) return; const btnContainer = document.createElement('div'); btnContainer.className = 'btn-container'; btnContainer.style.cssText = ` display: flex; justify-content: center; gap: 6px; margin-top: 4px; position: relative; z-index: 2; opacity: ${settings.buttonOpacity}; `; const readBtn = document.createElement('button'); readBtn.textContent = '已阅'; readBtn.style.cssText = ` padding: 2px 6px; border-radius: 4px; border: 1px solid #4CAF50; background-color: rgba(76,175,80,0.2); color: #4CAF50; cursor: pointer; font-size: 13px; transition: all 0.2s; min-width: 50px; `; const passBtn = document.createElement('button'); passBtn.textContent = '删除'; passBtn.style.cssText = ` padding: 2px 6px; border-radius: 5px; border: 1px solid #f44336; background-color: rgba(244,67,54,0.2); color: #f44336; cursor: pointer; font-size: 13px; min-width: 50px; `; // 仅已阅按钮添加hover效果 readBtn.addEventListener('mouseover', () => { readBtn.style.backgroundColor = 'rgba(76,175,80,0.4)'; }); readBtn.addEventListener('mouseout', () => { readBtn.style.backgroundColor = 'rgba(76,175,80,0.2)'; }); readBtn.addEventListener('click', e => { e.stopPropagation(); animeState[title] = animeState[title] === 'read' ? 'none' : 'read'; applyStates(); saveAnimeState(animeState); updateCountDisplay(); }); passBtn.addEventListener('click', e => { e.stopPropagation(); animeState[title] = animeState[title] === 'pass' ? 'none' : 'pass'; applyStates(); saveAnimeState(animeState); updateCountDisplay(); }); btnContainer.appendChild(readBtn); btnContainer.appendChild(passBtn); // 尝试多种插入位置以适应不同页面 const table = item.querySelector('table'); if (table) { table.parentNode.insertBefore(btnContainer, table.nextSibling); } else { const img = item.querySelector('.div_sp'); if (img) { img.parentNode.insertBefore(btnContainer, img.nextSibling); } else { item.appendChild(btnContainer); } } }); // 初始加载时设置复制按钮 setupCopyButtons(); } // 设置复制按钮(在文字区域内) function setupCopyButtons() { const animeItems = document.querySelectorAll('.anime-item'); if (!animeItems.length) return; animeItems.forEach(item => { const title = item.querySelector('.anime-title').textContent; // 尝试多种选择器以适应不同页面 const titleSelectors = [ '.date_title', '.date_title_', '.date_title1, .date_title2', '.sp_title', '.sp_title_' ]; let titleEl = null; for (const selector of titleSelectors) { titleEl = item.querySelector(selector); if (titleEl) break; } if (!titleEl) { // 作为备选方案,尝试查找包含标题文本的元素 const possibleElements = item.querySelectorAll('td[class*="title"], div[class*="title"]'); for (const el of possibleElements) { if (el.textContent.trim().length > 5) { // 假设标题至少5个字符 titleEl = el; break; } } } if (!titleEl) { console.warn('无法找到标题元素用于复制按钮:', item); return; } // 移除已有的复制按钮 const existingCopyBtn = item.querySelector('.copy-btn'); if (existingCopyBtn) existingCopyBtn.remove(); // 创建复制按钮放在标题元素内 const copyBtn = document.createElement('button'); copyBtn.className = 'copy-btn'; copyBtn.textContent = '复制'; copyBtn.title = '复制标题'; copyBtn.style.cssText = ` position: absolute; top: 2px; right: 4px; padding: 2px 6px; font-size: 11px; background: rgba(0,0,0,0.6); color: #fff; border: none; border-radius: 3px; opacity: 0; cursor: pointer; transition: opacity 0.2s; z-index: 99999; /* 最高层级 */ pointer-events: auto; filter: none !important; /* 确保不受父元素滤镜影响 */ `; // 确保标题元素有相对定位 titleEl.style.position = 'relative'; titleEl.appendChild(copyBtn); // 悬浮显示/隐藏 titleEl.addEventListener('mouseenter', () => { copyBtn.style.opacity = '1'; }); titleEl.addEventListener('mouseleave', () => { copyBtn.style.opacity = '0'; }); copyBtn.addEventListener('click', e => { e.stopPropagation(); navigator.clipboard.writeText(title).then(() => { const old = copyBtn.textContent; copyBtn.textContent = '已复制!'; copyBtn.style.background = '#4CAF50'; setTimeout(() => { copyBtn.textContent = old; copyBtn.style.background = 'rgba(0,0,0,0.6)'; }, 1000); }); }); }); } // 设置详情折叠状态保存 function setupDetails() { const detailsEls = document.querySelectorAll('details'); const detailsState = loadDetailsState(); detailsEls.forEach((d, i) => { const key = `details_${i}`; if (detailsState[key] !== undefined) d.open = detailsState[key]; d.addEventListener('toggle', () => { detailsState[key] = d.open; saveDetailsState(detailsState); }); }); } // 点击空白处关闭设置面板(修复版) function setupPanelCloseOnOutsideClick() { document.addEventListener('click', (e) => { const panel = document.getElementById('anime-tracker-control'); const ball = document.getElementById('anime-tracker-ball'); if (!panel || panel.style.display !== 'block') return; // 检查点击是否在面板或悬浮球内部 const isPanelClick = panel.contains(e.target); const isBallClick = ball && ball.contains(e.target); // 如果开启了"保持菜单"设置,则不关闭面板 if (settings.keepPanelOpen) return; // 修复点:无论何种方式打开,点击外部都关闭面板 if (!isPanelClick && !isBallClick) { // 重置所有打开状态 settings.hoverPanelOpen = false; settings.panelOpen = false; saveSettings(settings); panel.style.display = 'none'; toggleFloatingBall(true); } }); } // 初始化 const settings = loadSettings(); const animeState = loadAnimeState(); let readCounter = null; let passCounter = null; function addNavSettingButton() { const nav = document.querySelector('#nav'); if (nav) { const btn = document.createElement('a'); btn.textContent = '标记设置'; btn.href = '#'; btn.style.marginLeft = '15px'; btn.style.cursor = 'pointer'; btn.style.fontSize = '14px'; btn.addEventListener('click', e => { e.preventDefault(); settings.panelOpen = true; settings.hoverPanelOpen = false; saveSettings(settings); createControlPanel(); }); nav.appendChild(btn); } } function init() { // 排除特定页面 if (window.location.href.includes('/new/')) return; addNavSettingButton(); setupAnimeItems(); applyStates(); setupDetails(); createControlPanel(); createFloatingBall(); toggleFloatingBall(true); setupPanelCloseOnOutsideClick(); } window.addEventListener('load', init); if (document.readyState === 'complete') { setTimeout(init, 500); } })();