ESJ Zone:自动展开折叠章节

自动展开已折叠的最后阅读章节(或展开所有章节)。

  1. // ==UserScript==
  2. // @name ESJ Zone: Auto Expand Collapsed Chapters
  3. // @name:zh-TW ESJ Zone:自動展開摺疊章節
  4. // @name:zh-CN ESJ Zone:自动展开折叠章节
  5. // @description Expand the collapsed, last read chapters (alternatively, all chapters) automatically.
  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.1.4
  12. // @license MIT
  13. // @match https://www.esjzone.cc/detail/*.html
  14. // @match https://www.esjzone.me/detail/*.html
  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. // @supportURL https://greasyfork.org/scripts/487306/feedback
  22. // ==/UserScript==
  23.  
  24. const LL = (function()
  25. {
  26. const translations =
  27. {
  28. "en": {
  29. COMMAND: {
  30. SETTING: "Change Expand Setting",
  31. },
  32. SETTING: {
  33. TITLE: "Expand Setting",
  34. EXPAND_BEHAVIOUR: "Expand Behaviour",
  35. EXPAND_ALL: "Expand All Chapters",
  36. LAST_READ: "Last Read Chapters Only",
  37. CANCEL: "Cancel",
  38. SAVE: "Save",
  39. },
  40. },
  41. "zh-TW": {
  42. COMMAND: {
  43. SETTING: "更改展開設定",
  44. },
  45. SETTING: {
  46. TITLE: "展開設定",
  47. EXPAND_BEHAVIOUR: "展開行為",
  48. EXPAND_ALL: "展開所有章節",
  49. LAST_READ: "展開最後閱讀章節",
  50. CANCEL: "取消",
  51. SAVE: "儲存",
  52. },
  53. },
  54. "zh-CN": {
  55. COMMAND: {
  56. SETTING: "更改展開设定",
  57. },
  58. SETTING: {
  59. TITLE: "展開设定",
  60. EXPAND_BEHAVIOUR: "展开行为",
  61. EXPAND_ALL: "展开所有章节",
  62. LAST_READ: "展开最后阅读章节",
  63. CANCEL: "取消",
  64. SAVE: "储存",
  65. },
  66. },
  67. };
  68.  
  69. let locale = "en";
  70. for (let _locale of navigator.languages.map((language) => new Intl.Locale(language)))
  71. {
  72. if (_locale.language === "zh")
  73. {
  74. _locale = new Intl.Locale("zh", { region: _locale.maximize().region });
  75. }
  76. ;
  77. if (_locale.baseName in translations)
  78. {
  79. locale = _locale.baseName;
  80. break;
  81. }
  82. }
  83.  
  84. return i18nObject(locale, translations[locale]);
  85. })();
  86.  
  87. const EVENT_KEY = uuid();
  88.  
  89. const ExpandOptions =
  90. {
  91. EXPAND_ALL: "expand_all",
  92. LAST_READ: "last_read",
  93. };
  94.  
  95. GM.registerMenuCommand(LL.COMMAND.SETTING(), showExpandSetting);
  96.  
  97. (async () =>
  98. {
  99. const setting = await getExpandSetting();
  100.  
  101. const elements = document.querySelectorAll("#chapterList details");
  102. for (const element of elements)
  103. {
  104. if ((setting === ExpandOptions.EXPAND_ALL) || (element.querySelector("p.active") !== null))
  105. {
  106. element.open = true;
  107. }
  108. }
  109. })();
  110.  
  111. function getExpandSetting()
  112. {
  113. return GM.getValue("expand", ExpandOptions.LAST_READ);
  114. }
  115.  
  116. let settingOpened = false;
  117.  
  118. function showExpandSetting()
  119. {
  120. if (settingOpened) { return Promise.resolve(new Error("Setting was already opened.")); }
  121.  
  122. return new Promise(async (resolve) =>
  123. {
  124. const setting = await getExpandSetting();
  125.  
  126. const form = document.createElement("form");
  127. form.id = uuid();
  128. form.classList.add("modal", "fade");
  129. form.addEventListener("submit", async (event) =>
  130. {
  131. event.preventDefault();
  132.  
  133. const settings = new FormData(form);
  134. await GM.setValue("expand", settings.get("expand"));
  135.  
  136. window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:hideModal`, { detail: `#${form.id}` }));
  137. });
  138. form.addEventListener("hide.bs.modal", () => resolve());
  139. form.addEventListener("hidden.bs.modal", () =>
  140. {
  141. form.remove();
  142. settingOpened = false;
  143. });
  144.  
  145. const modalDialog = document.createElement("div");
  146. modalDialog.classList.add("modal-dialog");
  147.  
  148. const modalContent = document.createElement("div");
  149. modalContent.classList.add("modal-content");
  150.  
  151. const modalHeader = document.createElement("div");
  152. modalHeader.classList.add("modal-header");
  153.  
  154. const modalTitle = document.createElement("h4");
  155. modalTitle.classList.add("modal-title");
  156. modalTitle.innerText = LL.SETTING.TITLE();
  157.  
  158. const closeButton = document.createElement("button");
  159. closeButton.classList.add("close");
  160. closeButton.type = "button";
  161. closeButton.dataset.dismiss = "modal";
  162. closeButton.innerHTML = `<span aria-hidden="true">×</span>`;
  163.  
  164. const modalBody = document.createElement("div");
  165. modalBody.classList.add("modal-body");
  166.  
  167. const expandFormGroup = document.createElement("div");
  168. expandFormGroup.classList.add("form-group");
  169.  
  170. const expandSelect = document.createElement("select");
  171. expandSelect.id = "expand-select";
  172. expandSelect.classList.add("form-control");
  173. expandSelect.name = "expand";
  174.  
  175. const expandAllOption = document.createElement("option");
  176. expandAllOption.value = ExpandOptions.EXPAND_ALL;
  177. expandAllOption.selected = (setting === ExpandOptions.EXPAND_ALL);
  178. expandAllOption.innerText = LL.SETTING.EXPAND_ALL();
  179.  
  180. const lastReadOption = document.createElement("option");
  181. lastReadOption.value = ExpandOptions.LAST_READ;
  182. lastReadOption.selected = (setting === ExpandOptions.LAST_READ);
  183. lastReadOption.innerText = LL.SETTING.LAST_READ();
  184.  
  185. const modalFooter = document.createElement("div");
  186. modalFooter.classList.add("modal-footer");
  187.  
  188. const cancelButton = document.createElement("button");
  189. cancelButton.classList.add("btn", "btn-default");
  190. cancelButton.type = "button";
  191. cancelButton.dataset.dismiss = "modal";
  192. cancelButton.innerText = LL.SETTING.CANCEL();
  193.  
  194. const saveButton = document.createElement("button");
  195. saveButton.classList.add("btn", "btn-primary");
  196. cancelButton.type = "submit";
  197. saveButton.innerText = LL.SETTING.SAVE();
  198.  
  199. modalHeader.append(modalTitle, closeButton);
  200. expandSelect.append(expandAllOption, lastReadOption);
  201. expandFormGroup.append(expandSelect);
  202. modalBody.append(expandFormGroup);
  203. modalFooter.append(cancelButton, saveButton);
  204. modalContent.append(modalHeader, modalBody, modalFooter);
  205. modalDialog.append(modalContent);
  206. form.append(modalDialog);
  207. document.body.append(form);
  208.  
  209. window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:showModal`, { detail: `#${form.id}` }));
  210. settingOpened = true;
  211. });
  212. }
  213.  
  214. const PageScript = ({ EVENT_KEY }) =>
  215. {
  216. window.addEventListener(`${EVENT_KEY}:showModal`, ({ detail: selector }) =>
  217. {
  218. $(selector)
  219. .on("hide.bs.modal", (event) =>
  220. {
  221. event.target.dispatchEvent(new CustomEvent("hide.bs.modal", { ...event }));
  222. })
  223. .on("hidden.bs.modal", (event) =>
  224. {
  225. event.target.dispatchEvent(new CustomEvent("hidden.bs.modal", { ...event }));
  226. })
  227. .modal("show");
  228. });
  229.  
  230. window.addEventListener(`${EVENT_KEY}:hideModal`, ({ detail: selector }) =>
  231. {
  232. $(selector)
  233. .modal("hide");
  234. });
  235. };
  236.  
  237. const scriptWrapper = document.createElement("div");
  238. scriptWrapper.style.display = "none";
  239. const shadowRoot = scriptWrapper.attachShadow({ mode: "closed" });
  240. const script = document.createElement("script");
  241. script.textContent = `(${PageScript})(${JSON.stringify({ EVENT_KEY })}); //# sourceURL=userscript://page/${encodeURI(GM.info.script.name)}.js`;
  242. shadowRoot.append(script);
  243. (document.body ?? document.head ?? document.documentElement).append(scriptWrapper);