Commit 3779d0d650174a19772e8ccad74d892554310b54
initial work upgrading to use latest patchbay (currently only displaying patchbay views)
still need to port all of the patchwork-next views to the new apiMatt McKegg committed on 2/10/2017, 8:21:26 AM
Parent: 02bc77c77c6801ad0cfcffd1a585dfed52a2f5f8
Files changed
lib/friends-with-gossip-priority.js | ||
---|---|---|
@@ -1,178 +1,0 @@ | ||
1 | -var Graphmitter = require('graphmitter') | |
2 | -var pull = require('pull-stream') | |
3 | -var mlib = require('ssb-msgs') | |
4 | -var memview = require('level-memview') | |
5 | -var pushable = require('pull-pushable') | |
6 | -var mdm = require('mdmanifest') | |
7 | -var valid = require('scuttlebot/lib/validators') | |
8 | -var apidoc = require('scuttlebot/lib/apidocs').friends | |
9 | - | |
10 | -// friends plugin | |
11 | -// methods to analyze the social graph | |
12 | -// maintains a 'follow' and 'flag' graph | |
13 | - | |
14 | -function isFunction (f) { | |
15 | - return 'function' === typeof f | |
16 | -} | |
17 | - | |
18 | -function isString (s) { | |
19 | - return 'string' === typeof s | |
20 | -} | |
21 | - | |
22 | -function isFriend (friends, a, b) { | |
23 | - return friends[a] && friends[b] && friends[a][b] && friends[b][a] | |
24 | -} | |
25 | - | |
26 | -exports.name = 'friends' | |
27 | -exports.version = '1.0.0' | |
28 | -exports.manifest = mdm.manifest(apidoc) | |
29 | - | |
30 | -exports.init = function (sbot, config) { | |
31 | - | |
32 | - var graphs = { | |
33 | - follow: new Graphmitter(), | |
34 | - flag: new Graphmitter() | |
35 | - } | |
36 | - | |
37 | - // view processor | |
38 | - var syncCbs = [] | |
39 | - function awaitSync (cb) { | |
40 | - if (syncCbs) syncCbs.push(cb) | |
41 | - else cb() | |
42 | - } | |
43 | - | |
44 | - // read/watch the log for changes to the social graph | |
45 | - pull(sbot.createLogStream({ live: true }), pull.drain(function (msg) { | |
46 | - | |
47 | - if (msg.sync) { | |
48 | - syncCbs.forEach(function (cb) { cb() }) | |
49 | - syncCbs = null | |
50 | - | |
51 | - if (sbot.gossip) { | |
52 | - // prioritize friends | |
53 | - var friends = graphs['follow'].toJSON() | |
54 | - sbot.gossip.peers().forEach(function(peer) { | |
55 | - if (isFriend(friends, sbot.id, peer.key)) { | |
56 | - sbot.gossip.add(peer, 'friends') | |
57 | - } | |
58 | - }) | |
59 | - } | |
60 | - | |
61 | - return | |
62 | - } | |
63 | - | |
64 | - var c = msg.value.content | |
65 | - if (c.type == 'contact') { | |
66 | - mlib.asLinks(c.contact, 'feed').forEach(function (link) { | |
67 | - if ('following' in c) { | |
68 | - if (c.following) | |
69 | - graphs.follow.edge(msg.value.author, link.link, true) | |
70 | - else | |
71 | - graphs.follow.del(msg.value.author, link.link) | |
72 | - | |
73 | - } | |
74 | - if ('flagged' in c) { | |
75 | - if (c.flagged) | |
76 | - graphs.flag.edge(msg.value.author, link.link, c.flagged) | |
77 | - else | |
78 | - graphs.flag.del(msg.value.author, link.link) | |
79 | - } | |
80 | - }) | |
81 | - } | |
82 | - })) | |
83 | - | |
84 | - return { | |
85 | - | |
86 | - get: valid.sync(function (opts) { | |
87 | - var g = graphs[opts.graph || 'follow'] | |
88 | - if(!g) throw new Error('opts.graph must be provided') | |
89 | - return g.get(opts.source, opts.dest) | |
90 | - }, 'object?'), | |
91 | - | |
92 | - all: valid.async(function (graph, cb) { | |
93 | - if (typeof graph == 'function') { | |
94 | - cb = graph | |
95 | - graph = null | |
96 | - } | |
97 | - if (!graph) | |
98 | - graph = 'follow' | |
99 | - awaitSync(function () { | |
100 | - cb(null, graphs[graph] ? graphs[graph].toJSON() : null) | |
101 | - }) | |
102 | - }, 'string?'), | |
103 | - | |
104 | - path: valid.sync(function (opts) { | |
105 | - if(isString(opts)) | |
106 | - opts = {source: sbot.id, dest: opts} | |
107 | - return graphs.follow.path(opts) | |
108 | - | |
109 | - }, 'string|object'), | |
110 | - | |
111 | - createFriendStream: valid.source(function (opts) { | |
112 | - opts = opts || {} | |
113 | - var live = opts.live === true | |
114 | - var meta = opts.meta === true | |
115 | - var start = opts.start || sbot.id | |
116 | - var graph = graphs[opts.graph || 'follow'] | |
117 | - if(!graph) | |
118 | - return pull.error(new Error('unknown graph:' + opts.graph)) | |
119 | - var cancel, ps = pushable(function () { | |
120 | - cancel && cancel() | |
121 | - }) | |
122 | - | |
123 | - function push (to, hops) { | |
124 | - return ps.push(meta ? {id: to, hops: hops} : to) | |
125 | - } | |
126 | - | |
127 | - //by default, also emit your own key. | |
128 | - if(opts.self !== false) | |
129 | - push(start, 0) | |
130 | - | |
131 | - var conf = config.friends || {} | |
132 | - cancel = graph.traverse({ | |
133 | - start: start, | |
134 | - hops: opts.hops || conf.hops || 3, | |
135 | - max: opts.dunbar || conf.dunbar || 150, | |
136 | - each: function (_, to, hops) { | |
137 | - if(to !== start) push(to, hops) | |
138 | - } | |
139 | - }) | |
140 | - | |
141 | - if(!live) { cancel(); ps.end() } | |
142 | - | |
143 | - return ps | |
144 | - }, 'createFriendStreamOpts?'), | |
145 | - | |
146 | - hops: valid.async(function (start, graph, opts, cb) { | |
147 | - if (typeof opts == 'function') { // (start|opts, graph, cb) | |
148 | - cb = opts | |
149 | - opts = null | |
150 | - } else if (typeof graph == 'function') { // (start|opts, cb) | |
151 | - cb = graph | |
152 | - opts = graph = null | |
153 | - } | |
154 | - opts = opts || {} | |
155 | - if(isString(start)) { // (start, ...) | |
156 | - // first arg is id string | |
157 | - opts.start = start | |
158 | - } else if (start && typeof start == 'object') { // (opts, ...) | |
159 | - // first arg is opts | |
160 | - for (var k in start) | |
161 | - opts[k] = start[k] | |
162 | - } | |
163 | - | |
164 | - var conf = config.friends || {} | |
165 | - opts.start = opts.start || sbot.id | |
166 | - opts.dunbar = opts.dunbar || conf.dunbar || 150 | |
167 | - opts.hops = opts.hops || conf.hops || 3 | |
168 | - | |
169 | - var g = graphs[graph || 'follow'] | |
170 | - if (!g) | |
171 | - return cb(new Error('Invalid graph type: '+graph)) | |
172 | - | |
173 | - awaitSync(function () { | |
174 | - cb(null, g.traverse(opts)) | |
175 | - }) | |
176 | - }, ['feedId', 'string?', 'object?'], ['createFriendStreamOpts']) | |
177 | - } | |
178 | -} |
lib/persistent-gossip/index.js | ||
---|---|---|
@@ -1,255 +1,0 @@ | ||
1 | -'use strict' | |
2 | -var pull = require('pull-stream') | |
3 | -var Notify = require('pull-notify') | |
4 | -var mdm = require('mdmanifest') | |
5 | -var valid = require('scuttlebot/lib/validators') | |
6 | -var apidoc = require('scuttlebot/lib/apidocs').gossip | |
7 | -var u = require('scuttlebot/lib/util') | |
8 | -var ref = require('ssb-ref') | |
9 | -var ping = require('pull-ping') | |
10 | -var stats = require('statistics') | |
11 | -var Schedule = require('./schedule') | |
12 | -var Init = require('./init') | |
13 | -var AtomicFile = require('atomic-file') | |
14 | -var path = require('path') | |
15 | -var deepEqual = require('deep-equal') | |
16 | - | |
17 | -function isFunction (f) { | |
18 | - return 'function' === typeof f | |
19 | -} | |
20 | - | |
21 | -function stringify(peer) { | |
22 | - return [peer.host, peer.port, peer.key].join(':') | |
23 | -} | |
24 | - | |
25 | -/* | |
26 | -Peers : [{ | |
27 | - key: id, | |
28 | - host: ip, | |
29 | - port: int, | |
30 | - //to be backwards compatible with patchwork... | |
31 | - announcers: {length: int} | |
32 | - source: 'pub'|'manual'|'local' | |
33 | -}] | |
34 | -*/ | |
35 | - | |
36 | - | |
37 | -module.exports = { | |
38 | - name: 'gossip', | |
39 | - version: '1.0.0', | |
40 | - manifest: mdm.manifest(apidoc), | |
41 | - permissions: { | |
42 | - anonymous: {allow: ['ping']} | |
43 | - }, | |
44 | - init: function (server, config) { | |
45 | - var notify = Notify() | |
46 | - var conf = config.gossip || {} | |
47 | - var home = ref.parseAddress(server.getAddress()) | |
48 | - | |
49 | - var stateFile = AtomicFile(path.join(config.path, 'gossip.json')) | |
50 | - | |
51 | - //Known Peers | |
52 | - var peers = [] | |
53 | - | |
54 | - function getPeer(id) { | |
55 | - return u.find(peers, function (e) { | |
56 | - return e && e.key === id | |
57 | - }) | |
58 | - } | |
59 | - | |
60 | - var timer_ping = 5*6e4 | |
61 | - | |
62 | - var gossip = { | |
63 | - wakeup: 0, | |
64 | - peers: function () { | |
65 | - return peers | |
66 | - }, | |
67 | - get: function (addr) { | |
68 | - addr = ref.parseAddress(addr) | |
69 | - return u.find(peers, function (a) { | |
70 | - return ( | |
71 | - addr.port === a.port | |
72 | - && addr.host === a.host | |
73 | - && addr.key === a.key | |
74 | - ) | |
75 | - }) | |
76 | - }, | |
77 | - connect: valid.async(function (addr, cb) { | |
78 | - addr = ref.parseAddress(addr) | |
79 | - if (!addr || typeof addr != 'object') | |
80 | - return cb(new Error('first param must be an address')) | |
81 | - | |
82 | - if(!addr.key) return cb(new Error('address must have ed25519 key')) | |
83 | - // add peer to the table, incase it isn't already. | |
84 | - gossip.add(addr, 'manual') | |
85 | - var p = gossip.get(addr) | |
86 | - if(!p) return cb() | |
87 | - | |
88 | - p.stateChange = Date.now() | |
89 | - p.state = 'connecting' | |
90 | - server.connect(p, function (err, rpc) { | |
91 | - if (err) { | |
92 | - p.state = undefined | |
93 | - p.failure = (p.failure || 0) + 1 | |
94 | - p.stateChange = Date.now() | |
95 | - notify({ type: 'connect-failure', peer: p }) | |
96 | - server.emit('log:info', ['SBOT', p.host+':'+p.port+p.key, 'connection failed', err.message || err]) | |
97 | - p.duration = stats(p.duration, 0) | |
98 | - return (cb && cb(err)) | |
99 | - } | |
100 | - else { | |
101 | - p.state = 'connected' | |
102 | - p.failure = 0 | |
103 | - } | |
104 | - cb && cb(null, rpc) | |
105 | - }) | |
106 | - | |
107 | - }, 'string|object'), | |
108 | - | |
109 | - disconnect: valid.async(function (addr, cb) { | |
110 | - var peer = this.get(addr) | |
111 | - | |
112 | - peer.state = 'disconnecting' | |
113 | - peer.stateChange = Date.now() | |
114 | - if(!peer || !peer.disconnect) cb && cb() | |
115 | - else peer.disconnect(true, function (err) { | |
116 | - peer.stateChange = Date.now() | |
117 | - }) | |
118 | - | |
119 | - }, 'string|object'), | |
120 | - | |
121 | - changes: function () { | |
122 | - return notify.listen() | |
123 | - }, | |
124 | - //add an address to the peer table. | |
125 | - add: valid.sync(function (addr, source) { | |
126 | - addr = ref.parseAddress(addr) | |
127 | - if(!ref.isAddress(addr)) | |
128 | - throw new Error('not a valid address:' + JSON.stringify(addr)) | |
129 | - // check that this is a valid address, and not pointing at self. | |
130 | - | |
131 | - if(addr.key === home.key) return | |
132 | - if(addr.host === home.host && addr.port === home.port) return | |
133 | - | |
134 | - var f = gossip.get(addr) | |
135 | - | |
136 | - if(!f) { | |
137 | - // new peer | |
138 | - addr.source = source | |
139 | - addr.announcers = 1 | |
140 | - addr.duration = addr.duration || null | |
141 | - peers.push(addr) | |
142 | - notify({ type: 'discover', peer: addr, source: source || 'manual' }) | |
143 | - return addr | |
144 | - } else if (source === 'friends' || source === 'local') { | |
145 | - // this peer is a friend or local, override old source to prioritize gossip | |
146 | - f.source = source | |
147 | - } | |
148 | - //don't count local over and over | |
149 | - else if(f.source != 'local') | |
150 | - f.announcers ++ | |
151 | - | |
152 | - return f | |
153 | - }, 'string|object', 'string?'), | |
154 | - delete: function (addr) { | |
155 | - var peer = gossip.get(addr) | |
156 | - var index = peers.indexOf(peer) | |
157 | - if (~index) { | |
158 | - peers.splice(index, 1) | |
159 | - } | |
160 | - }, | |
161 | - ping: function (opts) { | |
162 | - var timeout = config.timers && config.timers.ping || 5*60e3 | |
163 | - //between 10 seconds and 30 minutes, default 5 min | |
164 | - timeout = Math.max(10e3, Math.min(timeout, 30*60e3)) | |
165 | - return ping({timeout: timeout}) | |
166 | - }, | |
167 | - reconnect: function () { | |
168 | - for(var id in server.peers) | |
169 | - if(id !== server.id) //don't disconnect local client | |
170 | - server.peers[id].forEach(function (peer) { | |
171 | - peer.close(true) | |
172 | - }) | |
173 | - return gossip.wakeup = Date.now() | |
174 | - } | |
175 | - } | |
176 | - | |
177 | - Schedule (gossip, config, server) | |
178 | - Init (gossip, config, server) | |
179 | - //get current state | |
180 | - | |
181 | - server.on('rpc:connect', function (rpc, isClient) { | |
182 | - var peer = getPeer(rpc.id) | |
183 | - //don't track clients that connect, but arn't considered peers. | |
184 | - //maybe we should though? | |
185 | - if(!peer) return | |
186 | - console.log('Connected', stringify(peer)) | |
187 | - //means that we have created this connection, not received it. | |
188 | - peer.client = !!isClient | |
189 | - peer.state = 'connected' | |
190 | - peer.stateChange = Date.now() | |
191 | - peer.disconnect = function (err, cb) { | |
192 | - if(isFunction(err)) cb = err, err = null | |
193 | - rpc.close(err, cb) | |
194 | - } | |
195 | - | |
196 | - if(isClient) { | |
197 | - //default ping is 5 minutes... | |
198 | - var pp = ping({serve: true, timeout: timer_ping}, function (_) {}) | |
199 | - peer.ping = {rtt: pp.rtt, skew: pp.skew} | |
200 | - pull( | |
201 | - pp, | |
202 | - rpc.gossip.ping({timeout: timer_ping}, function (err) { | |
203 | - if(err.name === 'TypeError') peer.ping.fail = true | |
204 | - }), | |
205 | - pp | |
206 | - ) | |
207 | - } | |
208 | - | |
209 | - rpc.on('closed', function () { | |
210 | - console.log('Disconnected', stringify(peer)) | |
211 | - //track whether we have successfully connected. | |
212 | - //or how many failures there have been. | |
213 | - var since = peer.stateChange | |
214 | - peer.stateChange = Date.now() | |
215 | -// if(peer.state === 'connected') //may be "disconnecting" | |
216 | - peer.duration = stats(peer.duration, peer.stateChange - since) | |
217 | -// console.log(peer.duration) | |
218 | - peer.state = undefined | |
219 | - notify({ type: 'disconnect', peer: peer }) | |
220 | - server.emit('log:info', ['SBOT', rpc.id, 'disconnect']) | |
221 | - }) | |
222 | - | |
223 | - notify({ type: 'connect', peer: peer }) | |
224 | - }) | |
225 | - | |
226 | - var last | |
227 | - stateFile.get(function (err, ary) { | |
228 | - last = ary || [] | |
229 | - if(Array.isArray(ary)) | |
230 | - ary.forEach(function (v) { | |
231 | - delete v.state | |
232 | - // don't add local peers (wait to rediscover) | |
233 | - if(v.source !== 'local') { | |
234 | - gossip.add(v, 'stored') | |
235 | - } | |
236 | - }) | |
237 | - }) | |
238 | - | |
239 | - var int = setInterval(function () { | |
240 | - var copy = JSON.parse(JSON.stringify(peers)) | |
241 | - copy.forEach(function (e) { | |
242 | - delete e.state | |
243 | - }) | |
244 | - if(deepEqual(copy, last)) return | |
245 | - last = copy | |
246 | - stateFile.set(copy, function(err) { | |
247 | - if (err) console.log(err) | |
248 | - }) | |
249 | - }, 10*1000) | |
250 | - | |
251 | - if(int.unref) int.unref() | |
252 | - | |
253 | - return gossip | |
254 | - } | |
255 | -} |
lib/persistent-gossip/init.js | ||
---|---|---|
@@ -1,31 +1,0 @@ | ||
1 | -var isArray = Array.isArray | |
2 | -var pull = require('pull-stream') | |
3 | -var ref = require('ssb-ref') | |
4 | - | |
5 | -module.exports = function (gossip, config, server) { | |
6 | - | |
7 | - // populate peertable with configured seeds (mainly used in testing) | |
8 | - var seeds = config.seeds | |
9 | - | |
10 | - ;(isArray(seeds) ? seeds : [seeds]).filter(Boolean) | |
11 | - .forEach(function (addr) { gossip.add(addr, 'seed') }) | |
12 | - | |
13 | - // populate peertable with pub announcements on the feed | |
14 | - pull( | |
15 | - server.messagesByType({ | |
16 | - type: 'pub', live: true, keys: false | |
17 | - }), | |
18 | - pull.drain(function (msg) { | |
19 | - if(msg.sync) return | |
20 | - if(!msg.content.address) return | |
21 | - if(ref.isAddress(msg.content.address)) | |
22 | - gossip.add(msg.content.address, 'pub') | |
23 | - }) | |
24 | - ) | |
25 | - | |
26 | - // populate peertable with announcements on the LAN multicast | |
27 | - server.on('local', function (_peer) { | |
28 | - gossip.add(_peer, 'local') | |
29 | - }) | |
30 | - | |
31 | -} |
lib/persistent-gossip/schedule.js | ||
---|---|---|
@@ -1,233 +1,0 @@ | ||
1 | -var ip = require('ip') | |
2 | -var onWakeup = require('on-wakeup') | |
3 | -var onNetwork = require('on-change-network') | |
4 | -var hasNetwork = require('has-network') | |
5 | - | |
6 | -var pull = require('pull-stream') | |
7 | - | |
8 | -function not (fn) { | |
9 | - return function (e) { return !fn(e) } | |
10 | -} | |
11 | - | |
12 | -function and () { | |
13 | - var args = [].slice.call(arguments) | |
14 | - return function (value) { | |
15 | - return args.every(function (fn) { return fn.call(null, value) }) | |
16 | - } | |
17 | -} | |
18 | - | |
19 | -//min delay (delay since last disconnect of most recent peer in unconnected set) | |
20 | -//unconnected filter delay peer < min delay | |
21 | -function delay (failures, factor, max) { | |
22 | - return Math.min(Math.pow(2, failures)*factor, max || Infinity) | |
23 | -} | |
24 | - | |
25 | -function maxStateChange (M, e) { | |
26 | - return Math.max(M, e.stateChange || 0) | |
27 | -} | |
28 | - | |
29 | -function peerNext(peer, opts) { | |
30 | - return (peer.stateChange|0) + delay(peer.failure|0, opts.factor, opts.max) | |
31 | -} | |
32 | - | |
33 | - | |
34 | -//detect if not connected to wifi or other network | |
35 | -//(i.e. if there is only localhost) | |
36 | - | |
37 | -function isOffline (e) { | |
38 | - if(ip.isLoopback(e.host)) return false | |
39 | - return !hasNetwork() | |
40 | -} | |
41 | - | |
42 | -var isOnline = not(isOffline) | |
43 | - | |
44 | -function isLocal (e) { | |
45 | - // don't rely on private ip address, because | |
46 | - // cjdns creates fake private ip addresses. | |
47 | - return ip.isPrivate(e.host) && e.source === 'local' | |
48 | -} | |
49 | - | |
50 | -function isFriend (e) { | |
51 | - return e.source === 'friends' | |
52 | -} | |
53 | - | |
54 | -function isUnattempted (e) { | |
55 | - return !e.stateChange | |
56 | -} | |
57 | - | |
58 | -//select peers which have never been successfully connected to yet, | |
59 | -//but have been tried. | |
60 | -function isInactive (e) { | |
61 | - return e.stateChange && (!e.duration || e.duration.mean == 0) | |
62 | -} | |
63 | - | |
64 | -function isLongterm (e) { | |
65 | - return e.ping && e.ping.rtt && e.ping.rtt.mean > 0 | |
66 | -} | |
67 | - | |
68 | -//peers which we can connect to, but are not upgraded. | |
69 | -//select peers which we can connect to, but are not upgraded to LT. | |
70 | -//assume any peer is legacy, until we know otherwise... | |
71 | -function isLegacy (peer) { | |
72 | - return peer.duration && peer.duration.mean > 0 && !exports.isLongterm(peer) | |
73 | -} | |
74 | - | |
75 | -function isConnect (e) { | |
76 | - return 'connected' === e.state || 'connecting' === e.state | |
77 | -} | |
78 | - | |
79 | -//sort oldest to newest then take first n | |
80 | -function earliest(peers, n) { | |
81 | - return peers.sort(function (a, b) { | |
82 | - return a.stateChange - b.stateChange | |
83 | - }).slice(0, Math.max(n, 0)) | |
84 | -} | |
85 | - | |
86 | -function select(peers, ts, filter, opts) { | |
87 | - if(opts.disable) return [] | |
88 | - //opts: { quota, groupMin, min, factor, max } | |
89 | - var type = peers.filter(filter) | |
90 | - var unconnect = type.filter(not(isConnect)) | |
91 | - var count = Math.max(opts.quota - type.filter(isConnect).length, 0) | |
92 | - var min = unconnect.reduce(maxStateChange, 0) + opts.groupMin | |
93 | - if(ts < min) return [] | |
94 | - | |
95 | - return earliest(unconnect.filter(function (peer) { | |
96 | - return peerNext(peer, opts) < ts | |
97 | - }), count) | |
98 | -} | |
99 | - | |
100 | -var schedule = exports = module.exports = | |
101 | -function (gossip, config, server) { | |
102 | -// return | |
103 | - var min = 60e3, hour = 60*60e3 | |
104 | - | |
105 | - //trigger hard reconnect after suspend or local network changes | |
106 | - onWakeup(gossip.reconnect) | |
107 | - onNetwork(gossip.reconnect) | |
108 | - | |
109 | - function conf(name, def) { | |
110 | - if(!config.gossip) return def | |
111 | - var value = config.gossip[name] | |
112 | - return (value === undefined || value === '') ? def : value | |
113 | - } | |
114 | - | |
115 | - function connect (peers, ts, name, filter, opts) { | |
116 | - opts.group = name | |
117 | - var connected = peers.filter(isConnect).filter(filter) | |
118 | - | |
119 | - //disconnect if over quota | |
120 | - if(connected.length > opts.quota) { | |
121 | - return earliest(connected, connected.length - opts.quota) | |
122 | - .forEach(function (peer) { | |
123 | - gossip.disconnect(peer) | |
124 | - }) | |
125 | - } | |
126 | - | |
127 | - //will return [] if the quota is full | |
128 | - var selected = select(peers, ts, and(filter, isOnline), opts) | |
129 | - selected | |
130 | - .forEach(function (peer) { | |
131 | - gossip.connect(peer) | |
132 | - }) | |
133 | - } | |
134 | - | |
135 | - | |
136 | - var connecting = false | |
137 | - function connections () { | |
138 | - if(connecting) return | |
139 | - connecting = true | |
140 | - setTimeout(function () { | |
141 | - connecting = false | |
142 | - var ts = Date.now() | |
143 | - var peers = gossip.peers() | |
144 | - | |
145 | - var connected = peers.filter(and(isConnect, not(isLocal), not(isFriend))).length | |
146 | - var connectedFriends = peers.filter(and(isConnect, isFriend)).length | |
147 | - | |
148 | - connect(peers, ts, 'local', exports.isLocal, { | |
149 | - quota: 3, factor: 2e3, max: 10*min, groupMin: 1e3, | |
150 | - disable: !conf('local', true) | |
151 | - }) | |
152 | - | |
153 | - // prioritize friends | |
154 | - connect(peers, ts, 'friends', and(exports.isFriend, exports.isLongterm), { | |
155 | - quota: 2, factor: 10e3, max: 10*min, groupMin: 5e3, | |
156 | - disable: !conf('local', true) | |
157 | - }) | |
158 | - | |
159 | - if (connectedFriends < 2) | |
160 | - connect(peers, ts, 'attemptFriend', and(exports.isFriend, exports.isUnattempted), { | |
161 | - min: 0, quota: 1, factor: 0, max: 0, groupMin: 0, | |
162 | - disable: !conf('global', true) | |
163 | - }) | |
164 | - | |
165 | - connect(peers, ts, 'retryFriends', and(exports.isFriend, exports.isInactive), { | |
166 | - min: 0, | |
167 | - quota: 3, factor: 60e3, max: 3*60*60e3, groupMin: 5*60e3 | |
168 | - }) | |
169 | - | |
170 | - // standard longterm peers | |
171 | - connect(peers, ts, 'longterm', and( | |
172 | - exports.isLongterm, | |
173 | - not(exports.isFriend), | |
174 | - not(exports.isLocal) | |
175 | - ), { | |
176 | - quota: 2, factor: 10e3, max: 10*min, groupMin: 5e3, | |
177 | - disable: !conf('global', true) | |
178 | - }) | |
179 | - | |
180 | - if(!connected) | |
181 | - connect(peers, ts, 'attempt', exports.isUnattempted, { | |
182 | - min: 0, quota: 1, factor: 0, max: 0, groupMin: 0, | |
183 | - disable: !conf('global', true) | |
184 | - }) | |
185 | - | |
186 | - //quota, groupMin, min, factor, max | |
187 | - connect(peers, ts, 'retry', exports.isInactive, { | |
188 | - min: 0, | |
189 | - quota: 3, factor: 5*60e3, max: 3*60*60e3, groupMin: 5*50e3 | |
190 | - }) | |
191 | - | |
192 | - var longterm = peers.filter(isConnect).filter(exports.isLongterm).length | |
193 | - | |
194 | - connect(peers, ts, 'legacy', exports.isLegacy, { | |
195 | - quota: 3 - longterm, | |
196 | - factor: 5*min, max: 3*hour, groupMin: 5*min, | |
197 | - disable: !conf('global', true) | |
198 | - }) | |
199 | - | |
200 | - peers.filter(isConnect).forEach(function (e) { | |
201 | - var permanent = exports.isLongterm(e) || exports.isLocal(e) | |
202 | - if((!permanent || e.state === 'connecting') && e.stateChange + 10e3 < ts) { | |
203 | - gossip.disconnect(e) | |
204 | - } | |
205 | - }) | |
206 | - | |
207 | - }, 100*Math.random()) | |
208 | - | |
209 | - } | |
210 | - | |
211 | - pull( | |
212 | - gossip.changes(), | |
213 | - pull.drain(function (ev) { | |
214 | - if(ev.type == 'disconnect') | |
215 | - connections() | |
216 | - }) | |
217 | - ) | |
218 | - | |
219 | - var int = setInterval(connections, 2e3) | |
220 | - if(int.unref) int.unref() | |
221 | - | |
222 | - connections() | |
223 | - | |
224 | -} | |
225 | - | |
226 | -exports.isUnattempted = isUnattempted | |
227 | -exports.isInactive = isInactive | |
228 | -exports.isLongterm = isLongterm | |
229 | -exports.isLegacy = isLegacy | |
230 | -exports.isLocal = isLocal | |
231 | -exports.isFriend = isFriend | |
232 | -exports.isConnectedOrConnecting = isConnect | |
233 | -exports.select = select |
main-window.js | ||
---|---|---|
@@ -1,198 +1,26 @@ | ||
1 | -var Modules = require('./modules') | |
2 | -var h = require('./lib/h') | |
3 | -var Value = require('@mmckegg/mutant/value') | |
4 | -var when = require('@mmckegg/mutant/when') | |
5 | -var computed = require('@mmckegg/mutant/computed') | |
6 | -var toCollection = require('@mmckegg/mutant/dict-to-collection') | |
7 | -var MutantDict = require('@mmckegg/mutant/dict') | |
8 | -var MutantMap = require('@mmckegg/mutant/map') | |
9 | -var watch = require('@mmckegg/mutant/watch') | |
1 | +module.exports = function (config) { | |
2 | + var modules = require('depject')( | |
3 | + overrideConfig(config), | |
4 | + require('patchbay/modules_extra'), | |
5 | + require('patchbay/modules_basic'), | |
6 | + require('patchbay/modules_core'), | |
7 | + require('./modules') | |
8 | + ) | |
10 | 9 | |
11 | -var plugs = require('patchbay/plugs') | |
10 | + return modules.app[0]() | |
11 | +} | |
12 | 12 | |
13 | -module.exports = function (config, ssbClient) { | |
14 | - var modules = Modules(config, ssbClient) | |
15 | - | |
16 | - var screenView = plugs.first(modules.plugs.screen_view) | |
17 | - | |
18 | - var searchTimer = null | |
19 | - var searchBox = h('input.search', { | |
20 | - type: 'search', | |
21 | - placeholder: 'word, @key, #channel' | |
22 | - }) | |
23 | - | |
24 | - searchBox.oninput = function () { | |
25 | - clearTimeout(searchTimer) | |
26 | - searchTimer = setTimeout(doSearch, 500) | |
27 | - } | |
28 | - | |
29 | - searchBox.onfocus = function () { | |
30 | - if (searchBox.value) { | |
31 | - doSearch() | |
32 | - } | |
33 | - } | |
34 | - | |
35 | - var forwardHistory = [] | |
36 | - var backHistory = [] | |
37 | - | |
38 | - var views = MutantDict({ | |
39 | - // preload tabs (and subscribe to update notifications) | |
40 | - '/public': screenView('/public'), | |
41 | - '/private': screenView('/private'), | |
42 | - [ssbClient.id]: screenView(ssbClient.id), | |
43 | - '/notifications': screenView('/notifications') | |
44 | - }) | |
45 | - | |
46 | - var lastViewed = {} | |
47 | - | |
48 | - // delete cached view after 30 mins of last seeing | |
49 | - setInterval(() => { | |
50 | - views.keys().forEach((view) => { | |
51 | - if (lastViewed[view] !== true && Date.now() - lastViewed[view] > (30 * 60e3) && view !== currentView()) { | |
52 | - views.delete(view) | |
53 | - } | |
54 | - }) | |
55 | - }, 60e3) | |
56 | - | |
57 | - var canGoForward = Value(false) | |
58 | - var canGoBack = Value(false) | |
59 | - var currentView = Value('/public') | |
60 | - | |
61 | - watch(currentView, (view) => { | |
62 | - window.location.hash = `#${view}` | |
63 | - }) | |
64 | - | |
65 | - window.onhashchange = function (ev) { | |
66 | - var path = window.location.hash.substring(1) | |
67 | - if (path) { | |
68 | - setView(path) | |
69 | - } | |
70 | - } | |
71 | - | |
72 | - var mainElement = h('div.main', MutantMap(toCollection(views), (item) => { | |
73 | - return h('div.view', { | |
74 | - hidden: computed([item.key, currentView], (a, b) => a !== b) | |
75 | - }, [ item.value ]) | |
76 | - })) | |
77 | - | |
78 | - return h('MainWindow', { | |
79 | - classList: [ '-' + process.platform ] | |
80 | - }, [ | |
81 | - h('div.top', [ | |
82 | - h('span.history', [ | |
83 | - h('a', { | |
84 | - 'ev-click': goBack, | |
85 | - classList: [ when(canGoBack, '-active') ] | |
86 | - }, '<'), | |
87 | - h('a', { | |
88 | - 'ev-click': goForward, | |
89 | - classList: [ when(canGoForward, '-active') ] | |
90 | - }, '>') | |
91 | - ]), | |
92 | - h('span.nav', [ | |
93 | - tab('Public', '/public'), | |
94 | - tab('Private', '/private') | |
95 | - ]), | |
96 | - h('span.appTitle', ['Patchwork']), | |
97 | - h('span', [ searchBox ]), | |
98 | - h('span.nav', [ | |
99 | - tab('Profile', ssbClient.id), | |
100 | - tab('Mentions', '/notifications') | |
101 | - ]) | |
102 | - ]), | |
103 | - mainElement | |
104 | - ]) | |
105 | - | |
106 | - // scoped | |
107 | - | |
108 | - function tab (name, view) { | |
109 | - var instance = views.get(view) | |
110 | - lastViewed[view] = true | |
111 | - return h('a', { | |
112 | - 'ev-click': function (ev) { | |
113 | - if (instance.pendingUpdates && instance.pendingUpdates() && instance.reload) { | |
114 | - instance.reload() | |
13 | +function overrideConfig (config) { | |
14 | + return { | |
15 | + config: { | |
16 | + gives: {'config': true}, | |
17 | + create: function (api) { | |
18 | + return { | |
19 | + config () { | |
20 | + return config | |
21 | + } | |
115 | 22 | } |
116 | - }, | |
117 | - href: `#${view}`, | |
118 | - classList: [ | |
119 | - when(selected(view), '-selected') | |
120 | - ] | |
121 | - }, [ | |
122 | - name, | |
123 | - when(instance.pendingUpdates, [ | |
124 | - ' (', instance.pendingUpdates, ')' | |
125 | - ]) | |
126 | - ]) | |
127 | - } | |
128 | - | |
129 | - function goBack () { | |
130 | - if (backHistory.length) { | |
131 | - canGoForward.set(true) | |
132 | - forwardHistory.push(currentView()) | |
133 | - currentView.set(backHistory.pop()) | |
134 | - canGoBack.set(backHistory.length > 0) | |
135 | - } | |
136 | - } | |
137 | - | |
138 | - function goForward () { | |
139 | - if (forwardHistory.length) { | |
140 | - backHistory.push(currentView()) | |
141 | - currentView.set(forwardHistory.pop()) | |
142 | - canGoForward.set(forwardHistory.length > 0) | |
143 | - canGoBack.set(true) | |
144 | - } | |
145 | - } | |
146 | - | |
147 | - function setView (view) { | |
148 | - if (!views.has(view)) { | |
149 | - views.put(view, screenView(view)) | |
150 | - } | |
151 | - | |
152 | - if (lastViewed[view] !== true) { | |
153 | - lastViewed[view] = Date.now() | |
154 | - } | |
155 | - | |
156 | - if (currentView() && lastViewed[currentView()] !== true) { | |
157 | - lastViewed[currentView()] = Date.now() | |
158 | - } | |
159 | - | |
160 | - if (view !== currentView()) { | |
161 | - canGoForward.set(false) | |
162 | - canGoBack.set(true) | |
163 | - forwardHistory.length = 0 | |
164 | - backHistory.push(currentView()) | |
165 | - currentView.set(view) | |
166 | - } | |
167 | - } | |
168 | - | |
169 | - function doSearch () { | |
170 | - var value = searchBox.value.trim() | |
171 | - if (value.startsWith('/') || value.startsWith('?') || value.startsWith('@') || value.startsWith('#') || value.startsWith('%')) { | |
172 | - setView(value) | |
173 | - } else if (value.trim()) { | |
174 | - setView(`?${value.trim()}`) | |
175 | - } else { | |
176 | - setView('/public') | |
177 | - } | |
178 | - } | |
179 | - | |
180 | - function selected (view) { | |
181 | - return computed([currentView, view], (currentView, view) => { | |
182 | - return currentView === view | |
183 | - }) | |
184 | - } | |
185 | -} | |
186 | - | |
187 | -function isSame (a, b) { | |
188 | - if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { | |
189 | - for (var i = 0; i < a.length; i++) { | |
190 | - if (a[i] !== b[i]) { | |
191 | - return false | |
192 | 23 | } |
193 | 24 | } |
194 | - return true | |
195 | - } else if (a === b) { | |
196 | - return true | |
197 | 25 | } |
198 | 26 | } |
modules/index.js | ||
---|---|---|
@@ -1,31 +1,1 @@ | ||
1 | -var SbotApi = require('../api') | |
2 | -var extend = require('xtend') | |
3 | -var combine = require('depject') | |
4 | -var fs = require('fs') | |
5 | -var patchbayModules = require('patchbay/modules') | |
6 | - | |
7 | -module.exports = function (config, ssbClient, overrides) { | |
8 | - var api = SbotApi(ssbClient, config) | |
9 | - var localModules = getLocalModules() | |
10 | - return combine(extend(patchbayModules, localModules, { | |
11 | - 'sbot-api.js': api, | |
12 | - 'blob-url.js': { | |
13 | - blob_url: function (link) { | |
14 | - var prefix = config.blobsPrefix != null ? config.blobsPrefix : `http://localhost:${config.blobsPort}` | |
15 | - if (typeof link.link === 'string') { | |
16 | - link = link.link | |
17 | - } | |
18 | - return `${prefix}/${encodeURIComponent(link)}` | |
19 | - } | |
20 | - } | |
21 | - }, overrides)) | |
22 | -} | |
23 | - | |
24 | -function getLocalModules () { | |
25 | - return fs.readdirSync(__dirname).reduce(function (result, e) { | |
26 | - if (e !== 'index.js' && /\js$/.test(e)) { | |
27 | - result[e] = require('./' + e) | |
28 | - } | |
29 | - return result | |
30 | - }, {}) | |
31 | -} | |
1 | +module.exports = require('bulk-require')(__dirname, ['**/!(index).js']) |
modules/about.js | ||
---|---|---|
@@ -1,37 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | - | |
3 | -function idLink (id) { | |
4 | - return h('a', {href:'#'+id}, id.slice(0, 10)) | |
5 | -} | |
6 | - | |
7 | -function asLink (ln) { | |
8 | - return 'string' === typeof ln ? ln : ln.link | |
9 | -} | |
10 | - | |
11 | -var plugs = require('patchbay/plugs') | |
12 | -var blob_url = plugs.first(exports.blob_url = []) | |
13 | -var avatar_name = plugs.first(exports.avatar_name = []) | |
14 | -var avatar_link = plugs.first(exports.avatar_link = []) | |
15 | - | |
16 | -exports.message_content = function (msg) { | |
17 | - if(msg.value.content.type !== 'about' || !msg.value.content.about) return | |
18 | - | |
19 | - if(!msg.value.content.image && !msg.value.content.name) | |
20 | - return | |
21 | - | |
22 | - var about = msg.value.content | |
23 | - var id = msg.value.content.about | |
24 | - return h('p', | |
25 | - about.about === msg.value.author | |
26 | - ? h('span', 'self-identifies ') | |
27 | - : h('span', 'identifies ', about.name ? idLink(id) : avatar_link(id, avatar_name(id))), | |
28 | - ' as ', | |
29 | - h('a', {href:"#"+about.about}, | |
30 | - about.name || null, | |
31 | - about.image | |
32 | - ? h('img.avatar--fullsize', {src: blob_url(about.image)}) | |
33 | - : null | |
34 | - ) | |
35 | - ) | |
36 | - | |
37 | -} |
modules/app.js | ||
---|---|---|
@@ -1,0 +1,194 @@ | ||
1 | +var h = require('../lib/h') | |
2 | +var Value = require('mutant/value') | |
3 | +var when = require('mutant/when') | |
4 | +var computed = require('mutant/computed') | |
5 | +var toCollection = require('mutant/dict-to-collection') | |
6 | +var MutantDict = require('mutant/dict') | |
7 | +var MutantMap = require('mutant/map') | |
8 | +var watch = require('mutant/watch') | |
9 | + | |
10 | +exports.needs = { | |
11 | + page: 'first', | |
12 | + sbot: { | |
13 | + get_id: 'first' | |
14 | + } | |
15 | +} | |
16 | + | |
17 | +exports.gives = { | |
18 | + app: true | |
19 | +} | |
20 | + | |
21 | +exports.create = function (api) { | |
22 | + return { | |
23 | + app: function () { | |
24 | + var key = api.sbot.get_id() | |
25 | + var searchTimer = null | |
26 | + var searchBox = h('input.search', { | |
27 | + type: 'search', | |
28 | + placeholder: 'word, @key, #channel' | |
29 | + }) | |
30 | + | |
31 | + searchBox.oninput = function () { | |
32 | + clearTimeout(searchTimer) | |
33 | + searchTimer = setTimeout(doSearch, 500) | |
34 | + } | |
35 | + | |
36 | + searchBox.onfocus = function () { | |
37 | + if (searchBox.value) { | |
38 | + doSearch() | |
39 | + } | |
40 | + } | |
41 | + | |
42 | + var forwardHistory = [] | |
43 | + var backHistory = [] | |
44 | + | |
45 | + var views = MutantDict({ | |
46 | + // preload tabs (and subscribe to update notifications) | |
47 | + '/public': api.page('/public'), | |
48 | + '/private': api.page('/private'), | |
49 | + [key]: api.page(key), | |
50 | + '/notifications': api.page('/notifications') | |
51 | + }) | |
52 | + | |
53 | + var lastViewed = {} | |
54 | + | |
55 | + // delete cached view after 30 mins of last seeing | |
56 | + setInterval(() => { | |
57 | + views.keys().forEach((view) => { | |
58 | + if (lastViewed[view] !== true && Date.now() - lastViewed[view] > (30 * 60e3) && view !== currentView()) { | |
59 | + views.delete(view) | |
60 | + } | |
61 | + }) | |
62 | + }, 60e3) | |
63 | + | |
64 | + var canGoForward = Value(false) | |
65 | + var canGoBack = Value(false) | |
66 | + var currentView = Value('/public') | |
67 | + | |
68 | + watch(currentView, (view) => { | |
69 | + window.location.hash = `#${view}` | |
70 | + }) | |
71 | + | |
72 | + window.onhashchange = function (ev) { | |
73 | + var path = window.location.hash.substring(1) | |
74 | + if (path) { | |
75 | + setView(path) | |
76 | + } | |
77 | + } | |
78 | + | |
79 | + var mainElement = h('div.main', MutantMap(toCollection(views), (item) => { | |
80 | + return h('div.view', { | |
81 | + hidden: computed([item.key, currentView], (a, b) => a !== b) | |
82 | + }, [ item.value ]) | |
83 | + })) | |
84 | + | |
85 | + return h('MainWindow', { | |
86 | + classList: [ '-' + process.platform ] | |
87 | + }, [ | |
88 | + h('div.top', [ | |
89 | + h('span.history', [ | |
90 | + h('a', { | |
91 | + 'ev-click': goBack, | |
92 | + classList: [ when(canGoBack, '-active') ] | |
93 | + }, '<'), | |
94 | + h('a', { | |
95 | + 'ev-click': goForward, | |
96 | + classList: [ when(canGoForward, '-active') ] | |
97 | + }, '>') | |
98 | + ]), | |
99 | + h('span.nav', [ | |
100 | + tab('Public', '/public'), | |
101 | + tab('Private', '/private') | |
102 | + ]), | |
103 | + h('span.appTitle', ['Patchwork']), | |
104 | + h('span', [ searchBox ]), | |
105 | + h('span.nav', [ | |
106 | + tab('Profile', key), | |
107 | + tab('Mentions', '/notifications') | |
108 | + ]) | |
109 | + ]), | |
110 | + mainElement | |
111 | + ]) | |
112 | + | |
113 | + // scoped | |
114 | + | |
115 | + function tab (name, view) { | |
116 | + var instance = views.get(view) | |
117 | + lastViewed[view] = true | |
118 | + return h('a', { | |
119 | + 'ev-click': function (ev) { | |
120 | + if (instance.pendingUpdates && instance.pendingUpdates() && instance.reload) { | |
121 | + instance.reload() | |
122 | + } | |
123 | + }, | |
124 | + href: `#${view}`, | |
125 | + classList: [ | |
126 | + when(selected(view), '-selected') | |
127 | + ] | |
128 | + }, [ | |
129 | + name, | |
130 | + when(instance.pendingUpdates, [ | |
131 | + ' (', instance.pendingUpdates, ')' | |
132 | + ]) | |
133 | + ]) | |
134 | + } | |
135 | + | |
136 | + function goBack () { | |
137 | + if (backHistory.length) { | |
138 | + canGoForward.set(true) | |
139 | + forwardHistory.push(currentView()) | |
140 | + currentView.set(backHistory.pop()) | |
141 | + canGoBack.set(backHistory.length > 0) | |
142 | + } | |
143 | + } | |
144 | + | |
145 | + function goForward () { | |
146 | + if (forwardHistory.length) { | |
147 | + backHistory.push(currentView()) | |
148 | + currentView.set(forwardHistory.pop()) | |
149 | + canGoForward.set(forwardHistory.length > 0) | |
150 | + canGoBack.set(true) | |
151 | + } | |
152 | + } | |
153 | + | |
154 | + function setView (view) { | |
155 | + if (!views.has(view)) { | |
156 | + views.put(view, api.page(view)) | |
157 | + } | |
158 | + | |
159 | + if (lastViewed[view] !== true) { | |
160 | + lastViewed[view] = Date.now() | |
161 | + } | |
162 | + | |
163 | + if (currentView() && lastViewed[currentView()] !== true) { | |
164 | + lastViewed[currentView()] = Date.now() | |
165 | + } | |
166 | + | |
167 | + if (view !== currentView()) { | |
168 | + canGoForward.set(false) | |
169 | + canGoBack.set(true) | |
170 | + forwardHistory.length = 0 | |
171 | + backHistory.push(currentView()) | |
172 | + currentView.set(view) | |
173 | + } | |
174 | + } | |
175 | + | |
176 | + function doSearch () { | |
177 | + var value = searchBox.value.trim() | |
178 | + if (value.startsWith('/') || value.startsWith('?') || value.startsWith('@') || value.startsWith('#') || value.startsWith('%')) { | |
179 | + setView(value) | |
180 | + } else if (value.trim()) { | |
181 | + setView(`?${value.trim()}`) | |
182 | + } else { | |
183 | + setView('/public') | |
184 | + } | |
185 | + } | |
186 | + | |
187 | + function selected (view) { | |
188 | + return computed([currentView, view], (currentView, view) => { | |
189 | + return currentView === view | |
190 | + }) | |
191 | + } | |
192 | + } | |
193 | + } | |
194 | +} |
modules/channel.js | ||
---|---|---|
@@ -1,78 +1,0 @@ | ||
1 | -var when = require('@mmckegg/mutant/when') | |
2 | -var send = require('@mmckegg/mutant/send') | |
3 | -var plugs = require('patchbay/plugs') | |
4 | -var message_compose = plugs.first(exports.message_compose = []) | |
5 | -var sbot_log = plugs.first(exports.sbot_log = []) | |
6 | -var feed_summary = plugs.first(exports.feed_summary = []) | |
7 | -var h = require('../lib/h') | |
8 | -var pull = require('pull-stream') | |
9 | -var obs_subscribed_channels = plugs.first(exports.obs_subscribed_channels = []) | |
10 | -var get_id = plugs.first(exports.get_id = []) | |
11 | -var publish = plugs.first(exports.sbot_publish = []) | |
12 | - | |
13 | -exports.screen_view = function (path, sbot) { | |
14 | - if (path[0] === '#') { | |
15 | - var channel = path.substr(1) | |
16 | - var subscribedChannels = obs_subscribed_channels(get_id()) | |
17 | - | |
18 | - return feed_summary((opts) => { | |
19 | - return pull( | |
20 | - sbot_log(opts), | |
21 | - pull.map(matchesChannel) | |
22 | - ) | |
23 | - }, [ | |
24 | - h('PageHeading', [ | |
25 | - h('h1', `#${channel}`), | |
26 | - h('div.meta', [ | |
27 | - when(subscribedChannels.has(channel), | |
28 | - h('a -unsubscribe', { | |
29 | - 'href': '#', | |
30 | - 'title': 'Click to unsubscribe', | |
31 | - 'ev-click': send(unsubscribe, channel) | |
32 | - }, 'Subscribed'), | |
33 | - h('a -subscribe', { | |
34 | - 'href': '#', | |
35 | - 'ev-click': send(subscribe, channel) | |
36 | - }, 'Subscribe') | |
37 | - ) | |
38 | - ]) | |
39 | - ]), | |
40 | - message_compose({type: 'post', channel: channel}, {placeholder: 'Write a message in this channel\n\n\n\nPeople who follow you or subscribe to this channel will also see this message in their main feed.\n\nTo create a new channel, type the channel name (preceded by a #) into the search box above. e.g #cat-pics'}) | |
41 | - ]) | |
42 | - } | |
43 | - | |
44 | - // scoped | |
45 | - | |
46 | - function matchesChannel (msg) { | |
47 | - if (msg.sync) console.error('SYNC', msg) | |
48 | - var c = msg && msg.value && msg.value.content | |
49 | - if (c && c.channel === channel) { | |
50 | - return msg | |
51 | - } else { | |
52 | - return {timestamp: msg.timestamp} | |
53 | - } | |
54 | - } | |
55 | -} | |
56 | - | |
57 | -exports.message_meta = function (msg) { | |
58 | - var chan = msg.value.content.channel | |
59 | - if (chan) { | |
60 | - return h('a.channel', {href: '##' + chan}, '#' + chan) | |
61 | - } | |
62 | -} | |
63 | - | |
64 | -function subscribe (id) { | |
65 | - publish({ | |
66 | - type: 'channel', | |
67 | - channel: id, | |
68 | - subscribed: true | |
69 | - }) | |
70 | -} | |
71 | - | |
72 | -function unsubscribe (id) { | |
73 | - publish({ | |
74 | - type: 'channel', | |
75 | - channel: id, | |
76 | - subscribed: false | |
77 | - }) | |
78 | -} |
modules/helpers/blob-url.js | ||
---|---|---|
@@ -1,0 +1,22 @@ | ||
1 | +exports.needs = { | |
2 | + config: 'first' | |
3 | +} | |
4 | + | |
5 | +exports.gives = { | |
6 | + helpers: { blob_url: true } | |
7 | +} | |
8 | + | |
9 | +exports.create = function (api) { | |
10 | + return { | |
11 | + helpers: { | |
12 | + blob_url (link) { | |
13 | + var config = api.config() | |
14 | + var prefix = config.blobsPrefix != null ? config.blobsPrefix : `http://localhost:${config.blobsPort}` | |
15 | + if (typeof link.link === 'string') { | |
16 | + link = link.link | |
17 | + } | |
18 | + return `${prefix}/${encodeURIComponent(link)}` | |
19 | + } | |
20 | + } | |
21 | + } | |
22 | +} |
modules/helpers/emoji.js | ||
---|---|---|
@@ -1,0 +1,30 @@ | ||
1 | +var emojis = require('emoji-named-characters') | |
2 | +var emojiNames = Object.keys(emojis) | |
3 | + | |
4 | +exports.needs = { | |
5 | + helpers: { blob_url: 'first' } | |
6 | +} | |
7 | + | |
8 | +exports.gives = { | |
9 | + helpers: { | |
10 | + emoji_names: true, | |
11 | + emoji_url: true | |
12 | + } | |
13 | +} | |
14 | + | |
15 | +exports.create = function (api) { | |
16 | + return { | |
17 | + helpers: { | |
18 | + emoji_names, | |
19 | + emoji_url | |
20 | + } | |
21 | + } | |
22 | + | |
23 | + function emoji_names () { | |
24 | + return emojiNames | |
25 | + } | |
26 | + | |
27 | + function emoji_url (emoji) { | |
28 | + return emoji in emojis && `img/emoji/${emoji}.png` | |
29 | + } | |
30 | +} |
modules/data-feed.js | ||
---|---|---|
@@ -1,32 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | -var u = require('patchbay/util') | |
3 | -var pull = require('pull-stream') | |
4 | -var Scroller = require('pull-scroll') | |
5 | - | |
6 | -var plugs = require('patchbay/plugs') | |
7 | -var sbot_log = plugs.first(exports.sbot_log = []) | |
8 | -var data_render = plugs.first(exports.data_render = []) | |
9 | - | |
10 | -exports.screen_view = function (path, sbot) { | |
11 | - if(path === '/data-feed' || path === '/data') { | |
12 | - var content = h('div.column.scroller__content') | |
13 | - var div = h('div.column.scroller', | |
14 | - {style: {'overflow':'auto'}}, | |
15 | - h('div.scroller__wrapper', | |
16 | - content | |
17 | - ) | |
18 | - ) | |
19 | - | |
20 | - pull( | |
21 | - u.next(sbot_log, {old: false, limit: 100}), | |
22 | - Scroller(div, content, data_render, true, false) | |
23 | - ) | |
24 | - | |
25 | - pull( | |
26 | - u.next(sbot_log, {reverse: true, limit: 100, live: false}), | |
27 | - Scroller(div, content, data_render, false, false) | |
28 | - ) | |
29 | - | |
30 | - return div | |
31 | - } | |
32 | -} |
modules/feed-summary.js | ||
---|---|---|
@@ -1,215 +1,0 @@ | ||
1 | -var Value = require('@mmckegg/mutant/value') | |
2 | -var h = require('@mmckegg/mutant/html-element') | |
3 | -var when = require('@mmckegg/mutant/when') | |
4 | -var computed = require('@mmckegg/mutant/computed') | |
5 | -var MutantArray = require('@mmckegg/mutant/array') | |
6 | -var Abortable = require('pull-abortable') | |
7 | -var Scroller = require('../lib/pull-scroll') | |
8 | -var FeedSummary = require('../lib/feed-summary') | |
9 | -var onceTrue = require('../lib/once-true') | |
10 | - | |
11 | -var m = require('../lib/h') | |
12 | - | |
13 | -var pull = require('pull-stream') | |
14 | - | |
15 | -var plugs = require('patchbay/plugs') | |
16 | -var message_render = plugs.first(exports.message_render = []) | |
17 | -var message_link = plugs.first(exports.message_link = []) | |
18 | -var person = plugs.first(exports.person = []) | |
19 | -var many_people = plugs.first(exports.many_people = []) | |
20 | -var people_names = plugs.first(exports.people_names = []) | |
21 | -var sbot_get = plugs.first(exports.sbot_get = []) | |
22 | -var get_id = plugs.first(exports.get_id = []) | |
23 | - | |
24 | -exports.feed_summary = function (getStream, prefix, opts) { | |
25 | - var sync = Value(false) | |
26 | - var updates = Value(0) | |
27 | - | |
28 | - var filter = opts && opts.filter | |
29 | - var bumpFilter = opts && opts.bumpFilter | |
30 | - var windowSize = opts && opts.windowSize | |
31 | - var waitFor = opts && opts.waitFor || true | |
32 | - | |
33 | - var updateLoader = m('a Notifier -loader', { | |
34 | - href: '#', | |
35 | - 'ev-click': refresh | |
36 | - }, [ | |
37 | - 'Show ', | |
38 | - h('strong', [updates]), ' ', | |
39 | - when(computed(updates, a => a === 1), 'update', 'updates') | |
40 | - ]) | |
41 | - | |
42 | - var content = h('div.column.scroller__content') | |
43 | - | |
44 | - var scrollElement = h('div.column.scroller', { | |
45 | - style: { | |
46 | - 'overflow': 'auto' | |
47 | - } | |
48 | - }, [ | |
49 | - h('div.scroller__wrapper', [ | |
50 | - prefix, content | |
51 | - ]) | |
52 | - ]) | |
53 | - | |
54 | - setTimeout(refresh, 10) | |
55 | - | |
56 | - onceTrue(waitFor, () => { | |
57 | - pull( | |
58 | - getStream({old: false}), | |
59 | - pull.drain((item) => { | |
60 | - var type = item && item.value && item.value.content.type | |
61 | - if (type && type !== 'vote') { | |
62 | - if (item.value && item.value.author === get_id() && !updates()) { | |
63 | - return refresh() | |
64 | - } | |
65 | - if (filter) { | |
66 | - var update = (item.value.content.type === 'post' && item.value.content.root) ? { | |
67 | - type: 'message', | |
68 | - messageId: item.value.content.root, | |
69 | - channel: item.value.content.channel | |
70 | - } : { | |
71 | - type: 'message', | |
72 | - author: item.value.author, | |
73 | - channel: item.value.content.channel, | |
74 | - messageId: item.key | |
75 | - } | |
76 | - | |
77 | - ensureAuthor(update, (err, update) => { | |
78 | - if (!err) { | |
79 | - if (filter(update)) { | |
80 | - updates.set(updates() + 1) | |
81 | - } | |
82 | - } | |
83 | - }) | |
84 | - } else { | |
85 | - updates.set(updates() + 1) | |
86 | - } | |
87 | - } | |
88 | - }) | |
89 | - ) | |
90 | - }) | |
91 | - | |
92 | - var abortLastFeed = null | |
93 | - | |
94 | - var result = MutantArray([ | |
95 | - when(updates, updateLoader), | |
96 | - when(sync, scrollElement, m('Loading -large')) | |
97 | - ]) | |
98 | - | |
99 | - result.reload = refresh | |
100 | - result.pendingUpdates = updates | |
101 | - | |
102 | - return result | |
103 | - | |
104 | - // scoped | |
105 | - | |
106 | - function refresh () { | |
107 | - if (abortLastFeed) { | |
108 | - abortLastFeed() | |
109 | - } | |
110 | - updates.set(0) | |
111 | - sync.set(false) | |
112 | - content.innerHTML = '' | |
113 | - | |
114 | - var abortable = Abortable() | |
115 | - abortLastFeed = abortable.abort | |
116 | - | |
117 | - pull( | |
118 | - FeedSummary(getStream, {windowSize, bumpFilter}, () => { | |
119 | - sync.set(true) | |
120 | - }), | |
121 | - pull.asyncMap(ensureAuthor), | |
122 | - pull.filter((item) => { | |
123 | - if (filter) { | |
124 | - return filter(item) | |
125 | - } else { | |
126 | - return true | |
127 | - } | |
128 | - }), | |
129 | - abortable, | |
130 | - Scroller(scrollElement, content, renderItem, false, false) | |
131 | - ) | |
132 | - } | |
133 | -} | |
134 | - | |
135 | -function ensureAuthor (item, cb) { | |
136 | - if (item.type === 'message' && !item.message) { | |
137 | - sbot_get(item.messageId, (_, value) => { | |
138 | - if (value) { | |
139 | - item.author = value.author | |
140 | - } | |
141 | - cb(null, item) | |
142 | - }) | |
143 | - } else { | |
144 | - cb(null, item) | |
145 | - } | |
146 | -} | |
147 | - | |
148 | -function renderItem (item) { | |
149 | - if (item.type === 'message') { | |
150 | - var meta = null | |
151 | - var previousId = item.messageId | |
152 | - var replies = item.replies.slice(-4).map((msg) => { | |
153 | - var result = message_render(msg, {inContext: true, inSummary: true, previousId}) | |
154 | - previousId = msg.key | |
155 | - return result | |
156 | - }) | |
157 | - var renderedMessage = item.message ? message_render(item.message, {inContext: true}) : null | |
158 | - if (renderedMessage) { | |
159 | - if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
160 | - meta = m('div.meta', { | |
161 | - title: people_names(item.repliesFrom) | |
162 | - }, [ | |
163 | - many_people(item.repliesFrom), ' replied' | |
164 | - ]) | |
165 | - } else if (item.lastUpdateType === 'dig' && item.digs.size) { | |
166 | - meta = m('div.meta', { | |
167 | - title: people_names(item.digs) | |
168 | - }, [ | |
169 | - many_people(item.digs), ' dug this message' | |
170 | - ]) | |
171 | - } | |
172 | - | |
173 | - return m('FeedEvent', [ | |
174 | - meta, | |
175 | - renderedMessage, | |
176 | - when(replies.length, [ | |
177 | - when(item.replies.length > replies.length, | |
178 | - m('a.full', {href: `#${item.messageId}`}, ['View full thread']) | |
179 | - ), | |
180 | - m('div.replies', replies) | |
181 | - ]) | |
182 | - ]) | |
183 | - } else { | |
184 | - if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
185 | - meta = m('div.meta', { | |
186 | - title: people_names(item.repliesFrom) | |
187 | - }, [ | |
188 | - many_people(item.repliesFrom), ' replied to ', message_link(item.messageId) | |
189 | - ]) | |
190 | - } else if (item.lastUpdateType === 'dig' && item.digs.size) { | |
191 | - meta = m('div.meta', { | |
192 | - title: people_names(item.digs) | |
193 | - }, [ | |
194 | - many_people(item.digs), ' dug ', message_link(item.messageId) | |
195 | - ]) | |
196 | - } | |
197 | - | |
198 | - if (meta || replies.length) { | |
199 | - return m('FeedEvent', [ | |
200 | - meta, m('div.replies', replies) | |
201 | - ]) | |
202 | - } | |
203 | - } | |
204 | - } else if (item.type === 'follow') { | |
205 | - return m('FeedEvent -follow', [ | |
206 | - m('div.meta', { | |
207 | - title: people_names(item.contacts) | |
208 | - }, [ | |
209 | - person(item.id), ' followed ', many_people(item.contacts) | |
210 | - ]) | |
211 | - ]) | |
212 | - } | |
213 | - | |
214 | - return h('div') | |
215 | -} |
modules/sbot.js | ||
---|---|---|
@@ -1,0 +1,187 @@ | ||
1 | +var pull = require('pull-stream') | |
2 | +var ssbKeys = require('ssb-keys') | |
3 | +var ref = require('ssb-ref') | |
4 | +var Reconnect = require('pull-reconnect') | |
5 | + | |
6 | +function Hash (onHash) { | |
7 | + var buffers = [] | |
8 | + return pull.through(function (data) { | |
9 | + buffers.push('string' === typeof data | |
10 | + ? new Buffer(data, 'utf8') | |
11 | + : data | |
12 | + ) | |
13 | + }, function (err) { | |
14 | + if(err && !onHash) throw err | |
15 | + var b = buffers.length > 1 ? Buffer.concat(buffers) : buffers[0] | |
16 | + var h = '&'+ssbKeys.hash(b) | |
17 | + onHash && onHash(err, h) | |
18 | + }) | |
19 | +} | |
20 | +//uncomment this to use from browser... | |
21 | +//also depends on having ssb-ws installed. | |
22 | +//var createClient = require('ssb-lite') | |
23 | +var createClient = require('ssb-client') | |
24 | + | |
25 | +var createFeed = require('ssb-feed') | |
26 | +var keys = require('patchbay/keys') | |
27 | + | |
28 | +var cache = CACHE = {} | |
29 | + | |
30 | +exports.needs = { | |
31 | + connection_status: 'map', | |
32 | + config: 'first' | |
33 | +} | |
34 | + | |
35 | +exports.gives = { | |
36 | +// connection_status: true, | |
37 | + sbot: { | |
38 | + blobs_add: true, | |
39 | + links: true, | |
40 | + links2: true, | |
41 | + query: true, | |
42 | + fulltext_search: true, | |
43 | + get: true, | |
44 | + log: true, | |
45 | + user_feed: true, | |
46 | + gossip_peers: true, | |
47 | + gossip_connect: true, | |
48 | + progress: true, | |
49 | + publish: true, | |
50 | + whoami: true, | |
51 | + | |
52 | + // additional | |
53 | + get_id: true | |
54 | + } | |
55 | +} | |
56 | + | |
57 | +exports.create = function (api) { | |
58 | + | |
59 | + var sbot = null | |
60 | + var config = api.config() | |
61 | + | |
62 | + var rec = Reconnect(function (isConn) { | |
63 | + function notify (value) { | |
64 | + isConn(value); api.connection_status(value) | |
65 | + } | |
66 | + | |
67 | + createClient(config.keys, config, function (err, _sbot) { | |
68 | + if(err) | |
69 | + return notify(err) | |
70 | + | |
71 | + sbot = _sbot | |
72 | + sbot.on('closed', function () { | |
73 | + sbot = null | |
74 | + notify(new Error('closed')) | |
75 | + }) | |
76 | + | |
77 | + notify() | |
78 | + }) | |
79 | + }) | |
80 | + | |
81 | + var internal = { | |
82 | + getLatest: rec.async(function (id, cb) { | |
83 | + sbot.getLatest(id, cb) | |
84 | + }), | |
85 | + add: rec.async(function (msg, cb) { | |
86 | + sbot.add(msg, cb) | |
87 | + }) | |
88 | + } | |
89 | + | |
90 | + var feed = createFeed(internal, keys, {remote: true}) | |
91 | + | |
92 | + return { | |
93 | + // connection_status, | |
94 | + sbot: { | |
95 | + blobs_add: rec.sink(function (cb) { | |
96 | + return pull( | |
97 | + Hash(function (err, id) { | |
98 | + if(err) return cb(err) | |
99 | + //completely UGLY hack to tell when the blob has been sucessfully written... | |
100 | + var start = Date.now(), n = 5 | |
101 | + ;(function next () { | |
102 | + setTimeout(function () { | |
103 | + sbot.blobs.has(id, function (err, has) { | |
104 | + if(has) return cb(null, id) | |
105 | + if(n--) next() | |
106 | + else cb(new Error('write failed')) | |
107 | + }) | |
108 | + }, Date.now() - start) | |
109 | + })() | |
110 | + }), | |
111 | + sbot.blobs.add() | |
112 | + ) | |
113 | + }), | |
114 | + links: rec.source(function (query) { | |
115 | + return sbot.links(query) | |
116 | + }), | |
117 | + links2: rec.source(function (query) { | |
118 | + return sbot.links2.read(query) | |
119 | + }), | |
120 | + query: rec.source(function (query) { | |
121 | + return sbot.query.read(query) | |
122 | + }), | |
123 | + log: rec.source(function (opts) { | |
124 | + return pull( | |
125 | + sbot.createLogStream(opts), | |
126 | + pull.through(function (e) { | |
127 | + CACHE[e.key] = CACHE[e.key] || e.value | |
128 | + }) | |
129 | + ) | |
130 | + }), | |
131 | + user_feed: rec.source(function (opts) { | |
132 | + return sbot.createUserStream(opts) | |
133 | + }), | |
134 | + fulltext_search: rec.source(function (opts) { | |
135 | + return sbot.fulltext.search(opts) | |
136 | + }), | |
137 | + get: rec.async(function (key, cb) { | |
138 | + if('function' !== typeof cb) | |
139 | + throw new Error('cb must be function') | |
140 | + if(CACHE[key]) cb(null, CACHE[key]) | |
141 | + else sbot.get(key, function (err, value) { | |
142 | + if(err) return cb(err) | |
143 | + cb(null, CACHE[key] = value) | |
144 | + }) | |
145 | + }), | |
146 | + gossip_peers: rec.async(function (cb) { | |
147 | + sbot.gossip.peers(cb) | |
148 | + }), | |
149 | + //liteclient won't have permissions for this | |
150 | + gossip_connect: rec.async(function (opts, cb) { | |
151 | + sbot.gossip.connect(opts, cb) | |
152 | + }), | |
153 | + progress: rec.source(function () { | |
154 | + return sbot.replicate.changes() | |
155 | + }), | |
156 | + publish: rec.async(function (content, cb) { | |
157 | + if(content.recps) | |
158 | + content = ssbKeys.box(content, content.recps.map(function (e) { | |
159 | + return ref.isFeed(e) ? e : e.link | |
160 | + })) | |
161 | + else if(content.mentions) | |
162 | + content.mentions.forEach(function (mention) { | |
163 | + if(ref.isBlob(mention.link)) { | |
164 | + sbot.blobs.push(mention.link, function (err) { | |
165 | + if(err) console.error(err) | |
166 | + }) | |
167 | + } | |
168 | + }) | |
169 | + | |
170 | + feed.add(content, function (err, msg) { | |
171 | + if(err) console.error(err) | |
172 | + else if(!cb) console.log(msg) | |
173 | + cb && cb(err, msg) | |
174 | + }) | |
175 | + }), | |
176 | + whoami: rec.async(function (cb) { | |
177 | + sbot.whoami(cb) | |
178 | + }), | |
179 | + | |
180 | + // ADDITIONAL: | |
181 | + | |
182 | + get_id: function () { | |
183 | + return keys.id | |
184 | + } | |
185 | + } | |
186 | + } | |
187 | +} |
modules/feed.js | ||
---|---|---|
@@ -1,20 +1,0 @@ | ||
1 | -var ref = require('ssb-ref') | |
2 | -var h = require('hyperscript') | |
3 | -var extend = require('xtend') | |
4 | - | |
5 | -var plugs = require('patchbay/plugs') | |
6 | -var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
7 | -var avatar_profile = plugs.first(exports.avatar_profile = []) | |
8 | -var feed_summary = plugs.first(exports.feed_summary = []) | |
9 | - | |
10 | -exports.screen_view = function (id) { | |
11 | - if (ref.isFeed(id)) { | |
12 | - return feed_summary((opts) => { | |
13 | - return sbot_user_feed(extend(opts, {id: id})) | |
14 | - }, [ | |
15 | - h('div', [avatar_profile(id)]) | |
16 | - ], { | |
17 | - windowSize: 50 | |
18 | - }) | |
19 | - } | |
20 | -} |
modules/git-mini-messages.js | ||
---|---|---|
@@ -1,26 +1,0 @@ | ||
1 | -var h = require('../lib/h') | |
2 | -var when = require('@mmckegg/mutant/when') | |
3 | -var plugs = require('patchbay/plugs') | |
4 | -var message_link = plugs.first(exports.message_link = []) | |
5 | - | |
6 | -exports.message_content = exports.message_content_mini = function (msg, sbot) { | |
7 | - if (msg.value.content.type === 'git-update') { | |
8 | - var commits = msg.value.content.commits || [] | |
9 | - return [ | |
10 | - h('a', {href: `#${msg.key}`, title: commitSummary(commits)}, [ | |
11 | - 'pushed', | |
12 | - when(commits, [' ', pluralizeCommits(commits)]) | |
13 | - ]), | |
14 | - ' to ', | |
15 | - message_link(msg.value.content.repo) | |
16 | - ] | |
17 | - } | |
18 | -} | |
19 | - | |
20 | -function pluralizeCommits (commits) { | |
21 | - return when(commits.length === 1, '1 commit', `${commits.length} commits`) | |
22 | -} | |
23 | - | |
24 | -function commitSummary (commits) { | |
25 | - return commits.map(commit => `- ${commit.title}`).join('\n') | |
26 | -} |
modules/like.js | ||
---|---|---|
@@ -1,72 +1,0 @@ | ||
1 | -var h = require('../lib/h') | |
2 | -var computed = require('@mmckegg/mutant/computed') | |
3 | -var when = require('@mmckegg/mutant/when') | |
4 | -var plugs = require('patchbay/plugs') | |
5 | -var message_link = plugs.first(exports.message_link = []) | |
6 | -var get_id = plugs.first(exports.get_id = []) | |
7 | -var get_likes = plugs.first(exports.get_likes = []) | |
8 | -var publish = plugs.first(exports.sbot_publish = []) | |
9 | -var people_names = plugs.first(exports.people_names = []) | |
10 | - | |
11 | -exports.message_content = exports.message_content_mini = function (msg, sbot) { | |
12 | - if (msg.value.content.type !== 'vote') return | |
13 | - var link = msg.value.content.vote.link | |
14 | - return [ | |
15 | - msg.value.content.vote.value > 0 ? 'dug' : 'undug', | |
16 | - ' ', message_link(link) | |
17 | - ] | |
18 | -} | |
19 | - | |
20 | -exports.message_meta = function (msg, sbot) { | |
21 | - return computed(get_likes(msg.key), likeCount) | |
22 | -} | |
23 | - | |
24 | -exports.message_action = function (msg, sbot) { | |
25 | - var id = get_id() | |
26 | - var dug = computed([get_likes(msg.key), id], doesLike) | |
27 | - dug(() => {}) | |
28 | - | |
29 | - if (msg.value.content.type !== 'vote') { | |
30 | - return h('a.dig', { | |
31 | - href: '#', | |
32 | - 'ev-click': function () { | |
33 | - var dig = dug() ? { | |
34 | - type: 'vote', | |
35 | - vote: { link: msg.key, value: 0, expression: 'Undig' } | |
36 | - } : { | |
37 | - type: 'vote', | |
38 | - vote: { link: msg.key, value: 1, expression: 'Dig' } | |
39 | - } | |
40 | - if (msg.value.content.recps) { | |
41 | - dig.recps = msg.value.content.recps.map(function (e) { | |
42 | - return e && typeof e !== 'string' ? e.link : e | |
43 | - }) | |
44 | - dig.private = true | |
45 | - } | |
46 | - publish(dig) | |
47 | - } | |
48 | - }, when(dug, 'Undig', 'Dig')) | |
49 | - } | |
50 | -} | |
51 | - | |
52 | -function doesLike (likes, userId) { | |
53 | - return likes && likes[userId] && likes[userId][0] || false | |
54 | -} | |
55 | - | |
56 | -function likeCount (data) { | |
57 | - var likes = getLikes(data) | |
58 | - if (likes.length) { | |
59 | - return [' ', h('span.likes', { | |
60 | - title: people_names(likes) | |
61 | - }, ['+', h('strong', `${likes.length}`)])] | |
62 | - } | |
63 | -} | |
64 | - | |
65 | -function getLikes (likes) { | |
66 | - return Object.keys(likes).reduce((result, id) => { | |
67 | - if (likes[id][0]) { | |
68 | - result.push(id) | |
69 | - } | |
70 | - return result | |
71 | - }, []) | |
72 | -} |
modules/many-people.js | ||
---|---|---|
@@ -1,31 +1,0 @@ | ||
1 | -var plugs = require('patchbay/plugs') | |
2 | -var person = plugs.first(exports.person = []) | |
3 | -exports.many_people = manyPeople | |
4 | - | |
5 | -function manyPeople (ids) { | |
6 | - ids = Array.from(ids) | |
7 | - var featuredIds = ids.slice(-3).reverse() | |
8 | - | |
9 | - if (ids.length) { | |
10 | - if (ids.length > 3) { | |
11 | - return [ | |
12 | - person(featuredIds[0]), ', ', | |
13 | - person(featuredIds[1]), | |
14 | - ' and ', ids.length - 2, ' others' | |
15 | - ] | |
16 | - } else if (ids.length === 3) { | |
17 | - return [ | |
18 | - person(featuredIds[0]), ', ', | |
19 | - person(featuredIds[1]), ' and ', | |
20 | - person(featuredIds[2]) | |
21 | - ] | |
22 | - } else if (ids.length === 2) { | |
23 | - return [ | |
24 | - person(featuredIds[0]), ' and ', | |
25 | - person(featuredIds[1]) | |
26 | - ] | |
27 | - } else { | |
28 | - return person(featuredIds[0]) | |
29 | - } | |
30 | - } | |
31 | -} |
modules/message-confirm.js | ||
---|---|---|
@@ -1,51 +1,0 @@ | ||
1 | -var lightbox = require('hyperlightbox') | |
2 | -var h = require('../lib/h') | |
3 | -var plugs = require('patchbay/plugs') | |
4 | -var get_id = plugs.first(exports.get_id = []) | |
5 | -var publish = plugs.first(exports.sbot_publish = []) | |
6 | -var message_render = plugs.first(exports.message_render = []) | |
7 | - | |
8 | -exports.message_confirm = function (content, cb) { | |
9 | - cb = cb || function () {} | |
10 | - | |
11 | - var lb = lightbox() | |
12 | - document.body.appendChild(lb) | |
13 | - | |
14 | - var msg = { | |
15 | - value: { | |
16 | - author: get_id(), | |
17 | - previous: null, | |
18 | - sequence: null, | |
19 | - timestamp: Date.now(), | |
20 | - content: content | |
21 | - } | |
22 | - } | |
23 | - | |
24 | - var okay = h('button', { | |
25 | - 'ev-click': function () { | |
26 | - lb.remove() | |
27 | - publish(content, cb) | |
28 | - }, | |
29 | - 'ev-keydown': function (ev) { | |
30 | - if (ev.keyCode === 27) cancel.click() // escape | |
31 | - } | |
32 | - }, [ | |
33 | - 'okay' | |
34 | - ]) | |
35 | - | |
36 | - var cancel = h('button', {'ev-click': function () { | |
37 | - lb.remove() | |
38 | - cb(null) | |
39 | - }}, [ | |
40 | - 'Cancel' | |
41 | - ]) | |
42 | - | |
43 | - lb.show(h('MessageConfirm', [ | |
44 | - h('section', [ | |
45 | - message_render(msg) | |
46 | - ]), | |
47 | - h('footer', [okay, cancel]) | |
48 | - ])) | |
49 | - | |
50 | - okay.focus() | |
51 | -} |
modules/message-name.js | ||
---|---|---|
@@ -1,44 +1,0 @@ | ||
1 | -var plugs = require('patchbay/plugs') | |
2 | -var sbot_links = plugs.first(exports.sbot_links = []) | |
3 | -var get_id = plugs.first(exports.get_id = []) | |
4 | -var sbot_get = plugs.first(exports.sbot_get = []) | |
5 | -var getAvatar = require('ssb-avatar') | |
6 | - | |
7 | -exports.message_name = function (id, cb) { | |
8 | - sbot_get(id, function (err, value) { | |
9 | - if (err && err.name === 'NotFoundError') { | |
10 | - return cb(null, id.substring(0, 10) + '...(missing)') | |
11 | - } else if (value.content.type === 'post' && typeof value.content.text === 'string') { | |
12 | - if (value.content.text.trim()) { | |
13 | - return cb(null, titleFromMarkdown(value.content.text, 40)) | |
14 | - } | |
15 | - } else if (value.content.type === 'git-repo') { | |
16 | - return getRepoName(id, cb) | |
17 | - } else if (typeof value.content.text === 'string') { | |
18 | - return cb(null, value.content.type + ': ' + titleFromMarkdown(value.content.text, 30)) | |
19 | - } | |
20 | - | |
21 | - return cb(null, id.substring(0, 10) + '...') | |
22 | - }) | |
23 | -} | |
24 | - | |
25 | -function titleFromMarkdown (text, max) { | |
26 | - text = text.trim().split('\n', 2).join('\n') | |
27 | - text = text.replace(/_|`|\*|\#|\[.*?\]|\(\S*?\)/g, '').trim() | |
28 | - text = text.replace(/\:$/, '') | |
29 | - text = text.trim().split('\n', 1)[0].trim() | |
30 | - if (text.length > max) { | |
31 | - text = text.substring(0, max - 2) + '...' | |
32 | - } | |
33 | - return text | |
34 | -} | |
35 | - | |
36 | -function getRepoName (id, cb) { | |
37 | - getAvatar({ | |
38 | - links: sbot_links, | |
39 | - get: sbot_get | |
40 | - }, get_id(), id, function (err, avatar) { | |
41 | - if (err) return cb(err) | |
42 | - cb(null, avatar.name) | |
43 | - }) | |
44 | -} |
modules/message.js | ||
---|---|---|
@@ -1,119 +1,0 @@ | ||
1 | -var h = require('../lib/h') | |
2 | -var when = require('@mmckegg/mutant/when') | |
3 | - | |
4 | -var plugs = require('patchbay/plugs') | |
5 | -var message_content = plugs.first(exports.message_content = []) | |
6 | -var message_content_mini = plugs.first(exports.message_content_mini = []) | |
7 | -var message_link = plugs.first(exports.message_link = []) | |
8 | -var avatar_image = plugs.first(exports.avatar_image = []) | |
9 | -var avatar_name = plugs.first(exports.avatar_name = []) | |
10 | -var avatar_link = plugs.first(exports.avatar_link = []) | |
11 | -var message_meta = plugs.map(exports.message_meta = []) | |
12 | -var message_main_meta = plugs.map(exports.message_main_meta = []) | |
13 | -var message_action = plugs.map(exports.message_action = []) | |
14 | -var contextMenu = require('../lib/context-menu') | |
15 | - | |
16 | -exports.data_render = function (msg) { | |
17 | - var div = h('Message -data', { | |
18 | - 'ev-contextmenu': contextMenu.bind(null, msg) | |
19 | - }, [ | |
20 | - messageHeader(msg), | |
21 | - h('section', [ | |
22 | - h('pre', [ | |
23 | - JSON.stringify(msg, null, 2) | |
24 | - ]) | |
25 | - ]) | |
26 | - ]) | |
27 | - return div | |
28 | -} | |
29 | - | |
30 | -exports.message_render = function (msg, opts) { | |
31 | - opts = opts || {} | |
32 | - var inContext = opts.inContext | |
33 | - var previousId = opts.previousId | |
34 | - var inSummary = opts.inSummary | |
35 | - | |
36 | - var elMini = message_content_mini(msg) | |
37 | - var el = message_content(msg) | |
38 | - | |
39 | - if (elMini && (!el || inSummary)) { | |
40 | - var div = h('Message', { | |
41 | - 'ev-contextmenu': contextMenu.bind(null, msg) | |
42 | - }, [ | |
43 | - h('header', [ | |
44 | - h('div.mini', [ | |
45 | - avatar_link(msg.value.author, avatar_name(msg.value.author), ''), | |
46 | - ' ', elMini | |
47 | - ]), | |
48 | - h('div.meta', [message_main_meta(msg)]) | |
49 | - ]) | |
50 | - ]) | |
51 | - div.setAttribute('tabindex', '0') | |
52 | - return div | |
53 | - } | |
54 | - | |
55 | - if (!el) return | |
56 | - | |
57 | - var classList = [] | |
58 | - var replyInfo = null | |
59 | - | |
60 | - if (msg.value.content.root) { | |
61 | - classList.push('-reply') | |
62 | - if (!inContext) { | |
63 | - replyInfo = h('span', ['in reply to ', message_link(msg.value.content.root)]) | |
64 | - } else if (previousId && last(msg.value.content.branch) && previousId !== last(msg.value.content.branch)) { | |
65 | - replyInfo = h('span', ['in reply to ', message_link(last(msg.value.content.branch))]) | |
66 | - } | |
67 | - } | |
68 | - | |
69 | - var element = h('Message', { | |
70 | - classList, | |
71 | - 'ev-contextmenu': contextMenu.bind(null, msg), | |
72 | - 'ev-keydown': function (ev) { | |
73 | - // on enter, hit first meta. | |
74 | - if (ev.keyCode === 13) { | |
75 | - element.querySelector('.enter').click() | |
76 | - } | |
77 | - } | |
78 | - }, [ | |
79 | - messageHeader(msg, replyInfo), | |
80 | - h('section', [el]), | |
81 | - when(msg.key, h('footer', [ | |
82 | - h('div.actions', [ | |
83 | - message_action(msg), | |
84 | - h('a', {href: '#' + msg.key}, 'Reply') | |
85 | - ]) | |
86 | - ])) | |
87 | - ]) | |
88 | - | |
89 | - // ); hyperscript does not seem to set attributes correctly. | |
90 | - element.setAttribute('tabindex', '0') | |
91 | - | |
92 | - return element | |
93 | -} | |
94 | - | |
95 | -function messageHeader (msg, replyInfo) { | |
96 | - return h('header', [ | |
97 | - h('div.main', [ | |
98 | - h('a.avatar', {href: `#${msg.value.author}`}, avatar_image(msg.value.author)), | |
99 | - h('div.main', [ | |
100 | - h('div.name', [ | |
101 | - h('a', {href: `#${msg.value.author}`}, avatar_name(msg.value.author)) | |
102 | - ]), | |
103 | - h('div.meta', [ | |
104 | - message_main_meta(msg), | |
105 | - ' ', replyInfo | |
106 | - ]) | |
107 | - ]) | |
108 | - ]), | |
109 | - h('div.meta', message_meta(msg)) | |
110 | - ]) | |
111 | -} | |
112 | - | |
113 | -function last (array) { | |
114 | - if (Array.isArray(array)) { | |
115 | - return array[array.length - 1] | |
116 | - } else { | |
117 | - return array | |
118 | - } | |
119 | -} |
modules/notifications.js | ||
---|---|---|
@@ -1,148 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var paramap = require('pull-paramap') | |
3 | -var plugs = require('patchbay/plugs') | |
4 | -var cont = require('cont') | |
5 | -var ref = require('ssb-ref') | |
6 | - | |
7 | -var sbot_log = plugs.first(exports.sbot_log = []) | |
8 | -var sbot_get = plugs.first(exports.sbot_get = []) | |
9 | -var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
10 | -var message_unbox = plugs.first(exports.message_unbox = []) | |
11 | -var get_id = plugs.first(exports.get_id = []) | |
12 | -var feed_summary = plugs.first(exports.feed_summary = []) | |
13 | - | |
14 | -exports.screen_view = function (path) { | |
15 | - if (path === '/notifications') { | |
16 | - var oldest = null | |
17 | - var id = get_id() | |
18 | - var ids = { | |
19 | - [id]: true | |
20 | - } | |
21 | - | |
22 | - getFirstMessage(id, function (err, msg) { | |
23 | - if (err) return console.error(err) | |
24 | - if (!oldest || msg.value.timestamp < oldest) { | |
25 | - oldest = msg.value.timestamp | |
26 | - } | |
27 | - }) | |
28 | - | |
29 | - return feed_summary((opts) => { | |
30 | - if (opts.old === false) { | |
31 | - return pull( | |
32 | - sbot_log(opts), | |
33 | - unbox(), | |
34 | - notifications(ids), | |
35 | - pull.filter() | |
36 | - ) | |
37 | - } else { | |
38 | - return pull( | |
39 | - sbot_log(opts), | |
40 | - unbox(), | |
41 | - notifications(ids), | |
42 | - pull.filter(), | |
43 | - pull.take(function (msg) { | |
44 | - // abort stream after we pass the oldest messages of our feeds | |
45 | - return !oldest || msg.value.timestamp > oldest | |
46 | - }) | |
47 | - ) | |
48 | - } | |
49 | - }, [], { | |
50 | - windowSize: 200, | |
51 | - filter: (group) => { | |
52 | - return ( | |
53 | - ((group.message || group.type !== 'message') && (group.author !== id || group.digs && group.digs.size)) || ( | |
54 | - group.repliesFrom && group.repliesFrom.size && ( | |
55 | - !group.repliesFrom.has(id) || group.repliesFrom.size > 1 | |
56 | - ) | |
57 | - ) | |
58 | - ) | |
59 | - } | |
60 | - }) | |
61 | - } | |
62 | -} | |
63 | - | |
64 | -function unbox () { | |
65 | - return pull( | |
66 | - pull.map(function (msg) { | |
67 | - return msg.value && typeof msg.value.content === 'string' | |
68 | - ? message_unbox(msg) | |
69 | - : msg | |
70 | - }), | |
71 | - pull.filter(Boolean) | |
72 | - ) | |
73 | -} | |
74 | - | |
75 | -function notifications (ourIds) { | |
76 | - function linksToUs (link) { | |
77 | - return link && link.link in ourIds | |
78 | - } | |
79 | - | |
80 | - function isOurMsg (id, cb) { | |
81 | - if (!id) return cb(null, false) | |
82 | - if (typeof id === 'object' && typeof id.link === 'string') id = id.link | |
83 | - if (!ref.isMsg(id)) return cb(null, false) | |
84 | - sbot_get(id, function (err, msg) { | |
85 | - if (err && err.name === 'NotFoundError') cb(null, false) | |
86 | - else if (err) cb(err) | |
87 | - else if (msg.content.type === 'issue' || msg.content.type === 'pull-request') { | |
88 | - isOurMsg(msg.content.repo || msg.content.project, cb) | |
89 | - } else { | |
90 | - cb(err, msg.author in ourIds) | |
91 | - } | |
92 | - }) | |
93 | - } | |
94 | - | |
95 | - function isAnyOurMessage (msg, ids, cb) { | |
96 | - cont.para(ids.map(function (id) { | |
97 | - return function (cb) { isOurMsg(id, cb) } | |
98 | - }))(function (err, results) { | |
99 | - if (err) cb(err) | |
100 | - else if (results.some(Boolean)) cb(null, msg) | |
101 | - else cb() | |
102 | - }) | |
103 | - } | |
104 | - | |
105 | - return paramap(function (msg, cb) { | |
106 | - var c = msg.value && msg.value.content | |
107 | - if (!c || typeof c !== 'object') return cb() | |
108 | - if (msg.value.author in ourIds) return cb(null, msg) | |
109 | - | |
110 | - if (c.mentions && Array.isArray(c.mentions) && c.mentions.some(linksToUs)) { | |
111 | - return cb(null, msg) | |
112 | - } | |
113 | - | |
114 | - if (msg.private) { | |
115 | - return cb(null, msg) | |
116 | - } | |
117 | - | |
118 | - switch (c.type) { | |
119 | - case 'post': | |
120 | - if (c.branch || c.root) { | |
121 | - return isAnyOurMessage(msg, [].concat(c.branch, c.root), cb) | |
122 | - } else { | |
123 | - return cb() | |
124 | - } | |
125 | - case 'contact': | |
126 | - return cb(null, c.contact in ourIds ? msg : null) | |
127 | - case 'vote': | |
128 | - if (c.vote && c.vote.link) | |
129 | - return isOurMsg(c.vote.link, function (err, isOurs) { | |
130 | - cb(err, isOurs ? msg : null) | |
131 | - }) | |
132 | - else return cb() | |
133 | - case 'issue': | |
134 | - case 'pull-request': | |
135 | - return isOurMsg(c.project || c.repo, function (err, isOurs) { | |
136 | - cb(err, isOurs ? msg : null) | |
137 | - }) | |
138 | - case 'issue-edit': | |
139 | - return isAnyOurMessage(msg, [c.issue].concat(c.issues), cb) | |
140 | - default: | |
141 | - cb() | |
142 | - } | |
143 | - }, 4) | |
144 | -} | |
145 | - | |
146 | -function getFirstMessage (feedId, cb) { | |
147 | - sbot_user_feed({id: feedId, gte: 0, limit: 1})(null, cb) | |
148 | -} |
modules/obs-connected.js | ||
---|---|---|
@@ -1,29 +1,0 @@ | ||
1 | -var MutantSet = require('@mmckegg/mutant/set') | |
2 | -var plugs = require('patchbay/plugs') | |
3 | -var sbot_gossip_peers = plugs.first(exports.sbot_gossip_peers = []) | |
4 | - | |
5 | -var cache = null | |
6 | - | |
7 | -exports.obs_connected = function () { | |
8 | - if (cache) { | |
9 | - return cache | |
10 | - } else { | |
11 | - var result = MutantSet([], {nextTick: true}) | |
12 | - // todo: make this clean up on unlisten | |
13 | - | |
14 | - refresh() | |
15 | - setInterval(refresh, 10e3) | |
16 | - | |
17 | - cache = result | |
18 | - return result | |
19 | - } | |
20 | - | |
21 | - // scope | |
22 | - | |
23 | - function refresh () { | |
24 | - sbot_gossip_peers((err, peers) => { | |
25 | - if (err) throw console.log(err) | |
26 | - result.set(peers.filter(x => x.state === 'connected').map(x => x.key)) | |
27 | - }) | |
28 | - } | |
29 | -} |
modules/obs-following.js | ||
---|---|---|
@@ -1,47 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var computed = require('@mmckegg/mutant/computed') | |
3 | -var MutantPullReduce = require('../lib/mutant-pull-reduce') | |
4 | -var plugs = require('patchbay/plugs') | |
5 | -var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
6 | -var cache = {} | |
7 | -var throttle = require('@mmckegg/mutant/throttle') | |
8 | - | |
9 | -exports.obs_following = function (userId) { | |
10 | - if (cache[userId]) { | |
11 | - return cache[userId] | |
12 | - } else { | |
13 | - var stream = pull( | |
14 | - sbot_user_feed({id: userId, live: true}), | |
15 | - pull.filter((msg) => { | |
16 | - return !msg.value || msg.value.content.type === 'contact' | |
17 | - }) | |
18 | - ) | |
19 | - | |
20 | - var result = MutantPullReduce(stream, (result, msg) => { | |
21 | - var c = msg.value.content | |
22 | - if (c.contact) { | |
23 | - if (typeof c.following === 'boolean') { | |
24 | - if (c.following) { | |
25 | - result.add(c.contact) | |
26 | - } else { | |
27 | - result.delete(c.contact) | |
28 | - } | |
29 | - } | |
30 | - } | |
31 | - return result | |
32 | - }, { | |
33 | - startValue: new Set(), | |
34 | - nextTick: true | |
35 | - }) | |
36 | - | |
37 | - var instance = throttle(result, 2000) | |
38 | - instance.sync = result.sync | |
39 | - | |
40 | - instance.has = function (value) { | |
41 | - return computed(instance, x => x.has(value)) | |
42 | - } | |
43 | - | |
44 | - cache[userId] = instance | |
45 | - return instance | |
46 | - } | |
47 | -} |
modules/obs-local.js | ||
---|---|---|
@@ -1,29 +1,0 @@ | ||
1 | -var MutantSet = require('@mmckegg/mutant/set') | |
2 | -var plugs = require('patchbay/plugs') | |
3 | -var sbot_list_local = plugs.first(exports.sbot_list_local = []) | |
4 | - | |
5 | -var cache = null | |
6 | - | |
7 | -exports.obs_local = function () { | |
8 | - if (cache) { | |
9 | - return cache | |
10 | - } else { | |
11 | - var result = MutantSet([], {nextTick: true}) | |
12 | - // todo: make this clean up on unlisten | |
13 | - | |
14 | - refresh() | |
15 | - setInterval(refresh, 10e3) | |
16 | - | |
17 | - cache = result | |
18 | - return result | |
19 | - } | |
20 | - | |
21 | - // scope | |
22 | - | |
23 | - function refresh () { | |
24 | - sbot_list_local((err, keys) => { | |
25 | - if (err) throw console.log(err) | |
26 | - result.set(keys) | |
27 | - }) | |
28 | - } | |
29 | -} |
modules/obs-recently-updated-feeds.js | ||
---|---|---|
@@ -1,36 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var pullCat = require('pull-cat') | |
3 | -var computed = require('@mmckegg/mutant/computed') | |
4 | -var MutantPullReduce = require('../lib/mutant-pull-reduce') | |
5 | -var plugs = require('patchbay/plugs') | |
6 | -var sbot_log = plugs.first(exports.sbot_log = []) | |
7 | -var throttle = require('@mmckegg/mutant/throttle') | |
8 | -var hr = 60 * 60 * 1000 | |
9 | - | |
10 | -exports.obs_recently_updated_feeds = function (limit) { | |
11 | - var stream = pull( | |
12 | - pullCat([ | |
13 | - sbot_log({reverse: true, limit: limit || 500}), | |
14 | - sbot_log({old: false}) | |
15 | - ]) | |
16 | - ) | |
17 | - | |
18 | - var result = MutantPullReduce(stream, (result, msg) => { | |
19 | - if (msg.value.timestamp && Date.now() - msg.value.timestamp < 24 * hr) { | |
20 | - result.add(msg.value.author) | |
21 | - } | |
22 | - return result | |
23 | - }, { | |
24 | - startValue: new Set(), | |
25 | - nextTick: true | |
26 | - }) | |
27 | - | |
28 | - var instance = throttle(result, 2000) | |
29 | - instance.sync = result.sync | |
30 | - | |
31 | - instance.has = function (value) { | |
32 | - return computed(instance, x => x.has(value)) | |
33 | - } | |
34 | - | |
35 | - return instance | |
36 | -} |
modules/obs-subscribed-channels.js | ||
---|---|---|
@@ -1,50 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var computed = require('@mmckegg/mutant/computed') | |
3 | -var MutantPullReduce = require('../lib/mutant-pull-reduce') | |
4 | -var plugs = require('patchbay/plugs') | |
5 | -var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
6 | -var cache = {} | |
7 | -var throttle = require('@mmckegg/mutant/throttle') | |
8 | - | |
9 | -exports.obs_subscribed_channels = function (userId) { | |
10 | - if (cache[userId]) { | |
11 | - return cache[userId] | |
12 | - } else { | |
13 | - var stream = pull( | |
14 | - sbot_user_feed({id: userId, live: true}), | |
15 | - pull.filter((msg) => { | |
16 | - return !msg.value || msg.value.content.type === 'channel' | |
17 | - }) | |
18 | - ) | |
19 | - | |
20 | - var result = MutantPullReduce(stream, (result, msg) => { | |
21 | - var c = msg.value.content | |
22 | - if (typeof c.channel === 'string' && c.channel) { | |
23 | - var channel = c.channel.trim() | |
24 | - if (channel) { | |
25 | - if (typeof c.subscribed === 'boolean') { | |
26 | - if (c.subscribed) { | |
27 | - result.add(channel) | |
28 | - } else { | |
29 | - result.delete(channel) | |
30 | - } | |
31 | - } | |
32 | - } | |
33 | - } | |
34 | - return result | |
35 | - }, { | |
36 | - startValue: new Set(), | |
37 | - nextTick: true | |
38 | - }) | |
39 | - | |
40 | - var instance = throttle(result, 2000) | |
41 | - instance.sync = result.sync | |
42 | - | |
43 | - instance.has = function (value) { | |
44 | - return computed(instance, x => x.has(value)) | |
45 | - } | |
46 | - | |
47 | - cache[userId] = instance | |
48 | - return instance | |
49 | - } | |
50 | -} |
modules/people-names.js | ||
---|---|---|
@@ -1,22 +1,0 @@ | ||
1 | -var Value = require('@mmckegg/mutant/value') | |
2 | -var plugs = require('patchbay/plugs') | |
3 | -var signifier = plugs.first(exports.signifier = []) | |
4 | -var computed = require('@mmckegg/mutant/computed') | |
5 | - | |
6 | -exports.people_names = function (ids) { | |
7 | - return computed(Array.from(ids).map(ObservName), join) || '' | |
8 | -} | |
9 | - | |
10 | -function join (...args) { | |
11 | - return args.join('\n') | |
12 | -} | |
13 | - | |
14 | -function ObservName (id) { | |
15 | - var obs = Value(id.slice(0, 10)) | |
16 | - signifier(id, (_, value) => { | |
17 | - if (value && value.length) { | |
18 | - obs.set(value[0].name) | |
19 | - } | |
20 | - }) | |
21 | - return obs | |
22 | -} |
modules/person.js | ||
---|---|---|
@@ -1,9 +1,0 @@ | ||
1 | -var plugs = require('patchbay/plugs') | |
2 | -var avatar_name = plugs.first(exports.avatar_name = []) | |
3 | -var avatar_link = plugs.first(exports.avatar_link = []) | |
4 | - | |
5 | -exports.person = person | |
6 | - | |
7 | -function person (id) { | |
8 | - return avatar_link(id, avatar_name(id), '') | |
9 | -} |
modules/post.js | ||
---|---|---|
@@ -1,13 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | -var plugs = require('patchbay/plugs') | |
3 | -var message_link = plugs.first(exports.message_link = []) | |
4 | -var markdown = plugs.first(exports.markdown = []) | |
5 | - | |
6 | -exports.message_content = function (data) { | |
7 | - if(!data.value.content || !data.value.content.text) return | |
8 | - | |
9 | - return h('div', | |
10 | - markdown(data.value.content) | |
11 | - ) | |
12 | - | |
13 | -} |
modules/private.js | ||
---|---|---|
@@ -1,84 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var ref = require('ssb-ref') | |
3 | -var plugs = require('patchbay/plugs') | |
4 | -var message_compose = plugs.first(exports.message_compose = []) | |
5 | -var sbot_log = plugs.first(exports.sbot_log = []) | |
6 | -var feed_summary = plugs.first(exports.feed_summary = []) | |
7 | -var message_unbox = plugs.first(exports.message_unbox = []) | |
8 | -var get_id = plugs.first(exports.get_id = []) | |
9 | -var avatar_image_link = plugs.first(exports.avatar_image_link = []) | |
10 | -var update_cache = plugs.first(exports.update_cache = []) | |
11 | -var h = require('../lib/h') | |
12 | - | |
13 | -exports.screen_view = function (path, sbot) { | |
14 | - if (path === '/private') { | |
15 | - var id = get_id() | |
16 | - | |
17 | - return feed_summary((opts) => { | |
18 | - return pull( | |
19 | - sbot_log(opts), | |
20 | - loosen(10), // release tight loops if they continue too long (avoid scroll jank) | |
21 | - unbox(), | |
22 | - pull.through((item) => { | |
23 | - if (item.value) { | |
24 | - update_cache(item) | |
25 | - } | |
26 | - }) | |
27 | - ) | |
28 | - }, [ | |
29 | - message_compose({type: 'post', recps: [], private: true}, { | |
30 | - prepublish: function (msg) { | |
31 | - msg.recps = [id].concat(msg.mentions).filter(function (e) { | |
32 | - return ref.isFeed(typeof e === 'string' ? e : e.link) | |
33 | - }) | |
34 | - if (!msg.recps.length) { | |
35 | - throw new Error('cannot make private message without recipients - just mention the user in an at reply in the message you send') | |
36 | - } | |
37 | - return msg | |
38 | - }, | |
39 | - placeholder: `Write a private message \n\n\n\nThis can only be read by yourself and people you have @mentioned.` | |
40 | - }) | |
41 | - ], { | |
42 | - windowSize: 1000 | |
43 | - }) | |
44 | - } | |
45 | -} | |
46 | - | |
47 | -exports.message_meta = function (msg) { | |
48 | - if (msg.value.content.recps || msg.value.private) { | |
49 | - return h('span.private', [ | |
50 | - map(msg.value.content.recps, function (id) { | |
51 | - return avatar_image_link(typeof id === 'string' ? id : id.link, 'thumbnail') | |
52 | - }) | |
53 | - ]) | |
54 | - } | |
55 | -} | |
56 | - | |
57 | -function unbox () { | |
58 | - return pull( | |
59 | - pull.filter(function (msg) { | |
60 | - return typeof msg.value.content === 'string' | |
61 | - }), | |
62 | - pull.map(function (msg) { | |
63 | - return message_unbox(msg) || { timestamp: msg.timestamp } | |
64 | - }) | |
65 | - ) | |
66 | -} | |
67 | - | |
68 | -function map (ary, iter) { | |
69 | - if (Array.isArray(ary)) return ary.map(iter) | |
70 | -} | |
71 | - | |
72 | -function loosen (max) { | |
73 | - var lastRelease = Date.now() | |
74 | - return pull.asyncMap(function (item, cb) { | |
75 | - if (Date.now() - lastRelease > max) { | |
76 | - setImmediate(() => { | |
77 | - lastRelease = Date.now() | |
78 | - cb(null, item) | |
79 | - }) | |
80 | - } else { | |
81 | - cb(null, item) | |
82 | - } | |
83 | - }) | |
84 | -} |
modules/public.js | ||
---|---|---|
@@ -1,225 +1,0 @@ | ||
1 | -var MutantMap = require('@mmckegg/mutant/map') | |
2 | -var computed = require('@mmckegg/mutant/computed') | |
3 | -var when = require('@mmckegg/mutant/when') | |
4 | -var send = require('@mmckegg/mutant/send') | |
5 | -var pull = require('pull-stream') | |
6 | -var extend = require('xtend') | |
7 | - | |
8 | -var plugs = require('patchbay/plugs') | |
9 | -var h = require('../lib/h') | |
10 | -var message_compose = plugs.first(exports.message_compose = []) | |
11 | -var sbot_log = plugs.first(exports.sbot_log = []) | |
12 | -var sbot_feed = plugs.first(exports.sbot_feed = []) | |
13 | -var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
14 | - | |
15 | -var feed_summary = plugs.first(exports.feed_summary = []) | |
16 | -var obs_channels = plugs.first(exports.obs_channels = []) | |
17 | -var obs_subscribed_channels = plugs.first(exports.obs_subscribed_channels = []) | |
18 | -var get_id = plugs.first(exports.get_id = []) | |
19 | -var publish = plugs.first(exports.sbot_publish = []) | |
20 | -var obs_following = plugs.first(exports.obs_following = []) | |
21 | -var obs_recently_updated_feeds = plugs.first(exports.obs_recently_updated_feeds = []) | |
22 | -var avatar_image = plugs.first(exports.avatar_image = []) | |
23 | -var avatar_name = plugs.first(exports.avatar_name = []) | |
24 | -var obs_local = plugs.first(exports.obs_local = []) | |
25 | -var obs_connected = plugs.first(exports.obs_connected = []) | |
26 | - | |
27 | -exports.screen_view = function (path, sbot) { | |
28 | - if (path === '/public') { | |
29 | - var id = get_id() | |
30 | - var channels = computed(obs_channels(), items => items.slice(0, 8), {comparer: arrayEq}) | |
31 | - var subscribedChannels = obs_subscribed_channels(id) | |
32 | - var loading = computed(subscribedChannels.sync, x => !x) | |
33 | - var connectedPeers = obs_connected() | |
34 | - var localPeers = obs_local() | |
35 | - var connectedPubs = computed([connectedPeers, localPeers], (c, l) => c.filter(x => !l.includes(x))) | |
36 | - var following = obs_following(id) | |
37 | - | |
38 | - var oldest = Date.now() - (2 * 24 * 60 * 60e3) | |
39 | - getFirstMessage(id, (_, msg) => { | |
40 | - if (msg) { | |
41 | - // fall back to timestamp stream before this, give 48 hrs for feeds to stabilize | |
42 | - if (msg.value.timestamp > oldest) { | |
43 | - oldest = Date.now() | |
44 | - } | |
45 | - } | |
46 | - }) | |
47 | - | |
48 | - var whoToFollow = computed([obs_following(id), obs_recently_updated_feeds(200)], (following, recent) => { | |
49 | - return Array.from(recent).filter(x => x !== id && !following.has(x)).slice(0, 10) | |
50 | - }) | |
51 | - | |
52 | - var feedSummary = feed_summary(getFeed, [ | |
53 | - message_compose({type: 'post'}, {placeholder: 'Write a public message'}) | |
54 | - ], { | |
55 | - waitUntil: computed([ | |
56 | - following.sync, | |
57 | - subscribedChannels.sync | |
58 | - ], x => x.every(Boolean)), | |
59 | - windowSize: 500, | |
60 | - filter: (item) => { | |
61 | - return ( | |
62 | - id === item.author || | |
63 | - following().has(item.author) || | |
64 | - subscribedChannels().has(item.channel) || | |
65 | - (item.repliesFrom && item.repliesFrom.has(id)) || | |
66 | - item.digs && item.digs.has(id) | |
67 | - ) | |
68 | - }, | |
69 | - bumpFilter: (msg, group) => { | |
70 | - if (!group.message) { | |
71 | - return ( | |
72 | - isMentioned(id, msg.value.content.mentions) || | |
73 | - msg.value.author === id || ( | |
74 | - fromDay(msg, group.fromTime) && ( | |
75 | - following().has(msg.value.author) || | |
76 | - group.repliesFrom.has(id) | |
77 | - ) | |
78 | - ) | |
79 | - ) | |
80 | - } | |
81 | - return true | |
82 | - } | |
83 | - }) | |
84 | - | |
85 | - var result = h('SplitView', [ | |
86 | - h('div.side', [ | |
87 | - h('h2', 'Active Channels'), | |
88 | - when(loading, [ h('Loading') ]), | |
89 | - h('ChannelList', { | |
90 | - hidden: loading | |
91 | - }, [ | |
92 | - MutantMap(channels, (channel) => { | |
93 | - var subscribed = subscribedChannels.has(channel.id) | |
94 | - return h('a.channel', { | |
95 | - href: `##${channel.id}`, | |
96 | - classList: [ | |
97 | - when(subscribed, '-subscribed') | |
98 | - ] | |
99 | - }, [ | |
100 | - h('span.name', '#' + channel.id), | |
101 | - when(subscribed, | |
102 | - h('a -unsubscribe', { | |
103 | - 'ev-click': send(unsubscribe, channel.id) | |
104 | - }, 'Unsubscribe'), | |
105 | - h('a -subscribe', { | |
106 | - 'ev-click': send(subscribe, channel.id) | |
107 | - }, 'Subscribe') | |
108 | - ) | |
109 | - ]) | |
110 | - }, {maxTime: 5}) | |
111 | - ]), | |
112 | - | |
113 | - when(computed(localPeers, x => x.length), h('h2', 'Local')), | |
114 | - h('ProfileList', [ | |
115 | - MutantMap(localPeers, (id) => { | |
116 | - return h('a.profile', { | |
117 | - classList: [ | |
118 | - when(computed([connectedPeers, id], (p, id) => p.includes(id)), '-connected') | |
119 | - ], | |
120 | - href: `#${id}` | |
121 | - }, [ | |
122 | - h('div.avatar', [avatar_image(id)]), | |
123 | - h('div.main', [ | |
124 | - h('div.name', [ avatar_name(id) ]) | |
125 | - ]) | |
126 | - ]) | |
127 | - }) | |
128 | - ]), | |
129 | - | |
130 | - when(computed(whoToFollow, x => x.length), h('h2', 'Who to follow')), | |
131 | - h('ProfileList', [ | |
132 | - MutantMap(whoToFollow, (id) => { | |
133 | - return h('a.profile', { | |
134 | - href: `#${id}` | |
135 | - }, [ | |
136 | - h('div.avatar', [avatar_image(id)]), | |
137 | - h('div.main', [ | |
138 | - h('div.name', [ avatar_name(id) ]) | |
139 | - ]) | |
140 | - ]) | |
141 | - }) | |
142 | - ]), | |
143 | - | |
144 | - when(computed(connectedPubs, x => x.length), h('h2', 'Connected Pubs')), | |
145 | - h('ProfileList', [ | |
146 | - MutantMap(connectedPubs, (id) => { | |
147 | - return h('a.profile', { | |
148 | - classList: [ '-connected' ], | |
149 | - href: `#${id}` | |
150 | - }, [ | |
151 | - h('div.avatar', [avatar_image(id)]), | |
152 | - h('div.main', [ | |
153 | - h('div.name', [ avatar_name(id) ]) | |
154 | - ]) | |
155 | - ]) | |
156 | - }) | |
157 | - ]) | |
158 | - ]), | |
159 | - h('div.main', [ feedSummary ]) | |
160 | - ]) | |
161 | - | |
162 | - result.pendingUpdates = feedSummary.pendingUpdates | |
163 | - result.reload = feedSummary.reload | |
164 | - | |
165 | - return result | |
166 | - } | |
167 | - | |
168 | - // scoped | |
169 | - | |
170 | - function getFeed (opts) { | |
171 | - if (opts.lt && opts.lt < oldest) { | |
172 | - opts = extend(opts, {lt: parseInt(opts.lt, 10)}) | |
173 | - return pull( | |
174 | - sbot_feed(opts), | |
175 | - pull.map((msg) => { | |
176 | - if (msg.sync) { | |
177 | - return msg | |
178 | - } else { | |
179 | - return {key: msg.key, value: msg.value, timestamp: msg.value.timestamp} | |
180 | - } | |
181 | - }) | |
182 | - ) | |
183 | - } else { | |
184 | - return sbot_log(opts) | |
185 | - } | |
186 | - } | |
187 | -} | |
188 | - | |
189 | -function fromDay (msg, fromTime) { | |
190 | - return (fromTime - msg.timestamp) < (24 * 60 * 60e3) | |
191 | -} | |
192 | - | |
193 | -function isMentioned (id, list) { | |
194 | - if (Array.isArray(list)) { | |
195 | - return list.includes(id) | |
196 | - } else { | |
197 | - return false | |
198 | - } | |
199 | -} | |
200 | - | |
201 | -function subscribe (id) { | |
202 | - publish({ | |
203 | - type: 'channel', | |
204 | - channel: id, | |
205 | - subscribed: true | |
206 | - }) | |
207 | -} | |
208 | - | |
209 | -function unsubscribe (id) { | |
210 | - publish({ | |
211 | - type: 'channel', | |
212 | - channel: id, | |
213 | - subscribed: false | |
214 | - }) | |
215 | -} | |
216 | - | |
217 | -function arrayEq (a, b) { | |
218 | - if (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a !== b) { | |
219 | - return a.every((value, i) => value === b[i]) | |
220 | - } | |
221 | -} | |
222 | - | |
223 | -function getFirstMessage (feedId, cb) { | |
224 | - sbot_user_feed({id: feedId, gte: 0, limit: 1})(null, cb) | |
225 | -} |
modules/raw.js | ||
---|---|---|
@@ -1,1 +1,0 @@ | ||
1 | -// disable |
modules/thread.js | ||
---|---|---|
@@ -1,127 +1,0 @@ | ||
1 | -var ui = require('patchbay/ui') | |
2 | -var pull = require('pull-stream') | |
3 | -var Cat = require('pull-cat') | |
4 | -var sort = require('ssb-sort') | |
5 | -var ref = require('ssb-ref') | |
6 | -var h = require('hyperscript') | |
7 | -var u = require('patchbay/util') | |
8 | -var Scroller = require('pull-scroll') | |
9 | - | |
10 | - | |
11 | -function once (cont) { | |
12 | - var ended = false | |
13 | - return function (abort, cb) { | |
14 | - if(abort) return cb(abort) | |
15 | - else if (ended) return cb(ended) | |
16 | - else | |
17 | - cont(function (err, data) { | |
18 | - if(err) return cb(ended = err) | |
19 | - ended = true | |
20 | - cb(null, data) | |
21 | - }) | |
22 | - } | |
23 | -} | |
24 | - | |
25 | -var plugs = require('patchbay/plugs') | |
26 | - | |
27 | -var message_render = plugs.first(exports.message_render = []) | |
28 | -var message_name = plugs.first(exports.message_name = []) | |
29 | -var message_compose = plugs.first(exports.message_compose = []) | |
30 | -var message_unbox = plugs.first(exports.message_unbox = []) | |
31 | - | |
32 | -var sbot_get = plugs.first(exports.sbot_get = []) | |
33 | -var sbot_links = plugs.first(exports.sbot_links = []) | |
34 | -var get_id = plugs.first(exports.get_id = []) | |
35 | - | |
36 | -function getThread (root, cb) { | |
37 | - //in this case, it's inconvienent that panel only takes | |
38 | - //a stream. maybe it would be better to accept an array? | |
39 | - | |
40 | - sbot_get(root, function (err, value) { | |
41 | - var msg = {key: root, value: value} | |
42 | -// if(value.content.root) return getThread(value.content.root, cb) | |
43 | - | |
44 | - pull( | |
45 | - sbot_links({rel: 'root', dest: root, values: true, keys: true}), | |
46 | - pull.collect(function (err, ary) { | |
47 | - if(err) return cb(err) | |
48 | - ary.unshift(msg) | |
49 | - cb(null, ary) | |
50 | - }) | |
51 | - ) | |
52 | - }) | |
53 | - | |
54 | -} | |
55 | - | |
56 | -exports.screen_view = function (id) { | |
57 | - if(ref.isMsg(id)) { | |
58 | - var meta = { | |
59 | - type: 'post', | |
60 | - root: id, | |
61 | - branch: id //mutated when thread is loaded. | |
62 | - } | |
63 | - | |
64 | - var previousId = id | |
65 | - var content = h('div.column.scroller__content') | |
66 | - var div = h('div.column.scroller', | |
67 | - {style: {'overflow-y': 'auto'}}, | |
68 | - h('div.scroller__wrapper', | |
69 | - content, | |
70 | - message_compose(meta, {shrink: false, placeholder: 'Write a reply'}) | |
71 | - ) | |
72 | - ) | |
73 | - | |
74 | - message_name(id, function (err, name) { | |
75 | - div.title = name | |
76 | - }) | |
77 | - | |
78 | - pull( | |
79 | - sbot_links({ | |
80 | - rel: 'root', dest: id, keys: true, old: false | |
81 | - }), | |
82 | - pull.drain(function (msg) { | |
83 | - loadThread() //redraw thread | |
84 | - }, function () {} ) | |
85 | - ) | |
86 | - | |
87 | - | |
88 | - function loadThread () { | |
89 | - getThread(id, function (err, thread) { | |
90 | - //would probably be better keep an id for each message element | |
91 | - //(i.e. message key) and then update it if necessary. | |
92 | - //also, it may have moved (say, if you received a missing message) | |
93 | - content.innerHTML = '' | |
94 | - //decrypt | |
95 | - thread = thread.map(function (msg) { | |
96 | - return 'string' === typeof msg.value.content ? message_unbox(msg) : msg | |
97 | - }) | |
98 | - | |
99 | - if(err) return content.appendChild(h('pre', err.stack)) | |
100 | - sort(thread).map((msg) => { | |
101 | - var result = message_render(msg, {inContext: true, previousId}) | |
102 | - previousId = msg.key | |
103 | - return result | |
104 | - }).filter(Boolean).forEach(function (el) { | |
105 | - content.appendChild(el) | |
106 | - }) | |
107 | - | |
108 | - var branches = sort.heads(thread) | |
109 | - meta.branch = branches.length > 1 ? branches : branches[0] | |
110 | - meta.root = thread[0].value.content.root || thread[0].key | |
111 | - meta.channel = thread[0].value.content.channel | |
112 | - | |
113 | - var recps = thread[0].value.content.recps | |
114 | - var private = thread[0].value.private | |
115 | - if(private) { | |
116 | - if(recps) | |
117 | - meta.recps = recps | |
118 | - else | |
119 | - meta.recps = [thread[0].value.author, get_id()] | |
120 | - } | |
121 | - }) | |
122 | - } | |
123 | - | |
124 | - loadThread() | |
125 | - return div | |
126 | - } | |
127 | -} |
modules/timestamp.js | ||
---|---|---|
@@ -1,20 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | -var human = require('human-time') | |
3 | - | |
4 | -function updateTimestampEl(el) { | |
5 | - el.firstChild.nodeValue = human(new Date(el.timestamp)) | |
6 | - return el | |
7 | -} | |
8 | - | |
9 | -setInterval(function () { | |
10 | - var els = [].slice.call(document.querySelectorAll('.pw__timestamp')) | |
11 | - els.forEach(updateTimestampEl) | |
12 | -}, 60e3) | |
13 | - | |
14 | -exports.message_main_meta = function (msg) { | |
15 | - return updateTimestampEl(h('a.enter.pw__timestamp', { | |
16 | - href: '#'+msg.key, | |
17 | - timestamp: msg.value.timestamp, | |
18 | - title: new Date(msg.value.timestamp) | |
19 | - }, '')) | |
20 | -} |
package.json | ||
---|---|---|
@@ -27,9 +27,9 @@ | ||
27 | 27 | "micro-css": "~0.6.2", |
28 | 28 | "non-private-ip": "^1.4.1", |
29 | 29 | "on-change-network": "0.0.2", |
30 | 30 | "on-wakeup": "^1.0.1", |
31 | - "patchbay": "~4.0.1", | |
31 | + "patchbay": "github:ssbc/patchbay#extract-styles-from-depject", | |
32 | 32 | "prebuild": "github:mmckegg/prebuild#use-npm-conf", |
33 | 33 | "pull-abortable": "^4.1.0", |
34 | 34 | "pull-file": "~1.0.0", |
35 | 35 | "pull-identify-filetype": "^1.1.0", |
@@ -38,9 +38,9 @@ | ||
38 | 38 | "pull-pause": "0.0.0", |
39 | 39 | "pull-ping": "^2.0.2", |
40 | 40 | "pull-pushable": "^2.0.1", |
41 | 41 | "pull-stream": "~3.4.5", |
42 | - "scuttlebot": "~9.2.0", | |
42 | + "scuttlebot": "^9.4.3", | |
43 | 43 | "sorted-array-functions": "~1.0.0", |
44 | 44 | "ssb-avatar": "^0.2.0", |
45 | 45 | "ssb-blobs": "~0.1.7", |
46 | 46 | "ssb-keys": "~7.0.0", |
server-process.js | ||
---|---|---|
@@ -4,10 +4,10 @@ | ||
4 | 4 | var electron = require('electron') |
5 | 5 | |
6 | 6 | var createSbot = require('scuttlebot') |
7 | 7 | .use(require('scuttlebot/plugins/master')) |
8 | - .use(require('./lib/persistent-gossip')) // override | |
9 | - .use(require('./lib/friends-with-gossip-priority')) | |
8 | + .use(require('scuttlebot/plugins/gossip')) // override | |
9 | + .use(require('scuttlebot/plugins/friends')) | |
10 | 10 | .use(require('scuttlebot/plugins/replicate')) |
11 | 11 | .use(require('ssb-blobs')) |
12 | 12 | .use(require('scuttlebot/plugins/invite')) |
13 | 13 | .use(require('scuttlebot/plugins/block')) |
styles/index.js | ||
---|---|---|
@@ -2,11 +2,10 @@ | ||
2 | 2 | var path = require('path') |
3 | 3 | var compile = require('micro-css') |
4 | 4 | var result = '' |
5 | 5 | var additional = '' |
6 | +var baseStyles = require('patchbay/styles') | |
6 | 7 | |
7 | -additional += fs.readFileSync(require.resolve('patchbay/style.css'), 'utf8') | |
8 | - | |
9 | 8 | fs.readdirSync(__dirname).forEach(function (file) { |
10 | 9 | if (/\.mcss$/i.test(file)) { |
11 | 10 | result += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n' |
12 | 11 | } |
@@ -15,5 +14,5 @@ | ||
15 | 14 | additional += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n' |
16 | 15 | } |
17 | 16 | }) |
18 | 17 | |
19 | -module.exports = compile(result) + additional | |
18 | +module.exports = baseStyles + compile(result) + additional |
styles/channel-list.mcss | ||
---|---|---|
@@ -1,73 +1,0 @@ | ||
1 | -ChannelList { | |
2 | - a.channel { | |
3 | - display: flex; | |
4 | - padding: 8px 10px; | |
5 | - font-size: 110%; | |
6 | - margin: 4px 0; | |
7 | - background: rgba(255, 255, 255, 0.22); | |
8 | - border-radius: 5px; | |
9 | - position: relative | |
10 | - transition: background-color 0.2s | |
11 | - max-width: 250px; | |
12 | - | |
13 | - background-repeat: no-repeat | |
14 | - background-position: right | |
15 | - | |
16 | - -subscribed { | |
17 | - background-image: svg(subscribed) | |
18 | - span.name { | |
19 | - font-weight: bold | |
20 | - } | |
21 | - } | |
22 | - | |
23 | - @svg subscribed { | |
24 | - width: 20px | |
25 | - height: 12px | |
26 | - content: "<circle cx='6' stroke='#888' fill='none' cy='6' r='5' /> <circle cx='6' cy='6' r='3' fill='#888'/>" | |
27 | - } | |
28 | - | |
29 | - :hover { | |
30 | - background: rgba(255, 255, 255, 0.4); | |
31 | - text-decoration: none; | |
32 | - a { | |
33 | - transition: opacity 0.05s | |
34 | - opacity: 1 | |
35 | - } | |
36 | - } | |
37 | - | |
38 | - span.name { | |
39 | - flex: 1 | |
40 | - white-space: nowrap; | |
41 | - min-width: 0; | |
42 | - } | |
43 | - | |
44 | - a { | |
45 | - display: inline | |
46 | - opacity: 0; | |
47 | - font-size: 80%; | |
48 | - background-color: rgb(112, 112, 112); | |
49 | - transition: opacity 0.2s, background-color 0.4s | |
50 | - padding: 9px 10px; | |
51 | - color: white; | |
52 | - border-radius: 4px; | |
53 | - font-weight: bold; | |
54 | - margin: -8px -10px -8px 4px; | |
55 | - border-top-left-radius: 0; | |
56 | - border-bottom-left-radius: 0; | |
57 | - border-left: 2px solid rgba(255, 255, 255, 0.9); | |
58 | - text-decoration: none | |
59 | - | |
60 | - -subscribe { | |
61 | - :hover { | |
62 | - background-color: rgb(112, 184, 212); | |
63 | - } | |
64 | - } | |
65 | - | |
66 | - -unsubscribe { | |
67 | - :hover { | |
68 | - background: rgb(212, 112, 112); | |
69 | - } | |
70 | - } | |
71 | - } | |
72 | - } | |
73 | -} |
styles/emoji.css | ||
---|---|---|
@@ -1,0 +1,6 @@ | ||
1 | +img.emoji { | |
2 | + width: 1.5em; | |
3 | + height: 1.5em; | |
4 | + align-content: center; | |
5 | + margin-top: -0.2em; | |
6 | +} |
styles/feed-event.mcss | ||
---|---|---|
@@ -1,36 +1,0 @@ | ||
1 | -FeedEvent { | |
2 | - display: flex | |
3 | - flex: 1 | |
4 | - flex-direction: column | |
5 | - background: white | |
6 | - margin-top: 10px | |
7 | - | |
8 | - div { | |
9 | - flex: 1 | |
10 | - } | |
11 | - | |
12 | - a.full { | |
13 | - display: block; | |
14 | - padding: 10px; | |
15 | - background: #daecd6; | |
16 | - border-top: 1px solid #bbc9d2; | |
17 | - border-bottom: 1px solid #bbc9d2; | |
18 | - text-align: center; | |
19 | - color: #759053; | |
20 | - } | |
21 | - | |
22 | - div.replies { | |
23 | - font-size: 100% | |
24 | - display: flex | |
25 | - flex-direction: column | |
26 | - div { | |
27 | - flex: 1 | |
28 | - margin: 0 | |
29 | - } | |
30 | - } | |
31 | - | |
32 | - div.meta { | |
33 | - font-size: 100% | |
34 | - padding: 10px | |
35 | - } | |
36 | -} |
styles/loading.mcss | ||
---|---|---|
@@ -1,63 +1,0 @@ | ||
1 | -Loading { | |
2 | - height: 25% | |
3 | - display: flex | |
4 | - align-items: center | |
5 | - justify-content: center | |
6 | - | |
7 | - -inline { | |
8 | - height: 16px | |
9 | - width: 16px | |
10 | - display: inline-block | |
11 | - margin: -3px 3px | |
12 | - | |
13 | - ::before { | |
14 | - display: block | |
15 | - height: 16px | |
16 | - width: 16px | |
17 | - } | |
18 | - } | |
19 | - | |
20 | - -large { | |
21 | - ::before { | |
22 | - height: 100px | |
23 | - width: 100px | |
24 | - } | |
25 | - ::after { | |
26 | - color: #CCC; | |
27 | - content: 'Loading...' | |
28 | - font-size: 200% | |
29 | - } | |
30 | - } | |
31 | - | |
32 | - ::before { | |
33 | - content: ' ' | |
34 | - height: 50px | |
35 | - width: 50px | |
36 | - background-image: svg(waitingIcon) | |
37 | - background-repeat: no-repeat | |
38 | - background-position: center | |
39 | - background-size: contain | |
40 | - animation: spin 3s infinite linear | |
41 | - } | |
42 | -} | |
43 | - | |
44 | -@svg waitingIcon { | |
45 | - width: 30px | |
46 | - height: 30px | |
47 | - content: "<circle cx='15' cy='15' r='10' /><circle cx='10' cy='10' r='2' /><circle cx='20' cy='20' r='3' />" | |
48 | - | |
49 | - circle { | |
50 | - stroke: #CCC | |
51 | - stroke-width: 3px | |
52 | - fill: none | |
53 | - } | |
54 | -} | |
55 | - | |
56 | -@keyframes spin { | |
57 | - 0% { | |
58 | - transform: rotate(0deg); | |
59 | - } | |
60 | - 100% { | |
61 | - transform: rotate(360deg); | |
62 | - } | |
63 | -} |
styles/message-confirm.mcss | ||
---|---|---|
@@ -1,18 +1,0 @@ | ||
1 | -MessageConfirm { | |
2 | - section { | |
3 | - max-height: 80vh; | |
4 | - overflow: auto; | |
5 | - margin: -20px; | |
6 | - padding: 20px; | |
7 | - margin-bottom: 0; | |
8 | - } | |
9 | - footer { | |
10 | - text-align: right; | |
11 | - background: #e2e2e2; | |
12 | - margin: 0px -20px -20px; | |
13 | - padding: 5px; | |
14 | - border-top: 1px solid #d6d6d6; | |
15 | - position: relative; | |
16 | - box-shadow: 0 0 6px rgba(51, 51, 51, 0.47); | |
17 | - } | |
18 | -} |
styles/message.mcss | ||
---|---|---|
@@ -1,166 +1,0 @@ | ||
1 | -Message { | |
2 | - display: flex | |
3 | - flex-direction: column | |
4 | - box-shadow: #dadada 1px 2px 8px | |
5 | - border: 1px solid #f5f5f5 | |
6 | - background: white | |
7 | - position: relative | |
8 | - font-size: 120% | |
9 | - | |
10 | - :focus { | |
11 | - z-index: 1 | |
12 | - } | |
13 | - | |
14 | - -data { | |
15 | - header { | |
16 | - div.main { | |
17 | - font-size: 80% | |
18 | - a.avatar { | |
19 | - img { | |
20 | - width: 25px | |
21 | - } | |
22 | - } | |
23 | - } | |
24 | - } | |
25 | - (pre) { | |
26 | - overflow: auto | |
27 | - max-height: 200px | |
28 | - } | |
29 | - } | |
30 | - | |
31 | - -reply { | |
32 | - font-size: 100% | |
33 | - header { | |
34 | - div.main { | |
35 | - a.avatar { | |
36 | - img { | |
37 | - width: 30px | |
38 | - } | |
39 | - } | |
40 | - } | |
41 | - } | |
42 | - } | |
43 | - | |
44 | - header { | |
45 | - margin: 15px 15px | |
46 | - display: flex | |
47 | - | |
48 | - div.mini { | |
49 | - flex: 1 | |
50 | - } | |
51 | - | |
52 | - div.main { | |
53 | - display: flex | |
54 | - flex: 1 | |
55 | - | |
56 | - a.avatar { | |
57 | - img { | |
58 | - width: 50px | |
59 | - } | |
60 | - } | |
61 | - | |
62 | - div.main { | |
63 | - div.name { | |
64 | - font-size: 120% | |
65 | - a { | |
66 | - color: #444 | |
67 | - font-weight: bold | |
68 | - } | |
69 | - } | |
70 | - div.meta { | |
71 | - font-size: 90% | |
72 | - } | |
73 | - margin-left: 10px | |
74 | - } | |
75 | - | |
76 | - div.meta { | |
77 | - | |
78 | - } | |
79 | - } | |
80 | - | |
81 | - div.meta { | |
82 | - | |
83 | - em { | |
84 | - display: inline-block | |
85 | - padding: 4px | |
86 | - } | |
87 | - | |
88 | - a.channel { | |
89 | - display: inline-block | |
90 | - padding: 4px | |
91 | - } | |
92 | - | |
93 | - span.likes { | |
94 | - color: #ffffff; | |
95 | - background: linear-gradient(45deg, #859c88, #87d47d); | |
96 | - padding: 5px 8px; | |
97 | - border-radius: 10px; | |
98 | - display: inline-block; | |
99 | - vertical-align: top; | |
100 | - } | |
101 | - | |
102 | - span.private { | |
103 | - display: inline-block; | |
104 | - margin: -3px -3px -3px 4px; | |
105 | - border: 4px solid #525050; | |
106 | - position: relative; | |
107 | - | |
108 | - a { | |
109 | - display: inline-block | |
110 | - | |
111 | - img { | |
112 | - margin: 0 | |
113 | - vertical-align: bottom | |
114 | - border: none | |
115 | - } | |
116 | - } | |
117 | - | |
118 | - :after { | |
119 | - content: 'private'; | |
120 | - position: absolute; | |
121 | - background: #525050; | |
122 | - bottom: 0; | |
123 | - left: -1px; | |
124 | - font-size: 10px; | |
125 | - padding: 2px 4px 0 2px; | |
126 | - border-top-right-radius: 5px; | |
127 | - color: white; | |
128 | - font-weight: bold; | |
129 | - pointer-events: none; | |
130 | - white-space: nowrap | |
131 | - } | |
132 | - } | |
133 | - } | |
134 | - } | |
135 | - | |
136 | - section { | |
137 | - margin: 0 15px | |
138 | - (img) { | |
139 | - max-width: 100% | |
140 | - } | |
141 | - } | |
142 | - | |
143 | - footer { | |
144 | - background: #fdfdfd | |
145 | - padding: 15px 15px | |
146 | - text-align: right | |
147 | - | |
148 | - div.actions { | |
149 | - a { | |
150 | - margin-left: 5px; | |
151 | - color: #5f5f5f; | |
152 | - padding: 3px 6px; | |
153 | - background: white; | |
154 | - border: 2px solid #DDD; | |
155 | - border-radius: 4px; | |
156 | - | |
157 | - :hover { | |
158 | - background: #cbeeff; | |
159 | - color: #3b7ba2; | |
160 | - text-decoration: none; | |
161 | - border-color: #8eafc1; | |
162 | - } | |
163 | - } | |
164 | - } | |
165 | - } | |
166 | -} |
styles/notifier.mcss | ||
---|---|---|
@@ -1,24 +1,0 @@ | ||
1 | -Notifier { | |
2 | - padding: 10px; | |
3 | - display: block; | |
4 | - background: rgb(214, 228, 236); | |
5 | - border-bottom: 1px solid rgb(187, 201, 210); | |
6 | - text-align: center; | |
7 | - | |
8 | - :hover { | |
9 | - background: rgb(220, 242, 255); | |
10 | - } | |
11 | - | |
12 | - animation: 0.5s slide-in | |
13 | - position: relative | |
14 | - | |
15 | - -loader { | |
16 | - background: rgb(255, 239, 217); | |
17 | - border-bottom: 1px solid rgb(228, 220, 195); | |
18 | - color: #10100c !important; | |
19 | - | |
20 | - :hover { | |
21 | - background: rgb(245, 229, 207); | |
22 | - } | |
23 | - } | |
24 | -} |
styles/page-heading.mcss | ||
---|---|---|
@@ -1,43 +1,0 @@ | ||
1 | -PageHeading { | |
2 | - display: flex | |
3 | - h1 { | |
4 | - flex: 1 | |
5 | - } | |
6 | - div.meta { | |
7 | - a { | |
8 | - font-size: 80%; | |
9 | - background: rgb(112, 112, 112); | |
10 | - border: 2px solid #313131; | |
11 | - transition: opacity 0.2s; | |
12 | - opacity: 0.6; | |
13 | - padding: 6px 12px; | |
14 | - color: white; | |
15 | - border-radius: 4px; | |
16 | - font-weight: bold; | |
17 | - text-decoration: none; | |
18 | - display: block; | |
19 | - | |
20 | - -subscribe { | |
21 | - background-color: rgb(88, 171, 204); | |
22 | - border-color: #20699c; | |
23 | - } | |
24 | - | |
25 | - -unsubscribe { | |
26 | - background-repeat: no-repeat | |
27 | - background-position: right | |
28 | - background-image: svg(subscribed) | |
29 | - padding-right: 25px | |
30 | - } | |
31 | - | |
32 | - :hover { | |
33 | - opacity: 1 | |
34 | - } | |
35 | - } | |
36 | - } | |
37 | - | |
38 | - @svg subscribed { | |
39 | - width: 20px | |
40 | - height: 12px | |
41 | - content: "<circle cx='6' stroke='#FFF' fill='none' cy='6' r='5' /> <circle cx='6' cy='6' r='3' fill='#FFF'/>" | |
42 | - } | |
43 | -} |
styles/patchbay-tweaks.css | ||
---|---|---|
@@ -1,59 +1,0 @@ | ||
1 | -.scroller__wrapper { | |
2 | - width: 600px; | |
3 | - padding: 20px; | |
4 | -} | |
5 | - | |
6 | -.compose__controls > input[type=file] { | |
7 | - flex: 1; | |
8 | - padding: 5px; | |
9 | -} | |
10 | - | |
11 | -a.avatar { | |
12 | - display: inline; | |
13 | - font-weight: bold; | |
14 | - color: #222 | |
15 | -} | |
16 | - | |
17 | -div.avatar > a.avatar { | |
18 | - display: flex; | |
19 | - font-size: 120%; | |
20 | -} | |
21 | - | |
22 | -img.emoji { | |
23 | - width: 1.5em; | |
24 | - height: 1.5em; | |
25 | - align-content: center; | |
26 | - margin-top: -0.2em; | |
27 | -} | |
28 | - | |
29 | -div.compose textarea { | |
30 | - transition: height 0.1s | |
31 | -} | |
32 | - | |
33 | -div.lightbox { | |
34 | - box-shadow: #5f5f5f 1px 2px 100px; | |
35 | - bottom: inherit !important; | |
36 | - overflow: hidden !important; | |
37 | -} | |
38 | - | |
39 | -div.suggest-box { | |
40 | - background: #333; | |
41 | -} | |
42 | - | |
43 | -div.suggest-box ul { | |
44 | - left: 390px; | |
45 | - top: 87px; | |
46 | - position: fixed; | |
47 | - padding: 5px; | |
48 | - border-radius: 3px; | |
49 | - background: #fdfdfd; | |
50 | - border: 1px solid #CCC; | |
51 | -} | |
52 | - | |
53 | -div.suggest-box li { | |
54 | - padding: 3px; | |
55 | -} | |
56 | - | |
57 | -div.suggest-box strong { | |
58 | - font-weight: normal; | |
59 | -} |
styles/profile-list.mcss | ||
---|---|---|
@@ -1,51 +1,0 @@ | ||
1 | -ProfileList { | |
2 | - a.profile { | |
3 | - display: flex; | |
4 | - padding: 4px; | |
5 | - font-size: 110%; | |
6 | - margin: 4px 0; | |
7 | - background: rgba(255, 255, 255, 0.22); | |
8 | - border-radius: 5px; | |
9 | - position: relative | |
10 | - text-decoration: none | |
11 | - transition: background-color 0.2s | |
12 | - | |
13 | - background-repeat: no-repeat | |
14 | - background-position: right | |
15 | - | |
16 | - -connected { | |
17 | - background-image: svg(connected) | |
18 | - } | |
19 | - | |
20 | - @svg connected { | |
21 | - width: 20px | |
22 | - height: 12px | |
23 | - content: "<circle cx='6' stroke='none' fill='green' cy='6' r='5' />" | |
24 | - } | |
25 | - | |
26 | - :hover { | |
27 | - background-color: rgba(255, 255, 255, 0.4); | |
28 | - } | |
29 | - | |
30 | - div.avatar { | |
31 | - img { | |
32 | - width: 40px | |
33 | - height: 40px | |
34 | - } | |
35 | - } | |
36 | - | |
37 | - div.main { | |
38 | - display: flex | |
39 | - flex-direction: column | |
40 | - flex: 1 | |
41 | - margin-left: 10px | |
42 | - justify-content: center | |
43 | - div.name { | |
44 | - font-weight: bold | |
45 | - font-size: 110% | |
46 | - color: #333 | |
47 | - -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,1) 90%, rgba(0,0,0,0)) | |
48 | - } | |
49 | - } | |
50 | - } | |
51 | -} |
styles/split-view.mcss | ||
---|---|---|
@@ -1,28 +1,0 @@ | ||
1 | -SplitView { | |
2 | - display: flex | |
3 | - flex: 3 | |
4 | - div.main { | |
5 | - display: flex | |
6 | - flex-direction: column | |
7 | - flex: 1 | |
8 | - } | |
9 | - div.side { | |
10 | - min-width: 280px; | |
11 | - padding: 20px; | |
12 | - background: linear-gradient(100deg, #cee4ef, #efebeb); | |
13 | - border-right: 1px solid #dcdcdc; | |
14 | - overflow-y: auto; | |
15 | - | |
16 | - h2 { | |
17 | - margin-top: 20px | |
18 | - margin-bottom: 8px | |
19 | - color: #527b90; | |
20 | - text-shadow: 0px 0px 3px #fff; | |
21 | - font-weight: normal; | |
22 | - span.sub { | |
23 | - font-weight: normal | |
24 | - font-size: 90% | |
25 | - } | |
26 | - } | |
27 | - } | |
28 | -} |
old_modules/about.js | ||
---|---|---|
@@ -1,0 +1,37 @@ | ||
1 | +var h = require('hyperscript') | |
2 | + | |
3 | +function idLink (id) { | |
4 | + return h('a', {href:'#'+id}, id.slice(0, 10)) | |
5 | +} | |
6 | + | |
7 | +function asLink (ln) { | |
8 | + return 'string' === typeof ln ? ln : ln.link | |
9 | +} | |
10 | + | |
11 | +var plugs = require('patchbay/plugs') | |
12 | +var blob_url = plugs.first(exports.blob_url = []) | |
13 | +var avatar_name = plugs.first(exports.avatar_name = []) | |
14 | +var avatar_link = plugs.first(exports.avatar_link = []) | |
15 | + | |
16 | +exports.message_content = function (msg) { | |
17 | + if(msg.value.content.type !== 'about' || !msg.value.content.about) return | |
18 | + | |
19 | + if(!msg.value.content.image && !msg.value.content.name) | |
20 | + return | |
21 | + | |
22 | + var about = msg.value.content | |
23 | + var id = msg.value.content.about | |
24 | + return h('p', | |
25 | + about.about === msg.value.author | |
26 | + ? h('span', 'self-identifies ') | |
27 | + : h('span', 'identifies ', about.name ? idLink(id) : avatar_link(id, avatar_name(id))), | |
28 | + ' as ', | |
29 | + h('a', {href:"#"+about.about}, | |
30 | + about.name || null, | |
31 | + about.image | |
32 | + ? h('img.avatar--fullsize', {src: blob_url(about.image)}) | |
33 | + : null | |
34 | + ) | |
35 | + ) | |
36 | + | |
37 | +} |
old_modules/app.js | ||
---|---|---|
@@ -1,0 +1,198 @@ | ||
1 | +var Modules = require('./modules') | |
2 | +var h = require('./lib/h') | |
3 | +var Value = require('@mmckegg/mutant/value') | |
4 | +var when = require('@mmckegg/mutant/when') | |
5 | +var computed = require('@mmckegg/mutant/computed') | |
6 | +var toCollection = require('@mmckegg/mutant/dict-to-collection') | |
7 | +var MutantDict = require('@mmckegg/mutant/dict') | |
8 | +var MutantMap = require('@mmckegg/mutant/map') | |
9 | +var watch = require('@mmckegg/mutant/watch') | |
10 | + | |
11 | +var plugs = require('patchbay/plugs') | |
12 | + | |
13 | +module.exports = function (config, ssbClient) { | |
14 | + var modules = Modules(config, ssbClient) | |
15 | + | |
16 | + var screenView = plugs.first(modules.plugs.screen_view) | |
17 | + | |
18 | + var searchTimer = null | |
19 | + var searchBox = h('input.search', { | |
20 | + type: 'search', | |
21 | + placeholder: 'word, @key, #channel' | |
22 | + }) | |
23 | + | |
24 | + searchBox.oninput = function () { | |
25 | + clearTimeout(searchTimer) | |
26 | + searchTimer = setTimeout(doSearch, 500) | |
27 | + } | |
28 | + | |
29 | + searchBox.onfocus = function () { | |
30 | + if (searchBox.value) { | |
31 | + doSearch() | |
32 | + } | |
33 | + } | |
34 | + | |
35 | + var forwardHistory = [] | |
36 | + var backHistory = [] | |
37 | + | |
38 | + var views = MutantDict({ | |
39 | + // preload tabs (and subscribe to update notifications) | |
40 | + '/public': screenView('/public'), | |
41 | + '/private': screenView('/private'), | |
42 | + [ssbClient.id]: screenView(ssbClient.id), | |
43 | + '/notifications': screenView('/notifications') | |
44 | + }) | |
45 | + | |
46 | + var lastViewed = {} | |
47 | + | |
48 | + // delete cached view after 30 mins of last seeing | |
49 | + setInterval(() => { | |
50 | + views.keys().forEach((view) => { | |
51 | + if (lastViewed[view] !== true && Date.now() - lastViewed[view] > (30 * 60e3) && view !== currentView()) { | |
52 | + views.delete(view) | |
53 | + } | |
54 | + }) | |
55 | + }, 60e3) | |
56 | + | |
57 | + var canGoForward = Value(false) | |
58 | + var canGoBack = Value(false) | |
59 | + var currentView = Value('/public') | |
60 | + | |
61 | + watch(currentView, (view) => { | |
62 | + window.location.hash = `#${view}` | |
63 | + }) | |
64 | + | |
65 | + window.onhashchange = function (ev) { | |
66 | + var path = window.location.hash.substring(1) | |
67 | + if (path) { | |
68 | + setView(path) | |
69 | + } | |
70 | + } | |
71 | + | |
72 | + var mainElement = h('div.main', MutantMap(toCollection(views), (item) => { | |
73 | + return h('div.view', { | |
74 | + hidden: computed([item.key, currentView], (a, b) => a !== b) | |
75 | + }, [ item.value ]) | |
76 | + })) | |
77 | + | |
78 | + return h('MainWindow', { | |
79 | + classList: [ '-' + process.platform ] | |
80 | + }, [ | |
81 | + h('div.top', [ | |
82 | + h('span.history', [ | |
83 | + h('a', { | |
84 | + 'ev-click': goBack, | |
85 | + classList: [ when(canGoBack, '-active') ] | |
86 | + }, '<'), | |
87 | + h('a', { | |
88 | + 'ev-click': goForward, | |
89 | + classList: [ when(canGoForward, '-active') ] | |
90 | + }, '>') | |
91 | + ]), | |
92 | + h('span.nav', [ | |
93 | + tab('Public', '/public'), | |
94 | + tab('Private', '/private') | |
95 | + ]), | |
96 | + h('span.appTitle', ['Patchwork']), | |
97 | + h('span', [ searchBox ]), | |
98 | + h('span.nav', [ | |
99 | + tab('Profile', ssbClient.id), | |
100 | + tab('Mentions', '/notifications') | |
101 | + ]) | |
102 | + ]), | |
103 | + mainElement | |
104 | + ]) | |
105 | + | |
106 | + // scoped | |
107 | + | |
108 | + function tab (name, view) { | |
109 | + var instance = views.get(view) | |
110 | + lastViewed[view] = true | |
111 | + return h('a', { | |
112 | + 'ev-click': function (ev) { | |
113 | + if (instance.pendingUpdates && instance.pendingUpdates() && instance.reload) { | |
114 | + instance.reload() | |
115 | + } | |
116 | + }, | |
117 | + href: `#${view}`, | |
118 | + classList: [ | |
119 | + when(selected(view), '-selected') | |
120 | + ] | |
121 | + }, [ | |
122 | + name, | |
123 | + when(instance.pendingUpdates, [ | |
124 | + ' (', instance.pendingUpdates, ')' | |
125 | + ]) | |
126 | + ]) | |
127 | + } | |
128 | + | |
129 | + function goBack () { | |
130 | + if (backHistory.length) { | |
131 | + canGoForward.set(true) | |
132 | + forwardHistory.push(currentView()) | |
133 | + currentView.set(backHistory.pop()) | |
134 | + canGoBack.set(backHistory.length > 0) | |
135 | + } | |
136 | + } | |
137 | + | |
138 | + function goForward () { | |
139 | + if (forwardHistory.length) { | |
140 | + backHistory.push(currentView()) | |
141 | + currentView.set(forwardHistory.pop()) | |
142 | + canGoForward.set(forwardHistory.length > 0) | |
143 | + canGoBack.set(true) | |
144 | + } | |
145 | + } | |
146 | + | |
147 | + function setView (view) { | |
148 | + if (!views.has(view)) { | |
149 | + views.put(view, screenView(view)) | |
150 | + } | |
151 | + | |
152 | + if (lastViewed[view] !== true) { | |
153 | + lastViewed[view] = Date.now() | |
154 | + } | |
155 | + | |
156 | + if (currentView() && lastViewed[currentView()] !== true) { | |
157 | + lastViewed[currentView()] = Date.now() | |
158 | + } | |
159 | + | |
160 | + if (view !== currentView()) { | |
161 | + canGoForward.set(false) | |
162 | + canGoBack.set(true) | |
163 | + forwardHistory.length = 0 | |
164 | + backHistory.push(currentView()) | |
165 | + currentView.set(view) | |
166 | + } | |
167 | + } | |
168 | + | |
169 | + function doSearch () { | |
170 | + var value = searchBox.value.trim() | |
171 | + if (value.startsWith('/') || value.startsWith('?') || value.startsWith('@') || value.startsWith('#') || value.startsWith('%')) { | |
172 | + setView(value) | |
173 | + } else if (value.trim()) { | |
174 | + setView(`?${value.trim()}`) | |
175 | + } else { | |
176 | + setView('/public') | |
177 | + } | |
178 | + } | |
179 | + | |
180 | + function selected (view) { | |
181 | + return computed([currentView, view], (currentView, view) => { | |
182 | + return currentView === view | |
183 | + }) | |
184 | + } | |
185 | +} | |
186 | + | |
187 | +function isSame (a, b) { | |
188 | + if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { | |
189 | + for (var i = 0; i < a.length; i++) { | |
190 | + if (a[i] !== b[i]) { | |
191 | + return false | |
192 | + } | |
193 | + } | |
194 | + return true | |
195 | + } else if (a === b) { | |
196 | + return true | |
197 | + } | |
198 | +} |
old_modules/channel.js | ||
---|---|---|
@@ -1,0 +1,78 @@ | ||
1 | +var when = require('@mmckegg/mutant/when') | |
2 | +var send = require('@mmckegg/mutant/send') | |
3 | +var plugs = require('patchbay/plugs') | |
4 | +var message_compose = plugs.first(exports.message_compose = []) | |
5 | +var sbot_log = plugs.first(exports.sbot_log = []) | |
6 | +var feed_summary = plugs.first(exports.feed_summary = []) | |
7 | +var h = require('../lib/h') | |
8 | +var pull = require('pull-stream') | |
9 | +var obs_subscribed_channels = plugs.first(exports.obs_subscribed_channels = []) | |
10 | +var get_id = plugs.first(exports.get_id = []) | |
11 | +var publish = plugs.first(exports.sbot_publish = []) | |
12 | + | |
13 | +exports.screen_view = function (path, sbot) { | |
14 | + if (path[0] === '#') { | |
15 | + var channel = path.substr(1) | |
16 | + var subscribedChannels = obs_subscribed_channels(get_id()) | |
17 | + | |
18 | + return feed_summary((opts) => { | |
19 | + return pull( | |
20 | + sbot_log(opts), | |
21 | + pull.map(matchesChannel) | |
22 | + ) | |
23 | + }, [ | |
24 | + h('PageHeading', [ | |
25 | + h('h1', `#${channel}`), | |
26 | + h('div.meta', [ | |
27 | + when(subscribedChannels.has(channel), | |
28 | + h('a -unsubscribe', { | |
29 | + 'href': '#', | |
30 | + 'title': 'Click to unsubscribe', | |
31 | + 'ev-click': send(unsubscribe, channel) | |
32 | + }, 'Subscribed'), | |
33 | + h('a -subscribe', { | |
34 | + 'href': '#', | |
35 | + 'ev-click': send(subscribe, channel) | |
36 | + }, 'Subscribe') | |
37 | + ) | |
38 | + ]) | |
39 | + ]), | |
40 | + message_compose({type: 'post', channel: channel}, {placeholder: 'Write a message in this channel\n\n\n\nPeople who follow you or subscribe to this channel will also see this message in their main feed.\n\nTo create a new channel, type the channel name (preceded by a #) into the search box above. e.g #cat-pics'}) | |
41 | + ]) | |
42 | + } | |
43 | + | |
44 | + // scoped | |
45 | + | |
46 | + function matchesChannel (msg) { | |
47 | + if (msg.sync) console.error('SYNC', msg) | |
48 | + var c = msg && msg.value && msg.value.content | |
49 | + if (c && c.channel === channel) { | |
50 | + return msg | |
51 | + } else { | |
52 | + return {timestamp: msg.timestamp} | |
53 | + } | |
54 | + } | |
55 | +} | |
56 | + | |
57 | +exports.message_meta = function (msg) { | |
58 | + var chan = msg.value.content.channel | |
59 | + if (chan) { | |
60 | + return h('a.channel', {href: '##' + chan}, '#' + chan) | |
61 | + } | |
62 | +} | |
63 | + | |
64 | +function subscribe (id) { | |
65 | + publish({ | |
66 | + type: 'channel', | |
67 | + channel: id, | |
68 | + subscribed: true | |
69 | + }) | |
70 | +} | |
71 | + | |
72 | +function unsubscribe (id) { | |
73 | + publish({ | |
74 | + type: 'channel', | |
75 | + channel: id, | |
76 | + subscribed: false | |
77 | + }) | |
78 | +} |
old_modules/data-feed.js | ||
---|---|---|
@@ -1,0 +1,32 @@ | ||
1 | +var h = require('hyperscript') | |
2 | +var u = require('patchbay/util') | |
3 | +var pull = require('pull-stream') | |
4 | +var Scroller = require('pull-scroll') | |
5 | + | |
6 | +var plugs = require('patchbay/plugs') | |
7 | +var sbot_log = plugs.first(exports.sbot_log = []) | |
8 | +var data_render = plugs.first(exports.data_render = []) | |
9 | + | |
10 | +exports.screen_view = function (path, sbot) { | |
11 | + if(path === '/data-feed' || path === '/data') { | |
12 | + var content = h('div.column.scroller__content') | |
13 | + var div = h('div.column.scroller', | |
14 | + {style: {'overflow':'auto'}}, | |
15 | + h('div.scroller__wrapper', | |
16 | + content | |
17 | + ) | |
18 | + ) | |
19 | + | |
20 | + pull( | |
21 | + u.next(sbot_log, {old: false, limit: 100}), | |
22 | + Scroller(div, content, data_render, true, false) | |
23 | + ) | |
24 | + | |
25 | + pull( | |
26 | + u.next(sbot_log, {reverse: true, limit: 100, live: false}), | |
27 | + Scroller(div, content, data_render, false, false) | |
28 | + ) | |
29 | + | |
30 | + return div | |
31 | + } | |
32 | +} |
old_modules/feed-summary.js | ||
---|---|---|
@@ -1,0 +1,215 @@ | ||
1 | +var Value = require('@mmckegg/mutant/value') | |
2 | +var h = require('@mmckegg/mutant/html-element') | |
3 | +var when = require('@mmckegg/mutant/when') | |
4 | +var computed = require('@mmckegg/mutant/computed') | |
5 | +var MutantArray = require('@mmckegg/mutant/array') | |
6 | +var Abortable = require('pull-abortable') | |
7 | +var Scroller = require('../lib/pull-scroll') | |
8 | +var FeedSummary = require('../lib/feed-summary') | |
9 | +var onceTrue = require('../lib/once-true') | |
10 | + | |
11 | +var m = require('../lib/h') | |
12 | + | |
13 | +var pull = require('pull-stream') | |
14 | + | |
15 | +var plugs = require('patchbay/plugs') | |
16 | +var message_render = plugs.first(exports.message_render = []) | |
17 | +var message_link = plugs.first(exports.message_link = []) | |
18 | +var person = plugs.first(exports.person = []) | |
19 | +var many_people = plugs.first(exports.many_people = []) | |
20 | +var people_names = plugs.first(exports.people_names = []) | |
21 | +var sbot_get = plugs.first(exports.sbot_get = []) | |
22 | +var get_id = plugs.first(exports.get_id = []) | |
23 | + | |
24 | +exports.feed_summary = function (getStream, prefix, opts) { | |
25 | + var sync = Value(false) | |
26 | + var updates = Value(0) | |
27 | + | |
28 | + var filter = opts && opts.filter | |
29 | + var bumpFilter = opts && opts.bumpFilter | |
30 | + var windowSize = opts && opts.windowSize | |
31 | + var waitFor = opts && opts.waitFor || true | |
32 | + | |
33 | + var updateLoader = m('a Notifier -loader', { | |
34 | + href: '#', | |
35 | + 'ev-click': refresh | |
36 | + }, [ | |
37 | + 'Show ', | |
38 | + h('strong', [updates]), ' ', | |
39 | + when(computed(updates, a => a === 1), 'update', 'updates') | |
40 | + ]) | |
41 | + | |
42 | + var content = h('div.column.scroller__content') | |
43 | + | |
44 | + var scrollElement = h('div.column.scroller', { | |
45 | + style: { | |
46 | + 'overflow': 'auto' | |
47 | + } | |
48 | + }, [ | |
49 | + h('div.scroller__wrapper', [ | |
50 | + prefix, content | |
51 | + ]) | |
52 | + ]) | |
53 | + | |
54 | + setTimeout(refresh, 10) | |
55 | + | |
56 | + onceTrue(waitFor, () => { | |
57 | + pull( | |
58 | + getStream({old: false}), | |
59 | + pull.drain((item) => { | |
60 | + var type = item && item.value && item.value.content.type | |
61 | + if (type && type !== 'vote') { | |
62 | + if (item.value && item.value.author === get_id() && !updates()) { | |
63 | + return refresh() | |
64 | + } | |
65 | + if (filter) { | |
66 | + var update = (item.value.content.type === 'post' && item.value.content.root) ? { | |
67 | + type: 'message', | |
68 | + messageId: item.value.content.root, | |
69 | + channel: item.value.content.channel | |
70 | + } : { | |
71 | + type: 'message', | |
72 | + author: item.value.author, | |
73 | + channel: item.value.content.channel, | |
74 | + messageId: item.key | |
75 | + } | |
76 | + | |
77 | + ensureAuthor(update, (err, update) => { | |
78 | + if (!err) { | |
79 | + if (filter(update)) { | |
80 | + updates.set(updates() + 1) | |
81 | + } | |
82 | + } | |
83 | + }) | |
84 | + } else { | |
85 | + updates.set(updates() + 1) | |
86 | + } | |
87 | + } | |
88 | + }) | |
89 | + ) | |
90 | + }) | |
91 | + | |
92 | + var abortLastFeed = null | |
93 | + | |
94 | + var result = MutantArray([ | |
95 | + when(updates, updateLoader), | |
96 | + when(sync, scrollElement, m('Loading -large')) | |
97 | + ]) | |
98 | + | |
99 | + result.reload = refresh | |
100 | + result.pendingUpdates = updates | |
101 | + | |
102 | + return result | |
103 | + | |
104 | + // scoped | |
105 | + | |
106 | + function refresh () { | |
107 | + if (abortLastFeed) { | |
108 | + abortLastFeed() | |
109 | + } | |
110 | + updates.set(0) | |
111 | + sync.set(false) | |
112 | + content.innerHTML = '' | |
113 | + | |
114 | + var abortable = Abortable() | |
115 | + abortLastFeed = abortable.abort | |
116 | + | |
117 | + pull( | |
118 | + FeedSummary(getStream, {windowSize, bumpFilter}, () => { | |
119 | + sync.set(true) | |
120 | + }), | |
121 | + pull.asyncMap(ensureAuthor), | |
122 | + pull.filter((item) => { | |
123 | + if (filter) { | |
124 | + return filter(item) | |
125 | + } else { | |
126 | + return true | |
127 | + } | |
128 | + }), | |
129 | + abortable, | |
130 | + Scroller(scrollElement, content, renderItem, false, false) | |
131 | + ) | |
132 | + } | |
133 | +} | |
134 | + | |
135 | +function ensureAuthor (item, cb) { | |
136 | + if (item.type === 'message' && !item.message) { | |
137 | + sbot_get(item.messageId, (_, value) => { | |
138 | + if (value) { | |
139 | + item.author = value.author | |
140 | + } | |
141 | + cb(null, item) | |
142 | + }) | |
143 | + } else { | |
144 | + cb(null, item) | |
145 | + } | |
146 | +} | |
147 | + | |
148 | +function renderItem (item) { | |
149 | + if (item.type === 'message') { | |
150 | + var meta = null | |
151 | + var previousId = item.messageId | |
152 | + var replies = item.replies.slice(-4).map((msg) => { | |
153 | + var result = message_render(msg, {inContext: true, inSummary: true, previousId}) | |
154 | + previousId = msg.key | |
155 | + return result | |
156 | + }) | |
157 | + var renderedMessage = item.message ? message_render(item.message, {inContext: true}) : null | |
158 | + if (renderedMessage) { | |
159 | + if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
160 | + meta = m('div.meta', { | |
161 | + title: people_names(item.repliesFrom) | |
162 | + }, [ | |
163 | + many_people(item.repliesFrom), ' replied' | |
164 | + ]) | |
165 | + } else if (item.lastUpdateType === 'dig' && item.digs.size) { | |
166 | + meta = m('div.meta', { | |
167 | + title: people_names(item.digs) | |
168 | + }, [ | |
169 | + many_people(item.digs), ' dug this message' | |
170 | + ]) | |
171 | + } | |
172 | + | |
173 | + return m('FeedEvent', [ | |
174 | + meta, | |
175 | + renderedMessage, | |
176 | + when(replies.length, [ | |
177 | + when(item.replies.length > replies.length, | |
178 | + m('a.full', {href: `#${item.messageId}`}, ['View full thread']) | |
179 | + ), | |
180 | + m('div.replies', replies) | |
181 | + ]) | |
182 | + ]) | |
183 | + } else { | |
184 | + if (item.lastUpdateType === 'reply' && item.repliesFrom.size) { | |
185 | + meta = m('div.meta', { | |
186 | + title: people_names(item.repliesFrom) | |
187 | + }, [ | |
188 | + many_people(item.repliesFrom), ' replied to ', message_link(item.messageId) | |
189 | + ]) | |
190 | + } else if (item.lastUpdateType === 'dig' && item.digs.size) { | |
191 | + meta = m('div.meta', { | |
192 | + title: people_names(item.digs) | |
193 | + }, [ | |
194 | + many_people(item.digs), ' dug ', message_link(item.messageId) | |
195 | + ]) | |
196 | + } | |
197 | + | |
198 | + if (meta || replies.length) { | |
199 | + return m('FeedEvent', [ | |
200 | + meta, m('div.replies', replies) | |
201 | + ]) | |
202 | + } | |
203 | + } | |
204 | + } else if (item.type === 'follow') { | |
205 | + return m('FeedEvent -follow', [ | |
206 | + m('div.meta', { | |
207 | + title: people_names(item.contacts) | |
208 | + }, [ | |
209 | + person(item.id), ' followed ', many_people(item.contacts) | |
210 | + ]) | |
211 | + ]) | |
212 | + } | |
213 | + | |
214 | + return h('div') | |
215 | +} |
old_modules/feed.js | ||
---|---|---|
@@ -1,0 +1,20 @@ | ||
1 | +var ref = require('ssb-ref') | |
2 | +var h = require('hyperscript') | |
3 | +var extend = require('xtend') | |
4 | + | |
5 | +var plugs = require('patchbay/plugs') | |
6 | +var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
7 | +var avatar_profile = plugs.first(exports.avatar_profile = []) | |
8 | +var feed_summary = plugs.first(exports.feed_summary = []) | |
9 | + | |
10 | +exports.screen_view = function (id) { | |
11 | + if (ref.isFeed(id)) { | |
12 | + return feed_summary((opts) => { | |
13 | + return sbot_user_feed(extend(opts, {id: id})) | |
14 | + }, [ | |
15 | + h('div', [avatar_profile(id)]) | |
16 | + ], { | |
17 | + windowSize: 50 | |
18 | + }) | |
19 | + } | |
20 | +} |
old_modules/git-mini-messages.js | ||
---|---|---|
@@ -1,0 +1,26 @@ | ||
1 | +var h = require('../lib/h') | |
2 | +var when = require('@mmckegg/mutant/when') | |
3 | +var plugs = require('patchbay/plugs') | |
4 | +var message_link = plugs.first(exports.message_link = []) | |
5 | + | |
6 | +exports.message_content = exports.message_content_mini = function (msg, sbot) { | |
7 | + if (msg.value.content.type === 'git-update') { | |
8 | + var commits = msg.value.content.commits || [] | |
9 | + return [ | |
10 | + h('a', {href: `#${msg.key}`, title: commitSummary(commits)}, [ | |
11 | + 'pushed', | |
12 | + when(commits, [' ', pluralizeCommits(commits)]) | |
13 | + ]), | |
14 | + ' to ', | |
15 | + message_link(msg.value.content.repo) | |
16 | + ] | |
17 | + } | |
18 | +} | |
19 | + | |
20 | +function pluralizeCommits (commits) { | |
21 | + return when(commits.length === 1, '1 commit', `${commits.length} commits`) | |
22 | +} | |
23 | + | |
24 | +function commitSummary (commits) { | |
25 | + return commits.map(commit => `- ${commit.title}`).join('\n') | |
26 | +} |
old_modules/index.js | ||
---|---|---|
@@ -1,0 +1,31 @@ | ||
1 | +var SbotApi = require('../api') | |
2 | +var extend = require('xtend') | |
3 | +var combine = require('depject') | |
4 | +var fs = require('fs') | |
5 | +var patchbayModules = require('patchbay/modules') | |
6 | + | |
7 | +module.exports = function (config, ssbClient, overrides) { | |
8 | + var api = SbotApi(ssbClient, config) | |
9 | + var localModules = getLocalModules() | |
10 | + return combine(extend(patchbayModules, localModules, { | |
11 | + 'sbot-api.js': api, | |
12 | + 'blob-url.js': { | |
13 | + blob_url: function (link) { | |
14 | + var prefix = config.blobsPrefix != null ? config.blobsPrefix : `http://localhost:${config.blobsPort}` | |
15 | + if (typeof link.link === 'string') { | |
16 | + link = link.link | |
17 | + } | |
18 | + return `${prefix}/${encodeURIComponent(link)}` | |
19 | + } | |
20 | + } | |
21 | + }, overrides)) | |
22 | +} | |
23 | + | |
24 | +function getLocalModules () { | |
25 | + return fs.readdirSync(__dirname).reduce(function (result, e) { | |
26 | + if (e !== 'index.js' && /\js$/.test(e)) { | |
27 | + result[e] = require('./' + e) | |
28 | + } | |
29 | + return result | |
30 | + }, {}) | |
31 | +} |
old_modules/like.js | ||
---|---|---|
@@ -1,0 +1,72 @@ | ||
1 | +var h = require('../lib/h') | |
2 | +var computed = require('@mmckegg/mutant/computed') | |
3 | +var when = require('@mmckegg/mutant/when') | |
4 | +var plugs = require('patchbay/plugs') | |
5 | +var message_link = plugs.first(exports.message_link = []) | |
6 | +var get_id = plugs.first(exports.get_id = []) | |
7 | +var get_likes = plugs.first(exports.get_likes = []) | |
8 | +var publish = plugs.first(exports.sbot_publish = []) | |
9 | +var people_names = plugs.first(exports.people_names = []) | |
10 | + | |
11 | +exports.message_content = exports.message_content_mini = function (msg, sbot) { | |
12 | + if (msg.value.content.type !== 'vote') return | |
13 | + var link = msg.value.content.vote.link | |
14 | + return [ | |
15 | + msg.value.content.vote.value > 0 ? 'dug' : 'undug', | |
16 | + ' ', message_link(link) | |
17 | + ] | |
18 | +} | |
19 | + | |
20 | +exports.message_meta = function (msg, sbot) { | |
21 | + return computed(get_likes(msg.key), likeCount) | |
22 | +} | |
23 | + | |
24 | +exports.message_action = function (msg, sbot) { | |
25 | + var id = get_id() | |
26 | + var dug = computed([get_likes(msg.key), id], doesLike) | |
27 | + dug(() => {}) | |
28 | + | |
29 | + if (msg.value.content.type !== 'vote') { | |
30 | + return h('a.dig', { | |
31 | + href: '#', | |
32 | + 'ev-click': function () { | |
33 | + var dig = dug() ? { | |
34 | + type: 'vote', | |
35 | + vote: { link: msg.key, value: 0, expression: 'Undig' } | |
36 | + } : { | |
37 | + type: 'vote', | |
38 | + vote: { link: msg.key, value: 1, expression: 'Dig' } | |
39 | + } | |
40 | + if (msg.value.content.recps) { | |
41 | + dig.recps = msg.value.content.recps.map(function (e) { | |
42 | + return e && typeof e !== 'string' ? e.link : e | |
43 | + }) | |
44 | + dig.private = true | |
45 | + } | |
46 | + publish(dig) | |
47 | + } | |
48 | + }, when(dug, 'Undig', 'Dig')) | |
49 | + } | |
50 | +} | |
51 | + | |
52 | +function doesLike (likes, userId) { | |
53 | + return likes && likes[userId] && likes[userId][0] || false | |
54 | +} | |
55 | + | |
56 | +function likeCount (data) { | |
57 | + var likes = getLikes(data) | |
58 | + if (likes.length) { | |
59 | + return [' ', h('span.likes', { | |
60 | + title: people_names(likes) | |
61 | + }, ['+', h('strong', `${likes.length}`)])] | |
62 | + } | |
63 | +} | |
64 | + | |
65 | +function getLikes (likes) { | |
66 | + return Object.keys(likes).reduce((result, id) => { | |
67 | + if (likes[id][0]) { | |
68 | + result.push(id) | |
69 | + } | |
70 | + return result | |
71 | + }, []) | |
72 | +} |
old_modules/many-people.js | ||
---|---|---|
@@ -1,0 +1,31 @@ | ||
1 | +var plugs = require('patchbay/plugs') | |
2 | +var person = plugs.first(exports.person = []) | |
3 | +exports.many_people = manyPeople | |
4 | + | |
5 | +function manyPeople (ids) { | |
6 | + ids = Array.from(ids) | |
7 | + var featuredIds = ids.slice(-3).reverse() | |
8 | + | |
9 | + if (ids.length) { | |
10 | + if (ids.length > 3) { | |
11 | + return [ | |
12 | + person(featuredIds[0]), ', ', | |
13 | + person(featuredIds[1]), | |
14 | + ' and ', ids.length - 2, ' others' | |
15 | + ] | |
16 | + } else if (ids.length === 3) { | |
17 | + return [ | |
18 | + person(featuredIds[0]), ', ', | |
19 | + person(featuredIds[1]), ' and ', | |
20 | + person(featuredIds[2]) | |
21 | + ] | |
22 | + } else if (ids.length === 2) { | |
23 | + return [ | |
24 | + person(featuredIds[0]), ' and ', | |
25 | + person(featuredIds[1]) | |
26 | + ] | |
27 | + } else { | |
28 | + return person(featuredIds[0]) | |
29 | + } | |
30 | + } | |
31 | +} |
old_modules/message-confirm.js | ||
---|---|---|
@@ -1,0 +1,51 @@ | ||
1 | +var lightbox = require('hyperlightbox') | |
2 | +var h = require('../lib/h') | |
3 | +var plugs = require('patchbay/plugs') | |
4 | +var get_id = plugs.first(exports.get_id = []) | |
5 | +var publish = plugs.first(exports.sbot_publish = []) | |
6 | +var message_render = plugs.first(exports.message_render = []) | |
7 | + | |
8 | +exports.message_confirm = function (content, cb) { | |
9 | + cb = cb || function () {} | |
10 | + | |
11 | + var lb = lightbox() | |
12 | + document.body.appendChild(lb) | |
13 | + | |
14 | + var msg = { | |
15 | + value: { | |
16 | + author: get_id(), | |
17 | + previous: null, | |
18 | + sequence: null, | |
19 | + timestamp: Date.now(), | |
20 | + content: content | |
21 | + } | |
22 | + } | |
23 | + | |
24 | + var okay = h('button', { | |
25 | + 'ev-click': function () { | |
26 | + lb.remove() | |
27 | + publish(content, cb) | |
28 | + }, | |
29 | + 'ev-keydown': function (ev) { | |
30 | + if (ev.keyCode === 27) cancel.click() // escape | |
31 | + } | |
32 | + }, [ | |
33 | + 'okay' | |
34 | + ]) | |
35 | + | |
36 | + var cancel = h('button', {'ev-click': function () { | |
37 | + lb.remove() | |
38 | + cb(null) | |
39 | + }}, [ | |
40 | + 'Cancel' | |
41 | + ]) | |
42 | + | |
43 | + lb.show(h('MessageConfirm', [ | |
44 | + h('section', [ | |
45 | + message_render(msg) | |
46 | + ]), | |
47 | + h('footer', [okay, cancel]) | |
48 | + ])) | |
49 | + | |
50 | + okay.focus() | |
51 | +} |
old_modules/message-name.js | ||
---|---|---|
@@ -1,0 +1,44 @@ | ||
1 | +var plugs = require('patchbay/plugs') | |
2 | +var sbot_links = plugs.first(exports.sbot_links = []) | |
3 | +var get_id = plugs.first(exports.get_id = []) | |
4 | +var sbot_get = plugs.first(exports.sbot_get = []) | |
5 | +var getAvatar = require('ssb-avatar') | |
6 | + | |
7 | +exports.message_name = function (id, cb) { | |
8 | + sbot_get(id, function (err, value) { | |
9 | + if (err && err.name === 'NotFoundError') { | |
10 | + return cb(null, id.substring(0, 10) + '...(missing)') | |
11 | + } else if (value.content.type === 'post' && typeof value.content.text === 'string') { | |
12 | + if (value.content.text.trim()) { | |
13 | + return cb(null, titleFromMarkdown(value.content.text, 40)) | |
14 | + } | |
15 | + } else if (value.content.type === 'git-repo') { | |
16 | + return getRepoName(id, cb) | |
17 | + } else if (typeof value.content.text === 'string') { | |
18 | + return cb(null, value.content.type + ': ' + titleFromMarkdown(value.content.text, 30)) | |
19 | + } | |
20 | + | |
21 | + return cb(null, id.substring(0, 10) + '...') | |
22 | + }) | |
23 | +} | |
24 | + | |
25 | +function titleFromMarkdown (text, max) { | |
26 | + text = text.trim().split('\n', 2).join('\n') | |
27 | + text = text.replace(/_|`|\*|\#|\[.*?\]|\(\S*?\)/g, '').trim() | |
28 | + text = text.replace(/\:$/, '') | |
29 | + text = text.trim().split('\n', 1)[0].trim() | |
30 | + if (text.length > max) { | |
31 | + text = text.substring(0, max - 2) + '...' | |
32 | + } | |
33 | + return text | |
34 | +} | |
35 | + | |
36 | +function getRepoName (id, cb) { | |
37 | + getAvatar({ | |
38 | + links: sbot_links, | |
39 | + get: sbot_get | |
40 | + }, get_id(), id, function (err, avatar) { | |
41 | + if (err) return cb(err) | |
42 | + cb(null, avatar.name) | |
43 | + }) | |
44 | +} |
old_modules/message.js | ||
---|---|---|
@@ -1,0 +1,119 @@ | ||
1 | +var h = require('../lib/h') | |
2 | +var when = require('@mmckegg/mutant/when') | |
3 | + | |
4 | +var plugs = require('patchbay/plugs') | |
5 | +var message_content = plugs.first(exports.message_content = []) | |
6 | +var message_content_mini = plugs.first(exports.message_content_mini = []) | |
7 | +var message_link = plugs.first(exports.message_link = []) | |
8 | +var avatar_image = plugs.first(exports.avatar_image = []) | |
9 | +var avatar_name = plugs.first(exports.avatar_name = []) | |
10 | +var avatar_link = plugs.first(exports.avatar_link = []) | |
11 | +var message_meta = plugs.map(exports.message_meta = []) | |
12 | +var message_main_meta = plugs.map(exports.message_main_meta = []) | |
13 | +var message_action = plugs.map(exports.message_action = []) | |
14 | +var contextMenu = require('../lib/context-menu') | |
15 | + | |
16 | +exports.data_render = function (msg) { | |
17 | + var div = h('Message -data', { | |
18 | + 'ev-contextmenu': contextMenu.bind(null, msg) | |
19 | + }, [ | |
20 | + messageHeader(msg), | |
21 | + h('section', [ | |
22 | + h('pre', [ | |
23 | + JSON.stringify(msg, null, 2) | |
24 | + ]) | |
25 | + ]) | |
26 | + ]) | |
27 | + return div | |
28 | +} | |
29 | + | |
30 | +exports.message_render = function (msg, opts) { | |
31 | + opts = opts || {} | |
32 | + var inContext = opts.inContext | |
33 | + var previousId = opts.previousId | |
34 | + var inSummary = opts.inSummary | |
35 | + | |
36 | + var elMini = message_content_mini(msg) | |
37 | + var el = message_content(msg) | |
38 | + | |
39 | + if (elMini && (!el || inSummary)) { | |
40 | + var div = h('Message', { | |
41 | + 'ev-contextmenu': contextMenu.bind(null, msg) | |
42 | + }, [ | |
43 | + h('header', [ | |
44 | + h('div.mini', [ | |
45 | + avatar_link(msg.value.author, avatar_name(msg.value.author), ''), | |
46 | + ' ', elMini | |
47 | + ]), | |
48 | + h('div.meta', [message_main_meta(msg)]) | |
49 | + ]) | |
50 | + ]) | |
51 | + div.setAttribute('tabindex', '0') | |
52 | + return div | |
53 | + } | |
54 | + | |
55 | + if (!el) return | |
56 | + | |
57 | + var classList = [] | |
58 | + var replyInfo = null | |
59 | + | |
60 | + if (msg.value.content.root) { | |
61 | + classList.push('-reply') | |
62 | + if (!inContext) { | |
63 | + replyInfo = h('span', ['in reply to ', message_link(msg.value.content.root)]) | |
64 | + } else if (previousId && last(msg.value.content.branch) && previousId !== last(msg.value.content.branch)) { | |
65 | + replyInfo = h('span', ['in reply to ', message_link(last(msg.value.content.branch))]) | |
66 | + } | |
67 | + } | |
68 | + | |
69 | + var element = h('Message', { | |
70 | + classList, | |
71 | + 'ev-contextmenu': contextMenu.bind(null, msg), | |
72 | + 'ev-keydown': function (ev) { | |
73 | + // on enter, hit first meta. | |
74 | + if (ev.keyCode === 13) { | |
75 | + element.querySelector('.enter').click() | |
76 | + } | |
77 | + } | |
78 | + }, [ | |
79 | + messageHeader(msg, replyInfo), | |
80 | + h('section', [el]), | |
81 | + when(msg.key, h('footer', [ | |
82 | + h('div.actions', [ | |
83 | + message_action(msg), | |
84 | + h('a', {href: '#' + msg.key}, 'Reply') | |
85 | + ]) | |
86 | + ])) | |
87 | + ]) | |
88 | + | |
89 | + // ); hyperscript does not seem to set attributes correctly. | |
90 | + element.setAttribute('tabindex', '0') | |
91 | + | |
92 | + return element | |
93 | +} | |
94 | + | |
95 | +function messageHeader (msg, replyInfo) { | |
96 | + return h('header', [ | |
97 | + h('div.main', [ | |
98 | + h('a.avatar', {href: `#${msg.value.author}`}, avatar_image(msg.value.author)), | |
99 | + h('div.main', [ | |
100 | + h('div.name', [ | |
101 | + h('a', {href: `#${msg.value.author}`}, avatar_name(msg.value.author)) | |
102 | + ]), | |
103 | + h('div.meta', [ | |
104 | + message_main_meta(msg), | |
105 | + ' ', replyInfo | |
106 | + ]) | |
107 | + ]) | |
108 | + ]), | |
109 | + h('div.meta', message_meta(msg)) | |
110 | + ]) | |
111 | +} | |
112 | + | |
113 | +function last (array) { | |
114 | + if (Array.isArray(array)) { | |
115 | + return array[array.length - 1] | |
116 | + } else { | |
117 | + return array | |
118 | + } | |
119 | +} |
old_modules/notifications.js | ||
---|---|---|
@@ -1,0 +1,148 @@ | ||
1 | +var pull = require('pull-stream') | |
2 | +var paramap = require('pull-paramap') | |
3 | +var plugs = require('patchbay/plugs') | |
4 | +var cont = require('cont') | |
5 | +var ref = require('ssb-ref') | |
6 | + | |
7 | +var sbot_log = plugs.first(exports.sbot_log = []) | |
8 | +var sbot_get = plugs.first(exports.sbot_get = []) | |
9 | +var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
10 | +var message_unbox = plugs.first(exports.message_unbox = []) | |
11 | +var get_id = plugs.first(exports.get_id = []) | |
12 | +var feed_summary = plugs.first(exports.feed_summary = []) | |
13 | + | |
14 | +exports.screen_view = function (path) { | |
15 | + if (path === '/notifications') { | |
16 | + var oldest = null | |
17 | + var id = get_id() | |
18 | + var ids = { | |
19 | + [id]: true | |
20 | + } | |
21 | + | |
22 | + getFirstMessage(id, function (err, msg) { | |
23 | + if (err) return console.error(err) | |
24 | + if (!oldest || msg.value.timestamp < oldest) { | |
25 | + oldest = msg.value.timestamp | |
26 | + } | |
27 | + }) | |
28 | + | |
29 | + return feed_summary((opts) => { | |
30 | + if (opts.old === false) { | |
31 | + return pull( | |
32 | + sbot_log(opts), | |
33 | + unbox(), | |
34 | + notifications(ids), | |
35 | + pull.filter() | |
36 | + ) | |
37 | + } else { | |
38 | + return pull( | |
39 | + sbot_log(opts), | |
40 | + unbox(), | |
41 | + notifications(ids), | |
42 | + pull.filter(), | |
43 | + pull.take(function (msg) { | |
44 | + // abort stream after we pass the oldest messages of our feeds | |
45 | + return !oldest || msg.value.timestamp > oldest | |
46 | + }) | |
47 | + ) | |
48 | + } | |
49 | + }, [], { | |
50 | + windowSize: 200, | |
51 | + filter: (group) => { | |
52 | + return ( | |
53 | + ((group.message || group.type !== 'message') && (group.author !== id || group.digs && group.digs.size)) || ( | |
54 | + group.repliesFrom && group.repliesFrom.size && ( | |
55 | + !group.repliesFrom.has(id) || group.repliesFrom.size > 1 | |
56 | + ) | |
57 | + ) | |
58 | + ) | |
59 | + } | |
60 | + }) | |
61 | + } | |
62 | +} | |
63 | + | |
64 | +function unbox () { | |
65 | + return pull( | |
66 | + pull.map(function (msg) { | |
67 | + return msg.value && typeof msg.value.content === 'string' | |
68 | + ? message_unbox(msg) | |
69 | + : msg | |
70 | + }), | |
71 | + pull.filter(Boolean) | |
72 | + ) | |
73 | +} | |
74 | + | |
75 | +function notifications (ourIds) { | |
76 | + function linksToUs (link) { | |
77 | + return link && link.link in ourIds | |
78 | + } | |
79 | + | |
80 | + function isOurMsg (id, cb) { | |
81 | + if (!id) return cb(null, false) | |
82 | + if (typeof id === 'object' && typeof id.link === 'string') id = id.link | |
83 | + if (!ref.isMsg(id)) return cb(null, false) | |
84 | + sbot_get(id, function (err, msg) { | |
85 | + if (err && err.name === 'NotFoundError') cb(null, false) | |
86 | + else if (err) cb(err) | |
87 | + else if (msg.content.type === 'issue' || msg.content.type === 'pull-request') { | |
88 | + isOurMsg(msg.content.repo || msg.content.project, cb) | |
89 | + } else { | |
90 | + cb(err, msg.author in ourIds) | |
91 | + } | |
92 | + }) | |
93 | + } | |
94 | + | |
95 | + function isAnyOurMessage (msg, ids, cb) { | |
96 | + cont.para(ids.map(function (id) { | |
97 | + return function (cb) { isOurMsg(id, cb) } | |
98 | + }))(function (err, results) { | |
99 | + if (err) cb(err) | |
100 | + else if (results.some(Boolean)) cb(null, msg) | |
101 | + else cb() | |
102 | + }) | |
103 | + } | |
104 | + | |
105 | + return paramap(function (msg, cb) { | |
106 | + var c = msg.value && msg.value.content | |
107 | + if (!c || typeof c !== 'object') return cb() | |
108 | + if (msg.value.author in ourIds) return cb(null, msg) | |
109 | + | |
110 | + if (c.mentions && Array.isArray(c.mentions) && c.mentions.some(linksToUs)) { | |
111 | + return cb(null, msg) | |
112 | + } | |
113 | + | |
114 | + if (msg.private) { | |
115 | + return cb(null, msg) | |
116 | + } | |
117 | + | |
118 | + switch (c.type) { | |
119 | + case 'post': | |
120 | + if (c.branch || c.root) { | |
121 | + return isAnyOurMessage(msg, [].concat(c.branch, c.root), cb) | |
122 | + } else { | |
123 | + return cb() | |
124 | + } | |
125 | + case 'contact': | |
126 | + return cb(null, c.contact in ourIds ? msg : null) | |
127 | + case 'vote': | |
128 | + if (c.vote && c.vote.link) | |
129 | + return isOurMsg(c.vote.link, function (err, isOurs) { | |
130 | + cb(err, isOurs ? msg : null) | |
131 | + }) | |
132 | + else return cb() | |
133 | + case 'issue': | |
134 | + case 'pull-request': | |
135 | + return isOurMsg(c.project || c.repo, function (err, isOurs) { | |
136 | + cb(err, isOurs ? msg : null) | |
137 | + }) | |
138 | + case 'issue-edit': | |
139 | + return isAnyOurMessage(msg, [c.issue].concat(c.issues), cb) | |
140 | + default: | |
141 | + cb() | |
142 | + } | |
143 | + }, 4) | |
144 | +} | |
145 | + | |
146 | +function getFirstMessage (feedId, cb) { | |
147 | + sbot_user_feed({id: feedId, gte: 0, limit: 1})(null, cb) | |
148 | +} |
old_modules/obs-connected.js | ||
---|---|---|
@@ -1,0 +1,29 @@ | ||
1 | +var MutantSet = require('@mmckegg/mutant/set') | |
2 | +var plugs = require('patchbay/plugs') | |
3 | +var sbot_gossip_peers = plugs.first(exports.sbot_gossip_peers = []) | |
4 | + | |
5 | +var cache = null | |
6 | + | |
7 | +exports.obs_connected = function () { | |
8 | + if (cache) { | |
9 | + return cache | |
10 | + } else { | |
11 | + var result = MutantSet([], {nextTick: true}) | |
12 | + // todo: make this clean up on unlisten | |
13 | + | |
14 | + refresh() | |
15 | + setInterval(refresh, 10e3) | |
16 | + | |
17 | + cache = result | |
18 | + return result | |
19 | + } | |
20 | + | |
21 | + // scope | |
22 | + | |
23 | + function refresh () { | |
24 | + sbot_gossip_peers((err, peers) => { | |
25 | + if (err) throw console.log(err) | |
26 | + result.set(peers.filter(x => x.state === 'connected').map(x => x.key)) | |
27 | + }) | |
28 | + } | |
29 | +} |
old_modules/obs-following.js | ||
---|---|---|
@@ -1,0 +1,47 @@ | ||
1 | +var pull = require('pull-stream') | |
2 | +var computed = require('@mmckegg/mutant/computed') | |
3 | +var MutantPullReduce = require('../lib/mutant-pull-reduce') | |
4 | +var plugs = require('patchbay/plugs') | |
5 | +var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
6 | +var cache = {} | |
7 | +var throttle = require('@mmckegg/mutant/throttle') | |
8 | + | |
9 | +exports.obs_following = function (userId) { | |
10 | + if (cache[userId]) { | |
11 | + return cache[userId] | |
12 | + } else { | |
13 | + var stream = pull( | |
14 | + sbot_user_feed({id: userId, live: true}), | |
15 | + pull.filter((msg) => { | |
16 | + return !msg.value || msg.value.content.type === 'contact' | |
17 | + }) | |
18 | + ) | |
19 | + | |
20 | + var result = MutantPullReduce(stream, (result, msg) => { | |
21 | + var c = msg.value.content | |
22 | + if (c.contact) { | |
23 | + if (typeof c.following === 'boolean') { | |
24 | + if (c.following) { | |
25 | + result.add(c.contact) | |
26 | + } else { | |
27 | + result.delete(c.contact) | |
28 | + } | |
29 | + } | |
30 | + } | |
31 | + return result | |
32 | + }, { | |
33 | + startValue: new Set(), | |
34 | + nextTick: true | |
35 | + }) | |
36 | + | |
37 | + var instance = throttle(result, 2000) | |
38 | + instance.sync = result.sync | |
39 | + | |
40 | + instance.has = function (value) { | |
41 | + return computed(instance, x => x.has(value)) | |
42 | + } | |
43 | + | |
44 | + cache[userId] = instance | |
45 | + return instance | |
46 | + } | |
47 | +} |
old_modules/obs-local.js | ||
---|---|---|
@@ -1,0 +1,29 @@ | ||
1 | +var MutantSet = require('@mmckegg/mutant/set') | |
2 | +var plugs = require('patchbay/plugs') | |
3 | +var sbot_list_local = plugs.first(exports.sbot_list_local = []) | |
4 | + | |
5 | +var cache = null | |
6 | + | |
7 | +exports.obs_local = function () { | |
8 | + if (cache) { | |
9 | + return cache | |
10 | + } else { | |
11 | + var result = MutantSet([], {nextTick: true}) | |
12 | + // todo: make this clean up on unlisten | |
13 | + | |
14 | + refresh() | |
15 | + setInterval(refresh, 10e3) | |
16 | + | |
17 | + cache = result | |
18 | + return result | |
19 | + } | |
20 | + | |
21 | + // scope | |
22 | + | |
23 | + function refresh () { | |
24 | + sbot_list_local((err, keys) => { | |
25 | + if (err) throw console.log(err) | |
26 | + result.set(keys) | |
27 | + }) | |
28 | + } | |
29 | +} |
old_modules/obs-recently-updated-feeds.js | ||
---|---|---|
@@ -1,0 +1,36 @@ | ||
1 | +var pull = require('pull-stream') | |
2 | +var pullCat = require('pull-cat') | |
3 | +var computed = require('@mmckegg/mutant/computed') | |
4 | +var MutantPullReduce = require('../lib/mutant-pull-reduce') | |
5 | +var plugs = require('patchbay/plugs') | |
6 | +var sbot_log = plugs.first(exports.sbot_log = []) | |
7 | +var throttle = require('@mmckegg/mutant/throttle') | |
8 | +var hr = 60 * 60 * 1000 | |
9 | + | |
10 | +exports.obs_recently_updated_feeds = function (limit) { | |
11 | + var stream = pull( | |
12 | + pullCat([ | |
13 | + sbot_log({reverse: true, limit: limit || 500}), | |
14 | + sbot_log({old: false}) | |
15 | + ]) | |
16 | + ) | |
17 | + | |
18 | + var result = MutantPullReduce(stream, (result, msg) => { | |
19 | + if (msg.value.timestamp && Date.now() - msg.value.timestamp < 24 * hr) { | |
20 | + result.add(msg.value.author) | |
21 | + } | |
22 | + return result | |
23 | + }, { | |
24 | + startValue: new Set(), | |
25 | + nextTick: true | |
26 | + }) | |
27 | + | |
28 | + var instance = throttle(result, 2000) | |
29 | + instance.sync = result.sync | |
30 | + | |
31 | + instance.has = function (value) { | |
32 | + return computed(instance, x => x.has(value)) | |
33 | + } | |
34 | + | |
35 | + return instance | |
36 | +} |
old_modules/obs-subscribed-channels.js | ||
---|---|---|
@@ -1,0 +1,50 @@ | ||
1 | +var pull = require('pull-stream') | |
2 | +var computed = require('@mmckegg/mutant/computed') | |
3 | +var MutantPullReduce = require('../lib/mutant-pull-reduce') | |
4 | +var plugs = require('patchbay/plugs') | |
5 | +var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
6 | +var cache = {} | |
7 | +var throttle = require('@mmckegg/mutant/throttle') | |
8 | + | |
9 | +exports.obs_subscribed_channels = function (userId) { | |
10 | + if (cache[userId]) { | |
11 | + return cache[userId] | |
12 | + } else { | |
13 | + var stream = pull( | |
14 | + sbot_user_feed({id: userId, live: true}), | |
15 | + pull.filter((msg) => { | |
16 | + return !msg.value || msg.value.content.type === 'channel' | |
17 | + }) | |
18 | + ) | |
19 | + | |
20 | + var result = MutantPullReduce(stream, (result, msg) => { | |
21 | + var c = msg.value.content | |
22 | + if (typeof c.channel === 'string' && c.channel) { | |
23 | + var channel = c.channel.trim() | |
24 | + if (channel) { | |
25 | + if (typeof c.subscribed === 'boolean') { | |
26 | + if (c.subscribed) { | |
27 | + result.add(channel) | |
28 | + } else { | |
29 | + result.delete(channel) | |
30 | + } | |
31 | + } | |
32 | + } | |
33 | + } | |
34 | + return result | |
35 | + }, { | |
36 | + startValue: new Set(), | |
37 | + nextTick: true | |
38 | + }) | |
39 | + | |
40 | + var instance = throttle(result, 2000) | |
41 | + instance.sync = result.sync | |
42 | + | |
43 | + instance.has = function (value) { | |
44 | + return computed(instance, x => x.has(value)) | |
45 | + } | |
46 | + | |
47 | + cache[userId] = instance | |
48 | + return instance | |
49 | + } | |
50 | +} |
old_modules/people-names.js | ||
---|---|---|
@@ -1,0 +1,22 @@ | ||
1 | +var Value = require('@mmckegg/mutant/value') | |
2 | +var plugs = require('patchbay/plugs') | |
3 | +var signifier = plugs.first(exports.signifier = []) | |
4 | +var computed = require('@mmckegg/mutant/computed') | |
5 | + | |
6 | +exports.people_names = function (ids) { | |
7 | + return computed(Array.from(ids).map(ObservName), join) || '' | |
8 | +} | |
9 | + | |
10 | +function join (...args) { | |
11 | + return args.join('\n') | |
12 | +} | |
13 | + | |
14 | +function ObservName (id) { | |
15 | + var obs = Value(id.slice(0, 10)) | |
16 | + signifier(id, (_, value) => { | |
17 | + if (value && value.length) { | |
18 | + obs.set(value[0].name) | |
19 | + } | |
20 | + }) | |
21 | + return obs | |
22 | +} |
old_modules/person.js | ||
---|---|---|
@@ -1,0 +1,9 @@ | ||
1 | +var plugs = require('patchbay/plugs') | |
2 | +var avatar_name = plugs.first(exports.avatar_name = []) | |
3 | +var avatar_link = plugs.first(exports.avatar_link = []) | |
4 | + | |
5 | +exports.person = person | |
6 | + | |
7 | +function person (id) { | |
8 | + return avatar_link(id, avatar_name(id), '') | |
9 | +} |
old_modules/post.js | ||
---|---|---|
@@ -1,0 +1,13 @@ | ||
1 | +var h = require('hyperscript') | |
2 | +var plugs = require('patchbay/plugs') | |
3 | +var message_link = plugs.first(exports.message_link = []) | |
4 | +var markdown = plugs.first(exports.markdown = []) | |
5 | + | |
6 | +exports.message_content = function (data) { | |
7 | + if(!data.value.content || !data.value.content.text) return | |
8 | + | |
9 | + return h('div', | |
10 | + markdown(data.value.content) | |
11 | + ) | |
12 | + | |
13 | +} |
old_modules/private.js | ||
---|---|---|
@@ -1,0 +1,84 @@ | ||
1 | +var pull = require('pull-stream') | |
2 | +var ref = require('ssb-ref') | |
3 | +var plugs = require('patchbay/plugs') | |
4 | +var message_compose = plugs.first(exports.message_compose = []) | |
5 | +var sbot_log = plugs.first(exports.sbot_log = []) | |
6 | +var feed_summary = plugs.first(exports.feed_summary = []) | |
7 | +var message_unbox = plugs.first(exports.message_unbox = []) | |
8 | +var get_id = plugs.first(exports.get_id = []) | |
9 | +var avatar_image_link = plugs.first(exports.avatar_image_link = []) | |
10 | +var update_cache = plugs.first(exports.update_cache = []) | |
11 | +var h = require('../lib/h') | |
12 | + | |
13 | +exports.screen_view = function (path, sbot) { | |
14 | + if (path === '/private') { | |
15 | + var id = get_id() | |
16 | + | |
17 | + return feed_summary((opts) => { | |
18 | + return pull( | |
19 | + sbot_log(opts), | |
20 | + loosen(10), // release tight loops if they continue too long (avoid scroll jank) | |
21 | + unbox(), | |
22 | + pull.through((item) => { | |
23 | + if (item.value) { | |
24 | + update_cache(item) | |
25 | + } | |
26 | + }) | |
27 | + ) | |
28 | + }, [ | |
29 | + message_compose({type: 'post', recps: [], private: true}, { | |
30 | + prepublish: function (msg) { | |
31 | + msg.recps = [id].concat(msg.mentions).filter(function (e) { | |
32 | + return ref.isFeed(typeof e === 'string' ? e : e.link) | |
33 | + }) | |
34 | + if (!msg.recps.length) { | |
35 | + throw new Error('cannot make private message without recipients - just mention the user in an at reply in the message you send') | |
36 | + } | |
37 | + return msg | |
38 | + }, | |
39 | + placeholder: `Write a private message \n\n\n\nThis can only be read by yourself and people you have @mentioned.` | |
40 | + }) | |
41 | + ], { | |
42 | + windowSize: 1000 | |
43 | + }) | |
44 | + } | |
45 | +} | |
46 | + | |
47 | +exports.message_meta = function (msg) { | |
48 | + if (msg.value.content.recps || msg.value.private) { | |
49 | + return h('span.private', [ | |
50 | + map(msg.value.content.recps, function (id) { | |
51 | + return avatar_image_link(typeof id === 'string' ? id : id.link, 'thumbnail') | |
52 | + }) | |
53 | + ]) | |
54 | + } | |
55 | +} | |
56 | + | |
57 | +function unbox () { | |
58 | + return pull( | |
59 | + pull.filter(function (msg) { | |
60 | + return typeof msg.value.content === 'string' | |
61 | + }), | |
62 | + pull.map(function (msg) { | |
63 | + return message_unbox(msg) || { timestamp: msg.timestamp } | |
64 | + }) | |
65 | + ) | |
66 | +} | |
67 | + | |
68 | +function map (ary, iter) { | |
69 | + if (Array.isArray(ary)) return ary.map(iter) | |
70 | +} | |
71 | + | |
72 | +function loosen (max) { | |
73 | + var lastRelease = Date.now() | |
74 | + return pull.asyncMap(function (item, cb) { | |
75 | + if (Date.now() - lastRelease > max) { | |
76 | + setImmediate(() => { | |
77 | + lastRelease = Date.now() | |
78 | + cb(null, item) | |
79 | + }) | |
80 | + } else { | |
81 | + cb(null, item) | |
82 | + } | |
83 | + }) | |
84 | +} |
old_modules/public.js | ||
---|---|---|
@@ -1,0 +1,225 @@ | ||
1 | +var MutantMap = require('@mmckegg/mutant/map') | |
2 | +var computed = require('@mmckegg/mutant/computed') | |
3 | +var when = require('@mmckegg/mutant/when') | |
4 | +var send = require('@mmckegg/mutant/send') | |
5 | +var pull = require('pull-stream') | |
6 | +var extend = require('xtend') | |
7 | + | |
8 | +var plugs = require('patchbay/plugs') | |
9 | +var h = require('../lib/h') | |
10 | +var message_compose = plugs.first(exports.message_compose = []) | |
11 | +var sbot_log = plugs.first(exports.sbot_log = []) | |
12 | +var sbot_feed = plugs.first(exports.sbot_feed = []) | |
13 | +var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) | |
14 | + | |
15 | +var feed_summary = plugs.first(exports.feed_summary = []) | |
16 | +var obs_channels = plugs.first(exports.obs_channels = []) | |
17 | +var obs_subscribed_channels = plugs.first(exports.obs_subscribed_channels = []) | |
18 | +var get_id = plugs.first(exports.get_id = []) | |
19 | +var publish = plugs.first(exports.sbot_publish = []) | |
20 | +var obs_following = plugs.first(exports.obs_following = []) | |
21 | +var obs_recently_updated_feeds = plugs.first(exports.obs_recently_updated_feeds = []) | |
22 | +var avatar_image = plugs.first(exports.avatar_image = []) | |
23 | +var avatar_name = plugs.first(exports.avatar_name = []) | |
24 | +var obs_local = plugs.first(exports.obs_local = []) | |
25 | +var obs_connected = plugs.first(exports.obs_connected = []) | |
26 | + | |
27 | +exports.screen_view = function (path, sbot) { | |
28 | + if (path === '/public') { | |
29 | + var id = get_id() | |
30 | + var channels = computed(obs_channels(), items => items.slice(0, 8), {comparer: arrayEq}) | |
31 | + var subscribedChannels = obs_subscribed_channels(id) | |
32 | + var loading = computed(subscribedChannels.sync, x => !x) | |
33 | + var connectedPeers = obs_connected() | |
34 | + var localPeers = obs_local() | |
35 | + var connectedPubs = computed([connectedPeers, localPeers], (c, l) => c.filter(x => !l.includes(x))) | |
36 | + var following = obs_following(id) | |
37 | + | |
38 | + var oldest = Date.now() - (2 * 24 * 60 * 60e3) | |
39 | + getFirstMessage(id, (_, msg) => { | |
40 | + if (msg) { | |
41 | + // fall back to timestamp stream before this, give 48 hrs for feeds to stabilize | |
42 | + if (msg.value.timestamp > oldest) { | |
43 | + oldest = Date.now() | |
44 | + } | |
45 | + } | |
46 | + }) | |
47 | + | |
48 | + var whoToFollow = computed([obs_following(id), obs_recently_updated_feeds(200)], (following, recent) => { | |
49 | + return Array.from(recent).filter(x => x !== id && !following.has(x)).slice(0, 10) | |
50 | + }) | |
51 | + | |
52 | + var feedSummary = feed_summary(getFeed, [ | |
53 | + message_compose({type: 'post'}, {placeholder: 'Write a public message'}) | |
54 | + ], { | |
55 | + waitUntil: computed([ | |
56 | + following.sync, | |
57 | + subscribedChannels.sync | |
58 | + ], x => x.every(Boolean)), | |
59 | + windowSize: 500, | |
60 | + filter: (item) => { | |
61 | + return ( | |
62 | + id === item.author || | |
63 | + following().has(item.author) || | |
64 | + subscribedChannels().has(item.channel) || | |
65 | + (item.repliesFrom && item.repliesFrom.has(id)) || | |
66 | + item.digs && item.digs.has(id) | |
67 | + ) | |
68 | + }, | |
69 | + bumpFilter: (msg, group) => { | |
70 | + if (!group.message) { | |
71 | + return ( | |
72 | + isMentioned(id, msg.value.content.mentions) || | |
73 | + msg.value.author === id || ( | |
74 | + fromDay(msg, group.fromTime) && ( | |
75 | + following().has(msg.value.author) || | |
76 | + group.repliesFrom.has(id) | |
77 | + ) | |
78 | + ) | |
79 | + ) | |
80 | + } | |
81 | + return true | |
82 | + } | |
83 | + }) | |
84 | + | |
85 | + var result = h('SplitView', [ | |
86 | + h('div.side', [ | |
87 | + h('h2', 'Active Channels'), | |
88 | + when(loading, [ h('Loading') ]), | |
89 | + h('ChannelList', { | |
90 | + hidden: loading | |
91 | + }, [ | |
92 | + MutantMap(channels, (channel) => { | |
93 | + var subscribed = subscribedChannels.has(channel.id) | |
94 | + return h('a.channel', { | |
95 | + href: `##${channel.id}`, | |
96 | + classList: [ | |
97 | + when(subscribed, '-subscribed') | |
98 | + ] | |
99 | + }, [ | |
100 | + h('span.name', '#' + channel.id), | |
101 | + when(subscribed, | |
102 | + h('a -unsubscribe', { | |
103 | + 'ev-click': send(unsubscribe, channel.id) | |
104 | + }, 'Unsubscribe'), | |
105 | + h('a -subscribe', { | |
106 | + 'ev-click': send(subscribe, channel.id) | |
107 | + }, 'Subscribe') | |
108 | + ) | |
109 | + ]) | |
110 | + }, {maxTime: 5}) | |
111 | + ]), | |
112 | + | |
113 | + when(computed(localPeers, x => x.length), h('h2', 'Local')), | |
114 | + h('ProfileList', [ | |
115 | + MutantMap(localPeers, (id) => { | |
116 | + return h('a.profile', { | |
117 | + classList: [ | |
118 | + when(computed([connectedPeers, id], (p, id) => p.includes(id)), '-connected') | |
119 | + ], | |
120 | + href: `#${id}` | |
121 | + }, [ | |
122 | + h('div.avatar', [avatar_image(id)]), | |
123 | + h('div.main', [ | |
124 | + h('div.name', [ avatar_name(id) ]) | |
125 | + ]) | |
126 | + ]) | |
127 | + }) | |
128 | + ]), | |
129 | + | |
130 | + when(computed(whoToFollow, x => x.length), h('h2', 'Who to follow')), | |
131 | + h('ProfileList', [ | |
132 | + MutantMap(whoToFollow, (id) => { | |
133 | + return h('a.profile', { | |
134 | + href: `#${id}` | |
135 | + }, [ | |
136 | + h('div.avatar', [avatar_image(id)]), | |
137 | + h('div.main', [ | |
138 | + h('div.name', [ avatar_name(id) ]) | |
139 | + ]) | |
140 | + ]) | |
141 | + }) | |
142 | + ]), | |
143 | + | |
144 | + when(computed(connectedPubs, x => x.length), h('h2', 'Connected Pubs')), | |
145 | + h('ProfileList', [ | |
146 | + MutantMap(connectedPubs, (id) => { | |
147 | + return h('a.profile', { | |
148 | + classList: [ '-connected' ], | |
149 | + href: `#${id}` | |
150 | + }, [ | |
151 | + h('div.avatar', [avatar_image(id)]), | |
152 | + h('div.main', [ | |
153 | + h('div.name', [ avatar_name(id) ]) | |
154 | + ]) | |
155 | + ]) | |
156 | + }) | |
157 | + ]) | |
158 | + ]), | |
159 | + h('div.main', [ feedSummary ]) | |
160 | + ]) | |
161 | + | |
162 | + result.pendingUpdates = feedSummary.pendingUpdates | |
163 | + result.reload = feedSummary.reload | |
164 | + | |
165 | + return result | |
166 | + } | |
167 | + | |
168 | + // scoped | |
169 | + | |
170 | + function getFeed (opts) { | |
171 | + if (opts.lt && opts.lt < oldest) { | |
172 | + opts = extend(opts, {lt: parseInt(opts.lt, 10)}) | |
173 | + return pull( | |
174 | + sbot_feed(opts), | |
175 | + pull.map((msg) => { | |
176 | + if (msg.sync) { | |
177 | + return msg | |
178 | + } else { | |
179 | + return {key: msg.key, value: msg.value, timestamp: msg.value.timestamp} | |
180 | + } | |
181 | + }) | |
182 | + ) | |
183 | + } else { | |
184 | + return sbot_log(opts) | |
185 | + } | |
186 | + } | |
187 | +} | |
188 | + | |
189 | +function fromDay (msg, fromTime) { | |
190 | + return (fromTime - msg.timestamp) < (24 * 60 * 60e3) | |
191 | +} | |
192 | + | |
193 | +function isMentioned (id, list) { | |
194 | + if (Array.isArray(list)) { | |
195 | + return list.includes(id) | |
196 | + } else { | |
197 | + return false | |
198 | + } | |
199 | +} | |
200 | + | |
201 | +function subscribe (id) { | |
202 | + publish({ | |
203 | + type: 'channel', | |
204 | + channel: id, | |
205 | + subscribed: true | |
206 | + }) | |
207 | +} | |
208 | + | |
209 | +function unsubscribe (id) { | |
210 | + publish({ | |
211 | + type: 'channel', | |
212 | + channel: id, | |
213 | + subscribed: false | |
214 | + }) | |
215 | +} | |
216 | + | |
217 | +function arrayEq (a, b) { | |
218 | + if (Array.isArray(a) && Array.isArray(b) && a.length === b.length && a !== b) { | |
219 | + return a.every((value, i) => value === b[i]) | |
220 | + } | |
221 | +} | |
222 | + | |
223 | +function getFirstMessage (feedId, cb) { | |
224 | + sbot_user_feed({id: feedId, gte: 0, limit: 1})(null, cb) | |
225 | +} |
old_modules/raw.js | ||
---|---|---|
@@ -1,0 +1,1 @@ | ||
1 | +// disable |
old_modules/thread.js | ||
---|---|---|
@@ -1,0 +1,127 @@ | ||
1 | +var ui = require('patchbay/ui') | |
2 | +var pull = require('pull-stream') | |
3 | +var Cat = require('pull-cat') | |
4 | +var sort = require('ssb-sort') | |
5 | +var ref = require('ssb-ref') | |
6 | +var h = require('hyperscript') | |
7 | +var u = require('patchbay/util') | |
8 | +var Scroller = require('pull-scroll') | |
9 | + | |
10 | + | |
11 | +function once (cont) { | |
12 | + var ended = false | |
13 | + return function (abort, cb) { | |
14 | + if(abort) return cb(abort) | |
15 | + else if (ended) return cb(ended) | |
16 | + else | |
17 | + cont(function (err, data) { | |
18 | + if(err) return cb(ended = err) | |
19 | + ended = true | |
20 | + cb(null, data) | |
21 | + }) | |
22 | + } | |
23 | +} | |
24 | + | |
25 | +var plugs = require('patchbay/plugs') | |
26 | + | |
27 | +var message_render = plugs.first(exports.message_render = []) | |
28 | +var message_name = plugs.first(exports.message_name = []) | |
29 | +var message_compose = plugs.first(exports.message_compose = []) | |
30 | +var message_unbox = plugs.first(exports.message_unbox = []) | |
31 | + | |
32 | +var sbot_get = plugs.first(exports.sbot_get = []) | |
33 | +var sbot_links = plugs.first(exports.sbot_links = []) | |
34 | +var get_id = plugs.first(exports.get_id = []) | |
35 | + | |
36 | +function getThread (root, cb) { | |
37 | + //in this case, it's inconvienent that panel only takes | |
38 | + //a stream. maybe it would be better to accept an array? | |
39 | + | |
40 | + sbot_get(root, function (err, value) { | |
41 | + var msg = {key: root, value: value} | |
42 | +// if(value.content.root) return getThread(value.content.root, cb) | |
43 | + | |
44 | + pull( | |
45 | + sbot_links({rel: 'root', dest: root, values: true, keys: true}), | |
46 | + pull.collect(function (err, ary) { | |
47 | + if(err) return cb(err) | |
48 | + ary.unshift(msg) | |
49 | + cb(null, ary) | |
50 | + }) | |
51 | + ) | |
52 | + }) | |
53 | + | |
54 | +} | |
55 | + | |
56 | +exports.screen_view = function (id) { | |
57 | + if(ref.isMsg(id)) { | |
58 | + var meta = { | |
59 | + type: 'post', | |
60 | + root: id, | |
61 | + branch: id //mutated when thread is loaded. | |
62 | + } | |
63 | + | |
64 | + var previousId = id | |
65 | + var content = h('div.column.scroller__content') | |
66 | + var div = h('div.column.scroller', | |
67 | + {style: {'overflow-y': 'auto'}}, | |
68 | + h('div.scroller__wrapper', | |
69 | + content, | |
70 | + message_compose(meta, {shrink: false, placeholder: 'Write a reply'}) | |
71 | + ) | |
72 | + ) | |
73 | + | |
74 | + message_name(id, function (err, name) { | |
75 | + div.title = name | |
76 | + }) | |
77 | + | |
78 | + pull( | |
79 | + sbot_links({ | |
80 | + rel: 'root', dest: id, keys: true, old: false | |
81 | + }), | |
82 | + pull.drain(function (msg) { | |
83 | + loadThread() //redraw thread | |
84 | + }, function () {} ) | |
85 | + ) | |
86 | + | |
87 | + | |
88 | + function loadThread () { | |
89 | + getThread(id, function (err, thread) { | |
90 | + //would probably be better keep an id for each message element | |
91 | + //(i.e. message key) and then update it if necessary. | |
92 | + //also, it may have moved (say, if you received a missing message) | |
93 | + content.innerHTML = '' | |
94 | + //decrypt | |
95 | + thread = thread.map(function (msg) { | |
96 | + return 'string' === typeof msg.value.content ? message_unbox(msg) : msg | |
97 | + }) | |
98 | + | |
99 | + if(err) return content.appendChild(h('pre', err.stack)) | |
100 | + sort(thread).map((msg) => { | |
101 | + var result = message_render(msg, {inContext: true, previousId}) | |
102 | + previousId = msg.key | |
103 | + return result | |
104 | + }).filter(Boolean).forEach(function (el) { | |
105 | + content.appendChild(el) | |
106 | + }) | |
107 | + | |
108 | + var branches = sort.heads(thread) | |
109 | + meta.branch = branches.length > 1 ? branches : branches[0] | |
110 | + meta.root = thread[0].value.content.root || thread[0].key | |
111 | + meta.channel = thread[0].value.content.channel | |
112 | + | |
113 | + var recps = thread[0].value.content.recps | |
114 | + var private = thread[0].value.private | |
115 | + if(private) { | |
116 | + if(recps) | |
117 | + meta.recps = recps | |
118 | + else | |
119 | + meta.recps = [thread[0].value.author, get_id()] | |
120 | + } | |
121 | + }) | |
122 | + } | |
123 | + | |
124 | + loadThread() | |
125 | + return div | |
126 | + } | |
127 | +} |
old_modules/timestamp.js | ||
---|---|---|
@@ -1,0 +1,20 @@ | ||
1 | +var h = require('hyperscript') | |
2 | +var human = require('human-time') | |
3 | + | |
4 | +function updateTimestampEl(el) { | |
5 | + el.firstChild.nodeValue = human(new Date(el.timestamp)) | |
6 | + return el | |
7 | +} | |
8 | + | |
9 | +setInterval(function () { | |
10 | + var els = [].slice.call(document.querySelectorAll('.pw__timestamp')) | |
11 | + els.forEach(updateTimestampEl) | |
12 | +}, 60e3) | |
13 | + | |
14 | +exports.message_main_meta = function (msg) { | |
15 | + return updateTimestampEl(h('a.enter.pw__timestamp', { | |
16 | + href: '#'+msg.key, | |
17 | + timestamp: msg.value.timestamp, | |
18 | + title: new Date(msg.value.timestamp) | |
19 | + }, '')) | |
20 | +} |
old_styles/channel-list.mcss | ||
---|---|---|
@@ -1,0 +1,73 @@ | ||
1 | +ChannelList { | |
2 | + a.channel { | |
3 | + display: flex; | |
4 | + padding: 8px 10px; | |
5 | + font-size: 110%; | |
6 | + margin: 4px 0; | |
7 | + background: rgba(255, 255, 255, 0.22); | |
8 | + border-radius: 5px; | |
9 | + position: relative | |
10 | + transition: background-color 0.2s | |
11 | + max-width: 250px; | |
12 | + | |
13 | + background-repeat: no-repeat | |
14 | + background-position: right | |
15 | + | |
16 | + -subscribed { | |
17 | + background-image: svg(subscribed) | |
18 | + span.name { | |
19 | + font-weight: bold | |
20 | + } | |
21 | + } | |
22 | + | |
23 | + @svg subscribed { | |
24 | + width: 20px | |
25 | + height: 12px | |
26 | + content: "<circle cx='6' stroke='#888' fill='none' cy='6' r='5' /> <circle cx='6' cy='6' r='3' fill='#888'/>" | |
27 | + } | |
28 | + | |
29 | + :hover { | |
30 | + background: rgba(255, 255, 255, 0.4); | |
31 | + text-decoration: none; | |
32 | + a { | |
33 | + transition: opacity 0.05s | |
34 | + opacity: 1 | |
35 | + } | |
36 | + } | |
37 | + | |
38 | + span.name { | |
39 | + flex: 1 | |
40 | + white-space: nowrap; | |
41 | + min-width: 0; | |
42 | + } | |
43 | + | |
44 | + a { | |
45 | + display: inline | |
46 | + opacity: 0; | |
47 | + font-size: 80%; | |
48 | + background-color: rgb(112, 112, 112); | |
49 | + transition: opacity 0.2s, background-color 0.4s | |
50 | + padding: 9px 10px; | |
51 | + color: white; | |
52 | + border-radius: 4px; | |
53 | + font-weight: bold; | |
54 | + margin: -8px -10px -8px 4px; | |
55 | + border-top-left-radius: 0; | |
56 | + border-bottom-left-radius: 0; | |
57 | + border-left: 2px solid rgba(255, 255, 255, 0.9); | |
58 | + text-decoration: none | |
59 | + | |
60 | + -subscribe { | |
61 | + :hover { | |
62 | + background-color: rgb(112, 184, 212); | |
63 | + } | |
64 | + } | |
65 | + | |
66 | + -unsubscribe { | |
67 | + :hover { | |
68 | + background: rgb(212, 112, 112); | |
69 | + } | |
70 | + } | |
71 | + } | |
72 | + } | |
73 | +} |
old_styles/feed-event.mcss | ||
---|---|---|
@@ -1,0 +1,36 @@ | ||
1 | +FeedEvent { | |
2 | + display: flex | |
3 | + flex: 1 | |
4 | + flex-direction: column | |
5 | + background: white | |
6 | + margin-top: 10px | |
7 | + | |
8 | + div { | |
9 | + flex: 1 | |
10 | + } | |
11 | + | |
12 | + a.full { | |
13 | + display: block; | |
14 | + padding: 10px; | |
15 | + background: #daecd6; | |
16 | + border-top: 1px solid #bbc9d2; | |
17 | + border-bottom: 1px solid #bbc9d2; | |
18 | + text-align: center; | |
19 | + color: #759053; | |
20 | + } | |
21 | + | |
22 | + div.replies { | |
23 | + font-size: 100% | |
24 | + display: flex | |
25 | + flex-direction: column | |
26 | + div { | |
27 | + flex: 1 | |
28 | + margin: 0 | |
29 | + } | |
30 | + } | |
31 | + | |
32 | + div.meta { | |
33 | + font-size: 100% | |
34 | + padding: 10px | |
35 | + } | |
36 | +} |
old_styles/loading.mcss | ||
---|---|---|
@@ -1,0 +1,63 @@ | ||
1 | +Loading { | |
2 | + height: 25% | |
3 | + display: flex | |
4 | + align-items: center | |
5 | + justify-content: center | |
6 | + | |
7 | + -inline { | |
8 | + height: 16px | |
9 | + width: 16px | |
10 | + display: inline-block | |
11 | + margin: -3px 3px | |
12 | + | |
13 | + ::before { | |
14 | + display: block | |
15 | + height: 16px | |
16 | + width: 16px | |
17 | + } | |
18 | + } | |
19 | + | |
20 | + -large { | |
21 | + ::before { | |
22 | + height: 100px | |
23 | + width: 100px | |
24 | + } | |
25 | + ::after { | |
26 | + color: #CCC; | |
27 | + content: 'Loading...' | |
28 | + font-size: 200% | |
29 | + } | |
30 | + } | |
31 | + | |
32 | + ::before { | |
33 | + content: ' ' | |
34 | + height: 50px | |
35 | + width: 50px | |
36 | + background-image: svg(waitingIcon) | |
37 | + background-repeat: no-repeat | |
38 | + background-position: center | |
39 | + background-size: contain | |
40 | + animation: spin 3s infinite linear | |
41 | + } | |
42 | +} | |
43 | + | |
44 | +@svg waitingIcon { | |
45 | + width: 30px | |
46 | + height: 30px | |
47 | + content: "<circle cx='15' cy='15' r='10' /><circle cx='10' cy='10' r='2' /><circle cx='20' cy='20' r='3' />" | |
48 | + | |
49 | + circle { | |
50 | + stroke: #CCC | |
51 | + stroke-width: 3px | |
52 | + fill: none | |
53 | + } | |
54 | +} | |
55 | + | |
56 | +@keyframes spin { | |
57 | + 0% { | |
58 | + transform: rotate(0deg); | |
59 | + } | |
60 | + 100% { | |
61 | + transform: rotate(360deg); | |
62 | + } | |
63 | +} |
old_styles/message-confirm.mcss | ||
---|---|---|
@@ -1,0 +1,18 @@ | ||
1 | +MessageConfirm { | |
2 | + section { | |
3 | + max-height: 80vh; | |
4 | + overflow: auto; | |
5 | + margin: -20px; | |
6 | + padding: 20px; | |
7 | + margin-bottom: 0; | |
8 | + } | |
9 | + footer { | |
10 | + text-align: right; | |
11 | + background: #e2e2e2; | |
12 | + margin: 0px -20px -20px; | |
13 | + padding: 5px; | |
14 | + border-top: 1px solid #d6d6d6; | |
15 | + position: relative; | |
16 | + box-shadow: 0 0 6px rgba(51, 51, 51, 0.47); | |
17 | + } | |
18 | +} |
old_styles/message.mcss | ||
---|---|---|
@@ -1,0 +1,166 @@ | ||
1 | +Message { | |
2 | + display: flex | |
3 | + flex-direction: column | |
4 | + box-shadow: #dadada 1px 2px 8px | |
5 | + border: 1px solid #f5f5f5 | |
6 | + background: white | |
7 | + position: relative | |
8 | + font-size: 120% | |
9 | + | |
10 | + :focus { | |
11 | + z-index: 1 | |
12 | + } | |
13 | + | |
14 | + -data { | |
15 | + header { | |
16 | + div.main { | |
17 | + font-size: 80% | |
18 | + a.avatar { | |
19 | + img { | |
20 | + width: 25px | |
21 | + } | |
22 | + } | |
23 | + } | |
24 | + } | |
25 | + (pre) { | |
26 | + overflow: auto | |
27 | + max-height: 200px | |
28 | + } | |
29 | + } | |
30 | + | |
31 | + -reply { | |
32 | + font-size: 100% | |
33 | + header { | |
34 | + div.main { | |
35 | + a.avatar { | |
36 | + img { | |
37 | + width: 30px | |
38 | + } | |
39 | + } | |
40 | + } | |
41 | + } | |
42 | + } | |
43 | + | |
44 | + header { | |
45 | + margin: 15px 15px | |
46 | + display: flex | |
47 | + | |
48 | + div.mini { | |
49 | + flex: 1 | |
50 | + } | |
51 | + | |
52 | + div.main { | |
53 | + display: flex | |
54 | + flex: 1 | |
55 | + | |
56 | + a.avatar { | |
57 | + img { | |
58 | + width: 50px | |
59 | + } | |
60 | + } | |
61 | + | |
62 | + div.main { | |
63 | + div.name { | |
64 | + font-size: 120% | |
65 | + a { | |
66 | + color: #444 | |
67 | + font-weight: bold | |
68 | + } | |
69 | + } | |
70 | + div.meta { | |
71 | + font-size: 90% | |
72 | + } | |
73 | + margin-left: 10px | |
74 | + } | |
75 | + | |
76 | + div.meta { | |
77 | + | |
78 | + } | |
79 | + } | |
80 | + | |
81 | + div.meta { | |
82 | + | |
83 | + em { | |
84 | + display: inline-block | |
85 | + padding: 4px | |
86 | + } | |
87 | + | |
88 | + a.channel { | |
89 | + display: inline-block | |
90 | + padding: 4px | |
91 | + } | |
92 | + | |
93 | + span.likes { | |
94 | + color: #ffffff; | |
95 | + background: linear-gradient(45deg, #859c88, #87d47d); | |
96 | + padding: 5px 8px; | |
97 | + border-radius: 10px; | |
98 | + display: inline-block; | |
99 | + vertical-align: top; | |
100 | + } | |
101 | + | |
102 | + span.private { | |
103 | + display: inline-block; | |
104 | + margin: -3px -3px -3px 4px; | |
105 | + border: 4px solid #525050; | |
106 | + position: relative; | |
107 | + | |
108 | + a { | |
109 | + display: inline-block | |
110 | + | |
111 | + img { | |
112 | + margin: 0 | |
113 | + vertical-align: bottom | |
114 | + border: none | |
115 | + } | |
116 | + } | |
117 | + | |
118 | + :after { | |
119 | + content: 'private'; | |
120 | + position: absolute; | |
121 | + background: #525050; | |
122 | + bottom: 0; | |
123 | + left: -1px; | |
124 | + font-size: 10px; | |
125 | + padding: 2px 4px 0 2px; | |
126 | + border-top-right-radius: 5px; | |
127 | + color: white; | |
128 | + font-weight: bold; | |
129 | + pointer-events: none; | |
130 | + white-space: nowrap | |
131 | + } | |
132 | + } | |
133 | + } | |
134 | + } | |
135 | + | |
136 | + section { | |
137 | + margin: 0 15px | |
138 | + (img) { | |
139 | + max-width: 100% | |
140 | + } | |
141 | + } | |
142 | + | |
143 | + footer { | |
144 | + background: #fdfdfd | |
145 | + padding: 15px 15px | |
146 | + text-align: right | |
147 | + | |
148 | + div.actions { | |
149 | + a { | |
150 | + margin-left: 5px; | |
151 | + color: #5f5f5f; | |
152 | + padding: 3px 6px; | |
153 | + background: white; | |
154 | + border: 2px solid #DDD; | |
155 | + border-radius: 4px; | |
156 | + | |
157 | + :hover { | |
158 | + background: #cbeeff; | |
159 | + color: #3b7ba2; | |
160 | + text-decoration: none; | |
161 | + border-color: #8eafc1; | |
162 | + } | |
163 | + } | |
164 | + } | |
165 | + } | |
166 | +} |
old_styles/notifier.mcss | ||
---|---|---|
@@ -1,0 +1,24 @@ | ||
1 | +Notifier { | |
2 | + padding: 10px; | |
3 | + display: block; | |
4 | + background: rgb(214, 228, 236); | |
5 | + border-bottom: 1px solid rgb(187, 201, 210); | |
6 | + text-align: center; | |
7 | + | |
8 | + :hover { | |
9 | + background: rgb(220, 242, 255); | |
10 | + } | |
11 | + | |
12 | + animation: 0.5s slide-in | |
13 | + position: relative | |
14 | + | |
15 | + -loader { | |
16 | + background: rgb(255, 239, 217); | |
17 | + border-bottom: 1px solid rgb(228, 220, 195); | |
18 | + color: #10100c !important; | |
19 | + | |
20 | + :hover { | |
21 | + background: rgb(245, 229, 207); | |
22 | + } | |
23 | + } | |
24 | +} |
old_styles/page-heading.mcss | ||
---|---|---|
@@ -1,0 +1,43 @@ | ||
1 | +PageHeading { | |
2 | + display: flex | |
3 | + h1 { | |
4 | + flex: 1 | |
5 | + } | |
6 | + div.meta { | |
7 | + a { | |
8 | + font-size: 80%; | |
9 | + background: rgb(112, 112, 112); | |
10 | + border: 2px solid #313131; | |
11 | + transition: opacity 0.2s; | |
12 | + opacity: 0.6; | |
13 | + padding: 6px 12px; | |
14 | + color: white; | |
15 | + border-radius: 4px; | |
16 | + font-weight: bold; | |
17 | + text-decoration: none; | |
18 | + display: block; | |
19 | + | |
20 | + -subscribe { | |
21 | + background-color: rgb(88, 171, 204); | |
22 | + border-color: #20699c; | |
23 | + } | |
24 | + | |
25 | + -unsubscribe { | |
26 | + background-repeat: no-repeat | |
27 | + background-position: right | |
28 | + background-image: svg(subscribed) | |
29 | + padding-right: 25px | |
30 | + } | |
31 | + | |
32 | + :hover { | |
33 | + opacity: 1 | |
34 | + } | |
35 | + } | |
36 | + } | |
37 | + | |
38 | + @svg subscribed { | |
39 | + width: 20px | |
40 | + height: 12px | |
41 | + content: "<circle cx='6' stroke='#FFF' fill='none' cy='6' r='5' /> <circle cx='6' cy='6' r='3' fill='#FFF'/>" | |
42 | + } | |
43 | +} |
old_styles/patchbay-tweaks.css | ||
---|---|---|
@@ -1,0 +1,59 @@ | ||
1 | +.scroller__wrapper { | |
2 | + width: 600px; | |
3 | + padding: 20px; | |
4 | +} | |
5 | + | |
6 | +.compose__controls > input[type=file] { | |
7 | + flex: 1; | |
8 | + padding: 5px; | |
9 | +} | |
10 | + | |
11 | +a.avatar { | |
12 | + display: inline; | |
13 | + font-weight: bold; | |
14 | + color: #222 | |
15 | +} | |
16 | + | |
17 | +div.avatar > a.avatar { | |
18 | + display: flex; | |
19 | + font-size: 120%; | |
20 | +} | |
21 | + | |
22 | +img.emoji { | |
23 | + width: 1.5em; | |
24 | + height: 1.5em; | |
25 | + align-content: center; | |
26 | + margin-top: -0.2em; | |
27 | +} | |
28 | + | |
29 | +div.compose textarea { | |
30 | + transition: height 0.1s | |
31 | +} | |
32 | + | |
33 | +div.lightbox { | |
34 | + box-shadow: #5f5f5f 1px 2px 100px; | |
35 | + bottom: inherit !important; | |
36 | + overflow: hidden !important; | |
37 | +} | |
38 | + | |
39 | +div.suggest-box { | |
40 | + background: #333; | |
41 | +} | |
42 | + | |
43 | +div.suggest-box ul { | |
44 | + left: 390px; | |
45 | + top: 87px; | |
46 | + position: fixed; | |
47 | + padding: 5px; | |
48 | + border-radius: 3px; | |
49 | + background: #fdfdfd; | |
50 | + border: 1px solid #CCC; | |
51 | +} | |
52 | + | |
53 | +div.suggest-box li { | |
54 | + padding: 3px; | |
55 | +} | |
56 | + | |
57 | +div.suggest-box strong { | |
58 | + font-weight: normal; | |
59 | +} |
old_styles/profile-list.mcss | ||
---|---|---|
@@ -1,0 +1,51 @@ | ||
1 | +ProfileList { | |
2 | + a.profile { | |
3 | + display: flex; | |
4 | + padding: 4px; | |
5 | + font-size: 110%; | |
6 | + margin: 4px 0; | |
7 | + background: rgba(255, 255, 255, 0.22); | |
8 | + border-radius: 5px; | |
9 | + position: relative | |
10 | + text-decoration: none | |
11 | + transition: background-color 0.2s | |
12 | + | |
13 | + background-repeat: no-repeat | |
14 | + background-position: right | |
15 | + | |
16 | + -connected { | |
17 | + background-image: svg(connected) | |
18 | + } | |
19 | + | |
20 | + @svg connected { | |
21 | + width: 20px | |
22 | + height: 12px | |
23 | + content: "<circle cx='6' stroke='none' fill='green' cy='6' r='5' />" | |
24 | + } | |
25 | + | |
26 | + :hover { | |
27 | + background-color: rgba(255, 255, 255, 0.4); | |
28 | + } | |
29 | + | |
30 | + div.avatar { | |
31 | + img { | |
32 | + width: 40px | |
33 | + height: 40px | |
34 | + } | |
35 | + } | |
36 | + | |
37 | + div.main { | |
38 | + display: flex | |
39 | + flex-direction: column | |
40 | + flex: 1 | |
41 | + margin-left: 10px | |
42 | + justify-content: center | |
43 | + div.name { | |
44 | + font-weight: bold | |
45 | + font-size: 110% | |
46 | + color: #333 | |
47 | + -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,1) 90%, rgba(0,0,0,0)) | |
48 | + } | |
49 | + } | |
50 | + } | |
51 | +} |
old_styles/split-view.mcss | ||
---|---|---|
@@ -1,0 +1,28 @@ | ||
1 | +SplitView { | |
2 | + display: flex | |
3 | + flex: 3 | |
4 | + div.main { | |
5 | + display: flex | |
6 | + flex-direction: column | |
7 | + flex: 1 | |
8 | + } | |
9 | + div.side { | |
10 | + min-width: 280px; | |
11 | + padding: 20px; | |
12 | + background: linear-gradient(100deg, #cee4ef, #efebeb); | |
13 | + border-right: 1px solid #dcdcdc; | |
14 | + overflow-y: auto; | |
15 | + | |
16 | + h2 { | |
17 | + margin-top: 20px | |
18 | + margin-bottom: 8px | |
19 | + color: #527b90; | |
20 | + text-shadow: 0px 0px 3px #fff; | |
21 | + font-weight: normal; | |
22 | + span.sub { | |
23 | + font-weight: normal | |
24 | + font-size: 90% | |
25 | + } | |
26 | + } | |
27 | + } | |
28 | +} |
Built with git-ssb-web