git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Tree: be8e636ad6ac100c2299cc221a5e35e396a1ece6

Files: be8e636ad6ac100c2299cc221a5e35e396a1ece6 / modules / page / html / render / public.js

8161 bytesRaw
1var nest = require('depnest')
2var { h, send, when, computed, map } = require('mutant')
3var extend = require('xtend')
4var pull = require('pull-stream')
5
6exports.needs = nest({
7 sbot: {
8 pull: {
9 log: 'first',
10 feed: 'first',
11 userFeed: 'first'
12 },
13 obs: {
14 connectedPeers: 'first',
15 localPeers: 'first'
16 }
17 },
18 'about.html.image': 'first',
19 'about.obs.name': 'first',
20 'invite.sheet': 'first',
21
22 'message.html.compose': 'first',
23 'message.async.publish': 'first',
24 'progress.html.peer': 'first',
25
26 'feed.html.rollup': 'first',
27 'profile.obs.recentlyUpdated': 'first',
28 'contact.obs.following': 'first',
29 'channel.obs': {
30 subscribed: 'first',
31 recent: 'first'
32 },
33 'keys.sync.id': 'first'
34})
35
36exports.gives = nest({
37 'page.html.render': true
38})
39
40exports.create = function (api) {
41 return nest('page.html.render', page)
42
43 function page (path) {
44 if (path !== '/public') return // "/" is a sigil for "page"
45
46 var id = api.keys.sync.id()
47 var following = api.contact.obs.following(id)
48 var subscribedChannels = api.channel.obs.subscribed(id)
49 var loading = computed(subscribedChannels.sync, x => !x)
50 var channels = computed(api.channel.obs.recent(), items => items.slice(0, 8), {comparer: arrayEq})
51 var connectedPeers = api.sbot.obs.connectedPeers()
52 var localPeers = api.sbot.obs.localPeers()
53 var connectedPubs = computed([connectedPeers, localPeers], (c, l) => c.filter(x => !l.includes(x)))
54
55 var oldest = Date.now() - (2 * 24 * 60 * 60e3)
56 getFirstMessage(id, (_, msg) => {
57 if (msg) {
58 // fall back to timestamp stream before this, give 48 hrs for feeds to stabilize
59 if (msg.value.timestamp > oldest) {
60 oldest = Date.now()
61 }
62 }
63 })
64
65 var prepend = [
66 api.message.html.compose({ meta: { type: 'post' }, placeholder: 'Write a public message' })
67 ]
68
69 var feedView = api.feed.html.rollup(getFeed, {
70 prepend,
71 waitFor: computed([
72 following.sync,
73 subscribedChannels.sync
74 ], (...x) => x.every(Boolean)),
75 windowSize: 500,
76 filter: (item) => {
77 return !item.boxed && (item.lastUpdateType !== 'post' || item.message) && (
78 id === item.author ||
79 (item.author && following().has(item.author)) ||
80 (item.type === 'message' && subscribedChannels().has(item.channel)) ||
81 (item.type === 'subscribe' && item.subscribers.size) ||
82 (item.repliesFrom && item.repliesFrom.has(id)) ||
83 item.likes && item.likes.has(id)
84 )
85 },
86 bumpFilter: (msg, group) => {
87 if (group.type === 'subscribe') {
88 removeStrangers(group.subscribers)
89 }
90
91 if (group.type === 'message') {
92 removeStrangers(group.likes)
93 removeStrangers(group.repliesFrom)
94
95 if (!group.message) {
96 // if message is old, only show replies from friends
97 group.replies = group.replies.filter(x => {
98 return (x.value.author === id || following().has(x.value.author))
99 })
100 }
101 }
102
103 if (!group.message) {
104 return (
105 isMentioned(id, msg.value.content.mentions) ||
106 msg.value.author === id || (
107 fromDay(msg, group.fromTime) && (
108 following().has(msg.value.author) ||
109 group.repliesFrom.has(id)
110 )
111 )
112 )
113 }
114 return true
115 }
116 })
117
118 var result = h('div.SplitView', [
119 h('div.side', [
120 getSidebar()
121 ]),
122 h('div.main', feedView)
123 ])
124
125 result.pendingUpdates = feedView.pendingUpdates
126 result.reload = feedView.reload
127
128 return result
129
130 function removeStrangers (set) {
131 if (set) {
132 Array.from(set).forEach(key => {
133 if (!following().has(key) && key !== id) {
134 set.delete(key)
135 }
136 })
137 }
138 }
139
140 function getSidebar () {
141 var whoToFollow = computed([following, api.profile.obs.recentlyUpdated(), localPeers], (following, recent, peers) => {
142 return Array.from(recent).filter(x => x !== id && !following.has(x) && !peers.includes(x)).slice(0, 10)
143 })
144 return [
145 h('button -pub -full', {
146 'ev-click': api.invite.sheet
147 }, '+ Join Pub'),
148 when(computed(channels, x => x.length), h('h2', 'Active Channels')),
149 when(loading, [ h('Loading') ]),
150 h('div', {
151 classList: 'ChannelList',
152 hidden: loading
153 }, [
154 map(channels, (channel) => {
155 var subscribed = subscribedChannels.has(channel)
156 return h('a.channel', {
157 href: `#${channel}`,
158 classList: [
159 when(subscribed, '-subscribed')
160 ]
161 }, [
162 h('span.name', '#' + channel),
163 when(subscribed,
164 h('a.-unsubscribe', {
165 'ev-click': send(unsubscribe, channel)
166 }, 'Unsubscribe'),
167 h('a.-subscribe', {
168 'ev-click': send(subscribe, channel)
169 }, 'Subscribe')
170 )
171 ])
172 }, {maxTime: 5})
173 ]),
174
175 PeerList(localPeers, 'Local'),
176 PeerList(connectedPubs, 'Connected Pubs'),
177
178 when(computed(whoToFollow, x => x.length), h('h2', 'Who to follow')),
179 when(following.sync,
180 h('div', {
181 classList: 'ProfileList'
182 }, [
183 map(whoToFollow, (id) => {
184 return h('a.profile', {
185 href: id
186 }, [
187 h('div.avatar', [api.about.html.image(id)]),
188 h('div.main', [
189 h('div.name', [ api.about.obs.name(id) ])
190 ])
191 ])
192 })
193 ])
194 )
195 ]
196 }
197
198 function PeerList (ids, title) {
199 return [
200 when(computed(ids, x => x.length), h('h2', title)),
201 h('div', {
202 classList: 'ProfileList'
203 }, [
204 map(ids, (id) => {
205 return h('a.profile', {
206 classList: [ '-connected' ],
207 href: id
208 }, [
209 h('div.avatar', [api.about.html.image(id)]),
210 h('div.main', [
211 h('div.name', [ api.about.obs.name(id) ])
212 ]),
213 h('div.progress', [
214 api.progress.html.peer(id)
215 ])
216 ])
217 })
218 ])
219 ]
220 }
221
222 function getFeed (opts) {
223 if (opts.lt && opts.lt < oldest) {
224 opts = extend(opts, {lt: parseInt(opts.lt, 10)})
225 }
226
227 return pull(
228 api.sbot.pull.feed(opts),
229 pull.map((msg) => {
230 if (msg.sync) return msg
231 return {key: msg.key, value: msg.value, timestamp: msg.value.timestamp}
232 })
233 )
234
235 // if (opts.lt && opts.lt < oldest) {
236 // opts = extend(opts, {lt: parseInt(opts.lt, 10)})
237 // return pull(
238 // api.sbot.pull.feed(opts),
239 // pull.map((msg) => {
240 // if (msg.sync) {
241 // return msg
242 // } else {
243 // return {key: msg.key, value: msg.value, timestamp: msg.value.timestamp}
244 // }
245 // })
246 // )
247 // } else {
248 // return api.sbot.pull.log(opts)
249 // }
250 }
251
252 function getFirstMessage (feedId, cb) {
253 api.sbot.pull.userFeed({id: feedId, gte: 0, limit: 1})(null, cb)
254 }
255
256 function subscribe (id) {
257 api.message.async.publish({
258 type: 'channel',
259 channel: id,
260 subscribed: true
261 })
262 }
263
264 function unsubscribe (id) {
265 api.message.async.publish({
266 type: 'channel',
267 channel: id,
268 subscribed: false
269 })
270 }
271 }
272}
273
274function isMentioned (id, list) {
275 if (Array.isArray(list)) {
276 return list.includes(id)
277 } else {
278 return false
279 }
280}
281
282function fromDay (msg, fromTime) {
283 return (fromTime - msg.timestamp) < (24 * 60 * 60e3)
284}
285
286function arrayEq (a, b) {
287 if (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a !== b) {
288 return a.every((value, i) => value === b[i])
289 }
290}
291

Built with git-ssb-web