your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { dev } from '$app/environment';
3 import { user } from '$lib/atproto';
4 import type { WebsiteData } from '$lib/types';
5 import { Button, Input, Navbar, Popover, Toggle, toast } from '@foxui/core';
6
7 let {
8 data,
9 linkValue = $bindable(),
10 newCard,
11 addLink,
12
13 showingMobileView = $bindable(),
14 isSaving = $bindable(),
15 hasUnsavedChanges,
16
17 save,
18
19 handleImageInputChange,
20 handleVideoInputChange,
21
22 showCardCommand
23 }: {
24 data: WebsiteData;
25 linkValue: string;
26 newCard: (type: string) => void;
27 addLink: (url: string) => void;
28
29 showingMobileView: boolean;
30
31 isSaving: boolean;
32 hasUnsavedChanges: boolean;
33
34 save: () => Promise<void>;
35
36 handleImageInputChange: (evt: Event) => void;
37 handleVideoInputChange: (evt: Event) => void;
38
39 showCardCommand: () => void;
40 } = $props();
41
42 let linkPopoverOpen = $state(false);
43
44 let imageInputRef: HTMLInputElement | undefined = $state();
45 let videoInputRef: HTMLInputElement | undefined = $state();
46
47 function getShareUrl() {
48 const base = typeof window !== 'undefined' ? window.location.origin : '';
49 const pagePath =
50 data.page && data.page !== 'blento.self' ? `/${data.page.replace('blento.', '')}` : '';
51 return `${base}/${data.handle}${pagePath}`;
52 }
53
54 async function copyShareLink() {
55 const url = getShareUrl();
56 await navigator.clipboard.writeText(url);
57 toast.success('Link copied to clipboard!');
58 }
59</script>
60
61<input
62 type="file"
63 accept="image/*"
64 onchange={handleImageInputChange}
65 class="hidden"
66 id="image-input"
67 multiple
68 bind:this={imageInputRef}
69/>
70
71<input
72 type="file"
73 accept="video/*"
74 onchange={handleVideoInputChange}
75 class="hidden"
76 multiple
77 bind:this={videoInputRef}
78/>
79
80{#if dev || (user.isLoggedIn && user.profile?.did === data.did)}
81 <Navbar
82 class={[
83 'dark:bg-base-900 bg-base-100 top-auto bottom-2 mx-4 mt-3 max-w-3xl rounded-full px-4 md:mx-auto lg:inline-flex',
84 !dev ? 'hidden' : ''
85 ]}
86 >
87 <div class="flex items-center gap-2">
88 <Button
89 size="iconLg"
90 variant="ghost"
91 class="backdrop-blur-none"
92 onclick={() => {
93 newCard('section');
94 }}
95 >
96 <svg
97 xmlns="http://www.w3.org/2000/svg"
98 viewBox="0 0 24 24"
99 fill="none"
100 stroke="currentColor"
101 stroke-width="2"
102 stroke-linecap="round"
103 stroke-linejoin="round"
104 ><path d="M6 12h12" /><path d="M6 20V4" /><path d="M18 20V4" /></svg
105 >
106 </Button>
107
108 <Button
109 size="iconLg"
110 variant="ghost"
111 class="backdrop-blur-none"
112 onclick={() => {
113 newCard('text');
114 }}
115 >
116 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
117 ><path
118 fill="none"
119 stroke="currentColor"
120 stroke-linecap="round"
121 stroke-linejoin="round"
122 stroke-width="2"
123 d="m15 16l2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16m-6.303-2h5.606M2 16l4.039-9.69a.5.5 0 0 1 .923 0L11 16m-7.696-3h6.392"
124 /></svg
125 >
126 </Button>
127
128 <Popover sideOffset={16} bind:open={linkPopoverOpen} class="bg-base-100 dark:bg-base-900">
129 {#snippet child({ props })}
130 <Button
131 size="iconLg"
132 variant="ghost"
133 class="backdrop-blur-none"
134 onclick={() => {
135 newCard('link');
136 }}
137 {...props}
138 >
139 <svg
140 xmlns="http://www.w3.org/2000/svg"
141 fill="none"
142 viewBox="-2 -2 28 28"
143 stroke-width="2"
144 stroke="currentColor"
145 >
146 <path
147 stroke-linecap="round"
148 stroke-linejoin="round"
149 d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
150 />
151 </svg>
152 </Button>
153 {/snippet}
154 <Input
155 spellcheck={false}
156 type="url"
157 bind:value={linkValue}
158 onkeydown={(event) => {
159 if (event.code === 'Enter') {
160 addLink(linkValue);
161 event.preventDefault();
162 }
163 }}
164 placeholder="Enter link"
165 />
166 <Button onclick={() => addLink(linkValue)} size="icon"
167 ><svg
168 xmlns="http://www.w3.org/2000/svg"
169 fill="none"
170 viewBox="0 0 24 24"
171 stroke-width="2"
172 stroke="currentColor"
173 class="size-6"
174 >
175 <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
176 </svg>
177 </Button>
178 </Popover>
179
180 <Button
181 size="iconLg"
182 variant="ghost"
183 class="backdrop-blur-none"
184 onclick={() => {
185 imageInputRef?.click();
186 }}
187 >
188 <svg
189 xmlns="http://www.w3.org/2000/svg"
190 fill="none"
191 viewBox="0 0 24 24"
192 stroke-width="2"
193 stroke="currentColor"
194 >
195 <path
196 stroke-linecap="round"
197 stroke-linejoin="round"
198 d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
199 />
200 </svg>
201 </Button>
202
203 {#if dev}
204 <Button
205 size="iconLg"
206 variant="ghost"
207 class="backdrop-blur-none"
208 onclick={() => {
209 videoInputRef?.click();
210 }}
211 >
212 <svg
213 xmlns="http://www.w3.org/2000/svg"
214 fill="none"
215 viewBox="0 0 24 24"
216 stroke-width="1.5"
217 stroke="currentColor"
218 >
219 <path
220 stroke-linecap="round"
221 stroke-linejoin="round"
222 d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z"
223 />
224 </svg>
225 </Button>
226 {/if}
227
228 <Button size="iconLg" variant="ghost" class="backdrop-blur-none" onclick={showCardCommand}>
229 <svg
230 xmlns="http://www.w3.org/2000/svg"
231 fill="none"
232 viewBox="0 0 24 24"
233 stroke-width="1.5"
234 stroke="currentColor"
235 >
236 <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
237 </svg>
238 </Button>
239 </div>
240 <div class="flex items-center gap-2">
241 <Toggle
242 class="hidden bg-transparent backdrop-blur-none lg:block dark:bg-transparent"
243 bind:pressed={showingMobileView}
244 >
245 <svg
246 xmlns="http://www.w3.org/2000/svg"
247 fill="none"
248 viewBox="0 0 24 24"
249 stroke-width="1.5"
250 stroke="currentColor"
251 class="size-6"
252 >
253 <path
254 stroke-linecap="round"
255 stroke-linejoin="round"
256 d="M10.5 1.5H8.25A2.25 2.25 0 0 0 6 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h7.5A2.25 2.25 0 0 0 18 20.25V3.75a2.25 2.25 0 0 0-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"
257 />
258 </svg>
259 </Toggle>
260 {#if hasUnsavedChanges}
261 <Button
262 disabled={isSaving}
263 onclick={async () => {
264 save();
265 }}>{isSaving ? 'Saving...' : 'Save'}</Button
266 >
267 {:else}
268 <Button onclick={copyShareLink}>
269 <svg
270 xmlns="http://www.w3.org/2000/svg"
271 fill="none"
272 viewBox="0 0 24 24"
273 stroke-width="1.5"
274 stroke="currentColor"
275 class="size-5"
276 >
277 <path
278 stroke-linecap="round"
279 stroke-linejoin="round"
280 d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
281 />
282 </svg>
283 Share
284 </Button>
285 {/if}
286 </div>
287 </Navbar>
288{/if}