diff options
Diffstat (limited to 'lib/render-msg.js')
-rw-r--r-- | lib/render-msg.js | 443 |
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) + }) +} |