your personal website on atproto - mirror blento.app

commit

Florian bcc90181 9059b09f

+377 -26
-1
src/lib/cards/BlueskyMediaCard/CreateBlueskyMediaCardModal.svelte
··· 62 62 {#each mediaList as media (media.thumbnail || media.playlist)} 63 63 <button 64 64 onclick={() => { 65 - console.log(media); 66 65 selected = media; 67 66 if (media.isVideo) { 68 67 item.cardData = {
-1
src/lib/cards/FluidTextCard/FluidTextCard.svelte
··· 203 203 204 204 const computedColor = getHexOfCardColor(item); 205 205 const hue = colorToHue(computedColor) / 360; 206 - console.log(computedColor, hue); 207 206 208 207 // Wait for a frame to ensure dimensions are set 209 208 requestAnimationFrame(() => {
+76
src/lib/cards/GitHubContributorsCard/CreateGitHubContributorsCardModal.svelte
··· 1 + <script lang="ts"> 2 + import { Button, Input, Modal, Subheading } from '@foxui/core'; 3 + import type { CreationModalComponentProps } from '../types'; 4 + 5 + let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props(); 6 + 7 + let errorMessage = $state(''); 8 + let inputValue = $state(''); 9 + </script> 10 + 11 + <Modal open={true} closeButton={false}> 12 + <form 13 + onsubmit={() => { 14 + let input = inputValue.trim(); 15 + if (!input) { 16 + errorMessage = 'Please enter a repository in owner/repo format or a GitHub URL'; 17 + return; 18 + } 19 + 20 + let owner: string | undefined; 21 + let repo: string | undefined; 22 + 23 + // Try parsing as URL first 24 + try { 25 + const parsed = new URL(input); 26 + if (/^(www\.)?github\.com$/.test(parsed.hostname)) { 27 + const segments = parsed.pathname.split('/').filter(Boolean); 28 + if (segments.length >= 2) { 29 + owner = segments[0]; 30 + repo = segments[1]; 31 + } 32 + } 33 + } catch { 34 + // Not a URL, try as owner/repo format 35 + const parts = input.split('/'); 36 + if (parts.length === 2) { 37 + owner = parts[0].trim(); 38 + repo = parts[1].trim(); 39 + } 40 + } 41 + 42 + if (!owner || !repo) { 43 + errorMessage = 'Please enter a valid owner/repo or GitHub repository URL'; 44 + return; 45 + } 46 + 47 + item.cardData.owner = owner; 48 + item.cardData.repo = repo; 49 + item.cardData.href = `https://github.com/${owner}/${repo}`; 50 + 51 + item.w = 4; 52 + item.mobileW = 8; 53 + item.h = 2; 54 + item.mobileH = 4; 55 + 56 + oncreate?.(); 57 + }} 58 + class="flex flex-col gap-2" 59 + > 60 + <Subheading>Enter a GitHub repository</Subheading> 61 + <Input 62 + bind:value={inputValue} 63 + placeholder="owner/repo or https://github.com/owner/repo" 64 + class="mt-4" 65 + /> 66 + 67 + {#if errorMessage} 68 + <p class="mt-2 text-sm text-red-600">{errorMessage}</p> 69 + {/if} 70 + 71 + <div class="mt-4 flex justify-end gap-2"> 72 + <Button onclick={oncancel} variant="ghost">Cancel</Button> 73 + <Button type="submit">Create</Button> 74 + </div> 75 + </form> 76 + </Modal>
+149
src/lib/cards/GitHubContributorsCard/GitHubContributorsCard.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import type { ContentComponentProps } from '../types'; 4 + import { getAdditionalUserData, getCanEdit, getIsMobile } from '$lib/website/context'; 5 + import type { GitHubContributor, GitHubContributorsLoadedData } from '.'; 6 + 7 + let { item }: ContentComponentProps = $props(); 8 + 9 + const isMobile = getIsMobile(); 10 + const canEdit = getCanEdit(); 11 + const additionalData = getAdditionalUserData(); 12 + 13 + let owner: string = $derived(item.cardData.owner ?? ''); 14 + let repo: string = $derived(item.cardData.repo ?? ''); 15 + let repoKey: string = $derived(owner && repo ? `${owner}/${repo}` : ''); 16 + 17 + let serverContributors: GitHubContributor[] = $derived.by(() => { 18 + if (!repoKey) return []; 19 + const data = additionalData[item.cardType] as GitHubContributorsLoadedData | undefined; 20 + return data?.[repoKey] ?? []; 21 + }); 22 + 23 + let clientContributors: GitHubContributor[] = $state([]); 24 + 25 + let allContributors: GitHubContributor[] = $derived( 26 + serverContributors.length > 0 ? serverContributors : clientContributors 27 + ); 28 + 29 + let namedContributors: GitHubContributor[] = $derived( 30 + allContributors.filter((c) => !c.anonymous) 31 + ); 32 + 33 + onMount(() => { 34 + if (serverContributors.length === 0 && repoKey) { 35 + loadContributors(); 36 + } 37 + }); 38 + 39 + async function loadContributors() { 40 + if (!owner || !repo) return; 41 + try { 42 + const response = await fetch( 43 + `/api/github/contributors?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}` 44 + ); 45 + if (response.ok) { 46 + const data = await response.json(); 47 + clientContributors = [ 48 + ...data, 49 + ...data.map((v) => { 50 + return { ...v, username: v.username + '1' }; 51 + }) 52 + ]; 53 + } 54 + } catch (error) { 55 + console.error('Failed to fetch GitHub contributors:', error); 56 + } 57 + } 58 + 59 + let containerWidth = $state(0); 60 + let containerHeight = $state(0); 61 + 62 + let totalItems = $derived(namedContributors.length); 63 + 64 + const GAP = 6; 65 + const MIN_SIZE = 16; 66 + const MAX_SIZE = 80; 67 + 68 + let computedSize = $derived.by(() => { 69 + if (!containerWidth || !containerHeight || totalItems === 0) return 40; 70 + 71 + let lo = MIN_SIZE; 72 + let hi = MAX_SIZE; 73 + 74 + while (lo <= hi) { 75 + const mid = Math.floor((lo + hi) / 2); 76 + // Reserve ~1 avatar of padding on each side 77 + const availW = containerWidth - mid * 2; 78 + const availH = containerHeight - mid * 2; 79 + if (availW <= 0 || availH <= 0) { 80 + hi = mid - 1; 81 + continue; 82 + } 83 + const cols = Math.floor((availW + GAP) / (mid + GAP)); 84 + const rows = Math.floor((availH + GAP) / (mid + GAP)); 85 + if (cols > 0 && rows > 0 && cols * rows >= totalItems) { 86 + lo = mid + 1; 87 + } else { 88 + hi = mid - 1; 89 + } 90 + } 91 + 92 + return Math.max(MIN_SIZE, hi); 93 + }); 94 + 95 + let padding = $derived(computedSize / 2); 96 + 97 + let textSize = $derived( 98 + computedSize < 24 ? 'text-[10px]' : computedSize < 40 ? 'text-xs' : 'text-sm' 99 + ); 100 + </script> 101 + 102 + <div 103 + class="flex h-full w-full items-center justify-center overflow-hidden px-2" 104 + bind:clientWidth={containerWidth} 105 + bind:clientHeight={containerHeight} 106 + > 107 + {#if !owner || !repo} 108 + {#if canEdit()} 109 + <span class="text-base-400 dark:text-base-500 accent:text-accent-300 text-sm"> 110 + Enter a repository 111 + </span> 112 + {/if} 113 + {:else if totalItems > 0} 114 + <div style="padding: {padding}px;"> 115 + <div class="flex flex-wrap items-center justify-center" style="gap: {GAP}px;"> 116 + {#each namedContributors as contributor (contributor.username)} 117 + <div class="relative"> 118 + <a 119 + href="https://github.com/{contributor.username}" 120 + target="_blank" 121 + rel="noopener noreferrer" 122 + class="accent:ring-accent-500 relative block rounded-full ring-2 ring-white transition-transform hover:scale-110 dark:ring-neutral-900" 123 + > 124 + {#if contributor.avatarUrl} 125 + <img 126 + src={contributor.avatarUrl} 127 + alt={contributor.username} 128 + class="rounded-full object-cover" 129 + style="width: {computedSize}px; height: {computedSize}px;" 130 + /> 131 + {:else} 132 + <div 133 + class="bg-base-200 dark:bg-base-700 accent:bg-accent-400 flex items-center justify-center rounded-full" 134 + style="width: {computedSize}px; height: {computedSize}px;" 135 + > 136 + <span 137 + class="text-base-500 dark:text-base-400 accent:text-accent-100 {textSize} font-medium" 138 + > 139 + {contributor.username.charAt(0).toUpperCase()} 140 + </span> 141 + </div> 142 + {/if} 143 + </a> 144 + </div> 145 + {/each} 146 + </div> 147 + </div> 148 + {/if} 149 + </div>
+55
src/lib/cards/GitHubContributorsCard/index.ts
··· 1 + import type { CardDefinition } from '../types'; 2 + import GitHubContributorsCard from './GitHubContributorsCard.svelte'; 3 + import CreateGitHubContributorsCardModal from './CreateGitHubContributorsCardModal.svelte'; 4 + 5 + export type GitHubContributor = { 6 + username: string; 7 + avatarUrl: string | null; 8 + contributions: number; 9 + anonymous: boolean; 10 + }; 11 + 12 + export type GitHubContributorsLoadedData = Record<string, GitHubContributor[] | undefined>; 13 + 14 + export const GitHubContributorsCardDefinition = { 15 + type: 'githubContributors', 16 + contentComponent: GitHubContributorsCard, 17 + creationModalComponent: CreateGitHubContributorsCardModal, 18 + createNew: (card) => { 19 + card.w = 4; 20 + card.h = 2; 21 + card.mobileW = 8; 22 + card.mobileH = 4; 23 + card.cardData.owner = ''; 24 + card.cardData.repo = ''; 25 + }, 26 + loadData: async (items) => { 27 + const contributorsData: GitHubContributorsLoadedData = {}; 28 + for (const item of items) { 29 + const { owner, repo } = item.cardData; 30 + if (!owner || !repo) continue; 31 + const key = `${owner}/${repo}`; 32 + if (contributorsData[key]) continue; 33 + try { 34 + const response = await fetch( 35 + `https://blento.app/api/github/contributors?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}` 36 + ); 37 + if (response.ok) { 38 + contributorsData[key] = await response.json(); 39 + } 40 + } catch (error) { 41 + console.error('Failed to fetch GitHub contributors:', error); 42 + } 43 + } 44 + return contributorsData; 45 + }, 46 + allowSetColor: true, 47 + defaultColor: 'base', 48 + minW: 2, 49 + minH: 2, 50 + name: 'GitHub Contributors', 51 + groups: ['Social'], 52 + keywords: ['github', 'contributors', 'open source', 'repository'], 53 + icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" /></svg>`, 54 + canHaveLabel: true 55 + } as CardDefinition & { type: 'githubContributors' };
-1
src/lib/cards/GitHubProfileCard/GitHubProfileCard.svelte
··· 21 21 ); 22 22 23 23 onMount(async () => { 24 - console.log(contributionsData); 25 24 if (!contributionsData && item.cardData?.user) { 26 25 try { 27 26 const response = await fetch(`/api/github?user=${encodeURIComponent(item.cardData.user)}`);
-1
src/lib/cards/GitHubProfileCard/index.ts
··· 30 30 onUrlHandler: (url, item) => { 31 31 const username = getGitHubUsername(url); 32 32 33 - console.log(username); 34 33 if (!username) return; 35 34 36 35 item.cardData.href = url;
-2
src/lib/cards/LatestBlueskyPostCard/LatestBlueskyPostCard.svelte
··· 23 23 })) as any 24 24 ).feed; 25 25 26 - console.log(feed); 27 - 28 26 data[item.cardType] = feed; 29 27 } 30 28 });
-1
src/lib/cards/LivestreamCard/index.ts
··· 65 65 }, 66 66 67 67 onUrlHandler: (url, item) => { 68 - console.log(url, 'https://stream.place/' + user.profile?.handle); 69 68 if (url === 'https://stream.place/' + user.profile?.handle) { 70 69 item.w = 4; 71 70 item.h = 4;
-2
src/lib/cards/MapCard/CreateMapCardModal.svelte
··· 20 20 if (response.ok) { 21 21 const data = await response.json(); 22 22 23 - console.log(data); 24 - 25 23 if (!data.lat || !data.lon) throw new Error('lat or lon not found'); 26 24 27 25 item.cardData.lat = data.lat;
+1 -1
src/lib/cards/MapCard/Map.svelte
··· 15 15 16 16 onMount(() => { 17 17 if (!mapContainer || !env.PUBLIC_MAPBOX_TOKEN) { 18 - console.log('no map container or no mapbox token'); 18 + console.error('no map container or no mapbox token'); 19 19 return; 20 20 } 21 21
-3
src/lib/cards/PhotoGalleryCard/PhotoGalleryCard.svelte
··· 33 33 let handle = getHandleContext(); 34 34 35 35 onMount(async () => { 36 - console.log(feed); 37 36 if (!feed) { 38 37 feed = ( 39 38 (await CardDefinitionsByType[item.cardType]?.loadData?.([item], { ··· 41 40 handle 42 41 })) as Record<string, PhotoItem[]> | undefined 43 42 )?.[item.cardData.galleryUri]; 44 - 45 - console.log(feed); 46 43 47 44 data[item.cardType] = feed; 48 45 }
-3
src/lib/cards/PopfeedReviews/PopfeedReviewsCard.svelte
··· 21 21 let handle = getHandleContext(); 22 22 23 23 onMount(async () => { 24 - console.log(feed); 25 24 if (!feed) { 26 25 feed = (await CardDefinitionsByType[item.cardType]?.loadData?.([], { 27 26 did, 28 27 handle 29 28 })) as any; 30 - 31 - console.log(feed); 32 29 33 30 data[item.cardType] = feed; 34 31 }
+3 -1
src/lib/cards/index.ts
··· 36 36 import { ButtonCardDefinition } from './ButtonCard'; 37 37 import { GuestbookCardDefinition } from './GuestbookCard'; 38 38 import { FriendsCardDefinition } from './FriendsCard'; 39 + import { GitHubContributorsCardDefinition } from './GitHubContributorsCard'; 39 40 // import { Model3DCardDefinition } from './Model3DCard'; 40 41 41 42 export const AllCardDefinitions = [ ··· 76 77 SpotifyCardDefinition, 77 78 AppleMusicCardDefinition, 78 79 // Model3DCardDefinition 79 - FriendsCardDefinition 80 + FriendsCardDefinition, 81 + GitHubContributorsCardDefinition 80 82 ] as const; 81 83 82 84 export const CardDefinitionsByType = AllCardDefinitions.reduce(
-2
src/lib/components/bluesky-post/index.ts
··· 35 35 // const reason = data.reason; 36 36 // const reply = data.reply?.parent; 37 37 // const replyId = reply?.uri?.split('/').pop(); 38 - console.log(JSON.parse(JSON.stringify(data))); 39 - 40 38 const id = post.uri.split('/').pop(); 41 39 42 40 return {
+1 -1
src/lib/website/EditableWebsite.svelte
··· 271 271 // Refresh cached data 272 272 await fetch('/' + data.handle + '/api/refresh'); 273 273 } catch (error) { 274 - console.log(error); 274 + console.error(error); 275 275 showSaveModal = false; 276 276 toast.error('Error saving page!'); 277 277 } finally {
+38
src/lib/website/EmptyState.svelte
··· 2 2 import BaseCard from '$lib/cards/BaseCard/BaseCard.svelte'; 3 3 import Card from '$lib/cards/Card/Card.svelte'; 4 4 import type { Item, WebsiteData } from '$lib/types'; 5 + import { text } from '@sveltejs/kit'; 5 6 6 7 let { data }: { data: WebsiteData } = $props(); 7 8 ··· 44 45 platform: 'bluesky', 45 46 href: `https://bsky.app/profile/${data.handle}`, 46 47 color: '0285FF' 48 + } 49 + }); 50 + 51 + items.push({ 52 + id: 'empty-instruction', 53 + x: 0, 54 + y: 3, 55 + w: 8, 56 + h: 1, 57 + mobileX: 0, 58 + mobileY: 6, 59 + mobileW: 8, 60 + mobileH: 2, 61 + cardType: 'text', 62 + color: 'transparent', 63 + cardData: { 64 + text: `Is this your account? Login to start creating your blento!`, 65 + textAlign: 'center', 66 + verticalAlign: 'bottom' 67 + } 68 + }); 69 + 70 + items.push({ 71 + id: 'empty-login-button', 72 + x: 0, 73 + y: 4, 74 + w: 8, 75 + h: 1, 76 + mobileX: 0, 77 + mobileY: 8, 78 + mobileW: 8, 79 + mobileH: 2, 80 + cardType: 'button', 81 + color: 'transparent', 82 + cardData: { 83 + href: '#login', 84 + text: `Login` 47 85 } 48 86 }); 49 87
-1
src/routes/(auth)/oauth/callback/+page.svelte
··· 4 4 import { getHandleOrDid } from '$lib/atproto/methods'; 5 5 6 6 $effect(() => { 7 - console.log('hello', user); 8 7 if (user.profile) { 9 8 goto('/' + getHandleOrDid(user.profile) + '/edit', {}); 10 9 }
+1 -3
src/routes/api/github/+server.ts
··· 26 26 27 27 try { 28 28 const response = await fetch(GithubAPIURL + user); 29 - console.log('hello', user); 30 29 31 30 if (!response.ok) { 32 - console.log('error', response.statusText); 31 + console.error('error', response.statusText); 33 32 return json( 34 33 { error: 'Failed to fetch GitHub data ' + response.statusText }, 35 34 { status: response.status } ··· 39 38 const data = await response.json(); 40 39 41 40 if (!data?.user) { 42 - console.log('user not found', response.statusText); 43 41 return json({ error: 'User not found' }, { status: 404 }); 44 42 } 45 43
+53
src/routes/api/github/contributors/+server.ts
··· 1 + import { json } from '@sveltejs/kit'; 2 + import type { RequestHandler } from './$types'; 3 + 4 + const GithubContributorsAPIURL = 5 + 'https://edge-function-github-contribution.vercel.app/api/github-contributors'; 6 + 7 + export const GET: RequestHandler = async ({ url, platform }) => { 8 + const owner = url.searchParams.get('owner'); 9 + const repo = url.searchParams.get('repo'); 10 + 11 + if (!owner || !repo) { 12 + return json({ error: 'Missing owner or repo parameter' }, { status: 400 }); 13 + } 14 + 15 + const cacheKey = `#github-contributors:${owner}/${repo}`; 16 + const cachedData = await platform?.env?.USER_DATA_CACHE?.get(cacheKey); 17 + 18 + if (cachedData) { 19 + const parsedCache = JSON.parse(cachedData); 20 + 21 + const TWELVE_HOURS = 12 * 60 * 60 * 1000; 22 + const now = Date.now(); 23 + 24 + if (now - (parsedCache.updatedAt || 0) < TWELVE_HOURS) { 25 + return json(parsedCache.data); 26 + } 27 + } 28 + 29 + try { 30 + const response = await fetch( 31 + `${GithubContributorsAPIURL}?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}` 32 + ); 33 + 34 + if (!response.ok) { 35 + return json( 36 + { error: 'Failed to fetch GitHub contributors ' + response.statusText }, 37 + { status: response.status } 38 + ); 39 + } 40 + 41 + const data = await response.json(); 42 + 43 + await platform?.env?.USER_DATA_CACHE?.put( 44 + cacheKey, 45 + JSON.stringify({ data, updatedAt: Date.now() }) 46 + ); 47 + 48 + return json(data); 49 + } catch (error) { 50 + console.error('Error fetching GitHub contributors:', error); 51 + return json({ error: 'Failed to fetch GitHub contributors' }, { status: 500 }); 52 + } 53 + };
-2
src/routes/api/update/+server.ts
··· 18 18 for (const handle of existingUsersHandle) { 19 19 if (!handle) continue; 20 20 21 - console.log('updating', handle); 22 21 try { 23 22 const cached = await getCache(handle, 'self', cache as UserCache); 24 23 if (!cached) await loadData(handle, cache as UserCache, true); ··· 26 25 console.error(error); 27 26 return json('error'); 28 27 } 29 - console.log('updated', handle); 30 28 } 31 29 32 30 return json('ok');