aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/app.js53
-rw-r--r--lib/render-msg.js2
-rw-r--r--lib/render.js5
-rw-r--r--lib/serve.js129
-rw-r--r--lib/util.js3
5 files changed, 155 insertions, 37 deletions
diff --git a/lib/app.js b/lib/app.js
index a3bfbf6..dfec6bd 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -250,15 +250,17 @@ App.prototype.streamPeers = function (opts) {
App.prototype.getFollow = function (source, dest, cb) {
var self = this
pull(
- self.sbot.links({source: source, dest: dest, rel: 'contact', values: true}),
- pull.filter(function (msg) {
- var c = msg && msg.value && msg.value.content
+ self.sbot.links({source: source, dest: dest, rel: 'contact', reverse: true,
+ values: true, meta: false, keys: false}),
+ pull.filter(function (value) {
+ var c = value && value.content
return c && c.type === 'contact'
}),
- pull.reduce(function (doesFollow, msg) {
- var c = msg.value.content
- return !!c.following
- }, false, cb)
+ pull.take(1),
+ pull.collect(function (err, msgs) {
+ if (err) return cb(err)
+ cb(null, msgs[0] && !!msgs[0].content.following)
+ })
)
}
@@ -270,6 +272,43 @@ App.prototype.streamChannels = function (opts) {
return pull(
this.sbot.messagesByType({type: 'channel', reverse: true}),
this.unboxMessages(),
+ pull.filter(function (msg) {
+ return msg.value.content.subscribed
+ }),
+ pull.map(function (msg) {
+ return msg.value.content.channel
+ }),
+ pull.unique()
+ )
+}
+
+App.prototype.streamMyChannels = function (id, opts) {
+ // use ssb-query plugin if it is available, since it has an index for
+ // author + type
+ if (this.sbot.query) return pull(
+ this.sbot.query.read({
+ reverse: true,
+ query: [
+ {$filter: {
+ value: {
+ author: id,
+ content: {type: 'channel', subscribed: true}
+ }
+ }},
+ {$map: ['value', 'content', 'channel']}
+ ]
+ }),
+ pull.unique()
+ )
+
+ return pull(
+ this.sbot.createUserStream({id: id, reverse: true}),
+ this.unboxMessages(),
+ pull.filter(function (msg) {
+ if (msg.value.content.type == 'channel') {
+ return msg.value.content.subscribed
+ }
+ }),
pull.map(function (msg) {
return msg.value.content.channel
}),
diff --git a/lib/render-msg.js b/lib/render-msg.js
index 931b7a6..55ea863 100644
--- a/lib/render-msg.js
+++ b/lib/render-msg.js
@@ -344,7 +344,7 @@ RenderMsg.prototype.link = function (link, cb) {
var ref = u.linkDest(link)
if (!ref) return cb(null, '')
self.getName(ref, function (err, name) {
- if (err) return cb(err)
+ if (err) name = truncate(ref, 10)
cb(null, h('a', {href: self.toUrl(ref)}, name))
})
}
diff --git a/lib/render.js b/lib/render.js
index bc4ce5b..1cd92e6 100644
--- a/lib/render.js
+++ b/lib/render.js
@@ -76,6 +76,7 @@ Render.prototype.emoji = function (emoji) {
h('img.ssb-emoji', {
src: this.opts.emoji_base + emoji + '.png',
alt: name,
+ height: 17,
title: name,
}) : name
}
@@ -146,7 +147,8 @@ Render.prototype.toUrl = function (href) {
case '&':
if (!u.isRef(href)) return false
return this.opts.blob_base + href
- case '#': return this.opts.base + encodeURIComponent(href)
+ case '#': return this.opts.base + 'channel/' +
+ encodeURIComponent(href.substr(1))
case '/': return this.opts.base + href.substr(1)
case '?': return this.opts.base + 'search?q=' + encodeURIComponent(href)
}
@@ -165,6 +167,7 @@ Render.prototype.avatarImage = function (link, cb) {
if (!link) return cb(), ''
if (typeof link === 'string') link = {link: link}
var img = h('img.ssb-avatar-image', {
+ width: 72,
alt: ' '
})
if (link.image) gotAbout(null, link)
diff --git a/lib/serve.js b/lib/serve.js
index 9296c46..a5c2e48 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -122,10 +122,17 @@ Serve.prototype.go = function () {
else next()
}
- function next(err) {
+ function next(err, publishedMsg) {
if (err) {
self.res.writeHead(400, {'Content-Type': 'text/plain'})
self.res.end(err.stack)
+ } else if (publishedMsg) {
+ if (self.data.redirect_to_published_msg) {
+ self.redirect(self.app.render.toUrl(publishedMsg.key))
+ } else {
+ self.publishedMsg = publishedMsg
+ self.handle()
+ }
} else {
self.handle()
}
@@ -178,8 +185,7 @@ Serve.prototype.publish = function (content, cb) {
if (err) return cb(err)
delete self.data.text
delete self.data.recps
- self.publishedMsg = msg
- return cb()
+ return cb(null, msg)
})
})
}
@@ -209,6 +215,13 @@ Serve.prototype.respondSink = function (status, headers, cb) {
})
}
+Serve.prototype.redirect = function (dest) {
+ this.res.writeHead(302, {
+ Location: dest
+ })
+ this.res.end()
+}
+
Serve.prototype.path = function (url) {
var m
url = url.replace(/^\/+/, '/')
@@ -217,7 +230,8 @@ Serve.prototype.path = function (url) {
case '/robots.txt': return this.res.end('User-agent: *')
}
if (m = /^\/%23(.*)/.exec(url)) {
- return this.channel(decodeURIComponent(m[1]))
+ return this.redirect(this.app.render.toUrl('/channel/'
+ + decodeURIComponent(m[1])))
}
m = /^([^.]*)(?:\.(.*))?$/.exec(url)
switch (m[1]) {
@@ -235,6 +249,7 @@ Serve.prototype.path = function (url) {
}
m = /^(\/?[^\/]*)(\/.*)?$/.exec(url)
switch (m[1]) {
+ case '/channel': return this.channel(m[2])
case '/type': return this.type(m[2])
case '/links': return this.links(m[2])
case '/static': return this.static(m[2])
@@ -364,10 +379,7 @@ Serve.prototype.search = function (ext) {
}
if (u.isRef(searchQ) || searchQ[0] === '#') {
- self.res.writeHead(302, {
- Location: self.app.render.toUrl(searchQ)
- })
- return self.res.end()
+ return self.redirect(self.app.render.toUrl(searchQ))
}
pull(
@@ -462,6 +474,7 @@ Serve.prototype.compose = function (ext) {
var self = this
self.composer({
channel: '',
+ redirectToPublishedMsg: true,
}, function (err, composer) {
if (err) return cb(err)
pull(
@@ -521,20 +534,48 @@ Serve.prototype.peers = function (ext) {
Serve.prototype.channels = function (ext) {
var self = this
+ var id = self.app.sbot.id
+
+ function renderMyChannels() {
+ return pull(
+ self.app.streamMyChannels(id),
+ paramap(function (channel, cb) {
+ // var subscribed = false
+ cb(null, [
+ h('a', {href: self.app.render.toUrl('/channel/' + channel)}, '#' + channel),
+ ' '
+ ])
+ }, 8),
+ pull.map(u.toHTML),
+ self.wrapMyChannels()
+ )
+ }
+
+ function renderNetworkChannels() {
+ return pull(
+ self.app.streamChannels(),
+ paramap(function (channel, cb) {
+ // var subscribed = false
+ cb(null, [
+ h('a', {href: self.app.render.toUrl('/channel/' + channel)}, '#' + channel),
+ ' '
+ ])
+ }, 8),
+ pull.map(u.toHTML),
+ self.wrapChannels()
+ )
+ }
pull(
- self.app.streamChannels(),
- paramap(function (channel, cb) {
- var subscribed = false
- cb(null, [
- h('a', {href: self.app.render.toUrl('#' + channel)}, '#' + channel),
- ' '
+ cat([
+ ph('section', {}, [
+ ph('h3', {}, 'Channels:'),
+ renderMyChannels(),
+ renderNetworkChannels()
])
- }, 8),
- pull.map(u.toHTML),
- self.wrapChannels(),
- self.wrapPage('channels'),
- self.respondSink(200, {
+ ]),
+ this.wrapPage('channels'),
+ this.respondSink(200, {
'Content-Type': ctype(ext)
})
)
@@ -721,7 +762,8 @@ Serve.prototype.rawId = function (id) {
})
}
-Serve.prototype.channel = function (channel) {
+Serve.prototype.channel = function (path) {
+ var channel = decodeURIComponent(String(path).substr(1))
var q = this.query
var gt = Number(q.gt) || -Infinity
var lt = Number(q.lt) || Date.now()
@@ -771,7 +813,8 @@ Serve.prototype.id = function (id, ext) {
if (self.query.raw != null) return self.rawId(id)
this.app.getMsgDecrypted(id, function (err, rootMsg) {
- if (err && err.name === 'NotFoundError') err = null, rootMsg = {key: id}
+ 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
@@ -795,7 +838,9 @@ Serve.prototype.id = function (id, ext) {
self.wrapThread({
recps: recps,
root: threadRootId,
- branches: id === threadRootId ? threadHeads(links, id) : id,
+ post: id,
+ branches: threadHeads(links, threadRootId),
+ postBranches: threadRootId !== id && threadHeads(links, id),
channel: channel,
}),
self.wrapPage(id),
@@ -1041,6 +1086,7 @@ Serve.prototype.wrapPage = function (title, searchQ) {
h('meta', {charset: 'utf-8'}),
h('title', title),
h('meta', {name: 'viewport', content: 'width=device-width,initial-scale=1'}),
+ h('link', {rel: 'icon', href: render.toUrl('/static/hermie.ico'), type: 'image/x-icon'}),
h('style', styles())
),
h('body',
@@ -1233,8 +1279,10 @@ Serve.prototype.wrapThread = function (opts) {
placeholder: recps ? 'private reply' : 'reply',
id: 'reply',
root: opts.root,
+ post: opts.post,
channel: opts.channel || '',
branches: opts.branches,
+ postBranches: opts.postBranches,
recps: recps,
}, function (err, composer) {
if (err) return cb(err)
@@ -1282,7 +1330,7 @@ Serve.prototype.wrapChannel = function (channel) {
cb(null, [
h('section',
h('h3.feed-name',
- h('a', {href: self.app.render.toUrl('#' + channel)}, '#' + channel)
+ h('a', {href: self.app.render.toUrl('/channel/' + channel)}, '#' + channel)
)
),
composer,
@@ -1337,7 +1385,21 @@ Serve.prototype.wrapChannels = function (opts) {
return u.hyperwrap(function (channels, cb) {
cb(null, [
h('section',
- h('h3', 'Channels')
+ h('h4', 'Network')
+ ),
+ h('section',
+ channels
+ )
+ ])
+ })
+}
+
+Serve.prototype.wrapMyChannels = function (opts) {
+ var self = this
+ return u.hyperwrap(function (channels, cb) {
+ cb(null, [
+ h('section',
+ h('h4', 'Subscribed')
),
h('section',
channels
@@ -1414,6 +1476,12 @@ Serve.prototype.composer = function (opts, cb) {
channel != null ?
h('div', '#', h('input', {name: 'channel', placeholder: 'channel',
value: channel})) : '',
+ opts.root !== opts.post ? h('div',
+ h('label', {for: 'fork_thread'},
+ h('input', {id: 'fork_thread', type: 'checkbox', name: 'fork_thread', value: 'post', checked: data.fork_thread || undefined}),
+ ' fork thread'
+ )
+ ) : '',
h('textarea', {
id: opts.id,
name: 'text',
@@ -1428,7 +1496,7 @@ Serve.prototype.composer = function (opts, cb) {
h('code', '@' + mention.name), ': ',
h('input', {name: 'mention_name', type: 'hidden',
value: mention.name}),
- h('input.id-input', {name: 'mention_id',
+ h('input.id-input', {name: 'mention_id', size: 60,
value: mention.id, placeholder: 'id'}))
}))
] : '',
@@ -1487,8 +1555,13 @@ Serve.prototype.composer = function (opts, cb) {
} else {
if (opts.recps) content.recps = opts.recps
}
- if (opts.root) content.root = opts.root
- if (opts.branches) content.branch = u.fromArray(opts.branches)
+ if (data.fork_thread) {
+ content.root = opts.post || undefined
+ content.branch = u.fromArray(opts.postBranches) || undefined
+ } else {
+ content.root = opts.root || undefined
+ content.branch = u.fromArray(opts.branches) || undefined
+ }
if (channel) content.channel = data.channel
}
var msg = {
@@ -1511,6 +1584,8 @@ Serve.prototype.composer = function (opts, cb) {
return [
h('input', {type: 'hidden', name: 'content',
value: JSON.stringify(content)}),
+ opts.redirectToPublishedMsg ? h('input', {type: 'hidden',
+ name: 'redirect_to_published_msg', value: '1'}) : '',
h('div', h('em', 'draft:')),
msgContainer,
h('div.composer-actions',
diff --git a/lib/util.js b/lib/util.js
index 2c0cf03..5546716 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -6,6 +6,7 @@ var u = exports
u.ssbRefRegex = /((?:@|%|&|ssb:\/\/%)[A-Za-z0-9\/+]{43}=\.[\w\d]+)/g
u.isRef = function (str) {
+ if (!str) return false
u.ssbRefRegex.lastIndex = 0
return u.ssbRefRegex.test(str)
}
@@ -38,7 +39,7 @@ u.toHTML = function (el) {
return h('div', el).innerHTML
}
var html = el.outerHTML || String(el)
- if (el.nodeName === 'html') html = '<!doctype html><link rel="icon" href="static/hermie.ico" type="image/x-icon"/>' + html + '\n'
+ if (el.nodeName === 'html') html = '<!doctype html>' + html + '\n'
return html
}