your personal website on atproto - mirror blento.app

align settigns for text cards, small fixes, layout changes

Florian 10276847 613d2ebd

+266 -55
+2 -2
src/lib/EditableWebsite.svelte
··· 271 271 <Profile {handle} {did} {data} /> 272 272 273 273 <div 274 - class="mx-auto max-w-2xl @5xl/wrapper:grid @5xl/wrapper:max-w-7xl @5xl/wrapper:grid-cols-4 @7xl/wrapper:grid-cols-3" 274 + class="mx-auto max-w-2xl @5xl/wrapper:grid @5xl/wrapper:max-w-7xl @5xl/wrapper:grid-cols-4" 275 275 > 276 276 <div></div> 277 277 <!-- svelte-ignore a11y_no_static_element_interactions --> ··· 334 334 activeDragElement.element = null; 335 335 return true; 336 336 }} 337 - class="@container/grid relative col-span-3 px-2 py-8 @5xl/wrapper:px-8 @7xl/wrapper:col-span-2" 337 + class="@container/grid relative col-span-3 px-2 py-8 @5xl/wrapper:px-8" 338 338 > 339 339 {#each items as item, i (item.id)} 340 340 <!-- {#if item !== activeDragElement.item} -->
+1 -1
src/lib/Profile.svelte
··· 29 29 30 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 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 @7xl/wrapper:w-1/3" 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 33 > 34 34 <div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24"> 35 35 <img
+2 -2
src/lib/Website.svelte
··· 39 39 <div class="@container/wrapper relative w-full"> 40 40 <Profile {handle} {did} {data} showEditButton={true} /> 41 41 42 - <div class="mx-auto max-w-2xl lg:grid lg:max-w-none lg:grid-cols-4 xl:grid-cols-3"> 42 + <div class="mx-auto max-w-2xl lg:grid lg:max-w-none lg:grid-cols-4"> 43 43 <div></div> 44 44 <div 45 45 bind:this={container} 46 - class="@container/grid relative col-span-3 px-2 py-8 lg:px-8 xl:col-span-2" 46 + class="@container/grid relative col-span-3 px-2 py-8 lg:px-8" 47 47 > 48 48 {#each items.toSorted(sortItems) as item} 49 49 <BaseCard {item}>
+39 -39
src/lib/cards/BaseCard/BaseEditingCard.svelte
··· 133 133 document.removeEventListener('pointerup', handleResizeEnd); 134 134 } 135 135 136 - 137 136 function canSetSize(w: number, h: number) { 138 137 if (!cardDef) return false; 139 138 140 - if(isMobile()) { 139 + if (isMobile()) { 141 140 w *= 2; 142 141 h *= 2; 143 142 } ··· 146 145 } 147 146 148 147 function setSize(w: number, h: number) { 149 - 150 - if(isMobile()) { 148 + if (isMobile()) { 151 149 w *= 2; 152 150 h *= 2; 153 151 } 154 152 onsetsize?.(w, h); 155 153 } 154 + 155 + let settingsPopoverOpen = $state(false); 156 156 </script> 157 157 158 158 <BaseCard {item} {...rest} isEditing={true} bind:ref showOutline={isResizing}> ··· 189 189 <div 190 190 class={[ 191 191 'absolute -bottom-7 w-full items-center justify-center text-xs group-focus-within:inline-flex group-hover:inline-flex', 192 - colorPopoverOpen ? 'inline-flex' : 'hidden' 192 + colorPopoverOpen || settingsPopoverOpen ? 'inline-flex' : 'hidden' 193 193 ]} 194 194 > 195 195 <div 196 - class="bg-base-100 z-[100] border-base-200 dark:bg-base-800 dark:border-base-700 inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg" 196 + class="bg-base-100 border-base-200 dark:bg-base-800 dark:border-base-700 z-[100] inline-flex items-center gap-0.5 rounded-2xl border p-1 px-2 shadow-lg" 197 197 > 198 198 <Popover bind:open={colorPopoverOpen}> 199 199 {#snippet child({ props })} ··· 233 233 class="w-64" 234 234 /> 235 235 </Popover> 236 - 237 236 238 237 {#if canSetSize(2, 2)} 239 238 <button ··· 284 283 </button> 285 284 {/if} 286 285 287 - {#if onshowsettings} 288 - <button 289 - onclick={() => { 290 - onshowsettings(); 291 - }} 292 - class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2" 293 - > 294 - <svg 295 - xmlns="http://www.w3.org/2000/svg" 296 - fill="none" 297 - viewBox="0 0 24 24" 298 - stroke-width="2" 299 - stroke="currentColor" 300 - class="size-5" 301 - > 302 - <path 303 - stroke-linecap="round" 304 - stroke-linejoin="round" 305 - d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" 306 - /> 307 - <path 308 - stroke-linecap="round" 309 - stroke-linejoin="round" 310 - d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" 311 - /> 312 - </svg> 313 - 314 - <span class="sr-only">open card settings</span> 315 - </button> 286 + {#if cardDef.settingsComponent} 287 + <Popover bind:open={settingsPopoverOpen}> 288 + {#snippet child({ props })} 289 + <button {...props} class="hover:bg-accent-500/10 cursor-pointer rounded-xl p-2"> 290 + <svg 291 + xmlns="http://www.w3.org/2000/svg" 292 + fill="none" 293 + viewBox="0 0 24 24" 294 + stroke-width="2" 295 + stroke="currentColor" 296 + class="size-5" 297 + > 298 + <path 299 + stroke-linecap="round" 300 + stroke-linejoin="round" 301 + d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" 302 + /> 303 + <path 304 + stroke-linecap="round" 305 + stroke-linejoin="round" 306 + d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" 307 + /> 308 + </svg> 309 + </button> 310 + {/snippet} 311 + <cardDef.settingsComponent bind:item /> 312 + </Popover> 316 313 {/if} 317 314 </div> 318 315 </div> ··· 320 317 {#if cardDef.canResize !== false} 321 318 <!-- Resize handle at bottom right corner --> 322 319 <!-- svelte-ignore a11y_no_static_element_interactions --> 323 - 324 - <div onpointerdown={handleResizeStart} class="absolute hidden group-hover:block right-0.5 bottom-0.5 pointer-events-auto cursor-se-resize bg-base-300/70 p-1 dark:bg-base-900/70 rounded-md rounded-br-3xl"> 320 + 321 + <div 322 + onpointerdown={handleResizeStart} 323 + class="bg-base-300/70 dark:bg-base-900/70 pointer-events-auto absolute right-0.5 bottom-0.5 hidden cursor-se-resize rounded-md rounded-br-3xl p-1 group-hover:block" 324 + > 325 325 <svg 326 326 xmlns="http://www.w3.org/2000/svg" 327 327 viewBox="0 0 24 24" ··· 349 349 /> 350 350 </svg> 351 351 <span class="sr-only">Resize card</span> 352 - </div> 352 + </div> 353 353 {/if} 354 354 {/if} 355 355 {/snippet}
+29 -1
src/lib/cards/LinkCard/EditingLinkCard.svelte
··· 6 6 let { item = $bindable() }: ContentComponentProps = $props(); 7 7 8 8 let isMobile = getIsMobile(); 9 + 10 + let faviconHasError = $state(false); 9 11 </script> 10 12 11 13 <div class="flex h-full flex-col justify-between p-4"> 12 14 <div> 13 15 {#if item.cardData.favicon} 14 - <img class="mb-2 size-8 rounded-lg object-cover" src={item.cardData.favicon} alt="" /> 16 + {#if !faviconHasError} 17 + <img 18 + class="mb-2 size-8 rounded-lg object-cover" 19 + onerror={() => (faviconHasError = true)} 20 + src={item.cardData.favicon} 21 + alt="" 22 + /> 23 + {:else} 24 + <div 25 + class="bg-base-100 border-base-300 dark:border-base-800 dark:bg-base-900 mb-2 inline-flex size-8 items-center justify-center rounded-lg border shadow-sm" 26 + > 27 + <svg 28 + xmlns="http://www.w3.org/2000/svg" 29 + fill="none" 30 + viewBox="0 0 24 24" 31 + stroke-width="1.5" 32 + stroke="currentColor" 33 + class="size-4" 34 + > 35 + <path 36 + stroke-linecap="round" 37 + stroke-linejoin="round" 38 + 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" 39 + /> 40 + </svg> 41 + </div> 42 + {/if} 15 43 {/if} 16 44 17 45 <div
+38 -5
src/lib/cards/LinkCard/LinkCard.svelte
··· 1 1 <script lang="ts"> 2 2 import { getIsMobile } from '$lib/helper'; 3 3 import type { ContentComponentProps } from '../types'; 4 - 4 + 5 5 let { item }: ContentComponentProps = $props(); 6 6 7 7 let isMobile = getIsMobile(); 8 + 9 + let faviconHasError = $state(false); 8 10 </script> 9 11 10 12 <div class="flex h-full flex-col justify-between p-4"> 11 13 <div> 12 14 {#if item.cardData.favicon} 13 - <img class="mb-2 size-8 rounded-lg object-cover" src={item.cardData.favicon} alt="" /> 15 + {#if !faviconHasError} 16 + <img 17 + class="mb-2 size-8 rounded-lg object-cover" 18 + onerror={() => (faviconHasError = true)} 19 + src={item.cardData.favicon} 20 + alt="" 21 + /> 22 + {:else} 23 + <div 24 + class="bg-base-100 border-base-300 dark:border-base-800 dark:bg-base-900 mb-2 inline-flex size-8 items-center justify-center rounded-lg border shadow-sm" 25 + > 26 + <svg 27 + xmlns="http://www.w3.org/2000/svg" 28 + fill="none" 29 + viewBox="0 0 24 24" 30 + stroke-width="1.5" 31 + stroke="currentColor" 32 + class="size-4" 33 + > 34 + <path 35 + stroke-linecap="round" 36 + stroke-linejoin="round" 37 + 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" 38 + /> 39 + </svg> 40 + </div> 41 + {/if} 14 42 {/if} 15 - <div class={["text-base-900 dark:text-base-50 text-lg font-semibold", 16 - ((isMobile() && item.mobileH < 8) || (!isMobile() && item.h < 4)) ? 'line-clamp-2' : '' 17 - ]}>{item.cardData.title}</div> 43 + <div 44 + class={[ 45 + 'text-base-900 dark:text-base-50 text-lg font-semibold', 46 + (isMobile() && item.mobileH < 8) || (!isMobile() && item.h < 4) ? 'line-clamp-2' : '' 47 + ]} 48 + > 49 + {item.cardData.title} 50 + </div> 18 51 <!-- <div class="text-base-800 dark:text-base-100 mt-2 text-xs">{item.cardData.description}</div> --> 19 52 <div class="text-accent-600 dark:text-accent-400 mt-2 text-xs font-light"> 20 53 {item.cardData.domain}
+6 -1
src/lib/cards/TextCard/EditingTextCard.svelte
··· 1 1 <script lang="ts"> 2 2 import type { Item } from '$lib/types'; 3 + import { textAlignClasses, verticalAlignClasses } from '.'; 3 4 import type { ContentComponentProps } from '../types'; 4 5 import MarkdownTextEditor from '../utils/MarkdownTextEditor.svelte'; 5 6 ··· 7 8 </script> 8 9 9 10 <div 10 - class="prose dark:prose-invert prose-base prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 prose-sm hover:bg-base-500/20 prose-p:first:mt-0 prose-p:last:mb-0 m-1 max-h-full overflow-y-scroll rounded-md p-1" 11 + class={[ 12 + 'prose dark:prose-invert prose-base prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 prose-sm 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', 13 + textAlignClasses[item.cardData.textAlign as string], 14 + verticalAlignClasses[item.cardData.verticalAlign as string] 15 + ]} 11 16 > 12 17 <MarkdownTextEditor bind:item /> 13 18 </div>
+7 -2
src/lib/cards/TextCard/TextCard.svelte
··· 1 1 <script lang="ts"> 2 2 import { marked } from 'marked'; 3 3 import type { ContentComponentProps } from '../types'; 4 + import { textAlignClasses, verticalAlignClasses } from '.'; 4 5 5 6 let { item }: ContentComponentProps = $props(); 6 7 ··· 10 11 </script> 11 12 12 13 <div 13 - class="prose dark:prose-invert prose-base prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 prose-sm prose-p:first:mt-0 prose-p:last:mb-0 m-1 max-h-full overflow-y-scroll rounded-md p-1 break-words" 14 + class={[ 15 + 'prose dark:prose-invert prose-base prose-a:no-underline prose-a:text-accent-600 dark:prose-a:text-accent-400 prose-sm prose-p:first:mt-0 prose-p:last:mb-0 inline-flex h-full w-full overflow-y-scroll rounded-md p-3', 16 + textAlignClasses?.[item.cardData.textAlign as string], 17 + verticalAlignClasses[item.cardData.verticalAlign as string] 18 + ]} 14 19 > 15 - {@html marked.parse(item.cardData.text ?? '', { renderer })} 20 + <span>{@html marked.parse(item.cardData.text ?? '', { renderer })}</span> 16 21 </div>
+122
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 + 8 + const classes = 'size-8 min-w-8 [&_svg]:size-3 cursor-pointer'; 9 + </script> 10 + 11 + <div class="flex flex-col gap-2"> 12 + <ToggleGroup 13 + type="single" 14 + bind:value={ 15 + () => { 16 + return item.cardData.textAlign ?? 'left'; 17 + }, 18 + (value) => { 19 + if (!value) return; 20 + item.cardData.textAlign = value; 21 + } 22 + } 23 + > 24 + <ToggleGroupItem size="sm" value="left" class={classes} 25 + ><svg 26 + xmlns="http://www.w3.org/2000/svg" 27 + viewBox="0 0 24 24" 28 + fill="none" 29 + stroke="currentColor" 30 + stroke-width="1.5" 31 + stroke-linecap="round" 32 + stroke-linejoin="round"><path d="M21 5H3" /><path d="M15 12H3" /><path d="M17 19H3" /></svg 33 + ></ToggleGroupItem 34 + > 35 + <ToggleGroupItem size="sm" value="center" class={classes} 36 + ><svg 37 + xmlns="http://www.w3.org/2000/svg" 38 + viewBox="0 0 24 24" 39 + fill="none" 40 + stroke="currentColor" 41 + stroke-width="1.5" 42 + stroke-linecap="round" 43 + stroke-linejoin="round"><path d="M21 5H3" /><path d="M17 12H7" /><path d="M19 19H5" /></svg 44 + ></ToggleGroupItem 45 + > 46 + <ToggleGroupItem size="sm" value="right" class={classes} 47 + ><svg 48 + xmlns="http://www.w3.org/2000/svg" 49 + viewBox="0 0 24 24" 50 + fill="none" 51 + stroke="currentColor" 52 + stroke-width="1.5" 53 + stroke-linecap="round" 54 + stroke-linejoin="round"><path d="M21 5H3" /><path d="M21 12H9" /><path d="M21 19H7" /></svg 55 + ></ToggleGroupItem 56 + > 57 + </ToggleGroup> 58 + 59 + <ToggleGroup 60 + type="single" 61 + bind:value={ 62 + () => { 63 + return item.cardData.verticalAlign ?? 'top'; 64 + }, 65 + (value) => { 66 + if (!value) return; 67 + item.cardData.verticalAlign = value; 68 + } 69 + } 70 + > 71 + <ToggleGroupItem size="sm" value="top" class={classes} 72 + ><svg 73 + xmlns="http://www.w3.org/2000/svg" 74 + viewBox="0 0 24 24" 75 + fill="none" 76 + stroke="currentColor" 77 + stroke-width="1.5" 78 + stroke-linecap="round" 79 + stroke-linejoin="round" 80 + ><rect width="6" height="16" x="4" y="6" rx="2" /><rect 81 + width="6" 82 + height="9" 83 + x="14" 84 + y="6" 85 + rx="2" 86 + /><path d="M22 2H2" /></svg 87 + > 88 + </ToggleGroupItem> 89 + <ToggleGroupItem size="sm" value="center" class={classes} 90 + ><svg 91 + xmlns="http://www.w3.org/2000/svg" 92 + viewBox="0 0 24 24" 93 + fill="none" 94 + stroke="currentColor" 95 + stroke-width="1.5" 96 + stroke-linecap="round" 97 + stroke-linejoin="round" 98 + ><rect width="10" height="6" x="7" y="9" rx="2" /><path d="M22 20H2" /><path 99 + d="M22 4H2" 100 + /></svg 101 + ></ToggleGroupItem 102 + > 103 + <ToggleGroupItem size="sm" value="bottom" class={classes} 104 + ><svg 105 + xmlns="http://www.w3.org/2000/svg" 106 + viewBox="0 0 24 24" 107 + fill="none" 108 + stroke="currentColor" 109 + stroke-width="1.5" 110 + stroke-linecap="round" 111 + stroke-linejoin="round" 112 + ><rect width="14" height="6" x="5" y="12" rx="2" /><rect 113 + width="10" 114 + height="6" 115 + x="7" 116 + y="2" 117 + rx="2" 118 + /><path d="M2 22h20" /></svg 119 + ></ToggleGroupItem 120 + > 121 + </ToggleGroup> 122 + </div>
+17 -1
src/lib/cards/TextCard/index.ts
··· 1 1 import type { CardDefinition } from '../types'; 2 2 import EditingTextCard from './EditingTextCard.svelte'; 3 3 import TextCard from './TextCard.svelte'; 4 + import TextCardSettings from './TextCardSettings.svelte'; 4 5 5 6 export const TextCardDefinition = { 6 7 type: 'text', ··· 11 12 card.cardData = { 12 13 text: 'hello world' 13 14 }; 14 - } 15 + }, 16 + 17 + settingsComponent: TextCardSettings 15 18 } as CardDefinition & { type: 'text' }; 19 + 20 + export const textAlignClasses: Record<string, string> = { 21 + left: '', 22 + center: 'text-center justify-center', 23 + right: 'text-end justify-end' 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 +
+2
src/lib/cards/types.ts
··· 37 37 sidebarComponent?: Component<SidebarComponentProps>; 38 38 sidebarButtonText?: string; 39 39 40 + settingsComponent?: Component<ContentComponentProps>; 41 + 40 42 loadData?: ( 41 43 items: Item[], 42 44 { did, handle, platform }: { did: string; handle: string; platform?: App.Platform }
+1 -1
src/lib/index.ts
··· 1 1 // place files you want to import through the `$lib` alias in this folder. 2 - export const margin = 20; 2 + export const margin = 16; 3 3 export const mobileMargin = 12; 4 4 5 5 export const COLUMNS = 8;