This repository has no description
0

Configure Feed

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

core / web / src / lib / components / strings / StringForm.svelte
4.3 kB 146 lines
1<script lang="ts"> 2 import { untrack } from "svelte"; 3 import { now as tidNow } from "@atcute/tid"; 4 import Button from "$lib/components/ui/Button.svelte"; 5 import Spinner from "$lib/components/ui/Spinner.svelte"; 6 import ErrorAlert from "$lib/components/ui/Error.svelte"; 7 import ArrowUp from "$icon/arrow-up"; 8 import X from "$icon/x"; 9 import { getAuth } from "$lib/auth.svelte"; 10 import { putString } from "$lib/api/strings"; 11 import type { StringRecord } from "$lib/api/records"; 12 13 interface Props { 14 mode?: "create" | "edit"; 15 rkey?: string; 16 createdAt?: string; 17 filename?: string; 18 description?: string; 19 content?: string; 20 onsaved: (rkey: string) => void; 21 oncancel?: () => void; 22 } 23 24 let { 25 mode = "create", 26 rkey, 27 createdAt, 28 filename: initialFilename = "", 29 description: initialDescription = "", 30 content: initialContent = "", 31 onsaved, 32 oncancel 33 }: Props = $props(); 34 35 const auth = getAuth(); 36 37 let filename = $state(untrack(() => initialFilename)); 38 let description = $state(untrack(() => initialDescription)); 39 let content = $state(untrack(() => initialContent)); 40 let isPublishing = $state(false); 41 let error = $state<string | null>(null); 42 43 const lineCount = $derived(content === "" ? 0 : content.split("\n").length); 44 const byteCount = $derived(new TextEncoder().encode(content).length); 45 46 const handleSubmit = async (e: SubmitEvent) => { 47 e.preventDefault(); 48 const agent = auth.agent; 49 if (!agent || isPublishing) return; 50 isPublishing = true; 51 error = null; 52 try { 53 const targetRkey = rkey ?? tidNow(); 54 const record: StringRecord = { 55 $type: "sh.tangled.string", 56 filename, 57 description, 58 contents: content, 59 createdAt: createdAt ?? new Date().toISOString() 60 }; 61 await putString(agent, targetRkey, record); 62 onsaved(targetRkey); 63 } catch (err) { 64 error = err instanceof Error ? err.message : "Failed to publish string"; 65 } finally { 66 isPublishing = false; 67 } 68 }; 69</script> 70 71<div class="rounded border border-border-default bg-background-default"> 72 <form onsubmit={handleSubmit}> 73 <div class="flex flex-col gap-2 p-4"> 74 <div class="flex flex-col gap-2 md:flex-row"> 75 <input 76 type="text" 77 id="filename" 78 name="filename" 79 placeholder="Filename" 80 required 81 bind:value={filename} 82 disabled={isPublishing} 83 class="rounded border border-border-default bg-background-default px-2 py-2 text-sm text-foreground-default placeholder:text-foreground-placeholder focus:ring-1 focus:ring-border-strong focus:outline-none disabled:opacity-50 md:max-w-64" 84 /> 85 <input 86 type="text" 87 id="description" 88 name="description" 89 placeholder="Description ..." 90 maxlength={280} 91 bind:value={description} 92 disabled={isPublishing} 93 class="flex-1 rounded border border-border-default bg-background-default px-2 py-2 text-sm text-foreground-default placeholder:text-foreground-placeholder focus:ring-1 focus:ring-border-strong focus:outline-none disabled:opacity-50" 94 /> 95 </div> 96 97 <textarea 98 id="content" 99 name="content" 100 placeholder="Paste your string here!" 101 required 102 rows={20} 103 spellcheck={false} 104 bind:value={content} 105 disabled={isPublishing} 106 class="w-full resize-y rounded border border-border-default bg-background-default px-2 py-2 font-mono text-sm text-foreground-default placeholder:text-foreground-placeholder focus:ring-1 focus:ring-border-strong focus:outline-none disabled:opacity-50" 107 ></textarea> 108 </div> 109 110 <div class="flex items-center justify-between px-4 py-3"> 111 <div class="text-sm text-foreground-subtle"> 112 <span>{lineCount} line{lineCount !== 1 ? "s" : ""}</span> 113 <span class="px-1 select-none">·</span> 114 <span>{byteCount} byte{byteCount !== 1 ? "s" : ""}</span> 115 </div> 116 117 <div class="flex items-center gap-2"> 118 {#if mode === "edit"} 119 <Button 120 type="button" 121 variant="default" 122 icon={X} 123 disabled={isPublishing} 124 onclick={oncancel} 125 > 126 Cancel 127 </Button> 128 {/if} 129 <Button 130 type="submit" 131 variant="primary" 132 icon={isPublishing ? Spinner : ArrowUp} 133 disabled={isPublishing} 134 > 135 Publish 136 </Button> 137 </div> 138 </div> 139 140 {#if error} 141 <div class="px-4 pb-4"> 142 <ErrorAlert label={error} /> 143 </div> 144 {/if} 145 </form> 146</div>