your personal website on atproto - mirror blento.app

add standard site document list, small fixes

Florian f4e2e6c7 dbe2f418

+147 -16
+32
src/lib/cards/StandardSiteDocumentListCard/BlogEntry.svelte
··· 1 + <script lang="ts"> 2 + import { RelativeTime } from '@foxui/time'; 3 + import DateTime from './DateTime.svelte'; 4 + 5 + let { 6 + title, 7 + date, 8 + description, 9 + href 10 + }: { title: string; date: string; description: string; href: string } = $props(); 11 + </script> 12 + 13 + <article class="group/article relative isolate flex flex-col"> 14 + <div class={'text-base-500 accent:text-accent-950 flex shrink-0 items-center gap-x-4 text-xs'}> 15 + <DateTime date={new Date(date)} /> 16 + </div> 17 + <div class="max-w-xl"> 18 + <div class="text-base-900 dark:text-base-50 mt-3 text-lg leading-6 font-semibold"> 19 + <a {href} target="_blank"> 20 + <span class="absolute inset-0"></span> 21 + {title} 22 + </a> 23 + 24 + <div 25 + class="bg-base-200/30 accent:bg-accent-200/20 dark:bg-base-800/30 absolute -inset-2 -z-10 scale-95 rounded-2xl opacity-0 transition-all duration-150 group-hover/article:scale-100 group-hover/article:opacity-100" 26 + ></div> 27 + </div> 28 + <p class="text-base-600 dark:text-base-400 accent:text-base-800 mt-5 text-sm leading-6"> 29 + {description} 30 + </p> 31 + </div> 32 + </article>
+11
src/lib/cards/StandardSiteDocumentListCard/DateTime.svelte
··· 1 + <script lang="ts"> 2 + let { date }: { date: Date } = $props(); 3 + </script> 4 + 5 + <time datetime={date.toISOString()}> 6 + {date.toLocaleDateString('en-us', { 7 + year: 'numeric', 8 + month: 'short', 9 + day: 'numeric' 10 + })} 11 + </time>
+41
src/lib/cards/StandardSiteDocumentListCard/StandardSiteDocumentListCard.svelte
··· 1 + <script lang="ts"> 2 + import { getAdditionalUserData, getDidContext, getHandleContext } from '$lib/website/context'; 3 + import { onMount } from 'svelte'; 4 + import { CardDefinitionsByType } from '..'; 5 + import type { ContentComponentProps } from '../types'; 6 + import BlogEntry from './BlogEntry.svelte'; 7 + 8 + let { item }: ContentComponentProps = $props(); 9 + 10 + const data = getAdditionalUserData(); 11 + // svelte-ignore state_referenced_locally 12 + let feed = $state(data[item.cardType] as any); 13 + 14 + let did = getDidContext(); 15 + let handle = getHandleContext(); 16 + 17 + onMount(async () => { 18 + console.log(feed); 19 + if (!feed) { 20 + feed = (await CardDefinitionsByType[item.cardType]?.loadData?.([item], { 21 + did, 22 + handle 23 + })) as any; 24 + 25 + console.log(feed); 26 + 27 + data[item.cardType] = feed; 28 + } 29 + }); 30 + </script> 31 + 32 + <div class="flex h-full flex-col gap-10 overflow-y-scroll p-8"> 33 + {#each feed ?? [] as document} 34 + <BlogEntry 35 + title={document.value.title} 36 + description={document.value.description} 37 + date={document.value.publishedAt} 38 + href={document.value.href} 39 + /> 40 + {/each} 41 + </div>
+41
src/lib/cards/StandardSiteDocumentListCard/index.ts
··· 1 + import { getRecord, listRecords } from '$lib/oauth/atproto'; 2 + import { parseUri } from '$lib/oauth/utils'; 3 + import type { CardDefinition } from '../types'; 4 + import StandardSiteDocumentListCard from './StandardSiteDocumentListCard.svelte'; 5 + 6 + export const StandardSiteDocumentListCardDefinition = { 7 + type: 'publicationList', 8 + contentComponent: StandardSiteDocumentListCard, 9 + createNew: (card) => { 10 + card.w = 4; 11 + card.mobileW = 8; 12 + card.mobileH = 6; 13 + }, 14 + 15 + loadData: async (items, { did }) => { 16 + const records = await listRecords({ did, collection: 'site.standard.document' }); 17 + 18 + const publications: Record<string, string> = {}; 19 + for (const record of records) { 20 + const site = record.value.site as string; 21 + 22 + if (site.startsWith('at://')) { 23 + if (!publications[site]) { 24 + const siteParts = parseUri(site); 25 + 26 + const publicationRecord = await getRecord(siteParts); 27 + 28 + publications[site] = publicationRecord.value.url as string; 29 + } 30 + 31 + record.value.href = publications[site] + record.value.path; 32 + } else { 33 + record.value.href = site + record.value.path; 34 + } 35 + } 36 + 37 + return records; 38 + }, 39 + 40 + sidebarButtonText: 'site.standard.document list' 41 + } as CardDefinition & { type: 'site.standard.document list' };
+10 -13
src/lib/cards/TealFMPlaysCard/TealFMPlaysCard.svelte
··· 16 16 let handle = getHandleContext(); 17 17 18 18 onMount(async () => { 19 - console.log(feed); 20 - if (!feed) { 21 - feed = (await CardDefinitionsByType[item.cardType]?.loadData?.([], { 22 - did, 23 - handle 24 - })) as any; 19 + if (feed) return; 25 20 26 - console.log(feed); 21 + feed = (await CardDefinitionsByType[item.cardType]?.loadData?.([], { 22 + did, 23 + handle 24 + })) as any; 27 25 28 - data[item.cardType] = feed; 29 - } 26 + data[item.cardType] = feed; 30 27 }); 31 28 32 29 function isNumeric(str: string) { 33 - if (typeof str != 'string') return false; // we only process strings! 30 + if (typeof str != 'string') return false; 34 31 return ( 35 - !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)... 32 + !isNaN(str) && 36 33 !isNaN(parseFloat(str)) 37 - ); // ...and ensure strings of whitespace fail 34 + ); 38 35 } 39 36 </script> 40 37 ··· 74 71 {/snippet} 75 72 76 73 <div class="z-10 flex h-full w-full flex-col gap-4 overflow-y-scroll p-4"> 77 - {#each (feed ?? []).slice(0, 20) as play} 74 + {#each feed ?? [] as play} 78 75 {#if play.value.originUrl} 79 76 <a href={play.value.originUrl} target="_blank" rel="noopener noreferrer" class="w-full"> 80 77 {@render musicItem(play)}
+3 -1
src/lib/cards/index.ts
··· 21 21 import { PopfeedReviewsCardDefinition } from './PopfeedReviews'; 22 22 import { TealFMPlaysCardDefinition } from './TealFMPlaysCard'; 23 23 import { PhotoGalleryCardDefinition } from './PhotoGalleryCard'; 24 + import { StandardSiteDocumentListCardDefinition } from './StandardSiteDocumentListCard'; 24 25 25 26 export const AllCardDefinitions = [ 26 27 ImageCardDefinition, ··· 44 45 TetrisCardDefinition, 45 46 PopfeedReviewsCardDefinition, 46 47 TealFMPlaysCardDefinition, 47 - PhotoGalleryCardDefinition 48 + PhotoGalleryCardDefinition, 49 + StandardSiteDocumentListCardDefinition 48 50 ] as const; 49 51 50 52 export const CardDefinitionsByType = AllCardDefinitions.reduce(
+1 -1
src/lib/website/Account.svelte
··· 24 24 <Button variant="ghost" onclick={logout}>Logout</Button> 25 25 </Popover> 26 26 </div> 27 - {:else} 27 + {:else if !client.isInitializing} 28 28 <div 29 29 class="dark:bg-base-950 border-base-200 dark:border-base-900 fixed top-4 right-4 z-20 flex flex-col gap-4 rounded-2xl border bg-white p-4 shadow-lg" 30 30 >
+1 -1
src/lib/website/EditBar.svelte
··· 60 60 bind:this={videoInputRef} 61 61 /> 62 62 63 - {#if client.isLoggedIn && client.profile?.did === data.did} 63 + {#if dev || (client.isLoggedIn && client.profile?.did === data.did)} 64 64 <Navbar 65 65 class={[ 66 66 'dark:bg-base-900 bg-base-100 top-auto bottom-2 mx-4 mt-3 max-w-3xl rounded-full px-4 md:mx-auto lg:inline-flex',
+7
src/lib/website/load.ts
··· 17 17 const update = result.updatedAt; 18 18 const timePassed = (Date.now() - update) / 1000; 19 19 20 + const ONE_DAY = 60 * 60 * 24; 21 + 20 22 if (!result.version || result.version !== CURRENT_CACHE_VERSION) { 21 23 console.log('skipping cache because of version mismatch'); 24 + return; 25 + } 26 + 27 + if (timePassed > ONE_DAY) { 28 + console.log('skipping cache because of age'); 22 29 return; 23 30 } 24 31