diff options
-rw-r--r-- | lib/app.js | 15 | ||||
-rw-r--r-- | lib/serve.js | 95 | ||||
-rw-r--r-- | lib/util.js | 17 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | static/styles.css | 8 |
5 files changed, 118 insertions, 19 deletions
@@ -5,6 +5,8 @@ var pkg = require('../package') var u = require('./util') var pull = require('pull-stream') var ssbAvatar = require('ssb-avatar') +var hasher = require('pull-hash/ext/ssb') +var multicb = require('multicb') var Serve = require('./serve') var Render = require('./render') @@ -94,6 +96,19 @@ App.prototype.publish = function (content, cb) { } } +App.prototype.addBlob = function (cb) { + var done = multicb({pluck: 1, spread: true}) + var hashCb = done() + var addCb = done() + done(function (err, hash, add) { + cb(err, hash) + }) + return pull( + hasher(hashCb), + this.sbot.blobs.add(addCb) + ) +} + function getMsgWithValue(sbot, id, cb) { sbot.get(id, function (err, value) { if (err) return cb(err) diff --git a/lib/serve.js b/lib/serve.js index 5836965..0a81fa1 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -14,6 +14,7 @@ var paginate = require('pull-paginate') var ssbMentions = require('ssb-mentions') var multicb = require('multicb') var pkg = require('../package') +var Busboy = require('busboy') module.exports = Serve @@ -53,18 +54,47 @@ Serve.prototype.go = function () { var self = this if (this.req.method === 'POST' || this.req.method === 'PUT') { - pull( - toPull(this.req), - pull.collect(function (err, bufs) { - var data - if (!err) try { - data = qs.parse(Buffer.concat(bufs).toString('ascii')) - } catch(e) { - err = e - } + if (/^multipart\/form-data/.test(this.req.headers['content-type'])) { + var data = {} + var erred + var busboy = new Busboy({headers: this.req.headers}) + var filesCb = multicb({pluck: 1}) + busboy.on('finish', filesCb()) + filesCb(function (err) { gotData(err, data) }) - ) + busboy.on('file', function (fieldname, file, filename, encoding, mimetype) { + var done = multicb({pluck: 1, spread: true}) + var cb = filesCb() + pull( + toPull(file), + u.pullLength(done()), + self.app.addBlob(done()) + ) + done(function (err, size, id) { + if (err) return cb(err) + data[fieldname] = {link: id, name: filename, type: mimetype, size: size} + cb() + }) + }) + busboy.on('field', function (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) { + data[fieldname] = val + }) + this.req.pipe(busboy) + } else { + pull( + toPull(this.req), + pull.collect(function (err, bufs) { + var data + if (!err) try { + data = qs.parse(Buffer.concat(bufs).toString('ascii')) + } catch(e) { + err = e + } + gotData(err, data) + }) + ) + } } else { gotData(null, {}) } @@ -768,9 +798,20 @@ Serve.prototype.composer = function (opts, cb) { opts = opts || {} var data = self.data + var blobs = u.tryDecodeJSON(data.blobs) || {} + if (data.upload && typeof data.upload === 'object') { + blobs[data.upload.link] = { + type: data.upload.type, + size: data.upload.size, + } + } + var done = multicb({pluck: 1, spread: true}) done()(null, h('section.composer', - h('form', {method: 'post', action: opts.id ? '#' + opts.id : ''}, + h('form', {method: 'post', action: opts.id ? '#' + opts.id : '', + enctype: 'multipart/form-data'}, + h('input', {type: 'hidden', name: 'blobs', + value: JSON.stringify(blobs)}), opts.recps ? self.app.render.privateLine(opts.recps, done()) : opts.private ? h('div', h('input.recps-input', {name: 'recps', value: data.recps || '', placeholder: 'recipient ids'})) : '', @@ -784,9 +825,22 @@ Serve.prototype.composer = function (opts, cb) { cols: 70, placeholder: opts.placeholder || 'public message', }, data.text || ''), - h('div.composer-actions', - h('input', {type: 'submit', name: 'action', value: 'raw'}), ' ', - h('input', {type: 'submit', name: 'action', value: 'preview'})), + h('table.ssb-msgs', + h('tr.msg-row', + h('td.msg-left', {colspan: 2}, + h('input', {type: 'file', name: 'upload'}), ' ', + h('input', {type: 'submit', name: 'action', value: 'attach'}) + ), + h('td.msg-right', + h('input', {type: 'submit', name: 'action', value: 'raw'}), ' ', + h('input', {type: 'submit', name: 'action', value: 'preview'}) + ) + ) + ), + data.upload ? [ + h('div', h('em', 'attach:')), + h('pre', '[' + data.upload.name + '](' + data.upload.link + ')') + ] : '', data.action === 'preview' ? preview(false, done()) : data.action === 'raw' ? preview(true, done()) : data.action === 'publish' ? publish(done()) : '' @@ -806,7 +860,18 @@ Serve.prototype.composer = function (opts, cb) { text: data.text, } var mentions = ssbMentions(data.text) - if (mentions.length) content.mentions = mentions + if (mentions.length) { + content.mentions = mentions.map(function (mention) { + var blob = blobs[mention.link] + if (blob) { + if (!isNaN(blob.size)) + mention.size = blob.size + if (blob.type && blob.type !== 'application/octet-stream') + mention.type = blob.type + } + return mention + }) + } if (data.recps != null) { if (opts.recps) return cb(new Error('got recps in opts and data')) content.recps = [myId] diff --git a/lib/util.js b/lib/util.js index d6c3133..fe7a335 100644 --- a/lib/util.js +++ b/lib/util.js @@ -78,3 +78,20 @@ u.renderError = function(err) { h('h3', err.name), h('pre', err.stack)) } + +u.pullLength = function (cb) { + var len = 0 + return pull.through(function (data) { + len += data.length + }, function (err) { + cb(err, len) + }) +} + +u.tryDecodeJSON = function (json) { + try { + return JSON.parse(json) + } catch(e) { + return null + } +} diff --git a/package.json b/package.json index 8b769eb..4d47f7f 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "plain ssb web ui", "dependencies": { "asyncmemo": "^1.0.0", + "busboy": "^0.2.14", "emoji-named-characters": "^1.0.2", "emoji-server": "^1.0.0", "human-time": "^0.0.1", @@ -11,6 +12,7 @@ "lrucache": "^1.0.2", "multicb": "^1.2.1", "pull-cat": "^1.1.11", + "pull-hash": "^1.0.0", "pull-paginate": "^1.0.0", "pull-paramap": "^1.2.1", "pull-stream": "^3.5.0", diff --git a/static/styles.css b/static/styles.css index 3413be8..d27ca0a 100644 --- a/static/styles.css +++ b/static/styles.css @@ -8,10 +8,6 @@ section { padding: 1ex; } -.composer-actions { - text-align: right; -} - .ssb-post img { max-width: 100%; width: 640px; @@ -120,6 +116,10 @@ td { width: 100%; } +.msg-right { + text-align: right; +} + .feed-name { padding: .75ex; } |