git ssb

2+

mixmix / ticktack



Tree: 1239690dc41098bef2ddfd0f321e54dc0ab27fd7

Files: 1239690dc41098bef2ddfd0f321e54dc0ab27fd7 / app / page / blogIndex.js

5882 bytesRaw
1const nest = require('depnest')
2const { h, Array: MutantArray, resolve, computed } = require('mutant')
3const Scroller = require('mutant-scroll')
4const pull = require('pull-stream')
5
6const Next = require('pull-next')
7const get = require('lodash/get')
8const clone = require('lodash/cloneDeep')
9
10exports.gives = nest('app.page.blogIndex')
11
12exports.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
28exports.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
159function 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
170function 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