your personal website on atproto - mirror blento.app

v0.1

+50 -90
+4 -4
src/lib/cards/BaseCard/BaseEditingCard.svelte
··· 57 57 58 58 const cardDef = $derived(CardDefinitionsByType[item.cardType]); 59 59 60 - const minW = $derived(cardDef?.minW ?? (isMobile() ? 2 : 2)); 61 - const minH = $derived(cardDef?.minH ?? (isMobile() ? 2 : 2)); 60 + const minW = $derived(cardDef.minW ?? (isMobile() ? 2 : 2)); 61 + const minH = $derived(cardDef.minH ?? (isMobile() ? 2 : 2)); 62 62 63 - const maxW = $derived(cardDef?.maxW ?? COLUMNS); 64 - const maxH = $derived(cardDef?.maxH ?? (isMobile() ? 12 : 6)); 63 + const maxW = $derived(cardDef.maxW ?? COLUMNS); 64 + const maxH = $derived(cardDef.maxH ?? (isMobile() ? 12 : 6)); 65 65 66 66 // Resize handle state 67 67 let isResizing = $state(false);
+24 -38
src/lib/components/qr/QRCodeDisplay.svelte
··· 1 1 <script lang="ts"> 2 - import { browser } from '$app/environment'; 2 + import { onMount } from 'svelte'; 3 3 4 - let { url, size = 280, logo }: { url: string; size?: number; logo?: string } = $props(); 4 + let { url, size = 280 }: { url: string; size?: number } = $props(); 5 5 6 6 let container: HTMLDivElement | undefined = $state(); 7 7 8 - $effect(() => { 9 - if (!browser || !container) return; 10 - 11 - const render = async () => { 12 - const QRCodeStylingModule = await import('qr-code-styling'); 13 - const QRCodeStyling = QRCodeStylingModule.default; 14 - 15 - container!.innerHTML = ''; 8 + onMount(async () => { 9 + if (!container) return; 16 10 17 - const options: ConstructorParameters<typeof QRCodeStyling>[0] = { 18 - width: size, 19 - height: size, 20 - data: url, 21 - dotsOptions: { 22 - color: '#000', 23 - type: 'rounded' 24 - }, 25 - backgroundOptions: { 26 - color: '#fff' 27 - }, 28 - cornersSquareOptions: { 29 - type: 'extra-rounded' 30 - }, 31 - cornersDotOptions: { 32 - type: 'dot' 33 - } 34 - }; 11 + const module = await import('qr-code-styling'); 12 + const QRCodeStyling = module.default; 35 13 36 - if (logo) { 37 - options.image = logo; 38 - options.imageOptions = { 39 - crossOrigin: 'anonymous', 40 - margin: 4 41 - }; 14 + const options: ConstructorParameters<typeof QRCodeStyling>[0] = { 15 + width: size, 16 + height: size, 17 + data: url, 18 + dotsOptions: { 19 + color: '#000', 20 + type: 'rounded' 21 + }, 22 + backgroundOptions: { 23 + color: '#fff' 24 + }, 25 + cornersSquareOptions: { 26 + type: 'extra-rounded' 27 + }, 28 + cornersDotOptions: { 29 + type: 'dot' 42 30 } 43 - 44 - const qrCode = new QRCodeStyling(options); 45 - qrCode.append(container!); 46 31 }; 47 32 48 - render(); 33 + const qrCode = new QRCodeStyling(options); 34 + qrCode.append(container); 49 35 }); 50 36 </script> 51 37
+1 -3
src/lib/components/qr/QRCodeModal.svelte
··· 28 28 toast.error('Failed to copy'); 29 29 } 30 30 } 31 - 32 - const logoUrl = $derived(context.avatar || context.favicon); 33 31 </script> 34 32 35 33 <Modal bind:open closeButton={true} class="max-w-sm"> ··· 54 52 {/if} 55 53 56 54 <div class="overflow-hidden rounded-2xl"> 57 - <QRCodeDisplay url={href} size={280} logo={logoUrl} /> 55 + <QRCodeDisplay url={href} size={280} /> 58 56 </div> 59 57 60 58 <div class="flex w-full items-center gap-2">
+9 -5
src/lib/components/qr/qrOverlay.svelte.ts
··· 12 12 } 13 13 14 14 export function qrOverlay( 15 - node: HTMLAnchorElement, 16 - params: { context?: QRContext; disabled?: boolean } = {} 15 + node: HTMLElement, 16 + params: { href?: string; context?: QRContext; disabled?: boolean } = {} 17 17 ) { 18 18 const LONG_PRESS_DURATION = 500; 19 19 let longPressTimer: ReturnType<typeof setTimeout> | null = null; 20 20 let isLongPress = false; 21 21 22 + function getHref() { 23 + return params.href || (node as HTMLAnchorElement).href || ''; 24 + } 25 + 22 26 function startLongPress() { 23 27 if (params.disabled) return; 24 28 isLongPress = false; 25 29 longPressTimer = setTimeout(() => { 26 30 isLongPress = true; 27 - openModal?.(node.href, params.context ?? {}); 31 + openModal?.(getHref(), params.context ?? {}); 28 32 }, LONG_PRESS_DURATION); 29 33 } 30 34 ··· 45 49 function handleContextMenu(e: MouseEvent) { 46 50 if (params.disabled) return; 47 51 e.preventDefault(); 48 - openModal?.(node.href, params.context ?? {}); 52 + openModal?.(getHref(), params.context ?? {}); 49 53 } 50 54 51 55 node.addEventListener('pointerdown', startLongPress); ··· 56 60 node.addEventListener('contextmenu', handleContextMenu); 57 61 58 62 return { 59 - update(newParams: { context?: QRContext; disabled?: boolean }) { 63 + update(newParams: { href?: string; context?: QRContext; disabled?: boolean }) { 60 64 params = newParams; 61 65 }, 62 66 destroy() {
+12 -40
src/lib/website/Profile.svelte
··· 8 8 import { getDescription, getName } from '$lib/helper'; 9 9 import { page } from '$app/state'; 10 10 import type { ActorIdentifier } from '@atcute/lexicons'; 11 - import QRCodeModal from '$lib/components/qr/QRCodeModal.svelte'; 11 + import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte'; 12 12 13 13 let { 14 14 data, ··· 22 22 renderer.link = ({ href, title, text }) => 23 23 `<a target="_blank" href="${href}" title="${title}">${text}</a>`; 24 24 25 - let qrOpen = $state(false); 26 - let longPressTimer: ReturnType<typeof setTimeout> | null = null; 27 - 28 - const profileUrl = $derived(`${page.url}/${data.handle}`); 29 - 30 - function startLongPress() { 31 - longPressTimer = setTimeout(() => { 32 - qrOpen = true; 33 - }, 500); 34 - } 35 - 36 - function cancelLongPress() { 37 - if (longPressTimer) { 38 - clearTimeout(longPressTimer); 39 - longPressTimer = null; 40 - } 41 - } 42 - 43 - function handleContextMenu(e: MouseEvent) { 44 - e.preventDefault(); 45 - qrOpen = true; 46 - } 25 + const profileUrl = $derived(`${page.url.origin}/${data.handle}`); 47 26 </script> 48 27 49 28 <!-- lg:fixed lg:h-screen lg:w-1/4 lg:max-w-none lg:px-12 lg:pt-24 xl:w-1/3 --> ··· 51 30 class="mx-auto flex max-w-lg flex-col justify-between px-8 @5xl/wrapper:fixed @5xl/wrapper:h-screen @5xl/wrapper:w-1/4 @5xl/wrapper:max-w-none @5xl/wrapper:px-12" 52 31 > 53 32 <div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24"> 54 - <!-- svelte-ignore a11y_no_static_element_interactions --> 55 - <div 56 - class="w-fit cursor-pointer" 57 - onpointerdown={startLongPress} 58 - onpointerup={cancelLongPress} 59 - onpointercancel={cancelLongPress} 60 - onpointerleave={cancelLongPress} 61 - oncontextmenu={handleContextMenu} 33 + <a 34 + href={profileUrl} 35 + class="w-fit" 36 + use:qrOverlay={{ 37 + context: { 38 + title: getName(data), 39 + avatar: data.profile.avatar 40 + } 41 + }} 62 42 > 63 43 {#if data.profile.avatar} 64 44 <img ··· 69 49 {:else} 70 50 <div class="bg-base-300 dark:bg-base-700 size-32 rounded-full @5xl/wrapper:size-44"></div> 71 51 {/if} 72 - </div> 73 - <QRCodeModal 74 - bind:open={qrOpen} 75 - href={profileUrl} 76 - context={{ 77 - title: getName(data), 78 - avatar: data.profile.avatar 79 - }} 80 - /> 52 + </a> 81 53 82 54 <div class="text-4xl font-bold wrap-anywhere"> 83 55 {getName(data)}