import {
atUriOf,
datetimeOf,
didOf,
nsidOf,
type AtUri,
type Datetime,
type Did,
type GitOid,
type KnotOrigin,
type Rkey,
type RawResourceUri
} from "#lib/api/syntax.ts";
import { parseCanonicalResourceUri } from "@atcute/lexicons/syntax";
import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
import type { BobbinContext, XrpcRequestInit } from "#lib/api/client.ts";
import { jsonGet } from "#lib/api/_request.ts";
import { createCob, eraseCob, updateCob, type CobRecord, type CobRef } from "#lib/api/cob.ts";
import { filterBlobsFromMarkdown } from "#lib/api/markupBlobs.ts";
import type { Blob } from "@atcute/lexicons/interfaces";
import type { RecordView } from "#lib/api/records.ts";
import { deleteRecord, putRecord } from "#lib/api/write.ts";
import type * as OrgTangledFeedComment from "#lib/api/lexicons/types/org/tangled/feed/comment.ts";
import type * as OrgTangledFeedDefs from "#lib/api/lexicons/types/org/tangled/feed/defs.ts";
export type CommentRecord = OrgTangledFeedComment.Main;
export const COMMENT_COLLECTION = nsidOf("org.tangled.feed.comment");
// subject and replyTo have to name the current version or the knot refuses
export interface CommentDraft {
subject: CobRef;
replyTo?: CobRef;
text: string;
blobs?: readonly Blob[];
embed?: { repo: Did; commit: GitOid };
}
const commentRecord = (
{ subject, replyTo, text, blobs, embed }: CommentDraft,
createdAt = datetimeOf(new Date().toISOString())
): CommentRecord => ({
$type: "org.tangled.feed.comment",
subject: { $type: "com.atproto.repo.strongRef", ...subject },
replyTo,
body: {
$type: "org.tangled.markup.markdown",
text,
blobs: filterBlobsFromMarkdown(undefined, blobs, text)
},
...(embed
? {
embed: {
$type: "org.tangled.embed.commit",
repo: embed.repo,
commit: { $type: "org.tangled.git.oid", oid: embed.commit }
}
}
: {}),
createdAt
});
export const putComment = async (
agent: OAuthUserAgent,
rkey: Rkey,
draft: CommentDraft,
createdAt?: Datetime
): Promise<RecordView<CommentRecord> & { uri: RawResourceUri }> => {
const record = commentRecord(draft, createdAt);
const { uri, cid } = await putRecord(agent, COMMENT_COLLECTION, rkey, record);
return { uri, cid, value: record };
};
export const deleteComment = async (agent: OAuthUserAgent, rkey: Rkey): Promise<{ uri: AtUri }> => {
const uri = atUriOf(`at://${agent.sub}/${COMMENT_COLLECTION}/${rkey}`);
await deleteRecord(agent, COMMENT_COLLECTION, rkey);
return { uri };
};
export const createComment = async (
agent: OAuthUserAgent,
knot: KnotOrigin,
draft: CommentDraft
): Promise<CobRecord<CommentRecord>> => {
const record = commentRecord(draft);
const { repo } = parseCanonicalResourceUri(draft.subject.uri);
return {
...(await createCob(agent, knot, didOf(repo), COMMENT_COLLECTION, record)),
value: record
};
};
export const editComment = (
agent: OAuthUserAgent,
knot: KnotOrigin,
from: CobRef,
text: string,
blobs?: readonly Blob[]
): Promise<CobRecord<CommentRecord>> =>
updateCob<CommentRecord>(agent, knot, from, (current) => ({
...current,
body: {
...current.body,
text,
blobs: filterBlobsFromMarkdown(current.body.blobs, blobs, text)
}
}));
export const eraseComment = (
agent: OAuthUserAgent,
knot: KnotOrigin,
from: CobRef
): Promise<void> => eraseCob(agent, knot, from);
export interface CommentListPage {
cursor?: string | null;
items: OrgTangledFeedDefs.CommentView[];
total?: number;
}
export const listComments = (
ctx: BobbinContext,
subject: AtUri,
filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {},
init?: XrpcRequestInit
) => jsonGet<CommentListPage>(ctx, "org.tangled.feed.listComments", { subject, ...filter }, init);