git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: f80296e49e5bd1b20ac5c2df3b224d7b61c2d43d

Files: f80296e49e5bd1b20ac5c2df3b224d7b61c2d43d / modules / feed / html / rollup.js

9338 bytesRaw
1var Value = require('mutant/value')
2var Proxy = require('mutant/proxy')
3var when = require('mutant/when')
4var computed = require('mutant/computed')
5var h = require('mutant/h')
6var MutantArray = require('mutant/array')
7var Abortable = require('pull-abortable')
8var map = require('mutant/map')
9var pull = require('pull-stream')
10var nest = require('depnest')
11
12var onceTrue = require('mutant/once-true')
13var Scroller = require('../../../lib/pull-scroll')
14
15exports.needs = nest({
16 'message.html': {
17 render: 'first',
18 link: 'first'
19 },
20 'app.sync.externalHandler': 'first',
21 'sbot.async.get': 'first',
22 'keys.sync.id': 'first',
23 'about.obs.name': 'first',
24 feed: {
25 'html.rollup': 'first',
26 'pull.summary': 'first'
27 },
28 profile: {
29 'html.person': 'first'
30 }
31})
32
33exports.gives = nest({
34 'feed.html': ['rollup']
35})
36
37exports.create = function (api) {
38 return nest({
39 'feed.html': { rollup }
40 })
41 function rollup (getStream, opts) {
42 var loading = Proxy(true)
43 var updates = Value(0)
44
45 var filter = opts && opts.filter
46 var bumpFilter = opts && opts.bumpFilter
47 var windowSize = opts && opts.windowSize
48 var waitFor = opts && opts.waitFor || true
49
50 var newSinceRefresh = new Set()
51 var newInSession = new Set()
52 var prioritized = {}
53
54 var updateLoader = h('a Notifier -loader', {
55 href: '#',
56 'ev-click': refresh
57 }, [
58 'Show ',
59 h('strong', [updates]), ' ',
60 when(computed(updates, a => a === 1), 'update', 'updates')
61 ])
62
63 var content = Value()
64
65 var container = h('Scroller', {
66 style: { overflow: 'auto' }
67 }, [
68 h('div.wrapper', [
69 h('section.prepend', opts.prepend),
70 content,
71 when(loading, h('Loading -large'))
72 ])
73 ])
74
75 onceTrue(waitFor, () => {
76 refresh()
77 pull(
78 getStream({old: false}),
79 pull.drain((item) => {
80 var type = item && item.value && item.value.content.type
81
82 // prioritize new messages on next refresh
83 newInSession.add(item.key)
84 newSinceRefresh.add(item.key)
85
86 // ignore message handled by another app
87 if (api.app.sync.externalHandler(item)) return
88
89 if (type && type !== 'vote' && typeof item.value.content === 'object' && item.value.timestamp > twoDaysAgo()) {
90 if (item.value && item.value.author === api.keys.sync.id() && !updates()) {
91 return refresh()
92 }
93 if (filter) {
94 if (item.value.content.type === 'post') {
95 var update = (item.value.content.root) ? {
96 type: 'message',
97 messageId: item.value.content.root,
98 channel: item.value.content.channel
99 } : {
100 type: 'message',
101 author: item.value.author,
102 channel: item.value.content.channel,
103 messageId: item.key
104 }
105
106 ensureMessageAndAuthor(update, (err, update) => {
107 if (!err) {
108 if (filter(update)) {
109 updates.set(updates() + 1)
110 }
111 }
112 })
113 }
114 } else {
115 updates.set(updates() + 1)
116 }
117 }
118 })
119 )
120 })
121
122 var abortLastFeed = null
123
124 var result = MutantArray([
125 when(updates, updateLoader),
126 container
127 ])
128
129 result.reload = refresh
130 result.pendingUpdates = updates
131
132 return result
133
134 // scoped
135
136 function refresh () {
137 if (abortLastFeed) {
138 abortLastFeed()
139 }
140 updates.set(0)
141
142 content.set(
143 h('section.content')
144 )
145
146 var abortable = Abortable()
147 abortLastFeed = abortable.abort
148
149 prioritized = {}
150 newSinceRefresh.forEach(x => {
151 prioritized[x] = 2
152 })
153
154 var stream = api.feed.pull.summary(getStream, {windowSize, bumpFilter, prioritized})
155 loading.set(stream.loading)
156
157 pull(
158 stream,
159 pull.asyncMap(ensureMessageAndAuthor),
160 pull.filter((item) => {
161 // ignore messages that are handled by other apps
162 if (item.rootMessage && api.app.sync.externalHandler(item.rootMessage)) return
163 if (filter) {
164 return filter(item)
165 } else {
166 return true
167 }
168 }),
169 abortable,
170 Scroller(container, content(), renderItem, false, false)
171 )
172
173 // clear high prioritized items
174 newSinceRefresh.clear()
175 }
176
177 function renderItem (item) {
178 var classList = []
179 if (item.priority >= 2) {
180 classList.push('-new')
181 }
182
183 if (item.type === 'message') {
184 var meta = null
185 var previousId = item.messageId
186 var replies = item.replies.slice(-4).map((msg) => {
187 var result = api.message.html.render(msg, {
188 inContext: true,
189 inSummary: true,
190 previousId,
191 priority: prioritized[msg.key]
192 })
193 previousId = msg.key
194 return result
195 })
196 var renderedMessage = item.message ? api.message.html.render(item.message, {inContext: true}) : null
197 if (renderedMessage) {
198 if (item.lastUpdateType === 'reply' && item.repliesFrom.size) {
199 meta = h('div.meta', {
200 title: names(item.repliesFrom)
201 }, [
202 many(item.repliesFrom, api.profile.html.person), ' replied'
203 ])
204 } else if (item.lastUpdateType === 'like' && item.likes.size) {
205 meta = h('div.meta', {
206 title: names(item.likes)
207 }, [
208 many(item.likes, api.profile.html.person), ' liked this message'
209 ])
210 }
211
212 return h('FeedEvent', [
213 meta,
214 renderedMessage,
215 when(replies.length, [
216 when(item.replies.length > replies.length || opts.partial,
217 h('a.full', {href: item.messageId}, ['View full thread'])
218 ),
219 h('div.replies', replies)
220 ])
221 ])
222 } else {
223 // when there is no root message in this window,
224 // try and show reply message, only show like message if we have nothing else to give
225 if (item.repliesFrom.size) {
226 meta = h('div.meta', {
227 title: names(item.repliesFrom)
228 }, [
229 many(item.repliesFrom, api.profile.html.person), ' replied to ', api.message.html.link(item.messageId)
230 ])
231 } else if (item.lastUpdateType === 'like' && item.likes.size) {
232 meta = h('div.meta', {
233 title: names(item.likes)
234 }, [
235 many(item.likes, api.profile.html.person), ' liked ', api.message.html.link(item.messageId)
236 ])
237 }
238
239 // only show this event if it has a meta description
240 if (meta) {
241 return h('FeedEvent', [
242 meta, h('div.replies', replies)
243 ])
244 }
245 }
246 } else if (item.type === 'follow') {
247 return h('FeedEvent -follow', {classList}, [
248 h('div.meta', {
249 title: names(item.contacts)
250 }, [
251 api.profile.html.person(item.id), ' followed ', many(item.contacts, api.profile.html.person)
252 ])
253 ])
254 } else if (item.type === 'subscribe') {
255 return h('FeedEvent -subscribe', {classList}, [
256 h('div.meta', {
257 title: names(item.subscribers)
258 }, [
259 many(item.subscribers, api.profile.html.person),
260 ' subscribed to ',
261 h('a', {href: `#${item.channel}`}, `#${item.channel}`)
262 ])
263 ])
264 }
265
266 return h('div')
267 }
268 }
269
270 function ensureMessageAndAuthor (item, cb) {
271 if (item.type === 'message' && !item.rootMessage) {
272 if (item.message) {
273 item.rootMessage = item.message
274 cb(null, item)
275 } else {
276 api.sbot.async.get(item.messageId, (_, value) => {
277 if (value) {
278 item.author = value.author
279 item.rootMessage = {key: item.messageId, value}
280 }
281 cb(null, item)
282 })
283 }
284 } else {
285 cb(null, item)
286 }
287 }
288
289 function names (ids) {
290 var items = map(Array.from(ids), api.about.obs.name)
291 return computed([items], (names) => names.map((n) => `- ${n}`).join('\n'))
292 }
293}
294
295function twoDaysAgo () {
296 return Date.now() - (2 * 24 * 60 * 60 * 1000)
297}
298
299function many (ids, fn) {
300 ids = Array.from(ids)
301 var featuredIds = ids.slice(-4).reverse()
302
303 if (ids.length) {
304 if (ids.length > 4) {
305 return [
306 fn(featuredIds[0]), ', ',
307 fn(featuredIds[1]), ', ',
308 fn(featuredIds[2]), ' and ',
309 ids.length - 3, ' others'
310 ]
311 } else if (ids.length === 4) {
312 return [
313 fn(featuredIds[0]), ', ',
314 fn(featuredIds[1]), ', ',
315 fn(featuredIds[2]), ' and ',
316 fn(featuredIds[3])
317 ]
318 } else if (ids.length === 3) {
319 return [
320 fn(featuredIds[0]), ', ',
321 fn(featuredIds[1]), ' and ',
322 fn(featuredIds[2])
323 ]
324 } else if (ids.length === 2) {
325 return [
326 fn(featuredIds[0]), ' and ',
327 fn(featuredIds[1])
328 ]
329 } else {
330 return fn(featuredIds[0])
331 }
332 }
333}
334

Built with git-ssb-web