diff --git a/src/content/ui/lifecycle.ts b/src/content/ui/lifecycle.ts index 2ed83e4..c5b7b57 100644 --- a/src/content/ui/lifecycle.ts +++ b/src/content/ui/lifecycle.ts @@ -6,6 +6,38 @@ const CONTAINER_ID = "ytpf-playlist-table"; let currentHandle: PlaylistTableHandle | null = null; +function findAnchor(): { parent: Element; before: Element | null } | null { + // Primary: before ytd-playlist-video-list-renderer (sibling) + const videoList = document.querySelector("ytd-playlist-video-list-renderer"); + if (videoList?.parentElement) { + return { parent: videoList.parentElement, before: videoList }; + } + + // Fallback: end of ytd-section-list-renderer > #contents + const sectionContents = document.querySelector( + "ytd-section-list-renderer > #contents", + ); + if (sectionContents) { + return { parent: sectionContents, before: null }; + } + + // Last resort: #primary + const primary = document.querySelector( + "ytd-two-column-browse-results-renderer > #primary", + ); + if (primary) { + return { parent: primary, before: null }; + } + + return null; +} + +function insertAt(el: HTMLElement, anchor: { parent: Element; before: Element | null }): void { + anchor.parent.insertBefore(el, anchor.before); +} + +let pendingObserver: MutationObserver | null = null; + export function mountTable(data: PlaylistData): void { unmountTable(); injectStyles(); @@ -14,32 +46,22 @@ export function mountTable(data: PlaylistData): void { const el = currentHandle.element; el.id = CONTAINER_ID; - // Primary: before ytd-playlist-video-list-renderer (sibling) - const videoList = document.querySelector("ytd-playlist-video-list-renderer"); - if (videoList?.parentElement) { - videoList.parentElement.insertBefore(el, videoList); + const anchor = findAnchor(); + if (anchor) { + insertAt(el, anchor); return; } - // Fallback: end of ytd-section-list-renderer > #contents - const sectionContents = document.querySelector( - "ytd-section-list-renderer > #contents", - ); - if (sectionContents) { - sectionContents.appendChild(el); - return; - } - - // Last resort: #primary - const primary = document.querySelector( - "ytd-two-column-browse-results-renderer > #primary", - ); - if (primary) { - primary.appendChild(el); - return; - } - - console.warn("[yt-playlist-features] Could not find anchor to mount table"); + // DOM not ready yet — wait for the anchor element to appear + pendingObserver = new MutationObserver(() => { + const a = findAnchor(); + if (a) { + pendingObserver!.disconnect(); + pendingObserver = null; + insertAt(el, a); + } + }); + pendingObserver.observe(document.body, { childList: true, subtree: true }); } export function appendToTable(newVideos: PlaylistVideo[]): void { @@ -51,6 +73,10 @@ export function setTableComplete(extractedCount: number): void { } export function unmountTable(): void { + if (pendingObserver) { + pendingObserver.disconnect(); + pendingObserver = null; + } currentHandle = null; document.getElementById(CONTAINER_ID)?.remove(); }