let pageSlug = null; /* ----------------------------- * DOM builders * ----------------------------- */ function showReplyForm(container, parentId) { if (container.querySelector(".reply-form")) return; const form = document.createElement("form"); form.className = "reply-form"; form.innerHTML = ` `; form.addEventListener("submit", async e => { e.preventDefault(); const author = form.author.value.trim(); const content = form.content.value.trim(); if (!content) return; const ok = await postComment(author, content, parentId); if (ok) loadComments(); else alert("Failed to post reply"); }); container.appendChild(form); } function createComment(comment) { const wrapper = document.createElement("div"); wrapper.className = "comment"; wrapper.dataset.id = comment.id; const author = document.createElement("strong"); author.textContent = comment.author || "Anonymous"; const body = document.createElement("p"); body.textContent = comment.content; const time = document.createElement("time"); time.className = "comment-date"; time.dateTime = comment.created_at; time.textContent = new Date(comment.created_at).toLocaleString(); const replyBtn = document.createElement("button"); replyBtn.className = "comment-reply"; replyBtn.textContent = "Reply"; replyBtn.onclick = () => showReplyForm(wrapper, Number(comment.id)); wrapper.append(author, body, time, replyBtn); if (comment.children?.length) { const replies = document.createElement("div"); replies.className = "comment-children"; comment.children.forEach(child => { replies.appendChild(createComment(child)); }); wrapper.appendChild(replies); } console.log("PARENT_ID RECEIVED:", comment.parent_id) return wrapper; } function buildCommentTree(comments) { const byId = {}; const roots = []; comments.forEach(c => { c.children = []; byId[c.id] = c; }); comments.forEach(c => { if (c.parent_id) { const parent = byId[c.parent_id]; if (parent) parent.children.push(c); } else { roots.push(c); } }); return roots; } /* ----------------------------- * Rendering * ----------------------------- */ function renderComments(comments) { const list = document.getElementById("comments-list"); list.innerHTML = ""; if (!comments.length) { const empty = document.createElement("p"); empty.className = "comments-empty"; empty.textContent = "No comments yet."; list.appendChild(empty); return; } const tree = buildCommentTree(comments); tree.forEach(c => list.appendChild(createComment(c))); } /* ----------------------------- * API * ----------------------------- */ async function fetchComments() { const res = await fetch(`/api/comments/${pageSlug}`); if (!res.ok) { console.error("Failed to fetch comments"); return []; } return await res.json(); } async function postComment(author, content, parentId = null) { const res = await fetch("/api/comments", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ page_slug: pageSlug, author: author || null, content, parent_id: parentId }) }); return res.ok; } /* ----------------------------- * Form handling * ----------------------------- */ function wireCommentForm() { const form = document.getElementById("comment-form"); form.addEventListener("submit", async e => { e.preventDefault(); const authorInput = form.querySelector("input[name='author']"); const textarea = form.querySelector("textarea[name='content']"); const author = authorInput.value.trim(); const content = textarea.value.trim(); if (!content) return; const ok = await postComment(author, content); if (ok) { textarea.value = ""; loadComments(); // same pattern as your Kanban board } else { alert("Failed to post comment"); } }); } /* ----------------------------- * Entry point * ----------------------------- */ async function loadComments() { const comments = await fetchComments(); renderComments(comments); } function initComments() { const section = document.getElementById("comments"); if (!section) return; pageSlug = section.dataset.slug; wireCommentForm(); loadComments(); } initComments();