aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/app.js81
-rw-r--r--lib/git.js667
-rw-r--r--lib/render-msg.js39
-rw-r--r--lib/serve.js598
-rw-r--r--package-lock.json66
-rw-r--r--package.json8
-rw-r--r--static/styles.css3
7 files changed, 675 insertions, 787 deletions
diff --git a/lib/app.js b/lib/app.js
index f3b1774..9166eb2 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -11,7 +11,7 @@ var About = require('./about')
var Follows = require('./follows')
var Serve = require('./serve')
var Render = require('./render')
-var Git = require('./git')
+var Git = require('ssb-git')
var cat = require('pull-cat')
var proc = require('child_process')
var toPull = require('stream-to-pull-stream')
@@ -62,7 +62,7 @@ function App(sbot, config) {
this.unboxMsg = this.unboxMsg.bind(this)
this.render = new Render(this, this.opts)
- this.git = new Git(this)
+ this.git = new Git(this.sbot, this.config)
this.contacts = new Contacts(this.sbot)
this.follows = new Follows(this.sbot, this.contacts)
@@ -101,7 +101,7 @@ App.prototype.error = console.error.bind(console, logPrefix)
App.prototype.unboxMsg = function (msg, cb) {
var self = this
- var c = msg.value && msg.value.content
+ var c = msg && msg.value && msg.value.content
if (typeof c !== 'string') cb(null, msg)
else self.unboxContent(c, function (err, content) {
if (err) {
@@ -905,3 +905,78 @@ App.prototype.expandOoo = function (opts, cb) {
}
}
}
+
+App.prototype.getLineComments = function (opts, cb) {
+ // get line comments for a git-update message and git object id.
+ // line comments include message id, commit id and path
+ // but we have message id and git object hash.
+ // look up the git object hash for each line-comment
+ // to verify that it is for the git object file we want
+ var updateId = opts.obj.msg.key
+ var objId = opts.hash
+ var self = this
+ var lineComments = {}
+ pull(
+ self.sbot.backlinks ? self.sbot.backlinks.read({
+ query: [
+ {$filter: {
+ dest: updateId,
+ value: {
+ content: {
+ type: 'line-comment',
+ updateId: updateId,
+ }
+ }
+ }}
+ ]
+ }) : pull(
+ self.sbot.links({
+ dest: updateId,
+ rel: 'updateId',
+ values: true
+ }),
+ pull.filter(function (msg) {
+ var c = msg && msg.value && msg.value.content
+ return c && c.type === 'line-comment'
+ && c.updateId === updateId
+ })
+ ),
+ paramap(function (msg, cb) {
+ var c = msg.value.content
+ self.git.getObjectAtPath({
+ msg: updateId,
+ obj: c.commitId,
+ path: c.filePath,
+ }, function (err, info) {
+ if (err) return cb(err)
+ cb(null, {
+ obj: info.obj,
+ hash: info.hash,
+ msg: msg,
+ })
+ })
+ }, 4),
+ pull.filter(function (info) {
+ return info.hash === objId
+ }),
+ pull.drain(function (info) {
+ lineComments[info.msg.value.content.line] = info
+ }, function (err) {
+ cb(err, lineComments)
+ })
+ )
+}
+
+App.prototype.getThread = function (msg) {
+ return cat([
+ pull.once(msg),
+ this.sbot.backlinks ? this.sbot.backlinks.read({
+ query: [
+ {$filter: {dest: msg.key}}
+ ]
+ }) : this.sbot.links({
+ dest: msg.key,
+ values: true
+ })
+ ])
+}
diff --git a/lib/git.js b/lib/git.js
deleted file mode 100644
index cfcea9e..0000000
--- a/lib/git.js
+++ /dev/null
@@ -1,667 +0,0 @@
-var pull = require('pull-stream')
-var paramap = require('pull-paramap')
-var lru = require('hashlru')
-var memo = require('asyncmemo')
-var u = require('./util')
-var packidx = require('pull-git-packidx-parser')
-var Reader = require('pull-reader')
-var toPull = require('stream-to-pull-stream')
-var zlib = require('zlib')
-var looper = require('looper')
-var multicb = require('multicb')
-var kvdiff = require('pull-kvdiff')
-
-var ObjectNotFoundError = u.customError('ObjectNotFoundError')
-
-var types = {
- blob: true,
- commit: true,
- tree: true,
-}
-var emptyBlobHash = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391'
-
-module.exports = Git
-
-function Git(app) {
- this.app = app
-
- this.findObject = memo({
- cache: lru(5),
- asString: function (opts) {
- return opts.obj + opts.headMsgId
- }
- }, this._findObject.bind(this))
-
- this.findObjectInMsg = memo({
- cache: lru(5),
- asString: function (opts) {
- return opts.obj + opts.msg
- }
- }, this._findObjectInMsg.bind(this))
-
- this.getPackIndex = memo({
- cache: lru(4),
- asString: JSON.stringify
- }, this._getPackIndex.bind(this))
-}
-
-// open, read, buffer and callback an object
-Git.prototype.getObject = function (opts, cb) {
- var self = this
- self.openObject(opts, function (err, obj) {
- if (err) return cb(err)
- pull(
- self.readObject(obj),
- u.pullConcat(cb)
- )
- })
-}
-
-// get a message that pushed an object
-Git.prototype.getObjectMsg = function (opts, cb) {
- this.findObject(opts, function (err, loc) {
- if (err) return cb(err)
- cb(null, loc.msg)
- })
-}
-
-Git.prototype.openObject = function (opts, cb) {
- var self = this
- self.findObjectInMsg(opts, function (err, loc) {
- if (err) return cb(err)
- self.app.ensureHasBlobs([loc.packLink], function (err) {
- if (err) return cb(err)
- cb(null, {
- type: opts.type,
- length: opts.length,
- offset: loc.offset,
- next: loc.next,
- packLink: loc.packLink,
- idx: loc.idx,
- msg: loc.msg,
- })
- })
- })
-}
-
-Git.prototype.readObject = function (obj) {
- if (obj.offset === obj.next) return pull.empty()
- return pull(
- this.app.readBlobSlice(obj.packLink, {start: obj.offset, end: obj.next}),
- this.decodeObject({
- type: obj.type,
- length: obj.length,
- packLink: obj.packLink,
- idx: obj.idx,
- })
- )
-}
-
-// find which packfile contains a git object, and where in the packfile it is
-// located
-Git.prototype._findObject = function (opts, cb) {
- if (!opts.headMsgId) return cb(new TypeError('missing head message id'))
- if (!opts.obj) return cb(new TypeError('missing object id'))
- var self = this
- var objId = opts.obj
- if (objId === emptyBlobHash) {
- // special case: the empty blob may be found anywhere
- self.app.getMsgDecrypted(opts.headMsgId, function (err, msg) {
- if (err) return cb(err)
- return cb(null, {
- offset: 0,
- next: 0,
- packLink: null,
- idx: null,
- msg: msg,
- })
- })
- }
- self.findObjectMsgs(opts, function (err, msgs) {
- if (err) return cb(err)
- if (msgs.length === 0)
- return cb(new ObjectNotFoundError('unable to find git object ' + objId))
- self.findObjectInMsgs(objId, msgs, cb)
- })
-}
-
-Git.prototype._findObjectInMsg = function (opts, cb) {
- if (!opts.msg) return cb(new TypeError('missing message id'))
- if (!opts.obj) return cb(new TypeError('missing object id'))
- var self = this
- self.app.getMsgDecrypted(opts.msg, function (err, msg) {
- if (err) return cb(err)
- self.findObjectInMsgs(opts.obj, [msg], cb)
- })
-}
-
-Git.prototype.findObjectInMsgs = function (objId, msgs, cb) {
- var self = this
- var objIdBuf = new Buffer(objId, 'hex')
- // if blobs may need to be fetched, try to ask the user about as many of them
- // at one time as possible
- var packidxs = [].concat.apply([], msgs.map(function (msg) {
- var c = msg.value.content
- var idxs = u.toArray(c.indexes).map(u.toLink)
- return u.toArray(c.packs).map(u.toLink).map(function (pack, i) {
- var idx = idxs[i]
- if (pack && idx) return {
- msg: msg,
- packLink: pack,
- idxLink: idx,
- }
- })
- })).filter(Boolean)
- var blobLinks = packidxs.length === 1
- ? [packidxs[0].idxLink, packidxs[0].packLink]
- : packidxs.map(function (packidx) {
- return packidx.idxLink
- })
- self.app.ensureHasBlobs(blobLinks, function (err) {
- if (err) return cb(err)
- pull(
- pull.values(packidxs),
- paramap(function (pack, cb) {
- self.getPackIndex(pack.idxLink, function (err, idx) {
- if (err) return cb(err)
- var offset = idx.find(objIdBuf)
- if (!offset) return cb()
- cb(null, {
- offset: offset.offset,
- next: offset.next,
- packLink: pack.packLink,
- idx: idx,
- msg: pack.msg,
- })
- })
- }, 4),
- pull.filter(),
- pull.take(1),
- pull.collect(function (err, offsets) {
- if (err) return cb(err)
- if (offsets.length === 0)
- return cb(new ObjectNotFoundError('unable to find git object '
- + objId + ' in ' + msgs.length + ' messages'))
- cb(null, offsets[0])
- })
- )
- })
-}
-
-// given an object id and ssb msg id, get a set of messages of which at least one pushed the object.
-Git.prototype.findObjectMsgs = function (opts, cb) {
- var self = this
- var id = opts.obj
- var headMsgId = opts.headMsgId
- var ended = false
- var waiting = 0
- var maybeMsgs = []
-
- function cbOnce(err, msgs) {
- if (ended) return
- ended = true
- cb(err, msgs)
- }
-
- function objectMatches(commit) {
- return commit && (commit === id || commit.sha1 === id)
- }
-
- if (!headMsgId) return cb(new TypeError('missing head message id'))
- if (!u.isRef(headMsgId))
- return cb(new TypeError('bad head message id \'' + headMsgId + '\''))
-
- ;(function getMsg(id) {
- waiting++
- self.app.getMsgDecrypted(id, function (err, msg) {
- waiting--
- if (ended) return
- if (err && err.name == 'NotFoundError')
- return cbOnce(new Error('missing message ' + headMsgId))
- if (err) return cbOnce(err)
- var c = msg.value.content
- if (typeof c === 'string')
- return cbOnce(new Error('unable to decrypt message ' + msg.key))
- if ((u.toArray(c.object_ids).some(objectMatches))
- || (u.toArray(c.tags).some(objectMatches))
- || (u.toArray(c.commits).some(objectMatches))) {
- // found the object
- return cbOnce(null, [msg])
- } else if (!c.object_ids) {
- // the object might be here
- maybeMsgs.push(msg)
- }
- // traverse the DAG to keep looking for the object
- u.toArray(c.repoBranch).filter(u.isRef).forEach(getMsg)
- if (waiting === 0) {
- cbOnce(null, maybeMsgs)
- }
- })
- })(headMsgId)
-}
-
-Git.prototype._getPackIndex = function (idxBlobLink, cb) {
- pull(this.app.readBlob(idxBlobLink), packidx(cb))
-}
-
-var objectTypes = [
- 'none', 'commit', 'tree', 'blob',
- 'tag', 'unused', 'ofs-delta', 'ref-delta'
-]
-
-function readTypedVarInt(reader, cb) {
- var type, value, shift
- reader.read(1, function (end, buf) {
- if (ended = end) return cb(end)
- var firstByte = buf[0]
- type = objectTypes[(firstByte >> 4) & 7]
- value = firstByte & 15
- shift = 4
- checkByte(firstByte)
- })
-
- function checkByte(byte) {
- if (byte & 0x80)
- reader.read(1, gotByte)
- else
- cb(null, type, value)
- }
-
- function gotByte(end, buf) {
- if (ended = end) return cb(end)
- var byte = buf[0]
- value += (byte & 0x7f) << shift
- shift += 7
- checkByte(byte)
- }
-}
-
-function readVarInt(reader, cb) {
- var value = 0, shift = 0
- reader.read(1, function gotByte(end, buf) {
- if (ended = end) return cb(end)
- var byte = buf[0]
- value += (byte & 0x7f) << shift
- shift += 7
- if (byte & 0x80)
- reader.read(1, gotByte)
- else
- cb(null, value)
- })
-}
-
-function inflate(read) {
- return toPull(zlib.createInflate())(read)
-}
-
-Git.prototype.decodeObject = function (opts) {
- var self = this
- var packLink = opts.packLink
- return function (read) {
- var reader = Reader()
- reader(read)
- return u.readNext(function (cb) {
- readTypedVarInt(reader, function (end, type, length) {
- if (end === true) cb(new Error('Missing object type'))
- else if (end) cb(end)
- else if (type === 'ref-delta') getObjectFromRefDelta(length, cb)
- else if (opts.type && type !== opts.type)
- cb(new Error('expected type \'' + opts.type + '\' ' +
- 'but found \'' + type + '\''))
- else if (opts.length && length !== opts.length)
- cb(new Error('expected length ' + opts.length + ' ' +
- 'but found ' + length))
- else cb(null, inflate(reader.read()))
- })
- })
-
- function getObjectFromRefDelta(length, cb) {
- reader.read(20, function (end, sourceHash) {
- if (end) return cb(end)
- var inflatedReader = Reader()
- pull(reader.read(), inflate, inflatedReader)
- readVarInt(inflatedReader, function (err, expectedSourceLength) {
- if (err) return cb(err)
- readVarInt(inflatedReader, function (err, expectedTargetLength) {
- if (err) return cb(err)
- var offset = opts.idx.find(sourceHash)
- if (!offset) return cb(null, 'missing source object ' +
- sourcehash.toString('hex'))
- var readSource = pull(
- self.app.readBlobSlice(opts.packLink, {
- start: offset.offset,
- end: offset.next
- }),
- self.decodeObject({
- type: opts.type,
- length: expectedSourceLength,
- packLink: opts.packLink,
- idx: opts.idx
- })
- )
- cb(null, patchObject(inflatedReader, length, readSource, expectedTargetLength))
- })
- })
- })
- }
- }
-}
-
-function readOffsetSize(cmd, reader, readCb) {
- var offset = 0, size = 0
-
- function addByte(bit, outPos, cb) {
- if (cmd & (1 << bit))
- reader.read(1, function (err, buf) {
- if (err) readCb(err)
- else cb(buf[0] << (outPos << 3))
- })
- else
- cb(0)
- }
-
- addByte(0, 0, function (val) {
- offset = val
- addByte(1, 1, function (val) {
- offset |= val
- addByte(2, 2, function (val) {
- offset |= val
- addByte(3, 3, function (val) {
- offset |= val
- addSize()
- })
- })
- })
- })
- function addSize() {
- addByte(4, 0, function (val) {
- size = val
- addByte(5, 1, function (val) {
- size |= val
- addByte(6, 2, function (val) {
- size |= val
- readCb(null, offset, size || 0x10000)
- })
- })
- })
- }
-}
-
-function patchObject(deltaReader, deltaLength, readSource, targetLength) {
- var srcBuf
- var ended
-
- return u.readNext(function (cb) {
- pull(readSource, u.pullConcat(function (err, buf) {
- if (err) return cb(err)
- srcBuf = buf
- cb(null, read)
- }))
- })
-
- function read(abort, cb) {
- if (ended) return cb(ended)
- deltaReader.read(1, function (end, dBuf) {
- if (ended = end) return cb(end)
- var cmd = dBuf[0]
- if (cmd & 0x80)
- // skip a variable amount and then pass through a variable amount
- readOffsetSize(cmd, deltaReader, function (err, offset, size) {
- if (err) return earlyEnd(err)
- var buf = srcBuf.slice(offset, offset + size)
- cb(end, buf)
- })
- else if (cmd)
- // insert `cmd` bytes from delta
- deltaReader.read(cmd, cb)
- else
- cb(new Error("unexpected delta opcode 0"))
- })
-
- function earlyEnd(err) {
- cb(err === true ? new Error('stream ended early') : err)
- }
- }
-}
-
-var gitNameRegex = /^(.*) <(([^>@]*)(@[^>]*)?)> (.*) (.*)$/
-function parseName(line) {
- var m = gitNameRegex.exec(line)
- if (!m) return null
- return {
- name: m[1],
- email: m[2],
- localpart: m[3],
- feed: u.isRef(m[4]) && m[4] || undefined,
- date: new Date(m[5] * 1000),
- tz: m[6],
- }
-}
-
-Git.prototype.getCommit = function (obj, cb) {
- pull(this.readObject(obj), u.pullConcat(function (err, buf) {
- if (err) return cb(err)
- var commit = {
- msg: obj.msg,
- parents: [],
- }
- var authorLine, committerLine
- var lines = buf.toString('utf8').split('\n')
- for (var line; (line = lines.shift()); ) {
- var parts = line.split(' ')
- var prop = parts.shift()
- var value = parts.join(' ')
- switch (prop) {
- case 'tree':
- commit.tree = value
- break
- case 'parent':
- commit.parents.push(value)
- break
- case 'author':
- authorLine = value
- break
- case 'committer':
- committerLine = value
- break
- case 'gpgsig':
- var sigLines = [value]
- while (lines[0] && lines[0][0] == ' ')
- sigLines.push(lines.shift().slice(1))
- commit.gpgsig = sigLines.join('\n')
- break
- default:
- return cb(new TypeError('unknown git object property ' + prop))
- }
- }
- commit.committer = parseName(committerLine)
- if (authorLine !== committerLine) commit.author = parseName(authorLine)
- commit.body = lines.join('\n')
- cb(null, commit)
- }))
-}
-
-Git.prototype.getTag = function (obj, cb) {
- pull(this.readObject(obj), u.pullConcat(function (err, buf) {
- if (err) return cb(err)
- var tag = {
- msg: obj.msg,
- }
- var authorLine, tagterLine
- var lines = buf.toString('utf8').split('\n')
- for (var line; (line = lines.shift()); ) {
- var parts = line.split(' ')
- var prop = parts.shift()
- var value = parts.join(' ')
- switch (prop) {
- case 'object':
- tag.object = value
- break
- case 'type':
- if (!types[value])
- return cb(new TypeError('unknown git object type ' + type))
- tag.type = value
- break
- case 'tag':
- tag.tag = value
- break
- case 'tagger':
- tag.tagger = parseName(value)
- break
- default:
- return cb(new TypeError('unknown git object property ' + prop))
- }
- }
- tag.body = lines.join('\n')
- cb(null, tag)
- }))
-}
-
-function readCString(reader, cb) {
- var chars = []
- var loop = looper(function () {
- reader.read(1, next)
- })
- function next(err, ch) {
- if (err) return cb(err)
- if (ch[0] === 0) return cb(null, Buffer.concat(chars).toString('utf8'))
- chars.push(ch)
- loop()
- }
- loop()
-}
-
-Git.prototype.readTree = function (obj) {
- var self = this
- var reader = Reader()
- reader(this.readObject(obj))
- return function (abort, cb) {
- if (abort) return reader.abort(abort, cb)
- readCString(reader, function (err, str) {
- if (err) return cb(err)
- var parts = str.split(' ')
- var mode = parseInt(parts[0], 8)
- var name = parts.slice(1).join(' ')
- reader.read(20, function (err, hash) {
- if (err) return cb(err)
- cb(null, {
- name: name,
- mode: mode,
- hash: hash.toString('hex'),
- type: mode === 0040000 ? 'tree' :
- mode === 0160000 ? 'commit' : 'blob',
- })
- })
- })
- }
-}
-
-Git.prototype.readCommitChanges = function (commit) {
- var self = this
- return u.readNext(function (cb) {
- var done = multicb({pluck: 1})
- commit.parents.forEach(function (rev) {
- var cb = done()
- self.getObjectMsg({
- obj: rev,
- headMsgId: commit.msg.key,
- type: 'commit',
- }, function (err, msg) {
- if (err) return cb(err)
- self.openObject({
- obj: rev,
- msg: msg.key,
- }, function (err, obj) {
- if (err) return cb(err)
- self.getCommit(obj, cb)
- })
- })
- })
- done()(null, commit)
- done(function (err, commits) {
- if (err) return cb(err)
- var done = multicb({pluck: 1})
- commits.forEach(function (commit) {
- var cb = done()
- if (!commit.tree) return cb(null, pull.empty())
- self.getObjectMsg({
- obj: commit.tree,
- headMsgId: commit.msg.key,
- type: 'tree',
- }, function (err, msg) {
- if (err) return cb(err)
- self.openObject({
- obj: commit.tree,
- msg: commit.msg.key,
- }, cb)
- })
- })
- done(function (err, trees) {
- if (err) return cb(err)
- cb(null, self.diffTreesRecursive(trees))
- })
- })
- })
-}
-
-Git.prototype.diffTrees = function (objs) {
- var self = this
- return pull(
- kvdiff(objs.map(function (obj) {
- return self.readTree(obj)
- }), 'name'),
- pull.map(function (item) {
- var diff = item.diff || {}
- var head = item.values[item.values.length-1]
- var created = true
- for (var k = 0; k < item.values.length-1; k++)
- if (item.values[k]) created = false
- return {
- name: item.key,
- hash: item.values.map(function (val) { return val.hash }),
- mode: diff.mode,
- type: item.values.map(function (val) { return val.type }),
- deleted: !head,
- created: created
- }
- })
- )
-}
-
-Git.prototype.diffTreesRecursive = function (objs) {
- var self = this
- return pull(
- self.diffTrees(objs),
- paramap(function (item, cb) {
- if (!item.type.some(function (t) { return t === 'tree' }))
- return cb(null, [item])
- var done = multicb({pluck: 1})
- item.type.forEach(function (type, i) {
- var cb = done()
- if (type !== 'tree') return cb(null, pull.once(item))
- var hash = item.hash[i]
- self.getObjectMsg({
- obj: hash,
- headMsgId: objs[i].msg.key,
- }, function (err, msg) {
- if (err) return cb(err)
- self.openObject({
- obj: hash,
- msg: msg.key,
- }, cb)
- })
- })
- done(function (err, objs) {
- if (err) return cb(err)
- cb(null, pull(
- self.diffTreesRecursive(objs),
- pull.map(function (f) {
- f.name = item.name + '/' + f.name
- return f
- })
- ))
- })
- }, 4),
- pull.flatten()
- )
-}
diff --git a/lib/render-msg.js b/lib/render-msg.js
index d4ad670..1603d24 100644
--- a/lib/render-msg.js
+++ b/lib/render-msg.js
@@ -10,6 +10,7 @@ function RenderMsg(render, app, msg, opts) {
this.render = render
this.app = app
this.msg = msg
+ this.serve = opts.serve
this.value = msg && msg.value || {}
var content = this.value.content
this.c = content || {}
@@ -20,6 +21,13 @@ function RenderMsg(render, app, msg, opts) {
this.shouldWrap = this.opts.wrap !== false
}
+RenderMsg.prototype.getMsg = function (id, cb) {
+ if (!id) return cb()
+ return this.serve
+ ? this.serve.getMsgDecryptedMaybeOoo(id, cb)
+ : this.app.getMsgDecryptedOoo(id, cb)
+}
+
RenderMsg.prototype.toUrl = function (href) {
return this.render.toUrl(href)
}
@@ -287,6 +295,7 @@ RenderMsg.prototype.message = function (cb) {
case 'talenet-idea-comment':
case 'talenet-idea-comment_reply': return this.ideaComment(cb)
case 'about-resource': return this.aboutResource(cb)
+ case 'line-comment': return this.lineComment(cb)
default: return this.object(cb)
}
}
@@ -1638,3 +1647,33 @@ RenderMsg.prototype.aboutResource = function (cb) {
h('blockquote', {innerHTML: self.render.markdown(self.c.description)})
), cb)
}
+
+RenderMsg.prototype.lineComment = function (cb) {
+ var self = this
+ var done = multicb({pluck: 1, spread: true})
+ self.link(self.c.repo, done())
+ self.getMsg(self.c.updateId, done())
+ done(function (err, repoLink, updateMsg) {
+ if (err) return cb(err)
+ return self.wrap(h('div',
+ h('div', h('small', '> ',
+ repoLink, ' ',
+ h('a', {
+ href: self.toUrl(self.c.updateId)
+ },
+ updateMsg
+ ? htime(new Date(updateMsg.value.timestamp))
+ : String(self.c.updateId)
+ ), ' ',
+ h('a', {
+ href: self.toUrl('/git/commit/' + self.c.commitId + '?msg=' + encodeURIComponent(self.c.updateId))
+ }, String(self.c.commitId).substr(0, 8)), ' ',
+ h('a', {
+ href: self.toUrl('/git/line-comment/' +
+ encodeURIComponent(self.msg.key || JSON.stringify(self.msg)))
+ }, h('code', self.c.filePath + ':' + self.c.line))
+ )),
+ self.c.text ?
+ h('div', {innerHTML: self.render.markdown(self.c.text)}) : ''), cb)
+ })
+}
diff --git a/lib/serve.js b/lib/serve.js
index 77cfdb4..5ba0004 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) {
@@ -537,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}, [
@@ -550,8 +554,8 @@ Serve.prototype.advsearch = function (ext) {
hasQuery && pull(
self.app.advancedSearch(q),
self.renderThread({
- feed: q.source,
- }),
+ feed: q.source,
+ }),
self.wrapMessages()
)
]),
@@ -577,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)
)),
@@ -1013,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) {
@@ -1273,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)
)
@@ -1299,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,
@@ -1327,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,
@@ -1570,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
})
@@ -1623,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
])
@@ -1646,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')
}
}
@@ -1780,13 +1801,29 @@ Serve.prototype.gitCommit = function (rev) {
pull.map(function (file) {
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(obj.msg.key))
+ }, 'created')
+ : file.hash ?
+ ph('a', {href:
+ self.app.render.toUrl('/git/diff/'
+ + file.hash[0] + '..' + file.hash[1]
+ + '?msg=' + encodeURIComponent(obj.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
})
))
]
@@ -1908,23 +1945,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' ? '/' : '')),
@@ -1949,6 +1971,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
})
)
]),
@@ -2006,6 +2033,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
@@ -2019,6 +2047,295 @@ 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,
+ 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) {
@@ -2310,6 +2627,7 @@ Serve.prototype.zip = function (url) {
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) {
@@ -2336,9 +2654,76 @@ 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)
+ return [
+ ph('tr', [
+ ph('td', ph('a', {
+ name: id,
+ href: '?msg=' + encodeURIComponent(self.query.msg)
+ + '&commit=' + encodeURIComponent(self.query.commit)
+ + '&path=' + encodeURIComponent(self.query.path)
+ + '&comment=' + idEnc
+ + '#' + 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,
+ filePath: filePath,
+ })
+ )
+ ) : ''
+ ]
+ })
+ )
+ ))
+ }
+ })
}
}
@@ -2409,7 +2794,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,
@@ -2552,6 +2938,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
@@ -2645,6 +3036,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'})) : '',
@@ -2712,6 +3104,14 @@ 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.line = opts.lineComment.line
+ }
var mentions = ssbMentions(data.text, {bareFeedNames: true, emoji: true})
.filter(function (mention) {
if (mention.emoji) {
@@ -2815,15 +3215,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)
@@ -2838,9 +3238,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)
diff --git a/package-lock.json b/package-lock.json
index 0f5d62f..905c326 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -180,6 +180,11 @@
"streamsearch": "0.1.2"
}
},
+ "diff": {
+ "version": "3.3.1",
+ "resolved": "http://localhost:8989/blobs/get/&+6G9mvp4Q/5bujMKlbFdbrub2IXMPleXIqTqyyvY5Ww=.sha256",
+ "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU="
+ },
"ed2curve": {
"version": "0.1.4",
"resolved": "http://localhost:8989/blobs/get/&X6VtEiGqSVFiIHAZAXFZXB5q9iUd3gULIU9c0xyynNo=.sha256",
@@ -492,6 +497,7 @@
"readable-stream": {
"version": "1.0.34",
"resolved": "http://localhost:8989/blobs/get/&0QEMlyAUePLZCW/tXOEhdYfVI4R19FBv4JyHzdW8VqY=.sha256",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"requires": {
"core-util-is": "http://localhost:8989/blobs/get/&pKRNq2V57ePgat5Y0m+P1kLq4JFT/VnGCPy3lRpJk5g=.sha256",
"inherits": "2.0.3",
@@ -772,17 +778,15 @@
"resolved": "http://localhost:8989/blobs/get/&+uVE8RHNwJIJa68sQGICGbxnCTCOdLLhSt/Pb4NmgL0=.sha256",
"integrity": "sha256-+uVE8RHNwJIJa68sQGICGbxnCTCOdLLhSt/Pb4NmgL0="
},
+ "pull-catch": {
+ "version": "1.0.0",
+ "resolved": "http://localhost:8989/blobs/get/&K8t9klRhYJkdveaxSpBMFwzMtmNyLwNRTQUeCVnUvYU=.sha256",
+ "integrity": "sha256-K8t9klRhYJkdveaxSpBMFwzMtmNyLwNRTQUeCVnUvYU="
+ },
"pull-defer": {
"version": "http://localhost:8989/blobs/get/&mDI2o/JC9yvFOVbeAhDN+hwTm7cK+dBwISYO49EMY24=.sha256",
"integrity": "sha1-CIew/7MK8ypW2+z6csFnInHwexM="
},
- "pull-git-packidx-parser": {
- "version": "http://localhost:8989/blobs/get/&ou0MPQZabBgzrHDu54jzLU3Sc6Rf5a/lost0whPQUJ0=.sha256",
- "integrity": "sha1-LYvwr+SCSJfuA4QL/k9ahq/syiE=",
- "requires": {
- "pull-stream": "3.6.1"
- }
- },
"pull-goodbye": {
"version": "0.0.2",
"resolved": "http://localhost:8989/blobs/get/&MQhN9Pp3BYaj0TzxK39JfAK7CjVceg1EPfNhhFnZ9sU=.sha256",
@@ -842,14 +846,6 @@
}
}
},
- "pull-kvdiff": {
- "version": "0.0.1",
- "resolved": "http://localhost:8989/blobs/get/&xxfT88LrbK4FSIhpaNmIAt4jzsaBgVugeaDPVwC1gCU=.sha256",
- "integrity": "sha256-xxfT88LrbK4FSIhpaNmIAt4jzsaBgVugeaDPVwC1gCU=",
- "requires": {
- "multicb": "1.2.2"
- }
- },
"pull-many": {
"version": "http://localhost:8989/blobs/get/&wBHfPheBEjwjjkNoeM5mWtU/tYDs8+xnt5w6C1+EK3g=.sha256",
"integrity": "sha1-Pa3ZttFWxUVyG9qNAAPdjqoGKT4=",
@@ -884,6 +880,14 @@
"resolved": "http://localhost:8989/blobs/get/&fFOPAd5Js7mJkEU4XHeJ6vE6H7nsTOegHpjY0hpTu00=.sha256",
"integrity": "sha256-fFOPAd5Js7mJkEU4XHeJ6vE6H7nsTOegHpjY0hpTu00="
},
+ "pull-split": {
+ "version": "0.2.0",
+ "resolved": "http://localhost:8989/blobs/get/&OWAh7XbI03yih8UH5Vu0qrYM6TGx0q8uah06dm22CJo=.sha256",
+ "integrity": "sha256-OWAh7XbI03yih8UH5Vu0qrYM6TGx0q8uah06dm22CJo=",
+ "requires": {
+ "pull-through": "1.0.18"
+ }
+ },
"pull-stream": {
"version": "3.6.1",
"resolved": "http://localhost:8989/blobs/get/&xEhoJll+9Z5EYr7s7MUgCbBhdF1nekcqnIdIKV4z2SU=.sha256",
@@ -904,6 +908,11 @@
}
}
},
+ "pull-utf8-decoder": {
+ "version": "1.0.2",
+ "resolved": "http://localhost:8989/blobs/get/&JHBdLm6x7Rsu08/eLVWYKq+YpGna4xnvqM44ucX2tpk=.sha256",
+ "integrity": "sha256-JHBdLm6x7Rsu08/eLVWYKq+YpGna4xnvqM44ucX2tpk="
+ },
"pull-ws": {
"version": "3.3.0",
"resolved": "http://localhost:8989/blobs/get/&xe26a32xW10OFqKs+xdyUTSobJPCAGdnv7yIAjoqmlk=.sha256",
@@ -928,6 +937,7 @@
"readable-stream": {
"version": "1.0.34",
"resolved": "http://localhost:8989/blobs/get/&0QEMlyAUePLZCW/tXOEhdYfVI4R19FBv4JyHzdW8VqY=.sha256",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"requires": {
"core-util-is": "http://localhost:8989/blobs/get/&pKRNq2V57ePgat5Y0m+P1kLq4JFT/VnGCPy3lRpJk5g=.sha256",
"inherits": "2.0.3",
@@ -1069,6 +1079,7 @@
"readable-stream": {
"version": "1.0.34",
"resolved": "http://localhost:8989/blobs/get/&0QEMlyAUePLZCW/tXOEhdYfVI4R19FBv4JyHzdW8VqY=.sha256",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"requires": {
"core-util-is": "http://localhost:8989/blobs/get/&pKRNq2V57ePgat5Y0m+P1kLq4JFT/VnGCPy3lRpJk5g=.sha256",
"inherits": "2.0.3",
@@ -1206,6 +1217,30 @@
"version": "http://localhost:8989/blobs/get/&5yyYBZpB4w+X6kW42KMh43Xz9KP3XW79mYOj40/CHA4=.sha256",
"integrity": "sha1-Fg4kETeCqcpegGByqnpl58hl2/I="
},
+ "ssb-mentions": {
+ "version": "http://localhost:8989/blobs/get/&GjuxknqKwJqHznKueFNCyIh52v1woz5PB41vqmoHfyM=.sha256",
+ "integrity": "sha1-AMd4SslTCsJi2h5902MlCCA1UEY=",
+ "requires": {
+ "ssb-marked": "http://localhost:8989/blobs/get/&5yyYBZpB4w+X6kW42KMh43Xz9KP3XW79mYOj40/CHA4=.sha256",
+ "ssb-ref": "2.7.1"
+ },
+ "dependencies": {
+ "is-valid-domain": {
+ "version": "0.0.2",
+ "resolved": "http://localhost:8989/blobs/get/&phjiap+k1lGC5LPVba/w4Caomw5o0NQp2Hol+u/YAzE=.sha256",
+ "integrity": "sha1-PnqUI/98Oy/hFmOvvW04N6JR+3c="
+ },
+ "ssb-ref": {
+ "version": "2.7.1",
+ "resolved": "http://localhost:8989/blobs/get/&wLimOD3785KVj7kBIAbjN6GH8EMhKRkcZSPrEMhdhks=.sha256",
+ "integrity": "sha1-XU7/xUXsD/1/wVuieCmmQLiir7o=",
+ "requires": {
+ "ip": "1.1.5",
+ "is-valid-domain": "0.0.2"
+ }
+ }
+ }
+ },
"ssb-ref": {
"version": "http://localhost:8989/blobs/get/&wLimOD3785KVj7kBIAbjN6GH8EMhKRkcZSPrEMhdhks=.sha256",
"integrity": "sha1-XU7/xUXsD/1/wVuieCmmQLiir7o=",
@@ -1337,6 +1372,7 @@
"readable-stream": {
"version": "1.0.34",
"resolved": "http://localhost:8989/blobs/get/&0QEMlyAUePLZCW/tXOEhdYfVI4R19FBv4JyHzdW8VqY=.sha256",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"requires": {
"core-util-is": "http://localhost:8989/blobs/get/&pKRNq2V57ePgat5Y0m+P1kLq4JFT/VnGCPy3lRpJk5g=.sha256",
"inherits": "2.0.3",
diff --git a/package.json b/package.json
index d4f48d7..37d3ca4 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"asyncmemo": "^1.1.0",
"base64-url": "^2.0.0",
"busboy": "^0.2.14",
+ "diff": "^3.3.1",
"emoji-named-characters": "^1.0.2",
"emoji-server": "^1.0.0",
"hashlru": "^2.1.0",
@@ -13,21 +14,22 @@
"human-time": "^0.0.1",
"hyperscript": "^2.0.2",
"jpeg-autorotate": "^3.0.0",
- "looper": "^4.0.0",
"mime-types": "^2.1.12",
"multicb": "^1.2.1",
"pull-box-stream": "^1.0.12",
"pull-cat": "^1.1.11",
- "pull-git-packidx-parser": "^1.0.0",
+ "pull-catch": "^1.0.0",
"pull-hyperscript": "^0.2.2",
"pull-identify-filetype": "^1.1.0",
- "pull-kvdiff": "^0.0.1",
"pull-paginate": "^1.0.0",
"pull-paramap": "^1.2.1",
"pull-reader": "^1.2.9",
+ "pull-split": "^0.2.0",
"pull-stream": "^3.5.0",
+ "pull-utf8-decoder": "^1.0.2",
"ssb-client": "http://localhost:8989/blobs/get/&EAaUpI+wrJM5/ly1RqZW0GAEF4PmCAmABBj7e6UIrL0=.sha256",
"ssb-contact": "^1.2.0",
+ "ssb-git": "^0.6.0",
"ssb-marked": "^0.7.1",
"ssb-mentions": "http://localhost:8989/blobs/get/&GjuxknqKwJqHznKueFNCyIh52v1woz5PB41vqmoHfyM=.sha256",
"ssb-sort": "^1.0.0",
diff --git a/static/styles.css b/static/styles.css
index c26cf59..b22074f 100644
--- a/static/styles.css
+++ b/static/styles.css
@@ -193,3 +193,6 @@ table.ssb-object td {
.chess-square-dark {
background-color: #ccc;
}
+
+.diff-old { background-color: #ffe2dd; }
+.diff-new { background-color: #d1ffd6; }