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