your personal website on atproto - mirror blento.app

refactor pt8

Florian c8f0ca4d f1dfd27f

+252 -145
+14 -13
.claude/settings.local.json
··· 1 1 { 2 - "permissions": { 3 - "allow": [ 4 - "Bash(pnpm check:*)", 5 - "mcp__ide__getDiagnostics", 6 - "mcp__plugin_svelte_svelte__svelte-autofixer", 7 - "mcp__plugin_svelte_svelte__list-sections", 8 - "Bash(pkill:*)", 9 - "Bash(timeout 8 pnpm dev:*)", 10 - "Bash(git checkout:*)", 11 - "Bash(npx svelte-kit:*)", 12 - "Bash(ls:*)" 13 - ] 14 - } 2 + "permissions": { 3 + "allow": [ 4 + "Bash(pnpm check:*)", 5 + "mcp__ide__getDiagnostics", 6 + "mcp__plugin_svelte_svelte__svelte-autofixer", 7 + "mcp__plugin_svelte_svelte__list-sections", 8 + "Bash(pkill:*)", 9 + "Bash(timeout 8 pnpm dev:*)", 10 + "Bash(git checkout:*)", 11 + "Bash(npx svelte-kit:*)", 12 + "Bash(ls:*)", 13 + "Bash(pnpm format:*)" 14 + ] 15 + } 15 16 }
+5 -1
AGENTS.md
··· 26 26 27 27 ## Testing Guidelines 28 28 29 - - There is no dedicated test runner yet. Use `pnpm check` and `pnpm lint` before submitting changes. 29 + - There is no dedicated test runner yet. 30 + - **Before submitting changes, you must:** 31 + 1. Run `pnpm check` - Must complete with **0 errors and 0 warnings** 32 + 2. Run `pnpm format` - Format all code with Prettier 33 + 3. Run `pnpm lint` - Ensure no linting errors 30 34 - For UI changes, verify key flows manually (login, card editing, save/load, and route navigation across `[handle]` pages). 31 35 32 36 ## Commit & Pull Request Guidelines
+7
CLAUDE.md
··· 16 16 - `pnpm format` - Format code with Prettier 17 17 - `pnpm deploy` - Build and deploy to Cloudflare Workers 18 18 19 + ## Code Quality Requirements 20 + 21 + Before submitting changes: 22 + 23 + 1. **Run `pnpm check`** - Must complete with 0 errors and 0 warnings 24 + 2. **Run `pnpm format`** - Format all code with Prettier 25 + 19 26 ## Architecture 20 27 21 28 ### Tech Stack
+1 -3
src/lib/cards/BaseCard/BaseEditingCard.svelte
··· 36 36 item: Item; 37 37 ondelete: () => void; 38 38 onsetsize: (newW: number, newH: number) => void; 39 - onshowsettings?: () => void; 40 39 } & WithElementRef<HTMLAttributes<HTMLDivElement>>; 41 40 42 41 let { ··· 44 43 children, 45 44 ref = $bindable(null), 46 45 onsetsize, 47 - onshowsettings, 48 46 ondelete, 49 47 ...rest 50 48 }: BaseEditingCardProps = $props(); ··· 262 260 ]} 263 261 > 264 262 <div 265 - 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" 263 + 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" 266 264 > 267 265 {#if cardDef.allowSetColor !== false} 268 266 <Popover bind:open={colorPopoverOpen}>
+1 -1
src/lib/cards/BigSocialCard/CreateBigSocialCardModal.svelte
··· 1 1 <script lang="ts"> 2 2 import { Alert, Button, Input, Modal, Subheading } from '@foxui/core'; 3 3 import type { CreationModalComponentProps } from '../types'; 4 - import { detectPlatform, platformPatterns, platformsData } from '.'; 4 + import { detectPlatform, platformsData } from '.'; 5 5 6 6 let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props(); 7 7
+1 -11
src/lib/cards/BlueskyMediaCard/BlueskyMediaCard.svelte
··· 1 1 <script lang="ts"> 2 2 import { getDidContext } from '$lib/website/context'; 3 - import { getImageBlobUrl } from '$lib/atproto'; 4 3 import type { ContentComponentProps } from '../types'; 5 4 import Video from './Video.svelte'; 6 5 7 - let { item = $bindable(), ...rest }: ContentComponentProps = $props(); 6 + let { item = $bindable() }: ContentComponentProps = $props(); 8 7 9 8 const did = getDidContext(); 10 - 11 - function getSrc() { 12 - if (item.cardData.objectUrl) return item.cardData.objectUrl; 13 - 14 - if (item.cardData.image && typeof item.cardData.image === 'object') { 15 - return getImageBlobUrl({ did, blob: item.cardData.image }); 16 - } 17 - return item.cardData.image; 18 - } 19 9 </script> 20 10 21 11 {#if item.cardData.image}
+9 -3
src/lib/cards/BlueskyMediaCard/CreateBlueskyMediaCardModal.svelte
··· 9 9 10 10 let did = getDidContext(); 11 11 12 - let mediaList: { fullsize: string; isVideo?: boolean; playlist?: string, thumbnail?: string }[] = $state([]); 12 + let mediaList: { fullsize: string; isVideo?: boolean; playlist?: string; thumbnail?: string }[] = 13 + $state([]); 13 14 14 15 let isLoading = $state(true); 15 16 ··· 17 18 const authorFeed = await getAuthorFeed({ did }); 18 19 19 20 for (let post of authorFeed?.feed ?? []) { 20 - let images = post.post.embed?.$type === 'app.bsky.embed.images#view' ? post.post.embed : undefined 21 + let images = 22 + post.post.embed?.$type === 'app.bsky.embed.images#view' ? post.post.embed : undefined; 21 23 22 24 for (let image of images?.images ?? []) { 23 25 mediaList.push(image); 24 26 } 25 27 26 - if (post.post.embed?.$type === 'app.bsky.embed.video#view' && post.post.embed.thumbnail && post.post.embed.playlist) { 28 + if ( 29 + post.post.embed?.$type === 'app.bsky.embed.video#view' && 30 + post.post.embed.thumbnail && 31 + post.post.embed.playlist 32 + ) { 27 33 mediaList.push({ 28 34 ...post.post.embed, 29 35 isVideo: true,
-1
src/lib/cards/BlueskyMediaCard/Video.svelte
··· 43 43 </script> 44 44 45 45 <img src={video.thumbnail} class="absolute inset-0 -z-10 h-full w-full object-cover" alt="" /> 46 - <!-- svelte-ignore a11y_media_has_caption --> 47 46 <video 48 47 bind:this={element} 49 48 muted
+1 -1
src/lib/cards/BlueskyMediaCard/index.ts
··· 6 6 export const BlueskyMediaCardDefinition = { 7 7 type: 'blueskyMedia', 8 8 contentComponent: BlueskyMediaCard, 9 - createNew: (card) => {}, 9 + createNew: () => {}, 10 10 creationModalComponent: CreateBlueskyMediaCardModal, 11 11 sidebarButtonText: 'Bluesky Media', 12 12 sidebarComponent: SidebarItemBlueskyMediaCard
+1 -6
src/lib/cards/BlueskyProfileCard/index.ts
··· 4 4 export const BlueskyProfileCardDefinition = { 5 5 type: 'blueskyProfile', 6 6 contentComponent: BlueskyProfileCard, 7 - createNew: (card) => { 8 - // card.w = 4; 9 - // card.mobileW = 8; 10 - // card.h = 4; 11 - // card.mobileH = 8; 12 - } 7 + createNew: () => {} 13 8 } as CardDefinition & { type: 'blueskyProfile' };
+2 -2
src/lib/cards/EmbedCard/CreateEmbedCardModal.svelte
··· 9 9 async function checkUrl() { 10 10 errorMessage = ''; 11 11 try { 12 - const domain = new URL(item.cardData.href); 13 - } catch (error) { 12 + new URL(item.cardData.href); 13 + } catch { 14 14 errorMessage = 'Invalid URL!'; 15 15 return false; 16 16 }
+99 -14
src/lib/cards/FluidTextCard/FluidTextCard.svelte
··· 888 888 let sunrays: FBO; 889 889 let sunraysTemp: FBO; 890 890 891 - const blurProgram = new Program(gl, blurVertexShader, blurShader, createWebGLProgram, getUniforms); 892 - const copyProgram = new Program(gl, baseVertexShader, copyShader, createWebGLProgram, getUniforms); 893 - const clearProgram = new Program(gl, baseVertexShader, clearShader, createWebGLProgram, getUniforms); 894 - const colorProgram = new Program(gl, baseVertexShader, colorShader, createWebGLProgram, getUniforms); 895 - const splatProgram = new Program(gl, baseVertexShader, splatShader, createWebGLProgram, getUniforms); 896 - const advectionProgram = new Program(gl, baseVertexShader, advectionShader, createWebGLProgram, getUniforms); 897 - const divergenceProgram = new Program(gl, baseVertexShader, divergenceShader, createWebGLProgram, getUniforms); 898 - const curlProgram = new Program(gl, baseVertexShader, curlShader, createWebGLProgram, getUniforms); 899 - const vorticityProgram = new Program(gl, baseVertexShader, vorticityShader, createWebGLProgram, getUniforms); 900 - const pressureProgram = new Program(gl, baseVertexShader, pressureShader, createWebGLProgram, getUniforms); 901 - const gradienSubtractProgram = new Program(gl, baseVertexShader, gradientSubtractShader, createWebGLProgram, getUniforms); 902 - const sunraysMaskProgram = new Program(gl, baseVertexShader, sunraysMaskShader, createWebGLProgram, getUniforms); 903 - const sunraysProgram = new Program(gl, baseVertexShader, sunraysShader, createWebGLProgram, getUniforms); 891 + const blurProgram = new Program( 892 + gl, 893 + blurVertexShader, 894 + blurShader, 895 + createWebGLProgram, 896 + getUniforms 897 + ); 898 + const copyProgram = new Program( 899 + gl, 900 + baseVertexShader, 901 + copyShader, 902 + createWebGLProgram, 903 + getUniforms 904 + ); 905 + const clearProgram = new Program( 906 + gl, 907 + baseVertexShader, 908 + clearShader, 909 + createWebGLProgram, 910 + getUniforms 911 + ); 912 + const colorProgram = new Program( 913 + gl, 914 + baseVertexShader, 915 + colorShader, 916 + createWebGLProgram, 917 + getUniforms 918 + ); 919 + const splatProgram = new Program( 920 + gl, 921 + baseVertexShader, 922 + splatShader, 923 + createWebGLProgram, 924 + getUniforms 925 + ); 926 + const advectionProgram = new Program( 927 + gl, 928 + baseVertexShader, 929 + advectionShader, 930 + createWebGLProgram, 931 + getUniforms 932 + ); 933 + const divergenceProgram = new Program( 934 + gl, 935 + baseVertexShader, 936 + divergenceShader, 937 + createWebGLProgram, 938 + getUniforms 939 + ); 940 + const curlProgram = new Program( 941 + gl, 942 + baseVertexShader, 943 + curlShader, 944 + createWebGLProgram, 945 + getUniforms 946 + ); 947 + const vorticityProgram = new Program( 948 + gl, 949 + baseVertexShader, 950 + vorticityShader, 951 + createWebGLProgram, 952 + getUniforms 953 + ); 954 + const pressureProgram = new Program( 955 + gl, 956 + baseVertexShader, 957 + pressureShader, 958 + createWebGLProgram, 959 + getUniforms 960 + ); 961 + const gradienSubtractProgram = new Program( 962 + gl, 963 + baseVertexShader, 964 + gradientSubtractShader, 965 + createWebGLProgram, 966 + getUniforms 967 + ); 968 + const sunraysMaskProgram = new Program( 969 + gl, 970 + baseVertexShader, 971 + sunraysMaskShader, 972 + createWebGLProgram, 973 + getUniforms 974 + ); 975 + const sunraysProgram = new Program( 976 + gl, 977 + baseVertexShader, 978 + sunraysShader, 979 + createWebGLProgram, 980 + getUniforms 981 + ); 904 982 905 - const displayMaterial = new Material(gl, baseVertexShader, displayShaderSource, compileShader, createWebGLProgram, getUniforms); 983 + const displayMaterial = new Material( 984 + gl, 985 + baseVertexShader, 986 + displayShaderSource, 987 + compileShader, 988 + createWebGLProgram, 989 + getUniforms 990 + ); 906 991 907 992 function getResolution(resolution: number) { 908 993 let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
+1 -1
src/lib/cards/GIFCard/GiphySearchModal.svelte
··· 135 135 136 136 <div class="mt-4 flex-1 overflow-y-auto"> 137 137 {#if isLoading && displayResults.length === 0} 138 - <div class="flex h-[300px] items-center justify-center"> 138 + <div class="flex h-75 items-center justify-center"> 139 139 <p class="text-base-500">Loading...</p> 140 140 </div> 141 141 {:else if displayResults.length > 0}
-2
src/lib/cards/GameCards/DinoGameCard/DinoGameCard.svelte
··· 2 2 import type { ContentComponentProps } from '../../types'; 3 3 import { onMount, onDestroy } from 'svelte'; 4 4 5 - let { item }: ContentComponentProps = $props(); 6 - 7 5 let canvas: HTMLCanvasElement; 8 6 let container: HTMLDivElement; 9 7 let ctx: CanvasRenderingContext2D | null = null;
+3 -2
src/lib/cards/GameCards/DinoGameCard/index.ts
··· 1 - import type { CardDefinition } from '$lib/cards/types'; 1 + import type { CardDefinition, ContentComponentProps } from '$lib/cards/types'; 2 + import type { Component } from 'svelte'; 2 3 import DinoGameCard from './DinoGameCard.svelte'; 3 4 import SidebarItemDinoGameCard from './SidebarItemDinoGameCard.svelte'; 4 5 5 6 export const DinoGameCardDefinition = { 6 7 type: 'dino-game', 7 - contentComponent: DinoGameCard, 8 + contentComponent: DinoGameCard as unknown as Component<ContentComponentProps>, 8 9 sidebarComponent: SidebarItemDinoGameCard, 9 10 allowSetColor: true, 10 11 createNew: (card) => {
+1 -4
src/lib/cards/GameCards/TetrisCard/TetrisCard.svelte
··· 1 1 <script lang="ts"> 2 - import type { ContentComponentProps } from '../../types'; 3 2 import { onMount, onDestroy } from 'svelte'; 4 3 import Tetris8BitMusic from './Tetris8Bit.mp3'; 5 - 6 - let { item }: ContentComponentProps = $props(); 7 4 8 5 let canvas: HTMLCanvasElement; 9 6 let container: HTMLDivElement; ··· 252 249 253 250 oscillator.start(audioCtx.currentTime); 254 251 oscillator.stop(audioCtx.currentTime + duration); 255 - } catch (e) { 252 + } catch { 256 253 // Audio not supported 257 254 } 258 255 }
+3 -2
src/lib/cards/GameCards/TetrisCard/index.ts
··· 1 1 //Music by DJARTMUSIC - The Return Of The 8-bit Era 2 2 //https://pixabay.com/de/music/videospiele-the-return-of-the-8-bit-era-301292/ 3 3 4 - import type { CardDefinition } from '../../types'; 4 + import type { CardDefinition, ContentComponentProps } from '../../types'; 5 5 import TetrisCard from './TetrisCard.svelte'; 6 6 import SidebarItemTetrisCard from './SidebarItemTetrisCard.svelte'; 7 + import type { Component } from 'svelte'; 7 8 8 9 export const TetrisCardDefinition = { 9 10 type: 'tetris', 10 - contentComponent: TetrisCard, 11 + contentComponent: TetrisCard as unknown as Component<ContentComponentProps>, 11 12 sidebarComponent: SidebarItemTetrisCard, 12 13 allowSetColor: true, 13 14 defaultColor: 'accent',
+1 -4
src/lib/cards/GitHubProfileCard/GitHubProfileCard.svelte
··· 7 7 import GithubContributionsGraph from './GithubContributionsGraph.svelte'; 8 8 import { Button } from '@foxui/core'; 9 9 import { browser } from '$app/environment'; 10 - import { fade } from 'svelte/transition'; 11 10 12 11 let { item }: ContentComponentProps = $props(); 13 12 14 13 const data = getAdditionalUserData(); 15 14 16 - let isLoaded = $state(false); 17 15 // svelte-ignore state_referenced_locally 18 16 let contributionsData = $state( 19 17 (data[item.cardType] as GithubProfileLoadedData)?.[item.cardData.user] ··· 27 25 if (response.ok) { 28 26 contributionsData = await response.json(); 29 27 data[item.cardType] ??= {}; 30 - data[item.cardType][item.cardData.user] = contributionsData; 28 + (data[item.cardType] as GithubProfileLoadedData)[item.cardData.user] = contributionsData; 31 29 } 32 30 } catch (error) { 33 31 console.error('Failed to fetch GitHub contributions:', error); 34 32 } 35 33 } 36 - isLoaded = true; 37 34 }); 38 35 39 36 let isMobile = getIsMobile();
+1 -1
src/lib/cards/ImageCard/ImageCard.svelte
··· 3 3 import { getImageBlobUrl } from '$lib/atproto'; 4 4 import type { ContentComponentProps } from '../types'; 5 5 6 - let { item = $bindable(), ...rest }: ContentComponentProps = $props(); 6 + let { item = $bindable() }: ContentComponentProps = $props(); 7 7 8 8 const did = getDidContext(); 9 9
+3 -6
src/lib/cards/LinkCard/EditingLinkCard.svelte
··· 3 3 import { getIsMobile } from '$lib/website/context'; 4 4 import type { ContentComponentProps } from '../types'; 5 5 import PlainTextEditor from '../utils/PlainTextEditor.svelte'; 6 - import { onMount } from 'svelte'; 7 6 8 7 let { item = $bindable() }: ContentComponentProps = $props(); 9 8 ··· 18 17 let domain: string; 19 18 try { 20 19 domain = new URL(item.cardData.href).hostname; 21 - } catch (error) { 20 + } catch { 22 21 return; 23 22 } 24 23 item.cardData.domain = domain; ··· 34 33 item.cardData.title = data.title || ''; 35 34 item.cardData.image = data.images?.[0] || ''; 36 35 item.cardData.favicon = data.favicons?.[0] || undefined; 37 - } catch (error) { 36 + } catch { 38 37 return; 39 38 } 40 39 } 41 - 42 - $inspect(hasFetched); 43 40 44 41 $effect(() => { 45 42 if (hasFetched !== false || isFetchingMetadata) { ··· 108 105 placeholder="Title here" 109 106 /> 110 107 {:else} 111 - <span class={'text-base-900 dark:text-base-50 line-clamp-2 text-lg font-bold'}> 108 + <span class="text-base-900 dark:text-base-50 line-clamp-2 text-lg font-bold"> 112 109 Loading data... 113 110 </span> 114 111 {/if}
+1 -1
src/lib/cards/LivestreamCard/LivestreamCard.svelte
··· 20 20 let isLoaded = $state(false); 21 21 22 22 const data = getAdditionalUserData(); 23 - // svelte-ignore state_referenced_locally 23 + 24 24 let latestLivestream = $state( 25 25 data[item.cardType] as 26 26 | {
+1 -1
src/lib/cards/MapCard/CreateMapCardModal.svelte
··· 26 26 } else { 27 27 throw new Error('response not ok'); 28 28 } 29 - } catch (error) { 29 + } catch { 30 30 errorMessage = "Couldn't find that location!"; 31 31 return false; 32 32 } finally {
+19 -9
src/lib/cards/PhotoGalleryCard/PhotoGalleryCard.svelte
··· 12 12 13 13 import { ImageMasonry } from '@foxui/visual'; 14 14 15 + interface PhotoItem { 16 + uri: string; 17 + value: { 18 + photo: { $type: 'blob'; ref: { $link: string } }; 19 + aspectRatio: { width: number; height: number }; 20 + position?: number; 21 + }; 22 + } 23 + 15 24 let { item }: { item: Item } = $props(); 16 25 17 26 const data = getAdditionalUserData(); 18 27 // svelte-ignore state_referenced_locally 19 - let feed = $state((data[item.cardType] as any)?.[item.cardData.galleryUri]); 28 + let feed = $state( 29 + (data[item.cardType] as Record<string, PhotoItem[]> | undefined)?.[item.cardData.galleryUri] 30 + ); 20 31 21 32 let did = getDidContext(); 22 33 let handle = getHandleContext(); ··· 28 39 (await CardDefinitionsByType[item.cardType]?.loadData?.([item], { 29 40 did, 30 41 handle 31 - })) as any 42 + })) as Record<string, PhotoItem[]> | undefined 32 43 )?.[item.cardData.galleryUri]; 33 44 34 45 console.log(feed); ··· 39 50 40 51 let images = $derived( 41 52 feed 42 - ?.toSorted((a, b) => { 53 + ?.toSorted((a: PhotoItem, b: PhotoItem) => { 43 54 return (a.value.position ?? 0) - (b.value.position ?? 0); 44 55 }) 45 - .map((i) => { 46 - const { did } = parseUri(i.uri); 56 + .map((i: PhotoItem) => { 57 + const { did: photoDid } = parseUri(i.uri); 47 58 return { 48 - src: getImageBlobUrl({ did, blob: i.value.photo }), 59 + src: getImageBlobUrl({ did: photoDid, blob: i.value.photo }), 60 + name: '', 49 61 width: i.value.aspectRatio.width, 50 62 height: i.value.aspectRatio.height, 51 63 position: i.value.position ?? 0 52 64 }; 53 65 }) 54 66 ); 55 - $inspect(images); 67 + 56 68 let isMobile = getIsMobile(); 57 69 </script> 58 70 59 71 <div class="z-10 flex h-full w-full flex-col gap-4 overflow-y-scroll p-4"> 60 - {#each (feed ?? []).slice(0, 20) as photo}{/each} 61 - 62 72 <ImageMasonry 63 73 images={images ?? []} 64 74 showNames={false}
+17 -6
src/lib/cards/PhotoGalleryCard/index.ts
··· 2 2 import { getRecord, listRecords, parseUri } from '$lib/atproto'; 3 3 import PhotoGalleryCard from './PhotoGalleryCard.svelte'; 4 4 5 + interface GalleryItem { 6 + value: { 7 + gallery: string; 8 + item: string; 9 + position?: number; 10 + }; 11 + } 12 + 5 13 export const PhotoGalleryCardDefinition = { 6 14 type: 'photoGallery', 7 15 contentComponent: PhotoGalleryCard, ··· 18 26 loadData: async (items) => { 19 27 const itemsData: Record<string, unknown[]> = {}; 20 28 21 - const galleryItems: Record<string, unknown[] | undefined> = { 29 + const galleryItems: Record<string, GalleryItem[] | undefined> = { 22 30 'social.grain.gallery.item': undefined 23 31 }; 24 32 ··· 31 39 const itemCollection = 'social.grain.gallery.item'; 32 40 33 41 if (!galleryItems[itemCollection]) { 34 - galleryItems[itemCollection] = await listRecords({ 42 + galleryItems[itemCollection] = (await listRecords({ 35 43 did, 36 44 collection: itemCollection 37 - }); 45 + })) as unknown as GalleryItem[]; 38 46 } 39 47 40 - const images = galleryItems['social.grain.gallery.item'] 41 - ?.filter((i) => i.value.gallery === item.cardData.galleryUri) 48 + const galleryItemsList = galleryItems['social.grain.gallery.item']; 49 + if (!galleryItemsList) continue; 50 + 51 + const images = galleryItemsList 52 + .filter((i) => i.value.gallery === item.cardData.galleryUri) 42 53 .map(async (i) => { 43 - const itemData = parseUri(i.value.item as string); 54 + const itemData = parseUri(i.value.item); 44 55 const record = await getRecord(itemData); 45 56 return { ...record, value: { ...record.value, ...i.value } }; 46 57 });
+2 -2
src/lib/cards/PopfeedReviews/Rating.svelte
··· 39 39 {/snippet} 40 40 41 41 <div class={cn('text-accent-500 flex', className)}> 42 - {#each Array(fullStars) as _} 42 + {#each Array(fullStars)} 43 43 {@render star('text-accent-500')} 44 44 {/each} 45 45 {#if hasHalfStar} 46 46 {@render halfStar()} 47 47 {/if} 48 - {#each Array(emptyStars) as _} 48 + {#each Array(emptyStars)} 49 49 {@render star('text-base-400')} 50 50 {/each} 51 51 </div>
-2
src/lib/cards/SpecialCards/UpdatedBlentos/UpdatedBlentosCard.svelte
··· 8 8 const data = getAdditionalUserData(); 9 9 // svelte-ignore state_referenced_locally 10 10 const profiles = data[item.cardType] as AppBskyActorDefs.ProfileViewDetailed[]; 11 - 12 - $inspect(profiles); 13 11 </script> 14 12 15 13 <div class="flex h-full flex-col">
-1
src/lib/cards/StandardSiteDocumentListCard/BlogEntry.svelte
··· 1 1 <script lang="ts"> 2 - import { RelativeTime } from '@foxui/time'; 3 2 import DateTime from './DateTime.svelte'; 4 3 5 4 let {
-2
src/lib/cards/StatusphereCard/SettingsStatusphereCard.svelte
··· 9 9 const data = getAdditionalUserData(); 10 10 // svelte-ignore state_referenced_locally 11 11 let record = $state(data[item.cardType] as any); 12 - 13 - let animated = $derived(emojiToNotoAnimatedWebp(record.value.status)); 14 12 </script> 15 13 16 14 <EmojiPicker
-2
src/lib/cards/StatusphereCard/StatusphereCard.svelte
··· 3 3 import { getAdditionalUserData } from '$lib/website/context'; 4 4 import { emojiToNotoAnimatedWebp } from '.'; 5 5 6 - import icons from './icons.json'; 7 - 8 6 let { item }: { item: Item } = $props(); 9 7 10 8 const data = getAdditionalUserData();
+1 -1
src/lib/cards/TealFMPlaysCard/AlbumArt.svelte
··· 1 1 <script lang="ts"> 2 - let { releaseMbId, alt }: { releaseMbId: string; alt: string } = $props(); 2 + let { releaseMbId, alt }: { releaseMbId?: string; alt: string } = $props(); 3 3 4 4 let isLoading = $state(true); 5 5 let hasError = $state(false);
+20 -4
src/lib/cards/TealFMPlaysCard/TealFMPlaysCard.svelte
··· 6 6 import AlbumArt from './AlbumArt.svelte'; 7 7 import { RelativeTime } from '@foxui/time'; 8 8 9 + interface Artist { 10 + artistName: string; 11 + } 12 + 13 + interface PlayValue { 14 + releaseMbId?: string; 15 + trackName: string; 16 + playedTime?: string; 17 + artists?: Artist[]; 18 + originUrl?: string; 19 + } 20 + 21 + interface Play { 22 + value: PlayValue; 23 + } 24 + 9 25 let { item }: { item: Item } = $props(); 10 26 11 27 const data = getAdditionalUserData(); 12 28 // svelte-ignore state_referenced_locally 13 - let feed = $state(data[item.cardType] as any); 29 + let feed = $state(data[item.cardType] as Play[] | undefined); 14 30 15 31 let did = getDidContext(); 16 32 let handle = getHandleContext(); ··· 21 37 feed = (await CardDefinitionsByType[item.cardType]?.loadData?.([], { 22 38 did, 23 39 handle 24 - })) as any; 40 + })) as Play[] | undefined; 25 41 26 42 data[item.cardType] = feed; 27 43 }); 28 44 29 45 function isNumeric(str: string) { 30 46 if (typeof str != 'string') return false; 31 - return !isNaN(str) && !isNaN(parseFloat(str)); 47 + return !isNaN(Number(str)) && !isNaN(parseFloat(str)); 32 48 } 33 49 </script> 34 50 35 - {#snippet musicItem(play)} 51 + {#snippet musicItem(play: Play)} 36 52 <div class="flex w-full items-center gap-3"> 37 53 <div class="size-10 shrink-0"> 38 54 <AlbumArt releaseMbId={play.value.releaseMbId} alt="" />
-1
src/lib/cards/VideoCard/VideoCard.svelte
··· 45 45 </script> 46 46 47 47 {#key item.cardData.video || item.cardData.objectUrl} 48 - <!-- svelte-ignore a11y_media_has_caption --> 49 48 <video 50 49 bind:this={element} 51 50 muted
-5
src/lib/cards/utils/MarkdownTextEditor.svelte
··· 10 10 import TurndownService from 'turndown'; 11 11 import { RichTextLink } from './extensions/RichTextLink'; 12 12 import type { Item } from '$lib/types'; 13 - import { textAlignClasses, verticalAlignClasses } from '../TextCard'; 14 13 15 14 let element: HTMLElement | undefined = $state(); 16 - 17 - let loaded = $state(false); 18 15 19 16 let { 20 17 editor = $bindable(), ··· 108 105 handleDOMEvents: { drop: () => false } 109 106 } 110 107 }); 111 - 112 - loaded = true; 113 108 }); 114 109 115 110 onDestroy(() => {
+2 -2
src/lib/cards/utils/extensions/RichTextLink.ts
··· 68 68 */ 69 69 const RichTextLink = Link.extend<RichTextLinkOptions>({ 70 70 inclusive: false, 71 - addOptions() { 71 + addOptions(): LinkOptions { 72 72 return { 73 73 ...this.parent?.(), 74 74 openOnClick: 'whenNotEditable' 75 - }; 75 + } as LinkOptions; 76 76 }, 77 77 addAttributes() { 78 78 return {
+32 -15
src/lib/components/bluesky-post/index.ts
··· 61 61 embed: post.embed 62 62 ? ({ 63 63 type: blueskyEmbedTypeToEmbedType(post.embed?.$type), 64 - // eslint-disable-next-line @typescript-eslint/no-explicit-any 65 - images: post.embed?.images?.map((image: any) => ({ 64 + // Cast to any to handle union type - properties are conditionally accessed 65 + images: (post.embed as any)?.images?.map((image: any) => ({ 66 66 alt: image.alt, 67 67 thumb: image.thumb, 68 68 aspectRatio: image.aspectRatio, 69 69 fullsize: image.fullsize 70 70 })), 71 - external: post.embed?.external 71 + external: (post.embed as any)?.external 72 72 ? { 73 - href: post.embed.external.uri, 74 - title: post.embed.external.title, 75 - description: post.embed.external.description, 76 - thumb: post.embed.external.thumb 73 + href: (post.embed as any).external.uri, 74 + title: (post.embed as any).external.title, 75 + description: (post.embed as any).external.description, 76 + thumb: (post.embed as any).external.thumb 77 77 } 78 78 : undefined, 79 - video: post.embed 79 + video: (post.embed as any)?.playlist 80 80 ? { 81 - playlist: post.embed.playlist, 82 - thumb: post.embed.thumbnail, 83 - alt: post.embed.alt, 84 - aspectRatio: post.embed.aspectRatio 81 + playlist: (post.embed as any).playlist, 82 + thumb: (post.embed as any).thumbnail, 83 + alt: (post.embed as any).alt, 84 + aspectRatio: (post.embed as any).aspectRatio 85 85 } 86 86 : undefined 87 87 } as PostEmbed) ··· 91 91 }; 92 92 } 93 93 94 + interface MentionFeature { 95 + $type: 'app.bsky.richtext.facet#mention'; 96 + did: string; 97 + } 98 + 99 + interface LinkFeature { 100 + $type: 'app.bsky.richtext.facet#link'; 101 + uri: string; 102 + } 103 + 104 + interface TagFeature { 105 + $type: 'app.bsky.richtext.facet#tag'; 106 + tag: string; 107 + } 108 + 109 + type Feature = MentionFeature | LinkFeature | TagFeature; 110 + 94 111 const renderSegment = (segment: RichtextSegment, baseUrl: string) => { 95 112 const { text, features } = segment; 96 113 ··· 99 116 } 100 117 101 118 // segments can have multiple features, use the first one 102 - const feature = features[0]; 119 + const feature = features[0] as Feature; 103 120 104 121 const createLink = (href: string, text: string) => { 105 122 return `<a target="_blank" rel="noopener noreferrer nofollow" href="${encodeURI(href)}">${text}</a>`; ··· 107 124 108 125 switch (feature.$type) { 109 126 case 'app.bsky.richtext.facet#mention': 110 - return createLink(`${baseUrl}/profile/${segment.handle}`, segment.text); 127 + return createLink(`${baseUrl}/profile/${feature.did}`, segment.text); 111 128 case 'app.bsky.richtext.facet#link': 112 129 return createLink(feature.uri, segment.text); 113 130 case 'app.bsky.richtext.facet#tag': 114 - return createLink(`${baseUrl}/hashtag/${segment.tag}`, segment.text); 131 + return createLink(`${baseUrl}/hashtag/${feature.tag}`, segment.text); 115 132 default: 116 133 return `<span>${text}</span>`; 117 134 }
+1 -1
src/lib/components/post/Post.svelte
··· 1 1 <script lang="ts"> 2 2 import Embed from './embeds/Embed.svelte'; 3 - import { cn, Avatar, Prose } from '@foxui/core'; 3 + import { cn, Prose } from '@foxui/core'; 4 4 import type { WithChildren, WithElementRef } from 'bits-ui'; 5 5 import type { HTMLAttributes } from 'svelte/elements'; 6 6 import type { PostData } from '.';
-1
src/lib/components/post/embeds/Video.svelte
··· 34 34 : 'aspect-ratio: 16 / 9'} 35 35 class="border-base-300 dark:border-base-400/40 w-full max-w-full overflow-hidden rounded-2xl border" 36 36 > 37 - <!-- svelte-ignore a11y_media_has_caption --> 38 37 <video bind:this={element} class="h-full w-full" aria-label={data.video.alt}></video> 39 38 </div> 40 39
+1 -8
src/lib/website/EditableWebsite.svelte
··· 38 38 } = $props(); 39 39 40 40 let imageDragOver = $state(false); 41 - let imageDragPosition: { x: number; y: number } | null = $state(null); 42 41 43 42 // svelte-ignore state_referenced_locally 44 43 let items: Item[] = $state(data.cards); ··· 136 135 await savePage(data, items, publication); 137 136 138 137 publication = JSON.stringify(data.publication); 139 - } catch (error) { 138 + } catch { 140 139 toast.error('Error saving page!'); 141 140 } finally { 142 141 isSaving = false; ··· 150 149 let showSettings = $state(false); 151 150 152 151 let debugPoint = $state({ x: 0, y: 0 }); 153 - 154 - let linkPopoverOpen = $state(false); 155 152 156 153 function getDragXY( 157 154 e: DragEvent & { ··· 315 312 316 313 if (linkValue === url) { 317 314 linkValue = ''; 318 - linkPopoverOpen = false; 319 315 } 320 316 } 321 317 ··· 384 380 event.stopPropagation(); 385 381 386 382 imageDragOver = true; 387 - imageDragPosition = { x: event.clientX, y: event.clientY }; 388 383 } 389 384 } 390 385 ··· 392 387 event.preventDefault(); 393 388 event.stopPropagation(); 394 389 imageDragOver = false; 395 - imageDragPosition = null; 396 390 } 397 391 398 392 async function handleImageDrop(event: DragEvent) { ··· 401 395 const dropX = event.clientX; 402 396 const dropY = event.clientY; 403 397 imageDragOver = false; 404 - imageDragPosition = null; 405 398 406 399 if (!event.dataTransfer?.files?.length) return; 407 400
+1 -3
src/routes/all/+page.svelte
··· 1 1 <script lang="ts"> 2 - import { createEmptyCard, refreshData } from '$lib/helper.js'; 2 + import { createEmptyCard } from '$lib/helper.js'; 3 3 import Website from '$lib/website/Website.svelte'; 4 4 5 5 let { data } = $props(); 6 - 7 - $inspect(data.profiles); 8 6 </script> 9 7 10 8 <Website