198 lines
4.5 KiB
JavaScript
Executable File
198 lines
4.5 KiB
JavaScript
Executable File
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 = `
|
|
<input type="text" name="author" placeholder="Your name (optional)">
|
|
<textarea name="content" required placeholder="Write a reply..."></textarea>
|
|
<button type="submit">Post reply</button>
|
|
`;
|
|
|
|
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(comment => {
|
|
comment.children = [];
|
|
byId[comment.id] = comment;
|
|
});
|
|
|
|
comments.forEach(comment => {
|
|
if (comment.parent_id) {
|
|
const parent = byId[comment.parent_id];
|
|
if (parent) parent.children.push(comment);
|
|
} else {
|
|
roots.push(comment);
|
|
}
|
|
});
|
|
|
|
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(comment => list.appendChild(createComment(comment)));
|
|
}
|
|
|
|
|
|
/* -----------------------------
|
|
* 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 window.orgAuth.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 event => {
|
|
event.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();
|
|
} 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();
|