git ssb

3+

ev / decent



Commit 26964367b4576851356b5e7a87124ef6c8562dd2

decent 5: the garden and the wall

Ev Bogue committed on 11/17/2018, 6:35:42 PM
Parent: 4f422818b33f5b47c419b382a3fb55aeb837b014

Files changed

bin.jschanged
config/inject.jschanged
package-lock.jsonchanged
package.jsonchanged
readme.mdchanged
avatar.jsadded
decent.pngdeleted
compose.jsadded
config.jsadded
index.jsadded
keys.jsadded
manifest.jsonadded
mvd-indexes.jsadded
mvd.pngadded
render.jsadded
scuttlebot.jsadded
ssb-ws/.travis.ymladded
ssb-ws/LICENSEadded
ssb-ws/README.mdadded
ssb-ws/index.jsadded
ssb-ws/json-api.jsadded
ssb-ws/package.jsonadded
style.cssadded
style.css.jsonadded
style.jsadded
tools.jsadded
views.jsadded
bin.jsView
@@ -1,64 +1,106 @@
1-var fs = require('fs')
2-var path = require('path')
3-var ssbKeys = require('ssb-keys')
4-var stringify = require('pull-stringify')
5-var yargs = require('yargs').argv
1 +#! /usr/bin/env node
2 +
3 +var fs = require('fs')
4 +var path = require('path')
5 +var pull = require('pull-stream')
6 +var toPull = require('stream-to-pull-stream')
7 +var File = require('pull-file')
8 +var explain = require('explain-error')
9 +var ssbKeys = require('ssb-keys')
10 +var stringify = require('pull-stringify')
11 +var createHash = require('multiblob/util').createHash
12 +var minimist = require('minimist')
13 +var muxrpcli = require('muxrpcli')
14 +var cmdAliases = require('scuttlebot/lib/cli-cmd-aliases')
15 +var ProgressBar = require('scuttlebot/lib/progress')
16 +var packageJson = require('scuttlebot/package.json')
617 var open = require('opn')
7-var muxrpcli = require('muxrpcli')
818
9-var config = require('./config/inject')(yargs.appname || 'decent')
19 +//get config as cli options after --, options before that are
20 +//options to the command.
21 +var argv = process.argv.slice(2)
22 +var i = argv.indexOf('--')
23 +var conf = argv.slice(i+1)
24 +argv = ~i ? argv.slice(0, i) : argv
1025
11-config.keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret'))
26 +var config = require('./config/inject')(process.env.ssb_appname, minimist(conf))
1227
28 +var keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret'))
29 +if(keys.curve === 'k256')
30 + throw new Error('k256 curves are no longer supported,'+
31 + 'please delete' + path.join(config.path, 'secret'))
32 +
33 +var compiledClient = fs.readFileSync(path.join('./build/index.html'))
34 +
1335 var manifestFile = path.join(config.path, 'manifest.json')
1436
15-var decentClient = fs.readFileSync(path.join('./build/index.html'))
37 +if (argv[0] == 'server') {
38 + console.log(packageJson.name, packageJson.version, config.path, 'logging.level:'+config.logging.level)
39 + console.log('my key ID:', keys.public)
1640
17-var argv = process.argv.slice(2)
18-var i = argv.indexOf('--')
19-var conf = argv.slice(i+1)
20-argv = ~i ? argv.slice(0, i) : argv
41 + // special server command:
42 + // import sbot and start the server
2143
22-if (argv[0] == 'server') {
2344 var createSbot = require('scuttlebot')
45 + .use(require('scuttlebot/plugins/unix-socket'))
46 + .use(require('scuttlebot/plugins/no-auth'))
47 + .use(require('scuttlebot/plugins/plugins'))
2448 .use(require('scuttlebot/plugins/master'))
2549 .use(require('scuttlebot/plugins/gossip'))
2650 .use(require('scuttlebot/plugins/replicate'))
2751 .use(require('ssb-friends'))
2852 .use(require('ssb-blobs'))
53 + .use(require('scuttlebot/plugins/invite'))
54 + .use(require('scuttlebot/plugins/local'))
55 + .use(require('scuttlebot/plugins/logging'))
2956 .use(require('ssb-query'))
57 + .use(require('ssb-backlinks'))
58 + .use(require('ssb-search'))
59 + .use(require('./mvd-indexes'))
3060 .use(require('ssb-links'))
31- .use(require('ssb-ebt'))
32- .use(require('scuttlebot/plugins/invite'))
33- .use(require('scuttlebot/plugins/local'))
34- .use(require('decent-ws'))
61 + .use(require('./ssb-ws'))
62 + //.use(require('ssb-ebt'))
3563 .use({
3664 name: 'serve',
3765 version: '1.0.0',
3866 init: function (sbot) {
3967 sbot.ws.use(function (req, res, next) {
40- var send = {}
41- send = config
68 + var send = config
4269 delete send.keys
43- send.address = sbot.ws.getAddress()
70 + /*sbot.invite.create(1, function (err, cb) {
71 + send.invite = cb
72 + })*/
73 + send.address = sbot.getAddress()
4474 if(req.url == '/')
45- res.end(decentClient)
75 + res.end(compiledClient)
4676 if(req.url == '/get-config')
4777 res.end(JSON.stringify(send))
4878 else next()
4979 })
5080 }
5181 })
52-
82 + // add third-party plugins
83 + require('scuttlebot/plugins/plugins').loadUserPlugins(createSbot, config)
84 +
85 + // start server
86 +
5387 open('http://localhost:' + config.ws.port, {wait: false})
54-
88 +
89 + config.keys = keys
5590 var server = createSbot(config)
56-
91 +
92 + // write RPC manifest to ~/.ssb/manifest.json
5793 fs.writeFileSync(manifestFile, JSON.stringify(server.getManifest(), null, 2))
5894
95 + if(process.stdout.isTTY && (config.logging.level != 'info'))
96 + ProgressBar(server.progress)
5997 } else {
6098
99 + // normal command:
100 + // create a client connection to the server
101 +
102 + // read manifest.json
61103 var manifest
62104 try {
63105 manifest = JSON.parse(fs.readFileSync(manifestFile))
64106 } catch (err) {
@@ -68,40 +110,74 @@
68110 )
69111 }
70112
71113 // connect
72- require('ssb-client')(config.keys, {
114 + require('ssb-client')(keys, {
73115 manifest: manifest,
74116 port: config.port,
75117 host: config.host||'localhost',
76118 caps: config.caps,
77- key: config.key || config.keys.id
119 + key: config.key || keys.id
78120 }, function (err, rpc) {
79121 if(err) {
80122 if (/could not connect/.test(err.message)) {
81- console.log('Error: Could not connect to the scuttlebot server.')
82- console.log('Use the "server" command to start it.')
123 + var serverAddr = (config.host || 'localhost') + ":" + config.port;
124 + console.error('Error: Could not connect to the scuttlebot server ' + serverAddr)
125 + console.error('Use the "server" command to start it.')
83126 if(config.verbose) throw err
84127 process.exit(1)
85128 }
86129 throw err
87130 }
88131
132 + // add aliases
133 + for (var k in cmdAliases) {
134 + rpc[k] = rpc[cmdAliases[k]]
135 + manifest[k] = manifest[cmdAliases[k]]
136 + }
137 +
89138 // add some extra commands
90- manifest.version = 'async'
139 +// manifest.version = 'async'
91140 manifest.config = 'sync'
92- rpc.version = function (cb) {
93- console.log(require('./package.json').version)
94- cb()
95- }
141 +// rpc.version = function (cb) {
142 +// console.log(packageJson.version)
143 +// cb()
144 +// }
96145 rpc.config = function (cb) {
97146 console.log(JSON.stringify(config, null, 2))
98147 cb()
99148 }
100149
150 + // HACK
151 + // we need to output the hash of blobs that are added via blobs.add
152 + // because muxrpc doesnt support the `sink` callback yet, we need this manual override
153 + // -prf
154 + if (process.argv[2] === 'blobs.add') {
155 + var filename = process.argv[3]
156 + var source =
157 + filename ? File(process.argv[3])
158 + : !process.stdin.isTTY ? toPull.source(process.stdin)
159 + : (function () {
160 + console.error('USAGE:')
161 + console.error(' blobs.add <filename> # add a file')
162 + console.error(' source | blobs.add # read from stdin')
163 + process.exit(1)
164 + })()
165 + var hasher = createHash('sha256')
166 + pull(
167 + source,
168 + hasher,
169 + rpc.blobs.add(function (err) {
170 + if (err)
171 + throw err
172 + console.log('&'+hasher.digest)
173 + process.exit()
174 + })
175 + )
176 + return
177 + }
178 +
101179 // run commandline flow
102180 muxrpcli(argv, manifest, rpc, config.verbose)
103181 })
104182 }
105183
106-
107-
config/inject.jsView
@@ -1,83 +1,73 @@
11 var path = require('path')
22 var home = require('os-homedir')
3 +
34 var nonPrivate = require('non-private-ip')
45 var merge = require('deep-extend')
5-var id = require('ssb-keys')
6 +
67 var RC = require('rc')
8 +
79 var SEC = 1e3
810 var MIN = 60*SEC
911
1012 module.exports = function (name, override) {
11- if(name == null)
12- name = 'decent'
13-
14- console.log('Using the ' + name + ' config')
15-
16- var network
17-
18- if (name === 'ssb') {
19- network = {
20- port: 8008,
21- ws: {
22- port: 8989
23- },
24- caps: {
25- shs: '1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=',
26- sign: null
27- }
28- }
29- }
30-
31- if (name === 'decent') {
32- network = {
33- port: 3333,
34- ws: {
35- port: 3939
36- },
37- caps: {
38- shs: 'EVRctE2Iv8GrO/BpQCF34e2FMPsDJot9x0j846LjVtc=',
39- sign: null
40- }
41- }
42- }
43-
44- if (name === 'testnet') {
45- network = {
46- port: 9999,
47- ws: {
48- port: 9191
49- },
50- caps: {
51- shs: 'sR74I0+OW6LBYraQQ2YtFtqV5Ns77Tv5DyMfyWbrlpI=',
52- sign: null
53- }
54- }
55- }
56-
13 + name = name || 'ssb'
5714 var HOME = home() || 'browser' //most probably browser
58-
59- return RC(name, merge(network, {
60- name: name,
61- //standard stuff that probably doesn't need to change below
15 + var result = RC(name || 'ssb', merge({
16 + //just use an ipv4 address by default.
17 + //there have been some reports of seemingly non-private
18 + //ipv6 addresses being returned and not working.
19 + //https://github.com/ssbc/scuttlebot/pull/102
20 + allowPrivate: true,
21 + party: true,
6222 host: nonPrivate.v4 || '',
23 + port: 8008,
6324 timeout: 0,
6425 pub: true,
6526 local: true,
6627 friends: {
6728 dunbar: 150,
68- hops: 3
29 + hops: 1
6930 },
31 + ws: {
32 + port: 8989
33 + },
7034 gossip: {
7135 connections: 3
7236 },
37 + connections: {
38 + outgoing: {
39 + net: [{ transform: "shs" }]
40 + }
41 + },
7342 path: path.join(HOME, '.' + name),
7443 timers: {
7544 connection: 0,
7645 reconnect: 5*SEC,
7746 ping: 5*MIN,
7847 handshake: 5*SEC
7948 },
49 + //change these to make a test network that will not connect to the main network.
50 + caps: {
51 + //this is the key for accessing the ssb protocol.
52 + //this will be updated whenever breaking changes are made.
53 + //(see secret-handshake paper for a full explaination)
54 + //(generated by crypto.randomBytes(32).toString('base64'))
55 + shs: '1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=',
56 +
57 + //used to sign messages
58 + sign: null
59 + },
8060 master: [],
81- party: true //disable quotas
61 + logging: { level: 'notice' }
8262 }, override || {}))
63 +
64 + if (!result.connections.incoming) {
65 + result.connections.incoming = {
66 + net: [{ host: result.host, port: result.port, scope: "public", "transform": "shs" }],
67 + ws: [{ host: result.host, port: result.ws.port, scope: "public", "transform": "shs" }]
68 + }
69 + }
70 + return result
8371 }
72 +
73 +
package-lock.jsonView
The diff is too large to show. Use a local git client to view these changes.
Old file size: 215219 bytes
New file size: 236923 bytes
package.jsonView
@@ -1,56 +1,60 @@
11 {
2- "name": "decent-ssb",
3- "version": "4.5.0",
4- "description": "A decent(tralized) network for communication and development",
2 + "name": "mvd",
3 + "version": "1.14.1",
4 + "description": "minimum viable decent",
5 + "main": "index.js",
56 "scripts": {
67 "start": "node bin server",
8 + "decent": "node bin server --appname=decent",
79 "ssb": "node bin server --appname=ssb",
8- "build": "mkdir -p build && browserify node_modules/minbase/index.js | indexhtmlify > build/index.html",
9- "fonts": "git clone ssb://%XoC0EPXVomSYODRk0g9w6iGuD/vPCjinTVVW6Dz1hEQ=.sha256 build/ssp"
10 + "testnet": "node bin server --appname=testnet",
11 + "build": "node style.js && mkdir -p build && browserify index.js | indexhtmlify > build/index.html"
1012 },
11- "author": "Ev Bogue",
13 + "devDependencies": {
14 + "browserify": "^16.2.3",
15 + "indexhtmlify": "^1.3.1"
16 + },
17 + "author": "Ev Bogue <ev@evbogue.com>",
1218 "license": "MIT",
1319 "dependencies": {
14- "decent-ws": "^1.0.0",
15- "deep-extend": "^0.5.0",
16- "emoji-server": "^1.0.0",
17- "minbase": "^3.5.1",
18- "multiblob-http": "^0.3.1",
19- "multiserver": "^1.10.0",
20- "muxrpc": "^6.3.3",
20 + "dataurl-": "^0.1.0",
21 + "decent-ws": "1.0.4",
22 + "deep-extend": "^0.6.0",
23 + "diff": "^3.5.0",
24 + "ecstatic": "^3.3.0",
25 + "flumeview-query": "^7.1.0",
26 + "human-time": "0.0.1",
27 + "hyperfile": "^2.0.0",
28 + "hyperloadmore": "^1.1.0",
29 + "hyperscript": "^2.0.2",
30 + "hyperscroll": "^1.0.0",
2131 "muxrpcli": "^1.1.0",
22- "non-private-ip": "^1.4.2",
23- "opn": "^5.2.0",
32 + "non-private-ip": "^1.4.4",
33 + "opn": "^5.4.0",
2434 "os-homedir": "^1.0.2",
25- "pull-stream": "^3.4.5",
35 + "pull-more": "^1.1.0",
36 + "pull-next-query": "^1.0.0",
37 + "pull-reconnect": "0.0.3",
38 + "pull-stream": "^3.6.9",
2639 "pull-stringify": "^2.0.0",
27- "rc": "^1.2.1",
28- "scuttlebot": "^11.3.0",
29- "secure-scuttlebutt": "^18.0.5",
30- "ssb-blobs": "^1.1.4",
31- "ssb-client": "^4.5.3",
32- "ssb-ebt": "^5.1.4",
33- "ssb-friends": "^2.4.0",
34- "ssb-keys": "^7.0.13",
35- "ssb-links": "^3.0.0",
36- "ssb-names": "^3.1.1",
37- "ssb-query": "^2.0.0",
38- "ssb-ref": "^2.9.1",
39- "stack": "^0.1.0",
40- "yargs": "^11.0.0"
41- },
42- "devDependencies": {
43- "browserify": "^14.4.0",
44- "indexhtmlify": "^1.3.1"
45- },
46- "bin": {
47- "decent": "./bin.js"
48- },
49- "pkg": {
50- "assets": [
51- "node_modules/emoji-named-characters/pngs",
52- "build/",
53- "node_modules/opn"
54- ]
40 + "rc": "^1.2.8",
41 + "scuttlebot": "^13.0.3",
42 + "simple-mime": "^0.1.0",
43 + "split-buffer": "^1.0.0",
44 + "ssb-avatar": "^0.2.0",
45 + "ssb-backlinks": "^0.7.3",
46 + "ssb-blobs": "^1.1.6",
47 + "ssb-client": "^4.6.0",
48 + "ssb-ebt": "^5.2.7",
49 + "ssb-feed": "^2.3.0",
50 + "ssb-friends": "^3.1.6",
51 + "ssb-keys": "^7.1.3",
52 + "ssb-links": "^3.0.3",
53 + "ssb-markdown": "^3.6.0",
54 + "ssb-mentions": "^0.5.0",
55 + "ssb-query": "^2.3.0",
56 + "ssb-ref": "^2.13.6",
57 + "ssb-search": "^1.1.2",
58 + "visualize-buffer": "0.0.1"
5559 }
5660 }
readme.mdView
@@ -1,47 +1,38 @@
1-# Decent
1 +# decent 5
22
3-### A decent(ralized) network for business and development
3 +### The Garden and The Wall
44
5-#### A learning fork and altnet that combines [Scuttlebot](http://github.com/ssbc/scuttlebot) and [Minbase](http://github.com/evbogue/minbase)
5 +**What** Decent is a secure scuttlebutt server for your website. It hosts a minimal lite client so that visitors to your website can use [secure-scuttlebutt](http://scuttlebot.io) to write their own posts, and write on your wall.
66
7-In the beginning the web was distributed. Then companies in the valley centralized it for their own profit, impoverishing the creative class of the Internet. We're creating a decent alternative.
7 +**Why?** The reason I started developing with the original [scuttlebutt](http://github.com/dominictarr/scuttlebutt) back in 2012 was because I wanted a distributed social network running on my website. Decent attempts to be a social network for your website by leveraging ssb's distributed database, with a few modifications:
88
9-![Decent screenshot](decent.png)
9 ++ A Decent pub is also the moderator. This means whoever controls the Decent pub decides what it wants to replicate. We accomplish this by setting `friends: { hops: 1}` in the config. This keeps the amount of information on a Decent pub manageable by only showing a corner of the scuttleverse, instead of all of it
10 ++ Limiting it to 1 hop makes the database pretty small, because you're not syncing the entire scuttleverse
11 ++ While anyone can view and post to a Decent pub, new users won't replicate by default. Someone will have to follow them for them to replicate across the scuttleverse
12 ++ No private messages. If you want private messages, install [mvd](http://github.com/evbogue/mvd) on your computer
13 ++ Everyone gets a 'Wall' in Decent, making the UI easy for new users to write messages to each other or to the website creator
14 ++ Decent has mutable messages, so anyone can edit their messages. There is no 'delete' on Decent, because ssb is an append only log, and I don't want to give anyone the false sense of thinking that deleted messages actually go away. They do not.
1015
11-Decent is based on [Scuttlebot](http://scuttlebot.io), but uses an alternative network key: `EVRctE2Iv8GrO/BpQCF34e2FMPsDJot9x0j846LjVtc=`.
16 +Setting Decent up this way makes it a Garden (because your pub curates the data hosted on it by only showing users followed by the pub) and The Wall (gives users an easy way to talk to you from your website)
1217
13-Decent combines all of the necessary parts of Scuttlebot for a simpler install process
18 +In some ways Decent is a compromise, because the people who use Decent are not full peers on the network. However, I hope to encourage people to install a full ssb client on their desktop by limiting access to private messages on Decent pubs. In an ideal world everyone would run their own ssb server on their own computer, but we need to face that we are not living in an ideal world where everyone has the technical ability to install a complicated distributed social network written in Node.js on their computer right now.
1419
15-### Try Decent in your browser
20 +In an ideal world Decent would include two features that are not available right now:
1621
17-+ http://decent.gitmx.com/
18-+ http://decent.evbogue.com/
19-+ http://decent.gwenbell.com/
22 ++ A full peer in the browser
23 ++ The ability to easily delete a feed with the click of a single button
2024
21-### Install Decent on your local or vps
25 +Right now the server has to run on a VPS, and to delete someone's feed you need to unfollow and/or block a client and then resync your scuttlebot database from scratch.
2226
23-```
24-% git clone http://github.com/evbogue/decent.git
25-% cd decent && npm install
26-% npm run build
27-% npm start
28-```
27 +Try Decent at http://decent.evbogue.com/
2928
30-Navigate to http://localhost:3001/ to see your Decent.
29 +Remember! The first rule of Decent is: be decent.
3130
32-### Embed Decent on your website
31 +### history
3332
34-```
35-<iframe src="http://decent.gitmx.com/" style="width: 100%; border: none; height: 24em;"></iframe>
36-```
33 +`decent` is a fork of [mvd](http://github.com/evbogue/mvd)
3734
38-If you're deploying Decent on your local or your own VPS, you will need a follow from an existing Decent pub. Please request an invite: [ev@evbogue.com](mailto:ev@evbogue.com)
35 +In previous versions of Decent it was an 'altnet'. Decent now uses the main network key, and only shows a corner of the scuttleverse.
3936
40-Once you're on Decent, be sure to obey the first rule:
41-
42-1. Be decent
43-
44-Decent is maintained by [Ev Bogue](http://evbogue.com), based on the work of [Dominic Tarr](http://dominictarr.com).
45-
46-
47-
37 +---
38 +MIT
avatar.jsView
@@ -1,0 +1,92 @@
1 +var pull = require('pull-stream')
2 +var query = require('./scuttlebot').query
3 +var h = require('hyperscript')
4 +var visualize = require('visualize-buffer')
5 +
6 +var avatar = require('ssb-avatar')
7 +
8 +var sbot = require('./scuttlebot')
9 +
10 +var config = require('./config')()
11 +
12 +var id = require('./keys').id
13 +
14 +var ref = require('ssb-ref')
15 +
16 +module.exports.name = function (key) {
17 +
18 + var avatarname = h('span', key.substring(0, 10))
19 + if (ref.isFeedId(key)) {
20 + avatar(sbot, id, key, function (err, data) {
21 + if (err) throw err
22 + if (data.name) {
23 + if (data.name[0] != '@') {
24 + var name = '@' + data.name
25 + } else {
26 + var name = data.name
27 + }
28 + localStorage[key + 'name'] = name
29 + avatarname.textContent = name
30 + }
31 + })
32 + }
33 + return avatarname
34 +}
35 +
36 +module.exports.image = function (key) {
37 + var img = visualize(new Buffer(key.substring(1), 'base64'), 256)
38 +
39 + if (ref.isFeedId(key)) {
40 + avatar(sbot, id, key, function (err, data) {
41 + if (err) throw err
42 + if (data.image) {
43 + localStorage[key + 'image'] = data.image
44 + img.src = config.blobsUrl + data.image
45 + }
46 + })
47 + }
48 + return img
49 +}
50 +
51 +module.exports.cachedName = function (key) {
52 + var avatarname = h('span', key.substring(0, 10))
53 +
54 + if (localStorage[key + 'name']) {
55 + avatarname.textContent = localStorage[key + 'name']
56 + } else {
57 + if (ref.isFeedId(key)) {
58 + avatar(sbot, id, key, function (err, data) {
59 + if (data.name) {
60 + if (data.name[0] != '@') {
61 + var name = '@' + data.name
62 + } else {
63 + var name = data.name
64 + }
65 + localStorage[key + 'name'] = name
66 + avatarname.textContent = name
67 + }
68 + })
69 + }
70 + }
71 +
72 + return avatarname
73 +}
74 +
75 +module.exports.cachedImage = function (key) {
76 + var img = visualize(new Buffer(key.substring(1), 'base64'), 256)
77 +
78 + if (localStorage[key + 'image']) {
79 + img.src = config.blobsUrl + localStorage[key + 'image']
80 + } else {
81 + if (ref.isFeedId(key)) {
82 + avatar(sbot, id, key, function (err, data) {
83 + if (data.image) {
84 + localStorage[key + 'image'] = data.image
85 + img.src = config.blobsUrl + data.image
86 + }
87 + })
88 + }
89 + }
90 +
91 + return img
92 +}
decent.png
decent.png
compose.jsView
@@ -1,0 +1,187 @@
1 +var h = require('hyperscript')
2 +var pull = require('pull-stream')
3 +var sbot = require('./scuttlebot')
4 +var human = require('human-time')
5 +var id = require('./keys').id
6 +var mentions = require('ssb-mentions')
7 +
8 +var avatar = require('./avatar')
9 +var tools = require('./tools')
10 +
11 +var mime = require('simple-mime')('application/octect-stream')
12 +var split = require('split-buffer')
13 +
14 +var route = require('./views')
15 +
16 +function file_input (onAdded) {
17 + return h('label.btn', 'Upload file',
18 + h('input', { type: 'file', hidden: true,
19 + onchange: function (ev) {
20 + var file = ev.target.files[0]
21 + if (!file) return
22 + var reader = new FileReader()
23 + reader.onload = function () {
24 + pull(
25 + pull.values(split(new Buffer(reader.result), 64*1024)),
26 + sbot.addblob(function (err, blob) {
27 + if(err) return console.error(err)
28 + onAdded({
29 + link: blob,
30 + name: file.name,
31 + size: reader.result.length || reader.result.byteLength,
32 + type: mime(file.name)
33 + })
34 + })
35 + )
36 + }
37 + reader.readAsArrayBuffer(file)
38 + }
39 + }))
40 +}
41 +
42 +module.exports = function (opts, fallback) {
43 + var files = []
44 + var filesById = {}
45 +
46 + var composer = h('div.composer')
47 + var container = h('div.container')
48 + if (opts.boostAuthor) {
49 + var boostName = avatar.cachedName(opts.boostAuthor)
50 + }
51 + if (opts.boostContent) {
52 + var textarea = h('textarea.compose')
53 + var str = opts.boostContent
54 + var lines = str.split("\n")
55 + for(var i=0; i<lines.length; i++) {
56 + lines[i] = "> " + lines[i]
57 + }
58 + var newContent = lines.join("\n")
59 + var content = 'Boosting: ' + opts.boostKey + '\n\n' + newContent + '-[' + boostName.textContent + ']('+ opts.boostAuthor + ')'
60 + textarea.value = content
61 + }
62 +
63 + else if (opts.mentions) {
64 + var textarea = h('textarea.compose', opts.mentions)
65 + }
66 +
67 + else if (opts.type == 'wiki')
68 + var textarea = h('textarea.compose', {placeholder: opts.placeholder || 'Write a wiki (anyone can edit)'})
69 + else if (opts.type == 'post')
70 + var textarea = h('textarea.compose', {placeholder: opts.placeholder || 'Write a message (only you can edit)'})
71 + else
72 + var textarea = h('textarea.compose', {placeholder: opts.placeholder || 'Write a message (only you can edit)'}, fallback.messageText)
73 +
74 + var cancelBtn = h('button.btn', 'Cancel', {
75 + onclick: function () {
76 + var cancel
77 + console.log(opts)
78 +
79 + if (opts.type == 'edit') {
80 + cancel = document.getElementById('edit:' + opts.branch.substring(0,44))
81 + var oldMessage = h('div.message__body', tools.markdown(fallback.messageText))
82 + cancel.parentNode.replaceChild(oldMessage, cancel)
83 + oldMessage.parentNode.appendChild(fallback.buttons)
84 + } else if (opts.branch) {
85 + //cancel reply composer
86 + cancel = document.getElementById('re:' + opts.branch.substring(0,44))
87 + cancel.parentNode.removeChild(cancel)
88 + message = document.getElementById(opts.branch.substring(0,44))
89 + message.appendChild(fallback.buttons)
90 + } else {
91 + // cancel generic composer
92 + cancel = document.getElementById('composer')
93 + cancel.parentNode.removeChild(cancel)
94 + }
95 + }
96 +
97 + })
98 +
99 + var initialButtons = h('span',
100 + h('button.btn', 'Preview', {
101 + onclick: function () {
102 + if (textarea.value) {
103 + var msg = {}
104 +
105 + msg.value = {
106 + "author": id,
107 + "content": opts
108 + }
109 +
110 + msg.value.content.text = textarea.value
111 + msg.value.content.mentions = mentions(textarea.value).map(
112 + function (mention) {
113 + var file = filesById[mention.link]
114 + if (file) {
115 + if (file.type) mention.type = file.type
116 + if (file.size) mention.size = file.size
117 + }
118 + return mention
119 + }
120 + )
121 +
122 + if (opts.recps)
123 + msg.value.private = true
124 +
125 + console.log(msg)
126 + if (opts.type == 'post' || opts.type == 'wiki')
127 + var header = tools.header(msg)
128 + if (opts.type == 'update')
129 + var header = tools.timestamp(msg, {edited: true})
130 + var preview = h('div',
131 + header,
132 + h('div.message__content', tools.markdown(msg.value.content.text)),
133 + h('button.btn', 'Publish', {
134 + onclick: function () {
135 + if (msg.value.content) {
136 + sbot.publish(msg.value.content, function (err, msg) {
137 + if(err) throw err
138 + console.log('Published!', msg)
139 + if (opts.type == 'edit') {
140 + var message = document.getElementById(opts.branch.substring(0,44))
141 + fallback.messageText = msg.value.content.text
142 + var editBody = h('div.message__body',
143 + tools.timestamp(msg, {edited: true}),
144 + h('div', tools.markdown(msg.value.content.text))
145 + )
146 +
147 + message.replaceChild(editBody, message.childNodes[message.childNodes.length - 1])
148 + editBody.parentNode.appendChild(fallback.buttons)
149 + } else {
150 + if (opts.branch)
151 + cancel = document.getElementById('re:' + opts.branch.substring(0,44))
152 + else
153 + cancel = document.getElementById('composer')
154 + cancel.parentNode.removeChild(cancel)
155 + }
156 + })
157 + }
158 + }
159 + }),
160 + h('button.btn', 'Cancel', {
161 + onclick: function () {
162 + composer.replaceChild(container, composer.firstChild)
163 + container.appendChild(textarea)
164 + container.appendChild(initialButtons)
165 + }
166 + })
167 + )
168 + composer.replaceChild(preview, composer.firstChild)
169 + }
170 + }
171 + }),
172 + file_input(function (file) {
173 + files.push(file)
174 + filesById[file.link] = file
175 + var embed = file.type.indexOf('image/') === 0 ? '!' : ''
176 + textarea.value += embed + '['+file.name+']('+file.link+')'
177 + }),
178 + cancelBtn
179 + )
180 +
181 + composer.appendChild(container)
182 + container.appendChild(textarea)
183 + container.appendChild(initialButtons)
184 +
185 + return composer
186 +}
187 +
config.jsView
@@ -1,0 +1,35 @@
1 +var http = require('http')
2 +
3 +module.exports = function () {
4 + var host = window.location.origin
5 +
6 + //var config = require('./config/inject')()
7 + function getConfig () {
8 + http.get(host + '/get-config', function (res) {
9 + res.on('data', function (data, remote) {
10 + var config = data
11 + localStorage[host] = config
12 + })
13 + })
14 + }
15 +
16 + if (localStorage[host]) {
17 + var config = JSON.parse(localStorage[host])
18 + getConfig()
19 + } else {
20 + getConfig()
21 + setTimeout(function () {
22 + location.reload()
23 + }, 1000)
24 + }
25 +
26 + config.blobsUrl = 'http://localhost:8989/blobs/get/'
27 + config.emojiUrl = 'http://localhost:8989/img/emoji/'
28 + if (config.address) {
29 + addies = config.address.split(';')
30 + config.remote = addies[1]
31 + console.log(addies[1])
32 + }
33 +
34 + return config
35 +}
index.jsView
@@ -1,0 +1,52 @@
1 +var h = require('hyperscript')
2 +var route = require('./views')
3 +var avatar = require('./avatar')
4 +
5 +var compose = require('./compose')
6 +
7 +var id = require('./keys').id
8 +
9 +document.head.appendChild(h('style', require('./style.css.json')))
10 +
11 +var screen = h('div#screen')
12 +
13 +var search = h('input.search', {placeholder: 'Search'})
14 +
15 +var nav = h('div.navbar',
16 + h('div.internal',
17 + h('li', h('a', {href: '#' + id}, h('span.avatar--small', avatar.image(id)))),
18 + h('li', h('a', {href: '#' + id}, avatar.name(id))),
19 + h('li', h('a', 'Post', {
20 + onclick: function () {
21 + if (document.getElementById('composer')) { return }
22 + else {
23 + var currentScreen = document.getElementById('screen')
24 + var opts = {}
25 + opts.type = 'post'
26 + var composer = h('div.content#composer', h('div.message', compose(opts)))
27 + if (currentScreen.firstChild.firstChild) {
28 + currentScreen.firstChild.insertBefore(composer, currentScreen.firstChild.firstChild)
29 + } else {
30 + currentScreen.firstChild.appendChild(composer)
31 + }
32 + }
33 + }
34 + })),
35 + h('li', h('a', {href: '#' }, 'All')),
36 + h('li', h('a', {href: '#wall/' + id }, 'Wall')),
37 + h('li', h('a', {href: '#key' }, 'Key')),
38 + h('li.right', h('a', {href: 'http://github.com/evbogue/decent'}, '?')),
39 + )
40 +)
41 +
42 +document.body.appendChild(nav)
43 +document.body.appendChild(screen)
44 +route()
45 +
46 +window.onhashchange = function () {
47 + var oldscreen = document.getElementById('screen')
48 + var newscreen = h('div#screen')
49 + oldscreen.parentNode.replaceChild(newscreen, oldscreen)
50 + route()
51 +}
52 +
keys.jsView
@@ -1,0 +1,6 @@
1 +
2 +var config = require('./config')()
3 +var ssbKeys = require('ssb-keys')
4 +var path = require('path')
5 +
6 +module.exports = ssbKeys.loadOrCreateSync(path.join(config.caps.shs + '/secret'))
manifest.jsonView
@@ -1,0 +1,86 @@
1 +{
2 + "auth": "async",
3 + "address": "sync",
4 + "manifest": "sync",
5 + "get": "async",
6 + "createFeedStream": "source",
7 + "createLogStream": "source",
8 + "messagesByType": "source",
9 + "createHistoryStream": "source",
10 + "createUserStream": "source",
11 + "links": "source",
12 + "relatedMessages": "async",
13 + "add": "async",
14 + "publish": "async",
15 + "getAddress": "sync",
16 + "getLatest": "async",
17 + "latest": "source",
18 + "latestSequence": "async",
19 + "whoami": "sync",
20 + "usage": "sync",
21 + "gossip": {
22 + "peers": "sync",
23 + "add": "sync",
24 + "ping": "duplex",
25 + "connect": "async",
26 + "changes": "source",
27 + "reconnect": "sync"
28 + },
29 + "friends": {
30 + "all": "async",
31 + "hops": "async",
32 + "createFriendStream": "source",
33 + "get": "sync"
34 + },
35 + "replicate": {
36 + "changes": "source"
37 + },
38 + "invite": {
39 + "create": "async",
40 + "accept": "async",
41 + "use": "async"
42 + },
43 + "block": {
44 + "isBlocked": "sync"
45 + },
46 + "names": {
47 + "get": "async",
48 + "getImages": "async",
49 + "getImageFor": "async",
50 + "getSignifier": "async",
51 + "getSignifies": "async",
52 + "dump": "sync"
53 + },
54 + "private": {
55 + "publish": "async",
56 + "unbox": "sync"
57 + },
58 + "blobs": {
59 + "get": "source",
60 + "add": "sink",
61 + "ls": "source",
62 + "has": "async",
63 + "size": "async",
64 + "meta": "async",
65 + "want": "async",
66 + "push": "async",
67 + "changes": "source",
68 + "createWants": "source"
69 + },
70 + "links2": {
71 + "read": "source",
72 + "dump": "source"
73 + },
74 + "backlinks": {
75 + "read": "source",
76 + "dump": "source"
77 + },
78 + "query": {
79 + "read": "source",
80 + "dump": "source"
81 + },
82 + "ws": {},
83 + "search": {
84 + "query": "source"
85 + }
86 +}
mvd-indexes.jsView
@@ -1,0 +1,20 @@
1 +var Indexes = require('flumeview-query/indexes')
2 +var pkg = require('./package.json')
3 +exports.name = 'mvd-indexes'
4 +exports.version = pkg.version
5 +exports.manifest = {}
6 +
7 +exports.init = function (sbot, config) {
8 +
9 + var view =
10 + sbot._flumeUse('query/mvd', Indexes(1, {
11 + indexes: [
12 + {key: 'chr', value: [['value', 'timestamp' ]]}
13 + ]
14 + }))
15 +
16 + var indexes = view.indexes()
17 + sbot.query.add(indexes[0])
18 +
19 + return {}
20 +}
mvd.png
mvd.png
render.jsView
@@ -1,0 +1,277 @@
1 +var h = require('hyperscript')
2 +var pull = require('pull-stream')
3 +var human = require('human-time')
4 +
5 +var sbot = require('./scuttlebot')
6 +var composer = require('./compose')
7 +var tools = require('./tools')
8 +
9 +var config = require('./config')()
10 +var id = require('./keys').id
11 +var avatar = require('./avatar')
12 +var ssbAvatar = require('ssb-avatar')
13 +
14 +var diff = require('diff')
15 +
16 +function hash () {
17 + return window.location.hash.substring(1)
18 +}
19 +
20 +module.exports = function (msg) {
21 + var message = h('div.message#' + msg.key.substring(0, 44))
22 +
23 + if (!localStorage[msg.value.author])
24 + var cache = {mute: false}
25 + else
26 + var cache = JSON.parse(localStorage[msg.value.author])
27 +
28 + if (cache.mute == true) {
29 + var muted = h('span', ' muted')
30 + message.appendChild(tools.mini(msg, muted))
31 + return message
32 + }
33 +
34 + else if (msg.value.content.type == 'edit') {
35 + message.appendChild(tools.header(msg))
36 + var current = msg.value.content.text
37 + sbot.get(msg.value.content.updated, function (err, updated) {
38 + if (updated) {
39 + fragment = document.createDocumentFragment()
40 + var previous = updated.content.text
41 + var ready = diff.diffWords(previous, current)
42 + ready.forEach(function (part) {
43 + if (part.added === true) {
44 + color = 'blue'
45 + } else if (part.removed === true) {
46 + color = 'gray'
47 + } else {color = '#333'}
48 + var span = h('span')
49 + span.style.color = color
50 + if (part.removed === true) {
51 + span.appendChild(h('del', document.createTextNode(part.value)))
52 + } else {
53 + span.appendChild(document.createTextNode(part.value))
54 + }
55 + fragment.appendChild(span)
56 + })
57 + message.appendChild(h('code', fragment))
58 + }
59 + })
60 + return message
61 + }
62 +
63 +
64 + else if (msg.value.content.type == 'scat_message') {
65 + var src = hash()
66 + if (src != 'backchannel') {
67 + message.appendChild(h('button.btn.right', h('a', {href: '#backchannel'}, 'Chat')))
68 + }
69 + message.appendChild(tools.mini(msg, ' ' + msg.value.content.text))
70 + return message
71 + }
72 + else if (msg.value.content.type == 'contact') {
73 + if (msg.value.content.contact) {
74 + var contact = h('a', {href: '#' + msg.value.content.contact}, avatar.name(msg.value.content.contact))
75 + } else { var contact = h('p', 'no contact named')}
76 +
77 + if (msg.value.content.following == true) {
78 + var following = h('span', ' follows ', contact)
79 + message.appendChild(tools.mini(msg, following))
80 + }
81 + if (msg.value.content.following == false) {
82 + var unfollowing = h('span', ' unfollows ', contact)
83 + message.appendChild(tools.mini(msg, unfollowing))
84 + }
85 + if (msg.value.content.blocking == true) {
86 + var blocking = h('span', ' blocks ', contact)
87 + message.appendChild(tools.mini(msg, blocking))
88 + }
89 + if (msg.value.content.blocking == false) {
90 + var unblocking = h('span', ' unblocks ', contact)
91 + message.appendChild(tools.mini(msg, unblocking))
92 + }
93 + return message
94 +
95 + }
96 +
97 + else if (msg.value.content.type == 'git-update') {
98 +
99 + message.appendChild(tools.header(msg))
100 +
101 + var reponame = h('p', 'pushed to ', h('a', {href: '#' + msg.value.content.repo}, msg.value.content.repo))
102 +
103 + var cloneurl = h('pre', 'git clone ssb://' + msg.value.content.repo)
104 +
105 + message.appendChild(reponame)
106 +
107 +
108 + ssbAvatar(sbot, id, msg.value.content.repo, function (err, data) {
109 + if (data) {
110 + var actualname = h('p', 'pushed to ', h('a', {href: '#' + msg.value.content.repo}, '%' + data.name))
111 + reponame.parentNode.replaceChild(actualname, reponame)
112 + }
113 + })
114 +
115 + message.appendChild(cloneurl)
116 +
117 + var commits = h('ul')
118 + //if (msg.value.content.commits[0]) {
119 + if (msg.value.content.commits) {
120 + msg.value.content.commits.map(function (commit) {
121 + commits.appendChild(h('li', h('code', commit.sha1), ' - ', commit.title))
122 + })
123 +
124 + }
125 +
126 + message.appendChild(commits)
127 +
128 + return message
129 +
130 + }
131 + else if (msg.value.content.type == 'git-repo') {
132 + message.appendChild(tools.header(msg))
133 +
134 + var reponame = h('p', 'git-ssb repo ', h('a', {href: '#' + msg.key}, msg.key))
135 +
136 + message.appendChild(reponame)
137 +
138 + ssbAvatar(sbot, id, msg.key, function (err, data) {
139 + if (data)
140 + var actualname = h('p', 'git-ssb repo ', h('a', {href: '#' + msg.key}, '%' + data.name))
141 + reponame.parentNode.replaceChild(actualname, reponame)
142 + })
143 +
144 + var cloneurl = h('pre', 'git clone ssb://' + msg.key)
145 + message.appendChild(cloneurl)
146 + return message
147 + }
148 +
149 + else if (msg.value.content.type == 'post') {
150 + var opts = {
151 + type: 'post',
152 + branch: msg.key
153 + }
154 + var fallback = {}
155 +
156 +
157 + if (msg.value.content.root)
158 + opts.root = msg.value.content.root
159 + else
160 + opts.root = msg.key
161 +
162 + message.appendChild(tools.header(msg))
163 +
164 + if (msg.value.content.root)
165 + message.appendChild(h('span', 're: ', tools.messageLink(msg.value.content.root)))
166 +
167 + message.appendChild(h('div.message__body', tools.markdown(msg.value.content.text)))
168 +
169 + pull(
170 + sbot.query({query: [{$filter: {value: {content: {type: 'edit', original: msg.key}}}}], limit: 100}),
171 + pull.drain(function (update) {
172 + if (update.sync) {
173 + } else {
174 + var newMessage = h('div', tools.markdown(update.value.content.text))
175 + var latest = h('div.message__body',
176 + tools.timestamp(update, {edited: true}),
177 + newMessage
178 + )
179 + message.replaceChild(latest, message.childNodes[message.childNodes.length - 2])
180 + fallback.messageText = update.value.content.text
181 + opts.updated = update.key
182 + opts.original = msg.key
183 + }
184 + })
185 + )
186 +
187 + var name = avatar.name(msg.value.author)
188 +
189 + var buttons = h('div.buttons')
190 +
191 + buttons.appendChild(h('button.btn', 'Reply', {
192 + onclick: function () {
193 + opts.type = 'post'
194 + opts.mentions = '[' + name.textContent + '](' + msg.value.author + ')'
195 + if (msg.value.content.recps) {
196 + opts.recps = msg.value.content.recps
197 + }
198 + var r = message.childNodes.length - 1
199 + delete opts.updated
200 + delete opts.original
201 + delete fallback.messageText
202 + fallback.buttons = message.childNodes[r]
203 + var compose = h('div.message#re:' + msg.key.substring(0, 44), composer(opts, fallback))
204 + message.removeChild(message.childNodes[r])
205 + message.parentNode.insertBefore(compose, message.nextSibling)
206 + }
207 + }))
208 +
209 + /*buttons.appendChild(h('button.btn', 'Boost', {
210 + onclick: function () {
211 + opts.type = 'post'
212 + opts.mentions = '[' + name.textContent + '](' + msg.value.author + ')'
213 + if (msg.value.content.recps) {
214 + opts.recps = msg.value.content.recps
215 + }
216 + var r = message.childNodes.length - 1
217 + delete opts.updated
218 + delete opts.original
219 + delete fallback.messageText
220 + opts.boostContent = msg.value.content.text
221 + opts.boostKey = msg.key
222 + opts.boostAuthor = msg.value.author
223 + fallback.buttons = message.childNodes[r]
224 + var compose = h('div.message#re:' + msg.key.substring(0, 44), composer(opts, fallback))
225 + message.removeChild(message.childNodes[r])
226 + message.parentNode.insertBefore(compose, message.nextSibling)
227 + }
228 + }))*/
229 +
230 +
231 + if (msg.value.author == id)
232 + buttons.appendChild(h('button.btn', 'Edit', {
233 + onclick: function () {
234 + opts.type = 'edit'
235 + if (!fallback.messageText)
236 + fallback.messageText = msg.value.content.text
237 +
238 + if (!opts.updated)
239 + opts.updated = msg.key
240 + opts.original = msg.key
241 +
242 + var r = message.childNodes.length - 1
243 + fallback.buttons = message.childNodes[r]
244 + message.removeChild(message.childNodes[r])
245 + var compose = h('div#edit:' + msg.key.substring(0, 44), composer(opts, fallback))
246 + message.replaceChild(compose, message.lastElementChild)
247 + }
248 + }))
249 +
250 + //buttons.appendChild(tools.queueButton(msg))
251 + buttons.appendChild(tools.star(msg))
252 + message.appendChild(buttons)
253 + return message
254 +
255 + } else if (msg.value.content.type == 'vote') {
256 + if (msg.value.content.vote.value == 1)
257 + var link = h('span', ' ', h('img.emoji', {src: config.emojiUrl + 'star.png'}), ' ', h('a', {href: '#' + msg.value.content.vote.link}, tools.messageLink(msg.value.content.vote.link)))
258 + else if (msg.value.content.vote.value == -1)
259 + var link = h('span', ' ', h('img.emoji', {src: config.emojiUrl + 'stars.png'}), ' ', h('a', {href: '#' + msg.value.content.vote.link}, tools.messageLink(msg.value.content.vote.link)))
260 + message.appendChild(tools.mini(msg, link))
261 + return message
262 + } /*else if (typeof msg.value.content === 'string') {
263 + var privateMsg = h('span', ' sent a private message.')
264 + message.appendChild(tools.mini(msg, privateMsg))
265 + return message
266 + }*/ else {
267 +
268 + //FULL FALLBACK
269 + //message.appendChild(tools.header(msg))
270 + //message.appendChild(h('pre', tools.rawJSON(msg.value.content)))
271 +
272 + //MINI FALLBACK
273 + //var fallback = h('span', ' ' + msg.value.content.type)
274 + //message.appendChild(tools.mini(msg, fallback))
275 + return h('div')//message
276 + }
277 +}
scuttlebot.jsView
@@ -1,0 +1,120 @@
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 +var config = require('./config')()
7 +var createClient = require('ssb-client')
8 +var createFeed = require('ssb-feed')
9 +
10 +var keys = require('./keys')
11 +
12 +var CACHE = {}
13 +
14 +var rec = reconnect(function (isConn) {
15 + function notify (value) {
16 + isConn(value)
17 + }
18 +
19 + createClient(keys, {
20 + manifest: require('./manifest.json'),
21 + remote: config.remote,
22 + caps: config.caps
23 + }, function (err, _sbot) {
24 + if(err)
25 + return notify(err)
26 +
27 + sbot = _sbot
28 + sbot.on('closed', function () {
29 + sbot = null
30 + notify(new Error('closed'))
31 + })
32 +
33 + notify()
34 + })
35 +})
36 +
37 +var internal = {
38 + getLatest: rec.async(function (id, cb) {
39 + sbot.getLatest(id, cb)
40 + }),
41 + add: rec.async(function (msg, cb) {
42 + sbot.add(msg, cb)
43 + })
44 +}
45 +
46 +var feed = createFeed(internal, keys, {remote: true})
47 +
48 +module.exports = {
49 + acceptInvite: rec.async(function (invite, cb) {
50 + sbot.invite.accept(invite, cb)
51 + }),
52 + createLogStream: rec.source(function (opts) {
53 + return pull(
54 + sbot.createLogStream(opts),
55 + pull.through(function (e) {
56 + CACHE[e.key] = CACHE[e.key] || e.value
57 + })
58 + )
59 + }),
60 + userStream: rec.source(function (config) {
61 + return pull(
62 + sbot.createUserStream(config),
63 + pull.through(function (e) {
64 + CACHE[e.key] = CACHE[e.key] || e.value
65 + })
66 + )
67 + }),
68 + backlinks: rec.source(function (query) {
69 + return sbot.backlinks.read(query)
70 + }),
71 + query: rec.source(function (query) {
72 + return sbot.query.read(query)
73 + }),
74 + get: rec.async(function (key, cb) {
75 + //if('function' !== typeof cb)
76 + //throw new Error('cb must be function')
77 + if(CACHE[key]) cb(null, CACHE[key])
78 + else sbot.get(key, function (err, value) {
79 + if(err) return cb(err)
80 + cb(null, CACHE[key] = value)
81 + })
82 + }),
83 + links: rec.source(function (query) {
84 + return sbot.links(query)
85 + }),
86 + addblob: rec.sink(function (cb) {
87 + return sbot.blobs.add(cb)
88 + }),
89 + friends: {
90 + get: rec.async(function (opts, cb) {
91 + sbot.friends.get(opts, cb)
92 + })
93 + },
94 + search: {
95 + query: rec.source(function (opts) {
96 + return sbot.search.query(opts)
97 + })
98 + },
99 + publish: rec.async(function (content, cb) {
100 + if(content.recps)
101 + content = ssbKeys.box(content, content.recps.map(function (e) {
102 + return ref.isFeed(e) ? e : e.link
103 + }))
104 + else if(content.mentions)
105 + content.mentions.forEach(function (mention) {
106 + if(ref.isBlob(mention.link)) {
107 + sbot.blobs.push(mention.link, function (err) {
108 + if(err) console.error(err)
109 + })
110 + }
111 + })
112 + feed.add(content, function (err, msg) {
113 + if(err) console.error(err)
114 + else if(!cb) console.log(msg)
115 + cb && cb(err, msg)
116 + })
117 + })
118 +
119 +}
120 +
ssb-ws/.travis.ymlView
@@ -1,0 +1,4 @@
1 +language: node_js
2 +node_js:
3 + - 0.6
4 + - 0.8
ssb-ws/LICENSEView
@@ -1,0 +1,22 @@
1 +Copyright (c) 2016 'Dominic Tarr'
2 +
3 +Permission is hereby granted, free of charge,
4 +to any person obtaining a copy of this software and
5 +associated documentation files (the "Software"), to
6 +deal in the Software without restriction, including
7 +without limitation the rights to use, copy, modify,
8 +merge, publish, distribute, sublicense, and/or sell
9 +copies of the Software, and to permit persons to whom
10 +the Software is furnished to do so,
11 +subject to the following conditions:
12 +
13 +The above copyright notice and this permission notice
14 +shall be included in all copies or substantial portions of the Software.
15 +
16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20 +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ssb-ws/README.mdView
@@ -1,0 +1,43 @@
1 +# ssb-ws
2 +
3 +ssb-ws & http server for ssb.
4 +
5 +Work In Progress
6 +
7 +``` js
8 +sbot plugins.install ssb-ws
9 +```
10 +
11 +make sure you set a port in your config file (~/.ssb/config).
12 +
13 +``` json
14 +{
15 + "ws": {"port": 8989}
16 +}
17 +```
18 +
19 +## adding your own http handlers
20 +
21 +sometimes you need to do http, here is an example sbot plugin that
22 +exposes the websocket address.
23 +
24 +``` js
25 +require('scuttlebot')
26 + .use(require('ssb-ws'))
27 + .use({
28 + name: 'test123',
29 + version: '1.0.0',
30 + init: function (sbot) {
31 + sbot.ws.use(function (req, res, next) {
32 + if(req.url == '/get-address')
33 + res.end(sbot.ws.getAddress())
34 + else next()
35 + })
36 + }
37 + })
38 +```
39 +
40 +## License
41 +
42 +MIT
43 +
ssb-ws/index.jsView
@@ -1,0 +1,78 @@
1 +var WS = require('multiserver/plugins/ws')
2 +var http = require('http')
3 +var pull = require('pull-stream')
4 +var JSONApi = require('./json-api')
5 +
6 +var READ_AND_ADD = [ //except for add, of course
7 + 'get',
8 + 'getLatest',
9 + 'createLogStream',
10 + 'createUserStream',
11 +
12 + 'createHistoryStream',
13 + 'getAddress',
14 +
15 + 'links',
16 +
17 + 'blobs.add',
18 + 'blobs.size',
19 + 'blobs.has',
20 + 'blobs.get',
21 + 'blobs.changes',
22 + 'blobs.createWants',
23 + 'friends',
24 + 'add',
25 + 'search.query',
26 + 'backlinks.read',
27 + 'query.read',
28 + 'links2.read'
29 +]
30 +
31 +
32 +exports.name = 'ws'
33 +exports.version = require('./package.json').version
34 +exports.manifest = {}
35 +
36 +exports.init = function (sbot, config) {
37 + var port
38 + if(config.ws)
39 + port = config.ws.port
40 + if(!port)
41 + port = 1024+(~~(Math.random()*(65536-1024)))
42 +
43 + var layers = []
44 + var server, ws_server
45 +
46 + function createServer (config, instance) {
47 + instance = instance || 0
48 + if(server) return server
49 + server = http.createServer(JSONApi(sbot, layers)).listen(port+instance)
50 + ws_server = WS(Object.assign({
51 + server: server, port: port, host: config.host
52 + }, config))
53 + return server
54 + }
55 +
56 + sbot.auth.hook(function (fn, args) {
57 + var id = args[0]
58 + var cb = args[1]
59 + fn(id, function (err, value) {
60 + cb(null, {allow: READ_AND_ADD, deny: null})
61 + })
62 + })
63 +
64 + sbot.multiserver.transport({
65 + name: 'ws',
66 + create: function (config, instance) {
67 + createServer(config, instance)
68 + return ws_server
69 + }
70 + })
71 +
72 + return {
73 + use: function (handler) {
74 + layers.push(handler)
75 + }
76 + }
77 +}
78 +
ssb-ws/json-api.jsView
@@ -1,0 +1,77 @@
1 +'use strict'
2 +var ref = require('ssb-ref')
3 +var Stack = require('stack')
4 +var BlobsHttp = require('multiblob-http')
5 +var pull = require('pull-stream')
6 +var URL = require('url')
7 +var Emoji = require('emoji-server')
8 +
9 +function send(res, obj) {
10 + res.writeHead(200, {'Content-Type': 'application/json'})
11 + res.end(JSON.stringify(obj, null, 2))
12 +}
13 +
14 +var BoxStream = require('pull-box-stream')
15 +var zeros = new Buffer(24); zeros.fill(0)
16 +
17 +module.exports = function (sbot, layers) {
18 + var prefix = '/blobs'
19 + return Stack(
20 + function (req, res, next) {
21 + Stack.compose.apply(null, layers)(req, res, next)
22 + },
23 + Emoji('/img/emoji'),
24 + //blobs are served over CORS, so you can get blobs from any pub.
25 + /*function (req, res, next) {
26 + res.setHeader('Access-Control-Allow-Origin', '*')
27 + res.setHeader("Access-Control-Allow-Headers",
28 + "Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since");
29 + res.setHeader("Access-Control-Allow-Methods", "GET", "HEAD");
30 + next()
31 + },*/
32 + function (req, res, next) {
33 + var id
34 + try { id = decodeURIComponent(req.url.substring(5)) }
35 + catch (_) { id = req.url.substring(5) }
36 + if(req.url.substring(0, 5) !== '/msg/' || !ref.isMsg(id)) return next()
37 +
38 + sbot.get(id, function (err, msg) {
39 + if(err) return next(err)
40 + send(res, {key: id, value: msg})
41 + })
42 + },
43 + function (req, res, next) {
44 + if(!(req.method === "GET" || req.method == 'HEAD')) return next()
45 +
46 + var u = URL.parse('http://makeurlparseright.com'+req.url)
47 + var hash = decodeURIComponent(u.pathname.substring((prefix+'/get/').length))
48 + //check if we don't already have this, tell blobs we want it, if necessary.
49 + sbot.blobs.has(hash, function (err, has) {
50 + if(has) next()
51 + else sbot.blobs.want(hash, function (err, has) { next() })
52 + })
53 + },
54 + BlobsHttp(sbot.blobs, prefix, {size: false, transform: function (q) {
55 + if(q.unbox && /\.boxs$/.test(q.unbox)) {
56 + var key = new Buffer(q.unbox.replace(/\s/g, '+'), 'base64')
57 + if(key.length !== 32)
58 + return function (read) {
59 + return function (abort, cb) {
60 + read(new Error('key must be 32 bytes long'), cb)
61 + }
62 + }
63 + return BoxStream.createUnboxStream(
64 + new Buffer(key, 'base64'),
65 + zeros
66 + )
67 + }
68 + return pull.through()
69 + }})
70 + )
71 +}
72 +
73 +
74 +
75 +
76 +
77 +
ssb-ws/package.jsonView
@@ -1,0 +1,57 @@
1 +{
2 + "_from": "ssb-ws@^3.0.0",
3 + "_id": "ssb-ws@3.0.2",
4 + "_inBundle": false,
5 + "_integrity": "sha512-rQnbFIdzyQrB77y1UD0cWlW/TYO6rqCT1RO95p0ka6UQsEBtm/O7TN8h5AgroD9XqyxdusXhEbhobpUt41s1Cw==",
6 + "_location": "/ssb-ws",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "ssb-ws@^3.0.0",
12 + "name": "ssb-ws",
13 + "escapedName": "ssb-ws",
14 + "rawSpec": "^3.0.0",
15 + "saveSpec": null,
16 + "fetchSpec": "^3.0.0"
17 + },
18 + "_requiredBy": [
19 + "/scuttlebot"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/ssb-ws/-/ssb-ws-3.0.2.tgz",
22 + "_shasum": "e01e48c28128efb8318d0f291a6d85ef77ff1fdc",
23 + "_spec": "ssb-ws@^3.0.0",
24 + "_where": "/home/ev/mvd/node_modules/scuttlebot",
25 + "author": {
26 + "name": "'Dominic Tarr'",
27 + "email": "dominic.tarr@gmail.com",
28 + "url": "dominictarr.com"
29 + },
30 + "bugs": {
31 + "url": "https://github.com/dominictarr/ssb-ws/issues"
32 + },
33 + "bundleDependencies": false,
34 + "dependencies": {
35 + "emoji-server": "^1.0.0",
36 + "multiblob-http": "^0.4.2",
37 + "multiserver": "^1.13.5",
38 + "muxrpc": "^6.3.3",
39 + "pull-box-stream": "^1.0.13",
40 + "ssb-ref": "^2.3.0",
41 + "stack": "^0.1.0"
42 + },
43 + "deprecated": false,
44 + "description": "websocket & http server for ssb",
45 + "devDependencies": {},
46 + "homepage": "https://github.com/dominictarr/ssb-ws",
47 + "license": "MIT",
48 + "name": "ssb-ws",
49 + "repository": {
50 + "type": "git",
51 + "url": "git://github.com/dominictarr/ssb-ws.git"
52 + },
53 + "scripts": {
54 + "test": "set -e; for t in test/*.js; do node $t; done"
55 + },
56 + "version": "3.0.2"
57 +}
style.cssView
@@ -1,0 +1,252 @@
1 +body {
2 + margin: 0;
3 + background: #f5f5f5;
4 + font-family: sans-serif;
5 + color: #333;
6 + font-size: 14px;
7 + line-height: 20px;
8 +}
9 +
10 +#screen {
11 + position: absolute;
12 + top: 32px;
13 + bottom: 0px;
14 + left: 0px;
15 + right: 0px;
16 +}
17 +
18 +.hyperscroll {
19 + width: 100%;
20 +}
21 +
22 +.search {
23 + margin-top: 1.5px;
24 + float: right;
25 + width: 200px;
26 +}
27 +
28 +.header {
29 + padding-bottom: .7em;
30 + border-bottom: 1px solid #ddd;
31 +}
32 +
33 +h1, h2, h3, h4, h5, h6 {
34 + font-size: 1.2em;
35 + margin-top: .35ex;
36 +}
37 +
38 +hr {
39 + border: solid #ddd;
40 + clear: both;
41 + border-width: 1px 0 0;
42 + height: 0;
43 + margin-bottom: .9em;
44 +}
45 +
46 +
47 +p {
48 + margin-top: .35ex;
49 + margin-bottom: 10px;
50 +}
51 +.navbar a {
52 + color: #999;
53 + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
54 + text-decoration: none;
55 +}
56 +
57 +.navbar a:hover, .navbar a:focus {
58 + color: #fff;
59 + text-decoration: none;
60 +}
61 +
62 +.navbar {
63 + background: #1b1b1b;
64 + background: linear-gradient(#222, #111);
65 +}
66 +
67 +.navbar {
68 + width: 100%;
69 + position: fixed;
70 + z-index: 1000;
71 + margin: 0;
72 + padding-top: .3em;
73 + padding-bottom: .3em;
74 + left: 0; right: 0;
75 + top: 0;
76 +}
77 +
78 +.navbar .internal {
79 + max-width: 97%;
80 + margin-left: auto;
81 + margin-right: auto;
82 +}
83 +
84 +.navbar li {
85 + margin-top: .3em;
86 + float: left;
87 + margin-right: .6em;
88 + margin-left: .3em;
89 + list-style-type: none;
90 +}
91 +
92 +.navbar li.right {
93 + padding-left: .4em;
94 + padding-right: .4em;
95 + margin-top: .3em;
96 + margin-right: 1.7em;
97 + float: right;
98 + list-style-type: none;
99 + background: #333;
100 + border-radius: 100%;
101 +}
102 +
103 +.content {
104 + max-width: 680px;
105 + margin-left: auto;
106 + margin-right: auto;
107 +}
108 +
109 +.hyperscroll > .content {
110 + max-width: 680px;
111 + margin-left: auto;
112 + margin-right: auto;
113 +}
114 +
115 +.message, .message > *, .navbar, .navbar > * {
116 + animation: fadein .5s;
117 +}
118 +
119 +@keyframes fadein {
120 + from { opacity: 0; }
121 + to { opacity: 1; }
122 +}
123 +
124 +.message, .embedded {
125 + display: block;
126 + margin: .6em;
127 + background: #eee;
128 + padding: .7em;
129 + border-radius: 3px;
130 + border: 1px solid #ddd;
131 +}
132 +
133 +.embedded {
134 + padding-left: 1em;
135 +}
136 +
137 +.message:hover, .embedded:hover {
138 + background: #ddd;
139 +}
140 +
141 +.message img, .message video {
142 + max-width: 100%;
143 +}
144 +
145 +img {
146 + border-radius: 3px;
147 +}
148 +
149 +.timestamp, .votes {
150 + float: right;
151 +}
152 +
153 +.avatar--small img {
154 + vertical-align: top;
155 + width: 1.4em;
156 + height: 1.4em;
157 + margin-right: .2em;
158 +}
159 +
160 +.avatar--medium img {
161 + float: left;
162 + vertical-align: top;
163 + width: 5em;
164 + height: 5em;
165 + margin-right: .5em;
166 + margin-bottom: .5em;
167 +}
168 +
169 +.compose, textarea, input {
170 + font-family: sans-serif;
171 + font-size: 14px;
172 + line-height: 20px;
173 + background: #eee;
174 + border: none;
175 + border-radius: 3px;
176 +}
177 +
178 +textarea {
179 + width: 100%;
180 + height: 100px;
181 +}
182 +
183 +.compose:hover {
184 + #background: #141414;
185 +}
186 +
187 +.compose:focus {
188 + outline: none;
189 +}
190 +
191 +.emoji {
192 + padding: .2em;
193 +}
194 +
195 +.right {
196 + float: right;
197 + margin-right: .25em;
198 +}
199 +
200 +.emoji {
201 + *float: left;
202 + width: 1em;
203 + vertical-align: top;
204 +}
205 +
206 +pre {
207 + width: 100%;
208 + display: block;
209 +}
210 +
211 +code {
212 + display: inline-block;
213 + vertical-align: bottom;
214 +}
215 +
216 +code, pre {
217 +overflow: auto;
218 +word-break: break-all;
219 +word-wrap: break-word;
220 +white-space: pre;
221 +white-space: -moz-pre-wrap;
222 +white-space: pre-wrap;
223 +white-space: pre\9;
224 +}
225 +
226 +code, pre {
227 + font-size: 12px;
228 +}
229 +
230 +pre {
231 + margin: 0 0 10px;
232 + font-size: 13px;
233 + line-height: 20px;
234 +}
235 +
236 +button {margin: 0; margin-top: -.2em;}
237 +
238 +input {width: 88%; }
239 +
240 +#profile input {width: 50%;}
241 +
242 +.btn {
243 + padding: 2px 6px;
244 + margin-bottom: 0;
245 + margin-right: .2em;
246 + font-size: 14px;
247 + line-height: 20px;
248 + text-align: center;
249 + vertical-align: middle;
250 + cursor: pointer;
251 +}
252 +
style.css.jsonView
@@ -1,0 +1,1 @@
1 +"body {\n margin: 0;\n background: #f5f5f5;\n font-family: sans-serif;\n color: #333;\n font-size: 14px; \n line-height: 20px;\n}\n\n#screen {\n position: absolute;\n top: 32px;\n bottom: 0px;\n left: 0px;\n right: 0px;\n}\n\n.hyperscroll {\n width: 100%;\n}\n\n.search {\n margin-top: 1.5px;\n float: right;\n width: 200px;\n}\n\n.header {\n padding-bottom: .7em;\n border-bottom: 1px solid #ddd;\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-size: 1.2em;\n margin-top: .35ex;\n}\n\nhr {\n border: solid #ddd;\n clear: both;\n border-width: 1px 0 0;\n height: 0;\n margin-bottom: .9em;\n}\n\n\np {\n margin-top: .35ex;\n margin-bottom: 10px;\n}\n.navbar a {\n color: #999;\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n text-decoration: none;\n}\n\n.navbar a:hover, .navbar a:focus {\n color: #fff;\n text-decoration: none;\n}\n\n.navbar {\n background: #1b1b1b;\n background: linear-gradient(#222, #111);\n}\n\n.navbar {\n width: 100%;\n position: fixed;\n z-index: 1000;\n margin: 0;\n padding-top: .3em;\n padding-bottom: .3em;\n left: 0; right: 0;\n top: 0;\n}\n\n.navbar .internal {\n max-width: 97%;\n margin-left: auto;\n margin-right: auto;\n}\n\n.navbar li {\n margin-top: .3em;\n float: left;\n margin-right: .6em;\n margin-left: .3em;\n list-style-type: none;\n}\n\n.navbar li.right {\n padding-left: .4em;\n padding-right: .4em;\n margin-top: .3em;\n margin-right: 1.7em;\n float: right;\n list-style-type: none;\n background: #333;\n border-radius: 100%;\n}\n\n.content {\n max-width: 680px;\n margin-left: auto;\n margin-right: auto;\n}\n\n.hyperscroll > .content {\n max-width: 680px;\n margin-left: auto;\n margin-right: auto;\n}\n\n.message, .message > *, .navbar, .navbar > * {\n animation: fadein .5s;\n}\n\n@keyframes fadein {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.message, .embedded {\n display: block;\n margin: .6em;\n background: #eee;\n padding: .7em;\n border-radius: 3px;\n border: 1px solid #ddd;\n}\n\n.embedded {\n padding-left: 1em;\n}\n\n.message:hover, .embedded:hover {\n background: #ddd;\n}\n\n.message img, .message video {\n max-width: 100%;\n}\n\nimg {\n border-radius: 3px;\n}\n\n.timestamp, .votes {\n float: right;\n}\n \n.avatar--small img {\n vertical-align: top;\n width: 1.4em;\n height: 1.4em;\n margin-right: .2em;\n}\n\n.avatar--medium img {\n float: left;\n vertical-align: top;\n width: 5em;\n height: 5em;\n margin-right: .5em;\n margin-bottom: .5em;\n}\n\n.compose, textarea, input {\n font-family: sans-serif;\n font-size: 14px;\n line-height: 20px;\n background: #eee;\n border: none;\n border-radius: 3px;\n}\n\ntextarea {\n width: 100%;\n height: 100px;\n}\n\n.compose:hover {\n #background: #141414;\n}\n\n.compose:focus {\n outline: none;\n}\n\n.emoji {\n padding: .2em;\n}\n\n.right {\n float: right;\n margin-right: .25em;\n}\n\n.emoji {\n *float: left;\n width: 1em;\n vertical-align: top;\n}\n\npre {\n width: 100%;\n display: block;\n}\n\ncode {\n display: inline-block;\n vertical-align: bottom;\n}\n\ncode, pre {\noverflow: auto;\nword-break: break-all;\nword-wrap: break-word;\nwhite-space: pre;\nwhite-space: -moz-pre-wrap;\nwhite-space: pre-wrap;\nwhite-space: pre\\9;\n}\n\ncode, pre {\n font-size: 12px;\n}\n\npre {\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 20px;\n}\n\nbutton {margin: 0; margin-top: -.2em;}\n\ninput {width: 88%; }\n\n#profile input {width: 50%;}\n\n.btn {\n padding: 2px 6px;\n margin-bottom: 0;\n margin-right: .2em;\n font-size: 14px;\n line-height: 20px;\n text-align: center;\n vertical-align: middle;\n cursor: pointer;\n}\n\n"
style.jsView
@@ -1,0 +1,8 @@
1 +var fs = require('fs')
2 +var path = require('path')
3 +
4 +fs.writeFileSync(
5 + path.join(__dirname, 'style.css.json'),
6 + JSON.stringify(fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8'))
7 +)
8 +
tools.jsView
@@ -1,0 +1,517 @@
1 +var h = require('hyperscript')
2 +var human = require('human-time')
3 +var avatar = require('./avatar')
4 +var ref = require('ssb-ref')
5 +
6 +var ssbKeys = require('ssb-keys')
7 +
8 +var pull = require('pull-stream')
9 +
10 +var sbot = require('./scuttlebot')
11 +
12 +var config = require('./config')()
13 +
14 +var id = require('./keys').id
15 +
16 +module.exports.getBlocks = function (src) {
17 + var blocks = h('div.blocks', 'Blocking: ')
18 +
19 + pull(
20 + sbot.query({query: [{$filter: { value: { author: src, content: {type: 'contact'}}}}], live: true}),
21 + pull.drain(function (msg) {
22 + if (msg.value) {
23 + if (msg.value.content.blocking == true) {
24 + console.log(msg.value)
25 + var gotIt = document.getElementById('blocks:' + msg.value.content.contact.substring(0, 44))
26 + if (gotIt == null) {
27 + blocks.appendChild(h('a#blocks:'+ msg.value.content.contact.substring(0, 44), {title: avatar.cachedName(msg.value.content.contact).textContent, href: '#' + msg.value.content.contact}, h('span.avatar--small', avatar.cachedImage(msg.value.content.contact))))
28 + }
29 + }
30 + if (msg.value.content.blocking == false) {
31 + var gotIt = document.getElementById('blocks:' + msg.value.content.contact.substring(0, 44))
32 + if (gotIt != null) {
33 + gotIt.outerHTML = ''
34 + }
35 + }
36 + }
37 + })
38 + )
39 +
40 + return blocks
41 +
42 +}
43 +
44 +module.exports.getBlocked = function (src) {
45 + var blocked = h('div.blocked', 'Blocked by: ')
46 +
47 + pull(
48 + sbot.query({query: [{$filter: { value: { content: {type: 'contact', contact: src}}}}], live: true}),
49 + pull.drain(function (msg) {
50 + if (msg.value) {
51 + if (msg.value.content.blocking == true) {
52 + console.log(msg.value)
53 + var gotIt = document.getElementById('blocked:' + msg.value.content.contact.substring(0, 44))
54 + if (gotIt == null) {
55 + blocked.appendChild(h('a#blocked:'+ msg.value.author.substring(0, 44), {title: avatar.cachedName(msg.value.author).textContent, href: '#' + msg.value.author}, h('span.avatar--small', avatar.cachedImage(msg.value.author))))
56 + }
57 + }
58 + if (msg.value.content.blocking == false) {
59 + var gotIt = document.getElementById('blocked:' + msg.value.author.substring(0, 44))
60 + if (gotIt != null) {
61 + gotIt.outerHTML = ''
62 + }
63 + }
64 + }
65 + })
66 + )
67 +
68 + return blocked
69 +
70 +}
71 +
72 +module.exports.getFollowing = function (src) {
73 + var followingCount = 0
74 +
75 + var following = h('div.following', 'Following: ')
76 +
77 + following.appendChild(h('span#followingcount', '0'))
78 + following.appendChild(h('br'))
79 +
80 + pull(
81 + sbot.query({query: [{$filter: { value: { author: src, content: {type: 'contact'}}}}], live: true}),
82 + pull.drain(function (msg) {
83 + if (msg.value) {
84 + if (msg.value.content.following == true) {
85 + followingcount = document.getElementById('followingcount')
86 + followingCount++
87 + followingcount.textContent = followingCount
88 + var gotIt = document.getElementById('following:' + msg.value.content.contact.substring(0, 44))
89 + if (gotIt == null) {
90 + following.appendChild(h('a#following:'+ msg.value.content.contact.substring(0, 44), {title: avatar.cachedName(msg.value.content.contact).textContent, href: '#' + msg.value.content.contact}, h('span.avatar--small', avatar.cachedImage(msg.value.content.contact))))
91 + }
92 + }
93 + if (msg.value.content.following == false) {
94 + followingcount = document.getElementById('followingcount')
95 + followingCount--
96 + followingcount.textContent = followingCount
97 + var gotIt = document.getElementById('following:' + msg.value.content.contact.substring(0, 44))
98 + if (gotIt != null) {
99 + gotIt.outerHTML = ''
100 + }
101 + }
102 + }
103 + })
104 + )
105 + return following
106 +}
107 +
108 +module.exports.getFollowers = function (src) {
109 + var followerCount = 0
110 +
111 + var followers = h('div.followers', 'Followers: ')
112 +
113 + followers.appendChild(h('span#followercount', '0'))
114 + followers.appendChild(h('br'))
115 +
116 + pull(
117 + sbot.query({query: [{$filter: { value: { content: {type: 'contact', contact: src}}}}], live: true}),
118 + pull.drain(function (msg) {
119 + if (msg.value) {
120 + if (msg.value.content.following == true) {
121 + followcount = document.getElementById('followercount')
122 + followerCount++
123 + followcount.textContent = followerCount
124 + var gotIt = document.getElementById('followers:' + msg.value.author.substring(0, 44))
125 + if (gotIt == null) {
126 + followers.appendChild(h('a#followers:'+ msg.value.author.substring(0, 44), {title: avatar.cachedName(msg.value.author).textContent, href: '#' + msg.value.author}, h('span.avatar--small', avatar.cachedImage(msg.value.author))))
127 + }
128 + }
129 + if (msg.value.content.following == false) {
130 + followcount = document.getElementById('followercount')
131 + followerCount--
132 + followcount.textContent = followerCount
133 + var gotIt = document.getElementById('followers:' + msg.value.author.substring(0, 44))
134 + if (gotIt != null) {
135 + gotIt.outerHTML = ''
136 + }
137 + }
138 + }
139 + })
140 + )
141 +
142 + return followers
143 +}
144 +
145 +module.exports.queueButton = function (src) {
146 + var queueButton = h('span.queue:' + src.key.substring(0,44))
147 +
148 + var addToQueue = h('button.btn.right', 'Queue', {
149 + onclick: function () {
150 + var content = {
151 + type: 'queue',
152 + message: src.key,
153 + queue: true
154 + }
155 + sbot.publish(content, function (err, publish) {
156 + if (err) throw err
157 + console.log(publish)
158 + })
159 + }
160 + })
161 +
162 + var removeFromQueue = h('button.btn.right#', 'Done', {
163 + onclick: function () {
164 + var content = {
165 + type: 'queue',
166 + message: src.key,
167 + queue: false
168 + }
169 + sbot.publish(content, function (err, publish) {
170 + if (err) throw err
171 + console.log(publish)
172 + if (window.location.hash.substring(1) == 'queue') {
173 + setTimeout(function () {
174 + var gotIt = document.getElementById(src.key.substring(0,44))
175 + if (gotIt != null) {
176 + gotIt.outerHTML = ''
177 + }
178 + }, 100)
179 +
180 + }
181 + })
182 + }
183 + })
184 +
185 + pull(
186 + sbot.query({query: [{$filter: { value: { author: id, content: {type: 'queue', message: src.key}}}}], live: true}),
187 + pull.drain(function (msg) {
188 + if (msg.value) {
189 + if (msg.value.content.queue == true) {
190 + queueButton.removeChild(queueButton.childNodes[0])
191 + queueButton.appendChild(removeFromQueue)
192 + }
193 + if (msg.value.content.queue == false) {
194 + queueButton.removeChild(queueButton.childNodes[0])
195 + queueButton.appendChild(addToQueue)
196 + }
197 + }
198 + })
199 + )
200 +
201 + queueButton.appendChild(addToQueue)
202 +
203 + return queueButton
204 +}
205 +
206 +module.exports.follow = function (src) {
207 + var button = h('span.button')
208 +
209 + var followButton = h('button.btn', 'Follow ', avatar.name(src), {
210 + onclick: function () {
211 + var content = {
212 + type: 'contact',
213 + contact: src,
214 + following: true
215 + }
216 + sbot.publish(content, function (err, publish) {
217 + if (err) throw err
218 + console.log(publish)
219 + })
220 + }
221 + })
222 +
223 + var unfollowButton = h('button.btn', 'Unfollow ', avatar.name(src), {
224 + onclick: function () {
225 + var content = {
226 + type: 'contact',
227 + contact: src,
228 + following: false
229 + }
230 + sbot.publish(content, function (err, publish) {
231 + if (err) throw err
232 + console.log(publish)
233 + })
234 + }
235 + })
236 +
237 + pull(
238 + sbot.query({query: [{$filter: { value: { author: id, content: {type: 'contact', contact: src}}}}], live: true}),
239 + pull.drain(function (msg) {
240 + if (msg.value) {
241 + if (msg.value.content.following == true) {
242 + button.removeChild(button.firstChild)
243 + button.appendChild(unfollowButton)
244 + }
245 + if (msg.value.content.following == false) {
246 + button.removeChild(button.firstChild)
247 + button.appendChild(followButton)
248 + }
249 + }
250 + })
251 + )
252 +
253 + button.appendChild(followButton)
254 +
255 + return button
256 +}
257 +
258 +module.exports.box = function (content) {
259 + return ssbKeys.box(content, content.recps.map(function (e) {
260 + return ref.isFeed(e) ? e : e.link
261 + }))
262 +}
263 +
264 +module.exports.publish = function (content, cb) {
265 + if(content.recps)
266 + content = exports.box(content)
267 + sbot.publish(content, function (err, msg) {
268 + if(err) throw err
269 + console.log('Published!', msg)
270 + if(cb) cb(err, msg)
271 + })
272 +}
273 +
274 +module.exports.mute = function (src) {
275 + if (!localStorage[src])
276 + var cache = {mute: false}
277 + else
278 + var cache = JSON.parse(localStorage[src])
279 +
280 + if (cache.mute == true) {
281 + var mute = h('button.btn', 'Unmute', {
282 + onclick: function () {
283 + cache.mute = false
284 + localStorage[src] = JSON.stringify(cache)
285 + location.hash = '#'
286 + location.hash = src
287 + }
288 + })
289 + return mute
290 + } else {
291 + var mute = h('button.btn', 'Mute', {
292 + onclick: function () {
293 + cache.mute = true
294 + localStorage[src] = JSON.stringify(cache)
295 + location.hash = '#'
296 + location.hash = src
297 + }
298 + })
299 + return mute
300 + }
301 +}
302 +
303 +module.exports.star = function (msg) {
304 + var votebutton = h('span.star:' + msg.key.substring(0,44))
305 +
306 + var vote = {
307 + type: 'vote',
308 + vote: { link: msg.key, expression: 'Star' }
309 + }
310 +
311 + if (msg.value.content.recps) {
312 + vote.recps = msg.value.content.recps
313 + }
314 +
315 + var star = h('button.btn.right', 'Star ',
316 + h('img.emoji', {src: config.emojiUrl + 'star.png'}), {
317 + onclick: function () {
318 + vote.vote.value = 1
319 + if (vote.recps) {
320 + vote = exports.box(vote)
321 + }
322 + sbot.publish(vote, function (err, voted) {
323 + if(err) throw err
324 + })
325 + }
326 + }
327 + )
328 +
329 + var unstar = h('button.btn.right ', 'Unstar ',
330 + h('img.emoji', {src: config.emojiUrl + 'stars.png'}), {
331 + onclick: function () {
332 + vote.vote.value = -1
333 + sbot.publish(vote, function (err, voted) {
334 + if(err) throw err
335 + })
336 + }
337 + }
338 + )
339 +
340 + votebutton.appendChild(star)
341 +
342 + pull(
343 + sbot.links({rel: 'vote', dest: msg.key, live: true}),
344 + pull.drain(function (link) {
345 + if (link.key) {
346 + sbot.get(link.key, function (err, data) {
347 + if (err) throw err
348 + if (data.content.vote) {
349 + if (data.author == id) {
350 + if (data.content.vote.value == 1)
351 + votebutton.replaceChild(unstar, star)
352 + if (data.content.vote.value == -1)
353 + votebutton.replaceChild(star, unstar)
354 + }
355 + }
356 + })
357 + }
358 + })
359 + )
360 +
361 + return votebutton
362 +}
363 +
364 +function votes (msg) {
365 + var votes = h('div.votes')
366 + if (msg.key) {
367 + pull(
368 + sbot.links({rel: 'vote', dest: msg.key/*, live: true*/ }),
369 + pull.drain(function (link) {
370 + if (link.key) {
371 + sbot.get(link.key, function (err, data) {
372 + if (err) throw err
373 + if (data.content.vote) {
374 + if (data.content.vote.value == 1) {
375 + if (localStorage[data.author + 'name'])
376 + name = localStorage[data.author + 'name']
377 + else
378 + name = data.author
379 + votes.appendChild(h('a#vote:' + data.author.substring(0, 44), {href:'#' + data.author, title: name}, h('img.emoji', {src: config.emojiUrl + 'star.png'})))
380 + }
381 + else if (data.content.vote.value == -1) {
382 + var lookFor = 'vote:' + data.author.substring(0, 44)
383 + document.getElementById(lookFor, function (err, gotit) {
384 + if (err) throw err
385 + gotit.parentNode.removeChild(remove)
386 + })
387 + }
388 + }
389 + })
390 + }
391 + })
392 + )
393 + }
394 + return votes
395 +}
396 +
397 +module.exports.timestamp = function (msg, edited) {
398 + var timestamp
399 + if (edited)
400 + timestamp = h('span.timestamp', 'Edited by: ', h('a', {href: '#' + msg.value.author}, h('span.avatar--small', avatar.cachedImage(msg.value.author))), h('a', {href: '#' + msg.key}, human(new Date(msg.value.timestamp))))
401 + else
402 + timestamp = h('span.timestamp', h('a', {href: '#' + msg.key}, human(new Date(msg.value.timestamp))))
403 + return timestamp
404 +}
405 +
406 +
407 +module.exports.mini = function (msg, content) {
408 + var mini = h('div.mini')
409 +
410 + mini.appendChild(
411 + h('span.avatar',
412 + h('a', {href: '#' + msg.value.author},
413 + h('span.avatar--small', avatar.cachedImage(msg.value.author)),
414 + avatar.cachedName(msg.value.author)
415 + )
416 + )
417 + )
418 + var lock = h('span.right', h('img.emoji', {src: config.emojiUrl + 'lock.png'}))
419 +
420 +
421 + mini.appendChild(h('span', content))
422 + mini.appendChild(exports.timestamp(msg))
423 +
424 + if (msg.value.content.recps) {
425 + mini.appendChild(lock)
426 + }
427 +
428 + if (typeof msg.value.content === 'string') {
429 + mini.appendChild(lock)
430 + }
431 +
432 + return mini
433 +}
434 +
435 +module.exports.header = function (msg) {
436 + var header = h('div.header')
437 +
438 + header.appendChild(h('span.avatar',
439 + h('a', {href: '#' + msg.value.author},
440 + h('span.avatar--small', avatar.cachedImage(msg.value.author)),
441 + avatar.cachedName(msg.value.author)
442 + )
443 + )
444 + )
445 +
446 + header.appendChild(exports.timestamp(msg))
447 + header.appendChild(votes(msg))
448 +
449 + if (msg.value.private) {
450 + header.appendChild(h('span.right', ' ', h('img.emoji', {src: config.emojiUrl + 'lock.png'})))
451 + }
452 + if (msg.value.content.type == 'edit') {
453 + header.appendChild(h('span.right', ' Edited: ', h('a', {href: '#' + msg.value.content.original}, exports.messageLink(msg.value.content.original))))
454 + }
455 + return header
456 +}
457 +
458 +
459 +
460 +
461 +module.exports.messageName = function (id, cb) {
462 + // gets the first few characters of a message, for message-link
463 + function title (s) {
464 + var m = /^\n*([^\n]{0,40})/.exec(s)
465 + return m && (m[1].length == 40 ? m[1]+'...' : m[1])
466 + }
467 +
468 + sbot.get(id, function (err, value) {
469 + if(err && err.name == 'NotFoundError')
470 + return cb(null, id.substring(0, 10)+'...(missing)')
471 + if(value.content.type === 'post' && 'string' === typeof value.content.text)
472 + return cb(null, title(value.content.text))
473 + else if('string' === typeof value.content.text)
474 + return cb(null, value.content.type + ':'+title(value.content.text))
475 + else
476 + return cb(null, id.substring(0, 10)+'...')
477 + })
478 +}
479 +
480 +var messageName = exports.messageName
481 +
482 +module.exports.messageLink = function (id) {
483 + if (ref.isMsg(id)) {
484 + var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...')
485 + messageName(id, function (err, name) {
486 + if(err) console.error(err)
487 + else link.textContent = name
488 + })
489 + } else {
490 + var link = id
491 + }
492 + return link
493 +}
494 +
495 +module.exports.rawJSON = function (obj) {
496 + return JSON.stringify(obj, null, 2)
497 + .split(/([%@&][a-zA-Z0-9\/\+]{43}=*\.[\w]+)/)
498 + .map(function (e) {
499 + if(ref.isMsg(e) || ref.isFeed(e) || ref.isBlob(e)) {
500 + return h('a', {href: '#' + e}, e)
501 + }
502 + return e
503 + })
504 +}
505 +
506 +var markdown = require('ssb-markdown')
507 +var config = require('./config')()
508 +
509 +module.exports.markdown = function (msg, md) {
510 + return {innerHTML: markdown.block(msg, {toUrl: function (url, image) {
511 + if(url[0] == '%' || url[0] == '@' || url[0] == '#') return '#' + url
512 + if(url[0] !== '&') return url
513 + //if(url[0] == '&') return config.blobsUrl + url
514 + //if(!image) return url
515 + return config.blobsUrl + url
516 + }})}
517 +}
views.jsView
@@ -1,0 +1,576 @@
1 +var pull = require('pull-stream')
2 +var human = require('human-time')
3 +var sbot = require('./scuttlebot')
4 +var hyperscroll = require('hyperscroll')
5 +var hyperfile = require('hyperfile')
6 +var dataurl = require('dataurl-')
7 +var More = require('pull-more')
8 +var stream = require('hyperloadmore/stream')
9 +var h = require('hyperscript')
10 +var render = require('./render')
11 +var ref = require('ssb-ref')
12 +var client = require('ssb-client')
13 +
14 +var Next = require('pull-next-query')
15 +
16 +var config = require('./config')()
17 +
18 +var tools = require('./tools')
19 +var avatar = require('./avatar')
20 +var id = require('./keys').id
21 +
22 +var ssbKeys = require('ssb-keys')
23 +var keys = require('./keys')
24 +
25 +var compose = require('./compose')
26 +
27 +var mentionsStream = function (src) {
28 + var content = h('div.content')
29 +
30 + var screen = document.getElementById('screen')
31 +
32 + screen.appendChild(hyperscroll(content))
33 +
34 + function createStream (opts) {
35 + return pull(
36 + Next(sbot.backlinks, opts, ['value', 'timestamp']),
37 + pull.map(function (msg) {
38 + if (msg.value.private == true) return h('div.private')
39 + return render(msg)
40 + })
41 + )
42 + }
43 +
44 + pull(
45 + createStream({
46 + limit: 10,
47 + reverse: true,
48 + index: 'DTA',
49 + live: false,
50 + query: [{$filter: {dest: src}}]
51 + }),
52 + stream.bottom(content)
53 + )
54 +
55 + pull(
56 + createStream({
57 + limit: 10,
58 + old: false,
59 + index: 'DTA',
60 + live: true,
61 + query: [{$filter: {dest: src}}]
62 + }),
63 + stream.top(content)
64 + )
65 +
66 + var profile = h('div.content#profile', h('div.message'))
67 +
68 + if (screen.firstChild.firstChild) {
69 + screen.firstChild.insertBefore(profile, screen.firstChild.firstChild)
70 + } else {
71 + screen.firstChild.appendChild(profile)
72 + }
73 +
74 + var name = avatar.name(src)
75 +
76 + var editname = h('span',
77 + avatar.name(src),
78 + h('button.btn', 'New name', {
79 + onclick: function () {
80 + var nameput = h('input', {placeholder: name.textContent})
81 + var nameedit =
82 + h('span', nameput,
83 + h('button.btn', 'Preview', {
84 + onclick: function () {
85 + if (nameput.value[0] != '@')
86 + tobename = nameput.value
87 + else
88 + tobename = nameput.value.substring(1, 100)
89 + var newname = h('span', h('a', {href: '#' + src}, '@' + tobename), h('button.btn', 'Publish', {
90 + onclick: function () {
91 + var donename = h('span', h('a', {href: '#' + src}, '@' + tobename))
92 + sbot.publish({type: 'about', about: src, name: tobename})
93 + localStorage[src + 'name'] = tobename
94 + newname.parentNode.replaceChild(donename, newname)
95 + }
96 + }))
97 + nameedit.parentNode.replaceChild(newname, nameedit)
98 + }
99 + })
100 + )
101 + editname.parentNode.replaceChild(nameedit, editname)
102 + }
103 + })
104 + )
105 +
106 + var editimage = h('span',
107 + h('button.btn', 'New image', {
108 + onclick: function () {
109 + var upload =
110 + h('span',
111 + hyperfile.asDataURL(function (data) {
112 + if(data) {
113 + //img.src = data
114 + var _data = dataurl.parse(data)
115 + pull(
116 + pull.once(_data.data),
117 + sbot.addblob(function (err, hash) {
118 + if(err) return alert(err.stack)
119 + selected = {
120 + link: hash,
121 + size: _data.data.length,
122 + type: _data.mimetype
123 + }
124 + })
125 + )
126 + }
127 + }),
128 + h('button.btn', 'Preview image', {
129 + onclick: function() {
130 + if (selected) {
131 + console.log(selected)
132 + var oldImage = document.getElementById('profileImage')
133 + var newImage = h('span.avatar--medium', h('img', {src: config.blobsUrl + selected.link}))
134 + var publish = h('button.btn', 'Publish image', {
135 + onclick: function () {
136 + sbot.publish({
137 + type: 'about',
138 + about: src,
139 + image: selected
140 + }, function (err, published) {
141 + console.log(published)
142 + })
143 + }
144 + })
145 + upload.parentNode.replaceChild(publish, upload)
146 + oldImage.parentNode.replaceChild(newImage, oldImage)
147 + }
148 + }
149 + })
150 + )
151 + editimage.parentNode.replaceChild(upload, editimage)
152 + }
153 + })
154 + )
155 +
156 +
157 + var avatars = h('div.avatars',
158 + h('a', {href: '#' + src},
159 + h('span.avatar--medium#profileImage', avatar.image(src)),
160 + editname,
161 + h('br'),
162 + editimage
163 + )
164 + )
165 +
166 + pull(
167 + sbot.userStream({id: src, reverse: false, limit: 1}),
168 + pull.drain(function (msg) {
169 + var howlong = h('span', h('br'), ' arrived ', human(new Date(msg.value.timestamp)))
170 + avatars.appendChild(howlong)
171 + console.log(msg)
172 + })
173 + )
174 +
175 + var buttons = h('div.buttons')
176 +
177 + profile.firstChild.appendChild(avatars)
178 + profile.firstChild.appendChild(buttons)
179 + buttons.appendChild(tools.mute(src))
180 +
181 + setTimeout (function () {
182 + opts = {}
183 + opts.type = 'post'
184 + opts.mentions = '[' + name.textContent + '](' + src + ')'
185 + var composer = h('div#composer', h('div.message', compose(opts)))
186 + profile.appendChild(composer)
187 + }, 1000)
188 +
189 + //buttons.appendChild(writeMessage)
190 + buttons.appendChild(tools.follow(src))
191 +
192 + buttons.appendChild(h('a', {href: '#' + src}, h('button.btn', "View ", avatar.name(src), "'s feed")))
193 +}
194 +
195 +var userStream = function (src) {
196 + var content = h('div.content')
197 +
198 + var screen = document.getElementById('screen')
199 + screen.appendChild(hyperscroll(content))
200 + function createStream (opts) {
201 + return pull(
202 + More(sbot.userStream, opts, ['value', 'sequence']),
203 + pull.map(function (msg) {
204 + return render(h('div', msg))
205 + })
206 + )
207 + }
208 +
209 + pull(
210 + createStream({old: false, limit: 10, id: src}),
211 + stream.top(content)
212 + )
213 +
214 + pull(
215 + createStream({reverse: true, live: false, limit: 10, id: src}),
216 + stream.bottom(content)
217 + )
218 +
219 + var profile = h('div.content#profile', h('div.message'))
220 +
221 + if (screen.firstChild.firstChild) {
222 + screen.firstChild.insertBefore(profile, screen.firstChild.firstChild)
223 + } else {
224 + screen.firstChild.appendChild(profile)
225 + }
226 +
227 + var name = avatar.name(src)
228 +
229 + var editname = h('span',
230 + avatar.name(src),
231 + h('button.btn', 'New name', {
232 + onclick: function () {
233 + var nameput = h('input', {placeholder: name.textContent})
234 + var nameedit =
235 + h('span', nameput,
236 + h('button.btn', 'Preview', {
237 + onclick: function () {
238 + if (nameput.value[0] != '@')
239 + tobename = nameput.value
240 + else
241 + tobename = nameput.value.substring(1, 100)
242 + var newname = h('span', h('a', {href: '#' + src}, '@' + tobename), h('button.btn', 'Publish', {
243 + onclick: function () {
244 + var donename = h('span', h('a', {href: '#' + src}, '@' + tobename))
245 + sbot.publish({type: 'about', about: src, name: tobename})
246 + localStorage[src + 'name'] = tobename
247 + newname.parentNode.replaceChild(donename, newname)
248 + }
249 + }))
250 + nameedit.parentNode.replaceChild(newname, nameedit)
251 + }
252 + })
253 + )
254 + editname.parentNode.replaceChild(nameedit, editname)
255 + }
256 + })
257 + )
258 +
259 + var editimage = h('span',
260 + h('button.btn', 'New image', {
261 + onclick: function () {
262 + var upload =
263 + h('span',
264 + hyperfile.asDataURL(function (data) {
265 + if(data) {
266 + //img.src = data
267 + var _data = dataurl.parse(data)
268 + pull(
269 + pull.once(_data.data),
270 + sbot.addblob(function (err, hash) {
271 + if(err) return alert(err.stack)
272 + selected = {
273 + link: hash,
274 + size: _data.data.length,
275 + type: _data.mimetype
276 + }
277 + })
278 + )
279 + }
280 + }),
281 + h('button.btn', 'Preview image', {
282 + onclick: function() {
283 + if (selected) {
284 + console.log(selected)
285 + var oldImage = document.getElementById('profileImage')
286 + var newImage = h('span.avatar--medium', h('img', {src: config.blobsUrl + selected.link}))
287 + var publish = h('button.btn', 'Publish image', {
288 + onclick: function () {
289 + sbot.publish({
290 + type: 'about',
291 + about: src,
292 + image: selected
293 + }, function (err, published) {
294 + console.log(published)
295 + })
296 + }
297 + })
298 + upload.parentNode.replaceChild(publish, upload)
299 + oldImage.parentNode.replaceChild(newImage, oldImage)
300 + }
301 + }
302 + })
303 + )
304 + editimage.parentNode.replaceChild(upload, editimage)
305 + }
306 + })
307 + )
308 +
309 +
310 + var avatars = h('div.avatars',
311 + h('a', {href: '#' + src},
312 + h('span.avatar--medium#profileImage', avatar.image(src)),
313 + editname,
314 + h('br'),
315 + editimage
316 + )
317 + )
318 +
319 +
320 + pull(
321 + sbot.userStream({id: src, reverse: false, limit: 1}),
322 + pull.drain(function (msg) {
323 + var howlong = h('span', h('br'), ' arrived ', human(new Date(msg.value.timestamp)))
324 + avatars.appendChild(howlong)
325 + console.log(msg)
326 + })
327 + )
328 +
329 +
330 +
331 + var buttons = h('div.buttons')
332 +
333 + profile.firstChild.appendChild(avatars)
334 + profile.firstChild.appendChild(buttons)
335 + buttons.appendChild(tools.mute(src))
336 +
337 + buttons.appendChild(tools.follow(src))
338 +
339 + buttons.appendChild(h('a', {href: '#wall/' + src}, h('button.btn', "Write on ", avatar.name(src), "'s wall")))
340 +}
341 +
342 +var msgThread = function (src) {
343 +
344 + var content = h('div.content')
345 + var screen = document.getElementById('screen')
346 + screen.appendChild(hyperscroll(content))
347 +
348 + pull(
349 + sbot.query({query: [{$filter: { value: { content: {root: src}, timestamp: { $gt: 1 }}}}], live: true}),
350 + pull.drain(function (msg) {
351 + if (msg.value) {
352 + content.appendChild(render(msg))
353 + }
354 + })
355 + )
356 +
357 +
358 + sbot.get(src, function (err, data) {
359 + if (err) {
360 + var message = h('div.message', 'Missing message!')
361 + content.appendChild(message)
362 + }
363 + if (data) {
364 + data.value = data
365 + data.key = src
366 + console.log(data)
367 + var rootMsg = render(data)
368 +
369 + if (content.firstChild) {
370 + content.insertBefore(rootMsg, content.firstChild)
371 + } else {
372 + content.appendChild(rootMsg)
373 + }
374 + if (data.value.content.type == 'git-repo') {
375 + pull(
376 + sbot.backlinks({query: [{$filter: {value: {content: {type: 'git-update'}}, dest: src}}]}),
377 + pull.drain(function (msg) {
378 + if (msg.value) {
379 + content.appendChild(render(msg))
380 + }
381 + })
382 + )
383 + }
384 +
385 + }
386 + })
387 +}
388 +
389 +var keyPage = function () {
390 + var screen = document.getElementById('screen')
391 +
392 + var importKey = h('textarea.import', {placeholder: 'Import a new public/private key', name: 'textarea', style: 'width: 97%; height: 100px;'})
393 +
394 + var content = h('div.content',
395 + h('div.message#key',
396 + h('h1', 'Your Key'),
397 + h('p', {innerHTML: 'Your public/private key is: <pre><code>' + localStorage[config.caps.shs + '/secret'] + '</code></pre>'},
398 + h('button.btn', {onclick: function (e){
399 + localStorage[config.caps.shs +'/secret'] = ''
400 + alert('Your public/private key has been deleted')
401 + e.preventDefault()
402 + location.hash = ""
403 + location.reload()
404 + }}, 'Delete Key')
405 + ),
406 + h('hr'),
407 + h('form',
408 + importKey,
409 + h('button.btn', {onclick: function (e){
410 + if(importKey.value) {
411 + localStorage[config.caps.shs + '/secret'] = importKey.value.replace(/\s+/g, ' ')
412 + e.preventDefault()
413 + alert('Your public/private key has been updated')
414 + }
415 + location.hash = ""
416 + location.reload()
417 + }}, 'Import key'),
418 + )
419 + )
420 + )
421 +
422 + screen.appendChild(hyperscroll(content))
423 +}
424 +
425 +function everythingStream () {
426 +
427 + var screen = document.getElementById('screen')
428 + var content = h('div.content')
429 +
430 + screen.appendChild(hyperscroll(content))
431 +
432 + function createStream (opts) {
433 + return pull(
434 + Next(sbot.query, opts, ['value', 'timestamp']),
435 + pull.map(function (msg) {
436 + if (msg.value) {
437 + if (msg.value.timestamp > Date.now()) {
438 + return h('div.future')
439 + } else {
440 + return render(msg)
441 + }
442 + }
443 + })
444 + )
445 + }
446 +
447 + pull(
448 + createStream({
449 + limit: 10,
450 + reverse: true,
451 + live: false,
452 + query: [{$filter: { value: { timestamp: { $gt: 0 }}}}]
453 + }),
454 + stream.bottom(content)
455 + )
456 +
457 + pull(
458 + createStream({
459 + limit: 10,
460 + old: false,
461 + live: true,
462 + query: [{$filter: { value: { timestamp: { $gt: 0 }}}}]
463 + }),
464 + stream.top(content)
465 + )
466 +}
467 +
468 +function backchannel () {
469 +
470 + var screen = document.getElementById('screen')
471 + var content = h('div.content')
472 +
473 + screen.appendChild(hyperscroll(content))
474 +
475 + var chatbox = h('input', {placeholder: 'Backchannel'})
476 +
477 + var chat = h('div.content')
478 +
479 + var publish = h('button.btn', 'Publish', {
480 + onclick: function () {
481 + if (chatbox.value) {
482 + var content = {
483 + text: chatbox.value,
484 + type: 'scat_message'
485 + }
486 + sbot.publish(content, function (err, msg) {
487 + if (err) throw err
488 + chatbox.value = ''
489 + console.log('Published!', msg)
490 + })
491 + }
492 + }
493 + })
494 +
495 + chat.appendChild(h('div.message', chatbox, publish))
496 +
497 + if (screen.firstChild.firstChild) {
498 + screen.firstChild.insertBefore(chat, screen.firstChild.firstChild)
499 + } else {
500 + screen.firstChild.appendChild(chat)
501 + }
502 +
503 + function createStream (opts) {
504 + return pull(
505 + Next(sbot.query, opts, ['value', 'timestamp']),
506 + pull.map(function (msg) {
507 + if (msg.value) {
508 + return render(msg)
509 + }
510 + })
511 + )
512 + }
513 +
514 + pull(
515 + createStream({
516 + limit: 10,
517 + reverse: true,
518 + live: false,
519 + query: [{$filter: { value: { content: {type: 'scat_message'}, timestamp: { $gt: 0 }}}}]
520 + }),
521 + stream.bottom(content)
522 + )
523 +
524 + pull(
525 + createStream({
526 + limit: 10,
527 + old: false,
528 + live: true,
529 + query: [{$filter: { value: { content: {type: 'scat_message'}, timestamp: { $gt: 0 }}}}]
530 + }),
531 + stream.top(content)
532 + )
533 +}
534 +
535 +function search (src) {
536 + console.log('search' + src)
537 +
538 + var content = h('div.content')
539 + var screen = document.getElementById('screen')
540 + screen.appendChild(hyperscroll(content))
541 +
542 + pull(
543 + sbot.search.query({query: src, limit: 100}),
544 + pull.drain(function (search) {
545 + content.appendChild(render(search))
546 + })
547 + )
548 +
549 +}
550 +
551 +function hash () {
552 + return window.location.hash.substring(1)
553 +}
554 +
555 +module.exports = function () {
556 + var src = hash()
557 +
558 + if (ref.isFeed(src)) {
559 + userStream(src)
560 + } else if (ref.isMsg(src)) {
561 + msgThread(src)
562 + } else if (ref.isFeed(src.substring(5))) {
563 + mentionsStream(src.substring(5))
564 + } else if (src == 'backchannel') {
565 + backchannel()
566 + } else if (src == 'key') {
567 + keyPage()
568 + } else if (src[0] == '?' || (src[0] == '#')) {
569 + if (src[0] == '#')
570 + search(src.split('%20').join(' '))
571 + else
572 + search(src.substr(1).split('%20').join(' '))
573 + } else {
574 + everythingStream()
575 + }
576 +}

Built with git-ssb-web