blog.js

← Back to explorer
static/js/blog.js
async function postJson(url, payload) {
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload || {})
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) {
    const msg = data && data.error ? data.error : "Request failed";
    throw new Error(msg);
  }
  return data;
}

function fmtDate(isoOrStr) {
  try {
    const d = new Date(isoOrStr);
    return d.toLocaleString();
  } catch (_) {
    return "";
  }
}

document.addEventListener("DOMContentLoaded", () => {
  const likeBtn = document.getElementById("likeBtn");
  const likeCount = document.getElementById("likeCount");
  const postIdEl = document.getElementById("postId");
  const commentForm = document.getElementById("commentForm");
  const commentError = document.getElementById("commentError");

  const postId = postIdEl ? postIdEl.value : null;

  if (likeBtn && likeCount && postId) {
    likeBtn.addEventListener("click", async () => {
      likeBtn.disabled = true;
      try {
        const data = await postJson(`/api/blog/${postId}/like`, {});
        likeCount.textContent = String(data.count);
        likeBtn.dataset.liked = data.liked ? "1" : "0";
        likeBtn.classList.toggle("is-liked", !!data.liked);
      } catch (e) {
        alert(e.message || "Could not update like");
      } finally {
        likeBtn.disabled = false;
      }
    });
  }

  const copyBtn = document.getElementById("copyLinkBtn");
  if (copyBtn) {
    copyBtn.addEventListener("click", async () => {
      try {
        await navigator.clipboard.writeText(window.location.href);

        // Create floating "Copied!" notification
        const toast = document.createElement("div");
        toast.className = "copy-toast";
        toast.textContent = "Copied!";
        document.body.appendChild(toast);

        // Position near the button
        const rect = copyBtn.getBoundingClientRect();
        toast.style.position = "fixed";
        toast.style.top = `${rect.top - 40}px`;
        toast.style.left = `${rect.left + rect.width / 2}px`;
        toast.style.transform = "translateX(-50%)";

        // Trigger animation
        setTimeout(() => toast.classList.add("show"), 10);

        // Remove after 1 second
        setTimeout(() => {
          toast.classList.remove("show");
          setTimeout(() => toast.remove(), 300);
        }, 1000);
      } catch (err) {
        console.error("Failed to copy:", err);
      }
    });
  }

  if (commentForm && postId) {
    commentForm.addEventListener("submit", async (e) => {
      e.preventDefault();
      commentError.textContent = "";

      const fd = new FormData(commentForm);
      const display_name = (fd.get("display_name") || "").toString();
      const content = (fd.get("content") || "").toString();

      const submitBtn = commentForm.querySelector("button[type='submit']");
      if (submitBtn) submitBtn.disabled = true;

      try {
        await postJson(`/api/blog/${postId}/comment`, { display_name, content });
        window.location.reload();
      } catch (err) {
        commentError.textContent = err.message || "Could not post comment";
      } finally {
        if (submitBtn) submitBtn.disabled = false;
      }
    });
  }
});