your personal website on atproto - mirror blento.app

refactor pt8

Florian c8f0ca4d f1dfd27f

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