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:
@@ -711,6 +711,10 @@ img {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comment-reply-form {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.comment-form > a {
|
||||
flex-shrink: 0;
|
||||
line-height: 0;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user