your personal website on atproto - mirror blento.app
at event-descriptions-markdown-support 157 lines 4.6 kB view raw
1<script lang="ts"> 2 import type { WebsiteData } from '$lib/types'; 3 import { getImage, compressImage, getProfilePosition } from '$lib/helper'; 4 import PlainTextEditor from '$lib/components/PlainTextEditor.svelte'; 5 import MarkdownTextEditor from '$lib/components/MarkdownTextEditor.svelte'; 6 import { Avatar } from '@foxui/core'; 7 import MadeWithBlento from './MadeWithBlento.svelte'; 8 import Pronouns from './Pronouns.svelte'; 9 10 let { data = $bindable(), hideBlento = false }: { data: WebsiteData; hideBlento?: boolean } = 11 $props(); 12 13 let fileInput: HTMLInputElement; 14 let isHoveringAvatar = $state(false); 15 16 async function handleAvatarChange(event: Event) { 17 const target = event.target as HTMLInputElement; 18 const file = target.files?.[0]; 19 if (!file) return; 20 21 try { 22 const compressedBlob = await compressImage(file); 23 const objectUrl = URL.createObjectURL(compressedBlob); 24 25 data.publication.icon = { 26 blob: compressedBlob, 27 objectUrl 28 } as any; 29 30 data = { ...data }; 31 } catch (error) { 32 console.error('Failed to process image:', error); 33 } 34 } 35 36 function getAvatarUrl(): string | undefined { 37 const customIcon = getImage(data.publication, data.did, 'icon'); 38 if (customIcon) return customIcon; 39 return data.profile.avatar; 40 } 41 42 function handleFileInputClick() { 43 fileInput.click(); 44 } 45 46 let profilePosition = $derived(getProfilePosition(data)); 47 48 function onTextUpdate() { 49 data = { ...data }; 50 } 51</script> 52 53<div 54 class={[ 55 'relative mx-auto flex max-w-lg flex-col justify-between px-8', 56 profilePosition === 'side' 57 ? '@5xl/wrapper:fixed @5xl/wrapper:h-screen @5xl/wrapper:w-1/4 @5xl/wrapper:max-w-none @5xl/wrapper:px-12' 58 : '@5xl/wrapper:max-w-4xl @5xl/wrapper:px-12' 59 ]} 60> 61 <div 62 class={[ 63 'flex flex-col gap-4 pt-16 pb-4', 64 profilePosition === 'side' && '@5xl/wrapper:h-screen @5xl/wrapper:pt-24' 65 ]} 66 > 67 <!-- Avatar with edit capability --> 68 <button 69 type="button" 70 class={[ 71 'group relative size-32 shrink-0 cursor-pointer overflow-hidden rounded-full', 72 profilePosition === 'side' && '@5xl/wrapper:size-44' 73 ]} 74 onmouseenter={() => (isHoveringAvatar = true)} 75 onmouseleave={() => (isHoveringAvatar = false)} 76 onclick={handleFileInputClick} 77 > 78 <Avatar 79 src={getAvatarUrl()} 80 class={[ 81 'border-base-400 dark:border-base-800 size-32 shrink-0 rounded-full border object-cover', 82 profilePosition === 'side' && '@5xl/wrapper:size-44' 83 ]} 84 /> 85 86 <!-- Hover overlay --> 87 <div 88 class={[ 89 'absolute inset-0 flex items-center justify-center rounded-full bg-black/50 transition-opacity duration-200', 90 isHoveringAvatar ? 'opacity-100' : 'opacity-0' 91 ]} 92 > 93 <div class="text-center text-sm text-white"> 94 <svg 95 xmlns="http://www.w3.org/2000/svg" 96 fill="none" 97 viewBox="0 0 24 24" 98 stroke-width="1.5" 99 stroke="currentColor" 100 class="mx-auto mb-1 size-6" 101 > 102 <path 103 stroke-linecap="round" 104 stroke-linejoin="round" 105 d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z" 106 /> 107 <path 108 stroke-linecap="round" 109 stroke-linejoin="round" 110 d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z" 111 /> 112 </svg> 113 <span class="font-medium">Click to change</span> 114 </div> 115 </div> 116 </button> 117 118 <input 119 bind:this={fileInput} 120 type="file" 121 accept="image/*" 122 class="hidden" 123 onchange={handleAvatarChange} 124 /> 125 126 <!-- Editable Name --> 127 {#if data.publication} 128 <div class="text-4xl font-bold wrap-anywhere"> 129 <PlainTextEditor 130 bind:contentDict={data.publication} 131 key="name" 132 placeholder="Your name" 133 onupdate={onTextUpdate} 134 /> 135 </div> 136 {/if} 137 138 <Pronouns bind:data editing /> 139 140 <!-- Editable Description --> 141 <div class="scrollbar -mx-4 grow overflow-x-hidden overflow-y-scroll px-4"> 142 {#if data.publication} 143 <MarkdownTextEditor 144 bind:contentDict={data.publication} 145 key="description" 146 placeholder="Something about me..." 147 class="text-base-600 dark:text-base-400 prose dark:prose-invert prose-a:text-accent-500 prose-a:no-underline" 148 onupdate={onTextUpdate} 149 /> 150 {/if} 151 </div> 152 153 {#if !hideBlento} 154 <MadeWithBlento class="hidden {profilePosition === 'side' && '@5xl/wrapper:block'}" /> 155 {/if} 156 </div> 157</div>