From 9e093310283988200235e4583a02bf815b0dbd08 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Fri, 06 Mar 2026 16:35:47 +0000 Subject: [PATCH] Merge pull request #243 from flo-bit/cards-as-embeds cards as embeds --- src/routes/+layout.svelte | 7 ++++++- src/lib/website/EmbeddedCard.svelte | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib/website/load.ts | 252 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------- src/lib/cards/_base/BaseCard/BaseCard.svelte | 22 ++++++++++++++++++---- src/lib/cards/core/LinkCard/EditingLinkCard.svelte | 20 +++++++++++++++----- src/lib/cards/core/LinkCard/LinkCard.svelte | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- src/lib/cards/core/LinkCard/LinkCardSettings.svelte | 4 ++-- src/lib/cards/media/LivestreamCard/LivestreamCard.svelte | 27 ++++++++++++++++----------- src/lib/cards/media/PhotoGalleryCard/PhotoGalleryCard.svelte | 33 ++++++++++++++++++++++++--------- src/lib/cards/media/RockskyPlaysCard/RockskyPlaysCard.svelte | 1 + src/lib/cards/media/TealFMPlaysCard/TealFMPlaysCard.svelte | 3 ++- src/lib/cards/social/EventCard/EventCard.svelte | 53 +++++++++++++++++++++++++++++++++++++++++------------ src/lib/cards/social/FriendsCard/FriendsCard.svelte | 61 ++++++++++++++++++++++++++++++++++++++++++------------------- src/lib/cards/social/GitHubProfileCard/GitHubProfileCard.svelte | 52 ++++++++++++++++++++++++++++++++++++++++------------ src/lib/cards/social/UpcomingEventsCard/UpcomingEventsCard.svelte | 8 +------- src/routes/[[actor=actor]]/card/[rkey]/+page.server.ts | 16 ++++++++++++++++ src/routes/[[actor=actor]]/card/[rkey]/+page.svelte | 7 +++++++ src/routes/[[actor=actor]]/embed/type/[type]/+page.server.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/routes/[[actor=actor]]/embed/type/[type]/+page.svelte | 7 +++++++ 19 file(s) changed, 750 insertion(s)(+), 163 deletion(s)(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -11,6 +11,9 @@ import LoginModal from '$lib/atproto/UI/LoginModal.svelte'; let { children, data } = $props(); + let showThemeToggle = $derived( + !/(?:\/card\/[^/]+|\/embed\/type\/[^/]+)$/.test(page.url.pathname) + ); const errorMessages: Record string> = { handle_not_found: (p) => `Handle ${p.get('handle') ?? ''} not found!` @@ -25,7 +28,9 @@ {@render children()} - +{#if showThemeToggle} + +{/if} diff --git a/src/lib/website/EmbeddedCard.svelte b/src/lib/website/EmbeddedCard.svelte new file mode 100644 --- /dev/null +++ b/src/lib/website/EmbeddedCard.svelte @@ -0,0 +1,177 @@ + + + + + + + {@html themeScript} + + + + + + +
+
+
+ + + +
+
+
+
+ + diff --git a/src/lib/website/load.ts b/src/lib/website/load.ts --- a/src/lib/website/load.ts +++ b/src/lib/website/load.ts @@ -1,6 +1,7 @@ import { getDetailedProfile, listRecords, resolveHandle, parseUri, getRecord } from '$lib/atproto'; import { CardDefinitionsByType } from '$lib/cards'; import type { CacheService } from '$lib/cache'; +import { createEmptyCard } from '$lib/helper'; import type { Item, WebsiteData } from '$lib/types'; import { error } from '@sveltejs/kit'; import type { ActorIdentifier, Did } from '@atcute/lexicons'; @@ -85,42 +86,11 @@ getDetailedProfile({ did }) ]); - const cardTypes = new Set(cards.map((v) => v.value.cardType ?? '') as string[]); - const cardTypesArray = Array.from(cardTypes); - - const additionDataPromises: Record> = {}; - - const loadOptions = { did, handle, cache }; - - for (const cardType of cardTypesArray) { - const cardDef = CardDefinitionsByType[cardType]; - - const items = cards.filter((v) => cardType === v.value.cardType).map((v) => v.value) as Item[]; - - try { - if (cardDef?.loadDataServer) { - additionDataPromises[cardType] = cardDef.loadDataServer(items, { - ...loadOptions, - env - }); - } else if (cardDef?.loadData) { - additionDataPromises[cardType] = cardDef.loadData(items, loadOptions); - } - } catch { - console.error('error getting additional data for', cardType); - } - } - - await Promise.all(Object.values(additionDataPromises)); - - const additionalData: Record = {}; - for (const [key, value] of Object.entries(additionDataPromises)) { - try { - additionalData[key] = await value; - } catch (error) { - console.log('error loading', key, error); - } - } + const additionalData = await loadAdditionalData( + cards.map((v) => ({ ...v.value })) as Item[], + { did, handle, cache }, + env + ); const result = { page: 'blento.' + page, @@ -157,9 +127,148 @@ return checkData(parsedResult); } -function migrateFromV0ToV1(data: WebsiteData): WebsiteData { - for (const card of data.cards) { - if (card.version) continue; +export async function loadCardData( + handle: ActorIdentifier, + rkey: string, + cache: CacheService | undefined, + env?: Record +): Promise { + if (!handle) throw error(404); + if (handle === 'favicon.ico') throw error(404); + + let did: Did | undefined = undefined; + if (isHandle(handle)) { + did = await resolveHandle({ handle }); + } else if (isDid(handle)) { + did = handle; + } else { + throw error(404); + } + + const [cardRecord, profile] = await Promise.all([ + getRecord({ + did, + collection: 'app.blento.card', + rkey + }).catch(() => undefined), + getDetailedProfile({ did }) + ]); + + if (!cardRecord?.value) { + throw error(404, 'Card not found'); + } + + const card = migrateCard(structuredClone(cardRecord.value) as Item); + const page = card.page ?? 'blento.self'; + + const publication = await getRecord({ + did, + collection: page === 'blento.self' ? 'site.standard.publication' : 'app.blento.page', + rkey: page + }).catch(() => undefined); + + const cards = [card]; + const resolvedHandle = profile?.handle || (isHandle(handle) ? handle : did); + + const additionalData = await loadAdditionalData( + cards, + { did, handle: resolvedHandle, cache }, + env + ); + + const result = { + page, + handle: resolvedHandle, + did, + cards, + publication: + publication?.value ?? + ({ + name: profile?.displayName || profile?.handle, + description: profile?.description + } as WebsiteData['publication']), + additionalData, + profile, + updatedAt: Date.now(), + version: CURRENT_CACHE_VERSION + }; + + return result; +} + +export async function loadCardTypeData( + handle: ActorIdentifier, + type: string, + cardData: Record, + cache: CacheService | undefined, + env?: Record +): Promise { + if (!handle) throw error(404); + if (handle === 'favicon.ico') throw error(404); + + const cardDef = CardDefinitionsByType[type]; + if (!cardDef) { + throw error(404, 'Card type not found'); + } + + let did: Did | undefined = undefined; + if (isHandle(handle)) { + did = await resolveHandle({ handle }); + } else if (isDid(handle)) { + did = handle; + } else { + throw error(404); + } + + const [publication, profile] = await Promise.all([ + getRecord({ + did, + collection: 'site.standard.publication', + rkey: 'blento.self' + }).catch(() => undefined), + getDetailedProfile({ did }) + ]); + + const card = createEmptyCard('blento.self'); + card.cardType = type; + + cardDef.createNew?.(card); + card.cardData = { + ...card.cardData, + ...cardData + }; + + const cards = [card]; + const resolvedHandle = profile?.handle || (isHandle(handle) ? handle : did); + + const additionalData = await loadAdditionalData( + cards, + { did, handle: resolvedHandle, cache }, + env + ); + + const result = { + page: 'blento.self', + handle: resolvedHandle, + did, + cards, + publication: + publication?.value ?? + ({ + name: profile?.displayName || profile?.handle, + description: profile?.description + } as WebsiteData['publication']), + additionalData, + profile, + updatedAt: Date.now(), + version: CURRENT_CACHE_VERSION + }; + + return checkData(result); +} + +function migrateCard(card: Item): Item { + if (!card.version) { card.x *= 2; card.y *= 2; card.h *= 2; @@ -171,28 +280,58 @@ card.version = 1; } - return data; + if (!card.version || card.version < 2) { + card.page = 'blento.self'; + card.version = 2; + } + + const cardDef = CardDefinitionsByType[card.cardType]; + cardDef?.migrate?.(card); + + return card; } -function migrateFromV1ToV2(data: WebsiteData): WebsiteData { - for (const card of data.cards) { - if (!card.version || card.version < 2) { - card.page = 'blento.self'; - card.version = 2; +async function loadAdditionalData( + cards: Item[], + { did, handle, cache }: { did: Did; handle: string; cache?: CacheService }, + env?: Record +) { + const cardTypes = new Set(cards.map((v) => v.cardType ?? '') as string[]); + const cardTypesArray = Array.from(cardTypes); + const additionDataPromises: Record> = {}; + + for (const cardType of cardTypesArray) { + const cardDef = CardDefinitionsByType[cardType]; + const items = cards.filter((v) => cardType === v.cardType); + + try { + if (cardDef?.loadDataServer) { + additionDataPromises[cardType] = cardDef.loadDataServer(items, { + did, + handle, + cache, + env + }); + } else if (cardDef?.loadData) { + additionDataPromises[cardType] = cardDef.loadData(items, { did, handle, cache }); + } + } catch { + console.error('error getting additional data for', cardType); } } - return data; -} -function migrateCards(data: WebsiteData): WebsiteData { - for (const card of data.cards) { - const cardDef = CardDefinitionsByType[card.cardType]; + await Promise.all(Object.values(additionDataPromises)); - if (!cardDef?.migrate) continue; - - cardDef.migrate(card); + const additionalData: Record = {}; + for (const [key, value] of Object.entries(additionDataPromises)) { + try { + additionalData[key] = await value; + } catch (error) { + console.log('error loading', key, error); + } } - return data; + + return additionalData; } function checkData(data: WebsiteData): WebsiteData { @@ -214,5 +353,8 @@ } function migrateData(data: WebsiteData): WebsiteData { - return migrateCards(migrateFromV1ToV2(migrateFromV0ToV1(data))); + for (const card of data.cards) { + migrateCard(card); + } + return data; } diff --git a/src/lib/cards/_base/BaseCard/BaseCard.svelte b/src/lib/cards/_base/BaseCard/BaseCard.svelte --- a/src/lib/cards/_base/BaseCard/BaseCard.svelte +++ b/src/lib/cards/_base/BaseCard/BaseCard.svelte @@ -18,6 +18,7 @@ isEditing?: boolean; showOutline?: boolean; locked?: boolean; + fillPage?: boolean; } & WithElementRef>; let { @@ -28,6 +29,7 @@ controls, showOutline, locked = false, + fillPage = false, class: className, ...rest }: BaseCardProps = $props(); @@ -38,11 +40,14 @@
@@ -84,14 +90,22 @@ diff --git a/src/lib/cards/core/LinkCard/LinkCard.svelte b/src/lib/cards/core/LinkCard/LinkCard.svelte --- a/src/lib/cards/core/LinkCard/LinkCard.svelte +++ b/src/lib/cards/core/LinkCard/LinkCard.svelte @@ -1,13 +1,10 @@ {#if item.cardData.showBackgroundImage && item.cardData.image} -
+ -
+ {#if item.cardData.href && !isEditing} @@ -73,8 +65,8 @@ {/if}
{:else} -
-
+