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.

1import type { BlockNode } from '../blocks/render'; 2import { splitAssets, mergeAssets, type AssetSkeleton } from './held-assets'; 3import { 4 createIndexedDbAssetStore, 5 createMemoryAssetStore, 6 type AssetStore, 7} from './asset-store'; 8 9const DRAFT_KEY = 'skypress:write:draft'; 10const INTENT_KEY = 'skypress:write:publish-intent'; 11 12/** The editor content the writing-first flow persists across the OAuth redirect. */ 13export interface WriteDraft { 14 title: string; 15 lede: string; 16 blocks: BlockNode[]; 17 coverDataUrl: string | null; 18} 19 20interface StoredMeta { 21 title: string; 22 lede: string; 23 skeleton: AssetSkeleton; 24} 25 26export interface DraftStore { 27 save( draft: WriteDraft ): Promise< void >; 28 load(): Promise< WriteDraft | null >; 29 clear(): Promise< void >; 30 setPublishIntent(): void; 31 /** Reads the intent flag and clears it — true at most once per set. */ 32 consumePublishIntent(): boolean; 33} 34 35/** 36 * Persist the writing-first draft so it survives the full-page OAuth redirect. Light metadata 37 * and the token-skeletoned block tree go in `localStorage`; the heavy image bytes (data: URLs) 38 * go in the injected `AssetStore` (IndexedDB in the browser). `setPublishIntent` records that 39 * the writer hit Publish before the redirect, so the flow auto-resumes on return. 40 */ 41export function createDraftStore( opts: { assets?: AssetStore; storage?: Storage } = {} ): DraftStore { 42 const storage = opts.storage ?? window.localStorage; 43 const assets = opts.assets ?? createIndexedDbAssetStore(); 44 45 return { 46 async save( draft ) { 47 const { skeleton, assets: bytes } = splitAssets( draft.blocks, draft.coverDataUrl ); 48 await assets.clear(); 49 await assets.put( bytes ); 50 const meta: StoredMeta = { title: draft.title, lede: draft.lede, skeleton }; 51 storage.setItem( DRAFT_KEY, JSON.stringify( meta ) ); 52 }, 53 async load() { 54 const raw = storage.getItem( DRAFT_KEY ); 55 if ( ! raw ) { 56 return null; 57 } 58 let meta: StoredMeta; 59 try { 60 meta = JSON.parse( raw ) as StoredMeta; 61 } catch { 62 return null; 63 } 64 const bytes = await assets.getAll(); 65 const { blocks, coverDataUrl } = mergeAssets( meta.skeleton, bytes ); 66 return { title: meta.title, lede: meta.lede, blocks, coverDataUrl }; 67 }, 68 async clear() { 69 storage.removeItem( DRAFT_KEY ); 70 await assets.clear(); 71 }, 72 setPublishIntent() { 73 storage.setItem( INTENT_KEY, '1' ); 74 }, 75 consumePublishIntent() { 76 const had = storage.getItem( INTENT_KEY ) === '1'; 77 storage.removeItem( INTENT_KEY ); 78 return had; 79 }, 80 }; 81} 82 83export { createMemoryAssetStore };