Hosted onnoosphere.hyper.mediavia theHypermedia Protocol

Image Handling in Web Comment Editorhttps://linear.app/seedhypermedia/issue/SHM-1622/i-cant-paste-images-on-web-mobile-comment-box

Problem

When users insert images into the web comment editor, we currently use data URLs (base64 encoding) to store images in drafts. This allows unauthenticated users to create drafts with images that persist across sessions without uploading to IPFS until the comment is published. However, the current data URL approach has significant UX issues:

Browser Freezing: Base64 encoding blocks the main thread, causing the editor to freeze, especially with larger images (>1MB)

Performance: Data URLs are 33% larger than the original files, slowing down draft serialization and deserialization

Poor Mobile Experience: The encoding delay is even more noticeable on mobile devices

Why We Can't Just Upload Immediately

  • Unauthenticated Access: Anyone can create comment drafts without an account

  • Orphaned Uploads: If we upload images immediately, users who add images but never publish would create orphaned IPFS data

  • Resource Abuse: Malicious users could paste many images and waste daemon resources

Solution

I asked AI as suggested and this is what it came up with:

Option 1: Blob URLs ❌ NOT GOOD for your use case

const displaySrc = URL.createObjectURL(img) // "blob:http://..."

Problems:

  • Blob URLs are temporary - they die when the page reloads

  • Can't be serialized to localStorage/IndexedDB

  • Lost when user closes the tab

  • Your cached drafts would lose images!

Verdict: Won't work for persistent drafts.

Option 2: IndexedDB with Blob Storage ✅ BEST for your use case

Store the actual File/Blob in IndexedDB, not the base64:

// Store original file in IndexedDB
const imageId = `draft-img-${Date.now()}`
await indexedDB.put('draft-images', img, imageId)

// Create blob URL for display (fast!)
const displaySrc = URL.createObjectURL(img)

node.create({
  displaySrc: displaySrc,     // Temporary display (fast)
  imageId: imageId,           // Reference to IndexedDB
  name: name,
})

// On mount/reload: recreate blob URL from IndexedDB
const storedBlob = await indexedDB.get('draft-images', imageId)
if (storedBlob) {
  const displaySrc = URL.createObjectURL(storedBlob)
  // Update block with new blob URL
}

Pros:

  • Fast - no base64 encoding

  • Persistent - survives page reload

  • Secure - stays in user's browser, no server upload

  • Efficient - stores binary, not base64

  • Cleanup - can delete IndexedDB entries when draft is published/discarded

Cons:

  • More code complexity

  • Need to handle IndexedDB lifecycle

Option 3: Data URLs (Current approach) ⚠️ Works but slow

const dataUrl = await readAsDataURL(img) // "data:image/jpeg;base64,..."

Pros:

  • Serializable (can store in localStorage)

  • Persistent

  • Simple

Cons:

  • Very slow - base64 encoding blocks UI

  • 33% larger than original file

  • Freezes browser for large images

Option 4: Upload Immediately ⚠️ Orphan problem

Cons:

  • Creates orphaned IPFS uploads

  • Wastes daemon resources

  • Security issue - unauthenticated users can fill your IPFS

My Recommendation: IndexedDB (Option 2)

Here's why:

  1. Solves the freezing problem - no base64 encoding

  2. Keeps drafts secure - client-side only until published

  3. Persists across sessions - user can reload and continue

  4. Clean architecture - natural place to store draft data

  5. Easy cleanup - delete from IndexedDB when draft published/discarded

Do you like what you are reading?. Subscribe to receive updates.

Unsubscribe anytime