feat: add inline recent comments and reply forms to post cards on feed page

Extract comment item building into `_build_comment_items` helper in database.py, import `get_recent_comments_by_post_uids` in feed router to attach up to 3 recent comments per post, add `post-card-comments` CSS class and `.comment-reply-form` margin rule, refactor `CommentManager` to use event delegation and toggle reply forms via a hidden template, move comment rendering into a reusable `_comment.html` macro, and include recent comments in `_post_card.html` with inline reply capability.
This commit is contained in:
2026-06-05 16:42:43 +00:00
parent 7d4612b2eb
commit 5a263cfebe
9 changed files with 155 additions and 88 deletions
+4
View File
@@ -711,6 +711,10 @@ img {
align-items: center;
}
.comment-reply-form {
margin-top: 0.75rem;
}
.comment-form > a {
flex-shrink: 0;
line-height: 0;
+9
View File
@@ -566,3 +566,12 @@
color: var(--text-muted);
font-size: 0.875rem;
}
.post-card-comments {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: 0.75rem;
}
+73 -21
View File
@@ -4,28 +4,80 @@ export class CommentManager {
}
initCommentReply() {
document.querySelectorAll("[data-action='reply']").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
const comment = btn.closest(".comment");
const commentForm = document.querySelector(".comment-form");
if (!comment || !commentForm) return;
const textarea = commentForm.querySelector("textarea");
if (!textarea) return;
let parentInput = commentForm.querySelector('input[name="parent_uid"]');
if (!parentInput) {
parentInput = document.createElement("input");
parentInput.type = "hidden";
parentInput.name = "parent_uid";
commentForm.appendChild(parentInput);
}
const commentBody = comment.querySelector(".comment-body");
if (commentBody && commentBody.dataset.commentUid) {
parentInput.value = commentBody.dataset.commentUid;
}
textarea.focus();
textarea.scrollIntoView({ behavior: "smooth" });
document.addEventListener("click", (e) => {
const btn = e.target.closest("[data-action='reply']");
if (!btn) return;
e.preventDefault();
this.toggleReplyForm(btn);
});
}
toggleReplyForm(btn) {
const comment = btn.closest(".comment");
if (!comment) return;
const body = comment.querySelector(".comment-body");
if (!body) return;
const existing = body.querySelector(":scope > .comment-reply-form");
if (existing) {
existing.remove();
return;
}
const template = document.getElementById("comment-reply-template");
if (!template) return;
const container = comment.closest(".post-card, .comments-section");
const source = container && container.querySelector(".comment-form:not(.comment-reply-form)");
if (!source) return;
const targetUid = source.querySelector('input[name="target_uid"]').value;
const targetType = source.querySelector('input[name="target_type"]').value;
const fragment = template.content.cloneNode(true);
const form = fragment.querySelector(".comment-form");
if (!form) return;
form.classList.add("comment-reply-form");
form.querySelector('input[name="target_uid"]').value = targetUid;
form.querySelector('input[name="target_type"]').value = targetType;
const parentInput = document.createElement("input");
parentInput.type = "hidden";
parentInput.name = "parent_uid";
parentInput.value = body.dataset.commentUid || "";
form.appendChild(parentInput);
const cancel = document.createElement("button");
cancel.type = "button";
cancel.className = "comment-action-btn comment-reply-cancel";
cancel.textContent = "Cancel";
cancel.addEventListener("click", () => form.remove());
form.querySelector(".comment-form-actions").appendChild(cancel);
const actions = body.querySelector(":scope > .comment-actions");
actions.insertAdjacentElement("afterend", form);
this.enhanceForm(form);
const textarea = form.querySelector("textarea");
if (textarea) textarea.focus();
}
enhanceForm(form) {
const enhancer = window.app && window.app.content;
if (enhancer) {
enhancer.initEmojiPickers();
enhancer.initMentionInputs();
enhancer.initAttachmentManagers();
}
const textarea = form.querySelector("textarea");
if (textarea) {
textarea.addEventListener("input", () => {
textarea.style.height = "auto";
textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
});
}
form.addEventListener("submit", () => {
const btn = form.querySelector("button[type='submit']");
if (btn) btn.disabled = true;
});
}
}