import type { LinkEntry } from "../../common/lib.ts"; import { getLinks, createLink, updateLink, deleteLink, login } from "./api.ts"; import { Toast } from "./toast.ts"; const toast = new Toast(); const linkList = document.getElementById("link-list")!; const editor = document.getElementById("editor")!; const form = document.getElementById("link-form") as HTMLFormElement; const saveBtn = document.getElementById("save-btn") as HTMLButtonElement; const statsEl = document.getElementById("stats")!; const ogPreview = document.getElementById("og-preview")!; let currentEditing: LinkEntry | null = null; let tempIdCounter = 0; async function renderList() { linkList.innerHTML = ""; const res = await getLinks(); if (!res.success) { toast.show(res.error || "Failed to fetch links."); return; } res.data?.data?.forEach((link) => { const el = document.createElement("button"); el.className = "btn text-left flex justify-between items-center"; el.textContent = `${link.short || "(empty)"} → ${link.target || "(empty)"}`; el.addEventListener("click", () => editLink(link)); linkList.appendChild(el); }); } function editLink(link: LinkEntry) { currentEditing = link; editor.classList.remove("hidden"); (document.getElementById("link-target") as HTMLInputElement).value = link.target; (document.getElementById("link-short") as HTMLInputElement).value = link.short; (document.getElementById("og-title") as HTMLInputElement).value = link.ogTitle || ""; (document.getElementById("og-desc") as HTMLTextAreaElement).value = link.ogDesc || ""; (document.getElementById("og-image") as HTMLInputElement).value = link.ogImage || ""; updateStats(); updateSaveState(); updateOGPreview(); } function updateSaveState() { const end = (document.getElementById("link-target") as HTMLInputElement).value.trim(); const short = (document.getElementById("link-short") as HTMLInputElement).value.trim(); saveBtn.disabled = !(end && short); } function updateStats() { if (!currentEditing) return; statsEl.textContent = `Clicks: ${currentEditing?.clicks ?? 0}`; } document.getElementById("new-entry")!.addEventListener("click", () => { const tempLink: LinkEntry = { id: `temp-${tempIdCounter++}`, target: "", short: "", clicks: 0 }; editLink(tempLink); }); form.addEventListener("input", () => { updateSaveState(); updateOGPreview(); }); form.addEventListener("submit", async (e) => { e.preventDefault(); if (!currentEditing) return; const updatedLink: Partial = { target: (document.getElementById("link-target") as HTMLInputElement).value, short: (document.getElementById("link-short") as HTMLInputElement).value, ogTitle: (document.getElementById("og-title") as HTMLInputElement).value, ogDesc: (document.getElementById("og-desc") as HTMLTextAreaElement).value, ogImage: (document.getElementById("og-image") as HTMLInputElement).value, }; if (currentEditing.id.startsWith("temp-")) { const res = await createLink(updatedLink); if (!res.success) { toast.show(res.error || "Failed to create link."); return; } currentEditing = res.data?.data!; } else { const res = await updateLink(currentEditing.id, updatedLink); if (!res.success) { toast.show(res.error || "Failed to update link."); return; } currentEditing = res.data?.data!; } await renderList(); updateSaveState(); updateStats(); }); document.getElementById("delete-btn")!.addEventListener("click", async () => { if (!currentEditing) return; const res = await deleteLink(currentEditing.id); if (!res.success) { toast.show(res.error || "Failed to delete link."); return; } currentEditing = null; editor.classList.add("hidden"); await renderList(); }); function updateOGPreview() { if (!currentEditing) return; const title = (document.getElementById("og-title") as HTMLInputElement).value.trim(); const desc = (document.getElementById("og-desc") as HTMLTextAreaElement).value.trim(); const img = (document.getElementById("og-image") as HTMLInputElement).value.trim(); const hasTitle = title.length > 0; const hasDesc = desc.length > 0; const hasText = hasTitle || hasDesc; const hasImg = img.length > 0; if (!hasTitle && !hasDesc && !hasImg) { ogPreview.innerHTML = ""; return; } if (!hasText && hasImg) { ogPreview.innerHTML = `
`; return; } if (!hasImg && hasText) { ogPreview.innerHTML = `
${ hasTitle ? `

${title}

` : "" } ${ hasDesc ? `

${desc}

` : "" }
`; return; } ogPreview.innerHTML = `
OG Image
${ hasTitle ? `

${title}

` : "" } ${ hasDesc ? `

${desc}

` : "" }
`; } (async () => { const res = await login(localStorage.password); if (!res.success) { toast.show(res.error || "Login failed."); localStorage.removeItem("password"); location.href = "/"; return; } await renderList(); })();