import { marked } from 'marked';
import './style.css'
// convert Facets to Markdown (before Markdown parsing) if Markdown passing is enabled
// convert them to HTML code otherwise
function convertFacets(text: string, facets: any[], toMarkown: boolean): string {
if (!facets || facets.length === 0) {
return text;
}
const encoder = new TextEncoder();
const postBytes = encoder.encode(text);
facets.sort((a, b) => a.byteStart - b.byteStart);
let result = '';
let lastPos = 0;
const decoder = new TextDecoder();
for (const facet of facets) {
const beforeBytes = postBytes.slice(lastPos, facet.byteStart);
result += decoder.decode(beforeBytes);
const facetBytes = postBytes.slice(facet.byteStart, facet.byteEnd);
const facetText = decoder.decode(facetBytes);
if (toMarkown) {
switch (facet.type) {
case 'link':
result += `[${facetText}](${facet.value})`;
break;
case 'mention':
result += `[@${facetText}](https://bsky.app/profile/${facet.value})`;
break;
case 'tag':
result += `[#${facetText}](https://bsky.app/hashtag/${facet.value})`;
break;
default: // in case Bluesky ever adds other facet types
result += facetText; // if we don't recognize the type, just add it as plain text
break;
}
} else {
switch (facet.type) {
case 'link':
result += `${facetText}`;
break;
case 'mention':
result += `${facetText}`;
break;
case 'tag':
result += `#${facetText}`;
break;
default:
result += facetText;
break;
}
}
lastPos = facet.byteEnd;
}
// add remaining text
const remainingBytes = postBytes.slice(lastPos);
result += decoder.decode(remainingBytes);
return result;
}
function renderAuthor(note: any): string {
const hasDisplayName = note.authorName && note.authorName !== '';
const hasValidHandle = note.authorHandle !== 'handle.invalid';
const pronouns = note.pronouns ? `@${note.authorHandle}`;
} else if (hasDisplayName && !hasValidHandle) {
return `
${note.authorName}${pronouns}
⚠️ InvalidHandle
`;
} else if (!hasDisplayName && hasValidHandle) {
return `
@${note.authorHandle}${pronouns}
`;
} else {
return `
⚠️ InvalidHandle${pronouns}
`;
}
}
async function main() {
const sessionCheck = await fetch('/api/session', {
credentials: 'include'
});
if (!sessionCheck.ok) {
window.location.href = '/login.html';
throw new Error('not authenticated');
}
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('/api/posts', {
credentials: 'include'
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const notes = await response.json();
app.innerHTML = '';
for (const note of notes) {
let parsedText = '';
if(note.facets && note.facets.length > 0) {
parsedText = convertFacets(note.text || '', note.facets || [], true);
} else {
parsedText = note.text;
}
const noteText = marked.parseInline(parsedText, { breaks: true });
const postHTML = `