Commit 2264a334a0bf3da0cda5d13e61903a94651feafe
Merge pull request #69 from ssbc/message_modules_folder
[WIP] ideal layout for nested message modulesmix irving authored on 1/22/2017, 4:04:13 AM
GitHub committed on 1/22/2017, 4:04:13 AM
Parent: d45e0ad160051c97802cf3566af0ab967fcc0c0e
Parent: 6e6e6af8b885b2550db8d474e3eb01894db7e5e3
Files changed
embedded.js | ||
---|---|---|
@@ -1,9 +1,12 @@ | ||
1 … | +// polyfills | |
2 … | +require('setimmediate') | |
3 … | + | |
1 | 4 … | require('depject')( |
2 | 5 … | require('./modules_embedded'), |
3 | 6 … | require('./modules_basic'), |
4 | 7 … | require('./modules_extra') |
5 | -).plugs.app[0]() | |
8 … | +).app[0]() | |
6 | 9 … | |
7 | 10 … | |
8 | 11 … | |
9 | 12 … |
modules_basic/index.js | ||
---|---|---|
@@ -1,33 +1,22 @@ | ||
1 | 1 … | module.exports = { |
2 | - "about.js": require('./about.js'), | |
3 | - "avatar-edit.js": require('./avatar-edit.js'), | |
4 | - "avatar-image.js": require('./avatar-image.js'), | |
5 | - "avatar-link.js": require('./avatar-link.js'), | |
6 | - "avatar-name.js": require('./avatar-name.js'), | |
7 | - "avatar-profile.js": require('./avatar-profile.js'), | |
8 | - "avatar.js": require('./avatar.js'), | |
9 | - "compose.js": require('./compose.js'), | |
10 | - "feed.js": require('./feed.js'), | |
11 | - "follow.js": require('./follow.js'), | |
12 | - "invite.js": require('./invite.js'), | |
13 | - "like.js": require('./like.js'), | |
14 | - "markdown.js": require('./markdown.js'), | |
15 | - "message-author.js": require('./message-author.js'), | |
16 | - "message-backlinks.js": require('./message-backlinks.js'), | |
17 | - "message-link.js": require('./message-link.js'), | |
18 | - "message-name.js": require('./message-name.js'), | |
19 | - "message.js": require('./message.js'), | |
20 | - "names.js": require('./names.js'), | |
21 | - "post.js": require('./post.js'), | |
22 | - "private.js": require('./private.js'), | |
23 | - "pub.js": require('./pub.js'), | |
24 | - "public.js": require('./public.js'), | |
25 | - "relationships.js": require('./relationships.js'), | |
26 | - "reply.js": require('./reply.js'), | |
27 | - "search-box.js": require('./search-box.js'), | |
28 | - "setup.js": require('./setup'), | |
29 | - "suggest-mentions.js": require('./suggest-mentions.js'), | |
30 | - "thread.js": require('./thread.js'), | |
31 | - "timestamp.js": require('./timestamp.js') | |
2 … | + 'about': require('./about'), | |
3 … | + 'avatar': require('./avatar'), | |
4 … | + 'compose': require('./compose'), | |
5 … | + 'emoji': require('./emoji'), | |
6 … | + 'feed': require('./feed'), | |
7 … | + 'follow': require('./follow'), | |
8 … | + 'invite': require('./invite'), | |
9 … | + 'like': require('./like'), | |
10 … | + 'markdown': require('./markdown'), | |
11 … | + 'message': require('./message'), | |
12 … | + 'post': require('./post'), | |
13 … | + 'private': require('./private'), | |
14 … | + 'pub': require('./pub'), | |
15 … | + 'public': require('./public'), | |
16 … | + 'relationships': require('./relationships'), | |
17 … | + 'reply': require('./reply'), | |
18 … | + 'setup': require('./setup'), | |
19 … | + 'thread': require('./thread'), | |
20 … | + 'timestamp': require('./timestamp') | |
32 | 21 … | } |
33 | 22 … |
modules_basic/relationships.js | ||
---|---|---|
@@ -1,17 +1,19 @@ | ||
1 | 1 … | var pull = require('pull-stream') |
2 | 2 … | |
3 … | +//this is a bit crude, and doesn't actually show unfollows yet. | |
4 … | + | |
3 | 5 … | function makeQuery (a, b) { |
4 | 6 … | return {"$filter": { |
5 | - value: { | |
6 | - author: a, | |
7 | - content: { | |
8 | - type: 'contact', | |
9 | - contact: b, | |
10 | - following: true | |
11 | - } | |
12 | - }, | |
13 | - }} | |
7 … | + value: { | |
8 … | + author: a, | |
9 … | + content: { | |
10 … | + type: 'contact', | |
11 … | + contact: b, | |
12 … | + following: true | |
13 … | + } | |
14 … | + }, | |
15 … | + }} | |
14 | 16 … | } |
15 | 17 … | |
16 | 18 … | |
17 | 19 … | exports.needs = { sbot_query: 'first' } |
modules_basic/avatar-edit.js | ||
---|---|---|
@@ -1,143 +1,0 @@ | ||
1 | -'use strict' | |
2 | -var dataurl = require('dataurl-') | |
3 | -var hyperfile = require('hyperfile') | |
4 | -var hypercrop = require('hypercrop') | |
5 | -var hyperlightbox = require('hyperlightbox') | |
6 | -var h = require('hyperscript') | |
7 | -var pull = require('pull-stream') | |
8 | -var getAvatar = require('ssb-avatar') | |
9 | -var ref = require('ssb-ref') | |
10 | -var visualize = require('visualize-buffer') | |
11 | -var self_id = require('../keys').id | |
12 | - | |
13 | -function crop (d, cb) { | |
14 | - var canvas = hypercrop(h('img', {src: d})) | |
15 | - | |
16 | - return h('div.column.avatar_pic', | |
17 | - canvas, | |
18 | - //canvas.selection, | |
19 | - h('div.row.avatar_pic__controls', | |
20 | - h('button', 'okay', {onclick: function () { | |
21 | - cb(null, canvas.selection.toDataURL()) | |
22 | - }}), | |
23 | - h('button', 'cancel', {onclick: function () { | |
24 | - cb(new Error('canceled')) | |
25 | - }}) | |
26 | - ) | |
27 | - ) | |
28 | -} | |
29 | - | |
30 | -exports.needs = { | |
31 | - message_confirm: 'first', | |
32 | - sbot_blobs_add: 'first', | |
33 | - blob_url: 'first', | |
34 | - sbot_links: 'first', | |
35 | - avatar_name: 'first' | |
36 | -} | |
37 | - | |
38 | -exports.gives = 'avatar_edit' | |
39 | - | |
40 | -exports.create = function (api) { | |
41 | - return function (id) { | |
42 | - | |
43 | - var img = visualize(new Buffer(id.substring(1), 'base64'), 256) | |
44 | - img.classList.add('avatar--large') | |
45 | - | |
46 | - var lb = hyperlightbox() | |
47 | - var name_input = h('input', {placeholder: 'rename'}) | |
48 | - var name = api.avatar_name(id) | |
49 | - var selected = null | |
50 | - | |
51 | - getAvatar({links: api.sbot_links}, self_id, id, function (err, avatar) { | |
52 | - if (err) return console.error(err) | |
53 | - //don't show user has already selected an avatar. | |
54 | - if(selected) return | |
55 | - if(ref.isBlob(avatar.image)) | |
56 | - img.src = api.blob_url(avatar.image) | |
57 | - }) | |
58 | - | |
59 | - var also_pictured = h('div.profile__alsopicturedas.wrap') | |
60 | - | |
61 | - pull( | |
62 | - api.sbot_links({dest: id, rel: 'about', values: true}), | |
63 | - pull.map(function (e) { | |
64 | - return e.value.content.image | |
65 | - }), | |
66 | - pull.filter(function (e) { | |
67 | - return e && 'string' == typeof e.link | |
68 | - }), | |
69 | - pull.unique('link'), | |
70 | - pull.drain(function (image) { | |
71 | - also_pictured.appendChild( | |
72 | - h('a', {href:'#', onclick: function (ev) { | |
73 | - ev.stopPropagation() | |
74 | - ev.preventDefault() | |
75 | - selected = image | |
76 | - img.src = api.blob_url(image.link || image) | |
77 | - }}, | |
78 | - h('img.avatar--thumbnail', {src: api.blob_url(image)}) | |
79 | - ) | |
80 | - ) | |
81 | - }) | |
82 | - ) | |
83 | - | |
84 | - return h('div.row.profile', | |
85 | - lb, | |
86 | - img, | |
87 | - h('div.column.profile__info', | |
88 | - h('strong', name), | |
89 | - name_input, | |
90 | - | |
91 | - hyperfile.asDataURL(function (data) { | |
92 | - var el = crop(data, function (err, data) { | |
93 | - if(data) { | |
94 | - img.src = data | |
95 | - var _data = dataurl.parse(data) | |
96 | - pull( | |
97 | - pull.once(_data.data), | |
98 | - api.sbot_blobs_add(function (err, hash) { | |
99 | - //TODO. Alerts are EVIL. | |
100 | - //I use them only in a moment of weakness. | |
101 | - | |
102 | - if(err) return alert(err.stack) | |
103 | - selected = { | |
104 | - link: hash, | |
105 | - size: _data.data.length, | |
106 | - type: _data.mimetype, | |
107 | - width: 512, | |
108 | - height: 512 | |
109 | - } | |
110 | - | |
111 | - }) | |
112 | - ) | |
113 | - } | |
114 | - lb.close() | |
115 | - }) | |
116 | - lb.show(el) | |
117 | - }), | |
118 | - h('button', 'update', {onclick: function () { | |
119 | - if(name_input.value) | |
120 | - name.textContent = name_input.value | |
121 | - | |
122 | - if(selected) | |
123 | - api.message_confirm({ | |
124 | - type: 'about', | |
125 | - about: id, | |
126 | - name: name_input.value || undefined, | |
127 | - image: selected | |
128 | - }) | |
129 | - else if(name_input.value) //name only | |
130 | - api.message_confirm({ | |
131 | - type: 'about', | |
132 | - about: id, | |
133 | - name: name_input.value || undefined, | |
134 | - }) | |
135 | - else | |
136 | - //another moment of weakness | |
137 | - alert('must select a name or image') | |
138 | - }}), | |
139 | - also_pictured | |
140 | - ) | |
141 | - ) | |
142 | - } | |
143 | -} |
modules_basic/avatar/avatar.js | ||
---|---|---|
@@ -1,0 +1,43 @@ | ||
1 … | + | |
2 … | +exports.needs = { | |
3 … | + avatar_name: 'first', | |
4 … | + avatar_image: 'first', | |
5 … | + avatar_link: 'first' | |
6 … | +} | |
7 … | + | |
8 … | +exports.gives = { | |
9 … | + avatar: true, | |
10 … | + avatar_image_name_link: true, | |
11 … | + avatar_image_link: true, | |
12 … | + avatar_name_link: true | |
13 … | +} | |
14 … | + | |
15 … | +exports.create = function (api) { | |
16 … | + return { | |
17 … | + avatar, | |
18 … | + avatar_image_name_link, | |
19 … | + avatar_image_link, | |
20 … | + avatar_name_link | |
21 … | + } | |
22 … | + | |
23 … | + function avatar (author, classes) { | |
24 … | + return exports.avatar_image_name_link(author, classes) | |
25 … | + } | |
26 … | + | |
27 … | + function avatar_image_name_link (author, classes) { | |
28 … | + return api.avatar_link(author, [ | |
29 … | + api.avatar_image(author, classes), | |
30 … | + api.avatar_name(author) | |
31 … | + ]) | |
32 … | + } | |
33 … | + | |
34 … | + function avatar_image_link (author, classes) { | |
35 … | + return api.avatar_link(author, api.avatar_image(author, classes)) | |
36 … | + } | |
37 … | + | |
38 … | + function avatar_name_link (author, classes) { | |
39 … | + return api.avatar_link(author, api.avatar_name(author)) | |
40 … | + } | |
41 … | +} | |
42 … | + | |
43 … | + |
modules_basic/avatar/edit.js | ||
---|---|---|
@@ -1,0 +1,143 @@ | ||
1 … | +'use strict' | |
2 … | +var dataurl = require('dataurl-') | |
3 … | +var hyperfile = require('hyperfile') | |
4 … | +var hypercrop = require('hypercrop') | |
5 … | +var hyperlightbox = require('hyperlightbox') | |
6 … | +var h = require('hyperscript') | |
7 … | +var pull = require('pull-stream') | |
8 … | +var getAvatar = require('ssb-avatar') | |
9 … | +var ref = require('ssb-ref') | |
10 … | +var visualize = require('visualize-buffer') | |
11 … | +var self_id = require('../../keys').id | |
12 … | + | |
13 … | +function crop (d, cb) { | |
14 … | + var canvas = hypercrop(h('img', {src: d})) | |
15 … | + | |
16 … | + return h('div.column.avatar_pic', | |
17 … | + canvas, | |
18 … | + //canvas.selection, | |
19 … | + h('div.row.avatar_pic__controls', | |
20 … | + h('button', 'okay', {onclick: function () { | |
21 … | + cb(null, canvas.selection.toDataURL()) | |
22 … | + }}), | |
23 … | + h('button', 'cancel', {onclick: function () { | |
24 … | + cb(new Error('canceled')) | |
25 … | + }}) | |
26 … | + ) | |
27 … | + ) | |
28 … | +} | |
29 … | + | |
30 … | +exports.needs = { | |
31 … | + message_confirm: 'first', | |
32 … | + sbot_blobs_add: 'first', | |
33 … | + blob_url: 'first', | |
34 … | + sbot_links: 'first', | |
35 … | + avatar_name: 'first' | |
36 … | +} | |
37 … | + | |
38 … | +exports.gives = 'avatar_edit' | |
39 … | + | |
40 … | +exports.create = function (api) { | |
41 … | + return function (id) { | |
42 … | + | |
43 … | + var img = visualize(new Buffer(id.substring(1), 'base64'), 256) | |
44 … | + img.classList.add('avatar--large') | |
45 … | + | |
46 … | + var lb = hyperlightbox() | |
47 … | + var name_input = h('input', {placeholder: 'rename'}) | |
48 … | + var name = api.avatar_name(id) | |
49 … | + var selected = null | |
50 … | + | |
51 … | + getAvatar({links: api.sbot_links}, self_id, id, function (err, avatar) { | |
52 … | + if (err) return console.error(err) | |
53 … | + //don't show user has already selected an avatar. | |
54 … | + if(selected) return | |
55 … | + if(ref.isBlob(avatar.image)) | |
56 … | + img.src = api.blob_url(avatar.image) | |
57 … | + }) | |
58 … | + | |
59 … | + var also_pictured = h('div.profile__alsopicturedas.wrap') | |
60 … | + | |
61 … | + pull( | |
62 … | + api.sbot_links({dest: id, rel: 'about', values: true}), | |
63 … | + pull.map(function (e) { | |
64 … | + return e.value.content.image | |
65 … | + }), | |
66 … | + pull.filter(function (e) { | |
67 … | + return e && 'string' == typeof e.link | |
68 … | + }), | |
69 … | + pull.unique('link'), | |
70 … | + pull.drain(function (image) { | |
71 … | + also_pictured.appendChild( | |
72 … | + h('a', {href:'#', onclick: function (ev) { | |
73 … | + ev.stopPropagation() | |
74 … | + ev.preventDefault() | |
75 … | + selected = image | |
76 … | + img.src = api.blob_url(image.link || image) | |
77 … | + }}, | |
78 … | + h('img.avatar--thumbnail', {src: api.blob_url(image)}) | |
79 … | + ) | |
80 … | + ) | |
81 … | + }) | |
82 … | + ) | |
83 … | + | |
84 … | + return h('div.row.profile', | |
85 … | + lb, | |
86 … | + img, | |
87 … | + h('div.column.profile__info', | |
88 … | + h('strong', name), | |
89 … | + name_input, | |
90 … | + | |
91 … | + hyperfile.asDataURL(function (data) { | |
92 … | + var el = crop(data, function (err, data) { | |
93 … | + if(data) { | |
94 … | + img.src = data | |
95 … | + var _data = dataurl.parse(data) | |
96 … | + pull( | |
97 … | + pull.once(_data.data), | |
98 … | + api.sbot_blobs_add(function (err, hash) { | |
99 … | + //TODO. Alerts are EVIL. | |
100 … | + //I use them only in a moment of weakness. | |
101 … | + | |
102 … | + if(err) return alert(err.stack) | |
103 … | + selected = { | |
104 … | + link: hash, | |
105 … | + size: _data.data.length, | |
106 … | + type: _data.mimetype, | |
107 … | + width: 512, | |
108 … | + height: 512 | |
109 … | + } | |
110 … | + | |
111 … | + }) | |
112 … | + ) | |
113 … | + } | |
114 … | + lb.close() | |
115 … | + }) | |
116 … | + lb.show(el) | |
117 … | + }), | |
118 … | + h('button', 'update', {onclick: function () { | |
119 … | + if(name_input.value) | |
120 … | + name.textContent = name_input.value | |
121 … | + | |
122 … | + if(selected) | |
123 … | + api.message_confirm({ | |
124 … | + type: 'about', | |
125 … | + about: id, | |
126 … | + name: name_input.value || undefined, | |
127 … | + image: selected | |
128 … | + }) | |
129 … | + else if(name_input.value) //name only | |
130 … | + api.message_confirm({ | |
131 … | + type: 'about', | |
132 … | + about: id, | |
133 … | + name: name_input.value || undefined, | |
134 … | + }) | |
135 … | + else | |
136 … | + //another moment of weakness | |
137 … | + alert('must select a name or image') | |
138 … | + }}), | |
139 … | + also_pictured | |
140 … | + ) | |
141 … | + ) | |
142 … | + } | |
143 … | +} |
modules_basic/avatar/image.js | ||
---|---|---|
@@ -1,0 +1,102 @@ | ||
1 … | +'use strict' | |
2 … | +var h = require('hyperscript') | |
3 … | +var visualize = require('visualize-buffer') | |
4 … | + | |
5 … | +var pull = require('pull-stream') | |
6 … | + | |
7 … | +var self_id = require('../../keys').id | |
8 … | + | |
9 … | +exports.needs = { | |
10 … | + sbot_query: 'first', | |
11 … | + blob_url: 'first' | |
12 … | +} | |
13 … | + | |
14 … | +exports.gives = { | |
15 … | + connection_status: true, avatar_image: true | |
16 … | +} | |
17 … | + | |
18 … | +var ready = false | |
19 … | +var waiting = [] | |
20 … | + | |
21 … | +var last = 0 | |
22 … | + | |
23 … | +var cache = {} | |
24 … | + | |
25 … | +exports.create = function (api) { | |
26 … | + var avatars = {} | |
27 … | + | |
28 … | + //blah blah | |
29 … | + return { | |
30 … | + connection_status: function (err) { | |
31 … | + if (err) return | |
32 … | + pull( | |
33 … | + api.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 | |
72 … | + | |
73 … | + }) | |
74 … | + ) | |
75 … | + }, | |
76 … | + | |
77 … | + avatar_image: function (author, classes) { | |
78 … | + classes = classes || '' | |
79 … | + if(classes && 'string' === typeof classes) classes = '.avatar--'+classes | |
80 … | + | |
81 … | + function gen (id) { | |
82 … | + if(cache[id]) return h('img', {src: cache[id]}) | |
83 … | + var img = visualize(new Buffer(author.substring(1), 'base64'), 256) | |
84 … | + cache[id] = img.src | |
85 … | + return img | |
86 … | + } | |
87 … | + | |
88 … | + var img = ready && avatars[author] ? h('img', {src: api.blob_url(avatars[author].image)}) : gen(author) | |
89 … | + | |
90 … | + ;(classes || '').split('.').filter(Boolean).forEach(function (c) { | |
91 … | + img.classList.add(c) | |
92 … | + }) | |
93 … | + | |
94 … | + if(!ready) | |
95 … | + waiting.push(function () { | |
96 … | + if(avatars[author]) img.src = api.blob_url(avatars[author].image) | |
97 … | + }) | |
98 … | + | |
99 … | + return img | |
100 … | + } | |
101 … | + } | |
102 … | +} |
modules_basic/avatar/index.js | ||
---|---|---|
@@ -1,0 +1,9 @@ | ||
1 … | +module.exports = { | |
2 … | + 'edit': require('./edit'), | |
3 … | + 'image': require('./image'), | |
4 … | + 'link': require('./link'), | |
5 … | + 'name': require('./name'), | |
6 … | + 'profile': require('./profile'), | |
7 … | + 'avatar': require('./avatar') | |
8 … | +} | |
9 … | + |
modules_basic/avatar/link.js | ||
---|---|---|
@@ -1,0 +1,24 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | + | |
3 … | +exports.needs = { | |
4 … | + signifier: 'first' | |
5 … | +} | |
6 … | + | |
7 … | +exports.gives = 'avatar_link' | |
8 … | + | |
9 … | +exports.create = function (api) { | |
10 … | + return function (id, element) { | |
11 … | + | |
12 … | + var link = h('a.avatar', {href: "#"+id, title: id}, element) | |
13 … | + | |
14 … | + api.signifier(id, function (_, names) { | |
15 … | + if(names.length) | |
16 … | + link.title = names[0].name + '\n '+id | |
17 … | + }) | |
18 … | + | |
19 … | + return link | |
20 … | + } | |
21 … | +} | |
22 … | + | |
23 … | + | |
24 … | + |
modules_basic/avatar/name.js | ||
---|---|---|
@@ -1,0 +1,29 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | + | |
3 … | +exports.needs = { | |
4 … | + signifier: 'first' | |
5 … | +} | |
6 … | + | |
7 … | +exports.gives = 'avatar_name' | |
8 … | + | |
9 … | +exports.create = function (api) { | |
10 … | + | |
11 … | + return function name (id) { | |
12 … | + var n = h('span', id ? 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 … | + | |
28 … | +} | |
29 … | + |
modules_basic/avatar/profile.js | ||
---|---|---|
@@ -1,0 +1,79 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | +var pull = require('pull-stream') | |
3 … | + | |
4 … | +exports.needs = { | |
5 … | + avatar_image_link: 'first', | |
6 … | + avatar_action: 'map', | |
7 … | + avatar_edit: 'first', | |
8 … | + follows: 'first', | |
9 … | + followers: 'first' | |
10 … | +} | |
11 … | + | |
12 … | +exports.gives = 'avatar_profile' | |
13 … | + | |
14 … | +function streamToList(stream, el) { | |
15 … | + pull( | |
16 … | + stream, | |
17 … | + pull.drain(function (item) { | |
18 … | + if(item) el.appendChild(item) | |
19 … | + }) | |
20 … | + ) | |
21 … | + return el | |
22 … | +} | |
23 … | + | |
24 … | +exports.create = function (api) { | |
25 … | + | |
26 … | + function image_link (id) { | |
27 … | + return api.avatar_image_link(id, 'thumbnail') | |
28 … | + } | |
29 … | + | |
30 … | + return function (id) { | |
31 … | + | |
32 … | + var follows_el = h('div.profile__follows.wrap') | |
33 … | + var friends_el = h('div.profile__friendss.wrap') | |
34 … | + var followers_el = h('div.profile__followers.wrap') | |
35 … | + var a, b | |
36 … | + | |
37 … | + pull(api.follows(id), pull.unique(), pull.collect(function (err, ary) { | |
38 … | + a = ary || []; next() | |
39 … | + })) | |
40 … | + pull(api.followers(id), pull.unique(), pull.collect(function (err, ary) { | |
41 … | + b = ary || {}; next() | |
42 … | + })) | |
43 … | + | |
44 … | + function next () { | |
45 … | + if(!(a && b)) return | |
46 … | + var _c = [], _a = [], _b = [] | |
47 … | + | |
48 … | + a.forEach(function (id) { | |
49 … | + if(!~b.indexOf(id)) _a.push(id) | |
50 … | + else _c.push(id) | |
51 … | + }) | |
52 … | + b.forEach(function (id) { | |
53 … | + if(!~_c.indexOf(id)) _b.push(id) | |
54 … | + }) | |
55 … | + function add (ary, el) { | |
56 … | + ary.forEach(function (id) { el.appendChild(image_link(id)) }) | |
57 … | + } | |
58 … | + | |
59 … | + add(_a, follows_el) | |
60 … | + add(_c, friends_el) | |
61 … | + add(_b, followers_el) | |
62 … | + } | |
63 … | + | |
64 … | + | |
65 … | + return h('div.column.profile', | |
66 … | + api.avatar_edit(id), | |
67 … | + api.avatar_action(id), | |
68 … | + h('div.profile__relationships.column', | |
69 … | + h('strong', 'follows'), | |
70 … | + follows_el, | |
71 … | + h('strong', 'friends'), | |
72 … | + friends_el, | |
73 … | + h('strong', 'followers'), | |
74 … | + followers_el | |
75 … | + ) | |
76 … | + ) | |
77 … | + } | |
78 … | + | |
79 … | +} |
modules_basic/avatar-image.js | ||
---|---|---|
@@ -1,102 +1,0 @@ | ||
1 | -'use strict' | |
2 | -var h = require('hyperscript') | |
3 | -var visualize = require('visualize-buffer') | |
4 | - | |
5 | -var pull = require('pull-stream') | |
6 | - | |
7 | -var self_id = require('../keys').id | |
8 | - | |
9 | -exports.needs = { | |
10 | - sbot_query: 'first', | |
11 | - blob_url: 'first' | |
12 | -} | |
13 | - | |
14 | -exports.gives = { | |
15 | - connection_status: true, avatar_image: true | |
16 | -} | |
17 | - | |
18 | -var ready = false | |
19 | -var waiting = [] | |
20 | - | |
21 | -var last = 0 | |
22 | - | |
23 | -var cache = {} | |
24 | - | |
25 | -exports.create = function (api) { | |
26 | - var avatars = {} | |
27 | - | |
28 | - //blah blah | |
29 | - return { | |
30 | - connection_status: function (err) { | |
31 | - if (err) return | |
32 | - pull( | |
33 | - api.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 | |
72 | - | |
73 | - }) | |
74 | - ) | |
75 | - }, | |
76 | - | |
77 | - avatar_image: function (author, classes) { | |
78 | - classes = classes || '' | |
79 | - if(classes && 'string' === typeof classes) classes = '.avatar--'+classes | |
80 | - | |
81 | - function gen (id) { | |
82 | - if(cache[id]) return h('img', {src: cache[id]}) | |
83 | - var img = visualize(new Buffer(author.substring(1), 'base64'), 256) | |
84 | - cache[id] = img.src | |
85 | - return img | |
86 | - } | |
87 | - | |
88 | - var img = ready && avatars[author] ? h('img', {src: api.blob_url(avatars[author].image)}) : gen(author) | |
89 | - | |
90 | - ;(classes || '').split('.').filter(Boolean).forEach(function (c) { | |
91 | - img.classList.add(c) | |
92 | - }) | |
93 | - | |
94 | - if(!ready) | |
95 | - waiting.push(function () { | |
96 | - if(avatars[author]) img.src = api.blob_url(avatars[author].image) | |
97 | - }) | |
98 | - | |
99 | - return img | |
100 | - } | |
101 | - } | |
102 | -} |
modules_basic/avatar-link.js | ||
---|---|---|
@@ -1,22 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | - | |
3 | -exports.needs = {signifier: 'first'} | |
4 | - | |
5 | -exports.gives = 'avatar_link' | |
6 | - | |
7 | -exports.create = function (api) { | |
8 | - return function (id, element) { | |
9 | - | |
10 | - var link = h('a.avatar', {href: "#"+id, title: id}, element) | |
11 | - | |
12 | - api.signifier(id, function (_, names) { | |
13 | - if(names.length) | |
14 | - link.title = names[0].name + '\n '+id | |
15 | - }) | |
16 | - | |
17 | - return link | |
18 | - } | |
19 | -} | |
20 | - | |
21 | - | |
22 | - |
modules_basic/avatar-name.js | ||
---|---|---|
@@ -1,27 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | - | |
3 | -exports.needs = { signifier: 'first' } | |
4 | - | |
5 | -exports.gives = 'avatar_name' | |
6 | - | |
7 | -exports.create = function (api) { | |
8 | - | |
9 | - return function name (id) { | |
10 | - var n = h('span', id ? id.substring(0, 10) : "") | |
11 | - | |
12 | - //choose the most popular name for this person. | |
13 | - //for anything like this you'll see I have used sbot.links2 | |
14 | - //which is the ssb-links plugin. as you'll see the query interface | |
15 | - //is pretty powerful! | |
16 | - //TODO: "most popular" name is easily gameable. | |
17 | - //must come up with something better than this. | |
18 | - | |
19 | - api.signifier(id, function (_, names) { | |
20 | - if(names.length) n.textContent = names[0].name | |
21 | - }) | |
22 | - | |
23 | - return n | |
24 | - } | |
25 | - | |
26 | -} | |
27 | - |
modules_basic/emoji.js | ||
---|---|---|
@@ -1,0 +1,18 @@ | ||
1 … | +var emojis = require('emoji-named-characters') | |
2 … | +var emojiNames = Object.keys(emojis) | |
3 … | + | |
4 … | +exports.needs = { blob_url: 'first' } | |
5 … | +exports.gives = { emoji_names: true, emoji_url: true } | |
6 … | + | |
7 … | +exports.create = function (api) { | |
8 … | + return { | |
9 … | + emoji_names: function () { | |
10 … | + return emojiNames | |
11 … | + }, | |
12 … | + emoji_url: function (emoji) { | |
13 … | + return emoji in emojis && | |
14 … | + api.blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png' | |
15 … | + } | |
16 … | + } | |
17 … | +} | |
18 … | + |
modules_basic/avatar-profile.js | ||
---|---|---|
@@ -1,79 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | -var pull = require('pull-stream') | |
3 | - | |
4 | -exports.needs = { | |
5 | - avatar_image_link: 'first', | |
6 | - avatar_action: 'map', | |
7 | - avatar_edit: 'first', | |
8 | - follows: 'first', | |
9 | - followers: 'first' | |
10 | -} | |
11 | - | |
12 | -exports.gives = 'avatar_profile' | |
13 | - | |
14 | -function streamToList(stream, el) { | |
15 | - pull( | |
16 | - stream, | |
17 | - pull.drain(function (item) { | |
18 | - if(item) el.appendChild(item) | |
19 | - }) | |
20 | - ) | |
21 | - return el | |
22 | -} | |
23 | - | |
24 | -exports.create = function (api) { | |
25 | - | |
26 | - function image_link (id) { | |
27 | - return api.avatar_image_link(id, 'thumbnail') | |
28 | - } | |
29 | - | |
30 | - return function (id) { | |
31 | - | |
32 | - var follows_el = h('div.profile__follows.wrap') | |
33 | - var friends_el = h('div.profile__friendss.wrap') | |
34 | - var followers_el = h('div.profile__followers.wrap') | |
35 | - var a, b | |
36 | - | |
37 | - pull(api.follows(id), pull.unique(), pull.collect(function (err, ary) { | |
38 | - a = ary || []; next() | |
39 | - })) | |
40 | - pull(api.followers(id), pull.unique(), pull.collect(function (err, ary) { | |
41 | - b = ary || {}; next() | |
42 | - })) | |
43 | - | |
44 | - function next () { | |
45 | - if(!(a && b)) return | |
46 | - var _c = [], _a = [], _b = [] | |
47 | - | |
48 | - a.forEach(function (id) { | |
49 | - if(!~b.indexOf(id)) _a.push(id) | |
50 | - else _c.push(id) | |
51 | - }) | |
52 | - b.forEach(function (id) { | |
53 | - if(!~_c.indexOf(id)) _b.push(id) | |
54 | - }) | |
55 | - function add (ary, el) { | |
56 | - ary.forEach(function (id) { el.appendChild(image_link(id)) }) | |
57 | - } | |
58 | - | |
59 | - add(_a, follows_el) | |
60 | - add(_c, friends_el) | |
61 | - add(_b, followers_el) | |
62 | - } | |
63 | - | |
64 | - | |
65 | - return h('div.column.profile', | |
66 | - api.avatar_edit(id), | |
67 | - api.avatar_action(id), | |
68 | - h('div.profile__relationships.column', | |
69 | - h('strong', 'follows'), | |
70 | - follows_el, | |
71 | - h('strong', 'friends'), | |
72 | - friends_el, | |
73 | - h('strong', 'followers'), | |
74 | - followers_el | |
75 | - ) | |
76 | - ) | |
77 | - } | |
78 | - | |
79 | -} |
modules_basic/avatar.js | ||
---|---|---|
@@ -1,43 +1,0 @@ | ||
1 | - | |
2 | -exports.needs = { | |
3 | - avatar_name: 'first', | |
4 | - avatar_image: 'first', | |
5 | - avatar_link: 'first' | |
6 | -} | |
7 | - | |
8 | -exports.gives = { | |
9 | - avatar: true, | |
10 | - avatar_image_name_link: true, | |
11 | - avatar_image_link: true, | |
12 | - avatar_name_link: true | |
13 | -} | |
14 | - | |
15 | -exports.create = function (api) { | |
16 | - return { | |
17 | - avatar, | |
18 | - avatar_image_name_link, | |
19 | - avatar_image_link, | |
20 | - avatar_name_link | |
21 | - } | |
22 | - | |
23 | - function avatar (author, classes) { | |
24 | - return exports.avatar_image_name_link(author, classes) | |
25 | - } | |
26 | - | |
27 | - function avatar_image_name_link (author, classes) { | |
28 | - return api.avatar_link(author, [ | |
29 | - api.avatar_image(author, classes), | |
30 | - api.avatar_name(author) | |
31 | - ]) | |
32 | - } | |
33 | - | |
34 | - function avatar_image_link (author, classes) { | |
35 | - return api.avatar_link(author, api.avatar_image(author, classes)) | |
36 | - } | |
37 | - | |
38 | - function avatar_name_link (author, classes) { | |
39 | - return api.avatar_link(author, api.avatar_name(author)) | |
40 | - } | |
41 | -} | |
42 | - | |
43 | - |
modules_basic/index.test.js | ||
---|---|---|
@@ -1,0 +1,13 @@ | ||
1 … | +const test = require('tape') | |
2 … | +const combine = require('depject') | |
3 … | + | |
4 … | +process.env.ssb_appname = 'test' | |
5 … | + | |
6 … | +const core = require('../modules_core') | |
7 … | +const basic = require('./') | |
8 … | + | |
9 … | +test('modules_basic has no outside deps', t => { | |
10 … | + t.ok(combine(basic, core)) | |
11 … | + t.end() | |
12 … | +}) | |
13 … | + |
modules_basic/message/author.js | ||
---|---|---|
@@ -1,0 +1,39 @@ | ||
1 … | +const fs = require('fs') | |
2 … | +const h = require('../../h') | |
3 … | +const { when }= require('@mmckegg/mutant') | |
4 … | + | |
5 … | +exports.needs = { | |
6 … | + avatar_link: 'first', | |
7 … | + avatar_image: 'first', | |
8 … | + avatar_name: 'first', | |
9 … | + timestamp: 'first' | |
10 … | +} | |
11 … | + | |
12 … | +exports.gives = { | |
13 … | + message_author: true, | |
14 … | + mcss: true | |
15 … | +} | |
16 … | + | |
17 … | +exports.create = function (api) { | |
18 … | + return { | |
19 … | + message_author, | |
20 … | + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
21 … | + } | |
22 … | + | |
23 … | + function message_author (msg, opts = {}) { | |
24 … | + var { size = 'small' } = opts | |
25 … | + var { value } = msg | |
26 … | + var { author } = value | |
27 … | + | |
28 … | + return h('MessageAuthor', { | |
29 … | + className: `-${size}` | |
30 … | + }, [ | |
31 … | + when(size !== 'mini', | |
32 … | + h('section -image', api.avatar_link(author, api.avatar_image(author, 'thumbnail'))) | |
33 … | + ), | |
34 … | + h('section -name', api.avatar_link(author, api.avatar_name(author))), | |
35 … | + h('section -timestamp', api.timestamp(msg)) | |
36 … | + ]) | |
37 … | + } | |
38 … | +} | |
39 … | + |
modules_basic/message/author.mcss | ||
---|---|---|
@@ -1,0 +1,38 @@ | ||
1 … | +MessageAuthor { | |
2 … | + display: flex | |
3 … | + flex-direction: column | |
4 … | + | |
5 … | + section { | |
6 … | + -image { | |
7 … | + margin-bottom: .3rem | |
8 … | + } | |
9 … | + | |
10 … | + -name { | |
11 … | + max-width: 7rem | |
12 … | + a { $textPrimary } | |
13 … | + } | |
14 … | + | |
15 … | + -timestamp { | |
16 … | + | |
17 … | + } | |
18 … | + } | |
19 … | + | |
20 … | + -mini { | |
21 … | + flex-direction: row | |
22 … | + | |
23 … | + section { | |
24 … | + margin-right: .5rem | |
25 … | + | |
26 … | + -name { | |
27 … | + position: initial | |
28 … | + left: initial | |
29 … | + min-width: 6.5rem | |
30 … | + max-width: none | |
31 … | + } | |
32 … | + | |
33 … | + -timestamp { | |
34 … | + | |
35 … | + } | |
36 … | + } | |
37 … | + } | |
38 … | +} |
modules_basic/message/backlinks.js | ||
---|---|---|
@@ -1,0 +1,48 @@ | ||
1 … | +const fs = require('fs') | |
2 … | +const h = require('../../h') | |
3 … | + | |
4 … | +exports.needs = { | |
5 … | + message_name: 'first' | |
6 … | +} | |
7 … | + | |
8 … | +exports.gives = { | |
9 … | + message_backlinks: true, | |
10 … | + mcss: true | |
11 … | +} | |
12 … | + | |
13 … | +exports.create = function (api) { | |
14 … | + return { | |
15 … | + message_backlinks, | |
16 … | + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
17 … | + } | |
18 … | + | |
19 … | + function message_backlinks (msg) { | |
20 … | + var links = [] | |
21 … | + for(var k in CACHE) { | |
22 … | + var _msg = CACHE[k] | |
23 … | + var mentions = _msg.content.mentions | |
24 … | + | |
25 … | + if(Array.isArray(mentions)) { | |
26 … | + for(var i = 0; i < mentions.length; i++) | |
27 … | + if(mentions[i].link == msg.key) | |
28 … | + links.push(k) | |
29 … | + } | |
30 … | + } | |
31 … | + | |
32 … | + if (links.length === 0) return null | |
33 … | + | |
34 … | + var hrefList = h('ul') | |
35 … | + links.forEach(link => { | |
36 … | + api.message_name(link, (err, name) => { | |
37 … | + if (err) throw err | |
38 … | + hrefList.appendChild(h('li', | |
39 … | + h('a -backlink', { href: `#${link}` }, name) | |
40 … | + )) | |
41 … | + }) | |
42 … | + }) | |
43 … | + return h('MessageBacklinks', [ | |
44 … | + h('header', 'backlinks:'), | |
45 … | + hrefList | |
46 … | + ]) | |
47 … | + } | |
48 … | +} |
modules_basic/message/backlinks.mcss | ||
---|---|---|
@@ -1,0 +1,21 @@ | ||
1 … | +MessageBacklinks { | |
2 … | + font-size: .9rem | |
3 … | + margin-top: .5rem | |
4 … | + | |
5 … | + header { | |
6 … | + $textSubtle | |
7 … | + } | |
8 … | + | |
9 … | + ul { | |
10 … | + padding-left: 1rem | |
11 … | + | |
12 … | + li { | |
13 … | + a { | |
14 … | + -backlink { | |
15 … | + $textSubtle | |
16 … | + | |
17 … | + } | |
18 … | + } | |
19 … | + } | |
20 … | + } | |
21 … | +} |
modules_basic/message/confirm.js | ||
---|---|---|
@@ -1,0 +1,73 @@ | ||
1 … | +var fs = require('fs') | |
2 … | +var lightbox = require('hyperlightbox') | |
3 … | +var h = require('../../h') | |
4 … | +var self_id = require('../../keys').id | |
5 … | +//publish or add | |
6 … | + | |
7 … | +exports.needs = { | |
8 … | + publish: 'first', | |
9 … | + message_render: 'first', | |
10 … | + avatar: 'first', | |
11 … | + message_meta: 'map' | |
12 … | +} | |
13 … | + | |
14 … | +exports.gives = { | |
15 … | + message_confirm: true, | |
16 … | + mcss: true | |
17 … | +} | |
18 … | + | |
19 … | +exports.create = function (api) { | |
20 … | + return { | |
21 … | + message_confirm, | |
22 … | + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
23 … | + } | |
24 … | + | |
25 … | + function message_confirm (content, cb) { | |
26 … | + | |
27 … | + cb = cb || function () {} | |
28 … | + | |
29 … | + var lb = lightbox() | |
30 … | + document.body.appendChild(lb) | |
31 … | + | |
32 … | + var msg = { | |
33 … | + key: "DRAFT", | |
34 … | + value: { | |
35 … | + author: self_id, | |
36 … | + previous: null, | |
37 … | + sequence: null, | |
38 … | + timestamp: Date.now(), | |
39 … | + content: content | |
40 … | + } | |
41 … | + } | |
42 … | + | |
43 … | + var okay = h('button', { | |
44 … | + 'ev-click': () => { | |
45 … | + lb.remove() | |
46 … | + api.publish(content, cb) | |
47 … | + }}, | |
48 … | + 'okay' | |
49 … | + ) | |
50 … | + | |
51 … | + var cancel = h('button', { | |
52 … | + 'ev-click': () => { | |
53 … | + lb.remove() | |
54 … | + cb(null) | |
55 … | + }}, | |
56 … | + 'Cancel' | |
57 … | + ) | |
58 … | + | |
59 … | + okay.addEventListener('keydown', function (ev) { | |
60 … | + if(ev.keyCode === 27) cancel.click() //escape | |
61 … | + }) | |
62 … | + | |
63 … | + lb.show(h('MessageConfirm', [ | |
64 … | + h('header -preview_description', h('h1', 'Preview')), | |
65 … | + h('section -message_preview', api.message_render(msg)), | |
66 … | + h('section -actions', [okay, cancel]) | |
67 … | + ] | |
68 … | + )) | |
69 … | + | |
70 … | + okay.focus() | |
71 … | + } | |
72 … | +} | |
73 … | + |
modules_basic/message/confirm.mcss | ||
---|---|---|
@@ -1,0 +1,39 @@ | ||
1 … | +MessageConfirm { | |
2 … | + section { | |
3 … | + -preview_description { | |
4 … | + } | |
5 … | + | |
6 … | + -message_preview { | |
7 … | + background-color: white | |
8 … | + | |
9 … | + div { | |
10 … | + border: none | |
11 … | + | |
12 … | + header.author { | |
13 … | + div { | |
14 … | + section { | |
15 … | + -timestamp { | |
16 … | + display: none | |
17 … | + } | |
18 … | + } | |
19 … | + } | |
20 … | + } | |
21 … | + | |
22 … | + section.action { | |
23 … | + display: none | |
24 … | + } | |
25 … | + } | |
26 … | + } | |
27 … | + | |
28 … | + -actions { | |
29 … | + margin-top: 1rem | |
30 … | + display: flex | |
31 … | + justify-content: flex-end | |
32 … | + | |
33 … | + button { | |
34 … | + margin: 0 0 0 1rem | |
35 … | + } | |
36 … | + } | |
37 … | + } | |
38 … | +} | |
39 … | + |
modules_basic/message/index.js | ||
---|---|---|
@@ -1,0 +1,8 @@ | ||
1 … | +module.exports = { | |
2 … | + 'author': require('./author'), | |
3 … | + 'backlinks': require('./backlinks'), | |
4 … | + 'confirm': require('./confirm'), | |
5 … | + 'link': require('./link'), | |
6 … | + 'name': require('./name'), | |
7 … | + 'render': require('./render'), | |
8 … | +} |
modules_basic/message/link.js | ||
---|---|---|
@@ -1,0 +1,35 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | +var ref = require('ssb-ref') | |
3 … | + | |
4 … | +exports.needs = { | |
5 … | + message_name: 'first' | |
6 … | +} | |
7 … | + | |
8 … | +exports.gives = 'message_link' | |
9 … | + | |
10 … | +exports.create = function (api) { | |
11 … | + | |
12 … | + return function (id) { | |
13 … | + | |
14 … | + if('string' !== typeof id) | |
15 … | + throw new Error('link must be to message id') | |
16 … | + | |
17 … | + var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...') | |
18 … | + | |
19 … | + if(ref.isMsg(id)) | |
20 … | + api.message_name(id, function (err, name) { | |
21 … | + if(err) console.error(err) | |
22 … | + else link.textContent = name | |
23 … | + }) | |
24 … | + | |
25 … | + return link | |
26 … | + } | |
27 … | +} | |
28 … | + | |
29 … | + | |
30 … | + | |
31 … | + | |
32 … | + | |
33 … | + | |
34 … | + | |
35 … | + |
modules_basic/message/name.js | ||
---|---|---|
@@ -1,0 +1,26 @@ | ||
1 … | + | |
2 … | +function title (s) { | |
3 … | + var m = /^\n*([^\n]{0,40})/.exec(s) | |
4 … | + return m && (m[1].length == 40 ? m[1]+'...' : m[1]) | |
5 … | +} | |
6 … | + | |
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 … | + } | |
25 … | +} | |
26 … | + |
modules_basic/message/render.js | ||
---|---|---|
@@ -1,0 +1,87 @@ | ||
1 … | +const fs = require('fs') | |
2 … | +const h = require('../../h') | |
3 … | + | |
4 … | +exports.needs = { | |
5 … | + avatar_name: 'first', | |
6 … | + avatar_link: 'first', | |
7 … | + message_action: 'map', | |
8 … | + message_author: 'first', | |
9 … | + message_backlinks: 'first', | |
10 … | + message_content: 'first', | |
11 … | + message_content_mini: 'first', | |
12 … | + message_title: 'first', | |
13 … | + message_link: 'first', | |
14 … | + message_meta: 'map', | |
15 … | +} | |
16 … | + | |
17 … | +exports.gives = { | |
18 … | + message_render: true, | |
19 … | + mcss: true | |
20 … | +} | |
21 … | + | |
22 … | +exports.create = function (api) { | |
23 … | + return { | |
24 … | + message_render, | |
25 … | + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
26 … | + } | |
27 … | + | |
28 … | + function message_render (msg) { | |
29 … | + var content = api.message_content_mini(msg) | |
30 … | + if (content) return mini(msg, content) | |
31 … | + | |
32 … | + content = api.message_content(msg) | |
33 … | + if (!content) return mini(msg, message_content_mini_fallback(msg)) | |
34 … | + | |
35 … | + var msgEl = h('Message', { | |
36 … | + 'ev-keydown': navigateToMessageOnEnter, | |
37 … | + attributes: { | |
38 … | + tabindex: '0' | |
39 … | + } | |
40 … | + }, [ | |
41 … | + h('header.author', api.message_author(msg)), | |
42 … | + h('section.title', api.message_title(msg)), | |
43 … | + h('section.meta', api.message_meta(msg)), | |
44 … | + h('section.content', content), | |
45 … | + h('section.action', api.message_action(msg)), | |
46 … | + h('footer.backlinks', api.message_backlinks(msg)) | |
47 … | + ]) | |
48 … | + return msgEl | |
49 … | + | |
50 … | + function navigateToMessageOnEnter (ev) { | |
51 … | + // on enter, hit first meta. | |
52 … | + if(ev.keyCode == 13) { | |
53 … | + | |
54 … | + // unless in an input | |
55 … | + if (ev.target.nodeName === 'INPUT' | |
56 … | + || ev.target.nodeName === 'TEXTAREA') return | |
57 … | + | |
58 … | + // HACK! (mw) | |
59 … | + // there's no exported api to open a new tab. :/ | |
60 … | + // it's only done in `app.js` module in an`onhashchange` handler. | |
61 … | + // sooooooo yeah this shit for now :) | |
62 … | + var wtf = h('a', { href: `#${msg.key}` }) | |
63 … | + msgEl.appendChild(wtf) | |
64 … | + wtf.click() | |
65 … | + msgEl.removeChild(wtf) | |
66 … | + } | |
67 … | + } | |
68 … | + } | |
69 … | + | |
70 … | + function mini(msg, el) { | |
71 … | + return h('Message -mini', { | |
72 … | + attributes: { | |
73 … | + tabindex: '0' | |
74 … | + } | |
75 … | + }, [ | |
76 … | + h('header.author', api.message_author(msg, { size: 'mini' })), | |
77 … | + h('section.meta', api.message_meta(msg)), | |
78 … | + h('section.content', el) | |
79 … | + ]) | |
80 … | + } | |
81 … | +} | |
82 … | + | |
83 … | + | |
84 … | +function message_content_mini_fallback(msg) { | |
85 … | + return h('code', msg.value.content.type) | |
86 … | +} | |
87 … | + |
modules_basic/message/render.mcss | ||
---|---|---|
@@ -1,0 +1,80 @@ | ||
1 … | +Message { | |
2 … | + padding: 1rem .5rem 1rem 7.5rem | |
3 … | + border-top: solid 1px gainsboro | |
4 … | + min-height: 5rem | |
5 … | + | |
6 … | + position: relative | |
7 … | + display: flex | |
8 … | + flex-direction: row | |
9 … | + flex-wrap: wrap | |
10 … | + justify-content: flex-end | |
11 … | + | |
12 … | + header.author { | |
13 … | + position: absolute | |
14 … | + left: .5rem | |
15 … | + } | |
16 … | + | |
17 … | + section.title { | |
18 … | + flex-grow: 1 | |
19 … | + font-size: .9rem | |
20 … | + } | |
21 … | + | |
22 … | + section.meta { | |
23 … | + display: flex | |
24 … | + a { | |
25 … | + margin-left: .2rem | |
26 … | + $textSubtle | |
27 … | + } | |
28 … | + | |
29 … | + input{ | |
30 … | + margin-right: 0 | |
31 … | + order: 99 | |
32 … | + } | |
33 … | + } | |
34 … | + | |
35 … | + section.content { | |
36 … | + flex-basis: 100% | |
37 … | + | |
38 … | + (img) { | |
39 … | + max-width: 100% | |
40 … | + } | |
41 … | + } | |
42 … | + | |
43 … | + section.action { | |
44 … | + flex-basis: 100% | |
45 … | + display: flex | |
46 … | + justify-content: flex-end | |
47 … | + | |
48 … | + a { | |
49 … | + margin-left: .5em | |
50 … | + } | |
51 … | + } | |
52 … | + | |
53 … | + footer.backlinks { | |
54 … | + flex-basis: 100% | |
55 … | + } | |
56 … | + | |
57 … | + | |
58 … | + -mini { | |
59 … | + font-size: .9rem | |
60 … | + justify-content: flex-start | |
61 … | + padding: .25rem .5rem | |
62 … | + min-height: inherit | |
63 … | + | |
64 … | + header.author { | |
65 … | + order: 0 | |
66 … | + position: initial | |
67 … | + left: initial | |
68 … | + } | |
69 … | + | |
70 … | + section.content { | |
71 … | + order: 1 | |
72 … | + flex-basis: initial | |
73 … | + flex-grow: 1 | |
74 … | + } | |
75 … | + | |
76 … | + section.meta { | |
77 … | + order: 2 | |
78 … | + } | |
79 … | + } | |
80 … | +} |
modules_basic/message-author.js | ||
---|---|---|
@@ -1,39 +1,0 @@ | ||
1 | -const fs = require('fs') | |
2 | -const h = require('../h') | |
3 | -const { when }= require('@mmckegg/mutant') | |
4 | - | |
5 | -exports.needs = { | |
6 | - avatar_link: 'first', | |
7 | - avatar_image: 'first', | |
8 | - avatar_name: 'first', | |
9 | - timestamp: 'first' | |
10 | -} | |
11 | - | |
12 | -exports.gives = { | |
13 | - message_author: true, | |
14 | - mcss: true | |
15 | -} | |
16 | - | |
17 | -exports.create = function (api) { | |
18 | - return { | |
19 | - message_author, | |
20 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
21 | - } | |
22 | - | |
23 | - function message_author (msg, opts = {}) { | |
24 | - var { size = 'small' } = opts | |
25 | - var { value } = msg | |
26 | - var { author } = value | |
27 | - | |
28 | - return h('MessageAuthor', { | |
29 | - className: `-${size}` | |
30 | - }, [ | |
31 | - when(size !== 'mini', | |
32 | - h('section -image', api.avatar_link(author, api.avatar_image(author, 'thumbnail'))) | |
33 | - ), | |
34 | - h('section -name', api.avatar_link(author, api.avatar_name(author))), | |
35 | - h('section -timestamp', api.timestamp(msg)) | |
36 | - ]) | |
37 | - } | |
38 | -} | |
39 | - |
modules_basic/message-author.mcss | ||
---|---|---|
@@ -1,38 +1,0 @@ | ||
1 | -MessageAuthor { | |
2 | - display: flex | |
3 | - flex-direction: column | |
4 | - | |
5 | - section { | |
6 | - -image { | |
7 | - margin-bottom: .3rem | |
8 | - } | |
9 | - | |
10 | - -name { | |
11 | - max-width: 7rem | |
12 | - a { $textPrimary } | |
13 | - } | |
14 | - | |
15 | - -timestamp { | |
16 | - | |
17 | - } | |
18 | - } | |
19 | - | |
20 | - -mini { | |
21 | - flex-direction: row | |
22 | - | |
23 | - section { | |
24 | - margin-right: .5rem | |
25 | - | |
26 | - -name { | |
27 | - position: initial | |
28 | - left: initial | |
29 | - min-width: 6.5rem | |
30 | - max-width: none | |
31 | - } | |
32 | - | |
33 | - -timestamp { | |
34 | - | |
35 | - } | |
36 | - } | |
37 | - } | |
38 | -} |
modules_basic/message-backlinks.js | ||
---|---|---|
@@ -1,48 +1,0 @@ | ||
1 | -const fs = require('fs') | |
2 | -const h = require('../h') | |
3 | - | |
4 | -exports.needs = { | |
5 | - message_name: 'first' | |
6 | -} | |
7 | - | |
8 | -exports.gives = { | |
9 | - message_backlinks: true, | |
10 | - mcss: true | |
11 | -} | |
12 | - | |
13 | -exports.create = function (api) { | |
14 | - return { | |
15 | - message_backlinks, | |
16 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
17 | - } | |
18 | - | |
19 | - function message_backlinks (msg) { | |
20 | - var links = [] | |
21 | - for(var k in CACHE) { | |
22 | - var _msg = CACHE[k] | |
23 | - var mentions = _msg.content.mentions | |
24 | - | |
25 | - if(Array.isArray(mentions)) { | |
26 | - for(var i = 0; i < mentions.length; i++) | |
27 | - if(mentions[i].link == msg.key) | |
28 | - links.push(k) | |
29 | - } | |
30 | - } | |
31 | - | |
32 | - if (links.length === 0) return null | |
33 | - | |
34 | - var hrefList = h('ul') | |
35 | - links.forEach(link => { | |
36 | - api.message_name(link, (err, name) => { | |
37 | - if (err) throw err | |
38 | - hrefList.appendChild(h('li', | |
39 | - h('a -backlink', { href: `#${link}` }, name) | |
40 | - )) | |
41 | - }) | |
42 | - }) | |
43 | - return h('MessageBacklinks', [ | |
44 | - h('header', 'backlinks:'), | |
45 | - hrefList | |
46 | - ]) | |
47 | - } | |
48 | -} |
modules_basic/message-backlinks.mcss | ||
---|---|---|
@@ -1,21 +1,0 @@ | ||
1 | -MessageBacklinks { | |
2 | - font-size: .9rem | |
3 | - margin-top: .5rem | |
4 | - | |
5 | - header { | |
6 | - $textSubtle | |
7 | - } | |
8 | - | |
9 | - ul { | |
10 | - padding-left: 1rem | |
11 | - | |
12 | - li { | |
13 | - a { | |
14 | - -backlink { | |
15 | - $textSubtle | |
16 | - | |
17 | - } | |
18 | - } | |
19 | - } | |
20 | - } | |
21 | -} |
modules_basic/message-link.js | ||
---|---|---|
@@ -1,35 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | -var ref = require('ssb-ref') | |
3 | - | |
4 | -exports.needs = { | |
5 | - message_name: 'first' | |
6 | -} | |
7 | - | |
8 | -exports.gives = 'message_link' | |
9 | - | |
10 | -exports.create = function (api) { | |
11 | - | |
12 | - return function (id) { | |
13 | - | |
14 | - if('string' !== typeof id) | |
15 | - throw new Error('link must be to message id') | |
16 | - | |
17 | - var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...') | |
18 | - | |
19 | - if(ref.isMsg(id)) | |
20 | - api.message_name(id, function (err, name) { | |
21 | - if(err) console.error(err) | |
22 | - else link.textContent = name | |
23 | - }) | |
24 | - | |
25 | - return link | |
26 | - } | |
27 | -} | |
28 | - | |
29 | - | |
30 | - | |
31 | - | |
32 | - | |
33 | - | |
34 | - | |
35 | - |
modules_basic/message-name.js | ||
---|---|---|
@@ -1,26 +1,0 @@ | ||
1 | - | |
2 | -function title (s) { | |
3 | - var m = /^\n*([^\n]{0,40})/.exec(s) | |
4 | - return m && (m[1].length == 40 ? m[1]+'...' : m[1]) | |
5 | -} | |
6 | - | |
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 | - } | |
25 | -} | |
26 | - |
modules_basic/message.js | ||
---|---|---|
@@ -1,87 +1,0 @@ | ||
1 | -const fs = require('fs') | |
2 | -const h = require('../h') | |
3 | - | |
4 | -exports.needs = { | |
5 | - avatar_name: 'first', | |
6 | - avatar_link: 'first', | |
7 | - message_action: 'map', | |
8 | - message_author: 'first', | |
9 | - message_backlinks: 'first', | |
10 | - message_content: 'first', | |
11 | - message_content_mini: 'first', | |
12 | - message_title: 'first', | |
13 | - message_link: 'first', | |
14 | - message_meta: 'map', | |
15 | -} | |
16 | - | |
17 | -exports.gives = { | |
18 | - message_render: true, | |
19 | - mcss: true | |
20 | -} | |
21 | - | |
22 | -exports.create = function (api) { | |
23 | - return { | |
24 | - message_render, | |
25 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
26 | - } | |
27 | - | |
28 | - function message_render (msg) { | |
29 | - var content = api.message_content_mini(msg) | |
30 | - if (content) return mini(msg, content) | |
31 | - | |
32 | - content = api.message_content(msg) | |
33 | - if (!content) return mini(msg, message_content_mini_fallback(msg)) | |
34 | - | |
35 | - var msgEl = h('Message', { | |
36 | - 'ev-keydown': navigateToMessageOnEnter, | |
37 | - attributes: { | |
38 | - tabindex: '0' | |
39 | - } | |
40 | - }, [ | |
41 | - h('header.author', api.message_author(msg)), | |
42 | - h('section.title', api.message_title(msg)), | |
43 | - h('section.meta', api.message_meta(msg)), | |
44 | - h('section.content', content), | |
45 | - h('section.action', api.message_action(msg)), | |
46 | - h('footer.backlinks', api.message_backlinks(msg)) | |
47 | - ]) | |
48 | - return msgEl | |
49 | - | |
50 | - function navigateToMessageOnEnter (ev) { | |
51 | - // on enter, hit first meta. | |
52 | - if(ev.keyCode == 13) { | |
53 | - | |
54 | - // unless in an input | |
55 | - if (ev.target.nodeName === 'INPUT' | |
56 | - || ev.target.nodeName === 'TEXTAREA') return | |
57 | - | |
58 | - // HACK! (mw) | |
59 | - // there's no exported api to open a new tab. :/ | |
60 | - // it's only done in `app.js` module in an`onhashchange` handler. | |
61 | - // sooooooo yeah this shit for now :) | |
62 | - var wtf = h('a', { href: `#${msg.key}` }) | |
63 | - msgEl.appendChild(wtf) | |
64 | - wtf.click() | |
65 | - msgEl.removeChild(wtf) | |
66 | - } | |
67 | - } | |
68 | - } | |
69 | - | |
70 | - function mini(msg, el) { | |
71 | - return h('Message -mini', { | |
72 | - attributes: { | |
73 | - tabindex: '0' | |
74 | - } | |
75 | - }, [ | |
76 | - h('header.author', api.message_author(msg, { size: 'mini' })), | |
77 | - h('section.meta', api.message_meta(msg)), | |
78 | - h('section.content', el) | |
79 | - ]) | |
80 | - } | |
81 | -} | |
82 | - | |
83 | - | |
84 | -function message_content_mini_fallback(msg) { | |
85 | - return h('code', msg.value.content.type) | |
86 | -} | |
87 | - |
modules_basic/message.mcss | ||
---|---|---|
@@ -1,80 +1,0 @@ | ||
1 | -Message { | |
2 | - padding: 1rem .5rem 1rem 7.5rem | |
3 | - border-top: solid 1px gainsboro | |
4 | - min-height: 5rem | |
5 | - | |
6 | - position: relative | |
7 | - display: flex | |
8 | - flex-direction: row | |
9 | - flex-wrap: wrap | |
10 | - justify-content: flex-end | |
11 | - | |
12 | - header.author { | |
13 | - position: absolute | |
14 | - left: .5rem | |
15 | - } | |
16 | - | |
17 | - section.title { | |
18 | - flex-grow: 1 | |
19 | - font-size: .9rem | |
20 | - } | |
21 | - | |
22 | - section.meta { | |
23 | - display: flex | |
24 | - a { | |
25 | - margin-left: .2rem | |
26 | - $textSubtle | |
27 | - } | |
28 | - | |
29 | - input{ | |
30 | - margin-right: 0 | |
31 | - order: 99 | |
32 | - } | |
33 | - } | |
34 | - | |
35 | - section.content { | |
36 | - flex-basis: 100% | |
37 | - | |
38 | - (img) { | |
39 | - max-width: 100% | |
40 | - } | |
41 | - } | |
42 | - | |
43 | - section.action { | |
44 | - flex-basis: 100% | |
45 | - display: flex | |
46 | - justify-content: flex-end | |
47 | - | |
48 | - a { | |
49 | - margin-left: .5em | |
50 | - } | |
51 | - } | |
52 | - | |
53 | - footer.backlinks { | |
54 | - flex-basis: 100% | |
55 | - } | |
56 | - | |
57 | - | |
58 | - -mini { | |
59 | - font-size: .9rem | |
60 | - justify-content: flex-start | |
61 | - padding: .25rem .5rem | |
62 | - min-height: inherit | |
63 | - | |
64 | - header.author { | |
65 | - order: 0 | |
66 | - position: initial | |
67 | - left: initial | |
68 | - } | |
69 | - | |
70 | - section.content { | |
71 | - order: 1 | |
72 | - flex-basis: initial | |
73 | - flex-grow: 1 | |
74 | - } | |
75 | - | |
76 | - section.meta { | |
77 | - order: 2 | |
78 | - } | |
79 | - } | |
80 | -} |
modules_basic/names.js | ||
---|---|---|
@@ -1,179 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var many = require('pull-many') | |
3 | -var mfr = require('map-filter-reduce') | |
4 | - | |
5 | -function all(stream, cb) { | |
6 | - pull(stream, pull.collect(cb)) | |
7 | -} | |
8 | - | |
9 | -exports.needs = { | |
10 | - sbot_links2: 'first', | |
11 | - sbot_query: 'first' | |
12 | -} | |
13 | - | |
14 | -exports.gives = { | |
15 | - connection_status: true, | |
16 | - signifier: true, | |
17 | - signified: true, | |
18 | -} | |
19 | - | |
20 | -/* | |
21 | - filter(rel: ['mentions', prefix('@')]) | reduce(name: rel[1], value: count()) | |
22 | -*/ | |
23 | - | |
24 | -var filter = { | |
25 | - $filter: { | |
26 | - rel: ["mentions", {$prefix: "@"}] | |
27 | - } | |
28 | -} | |
29 | -var map = { | |
30 | - $map: { | |
31 | - name: ['rel', 1], | |
32 | - id: 'dest', | |
33 | - ts: 'ts', | |
34 | - } | |
35 | -} | |
36 | - | |
37 | -var reduce = { | |
38 | - $reduce: { | |
39 | - name: 'name', | |
40 | - id: 'id', | |
41 | - rank: {$count: true}, | |
42 | - ts: {$max: 'ts'} | |
43 | - } | |
44 | -} | |
45 | - | |
46 | -var filter2 = { | |
47 | - $filter: { | |
48 | - value: { | |
49 | - content: { | |
50 | - type: "about", | |
51 | - name: {"$prefix": ""}, | |
52 | - about: {"$prefix": ""} | |
53 | - } | |
54 | - } | |
55 | - } | |
56 | -} | |
57 | - | |
58 | -var map2 = { | |
59 | - $map: { | |
60 | - name: ["value", "content", "name"], | |
61 | - id: ['value', 'content', 'about'], | |
62 | - ts: "timestamp" | |
63 | - } | |
64 | -} | |
65 | - | |
66 | -//union with this query... | |
67 | - | |
68 | -var names = NAMES = [] | |
69 | -function update(name) { | |
70 | - var n = names.find(function (e) { | |
71 | - return e.id == name.id && e.name == e.name | |
72 | - }) | |
73 | - if(!n) { | |
74 | - name.rank = 1 | |
75 | - //this should be inserted at the right place... | |
76 | - names.push(name) | |
77 | - } | |
78 | - else | |
79 | - n.rank = n.rank += (name.rank || 1) | |
80 | -} | |
81 | - | |
82 | -var ready = false, waiting = [] | |
83 | - | |
84 | -var merge = { | |
85 | - $reduce: { | |
86 | - name: 'name', | |
87 | - id: 'id', | |
88 | - rank: {$sum: 'rank'}, | |
89 | - ts: {$max: 'ts'} | |
90 | - } | |
91 | -} | |
92 | - | |
93 | -function add_sigil(stream) { | |
94 | - return pull(stream, pull.map(function (e) { | |
95 | - if (e && e.id && e.name && e.id[0] !== e.name[0]) | |
96 | - e.name = e.id[0] + e.name | |
97 | - return e | |
98 | - }) | |
99 | - ) | |
100 | -} | |
101 | - | |
102 | -var queryNamedGitRepos = [ | |
103 | - {$filter: { | |
104 | - value: { | |
105 | - content: { | |
106 | - type: "git-repo", | |
107 | - name: {"$prefix": ""} | |
108 | - } | |
109 | - } | |
110 | - }}, | |
111 | - {$map: { | |
112 | - name: ["value", "content", "name"], | |
113 | - id: ['key'], | |
114 | - ts: "timestamp" | |
115 | - }}, | |
116 | - reduce | |
117 | -] | |
118 | -exports.create = function (api) { | |
119 | - | |
120 | - var exports = {} | |
121 | - exports.connection_status = function (err) { | |
122 | - if(!err) { | |
123 | - pull( | |
124 | - many([ | |
125 | - api.sbot_links2({query: [filter, map, reduce]}), | |
126 | - add_sigil(api.sbot_query({query: [filter2, map2, reduce]})), | |
127 | - add_sigil(api.sbot_query({query: queryNamedGitRepos})) | |
128 | - ]), | |
129 | - //reducing also ensures order by the lookup properties | |
130 | - //in this case: [name, id] | |
131 | - mfr.reduce(merge), | |
132 | - pull.collect(function (err, ary) { | |
133 | - if(!err) { | |
134 | - NAMES = names = ary | |
135 | - ready = true | |
136 | - while(waiting.length) waiting.shift()() | |
137 | - } | |
138 | - }) | |
139 | - ) | |
140 | - | |
141 | - pull(many([ | |
142 | - api.sbot_links2({query: [filter, map], old: false}), | |
143 | - add_sigil(api.sbot_query({query: [filter2, map2], old: false})), | |
144 | - add_sigil(api.sbot_query({query: queryNamedGitRepos, old: false})) | |
145 | - ]), | |
146 | - pull.drain(update)) | |
147 | - } | |
148 | - } | |
149 | - | |
150 | - function async(fn) { | |
151 | - return function (value, cb) { | |
152 | - function go () { cb(null, fn(value)) } | |
153 | - if(ready) go() | |
154 | - else waiting.push(go) | |
155 | - } | |
156 | - } | |
157 | - | |
158 | - function rank(ary) { | |
159 | - //sort by most used, or most recently used | |
160 | - return ary.sort(function (a, b) { return b.rank - a.rank || b.ts - a.ts }) | |
161 | - } | |
162 | - | |
163 | - //we are just iterating over the entire array. | |
164 | - //if this becomes a problem, maintain two arrays | |
165 | - //one of each sort order, but do not duplicate the objects. | |
166 | - //that should mean the space required is just 2x object references, | |
167 | - //not 2x objects, and we can use binary search to find matches. | |
168 | - | |
169 | - exports.signifier = async(function (id) { | |
170 | - return rank(names.filter(function (e) { return e.id == id})) | |
171 | - }) | |
172 | - | |
173 | - exports.signified = async(function (name) { | |
174 | - var rx = new RegExp('^'+name) | |
175 | - return rank(names.filter(function (e) { return rx.test(e.name) })) | |
176 | - }) | |
177 | - | |
178 | - return exports | |
179 | -} |
modules_basic/search-box.js | ||
---|---|---|
@@ -1,68 +1,0 @@ | ||
1 | -'use strict' | |
2 | -var cont = require('cont') | |
3 | -var h = require('hyperscript') | |
4 | -var suggest = require('suggest-box') | |
5 | - | |
6 | -exports.needs = { | |
7 | - sbot_query: 'first', sbot_links2: 'first', | |
8 | - suggest_search: 'map' //REWRITE | |
9 | -} | |
10 | - | |
11 | -exports.gives = 'search_box' | |
12 | - | |
13 | -exports.create = function (api) { | |
14 | - | |
15 | - return function (go) { | |
16 | - | |
17 | - var suggestBox | |
18 | - var search = h('input.searchprompt', { | |
19 | - type: 'search', | |
20 | - placeholder: 'Commands', | |
21 | - onkeydown: function (ev) { | |
22 | - switch (ev.keyCode) { | |
23 | - case 13: // enter | |
24 | - if (suggestBox && suggestBox.active) { | |
25 | - suggestBox.complete() | |
26 | - ev.stopPropagation() | |
27 | - } | |
28 | - if (go(search.value.trim(), !ev.ctrlKey)) | |
29 | - search.blur() | |
30 | - return | |
31 | - case 27: // escape | |
32 | - ev.preventDefault() | |
33 | - search.blur() | |
34 | - return | |
35 | - } | |
36 | - } | |
37 | - }) | |
38 | - | |
39 | - search.activate = function (sigil, ev) { | |
40 | - search.focus() | |
41 | - ev.preventDefault() | |
42 | - if (search.value[0] === sigil) { | |
43 | - search.selectionStart = 1 | |
44 | - search.selectionEnd = search.value.length | |
45 | - } else { | |
46 | - search.value = sigil | |
47 | - } | |
48 | - } | |
49 | - | |
50 | - // delay until the element has a parent | |
51 | - setTimeout(function () { | |
52 | - suggestBox = suggest(search, function (word, cb) { | |
53 | - cont.para(api.suggest_search(word)) | |
54 | - (function (err, ary) { | |
55 | - if(err) return cb(err) | |
56 | - | |
57 | - cb(null, ary.filter(Boolean).reduce(function (a, b) { | |
58 | - return a.concat(b) | |
59 | - }, [])) | |
60 | - }) | |
61 | - }, {}) | |
62 | - }, 10) | |
63 | - | |
64 | - return search | |
65 | - } | |
66 | - | |
67 | -} | |
68 | - |
modules_basic/suggest-mentions.js | ||
---|---|---|
@@ -1,63 +1,0 @@ | ||
1 | - | |
2 | -exports.needs = { | |
3 | - sbot_links2: 'first', | |
4 | - blob_url: 'first', | |
5 | - signified: 'first', | |
6 | - builtin_tabs: 'map' | |
7 | -} | |
8 | - | |
9 | -exports.gives = { | |
10 | - suggest_mentions: true, | |
11 | - suggest_search: true | |
12 | -} | |
13 | - | |
14 | -exports.create = function (api) { | |
15 | - | |
16 | - return { | |
17 | - suggest_mentions: function (word) { | |
18 | - return function (cb) { | |
19 | - if(!/^[%&@]\w/.test(word)) return cb() | |
20 | - | |
21 | - api.signified(word, function (err, names) { | |
22 | - if(err) cb(err) | |
23 | - else cb(null, names.map(function (e) { | |
24 | - return { | |
25 | - title: e.name + ': ' + e.id.substring(0,10)+' ('+e.rank+')', | |
26 | - value: '['+e.name+']('+e.id+')', | |
27 | - rank: e.rank, | |
28 | - //TODO: avatar images... | |
29 | - } | |
30 | - })) | |
31 | - }) | |
32 | - } | |
33 | - }, | |
34 | - | |
35 | - suggest_search: function (query) { | |
36 | - return function (cb) { | |
37 | - if(/^[@%]\w/.test(query)) { | |
38 | - api.signified(query, function (_, names) { | |
39 | - cb(null, names.map(function (e) { | |
40 | - return { | |
41 | - title: e.name + ':'+e.id.substring(0, 10), | |
42 | - value: e.id, | |
43 | - subtitle: e.rank, | |
44 | - rank: e.rank | |
45 | - } | |
46 | - })) | |
47 | - }) | |
48 | - | |
49 | - } else if(/^\//.test(query)) { | |
50 | - var tabs = [].concat.apply([], api.builtin_tabs()) | |
51 | - cb(null, tabs.filter(function (name) { | |
52 | - return name.substr(0, query.length) === query | |
53 | - }).map(function (name) { | |
54 | - return { | |
55 | - title: name, | |
56 | - value: name, | |
57 | - } | |
58 | - })) | |
59 | - } else cb() | |
60 | - } | |
61 | - } | |
62 | - } | |
63 | -} |
modules_core/index.js | ||
---|---|---|
@@ -1,15 +1,15 @@ | ||
1 | 1 … | module.exports = { |
2 | -// "_screen_view.js": require('./_screen_view.js'), | |
3 | - "app.js": require('./app.js'), | |
4 | - "blob-url.js": require('./blob-url.js'), | |
5 | - "crypto.js": require('./crypto.js'), | |
6 | - "external-confirm.js": require('./external-confirm.js'), | |
7 | - "file-input.js": require('./file-input.js'), | |
8 | - "menu.js": require('./menu.js'), | |
9 | - "message-confirm.js": require('./message-confirm.js'), | |
10 | - "tabs.js": require('./tabs.js'), | |
11 | - "sbot.js": require('./sbot.js'), | |
12 | - "styles.js": require('./styles.js'), | |
13 | - "style-mixins.js": require('./style-mixins.js') | |
2 … | + 'app': require('./app'), | |
3 … | + 'blob-url': require('./blob-url'), | |
4 … | + 'crypto': require('./crypto'), | |
5 … | + 'external-confirm': require('./external-confirm'), | |
6 … | + 'file-input': require('./file-input'), | |
7 … | + 'menu': require('./menu'), | |
8 … | + 'names': require('./names'), | |
9 … | + 'tabs': require('./tabs'), | |
10 … | + 'sbot': require('./sbot'), | |
11 … | + 'search-box': require('./search-box'), | |
12 … | + 'style': require('./style'), | |
13 … | + 'suggest-mentions': require('./suggest-mentions') | |
14 | 14 … | } |
15 | 15 … |
modules_core/menu.js | ||
---|---|---|
@@ -1,23 +1,28 @@ | ||
1 | -var h = require('hyperscript') | |
1 … | +const h = require('hyperscript') | |
2 | 2 … | |
3 | 3 … | module.exports = { |
4 | - needs: {menu_items: 'map'}, | |
5 | - gives: {connection_status: true, menu: true}, | |
4 … | + needs: { | |
5 … | + menu_items: 'map' | |
6 … | + }, | |
7 … | + gives: { | |
8 … | + connection_status: true, | |
9 … | + menu: true, | |
10 … | + menu_items: true | |
11 … | + }, | |
6 | 12 … | create: function (api) { |
13 … | + const { menu_items } = api | |
7 | 14 … | |
8 | - var menu_items = api.menu_items | |
9 | - | |
10 | 15 … | var status = h('div.status.error') //start off disconnected |
11 | 16 … | var list = h('div.menu.column', {style: 'display: none;'}) |
12 | 17 … | |
13 | - var menu = h('div.column', status, list , { | |
14 | - onmouseover: function (e) { | |
15 | - list.style.display = 'flex' | |
16 | - }, onmouseout: function () { | |
17 | - list.style.display = 'none' | |
18 | - } | |
19 | - }) | |
18 … | + var menu = h('div.column', { | |
19 … | + onmouseover: () => list.style.display = 'flex', | |
20 … | + onmouseout: () => list.style.display = 'none' | |
21 … | + }, [ | |
22 … | + status, | |
23 … | + list | |
24 … | + ]) | |
20 | 25 … | |
21 | 26 … | setTimeout(function () { |
22 | 27 … | menu_items().forEach(function (el) { |
23 | 28 … | if(el) |
@@ -25,15 +30,14 @@ | ||
25 | 30 … | }) |
26 | 31 … | }, 0) |
27 | 32 … | |
28 | 33 … | return { |
29 | - connection_status: function (err) { | |
34 … | + connection_status: (err) => { | |
30 | 35 … | if(err) status.classList.add('error') |
31 | 36 … | else status.classList.remove('error') |
32 | 37 … | }, |
33 | - menu: function () { | |
34 | - return menu | |
35 | - } | |
38 … | + menu: () => menu, | |
39 … | + menu_items: () => null | |
36 | 40 … | } |
37 | 41 … | } |
38 | 42 … | } |
39 | 43 … |
modules_core/sbot.js | ||
---|---|---|
@@ -59,8 +59,14 @@ | ||
59 | 59 … | var opts = createConfig() |
60 | 60 … | var sbot = null |
61 | 61 … | var connection_status = [] |
62 | 62 … | |
63 … | + var rec = { | |
64 … | + sync: () => {}, | |
65 … | + async: () => {}, | |
66 … | + source: () => {}, | |
67 … | + } | |
68 … | + | |
63 | 69 … | var rec = Reconnect(function (isConn) { |
64 | 70 … | function notify (value) { |
65 | 71 … | isConn(value); api.connection_status(value) //.forEach(function (fn) { fn(value) }) |
66 | 72 … | } |
modules_core/tabs.js | |||
---|---|---|---|
@@ -8,9 +8,14 @@ | |||
8 | 8 … | if(el.tagName !== 'A') return ancestor(el.parentElement) | |
9 | 9 … | return el | |
10 | 10 … | } | |
11 | 11 … | ||
12 | -exports.needs = {screen_view: 'first', search_box: 'first', menu: 'first', 'external_confirm':'first'} | ||
12 … | +exports.needs = { | ||
13 … | + screen_view: 'first', | ||
14 … | + search_box: 'first', | ||
15 … | + menu: 'first', | ||
16 … | + external_confirm:'first' | ||
17 … | +} | ||
13 | 18 … | ||
14 | 19 … | exports.gives = 'screen_view' | |
15 | 20 … | ||
16 | 21 … | exports.create = function (api) { |
modules_core/index.test.js | ||
---|---|---|
@@ -1,0 +1,12 @@ | ||
1 … | +const test = require('tape') | |
2 … | +const combine = require('depject') | |
3 … | + | |
4 … | +process.env.ssb_appname = 'test' | |
5 … | + | |
6 … | +const core = require('./') | |
7 … | + | |
8 … | +test('modules_core has no outside deps', t => { | |
9 … | + t.ok(combine(core)) | |
10 … | + t.end() | |
11 … | +}) | |
12 … | + |
modules_core/message-confirm.js | ||
---|---|---|
@@ -1,73 +1,0 @@ | ||
1 | -var fs = require('fs') | |
2 | -var lightbox = require('hyperlightbox') | |
3 | -var h = require('../h') | |
4 | -var self_id = require('../keys').id | |
5 | -//publish or add | |
6 | - | |
7 | -exports.needs = { | |
8 | - publish: 'first', | |
9 | - message_render: 'first', | |
10 | - avatar: 'first', | |
11 | - message_meta: 'map' | |
12 | -} | |
13 | - | |
14 | -exports.gives = { | |
15 | - message_confirm: true, | |
16 | - mcss: true | |
17 | -} | |
18 | - | |
19 | -exports.create = function (api) { | |
20 | - return { | |
21 | - message_confirm, | |
22 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
23 | - } | |
24 | - | |
25 | - function message_confirm (content, cb) { | |
26 | - | |
27 | - cb = cb || function () {} | |
28 | - | |
29 | - var lb = lightbox() | |
30 | - document.body.appendChild(lb) | |
31 | - | |
32 | - var msg = { | |
33 | - key: "DRAFT", | |
34 | - value: { | |
35 | - author: self_id, | |
36 | - previous: null, | |
37 | - sequence: null, | |
38 | - timestamp: Date.now(), | |
39 | - content: content | |
40 | - } | |
41 | - } | |
42 | - | |
43 | - var okay = h('button', { | |
44 | - 'ev-click': () => { | |
45 | - lb.remove() | |
46 | - api.publish(content, cb) | |
47 | - }}, | |
48 | - 'okay' | |
49 | - ) | |
50 | - | |
51 | - var cancel = h('button', { | |
52 | - 'ev-click': () => { | |
53 | - lb.remove() | |
54 | - cb(null) | |
55 | - }}, | |
56 | - 'Cancel' | |
57 | - ) | |
58 | - | |
59 | - okay.addEventListener('keydown', function (ev) { | |
60 | - if(ev.keyCode === 27) cancel.click() //escape | |
61 | - }) | |
62 | - | |
63 | - lb.show(h('MessageConfirm', [ | |
64 | - h('header -preview_description', h('h1', 'Preview')), | |
65 | - h('section -message_preview', api.message_render(msg)), | |
66 | - h('section -actions', [okay, cancel]) | |
67 | - ] | |
68 | - )) | |
69 | - | |
70 | - okay.focus() | |
71 | - } | |
72 | -} | |
73 | - |
modules_core/message-confirm.mcss | ||
---|---|---|
@@ -1,39 +1,0 @@ | ||
1 | -MessageConfirm { | |
2 | - section { | |
3 | - -preview_description { | |
4 | - } | |
5 | - | |
6 | - -message_preview { | |
7 | - background-color: white | |
8 | - | |
9 | - div { | |
10 | - border: none | |
11 | - | |
12 | - header.author { | |
13 | - div { | |
14 | - section { | |
15 | - -timestamp { | |
16 | - display: none | |
17 | - } | |
18 | - } | |
19 | - } | |
20 | - } | |
21 | - | |
22 | - section.action { | |
23 | - display: none | |
24 | - } | |
25 | - } | |
26 | - } | |
27 | - | |
28 | - -actions { | |
29 | - margin-top: 1rem | |
30 | - display: flex | |
31 | - justify-content: flex-end | |
32 | - | |
33 | - button { | |
34 | - margin: 0 0 0 1rem | |
35 | - } | |
36 | - } | |
37 | - } | |
38 | -} | |
39 | - |
modules_core/names.js | ||
---|---|---|
@@ -1,0 +1,179 @@ | ||
1 … | +var pull = require('pull-stream') | |
2 … | +var many = require('pull-many') | |
3 … | +var mfr = require('map-filter-reduce') | |
4 … | + | |
5 … | +function all(stream, cb) { | |
6 … | + pull(stream, pull.collect(cb)) | |
7 … | +} | |
8 … | + | |
9 … | +exports.needs = { | |
10 … | + sbot_links2: 'first', | |
11 … | + sbot_query: 'first' | |
12 … | +} | |
13 … | + | |
14 … | +exports.gives = { | |
15 … | + connection_status: true, | |
16 … | + signifier: true, | |
17 … | + signified: true, | |
18 … | +} | |
19 … | + | |
20 … | +/* | |
21 … | + filter(rel: ['mentions', prefix('@')]) | reduce(name: rel[1], value: count()) | |
22 … | +*/ | |
23 … | + | |
24 … | +var filter = { | |
25 … | + $filter: { | |
26 … | + rel: ["mentions", {$prefix: "@"}] | |
27 … | + } | |
28 … | +} | |
29 … | +var map = { | |
30 … | + $map: { | |
31 … | + name: ['rel', 1], | |
32 … | + id: 'dest', | |
33 … | + ts: 'ts', | |
34 … | + } | |
35 … | +} | |
36 … | + | |
37 … | +var reduce = { | |
38 … | + $reduce: { | |
39 … | + name: 'name', | |
40 … | + id: 'id', | |
41 … | + rank: {$count: true}, | |
42 … | + ts: {$max: 'ts'} | |
43 … | + } | |
44 … | +} | |
45 … | + | |
46 … | +var filter2 = { | |
47 … | + $filter: { | |
48 … | + value: { | |
49 … | + content: { | |
50 … | + type: "about", | |
51 … | + name: {"$prefix": ""}, | |
52 … | + about: {"$prefix": ""} | |
53 … | + } | |
54 … | + } | |
55 … | + } | |
56 … | +} | |
57 … | + | |
58 … | +var map2 = { | |
59 … | + $map: { | |
60 … | + name: ["value", "content", "name"], | |
61 … | + id: ['value', 'content', 'about'], | |
62 … | + ts: "timestamp" | |
63 … | + } | |
64 … | +} | |
65 … | + | |
66 … | +//union with this query... | |
67 … | + | |
68 … | +var names = NAMES = [] | |
69 … | +function update(name) { | |
70 … | + var n = names.find(function (e) { | |
71 … | + return e.id == name.id && e.name == e.name | |
72 … | + }) | |
73 … | + if(!n) { | |
74 … | + name.rank = 1 | |
75 … | + //this should be inserted at the right place... | |
76 … | + names.push(name) | |
77 … | + } | |
78 … | + else | |
79 … | + n.rank = n.rank += (name.rank || 1) | |
80 … | +} | |
81 … | + | |
82 … | +var ready = false, waiting = [] | |
83 … | + | |
84 … | +var merge = { | |
85 … | + $reduce: { | |
86 … | + name: 'name', | |
87 … | + id: 'id', | |
88 … | + rank: {$sum: 'rank'}, | |
89 … | + ts: {$max: 'ts'} | |
90 … | + } | |
91 … | +} | |
92 … | + | |
93 … | +function add_sigil(stream) { | |
94 … | + return pull(stream, pull.map(function (e) { | |
95 … | + if (e && e.id && e.name && e.id[0] !== e.name[0]) | |
96 … | + e.name = e.id[0] + e.name | |
97 … | + return e | |
98 … | + }) | |
99 … | + ) | |
100 … | +} | |
101 … | + | |
102 … | +var queryNamedGitRepos = [ | |
103 … | + {$filter: { | |
104 … | + value: { | |
105 … | + content: { | |
106 … | + type: "git-repo", | |
107 … | + name: {"$prefix": ""} | |
108 … | + } | |
109 … | + } | |
110 … | + }}, | |
111 … | + {$map: { | |
112 … | + name: ["value", "content", "name"], | |
113 … | + id: ['key'], | |
114 … | + ts: "timestamp" | |
115 … | + }}, | |
116 … | + reduce | |
117 … | +] | |
118 … | +exports.create = function (api) { | |
119 … | + | |
120 … | + var exports = {} | |
121 … | + exports.connection_status = function (err) { | |
122 … | + if(!err) { | |
123 … | + pull( | |
124 … | + many([ | |
125 … | + api.sbot_links2({query: [filter, map, reduce]}), | |
126 … | + add_sigil(api.sbot_query({query: [filter2, map2, reduce]})), | |
127 … | + add_sigil(api.sbot_query({query: queryNamedGitRepos})) | |
128 … | + ]), | |
129 … | + //reducing also ensures order by the lookup properties | |
130 … | + //in this case: [name, id] | |
131 … | + mfr.reduce(merge), | |
132 … | + pull.collect(function (err, ary) { | |
133 … | + if(!err) { | |
134 … | + NAMES = names = ary | |
135 … | + ready = true | |
136 … | + while(waiting.length) waiting.shift()() | |
137 … | + } | |
138 … | + }) | |
139 … | + ) | |
140 … | + | |
141 … | + pull(many([ | |
142 … | + api.sbot_links2({query: [filter, map], old: false}), | |
143 … | + add_sigil(api.sbot_query({query: [filter2, map2], old: false})), | |
144 … | + add_sigil(api.sbot_query({query: queryNamedGitRepos, old: false})) | |
145 … | + ]), | |
146 … | + pull.drain(update)) | |
147 … | + } | |
148 … | + } | |
149 … | + | |
150 … | + function async(fn) { | |
151 … | + return function (value, cb) { | |
152 … | + function go () { cb(null, fn(value)) } | |
153 … | + if(ready) go() | |
154 … | + else waiting.push(go) | |
155 … | + } | |
156 … | + } | |
157 … | + | |
158 … | + function rank(ary) { | |
159 … | + //sort by most used, or most recently used | |
160 … | + return ary.sort(function (a, b) { return b.rank - a.rank || b.ts - a.ts }) | |
161 … | + } | |
162 … | + | |
163 … | + //we are just iterating over the entire array. | |
164 … | + //if this becomes a problem, maintain two arrays | |
165 … | + //one of each sort order, but do not duplicate the objects. | |
166 … | + //that should mean the space required is just 2x object references, | |
167 … | + //not 2x objects, and we can use binary search to find matches. | |
168 … | + | |
169 … | + exports.signifier = async(function (id) { | |
170 … | + return rank(names.filter(function (e) { return e.id == id})) | |
171 … | + }) | |
172 … | + | |
173 … | + exports.signified = async(function (name) { | |
174 … | + var rx = new RegExp('^'+name) | |
175 … | + return rank(names.filter(function (e) { return rx.test(e.name) })) | |
176 … | + }) | |
177 … | + | |
178 … | + return exports | |
179 … | +} |
modules_core/style-mixins.js | ||
---|---|---|
@@ -1,22 +1,0 @@ | ||
1 | - | |
2 | -const mixins = ` | |
3 | - $textPrimary { | |
4 | - color: #222 | |
5 | - } | |
6 | - | |
7 | - $textSubtle { | |
8 | - color: gray | |
9 | - } | |
10 | -` | |
11 | - | |
12 | -module.exports = { | |
13 | - gives: { | |
14 | - mcss: true | |
15 | - }, | |
16 | - create: function (api) { | |
17 | - return { | |
18 | - mcss: () => mixins | |
19 | - } | |
20 | - } | |
21 | -} | |
22 | - |
modules_core/search-box.js | ||
---|---|---|
@@ -1,0 +1,68 @@ | ||
1 … | +'use strict' | |
2 … | +var cont = require('cont') | |
3 … | +var h = require('hyperscript') | |
4 … | +var suggest = require('suggest-box') | |
5 … | + | |
6 … | +exports.needs = { | |
7 … | + sbot_query: 'first', sbot_links2: 'first', | |
8 … | + suggest_search: 'map' //REWRITE | |
9 … | +} | |
10 … | + | |
11 … | +exports.gives = 'search_box' | |
12 … | + | |
13 … | +exports.create = function (api) { | |
14 … | + | |
15 … | + return function (go) { | |
16 … | + | |
17 … | + var suggestBox | |
18 … | + var search = h('input.searchprompt', { | |
19 … | + type: 'search', | |
20 … | + placeholder: 'Commands', | |
21 … | + onkeydown: function (ev) { | |
22 … | + switch (ev.keyCode) { | |
23 … | + case 13: // enter | |
24 … | + if (suggestBox && suggestBox.active) { | |
25 … | + suggestBox.complete() | |
26 … | + ev.stopPropagation() | |
27 … | + } | |
28 … | + if (go(search.value.trim(), !ev.ctrlKey)) | |
29 … | + search.blur() | |
30 … | + return | |
31 … | + case 27: // escape | |
32 … | + ev.preventDefault() | |
33 … | + search.blur() | |
34 … | + return | |
35 … | + } | |
36 … | + } | |
37 … | + }) | |
38 … | + | |
39 … | + search.activate = function (sigil, ev) { | |
40 … | + search.focus() | |
41 … | + ev.preventDefault() | |
42 … | + if (search.value[0] === sigil) { | |
43 … | + search.selectionStart = 1 | |
44 … | + search.selectionEnd = search.value.length | |
45 … | + } else { | |
46 … | + search.value = sigil | |
47 … | + } | |
48 … | + } | |
49 … | + | |
50 … | + // delay until the element has a parent | |
51 … | + setTimeout(function () { | |
52 … | + suggestBox = suggest(search, function (word, cb) { | |
53 … | + cont.para(api.suggest_search(word)) | |
54 … | + (function (err, ary) { | |
55 … | + if(err) return cb(err) | |
56 … | + | |
57 … | + cb(null, ary.filter(Boolean).reduce(function (a, b) { | |
58 … | + return a.concat(b) | |
59 … | + }, [])) | |
60 … | + }) | |
61 … | + }, {}) | |
62 … | + }, 10) | |
63 … | + | |
64 … | + return search | |
65 … | + } | |
66 … | + | |
67 … | +} | |
68 … | + |
modules_core/styles.js | ||
---|---|---|
@@ -1,33 +1,0 @@ | ||
1 | -var compile = require('micro-css') | |
2 | -var fs = require('fs') | |
3 | -var Path = require('path') | |
4 | - | |
5 | -// TODO distribute these styles across all | |
6 | -// the relevant modules, not as a core style. | |
7 | -var coreStyle = fs.readFileSync(Path.join(__dirname, '../style.css')) | |
8 | - | |
9 | -module.exports = { | |
10 | - needs: { | |
11 | - mcss: 'map', | |
12 | - css: 'map' | |
13 | - }, | |
14 | - gives: { | |
15 | - mcss: true, | |
16 | - css: true, | |
17 | - styles: true | |
18 | - }, | |
19 | - create: function (api) { | |
20 | - var styles = '' | |
21 | - process.nextTick(function () { | |
22 | - var mcss = api.mcss().join('\n') | |
23 | - var css = api.css().join('\n') | |
24 | - styles = coreStyle + compile(mcss) + css | |
25 | - }) | |
26 | - return { | |
27 | - styles: function () { return styles }, | |
28 | - // export empty styles | |
29 | - mcss: function () { return '' }, | |
30 | - css: function () { return '' } | |
31 | - } | |
32 | - } | |
33 | -} |
modules_core/style/index.js | ||
---|---|---|
@@ -1,0 +1,5 @@ | ||
1 … | +module.exports = { | |
2 … | + 'styles': require('./styles'), | |
3 … | + 'mixins': require('./mixins') | |
4 … | +} | |
5 … | + |
modules_core/style/mixins.js | ||
---|---|---|
@@ -1,0 +1,22 @@ | ||
1 … | + | |
2 … | +const mixins = ` | |
3 … | + $textPrimary { | |
4 … | + color: #222 | |
5 … | + } | |
6 … | + | |
7 … | + $textSubtle { | |
8 … | + color: gray | |
9 … | + } | |
10 … | +` | |
11 … | + | |
12 … | +module.exports = { | |
13 … | + gives: { | |
14 … | + mcss: true | |
15 … | + }, | |
16 … | + create: function (api) { | |
17 … | + return { | |
18 … | + mcss: () => mixins | |
19 … | + } | |
20 … | + } | |
21 … | +} | |
22 … | + |
modules_core/style/styles.js | ||
---|---|---|
@@ -1,0 +1,33 @@ | ||
1 … | +var compile = require('micro-css') | |
2 … | +var fs = require('fs') | |
3 … | +var Path = require('path') | |
4 … | + | |
5 … | +// TODO distribute these styles across all | |
6 … | +// the relevant modules, not as a core style. | |
7 … | +var coreStyle = fs.readFileSync(Path.join(__dirname, '../../style.css')) | |
8 … | + | |
9 … | +module.exports = { | |
10 … | + needs: { | |
11 … | + mcss: 'map', | |
12 … | + css: 'map' | |
13 … | + }, | |
14 … | + gives: { | |
15 … | + mcss: true, | |
16 … | + css: true, | |
17 … | + styles: true | |
18 … | + }, | |
19 … | + create: function (api) { | |
20 … | + var styles = '' | |
21 … | + process.nextTick(function () { | |
22 … | + var mcss = api.mcss().join('\n') | |
23 … | + var css = api.css().join('\n') | |
24 … | + styles = coreStyle + compile(mcss) + css | |
25 … | + }) | |
26 … | + return { | |
27 … | + styles: function () { return styles }, | |
28 … | + // export empty styles | |
29 … | + mcss: function () { return '' }, | |
30 … | + css: function () { return '' } | |
31 … | + } | |
32 … | + } | |
33 … | +} |
modules_core/suggest-mentions.js | ||
---|---|---|
@@ -1,0 +1,68 @@ | ||
1 … | + | |
2 … | +exports.needs = { | |
3 … | + sbot_links2: 'first', | |
4 … | + blob_url: 'first', | |
5 … | + signified: 'first', | |
6 … | + builtin_tabs: 'map' | |
7 … | +} | |
8 … | + | |
9 … | +exports.gives = { | |
10 … | + suggest_mentions: true, | |
11 … | + suggest_search: true, | |
12 … | + builtin_tabs: true | |
13 … | +} | |
14 … | + | |
15 … | +exports.create = function (api) { | |
16 … | + | |
17 … | + return { | |
18 … | + suggest_mentions, | |
19 … | + suggest_search, | |
20 … | + builtin_tabs: () => null | |
21 … | + } | |
22 … | + | |
23 … | + function suggest_mentions (word) { | |
24 … | + return function (cb) { | |
25 … | + if(!/^[%&@]\w/.test(word)) return cb() | |
26 … | + | |
27 … | + api.signified(word, function (err, names) { | |
28 … | + if(err) cb(err) | |
29 … | + else cb(null, names.map(function (e) { | |
30 … | + return { | |
31 … | + title: e.name + ': ' + e.id.substring(0,10)+' ('+e.rank+')', | |
32 … | + value: '['+e.name+']('+e.id+')', | |
33 … | + rank: e.rank, | |
34 … | + //TODO: avatar images... | |
35 … | + } | |
36 … | + })) | |
37 … | + }) | |
38 … | + } | |
39 … | + } | |
40 … | + | |
41 … | + function suggest_search (query) { | |
42 … | + return function (cb) { | |
43 … | + if(/^[@%]\w/.test(query)) { | |
44 … | + api.signified(query, function (_, names) { | |
45 … | + cb(null, names.map(function (e) { | |
46 … | + return { | |
47 … | + title: e.name + ':'+e.id.substring(0, 10), | |
48 … | + value: e.id, | |
49 … | + subtitle: e.rank, | |
50 … | + rank: e.rank | |
51 … | + } | |
52 … | + })) | |
53 … | + }) | |
54 … | + | |
55 … | + } else if(/^\//.test(query)) { | |
56 … | + var tabs = [].concat.apply([], api.builtin_tabs()) | |
57 … | + cb(null, tabs.filter(function (name) { | |
58 … | + return name.substr(0, query.length) === query | |
59 … | + }).map(function (name) { | |
60 … | + return { | |
61 … | + title: name, | |
62 … | + value: name, | |
63 … | + } | |
64 … | + })) | |
65 … | + } else cb() | |
66 … | + } | |
67 … | + } | |
68 … | +} |
modules_embedded/index.js | ||
---|---|---|
@@ -1,11 +1,12 @@ | ||
1 | 1 … | module.exports = { |
2 | - "_screen_view.js": require('../modules_core/_screen_view.js'), | |
3 | - "app.js": require('../modules_core/app.js'), | |
4 | - "blob-url.js": require('../modules_core/blob-url.js'), | |
5 | - "crypto.js": require('../modules_core/crypto.js'), | |
6 | - "file-input.js": require('../modules_core/file-input.js'), | |
7 | - "menu.js": require('../modules_core/menu.js'), | |
8 | - "message-confirm.js": require('../modules_core/message-confirm.js'), | |
9 | - "tabs.js": require('../modules_core/tabs.js'), | |
10 | - "sbot.js": require('./sbot.js') | |
2 … | + "_screen_view.": require('../modules_core/_screen_view.'), | |
3 … | + "app.": require('../modules_core/app.'), | |
4 … | + "blob-url.": require('../modules_core/blob-url.'), | |
5 … | + "crypto.": require('../modules_core/crypto.'), | |
6 … | + "file-input.": require('../modules_core/file-input.'), | |
7 … | + "menu.": require('../modules_core/menu.'), | |
8 … | + "message-confirm.": require('../modules_core/message-confirm.'), | |
9 … | + "tabs.": require('../modules_core/tabs.'), | |
10 … | + "sbot.": require('./sbot.') | |
11 | 11 … | } |
12 … | + |
modules_extra/index.js | ||
---|---|---|
@@ -1,23 +1,24 @@ | ||
1 | 1 … | module.exports = { |
2 | - // "audio-mp3.js": require('./audio-mp3.js'), | |
3 | - "blob.js": require('./blob.js'), | |
4 | - "channel.js": require('./channel.js'), | |
5 | - "emoji.js": require('./emoji.js'), | |
6 | - "suggest-emoji.js": require('./suggest-emoji.js'), | |
7 | - "dns.js": require('./dns.js'), | |
8 | - "git.js": require('./git.js'), | |
9 | - "git-ssb.js": require('./git-ssb.js'), | |
10 | - "key.js": require('./key.js'), | |
11 | - "notifications.js": require('./notifications.js'), | |
12 | - "meta-image.js": require('./meta-image.js'), | |
13 | - "music-release-cc.js": require('./music-release-cc.js'), | |
14 | - "music-release.js": require('./music-release.js'), | |
15 | - "network.js": require('./network.js'), | |
16 | - "query.js": require('./query.js'), | |
17 | - "raw.js": require('./raw.js'), | |
18 | - "search.js": require('./search'), | |
19 | - "split.js": require('./split.js'), | |
20 | - "versions.js": require('./versions.js') | |
2 … | + // 'audio-mp3': require('./audio-mp3'), | |
3 … | + 'blob': require('./blob'), | |
4 … | + 'channel': require('./channel'), | |
5 … | + 'suggest-emoji': require('./suggest-emoji'), | |
6 … | + 'dns': require('./dns'), | |
7 … | + 'git': require('./git'), | |
8 … | + 'git-ssb': require('./git-ssb'), | |
9 … | + 'key': require('./key'), | |
10 … | + 'notifications': require('./notifications'), | |
11 … | + 'meta-image': require('./meta-image'), | |
12 … | + 'music': { | |
13 … | + 'release-cc': require('./music/release-cc'), | |
14 … | + 'release': require('./music/release') | |
15 … | + }, | |
16 … | + 'network': require('./network'), | |
17 … | + 'query': require('./query'), | |
18 … | + 'raw': require('./raw'), | |
19 … | + 'search': require('./search'), | |
20 … | + 'split': require('./split'), | |
21 … | + 'versions': require('./versions') | |
21 | 22 … | } |
22 | 23 … | |
23 | 24 … |
modules_extra/key.js | ||
---|---|---|
@@ -1,8 +1,9 @@ | ||
1 | 1 … | var h = require('hyperscript') |
2 | 2 … | |
3 | 3 … | exports.gives = { |
4 | - menu_items: true, screen_view: true | |
4 … | + menu_items: true, | |
5 … | + screen_view: true | |
5 | 6 … | } |
6 | 7 … | |
7 | 8 … | exports.create = function (api) { |
8 | 9 … | return { |
modules_extra/emoji.js | ||
---|---|---|
@@ -1,18 +1,0 @@ | ||
1 | -var emojis = require('emoji-named-characters') | |
2 | -var emojiNames = Object.keys(emojis) | |
3 | - | |
4 | -exports.needs = { blob_url: 'first' } | |
5 | -exports.gives = { emoji_names: true, emoji_url: true } | |
6 | - | |
7 | -exports.create = function (api) { | |
8 | - return { | |
9 | - emoji_names: function () { | |
10 | - return emojiNames | |
11 | - }, | |
12 | - emoji_url: function (emoji) { | |
13 | - return emoji in emojis && | |
14 | - api.blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png' | |
15 | - } | |
16 | - } | |
17 | -} | |
18 | - |
modules_extra/index.test.js | ||
---|---|---|
@@ -1,0 +1,20 @@ | ||
1 … | +const test = require('tape') | |
2 … | +const jsdom = require('jsdom') | |
3 … | +const combine = require('depject') | |
4 … | + | |
5 … | +process.env.ssb_appname = 'test' | |
6 … | + | |
7 … | +const core = require('../modules_core') | |
8 … | +const basic = require('../modules_basic') | |
9 … | +const extra = require('./') | |
10 … | + | |
11 … | +// jsdom.env('<body></body>', (err, window) => { | |
12 … | + | |
13 … | + test('modules_extra has no outside deps', t => { | |
14 … | + t.ok(combine(extra, basic, core)) | |
15 … | + t.end() | |
16 … | + // window.close() | |
17 … | + }) | |
18 … | +// }) | |
19 … | + | |
20 … | + |
modules_extra/music-release-cc.js | ||
---|---|---|
@@ -1,80 +1,0 @@ | ||
1 | -var markdown = require('ssb-markdown'); | |
2 | -var h = require('hyperscript'); | |
3 | -var u = require('../util'); | |
4 | -var ref = require('ssb-ref'); | |
5 | - | |
6 | -//render a message | |
7 | - | |
8 | -exports.needs = { blob_url: 'first' } | |
9 | -exports.gives = 'message_content' | |
10 | - | |
11 | -exports.create = function (api) { | |
12 | - return function(msg, sbot) { | |
13 | - if (msg.value.content.type !== 'music-release-cc') | |
14 | - return; | |
15 | - | |
16 | - var tracks = msg.value.content.tracks; | |
17 | - return h('div', | |
18 | - h('img', { "src" : api.blob_url(msg.value.content.cover) }), | |
19 | - h('h1', msg.value.content.title), | |
20 | - h('ol', | |
21 | - Object.keys(tracks).map(function(k) { | |
22 | - var t = tracks[k]; | |
23 | - return h('li', t.fname, | |
24 | - h("br"), | |
25 | - h('audio', { | |
26 | - "controls" : true, | |
27 | - "src" : api.blob_url(t.link) | |
28 | - })) | |
29 | - })), | |
30 | - h('p', | |
31 | - "More info:", h('a', { href : msg.value.content.archivedotorg }, "archive.org"), | |
32 | - h("br"), | |
33 | - "License:", h('a', { href : msg.value.content.license }, "Link"))) | |
34 | - } | |
35 | -} | |
36 | - | |
37 | -// copied from like.js | |
38 | - | |
39 | -// inspiration for waveform range selection | |
40 | - | |
41 | -// idea: handout invite codes for upload of tracks to be cached by the pub | |
42 | - | |
43 | -// exports.message_meta = function (msg, sbot) { | |
44 | - | |
45 | -// var yupps = h('a') | |
46 | - | |
47 | -// pull( | |
48 | -// sbot_links({dest: msg.key, rel: 'vote'}), | |
49 | -// pull.collect(function (err, votes) { | |
50 | -// if(votes.length === 1) | |
51 | -// yupps.textContent = ' 1 yup' | |
52 | -// if(votes.length) | |
53 | -// yupps.textContent = ' ' + votes.length + ' yupps' | |
54 | -// }) | |
55 | -// ) | |
56 | - | |
57 | -// return yupps | |
58 | -// } | |
59 | - | |
60 | -// exports.message_action = function (msg, sbot) { | |
61 | -// if(msg.value.content.type !== 'vote') | |
62 | -// return h('a', {href: '#', onclick: function () { | |
63 | -// var yup = { | |
64 | -// type: 'vote', | |
65 | -// vote: { link: msg.key, value: 1, expression: 'yup' } | |
66 | -// } | |
67 | -// if(msg.value.content.recps) { | |
68 | -// yup.recps = msg.value.content.recps.map(function (e) { | |
69 | -// return e && typeof e !== 'string' ? e.link : e | |
70 | -// }) | |
71 | -// yup.private = true | |
72 | -// } | |
73 | -// //TODO: actually publish... | |
74 | - | |
75 | -// message_confirm(yup) | |
76 | -// }}, 'yup') | |
77 | - | |
78 | -// } | |
79 | - | |
80 | - |
modules_extra/music/release-cc.js | ||
---|---|---|
@@ -1,0 +1,87 @@ | ||
1 … | +var markdown = require('ssb-markdown'); | |
2 … | +var h = require('hyperscript'); | |
3 … | +var u = require('../../util'); | |
4 … | +var ref = require('ssb-ref'); | |
5 … | + | |
6 … | +//render a message | |
7 … | + | |
8 … | +exports.needs = { | |
9 … | + blob_url: 'first' | |
10 … | +} | |
11 … | + | |
12 … | +exports.gives = 'message_content' | |
13 … | + | |
14 … | +exports.create = function (api) { | |
15 … | + return function(msg, sbot) { | |
16 … | + if (msg.value.content.type !== 'music-release-cc') | |
17 … | + return; | |
18 … | + | |
19 … | + var tracks = msg.value.content.tracks; | |
20 … | + return h('div', | |
21 … | + h('img', { "src" : api.blob_url(msg.value.content.cover) }), | |
22 … | + h('h1', msg.value.content.title), | |
23 … | + h('ol', | |
24 … | + Object.keys(tracks).map(function(k) { | |
25 … | + var t = tracks[k]; | |
26 … | + return h('li', t.fname, | |
27 … | + h("br"), | |
28 … | + h('audio', { | |
29 … | + "controls" : true, | |
30 … | + "src" : api.blob_url(t.link) | |
31 … | + }) | |
32 … | + ) | |
33 … | + }) | |
34 … | + ), | |
35 … | + h('p', | |
36 … | + "More info:", h('a', { href : msg.value.content.archivedotorg }, "archive.org"), | |
37 … | + h("br"), | |
38 … | + "License:", h('a', { href : msg.value.content.license }, "Link") | |
39 … | + ) | |
40 … | + ) | |
41 … | + } | |
42 … | +} | |
43 … | + | |
44 … | +// copied from like.js | |
45 … | + | |
46 … | +// inspiration for waveform range selection | |
47 … | + | |
48 … | +// idea: handout invite codes for upload of tracks to be cached by the pub | |
49 … | + | |
50 … | +// exports.message_meta = function (msg, sbot) { | |
51 … | + | |
52 … | +// var yupps = h('a') | |
53 … | + | |
54 … | +// pull( | |
55 … | +// sbot_links({dest: msg.key, rel: 'vote'}), | |
56 … | +// pull.collect(function (err, votes) { | |
57 … | +// if(votes.length === 1) | |
58 … | +// yupps.textContent = ' 1 yup' | |
59 … | +// if(votes.length) | |
60 … | +// yupps.textContent = ' ' + votes.length + ' yupps' | |
61 … | +// }) | |
62 … | +// ) | |
63 … | + | |
64 … | +// return yupps | |
65 … | +// } | |
66 … | + | |
67 … | +// exports.message_action = function (msg, sbot) { | |
68 … | +// if(msg.value.content.type !== 'vote') | |
69 … | +// return h('a', {href: '#', onclick: function () { | |
70 … | +// var yup = { | |
71 … | +// type: 'vote', | |
72 … | +// vote: { link: msg.key, value: 1, expression: 'yup' } | |
73 … | +// } | |
74 … | +// if(msg.value.content.recps) { | |
75 … | +// yup.recps = msg.value.content.recps.map(function (e) { | |
76 … | +// return e && typeof e !== 'string' ? e.link : e | |
77 … | +// }) | |
78 … | +// yup.private = true | |
79 … | +// } | |
80 … | +// //TODO: actually publish... | |
81 … | + | |
82 … | +// message_confirm(yup) | |
83 … | +// }}, 'yup') | |
84 … | + | |
85 … | +// } | |
86 … | + | |
87 … | + |
modules_extra/music/release.js | ||
---|---|---|
@@ -1,0 +1,43 @@ | ||
1 … | +var markdown = require('ssb-markdown'); | |
2 … | +var h = require('hyperscript'); | |
3 … | +var u = require('../../util'); | |
4 … | +var ref = require('ssb-ref'); | |
5 … | + | |
6 … | +//render a message | |
7 … | + | |
8 … | +exports.gives = 'message_content' | |
9 … | + | |
10 … | +exports.create = function () { | |
11 … | + | |
12 … | + return function(msg, sbot) { | |
13 … | + if (msg.value.content.type !== 'music-release') | |
14 … | + return; | |
15 … | + | |
16 … | + var v = msg.value.content; | |
17 … | + return h('div', [ | |
18 … | + // h('img', { "src" : "http://localhost:7777/" + encodeURIComponent(v.cover) }), | |
19 … | + h('h1', v.Title), | |
20 … | + h("p", v.Description), | |
21 … | + h("dl", [ | |
22 … | + h("dt", "Creator"), | |
23 … | + h("dd", v.Creator), | |
24 … | + | |
25 … | + h("dt", "Identifier"), | |
26 … | + h("dd", v.Identifier), | |
27 … | + | |
28 … | + h("dt", "Published"), | |
29 … | + h("dd", v.Publicdate), | |
30 … | + | |
31 … | + h("dt", "Runtime"), | |
32 … | + h("dd", v.Runtime), | |
33 … | + | |
34 … | + h("dt", "Source"), | |
35 … | + h("dd", v.Source), | |
36 … | + | |
37 … | + h("dt", "License"), | |
38 … | + h("dd", h('a', { href : v.Licenseurl }, "Link")) | |
39 … | + ]) | |
40 … | + ]) | |
41 … | + } | |
42 … | +} | |
43 … | + |
package.json | ||
---|---|---|
@@ -6,14 +6,22 @@ | ||
6 | 6 … | "repository": { |
7 | 7 … | "type": "git", |
8 | 8 … | "url": "git://github.com/dominictarr/patchbay.git" |
9 | 9 … | }, |
10 … | + "scripts": { | |
11 … | + "lite": "mkdir -p build && browserify index.js | indexhtmlify --title patchbay > build/index.html", | |
12 … | + "start": "electro index.js", | |
13 … | + "bundle": "mkdir -p build && browselectrify index.js > build/bundle.js", | |
14 … | + "rebuild": "npm rebuild --runtime=electron --target=$(electron -v) --abi=$(electron --abi) --disturl=https://atom.io/download/atom-shell", | |
15 … | + "graph": "node index.js | dot -Tsvg > graph.svg", | |
16 … | + "test": "browserify modules_*/index.test.js | tape-run" | |
17 … | + }, | |
10 | 18 … | "dependencies": { |
11 | 19 … | "@mmckegg/mutant": "^3.12.0", |
12 | 20 … | "brfs": "^1.4.3", |
13 | 21 … | "cont": "^1.0.3", |
14 | 22 … | "dataurl-": "^0.1.0", |
15 | - "depject": "^3.0.0", | |
23 … | + "depject": "^3.1.4", | |
16 | 24 … | "es2040": "^1.2.4", |
17 | 25 … | "hjson": "^2.0.3", |
18 | 26 … | "human-time": "0.0.1", |
19 | 27 … | "hypercombo": "0.1.0", |
@@ -64,23 +72,17 @@ | ||
64 | 72 … | "devDependencies": { |
65 | 73 … | "browselectrify": "^1.0.1", |
66 | 74 … | "electro": "^2.0.3", |
67 | 75 … | "electron": "^1.4.10", |
68 | - "indexhtmlify": "^1.3.1" | |
76 … | + "indexhtmlify": "^1.3.1", | |
77 … | + "tape": "^4.6.3", | |
78 … | + "tape-run": "^2.1.5" | |
69 | 79 … | }, |
70 | 80 … | "browserify": { |
71 | 81 … | "transform": [ |
72 | 82 … | "brfs", |
73 | 83 … | "es2040" |
74 | 84 … | ] |
75 | 85 … | }, |
76 | - "scripts": { | |
77 | - "lite": "mkdir -p build && browserify index.js | indexhtmlify --title patchbay > build/index.html", | |
78 | - "start": "electro index.js", | |
79 | - "bundle": "mkdir -p build && browselectrify index.js > build/bundle.js", | |
80 | - "rebuild": "npm rebuild --runtime=electron --target=$(electron -v) --abi=$(electron --abi) --disturl=https://atom.io/download/atom-shell", | |
81 | - "graph": "node index.js | dot -Tsvg > graph.svg", | |
82 | - "test": "set -e; for t in test/*.js; do node $t; done" | |
83 | - }, | |
84 | 86 … | "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)", |
85 | 87 … | "license": "MIT" |
86 | 88 … | } |
Built with git-ssb-web