`;
document.body.appendChild(notificationElement);
timeout = setTimeout(() => {
if (notificationElement) {
notificationElement.style.animation = 'fadeOut 0.3s ease-out forwards';
setTimeout(cleanup, 300);
}
}, duration);
} catch(e) {}
};
fn.cancel = cleanup;
return fn;
})(),
setCache(key, value, ttl = CONFIG.CACHE_TTL) { cache.set(key, new CacheItem(value, ttl)); },
getCache(key) { const item = cache.get(key); if (item && item.isValid()) return item.value; if (item) cache.delete(key); return null; },
clearCache() { cache.clear(); },
startCleanup,
stopCleanup,
migrateFlickerList,
detectPageBrightness,
dispose() {
stopCleanup();
this.clearCache();
this.removeElementById(CONFIG.STYLE_ID);
this.removeElementById(CONFIG.ANIMATION_ID);
this.removeElementById(CONFIG.FILTER_STYLE_ID);
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = warmOverlay = null;
if (styleIntegrityTimer) clearInterval(styleIntegrityTimer);
if (recoveryObserver) recoveryObserver.disconnect();
if (headObserver) headObserver.disconnect();
if (preloadTimer) clearTimeout(preloadTimer);
if (ensureAppliedTimer) clearTimeout(ensureAppliedTimer);
if (rafRecoveryId) cancelAnimationFrame(rafRecoveryId);
stopThemeMode();
stopSoftwarmMode();
}
};
})();
Util.addStyle(CONFIG.ANIMATION_ID, `
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes fadeOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
`);
Util.migrateFlickerList();
// ==================== 强制专用模式相关函数 ====================
function isForceSpecialSite() {
if (!Util.getValue('forceSpecialModeEnabled')) return false;
const host = location.host;
const list = Util.getValue('forceSpecialList') || [];
for (const item of list) {
let domain = typeof item === 'string' ? item : item.domain;
if (domain === host) return true;
}
return false;
}
function getForceSpecialModeForSite() {
if (!isForceSpecialSite()) return null;
const host = location.host;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (!entry) return null;
if (typeof entry === 'string') {
if (VALID_MODES.includes(entry)) return entry;
return null;
}
if (typeof entry === 'object' && entry.mode && VALID_MODES.includes(entry.mode)) return entry.mode;
return null;
} catch(e) { return null; }
}
function addToForceSpecialList(domain) {
let list = Util.getValue('forceSpecialList') || [];
const exists = list.some(item => {
let d = typeof item === 'string' ? item : item.domain;
return d === domain;
});
if (exists) return false;
list.push({ domain: domain, addedTime: Date.now() });
Util.setValue('forceSpecialList', list);
Util.showNotification(`已将 ${domain} 添加到强制专用模式列表`, 'success', 1500);
return true;
}
function removeFromForceSpecialList(domain) {
let list = Util.getValue('forceSpecialList') || [];
let newList = list.filter(item => {
let d = typeof item === 'string' ? item : item.domain;
return d !== domain;
});
Util.setValue('forceSpecialList', newList);
Util.showNotification(`已从强制专用模式列表移除 ${domain}`, 'success', 1500);
}
function clearForceSpecialList() {
Util.setValue('forceSpecialList', []);
Util.showNotification('已清空强制专用模式列表', 'success', 1500);
}
// ==================== 样式生成函数 ====================
function generateFullStyleCSS(mode) {
if (mode === 'light') return '';
if (mode === 'dark') {
let style_30 = Util.getValue('customDark3') || '90';
let dark3Exclude = Util.getValue('dark3Exclude');
let isFirefox = /Firefox/i.test(navigator.userAgent);
let filterValue = `invert(${style_30}%)`;
let baseCSS = isFirefox ?
`html { filter: ${filterValue} !important; background-image: url(); text-shadow: 0 0 0 !important; }` :
`html { filter: ${filterValue} !important; text-shadow: 0 0 0 !important; }`;
let excludeCSS = `${dark3Exclude} { filter: invert(1) !important; }`;
let scrollbarCSS = `
::-webkit-scrollbar { height: 12px !important; width: 12px !important; }
::-webkit-scrollbar-thumb { border-radius: 0; border-color: transparent; border-style: dashed; background-color: #3f4752 !important; background-clip: padding-box; transition: background-color .32s ease-in-out; }
::-webkit-scrollbar-corner { background: #202020 !important; }
::-webkit-scrollbar-track { background-color: #22272e !important; }
::-webkit-scrollbar-thumb:hover { background: #3f4752 !important; }
`;
return baseCSS + excludeCSS + scrollbarCSS;
}
if (mode === 'filter') {
return '';
}
if (THEMES[mode]) {
const theme = THEMES[mode];
let inv = theme.invert;
let hue = theme.hue;
let bright = theme.bright;
let contrast = theme.contrast;
let saturate = theme.saturate;
let bg = theme.bg;
const filter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`;
const mediaFilter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`;
let css = `html{filter:${filter}!important;background:${bg};will-change:filter,background;}`;
css += `img, video, iframe, canvas, [style*="background-image"]{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `img[src*=".svg"], svg{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `video::-webkit-media-controls-panel, video::-webkit-media-controls{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `picture, source{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
return css;
}
return '';
}
function generateCodePageCSS() {
let style_30 = Util.getValue('customDark3') || '90';
let isFirefox = /Firefox/i.test(navigator.userAgent);
let baseCSS = isFirefox ?
`html { filter: invert(${style_30}%) !important; background-image: url(); background-color: #0d1117 !important; text-shadow: 0 0 0 !important; }` :
`html { filter: invert(${style_30}%) !important; background-color: #0d1117 !important; text-shadow: 0 0 0 !important; }`;
let excludeCSS = `img, svg, canvas, iframe, .icon, .logo, .avatar, [src*=".svg"], [src*=".png"], [src*=".jpg"], [src*=".jpeg"], [src*=".gif"] { filter: invert(1) !important; }`;
let codeCSS = `
body, html { background-color: #0d1117 !important; color: #c9d1d9 !important; }
pre, code, .hljs, .CodeMirror, .code-viewer, .code-area, .editor-container,
.container, .main-container, .wrapper, .content, .code-container, .script-container,
.page-content, .main-content, div[style*="font-family:monospace"],
div[style*="font-family: monospace"] {
background-color: #0d1117 !important;
color: #c9d1d9 !important;
border-color: #30363d !important;
}
.header, .navbar, .nav, .top-bar, .breadcrumb, .nav-bar {
background-color: #161b22 !important;
border-color: #30363d !important;
color: #c9d1d9 !important;
}
.line-numbers, .line-number, .linenumber, .ln, .lnum {
background-color: #161b22 !important;
color: #8b949e !important;
border-right-color: #30363d !important;
}
.keyword, .function, .class-name, .operator, .punctuation, .string,
.comment, .variable, .constant, .built_in, .attr-name, .selector, .property {
color: #d2a8ff !important;
}
.string, [class*="key"] { color: #79c0ff !important; }
.number { color: #56d364 !important; }
.boolean, .null { color: #ff7b72 !important; }
::-webkit-scrollbar { background-color: #0d1117 !important; }
::-webkit-scrollbar-thumb { background-color: #30363d !important; border-radius: 6px; }
a { color: #58a6ff !important; }
button, .btn, .button, input[type="button"], input[type="submit"] {
background-color: #238636 !important;
color: #ffffff !important;
border-color: #2ea043 !important;
}
::selection { background-color: #1c6b48 !important; color: #ffffff !important; }
`;
return baseCSS + excludeCSS + codeCSS;
}
function isJsonOrCodePage() {
const url = location.href;
if (url.endsWith('.js') || url.includes('.js?') || url.endsWith('.user.js') ||
url.endsWith('.json') || url.includes('.json?') || url.includes('.json#')) {
return true;
}
if (url.includes('raw.githubusercontent.com') || url.includes('gh-proxy.org') ||
url.includes('scriptcat.org/code/') || url.includes('updategf.qytechs.cn/scripts/')) {
return true;
}
const contentType = document.contentType || '';
if (contentType.includes('application/json') || contentType.includes('text/json')) {
return true;
}
if (document.body) {
const text = document.body.textContent.trim();
if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) {
try { JSON.parse(text); return true; } catch(e) {}
}
}
return false;
}
function getCSSForMode(mode) {
if (mode === 'light') return '';
let cacheKey = mode;
if (mode === 'dark') cacheKey = `dark_${Util.getValue('customDark3')}`;
if (cachedCSS.has(cacheKey)) return cachedCSS.get(cacheKey);
let css = '';
const isCodePage = isJsonOrCodePage();
if (isCodePage) {
css = generateCodePageCSS();
} else if (mode === 'dark') {
css = generateFullStyleCSS('dark');
} else if (mode === 'filter') {
css = null;
} else if (mode === 'softwarm' || mode === 'themecolor') {
css = null;
} else if (THEMES[mode]) {
css = generateFullStyleCSS(mode);
}
if (css !== null) {
cachedCSS.set(cacheKey, css);
if (cachedCSS.size > 20) {
const firstKey = cachedCSS.keys().next().value;
cachedCSS.delete(firstKey);
}
}
return css;
}
function applyModeByMode(mode) {
if (mode === 'softwarm') {
removeFilterMode();
Util.removeElementById(CONFIG.STYLE_ID);
stopThemeMode();
applySoftwarmMode();
return;
}
if (mode === 'themecolor') {
removeOverlays();
removeFilterMode();
Util.removeElementById(CONFIG.STYLE_ID);
stopSoftwarmMode();
applyThemeMode();
return;
}
if (mode === 'filter') {
removeOverlays();
Util.removeElementById(CONFIG.STYLE_ID);
stopThemeMode();
stopSoftwarmMode();
applyFilterMode();
return;
}
removeOverlays();
removeFilterMode();
stopThemeMode();
stopSoftwarmMode();
const css = getCSSForMode(mode);
if (css === undefined || css === null || css === '') {
Util.removeElementById(CONFIG.STYLE_ID);
} else {
Util.addStyle(CONFIG.STYLE_ID, css);
}
}
function removeOverlays() {
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = null; warmOverlay = null;
}
function removeFilterMode() {
if (filterStyle && filterStyle.parentNode) filterStyle.remove();
let svg = document.getElementById(CONFIG.FILTER_SVG_ID);
if (svg) svg.remove();
filterStyle = null;
}
function applyFilterMode() {
removeFilterMode();
let excludeList = Util.getValue('filterExcludeList');
if (excludeList.includes(location.host)) return;
if (!document.getElementById(CONFIG.FILTER_SVG_ID)) {
let svgDom = '';
let div = document.createElementNS('http://www.w3.org/2000/svg', 'div');
div.innerHTML = svgDom;
let frag = document.createDocumentFragment();
while (div.firstChild) frag.appendChild(div.firstChild);
document.head.appendChild(frag);
}
let isFirefox = /Firefox/i.test(navigator.userAgent);
let filter = isFirefox ?
`filter: url('data:image/svg+xml;utf8,#eye-protect-filter') !important;` :
'-webkit-filter: url(#eye-protect-filter) !important; filter: url(#eye-protect-filter) !important;';
let reverseFilter = isFirefox ?
`filter: url('data:image/svg+xml;utf8,#eye-protect-filter-reverse') !important;` :
'-webkit-filter: url(#eye-protect-filter-reverse) !important; filter: url(#eye-protect-filter-reverse) !important;';
let css = `
@media screen {
html { ${filter} scrollbar-color: #454a4d #202324; }
img, video, iframe, canvas, :not(object):not(body) > embed, object, svg image, [style*="background:url"], [style*="background-image:url"], [style*="background: url"], [style*="background-image: url"], [background], twitterwidget, .no-dark-mode, .sr-reader, .sr-backdrop { ${reverseFilter} }
[style*="background:url"] *, [style*="background-image:url"] *, [style*="background: url"] *, [style*="background-image: url"] *, input, [background] *, img[src^="https://s0.wp.com/latex.php"], twitterwidget .NaturalImage-image { -webkit-filter: none !important; filter: none !important; }
html { text-shadow: 0 0 0 !important; }
.no-filter, :-webkit-full-screen, :-webkit-full-screen *, :-moz-full-screen, :-moz-full-screen *, :fullscreen, :fullscreen * { -webkit-filter: none !important; filter: none !important; }
::-webkit-scrollbar { background-color: #202324; color: #aba499; }
::-webkit-scrollbar-thumb { background-color: #454a4d; }
::-webkit-scrollbar-thumb:hover { background-color: #575e62; }
::-webkit-scrollbar-thumb:active { background-color: #484e51; }
::-webkit-scrollbar-corner { background-color: #181a1b; }
html { background: #fff !important; }
}
`;
if (filterStyle && filterStyle.textContent === css) return;
filterStyle = document.createElement('style');
filterStyle.id = CONFIG.FILTER_STYLE_ID;
filterStyle.textContent = css;
document.head.appendChild(filterStyle);
}
// ==================== 超强预加载与即时模式 ====================
function preGenerateCommonCSS() {
const commonModes = ['dark', 'soft', 'pure', 'eyecare'];
for (const mode of commonModes) {
if (!cachedCSS.has(mode)) {
const css = generateFullStyleCSS(mode);
if (css) cachedCSS.set(mode, css);
}
}
}
function immediateStyleInjection() {
if (immediateApplyDone) return;
if (Util.isBlacklisted() || Util.isFlickerSuppressSite()) {
immediateApplyDone = true;
return;
}
const mode = getEffectiveMode();
if (mode === 'light') {
immediateApplyDone = true;
return;
}
if (mode === 'softwarm') {
if (document.documentElement) {
applySoftwarmMode();
immediateApplyDone = true;
} else {
const waitForRoot = setInterval(() => {
if (document.documentElement) {
clearInterval(waitForRoot);
applySoftwarmMode();
immediateApplyDone = true;
}
}, 5);
setTimeout(() => { clearInterval(waitForRoot); immediateApplyDone = true; }, 500);
}
return;
}
if (mode === 'filter') {
if (document.head) {
applyFilterMode();
immediateApplyDone = true;
} else {
if (!headObserver) {
headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect();
applyFilterMode();
immediateApplyDone = true;
}
});
headObserver.observe(document.documentElement, { childList: true, subtree: true });
}
}
return;
}
if (mode === 'themecolor') {
if (document.head) {
applyThemeMode();
immediateApplyDone = true;
} else {
if (!headObserver) {
headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect();
applyThemeMode();
immediateApplyDone = true;
}
});
headObserver.observe(document.documentElement, { childList: true, subtree: true });
}
}
return;
}
const css = getCSSForMode(mode);
if (css && css !== '') {
if (document.head) {
Util.addStyle(CONFIG.STYLE_ID, css);
immediateApplyDone = true;
} else {
if (!headObserver) {
headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect();
Util.addStyle(CONFIG.STYLE_ID, css);
immediateApplyDone = true;
}
});
headObserver.observe(document.documentElement, { childList: true, subtree: true });
}
}
} else {
immediateApplyDone = true;
}
}
function startPreload() {
if (preloadTimer) clearTimeout(preloadTimer);
const checkAndInject = () => {
if (earlyInjected) return;
if (Util.isBlacklisted() || Util.isFlickerSuppressSite()) {
earlyInjected = true;
return;
}
const mode = getEffectiveMode();
if (mode === 'softwarm' || mode === 'filter' || mode === 'themecolor') {
earlyInjected = true;
return;
}
const css = getCSSForMode(mode);
if (css && css !== '') {
if (document.head) {
Util.addStyle(CONFIG.STYLE_ID, css);
earlyInjected = true;
return;
}
}
preloadRetryCount++;
if (preloadRetryCount < MAX_PRELOAD_RETRY) {
preloadTimer = setTimeout(checkAndInject, 10);
} else {
earlyInjected = true;
}
};
checkAndInject();
}
function earlyApplyStyles() {
if (earlyInjected) return;
const preloadEnabled = Util.getValue('PRELOAD_ENABLED');
const instantMode = Util.getValue('INSTANT_MODE');
if (!preloadEnabled && !instantMode) return;
startPreload();
}
// ==================== 模式管理辅助函数 ====================
function getCurrentGlobalMode() {
let mode = Util.getValue('currentMode');
if (!mode || !MODE_NAMES[mode]) mode = 'light';
return mode;
}
function getSiteSpecificMode(domain, ignoreTempDisable = false) {
let host = domain || location.host;
if (!ignoreTempDisable && Util.getValue('tempDisableAllSiteModes')) return null;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (!entry) return null;
if (typeof entry === 'string') {
if (VALID_MODES.includes(entry)) return entry;
delete siteModes[host];
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
return null;
}
if (typeof entry === 'object' && entry.mode && VALID_MODES.includes(entry.mode)) return entry;
delete siteModes[host];
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
return null;
} catch(e) { return null; }
}
function setSiteSpecificMode(domain, mode, extra = null) {
let host = domain;
if (!host) return;
if (mode && mode !== 'default' && !VALID_MODES.includes(mode)) {
console.warn('无效的专用模式:', mode);
Util.showNotification(`模式 "${mode}" 无效,已忽略`, 'warning', 2000);
return;
}
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
if (mode === 'default' || mode === null) {
delete siteModes[host];
} else if (mode === 'softwarm') {
let existing = siteModes[host] || {};
siteModes[host] = {
mode: 'softwarm',
type: extra && extra.type ? extra.type : (existing.type || 'green'),
greenOpacity: extra && extra.greenOpacity !== undefined ? extra.greenOpacity : (existing.greenOpacity !== undefined ? existing.greenOpacity : 0.3),
warmOpacity: extra && extra.warmOpacity !== undefined ? extra.warmOpacity : (existing.warmOpacity !== undefined ? existing.warmOpacity : 0.3)
};
} else if (mode === 'themecolor') {
let colors = extra && extra.colors ? extra.colors : { bg: '#C7EDCC', link: '#2E7D32' };
siteModes[host] = { mode: 'themecolor', colors: colors };
} else {
siteModes[host] = mode;
}
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
Util.showNotification(`已为 ${host} 设置专用模式: ${MODE_NAMES[mode] || mode}`, 'success', 1500);
} catch(e) { console.error('设置网站专用模式出错:', e); }
}
function shouldApplyGlobalMode() {
let globalEnable = Util.getValue('globalEnable');
let enableList = Util.getValue('enableList');
let forcedList = Util.getValue('forcedEnableList');
let host = location.host;
let basicEnabled = false;
if (forcedList.includes(host)) {
basicEnabled = (globalEnable || enableList.includes(host));
} else {
basicEnabled = (globalEnable || enableList.includes(host));
}
if (!basicEnabled) return false;
const inWhitelist = enableList.includes(host);
const avoidDarkSite = Util.getValue('AVOID_DARK_SITE');
if (avoidDarkSite && !inWhitelist) {
try {
const brightness = Util.detectPageBrightness();
const threshold = Util.getValue('BRIGHTNESS_THRESHOLD');
if (brightness < threshold) return false;
} catch(e) {}
}
return true;
}
function getEffectiveMode() {
const host = location.host;
if (Util.isBlacklisted()) return 'light';
const forceMode = getForceSpecialModeForSite();
if (forceMode !== null) return forceMode;
let siteSpecific = getSiteSpecificMode();
if (siteSpecific) {
if (typeof siteSpecific === 'string') return siteSpecific;
if (typeof siteSpecific === 'object' && siteSpecific.mode) return siteSpecific.mode;
}
if (shouldApplyGlobalMode()) {
return getCurrentGlobalMode();
}
return 'light';
}
// ==================== 强效恢复守护 ====================
function isStyleCorrectlyApplied(mode) {
if (mode === 'light') return true;
if (mode === 'softwarm') {
const type = getSoftwarmTypeForSite();
const opacity = (type === 'green') ? getSoftwarmGreenOpacityForSite() : getSoftwarmWarmOpacityForSite();
if (opacity <= 0) return true;
if (type === 'green') {
return greenOverlay && document.documentElement.contains(greenOverlay) &&
greenOverlay.style.opacity === String(opacity);
} else {
return warmOverlay && document.documentElement.contains(warmOverlay) &&
warmOverlay.style.opacity === String(opacity);
}
}
if (mode === 'filter') {
return filterStyle && document.head.contains(filterStyle) && filterStyle.textContent.includes('feColorMatrix');
}
if (mode === 'themecolor') {
return customModeActive && customStyleEl && document.head.contains(customStyleEl);
}
const styleEl = document.getElementById(CONFIG.STYLE_ID);
if (!styleEl || !document.head.contains(styleEl)) return false;
const expectedCSS = getCSSForMode(mode);
if (!expectedCSS) return false;
return styleEl.textContent.replace(/\s/g, '') === expectedCSS.replace(/\s/g, '');
}
function forceReapplyMode() {
if (Util.isBlacklisted()) return;
const mode = getEffectiveMode();
if (mode === 'light') {
Util.removeElementById(CONFIG.STYLE_ID);
removeOverlays();
removeFilterMode();
stopThemeMode();
stopSoftwarmMode();
} else {
applyModeByMode(mode);
}
lastAppliedMode = mode;
saveStyleSnapshot();
}
function startEnsureApplied() {
if (ensureAppliedTimer) clearTimeout(ensureAppliedTimer);
ensureAppliedRetries = 0;
function checkAndApply() {
if (Util.isBlacklisted()) {
ensureAppliedRetries = MAX_ENSURE_RETRIES;
return;
}
const mode = getEffectiveMode();
if (mode !== 'light' && !isStyleCorrectlyApplied(mode)) {
forceReapplyMode();
}
ensureAppliedRetries++;
if (ensureAppliedRetries < MAX_ENSURE_RETRIES) {
ensureAppliedTimer = setTimeout(checkAndApply, ENSURE_RETRY_INTERVAL);
} else {
ensureAppliedTimer = null;
}
}
ensureAppliedTimer = setTimeout(checkAndApply, ENSURE_RETRY_INTERVAL);
}
function stopEnsureApplied() {
if (ensureAppliedTimer) {
clearTimeout(ensureAppliedTimer);
ensureAppliedTimer = null;
}
}
function startRafRecovery() {
if (rafRecoveryId) cancelAnimationFrame(rafRecoveryId);
let lastCheck = 0;
function recoveryLoop(now) {
if (!Util.getValue('FORCE_RECOVERY')) {
rafRecoveryId = requestAnimationFrame(recoveryLoop);
return;
}
if (now - lastCheck >= RECOVERY_RAF_INTERVAL) {
lastCheck = now;
if (!Util.isBlacklisted()) {
const mode = getEffectiveMode();
if (mode !== 'light' && !isStyleCorrectlyApplied(mode)) {
forceReapplyMode();
}
}
}
rafRecoveryId = requestAnimationFrame(recoveryLoop);
}
rafRecoveryId = requestAnimationFrame(recoveryLoop);
}
function stopRafRecovery() {
if (rafRecoveryId) {
cancelAnimationFrame(rafRecoveryId);
rafRecoveryId = null;
}
}
// ==================== bfcache 无闪烁恢复核心 ====================
function saveStyleSnapshot() {
const mode = getEffectiveMode();
if (mode === 'light') {
GM_setValue('bfcache_snapshot', { mode: 'light' });
return;
}
const snapshot = { mode: mode, timestamp: Date.now() };
if (mode === 'softwarm') {
snapshot.softwarmType = getSoftwarmTypeForSite();
snapshot.softwarmGreenOpacity = getSoftwarmGreenOpacityForSite();
snapshot.softwarmWarmOpacity = getSoftwarmWarmOpacityForSite();
} else if (mode === 'filter') {
snapshot.css = null;
} else if (mode === 'themecolor') {
const { bg, link } = getCurrentThemeColors();
snapshot.themeColors = { bg, link };
} else {
const css = getCSSForMode(mode);
snapshot.css = css;
}
GM_setValue('bfcache_snapshot', snapshot);
}
function restoreFromSnapshot() {
const snapshot = GM_getValue('bfcache_snapshot');
if (!snapshot || !snapshot.mode || snapshot.mode === 'light') {
Util.removeElementById(CONFIG.STYLE_ID);
removeOverlays();
removeFilterMode();
stopThemeMode();
stopSoftwarmMode();
return;
}
const mode = snapshot.mode;
if (mode === 'softwarm') {
if (snapshot.softwarmType) {
Util.setValue('softwarmGlobalType', snapshot.softwarmType);
Util.setValue('softwarmGlobalGreenOpacity', snapshot.softwarmGreenOpacity || 0.3);
Util.setValue('softwarmGlobalWarmOpacity', snapshot.softwarmWarmOpacity || 0.3);
}
applySoftwarmMode();
} else if (mode === 'filter') {
applyFilterMode();
} else if (mode === 'themecolor') {
if (snapshot.themeColors) {
updateThemeCSSVars(snapshot.themeColors.bg, snapshot.themeColors.link);
customModeActive = true;
if (!customModeObserver) startThemeModeObserver();
else scanAndTagForThemeMode();
} else {
applyThemeMode();
}
} else {
const css = snapshot.css;
if (css) {
Util.addStyle(CONFIG.STYLE_ID, css);
} else {
const newCss = getCSSForMode(mode);
if (newCss) Util.addStyle(CONFIG.STYLE_ID, newCss);
}
}
lastAppliedMode = mode;
}
// ==================== 强制恢复增强模块 ====================
function isStyleValidForMode(mode) {
if (mode === 'light') return true;
if (mode === 'softwarm') {
const type = getSoftwarmTypeForSite();
const opacity = (type === 'green') ? getSoftwarmGreenOpacityForSite() : getSoftwarmWarmOpacityForSite();
if (opacity <= 0) return true;
if (type === 'green') {
return greenOverlay && document.documentElement.contains(greenOverlay);
} else {
return warmOverlay && document.documentElement.contains(warmOverlay);
}
}
if (mode === 'filter') {
return filterStyle && document.head.contains(filterStyle);
}
if (mode === 'themecolor') {
return customModeActive && customStyleEl && document.head.contains(customStyleEl);
}
const styleEl = document.getElementById(CONFIG.STYLE_ID);
return styleEl && document.head.contains(styleEl);
}
function safeRecover(force = false) {
if (!Util.getValue('FORCE_RECOVERY')) return;
if (isRecovering) return;
isRecovering = true;
setTimeout(() => {
try {
const mode = getEffectiveMode();
if (!force && lastAppliedMode === mode && isStyleValidForMode(mode)) {
isRecovering = false;
return;
}
forceReapplyMode();
lastAppliedMode = getEffectiveMode();
} catch(e) {
console.warn('强制恢复失败', e);
} finally {
isRecovering = false;
}
}, CONFIG.RECOVERY_DEBOUNCE);
}
function startEnhancedRecovery() {
if (!Util.getValue('FORCE_RECOVERY')) return;
if (styleIntegrityTimer) clearInterval(styleIntegrityTimer);
styleIntegrityTimer = setInterval(() => {
if (Util.isBlacklisted()) return;
safeRecover(false);
}, CONFIG.RECOVERY_INTERVAL);
if (recoveryObserver) recoveryObserver.disconnect();
recoveryObserver = new MutationObserver((mutations) => {
if (!Util.getValue('FORCE_RECOVERY')) return;
if (Util.isBlacklisted()) return;
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.removedNodes.length) {
for (const node of mutation.removedNodes) {
if (node.nodeType === 1) {
const id = node.id;
if (id === CONFIG.STYLE_ID || id === CONFIG.FILTER_STYLE_ID ||
id === 'eye-protect-green-overlay' || id === 'eye-protect-warm-overlay' ||
id === 'theme-mode-core-style') {
safeRecover(true);
return;
}
}
}
}
}
});
const targetNode = document.head || document.documentElement;
if (targetNode) {
recoveryObserver.observe(targetNode, { childList: true, subtree: true });
} else {
setTimeout(() => startEnhancedRecovery(), 100);
}
}
// ==================== 页面返回修复 ====================
function patchHistoryAPI() {
try {
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
const immediateReapply = () => {
if (navigationTimer) clearTimeout(navigationTimer);
if (!Util.isBlacklisted()) {
forceReapplyMode();
ObserverManager.restart();
stopEnsureApplied();
startEnsureApplied();
}
navigationTimer = setTimeout(() => {
if (!Util.isBlacklisted()) {
forceReapplyMode();
ObserverManager.restart();
stopEnsureApplied();
startEnsureApplied();
}
navigationTimer = null;
}, 100);
};
history.pushState = function() { originalPushState.apply(this, arguments); immediateReapply(); };
history.replaceState = function() { originalReplaceState.apply(this, arguments); immediateReapply(); };
window.addEventListener('popstate', () => {
immediateReapply();
});
window.addEventListener('pagehide', () => {
saveStyleSnapshot();
stopEnsureApplied();
});
window.addEventListener('pageshow', (event) => {
if (!bfCacheRestoring) {
bfCacheRestoring = true;
if (event.persisted) {
forceReapplyMode();
} else {
forceReapplyMode();
}
if (!Util.isBlacklisted()) {
ObserverManager.restart();
}
stopEnsureApplied();
startEnsureApplied();
bfCacheRestoring = false;
}
});
window.addEventListener('beforeunload', () => {
if (history.pushState !== originalPushState) history.pushState = originalPushState;
if (history.replaceState !== originalReplaceState) history.replaceState = originalReplaceState;
stopEnsureApplied();
}, { once: true });
} catch(e) {}
}
function initVisibilityRecovery() {
const handleVisibilityChange = () => {
if (!document.hidden && Util.getValue('FORCE_RECOVERY')) {
safeRecover(true);
stopEnsureApplied();
startEnsureApplied();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
startEnhancedRecovery();
}
// ==================== ObserverManager ====================
const ObserverManager = (() => {
let mutationObserver = null, styleObserver = null, isActive = false, debounceTimer = null, isSuppressMode = false, keepAliveTimer = null, lastApplyTime = Date.now();
let observerNode = null, isDisposed = false;
function handleMutations(mutations) {
if (isDisposed) return;
lastApplyTime = Date.now();
if (debounceTimer) clearTimeout(debounceTimer);
const isSuppress = Util.isFlickerSuppressSite();
const delay = CONFIG.DEBOUNCE_DELAY;
const nodeThreshold = isSuppress ? 10 : 3;
debounceTimer = setTimeout(() => {
if (isDisposed) return;
const significantChange = mutations.some(m => m.type === 'childList' && m.addedNodes.length > nodeThreshold);
if (significantChange) {
if (isSuppress) { pause(); setTimeout(() => resume(), 50); }
forceReapplyMode();
}
debounceTimer = null;
}, delay);
}
function startKeepAlive() {
if (isDisposed) return;
if (keepAliveTimer) clearInterval(keepAliveTimer);
keepAliveTimer = setInterval(() => {
if (isDisposed) return;
if (Date.now() - lastApplyTime > 30000) { forceReapplyMode(); lastApplyTime = Date.now(); }
}, 15000);
}
function stopKeepAlive() { if (keepAliveTimer) { clearInterval(keepAliveTimer); keepAliveTimer = null; } }
return {
start() {
if (isActive || isDisposed) return;
if (!document.body) { setTimeout(() => this.start(), 100); return; }
isActive = true; isDisposed = false; isSuppressMode = Util.isFlickerSuppressSite(); lastApplyTime = Date.now(); observerNode = document.body;
mutationObserver = new MutationObserver(handleMutations);
try { mutationObserver.observe(observerNode, { childList: true, subtree: true, attributes: false, characterData: false }); } catch(e) {}
if (Util.getValue('FORCE_RECOVERY')) {
styleObserver = new MutationObserver((mutations) => {
if (isSuppressMode || isDisposed) return;
for (let m of mutations) {
if (m.removedNodes.length) {
for (let node of m.removedNodes) {
if (node.nodeType === 1 && (node.id === CONFIG.STYLE_ID || node.id === CONFIG.FILTER_STYLE_ID || node.id === 'eye-protect-green-overlay' || node.id === 'eye-protect-warm-overlay' || node.id === 'theme-mode-core-style')) {
setTimeout(() => { if (!isDisposed) forceReapplyMode(); }, 20);
lastApplyTime = Date.now(); return;
}
}
}
}
});
try { styleObserver.observe(document.head || document.documentElement, { childList: true, subtree: false }); } catch(e) {}
}
startKeepAlive();
},
stop() {
isActive = false; stopKeepAlive();
if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }
if (mutationObserver) { try { mutationObserver.disconnect(); mutationObserver = null; } catch(e) {} }
if (styleObserver) { try { styleObserver.disconnect(); styleObserver = null; } catch(e) {} }
observerNode = null; isSuppressMode = false;
},
pause() {
if (mutationObserver) try { mutationObserver.disconnect(); } catch(e) {}
if (styleObserver) try { styleObserver.disconnect(); } catch(e) {}
stopKeepAlive();
},
resume() {
if (!isActive || isDisposed) return;
if (!observerNode) observerNode = document.body;
startKeepAlive();
if (mutationObserver && observerNode) try { mutationObserver.observe(observerNode, { childList: true, subtree: true, attributes: false, characterData: false }); } catch(e) {}
if (styleObserver && Util.getValue('FORCE_RECOVERY')) try { styleObserver.observe(document.head || document.documentElement, { childList: true, subtree: false }); } catch(e) {}
},
restart() { this.stop(); setTimeout(() => this.start(), 50); },
dispose() { isDisposed = true; this.stop(); }
};
})();
// ==================== 样式管理器 ====================
const StyleManager = (() => {
function apply(force = false) {
const mode = getEffectiveMode();
if (mode === 'light') {
cleanAllEffects();
} else {
applyModeByMode(mode);
}
lastAppliedMode = mode;
saveStyleSnapshot();
}
function cleanAllEffects() {
Util.removeElementById(CONFIG.STYLE_ID);
removeOverlays();
removeFilterMode();
stopThemeMode();
stopSoftwarmMode();
GM_setValue('bfcache_snapshot', { mode: 'light' });
}
return { apply };
})();
// ==================== 切换模式核心函数 ====================
function getNextModeInCycle(currentMode, cycleModes) {
let modes = (cycleModes && Array.isArray(cycleModes) && cycleModes.length > 0) ? [...cycleModes] : ['dark'];
const idx = modes.indexOf(currentMode);
if (idx !== -1) {
const nextIdx = (idx + 1) % modes.length;
return modes[nextIdx];
} else {
if (currentMode === 'light') {
return modes[0];
}
if (modes.includes('light')) return 'light';
return modes[0];
}
}
function switchMode() {
let oldMode = Util.getValue('currentMode') || 'light';
let cycleModes = Util.getValue('globalCycleModes');
if (!Array.isArray(cycleModes) || cycleModes.length === 0) {
cycleModes = ['light', 'dark', 'eyecare', 'softwarm', 'themecolor', 'filter', 'soft', 'pure'];
Util.setValue('globalCycleModes', cycleModes);
}
const nextMode = getNextModeInCycle(oldMode, cycleModes);
Util.setValue('currentMode', nextMode);
handleAutoDisableTempModeOnModeChange(nextMode, oldMode);
StyleManager.apply(true);
setTimeout(() => {
refreshMenu();
Util.showNotification(`已切换到 ${getModeName(nextMode)}`, 'success', 1500);
scheduleUIUpdate();
}, 0);
}
// ==================== 菜单与设置面板 ====================
function clearMenu() { menuCommands.forEach(cmd => { try { GM_unregisterMenuCommand(cmd); } catch(e) {} }); menuCommands = []; }
function refreshMenu() { clearMenu(); initMenu(); }
function getModeName(mode) { return MODE_NAMES[mode] || '白天模式'; }
function getSiteModeName(mode) {
if (typeof mode === 'object' && mode.mode === 'softwarm') return '🍃 柔暖双色';
if (typeof mode === 'object' && mode.mode === 'themecolor') return '🌈 护眼主题';
return MODE_NAMES[mode] || '默认';
}
function handleAutoDisableTempModeOnModeChange(newMode, oldMode) {
const autoMode = Util.getValue('autoDisableTempMode');
if (!autoMode) return;
const alsoEnableForce = Util.getValue('autoDisableAlsoEnableForceSpecial');
if (newMode === autoMode && oldMode !== autoMode) {
let changed = false;
if (Util.getValue('tempDisableAllSiteModes') !== true) {
Util.setValue('tempDisableAllSiteModes', true);
changed = true;
}
if (alsoEnableForce && Util.getValue('forceSpecialModeEnabled') !== true) {
Util.setValue('forceSpecialModeEnabled', true);
changed = true;
}
if (changed) {
StyleManager.apply(true);
refreshMenu();
const msg = alsoEnableForce ? '已自动关闭专用模式并开启强制专用模式' : '已自动关闭专用模式';
Util.showNotification(`${msg}(进入 ${getModeName(newMode)} 模式)`, 'info', 2000);
}
}
else if (oldMode === autoMode && newMode !== autoMode) {
let changed = false;
if (Util.getValue('tempDisableAllSiteModes') !== false) {
Util.setValue('tempDisableAllSiteModes', false);
changed = true;
}
if (Util.getValue('forceSpecialModeEnabled') !== false) {
Util.setValue('forceSpecialModeEnabled', false);
changed = true;
}
if (changed) {
StyleManager.apply(true);
refreshMenu();
Util.showNotification(`已恢复专用模式(离开 ${getModeName(oldMode)} 模式)`, 'info', 2000);
}
}
}
let pendingUIUpdate = false;
function scheduleUIUpdate() {
if (pendingUIUpdate) return;
pendingUIUpdate = true;
requestAnimationFrame(() => {
if (currentSettingsShadow) {
updateGlobalSoftwarmPanelVisibility(currentSettingsShadow);
updateGlobalThemePanelVisibility(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
pendingUIUpdate = false;
});
}
function updateGlobalSoftwarmPanelVisibility(shadowRoot) {
if (!shadowRoot) return;
const globalSoftwarmPanel = shadowRoot.querySelector('#globalSoftwarmPanel');
if (!globalSoftwarmPanel) return;
const currentGlobalMode = getCurrentGlobalMode();
globalSoftwarmPanel.style.display = (currentGlobalMode === 'softwarm') ? 'block' : 'none';
if (currentGlobalMode === 'softwarm') {
const type = Util.getValue('softwarmGlobalType') || 'green';
const greenContainer = shadowRoot.querySelector('#globalSoftwarmGreenContainer');
const warmContainer = shadowRoot.querySelector('#globalSoftwarmWarmContainer');
if (greenContainer && warmContainer) {
greenContainer.style.display = type === 'green' ? 'block' : 'none';
warmContainer.style.display = type === 'warm' ? 'block' : 'none';
// 同步滑块值
const greenSlider = shadowRoot.querySelector('#globalSoftwarmGreenSlider');
const warmSlider = shadowRoot.querySelector('#globalSoftwarmWarmSlider');
const greenValue = shadowRoot.querySelector('#globalSoftwarmGreenValue');
const warmValue = shadowRoot.querySelector('#globalSoftwarmWarmValue');
if (greenSlider && greenValue) {
greenSlider.value = Util.getValue('softwarmGlobalGreenOpacity');
greenValue.textContent = Math.round(greenSlider.value * 100);
}
if (warmSlider && warmValue) {
warmSlider.value = Util.getValue('softwarmGlobalWarmOpacity');
warmValue.textContent = Math.round(warmSlider.value * 100);
}
}
}
}
function updateGlobalThemePanelVisibility(shadowRoot) {
if (!shadowRoot) return;
const globalThemePanel = shadowRoot.querySelector('#globalThemePanel');
if (!globalThemePanel) return;
const currentGlobalMode = getCurrentGlobalMode();
globalThemePanel.style.display = (currentGlobalMode === 'themecolor') ? 'block' : 'none';
}
function isSpecialModeActive() {
if (Util.getValue('tempDisableAllSiteModes')) return false;
let siteSpecific = getSiteSpecificMode();
if (siteSpecific && siteSpecific !== 'default') return true;
if (isForceSpecialSite() && getForceSpecialModeForSite() !== null) return true;
return false;
}
function updateSettingsPanelUI(shadowRoot) {
if (!shadowRoot) return;
const currentGlobalMode = getCurrentGlobalMode();
const cycleModes = Util.getValue('globalCycleModes') || ['dark'];
const nextMode = getNextModeInCycle(currentGlobalMode, cycleModes);
const nextModeName = MODE_NAMES[nextMode] || '白天模式';
const toggleBtn = shadowRoot.querySelector('#toggleMode');
if (toggleBtn) toggleBtn.textContent = `切换 ${nextModeName}`;
const actualMode = getEffectiveMode();
const hasSpecial = isSpecialModeActive();
let modeDisplayText = getSiteModeName(actualMode);
if (hasSpecial) modeDisplayText += ' [专用]';
const modeDisplaySpan = shadowRoot.querySelector('.settings-section:first-child div:first-child span:last-child');
if (modeDisplaySpan) modeDisplaySpan.textContent = modeDisplayText;
const forceSpecialCheckbox = shadowRoot.querySelector('#forceSpecialEnabled');
if (forceSpecialCheckbox) {
forceSpecialCheckbox.checked = Util.getValue('forceSpecialModeEnabled');
}
const tempDisableCheckbox = shadowRoot.querySelector('#tempDisableAllSiteModes');
if (tempDisableCheckbox) {
tempDisableCheckbox.checked = Util.getValue('tempDisableAllSiteModes');
const tempDisableStatusText = shadowRoot.querySelector('#tempDisableStatusText');
if (tempDisableStatusText) {
tempDisableStatusText.textContent = Util.getValue('tempDisableAllSiteModes') ? '已关闭' : '已启用';
tempDisableStatusText.style.color = Util.getValue('tempDisableAllSiteModes') ? '#dc3545' : '#007bff';
}
}
updateGlobalSoftwarmPanelVisibility(shadowRoot);
updateGlobalThemePanelVisibility(shadowRoot);
}
function toggleGlobal() {
let current = Util.getValue('globalEnable');
Util.setValue('globalEnable', !current);
StyleManager.apply(true);
refreshMenu();
Util.showNotification(!current ? '已开启全局模式' : '已关闭全局模式', 'success', 1500);
if (currentSettingsShadow) {
updateSettingsPanelUI(currentSettingsShadow);
}
}
function toggleCurrentSite() {
let enableList = Util.getValue('enableList');
let host = location.host;
if (enableList.includes(host)) {
enableList = enableList.filter(domain => domain !== host);
Util.setValue('enableList', enableList);
StyleManager.apply(true);
Util.showNotification('已在当前网站禁用护眼模式', 'success', 1500);
} else {
enableList.push(host);
Util.setValue('enableList', enableList);
StyleManager.apply(true);
Util.showNotification('已在当前网站启用护眼模式', 'success', 1500);
}
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleForceEnable() {
let forcedList = Util.getValue('forcedEnableList');
let host = location.host;
if (forcedList.includes(host)) {
forcedList = forcedList.filter(domain => domain !== host);
Util.showNotification('已取消强制启用当前网站', 'info', 1500);
} else {
forcedList.push(host);
Util.showNotification('已强制启用当前网站', 'success', 1500);
}
Util.setValue('forcedEnableList', forcedList);
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleTempDisableAllSiteModes() {
let current = Util.getValue('tempDisableAllSiteModes');
let newState = !current;
Util.setValue('tempDisableAllSiteModes', newState);
StyleManager.apply(true);
refreshMenu();
Util.showNotification(newState ? '已关闭专用模式,使用全局设置' : '已恢复专用模式', 'success', 1500);
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleBlacklist() {
let blacklist = Util.getValue('blacklist');
let host = location.host;
if (blacklist.includes(host)) {
blacklist = blacklist.filter(domain => domain !== host);
Util.setValue('blacklist', blacklist);
Util.showNotification('已将当前网站从黑名单中移除', 'success', 1500);
StyleManager.apply(true);
} else {
blacklist.push(host);
Util.setValue('blacklist', blacklist);
Util.showNotification('已将当前网站添加到黑名单', 'info', 1500);
StyleManager.apply(true);
}
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function clearBlacklist() {
if (confirm('确定要清空黑名单吗?这将移除所有在黑名单中的网站。')) {
Util.setValue('blacklist', []);
Util.showNotification('已清空黑名单', 'success', 1500);
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
}
function clearForcedList() {
if (confirm('确定要清空强制启用列表吗?')) {
Util.setValue('forcedEnableList', []);
Util.showNotification('已清空强制启用列表', 'success', 1500);
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
}
function addCurrentToFlickerSuppress() {
let host = location.host;
let list = Util.getValue('FLICKER_SUPPRESS_LIST') || [];
const exists = list.some(item => {
let domain = typeof item === 'string' ? item : item.domain;
return domain === host;
});
if (exists) {
Util.showNotification('当前网站已在闪烁抑制列表中', 'info', 1500);
return;
}
list.push({ domain: host, addedTime: Date.now() });
Util.setValue('FLICKER_SUPPRESS_LIST', list);
Util.showNotification(`已将 ${host} 添加到闪烁抑制列表`, 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshFlickerListUI(currentSettingsShadow);
}
}
function removeFromFlickerSuppress(domain) {
let list = Util.getValue('FLICKER_SUPPRESS_LIST') || [];
let newList = list.filter(item => {
let d = typeof item === 'string' ? item : item.domain;
return d !== domain;
});
Util.setValue('FLICKER_SUPPRESS_LIST', newList);
Util.showNotification(`已从闪烁抑制列表移除 ${domain}`, 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshFlickerListUI(currentSettingsShadow);
}
}
function clearAllFlickerSuppress() {
if (confirm('确定要清空整个闪烁抑制列表吗?')) {
Util.setValue('FLICKER_SUPPRESS_LIST', []);
Util.showNotification('已清空闪烁抑制列表', 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshFlickerListUI(currentSettingsShadow);
}
}
}
function addCurrentToForceSpecial() {
let host = location.host;
if (addToForceSpecialList(host)) {
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
} else {
Util.showNotification('当前网站已在强制专用模式列表中', 'info', 1500);
}
}
function removeFromForceSpecial(domain) {
removeFromForceSpecialList(domain);
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
function clearForceSpecial() {
if (confirm('确定要清空强制专用模式列表吗?')) {
clearForceSpecialList();
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
}
// ==================== 列表渲染辅助函数 ====================
function renderListWithCurrentFirst(listObjects, currentHost) {
if (!listObjects || listObjects.length === 0) return '
暂无网站
';
let currentItem = null;
let otherItems = [];
for (let item of listObjects) {
let domain = item.domain;
if (domain === currentHost) {
currentItem = item;
} else {
otherItems.push(item);
}
}
otherItems.sort((a, b) => b.addedTime - a.addedTime);
let allItems = [];
if (currentItem) allItems.push(currentItem);
allItems.push(...otherItems);
let html = '';
allItems.forEach(item => {
let domain = item.domain;
html += `
${escapeHtml(domain)}
`;
});
return html;
}
function escapeHtml(str) { return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }); }
function refreshFlickerListUI(shadowRoot) {
const rawList = Util.getValue('FLICKER_SUPPRESS_LIST') || [];
let listObjects = rawList.map(item => {
if (typeof item === 'string') return { domain: item, addedTime: Date.now() };
return item;
});
const container = shadowRoot.querySelector('#flickerSuppressListContainer');
if (container) {
container.innerHTML = renderListWithCurrentFirst(listObjects, location.host);
container.querySelectorAll('.remove-item').forEach(btn => {
btn.addEventListener('click', (e) => {
const site = btn.getAttribute('data-site');
if (site) removeFromFlickerSuppress(site);
});
});
}
}
function refreshForceSpecialListUI(shadowRoot) {
const rawList = Util.getValue('forceSpecialList') || [];
let listObjects = rawList.map(item => {
if (typeof item === 'string') return { domain: item, addedTime: Date.now() };
return item;
});
const container = shadowRoot.querySelector('#forceSpecialListContainer');
if (container) {
container.innerHTML = renderListWithCurrentFirst(listObjects, location.host);
container.querySelectorAll('.remove-item').forEach(btn => {
btn.addEventListener('click', (e) => {
const site = btn.getAttribute('data-site');
if (site) removeFromForceSpecial(site);
});
});
}
}
// ==================== 设置面板 ====================
function showSettings() {
if (!document.body) { setTimeout(showSettings, 50); return; }
try {
let existingHost = document.querySelector('.eye-protect-settings-host');
if (existingHost) existingHost.remove();
if (currentSettingsHost) currentSettingsHost = null;
if (currentSettingsShadow) currentSettingsShadow = null;
let loadingPanel = document.createElement('div');
loadingPanel.className = 'eye-protect-settings-panel loading';
loadingPanel.style.cssText = `position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:300px;background:#fff;color:#333;border-radius:10px;box-shadow:0 5px 20px rgba(0,0,0,0.2);z-index:2147483647;font-family:system-ui,sans-serif;border:1px solid #e0e0e0;padding:20px;text-align:center;font-size:14px;`;
loadingPanel.innerHTML = `
⏳ 正在加载设置面板...
`;
document.body.appendChild(loadingPanel);
setTimeout(() => {
let currentMode = Util.getValue('currentMode');
let globalEnable = Util.getValue('globalEnable');
let enableList = Util.getValue('enableList');
let blacklist = Util.getValue('blacklist');
let forcedList = Util.getValue('forcedEnableList');
let cycleModes = Util.getValue('globalCycleModes');
if (!Array.isArray(cycleModes)) cycleModes = ['light', 'dark', 'eyecare', 'softwarm', 'themecolor', 'filter', 'soft', 'pure'];
const validModes = ['light', 'dark', 'soft', 'eyecare', 'pure', 'filter', 'softwarm', 'themecolor'];
cycleModes = cycleModes.filter(m => validModes.includes(m));
if (cycleModes.length === 0) cycleModes = ['light', 'dark', 'eyecare', 'softwarm', 'themecolor', 'filter', 'soft', 'pure'];
let host = location.host;
let siteEnabled = enableList.includes(host);
let isForced = forcedList.includes(host);
let isBlacklisted = blacklist.includes(host);
let blacklistCount = blacklist.length;
let siteSpecific = getSiteSpecificMode();
let siteSpecificMode = siteSpecific ? (typeof siteSpecific === 'string' ? siteSpecific : siteSpecific.mode) : null;
let siteSpecificThemeColors = (siteSpecific && typeof siteSpecific === 'object' && siteSpecific.mode === 'themecolor') ? siteSpecific.colors : null;
let siteSpecificSoftwarm = (siteSpecific && typeof siteSpecific === 'object' && siteSpecific.mode === 'softwarm') ? siteSpecific : null;
let tempDisableAll = Util.getValue('tempDisableAllSiteModes');
let autoDisableTempMode = Util.getValue('autoDisableTempMode') || '';
let autoDisableAlsoEnableForce = Util.getValue('autoDisableAlsoEnableForceSpecial') || false;
let runDuringDay = Util.getValue('runDuringDay');
let darkAuto = Util.getValue('darkAuto');
let customDark3 = Util.getValue('customDark3');
let showNotification = Util.getValue('SHOW_NOTIFICATION');
let flickerSuppressListRaw = Util.getValue('FLICKER_SUPPRESS_LIST') || [];
let preloadEnabled = Util.getValue('PRELOAD_ENABLED');
let instantMode = Util.getValue('INSTANT_MODE');
let forceRecovery = Util.getValue('FORCE_RECOVERY');
let avoidDarkSite = Util.getValue('AVOID_DARK_SITE');
let brightnessThreshold = Util.getValue('BRIGHTNESS_THRESHOLD');
let panelWidth = Util.getValue('panelWidth') || 720;
let panelHeight = Util.getValue('panelHeight') || null;
let panelLeft = Util.getValue('panelLeft') || '50%';
let panelTop = Util.getValue('panelTop') || '50%';
let panelTransform = Util.getValue('panelTransform') || 'translate(-50%, -50%)';
let forceSpecialEnabled = Util.getValue('forceSpecialModeEnabled');
let forceSpecialListRaw = Util.getValue('forceSpecialList') || [];
let globalThemeBg = Util.getValue('themecolorBgColor');
let globalThemeLink = Util.getValue('themecolorLinkColor');
let globalSoftwarmType = Util.getValue('softwarmGlobalType') || 'green';
let globalSoftwarmGreenOpacity = Util.getValue('softwarmGlobalGreenOpacity');
let globalSoftwarmWarmOpacity = Util.getValue('softwarmGlobalWarmOpacity');
let flickerListObjects = flickerSuppressListRaw.map(item => {
if (typeof item === 'string') return { domain: item, addedTime: Date.now() };
return item;
});
let forceSpecialListObjects = forceSpecialListRaw.map(item => {
if (typeof item === 'string') return { domain: item, addedTime: Date.now() };
return item;
});
function buildCycleListHtml() {
if (cycleModes.length === 0) return '
暂无模式,请点击下方按钮添加
';
let itemsHtml = '';
cycleModes.forEach((mode, idx) => {
let name = MODE_NAMES[mode] || mode;
itemsHtml += `