Commit 07678ab95020edf09925a491966765d79e086d0e
Merge branch 'master' into fulltext-search-update
mix irving committed on 2/5/2017, 2:05:26 AMParent: 70bc98e1fcb76213a7b487d8e8b7a8f49cc45943
Parent: 5840ace3e723ac68ca995db5a3598cb2ac264c83
Files changed
README.md | ||
---|---|---|
@@ -30,9 +30,9 @@ | ||
30 | 30 … | # restart sbot server (go back to previous tab and kill it) |
31 | 31 … | ``` |
32 | 32 … | now clone and run patchbay. |
33 | 33 … | ``` |
34 | -git clone https://github.com/dominictarr/patchbay.git | |
34 … | +git clone https://github.com/ssbc/patchbay.git | |
35 | 35 … | cd patchbay |
36 | 36 … | npm install |
37 | 37 … | npm run rebuild |
38 | 38 … | npm run bundle |
config.js | ||
---|---|---|
@@ -23,10 +23,10 @@ | ||
23 | 23 … | else |
24 | 24 … | blobsUrl = 'http://localhost:8989/blobs/get' |
25 | 25 … | |
26 | 26 … | return { |
27 | - remote: remote, | |
28 | - blobsUrl: blobsUrl | |
27 … | + remote, | |
28 … | + blobsUrl | |
29 | 29 … | } |
30 | 30 … | } |
31 | 31 … | |
32 | 32 … |
embedded.js | ||
---|---|---|
@@ -1,9 +1,12 @@ | ||
1 … | +// polyfills | |
2 … | +require('setimmediate') | |
3 … | + | |
1 | 4 … | require('depject')( |
2 | 5 … | require('./modules_embedded'), |
3 | 6 … | require('./modules_basic'), |
4 | 7 … | require('./modules_extra') |
5 | -).plugs.app[0]() | |
8 … | +).app[0]() | |
6 | 9 … | |
7 | 10 … | |
8 | 11 … | |
9 | 12 … |
keyscroll.js | ||
---|---|---|
@@ -23,10 +23,10 @@ | ||
23 | 23 … | curMsgEl = el |
24 | 24 … | } |
25 | 25 … | |
26 | 26 … | return function scroll(d) { |
27 | - selectChild(!curMsgEl ? container.firstChild | |
27 … | + selectChild((!curMsgEl || d == 'first') ? container.firstChild | |
28 … | + : d < 0 ? curMsgEl.previousElementSibling || container.firstChild | |
28 | 29 … | : d > 0 ? curMsgEl.nextElementSibling || container.lastChild |
29 | - : d < 0 ? curMsgEl.previousElementSibling || container.firstChild | |
30 | 30 … | : curMsgEl) |
31 | 31 … | } |
32 | 32 … | } |
modules_basic/compose.js | ||
---|---|---|
@@ -1,13 +1,12 @@ | ||
1 | 1 … | |
2 | 2 … | const fs = require('fs') |
3 | 3 … | const h = require('../h') |
4 | -const suggest = require('suggest-box') | |
5 | 4 … | const mentions = require('ssb-mentions') |
6 | -const cont = require('cont') | |
7 | 5 … | |
8 | 6 … | exports.needs = { |
9 | 7 … | suggest_mentions: 'map', //<-- THIS MUST BE REWRITTEN |
8 … | + build_suggest_box: 'first', | |
10 | 9 … | publish: 'first', |
11 | 10 … | message_content: 'first', |
12 | 11 … | message_confirm: 'first', |
13 | 12 … | file_input: 'first' |
@@ -125,24 +124,17 @@ | ||
125 | 124 … | var actions = h('section.actions', [ |
126 | 125 … | fileInput, publishBtn |
127 | 126 … | ]) |
128 | 127 … | |
128 … | + api.build_suggest_box(textArea, api.suggest_mentions) | |
129 … | + | |
129 | 130 … | var composer = h('Compose', { |
130 | 131 … | className: opts.shrink === false ? '-expanded' : '-contracted' |
131 | 132 … | }, [ |
132 | 133 … | textArea, |
133 | 134 … | actions |
134 | 135 … | ]) |
135 | 136 … | |
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 | - }, {}) | |
145 | 137 … | |
146 | 138 … | return composer |
147 | 139 … | } |
148 | 140 … |
modules_basic/feed.js | ||
---|---|---|
@@ -4,8 +4,9 @@ | ||
4 | 4 … | var pull = require('pull-stream') |
5 | 5 … | var u = require('../util') |
6 | 6 … | |
7 | 7 … | exports.needs = { |
8 … | + build_scroller: 'first', | |
8 | 9 … | sbot_user_feed: 'first', |
9 | 10 … | message_render: 'first', |
10 | 11 … | avatar_profile: 'first', |
11 | 12 … | signifier: 'first' |
@@ -18,43 +19,34 @@ | ||
18 | 19 … | |
19 | 20 … | return function (id) { |
20 | 21 … | //TODO: header of user info, avatars, names, follows. |
21 | 22 … | |
22 | - if(ref.isFeed(id)) { | |
23 … | + if(!ref.isFeed(id)) return | |
23 | 24 … | |
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 | |
32 | 32 … | |
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 … | + ) | |
36 | 37 … | |
38 … | + //how to handle when have scrolled past the start??? | |
37 | 39 … | |
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 … | + ) | |
42 | 48 … | |
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 | |
57 | 50 … | } |
58 | - | |
59 | 51 … | } |
60 | 52 … |
modules_basic/follow.js | ||
---|---|---|
@@ -1,5 +1,6 @@ | ||
1 | -var h = require('hyperscript') | |
1 … | +const fs = require('fs') | |
2 … | +const h = require('../h') | |
2 | 3 … | |
3 | 4 … | //render a message when someone follows someone, |
4 | 5 … | //so you see new users |
5 | 6 … | function isRelated(value, name) { |
@@ -17,74 +18,93 @@ | ||
17 | 18 … | exports.gives = { |
18 | 19 … | message_content: true, |
19 | 20 … | message_content_mini: true, |
20 | 21 … | avatar_action: true, |
22 … | + mcss: true | |
21 | 23 … | } |
22 | 24 … | |
23 | 25 … | 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' | |
31 | 38 … | 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), '') | |
34 | 42 … | ] |
35 | 43 … | } |
36 | 44 … | } |
37 | 45 … | |
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 … | + ]) | |
45 | 55 … | } |
46 | 56 … | } |
47 | 57 … | |
48 | - exports.avatar_action = function (id) { | |
58 … | + function avatar_action (id) { | |
49 | 59 … | var follows_you, you_follow |
50 | 60 … | |
51 | 61 … | 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 | |
54 | 64 … | update() |
55 | 65 … | }) |
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 | |
58 | 68 … | update() |
59 | 69 … | }) |
60 | 70 … | |
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') | |
63 | 73 … | |
64 | 74 … | function update () { |
65 | 75 … | 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' | |
69 | 79 … | : '' |
70 | 80 … | ) |
81 … | + | |
82 … | + // wait till finished loading before offering follow options | |
83 … | + if (you_follow === undefined) return | |
84 … | + followBtn.textContent = you_follow ? 'unfollow' : 'follow' | |
85 … | + } | |
71 | 86 … | |
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 … | + }) | |
73 | 106 … | } |
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 … | + | |
88 | 108 … | } |
89 | 109 … | return exports |
90 | 110 … | } |
modules_basic/index.js | ||
---|---|---|
@@ -1,33 +1,23 @@ | ||
1 | 1 … | module.exports = { |
2 | - "about.js": require('./about.js'), | |
3 | - "avatar-edit.js": require('./avatar-edit.js'), | |
4 | - "avatar-image.js": require('./avatar-image.js'), | |
5 | - "avatar-link.js": require('./avatar-link.js'), | |
6 | - "avatar-name.js": require('./avatar-name.js'), | |
7 | - "avatar-profile.js": require('./avatar-profile.js'), | |
8 | - "avatar.js": require('./avatar.js'), | |
9 | - "compose.js": require('./compose.js'), | |
10 | - "feed.js": require('./feed.js'), | |
11 | - "follow.js": require('./follow.js'), | |
12 | - "invite.js": require('./invite.js'), | |
13 | - "like.js": require('./like.js'), | |
14 | - "markdown.js": require('./markdown.js'), | |
15 | - "message-author.js": require('./message-author.js'), | |
16 | - "message-backlinks.js": require('./message-backlinks.js'), | |
17 | - "message-link.js": require('./message-link.js'), | |
18 | - "message-name.js": require('./message-name.js'), | |
19 | - "message.js": require('./message.js'), | |
20 | - "names.js": require('./names.js'), | |
21 | - "post.js": require('./post.js'), | |
22 | - "private.js": require('./private.js'), | |
23 | - "pub.js": require('./pub.js'), | |
24 | - "public.js": require('./public.js'), | |
25 | - "relationships.js": require('./relationships.js'), | |
26 | - "reply.js": require('./reply.js'), | |
27 | - "search-box.js": require('./search-box.js'), | |
28 | - "setup.js": require('./setup'), | |
29 | - "suggest-mentions.js": require('./suggest-mentions.js'), | |
30 | - "thread.js": require('./thread.js'), | |
31 | - "timestamp.js": require('./timestamp.js') | |
2 … | + 'about': require('./about'), | |
3 … | + 'avatar': require('./avatar'), | |
4 … | + 'compose': require('./compose'), | |
5 … | + 'emoji': require('./emoji'), | |
6 … | + 'feed': require('./feed'), | |
7 … | + 'follow': require('./follow'), | |
8 … | + 'invite': require('./invite'), | |
9 … | + 'like': require('./like'), | |
10 … | + 'markdown': require('./markdown'), | |
11 … | + 'message': require('./message'), | |
12 … | + 'post': require('./post'), | |
13 … | + 'private': require('./private'), | |
14 … | + 'pub': require('./pub'), | |
15 … | + 'public': require('./public'), | |
16 … | + 'relationships': require('./relationships'), | |
17 … | + 'reply': require('./reply'), | |
18 … | + 'scroller': require('./scroller'), | |
19 … | + 'setup': require('./setup'), | |
20 … | + 'thread': require('./thread'), | |
21 … | + 'timestamp': require('./timestamp') | |
32 | 22 … | } |
33 | 23 … |
modules_basic/like.js | ||
---|---|---|
@@ -39,20 +39,22 @@ | ||
39 | 39 … | )) |
40 | 40 … | votes.push({source: CACHE[k].author, dest: k, rel: 'vote'}) |
41 | 41 … | } |
42 | 42 … | |
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 | |
47 | 44 … | |
45 … | + const symbol = '\u2713' // tick 🗸 | |
46 … | + | |
47 … | + digs.textContent = votes.length > 4 | |
48 … | + ? votes.length + ' ' + symbol | |
49 … | + : Array(votes.length).fill(symbol).join('') | |
50 … | + | |
48 | 51 … | 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 … | + }) | |
55 | 57 … | ) |
56 | 58 … | |
57 | 59 … | return digs |
58 | 60 … | } |
modules_basic/markdown.js | ||
---|---|---|
@@ -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') | |
4 | 5 … | |
5 | 6 … | exports.needs = { |
6 | 7 … | blob_url: 'first', |
7 | 8 … | emoji_url: 'first' |
8 | 9 … | } |
9 | 10 … | |
10 | -exports.gives = 'markdown' | |
11 … | +exports.gives = { | |
12 … | + markdown: true, | |
13 … | + mcss: true | |
14 … | +} | |
11 | 15 … | |
12 | 16 … | 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') | |
21 | 20 … | } |
22 | 21 … | |
23 | - return function (content) { | |
22 … | + function markdown (content) { | |
24 | 23 … | if('string' === typeof content) |
25 | 24 … | content = {text: content} |
26 | 25 … | //handle patchwork style mentions. |
27 | 26 … | var mentions = {} |
@@ -29,18 +28,29 @@ | ||
29 | 28 … | content.mentions.forEach(function (link) { |
30 | 29 … | if(link.name) mentions["@"+link.name] = link.link |
31 | 30 … | }) |
32 | 31 … | |
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, { | |
35 | 34 … | emoji: renderEmoji, |
36 | - toUrl: function (id) { | |
35 … | + toUrl: (id) => { | |
37 | 36 … | if(ref.isBlob(id)) return api.blob_url(id) |
38 | 37 … | return '#'+(mentions[id]?mentions[id]:id) |
39 | - } | |
38 … | + }, | |
39 … | + imageLink: (id) => '#' + id | |
40 | 40 … | }) |
41 | 41 … | |
42 | 42 … | return md |
43 | 43 … | |
44 | 44 … | } |
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 … | + | |
45 | 55 … | } |
46 | 56 … |
modules_basic/private.js | ||
---|---|---|
@@ -1,16 +1,18 @@ | ||
1 | 1 … | |
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') | |
7 | 8 … | |
8 | 9 … | function map(ary, iter) { |
9 | 10 … | if(Array.isArray(ary)) return ary.map(iter) |
10 | 11 … | } |
11 | 12 … | |
12 | 13 … | exports.needs = { |
14 … | + build_scroller: 'first', | |
13 | 15 … | message_render: 'first', |
14 | 16 … | message_compose: 'first', |
15 | 17 … | message_unbox: 'first', |
16 | 18 … | sbot_log: 'first', |
@@ -22,9 +24,10 @@ | ||
22 | 24 … | exports.gives = { |
23 | 25 … | builtin_tabs: true, |
24 | 26 … | screen_view: true, |
25 | 27 … | message_meta: true, |
26 | - message_content_mini: true | |
28 … | + message_content_mini: true, | |
29 … | + // mcss: true | |
27 | 30 … | } |
28 | 31 … | |
29 | 32 … | exports.create = function (api) { |
30 | 33 … | |
@@ -40,79 +43,90 @@ | ||
40 | 43 … | ) |
41 | 44 … | } |
42 | 45 … | |
43 | 46 … | 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 … | + } | |
47 | 53 … | |
48 | - screen_view: function (path) { | |
49 | - if(path !== '/private') return | |
54 … | + function builtin_tabs () { | |
55 … | + return ['/private'] | |
56 … | + } | |
50 | 57 … | |
51 | - var div = h('div.column.scroller', | |
52 | - {style: {'overflow':'auto'}}) | |
58 … | + function screen_view (path) { | |
59 … | + if(path !== '/private') return | |
53 | 60 … | |
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 }) | |
63 | 76 … | |
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.')) | |
78 | 86 … | |
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 … | + ) | |
81 | 92 … | |
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 … | + }) | |
87 | 101 … | |
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 … | + } | |
96 | 104 … | |
97 | - return div | |
98 | - }, | |
105 … | + function message_meta (msg) { | |
106 … | + if(!msg.value.content.recps && ! msg.value.private) return | |
99 | 107 … | |
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 … | + } | |
106 | 122 … | |
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' | |
114 | 129 … | } |
115 | 130 … | } |
116 | - | |
117 | 131 … | } |
118 | 132 … |
modules_basic/public.js | ||
---|---|---|
@@ -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') | |
5 | 6 … | |
6 | 7 … | exports.needs = { |
8 … | + build_scroller: 'first', | |
7 | 9 … | message_render: 'first', |
8 | 10 … | message_compose: 'first', |
9 | 11 … | sbot_log: 'first', |
10 | 12 … | } |
11 | 13 … | |
12 | 14 … | exports.gives = { |
13 | - builtin_tabs: true, screen_view: true | |
15 … | + builtin_tabs: true, | |
16 … | + screen_view: true, | |
17 … | + // mcss: true | |
14 | 18 … | } |
15 | 19 … | |
16 | 20 … | exports.create = function (api) { |
17 | - | |
18 | 21 … | 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 … | + } | |
22 | 26 … | |
23 | - screen_view: function (path, sbot) { | |
24 | - if(path === '/public') { | |
27 … | + function builtin_tabs () { | |
28 … | + return ['/public'] | |
29 … | + } | |
25 | 30 … | |
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 | |
34 | 33 … | |
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 }) | |
39 | 36 … | |
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 … | + ) | |
44 | 41 … | |
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 | |
48 | 48 … | } |
49 | 49 … | } |
50 … | + |
modules_basic/relationships.js | ||
---|---|---|
@@ -1,17 +1,19 @@ | ||
1 | 1 … | var pull = require('pull-stream') |
2 | 2 … | |
3 … | +//this is a bit crude, and doesn't actually show unfollows yet. | |
4 … | + | |
3 | 5 … | function makeQuery (a, b) { |
4 | 6 … | return {"$filter": { |
5 | - value: { | |
6 | - author: a, | |
7 | - content: { | |
8 | - type: 'contact', | |
9 | - contact: b, | |
10 | - following: true | |
11 | - } | |
12 | - }, | |
13 | - }} | |
7 … | + value: { | |
8 … | + author: a, | |
9 … | + content: { | |
10 … | + type: 'contact', | |
11 … | + contact: b, | |
12 … | + following: true | |
13 … | + } | |
14 … | + }, | |
15 … | + }} | |
14 | 16 … | } |
15 | 17 … | |
16 | 18 … | |
17 | 19 … | exports.needs = { sbot_query: 'first' } |
modules_basic/setup.js | ||
---|---|---|
@@ -1,5 +1,4 @@ | ||
1 | - | |
2 | 1 … | var h = require('hyperscript') |
3 | 2 … | var pull = require('pull-stream') |
4 | 3 … | |
5 | 4 … | exports.needs = { |
@@ -10,8 +9,15 @@ | ||
10 | 9 … | sbot_progress: 'first', |
11 | 10 … | sbot_query: 'first' |
12 | 11 … | } |
13 | 12 … | |
13 … | +exports.gives = { | |
14 … | + setup_is_fresh_install: true, | |
15 … | + progress_bar: true, | |
16 … | + setup_joined_network: true, | |
17 … | + screen_view: true | |
18 … | +} | |
19 … | + | |
14 | 20 … | //maybe this could show the pubs, or |
15 | 21 … | //if someone locally follows you, |
16 | 22 … | //it could show the second degree pubs? |
17 | 23 … | |
@@ -28,13 +34,17 @@ | ||
28 | 34 … | }}] |
29 | 35 … | } |
30 | 36 … | |
31 | 37 … | exports.create = function (api) { |
38 … | + return { | |
39 … | + setup_is_fresh_install, | |
40 … | + progress_bar, | |
41 … | + setup_joined_network, | |
42 … | + screen_view | |
43 … | + } | |
32 | 44 … | |
33 | - var exports = {} | |
34 | - | |
35 | 45 … | //test whether we are connected to the ssb network. |
36 | - exports.setup_is_fresh_install = function (cb) { | |
46 … | + function setup_is_fresh_install (cb) { | |
37 | 47 … | //test by checking whether you have any friends following you? |
38 | 48 … | pull( |
39 | 49 … | api.sbot_query({query: followers_query(id), limit: 1, live: false}), |
40 | 50 … | pull.collect(function (err, ary) { |
@@ -78,9 +88,9 @@ | ||
78 | 88 … | |
79 | 89 … | return h('div.invite-form.row', input, accept) |
80 | 90 … | } |
81 | 91 … | |
82 | - exports.progress_bar = function () { | |
92 … | + function progress_bar () { | |
83 | 93 … | var liquid = h('div.hyperprogress__liquid', '.') |
84 | 94 … | var bar = h('div.hyperprogress__bar', liquid) |
85 | 95 … | liquid.style.width = '0%' |
86 | 96 … | |
@@ -99,9 +109,9 @@ | ||
99 | 109 … | |
100 | 110 … | //when you join the network, I want this to show as people follow you. |
101 | 111 … | //that could be when a pub accepts the invite, or when a local peer accepts. |
102 | 112 … | |
103 | - exports.setup_joined_network = function (id) { | |
113 … | + function setup_joined_network (id) { | |
104 | 114 … | var followers = h('div.column') |
105 | 115 … | var label = h('label', 'not connected to a network') |
106 | 116 … | var joined = h('div.setup__joined', label, followers) |
107 | 117 … | |
@@ -118,31 +128,32 @@ | ||
118 | 128 … | |
119 | 129 … | return joined |
120 | 130 … | } |
121 | 131 … | |
122 | - exports.screen_view = function (path) { | |
132 … | + function screen_view (path) { | |
123 | 133 … | if(path !== '/setup') return |
124 | 134 … | |
125 | - var id = require('../keys').id | |
135 … | + var { id } = require('../keys') | |
126 | 136 … | |
127 | 137 … | //set up an avatar |
128 | 138 … | |
129 | 139 … | 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. | |
140 | 152 … | |
141 | - exports.progress_bar(), | |
142 | - exports.setup_joined_network(require('../keys').id) | |
143 | - )) | |
153 … | + progress_bar(), | |
154 … | + setup_joined_network(id) | |
155 … | + ]) | |
156 … | + ]) | |
144 | 157 … | } |
158 … | +} | |
145 | 159 … | |
146 | - return exports | |
147 | - | |
148 | -} |
modules_basic/thread.js | ||
---|---|---|
@@ -18,8 +18,9 @@ | ||
18 | 18 … | } |
19 | 19 … | } |
20 | 20 … | |
21 | 21 … | exports.needs = { |
22 … | + build_scroller: 'first', | |
22 | 23 … | message_render: 'first', |
23 | 24 … | message_name: 'first', |
24 | 25 … | message_compose: 'first', |
25 | 26 … | message_unbox: 'first', |
@@ -60,19 +61,13 @@ | ||
60 | 61 … | root: id, |
61 | 62 … | branch: id //mutated when thread is loaded. |
62 | 63 … | } |
63 | 64 … | |
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 }) | |
72 | 67 … | |
73 | 68 … | api.message_name(id, function (err, name) { |
74 | - div.title = name | |
69 … | + container.title = name | |
75 | 70 … | }) |
76 | 71 … | |
77 | 72 … | pull( |
78 | 73 … | api.sbot_links({ |
@@ -118,8 +113,8 @@ | ||
118 | 113 … | }) |
119 | 114 … | } |
120 | 115 … | |
121 | 116 … | loadThread() |
122 | - return div | |
117 … | + return container | |
123 | 118 … | } |
124 | 119 … | } |
125 | 120 … | } |
modules_basic/avatar-edit.js | ||
---|---|---|
@@ -1,143 +1,0 @@ | ||
1 | -'use strict' | |
2 | -var dataurl = require('dataurl-') | |
3 | -var hyperfile = require('hyperfile') | |
4 | -var hypercrop = require('hypercrop') | |
5 | -var hyperlightbox = require('hyperlightbox') | |
6 | -var h = require('hyperscript') | |
7 | -var pull = require('pull-stream') | |
8 | -var getAvatar = require('ssb-avatar') | |
9 | -var ref = require('ssb-ref') | |
10 | -var visualize = require('visualize-buffer') | |
11 | -var self_id = require('../keys').id | |
12 | - | |
13 | -function crop (d, cb) { | |
14 | - var canvas = hypercrop(h('img', {src: d})) | |
15 | - | |
16 | - return h('div.column.avatar_pic', | |
17 | - canvas, | |
18 | - //canvas.selection, | |
19 | - h('div.row.avatar_pic__controls', | |
20 | - h('button', 'okay', {onclick: function () { | |
21 | - cb(null, canvas.selection.toDataURL()) | |
22 | - }}), | |
23 | - h('button', 'cancel', {onclick: function () { | |
24 | - cb(new Error('canceled')) | |
25 | - }}) | |
26 | - ) | |
27 | - ) | |
28 | -} | |
29 | - | |
30 | -exports.needs = { | |
31 | - message_confirm: 'first', | |
32 | - sbot_blobs_add: 'first', | |
33 | - blob_url: 'first', | |
34 | - sbot_links: 'first', | |
35 | - avatar_name: 'first' | |
36 | -} | |
37 | - | |
38 | -exports.gives = 'avatar_edit' | |
39 | - | |
40 | -exports.create = function (api) { | |
41 | - return function (id) { | |
42 | - | |
43 | - var img = visualize(new Buffer(id.substring(1), 'base64'), 256) | |
44 | - img.classList.add('avatar--large') | |
45 | - | |
46 | - var lb = hyperlightbox() | |
47 | - var name_input = h('input', {placeholder: 'rename'}) | |
48 | - var name = api.avatar_name(id) | |
49 | - var selected = null | |
50 | - | |
51 | - getAvatar({links: api.sbot_links}, self_id, id, function (err, avatar) { | |
52 | - if (err) return console.error(err) | |
53 | - //don't show user has already selected an avatar. | |
54 | - if(selected) return | |
55 | - if(ref.isBlob(avatar.image)) | |
56 | - img.src = api.blob_url(avatar.image) | |
57 | - }) | |
58 | - | |
59 | - var also_pictured = h('div.profile__alsopicturedas.wrap') | |
60 | - | |
61 | - pull( | |
62 | - api.sbot_links({dest: id, rel: 'about', values: true}), | |
63 | - pull.map(function (e) { | |
64 | - return e.value.content.image | |
65 | - }), | |
66 | - pull.filter(function (e) { | |
67 | - return e && 'string' == typeof e.link | |
68 | - }), | |
69 | - pull.unique('link'), | |
70 | - pull.drain(function (image) { | |
71 | - also_pictured.appendChild( | |
72 | - h('a', {href:'#', onclick: function (ev) { | |
73 | - ev.stopPropagation() | |
74 | - ev.preventDefault() | |
75 | - selected = image | |
76 | - img.src = api.blob_url(image.link || image) | |
77 | - }}, | |
78 | - h('img.avatar--thumbnail', {src: api.blob_url(image)}) | |
79 | - ) | |
80 | - ) | |
81 | - }) | |
82 | - ) | |
83 | - | |
84 | - return h('div.row.profile', | |
85 | - lb, | |
86 | - img, | |
87 | - h('div.column.profile__info', | |
88 | - h('strong', name), | |
89 | - name_input, | |
90 | - | |
91 | - hyperfile.asDataURL(function (data) { | |
92 | - var el = crop(data, function (err, data) { | |
93 | - if(data) { | |
94 | - img.src = data | |
95 | - var _data = dataurl.parse(data) | |
96 | - pull( | |
97 | - pull.once(_data.data), | |
98 | - api.sbot_blobs_add(function (err, hash) { | |
99 | - //TODO. Alerts are EVIL. | |
100 | - //I use them only in a moment of weakness. | |
101 | - | |
102 | - if(err) return alert(err.stack) | |
103 | - selected = { | |
104 | - link: hash, | |
105 | - size: _data.data.length, | |
106 | - type: _data.mimetype, | |
107 | - width: 512, | |
108 | - height: 512 | |
109 | - } | |
110 | - | |
111 | - }) | |
112 | - ) | |
113 | - } | |
114 | - lb.close() | |
115 | - }) | |
116 | - lb.show(el) | |
117 | - }), | |
118 | - h('button', 'update', {onclick: function () { | |
119 | - if(name_input.value) | |
120 | - name.textContent = name_input.value | |
121 | - | |
122 | - if(selected) | |
123 | - api.message_confirm({ | |
124 | - type: 'about', | |
125 | - about: id, | |
126 | - name: name_input.value || undefined, | |
127 | - image: selected | |
128 | - }) | |
129 | - else if(name_input.value) //name only | |
130 | - api.message_confirm({ | |
131 | - type: 'about', | |
132 | - about: id, | |
133 | - name: name_input.value || undefined, | |
134 | - }) | |
135 | - else | |
136 | - //another moment of weakness | |
137 | - alert('must select a name or image') | |
138 | - }}), | |
139 | - also_pictured | |
140 | - ) | |
141 | - ) | |
142 | - } | |
143 | -} |
modules_basic/avatar/avatar.js | ||
---|---|---|
@@ -1,0 +1,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.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,0 +1,9 @@ | ||
1 … | +module.exports = { | |
2 … | + 'edit': require('./edit'), | |
3 … | + 'image': require('./image'), | |
4 … | + 'link': require('./link'), | |
5 … | + 'name': require('./name'), | |
6 … | + 'profile': require('./profile'), | |
7 … | + 'avatar': require('./avatar') | |
8 … | +} | |
9 … | + |
modules_basic/avatar/link.js | ||
---|---|---|
@@ -1,0 +1,24 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | + | |
3 … | +exports.needs = { | |
4 … | + signifier: 'first' | |
5 … | +} | |
6 … | + | |
7 … | +exports.gives = 'avatar_link' | |
8 … | + | |
9 … | +exports.create = function (api) { | |
10 … | + return function (id, element) { | |
11 … | + | |
12 … | + var link = h('a.avatar', {href: "#"+id, title: id}, element) | |
13 … | + | |
14 … | + api.signifier(id, function (_, names) { | |
15 … | + if(names.length) | |
16 … | + link.title = names[0].name + '\n '+id | |
17 … | + }) | |
18 … | + | |
19 … | + return link | |
20 … | + } | |
21 … | +} | |
22 … | + | |
23 … | + | |
24 … | + |
modules_basic/avatar/name.js | ||
---|---|---|
@@ -1,0 +1,29 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | + | |
3 … | +exports.needs = { | |
4 … | + signifier: 'first' | |
5 … | +} | |
6 … | + | |
7 … | +exports.gives = 'avatar_name' | |
8 … | + | |
9 … | +exports.create = function (api) { | |
10 … | + | |
11 … | + return function name (id) { | |
12 … | + var n = h('span', id ? id.substring(0, 10) : "") | |
13 … | + | |
14 … | + //choose the most popular name for this person. | |
15 … | + //for anything like this you'll see I have used sbot.links2 | |
16 … | + //which is the ssb-links plugin. as you'll see the query interface | |
17 … | + //is pretty powerful! | |
18 … | + //TODO: "most popular" name is easily gameable. | |
19 … | + //must come up with something better than this. | |
20 … | + | |
21 … | + api.signifier(id, function (_, names) { | |
22 … | + if(names.length) n.textContent = names[0].name | |
23 … | + }) | |
24 … | + | |
25 … | + return n | |
26 … | + } | |
27 … | + | |
28 … | +} | |
29 … | + |
modules_basic/avatar/profile.js | ||
---|---|---|
@@ -1,0 +1,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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,102 +1,0 @@ | ||
1 | -'use strict' | |
2 | -var h = require('hyperscript') | |
3 | -var visualize = require('visualize-buffer') | |
4 | - | |
5 | -var pull = require('pull-stream') | |
6 | - | |
7 | -var self_id = require('../keys').id | |
8 | - | |
9 | -exports.needs = { | |
10 | - sbot_query: 'first', | |
11 | - blob_url: 'first' | |
12 | -} | |
13 | - | |
14 | -exports.gives = { | |
15 | - connection_status: true, avatar_image: true | |
16 | -} | |
17 | - | |
18 | -var ready = false | |
19 | -var waiting = [] | |
20 | - | |
21 | -var last = 0 | |
22 | - | |
23 | -var cache = {} | |
24 | - | |
25 | -exports.create = function (api) { | |
26 | - var avatars = {} | |
27 | - | |
28 | - //blah blah | |
29 | - return { | |
30 | - connection_status: function (err) { | |
31 | - if (err) return | |
32 | - pull( | |
33 | - api.sbot_query({ | |
34 | - query: [{ | |
35 | - $filter: { | |
36 | - timestamp: {$gt: last || 0 }, | |
37 | - value: { content: { | |
38 | - type: "about", | |
39 | - about: {$prefix: "@"}, | |
40 | - image: {link: {$prefix: "&"}} | |
41 | - }} | |
42 | - }}, | |
43 | - { | |
44 | - $map: { | |
45 | - id: ["value", "content", "about"], | |
46 | - image: ["value", "content", "image", "link"], | |
47 | - by: ["value", "author"], | |
48 | - ts: 'timestamp' | |
49 | - }}], | |
50 | - live: true | |
51 | - }), | |
52 | - pull.drain(function (a) { | |
53 | - if(a.sync) { | |
54 | - ready = true | |
55 | - while(waiting.length) waiting.shift()() | |
56 | - return | |
57 | - } | |
58 | - last = a.ts | |
59 | - //set image for avatar. | |
60 | - //overwrite another avatar | |
61 | - //you picked. | |
62 | - if( | |
63 | - //if there is no avatar | |
64 | - (!avatars[a.id]) || | |
65 | - //if i chose this avatar | |
66 | - (a.by == self_id) || | |
67 | - //they chose their own avatar, | |
68 | - //and current avatar was not chosen by me | |
69 | - (a.by === a.id && avatars[a.id].by != self_id) | |
70 | - ) | |
71 | - avatars[a.id] = a | |
72 | - | |
73 | - }) | |
74 | - ) | |
75 | - }, | |
76 | - | |
77 | - avatar_image: function (author, classes) { | |
78 | - classes = classes || '' | |
79 | - if(classes && 'string' === typeof classes) classes = '.avatar--'+classes | |
80 | - | |
81 | - function gen (id) { | |
82 | - if(cache[id]) return h('img', {src: cache[id]}) | |
83 | - var img = visualize(new Buffer(author.substring(1), 'base64'), 256) | |
84 | - cache[id] = img.src | |
85 | - return img | |
86 | - } | |
87 | - | |
88 | - var img = ready && avatars[author] ? h('img', {src: api.blob_url(avatars[author].image)}) : gen(author) | |
89 | - | |
90 | - ;(classes || '').split('.').filter(Boolean).forEach(function (c) { | |
91 | - img.classList.add(c) | |
92 | - }) | |
93 | - | |
94 | - if(!ready) | |
95 | - waiting.push(function () { | |
96 | - if(avatars[author]) img.src = api.blob_url(avatars[author].image) | |
97 | - }) | |
98 | - | |
99 | - return img | |
100 | - } | |
101 | - } | |
102 | -} |
modules_basic/avatar-link.js | ||
---|---|---|
@@ -1,22 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | - | |
3 | -exports.needs = {signifier: 'first'} | |
4 | - | |
5 | -exports.gives = 'avatar_link' | |
6 | - | |
7 | -exports.create = function (api) { | |
8 | - return function (id, element) { | |
9 | - | |
10 | - var link = h('a.avatar', {href: "#"+id, title: id}, element) | |
11 | - | |
12 | - api.signifier(id, function (_, names) { | |
13 | - if(names.length) | |
14 | - link.title = names[0].name + '\n '+id | |
15 | - }) | |
16 | - | |
17 | - return link | |
18 | - } | |
19 | -} | |
20 | - | |
21 | - | |
22 | - |
modules_basic/avatar-name.js | ||
---|---|---|
@@ -1,27 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | - | |
3 | -exports.needs = { signifier: 'first' } | |
4 | - | |
5 | -exports.gives = 'avatar_name' | |
6 | - | |
7 | -exports.create = function (api) { | |
8 | - | |
9 | - return function name (id) { | |
10 | - var n = h('span', id ? id.substring(0, 10) : "") | |
11 | - | |
12 | - //choose the most popular name for this person. | |
13 | - //for anything like this you'll see I have used sbot.links2 | |
14 | - //which is the ssb-links plugin. as you'll see the query interface | |
15 | - //is pretty powerful! | |
16 | - //TODO: "most popular" name is easily gameable. | |
17 | - //must come up with something better than this. | |
18 | - | |
19 | - api.signifier(id, function (_, names) { | |
20 | - if(names.length) n.textContent = names[0].name | |
21 | - }) | |
22 | - | |
23 | - return n | |
24 | - } | |
25 | - | |
26 | -} | |
27 | - |
modules_basic/emoji.js | ||
---|---|---|
@@ -1,0 +1,18 @@ | ||
1 … | +var emojis = require('emoji-named-characters') | |
2 … | +var emojiNames = Object.keys(emojis) | |
3 … | + | |
4 … | +exports.needs = { blob_url: 'first' } | |
5 … | +exports.gives = { emoji_names: true, emoji_url: true } | |
6 … | + | |
7 … | +exports.create = function (api) { | |
8 … | + return { | |
9 … | + emoji_names: function () { | |
10 … | + return emojiNames | |
11 … | + }, | |
12 … | + emoji_url: function (emoji) { | |
13 … | + return emoji in emojis && | |
14 … | + api.blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png' | |
15 … | + } | |
16 … | + } | |
17 … | +} | |
18 … | + |
modules_basic/avatar-profile.js | ||
---|---|---|
@@ -1,79 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | -var pull = require('pull-stream') | |
3 | - | |
4 | -exports.needs = { | |
5 | - avatar_image_link: 'first', | |
6 | - avatar_action: 'map', | |
7 | - avatar_edit: 'first', | |
8 | - follows: 'first', | |
9 | - followers: 'first' | |
10 | -} | |
11 | - | |
12 | -exports.gives = 'avatar_profile' | |
13 | - | |
14 | -function streamToList(stream, el) { | |
15 | - pull( | |
16 | - stream, | |
17 | - pull.drain(function (item) { | |
18 | - if(item) el.appendChild(item) | |
19 | - }) | |
20 | - ) | |
21 | - return el | |
22 | -} | |
23 | - | |
24 | -exports.create = function (api) { | |
25 | - | |
26 | - function image_link (id) { | |
27 | - return api.avatar_image_link(id, 'thumbnail') | |
28 | - } | |
29 | - | |
30 | - return function (id) { | |
31 | - | |
32 | - var follows_el = h('div.profile__follows.wrap') | |
33 | - var friends_el = h('div.profile__friendss.wrap') | |
34 | - var followers_el = h('div.profile__followers.wrap') | |
35 | - var a, b | |
36 | - | |
37 | - pull(api.follows(id), pull.unique(), pull.collect(function (err, ary) { | |
38 | - a = ary || []; next() | |
39 | - })) | |
40 | - pull(api.followers(id), pull.unique(), pull.collect(function (err, ary) { | |
41 | - b = ary || {}; next() | |
42 | - })) | |
43 | - | |
44 | - function next () { | |
45 | - if(!(a && b)) return | |
46 | - var _c = [], _a = [], _b = [] | |
47 | - | |
48 | - a.forEach(function (id) { | |
49 | - if(!~b.indexOf(id)) _a.push(id) | |
50 | - else _c.push(id) | |
51 | - }) | |
52 | - b.forEach(function (id) { | |
53 | - if(!~_c.indexOf(id)) _b.push(id) | |
54 | - }) | |
55 | - function add (ary, el) { | |
56 | - ary.forEach(function (id) { el.appendChild(image_link(id)) }) | |
57 | - } | |
58 | - | |
59 | - add(_a, follows_el) | |
60 | - add(_c, friends_el) | |
61 | - add(_b, followers_el) | |
62 | - } | |
63 | - | |
64 | - | |
65 | - return h('div.column.profile', | |
66 | - api.avatar_edit(id), | |
67 | - api.avatar_action(id), | |
68 | - h('div.profile__relationships.column', | |
69 | - h('strong', 'follows'), | |
70 | - follows_el, | |
71 | - h('strong', 'friends'), | |
72 | - friends_el, | |
73 | - h('strong', 'followers'), | |
74 | - followers_el | |
75 | - ) | |
76 | - ) | |
77 | - } | |
78 | - | |
79 | -} |
modules_basic/avatar.js | ||
---|---|---|
@@ -1,43 +1,0 @@ | ||
1 | - | |
2 | -exports.needs = { | |
3 | - avatar_name: 'first', | |
4 | - avatar_image: 'first', | |
5 | - avatar_link: 'first' | |
6 | -} | |
7 | - | |
8 | -exports.gives = { | |
9 | - avatar: true, | |
10 | - avatar_image_name_link: true, | |
11 | - avatar_image_link: true, | |
12 | - avatar_name_link: true | |
13 | -} | |
14 | - | |
15 | -exports.create = function (api) { | |
16 | - return { | |
17 | - avatar, | |
18 | - avatar_image_name_link, | |
19 | - avatar_image_link, | |
20 | - avatar_name_link | |
21 | - } | |
22 | - | |
23 | - function avatar (author, classes) { | |
24 | - return exports.avatar_image_name_link(author, classes) | |
25 | - } | |
26 | - | |
27 | - function avatar_image_name_link (author, classes) { | |
28 | - return api.avatar_link(author, [ | |
29 | - api.avatar_image(author, classes), | |
30 | - api.avatar_name(author) | |
31 | - ]) | |
32 | - } | |
33 | - | |
34 | - function avatar_image_link (author, classes) { | |
35 | - return api.avatar_link(author, api.avatar_image(author, classes)) | |
36 | - } | |
37 | - | |
38 | - function avatar_name_link (author, classes) { | |
39 | - return api.avatar_link(author, api.avatar_name(author)) | |
40 | - } | |
41 | -} | |
42 | - | |
43 | - |
modules_basic/follow.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,0 +1,13 @@ | ||
1 … | +const test = require('tape') | |
2 … | +const combine = require('depject') | |
3 … | + | |
4 … | +process.env.ssb_appname = 'test' | |
5 … | + | |
6 … | +const core = require('../modules_core') | |
7 … | +const basic = require('./') | |
8 … | + | |
9 … | +test('modules_basic has no outside deps', t => { | |
10 … | + t.ok(combine(basic, core)) | |
11 … | + t.end() | |
12 … | +}) | |
13 … | + |
modules_basic/message-author.js | ||
---|---|---|
@@ -1,39 +1,0 @@ | ||
1 | -const fs = require('fs') | |
2 | -const h = require('../h') | |
3 | -const { when }= require('@mmckegg/mutant') | |
4 | - | |
5 | -exports.needs = { | |
6 | - avatar_link: 'first', | |
7 | - avatar_image: 'first', | |
8 | - avatar_name: 'first', | |
9 | - timestamp: 'first' | |
10 | -} | |
11 | - | |
12 | -exports.gives = { | |
13 | - message_author: true, | |
14 | - mcss: true | |
15 | -} | |
16 | - | |
17 | -exports.create = function (api) { | |
18 | - return { | |
19 | - message_author, | |
20 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
21 | - } | |
22 | - | |
23 | - function message_author (msg, opts = {}) { | |
24 | - var { size = 'small' } = opts | |
25 | - var { value } = msg | |
26 | - var { author } = value | |
27 | - | |
28 | - return h('MessageAuthor', { | |
29 | - className: `-${size}` | |
30 | - }, [ | |
31 | - when(size !== 'mini', | |
32 | - h('section -image', api.avatar_link(author, api.avatar_image(author, 'thumbnail'))) | |
33 | - ), | |
34 | - h('section -name', api.avatar_link(author, api.avatar_name(author))), | |
35 | - h('section -timestamp', api.timestamp(msg)) | |
36 | - ]) | |
37 | - } | |
38 | -} | |
39 | - |
modules_basic/message-author.mcss | ||
---|---|---|
@@ -1,38 +1,0 @@ | ||
1 | -MessageAuthor { | |
2 | - display: flex | |
3 | - flex-direction: column | |
4 | - | |
5 | - section { | |
6 | - -image { | |
7 | - margin-bottom: .3rem | |
8 | - } | |
9 | - | |
10 | - -name { | |
11 | - max-width: 7rem | |
12 | - a { $textPrimary } | |
13 | - } | |
14 | - | |
15 | - -timestamp { | |
16 | - | |
17 | - } | |
18 | - } | |
19 | - | |
20 | - -mini { | |
21 | - flex-direction: row | |
22 | - | |
23 | - section { | |
24 | - margin-right: .5rem | |
25 | - | |
26 | - -name { | |
27 | - position: initial | |
28 | - left: initial | |
29 | - min-width: 6.5rem | |
30 | - max-width: none | |
31 | - } | |
32 | - | |
33 | - -timestamp { | |
34 | - | |
35 | - } | |
36 | - } | |
37 | - } | |
38 | -} |
modules_basic/markdown.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,48 +1,0 @@ | ||
1 | -const fs = require('fs') | |
2 | -const h = require('../h') | |
3 | - | |
4 | -exports.needs = { | |
5 | - message_name: 'first' | |
6 | -} | |
7 | - | |
8 | -exports.gives = { | |
9 | - message_backlinks: true, | |
10 | - mcss: true | |
11 | -} | |
12 | - | |
13 | -exports.create = function (api) { | |
14 | - return { | |
15 | - message_backlinks, | |
16 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
17 | - } | |
18 | - | |
19 | - function message_backlinks (msg) { | |
20 | - var links = [] | |
21 | - for(var k in CACHE) { | |
22 | - var _msg = CACHE[k] | |
23 | - var mentions = _msg.content.mentions | |
24 | - | |
25 | - if(Array.isArray(mentions)) { | |
26 | - for(var i = 0; i < mentions.length; i++) | |
27 | - if(mentions[i].link == msg.key) | |
28 | - links.push(k) | |
29 | - } | |
30 | - } | |
31 | - | |
32 | - if (links.length === 0) return null | |
33 | - | |
34 | - var hrefList = h('ul') | |
35 | - links.forEach(link => { | |
36 | - api.message_name(link, (err, name) => { | |
37 | - if (err) throw err | |
38 | - hrefList.appendChild(h('li', | |
39 | - h('a -backlink', { href: `#${link}` }, name) | |
40 | - )) | |
41 | - }) | |
42 | - }) | |
43 | - return h('MessageBacklinks', [ | |
44 | - h('header', 'backlinks:'), | |
45 | - hrefList | |
46 | - ]) | |
47 | - } | |
48 | -} |
modules_basic/message/author.js | ||
---|---|---|
@@ -1,0 +1,39 @@ | ||
1 … | +const fs = require('fs') | |
2 … | +const h = require('../../h') | |
3 … | +const { when }= require('@mmckegg/mutant') | |
4 … | + | |
5 … | +exports.needs = { | |
6 … | + avatar_link: 'first', | |
7 … | + avatar_image: 'first', | |
8 … | + avatar_name: 'first', | |
9 … | + timestamp: 'first' | |
10 … | +} | |
11 … | + | |
12 … | +exports.gives = { | |
13 … | + message_author: true, | |
14 … | + mcss: true | |
15 … | +} | |
16 … | + | |
17 … | +exports.create = function (api) { | |
18 … | + return { | |
19 … | + message_author, | |
20 … | + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
21 … | + } | |
22 … | + | |
23 … | + function message_author (msg, opts = {}) { | |
24 … | + var { size = 'small' } = opts | |
25 … | + var { value } = msg | |
26 … | + var { author } = value | |
27 … | + | |
28 … | + return h('MessageAuthor', { | |
29 … | + className: `-${size}` | |
30 … | + }, [ | |
31 … | + when(size !== 'mini', | |
32 … | + h('section -image', api.avatar_link(author, api.avatar_image(author, 'thumbnail'))) | |
33 … | + ), | |
34 … | + h('section -name', api.avatar_link(author, api.avatar_name(author))), | |
35 … | + h('section -timestamp', api.timestamp(msg)) | |
36 … | + ]) | |
37 … | + } | |
38 … | +} | |
39 … | + |
modules_basic/message/author.mcss | ||
---|---|---|
@@ -1,0 +1,38 @@ | ||
1 … | +MessageAuthor { | |
2 … | + display: flex | |
3 … | + flex-direction: column | |
4 … | + | |
5 … | + section { | |
6 … | + -image { | |
7 … | + margin-bottom: .3rem | |
8 … | + } | |
9 … | + | |
10 … | + -name { | |
11 … | + max-width: 7rem | |
12 … | + a { $textPrimary } | |
13 … | + } | |
14 … | + | |
15 … | + -timestamp { | |
16 … | + | |
17 … | + } | |
18 … | + } | |
19 … | + | |
20 … | + -mini { | |
21 … | + flex-direction: row | |
22 … | + | |
23 … | + section { | |
24 … | + margin-right: .5rem | |
25 … | + | |
26 … | + -name { | |
27 … | + position: initial | |
28 … | + left: initial | |
29 … | + min-width: 6.5rem | |
30 … | + max-width: none | |
31 … | + } | |
32 … | + | |
33 … | + -timestamp { | |
34 … | + | |
35 … | + } | |
36 … | + } | |
37 … | + } | |
38 … | +} |
modules_basic/message/backlinks.js | ||
---|---|---|
@@ -1,0 +1,48 @@ | ||
1 … | +const fs = require('fs') | |
2 … | +const h = require('../../h') | |
3 … | + | |
4 … | +exports.needs = { | |
5 … | + message_name: 'first' | |
6 … | +} | |
7 … | + | |
8 … | +exports.gives = { | |
9 … | + message_backlinks: true, | |
10 … | + mcss: true | |
11 … | +} | |
12 … | + | |
13 … | +exports.create = function (api) { | |
14 … | + return { | |
15 … | + message_backlinks, | |
16 … | + mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
17 … | + } | |
18 … | + | |
19 … | + function message_backlinks (msg) { | |
20 … | + var links = [] | |
21 … | + for(var k in CACHE) { | |
22 … | + var _msg = CACHE[k] | |
23 … | + var mentions = _msg.content.mentions | |
24 … | + | |
25 … | + if(Array.isArray(mentions)) { | |
26 … | + for(var i = 0; i < mentions.length; i++) | |
27 … | + if(mentions[i].link == msg.key) | |
28 … | + links.push(k) | |
29 … | + } | |
30 … | + } | |
31 … | + | |
32 … | + if (links.length === 0) return null | |
33 … | + | |
34 … | + var hrefList = h('ul') | |
35 … | + links.forEach(link => { | |
36 … | + api.message_name(link, (err, name) => { | |
37 … | + if (err) throw err | |
38 … | + hrefList.appendChild(h('li', | |
39 … | + h('a -backlink', { href: `#${link}` }, name) | |
40 … | + )) | |
41 … | + }) | |
42 … | + }) | |
43 … | + return h('MessageBacklinks', [ | |
44 … | + h('header', 'backlinks:'), | |
45 … | + hrefList | |
46 … | + ]) | |
47 … | + } | |
48 … | +} |
modules_basic/message/backlinks.mcss | ||
---|---|---|
@@ -1,0 +1,21 @@ | ||
1 … | +MessageBacklinks { | |
2 … | + font-size: .9rem | |
3 … | + margin-top: .5rem | |
4 … | + | |
5 … | + header { | |
6 … | + $textSubtle | |
7 … | + } | |
8 … | + | |
9 … | + ul { | |
10 … | + padding-left: 1rem | |
11 … | + | |
12 … | + li { | |
13 … | + a { | |
14 … | + -backlink { | |
15 … | + $textSubtle | |
16 … | + | |
17 … | + } | |
18 … | + } | |
19 … | + } | |
20 … | + } | |
21 … | +} |
modules_basic/message/confirm.js | ||
---|---|---|
@@ -1,0 +1,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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,0 +1,8 @@ | ||
1 … | +module.exports = { | |
2 … | + 'author': require('./author'), | |
3 … | + 'backlinks': require('./backlinks'), | |
4 … | + 'confirm': require('./confirm'), | |
5 … | + 'link': require('./link'), | |
6 … | + 'name': require('./name'), | |
7 … | + 'render': require('./render'), | |
8 … | +} |
modules_basic/message/link.js | ||
---|---|---|
@@ -1,0 +1,35 @@ | ||
1 … | +var h = require('hyperscript') | |
2 … | +var ref = require('ssb-ref') | |
3 … | + | |
4 … | +exports.needs = { | |
5 … | + message_name: 'first' | |
6 … | +} | |
7 … | + | |
8 … | +exports.gives = 'message_link' | |
9 … | + | |
10 … | +exports.create = function (api) { | |
11 … | + | |
12 … | + return function (id) { | |
13 … | + | |
14 … | + if('string' !== typeof id) | |
15 … | + throw new Error('link must be to message id') | |
16 … | + | |
17 … | + var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...') | |
18 … | + | |
19 … | + if(ref.isMsg(id)) | |
20 … | + api.message_name(id, function (err, name) { | |
21 … | + if(err) console.error(err) | |
22 … | + else link.textContent = name | |
23 … | + }) | |
24 … | + | |
25 … | + return link | |
26 … | + } | |
27 … | +} | |
28 … | + | |
29 … | + | |
30 … | + | |
31 … | + | |
32 … | + | |
33 … | + | |
34 … | + | |
35 … | + |
modules_basic/message/name.js | ||
---|---|---|
@@ -1,0 +1,26 @@ | ||
1 … | + | |
2 … | +function title (s) { | |
3 … | + var m = /^\n*([^\n]{0,40})/.exec(s) | |
4 … | + return m && (m[1].length == 40 ? m[1]+'...' : m[1]) | |
5 … | +} | |
6 … | + | |
7 … | +exports.needs = { sbot_get: 'first' } | |
8 … | +exports.gives = 'message_name' | |
9 … | + | |
10 … | +//TODO: rewrite as observable? | |
11 … | + | |
12 … | +exports.create = function (api) { | |
13 … | + return function (id, cb) { | |
14 … | + api.sbot_get(id, function (err, value) { | |
15 … | + if(err && err.name == 'NotFoundError') | |
16 … | + return cb(null, id.substring(0, 10)+'...(missing)') | |
17 … | + if(value.content.type === 'post' && 'string' === typeof value.content.text) | |
18 … | + return cb(null, title(value.content.text)) | |
19 … | + else if('string' === typeof value.content.text) | |
20 … | + return cb(null, value.content.type + ':'+title(value.content.text)) | |
21 … | + else | |
22 … | + return cb(null, id.substring(0, 10)+'...') | |
23 … | + }) | |
24 … | + } | |
25 … | +} | |
26 … | + |
modules_basic/message/render.js | ||
---|---|---|
@@ -1,0 +1,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.mcss | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -1,21 +1,0 @@ | ||
1 | -MessageBacklinks { | |
2 | - font-size: .9rem | |
3 | - margin-top: .5rem | |
4 | - | |
5 | - header { | |
6 | - $textSubtle | |
7 | - } | |
8 | - | |
9 | - ul { | |
10 | - padding-left: 1rem | |
11 | - | |
12 | - li { | |
13 | - a { | |
14 | - -backlink { | |
15 | - $textSubtle | |
16 | - | |
17 | - } | |
18 | - } | |
19 | - } | |
20 | - } | |
21 | -} |
modules_basic/message-link.js | ||
---|---|---|
@@ -1,35 +1,0 @@ | ||
1 | -var h = require('hyperscript') | |
2 | -var ref = require('ssb-ref') | |
3 | - | |
4 | -exports.needs = { | |
5 | - message_name: 'first' | |
6 | -} | |
7 | - | |
8 | -exports.gives = 'message_link' | |
9 | - | |
10 | -exports.create = function (api) { | |
11 | - | |
12 | - return function (id) { | |
13 | - | |
14 | - if('string' !== typeof id) | |
15 | - throw new Error('link must be to message id') | |
16 | - | |
17 | - var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...') | |
18 | - | |
19 | - if(ref.isMsg(id)) | |
20 | - api.message_name(id, function (err, name) { | |
21 | - if(err) console.error(err) | |
22 | - else link.textContent = name | |
23 | - }) | |
24 | - | |
25 | - return link | |
26 | - } | |
27 | -} | |
28 | - | |
29 | - | |
30 | - | |
31 | - | |
32 | - | |
33 | - | |
34 | - | |
35 | - |
modules_basic/message-name.js | ||
---|---|---|
@@ -1,26 +1,0 @@ | ||
1 | - | |
2 | -function title (s) { | |
3 | - var m = /^\n*([^\n]{0,40})/.exec(s) | |
4 | - return m && (m[1].length == 40 ? m[1]+'...' : m[1]) | |
5 | -} | |
6 | - | |
7 | -exports.needs = { sbot_get: 'first' } | |
8 | -exports.gives = 'message_name' | |
9 | - | |
10 | -//TODO: rewrite as observable? | |
11 | - | |
12 | -exports.create = function (api) { | |
13 | - return function (id, cb) { | |
14 | - api.sbot_get(id, function (err, value) { | |
15 | - if(err && err.name == 'NotFoundError') | |
16 | - return cb(null, id.substring(0, 10)+'...(missing)') | |
17 | - if(value.content.type === 'post' && 'string' === typeof value.content.text) | |
18 | - return cb(null, title(value.content.text)) | |
19 | - else if('string' === typeof value.content.text) | |
20 | - return cb(null, value.content.type + ':'+title(value.content.text)) | |
21 | - else | |
22 | - return cb(null, id.substring(0, 10)+'...') | |
23 | - }) | |
24 | - } | |
25 | -} | |
26 | - |
modules_basic/message.js | ||
---|---|---|
@@ -1,87 +1,0 @@ | ||
1 | -const fs = require('fs') | |
2 | -const h = require('../h') | |
3 | - | |
4 | -exports.needs = { | |
5 | - avatar_name: 'first', | |
6 | - avatar_link: 'first', | |
7 | - message_action: 'map', | |
8 | - message_author: 'first', | |
9 | - message_backlinks: 'first', | |
10 | - message_content: 'first', | |
11 | - message_content_mini: 'first', | |
12 | - message_title: 'first', | |
13 | - message_link: 'first', | |
14 | - message_meta: 'map', | |
15 | -} | |
16 | - | |
17 | -exports.gives = { | |
18 | - message_render: true, | |
19 | - mcss: true | |
20 | -} | |
21 | - | |
22 | -exports.create = function (api) { | |
23 | - return { | |
24 | - message_render, | |
25 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
26 | - } | |
27 | - | |
28 | - function message_render (msg) { | |
29 | - var content = api.message_content_mini(msg) | |
30 | - if (content) return mini(msg, content) | |
31 | - | |
32 | - content = api.message_content(msg) | |
33 | - if (!content) return mini(msg, message_content_mini_fallback(msg)) | |
34 | - | |
35 | - var msgEl = h('Message', { | |
36 | - 'ev-keydown': navigateToMessageOnEnter, | |
37 | - attributes: { | |
38 | - tabindex: '0' | |
39 | - } | |
40 | - }, [ | |
41 | - h('header.author', api.message_author(msg)), | |
42 | - h('section.title', api.message_title(msg)), | |
43 | - h('section.meta', api.message_meta(msg)), | |
44 | - h('section.content', content), | |
45 | - h('section.action', api.message_action(msg)), | |
46 | - h('footer.backlinks', api.message_backlinks(msg)) | |
47 | - ]) | |
48 | - return msgEl | |
49 | - | |
50 | - function navigateToMessageOnEnter (ev) { | |
51 | - // on enter, hit first meta. | |
52 | - if(ev.keyCode == 13) { | |
53 | - | |
54 | - // unless in an input | |
55 | - if (ev.target.nodeName === 'INPUT' | |
56 | - || ev.target.nodeName === 'TEXTAREA') return | |
57 | - | |
58 | - // HACK! (mw) | |
59 | - // there's no exported api to open a new tab. :/ | |
60 | - // it's only done in `app.js` module in an`onhashchange` handler. | |
61 | - // sooooooo yeah this shit for now :) | |
62 | - var wtf = h('a', { href: `#${msg.key}` }) | |
63 | - msgEl.appendChild(wtf) | |
64 | - wtf.click() | |
65 | - msgEl.removeChild(wtf) | |
66 | - } | |
67 | - } | |
68 | - } | |
69 | - | |
70 | - function mini(msg, el) { | |
71 | - return h('Message -mini', { | |
72 | - attributes: { | |
73 | - tabindex: '0' | |
74 | - } | |
75 | - }, [ | |
76 | - h('header.author', api.message_author(msg, { size: 'mini' })), | |
77 | - h('section.meta', api.message_meta(msg)), | |
78 | - h('section.content', el) | |
79 | - ]) | |
80 | - } | |
81 | -} | |
82 | - | |
83 | - | |
84 | -function message_content_mini_fallback(msg) { | |
85 | - return h('code', msg.value.content.type) | |
86 | -} | |
87 | - |
modules_basic/message.mcss | ||
---|---|---|
@@ -1,80 +1,0 @@ | ||
1 | -Message { | |
2 | - padding: 1rem .5rem 1rem 7.5rem | |
3 | - border-top: solid 1px gainsboro | |
4 | - min-height: 5rem | |
5 | - | |
6 | - position: relative | |
7 | - display: flex | |
8 | - flex-direction: row | |
9 | - flex-wrap: wrap | |
10 | - justify-content: flex-end | |
11 | - | |
12 | - header.author { | |
13 | - position: absolute | |
14 | - left: .5rem | |
15 | - } | |
16 | - | |
17 | - section.title { | |
18 | - flex-grow: 1 | |
19 | - font-size: .9rem | |
20 | - } | |
21 | - | |
22 | - section.meta { | |
23 | - display: flex | |
24 | - a { | |
25 | - margin-left: .2rem | |
26 | - $textSubtle | |
27 | - } | |
28 | - | |
29 | - input{ | |
30 | - margin-right: 0 | |
31 | - order: 99 | |
32 | - } | |
33 | - } | |
34 | - | |
35 | - section.content { | |
36 | - flex-basis: 100% | |
37 | - | |
38 | - (img) { | |
39 | - max-width: 100% | |
40 | - } | |
41 | - } | |
42 | - | |
43 | - section.action { | |
44 | - flex-basis: 100% | |
45 | - display: flex | |
46 | - justify-content: flex-end | |
47 | - | |
48 | - a { | |
49 | - margin-left: .5em | |
50 | - } | |
51 | - } | |
52 | - | |
53 | - footer.backlinks { | |
54 | - flex-basis: 100% | |
55 | - } | |
56 | - | |
57 | - | |
58 | - -mini { | |
59 | - font-size: .9rem | |
60 | - justify-content: flex-start | |
61 | - padding: .25rem .5rem | |
62 | - min-height: inherit | |
63 | - | |
64 | - header.author { | |
65 | - order: 0 | |
66 | - position: initial | |
67 | - left: initial | |
68 | - } | |
69 | - | |
70 | - section.content { | |
71 | - order: 1 | |
72 | - flex-basis: initial | |
73 | - flex-grow: 1 | |
74 | - } | |
75 | - | |
76 | - section.meta { | |
77 | - order: 2 | |
78 | - } | |
79 | - } | |
80 | -} |
modules_basic/names.js | ||
---|---|---|
@@ -1,179 +1,0 @@ | ||
1 | -var pull = require('pull-stream') | |
2 | -var many = require('pull-many') | |
3 | -var mfr = require('map-filter-reduce') | |
4 | - | |
5 | -function all(stream, cb) { | |
6 | - pull(stream, pull.collect(cb)) | |
7 | -} | |
8 | - | |
9 | -exports.needs = { | |
10 | - sbot_links2: 'first', | |
11 | - sbot_query: 'first' | |
12 | -} | |
13 | - | |
14 | -exports.gives = { | |
15 | - connection_status: true, | |
16 | - signifier: true, | |
17 | - signified: true, | |
18 | -} | |
19 | - | |
20 | -/* | |
21 | - filter(rel: ['mentions', prefix('@')]) | reduce(name: rel[1], value: count()) | |
22 | -*/ | |
23 | - | |
24 | -var filter = { | |
25 | - $filter: { | |
26 | - rel: ["mentions", {$prefix: "@"}] | |
27 | - } | |
28 | -} | |
29 | -var map = { | |
30 | - $map: { | |
31 | - name: ['rel', 1], | |
32 | - id: 'dest', | |
33 | - ts: 'ts', | |
34 | - } | |
35 | -} | |
36 | - | |
37 | -var reduce = { | |
38 | - $reduce: { | |
39 | - name: 'name', | |
40 | - id: 'id', | |
41 | - rank: {$count: true}, | |
42 | - ts: {$max: 'ts'} | |
43 | - } | |
44 | -} | |
45 | - | |
46 | -var filter2 = { | |
47 | - $filter: { | |
48 | - value: { | |
49 | - content: { | |
50 | - type: "about", | |
51 | - name: {"$prefix": ""}, | |
52 | - about: {"$prefix": ""} | |
53 | - } | |
54 | - } | |
55 | - } | |
56 | -} | |
57 | - | |
58 | -var map2 = { | |
59 | - $map: { | |
60 | - name: ["value", "content", "name"], | |
61 | - id: ['value', 'content', 'about'], | |
62 | - ts: "timestamp" | |
63 | - } | |
64 | -} | |
65 | - | |
66 | -//union with this query... | |
67 | - | |
68 | -var names = NAMES = [] | |
69 | -function update(name) { | |
70 | - var n = names.find(function (e) { | |
71 | - return e.id == name.id && e.name == e.name | |
72 | - }) | |
73 | - if(!n) { | |
74 | - name.rank = 1 | |
75 | - //this should be inserted at the right place... | |
76 | - names.push(name) | |
77 | - } | |
78 | - else | |
79 | - n.rank = n.rank += (name.rank || 1) | |
80 | -} | |
81 | - | |
82 | -var ready = false, waiting = [] | |
83 | - | |
84 | -var merge = { | |
85 | - $reduce: { | |
86 | - name: 'name', | |
87 | - id: 'id', | |
88 | - rank: {$sum: 'rank'}, | |
89 | - ts: {$max: 'ts'} | |
90 | - } | |
91 | -} | |
92 | - | |
93 | -function add_sigil(stream) { | |
94 | - return pull(stream, pull.map(function (e) { | |
95 | - if (e && e.id && e.name && e.id[0] !== e.name[0]) | |
96 | - e.name = e.id[0] + e.name | |
97 | - return e | |
98 | - }) | |
99 | - ) | |
100 | -} | |
101 | - | |
102 | -var queryNamedGitRepos = [ | |
103 | - {$filter: { | |
104 | - value: { | |
105 | - content: { | |
106 | - type: "git-repo", | |
107 | - name: {"$prefix": ""} | |
108 | - } | |
109 | - } | |
110 | - }}, | |
111 | - {$map: { | |
112 | - name: ["value", "content", "name"], | |
113 | - id: ['key'], | |
114 | - ts: "timestamp" | |
115 | - }}, | |
116 | - reduce | |
117 | -] | |
118 | -exports.create = function (api) { | |
119 | - | |
120 | - var exports = {} | |
121 | - exports.connection_status = function (err) { | |
122 | - if(!err) { | |
123 | - pull( | |
124 | - many([ | |
125 | - api.sbot_links2({query: [filter, map, reduce]}), | |
126 | - add_sigil(api.sbot_query({query: [filter2, map2, reduce]})), | |
127 | - add_sigil(api.sbot_query({query: queryNamedGitRepos})) | |
128 | - ]), | |
129 | - //reducing also ensures order by the lookup properties | |
130 | - //in this case: [name, id] | |
131 | - mfr.reduce(merge), | |
132 | - pull.collect(function (err, ary) { | |
133 | - if(!err) { | |
134 | - NAMES = names = ary | |
135 | - ready = true | |
136 | - while(waiting.length) waiting.shift()() | |
137 | - } | |
138 | - }) | |
139 | - ) | |
140 | - | |
141 | - pull(many([ | |
142 | - api.sbot_links2({query: [filter, map], old: false}), | |
143 | - add_sigil(api.sbot_query({query: [filter2, map2], old: false})), | |
144 | - add_sigil(api.sbot_query({query: queryNamedGitRepos, old: false})) | |
145 | - ]), | |
146 | - pull.drain(update)) | |
147 | - } | |
148 | - } | |
149 | - | |
150 | - function async(fn) { | |
151 | - return function (value, cb) { | |
152 | - function go () { cb(null, fn(value)) } | |
153 | - if(ready) go() | |
154 | - else waiting.push(go) | |
155 | - } | |
156 | - } | |
157 | - | |
158 | - function rank(ary) { | |
159 | - //sort by most used, or most recently used | |
160 | - return ary.sort(function (a, b) { return b.rank - a.rank || b.ts - a.ts }) | |
161 | - } | |
162 | - | |
163 | - //we are just iterating over the entire array. | |
164 | - //if this becomes a problem, maintain two arrays | |
165 | - //one of each sort order, but do not duplicate the objects. | |
166 | - //that should mean the space required is just 2x object references, | |
167 | - //not 2x objects, and we can use binary search to find matches. | |
168 | - | |
169 | - exports.signifier = async(function (id) { | |
170 | - return rank(names.filter(function (e) { return e.id == id})) | |
171 | - }) | |
172 | - | |
173 | - exports.signified = async(function (name) { | |
174 | - var rx = new RegExp('^'+name) | |
175 | - return rank(names.filter(function (e) { return rx.test(e.name) })) | |
176 | - }) | |
177 | - | |
178 | - return exports | |
179 | -} |
modules_basic/scroller.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,68 +1,0 @@ | ||
1 | -'use strict' | |
2 | -var cont = require('cont') | |
3 | -var h = require('hyperscript') | |
4 | -var suggest = require('suggest-box') | |
5 | - | |
6 | -exports.needs = { | |
7 | - sbot_query: 'first', sbot_links2: 'first', | |
8 | - suggest_search: 'map' //REWRITE | |
9 | -} | |
10 | - | |
11 | -exports.gives = 'search_box' | |
12 | - | |
13 | -exports.create = function (api) { | |
14 | - | |
15 | - return function (go) { | |
16 | - | |
17 | - var suggestBox | |
18 | - var search = h('input.searchprompt', { | |
19 | - type: 'search', | |
20 | - placeholder: 'Commands', | |
21 | - onkeydown: function (ev) { | |
22 | - switch (ev.keyCode) { | |
23 | - case 13: // enter | |
24 | - if (suggestBox && suggestBox.active) { | |
25 | - suggestBox.complete() | |
26 | - ev.stopPropagation() | |
27 | - } | |
28 | - if (go(search.value.trim(), !ev.ctrlKey)) | |
29 | - search.blur() | |
30 | - return | |
31 | - case 27: // escape | |
32 | - ev.preventDefault() | |
33 | - search.blur() | |
34 | - return | |
35 | - } | |
36 | - } | |
37 | - }) | |
38 | - | |
39 | - search.activate = function (sigil, ev) { | |
40 | - search.focus() | |
41 | - ev.preventDefault() | |
42 | - if (search.value[0] === sigil) { | |
43 | - search.selectionStart = 1 | |
44 | - search.selectionEnd = search.value.length | |
45 | - } else { | |
46 | - search.value = sigil | |
47 | - } | |
48 | - } | |
49 | - | |
50 | - // delay until the element has a parent | |
51 | - setTimeout(function () { | |
52 | - suggestBox = suggest(search, function (word, cb) { | |
53 | - cont.para(api.suggest_search(word)) | |
54 | - (function (err, ary) { | |
55 | - if(err) return cb(err) | |
56 | - | |
57 | - cb(null, ary.filter(Boolean).reduce(function (a, b) { | |
58 | - return a.concat(b) | |
59 | - }, [])) | |
60 | - }) | |
61 | - }, {}) | |
62 | - }, 10) | |
63 | - | |
64 | - return search | |
65 | - } | |
66 | - | |
67 | -} | |
68 | - |
modules_basic/suggest-mentions.js | ||
---|---|---|
@@ -1,63 +1,0 @@ | ||
1 | - | |
2 | -exports.needs = { | |
3 | - sbot_links2: 'first', | |
4 | - blob_url: 'first', | |
5 | - signified: 'first', | |
6 | - builtin_tabs: 'map' | |
7 | -} | |
8 | - | |
9 | -exports.gives = { | |
10 | - suggest_mentions: true, | |
11 | - suggest_search: true | |
12 | -} | |
13 | - | |
14 | -exports.create = function (api) { | |
15 | - | |
16 | - return { | |
17 | - suggest_mentions: function (word) { | |
18 | - return function (cb) { | |
19 | - if(!/^[%&@]\w/.test(word)) return cb() | |
20 | - | |
21 | - api.signified(word, function (err, names) { | |
22 | - if(err) cb(err) | |
23 | - else cb(null, names.map(function (e) { | |
24 | - return { | |
25 | - title: e.name + ': ' + e.id.substring(0,10)+' ('+e.rank+')', | |
26 | - value: '['+e.name+']('+e.id+')', | |
27 | - rank: e.rank, | |
28 | - //TODO: avatar images... | |
29 | - } | |
30 | - })) | |
31 | - }) | |
32 | - } | |
33 | - }, | |
34 | - | |
35 | - suggest_search: function (query) { | |
36 | - return function (cb) { | |
37 | - if(/^[@%]\w/.test(query)) { | |
38 | - api.signified(query, function (_, names) { | |
39 | - cb(null, names.map(function (e) { | |
40 | - return { | |
41 | - title: e.name + ':'+e.id.substring(0, 10), | |
42 | - value: e.id, | |
43 | - subtitle: e.rank, | |
44 | - rank: e.rank | |
45 | - } | |
46 | - })) | |
47 | - }) | |
48 | - | |
49 | - } else if(/^\//.test(query)) { | |
50 | - var tabs = [].concat.apply([], api.builtin_tabs()) | |
51 | - cb(null, tabs.filter(function (name) { | |
52 | - return name.substr(0, query.length) === query | |
53 | - }).map(function (name) { | |
54 | - return { | |
55 | - title: name, | |
56 | - value: name, | |
57 | - } | |
58 | - })) | |
59 | - } else cb() | |
60 | - } | |
61 | - } | |
62 | - } | |
63 | -} |
modules_core/app.js | ||
---|---|---|
@@ -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') | |
3 | 5 … | |
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 … | +} | |
15 | 10 … | |
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 … | +} | |
22 | 15 … | |
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 … | + } | |
26 | 21 … | |
27 | - var view = api.screen_view(hash() || 'tabs') | |
22 … | + function app () { | |
23 … | + process.nextTick(() => insertCss(api.styles())) | |
28 | 24 … | |
29 | - var screen = h('div.screen.column', view) | |
25 … | + var view = Value(getView()) | |
26 … | + var screen = h('App', view) | |
30 | 27 … | |
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) | |
34 | 30 … | |
35 | - if(_view) screen.replaceChild(view, _view) | |
36 | - else document.body.appendChild(view) | |
37 | - } | |
31 … | + window.addEventListener('error', window.onError = displayError) | |
38 | 32 … | |
39 | - document.body.appendChild(screen) | |
33 … | + return screen | |
34 … | + } | |
40 | 35 … | |
41 | - return screen | |
42 | - } | |
36 … | + function getView () { | |
37 … | + const view = window.location.hash.substring(1) || 'tabs' | |
38 … | + return api.screen_view(view) | |
43 | 39 … | } |
44 | 40 … | } |
45 | 41 … | |
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 … | +} | |
46 | 58 … |
modules_core/external-confirm.js | ||
---|---|---|
@@ -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') | |
4 | 5 … | |
5 | -exports.gives = 'external_confirm' | |
6 … | +exports.gives = { | |
7 … | + external_confirm: true, | |
8 … | + mcss:true | |
9 … | +} | |
6 | 10 … | |
7 | 11 … | 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) { | |
9 | 18 … | var lb = lightbox() |
10 | 19 … | document.body.appendChild(lb) |
11 | 20 … | |
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 … | + ) | |
16 | 28 … | |
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 … | + ) | |
20 | 35 … | |
21 | 36 … | okay.addEventListener('keydown', function (ev) { |
22 | 37 … | if (ev.keyCode === 27) cancel.click() // escape |
23 | 38 … | }) |
24 | 39 … | |
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) | |
35 | 45 … | ]), |
36 | - h('div.row', [ | |
37 | - okay, | |
38 | - cancel | |
39 | - ]) | |
40 | - )) | |
46 … | + h('section.actions', [cancel, okay]) | |
47 … | + ])) | |
41 | 48 … | |
42 | 49 … | okay.focus() |
43 | 50 … | } |
44 | 51 … | } |
modules_core/index.js | ||
---|---|---|
@@ -1,15 +1,16 @@ | ||
1 | 1 … | module.exports = { |
2 | -// "_screen_view.js": require('./_screen_view.js'), | |
3 | - "app.js": require('./app.js'), | |
4 | - "blob-url.js": require('./blob-url.js'), | |
5 | - "crypto.js": require('./crypto.js'), | |
6 | - "external-confirm.js": require('./external-confirm.js'), | |
7 | - "file-input.js": require('./file-input.js'), | |
8 | - "menu.js": require('./menu.js'), | |
9 | - "message-confirm.js": require('./message-confirm.js'), | |
10 | - "tabs.js": require('./tabs.js'), | |
11 | - "sbot.js": require('./sbot.js'), | |
12 | - "styles.js": require('./styles.js'), | |
13 | - "style-mixins.js": require('./style-mixins.js') | |
2 … | + 'app': require('./app'), | |
3 … | + 'blob-url': require('./blob-url'), | |
4 … | + 'crypto': require('./crypto'), | |
5 … | + 'external-confirm': require('./external-confirm'), | |
6 … | + 'file-input': require('./file-input'), | |
7 … | + 'menu': require('./menu'), | |
8 … | + 'names': require('./names'), | |
9 … | + 'tabs': require('./tabs'), | |
10 … | + 'sbot': require('./sbot'), | |
11 … | + 'search-box': require('./search-box'), | |
12 … | + 'style': require('./style'), | |
13 … | + 'suggest-box': require('./suggest-box'), | |
14 … | + 'suggest-mentions': require('./suggest-mentions') | |
14 | 15 … | } |
15 | 16 … |
modules_core/menu.js | ||
---|---|---|
@@ -1,23 +1,28 @@ | ||
1 | -var h = require('hyperscript') | |
1 … | +const h = require('hyperscript') | |
2 | 2 … | |
3 | 3 … | module.exports = { |
4 | - needs: {menu_items: 'map'}, | |
5 | - gives: {connection_status: true, menu: true}, | |
4 … | + needs: { | |
5 … | + menu_items: 'map' | |
6 … | + }, | |
7 … | + gives: { | |
8 … | + connection_status: true, | |
9 … | + menu: true, | |
10 … | + menu_items: true | |
11 … | + }, | |
6 | 12 … | create: function (api) { |
13 … | + const { menu_items } = api | |
7 | 14 … | |
8 | - var menu_items = api.menu_items | |
9 | - | |
10 | 15 … | var status = h('div.status.error') //start off disconnected |
11 | 16 … | var list = h('div.menu.column', {style: 'display: none;'}) |
12 | 17 … | |
13 | - var menu = h('div.column', status, list , { | |
14 | - onmouseover: function (e) { | |
15 | - list.style.display = 'flex' | |
16 | - }, onmouseout: function () { | |
17 | - list.style.display = 'none' | |
18 | - } | |
19 | - }) | |
18 … | + var menu = h('div.column', { | |
19 … | + onmouseover: () => list.style.display = 'flex', | |
20 … | + onmouseout: () => list.style.display = 'none' | |
21 … | + }, [ | |
22 … | + status, | |
23 … | + list | |
24 … | + ]) | |
20 | 25 … | |
21 | 26 … | setTimeout(function () { |
22 | 27 … | menu_items().forEach(function (el) { |
23 | 28 … | if(el) |
@@ -25,15 +30,14 @@ | ||
25 | 30 … | }) |
26 | 31 … | }, 0) |
27 | 32 … | |
28 | 33 … | return { |
29 | - connection_status: function (err) { | |
34 … | + connection_status: (err) => { | |
30 | 35 … | if(err) status.classList.add('error') |
31 | 36 … | else status.classList.remove('error') |
32 | 37 … | }, |
33 | - menu: function () { | |
34 | - return menu | |
35 | - } | |
38 … | + menu: () => menu, | |
39 … | + menu_items: () => null | |
36 | 40 … | } |
37 | 41 … | } |
38 | 42 … | } |
39 | 43 … |
modules_core/sbot.js | ||
---|---|---|
@@ -60,8 +60,14 @@ | ||
60 | 60 … | var opts = createConfig() |
61 | 61 … | var sbot = null |
62 | 62 … | var connection_status = [] |
63 | 63 … | |
64 … | + var rec = { | |
65 … | + sync: () => {}, | |
66 … | + async: () => {}, | |
67 … | + source: () => {}, | |
68 … | + } | |
69 … | + | |
64 | 70 … | var rec = Reconnect(function (isConn) { |
65 | 71 … | function notify (value) { |
66 | 72 … | isConn(value); api.connection_status(value) //.forEach(function (fn) { fn(value) }) |
67 | 73 … | } |
modules_core/tabs.js | ||
---|---|---|
@@ -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') | |
5 | 6 … | |
6 | 7 … | function ancestor (el) { |
7 | 8 … | if(!el) return |
8 | 9 … | if(el.tagName !== 'A') return ancestor(el.parentElement) |
9 | 10 … | return el |
10 | 11 … | } |
11 | 12 … | |
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 … | +} | |
13 | 20 … | |
14 | 21 … | exports.gives = 'screen_view' |
15 | 22 … | |
16 | 23 … | exports.create = function (api) { |
17 | 24 … | return function (path) { |
18 | - if(path !== 'tabs') | |
19 | - return | |
25 … | + if(path !== 'tabs') return | |
20 | 26 … | |
21 | 27 … | 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) | |
25 | 29 … | if(search) |
26 | 30 … | if(ids.length > 1) |
27 | - search.value = 'split('+ids.join(',')+')' | |
31 … | + search.input.value = 'split('+ids.join(',')+')' | |
28 | 32 … | else |
29 | - search.value = ids[0] | |
33 … | + search.input.value = ids[0] | |
30 | 34 … | } |
31 | 35 … | |
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) => { | |
37 | 38 … | if(tabs.has(path)) { |
38 | 39 … | tabs.select(path) |
39 | 40 … | return true |
40 | 41 … | } |
41 | - var el = api.screen_view(path) | |
42 | 42 … | |
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 | |
50 | 51 … | }) |
51 | 52 … | |
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 … | + ) | |
58 | 60 … | |
59 | 61 … | var saved = [] |
60 | 62 … | // try { saved = JSON.parse(localStorage.openTabs) } |
61 | 63 … | // catch (_) { } |
@@ -67,13 +69,14 @@ | ||
67 | 69 … | var el = api.screen_view(path) |
68 | 70 … | if(!el) return |
69 | 71 … | el.id = el.id || path |
70 | 72 … | if (!el) return |
71 | - el.scroll = keyscroll(el.querySelector('.scroller__content')) | |
73 … | + el.scroll = keyscroll(el.querySelector('.Scroller .\\.content')) | |
72 | 74 … | if(el) tabs.add(el, false, false) |
73 | 75 … | }) |
74 | 76 … | |
75 | 77 … | tabs.select(0) |
78 … | + search.input.value = null // start with an empty field to show placeholder | |
76 | 79 … | |
77 | 80 … | //handle link clicks |
78 | 81 … | window.onclick = function (ev) { |
79 | 82 … | var link = ancestor(ev.target) |
@@ -95,32 +98,42 @@ | ||
95 | 98 … | |
96 | 99 … | var el = api.screen_view(path) |
97 | 100 … | if(el) { |
98 | 101 … | el.id = el.id || path |
99 | - el.scroll = keyscroll(el.querySelector('.scroller__content')) | |
102 … | + el.scroll = keyscroll(el.querySelector('.Scroller .\\.content')) | |
100 | 103 … | tabs.add(el, !ev.ctrlKey, !!ev.shiftKey) |
101 | 104 … | // localStorage.openTabs = JSON.stringify(tabs.tabs) |
102 | 105 … | } |
103 | 106 … | |
104 | 107 … | return false |
105 | 108 … | } |
106 | 109 … | |
110 … | + var gPressed = false | |
107 | 111 … | window.addEventListener('keydown', function (ev) { |
108 | 112 … | if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA') |
109 | 113 … | 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 … | + | |
110 | 124 … | switch(ev.keyCode) { |
111 | - | |
112 | 125 … | // scroll through tabs |
113 | 126 … | case 72: // h |
114 | 127 … | return tabs.selectRelative(-1) |
115 | 128 … | case 76: // l |
116 | 129 … | return tabs.selectRelative(1) |
117 | 130 … | |
118 | 131 … | // scroll through messages |
119 | 132 … | case 74: // j |
120 | - return tabs.get(tabs.selected[0]).scroll(1) | |
133 … | + return tabs.get(tabs.selected[0]).firstChild.scroll(1) | |
121 | 134 … | case 75: // k |
122 | - return tabs.get(tabs.selected[0]).scroll(-1) | |
135 … | + return tabs.get(tabs.selected[0]).firstChild.scroll(-1) | |
123 | 136 … | |
124 | 137 … | // close a tab |
125 | 138 … | case 88: // x |
126 | 139 … | if (tabs.selected) { |
@@ -159,47 +172,53 @@ | ||
159 | 172 … | } |
160 | 173 … | }) |
161 | 174 … | |
162 | 175 … | // 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() | |
171 | 180 … | |
172 | 181 … | // remove loader error handler |
173 | 182 … | if (window.onError) { |
174 | 183 … | window.removeEventListener('error', window.onError) |
175 | 184 … | delete window.onError |
176 | 185 … | } |
177 | 186 … | |
178 | 187 … | // 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 | |
181 | 190 … | if(!tabs.has('errors')) |
182 | 191 … | tabs.add(errors, false) |
183 | - var el = h('div.message', | |
192 … | + const el = h('div.message', [ | |
184 | 193 … | h('strong', err.message), |
185 | - h('pre', err.stack)) | |
194 … | + h('pre', err.stack) | |
195 … | + ]) | |
186 | 196 … | if (errorsContent.firstChild) |
187 | 197 … | errorsContent.insertBefore(el, errorsContent.firstChild) |
188 | 198 … | else |
189 | 199 … | errorsContent.appendChild(el) |
190 | 200 … | }) |
191 | 201 … | |
192 | 202 … | 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 => { | |
194 | 214 … | 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() | |
199 | 218 … | menu.append(new MenuItem({ |
200 | 219 … | label: 'Inspect Element', |
201 | - click: function () { | |
220 … | + click: () => { | |
202 | 221 … | remote.getCurrentWindow().inspectElement(ev.x, ev.y) |
203 | 222 … | } |
204 | 223 … | })) |
205 | 224 … | menu.popup(remote.getCurrentWindow()) |
modules_core/app.mcss | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,73 +1,0 @@ | ||
1 | -var fs = require('fs') | |
2 | -var lightbox = require('hyperlightbox') | |
3 | -var h = require('../h') | |
4 | -var self_id = require('../keys').id | |
5 | -//publish or add | |
6 | - | |
7 | -exports.needs = { | |
8 | - publish: 'first', | |
9 | - message_render: 'first', | |
10 | - avatar: 'first', | |
11 | - message_meta: 'map' | |
12 | -} | |
13 | - | |
14 | -exports.gives = { | |
15 | - message_confirm: true, | |
16 | - mcss: true | |
17 | -} | |
18 | - | |
19 | -exports.create = function (api) { | |
20 | - return { | |
21 | - message_confirm, | |
22 | - mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') | |
23 | - } | |
24 | - | |
25 | - function message_confirm (content, cb) { | |
26 | - | |
27 | - cb = cb || function () {} | |
28 | - | |
29 | - var lb = lightbox() | |
30 | - document.body.appendChild(lb) | |
31 | - | |
32 | - var msg = { | |
33 | - key: "DRAFT", | |
34 | - value: { | |
35 | - author: self_id, | |
36 | - previous: null, | |
37 | - sequence: null, | |
38 | - timestamp: Date.now(), | |
39 | - content: content | |
40 | - } | |
41 | - } | |
42 | - | |
43 | - var okay = h('button', { | |
44 | - 'ev-click': () => { | |
45 | - lb.remove() | |
46 | - api.publish(content, cb) | |
47 | - }}, | |
48 | - 'okay' | |
49 | - ) | |
50 | - | |
51 | - var cancel = h('button', { | |
52 | - 'ev-click': () => { | |
53 | - lb.remove() | |
54 | - cb(null) | |
55 | - }}, | |
56 | - 'Cancel' | |
57 | - ) | |
58 | - | |
59 | - okay.addEventListener('keydown', function (ev) { | |
60 | - if(ev.keyCode === 27) cancel.click() //escape | |
61 | - }) | |
62 | - | |
63 | - lb.show(h('MessageConfirm', [ | |
64 | - h('header -preview_description', h('h1', 'Preview')), | |
65 | - h('section -message_preview', api.message_render(msg)), | |
66 | - h('section -actions', [okay, cancel]) | |
67 | - ] | |
68 | - )) | |
69 | - | |
70 | - okay.focus() | |
71 | - } | |
72 | -} | |
73 | - |
modules_core/message-confirm.mcss | ||
---|---|---|
@@ -1,39 +1,0 @@ | ||
1 | -MessageConfirm { | |
2 | - section { | |
3 | - -preview_description { | |
4 | - } | |
5 | - | |
6 | - -message_preview { | |
7 | - background-color: white | |
8 | - | |
9 | - div { | |
10 | - border: none | |
11 | - | |
12 | - header.author { | |
13 | - div { | |
14 | - section { | |
15 | - -timestamp { | |
16 | - display: none | |
17 | - } | |
18 | - } | |
19 | - } | |
20 | - } | |
21 | - | |
22 | - section.action { | |
23 | - display: none | |
24 | - } | |
25 | - } | |
26 | - } | |
27 | - | |
28 | - -actions { | |
29 | - margin-top: 1rem | |
30 | - display: flex | |
31 | - justify-content: flex-end | |
32 | - | |
33 | - button { | |
34 | - margin: 0 0 0 1rem | |
35 | - } | |
36 | - } | |
37 | - } | |
38 | -} | |
39 | - |
modules_core/index.test.js | ||
---|---|---|
@@ -1,0 +1,12 @@ | ||
1 … | +const test = require('tape') | |
2 … | +const combine = require('depject') | |
3 … | + | |
4 … | +process.env.ssb_appname = 'test' | |
5 … | + | |
6 … | +const core = require('./') | |
7 … | + | |
8 … | +test('modules_core has no outside deps', t => { | |
9 … | + t.ok(combine(core)) | |
10 … | + t.end() | |
11 … | +}) | |
12 … | + |
modules_core/style-mixins.js | ||
---|---|---|
@@ -1,22 +1,0 @@ | ||
1 | - | |
2 | -const mixins = ` | |
3 | - $textPrimary { | |
4 | - color: #222 | |
5 | - } | |
6 | - | |
7 | - $textSubtle { | |
8 | - color: gray | |
9 | - } | |
10 | -` | |
11 | - | |
12 | -module.exports = { | |
13 | - gives: { | |
14 | - mcss: true | |
15 | - }, | |
16 | - create: function (api) { | |
17 | - return { | |
18 | - mcss: () => mixins | |
19 | - } | |
20 | - } | |
21 | -} | |
22 | - |
modules_core/names.js | ||
---|---|---|
@@ -1,0 +1,179 @@ | ||
1 … | +var pull = require('pull-stream') | |
2 … | +var many = require('pull-many') | |
3 … | +var mfr = require('map-filter-reduce') | |
4 … | + | |
5 … | +function all(stream, cb) { | |
6 … | + pull(stream, pull.collect(cb)) | |
7 … | +} | |
8 … | + | |
9 … | +exports.needs = { | |
10 … | + sbot_links2: 'first', | |
11 … | + sbot_query: 'first' | |
12 … | +} | |
13 … | + | |
14 … | +exports.gives = { | |
15 … | + connection_status: true, | |
16 … | + signifier: true, | |
17 … | + signified: true, | |
18 … | +} | |
19 … | + | |
20 … | +/* | |
21 … | + filter(rel: ['mentions', prefix('@')]) | reduce(name: rel[1], value: count()) | |
22 … | +*/ | |
23 … | + | |
24 … | +var filter = { | |
25 … | + $filter: { | |
26 … | + rel: ["mentions", {$prefix: "@"}] | |
27 … | + } | |
28 … | +} | |
29 … | +var map = { | |
30 … | + $map: { | |
31 … | + name: ['rel', 1], | |
32 … | + id: 'dest', | |
33 … | + ts: 'ts', | |
34 … | + } | |
35 … | +} | |
36 … | + | |
37 … | +var reduce = { | |
38 … | + $reduce: { | |
39 … | + name: 'name', | |
40 … | + id: 'id', | |
41 … | + rank: {$count: true}, | |
42 … | + ts: {$max: 'ts'} | |
43 … | + } | |
44 … | +} | |
45 … | + | |
46 … | +var filter2 = { | |
47 … | + $filter: { | |
48 … | + value: { | |
49 … | + content: { | |
50 … | + type: "about", | |
51 … | + name: {"$prefix": ""}, | |
52 … | + about: {"$prefix": ""} | |
53 … | + } | |
54 … | + } | |
55 … | + } | |
56 … | +} | |
57 … | + | |
58 … | +var map2 = { | |
59 … | + $map: { | |
60 … | + name: ["value", "content", "name"], | |
61 … | + id: ['value', 'content', 'about'], | |
62 … | + ts: "timestamp" | |
63 … | + } | |
64 … | +} | |
65 … | + | |
66 … | +//union with this query... | |
67 … | + | |
68 … | +var names = NAMES = [] | |
69 … | +function update(name) { | |
70 … | + var n = names.find(function (e) { | |
71 … | + return e.id == name.id && e.name == e.name | |
72 … | + }) | |
73 … | + if(!n) { | |
74 … | + name.rank = 1 | |
75 … | + //this should be inserted at the right place... | |
76 … | + names.push(name) | |
77 … | + } | |
78 … | + else | |
79 … | + n.rank = n.rank += (name.rank || 1) | |
80 … | +} | |
81 … | + | |
82 … | +var ready = false, waiting = [] | |
83 … | + | |
84 … | +var merge = { | |
85 … | + $reduce: { | |
86 … | + name: 'name', | |
87 … | + id: 'id', | |
88 … | + rank: {$sum: 'rank'}, | |
89 … | + ts: {$max: 'ts'} | |
90 … | + } | |
91 … | +} | |
92 … | + | |
93 … | +function add_sigil(stream) { | |
94 … | + return pull(stream, pull.map(function (e) { | |
95 … | + if (e && e.id && e.name && e.id[0] !== e.name[0]) | |
96 … | + e.name = e.id[0] + e.name | |
97 … | + return e | |
98 … | + }) | |
99 … | + ) | |
100 … | +} | |
101 … | + | |
102 … | +var queryNamedGitRepos = [ | |
103 … | + {$filter: { | |
104 … | + value: { | |
105 … | + content: { | |
106 … | + type: "git-repo", | |
107 … | + name: {"$prefix": ""} | |
108 … | + } | |
109 … | + } | |
110 … | + }}, | |
111 … | + {$map: { | |
112 … | + name: ["value", "content", "name"], | |
113 … | + id: ['key'], | |
114 … | + ts: "timestamp" | |
115 … | + }}, | |
116 … | + reduce | |
117 … | +] | |
118 … | +exports.create = function (api) { | |
119 … | + | |
120 … | + var exports = {} | |
121 … | + exports.connection_status = function (err) { | |
122 … | + if(!err) { | |
123 … | + pull( | |
124 … | + many([ | |
125 … | + api.sbot_links2({query: [filter, map, reduce]}), | |
126 … | + add_sigil(api.sbot_query({query: [filter2, map2, reduce]})), | |
127 … | + add_sigil(api.sbot_query({query: queryNamedGitRepos})) | |
128 … | + ]), | |
129 … | + //reducing also ensures order by the lookup properties | |
130 … | + //in this case: [name, id] | |
131 … | + mfr.reduce(merge), | |
132 … | + pull.collect(function (err, ary) { | |
133 … | + if(!err) { | |
134 … | + NAMES = names = ary | |
135 … | + ready = true | |
136 … | + while(waiting.length) waiting.shift()() | |
137 … | + } | |
138 … | + }) | |
139 … | + ) | |
140 … | + | |
141 … | + pull(many([ | |
142 … | + api.sbot_links2({query: [filter, map], old: false}), | |
143 … | + add_sigil(api.sbot_query({query: [filter2, map2], old: false})), | |
144 … | + add_sigil(api.sbot_query({query: queryNamedGitRepos, old: false})) | |
145 … | + ]), | |
146 … | + pull.drain(update)) | |
147 … | + } | |
148 … | + } | |
149 … | + | |
150 … | + function async(fn) { | |
151 … | + return function (value, cb) { | |
152 … | + function go () { cb(null, fn(value)) } | |
153 … | + if(ready) go() | |
154 … | + else waiting.push(go) | |
155 … | + } | |
156 … | + } | |
157 … | + | |
158 … | + function rank(ary) { | |
159 … | + //sort by most used, or most recently used | |
160 … | + return ary.sort(function (a, b) { return b.rank - a.rank || b.ts - a.ts }) | |
161 … | + } | |
162 … | + | |
163 … | + //we are just iterating over the entire array. | |
164 … | + //if this becomes a problem, maintain two arrays | |
165 … | + //one of each sort order, but do not duplicate the objects. | |
166 … | + //that should mean the space required is just 2x object references, | |
167 … | + //not 2x objects, and we can use binary search to find matches. | |
168 … | + | |
169 … | + exports.signifier = async(function (id) { | |
170 … | + return rank(names.filter(function (e) { return e.id == id})) | |
171 … | + }) | |
172 … | + | |
173 … | + exports.signified = async(function (name) { | |
174 … | + var rx = new RegExp('^'+name) | |
175 … | + return rank(names.filter(function (e) { return rx.test(e.name) })) | |
176 … | + }) | |
177 … | + | |
178 … | + return exports | |
179 … | +} |
modules_core/styles.js | ||
---|---|---|
@@ -1,33 +1,0 @@ | ||
1 | -var compile = require('micro-css') | |
2 | -var fs = require('fs') | |
3 | -var Path = require('path') | |
4 | - | |
5 | -// TODO distribute these styles across all | |
6 | -// the relevant modules, not as a core style. | |
7 | -var coreStyle = fs.readFileSync(Path.join(__dirname, '../style.css')) | |
8 | - | |
9 | -module.exports = { | |
10 | - needs: { | |
11 | - mcss: 'map', | |
12 | - css: 'map' | |
13 | - }, | |
14 | - gives: { | |
15 | - mcss: true, | |
16 | - css: true, | |
17 | - styles: true | |
18 | - }, | |
19 | - create: function (api) { | |
20 | - var styles = '' | |
21 | - process.nextTick(function () { | |
22 | - var mcss = api.mcss().join('\n') | |
23 | - var css = api.css().join('\n') | |
24 | - styles = coreStyle + compile(mcss) + css | |
25 | - }) | |
26 | - return { | |
27 | - styles: function () { return styles }, | |
28 | - // export empty styles | |
29 | - mcss: function () { return '' }, | |
30 | - css: function () { return '' } | |
31 | - } | |
32 | - } | |
33 | -} |
modules_core/search-box.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,0 +1,33 @@ | ||
1 … | +var compile = require('micro-css') | |
2 … | +var fs = require('fs') | |
3 … | +var Path = require('path') | |
4 … | + | |
5 … | +// TODO distribute these styles across all | |
6 … | +// the relevant modules, not as a core style. | |
7 … | +var coreStyle = fs.readFileSync(Path.join(__dirname, '../../style.css')) | |
8 … | + | |
9 … | +module.exports = { | |
10 … | + needs: { | |
11 … | + mcss: 'map', | |
12 … | + css: 'map' | |
13 … | + }, | |
14 … | + gives: { | |
15 … | + mcss: true, | |
16 … | + css: true, | |
17 … | + styles: true | |
18 … | + }, | |
19 … | + create: function (api) { | |
20 … | + var styles = '' | |
21 … | + process.nextTick(function () { | |
22 … | + var mcss = api.mcss().join('\n') | |
23 … | + var css = api.css().join('\n') | |
24 … | + styles = coreStyle + compile(mcss) + css | |
25 … | + }) | |
26 … | + return { | |
27 … | + styles: function () { return styles }, | |
28 … | + // export empty styles | |
29 … | + mcss: function () { return '' }, | |
30 … | + css: function () { return '' } | |
31 … | + } | |
32 … | + } | |
33 … | +} |
modules_core/suggest-box.css | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -1,11 +1,12 @@ | ||
1 | 1 … | module.exports = { |
2 | - "_screen_view.js": require('../modules_core/_screen_view.js'), | |
3 | - "app.js": require('../modules_core/app.js'), | |
4 | - "blob-url.js": require('../modules_core/blob-url.js'), | |
5 | - "crypto.js": require('../modules_core/crypto.js'), | |
6 | - "file-input.js": require('../modules_core/file-input.js'), | |
7 | - "menu.js": require('../modules_core/menu.js'), | |
8 | - "message-confirm.js": require('../modules_core/message-confirm.js'), | |
9 | - "tabs.js": require('../modules_core/tabs.js'), | |
10 | - "sbot.js": require('./sbot.js') | |
2 … | + "_screen_view.": require('../modules_core/_screen_view.'), | |
3 … | + "app.": require('../modules_core/app.'), | |
4 … | + "blob-url.": require('../modules_core/blob-url.'), | |
5 … | + "crypto.": require('../modules_core/crypto.'), | |
6 … | + "file-input.": require('../modules_core/file-input.'), | |
7 … | + "menu.": require('../modules_core/menu.'), | |
8 … | + "message-confirm.": require('../modules_core/message-confirm.'), | |
9 … | + "tabs.": require('../modules_core/tabs.'), | |
10 … | + "sbot.": require('./sbot.') | |
11 | 11 … | } |
12 … | + |
modules_extra/blob.js | ||
---|---|---|
@@ -19,9 +19,9 @@ | ||
19 | 19 … | |
20 | 20 … | function screen_view (path) { |
21 | 21 … | if(!ref.isBlob(path)) return |
22 | 22 … | |
23 | - return h('Blob', [ | |
23 … | + return h('Blob', { id: path, title: path.slice(0, 9) + '...' }, [ | |
24 | 24 … | h('iframe', { |
25 | 25 … | src: api.blob_url(path), |
26 | 26 … | sandbox: '' |
27 | 27 … | }) |
modules_extra/channel.js | ||
---|---|---|
@@ -3,17 +3,21 @@ | ||
3 | 3 … | var Scroller = require('pull-scroll') |
4 | 4 … | var mfr = require('map-filter-reduce') |
5 | 5 … | |
6 | 6 … | exports.needs = { |
7 … | + build_scroller: 'first', | |
7 | 8 … | message_render: 'first', |
8 | 9 … | message_compose: 'first', |
9 | 10 … | sbot_log: 'first', |
10 | 11 … | sbot_query: 'first', |
11 | 12 … | } |
12 | 13 … | |
13 | 14 … | 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 | |
16 | 20 … | } |
17 | 21 … | |
18 | 22 … | exports.create = function (api) { |
19 | 23 … | |
@@ -26,91 +30,114 @@ | ||
26 | 30 … | rank: {$count: true} |
27 | 31 … | }} |
28 | 32 … | |
29 | 33 … | 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 … | + } | |
38 | 40 … | |
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 … | + } | |
47 | 49 … | |
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) | |
53 | 53 … | |
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 }) | |
59 | 56 … | |
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 | |
68 | 61 … | } |
69 | - }, | |
70 | 62 … | |
71 | - connection_status: function (err) { | |
72 | - if(err) return | |
73 | - | |
74 | - channels = [] | |
75 | - | |
76 | 63 … | 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) | |
82 | 67 … | ) |
83 | 68 … | |
84 | 69 … | 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) | |
95 | 74 … | ) |
96 | - }, | |
97 | 75 … | |
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 | |
103 | 100 … | }) |
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 … | + })) | |
113 | 122 … | } |
114 | 123 … | } |
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 … | + } | |
115 | 142 … | } |
116 | 143 … |
modules_extra/git-ssb.js | ||
---|---|---|
@@ -3,15 +3,17 @@ | ||
3 | 3 … | var pull = require('pull-stream') |
4 | 4 … | var Scroller = require('pull-scroll') |
5 | 5 … | |
6 | 6 … | exports.needs = { |
7 … | + build_scroller: 'first', | |
7 | 8 … | message_render: 'first', |
8 | 9 … | message_compose: 'first', |
9 | 10 … | sbot_log: 'first' |
10 | 11 … | } |
11 | 12 … | |
12 | 13 … | exports.gives = { |
13 | - menu_items: true, screen_view: true | |
14 … | + menu_items: true, | |
15 … | + screen_view: true | |
14 | 16 … | } |
15 | 17 … | |
16 | 18 … | exports.create = function (api) { |
17 | 19 … | return { |
@@ -21,29 +23,25 @@ | ||
21 | 23 … | |
22 | 24 … | screen_view: function (path, sbot) { |
23 | 25 … | if(path === '/git-ssb') { |
24 | 26 … | |
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() | |
30 | 28 … | |
31 | 29 … | pull( |
32 | 30 … | 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) | |
34 | 32 … | ) |
35 | 33 … | |
36 | 34 … | pull( |
37 | 35 … | u.next(api.sbot_log, {reverse: true, limit: 100, live: false}), |
38 | 36 … | pull.filter(function(msg) { return msg.value.content.type }), |
39 | 37 … | pull.filter(function(msg) { |
40 | 38 … | return msg.value.content.type.match(/^git/) |
41 | 39 … | }), |
42 | - Scroller(div, content, api.message_render, false, false) | |
40 … | + Scroller(container, content, api.message_render, false, false) | |
43 | 41 … | ) |
44 | 42 … | |
45 | - return div | |
43 … | + return container | |
46 | 44 … | } |
47 | 45 … | } |
48 | 46 … | } |
49 | 47 … | } |
modules_extra/git.js | ||
---|---|---|
@@ -455,27 +455,28 @@ | ||
455 | 455 … | var c = msg.value.content |
456 | 456 … | |
457 | 457 … | if(c.type === 'git-repo') { |
458 | 458 … | 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)) : '' | |
461 | 462 … | ]) |
462 | 463 … | } |
463 | 464 … | |
464 | 465 … | if(c.type === 'git-update') { |
465 | - return h('p', 'pushed to ', repoLink(c.repo)) | |
466 … | + return h('div', 'pushed to ', repoLink(c.repo)) | |
466 | 467 … | } |
467 | 468 … | |
468 | 469 … | if(c.type === 'issue-edit') { |
469 | 470 … | return |
470 | 471 … | } |
471 | 472 … | |
472 | 473 … | if(c.type === 'issue') { |
473 | - return h('p', 'opened issue on ', repoLink(c.project)) | |
474 … | + return h('div', 'opened issue on ', repoLink(c.project)) | |
474 | 475 … | } |
475 | 476 … | |
476 | 477 … | if(c.type === 'pull-request') { |
477 | - return h('p', 'opened pull-request ', [ | |
478 … | + return h('div', 'opened pull-request ', [ | |
478 | 479 … | 'to ', repoLink(c.repo), ':', c.branch, ' ', |
479 | 480 … | 'from ', repoLink(c.head_repo), ':', c.head_branch |
480 | 481 … | ]) |
481 | 482 … | } |
modules_extra/index.js | ||
---|---|---|
@@ -1,23 +1,24 @@ | ||
1 | 1 … | module.exports = { |
2 | - // "audio-mp3.js": require('./audio-mp3.js'), | |
3 | - "blob.js": require('./blob.js'), | |
4 | - "channel.js": require('./channel.js'), | |
5 | - "emoji.js": require('./emoji.js'), | |
6 | - "suggest-emoji.js": require('./suggest-emoji.js'), | |
7 | - "dns.js": require('./dns.js'), | |
8 | - "git.js": require('./git.js'), | |
9 | - "git-ssb.js": require('./git-ssb.js'), | |
10 | - "key.js": require('./key.js'), | |
11 | - "notifications.js": require('./notifications.js'), | |
12 | - "meta-image.js": require('./meta-image.js'), | |
13 | - "music-release-cc.js": require('./music-release-cc.js'), | |
14 | - "music-release.js": require('./music-release.js'), | |
15 | - "network.js": require('./network.js'), | |
16 | - "query.js": require('./query.js'), | |
17 | - "raw.js": require('./raw.js'), | |
18 | - "search.js": require('./search'), | |
19 | - "split.js": require('./split.js'), | |
20 | - "versions.js": require('./versions.js') | |
2 … | + // 'audio-mp3': require('./audio-mp3'), | |
3 … | + 'blob': require('./blob'), | |
4 … | + 'channel': require('./channel'), | |
5 … | + 'suggest-emoji': require('./suggest-emoji'), | |
6 … | + 'dns': require('./dns'), | |
7 … | + 'git': require('./git'), | |
8 … | + 'git-ssb': require('./git-ssb'), | |
9 … | + 'key': require('./key'), | |
10 … | + 'notifications': require('./notifications'), | |
11 … | + 'meta-image': require('./meta-image'), | |
12 … | + 'music': { | |
13 … | + 'release-cc': require('./music/release-cc'), | |
14 … | + 'release': require('./music/release') | |
15 … | + }, | |
16 … | + 'network': require('./network'), | |
17 … | + 'query': require('./query'), | |
18 … | + 'raw': require('./raw'), | |
19 … | + 'search': require('./search'), | |
20 … | + 'split': require('./split'), | |
21 … | + 'versions': require('./versions') | |
21 | 22 … | } |
22 | 23 … | |
23 | 24 … |
modules_extra/key.js | ||
---|---|---|
@@ -1,8 +1,9 @@ | ||
1 | 1 … | var h = require('hyperscript') |
2 | 2 … | |
3 | 3 … | exports.gives = { |
4 | - menu_items: true, screen_view: true | |
4 … | + menu_items: true, | |
5 … | + screen_view: true | |
5 | 6 … | } |
6 | 7 … | |
7 | 8 … | exports.create = function (api) { |
8 | 9 … | return { |
modules_extra/notifications.js | ||
---|---|---|
@@ -7,8 +7,9 @@ | ||
7 | 7 … | var cont = require('cont') |
8 | 8 … | var ref = require('ssb-ref') |
9 | 9 … | |
10 | 10 … | exports.needs = { |
11 … | + build_scroller: 'first', | |
11 | 12 … | message_render: 'first', |
12 | 13 … | sbot_log: 'first', |
13 | 14 … | sbot_get: 'first', |
14 | 15 … | sbot_user_feed: 'first', |
@@ -126,22 +127,16 @@ | ||
126 | 127 … | oldest = msg.value.timestamp |
127 | 128 … | } |
128 | 129 … | }) |
129 | 130 … | |
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() | |
137 | 132 … | |
138 | 133 … | pull( |
139 | 134 … | u.next(api.sbot_log, {old: false, limit: 100}), |
140 | 135 … | unbox(), |
141 | 136 … | notifications(ids), |
142 | 137 … | pull.filter(), |
143 | - Scroller(div, content, api.message_render, true, false) | |
138 … | + Scroller(container, content, api.message_render, true, false) | |
144 | 139 … | ) |
145 | 140 … | |
146 | 141 … | pull( |
147 | 142 … | u.next(api.sbot_log, {reverse: true, limit: 100, live: false}), |
@@ -151,12 +146,12 @@ | ||
151 | 146 … | pull.take(function (msg) { |
152 | 147 … | // abort stream after we pass the oldest messages of our feeds |
153 | 148 … | return !oldest ? true : msg.value.timestamp > oldest |
154 | 149 … | }), |
155 | - Scroller(div, content, api.message_render, false, false) | |
150 … | + Scroller(container, content, api.message_render, false, false) | |
156 | 151 … | ) |
157 | 152 … | |
158 | - return div | |
153 … | + return container | |
159 | 154 … | } |
160 | 155 … | } |
161 | 156 … | } |
162 | 157 … | } |
modules_extra/raw.js | ||
---|---|---|
@@ -1,40 +1,37 @@ | ||
1 | 1 … | var h = require('hyperscript') |
2 | 2 … | |
3 | -// from ssb-ref | |
4 | -var refRegex = /((?:@|%|&)[A-Za-z0-9\/+]{43}=\.[\w\d]+)/g | |
5 | - | |
6 | 3 … | exports.gives = 'message_meta' |
7 | 4 … | |
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 | -} | |
15 | 5 … | |
16 | 6 … | exports.create = function (api) { |
17 | 7 … | return function (msg) { |
18 | 8 … | var tmp = h('div') |
19 | 9 … | var el |
20 | 10 … | 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 … | + }, | |
24 | 22 … | onclick: function () { |
23 … | + clicked = !clicked | |
24 … | + | |
25 | 25 … | // HACK (mw) yo we need a better way to replace the content |
26 | 26 … | 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) { | |
29 | 29 … | // move away the content |
30 | 30 … | while (el = msgContentEl.firstChild) |
31 | 31 … | tmp.appendChild(el) |
32 | 32 … | // 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) ) | |
37 | 34 … | msgContentEl.appendChild(pre) |
38 | 35 … | } else { |
39 | 36 … | // hide the raw stuff |
40 | 37 … | msgContentEl.removeChild(pre) |
@@ -42,8 +39,45 @@ | ||
42 | 39 … | while (el = tmp.firstChild) |
43 | 40 … | msgContentEl.appendChild(el) |
44 | 41 … | } |
45 | 42 … | } |
46 | - }) | |
43 … | + }, symbol) | |
47 | 44 … | } |
48 | 45 … | } |
49 | 46 … | |
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.js | ||
---|---|---|
@@ -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') | |
6 | 8 … | |
7 | 9 … | exports.needs = { |
10 … | + build_scroller: 'first', | |
8 | 11 … | message_render: 'first', |
9 | 12 … | sbot_log: 'first', |
10 | 13 … | sbot_fulltext_search: 'first' |
11 | 14 … | } |
12 | 15 … | |
13 | -exports.gives = 'screen_view' | |
16 … | +exports.gives = { | |
17 … | + screen_view: true, | |
18 … | + mcss: true | |
19 … | +} | |
14 | 20 … | |
15 | 21 … | var whitespace = /\s+/ |
16 | 22 … | |
17 | 23 … | function andSearch(terms, inputs) { |
@@ -69,64 +75,69 @@ | ||
69 | 75 … | } |
70 | 76 … | |
71 | 77 … | exports.create = function (api) { |
72 | 78 … | |
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 … | + } | |
78 | 83 … | |
79 | - var total = 0, matches = 0 | |
80 | - var usingLinearSearch = false | |
84 … | + function screen_view (path) { | |
85 … | + if (path[0] !== '?') return | |
81 | 86 … | |
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 … | + ]) | |
90 | 101 … | ) |
102 … | + ]) | |
103 … | + var { container, content } = api.build_scroller({ prepend: searchHeader }) | |
104 … | + container.id = path // helps tabs find this tab | |
91 | 105 … | |
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 … | + } | |
101 | 113 … | |
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 … | + } | |
107 | 119 … | |
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 … | + ) | |
113 | 125 … | |
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 … | + ) | |
127 | 139 … | |
128 | - return div | |
129 | - } | |
140 … | + return container | |
130 | 141 … | } |
142 … | +} | |
131 | 143 … | |
132 | -} |
modules_extra/emoji.js | ||
---|---|---|
@@ -1,18 +1,0 @@ | ||
1 | -var emojis = require('emoji-named-characters') | |
2 | -var emojiNames = Object.keys(emojis) | |
3 | - | |
4 | -exports.needs = { blob_url: 'first' } | |
5 | -exports.gives = { emoji_names: true, emoji_url: true } | |
6 | - | |
7 | -exports.create = function (api) { | |
8 | - return { | |
9 | - emoji_names: function () { | |
10 | - return emojiNames | |
11 | - }, | |
12 | - emoji_url: function (emoji) { | |
13 | - return emoji in emojis && | |
14 | - api.blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png' | |
15 | - } | |
16 | - } | |
17 | -} | |
18 | - |
modules_extra/index.test.js | ||
---|---|---|
@@ -1,0 +1,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.js | ||
---|---|---|
@@ -1,80 +1,0 @@ | ||
1 | -var markdown = require('ssb-markdown'); | |
2 | -var h = require('hyperscript'); | |
3 | -var u = require('../util'); | |
4 | -var ref = require('ssb-ref'); | |
5 | - | |
6 | -//render a message | |
7 | - | |
8 | -exports.needs = { blob_url: 'first' } | |
9 | -exports.gives = 'message_content' | |
10 | - | |
11 | -exports.create = function (api) { | |
12 | - return function(msg, sbot) { | |
13 | - if (msg.value.content.type !== 'music-release-cc') | |
14 | - return; | |
15 | - | |
16 | - var tracks = msg.value.content.tracks; | |
17 | - return h('div', | |
18 | - h('img', { "src" : api.blob_url(msg.value.content.cover) }), | |
19 | - h('h1', msg.value.content.title), | |
20 | - h('ol', | |
21 | - Object.keys(tracks).map(function(k) { | |
22 | - var t = tracks[k]; | |
23 | - return h('li', t.fname, | |
24 | - h("br"), | |
25 | - h('audio', { | |
26 | - "controls" : true, | |
27 | - "src" : api.blob_url(t.link) | |
28 | - })) | |
29 | - })), | |
30 | - h('p', | |
31 | - "More info:", h('a', { href : msg.value.content.archivedotorg }, "archive.org"), | |
32 | - h("br"), | |
33 | - "License:", h('a', { href : msg.value.content.license }, "Link"))) | |
34 | - } | |
35 | -} | |
36 | - | |
37 | -// copied from like.js | |
38 | - | |
39 | -// inspiration for waveform range selection | |
40 | - | |
41 | -// idea: handout invite codes for upload of tracks to be cached by the pub | |
42 | - | |
43 | -// exports.message_meta = function (msg, sbot) { | |
44 | - | |
45 | -// var yupps = h('a') | |
46 | - | |
47 | -// pull( | |
48 | -// sbot_links({dest: msg.key, rel: 'vote'}), | |
49 | -// pull.collect(function (err, votes) { | |
50 | -// if(votes.length === 1) | |
51 | -// yupps.textContent = ' 1 yup' | |
52 | -// if(votes.length) | |
53 | -// yupps.textContent = ' ' + votes.length + ' yupps' | |
54 | -// }) | |
55 | -// ) | |
56 | - | |
57 | -// return yupps | |
58 | -// } | |
59 | - | |
60 | -// exports.message_action = function (msg, sbot) { | |
61 | -// if(msg.value.content.type !== 'vote') | |
62 | -// return h('a', {href: '#', onclick: function () { | |
63 | -// var yup = { | |
64 | -// type: 'vote', | |
65 | -// vote: { link: msg.key, value: 1, expression: 'yup' } | |
66 | -// } | |
67 | -// if(msg.value.content.recps) { | |
68 | -// yup.recps = msg.value.content.recps.map(function (e) { | |
69 | -// return e && typeof e !== 'string' ? e.link : e | |
70 | -// }) | |
71 | -// yup.private = true | |
72 | -// } | |
73 | -// //TODO: actually publish... | |
74 | - | |
75 | -// message_confirm(yup) | |
76 | -// }}, 'yup') | |
77 | - | |
78 | -// } | |
79 | - | |
80 | - |
modules_extra/music/release-cc.js | ||
---|---|---|
@@ -1,0 +1,87 @@ | ||
1 … | +var markdown = require('ssb-markdown'); | |
2 … | +var h = require('hyperscript'); | |
3 … | +var u = require('../../util'); | |
4 … | +var ref = require('ssb-ref'); | |
5 … | + | |
6 … | +//render a message | |
7 … | + | |
8 … | +exports.needs = { | |
9 … | + blob_url: 'first' | |
10 … | +} | |
11 … | + | |
12 … | +exports.gives = 'message_content' | |
13 … | + | |
14 … | +exports.create = function (api) { | |
15 … | + return function(msg, sbot) { | |
16 … | + if (msg.value.content.type !== 'music-release-cc') | |
17 … | + return; | |
18 … | + | |
19 … | + var tracks = msg.value.content.tracks; | |
20 … | + return h('div', | |
21 … | + h('img', { "src" : api.blob_url(msg.value.content.cover) }), | |
22 … | + h('h1', msg.value.content.title), | |
23 … | + h('ol', | |
24 … | + Object.keys(tracks).map(function(k) { | |
25 … | + var t = tracks[k]; | |
26 … | + return h('li', t.fname, | |
27 … | + h("br"), | |
28 … | + h('audio', { | |
29 … | + "controls" : true, | |
30 … | + "src" : api.blob_url(t.link) | |
31 … | + }) | |
32 … | + ) | |
33 … | + }) | |
34 … | + ), | |
35 … | + h('p', | |
36 … | + "More info:", h('a', { href : msg.value.content.archivedotorg }, "archive.org"), | |
37 … | + h("br"), | |
38 … | + "License:", h('a', { href : msg.value.content.license }, "Link") | |
39 … | + ) | |
40 … | + ) | |
41 … | + } | |
42 … | +} | |
43 … | + | |
44 … | +// copied from like.js | |
45 … | + | |
46 … | +// inspiration for waveform range selection | |
47 … | + | |
48 … | +// idea: handout invite codes for upload of tracks to be cached by the pub | |
49 … | + | |
50 … | +// exports.message_meta = function (msg, sbot) { | |
51 … | + | |
52 … | +// var yupps = h('a') | |
53 … | + | |
54 … | +// pull( | |
55 … | +// sbot_links({dest: msg.key, rel: 'vote'}), | |
56 … | +// pull.collect(function (err, votes) { | |
57 … | +// if(votes.length === 1) | |
58 … | +// yupps.textContent = ' 1 yup' | |
59 … | +// if(votes.length) | |
60 … | +// yupps.textContent = ' ' + votes.length + ' yupps' | |
61 … | +// }) | |
62 … | +// ) | |
63 … | + | |
64 … | +// return yupps | |
65 … | +// } | |
66 … | + | |
67 … | +// exports.message_action = function (msg, sbot) { | |
68 … | +// if(msg.value.content.type !== 'vote') | |
69 … | +// return h('a', {href: '#', onclick: function () { | |
70 … | +// var yup = { | |
71 … | +// type: 'vote', | |
72 … | +// vote: { link: msg.key, value: 1, expression: 'yup' } | |
73 … | +// } | |
74 … | +// if(msg.value.content.recps) { | |
75 … | +// yup.recps = msg.value.content.recps.map(function (e) { | |
76 … | +// return e && typeof e !== 'string' ? e.link : e | |
77 … | +// }) | |
78 … | +// yup.private = true | |
79 … | +// } | |
80 … | +// //TODO: actually publish... | |
81 … | + | |
82 … | +// message_confirm(yup) | |
83 … | +// }}, 'yup') | |
84 … | + | |
85 … | +// } | |
86 … | + | |
87 … | + |
modules_extra/music/release.js | ||
---|---|---|
@@ -1,0 +1,43 @@ | ||
1 … | +var markdown = require('ssb-markdown'); | |
2 … | +var h = require('hyperscript'); | |
3 … | +var u = require('../../util'); | |
4 … | +var ref = require('ssb-ref'); | |
5 … | + | |
6 … | +//render a message | |
7 … | + | |
8 … | +exports.gives = 'message_content' | |
9 … | + | |
10 … | +exports.create = function () { | |
11 … | + | |
12 … | + return function(msg, sbot) { | |
13 … | + if (msg.value.content.type !== 'music-release') | |
14 … | + return; | |
15 … | + | |
16 … | + var v = msg.value.content; | |
17 … | + return h('div', [ | |
18 … | + // h('img', { "src" : "http://localhost:7777/" + encodeURIComponent(v.cover) }), | |
19 … | + h('h1', v.Title), | |
20 … | + h("p", v.Description), | |
21 … | + h("dl", [ | |
22 … | + h("dt", "Creator"), | |
23 … | + h("dd", v.Creator), | |
24 … | + | |
25 … | + h("dt", "Identifier"), | |
26 … | + h("dd", v.Identifier), | |
27 … | + | |
28 … | + h("dt", "Published"), | |
29 … | + h("dd", v.Publicdate), | |
30 … | + | |
31 … | + h("dt", "Runtime"), | |
32 … | + h("dd", v.Runtime), | |
33 … | + | |
34 … | + h("dt", "Source"), | |
35 … | + h("dd", v.Source), | |
36 … | + | |
37 … | + h("dt", "License"), | |
38 … | + h("dd", h('a', { href : v.Licenseurl }, "Link")) | |
39 … | + ]) | |
40 … | + ]) | |
41 … | + } | |
42 … | +} | |
43 … | + |
modules_extra/search.mcss | ||
---|---|---|
@@ -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.json | ||
---|---|---|
@@ -1,44 +1,53 @@ | ||
1 | 1 … | { |
2 | 2 … | "name": "patchbay", |
3 | 3 … | "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", | |
6 | 6 … | "repository": { |
7 | 7 … | "type": "git", |
8 | - "url": "git://github.com/dominictarr/patchbay.git" | |
8 … | + "url": "git://github.com/ssbc/patchbay.git" | |
9 | 9 … | }, |
10 … | + "scripts": { | |
11 … | + "lite": "mkdir -p build && browserify index.js | indexhtmlify --title patchbay > build/index.html", | |
12 … | + "start": "electro index.js", | |
13 … | + "bundle": "mkdir -p build && browselectrify index.js > build/bundle.js", | |
14 … | + "rebuild": "npm rebuild --runtime=electron --target=$(electron -v) --abi=$(electron --abi) --disturl=https://atom.io/download/atom-shell", | |
15 … | + "graph": "node index.js | dot -Tsvg > graph.svg", | |
16 … | + "test": "browserify modules_*/index.test.js | tape-run" | |
17 … | + }, | |
10 | 18 … | "dependencies": { |
11 | 19 … | "@mmckegg/mutant": "^3.12.0", |
12 | 20 … | "brfs": "^1.4.3", |
13 | 21 … | "cont": "^1.0.3", |
14 | 22 … | "dataurl-": "^0.1.0", |
15 | - "depject": "^3.0.0", | |
23 … | + "depject": "^3.1.4", | |
16 | 24 … | "es2040": "^1.2.4", |
17 | 25 … | "hjson": "^2.0.3", |
18 | 26 … | "human-time": "0.0.1", |
19 | 27 … | "hypercombo": "0.1.0", |
20 | - "hypercrop": "^1.0.1", | |
28 … | + "hypercrop": "^1.1.0", | |
21 | 29 … | "hyperfile": "^1.1.0", |
22 | 30 … | "hyperlightbox": "1.0.0", |
23 | 31 … | "hyperprogress": "0.1.0", |
24 | 32 … | "hyperscript": "^1.4.7", |
25 | - "hypertabs": "^3.0.1", | |
33 … | + "hypertabs": "^4.0.0", | |
26 | 34 … | "insert-css": "^2.0.0", |
27 | 35 … | "is-visible": "^2.1.1", |
28 | 36 … | "kvgraph": "^0.1.0", |
29 | 37 … | "map-filter-reduce": "^3.0.1", |
30 | 38 … | "micro-css": "^0.6.2", |
31 | 39 … | "mime-types": "^2.1.11", |
32 | 40 … | "moment": "^2.13.0", |
41 … | + "on-load": "^3.2.0", | |
33 | 42 … | "open-external": "^0.1.1", |
34 | 43 … | "peaks.js": "^0.4.7", |
35 | 44 … | "pull-cat": "^1.1.9", |
36 | 45 … | "pull-many": "^1.0.7", |
37 | 46 … | "pull-next": "^0.0.1", |
38 | 47 … | "pull-paramap": "^1.1.6", |
39 | 48 … | "pull-reconnect": "^0.0.3", |
40 | - "pull-scroll": "^1.0.1", | |
49 … | + "pull-scroll": "^1.0.3", | |
41 | 50 … | "pull-stream": "^3.4.5", |
42 | 51 … | "scuttlebot": "^8.7.2", |
43 | 52 … | "setimmediate": "^1.0.5", |
44 | 53 … | "simple-mime": "^0.1.0", |
@@ -50,9 +59,9 @@ | ||
50 | 59 … | "ssb-feed": "^2.2.1", |
51 | 60 … | "ssb-git": "^0.4.1", |
52 | 61 … | "ssb-keys": "^6.1.0", |
53 | 62 … | "ssb-links": "^2.0.0", |
54 | - "ssb-markdown": "^3.0.0", | |
63 … | + "ssb-markdown": "^3.2.1", | |
55 | 64 … | "ssb-mentions": "^0.1.0", |
56 | 65 … | "ssb-query": "^0.1.1", |
57 | 66 … | "ssb-ref": "^2.6.2", |
58 | 67 … | "ssb-sort": "^1.0.0", |
@@ -64,23 +73,17 @@ | ||
64 | 73 … | "devDependencies": { |
65 | 74 … | "browselectrify": "^1.0.1", |
66 | 75 … | "electro": "^2.0.3", |
67 | 76 … | "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" | |
69 | 80 … | }, |
70 | 81 … | "browserify": { |
71 | 82 … | "transform": [ |
72 | 83 … | "brfs", |
73 | 84 … | "es2040" |
74 | 85 … | ] |
75 | 86 … | }, |
76 | - "scripts": { | |
77 | - "lite": "mkdir -p build && browserify index.js | indexhtmlify --title patchbay > build/index.html", | |
78 | - "start": "electro index.js", | |
79 | - "bundle": "mkdir -p build && browselectrify index.js > build/bundle.js", | |
80 | - "rebuild": "npm rebuild --runtime=electron --target=$(electron -v) --abi=$(electron --abi) --disturl=https://atom.io/download/atom-shell", | |
81 | - "graph": "node index.js | dot -Tsvg > graph.svg", | |
82 | - "test": "set -e; for t in test/*.js; do node $t; done" | |
83 | - }, | |
84 | 87 … | "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)", |
85 | 88 … | "license": "MIT" |
86 | 89 … | } |
style.css | ||
---|---|---|
@@ -28,15 +28,8 @@ | ||
28 | 28 … | color: #005580; |
29 | 29 … | text-decoration: underline; |
30 | 30 … | } |
31 | 31 … | |
32 | -.screen { | |
33 | - position: absolute; | |
34 | - top: 0; bottom: 0; | |
35 | - left: 0; right: 0; | |
36 | - overflow-y: hidden; | |
37 | -} | |
38 | - | |
39 | 32 … | .column { |
40 | 33 … | display: flex; |
41 | 34 … | flex-direction: column; |
42 | 35 … | min-height:0px; |
@@ -65,18 +58,8 @@ | ||
65 | 58 … | .expand { |
66 | 59 … | justify-content: space-between; |
67 | 60 … | } |
68 | 61 … | |
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 | - | |
79 | 62 … | pre { |
80 | 63 … | white-space: pre-wrap; |
81 | 64 … | word-wrap: break-word; |
82 | 65 … | } |
@@ -150,21 +133,8 @@ | ||
150 | 133 … | border-radius: .2em; |
151 | 134 … | z-index: 5; |
152 | 135 … | } |
153 | 136 … | |
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 | - | |
167 | 137 … | /* messages */ |
168 | 138 … | /* is .title used any more? */ |
169 | 139 … | |
170 | 140 … | .title { |
@@ -177,39 +147,9 @@ | ||
177 | 147 … | vertical-align: top; |
178 | 148 … | } |
179 | 149 … | |
180 | 150 … | |
181 | -/* -- suggest box */ | |
182 | 151 … | |
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 | - | |
212 | 152 … | /* avatar */ |
213 | 153 … | |
214 | 154 … | .avatar--large, |
215 | 155 … | .avatar--thumbnail, |
@@ -237,21 +177,8 @@ | ||
237 | 177 … | .avatar--fullsize { |
238 | 178 … | width: 50%; |
239 | 179 … | } |
240 | 180 … | |
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 | - | |
254 | 181 … | /* lightbox - used in message-confirm */ |
255 | 182 … | |
256 | 183 … | .lightbox { |
257 | 184 … | position: fixed; |
@@ -259,8 +186,9 @@ | ||
259 | 186 … | right: 0px; |
260 | 187 … | top: 50px; |
261 | 188 … | overflow: auto; |
262 | 189 … | width: 650px; |
190 … | + max-width: 100%; | |
263 | 191 … | padding: 25px; |
264 | 192 … | margin: auto; |
265 | 193 … | |
266 | 194 … | z-index: 2; |
@@ -269,22 +197,9 @@ | ||
269 | 197 … | border: 1px solid #eee; |
270 | 198 … | border-radius: .2em; |
271 | 199 … | } |
272 | 200 … | |
273 | -/* searchprompt */ | |
274 | 201 … | |
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 | - | |
287 | 202 … | /* TextNodeSearcher highlights */ |
288 | 203 … | |
289 | 204 … | highlight { |
290 | 205 … | background: #ff8; |
@@ -318,79 +233,16 @@ | ||
318 | 233 … | border-radius: 100%; |
319 | 234 … | background: #08c; |
320 | 235 … | } |
321 | 236 … | |
322 | -.error { | |
323 | - background: red; | |
324 | -} | |
325 | - | |
326 | 237 … | /* tabs */ |
327 | 238 … | |
328 | 239 … | .header { |
329 | 240 … | background: #f5f5f5; |
330 | 241 … | border-bottom: 1px inset; |
331 | 242 … | flex-shrink: 0; |
332 | 243 … | } |
333 | 244 … | |
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 | - | |
393 | 245 … | /* progress bar */ |
394 | 246 … | |
395 | 247 … | .hyperprogress__bar { |
396 | 248 … | background: darkgrey; |
Built with git-ssb-web