aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2017-05-09 15:17:46 -1000
committercel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2017-05-09 20:22:20 -1000
commitd8f6c6a19274cf53d9967d3597dd582ee7233ca3 (patch)
tree5bce370970e849da86c48774753e69ef8fad635f
parent16a3027dc258d934ce38f4ab143c41246a5c1862 (diff)
downloadpatchfoo-d8f6c6a19274cf53d9967d3597dd582ee7233ca3.tar.gz
patchfoo-d8f6c6a19274cf53d9967d3597dd582ee7233ca3.zip
Add votes page
-rw-r--r--lib/app.js60
-rw-r--r--lib/serve.js86
2 files changed, 146 insertions, 0 deletions
diff --git a/lib/app.js b/lib/app.js
index b57e5a6..e503443 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -285,3 +285,63 @@ App.prototype.streamChannels = function (opts) {
App.prototype.createContactStreams = function (id) {
return new Contacts(this.sbot).createContactStreams(id)
}
+
+function compareVoted(a, b) {
+ return b.value - a.value
+}
+
+App.prototype.getVoted = function (_opts, cb) {
+ if (isNaN(_opts.limit)) return pull.error(new Error('missing limit'))
+ var self = this
+ var opts = {
+ type: 'vote',
+ limit: _opts.limit * 100,
+ reverse: !!_opts.reverse,
+ gt: _opts.gt || undefined,
+ lt: _opts.lt || undefined,
+ }
+
+ var votedObj = {}
+ var votedArray = []
+ var numItems = 0
+ var firstTimestamp, lastTimestamp
+ pull(
+ self.sbot.messagesByType(opts),
+ self.unboxMessages(),
+ pull.take(function () {
+ return numItems < _opts.limit
+ }),
+ pull.drain(function (msg) {
+ if (!firstTimestamp) firstTimestamp = msg.timestamp
+ lastTimestamp = msg.timestamp
+ var vote = msg.value.content.vote
+ if (!vote) return
+ var target = u.linkDest(vote)
+ var votes = votedObj[target]
+ if (!votes) {
+ numItems++
+ votes = {id: target, value: 0, feedsObj: {}, feeds: []}
+ votedObj[target] = votes
+ votedArray.push(votes)
+ }
+ if (msg.value.author in votes.feedsObj) {
+ if (!opts.reverse) return // leave latest vote value as-is
+ // remove old vote value
+ votes.value -= votes.feedsObj[msg.value.author]
+ } else {
+ votes.feeds.push(msg.value.author)
+ }
+ var value = vote.value > 0 ? 1 : vote.value < 0 ? -1 : 0
+ votes.feedsObj[msg.value.author] = value
+ votes.value += value
+ }, function (err) {
+ if (err) return cb(err)
+ var items = votedArray
+ if (opts.reverse) items.reverse()
+ items.sort(compareVoted)
+ cb(null, {items: items,
+ firstTimestamp: firstTimestamp,
+ lastTimestamp: lastTimestamp})
+ })
+ )
+}
diff --git a/lib/serve.js b/lib/serve.js
index 002c520..2f6caab 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -256,6 +256,7 @@ Serve.prototype.path = function (url) {
case '/friends': return this.friends(m[2])
case '/live': return this.live(m[2])
case '/compose': return this.compose(m[2])
+ case '/votes': return this.votes(m[2])
}
m = /^(\/?[^\/]*)(\/.*)?$/.exec(url)
switch (m[1]) {
@@ -496,6 +497,90 @@ Serve.prototype.compose = function (ext) {
})
}
+Serve.prototype.votes = function (path) {
+ if (path) return pull(
+ pull.once(u.renderError(new Error('Not implemented')).outerHTML),
+ this.wrapPage('#' + channel),
+ this.respondSink(404, {'Content-Type': ctype('html')})
+ )
+
+ var self = this
+ var q = self.query
+ var opts = {
+ reverse: !q.forwards,
+ limit: Number(q.limit) || 50,
+ }
+ var gt = Number(q.gt)
+ if (gt) opts.gt = gt
+ var lt = Number(q.lt)
+ if (lt) opts.lt = lt
+
+ self.app.getVoted(opts, function (err, voted) {
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('#' + channel),
+ self.respondSink(500, {'Content-Type': ctype('html')})
+ )
+
+ pull(
+ ph('table', [
+ ph('thead', [
+ ph('tr', [
+ ph('td', {colspan: 2}, self.syncPager({
+ first: voted.firstTimestamp,
+ last: voted.lastTimestamp,
+ }))
+ ])
+ ]),
+ ph('tbody', pull(
+ pull.values(voted.items),
+ paramap(function (item, cb) {
+ cb(null, ph('tr', [
+ ph('td', [String(item.value)]),
+ ph('td', [
+ self.pullIdLink(item.id),
+ pull.once(' dug by '),
+ self.renderIdsList()(pull.values(item.feeds))
+ ])
+ ]))
+ }, 8)
+ )),
+ ph('tfoot', {}, []),
+ ]),
+ self.wrapPage('votes'),
+ self.respondSink(200, {
+ 'Content-Type': ctype('html')
+ })
+ )
+ })
+}
+
+Serve.prototype.syncPager = function (opts) {
+ var q = this.query
+ var reverse = !q.forwards
+ var min = (reverse ? opts.last : opts.first) || Number(q.gt)
+ var max = (reverse ? opts.first : opts.last) || Number(q.lt)
+ var minDate = new Date(min)
+ var maxDate = new Date(max)
+ var qOlder = mergeOpts(q, {lt: min, gt: undefined, forwards: undefined})
+ var qNewer = mergeOpts(q, {gt: max, lt: undefined, forwards: 1})
+ var atNewest = reverse ? !q.lt : !max
+ var atOldest = reverse ? !min : !q.gt
+ if (atNewest && !reverse) qOlder.lt++
+ if (atOldest && reverse) qNewer.gt--
+ return h('div',
+ atOldest ? 'oldest' : [
+ h('a', {href: '?' + qs.stringify(qOlder)}, '<<'), ' ',
+ h('span', {title: minDate.toString()}, htime(minDate)), ' ',
+ ],
+ ' - ',
+ atNewest ? 'now' : [
+ h('span', {title: maxDate.toString()}, htime(maxDate)), ' ',
+ h('a', {href: '?' + qs.stringify(qNewer)}, '>>')
+ ]
+ ).outerHTML
+}
+
Serve.prototype.peers = function (ext) {
var self = this
if (self.data.action === 'connect') {
@@ -1002,6 +1087,7 @@ Serve.prototype.wrapPage = function (title, searchQ) {
h('a', {href: render.toUrl('/advsearch')}, 'search'), ' ',
h('a', {href: render.toUrl('/live')}, 'live'), ' ',
h('a', {href: render.toUrl('/compose')}, 'compose'), ' ',
+ h('a', {href: render.toUrl('/votes')}, 'votes'), ' ',
render.idLink(self.app.sbot.id, done()), ' ',
h('input.search-input', {name: 'q', value: searchQ,
placeholder: 'search'})