Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import { ok } from "@atcute/client";import { CODEC_RAW, create, toString } from "@atcute/cid";import { mainSchema as uploadBlobSchema } from "@atcute/atproto/types/repo/uploadBlob";import { isBlob, type Blob } from "@atcute/lexicons/interfaces";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { createClient, mintServiceAuth } from "#lib/auth/agent.ts";import { buildUrl, toResponseError } from "#lib/api/_request.ts";import type { BobbinContext } from "#lib/api/client.ts";import { resolveMiniDoc } from "#lib/api/identity.ts";import { nsidOf, type Did } from "#lib/api/syntax.ts";import type * as OrgTangledMarkupMarkdown from "#lib/api/lexicons/types/org/tangled/markup/markdown.ts";
const UPLOAD_BLOB = nsidOf("com.atproto.repo.uploadBlob");
// an image dropped into the editor, linked by cid before it's uploadedexport interface PendingImage { cid: string; file: File; // local preview url url: string;}
// pre-computed CIDexport const blobCidOf = async (file: File): Promise<string> => toString(await create(CODEC_RAW, new Uint8Array(await file.arrayBuffer())));
export const uploadBlob = async ( agent: OAuthUserAgent, bobbin: BobbinContext, owner: Did, file: File): Promise<Blob> => { if (owner === agent.sub) { const { blob } = await ok(createClient(agent).call(uploadBlobSchema, { input: file })); if (!isBlob(blob)) throw new Error("The PDS answered with a legacy blob."); return blob; } const [{ pds }, token] = await Promise.all([ resolveMiniDoc(bobbin, owner), mintServiceAuth(agent, { aud: owner, lxm: UPLOAD_BLOB }) ]); const response = await fetch(buildUrl(pds, UPLOAD_BLOB), { method: "POST", headers: { "content-type": file.type, accept: "application/json", authorization: `Bearer ${token}` }, body: file }); if (!response.ok) throw await toResponseError(response); const { blob } = (await response.json()) as { blob?: unknown }; if (!isBlob(blob)) throw new Error("The PDS answered with a malformed blob."); return blob;};
export const uploadPending = async ( agent: OAuthUserAgent, bobbin: BobbinContext, owner: Did, pending: readonly PendingImage[], text: string): Promise<Blob[]> => { const linked = [...new Map(pending.map((image) => [image.cid, image])).values()].filter( (image) => text.includes(image.cid) ); return Promise.all( linked.map(async ({ cid, file }) => { const blob = await uploadBlob(agent, bobbin, owner, file); if (blob.ref.$link !== cid) throw new Error(`"${file.name}" was stored as ${blob.ref.$link}, not ${cid}.`); return blob; }) );};
type MarkdownBlob = NonNullable<OrgTangledMarkupMarkdown.Main["blobs"]>[number];
export const filterBlobsFromMarkdown = ( existing: readonly MarkdownBlob[] | undefined, added: readonly Blob[] | undefined, text: string): MarkdownBlob[] | undefined => { const seen = new Set<string>(); const pinned = [ ...(existing ?? []), ...(added ?? []).map((image) => ({ $type: "org.tangled.markup.embed.image" as const, image })) ].filter((embed) => { const blob = "image" in embed ? embed.image : embed.video; const cid = isBlob(blob) ? blob.ref.$link : blob.cid; if (seen.has(cid) || !text.includes(cid)) return false; seen.add(cid); return true; }); return pinned.length ? pinned : undefined;};