This repository has no description
1<script module lang="ts">
2 export interface StringFormData {
3 filename: string;
4 description: string;
5 content: string;
6 }
7</script>
8
9<script lang="ts">
10 import { untrack } from "svelte";
11 import Button from "$lib/components/ui/Button.svelte";
12 import Spinner from "$lib/components/ui/Spinner.svelte";
13 import ErrorAlert from "$lib/components/ui/Error.svelte";
14 import ArrowUp from "$icon/arrow-up";
15 import X from "$icon/x";
16
17 interface Props {
18 mode?: "create" | "edit";
19 filename?: string;
20 description?: string;
21 content?: string;
22 onsubmit: (data: StringFormData) => Promise<void>;
23 oncancel?: () => void;
24 }
25
26 let {
27 mode = "create",
28 filename: initialFilename = "",
29 description: initialDescription = "",
30 content: initialContent = "",
31 onsubmit,
32 oncancel
33 }: Props = $props();
34
35 // seed once; the props can't change while the form is open.
36 let filename = $state(untrack(() => initialFilename));
37 let description = $state(untrack(() => initialDescription));
38 let content = $state(untrack(() => initialContent));
39 let isPublishing = $state(false);
40 let error = $state<string | null>(null);
41
42 const lineCount = $derived(content === "" ? 0 : content.split("\n").length);
43 const byteCount = $derived(new TextEncoder().encode(content).length);
44
45 const handleSubmit = async (e: SubmitEvent) => {
46 e.preventDefault();
47 isPublishing = true;
48 error = null;
49 try {
50 await onsubmit({ filename, description, content });
51 } catch (err) {
52 error = err instanceof Error ? err.message : "Failed to publish string";
53 } finally {
54 isPublishing = false;
55 }
56 };
57</script>
58
59<div class="rounded border border-border-default bg-background-default">
60 <form onsubmit={handleSubmit}>
61 <div class="flex flex-col gap-2 p-4">
62 <div class="flex flex-col gap-2 md:flex-row">
63 <input
64 type="text"
65 id="filename"
66 name="filename"
67 placeholder="Filename"
68 required
69 bind:value={filename}
70 disabled={isPublishing}
71 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"
72 />
73 <input
74 type="text"
75 id="description"
76 name="description"
77 placeholder="Description ..."
78 maxlength={280}
79 bind:value={description}
80 disabled={isPublishing}
81 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"
82 />
83 </div>
84
85 <textarea
86 id="content"
87 name="content"
88 placeholder="Paste your string here!"
89 required
90 rows={20}
91 spellcheck={false}
92 bind:value={content}
93 disabled={isPublishing}
94 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"
95 ></textarea>
96 </div>
97
98 <div class="flex items-center justify-between px-4 py-3">
99 <div class="text-sm text-foreground-subtle">
100 <span>{lineCount} line{lineCount !== 1 ? "s" : ""}</span>
101 <span class="px-1 select-none">·</span>
102 <span>{byteCount} byte{byteCount !== 1 ? "s" : ""}</span>
103 </div>
104
105 <div class="flex items-center gap-2">
106 {#if mode === "edit"}
107 <Button
108 type="button"
109 variant="default"
110 icon={X}
111 disabled={isPublishing}
112 onclick={oncancel}
113 >
114 Cancel
115 </Button>
116 {/if}
117 <Button
118 type="submit"
119 variant="primary"
120 icon={isPublishing ? Spinner : ArrowUp}
121 disabled={isPublishing}
122 >
123 Publish
124 </Button>
125 </div>
126 </div>
127
128 {#if error}
129 <div class="px-4 pb-4">
130 <ErrorAlert label={error} />
131 </div>
132 {/if}
133 </form>
134</div>