12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337 |
- import { compress, decompress, debounce, isScrollable } from "@sv443-network/userutils";
- import { defaultConfig, getFeatures, migrations, saveFeatures, setDefaultFeatures } from "../config";
- import { buildNumber, compressionFormat, host, mode, scriptInfo } from "../constants";
- import { featInfo, disableBeforeUnload } from "../features/index";
- import { error, getResourceUrl, info, log, resourceToHTMLString, warn, getLocale, hasKey, initTranslations, setLocale, t, parseMarkdown, getChangelogMd, compressionSupported } from "../utils";
- import { formatVersion } from "../config";
- import { emitSiteEvent, siteEvents } from "../siteEvents";
- import type { FeatureCategory, FeatureKey, FeatureConfig, HotkeyObj, FeatureInfo } from "../types";
- import "./menu_old.css";
- import { createHotkeyInput, createToggleInput } from "../components";
- import pkg from "../../package.json" assert { type: "json" };
- //#MARKER create menu elements
- let isCfgMenuAdded = false;
- export let isCfgMenuOpen = false;
- /** Threshold in pixels from the top of the options container that dictates for how long the scroll indicator is shown */
- const scrollIndicatorOffsetThreshold = 30;
- let scrollIndicatorEnabled = true;
- /** Locale at the point of initializing the config menu */
- let initLocale: string | undefined;
- /** Stringified config at the point of initializing the config menu */
- let initConfig: string | undefined;
- /**
- * Adds an element to open the BetterYTM menu
- * @deprecated to be replaced with new menu - see https://github.com/Sv443/BetterYTM/issues/23
- */
- async function addCfgMenu() {
- if(isCfgMenuAdded)
- return;
- isCfgMenuAdded = true;
- initLocale = getFeatures().locale;
- initConfig = JSON.stringify(getFeatures());
- const initLangReloadText = t("lang_changed_prompt_reload");
- //#SECTION backdrop & menu container
- const backgroundElem = document.createElement("div");
- backgroundElem.id = "bytm-cfg-menu-bg";
- backgroundElem.classList.add("bytm-menu-bg");
- backgroundElem.ariaLabel = backgroundElem.title = t("close_menu_tooltip");
- backgroundElem.style.visibility = "hidden";
- backgroundElem.style.display = "none";
- backgroundElem.addEventListener("click", (e) => {
- if(isCfgMenuOpen && (e.target as HTMLElement)?.id === "bytm-cfg-menu-bg")
- closeCfgMenu(e);
- });
- document.body.addEventListener("keydown", (e) => {
- if(isCfgMenuOpen && e.key === "Escape")
- closeCfgMenu(e);
- });
- const menuContainer = document.createElement("div");
- menuContainer.ariaLabel = menuContainer.title = ""; // prevent bg title from propagating downwards
- menuContainer.classList.add("bytm-menu");
- menuContainer.id = "bytm-cfg-menu";
- //#SECTION title bar
- const headerElem = document.createElement("div");
- headerElem.classList.add("bytm-menu-header");
- const titleCont = document.createElement("div");
- titleCont.className = "bytm-menu-titlecont";
- titleCont.role = "heading";
- titleCont.ariaLevel = "1";
- const titleElem = document.createElement("h2");
- titleElem.className = "bytm-menu-title";
- const titleTextElem = document.createElement("div");
- titleTextElem.textContent = t("config_menu_title", scriptInfo.name);
- titleElem.appendChild(titleTextElem);
- const linksCont = document.createElement("div");
- linksCont.id = "bytm-menu-linkscont";
- linksCont.role = "navigation";
- const addLink = (imgSrc: string, href: string, title: string) => {
- const anchorElem = document.createElement("a");
- anchorElem.className = "bytm-menu-link bytm-no-select";
- anchorElem.rel = "noopener noreferrer";
- anchorElem.href = href;
- anchorElem.target = "_blank";
- anchorElem.tabIndex = 0;
- anchorElem.role = "button";
- anchorElem.ariaLabel = anchorElem.title = title;
- const imgElem = document.createElement("img");
- imgElem.className = "bytm-menu-img";
- imgElem.src = imgSrc;
- imgElem.style.width = "32px";
- imgElem.style.height = "32px";
- anchorElem.appendChild(imgElem);
- linksCont.appendChild(anchorElem);
- };
- addLink(await getResourceUrl("img-discord"), "https://dc.sv443.net/", t("open_discord"));
- const links: [name: string, ...Parameters<typeof addLink>][] = [
- ["github", await getResourceUrl("img-github"), scriptInfo.namespace, t("open_github", scriptInfo.name)],
- ["greasyfork", await getResourceUrl("img-greasyfork"), pkg.hosts.greasyfork, t("open_greasyfork", scriptInfo.name)],
- ["openuserjs", await getResourceUrl("img-openuserjs"), pkg.hosts.openuserjs, t("open_openuserjs", scriptInfo.name)],
- ];
- const hostLink = links.find(([name]) => name === host);
- const otherLinks = links.filter(([name]) => name !== host);
- const reorderedLinks = hostLink ? [hostLink, ...otherLinks] : links;
- for(const [, ...args] of reorderedLinks)
- addLink(...args);
- const closeElem = document.createElement("img");
- closeElem.classList.add("bytm-menu-close");
- closeElem.role = "button";
- closeElem.tabIndex = 0;
- closeElem.src = await getResourceUrl("img-close");
- closeElem.ariaLabel = closeElem.title = t("close_menu_tooltip");
- closeElem.addEventListener("click", closeCfgMenu);
- closeElem.addEventListener("keydown", ({ key }) => key === "Enter" && closeCfgMenu());
- titleCont.appendChild(titleElem);
- titleCont.appendChild(linksCont);
- headerElem.appendChild(titleCont);
- headerElem.appendChild(closeElem);
- //#SECTION footer
- const footerCont = document.createElement("div");
- footerCont.className = "bytm-menu-footer-cont";
- const footerElemCont = document.createElement("div");
- const footerElem = document.createElement("div");
- footerElem.classList.add("bytm-menu-footer", "hidden");
- footerElem.textContent = t("reload_hint");
- const reloadElem = document.createElement("button");
- reloadElem.classList.add("bytm-btn");
- reloadElem.style.marginLeft = "10px";
- reloadElem.textContent = t("reload_now");
- reloadElem.ariaLabel = reloadElem.title = t("reload_tooltip");
- reloadElem.addEventListener("click", () => {
- closeCfgMenu();
- disableBeforeUnload();
- location.reload();
- });
- footerElem.appendChild(reloadElem);
- footerElemCont.appendChild(footerElem);
- const resetElem = document.createElement("button");
- resetElem.classList.add("bytm-btn");
- resetElem.ariaLabel = resetElem.title = t("reset_tooltip");
- resetElem.textContent = t("reset");
- resetElem.addEventListener("click", async () => {
- if(confirm(t("reset_confirm"))) {
- await setDefaultFeatures();
- closeCfgMenu();
- disableBeforeUnload();
- location.reload();
- }
- });
- const exportElem = document.createElement("button");
- exportElem.classList.add("bytm-btn");
- exportElem.ariaLabel = exportElem.title = t("export_tooltip");
- exportElem.textContent = t("export");
- exportElem.addEventListener("click", async () => {
- await openExportMenu();
- closeCfgMenu(undefined, false);
- });
- const importElem = document.createElement("button");
- importElem.classList.add("bytm-btn");
- importElem.ariaLabel = importElem.title = t("import_tooltip");
- importElem.textContent = t("import");
- importElem.addEventListener("click", async () => {
- await openImportMenu();
- closeCfgMenu(undefined, false);
- });
- const buttonsCont = document.createElement("div");
- buttonsCont.id = "bytm-menu-footer-buttons-cont";
- buttonsCont.appendChild(exportElem);
- buttonsCont.appendChild(importElem);
- buttonsCont.appendChild(resetElem);
- footerCont.appendChild(footerElemCont);
- footerCont.appendChild(buttonsCont);
- //#SECTION feature list
- const featuresCont = document.createElement("div");
- featuresCont.id = "bytm-menu-opts";
- /** Gets called whenever the feature config is changed */
- const confChanged = debounce(async (key: keyof typeof defaultConfig, initialVal: number | boolean | Record<string, unknown>, newVal: number | boolean | Record<string, unknown>) => {
- const fmt = (val: unknown) => typeof val === "object" ? JSON.stringify(val) : String(val);
- info(`Feature config changed at key '${key}', from value '${fmt(initialVal)}' to '${fmt(newVal)}'`);
- const featConf = JSON.parse(JSON.stringify(getFeatures()));
- featConf[key] = newVal as never;
- await saveFeatures(featConf);
- if(initConfig !== JSON.stringify(featConf))
- footerElem.classList.remove("hidden");
- else
- footerElem.classList.add("hidden");
- if(initLocale !== featConf.locale) {
- await initTranslations(featConf.locale);
- setLocale(featConf.locale);
- const newText = t("lang_changed_prompt_reload");
- const confirmText = newText !== initLangReloadText ? `${newText}\n\n────────────────────────────────\n\n${initLangReloadText}` : newText;
- if(confirm(confirmText)) {
- closeCfgMenu();
- disableBeforeUnload();
- location.reload();
- }
- }
- else if(getLocale() !== featConf.locale)
- setLocale(featConf.locale);
- });
- const featureCfg = getFeatures();
- const featureCfgWithCategories = Object.entries(featInfo)
- .reduce(
- (acc, [key, { category }]) => {
- if(!acc[category])
- acc[category] = {} as Record<FeatureKey, unknown>;
- acc[category][key as FeatureKey] = featureCfg[key as FeatureKey];
- return acc;
- },
- {} as Record<FeatureCategory, Record<FeatureKey, unknown>>,
- );
- const fmtVal = (v: unknown) => typeof v === "object" ? JSON.stringify(v) : String(v).trim();
- for(const category in featureCfgWithCategories) {
- const featObj = featureCfgWithCategories[category as FeatureCategory];
- const catHeaderElem = document.createElement("h3");
- catHeaderElem.classList.add("bytm-ftconf-category-header");
- catHeaderElem.role = "heading";
- catHeaderElem.ariaLevel = "2";
- catHeaderElem.textContent = `${t(`feature_category_${category}`)}:`;
- featuresCont.appendChild(catHeaderElem);
- for(const featKey in featObj) {
- const ftInfo = featInfo[featKey as keyof typeof featureCfg] as FeatureInfo[keyof typeof featureCfg];
- // @ts-ignore
- if(!ftInfo || ftInfo.hidden === true)
- continue;
- if(ftInfo.advanced && !featureCfg.advancedMode)
- continue;
- const { type, default: ftDefault } = ftInfo;
- // @ts-ignore
- const step = ftInfo?.step ?? undefined;
- const val = featureCfg[featKey as keyof typeof featureCfg];
- const initialVal = val ?? ftDefault ?? undefined;
- const ftConfElem = document.createElement("div");
- ftConfElem.classList.add("bytm-ftitem");
- {
- const featLeftSideElem = document.createElement("div");
- featLeftSideElem.classList.add("bytm-ftitem-leftside");
- if(getFeatures().advancedMode)
- featLeftSideElem.title = `${featKey}${ftInfo.advanced ? " (advanced)" : ""} - Default: ${fmtVal(ftDefault)}`;
- const textElem = document.createElement("span");
- textElem.textContent = t(`feature_desc_${featKey}`);
- let adornmentElem: undefined | HTMLElement;
- const adornContent = ftInfo.textAdornment?.();
- const adornContentAw = adornContent instanceof Promise ? await adornContent : adornContent;
- if((typeof adornContent === "string" || adornContent instanceof Promise) && typeof adornContentAw !== "undefined") {
- adornmentElem = document.createElement("span");
- adornmentElem.id = `bytm-ftitem-${featKey}-adornment`;
- adornmentElem.classList.add("bytm-ftitem-adornment");
- adornmentElem.innerHTML = adornContentAw;
- }
- let helpElem: undefined | HTMLDivElement;
- // @ts-ignore
- const hasHelpTextFunc = typeof featInfo[featKey as keyof typeof featInfo]?.helpText === "function";
- // @ts-ignore
- const helpTextVal: string | undefined = hasHelpTextFunc && featInfo[featKey as keyof typeof featInfo]!.helpText();
- if(hasKey(`feature_helptext_${featKey}`) || (helpTextVal && hasKey(helpTextVal))) {
- const helpElemImgHtml = await resourceToHTMLString("img-help");
- if(helpElemImgHtml) {
- helpElem = document.createElement("div");
- helpElem.classList.add("bytm-ftitem-help-btn", "bytm-generic-btn");
- helpElem.ariaLabel = helpElem.title = t("feature_help_button_tooltip");
- helpElem.role = "button";
- helpElem.tabIndex = 0;
- helpElem.innerHTML = helpElemImgHtml;
- const helpElemClicked = (e: MouseEvent | KeyboardEvent) => {
- e.preventDefault();
- e.stopPropagation();
- openHelpDialog(featKey as FeatureKey);
- };
- helpElem.addEventListener("click", helpElemClicked);
- helpElem.addEventListener("keydown", (e) => e.key === "Enter" && helpElemClicked(e));
- }
- else {
- error(`Couldn't create help button SVG element for feature '${featKey}'`);
- }
- }
- adornmentElem && featLeftSideElem.appendChild(adornmentElem);
- featLeftSideElem.appendChild(textElem);
- helpElem && featLeftSideElem.appendChild(helpElem);
- ftConfElem.appendChild(featLeftSideElem);
- }
- {
- let inputType: string | undefined = "text";
- let inputTag: string | undefined = "input";
- switch(type)
- {
- case "toggle":
- inputTag = undefined;
- inputType = undefined;
- break;
- case "slider":
- inputType = "range";
- break;
- case "number":
- inputType = "number";
- break;
- case "text":
- inputType = "text";
- break;
- case "select":
- inputTag = "select";
- inputType = undefined;
- break;
- case "hotkey":
- inputTag = undefined;
- inputType = undefined;
- break;
- case "button":
- inputTag = undefined;
- inputType = undefined;
- break;
- }
- const inputElemId = `bytm-ftconf-${featKey}-input`;
- const ctrlElem = document.createElement("span");
- ctrlElem.classList.add("bytm-ftconf-ctrl");
- if(inputTag) {
- // standard input element:
- const inputElem = document.createElement(inputTag) as HTMLInputElement;
- inputElem.classList.add("bytm-ftconf-input");
- inputElem.id = inputElemId;
- if(inputType)
- inputElem.type = inputType;
- // @ts-ignore
- if(typeof ftInfo.min !== "undefined")// @ts-ignore
- inputElem.min = ftInfo.min;
- // @ts-ignore
- if(ftInfo.max !== "undefined") // @ts-ignore
- inputElem.max = ftInfo.max;
- if(typeof initialVal !== "undefined")
- inputElem.value = String(initialVal);
- if(type === "number" || type === "slider" && step)
- inputElem.step = String(step);
- if(type === "toggle" && typeof initialVal !== "undefined")
- inputElem.checked = Boolean(initialVal);
- // @ts-ignore
- const unitTxt = (typeof ftInfo.unit === "string" ? ftInfo.unit : (
- // @ts-ignore
- typeof ftInfo.unit === "function" ? ftInfo.unit(Number(inputElem.value)) : ""
- ));
- let labelElem: HTMLLabelElement | undefined;
- let lastDisplayedVal: string | undefined;
- if(type === "slider") {
- labelElem = document.createElement("label");
- labelElem.classList.add("bytm-ftconf-label", "bytm-slider-label");
- labelElem.textContent = `${fmtVal(initialVal)} ${unitTxt}`;
- inputElem.addEventListener("input", () => {
- if(labelElem && lastDisplayedVal !== inputElem.value) {
- labelElem.textContent = `${fmtVal(inputElem.value)} ${unitTxt}`;
- lastDisplayedVal = inputElem.value;
- }
- });
- }
- else if(type === "select") {
- const ftOpts = typeof ftInfo.options === "function"
- ? ftInfo.options()
- : ftInfo.options;
- for(const { value, label } of ftOpts) {
- const optionElem = document.createElement("option");
- optionElem.value = String(value);
- optionElem.textContent = label;
- if(value === initialVal)
- optionElem.selected = true;
- inputElem.appendChild(optionElem);
- }
- }
- if(type === "text") {
- let lastValue: string | undefined = inputElem.value && inputElem.value.length > 0 ? inputElem.value : ftInfo.default;
- const textInputUpdate = () => {
- let v: string | number = String(inputElem.value).trim();
- if(type === "text" && ftInfo.normalize)
- v = inputElem.value = ftInfo.normalize(String(v));
- if(v === lastValue)
- return;
- lastValue = v;
- if(v === "")
- v = ftInfo.default;
- if(typeof initialVal !== "undefined")
- confChanged(featKey as keyof FeatureConfig, initialVal, v);
- };
- inputElem.addEventListener("blur", () => textInputUpdate());
- inputElem.addEventListener("keydown", (e) => e.key === "Tab" && textInputUpdate());
- }
- else {
- inputElem.addEventListener("input", () => {
- let v: string | number = String(inputElem.value).trim();
- if(["number", "slider"].includes(type) || v.match(/^-?\d+$/))
- v = Number(v);
- if(typeof initialVal !== "undefined")
- confChanged(featKey as keyof FeatureConfig, initialVal, (type !== "toggle" ? v : inputElem.checked));
- });
- }
- if(labelElem) {
- labelElem.id = `bytm-ftconf-${featKey}-label`;
- labelElem.htmlFor = inputElemId;
- ctrlElem.appendChild(labelElem);
- }
- ctrlElem.appendChild(inputElem);
- }
- else {
- // custom input element:
- let wrapperElem: HTMLElement | undefined;
- switch(type) {
- case "hotkey":
- wrapperElem = createHotkeyInput({
- initialValue: typeof initialVal === "object" ? initialVal as HotkeyObj : undefined,
- onChange: (hotkey) => confChanged(featKey as keyof FeatureConfig, initialVal, hotkey),
- });
- break;
- case "toggle":
- wrapperElem = await createToggleInput({
- initialValue: Boolean(initialVal),
- onChange: (checked) => confChanged(featKey as keyof FeatureConfig, initialVal, checked),
- id: `ftconf-${featKey}`,
- labelPos: "left",
- });
- break;
- case "button":
- wrapperElem = document.createElement("button");
- wrapperElem.tabIndex = 0;
- wrapperElem.textContent = wrapperElem.ariaLabel = wrapperElem.title = hasKey(`feature_btn_${featKey}`) ? t(`feature_btn_${featKey}`) : t("trigger_btn_action");
- wrapperElem.addEventListener("click", () => ftInfo.click());
- break;
- }
- ctrlElem.appendChild(wrapperElem!);
- }
- ftConfElem.appendChild(ctrlElem);
- }
- featuresCont.appendChild(ftConfElem);
- }
- }
- //#SECTION set values of inputs on external change
- siteEvents.on("rebuildCfgMenu", (newConfig) => {
- for(const ftKey in featInfo) {
- const ftElem = document.querySelector<HTMLInputElement>(`#bytm-ftconf-${ftKey}-input`);
- const labelElem = document.querySelector<HTMLLabelElement>(`#bytm-ftconf-${ftKey}-label`);
- if(!ftElem)
- continue;
- const ftInfo = featInfo[ftKey as keyof typeof featInfo];
- const value = newConfig[ftKey as keyof FeatureConfig];
- if(ftInfo.type === "toggle")
- ftElem.checked = Boolean(value);
- else
- ftElem.value = String(value);
- if(!labelElem)
- continue;
- // @ts-ignore
- const unitTxt = " " + (typeof ftInfo.unit === "string" ? ftInfo.unit : (
- // @ts-ignore
- typeof ftInfo.unit === "function" ? ftInfo.unit(Number(ftElem.value)) : ""
- ));
- if(ftInfo.type === "slider")
- labelElem.textContent = `${fmtVal(Number(value))} ${unitTxt}`;
- }
- info("Rebuilt config menu");
- });
- //#SECTION scroll indicator
- const scrollIndicator = document.createElement("img");
- scrollIndicator.id = "bytm-menu-scroll-indicator";
- scrollIndicator.src = await getResourceUrl("img-arrow_down");
- scrollIndicator.role = "button";
- scrollIndicator.ariaLabel = scrollIndicator.title = t("scroll_to_bottom");
- featuresCont.appendChild(scrollIndicator);
- scrollIndicator.addEventListener("click", () => {
- const bottomAnchor = document.querySelector("#bytm-menu-bottom-anchor");
- bottomAnchor?.scrollIntoView({
- behavior: "smooth",
- });
- });
- featuresCont.addEventListener("scroll", (evt: Event) => {
- const scrollPos = (evt.target as HTMLDivElement)?.scrollTop ?? 0;
- const scrollIndicator = document.querySelector<HTMLImageElement>("#bytm-menu-scroll-indicator");
- if(!scrollIndicator)
- return;
- if(scrollIndicatorEnabled && scrollPos > scrollIndicatorOffsetThreshold && !scrollIndicator.classList.contains("bytm-hidden")) {
- scrollIndicator.classList.add("bytm-hidden");
- }
- else if(scrollIndicatorEnabled && scrollPos <= scrollIndicatorOffsetThreshold && scrollIndicator.classList.contains("bytm-hidden")) {
- scrollIndicator.classList.remove("bytm-hidden");
- }
- });
- const bottomAnchor = document.createElement("div");
- bottomAnchor.id = "bytm-menu-bottom-anchor";
- featuresCont.appendChild(bottomAnchor);
- //#SECTION finalize
- menuContainer.appendChild(headerElem);
- menuContainer.appendChild(featuresCont);
- const versionElemCont = document.createElement("div");
- versionElemCont.id = "bytm-menu-version";
- const versionElem = document.createElement("a");
- versionElem.classList.add("bytm-link");
- versionElem.role = "button";
- versionElem.tabIndex = 0;
- versionElem.ariaLabel = versionElem.title = t("version_tooltip", scriptInfo.version, buildNumber);
- versionElem.textContent = `v${scriptInfo.version} (${buildNumber})${mode === "development" ? " [dev build]" : ""}`;
- const versionElemClicked = async (e: MouseEvent | KeyboardEvent) => {
- e.preventDefault();
- e.stopPropagation();
- await openChangelogMenu("cfgMenu");
- closeCfgMenu(undefined, false);
- };
- versionElem.addEventListener("click", versionElemClicked);
- versionElem.addEventListener("keydown", (e) => e.key === "Enter" && versionElemClicked(e));
- menuContainer.appendChild(footerCont);
- versionElemCont.appendChild(versionElem);
- titleElem.appendChild(versionElemCont);
- backgroundElem.appendChild(menuContainer);
- document.body.appendChild(backgroundElem);
- window.addEventListener("resize", debounce(checkToggleScrollIndicator, 150));
- log("Added menu element");
- // ensure stuff is reset if menu was opened before being added
- isCfgMenuOpen = false;
- document.body.classList.remove("bytm-disable-scroll");
- document.querySelector("ytmusic-app")?.removeAttribute("inert");
- backgroundElem.style.visibility = "hidden";
- backgroundElem.style.display = "none";
- }
- /** Closes the config menu if it is open. If a bubbling event is passed, its propagation will be prevented. */
- export function closeCfgMenu(evt?: MouseEvent | KeyboardEvent, enableScroll = true) {
- if(!isCfgMenuOpen)
- return;
- isCfgMenuOpen = false;
- evt?.bubbles && evt.stopPropagation();
- if(enableScroll) {
- document.body.classList.remove("bytm-disable-scroll");
- document.querySelector("ytmusic-app")?.removeAttribute("inert");
- }
- const menuBg = document.querySelector<HTMLElement>("#bytm-cfg-menu-bg");
- siteEvents.emit("cfgMenuClosed");
- if(!menuBg)
- return;
- menuBg.style.visibility = "hidden";
- menuBg.style.display = "none";
- }
- /** Opens the config menu if it is closed */
- export async function openCfgMenu() {
- if(!isCfgMenuAdded)
- await addCfgMenu();
- if(isCfgMenuOpen)
- return;
- isCfgMenuOpen = true;
- document.body.classList.add("bytm-disable-scroll");
- document.querySelector("ytmusic-app")?.setAttribute("inert", "true");
- const menuBg = document.querySelector<HTMLElement>("#bytm-cfg-menu-bg");
- if(!menuBg)
- return;
- menuBg.style.visibility = "visible";
- menuBg.style.display = "block";
- checkToggleScrollIndicator();
- }
- /** Checks if the features container is scrollable and toggles the scroll indicator accordingly */
- function checkToggleScrollIndicator() {
- const featuresCont = document.querySelector<HTMLElement>("#bytm-menu-opts");
- const scrollIndicator = document.querySelector<HTMLElement>("#bytm-menu-scroll-indicator");
-
- // disable scroll indicator if container doesn't scroll
- if(featuresCont && scrollIndicator) {
- const verticalScroll = isScrollable(featuresCont).vertical;
- /** If true, the indicator's threshold is under the available scrollable space and so it should be disabled */
- const underThreshold = featuresCont.scrollHeight - featuresCont.clientHeight <= scrollIndicatorOffsetThreshold;
- if(!underThreshold && verticalScroll && !scrollIndicatorEnabled) {
- scrollIndicatorEnabled = true;
- scrollIndicator.classList.remove("bytm-hidden");
- }
- if((!verticalScroll && scrollIndicatorEnabled) || underThreshold) {
- scrollIndicatorEnabled = false;
- scrollIndicator.classList.add("bytm-hidden");
- }
- }
- }
- //#MARKER help dialog
- let isHelpDialogOpen = false;
- /** Key of the feature currently loaded in the help dialog */
- let helpDialogCurFeature: FeatureKey | undefined;
- /** Opens the feature help dialog for the given feature */
- async function openHelpDialog(featureKey: FeatureKey) {
- if(isHelpDialogOpen)
- return;
- isHelpDialogOpen = true;
- let menuBgElem: HTMLElement;
- if(!helpDialogCurFeature) {
- // create menu
- const headerElem = document.createElement("div");
- headerElem.classList.add("bytm-menu-header", "small");
- const titleCont = document.createElement("div");
- titleCont.className = "bytm-menu-titlecont-no-title";
- titleCont.role = "heading";
- titleCont.ariaLevel = "1";
- const helpIconSvg = await resourceToHTMLString("img-help");
- if(helpIconSvg)
- titleCont.innerHTML = helpIconSvg;
- const closeElem = document.createElement("img");
- closeElem.classList.add("bytm-menu-close", "small");
- closeElem.role = "button";
- closeElem.tabIndex = 0;
- closeElem.src = await getResourceUrl("img-close");
- closeElem.ariaLabel = closeElem.title = t("close_menu_tooltip");
- closeElem.addEventListener("click", (e) => closeHelpDialog(e));
- closeElem.addEventListener("keydown", (e) => e.key === "Enter" && closeHelpDialog(e));
- headerElem.appendChild(titleCont);
- headerElem.appendChild(closeElem);
- menuBgElem = document.createElement("div");
- menuBgElem.id = "bytm-feat-help-menu-bg";
- menuBgElem.classList.add("bytm-menu-bg");
- menuBgElem.ariaLabel = menuBgElem.title = t("close_menu_tooltip");
- menuBgElem.style.visibility = "hidden";
- menuBgElem.style.display = "none";
- menuBgElem.addEventListener("click", (e) => {
- if(isHelpDialogOpen && (e.target as HTMLElement)?.id === "bytm-feat-help-menu-bg")
- closeHelpDialog(e);
- });
- document.body.addEventListener("keydown", (e) => {
- if(isHelpDialogOpen && e.key === "Escape")
- closeHelpDialog(e);
- });
- const menuContainer = document.createElement("div");
- menuContainer.ariaLabel = menuContainer.title = ""; // prevent bg title from propagating downwards
- menuContainer.classList.add("bytm-menu");
- menuContainer.id = "bytm-feat-help-menu";
- const featDescElem = document.createElement("h3");
- featDescElem.id = "bytm-feat-help-menu-desc";
- const helpTextElem = document.createElement("div");
- helpTextElem.id = "bytm-feat-help-menu-text";
- menuContainer.appendChild(headerElem);
- menuContainer.appendChild(featDescElem);
- menuContainer.appendChild(helpTextElem);
- menuBgElem.appendChild(menuContainer);
- document.body.appendChild(menuBgElem);
- }
- else
- menuBgElem = document.querySelector<HTMLElement>("#bytm-feat-help-menu-bg")!;
- if(helpDialogCurFeature !== featureKey) {
- // update help text
- const featDescElem = menuBgElem.querySelector<HTMLElement>("#bytm-feat-help-menu-desc")!;
- const helpTextElem = menuBgElem.querySelector<HTMLElement>("#bytm-feat-help-menu-text")!;
- featDescElem.textContent = t(`feature_desc_${featureKey}`);
- // @ts-ignore
- const helpText: string | undefined = featInfo[featureKey]?.helpText?.();
- helpTextElem.textContent = helpText ?? t(`feature_helptext_${featureKey}`);
- }
- // show menu
- const menuBg = document.querySelector<HTMLElement>("#bytm-feat-help-menu-bg");
- if(!menuBg)
- return warn("Couldn't find feature help dialog background element");
- helpDialogCurFeature = featureKey;
- menuBg.style.visibility = "visible";
- menuBg.style.display = "block";
- document.querySelector("#bytm-cfg-menu")?.setAttribute("inert", "true");
- }
- function closeHelpDialog(evt?: MouseEvent | KeyboardEvent) {
- if(!isHelpDialogOpen)
- return;
- isHelpDialogOpen = false;
- evt?.bubbles && evt.stopPropagation();
- const menuBg = document.querySelector<HTMLElement>("#bytm-feat-help-menu-bg");
- if(!menuBg)
- return warn("Couldn't find feature help dialog background element");
- menuBg.style.visibility = "hidden";
- menuBg.style.display = "none";
- document.querySelector("#bytm-cfg-menu")?.removeAttribute("inert");
- }
- //#MARKER export menu
- let isExportMenuAdded = false;
- let isExportMenuOpen = false;
- let copiedTxtTimeout: number | undefined = undefined;
- let lastUncompressedCfgString: string | undefined;
- /** Adds a menu to copy the current configuration as compressed (if supported) or uncompressed JSON (hidden by default) */
- async function addExportMenu() {
- const canCompress = await compressionSupported();
- const menuBgElem = document.createElement("div");
- menuBgElem.id = "bytm-export-menu-bg";
- menuBgElem.classList.add("bytm-menu-bg");
- menuBgElem.ariaLabel = menuBgElem.title = t("close_menu_tooltip");
- menuBgElem.style.visibility = "hidden";
- menuBgElem.style.display = "none";
- menuBgElem.addEventListener("click", (e) => {
- if(isExportMenuOpen && (e.target as HTMLElement)?.id === "bytm-export-menu-bg") {
- closeExportMenu(e);
- openCfgMenu();
- }
- });
- document.body.addEventListener("keydown", (e) => {
- if(isExportMenuOpen && e.key === "Escape") {
- closeExportMenu(e);
- openCfgMenu();
- }
- });
- const menuContainer = document.createElement("div");
- menuContainer.ariaLabel = menuContainer.title = ""; // prevent bg title from propagating downwards
- menuContainer.classList.add("bytm-menu");
- menuContainer.id = "bytm-export-menu";
- //#SECTION title bar
- const headerElem = document.createElement("div");
- headerElem.classList.add("bytm-menu-header");
- const titleCont = document.createElement("div");
- titleCont.className = "bytm-menu-titlecont";
- titleCont.role = "heading";
- titleCont.ariaLevel = "1";
- const titleElem = document.createElement("h2");
- titleElem.className = "bytm-menu-title";
- titleElem.textContent = t("export_menu_title", scriptInfo.name);
- const closeElem = document.createElement("img");
- closeElem.classList.add("bytm-menu-close");
- closeElem.role = "button";
- closeElem.tabIndex = 0;
- closeElem.src = await getResourceUrl("img-close");
- closeElem.ariaLabel = closeElem.title = t("close_menu_tooltip");
- const closeExportMenuClicked = (e: MouseEvent | KeyboardEvent) => {
- closeExportMenu(e);
- openCfgMenu();
- };
- closeElem.addEventListener("click", (e) => closeExportMenuClicked(e));
- closeElem.addEventListener("keydown", (e) => e.key === "Enter" && closeExportMenuClicked(e));
- titleCont.appendChild(titleElem);
- headerElem.appendChild(titleCont);
- headerElem.appendChild(closeElem);
- //#SECTION body
- const menuBodyElem = document.createElement("div");
- menuBodyElem.classList.add("bytm-menu-body");
- const textElem = document.createElement("div");
- textElem.id = "bytm-export-menu-text";
- textElem.textContent = t("export_hint");
- const textAreaElem = document.createElement("textarea");
- textAreaElem.id = "bytm-export-menu-textarea";
- textAreaElem.readOnly = true;
- const cfgString = JSON.stringify({ formatVersion, data: getFeatures() });
- lastUncompressedCfgString = JSON.stringify({ formatVersion, data: getFeatures() }, undefined, 2);
- textAreaElem.value = canCompress ? await compress(cfgString, compressionFormat, "string") : cfgString;
- siteEvents.on("configChanged", async (data) => {
- const textAreaElem = document.querySelector<HTMLTextAreaElement>("#bytm-export-menu-textarea");
- const cfgString = JSON.stringify({ formatVersion, data });
- lastUncompressedCfgString = JSON.stringify({ formatVersion, data }, undefined, 2);
- if(textAreaElem)
- textAreaElem.value = canCompress ? await compress(cfgString, compressionFormat, "string") : cfgString;
- });
- //#SECTION footer
- const footerElem = document.createElement("div");
- footerElem.classList.add("bytm-menu-footer-right");
- const copyBtnElem = document.createElement("button");
- copyBtnElem.classList.add("bytm-btn");
- copyBtnElem.textContent = t("copy_to_clipboard");
- copyBtnElem.ariaLabel = copyBtnElem.title = t("copy_config_tooltip");
- const copiedTextElem = document.createElement("span");
- copiedTextElem.id = "bytm-export-menu-copied-txt";
- copiedTextElem.classList.add("bytm-menu-footer-copied");
- copiedTextElem.textContent = t("copied_notice");
- copiedTextElem.style.display = "none";
- const copyBtnClicked = async (evt: MouseEvent | KeyboardEvent) => {
- evt?.bubbles && evt.stopPropagation();
- const textAreaElem = document.querySelector<HTMLTextAreaElement>("#bytm-export-menu-textarea");
- if(textAreaElem) {
- GM.setClipboard(String(evt?.shiftKey || evt?.ctrlKey ? lastUncompressedCfgString : textAreaElem.value));
- copiedTextElem.style.display = "inline-block";
- if(typeof copiedTxtTimeout === "undefined") {
- copiedTxtTimeout = setTimeout(() => {
- copiedTextElem.style.display = "none";
- copiedTxtTimeout = undefined;
- }, 3000) as unknown as number;
- }
- }
- };
- copyBtnElem.addEventListener("click", copyBtnClicked);
- copyBtnElem.addEventListener("keydown", (e) => e.key === "Enter" && copyBtnClicked(e));
- // flex-direction is row-reverse
- footerElem.appendChild(copyBtnElem);
- footerElem.appendChild(copiedTextElem);
- //#SECTION finalize
- menuBodyElem.appendChild(textElem);
- menuBodyElem.appendChild(textAreaElem);
- menuBodyElem.appendChild(footerElem);
- menuContainer.appendChild(headerElem);
- menuContainer.appendChild(menuBodyElem);
-
- menuBgElem.appendChild(menuContainer);
- document.body.appendChild(menuBgElem);
- }
- /** Closes the export menu if it is open. If a bubbling event is passed, its propagation will be prevented. */
- function closeExportMenu(evt: MouseEvent | KeyboardEvent) {
- if(!isExportMenuOpen)
- return;
- isExportMenuOpen = false;
- evt?.bubbles && evt.stopPropagation();
- const menuBg = document.querySelector<HTMLElement>("#bytm-export-menu-bg");
- if(!menuBg)
- return warn("Couldn't find export menu background element");
- menuBg.style.visibility = "hidden";
- menuBg.style.display = "none";
- const copiedTxt = document.querySelector<HTMLElement>("#bytm-export-menu-copied-txt");
- if(copiedTxt) {
- copiedTxt.style.display = "none";
- if(typeof copiedTxtTimeout === "number") {
- clearTimeout(copiedTxtTimeout);
- copiedTxtTimeout = undefined;
- }
- }
- }
- /** Opens the export menu if it is closed */
- async function openExportMenu() {
- if(!isExportMenuAdded)
- await addExportMenu();
- isExportMenuAdded = true;
- if(isExportMenuOpen)
- return;
- isExportMenuOpen = true;
- document.body.classList.add("bytm-disable-scroll");
- document.querySelector("ytmusic-app")?.setAttribute("inert", "true");
- const menuBg = document.querySelector<HTMLElement>("#bytm-export-menu-bg");
- if(!menuBg)
- return warn("Couldn't find export menu background element");
- menuBg.style.visibility = "visible";
- menuBg.style.display = "block";
- }
- //#MARKER import menu
- let isImportMenuAdded = false;
- let isImportMenuOpen = false;
- /** Adds a menu to import a configuration from compressed or uncompressed JSON (hidden by default) */
- async function addImportMenu() {
- const menuBgElem = document.createElement("div");
- menuBgElem.id = "bytm-import-menu-bg";
- menuBgElem.classList.add("bytm-menu-bg");
- menuBgElem.ariaLabel = menuBgElem.title = t("close_menu_tooltip");
- menuBgElem.style.visibility = "hidden";
- menuBgElem.style.display = "none";
- menuBgElem.addEventListener("click", (e) => {
- if(isImportMenuOpen && (e.target as HTMLElement)?.id === "bytm-import-menu-bg") {
- closeImportMenu(e);
- openCfgMenu();
- }
- });
- document.body.addEventListener("keydown", (e) => {
- if(isImportMenuOpen && e.key === "Escape") {
- closeImportMenu(e);
- openCfgMenu();
- }
- });
- const menuContainer = document.createElement("div");
- menuContainer.ariaLabel = menuContainer.title = ""; // prevent bg title from propagating downwards
- menuContainer.classList.add("bytm-menu");
- menuContainer.id = "bytm-import-menu";
- //#SECTION title bar
- const headerElem = document.createElement("div");
- headerElem.classList.add("bytm-menu-header");
- const titleCont = document.createElement("div");
- titleCont.className = "bytm-menu-titlecont";
- titleCont.role = "heading";
- titleCont.ariaLevel = "1";
- const titleElem = document.createElement("h2");
- titleElem.className = "bytm-menu-title";
- titleElem.textContent = t("import_menu_title", scriptInfo.name);
- const closeElem = document.createElement("img");
- closeElem.classList.add("bytm-menu-close");
- closeElem.role = "button";
- closeElem.tabIndex = 0;
- closeElem.src = await getResourceUrl("img-close");
- closeElem.ariaLabel = closeElem.title = t("close_menu_tooltip");
- const closeImportMenuClicked = (e: MouseEvent | KeyboardEvent) => {
- closeImportMenu(e);
- openCfgMenu();
- };
- closeElem.addEventListener("click", closeImportMenuClicked);
- closeElem.addEventListener("keydown", (e) => e.key === "Enter" && closeImportMenuClicked(e));
- titleCont.appendChild(titleElem);
- headerElem.appendChild(titleCont);
- headerElem.appendChild(closeElem);
- //#SECTION body
- const menuBodyElem = document.createElement("div");
- menuBodyElem.classList.add("bytm-menu-body");
- const textElem = document.createElement("div");
- textElem.id = "bytm-import-menu-text";
- textElem.textContent = t("import_hint");
- const textAreaElem = document.createElement("textarea");
- textAreaElem.id = "bytm-import-menu-textarea";
- //#SECTION footer
- const footerElem = document.createElement("div");
- footerElem.classList.add("bytm-menu-footer-right");
- const importBtnElem = document.createElement("button");
- importBtnElem.classList.add("bytm-btn");
- importBtnElem.textContent = t("import");
- importBtnElem.ariaLabel = importBtnElem.title = t("start_import_tooltip");
- importBtnElem.addEventListener("click", async (evt) => {
- evt?.bubbles && evt.stopPropagation();
- const textAreaElem = document.querySelector<HTMLTextAreaElement>("#bytm-import-menu-textarea");
- if(!textAreaElem)
- return warn("Couldn't find import menu textarea element");
- try {
- /** Tries to parse an uncompressed or compressed input string as a JSON object */
- const decode = async (input: string) => {
- try {
- return JSON.parse(input);
- }
- catch {
- try {
- return JSON.parse(await decompress(input, compressionFormat, "string"));
- }
- catch(err) {
- warn("Couldn't import configuration:", err);
- return null;
- }
- }
- };
- const parsed = await decode(textAreaElem.value.trim());
- if(typeof parsed !== "object")
- return alert(t("import_error_invalid"));
- if(typeof parsed.formatVersion !== "number")
- return alert(t("import_error_no_format_version"));
- if(typeof parsed.data !== "object" || parsed.data === null || Object.keys(parsed.data).length === 0)
- return alert(t("import_error_no_data"));
- if(parsed.formatVersion < formatVersion) {
- let newData = JSON.parse(JSON.stringify(parsed.data));
- const sortedMigrations = Object.entries(migrations)
- .sort(([a], [b]) => Number(a) - Number(b));
- let curFmtVer = Number(parsed.formatVersion);
- for(const [fmtVer, migrationFunc] of sortedMigrations) {
- const ver = Number(fmtVer);
- if(curFmtVer < formatVersion && curFmtVer < ver) {
- try {
- const migRes = JSON.parse(JSON.stringify(migrationFunc(newData)));
- newData = migRes instanceof Promise ? await migRes : migRes;
- curFmtVer = ver;
- }
- catch(err) {
- console.error(`Error while running migration function for format version ${fmtVer}:`, err);
- }
- }
- }
- parsed.formatVersion = curFmtVer;
- parsed.data = newData;
- }
- else if(parsed.formatVersion !== formatVersion)
- return alert(t("import_error_wrong_format_version", formatVersion, parsed.formatVersion));
- await saveFeatures({ ...getFeatures(), ...parsed.data });
- if(confirm(t("import_success_confirm_reload"))) {
- disableBeforeUnload();
- return location.reload();
- }
- emitSiteEvent("rebuildCfgMenu", parsed.data);
- closeImportMenu();
- openCfgMenu();
- }
- catch(err) {
- warn("Couldn't import configuration:", err);
- alert(t("import_error_invalid"));
- }
- });
- footerElem.appendChild(importBtnElem);
- //#SECTION finalize
- menuBodyElem.appendChild(textElem);
- menuBodyElem.appendChild(textAreaElem);
- menuBodyElem.appendChild(footerElem);
- menuContainer.appendChild(headerElem);
- menuContainer.appendChild(menuBodyElem);
-
- menuBgElem.appendChild(menuContainer);
- document.body.appendChild(menuBgElem);
- }
- /** Closes the import menu if it is open. If a bubbling event is passed, its propagation will be prevented. */
- function closeImportMenu(evt?: MouseEvent | KeyboardEvent) {
- if(!isImportMenuOpen)
- return;
- isImportMenuOpen = false;
- evt?.bubbles && evt.stopPropagation();
- const menuBg = document.querySelector<HTMLElement>("#bytm-import-menu-bg");
- const textAreaElem = document.querySelector<HTMLTextAreaElement>("#bytm-import-menu-textarea");
- if(textAreaElem)
- textAreaElem.value = "";
- if(!menuBg)
- return warn("Couldn't find import menu background element");
- menuBg.style.visibility = "hidden";
- menuBg.style.display = "none";
- }
- /** Opens the import menu if it is closed */
- async function openImportMenu() {
- if(!isImportMenuAdded)
- await addImportMenu();
- isImportMenuAdded = true;
- if(isImportMenuOpen)
- return;
- isImportMenuOpen = true;
- document.body.classList.add("bytm-disable-scroll");
- document.querySelector("ytmusic-app")?.setAttribute("inert", "true");
- const menuBg = document.querySelector<HTMLElement>("#bytm-import-menu-bg");
- if(!menuBg)
- return warn("Couldn't find import menu background element");
- menuBg.style.visibility = "visible";
- menuBg.style.display = "block";
- }
- //#MARKER changelog menu
- let isChangelogMenuAdded = false;
- let isChangelogMenuOpen = false;
- /** Adds a changelog menu (hidden by default) */
- async function addChangelogMenu() {
- const menuBgElem = document.createElement("div");
- menuBgElem.id = "bytm-changelog-menu-bg";
- menuBgElem.classList.add("bytm-menu-bg");
- menuBgElem.ariaLabel = menuBgElem.title = t("close_menu_tooltip");
- menuBgElem.style.visibility = "hidden";
- menuBgElem.style.display = "none";
- menuBgElem.addEventListener("click", (e) => {
- if(isChangelogMenuOpen && (e.target as HTMLElement)?.id === "bytm-changelog-menu-bg") {
- closeChangelogMenu(e);
- if(menuBgElem.dataset.returnTo === "cfgMenu")
- openCfgMenu();
- }
- });
- document.body.addEventListener("keydown", (e) => {
- if(isChangelogMenuOpen && e.key === "Escape") {
- closeChangelogMenu(e);
- if(menuBgElem.dataset.returnTo === "cfgMenu")
- openCfgMenu();
- }
- });
- const menuContainer = document.createElement("div");
- menuContainer.ariaLabel = menuContainer.title = ""; // prevent bg title from propagating downwards
- menuContainer.classList.add("bytm-menu");
- menuContainer.id = "bytm-changelog-menu";
- //#SECTION title bar
- const headerElem = document.createElement("div");
- headerElem.classList.add("bytm-menu-header");
- const titleCont = document.createElement("div");
- titleCont.className = "bytm-menu-titlecont";
- titleCont.role = "heading";
- titleCont.ariaLevel = "1";
- const titleElem = document.createElement("h2");
- titleElem.className = "bytm-menu-title";
- titleElem.textContent = t("changelog_menu_title", scriptInfo.name);
- const closeElem = document.createElement("img");
- closeElem.classList.add("bytm-menu-close");
- closeElem.role = "button";
- closeElem.tabIndex = 0;
- closeElem.src = await getResourceUrl("img-close");
- closeElem.ariaLabel = closeElem.title = t("close_menu_tooltip");
- const closeChangelogMenuClicked = (e: MouseEvent | KeyboardEvent) => {
- closeChangelogMenu(e);
- if(menuBgElem.dataset.returnTo === "cfgMenu")
- openCfgMenu();
- };
- closeElem.addEventListener("click", closeChangelogMenuClicked);
- closeElem.addEventListener("keydown", (e) => e.key === "Enter" && closeChangelogMenuClicked(e));
- titleCont.appendChild(titleElem);
- headerElem.appendChild(titleCont);
- headerElem.appendChild(closeElem);
- //#SECTION body
- const getChangelogHtml = (async () => {
- try {
- const changelogMd = await getChangelogMd();
- return await parseMarkdown(changelogMd);
- }
- catch(err) {
- return `Error: ${err}`;
- }
- });
- const menuBodyElem = document.createElement("div");
- menuBodyElem.id = "bytm-changelog-menu-body";
- menuBodyElem.classList.add("bytm-menu-body");
- const textElem = document.createElement("div");
- textElem.id = "bytm-changelog-menu-text";
- textElem.classList.add("bytm-markdown-container");
- textElem.innerHTML = await getChangelogHtml();
- //#SECTION finalize
- menuBodyElem.appendChild(textElem);
- menuContainer.appendChild(headerElem);
- menuContainer.appendChild(menuBodyElem);
-
- menuBgElem.appendChild(menuContainer);
- document.body.appendChild(menuBgElem);
- const anchors = document.querySelectorAll<HTMLAnchorElement>("#bytm-changelog-menu-text a");
- for(const anchor of anchors) {
- anchor.ariaLabel = anchor.title = anchor.href;
- anchor.target = "_blank";
- }
- }
- /** Closes the changelog menu if it is open. If a bubbling event is passed, its propagation will be prevented. */
- function closeChangelogMenu(evt?: MouseEvent | KeyboardEvent) {
- if(!isChangelogMenuOpen)
- return;
- isChangelogMenuOpen = false;
- evt?.bubbles && evt.stopPropagation();
- const menuBg = document.querySelector<HTMLElement>("#bytm-changelog-menu-bg");
- if(!menuBg)
- return warn("Couldn't find changelog menu background element");
- menuBg.style.visibility = "hidden";
- menuBg.style.display = "none";
- }
- /**
- * Opens the changelog menu if it is closed
- * @param returnTo What menu to open after the changelog menu is closed
- */
- export async function openChangelogMenu(returnTo: "cfgMenu" | "exit" = "cfgMenu") {
- if(!isChangelogMenuAdded)
- await addChangelogMenu();
- isChangelogMenuAdded = true;
- if(isChangelogMenuOpen)
- return;
- isChangelogMenuOpen = true;
- document.body.classList.add("bytm-disable-scroll");
- document.querySelector("ytmusic-app")?.setAttribute("inert", "true");
- const menuBg = document.querySelector<HTMLElement>("#bytm-changelog-menu-bg");
- if(!menuBg)
- return warn("Couldn't find changelog menu background element");
- menuBg.dataset.returnTo = returnTo;
- menuBg.style.visibility = "visible";
- menuBg.style.display = "block";
- }
|