git ssb

16+

Dominic / patchbay



Commit 07678ab95020edf09925a491966765d79e086d0e

Merge branch 'master' into fulltext-search-update

mix irving committed on 2/5/2017, 2:05:26 AM
Parent: 70bc98e1fcb76213a7b487d8e8b7a8f49cc45943
Parent: 5840ace3e723ac68ca995db5a3598cb2ac264c83

Files changed

README.mdchanged
config.jschanged
embedded.jschanged
keyscroll.jschanged
modules_basic/compose.jschanged
modules_basic/feed.jschanged
modules_basic/follow.jschanged
modules_basic/index.jschanged
modules_basic/like.jschanged
modules_basic/markdown.jschanged
modules_basic/private.jschanged
modules_basic/public.jschanged
modules_basic/relationships.jschanged
modules_basic/setup.jschanged
modules_basic/thread.jschanged
modules_basic/avatar-edit.jsdeleted
modules_basic/avatar/avatar.jsadded
modules_basic/avatar/edit.jsadded
modules_basic/avatar/edit.mcssadded
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/profile.mcssadded
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/follow.mcssadded
modules_basic/index.test.jsadded
modules_basic/message-author.jsdeleted
modules_basic/message-author.mcssdeleted
modules_basic/markdown.mcssadded
modules_basic/message-backlinks.jsdeleted
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-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/scroller.jsadded
modules_basic/scroller.mcssadded
modules_basic/search-box.jsdeleted
modules_basic/suggest-mentions.jsdeleted
modules_core/app.jschanged
modules_core/external-confirm.jschanged
modules_core/index.jschanged
modules_core/menu.jschanged
modules_core/sbot.jschanged
modules_core/tabs.jschanged
modules_core/app.mcssadded
modules_core/external-confirm.mcssadded
modules_core/message-confirm.jsdeleted
modules_core/message-confirm.mcssdeleted
modules_core/index.test.jsadded
modules_core/style-mixins.jsdeleted
modules_core/names.jsadded
modules_core/styles.jsdeleted
modules_core/search-box.jsadded
modules_core/search-box.mcssadded
modules_core/style/hypertabs.jsadded
modules_core/style/hypertabs.mcssadded
modules_core/style/index.jsadded
modules_core/style/mixins.jsadded
modules_core/style/styles.jsadded
modules_core/suggest-box.cssadded
modules_core/suggest-box.jsadded
modules_core/suggest-mentions.jsadded
modules_embedded/index.jschanged
modules_extra/blob.jschanged
modules_extra/channel.jschanged
modules_extra/git-ssb.jschanged
modules_extra/git.jschanged
modules_extra/index.jschanged
modules_extra/key.jschanged
modules_extra/notifications.jschanged
modules_extra/raw.jschanged
modules_extra/search.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
modules_extra/search.mcssadded
package.jsonchanged
style.csschanged
README.mdView
@@ -30,9 +30,9 @@
3030 # restart sbot server (go back to previous tab and kill it)
3131 ```
3232 now clone and run patchbay.
3333 ```
34-git clone https://github.com/dominictarr/patchbay.git
34 +git clone https://github.com/ssbc/patchbay.git
3535 cd patchbay
3636 npm install
3737 npm run rebuild
3838 npm run bundle
config.jsView
@@ -23,10 +23,10 @@
2323 else
2424 blobsUrl = 'http://localhost:8989/blobs/get'
2525
2626 return {
27- remote: remote,
28- blobsUrl: blobsUrl
27 + remote,
28 + blobsUrl
2929 }
3030 }
3131
3232
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
keyscroll.jsView
@@ -23,10 +23,10 @@
2323 curMsgEl = el
2424 }
2525
2626 return function scroll(d) {
27- selectChild(!curMsgEl ? container.firstChild
27 + selectChild((!curMsgEl || d == 'first') ? container.firstChild
28 + : d < 0 ? curMsgEl.previousElementSibling || container.firstChild
2829 : d > 0 ? curMsgEl.nextElementSibling || container.lastChild
29- : d < 0 ? curMsgEl.previousElementSibling || container.firstChild
3030 : curMsgEl)
3131 }
3232 }
modules_basic/compose.jsView
@@ -1,13 +1,12 @@
11 'use strict'
22 const fs = require('fs')
33 const h = require('../h')
4-const suggest = require('suggest-box')
54 const mentions = require('ssb-mentions')
6-const cont = require('cont')
75
86 exports.needs = {
97 suggest_mentions: 'map', //<-- THIS MUST BE REWRITTEN
8 + build_suggest_box: 'first',
109 publish: 'first',
1110 message_content: 'first',
1211 message_confirm: 'first',
1312 file_input: 'first'
@@ -125,24 +124,17 @@
125124 var actions = h('section.actions', [
126125 fileInput, publishBtn
127126 ])
128127
128 + api.build_suggest_box(textArea, api.suggest_mentions)
129 +
129130 var composer = h('Compose', {
130131 className: opts.shrink === false ? '-expanded' : '-contracted'
131132 }, [
132133 textArea,
133134 actions
134135 ])
135136
136- suggest(textArea, (name, cb) => {
137- cont.para(api.suggest_mentions(name))
138- ((err, ary) => {
139- cb(null, ary.reduce((a, b) => {
140- if(!b) return a
141- return a.concat(b)
142- }, []))
143- })
144- }, {})
145137
146138 return composer
147139 }
148140
modules_basic/feed.jsView
@@ -4,8 +4,9 @@
44 var pull = require('pull-stream')
55 var u = require('../util')
66
77 exports.needs = {
8 + build_scroller: 'first',
89 sbot_user_feed: 'first',
910 message_render: 'first',
1011 avatar_profile: 'first',
1112 signifier: 'first'
@@ -18,43 +19,34 @@
1819
1920 return function (id) {
2021 //TODO: header of user info, avatars, names, follows.
2122
22- if(ref.isFeed(id)) {
23 + if(!ref.isFeed(id)) return
2324
24- var content = h('div.column.scroller__content')
25- var div = h('div.column.scroller',
26- {style: {'overflow':'auto'}},
27- h('div.scroller__wrapper',
28- h('div', api.avatar_profile(id)),
29- content
30- )
31- )
25 + const profile = h('div', api.avatar_profile(id))
26 + var { container, content } = api.build_scroller({ prepend: [profile, h('header', 'Activity')] })
27 +
28 + api.signifier(id, function (_, names) {
29 + if(names.length) container.title = names[0].name
30 + })
31 + container.id = id
3232
33- api.signifier(id, function (_, names) {
34- if(names.length) div.title = names[0].name
35- })
33 + pull(
34 + api.sbot_user_feed({id: id, old: false, live: true}),
35 + Scroller(container, content, api.message_render, true, false)
36 + )
3637
38 + //how to handle when have scrolled past the start???
3739
38- pull(
39- api.sbot_user_feed({id: id, old: false, live: true}),
40- Scroller(div, content, api.message_render, true, false)
41- )
40 + pull(
41 + u.next(api.sbot_user_feed, {
42 + id: id, reverse: true,
43 + limit: 50, live: false
44 + }, ['value', 'sequence']),
45 + // pull.through(console.log.bind(console)),
46 + Scroller(container, content, api.message_render, false, false)
47 + )
4248
43- //how to handle when have scrolled past the start???
44-
45- pull(
46- u.next(api.sbot_user_feed, {
47- id: id, reverse: true,
48- limit: 50, live: false
49- }, ['value', 'sequence']),
50- pull.through(console.log.bind(console)),
51- Scroller(div, content, api.message_render, false, false)
52- )
53-
54- return div
55-
56- }
49 + return container
5750 }
58-
5951 }
6052
modules_basic/follow.jsView
@@ -1,5 +1,6 @@
1-var h = require('hyperscript')
1 +const fs = require('fs')
2 +const h = require('../h')
23
34 //render a message when someone follows someone,
45 //so you see new users
56 function isRelated(value, name) {
@@ -17,74 +18,93 @@
1718 exports.gives = {
1819 message_content: true,
1920 message_content_mini: true,
2021 avatar_action: true,
22 + mcss: true
2123 }
2224
2325 exports.create = function (api) {
24- var exports = {}
25- exports.message_content =
26- exports.message_content_mini = function (msg) {
27- var content = msg.value.content
28- if(content.type == 'contact' && content.contact) {
29- var relation = isRelated(content.following, 'follows')
30- if(content.blocking) relation = 'blocks'
26 + return {
27 + message_content_mini,
28 + message_content,
29 + avatar_action,
30 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
31 + }
32 +
33 + function message_content_mini (msg) {
34 + const { type, contact, following, blocking } = msg.value.content
35 + if(type == 'contact' && contact) {
36 + var relation = isRelated(following, 'follows')
37 + if(blocking) relation = 'blocks'
3138 return [
32- relation, ' ',
33- api.avatar_link(content.contact, api.avatar_name(content.contact), '')
39 + relation,
40 + ' ',
41 + api.avatar_link(contact, api.avatar_name(contact), '')
3442 ]
3543 }
3644 }
3745
38- exports.message_content = function (msg) {
39-
40- var content = msg.value.content
41- if(content.type == 'contact' && content.contact) {
42- var relation = isRelated(content.following, 'follows')
43- if(content.blocking) relation = 'blocks'
44- return h('div.contact', relation, api.avatar(msg.value.content.contact, 'thumbnail'))
46 + function message_content (msg) {
47 + const { type, contact, following, blocking } = msg.value.content
48 + if(type == 'contact' && contact) {
49 + var relation = isRelated(following, 'follows')
50 + if(blocking) relation = 'blocks'
51 + return h('div.contact', [
52 + relation,
53 + api.avatar(contact, 'thumbnail')
54 + ])
4555 }
4656 }
4757
48- exports.avatar_action = function (id) {
58 + function avatar_action (id) {
4959 var follows_you, you_follow
5060
5161 var self_id = require('../keys').id
52- api.follower_of(self_id, id, function (err, f) {
53- you_follow = f
62 + api.follower_of(self_id, id, (err, f) => {
63 + you_follow = f || false
5464 update()
5565 })
56- api.follower_of(id, self_id, function (err, f) {
57- follows_you = f
66 + api.follower_of(id, self_id, (err, f) => {
67 + follows_you = f || false
5868 update()
5969 })
6070
61- var state = h('label')
62- var label = h('span')
71 + var followBtn = h('button', { 'ev-click': toggleFollow }, 'loading')
72 + var state = h('label', 'loading')
6373
6474 function update () {
6575 state.textContent = (
66- follows_you && you_follow ? 'friend'
67- : follows_you ? 'follows you'
68- : you_follow ? 'you follow'
76 + follows_you && you_follow ? '- you are friends'
77 + : follows_you ? '- they follow you'
78 + : you_follow ? '- you are following'
6979 : ''
7080 )
81 +
82 + // wait till finished loading before offering follow options
83 + if (you_follow === undefined) return
84 + followBtn.textContent = you_follow ? 'unfollow' : 'follow'
85 + }
7186
72- label.textContent = you_follow ? 'unfollow' : 'follow'
87 + return h('Follow', [
88 + followBtn,
89 + state
90 + ])
91 +
92 + function toggleFollow () {
93 + if (followBtn.textContent === 'loading') return
94 + const msg = {
95 + type: 'contact',
96 + contact: id,
97 + following: !you_follow
98 + }
99 +
100 + api.message_confirm(msg, (err, msg) => {
101 + if (err) return console.error(err)
102 +
103 + you_follow = !you_follow
104 + update()
105 + })
73106 }
74-
75- return h('div', state,
76- h('a', {href:'#', onclick: function () {
77- api.message_confirm({
78- type: 'contact',
79- contact: id,
80- following: !you_follow
81- }, function (err, msg) {
82- if (err) return console.error(err)
83- you_follow = msg.value.content.following
84- update()
85- })
86- }}, h('br'), label)
87- )
107 +
88108 }
89109 return exports
90110 }
modules_basic/index.jsView
@@ -1,33 +1,23 @@
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 + 'scroller': require('./scroller'),
19 + 'setup': require('./setup'),
20 + 'thread': require('./thread'),
21 + 'timestamp': require('./timestamp')
3222 }
3323
modules_basic/like.jsView
@@ -39,20 +39,22 @@
3939 ))
4040 votes.push({source: CACHE[k].author, dest: k, rel: 'vote'})
4141 }
4242
43- if(votes.length === 1)
44- digs.textContent = ' 1 Dig'
45- else if(votes.length > 1)
46- digs.textContent = ' ' + votes.length + ' Digs'
43 + if (votes.length === 0) return
4744
45 + const symbol = '\u2713' // tick 🗸
46 +
47 + digs.textContent = votes.length > 4
48 + ? votes.length + ' ' + symbol
49 + : Array(votes.length).fill(symbol).join('')
50 +
4851 pull(
49- pull.values(votes.map(vote => {
50- return api.avatar_name(vote.source)
51- })),
52- pull.collect(function (err, ary) {
53- digs.title = ary.map(x => x.innerHTML).join(" ")
54- })
52 + pull.values(votes.map(vote => api.avatar_name(vote.source))),
53 + pull.collect((err, ary) => {
54 + if (err) console.error(err)
55 + digs.title = 'Dug by:\n' + ary.map(x => x.innerHTML).join("\n")
56 + })
5557 )
5658
5759 return digs
5860 }
modules_basic/markdown.jsView
@@ -1,27 +1,26 @@
1-var markdown = require('ssb-markdown')
2-var h = require('hyperscript')
3-var ref = require('ssb-ref')
1 +const renderer = require('ssb-markdown')
2 +const fs = require('fs')
3 +const h = require('../h')
4 +const ref = require('ssb-ref')
45
56 exports.needs = {
67 blob_url: 'first',
78 emoji_url: 'first'
89 }
910
10-exports.gives = 'markdown'
11 +exports.gives = {
12 + markdown: true,
13 + mcss: true
14 +}
1115
1216 exports.create = function (api) {
13-
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">'
17 + return {
18 + markdown,
19 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
2120 }
2221
23- return function (content) {
22 + function markdown (content) {
2423 if('string' === typeof content)
2524 content = {text: content}
2625 //handle patchwork style mentions.
2726 var mentions = {}
@@ -29,18 +28,29 @@
2928 content.mentions.forEach(function (link) {
3029 if(link.name) mentions["@"+link.name] = link.link
3130 })
3231
33- var md = h('div.markdown')
34- md.innerHTML = markdown.block(content.text, {
32 + var md = h('Markdown')
33 + md.innerHTML = renderer.block(content.text, {
3534 emoji: renderEmoji,
36- toUrl: function (id) {
35 + toUrl: (id) => {
3736 if(ref.isBlob(id)) return api.blob_url(id)
3837 return '#'+(mentions[id]?mentions[id]:id)
39- }
38 + },
39 + imageLink: (id) => '#' + id
4040 })
4141
4242 return md
4343
4444 }
45 +
46 + function renderEmoji(emoji) {
47 + var url = api.emoji_url(emoji)
48 + if (!url) return ':' + emoji + ':'
49 + return '<img src="' + encodeURI(url) + '"'
50 + + ' alt=":' + escape(emoji) + ':"'
51 + + ' title=":' + escape(emoji) + ':"'
52 + + ' class="emoji">'
53 + }
54 +
4555 }
4656
modules_basic/private.jsView
@@ -1,16 +1,18 @@
11 'use strict'
2-var h = require('hyperscript')
3-var u = require('../util')
4-var pull = require('pull-stream')
5-var Scroller = require('pull-scroll')
6-var ref = require('ssb-ref')
2 +const fs = require('fs')
3 +const h = require('../h')
4 +const u = require('../util')
5 +const pull = require('pull-stream')
6 +const Scroller = require('pull-scroll')
7 +const ref = require('ssb-ref')
78
89 function map(ary, iter) {
910 if(Array.isArray(ary)) return ary.map(iter)
1011 }
1112
1213 exports.needs = {
14 + build_scroller: 'first',
1315 message_render: 'first',
1416 message_compose: 'first',
1517 message_unbox: 'first',
1618 sbot_log: 'first',
@@ -22,9 +24,10 @@
2224 exports.gives = {
2325 builtin_tabs: true,
2426 screen_view: true,
2527 message_meta: true,
26- message_content_mini: true
28 + message_content_mini: true,
29 + // mcss: true
2730 }
2831
2932 exports.create = function (api) {
3033
@@ -40,79 +43,90 @@
4043 )
4144 }
4245
4346 return {
44- builtin_tabs: function () {
45- return ['/private']
46- },
47 + builtin_tabs,
48 + screen_view,
49 + message_meta,
50 + message_content_mini,
51 + // mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
52 + }
4753
48- screen_view: function (path) {
49- if(path !== '/private') return
54 + function builtin_tabs () {
55 + return ['/private']
56 + }
5057
51- var div = h('div.column.scroller',
52- {style: {'overflow':'auto'}})
58 + function screen_view (path) {
59 + if(path !== '/private') return
5360
54- // if local id is different from sbot id, sbot won't have indexes of
55- // private threads
56- //TODO: put all private indexes client side.
57- var id = require('../keys').id
58- api.sbot_whoami(function (err, feed) {
59- if (err) return console.error(err)
60- if(id !== feed.id)
61- return div.appendChild(h('h4',
62- 'Private messages are not supported in the lite client.'))
61 + var composer = api.message_compose(
62 + {type: 'post', recps: [], private: true},
63 + {
64 + prepublish: function (msg) {
65 + msg.recps = [id].concat(msg.mentions).filter(function (e) {
66 + return ref.isFeed('string' === typeof e ? e : e.link)
67 + })
68 + if(!msg.recps.length)
69 + throw new Error('cannot make private message without recipients - just mention the user in an at reply in the message you send')
70 + return msg
71 + },
72 + placeholder: 'Write a private message'
73 + }
74 + )
75 + var { container, content } = api.build_scroller({ prepend: composer })
6376
64- var compose = api.message_compose(
65- {type: 'post', recps: [], private: true},
66- {
67- prepublish: function (msg) {
68- msg.recps = [id].concat(msg.mentions).filter(function (e) {
69- return ref.isFeed('string' === typeof e ? e : e.link)
70- })
71- if(!msg.recps.length)
72- throw new Error('cannot make private message without recipients - just mention the user in an at reply in the message you send')
73- return msg
74- },
75- placeholder: 'Write a private message'
76- }
77- )
77 + // if local id is different from sbot id, sbot won't have indexes of
78 + // private threads
79 + //TODO: put all private indexes client side.
80 + var id = require('../keys').id
81 + api.sbot_whoami(function (err, feed) {
82 + if (err) return console.error(err)
83 + if(id !== feed.id)
84 + return container.appendChild(h('h4',
85 + 'Private messages are not supported in the lite client.'))
7886
79- var content = h('div.column.scroller__content')
80- div.appendChild(h('div.scroller__wrapper', compose, content))
87 + pull(
88 + u.next(api.sbot_log, {old: false, limit: 100}),
89 + unbox(),
90 + Scroller(container, content, api.message_render, true, false)
91 + )
8192
82- pull(
83- u.next(api.sbot_log, {old: false, limit: 100}),
84- unbox(),
85- Scroller(div, content, api.message_render, true, false)
86- )
93 + pull(
94 + u.next(api.sbot_log, {reverse: true, limit: 1000}),
95 + unbox(),
96 + Scroller(container, content, api.message_render, false, false, function (err) {
97 + if(err) throw err
98 + })
99 + )
100 + })
87101
88- pull(
89- u.next(api.sbot_log, {reverse: true, limit: 1000}),
90- unbox(),
91- Scroller(div, content, api.message_render, false, false, function (err) {
92- if(err) throw err
93- })
94- )
95- })
102 + return container
103 + }
96104
97- return div
98- },
105 + function message_meta (msg) {
106 + if(!msg.value.content.recps && ! msg.value.private) return
99107
100- message_meta: function (msg) {
101- if(msg.value.content.recps || msg.value.private)
102- return h('span.row', 'PRIVATE', map(msg.value.content.recps, function (id) {
103- return api.avatar_image_link('string' == typeof id ? id : id.link, 'thumbnail')
104- }))
105- },
108 + return h('div', {
109 + style: {
110 + display: 'flex',
111 + 'align-items': 'center',
112 + color: 'gray'
113 + }
114 + }, [
115 + h('div', 'private: ['),
116 + map(msg.value.content.recps, id => (
117 + api.avatar_image_link('string' == typeof id ? id : id.link)
118 + )),
119 + h('div', ']'),
120 + ])
121 + }
106122
107- message_content_mini: function (msg, sbot) {
108- if (typeof msg.value.content === 'string') {
109- var icon = api.emoji_url('lock')
110- return icon
111- ? h('img', {className: 'emoji', src: icon})
112- : 'PRIVATE'
113- }
123 + function message_content_mini (msg, sbot) {
124 + if (typeof msg.value.content === 'string') {
125 + var icon = api.emoji_url('lock')
126 + return icon
127 + ? h('img', {className: 'emoji', src: icon})
128 + : 'PRIVATE'
114129 }
115130 }
116-
117131 }
118132
modules_basic/public.jsView
@@ -1,49 +1,50 @@
1-var h = require('hyperscript')
2-var u = require('../util')
3-var pull = require('pull-stream')
4-var Scroller = require('pull-scroll')
1 +const fs = require('fs')
2 +const h = require('hyperscript')
3 +const u = require('../util')
4 +const pull = require('pull-stream')
5 +const Scroller = require('pull-scroll')
56
67 exports.needs = {
8 + build_scroller: 'first',
79 message_render: 'first',
810 message_compose: 'first',
911 sbot_log: 'first',
1012 }
1113
1214 exports.gives = {
13- builtin_tabs: true, screen_view: true
15 + builtin_tabs: true,
16 + screen_view: true,
17 + // mcss: true
1418 }
1519
1620 exports.create = function (api) {
17-
1821 return {
19- builtin_tabs: function () {
20- return ['/public']
21- },
22 + builtin_tabs,
23 + screen_view,
24 + // mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
25 + }
2226
23- screen_view: function (path, sbot) {
24- if(path === '/public') {
27 + function builtin_tabs () {
28 + return ['/public']
29 + }
2530
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- api.message_compose({type: 'post'}, {placeholder: 'Write a public message'}),
31- content
32- )
33- )
31 + function screen_view (path, sbot) {
32 + if(path !== '/public') return
3433
35- pull(
36- u.next(api.sbot_log, {old: false, limit: 100}),
37- Scroller(div, content, api.message_render, true, false)
38- )
34 + const composer = api.message_compose({type: 'post'}, {placeholder: 'Write a public message'})
35 + var { container, content } = api.build_scroller({ prepend: composer })
3936
40- pull(
41- u.next(api.sbot_log, {reverse: true, limit: 100, live: false}),
42- Scroller(div, content, api.message_render, false, false)
43- )
37 + pull(
38 + u.next(api.sbot_log, {old: false, limit: 100}),
39 + Scroller(container, content, api.message_render, true, false)
40 + )
4441
45- return div
46- }
47- }
42 + pull(
43 + u.next(api.sbot_log, {reverse: true, limit: 100, live: false}),
44 + Scroller(container, content, api.message_render, false, false)
45 + )
46 +
47 + return container
4848 }
4949 }
50 +
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/setup.jsView
@@ -1,5 +1,4 @@
1-
21 var h = require('hyperscript')
32 var pull = require('pull-stream')
43
54 exports.needs = {
@@ -10,8 +9,15 @@
109 sbot_progress: 'first',
1110 sbot_query: 'first'
1211 }
1312
13 +exports.gives = {
14 + setup_is_fresh_install: true,
15 + progress_bar: true,
16 + setup_joined_network: true,
17 + screen_view: true
18 +}
19 +
1420 //maybe this could show the pubs, or
1521 //if someone locally follows you,
1622 //it could show the second degree pubs?
1723
@@ -28,13 +34,17 @@
2834 }}]
2935 }
3036
3137 exports.create = function (api) {
38 + return {
39 + setup_is_fresh_install,
40 + progress_bar,
41 + setup_joined_network,
42 + screen_view
43 + }
3244
33- var exports = {}
34-
3545 //test whether we are connected to the ssb network.
36- exports.setup_is_fresh_install = function (cb) {
46 + function setup_is_fresh_install (cb) {
3747 //test by checking whether you have any friends following you?
3848 pull(
3949 api.sbot_query({query: followers_query(id), limit: 1, live: false}),
4050 pull.collect(function (err, ary) {
@@ -78,9 +88,9 @@
7888
7989 return h('div.invite-form.row', input, accept)
8090 }
8191
82- exports.progress_bar = function () {
92 + function progress_bar () {
8393 var liquid = h('div.hyperprogress__liquid', '.')
8494 var bar = h('div.hyperprogress__bar', liquid)
8595 liquid.style.width = '0%'
8696
@@ -99,9 +109,9 @@
99109
100110 //when you join the network, I want this to show as people follow you.
101111 //that could be when a pub accepts the invite, or when a local peer accepts.
102112
103- exports.setup_joined_network = function (id) {
113 + function setup_joined_network (id) {
104114 var followers = h('div.column')
105115 var label = h('label', 'not connected to a network')
106116 var joined = h('div.setup__joined', label, followers)
107117
@@ -118,31 +128,32 @@
118128
119129 return joined
120130 }
121131
122- exports.screen_view = function (path) {
132 + function screen_view (path) {
123133 if(path !== '/setup') return
124134
125- var id = require('../keys').id
135 + var { id } = require('../keys')
126136
127137 //set up an avatar
128138
129139 var status = h('span')
130- return h('div.scroller', h('div.scroller__wrapper',
131- h('h1', 'welcome to patchbay!'),
132- h('div',
133- 'please choose avatar image and name',
134- api.avatar_edit(id)
135- ),
136- h('h2', 'join network'),
137- invite_form(),
138- //show avatars of anyone on the same local network.
139- //show realtime changes in your followers, especially for local.
140 + var invite = h('input', {placeholder: 'invite code'})
141 + return h('div.scroller', [
142 + h('div.scroller__wrapper', [
143 + h('h1', 'welcome to patchbay!'),
144 + h('div',
145 + 'please choose avatar image and name',
146 + api.avatar_edit(id)
147 + ),
148 + h('h2', 'join network'),
149 + invite_form(),
150 + //show avatars of anyone on the same local network.
151 + //show realtime changes in your followers, especially for local.
140152
141- exports.progress_bar(),
142- exports.setup_joined_network(require('../keys').id)
143- ))
153 + progress_bar(),
154 + setup_joined_network(id)
155 + ])
156 + ])
144157 }
158 +}
145159
146- return exports
147-
148-}
modules_basic/thread.jsView
@@ -18,8 +18,9 @@
1818 }
1919 }
2020
2121 exports.needs = {
22 + build_scroller: 'first',
2223 message_render: 'first',
2324 message_name: 'first',
2425 message_compose: 'first',
2526 message_unbox: 'first',
@@ -60,19 +61,13 @@
6061 root: id,
6162 branch: id //mutated when thread is loaded.
6263 }
6364
64- var content = h('div.column.scroller__content')
65- var div = h('div.column.scroller',
66- {style: {'overflow-y': 'auto'}},
67- h('div.scroller__wrapper',
68- content,
69- api.message_compose(meta, {shrink: false, placeholder: 'Write a reply'})
70- )
71- )
65 + var composer = api.message_compose(meta, {shrink: false, placeholder: 'Write a reply'})
66 + var { container, content } = api.build_scroller({ append: composer })
7267
7368 api.message_name(id, function (err, name) {
74- div.title = name
69 + container.title = name
7570 })
7671
7772 pull(
7873 api.sbot_links({
@@ -118,8 +113,8 @@
118113 })
119114 }
120115
121116 loadThread()
122- return div
117 + return container
123118 }
124119 }
125120 }
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,44 @@
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 + // ??? BUG
25 + return exports.avatar_image_name_link(author, classes)
26 + }
27 +
28 + function avatar_image_name_link (author, classes) {
29 + return api.avatar_link(author, [
30 + api.avatar_image(author, classes),
31 + api.avatar_name(author)
32 + ])
33 + }
34 +
35 + function avatar_image_link (author, classes) {
36 + return api.avatar_link(author, api.avatar_image(author, classes))
37 + }
38 +
39 + function avatar_name_link (author, classes) {
40 + return api.avatar_link(author, api.avatar_name(author))
41 + }
42 +}
43 +
44 +
modules_basic/avatar/edit.jsView
@@ -1,0 +1,209 @@
1 +'use strict'
2 +const fs = require('fs')
3 +const dataurl = require('dataurl-')
4 +const hyperfile = require('hyperfile')
5 +const hypercrop = require('hypercrop')
6 +const hyperlightbox = require('hyperlightbox')
7 +const h = require('../../h')
8 +const {
9 + Value, Array: MutantArray, Dict: MutantObject, Struct,
10 + map, computed, when, dictToCollection
11 +} = require('@mmckegg/mutant')
12 +const pull = require('pull-stream')
13 +const getAvatar = require('ssb-avatar')
14 +const ref = require('ssb-ref')
15 +const visualize = require('visualize-buffer')
16 +const self_id = require('../../keys').id
17 +
18 +function crop (d, cb) {
19 + var canvas = hypercrop(h('img', {src: d}))
20 +
21 + return h('AvatarEditor', [
22 + h('header', 'Click and drag to crop your avatar.'),
23 + canvas,
24 + //canvas.selection,
25 + h('section.actions', [
26 + h('button.cancel', {'ev-click': () => cb(new Error('canceled')) }, 'cancel'),
27 + h('button.okay', {'ev-click': () => cb(null, canvas.selection.toDataURL()) }, 'okay')
28 + ])
29 + ])
30 +}
31 +
32 +exports.needs = {
33 + message_confirm: 'first',
34 + sbot_blobs_add: 'first',
35 + blob_url: 'first',
36 + sbot_links: 'first',
37 + avatar_name: 'first'
38 +}
39 +
40 +exports.gives = {
41 + avatar_edit: true,
42 + mcss: true
43 +}
44 +
45 +exports.create = function (api) {
46 + return {
47 + avatar_edit,
48 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
49 + }
50 +
51 + function avatar_edit (id) {
52 +
53 + var avatar = Struct({
54 + original: Value(visualize(new Buffer(id.substring(1), 'base64'), 256).src),
55 + new: MutantObject()
56 + })
57 +
58 + getAvatar({links: api.sbot_links}, self_id, id, (err, _avatar) => {
59 + if (err) return console.error(err)
60 + //don't show user has already selected an avatar.
61 + if(ref.isBlob(_avatar.image))
62 + avatar.original.set(api.blob_url(_avatar.image))
63 + })
64 +
65 + var name = Struct({
66 + original: Value(api.avatar_name(id)),
67 + new: Value()
68 + })
69 +
70 + var images = MutantArray()
71 + pull(
72 + api.sbot_links({dest: id, rel: 'about', values: true}),
73 + pull.map(e => e.value.content.image),
74 + pull.filter(e => e && 'string' == typeof e.link),
75 + pull.unique('link'),
76 + pull.drain(image => images.push(image) )
77 + )
78 +
79 + var namesRecord = MutantObject()
80 + // TODO constrain query to one name per peer?
81 + pull(
82 + api.sbot_links({dest: id, rel: 'about', values: true}),
83 + pull.map(e => e.value.content.name),
84 + pull.filter(Boolean),
85 + pull.drain(name => {
86 + var n = namesRecord.get(name) || 0
87 + namesRecord.put(name, n+1)
88 + })
89 + )
90 + var names = dictToCollection(namesRecord)
91 +
92 + var lb = hyperlightbox()
93 +
94 + // TODO load this in, make this editable
95 + var description = ''
96 +
97 + var isPossibleUpdate = computed([name.new, avatar.new], (name, avatar) => {
98 + return name || avatar.link
99 + })
100 +
101 + var avatarSrc = computed([avatar], avatar => {
102 + if (avatar.new.link) return api.blob_url(avatar.new.link)
103 + else return avatar.original
104 + })
105 +
106 + var displayedName = computed([name], name => {
107 + if (name.new) return '@'+name.new
108 + else return name.original
109 + })
110 +
111 + return h('ProfileEdit', [
112 + h('section.lightbox', lb),
113 + h('section.avatar', [
114 + h('section', [
115 + h('img', { src: avatarSrc }),
116 + ]),
117 + h('footer', displayedName),
118 + ]),
119 + h('section.description', description),
120 + h('section.aliases', [
121 + h('header', 'Aliases'),
122 + h('section.avatars', [
123 + h('header', 'Avatars'),
124 + map(images, image => h('img', {
125 + 'src': api.blob_url(image),
126 + 'ev-click': () => avatar.new.set(image)
127 + })),
128 + h('div.file-upload', [
129 + hyperfile.asDataURL(dataUrlCallback)
130 + ])
131 + ]),
132 + h('section.names', [
133 + h('header', 'Names'),
134 + h('section', [
135 + map(names, n => h('div', { 'ev-click': () => name.new.set(n.key()) }, [
136 + h('div.name', n.key),
137 + h('div.count', n.value)
138 + ])),
139 + h('input', {
140 + placeholder: ' + another name',
141 + 'ev-keyup': e => name.new.set(e.target.value)
142 + })
143 + ])
144 + ]),
145 + when(isPossibleUpdate, h('section.action', [
146 + h('button.cancel', { 'ev-click': clearNewSelections }, 'cancel'),
147 + h('button.confirm', { 'ev-click': handleUpdateClick }, 'confirm changes')
148 + ]))
149 + ])
150 + ])
151 +
152 + function dataUrlCallback (data) {
153 + var el = crop(data, (err, data) => {
154 + if(data) {
155 + var _data = dataurl.parse(data)
156 + pull(
157 + pull.once(_data.data),
158 + api.sbot_blobs_add((err, hash) => {
159 + //TODO. Alerts are EVIL.
160 + //I use them only in a moment of weakness.
161 +
162 + if(err) return alert(err.stack)
163 + avatar.new.set({
164 + link: hash,
165 + size: _data.data.length,
166 + type: _data.mimetype,
167 + width: 512,
168 + height: 512
169 + })
170 + })
171 + )
172 + }
173 + lb.close()
174 + })
175 + lb.show(el)
176 + }
177 +
178 + function clearNewSelections () {
179 + name.new.set(null)
180 + avatar.new.set({})
181 + }
182 +
183 + function handleUpdateClick () {
184 + const newName = name.new()
185 + const newAvatar = avatar.new()
186 +
187 + const msg = {
188 + type: 'about',
189 + about: id
190 + }
191 +
192 + if (newName) msg.name = newName
193 + if (newAvatar.link) msg.image = newAvatar
194 +
195 + api.message_confirm(msg, (err, data) => {
196 + if (err) return console.error(err)
197 +
198 + if (newName) name.original.set('@'+newName)
199 + if (newAvatar.link) avatar.original.set(api.blob_url(newAvatar.link))
200 +
201 + clearNewSelections()
202 +
203 + // TODO - update aliases displayed
204 + })
205 + }
206 + }
207 +
208 +}
209 +
modules_basic/avatar/edit.mcssView
@@ -1,0 +1,182 @@
1 +ProfileEdit {
2 + display: flex
3 + flex-wrap: wrap
4 + justify-content: space-between
5 +
6 + margin-bottom: 2rem
7 +
8 + section.lightbox {
9 + position: absolute
10 + }
11 +
12 + section.avatar {
13 + margin-right: 1rem
14 +
15 + section img {
16 + width: 256px
17 + height: 256px
18 + }
19 +
20 + footer {
21 + font-size: 1.2rem
22 + }
23 + }
24 +
25 + section.description {
26 + flex-basis: 40%
27 + flex-grow: 1
28 +
29 + margin-top: 1rem
30 + }
31 +
32 + section.aliases {
33 + flex-basis: 100%
34 +
35 + margin-top: 1rem
36 +
37 + header {
38 + margin-bottom: .8rem
39 + border-bottom: 1px gainsboro solid
40 + }
41 +
42 + section {
43 + display: flex
44 + flex-wrap: wrap
45 + align-content: flex-start
46 +
47 + margin-bottom: 1rem
48 +
49 + header {
50 + flex-basis: 100%
51 +
52 + font-size: .9rem
53 + $textSubtle
54 +
55 + margin-bottom: .2rem
56 + }
57 +
58 + input {
59 + }
60 + }
61 +
62 + section.avatars {
63 + img {
64 + $avatar-large
65 + margin: 0 .15rem 0.2rem 0
66 +
67 + cursor: pointer
68 + }
69 +
70 + div.file-upload {
71 + position: relative
72 +
73 + input[type="file"] {
74 + $avatar-large
75 + color: transparent
76 +
77 + ::-webkit-file-upload-button {
78 + visibility: hidden
79 + }
80 +
81 + ::before {
82 + position: absolute
83 +
84 + background: #fff
85 + color: #666
86 + border: 1px solid #bbb
87 + border-radius: .2rem
88 + padding: .5rem
89 + cursor: pointer
90 +
91 + margin: 0
92 + padding: 10% 20%
93 + top: 12%
94 + left: 18%
95 +
96 + content: '+'
97 + font-size: 1.4rem
98 +
99 + outline: none
100 + white-space: nowrap
101 + -webkit-user-select: none
102 + }
103 +
104 + :active, :focus {
105 + outline: none
106 + box-shadow: none
107 + }
108 + }
109 + }
110 + }
111 +
112 + section.names {
113 + header {
114 + }
115 +
116 + section {
117 + display: flex
118 + flex-wrap: wrap
119 +
120 + div {
121 + display: flex
122 + cursor: pointer
123 +
124 + border: 1px gainsboro solid
125 + margin: 0 .4rem .5rem 0
126 +
127 + div { padding: .3rem }
128 +
129 + div.name {
130 + border-right: 1px gainsboro solid
131 + }
132 +
133 + div.count {
134 + font-size: .9rem
135 + background-color: #eeeeee
136 + }
137 + }
138 +
139 + input {
140 + border: 1px gainsboro solid
141 + font-size: 1rem
142 + height: 1.7rem
143 + }
144 + }
145 +
146 + }
147 +
148 + section.action {
149 + button.cancel {
150 + margin-left: 0
151 + }
152 +
153 + button.confirm {
154 + color: #fff
155 + $backgroundPrimary
156 + border: none
157 + }
158 + }
159 + }
160 +}
161 +
162 +AvatarEditor {
163 + header {
164 + font-weight: 600
165 + margin-bottom: .5rem
166 + }
167 +
168 + section.actions {
169 + display: flex
170 + justify-content: flex-end
171 +
172 + button.cancel {
173 +
174 + }
175 +
176 + button.okay {
177 + margin-right: 0
178 +
179 + }
180 + }
181 +}
182 +
modules_basic/avatar/image.jsView
@@ -1,0 +1,117 @@
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,
16 + avatar_image_src: true,
17 + avatar_image: true
18 +}
19 +
20 +var ready = false
21 +var waiting = []
22 +
23 +var last = 0
24 +
25 +var cache = {}
26 +
27 +exports.create = function (api) {
28 + var avatars = {}
29 +
30 + //blah blah
31 + return {
32 + connection_status: function (err) {
33 + if (err) return
34 + pull(
35 + api.sbot_query({
36 + query: [{
37 + $filter: {
38 + timestamp: {$gt: last || 0 },
39 + value: { content: {
40 + type: "about",
41 + about: {$prefix: "@"},
42 + image: {link: {$prefix: "&"}}
43 + }}
44 + }},
45 + {
46 + $map: {
47 + id: ["value", "content", "about"],
48 + image: ["value", "content", "image", "link"],
49 + by: ["value", "author"],
50 + ts: 'timestamp'
51 + }}],
52 + live: true
53 + }),
54 + pull.drain(function (a) {
55 + if(a.sync) {
56 + ready = true
57 + while(waiting.length) waiting.shift()()
58 + return
59 + }
60 + last = a.ts
61 + //set image for avatar.
62 + //overwrite another avatar
63 + //you picked.
64 + if(
65 + //if there is no avatar
66 + (!avatars[a.id]) ||
67 + //if i chose this avatar
68 + (a.by == self_id) ||
69 + //they chose their own avatar,
70 + //and current avatar was not chosen by me
71 + (a.by === a.id && avatars[a.id].by != self_id)
72 + )
73 + avatars[a.id] = a
74 +
75 + })
76 + )
77 + },
78 +
79 + avatar_image_src: function (author) {
80 + return ready && avatars[author]
81 + ? api.blob_url(avatars[author].image)
82 + : genSrc(author)
83 +
84 + function genSrc (id) {
85 + if(cache[id]) return cache[id]
86 + var { src } = visualize(new Buffer(author.substring(1), 'base64'), 256)
87 + cache[id] = src
88 + return src
89 + }
90 + },
91 +
92 + avatar_image: function (author, classes) {
93 + classes = classes || ''
94 + if(classes && 'string' === typeof classes) classes = '.avatar--'+classes
95 +
96 + function gen (id) {
97 + if(cache[id]) return h('img', {src: cache[id]})
98 + var img = visualize(new Buffer(author.substring(1), 'base64'), 256)
99 + cache[id] = img.src
100 + return img
101 + }
102 +
103 + var img = ready && avatars[author] ? h('img', {src: api.blob_url(avatars[author].image)}) : gen(author)
104 +
105 + ;(classes || '').split('.').filter(Boolean).forEach(function (c) {
106 + img.classList.add(c)
107 + })
108 +
109 + if(!ready)
110 + waiting.push(function () {
111 + if(avatars[author]) img.src = api.blob_url(avatars[author].image)
112 + })
113 +
114 + return img
115 + }
116 + }
117 +}
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,86 @@
1 +const fs = require('fs')
2 +const h = require('../../h')
3 +const pull = require('pull-stream')
4 +const { unique, drain } = pull
5 +const {
6 + Array: MutantArray,
7 + map, computed, when, dictToCollection
8 +} = require('@mmckegg/mutant')
9 +
10 +
11 +exports.needs = {
12 + avatar_image_link: 'first',
13 + avatar_action: 'map',
14 + avatar_edit: 'first',
15 + follows: 'first',
16 + followers: 'first'
17 +}
18 +
19 +exports.gives = {
20 + avatar_profile: true,
21 + mcss: true
22 +}
23 +
24 +exports.create = function (api) {
25 + return {
26 + avatar_profile,
27 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
28 + }
29 +
30 + function avatar_profile (id) {
31 +
32 + var rawFollows = MutantArray()
33 + var rawFollowers = MutantArray()
34 + var friends = computed([rawFollows, rawFollowers], (follows, followers) => {
35 + return follows.filter(follow => followers.includes(follow))
36 + })
37 +
38 + var follows = computed([rawFollows, friends], (follows, friends) => {
39 + return follows.filter(follow => !friends.includes(follow))
40 + })
41 + var followers = computed([rawFollowers, friends], (followers, friends) => {
42 + return followers.filter(follower => !friends.includes(follower))
43 + })
44 +
45 + pull(
46 + api.follows(id),
47 + unique(),
48 + drain(
49 + peer => rawFollows.push(peer),
50 + (err, data) => console.log('follows drain done', err, data)
51 + )
52 + )
53 + pull(
54 + api.followers(id),
55 + unique(),
56 + drain(
57 + peer => rawFollowers.push(peer),
58 + (err, data) => console.log('followers drain done', err, data)
59 + )
60 + )
61 +
62 + return h('Profile', [
63 + h('section.edit', api.avatar_edit(id)),
64 + h('section.relationships', [
65 + h('header', 'Relationships'),
66 + h('div.your-status', [
67 + h('header', 'Your status'),
68 + h('section.action', api.avatar_action(id))
69 + ]),
70 + h('div.friends', [
71 + h('header', 'Friends'),
72 + h('section', map(friends, id => api.avatar_image_link(id)))
73 + ]),
74 + h('div.follows', [
75 + h('header', 'Follows'),
76 + h('section', map(follows, id => api.avatar_image_link(id)))
77 + ]),
78 + h('div.followers', [
79 + h('header', 'Followers'),
80 + h('section', map(followers, id => api.avatar_image_link(id)))
81 + ])
82 + ])
83 + ])
84 + }
85 +
86 +}
modules_basic/avatar/profile.mcssView
@@ -1,0 +1,71 @@
1 +Profile {
2 +
3 + section.edit {
4 +
5 + }
6 +
7 + section.relationships {
8 + header {
9 + margin-bottom: .8rem
10 + border-bottom: 1px gainsboro solid
11 + }
12 +
13 + div {
14 + display: flex
15 + flex-wrap: wrap
16 + justify-content: space-between
17 + align-content: flex-start
18 +
19 + min-height: 5rem
20 + margin-bottom: 2rem
21 +
22 + header {
23 + flex-basis: 100%
24 +
25 + $textSubtle
26 + font-size: .9rem
27 +
28 + margin-bottom: .2rem
29 + }
30 +
31 + section a {
32 + margin-right: .2rem
33 +
34 + img { $avatar-small }
35 + }
36 + }
37 +
38 + div.your-status {
39 + margin: 0
40 + section.action {
41 + }
42 + }
43 +
44 + div.friends {
45 + section a {
46 + margin: 0 .2rem 0.2rem 0
47 +
48 + img {
49 + $avatar-large
50 + }
51 + }
52 + }
53 +
54 + div.follows {
55 + }
56 +
57 + div.followers {
58 + }
59 + }
60 +}
61 +
62 +$avatar-large {
63 + width: 56px
64 + height: 56px
65 +}
66 +
67 +$avatar-small {
68 + width: 32px
69 + height: 32px
70 +}
71 +
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/follow.mcssView
@@ -1,0 +1,11 @@
1 +Follow {
2 + button {
3 + width: 6rem
4 + margin: 0 .5rem 0 0
5 + }
6 +
7 + label {
8 + $textSubtle
9 + }
10 +}
11 +
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,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/markdown.mcssView
@@ -1,0 +1,14 @@
1 +Markdown {
2 +
3 + blockquote {
4 + margin: 1rem 0;
5 + padding-left: 1rem;
6 +
7 + border-left: 3px gainsboro solid
8 + }
9 +
10 + (ul) {
11 + padding-left: 1rem;
12 + }
13 +}
14 +
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/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,74 @@
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.okay', {
44 + 'ev-click': () => {
45 + lb.remove()
46 + api.publish(content, cb)
47 + }},
48 + 'okay'
49 + )
50 +
51 + var cancel = h('button.cancel', {
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', [
65 + h('h1', 'Preview')
66 + ]),
67 + h('section -message_preview', api.message_render(msg)),
68 + h('section -actions', [cancel, okay])
69 + ]))
70 +
71 + okay.focus()
72 + }
73 +}
74 +
modules_basic/message/confirm.mcssView
@@ -1,0 +1,40 @@
1 +MessageConfirm {
2 + section {
3 + -preview_description {
4 + }
5 +
6 + -message_preview {
7 + max-height: 70vh
8 + overflow-y: auto
9 +
10 + background-color: white
11 +
12 + div {
13 + header.author {
14 + div {
15 + section {
16 + -timestamp {
17 + display: none
18 + }
19 + }
20 + }
21 + }
22 +
23 + section.action {
24 + display: none
25 + }
26 + }
27 + }
28 +
29 + -actions {
30 + margin-top: 1rem
31 + display: flex
32 + justify-content: flex-end
33 +
34 + button {
35 + margin: 0 0 0 1rem
36 + }
37 + }
38 + }
39 +}
40 +
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,89 @@
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.raw-content'),
46 + h('section.action', api.message_action(msg)),
47 + h('footer.backlinks', api.message_backlinks(msg))
48 + ])
49 + return msgEl
50 +
51 + function navigateToMessageOnEnter (ev) {
52 + // on enter (or 'o'), hit first meta.
53 + if(ev.keyCode == 13 || ev.keyCode == 79) {
54 +
55 + // unless in an input
56 + if (ev.target.nodeName === 'INPUT'
57 + || ev.target.nodeName === 'TEXTAREA') return
58 +
59 + // HACK! (mw)
60 + // there's no exported api to open a new tab. :/
61 + // it's only done in `app.js` module in an`onhashchange` handler.
62 + // sooooooo yeah this shit for now :)
63 + var wtf = h('a', { href: `#${msg.key}` })
64 + msgEl.appendChild(wtf)
65 + wtf.click()
66 + msgEl.removeChild(wtf)
67 + }
68 + }
69 + }
70 +
71 + function mini(msg, el) {
72 + return h('Message -mini', {
73 + attributes: {
74 + tabindex: '0'
75 + }
76 + }, [
77 + h('header.author', api.message_author(msg, { size: 'mini' })),
78 + h('section.meta', api.message_meta(msg)),
79 + h('section.content', el),
80 + h('section.raw-content')
81 + ])
82 + }
83 +}
84 +
85 +
86 +function message_content_mini_fallback(msg) {
87 + return h('code', msg.value.content.type)
88 +}
89 +
modules_basic/message/render.mcssView
@@ -1,0 +1,107 @@
1 +Message {
2 + padding: 1rem .5rem 1rem 7.5rem
3 + min-height: 5rem
4 +
5 + position: relative
6 + display: flex
7 + flex-direction: row
8 + flex-wrap: wrap
9 + justify-content: flex-end
10 +
11 + header.author {
12 + position: absolute
13 + left: .5rem
14 + }
15 +
16 + section.title {
17 + flex-grow: 1
18 + font-size: .9rem
19 + }
20 +
21 + section.meta {
22 + display: flex
23 + align-items: center
24 + * {
25 + margin-left: .4rem
26 + }
27 +
28 + a {
29 + $textSubtle
30 + }
31 +
32 + /* this is for private message_meta, TODO find a better home */
33 + (img) {
34 + height: 1.8rem
35 + width: 1.8rem
36 + margin: 0 .2rem -.2rem
37 + }
38 + }
39 +
40 + section.content {
41 + flex-basis: 100%
42 +
43 + (img) {
44 + max-width: 100%
45 + }
46 + }
47 +
48 + section.raw-content {
49 + margin-left: -7rem
50 + flex-basis: 130%
51 + pre {
52 + border: 1px gainsboro solid
53 + padding: .8rem
54 +
55 + span {
56 + font-weight: 600
57 + }
58 + a {
59 + word-break: break-all
60 + }
61 + }
62 + }
63 +
64 + section.action {
65 + flex-basis: 100%
66 + display: flex
67 + justify-content: flex-end
68 +
69 + font-size: .9rem
70 + a {
71 + margin-left: .5em
72 + }
73 + }
74 +
75 + footer.backlinks {
76 + flex-basis: 100%
77 + }
78 +
79 +
80 + -mini {
81 + font-size: .9rem
82 + justify-content: flex-start
83 + padding: .25rem .5rem
84 + min-height: inherit
85 +
86 + header.author {
87 + order: 0
88 + position: initial
89 + left: initial
90 + }
91 +
92 + section.content {
93 + order: 1
94 + flex-basis: initial
95 + flex-grow: 1
96 + }
97 +
98 + section.meta {
99 + order: 2
100 + }
101 +
102 + section.raw-content {
103 + order: 3
104 + margin-left: 0
105 + }
106 + }
107 +}
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/scroller.jsView
@@ -1,0 +1,32 @@
1 +const fs = require('fs')
2 +const h = require('../h')
3 +
4 +exports.gives = {
5 + build_scroller: true,
6 + mcss: true
7 +}
8 +
9 +exports.create = function (api) {
10 + return {
11 + build_scroller,
12 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
13 + }
14 +
15 + function build_scroller ({ prepend = [], append = [] } = {}) {
16 + const content = h('section.content')
17 +
18 + const container = h('Scroller', { style: { overflow: 'auto' } }, [
19 + h('div.wrapper', [
20 + h('header', prepend),
21 + content,
22 + h('footer', append)
23 + ])
24 + ])
25 +
26 + return {
27 + content,
28 + container
29 + }
30 + }
31 +}
32 +
modules_basic/scroller.mcssView
@@ -1,0 +1,24 @@
1 +Scroller {
2 + display: flex
3 + flex-direction: column
4 +
5 + overflow: auto
6 + width: 100%
7 + height: 100%
8 + min-height: 0px
9 +
10 + div.wrapper {
11 + flex: 1
12 + width: 600px
13 + margin-left: auto
14 + margin-right: auto
15 +
16 + section.content {
17 +
18 + div {
19 + border-bottom: solid 1px gainsboro
20 + }
21 + }
22 + }
23 +}
24 +
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/app.jsView
@@ -1,46 +1,58 @@
1-var h = require('hyperscript')
2-var insertCss = require('insert-css')
1 +const fs = require('fs')
2 +const h = require('../h')
3 +const { Value } = require('@mmckegg/mutant')
4 +const insertCss = require('insert-css')
35
4-module.exports = {
5- needs: {
6- screen_view: 'first',
7- styles: 'first'
8- },
9- gives: 'app',
10- create: function (api) {
11- return function () {
12- process.nextTick(function () {
13- insertCss(api.styles())
14- })
6 +exports.needs = {
7 + screen_view: 'first',
8 + styles: 'first'
9 +}
1510
16- window.addEventListener('error', window.onError = function (e) {
17- document.body.appendChild(h('div.error',
18- h('h1', e.message),
19- h('big', h('code', e.filename + ':' + e.lineno)),
20- h('pre', e.error ? (e.error.stack || e.error.toString()) : e.toString())))
21- })
11 +exports.gives = {
12 + app: true,
13 + mcss: true
14 +}
2215
23- function hash() {
24- return window.location.hash.substring(1)
25- }
16 +exports.create = function (api) {
17 + return {
18 + app,
19 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
20 + }
2621
27- var view = api.screen_view(hash() || 'tabs')
22 + function app () {
23 + process.nextTick(() => insertCss(api.styles()))
2824
29- var screen = h('div.screen.column', view)
25 + var view = Value(getView())
26 + var screen = h('App', view)
3027
31- window.onhashchange = function (ev) {
32- var _view = view
33- view = api.screen_view(hash() || 'tabs')
28 + window.onhashchange = () => view.set(getView())
29 + document.body.appendChild(screen)
3430
35- if(_view) screen.replaceChild(view, _view)
36- else document.body.appendChild(view)
37- }
31 + window.addEventListener('error', window.onError = displayError)
3832
39- document.body.appendChild(screen)
33 + return screen
34 + }
4035
41- return screen
42- }
36 + function getView () {
37 + const view = window.location.hash.substring(1) || 'tabs'
38 + return api.screen_view(view)
4339 }
4440 }
4541
42 +function displayError (e) {
43 + document.body.querySelector('.\\.content').appendChild(
44 + h('div.page', [
45 + h('Error', { title: e.message }, [
46 + h('h1', e.message),
47 + h('big', [
48 + h('code', e.filename + ':' + e.lineno)
49 + ]),
50 + h('pre', e.error
51 + ? (e.error.stack || e.error.toString())
52 + : e.toString()
53 + )
54 + ])
55 + ])
56 + )
57 +}
4658
modules_core/external-confirm.jsView
@@ -1,44 +1,51 @@
1-var lightbox = require('hyperlightbox')
2-var h = require('hyperscript')
3-var open = require('open-external')
1 +const lightbox = require('hyperlightbox')
2 +const fs = require('fs')
3 +const h = require('../h')
4 +const open = require('open-external')
45
5-exports.gives = 'external_confirm'
6 +exports.gives = {
7 + external_confirm: true,
8 + mcss:true
9 +}
610
711 exports.create = function (api) {
8- return function (href) {
12 + return {
13 + external_confirm,
14 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
15 + }
16 +
17 + function external_confirm (href) {
918 var lb = lightbox()
1019 document.body.appendChild(lb)
1120
12- var okay = h('button', 'open', {onclick: function () {
13- lb.remove()
14- open(href)
15- }})
21 + var okay = h('button.okay', {
22 + 'ev-click': () => {
23 + lb.remove()
24 + open(href)
25 + }},
26 + 'open'
27 + )
1628
17- var cancel = h('button', 'Cancel', {onclick: function () {
18- lb.remove()
19- }})
29 + var cancel = h('button.cancel', {
30 + 'ev-click': () => {
31 + lb.remove()
32 + }},
33 + 'cancel'
34 + )
2035
2136 okay.addEventListener('keydown', function (ev) {
2237 if (ev.keyCode === 27) cancel.click() // escape
2338 })
2439
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- ])
40 + lb.show(h('ExternalConfirm', [
41 + h('header', 'External link'),
42 + h('section.prompt', [
43 + h('div.question', 'Open this link in your external browser?'),
44 + h('pre.link', href)
3545 ]),
36- h('div.row', [
37- okay,
38- cancel
39- ])
40- ))
46 + h('section.actions', [cancel, okay])
47 + ]))
4148
4249 okay.focus()
4350 }
4451 }
modules_core/index.jsView
@@ -1,15 +1,16 @@
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-box': require('./suggest-box'),
14 + 'suggest-mentions': require('./suggest-mentions')
1415 }
1516
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
@@ -60,8 +60,14 @@
6060 var opts = createConfig()
6161 var sbot = null
6262 var connection_status = []
6363
64 + var rec = {
65 + sync: () => {},
66 + async: () => {},
67 + source: () => {},
68 + }
69 +
6470 var rec = Reconnect(function (isConn) {
6571 function notify (value) {
6672 isConn(value); api.connection_status(value) //.forEach(function (fn) { fn(value) })
6773 }
modules_core/tabs.jsView
@@ -1,61 +1,63 @@
1-var Tabs = require('hypertabs')
2-var h = require('hyperscript')
3-var keyscroll = require('../keyscroll')
4-var open = require('open-external')
1 +const Tabs = require('hypertabs')
2 +const h = require('../h')
3 +const keyscroll = require('../keyscroll')
4 +const open = require('open-external')
5 +const { webFrame, remote } = require('electron')
56
67 function ancestor (el) {
78 if(!el) return
89 if(el.tagName !== 'A') return ancestor(el.parentElement)
910 return el
1011 }
1112
12-exports.needs = {screen_view: 'first', search_box: 'first', menu: 'first', 'external_confirm':'first'}
13 +exports.needs = {
14 + build_scroller: 'first',
15 + screen_view: 'first',
16 + search_box: 'first',
17 + menu: 'first',
18 + external_confirm:'first'
19 +}
1320
1421 exports.gives = 'screen_view'
1522
1623 exports.create = function (api) {
1724 return function (path) {
18- if(path !== 'tabs')
19- return
25 + if(path !== 'tabs') return
2026
2127 function setSelected (indexes) {
22- var ids = indexes.map(function (index) {
23- return tabs.get(index).id
24- })
28 + const ids = indexes.map(index => tabs.get(index).content.id)
2529 if(search)
2630 if(ids.length > 1)
27- search.value = 'split('+ids.join(',')+')'
31 + search.input.value = 'split('+ids.join(',')+')'
2832 else
29- search.value = ids[0]
33 + search.input.value = ids[0]
3034 }
3135
32- var search
33- var tabs = Tabs(setSelected)
34-
35- search = api.search_box(function (path, change) {
36-
36 + const tabs = Tabs(setSelected)
37 + const search = api.search_box((path, change) => {
3738 if(tabs.has(path)) {
3839 tabs.select(path)
3940 return true
4041 }
41- var el = api.screen_view(path)
4242
43- if(el) {
44- if(!el.title) el.title = path
45- el.scroll = keyscroll(el.querySelector('.scroller__content'))
46- tabs.add(el, change)
47- // localStorage.openTabs = JSON.stringify(tabs.tabs)
48- return change
49- }
43 + const el = api.screen_view(path)
44 + if (!el) return
45 +
46 + if(!el.title) el.title = path
47 + el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
48 + tabs.add(el, change)
49 +// localStorage.openTabs = JSON.stringify(tabs.tabs)
50 + return change
5051 })
5152
52- //reposition hypertabs menu to inside a container...
53- tabs.insertBefore(h('div.header.row',
54- h('div.header__tabs.row', tabs.firstChild), //tabs
55- h('div.header__search.row.end', h('div', search), api.menu())
56- ), tabs.firstChild)
57- // tabs.insertBefore(search, tabs.firstChild.nextSibling)
53 + // TODO add options to Tabs : e.g. Tabs(setSelected, { append: el })
54 + tabs.firstChild.appendChild(
55 + h('div.extra', [
56 + search,
57 + api.menu()
58 + ])
59 + )
5860
5961 var saved = []
6062 // try { saved = JSON.parse(localStorage.openTabs) }
6163 // catch (_) { }
@@ -67,13 +69,14 @@
6769 var el = api.screen_view(path)
6870 if(!el) return
6971 el.id = el.id || path
7072 if (!el) return
71- el.scroll = keyscroll(el.querySelector('.scroller__content'))
73 + el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
7274 if(el) tabs.add(el, false, false)
7375 })
7476
7577 tabs.select(0)
78 + search.input.value = null // start with an empty field to show placeholder
7679
7780 //handle link clicks
7881 window.onclick = function (ev) {
7982 var link = ancestor(ev.target)
@@ -95,32 +98,42 @@
9598
9699 var el = api.screen_view(path)
97100 if(el) {
98101 el.id = el.id || path
99- el.scroll = keyscroll(el.querySelector('.scroller__content'))
102 + el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
100103 tabs.add(el, !ev.ctrlKey, !!ev.shiftKey)
101104 // localStorage.openTabs = JSON.stringify(tabs.tabs)
102105 }
103106
104107 return false
105108 }
106109
110 + var gPressed = false
107111 window.addEventListener('keydown', function (ev) {
108112 if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA')
109113 return
114 +
115 + // scroll to top
116 + if (ev.keyCode == 71) { // g
117 + if (!gPressed) return gPressed = true
118 + var el = tabs.get(tabs.selected[0]).firstChild.scroll('first')
119 + gPressed = false
120 + } else {
121 + gPressed = false
122 + }
123 +
110124 switch(ev.keyCode) {
111-
112125 // scroll through tabs
113126 case 72: // h
114127 return tabs.selectRelative(-1)
115128 case 76: // l
116129 return tabs.selectRelative(1)
117130
118131 // scroll through messages
119132 case 74: // j
120- return tabs.get(tabs.selected[0]).scroll(1)
133 + return tabs.get(tabs.selected[0]).firstChild.scroll(1)
121134 case 75: // k
122- return tabs.get(tabs.selected[0]).scroll(-1)
135 + return tabs.get(tabs.selected[0]).firstChild.scroll(-1)
123136
124137 // close a tab
125138 case 88: // x
126139 if (tabs.selected) {
@@ -159,47 +172,53 @@
159172 }
160173 })
161174
162175 // errors tab
163- var errorsContent = h('div.column.scroller__content')
164- var errors = h('div.column.scroller', {
165- id: 'errors',
166- style: {'overflow':'auto'}
167- }, h('div.scroller__wrapper',
168- errorsContent
169- )
170- )
176 + var {
177 + container: errors,
178 + content: errorsContent
179 + } = api.build_scroller()
171180
172181 // remove loader error handler
173182 if (window.onError) {
174183 window.removeEventListener('error', window.onError)
175184 delete window.onError
176185 }
177186
178187 // put errors in a tab
179- window.addEventListener('error', function (ev) {
180- var err = ev.error || ev
188 + window.addEventListener('error', ev => {
189 + const err = ev.error || ev
181190 if(!tabs.has('errors'))
182191 tabs.add(errors, false)
183- var el = h('div.message',
192 + const el = h('div.message', [
184193 h('strong', err.message),
185- h('pre', err.stack))
194 + h('pre', err.stack)
195 + ])
186196 if (errorsContent.firstChild)
187197 errorsContent.insertBefore(el, errorsContent.firstChild)
188198 else
189199 errorsContent.appendChild(el)
190200 })
191201
192202 if (process.versions.electron) {
193- window.addEventListener('contextmenu', function (ev) {
203 +
204 + window.addEventListener('mousewheel', ev => {
205 + const { ctrlKey, deltaY } = ev
206 + if (ctrlKey) {
207 + const direction = (deltaY / Math.abs(deltaY))
208 + const currentZoom = webFrame.getZoomLevel()
209 + webFrame.setZoomLevel(currentZoom - direction)
210 + }
211 + })
212 +
213 + window.addEventListener('contextmenu', ev => {
194214 ev.preventDefault()
195- var remote = require('electron').remote
196- var Menu = remote.Menu
197- var MenuItem = remote.MenuItem
198- var menu = new Menu()
215 + const Menu = remote.Menu
216 + const MenuItem = remote.MenuItem
217 + const menu = new Menu()
199218 menu.append(new MenuItem({
200219 label: 'Inspect Element',
201- click: function () {
220 + click: () => {
202221 remote.getCurrentWindow().inspectElement(ev.x, ev.y)
203222 }
204223 }))
205224 menu.popup(remote.getCurrentWindow())
modules_core/app.mcssView
@@ -1,0 +1,29 @@
1 +App {
2 + position: absolute
3 +
4 + top: 0
5 + bottom: 0
6 + left: 0
7 + right: 0
8 + overflow: hidden
9 + min-height: 0px
10 +
11 +}
12 +
13 +
14 +Error {
15 + padding: 1rem
16 +
17 + h1 {
18 + color: red
19 + }
20 +
21 + big {
22 + }
23 +
24 + pre {
25 + padding: 1rem
26 + border: 1px gainsboro solid
27 + }
28 +}
29 +
modules_core/external-confirm.mcssView
@@ -1,0 +1,34 @@
1 +ExternalConfirm {
2 + header {
3 + font-weight: 600
4 + margin-bottom: .5rem
5 + }
6 +
7 + section.prompt {
8 + background: #fff
9 + padding: 1rem
10 + margin-bottom: 1rem
11 +
12 + div.question{
13 +
14 + }
15 +
16 + pre.link {
17 +
18 + }
19 + }
20 +
21 + section.actions {
22 + display: flex
23 + justify-content: flex-end
24 +
25 + button.cancel {
26 +
27 + }
28 + button.okay {
29 + margin-right: 0
30 +
31 + }
32 + }
33 +}
34 +
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/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/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/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/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/search-box.jsView
@@ -1,0 +1,64 @@
1 +'use strict'
2 +const h = require('../h')
3 +const fs = require('fs')
4 +
5 +
6 +exports.needs = {
7 + suggest_search: 'map', //REWRITE
8 + build_suggest_box: 'first'
9 +}
10 +
11 +exports.gives = {
12 + search_box: true,
13 + mcss: true
14 +}
15 +
16 +exports.create = function (api) {
17 +
18 + return {
19 + search_box,
20 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
21 + }
22 +
23 + function search_box (go) {
24 + const input = h('input', {
25 + type: 'search',
26 + placeholder: 'Commands',
27 + 'ev-keyup': ev => {
28 + switch (ev.keyCode) {
29 + case 13: // enter
30 + if (go(input.value.trim(), !ev.ctrlKey))
31 + input.blur()
32 + return
33 + case 27: // escape
34 + ev.preventDefault()
35 + input.blur()
36 + return
37 + }
38 + }
39 + })
40 + input.addEventListener('suggestselect', ev => {
41 + if (go(input.value.trim(), !ev.ctrlKey))
42 + input.blur()
43 + })
44 + const search = h('Search', input)
45 +
46 + search.input = input
47 + search.activate = (sigil, ev) => {
48 + input.focus()
49 + ev.preventDefault()
50 + if (input.value[0] === sigil) {
51 + input.selectionStart = 1
52 + input.selectionEnd = input.value.length
53 + } else {
54 + input.value = sigil
55 + }
56 + }
57 +
58 + const suggestBox = api.build_suggest_box(input, api.suggest_search)
59 +
60 + return search
61 + }
62 +
63 +}
64 +
modules_core/search-box.mcssView
@@ -1,0 +1,13 @@
1 +Search {
2 + input {
3 + height: 1.4rem
4 + border-radius: .1rem
5 +
6 + padding-left: .7rem
7 + padding-right: .5rem
8 +
9 + margin-left: 1em
10 + /* margin-right: 1em */
11 + }
12 +}
13 +
modules_core/style/hypertabs.jsView
@@ -1,0 +1,13 @@
1 +const fs = require('fs')
2 +
3 +module.exports = {
4 + gives: {
5 + mcss: true
6 + },
7 + create: function (api) {
8 + return {
9 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
10 + }
11 + }
12 +}
13 +
modules_core/style/hypertabs.mcssView
@@ -1,0 +1,93 @@
1 +Hypertabs {
2 + display: flex
3 + flex-direction: column
4 +
5 + height: 100% /* needed to stop scroller blowing out */
6 +
7 + nav {
8 + display: flex
9 +
10 + background: linear-gradient(to bottom, #efefef, #e5e5e5)
11 +
12 + section.tabs {
13 + flex-grow: 1
14 + display: flex
15 + min-width: 0
16 +
17 + div.tab {
18 + flex-grow: 1
19 +
20 + display: flex
21 + align-items: center
22 + justify-content: space-between
23 +
24 + font-size: .9rem
25 + background-color: #efefef
26 + overflow-x: hidden
27 +
28 + padding: 0 .4rem
29 + margin-left: .6rem
30 + border: 1px gainsboro solid
31 + border-bottom: none
32 +
33 + -selected {
34 + color: #222
35 + background-color: #fff
36 +
37 + a.close {
38 + visibility: visible
39 + }
40 + }
41 +
42 + -notify {
43 + background-color: orange;
44 + }
45 +
46 +
47 + a {
48 + color: #666
49 +
50 + :hover {
51 + color: #0088cc
52 + text-decoration: none
53 + }
54 + }
55 +
56 + a.link {
57 + flex-grow: 1
58 + flex-shrink: 0
59 + overflow-x: hidden
60 + min-width: 0
61 + max-width: 90%
62 + white-space: nowrap
63 + text-overflow: ellipsis
64 + }
65 +
66 + a.close {
67 + visibility: hidden
68 + }
69 + }
70 +
71 + }
72 +
73 + div.extra {
74 + display: flex
75 + align-items: center
76 + }
77 + }
78 +
79 + section.content {
80 + display: flex
81 +
82 + height: 100% /* needed to stop making nav weird */
83 +
84 + div.page {
85 + flex-grow: 1
86 +
87 + display: flex /*hack to get give Scroller context it needs */
88 +
89 + padding-top: .2rem
90 + }
91 + }
92 +}
93 +
modules_core/style/index.jsView
@@ -1,0 +1,6 @@
1 +module.exports = {
2 + 'hypertabs': require('./hypertabs'),
3 + 'mixins': require('./mixins'),
4 + 'styles': require('./styles')
5 +}
6 +
modules_core/style/mixins.jsView
@@ -1,0 +1,26 @@
1 +
2 +const mixins = `
3 + $textPrimary {
4 + color: #222
5 + }
6 +
7 + $textSubtle {
8 + color: gray
9 + }
10 +
11 + $backgroundPrimary {
12 + background-color: #50aadf
13 + }
14 +`
15 +
16 +module.exports = {
17 + gives: {
18 + mcss: true
19 + },
20 + create: function (api) {
21 + return {
22 + mcss: () => mixins
23 + }
24 + }
25 +}
26 +
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-box.cssView
@@ -1,0 +1,71 @@
1 +/*
2 +NOTE the suggest-box module appends the suggestion div to the end of the body,
3 +so wrapping it in a module is impossible.
4 +
5 +Writing mcss for this is hard, so I've just written tight css selectors
6 +
7 +Schema:
8 +
9 + body div.suggest-box ul {
10 + li {
11 + img {}
12 + strong {}
13 + small {}
14 + }
15 +
16 + li.selected {
17 + img {}
18 + strong {}
19 + small {}
20 + }
21 + }
22 +*/
23 +
24 +
25 +body > .suggest-box {
26 + width: max-content;
27 + background-color: #fff;
28 + border: 1px gainsboro solid;
29 +
30 + padding: .2rem .5rem;
31 + margin-top: .35rem;
32 +}
33 +
34 +body > .suggest-box > ul {
35 + list-style-type: none;
36 + padding: 0;
37 +}
38 +
39 +body > .suggest-box > ul > li {
40 + display: flex;
41 + align-items: center;
42 +
43 + padding-right: .2rem;
44 + margin-bottom: .2rem;
45 +}
46 +
47 +body > .suggest-box > ul > li.selected {
48 + color: #fff;
49 + background: #0caaf9;
50 +}
51 +
52 +body > .suggest-box > ul > li > img {
53 + height: 36px;
54 + width: 36px;
55 + padding: .2rem;
56 + /* TODO make smaller emoji thumbnails */
57 +}
58 +
59 +body > .suggest-box > ul > li > strong {
60 + flex-grow: 1;
61 + margin-left: .5rem;
62 + font-weight: 300;
63 +}
64 +
65 +body > .suggest-box > ul > li > small {
66 + font-family: monospace;
67 + margin-left: .5rem;
68 + padding-right: .2rem;
69 + font-size: 1rem;
70 +}
71 +
modules_core/suggest-box.jsView
@@ -1,0 +1,43 @@
1 +const fs = require('fs')
2 +const h = require('../h')
3 +const onload = require('on-load')
4 +const { para } = require('cont')
5 +const Suggest = require('suggest-box')
6 +
7 +exports.needs = {}
8 +
9 +exports.gives = {
10 + build_suggest_box: true,
11 + css: true
12 +}
13 +
14 +exports.create = function (api) {
15 + return {
16 + build_suggest_box,
17 + css: () => fs.readFileSync(__filename.replace(/js$/, 'css'), 'utf8') // NOTE css
18 + }
19 +
20 + function build_suggest_box (inputNode, asyncSuggesters, opts = {}) {
21 + function suggester (inputText, cb) {
22 + para(asyncSuggesters(inputText))
23 + ((err, ary) => {
24 + if(err) return cb(err)
25 +
26 + var suggestions = ary.filter(Boolean).reduce((a, b) => a.concat(b), [])
27 + cb(null, suggestions)
28 + })
29 + }
30 +
31 + var suggestBox
32 + onload(inputNode, (el) => {
33 + suggestBox = Suggest(el, suggester, opts)
34 + })
35 +
36 + // HACK (mix) : onload is needed because Suggest demands a parent node.
37 + // I've chosen this over forcing users to pass a callback
38 + return {
39 + complete: () => suggestBox.complete()
40 + }
41 + }
42 +}
43 +
modules_core/suggest-mentions.jsView
@@ -1,0 +1,91 @@
1 +
2 +exports.needs = {
3 + sbot_links2: 'first',
4 + blob_url: 'first',
5 + signified: 'first',
6 + builtin_tabs: 'map',
7 + avatar_image_src: 'first'
8 +}
9 +
10 +exports.gives = {
11 + suggest_mentions: true,
12 + suggest_search: true,
13 + builtin_tabs: true
14 +}
15 +
16 +exports.create = function (api) {
17 +
18 + return {
19 + suggest_mentions,
20 + suggest_search,
21 + builtin_tabs: () => null
22 + }
23 +
24 + function suggest_mentions (word) {
25 + return function (cb) {
26 + if(!/^[%&@]\w/.test(word)) return cb()
27 +
28 + api.signified(word, (err, names) => {
29 + if(err) return cb(err)
30 +
31 + cb(null, names.map(e => {
32 + const { name, rank, id } = e
33 + return {
34 + title: name,
35 + subtitle: `(${rank}) ${id.substring(0,10)}`,
36 + value: '['+name+']('+id+')',
37 + rank,
38 + image: api.avatar_image_src(id),
39 + showBoth: true
40 + }
41 + }))
42 + })
43 + }
44 + }
45 +
46 + function suggest_search (query) {
47 + return function (cb) {
48 + if(/^@\w/.test(query)) {
49 + api.signified(query, (err, names) => {
50 + if(err) return cb(err)
51 +
52 + cb(null, names.map(e => {
53 + const { name, rank, id } = e
54 + return {
55 + title: name,
56 + subtitle: `(${rank}) ${id.substring(0,10)}`,
57 + value: id,
58 + rank,
59 + image: api.avatar_image_src(id),
60 + showBoth: true
61 + }
62 + }))
63 + })
64 + } else if (/^%\w/.test(query)) {
65 + api.signified(query, (err, names) => {
66 + if(err) return cb(err)
67 +
68 + cb(null, names.map(e => {
69 + const { name, rank, id } = e
70 + return {
71 + title: name,
72 + subtitle: `(${rank}) ${id.substring(0,10)}`,
73 + value: id,
74 + rank
75 + }
76 + }))
77 + })
78 + } else if(/^\//.test(query)) {
79 + var tabs = [].concat.apply([], api.builtin_tabs())
80 + cb(null, tabs.filter(function (name) {
81 + return name && name.substr(0, query.length) === query
82 + }).map(function (name) {
83 + return {
84 + title: name,
85 + value: name,
86 + }
87 + }))
88 + } else cb()
89 + }
90 + }
91 +}
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/blob.jsView
@@ -19,9 +19,9 @@
1919
2020 function screen_view (path) {
2121 if(!ref.isBlob(path)) return
2222
23- return h('Blob', [
23 + return h('Blob', { id: path, title: path.slice(0, 9) + '...' }, [
2424 h('iframe', {
2525 src: api.blob_url(path),
2626 sandbox: ''
2727 })
modules_extra/channel.jsView
@@ -3,17 +3,21 @@
33 var Scroller = require('pull-scroll')
44 var mfr = require('map-filter-reduce')
55
66 exports.needs = {
7 + build_scroller: 'first',
78 message_render: 'first',
89 message_compose: 'first',
910 sbot_log: 'first',
1011 sbot_query: 'first',
1112 }
1213
1314 exports.gives = {
14- message_meta: true, screen_view: true,
15- connection_status: true, suggest_search: true
15 + message_meta: true,
16 + screen_view: true,
17 + connection_status: true,
18 + suggest_search: true,
19 + suggest_mentions: true
1620 }
1721
1822 exports.create = function (api) {
1923
@@ -26,91 +30,114 @@
2630 rank: {$count: true}
2731 }}
2832
2933 return {
30- message_meta: function (msg) {
31- var chan = msg.value.content.channel
32- if (chan)
33- return h('a', {href: '##'+chan}, '#'+chan)
34- },
35- screen_view: function (path) {
36- if(path[0] === '#') {
37- var channel = path.substr(1)
34 + message_meta,
35 + screen_view,
36 + connection_status,
37 + suggest_search,
38 + suggest_mentions
39 + }
3840
39- var content = h('div.column.scroller__content')
40- var div = h('div.column.scroller',
41- {style: {'overflow':'auto'}},
42- h('div.scroller__wrapper',
43- api.message_compose({type: 'post', channel: channel}),
44- content
45- )
46- )
41 + function message_meta (msg) {
42 + var chan = msg.value.content.channel
43 + if (chan)
44 + return h('a', {
45 + href: '##'+chan,
46 + style: { order: 98 },
47 + }, '#'+chan)
48 + }
4749
48- function matchesChannel(msg) {
49- if (msg.sync) console.error('SYNC', msg)
50- var c = msg && msg.value && msg.value.content
51- return c && c.channel === channel
52- }
50 + function screen_view (path) {
51 + if(path[0] === '#') {
52 + var channel = path.substr(1)
5353
54- pull(
55- api.sbot_log({old: false}),
56- pull.filter(matchesChannel),
57- Scroller(div, content, api.message_render, true, false)
58- )
54 + var composer = api.message_compose({type: 'post', channel: channel})
55 + var { container, content } = api.build_scroller({ prepend: composer })
5956
60- pull(
61- api.sbot_query({reverse: true, query: [
62- {$filter: {value: {content: {channel: channel}}}}
63- ]}),
64- Scroller(div, content, api.message_render, false, false)
65- )
66-
67- return div
57 + function matchesChannel(msg) {
58 + if (msg.sync) console.error('SYNC', msg)
59 + var c = msg && msg.value && msg.value.content
60 + return c && c.channel === channel
6861 }
69- },
7062
71- connection_status: function (err) {
72- if(err) return
73-
74- channels = []
75-
7663 pull(
77- api.sbot_query({query: [filter, map, reduce]}),
78- pull.collect(function (err, chans) {
79- if (err) return console.error(err)
80- channels = chans.concat(channels)
81- })
64 + api.sbot_log({old: false}),
65 + pull.filter(matchesChannel),
66 + Scroller(container, content, api.message_render, true, false)
8267 )
8368
8469 pull(
85- api.sbot_log({old: false}),
86- mfr.filter(filter),
87- mfr.map(map),
88- pull.drain(function (chan) {
89- var c = channels.find(function (e) {
90- return e.name === chan.name
91- })
92- if (c) c.rank++
93- else channels.push(chan)
94- })
70 + api.sbot_query({reverse: true, query: [
71 + {$filter: {value: {content: {channel: channel}}}}
72 + ]}),
73 + Scroller(container, content, api.message_render, false, false)
9574 )
96- },
9775
98- suggest_search: function (query) {
99- return function (cb) {
100- if(!/^#\w/.test(query)) return cb()
101- cb(null, channels.filter(function (chan) {
102- return ('#'+chan.name).substring(0, query.length) === query
76 + return container
77 + }
78 + }
79 +
80 + function connection_status (err) {
81 + if(err) return
82 +
83 + channels = []
84 +
85 + pull(
86 + api.sbot_query({query: [filter, map, reduce]}),
87 + pull.collect(function (err, chans) {
88 + if (err) return console.error(err)
89 + channels = chans.concat(channels)
90 + })
91 + )
92 +
93 + pull(
94 + api.sbot_log({old: false}),
95 + mfr.filter(filter),
96 + mfr.map(map),
97 + pull.drain(function (chan) {
98 + var c = channels.find(function (e) {
99 + return e.name === chan.name
103100 })
104- .map(function (chan) {
105- var name = '#'+chan.name
106- return {
107- title: name,
108- value: name,
109- subtitle: chan.rank
110- }
111- }))
112- }
101 + if (c) c.rank++
102 + else channels.push(chan)
103 + })
104 + )
105 + }
106 +
107 + function suggest_search (query) {
108 + return function (cb) {
109 + if(!/^#\w/.test(query)) return cb()
110 +
111 + cb(null, channels.filter(function (chan) {
112 + return ('#'+chan.name).substring(0, query.length) === query
113 + })
114 + .map(function (chan) {
115 + var name = '#'+chan.name
116 + return {
117 + title: name,
118 + subtitle: '(' + chan.rank + ')',
119 + value: name
120 + }
121 + }))
113122 }
114123 }
124 +
125 + function suggest_mentions (query) {
126 + return function (cb) {
127 + if(!/^#\w/.test(query)) return cb()
128 +
129 + cb(null, channels.filter(function (chan) {
130 + return ('#'+chan.name).substring(0, query.length) === query
131 + })
132 + .map(function (chan) {
133 + var name = '#'+chan.name
134 + return {
135 + title: name,
136 + subtitle: '(' + chan.rank + ')',
137 + value: '['+name+']('+name+')'
138 + }
139 + }))
140 + }
141 + }
115142 }
116143
modules_extra/git-ssb.jsView
@@ -3,15 +3,17 @@
33 var pull = require('pull-stream')
44 var Scroller = require('pull-scroll')
55
66 exports.needs = {
7 + build_scroller: 'first',
78 message_render: 'first',
89 message_compose: 'first',
910 sbot_log: 'first'
1011 }
1112
1213 exports.gives = {
13- menu_items: true, screen_view: true
14 + menu_items: true,
15 + screen_view: true
1416 }
1517
1618 exports.create = function (api) {
1719 return {
@@ -21,29 +23,25 @@
2123
2224 screen_view: function (path, sbot) {
2325 if(path === '/git-ssb') {
2426
25- var content = h('div.column.scroller__content')
26- var div = h('div.column.scroller',
27- {style: {'overflow':'auto'}},
28- h('div.scroller__wrapper', content)
29- )
27 + var { container, content } = api.build_scroller()
3028
3129 pull(
3230 u.next(api.sbot_log, {old: false, limit: 100}),
33- Scroller(div, content, api.message_render, true, false)
31 + Scroller(container, content, api.message_render, true, false)
3432 )
3533
3634 pull(
3735 u.next(api.sbot_log, {reverse: true, limit: 100, live: false}),
3836 pull.filter(function(msg) { return msg.value.content.type }),
3937 pull.filter(function(msg) {
4038 return msg.value.content.type.match(/^git/)
4139 }),
42- Scroller(div, content, api.message_render, false, false)
40 + Scroller(container, content, api.message_render, false, false)
4341 )
4442
45- return div
43 + return container
4644 }
4745 }
4846 }
4947 }
modules_extra/git.jsView
@@ -455,27 +455,28 @@
455455 var c = msg.value.content
456456
457457 if(c.type === 'git-repo') {
458458 return h('div', [
459- h('p', 'git repo ', repoName(msg.key)),
460- c.upstream ? h('p', 'fork of ', repoLink(c.upstream)) : ''
459 + h('div', 'git repo ', repoName(msg.key)),
460 + c.upstream ? h('br') : '',
461 + c.upstream ? h('div', 'fork of ', repoLink(c.upstream)) : ''
461462 ])
462463 }
463464
464465 if(c.type === 'git-update') {
465- return h('p', 'pushed to ', repoLink(c.repo))
466 + return h('div', 'pushed to ', repoLink(c.repo))
466467 }
467468
468469 if(c.type === 'issue-edit') {
469470 return
470471 }
471472
472473 if(c.type === 'issue') {
473- return h('p', 'opened issue on ', repoLink(c.project))
474 + return h('div', 'opened issue on ', repoLink(c.project))
474475 }
475476
476477 if(c.type === 'pull-request') {
477- return h('p', 'opened pull-request ', [
478 + return h('div', 'opened pull-request ', [
478479 'to ', repoLink(c.repo), ':', c.branch, ' ',
479480 'from ', repoLink(c.head_repo), ':', c.head_branch
480481 ])
481482 }
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/notifications.jsView
@@ -7,8 +7,9 @@
77 var cont = require('cont')
88 var ref = require('ssb-ref')
99
1010 exports.needs = {
11 + build_scroller: 'first',
1112 message_render: 'first',
1213 sbot_log: 'first',
1314 sbot_get: 'first',
1415 sbot_user_feed: 'first',
@@ -126,22 +127,16 @@
126127 oldest = msg.value.timestamp
127128 }
128129 })
129130
130- var content = h('div.column.scroller__content')
131- var div = h('div.column.scroller',
132- {style: {'overflow':'auto'}},
133- h('div.scroller__wrapper',
134- content
135- )
136- )
131 + var { container, content } = api.build_scroller()
137132
138133 pull(
139134 u.next(api.sbot_log, {old: false, limit: 100}),
140135 unbox(),
141136 notifications(ids),
142137 pull.filter(),
143- Scroller(div, content, api.message_render, true, false)
138 + Scroller(container, content, api.message_render, true, false)
144139 )
145140
146141 pull(
147142 u.next(api.sbot_log, {reverse: true, limit: 100, live: false}),
@@ -151,12 +146,12 @@
151146 pull.take(function (msg) {
152147 // abort stream after we pass the oldest messages of our feeds
153148 return !oldest ? true : msg.value.timestamp > oldest
154149 }),
155- Scroller(div, content, api.message_render, false, false)
150 + Scroller(container, content, api.message_render, false, false)
156151 )
157152
158- return div
153 + return container
159154 }
160155 }
161156 }
162157 }
modules_extra/raw.jsView
@@ -1,40 +1,37 @@
11 var h = require('hyperscript')
22
3-// from ssb-ref
4-var refRegex = /((?:@|%|&)[A-Za-z0-9\/+]{43}=\.[\w\d]+)/g
5-
63 exports.gives = 'message_meta'
74
8-function linkify (text) {
9- var arr = text.split(refRegex)
10- for (var i = 1; i < arr.length; i += 2) {
11- arr[i] = h('a', {href: '#' + arr[i]}, arr[i])
12- }
13- return arr
14-}
155
166 exports.create = function (api) {
177 return function (msg) {
188 var tmp = h('div')
199 var el
2010 var pre
21- return h('input', {
22- type: 'checkbox',
23- title: 'View Data',
11 + const symbol = '\u2699' // gear ⚙
12 + var clicked = false
13 +
14 + return h('a', {
15 + title: 'View raw data',
16 + style: {
17 + order: 99,
18 + color: '#a8a8a8',
19 + 'font-size': '1rem',
20 + cursor: 'pointer',
21 + },
2422 onclick: function () {
23 + clicked = !clicked
24 +
2525 // HACK (mw) yo we need a better way to replace the content
2626 var msgEl = this.parentNode.parentNode
27- var msgContentEl = msgEl.querySelector('.\\.content')
28- if (this.checked) {
27 + var msgContentEl = msgEl.querySelector('.\\.raw-content')
28 + if (clicked) {
2929 // move away the content
3030 while (el = msgContentEl.firstChild)
3131 tmp.appendChild(el)
3232 // show the raw stuff
33- if (!pre) pre = h('pre', linkify(JSON.stringify({
34- key: msg.key,
35- value: msg.value
36- }, 0, 2)))
33 + if (!pre) pre = h('pre', buildRawMsg(msg) )
3734 msgContentEl.appendChild(pre)
3835 } else {
3936 // hide the raw stuff
4037 msgContentEl.removeChild(pre)
@@ -42,8 +39,45 @@
4239 while (el = tmp.firstChild)
4340 msgContentEl.appendChild(el)
4441 }
4542 }
46- })
43 + }, symbol)
4744 }
4845 }
4946
47 +
48 +function buildRawMsg (msg) {
49 + return colorKeys(linkify(
50 + JSON.stringify({
51 + key: msg.key,
52 + value: msg.value
53 + }, 0, 2)
54 + ))
55 +}
56 +
57 +function colorKeys (chunks) {
58 + var newArray = []
59 + chunks.forEach(chunk => {
60 + if (typeof chunk !== 'string') return newArray.push(chunk)
61 +
62 + var arr = chunk.split(/("[^"]+":)/)
63 + for (var i = 1; i < arr.length; i += 2) {
64 + arr[i] = h('span', arr[i])
65 + }
66 + newArray = [...newArray, ...arr]
67 + })
68 +
69 + return newArray
70 +}
71 +
72 +function linkify (text) {
73 + // from ssb-ref
74 + var refRegex = /((?:@|%|&)[A-Za-z0-9\/+]{43}=\.[\w\d]+)/g
75 +
76 + var arr = text.split(refRegex)
77 + for (var i = 1; i < arr.length; i += 2) {
78 + arr[i] = h('a', {href: '#' + arr[i]}, arr[i])
79 + }
80 + return arr
81 +}
82 +
83 +
modules_extra/search.jsView
@@ -1,17 +1,23 @@
1-var h = require('hyperscript')
2-var u = require('../util')
3-var pull = require('pull-stream')
4-var Scroller = require('pull-scroll')
5-var TextNodeSearcher = require('text-node-searcher')
1 +const h = require('../h')
2 +const fs = require('fs')
3 +const { Value, when } = require('@mmckegg/mutant')
4 +const u = require('../util')
5 +const pull = require('pull-stream')
6 +const Scroller = require('pull-scroll')
7 +const TextNodeSearcher = require('text-node-searcher')
68
79 exports.needs = {
10 + build_scroller: 'first',
811 message_render: 'first',
912 sbot_log: 'first',
1013 sbot_fulltext_search: 'first'
1114 }
1215
13-exports.gives = 'screen_view'
16 +exports.gives = {
17 + screen_view: true,
18 + mcss: true
19 +}
1420
1521 var whitespace = /\s+/
1622
1723 function andSearch(terms, inputs) {
@@ -69,64 +75,69 @@
6975 }
7076
7177 exports.create = function (api) {
7278
73- return function (path) {
74- if(path[0] === '?') {
75- var queryStr = path.substr(1).trim()
76- var query = queryStr.split(whitespace)
77- var _matches = searchFilter(query)
79 + return {
80 + screen_view,
81 + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8')
82 + }
7883
79- var total = 0, matches = 0
80- var usingLinearSearch = false
84 + function screen_view (path) {
85 + if (path[0] !== '?') return
8186
82- var header = h('div.search_header', '')
83- var content = h('div.column.scroller__content')
84- var div = h('div.column.scroller',
85- {style: {'overflow':'auto'}},
86- h('div.scroller__wrapper',
87- header,
88- content
89- )
87 + var queryStr = path.substr(1).trim()
88 + var query = queryStr.split(whitespace)
89 + var _matches = searchFilter(query)
90 +
91 + const isLinearSearch = Value(false)
92 + const searched = Value(0)
93 + const matches = Value(0)
94 + const searchHeader = h('Search', [
95 + h('header', h('h1', query.join(' '))),
96 + when(isLinearSearch,
97 + h('section.details', [
98 + h('div.searched', ['Searched: ', searched]),
99 + h('div.matches', [matches, ' matches'])
100 + ])
90101 )
102 + ])
103 + var { container, content } = api.build_scroller({ prepend: searchHeader })
104 + container.id = path // helps tabs find this tab
91105
92- function matchesQuery (data) {
93- total++
94- var m = _matches(data)
95- if(m) matches++
96- if(usingLinearSearch) {
97- header.textContent = 'searched:'+total+', found:'+matches
98- }
99- return m
100- }
106 + function matchesQuery (data) {
107 + searched.set(searched() + 1)
108 + var m = _matches(data)
109 + if(m) matches.set(matches() +1 )
110 +
111 + return m
112 + }
101113
102- function renderMsg(msg) {
103- var el = api.message_render(msg)
104- highlight(el, createOrRegExp(query))
105- return el
106- }
114 + function renderMsg(msg) {
115 + var el = api.message_render(msg)
116 + highlight(el, createOrRegExp(query))
117 + return el
118 + }
107119
108- pull(
109- api.sbot_log({old: false}),
110- pull.filter(matchesQuery),
111- Scroller(div, content, renderMsg, true, false)
112- )
120 + pull(
121 + api.sbot_log({old: false}),
122 + pull.filter(matchesQuery),
123 + Scroller(container, content, renderMsg, true, false)
124 + )
113125
114- pull(
115- u.next(api.sbot_fulltext_search, {query: queryStr, reverse: true, limit: 500, live: false}),
116- fallback(function (err) {
117- if (/^no source/.test(err.message)) {
118- usingLinearSearch = true
119- return pull(
120- u.next(api.sbot_log, {reverse: true, limit: 500, live: false}),
121- pull.filter(matchesQuery)
122- )
123- }
124- }),
125- Scroller(div, content, renderMsg, false, false)
126- )
126 + pull(
127 + u.next(api.sbot_fulltext_search, {query: queryStr, reverse: true, limit: 500, live: false}),
128 + fallback((err) => {
129 + if (/^no source/.test(err.message)) {
130 + isLinearSearch.set(true)
131 + return pull(
132 + u.next(api.sbot_log, {reverse: true, limit: 500, live: false}),
133 + pull.filter(matchesQuery)
134 + )
135 + }
136 + }),
137 + Scroller(container, content, renderMsg, false, false)
138 + )
127139
128- return div
129- }
140 + return container
130141 }
142 +}
131143
132-}
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,14 @@
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('../modules_basic')
8 +const extra = require('./')
9 +
10 +test('modules_extra has no outside deps', t => {
11 + t.ok(combine(extra, basic, core))
12 + t.end()
13 +})
14 +
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 +
modules_extra/search.mcssView
@@ -1,0 +1,22 @@
1 +Search {
2 + padding: .5rem
3 + border-bottom: solid 1px gainsboro
4 +
5 + header {
6 + h1 {
7 + }
8 + }
9 +
10 + section.details {
11 + display: flex
12 + justify-content: space-between
13 +
14 + font-family: monospace
15 +
16 + div.searched {}
17 + div.matched {}
18 + }
19 +
20 +}
21 +
22 +
package.jsonView
@@ -1,44 +1,53 @@
11 {
22 "name": "patchbay",
33 "description": "a pluggable patchwork",
4- "version": "6.1.13",
5- "homepage": "https://github.com/dominictarr/patchbay",
4 + "version": "6.7.4",
5 + "homepage": "https://github.com/ssbc/patchbay",
66 "repository": {
77 "type": "git",
8- "url": "git://github.com/dominictarr/patchbay.git"
8 + "url": "git://github.com/ssbc/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",
20- "hypercrop": "^1.0.1",
28 + "hypercrop": "^1.1.0",
2129 "hyperfile": "^1.1.0",
2230 "hyperlightbox": "1.0.0",
2331 "hyperprogress": "0.1.0",
2432 "hyperscript": "^1.4.7",
25- "hypertabs": "^3.0.1",
33 + "hypertabs": "^4.0.0",
2634 "insert-css": "^2.0.0",
2735 "is-visible": "^2.1.1",
2836 "kvgraph": "^0.1.0",
2937 "map-filter-reduce": "^3.0.1",
3038 "micro-css": "^0.6.2",
3139 "mime-types": "^2.1.11",
3240 "moment": "^2.13.0",
41 + "on-load": "^3.2.0",
3342 "open-external": "^0.1.1",
3443 "peaks.js": "^0.4.7",
3544 "pull-cat": "^1.1.9",
3645 "pull-many": "^1.0.7",
3746 "pull-next": "^0.0.1",
3847 "pull-paramap": "^1.1.6",
3948 "pull-reconnect": "^0.0.3",
40- "pull-scroll": "^1.0.1",
49 + "pull-scroll": "^1.0.3",
4150 "pull-stream": "^3.4.5",
4251 "scuttlebot": "^8.7.2",
4352 "setimmediate": "^1.0.5",
4453 "simple-mime": "^0.1.0",
@@ -50,9 +59,9 @@
5059 "ssb-feed": "^2.2.1",
5160 "ssb-git": "^0.4.1",
5261 "ssb-keys": "^6.1.0",
5362 "ssb-links": "^2.0.0",
54- "ssb-markdown": "^3.0.0",
63 + "ssb-markdown": "^3.2.1",
5564 "ssb-mentions": "^0.1.0",
5665 "ssb-query": "^0.1.1",
5766 "ssb-ref": "^2.6.2",
5867 "ssb-sort": "^1.0.0",
@@ -64,23 +73,17 @@
6473 "devDependencies": {
6574 "browselectrify": "^1.0.1",
6675 "electro": "^2.0.3",
6776 "electron": "^1.4.10",
68- "indexhtmlify": "^1.3.1"
77 + "indexhtmlify": "^1.3.1",
78 + "tape": "^4.6.3",
79 + "tape-run": "^2.1.5"
6980 },
7081 "browserify": {
7182 "transform": [
7283 "brfs",
7384 "es2040"
7485 ]
7586 },
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- },
8487 "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
8588 "license": "MIT"
8689 }
style.cssView
@@ -28,15 +28,8 @@
2828 color: #005580;
2929 text-decoration: underline;
3030 }
3131
32-.screen {
33- position: absolute;
34- top: 0; bottom: 0;
35- left: 0; right: 0;
36- overflow-y: hidden;
37-}
38-
3932 .column {
4033 display: flex;
4134 flex-direction: column;
4235 min-height:0px;
@@ -65,18 +58,8 @@
6558 .expand {
6659 justify-content: space-between;
6760 }
6861
69-.scroll-y {
70- overflow-y: auto;
71- min-height: 0px;
72-}
73-
74-.scroll-x {
75- overflow-x: auto;
76- min-width: 0px;
77-}
78-
7962 pre {
8063 white-space: pre-wrap;
8164 word-wrap: break-word;
8265 }
@@ -150,21 +133,8 @@
150133 border-radius: .2em;
151134 z-index: 5;
152135 }
153136
154-/* scrolling feeds, threads */
155-
156-.scroller {
157- width: 100%;
158-}
159-
160-.scroller__wrapper {
161- flex: 1;
162- max-width: 600px;
163- margin-left: auto;
164- margin-right: auto;
165-}
166-
167137 /* messages */
168138 /* is .title used any more? */
169139
170140 .title {
@@ -177,39 +147,9 @@
177147 vertical-align: top;
178148 }
179149
180150
181-/* -- suggest box */
182151
183-.suggest-box > * {
184- display: block;
185-}
186-
187-.suggest-box ul {
188- padding: 0;
189- list-style-type: none;
190- padding-left: 0;
191- background: #eee;
192- border: 1px solid #eee;
193- border-radius: 2px;
194-}
195-
196-.suggest-box .selected {
197- background: white;
198-}
199-
200-.suggest-box {
201- width: max-content;
202- background: #white;
203- border-radius: 1em;
204-}
205-
206-/* emoji */
207-.suggest-box img {
208- height: 20px;
209- width: 20px;
210-}
211-
212152 /* avatar */
213153
214154 .avatar--large,
215155 .avatar--thumbnail,
@@ -237,21 +177,8 @@
237177 .avatar--fullsize {
238178 width: 50%;
239179 }
240180
241-.profile {
242- padding: .5ex;
243- overflow: auto;
244-}
245-
246-.profile input {
247- width: 100%;
248-}
249-
250-.profile__info {
251- margin-left: .5em;
252-}
253-
254181 /* lightbox - used in message-confirm */
255182
256183 .lightbox {
257184 position: fixed;
@@ -259,8 +186,9 @@
259186 right: 0px;
260187 top: 50px;
261188 overflow: auto;
262189 width: 650px;
190 + max-width: 100%;
263191 padding: 25px;
264192 margin: auto;
265193
266194 z-index: 2;
@@ -269,22 +197,9 @@
269197 border: 1px solid #eee;
270198 border-radius: .2em;
271199 }
272200
273-/* searchprompt */
274201
275-.searchprompt {
276- float: left;
277- width: 85%;
278- height: 2em;
279- margin-top: .3em;
280- border-radius: 1em;
281- padding-left: .7em;
282- padding-right: 0.5em;
283- margin-left: 1em;
284- margin-right: 1em;
285-}
286-
287202 /* TextNodeSearcher highlights */
288203
289204 highlight {
290205 background: #ff8;
@@ -318,79 +233,16 @@
318233 border-radius: 100%;
319234 background: #08c;
320235 }
321236
322-.error {
323- background: red;
324-}
325-
326237 /* tabs */
327238
328239 .header {
329240 background: #f5f5f5;
330241 border-bottom: 1px inset;
331242 flex-shrink: 0;
332243 }
333244
334-.header__tabs {
335- width: 100%;
336- min-width: 0px;
337-}
338-
339-/* --- hypertabs ------- */
340-
341-.hypertabs__tabs {
342- min-width: 0px;
343- width: 100%;
344-}
345-
346-.hypertabs__tab {
347- overflow-x: hidden;
348- min-width: 0px;
349- width: 100%;
350-}
351-
352-.hypertabs__button {
353- overflow-x: hidden;
354- min-width: 0px;
355- width: 100%;
356-}
357-
358-.hypertabs__tab {
359- color: black;
360- background: #f5f5f5;
361- border-top-left-radius: 5px;
362- margin-left: -3px;
363- border-bottom: none;
364- padding-top: .56em;
365- padding-left: 1em;
366- border-left: 1px solid #ddd;
367- width: 100%;
368-}
369-
370-.hypertabs__tab > a {
371- color: #666;
372- text-decoration: none;
373- white-space: nowrap;
374- font-size: .9em;
375-}
376-
377-.hypertabs--selected {
378- font-weight: bold;
379- background: #eee;
380- border-top-right-radius: 5px;
381- z-index: 1;
382-}
383-
384-.hypertabs__x {
385- display: none;
386- transform: translate(-4px, -3px);
387-}
388-
389-.hypertabs--selected .hypertabs__x {
390- display: block;
391-}
392-
393245 /* progress bar */
394246
395247 .hyperprogress__bar {
396248 background: darkgrey;

Built with git-ssb-web