← Files WixARCHIVED FILE
skills/wix-headless-templates/blog/project/src/wix/blog/comments.ts
4.6 KB · Oct 8, 2026 · 12:02 UTC
// Post comments (Wix Comments) — the only file that touches raw comment entities on this transport.
// Goes over `wixFetch` (the SDK's authenticated fetch — the same identity as every module call,
// member or anonymous visitor) rather than the `@wix/comments` module, which the blog template does
// not depend on. The rules and mappers live in ./comments-core (shared with the REST twin in
// templates/blog/rest/comments.ts). Who may comment (members only, or guests too) is the dashboard's
// "Who can comment" setting, enforced by the server — a denied create surfaces as isPermissionDenied.
// docs: https://dev.wix.com/docs/api-reference/crm/community/comments/comments/list-comments-by-resource.md
// docs: https://dev.wix.com/docs/api-reference/crm/community/comments/comments/create-comment.md
// docs: https://dev.wix.com/docs/api-reference/crm/community/comments/comments/delete-comment.md
import { wixFetch } from "../sdk";
import { fetchAuthorsByIds } from "./authors";
import {
commentAuthorIds,
createCommentBody,
listCommentsParams,
listRepliesParams,
rawCommentsOf,
toComment,
toCommentPage,
toCommentThread,
type CreateCommentOptions,
type ListCommentsOptions,
} from "./comments-core";
import type { Raw } from "./posts-core";
import type { BlogComment, CommentPage, CommentThread } from "./types";
export { type CreateCommentOptions, type ListCommentsOptions };
const COMMENTS = "/comments/v1/comments";
// One authenticated JSON call; a non-2xx becomes an Error carrying `status` and the application code,
// the shape posts-core's isNotFound / isPermissionDenied read.
async function call(path: string, init?: RequestInit): Promise<Raw> {
const res = await wixFetch(path, init);
if (res.status === 204) return {};
const json = (await res.json().catch(() => ({}))) as Raw;
if (!res.ok) {
throw Object.assign(new Error(json?.message || `${init?.method ?? "GET"} ${path} failed (${res.status}).`), {
status: res.status,
code: json?.details?.applicationError?.code,
details: json?.details,
});
}
return json;
}
const post = (path: string, body: unknown) =>
call(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
/**
* One page of top-level comments plus each one's reply seam (cursor, no replies yet).
* GET /comments/v1/comments/list-by-resource?appId=&contextId=&resourceId=&commentSort.order=&replySort.order=OLDEST_FIRST&cursorPaging.limit=10&cursorPaging.repliesLimit=0[&cursorPaging.cursor=]
*/
export async function fetchComments(o: ListCommentsOptions & { currentMemberId?: string | null }): Promise<CommentPage> {
const res = await call(`${COMMENTS}/list-by-resource?${new URLSearchParams(listCommentsParams(o))}`);
const authors = await fetchAuthorsByIds(commentAuthorIds(rawCommentsOf(res)));
return toCommentPage(res, { authors, currentMemberId: o.currentMemberId ?? null });
}
/**
* One page of a top-level comment's replies, from the cursor its seam (or a previous page) handed back.
* GET /comments/v1/comments/list-by-resource?appId=&contextId=&resourceId=&replySort.order=OLDEST_FIRST&cursorPaging.limit=10&cursorPaging.cursor=
*/
export async function fetchReplies(o: { referenceId: string; topLevelId: string; cursor: string; currentMemberId?: string | null }): Promise<CommentThread> {
const res = await call(`${COMMENTS}/list-by-resource?${new URLSearchParams(listRepliesParams(o))}`);
const authors = await fetchAuthorsByIds(commentAuthorIds(rawCommentsOf(res)));
return toCommentThread(res, { authors, currentMemberId: o.currentMemberId ?? null, topLevelId: o.topLevelId });
}
/**
* Post a comment (or a reply, with `parentId`) as the current identity. Throws — isPermissionDenied(e)
* means this viewer may not comment here (sign in). The answer may be PENDING when the owner moderates.
* POST /comments/v1/comments { comment: { appId, contextId, resourceId, content: { richContent }, parentComment?: { id } } }
*/
export async function createComment(o: CreateCommentOptions & { topLevelId?: string | null; currentMemberId?: string | null }): Promise<BlogComment> {
const res = await post(COMMENTS, createCommentBody(o));
const raw: Raw = res?.comment ?? {};
const authors = await fetchAuthorsByIds(commentAuthorIds([raw]));
return toComment(raw, { authors, currentMemberId: o.currentMemberId ?? null, topLevelId: o.topLevelId ?? o.parentId ?? null });
}
/** Delete the current member's own comment. DELETE /comments/v1/comments/{commentId} */
export async function deleteComment(commentId: string): Promise<void> {
await call(`${COMMENTS}/${encodeURIComponent(commentId)}`, { method: "DELETE" });
}
SHA-256: 8e78f5e2e3620b74de21fea2c17e8e5c130fb13de3d85aec93be3dc1308ebda2