This commit is contained in:
+78
-21
@@ -37,12 +37,22 @@ const clearLogs = document.getElementById("clearLogs");
|
||||
const downloadLogs = document.getElementById("downloadLogs");
|
||||
const debugLogList = document.getElementById("debugLogList");
|
||||
const debugSummary = document.getElementById("debugSummary");
|
||||
const resultModal = document.getElementById("resultModal");
|
||||
const resultModalTitle = document.getElementById("resultModalTitle");
|
||||
const resultModalSource = document.getElementById("resultModalSource");
|
||||
const resultModalSnippet = document.getElementById("resultModalSnippet");
|
||||
const resultModalReason = document.getElementById("resultModalReason");
|
||||
const resultModalFrame = document.getElementById("resultModalFrame");
|
||||
const resultModalOpenExternal = document.getElementById("resultModalOpenExternal");
|
||||
const resultModalDownload = document.getElementById("resultModalDownload");
|
||||
const closeResultModal = document.getElementById("closeResultModal");
|
||||
|
||||
let pendingDownload = null;
|
||||
let cropStart = 0;
|
||||
let cropEnd = 0;
|
||||
let cropMax = 0;
|
||||
let activeThumb = null;
|
||||
let activeResultItem = null;
|
||||
const activePlatforms = new Set(["envato", "artgrid", "google video"]);
|
||||
const hlsInstances = new WeakMap();
|
||||
const debugEntries = [];
|
||||
@@ -319,13 +329,13 @@ function renderResults(results) {
|
||||
const image = node.querySelector("img");
|
||||
const previewVideo = node.querySelector(".preview-hover");
|
||||
const overlays = node.querySelectorAll(".preview-overlay");
|
||||
node.href = item.link;
|
||||
image.src = item.thumbnailUrl || "https://placehold.co/1280x720/0a0a0a/ffffff?text=Preview";
|
||||
image.alt = item.title;
|
||||
node.querySelector("h3").textContent = item.title;
|
||||
node.querySelector(".result-snippet").textContent = item.snippet || item.reason || item.source || "";
|
||||
node.querySelector(".result-reason").textContent = item.reason ? `AI note: ${item.reason}` : "";
|
||||
node.querySelector(".result-reason").textContent = item.reason ? `AI 노트: ${item.reason}` : "";
|
||||
node.querySelector(".source-badge").textContent = item.source;
|
||||
node.addEventListener("click", () => openResultModal(item));
|
||||
previewVideo.poster = item.thumbnailUrl || "";
|
||||
if (item.previewVideoUrl) {
|
||||
const mediaArea = node.querySelector(".relative");
|
||||
@@ -347,6 +357,53 @@ function renderResults(results) {
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareDirectDownload(targetUrl) {
|
||||
downloadResult.textContent = "checking duplicate history...";
|
||||
const dup = await api(`/api/history/check?url=${encodeURIComponent(targetUrl)}`);
|
||||
let force = false;
|
||||
if (dup.exists) {
|
||||
force = window.confirm("동일 URL 다운로드 이력이 있습니다. 계속 진행할까요?");
|
||||
if (!force) {
|
||||
downloadResult.textContent = "cancelled";
|
||||
return;
|
||||
}
|
||||
}
|
||||
pendingDownload = { url: targetUrl, force };
|
||||
downloadResult.textContent = "loading preview...";
|
||||
const preview = await api("/api/download/preview", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ url: targetUrl }),
|
||||
});
|
||||
openPreviewModal(preview);
|
||||
downloadResult.textContent = "preview loaded";
|
||||
}
|
||||
|
||||
function openResultModal(item) {
|
||||
activeResultItem = item;
|
||||
resultModalTitle.textContent = item.title || "Untitled";
|
||||
resultModalSource.textContent = item.source || "";
|
||||
resultModalSnippet.textContent = item.snippet || "원본 페이지에서 사용할 수 있는 설명이 없습니다.";
|
||||
resultModalReason.textContent = item.reason || "AI 노트가 없습니다.";
|
||||
resultModalFrame.src = item.link || "about:blank";
|
||||
resultModalOpenExternal.href = item.link || "#";
|
||||
const canDirectDownload = item.source === "Google Video" && item.link;
|
||||
resultModalDownload.classList.toggle("hidden", !canDirectDownload);
|
||||
resultModal.classList.remove("hidden");
|
||||
resultModal.classList.add("flex");
|
||||
logEvent("result:modal:open", { title: item.title, source: item.source, link: item.link });
|
||||
}
|
||||
|
||||
function closeResultViewer() {
|
||||
if (!resultModal.classList.contains("hidden")) {
|
||||
logEvent("result:modal:close", { title: activeResultItem?.title || "" });
|
||||
}
|
||||
activeResultItem = null;
|
||||
resultModalFrame.src = "about:blank";
|
||||
resultModal.classList.add("hidden");
|
||||
resultModal.classList.remove("flex");
|
||||
}
|
||||
|
||||
searchForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
setStatus("preparing search", 5);
|
||||
@@ -458,26 +515,8 @@ fileInput.addEventListener("change", async () => {
|
||||
|
||||
downloadForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
downloadResult.textContent = "checking duplicate history...";
|
||||
try {
|
||||
const dup = await api(`/api/history/check?url=${encodeURIComponent(downloadUrl.value)}`);
|
||||
let force = false;
|
||||
if (dup.exists) {
|
||||
force = window.confirm("동일 URL 다운로드 이력이 있습니다. 계속 진행할까요?");
|
||||
if (!force) {
|
||||
downloadResult.textContent = "cancelled";
|
||||
return;
|
||||
}
|
||||
}
|
||||
pendingDownload = { url: downloadUrl.value, force };
|
||||
downloadResult.textContent = "loading preview...";
|
||||
const preview = await api("/api/download/preview", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ url: downloadUrl.value }),
|
||||
});
|
||||
openPreviewModal(preview);
|
||||
downloadResult.textContent = "preview loaded";
|
||||
await prepareDirectDownload(downloadUrl.value);
|
||||
} catch (error) {
|
||||
downloadResult.textContent = error.message;
|
||||
logEvent("download:preview:error", { message: error.message, data: error.data || null });
|
||||
@@ -509,6 +548,24 @@ 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 || "" });
|
||||
}
|
||||
});
|
||||
previewModal.addEventListener("click", (event) => {
|
||||
if (event.target === previewModal) {
|
||||
closeModal();
|
||||
|
||||
+37
-4
@@ -149,8 +149,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="resultModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/80 px-4">
|
||||
<div class="flex h-[88vh] w-full max-w-7xl flex-col overflow-hidden rounded-3xl border border-white/10 bg-zinc-950 shadow-2xl">
|
||||
<div class="flex items-center justify-between border-b border-white/10 px-5 py-4">
|
||||
<div class="min-w-0">
|
||||
<p id="resultModalSource" class="text-xs uppercase tracking-[0.25em] text-zinc-500"></p>
|
||||
<h3 id="resultModalTitle" class="mt-1 truncate text-xl font-semibold text-white"></h3>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<a id="resultModalOpenExternal" target="_blank" rel="noreferrer" class="rounded-full border border-white/10 px-3 py-2 text-xs uppercase tracking-[0.2em] text-zinc-300">Open</a>
|
||||
<button id="resultModalDownload" type="button" class="hidden rounded-full border border-white/10 px-3 py-2 text-xs uppercase tracking-[0.2em] text-zinc-300">Direct Download</button>
|
||||
<button id="closeResultModal" type="button" class="rounded-full border border-white/10 px-3 py-2 text-xs uppercase tracking-[0.2em] text-zinc-300">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid min-h-0 flex-1 gap-0 lg:grid-cols-[1.5fr_0.85fr]">
|
||||
<div class="min-h-0 border-b border-white/10 lg:border-b-0 lg:border-r">
|
||||
<iframe id="resultModalFrame" class="h-full w-full bg-white" referrerpolicy="no-referrer"></iframe>
|
||||
</div>
|
||||
<div class="min-h-0 overflow-auto px-5 py-5">
|
||||
<div class="space-y-5">
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.25em] text-zinc-500">Source Summary</p>
|
||||
<p id="resultModalSnippet" class="mt-2 text-sm leading-7 text-zinc-300"></p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.25em] text-zinc-500">AI Note</p>
|
||||
<p id="resultModalReason" class="mt-2 whitespace-pre-wrap text-sm leading-7 text-zinc-200"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="searchCardTemplate">
|
||||
<a target="_blank" rel="noreferrer" class="group overflow-hidden rounded-3xl border border-white/10 bg-black/30 transition hover:border-white/30">
|
||||
<button type="button" class="group overflow-hidden rounded-3xl border border-white/10 bg-black/30 text-left transition hover:border-white/30">
|
||||
<div class="relative aspect-video overflow-hidden bg-zinc-900">
|
||||
<img class="h-full w-full object-cover transition duration-500 group-hover:scale-105" alt="" />
|
||||
<video class="preview-hover absolute inset-0 hidden h-full w-full object-cover" muted loop playsinline preload="none"></video>
|
||||
@@ -160,11 +193,11 @@
|
||||
<div class="space-y-2 p-5">
|
||||
<h3 class="line-clamp-2 text-base font-medium text-white"></h3>
|
||||
<p class="result-snippet line-clamp-3 text-sm text-zinc-400"></p>
|
||||
<p class="result-reason line-clamp-2 text-xs uppercase tracking-[0.12em] text-zinc-500"></p>
|
||||
<p class="result-reason line-clamp-2 text-xs tracking-[0.02em] text-zinc-500"></p>
|
||||
</div>
|
||||
</a>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script src="/app.js?v=20260313i" defer></script>
|
||||
<script src="/app.js?v=20260316a" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user