import { marked } from 'marked'; import './style.css' function renderTextWithFacets(text: string, facets: any[]): string { if (!facets || facets.length === 0) { return text; } // convert text to UTF-8 bytes because Bluesky counts bytes in UTF-8 const encoder = new TextEncoder(); const postBytes = encoder.encode(text); // sort facets by start position facets.sort((a, b) => a.byteStart - b.byteStart); let result = ''; let lastPos = 0; const decoder = new TextDecoder(); for (const facet of facets) { // add text before this facet const beforeBytes = postBytes.slice(lastPos, facet.byteStart); result += decoder.decode(beforeBytes); // add the faceted text as a link const facetBytes = postBytes.slice(facet.byteStart, facet.byteEnd); const facetText = decoder.decode(facetBytes); switch (facet.type) { case 'link': result += `${facetText}`; break; case 'mention': result += `${facetText}`; break; case 'tag': result += `#${facetText}`; break; default: // in case Bluesky ever adds other fcaet types result += facetText; // if we don't recognize the type, just add it as plain text break; } lastPos = facet.byteEnd; } // add remaining text const remainingBytes = postBytes.slice(lastPos); result += decoder.decode(remainingBytes); console.log(result); return result; } async function main() { const app = document.querySelector('#app'); if (!app) { console.error('App container not found'); throw new Error('App container not found'); } app.innerHTML = '
'; try { const response = await fetch('http://localhost:8080/api/posts'); if (!response.ok) throw new Error(`HTTP ${response.status}`); const posts = await response.json(); app.innerHTML = ''; for (const post of posts) { let parsedText = ''; if(post.facets && post.facets.length > 0) { parsedText = renderTextWithFacets(post.text || '', post.facets || []); } else { parsedText = post.text; } const postText = marked.parseInline(parsedText, { breaks: true }); const postHTML = `
avatar
@${post.authorHandle}
${postText || '
'}
${new Date(post.createdAt).toLocaleString()}
`; app.innerHTML += postHTML; } } catch (err) { app.innerHTML = `
error loading posts: ${err}
`; } } main();