标记番剧为已阅或删除状态,支持显示/隐藏标记番剧,一键复制动画名称
// ==UserScript==
// @name YUC.Wiki 番剧标记助手
// @namespace http://tampermonkey.net/
// @version 1.2
// @description 标记番剧为已阅或删除状态,支持显示/隐藏标记番剧,一键复制动画名称
// @author Cylirix
// @match https://yuc.wiki/*
// @exclude https://yuc.wiki/new/
// @icon https://yuc.wiki/images/32x32.png
// @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';
const SEASON_STATE_KEY = 'YUC_SEASON_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,
seasonOpacity: 0.6 // 新增月份标记透明度设置
};
// 加载设置
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 loadSeasonState() {
const saved = GM_getValue(SEASON_STATE_KEY, '{}');
return JSON.parse(saved);
}
// 保存月份状态
function saveSeasonState(state) {
GM_setValue(SEASON_STATE_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;
right: 20px;
background: rgba(255, 255, 255, 0.85);
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 seasonOpacityRow = document.createElement('div');
seasonOpacityRow.style.display = 'flex';
seasonOpacityRow.style.alignItems = 'center';
seasonOpacityRow.style.marginBottom = '12px';
const seasonOpacityLabel = document.createElement('label');
seasonOpacityLabel.textContent = '季度(状态)透明度:';
seasonOpacityLabel.style.marginRight = '8px';
seasonOpacityLabel.style.flex = '0 0 auto';
seasonOpacityLabel.style.fontSize = '14px';
const seasonOpacitySlider = document.createElement('input');
seasonOpacitySlider.type = 'range';
seasonOpacitySlider.min = '0.1';
seasonOpacitySlider.max = '1.0';
seasonOpacitySlider.step = '0.05';
seasonOpacitySlider.value = settings.seasonOpacity;
seasonOpacitySlider.style.flex = '1';
seasonOpacitySlider.style.height = '16px';
const seasonOpacityValue = document.createElement('div');
seasonOpacityValue.textContent = Math.round(settings.seasonOpacity * 100) + '%';
seasonOpacityValue.style.flex = '0 0 40px';
seasonOpacityValue.style.textAlign = 'right';
seasonOpacityValue.style.paddingLeft = '8px';
seasonOpacityValue.style.fontSize = '14px';
seasonOpacitySlider.addEventListener('input', () => {
settings.seasonOpacity = parseFloat(seasonOpacitySlider.value);
seasonOpacityValue.textContent = Math.round(settings.seasonOpacity * 100) + '%';
applySeasonStates();
});
seasonOpacityRow.appendChild(seasonOpacityLabel);
seasonOpacityRow.appendChild(seasonOpacitySlider);
seasonOpacityRow.appendChild(seasonOpacityValue);
panel.appendChild(seasonOpacityRow);
// 按钮可视度控制
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, '{}');
GM_setValue(SEASON_STATE_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;
-moz-user-select: none;
-ms-user-select: none;
-webkit-touch-callout: none;
-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();
// 应用月份状态
applySeasonStates();
// 应用复制按钮
setupCopyButtons();
}
// 应用月份状态
function applySeasonStates() {
document.querySelectorAll('.season-item').forEach(item => {
const link = item.querySelector('a').href;
const state = seasonState[link] || 'none';
if (state === 'read') {
item.style.opacity = settings.seasonOpacity;
item.style.filter = 'grayscale(100%)';
} else {
item.style.opacity = '';
item.style.filter = '';
}
});
}
// 获取动画标题文本
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 > 1) {
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) {
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 setupSeasonItems() {
const seasonItems = document.querySelectorAll('.links-of-blogroll-item:not(.season-item)');
if (!seasonItems.length) return;
seasonItems.forEach(item => {
item.classList.add('season-item');
const link = item.querySelector('a').href;
if (link.includes('/new') || link.includes('/sp') || link.includes('/movie')) return;
if (item.querySelector('.season-btn-container')) return;
// 创建按钮容器(修改样式部分)
const btnContainer = document.createElement('div');
btnContainer.className = 'season-btn-container';
btnContainer.style.cssText = `
display: inline-flex !important;
margin-left: 2px;
gap: 2px;
opacity: 0; /* 初始状态完全透明 */
transition: opacity 0.2s ease;
pointer-events: none; /* 初始状态不可交互 */
`;
// 创建按钮
const readBtn = document.createElement('button');
readBtn.textContent = '已阅';
readBtn.style.cssText = `
padding: 1px 4px;
border-radius: 3px;
border: 1px solid #4CAF50;
background-color: rgba(76,175,80,0.2);
color: #4CAF50;
cursor: pointer;
font-size: 12px;
min-width: 38px;
line-height: 1.2;
transform: scale(0.9);
transition: all 0.2s;
`;
// 修改悬浮逻辑 - 使用JavaScript控制透明度
item.addEventListener('mouseenter', () => {
btnContainer.style.opacity = settings.buttonOpacity || 0.8;
btnContainer.style.pointerEvents = 'auto';
});
item.addEventListener('mouseleave', () => {
btnContainer.style.opacity = 0;
btnContainer.style.pointerEvents = 'none';
});
// 调整链接容器布局
const linkContainer = item.querySelector('a').parentNode;
linkContainer.style.cssText = `
display: inline-flex;
align-items: flex-end;
gap: 3px;
`;
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();
seasonState[link] = seasonState[link] === 'read' ? 'none' : 'read';
applySeasonStates();
saveSeasonState(seasonState);
});
btnContainer.appendChild(readBtn);
item.querySelector('a').insertAdjacentElement('afterend', btnContainer);
});
}
// 设置复制按钮(在文字区域内)
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_', '.movie_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 > 1) {
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();
const seasonState = loadSeasonState();
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() {
setupSeasonItems();
addNavSettingButton();
setupAnimeItems();
applyStates();
setupDetails();
createControlPanel();
createFloatingBall();
toggleFloatingBall(true);
setupPanelCloseOnOutsideClick();
}
window.addEventListener('load', init);
if (document.readyState === 'complete') {
setTimeout(init, 500);
}
})();