git ssb

10+

Matt McKegg / patchwork



Tree: c115a76269ff7b6be741fd07738c2364a4bcea1d

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

7519 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 recentChannels = api.channel.obs.recent()
50 var loading = computed([subscribedChannels.sync, recentChannels.sync], (...args) => !args.every(Boolean))
51 var channels = computed(recentChannels, items => items.slice(0, 8), {comparer: arrayEq})
52 var connectedPeers = api.sbot.obs.connectedPeers()
53 var localPeers = api.sbot.obs.localPeers()
54 var connectedPubs = computed([connectedPeers, localPeers], (c, l) => c.filter(x => !l.includes(x)))
55
56 var prepend = [
57 api.message.html.compose({ meta: { type: 'post' }, placeholder: 'Write a public message' })
58 ]
59
60 var feedView = api.feed.html.rollup(getFeed, {
61 prepend,
62 waitFor: computed([
63 following.sync,
64 subscribedChannels.sync
65 ], (...x) => x.every(Boolean)),
66 windowSize: 1000,
67 filter: (item) => {
68 return !item.boxed && (item.lastUpdateType !== 'post' || item.message) && (
69 id === item.author ||
70 (item.author && following().has(item.author)) ||
71 (item.type === 'message' && subscribedChannels().has(item.channel)) ||
72 (item.type === 'subscribe' && item.subscribers.size) ||
73 (item.repliesFrom && item.repliesFrom.has(id)) ||
74 item.likes && item.likes.has(id)
75 )
76 },
77 bumpFilter: (msg, group) => {
78 if (group.type === 'subscribe') {
79 removeStrangers(group.subscribers)
80 }
81
82 if (group.type === 'message') {
83 removeStrangers(group.likes)
84 removeStrangers(group.repliesFrom)
85
86 if (!group.message) {
87 // if message is old, only show replies from friends
88 group.replies = group.replies.filter(x => {
89 return (x.value.author === id || following().has(x.value.author))
90 })
91 }
92 }
93
94 if (!group.message) {
95 return (
96 isMentioned(id, msg.value.content.mentions) ||
97 msg.value.author === id || (
98 fromDay(msg, group.fromTime) && (
99 following().has(msg.value.author) ||
100 group.repliesFrom.has(id)
101 )
102 )
103 )
104 }
105 return true
106 }
107 })
108
109 var result = h('div.SplitView', [
110 h('div.side', [
111 getSidebar()
112 ]),
113 h('div.main', feedView)
114 ])
115
116 result.pendingUpdates = feedView.pendingUpdates
117 result.reload = feedView.reload
118
119 return result
120
121 function removeStrangers (set) {
122 if (set) {
123 Array.from(set).forEach(key => {
124 if (!following().has(key) && key !== id) {
125 set.delete(key)
126 }
127 })
128 }
129 }
130
131 function getSidebar () {
132 var whoToFollow = computed([following, api.profile.obs.recentlyUpdated(), localPeers], (following, recent, peers) => {
133 return Array.from(recent).filter(x => x !== id && !following.has(x) && !peers.includes(x)).slice(0, 10)
134 })
135 return [
136 h('button -pub -full', {
137 'ev-click': api.invite.sheet
138 }, '+ Join Pub'),
139 when(computed(channels, x => x.length), h('h2', 'Active Channels')),
140 when(loading, [ h('Loading') ]),
141 h('div', {
142 classList: 'ChannelList',
143 hidden: loading
144 }, [
145 map(channels, (channel) => {
146 var subscribed = subscribedChannels.has(channel)
147 return h('a.channel', {
148 href: `#${channel}`,
149 classList: [
150 when(subscribed, '-subscribed')
151 ]
152 }, [
153 h('span.name', '#' + channel),
154 when(subscribed,
155 h('a.-unsubscribe', {
156 'ev-click': send(unsubscribe, channel)
157 }, 'Unsubscribe'),
158 h('a.-subscribe', {
159 'ev-click': send(subscribe, channel)
160 }, 'Subscribe')
161 )
162 ])
163 }, {maxTime: 5}),
164 h('a.channel -more', {href: '/channels'}, 'More Channels...')
165 ]),
166
167 PeerList(localPeers, 'Local'),
168 PeerList(connectedPubs, 'Connected Pubs'),
169
170 when(computed(whoToFollow, x => x.length), h('h2', 'Who to follow')),
171 when(following.sync,
172 h('div', {
173 classList: 'ProfileList'
174 }, [
175 map(whoToFollow, (id) => {
176 return h('a.profile', {
177 href: id
178 }, [
179 h('div.avatar', [api.about.html.image(id)]),
180 h('div.main', [
181 h('div.name', [ api.about.obs.name(id) ])
182 ])
183 ])
184 })
185 ])
186 )
187 ]
188 }
189
190 function PeerList (ids, title) {
191 return [
192 when(computed(ids, x => x.length), h('h2', title)),
193 h('div', {
194 classList: 'ProfileList'
195 }, [
196 map(ids, (id) => {
197 return h('a.profile', {
198 classList: [ '-connected' ],
199 href: id
200 }, [
201 h('div.avatar', [api.about.html.image(id)]),
202 h('div.main', [
203 h('div.name', [ api.about.obs.name(id) ])
204 ]),
205 h('div.progress', [
206 api.progress.html.peer(id)
207 ])
208 ])
209 })
210 ])
211 ]
212 }
213
214 function getFeed (opts) {
215 if (opts.lt) {
216 opts = extend(opts, {lt: parseInt(opts.lt, 10)})
217 }
218
219 return pull(
220 api.sbot.pull.feed(opts),
221 pull.map((msg) => {
222 if (msg.sync) return msg
223 return {key: msg.key, value: msg.value, timestamp: msg.value.timestamp}
224 })
225 )
226 }
227
228 function getFirstMessage (feedId, cb) {
229 api.sbot.pull.userFeed({id: feedId, gte: 0, limit: 1})(null, cb)
230 }
231
232 function subscribe (id) {
233 api.message.async.publish({
234 type: 'channel',
235 channel: id,
236 subscribed: true
237 })
238 }
239
240 function unsubscribe (id) {
241 api.message.async.publish({
242 type: 'channel',
243 channel: id,
244 subscribed: false
245 })
246 }
247 }
248}
249
250function isMentioned (id, list) {
251 if (Array.isArray(list)) {
252 return list.includes(id)
253 } else {
254 return false
255 }
256}
257
258function fromDay (msg, fromTime) {
259 return (fromTime - msg.timestamp) < (24 * 60 * 60e3)
260}
261
262function arrayEq (a, b) {
263 if (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a !== b) {
264 return a.every((value, i) => value === b[i])
265 }
266}
267

Built with git-ssb-web