data endpoint for entity 90008 (aka. a website)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at svelte 132 lines 3.8 kB view raw
1<script module lang="ts"> 2 import type { Post } from '$lib/bluesky'; 3 4 export interface OutgoingLink { 5 name: string; 6 link: string; 7 } 8 9 export interface NoteData { 10 content: string; 11 published: number; 12 hasMedia: boolean; 13 hasQuote: boolean; 14 outgoingLinks?: OutgoingLink[]; 15 purposeAction?: string; 16 children?: NoteData[]; 17 depth?: number; 18 } 19 20 export const flattenNotes = (note: NoteData, currentDepth: number = 0): NoteData[] => { 21 note.depth = currentDepth; 22 const flattened = [note]; 23 if (note.children) { 24 note.children.forEach((child) => { 25 flattened.push(...flattenNotes(child, currentDepth + 1)); 26 }); 27 } 28 return flattened; 29 }; 30 31 export const noteFromBskyPost = ({ record: post, uri }: Post): NoteData => { 32 return { 33 content: post.text, 34 published: new Date(post.createdAt).getTime(), 35 outgoingLinks: [{ name: 'bsky', link: uri }], 36 hasMedia: 37 (post.embed?.$type === 'app.bsky.embed.images' || 38 post.embed?.$type === 'app.bsky.embed.video' || 39 post.embed?.$type === 'app.bsky.embed.recordWithMedia') ?? 40 false, 41 hasQuote: 42 (post.embed?.$type === 'app.bsky.embed.record' || 43 post.embed?.$type === 'app.bsky.embed.recordWithMedia') ?? 44 false 45 }; 46 }; 47</script> 48 49<script lang="ts"> 50 import Token from './token.svelte'; 51 import { renderDate, renderRelativeDate } from '$lib/dateFmt'; 52 53 interface Props { 54 rootNote: NoteData; 55 isHighlighted?: boolean; 56 onlyContent?: boolean; 57 showOutgoing?: boolean; 58 mapOutgoingNames?: Record<string, string>; 59 } 60 61 let { 62 rootNote, 63 isHighlighted = false, 64 onlyContent = false, 65 showOutgoing = true, 66 mapOutgoingNames = {} 67 }: Props = $props(); 68 69 const getOutgoingLink = ({ name, link }: { name: string; link: string }) => { 70 if (name.startsWith('bsky')) { 71 // Parse the atproto URI to extract DID and rkey 72 const match = link.match(/at:\/\/(did:[^/]+)\/[^/]+\/([^/]+)/); 73 if (match && match.length >= 3) { 74 // eslint-disable-next-line @typescript-eslint/no-unused-vars 75 const [_, did, rkey] = match; 76 link = `https://bsky.app/profile/${did}/post/${rkey}`; 77 } 78 if (name === 'bsky-reply') { 79 return ['reply', link]; 80 } else { 81 return [name, link]; 82 } 83 } 84 return [name, link]; 85 }; 86 // this is ASS this should be a tailwind class 87 const getTextShadowStyle = (color: string) => { 88 return `text-shadow: 0 0 1px theme(colors.ralsei.black), 0 0 5px ${color};`; 89 }; 90 const outgoingLinkColors: Record<string, string> = { 91 bsky: 'rgb(0, 133, 255)', 92 reply: 'rgb(0, 133, 255)' 93 }; 94</script> 95 96{#each flattenNotes(rootNote) as note} 97 <p class="m-0 max-w-[70ch] text-wrap break-words leading-tight align-middle"> 98 {#if note.depth ?? 0 > 0} 99 <span class="inline-block">|{'=='.repeat(note.depth ?? 0)}</span>&gt; 100 {/if} 101 {#if !onlyContent} 102 {#if (note.purposeAction ?? '').length > 0} 103 <Token v="({note.purposeAction!})" small={!isHighlighted} funct /> 104 {/if} 105 {#if note.purposeAction !== 'reply'} 106 <Token 107 title={renderDate(note.published)} 108 v={renderRelativeDate(note.published)} 109 small={!isHighlighted} 110 /> 111 {/if} 112 {/if} 113 <Token v={note.content} str /> 114 {#if note.hasMedia}<Token v="-contains media-" keywd small />{/if} 115 {#if note.hasQuote}<Token v="-contains quote-" keywd small />{/if} 116 {#if showOutgoing} 117 {#each (note.outgoingLinks ?? []).map(getOutgoingLink) as [name, link]} 118 {@const color = outgoingLinkColors[name]} 119 {@const viewName = mapOutgoingNames[name] ?? name} 120 {#if viewName.length > 0} 121 <span class="text-sm" 122 ><Token v="(" punct /><a 123 class="hover:motion-safe:animate-squiggle hover:underline" 124 style="color: {color};{getTextShadowStyle(color)}" 125 href={link}>{viewName}</a 126 ><Token v=")" punct /></span 127 > 128 {/if} 129 {/each} 130 {/if} 131 </p> 132{/each}