git ssb

2+

mixmix / ticktack



Tree: 354bcc491180e33154ea042ff61d5912ae2d95ab

Files: 354bcc491180e33154ea042ff61d5912ae2d95ab / app / html / context.js

8938 bytesRaw
1const nest = require('depnest')
2const { h, computed, map, when, Dict, Array: MutantArray, Value, Set, resolve } = require('mutant')
3const pull = require('pull-stream')
4const next = require('pull-next-step')
5const get = require('lodash/get')
6const isEmpty = require('lodash/isEmpty')
7
8exports.gives = nest('app.html.context')
9
10exports.needs = nest({
11 'app.html.scroller': 'first',
12 'about.html.avatar': 'first',
13 'about.obs.name': 'first',
14 'feed.pull.private': 'first',
15 'feed.pull.rollup': 'first',
16 'feed.pull.public': 'first',
17 'keys.sync.id': 'first',
18 'history.sync.push': 'first',
19 'message.html.subject': 'first',
20 'sbot.obs.localPeers': 'first',
21 'translations.sync.strings': 'first',
22 'unread.sync.isUnread': 'first'
23})
24
25exports.create = (api) => {
26 var recentMsgCache = MutantArray()
27 var usersLastMsgCache = Dict() // { id: [ msgs ] }
28 var usersUnreadMsgsCache = Dict() // { id: [ msgs ] }
29
30 return nest('app.html.context', context)
31
32 function context (location) {
33 const strings = api.translations.sync.strings()
34 const myKey = api.keys.sync.id()
35
36 var nearby = api.sbot.obs.localPeers()
37
38 // Unread message counts
39 const updateUserUnreadMsgsCache = (msg) => {
40 var cache = getUserUnreadMsgsCache(msg.value.author)
41
42 if(api.unread.sync.isUnread(msg))
43 cache.add(msg.key)
44 else
45 cache.delete(msg.key)
46 }
47 pull(
48 next(api.feed.pull.private, {reverse: true, limit: 1000, live: false, property: ['value', 'timestamp']}),
49 privateMsgFilter(),
50 pull.drain(updateUserUnreadMsgsCache)
51 )
52
53 pull(
54 next(api.feed.pull.private, {old: false, live: true, property: ['value', 'timestamp']}),
55 privateMsgFilter(),
56 pull.drain(updateUserUnreadMsgsCache)
57 )
58
59 //TODO: calculate unread state for public threads/blogs
60 // pull(
61 // next(api.feed.pull.public, {reverse: true, limit: 100, live: false, property: ['value', 'timestamp']}),
62 // pull.drain(msg => {
63 //
64 // })
65 // )
66
67 return h('Context -feed', [
68 LevelOneContext(),
69 LevelTwoContext()
70 ])
71
72 function LevelOneContext () {
73 function isDiscoverContext (loc) {
74 const PAGES_UNDER_DISCOVER = ['blogIndex', 'blogShow', 'userShow']
75
76 return PAGES_UNDER_DISCOVER.includes(location.page)
77 || get(location, 'value.private') === undefined
78 }
79
80 const prepend = [
81 // Nearby
82 computed(nearby, n => !isEmpty(n) ? h('header', strings.peopleNearby) : null),
83 map(nearby, feedId => Option({
84 notifications: notifications(feedId),
85 imageEl: api.about.html.avatar(feedId, 'small'),
86 label: api.about.obs.name(feedId),
87 selected: location.feed === feedId,
88 location: computed(recentMsgCache, recent => {
89 const lastMsg = recent.find(msg => msg.value.author === feedId)
90 return lastMsg
91 ? Object.assign(lastMsg, { feed: feedId })
92 : { page: 'threadNew', feed: feedId }
93 }),
94 }), { comparer: (a, b) => a === b }),
95
96 // ---------------------
97 computed(nearby, n => !isEmpty(n) ? h('hr') : null),
98
99 // Discover
100 Option({
101 // notifications: '!', //TODO - count this!
102 imageEl: h('i.fa.fa-binoculars'),
103 label: strings.blogIndex.title,
104 selected: isDiscoverContext(location),
105 location: { page: 'blogIndex' },
106 })
107 ]
108
109 return api.app.html.scroller({
110 classList: [ 'level', '-one' ],
111 prepend,
112 stream: api.feed.pull.private,
113 filter: privateMsgFilter,
114 store: recentMsgCache,
115 updateTop: updateRecentMsgCache,
116 updateBottom: updateRecentMsgCache,
117 render: (msgObs) => {
118 const msg = resolve(msgObs)
119 const { author } = msg.value
120 if (nearby.has(author)) return
121
122 return Option({
123 //the number of threads with each peer
124 notifications: notifications(author),
125 imageEl: api.about.html.avatar(author),
126 label: api.about.obs.name(author),
127 selected: location.feed === author,
128 location: Object.assign({}, msg, { feed: author }) // TODO make obs?
129 })
130 }
131 })
132
133 function updateRecentMsgCache (soFar, newMsg) {
134 soFar.transaction(() => {
135 const { author, timestamp } = newMsg.value
136 const index = indexOf(soFar, (msg) => author === resolve(msg).value.author)
137 var object = Value()
138
139 if (index >= 0) {
140 // reference already exists, lets use this instead!
141 const existingMsg = soFar.get(index)
142
143 if (resolve(existingMsg).value.timestamp > timestamp) return
144 // but abort if the existing reference is newer
145
146 object = existingMsg
147 soFar.deleteAt(index)
148 }
149
150 object.set(newMsg)
151
152 const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
153 if (justOlderPosition > -1) {
154 soFar.insert(object, justOlderPosition)
155 } else {
156 soFar.push(object)
157 }
158 })
159 }
160
161 }
162
163 function getUserUnreadMsgsCache (author) {
164 var cache = usersUnreadMsgsCache.get(author)
165 if (!cache) {
166 cache = Set ()
167 usersUnreadMsgsCache.put(author, cache)
168 }
169 return cache
170 }
171
172 function notifications (author) {
173 return computed(getUserUnreadMsgsCache(author), cache => cache.length)
174
175 // TODO find out why this doesn't work
176 // return getUserUnreadMsgsCache(feedId)
177 // .getLength
178 }
179
180 function LevelTwoContext () {
181 const { key, value, feed: targetUser, page } = location
182 const root = get(value, 'content.root', key)
183 if (!targetUser) return
184 if (page === 'userShow') return
185
186
187 const prepend = Option({
188 selected: page === 'threadNew',
189 location: {page: 'threadNew', feed: targetUser},
190 label: h('Button', strings.threadNew.action.new),
191 })
192
193 var userLastMsgCache = usersLastMsgCache.get(targetUser)
194 if (!userLastMsgCache) {
195 userLastMsgCache = MutantArray()
196 usersLastMsgCache.put(targetUser, userLastMsgCache)
197 }
198
199 return api.app.html.scroller({
200 classList: [ 'level', '-two' ],
201 prepend,
202 stream: api.feed.pull.private,
203 filter: () => pull(
204 pull.filter(msg => !msg.value.content.root),
205 pull.filter(msg => msg.value.content.type === 'post'),
206 pull.filter(msg => msg.value.content.recps),
207 pull.filter(msg => msg.value.content.recps
208 .map(recp => typeof recp === 'object' ? recp.link : recp)
209 .some(recp => recp === targetUser)
210 )
211 ),
212 store: userLastMsgCache,
213 updateTop: updateLastMsgCache,
214 updateBottom: updateLastMsgCache,
215 render: (rootMsgObs) => {
216 const rootMsg = resolve(rootMsgObs)
217 return Option({
218 label: api.message.html.subject(rootMsg),
219 selected: rootMsg.key === root,
220 location: Object.assign(rootMsg, { feed: targetUser }),
221 })
222 }
223 })
224
225 function updateLastMsgCache (soFar, newMsg) {
226 soFar.transaction(() => {
227 const { timestamp } = newMsg.value
228 const index = indexOf(soFar, (msg) => timestamp === resolve(msg).value.timestamp)
229
230 if (index >= 0) return
231 // if reference already exists, abort
232
233 var object = Value(newMsg)
234
235 const justOlderPosition = indexOf(soFar, (msg) => timestamp > resolve(msg).value.timestamp)
236 if (justOlderPosition > -1) {
237 soFar.insert(object, justOlderPosition)
238 } else {
239 soFar.push(object)
240 }
241 })
242 }
243 }
244
245 function Option ({ notifications = 0, imageEl, label, location, selected }) {
246 const className = selected ? '-selected' : ''
247 const goToLocation = (e) => {
248 e.preventDefault()
249 e.stopPropagation()
250 api.history.sync.push(resolve(location))
251 }
252
253 if (!imageEl) {
254 return h('Option', { className, 'ev-click': goToLocation }, [
255 h('div.label', label)
256 ])
257 }
258
259 return h('Option', { className }, [
260 h('div.circle', [
261 when(notifications, h('div.alert', notifications)),
262 imageEl
263 ]),
264 h('div.label', { 'ev-click': goToLocation }, label)
265 ])
266 }
267
268 function privateMsgFilter () {
269 return pull(
270 pull.filter(msg => msg.value.content.type === 'post'),
271 pull.filter(msg => msg.value.author != myKey),
272 pull.filter(msg => msg.value.content.recps)
273 )
274 }
275 }
276}
277
278function indexOf (array, fn) {
279 for (var i = 0; i < array.getLength(); i++) {
280 if (fn(array.get(i))) {
281 return i
282 }
283 }
284 return -1
285}
286
287

Built with git-ssb-web