blob: f53632c3568d643959f5ba2bf80725bf73785eef (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
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 += `<a href="${facet.value}" target="_blank">${facetText}</a>`;
break;
case 'mention':
result += `<a href="https://bsky.app/profile/${facet.value}" target="_blank">${facetText}</a>`;
break;
case 'tag':
result += `<a href="https://bsky.app/hashtag/${facet.value}" target="_blank">#${facetText}</a>`;
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 = '<div class="spinner"></div>';
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 = `
<div class="note">
<img class="note-avatar" src="https://cdn.bsky.app/img/avatar/plain/did:plc:hyyqbqqhpxt2bnn7s2e2iuxd/bafkreidoiynom7v2y5rpsm467l3d2cnr7ozvlfusrchjey3k2jtyquufq4@jpeg" alt="avatar">
<div class="note-content">
<div style="font-weight: bold">@${post.authorHandle}</div>
<div class="note-text">${postText || '<br>'}</div>
<div style="font-size: 12px; color: #888">${new Date(post.createdAt).toLocaleString()}</div>
</div>
</div>
`;
app.innerHTML += postHTML;
}
} catch (err) {
app.innerHTML = `<div style="color: #f88">error loading posts: ${err}</div>`;
}
}
main();
|