an app to share curated trails sidetrail.app
1

Configure Feed

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

1import { saveDraft, type DraftInput } from "./actions"; 2 3function hasContent(d: DraftInput): boolean { 4 return !!( 5 d.title.trim() || 6 d.description.trim() || 7 d.stops.some((s) => s.title.trim() || s.content?.trim() || s.buttonText?.trim() || s.external) 8 ); 9} 10 11export class DraftSaver { 12 private rkey: string; 13 private version: number; 14 private onWarning: (msg: string) => void; 15 16 private pending: DraftInput | null = null; 17 private lastSaved: string; 18 private saving = false; 19 20 constructor( 21 rkey: string, 22 version: number, 23 initial: DraftInput, 24 onWarning: (msg: string) => void, 25 ) { 26 this.rkey = rkey; 27 this.version = version; 28 this.lastSaved = JSON.stringify(initial); 29 this.onWarning = onWarning; 30 } 31 32 save(draft: DraftInput): void { 33 const json = JSON.stringify(draft); 34 if (json === this.lastSaved || !hasContent(draft)) return; 35 36 this.pending = draft; 37 this.flush(); 38 } 39 40 async saveNow(draft: DraftInput): Promise<void> { 41 this.pending = draft; 42 await this.flush(); 43 } 44 45 private async flush(): Promise<void> { 46 if (!this.pending || this.saving) return; 47 48 this.saving = true; 49 const draft = this.pending; 50 const json = JSON.stringify(draft); 51 52 try { 53 const result = await saveDraft(this.rkey, draft, this.version); 54 if (result.success) { 55 this.version = result.version; 56 this.lastSaved = json; 57 if (this.pending === draft) this.pending = null; 58 if (result.warning === "overwrote_newer") { 59 this.onWarning("saved, but overwrote changes from another device"); 60 } 61 } 62 } catch (e) { 63 console.error("Failed to save draft:", e); 64 } finally { 65 this.saving = false; 66 if (this.pending) this.flush(); 67 } 68 } 69}