aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/about.js59
-rw-r--r--lib/app.js6
-rw-r--r--lib/render-msg.js2
-rw-r--r--lib/render.js2
-rw-r--r--lib/serve.js108
-rw-r--r--lib/util.js10
6 files changed, 162 insertions, 25 deletions
diff --git a/lib/about.js b/lib/about.js
new file mode 100644
index 0000000..4f1d582
--- /dev/null
+++ b/lib/about.js
@@ -0,0 +1,59 @@
+var pull = require('pull-stream')
+// var defer = require('pull-defer')
+// var many = require('pull-many')
+var multicb = require('multicb')
+var u = require('./util')
+
+module.exports = About
+
+function About(sbot) {
+ if (!(this instanceof About)) return new About(sbot)
+ this.sbot = sbot
+}
+
+About.prototype.createAboutOpStream = function (id) {
+ return pull(
+ this.sbot.links({dest: id, rel: 'about', values: true, reverse: true}),
+ pull.map(function (msg) {
+ var c = msg.value.content || {}
+ return Object.keys(c).filter(function (key) {
+ return key !== 'about'
+ && key !== 'type'
+ && key !== 'recps'
+ }).map(function (key) {
+ var value = c[key]
+ if (u.isRef(value)) value = {link: value}
+ return {
+ id: msg.key,
+ author: msg.value.author,
+ timestamp: msg.value.timestamp,
+ prop: key,
+ value: value,
+ remove: value && typeof value === 'object' && value.remove,
+ }
+ })
+ }),
+ pull.flatten()
+ )
+}
+
+About.prototype.createAboutStreams = function (id) {
+ var ops = this.createAboutOpStream(id)
+ var scalars = {/* author: {prop: value} */}
+ var sets = {/* author: {prop: {link}} */}
+
+ var setsDone = multicb({pluck: 1, spread: true})
+ setsDone()(null, pull.values([]))
+ return {
+ scalars: pull(
+ ops,
+ pull.unique(function (op) {
+ return op.author + '-' + op.prop + '-' + (op.value ? op.value.link : '')
+ }),
+ pull.filter(function (op) {
+ return !op.remove
+ })
+ ),
+ sets: u.readNext(setsDone)
+ }
+}
diff --git a/lib/app.js b/lib/app.js
index a76687c..a3bfbf6 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -9,7 +9,7 @@ var hasher = require('pull-hash/ext/ssb')
var multicb = require('multicb')
var paramap = require('pull-paramap')
var Contacts = require('ssb-contact')
-
+var About = require('./about')
var Serve = require('./serve')
var Render = require('./render')
@@ -280,3 +280,7 @@ App.prototype.streamChannels = function (opts) {
App.prototype.createContactStreams = function (id) {
return new Contacts(this.sbot).createContactStreams(id)
}
+
+App.prototype.createAboutStreams = function (id) {
+ return new About(this.sbot).createAboutStreams(id)
+}
diff --git a/lib/render-msg.js b/lib/render-msg.js
index 2a0e7df..931b7a6 100644
--- a/lib/render-msg.js
+++ b/lib/render-msg.js
@@ -149,6 +149,8 @@ RenderMsg.prototype.actions = function () {
this.msg.rel ? [this.msg.rel, ' '] : '',
this.opts.withGt && this.msg.timestamp ? [
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')
) : [
diff --git a/lib/render.js b/lib/render.js
index 316ad2f..bc4ce5b 100644
--- a/lib/render.js
+++ b/lib/render.js
@@ -183,7 +183,7 @@ Render.prototype.prepareLink = function (link, cb) {
if (link.name || !link.link) cb(null, link)
else this.app.getAbout(link.link, function (err, about) {
if (err) return cb(null, link)
- link.name = about.name || (link.link.substr(0, 8) + '…')
+ link.name = about.name || about.title || (link.link.substr(0, 8) + '…')
if (link.name && link.name[0] === link.link[0]) {
link.name = link.name.substr(1)
}
diff --git a/lib/serve.js b/lib/serve.js
index 4fe3cd6..9296c46 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -26,16 +26,6 @@ var emojiDir = path.join(require.resolve('emoji-named-characters'), '../pngs')
var urlIdRegex = /^(?:\/+(([%&@]|%25)(?:[A-Za-z0-9\/+]|%2[Ff]|%2[Bb]){43}(?:=|%3D)\.(?:sha256|ed25519))(?:\.([^?]*))?|(\/.*?))(?:\?(.*))?$/
-function isMsgEncrypted(msg) {
- var c = msg && msg.value.content
- return typeof c === 'string'
-}
-
-function isMsgReadable(msg) {
- var c = msg && msg.value && msg.value.content
- return typeof c === 'object' && c !== null
-}
-
function ctype(name) {
switch (name && /[^.\/]*$/.exec(name)[0] || 'html') {
case 'html': return 'text/html'
@@ -250,6 +240,7 @@ Serve.prototype.path = function (url) {
case '/static': return this.static(m[2])
case '/emoji': return this.emoji(m[2])
case '/contacts': return this.contacts(m[2])
+ case '/about': return this.about(m[2])
}
return this.respond(404, 'Not found')
}
@@ -349,9 +340,9 @@ Serve.prototype.private = function (ext) {
pull(
this.app.createLogStream(opts),
- pull.filter(isMsgEncrypted),
+ pull.filter(u.isMsgEncrypted),
this.app.unboxMessages(),
- pull.filter(isMsgReadable),
+ pull.filter(u.isMsgReadable),
pull.take(limit),
this.renderThreadPaginated(opts, null, q),
this.wrapMessages(),
@@ -549,6 +540,14 @@ Serve.prototype.channels = function (ext) {
)
}
+Serve.prototype.phIdLink = function (id) {
+ return pull(
+ pull.once(id),
+ pull.asyncMap(this.renderIdLink.bind(this)),
+ pull.map(u.toHTML)
+ )
+}
+
Serve.prototype.contacts = function (path) {
var self = this
var id = String(path).substr(1)
@@ -570,18 +569,10 @@ Serve.prototype.contacts = function (path) {
)
}
- 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('h3', {}, ['Contacts: ', self.phIdLink(id)]),
ph('h4', {}, 'Friends'),
renderFriendsList()(contacts.friends),
ph('h4', {}, 'Follows'),
@@ -597,9 +588,79 @@ Serve.prototype.contacts = function (path) {
)
}
+Serve.prototype.about = function (path) {
+ var self = this
+ var id = decodeURIComponent(String(path).substr(1))
+ var abouts = self.app.createAboutStreams(id)
+ var render = self.app.render
+
+ function renderAboutOpImage(link) {
+ if (!link) return
+ if (!u.isRef(link.link)) return ph('code', {}, JSON.stringify(link))
+ return ph('img', {
+ class: 'ssb-avatar-image',
+ src: render.imageUrl(link.link),
+ alt: link.link
+ + (link.size ? ' (' + render.formatSize(link.size) + ')' : '')
+ })
+ }
+
+ function renderAboutOpValue(value) {
+ if (!value) return
+ if (u.isRef(value.link)) return self.phIdLink(value.link)
+ if (value.epoch) return new Date(value.epoch).toUTCString()
+ return ph('code', {}, JSON.stringify(value))
+ }
+
+ function renderAboutOpContent(op) {
+ if (op.prop === 'image')
+ return renderAboutOpImage(op.value)
+ if (op.prop === 'description')
+ return h('div', {innerHTML: render.markdown(op.value)}).outerHTML
+ if (op.prop === 'title')
+ return h('strong', op.value).outerHTML
+ if (op.prop === 'name')
+ return h('u', op.value).outerHTML
+ return renderAboutOpValue(op.value)
+ }
+
+ function renderAboutOp(op) {
+ return ph('tr', {}, [
+ ph('td', self.phIdLink(op.author)),
+ ph('td',
+ ph('a', {href: render.toUrl(op.id)},
+ htime(new Date(op.timestamp)))),
+ ph('td', op.prop),
+ ph('td', renderAboutOpContent(op))
+ ])
+ }
+
+ pull(
+ cat([
+ ph('section', {}, [
+ ph('h3', {}, ['About: ', self.phIdLink(id)]),
+ ph('table', {},
+ pull(abouts.scalars, pull.map(renderAboutOp))
+ ),
+ pull(
+ abouts.sets,
+ pull.map(function (op) {
+ return h('pre', JSON.stringify(op, 0, 2))
+ }),
+ pull.map(u.toHTML)
+ )
+ ])
+ ]),
+ this.wrapPage('about: ' + id),
+ this.respondSink(200, {
+ 'Content-Type': ctype('html')
+ })
+ )
+}
+
Serve.prototype.type = function (path) {
var q = this.query
- var type = path.substr(1)
+ var type = decodeURIComponent(path.substr(1))
var opts = {
reverse: !q.forwards,
lt: Number(q.lt) || Date.now(),
@@ -1096,7 +1157,8 @@ Serve.prototype.wrapUserFeed = function (isScrolled, id) {
h('tr',
h('td'),
h('td',
- h('a', {href: render.toUrl('/contacts/' + id)}, 'contacts')
+ h('a', {href: render.toUrl('/contacts/' + id)}, 'contacts'), ' ',
+ h('a', {href: render.toUrl('/about/' + id)}, 'about')
)
),
h('tr',
diff --git a/lib/util.js b/lib/util.js
index 0a6356c..2c0cf03 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -103,3 +103,13 @@ u.extractFeedIds = function (str) {
})
return ids
}
+
+u.isMsgReadable = function (msg) {
+ var c = msg && msg.value && msg.value.content
+ return typeof c === 'object' && c !== null
+}
+
+u.isMsgEncrypted = function (msg) {
+ var c = msg && msg.value.content
+ return typeof c === 'string'
+}