git ssb

16+

Dominic / patchbay



Commit e33a98abcc85d3a3b35c0b2db5b98200b0614954

Merge branch 'master' into blob

cel committed on 12/9/2016, 11:52:43 PM
Parent: cc9a7d40d27d6abfd9be99ee32cc20b8df57accf
Parent: 3e23c0d59b19aeeeb752a8470c4637a82bea21d9

Files changed

basic.jschanged
index.jschanged
modules_basic/about.jschanged
modules_basic/avatar-edit.jschanged
modules_basic/avatar-image.jschanged
modules_basic/avatar-link.jschanged
modules_basic/avatar-name.jschanged
modules_basic/avatar-profile.jschanged
modules_basic/avatar.jschanged
modules_basic/compose.jschanged
modules_basic/feed.jschanged
modules_basic/follow.jschanged
modules_basic/index.jschanged
modules_basic/invite.jschanged
modules_basic/like.jschanged
modules_basic/markdown.jschanged
modules_basic/message-link.jschanged
modules_basic/message-name.jschanged
modules_basic/message.jschanged
modules_basic/names.jschanged
modules_basic/post.jschanged
modules_basic/private.jschanged
modules_basic/pub.jschanged
modules_basic/public.jschanged
modules_basic/relationships.jschanged
modules_basic/search-box.jschanged
modules_basic/setup.jschanged
modules_basic/suggest-mentions.jschanged
modules_basic/thread.jschanged
modules_basic/timestamp.jschanged
modules_basic/suggest.jsdeleted
modules_core/app.jschanged
modules_core/blob-url.jschanged
modules_core/crypto.jschanged
modules_core/file-input.jschanged
modules_core/index.jschanged
modules_core/menu.jschanged
modules_core/message-confirm.jschanged
modules_core/sbot.jschanged
modules_core/tabs.jschanged
modules_extra/audio-mp3.jschanged
modules_extra/blob.jschanged
modules_extra/channel.jschanged
modules_extra/dns.jschanged
modules_extra/emoji.jschanged
modules_extra/git-ssb.jschanged
modules_extra/git.jschanged
modules_extra/meta-image.jschanged
modules_extra/music-release-cc.jschanged
modules_extra/music-release.jschanged
modules_extra/network.jschanged
modules_extra/notifications.jschanged
modules_extra/query.jschanged
modules_extra/raw.jschanged
modules_extra/search.jschanged
modules_extra/split.jschanged
modules_extra/suggest-emoji.jschanged
modules_extra/versions.jschanged
package.jsonchanged
basic.jsView
@@ -1,6 +1,6 @@
11 require('depject')(
22 require('./modules_core'),
33 require('./modules_basic')
4-).plugs.app[0]()
4 +).app[0]()
55
66
index.jsView
@@ -2,6 +2,6 @@
22 // from more specialized to more general
33 require('./modules_extra'),
44 require('./modules_basic'),
55 require('./modules_core')
6-).plugs.app[0]()
6 +).app[0]()
77
modules_basic/about.jsView
@@ -8,33 +8,41 @@
88 function asLink (ln) {
99 return 'string' === typeof ln ? ln : ln.link
1010 }
1111
12-var blob_url = require('../plugs').first(exports.blob_url = [])
12 +//var blob_url = require('../plugs').first(exports.blob_url = [])
1313
14-exports.message_content = function (msg) {
15- if(msg.value.content.type !== 'about') return
14 +exports.needs = {
15 + blob_url: 'first'
16 +}
1617
17- if(!msg.value.content.image && !msg.value.content.name)
18- return
18 +exports.gives = 'message_content'
1919
20- var about = msg.value.content
21- var id = msg.value.content.about
22- return h('p',
23- about.about === msg.value.author
24- ? h('span', 'self-identifies ')
25- : h('span', 'identifies ', idLink(id)),
26- ' as ',
27- h('a', {href:"#"+about.about},
28- about.name || null,
29- about.image
30- ? h('img.avatar--fullsize', {src: blob_url(about.image)})
31- : null
20 +exports.create = function (api) {
21 +
22 + return function (msg) {
23 + if(msg.value.content.type !== 'about') return
24 +
25 + if(!msg.value.content.image && !msg.value.content.name)
26 + return
27 +
28 + var about = msg.value.content
29 + var id = msg.value.content.about
30 + return h('p',
31 + about.about === msg.value.author
32 + ? h('span', 'self-identifies ')
33 + : h('span', 'identifies ', idLink(id)),
34 + ' as ',
35 + h('a', {href:"#"+about.about},
36 + about.name || null,
37 + about.image
38 + ? h('img.avatar--fullsize', {src: api.blob_url(about.image)})
39 + : null
40 + )
3241 )
33- )
3442
43 + }
44 +
3545 }
3646
3747
3848
39-
40-
modules_basic/avatar-edit.jsView
@@ -1,4 +1,5 @@
1 +'use strict'
12 var dataurl = require('dataurl-')
23 var hyperfile = require('hyperfile')
34 var hypercrop = require('hypercrop')
45 var hyperlightbox = require('hyperlightbox')
@@ -9,13 +10,14 @@
910 var ref = require('ssb-ref')
1011 var visualize = require('visualize-buffer')
1112 var self_id = require('../keys').id
1213
13-var confirm = plugs.first(exports.message_confirm = [])
14-var sbot_blobs_add = plugs.first(exports.sbot_blobs_add = [])
15-var blob_url = plugs.first(exports.blob_url = [])
16-var sbot_links = plugs.first(exports.sbot_links = [])
17-var avatar_name = plugs.first(exports.avatar_name = [])
14 +//var confirm = plugs.first(exports.message_confirm = [])
15 +//var sbot_blobs_add = plugs.first(exports.sbot_blobs_add = [])
16 +//var blob_url = plugs.first(exports.blob_url = [])
17 +//var sbot_links = plugs.first(exports.sbot_links = [])
18 +//var avatar_name = plugs.first(exports.avatar_name = [])
19 +//
1820
1921 function crop (d, cb) {
2022 var data
2123 var canvas = hypercrop(h('img', {src: d}))
@@ -33,109 +35,118 @@
3335 )
3436 )
3537 }
3638
37-exports.avatar_edit = function (id) {
39 +exports.needs = {
40 + message_confirm: 'first',
41 + sbot_blobs_add: 'first',
42 + blob_url: 'first',
43 + sbot_links: 'first',
44 + avatar_name: 'first'
45 +}
3846
39- var img = visualize(new Buffer(id.substring(1), 'base64'), 256)
40- img.classList.add('avatar--large')
47 +exports.gives = 'avatar_edit'
4148
42- var lb = hyperlightbox()
43- var name_input = h('input', {placeholder: 'rename'})
44- var name = avatar_name(id)
45- var selected = null, selected_data = null
49 +exports.create = function (api) {
50 + return function (id) {
4651
47- getAvatar({links: sbot_links}, self_id, id, function (err, avatar) {
48- if (err) return console.error(err)
49- //don't show user has already selected an avatar.
50- if(selected) return
51- if(ref.isBlob(avatar.image))
52- img.src = blob_url(avatar.image)
53- })
52 + var img = visualize(new Buffer(id.substring(1), 'base64'), 256)
53 + img.classList.add('avatar--large')
5454
55- var also_pictured = h('div.profile__alsopicturedas.wrap')
55 + var lb = hyperlightbox()
56 + var name_input = h('input', {placeholder: 'rename'})
57 + var name = api.avatar_name(id)
58 + var selected = null, selected_data = null
5659
57- pull(
58- sbot_links({dest: id, rel: 'about', values: true}),
59- pull.map(function (e) {
60- return e.value.content.image
61- }),
62- pull.filter(function (e) {
63- return e && 'string' == typeof e.link
64- }),
65- pull.unique('link'),
66- pull.drain(function (image) {
67- also_pictured.appendChild(
68- h('a', {href:'#', onclick: function (ev) {
69- ev.stopPropagation()
70- ev.preventDefault()
71- selected = image
72- img.src = blob_url(image.link || image)
73- }},
74- h('img.avatar--thumbnail', {src: blob_url(image)})
75- )
76- )
60 + getAvatar({links: api.sbot_links}, self_id, id, function (err, avatar) {
61 + if (err) return console.error(err)
62 + //don't show user has already selected an avatar.
63 + if(selected) return
64 + if(ref.isBlob(avatar.image))
65 + img.src = api.blob_url(avatar.image)
7766 })
78- )
7967
80- return h('div.row.profile',
81- lb,
82- img,
83- h('div.column.profile__info',
84- h('strong', name),
85- name_input,
68 + var also_pictured = h('div.profile__alsopicturedas.wrap')
8669
87- hyperfile.asDataURL(function (data) {
88- var el = crop(data, function (err, data) {
89- if(data) {
90- img.src = data
91- var _data = dataurl.parse(data)
92- pull(
93- pull.once(_data.data),
94- sbot_blobs_add(function (err, hash) {
95- //TODO. Alerts are EVIL.
96- //I use them only in a moment of weakness.
70 + pull(
71 + api.sbot_links({dest: id, rel: 'about', values: true}),
72 + pull.map(function (e) {
73 + return e.value.content.image
74 + }),
75 + pull.filter(function (e) {
76 + return e && 'string' == typeof e.link
77 + }),
78 + pull.unique('link'),
79 + pull.drain(function (image) {
80 + also_pictured.appendChild(
81 + h('a', {href:'#', onclick: function (ev) {
82 + ev.stopPropagation()
83 + ev.preventDefault()
84 + selected = image
85 + img.src = api.blob_url(image.link || image)
86 + }},
87 + h('img.avatar--thumbnail', {src: api.blob_url(image)})
88 + )
89 + )
90 + })
91 + )
9792
98- if(err) return alert(err.stack)
99- selected = {
100- link: hash,
101- size: _data.data.length,
102- type: _data.mimetype,
103- width: 512,
104- height: 512
105- }
93 + return h('div.row.profile',
94 + lb,
95 + img,
96 + h('div.column.profile__info',
97 + h('strong', name),
98 + name_input,
10699
107- })
108- )
109- }
110- lb.close()
111- })
112- lb.show(el)
113- }),
114- h('button', 'update', {onclick: function () {
115- if(name_input.value)
116- name.textContent = name_input.value
100 + hyperfile.asDataURL(function (data) {
101 + var el = crop(data, function (err, data) {
102 + if(data) {
103 + img.src = data
104 + var _data = dataurl.parse(data)
105 + pull(
106 + pull.once(_data.data),
107 + api.sbot_blobs_add(function (err, hash) {
108 + //TODO. Alerts are EVIL.
109 + //I use them only in a moment of weakness.
117110
118- if(selected)
119- confirm({
120- type: 'about',
121- about: id,
122- name: name_input.value || undefined,
123- image: selected
111 + if(err) return alert(err.stack)
112 + selected = {
113 + link: hash,
114 + size: _data.data.length,
115 + type: _data.mimetype,
116 + width: 512,
117 + height: 512
118 + }
119 +
120 + })
121 + )
122 + }
123 + lb.close()
124124 })
125- else if(name_input.value) //name only
126- confirm({
127- type: 'about',
128- about: id,
129- name: name_input.value || undefined,
130- })
131- else
132- //another moment of weakness
133- alert('must select a name or image')
134- }}),
135- also_pictured
125 + lb.show(el)
126 + }),
127 + h('button', 'update', {onclick: function () {
128 + if(name_input.value)
129 + name.textContent = name_input.value
130 +
131 + if(selected)
132 + api.message_confirm({
133 + type: 'about',
134 + about: id,
135 + name: name_input.value || undefined,
136 + image: selected
137 + })
138 + else if(name_input.value) //name only
139 + confirm({
140 + type: 'about',
141 + about: id,
142 + name: name_input.value || undefined,
143 + })
144 + else
145 + //another moment of weakness
146 + alert('must select a name or image')
147 + }}),
148 + also_pictured
149 + )
136150 )
137- )
151 + }
138152 }
139-
140-
141-
modules_basic/avatar-image.jsView
@@ -1,108 +1,110 @@
1-
1 +'use strict'
22 var getAvatar = require('ssb-avatar')
33 var h = require('hyperscript')
44 var ref = require('ssb-ref')
55 var path = require('path')
66 var visualize = require('visualize-buffer')
77
8-var plugs = require('../plugs')
9-var sbot_query = plugs.first(exports.sbot_query = [])
10-var blob_url = require('../plugs').first(exports.blob_url = [])
11-
128 var pull = require('pull-stream')
139
14-var id = require('../keys').id
10 +var self_id = require('../keys').id
1511
16-var avatars = AVATARS = {}
12 +//var plugs = require('../plugs')
13 +//var sbot_query = plugs.first(exports.sbot_query = [])
14 +//var blob_url = require('../plugs').first(exports.blob_url = [])
15 +//
1716
17 +exports.needs = {
18 + sbot_query: 'first',
19 + blob_url: 'first'
20 +}
21 +
22 +exports.gives = {
23 + connection_status: true, avatar_image: true
24 +}
25 +
26 +
1827 function isFunction (f) {
1928 return 'function' === typeof f
2029 }
2130
22-var self_id = require('../keys').id
2331
2432 var ready = false
2533 var waiting = []
2634
2735 var last = 0
2836
29-//blah blah
30-exports.connection_status = function (err) {
31- if (err) return
32-pull(
33- sbot_query({
34- query: [{
35- $filter: {
36- timestamp: {$gt: last || 0 },
37- value: { content: {
38- type: "about",
39- about: {$prefix: "@"},
40- image: {link: {$prefix: "&"}}
41- }}
42- }},
43- {
44- $map: {
45- id: ["value", "content", "about"],
46- image: ["value", "content", "image", "link"],
47- by: ["value", "author"],
48- ts: 'timestamp'
49- }}],
50- live: true
51- }),
52- pull.drain(function (a) {
53- if(a.sync) {
54- ready = true
55- while(waiting.length) waiting.shift()()
56- return
57- }
58- last = a.ts
59- //set image for avatar.
60- //overwrite another avatar
61- //you picked.
62- if(
63- //if there is no avatar
64- (!avatars[a.id]) ||
65- //if i chose this avatar
66- (a.by == self_id) ||
67- //they chose their own avatar,
68- //and current avatar was not chosen by me
69- (a.by === a.id && avatars[a.id].by != self_id)
70- )
71- avatars[a.id] = a
37 +exports.create = function (api) {
38 + var avatars = {}
7239
73- })
74-)
75-}
40 + //blah blah
41 + return {
42 + connection_status: function (err) {
43 + if (err) return
44 + pull(
45 + api.sbot_query({
46 + query: [{
47 + $filter: {
48 + timestamp: {$gt: last || 0 },
49 + value: { content: {
50 + type: "about",
51 + about: {$prefix: "@"},
52 + image: {link: {$prefix: "&"}}
53 + }}
54 + }},
55 + {
56 + $map: {
57 + id: ["value", "content", "about"],
58 + image: ["value", "content", "image", "link"],
59 + by: ["value", "author"],
60 + ts: 'timestamp'
61 + }}],
62 + live: true
63 + }),
64 + pull.drain(function (a) {
65 + if(a.sync) {
66 + ready = true
67 + while(waiting.length) waiting.shift()()
68 + return
69 + }
70 + last = a.ts
71 + //set image for avatar.
72 + //overwrite another avatar
73 + //you picked.
74 + if(
75 + //if there is no avatar
76 + (!avatars[a.id]) ||
77 + //if i chose this avatar
78 + (a.by == self_id) ||
79 + //they chose their own avatar,
80 + //and current avatar was not chosen by me
81 + (a.by === a.id && avatars[a.id].by != self_id)
82 + )
83 + avatars[a.id] = a
7684
77-exports.avatar_image = function (author, classes) {
78- classes = classes || ''
79- if(classes && 'string' === typeof classes) classes = '.avatar--'+classes
85 + })
86 + )
87 + },
8088
81- var img = visualize(new Buffer(author.substring(1), 'base64'), 256)
82- ;(classes || '').split('.').filter(Boolean).forEach(function (c) {
83- img.classList.add(c)
84- })
89 + avatar_image: function (author, classes) {
90 + classes = classes || ''
91 + if(classes && 'string' === typeof classes) classes = '.avatar--'+classes
8592
86- function go () {
87- if(avatars[author]) img.src = blob_url(avatars[author].image)
88- }
93 + var img = visualize(new Buffer(author.substring(1), 'base64'), 256)
94 + ;(classes || '').split('.').filter(Boolean).forEach(function (c) {
95 + img.classList.add(c)
96 + })
8997
90- if(!ready)
91- waiting.push(go)
92- else go()
98 + function go () {
99 + if(avatars[author]) img.src = api.blob_url(avatars[author].image)
100 + }
93101
94- return img
102 + if(!ready)
103 + waiting.push(go)
104 + else go()
105 +
106 + return img
107 + }
108 + }
95109 }
96110
97-
98-
99-
100-
101-
102-
103-
104-
105-
106-
107-
108-
modules_basic/avatar-link.jsView
@@ -1,18 +1,22 @@
11 var h = require('hyperscript')
2-var plugs = require('../plugs')
3-var avatar_name = plugs.first(exports.avatar_name = [])
42
5-var signifier = require('../plugs').first(exports.signifier = [])
3 +exports.needs = {signifier: 'first'}
64
7-exports.avatar_link = function (id, element) {
5 +exports.gives = 'avatar_link'
86
9- var link = h('a.avatar', {href: "#"+id, title: id}, element)
7 +exports.create = function (api) {
8 + return function (id, element) {
109
11- signifier(id, function (_, names) {
12- if(names.length)
13- link.title = names[0].name + '\n '+id
14- })
10 + var link = h('a.avatar', {href: "#"+id, title: id}, element)
1511
16- return link
12 + api.signifier(id, function (_, names) {
13 + if(names.length)
14 + link.title = names[0].name + '\n '+id
15 + })
16 +
17 + return link
18 + }
1719 }
1820
21 +
22 +
modules_basic/avatar-name.jsView
@@ -1,22 +1,29 @@
11
22 var signifier = require('../plugs').first(exports.signifier = [])
33 var h = require('hyperscript')
44
5-exports.avatar_name =
6-function name (id) {
7- var n = h('span', id.substring(0, 10))
5 +exports.needs = { signifier: 'first' }
86
9- //choose the most popular name for this person.
10- //for anything like this you'll see I have used sbot.links2
11- //which is the ssb-links plugin. as you'll see the query interface
12- //is pretty powerful!
13- //TODO: "most popular" name is easily gameable.
14- //must come up with something better than this.
7 +exports.gives = 'avatar_name'
158
16- signifier(id, function (_, names) {
17- if(names.length) n.textContent = names[0].name
18- })
9 +exports.create = function (api) {
1910
20- return n
11 + return function name (id) {
12 + var n = h('span', id.substring(0, 10))
13 +
14 + //choose the most popular name for this person.
15 + //for anything like this you'll see I have used sbot.links2
16 + //which is the ssb-links plugin. as you'll see the query interface
17 + //is pretty powerful!
18 + //TODO: "most popular" name is easily gameable.
19 + //must come up with something better than this.
20 +
21 + api.signifier(id, function (_, names) {
22 + if(names.length) n.textContent = names[0].name
23 + })
24 +
25 + return n
26 + }
27 +
2128 }
2229
modules_basic/avatar-profile.jsView
@@ -1,15 +1,25 @@
11 var h = require('hyperscript')
2-var plugs = require('../plugs')
32 var pull = require('pull-stream')
43
5-var avatar_image_link = plugs.first(exports.avatar_image_link = [])
6-var avatar_action = plugs.map(exports.avatar_action = [])
7-var avatar_edit = plugs.first(exports.avatar_edit = [])
4 +//var plugs = require('../plugs')
5 +//var avatar_image_link = plugs.first(exports.avatar_image_link = [])
6 +//var avatar_action = plugs.map(exports.avatar_action = [])
7 +//var avatar_edit = plugs.first(exports.avatar_edit = [])
88
9-var follows = plugs.first(exports.follows = [])
10-var followers = plugs.first(exports.followers = [])
9 +//var follows = plugs.first(exports.follows = [])
10 +//var followers = plugs.first(exports.followers = [])
11 +//
12 +exports.needs = {
13 + avatar_image_link: 'first',
14 + avatar_action: 'map',
15 + avatar_edit: 'first',
16 + follows: 'first',
17 + followers: 'first'
18 +}
1119
20 +exports.gives = 'avatar_profile'
21 +
1222 function streamToList(stream, el) {
1323 pull(
1424 stream,
1525 pull.drain(function (item) {
@@ -18,57 +28,61 @@
1828 )
1929 return el
2030 }
2131
22-function image_link (id) {
23- return avatar_image_link(id, 'thumbnail')
24-}
32 +exports.create = function (api) {
2533
26-exports.avatar_profile = function (id) {
34 + function image_link (id) {
35 + return api.avatar_image_link(id, 'thumbnail')
36 + }
2737
28- var follows_el = h('div.profile__follows.wrap')
29- var friends_el = h('div.profile__friendss.wrap')
30- var followers_el = h('div.profile__followers.wrap')
31- var a, b
38 + return function (id) {
3239
33- pull(follows(id), pull.unique(), pull.collect(function (err, ary) {
34- a = ary || []; next()
35- }))
36- pull(followers(id), pull.unique(), pull.collect(function (err, ary) {
37- b = ary || {}; next()
38- }))
40 + var follows_el = h('div.profile__follows.wrap')
41 + var friends_el = h('div.profile__friendss.wrap')
42 + var followers_el = h('div.profile__followers.wrap')
43 + var a, b
3944
40- function next () {
41- if(!(a && b)) return
42- var _c = [], _a = [], _b = []
45 + pull(api.follows(id), pull.unique(), pull.collect(function (err, ary) {
46 + a = ary || []; next()
47 + }))
48 + pull(api.followers(id), pull.unique(), pull.collect(function (err, ary) {
49 + b = ary || {}; next()
50 + }))
4351
44- a.forEach(function (id) {
45- if(!~b.indexOf(id)) _a.push(id)
46- else _c.push(id)
47- })
48- b.forEach(function (id) {
49- if(!~_c.indexOf(id)) _b.push(id)
50- })
51- function add (ary, el) {
52- ary.forEach(function (id) { el.appendChild(image_link(id)) })
52 + function next () {
53 + if(!(a && b)) return
54 + var _c = [], _a = [], _b = []
55 +
56 + a.forEach(function (id) {
57 + if(!~b.indexOf(id)) _a.push(id)
58 + else _c.push(id)
59 + })
60 + b.forEach(function (id) {
61 + if(!~_c.indexOf(id)) _b.push(id)
62 + })
63 + function add (ary, el) {
64 + ary.forEach(function (id) { el.appendChild(image_link(id)) })
65 + }
66 +
67 + add(_a, follows_el)
68 + add(_c, friends_el)
69 + add(_b, followers_el)
5370 }
5471
55- add(_a, follows_el)
56- add(_c, friends_el)
57- add(_b, followers_el)
72 +
73 + return h('div.column.profile',
74 + api.avatar_edit(id),
75 + api.avatar_action(id),
76 + h('div.profile__relationships.column',
77 + h('strong', 'follows'),
78 + follows_el,
79 + h('strong', 'friends'),
80 + friends_el,
81 + h('strong', 'followers'),
82 + followers_el
83 + )
84 + )
5885 }
5986
60-
61- return h('div.column.profile',
62- avatar_edit(id),
63- avatar_action(id),
64- h('div.profile__relationships.column',
65- h('strong', 'follows'),
66- follows_el,
67- h('strong', 'friends'),
68- friends_el,
69- h('strong', 'followers'),
70- followers_el
71- )
72- )
7387 }
7488
modules_basic/avatar.jsView
@@ -1,23 +1,37 @@
11 var h = require('hyperscript')
22 var u = require('../util')
3-var plugs = require('../plugs')
43
5-var avatar_name = plugs.first(exports.avatar_name = [])
6-var avatar_image = plugs.first(exports.avatar_image = [])
7-var avatar_link = plugs.first(exports.avatar_link = [])
8-
9-exports.avatar = function (author, classes) {
10- return exports.avatar_image_name_link(author, classes)
4 +exports.needs = {
5 + avatar_name: 'first',
6 + avatar_image: 'first',
7 + avatar_link: 'first'
118 }
129
13-exports.avatar_image_name_link = function (author, classes) {
14- return avatar_link(author, [
15- avatar_image(author, classes),
16- avatar_name(author)
17- ])
10 +exports.gives = {
11 + avatar: true,
12 + avatar_image_name_link: true,
13 + avatar_image_link: true
1814 }
1915
20-exports.avatar_image_link = function (author, classes) {
21- return avatar_link(author, avatar_image(author, classes))
16 +exports.create = function (api) {
17 +
18 + var exports = {}
19 + exports.avatar = function (author, classes) {
20 + return exports.avatar_image_name_link(author, classes)
21 + }
22 +
23 + exports.avatar_image_name_link = function (author, classes) {
24 + return api.avatar_link(author, [
25 + api.avatar_image(author, classes),
26 + api.avatar_name(author)
27 + ])
28 + }
29 +
30 + exports.avatar_image_link = function (author, classes) {
31 + return api.avatar_link(author, api.avatar_image(author, classes))
32 + }
33 +
34 + return exports
2235 }
2336
37 +
modules_basic/compose.jsView
@@ -3,17 +3,27 @@
33 var u = require('../util')
44 var suggest = require('suggest-box')
55 var mentions = require('ssb-mentions')
66 var lightbox = require('hyperlightbox')
7 +var cont = require('cont')
78
8-var plugs = require('../plugs')
9 +//var plugs = require('../plugs')
10 +//var suggest_mentions= plugs.asyncConcat(exports.suggest_mentions = [])
11 +//var publish = plugs.first(exports.sbot_publish = [])
12 +//var message_content = plugs.first(exports.message_content = [])
13 +//var message_confirm = plugs.first(exports.message_confirm = [])
14 +//var file_input = plugs.first(exports.file_input = [])
915
10-var suggest_mentions= plugs.asyncConcat(exports.suggest_mentions = [])
11-var publish = plugs.first(exports.sbot_publish = [])
12-var message_content = plugs.first(exports.message_content = [])
13-var message_confirm = plugs.first(exports.message_confirm = [])
14-var file_input = plugs.first(exports.file_input = [])
16 +exports.needs = {
17 + suggest_mentions: 'map', //<-- THIS MUST BE REWRITTEN
18 + publish: 'first',
19 + message_content: 'first',
20 + message_confirm: 'first',
21 + file_input: 'first'
22 +}
1523
24 +exports.gives = 'message_compose'
25 +
1626 function id (e) { return e }
1727
1828 /*
1929 opts can take
@@ -22,111 +32,124 @@
2232 prepublish: function. called before publishing a message.
2333 shrink: boolean. set to false, to make composer not shrink (or hide controls) when unfocused.
2434 */
2535
26-exports.message_compose = function (meta, opts, cb) {
27- if('function' === typeof cb) {
28- if('function' === typeof opts)
29- opts = {prepublish: opts}
30- }
36 +exports.create = function (api) {
3137
32- if(!opts) opts = {}
33- opts.prepublish = opts.prepublish || id
38 + return function (meta, opts, cb) {
39 + if('function' === typeof cb) {
40 + if('function' === typeof opts)
41 + opts = {prepublish: opts}
42 + }
3443
35- var accessories
36- meta = meta || {}
37- if(!meta.type) throw new Error('message must have type')
38- var ta = h('textarea', {
39- placeholder: opts.placeholder || 'Write a message',
40- style: {height: opts.shrink === false ? '200px' : ''}
41- })
44 + if(!opts) opts = {}
45 + opts.prepublish = opts.prepublish || id
4246
43- if(opts.shrink !== false) {
44- var blur
45- ta.addEventListener('focus', function () {
46- clearTimeout(blur)
47- if(!ta.value) {
48- ta.style.height = '200px'
49- }
50- accessories.style.display = 'block'
47 + var accessories
48 + meta = meta || {}
49 + if(!meta.type) throw new Error('message must have type')
50 + var ta = h('textarea', {
51 + placeholder: opts.placeholder || 'Write a message',
52 + style: {height: opts.shrink === false ? '200px' : ''}
5153 })
52- ta.addEventListener('blur', function () {
53- //don't shrink right away, so there is time
54- //to click the publish button.
55- clearTimeout(blur)
56- blur = setTimeout(function () {
57- if(ta.value) return
58- ta.style.height = '50px'
59- accessories.style.display = 'none'
60- }, 200)
54 +
55 + if(opts.shrink !== false) {
56 + var blur
57 + ta.addEventListener('focus', function () {
58 + clearTimeout(blur)
59 + if(!ta.value) {
60 + ta.style.height = '200px'
61 + }
62 + accessories.style.display = 'block'
63 + })
64 + ta.addEventListener('blur', function () {
65 + //don't shrink right away, so there is time
66 + //to click the publish button.
67 + clearTimeout(blur)
68 + blur = setTimeout(function () {
69 + if(ta.value) return
70 + ta.style.height = '50px'
71 + accessories.style.display = 'none'
72 + }, 200)
73 + })
74 + }
75 +
76 + ta.addEventListener('keydown', function (ev) {
77 + if(ev.keyCode === 13 && ev.ctrlKey) publish()
6178 })
62- }
6379
64- ta.addEventListener('keydown', function (ev) {
65- if(ev.keyCode === 13 && ev.ctrlKey) publish()
66- })
80 + var files = []
81 + var filesById = {}
6782
68- var files = []
69- var filesById = {}
70-
71- function publish() {
72- publishBtn.disabled = true
73- var content
74- try {
75- content = JSON.parse(ta.value)
76- } catch (err) {
77- meta.text = ta.value
78- meta.mentions = mentions(ta.value).map(function (mention) {
79- // merge markdown-detected mention with file info
80- var file = filesById[mention.link]
81- if (file) {
82- if (file.type) mention.type = file.type
83- if (file.size) mention.size = file.size
84- }
85- return mention
86- })
83 + function publish() {
84 + publishBtn.disabled = true
85 + var content
8786 try {
88- meta = opts.prepublish(meta)
87 + content = JSON.parse(ta.value)
8988 } catch (err) {
90- publishBtn.disabled = false
91- if (cb) cb(err)
92- else alert(err.message)
89 + meta.text = ta.value
90 + meta.mentions = mentions(ta.value).map(function (mention) {
91 + // merge markdown-detected mention with file info
92 + var file = filesById[mention.link]
93 + if (file) {
94 + if (file.type) mention.type = file.type
95 + if (file.size) mention.size = file.size
96 + }
97 + return mention
98 + })
99 + try {
100 + meta = opts.prepublish(meta)
101 + } catch (err) {
102 + publishBtn.disabled = false
103 + if (cb) cb(err)
104 + else alert(err.message)
105 + }
106 + return api.message_confirm(meta, done)
93107 }
94- return message_confirm(meta, done)
95- }
96- message_confirm(content, done)
97108
98- function done (err, msg) {
99- publishBtn.disabled = false
100- if(err) return alert(err.stack)
101- else if (msg) ta.value = ''
109 + api.message_confirm(content, done)
102110
103- if (cb) cb(err, msg)
111 + function done (err, msg) {
112 + publishBtn.disabled = false
113 + if(err) return alert(err.stack)
114 + else if (msg) ta.value = ''
115 +
116 + if (cb) cb(err, msg)
117 + }
104118 }
105- }
106119
107120
108- var publishBtn = h('button', 'Publish', {onclick: publish})
109- var composer =
110- h('div.compose', h('div.column', ta,
111- accessories = h('div.row.compose__controls',
112- //hidden until you focus the textarea
113- {style: {display: opts.shrink === false ? '' : 'none'}},
114- file_input(function (file) {
115- files.push(file)
116- filesById[file.link] = file
121 + var publishBtn = h('button', 'Publish', {onclick: publish})
122 + var composer =
123 + h('div.compose', h('div.column', ta,
124 + accessories = h('div.row.compose__controls',
125 + //hidden until you focus the textarea
126 + {style: {display: opts.shrink === false ? '' : 'none'}},
127 + api.file_input(function (file) {
128 + files.push(file)
129 + filesById[file.link] = file
117130
118- var embed = file.type.indexOf('image/') === 0 ? '!' : ''
119- ta.value += embed + '['+file.name+']('+file.link+')'
120- console.log('added:', file)
121- }),
122- publishBtn)
131 + var embed = file.type.indexOf('image/') === 0 ? '!' : ''
132 + ta.value += embed + '['+file.name+']('+file.link+')'
133 + console.log('added:', file)
134 + }),
135 + publishBtn)
136 + )
123137 )
124- )
125138
126- suggest(ta, suggest_mentions, {})
139 + suggest(ta, function (name, cb) {
140 + cont.para(api.suggest_mentions(name))
141 + (function (err, ary) {
142 + cb(null, ary.reduce(function (a, b) {
143 + if(!b) return a
144 + return a.concat(b)
145 + }, []))
146 + })
147 + }, {})
127148
128- return composer
149 + return composer
129150
151 + }
152 +
130153 }
131154
132155
modules_basic/feed.jsView
@@ -3,54 +3,64 @@
33 var h = require('hyperscript')
44 var pull = require('pull-stream')
55 var u = require('../util')
66
7-var plugs = require('../plugs')
8-var sbot_user_feed = plugs.first(exports.sbot_user_feed = [])
9-var message_render = plugs.first(exports.message_render = [])
10-var avatar_profile = plugs.first(exports.avatar_profile = [])
11-var signifier = plugs.first(exports.signifier = [])
7 +//var plugs = require('../plugs')
8 +//var sbot_user_feed = plugs.first(exports.sbot_user_feed = [])
9 +//var message_render = plugs.first(exports.message_render = [])
10 +//var avatar_profile = plugs.first(exports.avatar_profile = [])
11 +//var signifier = plugs.first(exports.signifier = [])
1212
13-exports.screen_view = function (id) {
14- //TODO: header of user info, avatars, names, follows.
13 +exports.needs = {
14 + sbot_user_feed: 'first',
15 + message_render: 'first',
16 + avatar_profile: 'first',
17 + signifier: 'first'
18 +}
1519
16- if(ref.isFeed(id)) {
20 +exports.gives = 'screen_view'
1721
18- var content = h('div.column.scroller__content')
19- var div = h('div.column.scroller',
20- {style: {'overflow':'auto'}},
21- h('div.scroller__wrapper',
22- h('div', avatar_profile(id)),
23- content
24- )
25- )
2622
27- signifier(id, function (_, names) {
28- if(names.length) div.title = names[0].name
29- })
23 +exports.create = function (api) {
3024
25 + return function (id) {
26 + //TODO: header of user info, avatars, names, follows.
3127
32- pull(
33- sbot_user_feed({id: id, old: false, live: true}),
34- Scroller(div, content, message_render, true, false)
35- )
28 + if(ref.isFeed(id)) {
3629
37- //how to handle when have scrolled past the start???
30 + var content = h('div.column.scroller__content')
31 + var div = h('div.column.scroller',
32 + {style: {'overflow':'auto'}},
33 + h('div.scroller__wrapper',
34 + h('div', api.avatar_profile(id)),
35 + content
36 + )
37 + )
3838
39- pull(
40- u.next(sbot_user_feed, {
41- id: id, reverse: true,
42- limit: 50, live: false
43- }, ['value', 'sequence']),
44- Scroller(div, content, message_render, false, false)
45- )
39 + api.signifier(id, function (_, names) {
40 + if(names.length) div.title = names[0].name
41 + })
4642
47- return div
4843
49- }
50-}
44 + pull(
45 + api.sbot_user_feed({id: id, old: false, live: true}),
46 + Scroller(div, content, api.message_render, true, false)
47 + )
5148
49 + //how to handle when have scrolled past the start???
5250
51 + pull(
52 + u.next(api.sbot_user_feed, {
53 + id: id, reverse: true,
54 + limit: 50, live: false
55 + }, ['value', 'sequence']),
56 + pull.through(console.log.bind(console)),
57 + Scroller(div, content, api.message_render, false, false)
58 + )
5359
60 + return div
5461
62 + }
63 + }
5564
65 +}
5666
modules_basic/follow.jsView
@@ -1,86 +1,99 @@
11 var h = require('hyperscript')
22 var u = require('../util')
3-var plugs = require('../plugs')
4-var avatar = plugs.first(exports.avatar = [])
5-var avatar_name = plugs.first(exports.avatar_name = [])
6-var avatar_link = plugs.first(exports.avatar_link = [])
73 var pull = require('pull-stream')
8-var plugs = require('../plugs')
94
5 +//var plugs = require('../plugs')
6 +//var avatar = plugs.first(exports.avatar = [])
7 +//var avatar_name = plugs.first(exports.avatar_name = [])
8 +//var avatar_link = plugs.first(exports.avatar_link = [])
9 +//var message_confirm = plugs.first(exports.message_confirm = [])
10 +//var follower_of = plugs.first(exports.follower_of = [])
11 +
1012 //render a message when someone follows someone,
1113 //so you see new users
1214 function isRelated(value, name) {
1315 return value ? name : value === false ? 'un'+name : ''
1416 }
1517
16-exports.message_content =
17-exports.message_content_mini = function (msg) {
18- var content = msg.value.content
19- if(content.type == 'contact' && content.contact) {
20- var relation = isRelated(content.following, 'follows')
21- if(content.blocking) relation = 'blocks'
22- return [
23- relation, ' ',
24- avatar_link(content.contact, avatar_name(content.contact), '')
25- ]
26- }
18 +exports.needs = {
19 + avatar: 'first',
20 + avatar_name: 'first',
21 + avatar_link: 'first',
22 + message_confirm: 'first',
23 + follower_of: 'first'
2724 }
2825
29-exports.message_content = function (msg) {
26 +exports.gives = {
27 + message_content: true,
28 + message_content_mini: true,
29 + avatar_action: true,
30 +}
3031
31- var content = msg.value.content
32- if(content.type == 'contact' && content.contact) {
33- var relation = isRelated(content.following, 'follows')
34- if(content.blocking) relation = 'blocks'
35- return h('div.contact', relation, avatar(msg.value.content.contact, 'thumbnail'))
32 +exports.create = function (api) {
33 + var exports = {}
34 + exports.message_content =
35 + exports.message_content_mini = function (msg) {
36 + var content = msg.value.content
37 + if(content.type == 'contact' && content.contact) {
38 + var relation = isRelated(content.following, 'follows')
39 + if(content.blocking) relation = 'blocks'
40 + return [
41 + relation, ' ',
42 + api.avatar_link(content.contact, api.avatar_name(content.contact), '')
43 + ]
44 + }
3645 }
37-}
3846
39-var message_confirm = plugs.first(exports.message_confirm = [])
40-var follower_of = plugs.first(exports.follower_of = [])
47 + exports.message_content = function (msg) {
4148
42-exports.avatar_action = function (id) {
43- var follows_you, you_follow
49 + var content = msg.value.content
50 + if(content.type == 'contact' && content.contact) {
51 + var relation = isRelated(content.following, 'follows')
52 + if(content.blocking) relation = 'blocks'
53 + return h('div.contact', relation, api.avatar(msg.value.content.contact, 'thumbnail'))
54 + }
55 + }
4456
45- var self_id = require('../keys').id
46- follower_of(self_id, id, function (err, f) {
47- you_follow = f
48- update()
49- })
50- follower_of(id, self_id, function (err, f) {
51- follows_you = f
52- update()
53- })
57 + exports.avatar_action = function (id) {
58 + var follows_you, you_follow
5459
55- var state = h('label')
56- var label = h('span')
60 + var self_id = require('../keys').id
61 + api.follower_of(self_id, id, function (err, f) {
62 + you_follow = f
63 + update()
64 + })
65 + api.follower_of(id, self_id, function (err, f) {
66 + follows_you = f
67 + update()
68 + })
5769
58- function update () {
59- state.textContent = (
60- follows_you && you_follow ? 'friend'
61- : follows_you ? 'follows you'
62- : you_follow ? 'you follow'
63- : ''
70 + var state = h('label')
71 + var label = h('span')
72 +
73 + function update () {
74 + state.textContent = (
75 + follows_you && you_follow ? 'friend'
76 + : follows_you ? 'follows you'
77 + : you_follow ? 'you follow'
78 + : ''
79 + )
80 +
81 + label.textContent = you_follow ? 'unfollow' : 'follow'
82 + }
83 +
84 + return h('div', state,
85 + h('a', {href:'#', onclick: function () {
86 + api.message_confirm({
87 + type: 'contact',
88 + contact: id,
89 + following: !you_follow
90 + }, function (err, msg) {
91 + if (err) return console.error(err)
92 + you_follow = msg.value.content.following
93 + update()
94 + })
95 + }}, h('br'), label)
6496 )
65-
66- label.textContent = you_follow ? 'unfollow' : 'follow'
6797 }
68-
69- return h('div', state,
70- h('a', {href:'#', onclick: function () {
71- message_confirm({
72- type: 'contact',
73- contact: id,
74- following: !you_follow
75- }, function (err, msg) {
76- if (err) return console.error(err)
77- you_follow = msg.value.content.following
78- update()
79- })
80- }}, h('br'), label)
81- )
98 + return exports
8299 }
83-
84-
85-
86-
modules_basic/index.jsView
@@ -23,8 +23,8 @@
2323 "relationships.js": require('./relationships.js'),
2424 "search-box.js": require('./search-box.js'),
2525 "setup.js": require('./setup'),
2626 "suggest-mentions.js": require('./suggest-mentions.js'),
27- "suggest.js": require('./suggest.js'),
2827 "thread.js": require('./thread.js'),
2928 "timestamp.js": require('./timestamp.js')
3029 }
30 +
modules_basic/invite.jsView
@@ -1,112 +1,126 @@
1-
1 +'use strict'
22 var ref = require('ssb-ref')
33 var ssbClient = require('ssb-client')
44 var id = require('../keys').id
55 var h = require('hyperscript')
66
77 var Progress = require('hyperprogress')
88
9-var plugs = require('../plugs')
10-var sbot_publish = plugs.first(exports.sbot_publish = [])
11-var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = [])
12-var follower_of = plugs.first(exports.follower_of = [])
9 +//var plugs = require('../plugs')
10 +//var sbot_publish = plugs.first(exports.sbot_publish = [])
11 +//var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = [])
12 +//var follower_of = plugs.first(exports.follower_of = [])
1313
14-exports.invite_parse = function (invite) {
15- return ref.parseInvite(invite)
14 +exports.needs = {
15 + sbot_publish: 'first',
16 + sbot_gossip_connect: 'first',
17 + follower_of: 'first',
18 + invite_parse: 'first',
1619 }
1720
18-exports.invite_accept = function (invite, onProgress, cb) {
19- var data = exports.invite_parse(invite)
20- if(!data) return cb(new Error('not a valid invite code:' + invite))
21 +exports.gives = {
22 + invite_parse: true,
23 + invite_accept: true,
24 + screen_view: true
25 +}
2126
22- onProgress('connecting...')
27 +exports.create = function (api) {
28 + var self
29 + return self = {
30 + invite_parse: function (invite) {
31 + return ref.parseInvite(invite)
32 + },
33 +
34 + invite_accept: function (invite, onProgress, cb) {
35 + var data = self.invite_parse(invite)
36 + if(!data) return cb(new Error('not a valid invite code:' + invite))
37 +
38 + onProgress('connecting...')
2339
24- sbot_gossip_connect(data.remote, function (err) {
25- if(err) console.log(err)
26- })
40 + api.sbot_gossip_connect(data.remote, function (err) {
41 + if(err) console.log(err)
42 + })
2743
28- ssbClient(null, {
29- remote: data.invite,
30- manifest: { invite: {use: 'async'}, getAddress: 'async' }
31- }, function (err, sbot) {
32- if(err) return cb(err)
33- onProgress('requesting follow...')
34- console.log(sbot)
35- sbot.invite.use({feed: id}, function (err, msg) {
44 + ssbClient(null, {
45 + remote: data.invite,
46 + manifest: { invite: {use: 'async'}, getAddress: 'async' }
47 + }, function (err, sbot) {
48 + if(err) return cb(err)
49 + onProgress('requesting follow...')
50 + console.log(sbot)
51 + sbot.invite.use({feed: id}, function (err, msg) {
3652
37- //if they already follow us, just check we actually follow them.
38- if(err) follower_of(id, data.key, function (_err, follows) {
39- if(follows) cb(err)
53 + //if they already follow us, just check we actually follow them.
54 + if(err) api.follower_of(id, data.key, function (_err, follows) {
55 + if(follows) cb(err)
56 + else next()
57 + })
4058 else next()
41- })
42- else next()
4359
44- function next () {
45- onProgress('following...')
60 + function next () {
61 + onProgress('following...')
4662
47- //remove the seed from the shs address.
48- //then it's correct address.
49- //this should make the browser connect to this as remote.
50- //we don't want to do this if when using this locally, though.
51- if(process.title === 'browser')
52- localStorage.remote = data.remote
63 + //remove the seed from the shs address.
64 + //then it's correct address.
65 + //this should make the browser connect to this as remote.
66 + //we don't want to do this if when using this locally, though.
67 + if(process.title === 'browser')
68 + localStorage.remote = data.remote
5369
54- sbot_publish({
55- type: 'contact',
56- contact: data.key,
57- following: true,
58- }, cb)
59- }
60- })
61- })
62-}
70 + api.sbot_publish({
71 + type: 'contact',
72 + contact: data.key,
73 + following: true,
74 + }, cb)
75 + }
76 + })
77 + })
78 + },
6379
64-exports.screen_view = function (invite) {
80 + screen_view: function (invite) {
6581
66- var data = ref.parseInvite(invite)
67- if(!data) return
82 + var data = ref.parseInvite(invite)
83 + if(!data) return
6884
69- var progress = Progress(4)
85 + var progress = Progress(4)
7086
71- //connect to server
72- //request follow
73- //post pub announce
74- //post follow pub
75- var div = h('div.column',
76- h('div',
77- "you have been invited to join:", h('br'),
78- h('code', data.invite)
79- ),
80- h('button', 'accept', {onclick: attempt}),
81- progress
82- )
87 + //connect to server
88 + //request follow
89 + //post pub announce
90 + //post follow pub
91 + var div = h('div.column',
92 + h('div',
93 + "you have been invited to join:", h('br'),
94 + h('code', data.invite)
95 + ),
96 + h('button', 'accept', {onclick: attempt}),
97 + progress
98 + )
8399
84- function attempt () {
85- exports.invite_accept(invite, function (message) {
86- progress.next(message)
87- }, function (err) {
88- if(err) return progress.fail(err)
89- progress.complete()
90- //check for redirect
91- var parts = location.hash.substring(1).split('#')
100 + function attempt () {
101 + self.invite_accept(invite, function (message) {
102 + progress.next(message)
103 + }, function (err) {
104 + if(err) return progress.fail(err)
105 + progress.complete()
106 + //check for redirect
107 + var parts = location.hash.substring(1).split('#')
92108
93- //TODO: handle in a consistent way with either hashrouting
94- //or with tabs...
95- if(parts[0] === data.invite)
96- location.hash = ''
97- else
98- console.log("NO REDIRECT")
99- })
100- }
109 + //TODO: handle in a consistent way with either hashrouting
110 + //or with tabs...
111 + if(parts[0] === data.invite)
112 + location.hash = ''
113 + else
114 + console.log("NO REDIRECT")
115 + })
116 + }
101117
102- // If we are in the browser,
103- // and do not already have a remote set, automatically trigger the invite.
104- if(process.title == 'browser' && !localStorage.remote) attempt()
118 + // If we are in the browser,
119 + // and do not already have a remote set, automatically trigger the invite.
120 + if(process.title == 'browser' && !localStorage.remote) attempt()
105121
106- return div
122 + return div
123 + }
124 + }
107125 }
108126
109-
110-
111-
112-
modules_basic/like.jsView
@@ -4,59 +4,75 @@
44 var pull = require('pull-stream')
55
66 var plugs = require('../plugs')
77
8-var message_confirm = plugs.first(exports.message_confirm = [])
9-var message_link = plugs.first(exports.message_link = [])
10-var sbot_links = plugs.first(exports.sbot_links = [])
8 +//var message_confirm = plugs.first(exports.message_confirm = [])
9 +//var message_link = plugs.first(exports.message_link = [])
10 +//var sbot_links = plugs.first(exports.sbot_links = [])
1111
12 +exports.needs = {
13 + message_confirm: 'first',
14 + message_link: 'first',
15 + sbot_links: 'first'
16 +}
1217
13-exports.message_content =
14-exports.message_content_mini = function (msg, sbot) {
15- if(msg.value.content.type !== 'vote') return
16- var link = msg.value.content.vote.link
17- return [
18- msg.value.content.vote.value > 0 ? 'dug' : 'undug',
19- ' ', message_link(link)
20- ]
18 +exports.gives = {
19 + message_content: true,
20 + message_content_mini: true,
21 + message_meta: true,
22 + message_action: true
2123 }
2224
23-exports.message_meta = function (msg, sbot) {
24- var digs = h('a')
25 +exports.create = function (api) {
26 + var exports = {}
2527
26- var votes = []
27- for(var k in CACHE) {
28- if(CACHE[k].content.type == 'vote' &&
29- (CACHE[k].content.vote == msg.key ||
30- CACHE[k].content.vote.link == msg.key
31- ))
32- votes.push({source: CACHE[k].author, dest: k, rel: 'vote'})
28 + exports.message_content =
29 + exports.message_content_mini = function (msg, sbot) {
30 + if(msg.value.content.type !== 'vote') return
31 + var link = msg.value.content.vote.link
32 + return [
33 + msg.value.content.vote.value > 0 ? 'dug' : 'undug',
34 + ' ', api.message_link(link)
35 + ]
3336 }
3437
35- if(votes.length === 1)
36- digs.textContent = ' 1 Dig'
37- if(votes.length > 1)
38- digs.textContent = ' ' + votes.length + ' Digs'
38 + exports.message_meta = function (msg, sbot) {
39 + var digs = h('a')
3940
40- return digs
41-}
41 + var votes = []
42 + for(var k in CACHE) {
43 + if(CACHE[k].content.type == 'vote' &&
44 + (CACHE[k].content.vote == msg.key ||
45 + CACHE[k].content.vote.link == msg.key
46 + ))
47 + votes.push({source: CACHE[k].author, dest: k, rel: 'vote'})
48 + }
4249
43-exports.message_action = function (msg, sbot) {
44- if(msg.value.content.type !== 'vote')
45- return h('a.dig', {href: '#', onclick: function () {
46- var dig = {
47- type: 'vote',
48- vote: { link: msg.key, value: 1, expression: 'Dig' }
49- }
50- if(msg.value.content.recps) {
51- dig.recps = msg.value.content.recps.map(function (e) {
52- return e && typeof e !== 'string' ? e.link : e
53- })
54- dig.private = true
55- }
56- //TODO: actually publish...
50 + if(votes.length === 1)
51 + digs.textContent = ' 1 Dig'
52 + if(votes.length > 1)
53 + digs.textContent = ' ' + votes.length + ' Digs'
5754
58- message_confirm(dig)
59- }}, 'Dig')
55 + return digs
56 + }
6057
58 + exports.message_action = function (msg, sbot) {
59 + if(msg.value.content.type !== 'vote')
60 + return h('a.dig', {href: '#', onclick: function () {
61 + var dig = {
62 + type: 'vote',
63 + vote: { link: msg.key, value: 1, expression: 'Dig' }
64 + }
65 + if(msg.value.content.recps) {
66 + dig.recps = msg.value.content.recps.map(function (e) {
67 + return e && typeof e !== 'string' ? e.link : e
68 + })
69 + dig.private = true
70 + }
71 + //TODO: actually publish...
72 +
73 + api.message_confirm(dig)
74 + }}, 'Dig')
75 +
76 + }
77 + return exports
6178 }
62-
modules_basic/markdown.jsView
@@ -1,40 +1,52 @@
11 var markdown = require('ssb-markdown')
22 var h = require('hyperscript')
33 var ref = require('ssb-ref')
44
5-var plugs = require('../plugs')
6-var blob_url = plugs.first(exports.blob_url = [])
7-var emoji_url = plugs.first(exports.emoji_url = [])
5 +//var plugs = require('../plugs')
6 +//var blob_url = plugs.first(exports.blob_url = [])
7 +//var emoji_url = plugs.first(exports.emoji_url = [])
88
9-function renderEmoji(emoji) {
10- var url = emoji_url(emoji)
11- if (!url) return ':' + emoji + ':'
12- return '<img src="' + encodeURI(url) + '"'
13- + ' alt=":' + escape(emoji) + ':"'
14- + ' title=":' + escape(emoji) + ':"'
15- + ' class="emoji">'
9 +exports.needs = {
10 + blob_url: 'first',
11 +// emoji_url: 'first'
1612 }
1713
18-exports.markdown = function (content) {
19- if('string' === typeof content)
20- content = {text: content}
21- //handle patchwork style mentions.
22- var mentions = {}
23- if(Array.isArray(content.mentions))
24- content.mentions.forEach(function (link) {
25- if(link.name) mentions["@"+link.name] = link.link
14 +exports.gives = 'markdown'
15 +
16 +exports.create = function (api) {
17 +
18 +//emoji is in extra, disable for a sec
19 +
20 +// function renderEmoji(emoji) {
21 +// var url = api.emoji_url(emoji)
22 +// if (!url) return ':' + emoji + ':'
23 +// return '<img src="' + encodeURI(url) + '"'
24 +// + ' alt=":' + escape(emoji) + ':"'
25 +// + ' title=":' + escape(emoji) + ':"'
26 +// + ' class="emoji">'
27 +// }
28 +
29 + return function (content) {
30 + if('string' === typeof content)
31 + content = {text: content}
32 + //handle patchwork style mentions.
33 + var mentions = {}
34 + if(Array.isArray(content.mentions))
35 + content.mentions.forEach(function (link) {
36 + if(link.name) mentions["@"+link.name] = link.link
37 + })
38 +
39 + var md = h('div.markdown')
40 + md.innerHTML = markdown.block(content.text, {
41 + // emoji: renderEmoji,
42 + toUrl: function (id) {
43 + if(ref.isBlob(id)) return api.blob_url(id)
44 + return '#'+(mentions[id]?mentions[id]:id)
45 + }
2646 })
2747
28- var md = h('div.markdown')
29- md.innerHTML = markdown.block(content.text, {
30- emoji: renderEmoji,
31- toUrl: function (id) {
32- if(ref.isBlob(id)) return blob_url(id)
33- return '#'+(mentions[id]?mentions[id]:id)
34- }
35- })
48 + return md
3649
37- return md
38-
50 + }
3951 }
4052
modules_basic/message-link.jsView
@@ -1,29 +1,35 @@
11 var h = require('hyperscript')
22 var ref = require('ssb-ref')
33
4-var first = require('../plugs').first
5-var sbot_get = first(exports.sbot_get = [])
6-var message_name = first(exports.message_name = [])
4 +//var first = require('../plugs').first
5 +//var sbot_get = first(exports.sbot_get = [])
6 +//var message_name = first(exports.message_name = [])
77
8-exports.message_link = function (id) {
8 +exports.needs = {
9 + message_name: 'first'
10 +}
911
10- if('string' !== typeof id)
11- throw new Error('link must be to message id')
12 +exports.gives = 'message_link'
1213
13- var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...')
14 +exports.create = function (api) {
1415
15- if(ref.isMsg(id))
16- message_name(id, function (err, name) {
17- if(err) console.error(err)
18- else link.textContent = name
19- })
16 + return function (id) {
2017
21- return link
22-}
18 + if('string' !== typeof id)
19 + throw new Error('link must be to message id')
2320
21 + var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...')
2422
23 + if(ref.isMsg(id))
24 + api.message_name(id, function (err, name) {
25 + if(err) console.error(err)
26 + else link.textContent = name
27 + })
2528
29 + return link
30 + }
31 +}
2632
2733
2834
2935
modules_basic/message-name.jsView
@@ -1,21 +1,26 @@
11
2-var sbot_get = require('../plugs').first(exports.sbot_get = [])
3-
42 function title (s) {
53 var m = /^\n*([^\n]{0,40})/.exec(s)
64 return m && (m[1].length == 40 ? m[1]+'...' : m[1])
75 }
86
9-exports.message_name = function (id, cb) {
10- sbot_get(id, function (err, value) {
11- if(err && err.name == 'NotFoundError')
12- return cb(null, id.substring(0, 10)+'...(missing)')
13- if(value.content.type === 'post' && 'string' === typeof value.content.text)
14- return cb(null, title(value.content.text))
15- else if('string' === typeof value.content.text)
16- return cb(null, value.content.type + ':'+title(value.content.text))
17- else
18- return cb(null, id.substring(0, 10)+'...')
19- })
7 +exports.needs = { sbot_get: 'first' }
8 +exports.gives = 'message_name'
9 +
10 +//TODO: rewrite as observable?
11 +
12 +exports.create = function (api) {
13 + return function (id, cb) {
14 + api.sbot_get(id, function (err, value) {
15 + if(err && err.name == 'NotFoundError')
16 + return cb(null, id.substring(0, 10)+'...(missing)')
17 + if(value.content.type === 'post' && 'string' === typeof value.content.text)
18 + return cb(null, title(value.content.text))
19 + else if('string' === typeof value.content.text)
20 + return cb(null, value.content.type + ':'+title(value.content.text))
21 + else
22 + return cb(null, id.substring(0, 10)+'...')
23 + })
24 + }
2025 }
2126
modules_basic/message.jsView
@@ -1,109 +1,122 @@
11 var h = require('hyperscript')
22 var u = require('../util')
33 var pull = require('pull-stream')
44
5-var plugs = require('../plugs')
6-var message_content = plugs.first(exports.message_content = [])
7-var message_content_mini = plugs.first(exports.message_content_mini = [])
8-var avatar = plugs.first(exports.avatar = [])
9-var avatar_name = plugs.first(exports.avatar_name = [])
10-var avatar_link = plugs.first(exports.avatar_link = [])
11-var message_meta = plugs.map(exports.message_meta = [])
12-var message_action = plugs.map(exports.message_action = [])
13-var message_link = plugs.first(exports.message_link = [])
5 +//var plugs = require('../plugs')
6 +//
7 +//var message_content = plugs.first(exports.message_content = [])
8 +//var message_content_mini = plugs.first(exports.message_content_mini = [])
9 +//
10 +//var avatar = plugs.first(exports.avatar = [])
11 +//var avatar_name = plugs.first(exports.avatar_name = [])
12 +//var avatar_link = plugs.first(exports.avatar_link = [])
13 +//var message_meta = plugs.map(exports.message_meta = [])
14 +//var message_action = plugs.map(exports.message_action = [])
15 +//var message_link = plugs.first(exports.message_link = [])
16 +//
17 +//var sbot_links = plugs.first(exports.sbot_links = [])
1418
15-var sbot_links = plugs.first(exports.sbot_links = [])
16-
17-function mini(msg, el) {
18- var div = h('div.message.message--mini',
19- h('div.row',
20- h('div',
21- avatar_link(msg.value.author, avatar_name(msg.value.author)),
22- h('span.message_content', el)),
23- h('div.message_meta.row', message_meta(msg))
24- )
25- )
26- div.setAttribute('tabindex', '0')
27- return div
19 +exports.needs = {
20 + message_content: 'first',
21 + message_content_mini: 'first',
22 + avatar: 'first',
23 + avatar_name: 'first',
24 + avatar_link: 'first',
25 + message_meta: 'map',
26 + message_action: 'map',
27 + message_link: 'first',
28 +// sbot_links: 'first'
2829 }
2930
31 +exports.gives = 'message_render'
32 +
3033 function message_content_mini_fallback(msg) {
3134 return h('code', msg.value.content.type)
3235 }
3336
34-exports.message_render = function (msg, sbot) {
35- var el = message_content_mini(msg)
36- if(el) return mini(msg, el)
37 +exports.create = function (api) {
3738
38- var el = message_content(msg)
39- if(!el) return mini(msg, message_content_mini_fallback(msg))
40-
41- var links = []
42- for(var k in CACHE) {
43- var _msg = CACHE[k]
44- if(Array.isArray(_msg.content.mentions)) {
45- for(var i = 0; i < _msg.content.mentions.length; i++)
46- if(_msg.content.mentions[i].link == msg.key)
47- links.push(k)
48- }
39 + function mini(msg, el) {
40 + var div = h('div.message.message--mini',
41 + h('div.row',
42 + h('div',
43 + api.avatar_link(msg.value.author, api.avatar_name(msg.value.author)),
44 + h('span.message_content', el)),
45 + h('div.message_meta.row', api.message_meta(msg))
46 + )
47 + )
48 + div.setAttribute('tabindex', '0')
49 + return div
4950 }
5051
51- var backlinks = h('div.backlinks')
52- if(links.length)
53- backlinks.appendChild(h('label', 'backlinks:',
54- h('div', links.map(function (key) {
55- return message_link(key)
56- }))
57- ))
52 + return function (msg, sbot) {
53 + var el = api.message_content_mini(msg)
54 + if(el) return mini(msg, el)
5855
56 + var el = api.message_content(msg)
57 + if(!el) return mini(msg, message_content_mini_fallback(msg))
5958
60-// pull(
61-// sbot_links({dest: msg.key, rel: 'mentions', keys: true}),
62-// pull.collect(function (err, links) {
63-// if(links.length)
64-// backlinks.appendChild(h('label', 'backlinks:',
65-// h('div', links.map(function (link) {
66-// return message_link(link.key)
67-// }))
68-// ))
69-// })
70-// )
71-
72- var msg = h('div.message',
73- h('div.title.row',
74- h('div.avatar', avatar(msg.value.author, 'thumbnail')),
75- h('div.message_meta.row', message_meta(msg))
76- ),
77- h('div.message_content', el),
78- h('div.message_actions.row',
79- h('div.actions', message_action(msg),
80- h('a', {href: '#' + msg.key}, 'Reply')
81- )
82- ),
83- backlinks,
84- {onkeydown: function (ev) {
85- //on enter, hit first meta.
86- if(ev.keyCode == 13) {
87-
88- // unless in an input
89- if (ev.target.nodeName === 'INPUT'
90- || ev.target.nodeName === 'TEXTAREA') return
91-
92- msg.querySelector('.enter').click()
59 + var links = []
60 + for(var k in CACHE) {
61 + var _msg = CACHE[k]
62 + if(Array.isArray(_msg.content.mentions)) {
63 + for(var i = 0; i < _msg.content.mentions.length; i++)
64 + if(_msg.content.mentions[i].link == msg.key)
65 + links.push(k)
9366 }
94- }}
95- )
67 + }
9668
97- // ); hyperscript does not seem to set attributes correctly.
98- msg.setAttribute('tabindex', '0')
69 + var backlinks = h('div.backlinks')
70 + if(links.length)
71 + backlinks.appendChild(h('label', 'backlinks:',
72 + h('div', links.map(function (key) {
73 + return api.message_link(key)
74 + }))
75 + ))
9976
100- return msg
101-}
10277
78 + // pull(
79 + // sbot_links({dest: msg.key, rel: 'mentions', keys: true}),
80 + // pull.collect(function (err, links) {
81 + // if(links.length)
82 + // backlinks.appendChild(h('label', 'backlinks:',
83 + // h('div', links.map(function (link) {
84 + // return message_link(link.key)
85 + // }))
86 + // ))
87 + // })
88 + // )
10389
90 + var msg = h('div.message',
91 + h('div.title.row',
92 + h('div.avatar', api.avatar(msg.value.author, 'thumbnail')),
93 + h('div.message_meta.row', api.message_meta(msg))
94 + ),
95 + h('div.message_content', el),
96 + h('div.message_actions.row',
97 + h('div.actions', api.message_action(msg),
98 + h('a', {href: '#' + msg.key}, 'Reply')
99 + )
100 + ),
101 + backlinks,
102 + {onkeydown: function (ev) {
103 + //on enter, hit first meta.
104 + if(ev.keyCode == 13) {
104105
106 + // unless in an input
107 + if (ev.target.nodeName === 'INPUT'
108 + || ev.target.nodeName === 'TEXTAREA') return
105109
110 + msg.querySelector('.enter').click()
111 + }
112 + }}
113 + )
106114
115 + // ); hyperscript does not seem to set attributes correctly.
116 + msg.setAttribute('tabindex', '0')
107117
118 + return msg
119 + }
120 +}
108121
109122
modules_basic/names.jsView
@@ -5,12 +5,23 @@
55 function all(stream, cb) {
66 pull(stream, pull.collect(cb))
77 }
88
9-var plugs = require('../plugs')
10-var sbot_links2 = plugs.first(exports.sbot_links2 = [])
11-var sbot_query = plugs.first(exports.sbot_query = [])
9 +//var plugs = require('../plugs')
10 +//var sbot_links2 = plugs.first(exports.sbot_links2 = [])
11 +//var sbot_query = plugs.first(exports.sbot_query = [])
12 +//
13 +exports.needs = {
14 + sbot_links2: 'first',
15 + sbot_query: 'first'
16 +}
1217
18 +exports.gives = {
19 + connection_status: true,
20 + signifier: true,
21 + signified: true,
22 +}
23 +
1324 /*
1425 filter(rel: ['mentions', prefix('@')]) | reduce(name: rel[1], value: count())
1526 */
1627
@@ -107,63 +118,66 @@
107118 ts: "timestamp"
108119 }},
109120 reduce
110121 ]
122 +exports.create = function (api) {
111123
124 + var exports = {}
125 + exports.connection_status = function (err) {
126 + if(!err) {
127 + pull(
128 + many([
129 + api.sbot_links2({query: [filter, map, reduce]}),
130 + add_sigil(api.sbot_query({query: [filter2, map2, reduce]})),
131 + add_sigil(api.sbot_query({query: queryNamedGitRepos}))
132 + ]),
133 + //reducing also ensures order by the lookup properties
134 + //in this case: [name, id]
135 + mfr.reduce(merge),
136 + pull.collect(function (err, ary) {
137 + if(!err) {
138 + NAMES = names = ary
139 + ready = true
140 + while(waiting.length) waiting.shift()()
141 + }
142 + })
143 + )
112144
113-exports.connection_status = function (err) {
114- if(!err) {
115- pull(
116- many([
117- sbot_links2({query: [filter, map, reduce]}),
118- add_sigil(sbot_query({query: [filter2, map2, reduce]})),
119- add_sigil(sbot_query({query: queryNamedGitRepos}))
145 + pull(many([
146 + api.sbot_links2({query: [filter, map], old: false}),
147 + add_sigil(api.sbot_query({query: [filter2, map2], old: false})),
148 + add_sigil(api.sbot_query({query: queryNamedGitRepos, old: false}))
120149 ]),
121- //reducing also ensures order by the lookup properties
122- //in this case: [name, id]
123- mfr.reduce(merge),
124- pull.collect(function (err, ary) {
125- if(!err) {
126- NAMES = names = ary
127- ready = true
128- while(waiting.length) waiting.shift()()
129- }
130- })
131- )
150 + pull.drain(update))
151 + }
152 + }
132153
133- pull(many([
134- sbot_links2({query: [filter, map], old: false}),
135- add_sigil(sbot_query({query: [filter2, map2], old: false})),
136- add_sigil(sbot_query({query: queryNamedGitRepos, old: false}))
137- ]),
138- pull.drain(update))
154 + function async(fn) {
155 + return function (value, cb) {
156 + function go () { cb(null, fn(value)) }
157 + if(ready) go()
158 + else waiting.push(go)
159 + }
139160 }
140-}
141161
142-function async(fn) {
143- return function (value, cb) {
144- function go () { cb(null, fn(value)) }
145- if(ready) go()
146- else waiting.push(go)
162 + function rank(ary) {
163 + //sort by most used, or most recently used
164 + return ary.sort(function (a, b) { return b.rank - a.rank || b.ts - a.ts })
147165 }
148-}
149166
150-function rank(ary) {
151- //sort by most used, or most recently used
152- return ary.sort(function (a, b) { return b.rank - a.rank || b.ts - a.ts })
153-}
167 + //we are just iterating over the entire array.
168 + //if this becomes a problem, maintain two arrays
169 + //one of each sort order, but do not duplicate the objects.
170 + //that should mean the space required is just 2x object references,
171 + //not 2x objects, and we can use binary search to find matches.
154172
155-//we are just iterating over the entire array.
156-//if this becomes a problem, maintain two arrays
157-//one of each sort order, but do not duplicate the objects.
158-//that should mean the space required is just 2x object references,
159-//not 2x objects, and we can use binary search to find matches.
173 + exports.signifier = async(function (id) {
174 + return rank(names.filter(function (e) { return e.id == id}))
175 + })
160176
161-exports.signifier = async(function (id) {
162- return rank(names.filter(function (e) { return e.id == id}))
163-})
177 + exports.signified = async(function (name) {
178 + var rx = new RegExp('^'+name)
179 + return rank(names.filter(function (e) { return rx.test(e.name) }))
180 + })
164181
165-exports.signified = async(function (name) {
166- var rx = new RegExp('^'+name)
167- return rank(names.filter(function (e) { return rx.test(e.name) }))
168-})
169-
182 + return exports
183 +}
modules_basic/post.jsView
@@ -4,23 +4,30 @@
44 var ref = require('ssb-ref')
55
66 //render a message
77
8-var plugs = require('../plugs')
9-var message_link = plugs.first(exports.message_link = [])
10-var markdown = plugs.first(exports.markdown = [])
8 +//var plugs = require('../plugs')
9 +//var message_link = plugs.first(exports.message_link = [])
10 +//var markdown = plugs.first(exports.markdown = [])
11 +//
1112
12-exports.message_content = function (data) {
13- if(!data.value.content || !data.value.content.text) return
13 +exports.needs = { message_link: 'first', markdown: 'first' }
1414
15- var root = data.value.content.root
16- var re = !root ? null : h('span', 're: ', message_link(root))
15 +exports.gives = 'message_content'
1716
18- return h('div',
19- re,
20- markdown(data.value.content)
21- )
17 +exports.create = function (api) {
18 + return function (data) {
19 + if(!data.value.content || !data.value.content.text) return
2220
21 + var root = data.value.content.root
22 + var re = !root ? null : h('span', 're: ', api.message_link(root))
23 +
24 + return h('div',
25 + re,
26 + api.markdown(data.value.content)
27 + )
28 +
29 + }
2330 }
2431
2532
2633
@@ -33,5 +40,4 @@
3340
3441
3542
3643
37-
modules_basic/private.jsView
@@ -1,105 +1,128 @@
1 +'use strict'
12 var h = require('hyperscript')
23 var u = require('../util')
34 var pull = require('pull-stream')
45 var Scroller = require('pull-scroll')
56 var ref = require('ssb-ref')
67
7-var plugs = require('../plugs')
8 +//var plugs = require('../plugs')
9 +//
10 +//var message_render = plugs.first(exports.message_render = [])
11 +//var message_compose = plugs.first(exports.message_compose = [])
12 +//var message_unbox = plugs.first(exports.message_unbox = [])
13 +//var sbot_log = plugs.first(exports.sbot_log = [])
14 +//var sbot_whoami = plugs.first(exports.sbot_whoami = [])
15 +//var avatar_image_link = plugs.first(exports.avatar_image_link = [])
16 +//var emoji_url = plugs.first(exports.emoji_url = [])
817
9-var message_render = plugs.first(exports.message_render = [])
10-var message_compose = plugs.first(exports.message_compose = [])
11-var message_unbox = plugs.first(exports.message_unbox = [])
12-var sbot_log = plugs.first(exports.sbot_log = [])
13-var sbot_whoami = plugs.first(exports.sbot_whoami = [])
14-var avatar_image_link = plugs.first(exports.avatar_image_link = [])
15-var emoji_url = plugs.first(exports.emoji_url = [])
18 +function map(ary, iter) {
19 + if(Array.isArray(ary)) return ary.map(iter)
20 +}
1621
17-function unbox () {
18- return pull(
19- pull.filter(function (msg) {
20- return 'string' == typeof msg.value.content
21- }),
22- pull.map(function (msg) {
23- return message_unbox(msg)
24- }),
25- pull.filter(Boolean)
26- )
22 +exports.needs = {
23 + message_render: 'first',
24 + message_compose: 'first',
25 + message_unbox: 'first',
26 + sbot_log: 'first',
27 + sbot_whoami: 'first',
28 + avatar_image_link: 'first',
29 +// emoji_link: 'first'
2730 }
2831
29-exports.builtin_tabs = function () {
30- return ['/private']
32 +exports.gives = {
33 + builtin_tabs: true,
34 + screen_view: true,
35 + message_meta: true,
36 + message_content_mini: true
3137 }
3238
33-exports.screen_view = function (path) {
34- if(path !== '/private') return
39 +exports.create = function (api) {
3540
36- var div = h('div.column.scroller',
37- {style: {'overflow':'auto'}})
41 + function unbox () {
42 + return pull(
43 + pull.filter(function (msg) {
44 + return 'string' == typeof msg.value.content
45 + }),
46 + pull.map(function (msg) {
47 + return api.message_unbox(msg)
48 + }),
49 + pull.filter(Boolean)
50 + )
51 + }
3852
39- // if local id is different from sbot id, sbot won't have indexes of
40- // private threads
41- var id = require('../keys').id
42- sbot_whoami(function (err, feed) {
43- if (err) return console.error(err)
44- if(id !== feed.id)
45- return div.appendChild(h('h4',
46- 'Private messages are not supported in the lite client.'))
53 + return {
54 + builtin_tabs: function () {
55 + return ['/private']
56 + },
4757
48- var compose = message_compose(
49- {type: 'post', recps: [], private: true},
50- {
51- prepublish: function (msg) {
52- msg.recps = [id].concat(msg.mentions).filter(function (e) {
53- return ref.isFeed('string' === typeof e ? e : e.link)
54- })
55- if(!msg.recps.length)
56- throw new Error('cannot make private message without recipients - just mention the user in an at reply in the message you send')
57- return msg
58- },
59- placeholder: 'Write a private message'
60- }
61- )
58 + screen_view: function (path) {
59 + if(path !== '/private') return
6260
63- var content = h('div.column.scroller__content')
64- div.appendChild(h('div.scroller__wrapper', compose, content))
61 + var div = h('div.column.scroller',
62 + {style: {'overflow':'auto'}})
6563
66- pull(
67- u.next(sbot_log, {old: false, limit: 100}),
68- unbox(),
69- Scroller(div, content, message_render, true, false)
70- )
64 + // if local id is different from sbot id, sbot won't have indexes of
65 + // private threads
66 + //TODO: put all private indexes client side.
67 + var id = require('../keys').id
68 + api.sbot_whoami(function (err, feed) {
69 + if (err) return console.error(err)
70 + if(id !== feed.id)
71 + return div.appendChild(h('h4',
72 + 'Private messages are not supported in the lite client.'))
7173
72- pull(
73- u.next(sbot_log, {reverse: true, limit: 1000}),
74- unbox(),
75- Scroller(div, content, message_render, false, false, function (err) {
76- if(err) throw err
74 + var compose = api.message_compose(
75 + {type: 'post', recps: [], private: true},
76 + {
77 + prepublish: function (msg) {
78 + msg.recps = [id].concat(msg.mentions).filter(function (e) {
79 + return ref.isFeed('string' === typeof e ? e : e.link)
80 + })
81 + if(!msg.recps.length)
82 + throw new Error('cannot make private message without recipients - just mention the user in an at reply in the message you send')
83 + return msg
84 + },
85 + placeholder: 'Write a private message'
86 + }
87 + )
88 +
89 + var content = h('div.column.scroller__content')
90 + div.appendChild(h('div.scroller__wrapper', compose, content))
91 +
92 + pull(
93 + u.next(api.sbot_log, {old: false, limit: 100}),
94 + unbox(),
95 + Scroller(div, content, api.message_render, true, false)
96 + )
97 +
98 + pull(
99 + u.next(api.sbot_log, {reverse: true, limit: 1000}),
100 + unbox(),
101 + Scroller(div, content, api.message_render, false, false, function (err) {
102 + if(err) throw err
103 + })
104 + )
77105 })
78- )
79- })
80106
81- return div
82-}
107 + return div
108 + },
83109
84-function map(ary, iter) {
85- if(Array.isArray(ary)) return ary.map(iter)
86-}
110 + message_meta: function (msg) {
111 + if(msg.value.content.recps || msg.value.private)
112 + return h('span.row', 'PRIVATE', map(msg.value.content.recps, function (id) {
113 + return api.avatar_image_link('string' == typeof id ? id : id.link, 'thumbnail')
114 + }))
115 + },
87116
88-exports.message_meta = function (msg) {
89- if(msg.value.content.recps || msg.value.private)
90- return h('span.row', 'PRIVATE', map(msg.value.content.recps, function (id) {
91- return avatar_image_link('string' == typeof id ? id : id.link, 'thumbnail')
92- }))
93-}
117 + message_content_mini: function (msg, sbot) {
118 + if (typeof msg.value.content === 'string') {
119 + var icon = false //api.emoji_url('lock')
120 + return icon
121 + ? h('img', {className: 'emoji', src: icon})
122 + : 'PRIVATE'
123 + }
124 + }
125 + }
94126
95-exports.message_content_mini = function (msg, sbot) {
96- if (typeof msg.value.content === 'string') {
97- var icon = emoji_url('lock')
98- return icon
99- ? h('img', {className: 'emoji', src: icon})
100- : 'PRIVATE'
101- }
102127 }
103128
104-
105-
modules_basic/pub.jsView
@@ -1,18 +1,29 @@
11 var h = require('hyperscript')
2-var plugs = require('../plugs')
3-var avatar_name = plugs.first(exports.avatar_name = [])
4-var avatar_link = plugs.first(exports.avatar_link = [])
2 +//var plugs = require('../plugs')
3 +//var avatar_name = plugs.first(exports.avatar_name = [])
4 +//var avatar_link = plugs.first(exports.avatar_link = [])
5 +//
6 +exports.needs = {
7 + avatar_name: 'first',
8 + avatar_link: 'first'
9 +}
510
6-exports.message_content = function (msg, sbot) {
7- var c = msg.value.content
8- if (c.type === 'pub') {
9- var address = c.address || {}
10- return [
11- h('p', 'announced an address for ',
12- avatar_link(address.key, avatar_name(address.key)), ':'),
13- h('blockquote',
14- h('code', address.host + ':' + address.port)
15- )
16- ]
11 +exports.gives = 'message_content'
12 +
13 +exports.create = function (api) {
14 +
15 + return function (msg, sbot) {
16 + var c = msg.value.content
17 + if (c.type === 'pub') {
18 + var address = c.address || {}
19 + return [
20 + h('p', 'announced an address for ',
21 + api.avatar_link(address.key, api.avatar_name(address.key)), ':'),
22 + h('blockquote',
23 + h('code', address.host + ':' + address.port)
24 + )
25 + ]
26 + }
1727 }
28 +
1829 }
modules_basic/public.jsView
@@ -2,39 +2,53 @@
22 var u = require('../util')
33 var pull = require('pull-stream')
44 var Scroller = require('pull-scroll')
55
6-var plugs = require('../plugs')
7-var message_render = plugs.first(exports.message_render = [])
8-var message_compose = plugs.first(exports.message_compose = [])
9-var sbot_log = plugs.first(exports.sbot_log = [])
6 +//var plugs = require('../plugs')
7 +//var message_render = plugs.first(exports.message_render = [])
8 +//var message_compose = plugs.first(exports.message_compose = [])
9 +//var sbot_log = plugs.first(exports.sbot_log = [])
1010
11-exports.builtin_tabs = function () {
12- return ['/public']
11 +exports.needs = {
12 + message_render: 'first',
13 + message_compose: 'first',
14 + sbot_log: 'first',
1315 }
1416
15-exports.screen_view = function (path, sbot) {
16- if(path === '/public') {
17 +exports.gives = {
18 + builtin_tabs: true, screen_view: true
19 +}
1720
18- var content = h('div.column.scroller__content')
19- var div = h('div.column.scroller',
20- {style: {'overflow':'auto'}},
21- h('div.scroller__wrapper',
22- message_compose({type: 'post'}, {placeholder: 'Write a public message'}),
23- content
24- )
25- )
21 +exports.create = function (api) {
2622
27- pull(
28- u.next(sbot_log, {old: false, limit: 100}),
29- Scroller(div, content, message_render, true, false)
30- )
23 + return {
24 + builtin_tabs: function () {
25 + return ['/public']
26 + },
3127
32- pull(
33- u.next(sbot_log, {reverse: true, limit: 100, live: false}),
34- Scroller(div, content, message_render, false, false)
35- )
28 + screen_view: function (path, sbot) {
29 + if(path === '/public') {
3630
37- return div
31 + var content = h('div.column.scroller__content')
32 + var div = h('div.column.scroller',
33 + {style: {'overflow':'auto'}},
34 + h('div.scroller__wrapper',
35 + api.message_compose({type: 'post'}, {placeholder: 'Write a public message'}),
36 + content
37 + )
38 + )
39 +
40 + pull(
41 + u.next(api.sbot_log, {old: false, limit: 100}),
42 + Scroller(div, content, api.message_render, true, false)
43 + )
44 +
45 + pull(
46 + u.next(api.sbot_log, {reverse: true, limit: 100, live: false}),
47 + Scroller(div, content, api.message_render, false, false)
48 + )
49 +
50 + return div
51 + }
52 + }
3853 }
3954 }
40-
modules_basic/relationships.jsView
@@ -1,8 +1,8 @@
11 var pull = require('pull-stream')
2-var plugs = require('../plugs')
2 +//var plugs = require('../plugs')
33
4-var sbot_query = plugs.first(exports.sbot_query = [])
4 +//var sbot_query = plugs.first(exports.sbot_query = [])
55
66 //this is a bit crude, and doesn't actually show unfollows yet.
77
88 function makeQuery (a, b) {
@@ -18,33 +18,45 @@
1818 }}
1919 }
2020
2121
22-exports.follows = function (id, cb) {
23- return sbot_query({query: [
24- makeQuery(id, {$prefix:"@"}),
25- {"$map": ['value', 'content', 'contact']}
26- ]})
27-}
22 +exports.needs = { sbot_query: 'first' }
2823
29-exports.followers = function (id) {
30- return sbot_query({query: [
31- makeQuery({$prefix:"@"}, id),
32- {"$map": ['value', 'author']}
33- ]})
24 +exports.gives = {
25 + follows: true,
26 + followers: true,
27 + follower_of: true
3428 }
3529
36-exports.follower_of = function (source, dest, cb) {
37- pull(
38- sbot_query({query: [
39- makeQuery(source, dest),
40- {$map: ['value', 'content', 'following']}
41- ]}),
42- pull.collect(function (err, ary) {
43- if(err) return cb(err)
44- else cb(null, ary.pop()) //will be true, or undefined/false
45- })
46- )
47-}
30 +exports.create = function (api) {
4831
32 + return {
33 + follows: function (id, cb) {
34 + return api.sbot_query({query: [
35 + makeQuery(id, {$prefix:"@"}),
36 + {"$map": ['value', 'content', 'contact']}
37 + ]})
38 + },
4939
40 + followers: function (id) {
41 + return api.sbot_query({query: [
42 + makeQuery({$prefix:"@"}, id),
43 + {"$map": ['value', 'author']}
44 + ]})
45 + },
5046
47 + follower_of: function (source, dest, cb) {
48 + pull(
49 + api.sbot_query({query: [
50 + makeQuery(source, dest),
51 + {$map: ['value', 'content', 'following']}
52 + ]}),
53 + pull.collect(function (err, ary) {
54 + if(err) return cb(err)
55 + else cb(null, ary.pop()) //will be true, or undefined/false
56 + })
57 + )
58 + }
59 + }
60 +
61 +}
62 +
modules_basic/search-box.jsView
@@ -1,55 +1,78 @@
1 +'use strict'
2 +var cont = require('cont')
13 var h = require('hyperscript')
24 var suggest = require('suggest-box')
35 var pull = require('pull-stream')
4-var plugs = require('../plugs')
5-var sbot_query = plugs.first(exports.sbot_query = [])
6-var sbot_links2 = plugs.first(exports.sbot_links2 = [])
7-var suggest_search = plugs.asyncConcat(exports.suggest_search = [])
86
7 +//var plugs = require('../plugs')
8 +//var sbot_query = plugs.first(exports.sbot_query = [])
9 +//var sbot_links2 = plugs.first(exports.sbot_links2 = [])
10 +//var suggest_search = plugs.asyncConcat(exports.suggest_search = [])
11 +
12 +exports.needs = {
13 + sbot_query: 'first', sbot_links2: 'first',
14 + suggest_search: 'map' //REWRITE
15 +}
16 +
917 var channels = []
1018
19 +exports.gives = 'search_box'
1120
12-exports.search_box = function (go) {
21 +exports.create = function (api) {
1322
14- var suggestBox
15- var search = h('input.searchprompt', {
16- type: 'search',
17- placeholder: 'Commands',
18- onkeydown: function (ev) {
19- switch (ev.keyCode) {
20- case 13: // enter
21- if (suggestBox && suggestBox.active) {
22- suggestBox.complete()
23- ev.stopPropagation()
24- }
25- if (go(search.value.trim(), !ev.ctrlKey))
23 + return function (go) {
24 +
25 + var suggestBox
26 + var search = h('input.searchprompt', {
27 + type: 'search',
28 + placeholder: 'Commands',
29 + onkeydown: function (ev) {
30 + switch (ev.keyCode) {
31 + case 13: // enter
32 + if (suggestBox && suggestBox.active) {
33 + suggestBox.complete()
34 + ev.stopPropagation()
35 + }
36 + if (go(search.value.trim(), !ev.ctrlKey))
37 + search.blur()
38 + return
39 + case 27: // escape
40 + ev.preventDefault()
2641 search.blur()
27- return
28- case 27: // escape
29- ev.preventDefault()
30- search.blur()
31- return
42 + return
43 + }
3244 }
33- }
34- })
45 + })
3546
36- search.activate = function (sigil, ev) {
37- search.focus()
38- ev.preventDefault()
39- if (search.value[0] === sigil) {
40- search.selectionStart = 1
41- search.selectionEnd = search.value.length
42- } else {
43- search.value = sigil
47 + search.activate = function (sigil, ev) {
48 + search.focus()
49 + ev.preventDefault()
50 + if (search.value[0] === sigil) {
51 + search.selectionStart = 1
52 + search.selectionEnd = search.value.length
53 + } else {
54 + search.value = sigil
55 + }
4456 }
45- }
4657
47- var suggestions = {}
58 + var suggestions = {}
4859
49- // delay until the element has a parent
50- setTimeout(function () {
51- suggestBox = suggest(search, suggest_search, {})
52- }, 10)
60 + // delay until the element has a parent
61 + setTimeout(function () {
62 + suggestBox = suggest(search, function (word, cb) {
63 + cont.para(api.suggest_search(word))
64 + (function (err, ary) {
65 + if(err) return cb(err)
5366
54- return search
67 + cb(null, ary.filter(Boolean).reduce(function (a, b) {
68 + return a.concat(b)
69 + }, []))
70 + })
71 + }, {})
72 + }, 10)
73 +
74 + return search
75 + }
76 +
5577 }
78 +
modules_basic/setup.jsView
@@ -1,16 +1,25 @@
11
22 var h = require('hyperscript')
33 var pull = require('pull-stream')
44
5-var plugs = require('../plugs')
5 +//var plugs = require('../plugs')
6 +//
7 +//var avatar_edit = plugs.first(exports.avatar_edit = [])
8 +//var invite_parse = plugs.first(exports.invite_parse = [])
9 +//var invite_accept = plugs.first(exports.invite_accept = [])
10 +//var sbot_progress = plugs.first(exports.sbot_progress = [])
11 +//var sbot_query = plugs.first(exports.sbot_query = [])
12 +//var avatar = plugs.first(exports.avatar = [])
613
7-var avatar_edit = plugs.first(exports.avatar_edit = [])
8-var invite_parse = plugs.first(exports.invite_parse = [])
9-var invite_accept = plugs.first(exports.invite_accept = [])
10-var sbot_progress = plugs.first(exports.sbot_progress = [])
11-var sbot_query = plugs.first(exports.sbot_query = [])
12-var avatar = plugs.first(exports.avatar = [])
14 +exports.needs = {
15 + avatar: 'first',
16 + avatar_edit: 'first',
17 + invite_parse: 'first',
18 + invite_accept: 'first',
19 + sbot_progress: 'first',
20 + sbot_query: 'first'
21 +}
1322
1423 //maybe this could show the pubs, or
1524 //if someone locally follows you,
1625 //it could show the second degree pubs?
@@ -27,119 +36,125 @@
2736 }}
2837 }}]
2938 }
3039
31-//test whether we are connected to the ssb network.
32-exports.setup_is_fresh_install = function (cb) {
33- //test by checking whether you have any friends following you?
34- pull(
35- sbot_query({query: followers_query(id), limit: 1, live: false}),
36- pull.collect(function (err, ary) {
37- cb(err, !!ary.length)
38- })
39- )
40-}
40 +exports.create = function (api) {
4141
42-function invite_form () {
43- var accept = h('button', 'enter code', {disabled: true, onclick: function () {
44- invite_accept(input.value, function (msg) {
45- status.textContent = msg
46- }, function (err) {
47- if(err) {
48- accept.textContent = 'error:'+(err.message || err.stack || error.type)
49- console.error(err)
42 + var exports = {}
43 +
44 + //test whether we are connected to the ssb network.
45 + exports.setup_is_fresh_install = function (cb) {
46 + //test by checking whether you have any friends following you?
47 + pull(
48 + api.sbot_query({query: followers_query(id), limit: 1, live: false}),
49 + pull.collect(function (err, ary) {
50 + cb(err, !!ary.length)
51 + })
52 + )
53 + }
54 +
55 + function invite_form () {
56 + var accept = h('button', 'enter code', {disabled: true, onclick: function () {
57 + api.invite_accept(input.value, function (msg) {
58 + status.textContent = msg
59 + }, function (err) {
60 + if(err) {
61 + accept.textContent = 'error:'+(err.message || err.stack || error.type)
62 + console.error(err)
63 + }
64 + else {
65 + input.value = ''
66 + accept.textContent = 'success!'
67 + }
68 + })
69 + }})
70 +
71 + function parseInput () {
72 + if(!input.value) {
73 + accept.disabled = true
74 + accept.textContent = 'enter code'
5075 }
76 + else if(!invite_parse(input.value)) {
77 + accept.disabled = true
78 + accept.textContent = 'invalid code'
79 + }
5180 else {
52- input.value = ''
53- accept.textContent = 'success!'
81 + accept.disabled = false
82 + accept.textContent = 'accept'
5483 }
55- })
56- }})
84 + }
5785
58- function parseInput () {
59- if(!input.value) {
60- accept.disabled = true
61- accept.textContent = 'enter code'
62- }
63- else if(!invite_parse(input.value)) {
64- accept.disabled = true
65- accept.textContent = 'invalid code'
66- }
67- else {
68- accept.disabled = false
69- accept.textContent = 'accept'
70- }
86 + var input = h('input.wide', {placeholder: 'invite code', oninput: parseInput, onchange: parseInput})
87 +
88 + return h('div.invite-form.row', input, accept)
7189 }
7290
73- var input = h('input.wide', {placeholder: 'invite code', oninput: parseInput, onchange: parseInput})
91 + exports.progress_bar = function () {
92 + var liquid = h('div.hyperprogress__liquid', '.')
93 + var bar = h('div.hyperprogress__bar', liquid)
94 + liquid.style.width = '0%'
7495
75- return h('div.invite-form.row', input, accept)
76-}
96 + pull(
97 + api.sbot_progress(),
98 + pull.drain(function (e) {
99 + liquid.style.width = Math.round((e.progress/e.total)*100)+'%'
100 + })
101 + )
77102
78-exports.progress_bar = function () {
79- var liquid = h('div.hyperprogress__liquid', '.')
80- var bar = h('div.hyperprogress__bar', liquid)
81- liquid.style.width = '0%'
103 + return bar
104 + }
82105
83- pull(
84- sbot_progress(),
85- pull.drain(function (e) {
86- liquid.style.width = Math.round((e.progress/e.total)*100)+'%'
87- })
88- )
106 + //show the first 5 followers, and how they join you to the network.
107 + //so this will show if a local peer follows you.
89108
90- return bar
91-}
109 + //when you join the network, I want this to show as people follow you.
110 + //that could be when a pub accepts the invite, or when a local peer accepts.
92111
93-//show the first 5 followers, and how they join you to the network.
94-//so this will show if a local peer follows you.
112 + exports.setup_joined_network = function (id) {
113 + var followers = h('div.column')
114 + var label = h('label', 'not connected to a network')
115 + var joined = h('div.setup__joined', label, followers)
95116
96-//when you join the network, I want this to show as people follow you.
97-//that could be when a pub accepts the invite, or when a local peer accepts.
117 + pull(
118 + api.sbot_query({query: followers_query(id), limit: 5, live: true, sync: false}),
119 + pull.drain(function (follower) {
120 + if(follower.sync) return
121 + label.textContent = 'connected to network via...'
122 + followers.appendChild(
123 + api.avatar(follower.value.author, 'thumbnail')
124 + )
125 + })
126 + )
98127
99-exports.setup_joined_network = function (id) {
100- var followers = h('div.column')
101- var label = h('label', 'not connected to a network')
102- var joined = h('div.setup__joined', label,followers)
128 + return joined
129 + }
103130
104- pull(
105- sbot_query({query: followers_query(id), limit: 5, live: true, sync: false}),
106- pull.drain(function (follower) {
107- if(follower.sync) return
108- label.textContent = 'connected to network via...'
109- followers.appendChild(
110- avatar(follower.value.author, 'thumbnail')
111- )
112- })
113- )
131 + exports.screen_view = function (path) {
114132
115- return joined
116-}
133 + if(path !== '/setup') return
117134
118-exports.screen_view = function (path) {
135 + var id = require('../keys').id
119136
120- if(path !== '/setup') return
137 + //set up an avatar
121138
122- var id = require('../keys').id
123139
124- //set up an avatar
140 + var status = h('span')
141 + var invite = h('input', {placeholder: 'invite code'})
142 + return h('div.scroller', h('div.scroller__wrapper',
143 + h('h1', 'welcome to patchbay!'),
144 + h('div',
145 + 'please choose avatar image and name',
146 + api.avatar_edit(id)
147 + ),
148 + h('h2', 'join network'),
149 + invite_form(),
150 + //show avatars of anyone on the same local network.
151 + //show realtime changes in your followers, especially for local.
125152
153 + exports.progress_bar(),
154 + exports.setup_joined_network(require('../keys').id)
155 + ))
156 + }
126157
127- var status = h('span')
128- var invite = h('input', {placeholder: 'invite code'})
129- return h('div.scroller', h('div.scroller__wrapper',
130- h('h1', 'welcome to patchbay!'),
131- h('div',
132- 'please choose avatar image and name',
133- avatar_edit(id)
134- ),
135- h('h2', 'join network'),
136- invite_form(),
137- //show avatars of anyone on the same local network.
138- //show realtime changes in your followers, especially for local.
158 + return exports
139159
140- exports.progress_bar(),
141- exports.setup_joined_network(require('../keys').id)
142- ))
143160 }
144-
145-
modules_basic/suggest-mentions.jsView
@@ -2,64 +2,71 @@
22 function isImage (filename) {
33 return /\.(gif|jpg|png|svg)$/i.test(filename)
44 }
55
6-var sbot_links2 = require('../plugs').first(exports.sbot_links2 = [])
7-var blob_url = require('../plugs').first(exports.blob_url = [])
8-var signified = require('../plugs').first(exports.signified = [])
9-var builtin_tabs = require('../plugs').map(exports.builtin_tabs = [])
6 +//var sbot_links2 = require('../plugs').first(exports.sbot_links2 = [])
7 +//var blob_url = require('../plugs').first(exports.blob_url = [])
8 +//var signified = require('../plugs').first(exports.signified = [])
9 +//var builtin_tabs = require('../plugs').map(exports.builtin_tabs = [])
1010
11-exports.suggest_mentions = function (word, cb) {
12- if(!/^[%&@]\w/.test(word)) return cb()
13-
14-
15- signified(word, function (err, names) {
16- if(err) cb(err)
17- else cb(null, names.map(function (e) {
18- return {
19- title: e.name + ': ' + e.id.substring(0,10)+' ('+e.rank+')',
20- value: '['+e.name+']('+e.id+')',
21- rank: e.rank,
22- //TODO: avatar images...
23- }
24- }))
25- })
11 +exports.needs = {
12 + sbot_links2: 'first',
13 + blob_url: 'first',
14 + signified: 'first',
15 + builtin_tabs: 'map'
2616 }
2717
28-exports.suggest_search = function (query, cb) {
29- if(/^[@%]\w/.test(query)) {
30- signified(query, function (_, names) {
31- cb(null, names.map(function (e) {
32- return {
33- title: e.name + ':'+e.id.substring(0, 10),
34- value: e.id,
35- subtitle: e.rank,
36- rank: e.rank
37- }
38- }))
39- })
40-
41- } else if(/^\//.test(query)) {
42- var tabs = [].concat.apply([], builtin_tabs())
43- cb(null, tabs.filter(function (name) {
44- return name.substr(0, query.length) === query
45- }).map(function (name) {
46- return {
47- title: name,
48- value: name,
49- }
50- }))
51- } else cb()
18 +exports.gives = {
19 + suggest_mentions: true,
20 + suggest_search: true
5221 }
5322
23 +exports.create = function (api) {
5424
25 + return {
26 + suggest_mentions: function (word) {
27 + return function (cb) {
28 + if(!/^[%&@]\w/.test(word)) return cb()
5529
30 + api.signified(word, function (err, names) {
31 + if(err) cb(err)
32 + else cb(null, names.map(function (e) {
33 + return {
34 + title: e.name + ': ' + e.id.substring(0,10)+' ('+e.rank+')',
35 + value: '['+e.name+']('+e.id+')',
36 + rank: e.rank,
37 + //TODO: avatar images...
38 + }
39 + }))
40 + })
41 + }
42 + },
5643
44 + suggest_search: function (query) {
45 + return function (cb) {
46 + if(/^[@%]\w/.test(query)) {
47 + api.signified(query, function (_, names) {
48 + cb(null, names.map(function (e) {
49 + return {
50 + title: e.name + ':'+e.id.substring(0, 10),
51 + value: e.id,
52 + subtitle: e.rank,
53 + rank: e.rank
54 + }
55 + }))
56 + })
5757
58-
59-
60-
61-
62-
63-
64-
65-
58 + } else if(/^\//.test(query)) {
59 + var tabs = [].concat.apply([], api.builtin_tabs())
60 + cb(null, tabs.filter(function (name) {
61 + return name.substr(0, query.length) === query
62 + }).map(function (name) {
63 + return {
64 + title: name,
65 + value: name,
66 + }
67 + }))
68 + } else cb()
69 + }
70 + }
71 + }
72 +}
modules_basic/thread.jsView
@@ -20,106 +20,119 @@
2020 })
2121 }
2222 }
2323
24-var plugs = require('../plugs')
24 +//var plugs = require('../plugs')
25 +//
26 +//var message_render = plugs.first(exports.message_render = [])
27 +//var message_name = plugs.first(exports.message_name = [])
28 +//var message_compose = plugs.first(exports.message_compose = [])
29 +//var message_unbox = plugs.first(exports.message_unbox = [])
30 +//
31 +//var sbot_get = plugs.first(exports.sbot_get = [])
32 +//var sbot_links = plugs.first(exports.sbot_links = [])
2533
26-var message_render = plugs.first(exports.message_render = [])
27-var message_name = plugs.first(exports.message_name = [])
28-var message_compose = plugs.first(exports.message_compose = [])
29-var message_unbox = plugs.first(exports.message_unbox = [])
34 +exports.needs = {
35 + message_render: 'first',
36 + message_name: 'first',
37 + message_compose: 'first',
38 + message_unbox: 'first',
39 + sbot_get: 'first',
40 + sbot_links: 'first'
41 +}
3042
31-var sbot_get = plugs.first(exports.sbot_get = [])
32-var sbot_links = plugs.first(exports.sbot_links = [])
43 +exports.gives = 'screen_view'
3344
34-function getThread (root, cb) {
35- //in this case, it's inconvienent that panel only takes
36- //a stream. maybe it would be better to accept an array?
3745
38- sbot_get(root, function (err, value) {
39- if (err) return cb(err)
40- var msg = {key: root, value: value}
41-// if(value.content.root) return getThread(value.content.root, cb)
46 +exports.create = function (api) {
4247
43- pull(
44- sbot_links({rel: 'root', dest: root, values: true, keys: true}),
45- pull.collect(function (err, ary) {
46- if(err) return cb(err)
47- ary.unshift(msg)
48- cb(null, ary)
49- })
50- )
51- })
48 + function getThread (root, cb) {
49 + //in this case, it's inconvienent that panel only takes
50 + //a stream. maybe it would be better to accept an array?
5251
53-}
52 + api.sbot_get(root, function (err, value) {
53 + if (err) return cb(err)
54 + var msg = {key: root, value: value}
55 + // if(value.content.root) return getThread(value.content.root, cb)
5456
55-exports.screen_view = function (id) {
56- if(ref.isMsg(id)) {
57- var meta = {
58- type: 'post',
59- root: id,
60- branch: id //mutated when thread is loaded.
61- }
57 + pull(
58 + api.sbot_links({rel: 'root', dest: root, values: true, keys: true}),
59 + pull.collect(function (err, ary) {
60 + if(err) return cb(err)
61 + ary.unshift(msg)
62 + cb(null, ary)
63 + })
64 + )
65 + })
6266
63- var content = h('div.column.scroller__content')
64- var div = h('div.column.scroller',
65- {style: {'overflow-y': 'auto'}},
66- h('div.scroller__wrapper',
67- content,
68- message_compose(meta, {shrink: false, placeholder: 'Write a reply'})
67 + }
68 +
69 + return function (id) {
70 + if(ref.isMsg(id)) {
71 + var meta = {
72 + type: 'post',
73 + root: id,
74 + branch: id //mutated when thread is loaded.
75 + }
76 +
77 + var content = h('div.column.scroller__content')
78 + var div = h('div.column.scroller',
79 + {style: {'overflow-y': 'auto'}},
80 + h('div.scroller__wrapper',
81 + content,
82 + api.message_compose(meta, {shrink: false, placeholder: 'Write a reply'})
83 + )
6984 )
70- )
7185
72- message_name(id, function (err, name) {
73- div.title = name
74- })
86 + api.message_name(id, function (err, name) {
87 + div.title = name
88 + })
7589
76- pull(
77- sbot_links({
78- rel: 'root', dest: id, keys: true, old: false
79- }),
80- pull.drain(function (msg) {
81- loadThread() //redraw thread
82- }, function () {} )
83- )
90 + pull(
91 + api.sbot_links({
92 + rel: 'root', dest: id, keys: true, old: false
93 + }),
94 + pull.drain(function (msg) {
95 + loadThread() //redraw thread
96 + }, function () {} )
97 + )
8498
8599
86- function loadThread () {
87- getThread(id, function (err, thread) {
88- //would probably be better keep an id for each message element
89- //(i.e. message key) and then update it if necessary.
90- //also, it may have moved (say, if you received a missing message)
91- content.innerHTML = ''
92- if(err) return content.appendChild(h('pre', err.stack))
100 + function loadThread () {
101 + getThread(id, function (err, thread) {
102 + //would probably be better keep an id for each message element
103 + //(i.e. message key) and then update it if necessary.
104 + //also, it may have moved (say, if you received a missing message)
105 + content.innerHTML = ''
106 + if(err) return content.appendChild(h('pre', err.stack))
93107
94- //decrypt
95- thread = thread.map(function (msg) {
96- return 'string' === typeof msg.value.content ? message_unbox(msg) : msg
97- })
108 + //decrypt
109 + thread = thread.map(function (msg) {
110 + return 'string' === typeof msg.value.content ? api.message_unbox(msg) : msg
111 + })
98112
99- if(err) return content.appendChild(h('pre', err.stack))
100- sort(thread).map(message_render).filter(Boolean).forEach(function (el) {
101- content.appendChild(el)
113 + if(err) return content.appendChild(h('pre', err.stack))
114 + sort(thread).map(api.message_render).filter(Boolean).forEach(function (el) {
115 + content.appendChild(el)
116 + })
117 +
118 + var branches = sort.heads(thread)
119 + meta.branch = branches.length > 1 ? branches : branches[0]
120 + meta.root = thread[0].value.content.root || thread[0].key
121 + meta.channel = thread[0].value.content.channel
122 +
123 + var recps = thread[0].value.content.recps
124 + var private = thread[0].value.private
125 + if(private) {
126 + if(recps)
127 + meta.recps = recps
128 + else
129 + meta.recps = [thread[0].value.author, self_id]
130 + }
102131 })
132 + }
103133
104- var branches = sort.heads(thread)
105- meta.branch = branches.length > 1 ? branches : branches[0]
106- meta.root = thread[0].value.content.root || thread[0].key
107- meta.channel = thread[0].value.content.channel
108-
109- var recps = thread[0].value.content.recps
110- var private = thread[0].value.private
111- if(private) {
112- if(recps)
113- meta.recps = recps
114- else
115- meta.recps = [thread[0].value.author, self_id]
116- }
117- })
134 + loadThread()
135 + return div
118136 }
119-
120- loadThread()
121- return div
122137 }
123138 }
124-
125-
modules_basic/timestamp.jsView
@@ -1,21 +1,28 @@
11 var h = require('hyperscript')
22 var human = require('human-time')
33
4-function updateTimestampEl(el) {
5- el.firstChild.nodeValue = human(new Date(el.timestamp))
6- return el
7-}
4 +exports.needs = {}
85
9-setInterval(function () {
10- var els = [].slice.call(document.querySelectorAll('.timestamp'))
11- els.forEach(updateTimestampEl)
12-}, 60e3)
6 +exports.gives = 'message_meta'
137
14-exports.message_meta = function (msg) {
15- return updateTimestampEl(h('a.enter.timestamp', {
16- href: '#'+msg.key,
17- timestamp: msg.value.timestamp,
18- title: new Date(msg.value.timestamp)
19- }, ''))
8 +exports.create = function () {
9 +
10 + function updateTimestampEl(el) {
11 + el.firstChild.nodeValue = human(new Date(el.timestamp))
12 + return el
13 + }
14 +
15 + setInterval(function () {
16 + var els = [].slice.call(document.querySelectorAll('.timestamp'))
17 + els.forEach(updateTimestampEl)
18 + }, 60e3)
19 +
20 + return function (msg) {
21 + return updateTimestampEl(h('a.enter.timestamp', {
22 + href: '#'+msg.key,
23 + timestamp: msg.value.timestamp,
24 + title: new Date(msg.value.timestamp)
25 + }, ''))
26 + }
27 +
2028 }
21-
modules_basic/suggest.jsView
@@ -1,3 +1,0 @@
1-
2-
3-
modules_core/app.jsView
@@ -1,68 +1,42 @@
11 var plugs = require('../plugs')
22 var h = require('hyperscript')
33
4-var screen_view = plugs.first(exports.screen_view = [])
4 +module.exports = {
5 + needs: {screen_view: 'first'},
6 + gives: 'app',
7 + create: function (api) {
8 + return function () {
9 + document.head.appendChild(h('style', require('../style.css.json')))
510
11 + window.addEventListener('error', window.onError = function (e) {
12 + document.body.appendChild(h('div.error',
13 + h('h1', e.message),
14 + h('big', h('code', e.filename + ':' + e.lineno)),
15 + h('pre', e.error ? (e.error.stack || e.error.toString()) : e.toString())))
16 + })
617
7-exports.app = function () {
8- document.head.appendChild(h('style', require('../style.css.json')))
18 + function hash() {
19 + return window.location.hash.substring(1)
20 + }
921
10- window.addEventListener('error', window.onError = function (e) {
11- document.body.appendChild(h('div.error',
12- h('h1', e.message),
13- h('big', h('code', e.filename + ':' + e.lineno)),
14- h('pre', e.error ? (e.error.stack || e.error.toString()) : e.toString())))
15- })
22 + console.log(hash() || 'tabs')
23 + var view = api.screen_view(hash() || 'tabs')
1624
17- function hash() {
18- return window.location.hash.substring(1)
19- }
25 + var screen = h('div.screen.column', view)
2026
21- var view = screen_view(hash() || 'tabs')
27 + window.onhashchange = function (ev) {
28 + var _view = view
29 + view = api.screen_view(hash() || 'tabs')
2230
23- var screen = h('div.screen.column', view)
31 + if(_view) screen.replaceChild(view, _view)
32 + else document.body.appendChild(view)
33 + }
2434
25- window.onhashchange = function (ev) {
26- var _view = view
27- view = screen_view(hash() || 'tabs')
35 + document.body.appendChild(screen)
2836
29- if(_view) screen.replaceChild(view, _view)
30- else document.body.appendChild(view)
37 + return screen
38 + }
3139 }
32-
33- document.body.appendChild(screen)
34-
35- return screen
36-
3740 }
3841
3942
40-
41-
42-
43-
44-
45-
46-
47-
48-
49-
50-
51-
52-
53-
54-
55-
56-
57-
58-
59-
60-
61-
62-
63-
64-
65-
66-
67-
68-
modules_core/blob-url.jsView
@@ -1,9 +1,13 @@
11 var config = require('../config')
22
3-exports.blob_url = function (link) {
4- if('string' == typeof link.link)
5- link = link.link
6- return config().blobsUrl + '/'+link
3 +module.exports = {
4 + gives: 'blob_url',
5 + create: function () {
6 + return function (link) {
7 + if('string' == typeof link.link)
8 + link = link.link
9 + return config().blobsUrl + '/'+link
10 + }
11 + }
712 }
813
9-
modules_core/crypto.jsView
@@ -16,33 +16,45 @@
1616 }
1717 }
1818
1919
20-var sbot_publish = require('../plugs').first(exports.sbot_publish = [])
20 +module.exports = {
2121
22-exports.message_unbox = function (msg) {
23- if(msg.value) {
24- var value = unbox_value(msg.value)
25- if(value)
26- return {
27- key: msg.key, value: value, timestamp: msg.timestamp
22 + needs: {sbot_publish: 'first'},
23 + gives: {
24 + message_unbox: true, message_box: true, publish: true
25 + },
26 + create: function (api) {
27 +
28 + var exports = {}
29 + exports.message_unbox = function (msg) {
30 + if(msg.value) {
31 + var value = unbox_value(msg.value)
32 + if(value)
33 + return {
34 + key: msg.key, value: value, timestamp: msg.timestamp
35 + }
36 + }
37 + else
38 + return unbox_value(msg)
2839 }
29- }
30- else
31- return unbox_value(msg)
32-}
3340
34-exports.message_box = function (content) {
35- return ssbKeys.box(content, content.recps.map(function (e) {
36- return ref.isFeed(e) ? e : e.link
37- }))
38-}
41 + exports.message_box = function (content) {
42 + return ssbKeys.box(content, content.recps.map(function (e) {
43 + return ref.isFeed(e) ? e : e.link
44 + }))
45 + }
3946
40-exports.publish = function (content, id) {
41- if(content.recps)
42- content = exports.message_box(content)
43- sbot_publish(content, function (err, msg) {
44- if(err) throw err
45- console.log('PUBLISHED', msg)
46- })
47 + exports.publish = function (content, cb) {
48 + if(content.recps)
49 + content = exports.message_box(content)
50 + api.sbot_publish(content, function (err, msg) {
51 + if(err) throw err
52 + console.log('PUBLISHED', msg)
53 + if(cb) cb(err, msg)
54 + })
55 + }
56 +
57 + return exports
58 + }
4759 }
4860
modules_core/file-input.jsView
@@ -7,31 +7,36 @@
77 var plugs = require('../plugs')
88
99 var add = plugs.first(exports.sbot_blobs_add = [])
1010
11-exports.file_input = function FileInput(onAdded) {
11 +module.exports = {
12 + needs: {sbot_blobs_add: 'first'},
13 + gives: 'file_input',
14 + create: function () {
15 + return function FileInput(onAdded) {
16 + return h('input', { type: 'file',
17 + onchange: function (ev) {
18 + var file = ev.target.files[0]
19 + if (!file) return
20 + var reader = new FileReader()
21 + reader.onload = function () {
22 + pull(
23 + pull.values(split(new Buffer(reader.result), 64*1024)),
24 + add(function (err, blob) {
25 + if(err) return console.error(err)
26 + onAdded({
27 + link: blob,
28 + name: file.name,
29 + size: reader.result.length || reader.result.byteLength,
30 + type: mime(file.name)
31 + })
1232
13- return h('input', { type: 'file',
14- onchange: function (ev) {
15- var file = ev.target.files[0]
16- if (!file) return
17- var reader = new FileReader()
18- reader.onload = function () {
19- pull(
20- pull.values(split(new Buffer(reader.result), 64*1024)),
21- add(function (err, blob) {
22- if(err) return console.error(err)
23- onAdded({
24- link: blob,
25- name: file.name,
26- size: reader.result.length || reader.result.byteLength,
27- type: mime(file.name)
28- })
29-
30- })
31- )
32- }
33- reader.readAsArrayBuffer(file)
33 + })
34 + )
35 + }
36 + reader.readAsArrayBuffer(file)
37 + }
38 + })
3439 }
35- })
40 + }
3641 }
3742
modules_core/index.jsView
@@ -1,6 +1,6 @@
11 module.exports = {
2- "_screen_view.js": require('./_screen_view.js'),
2 +// "_screen_view.js": require('./_screen_view.js'),
33 "app.js": require('./app.js'),
44 "blob-url.js": require('./blob-url.js'),
55 "crypto.js": require('./crypto.js'),
66 "file-input.js": require('./file-input.js'),
@@ -8,4 +8,5 @@
88 "message-confirm.js": require('./message-confirm.js'),
99 "tabs.js": require('./tabs.js'),
1010 "sbot.js": require('./sbot.js')
1111 }
12 +
modules_core/menu.jsView
@@ -1,31 +1,45 @@
11 var plugs = require('../plugs')
22 var h = require('hyperscript')
33
4-var menu_items = plugs.map(exports.menu_items = [])
4 +module.exports = {
5 + needs: {menu_items: 'map'},
6 + gives: {connection_status: true, menu: true},
7 + create: function (api) {
58
6-var status = h('div.status.error') //start off disconnected
7- var list = h('div.menu.column', {style: 'display: none;'})
9 + var menu_items = api.menu_items //plugs.map(exports.menu_items = [])
810
9-var menu = h('div.column', status, list , {
10- onmouseover: function (e) {
11- list.style.display = 'flex'
12- }, onmouseout: function () {
13- list.style.display = 'none'
14- }
15-})
11 + var status = h('div.status.error') //start off disconnected
12 + var list = h('div.menu.column', {style: 'display: none;'})
1613
17-exports.connection_status = function (err) {
18- if(err) status.classList.add('error')
19- else status.classList.remove('error')
20-}
14 + var menu = h('div.column', status, list , {
15 + onmouseover: function (e) {
16 + list.style.display = 'flex'
17 + }, onmouseout: function () {
18 + list.style.display = 'none'
19 + }
20 + })
2121
22-exports.menu = function () {
23- menu_items().forEach(function (el) {
24- list.appendChild(el)
25- })
22 + setTimeout(function () {
23 + menu_items().forEach(function (el) {
24 + if(el)
25 + list.appendChild(el)
26 + })
27 + }, 0)
2628
27- return menu
29 + return {
30 + connection_status: function (err) {
31 + if(err) status.classList.add('error')
32 + else status.classList.remove('error')
33 + },
34 + menu: function () {
35 + return menu
36 + }
37 + }
38 + }
2839 }
2940
3041
3142
43 +
44 +
45 +
modules_core/message-confirm.jsView
@@ -5,57 +5,65 @@
55 //publish or add
66
77 var plugs = require('../plugs')
88
9-var publish = plugs.first(exports.sbot_publish = [])
10-var message_content = plugs.first(exports.message_content = [])
11-var avatar = plugs.first(exports.avatar = [])
12-var message_meta = plugs.map(exports.message_meta = [])
9 +exports.needs = {
10 + publish: 'first', message_content: 'first', avatar: 'first',
11 + message_meta: 'map'
12 +}
1313
14-exports.message_confirm = function (content, cb) {
14 +exports.gives = 'message_confirm'
1515
16- cb = cb || function () {}
16 +//var publish = plugs.first(exports.sbot_publish = [])
17 +//var message_content = plugs.first(exports.message_content = [])
18 +//var avatar = plugs.first(exports.avatar = [])
19 +//var message_meta = plugs.map(exports.message_meta = [])
20 +//
21 +exports.create = function (api) {
22 + return function (content, cb) {
1723
18- var lb = lightbox()
19- document.body.appendChild(lb)
24 + cb = cb || function () {}
2025
21- var msg = {
22- key: "DRAFT",
23- value: {
24- author: self_id,
25- previous: null,
26- sequence: null,
27- timestamp: Date.now(),
28- content: content
26 + var lb = lightbox()
27 + document.body.appendChild(lb)
28 +
29 + var msg = {
30 + key: "DRAFT",
31 + value: {
32 + author: self_id,
33 + previous: null,
34 + sequence: null,
35 + timestamp: Date.now(),
36 + content: content
37 + }
2938 }
30- }
3139
32- var okay = h('button', 'okay', {onclick: function () {
33- lb.remove()
34- publish(content, cb)
35- }})
40 + var okay = h('button', 'okay', {onclick: function () {
41 + lb.remove()
42 + api.publish(content, cb)
43 + }})
3644
37- var cancel = h('button', 'Cancel', {onclick: function () {
38- lb.remove()
39- cb(null)
40- }})
45 + var cancel = h('button', 'Cancel', {onclick: function () {
46 + lb.remove()
47 + cb(null)
48 + }})
4149
42- okay.addEventListener('keydown', function (ev) {
43- if(ev.keyCode === 27) cancel.click() //escape
44- })
50 + okay.addEventListener('keydown', function (ev) {
51 + if(ev.keyCode === 27) cancel.click() //escape
52 + })
4553
46- lb.show(h('div.column.message-confirm',
47- h('div.message',
48- h('div.title.row',
49- h('div.avatar', avatar(msg.value.author, 'thumbnail')),
50- h('div.message_meta.row', message_meta(msg))
54 + lb.show(h('div.column.message-confirm',
55 + h('div.message',
56 + h('div.title.row',
57 + h('div.avatar', api.avatar(msg.value.author, 'thumbnail')),
58 + h('div.message_meta.row', api.message_meta(msg))
59 + ),
60 + h('div.message_content', api.message_content(msg)
61 + || h('pre', JSON.stringify(msg, null, 2)))
5162 ),
52- h('div.message_content', message_content(msg)
53- || h('pre', JSON.stringify(msg, null, 2)))
54- ),
55- h('div.row.message-confirm__controls', okay, cancel)
56- ))
63 + h('div.row.message-confirm__controls', okay, cancel)
64 + ))
5765
58- okay.focus()
59-
66 + okay.focus()
67 + }
6068 }
6169
modules_core/sbot.jsView
@@ -30,128 +30,160 @@
3030 var createFeed = require('ssb-feed')
3131 var keys = require('../keys')
3232 var ssbKeys = require('ssb-keys')
3333
34-
3534 var cache = CACHE = {}
3635
37-var opts = createConfig()
38-var sbot = null
39-var connection_status = []
36 +module.exports = {
37 + needs: {
38 + connection_status: 'map'
39 + },
40 + gives: {
41 +// connection_status: true,
42 + sbot_blobs_add: true,
43 + sbot_links: true,
44 + sbot_links2: true,
45 + sbot_query: true,
46 + sbot_get: true,
47 + sbot_log: true,
48 + sbot_user_feed: true,
49 + sbot_gossip_peers: true,
50 + sbot_gossip_connect: true,
51 + sbot_progress: true,
52 + sbot_publish: true,
53 + sbot_whoami: true
54 + },
4055
41-var rec = Reconnect(function (isConn) {
42- function notify (value) {
43- console.log('connection_status', value, connection_status)
44- isConn(value); connection_status.forEach(function (fn) { fn(value) })
45- }
56 +//module.exports = {
57 + create: function (api) {
4658
47- createClient(keys, {
48- manifest: require('../manifest.json'),
49- remote: require('../config')().remote
50- }, function (err, _sbot) {
51- if(err)
52- return notify(err)
59 + var opts = createConfig()
60 + var sbot = null
61 + var connection_status = []
5362
54- sbot = _sbot
55- sbot.on('closed', function () {
56- sbot = null
57- notify(new Error('closed'))
58- })
63 + var rec = Reconnect(function (isConn) {
64 + function notify (value) {
65 + isConn(value); api.connection_status(value) //.forEach(function (fn) { fn(value) })
66 + }
5967
60- notify()
61- })
62-})
68 + createClient(keys, {
69 + manifest: require('../manifest.json'),
70 + remote: require('../config')().remote
71 + }, function (err, _sbot) {
72 + if(err)
73 + return notify(err)
6374
64-var internal = {
65- getLatest: rec.async(function (id, cb) {
66- sbot.getLatest(id, cb)
67- }),
68- add: rec.async(function (msg, cb) {
69- sbot.add(msg, cb)
70- })
71-}
75 + sbot = _sbot
76 + sbot.on('closed', function () {
77 + sbot = null
78 + notify(new Error('closed'))
79 + })
7280
73-var feed = createFeed(internal, keys, {remote: true})
81 + notify()
82 + })
83 + })
7484
75-module.exports = {
76- connection_status: connection_status,
77- sbot_blobs_add: rec.sink(function (cb) {
78- return pull(
79- Hash(function (err, id) {
80- if(err) return cb(err)
81- //completely UGLY hack to tell when the blob has been sucessfully written...
82- var start = Date.now(), n = 5
83- ;(function next () {
84- setTimeout(function () {
85- sbot.blobs.has(id, function (err, has) {
86- if(has) return cb(null, id)
87- if(n--) next()
88- else cb(new Error('write failed'))
89- })
90- }, Date.now() - start)
91- })()
85 + var internal = {
86 + getLatest: rec.async(function (id, cb) {
87 + sbot.getLatest(id, cb)
9288 }),
93- sbot.blobs.add()
94- )
95- }),
96- sbot_links: rec.source(function (query) {
97- return sbot.links(query)
98- }),
99- sbot_links2: rec.source(function (query) {
100- return sbot.links2.read(query)
101- }),
102- sbot_query: rec.source(function (query) {
103- return sbot.query.read(query)
104- }),
105- sbot_log: rec.source(function (opts) {
106- return pull(
107- sbot.createLogStream(opts),
108- pull.through(function (e) {
109- CACHE[e.key] = CACHE[e.key] || e.value
89 + add: rec.async(function (msg, cb) {
90 + sbot.add(msg, cb)
11091 })
111- )
112- }),
113- sbot_user_feed: rec.source(function (opts) {
114- return sbot.createUserStream(opts)
115- }),
116- sbot_get: rec.async(function (key, cb) {
117- if(CACHE[key]) cb(null, CACHE[key])
118- else sbot.get(key, function (err, value) {
119- if(err) return cb(err)
120- cb(null, CACHE[key] = value)
121- })
122- }),
123- sbot_gossip_peers: rec.async(function (cb) {
124- sbot.gossip.peers(cb)
125- }),
126- //liteclient won't have permissions for this
127- sbot_gossip_connect: rec.async(function (opts, cb) {
128- sbot.gossip.connect(opts, cb)
129- }),
130- sbot_progress: rec.source(function () {
131- return sbot.replicate.changes()
132- }),
133- sbot_publish: rec.async(function (content, cb) {
134- if(content.recps)
135- content = ssbKeys.box(content, content.recps.map(function (e) {
136- return ref.isFeed(e) ? e : e.link
137- }))
138- else if(content.mentions)
139- content.mentions.forEach(function (mention) {
140- if(ref.isBlob(mention.link)) {
141- sbot.blobs.push(mention.link, function (err) {
142- if(err) console.error(err)
92 + }
93 +
94 + var feed = createFeed(internal, keys, {remote: true})
95 +
96 + return {
97 + connection_status: connection_status,
98 + sbot_blobs_add: rec.sink(function (cb) {
99 + return pull(
100 + Hash(function (err, id) {
101 + if(err) return cb(err)
102 + //completely UGLY hack to tell when the blob has been sucessfully written...
103 + var start = Date.now(), n = 5
104 + ;(function next () {
105 + setTimeout(function () {
106 + sbot.blobs.has(id, function (err, has) {
107 + if(has) return cb(null, id)
108 + if(n--) next()
109 + else cb(new Error('write failed'))
110 + })
111 + }, Date.now() - start)
112 + })()
113 + }),
114 + sbot.blobs.add()
115 + )
116 + }),
117 + sbot_links: rec.source(function (query) {
118 + return sbot.links(query)
119 + }),
120 + sbot_links2: rec.source(function (query) {
121 + return sbot.links2.read(query)
122 + }),
123 + sbot_query: rec.source(function (query) {
124 + return sbot.query.read(query)
125 + }),
126 + sbot_log: rec.source(function (opts) {
127 + return pull(
128 + sbot.createLogStream(opts),
129 + pull.through(function (e) {
130 + CACHE[e.key] = CACHE[e.key] || e.value
143131 })
144- }
132 + )
133 + }),
134 + sbot_user_feed: rec.source(function (opts) {
135 + return sbot.createUserStream(opts)
136 + }),
137 + sbot_get: rec.async(function (key, cb) {
138 + if('function' !== typeof cb)
139 + throw new Error('cb must be function')
140 + if(CACHE[key]) cb(null, CACHE[key])
141 + else sbot.get(key, function (err, value) {
142 + if(err) return cb(err)
143 + cb(null, CACHE[key] = value)
144 + })
145 + }),
146 + sbot_gossip_peers: rec.async(function (cb) {
147 + sbot.gossip.peers(cb)
148 + }),
149 + //liteclient won't have permissions for this
150 + sbot_gossip_connect: rec.async(function (opts, cb) {
151 + sbot.gossip.connect(opts, cb)
152 + }),
153 + sbot_progress: rec.source(function () {
154 + return sbot.replicate.changes()
155 + }),
156 + sbot_publish: rec.async(function (content, cb) {
157 + if(content.recps)
158 + content = ssbKeys.box(content, content.recps.map(function (e) {
159 + return ref.isFeed(e) ? e : e.link
160 + }))
161 + else if(content.mentions)
162 + content.mentions.forEach(function (mention) {
163 + if(ref.isBlob(mention.link)) {
164 + sbot.blobs.push(mention.link, function (err) {
165 + if(err) console.error(err)
166 + })
167 + }
168 + })
169 +
170 + feed.add(content, function (err, msg) {
171 + if(err) console.error(err)
172 + else if(!cb) console.log(msg)
173 + cb && cb(err, msg)
174 + })
175 + }),
176 + sbot_whoami: rec.async(function (cb) {
177 + sbot.whoami(cb)
145178 })
146-
147- feed.add(content, function (err, msg) {
148- if(err) console.error(err)
149- else if(!cb) console.log(msg)
150- cb && cb(err, msg)
151- })
152- }),
153- sbot_whoami: rec.async(function (cb) {
154- sbot.whoami(cb)
155- })
179 + }
180 + }
156181 }
157182
183 +
184 +
185 +
186 +
187 +
188 +
189 +
modules_core/tabs.jsView
@@ -10,214 +10,210 @@
1010 if(el.tagName !== 'A') return ancestor(el.parentElement)
1111 return el
1212 }
1313
14-var plugs = require('../plugs')
15-var screen_view = plugs.first(exports._screen_view = [])
16-var search_box = plugs.first(exports.search_box = [])
17-var menu = plugs.first(exports.menu = [])
14 +//var plugs = require('../plugs')
15 +//var screen_view = plugs.first(exports._screen_view = [])
16 +//var search_box = plugs.first(exports.search_box = [])
17 +//var menu = plugs.first(exports.menu = [])
1818
19-exports.message_render = []
19 +exports.needs = {screen_view: 'first', search_box: 'first', menu: 'first'}
2020
21-exports.screen_view = function (path) {
22- if(path !== 'tabs')
23- return
21 +exports.gives = 'screen_view'
2422
25- function setSelected (indexes) {
26- var ids = indexes.map(function (index) {
27- return tabs.get(index).id
28- })
29- if(ids.length > 1)
30- search.value = 'split('+ids.join(',')+')'
31- else
32- search.value = ids[0]
33- }
23 +exports.create = function (api) {
24 + return function (path) {
25 + if(path !== 'tabs')
26 + return
3427
35- var search
36- var tabs = Tabs(setSelected)
37-
38- search = search_box(function (path, change) {
39-
40- if(tabs.has(path)) {
41- tabs.select(path)
42- return true
28 + function setSelected (indexes) {
29 + var ids = indexes.map(function (index) {
30 + return tabs.get(index).id
31 + })
32 + if(search)
33 + if(ids.length > 1)
34 + search.value = 'split('+ids.join(',')+')'
35 + else
36 + search.value = ids[0]
4337 }
44- var el = screen_view(path)
4538
46- if(el) {
47- if(!el.title) el.title = path
48- el.scroll = keyscroll(el.querySelector('.scroller__content'))
49- tabs.add(el, change)
50-// localStorage.openTabs = JSON.stringify(tabs.tabs)
51- return change
52- }
53- })
39 + var search
40 + var tabs = Tabs(setSelected)
5441
55- //reposition hypertabs menu to inside a container...
56- tabs.insertBefore(h('div.header.row',
57- h('div.header__tabs.row', tabs.firstChild), //tabs
58- h('div.header__search.row.end', h('div', search), menu())
59- ), tabs.firstChild)
60-// tabs.insertBefore(search, tabs.firstChild.nextSibling)
42 + search = api.search_box(function (path, change) {
6143
62- var saved = []
63-// try { saved = JSON.parse(localStorage.openTabs) }
64-// catch (_) { }
44 + if(tabs.has(path)) {
45 + tabs.select(path)
46 + return true
47 + }
48 + var el = api.screen_view(path)
6549
66- if(!saved || saved.length < 3)
67- saved = ['/public', '/private', '/notifications']
50 + if(el) {
51 + if(!el.title) el.title = path
52 + el.scroll = keyscroll(el.querySelector('.scroller__content'))
53 + tabs.add(el, change)
54 + // localStorage.openTabs = JSON.stringify(tabs.tabs)
55 + return change
56 + }
57 + })
6858
69- saved.forEach(function (path) {
70- var el = screen_view(path)
71- if(!el) return
72- el.id = el.id || path
73- if (!el) return
74- el.scroll = keyscroll(el.querySelector('.scroller__content'))
75- if(el) tabs.add(el, false, false)
76- })
59 + //reposition hypertabs menu to inside a container...
60 + tabs.insertBefore(h('div.header.row',
61 + h('div.header__tabs.row', tabs.firstChild), //tabs
62 + h('div.header__search.row.end', h('div', search), api.menu())
63 + ), tabs.firstChild)
64 + // tabs.insertBefore(search, tabs.firstChild.nextSibling)
7765
78- tabs.select(0)
66 + var saved = []
67 + // try { saved = JSON.parse(localStorage.openTabs) }
68 + // catch (_) { }
7969
80- //handle link clicks
81- window.onclick = function (ev) {
82- var link = ancestor(ev.target)
83- if(!link) return
84- var path = link.hash.substring(1)
70 + if(!saved || saved.length < 3)
71 + saved = ['/public', '/private', '/notifications', '/data']
8572
86- ev.preventDefault()
87- ev.stopPropagation()
88-
89- //let the application handle this link
90- if (link.getAttribute('href') === '#') return
91-
92- //open external links.
93- //this ought to be made into something more runcible
94- if(open.isExternal(link.href)) return open(link.href)
95-
96- if(tabs.has(path))
97- return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey)
98-
99- var el = screen_view(path)
100- if(el) {
73 + saved.forEach(function (path) {
74 + var el = api.screen_view(path)
75 + if(!el) return
10176 el.id = el.id || path
77 + if (!el) return
10278 el.scroll = keyscroll(el.querySelector('.scroller__content'))
103- tabs.add(el, !ev.ctrlKey, !!ev.shiftKey)
104-// localStorage.openTabs = JSON.stringify(tabs.tabs)
105- }
79 + if(el) tabs.add(el, false, false)
80 + })
10681
107- return false
108- }
82 + tabs.select(0)
10983
110- window.addEventListener('keydown', function (ev) {
111- if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA')
112- return
113- switch(ev.keyCode) {
84 + //handle link clicks
85 + window.onclick = function (ev) {
86 + var link = ancestor(ev.target)
87 + if(!link) return
88 + var path = link.hash.substring(1)
11489
115- // scroll through tabs
116- case 72: // h
117- return tabs.selectRelative(-1)
118- case 76: // l
119- return tabs.selectRelative(1)
90 + ev.preventDefault()
91 + ev.stopPropagation()
12092
121- // scroll through messages
122- case 74: // j
123- return tabs.get(tabs.selected[0]).scroll(1)
124- case 75: // k
125- return tabs.get(tabs.selected[0]).scroll(-1)
93 + //let the application handle this link
94 + if (link.getAttribute('href') === '#') return
12695
127- // close a tab
128- case 88: // x
129- if (tabs.selected) {
130- var sel = tabs.selected
131- var i = sel.reduce(function (a, b) { return Math.min(a, b) })
132- tabs.remove(sel)
133- tabs.select(Math.max(i-1, 0))
134- }
135- return
96 + //open external links.
97 + //this ought to be made into something more runcible
98 + if(open.isExternal(link.href)) return open(link.href)
13699
137- // activate the search field
138- case 191: // /
139- if (ev.shiftKey)
140- search.activate('?', ev)
141- else
142- search.activate('/', ev)
143- return
100 + if(tabs.has(path))
101 + return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey)
144102
145- // navigate to a feed
146- case 50: // 2
147- if (ev.shiftKey)
148- search.activate('@', ev)
149- return
103 + var el = api.screen_view(path)
104 + if(el) {
105 + el.id = el.id || path
106 + el.scroll = keyscroll(el.querySelector('.scroller__content'))
107 + tabs.add(el, !ev.ctrlKey, !!ev.shiftKey)
108 + // localStorage.openTabs = JSON.stringify(tabs.tabs)
109 + }
150110
151- // navigate to a channel
152- case 51: // 3
153- if (ev.shiftKey)
154- search.activate('#', ev)
155- return
111 + return false
112 + }
156113
157- // navigate to a message
158- case 53: // 5
159- if (ev.shiftKey)
160- search.activate('%', ev)
114 + window.addEventListener('keydown', function (ev) {
115 + if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA')
161116 return
162- }
163- })
117 + switch(ev.keyCode) {
164118
165- // errors tab
166- var errorsContent = h('div.column.scroller__content')
167- var errors = h('div.column.scroller', {
168- id: 'errors',
169- style: {'overflow':'auto'}
170- }, h('div.scroller__wrapper',
171- errorsContent
172- )
173- )
119 + // scroll through tabs
120 + case 72: // h
121 + return tabs.selectRelative(-1)
122 + case 76: // l
123 + return tabs.selectRelative(1)
174124
175- // remove loader error handler
176- if (window.onError) {
177- window.removeEventListener('error', window.onError)
178- delete window.onError
179- }
125 + // scroll through messages
126 + case 74: // j
127 + return tabs.get(tabs.selected[0]).scroll(1)
128 + case 75: // k
129 + return tabs.get(tabs.selected[0]).scroll(-1)
180130
181- // put errors in a tab
182- window.addEventListener('error', function (ev) {
183- var err = ev.error || ev
184- if(!tabs.has('errors'))
185- tabs.add(errors, false)
186- var el = h('div.message',
187- h('strong', err.message),
188- h('pre', err.stack))
189- if (errorsContent.firstChild)
190- errorsContent.insertBefore(el, errorsContent.firstChild)
191- else
192- errorsContent.appendChild(el)
193- })
131 + // close a tab
132 + case 88: // x
133 + if (tabs.selected) {
134 + var sel = tabs.selected
135 + var i = sel.reduce(function (a, b) { return Math.min(a, b) })
136 + tabs.remove(sel)
137 + tabs.select(Math.max(i-1, 0))
138 + }
139 + return
194140
195- if (process.versions.electron) {
196- window.addEventListener('contextmenu', function (ev) {
197- ev.preventDefault()
198- var remote = require('electron').remote
199- var Menu = remote.Menu
200- var MenuItem = remote.MenuItem
201- var menu = new Menu()
202- menu.append(new MenuItem({
203- label: 'Inspect Element',
204- click: function () {
205- remote.getCurrentWindow().inspectElement(ev.x, ev.y)
206- }
207- }))
208- menu.popup(remote.getCurrentWindow())
209- })
210- }
141 + // activate the search field
142 + case 191: // /
143 + if (ev.shiftKey)
144 + search.activate('?', ev)
145 + else
146 + search.activate('/', ev)
147 + return
211148
212- return tabs
213-}
149 + // navigate to a feed
150 + case 50: // 2
151 + if (ev.shiftKey)
152 + search.activate('@', ev)
153 + return
214154
155 + // navigate to a channel
156 + case 51: // 3
157 + if (ev.shiftKey)
158 + search.activate('#', ev)
159 + return
215160
161 + // navigate to a message
162 + case 53: // 5
163 + if (ev.shiftKey)
164 + search.activate('%', ev)
165 + return
166 + }
167 + })
216168
169 + // errors tab
170 + var errorsContent = h('div.column.scroller__content')
171 + var errors = h('div.column.scroller', {
172 + id: 'errors',
173 + style: {'overflow':'auto'}
174 + }, h('div.scroller__wrapper',
175 + errorsContent
176 + )
177 + )
217178
179 + // remove loader error handler
180 + if (window.onError) {
181 + window.removeEventListener('error', window.onError)
182 + delete window.onError
183 + }
218184
185 + // put errors in a tab
186 + window.addEventListener('error', function (ev) {
187 + var err = ev.error || ev
188 + if(!tabs.has('errors'))
189 + tabs.add(errors, false)
190 + var el = h('div.message',
191 + h('strong', err.message),
192 + h('pre', err.stack))
193 + if (errorsContent.firstChild)
194 + errorsContent.insertBefore(el, errorsContent.firstChild)
195 + else
196 + errorsContent.appendChild(el)
197 + })
219198
199 + if (process.versions.electron) {
200 + window.addEventListener('contextmenu', function (ev) {
201 + ev.preventDefault()
202 + var remote = require('electron').remote
203 + var Menu = remote.Menu
204 + var MenuItem = remote.MenuItem
205 + var menu = new Menu()
206 + menu.append(new MenuItem({
207 + label: 'Inspect Element',
208 + click: function () {
209 + remote.getCurrentWindow().inspectElement(ev.x, ev.y)
210 + }
211 + }))
212 + menu.popup(remote.getCurrentWindow())
213 + })
214 + }
220215
216 + return tabs
217 + }
221218
222-
223-
219 +}
modules_extra/audio-mp3.jsView
@@ -4,25 +4,36 @@
44 var ref = require('ssb-ref');
55
66 //render a message
77
8-var plugs = require('../plugs');
9-var message_link = plugs.first(exports.message_link = []);
10-var message_confirm = plugs.first(exports.message_confirm = []);
11-var sbot_links = plugs.first(exports.sbot_links = []);
12-var blob_url = plugs.first(exports.blob_url = []);
8 +//var plugs = require('../plugs');
9 +//var message_link = plugs.first(exports.message_link = []);
10 +//var message_confirm = plugs.first(exports.message_confirm = []);
11 +//var sbot_links = plugs.first(exports.sbot_links = []);
12 +//var blob_url = plugs.first(exports.blob_url = []);
13 +//
14 +exports.needs = {
15 +// message_link: 'first',
16 +// message_confirm: 'first',
17 +// sbot_links: 'first',
18 + blobs_url: 'first'
19 +}
1320
14-exports.message_content = function(msg, sbot) {
21 +exports.gives = 'message_content'
22 +
23 +exports.create = function (api) {
24 +
25 + return function(msg) {
1526 if (msg.value.content.type !== 'audio-mp3')
1627 return;
1728
1829 var v = msg.value.content;
1930 return h('div',
2031 h('h2', "(" + v.Track + ") " + v.Title),
21- // h('img', { "src" : blob_url(v.cover) }),
32 + // h('img', { "src" : api.blob_url(v.cover) }),
2233 h('audio', {
2334 "controls" : true,
24- "src" : blob_url(v.link)
35 + "src" : api.blob_url(v.link)
2536 }))
2637 // h('dl',
2738 // Object.keys(v).map(function(k) {
2839 // return [
@@ -45,7 +56,7 @@
4556
4657 // "Title": "clouds",
4758 // "Track": "2",
4859 // "Width": "0",
49-
60 + }
5061 }
5162
modules_extra/blob.jsView
@@ -1,23 +1,27 @@
11 var h = require('hyperscript')
22 var ref = require('ssb-ref')
33
4-var plugs = require('../plugs')
5-var blob_url = plugs.first(exports.blob_url = [])
4 +exports.gives = 'screen_view'
65
7-exports.screen_view = function (path) {
8- if(ref.isBlob(path)) return blob_view(path)
6 +exports.needs = {
7 + blob_url: 'first'
98 }
109
11-function blob_view(id) {
12- return h('iframe', {
13- src: blob_url(id),
14- sandbox: '',
15- style: {
16- position: 'absolute',
17- width: '100%',
18- height: '100%',
19- border: 0,
20- }
21- })
10 +exports.create = function (api) {
11 + return function (path) {
12 + if(ref.isBlob(path)) return blob_view(path)
13 + }
14 +
15 + function blob_view(id) {
16 + return h('iframe', {
17 + src: api.blob_url(id),
18 + sandbox: '',
19 + style: {
20 + position: 'absolute',
21 + width: '100%',
22 + height: '100%',
23 + border: 0,
24 + }
25 + })
26 + }
2227 }
23-
modules_extra/channel.jsView
@@ -1,104 +1,123 @@
11 var h = require('hyperscript')
22 var u = require('../util')
33 var pull = require('pull-stream')
44 var Scroller = require('pull-scroll')
5-
6-var plugs = require('../plugs')
7-var message_render = plugs.first(exports.message_render = [])
8-var message_compose = plugs.first(exports.message_compose = [])
9-var sbot_log = plugs.first(exports.sbot_log = [])
10-var sbot_query = plugs.first(exports.sbot_query = [])
115 var mfr = require('map-filter-reduce')
126
13-exports.message_meta = function (msg) {
14- var chan = msg.value.content.channel
15- if (chan)
16- return h('a', {href: '##'+chan}, '#'+chan)
7 +//var plugs = require('../plugs')
8 +//var message_render = plugs.first(exports.message_render = [])
9 +//var message_compose = plugs.first(exports.message_compose = [])
10 +//var sbot_log = plugs.first(exports.sbot_log = [])
11 +//var sbot_query = plugs.first(exports.sbot_query = [])
12 +
13 +exports.needs = {
14 + message_render: 'first',
15 + message_compose: 'first',
16 + sbot_log: 'first',
17 + sbot_query: 'first',
1718 }
1819
19-exports.screen_view = function (path) {
20- if(path[0] === '#') {
21- var channel = path.substr(1)
20 +exports.gives = {
21 + message_meta: true, screen_view: true,
22 + connection_status: true, suggest_search: true
23 +}
2224
23- var content = h('div.column.scroller__content')
24- var div = h('div.column.scroller',
25- {style: {'overflow':'auto'}},
26- h('div.scroller__wrapper',
27- message_compose({type: 'post', channel: channel}),
28- content
29- )
30- )
25 +exports.create = function (api) {
3126
32- function matchesChannel(msg) {
33- if (msg.sync) console.error('SYNC', msg)
34- var c = msg && msg.value && msg.value.content
35- return c && c.channel === channel
36- }
27 + var channels
3728
38- pull(
39- sbot_log({old: false}),
40- pull.filter(matchesChannel),
41- Scroller(div, content, message_render, true, false)
42- )
29 + var filter = {$filter: {value: {content: {channel: {$gt: ''}}}}}
30 + var map = {$map: {'name': ['value', 'content', 'channel']}}
31 + var reduce = {$reduce: {
32 + name: 'name',
33 + rank: {$count: true}
34 + }}
4335
44- pull(
45- sbot_query({reverse: true, query: [
46- {$filter: {value: {content: {channel: channel}}}}
47- ]}),
48- Scroller(div, content, message_render, false, false)
49- )
36 + return {
37 + message_meta: function (msg) {
38 + var chan = msg.value.content.channel
39 + if (chan)
40 + return h('a', {href: '##'+chan}, '#'+chan)
41 + },
42 + screen_view: function (path) {
43 + if(path[0] === '#') {
44 + var channel = path.substr(1)
5045
51- return div
52- }
53-}
46 + var content = h('div.column.scroller__content')
47 + var div = h('div.column.scroller',
48 + {style: {'overflow':'auto'}},
49 + h('div.scroller__wrapper',
50 + api.message_compose({type: 'post', channel: channel}),
51 + content
52 + )
53 + )
5454
55-var channels
55 + function matchesChannel(msg) {
56 + if (msg.sync) console.error('SYNC', msg)
57 + var c = msg && msg.value && msg.value.content
58 + return c && c.channel === channel
59 + }
5660
57-var filter = {$filter: {value: {content: {channel: {$gt: ''}}}}}
58-var map = {$map: {'name': ['value', 'content', 'channel']}}
59-var reduce = {$reduce: {
60- name: 'name',
61- rank: {$count: true}
62-}}
61 + pull(
62 + api.sbot_log({old: false}),
63 + pull.filter(matchesChannel),
64 + Scroller(div, content, message_render, true, false)
65 + )
6366
64-exports.connection_status = function (err) {
65- if(err) return
67 + pull(
68 + api.sbot_query({reverse: true, query: [
69 + {$filter: {value: {content: {channel: channel}}}}
70 + ]}),
71 + Scroller(div, content, message_render, false, false)
72 + )
6673
67- channels = []
74 + return div
75 + }
76 + },
6877
69- pull(
70- sbot_query({query: [filter, map, reduce]}),
71- pull.collect(function (err, chans) {
72- if (err) return console.error(err)
73- channels = chans.concat(channels)
74- })
75- )
78 + connection_status: function (err) {
79 + if(err) return
7680
77- pull(
78- sbot_log({old: false}),
79- mfr.filter(filter),
80- mfr.map(map),
81- pull.drain(function (chan) {
82- var c = channels.find(function (e) {
83- return e.name === chan.name
84- })
85- if (c) c.rank++
86- else channels.push(chan)
87- })
88- )
89-}
81 + channels = []
9082
91-exports.suggest_search = function (query, cb) {
92- if(!/^#\w/.test(query)) return cb()
93- cb(null, channels.filter(function (chan) {
94- return ('#'+chan.name).substring(0, query.length) === query
95- })
96- .map(function (chan) {
97- var name = '#'+chan.name
98- return {
99- title: name,
100- value: name,
101- subtitle: chan.rank
83 + pull(
84 + api.sbot_query({query: [filter, map, reduce]}),
85 + pull.collect(function (err, chans) {
86 + if (err) return console.error(err)
87 + channels = chans.concat(channels)
88 + })
89 + )
90 +
91 + pull(
92 + api.sbot_log({old: false}),
93 + mfr.filter(filter),
94 + mfr.map(map),
95 + pull.drain(function (chan) {
96 + var c = channels.find(function (e) {
97 + return e.name === chan.name
98 + })
99 + if (c) c.rank++
100 + else channels.push(chan)
101 + })
102 + )
103 + },
104 +
105 + suggest_search: function (query) {
106 + return function (cb) {
107 + if(!/^#\w/.test(query)) return cb()
108 + cb(null, channels.filter(function (chan) {
109 + return ('#'+chan.name).substring(0, query.length) === query
110 + })
111 + .map(function (chan) {
112 + var name = '#'+chan.name
113 + return {
114 + title: name,
115 + value: name,
116 + subtitle: chan.rank
117 + }
118 + }))
119 + }
102120 }
103- }))
121 + }
104122 }
123 +
modules_extra/dns.jsView
@@ -7,22 +7,27 @@
77 function idLink(id) {
88 return h('a', {href: '#'+id}, id.substring(0, 10)+'…')
99 }
1010
11-exports.message_content = function (msg, sbot) {
12- var c = msg.value.content
11 +exports.gives = 'message_content'
1312
14- if(c.type === 'ssb-dns') {
15- var record = c.record || {}
16- return h('div',
17- h('p',
18- h('ins', {title: 'name'}, record.name), ' ',
19- h('em', {title: 'ttl'}, record.ttl), ' ',
20- h('span', {title: 'class'}, record.class), ' ',
21- h('span', {title: 'type'}, record.type),
22- h('pre', {title: 'data'},
23- JSON.stringify(record.data || record.value, null, 2)),
24- !c.branch ? null : h('div', h('span',
25- 'replaces: ', array(c.branch).map(idLink)))
26- ))
13 +exports.create = function () {
14 + return function (msg, sbot) {
15 + var c = msg.value.content
16 +
17 + if(c.type === 'ssb-dns') {
18 + var record = c.record || {}
19 + return h('div',
20 + h('p',
21 + h('ins', {title: 'name'}, record.name), ' ',
22 + h('em', {title: 'ttl'}, record.ttl), ' ',
23 + h('span', {title: 'class'}, record.class), ' ',
24 + h('span', {title: 'type'}, record.type),
25 + h('pre', {title: 'data'},
26 + JSON.stringify(record.data || record.value, null, 2)),
27 + !c.branch ? null : h('div', h('span',
28 + 'replaces: ', array(c.branch).map(idLink)))
29 + ))
30 + }
2731 }
32 +
2833 }
modules_extra/emoji.jsView
@@ -1,14 +1,22 @@
11 var emojis = require('emoji-named-characters')
22 var emojiNames = Object.keys(emojis)
33
4-var plugs = require('../plugs')
5-var blob_url = plugs.first(exports.blob_url = [])
4 +//var plugs = require('../plugs')
5 +//var blob_url = plugs.first(exports.blob_url = [])
6 +//
67
7-exports.emoji_names = function () {
8- return emojiNames
8 +exports.needs = { blob_url: 'first' }
9 +exports.gives = { emoji_names: true, emoji_url: true }
10 +
11 +exports.create = function (api) {
12 + return {
13 + emoji_names: function () {
14 + return emojiNames
15 + },
16 + emoji_url: function (emoji) {
17 + return emoji in emojis &&
18 + api.blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png'
19 + }
20 + }
921 }
1022
11-exports.emoji_url = function (emoji) {
12- return emoji in emojis &&
13- blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png'
14-}
modules_extra/git-ssb.jsView
@@ -2,40 +2,53 @@
22 var u = require('../util')
33 var pull = require('pull-stream')
44 var Scroller = require('pull-scroll')
55
6-var plugs = require('../plugs')
7-var message_render = plugs.first(exports.message_render = [])
8-var message_compose = plugs.first(exports.message_compose = [])
9-var sbot_log = plugs.first(exports.sbot_log = [])
6 +//var plugs = require('../plugs')
7 +//var message_render = plugs.first(exports.message_render = [])
8 +//var message_compose = plugs.first(exports.message_compose = [])
9 +//var sbot_log = plugs.first(exports.sbot_log = [])
1010
11-exports.menu_items = function () {
12- return h('a', {href: '#/git-ssb'}, '/git-ssb')
11 +exports.needs = {
12 + message_render: 'first',
13 + message_compose: 'first',
14 + sbot_log: 'first'
1315 }
1416
15-exports.screen_view = function (path, sbot) {
16- if(path === '/git-ssb') {
17 +exports.gives = {
18 + menu_items: true, screen_view: true
19 +}
1720
18- var content = h('div.column.scroller__content')
19- var div = h('div.column.scroller',
20- {style: {'overflow':'auto'}},
21- h('div.scroller__wrapper', content)
22- )
21 +exports.create = function (api) {
22 + return {
23 + menu_items: function () {
24 + return h('a', {href: '#/git-ssb'}, '/git-ssb')
25 + },
2326
24- pull(
25- u.next(sbot_log, {old: false, limit: 100}),
26- Scroller(div, content, message_render, true, false)
27- )
27 + screen_view: function (path, sbot) {
28 + if(path === '/git-ssb') {
2829
29- pull(
30- u.next(sbot_log, {reverse: true, limit: 100, live: false}),
31- pull.filter(function(msg) { return msg.value.content.type }),
32- pull.filter(function(msg) {
33- return msg.value.content.type.match(/^git/)
34- }),
35- Scroller(div, content, message_render, false, false)
36- )
30 + var content = h('div.column.scroller__content')
31 + var div = h('div.column.scroller',
32 + {style: {'overflow':'auto'}},
33 + h('div.scroller__wrapper', content)
34 + )
3735
38- return div
36 + pull(
37 + u.next(api.sbot_log, {old: false, limit: 100}),
38 + Scroller(div, content, api.message_render, true, false)
39 + )
40 +
41 + pull(
42 + u.next(api.sbot_log, {reverse: true, limit: 100, live: false}),
43 + pull.filter(function(msg) { return msg.value.content.type }),
44 + pull.filter(function(msg) {
45 + return msg.value.content.type.match(/^git/)
46 + }),
47 + Scroller(div, content, api.message_render, false, false)
48 + )
49 +
50 + return div
51 + }
52 + }
3953 }
4054 }
41-
modules_extra/git.jsView
@@ -1,491 +1,519 @@
1 +'use strict'
12 var h = require('hyperscript')
23 var pull = require('pull-stream')
34 var paramap = require('pull-paramap')
45 var cat = require('pull-cat')
56 var human = require('human-time')
67 var combobox = require('hypercombo')
78
8-var plugs = require('../plugs')
9-var message_link = plugs.first(exports.message_link = [])
10-var message_confirm = plugs.first(exports.message_confirm = [])
11-var message_compose = plugs.first(exports.message_compose = [])
12-var sbot_links = plugs.first(exports.sbot_links = [])
13-var sbot_links2 = plugs.first(exports.sbot_links2 = [])
14-var sbot_get = plugs.first(exports.sbot_get = [])
159 var getAvatar = require('ssb-avatar')
16-var avatar_name = plugs.first(exports.avatar_name = [])
17-var markdown = plugs.first(exports.markdown = [])
1810 var KVGraph = require('kvgraph')
1911 var mergeRepo = require('ssb-git/merge')
2012
21-var self_id = require('../keys').id
13 +//var plugs = require('../plugs')
14 +//var message_link = plugs.first(exports.message_link = [])
15 +//var message_confirm = plugs.first(exports.message_confirm = [])
16 +//var message_compose = plugs.first(exports.message_compose = [])
17 +//var sbot_links = plugs.first(exports.sbot_links = [])
18 +//var sbot_links2 = plugs.first(exports.sbot_links2 = [])
19 +//var sbot_get = plugs.first(exports.sbot_get = [])
20 +//var avatar_name = plugs.first(exports.avatar_name = [])
21 +//var markdown = plugs.first(exports.markdown = [])
2222
23-function shortRefName(ref) {
24- return ref.replace(/^refs\/(heads|tags)\//, '')
23 +exports.needs = {
24 + message_link: 'first',
25 + message_confirm: 'first',
26 + message_compose: 'first',
27 + sbot_links: 'first',
28 + sbot_links2: 'first',
29 + sbot_get: 'first',
30 + avatar_name: 'first',
31 + markdown: 'first'
2532 }
2633
27-function getRefs(msg) {
28- var updates = new KVGraph('key')
29- var _cb, _refs
30- pull(
31- sbot_links({
32- reverse: true,
33- // source: msg.value.author,
34- dest: msg.key,
35- rel: 'repo',
36- values: true
37- }),
38- pull.drain(function (link) {
39- if (link.value.content.type === 'git-update') {
40- updates.add(link)
41- }
42- }, function (err) {
43- var refs = updates.reduceRight(mergeRepo).refs
44- var cb = _cb
45- if (cb) delete _cb, cb(err, refs)
46- else _refs = refs
47- })
48- )
49-
50- return pull(
51- function fn(end, cb) {
52- if (end || fn.ended) cb(true)
53- fn.ended = true
54- if (_refs) cb(_refs)
55- else _cb = cb
56- },
57- pull.flatten()
58- )
34 +exports.gives = {
35 + message_action: true,
36 + message_meta: true,
37 + message_content: true
5938 }
6039
61-function getForks(id) {
62- return pull(
63- sbot_links({
64- reverse: true,
65- dest: id,
66- rel: 'upstream'
67- }),
68- pull.map(function (link) {
69- return {
70- id: link.key,
71- author: link.source
72- }
73- })
74- )
75-}
7640
77-function repoText(id) {
78- var text = document.createTextNode(id.substr(0, 10) + '…')
79- getAvatar({links: sbot_links, get: sbot_get}, self_id, id,
80- function (err, avatar) {
81- if(err) return console.error(err)
82- if (avatar.name[0] !== '%') avatar.name = '%' + avatar.name
83- text.nodeValue = avatar.name
84- })
85- return text
86-}
41 +var self_id = require('../keys').id
8742
88-function repoLink(id) {
89- return h('a', {href: '#'+id}, repoText(id))
43 +function shortRefName(ref) {
44 + return ref.replace(/^refs\/(heads|tags)\//, '')
9045 }
9146
92-function repoName(id) {
93- return h('ins', repoText(id))
94-}
47 +exports.create = function (api) {
9548
96-function getIssueState(id, cb) {
97- pull(
98- sbot_links({dest: id, rel: 'issues', values: true, reverse: true}),
99- pull.map(function (msg) {
100- return msg.value.content.issues
101- }),
102- pull.flatten(),
103- pull.filter(function (issue) {
104- return issue.link === id
105- }),
106- pull.map(function (issue) {
107- return issue.merged ? 'merged' : issue.open ? 'open' : 'closed'
108- }),
109- pull.take(1),
110- pull.collect(function (err, updates) {
111- cb(err, updates && updates[0] || 'open')
112- })
113- )
114-}
115-
116-//todo:
117-function messageTimestampLink(msg) {
118- var date = new Date(msg.value.timestamp)
119- return h('a.timestamp', {
120- timestamp: msg.value.timestamp,
121- title: date,
122- href: '#'+msg.key
123- }, human(date))
124-}
125-
126-// a thead+tbody where the thead only is added when the first row is added
127-function tableRows(headerRow) {
128- var thead = h('thead'), tbody = h('tbody')
129- var first = true
130- var t = [thead, tbody]
131- t.append = function (row) {
132- if (first) {
133- first = false
134- thead.appendChild(headerRow)
135- }
136- tbody.appendChild(row)
137- }
138- return t
139-}
140-
141-function renderIssueEdit(c) {
142- var id = c.issue || c.link
143- return [
144- c.title ? h('p', 'renamed issue ', message_link(id),
145- ' to ', h('ins', c.title)) : null,
146- c.open === false ? h('p', 'closed issue ', message_link(id)) : null,
147- c.open === true ? h('p', 'reopened issue ', message_link(id)) : null]
148-}
149-
150-exports.message_content = function (msg, sbot) {
151- var c = msg.value.content
152-
153- if(c.type === 'git-repo') {
154- var branchesT, tagsT, openIssuesT, closedIssuesT, openPRsT, closedPRsT
155- var forksT
156- var div = h('div',
157- h('p', 'git repo ', repoName(msg.key)),
158- c.upstream ? h('p', 'fork of ', repoLink(c.upstream)) : '',
159- h('p', h('code', 'ssb://' + msg.key)),
160- h('div.git-table-wrapper', {style: {'max-height': '12em'}},
161- h('table',
162- branchesT = tableRows(h('tr',
163- h('th', 'branch'),
164- h('th', 'commit'),
165- h('th', 'last update'))),
166- tagsT = tableRows(h('tr',
167- h('th', 'tag'),
168- h('th', 'commit'),
169- h('th', 'last update'))))),
170- h('div.git-table-wrapper', {style: {'max-height': '16em'}},
171- h('table',
172- openIssuesT = tableRows(h('tr',
173- h('th', 'open issues'))),
174- closedIssuesT = tableRows(h('tr',
175- h('th', 'closed issues'))))),
176- h('div.git-table-wrapper', {style: {'max-height': '16em'}},
177- h('table',
178- openPRsT = tableRows(h('tr',
179- h('th', 'open pull requests'))),
180- closedPRsT = tableRows(h('tr',
181- h('th', 'closed pull requests'))))),
182- h('div.git-table-wrapper',
183- h('table',
184- forksT = tableRows(h('tr',
185- h('th', 'forks'))))),
186- h('div', h('a', {href: '#', onclick: function (e) {
187- e.preventDefault()
188- this.parentNode.replaceChild(issueForm(msg), this)
189- }}, 'New Issue…')),
190- newPullRequestButton.call(this, msg)
191- )
192-
193- pull(getRefs(msg), pull.drain(function (ref) {
194- var name = ref.realname || ref.name
195- var author = ref.link && ref.link.value.author
196- var parts = /^refs\/(heads|tags)\/(.*)$/.exec(name) || []
197- var shortName = parts[2]
198- var t
199- if(parts[1] === 'heads') t = branchesT
200- else if(parts[1] === 'tags') t = tagsT
201- if(t) t.append(h('tr',
202- h('td', shortName,
203- ref.conflict ? [
204- h('br'),
205- h('a', {href: '#'+author}, avatar_name(author))
206- ] : ''),
207- h('td', h('code', ref.hash)),
208- h('td', messageTimestampLink(ref.link))))
209- }, function (err) {
210- if(err) console.error(err)
211- }))
212-
213- // list issues and pull requests
49 + function getRefs(msg) {
50 + var updates = new KVGraph('key')
51 + var _cb, _refs
21452 pull(
215- sbot_links({
53 + api.sbot_links({
21654 reverse: true,
55 + // source: msg.value.author,
21756 dest: msg.key,
218- rel: 'project',
57 + rel: 'repo',
21958 values: true
22059 }),
221- paramap(function (link, cb) {
222- getIssueState(link.key, function (err, state) {
223- if(err) return cb(err)
224- link.state = state
225- cb(null, link)
226- })
227- }),
22860 pull.drain(function (link) {
229- var c = link.value.content
230- var title = c.title || (c.text ? c.text.length > 70
231- ? c.text.substr(0, 70) + '…'
232- : c.text : link.key)
233- var author = link.value.author
234- var t = c.type === 'pull-request'
235- ? link.state === 'open' ? openPRsT : closedPRsT
236- : link.state === 'open' ? openIssuesT : closedIssuesT
237- t.append(h('tr',
238- h('td',
239- h('a', {href: '#'+link.key}, title), h('br'),
240- h('small',
241- 'opened ', messageTimestampLink(link),
242- ' by ', h('a', {href: '#'+author}, avatar_name(author))))))
61 + if (link.value.content.type === 'git-update') {
62 + updates.add(link)
63 + }
24364 }, function (err) {
244- if (err) console.error(err)
65 + var refs = updates.reduceRight(mergeRepo).refs
66 + var cb = _cb
67 + if (cb) _cb = null, cb(err, refs)
68 + else _refs = refs
24569 })
24670 )
24771
248- // list forks
249- pull(
250- getForks(msg.key),
251- pull.drain(function (fork) {
252- forksT.append(h('tr', h('td',
253- repoLink(fork.id),
254- ' by ', h('a', {href: '#'+fork.author}, avatar_name(fork.author)))))
255- }, function (err) {
256- if (err) console.error(err)
72 + return pull(
73 + function fn(end, cb) {
74 + if (end || fn.ended) cb(true)
75 + fn.ended = true
76 + if (_refs) cb(_refs)
77 + else _cb = cb
78 + },
79 + pull.flatten()
80 + )
81 + }
82 +
83 + function getForks(id) {
84 + return pull(
85 + api.sbot_links({
86 + reverse: true,
87 + dest: id,
88 + rel: 'upstream'
89 + }),
90 + pull.map(function (link) {
91 + return {
92 + id: link.key,
93 + author: link.source
94 + }
25795 })
25896 )
97 + }
25998
260- return div
99 + function repoText(id) {
100 + var text = document.createTextNode(id.substr(0, 10) + '…')
101 + getAvatar({links: api.sbot_links, get: api.sbot_get}, self_id, id,
102 + function (err, avatar) {
103 + if(err) return console.error(err)
104 + if (avatar.name[0] !== '%') avatar.name = '%' + avatar.name
105 + text.nodeValue = avatar.name
106 + })
107 + return text
261108 }
262109
263- if(c.type === 'git-update') {
264- return [
265- h('p', 'pushed to ', repoLink(c.repo)),
266- c.refs ? h('ul', Object.keys(c.refs).map(function (ref) {
267- var rev = c.refs[ref]
268- return h('li',
269- shortRefName(ref) + ': ',
270- rev ? h('code', rev) : h('em', 'deleted'))
271- })) : null,
272- Array.isArray(c.commits) ? [
273- h('ul',
274- c.commits.map(function (commit) {
275- return h('li',
276- typeof commit.sha1 === 'string' ?
277- [h('code', commit.sha1.substr(0, 8)), ' '] : null,
278- commit.title ?
279- h('q', commit.title) : null)
280- }),
281- c.commits_more > 0 ?
282- h('li', '+ ', c.commits_more, ' more') : null)
283- ] : null,
284- Array.isArray(c.issues) ? c.issues.map(function (issue) {
285- if (issue.merged === true)
286- return h('p', 'Merged ', message_link(issue.link), ' in ',
287- h('code', issue.object), ' ', h('q', issue.label))
288- if (issue.open === false)
289- return h('p', 'Closed ', message_link(issue.link), ' in ',
290- h('code', issue.object), ' ', h('q', issue.label))
291- }) : null,
292- newPullRequestButton.call(this, msg)
293- ]
110 + function repoLink(id) {
111 + return h('a', {href: '#'+id}, repoText(id))
294112 }
295113
296- if(c.type === 'issue-edit'
297- || (c.type === 'post' && c.text === '')) {
298- return h('div',
299- c.issue ? renderIssueEdit(c) : null,
300- c.issues ? c.issues.map(renderIssueEdit) : null)
114 + function repoName(id) {
115 + return h('ins', repoText(id))
301116 }
302117
303- if(c.type === 'issue') {
304- return h('div',
305- h('p', 'opened issue on ', repoLink(c.project)),
306- c.title ? h('h4', c.title) : '',
307- markdown(c)
118 + function getIssueState(id, cb) {
119 + pull(
120 + api.sbot_links({dest: id, rel: 'issues', values: true, reverse: true}),
121 + pull.map(function (msg) {
122 + return msg.value.content.issues
123 + }),
124 + pull.flatten(),
125 + pull.filter(function (issue) {
126 + return issue.link === id
127 + }),
128 + pull.map(function (issue) {
129 + return issue.merged ? 'merged' : issue.open ? 'open' : 'closed'
130 + }),
131 + pull.take(1),
132 + pull.collect(function (err, updates) {
133 + cb(err, updates && updates[0] || 'open')
134 + })
308135 )
309136 }
310137
311- if(c.type === 'pull-request') {
312- return h('div',
313- h('p', 'opened pull-request ',
314- 'to ', repoLink(c.repo), ':', c.branch, ' ',
315- 'from ', repoLink(c.head_repo), ':', c.head_branch),
316- c.title ? h('h4', c.title) : '',
317- markdown(c)
138 + //todo:
139 + function messageTimestampLink(msg) {
140 + var date = new Date(msg.value.timestamp)
141 + return h('a.timestamp', {
142 + timestamp: msg.value.timestamp,
143 + title: date,
144 + href: '#'+msg.key
145 + }, human(date))
146 + }
147 +
148 + // a thead+tbody where the thead only is added when the first row is added
149 + function tableRows(headerRow) {
150 + var thead = h('thead'), tbody = h('tbody')
151 + var first = true
152 + var t = [thead, tbody]
153 + t.append = function (row) {
154 + if (first) {
155 + first = false
156 + thead.appendChild(headerRow)
157 + }
158 + tbody.appendChild(row)
159 + }
160 + return t
161 + }
162 +
163 + function renderIssueEdit(c) {
164 + var id = c.issue || c.link
165 + return [
166 + c.title ? h('p', 'renamed issue ', api.message_link(id),
167 + ' to ', h('ins', c.title)) : null,
168 + c.open === false ? h('p', 'closed issue ', api.message_link(id)) : null,
169 + c.open === true ? h('p', 'reopened issue ', api.message_link(id)) : null]
170 + }
171 +
172 + function findMessageContent(el) {
173 + for(; el; el = el.parentNode) {
174 + if(el.classList.contains('message')) {
175 + return el.querySelector('.message_content')
176 + }
177 + }
178 + }
179 +
180 + function issueForm(msg, contentEl) {
181 + var form = h('form',
182 + h('strong', 'New Issue:'),
183 + api.message_compose(
184 + {type: 'issue', project: msg.key},
185 + function (value) { return value },
186 + function (err, issue) {
187 + if(err) return alert(err)
188 + if(!issue) return
189 + var title = issue.value.content.text
190 + if(title.length > 70) title = title.substr(0, 70) + '…'
191 + form.appendChild(h('div',
192 + h('a', {href: '#'+issue.key}, title)
193 + ))
194 + }
195 + )
318196 )
197 + return form
319198 }
320-}
321199
322-exports.message_meta = function (msg, sbot) {
323- var type = msg.value.content.type
324- if (type === 'issue' || type === 'pull-request') {
325- var el = h('em', '...')
326- // TODO: update if issue is changed
327- getIssueState(msg.key, function (err, state) {
328- if (err) return console.error(err)
329- el.textContent = state
200 + function branchMenu(msg, full) {
201 + return combobox({
202 + style: {'max-width': '14ex'},
203 + placeholder: 'branch…',
204 + default: 'master',
205 + read: msg && pull(getRefs(msg), pull.map(function (ref) {
206 + var m = /^refs\/heads\/(.*)$/.exec(ref.name)
207 + if(!m) return
208 + var branch = m[1]
209 + var label = branch
210 + if(full) {
211 + var updated = new Date(ref.link.value.timestamp)
212 + label = branch +
213 + ' · ' + human(updated) +
214 + ' · ' + ref.hash.substr(1, 8) +
215 + (ref.title ? ' · "' + ref.title + '"' : '')
216 + }
217 + return h('option', {value: branch}, label)
218 + }))
330219 })
331- return el
332220 }
333-}
334221
335-function findMessageContent(el) {
336- for(; el; el = el.parentNode) {
337- if(el.classList.contains('message')) {
338- return el.querySelector('.message_content')
339- }
222 + function newPullRequestButton(msg) {
223 + return h('div', [
224 + h('a', {
225 + href: '#',
226 + onclick: function (e) {
227 + e.preventDefault()
228 + this.parentNode.replaceChild(pullRequestForm(msg), this)
229 + }},
230 + 'New Pull Request…'
231 + )
232 + ])
340233 }
341-}
342234
343-function issueForm(msg, contentEl) {
344- var form = h('form',
345- h('strong', 'New Issue:'),
346- message_compose(
347- {type: 'issue', project: msg.key},
348- function (value) { return value },
349- function (err, issue) {
350- if(err) return alert(err)
351- if(!issue) return
352- var title = issue.value.content.text
353- if(title.length > 70) title = title.substr(0, 70) + '…'
354- form.appendChild(h('div',
355- h('a', {href: '#'+issue.key}, title)
356- ))
357- }
235 + function pullRequestForm(msg) {
236 + var headRepoInput
237 + var headBranchInput = branchMenu()
238 + var branchInput = branchMenu(msg)
239 + var form = h('form',
240 + h('strong', 'New Pull Request:'),
241 + h('div',
242 + 'from ',
243 + headRepoInput = combobox({
244 + style: {'max-width': '26ex'},
245 + onchange: function () {
246 + // list branches for selected repo
247 + var repoId = this.value
248 + if(repoId) api.sbot_get(repoId, function (err, value) {
249 + if(err) console.error(err)
250 + var msg = value && {key: repoId, value: value}
251 + headBranchInput = headBranchInput.swap(branchMenu(msg, true))
252 + })
253 + else headBranchInput = headBranchInput.swap(branchMenu())
254 + },
255 + read: pull(cat([
256 + pull.once({id: msg.key, author: msg.value.author}),
257 + getForks(msg.key)
258 + ]), pull.map(function (fork) {
259 + return h('option', {value: fork.id},
260 + repoLink(fork.id), ' by ', api.avatar_name(fork.author))
261 + }))
262 + }),
263 + ':',
264 + headBranchInput,
265 + ' to ',
266 + repoName(msg.key),
267 + ':',
268 + branchInput),
269 + api.message_compose(
270 + {
271 + type: 'pull-request',
272 + project: msg.key,
273 + repo: msg.key,
274 + },
275 + function (value) {
276 + value.branch = branchInput.value
277 + value.head_repo = headRepoInput.value
278 + value.head_branch = headBranchInput.value
279 + return value
280 + },
281 + function (err, issue) {
282 + if(err) return alert(err)
283 + if(!issue) return
284 + var title = issue.value.content.text
285 + if(title.length > 70) title = title.substr(0, 70) + '…'
286 + form.appendChild(h('div',
287 + h('a', {href: '#'+issue.key}, title)
288 + ))
289 + }
290 + )
358291 )
359- )
360- return form
361-}
292 + return form
293 + }
362294
363-function branchMenu(msg, full) {
364- return combobox({
365- style: {'max-width': '14ex'},
366- placeholder: 'branch…',
367- default: 'master',
368- read: msg && pull(getRefs(msg), pull.map(function (ref) {
369- var m = /^refs\/heads\/(.*)$/.exec(ref.name)
370- if(!m) return
371- var branch = m[1]
372- var label = branch
373- if(full) {
374- var updated = new Date(ref.link.value.timestamp)
375- label = branch +
376- ' · ' + human(updated) +
377- ' · ' + ref.hash.substr(1, 8) +
378- (ref.title ? ' · "' + ref.title + '"' : '')
295 +
296 +
297 + return {
298 + message_content: function (msg, sbot) {
299 + var c = msg.value.content
300 +
301 + if(c.type === 'git-repo') {
302 + var branchesT, tagsT, openIssuesT, closedIssuesT, openPRsT, closedPRsT
303 + var forksT
304 + var div = h('div',
305 + h('p', 'git repo ', repoName(msg.key)),
306 + c.upstream ? h('p', 'fork of ', repoLink(c.upstream)) : '',
307 + h('p', h('code', 'ssb://' + msg.key)),
308 + h('div.git-table-wrapper', {style: {'max-height': '12em'}},
309 + h('table',
310 + branchesT = tableRows(h('tr',
311 + h('th', 'branch'),
312 + h('th', 'commit'),
313 + h('th', 'last update'))),
314 + tagsT = tableRows(h('tr',
315 + h('th', 'tag'),
316 + h('th', 'commit'),
317 + h('th', 'last update'))))),
318 + h('div.git-table-wrapper', {style: {'max-height': '16em'}},
319 + h('table',
320 + openIssuesT = tableRows(h('tr',
321 + h('th', 'open issues'))),
322 + closedIssuesT = tableRows(h('tr',
323 + h('th', 'closed issues'))))),
324 + h('div.git-table-wrapper', {style: {'max-height': '16em'}},
325 + h('table',
326 + openPRsT = tableRows(h('tr',
327 + h('th', 'open pull requests'))),
328 + closedPRsT = tableRows(h('tr',
329 + h('th', 'closed pull requests'))))),
330 + h('div.git-table-wrapper',
331 + h('table',
332 + forksT = tableRows(h('tr',
333 + h('th', 'forks'))))),
334 + h('div', h('a', {href: '#', onclick: function (e) {
335 + e.preventDefault()
336 + this.parentNode.replaceChild(issueForm(msg), this)
337 + }}, 'New Issue…')),
338 + newPullRequestButton.call(this, msg)
339 + )
340 +
341 + pull(getRefs(msg), pull.drain(function (ref) {
342 + var name = ref.realname || ref.name
343 + var author = ref.link && ref.link.value.author
344 + var parts = /^refs\/(heads|tags)\/(.*)$/.exec(name) || []
345 + var shortName = parts[2]
346 + var t
347 + if(parts[1] === 'heads') t = branchesT
348 + else if(parts[1] === 'tags') t = tagsT
349 + if(t) t.append(h('tr',
350 + h('td', shortName,
351 + ref.conflict ? [
352 + h('br'),
353 + h('a', {href: '#'+author}, api.avatar_name(author))
354 + ] : ''),
355 + h('td', h('code', ref.hash)),
356 + h('td', messageTimestampLink(ref.link))))
357 + }, function (err) {
358 + if(err) console.error(err)
359 + }))
360 +
361 + // list issues and pull requests
362 + pull(
363 + api.sbot_links({
364 + reverse: true,
365 + dest: msg.key,
366 + rel: 'project',
367 + values: true
368 + }),
369 + paramap(function (link, cb) {
370 + getIssueState(link.key, function (err, state) {
371 + if(err) return cb(err)
372 + link.state = state
373 + cb(null, link)
374 + })
375 + }),
376 + pull.drain(function (link) {
377 + var c = link.value.content
378 + var title = c.title || (c.text ? c.text.length > 70
379 + ? c.text.substr(0, 70) + '…'
380 + : c.text : link.key)
381 + var author = link.value.author
382 + var t = c.type === 'pull-request'
383 + ? link.state === 'open' ? openPRsT : closedPRsT
384 + : link.state === 'open' ? openIssuesT : closedIssuesT
385 + t.append(h('tr',
386 + h('td',
387 + h('a', {href: '#'+link.key}, title), h('br'),
388 + h('small',
389 + 'opened ', messageTimestampLink(link),
390 + ' by ', h('a', {href: '#'+author}, api.avatar_name(author))))))
391 + }, function (err) {
392 + if (err) console.error(err)
393 + })
394 + )
395 +
396 + // list forks
397 + pull(
398 + getForks(msg.key),
399 + pull.drain(function (fork) {
400 + forksT.append(h('tr', h('td',
401 + repoLink(fork.id),
402 + ' by ', h('a', {href: '#'+fork.author}, api.avatar_name(fork.author)))))
403 + }, function (err) {
404 + if (err) console.error(err)
405 + })
406 + )
407 +
408 + return div
379409 }
380- return h('option', {value: branch}, label)
381- }))
382- })
383-}
384410
385-function newPullRequestButton(msg) {
386- return h('div', [
387- h('a', {
388- href: '#',
389- onclick: function (e) {
390- e.preventDefault()
391- this.parentNode.replaceChild(pullRequestForm(msg), this)
392- }},
393- 'New Pull Request…'
394- )
395- ])
396-}
411 + if(c.type === 'git-update') {
412 + return [
413 + h('p', 'pushed to ', repoLink(c.repo)),
414 + c.refs ? h('ul', Object.keys(c.refs).map(function (ref) {
415 + var rev = c.refs[ref]
416 + return h('li',
417 + shortRefName(ref) + ': ',
418 + rev ? h('code', rev) : h('em', 'deleted'))
419 + })) : null,
420 + Array.isArray(c.commits) ? [
421 + h('ul',
422 + c.commits.map(function (commit) {
423 + return h('li',
424 + typeof commit.sha1 === 'string' ?
425 + [h('code', commit.sha1.substr(0, 8)), ' '] : null,
426 + commit.title ?
427 + h('q', commit.title) : null)
428 + }),
429 + c.commits_more > 0 ?
430 + h('li', '+ ', c.commits_more, ' more') : null)
431 + ] : null,
432 + Array.isArray(c.issues) ? c.issues.map(function (issue) {
433 + if (issue.merged === true)
434 + return h('p', 'Merged ', api.message_link(issue.link), ' in ',
435 + h('code', issue.object), ' ', h('q', issue.label))
436 + if (issue.open === false)
437 + return h('p', 'Closed ', api.message_link(issue.link), ' in ',
438 + h('code', issue.object), ' ', h('q', issue.label))
439 + }) : null,
440 + newPullRequestButton.call(this, msg)
441 + ]
442 + }
397443
398-function pullRequestForm(msg) {
399- var headRepoInput
400- var headBranchInput = branchMenu()
401- var branchInput = branchMenu(msg)
402- var form = h('form',
403- h('strong', 'New Pull Request:'),
404- h('div',
405- 'from ',
406- headRepoInput = combobox({
407- style: {'max-width': '26ex'},
408- onchange: function () {
409- // list branches for selected repo
410- var repoId = this.value
411- if(repoId) sbot_get(repoId, function (err, value) {
412- if(err) console.error(err)
413- var msg = value && {key: repoId, value: value}
414- headBranchInput = headBranchInput.swap(branchMenu(msg, true))
444 + if(c.type === 'issue-edit'
445 + || (c.type === 'post' && c.text === '')) {
446 + return h('div',
447 + c.issue ? renderIssueEdit(c) : null,
448 + c.issues ? c.issues.map(renderIssueEdit) : null)
449 + }
450 +
451 + if(c.type === 'issue') {
452 + return h('div',
453 + h('p', 'opened issue on ', repoLink(c.project)),
454 + c.title ? h('h4', c.title) : '',
455 + api.markdown(c)
456 + )
457 + }
458 +
459 + if(c.type === 'pull-request') {
460 + return h('div',
461 + h('p', 'opened pull-request ',
462 + 'to ', repoLink(c.repo), ':', c.branch, ' ',
463 + 'from ', repoLink(c.head_repo), ':', c.head_branch),
464 + c.title ? h('h4', c.title) : '',
465 + api.markdown(c)
466 + )
467 + }
468 + },
469 +
470 + message_meta: function (msg, sbot) {
471 + var type = msg.value.content.type
472 + if (type === 'issue' || type === 'pull-request') {
473 + var el = h('em', '...')
474 + // TODO: update if issue is changed
475 + getIssueState(msg.key, function (err, state) {
476 + if (err) return console.error(err)
477 + el.textContent = state
478 + })
479 + return el
480 + }
481 + },
482 +
483 + message_action: function (msg, sbot) {
484 + var c = msg.value.content
485 + if(c.type === 'issue' || c.type === 'pull-request') {
486 + var isOpen
487 + var a = h('a', {href: '#', onclick: function (e) {
488 + e.preventDefault()
489 + api.message_confirm({
490 + type: 'issue-edit',
491 + root: msg.key,
492 + issues: [{
493 + link: msg.key,
494 + open: !isOpen
495 + }]
496 + }, function (err, msg) {
497 + if(err) return alert(err)
498 + if(!msg) return
499 + isOpen = msg.value.content.open
500 + update()
415501 })
416- else headBranchInput = headBranchInput.swap(branchMenu())
417- },
418- read: pull(cat([
419- pull.once({id: msg.key, author: msg.value.author}),
420- getForks(msg.key)
421- ]), pull.map(function (fork) {
422- return h('option', {value: fork.id},
423- repoLink(fork.id), ' by ', avatar_name(fork.author))
424- }))
425- }),
426- ':',
427- headBranchInput,
428- ' to ',
429- repoName(msg.key),
430- ':',
431- branchInput),
432- message_compose(
433- {
434- type: 'pull-request',
435- project: msg.key,
436- repo: msg.key,
437- },
438- function (value) {
439- value.branch = branchInput.value
440- value.head_repo = headRepoInput.value
441- value.head_branch = headBranchInput.value
442- return value
443- },
444- function (err, issue) {
445- if(err) return alert(err)
446- if(!issue) return
447- var title = issue.value.content.text
448- if(title.length > 70) title = title.substr(0, 70) + '…'
449- form.appendChild(h('div',
450- h('a', {href: '#'+issue.key}, title)
451- ))
502 + }})
503 + getIssueState(msg.key, function (err, state) {
504 + if (err) return console.error(err)
505 + isOpen = state === 'open'
506 + update()
507 + })
508 + function update() {
509 + a.textContent = c.type === 'pull-request'
510 + ? isOpen ? 'Close Pull Request' : 'Reopen Pull Request'
511 + : isOpen ? 'Close Issue' : 'Reopen Issue'
512 + }
513 + return a
452514 }
453- )
454- )
455- return form
456-}
457-
458-exports.message_action = function (msg, sbot) {
459- var c = msg.value.content
460- if(c.type === 'issue' || c.type === 'pull-request') {
461- var isOpen
462- var a = h('a', {href: '#', onclick: function (e) {
463- e.preventDefault()
464- message_confirm({
465- type: 'issue-edit',
466- root: msg.key,
467- issues: [{
468- link: msg.key,
469- open: !isOpen
470- }]
471- }, function (err, msg) {
472- if(err) return alert(err)
473- if(!msg) return
474- isOpen = msg.value.content.open
475- update()
476- })
477- }})
478- getIssueState(msg.key, function (err, state) {
479- if (err) return console.error(err)
480- isOpen = state === 'open'
481- update()
482- })
483- function update() {
484- a.textContent = c.type === 'pull-request'
485- ? isOpen ? 'Close Pull Request' : 'Reopen Pull Request'
486- : isOpen ? 'Close Issue' : 'Reopen Issue'
487515 }
488- return a
489516 }
490517 }
491518
519 +
modules_extra/meta-image.jsView
@@ -4,22 +4,26 @@
44 var ref = require('ssb-ref');
55
66 //render a message
77
8-var plugs = require('../plugs');
9-var message_link = plugs.first(exports.message_link = []);
10-var message_confirm = plugs.first(exports.message_confirm = []);
11-var sbot_links = plugs.first(exports.sbot_links = []);
12-var blob_url = plugs.first(exports.blob_url = []);
8 +//var plugs = require('../plugs');
9 +//var message_link = plugs.first(exports.message_link = []);
10 +//var message_confirm = plugs.first(exports.message_confirm = []);
11 +//var sbot_links = plugs.first(exports.sbot_links = []);
12 +//var blob_url = plugs.first(exports.blob_url = []);
13 +//
14 +exports.needs = { blob_url: 'first' }
15 +exports.gives = 'message_content'
1316
14-exports.message_content = function(msg, sbot) {
17 +exports.create = function (api) {
18 + return function(msg, sbot) {
1519 if (msg.value.content.type !== 'meta-image')
1620 return;
1721
1822 var v = msg.value.content;
1923 return h('div',
2024 // h('h2', "(" + v.Track + ") " + v.Title),
21- h('img', { "src" : blob_url(v.link) }))
25 + h('img', { "src" : api.blob_url(v.link) }))
2226
2327 // h('dl',
2428 // Object.keys(v).map(function(k) {
2529 // return [
@@ -43,6 +47,6 @@
4347 // "Title": "clouds",
4448 // "Track": "2",
4549 // "Width": "0",
4650
51 + }
4752 }
48-
modules_extra/music-release-cc.jsView
@@ -4,36 +4,42 @@
44 var ref = require('ssb-ref');
55
66 //render a message
77
8-var plugs = require('../plugs');
9-var message_link = plugs.first(exports.message_link = []);
10-var message_confirm = plugs.first(exports.message_confirm = []);
11-var sbot_links = plugs.first(exports.sbot_links = []);
12-var blob_url = plugs.first(exports.blob_url = []);
8 +//var plugs = require('../plugs');
9 +//var message_link = plugs.first(exports.message_link = []);
10 +//var message_confirm = plugs.first(exports.message_confirm = []);
11 +//var sbot_links = plugs.first(exports.sbot_links = []);
12 +//var blob_url = plugs.first(exports.blob_url = []);
13 +//
1314
14-exports.message_content = function(msg, sbot) {
15- if (msg.value.content.type !== 'music-release-cc')
16- return;
15 +exports.needs = { blob_url: 'first' }
16 +exports.gives = 'message_content'
1717
18- var tracks = msg.value.content.tracks;
19- return h('div',
20- h('img', { "src" : blob_url(msg.value.content.cover) }),
21- h('h1', msg.value.content.title),
22- h('ol',
23- Object.keys(tracks).map(function(k) {
24- var t = tracks[k];
25- return h('li', t.fname,
26- h("br"),
27- h('audio', {
28- "controls" : true,
29- "src" : blob_url(t.link)
30- }))
31- })),
32- h('p',
33- "More info:", h('a', { href : msg.value.content.archivedotorg }, "archive.org"),
34- h("br"),
35- "License:", h('a', { href : msg.value.content.license }, "Link")))
18 +exports.create = function (api) {
19 + return function(msg, sbot) {
20 + if (msg.value.content.type !== 'music-release-cc')
21 + return;
22 +
23 + var tracks = msg.value.content.tracks;
24 + return h('div',
25 + h('img', { "src" : api.blob_url(msg.value.content.cover) }),
26 + h('h1', msg.value.content.title),
27 + h('ol',
28 + Object.keys(tracks).map(function(k) {
29 + var t = tracks[k];
30 + return h('li', t.fname,
31 + h("br"),
32 + h('audio', {
33 + "controls" : true,
34 + "src" : api.blob_url(t.link)
35 + }))
36 + })),
37 + h('p',
38 + "More info:", h('a', { href : msg.value.content.archivedotorg }, "archive.org"),
39 + h("br"),
40 + "License:", h('a', { href : msg.value.content.license }, "Link")))
41 + }
3642 }
3743
3844 // copied from like.js
3945
@@ -77,4 +83,5 @@
7783 // }}, 'yup')
7884
7985 // }
8086
87 +
modules_extra/music-release.jsView
@@ -4,38 +4,43 @@
44 var ref = require('ssb-ref');
55
66 //render a message
77
8-var plugs = require('../plugs');
9-var message_link = plugs.first(exports.message_link = []);
10-var message_confirm = plugs.first(exports.message_confirm = []);
11-var sbot_links = plugs.first(exports.sbot_links = []);
8 +//var plugs = require('../plugs');
9 +//var message_link = plugs.first(exports.message_link = []);
10 +//var message_confirm = plugs.first(exports.message_confirm = []);
11 +//var sbot_links = plugs.first(exports.sbot_links = []);
12 +//
13 +exports.gives = 'message_content'
1214
13-exports.message_content = function(msg, sbot) {
14- if (msg.value.content.type !== 'music-release')
15- return;
15 +exports.create = function () {
1616
17- var v = msg.value.content;
18- return h('div',
19- // h('img', { "src" : "http://localhost:7777/" + encodeURIComponent(v.cover) }),
20- h('h1', v.Title),
21- h("p", v.Description),
22- h("dl",
17 + return function(msg, sbot) {
18 + if (msg.value.content.type !== 'music-release')
19 + return;
2320
24- h("dt", "Creator"),
25- h("dd", v.Creator),
21 + var v = msg.value.content;
22 + return h('div',
23 + // h('img', { "src" : "http://localhost:7777/" + encodeURIComponent(v.cover) }),
24 + h('h1', v.Title),
25 + h("p", v.Description),
26 + h("dl",
2627
27- h("dt", "Identifier"),
28- h("dd", v.Identifier),
28 + h("dt", "Creator"),
29 + h("dd", v.Creator),
2930
30- h("dt", "Published"),
31- h("dd", v.Publicdate),
31 + h("dt", "Identifier"),
32 + h("dd", v.Identifier),
3233
33- h("dt", "Runtime"),
34- h("dd", v.Runtime),
34 + h("dt", "Published"),
35 + h("dd", v.Publicdate),
3536
36- h("dt", "Source"),
37- h("dd", v.Source),
37 + h("dt", "Runtime"),
38 + h("dd", v.Runtime),
3839
39- h("dt", "License"),
40- h("dd", h('a', { href : v.Licenseurl }, "Link"))))
40 + h("dt", "Source"),
41 + h("dd", v.Source),
42 +
43 + h("dt", "License"),
44 + h("dd", h('a', { href : v.Licenseurl }, "Link"))))
45 + }
4146 }
modules_extra/network.jsView
@@ -1,11 +1,23 @@
11 var isVisible = require('is-visible').isVisible
22 var h = require('hyperscript')
3-var plugs = require('../plugs')
43
5-var avatar = plugs.first(exports.avatar = [])
6-var sbot_gossip_peers = plugs.first(exports.sbot_gossip_peers = [])
7-var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = [])
4 +//var avatar = plugs.first(exports.avatar = [])
5 +//var sbot_gossip_peers = plugs.first(exports.sbot_gossip_peers = [])
6 +//var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = [])
7 +
8 +exports.needs = {
9 + avatar: 'first',
10 + sbot_gossip_peers: 'first',
11 + sbot_gossip_connect: 'first'
12 +}
13 +
14 +exports.gives = {
15 + menu_items: true,
16 + builtin_tabs: true,
17 + screen_view: true
18 +}
19 +
820 //sbot_gossip_connect
921 //sbot_gossip_add
1022
1123 var human = require('human-time')
@@ -13,16 +25,8 @@
1325 function legacyToMultiServer(addr) {
1426 return 'net:'+addr.host + ':'+addr.port + '~shs:'+addr.key.substring(1).replace('.ed25519','')
1527 }
1628
17-exports.menu_items = function () {
18- return h('a', {href: '#/network'}, '/network')
19-}
20-
21-exports.builtin_tabs = function () {
22- return ['/network']
23-}
24-
2529 //types of peers
2630
2731
2832 //on the same wifi network
@@ -64,8 +68,12 @@
6468 : 'other' //should never happen
6569 )
6670 }
6771
72 +function origin (e) {
73 + return e.source === 'local' ? 0 : 1
74 +}
75 +
6876 var states = {
6977 connected: 3,
7078 connecting: 2
7179 }
@@ -91,62 +99,78 @@
9199 else
92100 return round(s)+'ms'
93101 }
94102
95-exports.screen_view = function (path) {
96103
97- if(path !== '/network') return
98104
99- var ol = h('ul.network')
105 +exports.create = function (api) {
100106
101- ;(function poll () {
107 + return {
108 + menu_items: function () {
109 + return h('a', {href: '#/network'}, '/network')
110 + },
102111
103- //if this tab isn't open, don't update.
104- //todo: make a better way to do this...
105- if(!isVisible(ol))
106- return setTimeout(poll, 1000)
112 + builtin_tabs: function () {
113 + return ['/network']
114 + },
107115
108- sbot_gossip_peers(function (err, list) {
109- ol.innerHTML = ''
110- list.sort(function (a, b) {
111- return (
112- (states[b.state] || 0) - (states[a.state] || 0)
113- || types[getType(b)] - types[getType(a)]
114- || b.stateChange - a.stateChange
115- )
116- }).forEach(function (peer) {
117- ol.appendChild(h('div',
118- avatar(peer.key, 'thumbnail'),
119- h('div',
120- peer.state || 'not connected',
121- ' ',
122- getType(peer),
123- ' ',
124- //TODO: show nicer details, with labels. etc.
125- (peer.ping && peer.ping.rtt) ? duration(peer.ping.rtt.mean) : '',
126- ' ',
127- (peer.ping && peer.ping.skew) ? duration(peer.ping.skew.mean) : '',
128- h('label',
129- {title: new Date(peer.stateChange).toString()},
130- peer.stateChange && ('(' + human(new Date(peer.stateChange))) + ')')
131- ),
132- 'source:'+peer.source,
133- h('pre', legacyToMultiServer(peer)),
134- h('button', 'connect', {onclick: function () {
135- sbot_gossip_connect(peer, function (err) {
136- if(err) console.error(err)
137- else console.log('connected to', peer)
138- })
139- }})
140- )
141- )
142- })
116 + screen_view: function (path) {
143117
144- setTimeout(poll, 5000)
145- })
118 + if(path !== '/network') return
146119
147- })()
120 + var ol = h('ul.network')
148121
149- return h('div.column.scroll-y', ol)
122 + ;(function poll () {
123 +
124 + //if this tab isn't open, don't update.
125 + //todo: make a better way to do this...
126 + if(!isVisible(ol))
127 + return setTimeout(poll, 1000)
128 +
129 + api.sbot_gossip_peers(function (err, list) {
130 + ol.innerHTML = ''
131 + list.sort(function (a, b) {
132 + return (
133 + (states[b.state] || 0) - (states[a.state] || 0)
134 + || origin(b) - origin(a)
135 + || types[getType(b)] - types[getType(a)]
136 + || b.stateChange - a.stateChange
137 + )
138 + }).forEach(function (peer) {
139 + ol.appendChild(h('div',
140 + api.avatar(peer.key, 'thumbnail'),
141 + h('div',
142 + peer.state || 'not connected',
143 + ' ',
144 + getType(peer),
145 + ' ',
146 + //TODO: show nicer details, with labels. etc.
147 + (peer.ping && peer.ping.rtt) ? duration(peer.ping.rtt.mean) : '',
148 + ' ',
149 + (peer.ping && peer.ping.skew) ? duration(peer.ping.skew.mean) : '',
150 + h('label',
151 + {title: new Date(peer.stateChange).toString()},
152 + peer.stateChange && ('(' + human(new Date(peer.stateChange))) + ')')
153 + ),
154 + 'source:'+peer.source,
155 + h('pre', legacyToMultiServer(peer)),
156 + h('button', 'connect', {onclick: function () {
157 + api.sbot_gossip_connect(peer, function (err) {
158 + if(err) console.error(err)
159 + else console.log('connected to', peer)
160 + })
161 + }})
162 + )
163 + )
164 + })
165 +
166 + setTimeout(poll, 5000)
167 + })
168 +
169 + })()
170 +
171 + return h('div.column.scroll-y', ol)
172 + }
173 + }
150174 }
151175
152176
modules_extra/notifications.jsView
@@ -1,4 +1,5 @@
1 +'use strict'
12 var h = require('hyperscript')
23 var u = require('../util')
34 var pull = require('pull-stream')
45 var Scroller = require('pull-scroll')
@@ -6,150 +7,164 @@
67 var plugs = require('../plugs')
78 var cont = require('cont')
89 var ref = require('ssb-ref')
910
10-var message_render = plugs.first(exports.message_render = [])
11-var sbot_log = plugs.first(exports.sbot_log = [])
12-var sbot_get = plugs.first(exports.sbot_get = [])
13-var sbot_user_feed = plugs.first(exports.sbot_user_feed = [])
14-var message_unbox = plugs.first(exports.message_unbox = [])
11 +//var message_render = plugs.first(exports.message_render = [])
12 +//var sbot_log = plugs.first(exports.sbot_log = [])
13 +//var sbot_get = plugs.first(exports.sbot_get = [])
14 +//var sbot_user_feed = plugs.first(exports.sbot_user_feed = [])
15 +//var message_unbox = plugs.first(exports.message_unbox = [])
1516
16-function unbox() {
17- return pull(
18- pull.map(function (msg) {
19- return msg.value && 'string' === typeof msg.value.content ?
20- message_unbox(msg) : msg
21- }),
22- pull.filter(Boolean)
23- )
17 +exports.needs = {
18 + message_render: 'first',
19 + sbot_log: 'first',
20 + sbot_get: 'first',
21 + sbot_user_feed: 'first',
22 + message_unbox: 'first'
2423 }
2524
26-exports.builtin_tabs = function () {
27- return ['/notifications']
25 +
26 +exports.gives = {
27 + builtin_tabs: true,
28 + screen_view: true
2829 }
2930
30-function notifications(ourIds) {
31-
32- function linksToUs(link) {
33- return link && link.link in ourIds
31 +exports.create = function (api) {
32 + function unbox() {
33 + return pull(
34 + pull.map(function (msg) {
35 + return msg.value && 'string' === typeof msg.value.content ?
36 + api.message_unbox(msg) : msg
37 + }),
38 + pull.filter(Boolean)
39 + )
3440 }
3541
36- function isOurMsg(id, cb) {
37- if (!id) return cb(null, false)
38- if (typeof id === 'object' && typeof id.link === 'string') id = id.link
39- if (!ref.isMsg(id)) return cb(null, false)
40- sbot_get(id, function (err, msg) {
41- if (err && err.name == 'NotFoundError') cb(null, false)
42- else if (err) cb(err)
43- else if (msg.content.type === 'issue' || msg.content.type === 'pull-request')
44- isOurMsg(msg.content.repo || msg.content.project, cb)
45- else cb(err, msg.author in ourIds)
46- })
47- }
42 + function notifications(ourIds) {
4843
49- function isAnyOurMessage(msg, ids, cb) {
50- cont.para(ids.map(function (id) {
51- return function (cb) { isOurMsg(id, cb) }
52- }))
53- (function (err, results) {
54- if (err) cb(err)
55- else if (results.some(Boolean)) cb(null, msg)
56- else cb()
57- })
58- }
44 + function linksToUs(link) {
45 + return link && link.link in ourIds
46 + }
5947
60- return paramap(function (msg, cb) {
61- var c = msg.value && msg.value.content
62- if (!c || typeof c !== 'object') return cb()
63- if (msg.value.author in ourIds) return cb()
48 + function isOurMsg(id, cb) {
49 + if (!id) return cb(null, false)
50 + if (typeof id === 'object' && typeof id.link === 'string') id = id.link
51 + if (!ref.isMsg(id)) return cb(null, false)
52 + api.sbot_get(id, function (err, msg) {
53 + if (err && err.name == 'NotFoundError') cb(null, false)
54 + else if (err) cb(err)
55 + else if (msg.content.type === 'issue' || msg.content.type === 'pull-request')
56 + isOurMsg(msg.content.repo || msg.content.project, cb)
57 + else cb(err, msg.author in ourIds)
58 + })
59 + }
6460
65- if (c.mentions && Array.isArray(c.mentions) && c.mentions.some(linksToUs))
66- return cb(null, msg)
61 + function isAnyOurMessage(msg, ids, cb) {
62 + cont.para(ids.map(function (id) {
63 + return function (cb) { isOurMsg(id, cb) }
64 + }))
65 + (function (err, results) {
66 + if (err) cb(err)
67 + else if (results.some(Boolean)) cb(null, msg)
68 + else cb()
69 + })
70 + }
6771
68- if (msg.private)
69- return cb(null, msg)
72 + return paramap(function (msg, cb) {
73 + var c = msg.value && msg.value.content
74 + if (!c || typeof c !== 'object') return cb()
75 + if (msg.value.author in ourIds) return cb()
7076
71- switch (c.type) {
72- case 'post':
73- if (c.branch || c.root)
74- return isAnyOurMessage(msg, [].concat(c.branch, c.root), cb)
75- else return cb()
77 + if (c.mentions && Array.isArray(c.mentions) && c.mentions.some(linksToUs))
78 + return cb(null, msg)
7679
77- case 'contact':
78- return cb(null, c.contact in ourIds ? msg : null)
80 + if (msg.private)
81 + return cb(null, msg)
7982
80- case 'vote':
81- if (c.vote && c.vote.link)
82- return isOurMsg(c.vote.link, function (err, isOurs) {
83- cb(err, isOurs ? msg : null)
84- })
83 + switch (c.type) {
84 + case 'post':
85 + if (c.branch || c.root)
86 + return isAnyOurMessage(msg, [].concat(c.branch, c.root), cb)
8587 else return cb()
8688
87- case 'issue':
88- case 'pull-request':
89- return isOurMsg(c.project || c.repo, function (err, isOurs) {
90- cb(err, isOurs ? msg : null)
91- })
89 + case 'contact':
90 + return cb(null, c.contact in ourIds ? msg : null)
9291
93- case 'issue-edit':
94- return isAnyOurMessage(msg, [c.issue].concat(c.issues), cb)
92 + case 'vote':
93 + if (c.vote && c.vote.link)
94 + return isOurMsg(c.vote.link, function (err, isOurs) {
95 + cb(err, isOurs ? msg : null)
96 + })
97 + else return cb()
9598
96- default:
97- cb()
98- }
99- }, 4)
100-}
99 + case 'issue':
100 + case 'pull-request':
101 + return isOurMsg(c.project || c.repo, function (err, isOurs) {
102 + cb(err, isOurs ? msg : null)
103 + })
101104
102-function getFirstMessage(feedId, cb) {
103- sbot_user_feed({id: feedId, gte: 0, limit: 1})(null, cb)
104-}
105 + case 'issue-edit':
106 + return isAnyOurMessage(msg, [c.issue].concat(c.issues), cb)
105107
106-exports.screen_view = function (path) {
107- if(path === '/notifications') {
108- var ids = {}
109- var oldest
110-
111- var id = require('../keys').id
112- ids[id] = true
113- getFirstMessage(id, function (err, msg) {
114- if (err) return console.error(err)
115- if (!oldest || msg.value.timestamp < oldest) {
116- oldest = msg.value.timestamp
108 + default:
109 + cb()
117110 }
118- })
111 + }, 4)
112 + }
119113
120- var content = h('div.column.scroller__content')
121- var div = h('div.column.scroller',
122- {style: {'overflow':'auto'}},
123- h('div.scroller__wrapper',
124- content
125- )
126- )
114 + function getFirstMessage(feedId, cb) {
115 + api.sbot_user_feed({id: feedId, gte: 0, limit: 1})(null, cb)
116 + }
127117
128- pull(
129- u.next(sbot_log, {old: false, limit: 100}),
130- unbox(),
131- notifications(ids),
132- pull.filter(),
133- Scroller(div, content, message_render, true, false)
134- )
118 + return {
119 + builtin_tabs: function () {
120 + return ['/notifications']
121 + },
135122
136- pull(
137- u.next(sbot_log, {reverse: true, limit: 100, live: false}),
138- unbox(),
139- notifications(ids),
140- pull.filter(),
141- pull.take(function (msg) {
142- // abort stream after we pass the oldest messages of our feeds
143- return !oldest || msg.value.timestamp > oldest
144- }),
145- Scroller(div, content, message_render, false, false)
146- )
123 + screen_view: function (path) {
124 + if(path === '/notifications') {
125 + var ids = {}
126 + var oldest
147127
148- return div
149- }
150-}
128 + var id = require('../keys').id
129 + ids[id] = true
130 + getFirstMessage(id, function (err, msg) {
131 + if (err) return console.error(err)
132 + if (!oldest || msg.value.timestamp < oldest) {
133 + oldest = msg.value.timestamp
134 + }
135 + })
151136
137 + var content = h('div.column.scroller__content')
138 + var div = h('div.column.scroller',
139 + {style: {'overflow':'auto'}},
140 + h('div.scroller__wrapper',
141 + content
142 + )
143 + )
152144
145 + pull(
146 + u.next(api.sbot_log, {old: false, limit: 100}),
147 + unbox(),
148 + notifications(ids),
149 + pull.filter(),
150 + Scroller(div, content, api.message_render, true, false)
151 + )
153152
153 + pull(
154 + u.next(api.sbot_log, {reverse: true, limit: 100, live: false}),
155 + unbox(),
156 + notifications(ids),
157 + pull.filter(),
158 + pull.take(function (msg) {
159 + // abort stream after we pass the oldest messages of our feeds
160 + return !oldest ? true : msg.value.timestamp > oldest
161 + }),
162 + Scroller(div, content, api.message_render, false, false)
163 + )
154164
165 + return div
166 + }
167 + }
168 + }
169 +}
155170
modules_extra/query.jsView
@@ -1,60 +1,66 @@
11 var h = require('hyperscript')
22 var pull = require('pull-stream')
33 var HJSON = require('hjson')
44
5-var sbot_query = require('../plugs').first(exports.sbot_query = [])
5 +//var sbot_query = require('../plugs').first(exports.sbot_query = [])
66
7-exports.menu_items = function () {
8- return h('a', {href:'#/query'}, '/query')
9-}
7 +exports.needs = { sbot_query: 'first' }
108
11-exports.builtin_tabs = function () {
12- return ['/query']
9 +exports.gives = {
10 + menu_items: true,
11 + builtin_tabs: true,
12 + screen_view: true
1313 }
1414
15-exports.screen_view = function (path) {
16- if(path != '/query') return
17- var output, status, editor, stream, query
15 +exports.create = function (api) {
1816
19- function parse () {
20- try {
21- query = HJSON.parse(editor.value)
22- } catch (err) {
23- return status.textContent = err.message
24- }
25- status.textContent = 'okay'
26- }
17 + return {
18 + menu_items: function () {
19 + return h('a', {href:'#/query'}, '/query')
20 + },
2721
28- return h('div.column.scroll',
29- editor = h('textarea', {style: 'min-height:100px;', oninput: parse, onkeydown: function (e) {
30- if(!(e.keyCode === 13 && e.ctrlKey)) return
22 + builtin_tabs: function () {
23 + return ['/query']
24 + },
25 +
26 + screen_view: function (path) {
27 + if(path != '/query') return
28 + var output, status, editor, stream, query
29 +
30 + function parse () {
31 + try {
32 + query = HJSON.parse(editor.value)
33 + } catch (err) {
34 + return status.textContent = err.message
35 + }
36 + status.textContent = 'okay'
37 + }
38 +
39 + return h('div.column.scroll',
40 + editor = h('textarea', {style: 'min-height:100px;', oninput: parse, onkeydown: function (e) {
41 + if(!(e.keyCode === 13 && e.ctrlKey)) return
3142
32- status.textContent = 'running...'
33- parse()
34- output.innerHTML = ''
35- if(stream) stream.abort()
43 + status.textContent = 'running...'
44 + parse()
45 + output.innerHTML = ''
46 + if(stream) stream.abort()
3647
37- console.log(query)
48 + console.log(query)
3849
39- stream = pull(
40- sbot_query({query: query, limit: 100}),
41- pull.drain(function (data) {
42- output.appendChild(h('pre.query__data',
43- JSON.stringify(data, null, 2)
44- ))
45- }, function (err) {
46- if(err) status.textContent = err.stack
47- })
50 + stream = pull(
51 + api.sbot_query({query: query, limit: 100}),
52 + pull.drain(function (data) {
53 + output.appendChild(h('pre.query__data',
54 + JSON.stringify(data, null, 2)
55 + ))
56 + }, function (err) {
57 + if(err) status.textContent = err.stack
58 + })
59 + )
60 + }}),
61 + status = h('div.query__status'),
62 + output = h('div.column.query__output', {style: 'overflow-y: scroll;'})
4863 )
49- }}),
50- status = h('div.query__status'),
51- output = h('div.column.query__output', {style: 'overflow-y: scroll;'})
52- )
64 + }
65 + }
5366 }
54-
55-
56-
57-
58-
59-
60-
modules_extra/raw.jsView
@@ -3,50 +3,53 @@
33 var pull = require('pull-stream')
44 var Scroller = require('pull-scroll')
55
66 var plugs = require('../plugs')
7-var message_render = plugs.first(exports.message_render = [])
8-var message_compose = plugs.first(exports.message_compose = [])
7 +//var message_render = plugs.first(exports.message_render = [])
8 +//var message_compose = plugs.first(exports.message_compose = [])
99
1010 // from ssb-ref
1111 var refRegex = /((?:@|%|&)[A-Za-z0-9\/+]{43}=\.[\w\d]+)/g
1212
13-exports.linkify = function (text) {
13 +exports.gives = 'message_meta'
14 +
15 +function linkify (text) {
1416 var arr = text.split(refRegex)
1517 for (var i = 1; i < arr.length; i += 2) {
1618 arr[i] = h('a', {href: '#' + arr[i]}, arr[i])
1719 }
1820 return arr
1921 }
2022
21-exports.message_meta = function (msg) {
22- var tmp = h('div')
23- var el
24- var pre
25- return h('input', {
26- type: 'checkbox',
27- title: 'View Data',
28- onclick: function () {
29- var msgEl = this.parentNode.parentNode.parentNode
30- var msgContentEl = msgEl.querySelector('.message_content')
31- if (this.checked) {
32- // move away the content
33- while (el = msgContentEl.firstChild)
34- tmp.appendChild(el)
35- // show the raw stuff
36- if (!pre) pre = h('pre', exports.linkify(JSON.stringify({
37- key: msg.key,
38- value: msg.value
39- }, 0, 2)))
40- msgContentEl.appendChild(pre)
41- } else {
42- // hide the raw stuff
43- msgContentEl.removeChild(pre)
44- // put back the content
45- while (el = tmp.firstChild)
46- msgContentEl.appendChild(el)
23 +exports.create = function (api) {
24 + return function (msg) {
25 + var tmp = h('div')
26 + var el
27 + var pre
28 + return h('input', {
29 + type: 'checkbox',
30 + title: 'View Data',
31 + onclick: function () {
32 + var msgEl = this.parentNode.parentNode.parentNode
33 + var msgContentEl = msgEl.querySelector('.message_content')
34 + if (this.checked) {
35 + // move away the content
36 + while (el = msgContentEl.firstChild)
37 + tmp.appendChild(el)
38 + // show the raw stuff
39 + if (!pre) pre = h('pre', linkify(JSON.stringify({
40 + key: msg.key,
41 + value: msg.value
42 + }, 0, 2)))
43 + msgContentEl.appendChild(pre)
44 + } else {
45 + // hide the raw stuff
46 + msgContentEl.removeChild(pre)
47 + // put back the content
48 + while (el = tmp.firstChild)
49 + msgContentEl.appendChild(el)
50 + }
4751 }
48- }
49- })
52 + })
53 + }
5054 }
5155
52-
modules_extra/search.jsView
@@ -3,11 +3,19 @@
33 var pull = require('pull-stream')
44 var Scroller = require('pull-scroll')
55 var TextNodeSearcher = require('text-node-searcher')
66
7-var plugs = require('../plugs')
8-var message_render = plugs.first(exports.message_render = [])
9-var sbot_log = plugs.first(exports.sbot_log = [])
7 +//var plugs = require('../plugs')
8 +//var message_render = plugs.first(exports.message_render = [])
9 +//var sbot_log = plugs.first(exports.sbot_log = [])
10 +
11 +exports.needs = {
12 + message_render: 'first',
13 + sbot_log: 'first'
14 +}
15 +
16 +exports.gives = 'screen_view'
17 +
1018 var whitespace = /\s+/
1119
1220 function andSearch(terms, inputs) {
1321 for(var i = 0; i < terms.length; i++) {
@@ -45,61 +53,56 @@
4553 searcher.highlight()
4654 return el
4755 }
4856
49-exports.screen_view = function (path) {
50- if(path[0] === '?') {
51- var query = path.substr(1).trim().split(whitespace)
52- var _matches = searchFilter(query)
57 +exports.create = function (api) {
5358
54- var total = 0, matches = 0
59 + return function (path) {
60 + if(path[0] === '?') {
61 + var query = path.substr(1).trim().split(whitespace)
62 + var _matches = searchFilter(query)
5563
56- var header = h('div.search_header', '')
57- var content = h('div.column.scroller__content')
58- var div = h('div.column.scroller',
59- {style: {'overflow':'auto'}},
60- h('div.scroller__wrapper',
61- header,
62- content
64 + var total = 0, matches = 0
65 +
66 + var header = h('div.search_header', '')
67 + var content = h('div.column.scroller__content')
68 + var div = h('div.column.scroller',
69 + {style: {'overflow':'auto'}},
70 + h('div.scroller__wrapper',
71 + header,
72 + content
73 + )
6374 )
64- )
6575
66- function matchesQuery (data) {
67- total++
68- var m = _matches(data)
69- if(m) matches++
70- header.textContent = 'searched:'+total+', found:'+matches
71- return m
72- }
76 + function matchesQuery (data) {
77 + total++
78 + var m = _matches(data)
79 + if(m) matches++
80 + header.textContent = 'searched:'+total+', found:'+matches
81 + return m
82 + }
7383
7484
7585
76- function renderMsg(msg) {
77- var el = message_render(msg)
78- highlight(el, createOrRegExp(query))
79- return el
80- }
86 + function renderMsg(msg) {
87 + var el = api.message_render(msg)
88 + highlight(el, createOrRegExp(query))
89 + return el
90 + }
8191
82- pull(
83- sbot_log({old: false}),
84- pull.filter(matchesQuery),
85- Scroller(div, content, renderMsg, true, false)
86- )
92 + pull(
93 + api.sbot_log({old: false}),
94 + pull.filter(matchesQuery),
95 + Scroller(div, content, renderMsg, true, false)
96 + )
8797
88- pull(
89- u.next(sbot_log, {reverse: true, limit: 500, live: false}),
90- pull.filter(matchesQuery),
91- Scroller(div, content, renderMsg, false, false)
92- )
98 + pull(
99 + u.next(api.sbot_log, {reverse: true, limit: 500, live: false}),
100 + pull.filter(matchesQuery),
101 + Scroller(div, content, renderMsg, false, false)
102 + )
93103
94- return div
104 + return div
105 + }
95106 }
107 +
96108 }
97-
98-
99-
100-
101-
102-
103-
104-
105-
modules_extra/split.jsView
@@ -1,17 +1,27 @@
11 var h = require('hyperscript')
22
3-var screen_view =
4- require('../plugs').first(exports._screen_view = [])
3 +//var screen_view =
4 +// require('../plugs').first(exports._screen_view = [])
55
6-exports.screen_view = function (path) {
7- var m = /^split\s*\((.*)\)$/.exec(path)
8- if(!m)
9- return
106
11- return h('div.row',
12- m[1].split(',').map(function (e) {
13- return screen_view(e.trim())
14- }).filter(Boolean)
15- )
7 +exports.needs = {
8 + screen_view: 'first'
169 }
1710
11 +exports.gives = 'screen_view'
12 +
13 +exports.create = function (api) {
14 +
15 + return function (path) {
16 + var m = /^split\s*\((.*)\)$/.exec(path)
17 + if(!m)
18 + return
19 +
20 + return h('div.row',
21 + m[1].split(',').map(function (e) {
22 + return api.screen_view(e.trim())
23 + }).filter(Boolean)
24 + )
25 + }
26 +
27 +}
modules_extra/suggest-emoji.jsView
@@ -1,19 +1,32 @@
1-var plugs = require('../plugs')
2-var emoji_url = plugs.first(exports.emoji_url = [])
3-var emoji_names = plugs.first(exports.emoji_names = [])
1 +//var plugs = require('../plugs')
2 +//var emoji_url = plugs.first(exports.emoji_url = [])
3 +//var emoji_names = plugs.first(exports.emoji_names = [])
44
5-exports.suggest_mentions = function (word, cb) {
6- if (word[0] !== ':' || word.length < 2) return cb()
7- word = word.substr(1)
8- if (word[word.length-1] === ':') word = word.substr(0, word.length-1)
9- cb(null, emoji_names().filter(function (name) {
10- return name.substr(0, word.length) === word
11- }).slice(0, 50).map(function (emoji) {
12- return {
13- image: emoji_url(emoji),
14- title: emoji,
15- subtitle: emoji,
16- value: ':' + emoji + ':'
5 +exports.needs = {
6 + emoji_url: 'first',
7 + emoji_names: 'first'
8 +}
9 +
10 +exports.gives = 'suggest_mentions'
11 +
12 +exports.create = function (api) {
13 +
14 + return function (word) {
15 + return function (cb) {
16 + if (word[0] !== ':' || word.length < 2) return cb()
17 + word = word.substr(1)
18 + if (word[word.length-1] === ':') word = word.substr(0, word.length-1)
19 + cb(null, api.emoji_names().filter(function (name) {
20 + return name.substr(0, word.length) === word
21 + }).slice(0, 50).map(function (emoji) {
22 + return {
23 + image: api.emoji_url(emoji),
24 + title: emoji,
25 + subtitle: emoji,
26 + value: ':' + emoji + ':'
27 + }
28 + }))
1729 }
18- }))
30 + }
31 +
1932 }
modules_extra/versions.jsView
@@ -1,40 +1,50 @@
11 var h = require('hyperscript')
22
3-exports.menu_items = function () {
4- return h('a', {href: '#/versions'}, '/versions')
3 +exports.gives = {
4 + menu_items: true,
5 + builtin_tabs: true,
6 + screen_view: true
57 }
68
7-exports.builtin_tabs = function () {
8- return ['/versions']
9-}
9 +exports.create = function () {
1010
11-exports.screen_view = function (path) {
12- if(path !== '/versions') return
11 + return {
12 + menu_items: function () {
13 + return h('a', {href: '#/versions'}, '/versions')
14 + },
1315
14- if('undefined' === typeof WebBoot)
15- return h('h1', 'must run with web-boot enabled enviroment')
16 + builtin_tabs: function () {
17 + return ['/versions']
18 + },
1619
17- var content = h('div.column')
20 + screen_view: function (path) {
21 + if(path !== '/versions') return
1822
19- WebBoot.versions(function (err, log) {
20- log.forEach(function (e, i) {
21- content.appendChild(
22- h('div.row',
23- h('a', {
24- href: '#/run:'+e.value,
25- onclick: function () {
26- WebBoot.run(e.value, function () {
27- console.log('rebooting to:', e.value)
28- })
29- }
30- }, ' ', e.value, ' ', new Date(e.ts)),
31- !i && h('label', '(current)')
32- )
33- )
34- })
23 + if('undefined' === typeof WebBoot)
24 + return h('h1', 'must run with web-boot enabled enviroment')
3525
36- })
26 + var content = h('div.column')
3727
38- return content
28 + WebBoot.versions(function (err, log) {
29 + log.forEach(function (e, i) {
30 + content.appendChild(
31 + h('div.row',
32 + h('a', {
33 + href: '#/run:'+e.value,
34 + onclick: function () {
35 + WebBoot.run(e.value, function () {
36 + console.log('rebooting to:', e.value)
37 + })
38 + }
39 + }, ' ', e.value, ' ', new Date(e.ts)),
40 + !i && h('label', '(current)')
41 + )
42 + )
43 + })
44 +
45 + })
46 +
47 + return content
48 + }
49 + }
3950 }
40-
package.jsonView
@@ -1,26 +1,26 @@
11 {
22 "name": "patchbay",
33 "description": "a pluggable patchwork",
4- "version": "5.5.0",
4 + "version": "6.0.1",
55 "homepage": "https://github.com/dominictarr/patchbay",
66 "repository": {
77 "type": "git",
88 "url": "git://github.com/dominictarr/patchbay.git"
99 },
1010 "dependencies": {
1111 "cont": "^1.0.3",
1212 "dataurl-": "^0.1.0",
13- "depject": "^2.0.0",
13 + "depject": "^3.0.0",
1414 "hjson": "^2.0.3",
1515 "human-time": "0.0.1",
1616 "hypercombo": "0.1.0",
1717 "hypercrop": "^1.0.1",
1818 "hyperfile": "^1.1.0",
1919 "hyperlightbox": "^0.1.3",
2020 "hyperprogress": "0.1.0",
2121 "hyperscript": "^1.4.7",
22- "hypertabs": "^2.2.0",
22 + "hypertabs": "^3.0.0",
2323 "is-visible": "^2.0.4",
2424 "kvgraph": "^0.1.0",
2525 "map-filter-reduce": "^3.0.1",
2626 "mime-types": "^2.1.11",
@@ -36,10 +36,10 @@
3636 "pull-stream": "^3.4.5",
3737 "scuttlebot": "^8.7.2",
3838 "simple-mime": "^0.1.0",
3939 "split-buffer": "^1.0.0",
40 + "ssb-avatar": "^0.2.0",
4041 "ssb-blobs": "^0.1.7",
41- "ssb-avatar": "^0.2.0",
4242 "ssb-client": "^4.0.3",
4343 "ssb-config": "^2.1.1",
4444 "ssb-feed": "^2.2.1",
4545 "ssb-git": "^0.4.1",

Built with git-ssb-web