Files
frontend-helper/src/lib/comment-client.ts
T

71 lines
2.4 KiB
TypeScript

// Browser-side comment calls. Each throws CommentError naming the action.
import { appendComment, type CommentItem } from "./comment-model";
export type CommentAction = "load" | "post";
export class CommentError extends Error {
constructor(public readonly action: CommentAction, public readonly signedOut: boolean) {
super(signedOut ? "sign_in" : `comment_${action}_failed`);
}
}
export function commentErrorText(action: CommentAction): string {
return action === "load" ? "Could not load comments." : "Could not post comment.";
}
async function parse(res: Response, action: CommentAction): Promise<Record<string, unknown>> {
if (res.status === 401) throw new CommentError(action, true);
if (!res.ok) throw new CommentError(action, false);
try {
return (await res.json()) as Record<string, unknown>;
} catch {
throw new CommentError(action, false);
}
}
export async function loadComments(postId: string, fetchImpl: typeof fetch = fetch): Promise<CommentItem[]> {
let res: Response;
try {
res = await fetchImpl(`/api/posts/${encodeURIComponent(postId)}/comments`, { credentials: "same-origin" });
} catch {
throw new CommentError("load", false);
}
const b = await parse(res, "load");
return Array.isArray(b.comments) ? (b.comments as CommentItem[]) : [];
}
/** Post a comment and return the list as it should now read. Throws and returns nothing on failure. */
export async function postComment(
postId: string,
text: string,
current: CommentItem[],
fetchImpl: typeof fetch = fetch,
): Promise<CommentItem[]> {
const content = text.trim();
if (!content) return current;
let res: Response;
try {
res = await fetchImpl(`/api/posts/${encodeURIComponent(postId)}/comments`, {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content }),
});
} catch {
throw new CommentError("post", false);
}
const b = await parse(res, "post");
const made = b.comment as CommentItem | null | undefined;
if (made && typeof made === "object") return appendComment(current, made);
// The feed did not return the new comment; read the list back.
try {
return await loadComments(postId, fetchImpl);
} catch {
return appendComment(current, {
key: `local#${current.length}`, id: null, authorName: "You",
createdAt: new Date().toISOString(), removed: false, content,
});
}
}