aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/main.ts
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/main.ts')
-rw-r--r--frontend/src/main.ts145
1 files changed, 118 insertions, 27 deletions
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index d0cff46..306578d 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -1,6 +1,25 @@
import { marked } from 'marked';
import './style.css'
+interface Note {
+ uri: string;
+ authorDID: string;
+ authorHandle: string;
+ authorName: string;
+ authorNoUnauth: boolean;
+ avatarURL: string;
+ text: string;
+ createdAt: string;
+ replyTo?: string;
+ parents?: Note[];
+ facets?: Array<{
+ byteStart: number;
+ byteEnd: number;
+ type: string;
+ value: string;
+ }>;
+}
+
// 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 {
@@ -66,20 +85,98 @@ function convertFacets(text: string, facets: any[], toMarkown: boolean): string
return result;
}
-function renderAuthor(note: any): string {
+function createAuthorElement(note: Note): HTMLElement {
+ const container = document.createElement('div');
+
const hasDisplayName = note.authorName && note.authorName !== '';
const hasValidHandle = note.authorHandle !== 'handle.invalid';
- const pronouns = note.pronouns ? `<span class="muted> · ${note.pronouns}</span>` : '';
if (hasDisplayName && hasValidHandle) {
- return `<div><strong>${note.authorName}</strong>${pronouns}</div><div class="muted">@${note.authorHandle}</div>`;
+ const nameDiv = document.createElement('div');
+ nameDiv.innerHTML = `<strong>${note.authorName}</strong>`;
+ container.appendChild(nameDiv);
+
+ const handleDiv = document.createElement('div');
+ handleDiv.className = 'muted';
+ handleDiv.textContent = `@${note.authorHandle}`;
+ container.appendChild(handleDiv);
} else if (hasDisplayName && !hasValidHandle) {
- return `<div><strong>${note.authorName}</strong>${pronouns}</div><div class="muted">⚠️ InvalidHandle</div>`;
+ const nameDiv = document.createElement('div');
+ nameDiv.innerHTML = `<strong>${note.authorName}</strong>`;
+ container.appendChild(nameDiv);
+
+ const warningDiv = document.createElement('div');
+ warningDiv.className = 'muted';
+ warningDiv.textContent = '⚠️ InvalidHandle';
+ container.appendChild(warningDiv);
} else if (!hasDisplayName && hasValidHandle) {
- return `<div><strong>@${note.authorHandle}</strong>${pronouns}</div>`;
+ const handleDiv = document.createElement('div');
+ handleDiv.innerHTML = `<strong>@${note.authorHandle}</strong>`;
+ container.appendChild(handleDiv);
+ } else {
+ const warningDiv = document.createElement('div');
+ warningDiv.innerHTML = '<strong>⚠️ InvalidHandle</strong>';
+ container.appendChild(warningDiv);
+ }
+
+ return container;
+}
+
+function createNoteElement(note: Note): HTMLElement {
+ const noteDiv = document.createElement('div');
+ noteDiv.className = 'note';
+
+ const avatar = document.createElement('img');
+ avatar.className = 'note-avatar';
+ avatar.src = note.avatarURL || '';
+ avatar.alt = 'avatar';
+ noteDiv.appendChild(avatar);
+
+ const content = document.createElement('div');
+ content.className = 'note-content';
+
+ content.appendChild(createAuthorElement(note));
+
+ const textDiv = document.createElement('div');
+ textDiv.className = 'note-text';
+ const markdownEnabled = true; // TODO: create a toggleable setting
+ let parsedText = '';
+ if (note.facets && note.facets.length > 0) {
+ parsedText = convertFacets(note.text || '', note.facets || [], markdownEnabled);
+ } else {
+ parsedText = note.text;
+ }
+
+ if (markdownEnabled) {
+ textDiv.innerHTML = marked.parseInline(parsedText, { breaks: true }) as string;
} else {
- return `<div><strong>⚠️ InvalidHandle</strong>${pronouns}</div>`;
+ textDiv.innerHTML = parsedText;
}
+ content.appendChild(textDiv);
+
+ const timeDiv = document.createElement('div');
+ timeDiv.className = 'muted';
+ timeDiv.textContent = new Date(note.createdAt).toLocaleString();
+ content.appendChild(timeDiv);
+
+ noteDiv.appendChild(content);
+
+ return noteDiv;
+}
+
+function createThreadElement(notes: Note[]): HTMLElement {
+ if (notes.length === 1) {
+ return createNoteElement(notes[0]);
+ }
+
+ const threadDiv = document.createElement('div');
+ threadDiv.className = 'reply-chain';
+
+ notes.forEach(note => {
+ threadDiv.appendChild(createNoteElement(note));
+ });
+
+ return threadDiv;
}
async function main() {
@@ -105,32 +202,26 @@ async function main() {
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
- const notes = await response.json();
+ const notes: Note[] = 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);
+ notes.forEach(note => {
+ if (!note.parents || note.parents.length === 0) {
+ app.appendChild(createNoteElement(note));
} else {
- parsedText = note.text;
+ // parents were fetched bottom to top, reverse + add child note
+ const thread: Note[] = [note, ...note.parents].reverse();
+
+ app.appendChild(createThreadElement(thread));
}
- const noteText = marked.parseInline(parsedText, { breaks: true });
-
- const postHTML = `
- <div class="note">
- <img class="note-avatar" src="${note.avatarURL}" alt="avatar">
- <div class="note-content">
- ${renderAuthor(note)}
- <div class="note-text">${noteText}</div>
- <div class="muted">${new Date(note.createdAt).toLocaleString()}</div>
- </div>
- </div>
- `;
- app.innerHTML += postHTML;
- }
+ });
} catch (err) {
- app.innerHTML = `<div style="color: #f88">error loading posts: ${err}</div>`;
+ const errorDiv = document.createElement('div');
+ errorDiv.style.color = '#f88';
+ errorDiv.textContent = `error loading posts: ${err}`;
+ app.innerHTML = '';
+ app.appendChild(errorDiv);
}
}