掘金主题切换器 | Juejin Dark Mode Toggle

一键切换掘金(juejin.cn)网站的黑暗/明亮主题模式。功能:1. 主题切换记忆;2. 响应式按钮;3. 适配掘金新版界面;4. SPA路由同步;5. 自动检测系统主题;6. 持久化存储主题偏好。

// ==UserScript==
// @name         掘金主题切换器 | Juejin Dark Mode Toggle
// @name:zh      掘金主题切换器
// @name:zh-TW   掘金主題切換器
// @namespace    juejin_theme_switcher_2025
// @version      1.5
// @description  一键切换掘金(juejin.cn)网站的黑暗/明亮主题模式。功能:1. 主题切换记忆;2. 响应式按钮;3. 适配掘金新版界面;4. SPA路由同步;5. 自动检测系统主题;6. 持久化存储主题偏好。
// @description:zh  一键切换掘金(juejin.cn)网站的黑暗/明亮主题模式,支持:1. 主题记忆;2. 响应式按钮;3. 新版界面适配;4. 路由同步;5. 系统主题检测;6. 偏好存储。
// @description:zh-TW 一鍵切換掘金(juejin.cn)網站的黑暗/明亮主題模式,支援:1. 主題記憶;2. 響應式按鈕;3. 新版界面適配;4. 路由同步;5. 系統主題檢測;6. 偏好存儲。
// @author       Wangshiwei
// @match        https://juejin.cn/*
// @match        https://*.juejin.cn/*
// @exclude      https://juejin.cn/extension?utm_source=jj_nav
// @exclude      https://aicoding.juejin.cn/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=juejin.cn
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // 初始化主题状态
    let isDarkTheme = GM_getValue('isDarkTheme', false);
    let observer;

    // 更新按钮样式(新增的声明)
    function updateButtonStyle() {
        const button = document.getElementById('juejin-theme-toggle');
        if (button) {
            button.innerHTML = isDarkTheme ? '☀️' : '🌙';
            button.style.backgroundColor = isDarkTheme ? '#333' : '#f0f0f0';
            button.style.color = isDarkTheme ? '#fff' : '#333';
        }
    }

    // 强化同步函数
    function applyTheme() {
        const body = document.body;
        if (!body) return;

        // 强制同步data-theme和class
        body.setAttribute('data-theme', isDarkTheme ? 'dark' : 'light');
        body.className = body.className.replace(/\b(light|dark)\b/g, '') + (isDarkTheme ? ' dark' : ' light');

        updateButtonStyle();
    }

    // 切换主题函数
    function toggleTheme() {
        isDarkTheme = !isDarkTheme;
        GM_setValue('isDarkTheme', isDarkTheme);
        applyTheme();
    }

    // 创建切换按钮
    function createThemeToggleButton() {
        const oldBtn = document.getElementById('juejin-theme-toggle');
        if (oldBtn) oldBtn.remove();

        const button = document.createElement('button');
        button.id = 'juejin-theme-toggle';
        button.innerHTML = isDarkTheme ? '☀️' : '🌙';
        Object.assign(button.style, {
            position: 'fixed',
            top: '12px',
            right: '0px',
            zIndex: '9999',
            padding: '8px 12px',
            borderRadius: '4px',
            border: 'none',
            cursor: 'pointer',
            backgroundColor: isDarkTheme ? '#333' : '#f0f0f0',
            color: isDarkTheme ? '#fff' : '#333',
            boxShadow: '0 2px 10px rgba(0, 0, 0, 0.2)',
            transition: 'all 0.3s ease'
        });

        button.addEventListener('click', toggleTheme);
        document.body.appendChild(button);
    }

    // 设置观察者
    function setupObserver() {
        if (observer) observer.disconnect();

        observer = new MutationObserver(function() {
            const body = document.body;
            if (body && (
                (body.getAttribute('data-theme') === 'dark') !== isDarkTheme ||
                body.classList.contains('dark') !== isDarkTheme
            )) {
                applyTheme();
            }

            if (!document.getElementById('juejin-theme-toggle')) {
                createThemeToggleButton();
            }
        });

        observer.observe(document.body, {
            attributes: true,
            attributeFilter: ['data-theme', 'class'],
            childList: true,
            subtree: true
        });
    }

    // 初始化
    function init() {
        if (!document.body) {
            setTimeout(init, 100);
            return;
        }

        applyTheme();
        createThemeToggleButton();
        setupObserver();

        setInterval(() => {
            if (!document.getElementById('juejin-theme-toggle')) {
                createThemeToggleButton();
            }
        }, 2000);
    }

    // 启动
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();