serve a static website from your pds

Refactor detail page

Changed files
+40 -15
src
lib
routes
~
sites
[name]
+13 -6
src/lib/atproto.ts
··· 6 export interface Bundle { 7 description?: string; 8 assets: Record<string, { $type: "blob"; ref: { $link: string }; mimeType: string; size: number }>; 9 createdAt: string; 10 } 11 ··· 55 }); 56 57 if (isXRPCErrorPayload(data)) throw new Error("couldn't load records"); 58 - return data; 59 } 60 61 - async updateBundle(rkey: string, description: string, files: File[]) { 62 - const bundle: Bundle = { description, assets: {}, createdAt: new Date().toISOString() }; 63 - 64 for (const file of files) { 65 if (!(file instanceof File)) continue; 66 ··· 68 if (isXRPCErrorPayload(data)) throw new Error("couldn't upload file"); 69 70 const filepath = file.webkitRelativePath?.replace(/^.+\//, "") ?? file.name; 71 - bundle.assets[filepath] = { 72 $type: "blob", 73 ref: data.blob.ref, 74 mimeType: file.type, ··· 76 }; 77 } 78 79 const { data } = await this.#client.post("com.atproto.repo.putRecord", { 80 input: { 81 repo: this.#did, 82 collection: "com.jakelazaroff.test", 83 rkey, 84 - record: bundle as any, 85 }, 86 }); 87 if (isXRPCErrorPayload(data)) throw new Error("couldn't deploy");
··· 6 export interface Bundle { 7 description?: string; 8 assets: Record<string, { $type: "blob"; ref: { $link: string }; mimeType: string; size: number }>; 9 + fallback?: { 10 + path: string; 11 + status?: number; 12 + }; 13 createdAt: string; 14 } 15 ··· 59 }); 60 61 if (isXRPCErrorPayload(data)) throw new Error("couldn't load records"); 62 + return data as any as Omit<typeof data, "value"> & { value: Bundle }; 63 } 64 65 + async uploadFiles(files: File[]) { 66 + const assets: Bundle["assets"] = {}; 67 for (const file of files) { 68 if (!(file instanceof File)) continue; 69 ··· 71 if (isXRPCErrorPayload(data)) throw new Error("couldn't upload file"); 72 73 const filepath = file.webkitRelativePath?.replace(/^.+\//, "") ?? file.name; 74 + assets[filepath] = { 75 $type: "blob", 76 ref: data.blob.ref, 77 mimeType: file.type, ··· 79 }; 80 } 81 82 + return assets; 83 + } 84 + 85 + async updateBundle(rkey: string, bundle: Bundle) { 86 const { data } = await this.#client.post("com.atproto.repo.putRecord", { 87 input: { 88 repo: this.#did, 89 collection: "com.jakelazaroff.test", 90 rkey, 91 + record: { ...(bundle as any), createdAt: new Date().toISOString() }, 92 }, 93 }); 94 if (isXRPCErrorPayload(data)) throw new Error("couldn't deploy");
+27 -9
src/routes/~/sites/[name]/+page.svelte
··· 7 8 let { params, data } = $props(); 9 10 - const atp = client(data.session); 11 12 const defaultDescription = "Uploaded from website"; 13 ··· 28 const form = e.currentTarget; 29 const formdata = new FormData(form); 30 31 - const rkey = formdata.get("rkey"); 32 - if (typeof rkey !== "string") throw new Error("invalid rkey"); 33 - 34 let description = formdata.get("description"); 35 if (typeof description !== "string" || !description) description = defaultDescription; 36 37 const files = formdata.getAll("files").filter(entry => entry instanceof File); 38 - await atp.updateBundle(rkey, description, files); 39 invalidate(`rkey:${rkey}`); 40 form.reset(); 41 } 42 </script> 43 ··· 93 <Icon name="toggle" /> 94 <span>Settings</span> 95 </h3> 96 - <form> 97 <fieldset> 98 <legend>Fallback</legend> 99 <label> 100 <span>path</span> 101 - <input name="fallback_path" /> 102 </label> 103 <label> 104 <span>200</span> 105 - <input type="radio" name="fallback_status" value="200" /> 106 </label> 107 <label> 108 <span>404</span> 109 - <input type="radio" name="fallback_status" value="404" /> 110 </label> 111 </fieldset> 112 <button>save</button>
··· 7 8 let { params, data } = $props(); 9 10 + let rkey = $derived(data.record.uri.split("/").at(-1) ?? ""); 11 + const atp = $derived(client(data.session)); 12 13 const defaultDescription = "Uploaded from website"; 14 ··· 29 const form = e.currentTarget; 30 const formdata = new FormData(form); 31 32 let description = formdata.get("description"); 33 if (typeof description !== "string" || !description) description = defaultDescription; 34 35 const files = formdata.getAll("files").filter(entry => entry instanceof File); 36 + const assets = await atp.uploadFiles(files); 37 + 38 + await atp.updateBundle(rkey, { ...(data.record.value as any), assets }); 39 invalidate(`rkey:${rkey}`); 40 form.reset(); 41 + } 42 + 43 + async function updateBundle(e: SubmitEvent & { currentTarget: HTMLFormElement }) { 44 + e.preventDefault(); 45 + const form = e.currentTarget; 46 + const formdata = new FormData(form); 47 + 48 + // todo: implement me 49 } 50 </script> 51 ··· 101 <Icon name="toggle" /> 102 <span>Settings</span> 103 </h3> 104 + <form onsubmit={updateBundle}> 105 <fieldset> 106 <legend>Fallback</legend> 107 <label> 108 <span>path</span> 109 + <input name="fallback_path" group={data.record.value.fallback?.path} /> 110 </label> 111 <label> 112 <span>200</span> 113 + <input 114 + type="radio" 115 + name="fallback_status" 116 + value="200" 117 + group={data.record.value.fallback?.status} 118 + /> 119 </label> 120 <label> 121 <span>404</span> 122 + <input 123 + type="radio" 124 + name="fallback_status" 125 + value="404" 126 + group={data.record.value.fallback?.status} 127 + /> 128 </label> 129 </fieldset> 130 <button>save</button>