Explorar o código

fix: all carousel anchors work now

Sv443 hai 1 ano
pai
achega
8d8cfb6409
Modificáronse 4 ficheiros con 46 adicións e 138 borrados
  1. 0 44
      src/events.ts
  2. 1 1
      src/features/layout.css
  3. 44 93
      src/features/layout.ts
  4. 1 0
      src/menu/menu_old.ts

+ 0 - 44
src/events.ts

@@ -14,10 +14,6 @@ export interface SiteEventsMap {
   queueChanged: (queueElement: HTMLElement) => void;
   /** Emitted whenever child nodes are added to or removed from the autoplay queue underneath the song queue */
   autoplayQueueChanged: (queueElement: HTMLElement) => void;
-  /** Emitted whenever carousel shelf containers are added or removed from their parent container */
-  carouselShelvesChanged: (elementMutations: Record<"addedNodes" | "removedNodes", NodeListOf<HTMLElement> | null>) => void;
-  /** Emitted once the home page is filled with content */
-  homePageLoaded: () => void;
 }
 
 /** EventEmitter instance that is used to detect changes to the site */
@@ -63,7 +59,6 @@ export async function initSiteEvents() {
     });
 
     //#SECTION home page observers
-    initHomeObservers();
 
     info("Successfully initialized SiteEvents observers");
 
@@ -76,42 +71,3 @@ export async function initSiteEvents() {
     error("Couldn't initialize SiteEvents observers due to an error:\n", err);
   }
 }
-
-/**
- * The home page might not exist yet if the site was accessed through any path like /watch directly.
- * This function will keep waiting for when the home page exists, then create the necessary MutationObservers.
- */
-async function initHomeObservers() {
-  let interval: NodeJS.Timer | undefined;
-
-  // hidden="" attribute is only present if the content of the page doesn't exist yet
-  // so this pauses execution until that attribute is removed
-  if(document.querySelector("ytmusic-browse-response#browse-page")?.hasAttribute("hidden")) {
-    await new Promise<void>((res) => {
-      interval = setInterval(() => {
-        if(!document.querySelector("ytmusic-browse-response#browse-page")?.hasAttribute("hidden")) {
-          clearInterval(interval as unknown as number);
-          res();
-        }
-      }, 50);
-    });
-  }
-
-  siteEvents.emit("homePageLoaded");
-
-  info("Initialized home page observers");
-
-  //#SECTION carousel shelves
-  const shelfContainerObs = new MutationObserver(([ { addedNodes, removedNodes } ]) => {
-    if(addedNodes.length > 0 || removedNodes.length > 0) {
-      info("Detected carousel shelf container change - added nodes:", addedNodes.length, "- removed nodes:", removedNodes.length);
-      siteEvents.emit("carouselShelvesChanged", { addedNodes, removedNodes } as Record<"addedNodes" | "removedNodes", NodeListOf<HTMLElement> | null>);
-    }
-  });
-
-  shelfContainerObs.observe(document.querySelector("#contents.ytmusic-section-list-renderer")!, {
-    childList: true,
-  });
-
-  observers.push(shelfContainerObs);
-}

+ 1 - 1
src/features/layout.css

@@ -200,7 +200,7 @@ ytmusic-responsive-list-item-renderer .left-items {
 }
 
 .bytm-carousel-shelf-anchor {
-  margin: 0 var(--ytmusic-responsive-list-item-thumbnail-margin-right, 16px) 0 0;
+  margin-right: 16px;
 }
 
 /* #MARKER volume slider */

+ 44 - 93
src/features/layout.ts

@@ -451,72 +451,67 @@ async function addQueueButtons(queueItem: HTMLElement) {
 
 //#MARKER anchor improvements
 
-
-// TODO: add to thumbnails in "songs" list on channel pages (/channel/$id)
-// TODO: add to thumbnails in playlists (/playlist?list=$id)
-// TODO:FIXME: only works for the first 7 items of each carousel shelf -> probably needs own mutation observer
-
-
 /** Adds anchors around elements and tweaks existing ones so songs are easier to open in a new tab */
 export function addAnchorImprovements() {
   //#SECTION carousel shelves
   try {
-    // home page
+    const preventDefault = (e: MouseEvent) => e.preventDefault();
 
-    /** Only adds anchor improvements for carousel shelves that contain the regular list-item-renderer, not the two-row-item-renderer */
-    const condCarouselImprovements = (el: HTMLElement) => {
-      const listItemRenderer = el.querySelector("ytmusic-responsive-list-item-renderer");
-      if(listItemRenderer) {
-        const itemsElem = el.querySelector<HTMLElement>("ul#items");
-        if(itemsElem) {
-          const improvedElems = improveCarouselAnchors(itemsElem);
-          improvedElems > 0 && log(`Added anchor improvements to ${improvedElems} carousel shelf ${autoPlural("item", improvedElems)}`);
-        }
+    /** Adds anchor improvements to &lt;ytmusic-responsive-list-item-renderer&gt; */
+    const addListItemAnchors = (items: NodeListOf<HTMLElement>) => {
+      for(const item of items) {
+        if(item.classList.contains("bytm-anchor-improved"))
+          return;
+
+        item.classList.add("bytm-anchor-improved");
+
+        const thumbnailElem = item.querySelector<HTMLElement>(".left-items");
+        const titleElem = item.querySelector<HTMLAnchorElement>(".title-column .title a");
+
+        if(!thumbnailElem || !titleElem)
+          return;
+
+        const anchorElem = document.createElement("a");
+        anchorElem.classList.add("bytm-anchor", "bytm-carousel-shelf-anchor");
+        anchorElem.href = titleElem?.href ?? "#";
+        anchorElem.target = "_self";
+        anchorElem.role = "button";
+
+        anchorElem.addEventListener("click", preventDefault);
+
+        addParent(thumbnailElem, anchorElem);
       }
     };
 
-    // initial three shelves aren't included in the event fire
-    onSelector("ytmusic-carousel-shelf-renderer", {
-      listener: () => {
-        const carouselShelves = document.body.querySelectorAll<HTMLElement>("ytmusic-carousel-shelf-renderer");
-        carouselShelves.forEach(condCarouselImprovements);
-      },
-    });
+    // home page
 
-    // every shelf that's loaded by scrolling:
-    siteEvents.on("carouselShelvesChanged", ({ addedNodes }) => {
-      if(addedNodes && addedNodes.length > 0)
-        addedNodes.forEach(condCarouselImprovements);
+    onSelector<HTMLElement>("#contents.ytmusic-section-list-renderer ytmusic-carousel-shelf-renderer ytmusic-responsive-list-item-renderer", {
+      continuous: true,
+      all: true,
+      listener: addListItemAnchors,
     });
 
     // related tab in /watch
 
-    // TODO: items are lazy-loaded so this needs to be done differently
-    // maybe the onSelectorExists feature can be expanded to conditionally support continuous checking & querySelectorAll
-    const relatedTabAnchorImprovements = (tabElem: HTMLElement) => {
-      const relatedCarouselShelves = tabElem?.querySelectorAll<HTMLElement>("ytmusic-carousel-shelf-renderer");
-      if(relatedCarouselShelves)
-        relatedCarouselShelves.forEach(condCarouselImprovements);
-    };
+    onSelector<HTMLElement>("ytmusic-tab-renderer[page-type=\"MUSIC_PAGE_TYPE_TRACK_RELATED\"] ytmusic-responsive-list-item-renderer", {
+      continuous: true,
+      all: true,
+      listener: addListItemAnchors,
+    });
 
-    const relatedTabContentsSelector = "ytmusic-section-list-renderer[page-type=\"MUSIC_PAGE_TYPE_TRACK_RELATED\"] #contents";
+    // playlists
 
-    onSelector("ytmusic-tab-renderer[page-type=\"MUSIC_PAGE_TYPE_TRACK_RELATED\"]", {
-      listener: (relatedTabContainer) => {
-        const relatedTabObserver = new MutationObserver(([ { addedNodes, removedNodes } ]) => {
-          if(addedNodes.length > 0 || removedNodes.length > 0)
-            relatedTabAnchorImprovements(document.querySelector<HTMLElement>(relatedTabContentsSelector)!);
-        });
-        relatedTabObserver.observe(relatedTabContainer, {
-          childList: true,
-        });
-      },
+    onSelector<HTMLElement>("#contents.ytmusic-section-list-renderer ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer", {
+      continuous: true,
+      all: true,
+      listener: addListItemAnchors,
     });
 
-    onSelector(relatedTabContentsSelector, {
-      listener: (relatedTabContents) => {
-        relatedTabAnchorImprovements(relatedTabContents);
-      },
+    // generic shelves
+    onSelector<HTMLElement>("#contents.ytmusic-section-list-renderer ytmusic-shelf-renderer ytmusic-responsive-list-item-renderer", {
+      continuous: true,
+      all: true,
+      listener: addListItemAnchors,
     });
   }
   catch(err) {
@@ -577,50 +572,6 @@ function improveSidebarAnchors(sidebarItems: NodeListOf<HTMLElement>) {
   });
 }
 
-/**
- * Actually adds the anchor improvements to carousel shelf items
- * @param itemsElement The container with the selector `ul#items` inside of each `ytmusic-carousel`
- */
-function improveCarouselAnchors(itemsElement: HTMLElement) {
-  if(itemsElement.classList.contains("bytm-anchors-improved"))
-    return 0;
-
-  let improvedElems = 0;
-  try {
-    const allListItems = itemsElement.querySelectorAll<HTMLElement>("ytmusic-responsive-list-item-renderer");
-    for(const listItem of allListItems) {
-      const thumbnailElem = listItem.querySelector<HTMLElement>(".left-items");
-      const titleElem = listItem.querySelector<HTMLAnchorElement>(".title-column yt-formatted-string.title a");
-
-      if(!thumbnailElem || !titleElem) {
-        error("Couldn't add carousel shelf anchor improvements because either the thumbnail or title element couldn't be found");
-        continue;
-      }
-
-      const thumbnailAnchor = document.createElement("a");
-      thumbnailAnchor.className = "bytm-carousel-shelf-anchor bytm-anchor";
-      thumbnailAnchor.href = titleElem.href;
-      thumbnailAnchor.target = "_self";
-      thumbnailAnchor.role = "button";
-
-      thumbnailAnchor.addEventListener("click", (e) => {
-        e.preventDefault();
-      });
-
-      addParent(thumbnailElem, thumbnailAnchor);
-      improvedElems++;
-    }
-  }
-  catch(err) {
-    error("Couldn't add anchor improvements due to error:", err);
-  }
-  finally {
-    itemsElement.classList.add("bytm-anchors-improved");
-  }
-
-  return improvedElems;
-}
-
 //#MARKER auto close toasts
 
 /** Closes toasts after a set amount of time */

+ 1 - 0
src/menu/menu_old.ts

@@ -394,6 +394,7 @@ export async function addMenu() {
 
   const versionElem = document.createElement("a");
   versionElem.id = "bytm-menu-version";
+  versionElem.role = "button";
   versionElem.title = `Version ${scriptInfo.version} - Build ${scriptInfo.lastCommit}`;
   versionElem.innerText = `v${scriptInfo.version} (${scriptInfo.lastCommit})`;