/* global chrome, browser */ if (typeof browser === "undefined" && typeof chrome !== "undefined") { browser = chrome; } const PROMPT_HOST_ID = "__trackeepYoutubePromptHost"; let lastNotifiedVideoId = ""; let lastUrl = ""; function parseYouTubeVideo(url, title) { let parsed; try { parsed = new URL(url); } catch (_) { return null; } const host = parsed.hostname.toLowerCase(); const path = parsed.pathname || ""; let videoId = ""; if (host === "youtu.be") { videoId = path.slice(1).split("/")[0]; } else if (path.startsWith("/watch")) { videoId = parsed.searchParams.get("v") || ""; } else if (path.startsWith("/shorts/")) { videoId = path.split("/")[2] || ""; } else if (path.startsWith("/embed/")) { videoId = path.split("/")[2] || ""; } if (!videoId) { return null; } const cleanedTitle = (title || "").replace(/\s*-\s*YouTube$/i, "").trim(); return { videoId, url: `https://www.youtube.com/watch?v=${videoId}`, title: cleanedTitle || "YouTube video", }; } function sendMessage(message, callback) { try { browser.runtime.sendMessage(message, callback); } catch (_) { if (typeof callback === "function") { callback(undefined); } } } function closePrompt() { const existing = document.getElementById(PROMPT_HOST_ID); if (existing) { existing.remove(); } } function renderPrompt(video) { closePrompt(); const host = document.createElement("div"); host.id = PROMPT_HOST_ID; const shadow = host.attachShadow({ mode: "open" }); shadow.innerHTML = `
Trackeep ยท YouTube

Save this video to your Trackeep?

${escapeHtml(video.title)}

`; document.documentElement.appendChild(host); const saveBtn = shadow.getElementById("saveNow"); const openBtn = shadow.getElementById("openSaver"); const laterBtn = shadow.getElementById("later"); const statusEl = shadow.getElementById("status"); const setStatus = (message, type) => { statusEl.textContent = message || ""; statusEl.className = `status${type ? ` ${type}` : ""}`; }; saveBtn.addEventListener("click", () => { saveBtn.disabled = true; openBtn.disabled = true; laterBtn.disabled = true; setStatus("Saving to Trackeep...", ""); sendMessage({ type: "trackeep:youtube-save-request", video }, (response) => { if (browser.runtime && browser.runtime.lastError) { setStatus("Could not reach extension background worker.", "error"); } else if (response && response.ok) { setStatus("Saved successfully.", "ok"); setTimeout(closePrompt, 1200); } else { setStatus( response && response.error ? response.error : "Save failed. Open saver to review.", "error" ); saveBtn.disabled = false; openBtn.disabled = false; laterBtn.disabled = false; } }); }); openBtn.addEventListener("click", () => { sendMessage({ type: "trackeep:youtube-open-saver", video }, () => { closePrompt(); }); }); laterBtn.addEventListener("click", () => { sendMessage({ type: "trackeep:youtube-dismissed", videoId: video.videoId }, () => { closePrompt(); }); }); window.setTimeout(() => { closePrompt(); }, 18000); } function escapeHtml(value) { return String(value || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function detectAndNotify(force) { const video = parseYouTubeVideo(window.location.href, document.title); if (!video) { closePrompt(); return; } if (!force && video.videoId === lastNotifiedVideoId && window.location.href === lastUrl) { return; } lastNotifiedVideoId = video.videoId; lastUrl = window.location.href; sendMessage({ type: "trackeep:youtube-video-detected", video }, (response) => { if (browser.runtime && browser.runtime.lastError) { return; } if (response && response.showPrompt) { renderPrompt(video); } else { closePrompt(); } }); } function initDetection() { let previousHref = window.location.href; const check = () => { if (window.location.href !== previousHref) { previousHref = window.location.href; detectAndNotify(true); } }; window.addEventListener("yt-navigate-finish", () => detectAndNotify(true), true); window.addEventListener("popstate", () => detectAndNotify(true), true); document.addEventListener("visibilitychange", () => { if (!document.hidden) { detectAndNotify(false); } }); const observer = new MutationObserver(check); observer.observe(document.documentElement, { childList: true, subtree: true, }); window.setInterval(check, 1200); detectAndNotify(true); } initDetection();