your personal website on atproto - mirror blento.app

refactor cards

Florian 13baa657 afb7a815

+395 -546
+1 -1
src/app.html
··· 4 <meta charset="utf-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 %sveltekit.head% 7 - 8 <script 9 defer 10 src="https://umami-wispy-dream-8048.fly.dev/script.js"
··· 4 <meta charset="utf-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 %sveltekit.head% 7 + 8 <script 9 defer 10 src="https://umami-wispy-dream-8048.fly.dev/script.js"
+16 -13
src/lib/EditableWebsite.svelte
··· 24 import type { CreationModalComponentProps } from './cards/types'; 25 import { dev } from '$app/environment'; 26 import { setDidContext } from './website/context'; 27 28 let { 29 handle, ··· 280 class="@container/grid relative col-span-3 px-2 py-8 @5xl/wrapper:px-8 @7xl/wrapper:col-span-2" 281 > 282 {#each items as item, i (item.id)} 283 - <EditingCard 284 - ondragstart={(e) => { 285 - const target = e.target as HTMLDivElement; 286 - activeDragElement.element = target; 287 - activeDragElement.w = item.w; 288 - activeDragElement.h = item.h; 289 - activeDragElement.item = item; 290 - 291 - const rect = target.getBoundingClientRect(); 292 - activeDragElement.mouseDeltaX = rect.left + margin - e.clientX; 293 - activeDragElement.mouseDeltaY = rect.top - e.clientY; 294 - }} 295 bind:item={items[i]} 296 ondelete={() => { 297 items = items.filter((it) => it !== item); ··· 307 308 fixCollisions(items, item, isMobile); 309 }} 310 - /> 311 {/each} 312 313 {#if activeDragElement.element && activeDragElement.x >= 0 && activeDragElement.item}
··· 24 import type { CreationModalComponentProps } from './cards/types'; 25 import { dev } from '$app/environment'; 26 import { setDidContext } from './website/context'; 27 + import BaseEditingCard from './cards/BaseCard/BaseEditingCard.svelte'; 28 29 let { 30 handle, ··· 281 class="@container/grid relative col-span-3 px-2 py-8 @5xl/wrapper:px-8 @7xl/wrapper:col-span-2" 282 > 283 {#each items as item, i (item.id)} 284 + <BaseEditingCard 285 bind:item={items[i]} 286 ondelete={() => { 287 items = items.filter((it) => it !== item); ··· 297 298 fixCollisions(items, item, isMobile); 299 }} 300 + ondragstart={(e) => { 301 + const target = e.target as HTMLDivElement; 302 + activeDragElement.element = target; 303 + activeDragElement.w = item.w; 304 + activeDragElement.h = item.h; 305 + activeDragElement.item = item; 306 + 307 + const rect = target.getBoundingClientRect(); 308 + activeDragElement.mouseDeltaX = rect.left + margin - e.clientX; 309 + activeDragElement.mouseDeltaY = rect.top - e.clientY; 310 + }} 311 + > 312 + <EditingCard bind:item={items[i]} /> 313 + </BaseEditingCard> 314 {/each} 315 316 {#if activeDragElement.element && activeDragElement.x >= 0 && activeDragElement.item}
+5 -2
src/lib/Website.svelte
··· 5 import type { Item } from './types'; 6 import { innerWidth } from 'svelte/reactivity/window'; 7 import { setDidContext } from './website/context'; 8 9 let { handle, did, items, data }: { handle: string; did: string; items: Item[]; data: any } = 10 $props(); ··· 36 class="@container/grid relative col-span-3 px-2 py-8 lg:px-8 xl:col-span-2" 37 > 38 {#each items.toSorted(sortItems) as item} 39 - <Card {item} /> 40 {/each} 41 <div style="height: {(maxHeight / 4) * 100}cqw;"></div> 42 </div> 43 </div> 44 45 - <div class="block text-xs font-light @5xl/wrapper:hidden mx-auto text-center pb-8"> 46 made with <a 47 href="https://blento.app" 48 target="_blank"
··· 5 import type { Item } from './types'; 6 import { innerWidth } from 'svelte/reactivity/window'; 7 import { setDidContext } from './website/context'; 8 + import BaseCard from './cards/BaseCard/BaseCard.svelte'; 9 10 let { handle, did, items, data }: { handle: string; did: string; items: Item[]; data: any } = 11 $props(); ··· 37 class="@container/grid relative col-span-3 px-2 py-8 lg:px-8 xl:col-span-2" 38 > 39 {#each items.toSorted(sortItems) as item} 40 + <BaseCard {item}> 41 + <Card {item} /> 42 + </BaseCard> 43 {/each} 44 <div style="height: {(maxHeight / 4) * 100}cqw;"></div> 45 </div> 46 </div> 47 48 + <div class="mx-auto block pb-8 text-center text-xs font-light @5xl/wrapper:hidden"> 49 made with <a 50 href="https://blento.app" 51 target="_blank"
-1
src/lib/cards/BaseCard/BaseCard.svelte
··· 4 import type { WithElementRef } from 'bits-ui'; 5 import type { Snippet } from 'svelte'; 6 import type { HTMLAttributes } from 'svelte/elements'; 7 - import { innerWidth } from 'svelte/reactivity/window'; 8 9 export type BaseCardProps = { 10 item: Item;
··· 4 import type { WithElementRef } from 'bits-ui'; 5 import type { Snippet } from 'svelte'; 6 import type { HTMLAttributes } from 'svelte/elements'; 7 8 export type BaseCardProps = { 9 item: Item;
+7 -11
src/lib/cards/BlueskyPostCard/BlueskyPostCard.svelte
··· 1 <script lang="ts"> 2 import { getAdditionalUserData } from '$lib/helper'; 3 - import BaseCard, { type BaseCardProps } from '../BaseCard/BaseCard.svelte'; 4 import { BlueskyPost } from '@foxui/social'; 5 - 6 - let { item, ...rest }: BaseCardProps = $props(); 7 8 const feed = getAdditionalUserData().recentPosts?.feed; 9 10 $inspect(feed); 11 </script> 12 13 - <BaseCard {item} {...rest}> 14 - <div class="flex max-h-full overflow-y-scroll p-4"> 15 - {#if feed?.[0].post} 16 - <BlueskyPost showLogo showReply={false} showBookmark={false} feedViewPost={feed?.[0].post} 17 - ></BlueskyPost> 18 - {:else}{/if} 19 - </div> 20 - </BaseCard>
··· 1 <script lang="ts"> 2 import { getAdditionalUserData } from '$lib/helper'; 3 import { BlueskyPost } from '@foxui/social'; 4 5 const feed = getAdditionalUserData().recentPosts?.feed; 6 7 $inspect(feed); 8 </script> 9 10 + <div class="flex max-h-full overflow-y-scroll p-4"> 11 + {#if feed?.[0].post} 12 + <BlueskyPost showLogo showBookmark={false} feedViewPost={feed?.[0].post}></BlueskyPost> 13 + {:else} 14 + Your latest bluesky post will appear here. 15 + {/if} 16 + </div>
-20
src/lib/cards/BlueskyPostCard/BlueskyPostEditingCard.svelte
··· 1 - <script lang="ts"> 2 - import { getAdditionalUserData } from '$lib/helper'; 3 - import type { Item } from '$lib/types'; 4 - import BaseEditingCard, { type BaseEditingCardProps } from '../BaseCard/BaseEditingCard.svelte'; 5 - import { BlueskyPost } from '@foxui/social'; 6 - 7 - let { item = $bindable<Item>(), ...rest }: BaseEditingCardProps = $props(); 8 - const feed = getAdditionalUserData().recentPosts?.feed; 9 - </script> 10 - 11 - <BaseEditingCard {item} {...rest}> 12 - <div class="flex max-h-full overflow-y-scroll p-4"> 13 - {#if feed?.[0].post} 14 - <BlueskyPost showLogo showReply={false} showBookmark={false} feedViewPost={feed?.[0].post} 15 - ></BlueskyPost> 16 - {:else} 17 - Your latest bluesky post will appear here. 18 - {/if} 19 - </div> 20 - </BaseEditingCard>
···
+5 -3
src/lib/cards/BlueskyPostCard/index.ts
··· 1 import type { CardDefinition } from '../types'; 2 - import BlueskyPostEditingCard from './BlueskyPostEditingCard.svelte'; 3 import BlueskyPostCard from './BlueskyPostCard.svelte'; 4 import SidebarItemBlueskyPostCard from './SidebarItemBlueskyPostCard.svelte'; 5 6 export const BlueskyPostCardDefinition = { 7 type: 'latestPost', 8 - cardComponent: BlueskyPostCard, 9 - editingCardComponent: BlueskyPostEditingCard, 10 createNew: (card) => { 11 card.cardType = 'latestPost'; 12 }, 13 sidebarComponent: SidebarItemBlueskyPostCard 14 } as CardDefinition & { type: 'latestPost' };
··· 1 import type { CardDefinition } from '../types'; 2 import BlueskyPostCard from './BlueskyPostCard.svelte'; 3 import SidebarItemBlueskyPostCard from './SidebarItemBlueskyPostCard.svelte'; 4 5 export const BlueskyPostCardDefinition = { 6 type: 'latestPost', 7 + contentComponent: BlueskyPostCard, 8 createNew: (card) => { 9 card.cardType = 'latestPost'; 10 + card.w = 2; 11 + card.mobileW = 4; 12 + card.h = 2; 13 + card.mobileH = 4; 14 }, 15 sidebarComponent: SidebarItemBlueskyPostCard 16 } as CardDefinition & { type: 'latestPost' };
+3 -5
src/lib/cards/Card/Card.svelte
··· 1 <script lang="ts"> 2 import { CardDefinitionsByType } from '..'; 3 - import BaseCard, { type BaseCardProps } from '../BaseCard/BaseCard.svelte'; 4 5 let { item, ref = $bindable(null), ...rest }: BaseCardProps = $props(); 6 </script> 7 8 {#if CardDefinitionsByType[item.cardType]} 9 {@const cardDef = CardDefinitionsByType[item.cardType]} 10 - <cardDef.cardComponent {item} {ref} {...rest} /> 11 {:else} 12 - <BaseCard {item} {...rest}> 13 - <div>Unsupported card type: {item.cardType}</div> 14 - </BaseCard> 15 {/if}
··· 1 <script lang="ts"> 2 import { CardDefinitionsByType } from '..'; 3 + import { type BaseCardProps } from '../BaseCard/BaseCard.svelte'; 4 5 let { item, ref = $bindable(null), ...rest }: BaseCardProps = $props(); 6 </script> 7 8 {#if CardDefinitionsByType[item.cardType]} 9 {@const cardDef = CardDefinitionsByType[item.cardType]} 10 + <cardDef.contentComponent {item} {...rest} /> 11 {:else} 12 + <div class="m-4">Unsupported card type: {item.cardType}</div> 13 {/if}
+8 -7
src/lib/cards/Card/EditingCard.svelte
··· 1 <script lang="ts"> 2 - import type { BaseEditingCardProps } from '../BaseCard/BaseEditingCard.svelte'; 3 import { CardDefinitionsByType } from '..'; 4 - import BaseCard from '../BaseCard/BaseCard.svelte'; 5 6 - let { item = $bindable(), ref = $bindable(), ...rest }: BaseEditingCardProps = $props(); 7 </script> 8 9 {#if CardDefinitionsByType[item.cardType]} 10 {@const cardDef = CardDefinitionsByType[item.cardType]} 11 - <cardDef.editingCardComponent bind:item bind:ref {...rest} /> 12 {:else} 13 - <BaseCard {item} {...rest}> 14 - <div>Unsupported card type: {item.cardType}</div> 15 - </BaseCard> 16 {/if}
··· 1 <script lang="ts"> 2 import { CardDefinitionsByType } from '..'; 3 + import type { ContentComponentProps } from '../types'; 4 5 + let { item = $bindable() }: ContentComponentProps = $props(); 6 </script> 7 8 {#if CardDefinitionsByType[item.cardType]} 9 {@const cardDef = CardDefinitionsByType[item.cardType]} 10 + {#if cardDef.editingContentComponent} 11 + <cardDef.editingContentComponent bind:item /> 12 + {:else} 13 + <cardDef.contentComponent bind:item /> 14 + {/if} 15 {:else} 16 + <div class="m-4">Unsupported card type: {item.cardType}</div> 17 {/if}
-63
src/lib/cards/ImageCard/EditingImageCard.svelte
··· 1 - <script lang="ts"> 2 - import { getDidContext } from '$lib/website/context'; 3 - import { getImageBlobUrl } from '$lib/website/utils'; 4 - import BaseEditingCard, { type BaseEditingCardProps } from '../BaseCard/BaseEditingCard.svelte'; 5 - 6 - let { item = $bindable(), ...rest }: BaseEditingCardProps = $props(); 7 - 8 - const did = getDidContext(); 9 - 10 - function getSrc() { 11 - if (item.cardData.objectUrl) return item.cardData.objectUrl; 12 - 13 - if (item.cardData.image && typeof item.cardData.image === 'object') { 14 - return getImageBlobUrl({ did, link: item.cardData.image?.ref?.$link }); 15 - } 16 - return item.cardData.image; 17 - } 18 - </script> 19 - 20 - <BaseEditingCard {item} {...rest}> 21 - {#key item.cardData.image || item.cardData.objectUrl} 22 - <img 23 - class={[ 24 - 'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out', 25 - item.cardData.href ? 'group-hover:scale-102' : '' 26 - ]} 27 - src={getSrc()} 28 - alt="" 29 - /> 30 - {/key} 31 - {#if item.cardData.href} 32 - <a 33 - href={item.cardData.href} 34 - class="absolute inset-0 h-full w-full" 35 - target="_blank" 36 - rel="noopener noreferrer" 37 - > 38 - <span class="sr-only"> 39 - {item.cardData.hrefText ?? 'Learn more'} 40 - </span> 41 - 42 - 43 - <div 44 - class="bg-base-800/30 border-base-900/30 absolute top-2 right-2 rounded-full border p-1 text-white opacity-50 backdrop-blur-lg group-focus-within:opacity-100 group-hover:opacity-100" 45 - > 46 - <svg 47 - xmlns="http://www.w3.org/2000/svg" 48 - fill="none" 49 - viewBox="0 0 24 24" 50 - stroke-width="2.5" 51 - stroke="currentColor" 52 - class="size-4" 53 - > 54 - <path 55 - stroke-linecap="round" 56 - stroke-linejoin="round" 57 - d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" 58 - /> 59 - </svg> 60 - </div> 61 - </a> 62 - {/if} 63 - </BaseEditingCard>
···
+41 -43
src/lib/cards/ImageCard/ImageCard.svelte
··· 1 <script lang="ts"> 2 import { getDidContext } from '$lib/website/context'; 3 import { getImageBlobUrl } from '$lib/website/utils'; 4 - import BaseCard, { type BaseCardProps } from '../BaseCard/BaseCard.svelte'; 5 6 - let { item, ...rest }: BaseCardProps = $props(); 7 8 const did = getDidContext(); 9 ··· 17 } 18 </script> 19 20 - <BaseCard {item} {...rest}> 21 - {#key item.cardData.image} 22 - <img 23 - class={[ 24 - 'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out', 25 - item.cardData.href ? 'group-hover:scale-102' : '' 26 - ]} 27 - src={getSrc()} 28 - alt="" 29 - /> 30 - {/key} 31 - {#if item.cardData.href} 32 - <a 33 - href={item.cardData.href} 34 - class="absolute inset-0 h-full w-full" 35 - target="_blank" 36 - rel="noopener noreferrer" 37 - > 38 - <span class="sr-only"> 39 - {item.cardData.hrefText ?? 'Learn more'} 40 - </span> 41 42 - <div 43 - class="bg-base-800/30 border-base-900/30 absolute top-2 right-2 rounded-full border p-1 text-white opacity-50 backdrop-blur-lg group-focus-within:opacity-100 group-hover:opacity-100" 44 > 45 - <svg 46 - xmlns="http://www.w3.org/2000/svg" 47 - fill="none" 48 - viewBox="0 0 24 24" 49 - stroke-width="2.5" 50 - stroke="currentColor" 51 - class="size-4" 52 - > 53 - <path 54 - stroke-linecap="round" 55 - stroke-linejoin="round" 56 - d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" 57 - /> 58 - </svg> 59 - </div> 60 - </a> 61 - {/if} 62 - </BaseCard>
··· 1 <script lang="ts"> 2 import { getDidContext } from '$lib/website/context'; 3 import { getImageBlobUrl } from '$lib/website/utils'; 4 + import type { ContentComponentProps } from '../types'; 5 6 + let { item = $bindable(), ...rest }: ContentComponentProps = $props(); 7 8 const did = getDidContext(); 9 ··· 17 } 18 </script> 19 20 + {#key item.cardData.image || item.cardData.objectUrl} 21 + <img 22 + class={[ 23 + 'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out', 24 + item.cardData.href ? 'group-hover:scale-102' : '' 25 + ]} 26 + src={getSrc()} 27 + alt="" 28 + /> 29 + {/key} 30 + {#if item.cardData.href} 31 + <a 32 + href={item.cardData.href} 33 + class="absolute inset-0 h-full w-full" 34 + target="_blank" 35 + rel="noopener noreferrer" 36 + > 37 + <span class="sr-only"> 38 + {item.cardData.hrefText ?? 'Learn more'} 39 + </span> 40 41 + <div 42 + class="bg-base-800/30 border-base-900/30 absolute top-2 right-2 rounded-full border p-1 text-white opacity-50 backdrop-blur-lg group-focus-within:opacity-100 group-hover:opacity-100" 43 + > 44 + <svg 45 + xmlns="http://www.w3.org/2000/svg" 46 + fill="none" 47 + viewBox="0 0 24 24" 48 + stroke-width="2.5" 49 + stroke="currentColor" 50 + class="size-4" 51 > 52 + <path 53 + stroke-linecap="round" 54 + stroke-linejoin="round" 55 + d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" 56 + /> 57 + </svg> 58 + </div> 59 + </a> 60 + {/if}
+1 -3
src/lib/cards/ImageCard/index.ts
··· 1 import { uploadBlob } from '$lib/website/utils'; 2 import type { CardDefinition } from '../types'; 3 import CreateImageCardModal from './CreateImageCardModal.svelte'; 4 - import EditingImageCard from './EditingImageCard.svelte'; 5 import ImageCard from './ImageCard.svelte'; 6 7 export const ImageCardDefinition = { 8 type: 'image', 9 - cardComponent: ImageCard, 10 - editingCardComponent: EditingImageCard, 11 createNew: (card) => { 12 card.cardType = 'image'; 13 card.cardData = {
··· 1 import { uploadBlob } from '$lib/website/utils'; 2 import type { CardDefinition } from '../types'; 3 import CreateImageCardModal from './CreateImageCardModal.svelte'; 4 import ImageCard from './ImageCard.svelte'; 5 6 export const ImageCardDefinition = { 7 type: 'image', 8 + contentComponent: ImageCard, 9 createNew: (card) => { 10 card.cardType = 'image'; 11 card.cardData = {
+24 -49
src/lib/cards/LinkCard/EditingLinkCard.svelte
··· 1 <script lang="ts"> 2 import { getIsMobile } from '$lib/helper'; 3 - import BaseEditingCard, { type BaseEditingCardProps } from '../BaseCard/BaseEditingCard.svelte'; 4 - import { innerWidth } from 'svelte/reactivity/window'; 5 import PlainTextEditor from '../utils/PlainTextEditor.svelte'; 6 7 - let { item = $bindable(), ...rest }: BaseEditingCardProps = $props(); 8 9 let isMobile = getIsMobile(); 10 </script> 11 12 - <BaseEditingCard {item} {...rest}> 13 - <div class="flex h-full flex-col justify-between p-4"> 14 - <div> 15 - {#if item.cardData.favicon} 16 - <img class="mb-2 size-8 rounded-lg object-cover" src={item.cardData.favicon} alt="" /> 17 - {/if} 18 19 - <div 20 - class="hover:bg-base-200/70 dark:hover:bg-base-800/70 -m-1 rounded-md p-1 transition-colors duration-200" 21 - > 22 - <PlainTextEditor 23 - class="text-base-900 dark:text-base-50 text-lg font-semibold" 24 - key="title" 25 - bind:item 26 - /> 27 - </div> 28 - <!-- <div class="text-base-800 dark:text-base-100 mt-2 text-xs">{item.cardData.description}</div> --> 29 - <div class="text-accent-600 dark:text-accent-400 mt-2 text-xs font-light"> 30 - {item.cardData.domain} 31 - </div> 32 </div> 33 - 34 - {#if ((isMobile() && item.mobileH >= 4) || (!isMobile() && item.h >= 2)) && item.cardData.image} 35 - <img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" /> 36 - {/if} 37 - <!-- {#key item.cardData.image} 38 - <img 39 - class={[ 40 - 'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out', 41 - item.cardData.href ? 'group-hover:scale-105' : '' 42 - ]} 43 - src={item.cardData.image} 44 - alt="" 45 - /> 46 - {/key} --> 47 - <!-- {#if item.cardData.href} 48 - <a 49 - href={item.cardData.href} 50 - class="absolute inset-0 h-full w-full" 51 - target="_blank" 52 - rel="noopener noreferrer" 53 - > 54 - <span class="sr-only"> 55 - {item.cardData.hrefText ?? 'Learn more'} 56 - </span> 57 - </a> 58 - {/if} --> 59 </div> 60 - </BaseEditingCard>
··· 1 <script lang="ts"> 2 import { getIsMobile } from '$lib/helper'; 3 + import type { ContentComponentProps } from '../types'; 4 import PlainTextEditor from '../utils/PlainTextEditor.svelte'; 5 6 + let { item = $bindable() }: ContentComponentProps = $props(); 7 8 let isMobile = getIsMobile(); 9 </script> 10 11 + <div class="flex h-full flex-col justify-between p-4"> 12 + <div> 13 + {#if item.cardData.favicon} 14 + <img class="mb-2 size-8 rounded-lg object-cover" src={item.cardData.favicon} alt="" /> 15 + {/if} 16 17 + <div 18 + class="hover:bg-base-200/70 dark:hover:bg-base-800/70 -m-1 rounded-md p-1 transition-colors duration-200" 19 + > 20 + <PlainTextEditor 21 + class="text-base-900 dark:text-base-50 text-lg font-semibold" 22 + key="title" 23 + bind:item 24 + /> 25 + </div> 26 + <!-- <div class="text-base-800 dark:text-base-100 mt-2 text-xs">{item.cardData.description}</div> --> 27 + <div class="text-accent-600 dark:text-accent-400 mt-2 text-xs font-light"> 28 + {item.cardData.domain} 29 </div> 30 </div> 31 + 32 + {#if ((isMobile() && item.mobileH >= 4) || (!isMobile() && item.h >= 2)) && item.cardData.image} 33 + <img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" /> 34 + {/if} 35 + </div>
+46 -48
src/lib/cards/LinkCard/LinkCard.svelte
··· 1 <script lang="ts"> 2 import { getIsMobile } from '$lib/helper'; 3 - import BaseCard, { type BaseCardProps } from '../BaseCard/BaseCard.svelte'; 4 - import { innerWidth } from 'svelte/reactivity/window'; 5 - let { item, ...rest }: BaseCardProps = $props(); 6 7 let isMobile = getIsMobile(); 8 </script> 9 10 - <BaseCard {item} {...rest}> 11 - <div class="flex h-full flex-col justify-between p-4"> 12 - <div> 13 - {#if item.cardData.favicon} 14 - <img class="mb-2 size-8 rounded-lg object-cover" src={item.cardData.favicon} alt="" /> 15 - {/if} 16 - <div class="text-base-900 dark:text-base-50 text-lg font-semibold">{item.cardData.title}</div> 17 - <!-- <div class="text-base-800 dark:text-base-100 mt-2 text-xs">{item.cardData.description}</div> --> 18 - <div class="text-accent-600 dark:text-accent-400 mt-2 text-xs font-light"> 19 - {item.cardData.domain} 20 - </div> 21 </div> 22 23 - {#if ((isMobile() && item.mobileH >= 4) || (!isMobile() && item.h >= 2)) && item.cardData.image} 24 - <img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" /> 25 - {/if} 26 - {#if item.cardData.href} 27 - <a 28 - href={item.cardData.href} 29 - class="absolute inset-0 h-full w-full" 30 - target="_blank" 31 - rel="noopener noreferrer" 32 - > 33 - <span class="sr-only"> 34 - {item.cardData.hrefText ?? 'Learn more'} 35 - </span> 36 37 - <div 38 - class="bg-base-800/30 border-base-900/30 absolute top-2 right-2 rounded-full border p-1 text-white opacity-50 backdrop-blur-lg group-focus-within:opacity-100 group-hover:opacity-100" 39 > 40 - <svg 41 - xmlns="http://www.w3.org/2000/svg" 42 - fill="none" 43 - viewBox="0 0 24 24" 44 - stroke-width="2.5" 45 - stroke="currentColor" 46 - class="size-4" 47 - > 48 - <path 49 - stroke-linecap="round" 50 - stroke-linejoin="round" 51 - d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" 52 - /> 53 - </svg> 54 - </div> 55 - </a> 56 - {/if} 57 - </div> 58 - </BaseCard>
··· 1 <script lang="ts"> 2 import { getIsMobile } from '$lib/helper'; 3 + import type { ContentComponentProps } from '../types'; 4 + 5 + let { item }: ContentComponentProps = $props(); 6 7 let isMobile = getIsMobile(); 8 </script> 9 10 + <div class="flex h-full flex-col justify-between p-4"> 11 + <div> 12 + {#if item.cardData.favicon} 13 + <img class="mb-2 size-8 rounded-lg object-cover" src={item.cardData.favicon} alt="" /> 14 + {/if} 15 + <div class="text-base-900 dark:text-base-50 text-lg font-semibold">{item.cardData.title}</div> 16 + <!-- <div class="text-base-800 dark:text-base-100 mt-2 text-xs">{item.cardData.description}</div> --> 17 + <div class="text-accent-600 dark:text-accent-400 mt-2 text-xs font-light"> 18 + {item.cardData.domain} 19 </div> 20 + </div> 21 22 + {#if ((isMobile() && item.mobileH >= 4) || (!isMobile() && item.h >= 2)) && item.cardData.image} 23 + <img class=" mb-2 max-h-32 w-full rounded-xl object-cover" src={item.cardData.image} alt="" /> 24 + {/if} 25 + {#if item.cardData.href} 26 + <a 27 + href={item.cardData.href} 28 + class="absolute inset-0 h-full w-full" 29 + target="_blank" 30 + rel="noopener noreferrer" 31 + > 32 + <span class="sr-only"> 33 + {item.cardData.hrefText ?? 'Learn more'} 34 + </span> 35 36 + <div 37 + class="bg-base-800/30 border-base-900/30 absolute top-2 right-2 rounded-full border p-1 text-white opacity-50 backdrop-blur-lg group-focus-within:opacity-100 group-hover:opacity-100" 38 + > 39 + <svg 40 + xmlns="http://www.w3.org/2000/svg" 41 + fill="none" 42 + viewBox="0 0 24 24" 43 + stroke-width="2.5" 44 + stroke="currentColor" 45 + class="size-4" 46 > 47 + <path 48 + stroke-linecap="round" 49 + stroke-linejoin="round" 50 + d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" 51 + /> 52 + </svg> 53 + </div> 54 + </a> 55 + {/if} 56 + </div>
+2 -2
src/lib/cards/LinkCard/index.ts
··· 5 6 export const LinkCardDefinition = { 7 type: 'link', 8 - cardComponent: LinkCard, 9 - editingCardComponent: EditingLinkCard, 10 createNew: (card) => { 11 card.cardType = 'link'; 12 card.cardData = {
··· 5 6 export const LinkCardDefinition = { 7 type: 'link', 8 + contentComponent: LinkCard, 9 + editingContentComponent: EditingLinkCard, 10 createNew: (card) => { 11 card.cardType = 'link'; 12 card.cardData = {
-13
src/lib/cards/SpecialCards/UpdatedBlentos/EditingUpdatedBlentosCard.svelte
··· 1 - <script lang="ts"> 2 - import type { Item } from '$lib/types'; 3 - import BaseEditingCard, { 4 - type BaseEditingCardProps 5 - } from '../../BaseCard/BaseEditingCard.svelte'; 6 - import MainUpdatedBlentosCards from './MainUpdatedBlentosCards.svelte'; 7 - 8 - let { item = $bindable<Item>(), ...rest }: BaseEditingCardProps = $props(); 9 - </script> 10 - 11 - <BaseEditingCard {item} {...rest}> 12 - <MainUpdatedBlentosCards /> 13 - </BaseEditingCard>
···
-46
src/lib/cards/SpecialCards/UpdatedBlentos/MainUpdatedBlentosCards.svelte
··· 1 - <script lang="ts"> 2 - import { getAdditionalUserData } from '$lib/helper'; 3 - import { getProfile } from '$lib/oauth/atproto'; 4 - import type { ProfileViewDetailed } from '@atproto/api/dist/client/types/app/bsky/actor/defs'; 5 - import { onMount } from 'svelte'; 6 - 7 - const recentRecords = getAdditionalUserData().recentRecords; 8 - 9 - let profiles: ProfileViewDetailed[] = $state([]); 10 - 11 - onMount(async () => { 12 - console.log(recentRecords); 13 - let uniqueDids = new Set<string>(); 14 - for (let record of recentRecords as { did: string }[]) { 15 - uniqueDids.add(record.did); 16 - } 17 - console.log(uniqueDids, Array.from(uniqueDids)); 18 - 19 - for (let did of Array.from(uniqueDids)) { 20 - console.log(did); 21 - const profile = await getProfile({ did }); 22 - profiles.push(profile); 23 - 24 - if (profiles.length > 9) return; 25 - } 26 - }); 27 - </script> 28 - 29 - <div class="pointer-events-none"> 30 - <div 31 - class="from-base-50 dark:from-base-950 absolute inset-0 bg-gradient-to-t to-transparent" 32 - ></div> 33 - <div class="absolute bottom-3 left-4 text-sm font-semibold">recently updated blentos</div> 34 - </div> 35 - <div class="flex h-full max-w-full items-center gap-4 overflow-x-scroll px-8"> 36 - {#each profiles as profile} 37 - <a 38 - href="/{profile.handle}" 39 - class=" hover:bg-base-200 dark:hover:bg-base-800 mb-4 flex h-fit min-w-24 flex-col items-center justify-center gap-2 rounded-xl p-2" 40 - target="_blank" 41 - > 42 - <div class="font-semibold line-clamp-2">{profile.displayName || profile.handle}</div> 43 - <img src={profile.avatar} class="aspect-square size-20 rounded-full" alt="" /> 44 - </a> 45 - {/each} 46 - </div>
···
+44 -10
src/lib/cards/SpecialCards/UpdatedBlentos/UpdatedBlentosCard.svelte
··· 1 <script lang="ts"> 2 - import { marked } from 'marked'; 3 - import BaseCard, { type BaseCardProps } from '../../BaseCard/BaseCard.svelte'; 4 - import MainUpdatedBlentosCards from './MainUpdatedBlentosCards.svelte'; 5 6 - let { item, ...rest }: BaseCardProps = $props(); 7 8 - const renderer = new marked.Renderer(); 9 - renderer.link = ({ href, title, text }) => 10 - `<a target="_blank" href="${href}" title="${title}">${text}</a>`; 11 </script> 12 13 - <BaseCard {item} {...rest}> 14 - <MainUpdatedBlentosCards /> 15 - </BaseCard>
··· 1 <script lang="ts"> 2 + import type { ContentComponentProps } from '$lib/cards/types'; 3 + import { getAdditionalUserData } from '$lib/helper'; 4 + import { getProfile } from '$lib/oauth/atproto'; 5 + import type { ProfileViewDetailed } from '@atproto/api/dist/client/types/app/bsky/actor/defs'; 6 + import { onMount } from 'svelte'; 7 + 8 + let { item }: ContentComponentProps = $props(); 9 + 10 + const recentRecords = getAdditionalUserData().recentRecords; 11 + 12 + let profiles: ProfileViewDetailed[] = $state([]); 13 14 + onMount(async () => { 15 + console.log(recentRecords); 16 + let uniqueDids = new Set<string>(); 17 + for (let record of recentRecords as { did: string }[]) { 18 + uniqueDids.add(record.did); 19 + } 20 + console.log(uniqueDids, Array.from(uniqueDids)); 21 22 + for (let did of Array.from(uniqueDids)) { 23 + console.log(did); 24 + const profile = await getProfile({ did }); 25 + profiles.push(profile); 26 + 27 + if (profiles.length > 9) return; 28 + } 29 + }); 30 </script> 31 32 + <div class="pointer-events-none"> 33 + <div 34 + class="from-base-50 dark:from-base-950 absolute inset-0 bg-gradient-to-t to-transparent" 35 + ></div> 36 + <div class="absolute bottom-3 left-4 text-sm font-semibold">recently updated blentos</div> 37 + </div> 38 + <div class="flex h-full max-w-full items-center gap-4 overflow-x-scroll px-8"> 39 + {#each profiles as profile} 40 + <a 41 + href="/{profile.handle}" 42 + class=" hover:bg-base-200 dark:hover:bg-base-800 mb-4 flex h-fit min-w-24 flex-col items-center justify-center gap-2 rounded-xl p-2" 43 + target="_blank" 44 + > 45 + <div class="line-clamp-2 font-semibold">{profile.displayName || profile.handle}</div> 46 + <img src={profile.avatar} class="aspect-square size-20 rounded-full" alt="" /> 47 + </a> 48 + {/each} 49 + </div>
+1 -3
src/lib/cards/SpecialCards/UpdatedBlentos/index.ts
··· 1 import type { CardDefinition } from '../../types'; 2 - import EditingUpdatedBlentosCard from './EditingUpdatedBlentosCard.svelte'; 3 import UpdatedBlentosCard from './UpdatedBlentosCard.svelte'; 4 5 export const UpdatedBlentosCardDefitition = { 6 type: 'updatedBlentos', 7 - cardComponent: UpdatedBlentosCard, 8 - editingCardComponent: EditingUpdatedBlentosCard 9 } as CardDefinition & { type: 'updatedBlentos' };
··· 1 import type { CardDefinition } from '../../types'; 2 import UpdatedBlentosCard from './UpdatedBlentosCard.svelte'; 3 4 export const UpdatedBlentosCardDefitition = { 5 type: 'updatedBlentos', 6 + contentComponent: UpdatedBlentosCard 7 } as CardDefinition & { type: 'updatedBlentos' };
+7 -9
src/lib/cards/TextCard/EditingTextCard.svelte
··· 1 <script lang="ts"> 2 import type { Item } from '$lib/types'; 3 - import BaseEditingCard, { type BaseEditingCardProps } from '../BaseCard/BaseEditingCard.svelte'; 4 import MarkdownTextEditor from '../utils/MarkdownTextEditor.svelte'; 5 6 - let { item = $bindable<Item>(), ...rest }: BaseEditingCardProps = $props(); 7 </script> 8 9 - <BaseEditingCard {item} {...rest}> 10 - <div 11 - 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 rounded-md p-1 overflow-y-scroll max-h-full" 12 - > 13 - <MarkdownTextEditor bind:item /> 14 - </div> 15 - </BaseEditingCard>
··· 1 <script lang="ts"> 2 import type { Item } from '$lib/types'; 3 + import type { ContentComponentProps } from '../types'; 4 import MarkdownTextEditor from '../utils/MarkdownTextEditor.svelte'; 5 6 + let { item = $bindable<Item>() }: ContentComponentProps = $props(); 7 </script> 8 9 + <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 + > 12 + <MarkdownTextEditor bind:item /> 13 + </div>
+7 -9
src/lib/cards/TextCard/TextCard.svelte
··· 1 <script lang="ts"> 2 import { marked } from 'marked'; 3 - import BaseCard, { type BaseCardProps } from '../BaseCard/BaseCard.svelte'; 4 5 - let { item, ...rest }: BaseCardProps = $props(); 6 7 const renderer = new marked.Renderer(); 8 renderer.link = ({ href, title, text }) => 9 `<a target="_blank" href="${href}" title="${title}">${text}</a>`; 10 </script> 11 12 - <BaseCard {item} {...rest}> 13 - <div 14 - 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 rounded-md p-1 break-words overflow-y-scroll max-h-full" 15 - > 16 - {@html marked.parse(item.cardData.text ?? '', { renderer })} 17 - </div> 18 - </BaseCard>
··· 1 <script lang="ts"> 2 import { marked } from 'marked'; 3 + import type { ContentComponentProps } from '../types'; 4 5 + let { item }: ContentComponentProps = $props(); 6 7 const renderer = new marked.Renderer(); 8 renderer.link = ({ href, title, text }) => 9 `<a target="_blank" href="${href}" title="${title}">${text}</a>`; 10 </script> 11 12 + <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 + > 15 + {@html marked.parse(item.cardData.text ?? '', { renderer })} 16 + </div>
+2 -2
src/lib/cards/TextCard/index.ts
··· 4 5 export const TextCardDefinition = { 6 type: 'text', 7 - cardComponent: TextCard, 8 - editingCardComponent: EditingTextCard, 9 createNew: (card) => { 10 card.cardType = 'text'; 11 card.cardData = {
··· 4 5 export const TextCardDefinition = { 6 type: 'text', 7 + contentComponent: TextCard, 8 + editingContentComponent: EditingTextCard, 9 createNew: (card) => { 10 card.cardType = 'text'; 11 card.cardData = {
-35
src/lib/cards/YoutubeVideo/EditingYoutubeCard.svelte
··· 1 - <script lang="ts"> 2 - import type { Item } from '$lib/types'; 3 - import BaseEditingCard, { type BaseEditingCardProps } from '../BaseCard/BaseEditingCard.svelte'; 4 - import { videoPlayer } from '../utils/YoutubeVideoPlayer.svelte'; 5 - 6 - let { item = $bindable<Item>(), ...rest }: BaseEditingCardProps = $props(); 7 - </script> 8 - 9 - <BaseEditingCard {item} {...rest}> 10 - <img 11 - class={[ 12 - 'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out', 13 - item.cardData.href ? 'group-hover:scale-102' : '' 14 - ]} 15 - src={item.cardData.poster} 16 - alt="" 17 - /> 18 - <button 19 - onclick={() => { 20 - videoPlayer.show(item.cardData.youtubeId); 21 - }} 22 - class="absolute inset-0 flex h-full w-full cursor-pointer items-center justify-center" 23 - > 24 - <span class="sr-only"> 25 - {item.cardData.hrefText ?? 'Learn more'} 26 - </span> 27 - 28 - <svg xmlns="http://www.w3.org/2000/svg" class="text-accent-500 w-14" viewBox="0 0 256 180" 29 - ><path 30 - fill="currentColor" 31 - d="M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134" 32 - /><path fill="#fff" d="m102.421 128.06l66.328-38.418l-66.328-38.418z" /></svg 33 - > 34 - </button> 35 - </BaseEditingCard>
···
+26 -33
src/lib/cards/YoutubeVideo/YoutubeCard.svelte
··· 1 <script lang="ts"> 2 - import { marked } from 'marked'; 3 - import BaseCard, { type BaseCardProps } from '../BaseCard/BaseCard.svelte'; 4 import { videoPlayer } from '../utils/YoutubeVideoPlayer.svelte'; 5 - 6 - let { item, ...rest }: BaseCardProps = $props(); 7 8 - const renderer = new marked.Renderer(); 9 - renderer.link = ({ href, title, text }) => 10 - `<a target="_blank" href="${href}" title="${title}">${text}</a>`; 11 </script> 12 13 - <BaseCard {item} {...rest}> 14 - <img 15 - class={[ 16 - 'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out', 17 - item.cardData.href ? 'group-hover:scale-102' : '' 18 - ]} 19 - src={item.cardData.poster} 20 - alt="" 21 - /> 22 - <button 23 - onclick={() => { 24 - videoPlayer.show(item.cardData.youtubeId); 25 - }} 26 - class="absolute inset-0 flex h-full w-full cursor-pointer items-center justify-center" 27 > 28 - <span class="sr-only"> 29 - {item.cardData.hrefText ?? 'Learn more'} 30 - </span> 31 - 32 - <svg xmlns="http://www.w3.org/2000/svg" class="text-accent-500 w-14" viewBox="0 0 256 180" 33 - ><path 34 - fill="currentColor" 35 - d="M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134" 36 - /><path fill="#fff" d="m102.421 128.06l66.328-38.418l-66.328-38.418z" /></svg 37 - > 38 - </button></BaseCard 39 - >
··· 1 <script lang="ts"> 2 import { videoPlayer } from '../utils/YoutubeVideoPlayer.svelte'; 3 + import type { ContentComponentProps } from '../types'; 4 5 + let { item }: ContentComponentProps = $props(); 6 </script> 7 8 + <img 9 + class={[ 10 + 'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out', 11 + item.cardData.href ? 'group-hover:scale-102' : '' 12 + ]} 13 + src={item.cardData.poster} 14 + alt="" 15 + /> 16 + <button 17 + onclick={() => { 18 + videoPlayer.show(item.cardData.youtubeId); 19 + }} 20 + class="absolute inset-0 flex h-full w-full cursor-pointer items-center justify-center" 21 + > 22 + <span class="sr-only"> 23 + {item.cardData.hrefText ?? 'Learn more'} 24 + </span> 25 + 26 + <svg xmlns="http://www.w3.org/2000/svg" class="text-accent-500 w-14" viewBox="0 0 256 180" 27 + ><path 28 + fill="currentColor" 29 + d="M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134" 30 + /><path fill="#fff" d="m102.421 128.06l66.328-38.418l-66.328-38.418z" /></svg 31 > 32 + </button>
+1 -3
src/lib/cards/YoutubeVideo/index.ts
··· 1 import type { CardDefinition } from '../types'; 2 import CreateYoutubeCardModal from './CreateYoutubeCardModal.svelte'; 3 - import EditingYoutubeCard from './EditingYoutubeCard.svelte'; 4 import SidebarItemYoutubeCard from './SidebarItemYoutubeCard.svelte'; 5 import YoutubeCard from './YoutubeCard.svelte'; 6 7 export const YoutubeCardDefinition = { 8 type: 'youtubeVideo', 9 - cardComponent: YoutubeCard, 10 - editingCardComponent: EditingYoutubeCard, 11 creationModalComponent: CreateYoutubeCardModal, 12 createNew: (card) => { 13 card.cardType = 'youtubeVideo';
··· 1 import type { CardDefinition } from '../types'; 2 import CreateYoutubeCardModal from './CreateYoutubeCardModal.svelte'; 3 import SidebarItemYoutubeCard from './SidebarItemYoutubeCard.svelte'; 4 import YoutubeCard from './YoutubeCard.svelte'; 5 6 export const YoutubeCardDefinition = { 7 type: 'youtubeVideo', 8 + contentComponent: YoutubeCard, 9 creationModalComponent: CreateYoutubeCardModal, 10 createNew: (card) => { 11 card.cardType = 'youtubeVideo';
+7 -4
src/lib/cards/types.ts
··· 1 import type { Component } from 'svelte'; 2 - import type { BaseCardProps } from './BaseCard/BaseCard.svelte'; 3 import type { Item } from '$lib/types'; 4 - import type { BaseEditingCardProps } from './BaseCard/BaseEditingCard.svelte'; 5 6 export type CreationModalComponentProps = { 7 item: Item; ··· 19 onclick: () => void; 20 }; 21 22 export type CardDefinition = { 23 - cardComponent: Component<BaseCardProps>; 24 - editingCardComponent: Component<BaseEditingCardProps>; 25 createNew?: (item: Item) => void; 26 creationModalComponent?: Component<CreationModalComponentProps>; 27 settingsModalComponent?: Component<{
··· 1 import type { Component } from 'svelte'; 2 import type { Item } from '$lib/types'; 3 4 export type CreationModalComponentProps = { 5 item: Item; ··· 17 onclick: () => void; 18 }; 19 20 + export type ContentComponentProps = { 21 + item: Item; 22 + }; 23 + 24 export type CardDefinition = { 25 + contentComponent: Component<ContentComponentProps>; 26 + editingContentComponent?: Component<ContentComponentProps>; 27 + 28 createNew?: (item: Item) => void; 29 creationModalComponent?: Component<CreationModalComponentProps>; 30 settingsModalComponent?: Component<{
+138
src/lib/website/load.ts
···
··· 1 + import { 2 + type Collection, 3 + type DownloadedData, 4 + type IndividualCollections, 5 + type ListCollections 6 + } from './types'; 7 + import { getRecord, listRecords, resolveHandle } from '$lib/oauth/atproto'; 8 + import type { Record as ListRecord } from '@atproto/api/dist/client/types/com/atproto/repo/listRecords'; 9 + import { data } from './data'; 10 + import { AtpBaseClient } from '@atproto/api'; 11 + import { env } from '$env/dynamic/private'; 12 + 13 + export async function loadData(handle: string) { 14 + const did = await resolveHandle({ handle }); 15 + 16 + const downloadedData = {} as DownloadedData; 17 + 18 + const promises: { 19 + collection: string; 20 + rkey?: string; 21 + record: Promise<ListRecord> | Promise<Record<string, ListRecord>>; 22 + }[] = []; 23 + 24 + for (const collection of Object.keys(data) as Collection[]) { 25 + const cfg = data[collection]; 26 + 27 + try { 28 + if (Array.isArray(cfg)) { 29 + for (const rkey of cfg) { 30 + const record = getRecord({ did, collection, rkey }); 31 + promises.push({ 32 + collection, 33 + rkey, 34 + record 35 + }); 36 + } 37 + } else if (cfg === 'all') { 38 + const records = listRecords({ did, collection }); 39 + promises.push({ collection, record: records }); 40 + } 41 + } catch (error) { 42 + console.error('failed getting', collection, cfg, error); 43 + } 44 + } 45 + 46 + await Promise.all(promises.map((v) => v.record)); 47 + 48 + for (const promise of promises) { 49 + if (promise.rkey) { 50 + downloadedData[promise.collection as IndividualCollections] ??= {} as Record< 51 + string, 52 + ListRecord 53 + >; 54 + downloadedData[promise.collection as IndividualCollections][promise.rkey] = 55 + (await promise.record) as ListRecord; 56 + } else { 57 + downloadedData[promise.collection as ListCollections] ??= (await promise.record) as Record< 58 + string, 59 + ListRecord 60 + >; 61 + } 62 + } 63 + 64 + const cardTypes = new Set( 65 + Object.values(downloadedData['app.blento.card']).map((v) => v.value.cardType) 66 + ); 67 + 68 + let recentRecords; 69 + if (cardTypes.has('updatedBlentos')) { 70 + try { 71 + // https://ufos-api.microcosm.blue/records?collection=app.blento.card 72 + const response = await fetch( 73 + 'https://ufos-api.microcosm.blue/records?collection=app.blento.card' 74 + ); 75 + recentRecords = await response.json(); 76 + } catch (error) { 77 + console.error('failed to fetch recent records', error); 78 + } 79 + } 80 + 81 + let recentPosts; 82 + 83 + if (cardTypes.has('latestPost')) { 84 + try { 85 + const agent = new AtpBaseClient({ service: 'https://api.bsky.app' }); 86 + const authorFeed = await agent.app.bsky.feed.getAuthorFeed({ 87 + actor: did, 88 + filter: 'posts_no_replies', 89 + limit: 2 90 + }); 91 + console.log(authorFeed.data); 92 + recentPosts = JSON.parse(JSON.stringify(authorFeed.data)); 93 + } catch (error) { 94 + console.error('failed to fetch recent posts', error); 95 + } 96 + } 97 + 98 + let metrics; 99 + // try { 100 + // const endAt = Date.now(); 101 + 102 + // const startAt = new Date(); 103 + // startAt.setFullYear(startAt.getFullYear() - 1); 104 + 105 + // const params = new URLSearchParams({ 106 + // startAt: startAt.getTime().toString(), 107 + // endAt: endAt.toString(), 108 + // unit: 'year', 109 + // timezone: 'America/Los_Angeles', 110 + // path: '/' + handle 111 + // }); 112 + 113 + // const url = `https://umami-wispy-dream-8048.fly.dev/api/websites/${env.ANALYTICS_WEBSITE_ID}/stats?${params}`; 114 + 115 + // console.log(url); 116 + // const metricsResponse = await fetch(url, { 117 + // method: 'GET', 118 + // headers: { 119 + // Authorization: 'Bearer ' + env.ANALYTICS_TOKEN 120 + // } 121 + // }); 122 + 123 + // metrics = await metricsResponse.json(); 124 + // console.log(metrics); 125 + // } catch (error) { 126 + // console.error(error); 127 + // } 128 + 129 + return { 130 + did, 131 + data: JSON.parse(JSON.stringify(downloadedData)) as DownloadedData, 132 + additionalData: { 133 + recentRecords, 134 + recentPosts, 135 + metrics 136 + } 137 + }; 138 + }
-105
src/lib/website/utils.ts
··· 1 - import { 2 - type Collection, 3 - type DownloadedData, 4 - type IndividualCollections, 5 - type ListCollections 6 - } from './types'; 7 - import { getRecord, listRecords, resolveHandle } from '$lib/oauth/atproto'; 8 - import type { Record as ListRecord } from '@atproto/api/dist/client/types/com/atproto/repo/listRecords'; 9 - import { data } from './data'; 10 import { client } from '$lib/oauth'; 11 - import { AtpBaseClient } from '@atproto/api'; 12 13 export function parseUri(uri: string) { 14 const [did, collection, rkey] = uri.split('/').slice(2); ··· 16 collection: `${string}.${string}.${string}`; 17 rkey: string; 18 did: string; 19 - }; 20 - } 21 - 22 - export async function loadData(handle: string) { 23 - const did = await resolveHandle({ handle }); 24 - 25 - const downloadedData = {} as DownloadedData; 26 - 27 - const promises: { 28 - collection: string; 29 - rkey?: string; 30 - record: Promise<ListRecord> | Promise<Record<string, ListRecord>>; 31 - }[] = []; 32 - 33 - for (const collection of Object.keys(data) as Collection[]) { 34 - const cfg = data[collection]; 35 - 36 - try { 37 - if (Array.isArray(cfg)) { 38 - for (const rkey of cfg) { 39 - const record = getRecord({ did, collection, rkey }); 40 - promises.push({ 41 - collection, 42 - rkey, 43 - record 44 - }); 45 - } 46 - } else if (cfg === 'all') { 47 - const records = listRecords({ did, collection }); 48 - promises.push({ collection, record: records }); 49 - } 50 - } catch (error) { 51 - console.error('failed getting', collection, cfg, error); 52 - } 53 - } 54 - 55 - await Promise.all(promises.map((v) => v.record)); 56 - 57 - for (const promise of promises) { 58 - if (promise.rkey) { 59 - downloadedData[promise.collection as IndividualCollections] ??= {} as Record< 60 - string, 61 - ListRecord 62 - >; 63 - downloadedData[promise.collection as IndividualCollections][promise.rkey] = 64 - (await promise.record) as ListRecord; 65 - } else { 66 - downloadedData[promise.collection as ListCollections] ??= (await promise.record) as Record< 67 - string, 68 - ListRecord 69 - >; 70 - } 71 - } 72 - 73 - const cardTypes = new Set( 74 - Object.values(downloadedData['app.blento.card']).map((v) => v.value.cardType) 75 - ); 76 - 77 - let recentRecords; 78 - if (cardTypes.has('updatedBlentos')) { 79 - try { 80 - // https://ufos-api.microcosm.blue/records?collection=app.blento.card 81 - const response = await fetch( 82 - 'https://ufos-api.microcosm.blue/records?collection=app.blento.card' 83 - ); 84 - recentRecords = await response.json(); 85 - } catch (error) { 86 - console.error('failed to fetch recent records', error); 87 - } 88 - } 89 - 90 - let recentPosts; 91 - 92 - if (cardTypes.has('latestPost')) { 93 - try { 94 - const agent = new AtpBaseClient({ service: 'https://api.bsky.app' }); 95 - const authorFeed = await agent.app.bsky.feed.getAuthorFeed({ 96 - actor: did, 97 - filter: 'posts_no_replies', 98 - limit: 2 99 - }); 100 - console.log(authorFeed.data); 101 - recentPosts = JSON.parse(JSON.stringify(authorFeed.data)); 102 - } catch (error) { 103 - console.error('failed to fetch recent posts', error); 104 - } 105 - } 106 - 107 - return { 108 - did, 109 - data: JSON.parse(JSON.stringify(downloadedData)) as DownloadedData, 110 - additionalData: { 111 - recentRecords, 112 - recentPosts 113 - } 114 }; 115 } 116
··· 1 import { client } from '$lib/oauth'; 2 3 export function parseUri(uri: string) { 4 const [did, collection, rkey] = uri.split('/').slice(2); ··· 6 collection: `${string}.${string}.${string}`; 7 rkey: string; 8 did: string; 9 }; 10 } 11
+1 -1
src/routes/+page.server.ts
··· 1 - import { loadData } from '$lib/website/utils'; 2 import { env } from '$env/dynamic/public'; 3 4 export async function load() {
··· 1 + import { loadData } from '$lib/website/load'; 2 import { env } from '$env/dynamic/public'; 3 4 export async function load() {
+1 -1
src/routes/[handle]/+layout.server.ts
··· 1 - import { loadData } from '$lib/website/utils'; 2 import { env } from '$env/dynamic/private'; 3 import { error } from '@sveltejs/kit'; 4
··· 1 + import { loadData } from '$lib/website/load'; 2 import { env } from '$env/dynamic/private'; 3 import { error } from '@sveltejs/kit'; 4
+1 -1
src/routes/edit/+page.server.ts
··· 1 - import { loadData } from '$lib/website/utils'; 2 import { env } from '$env/dynamic/public'; 3 4 export async function load() {
··· 1 + import { loadData } from '$lib/website/load'; 2 import { env } from '$env/dynamic/public'; 3 4 export async function load() {