git ssb

16+

Dominic / patchbay



Commit 2264a334a0bf3da0cda5d13e61903a94651feafe

Merge pull request #69 from ssbc/message_modules_folder

[WIP] ideal layout for nested message modules
mix 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.jschanged
modules_basic/index.jschanged
modules_basic/relationships.jschanged
modules_basic/avatar-edit.jsdeleted
modules_basic/avatar/avatar.jsadded
modules_basic/avatar/edit.jsadded
modules_basic/avatar/image.jsadded
modules_basic/avatar/index.jsadded
modules_basic/avatar/link.jsadded
modules_basic/avatar/name.jsadded
modules_basic/avatar/profile.jsadded
modules_basic/avatar-image.jsdeleted
modules_basic/avatar-link.jsdeleted
modules_basic/avatar-name.jsdeleted
modules_basic/emoji.jsadded
modules_basic/avatar-profile.jsdeleted
modules_basic/avatar.jsdeleted
modules_basic/index.test.jsadded
modules_basic/message/author.jsadded
modules_basic/message/author.mcssadded
modules_basic/message/backlinks.jsadded
modules_basic/message/backlinks.mcssadded
modules_basic/message/confirm.jsadded
modules_basic/message/confirm.mcssadded
modules_basic/message/index.jsadded
modules_basic/message/link.jsadded
modules_basic/message/name.jsadded
modules_basic/message/render.jsadded
modules_basic/message/render.mcssadded
modules_basic/message-author.jsdeleted
modules_basic/message-author.mcssdeleted
modules_basic/message-backlinks.jsdeleted
modules_basic/message-backlinks.mcssdeleted
modules_basic/message-link.jsdeleted
modules_basic/message-name.jsdeleted
modules_basic/message.jsdeleted
modules_basic/message.mcssdeleted
modules_basic/names.jsdeleted
modules_basic/search-box.jsdeleted
modules_basic/suggest-mentions.jsdeleted
modules_core/index.jschanged
modules_core/menu.jschanged
modules_core/sbot.jschanged
modules_core/tabs.jschanged
modules_core/index.test.jsadded
modules_core/message-confirm.jsdeleted
modules_core/message-confirm.mcssdeleted
modules_core/names.jsadded
modules_core/style-mixins.jsdeleted
modules_core/search-box.jsadded
modules_core/styles.jsdeleted
modules_core/style/index.jsadded
modules_core/style/mixins.jsadded
modules_core/style/styles.jsadded
modules_core/suggest-mentions.jsadded
modules_embedded/index.jschanged
modules_extra/index.jschanged
modules_extra/key.jschanged
modules_extra/emoji.jsdeleted
modules_extra/index.test.jsadded
modules_extra/music-release-cc.jsdeleted
modules_extra/music/release-cc.jsadded
modules_extra/music/release.jsadded
package.jsonchanged
embedded.jsView
@@ -1,9 +1,12 @@
1 +// polyfills
2 +require('setimmediate')
3 +
14 require('depject')(
25 require('./modules_embedded'),
36 require('./modules_basic'),
47 require('./modules_extra')
5-).plugs.app[0]()
8 +).app[0]()
69
710
811
912
modules_basic/index.jsView
@@ -1,33 +1,22 @@
11 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')
3221 }
3322
modules_basic/relationships.jsView
@@ -1,17 +1,19 @@
11 var pull = require('pull-stream')
22
3 +//this is a bit crude, and doesn't actually show unfollows yet.
4 +
35 function makeQuery (a, b) {
46 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 + }}
1416 }
1517
1618
1719 exports.needs = { sbot_query: 'first' }
modules_basic/avatar-edit.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -1,15 +1,15 @@
11 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')
1414 }
1515
modules_core/menu.jsView
@@ -1,23 +1,28 @@
1-var h = require('hyperscript')
1 +const h = require('hyperscript')
22
33 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 + },
612 create: function (api) {
13 + const { menu_items } = api
714
8- var menu_items = api.menu_items
9-
1015 var status = h('div.status.error') //start off disconnected
1116 var list = h('div.menu.column', {style: 'display: none;'})
1217
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 + ])
2025
2126 setTimeout(function () {
2227 menu_items().forEach(function (el) {
2328 if(el)
@@ -25,15 +30,14 @@
2530 })
2631 }, 0)
2732
2833 return {
29- connection_status: function (err) {
34 + connection_status: (err) => {
3035 if(err) status.classList.add('error')
3136 else status.classList.remove('error')
3237 },
33- menu: function () {
34- return menu
35- }
38 + menu: () => menu,
39 + menu_items: () => null
3640 }
3741 }
3842 }
3943
modules_core/sbot.jsView
@@ -59,8 +59,14 @@
5959 var opts = createConfig()
6060 var sbot = null
6161 var connection_status = []
6262
63 + var rec = {
64 + sync: () => {},
65 + async: () => {},
66 + source: () => {},
67 + }
68 +
6369 var rec = Reconnect(function (isConn) {
6470 function notify (value) {
6571 isConn(value); api.connection_status(value) //.forEach(function (fn) { fn(value) })
6672 }
modules_core/tabs.jsView
@@ -8,9 +8,14 @@
88 if(el.tagName !== 'A') return ancestor(el.parentElement)
99 return el
1010 }
1111
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 +}
1318
1419 exports.gives = 'screen_view'
1520
1621 exports.create = function (api) {
modules_core/index.test.jsView
@@ -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.jsView
@@ -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.mcssView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -1,0 +1,5 @@
1 +module.exports = {
2 + 'styles': require('./styles'),
3 + 'mixins': require('./mixins')
4 +}
5 +
modules_core/style/mixins.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -1,11 +1,12 @@
11 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.')
1111 }
12 +
modules_extra/index.jsView
@@ -1,23 +1,24 @@
11 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')
2122 }
2223
2324
modules_extra/key.jsView
@@ -1,8 +1,9 @@
11 var h = require('hyperscript')
22
33 exports.gives = {
4- menu_items: true, screen_view: true
4 + menu_items: true,
5 + screen_view: true
56 }
67
78 exports.create = function (api) {
89 return {
modules_extra/emoji.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsonView
@@ -6,14 +6,22 @@
66 "repository": {
77 "type": "git",
88 "url": "git://github.com/dominictarr/patchbay.git"
99 },
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 + },
1018 "dependencies": {
1119 "@mmckegg/mutant": "^3.12.0",
1220 "brfs": "^1.4.3",
1321 "cont": "^1.0.3",
1422 "dataurl-": "^0.1.0",
15- "depject": "^3.0.0",
23 + "depject": "^3.1.4",
1624 "es2040": "^1.2.4",
1725 "hjson": "^2.0.3",
1826 "human-time": "0.0.1",
1927 "hypercombo": "0.1.0",
@@ -64,23 +72,17 @@
6472 "devDependencies": {
6573 "browselectrify": "^1.0.1",
6674 "electro": "^2.0.3",
6775 "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"
6979 },
7080 "browserify": {
7181 "transform": [
7282 "brfs",
7383 "es2040"
7484 ]
7585 },
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- },
8486 "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
8587 "license": "MIT"
8688 }

Built with git-ssb-web