Commit 231a81f9e0adf80fa3566be93221cf1f5158fcb6
initial commit
Ev Bogue committed on 3/6/2019, 7:11:26 PMFiles changed
.gitignore | added |
avatar.js | added |
bin.js | added |
compose.js | added |
config.js | added |
index.js | added |
keys.js | added |
manifest.json | added |
package-lock.json | added |
package.json | added |
readme.md | added |
render.js | added |
scuttlebot.js | added |
style.css | added |
style.css.json | added |
style.js | added |
tools.js | added |
views.js | added |
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 … | +} |
bin.js | ||
---|---|---|
@@ -1,0 +1,186 @@ | ||
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('minsbot/lib/cli-cmd-aliases') | |
15 … | +var ProgressBar = require('minsbot/lib/progress') | |
16 … | +var packageJson = require('minsbot/package.json') | |
17 … | + | |
18 … | +var open = require('opn') | |
19 … | + | |
20 … | +//get config as cli options after --, options before that are | |
21 … | +//options to the command. | |
22 … | +var argv = process.argv.slice(2) | |
23 … | +var i = argv.indexOf('--') | |
24 … | +var conf = argv.slice(i+1) | |
25 … | +argv = ~i ? argv.slice(0, i) : argv | |
26 … | + | |
27 … | +var network = 'ssb' | |
28 … | +//var network = 'decent' | |
29 … | +//var network = 'testnet' | |
30 … | + | |
31 … | +var config = require('minsbot/config/inject')(network) | |
32 … | + | |
33 … | +keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret')) | |
34 … | + | |
35 … | +var liteClient = fs.readFileSync(path.join('./build/index.html')) | |
36 … | + | |
37 … | +var manifestFile = path.join(config.path, 'manifest.json') | |
38 … | + | |
39 … | +if (argv[0] == 'server') { | |
40 … | + console.log('my key ID:', keys.public) | |
41 … | + | |
42 … | + // special server command: | |
43 … | + // import sbot and start the server | |
44 … | + | |
45 … | + var createSbot = require('minsbot') | |
46 … | + .use(require('minsbot/plugins/plugins')) | |
47 … | + .use(require('minsbot/plugins/master')) | |
48 … | + .use(require('minsbot/plugins/gossip')) | |
49 … | + .use(require('minsbot/plugins/replicate')) | |
50 … | + .use(require('ssb-friends')) | |
51 … | + .use(require('ssb-blobs')) | |
52 … | + .use(require('minsbot/plugins/invite')) | |
53 … | + .use(require('minsbot/plugins/local')) | |
54 … | + .use(require('minsbot/plugins/logging')) | |
55 … | + .use(require('minsbot/query')) | |
56 … | + .use(require('ssb-links')) | |
57 … | + .use(require('ssb-backlinks')) | |
58 … | + .use(require('minsbot/decent-ws')) | |
59 … | + .use(require('ssb-ebt')) | |
60 … | + .use({ | |
61 … | + name: 'serve', | |
62 … | + version: '1.0.0', | |
63 … | + init: function (sbot) { | |
64 … | + sbot.ws.use(function (req, res, next) { | |
65 … | + //res.setHeader('Access-Control-Allow-Origin', '*') | |
66 … | + var send = config | |
67 … | + delete send.keys // very important to keep this, as it removes the server keys from the config before broadcast | |
68 … | + send.address = sbot.ws.getAddress() | |
69 … | + sbot.invite.create({modern: true}, function (err, cb) { | |
70 … | + send.invite = cb | |
71 … | + }) | |
72 … | + if(req.url == '/') | |
73 … | + res.end(liteClient) | |
74 … | + if(req.url == '/get-config') | |
75 … | + res.end(JSON.stringify(send)) | |
76 … | + else next() | |
77 … | + }) | |
78 … | + } | |
79 … | + }) | |
80 … | + | |
81 … | + open('http://localhost:' + config.ws.port, {wait: false}) | |
82 … | + | |
83 … | + | |
84 … | + // add third-party plugins | |
85 … | + //require('./plugins/plugins').loadUserPlugins(createSbot, config) | |
86 … | + | |
87 … | + // start server | |
88 … | + | |
89 … | + config.keys = keys | |
90 … | + var server = createSbot(config) | |
91 … | + | |
92 … | + // write RPC manifest to ~/.ssb/manifest.json | |
93 … | + fs.writeFileSync(manifestFile, JSON.stringify(server.getManifest(), null, 2)) | |
94 … | + | |
95 … | + | |
96 … | + ProgressBar(server.progress) | |
97 … | + | |
98 … | +} else { | |
99 … | + | |
100 … | + // normal command: | |
101 … | + // create a client connection to the server | |
102 … | + | |
103 … | + // read manifest.json | |
104 … | + var manifest | |
105 … | + try { | |
106 … | + manifest = JSON.parse(fs.readFileSync(manifestFile)) | |
107 … | + } catch (err) { | |
108 … | + throw explain(err, | |
109 … | + 'no manifest file' | |
110 … | + + '- should be generated first time server is run' | |
111 … | + ) | |
112 … | + } | |
113 … | + | |
114 … | + // connect | |
115 … | + require('ssb-client')(keys, { | |
116 … | + manifest: manifest, | |
117 … | + port: config.port, | |
118 … | + host: config.host||'localhost', | |
119 … | + caps: config.caps, | |
120 … | + key: config.key || keys.id | |
121 … | + }, function (err, rpc) { | |
122 … | + if(err) { | |
123 … | + if (/could not connect/.test(err.message)) { | |
124 … | + console.log('Error: Could not connect to the scuttlebot server.') | |
125 … | + console.log('Use the "server" command to start it.') | |
126 … | + if(config.verbose) throw err | |
127 … | + process.exit(1) | |
128 … | + } | |
129 … | + throw err | |
130 … | + } | |
131 … | + | |
132 … | + // add aliases | |
133 … | + for (var k in cmdAliases) { | |
134 … | + rpc[k] = rpc[cmdAliases[k]] | |
135 … | + manifest[k] = manifest[cmdAliases[k]] | |
136 … | + } | |
137 … | + | |
138 … | + // add some extra commands | |
139 … | + manifest.version = 'async' | |
140 … | + manifest.config = 'sync' | |
141 … | + rpc.version = function (cb) { | |
142 … | + console.log(require('./package.json').version) | |
143 … | + cb() | |
144 … | + } | |
145 … | + rpc.config = function (cb) { | |
146 … | + console.log(JSON.stringify(config, null, 2)) | |
147 … | + cb() | |
148 … | + } | |
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 … | + | |
179 … | + // run commandline flow | |
180 … | + muxrpcli(argv, manifest, rpc, config.verbose) | |
181 … | + }) | |
182 … | +} | |
183 … | + | |
184 … | + | |
185 … | + | |
186 … | + |
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,36 @@ | ||
1 … | +var http = require('http') | |
2 … | + | |
3 … | +module.exports = function () { | |
4 … | + //var host = window.location.origin | |
5 … | + | |
6 … | + var host = 'http://localhost:8989' | |
7 … | + | |
8 … | + function getConfig () { | |
9 … | + http.get(host + '/get-config', function (res) { | |
10 … | + res.on('data', function (data, remote) { | |
11 … | + var config = data | |
12 … | + localStorage[host] = config | |
13 … | + }) | |
14 … | + }) | |
15 … | + } | |
16 … | + | |
17 … | + if (localStorage[host]) { | |
18 … | + var config = JSON.parse(localStorage[host]) | |
19 … | + getConfig() | |
20 … | + } else { | |
21 … | + getConfig() | |
22 … | + setTimeout(function () { | |
23 … | + location.reload() | |
24 … | + }, 1000) | |
25 … | + } | |
26 … | + | |
27 … | + config.blobsUrl = host + '/blobs/get/' | |
28 … | + config.emojiUrl = host + '/img/emoji/' | |
29 … | + | |
30 … | + if (config.ws.remote) | |
31 … | + config.remote = config.ws.remote | |
32 … | + else | |
33 … | + config.remote = config.address | |
34 … | + | |
35 … | + return config | |
36 … | +} |
index.js | ||
---|---|---|
@@ -1,0 +1,54 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | +var route = require('./views') | |
3 … | +var avatar = require('./avatar') | |
4 … | +var compose = require('./compose') | |
5 … | +var id = require('./keys').id | |
6 … | + | |
7 … | +// append global stylesheet to <head> (built using style.js) | |
8 … | +document.head.appendChild(h('style', require('./style.css.json'))) | |
9 … | + | |
10 … | +// #screen is the element everything else gets appended. | |
11 … | +var screen = h('div#screen') | |
12 … | + | |
13 … | +// navbar is appended to the top of the document (outside of screen) | |
14 … | +var nav = h('div.navbar', | |
15 … | + h('div.internal', | |
16 … | + h('li', h('a', {href: '#' + id}, h('span.avatar--small', avatar.image(id)))), | |
17 … | + h('li', h('a', {href: '#' + id}, avatar.name(id))), | |
18 … | + h('li', h('a', 'New Post', { | |
19 … | + onclick: function () { | |
20 … | + if (document.getElementById('composer')) { return } | |
21 … | + else { | |
22 … | + var currentScreen = document.getElementById('screen') | |
23 … | + var opts = {} | |
24 … | + opts.type = 'post' | |
25 … | + var composer = h('div.content#composer', h('div.message', compose(opts))) | |
26 … | + if (currentScreen.firstChild.firstChild) { | |
27 … | + currentScreen.firstChild.insertBefore(composer, currentScreen.firstChild.firstChild) | |
28 … | + } else { | |
29 … | + currentScreen.firstChild.appendChild(composer) | |
30 … | + } | |
31 … | + } | |
32 … | + } | |
33 … | + })), | |
34 … | + h('li', h('a', {href: '#' }, 'All')), | |
35 … | + h('li', h('a', {href: '#wall/' + id }, 'Wall')), | |
36 … | + h('li', h('a', {href: '#key' }, 'Key')) | |
37 … | + ) | |
38 … | +) | |
39 … | + | |
40 … | +//append those two elements | |
41 … | +document.body.appendChild(nav) | |
42 … | +document.body.appendChild(screen) | |
43 … | + | |
44 … | +// call the router | |
45 … | +route() | |
46 … | + | |
47 … | +// this code clears the #screen element when the #hash changes | |
48 … | +window.onhashchange = function () { | |
49 … | + var oldscreen = document.getElementById('screen') | |
50 … | + var newscreen = h('div#screen') | |
51 … | + oldscreen.parentNode.replaceChild(newscreen, oldscreen) | |
52 … | + route() | |
53 … | +} | |
54 … | + |
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 … | +} |
package-lock.json | ||
---|---|---|
The diff is too large to show. Use a local git client to view these changes. Old file size: 0 bytes New file size: 242276 bytes |
package.json | ||
---|---|---|
@@ -1,0 +1,57 @@ | ||
1 … | +{ | |
2 … | + "name": "0qc", | |
3 … | + "version": "1.0.0", | |
4 … | + "description": "one query client (experimental)", | |
5 … | + "main": "index.js", | |
6 … | + "scripts": { | |
7 … | + "start": "node bin server", | |
8 … | + "build": "node style.js && mkdir -p build && browserify index.js | indexhtmlify > build/index.html" | |
9 … | + }, | |
10 … | + "devDependencies": { | |
11 … | + "browserify": "^16.2.2", | |
12 … | + "indexhtmlify": "^1.3.1" | |
13 … | + }, | |
14 … | + "author": "Ev Bogue <ev@evbogue.com>", | |
15 … | + "license": "MIT", | |
16 … | + "dependencies": { | |
17 … | + "dataurl-": "^0.1.0", | |
18 … | + "decent-ws": "1.0.4", | |
19 … | + "deep-extend": "^0.6.0", | |
20 … | + "diff": "^3.5.0", | |
21 … | + "emoji-server": "^1.0.0", | |
22 … | + "human-time": "0.0.1", | |
23 … | + "hyperfile": "^2.0.0", | |
24 … | + "hyperloadmore": "^1.1.0", | |
25 … | + "hyperscript": "^2.0.2", | |
26 … | + "hyperscroll": "^1.0.0", | |
27 … | + "minsbot": "^1.0.0", | |
28 … | + "multiblob-http": "^0.4.2", | |
29 … | + "muxrpcli": "^1.1.0", | |
30 … | + "non-private-ip": "^1.4.3", | |
31 … | + "opn": "^5.3.0", | |
32 … | + "os-homedir": "^1.0.2", | |
33 … | + "pull-more": "^1.1.0", | |
34 … | + "pull-next-query": "^1.0.0", | |
35 … | + "pull-reconnect": "0.0.3", | |
36 … | + "pull-stream": "^3.6.8", | |
37 … | + "pull-stringify": "^2.0.0", | |
38 … | + "rc": "^1.2.7", | |
39 … | + "simple-mime": "^0.1.0", | |
40 … | + "split-buffer": "^1.0.0", | |
41 … | + "ssb-avatar": "^0.2.0", | |
42 … | + "ssb-backlinks": "^0.7.1", | |
43 … | + "ssb-blobs": "^1.1.5", | |
44 … | + "ssb-client": "^4.5.7", | |
45 … | + "ssb-ebt": "^5.1.5", | |
46 … | + "ssb-feed": "^2.3.0", | |
47 … | + "ssb-friends": "^2.4.0", | |
48 … | + "ssb-keys": "^7.0.16", | |
49 … | + "ssb-links": "^3.0.3", | |
50 … | + "ssb-markdown": "^3.6.0", | |
51 … | + "ssb-mentions": "^0.5.0", | |
52 … | + "ssb-ref": "^2.11.1", | |
53 … | + "ssb-search": "^1.0.1", | |
54 … | + "stack": "^0.1.0", | |
55 … | + "visualize-buffer": "0.0.1" | |
56 … | + } | |
57 … | +} |
readme.md | ||
---|---|---|
@@ -1,0 +1,30 @@ | ||
1 … | +# one query client (0qc) | |
2 … | + | |
3 … | +so I woke up one morning and it occurred to me that I could render all information linking to a message using a single `sbot.backlinks` query. | |
4 … | + | |
5 … | +In [mvd](%NPNNvcnTMZUFZSWl/2Z4XX+YSdqsqOhyPacp+lgpQUw=.sha256) I'd been using a few queries per message to grab different things, and each query added additional complexity. Why not simplify by doing one query and then updating the message object based on what was returned? | |
6 … | + | |
7 … | +This is an experimental client that is a fork, and simplify of [mvd](%NPNNvcnTMZUFZSWl/2Z4XX+YSdqsqOhyPacp+lgpQUw=.sha256) | |
8 … | + | |
9 … | +To use it grab `minsbot` from `git-ssb` | |
10 … | + | |
11 … | +``` | |
12 … | +git clone ssb://%bEpNCZyU/f+Hi0Jhk6C9SQoNorT9NMyJen2NTCS0vaU=.sha256 minsbot | |
13 … | +cd minsbot | |
14 … | +npm install | |
15 … | +npm start | |
16 … | +``` | |
17 … | + | |
18 … | +Then navigate to another terminal and clone down `0qc` | |
19 … | + | |
20 … | +``` | |
21 … | +git clone | |
22 … | +npm install | |
23 … | +npm run build | |
24 … | +npm start | |
25 … | +``` | |
26 … | + | |
27 … | +and it should 'just work' because `0qc` will grab its sbot config from `http://localhost:8989` which is your `minsbot` server's websocket. If your `minsbot` server is located elsewhere, edit the hostname in `config` to target your sbot server. Ex: `http://evbogue.com:8989`. | |
28 … | + | |
29 … | +--- | |
30 … | +MIT |
render.js | |||
---|---|---|---|
@@ -1,0 +1,235 @@ | |||
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 ssbKeys = require('ssb-keys') | ||
15 … | +var keys = require('./keys') | ||
16 … | + | ||
17 … | +var diff = require('diff') | ||
18 … | + | ||
19 … | +function hash () { | ||
20 … | + return window.location.hash.substring(1) | ||
21 … | +} | ||
22 … | + | ||
23 … | +module.exports.render = function (msg) { | ||
24 … | + | ||
25 … | + var message = h('div.message#' + msg.key.substring(0, 44)) | ||
26 … | + | ||
27 … | + var messageContent = h('div.messageContent') | ||
28 … | + message.appendChild(messageContent) | ||
29 … | + | ||
30 … | + pull( | ||
31 … | + sbot.backlinks({ | ||
32 … | + query: [{$filter: {dest: msg.key}}], | ||
33 … | + live: true, | ||
34 … | + reverse: false, | ||
35 … | + index: 'DTA' | ||
36 … | + }), | ||
37 … | + pull.drain(function (data) { | ||
38 … | + if (data.sync) { | ||
39 … | + console.log('waiting') | ||
40 … | + } else { | ||
41 … | + var dataId = 'NOTNULL' | ||
42 … | + var dataId = document.getElementById(data.key.substring(0, 44)) | ||
43 … | + | ||
44 … | + if (dataId == null) { | ||
45 … | + console.log('rendering message') | ||
46 … | + if ( | ||
47 … | + (data.value.content.vote) || | ||
48 … | + (msg.key == data.value.content.root) || | ||
49 … | + (msg.key == data.value.content.repo) || | ||
50 … | + (msg.key == data.value.content.link) | ||
51 … | + ) { | ||
52 … | + message.appendChild(h('div.submessage', (exports.render(data)))) | ||
53 … | + } else if (!data.value.content.root) { | ||
54 … | + var link = h('span', ' backlinked from ', tools.messageLink(data.key)) | ||
55 … | + message.appendChild(h('div.submessage', h('div.messageContent', tools.mini(data, link)))) | ||
56 … | + } else { | ||
57 … | + //console.log('DOES NOT MATCH') | ||
58 … | + message.appendChild(h('div.submessage', (exports.render(data)))) | ||
59 … | + } | ||
60 … | + } else { console.log('message already rendered')} | ||
61 … | + } | ||
62 … | + }) | ||
63 … | + ) | ||
64 … | + | ||
65 … | + if (msg.value.content.type == 'post') { | ||
66 … | + | ||
67 … | + messageContent.appendChild(tools.header(msg)) | ||
68 … | + | ||
69 … | + if (msg.value.content.root) { | ||
70 … | + messageContent.appendChild(h('span', 're: ', tools.messageLink(msg.value.content.root))) | ||
71 … | + } | ||
72 … | + | ||
73 … | + messageContent.appendChild(h('div.message__body', tools.markdown(msg.value.content.text))) | ||
74 … | + return message | ||
75 … | + } | ||
76 … | + | ||
77 … | + if (msg.value.content.type == 'vote') { | ||
78 … | + if (msg.value.content.vote.value == 1) | ||
79 … | + 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))) | ||
80 … | + else if (msg.value.content.vote.value == -1) | ||
81 … | + 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))) | ||
82 … | + messageContent.appendChild(tools.mini(msg, link)) | ||
83 … | + return message | ||
84 … | + } | ||
85 … | + | ||
86 … | + if (msg.value.content.type == 'about') { | ||
87 … | + if (msg.value.content.image) { | ||
88 … | + var image = h('span.avatar--small', | ||
89 … | + ' identified ', | ||
90 … | + h('a', {href: '#' + msg.value.content.about}, avatar.cachedName(msg.value.content.about)), | ||
91 … | + ' as ', | ||
92 … | + h('img', {src: config.blobsUrl + msg.value.content.image.link}) | ||
93 … | + ) | ||
94 … | + messageContent.appendChild(tools.mini(msg, image)) | ||
95 … | + return message | ||
96 … | + } | ||
97 … | + if (msg.value.content.name) { | ||
98 … | + var name = h('span', | ||
99 … | + ' identified ', | ||
100 … | + h('a', {href: '#' + msg.value.content.about}, avatar.cachedName(msg.value.content.about)), | ||
101 … | + ' as ', msg.value.content.name | ||
102 … | + ) | ||
103 … | + messageContent.appendChild(tools.mini(msg, name)) | ||
104 … | + return message | ||
105 … | + } | ||
106 … | + } | ||
107 … | + | ||
108 … | + if (msg.value.content.type == 'label'){ | ||
109 … | + var content = h('span', ' labeled ', tools.messageLink(msg.value.content.link), ' as ', h('mark', h('a', {href: '#label/' + msg.value.content.label}, msg.value.content.label))) | ||
110 … | + messageContent.appendChild(tools.mini(msg, content)) | ||
111 … | + return message | ||
112 … | + } | ||
113 … | + | ||
114 … | + if (msg.value.content.type == 'queue') { | ||
115 … | + if (msg.value.content.queue == true) { | ||
116 … | + var content = h('span', ' added ', tools.messageLink(msg.value.content.message), ' to their ', h('a', {href: '#queue'}, 'queue')) | ||
117 … | + messageContent.appendChild(tools.mini(msg, content)) | ||
118 … | + } | ||
119 … | + if (msg.value.content.queue == false) { | ||
120 … | + var content = h('span', ' removed ', tools.messageLink(msg.value.content.message), ' from their ', h('a', {href: '#queue'}, 'queue')) | ||
121 … | + messageContent.appendChild(tools.mini(msg, content)) | ||
122 … | + | ||
123 … | + } | ||
124 … | + return message | ||
125 … | + } | ||
126 … | + | ||
127 … | + if (msg.value.content.type == 'scat_message') { | ||
128 … | + var src = hash() | ||
129 … | + if (src != 'backchannel') { | ||
130 … | + messageContent.appendChild(h('button.btn.right', h('a', {href: '#backchannel'}, 'Chat'))) | ||
131 … | + } | ||
132 … | + messageContent.appendChild(tools.mini(msg, ' ' + msg.value.content.text)) | ||
133 … | + return message | ||
134 … | + } | ||
135 … | + | ||
136 … | + else if (msg.value.content.type == 'contact') { | ||
137 … | + if (msg.value.content.contact) { | ||
138 … | + var contact = h('a', {href: '#' + msg.value.content.contact}, avatar.name(msg.value.content.contact)) | ||
139 … | + } else { var contact = h('p', 'no contact named')} | ||
140 … | + | ||
141 … | + if (msg.value.content.following == true) { | ||
142 … | + var following = h('span', ' follows ', contact) | ||
143 … | + messageContent.appendChild(tools.mini(msg, following)) | ||
144 … | + } | ||
145 … | + if (msg.value.content.following == false) { | ||
146 … | + var unfollowing = h('span', ' unfollows ', contact) | ||
147 … | + messageContent.appendChild(tools.mini(msg, unfollowing)) | ||
148 … | + } | ||
149 … | + if (msg.value.content.blocking == true) { | ||
150 … | + var blocking = h('span', ' blocks ', contact) | ||
151 … | + messageContent.appendChild(tools.mini(msg, blocking)) | ||
152 … | + } | ||
153 … | + if (msg.value.content.blocking == false) { | ||
154 … | + var unblocking = h('span', ' unblocks ', contact) | ||
155 … | + messageContent.appendChild(tools.mini(msg, unblocking)) | ||
156 … | + } | ||
157 … | + return message | ||
158 … | + | ||
159 … | + } | ||
160 … | + | ||
161 … | + | ||
162 … | + if (msg.value.content.type == 'git-update') { | ||
163 … | + | ||
164 … | + messageContent.appendChild(tools.header(msg)) | ||
165 … | + | ||
166 … | + var reponame = h('p', 'pushed to ', h('a', {href: '#' + msg.value.content.repo}, msg.value.content.repo)) | ||
167 … | + | ||
168 … | + var cloneurl = h('pre', 'git clone ssb://' + msg.value.content.repo) | ||
169 … | + | ||
170 … | + messageContent.appendChild(reponame) | ||
171 … | + | ||
172 … | + ssbAvatar(sbot, id, msg.value.content.repo, function (err, data) { | ||
173 … | + if (data) { | ||
174 … | + var actualname = h('p', 'pushed to ', h('a', {href: '#' + msg.value.content.repo}, '%' + data.name)) | ||
175 … | + reponame.parentNode.replaceChild(actualname, reponame) | ||
176 … | + } | ||
177 … | + }) | ||
178 … | + | ||
179 … | + messageContent.appendChild(cloneurl) | ||
180 … | + | ||
181 … | + var commits = h('ul') | ||
182 … | + if (msg.value.content.commits) { | ||
183 … | + msg.value.content.commits.map(function (commit) { | ||
184 … | + commits.appendChild(h('li', h('code', commit.sha1), ' - ', commit.title)) | ||
185 … | + }) | ||
186 … | + } | ||
187 … | + | ||
188 … | + messageContent.appendChild(commits) | ||
189 … | + | ||
190 … | + return message | ||
191 … | + } | ||
192 … | + | ||
193 … | + if (msg.value.content.type == 'git-repo') { | ||
194 … | + messageContent.appendChild(tools.header(msg)) | ||
195 … | + | ||
196 … | + var reponame = h('p', 'git-ssb repo ', h('a', {href: '#' + msg.key}, msg.key)) | ||
197 … | + | ||
198 … | + messageContent.appendChild(reponame) | ||
199 … | + | ||
200 … | + ssbAvatar(sbot, id, msg.key, function (err, data) { | ||
201 … | + if (data) | ||
202 … | + var actualname = h('p', 'git-ssb repo ', h('a', {href: '#' + msg.key}, '%' + data.name)) | ||
203 … | + reponame.parentNode.replaceChild(actualname, reponame) | ||
204 … | + }) | ||
205 … | + | ||
206 … | + var cloneurl = h('pre', 'git clone ssb://' + msg.key) | ||
207 … | + messageContent.appendChild(cloneurl) | ||
208 … | + return message | ||
209 … | + } | ||
210 … | + | ||
211 … | + if (typeof msg.value.content === 'string') { | ||
212 … | + var unboxed = ssbKeys.unbox(msg.value.content, keys) | ||
213 … | + if (unboxed) { | ||
214 … | + msg.value.content = unboxed | ||
215 … | + msg.value.private = true | ||
216 … | + return module.exports(msg) | ||
217 … | + } else { | ||
218 … | + var privateMsg = h('span', ' sent a private message.') | ||
219 … | + messageContent.appendChild(tools.mini(msg, privateMsg)) | ||
220 … | + return message | ||
221 … | + } | ||
222 … | + } | ||
223 … | + | ||
224 … | + else { | ||
225 … | + //FULL FALLBACK | ||
226 … | + //message.appendChild(tools.header(msg)) | ||
227 … | + messageContent.appendChild(tools.header(msg)) | ||
228 … | + messageContent.appendChild(h('pre', tools.rawJSON(msg.value))) | ||
229 … | + | ||
230 … | + //MINI FALLBACK | ||
231 … | + //var fallback = h('span', ' ' + msg.value.content.type) | ||
232 … | + //message.appendChild(tools.mini(msg, fallback)) | ||
233 … | + return message | ||
234 … | + } | ||
235 … | +} |
scuttlebot.js | ||
---|---|---|
@@ -1,0 +1,86 @@ | ||
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 … | +var config = require('./config')() | |
6 … | +var createClient = require('ssb-client') | |
7 … | +var createFeed = require('ssb-feed') | |
8 … | +var keys = require('./keys') | |
9 … | + | |
10 … | +var CACHE = {} | |
11 … | + | |
12 … | +var rec = reconnect(function (isConn) { | |
13 … | + function notify (value) { | |
14 … | + isConn(value) | |
15 … | + } | |
16 … | + | |
17 … | + createClient(keys, { | |
18 … | + manifest: require('./manifest.json'), | |
19 … | + remote: config.remote, | |
20 … | + caps: config.caps | |
21 … | + }, function (err, _sbot) { | |
22 … | + if(err) | |
23 … | + return notify(err) | |
24 … | + | |
25 … | + sbot = _sbot | |
26 … | + sbot.on('closed', function () { | |
27 … | + sbot = null | |
28 … | + notify(new Error('closed')) | |
29 … | + }) | |
30 … | + | |
31 … | + notify() | |
32 … | + }) | |
33 … | +}) | |
34 … | + | |
35 … | +var internal = { | |
36 … | + getLatest: rec.async(function (id, cb) { | |
37 … | + sbot.getLatest(id, cb) | |
38 … | + }), | |
39 … | + add: rec.async(function (msg, cb) { | |
40 … | + sbot.add(msg, cb) | |
41 … | + }) | |
42 … | +} | |
43 … | + | |
44 … | +var feed = createFeed(internal, keys, {remote: true}) | |
45 … | + | |
46 … | +module.exports = { | |
47 … | + backlinks: rec.source(function (query) { | |
48 … | + return sbot.backlinks.read(query) | |
49 … | + }), | |
50 … | + links: rec.source(function (query) { | |
51 … | + return sbot.links(query) | |
52 … | + }), | |
53 … | + query: rec.source(function (query) { | |
54 … | + return sbot.query.read(query) | |
55 … | + }), | |
56 … | + get: rec.async(function (key, cb) { | |
57 … | + if(CACHE[key]) cb(null, CACHE[key]) | |
58 … | + else sbot.get(key, function (err, value) { | |
59 … | + if(err) return cb(err) | |
60 … | + cb(null, CACHE[key] = value) | |
61 … | + }) | |
62 … | + }), | |
63 … | + addblob: rec.sink(function (cb) { | |
64 … | + return sbot.blobs.add(cb) | |
65 … | + }), | |
66 … | + publish: rec.async(function (content, cb) { | |
67 … | + if(content.recps) | |
68 … | + content = ssbKeys.box(content, content.recps.map(function (e) { | |
69 … | + return ref.isFeed(e) ? e : e.link | |
70 … | + })) | |
71 … | + else if(content.mentions) | |
72 … | + content.mentions.forEach(function (mention) { | |
73 … | + if(ref.isBlob(mention.link)) { | |
74 … | + sbot.blobs.push(mention.link, function (err) { | |
75 … | + if(err) console.error(err) | |
76 … | + }) | |
77 … | + } | |
78 … | + }) | |
79 … | + feed.add(content, function (err, msg) { | |
80 … | + if(err) console.error(err) | |
81 … | + else if(!cb) console.log(msg) | |
82 … | + cb && cb(err, msg) | |
83 … | + }) | |
84 … | + }) | |
85 … | +} | |
86 … | + |
style.css | ||
---|---|---|
@@ -1,0 +1,177 @@ | ||
1 … | +body { | |
2 … | + margin: 0; | |
3 … | + background: black; | |
4 … | + font-family: sans-serif; | |
5 … | + color: #f5f5f5; | |
6 … | + font-size: 14px; | |
7 … | + line-height: 20px; | |
8 … | +} | |
9 … | + | |
10 … | +#screen { | |
11 … | + position: absolute; | |
12 … | + top: 35px; | |
13 … | + bottom: 0px; | |
14 … | + left: 0px; | |
15 … | + right: 0px; | |
16 … | +} | |
17 … | + | |
18 … | +.hyperscroll { | |
19 … | + width: 100%; | |
20 … | +} | |
21 … | + | |
22 … | +.header { | |
23 … | + padding-bottom: .7em; | |
24 … | + border-bottom: 1px solid #252525; | |
25 … | +} | |
26 … | + | |
27 … | +h1, h2, h3, h4, h5, h6 { | |
28 … | + font-size: 1.2em; | |
29 … | + margin-top: .35ex; | |
30 … | +} | |
31 … | + | |
32 … | +hr { | |
33 … | + border: solid #222; | |
34 … | + clear: both; | |
35 … | + border-width: 1px 0 0; | |
36 … | + height: 0; | |
37 … | + margin-bottom: .9em; | |
38 … | +} | |
39 … | + | |
40 … | +p { | |
41 … | + margin-top: .35ex; | |
42 … | + margin-bottom: 10px; | |
43 … | +} | |
44 … | + | |
45 … | +a { | |
46 … | + color: cyan; | |
47 … | + text-decoration: none; | |
48 … | +} | |
49 … | + | |
50 … | +a:hover, a:focus { | |
51 … | + color: violet; | |
52 … | + text-decoration: underline; | |
53 … | +} | |
54 … | + | |
55 … | +.navbar { | |
56 … | + background: #1b1b1b; | |
57 … | + *background: linear-gradient(#222, #111); | |
58 … | + border-bottom: 1px solid #252525; | |
59 … | +} | |
60 … | + | |
61 … | +.navbar { | |
62 … | + width: 100%; | |
63 … | + position: fixed; | |
64 … | + z-index: 1000; | |
65 … | + margin: 0; | |
66 … | + padding-top: .3em; | |
67 … | + padding-bottom: .3em; | |
68 … | + left: 0; right: 0; | |
69 … | + top: 0; | |
70 … | +} | |
71 … | + | |
72 … | +.navbar .internal { | |
73 … | + max-width: 97%; | |
74 … | + margin-left: auto; | |
75 … | + margin-right: auto; | |
76 … | +} | |
77 … | + | |
78 … | +.navbar li { | |
79 … | + margin-top: .3em; | |
80 … | + float: left; | |
81 … | + margin-right: .6em; | |
82 … | + margin-left: .3em; | |
83 … | + list-style-type: none; | |
84 … | +} | |
85 … | + | |
86 … | +.navbar li.right { | |
87 … | + padding-left: .4em; | |
88 … | + padding-right: .4em; | |
89 … | + margin-top: .3em; | |
90 … | + margin-right: 1.7em; | |
91 … | + float: right; | |
92 … | + list-style-type: none; | |
93 … | + background: #333; | |
94 … | + border-radius: 100%; | |
95 … | +} | |
96 … | + | |
97 … | +.content { | |
98 … | + max-width: 760px; | |
99 … | + margin-left: auto; | |
100 … | + margin-right: auto; | |
101 … | +} | |
102 … | + | |
103 … | +.message, .message > *, .navbar, .navbar > * { | |
104 … | + animation: fadein .5s; | |
105 … | +} | |
106 … | + | |
107 … | +@keyframes fadein { | |
108 … | + from { opacity: 0; } | |
109 … | + to { opacity: 1; } | |
110 … | +} | |
111 … | + | |
112 … | +.messageContent { | |
113 … | + display: block; | |
114 … | + margin: .6em; | |
115 … | + background: #111; | |
116 … | + padding: .7em; | |
117 … | + border-radius: 3px; | |
118 … | + border: 1px solid #252525; | |
119 … | +} | |
120 … | + | |
121 … | +.submessage { | |
122 … | + margin-left: 2em; | |
123 … | +} | |
124 … | + | |
125 … | +.message img, .message video { | |
126 … | + max-width: 100%; | |
127 … | +} | |
128 … | + | |
129 … | +.avatar--small img { | |
130 … | + vertical-align: top; | |
131 … | + width: 1.4em; | |
132 … | + height: 1.4em; | |
133 … | + margin-right: .2em; | |
134 … | +} | |
135 … | + | |
136 … | +.avatar--medium img { | |
137 … | + float: left; | |
138 … | + vertical-align: top; | |
139 … | + width: 5em; | |
140 … | + height: 5em; | |
141 … | + margin-right: .5em; | |
142 … | + margin-bottom: .5em; | |
143 … | +} | |
144 … | + | |
145 … | +.right { | |
146 … | + float: right; | |
147 … | + margin-right: .25em; | |
148 … | +} | |
149 … | + | |
150 … | +.emoji { | |
151 … | + padding: .2em; | |
152 … | + *float: left; | |
153 … | + width: 1em; | |
154 … | + vertical-align: top; | |
155 … | +} | |
156 … | + | |
157 … | +pre { | |
158 … | + width: 100%; | |
159 … | + display: block; | |
160 … | +} | |
161 … | + | |
162 … | +code { | |
163 … | + display: inline-block; | |
164 … | + vertical-align: bottom; | |
165 … | +} | |
166 … | + | |
167 … | +code, pre { | |
168 … | + overflow: auto; | |
169 … | + word-break: break-all; | |
170 … | + word-wrap: break-word; | |
171 … | + white-space: pre; | |
172 … | + white-space: -moz-pre-wrap; | |
173 … | + white-space: pre-wrap; | |
174 … | + white-space: pre\9; | |
175 … | + color: violet; | |
176 … | +} | |
177 … | + |
style.css.json | ||
---|---|---|
@@ -1,0 +1,1 @@ | ||
1 … | +"body {\n margin: 0;\n background: black;\n font-family: sans-serif;\n color: #f5f5f5;\n font-size: 14px; \n line-height: 20px;\n}\n\n#screen {\n position: absolute;\n top: 35px;\n bottom: 0px;\n left: 0px;\n right: 0px;\n}\n\n.hyperscroll {\n width: 100%;\n}\n\n.header {\n padding-bottom: .7em;\n border-bottom: 1px solid #252525;\n}\n\nh1, h2, h3, h4, h5, h6 {\n font-size: 1.2em;\n margin-top: .35ex;\n}\n\nhr {\n border: solid #222;\n clear: both;\n border-width: 1px 0 0;\n height: 0;\n margin-bottom: .9em;\n}\n\np {\n margin-top: .35ex;\n margin-bottom: 10px;\n}\n\na {\n color: cyan;\n text-decoration: none;\n}\n\na:hover, a:focus {\n color: violet;\n text-decoration: underline;\n}\n\n.navbar {\n background: #1b1b1b;\n *background: linear-gradient(#222, #111);\n border-bottom: 1px solid #252525;\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: 760px;\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.messageContent {\n display: block;\n margin: .6em;\n background: #111;\n padding: .7em;\n border-radius: 3px;\n border: 1px solid #252525;\n}\n\n.submessage {\n margin-left: 2em;\n}\n\n.message img, .message video {\n max-width: 100%;\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.right {\n float: right;\n margin-right: .25em;\n}\n\n.emoji {\n padding: .2em;\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 {\n overflow: auto;\n word-break: break-all;\n word-wrap: break-word;\n white-space: pre;\n white-space: -moz-pre-wrap;\n white-space: pre-wrap;\n white-space: pre\\9;\n color: violet;\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,132 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | +var ref = require('ssb-ref') | |
3 … | +var ssbKeys = require('ssb-keys') | |
4 … | +var sbot = require('./scuttlebot') | |
5 … | +var config = require('./config')() | |
6 … | +var markdown = require('ssb-markdown') | |
7 … | +var id = require('./keys').id | |
8 … | +var avatar = require('./avatar') | |
9 … | +var human = require('human-time') | |
10 … | + | |
11 … | +module.exports.timestamp = function (msg, edited) { | |
12 … | + var timestamp = h('span.right', h('a', {href: '#' + msg.key}, human(new Date(msg.value.timestamp)))) | |
13 … | + return timestamp | |
14 … | +} | |
15 … | + | |
16 … | + | |
17 … | +module.exports.header = function (msg) { | |
18 … | + var header = h('div.header') | |
19 … | + | |
20 … | + header.appendChild(h('span.avatar', | |
21 … | + h('a', {href: '#' + msg.value.author}, | |
22 … | + h('span.avatar--small', avatar.cachedImage(msg.value.author)), | |
23 … | + avatar.cachedName(msg.value.author) | |
24 … | + ) | |
25 … | + ) | |
26 … | + ) | |
27 … | + | |
28 … | + header.appendChild(exports.timestamp(msg)) | |
29 … | + | |
30 … | + return header | |
31 … | +} | |
32 … | + | |
33 … | +module.exports.mini = function (msg, content) { | |
34 … | + var mini = h('div.mini') | |
35 … | + | |
36 … | + mini.appendChild( | |
37 … | + h('span.avatar', | |
38 … | + h('a', {href: '#' + msg.value.author}, | |
39 … | + h('span.avatar--small', avatar.cachedImage(msg.value.author)), | |
40 … | + avatar.cachedName(msg.value.author) | |
41 … | + ) | |
42 … | + ) | |
43 … | + ) | |
44 … | + var lock = h('span.right', h('img.emoji', {src: config.emojiUrl + 'lock.png'})) | |
45 … | + | |
46 … | + | |
47 … | + mini.appendChild(h('span', content)) | |
48 … | + mini.appendChild(exports.timestamp(msg)) | |
49 … | + | |
50 … | + if (msg.value.content.recps) { | |
51 … | + mini.appendChild(lock) | |
52 … | + } | |
53 … | + | |
54 … | + if (typeof msg.value.content === 'string') { | |
55 … | + mini.appendChild(lock) | |
56 … | + } | |
57 … | + | |
58 … | + return mini | |
59 … | +} | |
60 … | + | |
61 … | + | |
62 … | + | |
63 … | +module.exports.box = function (content) { | |
64 … | + return ssbKeys.box(content, content.recps.map(function (e) { | |
65 … | + return ref.isFeed(e) ? e : e.link | |
66 … | + })) | |
67 … | +} | |
68 … | + | |
69 … | +module.exports.publish = function (content, cb) { | |
70 … | + if(content.recps) | |
71 … | + content = exports.box(content) | |
72 … | + sbot.publish(content, function (err, msg) { | |
73 … | + if(err) throw err | |
74 … | + console.log('Published!', msg) | |
75 … | + if(cb) cb(err, msg) | |
76 … | + }) | |
77 … | +} | |
78 … | + | |
79 … | +module.exports.messageName = function (id, cb) { | |
80 … | + // gets the first few characters of a message, for message-link | |
81 … | + function title (s) { | |
82 … | + var m = /^\n*([^\n]{0,40})/.exec(s) | |
83 … | + return m && (m[1].length == 40 ? m[1]+'...' : m[1]) | |
84 … | + } | |
85 … | + | |
86 … | + sbot.get(id, function (err, value) { | |
87 … | + if(err && err.name == 'NotFoundError') | |
88 … | + return cb(null, id.substring(0, 10)+'...(missing)') | |
89 … | + if(value.content.type === 'post' && 'string' === typeof value.content.text) | |
90 … | + return cb(null, title(value.content.text)) | |
91 … | + else if('string' === typeof value.content.text) | |
92 … | + return cb(null, value.content.type + ':'+title(value.content.text)) | |
93 … | + else | |
94 … | + return cb(null, id.substring(0, 10)+'...') | |
95 … | + }) | |
96 … | +} | |
97 … | + | |
98 … | +var messageName = exports.messageName | |
99 … | + | |
100 … | +module.exports.messageLink = function (id) { | |
101 … | + if (ref.isMsg(id)) { | |
102 … | + var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...') | |
103 … | + messageName(id, function (err, name) { | |
104 … | + if(err) console.error(err) | |
105 … | + else link.textContent = name | |
106 … | + }) | |
107 … | + } else { | |
108 … | + var link = id | |
109 … | + } | |
110 … | + return link | |
111 … | +} | |
112 … | + | |
113 … | +module.exports.rawJSON = function (obj) { | |
114 … | + return JSON.stringify(obj, null, 2) | |
115 … | + .split(/([%@&][a-zA-Z0-9\/\+]{43}=*\.[\w]+)/) | |
116 … | + .map(function (e) { | |
117 … | + if(ref.isMsg(e) || ref.isFeed(e) || ref.isBlob(e)) { | |
118 … | + return h('a', {href: '#' + e}, e) | |
119 … | + } | |
120 … | + return e | |
121 … | + }) | |
122 … | +} | |
123 … | + | |
124 … | +module.exports.markdown = function (msg, md) { | |
125 … | + return {innerHTML: markdown.block(msg, {toUrl: function (url, image) { | |
126 … | + if(url[0] == '%' || url[0] == '@' || url[0] == '#') return '#' + url | |
127 … | + if(url[0] !== '&') return url | |
128 … | + //if(url[0] == '&') return config.blobsUrl + url | |
129 … | + //if(!image) return url | |
130 … | + return config.blobsUrl + url | |
131 … | + }})} | |
132 … | +} |
views.js | ||
---|---|---|
@@ -1,0 +1,212 @@ | ||
1 … | +var pull = require('pull-stream') | |
2 … | +var sbot = require('./scuttlebot') | |
3 … | +var hyperscroll = require('hyperscroll') | |
4 … | +var stream = require('hyperloadmore/stream') | |
5 … | +var h = require('hyperscript') | |
6 … | +var ref = require('ssb-ref') | |
7 … | +var next = require('pull-next-query') | |
8 … | + | |
9 … | +var render = require('./render').render | |
10 … | + | |
11 … | +var config = require('./config')() | |
12 … | + | |
13 … | +var id = require('./keys').id | |
14 … | + | |
15 … | +var compose = require('./compose') | |
16 … | + | |
17 … | +var keyPage = function () { | |
18 … | + var screen = document.getElementById('screen') | |
19 … | + | |
20 … | + var importKey = h('textarea.import', {placeholder: 'Import a new public/private key', name: 'textarea', style: 'width: 97%; height: 100px;'}) | |
21 … | + | |
22 … | + var content = h('div.content', | |
23 … | + h('div.message#key', | |
24 … | + h('h1', 'Your Key'), | |
25 … | + h('p', {innerHTML: 'Your public/private key is: <pre><code>' + localStorage[config.caps.shs + '/secret'] + '</code></pre>'}, | |
26 … | + h('button.btn', {onclick: function (e){ | |
27 … | + localStorage[config.caps.shs +'/secret'] = '' | |
28 … | + alert('Your public/private key has been deleted') | |
29 … | + e.preventDefault() | |
30 … | + location.hash = "" | |
31 … | + location.reload() | |
32 … | + }}, 'Delete Key') | |
33 … | + ), | |
34 … | + h('hr'), | |
35 … | + h('form', | |
36 … | + importKey, | |
37 … | + h('button.btn', {onclick: function (e){ | |
38 … | + if(importKey.value) { | |
39 … | + localStorage[config.caps.shs + '/secret'] = importKey.value.replace(/\s+/g, ' ') | |
40 … | + e.preventDefault() | |
41 … | + alert('Your public/private key has been updated') | |
42 … | + } | |
43 … | + location.hash = "" | |
44 … | + location.reload() | |
45 … | + }}, 'Import key'), | |
46 … | + ) | |
47 … | + ) | |
48 … | + ) | |
49 … | + screen.appendChild(hyperscroll(content)) | |
50 … | +} | |
51 … | + | |
52 … | +function getMessage (src) { | |
53 … | + | |
54 … | + var content = h('div.content') | |
55 … | + var screen = document.getElementById('screen') | |
56 … | + | |
57 … | + screen.appendChild(hyperscroll(content)) | |
58 … | + | |
59 … | + sbot.get(src, function (err, data) { | |
60 … | + if (err) { | |
61 … | + var message = h('div.message', 'Missing message!') | |
62 … | + content.appendChild(message) | |
63 … | + } | |
64 … | + if (data) { | |
65 … | + var message = {} | |
66 … | + | |
67 … | + message.value = data | |
68 … | + message.key = src | |
69 … | + | |
70 … | + if (content.firstChild) { | |
71 … | + content.insertBefore(render(message), content.firstChild) | |
72 … | + } else { | |
73 … | + content.appendChild(render(message)) | |
74 … | + } | |
75 … | + } | |
76 … | + }) | |
77 … | +} | |
78 … | + | |
79 … | + | |
80 … | +function userStream (src) { | |
81 … | + | |
82 … | + var screen = document.getElementById('screen') | |
83 … | + var content = h('div.content') | |
84 … | + | |
85 … | + screen.appendChild(hyperscroll(content)) | |
86 … | + | |
87 … | + function createStream (opts) { | |
88 … | + return pull( | |
89 … | + next(sbot.query, opts, ['value', 'timestamp']), | |
90 … | + pull.map(function (msg) { | |
91 … | + return render(msg) | |
92 … | + }) | |
93 … | + ) | |
94 … | + } | |
95 … | + | |
96 … | + pull( | |
97 … | + createStream({ | |
98 … | + limit: 100, | |
99 … | + reverse: true, | |
100 … | + live: false, | |
101 … | + query: [{$filter: { value: { author: src, timestamp: { $gt: 0 }}}}] | |
102 … | + }), | |
103 … | + stream.bottom(content) | |
104 … | + ) | |
105 … | + | |
106 … | + pull( | |
107 … | + createStream({ | |
108 … | + limit: 100, | |
109 … | + old: false, | |
110 … | + live: true, | |
111 … | + query: [{$filter: { value: {author: src, timestamp: { $gt: 0 }}}}] | |
112 … | + }), | |
113 … | + stream.top(content) | |
114 … | + ) | |
115 … | +} | |
116 … | + | |
117 … | +function mentionsStream (src) { | |
118 … | + | |
119 … | + var screen = document.getElementById('screen') | |
120 … | + var content = h('div.content') | |
121 … | + | |
122 … | + screen.appendChild(hyperscroll(content)) | |
123 … | + | |
124 … | + function createStream (opts) { | |
125 … | + return pull( | |
126 … | + next(sbot.backlinks, opts, ['value', 'timestamp']), | |
127 … | + pull.map(function (msg) { | |
128 … | + return render(msg) | |
129 … | + }) | |
130 … | + ) | |
131 … | + } | |
132 … | + | |
133 … | + pull( | |
134 … | + createStream({ | |
135 … | + limit: 100, | |
136 … | + reverse: true, | |
137 … | + index: 'DTA', | |
138 … | + live: false, | |
139 … | + query: [{$filter: {dest: src}}] | |
140 … | + }), | |
141 … | + stream.bottom(content) | |
142 … | + ) | |
143 … | + | |
144 … | + pull( | |
145 … | + createStream({ | |
146 … | + limit: 100, | |
147 … | + old: false, | |
148 … | + index: 'DTA', | |
149 … | + live: true, | |
150 … | + query: [{$filter: {dest: src}}] | |
151 … | + }), | |
152 … | + stream.top(content) | |
153 … | + ) | |
154 … | +} | |
155 … | + | |
156 … | +function everythingStream () { | |
157 … | + | |
158 … | + var screen = document.getElementById('screen') | |
159 … | + var content = h('div.content') | |
160 … | + | |
161 … | + screen.appendChild(hyperscroll(content)) | |
162 … | + | |
163 … | + function createStream (opts) { | |
164 … | + return pull( | |
165 … | + next(sbot.query, opts, ['value', 'timestamp']), | |
166 … | + pull.map(function (msg) { | |
167 … | + if (msg.value) { | |
168 … | + return render(msg) | |
169 … | + } | |
170 … | + }) | |
171 … | + ) | |
172 … | + } | |
173 … | + | |
174 … | + pull( | |
175 … | + createStream({ | |
176 … | + limit: 100, | |
177 … | + reverse: true, | |
178 … | + live: false, | |
179 … | + query: [{$filter: { value: { timestamp: { $gt: 0 }}}}] | |
180 … | + }), | |
181 … | + stream.bottom(content) | |
182 … | + ) | |
183 … | + | |
184 … | + pull( | |
185 … | + createStream({ | |
186 … | + limit: 100, | |
187 … | + old: false, | |
188 … | + live: true, | |
189 … | + query: [{$filter: { value: { timestamp: { $gt: 0 }}}}] | |
190 … | + }), | |
191 … | + stream.top(content) | |
192 … | + ) | |
193 … | +} | |
194 … | + | |
195 … | +module.exports = function () { | |
196 … | + var src = window.location.hash.substring(1) | |
197 … | + | |
198 … | + if (ref.isFeed(src)) { | |
199 … | + userStream(src) | |
200 … | + } else if (ref.isMsg(src)) { | |
201 … | + getMessage(src) | |
202 … | + } else if ((src.substring(0, 5) == 'wall/') && (ref.isFeed(src.substring(5)))) { | |
203 … | + mentionsStream(src.substring(5)) | |
204 … | + } else if (ref.isMsg(src)) { | |
205 … | + messageStream() | |
206 … | + } else if (src == 'key') { | |
207 … | + keyPage() | |
208 … | + } else { | |
209 … | + everythingStream() | |
210 … | + } | |
211 … | + | |
212 … | +} |
Built with git-ssb-web