diff --git a/TODO.md b/TODO.md index 8130224..8d3300e 100644 --- a/TODO.md +++ b/TODO.md @@ -255,6 +255,16 @@ - backend debug broadcasts ## Recent Change Log +- Date: `2026-03-16` +- What changed: + - Bumped frontend asset version and added result-modal initialization guards to avoid click failures when browser cache serves mismatched HTML/JS. +- Why it changed: + - The result modal stopped opening after the modal markup refactor, which is consistent with stale cached frontend assets or partially initialized modal DOM. +- How it was verified: + - static code inspection of modal DOM/JS bindings +- What is still risky or incomplete: + - Browser cache behavior itself was not fully reproduced here, so a hard refresh may still be needed in an already-open client session. + - Date: `2026-03-16` - What changed: - Envato preview extraction now also inspects `INITIAL_HYDRATION_DATA` when direct page meta / JSON-LD preview URLs are missing. diff --git a/frontend/app.js b/frontend/app.js index ec8d0a9..7d2217e 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -49,6 +49,20 @@ const resultModalMediaFrame = document.getElementById("resultModalMediaFrame"); const resultModalVideo = document.getElementById("resultModalVideo"); const resultModalThumbnail = document.getElementById("resultModalThumbnail"); const resultModalEmbedNotice = document.getElementById("resultModalEmbedNotice"); +const resultModalReady = Boolean( + resultModal && + resultModalTitle && + resultModalSource && + resultModalSnippet && + resultModalReason && + resultModalOpenExternal && + resultModalDownload && + closeResultModal && + resultModalMediaFrame && + resultModalVideo && + resultModalThumbnail && + resultModalEmbedNotice, +); let pendingDownload = null; let cropStart = 0; @@ -349,6 +363,9 @@ function hideModal(element) { } function resetResultModalMedia() { + if (!resultModalReady) { + return; + } resultModalVideo.pause(); detachVideoSource(resultModalVideo); resultModalMediaFrame.style.aspectRatio = ""; @@ -420,6 +437,10 @@ async function prepareDirectDownload(targetUrl) { } function openResultModal(item) { + if (!resultModalReady) { + logEvent("result:modal:error", { message: "result modal is not fully initialized" }); + return; + } activeResultItem = item; resultModalTitle.textContent = item.title || "Untitled"; resultModalSource.textContent = item.source || ""; @@ -444,6 +465,9 @@ function openResultModal(item) { } function closeResultViewer() { + if (!resultModalReady) { + return; + } if (!resultModal.classList.contains("hidden")) { logEvent("result:modal:close", { title: activeResultItem?.title || "" }); } @@ -586,24 +610,27 @@ confirmDownload.addEventListener("click", async () => { }); closePreviewModal.addEventListener("click", closeModal); -closeResultModal.addEventListener("click", closeResultViewer); -resultModal.addEventListener("click", (event) => { - if (event.target === resultModal) { - closeResultViewer(); - } -}); -resultModalDownload.addEventListener("click", async () => { - if (!activeResultItem?.link) { - return; - } - try { - closeResultViewer(); - await prepareDirectDownload(activeResultItem.link); - } catch (error) { - downloadResult.textContent = error.message; - logEvent("download:preview:error", { message: error.message, data: error.data || null, source: activeResultItem?.source || "" }); - } -}); +if (resultModalReady) { + closeResultModal.addEventListener("click", closeResultViewer); + resultModal.addEventListener("click", (event) => { + if (event.target === resultModal) { + closeResultViewer(); + } + }); + resultModalDownload.addEventListener("click", async () => { + if (!activeResultItem?.link) { + return; + } + const currentItem = activeResultItem; + try { + closeResultViewer(); + await prepareDirectDownload(currentItem.link); + } catch (error) { + downloadResult.textContent = error.message; + logEvent("download:preview:error", { message: error.message, data: error.data || null, source: currentItem?.source || "" }); + } + }); +} previewModal.addEventListener("click", (event) => { if (event.target === previewModal) { closeModal(); @@ -663,16 +690,18 @@ previewThumbnail.addEventListener("load", () => { previewMediaFrame.style.aspectRatio = `${previewThumbnail.naturalWidth} / ${previewThumbnail.naturalHeight}`; } }); -resultModalVideo.addEventListener("loadedmetadata", () => { - if (resultModalVideo.videoWidth > 0 && resultModalVideo.videoHeight > 0) { - resultModalMediaFrame.style.aspectRatio = `${resultModalVideo.videoWidth} / ${resultModalVideo.videoHeight}`; - } -}); -resultModalThumbnail.addEventListener("load", () => { - if (!resultModalVideo.src && resultModalThumbnail.naturalWidth > 0 && resultModalThumbnail.naturalHeight > 0) { - resultModalMediaFrame.style.aspectRatio = `${resultModalThumbnail.naturalWidth} / ${resultModalThumbnail.naturalHeight}`; - } -}); +if (resultModalReady) { + resultModalVideo.addEventListener("loadedmetadata", () => { + if (resultModalVideo.videoWidth > 0 && resultModalVideo.videoHeight > 0) { + resultModalMediaFrame.style.aspectRatio = `${resultModalVideo.videoWidth} / ${resultModalVideo.videoHeight}`; + } + }); + resultModalThumbnail.addEventListener("load", () => { + if (!resultModalVideo.src && resultModalThumbnail.naturalWidth > 0 && resultModalThumbnail.naturalHeight > 0) { + resultModalMediaFrame.style.aspectRatio = `${resultModalThumbnail.naturalWidth} / ${resultModalThumbnail.naturalHeight}`; + } + }); +} for (const button of platformToggles) { button.addEventListener("click", () => { const platform = button.dataset.platformToggle; diff --git a/frontend/index.html b/frontend/index.html index 527f7c0..f420118 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -206,6 +206,6 @@ - +