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