ESJ Zone 自动黑暗模式

根据浏览器的布景主题设定,自动从明亮和黑暗模式间切换。

  1. // ==UserScript==
  2. // @name Auto Dark Mode for ESJ Zone
  3. // @name:zh-TW ESJ Zone 自動黑暗模式
  4. // @name:zh-CN ESJ Zone 自动黑暗模式
  5. // @description Automatically switch the theme between light and dark, based on the browser’s color scheme preference.
  6. // @description:zh-TW 根據瀏覽器的佈景主題設定,自動從明亮和黑暗模式間切換。
  7. // @description:zh-CN 根据浏览器的布景主题设定,自动从明亮和黑暗模式间切换。
  8. // @icon https://icons.duckduckgo.com/ip3/www.esjzone.cc.ico
  9. // @author Jason Kwok
  10. // @namespace https://jasonhk.dev/
  11. // @version 1.0.9
  12. // @license MIT
  13. // @match https://www.esjzone.cc/*
  14. // @match https://www.esjzone.me/*
  15. // @run-at document-end
  16. // @grant GM.getValue
  17. // @grant GM.setValue
  18. // @grant GM.registerMenuCommand
  19. // @require https://unpkg.com/typesafe-i18n@5.26.2/dist/i18n.object.min.js
  20. // @require https://unpkg.com/uuid-random@1.3.2/uuid-random.min.js
  21. // @require https://update.greasyfork.org/scripts/494512/1373878/gm-inject.js
  22. // @supportURL https://greasyfork.org/scripts/488026/feedback
  23. // ==/UserScript==
  24.  
  25. const LL = (function()
  26. {
  27. const translations =
  28. {
  29. "en": {
  30. COMMAND: {
  31. SETTINGS: "Change Theme Settings",
  32. },
  33. SETTINGS: {
  34. TITLE: "Theme Settings",
  35. LIGHT_THEME: "Light Theme",
  36. WHITE: "White",
  37. BLUE: "Blue",
  38. GREEN: "Green",
  39. GRAY: "Pink",
  40. LIGHT_GRAY: "Light Gray",
  41. DARK_THEME: "Dark Theme",
  42. BLACK: "Black",
  43. DARK_GRAY: "Dark Gray",
  44. CANCEL: "Cancel",
  45. SAVE: "Save",
  46. },
  47. },
  48. "zh-TW": {
  49. COMMAND: {
  50. SETTINGS: "更改主題設定",
  51. },
  52. SETTINGS: {
  53. TITLE: "主題設定",
  54. LIGHT_THEME: "明亮主題",
  55. WHITE: "白色",
  56. BLUE: "藍色",
  57. GREEN: "綠色",
  58. PINK: "粉紅色",
  59. LIGHT_GRAY: "淺灰色",
  60. DARK_THEME: "黑暗主題",
  61. BLACK: "黑色",
  62. DARK_GRAY: "深灰色",
  63. CANCEL: "取消",
  64. SAVE: "儲存",
  65. },
  66. },
  67. "zh-CN": {
  68. COMMAND: {
  69. SETTINGS: "更改主题设定",
  70. },
  71. SETTINGS: {
  72. TITLE: "主题设定",
  73. LIGHT_THEME: "明亮主题",
  74. WHITE: "白色",
  75. BLUE: "蓝色",
  76. GREEN: "绿色",
  77. PINK: "粉红色",
  78. LIGHT_GRAY: "浅灰色",
  79. DARK_THEME: "黑暗主题",
  80. BLACK: "黑色",
  81. DARK_GRAY: "深灰色",
  82. CANCEL: "取消",
  83. SAVE: "储存",
  84. },
  85. },
  86. };
  87.  
  88. let locale = "en";
  89. for (let _locale of navigator.languages.map((language) => new Intl.Locale(language)))
  90. {
  91. if (_locale.language === "zh")
  92. {
  93. _locale = new Intl.Locale("zh", { region: _locale.maximize().region });
  94. }
  95. ;
  96. if (_locale.baseName in translations)
  97. {
  98. locale = _locale.baseName;
  99. break;
  100. }
  101. }
  102.  
  103. return i18nObject(locale, translations[locale]);
  104. })();
  105.  
  106. const EVENT_KEY = uuid();
  107.  
  108. const query = matchMedia("(prefers-color-scheme: dark)");
  109.  
  110. GM.registerMenuCommand(LL.COMMAND.SETTINGS(), async () =>
  111. {
  112. await showThemeSettings();
  113. updateTheme(query);
  114. });
  115.  
  116. query.addEventListener("change", updateTheme);
  117. updateTheme(query);
  118.  
  119. GM.injectPageScript(
  120. ({ EVENT_KEY }) =>
  121. {
  122. window.addEventListener(`${EVENT_KEY}:showModal`, ({ detail: selector }) =>
  123. {
  124. $(selector)
  125. .on("hide.bs.modal", (event) =>
  126. {
  127. event.target.dispatchEvent(new CustomEvent("hide.bs.modal", { ...event }));
  128. })
  129. .on("hidden.bs.modal", (event) =>
  130. {
  131. event.target.dispatchEvent(new CustomEvent("hidden.bs.modal", { ...event }));
  132. })
  133. .modal("show");
  134. });
  135.  
  136. window.addEventListener(`${EVENT_KEY}:hideModal`, ({ detail: selector }) =>
  137. {
  138. $(selector)
  139. .modal("hide");
  140. });
  141. },
  142. { EVENT_KEY });
  143.  
  144. function getLightTheme()
  145. {
  146. return GM.getValue("light_theme", "mycolor-0");
  147. }
  148.  
  149. function getDarkTheme()
  150. {
  151. return GM.getValue("dark_theme", "mycolor-1");
  152. }
  153.  
  154. function setThemeSettings(lightTheme, darkTheme)
  155. {
  156. return Promise.all([GM.setValue("light_theme", lightTheme), GM.setValue("dark_theme", darkTheme)]);
  157. }
  158.  
  159. function getExpectedTheme(isDarkMode)
  160. {
  161. return isDarkMode ? getDarkTheme() : getLightTheme();
  162. }
  163.  
  164. function getCurrentTheme()
  165. {
  166. return document.querySelector(".customizer-color-switch [id^=mycolor-].active")?.id ?? "mycolor-0";
  167. }
  168.  
  169. function setTheme(name)
  170. {
  171. document.querySelector(`.customizer-color-switch #${name}`).click();
  172. }
  173.  
  174. async function updateTheme({ matches: isDarkMode })
  175. {
  176. const expectedTheme = await getExpectedTheme(isDarkMode);
  177. if (getCurrentTheme() !== expectedTheme)
  178. {
  179. setTheme(expectedTheme);
  180. }
  181. }
  182.  
  183. let settingsOpened = false;
  184.  
  185. function showThemeSettings()
  186. {
  187. if (settingsOpened) { return Promise.reject(new Error("Settings was already opened.")); }
  188.  
  189. return new Promise(async (resolve) =>
  190. {
  191. const [lightTheme, darkTheme] = await Promise.all([getLightTheme(), getDarkTheme()]);
  192.  
  193. const form = document.createElement("form");
  194. form.id = uuid();
  195. form.classList.add("modal", "fade");
  196. form.addEventListener("submit", async (event) =>
  197. {
  198. event.preventDefault();
  199.  
  200. const settings = new FormData(form);
  201. await setThemeSettings(settings.get("light_theme"), settings.get("dark_theme"));
  202.  
  203. window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:hideModal`, { detail: `#${form.id}` }));
  204. });
  205. form.addEventListener("hide.bs.modal", () => resolve());
  206. form.addEventListener("hidden.bs.modal", () =>
  207. {
  208. form.remove();
  209. settingsOpened = false;
  210. });
  211.  
  212. const modalDialog = document.createElement("div");
  213. modalDialog.classList.add("modal-dialog");
  214.  
  215. const modalContent = document.createElement("div");
  216. modalContent.classList.add("modal-content");
  217.  
  218. const modalHeader = document.createElement("div");
  219. modalHeader.classList.add("modal-header");
  220.  
  221. const modalTitle = document.createElement("h4");
  222. modalTitle.classList.add("modal-title");
  223. modalTitle.innerText = LL.SETTINGS.TITLE();
  224.  
  225. const closeButton = document.createElement("button");
  226. closeButton.classList.add("close");
  227. closeButton.type = "button";
  228. closeButton.dataset.dismiss = "modal";
  229. closeButton.innerHTML = `<span aria-hidden="true">×</span>`;
  230.  
  231. const modalBody = document.createElement("div");
  232. modalBody.classList.add("modal-body");
  233.  
  234. const lightThemeFormGroup = document.createElement("div");
  235. lightThemeFormGroup.classList.add("form-group");
  236.  
  237. const lightThemeLabel = document.createElement("label");
  238. lightThemeLabel.htmlFor = "light-theme-select";
  239. lightThemeLabel.innerText = LL.SETTINGS.LIGHT_THEME();
  240.  
  241. const lightThemeSelect = document.createElement("select");
  242. lightThemeSelect.id = "light-theme-select";
  243. lightThemeSelect.classList.add("form-control");
  244. lightThemeSelect.name = "light_theme";
  245.  
  246. const whiteThemeOption = document.createElement("option");
  247. whiteThemeOption.value = "mycolor-0";
  248. whiteThemeOption.selected = (lightTheme === "mycolor-0");
  249. whiteThemeOption.innerText = LL.SETTINGS.WHITE();
  250.  
  251. const blueThemeOption = document.createElement("option");
  252. blueThemeOption.value = "mycolor-2";
  253. blueThemeOption.selected = (lightTheme === "mycolor-2");
  254. blueThemeOption.innerText = LL.SETTINGS.BLUE();
  255.  
  256. const greenThemeOption = document.createElement("option");
  257. greenThemeOption.value = "mycolor-3";
  258. greenThemeOption.selected = (lightTheme === "mycolor-3");
  259. greenThemeOption.innerText = LL.SETTINGS.GREEN();
  260.  
  261. const pinkThemeOption = document.createElement("option");
  262. pinkThemeOption.value = "mycolor-4";
  263. pinkThemeOption.selected = (lightTheme === "mycolor-4");
  264. pinkThemeOption.innerText = LL.SETTINGS.PINK();
  265.  
  266. const lightGrayThemeOption = document.createElement("option");
  267. lightGrayThemeOption.value = "mycolor-5";
  268. lightGrayThemeOption.selected = (lightTheme === "mycolor-5");
  269. lightGrayThemeOption.innerText = LL.SETTINGS.LIGHT_GRAY();
  270.  
  271. const darkThemeFormGroup = document.createElement("div");
  272. darkThemeFormGroup.classList.add("form-group");
  273.  
  274. const darkThemeLabel = document.createElement("label");
  275. darkThemeLabel.htmlFor = "dark-theme-select";
  276. darkThemeLabel.innerText = LL.SETTINGS.DARK_THEME();
  277.  
  278. const darkThemeSelect = document.createElement("select");
  279. darkThemeSelect.id = "dark-theme-select";
  280. darkThemeSelect.classList.add("form-control");
  281. darkThemeSelect.name = "dark_theme";
  282.  
  283. const blackThemeOption = document.createElement("option");
  284. blackThemeOption.value = "mycolor-1";
  285. blackThemeOption.selected = (darkTheme === "mycolor-1");
  286. blackThemeOption.innerText = LL.SETTINGS.BLACK();
  287.  
  288. const darkGrayThemeOption = document.createElement("option");
  289. darkGrayThemeOption.value = "mycolor-6";
  290. darkGrayThemeOption.selected = (darkTheme === "mycolor-6");
  291. darkGrayThemeOption.innerText = LL.SETTINGS.DARK_GRAY();
  292.  
  293. const modalFooter = document.createElement("div");
  294. modalFooter.classList.add("modal-footer");
  295.  
  296. const cancelButton = document.createElement("button");
  297. cancelButton.classList.add("btn", "btn-default");
  298. cancelButton.type = "button";
  299. cancelButton.dataset.dismiss = "modal";
  300. cancelButton.innerText = LL.SETTINGS.CANCEL();
  301.  
  302. const saveButton = document.createElement("button");
  303. saveButton.classList.add("btn", "btn-primary");
  304. cancelButton.type = "submit";
  305. saveButton.innerText = LL.SETTINGS.SAVE();
  306.  
  307. modalHeader.append(modalTitle, closeButton);
  308. lightThemeSelect.append(whiteThemeOption, blueThemeOption, greenThemeOption, pinkThemeOption, lightGrayThemeOption);
  309. lightThemeFormGroup.append(lightThemeLabel, lightThemeSelect);
  310. darkThemeSelect.append(blackThemeOption, darkGrayThemeOption);
  311. darkThemeFormGroup.append(darkThemeLabel, darkThemeSelect);
  312. modalBody.append(lightThemeFormGroup, darkThemeFormGroup);
  313. modalFooter.append(cancelButton, saveButton);
  314. modalContent.append(modalHeader, modalBody, modalFooter);
  315. modalDialog.append(modalContent);
  316. form.append(modalDialog);
  317. document.body.append(form);
  318.  
  319. window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:showModal`, { detail: `#${form.id}` }));
  320. settingsOpened = true;
  321. });
  322. }