71 lines
2.4 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
}
|