This repository has no description
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>