A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 2.4 kB View raw
1import type { Agent } from '@atproto/api'; 2import type { BlockNode } from '../blocks/render'; 3import { 4 attachBlobRefs, 5 buildGetBlobUrl, 6 type BlobRefJson, 7 type BlobUpload, 8} from '../media/blob'; 9import { isDataUrl, dataUrlToBlob } from './held-assets'; 10 11export interface PreparedPublishContent { 12 blocks: BlockNode[]; 13 coverImage?: BlobRefJson; 14} 15 16/** Upload one held `data:` URL to the PDS and return its portable blob ref + getBlob URL. */ 17async function uploadOne( 18 agent: Agent, 19 dataUrl: string, 20 did: string, 21 pdsUrl: string 22): Promise< BlobUpload > { 23 const blob = dataUrlToBlob( dataUrl ); 24 const res = await agent.uploadBlob( blob, { encoding: blob.type } ); 25 const out = res.data.blob; 26 const cid = out.ref.toString(); 27 return { 28 ref: { $type: 'blob', ref: { $link: cid }, mimeType: out.mimeType, size: out.size }, 29 url: buildGetBlobUrl( pdsUrl, did, cid ), 30 }; 31} 32 33/** 34 * Publish-time bridge for the writing-first flow: walk the block tree, upload every held 35 * (`data:`) image to the writer's PDS, and rewrite those blocks via `attachBlobRefs` so they 36 * carry `skypressBlob` + a portable getBlob URL (byte-identical to the eager path). External 37 * image URLs are left untouched. A held cover is uploaded into a `BlobRefJson` for the document 38 * record. Each distinct data URL uploads once. 39 */ 40export async function uploadHeldAssets( 41 agent: Agent, 42 input: { blocks: BlockNode[]; coverDataUrl: string | null; did: string; pdsUrl: string } 43): Promise< PreparedPublishContent > { 44 const { blocks, coverDataUrl, did, pdsUrl } = input; 45 46 // Collect every distinct held image URL in the tree (depth-first), upload once each. 47 const registry = new Map< string, BlobUpload >(); 48 const collect = ( nodes: BlockNode[] ): string[] => 49 nodes.flatMap( ( node ) => { 50 const url = node.attributes?.url; 51 const here = node.name === 'core/image' && isDataUrl( url ) ? [ url as string ] : []; 52 return [ ...here, ...collect( node.innerBlocks ?? [] ) ]; 53 } ); 54 55 for ( const url of new Set( collect( blocks ) ) ) { 56 registry.set( url, await uploadOne( agent, url, did, pdsUrl ) ); 57 } 58 59 const prepared = attachBlobRefs( blocks, ( url ) => registry.get( url ) ); 60 61 let coverImage: BlobRefJson | undefined; 62 if ( isDataUrl( coverDataUrl ) ) { 63 coverImage = ( await uploadOne( agent, coverDataUrl, did, pdsUrl ) ).ref; 64 } 65 66 return { blocks: prepared, coverImage }; 67}