zero-knowledge file sharing
13
fork

Configure Feed

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

lifetime max ttl check

Juliet 46547218 60ee66d3

+30 -7
+29 -6
web/src/pages/Upload.tsx
··· 14 14 const [burn, setBurn] = createSignal(false); 15 15 const [copied, setCopied] = createSignal(false); 16 16 const [maxFileSize, setMaxFileSize] = createSignal(0); 17 + const [maxTtl, setMaxTtl] = createSignal(""); 18 + const [expiryValue, setExpiryValue] = createSignal(""); 17 19 18 20 let fileInput!: HTMLInputElement; 19 - let expiryInput!: HTMLInputElement; 20 21 let activeXhr: XMLHttpRequest | null = null; 21 22 22 23 const RADIUS = 130; ··· 64 65 return !!f && !!max && f.size > max; 65 66 }); 66 67 68 + const parseDuration = (s: string): number | undefined => { 69 + const units: Record<string, number> = { s: 1, m: 60, h: 3600, d: 86400 }; 70 + const n = parseInt(s); 71 + const mult = units[s.trim().slice(-1)]; 72 + if (isNaN(n) || mult === undefined) return undefined; 73 + return n * mult; 74 + }; 75 + 76 + const expiryTooLong = createMemo(() => { 77 + const val = expiryValue(); 78 + const max = maxTtl(); 79 + if (!val || !max) return false; 80 + const valSec = parseDuration(val); 81 + const maxSec = parseDuration(max); 82 + if (valSec === undefined || maxSec === undefined) return false; 83 + return valSec > maxSec; 84 + }); 85 + 67 86 const statusText = () => { 68 87 if (encrypting()) return "encrypting\u2026"; 69 88 if (uploading()) return "uploading\u2026"; ··· 100 119 if (res.ok) { 101 120 const info = await res.json(); 102 121 setMaxFileSize(info.maxFileSize); 122 + if (info.maxTtl) { 123 + setMaxTtl(info.maxTtl); 124 + setExpiryValue(info.maxTtl); 125 + } 103 126 } 104 127 } catch {} 105 128 }); ··· 144 167 145 168 const formData = new FormData(); 146 169 formData.append("file", new Blob([ciphertext])); 147 - formData.append("expiresIn", expiryInput.value.trim()); 170 + formData.append("expiresIn", expiryValue().trim()); 148 171 formData.append("burnAfterRead", burn() ? "true" : "false"); 149 172 150 173 setEncrypting(false); ··· 200 223 return ( 201 224 <> 202 225 <div 203 - class="group relative mx-auto flex aspect-square w-[80vw] max-w-[500px] items-center justify-center" 226 + class="group relative mx-auto flex aspect-square w-[80vw] max-w-125 items-center justify-center" 204 227 onClick={() => !resultUrl() && fileInput.click()} 205 228 > 206 229 <svg ··· 392 415 <label class="text-muted flex items-center gap-3 select-none"> 393 416 lifetime 394 417 <input 395 - ref={expiryInput!} 396 418 type="text" 397 - value="24h" 419 + value={expiryValue()} 398 420 placeholder="30m, 24h, 7d" 399 - class="bg-surface border-border text-accent focus:border-accent w-28 rounded-md border px-2 py-1 text-center font-medium transition-colors outline-none" 421 + onInput={(e) => setExpiryValue(e.currentTarget.value)} 422 + class={`bg-surface text-accent w-28 rounded-md border px-2 py-1 text-center font-medium transition-colors outline-none ${expiryTooLong() ? "border-danger" : "border-border focus:border-accent"}`} 400 423 /> 401 424 </label> 402 425 <label
+1 -1
web/src/pages/View.tsx
··· 233 233 234 234 <Match when={contentType() === "binary"}> 235 235 <div class="bg-surface border-border rounded-lg border p-4 text-center"> 236 - <p class="mb-3 font-mono text-sm">{fileName()}</p> 236 + <p class="mb-3 text-sm">{fileName()}</p> 237 237 <button 238 238 class="bg-accent hover:bg-accent-hover w-full rounded-md border-none py-2.5 text-sm font-medium text-white transition-colors" 239 239 onClick={saveFile}