Browse Source

ref!: completely replace onSelectorOld with SelectorObserver instances

Sv443 1 year ago
parent
commit
b75633bfb1

+ 3 - 2
src/features/behavior.ts

@@ -1,7 +1,8 @@
 import { clamp, pauseFor } from "@sv443-network/userutils";
-import { domLoaded, error, getDomain, getVideoTime, getWatchId, info, log, onSelectorOld, videoSelector, waitVideoElementReady } from "../utils";
+import { domLoaded, error, getDomain, getVideoTime, getWatchId, info, log, videoSelector, waitVideoElementReady } from "../utils";
 import { LogLevel } from "../types";
 import { getFeatures } from "src/config";
+import { addSelectorListener } from "src/observers";
 
 //#MARKER beforeunload popup
 
@@ -52,7 +53,7 @@ export async function initAutoCloseToasts() {
     const animTimeout = 300;
     const closeTimeout = Math.max(getFeatures().closeToastsTimeout * 1000 + animTimeout, animTimeout);
 
-    onSelectorOld("tp-yt-paper-toast#toast", {
+    addSelectorListener("popupContainer", "tp-yt-paper-toast#toast", {
       all: true,
       continuous: true,
       listener: async (toastElems) => {

+ 18 - 16
src/features/layout.ts

@@ -2,7 +2,7 @@ import { addGlobalStyle, addParent, autoPlural, fetchAdvanced, insertAfter, open
 import { getFeatures } from "../config";
 import { siteEvents } from "../siteEvents";
 import { addSelectorListener } from "../observers";
-import { error, getResourceUrl, log, onSelectorOld, warn, t, onInteraction, getBestThumbnailUrl, getDomain } from "../utils";
+import { error, getResourceUrl, log, warn, t, onInteraction, getBestThumbnailUrl, getDomain } from "../utils";
 import { scriptInfo } from "../constants";
 import { openCfgMenu } from "../menu/menu_old";
 import "./layout.css";
@@ -34,7 +34,7 @@ export async function addWatermark() {
 
   onInteraction(watermark, watermarkOpenMenu);
 
-  onSelectorOld("ytmusic-nav-bar #left-content", {
+  addSelectorListener("navBar", "ytmusic-nav-bar #left-content", {
     listener: (logoElem) => insertAfter(logoElem, watermark),
   });
 
@@ -51,7 +51,7 @@ export async function improveLogo() {
     const res = await fetchAdvanced("https://music.youtube.com/img/on_platform_logo_dark.svg");
     const svg = await res.text();
 
-    onSelectorOld("ytmusic-logo a", {
+    addSelectorListener("navBar", "ytmusic-logo a", {
       listener: (logoElem) => {
         logoElem.classList.add("bytm-mod-logo", "bytm-no-select");
         logoElem.innerHTML = svg;
@@ -73,7 +73,7 @@ export async function improveLogo() {
 
 /** Exchanges the default YTM logo into BetterYTM's logo with a sick ass animation */
 function exchangeLogo() {
-  onSelectorOld(".bytm-mod-logo", {
+  addSelectorListener("navBar", ".bytm-mod-logo", {
     listener: async (logoElem) => {
       if(logoElem.classList.contains("bytm-logo-exchanged"))
         return;
@@ -147,13 +147,13 @@ export async function addConfigMenuOption(container: HTMLElement) {
 
 /** Removes the "Upgrade" / YT Music Premium tab from the sidebar */
 export async function removeUpgradeTab() {
-  onSelectorOld("ytmusic-app-layout tp-yt-app-drawer #contentContainer #guide-content #items ytmusic-guide-entry-renderer:nth-of-type(4)", {
+  addSelectorListener("sideBar", "#contentContainer #guide-content #items ytmusic-guide-entry-renderer:nth-of-type(4)", {
     listener: (tabElemLarge) => {
       tabElemLarge.remove();
       log("Removed large upgrade tab");
     },
   });
-  onSelectorOld("ytmusic-app-layout #mini-guide ytmusic-guide-renderer #sections ytmusic-guide-section-renderer[is-primary] #items ytmusic-guide-entry-renderer:nth-of-type(4)", {
+  addSelectorListener("sideBarMini", "ytmusic-guide-renderer #sections ytmusic-guide-section-renderer[is-primary] #items ytmusic-guide-entry-renderer:nth-of-type(4)", {
     listener: (tabElemSmall) => {
       tabElemSmall.remove();
       log("Removed small upgrade tab");
@@ -204,9 +204,11 @@ export async function addAnchorImprovements() {
       }
     };
 
+    // TODO: needs to be optimized
+
     // home page
 
-    onSelectorOld<HTMLElement>("#contents.ytmusic-section-list-renderer ytmusic-carousel-shelf-renderer ytmusic-responsive-list-item-renderer", {
+    addSelectorListener("body", "#contents.ytmusic-section-list-renderer ytmusic-carousel-shelf-renderer ytmusic-responsive-list-item-renderer", {
       continuous: true,
       all: true,
       listener: addListItemAnchors,
@@ -214,7 +216,7 @@ export async function addAnchorImprovements() {
 
     // related tab in /watch
 
-    onSelectorOld<HTMLElement>("ytmusic-tab-renderer[page-type=\"MUSIC_PAGE_TYPE_TRACK_RELATED\"] ytmusic-responsive-list-item-renderer", {
+    addSelectorListener("body", "ytmusic-tab-renderer[page-type=\"MUSIC_PAGE_TYPE_TRACK_RELATED\"] ytmusic-responsive-list-item-renderer", {
       continuous: true,
       all: true,
       listener: addListItemAnchors,
@@ -222,7 +224,7 @@ export async function addAnchorImprovements() {
 
     // playlists
 
-    onSelectorOld<HTMLElement>("#contents.ytmusic-section-list-renderer ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer", {
+    addSelectorListener("body", "#contents.ytmusic-section-list-renderer ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer", {
       continuous: true,
       all: true,
       listener: addListItemAnchors,
@@ -230,7 +232,7 @@ export async function addAnchorImprovements() {
 
     // generic shelves
 
-    onSelectorOld<HTMLElement>("#contents.ytmusic-section-list-renderer ytmusic-shelf-renderer ytmusic-responsive-list-item-renderer", {
+    addSelectorListener("body", "#contents.ytmusic-section-list-renderer ytmusic-shelf-renderer ytmusic-responsive-list-item-renderer", {
       continuous: true,
       all: true,
       listener: addListItemAnchors,
@@ -249,14 +251,14 @@ export async function addAnchorImprovements() {
       return items.length;
     };
 
-    onSelectorOld("ytmusic-app-layout tp-yt-app-drawer #contentContainer #guide-content #items ytmusic-guide-entry-renderer", {
+    addSelectorListener("sideBar", "#contentContainer #guide-content #items ytmusic-guide-entry-renderer", {
       listener: (sidebarCont) => {
         const itemsAmt = addSidebarAnchors(sidebarCont);
         log(`Added anchors around ${itemsAmt} sidebar ${autoPlural("item", itemsAmt)}`);
       },
     });
 
-    onSelectorOld("ytmusic-app-layout #mini-guide ytmusic-guide-renderer ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer", {
+    addSelectorListener("sideBarMini", "ytmusic-guide-renderer ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer", {
       listener: (miniSidebarCont) => {
         const itemsAmt = addSidebarAnchors(miniSidebarCont);
         log(`Added anchors around ${itemsAmt} mini sidebar ${autoPlural("item", itemsAmt)}`);
@@ -321,7 +323,7 @@ export async function initRemShareTrackParam() {
     }
   })();
 
-  addSelectorListener<HTMLInputElement>("body", sharePanelSel, {
+  addSelectorListener("body", sharePanelSel, {
     listener: (sharePanelEl) => {
       const obs = new MutationObserver(() => {
         const inputElem = sharePanelEl.querySelector<HTMLInputElement>(inputSel);
@@ -355,7 +357,7 @@ export async function fixSpacing() {
 
 /** Adds a button to the queue to scroll to the active song */
 export async function addScrollToActiveBtn() {
-  onSelectorOld("#side-panel #tabsContent tp-yt-paper-tab:nth-of-type(1)", {
+  addSelectorListener("sidePanel", "#tabsContent tp-yt-paper-tab:nth-of-type(1)", {
     listener: async (tabElem) => {
       const containerElem = document.createElement("div");
       containerElem.id = "bytm-scroll-to-active-btn-cont";
@@ -522,7 +524,7 @@ export async function initThumbnailOverlay() {
   
       toggleBtnElem.appendChild(imgElem);
   
-      onSelectorOld(".middle-controls-buttons ytmusic-like-button-renderer#like-button-renderer", {
+      addSelectorListener("playerBarMiddleButtons", "ytmusic-like-button-renderer#like-button-renderer", {
         listener: (likeContainer) => insertAfter(likeContainer, toggleBtnElem),
       });
     }
@@ -530,7 +532,7 @@ export async function initThumbnailOverlay() {
     log("Added thumbnail overlay");
   };
 
-  addSelectorListener("body", playerSelector, {
+  addSelectorListener("mainPanel", playerSelector, {
     listener(playerEl) {
       if(playerEl.getAttribute("player-ui-state") === "INACTIVE") {
         const obs = new MutationObserver(() => {

+ 3 - 2
src/features/lyrics.ts

@@ -1,11 +1,12 @@
 import { autoPlural, fetchAdvanced, insertAfter } from "@sv443-network/userutils";
 import Fuse from "fuse.js";
-import { constructUrlString, error, getResourceUrl, info, log, onSelectorOld, warn, t, tp } from "../utils";
+import { constructUrlString, error, getResourceUrl, info, log, warn, t, tp } from "../utils";
 import { emitInterface } from "../interface";
 import { mode, scriptInfo } from "../constants";
 import { getFeatures } from "../config";
 import { addLyricsCacheEntryBest, addLyricsCacheEntryPenalized, getLyricsCacheEntry } from "./lyricsCache";
 import type { LyricsCacheEntry } from "../types";
+import { addSelectorListener } from "src/observers";
 
 /** Ratelimit budget timeframe in seconds - should reflect what's in geniURL's docs */
 const geniUrlRatelimitTimeframe = 30;
@@ -16,7 +17,7 @@ let currentSongTitle = "";
 
 /** Adds a lyrics button to the media controls bar */
 export async function addMediaCtrlLyricsBtn() {
-  onSelectorOld(".middle-controls-buttons ytmusic-like-button-renderer#like-button-renderer", { listener: addActualMediaCtrlLyricsBtn });
+  addSelectorListener("playerBarMiddleButtons", "ytmusic-like-button-renderer#like-button-renderer", { listener: addActualMediaCtrlLyricsBtn });
 }
 
 /** Actually adds the lyrics button after the like button renderer has been verified to exist */

+ 3 - 4
src/features/songLists.ts

@@ -1,5 +1,5 @@
 import { autoPlural, openInNewTab, pauseFor } from "@sv443-network/userutils";
-import { clearInner, error, getResourceUrl, log, onInteraction, onSelectorOld, t, warn } from "../utils";
+import { clearInner, error, getResourceUrl, log, onInteraction, t, warn } from "../utils";
 import { SiteEventsMap, siteEvents } from "../siteEvents";
 import { emitInterface } from "../interface";
 import { fetchLyricsUrlTop, createLyricsBtn, sanitizeArtists, sanitizeSong, splitVideoTitle } from "./lyrics";
@@ -7,6 +7,7 @@ import { getLyricsCacheEntry } from "./lyricsCache";
 import type { LyricsCacheEntry } from "../types";
 import { getFeatures } from "../config";
 import "./songLists.css";
+import { addSelectorListener } from "src/observers";
 
 /** Initializes the queue buttons */
 export async function initQueueButtons() {
@@ -58,7 +59,7 @@ export async function initQueueButtons() {
 
   if(getFeatures().listButtonsPlacement === "everywhere") {
     for(const selector of listSelectors) {
-      onSelectorOld(selector, {
+      addSelectorListener("body", selector, {
         all: true,
         continuous: true,
         listener: (songLists) => {
@@ -68,8 +69,6 @@ export async function initQueueButtons() {
       });
     }
   }
-
-  // TODO: support grids?
 }
 
 /**

+ 7 - 8
src/features/volume.ts

@@ -1,16 +1,17 @@
 import { addGlobalStyle, addParent, type Stringifiable } from "@sv443-network/userutils";
 import { getFeatures } from "../config";
-import { error, log, onSelectorOld, resourceToHTMLString, t, waitVideoElementReady } from "../utils";
+import { error, log, resourceToHTMLString, t, waitVideoElementReady } from "../utils";
 import { siteEvents } from "../siteEvents";
 import { featInfo } from ".";
 import "./volume.css";
+import { addSelectorListener } from "src/observers";
 
 //#MARKER init
 
 /** Initializes all volume-related features */
 export async function initVolumeFeatures() {
   // not technically an input element but behaves pretty much the same
-  onSelectorOld<HTMLInputElement>("tp-yt-paper-slider#volume-slider", {
+  addSelectorListener<HTMLInputElement>("playerBarRightControls", "tp-yt-paper-slider#volume-slider", {
     listener: async (sliderElem) => {
       const volSliderCont = document.createElement("div");
       volSliderCont.id = "bytm-vol-slider-cont";
@@ -94,6 +95,8 @@ async function addVolumeSliderLabel(sliderElem: HTMLInputElement, sliderContaine
   labelElem.classList.add("label");
   labelElem.textContent = getLabel(sliderElem.value);
 
+  labelContElem.appendChild(labelElem);
+
   // prevent video from minimizing
   labelContElem.addEventListener("click", (e) => e.stopPropagation());
   labelContElem.addEventListener("keydown", (e) => ["Enter", "Space", " "].includes(e.key) && e.stopPropagation());
@@ -123,12 +126,8 @@ async function addVolumeSliderLabel(sliderElem: HTMLInputElement, sliderContaine
     updateLabel();
   });
 
-  onSelectorOld("#bytm-vol-slider-cont", {
-    listener: (volumeCont) => {
-      labelContElem.appendChild(labelElem);
-
-      volumeCont.appendChild(labelContElem);
-    },
+  addSelectorListener("playerBarRightControls", "#bytm-vol-slider-cont", {
+    listener: (volumeCont) => volumeCont.appendChild(labelContElem),
   });
 
   let lastSliderVal = Number(sliderElem.value);

+ 1 - 2
src/index.ts

@@ -1,5 +1,5 @@
 import { addGlobalStyle, compress, decompress, type Stringifiable } from "@sv443-network/userutils";
-import { domLoaded, initOnSelector, reserialize, warn } from "./utils";
+import { domLoaded, reserialize, warn } from "./utils";
 import { clearConfig, defaultData as defaultFeatData, getFeatures, initConfig, setFeatures } from "./config";
 import { buildNumber, compressionFormat, defaultLogLevel, mode, scriptInfo } from "./constants";
 import { error, getDomain, info, getSessionId, log, setLogLevel, initTranslations, setLocale } from "./utils";
@@ -120,7 +120,6 @@ async function onDomLoad() {
     insertGlobalStyle();
 
     initObservers();
-    initOnSelector();
 
     await initVersionCheck();
   }

+ 110 - 5
src/observers.ts

@@ -1,7 +1,19 @@
 import { SelectorListenerOptions, SelectorObserver, SelectorObserverOptions } from "@sv443-network/userutils";
-import type { ObserverName } from "./types";
 import { emitInterface } from "./interface";
-import { error } from "./utils";
+import { error, getDomain } from "./utils";
+
+export type ObserverName =
+  | "body"
+  | "navBar"
+  | "mainPanel"
+  | "sideBar"
+  | "sideBarMini"
+  | "sidePanel"
+  | "playerBar"
+  | "playerBarInfo"
+  | "playerBarMiddleButtons"
+  | "playerBarRightControls"
+  | "popupContainer";
 
 /** Options that are applied to every SelectorObserver instance */
 const defaultObserverOptions: SelectorObserverOptions = {
@@ -22,6 +34,64 @@ export function initObservers() {
 
     globservers.body.enable();
 
+    if(getDomain() !== "ytm")
+      return;
+
+    //#SECTION navBar = the navigation / title bar at the top of the page
+    const navBarSelector = "ytmusic-nav-bar";
+    globservers.navBar = new SelectorObserver(navBarSelector, {
+      ...defaultObserverOptions,
+      subtree: false,
+    });
+
+    globservers.body.addListener(navBarSelector, {
+      listener: () => globservers.navBar.enable(),
+    });
+
+    // #SECTION mainPanel = the main content panel - includes things like the video element
+    const mainPanelSelector = "ytmusic-player-page #main-panel";
+    globservers.mainPanel = new SelectorObserver(mainPanelSelector, {
+      ...defaultObserverOptions,
+      subtree: true,
+    });
+
+    globservers.body.addListener(mainPanelSelector, {
+      listener: () => globservers.mainPanel.enable(),
+    });
+
+    // #SECTION sideBar = the sidebar on the left side of the page
+    const sidebarSelector = "ytmusic-app-layout tp-yt-app-drawer";
+    globservers.sideBar = new SelectorObserver(sidebarSelector, {
+      ...defaultObserverOptions,
+      subtree: true,
+    });
+
+    globservers.body.addListener(sidebarSelector, {
+      listener: () => globservers.sideBar.enable(),
+    });
+
+    // #SECTION sideBarMini = the minimized sidebar on the left side of the page
+    const sideBarMiniSelector = "ytmusic-app-layout #mini-guide";
+    globservers.sideBarMini = new SelectorObserver(sideBarMiniSelector, {
+      ...defaultObserverOptions,
+      subtree: true,
+    });
+
+    globservers.body.addListener(sideBarMiniSelector, {
+      listener: () => globservers.sideBarMini.enable(),
+    });
+
+    // #SECTION sidePanel = the side panel on the right side of the /watch page
+    const sidePanelSelector = "#side-panel";
+    globservers.sidePanel = new SelectorObserver(sidePanelSelector, {
+      ...defaultObserverOptions,
+      subtree: true,
+    });
+
+    globservers.body.addListener(sidePanelSelector, {
+      listener: () => globservers.sidePanel.enable(),
+    });
+
     // #SECTION playerBar = media controls bar at the bottom of the page
     const playerBarSelector = "ytmusic-app-layout ytmusic-player-bar.ytmusic-app";
     globservers.playerBar = new SelectorObserver(playerBarSelector, {
@@ -30,7 +100,9 @@ export function initObservers() {
     });
 
     globservers.body.addListener(playerBarSelector, {
-      listener: () => globservers.playerBar.enable(),
+      listener: () => {
+        globservers.playerBar.enable();
+      },
     });
 
     // #SECTION playerBarInfo = song title, artist, album, etc. inside the player bar
@@ -45,6 +117,39 @@ export function initObservers() {
       listener: () => globservers.playerBarInfo.enable(),
     });
 
+    // #SECTION playerBarMiddleButtons = the buttons inside the player bar (like, dislike, lyrics, etc.)
+    const playerBarMiddleButtonsSelector = ".middle-controls .middle-controls-buttons";
+    globservers.playerBarMiddleButtons = new SelectorObserver(playerBarMiddleButtonsSelector, {
+      ...defaultObserverOptions,
+      subtree: true,
+    });
+
+    globservers.playerBar.addListener(playerBarMiddleButtonsSelector, {
+      listener: () => globservers.playerBarMiddleButtons.enable(),
+    });
+
+    // #SECTION playerBarRightControls = the controls on the right side of the player bar (volume, repeat, shuffle, etc.)
+    const playerBarRightControls = ".right-controls .middle-controls-buttons";
+    globservers.playerBarRightControls = new SelectorObserver(playerBarRightControls, {
+      ...defaultObserverOptions,
+      subtree: true,
+    });
+
+    globservers.playerBar.addListener(playerBarRightControls, {
+      listener: () => globservers.playerBarRightControls.enable(),
+    });
+
+    // #SECTION popupContainer = the container for popups (e.g. the queue popup)
+    const popupContainerSelector = "ytmusic-app ytmusic-popup-container";
+    globservers.popupContainer = new SelectorObserver(popupContainerSelector, {
+      ...defaultObserverOptions,
+      subtree: true,
+    });
+
+    globservers.body.addListener(popupContainerSelector, {
+      listener: () => globservers.popupContainer.enable(),
+    });
+
     //#SECTION finalize
 
     emitInterface("bytm:observersReady");
@@ -54,7 +159,7 @@ export function initObservers() {
   }
 }
 
-/** Interface function for adding listeners to the already present observers */
-export function addSelectorListener<TElem extends Element>(observerName: ObserverName, selector: string, options: SelectorListenerOptions<TElem>) {
+/** Interface function for adding listeners to the {@linkcode globservers} */
+export function addSelectorListener<TElem extends HTMLElement>(observerName: ObserverName, selector: string, options: SelectorListenerOptions<TElem>) {
   globservers[observerName].addListener(selector, options);
 }

+ 3 - 3
src/siteEvents.ts

@@ -1,5 +1,5 @@
 import { createNanoEvents } from "nanoevents";
-import { error, info, onSelectorOld } from "./utils";
+import { error, info } from "./utils";
 import { FeatureConfig } from "./types";
 import { emitInterface } from "./interface";
 import { addSelectorListener } from "./observers";
@@ -77,7 +77,7 @@ export async function initSiteEvents() {
     });
 
     // only observe added or removed elements
-    onSelectorOld("#side-panel #contents.ytmusic-player-queue", {
+    addSelectorListener("sidePanel", "#contents.ytmusic-player-queue", {
       listener: (el) => {
         queueObs.observe(el, {
           childList: true,
@@ -92,7 +92,7 @@ export async function initSiteEvents() {
       }
     });
 
-    onSelectorOld("#side-panel ytmusic-player-queue #automix-contents", {
+    addSelectorListener("sidePanel", "ytmusic-player-queue #automix-contents", {
       listener: (el) => {
         autoplayObs.observe(el, {
           childList: true,

+ 0 - 2
src/types.ts

@@ -43,8 +43,6 @@ export type HotkeyObj = {
   alt: boolean,
 };
 
-export type ObserverName = "body" | "playerBar" | "playerBarInfo";
-
 export type LyricsCacheEntry = {
   artist: string;
   song: string;

+ 5 - 4
src/utils/dom.ts

@@ -1,5 +1,6 @@
 import { getUnsafeWindow } from "@sv443-network/userutils";
-import { error, getDomain, onSelectorOld } from ".";
+import { error, getDomain } from ".";
+import { addSelectorListener } from "src/observers";
 
 //#MARKER video time & volume
 
@@ -20,7 +21,7 @@ export function getVideoTime() {
         if(vidElem)
           return res(Math.floor(vidElem.currentTime));
 
-        onSelectorOld<HTMLProgressElement>("tp-yt-paper-slider#progress-bar tp-yt-paper-progress#sliderBar", {
+        addSelectorListener<HTMLProgressElement>("playerBar", "tp-yt-paper-slider#progress-bar tp-yt-paper-progress#sliderBar", {
           listener: (pbEl) =>
             res(!isNaN(Number(pbEl.value)) ? Math.floor(Number(pbEl.value)) : null)
         });
@@ -58,7 +59,7 @@ export function getVideoTime() {
             }, 500);
         };
 
-        onSelectorOld<HTMLProgressElement>(pbSelector, { listener: observe });
+        addSelectorListener<HTMLProgressElement>("body", pbSelector, { listener: observe });
       }
     }
     catch(err) {
@@ -104,7 +105,7 @@ function ytForceShowVideoTime() {
 /** Waits for the video element to be in its readyState 4 / canplay state and returns it */
 export function waitVideoElementReady(): Promise<HTMLVideoElement> {
   return new Promise((res) => {
-    onSelectorOld<HTMLVideoElement>(videoSelector, {
+    addSelectorListener<HTMLVideoElement>("body", videoSelector, {
       listener: async (vidElem) => {
         if(vidElem) {
           // this is just after YT has finished doing their own shenanigans with the video time and volume

+ 0 - 1
src/utils/index.ts

@@ -2,6 +2,5 @@ export * from "./dom";
 export * from "./logging";
 export * from "./misc";
 export * from "./NanoEmitter";
-export * from "./onSelector";
 export * from "./translations";
 export * from "./xhr";

+ 0 - 108
src/utils/onSelector.ts

@@ -1,108 +0,0 @@
-/** Options for the {@linkcode onSelectorOld()} function */
-export type OnSelectorOpts<TElem extends Element = HTMLElement> = SelectorOptsOne<TElem> | SelectorOptsAll<TElem>;
-
-type SelectorOptsOne<TElem extends Element> = SelectorOptsCommon & {
-  /** Whether to use `querySelectorAll()` instead - default is false */
-  all?: false;
-  /** Gets called whenever the selector was found in the DOM */
-  listener: (element: TElem) => void;
-};
-
-type SelectorOptsAll<TElem extends Element> = SelectorOptsCommon & {
-  /** Whether to use `querySelectorAll()` instead - default is false */
-  all: true;
-  /** Gets called whenever the selector was found in the DOM */
-  listener: (elements: NodeListOf<TElem>) => void;
-};
-
-type SelectorOptsCommon = {
-  /** Whether to call the listener continuously instead of once - default is false */
-  continuous?: boolean;
-};
-
-const selectorMap = new Map<string, OnSelectorOpts[]>();
-
-/**
- * Calls the {@linkcode listener} as soon as the {@linkcode selector} exists in the DOM.  
- * Listeners are deleted when they are called once, unless `options.continuous` is set.  
- * Multiple listeners with the same selector may be registered.
- * @param selector The selector to listen for
- * @param options Used for switching to `querySelectorAll()` and for calling the listener continuously
- * @template TElem The type of element that the listener will return as its argument (defaults to the generic type HTMLElement)
- * @deprecated To be replaced with UserUtils' SelectorObserver class
- */
-export function onSelectorOld<TElem extends Element = HTMLElement>(
-  selector: string,
-  options: OnSelectorOpts<TElem>,
-) {
-  let selectorMapItems: OnSelectorOpts[] = [];
-  if(selectorMap.has(selector))
-    selectorMapItems = selectorMap.get(selector)!;
-
-  // I don't feel like dealing with intersecting types, this should work just fine at runtime
-  // @ts-ignore
-  selectorMapItems.push(options);
-
-  selectorMap.set(selector, selectorMapItems);
-  checkSelectorExists(selector, selectorMapItems);
-}
-
-/**
- * Removes all listeners registered in {@linkcode onSelectorOld()} that have the given selector
- * @returns Returns true when all listeners with the associated selector were found and removed, false otherwise
- */
-export function removeOnSelector(selector: string) {
-  return selectorMap.delete(selector);
-}
-
-function checkSelectorExists<TElem extends Element = HTMLElement>(selector: string, options: OnSelectorOpts<TElem>[]) {
-  const deleteIndices: number[] = [];
-  options.forEach((option, i) => {
-    try {
-      const elements = option.all ? document.querySelectorAll<TElem>(selector) : document.querySelector<TElem>(selector);
-      if((elements !== null && elements instanceof NodeList && elements.length > 0) || elements !== null) {
-        // I don't feel like dealing with intersecting types, this should work just fine at runtime
-        // @ts-ignore
-        option.listener(elements);
-        if(!option.continuous)
-          deleteIndices.push(i);
-      }
-    }
-    catch(err) {
-      console.error(`Couldn't call listener for selector '${selector}'`, err);
-    }
-  });
-
-  if(deleteIndices.length > 0) {
-    const newOptsArray = options.filter((_, i) => !deleteIndices.includes(i));
-    if(newOptsArray.length === 0)
-      selectorMap.delete(selector);
-    else {
-      // once again laziness strikes
-      // @ts-ignore
-      selectorMap.set(selector, newOptsArray);
-    }
-  }
-}
-
-/**
- * Initializes a MutationObserver that checks for all registered selectors whenever an element is added to or removed from the `<body>`
- * @param options For fine-tuning what triggers the MutationObserver's checking function - `subtree` and `childList` are set to true by default
- */
-export function initOnSelector(options: MutationObserverInit = {}) {
-  const observer = new MutationObserver(() => {
-    for(const [selector, options] of selectorMap.entries())
-      checkSelectorExists(selector, options);
-  });
-
-  observer.observe(document.body, {
-    subtree: true,
-    childList: true,
-    ...options,
-  });
-}
-
-/** Returns all currently registered selectors, as a map of selector strings to their associated options */
-export function getSelectorMap() {
-  return selectorMap;
-}