git ssb

10+

Matt McKegg / patchwork



Tree: 14f790fb7e7f37a2f2427d5587b29c83e420986b

Files: 14f790fb7e7f37a2f2427d5587b29c83e420986b / modules / page / html / render / public.js

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

Built with git-ssb-web