OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social / hyper-social-web / web / js / timeline.js

114 lines · javascript ref:
/**
 * Timeline rendering
 */

function renderPost (item) {
  const el = document.createElement('div')
  el.className = 'post' + (item.type === 'boost' ? ' post-type-boost' : '') + (item.type === 'reply' ? ' post-type-reply' : '')

  const platformLabel = {
    mastodon: 'Mastodon',
    atproto: 'Bluesky',
    instagram: 'Instagram',
    local: 'Local'
  }

  const time = new Date(item.timestamp)
  const timeStr = time.toLocaleDateString() + ' ' + time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })

  let boostInfo = ''
  if (item.type === 'boost' && item.metadata?.boostBy) {
    boostInfo = `<div style="font-size:12px;color:var(--text-muted);margin-bottom:4px">${item.metadata.boostBy} reposted</div>`
  }

  let mediaHtml = ''
  if (item.mediaUrl) {
    if (item.mediaType === 'image' || item.mediaType === 'gifv') {
      mediaHtml = `<div class="post-media"><img src="${escapeHtml(item.mediaUrl)}" loading="lazy" alt=""></div>`
    } else if (item.mediaType === 'video') {
      mediaHtml = `<div class="post-media"><a href="${escapeHtml(item.mediaUrl)}" target="_blank">[video]</a></div>`
    }
  }

  let metaHtml = ''
  if (item.metadata) {
    const parts = []
    if (item.metadata.likeCount) parts.push(item.metadata.likeCount + ' likes')
    if (item.metadata.repostCount) parts.push(item.metadata.repostCount + ' reposts')
    if (item.metadata.replyCount) parts.push(item.metadata.replyCount + ' replies')
    if (parts.length > 0) metaHtml = `<div class="post-meta">${parts.join(' · ')}</div>`
  }

  let syndicatedHtml = ''
  if (item.syndicated) {
    const platforms = Object.values(item.syndicated).map(s => s.platform).join(', ')
    syndicatedHtml = `<div class="post-syndicated">Published to: ${platforms}</div>`
  }

  let urlHtml = ''
  if (item.url) {
    urlHtml = `<div style="margin-top:4px"><a href="${escapeHtml(item.url)}" target="_blank" style="color:var(--accent);font-size:12px">View on ${platformLabel[item.source] || item.source}</a></div>`
  }

  const avatar = item.metadata?.avatar || ''
  const displayName = item.metadata?.displayName || ''
  const avatarHtml = avatar ? `<img class="post-avatar" src="${escapeHtml(avatar)}" alt="">` : `<div class="post-avatar post-avatar-placeholder"></div>`

  el.innerHTML = `
    ${boostInfo}
    <div class="post-header">
      ${avatarHtml}
      <div class="post-author-info">
        ${displayName ? `<span class="post-displayname">${escapeHtml(displayName)}</span>` : ''}
        <span class="post-author">${escapeHtml(item.author)}</span>
      </div>
      <span class="post-platform ${item.source}">${platformLabel[item.source] || item.source}</span>
      <span class="post-time">${timeStr}</span>
    </div>
    <div class="post-text">${escapeHtml(item.text)}</div>
    ${mediaHtml}
    ${metaHtml}
    ${syndicatedHtml}
    ${urlHtml}
  `
  return el
}

function renderNotification (notif) {
  const el = document.createElement('div')
  el.className = 'notification'

  const platformLabel = { mastodon: 'Mastodon', atproto: 'Bluesky' }
  const time = new Date(notif.timestamp)
  const timeStr = time.toLocaleDateString() + ' ' + time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })

  el.innerHTML = `
    <div class="post-header">
      <span class="notif-type">${escapeHtml(notif.type)}</span>
      <span class="post-platform ${notif.platform}">${platformLabel[notif.platform] || notif.platform}</span>
      <span class="post-time">${timeStr}</span>
    </div>
    <div><strong>${escapeHtml(notif.author)}</strong></div>
    ${notif.text ? `<div class="post-text">${escapeHtml(notif.text)}</div>` : ''}
  `
  return el
}

function renderAccountCard (account) {
  const el = document.createElement('div')
  el.className = 'account-card'
  el.innerHTML = `
    <div class="account-info">
      <span class="post-platform ${account.platform}">${account.platform}</span>
      <span>${escapeHtml(account.handle || account.instance || account.id)}</span>
    </div>
    <button class="btn btn-small btn-danger" onclick="disconnectAccount('${escapeHtml(account.id)}')">Disconnect</button>
  `
  return el
}

function escapeHtml (str) {
  if (!str) return ''
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}