Files: e6df29b999257c928b0fe5316875680a479dba2e / app / page / blogIndex.js
5882 bytesRaw
1 | const nest = require('depnest') |
2 | const { h, Array: MutantArray, resolve, computed } = require('mutant') |
3 | const Scroller = require('mutant-scroll') |
4 | const pull = require('pull-stream') |
5 | |
6 | const Next = require('pull-next') |
7 | const get = require('lodash/get') |
8 | const clone = require('lodash/cloneDeep') |
9 | |
10 | exports.gives = nest('app.page.blogIndex') |
11 | |
12 | exports.needs = nest({ |
13 | 'app.html.blogCard': 'first', |
14 | 'app.html.topNav': 'first', |
15 | 'app.html.sideNav': 'first', |
16 | 'blog.sync.isBlog': 'first', |
17 | 'sbot.pull.stream': 'first', |
18 | 'sbot.obs.connection': 'first', |
19 | 'history.sync.push': 'first', |
20 | 'keys.sync.id': 'first', |
21 | 'contact.obs.followers': 'first', |
22 | 'channel.obs.isSubscribedTo': 'first', |
23 | 'message.sync.isBlocked': 'first', |
24 | 'translations.sync.strings': 'first', |
25 | 'unread.sync.isUnread': 'first' |
26 | }) |
27 | |
28 | exports.create = (api) => { |
29 | var blogsCache = MutantArray() |
30 | |
31 | return nest('app.page.blogIndex', function (location) { |
32 | // location here can expected to be: { page: 'blogIndex', filter: 'All' } |
33 | const { page, filter = 'All' } = location |
34 | |
35 | var strings = api.translations.sync.strings() |
36 | blogsCache.clear() |
37 | |
38 | var blogs = Scroller({ |
39 | classList: ['content'], |
40 | prepend: [ |
41 | api.app.html.topNav(location), |
42 | // Filters() |
43 | ], |
44 | streamToTop: Source({ reverse: false, live: true, old: false, limit: 20 }, api, filter), |
45 | streamToBottom: Source({ reverse: true, live: false, limit: 20 }, api, filter), |
46 | updateTop: update, |
47 | updateBottom: update, |
48 | store: blogsCache, |
49 | render |
50 | }) |
51 | |
52 | // HACK |
53 | blogs.insertBefore(Filters(), blogs.querySelector('.content')) |
54 | |
55 | function Filters () { |
56 | const goTo = (loc) => () => api.history.sync.push(loc) |
57 | return h('Filters', [ |
58 | h('span -filter', { |
59 | className: filter === 'All' ? '-active' : '', |
60 | 'ev-click': goTo({ page, filter: 'All' }) |
61 | }, 'All'), |
62 | h('span', '|'), |
63 | h('span -filter', { |
64 | className: filter === 'Subscriptions' ? '-active' : '', |
65 | 'ev-click': goTo({ page, filter: 'Subscriptions' }) |
66 | }, 'Subscriptions'), |
67 | h('span', '|'), |
68 | h('span -filter', { |
69 | className: filter === 'Friends' ? '-active' : '', |
70 | 'ev-click': goTo({ page, filter: 'Friends' }) |
71 | }, 'Friends') |
72 | ]) |
73 | } |
74 | |
75 | return h('Page -blogIndex', { title: strings.home }, [ |
76 | api.app.html.sideNav(location), |
77 | blogs |
78 | ]) |
79 | }) |
80 | |
81 | function Source (opts, api, filter) { |
82 | const commonOpts = { |
83 | query: [{ |
84 | $filter: { |
85 | value: { |
86 | content: { |
87 | type: 'blog' |
88 | }, |
89 | timestamp: { $gt: 0, $lt: undefined } |
90 | } |
91 | } |
92 | }] |
93 | } |
94 | |
95 | const myId = api.keys.sync.id() |
96 | const { followers } = api.contact.obs |
97 | |
98 | const filterbyFriends = (msg) => { |
99 | if (filter !== 'Friends') return true |
100 | |
101 | const feed = msg.value.author |
102 | const theirFollowers = followers(feed) |
103 | const youFollowThem = computed(theirFollowers, followers => followers.includes(myId)) |
104 | |
105 | return resolve(youFollowThem) |
106 | } |
107 | |
108 | const filterBySubscription = (msg) => { |
109 | if (filter !== 'Subscriptions') return true |
110 | |
111 | if (!msg.value.content.hasOwnProperty('channel')) { |
112 | return false |
113 | } |
114 | |
115 | const channel = msg.value.content.channel |
116 | |
117 | return resolve(api.channel.obs.isSubscribedTo(channel)) |
118 | } |
119 | |
120 | return pull( |
121 | StepperStream( |
122 | (options) => api.sbot.pull.stream(sbot => sbot.query.read(options)), |
123 | Object.assign(commonOpts, opts) |
124 | ), |
125 | pull.filter(api.blog.sync.isBlog), // isBlog or Plog? |
126 | pull.filter(filterBySubscription), |
127 | pull.filter(filterbyFriends), |
128 | // pull.filter(msg => !msg.value.content.root), // show only root messages |
129 | pull.filter(msg => !api.message.sync.isBlocked(msg)) // this is already in feed.pull.type |
130 | ) |
131 | } |
132 | |
133 | function update (soFar, newBlog) { |
134 | soFar.transaction(() => { |
135 | var object = newBlog // Value(newBlog) |
136 | |
137 | const index = indexOf(soFar, (blog) => newBlog.key === resolve(blog).key) |
138 | // if blog already in cache, not needed again |
139 | if (index >= 0) return |
140 | |
141 | const justOlderPosition = indexOf(soFar, (msg) => newBlog.value.timestamp > resolve(msg).value.timestamp) |
142 | |
143 | if (justOlderPosition > -1) { |
144 | soFar.insert(object, justOlderPosition) |
145 | } else { |
146 | soFar.push(object) |
147 | } |
148 | }) |
149 | } |
150 | |
151 | function render (blog) { |
152 | const { recps, channel } = blog.value.content |
153 | var onClick |
154 | if (channel && !recps) { onClick = (ev) => api.history.sync.push(Object.assign({}, blog, { page: 'blogShow' })) } |
155 | return api.app.html.blogCard(blog, { onClick }) |
156 | } |
157 | } |
158 | |
159 | function indexOf (array, fn) { |
160 | for (var i = 0; i < array.getLength(); i++) { |
161 | if (fn(array.get(i))) { |
162 | return i |
163 | } |
164 | } |
165 | return -1 |
166 | } |
167 | |
168 | // this is needed because muxrpc doesn't do back-pressure yet |
169 | // this is a modified pull-next-step for ssb-query |
170 | function StepperStream (createStream, _opts) { |
171 | var opts = clone(_opts) |
172 | var last = null |
173 | var count = -1 |
174 | |
175 | return Next(() => { |
176 | if (last) { |
177 | if (count === 0) return |
178 | // mix: not sure which case this ends stream for |
179 | // |
180 | |
181 | var value = get(last, ['value', 'timestamp']) |
182 | if (value == null) return |
183 | |
184 | if (opts.reverse) { |
185 | opts.query[0].$filter.value.timestamp.$lt = value |
186 | } else { |
187 | opts.query[0].$filter.value.timestamp.$gt = value |
188 | } |
189 | last = null |
190 | } |
191 | |
192 | return pull( |
193 | createStream(clone(opts)), |
194 | pull.through( |
195 | (msg) => { |
196 | count++ |
197 | if (!msg.sync) { |
198 | last = msg |
199 | } |
200 | }, |
201 | (err) => { |
202 | // retry on errors... |
203 | if (err) { |
204 | count = -1 |
205 | return count |
206 | } |
207 | // end stream if there were no results |
208 | if (last == null) last = {} |
209 | } |
210 | ) |
211 | ) |
212 | }) |
213 | } |
214 |
Built with git-ssb-web