aboutsummaryrefslogtreecommitdiff
path: root/lib/serve.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/serve.js')
-rw-r--r--lib/serve.js198
1 files changed, 180 insertions, 18 deletions
diff --git a/lib/serve.js b/lib/serve.js
index 11e451a..5090f0e 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -102,6 +102,7 @@ Serve.prototype.go = function () {
gotData(err, data)
})
function addField(name, value) {
+ if (typeof value === 'string') value = value.replace(/\r\n/g, '\n')
if (!(name in data)) data[name] = value
else if (Array.isArray(data[name])) data[name].push(value)
else data[name] = [data[name], value]
@@ -135,7 +136,9 @@ Serve.prototype.go = function () {
pull.collect(function (err, bufs) {
var data
if (!err) try {
- data = qs.parse(Buffer.concat(bufs).toString('ascii'))
+ var str = Buffer.concat(bufs).toString('utf8')
+ str = str.replace(/%0D%0A/ig, '\n')
+ data = qs.parse(str)
} catch(e) {
err = e
}
@@ -177,6 +180,21 @@ Serve.prototype.go = function () {
}
}
+Serve.prototype.saveDraft = function (content, cb) {
+ var self = this
+ var data = self.data
+ var form = {}
+ for (var k in data) {
+ if (k === 'url' || k === 'draft_id' || k === 'content'
+ || k === 'draft_id' || k === 'save_draft') continue
+ form[k] = data[k]
+ }
+ self.app.saveDraft(data.draft_id, data.url, form, content, function (err, id) {
+ if (err) return cb(err)
+ cb(null, id || data.draft_id)
+ })
+}
+
Serve.prototype.publishJSON = function (cb) {
var content
try {
@@ -415,6 +433,7 @@ Serve.prototype.path = function (url) {
case '/web': return this.web(m[2])
case '/block': return this.block(m[2])
case '/script': return this.script(m[2])
+ case '/drafts': return this.drafts(m[2])
}
return this.respond(404, 'Not found')
}
@@ -1105,14 +1124,9 @@ Serve.prototype.aboutSelf = function (ext) {
ph('option', {value: 'null', selected: publicWebHosting == null}, '…'),
])
]),
- ph('p', {class: 'msg-right'}, [
- ph('input', {type: 'submit', name: 'preview_raw', value: 'Raw'}), ' ',
- ph('input', {type: 'submit', name: 'preview', value: 'Preview'})
- ])
+ self.phMsgActions(content),
]),
- content ? [
- self.phPreview(content, {raw: data.preview_raw})
- ] : ''
+ content ? self.phPreview(content, {raw: data.preview_raw}) : ''
]),
self.wrapPage('about self: ' + id),
self.respondSink(200, {
@@ -1140,6 +1154,14 @@ Serve.prototype.block = function (path) {
if (reason) content.reason = reason
}
+ function renderDraftLink(draftId) {
+ return pull.values([
+ ph('a', {href: self.app.render.toUrl('/drafts/' + encodeURIComponent(draftId)),
+ title: 'draft link'}, u.escapeHTML(draftId)),
+ ph('input', {type: 'hidden', name: 'draft_id', value: u.escapeHTML(draftId)}), ' ',
+ ])
+ }
+
pull(
ph('section', [
ph('h2', ['Block ', self.phIdLink(id)]),
@@ -1147,14 +1169,9 @@ Serve.prototype.block = function (path) {
'Reason: ', ph('input', {name: 'reason', value: reason || '',
style: 'width: 100%',
placeholder: 'spam, abuse, etc.'}),
- ph('p', {class: 'msg-right'}, [
- ph('input', {type: 'submit', name: 'preview_raw', value: 'Raw'}), ' ',
- ph('input', {type: 'submit', name: 'preview', value: 'Preview'})
- ])
+ self.phMsgActions(content),
]),
- content ? [
- self.phPreview(content, {raw: data.preview_raw})
- ] : ''
+ content ? self.phPreview(content, {raw: data.preview_raw}) : ''
]),
self.wrapPage('Block ' + id),
self.respondSink(200)
@@ -3569,12 +3586,23 @@ Serve.prototype.composer = function (opts, cb) {
data.recps = recpsToFeedIds(data.recps)
}
+ var draftLinkContainer
+ function renderDraftLink(draftId) {
+ if (!draftId) return []
+ var draftHref = self.app.render.toUrl('/drafts/' + encodeURIComponent(draftId))
+ return [
+ h('a', {href: draftHref, title: 'draft link'}, u.escapeHTML(draftId)),
+ h('input', {type: 'hidden', name: 'draft_id', value: u.escapeHTML(draftId)})
+ ]
+ }
+
var done = multicb({pluck: 1, spread: true})
done()(null, h('section.composer',
h('form', {method: 'post', action: opts.id ? '#' + opts.id : '',
enctype: 'multipart/form-data'},
h('input', {type: 'hidden', name: 'blobs',
value: JSON.stringify(blobs)}),
+ h('input', {type: 'hidden', name: 'url', value: self.req.url}),
h('input', {type: 'hidden', name: 'composer_id', value: opts.id}),
opts.recps ? self.app.render.privateLine(opts.recps, true, done()) :
opts.private ? h('div', h('input.recps-input', {name: 'recps',
@@ -3639,6 +3667,11 @@ Serve.prototype.composer = function (opts, cb) {
h('input', {type: 'file', name: 'upload'})
),
h('td.msg-right',
+ draftLinkContainer = h('span', renderDraftLink(data.draft_id)), ' ',
+ h('label', {for: 'save_draft'},
+ h('input', {type: 'checkbox', id: 'save_draft', name: 'save_draft', value: '1',
+ checked: data.save_draft || data.restored_draft ? 'checked' : undefined}),
+ ' save draft '),
h('input', {type: 'submit', name: 'action', value: 'raw'}), ' ',
h('input', {type: 'submit', name: 'action', value: 'preview'})
)
@@ -3674,7 +3707,7 @@ Serve.prototype.composer = function (opts, cb) {
var done = multicb({pluck: 1})
content = {
type: 'post',
- text: String(data.text).replace(/\r\n/g, '\n'),
+ text: String(data.text),
}
if (opts.lineComment) {
content.type = 'line-comment'
@@ -3759,8 +3792,20 @@ Serve.prototype.composer = function (opts, cb) {
if (content) gotContent(null, content)
else prepareContent(gotContent)
- function gotContent(err, content) {
+ function gotContent(err, _content) {
if (err) return cb(err)
+ content = _content
+ if (data.save_draft) self.saveDraft(content, saved)
+ else saved()
+ }
+
+ function saved(err, draftId) {
+ if (err) return cb(err)
+
+ if (draftId) {
+ draftLinkContainer.childNodes = renderDraftLink(draftId)
+ }
+
contentInput.value = JSON.stringify(content)
var msg = {
value: {
@@ -3825,6 +3870,7 @@ Serve.prototype.composer = function (opts, cb) {
}
Serve.prototype.phPreview = function (content, opts) {
+ var self = this
var msg = {
value: {
author: this.app.sbot.id,
@@ -3839,7 +3885,6 @@ Serve.prototype.phPreview = function (content, opts) {
if (estSize > 8192) warnings.push(ph('li', 'message is too long'))
return ph('form', {action: '', method: 'post'}, [
- ph('input', {type: 'hidden', name: 'content', value: u.escapeHTML(JSON.stringify(content))}),
ph('input', {type: 'hidden', name: 'redirect_to_published_msg', value: '1'}),
warnings.length ? [
ph('div', ph('em', 'warning:')),
@@ -3858,12 +3903,45 @@ Serve.prototype.phPreview = function (content, opts) {
}),
pull.map(u.toHTML)
)),
+ ph('input', {type: 'hidden', name: 'content', value: u.escapeHTML(JSON.stringify(content))}),
ph('div', {class: 'composer-actions'}, [
ph('input', {type: 'submit', name: 'action', value: 'publish'})
])
])
}
+Serve.prototype.phMsgActions = function (content) {
+ var self = this
+ var data = self.data
+
+ function renderDraftLink(draftId) {
+ return pull.values([
+ ph('a', {href: self.app.render.toUrl('/drafts/' + encodeURIComponent(draftId)),
+ title: 'draft link'}, u.escapeHTML(draftId)),
+ ph('input', {type: 'hidden', name: 'draft_id', value: u.escapeHTML(draftId)}), ' ',
+ ])
+ }
+
+ return [
+ ph('input', {type: 'hidden', name: 'url', value: self.req.url}),
+ ph('p', {class: 'msg-right'}, [
+ data.save_draft && content ? u.readNext(function (cb) {
+ self.saveDraft(content, function (err, draftId) {
+ if (err) return cb(err)
+ cb(null, renderDraftLink(draftId))
+ })
+ }) : data.draft_id ? renderDraftLink(data.draft_id) : '',
+ ph('label', {for: 'save_draft'}, [
+ ph('input', {type: 'checkbox', id: 'save_draft', name: 'save_draft', value: '1',
+ checked: data.save_draft || data.restored_draft ? 'checked' : undefined}),
+ ' save draft '
+ ]),
+ ph('input', {type: 'submit', name: 'preview_raw', value: 'Raw'}), ' ',
+ ph('input', {type: 'submit', name: 'preview', value: 'Preview'}),
+ ])
+ ]
+}
+
function hashBuf(buf) {
var hash = crypto.createHash('sha256')
hash.update(buf)
@@ -4146,3 +4224,87 @@ Serve.prototype.pub = function (path) {
self.respondSink(200)
)
}
+
+function hiddenInput(key, value) {
+ return Array.isArray(value) ? value.map(function (value) {
+ return ph('input', {type: 'hidden', name: key, value: u.escapeHTML(value)})
+ }) : ph('input', {type: 'hidden', name: key, value: u.escapeHTML(value)})
+}
+
+Serve.prototype.drafts = function (path) {
+ var self = this
+ var id = path && String(path).substr(1)
+ if (id) try { id = decodeURIComponent(id) }
+ catch(e) {}
+
+ if (id) {
+ return pull(
+ ph('section', [
+ ph('h3', [
+ ph('a', {href: self.app.render.toUrl('/drafts')}, 'Drafts'), ': ',
+ ph('a', {href: ''}, u.escapeHTML(id))
+ ]),
+ u.readNext(function (cb) {
+ if (self.data.draft_discard) {
+ return self.app.discardDraft(id, function (err) {
+ if (err) return cb(err)
+ cb(null, ph('div', 'Discarded'))
+ })
+ }
+ self.app.getDraft(id, function (err, draft) {
+ if (err) return cb(err)
+ var form = draft.form || {}
+ var content = draft.content || {type: 'post', text: ''}
+ var composerUrl = self.app.render.toUrl(draft.url)
+ + (form.composer_id ? '#' + encodeURIComponent(form.composer_id) : '')
+ cb(null, ph('div', [
+ ph('form', {method: 'post', action: composerUrl}, [
+ hiddenInput('draft_id', id),
+ hiddenInput('restored_draft', '1'),
+ Object.keys(form).map(function (key) {
+ if (key === 'draft_id' || key === 'save_draft') return ''
+ return hiddenInput(key, draft.form[key])
+ }),
+ ph('p', [
+ ph('input', {type: 'submit', name: 'draft_edit', value: 'Edit'})
+ ])
+ ]),
+ ph('form', {method: 'post', action: ''}, [
+ ph('p', [
+ ph('input', {type: 'submit', name: 'draft_discard', value: 'Discard',
+ title: 'Discard draft'})
+ ])
+ ]),
+ self.phPreview(content, {draftId: id})
+ ]))
+ })
+ })
+ ]),
+ self.wrapPage('draft: ' + id),
+ self.respondSink(200)
+ )
+ }
+
+ return pull(
+ ph('section', [
+ ph('h3', 'Drafts'),
+ ph('ul', pull(
+ self.app.listDrafts(),
+ pull.asyncMap(function (draft, cb) {
+ var form = draft.form || {}
+ var msg = {
+ key: '/drafts/' + draft.id,
+ value: {
+ author: self.app.sbot.id,
+ timestamp: Date.now(),
+ content: draft.content || {type: 'post'}
+ }
+ }
+ cb(null, ph('li', self.app.render.phMsgLink(msg)))
+ })
+ ))
+ ]),
+ self.wrapPage('drafts'),
+ self.respondSink(200)
+ )
+}