your personal website on atproto - mirror blento.app

commit

Florian 1b9377e9 8053b848

+355 -222
+1
package.json
··· 68 "link-preview-js": "^4.0.0", 69 "marked": "^15.0.11", 70 "plyr": "^3.8.4", 71 "svelte-sonner": "^1.0.7", 72 "tailwind-merge": "^3.4.0", 73 "tailwind-variants": "^3.2.2",
··· 68 "link-preview-js": "^4.0.0", 69 "marked": "^15.0.11", 70 "plyr": "^3.8.4", 71 + "simple-icons": "^16.5.0", 72 "svelte-sonner": "^1.0.7", 73 "tailwind-merge": "^3.4.0", 74 "tailwind-variants": "^3.2.2",
+9
pnpm-lock.yaml
··· 89 plyr: 90 specifier: ^3.8.4 91 version: 3.8.4 92 svelte-sonner: 93 specifier: ^1.0.7 94 version: 1.0.7(svelte@5.45.8) ··· 2668 side-channel@1.1.0: 2669 resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz} 2670 engines: {node: '>= 0.4'} 2671 2672 simple-swizzle@0.2.4: 2673 resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==, tarball: https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz} ··· 5438 side-channel-list: 1.0.0 5439 side-channel-map: 1.0.1 5440 side-channel-weakmap: 1.0.2 5441 5442 simple-swizzle@0.2.4: 5443 dependencies:
··· 89 plyr: 90 specifier: ^3.8.4 91 version: 3.8.4 92 + simple-icons: 93 + specifier: ^16.5.0 94 + version: 16.5.0 95 svelte-sonner: 96 specifier: ^1.0.7 97 version: 1.0.7(svelte@5.45.8) ··· 2671 side-channel@1.1.0: 2672 resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz} 2673 engines: {node: '>= 0.4'} 2674 + 2675 + simple-icons@16.5.0: 2676 + resolution: {integrity: sha512-72nn0oHADKx6Hknu7q6M0vfL8LiCUMKABOHane2+4xdqaFBSHfNNBjuZioihiqVQMz7IvVle4NKAM0IlXvl/9A==, tarball: https://registry.npmjs.org/simple-icons/-/simple-icons-16.5.0.tgz} 2677 + engines: {node: '>=0.12.18'} 2678 2679 simple-swizzle@0.2.4: 2680 resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==, tarball: https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz} ··· 5445 side-channel-list: 1.0.0 5446 side-channel-map: 1.0.1 5447 side-channel-weakmap: 1.0.2 5448 + 5449 + simple-icons@16.5.0: {} 5450 5451 simple-swizzle@0.2.4: 5452 dependencies:
+2 -2
src/app.html
··· 1 <!doctype html> 2 - <html lang="en" class="stone"> 3 <head> 4 <meta charset="utf-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> ··· 11 data-website-id="c55efa23-9abe-4a7e-b8fd-81b9fa7e8052" 12 ></script> 13 </head> 14 - <body data-sveltekit-preload-data="hover" class="bg-base-200/50 dark:bg-base-950"> 15 <div style="display: contents">%sveltekit.body%</div> 16 </body> 17 </html>
··· 1 <!doctype html> 2 + <html lang="en" class="neutral"> 3 <head> 4 <meta charset="utf-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> ··· 11 data-website-id="c55efa23-9abe-4a7e-b8fd-81b9fa7e8052" 12 ></script> 13 </head> 14 + <body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-900"> 15 <div style="display: contents">%sveltekit.body%</div> 16 </body> 17 </html>
+56 -3
src/lib/EditableWebsite.svelte
··· 28 import { setDidContext, setHandleContext } from './website/context'; 29 import BaseEditingCard from './cards/BaseCard/BaseEditingCard.svelte'; 30 import Settings from './Settings.svelte'; 31 32 let { 33 handle, ··· 234 } 235 </script> 236 237 {#if !dev} 238 <div 239 class="bg-base-200 dark:bg-base-800 fixed inset-0 z-50 inline-flex h-full w-full items-center justify-center p-4 text-center lg:hidden" ··· 270 > 271 <Profile {handle} {did} {data} /> 272 273 - <div 274 - class="mx-auto max-w-2xl @5xl/wrapper:grid @5xl/wrapper:max-w-7xl @5xl/wrapper:grid-cols-4" 275 - > 276 <div></div> 277 <!-- svelte-ignore a11y_no_static_element_interactions --> 278 <div
··· 28 import { setDidContext, setHandleContext } from './website/context'; 29 import BaseEditingCard from './cards/BaseCard/BaseEditingCard.svelte'; 30 import Settings from './Settings.svelte'; 31 + import ImageDropper from './components/ImageDropper.svelte'; 32 33 let { 34 handle, ··· 235 } 236 </script> 237 238 + <svelte:body 239 + onpaste={(event) => { 240 + const target = event.target; 241 + 242 + const active = document.activeElement; 243 + const isEditable = 244 + active instanceof HTMLInputElement || 245 + active instanceof HTMLTextAreaElement || 246 + active?.isContentEditable; 247 + 248 + if (isEditable) { 249 + // Let normal paste happen 250 + return; 251 + } 252 + 253 + const text = event.clipboardData?.getData('text/plain'); 254 + 255 + if (!text) return; 256 + 257 + try { 258 + const url = new URL(text); 259 + 260 + let item: Item = { 261 + id: TID.nextStr(), 262 + x: 0, 263 + y: 0, 264 + w: 2, 265 + h: 2, 266 + mobileH: 4, 267 + mobileW: 4, 268 + mobileX: 0, 269 + mobileY: 0, 270 + cardType: '', 271 + cardData: {} 272 + }; 273 + 274 + newItem.item = item; 275 + 276 + for (const cardDef of AllCardDefinitions) { 277 + if (cardDef.onUrlHandler?.(text, item)) { 278 + item.cardType = cardDef.type; 279 + saveNewItem(); 280 + } 281 + } 282 + 283 + newItem = {}; 284 + } catch (e) { 285 + return; 286 + } 287 + }} 288 + /> 289 + 290 + <!-- <ImageDropper processImageFile={(file: File) => {}} /> --> 291 + 292 {#if !dev} 293 <div 294 class="bg-base-200 dark:bg-base-800 fixed inset-0 z-50 inline-flex h-full w-full items-center justify-center p-4 text-center lg:hidden" ··· 325 > 326 <Profile {handle} {did} {data} /> 327 328 + <div class="mx-auto max-w-lg @5xl/wrapper:grid @5xl/wrapper:max-w-7xl @5xl/wrapper:grid-cols-4"> 329 <div></div> 330 <!-- svelte-ignore a11y_no_static_element_interactions --> 331 <div
+2 -2
src/lib/Profile.svelte
··· 29 30 <!-- lg:fixed lg:h-screen lg:w-1/4 lg:max-w-none lg:px-12 lg:pt-24 xl:w-1/3 --> 31 <div 32 - class="mx-auto flex max-w-2xl 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" 33 > 34 <div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24"> 35 {#if profileData?.avatar?.ref?.$link} 36 <img 37 - class="size-32 rounded-full @5xl/wrapper:size-44" 38 src={'https://cdn.bsky.app/img/avatar/plain/' + did + '/' + profileData.avatar.ref.$link} 39 alt="" 40 />
··· 29 30 <!-- lg:fixed lg:h-screen lg:w-1/4 lg:max-w-none lg:px-12 lg:pt-24 xl:w-1/3 --> 31 <div 32 + 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" 33 > 34 <div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24"> 35 {#if profileData?.avatar?.ref?.$link} 36 <img 37 + class="size-32 rounded-full @5xl/wrapper:size-44 border border-base-400 dark:border-base-800" 38 src={'https://cdn.bsky.app/img/avatar/plain/' + did + '/' + profileData.avatar.ref.$link} 39 alt="" 40 />
+1 -1
src/lib/Website.svelte
··· 39 <div class="@container/wrapper relative w-full"> 40 <Profile {handle} {did} {data} showEditButton={true} /> 41 42 - <div class="mx-auto max-w-2xl lg:grid lg:max-w-none lg:grid-cols-4"> 43 <div></div> 44 <div 45 bind:this={container}
··· 39 <div class="@container/wrapper relative w-full"> 40 <Profile {handle} {did} {data} showEditButton={true} /> 41 42 + <div class="mx-auto max-w-lg lg:grid lg:max-w-none lg:grid-cols-4"> 43 <div></div> 44 <div 45 bind:this={container}
+3 -1
src/lib/cards/BaseCard/BaseCard.svelte
··· 7 import { getColor } from '..'; 8 9 const colors = { 10 - base: 'bg-base-50 dark:bg-base-900', 11 accent: 12 'bg-accent-400 dark:bg-accent-500 accent', 13 transparent: '' ··· 27 isEditing = false, 28 controls, 29 showOutline, 30 ...rest 31 }: BaseCardProps = $props(); 32 ··· 43 color ? (colors[color] ?? colors.accent) : colors.base, 44 color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '', 45 showOutline ? 'outline-2' : '', 46 ]} 47 style={` 48 --mx: ${item.mobileX};
··· 7 import { getColor } from '..'; 8 9 const colors = { 10 + base: 'bg-base-200/50 dark:bg-base-950/50', 11 accent: 12 'bg-accent-400 dark:bg-accent-500 accent', 13 transparent: '' ··· 27 isEditing = false, 28 controls, 29 showOutline, 30 + class: className, 31 ...rest 32 }: BaseCardProps = $props(); 33 ··· 44 color ? (colors[color] ?? colors.accent) : colors.base, 45 color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '', 46 showOutline ? 'outline-2' : '', 47 + className 48 ]} 49 style={` 50 --mx: ${item.mobileX};
+6 -7
src/lib/cards/BaseCard/BaseEditingCard.svelte
··· 106 let newW = resizeStartW + gridDeltaW; 107 let newH = resizeStartH + gridDeltaH; 108 109 - console.log(item.mobileW, newW); 110 if (isMobile()) { 111 newW = Math.round(newW / 4) * 4; 112 } else { 113 newW = Math.round(newW / 2) * 2; 114 } 115 - console.log(item.mobileW, newW); 116 117 // Clamp to min/max 118 - newW = Math.max(minW, Math.min(maxW, newW)); 119 - newH = Math.max(minH, Math.min(maxH, newH)); 120 121 // Only call onsetsize if size changed 122 const currentW = isMobile() ? (item.mobileW ?? item.w) : item.w; ··· 137 if (!cardDef) return false; 138 139 if (isMobile()) { 140 - w *= 2; 141 - h *= 2; 142 } 143 144 return w >= minW && w <= maxW && h >= minH && h <= maxH; ··· 155 let settingsPopoverOpen = $state(false); 156 </script> 157 158 - <BaseCard {item} {...rest} isEditing={true} bind:ref showOutline={isResizing}> 159 {@render children?.()} 160 161 {#snippet controls()}
··· 106 let newW = resizeStartW + gridDeltaW; 107 let newH = resizeStartH + gridDeltaH; 108 109 if (isMobile()) { 110 newW = Math.round(newW / 4) * 4; 111 } else { 112 newW = Math.round(newW / 2) * 2; 113 } 114 + let mult = isMobile() ? 2 : 1; 115 116 // Clamp to min/max 117 + newW = Math.max(minW * mult, Math.min(maxW, newW)); 118 + newH = Math.max(minH * mult, Math.min(maxH, newH)); 119 120 // Only call onsetsize if size changed 121 const currentW = isMobile() ? (item.mobileW ?? item.w) : item.w; ··· 136 if (!cardDef) return false; 137 138 if (isMobile()) { 139 + 140 + return w >= minW && w*2 <= maxW && h >= minH && h*2 <= maxH; 141 } 142 143 return w >= minW && w <= maxW && h >= minH && h <= maxH; ··· 154 let settingsPopoverOpen = $state(false); 155 </script> 156 157 + <BaseCard {item} isEditing={true} bind:ref showOutline={isResizing} class="starting:scale-0 scale-100 starting:opacity-0 opacity-100" {...rest} > 158 {@render children?.()} 159 160 {#snippet controls()}
+9 -124
src/lib/cards/BigSocialCard/BigSocialCard.svelte
··· 1 <script lang="ts"> 2 import type { ContentComponentProps } from '../types'; 3 4 let { item }: ContentComponentProps = $props(); 5 6 const platform = $derived(item.cardData.platform as string); 7 </script> 8 9 <a 10 href={item.cardData.href} 11 target="_blank" 12 rel="noopener noreferrer" 13 - class="flex h-full w-full items-center justify-center p-4" 14 > 15 - <div class="flex aspect-square max-h-full max-w-full items-center justify-center"> 16 - {#if platform === 'instagram'} 17 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 18 - <path 19 - d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" 20 - /> 21 - </svg> 22 - {:else if platform === 'facebook'} 23 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 24 - <path 25 - d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" 26 - /> 27 - </svg> 28 - {:else if platform === 'twitter' || platform === 'x'} 29 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 30 - <path 31 - d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" 32 - /> 33 - </svg> 34 - {:else if platform === 'youtube'} 35 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 36 - <path 37 - d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" 38 - /> 39 - </svg> 40 - {:else if platform === 'tiktok'} 41 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 42 - <path 43 - d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z" 44 - /> 45 - </svg> 46 - {:else if platform === 'linkedin'} 47 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 48 - <path 49 - d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" 50 - /> 51 - </svg> 52 - {:else if platform === 'bluesky'} 53 - <svg class="h-full w-full" viewBox="0 0 568 501" fill="currentColor"> 54 - <path 55 - d="M123.121 33.6637C188.241 82.5526 258.281 181.681 284 234.873C309.719 181.681 379.759 82.5526 444.879 33.6637C491.866 -1.61183 568 -28.9064 568 57.9464C568 75.2916 558.055 203.659 552.222 224.501C531.947 296.954 458.067 315.434 392.347 304.249C507.222 323.8 536.444 388.56 473.333 453.32C353.473 576.312 301.061 422.461 287.631 383.039C285.169 374.014 284.017 369.587 284 371.839C283.983 369.587 282.831 374.014 280.369 383.039C266.939 422.461 214.527 576.312 94.6667 453.32C31.5556 388.56 60.7778 323.8 175.653 304.249C109.933 315.434 36.0535 296.954 15.7778 224.501C9.94525 203.659 0 75.2916 0 57.9464C0 -28.9064 76.1345 -1.61183 123.121 33.6637Z" 56 - /> 57 - </svg> 58 - {:else if platform === 'threads'} 59 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 60 - <path 61 - d="M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.96-.065-1.182.408-2.256 1.33-3.022.88-.732 2.07-1.128 3.446-1.145.875-.01 1.71.097 2.482.32.019-.776-.042-1.49-.184-2.115H9.346v-2.06h6.043c.122.534.19 1.245.195 2.098-.38-.125-.77-.227-1.172-.305-.94-.182-1.935-.227-2.955-.127-1.307.13-2.344.483-3.086 1.051-.655.5-1.01 1.143-.999 1.808.01.663.376 1.23.999 1.549.549.28 1.254.406 2.046.369.958-.046 1.704-.324 2.218-.825.334-.326.56-.74.688-1.242-.29-.083-.61-.153-.96-.207-.942-.148-2.042-.105-3.06.121l-.524-1.993c1.32-.294 2.695-.35 3.884-.159.476.077.912.184 1.312.317a9.9 9.9 0 0 0-.078-1.136l2.043-.284c.085.61.116 1.328.09 2.138.922.499 1.631 1.18 2.104 2.027.593 1.06.854 2.39.712 3.848-.142 1.456-.726 2.78-1.684 3.823-1.724 1.877-4.123 2.835-7.134 2.849z" 62 - /> 63 - </svg> 64 - {:else if platform === 'snapchat'} 65 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 66 - <path 67 - d="M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z" 68 - /> 69 - </svg> 70 - {:else if platform === 'pinterest'} 71 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 72 - <path 73 - d="M12.017 0C5.396 0 .029 5.367.029 11.987c0 5.079 3.158 9.417 7.618 11.162-.105-.949-.199-2.403.041-3.439.219-.937 1.406-5.957 1.406-5.957s-.359-.72-.359-1.781c0-1.663.967-2.911 2.168-2.911 1.024 0 1.518.769 1.518 1.688 0 1.029-.653 2.567-.992 3.992-.285 1.193.6 2.165 1.775 2.165 2.128 0 3.768-2.245 3.768-5.487 0-2.861-2.063-4.869-5.008-4.869-3.41 0-5.409 2.562-5.409 5.199 0 1.033.394 2.143.889 2.741.099.12.112.225.085.345-.09.375-.293 1.199-.334 1.363-.053.225-.172.271-.401.165-1.495-.69-2.433-2.878-2.433-4.646 0-3.776 2.748-7.252 7.92-7.252 4.158 0 7.392 2.967 7.392 6.923 0 4.135-2.607 7.462-6.233 7.462-1.214 0-2.354-.629-2.758-1.379l-.749 2.848c-.269 1.045-1.004 2.352-1.498 3.146 1.123.345 2.306.535 3.55.535 6.607 0 11.985-5.365 11.985-11.987C23.97 5.39 18.592.026 11.985.026L12.017 0z" 74 - /> 75 - </svg> 76 - {:else if platform === 'twitch'} 77 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 78 - <path 79 - d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z" 80 - /> 81 - </svg> 82 - {:else if platform === 'discord'} 83 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 84 - <path 85 - d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" 86 - /> 87 - </svg> 88 - {:else if platform === 'github'} 89 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 90 - <path 91 - d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" 92 - /> 93 - </svg> 94 - {:else if platform === 'spotify'} 95 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 96 - <path 97 - d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z" 98 - /> 99 - </svg> 100 - {:else if platform === 'reddit'} 101 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 102 - <path 103 - d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z" 104 - /> 105 - </svg> 106 - {:else if platform === 'whatsapp'} 107 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 108 - <path 109 - d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z" 110 - /> 111 - </svg> 112 - {:else if platform === 'telegram'} 113 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 114 - <path 115 - d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" 116 - /> 117 - </svg> 118 - {:else if platform === 'mastodon'} 119 - <svg class="h-full w-full" viewBox="0 0 24 24" fill="currentColor"> 120 - <path 121 - d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z" 122 - /> 123 - </svg> 124 - {:else} 125 - <svg 126 - class="h-full w-full" 127 - xmlns="http://www.w3.org/2000/svg" 128 - viewBox="0 0 24 24" 129 - fill="currentColor" 130 - > 131 - <path 132 - fill-rule="evenodd" 133 - d="M19.902 4.098a3.75 3.75 0 0 0-5.304 0l-4.5 4.5a3.75 3.75 0 0 0 1.035 6.037.75.75 0 0 1-.646 1.353 5.25 5.25 0 0 1-1.449-8.45l4.5-4.5a5.25 5.25 0 1 1 7.424 7.424l-1.757 1.757a.75.75 0 1 1-1.06-1.06l1.757-1.757a3.75 3.75 0 0 0 0-5.304Zm-7.389 4.267a.75.75 0 0 1 1-.353 5.25 5.25 0 0 1 1.449 8.45l-4.5 4.5a5.25 5.25 0 1 1-7.424-7.424l1.757-1.757a.75.75 0 1 1 1.06 1.06l-1.757 1.757a3.75 3.75 0 1 0 5.304 5.304l4.5-4.5a3.75 3.75 0 0 0-1.035-6.037.75.75 0 0 1-.354-1Z" 134 - clip-rule="evenodd" 135 - /> 136 - </svg> 137 - {/if} 138 </div> 139 </a>
··· 1 <script lang="ts"> 2 + import { platformsData } from '.'; 3 import type { ContentComponentProps } from '../types'; 4 5 let { item }: ContentComponentProps = $props(); 6 7 const platform = $derived(item.cardData.platform as string); 8 + 9 + $inspect(platformsData[platform].svg) 10 </script> 11 12 <a 13 href={item.cardData.href} 14 target="_blank" 15 rel="noopener noreferrer" 16 + class="flex h-full w-full items-center justify-center p-10" 17 + style={ 18 + `background-color: #${item.cardData.color}` 19 + } 20 > 21 + <div class="flex aspect-square max-h-full max-w-full items-center justify-center [&_svg]:size-full [&_svg]:max-w-60 [&_svg]:fill-white"> 22 + {@html platformsData[platform].svg} 23 </div> 24 </a>
+3 -54
src/lib/cards/BigSocialCard/CreateBigSocialCardModal.svelte
··· 1 <script lang="ts"> 2 import { Alert, Button, Input, Modal, Subheading } from '@foxui/core'; 3 import type { CreationModalComponentProps } from '../types'; 4 5 let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props(); 6 7 let errorMessage = $state(''); 8 9 - const platformPatterns: Record<string, RegExp> = { 10 - instagram: /(?:instagram\.com|instagr\.am)/i, 11 - facebook: /(?:facebook\.com|fb\.com|fb\.me)/i, 12 - twitter: /(?:twitter\.com)/i, 13 - x: /(?:x\.com)/i, 14 - youtube: /(?:youtube\.com|youtu\.be)/i, 15 - tiktok: /(?:tiktok\.com)/i, 16 - linkedin: /(?:linkedin\.com)/i, 17 - bluesky: /(?:bsky\.app|bsky\.social)/i, 18 - threads: /(?:threads\.net)/i, 19 - snapchat: /(?:snapchat\.com)/i, 20 - pinterest: /(?:pinterest\.com|pin\.it)/i, 21 - twitch: /(?:twitch\.tv)/i, 22 - discord: /(?:discord\.gg|discord\.com)/i, 23 - github: /(?:github\.com)/i, 24 - spotify: /(?:spotify\.com|open\.spotify\.com)/i, 25 - reddit: /(?:reddit\.com)/i, 26 - whatsapp: /(?:whatsapp\.com|wa\.me)/i, 27 - telegram: /(?:t\.me|telegram\.org)/i, 28 - mastodon: /(?:mastodon\.social|mastodon\.online|mstdn\.social)/i 29 - }; 30 - 31 - const platformColors: Record<string, string> = { 32 - instagram: 'pink', 33 - facebook: 'blue', 34 - twitter: 'sky', 35 - x: 'zinc', 36 - youtube: 'red', 37 - tiktok: 'zinc', 38 - linkedin: 'blue', 39 - bluesky: 'sky', 40 - threads: 'zinc', 41 - snapchat: 'yellow', 42 - pinterest: 'red', 43 - twitch: 'purple', 44 - discord: 'indigo', 45 - github: 'zinc', 46 - spotify: 'green', 47 - reddit: 'orange', 48 - whatsapp: 'green', 49 - telegram: 'sky', 50 - mastodon: 'purple' 51 - }; 52 - 53 - function detectPlatform(url: string): string | null { 54 - for (const [platform, pattern] of Object.entries(platformPatterns)) { 55 - if (pattern.test(url)) { 56 - return platform; 57 - } 58 - } 59 - return null; 60 - } 61 - 62 function handleCreate() { 63 errorMessage = ''; 64 ··· 76 } 77 78 item.cardData.platform = platform; 79 - item.color = platformColors[platform] || 'accent'; 80 oncreate(); 81 } 82 </script>
··· 1 <script lang="ts"> 2 import { Alert, Button, Input, Modal, Subheading } from '@foxui/core'; 3 import type { CreationModalComponentProps } from '../types'; 4 + import { detectPlatform, platformPatterns, platformsData } from '.'; 5 6 let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props(); 7 8 let errorMessage = $state(''); 9 10 function handleCreate() { 11 errorMessage = ''; 12 ··· 24 } 25 26 item.cardData.platform = platform; 27 + item.cardData.color = platformsData[platform].hex; 28 + 29 oncreate(); 30 } 31 </script>
+1 -1
src/lib/cards/BigSocialCard/SidebarItemBigSocialCard.svelte
··· 17 clip-rule="evenodd" 18 /> 19 </svg> 20 - Big Social</Button 21 >
··· 17 clip-rule="evenodd" 18 /> 19 </svg> 20 + Big Social Icon</Button 21 >
+89 -6
src/lib/cards/BigSocialCard/index.ts
··· 16 }; 17 card.w = 2; 18 card.h = 2; 19 - card.mobileW = 2; 20 - card.mobileH = 2; 21 }, 22 - allowSetColor: true, 23 - defaultColor: 'accent', 24 - minW: 1, 25 - minH: 1 26 } as CardDefinition & { type: 'bigsocial' };
··· 16 }; 17 card.w = 2; 18 card.h = 2; 19 + card.mobileW = 4; 20 + card.mobileH = 4; 21 }, 22 + allowSetColor: false, 23 + defaultColor: 'transparent', 24 + minW: 2, 25 + minH: 2, 26 + onUrlHandler: (url, item) => { 27 + const platform = detectPlatform(url); 28 + if (!platform) return null; 29 + 30 + item.cardData.platform = platform; 31 + item.cardData.color = platformsData[platform].hex; 32 + item.cardData.href = url; 33 + 34 + return item; 35 + } 36 } as CardDefinition & { type: 'bigsocial' }; 37 + 38 + export const platformPatterns: Record<string, RegExp> = { 39 + instagram: /(?:instagram\.com|instagr\.am)/i, 40 + facebook: /(?:facebook\.com|fb\.com|fb\.me)/i, 41 + twitter: /(?:twitter\.com)/i, 42 + x: /(?:x\.com)/i, 43 + youtube: /(?:youtube\.com|youtu\.be)/i, 44 + tiktok: /(?:tiktok\.com)/i, 45 + linkedin: /(?:linkedin\.com)/i, 46 + bluesky: /(?:bsky\.app|bsky\.social)/i, 47 + threads: /(?:threads\.net)/i, 48 + snapchat: /(?:snapchat\.com)/i, 49 + pinterest: /(?:pinterest\.com|pin\.it)/i, 50 + twitch: /(?:twitch\.tv)/i, 51 + discord: /(?:discord\.gg|discord\.com)/i, 52 + github: /(?:github\.com)/i, 53 + spotify: /(?:spotify\.com|open\.spotify\.com)/i, 54 + reddit: /(?:reddit\.com)/i, 55 + whatsapp: /(?:whatsapp\.com|wa\.me)/i, 56 + telegram: /(?:t\.me|telegram\.org)/i, 57 + mastodon: /(?:mastodon\.social|mastodon\.online|mstdn\.social)/i 58 + }; 59 + 60 + import { 61 + siInstagram, 62 + siFacebook, 63 + siX, 64 + siYoutube, 65 + siTiktok, 66 + siBluesky, 67 + siThreads, 68 + siSnapchat, 69 + siPinterest, 70 + siTwitch, 71 + siDiscord, 72 + siGithub, 73 + siSpotify, 74 + siReddit, 75 + siWhatsapp, 76 + siTelegram, 77 + siMastodon, 78 + type SimpleIcon 79 + } from 'simple-icons'; 80 + 81 + export const platformsData: Record<string, SimpleIcon> = { 82 + instagram: siInstagram, 83 + facebook: siFacebook, 84 + twitter: siX, 85 + x: siX, 86 + youtube: siYoutube, 87 + tiktok: siTiktok, 88 + bluesky: siBluesky, 89 + threads: siThreads, 90 + snapchat: siSnapchat, 91 + pinterest: siPinterest, 92 + twitch: siTwitch, 93 + discord: siDiscord, 94 + github: siGithub, 95 + spotify: siSpotify, 96 + reddit: siReddit, 97 + whatsapp: siWhatsapp, 98 + telegram: siTelegram, 99 + mastodon: siMastodon 100 + }; 101 + 102 + export function detectPlatform(url: string): string | null { 103 + for (const [platform, pattern] of Object.entries(platformPatterns)) { 104 + if (pattern.test(url)) { 105 + return platform; 106 + } 107 + } 108 + return null; 109 + }
+2 -1
src/lib/cards/LinkCard/EditingLinkCard.svelte
··· 1 <script lang="ts"> 2 import { getIsMobile } from '$lib/helper'; 3 import type { ContentComponentProps } from '../types'; 4 import PlainTextEditor from '../utils/PlainTextEditor.svelte'; ··· 57 </div> 58 </div> 59 60 - {#if ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image} 61 <img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" /> 62 {/if} 63 </div>
··· 1 <script lang="ts"> 2 + import { browser } from '$app/environment'; 3 import { getIsMobile } from '$lib/helper'; 4 import type { ContentComponentProps } from '../types'; 5 import PlainTextEditor from '../utils/PlainTextEditor.svelte'; ··· 58 </div> 59 </div> 60 61 + {#if browser && ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image} 62 <img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" /> 63 {/if} 64 </div>
+3 -2
src/lib/cards/LinkCard/LinkCard.svelte
··· 1 <script lang="ts"> 2 import { getIsMobile } from '$lib/helper'; 3 import type { ContentComponentProps } from '../types'; 4 ··· 56 </div> 57 </div> 58 59 - {#if ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image} 60 - <img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" /> 61 {/if} 62 {#if item.cardData.href} 63 <a
··· 1 <script lang="ts"> 2 + import { browser } from '$app/environment'; 3 import { getIsMobile } from '$lib/helper'; 4 import type { ContentComponentProps } from '../types'; 5 ··· 57 </div> 58 </div> 59 60 + {#if browser && ((isMobile() && item.mobileH >= 8) || (!isMobile() && item.h >= 4)) && item.cardData.image} 61 + <img class="mb-2 max-h-32 w-full starting:opacity-0 opacity-100 transition-opacity duration-100 rounded-xl object-cover" src={item.cardData.image} alt="" /> 62 {/if} 63 {#if item.cardData.href} 64 <a
+1 -1
src/lib/cards/SpecialCards/UpdatedBlentos/UpdatedBlentosCard.svelte
··· 22 target="_blank" 23 > 24 <img src={profile.avatar} class="aspect-square size-28 rounded-full" alt="" /> 25 - <div class="line-clamp-1 text-lg font-bold">{profile.displayName || profile.handle}</div> 26 </a> 27 {/each} 28 </div>
··· 22 target="_blank" 23 > 24 <img src={profile.avatar} class="aspect-square size-28 rounded-full" alt="" /> 25 + <div class="line-clamp-1 text-md font-bold text-center">{profile.displayName || profile.handle}</div> 26 </a> 27 {/each} 28 </div>
+18 -6
src/lib/cards/TextCard/EditingTextCard.svelte
··· 1 <script lang="ts"> 2 import type { Item } from '$lib/types'; 3 - import { textAlignClasses, verticalAlignClasses } from '.'; 4 import type { ContentComponentProps } from '../types'; 5 import MarkdownTextEditor from '../utils/MarkdownTextEditor.svelte'; 6 7 let { item = $bindable<Item>() }: ContentComponentProps = $props(); 8 </script> 9 10 <div 11 - class={[ 12 - 'prose dark:prose-invert prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 hover:bg-base-500/20 prose-p:first:mt-0 prose-p:last:mb-0 h-full overflow-y-scroll rounded-md p-2 inline-flex w-full max-w-none', 13 textAlignClasses[item.cardData.textAlign as string], 14 - verticalAlignClasses[item.cardData.verticalAlign as string] 15 - ]} 16 > 17 - <MarkdownTextEditor bind:item /> 18 </div>
··· 1 <script lang="ts"> 2 import type { Item } from '$lib/types'; 3 + import type { Editor } from '@tiptap/core'; 4 + import { textAlignClasses, textSizeClasses, verticalAlignClasses } from '.'; 5 import type { ContentComponentProps } from '../types'; 6 import MarkdownTextEditor from '../utils/MarkdownTextEditor.svelte'; 7 + import { cn } from '@foxui/core'; 8 9 let { item = $bindable<Item>() }: ContentComponentProps = $props(); 10 + 11 + let editor: Editor | null = $state(null); 12 + 13 + $inspect(textSizeClasses[item.cardData.textSize as number]); 14 </script> 15 16 + <!-- svelte-ignore a11y_no_static_element_interactions --> 17 + <!-- svelte-ignore a11y_click_events_have_key_events --> 18 <div 19 + class={cn( 20 + 'prose dark:prose-invert prose-neutral prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 hover:bg-base-700/5 accent:hover:bg-accent-300/20 prose-p:first:mt-0 prose-p:last:mb-0 inline-flex h-full w-full text-lg max-w-none overflow-y-scroll rounded-md p-2 transition-colors duration-150 cursor-text', 21 textAlignClasses[item.cardData.textAlign as string], 22 + verticalAlignClasses[item.cardData.verticalAlign as string], 23 + textSizeClasses[(item.cardData.textSize ?? 0) as number] 24 + )} 25 + onclick={() => { 26 + editor?.commands.focus('end'); 27 + }} 28 > 29 + <MarkdownTextEditor bind:item bind:editor /> 30 </div>
+4 -3
src/lib/cards/TextCard/TextCard.svelte
··· 1 <script lang="ts"> 2 import { marked } from 'marked'; 3 import type { ContentComponentProps } from '../types'; 4 - import { textAlignClasses, verticalAlignClasses } from '.'; 5 6 let { item }: ContentComponentProps = $props(); 7 ··· 12 13 <div 14 class={[ 15 - 'prose dark:prose-invert prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 prose-p:first:mt-0 prose-p:last:mb-0 inline-flex h-full w-full overflow-y-scroll rounded-md p-3 max-w-none', 16 textAlignClasses?.[item.cardData.textAlign as string], 17 - verticalAlignClasses[item.cardData.verticalAlign as string] 18 ]} 19 > 20 <span>{@html marked.parse(item.cardData.text ?? '', { renderer })}</span>
··· 1 <script lang="ts"> 2 import { marked } from 'marked'; 3 import type { ContentComponentProps } from '../types'; 4 + import { textAlignClasses, textSizeClasses, verticalAlignClasses } from '.'; 5 6 let { item }: ContentComponentProps = $props(); 7 ··· 12 13 <div 14 class={[ 15 + 'prose dark:prose-invert prose-neutral prose-sm prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 accent:prose-a:text-accent-950 accent:prose-a:underline accent:prose-p:text-base-900 prose-p:first:mt-0 prose-p:last:mb-0 prose-headings:first:mt-0 prose-headings:last:mb-0 inline-flex h-full min-h-full w-full max-w-none overflow-y-scroll rounded-md p-3 text-lg', 16 textAlignClasses?.[item.cardData.textAlign as string], 17 + verticalAlignClasses[item.cardData.verticalAlign as string], 18 + textSizeClasses[(item.cardData.textSize ?? 0) as number] 19 ]} 20 > 21 <span>{@html marked.parse(item.cardData.text ?? '', { renderer })}</span>
+50 -1
src/lib/cards/TextCard/TextCardSettings.svelte
··· 1 <script lang="ts"> 2 import type { Item } from '$lib/types'; 3 import type { ContentComponentProps } from '../types'; 4 - import { ToggleGroup, ToggleGroupItem } from '@foxui/core'; 5 6 let { item = $bindable<Item>() }: ContentComponentProps = $props(); 7 ··· 119 ></ToggleGroupItem 120 > 121 </ToggleGroup> 122 </div>
··· 1 <script lang="ts"> 2 import type { Item } from '$lib/types'; 3 import type { ContentComponentProps } from '../types'; 4 + import { ToggleGroup, ToggleGroupItem, Button } from '@foxui/core'; 5 6 let { item = $bindable<Item>() }: ContentComponentProps = $props(); 7 ··· 119 ></ToggleGroupItem 120 > 121 </ToggleGroup> 122 + 123 + <div> 124 + <Button 125 + variant="ghost" 126 + onclick={() => { 127 + item.cardData.textSize = Math.max((item.cardData.textSize ?? 0) - 1, 0); 128 + }} 129 + disabled={(item.cardData.textSize ?? 0) < 1} 130 + > 131 + <svg 132 + xmlns="http://www.w3.org/2000/svg" 133 + width="24" 134 + height="24" 135 + viewBox="0 0 24 24" 136 + fill="none" 137 + stroke="currentColor" 138 + stroke-width="2" 139 + stroke-linecap="round" 140 + stroke-linejoin="round" 141 + class="lucide lucide-aarrow-down-icon lucide-a-arrow-down" 142 + ><path d="m14 12 4 4 4-4" /><path d="M18 16V7" /><path 143 + d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" 144 + /><path d="M3.304 13h6.392" /></svg 145 + > 146 + </Button> 147 + <Button 148 + variant="ghost" 149 + onclick={() => { 150 + item.cardData.textSize = Math.min((item.cardData.textSize ?? 0) + 1, 5); 151 + }} 152 + disabled={(item.cardData.textSize ?? 0) > 4} 153 + > 154 + <svg 155 + xmlns="http://www.w3.org/2000/svg" 156 + width="24" 157 + height="24" 158 + viewBox="0 0 24 24" 159 + fill="none" 160 + stroke="currentColor" 161 + stroke-width="2" 162 + stroke-linecap="round" 163 + stroke-linejoin="round" 164 + class="lucide lucide-aarrow-up-icon lucide-a-arrow-up" 165 + ><path d="m14 11 4-4 4 4" /><path d="M18 16V7" /><path 166 + d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" 167 + /><path d="M3.304 13h6.392" /></svg 168 + > 169 + </Button> 170 + </div> 171 </div>
+10 -1
src/lib/cards/TextCard/index.ts
··· 24 }; 25 26 export const verticalAlignClasses: Record<string, string> = { 27 - top: 'items-start', 28 center: 'items-center-safe', 29 bottom: 'items-end-safe' 30 }; 31
··· 24 }; 25 26 export const verticalAlignClasses: Record<string, string> = { 27 + top: 'items-stretch', 28 center: 'items-center-safe', 29 bottom: 'items-end-safe' 30 }; 31 32 + export const textSizeClasses = [ 33 + 'text-lg', 34 + 'text-xl', 35 + 'text-2xl', 36 + 'text-3xl', 37 + 'text-4xl', 38 + 'text-5xl' 39 + ]; 40 +
+2
src/lib/cards/types.ts
··· 60 maxH?: number; 61 62 canResize?: boolean; 63 };
··· 60 maxH?: number; 61 62 canResize?: boolean; 63 + 64 + onUrlHandler?: (url: string, item: Item) => Item | null; 65 };
+17 -6
src/lib/cards/utils/MarkdownTextEditor.svelte
··· 10 import TurndownService from 'turndown'; 11 import { RichTextLink } from './extensions/RichTextLink'; 12 import type { Item } from '$lib/types'; 13 14 let element: HTMLElement | undefined = $state(); 15 - let editor: Editor | null = $state(null); 16 17 let loaded = $state(false); 18 19 let { 20 item = $bindable(), 21 placeholder = '', 22 defaultContent = '' 23 }: { 24 item: Item; 25 placeholder?: string; 26 defaultContent?: string; ··· 50 51 // parse to json 52 json = generateJSON(html, [ 53 - StarterKit.configure(), 54 Image.configure(), 55 RichTextLink.configure({ 56 openOnClick: false ··· 61 } 62 63 let extensions: Extensions = [ 64 - StarterKit.configure(), 65 Image.configure(), 66 Link.configure({ 67 openOnClick: false ··· 92 93 editorProps: { 94 attributes: { 95 - class: 'outline-none' 96 }, 97 - handleDOMEvents: { drop: () => true } 98 } 99 }); 100 ··· 108 }); 109 </script> 110 111 - <div bind:this={element}></div> 112 113 <style> 114 :global(.tiptap p.is-editor-empty:first-child::before) {
··· 10 import TurndownService from 'turndown'; 11 import { RichTextLink } from './extensions/RichTextLink'; 12 import type { Item } from '$lib/types'; 13 + import { textAlignClasses, verticalAlignClasses } from '../TextCard'; 14 15 let element: HTMLElement | undefined = $state(); 16 17 let loaded = $state(false); 18 19 let { 20 + editor = $bindable(), 21 item = $bindable(), 22 placeholder = '', 23 defaultContent = '' 24 }: { 25 + editor: Editor | null; 26 item: Item; 27 placeholder?: string; 28 defaultContent?: string; ··· 52 53 // parse to json 54 json = generateJSON(html, [ 55 + StarterKit.configure({ 56 + heading: false, 57 + bulletList: false, 58 + codeBlock: false 59 + }), 60 Image.configure(), 61 RichTextLink.configure({ 62 openOnClick: false ··· 67 } 68 69 let extensions: Extensions = [ 70 + StarterKit.configure({ 71 + heading: false, 72 + bulletList: false, 73 + codeBlock: false, 74 + dropcursor: false 75 + }), 76 Image.configure(), 77 Link.configure({ 78 openOnClick: false ··· 103 104 editorProps: { 105 attributes: { 106 + class: 'outline-none w-full' 107 }, 108 + handleDOMEvents: { drop: () => false } 109 } 110 }); 111 ··· 119 }); 120 </script> 121 122 + <div class="w-full" bind:this={element}></div> 123 124 <style> 125 :global(.tiptap p.is-editor-empty:first-child::before) {
+66
src/lib/components/ImageDropper.svelte
···
··· 1 + <script lang="ts"> 2 + import { Portal } from 'bits-ui'; 3 + 4 + let isDragOver = $state(false); 5 + 6 + let { 7 + processImageFile 8 + }: { 9 + processImageFile: (file: File) => Promise<void>; 10 + } = $props(); 11 + 12 + function handleDragOver(event: DragEvent) { 13 + event.preventDefault(); 14 + event.stopPropagation(); 15 + 16 + const dt = event.dataTransfer; 17 + if (!dt) return; 18 + 19 + let imageCount = 0; 20 + if (dt.items) { 21 + for (let i = 0; i < dt.items.length; i++) { 22 + const item = dt.items[i]; 23 + if (item && item.kind === 'file' && item.type.startsWith('image/')) { 24 + imageCount++; 25 + } 26 + } 27 + } else if (dt.files) { 28 + for (let i = 0; i < dt.files.length; i++) { 29 + const file = dt.files[i]; 30 + if (file?.type.startsWith('image/')) { 31 + imageCount++; 32 + } 33 + } 34 + } 35 + 36 + isDragOver = imageCount > 0; 37 + } 38 + function handleDragLeave(event: DragEvent) { 39 + event.preventDefault(); 40 + event.stopPropagation(); 41 + isDragOver = false; 42 + } 43 + async function handleDrop(event: DragEvent) { 44 + event.preventDefault(); 45 + event.stopPropagation(); 46 + isDragOver = false; 47 + if (!event.dataTransfer?.files?.length) return; 48 + for (const file of event.dataTransfer.files) { 49 + if (file?.type.startsWith('image/')) { 50 + await processImageFile(file); 51 + } 52 + } 53 + } 54 + </script> 55 + 56 + <svelte:window ondragover={handleDragOver} ondragleave={handleDragLeave} ondrop={handleDrop} /> 57 + 58 + {#if isDragOver} 59 + <Portal> 60 + <div 61 + class="bg-base-100/80 dark:bg-base-900/80 text-primary dark:text-base-100 pointer-events-none absolute inset-0 z-[1000] flex items-center justify-center text-4xl font-bold backdrop-blur-md" 62 + > 63 + Drop file to add it to your message 64 + </div> 65 + </Portal> 66 + {/if}