Commit 26964367b4576851356b5e7a87124ef6c8562dd2
decent 5: the garden and the wall
Ev Bogue committed on 11/17/2018, 6:35:42 PMParent: 4f422818b33f5b47c419b382a3fb55aeb837b014
Files changed
bin.js | changed |
config/inject.js | changed |
package-lock.json | changed |
package.json | changed |
readme.md | changed |
avatar.js | added |
decent.png | deleted |
compose.js | added |
config.js | added |
index.js | added |
keys.js | added |
manifest.json | added |
mvd-indexes.js | added |
mvd.png | added |
render.js | added |
scuttlebot.js | added |
ssb-ws/.travis.yml | added |
ssb-ws/LICENSE | added |
ssb-ws/README.md | added |
ssb-ws/index.js | added |
ssb-ws/json-api.js | added |
ssb-ws/package.json | added |
style.css | added |
style.css.json | added |
style.js | added |
tools.js | added |
views.js | added |
bin.js | ||
---|---|---|
@@ -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') | |
6 | 17 … | var open = require('opn') |
7 | -var muxrpcli = require('muxrpcli') | |
8 | 18 … | |
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 | |
10 | 25 … | |
11 | -config.keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret')) | |
26 … | +var config = require('./config/inject')(process.env.ssb_appname, minimist(conf)) | |
12 | 27 … | |
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 … | + | |
13 | 35 … | var manifestFile = path.join(config.path, 'manifest.json') |
14 | 36 … | |
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) | |
16 | 40 … | |
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 | |
21 | 43 … | |
22 | -if (argv[0] == 'server') { | |
23 | 44 … | var createSbot = require('scuttlebot') |
45 … | + .use(require('scuttlebot/plugins/unix-socket')) | |
46 … | + .use(require('scuttlebot/plugins/no-auth')) | |
47 … | + .use(require('scuttlebot/plugins/plugins')) | |
24 | 48 … | .use(require('scuttlebot/plugins/master')) |
25 | 49 … | .use(require('scuttlebot/plugins/gossip')) |
26 | 50 … | .use(require('scuttlebot/plugins/replicate')) |
27 | 51 … | .use(require('ssb-friends')) |
28 | 52 … | .use(require('ssb-blobs')) |
53 … | + .use(require('scuttlebot/plugins/invite')) | |
54 … | + .use(require('scuttlebot/plugins/local')) | |
55 … | + .use(require('scuttlebot/plugins/logging')) | |
29 | 56 … | .use(require('ssb-query')) |
57 … | + .use(require('ssb-backlinks')) | |
58 … | + .use(require('ssb-search')) | |
59 … | + .use(require('./mvd-indexes')) | |
30 | 60 … | .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')) | |
35 | 63 … | .use({ |
36 | 64 … | name: 'serve', |
37 | 65 … | version: '1.0.0', |
38 | 66 … | init: function (sbot) { |
39 | 67 … | sbot.ws.use(function (req, res, next) { |
40 | - var send = {} | |
41 | - send = config | |
68 … | + var send = config | |
42 | 69 … | 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() | |
44 | 74 … | if(req.url == '/') |
45 | - res.end(decentClient) | |
75 … | + res.end(compiledClient) | |
46 | 76 … | if(req.url == '/get-config') |
47 | 77 … | res.end(JSON.stringify(send)) |
48 | 78 … | else next() |
49 | 79 … | }) |
50 | 80 … | } |
51 | 81 … | }) |
52 | - | |
82 … | + // add third-party plugins | |
83 … | + require('scuttlebot/plugins/plugins').loadUserPlugins(createSbot, config) | |
84 … | + | |
85 … | + // start server | |
86 … | + | |
53 | 87 … | open('http://localhost:' + config.ws.port, {wait: false}) |
54 | - | |
88 … | + | |
89 … | + config.keys = keys | |
55 | 90 … | var server = createSbot(config) |
56 | - | |
91 … | + | |
92 … | + // write RPC manifest to ~/.ssb/manifest.json | |
57 | 93 … | fs.writeFileSync(manifestFile, JSON.stringify(server.getManifest(), null, 2)) |
58 | 94 … | |
95 … | + if(process.stdout.isTTY && (config.logging.level != 'info')) | |
96 … | + ProgressBar(server.progress) | |
59 | 97 … | } else { |
60 | 98 … | |
99 … | + // normal command: | |
100 … | + // create a client connection to the server | |
101 … | + | |
102 … | + // read manifest.json | |
61 | 103 … | var manifest |
62 | 104 … | try { |
63 | 105 … | manifest = JSON.parse(fs.readFileSync(manifestFile)) |
64 | 106 … | } catch (err) { |
@@ -68,40 +110,74 @@ | ||
68 | 110 … | ) |
69 | 111 … | } |
70 | 112 … | |
71 | 113 … | // connect |
72 | - require('ssb-client')(config.keys, { | |
114 … | + require('ssb-client')(keys, { | |
73 | 115 … | manifest: manifest, |
74 | 116 … | port: config.port, |
75 | 117 … | host: config.host||'localhost', |
76 | 118 … | caps: config.caps, |
77 | - key: config.key || config.keys.id | |
119 … | + key: config.key || keys.id | |
78 | 120 … | }, function (err, rpc) { |
79 | 121 … | if(err) { |
80 | 122 … | 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.') | |
83 | 126 … | if(config.verbose) throw err |
84 | 127 … | process.exit(1) |
85 | 128 … | } |
86 | 129 … | throw err |
87 | 130 … | } |
88 | 131 … | |
132 … | + // add aliases | |
133 … | + for (var k in cmdAliases) { | |
134 … | + rpc[k] = rpc[cmdAliases[k]] | |
135 … | + manifest[k] = manifest[cmdAliases[k]] | |
136 … | + } | |
137 … | + | |
89 | 138 … | // add some extra commands |
90 | - manifest.version = 'async' | |
139 … | +// manifest.version = 'async' | |
91 | 140 … | 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 … | +// } | |
96 | 145 … | rpc.config = function (cb) { |
97 | 146 … | console.log(JSON.stringify(config, null, 2)) |
98 | 147 … | cb() |
99 | 148 … | } |
100 | 149 … | |
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 … | + | |
101 | 179 … | // run commandline flow |
102 | 180 … | muxrpcli(argv, manifest, rpc, config.verbose) |
103 | 181 … | }) |
104 | 182 … | } |
105 | 183 … | |
106 | - | |
107 | - |
config/inject.js | ||
---|---|---|
@@ -1,83 +1,73 @@ | ||
1 | 1 … | var path = require('path') |
2 | 2 … | var home = require('os-homedir') |
3 … | + | |
3 | 4 … | var nonPrivate = require('non-private-ip') |
4 | 5 … | var merge = require('deep-extend') |
5 | -var id = require('ssb-keys') | |
6 … | + | |
6 | 7 … | var RC = require('rc') |
8 … | + | |
7 | 9 … | var SEC = 1e3 |
8 | 10 … | var MIN = 60*SEC |
9 | 11 … | |
10 | 12 … | 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' | |
57 | 14 … | 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, | |
62 | 22 … | host: nonPrivate.v4 || '', |
23 … | + port: 8008, | |
63 | 24 … | timeout: 0, |
64 | 25 … | pub: true, |
65 | 26 … | local: true, |
66 | 27 … | friends: { |
67 | 28 … | dunbar: 150, |
68 | - hops: 3 | |
29 … | + hops: 1 | |
69 | 30 … | }, |
31 … | + ws: { | |
32 … | + port: 8989 | |
33 … | + }, | |
70 | 34 … | gossip: { |
71 | 35 … | connections: 3 |
72 | 36 … | }, |
37 … | + connections: { | |
38 … | + outgoing: { | |
39 … | + net: [{ transform: "shs" }] | |
40 … | + } | |
41 … | + }, | |
73 | 42 … | path: path.join(HOME, '.' + name), |
74 | 43 … | timers: { |
75 | 44 … | connection: 0, |
76 | 45 … | reconnect: 5*SEC, |
77 | 46 … | ping: 5*MIN, |
78 | 47 … | handshake: 5*SEC |
79 | 48 … | }, |
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 … | + }, | |
80 | 60 … | master: [], |
81 | - party: true //disable quotas | |
61 … | + logging: { level: 'notice' } | |
82 | 62 … | }, 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 | |
83 | 71 … | } |
72 … | + | |
73 … | + |
package-lock.json | ||
---|---|---|
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.json | ||
---|---|---|
@@ -1,56 +1,60 @@ | ||
1 | 1 … | { |
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", | |
5 | 6 … | "scripts": { |
6 | 7 … | "start": "node bin server", |
8 … | + "decent": "node bin server --appname=decent", | |
7 | 9 … | "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" | |
10 | 12 … | }, |
11 | - "author": "Ev Bogue", | |
13 … | + "devDependencies": { | |
14 … | + "browserify": "^16.2.3", | |
15 … | + "indexhtmlify": "^1.3.1" | |
16 … | + }, | |
17 … | + "author": "Ev Bogue <ev@evbogue.com>", | |
12 | 18 … | "license": "MIT", |
13 | 19 … | "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", | |
21 | 31 … | "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", | |
24 | 34 … | "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", | |
26 | 39 … | "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" | |
55 | 59 … | } |
56 | 60 … | } |
readme.md | ||
---|---|---|
@@ -1,47 +1,38 @@ | ||
1 | -# Decent | |
1 … | +# decent 5 | |
2 | 2 … | |
3 | -### A decent(ralized) network for business and development | |
3 … | +### The Garden and The Wall | |
4 | 4 … | |
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. | |
6 | 6 … | |
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: | |
8 | 8 … | |
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. | |
10 | 15 … | |
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) | |
12 | 17 … | |
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. | |
14 | 19 … | |
15 | -### Try Decent in your browser | |
20 … | +In an ideal world Decent would include two features that are not available right now: | |
16 | 21 … | |
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 | |
20 | 24 … | |
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. | |
22 | 26 … | |
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/ | |
29 | 28 … | |
30 | -Navigate to http://localhost:3001/ to see your Decent. | |
29 … | +Remember! The first rule of Decent is: be decent. | |
31 | 30 … | |
32 | -### Embed Decent on your website | |
31 … | +### history | |
33 | 32 … | |
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) | |
37 | 34 … | |
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. | |
39 | 36 … | |
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.js | ||
---|---|---|
@@ -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 |
---|
compose.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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 |
---|
render.js | |||
---|---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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/LICENSE | ||
---|---|---|
@@ -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.md | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.css | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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