// ==UserScript==
// @name ESJ Zone: Auto Expand Collapsed Chapters
// @name:zh-TW ESJ Zone:自動展開摺疊章節
// @name:zh-CN ESJ Zone:自动展开折叠章节
// @description Expand the collapsed, last read chapters (alternatively, all chapters) automatically.
// @description:zh-TW 自動展開已折疊的最後閱讀章節(或展開所有章節)。
// @description:zh-CN 自动展开已折叠的最后阅读章节(或展开所有章节)。
// @icon https://icons.duckduckgo.com/ip3/www.esjzone.cc.ico
// @author Jason Kwok
// @namespace https://jasonhk.dev/
// @version 1.1.4
// @license MIT
// @match https://www.esjzone.cc/detail/*.html
// @match https://www.esjzone.me/detail/*.html
// @run-at document-end
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.registerMenuCommand
// @require https://unpkg.com/typesafe-i18n@5.26.2/dist/i18n.object.min.js
// @require https://unpkg.com/uuid-random@1.3.2/uuid-random.min.js
// @supportURL https://greasyfork.org/scripts/487306/feedback
// ==/UserScript==
const LL = (function()
{
const translations =
{
"en": {
COMMAND: {
SETTING: "Change Expand Setting",
},
SETTING: {
TITLE: "Expand Setting",
EXPAND_BEHAVIOUR: "Expand Behaviour",
EXPAND_ALL: "Expand All Chapters",
LAST_READ: "Last Read Chapters Only",
CANCEL: "Cancel",
SAVE: "Save",
},
},
"zh-TW": {
COMMAND: {
SETTING: "更改展開設定",
},
SETTING: {
TITLE: "展開設定",
EXPAND_BEHAVIOUR: "展開行為",
EXPAND_ALL: "展開所有章節",
LAST_READ: "展開最後閱讀章節",
CANCEL: "取消",
SAVE: "儲存",
},
},
"zh-CN": {
COMMAND: {
SETTING: "更改展開设定",
},
SETTING: {
TITLE: "展開设定",
EXPAND_BEHAVIOUR: "展开行为",
EXPAND_ALL: "展开所有章节",
LAST_READ: "展开最后阅读章节",
CANCEL: "取消",
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 ExpandOptions =
{
EXPAND_ALL: "expand_all",
LAST_READ: "last_read",
};
GM.registerMenuCommand(LL.COMMAND.SETTING(), showExpandSetting);
(async () =>
{
const setting = await getExpandSetting();
const elements = document.querySelectorAll("#chapterList details");
for (const element of elements)
{
if ((setting === ExpandOptions.EXPAND_ALL) || (element.querySelector("p.active") !== null))
{
element.open = true;
}
}
})();
function getExpandSetting()
{
return GM.getValue("expand", ExpandOptions.LAST_READ);
}
let settingOpened = false;
function showExpandSetting()
{
if (settingOpened) { return Promise.resolve(new Error("Setting was already opened.")); }
return new Promise(async (resolve) =>
{
const setting = await getExpandSetting();
const form = document.createElement("form");
form.id = uuid();
form.classList.add("modal", "fade");
form.addEventListener("submit", async (event) =>
{
event.preventDefault();
const settings = new FormData(form);
await GM.setValue("expand", settings.get("expand"));
window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:hideModal`, { detail: `#${form.id}` }));
});
form.addEventListener("hide.bs.modal", () => resolve());
form.addEventListener("hidden.bs.modal", () =>
{
form.remove();
settingOpened = false;
});
const modalDialog = document.createElement("div");
modalDialog.classList.add("modal-dialog");
const modalContent = document.createElement("div");
modalContent.classList.add("modal-content");
const modalHeader = document.createElement("div");
modalHeader.classList.add("modal-header");
const modalTitle = document.createElement("h4");
modalTitle.classList.add("modal-title");
modalTitle.innerText = LL.SETTING.TITLE();
const closeButton = document.createElement("button");
closeButton.classList.add("close");
closeButton.type = "button";
closeButton.dataset.dismiss = "modal";
closeButton.innerHTML = `<span aria-hidden="true">×</span>`;
const modalBody = document.createElement("div");
modalBody.classList.add("modal-body");
const expandFormGroup = document.createElement("div");
expandFormGroup.classList.add("form-group");
const expandSelect = document.createElement("select");
expandSelect.id = "expand-select";
expandSelect.classList.add("form-control");
expandSelect.name = "expand";
const expandAllOption = document.createElement("option");
expandAllOption.value = ExpandOptions.EXPAND_ALL;
expandAllOption.selected = (setting === ExpandOptions.EXPAND_ALL);
expandAllOption.innerText = LL.SETTING.EXPAND_ALL();
const lastReadOption = document.createElement("option");
lastReadOption.value = ExpandOptions.LAST_READ;
lastReadOption.selected = (setting === ExpandOptions.LAST_READ);
lastReadOption.innerText = LL.SETTING.LAST_READ();
const modalFooter = document.createElement("div");
modalFooter.classList.add("modal-footer");
const cancelButton = document.createElement("button");
cancelButton.classList.add("btn", "btn-default");
cancelButton.type = "button";
cancelButton.dataset.dismiss = "modal";
cancelButton.innerText = LL.SETTING.CANCEL();
const saveButton = document.createElement("button");
saveButton.classList.add("btn", "btn-primary");
cancelButton.type = "submit";
saveButton.innerText = LL.SETTING.SAVE();
modalHeader.append(modalTitle, closeButton);
expandSelect.append(expandAllOption, lastReadOption);
expandFormGroup.append(expandSelect);
modalBody.append(expandFormGroup);
modalFooter.append(cancelButton, saveButton);
modalContent.append(modalHeader, modalBody, modalFooter);
modalDialog.append(modalContent);
form.append(modalDialog);
document.body.append(form);
window.dispatchEvent(new CustomEvent(`${EVENT_KEY}:showModal`, { detail: `#${form.id}` }));
settingOpened = true;
});
}
const PageScript = ({ EVENT_KEY }) =>
{
window.addEventListener(`${EVENT_KEY}:showModal`, ({ detail: selector }) =>
{
$(selector)
.on("hide.bs.modal", (event) =>
{
event.target.dispatchEvent(new CustomEvent("hide.bs.modal", { ...event }));
})
.on("hidden.bs.modal", (event) =>
{
event.target.dispatchEvent(new CustomEvent("hidden.bs.modal", { ...event }));
})
.modal("show");
});
window.addEventListener(`${EVENT_KEY}:hideModal`, ({ detail: selector }) =>
{
$(selector)
.modal("hide");
});
};
const scriptWrapper = document.createElement("div");
scriptWrapper.style.display = "none";
const shadowRoot = scriptWrapper.attachShadow({ mode: "closed" });
const script = document.createElement("script");
script.textContent = `(${PageScript})(${JSON.stringify({ EVENT_KEY })}); //# sourceURL=userscript://page/${encodeURI(GM.info.script.name)}.js`;
shadowRoot.append(script);
(document.body ?? document.head ?? document.documentElement).append(scriptWrapper);