git ssb

16+

Dominic / patchbay



Commit 449d051990b8d8149dd2a3bb9078e269533e324e

Merge branch 'master' into blob

mix irving committed on 1/13/2017, 10:27:18 PM
Parent: e33a98abcc85d3a3b35c0b2db5b98200b0614954
Parent: 2ee7ff4448c085c5e1659d6a5833f545fe3c6e39

Files changed

.gitignorechanged
README.mdchanged
index.jschanged
modules_basic/about.jschanged
modules_basic/avatar-edit.jschanged
modules_basic/avatar-image.jschanged
modules_basic/avatar-name.jschanged
modules_basic/avatar.jschanged
modules_basic/compose.jschanged
modules_basic/index.jschanged
modules_basic/invite.jschanged
modules_basic/like.jschanged
modules_basic/markdown.jschanged
modules_basic/message.jschanged
modules_basic/post.jschanged
modules_basic/private.jschanged
modules_basic/thread.jschanged
modules_basic/timestamp.jschanged
modules_basic/about.mcssadded
modules_basic/compose.mcssadded
modules_basic/message-author.jsadded
modules_basic/message-author.mcssadded
modules_basic/message-backlinks.jsadded
modules_basic/message-backlinks.mcssadded
modules_basic/message.mcssadded
modules_basic/reply.jsadded
modules_basic/timestamp.mcssadded
modules_core/app.jschanged
modules_core/file-input.jschanged
modules_core/index.jschanged
modules_core/message-confirm.jschanged
modules_core/sbot.jschanged
modules_core/tabs.jschanged
modules_core/external-confirm.jsadded
modules_core/message-confirm.mcssadded
modules_core/style-mixins.jsadded
modules_core/styles.jsadded
modules_extra/channel.jschanged
modules_extra/git.jschanged
modules_extra/index.jschanged
modules_extra/network.jschanged
modules_extra/raw.jschanged
modules_extra/key.jsadded
modules_extra/network.mcssadded
package.jsonchanged
scripts/create-index.jsdeleted
scripts/style.jsdeleted
style.csschanged
h.jsadded
.gitignoreView
@@ -2,5 +2,4 @@
22 npm-debug.log
33 .npmignore
44 build
55 modules/_index.js
6-style.css.json
README.mdView
@@ -38,8 +38,27 @@
3838 npm run bundle
3939 npm start
4040 ```
4141
42 +## Lite
43 +
44 +To run a lite client in the browser instead of using electron, use npm
45 +run lite from the prompt instead of run bundle. After that you need to
46 +generate a modern invite:
47 +
48 +```
49 +sbot invite.create --modern
50 +```
51 +
52 +Also set up sbot to allow these connections with:
53 +
54 +```
55 +sbot server --allowPrivate
56 +```
57 +
58 +Lastly open build/index.html in a browser and append the invite
59 +created above using: index.html#ws://localhost:8989....
60 +
4261 ## how to add a feature
4362
4463 To add a new message type, add add a js to `./modules/` that
4564 exports a function named `message_content` (it should return an html element)
index.jsView
@@ -1,4 +1,7 @@
1 +// polyfills
2 +require('setimmediate')
3 +
14 require('depject')(
25 // from more specialized to more general
36 require('./modules_extra'),
47 require('./modules_basic'),
modules_basic/about.jsView
@@ -1,48 +1,100 @@
1 +const fs = require('fs')
2 +const h = require('../h')
3 +const { when } = require('@mmckegg/mutant')
14
2-var h = require('hyperscript')
3-
4-function idLink (id) {
5- return h('a', {href:'#'+id}, id)
5 +exports.needs = {
6 + blob_url: 'first',
7 + markdown: 'first'
68 }
79
8-function asLink (ln) {
9- return 'string' === typeof ln ? ln : ln.link
10 +exports.gives = {
11 + mcss: true,
12 + message_content: true,
13 + message_content_mini: true
1014 }
1115
12-//var blob_url = require('../plugs').first(exports.blob_url = [])
16 +exports.create = function (api) {
17 + return {
18 + message_content,
19 + message_content_mini,
20 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
21 + }
1322
14-exports.needs = {
15- blob_url: 'first'
16-}
23 + function message_content (msg) {
24 + if (msg.value.content.type !== 'about') return
1725
18-exports.gives = 'message_content'
26 + var { content: about, author: authorId } = msg.value
27 + var { about: aboutId, name, image, description } = about
1928
20-exports.create = function (api) {
29 + if (!aboutId) return null
2130
22- return function (msg) {
23- if(msg.value.content.type !== 'about') return
31 + return h('About', [
32 + Name({ aboutId, authorId, name }),
33 + Image({ aboutId, authorId, image }),
34 + Description({ aboutId, authorId, description })
35 + ])
36 + }
2437
25- if(!msg.value.content.image && !msg.value.content.name)
26- return
38 + function message_content_mini (msg) {
39 + if (msg.value.content.type !== 'about') return
2740
28- var about = msg.value.content
29- var id = msg.value.content.about
30- return h('p',
31- about.about === msg.value.author
32- ? h('span', 'self-identifies ')
33- : h('span', 'identifies ', idLink(id)),
34- ' as ',
35- h('a', {href:"#"+about.about},
36- about.name || null,
37- about.image
38- ? h('img.avatar--fullsize', {src: api.blob_url(about.image)})
39- : null
40- )
41- )
41 + var { content: about, author: authorId } = msg.value
42 + var { about: aboutId, name, image, description } = about
4243
44 + if (!aboutId) return null
45 + if (image || description) return null
46 +
47 + return h('About', Name({ aboutId, authorId, name }))
4348 }
4449
45-}
4650
51 + function Name ({ aboutId, authorId, name }) {
52 + if (!name) return null
53 + return h('section -name', [
54 + h('header', when(authorId === aboutId,
55 + 'self-identifies as',
56 + ['identifies ', targetLink(aboutId), ' as']
57 + )),
58 + h('section', h(
59 + 'a -name',
60 + { href: `#${aboutId}` },
61 + name
62 + ))
63 + ])
64 + }
4765
66 + function Image ({ aboutId, authorId, image }) {
67 + if (!image) return null
68 + return h('section -image', [
69 + h('header', when(authorId === aboutId,
70 + 'self-portrays as',
71 + ['portrays ', targetLink(aboutId), ' as']
72 + )),
73 + h('section', h(
74 + 'a -image',
75 + { href: `#${aboutId}` },
76 + h('img', { src: api.blob_url(image) })
77 + ))
78 + ])
79 + }
4880
81 + function Description ({ aboutId, authorId, description }) {
82 + if (!description) return null
83 + return h('section -description', [
84 + h('header', when(authorId === aboutId,
85 + 'self-describes as',
86 + ['describes ', targetLink(aboutId), ' as']
87 + )),
88 + h('section', api.markdown(description))
89 + ])
90 + }
91 +}
92 +
93 +function targetLink (aboutId) {
94 + const content = aboutId.slice(0, 9) + '...'
95 + return h(
96 + 'a -target',
97 + { href: `#${aboutId}` },
98 + content
99 + )
100 +}
modules_basic/avatar-edit.jsView
@@ -135,9 +135,9 @@
135135 name: name_input.value || undefined,
136136 image: selected
137137 })
138138 else if(name_input.value) //name only
139- confirm({
139 + api.message_confirm({
140140 type: 'about',
141141 about: id,
142142 name: name_input.value || undefined,
143143 })
modules_basic/avatar-image.jsView
@@ -33,8 +33,10 @@
3333 var waiting = []
3434
3535 var last = 0
3636
37 +var cache = {}
38 +
3739 exports.create = function (api) {
3840 var avatars = {}
3941
4042 //blah blah
@@ -89,20 +91,25 @@
8991 avatar_image: function (author, classes) {
9092 classes = classes || ''
9193 if(classes && 'string' === typeof classes) classes = '.avatar--'+classes
9294
93- var img = visualize(new Buffer(author.substring(1), 'base64'), 256)
95 + function gen (id) {
96 + if(cache[id]) return h('img', {src: cache[id]})
97 + var img = visualize(new Buffer(author.substring(1), 'base64'), 256)
98 + cache[id] = img.src
99 + return img
100 + }
101 +
102 + var img = ready && avatars[author] ? h('img', {src: api.blob_url(avatars[author].image)}) : gen(author)
103 +
94104 ;(classes || '').split('.').filter(Boolean).forEach(function (c) {
95105 img.classList.add(c)
96106 })
97107
98- function go () {
99- if(avatars[author]) img.src = api.blob_url(avatars[author].image)
100- }
101-
102108 if(!ready)
103- waiting.push(go)
104- else go()
109 + waiting.push(function () {
110 + if(avatars[author]) img.src = api.blob_url(avatars[author].image)
111 + })
105112
106113 return img
107114 }
108115 }
modules_basic/avatar-name.jsView
@@ -8,9 +8,9 @@
88
99 exports.create = function (api) {
1010
1111 return function name (id) {
12- var n = h('span', id.substring(0, 10))
12 + var n = h('span', id ? id.substring(0, 10) : "")
1313
1414 //choose the most popular name for this person.
1515 //for anything like this you'll see I have used sbot.links2
1616 //which is the ssb-links plugin. as you'll see the query interface
modules_basic/avatar.jsView
@@ -9,29 +9,37 @@
99
1010 exports.gives = {
1111 avatar: true,
1212 avatar_image_name_link: true,
13- avatar_image_link: true
13 + avatar_image_link: true,
14 + avatar_name_link: true
1415 }
1516
1617 exports.create = function (api) {
18 + return {
19 + avatar,
20 + avatar_image_name_link,
21 + avatar_image_link,
22 + avatar_name_link
23 + }
1724
18- var exports = {}
19- exports.avatar = function (author, classes) {
25 + function avatar (author, classes) {
2026 return exports.avatar_image_name_link(author, classes)
2127 }
2228
23- exports.avatar_image_name_link = function (author, classes) {
29 + function avatar_image_name_link (author, classes) {
2430 return api.avatar_link(author, [
2531 api.avatar_image(author, classes),
2632 api.avatar_name(author)
2733 ])
2834 }
2935
30- exports.avatar_image_link = function (author, classes) {
36 + function avatar_image_link (author, classes) {
3137 return api.avatar_link(author, api.avatar_image(author, classes))
3238 }
3339
34- return exports
40 + function avatar_name_link (author, classes) {
41 + return api.avatar_link(author, api.avatar_name(author))
42 + }
3543 }
3644
3745
modules_basic/compose.jsView
@@ -1,11 +1,11 @@
11 'use strict'
2-var h = require('hyperscript')
3-var u = require('../util')
4-var suggest = require('suggest-box')
5-var mentions = require('ssb-mentions')
6-var lightbox = require('hyperlightbox')
7-var cont = require('cont')
2 +const fs = require('fs')
3 +const h = require('../h')
4 +const u = require('../util')
5 +const suggest = require('suggest-box')
6 +const mentions = require('ssb-mentions')
7 +const cont = require('cont')
88
99 //var plugs = require('../plugs')
1010 //var suggest_mentions= plugs.asyncConcat(exports.suggest_mentions = [])
1111 //var publish = plugs.first(exports.sbot_publish = [])
@@ -20,61 +20,63 @@
2020 message_confirm: 'first',
2121 file_input: 'first'
2222 }
2323
24-exports.gives = 'message_compose'
24 +exports.gives = {
25 + 'message_compose': true,
26 + 'mcss': true
27 +}
2528
26-function id (e) { return e }
29 +exports.create = function (api) {
30 + return {
31 + message_compose,
32 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
33 + }
2734
28-/*
29- opts can take
35 + /*
36 + opts can take
3037
31- placeholder: string. placeholder text, defaults to "Write a message"
32- prepublish: function. called before publishing a message.
33- shrink: boolean. set to false, to make composer not shrink (or hide controls) when unfocused.
34-*/
38 + placeholder: string. placeholder text, defaults to "Write a message"
39 + prepublish: function. called before publishing a message.
40 + shrink: boolean. set to false, to make composer not shrink (or hide controls) when unfocused.
41 + */
3542
36-exports.create = function (api) {
43 + function message_compose (meta = {}, opts = {}, cb) {
44 + if(!meta.type) throw new Error('message must have type')
3745
38- return function (meta, opts, cb) {
3946 if('function' === typeof cb) {
40- if('function' === typeof opts)
47 + if('function' === typeof opts) {
4148 opts = {prepublish: opts}
4249 }
43-
44- if(!opts) opts = {}
50 + }
4551 opts.prepublish = opts.prepublish || id
4652
47- var accessories
48- meta = meta || {}
49- if(!meta.type) throw new Error('message must have type')
50- var ta = h('textarea', {
51- placeholder: opts.placeholder || 'Write a message',
52- style: {height: opts.shrink === false ? '200px' : ''}
53 + var actions
54 +
55 + var textArea = h('textarea', {
56 + placeholder: opts.placeholder || 'Write a message'
5357 })
5458
5559 if(opts.shrink !== false) {
5660 var blur
57- ta.addEventListener('focus', function () {
61 + textArea.addEventListener('focus', () => {
5862 clearTimeout(blur)
59- if(!ta.value) {
60- ta.style.height = '200px'
63 + if(!textArea.value) {
64 + composer.className = 'Compose -expanded'
6165 }
62- accessories.style.display = 'block'
6366 })
64- ta.addEventListener('blur', function () {
67 + textArea.addEventListener('blur', () => {
6568 //don't shrink right away, so there is time
6669 //to click the publish button.
6770 clearTimeout(blur)
68- blur = setTimeout(function () {
69- if(ta.value) return
70- ta.style.height = '50px'
71- accessories.style.display = 'none'
72- }, 200)
71 + blur = setTimeout(() => {
72 + if(textArea.value) return
73 + composer.className = 'Compose -contracted'
74 + }, 300)
7375 })
7476 }
7577
76- ta.addEventListener('keydown', function (ev) {
78 + textArea.addEventListener('keydown', ev => {
7779 if(ev.keyCode === 13 && ev.ctrlKey) publish()
7880 })
7981
8082 var files = []
@@ -83,12 +85,12 @@
8385 function publish() {
8486 publishBtn.disabled = true
8587 var content
8688 try {
87- content = JSON.parse(ta.value)
89 + content = JSON.parse(textArea.value)
8890 } catch (err) {
89- meta.text = ta.value
90- meta.mentions = mentions(ta.value).map(function (mention) {
91 + meta.text = textArea.value
92 + meta.mentions = mentions(textArea.value).map(mention => {
9193 // merge markdown-detected mention with file info
9294 var file = filesById[mention.link]
9395 if (file) {
9496 if (file.type) mention.type = file.type
@@ -110,46 +112,49 @@
110112
111113 function done (err, msg) {
112114 publishBtn.disabled = false
113115 if(err) return alert(err.stack)
114- else if (msg) ta.value = ''
116 + else if (msg) textArea.value = ''
115117
116118 if (cb) cb(err, msg)
117119 }
118120 }
119121
122 + var fileInput = api.file_input(file => {
123 + files.push(file)
124 + filesById[file.link] = file
120125
121- var publishBtn = h('button', 'Publish', {onclick: publish})
122- var composer =
123- h('div.compose', h('div.column', ta,
124- accessories = h('div.row.compose__controls',
125- //hidden until you focus the textarea
126- {style: {display: opts.shrink === false ? '' : 'none'}},
127- api.file_input(function (file) {
128- files.push(file)
129- filesById[file.link] = file
126 + var embed = file.type.indexOf('image/') === 0 ? '!' : ''
130127
131- var embed = file.type.indexOf('image/') === 0 ? '!' : ''
132- ta.value += embed + '['+file.name+']('+file.link+')'
133- console.log('added:', file)
134- }),
135- publishBtn)
136- )
137- )
128 + textArea.value += embed + '['+file.name+']('+file.link+')'
129 + composer.className = 'Compose -expanded'
130 + console.log('added:', file)
131 + })
132 + var publishBtn = h('button', {'ev-click': publish}, 'Publish' )
133 + var actions = h('section.actions', [
134 + fileInput, publishBtn
135 + ])
138136
139- suggest(ta, function (name, cb) {
137 + var composer = h('Compose', {
138 + className: opts.shrink === false ? '-expanded' : '-contracted'
139 + }, [
140 + textArea,
141 + actions
142 + ])
143 +
144 + suggest(textArea, (name, cb) => {
140145 cont.para(api.suggest_mentions(name))
141- (function (err, ary) {
142- cb(null, ary.reduce(function (a, b) {
146 + ((err, ary) => {
147 + cb(null, ary.reduce((a, b) => {
143148 if(!b) return a
144149 return a.concat(b)
145150 }, []))
146151 })
147152 }, {})
148153
149154 return composer
150-
151155 }
152156
153157 }
154158
159 +function id (e) { return e }
155160
modules_basic/index.jsView
@@ -11,8 +11,10 @@
1111 "follow.js": require('./follow.js'),
1212 "invite.js": require('./invite.js'),
1313 "like.js": require('./like.js'),
1414 "markdown.js": require('./markdown.js'),
15 + "message-author.js": require('./message-author.js'),
16 + "message-backlinks.js": require('./message-backlinks.js'),
1517 "message-link.js": require('./message-link.js'),
1618 "message-name.js": require('./message-name.js'),
1719 "message.js": require('./message.js'),
1820 "names.js": require('./names.js'),
@@ -20,8 +22,9 @@
2022 "private.js": require('./private.js'),
2123 "pub.js": require('./pub.js'),
2224 "public.js": require('./public.js'),
2325 "relationships.js": require('./relationships.js'),
26 + "reply.js": require('./reply.js'),
2427 "search-box.js": require('./search-box.js'),
2528 "setup.js": require('./setup'),
2629 "suggest-mentions.js": require('./suggest-mentions.js'),
2730 "thread.js": require('./thread.js'),
modules_basic/invite.jsView
@@ -34,9 +34,9 @@
3434 invite_accept: function (invite, onProgress, cb) {
3535 var data = self.invite_parse(invite)
3636 if(!data) return cb(new Error('not a valid invite code:' + invite))
3737
38- onProgress('connecting...')
38 + onProgress('Connecting...')
3939
4040 api.sbot_gossip_connect(data.remote, function (err) {
4141 if(err) console.log(err)
4242 })
@@ -45,21 +45,21 @@
4545 remote: data.invite,
4646 manifest: { invite: {use: 'async'}, getAddress: 'async' }
4747 }, function (err, sbot) {
4848 if(err) return cb(err)
49- onProgress('requesting follow...')
49 + onProgress('Requesting follow...')
5050 console.log(sbot)
5151 sbot.invite.use({feed: id}, function (err, msg) {
5252
5353 //if they already follow us, just check we actually follow them.
5454 if(err) api.follower_of(id, data.key, function (_err, follows) {
55- if(follows) cb(err)
55 + if(follows) { location.hash = '' }
5656 else next()
5757 })
5858 else next()
5959
6060 function next () {
61- onProgress('following...')
61 + onProgress('Following...')
6262
6363 //remove the seed from the shs address.
6464 //then it's correct address.
6565 //this should make the browser connect to this as remote.
@@ -87,15 +87,17 @@
8787 //connect to server
8888 //request follow
8989 //post pub announce
9090 //post follow pub
91- var div = h('div.column',
92- h('div',
93- "you have been invited to join:", h('br'),
94- h('code', data.invite)
91 + var div = h('div.column.scroller__wrapper',
92 + h('div.column.scroller__content', {style: 'margin-top: 25%;'},
93 + h('h1', {innerHTML: 'The <a href="https://scuttlebot.io">Secure Scuttlebutt</a> Lite Client'}),
94 + h('p', "You've been invited to join:"),
95 + h('p', h('code', data.invite))
9596 ),
96- h('button', 'accept', {onclick: attempt}),
97- progress
97 + h('p', h('button', 'Accept', {onclick: attempt})),
98 + progress,
99 + h('p', "Once you're in, give yourself a name and photo. And don't forget to say 'Hello!'")
98100 )
99101
100102 function attempt () {
101103 self.invite_accept(invite, function (message) {
modules_basic/like.jsView
@@ -9,8 +9,9 @@
99 //var message_link = plugs.first(exports.message_link = [])
1010 //var sbot_links = plugs.first(exports.sbot_links = [])
1111
1212 exports.needs = {
13 + avatar_name: 'first',
1314 message_confirm: 'first',
1415 message_link: 'first',
1516 sbot_links: 'first'
1617 }
@@ -48,11 +49,20 @@
4849 }
4950
5051 if(votes.length === 1)
5152 digs.textContent = ' 1 Dig'
52- if(votes.length > 1)
53 + else if(votes.length > 1)
5354 digs.textContent = ' ' + votes.length + ' Digs'
5455
56 + pull(
57 + pull.values(votes.map(vote => {
58 + return api.avatar_name(vote.source)
59 + })),
60 + pull.collect(function (err, ary) {
61 + digs.title = ary.map(x => x.innerHTML).join(" ")
62 + })
63 + )
64 +
5565 return digs
5666 }
5767
5868 exports.message_action = function (msg, sbot) {
modules_basic/markdown.jsView
@@ -1,32 +1,26 @@
11 var markdown = require('ssb-markdown')
22 var h = require('hyperscript')
33 var ref = require('ssb-ref')
44
5-//var plugs = require('../plugs')
6-//var blob_url = plugs.first(exports.blob_url = [])
7-//var emoji_url = plugs.first(exports.emoji_url = [])
8-
95 exports.needs = {
106 blob_url: 'first',
11-// emoji_url: 'first'
7 + emoji_url: 'first'
128 }
139
1410 exports.gives = 'markdown'
1511
1612 exports.create = function (api) {
1713
18-//emoji is in extra, disable for a sec
14 + function renderEmoji(emoji) {
15 + var url = api.emoji_url(emoji)
16 + if (!url) return ':' + emoji + ':'
17 + return '<img src="' + encodeURI(url) + '"'
18 + + ' alt=":' + escape(emoji) + ':"'
19 + + ' title=":' + escape(emoji) + ':"'
20 + + ' class="emoji">'
21 + }
1922
20-// function renderEmoji(emoji) {
21-// var url = api.emoji_url(emoji)
22-// if (!url) return ':' + emoji + ':'
23-// return '<img src="' + encodeURI(url) + '"'
24-// + ' alt=":' + escape(emoji) + ':"'
25-// + ' title=":' + escape(emoji) + ':"'
26-// + ' class="emoji">'
27-// }
28-
2923 return function (content) {
3024 if('string' === typeof content)
3125 content = {text: content}
3226 //handle patchwork style mentions.
@@ -37,9 +31,9 @@
3731 })
3832
3933 var md = h('div.markdown')
4034 md.innerHTML = markdown.block(content.text, {
41- // emoji: renderEmoji,
35 + emoji: renderEmoji,
4236 toUrl: function (id) {
4337 if(ref.isBlob(id)) return api.blob_url(id)
4438 return '#'+(mentions[id]?mentions[id]:id)
4539 }
modules_basic/message.jsView
@@ -1,122 +1,89 @@
1-var h = require('hyperscript')
2-var u = require('../util')
3-var pull = require('pull-stream')
1 +const fs = require('fs')
2 +const pull = require('pull-stream')
3 +const u = require('../util')
4 +const h = require('../h')
45
5-//var plugs = require('../plugs')
6-//
7-//var message_content = plugs.first(exports.message_content = [])
8-//var message_content_mini = plugs.first(exports.message_content_mini = [])
9-//
10-//var avatar = plugs.first(exports.avatar = [])
11-//var avatar_name = plugs.first(exports.avatar_name = [])
12-//var avatar_link = plugs.first(exports.avatar_link = [])
13-//var message_meta = plugs.map(exports.message_meta = [])
14-//var message_action = plugs.map(exports.message_action = [])
15-//var message_link = plugs.first(exports.message_link = [])
16-//
17-//var sbot_links = plugs.first(exports.sbot_links = [])
18-
196 exports.needs = {
20- message_content: 'first',
21- message_content_mini: 'first',
22- avatar: 'first',
237 avatar_name: 'first',
248 avatar_link: 'first',
25- message_meta: 'map',
269 message_action: 'map',
10 + message_author: 'first',
11 + message_backlinks: 'first',
12 + message_content: 'first',
13 + message_content_mini: 'first',
14 + message_title: 'first',
2715 message_link: 'first',
28-// sbot_links: 'first'
16 + message_meta: 'map',
2917 }
3018
31-exports.gives = 'message_render'
32-
33-function message_content_mini_fallback(msg) {
34- return h('code', msg.value.content.type)
19 +exports.gives = {
20 + message_render: true,
21 + mcss: true
3522 }
3623
3724 exports.create = function (api) {
38-
39- function mini(msg, el) {
40- var div = h('div.message.message--mini',
41- h('div.row',
42- h('div',
43- api.avatar_link(msg.value.author, api.avatar_name(msg.value.author)),
44- h('span.message_content', el)),
45- h('div.message_meta.row', api.message_meta(msg))
46- )
47- )
48- div.setAttribute('tabindex', '0')
49- return div
25 + return {
26 + message_render,
27 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
5028 }
5129
52- return function (msg, sbot) {
53- var el = api.message_content_mini(msg)
54- if(el) return mini(msg, el)
30 + function message_render (msg) {
31 + var content = api.message_content_mini(msg)
32 + if (content) return mini(msg, content)
5533
56- var el = api.message_content(msg)
57- if(!el) return mini(msg, message_content_mini_fallback(msg))
34 + content = api.message_content(msg)
35 + if (!content) return mini(msg, message_content_mini_fallback(msg))
5836
59- var links = []
60- for(var k in CACHE) {
61- var _msg = CACHE[k]
62- if(Array.isArray(_msg.content.mentions)) {
63- for(var i = 0; i < _msg.content.mentions.length; i++)
64- if(_msg.content.mentions[i].link == msg.key)
65- links.push(k)
37 + var msgEl = h('Message', {
38 + 'ev-keydown': navigateToMessageOnEnter,
39 + attributes: {
40 + tabindex: '0'
6641 }
67- }
42 + }, [
43 + h('header.author', api.message_author(msg)),
44 + h('section.title', api.message_title(msg)),
45 + h('section.meta', api.message_meta(msg)),
46 + h('section.content', content),
47 + h('section.action', api.message_action(msg)),
48 + h('footer.backlinks', api.message_backlinks(msg))
49 + ])
50 + return msgEl
6851
69- var backlinks = h('div.backlinks')
70- if(links.length)
71- backlinks.appendChild(h('label', 'backlinks:',
72- h('div', links.map(function (key) {
73- return api.message_link(key)
74- }))
75- ))
52 + function navigateToMessageOnEnter (ev) {
53 + // on enter, hit first meta.
54 + if(ev.keyCode == 13) {
7655
56 + // unless in an input
57 + if (ev.target.nodeName === 'INPUT'
58 + || ev.target.nodeName === 'TEXTAREA') return
7759
78- // pull(
79- // sbot_links({dest: msg.key, rel: 'mentions', keys: true}),
80- // pull.collect(function (err, links) {
81- // if(links.length)
82- // backlinks.appendChild(h('label', 'backlinks:',
83- // h('div', links.map(function (link) {
84- // return message_link(link.key)
85- // }))
86- // ))
87- // })
88- // )
60 + // HACK! (mw)
61 + // there's no exported api to open a new tab. :/
62 + // it's only done in `app.js` module in an`onhashchange` handler.
63 + // sooooooo yeah this shit for now :)
64 + var wtf = h('a', { href: `#${msg.key}` })
65 + msgEl.appendChild(wtf)
66 + wtf.click()
67 + msgEl.removeChild(wtf)
68 + }
69 + }
70 + }
8971
90- var msg = h('div.message',
91- h('div.title.row',
92- h('div.avatar', api.avatar(msg.value.author, 'thumbnail')),
93- h('div.message_meta.row', api.message_meta(msg))
94- ),
95- h('div.message_content', el),
96- h('div.message_actions.row',
97- h('div.actions', api.message_action(msg),
98- h('a', {href: '#' + msg.key}, 'Reply')
99- )
100- ),
101- backlinks,
102- {onkeydown: function (ev) {
103- //on enter, hit first meta.
104- if(ev.keyCode == 13) {
72 + function mini(msg, el) {
73 + return h('Message -mini', {
74 + attributes: {
75 + tabindex: '0'
76 + }
77 + }, [
78 + h('header.author', api.message_author(msg, { size: 'mini' })),
79 + h('section.meta', api.message_meta(msg)),
80 + h('section.content', el)
81 + ])
82 + }
83 +}
10584
106- // unless in an input
107- if (ev.target.nodeName === 'INPUT'
108- || ev.target.nodeName === 'TEXTAREA') return
10985
110- msg.querySelector('.enter').click()
111- }
112- }}
113- )
114-
115- // ); hyperscript does not seem to set attributes correctly.
116- msg.setAttribute('tabindex', '0')
117-
118- return msg
119- }
86 +function message_content_mini_fallback(msg) {
87 + return h('code', msg.value.content.type)
12088 }
12189
122-
modules_basic/post.jsView
@@ -9,24 +9,35 @@
99 //var message_link = plugs.first(exports.message_link = [])
1010 //var markdown = plugs.first(exports.markdown = [])
1111 //
1212
13-exports.needs = { message_link: 'first', markdown: 'first' }
13 +exports.needs = {
14 + message_link: 'first',
15 + markdown: 'first'
16 +}
1417
15-exports.gives = 'message_content'
18 +exports.gives = {
19 + message_content: true,
20 + message_title: true
21 +}
1622
1723 exports.create = function (api) {
18- return function (data) {
24 + return {
25 + message_content,
26 + message_title
27 + }
28 +
29 + function message_content (data) {
1930 if(!data.value.content || !data.value.content.text) return
2031
21- var root = data.value.content.root
22- var re = !root ? null : h('span', 're: ', api.message_link(root))
23-
2432 return h('div',
25- re,
2633 api.markdown(data.value.content)
2734 )
35 + }
2836
37 + function message_title (data) {
38 + var root = data.value.content && data.value.content.root
39 + return !root ? null : h('span', 're: ', api.message_link(root))
2940 }
3041 }
3142
3243
modules_basic/private.jsView
@@ -4,18 +4,8 @@
44 var pull = require('pull-stream')
55 var Scroller = require('pull-scroll')
66 var ref = require('ssb-ref')
77
8-//var plugs = require('../plugs')
9-//
10-//var message_render = plugs.first(exports.message_render = [])
11-//var message_compose = plugs.first(exports.message_compose = [])
12-//var message_unbox = plugs.first(exports.message_unbox = [])
13-//var sbot_log = plugs.first(exports.sbot_log = [])
14-//var sbot_whoami = plugs.first(exports.sbot_whoami = [])
15-//var avatar_image_link = plugs.first(exports.avatar_image_link = [])
16-//var emoji_url = plugs.first(exports.emoji_url = [])
17-
188 function map(ary, iter) {
199 if(Array.isArray(ary)) return ary.map(iter)
2010 }
2111
@@ -25,9 +15,9 @@
2515 message_unbox: 'first',
2616 sbot_log: 'first',
2717 sbot_whoami: 'first',
2818 avatar_image_link: 'first',
29-// emoji_link: 'first'
19 + emoji_url: 'first'
3020 }
3121
3222 exports.gives = {
3323 builtin_tabs: true,
@@ -115,9 +105,9 @@
115105 },
116106
117107 message_content_mini: function (msg, sbot) {
118108 if (typeof msg.value.content === 'string') {
119- var icon = false //api.emoji_url('lock')
109 + var icon = api.emoji_url('lock')
120110 return icon
121111 ? h('img', {className: 'emoji', src: icon})
122112 : 'PRIVATE'
123113 }
modules_basic/thread.jsView
@@ -120,10 +120,10 @@
120120 meta.root = thread[0].value.content.root || thread[0].key
121121 meta.channel = thread[0].value.content.channel
122122
123123 var recps = thread[0].value.content.recps
124- var private = thread[0].value.private
125- if(private) {
124 + var priv = thread[0].value['private']
125 + if(priv) {
126126 if(recps)
127127 meta.recps = recps
128128 else
129129 meta.recps = [thread[0].value.author, self_id]
modules_basic/timestamp.jsView
@@ -1,28 +1,46 @@
1-var h = require('hyperscript')
2-var human = require('human-time')
1 +const fs = require('fs')
2 +const h = require('../h')
3 +const human = require('human-time')
34
45 exports.needs = {}
56
6-exports.gives = 'message_meta'
7 +exports.gives = {
8 + timestamp: true,
9 + mcss: true
10 +}
711
812 exports.create = function () {
9-
10- function updateTimestampEl(el) {
11- el.firstChild.nodeValue = human(new Date(el.timestamp))
12- return el
13- }
14-
1513 setInterval(function () {
16- var els = [].slice.call(document.querySelectorAll('.timestamp'))
14 + var els = [].slice.call(document.querySelectorAll('.Timestamp'))
1715 els.forEach(updateTimestampEl)
1816 }, 60e3)
1917
20- return function (msg) {
21- return updateTimestampEl(h('a.enter.timestamp', {
22- href: '#'+msg.key,
23- timestamp: msg.value.timestamp,
24- title: new Date(msg.value.timestamp)
25- }, ''))
18 + return {
19 + timestamp,
20 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
2621 }
2722
23 + function updateTimestampEl (el) {
24 + var timestamp = Number(el.getAttribute('data-timestamp'))
25 + var display = human(new Date(timestamp)).replace(/minutes/, 'mins')
26 + el.querySelector('a').firstChild.nodeValue = display
27 + return el
28 + }
29 +
30 + function timestamp (msg) {
31 + var { key, value } = msg
32 + var { timestamp } = value
33 + var el = h('Timestamp', {
34 + attributes: {
35 + 'data-timestamp': timestamp
36 + }
37 + }, [
38 + h('a', {
39 + href: `#${key}`,
40 + title: new Date(timestamp)
41 + }, '')
42 + ])
43 + updateTimestampEl(el)
44 + return el
45 + }
2846 }
modules_basic/about.mcssView
@@ -1,0 +1,53 @@
1 +About {
2 + display: flex
3 + flex-wrap: wrap
4 +
5 + section {
6 + header {
7 + a {
8 + -target {
9 +
10 + }
11 + -name {
12 +
13 + }
14 + -image {
15 +
16 + }
17 + }
18 + }
19 +
20 + -name {
21 + display: flex
22 +
23 + header {
24 +
25 + }
26 +
27 + section {
28 + margin-left: .25rem
29 + }
30 + }
31 +
32 + -image {
33 + header {
34 +
35 + }
36 +
37 + section {
38 +
39 + }
40 + }
41 +
42 + -description {
43 + header {
44 +
45 + }
46 +
47 + section {
48 + padding-left: 1em;
49 + padding-right: 1em;
50 + }
51 + }
52 + }
53 +}
modules_basic/compose.mcssView
@@ -1,0 +1,90 @@
1 +Compose {
2 + display: flex
3 + flex-direction: column
4 +
5 + padding: 1rem .5rem 1rem 7.3rem
6 +
7 + textarea {
8 + border: 1px solid gainsboro
9 + }
10 +
11 + section.actions {
12 + display: flex
13 + flex-direction: row
14 + align-items: baseline
15 +
16 + margin-top: .4rem
17 +
18 + input[type="file"] {
19 + flex-grow: 1
20 +
21 + padding: .5rem 0
22 +
23 + width: 5.5rem
24 + color: transparent
25 +
26 + ::-webkit-file-upload-button {
27 + visibility: hidden
28 + }
29 +
30 + ::before {
31 + $composeButton
32 + padding-top: .3rem
33 +
34 + content: '📎'
35 + font-size: 1rem
36 +
37 + outline: none
38 + white-space: nowrap
39 + -webkit-user-select: none
40 + }
41 +
42 + :active, :focus {
43 + outline: none
44 + box-shadow: none
45 + }
46 + }
47 +
48 + button {
49 + $composeButton
50 +
51 + text-transform: uppercase
52 + font-weight: bold
53 + font-size: .7rem
54 + }
55 + }
56 +
57 + -expanded {
58 + textarea {
59 + height: 200px
60 + transition: height .15s ease-out
61 + }
62 +
63 + section.actions {
64 + display: flex
65 + }
66 + }
67 +
68 + -contracted {
69 + textarea {
70 + height: 50px
71 + transition: height .15s ease-in
72 + }
73 +
74 + section.actions {
75 + display: none
76 + }
77 + }
78 +
79 +}
80 +
81 +$composeButton {
82 + background: #fff
83 + color: #666
84 + border: 1px solid #bbb
85 + border-radius: .5rem
86 + padding: .5rem
87 + margin: 0
88 + cursor: pointer
89 +}
90 +
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.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/reply.jsView
@@ -1,0 +1,9 @@
1 +var h = require('../h')
2 +
3 +exports.gives = 'message_action'
4 +
5 +exports.create = function () {
6 + return function (msg) {
7 + return h('a', { href: '#' + msg.key }, 'Reply')
8 + }
9 +}
modules_basic/timestamp.mcssView
@@ -1,0 +1,7 @@
1 +Timestamp {
2 + a {
3 + $textSubtle
4 + font-size: .8rem
5 + }
6 +}
7 +
modules_core/app.jsView
@@ -1,13 +1,19 @@
11 var plugs = require('../plugs')
22 var h = require('hyperscript')
3 +var insertCss = require('insert-css')
34
45 module.exports = {
5- needs: {screen_view: 'first'},
6 + needs: {
7 + screen_view: 'first',
8 + styles: 'first'
9 + },
610 gives: 'app',
711 create: function (api) {
812 return function () {
9- document.head.appendChild(h('style', require('../style.css.json')))
13 + process.nextTick(function () {
14 + insertCss(api.styles())
15 + })
1016
1117 window.addEventListener('error', window.onError = function (e) {
1218 document.body.appendChild(h('div.error',
1319 h('h1', e.message),
modules_core/file-input.jsView
@@ -3,16 +3,12 @@
33 var pull = require('pull-stream')
44 var mime = require('simple-mime')('application/octect-stream')
55 var split = require('split-buffer')
66
7-var plugs = require('../plugs')
8-
9-var add = plugs.first(exports.sbot_blobs_add = [])
10-
117 module.exports = {
128 needs: {sbot_blobs_add: 'first'},
139 gives: 'file_input',
14- create: function () {
10 + create: function (api) {
1511 return function FileInput(onAdded) {
1612 return h('input', { type: 'file',
1713 onchange: function (ev) {
1814 var file = ev.target.files[0]
@@ -20,9 +16,9 @@
2016 var reader = new FileReader()
2117 reader.onload = function () {
2218 pull(
2319 pull.values(split(new Buffer(reader.result), 64*1024)),
24- add(function (err, blob) {
20 + api.sbot_blobs_add(function (err, blob) {
2521 if(err) return console.error(err)
2622 onAdded({
2723 link: blob,
2824 name: file.name,
modules_core/index.jsView
@@ -2,11 +2,14 @@
22 // "_screen_view.js": require('./_screen_view.js'),
33 "app.js": require('./app.js'),
44 "blob-url.js": require('./blob-url.js'),
55 "crypto.js": require('./crypto.js'),
6 + "external-confirm.js": require('./external-confirm.js'),
67 "file-input.js": require('./file-input.js'),
78 "menu.js": require('./menu.js'),
89 "message-confirm.js": require('./message-confirm.js'),
910 "tabs.js": require('./tabs.js'),
10- "sbot.js": require('./sbot.js')
11 + "sbot.js": require('./sbot.js'),
12 + "styles.js": require('./styles.js'),
13 + "style-mixins.js": require('./style-mixins.js')
1114 }
1215
modules_core/message-confirm.jsView
@@ -1,27 +1,33 @@
1 +var fs = require('fs')
12 var lightbox = require('hyperlightbox')
2-var h = require('hyperscript')
3 +var h = require('../h')
34 var u = require('../util')
45 var self_id = require('../keys').id
56 //publish or add
67
78 var plugs = require('../plugs')
89
910 exports.needs = {
10- publish: 'first', message_content: 'first', avatar: 'first',
11 + publish: 'first',
12 + message_render: 'first',
13 + avatar: 'first',
1114 message_meta: 'map'
1215 }
1316
14-exports.gives = 'message_confirm'
17 +exports.gives = {
18 + message_confirm: 'true',
19 + mcss: 'true'
20 +}
1521
16-//var publish = plugs.first(exports.sbot_publish = [])
17-//var message_content = plugs.first(exports.message_content = [])
18-//var avatar = plugs.first(exports.avatar = [])
19-//var message_meta = plugs.map(exports.message_meta = [])
20-//
2122 exports.create = function (api) {
22- return function (content, cb) {
23 + return {
24 + message_confirm,
25 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
26 + }
2327
28 + function message_confirm (content, cb) {
29 +
2430 cb = cb || function () {}
2531
2632 var lb = lightbox()
2733 document.body.appendChild(lb)
@@ -36,32 +42,33 @@
3642 content: content
3743 }
3844 }
3945
40- var okay = h('button', 'okay', {onclick: function () {
41- lb.remove()
42- api.publish(content, cb)
43- }})
46 + var okay = h('button', {
47 + 'ev-click': () => {
48 + lb.remove()
49 + api.publish(content, cb)
50 + }},
51 + 'okay'
52 + )
4453
45- var cancel = h('button', 'Cancel', {onclick: function () {
46- lb.remove()
47- cb(null)
48- }})
54 + var cancel = h('button', {
55 + 'ev-click': () => {
56 + lb.remove()
57 + cb(null)
58 + }},
59 + 'Cancel'
60 + )
4961
5062 okay.addEventListener('keydown', function (ev) {
5163 if(ev.keyCode === 27) cancel.click() //escape
5264 })
5365
54- lb.show(h('div.column.message-confirm',
55- h('div.message',
56- h('div.title.row',
57- h('div.avatar', api.avatar(msg.value.author, 'thumbnail')),
58- h('div.message_meta.row', api.message_meta(msg))
59- ),
60- h('div.message_content', api.message_content(msg)
61- || h('pre', JSON.stringify(msg, null, 2)))
62- ),
63- h('div.row.message-confirm__controls', okay, cancel)
66 + lb.show(h('MessageConfirm', [
67 + h('header -preview_description', h('h1', 'Preview')),
68 + h('section -message_preview', api.message_render(msg)),
69 + h('section -actions', [okay, cancel])
70 + ]
6471 ))
6572
6673 okay.focus()
6774 }
modules_core/sbot.jsView
@@ -66,9 +66,10 @@
6666 }
6767
6868 createClient(keys, {
6969 manifest: require('../manifest.json'),
70- remote: require('../config')().remote
70 + remote: require('../config')().remote,
71 + caps: config.caps
7172 }, function (err, _sbot) {
7273 if(err)
7374 return notify(err)
7475
modules_core/tabs.jsView
@@ -15,9 +15,9 @@
1515 //var screen_view = plugs.first(exports._screen_view = [])
1616 //var search_box = plugs.first(exports.search_box = [])
1717 //var menu = plugs.first(exports.menu = [])
1818
19-exports.needs = {screen_view: 'first', search_box: 'first', menu: 'first'}
19 +exports.needs = {screen_view: 'first', search_box: 'first', menu: 'first', 'external_confirm':'first'}
2020
2121 exports.gives = 'screen_view'
2222
2323 exports.create = function (api) {
@@ -94,9 +94,9 @@
9494 if (link.getAttribute('href') === '#') return
9595
9696 //open external links.
9797 //this ought to be made into something more runcible
98- if(open.isExternal(link.href)) return open(link.href)
98 + if(link.href && open.isExternal(link.href)) return api.external_confirm(link.href)
9999
100100 if(tabs.has(path))
101101 return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey)
102102
modules_core/external-confirm.jsView
@@ -1,0 +1,44 @@
1 +var lightbox = require('hyperlightbox')
2 +var h = require('hyperscript')
3 +var open = require('open-external')
4 +
5 +exports.gives = 'external_confirm'
6 +
7 +exports.create = function (api) {
8 + return function (href) {
9 + var lb = lightbox()
10 + document.body.appendChild(lb)
11 +
12 + var okay = h('button', 'open', {onclick: function () {
13 + lb.remove()
14 + open(href)
15 + }})
16 +
17 + var cancel = h('button', 'Cancel', {onclick: function () {
18 + lb.remove()
19 + }})
20 +
21 + okay.addEventListener('keydown', function (ev) {
22 + if (ev.keyCode === 27) cancel.click() // escape
23 + })
24 +
25 + lb.show(h('div.column',
26 + h('div', [
27 + h('div.title.row', [
28 + h('strong.row', [
29 + 'Do you want to open this external link in your default browser:'
30 + ])
31 + ]),
32 + h('div', [
33 + h('pre', href)
34 + ])
35 + ]),
36 + h('div.row', [
37 + okay,
38 + cancel
39 + ])
40 + ))
41 +
42 + okay.focus()
43 + }
44 +}
modules_core/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_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/styles.jsView
@@ -1,0 +1,34 @@
1 +var h = require('../h')
2 +var compile = require('micro-css')
3 +var fs = require('fs')
4 +var Path = require('path')
5 +
6 +// TODO distribute these styles across all
7 +// the relevant modules, not as a core style.
8 +var coreStyle = fs.readFileSync(Path.join(__dirname, '../style.css'))
9 +
10 +module.exports = {
11 + needs: {
12 + mcss: 'map',
13 + css: 'map'
14 + },
15 + gives: {
16 + mcss: true,
17 + css: true,
18 + styles: true
19 + },
20 + create: function (api) {
21 + var styles = ''
22 + process.nextTick(function () {
23 + var mcss = api.mcss().join('\n')
24 + var css = api.css().join('\n')
25 + styles = coreStyle + compile(mcss) + css
26 + })
27 + return {
28 + styles: function () { return styles },
29 + // export empty styles
30 + mcss: function () { return '' },
31 + css: function () { return '' }
32 + }
33 + }
34 +}
modules_extra/channel.jsView
@@ -3,14 +3,8 @@
33 var pull = require('pull-stream')
44 var Scroller = require('pull-scroll')
55 var mfr = require('map-filter-reduce')
66
7-//var plugs = require('../plugs')
8-//var message_render = plugs.first(exports.message_render = [])
9-//var message_compose = plugs.first(exports.message_compose = [])
10-//var sbot_log = plugs.first(exports.sbot_log = [])
11-//var sbot_query = plugs.first(exports.sbot_query = [])
12-
137 exports.needs = {
148 message_render: 'first',
159 message_compose: 'first',
1610 sbot_log: 'first',
@@ -60,16 +54,16 @@
6054
6155 pull(
6256 api.sbot_log({old: false}),
6357 pull.filter(matchesChannel),
64- Scroller(div, content, message_render, true, false)
58 + Scroller(div, content, api.message_render, true, false)
6559 )
6660
6761 pull(
6862 api.sbot_query({reverse: true, query: [
6963 {$filter: {value: {content: {channel: channel}}}}
7064 ]}),
71- Scroller(div, content, message_render, false, false)
65 + Scroller(div, content, api.message_render, false, false)
7266 )
7367
7468 return div
7569 }
modules_extra/git.jsView
@@ -33,9 +33,10 @@
3333
3434 exports.gives = {
3535 message_action: true,
3636 message_meta: true,
37- message_content: true
37 + message_content: true,
38 + message_title: true
3839 }
3940
4041
4142 var self_id = require('../keys').id
@@ -301,10 +302,8 @@
301302 if(c.type === 'git-repo') {
302303 var branchesT, tagsT, openIssuesT, closedIssuesT, openPRsT, closedPRsT
303304 var forksT
304305 var div = h('div',
305- h('p', 'git repo ', repoName(msg.key)),
306- c.upstream ? h('p', 'fork of ', repoLink(c.upstream)) : '',
307306 h('p', h('code', 'ssb://' + msg.key)),
308307 h('div.git-table-wrapper', {style: {'max-height': '12em'}},
309308 h('table',
310309 branchesT = tableRows(h('tr',
@@ -409,9 +408,8 @@
409408 }
410409
411410 if(c.type === 'git-update') {
412411 return [
413- h('p', 'pushed to ', repoLink(c.repo)),
414412 c.refs ? h('ul', Object.keys(c.refs).map(function (ref) {
415413 var rev = c.refs[ref]
416414 return h('li',
417415 shortRefName(ref) + ': ',
@@ -449,25 +447,54 @@
449447 }
450448
451449 if(c.type === 'issue') {
452450 return h('div',
453- h('p', 'opened issue on ', repoLink(c.project)),
454451 c.title ? h('h4', c.title) : '',
455452 api.markdown(c)
456453 )
457454 }
458455
459456 if(c.type === 'pull-request') {
460457 return h('div',
461- h('p', 'opened pull-request ',
462- 'to ', repoLink(c.repo), ':', c.branch, ' ',
463- 'from ', repoLink(c.head_repo), ':', c.head_branch),
464458 c.title ? h('h4', c.title) : '',
465459 api.markdown(c)
466460 )
467461 }
468462 },
469463
464 + message_title: function (msg) {
465 + var c = msg.value.content
466 +
467 + if(c.type === 'git-repo') {
468 + return h('div', [
469 + h('p', 'git repo ', repoName(msg.key)),
470 + c.upstream ? h('p', 'fork of ', repoLink(c.upstream)) : ''
471 + ])
472 + }
473 +
474 + if(c.type === 'git-update') {
475 + return h('p', 'pushed to ', repoLink(c.repo))
476 + }
477 +
478 + if(c.type === 'issue-edit' || (c.type === 'post' && c.text === '')) {
479 + return h('div', [
480 + c.issue ? renderIssueEdit(c) : null,
481 + c.issues ? c.issues.map(renderIssueEdit) : null
482 + ])
483 + }
484 +
485 + if(c.type === 'issue') {
486 + return h('p', 'opened issue on ', repoLink(c.project))
487 + }
488 +
489 + if(c.type === 'pull-request') {
490 + return h('p', 'opened pull-request ', [
491 + 'to ', repoLink(c.repo), ':', c.branch, ' ',
492 + 'from ', repoLink(c.head_repo), ':', c.head_branch
493 + ])
494 + }
495 + },
496 +
470497 message_meta: function (msg, sbot) {
471498 var type = msg.value.content.type
472499 if (type === 'issue' || type === 'pull-request') {
473500 var el = h('em', '...')
@@ -515,5 +542,4 @@
515542 }
516543 }
517544 }
518545
519-
modules_extra/index.jsView
@@ -1,13 +1,14 @@
11 module.exports = {
2- "audio-mp3.js": require('./audio-mp3.js'),
2 + // "audio-mp3.js": require('./audio-mp3.js'),
33 "blob.js": require('./blob.js'),
44 "channel.js": require('./channel.js'),
55 "emoji.js": require('./emoji.js'),
66 "suggest-emoji.js": require('./suggest-emoji.js'),
77 "dns.js": require('./dns.js'),
88 "git.js": require('./git.js'),
99 "git-ssb.js": require('./git-ssb.js'),
10 + "key.js": require('./key.js'),
1011 "notifications.js": require('./notifications.js'),
1112 "meta-image.js": require('./meta-image.js'),
1213 "music-release-cc.js": require('./music-release-cc.js'),
1314 "music-release.js": require('./music-release.js'),
modules_extra/network.jsView
@@ -1,92 +1,84 @@
1-var isVisible = require('is-visible').isVisible
2-var h = require('hyperscript')
1 +const fs = require('fs')
2 +// const { isVisible } = require('is-visible')
3 +const h = require('../h')
4 +const human = require('human-time')
35
6 +const {
7 + Struct, Value, Dict,
8 + dictToCollection, map: mutantMap, when, computed
9 +} = require('@mmckegg/mutant')
10 +
411 //var avatar = plugs.first(exports.avatar = [])
512 //var sbot_gossip_peers = plugs.first(exports.sbot_gossip_peers = [])
613 //var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = [])
714
815 exports.needs = {
9- avatar: 'first',
16 + avatar_image_link: 'first',
17 + avatar_name_link: 'first',
1018 sbot_gossip_peers: 'first',
1119 sbot_gossip_connect: 'first'
1220 }
1321
1422 exports.gives = {
1523 menu_items: true,
1624 builtin_tabs: true,
17- screen_view: true
25 + screen_view: true,
26 + mcss: true
1827 }
1928
2029 //sbot_gossip_connect
2130 //sbot_gossip_add
2231
23-var human = require('human-time')
2432
2533 function legacyToMultiServer(addr) {
2634 return 'net:'+addr.host + ':'+addr.port + '~shs:'+addr.key.substring(1).replace('.ed25519','')
2735 }
2836
29-//types of peers
30-
31-
3237 //on the same wifi network
33-function isLocal (e) {
38 +function isLocal (peer) {
3439 // don't rely on private ip address, because
3540 // cjdns creates fake private ip addresses.
36- return ip.isPrivate(e.host) && e.type === 'local'
41 + return ip.isPrivate(peer.host) && peer.type === 'local'
3742 }
3843
3944
40-//pub is running scuttlebot >=8
41-//have connected successfully.
42-function isLongterm (e) {
43- return e.ping && e.ping.rtt && e.ping.rtt.mean > 0
44-}
45 +function getType (peer) {
46 + return (
47 + isLongterm(peer) ? 'modern'
48 + : isLegacy(peer) ? 'legacy'
49 + : isInactive(peer) ? 'inactive'
50 + : isUnattempted(peer) ? 'unattempted'
51 + : 'other' //should never happen
52 + )
4553
46-//pub is running scuttlebot < 8
47-//have connected sucessfully
48-function isLegacy (peer) {
49- return /connect/.test(peer.state) || (peer.duration && peer.duration.mean) > 0 && !isLongterm(peer)
50-}
54 + //pub is running scuttlebot >=8
55 + //have connected successfully.
56 + function isLongterm (peer) {
57 + return peer.ping && peer.ping.rtt && peer.ping.rtt.mean > 0
58 + }
5159
52-//tried to connect, but failed.
53-function isInactive (e) {
54- return e.stateChange && (e.duration && e.duration.mean == 0)
55-}
60 + //pub is running scuttlebot < 8
61 + //have connected sucessfully
62 + function isLegacy (peer) {
63 + return /connect/.test(peer.state) || (peer.duration && peer.duration.mean) > 0 && !isLongterm(peer)
64 + }
5665
57-//havn't tried to connect peer yet.
58-function isUnattempted (e) {
59- return !e.stateChange
60-}
66 + //tried to connect, but failed.
67 + function isInactive (peer) {
68 + return peer.stateChange && (peer.duration && peer.duration.mean == 0)
69 + }
6170
62-function getType (e) {
63- return (
64- isLongterm(e) ? 'modern'
65- : isLegacy(e) ? 'legacy'
66- : isInactive(e) ? 'inactive'
67- : isUnattempted(e) ? 'unattempted'
68- : 'other' //should never happen
69- )
71 + //havn't tried to connect peer yet.
72 + function isUnattempted (peer) {
73 + return !peer.stateChange
74 + }
7075 }
7176
72-function origin (e) {
73- return e.source === 'local' ? 0 : 1
77 +function origin (peer) {
78 + return peer.source === 'local' ? 0 : 1
7479 }
7580
76-var states = {
77- connected: 3,
78- connecting: 2
79-}
80-
81-var types = {
82- modern: 4,
83- legacy: 3,
84- inactive: 2,
85- unattempted: 1,
86- other: 0
87-}
88-
8981 function round(n) {
9082 return Math.round(n*100)/100
9183 }
9284
@@ -99,78 +91,163 @@
9991 else
10092 return round(s)+'ms'
10193 }
10294
95 +function peerListSort (a, b) {
96 + var states = {
97 + connected: 3,
98 + connecting: 2
99 + }
103100
101 + //types of peers
102 + var types = {
103 + modern: 4,
104 + legacy: 3,
105 + inactive: 2,
106 + unattempted: 1,
107 + other: 0
108 + }
104109
105-exports.create = function (api) {
110 + return (
111 + (states[b.state] || 0) - (states[a.state] || 0)
112 + || origin(b) - origin(a)
113 + || types[getType(b)] - types[getType(a)]
114 + || b.stateChange - a.stateChange
115 + )
116 +}
106117
107- return {
108- menu_items: function () {
109- return h('a', {href: '#/network'}, '/network')
110- },
118 +function formatDate (time) {
119 + return new Date(time).toString()
120 +}
111121
112- builtin_tabs: function () {
113- return ['/network']
114- },
122 +function humanDate (time) {
123 + return human(new Date(time)).replace(/minute/, 'min').replace(/second/, 'sec')
124 +}
115125
116- screen_view: function (path) {
126 +exports.create = function (api) {
117127
118- if(path !== '/network') return
128 + return {
129 + menu_items: () => h('a', {href: '#/network'}, '/network'),
130 + builtin_tabs: () => ['/network'],
131 + screen_view,
132 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
133 + }
119134
120- var ol = h('ul.network')
135 + function screen_view (path) {
136 + if (path !== '/network') return
121137
122- ;(function poll () {
138 + var peers = obs_gossip_peers(api)
123139
124- //if this tab isn't open, don't update.
125- //todo: make a better way to do this...
126- if(!isVisible(ol))
127- return setTimeout(poll, 1000)
140 + return h('div', { style: {'overflow':'auto'}, className: 'column scroller' }, [
141 + h('Network', [
142 + mutantMap(peers, peer => {
143 + var { key, ping, source, state, stateChange } = peer
128144
129- api.sbot_gossip_peers(function (err, list) {
130- ol.innerHTML = ''
131- list.sort(function (a, b) {
132- return (
133- (states[b.state] || 0) - (states[a.state] || 0)
134- || origin(b) - origin(a)
135- || types[getType(b)] - types[getType(a)]
136- || b.stateChange - a.stateChange
137- )
138- }).forEach(function (peer) {
139- ol.appendChild(h('div',
140- api.avatar(peer.key, 'thumbnail'),
141- h('div',
142- peer.state || 'not connected',
143- ' ',
144- getType(peer),
145- ' ',
146- //TODO: show nicer details, with labels. etc.
147- (peer.ping && peer.ping.rtt) ? duration(peer.ping.rtt.mean) : '',
148- ' ',
149- (peer.ping && peer.ping.skew) ? duration(peer.ping.skew.mean) : '',
150- h('label',
151- {title: new Date(peer.stateChange).toString()},
152- peer.stateChange && ('(' + human(new Date(peer.stateChange))) + ')')
153- ),
154- 'source:'+peer.source,
155- h('pre', legacyToMultiServer(peer)),
156- h('button', 'connect', {onclick: function () {
157- api.sbot_gossip_connect(peer, function (err) {
145 + return h('NetworkConnection', [
146 + h('section.avatar', [
147 + api.avatar_image_link(key()),
148 + ]),
149 + h('section.name', [
150 + api.avatar_name_link(key()),
151 + ]),
152 + h('section.type', [
153 + computed(peer, getType),
154 + ]),
155 + h('section.source', [
156 + h('label', 'source:'),
157 + h('code', source)
158 + ]),
159 + h('section.state', [
160 + h('label', 'state:'),
161 + h('i', {
162 + className: computed(state, (state) => '-'+state)
163 + }),
164 + h('code', when(state, state, 'not connected'))
165 + ]),
166 + h('section.actions', [
167 + h('button', {
168 + 'ev-click': () => {
169 + api.sbot_gossip_connect(peer(), (err) => {
158170 if(err) console.error(err)
159- else console.log('connected to', peer)
171 + else console.log('connected to', peer())
160172 })
161- }})
173 + }},
174 + 'connect'
162175 )
163- )
164- })
165-
166- setTimeout(poll, 5000)
176 + ]),
177 + h('section.time-ago', [
178 + h('div',
179 + { title: computed(stateChange, formatDate) },
180 + [ computed(stateChange, humanDate) ]
181 + )
182 + ]),
183 + h('section.ping', [
184 + h('div.rtt', [
185 + h('label', 'rtt:'),
186 + h('code', computed(ping.rtt.mean, duration))
187 + ]),
188 + h('div.skew', [
189 + h('label', 'skew:'),
190 + h('code', computed(ping.skew.mean, duration))
191 + ]),
192 + ]),
193 + h('section.address', [
194 + h('code', computed(peer, legacyToMultiServer))
195 + ])
196 + ])
167197 })
198 + ])
199 + ])
200 + }
201 +}
168202
169- })()
203 +function obs_gossip_peers (api) {
204 + var timer = null
205 + var state = Dict({}, {
206 + onListen: () => {
207 + timer = setInterval(refresh, 5e3)
208 + },
209 + onUnlisten: () => {
210 + clearInterval(timer)
211 + }
212 + })
170213
171- return h('div.column.scroll-y', ol)
172- }
214 + refresh()
215 +
216 + return dictToCollection.values(state)
217 +
218 + function refresh () {
219 + api.sbot_gossip_peers((err, peers) => {
220 + peers.forEach(data => {
221 + var id = legacyToMultiServer(data)
222 + var current = state.get(id)
223 + if (!current) {
224 + current = Peer()
225 + current.set(data)
226 + state.put(id, current)
227 + } else {
228 + current.set(data)
229 + }
230 + })
231 + })
173232 }
174233 }
175234
235 +function Peer () {
236 + var peer = Struct({
237 + key: Value(),
238 + ping: Struct({
239 + rtt: Struct({
240 + mean: Value()
241 + }),
242 + skew: Struct({
243 + mean: Value()
244 + })
245 + }),
246 + source: Value(),
247 + state: Value(),
248 + stateChange: Value()
249 + })
176250
251 + return peer
252 +}
253 +
modules_extra/raw.jsView
@@ -28,10 +28,11 @@
2828 return h('input', {
2929 type: 'checkbox',
3030 title: 'View Data',
3131 onclick: function () {
32- var msgEl = this.parentNode.parentNode.parentNode
33- var msgContentEl = msgEl.querySelector('.message_content')
32 + // HACK (mw) yo we need a better way to replace the content
33 + var msgEl = this.parentNode.parentNode
34 + var msgContentEl = msgEl.querySelector('.\\.content')
3435 if (this.checked) {
3536 // move away the content
3637 while (el = msgContentEl.firstChild)
3738 tmp.appendChild(el)
modules_extra/key.jsView
@@ -1,0 +1,62 @@
1 +var h = require('hyperscript')
2 +var u = require('../util')
3 +var pull = require('pull-stream')
4 +var Scroller = require('pull-scroll')
5 +
6 +//var plugs = require('../plugs')
7 +// var message_render = plugs.first(exports.message_render = [])
8 +// var message_compose = plugs.first(exports.message_compose = [])
9 +// var sbot_log = plugs.first(exports.sbot_log = [])
10 +
11 +
12 +exports.gives = {
13 + menu_items: true, screen_view: true
14 +}
15 +
16 +exports.create = function (api) {
17 + return {
18 + menu_items: function () {
19 + return h('a', {href: '#/key'}, '/key')
20 + },
21 + screen_view: function (path, sbot) {
22 + if(path === '/key') {
23 + if(process.title === 'browser') {
24 + var importKey = h('textarea', {placeholder: 'import an existing public/private key', name: 'textarea'})
25 + var importRemote = h('textarea', {placeholder: 'import an existing remote', name: 'textarea'})
26 + var content = h('div.column.scroller__content')
27 + var div = h('div.column.scroller',
28 + {style: {'overflow':'auto'}},
29 + h('div.scroller__wrapper',
30 + h('div.column.scroller__content',
31 + h('div.message',
32 + h('p', {innerHTML: 'Your secret key is: <pre><code>' + localStorage['browser/.ssb/secret'] + '</code></pre>'}),
33 + h('form',
34 + importKey,
35 + h('button', {onclick: function (e){
36 + localStorage['browser/.ssb/secret'] = importKey.value.replace(/\s+/g, ' ')
37 + alert('Your public/private key has been updated')
38 + e.preventDefault()
39 + }}, 'Import'),
40 + h('p', {innerHTML: 'Your ws remote is: <pre>' + localStorage.remote + '</pre>'}),
41 + h('form',
42 + importRemote,
43 + h('button', {onclick: function (e){
44 + localStorage.remote = importRemote.value
45 + alert('Your websocket remote has been updated')
46 + e.preventDefault()
47 + }}, 'Import')
48 + )
49 + )
50 + )
51 + )
52 + )
53 + )
54 + return div
55 + } else {
56 + return h('p', 'Your key is saved at .ssb/secret')
57 + }
58 + }
59 + }
60 + }
61 +}
62 +
modules_extra/network.mcssView
@@ -1,0 +1,117 @@
1 +Network {
2 + div {
3 + margin: .8rem
4 + border-bottom: 1px solid gainsboro
5 + }
6 +
7 +}
8 +
9 +NetworkConnection {
10 + padding-left: 6rem
11 + padding-bottom: .8rem
12 + position: relative
13 +
14 + display: flex
15 + flex-direction: row
16 + flex-wrap: wrap
17 + align-items: center
18 +
19 + section.avatar {
20 + position: absolute
21 + left: 0
22 + top: 0
23 +
24 + a img {
25 + height: 3rem
26 + width: 3rem
27 + }
28 + }
29 +
30 + section.time-ago {
31 + position: absolute
32 + left: 0
33 + top: 3.5rem
34 +
35 + font-size: .8rem
36 + $textSubtle
37 + }
38 +
39 + section.name {
40 + flex-basis: 100%
41 +
42 + margin-right: 1rem
43 + }
44 +
45 + section.type {
46 + flex-basis: 100%
47 +
48 + font-size: .8rem
49 + $textSubtle
50 + }
51 +
52 + section.source {
53 + flex-basis: 100%
54 + label { margin-right: .5rem }
55 + }
56 +
57 + section.state {
58 + display: flex
59 + flex-direction: row
60 + align-items: center
61 +
62 + margin-right: 2rem
63 +
64 + label { margin-right: .5rem }
65 + i {
66 + width: 1rem
67 + height: 1rem
68 + border-radius: .5rem
69 +
70 + margin: 0 .2rem 0 .5rem
71 +
72 + background: #ccc
73 +
74 + -connecting, -disconnecting {
75 + background: #95cc95
76 + }
77 +
78 + -connected {
79 + background: #06e206
80 + }
81 + }
82 + }
83 +
84 + section.actions {
85 + button {
86 + padding: .2rem
87 + font-size: .7rem
88 + }
89 + }
90 +
91 +
92 + section.ping {
93 + flex-basis: 100%
94 + display: flex
95 +
96 + font-size: .8rem
97 + $textSubtle
98 +
99 + div.rtt {
100 + flex-basis: 8rem
101 + label { margin-right: .5rem }
102 + }
103 +
104 + div.skew {
105 + flex-basis: 8rem
106 + label { margin-right: .5rem }
107 + }
108 + }
109 +
110 + section.address{
111 + flex-basis: 100%
112 +
113 + font-size: .8rem
114 + $textSubtle
115 + }
116 +}
117 +
package.jsonView
@@ -1,29 +1,34 @@
11 {
22 "name": "patchbay",
33 "description": "a pluggable patchwork",
4- "version": "6.0.1",
4 + "version": "6.1.10",
55 "homepage": "https://github.com/dominictarr/patchbay",
66 "repository": {
77 "type": "git",
88 "url": "git://github.com/dominictarr/patchbay.git"
99 },
1010 "dependencies": {
11 + "@mmckegg/mutant": "^3.12.0",
12 + "brfs": "^1.4.3",
1113 "cont": "^1.0.3",
1214 "dataurl-": "^0.1.0",
1315 "depject": "^3.0.0",
16 + "es2040": "^1.2.4",
1417 "hjson": "^2.0.3",
1518 "human-time": "0.0.1",
1619 "hypercombo": "0.1.0",
1720 "hypercrop": "^1.0.1",
1821 "hyperfile": "^1.1.0",
19- "hyperlightbox": "^0.1.3",
22 + "hyperlightbox": "1.0.0",
2023 "hyperprogress": "0.1.0",
2124 "hyperscript": "^1.4.7",
2225 "hypertabs": "^3.0.0",
23- "is-visible": "^2.0.4",
26 + "insert-css": "^2.0.0",
27 + "is-visible": "^2.1.1",
2428 "kvgraph": "^0.1.0",
2529 "map-filter-reduce": "^3.0.1",
30 + "micro-css": "^0.6.2",
2631 "mime-types": "^2.1.11",
2732 "moment": "^2.13.0",
2833 "open-external": "^0.1.1",
2934 "peaks.js": "^0.4.7",
@@ -31,11 +36,12 @@
3136 "pull-many": "^1.0.7",
3237 "pull-next": "^0.0.1",
3338 "pull-paramap": "^1.1.6",
3439 "pull-reconnect": "^0.0.3",
35- "pull-scroll": "^0.2.0",
40 + "pull-scroll": "^1.0.1",
3641 "pull-stream": "^3.4.5",
3742 "scuttlebot": "^8.7.2",
43 + "setimmediate": "^1.0.5",
3844 "simple-mime": "^0.1.0",
3945 "split-buffer": "^1.0.0",
4046 "ssb-avatar": "^0.2.0",
4147 "ssb-blobs": "^0.1.7",
@@ -49,22 +55,29 @@
4955 "ssb-mentions": "^0.1.0",
5056 "ssb-query": "^0.1.1",
5157 "ssb-ref": "^2.6.2",
5258 "ssb-sort": "^1.0.0",
53- "ssb-ws": "^0.6.2",
59 + "ssb-ws": "^1.0.1",
5460 "suggest-box": "^2.2.1",
5561 "text-node-searcher": "^1.1.0",
56- "visualize-buffer": "0.0.0"
62 + "visualize-buffer": "0.0.1"
5763 },
5864 "devDependencies": {
5965 "browselectrify": "^1.0.1",
6066 "electro": "^2.0.3",
61- "electron": "^1.4.10"
67 + "electron": "^1.4.10",
68 + "indexhtmlify": "^1.3.1"
6269 },
70 + "browserify": {
71 + "transform": [
72 + "brfs",
73 + "es2040"
74 + ]
75 + },
6376 "scripts": {
64- "lite": "node scripts/style.js && mkdir -p build && browserify index.js | indexhtmlify > build/index.html",
77 + "lite": "mkdir -p build && browserify index.js | indexhtmlify --title patchbay > build/index.html",
6578 "start": "electro index.js",
66- "bundle": "node scripts/style.js && mkdir -p build && browselectrify index.js > build/bundle.js",
79 + "bundle": "mkdir -p build && browselectrify index.js > build/bundle.js",
6780 "rebuild": "npm rebuild --runtime=electron --target=$(electron -v) --abi=$(electron --abi) --disturl=https://atom.io/download/atom-shell",
6881 "graph": "node index.js | dot -Tsvg > graph.svg",
6982 "test": "set -e; for t in test/*.js; do node $t; done"
7083 },
scripts/create-index.jsView
@@ -1,8 +1,0 @@
1-var fs = require('fs')
2-var path = require('path')
3-
4-fs.writeFileSync(
5- path.join(__dirname, '..', 'style.css.json'),
6- JSON.stringify(fs.readFileSync(path.join(__dirname, '..', 'style.css'), 'utf8'))
7-)
8-
scripts/style.jsView
@@ -1,8 +1,0 @@
1-var fs = require('fs')
2-var path = require('path')
3-
4-fs.writeFileSync(
5- path.join(__dirname, '..', 'style.css.json'),
6- JSON.stringify(fs.readFileSync(path.join(__dirname, '..', 'style.css'), 'utf8'))
7-)
8-
style.cssView
@@ -1,6 +1,7 @@
11 body {
22 font-family: sans-serif;
3 + color: #222;
34 }
45
56 h1, h2, h3, h4, h5, h6, p, ul, ol {
67 margin-top: .35em;
@@ -101,9 +102,9 @@
101102 border-radius: .2em;
102103 font-family: sans-serif;
103104 }
104105
105-input:focus, .compose:focus, .message:focus, button:focus {
106 +input:focus, .compose:focus, button:focus {
106107 outline: none;
107108 border-color: #0088cc;
108109 box-shadow: 0 0 4px #0088cc;
109110 }
@@ -162,106 +163,15 @@
162163 margin-left: auto;
163164 margin-right: auto;
164165 }
165166
166-/* compose */
167-
168-.compose {
169- width: 100%;
170- margin-top: .5em;
171- margin-bottom: .5em;
172- border: 1px solid #f5f5f5;
173-}
174-
175-.compose button {
176- float: right;
177-}
178-
179167 /* messages */
168 +/* is .title used any more? */
180169
181-.message {
182- position: relative;
183- flex-basis: 0;
184- margin-top: .25em;
185- margin-bottom: .25em;
186- padding: .2em;
187- border-top: 1px solid #f5f5f5;
188- // border-bottom: 1px solid #f5f5f5;
189- background: white;
190-}
191-
192-.message:hover {
193- background: #f9f9f9;
194-}
195-
196-.message--mini {
197- font-size: 1em;
198- margin: 0;
199- padding: .2em;
200- border: none;
201- background: inherit;
202-}
203-
204-.message_content div > span {
205- font-size: 0.9em;
206- margin-bottom: 0.7em;
207- display: block;
208- color: #888;
209-}
210-
211-.message_content--mini div > span {
212- display: inline-block;
213-}
214-
215-.message_content div > span a {
216- color: #005d8c;
217-}
218-
219-.message_meta input {
220- font-size: .8em;
221-}
222-
223-.message_meta {
224- margin-left: auto;
225-}
226-
227-.message_meta > * {
228- margin-left: .5ex;
229-}
230-
231-.message_actions {
232- position: absolute;
233- bottom: 0; right: 0;
234- margin-right: .5ex;
235- margin-bottom: .5ex;
236-}
237-
238170 .title {
239171 padding: .5ex;
240172 }
241173
242-.message img {
243- max-width: 100%;
244-}
245-
246-.message > .title > .avatar {
247- margin-left: 0;
248-}
249-
250-.message_content {
251- padding: .5ex;
252-}
253-
254-.actions > * {
255- padding-left: 5px;
256- margin-left: 1px;
257-}
258-
259-.actions > :not(:last-child) {
260- border-right: 2px solid #eee;
261- padding-right: 5px;
262-}
263-
264174 .emoji {
265175 height: 1em;
266176 width: 1em;
267177 vertical-align: top;
@@ -315,11 +225,16 @@
315225 .avatar--thumbnail {
316226 width: 2.5em;
317227 height: 2.5em;
318228 float: left;
319- margin-right: .5ex;
229 + margin: 0 .25ex;
320230 }
321231
232 +.\.meta .avatar--thumbnail {
233 + width: 1.9em;
234 + height: 1.9em;
235 +}
236 +
322237 .avatar--fullsize {
323238 width: 50%;
324239 }
325240
@@ -338,19 +253,22 @@
338253
339254 /* lightbox - used in message-confirm */
340255
341256 .lightbox {
257 + position: fixed;
258 + left: 0px;
259 + right: 0px;
260 + top: 50px;
342261 overflow: auto;
343- padding: 1.5em;
344- margin-top: 3em;
345- margin-bottom: 3em;
346- width: 600px;
262 + width: 650px;
263 + padding: 25px;
264 + margin: auto;
265 +
266 + z-index: 2;
267 +
347268 background: #f5f5f5;
348- margin-left: auto;
349- margin-right: auto;
350269 border: 1px solid #eee;
351270 border-radius: .2em;
352- z-index: 2;
353271 }
354272
355273 /* searchprompt */
356274
@@ -359,9 +277,10 @@
359277 width: 85%;
360278 height: 2em;
361279 margin-top: .3em;
362280 border-radius: 1em;
363- padding-left: .2em;
281 + padding-left: .7em;
282 + padding-right: 0.5em;
364283 margin-left: 1em;
365284 margin-right: 1em;
366285 }
367286
h.jsView
@@ -1,0 +1,4 @@
1 +const { h } = require('@mmckegg/mutant')
2 +
3 +module.exports = require('micro-css/h')(h)
4 +

Built with git-ssb-web