git ssb

10+

Matt McKegg / patchwork



Tree: 971db96b011c54c34ce17deebe0068e374754022

Files: 971db96b011c54c34ce17deebe0068e374754022 / modules / page / html / render / public.js

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

Built with git-ssb-web