This repository has no description
1<script lang="ts">
2 import { goto } from "$app/navigation";
3 import { ok } from "@atcute/client";
4 import { mainSchema as createRecordSchema } from "@atcute/atproto/types/repo/createRecord";
5 import type { Nsid } from "@atcute/lexicons/syntax";
6 import { getAuth } from "$lib/auth.svelte";
7 import { createClient } from "$lib/auth/agent";
8 import { rkeyFromUri } from "$lib/api/uri";
9 import Button from "$lib/components/ui/Button.svelte";
10 import Spinner from "$lib/components/ui/Spinner.svelte";
11 import ErrorAlert from "$lib/components/ui/Error.svelte";
12 import ArrowUp from "$icon/arrow-up";
13
14 const auth = getAuth();
15 const user = $derived(auth.currentUser);
16
17 let filename = $state("");
18 let description = $state("");
19 let content = $state("");
20 let isPublishing = $state(false);
21 let error = $state<string | null>(null);
22
23 const lineCount = $derived(content === "" ? 0 : content.split("\n").length);
24 const byteCount = $derived(new TextEncoder().encode(content).length);
25
26 const handleSubmit = async (e: SubmitEvent) => {
27 e.preventDefault();
28 if (!auth.agent) return;
29 isPublishing = true;
30 error = null;
31 try {
32 const rpc = createClient(auth.agent);
33 const result = await ok(
34 rpc.call(createRecordSchema, {
35 input: {
36 repo: auth.agent.sub,
37 collection: "sh.tangled.string" as Nsid,
38 record: {
39 $type: "sh.tangled.string",
40 filename,
41 description,
42 contents: content,
43 createdAt: new Date().toISOString()
44 }
45 }
46 })
47 );
48 const rkey = rkeyFromUri(result.uri);
49 await goto(`/strings/${user?.handle}/${rkey}`);
50 } catch (err) {
51 error = err instanceof Error ? err.message : "Failed to publish string";
52 } finally {
53 isPublishing = false;
54 }
55 };
56</script>
57
58<svelte:head>
59 <title>New string · Tangled</title>
60</svelte:head>
61
62<div class="mx-auto w-full max-w-screen-lg px-4 py-12">
63 <div class="px-2 py-2">
64 <h1 class="text-xl font-bold text-foreground-default">Create a new string</h1>
65 <p class="text-sm text-foreground-subtle">Store and share code snippets with ease.</p>
66 </div>
67
68 <div class="mt-4 rounded border border-border-default bg-background-default">
69 <form onsubmit={handleSubmit}>
70 <div class="flex flex-col gap-2 p-4">
71 <div class="flex flex-col gap-2 md:flex-row">
72 <input
73 type="text"
74 id="filename"
75 name="filename"
76 placeholder="Filename"
77 required
78 bind:value={filename}
79 disabled={isPublishing}
80 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"
81 />
82 <input
83 type="text"
84 id="description"
85 name="description"
86 placeholder="Description ..."
87 maxlength={280}
88 bind:value={description}
89 disabled={isPublishing}
90 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"
91 />
92 </div>
93
94 <textarea
95 id="content"
96 name="content"
97 placeholder="Paste your string here!"
98 required
99 rows={20}
100 spellcheck={false}
101 bind:value={content}
102 disabled={isPublishing}
103 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"
104 ></textarea>
105 </div>
106
107 <div class="flex items-center justify-between px-4 py-3">
108 <div class="text-sm text-foreground-subtle">
109 <span>{lineCount} line{lineCount !== 1 ? "s" : ""}</span>
110 <span class="px-1 select-none">·</span>
111 <span>{byteCount} byte{byteCount !== 1 ? "s" : ""}</span>
112 </div>
113
114 <Button
115 type="submit"
116 variant="primary"
117 icon={isPublishing ? Spinner : ArrowUp}
118 disabled={isPublishing}
119 >
120 Publish
121 </Button>
122 </div>
123
124 {#if error}
125 <div class="px-4 pb-4">
126 <ErrorAlert label={error} />
127 </div>
128 {/if}
129 </form>
130 </div>
131</div>