Files
blog-eleventy-indiekit/js/comments.js
Ricardo 0c6229088c feat: rework single post view for better content hierarchy
- Add post-type header (Reply/Like/Repost/Bookmark/Note) for titleless posts
- Add left accent border to user content on interaction posts
- Collapse AI Usage box into a compact <details> summary line
- Collapse comments section when empty, auto-open when comments exist
- Collapse webmention send form behind <details> toggle

Confab-Link: http://localhost:8080/sessions/648a550c-4f65-46be-b9a9-6b7e0fd90751
2026-03-05 13:22:32 +01:00

152 lines
3.9 KiB
JavaScript

/**
* Client-side comments component (Alpine.js)
* Handles IndieAuth flow, comment submission, and display
*
* Registered via Alpine.data() so the component is available
* regardless of script loading order.
*/
document.addEventListener("alpine:init", () => {
Alpine.data("commentsSection", (targetUrl) => ({
targetUrl,
user: null,
meUrl: "",
commentText: "",
comments: [],
loading: true,
authLoading: false,
submitting: false,
statusMessage: "",
statusType: "info",
maxLength: 2000,
showForm: false,
async init() {
await this.checkSession();
await this.loadComments();
this.handleAuthReturn();
},
async checkSession() {
try {
const res = await fetch("/comments/api/session", {
credentials: "include",
});
if (res.ok) {
const data = await res.json();
if (data.user) this.user = data.user;
}
} catch {
// No session
}
},
handleAuthReturn() {
const params = new URLSearchParams(window.location.search);
const authError = params.get("auth_error");
if (authError) {
this.showStatus(`Authentication failed: ${authError}`, "error");
window.history.replaceState(
{},
"",
window.location.pathname + "#comments",
);
}
},
async loadComments() {
this.loading = true;
try {
const url = `/comments/api/comments?target=${encodeURIComponent(this.targetUrl)}`;
const res = await fetch(url);
if (res.ok) {
const data = await res.json();
this.comments = data.children || [];
}
} catch (e) {
console.error("[Comments] Load error:", e);
} finally {
this.loading = false;
}
},
async startAuth() {
this.authLoading = true;
try {
const res = await fetch("/comments/api/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
me: this.meUrl,
returnUrl: window.location.pathname + "#comments",
}),
credentials: "include",
});
if (!res.ok) {
const data = await res.json();
this.showStatus(data.error || "Auth failed", "error");
return;
}
const data = await res.json();
if (data.authUrl) {
window.location.href = data.authUrl;
}
} catch {
this.showStatus("Failed to start authentication", "error");
} finally {
this.authLoading = false;
}
},
async submitComment() {
if (!this.commentText.trim()) return;
this.submitting = true;
try {
const res = await fetch("/comments/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: this.commentText,
target: this.targetUrl,
}),
credentials: "include",
});
if (res.ok) {
const data = await res.json();
if (data.comment) {
this.comments.unshift(data.comment);
}
this.commentText = "";
this.showStatus("Comment posted!", "success");
} else {
const data = await res.json();
this.showStatus(data.error || "Failed to post", "error");
}
} catch {
this.showStatus("Error posting comment", "error");
} finally {
this.submitting = false;
}
},
signOut() {
document.cookie =
"comment_session=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
this.user = null;
this.showStatus("Signed out", "info");
},
showStatus(message, type = "info") {
this.statusMessage = message;
this.statusType = type;
setTimeout(() => {
this.statusMessage = "";
}, 5000);
},
}));
});