Ver código fonte

ref: move code to new userutils lib

Sven 1 ano atrás
pai
commit
d89d9b19fc

+ 1 - 0
README.md

@@ -84,6 +84,7 @@ Note: the tab needs to stay open on Firefox or the script will not update itself
 ### Attributions:
 This userscript depends on these runtime libraries:
 - [@billjs/event-emitter](https://npmjs.org/package/@billjs/event-emitter)
+- [@sv443-network/userutils](https://github.com/Sv443-Network/UserUtils)
   
 For development dependencies, please refer to `devDependencies` in [`package.json`](./package.json)
   

Diferenças do arquivo suprimidas por serem muito extensas
+ 156 - 267
dist/BetterYTM.user.js


+ 12 - 1
package-lock.json

@@ -9,7 +9,8 @@
       "version": "1.0.0",
       "license": "MIT",
       "dependencies": {
-        "@billjs/event-emitter": "^1.0.3"
+        "@billjs/event-emitter": "^1.0.3",
+        "@sv443-network/userutils": "^0.1.4"
       },
       "devDependencies": {
         "@types/express": "^4.17.17",
@@ -393,6 +394,11 @@
       "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==",
       "dev": true
     },
+    "node_modules/@sv443-network/userutils": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/@sv443-network/userutils/-/userutils-0.1.4.tgz",
+      "integrity": "sha512-OHxXNG6xluWBzkTpuj9Zcxja9hTn0VgM6CFJsFEMAmdBrUF1c/5UCays4V9XP7eHT6CTD9CgUmxqe3mfJYzj/g=="
+    },
     "node_modules/@trysound/sax": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -6336,6 +6342,11 @@
       "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==",
       "dev": true
     },
+    "@sv443-network/userutils": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/@sv443-network/userutils/-/userutils-0.1.4.tgz",
+      "integrity": "sha512-OHxXNG6xluWBzkTpuj9Zcxja9hTn0VgM6CFJsFEMAmdBrUF1c/5UCays4V9XP7eHT6CTD9CgUmxqe3mfJYzj/g=="
+    },
     "@trysound/sax": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",

+ 2 - 1
package.json

@@ -34,7 +34,8 @@
     "url": "https://github.com/Sv443/BetterYTM/issues"
   },
   "dependencies": {
-    "@billjs/event-emitter": "^1.0.3"
+    "@billjs/event-emitter": "^1.0.3",
+    "@sv443-network/userutils": "^0.1.4"
   },
   "devDependencies": {
     "@types/express": "^4.17.17",

+ 2 - 1
src/features/input.ts

@@ -1,4 +1,5 @@
-import { error, getUnsafeWindow, getVideoTime, info, log, warn } from "../utils";
+import { getUnsafeWindow } from "@sv443-network/userutils";
+import { error, getVideoTime, info, log, warn } from "../utils";
 import type { Domain } from "../types";
 import { getFeatures } from "../config";
 

+ 20 - 9
src/features/layout.ts

@@ -1,8 +1,9 @@
 import type { Event } from "@billjs/event-emitter";
+import { addGlobalStyle, addParent, autoPlural, fetchAdvanced, insertAfter, openInNewTab, pauseFor } from "@sv443-network/userutils";
 import type { FeatureConfig } from "../types";
 import { scriptInfo } from "../constants";
 import { getFeatures } from "../config";
-import { addGlobalStyle, addParent, autoPlural, error, fetchAdvanced, getAssetUrl, insertAfter, log, onSelectorExists, openInNewTab, pauseFor } from "../utils";
+import { error, getAssetUrl, log, onSelectorExists } from "../utils";
 import { getEvtData, siteEvents } from "../events";
 import { openMenu } from "./menu/menu_old";
 import { getGeniusUrl, createLyricsBtn, sanitizeArtists, sanitizeSong, getLyricsCacheEntry } from "./lyrics";
@@ -16,7 +17,7 @@ export async function preInitLayout() {
 
 //#MARKER BYTM-Config buttons
 
-let clicksAmt = 0, logoExchanged = false;
+let menuOpenAmt = 0, logoExchanged = false;
 
 /** Adds a watermark beneath the logo */
 export function addWatermark() {
@@ -32,11 +33,11 @@ export function addWatermark() {
 
   watermark.addEventListener("click", (e) => {
     e.stopPropagation();
-    clicksAmt++;
+    menuOpenAmt++;
 
-    if((!e.shiftKey || logoExchanged) && clicksAmt !== 5)
+    if((!e.shiftKey || logoExchanged) && menuOpenAmt !== 5)
       openMenu();
-    if((!logoExchanged && e.shiftKey) || clicksAmt === 5)
+    if((!logoExchanged && e.shiftKey) || menuOpenAmt === 5)
       exchangeLogo();
   });
 
@@ -44,7 +45,12 @@ export function addWatermark() {
   watermark.addEventListener("keydown", (e) => {
     if(e.key === "Enter") {
       e.stopPropagation();
-      openMenu();
+      menuOpenAmt++;
+
+      if((!e.shiftKey || logoExchanged) && menuOpenAmt !== 5)
+        openMenu();
+      if((!logoExchanged && e.shiftKey) || menuOpenAmt === 5)
+        exchangeLogo();
     }
   });
 
@@ -112,10 +118,14 @@ export function addConfigMenuOption(container: HTMLElement) {
 
   const cfgOptItemElem = document.createElement("div");
   cfgOptItemElem.className = "bytm-cfg-menu-option-item";
-  cfgOptItemElem.addEventListener("click", () => {
+  cfgOptItemElem.addEventListener("click", (e) => {
     const settingsBtnElem = document.querySelector<HTMLElement>("ytmusic-nav-bar ytmusic-settings-button tp-yt-paper-icon-button");
     settingsBtnElem?.click();
-    openMenu();
+    menuOpenAmt++;
+    if((!e.shiftKey || logoExchanged) && menuOpenAmt !== 5)
+      openMenu();
+    if((!logoExchanged && e.shiftKey) || menuOpenAmt === 5)
+      exchangeLogo();
   });
 
   const cfgOptIconElem = document.createElement("img");
@@ -160,11 +170,12 @@ export function setVolSliderSize() {
     return;
 
   const style = `\
+/* BetterYTM - set volume slider size */
 .volume-slider.ytmusic-player-bar, .expand-volume-slider.ytmusic-player-bar {
   width: ${size}px !important;
 }`;
 
-  addGlobalStyle(style, "vol-slider");
+  addGlobalStyle(style);
 }
 
 /** Sets the `step` attribute of the volume slider */

+ 2 - 1
src/features/lyrics.ts

@@ -1,4 +1,5 @@
-import { clamp, error, fetchAdvanced, getAssetUrl, info, insertAfter, log, onSelectorExists } from "../utils";
+import { clamp, fetchAdvanced, insertAfter } from "@sv443-network/userutils";
+import { error, getAssetUrl, info, log, onSelectorExists } from "../utils";
 
 /** Base URL of geniURL */
 export const geniUrlBase = "https://api.sv443.net/geniurl";

+ 2 - 1
src/features/menu/menu_old.ts

@@ -1,8 +1,9 @@
+import { debounce } from "@sv443-network/userutils";
 import { defaultFeatures, getFeatures, saveFeatureConf, setDefaultFeatConf } from "../../config";
 import { scriptInfo } from "../../constants";
 import { featInfo } from "../index";
 import { FeatureConfig } from "../../types";
-import { debounce, getAssetUrl, info, log } from "../../utils";
+import { getAssetUrl, info, log } from "../../utils";
 import "./menu_old.css";
 
 //#MARKER create menu elements

+ 4 - 3
src/index.ts

@@ -1,6 +1,7 @@
+import { addGlobalStyle, autoPlural, preloadImages } from "@sv443-network/userutils";
 import { loadFeatureConf } from "./config";
 import { logLevel, scriptInfo } from "./constants";
-import { addGlobalStyle, autoPlural, error, getAssetUrl, getDomain, initSelectorExistsCheck, log, onSelectorExists, precacheImages, setLogLevel } from "./utils";
+import { error, getAssetUrl, getDomain, initSelectorExistsCheck, log, onSelectorExists, setLogLevel } from "./utils";
 import { initSiteEvents } from "./events";
 import {
   // layout
@@ -57,7 +58,7 @@ async function init() {
     document.addEventListener("DOMContentLoaded", onDomLoad);
 
     if(domain === "ytm")
-      precacheImages(precacheImgs)
+      preloadImages(precacheImgs)
         .then(() => log(`Pre-cached ${precacheImgs.length} ${autoPlural("image", precacheImgs)}`))
         .catch((e) => error(`Pre-caching error: ${e}`));
   }
@@ -77,7 +78,7 @@ async function init() {
 /** Called when the DOM has finished loading and can be queried and altered by the userscript */
 async function onDomLoad() {
   // post-build these double quotes are replaced by backticks (if backticks are used here, webpack converts them to double quotes)
-  addGlobalStyle("{{GLOBAL_STYLE}}", "global");
+  addGlobalStyle("{{GLOBAL_STYLE}}");
 
   const features = await loadFeatureConf();
 

+ 2 - 2
src/tools/post-build.ts

@@ -88,10 +88,10 @@ type BuildStats = {
         MODE: mode,
         BRANCH: branch,
         BUILD_NUMBER: lastCommitSha,
-      },
+      }, 
     )
       // needs special treatment because the double quotes need to be replaced with backticks
-      .replace(/"(\/\*)?{{GLOBAL_STYLE}}(\*\/)?"/gm, `\`${globalStyle}\``);
+      .replace(/"(\/\*)?{{GLOBAL_STYLE}}(\*\/)?"/gm, `/* BetterYTM - global style */\n\`${globalStyle}\``);
 
     if(mode === "production")
       userscript = remSourcemapComments(userscript);

+ 0 - 9
src/types.ts

@@ -10,15 +10,6 @@ export type Domain = "yt" | "ytm";
 /** Feature category to be used in the menu for grouping features */
 export type FeatureCategory = "input" | "layout" | "lyrics";
 
-export type SelectorExistsOpts = {
-  /** The selector to check for */
-  selector: string;
-  /** Whether to use `querySelectorAll()` instead */
-  all?: boolean;
-  /** Whether to call the listener continuously instead of once */
-  continuous?: boolean;
-};
-
 /** Feature configuration */
 export interface FeatureConfig {
   //#SECTION input

+ 2 - 175
src/utils.ts

@@ -1,5 +1,6 @@
+import { clamp, getUnsafeWindow } from "@sv443-network/userutils";
 import { branch, scriptInfo } from "./constants";
-import type { Domain, LogLevel, SelectorExistsOpts } from "./types";
+import type { Domain, LogLevel } from "./types";
 
 //#SECTION logging
 
@@ -166,45 +167,6 @@ function ytForceShowVideoTime() {
 //   return isNaN(finalTime) ? 0 : finalTime;
 // }
 
-//#SECTION DOM
-
-/**
- * Inserts `afterNode` as a sibling just after the provided `beforeNode`
- * @returns Returns the `afterNode`
- */
-export function insertAfter(beforeNode: HTMLElement, afterNode: HTMLElement) {
-  beforeNode.parentNode?.insertBefore(afterNode, beforeNode.nextSibling);
-  return afterNode;
-}
-
-/** Adds a parent container around the provided element - returns the new parent node */
-export function addParent(element: HTMLElement, newParent: HTMLElement) {
-  const oldParent = element.parentNode;
-
-  if(!oldParent)
-    throw new Error("Element doesn't have a parent node");
-
-  oldParent.replaceChild(newParent, element);
-  newParent.appendChild(element);
-
-  return newParent;
-}
-
-/**
- * Adds global CSS style through a `<style>` element in the document's `<head>`
- * @param style CSS string
- * @param ref Reference name that is included in the `<style>`'s ID - prefixed with `betterytm-style-` - no ID is given if it's `undefined`
- */
-export function addGlobalStyle(style: string, ref?: string) {
-  const styleElem = document.createElement("style");
-  if(ref)
-    styleElem.id = `betterytm-style-${ref}`;
-  styleElem.innerHTML = style;
-  document.head.appendChild(styleElem);
-
-  log(`Inserted global style${ref ? ` with ref '${ref}'` : ""}:`, styleElem);
-}
-
 const selectorExistsMap = new Map<string, Array<(element: HTMLElement) => void>>();
 
 /**
@@ -225,33 +187,6 @@ export function onSelectorExists(selector: string, listener: (element: HTMLEleme
   }
 }
 
-/**
- * Calls the `listener` as soon as the `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.
- * @template TElem The type of element that this selector will return - FIXME: listener inferring doesn't work when this generic is given
- */
-export function onSelector<TElem = HTMLElement, TOpts extends SelectorExistsOpts = SelectorExistsOpts>(
-  options: TOpts,
-  listener: (element: TOpts["all"] extends true ? (TElem extends HTMLElement ? NodeListOf<TElem> : TElem) : TElem) => void,
-) {
-  // TODO:
-  void [options, listener];
-}
-
-// onSelector({
-//   selector: "a",
-//   all: true,
-// }, (elem) => {
-//   void elem;
-// });
-
-/** Removes all listeners registered in `onSelector()` with a matching selector property */
-export function removeOnSelector(selector: string) {
-  // TODO:
-  void [selector];
-}
-
 /** Initializes the MutationObserver responsible for checking selectors registered in `onSelectorExists()` */
 export function initSelectorExistsCheck() {
   const observer = new MutationObserver(() => {
@@ -272,79 +207,6 @@ export function initSelectorExistsCheck() {
   log("Initialized \"selector exists\" MutationObserver");
 }
 
-/** Preloads an array of image URLs so they can be loaded instantly from cache later on */
-export function precacheImages(srcUrls: string[], rejects = false) {
-  const promises = srcUrls.map(src => new Promise((res, rej) => {
-    const image = new Image();
-    image.src = src;
-    image.addEventListener("load", () => res(image));
-    image.addEventListener("error", () => rejects && rej(`Failed to preload image with URL '${src}'`));
-  }));
-
-  return Promise.allSettled(promises);
-}
-
-//#SECTION misc
-
-type FetchOpts = RequestInit & {
-  timeout: number;
-};
-
-/** Calls the fetch API with special options */
-export async function fetchAdvanced(url: string, options: Partial<FetchOpts> = {}) {
-  const { timeout = 10000 } = options;
-
-  const controller = new AbortController();
-  const id = setTimeout(() => controller.abort(), timeout);
-
-  const res = await fetch(url, {
-    ...options,
-    signal: controller.signal,
-  });
-
-  clearTimeout(id);
-  return res;
-}
-
-/**
- * Creates an invisible anchor with _blank target and clicks it.  
- * This has to be run in relatively quick succession to a user interaction event, else the browser rejects it.
- */
-export function openInNewTab(href: string) {
-  try {
-    const openElem = document.createElement("a");
-    Object.assign(openElem, {
-      className: "betterytm-open-in-new-tab",
-      target: "_blank",
-      rel: "noopener noreferrer",
-      href,
-    });
-    openElem.style.visibility = "hidden";
-
-    document.body.appendChild(openElem);
-    openElem.click();
-    // timeout just to be safe
-    setTimeout(() => openElem.remove(), 200);
-  }
-  catch(err) {
-    error("Couldn't open URL in a new tab due to an error:", err);
-  }
-}
-
-/**
- * Returns `unsafeWindow` if it is available, otherwise falls back to just `window`  
- * unsafeWindow is sometimes needed because otherwise YTM errors out - see [this issue](https://github.com/Sv443/BetterYTM/issues/18#show_issue)
- */
-export function getUnsafeWindow() {
-  try {
-    // throws ReferenceError if the "@grant unsafeWindow" isn't present
-    return unsafeWindow;
-  }
-  catch(e) {
-    return window;
-  }
-}
-
 /**
  * Returns the current domain as a constant string representation
  * @throws Throws if script runs on an unexpected website
@@ -364,38 +226,3 @@ export function getDomain(): Domain {
 export function getAssetUrl(path: string) {
   return `https://raw.githubusercontent.com/Sv443/BetterYTM/${branch}/assets/${path}`;
 }
-
-/**
- * Automatically appends an `s` to the passed `word`, if `num` is not equal to 1
- * @param word A word in singular form, to auto-convert to plural
- * @param num If this is an array, the amount of items is used
- */
-export function autoPlural(word: string, num: number | unknown[] | NodeList) {
-  if(Array.isArray(num) || num instanceof NodeList)
-    num = num.length;
-  return `${word}${num === 1 ? "" : "s"}`;
-}
-
-/** Ensures the passed `value` always stays between `min` and `max` */
-export function clamp(value: number, min: number, max: number) {
-  return Math.max(Math.min(value, max), min);
-}
-
-/** Pauses async execution for the specified time in ms */
-export function pauseFor(time: number) {
-  return new Promise((res) => {
-    setTimeout(res, time);
-  });
-}
-
-/**
- * Calls the passed `func` after the specified `timeout` in ms.  
- * Any subsequent calls to this function will reset the timer and discard previous calls.
- */
-export function debounce<TFunc extends (...args: TArgs[]) => void, TArgs = any>(func: TFunc, timeout = 300) { // eslint-disable-line @typescript-eslint/no-explicit-any
-  let timer: NodeJS.Timer | undefined;
-  return function(...args: TArgs[]) {
-    clearTimeout(timer);
-    timer = setTimeout(() => func.apply(this, args), timeout);
-  };
-}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff