aboutsummaryrefslogtreecommitdiff
path: root/lib/render-msg.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/render-msg.js')
-rw-r--r--lib/render-msg.js443
1 files changed, 403 insertions, 40 deletions
diff --git a/lib/render-msg.js b/lib/render-msg.js
index c0001fc..9b69603 100644
--- a/lib/render-msg.js
+++ b/lib/render-msg.js
@@ -25,11 +25,7 @@ RenderMsg.prototype.toUrl = function (href) {
}
RenderMsg.prototype.linkify = function (text) {
- var arr = text.split(u.ssbRefRegex)
- for (var i = 1; i < arr.length; i += 2) {
- arr[i] = h('a', {href: this.toUrl(arr[i])}, arr[i])
- }
- return arr
+ return this.render.linkify(text)
}
function token() {
@@ -151,8 +147,14 @@ RenderMsg.prototype.actions = function () {
h('a', {href: '?gt=' + this.msg.timestamp}, '↓'), ' '] : '',
this.c.type === 'gathering' ? [
h('a', {href: this.render.toUrl('/about/' + encodeURIComponent(this.msg.key))}, 'about'), ' '] : '',
- h('a', {href: this.toUrl(this.msg.key) + '?raw'}, 'raw'), ' ',
- this.voteFormInner('dig')
+ typeof this.c.text === 'string' ? [
+ h('a', {href: this.toUrl(this.msg.key) + '?raw=md',
+ title: 'view markdown source'}, 'md'), ' '] : '',
+ h('a', {href: this.toUrl(this.msg.key) + '?raw',
+ title: 'view raw message'}, 'raw'), ' ',
+ this.buttonsCommon(),
+ this.c.type === 'gathering' ? [this.attendButton(), ' '] : '',
+ this.voteButton('dig')
) : [
this.msg.rel ? [this.msg.rel, ' '] : ''
]
@@ -179,16 +181,29 @@ RenderMsg.prototype.recpsIds = function () {
: []
}
-RenderMsg.prototype.voteFormInner = function (expression) {
+RenderMsg.prototype.buttonsCommon = function () {
var chan = this.msg.value.content.channel
+ var recps = this.recpsIds()
return [
- h('input', {type: 'hidden', name: 'action', value: 'vote'}),
- h('input', {type: 'hidden', name: 'recps',
- value: this.recpsIds().join(',')}),
chan ? h('input', {type: 'hidden', name: 'channel', value: chan}) : '',
h('input', {type: 'hidden', name: 'link', value: this.msg.key}),
- h('input', {type: 'hidden', name: 'value', value: 1}),
- h('input', {type: 'submit', name: 'expression', value: expression})]
+ h('input', {type: 'hidden', name: 'recps', value: recps.join(',')})
+ ]
+}
+
+RenderMsg.prototype.voteButton = function (expression) {
+ var chan = this.msg.value.content.channel
+ return [
+ h('input', {type: 'hidden', name: 'vote_value', value: 1}),
+ h('input', {type: 'hidden', name: 'vote_expression', value: expression}),
+ h('input', {type: 'submit', name: 'action_vote', value: expression})]
+}
+
+RenderMsg.prototype.attendButton = function () {
+ var chan = this.msg.value.content.channel
+ return [
+ h('input', {type: 'submit', name: 'action_attend', value: 'attend'})
+ ]
}
RenderMsg.prototype.message = function (cb) {
@@ -223,9 +238,23 @@ RenderMsg.prototype.message = function (cb) {
case 'ferment/update':
case 'robeson/update':
return this.update(cb)
+ case 'chess_invite':
+ case 'ssb_chess_invite':
+ return this.chessInvite(cb)
+ case 'chess_invite_accept':
+ case 'ssb_chess_invite_accept':
+ return this.chessInviteAccept(cb)
+ case 'chess_move':
+ case 'ssb_chess_move':
+ return this.chessMove(cb)
+ case 'chess_game_end':
+ case 'ssb_chess_game_end':
+ return this.chessGameEnd(cb)
case 'wifi-network': return this.wifiNetwork(cb)
case 'mutual/credit': return this.mutualCredit(cb)
case 'mutual/account': return this.mutualAccount(cb)
+ case 'npm-publish': return this.npmPublish(cb)
+ case 'npm-packages': return this.npmPackages(cb)
default: return this.object(cb)
}
}
@@ -235,25 +264,33 @@ RenderMsg.prototype.encrypted = function (cb) {
}
RenderMsg.prototype.markdown = function (cb) {
+ if (this.opts.markdownSource)
+ return this.markdownSource(this.c.text, this.c.mentions)
return this.render.markdown(this.c.text, this.c.mentions)
}
+RenderMsg.prototype.markdownSource = function (text, mentions) {
+ return h('div',
+ h('pre', String(text)),
+ mentions ? [
+ h('div', h('em', 'mentions:')),
+ this.valueTable(mentions, function () {})
+ ] : ''
+ ).innerHTML
+}
+
RenderMsg.prototype.post = function (cb) {
var self = this
var done = multicb({pluck: 1, spread: true})
- var branchDone = multicb({pluck: 1})
- u.toArray(self.c.branch).forEach(function (branch) {
- self.link(branch, branchDone())
- })
if (self.c.root === self.c.branch) done()()
else self.link(self.c.root, done())
- branchDone(done())
+ self.links(self.c.branch, done())
done(function (err, rootLink, branchLinks) {
if (err) return self.wrap(u.renderError(err), cb)
self.wrap(h('div.ssb-post',
- rootLink ? h('div', h('small', '>> ', rootLink)) : '',
+ rootLink ? h('div', h('small', h('span.symbol', '→'), ' ', rootLink)) : '',
branchLinks.map(function (a, i) {
- return h('div', h('small', '> ', a))
+ return h('div', h('small', h('span.symbol', '  ↳'), ' ', a))
}),
h('div.ssb-post-text', {innerHTML: self.markdown()})
), cb)
@@ -320,6 +357,8 @@ RenderMsg.prototype.title = function (cb) {
} else {
if (self.c.type === 'ssb-dns')
cb(null, self.c.record && JSON.stringify(self.c.record.data) || self.msg.key)
+ else if (self.c.type === 'npm-publish')
+ self.npmPublishTitle(cb)
else
self.app.getAbout(self.msg.key, function (err, about) {
if (err) return cb(err)
@@ -362,17 +401,38 @@ RenderMsg.prototype.link1 = function (link, cb) {
return a
}
+RenderMsg.prototype.links = function (links, cb) {
+ var self = this
+ var done = multicb({pluck: 1})
+ u.toArray(links).forEach(function (link) {
+ self.link(link, done())
+ })
+ done(cb)
+}
+
function dateTime(d) {
var date = new Date(d.epoch)
- return date.toUTCString()
+ return date.toString()
// d.bias
// d.epoch
}
RenderMsg.prototype.about = function (cb) {
- var img = u.linkDest(this.c.image)
var done = multicb({pluck: 1, spread: true})
var elCb = done()
+
+ var isAttendingMsg = u.linkDest(this.c.attendee) === this.msg.value.author
+ && Object.keys(this.c).sort().join() === 'about,attendee,type'
+ if (isAttendingMsg) {
+ var attending = !this.c.attendee.remove
+ this.wrapMini([
+ attending ? ' is attending' : ' is not attending', ' ',
+ this.link1(this.c.about, done())
+ ], elCb)
+ return done(cb)
+ }
+
+ var img = u.linkDest(this.c.image)
// if there is a description, it is likely to be multi-line
var hasDescription = this.c.description != null
var wrap = hasDescription ? this.wrap : this.wrapMini
@@ -430,6 +490,7 @@ RenderMsg.prototype.contact = function (cb) {
' from ',
h('code', self.c.note)
] : '',
+ self.c.reason ? [' because ', h('q', self.c.reason)] : ''
], cb)
})
}
@@ -475,50 +536,58 @@ RenderMsg.prototype.gitUpdate = function (cb) {
var size = [].concat(self.c.packs, self.c.indexes)
.map(function (o) { return o && o.size })
.reduce(function (total, s) { return total + s })
- self.link(self.c.repo, function (err, a) {
+
+ var done = multicb({pluck: 1, spread: true})
+ self.link(self.c.repo, done())
+ self.render.npmPackageMentions(self.c.mentions, done())
+ done(function (err, a, pkgMentionsEl) {
if (err) return cb(err)
self.wrap(h('div.ssb-git-update',
'git push ', a, ' ',
!isNaN(size) ? [self.render.formatSize(size), ' '] : '',
self.c.refs ? h('ul', Object.keys(self.c.refs).map(function (ref) {
var id = self.c.refs[ref]
- return h('li',
- ref.replace(/^refs\/(heads|tags)\//, ''), ': ',
- id ? h('code', id) : h('em', 'deleted'))
+ var type = /^refs\/tags/.test(ref) ? 'tag' : 'commit'
+ var path = id && ('/git/' + type + '/' + encodeURIComponent(id)
+ + '?msg=' + encodeURIComponent(self.msg.key))
+ return h('li',
+ ref.replace(/^refs\/(heads|tags)\//, ''), ': ',
+ id ? h('a', {href: self.render.toUrl(path)}, h('code', id))
+ : h('em', 'deleted'))
})) : '',
Array.isArray(self.c.commits) ?
h('ul', self.c.commits.map(function (commit) {
- return h('li',
- h('code', String(commit.sha1).substr(0, 8)), ' ',
+ var path = '/git/commit/' + encodeURIComponent(commit.sha1)
+ + '?msg=' + encodeURIComponent(self.msg.key)
+ return h('li', h('a', {href: self.render.toUrl(path)},
+ h('code', String(commit.sha1).substr(0, 8))), ' ',
self.linkify(String(commit.title)),
- self.gitCommitBody(commit.body)
+ self.render.gitCommitBody(commit.body)
)
})) : '',
Array.isArray(self.c.tags) ?
h('ul', self.c.tags.map(function (tag) {
+ var path = '/git/tag/' + encodeURIComponent(tag.sha1)
+ + '?msg=' + encodeURIComponent(self.msg.key)
return h('li',
- h('code', String(tag.sha1).substr(0, 8)), ' ',
+ h('a', {href: self.render.toUrl(path)},
+ h('code', String(tag.sha1).substr(0, 8))), ' ',
'tagged ', String(tag.type), ' ',
h('code', String(tag.object).substr(0, 8)), ' ',
- String(tag.tag)
+ String(tag.tag),
+ tag.title ? [': ', self.linkify(String(tag.title).trim()), ' '] : '',
+ tag.body ? self.render.gitCommitBody(tag.body) : ''
)
})) : '',
self.c.commits_more ? h('div',
'+ ' + self.c.commits_more + ' more commits') : '',
self.c.tags_more ? h('div',
- '+ ' + self.c.tags_more + ' more tags') : ''
+ '+ ' + self.c.tags_more + ' more tags') : '',
+ pkgMentionsEl
), cb)
})
}
-RenderMsg.prototype.gitCommitBody = function (body) {
- if (!body) return ''
- var isMarkdown = !/^# Conflicts:$/m.test(body)
- return isMarkdown
- ? h('div', {innerHTML: this.render.markdown('\n' + body)})
- : h('pre', this.linkify('\n' + body))
-}
-
RenderMsg.prototype.gitPullRequest = function (cb) {
var self = this
var done = multicb({pluck: 1, spread: true})
@@ -800,3 +869,297 @@ RenderMsg.prototype.micro = function (cb) {
var el = h('span', {innerHTML: unwrapP(this.markdown())})
this.wrapMini(el, cb)
}
+
+function hJoin(els, seperator) {
+ return els.map(function (el, i) {
+ return [i === 0 ? '' : separator, el]
+ })
+}
+
+function asNpmReadme(readme) {
+ if (!readme || readme === 'ERROR: No README data found!') return
+ return u.ifString(readme)
+}
+
+function singleValue(obj) {
+ if (!obj || typeof obj !== 'object') return obj
+ var keys = Object.keys(obj)
+ if (keys.length === 1) return obj[keys[0]]
+}
+
+function ifDifferent(obj, value) {
+ if (singleValue(obj) !== value) return obj
+}
+
+RenderMsg.prototype.npmPublish = function (cb) {
+ var self = this
+ var render = self.render
+ var pkg = self.c.meta || {}
+ var pkgReadme = asNpmReadme(pkg.readme)
+ var pkgDescription = u.ifString(pkg.description)
+
+ var versions = Object.keys(pkg.versions || {})
+ var singleVersion = versions.length === 1 ? versions[0] : null
+ var singleRelease = singleVersion && pkg.versions[singleVersion]
+ var singleReadme = singleRelease && asNpmReadme(singleRelease.readme)
+
+ var distTags = pkg['dist-tags'] || {}
+ var distTagged = {}
+ for (var distTag in distTags)
+ if (distTag !== 'latest')
+ distTagged[distTags[distTag]] = distTag
+
+ self.links(self.c.previousPublish, function (err, prevLinks) {
+ if (err) return cb(err)
+ self.wrap([
+ h('div',
+ 'published ',
+ h('u', pkg.name), ' ',
+ hJoin(versions.map(function (version) {
+ var distTag = distTagged[version]
+ return [h('b', version), distTag ? [' (', h('i', distTag), ')'] : '']
+ }), ', ')
+ ),
+ pkgDescription ? h('div',
+ // TODO: make mdInline use custom emojis
+ h('q', {innerHTML: unwrapP(render.markdown(pkgDescription))})) : '',
+ prevLinks.length ? h('div', 'previous: ', prevLinks) : '',
+ pkgReadme && pkgReadme !== singleReadme ?
+ h('blockquote', {innerHTML: render.markdown(pkgReadme)}) : '',
+ versions.map(function (version, i) {
+ var release = pkg.versions[version] || {}
+ var license = u.ifString(release.license)
+ var author = ifDifferent(release.author, self.msg.value.author)
+ var description = u.ifString(release.description)
+ var readme = asNpmReadme(release.readme)
+ var keywords = u.toArray(release.keywords).map(u.ifString)
+ var dist = release.dist || {}
+ var size = u.ifNumber(dist.size)
+ return [
+ h > 0 ? h('br') : '',
+ version !== singleVersion ? h('div', 'version: ', version) : '',
+ author ? h('div', 'author: ', render.npmAuthorLink(author)) : '',
+ license ? h('div', 'license: ', h('code', license)) : '',
+ keywords.length ? h('div', 'keywords: ', keywords.join(', ')) : '',
+ size ? h('div', 'size: ', render.formatSize(size)) : '',
+ description && description !== pkgDescription ?
+ h('div', h('q', {innerHTML: render.markdown(description)})) : '',
+ readme ? h('blockquote', {innerHTML: render.markdown(readme)}) : ''
+ ]
+ })
+ ], cb)
+ })
+}
+
+RenderMsg.prototype.npmPackages = function (cb) {
+ var self = this
+ self.render.npmPackageMentions(self.c.mentions, function (err, el) {
+ if (err) return cb(err)
+ self.wrap(el, cb)
+ })
+}
+
+RenderMsg.prototype.npmPublishTitle = function (cb) {
+ var pkg = this.c.meta || {}
+ var name = pkg.name || pkg._id || '?'
+
+ var taggedVersions = {}
+ for (var version in pkg.versions || {})
+ taggedVersions[version] = []
+
+ var distTags = pkg['dist-tags'] || {}
+ for (var distTag in distTags) {
+ if (distTag === 'latest') continue
+ var version = distTags[distTag] || '?'
+ var tags = taggedVersions[version] || (taggedVersions[version] = [])
+ tags.push(distTag)
+ }
+
+ cb(null, name + '@' + Object.keys(taggedVersions).map(function (version) {
+ var tags = taggedVersions[version]
+ return (tags.length ? tags.join(',') + ':' : '') + version
+ }).join(','))
+}
+
+function expandDigitToSpaces(n) {
+ return ' '.substr(-n)
+}
+
+function parseFenRank (line) {
+ return line.replace(/\d/g, expandDigitToSpaces).split('')
+}
+
+function parseChess(fen) {
+ var fields = String(fen).split(/\s+/)
+ var ranks = fields[0].split('/')
+ var f2 = fields[2] || ''
+ return {
+ board: ranks.map(parseFenRank),
+ /*
+ nextMove: fields[1] === 'b' ? 'black'
+ : fields[1] === 'w' ? 'white' : 'unknown',
+ castling: f2 === '-' ? {} : {
+ w: {
+ k: 0 < f2.indexOf('K'),
+ q: 0 < f2.indexOf('Q'),
+ },
+ b: {
+ k: 0 < f2.indexOf('k'),
+ q: 0 < f2.indexOf('q'),
+ }
+ },
+ enpassantTarget: fields[3] === '-' ? null : fields[3],
+ halfmoves: Number(fields[4]),
+ fullmoves: Number(fields[5]),
+ */
+ }
+}
+
+var chessSymbols = {
+ ' ': [' ', ''],
+ P: ['♙', 'white pawn'],
+ N: ['♘', 'white knight'],
+ B: ['♗', 'white bishop'],
+ R: ['♖', 'white rook'],
+ Q: ['♕', 'white queen'],
+ K: ['♔', 'white king'],
+ p: ['♟', 'black pawn'],
+ n: ['♞', 'black knight'],
+ b: ['♝', 'black bishop'],
+ r: ['♜', 'black rook'],
+ q: ['♛', 'black queen'],
+ k: ['♚', 'black king'],
+}
+
+function renderChessSymbol(c, loc) {
+ var info = chessSymbols[c] || ['?', 'unknown']
+ return h('span.symbol', {
+ title: info[1] + (loc ? ' at ' + loc : '')
+ }, info[0])
+}
+
+function chessLocToIdxs(loc) {
+ var m = /^([a-h])([1-8])$/.exec(loc)
+ if (m) return [8 - m[2], m[1].charCodeAt(0) - 97]
+}
+
+function lookupPiece(board, loc) {
+ var idxs = chessLocToIdxs(loc)
+ return idxs && board[idxs[0]] && board[idxs[0]][idxs[1]]
+}
+
+function chessIdxsToLoc(i, j) {
+ return 'abcdefgh'[j] + (8-i)
+}
+
+RenderMsg.prototype.chessBoard = function (board) {
+ if (!board) return ''
+ return h('table.chess-board',
+ board.map(function (rank, i) {
+ return h('tr', rank.map(function (piece, j) {
+ var dark = (i ^ j) & 1
+ return h('td', {
+ class: 'chess-square chess-square-' + (dark ? 'dark' : 'light'),
+ }, renderChessSymbol(piece, chessIdxsToLoc(i, j)))
+ }))
+ })
+ )
+}
+
+RenderMsg.prototype.chessMove = function (cb) {
+ var self = this
+ var c = self.c
+ var fen = c.fen && c.fen.length === 2 ? c.pgnMove : c.fen
+ var game = parseChess(fen)
+ var piece = game && lookupPiece(game.board, c.dest)
+ self.link(self.c.root, function (err, rootLink) {
+ if (err) return cb(err)
+ self.wrap([
+ h('div', h('small', '> ', rootLink)),
+ h('p',
+ // 'player ', (c.ply || ''), ' ',
+ 'moved ', (piece ? renderChessSymbol(piece) : ''), ' ',
+ 'from ', c.orig, ' ',
+ 'to ', c.dest
+ ),
+ self.chessBoard(game.board)
+ ], cb)
+ })
+}
+
+RenderMsg.prototype.chessInvite = function (cb) {
+ var self = this
+ var myColor = self.c.myColor
+ self.link(self.c.inviting, function (err, link) {
+ if (err) return cb(err)
+ self.wrap([
+ 'invites ', link, ' to play chess',
+ // myColor ? h('p', 'my color is ' + myColor) : ''
+ ], cb)
+ })
+}
+
+RenderMsg.prototype.chessInviteAccept = function (cb) {
+ var self = this
+ self.link(self.c.root, function (err, rootLink) {
+ if (err) return cb(err)
+ self.wrap([
+ h('div', h('small', '> ', rootLink)),
+ h('p', 'accepts invitation to play chess')
+ ], cb)
+ })
+}
+
+RenderMsg.prototype.chessGameEnd = function (cb) {
+ var self = this
+ var c = self.c
+ if (c.status === 'resigned') return self.link(self.c.root, function (err, rootLink) {
+ if (err) return cb(err)
+ self.wrap([
+ h('div', h('small', '> ', rootLink)),
+ h('p', h('strong', 'resigned'))
+ ], cb)
+ })
+
+ var fen = c.fen && c.fen.length === 2 ? c.pgnMove : c.fen
+ var game = parseChess(fen)
+ var piece = game && lookupPiece(game.board, c.dest)
+ var done = multicb({pluck: 1, spread: true})
+ self.link(self.c.root, done())
+ self.link(self.c.winner, done())
+ done(function (err, rootLink, winnerLink) {
+ if (err) return cb(err)
+ self.wrap([
+ h('div', h('small', '> ', rootLink)),
+ h('p',
+ 'moved ', (piece ? renderChessSymbol(piece) : ''), ' ',
+ 'from ', c.orig, ' ',
+ 'to ', c.dest
+ ),
+ h('p',
+ h('strong', self.c.status), '. winner: ', h('strong', winnerLink)),
+ self.chessBoard(game.board)
+ ], cb)
+ })
+}
+
+RenderMsg.prototype.chessMove = function (cb) {
+ var self = this
+ var c = self.c
+ var fen = c.fen && c.fen.length === 2 ? c.pgnMove : c.fen
+ var game = parseChess(fen)
+ var piece = game && lookupPiece(game.board, c.dest)
+ self.link(self.c.root, function (err, rootLink) {
+ if (err) return cb(err)
+ self.wrap([
+ h('div', h('small', '> ', rootLink)),
+ h('p',
+ // 'player ', (c.ply || ''), ' ',
+ 'moved ', (piece ? renderChessSymbol(piece) : ''), ' ',
+ 'from ', c.orig, ' ',
+ 'to ', c.dest
+ ),
+ self.chessBoard(game.board)
+ ], cb)
+ })
+}