git ssb

10+

Matt McKegg / patchwork



Tree: 5751bfc7a66e62b9d47d5cc86f6a753fae3f41a0

Files: 5751bfc7a66e62b9d47d5cc86f6a753fae3f41a0 / modules / page / html / render / public.js

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

Built with git-ssb-web