diff options
Diffstat (limited to 'frontend/src/main.ts')
| -rw-r--r-- | frontend/src/main.ts | 145 |
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); } } |
