OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / hyper-social / hyper-social-web / web / js / composer.js

134 lines · javascript ref:
/**
 * POSSE Composer — text + media upload
 */

let selectedFile = null

function initComposer () {
  const textarea = document.getElementById('compose-text')
  const charCount = document.getElementById('char-count')
  const publishBtn = document.getElementById('publish-btn')
  const resultEl = document.getElementById('publish-result')
  const mediaInput = document.getElementById('media-input')
  const attachBtn = document.getElementById('attach-btn')
  const mediaPreview = document.getElementById('media-preview')
  const previewImg = document.getElementById('preview-img')
  const previewName = document.getElementById('preview-name')
  const removeMedia = document.getElementById('remove-media')
  const altText = document.getElementById('alt-text')

  textarea.addEventListener('input', () => {
    charCount.textContent = textarea.value.length
  })

  // Attach media
  attachBtn.addEventListener('click', () => mediaInput.click())

  mediaInput.addEventListener('change', () => {
    const file = mediaInput.files[0]
    if (!file) return

    selectedFile = file
    previewName.textContent = file.name + ' (' + formatSize(file.size) + ')'
    altText.style.display = 'block'

    if (file.type.startsWith('image/')) {
      const url = URL.createObjectURL(file)
      previewImg.src = url
      previewImg.style.display = 'block'
    } else {
      previewImg.style.display = 'none'
    }

    mediaPreview.style.display = 'flex'
    attachBtn.style.display = 'none'
  })

  removeMedia.addEventListener('click', () => {
    selectedFile = null
    mediaInput.value = ''
    mediaPreview.style.display = 'none'
    attachBtn.style.display = 'inline-block'
    altText.style.display = 'none'
    altText.value = ''
    previewImg.src = ''
  })

  // Publish
  publishBtn.addEventListener('click', async () => {
    const text = textarea.value.trim()
    if (!text) return

    const targets = []
    document.querySelectorAll('input[name="target"]:checked').forEach(cb => {
      targets.push(cb.value)
    })

    if (targets.length === 0) {
      showResult(resultEl, 'Select at least one platform', 'error')
      return
    }

    publishBtn.disabled = true
    publishBtn.textContent = selectedFile ? 'Processing & publishing...' : 'Publishing...'

    try {
      let data

      if (selectedFile) {
        // Multipart upload with media
        const form = new FormData()
        form.append('text', text)
        form.append('targets', JSON.stringify(targets))
        form.append('altText', altText.value || '')
        form.append('file', selectedFile)

        const res = await fetch(API.base + '/api/post-media', {
          method: 'POST',
          body: form
        })
        data = await res.json()
      } else {
        // Text-only
        data = await API.post(text, targets)
      }

      if (data.error) {
        showResult(resultEl, data.error, 'error')
      } else if (data.errors && Object.keys(data.errors).length > 0) {
        const errMsg = Object.entries(data.errors).map(([k, v]) => `${k}: ${v}`).join(', ')
        showResult(resultEl, 'Partial: ' + errMsg, 'error')
      } else {
        showResult(resultEl, 'Published to ' + targets.join(', '), 'success')
        textarea.value = ''
        charCount.textContent = '0'
        // Clear media
        selectedFile = null
        mediaInput.value = ''
        mediaPreview.style.display = 'none'
        attachBtn.style.display = 'inline-block'
        altText.style.display = 'none'
        altText.value = ''
      }
    } catch (err) {
      showResult(resultEl, err.message, 'error')
    }

    publishBtn.disabled = false
    publishBtn.textContent = 'Publish'
  })
}

function formatSize (bytes) {
  if (bytes < 1024) return bytes + ' B'
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
  return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
}

function showResult (el, msg, type) {
  el.style.display = 'block'
  el.className = 'result ' + type
  el.textContent = msg
  setTimeout(() => { el.style.display = 'none' }, 5000)
}