your personal website on atproto - mirror blento.app

Merge pull request #208 from flo-bit/germ

Germ

authored by

Florian and committed by
GitHub
cc0dcf2b dd0079fd

+175 -23
+3 -1
src/lib/cards/index.ts
··· 49 import { PlyrFMCardDefinition, PlyrFMCollectionCardDefinition } from './media/PlyrFMCard'; 50 import { MarginCardDefinition } from './social/MarginCard'; 51 import { SembleCollectionCardDefinition } from './social/SembleCollectionCard'; 52 // import { Model3DCardDefinition } from './visual/Model3DCard'; 53 54 export const AllCardDefinitions = [ ··· 103 PlyrFMCardDefinition, 104 PlyrFMCollectionCardDefinition, 105 MarginCardDefinition, 106 - SembleCollectionCardDefinition 107 ] as const; 108 109 export const CardDefinitionsByType = AllCardDefinitions.reduce(
··· 49 import { PlyrFMCardDefinition, PlyrFMCollectionCardDefinition } from './media/PlyrFMCard'; 50 import { MarginCardDefinition } from './social/MarginCard'; 51 import { SembleCollectionCardDefinition } from './social/SembleCollectionCard'; 52 + import { GermDMCardDefinition } from './social/GermDMCard'; 53 // import { Model3DCardDefinition } from './visual/Model3DCard'; 54 55 export const AllCardDefinitions = [ ··· 104 PlyrFMCardDefinition, 105 PlyrFMCollectionCardDefinition, 106 MarginCardDefinition, 107 + SembleCollectionCardDefinition, 108 + GermDMCardDefinition 109 ] as const; 110 111 export const CardDefinitionsByType = AllCardDefinitions.reduce(
+1
src/lib/cards/media/TealFMPlaysCard/index.ts
··· 22 }, 23 minW: 4, 24 canHaveLabel: true, 25 26 keywords: ['music', 'scrobble', 'listening', 'songs'], 27 name: 'Teal.fm Plays',
··· 22 }, 23 minW: 4, 24 canHaveLabel: true, 25 + canAdd: ({ collections }) => collections.includes('fm.teal.alpha.feed.play'), 26 27 keywords: ['music', 'scrobble', 'listening', 'songs'], 28 name: 'Teal.fm Plays',
+73
src/lib/cards/social/GermDMCard/GermDMCard.svelte
···
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import { getAdditionalUserData, getDidContext, getHandleContext } from '$lib/website/context'; 4 + import type { ContentComponentProps } from '../../types'; 5 + import { CardDefinitionsByType } from '../..'; 6 + import { user } from '$lib/atproto/auth.svelte'; 7 + import { platformsData } from '../BigSocialCard'; 8 + 9 + let { item = $bindable(), isEditing }: ContentComponentProps = $props(); 10 + 11 + const data = getAdditionalUserData(); 12 + 13 + let germData = $state( 14 + data[item.cardType] as { messageMeUrl: string; showButtonTo: string } | null | undefined 15 + ); 16 + 17 + let did = getDidContext(); 18 + let handle = getHandleContext(); 19 + let isLoaded = $state(false); 20 + 21 + onMount(async () => { 22 + if (!germData) { 23 + germData = (await CardDefinitionsByType[item.cardType]?.loadData?.([], { 24 + did, 25 + handle 26 + })) as { messageMeUrl: string; showButtonTo: string } | null | undefined; 27 + 28 + data[item.cardType] = germData; 29 + isLoaded = true; 30 + } 31 + }); 32 + 33 + const brandColor = `#${platformsData.germ.hex}`; 34 + 35 + const dmUrl = $derived.by(() => { 36 + if (!germData?.messageMeUrl) return undefined; 37 + const viewerDid = user.did; 38 + const fragment = viewerDid ? `${did}+${viewerDid}` : `${did}`; 39 + return `${germData.messageMeUrl}/web#${fragment}`; 40 + }); 41 + </script> 42 + 43 + {#if germData && dmUrl} 44 + <div 45 + class="flex h-full w-full items-center justify-center p-10" 46 + style="background-color: {brandColor}" 47 + > 48 + <div 49 + class="flex aspect-square max-h-full max-w-full items-center justify-center [&_svg]:size-full [&_svg]:max-w-60 [&_svg]:fill-white" 50 + > 51 + {@html platformsData.germ.svg} 52 + </div> 53 + </div> 54 + 55 + {#if !isEditing} 56 + <a href={dmUrl} target="_blank" rel="noopener noreferrer"> 57 + <div class="absolute inset-0 z-50"></div> 58 + <span class="sr-only">Message me on Germ</span> 59 + </a> 60 + {/if} 61 + {:else if isLoaded} 62 + <div 63 + class="text-base-500 dark:text-base-400 accent:text-white/70 flex h-full w-full items-center justify-center p-4 text-center text-sm" 64 + > 65 + Germ DM not available 66 + </div> 67 + {:else} 68 + <div 69 + class="text-base-500 dark:text-base-400 accent:text-white/70 flex h-full w-full items-center justify-center p-4 text-center text-sm" 70 + > 71 + Loading... 72 + </div> 73 + {/if}
+45
src/lib/cards/social/GermDMCard/index.ts
···
··· 1 + import { getRecord } from '$lib/atproto'; 2 + import type { CardDefinition } from '../../types'; 3 + import { platformsData } from '../BigSocialCard'; 4 + import GermDMCard from './GermDMCard.svelte'; 5 + 6 + const germ = platformsData.germ; 7 + 8 + export const GermDMCardDefinition = { 9 + type: 'germDM', 10 + contentComponent: GermDMCard, 11 + createNew: (card) => { 12 + card.w = 2; 13 + card.h = 2; 14 + card.mobileW = 4; 15 + card.mobileH = 4; 16 + card.cardData.label = 'Message me'; 17 + }, 18 + loadData: async (_items, { did }) => { 19 + try { 20 + const record = await getRecord({ 21 + did, 22 + collection: 'com.germnetwork.declaration', 23 + rkey: 'self' 24 + }); 25 + const value = record?.value; 26 + if (!value?.messageMe) return null; 27 + return { 28 + messageMeUrl: value.messageMe.messageMeUrl, 29 + showButtonTo: value.messageMe.showButtonTo 30 + }; 31 + } catch { 32 + return null; 33 + } 34 + }, 35 + canAdd: ({ collections }) => collections.includes('com.germnetwork.declaration'), 36 + defaultColor: 'transparent', 37 + allowSetColor: false, 38 + canHaveLabel: true, 39 + name: 'DM on Germ', 40 + keywords: ['germ', 'dm', 'message', 'chat', 'encrypted'], 41 + groups: ['Social'], 42 + icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> 43 + <path stroke-linecap="round" stroke-linejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 0 1-1.161.886l-.143.048a1.107 1.107 0 0 0-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 0 1-1.652.928l-.679-.906a1.125 1.125 0 0 0-1.906.172L4.5 15.75l-.612.153M12.75 3.031a9 9 0 0 0-8.862 12.872M12.75 3.031a9 9 0 0 1 6.69 14.036m0 0-.177-.529A2.25 2.25 0 0 0 17.128 15H16.5l-.324-.324a1.453 1.453 0 0 0-2.328.377l-.036.073a1.586 1.586 0 0 1-.982.816l-.99.282c-.55.157-.894.702-.8 1.267l.073.438c.08.474.49.821.97.821.846 0 1.598.542 1.865 1.345l.215.643m5.276-3.67a9.012 9.012 0 0 1-5.276 3.67m0 0a9 9 0 0 1-10.275-4.835M15.75 9c0 .896-.393 1.7-1.016 2.25" /> 44 + </svg>` 45 + } as CardDefinition & { type: 'germDM' };
+2
src/lib/cards/types.ts
··· 73 74 canResize?: boolean; 75 76 onUrlHandler?: (url: string, item: Item) => Item | null; 77 urlHandlerPriority?: number; 78
··· 73 74 canResize?: boolean; 75 76 + canAdd?: (context: { collections: string[] }) => boolean; 77 + 78 onUrlHandler?: (url: string, item: Item) => Item | null; 79 urlHandlerPriority?: number; 80
+40 -18
src/lib/components/card-command/CardCommand.svelte
··· 3 import type { CardDefinition } from '$lib/cards/types'; 4 import { Command, Dialog } from 'bits-ui'; 5 import { isTyping } from '$lib/helper'; 6 - 7 - const CardDefGroups = [ 8 - 'Core', 9 - ...Array.from( 10 - new Set( 11 - AllCardDefinitions.map((cardDef) => cardDef.groups) 12 - .flat() 13 - .filter((g) => g) 14 - ) 15 - ) 16 - .sort() 17 - .filter((g) => g !== 'Core') 18 - ]; 19 20 let { 21 open = $bindable(false), ··· 27 onlink?: (url: string, cardDef: CardDefinition) => void; 28 } = $props(); 29 30 let searchValue = $state(''); 31 32 let normalizedUrl = $derived.by(() => { ··· 44 45 let urlMatchingCards = $derived.by(() => { 46 if (!normalizedUrl) return []; 47 - return AllCardDefinitions.filter((d) => d.onUrlHandler) 48 .filter((d) => { 49 try { 50 const testItem = { cardData: {} }; ··· 150 <Command.Separator class="bg-base-900/5 dark:bg-base-50/5 my-1 h-px w-full" /> 151 {/if} 152 153 - {#each CardDefGroups as group, index (group)} 154 - {#if group && AllCardDefinitions.some((cardDef) => cardDef.groups?.includes(group))} 155 <Command.Group> 156 <Command.GroupHeading 157 class="text-base-600 dark:text-base-400 px-3 pt-4 pb-2 text-xs" ··· 159 {group} 160 </Command.GroupHeading> 161 <Command.GroupItems> 162 - {#each AllCardDefinitions.filter( (cardDef) => cardDef.groups?.includes(group) ) as cardDef (cardDef.type)} 163 <Command.Item 164 onSelect={() => { 165 open = false; ··· 179 {/each} 180 </Command.GroupItems> 181 </Command.Group> 182 - {#if index < CardDefGroups.length - 1} 183 <Command.Separator class="bg-base-900/5 dark:bg-base-50/5 my-1 h-px w-full" /> 184 {/if} 185 {/if}
··· 3 import type { CardDefinition } from '$lib/cards/types'; 4 import { Command, Dialog } from 'bits-ui'; 5 import { isTyping } from '$lib/helper'; 6 + import { describeRepo, user } from '$lib/atproto'; 7 8 let { 9 open = $bindable(false), ··· 15 onlink?: (url: string, cardDef: CardDefinition) => void; 16 } = $props(); 17 18 + let collections = $state<string[]>([]); 19 + let fetchedForDid = $state<string | undefined>(undefined); 20 + 21 + $effect(() => { 22 + if (open && user.did && fetchedForDid !== user.did) { 23 + const did = user.did; 24 + describeRepo({ did }).then((result) => { 25 + if (result?.collections) { 26 + collections = result.collections; 27 + } 28 + fetchedForDid = did; 29 + }); 30 + } 31 + }); 32 + 33 + let filteredCardDefs = $derived( 34 + AllCardDefinitions.filter((d) => !d.canAdd || d.canAdd({ collections })) 35 + ); 36 + 37 + let cardDefGroups = $derived([ 38 + 'Core', 39 + ...Array.from( 40 + new Set( 41 + filteredCardDefs 42 + .map((cardDef) => cardDef.groups) 43 + .flat() 44 + .filter((g) => g) 45 + ) 46 + ) 47 + .sort() 48 + .filter((g) => g !== 'Core') 49 + ]); 50 + 51 let searchValue = $state(''); 52 53 let normalizedUrl = $derived.by(() => { ··· 65 66 let urlMatchingCards = $derived.by(() => { 67 if (!normalizedUrl) return []; 68 + return filteredCardDefs 69 + .filter((d) => d.onUrlHandler) 70 .filter((d) => { 71 try { 72 const testItem = { cardData: {} }; ··· 172 <Command.Separator class="bg-base-900/5 dark:bg-base-50/5 my-1 h-px w-full" /> 173 {/if} 174 175 + {#each cardDefGroups as group, index (group)} 176 + {#if group && filteredCardDefs.some((cardDef) => cardDef.groups?.includes(group))} 177 <Command.Group> 178 <Command.GroupHeading 179 class="text-base-600 dark:text-base-400 px-3 pt-4 pb-2 text-xs" ··· 181 {group} 182 </Command.GroupHeading> 183 <Command.GroupItems> 184 + {#each filteredCardDefs.filter( (cardDef) => cardDef.groups?.includes(group) ) as cardDef (cardDef.type)} 185 <Command.Item 186 onSelect={() => { 187 open = false; ··· 201 {/each} 202 </Command.GroupItems> 203 </Command.Group> 204 + {#if index < cardDefGroups.length - 1} 205 <Command.Separator class="bg-base-900/5 dark:bg-base-50/5 my-1 h-px w-full" /> 206 {/if} 207 {/if}
+3 -1
src/lib/website/Context.svelte
··· 20 setAdditionalUserData(data.additionalData); 21 22 setCanEdit( 23 - () => (dev && isEditing === true) || (user.isLoggedIn && user.profile?.did === data.did && isEditing === true) 24 ); 25 26 // svelte-ignore state_referenced_locally
··· 20 setAdditionalUserData(data.additionalData); 21 22 setCanEdit( 23 + () => 24 + (dev && isEditing === true) || 25 + (user.isLoggedIn && user.profile?.did === data.did && isEditing === true) 26 ); 27 28 // svelte-ignore state_referenced_locally
+8 -3
src/routes/(auth)/oauth/callback/+page.svelte
··· 13 if (user.profile) { 14 if (hasRedirected) return; 15 16 - const redirect = localStorage.getItem('login-redirect'); 17 localStorage.removeItem('login-redirect'); 18 - console.log('redirect', redirect); 19 - goto(redirect || '/' + getHandleOrDid(user.profile) + '/edit', {}); 20 21 hasRedirected = true; 22 }
··· 13 if (user.profile) { 14 if (hasRedirected) return; 15 16 + let redirect = localStorage.getItem('login-redirect'); 17 localStorage.removeItem('login-redirect'); 18 + 19 + const editPath = '/' + getHandleOrDid(user.profile) + '/edit'; 20 + if (!redirect || redirect === '/' || redirect === 'https://blento.app' || redirect === 'https://blento.app/') { 21 + redirect = editPath; 22 + } 23 + 24 + goto(redirect, {}); 25 26 hasRedirected = true; 27 }