From a5ae107c74256ca7494ccecc7d94dc35638d231e Mon Sep 17 00:00:00 2001 From: cel Date: Tue, 18 Apr 2017 20:10:16 -0700 Subject: Add contacts pages --- lib/app.js | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/serve.js | 56 +++++++++++++++++++++++++++++ package.json | 3 ++ 3 files changed, 174 insertions(+) diff --git a/lib/app.js b/lib/app.js index d22e2b7..5630f41 100644 --- a/lib/app.js +++ b/lib/app.js @@ -8,6 +8,8 @@ var ssbAvatar = require('ssb-avatar') var hasher = require('pull-hash/ext/ssb') var multicb = require('multicb') var paramap = require('pull-paramap') +var many = require('pull-many') +var defer = require('pull-defer') var Serve = require('./serve') var Render = require('./render') @@ -239,3 +241,116 @@ App.prototype.streamChannels = function (opts) { pull.unique() ) } + +App.prototype.streamFollows = function (id) { + return pull( + this.sbot.links({ + source: id, + rel: 'contact', + values: true, + reverse: true, + }), + pull.map(function (msg) { + return msg.value.content + }), + pull.filter(), + pull.unique(function (c) { + return c.contact + }), + pull.filter(function (c) { + return c.following + }), + pull.map(function (c) { + return c.contact + }) + ) +} + +App.prototype.streamFollowers = function (id) { + return pull( + this.sbot.links({ + dest: id, + rel: 'contact', + values: true, + reverse: true, + }), + pull.unique(function (msg) { + return msg.value.author + }), + pull.filter(function (msg) { + var c = msg.value.content + return c && c.following + }), + pull.map(function (msg) { + return msg.value.author + }) + ) +} + +App.prototype.streamFriends = function (id, endCb) { + var follows = {}, followers = {} + return pull( + many([ + pull( + this.streamFollows(id), + pull.map(function (id) { + return {id: id, follow: true} + }) + ), + pull( + this.streamFollowers(id), + pull.map(function (id) { + return {id: id, follower: true} + }) + ) + ]), + pull.filter(function (op) { + if (op.follow) { + if (followers[op.id]) { + delete followers[op.id] + return true + } else { + follows[op.id] = true + return false + } + } + if (op.follower) { + if (follows[op.id]) { + delete follows[op.id] + return true + } else { + followers[op.id] = true + return false + } + } + }), + pull.map(function (op) { + return op.id + }), + endCb && function (read) { + return function (abort, cb) { + read(abort, function (end, data) { + cb(end, data) + if (end) endCb(end === true ? null : end, { + followers: Object.keys(followers), + follows: Object.keys(follows), + }) + }) + } + } + ) +} + +App.prototype.createContactStreams = function (id) { + var follows = defer.source() + var followers = defer.source() + var friends = this.streamFriends(id, function (err, more) { + follows.resolve(err ? pull.error(err) : pull.values(more.follows)) + followers.resolve(err ? pull.error(err) : pull.values(more.followers)) + }) + return { + friends: friends, + follows: follows, + followers: followers, + } +} diff --git a/lib/serve.js b/lib/serve.js index 4b0d58d..68eebda 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -18,6 +18,7 @@ var Busboy = require('busboy') var mime = require('mime-types') var ident = require('pull-identify-filetype') var htime = require('human-time') +var ph = require('pull-hyperscript') module.exports = Serve @@ -230,6 +231,7 @@ Serve.prototype.path = function (url) { case '/links': return this.links(m[2]) case '/static': return this.static(m[2]) case '/emoji': return this.emoji(m[2]) + case '/contacts': return this.contacts(m[2]) } return this.respond(404, 'Not found') } @@ -424,6 +426,53 @@ Serve.prototype.channels = function (ext) { ) } +Serve.prototype.contacts = function (path) { + var self = this + var id = String(path).substr(1) + var contacts = self.app.createContactStreams(id) + + function renderFriendsList() { + return pull( + paramap(function (id, cb) { + self.app.getAbout(id, function (err, about) { + var name = about && about.name || id.substr(0, 8) + '…' + cb(null, h('a', {href: self.app.render.toUrl('/contacts/' + id)}, name)) + }) + }, 8), + pull.map(function (el) { + return [el, ' '] + }), + pull.flatten(), + pull.map(u.toHTML) + ) + } + + function idLink(id) { + return pull( + pull.once(id), + pull.asyncMap(self.renderIdLink.bind(self)), + pull.map(u.toHTML) + ) + } + + pull( + cat([ + ph('section', {}, [ + ph('h3', {}, ['Contacts: ', idLink(id)]), + ph('h4', {}, 'Friends'), + renderFriendsList()(contacts.friends), + ph('h4', {}, 'Follows'), + renderFriendsList()(contacts.follows), + ph('h4', {}, 'Followers'), + renderFriendsList()(contacts.followers) + ]) + ]), + this.wrapPage('contacts: ' + id), + this.respondSink(200, { + 'Content-Type': ctype('html') + }) + ) +} Serve.prototype.type = function (path) { var q = this.query @@ -892,6 +941,7 @@ var relationshipActions = [ Serve.prototype.wrapUserFeed = function (isScrolled, id) { var self = this var myId = self.app.sbot.id + var render = self.app.render return u.hyperwrap(function (thread, cb) { var done = multicb({pluck: 1, spread: true}) self.app.getAbout(id, done()) @@ -912,6 +962,12 @@ Serve.prototype.wrapUserFeed = function (isScrolled, id) { about.description ? h('div', {innerHTML: self.app.render.markdown(about.description)}) : '' )), + h('tr', + h('td'), + h('td', + h('a', {href: render.toUrl('/contacts/' + id)}, 'contacts') + ) + ), isScrolled ? '' : [ id === myId ? '' : h('tr', h('td'), diff --git a/package.json b/package.json index 5589696..28f1704 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,11 @@ "mime-types": "^2.1.12", "multicb": "^1.2.1", "pull-cat": "^1.1.11", + "pull-defer": "^0.2.2", "pull-hash": "^1.0.0", + "pull-hyperscript": "^0.2.2", "pull-identify-filetype": "^1.1.0", + "pull-many": "^1.0.8", "pull-paginate": "^1.0.0", "pull-paramap": "^1.2.1", "pull-stream": "^3.5.0", -- cgit v1.2.3