import type { PlaylistData, PlaylistVideo } from "../../types/playlist"; import { injectStyles } from "./styles"; import type { DetailUpdate } from "../../types/playlist"; import { renderPlaylistTable, loadColumnPrefs, type PlaylistTableHandle } from "./table-renderer"; const CONTAINER_ID = "ytpf-playlist-table"; let currentHandle: PlaylistTableHandle | null = null; function findAnchor(): { parent: Element; before: Element | null } | null { // Primary: top of ytd-item-section-renderer (before any header/content) const itemSection = document.querySelector( "ytd-section-list-renderer ytd-item-section-renderer", ); if (itemSection) { return { parent: itemSection, before: itemSection.firstElementChild }; } // 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 async function mountTable(data: PlaylistData): Promise { unmountTable(); injectStyles(); const prefs = await loadColumnPrefs(); currentHandle = renderPlaylistTable(data, prefs); const el = currentHandle.element; el.id = CONTAINER_ID; const anchor = findAnchor(); if (anchor) { insertAt(el, anchor); return; } // 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 { currentHandle?.appendVideos(newVideos); } export function setTableComplete(extractedCount: number): void { currentHandle?.setComplete(extractedCount); } export function updateTableDetails(updates: DetailUpdate[]): void { currentHandle?.updateDetails(updates); } export function unmountTable(): void { if (pendingObserver) { pendingObserver.disconnect(); pendingObserver = null; } currentHandle = null; document.getElementById(CONTAINER_ID)?.remove(); } // Auto-cleanup on SPA navigation document.addEventListener("yt-navigate-start", unmountTable);