aboutsummaryrefslogtreecommitdiff
path: root/lib/serve.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/serve.js')
-rw-r--r--lib/serve.js739
1 files changed, 630 insertions, 109 deletions
diff --git a/lib/serve.js b/lib/serve.js
index 766b524..7e2fe3b 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -21,6 +21,10 @@ var htime = require('human-time')
var ph = require('pull-hyperscript')
var emojis = require('emoji-named-characters')
var jpeg = require('jpeg-autorotate')
+var Catch = require('pull-catch')
+var Diff = require('diff')
+var split = require('pull-split')
+var utf8 = require('pull-utf8-decoder')
module.exports = Serve
@@ -76,7 +80,7 @@ Serve.prototype.go = function () {
if (auth) {
var a = auth.split(' ')
if (a[0] == 'Basic') {
- tok = Buffer.from(a[1],'base64').toString('ascii')
+ tok = Buffer.from(a[1],'base64').toString('ascii')
}
}
if (tok != authtok) {
@@ -196,7 +200,7 @@ Serve.prototype.publishVote = function (next) {
}
}
-Serve.prototype.publishContact = function (cb) {
+Serve.prototype.publishContact = function (next) {
var content = {
type: 'contact',
contact: this.data.contact,
@@ -205,7 +209,14 @@ Serve.prototype.publishContact = function (cb) {
if (this.data.block) content.blocking = true
if (this.data.unfollow) content.following = false
if (this.data.unblock) content.blocking = false
- this.publish(content, cb)
+ if (this.app.previewContacts) {
+ var json = JSON.stringify(content, 0, 2)
+ var q = qs.stringify({text: json, action: 'preview'})
+ var url = this.app.render.toUrl('/compose?' + q)
+ this.redirect(url)
+ } else {
+ this.publish(content, next)
+ }
}
Serve.prototype.publishAttend = function (cb) {
@@ -335,6 +346,7 @@ Serve.prototype.path = function (url) {
case '/npm-prebuilds': return this.npmPrebuilds(m[2])
case '/npm-readme': return this.npmReadme(m[2])
case '/markdown': return this.markdown(m[2])
+ case '/zip': return this.zip(m[2])
}
return this.respond(404, 'Not found')
}
@@ -529,7 +541,7 @@ Serve.prototype.advsearch = function (ext) {
ph('td', ['#', ph('input', {name: 'channel', placeholder: 'channel',
class: 'id-input',
value: q.channel || ''})
- ])
+ ])
]),
ph('tr', [
ph('td', {colspan: 2}, [
@@ -542,8 +554,8 @@ Serve.prototype.advsearch = function (ext) {
hasQuery && pull(
self.app.advancedSearch(q),
self.renderThread({
- feed: q.source,
- }),
+ feed: q.source,
+ }),
self.wrapMessages()
)
]),
@@ -569,7 +581,7 @@ Serve.prototype.live = function (ext) {
self.app.sbot.createLogStream(opts),
self.app.render.renderFeeds({
withGt: true,
- filter: q.filter,
+ filter: q.filter,
}),
pull.map(u.toHTML)
)),
@@ -1005,57 +1017,71 @@ function threadHeads(msgs, rootId) {
}))
}
-Serve.prototype.id = function (id, path) {
+Serve.prototype.streamThreadWithComposer = function (opts) {
var self = this
- if (self.query.raw != null) return self.rawId(id)
-
- this.getMsgDecryptedMaybeOoo(id, function (err, rootMsg) {
- if (err && err.name === 'NotFoundError') err = null, rootMsg = {
- key: id, value: {content: false}}
- if (err) return self.respond(500, err.stack || err)
- var rootContent = rootMsg && rootMsg.value && rootMsg.value.content
- var recps = rootContent && rootContent.recps
- || (rootMsg.value.private
- ? [rootMsg.value.author, self.app.sbot.id].filter(uniques())
- : undefined)
- var threadRootId = rootContent && rootContent.root || id
- var channel
+ var id = opts.root
+ return ph('table', {class: 'ssb-msgs'}, u.readNext(next))
+ function next(cb) {
+ self.getMsgDecryptedMaybeOoo(id, function (err, rootMsg) {
+ if (err && err.name === 'NotFoundError') err = null, rootMsg = {
+ key: id, value: {content: false}}
+ if (err) return cb(new Error(err.stack))
+ if (!rootMsg) {
+ console.log('id', id, 'opts', opts)
+ }
+ var rootContent = rootMsg && rootMsg.value && rootMsg.value.content
+ var recps = rootContent && rootContent.recps
+ || (rootMsg.value.private
+ ? [rootMsg.value.author, self.app.sbot.id].filter(uniques())
+ : undefined)
+ var threadRootId = rootContent && rootContent.root || id
+ var channel = opts.channel
- pull(
- cat([pull.once(rootMsg), self.app.sbot.links({dest: id, values: true})]),
- pull.unique('key'),
- self.app.unboxMessages(),
- pull.through(function (msg) {
- var c = msg && msg.value.content
- if (!channel && c.channel) channel = c.channel
- }),
- pull.collect(function (err, links) {
- if (err) return gotLinks(err)
- if (!self.useOoo) return gotLinks(null, links)
- self.app.expandOoo({msgs: links, dest: id}, gotLinks)
- })
- )
- function gotLinks(err, links) {
- if (err) return self.respond(500, err.stack || err)
pull(
- pull.values(sort(links)),
- self.renderThread({
- msgId: id,
- }),
- self.wrapMessages(),
- self.wrapThread({
- recps: recps,
- root: threadRootId,
- post: id,
- branches: threadHeads(links, threadRootId),
- postBranches: threadRootId !== id && threadHeads(links, id),
- channel: channel,
+ cat([pull.once(rootMsg), self.app.sbot.links({dest: id, values: true})]),
+ pull.unique('key'),
+ self.app.unboxMessages(),
+ pull.through(function (msg) {
+ var c = msg && msg.value.content
+ if (!channel && c.channel) channel = c.channel
}),
- self.wrapPage(id),
- self.respondSink(200)
+ pull.collect(function (err, links) {
+ if (err) return gotLinks(err)
+ if (!self.useOoo) return gotLinks(null, links)
+ self.app.expandOoo({msgs: links, dest: id}, gotLinks)
+ })
)
- }
- })
+ function gotLinks(err, links) {
+ if (err) return cb(new Error(err.stack))
+ cb(null, pull(
+ pull.values(sort(links)),
+ self.renderThread({
+ msgId: id,
+ }),
+ self.wrapMessages(),
+ self.wrapThread({
+ recps: recps,
+ root: threadRootId,
+ post: id,
+ branches: threadHeads(links, threadRootId),
+ postBranches: threadRootId !== id && threadHeads(links, id),
+ placeholder: opts.placeholder,
+ channel: channel,
+ })
+ ))
+ }
+ })
+ }
+}
+
+Serve.prototype.id = function (id, path) {
+ var self = this
+ if (self.query.raw != null) return self.rawId(id)
+ pull(
+ self.streamThreadWithComposer({root: id}),
+ self.wrapPage(id),
+ self.respondSink(200)
+ )
}
Serve.prototype.userFeed = function (id, path) {
@@ -1210,15 +1236,20 @@ Serve.prototype.image = function (path) {
if (err) return heresTheData(err)
buffer = Buffer.concat(buffer)
- jpeg.rotate(buffer, {}, function (err, rotatedBuffer, orientation) {
- if (!err) buffer = rotatedBuffer
+ try {
+ jpeg.rotate(buffer, {}, function (err, rotatedBuffer, orientation) {
+ if (!err) buffer = rotatedBuffer
- heresTheData(null, buffer)
- pull(
- pull.once(buffer),
- self.respondSink()
- )
- })
+ heresTheData(null, buffer)
+ pull(
+ pull.once(buffer),
+ self.respondSink()
+ )
+ })
+ } catch (err) {
+ console.trace(err)
+ self.respond(500, err.message || err)
+ }
}
done(function (err, data, type) {
@@ -1260,6 +1291,7 @@ Serve.prototype.renderThread = function (opts) {
msgId: opts && opts.msgId,
filter: this.query.filter,
limit: Number(this.query.limit),
+ serve: this,
}),
pull.map(u.toHTML)
)
@@ -1286,7 +1318,7 @@ Serve.prototype.renderThreadPaginated = function (opts, feedId, q) {
function onFirst(msg, cb) {
var num = feedId ? msg.value.sequence :
opts.sortByTimestamp ? msg.value.timestamp :
- msg.timestamp || msg.ts
+ msg.timestamp || msg.ts
if (q.forwards) {
cb(null, links({
lt: num,
@@ -1314,7 +1346,7 @@ Serve.prototype.renderThreadPaginated = function (opts, feedId, q) {
function onLast(msg, cb) {
var num = feedId ? msg.value.sequence :
opts.sortByTimestamp ? msg.value.timestamp :
- msg.timestamp || msg.ts
+ msg.timestamp || msg.ts
if (q.forwards) {
cb(null, links({
lt: null,
@@ -1557,8 +1589,8 @@ Serve.prototype.friendInfo = function (id, myId) {
this.app.render.friendsList(),
pull.map(function (html) {
if (!first) {
- first = true
- return 'followed by your friends: ' + html
+ first = true
+ return 'followed by your friends: ' + html
}
return html
})
@@ -1610,15 +1642,15 @@ Serve.prototype.wrapUserFeed = function (isScrolled, id) {
)
]),
isScrolled || id === myId ? '' : [
- ph('tr', [
- ph('td'),
- ph('td', {class: 'follow-info'}, self.followInfo(id, myId))
- ]),
- ph('tr', [
- ph('td'),
- ph('td', self.friendInfo(id, myId))
- ])
- ]
+ ph('tr', [
+ ph('td'),
+ ph('td', {class: 'follow-info'}, self.followInfo(id, myId))
+ ]),
+ ph('tr', [
+ ph('td'),
+ ph('td', self.friendInfo(id, myId))
+ ])
+ ]
])),
thread
])
@@ -1633,6 +1665,8 @@ Serve.prototype.git = function (url) {
case 'tree': return this.gitTree(m[2])
case 'blob': return this.gitBlob(m[2])
case 'raw': return this.gitRaw(m[2])
+ case 'diff': return this.gitDiff(m[2])
+ case 'line-comment': return this.gitLineComment(m[2])
default: return this.respond(404, 'Not found')
}
}
@@ -1765,15 +1799,34 @@ Serve.prototype.gitCommit = function (rev) {
ph('table', pull(
self.app.git.readCommitChanges(commit),
pull.map(function (file) {
+ var msg = file.msg || obj.msg
return ph('tr', [
ph('td', ph('code', u.escapeHTML(file.name))),
- // ph('td', ph('code', u.escapeHTML(JSON.stringify(file.msg)))),
ph('td', file.deleted ? 'deleted'
- : file.created ? 'created'
- : file.hash ? 'changed'
+ : file.created ?
+ ph('a', {href:
+ self.app.render.toUrl('/git/blob/'
+ + (file.hash[1] || file.hash[0])
+ + '?msg=' + encodeURIComponent(msg.key))
+ + '&commit=' + rev
+ + '&path=' + encodeURIComponent(file.name)
+ }, 'created')
+ : file.hash ?
+ ph('a', {href:
+ self.app.render.toUrl('/git/diff/'
+ + file.hash[0] + '..' + file.hash[1]
+ + '?msg=' + encodeURIComponent(msg.key))
+ + '&commit=' + rev
+ + '&path=' + encodeURIComponent(file.name)
+ }, 'changed')
: file.mode ? 'mode changed'
: JSON.stringify(file))
])
+ }),
+ Catch(function (err) {
+ if (err && err.name === 'ObjectNotFoundError') return
+ if (err && err.name === 'BlobNotFoundError') return self.askWantBlobsForm(err.links)
+ return false
})
))
]
@@ -1895,23 +1948,8 @@ Serve.prototype.gitTree = function (rev) {
]),
missingBlobs ? self.askWantBlobsForm(missingBlobs) : ph('table', [
pull(
- self.app.git.readTree(obj),
- paramap(function (file, cb) {
- self.app.git.getObjectMsg({
- obj: file.hash,
- headMsgId: obj.msg.key,
- }, function (err, msg) {
- if (err && err.name === 'ObjectNotFoundError') return cb(null, file)
- if (err && err.name === 'BlobNotFoundError') return cb(null, {missingBlobs: err.links})
- if (err) return cb(err)
- file.msg = msg
- cb(null, file)
- })
- }, 8),
+ self.app.git.readTreeFull(obj),
pull.map(function (item) {
- if (item.missingBlobs) {
- return self.askWantBlobsForm(item.missingBlobs)
- }
if (!item.msg) return ph('tr', [
ph('td',
u.escapeHTML(item.name) + (item.type === 'tree' ? '/' : '')),
@@ -1936,6 +1974,11 @@ Serve.prototype.gitTree = function (rev) {
}, htime(fileDate))
),
])
+ }),
+ Catch(function (err) {
+ if (err && err.name === 'ObjectNotFoundError') return
+ if (err && err.name === 'BlobNotFoundError') return self.askWantBlobsForm(err.links)
+ return false
})
)
]),
@@ -1993,6 +2036,7 @@ Serve.prototype.gitBlob = function (rev) {
missingBlobs ? self.askWantBlobsForm(missingBlobs) : pull(
self.app.git.readObject(obj),
self.wrapBinary({
+ obj: obj,
rawUrl: self.app.render.toUrl('/git/raw/' + rev
+ '?msg=' + encodeURIComponent(msg.key)),
ext: self.query.ext
@@ -2006,6 +2050,296 @@ Serve.prototype.gitBlob = function (rev) {
})
}
+Serve.prototype.gitDiff = function (revs) {
+ var self = this
+ var parts = revs.split('..')
+ if (parts.length !== 2) return pull(
+ ph('div.error', 'revs should be <rev1>..<rev2>'),
+ self.wrapPage('git diff'),
+ self.respondSink(400)
+ )
+ var rev1 = parts[0]
+ var rev2 = parts[1]
+ if (!/[0-9a-f]{24}/.test(rev1)) return pull(
+ ph('div.error', 'rev 1 is not a git object id'),
+ self.wrapPage('git diff'),
+ self.respondSink(400)
+ )
+ if (!/[0-9a-f]{24}/.test(rev2)) return pull(
+ ph('div.error', 'rev 2 is not a git object id'),
+ self.wrapPage('git diff'),
+ self.respondSink(400)
+ )
+
+ if (!u.isRef(self.query.msg)) return pull(
+ ph('div.error', 'missing message id'),
+ self.wrapPage('git diff'),
+ self.respondSink(400)
+ )
+
+ var done = multicb({pluck: 1, spread: true})
+ // the msg qs param should point to the message for rev2 object. the msg for
+ // rev1 object we will have to look up.
+ self.app.git.getObjectMsg({
+ obj: rev1,
+ headMsgId: self.query.msg,
+ type: 'blob',
+ }, done())
+ self.getMsgDecryptedMaybeOoo(self.query.msg, done())
+ done(function (err, msg1, msg2) {
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('git diff ' + revs),
+ self.respondSink(400)
+ )
+ var msg1Date = new Date(msg1.value.timestamp)
+ var msg2Date = new Date(msg2.value.timestamp)
+ var revsShort = rev1.substr(0, 8) + '..' + rev2.substr(0, 8)
+ pull(
+ ph('section', [
+ ph('h3', ph('a', {href: ''}, revsShort)),
+ ph('div', [
+ ph('a', {
+ href: self.app.render.toUrl('/git/blob/' + rev1 + '?msg=' + encodeURIComponent(msg1.key))
+ }, rev1), ' ',
+ self.phIdLink(msg1.value.author), ' ',
+ ph('a', {
+ href: self.app.render.toUrl(msg1.key),
+ title: msg1Date.toLocaleString(),
+ }, htime(msg1Date))
+ ]),
+ ph('div', [
+ ph('a', {
+ href: self.app.render.toUrl('/git/blob/' + rev2 + '?msg=' + encodeURIComponent(msg2.key))
+ }, rev2), ' ',
+ self.phIdLink(msg2.value.author), ' ',
+ ph('a', {
+ href: self.app.render.toUrl(msg2.key),
+ title: msg2Date.toLocaleString(),
+ }, htime(msg2Date))
+ ]),
+ u.readNext(function (cb) {
+ var done = multicb({pluck: 1, spread: true})
+ self.app.git.openObject({
+ obj: rev1,
+ msg: msg1.key,
+ }, done())
+ self.app.git.openObject({
+ obj: rev2,
+ msg: msg2.key,
+ }, done())
+ /*
+ self.app.git.guessCommitAndPath({
+ obj: rev2,
+ msg: msg2.key,
+ }, done())
+ */
+ done(function (err, obj1, obj2/*, info2*/) {
+ if (err && err.name === 'BlobNotFoundError')
+ return cb(null, self.askWantBlobsForm(err.links))
+ if (err) return cb(err)
+
+ var done = multicb({pluck: 1, spread: true})
+ pull.collect(done())(self.app.git.readObject(obj1))
+ pull.collect(done())(self.app.git.readObject(obj2))
+ self.app.getLineComments({obj: obj2, hash: rev2}, done())
+ done(function (err, bufs1, bufs2, lineComments) {
+ if (err) return cb(err)
+ var str1 = Buffer.concat(bufs1, obj1.length).toString('utf8')
+ var str2 = Buffer.concat(bufs2, obj2.length).toString('utf8')
+ var diff = Diff.structuredPatch('', '', str1, str2)
+ cb(null, self.gitDiffTable(diff, lineComments, {
+ obj: obj2,
+ hash: rev2,
+ commit: self.query.commit, // info2.commit,
+ path: self.query.path, // info2.path,
+ }))
+ })
+ })
+ })
+ ]),
+ self.wrapPage('git diff'),
+ self.respondSink(200)
+ )
+ })
+}
+
+Serve.prototype.gitDiffTable = function (diff, lineComments, lineCommentInfo) {
+ var updateMsg = lineCommentInfo.obj.msg
+ var self = this
+ return pull(
+ ph('table', [
+ pull(
+ pull.values(diff.hunks),
+ pull.map(function (hunk) {
+ var oldLine = hunk.oldStart
+ var newLine = hunk.newStart
+ return [
+ ph('tr', [
+ ph('td', {colspan: 3}),
+ ph('td', ph('pre',
+ '@@ -' + oldLine + ',' + hunk.oldLines + ' ' +
+ '+' + newLine + ',' + hunk.newLines + ' @@'))
+ ]),
+ pull(
+ pull.values(hunk.lines),
+ pull.map(function (line) {
+ var s = line[0]
+ if (s == '\\') return
+ var html = self.app.render.highlight(line)
+ var lineNums = [s == '+' ? '' : oldLine++, s == '-' ? '' : newLine++]
+ var hash = lineCommentInfo.hash
+ var newLineNum = lineNums[lineNums.length-1]
+ var id = hash + '-' + (newLineNum || (lineNums[0] + '-'))
+ var idEnc = encodeURIComponent(id)
+ var allowComment = s !== '-'
+ && self.query.commit && self.query.path
+ return [
+ ph('tr', {
+ class: s == '+' ? 'diff-new' : s == '-' ? 'diff-old' : ''
+ }, [
+ lineNums.map(function (num, i) {
+ return ph('td', [
+ ph('a', {
+ name: i === 0 ? idEnc : undefined,
+ href: '#' + idEnc
+ }, String(num))
+ ])
+ }),
+ ph('td',
+ allowComment ? ph('a', {
+ href: '?msg=' +
+ encodeURIComponent(self.query.msg)
+ + '&comment=' + idEnc
+ + '&commit=' + encodeURIComponent(self.query.commit)
+ + '&path=' + encodeURIComponent(self.query.path)
+ + '#' + idEnc
+ }, '…') : ''
+ ),
+ ph('td', ph('pre', u.escapeHTML(html)))
+ ]),
+ (lineComments[newLineNum] ?
+ ph('tr',
+ ph('td', {colspan: 4},
+ self.renderLineCommentThread(lineComments[newLineNum], id)
+ )
+ )
+ : newLineNum && lineCommentInfo && self.query.comment === id ?
+ ph('tr',
+ ph('td', {colspan: 4},
+ self.renderLineCommentForm({
+ id: id,
+ line: newLineNum,
+ updateId: updateMsg.key,
+ blobId: hash,
+ repoId: updateMsg.value.content.repo,
+ commitId: lineCommentInfo.commit,
+ filePath: lineCommentInfo.path,
+ })
+ )
+ )
+ : '')
+ ]
+ })
+ )
+ ]
+ })
+ )
+ ])
+ )
+}
+
+Serve.prototype.renderLineCommentThread = function (lineComment, id) {
+ return this.streamThreadWithComposer({
+ root: lineComment.msg.key,
+ id: id,
+ placeholder: 'reply to line comment thread'
+ })
+}
+
+Serve.prototype.renderLineCommentForm = function (opts) {
+ return [
+ this.phComposer({
+ placeholder: 'comment on this line',
+ id: opts.id,
+ lineComment: opts
+ })
+ ]
+}
+
+// return a composer, pull-hyperscript style
+Serve.prototype.phComposer = function (opts) {
+ var self = this
+ return u.readNext(function (cb) {
+ self.composer(opts, function (err, composer) {
+ if (err) return cb(err)
+ cb(null, pull.once(composer.outerHTML))
+ })
+ })
+}
+
+Serve.prototype.gitLineComment = function (path) {
+ var self = this
+ var id
+ try {
+ id = decodeURIComponent(String(path))
+ if (id[0] === '%') {
+ return self.getMsgDecryptedMaybeOoo(id, gotMsg)
+ } else {
+ msg = JSON.parse(id)
+ }
+ } catch(e) {
+ return gotMsg(e)
+ }
+ gotMsg(null, msg)
+ function gotMsg(err, msg) {
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.respondSink(400, {'Content-Type': ctype('html')})
+ )
+ var c = msg && msg.value && msg.value.content
+ if (!c) return pull(
+ pull.once('Missing message ' + id),
+ self.respondSink(500, {'Content-Type': ctype('html')})
+ )
+ self.app.git.diffFile({
+ msg: c.updateId,
+ commit: c.commitId,
+ path: c.filePath,
+ }, function (err, file) {
+ if (err && err.name === 'BlobNotFoundError')
+ return self.askWantBlobs(err.links)
+ if (err) return pull(
+ pull.once(err.stack),
+ self.respondSink(400, {'Content-Type': 'text/plain'})
+ )
+ var path
+ if (file.created) {
+ path = '/git/blob/' + file.hash[1]
+ + '?msg=' + encodeURIComponent(c.updateId)
+ + '&commit=' + c.commitId
+ + '&path=' + encodeURIComponent(c.filePath)
+ + '#' + file.hash[1] + '-' + c.line
+ } else {
+ path = '/git/diff/' + file.hash[0] + '..' + file.hash[1]
+ + '?msg=' + encodeURIComponent(c.updateId)
+ + '&commit=' + c.commitId
+ + '&path=' + encodeURIComponent(c.filePath)
+ + '#' + file.hash[1] + '-' + c.line
+ }
+ var url = self.app.render.toUrl(path)
+ /*
+ return pull(
+ ph('a', {href: url}, path),
+ self.wrapPage(id),
+ self.respondSink(200)
+ )
+ */
+ self.redirect(url)
+ })
+ }
+}
+
Serve.prototype.gitObjectLinks = function (headMsgId, type) {
var self = this
return paramap(function (id, cb) {
@@ -2197,10 +2531,107 @@ Serve.prototype.markdown = function (url) {
)
}
+Serve.prototype.zip = function (url) {
+ var self = this
+ var parts = url.split('/').slice(1)
+ var id = decodeURIComponent(parts.shift())
+ var filename = parts.join('/')
+ var blobs = self.app.sbot.blobs
+ var etag = id + filename
+ var index = filename === '' || /\/$/.test(filename)
+ var indexFilename = index && (filename + 'index.html')
+ if (filename === '/' || /\/\/$/.test(filename)) {
+ // force directory listing if path ends in //
+ filename = filename.replace(/\/$/, '')
+ indexFilename = false
+ }
+ var files = index && []
+ if (self.req.headers['if-none-match'] === etag) return self.respond(304)
+ blobs.size(id, function (err, size) {
+ if (size == null) return askWantBlobsForm([id])
+ if (err) {
+ if (/^invalid/.test(err.message)) return self.respond(400, err.message)
+ else return self.respond(500, err.message || err)
+ }
+ var unzip = require('unzip')
+ var parseUnzip = unzip.Parse()
+ var gotEntry = false
+ parseUnzip.on('entry', function (entry) {
+ if (index) {
+ if (!gotEntry) {
+ if (entry.path === indexFilename) {
+ gotEntry = true
+ return serveFile(entry)
+ } else if (entry.path.substr(0, filename.length) === filename) {
+ files.push({path: entry.path, type: entry.type, props: entry.props})
+ }
+ }
+ } else {
+ if (!gotEntry && entry.path === filename) {
+ gotEntry = true
+ // if (false && entry.type === 'Directory') return serveDirectory(entry)
+ return serveFile(entry)
+ }
+ }
+ entry.autodrain()
+ })
+ parseUnzip.on('close', function () {
+ if (gotEntry) return
+ if (!index) return self.respond(404, 'Entry not found')
+ pull(
+ ph('section', {}, [
+ ph('h3', [
+ ph('a', {href: self.app.render.toUrl('/links/' + id)}, id.substr(0, 8) + '…'),
+ ' ',
+ ph('a', {href: self.app.render.toUrl('/zip/' + encodeURIComponent(id) + '/' + filename)}, filename || '/'),
+ ]),
+ pull(
+ pull.values(files),
+ pull.map(function (file) {
+ var path = '/zip/' + encodeURIComponent(id) + '/' + file.path
+ return ph('li', [
+ ph('a', {href: self.app.render.toUrl(path)}, file.path)
+ ])
+ })
+ )
+ ]),
+ self.wrapPage(id + filename),
+ self.respondSink(200)
+ )
+ gotEntry = true // so that the handler on error event does not run
+ })
+ parseUnzip.on('error', function (err) {
+ if (!gotEntry) return self.respond(400, err.message)
+ })
+ var size
+ function serveFile(entry) {
+ size = entry.size
+ pull(
+ toPull.source(entry),
+ ident(gotType),
+ self.respondSink()
+ )
+ }
+ pull(
+ self.app.getBlob(id),
+ toPull(parseUnzip)
+ )
+ function gotType(type) {
+ type = type && mime.lookup(type)
+ if (type) self.res.setHeader('Content-Type', type)
+ if (size) self.res.setHeader('Content-Length', size)
+ self.res.setHeader('Cache-Control', 'public, max-age=315360000')
+ self.res.setHeader('etag', etag)
+ self.res.writeHead(200)
+ }
+ })
+}
+
// wrap a binary source and render it or turn into an embed
Serve.prototype.wrapBinary = function (opts) {
var self = this
var ext = opts.ext
+ var hash = opts.obj.hash
return function (read) {
var readRendered, type
read = ident(function (_ext) {
@@ -2227,9 +2658,83 @@ Serve.prototype.wrapBinary = function (opts) {
src: opts.rawUrl
})
}
- return ph('pre', pull.map(function (buf) {
- return self.app.render.highlight(buf.toString('utf8'), ext)
- })(read))
+ if (type === 'text/markdown') {
+ // TODO: rewrite links to files/images to be correct
+ return ph('blockquote', u.readNext(function (cb) {
+ pull.collect(function (err, bufs) {
+ if (err) return cb(pull.error(err))
+ var text = Buffer.concat(bufs).toString('utf8')
+ return cb(null, pull.once(self.app.render.markdown(text)))
+ })(read)
+ }))
+ }
+ var i = 0
+ var updateMsg = opts.obj.msg
+ var commitId = self.query.commit
+ var filePath = self.query.path
+ var lineComments = opts.lineComments || {}
+ return u.readNext(function (cb) {
+ if (commitId && filePath) {
+ self.app.getLineComments({
+ obj: opts.obj,
+ hash: hash,
+ }, gotLineComments)
+ } else {
+ gotLineComments(null, {})
+ }
+ function gotLineComments(err, lineComments) {
+ if (err) return cb(err)
+ cb(null, ph('table',
+ pull(
+ read,
+ utf8(),
+ split(),
+ pull.map(function (line) {
+ var lineNum = i++
+ var id = hash + '-' + lineNum
+ var idEnc = encodeURIComponent(id)
+ var allowComment = self.query.commit && self.query.path
+ return [
+ ph('tr', [
+ ph('td',
+ allowComment ? ph('a', {
+ href: '?msg=' + encodeURIComponent(self.query.msg)
+ + '&commit=' + encodeURIComponent(self.query.commit)
+ + '&path=' + encodeURIComponent(self.query.path)
+ + '&comment=' + idEnc
+ + '#' + idEnc
+ }, '…') : ''
+ ),
+ ph('td', ph('a', {
+ name: id,
+ href: '#' + idEnc
+ }, String(lineNum))),
+ ph('td', ph('pre', self.app.render.highlight(line, ext)))
+ ]),
+ lineComments[lineNum] ? ph('tr',
+ ph('td', {colspan: 4},
+ self.renderLineCommentThread(lineComments[lineNum], id)
+ )
+ ) : '',
+ self.query.comment === id ? ph('tr',
+ ph('td', {colspan: 4},
+ self.renderLineCommentForm({
+ id: id,
+ line: lineNum,
+ updateId: updateMsg.key,
+ repoId: updateMsg.value.content.repo,
+ commitId: commitId,
+ blobId: hash,
+ filePath: filePath,
+ })
+ )
+ ) : ''
+ ]
+ })
+ )
+ ))
+ }
+ })
}
}
@@ -2300,7 +2805,8 @@ Serve.prototype.wrapThread = function (opts) {
self.app.render.prepareLinks(opts.recps, function (err, recps) {
if (err) return cb(er)
self.composer({
- placeholder: recps ? 'private reply' : 'reply',
+ placeholder: opts.placeholder
+ || (recps ? 'private reply' : 'reply'),
id: 'reply',
root: opts.root,
post: opts.post,
@@ -2439,6 +2945,11 @@ Serve.prototype.composer = function (opts, cb) {
var data = self.data
var myId = self.app.sbot.id
+ if (opts.id && data.composer_id && opts.id !== data.composer_id) {
+ // don't share data between multiple composers
+ data = {}
+ }
+
if (!data.text && self.query.text) data.text = self.query.text
if (!data.action && self.query.action) data.action = self.query.action
@@ -2532,6 +3043,7 @@ Serve.prototype.composer = function (opts, cb) {
enctype: 'multipart/form-data'},
h('input', {type: 'hidden', name: 'blobs',
value: JSON.stringify(blobs)}),
+ h('input', {type: 'hidden', name: 'composer_id', value: opts.id}),
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'})) : '',
@@ -2599,6 +3111,15 @@ Serve.prototype.composer = function (opts, cb) {
type: 'post',
text: String(data.text).replace(/\r\n/g, '\n'),
}
+ if (opts.lineComment) {
+ content.type = 'line-comment'
+ content.updateId = opts.lineComment.updateId
+ content.repo = opts.lineComment.repoId
+ content.commitId = opts.lineComment.commitId
+ content.filePath = opts.lineComment.filePath
+ content.blobId = opts.lineComment.blobId
+ content.line = opts.lineComment.line
+ }
var mentions = ssbMentions(data.text, {bareFeedNames: true, emoji: true})
.filter(function (mention) {
if (mention.emoji) {
@@ -2702,15 +3223,15 @@ Serve.prototype.composer = function (opts, cb) {
})
var draftMsg = {
- key: '%0000000000000000000000000000000000000000000=.sha256',
- value: {
- previous: '%0000000000000000000000000000000000000000000=.sha256',
- author: '@0000000000000000000000000000000000000000000=.ed25519',
- sequence: 1000,
- timestamp: 1000000000000,
- hash: 'sha256',
- content: content
- }
+ key: '%0000000000000000000000000000000000000000000=.sha256',
+ value: {
+ previous: '%0000000000000000000000000000000000000000000=.sha256',
+ author: '@0000000000000000000000000000000000000000000=.ed25519',
+ sequence: 1000,
+ timestamp: 1000000000000,
+ hash: 'sha256',
+ content: content
+ }
}
var estSize = JSON.stringify(draftMsg, null, 2).length
sizeEl.innerHTML = self.app.render.formatSize(estSize)
@@ -2725,9 +3246,9 @@ Serve.prototype.composer = function (opts, cb) {
pull.once(msg),
self.app.unboxMessages(),
self.app.render.renderFeeds({
- raw: raw,
- filter: self.query.filter,
- }),
+ raw: raw,
+ filter: self.query.filter,
+ }),
pull.drain(function (el) {
msgContainer.appendChild(h('tbody', el))
}, cb)