Files: cb2a914cb1b67be199e798ca44039444c43341fc / thread.js
5563 bytesRaw
1 | var pull = require('pull-stream') |
2 | var ref = require('ssb-ref') |
3 | var msum = require('markdown-summary') |
4 | var sort = require('ssb-sort') |
5 | var u = require('yap-util') |
6 | |
7 | var toUrl = u.toUrl |
8 | |
9 | function uniqueRecps (recps) { |
10 | if(!recps || !recps.length) return |
11 | recps = recps.map(function (e) { |
12 | return 'string' === typeof e ? e : e.link |
13 | }) |
14 | .filter(Boolean) |
15 | return recps.filter(function (id, i) { |
16 | return !~recps.indexOf(id, i+1) |
17 | }) |
18 | } |
19 | |
20 | function getThread(sbot, id, cb) { |
21 | pull( |
22 | sbot.query.read({ |
23 | //hack so that ssb-query filters on post but uses |
24 | //indexes for root. |
25 | query: [{$filter: { |
26 | value: { content: {root: id} } |
27 | }},{$filter: { |
28 | value: { content: {type: 'post'} } |
29 | }}] |
30 | }), |
31 | pull.collect(function (err, ary) { |
32 | if(err) return cb(err) |
33 | cb(null, ary) |
34 | }) |
35 | ) |
36 | } |
37 | |
38 | function isObject(o) { |
39 | return o && 'object' === typeof o |
40 | } |
41 | var isArray = Array.isArray |
42 | |
43 | function backlinks (sbot, id, cb) { |
44 | var likes = [], backlinks = [] |
45 | pull( |
46 | sbot.links({dest: id, values: true}), |
47 | pull.drain(function (e) { |
48 | var content = e.value.content |
49 | var vote = content.vote |
50 | if(isObject(vote) && |
51 | vote.value == 1 && vote.link == id) |
52 | likes.push(e) |
53 | else if(content.type == 'post' && isArray(content.mentions)) { |
54 | for(var i in content.mentions) { |
55 | var m = content.mentions[i] |
56 | if(m && m.link == id) { |
57 | backlinks.push(e) |
58 | return //if something links twice, don't back link it twice |
59 | } |
60 | } |
61 | } |
62 | }, function () { |
63 | cb(null, likes, backlinks) |
64 | }) |
65 | ) |
66 | } |
67 | |
68 | |
69 | module.exports = function (sbot) { |
70 | return function (opts, apply, req) { |
71 | var context = req.cookies |
72 | var since = apply.since |
73 | var tr = require('./translations')(context.lang) |
74 | return function (cb) { |
75 | var cacheTime = 0 |
76 | if(!ref.isMsg(opts.id)) |
77 | return cb(new Error('expected valid msg id as id')) |
78 | sbot.get({id:opts.id, private: true}, function (err, msg) { |
79 | if(err) return cb(err) |
80 | var data = {key: opts.id, value: msg, timestamp: msg.timestamp || Date.now() } |
81 | if(data.value.content.root) |
82 | cb(null, apply('message', data)) //just show one message |
83 | else if(data.value.content.type != 'post') |
84 | cb(null, apply('message', data)) //just show one message |
85 | else |
86 | getThread(sbot, opts.id, function (err, ary) { |
87 | var root = opts.id |
88 | ary = [data].concat(ary || []) |
89 | var branch = sort.heads(ary) |
90 | var recps = uniqueRecps(ary[0].value.content.recps) |
91 | |
92 | ary.unshift(data) |
93 | var o = {}, cacheTime |
94 | ary = ary.filter(function (e) { |
95 | if(o[e.key]) return false |
96 | return o[e.key] = true |
97 | }) |
98 | sort(ary) |
99 | var recipients = ' ' |
100 | if(ary[0].value.content.recps) |
101 | recipients = ['div.Recipients', tr('ThreadRecipients'), |
102 | ary[0].value.content.recps.map(function (e) { |
103 | return apply('avatar', e) |
104 | })] |
105 | cb(null, |
106 | ['div.thread', |
107 | apply.cacheAttrs(apply.toUrl('thread', opts), data.key, since), |
108 | ary[0].value.content.text && ['title', msum.title(ary[0].value.content.text)], |
109 | recipients, |
110 | ary.map(function (data) { |
111 | return ['div.MessageContainer', |
112 | apply('message', data), |
113 | function (cb) { |
114 | backlinks(sbot, data.key, function (err, likes, backlinks) { |
115 | if(err) return cb(err) |
116 | |
117 | var expression = tr('Like') |
118 | cb(null, ['div.MessageExtra', |
119 | apply('publish', { |
120 | id: context.id, |
121 | suggestedRecps: data.value.author, |
122 | content: { |
123 | type: 'vote', |
124 | root: root, branch: branch, |
125 | vote: { |
126 | link:data.key, value: 1, |
127 | expression: expression |
128 | }, |
129 | channel: data.value.content.channel, |
130 | recps: recps |
131 | }, |
132 | name: expression + ' ' + (likes.length ? '('+likes.length+')' : '') |
133 | }), |
134 | (backlinks.length ? |
135 | ['ul.MessageBacklinks', |
136 | backlinks.map(function (e) { |
137 | return ['li', apply('avatar', {id:e.value.author}), |
138 | ' ', |
139 | apply('/patch/messageLink', e), |
140 | ' ', |
141 | e.value.content.channel && apply('/patch/channelLink', e.value.content.channel) |
142 | ] |
143 | }) |
144 | ] : '') |
145 | ]) |
146 | }) |
147 | } |
148 | ] |
149 | }), |
150 | apply('compose', { |
151 | content: { |
152 | type: 'post', |
153 | root: root, |
154 | recps: recps, |
155 | branch: branch, |
156 | channel: ary[0].value.content.channel |
157 | } |
158 | }) |
159 | ] |
160 | ) |
161 | }) |
162 | }) |
163 | } |
164 | } |
165 | } |
166 |
Built with git-ssb-web