Greasy Fork

Auto Dark Mode for Penana Mobile

Automatically switch the theme between light and dark, based on the browser’s color scheme preference.

当前为 2024-02-22 提交的版本,查看 最新版本

// ==UserScript==
// @name               Auto Dark Mode for Penana Mobile
// @name:zh-TW         Penana 手機版自動黑暗模式
// @description        Automatically switch the theme between light and dark, based on the browser’s color scheme preference.
// @description:zh-TW  根據瀏覽器的佈景主題設定,自動從明亮和黑暗模式間切換。
// @icon               https://wsrv.nl/?url=https://static2.penana.com/img/mobile/app-icon/ios/128.png
// @author             Jason Kwok
// @namespace          https://jasonhk.dev/
// @version            1.0.0
// @license            MIT
// @match              https://m.penana.com/*
// @match              https://android.penana.com/*
// @run-at             document-idle
// @inject-into        page
// @grant              GM.getValue
// @grant              GM.setValue
// @grant              GM.registerMenuCommand
// @require            https://unpkg.com/[email protected]/dist/i18n.object.min.js
// @supportURL         https://greasyfork.org/scripts/488029/feedback
// ==/UserScript==

const LL = (function()
{
    const translations =
    {
        "en": {
            COMMAND: {
                SETTING: "Change Light Theme Setting",
            },
            SETTING: {
                SUBTITLE: "Light Theme Setting",
                LIGHT: "Light",
                WHITE: "White",
                BROWN: "Brown",
                SAVE: "Save",
            },
        },
        "zh-TW": {
            COMMAND: {
                SETTING: "更改明亮主題設定",
            },
            SETTING: {
                SUBTITLE: "明亮主題設定",
                LIGHT: "明亮",
                WHITE: "白色",
                BROWN: "褐色",
                SAVE: "儲存",
            },
        },
    };

    let locale = "en";
    for (let _locale of navigator.languages.map((language) => new Intl.Locale(language)))
    {
        if (_locale.language === "zh")
        {
            _locale = new Intl.Locale("zh", { region: _locale.maximize().region });
        }
;
        if (_locale.baseName in translations)
        {
            locale = _locale.baseName;
            break;
        }
    }

    return i18nObject(locale, translations[locale]);
})();

const isGreasemonkey = (GM.info.scriptHandler === "Greasemonkey");

const interval = setInterval(() =>
{
    if (!window.eval(`"setTheme" in window`)) { return; }
    clearInterval(interval);

    if (isGreasemonkey)
    {
        window.getCookie = window.eval("getCookie");
        window.setCookie = window.eval("setCookie");
        window.setTheme = window.eval("setTheme");
    }

    const query = matchMedia("(prefers-color-scheme: dark)");

    query.addEventListener("change", updateTheme);
    updateTheme(query);

    GM.registerMenuCommand(LL.COMMAND.SETTING(), async () =>
    {
        await showLightThemeSetting();
        updateTheme(query);
    });

    function getLightTheme()
    {
        return GM.getValue("light_theme", "lighttheme");
    }

    async function getExpectedTheme(isDarkMode)
    {
        return isDarkMode ? "darktheme" : getLightTheme();
    }

    function getCurrentTheme()
    {
        switch (true)
        {
            case document.body.classList.contains("darktheme"):
                return "darktheme";
            case document.body.classList.contains("whitetheme"):
                return "whitetheme";
            case document.body.classList.contains("browntheme"):
                return "browntheme";
            default:
                return "lighttheme";
        }
    }

    async function updateTheme({ matches: isDarkMode })
    {
        const expectedTheme = await getExpectedTheme(isDarkMode);
        if (getCurrentTheme() !== expectedTheme)
        {
            setTheme(expectedTheme);
            setCookie("darktheme", expectedTheme, 9999);
        }
    }

    function showLightThemeSetting()
    {
        return new Promise(async (resolve) =>
        {
            const popupWrapper = document.createElement("div");
            popupWrapper.classList.add("popupwrap", "narrowpopup", "scroll");

            const popupMask = document.createElement("div");
            popupMask.classList.add("mask", "pseudoclose");

            const popup = document.createElement("div");
            popup.classList.add("popup", "logregpopup", "popupbox", "invitebox", "scroll");

            const closeButton = document.createElement("a");
            closeButton.classList.add("close", "ignorelink", "newclose", "nonsticky", "floatright");
            closeButton.innerText = "×";

            const contents = document.createElement("div");
            contents.classList.add("padding10px");

            const subtitle = document.createElement("span");
            subtitle.classList.add("popup_subtitle", "font15px");
            subtitle.innerText = LL.SETTING.SUBTITLE();

            const form = document.createElement("form");
            form.style = "margin-top: 20px;";
            form.addEventListener("submit", async (event) =>
            {
                event.preventDefault();

                const options = new FormData(form);
                await GM.setValue("light_theme", options.get("light_theme"));

                popupWrapper.style = "display: none;";
            });

            const lightThemeLabelWrapper = document.createElement("p");

            const lightThemeLabel = document.createElement("label");

            const lightThemeRadio = document.createElement("input");
            lightThemeRadio.classList.add("with-gap");
            lightThemeRadio.name = "light_theme";
            lightThemeRadio.type = "radio";
            lightThemeRadio.required = true;
            lightThemeRadio.value = "lighttheme";

            const lightThemeText = document.createElement("span");
            lightThemeText.innerText = LL.SETTING.LIGHT();

            const whiteThemeLabelWrapper = document.createElement("p");

            const whiteThemeLabel = document.createElement("label");

            const whiteThemeRadio = document.createElement("input");
            whiteThemeRadio.classList.add("with-gap");
            whiteThemeRadio.name = "light_theme";
            whiteThemeRadio.type = "radio";
            whiteThemeRadio.required = true;
            whiteThemeRadio.value = "whitetheme";

            const whiteThemeText = document.createElement("span");
            whiteThemeText.innerText = LL.SETTING.WHITE();

            const brownThemeLabelWrapper = document.createElement("p");

            const brownThemeLabel = document.createElement("label");

            const brownThemeRadio = document.createElement("input");
            brownThemeRadio.classList.add("with-gap");
            brownThemeRadio.name = "light_theme";
            brownThemeRadio.type = "radio";
            brownThemeRadio.required = true;
            brownThemeRadio.value = "browntheme";

            const brownThemeText = document.createElement("span");
            brownThemeText.innerText = LL.SETTING.BROWN();

            const saveButton = document.createElement("button");
            saveButton.classList.add("btn", "waves-effect", "waves-light");
            saveButton.innerHTML = `${LL.SETTING.SAVE()} <i class="material-icons right">save</i>`;

            lightThemeLabel.append(lightThemeRadio, lightThemeText);
            lightThemeLabelWrapper.append(lightThemeLabel);
            whiteThemeLabel.append(whiteThemeRadio, whiteThemeText);
            whiteThemeLabelWrapper.append(whiteThemeLabel);
            brownThemeLabel.append(brownThemeRadio, brownThemeText);
            brownThemeLabelWrapper.append(brownThemeLabel);
            form.append(lightThemeLabelWrapper, whiteThemeLabelWrapper, brownThemeLabelWrapper, saveButton);
            contents.append(subtitle, form);
            popup.append(closeButton, contents);
            popupWrapper.append(popupMask, popup);
            document.body.append(popupWrapper);

            const theme = await getLightTheme();
            switch (theme)
            {
                case "lighttheme":
                    lightThemeRadio.checked = true;
                    break;
                case "whitetheme":
                    whiteThemeRadio.checked = true;
                    break;
                case "browntheme":
                    brownThemeRadio.checked = true;
                    break;
            }

            const observer = new MutationObserver((records) =>
            {
                observer.disconnect();

                resolve();
                popupWrapper.remove();
            });

            observer.observe(popupWrapper, { attributes: true, attributeFilter: ["style"] });
        });
    }
}, 100);