Commit 2150c96ae49e67fdcb638a88455af2c3ae8c9c53
Merge branch 'new-rollup'
# Conflicts: # package.jsonMatt McKegg committed on 6/20/2017, 4:43:36 PM
Parent: c4b41ff3f5a9235cc76395794ae0d413ab7c9eca
Parent: 0900401aa912baeaca5a68feb70f4847503c6c70
Files changed
lib/pull-scroll.js | ||
---|---|---|
@@ -2,9 +2,9 @@ | ||
2 | 2 … | // should really PR this, but Dominic might not accept it :D |
3 | 3 … | |
4 | 4 … | var pull = require('pull-stream') |
5 | 5 … | var Pause = require('pull-pause') |
6 | -var Obv = require('obv') | |
6 … | +var Value = require('mutant/value') | |
7 | 7 … | |
8 | 8 … | var next = 'undefined' === typeof setImmediate ? setTimeout : setImmediate |
9 | 9 … | var buffer = Math.max(window.innerHeight * 2, 1000) |
10 | 10 … | |
@@ -17,9 +17,9 @@ | ||
17 | 17 … | |
18 | 18 … | |
19 | 19 … | function Scroller(scroller, content, render, isPrepend, isSticky, cb) { |
20 | 20 … | assertScrollable(scroller) |
21 | - var obv = Obv() | |
21 … | + var obs = Value(0) | |
22 | 22 … | |
23 | 23 … | //if second argument is a function, |
24 | 24 … | //it means the scroller and content elements are the same. |
25 | 25 … | if('function' === typeof content) { |
@@ -42,9 +42,9 @@ | ||
42 | 42 … | if(queue.length) { |
43 | 43 … | var m = queue.shift() |
44 | 44 … | var r = render(m) |
45 | 45 … | append(scroller, content, r, isPrepend, isSticky) |
46 | - obv.set(queue.length) | |
46 … | + obs.set(queue.length) | |
47 | 47 … | } |
48 | 48 … | } |
49 | 49 … | |
50 | 50 … | function scroll (ev) { |
@@ -64,9 +64,9 @@ | ||
64 | 64 … | var stream = pull( |
65 | 65 … | pause, |
66 | 66 … | pull.drain(function (e) { |
67 | 67 … | queue.push(e) |
68 | - obv.set(queue.length) | |
68 … | + obs.set(queue.length) | |
69 | 69 … | |
70 | 70 … | if(content.clientHeight < window.innerHeight) |
71 | 71 … | add() |
72 | 72 … | |
@@ -74,19 +74,20 @@ | ||
74 | 74 … | if (isEnd(scroller, buffer, isPrepend)) |
75 | 75 … | add() |
76 | 76 … | } |
77 | 77 … | |
78 | - if(queue.length > 5) | |
78 … | + if(queue.length > 5) { | |
79 | 79 … | pause.pause() |
80 … | + } | |
80 | 81 … | |
81 | 82 … | }, function (err) { |
82 | 83 … | if(err) console.error(err) |
83 | 84 … | cb ? cb(err) : console.error(err) |
84 | 85 … | }) |
85 | 86 … | ) |
86 | 87 … | |
87 | 88 … | stream.visible = add |
88 | - stream.observ = obv | |
89 … | + stream.queue = obs | |
89 | 90 … | return stream |
90 | 91 … | } |
91 | 92 … | |
92 | 93 … |
lib/flumeview-channels.js | ||
---|---|---|
@@ -1,0 +1,39 @@ | ||
1 … | +var FlumeReduce = require('flumeview-reduce') | |
2 … | + | |
3 … | +exports.name = 'channels' | |
4 … | +exports.version = require('../package.json').version | |
5 … | +exports.manifest = { | |
6 … | + stream: 'source', | |
7 … | + get: 'async' | |
8 … | +} | |
9 … | + | |
10 … | +exports.init = function (ssb, config) { | |
11 … | + return ssb._flumeUse('channels', FlumeReduce(1, reduce, map)) | |
12 … | +} | |
13 … | + | |
14 … | +function reduce (result, item) { | |
15 … | + if (!result) result = {} | |
16 … | + if (item) { | |
17 … | + var value = result[item.channel] | |
18 … | + if (!value) { | |
19 … | + value = result[item.channel] = {count: 0, timestamp: 0} | |
20 … | + } | |
21 … | + value.count += 1 | |
22 … | + if (item.timestamp > value.timestamp) { | |
23 … | + value.timestamp = item.timestamp | |
24 … | + } | |
25 … | + } | |
26 … | + return result | |
27 … | +} | |
28 … | + | |
29 … | +function map (msg) { | |
30 … | + if (msg.value.content && typeof msg.value.content.channel === 'string') { | |
31 … | + var channel = msg.value.content.channel | |
32 … | + if (channel.length > 0 && channel.length < 30) { | |
33 … | + return { | |
34 … | + channel: channel.replace(/\s/g, ''), | |
35 … | + timestamp: msg.timestamp | |
36 … | + } | |
37 … | + } | |
38 … | + } | |
39 … | +} |
lib/next-stepper.js | ||
---|---|---|
@@ -1,0 +1,56 @@ | ||
1 … | +const pull = require('pull-stream') | |
2 … | +const Next = require('pull-next') | |
3 … | + | |
4 … | +module.exports = nextStepper | |
5 … | + | |
6 … | +// TODO - this should be another module? | |
7 … | + | |
8 … | +function nextStepper (createStream, opts, range) { | |
9 … | + range = range || (opts.reverse ? 'lt' : 'gt') | |
10 … | + | |
11 … | + var last = null | |
12 … | + var count = -1 | |
13 … | + | |
14 … | + return Next(function () { | |
15 … | + if (last) { | |
16 … | + if (count === 0) return | |
17 … | + var value = opts[range] = last | |
18 … | + if (value == null) return | |
19 … | + last = null | |
20 … | + } | |
21 … | + return pull( | |
22 … | + createStream(clone(opts)), | |
23 … | + pull.through(function (msg) { | |
24 … | + count++ | |
25 … | + if (!msg.sync) { | |
26 … | + last = msg | |
27 … | + } | |
28 … | + }, function (err) { | |
29 … | + // retry on errors... | |
30 … | + if (err) { | |
31 … | + count = -1 | |
32 … | + return count | |
33 … | + } | |
34 … | + // end stream if there were no results | |
35 … | + if (last == null) last = {} | |
36 … | + }) | |
37 … | + ) | |
38 … | + }) | |
39 … | +} | |
40 … | + | |
41 … | +function get (obj, path) { | |
42 … | + if (!obj) return undefined | |
43 … | + if (typeof path === 'string') return obj[path] | |
44 … | + if (Array.isArray(path)) { | |
45 … | + for (var i = 0; obj && i < path.length; i++) { | |
46 … | + obj = obj[path[i]] | |
47 … | + } | |
48 … | + return obj | |
49 … | + } | |
50 … | +} | |
51 … | + | |
52 … | +function clone (obj) { | |
53 … | + var _obj = {} | |
54 … | + for (var k in obj) _obj[k] = obj[k] | |
55 … | + return _obj | |
56 … | +} |
main-window.js | ||
---|---|---|
@@ -68,9 +68,8 @@ | ||
68 | 68 … | watch(pendingCount, count => { |
69 | 69 … | electron.remote.app.setBadgeCount(count) |
70 | 70 … | }) |
71 | 71 … | |
72 | - | |
73 | 72 … | insertCss(require('./styles')) |
74 | 73 … | |
75 | 74 … | var container = h(`MainWindow -${process.platform}`, [ |
76 | 75 … | h('div.top', [ |
modules/feed/html/rollup.js | ||
---|---|---|
@@ -1,306 +1,223 @@ | ||
1 | -var Value = require('mutant/value') | |
2 | -var Proxy = require('mutant/proxy') | |
3 | -var when = require('mutant/when') | |
4 | -var computed = require('mutant/computed') | |
5 | -var h = require('mutant/h') | |
6 | -var MutantArray = require('mutant/array') | |
1 … | +var nest = require('depnest') | |
2 … | +var {Value, Proxy, Array: MutantArray, h, computed, map, when, onceTrue, throttle} = require('mutant') | |
3 … | +var pull = require('pull-stream') | |
7 | 4 … | var Abortable = require('pull-abortable') |
8 | -var map = require('mutant/map') | |
9 | -var pull = require('pull-stream') | |
10 | -var nest = require('depnest') | |
11 | - | |
12 | -var onceTrue = require('mutant/once-true') | |
13 | 5 … | var Scroller = require('../../../lib/pull-scroll') |
6 … | +var nextStepper = require('../../../lib/next-stepper') | |
7 … | +var extend = require('xtend') | |
8 … | +var paramap = require('pull-paramap') | |
14 | 9 … | |
10 … | +var bumpMessages = { | |
11 … | + 'vote': 'liked this message', | |
12 … | + 'post': 'replied to this message', | |
13 … | + 'about': 'added changes', | |
14 … | + 'mention': 'mentioned you' | |
15 … | +} | |
16 … | + | |
15 | 17 … | exports.needs = nest({ |
16 | - 'message.html': { | |
17 | - render: 'first', | |
18 | - link: 'first' | |
19 | - }, | |
18 … | + 'about.obs.name': 'first', | |
20 | 19 … | 'app.sync.externalHandler': 'first', |
20 … | + 'message.html.render': 'first', | |
21 … | + 'profile.html.person': 'first', | |
22 … | + 'message.html.link': 'first', | |
23 … | + 'message.sync.root': 'first', | |
24 … | + 'feed.pull.rollup': 'first', | |
21 | 25 … | '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 | - } | |
26 … | + 'keys.sync.id': 'first' | |
31 | 27 … | }) |
32 | 28 … | |
33 | 29 … | exports.gives = nest({ |
34 | - 'feed.html': ['rollup'] | |
30 … | + 'feed.html.rollup': true | |
35 | 31 … | }) |
36 | 32 … | |
37 | 33 … | exports.create = function (api) { |
38 | - return nest({ | |
39 | - 'feed.html': { rollup } | |
40 | - }) | |
41 | - function rollup (getStream, opts) { | |
42 | - var loading = Proxy(true) | |
34 … | + return nest('feed.html.rollup', function (getStream, { | |
35 … | + prepend, | |
36 … | + rootFilter = returnTrue, | |
37 … | + bumpFilter = returnTrue, | |
38 … | + displayFilter = returnTrue, | |
39 … | + waitFor = true | |
40 … | + }) { | |
43 | 41 … | 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 | - var autoRefresh = opts && opts.autoRefresh | |
50 | - | |
51 | - var newSinceRefresh = new Set() | |
52 | - var newInSession = new Set() | |
53 | - var prioritized = {} | |
54 | - | |
55 | - var updateLoader = h('a Notifier -loader', { | |
56 | - href: '#', | |
57 | - 'ev-click': refresh | |
58 | - }, [ | |
59 | - 'Show ', | |
60 | - h('strong', [updates]), ' ', | |
61 | - when(computed(updates, a => a === 1), 'update', 'updates') | |
42 … | + var yourId = api.keys.sync.id() | |
43 … | + var throttledUpdates = throttle(updates, 200) | |
44 … | + var updateLoader = h('a Notifier -loader', { href: '#', 'ev-click': refresh }, [ | |
45 … | + 'Show ', h('strong', [throttledUpdates]), ' ', plural(throttledUpdates, 'update', 'updates') | |
62 | 46 … | ]) |
63 | 47 … | |
48 … | + var abortLastFeed = null | |
64 | 49 … | var content = Value() |
50 … | + var loading = Proxy(true) | |
51 … | + var newSinceRefresh = new Set() | |
52 … | + var highlightItems = new Set() | |
65 | 53 … | |
66 | 54 … | var container = h('Scroller', { |
67 | 55 … | style: { overflow: 'auto' } |
68 | 56 … | }, [ |
69 | 57 … | h('div.wrapper', [ |
70 | - h('section.prepend', opts.prepend), | |
58 … | + h('section.prepend', prepend), | |
71 | 59 … | content, |
72 | 60 … | when(loading, h('Loading -large')) |
73 | 61 … | ]) |
74 | 62 … | ]) |
75 | 63 … | |
76 | 64 … | onceTrue(waitFor, () => { |
77 | 65 … | refresh() |
66 … | + | |
67 … | + // display pending updates | |
78 | 68 … | pull( |
79 | 69 … | getStream({old: false}), |
80 | - pull.drain((item) => { | |
81 | - var type = item && item.value && item.value.content.type | |
70 … | + LookupRoot(), | |
71 … | + pull.filter((msg) => { | |
72 … | + return rootFilter(msg.root || msg) && bumpFilter(msg) | |
73 … | + }), | |
74 … | + pull.drain((msg) => { | |
75 … | + if (msg.value.content.type === 'vote') return | |
76 … | + if (api.app.sync.externalHandler(msg)) return | |
77 … | + newSinceRefresh.add(msg.key) | |
82 | 78 … | |
83 | - // prioritize new messages on next refresh | |
84 | - newInSession.add(item.key) | |
85 | - newSinceRefresh.add(item.key) | |
86 | - | |
87 | - // ignore message handled by another app | |
88 | - if (api.app.sync.externalHandler(item)) return | |
89 | - | |
90 | - if (type && type !== 'vote' && typeof item.value.content === 'object' && item.value.timestamp > twoDaysAgo()) { | |
91 | - if (autoRefresh && item.value && item.value.author === api.keys.sync.id() && !updates()) { | |
92 | - return refresh() | |
93 | - } | |
94 | - if (filter) { | |
95 | - if (item.value.content.type === 'post') { | |
96 | - var update = (item.value.content.root) ? { | |
97 | - type: 'message', | |
98 | - messageId: item.value.content.root, | |
99 | - channel: item.value.content.channel | |
100 | - } : { | |
101 | - type: 'message', | |
102 | - author: item.value.author, | |
103 | - channel: item.value.content.channel, | |
104 | - messageId: item.key | |
105 | - } | |
106 | - | |
107 | - ensureMessageAndAuthor(update, (err, update) => { | |
108 | - if (!err) { | |
109 | - if (filter(update)) { | |
110 | - updates.set(updates() + 1) | |
111 | - } | |
112 | - } | |
113 | - }) | |
114 | - } | |
115 | - } else { | |
116 | - updates.set(updates() + 1) | |
117 | - } | |
79 … | + if (updates() === 0 && msg.value.author === yourId && container.scrollTop < 20) { | |
80 … | + refresh() | |
81 … | + } else { | |
82 … | + updates.set(updates() + 1) | |
118 | 83 … | } |
119 | 84 … | }) |
120 | 85 … | ) |
121 | 86 … | }) |
122 | 87 … | |
123 | - var abortLastFeed = null | |
124 | - | |
125 | 88 … | var result = MutantArray([ |
126 | 89 … | when(updates, updateLoader), |
127 | 90 … | container |
128 | 91 … | ]) |
129 | 92 … | |
93 … | + result.pendingUpdates = throttledUpdates | |
130 | 94 … | result.reload = refresh |
131 | - result.pendingUpdates = updates | |
132 | 95 … | |
133 | 96 … | return result |
134 | 97 … | |
135 | - // scoped | |
136 | - | |
137 | 98 … | function refresh () { |
138 | - if (abortLastFeed) { | |
139 | - abortLastFeed() | |
140 | - } | |
99 … | + if (abortLastFeed) abortLastFeed() | |
141 | 100 … | updates.set(0) |
101 … | + content.set(h('section.content')) | |
142 | 102 … | |
143 | - content.set( | |
144 | - h('section.content') | |
145 | - ) | |
146 | - | |
147 | 103 … | var abortable = Abortable() |
148 | 104 … | abortLastFeed = abortable.abort |
149 | 105 … | |
150 | - prioritized = {} | |
151 | - newSinceRefresh.forEach(x => { | |
152 | - prioritized[x] = 2 | |
153 | - }) | |
106 … | + highlightItems = newSinceRefresh | |
107 … | + newSinceRefresh = new Set() | |
154 | 108 … | |
155 | - var stream = api.feed.pull.summary(getStream, {windowSize, bumpFilter, prioritized}) | |
156 | - loading.set(stream.loading) | |
109 … | + var done = Value(false) | |
110 … | + var stream = nextStepper(getStream, {reverse: true, limit: 50}) | |
111 … | + var scroller = Scroller(container, content(), renderItem, false, false, () => done.set(true)) | |
157 | 112 … | |
113 … | + // track loading state | |
114 … | + loading.set(computed([done, scroller.queue], (done, queue) => { | |
115 … | + return !done && queue < 5 | |
116 … | + })) | |
117 … | + | |
158 | 118 … | pull( |
159 | 119 … | stream, |
160 | - pull.asyncMap(ensureMessageAndAuthor), | |
161 | - pull.filter((item) => { | |
162 | - // ignore messages that are handled by other apps | |
163 | - if (item.rootMessage && api.app.sync.externalHandler(item.rootMessage)) return | |
164 | - if (filter) { | |
165 | - return filter(item) | |
166 | - } else { | |
167 | - return true | |
168 | - } | |
169 | - }), | |
120 … | + pull.filter(bumpFilter), | |
170 | 121 … | abortable, |
171 | - Scroller(container, content(), renderItem, false, false) | |
122 … | + api.feed.pull.rollup(rootFilter), | |
123 … | + scroller | |
172 | 124 … | ) |
173 | - | |
174 | - // clear high prioritized items | |
175 | - newSinceRefresh.clear() | |
176 | 125 … | } |
177 | 126 … | |
178 | - function renderItem (item) { | |
179 | - var classList = [] | |
180 | - if (item.priority >= 2) { | |
181 | - classList.push('-new') | |
182 | - } | |
127 … | + function renderItem (item, opts) { | |
128 … | + var partial = opts && opts.partial | |
129 … | + var meta = null | |
130 … | + var previousId = item.key | |
183 | 131 … | |
184 | - if (item.type === 'message') { | |
185 | - var meta = null | |
186 | - var previousId = item.messageId | |
187 | - var replies = item.replies.slice(-4).map((msg) => { | |
188 | - var result = api.message.html.render(msg, { | |
189 | - inContext: true, | |
190 | - inSummary: true, | |
191 | - previousId, | |
192 | - priority: prioritized[msg.key] | |
193 | - }) | |
194 | - previousId = msg.key | |
195 | - return result | |
132 … | + var groupedBumps = {} | |
133 … | + var lastBumpType = null | |
134 … | + | |
135 … | + item.replies.forEach(msg => { | |
136 … | + var value = bumpFilter(msg) | |
137 … | + if (value) { | |
138 … | + var type = typeof value === 'string' ? value : getType(msg) | |
139 … | + ;(groupedBumps[type] = groupedBumps[type] || []).push(msg) | |
140 … | + lastBumpType = type | |
141 … | + } | |
142 … | + }) | |
143 … | + | |
144 … | + var replies = item.replies.filter(isReply) | |
145 … | + var replyElements = replies.filter(displayFilter).sort(byAssertedTime).slice(-3).map((msg) => { | |
146 … | + var result = api.message.html.render(msg, { | |
147 … | + inContext: true, | |
148 … | + inSummary: true, | |
149 … | + previousId, | |
150 … | + priority: highlightItems.has(msg.key) ? 2 : 0 | |
196 | 151 … | }) |
197 | - var renderedMessage = item.message ? api.message.html.render(item.message, {inContext: true}) : null | |
198 | - if (renderedMessage) { | |
199 | - if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
200 | - meta = h('div.meta', { | |
201 | - title: names(item.repliesFrom) | |
202 | - }, [ | |
203 | - many(item.repliesFrom, api.profile.html.person), ' replied' | |
204 | - ]) | |
205 | - } else if (item.lastUpdateType === 'like' && item.likes.size) { | |
206 | - meta = h('div.meta', { | |
207 | - title: names(item.likes) | |
208 | - }, [ | |
209 | - many(item.likes, api.profile.html.person), ' liked this message' | |
210 | - ]) | |
211 | - } | |
152 … | + previousId = msg.key | |
153 … | + return result | |
154 … | + }) | |
212 | 155 … | |
213 | - return h('FeedEvent', [ | |
214 | - meta, | |
215 | - renderedMessage, | |
216 | - when(replies.length, [ | |
217 | - when(item.replies.length > replies.length || opts.partial, | |
218 | - h('a.full', {href: item.messageId}, ['View full thread']) | |
219 | - ), | |
220 | - h('div.replies', replies) | |
221 | - ]) | |
222 | - ]) | |
223 | - } else { | |
224 | - // when there is no root message in this window, | |
225 | - // try and show reply message, only show like message if we have nothing else to give | |
226 | - if (item.repliesFrom.size) { | |
227 | - meta = h('div.meta', { | |
228 | - title: names(item.repliesFrom) | |
229 | - }, [ | |
230 | - many(item.repliesFrom, api.profile.html.person), ' replied to ', api.message.html.link(item.messageId) | |
231 | - ]) | |
232 | - } else if (item.lastUpdateType === 'like' && item.likes.size) { | |
233 | - meta = h('div.meta', { | |
234 | - title: names(item.likes) | |
235 | - }, [ | |
236 | - many(item.likes, api.profile.html.person), ' liked ', api.message.html.link(item.messageId) | |
237 | - ]) | |
238 | - } | |
156 … | + var renderedMessage = api.message.html.render(item, {inContext: true}) | |
157 … | + if (!renderedMessage) return h('div') | |
158 … | + if (lastBumpType) { | |
159 … | + var bumps = lastBumpType === 'vote' | |
160 … | + ? getLikeAuthors(groupedBumps[lastBumpType]) | |
161 … | + : getAuthors(groupedBumps[lastBumpType]) | |
239 | 162 … | |
240 | - // only show this event if it has a meta description | |
241 | - if (meta) { | |
242 | - return h('FeedEvent', [ | |
243 | - meta, h('div.replies', replies) | |
244 | - ]) | |
245 | - } | |
246 | - } | |
247 | - } else if (item.type === 'follow') { | |
248 | - return h('FeedEvent -follow', {classList}, [ | |
249 | - h('div.meta', { | |
250 | - title: names(item.contacts) | |
251 | - }, [ | |
252 | - api.profile.html.person(item.id), ' followed ', many(item.contacts, api.profile.html.person) | |
253 | - ]) | |
163 … | + var description = bumpMessages[lastBumpType] || 'added changes' | |
164 … | + meta = h('div.meta', { title: names(bumps) }, [ | |
165 … | + many(bumps, api.profile.html.person), ' ', description | |
254 | 166 … | ]) |
255 | - } else if (item.type === 'subscribe') { | |
256 | - return h('FeedEvent -subscribe', {classList}, [ | |
257 | - h('div.meta', { | |
258 | - title: names(item.subscribers) | |
259 | - }, [ | |
260 | - many(item.subscribers, api.profile.html.person), | |
261 | - ' subscribed to ', | |
262 | - h('a', {href: `#${item.channel}`}, `#${item.channel}`) | |
263 | - ]) | |
264 | - ]) | |
265 | 167 … | } |
266 | 168 … | |
267 | - return h('div') | |
169 … | + return h('FeedEvent -post', { | |
170 … | + attributes: { | |
171 … | + 'data-root-id': item.key | |
172 … | + } | |
173 … | + }, [ | |
174 … | + meta, | |
175 … | + renderedMessage, | |
176 … | + when(replyElements.length, [ | |
177 … | + when(replies.length > replyElements.length || partial, | |
178 … | + h('a.full', {href: item.key}, ['View full thread (', replies.length, ')']) | |
179 … | + ), | |
180 … | + h('div.replies', replyElements) | |
181 … | + ]) | |
182 … | + ]) | |
268 | 183 … | } |
184 … | + }) | |
185 … | + | |
186 … | + function names (ids) { | |
187 … | + var items = map(Array.from(ids), api.about.obs.name) | |
188 … | + return computed([items], (names) => names.map((n) => `- ${n}`).join('\n')) | |
269 | 189 … | } |
270 | 190 … | |
271 | - function ensureMessageAndAuthor (item, cb) { | |
272 | - if (item.type === 'message' && !item.rootMessage) { | |
273 | - if (item.message) { | |
274 | - item.rootMessage = item.message | |
275 | - cb(null, item) | |
191 … | + function LookupRoot () { | |
192 … | + return paramap((msg, cb) => { | |
193 … | + var rootId = api.message.sync.root(msg) | |
194 … | + if (rootId) { | |
195 … | + api.sbot.async.get(rootId, (_, value) => { | |
196 … | + cb(null, extend(msg, { | |
197 … | + root: {key: rootId, value} | |
198 … | + })) | |
199 … | + }) | |
276 | 200 … | } else { |
277 | - api.sbot.async.get(item.messageId, (_, value) => { | |
278 | - if (value) { | |
279 | - item.author = value.author | |
280 | - item.rootMessage = {key: item.messageId, value} | |
281 | - } | |
282 | - cb(null, item) | |
283 | - }) | |
201 … | + cb(null, msg) | |
284 | 202 … | } |
285 | - } else { | |
286 | - cb(null, item) | |
287 | - } | |
203 … | + }) | |
288 | 204 … | } |
289 | - | |
290 | - function names (ids) { | |
291 | - var items = map(Array.from(ids), api.about.obs.name) | |
292 | - return computed([items], (names) => names.map((n) => `- ${n}`).join('\n')) | |
293 | - } | |
294 | 205 … | } |
295 | 206 … | |
296 | -function twoDaysAgo () { | |
297 | - return Date.now() - (2 * 24 * 60 * 60 * 1000) | |
207 … | +function plural (value, single, many) { | |
208 … | + return computed(value, (value) => { | |
209 … | + if (value === 1) { | |
210 … | + return single | |
211 … | + } else { | |
212 … | + return many | |
213 … | + } | |
214 … | + }) | |
298 | 215 … | } |
299 | 216 … | |
300 | 217 … | function many (ids, fn) { |
301 | 218 … | ids = Array.from(ids) |
302 | - var featuredIds = ids.slice(-4).reverse() | |
219 … | + var featuredIds = ids.slice(0, 4) | |
303 | 220 … | |
304 | 221 … | if (ids.length) { |
305 | 222 … | if (ids.length > 4) { |
306 | 223 … | return [ |
@@ -331,4 +248,50 @@ | ||
331 | 248 … | return fn(featuredIds[0]) |
332 | 249 … | } |
333 | 250 … | } |
334 | 251 … | } |
252 … | + | |
253 … | +function getAuthors (items) { | |
254 … | + return items.reduce((result, msg) => { | |
255 … | + result.add(msg.value.author) | |
256 … | + return result | |
257 … | + }, new Set()) | |
258 … | +} | |
259 … | + | |
260 … | +function getLikeAuthors (items) { | |
261 … | + return items.reduce((result, msg) => { | |
262 … | + if (msg.value.content.type === 'vote') { | |
263 … | + if (msg.value.content && msg.value.content.vote && msg.value.content.vote.value === 1) { | |
264 … | + result.add(msg.value.author) | |
265 … | + } else { | |
266 … | + result.delete(msg.value.author) | |
267 … | + } | |
268 … | + } | |
269 … | + return result | |
270 … | + }, new Set()) | |
271 … | +} | |
272 … | + | |
273 … | +function isUpdate (msg) { | |
274 … | + if (msg.value && msg.value.content) { | |
275 … | + var type = msg.value.content.type | |
276 … | + return type === 'about' | |
277 … | + } | |
278 … | +} | |
279 … | + | |
280 … | +function isReply (msg) { | |
281 … | + if (msg.value && msg.value.content) { | |
282 … | + var type = msg.value.content.type | |
283 … | + return type === 'post' || (type === 'about' && msg.value.content.attendee) | |
284 … | + } | |
285 … | +} | |
286 … | + | |
287 … | +function getType (msg) { | |
288 … | + return msg && msg.value && msg.value.content && msg.value.content.type | |
289 … | +} | |
290 … | + | |
291 … | +function returnTrue () { | |
292 … | + return true | |
293 … | +} | |
294 … | + | |
295 … | +function byAssertedTime (a, b) { | |
296 … | + return a.value.timestamp - b.value.timestamp | |
297 … | +} |
modules/feed/pull/summary.js | ||
---|---|---|
@@ -1,288 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var pullDefer = require('pull-defer') | |
3 | -var pullNext = require('pull-next') | |
4 | -var SortedArray = require('sorted-array-functions') | |
5 | -var nest = require('depnest') | |
6 | -var ref = require('ssb-ref') | |
7 | -var sustained = require('../../../lib/sustained') | |
8 | -var Value = require('mutant/value') | |
9 | - | |
10 | -exports.gives = nest({ | |
11 | - 'feed.pull': [ 'summary' ] | |
12 | -}) | |
13 | - | |
14 | -exports.create = function () { | |
15 | - return nest({ | |
16 | - 'feed.pull': { summary } | |
17 | - }) | |
18 | -} | |
19 | - | |
20 | -function summary (source, opts, cb) { | |
21 | - var bumpFilter = opts && opts.bumpFilter | |
22 | - var windowSize = opts && opts.windowSize || 1000 | |
23 | - var prioritized = opts && opts.prioritized || {} | |
24 | - | |
25 | - var loading = Value(true) | |
26 | - | |
27 | - var last = null | |
28 | - var returned = false | |
29 | - var done = false | |
30 | - | |
31 | - var result = pullNext(() => { | |
32 | - if (!done) { | |
33 | - loading.set(true) | |
34 | - var next = {reverse: true, limit: windowSize, live: false} | |
35 | - if (last) { | |
36 | - next.lt = last | |
37 | - } | |
38 | - var deferred = pullDefer.source() | |
39 | - pull( | |
40 | - source(next), | |
41 | - pull.collect((err, values) => { | |
42 | - loading.set(false) | |
43 | - if (err) throw err | |
44 | - if (!values.length) { | |
45 | - done = true | |
46 | - deferred.resolve(pull.values([])) | |
47 | - if (!returned) cb && cb() | |
48 | - returned = true | |
49 | - } else { | |
50 | - var fromTime = last && last.timestamp || Date.now() | |
51 | - last = values[values.length - 1] | |
52 | - groupMessages(values, fromTime, {bumpFilter, prioritized}, (err, result) => { | |
53 | - if (err) throw err | |
54 | - deferred.resolve( | |
55 | - pull.values(result) | |
56 | - ) | |
57 | - if (!returned) cb && cb() | |
58 | - returned = true | |
59 | - }) | |
60 | - } | |
61 | - }) | |
62 | - ) | |
63 | - } | |
64 | - return deferred | |
65 | - }) | |
66 | - | |
67 | - // switch to loading state immediately, only revert after no loading for > 200 ms | |
68 | - result.loading = sustained(loading, 500, x => x) | |
69 | - | |
70 | - return result | |
71 | -} | |
72 | - | |
73 | -function groupMessages (messages, fromTime, opts, cb) { | |
74 | - var subscribes = {} | |
75 | - var follows = {} | |
76 | - var messageUpdates = {} | |
77 | - reverseForEach(messages, function (msg) { | |
78 | - if (!msg.value) return | |
79 | - var c = msg.value.content | |
80 | - if (c.type === 'contact') { | |
81 | - updateContact(msg, follows, opts) | |
82 | - } else if (c.type === 'channel') { | |
83 | - updateChannel(msg, subscribes, opts) | |
84 | - } else if (c.type === 'vote') { | |
85 | - if (c.vote && c.vote.link) { | |
86 | - // only show likes of posts added in the current window | |
87 | - // and only for the main post | |
88 | - const group = messageUpdates[c.vote.link] | |
89 | - if (group) { | |
90 | - if (c.vote.value > 0) { | |
91 | - group.likes.add(msg.value.author) | |
92 | - group.relatedMessages.push(msg) | |
93 | - } else { | |
94 | - group.likes.delete(msg.value.author) | |
95 | - group.relatedMessages.push(msg) | |
96 | - } | |
97 | - } | |
98 | - } | |
99 | - } else { | |
100 | - if (c.root) { | |
101 | - const group = ensureMessage(c.root, messageUpdates) | |
102 | - group.fromTime = fromTime | |
103 | - group.repliesFrom.add(msg.value.author) | |
104 | - SortedArray.add(group.replies, msg, compareUserTimestamp) | |
105 | - group.channel = group.channel || msg.value.content.channel | |
106 | - group.relatedMessages.push(msg) | |
107 | - } else { | |
108 | - const group = ensureMessage(msg.key, messageUpdates) | |
109 | - group.fromTime = fromTime | |
110 | - group.lastUpdateType = 'post' | |
111 | - group.updated = msg.timestamp || msg.value.sequence | |
112 | - group.author = msg.value.author | |
113 | - group.channel = msg.value.content.channel | |
114 | - group.message = msg | |
115 | - group.boxed = typeof msg.value.content === 'string' | |
116 | - } | |
117 | - } | |
118 | - }, () => { | |
119 | - var result = [] | |
120 | - Object.keys(follows).forEach((key) => { | |
121 | - bumpIfNeeded(follows[key], opts) | |
122 | - if (follows[key].updated) { | |
123 | - SortedArray.add(result, follows[key], compareUpdated) | |
124 | - } | |
125 | - }) | |
126 | - Object.keys(subscribes).forEach((key) => { | |
127 | - bumpIfNeeded(subscribes[key], opts) | |
128 | - if (subscribes[key].updated) { | |
129 | - SortedArray.add(result, subscribes[key], compareUpdated) | |
130 | - } | |
131 | - }) | |
132 | - Object.keys(messageUpdates).forEach((key) => { | |
133 | - bumpIfNeeded(messageUpdates[key], opts) | |
134 | - if (messageUpdates[key].updated) { | |
135 | - SortedArray.add(result, messageUpdates[key], compareUpdated) | |
136 | - } | |
137 | - }) | |
138 | - cb(null, result) | |
139 | - }) | |
140 | -} | |
141 | - | |
142 | -function bumpIfNeeded (group, {bumpFilter, prioritized}) { | |
143 | - group.relatedMessages.forEach(msg => { | |
144 | - if (prioritized[msg.key] && group.priority < prioritized[msg.key]) { | |
145 | - group.priority = prioritized[msg.key] | |
146 | - } | |
147 | - | |
148 | - var shouldBump = !bumpFilter || bumpFilter(msg, group) | |
149 | - | |
150 | - // only bump when filter passes | |
151 | - var newUpdated = msg.timestamp || msg.value.sequence | |
152 | - if (!group.updated || (shouldBump && newUpdated > group.updated)) { | |
153 | - group.updated = newUpdated | |
154 | - if (msg.value.content.type === 'vote') { | |
155 | - if (group.likes.size) { | |
156 | - group.lastUpdateType = 'like' | |
157 | - } else if (group.repliesFrom.size) { | |
158 | - group.lastUpdateType = 'reply' | |
159 | - } else if (group.message) { | |
160 | - group.lastUpdateType = 'post' | |
161 | - } | |
162 | - } | |
163 | - | |
164 | - if (msg.value.content.type === 'post') { | |
165 | - if (msg.value.content.root) { | |
166 | - group.lastUpdateType = 'reply' | |
167 | - } else { | |
168 | - group.lastUpdateType = 'post' | |
169 | - } | |
170 | - } | |
171 | - } | |
172 | - }) | |
173 | -} | |
174 | - | |
175 | -function compareUpdated (a, b) { | |
176 | - // highest priority first | |
177 | - // then most recent date | |
178 | - return b.priority - a.priority || b.updated - a.updated | |
179 | -} | |
180 | - | |
181 | -function reverseForEach (items, fn, cb) { | |
182 | - var i = items.length - 1 | |
183 | - nextBatch() | |
184 | - | |
185 | - function nextBatch () { | |
186 | - var start = Date.now() | |
187 | - while (i >= 0) { | |
188 | - fn(items[i], i) | |
189 | - i -= 1 | |
190 | - if (Date.now() - start > 10) break | |
191 | - } | |
192 | - | |
193 | - if (i > 0) { | |
194 | - setImmediate(nextBatch) | |
195 | - } else { | |
196 | - cb && cb() | |
197 | - } | |
198 | - } | |
199 | -} | |
200 | - | |
201 | -function updateContact (msg, groups, opts) { | |
202 | - var c = msg.value.content | |
203 | - var id = msg.value.author | |
204 | - var group = groups[id] | |
205 | - if (ref.isFeed(c.contact)) { | |
206 | - if (c.following) { | |
207 | - if (!group) { | |
208 | - group = groups[id] = { | |
209 | - type: 'follow', | |
210 | - priority: 0, | |
211 | - relatedMessages: [], | |
212 | - lastUpdateType: null, | |
213 | - contacts: new Set(), | |
214 | - updated: 0, | |
215 | - author: id, | |
216 | - id: id | |
217 | - } | |
218 | - } | |
219 | - group.contacts.add(c.contact) | |
220 | - group.relatedMessages.push(msg) | |
221 | - } else { | |
222 | - if (group) { | |
223 | - group.contacts.delete(c.contact) | |
224 | - if (!group.contacts.size) { | |
225 | - delete groups[id] | |
226 | - } | |
227 | - } | |
228 | - } | |
229 | - } | |
230 | -} | |
231 | - | |
232 | -function updateChannel (msg, groups, opts) { | |
233 | - var c = msg.value.content | |
234 | - var channel = c.channel | |
235 | - var group = groups[channel] | |
236 | - if (typeof channel === 'string') { | |
237 | - if (c.subscribed) { | |
238 | - if (!group) { | |
239 | - group = groups[channel] = { | |
240 | - type: 'subscribe', | |
241 | - priority: 0, | |
242 | - relatedMessages: [], | |
243 | - lastUpdateType: null, | |
244 | - subscribers: new Set(), | |
245 | - updated: 0, | |
246 | - channel | |
247 | - } | |
248 | - } | |
249 | - group.subscribers.add(msg.value.author) | |
250 | - group.relatedMessages.push(msg) | |
251 | - } else { | |
252 | - if (group) { | |
253 | - group.subscribers.delete(msg.value.author) | |
254 | - if (!group.subscribers.size) { | |
255 | - delete groups[channel] | |
256 | - } | |
257 | - } | |
258 | - } | |
259 | - } | |
260 | -} | |
261 | - | |
262 | -function ensureMessage (id, groups) { | |
263 | - var group = groups[id] | |
264 | - if (!group) { | |
265 | - group = groups[id] = { | |
266 | - type: 'message', | |
267 | - priority: 0, | |
268 | - repliesFrom: new Set(), | |
269 | - relatedMessages: [], | |
270 | - replies: [], | |
271 | - message: null, | |
272 | - messageId: id, | |
273 | - likes: new Set(), | |
274 | - updated: 0 | |
275 | - } | |
276 | - } | |
277 | - return group | |
278 | -} | |
279 | - | |
280 | -function compareUserTimestamp (a, b) { | |
281 | - var isClose = !a.timestamp || !b.timestamp || Math.abs(a.timestamp - b.timestamp) < (10 * 60e3) | |
282 | - if (isClose) { | |
283 | - // recieved close together, use provided timestamps | |
284 | - return a.value.timestamp - b.value.timestamp | |
285 | - } else { | |
286 | - return a.timestamp - b.timestamp | |
287 | - } | |
288 | -} |
modules/page/html/render/all.js | ||
---|---|---|
@@ -28,10 +28,9 @@ | ||
28 | 28 … | api.message.html.compose({ meta: { type: 'post' }, placeholder: 'Write a public message' }) |
29 | 29 … | ] |
30 | 30 … | |
31 | 31 … | var feedView = api.feed.html.rollup(api.feed.pull.public, { |
32 | - prepend, | |
33 | - windowSize: 1000 | |
32 … | + prepend | |
34 | 33 … | }) |
35 | 34 … | |
36 | 35 … | var result = h('div.SplitView', [ |
37 | 36 … | h('div.main', feedView) |
modules/page/html/render/mentions.js | ||
---|---|---|
@@ -13,9 +13,20 @@ | ||
13 | 13 … | return nest('page.html.render', function mentions (path) { |
14 | 14 … | if (path !== '/mentions') return |
15 | 15 … | var id = api.keys.sync.id() |
16 | 16 … | return api.feed.html.rollup(api.feed.pull.mentions(id), { |
17 | - windowSize: 20, | |
18 | - partial: true | |
17 … | + bumpFilter: mentionFilter, | |
18 … | + displayFilter: mentionFilter | |
19 | 19 … | }) |
20 … | + | |
21 … | + // scoped | |
22 … | + function mentionFilter (msg) { | |
23 … | + if (Array.isArray(msg.value.content.mentions)) { | |
24 … | + if (msg.value.content.mentions.some(mention => { | |
25 … | + return mention && mention.link === id | |
26 … | + })) { | |
27 … | + return 'mention' | |
28 … | + } | |
29 … | + } | |
30 … | + } | |
20 | 31 … | }) |
21 | 32 … | } |
modules/page/html/render/private.js | ||
---|---|---|
@@ -27,7 +27,7 @@ | ||
27 | 27 … | placeholder: `Write a private message \n\n\n\nThis can only be read by yourself and people you have @mentioned.` |
28 | 28 … | }) |
29 | 29 … | ] |
30 | 30 … | |
31 | - return api.feed.html.rollup(api.feed.pull.private, { prepend, windowSize: 200 }) | |
31 … | + return api.feed.html.rollup(api.feed.pull.private, { prepend }) | |
32 | 32 … | }) |
33 | 33 … | } |
modules/page/html/render/profile.js | ||
---|---|---|
@@ -164,9 +164,13 @@ | ||
164 | 164 … | h('section', [ namePicker, imagePicker ]) |
165 | 165 … | ]) |
166 | 166 … | ]) |
167 | 167 … | |
168 | - var feedView = api.feed.html.rollup(api.feed.pull.profile(id), { prepend, autoRefresh: true }) | |
168 … | + var feedView = api.feed.html.rollup(api.feed.pull.profile(id), { | |
169 … | + prepend, | |
170 … | + displayFilter: (msg) => msg.value.author === id, | |
171 … | + bumpFilter: (msg) => msg.value.author === id, | |
172 … | + }) | |
169 | 173 … | |
170 | 174 … | var container = h('div', {className: 'SplitView'}, [ |
171 | 175 … | h('div.main', [ |
172 | 176 … | feedView |
@@ -208,8 +212,9 @@ | ||
208 | 212 … | h('div.name', [ api.about.obs.name(id) ]) |
209 | 213 … | ]) |
210 | 214 … | ]) |
211 | 215 … | }, { |
216 … | + maxTime: 5, | |
212 | 217 … | idle: true |
213 | 218 … | }) |
214 | 219 … | ]) |
215 | 220 … | ] |
modules/page/html/render/public.js | ||
---|---|---|
@@ -56,48 +56,28 @@ | ||
56 | 56 … | waitFor: computed([ |
57 | 57 … | following.sync, |
58 | 58 … | subscribedChannels.sync |
59 | 59 … | ], (...x) => x.every(Boolean)), |
60 | - windowSize: 1000, | |
61 | - filter: (item) => { | |
62 | - return !item.boxed && (item.lastUpdateType !== 'post' || item.message) && ( | |
63 | - id === item.author || | |
64 | - (item.author && following().has(item.author)) || | |
65 | - (item.type === 'message' && subscribedChannels().has(item.channel)) || | |
66 | - (item.type === 'subscribe' && item.subscribers.size) || | |
67 | - (item.repliesFrom && item.repliesFrom.has(id)) || | |
68 | - item.likes && item.likes.has(id) | |
69 | - ) | |
70 | - }, | |
71 | - bumpFilter: (msg, group) => { | |
72 | - if (group.type === 'subscribe') { | |
73 | - removeStrangers(group.subscribers) | |
74 | - } | |
75 | 60 … | |
76 | - if (group.type === 'message') { | |
77 | - removeStrangers(group.likes) | |
78 | - removeStrangers(group.repliesFrom) | |
61 … | + rootFilter: function (msg) { | |
62 … | + if (msg.value && msg.value.content && typeof msg.value.content === 'object') { | |
63 … | + var author = msg.value.author | |
64 … | + var type = msg.value.content.type | |
65 … | + var channel = msg.value.content.channel | |
79 | 66 … | |
80 | - if (!group.message) { | |
81 | - // if message is old, only show replies from friends | |
82 | - group.replies = group.replies.filter(x => { | |
83 | - return (x.value.author === id || following().has(x.value.author)) | |
84 | - }) | |
85 | - } | |
86 | - } | |
87 | - | |
88 | - if (!group.message) { | |
89 | 67 … | return ( |
90 | - isMentioned(id, msg.value.content.mentions) || | |
91 | - msg.value.author === id || ( | |
92 | - fromDay(msg, group.fromTime) && ( | |
93 | - following().has(msg.value.author) || | |
94 | - group.repliesFrom.has(id) | |
95 | - ) | |
96 | - ) | |
68 … | + id === author || | |
69 … | + following().has(author) || | |
70 … | + (type === 'message' && subscribedChannels().has(channel)) | |
97 | 71 … | ) |
98 | 72 … | } |
99 | - return true | |
73 … | + }, | |
74 … | + | |
75 … | + bumpFilter: function (msg) { | |
76 … | + if (msg.value && msg.value.content && typeof msg.value.content === 'object') { | |
77 … | + var author = msg.value.author | |
78 … | + return id === author || following().has(author) | |
79 … | + } | |
100 | 80 … | } |
101 | 81 … | }) |
102 | 82 … | |
103 | 83 … | var result = h('div.SplitView', [ |
@@ -111,52 +91,43 @@ | ||
111 | 91 … | result.reload = feedView.reload |
112 | 92 … | |
113 | 93 … | return result |
114 | 94 … | |
115 | - function removeStrangers (set) { | |
116 | - if (set) { | |
117 | - Array.from(set).forEach(key => { | |
118 | - if (!following().has(key) && key !== id) { | |
119 | - set.delete(key) | |
120 | - } | |
121 | - }) | |
122 | - } | |
123 | - } | |
124 | - | |
125 | 95 … | function getSidebar () { |
126 | 96 … | var whoToFollow = computed([following, api.profile.obs.recentlyUpdated(), localPeers], (following, recent, peers) => { |
127 | 97 … | return Array.from(recent).filter(x => x !== id && !following.has(x) && !peers.includes(x)).slice(0, 10) |
128 | 98 … | }) |
129 | 99 … | return [ |
130 | 100 … | h('button -pub -full', { |
131 | 101 … | 'ev-click': api.invite.sheet |
132 | 102 … | }, '+ Join Pub'), |
133 | - when(computed(channels, x => x.length), h('h2', 'Active Channels')), | |
134 | - when(loading, [ h('Loading') ]), | |
135 | - h('div', { | |
136 | - classList: 'ChannelList', | |
137 | - hidden: loading | |
138 | - }, [ | |
139 | - map(channels, (channel) => { | |
140 | - var subscribed = subscribedChannels.has(channel) | |
141 | - return h('a.channel', { | |
142 | - href: `#${channel}`, | |
143 | - classList: [ | |
144 | - when(subscribed, '-subscribed') | |
145 | - ] | |
146 | - }, [ | |
147 | - h('span.name', '#' + channel), | |
148 | - when(subscribed, | |
149 | - h('a.-unsubscribe', { | |
150 | - 'ev-click': send(unsubscribe, channel) | |
151 | - }, 'Unsubscribe'), | |
152 | - h('a.-subscribe', { | |
153 | - 'ev-click': send(subscribe, channel) | |
154 | - }, 'Subscribe') | |
155 | - ) | |
156 | - ]) | |
157 | - }, {maxTime: 5}), | |
158 | - h('a.channel -more', {href: '/channels'}, 'More Channels...') | |
103 … | + when(loading, [ h('Loading') ], [ | |
104 … | + when(computed(channels, x => x.length), h('h2', 'Active Channels')), | |
105 … | + h('div', { | |
106 … | + classList: 'ChannelList', | |
107 … | + hidden: loading | |
108 … | + }, [ | |
109 … | + map(channels, (channel) => { | |
110 … | + var subscribed = subscribedChannels.has(channel) | |
111 … | + return h('a.channel', { | |
112 … | + href: `#${channel}`, | |
113 … | + classList: [ | |
114 … | + when(subscribed, '-subscribed') | |
115 … | + ] | |
116 … | + }, [ | |
117 … | + h('span.name', '#' + channel), | |
118 … | + when(subscribed, | |
119 … | + h('a.-unsubscribe', { | |
120 … | + 'ev-click': send(unsubscribe, channel) | |
121 … | + }, 'Unsubscribe'), | |
122 … | + h('a.-subscribe', { | |
123 … | + 'ev-click': send(subscribe, channel) | |
124 … | + }, 'Subscribe') | |
125 … | + ) | |
126 … | + ]) | |
127 … | + }, {maxTime: 5}), | |
128 … | + h('a.channel -more', {href: '/channels'}, 'More Channels...') | |
129 … | + ]) | |
159 | 130 … | ]), |
160 | 131 … | |
161 | 132 … | PeerList(localPeers, 'Local'), |
162 | 133 … | PeerList(connectedPubs, 'Connected Pubs'), |
@@ -222,20 +193,8 @@ | ||
222 | 193 … | } |
223 | 194 … | } |
224 | 195 … | } |
225 | 196 … | |
226 | -function isMentioned (id, list) { | |
227 | - if (Array.isArray(list)) { | |
228 | - return list.includes(id) | |
229 | - } else { | |
230 | - return false | |
231 | - } | |
232 | -} | |
233 | - | |
234 | -function fromDay (msg, fromTime) { | |
235 | - return (fromTime - msg.timestamp) < (24 * 60 * 60e3) | |
236 | -} | |
237 | - | |
238 | 197 … | function arrayEq (a, b) { |
239 | 198 … | if (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a !== b) { |
240 | 199 … | return a.every((value, i) => value === b[i]) |
241 | 200 … | } |
package.json | ||
---|---|---|
@@ -28,12 +28,12 @@ | ||
28 | 28 … | "level": "~1.7.0", |
29 | 29 … | "micro-css": "^2.0.0", |
30 | 30 … | "mime-types": "^2.1.15", |
31 | 31 … | "moment": "^2.18.1", |
32 | - "mutant": "^3.20.2", | |
32 … | + "mutant": "^3.21.0", | |
33 | 33 … | "mutant-pull-reduce": "^1.1.0", |
34 | 34 … | "obv": "0.0.1", |
35 | - "patchcore": "~1.4.3", | |
35 … | + "patchcore": "~1.5.0", | |
36 | 36 … | "pull-abortable": "^4.1.0", |
37 | 37 … | "pull-defer": "^0.2.2", |
38 | 38 … | "pull-file": "~1.0.0", |
39 | 39 … | "pull-identify-filetype": "^1.1.0", |
plugs/message/html/render/channel.js | ||
---|---|---|
@@ -14,9 +14,9 @@ | ||
14 | 14 … | exports.create = function (api) { |
15 | 15 … | return nest('message.html.render', function renderMessage (msg, opts) { |
16 | 16 … | if (msg.value.content.type !== 'channel') return |
17 | 17 … | var element = api.message.html.layout(msg, extend({ |
18 | - content: messageContent(msg), | |
18 … | + miniContent: messageContent(msg), | |
19 | 19 … | layout: 'mini' |
20 | 20 … | }, opts)) |
21 | 21 … | |
22 | 22 … | return api.message.html.decorate(element, { msg }) |
plugs/message/html/render/following.js | ||
---|---|---|
@@ -1,0 +1,37 @@ | ||
1 … | +var h = require('mutant/h') | |
2 … | +var nest = require('depnest') | |
3 … | +var extend = require('xtend') | |
4 … | +var ref = require('ssb-ref') | |
5 … | + | |
6 … | +exports.needs = nest({ | |
7 … | + 'message.html': { | |
8 … | + decorate: 'reduce', | |
9 … | + layout: 'first' | |
10 … | + }, | |
11 … | + 'profile.html.person': 'first' | |
12 … | +}) | |
13 … | + | |
14 … | +exports.gives = nest('message.html.render') | |
15 … | + | |
16 … | +exports.create = function (api) { | |
17 … | + return nest('message.html.render', function renderMessage (msg, opts) { | |
18 … | + if (msg.value.content.type !== 'contact') return | |
19 … | + if (!ref.isFeed(msg.value.content.contact)) return | |
20 … | + if (typeof msg.value.content.following !== 'boolean') return | |
21 … | + | |
22 … | + var element = api.message.html.layout(msg, extend({ | |
23 … | + miniContent: messageContent(msg), | |
24 … | + layout: 'mini' | |
25 … | + }, opts)) | |
26 … | + | |
27 … | + return api.message.html.decorate(element, { msg }) | |
28 … | + }) | |
29 … | + | |
30 … | + function messageContent (msg) { | |
31 … | + var following = msg.value.content.following | |
32 … | + return [ | |
33 … | + following ? 'followed ' : 'unfollowed ', | |
34 … | + api.profile.html.person(msg.value.content.contact) | |
35 … | + ] | |
36 … | + } | |
37 … | +} |
plugs/channel/obs/recent.js | ||
---|---|---|
@@ -1,0 +1,78 @@ | ||
1 … | +// uses lib/flumeview-channels | |
2 … | + | |
3 … | +var nest = require('depnest') | |
4 … | +var pull = require('pull-stream') | |
5 … | + | |
6 … | +var { Value, Dict, Struct, computed, resolve, throttle } = require('mutant') | |
7 … | + | |
8 … | +exports.needs = nest({ | |
9 … | + 'sbot.pull.stream': 'first' | |
10 … | +}) | |
11 … | + | |
12 … | +exports.gives = nest({ | |
13 … | + 'channel.obs.recent': true | |
14 … | +}) | |
15 … | + | |
16 … | +exports.create = function (api) { | |
17 … | + var recentChannels = null | |
18 … | + var channelsLookup = null | |
19 … | + | |
20 … | + return nest({ | |
21 … | + 'channel.obs.recent': function () { | |
22 … | + load() | |
23 … | + return recentChannels | |
24 … | + } | |
25 … | + }) | |
26 … | + | |
27 … | + function load () { | |
28 … | + if (!recentChannels) { | |
29 … | + var sync = Value(false) | |
30 … | + channelsLookup = Dict() | |
31 … | + | |
32 … | + pull( | |
33 … | + api.sbot.pull.stream(sbot => sbot.channels.stream({live: true})), | |
34 … | + pull.drain(msg => { | |
35 … | + if (!sync()) { | |
36 … | + channelsLookup.transaction(() => { | |
37 … | + for (var channel in msg) { | |
38 … | + var obs = ChannelRef(channel) | |
39 … | + obs.set({ | |
40 … | + id: channel, | |
41 … | + updatedAt: msg[channel].timestamp, | |
42 … | + count: msg[channel].count | |
43 … | + }) | |
44 … | + channelsLookup.put(channel, obs) | |
45 … | + } | |
46 … | + sync.set(true) | |
47 … | + }) | |
48 … | + } else { | |
49 … | + var obs = channelsLookup.get(msg.channel) | |
50 … | + if (!obs) { | |
51 … | + obs = ChannelRef(msg.dest) | |
52 … | + channelsLookup.put(msg.dest, obs) | |
53 … | + } | |
54 … | + obs.set({ | |
55 … | + id: msg.channel, | |
56 … | + updatedAt: Math.max(resolve(obs.updatedAt), msg.timestamp), | |
57 … | + count: resolve(obs.count) + 1 | |
58 … | + }) | |
59 … | + } | |
60 … | + }) | |
61 … | + ) | |
62 … | + | |
63 … | + recentChannels = computed(throttle(channelsLookup, 1000), (lookup) => { | |
64 … | + var values = Object.keys(lookup).map(x => lookup[x]).sort((a, b) => b.updatedAt - a.updatedAt).map(x => x.id) | |
65 … | + return values | |
66 … | + }) | |
67 … | + recentChannels.sync = sync | |
68 … | + } | |
69 … | + } | |
70 … | +} | |
71 … | + | |
72 … | +function ChannelRef (id) { | |
73 … | + return Struct({ | |
74 … | + id, | |
75 … | + updatedAt: Value(0), | |
76 … | + count: Value(0) | |
77 … | + }, {merge: true}) | |
78 … | +} |
server-process.js | ||
---|---|---|
@@ -17,8 +17,9 @@ | ||
17 | 17 … | .use(require('scuttlebot/plugins/logging')) |
18 | 18 … | .use(require('ssb-query')) |
19 | 19 … | .use(require('ssb-about')) |
20 | 20 … | .use(require('ssb-contacts')) |
21 … | + .use(require('./lib/flumeview-channels')) | |
21 | 22 … | .use(require('./lib/progress-stream')) |
22 | 23 … | |
23 | 24 … | module.exports = function (ssbConfig) { |
24 | 25 … | var context = { |
Built with git-ssb-web