Auto Dark Mode for Penana Mobile

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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.1.0
// @license            MIT
// @match              https://m.penana.com/*
// @match              https://android.penana.com/*
// @run-at             document-end
// @grant              GM.getValue
// @grant              GM.setValue
// @grant              GM.registerMenuCommand
// @require            https://unpkg.com/[email protected]/dist/i18n.object.min.js
// @require            https://unpkg.com/[email protected]/uuid-random.min.js
// @require            https://update.greasyfork.org/scripts/494512/1373878/gm-inject.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 EVENT_KEY = uuid();

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

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

window.addEventListener(`${EVENT_KEY}:ready`, () =>
{
    query.addEventListener("change", updateTheme);
    updateTheme(query);
});

GM.injectPageScript(
    ({ EVENT_KEY }) =>
    {
        const interval = setInterval(() =>
        {
            if ("setTheme" in window)
            {
                clearInterval(interval);
                window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:ready`));
            }
        }, 100);

        window.addEventListener(`${EVENT_KEY}:setTheme`, ({ detail: theme }) =>
        {
            setTheme(theme);
            setCookie("darktheme", theme, 9999);
        });

        window.addEventListener(`${EVENT_KEY}:hidePopup`, ({ detail: selector }) =>
        {
            $(selector).hide();
        });
    },
    { EVENT_KEY });

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

function getExpectedTheme(isDarkMode)
{
    return isDarkMode ? Promise.resolve("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)
    {
        window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:setTheme`, { detail: expectedTheme }));
    }
}

function showLightThemeSetting()
{
    return new Promise(async (resolve) =>
    {
        const popupWrapper = document.createElement("div");
        popupWrapper.id = uuid();
        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"));

            window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:hidePopup`, { detail: `#${popupWrapper.id}` }));
        });

        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"] });
    });
}