Flag of Ukraine   We stand with the brave people of Ukraine. Stop the war. Find out how you can help.

Transloadit Markdown Snippets Example

This is a demo app that works a bit like Github Gists or Pastebin. You can add markdown snippets, and add file attachments to each snippet.

Uppy Remote Sources plugin (using Transloadit internally) generates an inline preview image for images, videos, and audio files.

You can view the Transloadit Assembly Template and the code for this demo.

⚠️ For this demo, snippets are stored locally in your browser. Attachments are stored in Transloadit’s temporary storage and expire after about 24 hours. In a real app, you can easily export files to a permanent storage solution, like Amazon S3 or Google Cloud.

Create a new snippet

Previous snippets


Console output:

On this page we're using the following HTML snippet:

<!-- Add Uppy styles. It is advisable to install Uppy from npm/yarn.
  But for experimenting, you can use also Transloadit’s CDN, Edgly:
  <link rel="stylesheet" href="https://releases.transloadit.com/uppy/v3.0.1/uppy.min.css"> -->
  <link rel="stylesheet" href="/uppy/uppy.min.css">
  <form id="new" class="form-snippet">
    <h2>Create a new snippet</h2>
    <label class="form-snippetTitle">
      <div class="form-snippetLabel">Snippet Title</div>
      <input class="form-snippetTitle-input" type="text" name="title" placeholder="Enter snippet title">
    </label>
    <label class="form-snippetText">
      <div class="form-snippetLabel">Snippet Content</div>
      <textarea class="form-textarea" name="snippet" placeholder="Enter snippet content"></textarea>
    </label>
    <button class="form-createSnippet" type="submit">
      Create
    </button>
  </form>

  <h2>Previous snippets</h2>

  <div id="snippets"></div>
</main>
<template id="snippet">
  <div class="snippet">
    <h3 class="snippet-title"></h3>
    <div class="snippet-content"></div>
  </div>
</template>

Along with this JavaScript:

/* eslint-env browser */
import marked from 'marked'
import dragdrop from 'drag-drop'
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Transloadit from '@uppy/transloadit'
import RemoteSources from '@uppy/remote-sources'
import Webcam from '@uppy/webcam'
import ImageEditor from '@uppy/image-editor'

const TRANSLOADIT_EXAMPLE_KEY = '35c1aed03f5011e982b6afe82599b6a0'
const TRANSLOADIT_EXAMPLE_TEMPLATE = '0b2ee2bc25dc43619700c2ce0a75164a'

function matchFilesAndThumbs (results) {
  const filesById = {}
  const thumbsById = {}

  for (const [stepName, result] of Object.entries(results)) {
    // eslint-disable-next-line no-shadow
    result.forEach(result => {
      if (stepName === 'thumbnails') {
        thumbsById[result.original_id] = result
      } else {
        filesById[result.original_id] = result
      }
    })
  }

  return Object.keys(filesById).map((key) => ({
    file: filesById[key],
    thumb: thumbsById[key],
  }))
}

/**
 * A textarea for markdown text, with support for file attachments.
 */
class MarkdownTextarea {
  constructor (element) {
    this.element = element
    this.controls = document.createElement('div')
    this.controls.classList.add('mdtxt-controls')
    this.uploadLine = document.createElement('button')
    this.uploadLine.setAttribute('type', 'button')
    this.uploadLine.classList.add('form-upload')

    this.uploadLine.appendChild(
      document.createTextNode('Tap here to upload an attachment'),
    )
  }

  install () {
    const { element } = this
    const wrapper = document.createElement('div')
    wrapper.classList.add('mdtxt')
    element.parentNode.replaceChild(wrapper, element)
    wrapper.appendChild(this.controls)
    wrapper.appendChild(element)
    wrapper.appendChild(this.uploadLine)

    this.setupTextareaDrop()
    this.setupUppy()
  }

  setupUppy = () => {
    this.uppy = new Uppy({ autoProceed: true })
      .use(Transloadit, {
        waitForEncoding: true,
        params: {
          auth: { key: TRANSLOADIT_EXAMPLE_KEY },
          template_id: TRANSLOADIT_EXAMPLE_TEMPLATE,
        },
      })
      .use(Dashboard, { closeAfterFinish: true, trigger: '.form-upload' })
      .use(ImageEditor, { target: Dashboard })
      .use(Webcam, { target: Dashboard })
      .use(RemoteSources, {
        companionUrl: 'https://api2.transloadit.com/companion',
      })

    this.uppy.on('complete', (result) => {
      const { successful, failed, transloadit } = result
      if (successful.length !== 0) {
        this.insertAttachments(
          matchFilesAndThumbs(transloadit[0].results),
        )
      } else {
        failed.forEach(error => {
          console.error(error)
          this.reportUploadError(error)
        })
      }
      this.uppy.cancelAll()
    })
  }

  setupTextareaDrop () {
    dragdrop(this.element, (files) => {
      this.uploadFiles(files)
    })
  }

  reportUploadError (err) {
    this.uploadLine.classList.add('error')
    const message = document.createElement('span')
    message.appendChild(document.createTextNode(err.message))
    this.uploadLine.insertChild(message, this.uploadLine.firstChild)
  }

  unreportUploadError () {
    this.uploadLine.classList.remove('error')
    const message = this.uploadLine.querySelector('message')
    if (message) {
      this.uploadLine.removeChild(message)
    }
  }

  insertAttachments (attachments) {
    attachments.forEach((attachment) => {
      const { file, thumb } = attachment
      const link = `\n[LABEL](${file.ssl_url})\n`
      const labelText = `View File ${file.basename}`
      if (thumb) {
        this.element.value += link.replace('LABEL', `![${labelText}](${thumb.ssl_url})`)
      } else {
        this.element.value += link.replace('LABEL', labelText)
      }
    })
  }

  uploadFiles = (files) => {
    const filesForUppy = files.map(file => {
      return {
        data: file,
        type: file.type,
        name: file.name,
        meta: file.meta || {},
      }
    })
    this.uppy.addFiles(filesForUppy)
  }
}

const textarea = new MarkdownTextarea(document.querySelector('#new textarea'))
textarea.install()

function renderSnippet (title, text) {
  const template = document.querySelector('#snippet')
  const newSnippet = document.importNode(template.content, true)
  const titleEl = newSnippet.querySelector('.snippet-title')
  const contentEl = newSnippet.querySelector('.snippet-content')

  titleEl.appendChild(document.createTextNode(title))
  contentEl.innerHTML = marked(text)

  const list = document.querySelector('#snippets')
  list.insertBefore(newSnippet, list.firstChild)
}

function saveSnippet (title, text) {
  const id = parseInt(localStorage.numSnippets || 0, 10)
  localStorage[`snippet_${id}`] = JSON.stringify({ title, text })
  localStorage.numSnippets = id + 1
}

function loadSnippets () {
  for (let id = 0; localStorage[`snippet_${id}`] != null; id += 1) {
    const { title, text } = JSON.parse(localStorage[`snippet_${id}`])
    renderSnippet(title, text)
  }
}

document.querySelector('#new').addEventListener('submit', (event) => {
  event.preventDefault()

  const title = event.target.elements['title'].value
    || 'Unnamed Snippet'
  const text = textarea.element.value

  saveSnippet(title, text)
  renderSnippet(title, text)

  // eslint-disable-next-line no-param-reassign
  event.target.querySelector('input').value = ''
  // eslint-disable-next-line no-param-reassign
  event.target.querySelector('textarea').value = ''
})

window.addEventListener('DOMContentLoaded', loadSnippets, { once: true })

Please see documentation for details.


Hey there stranger! Uppy is actively developed and the example section is our playground. Things might not work, but we're working hard to improve.

We're on a monthly release cycle and our latest version is v3.0.1, but the example pages reflect the latest work in our main branch. Here's what changed in main since v3.0.1 in terms of commits, while the CHANGELOG provides a higher level view of the things planned for our next release.

Files from the examples are uploaded to our test servers and deleted every 24-72 hours.