Переглянути джерело

fix: dont prevent bubbling of unrecognized keys in onInteraction

Sv443 11 місяців тому
батько
коміт
6f30667962
2 змінених файлів з 94 додано та 60 видалено
  1. 65 48
      dist/BetterYTM.user.js
  2. 29 12
      src/utils/dom.ts

+ 65 - 48
dist/BetterYTM.user.js

@@ -17,7 +17,7 @@
 // @license           AGPL-3.0-or-later
 // @author            Sv443
 // @copyright         Sv443 (https://github.com/Sv443)
-// @icon              https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/images/logo/logo_48.png?b=575031f
+// @icon              http://localhost:8710/assets/images/logo/logo_48.png?b=c36c83bf-a396-44c8-8ec7-7699959553d0
 // @match             https://music.youtube.com/*
 // @match             https://www.youtube.com/*
 // @run-at            document-start
@@ -35,42 +35,42 @@
 // @grant             GM.openInTab
 // @grant             unsafeWindow
 // @noframes
-// @resource          css-bundle              https://raw.githubusercontent.com/Sv443/BetterYTM/develop/dist/BetterYTM.css?b=575031f
-// @resource          css-above_queue_btns    https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/style/aboveQueueBtns.css?b=575031f
-// @resource          css-anchor_improvements https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/style/anchorImprovements.css?b=575031f
-// @resource          css-fix_hdr             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/style/fixHDR.css?b=575031f
-// @resource          css-fix_spacing         https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/style/fixSpacing.css?b=575031f
-// @resource          doc-changelog           https://raw.githubusercontent.com/Sv443/BetterYTM/develop/changelog.md?b=575031f
-// @resource          icon-advanced_mode      https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/plus_circle_small.svg?b=575031f
-// @resource          icon-arrow_down         https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/arrow_down.svg?b=575031f
-// @resource          icon-clear_list         https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/clear_list.svg?b=575031f
-// @resource          icon-delete             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/delete.svg?b=575031f
-// @resource          icon-error              https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/error.svg?b=575031f
-// @resource          icon-experimental       https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/beaker_small.svg?b=575031f
-// @resource          icon-globe              https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/globe.svg?b=575031f
-// @resource          icon-help               https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/help.svg?b=575031f
-// @resource          icon-image_filled       https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/image_filled.svg?b=575031f
-// @resource          icon-image              https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/image.svg?b=575031f
-// @resource          icon-link               https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/link.svg?b=575031f
-// @resource          icon-lyrics             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/lyrics.svg?b=575031f
-// @resource          icon-reload             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/refresh.svg?b=575031f
-// @resource          icon-skip_to            https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/skip_to.svg?b=575031f
-// @resource          icon-spinner            https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icons/spinner.svg?b=575031f
-// @resource          img-logo                https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/images/logo/logo_48.png?b=575031f
-// @resource          img-close               https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/images/close.png?b=575031f
-// @resource          img-discord             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/images/external/discord.png?b=575031f
-// @resource          img-github              https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/images/external/github.png?b=575031f
-// @resource          img-greasyfork          https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/images/external/greasyfork.png?b=575031f
-// @resource          img-openuserjs          https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/images/external/openuserjs.png?b=575031f
-// @resource          trans-de_DE             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/de_DE.json?b=575031f
-// @resource          trans-en_US             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/en_US.json?b=575031f
-// @resource          trans-en_UK             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/en_UK.json?b=575031f
-// @resource          trans-es_ES             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/es_ES.json?b=575031f
-// @resource          trans-fr_FR             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/fr_FR.json?b=575031f
-// @resource          trans-hi_IN             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/hi_IN.json?b=575031f
-// @resource          trans-ja_JA             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/ja_JA.json?b=575031f
-// @resource          trans-pt_BR             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/pt_BR.json?b=575031f
-// @resource          trans-zh_CN             https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/translations/zh_CN.json?b=575031f
+// @resource          css-bundle              http://localhost:8710/dist/BetterYTM.css?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          css-above_queue_btns    http://localhost:8710/assets/style/aboveQueueBtns.css?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          css-anchor_improvements http://localhost:8710/assets/style/anchorImprovements.css?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          css-fix_hdr             http://localhost:8710/assets/style/fixHDR.css?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          css-fix_spacing         http://localhost:8710/assets/style/fixSpacing.css?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          doc-changelog           http://localhost:8710/changelog.md?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-advanced_mode      http://localhost:8710/assets/icons/plus_circle_small.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-arrow_down         http://localhost:8710/assets/icons/arrow_down.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-clear_list         http://localhost:8710/assets/icons/clear_list.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-delete             http://localhost:8710/assets/icons/delete.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-error              http://localhost:8710/assets/icons/error.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-experimental       http://localhost:8710/assets/icons/beaker_small.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-globe              http://localhost:8710/assets/icons/globe.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-help               http://localhost:8710/assets/icons/help.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-image_filled       http://localhost:8710/assets/icons/image_filled.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-image              http://localhost:8710/assets/icons/image.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-link               http://localhost:8710/assets/icons/link.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-lyrics             http://localhost:8710/assets/icons/lyrics.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-reload             http://localhost:8710/assets/icons/refresh.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-skip_to            http://localhost:8710/assets/icons/skip_to.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          icon-spinner            http://localhost:8710/assets/icons/spinner.svg?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          img-logo                http://localhost:8710/assets/images/logo/logo_48.png?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          img-close               http://localhost:8710/assets/images/close.png?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          img-discord             http://localhost:8710/assets/images/external/discord.png?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          img-github              http://localhost:8710/assets/images/external/github.png?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          img-greasyfork          http://localhost:8710/assets/images/external/greasyfork.png?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          img-openuserjs          http://localhost:8710/assets/images/external/openuserjs.png?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-de_DE             http://localhost:8710/assets/translations/de_DE.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-en_US             http://localhost:8710/assets/translations/en_US.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-en_UK             http://localhost:8710/assets/translations/en_UK.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-es_ES             http://localhost:8710/assets/translations/es_ES.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-fr_FR             http://localhost:8710/assets/translations/fr_FR.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-hi_IN             http://localhost:8710/assets/translations/hi_IN.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-ja_JA             http://localhost:8710/assets/translations/ja_JA.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-pt_BR             http://localhost:8710/assets/translations/pt_BR.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
+// @resource          trans-zh_CN             http://localhost:8710/assets/translations/zh_CN.json?b=c36c83bf-a396-44c8-8ec7-7699959553d0
 // @require           https://cdn.jsdelivr.net/npm/@sv443-network/[email protected]/dist/index.global.js
 // @require           https://cdn.jsdelivr.net/npm/[email protected]/dist/fuse.basic.js
 // @require           https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.js
@@ -198,7 +198,7 @@ var PluginIntent;
 })(PluginIntent || (PluginIntent = {}));const modeRaw = "development";
 const branchRaw = "develop";
 const hostRaw = "github";
-const buildNumberRaw = "575031f";
+const buildNumberRaw = "498f8b9";
 /** The mode in which the script was built (production or development) */
 const mode = (modeRaw.match(/^#{{.+}}$/) ? "production" : modeRaw);
 /** The branch to use in various URLs that point to the GitHub repo */
@@ -1230,7 +1230,9 @@ function renderFooter$2() {
         copiedTextElem.style.display = "none";
         onInteraction(copyBtnElem, (evt) => __awaiter(this, void 0, void 0, function* () {
             (evt === null || evt === void 0 ? void 0 : evt.bubbles) && evt.stopPropagation();
-            GM.setClipboard(String((evt === null || evt === void 0 ? void 0 : evt.shiftKey) ? lastUncompressedCfgString : yield UserUtils.compress(JSON.stringify({ formatVersion, data: getFeatures() }), compressionFormat, "string")));
+            copyToClipboard(evt.shiftKey && lastUncompressedCfgString
+                ? lastUncompressedCfgString
+                : yield UserUtils.compress(JSON.stringify({ formatVersion, data: getFeatures() }), compressionFormat, "string"));
             copiedTextElem.style.display = "inline-block";
             if (typeof copiedTxtTimeout === "undefined") {
                 copiedTxtTimeout = setTimeout(() => {
@@ -2043,7 +2045,7 @@ function addCfgMenu() {
                         const copyHiddenInteraction = (e) => {
                             e.preventDefault();
                             e.stopPropagation();
-                            GM.setClipboard(String(getFeatures()[featKey]));
+                            copyToClipboard(getFeatures()[featKey]);
                             advCopyHintElem.style.display = "inline";
                             if (typeof hiddenCopiedTxtTimeout === "undefined") {
                                 hiddenCopiedTxtTimeout = setTimeout(() => {
@@ -5259,7 +5261,11 @@ function initObservers() {
  */
 function addSelectorListener(observerName, selector, options) {
     globservers[observerName].addListener(selector, options);
-}//#region video time, volume
+}/** Whether the DOM has finished loading and elements can be added or modified */
+let domLoaded = false;
+document.addEventListener("DOMContentLoaded", () => domLoaded = true);
+//#region video time, volume
+/** Returns the video element selector string based on the current domain */
 const getVideoSelector = () => getDomain() === "ytm" ? "ytmusic-player video" : "#player-container ytd-player video";
 /**
  * Returns the current video time in seconds, with the given {@linkcode precision} (2 decimal digits by default).
@@ -5355,9 +5361,6 @@ function waitVideoElementReady() {
     });
 }
 //#region other
-/** Whether the DOM has finished loading and elements can be added or modified */
-let domLoaded = false;
-document.addEventListener("DOMContentLoaded", () => domLoaded = true);
 /** Removes all child nodes of an element without invoking the slow-ish HTML parser */
 function clearInner(element) {
     while (element.hasChildNodes())
@@ -5368,6 +5371,7 @@ function clearNode(element) {
         clearNode(element.firstChild);
     element.parentNode.removeChild(element);
 }
+const interactionKeys = ["Enter", " ", "Space"];
 /**
  * Adds generic, accessible interaction listeners to the passed element.
  * All listeners have the default behavior prevented and stop immediate propagation.
@@ -5375,10 +5379,14 @@ function clearNode(element) {
  */
 function onInteraction(elem, listener, listenerOptions) {
     const proxListener = (e) => {
-        if (e instanceof KeyboardEvent && !(["Enter", " ", "Space", "Spacebar"].includes(e.key)))
-            return;
-        e.preventDefault();
-        e.stopImmediatePropagation();
+        if (e instanceof KeyboardEvent) {
+            if (interactionKeys.includes(e.key)) {
+                e.preventDefault();
+                e.stopImmediatePropagation();
+            }
+            else
+                return;
+        }
         listener(e);
     };
     elem.addEventListener("click", proxListener, listenerOptions);
@@ -5425,6 +5433,15 @@ function addStyleFromResource(key) {
         }
         return false;
     });
+}
+/** Copies the provided text to the clipboard and shows an error message for manual copying if the grant `GM.setClipboard` is not given. */
+function copyToClipboard(text) {
+    try {
+        GM.setClipboard(String(text));
+    }
+    catch (err) {
+        alert(t("copy_to_clipboard_error", String(text)));
+    }
 }let curLogLevel = LogLevel.Info;
 /** Common prefix to be able to tell logged messages apart and filter them in devtools */
 const consPrefix = `[${scriptInfo.name}]`;

+ 29 - 12
src/utils/dom.ts

@@ -1,10 +1,15 @@
-import { addGlobalStyle, getUnsafeWindow, randomId } from "@sv443-network/userutils";
-import { error, fetchCss, getDomain } from ".";
-import { addSelectorListener } from "src/observers";
-import type { ResourceKey } from "src/types";
+import { addGlobalStyle, getUnsafeWindow, randomId, type Stringifiable } from "@sv443-network/userutils";
+import { error, fetchCss, getDomain, t } from ".";
+import { addSelectorListener } from "../observers";
+import type { ResourceKey } from "../types";
+
+/** Whether the DOM has finished loading and elements can be added or modified */
+export let domLoaded = false;
+document.addEventListener("DOMContentLoaded", () => domLoaded = true);
 
 //#region video time, volume
 
+/** Returns the video element selector string based on the current domain */
 export const getVideoSelector = () => getDomain() === "ytm" ? "ytmusic-player video" : "#player-container ytd-player video";
 
 /**
@@ -125,10 +130,6 @@ export function waitVideoElementReady(): Promise<HTMLVideoElement> {
 
 //#region other
 
-/** Whether the DOM has finished loading and elements can be added or modified */
-export let domLoaded = false;
-document.addEventListener("DOMContentLoaded", () => domLoaded = true);
-
 /** Removes all child nodes of an element without invoking the slow-ish HTML parser */
 export function clearInner(element: Element) {
   while(element.hasChildNodes())
@@ -141,6 +142,8 @@ function clearNode(element: Element) {
   element.parentNode!.removeChild(element);
 }
 
+const interactionKeys = ["Enter", " ", "Space"];
+
 /**
  * Adds generic, accessible interaction listeners to the passed element.  
  * All listeners have the default behavior prevented and stop immediate propagation.
@@ -148,10 +151,14 @@ function clearNode(element: Element) {
  */
 export function onInteraction<TElem extends HTMLElement>(elem: TElem, listener: (evt: MouseEvent | KeyboardEvent) => void, listenerOptions?: AddEventListenerOptions) {
   const proxListener = (e: MouseEvent | KeyboardEvent) => {
-    if(e instanceof KeyboardEvent && !(["Enter", " ", "Space", "Spacebar"].includes(e.key)))
-      return;
-    e.preventDefault();
-    e.stopImmediatePropagation();
+    if(e instanceof KeyboardEvent) {
+      if(interactionKeys.includes(e.key)) {
+        e.preventDefault();
+        e.stopImmediatePropagation();
+      }
+      else return;
+    }
+    // clean up the other listener that isn't automatically removed if `once` is set
     listenerOptions?.once && e.type === "keydown" && elem.removeEventListener("click", proxListener, listenerOptions);
     listenerOptions?.once && e.type === "click" && elem.removeEventListener("keydown", proxListener, listenerOptions);
     listener(e);
@@ -202,3 +209,13 @@ export async function addStyleFromResource(key: ResourceKey & `css-${string}`) {
   }
   return false;
 }
+
+/** Copies the provided text to the clipboard and shows an error message for manual copying if the grant `GM.setClipboard` is not given. */
+export function copyToClipboard(text: Stringifiable) {
+  try {
+    GM.setClipboard(String(text));
+  }
+  catch(err) {
+    alert(t("copy_to_clipboard_error", String(text)));
+  }
+}