secure-scuttlebot classic

decent: re-export patchbay modules and simplify wiring

+80 -5094
+3 -4
decent/basic.js
··· 1 1 require('depject')( 2 - require('./modules_core'), 3 - require('./modules_basic') 2 + // from more specialized to more general 3 + require('../patchbay/modules_core'), 4 + require('../patchbay/modules_basic') 4 5 ).app[0]() 5 - 6 -
+1 -70
decent/config.js
··· 1 - 2 - var URL = require('url') 3 - 4 - function loadRemote () { 5 - var remote = null 6 - 7 - if (typeof window !== 'undefined' && window.PATCHBAY_REMOTE) { 8 - remote = window.PATCHBAY_REMOTE 9 - } 10 - 11 - return remote 12 - } 13 - 14 - function rewriteRemoteForLocation (remote) { 15 - if (!remote || typeof window === 'undefined' || !window.location) return remote 16 - 17 - try { 18 - var shsIndex = remote.indexOf('~shs:') 19 - if (shsIndex === -1) return remote 20 - 21 - var base = remote.substring(0, shsIndex) 22 - var parsed = URL.parse(base) 23 - var key = remote.substring(shsIndex + 5) 24 - var loc = window.location 25 - var isLocal = parsed && (parsed.hostname === 'localhost' || 26 - parsed.hostname === '127.0.0.1' || parsed.hostname === '::1') 27 - 28 - if (!isLocal) return remote 29 - 30 - var proto = loc.protocol === 'https:' ? 'wss' : 'ws' 31 - var hostname = loc.hostname || loc.host 32 - if (hostname && hostname.indexOf(':') !== -1 && hostname[0] !== '[') 33 - hostname = '[' + hostname + ']' 34 - var port = parsed && parsed.port ? parsed.port : loc.port 35 - var host = hostname + (port ? ':' + port : '') 36 - 37 - return proto + '://' + host + '~shs:' + key 38 - } catch (e) { 39 - return remote 40 - } 41 - } 42 - 43 - module.exports = function () { 44 - var remote = loadRemote() 45 - 46 - // In the browser, rewrite localhost remotes to match the page host, 47 - // while preserving the pubkey and port from the original remote. 48 - remote = rewriteRemoteForLocation(remote) 49 - 50 - //TODO: use _several_ remotes, so if one goes down, 51 - // you can still communicate via another... 52 - // also, if a blob does not load, use another pub... 53 - 54 - //if we are the light client, get our blobs from the same domain. 55 - var blobsUrl 56 - if (remote) { 57 - var r = URL.parse(remote.split('~')[0]) 58 - //this will work for ws and wss. 59 - r.protocol = r.protocol.replace('ws', 'http') 60 - r.pathname = '/blobs/get' 61 - blobsUrl = URL.format(r) 62 - } 63 - else 64 - blobsUrl = 'http://localhost:8989/blobs/get' 65 - 66 - return { 67 - remote: remote, 68 - blobsUrl: blobsUrl 69 - } 70 - } 1 + module.exports = require('../patchbay/config.js')
+1 -19
decent/electron-shim.js
··· 1 - function Menu () {} 2 - 3 - Menu.prototype.append = function () {} 4 - Menu.prototype.popup = function () {} 5 - 6 - function MenuItem () {} 7 - 8 - module.exports = { 9 - remote: { 10 - Menu: Menu, 11 - MenuItem: MenuItem, 12 - getCurrentWindow: function () { 13 - return { 14 - inspectElement: function () {} 15 - } 16 - } 17 - } 18 - } 19 - 1 + module.exports = require('../patchbay/electron-shim.js')
+5 -8
decent/embedded.js
··· 1 1 require('depject')( 2 - require('./modules_embedded'), 3 - require('./modules_basic'), 4 - require('./modules_extra') 5 - ).plugs.app[0]() 6 - 7 - 8 - 9 - 2 + // from more specialized to more general 3 + require('../patchbay/modules_embedded'), 4 + require('../patchbay/modules_basic'), 5 + require('../patchbay/modules_extra') 6 + ).app[0]()
+2 -3
decent/index.js
··· 1 1 require('depject')( 2 2 // from more specialized to more general 3 - require('./modules_extra'), 4 - require('./modules_basic'), 3 + require('../patchbay/modules_extra'), 4 + require('../patchbay/modules_basic'), 5 5 require('./modules_core') 6 6 ).app[0]() 7 -
+1 -5
decent/keys.js
··· 1 - var config = require('ssb-config/inject')(process.env.ssb_appname) 2 - var ssbKeys = require('ssb-keys') 3 - var path = require('path') 4 - module.exports = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret')) 5 - 1 + module.exports = require('../patchbay/keys.js')
+1 -32
decent/keyscroll.js
··· 1 - module.exports = function (container) { 2 - var curMsgEl 3 - 4 - if (!container) 5 - return function() {} 6 - 7 - container.addEventListener('click', onActivateChild, false) 8 - container.addEventListener('focus', onActivateChild, true) 9 - 10 - function onActivateChild(ev) { 11 - for (var el = ev.target; el; el = el.parentNode) { 12 - if (el.parentNode == container) { 13 - curMsgEl = el 14 - return 15 - } 16 - } 17 - } 18 - 19 - function selectChild(el) { 20 - if (!el) return 21 - (el.scrollIntoViewIfNeeded || el.scrollIntoView).call(el) 22 - el.focus() 23 - curMsgEl = el 24 - } 25 - 26 - return function scroll(d) { 27 - selectChild(!curMsgEl ? container.firstChild 28 - : d > 0 ? curMsgEl.nextElementSibling || container.lastChild 29 - : d < 0 ? curMsgEl.previousElementSibling || container.firstChild 30 - : curMsgEl) 31 - } 32 - } 1 + module.exports = require('../patchbay/keyscroll.js')
+1 -62
decent/modules_basic/about.js
··· 1 - 2 - var h = require('hyperscript') 3 - 4 - function idLink (id) { 5 - return h('a', {href:'#'+id}, id) 6 - } 7 - 8 - function asLink (ln) { 9 - return 'string' === typeof ln ? ln : ln.link 10 - } 11 - 12 - //var blob_url = require('../plugs').first(exports.blob_url = []) 13 - 14 - exports.needs = { 15 - blob_url: 'first' 16 - } 17 - 18 - exports.gives = { 19 - message_content: true, 20 - message_content_mini: true 21 - } 22 - 23 - exports.create = function (api) { 24 - var exports = {} 25 - 26 - exports.message_content = function (msg) { 27 - if(msg.value.content.type !== 'about') return 28 - 29 - if(!msg.value.content.image && !msg.value.content.name) 30 - return 31 - 32 - var about = msg.value.content 33 - var id = msg.value.content.about 34 - return h('p', 35 - about.about === msg.value.author 36 - ? h('span', 'self-identifies ') 37 - : h('span', 'identifies ', idLink(id)), 38 - ' as ', 39 - h('a', {href:"#"+about.about}, 40 - about.name || null, 41 - about.image 42 - ? h('img.avatar--thumbnail', {src: api.blob_url(asLink(about.image))}) 43 - : null 44 - ) 45 - ) 46 - } 47 - 48 - exports.message_content_mini = function (msg) { 49 - if(msg.value.content.type !== 'about') return 50 - 51 - var about = msg.value.content 52 - var id = msg.value.content.about 53 - if(!about.name) return 54 - 55 - if(about.about === msg.value.author) 56 - return ['self-identifies as ', h('a', {href:"#"+about.about}, about.name)] 57 - 58 - return ['identifies ', idLink(id), ' as ', h('a', {href:"#"+about.about}, about.name)] 59 - } 60 - 61 - return exports 62 - } 1 + module.exports = require('../../patchbay/modules_basic/about.js')
+1 -167
decent/modules_basic/avatar-edit.js
··· 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 plugs = require('../plugs') 10 - var ref = require('ssb-ref') 11 - var visualize = require('visualize-buffer') 12 - var self_id = require('../keys').id 13 - 14 - //var confirm = plugs.first(exports.message_confirm = []) 15 - //var sbot_blobs_add = plugs.first(exports.sbot_blobs_add = []) 16 - //var blob_url = plugs.first(exports.blob_url = []) 17 - //var sbot_links = plugs.first(exports.sbot_links = []) 18 - //var avatar_name = plugs.first(exports.avatar_name = []) 19 - // 20 - 21 - function crop (d, cb) { 22 - var canvas 23 - var controls = h('div.row.avatar_pic__controls', 24 - h('button', 'okay', {onclick: function () { 25 - if (!canvas || !canvas.selection) return cb(new Error('image not ready')) 26 - cb(null, canvas.selection.toDataURL()) 27 - }}), 28 - h('button', 'cancel', {onclick: function () { 29 - cb(new Error('canceled')) 30 - }}) 31 - ) 32 - 33 - var container = h('div.column.avatar_pic', 34 - // canvas will be inserted above controls once the image loads 35 - controls 36 - ) 37 - 38 - var img = new Image() 39 - img.onload = function () { 40 - canvas = hypercrop(img) 41 - container.insertBefore(canvas, controls) 42 - } 43 - img.src = d 44 - 45 - return container 46 - } 47 - 48 - exports.needs = { 49 - message_confirm: 'first', 50 - blob_url: 'first', 51 - blobs_url: 'first', 52 - sbot_links: 'first', 53 - avatar_name: 'first' 54 - } 55 - 56 - exports.gives = 'avatar_edit' 57 - 58 - exports.create = function (api) { 59 - return function (id) { 60 - 61 - var img = visualize(new Buffer(id.substring(1), 'base64'), 256) 62 - img.classList.add('avatar--large') 63 - 64 - var lb = hyperlightbox() 65 - var name_input = h('input', {placeholder: 'rename'}) 66 - var name = api.avatar_name(id) 67 - var selected = null, selected_data = null 68 - 69 - getAvatar({links: api.sbot_links}, self_id, id, function (err, avatar) { 70 - if (err) return console.error(err) 71 - //don't show user has already selected an avatar. 72 - if(selected) return 73 - if(ref.isBlob(avatar.image)) 74 - img.src = api.blob_url(avatar.image) 75 - }) 76 - 77 - var also_pictured = h('div.profile__alsopicturedas.wrap') 78 - 79 - pull( 80 - api.sbot_links({dest: id, rel: 'about', values: true}), 81 - pull.map(function (e) { 82 - return e.value.content.image 83 - }), 84 - pull.filter(function (e) { 85 - return e && 'string' == typeof e.link 86 - }), 87 - pull.unique('link'), 88 - pull.drain(function (image) { 89 - also_pictured.appendChild( 90 - h('a', {href:'#', onclick: function (ev) { 91 - ev.stopPropagation() 92 - ev.preventDefault() 93 - selected = image 94 - img.src = api.blob_url(image.link || image) 95 - }}, 96 - h('img.avatar--thumbnail', {src: api.blob_url(image)}) 97 - ) 98 - ) 99 - }) 100 - ) 101 - 102 - return h('div.row.profile', 103 - lb, 104 - img, 105 - h('div.column.profile__info', 106 - h('strong', name), 107 - name_input, 108 - 109 - hyperfile.asDataURL(function (data) { 110 - var el = crop(data, function (err, data) { 111 - if(data) { 112 - img.src = data 113 - var _data = dataurl.parse(data) 114 - var xhr = new XMLHttpRequest() 115 - var blobsUrl = api.blobs_url() 116 - var addUrl = blobsUrl.replace(/\/blobs\/get\/?$/, '/blobs/add') 117 - if (addUrl === blobsUrl) addUrl = '/blobs/add' 118 - xhr.open('POST', addUrl, true) 119 - xhr.responseType = 'text' 120 - xhr.onload = function () { 121 - if (xhr.status < 200 || xhr.status >= 300) { 122 - return alert('blob upload failed: status ' + xhr.status) 123 - } 124 - var hash = (xhr.responseText || '').trim() 125 - selected = { 126 - link: hash, 127 - size: _data.data.length, 128 - type: _data.mimetype, 129 - width: 512, 130 - height: 512 131 - } 132 - } 133 - xhr.onerror = function () { 134 - alert('blob upload network error') 135 - } 136 - xhr.send(_data.data) 137 - } 138 - lb.close() 139 - }) 140 - lb.show(el) 141 - }), 142 - h('button', 'update', {onclick: function () { 143 - if(name_input.value) 144 - name.textContent = name_input.value 145 - 146 - if(selected) 147 - api.message_confirm({ 148 - type: 'about', 149 - about: id, 150 - name: name_input.value || undefined, 151 - image: selected.link || selected 152 - }) 153 - else if(name_input.value) //name only 154 - api.message_confirm({ 155 - type: 'about', 156 - about: id, 157 - name: name_input.value || undefined, 158 - }) 159 - else 160 - //another moment of weakness 161 - alert('must select a name or image') 162 - }}), 163 - also_pictured 164 - ) 165 - ) 166 - } 167 - } 1 + module.exports = require('../../patchbay/modules_basic/avatar-edit.js')
+1 -118
decent/modules_basic/avatar-image.js
··· 1 - 'use strict' 2 - var getAvatar = require('ssb-avatar') 3 - var h = require('hyperscript') 4 - var ref = require('ssb-ref') 5 - var path = require('path') 6 - var visualize = require('visualize-buffer') 7 - 8 - var pull = require('pull-stream') 9 - 10 - var self_id = require('../keys').id 11 - 12 - //var plugs = require('../plugs') 13 - //var sbot_query = plugs.first(exports.sbot_query = []) 14 - //var blob_url = require('../plugs').first(exports.blob_url = []) 15 - // 16 - 17 - exports.needs = { 18 - sbot_query: 'first', 19 - blob_url: 'first' 20 - } 21 - 22 - exports.gives = { 23 - connection_status: true, avatar_image: true 24 - } 25 - 26 - 27 - function isFunction (f) { 28 - return 'function' === typeof f 29 - } 30 - 31 - 32 - var ready = false 33 - var waiting = [] 34 - 35 - var last = 0 36 - 37 - exports.create = function (api) { 38 - var avatars = {} 39 - 40 - //blah blah 41 - return { 42 - connection_status: function (err) { 43 - if (err) return 44 - pull( 45 - api.sbot_query({ 46 - query: [{ 47 - $filter: { 48 - timestamp: {$gt: last || 0 }, 49 - value: { content: { 50 - type: "about", 51 - about: {$prefix: "@"} 52 - }} 53 - }}, 54 - { 55 - $map: { 56 - id: ["value", "content", "about"], 57 - image: ["value", "content", "image"], 58 - by: ["value", "author"], 59 - ts: 'timestamp' 60 - }}], 61 - live: true 62 - }), 63 - pull.drain(function (a) { 64 - if(a.sync) { 65 - ready = true 66 - while(waiting.length) waiting.shift()() 67 - return 68 - } 69 - last = a.ts 70 - 71 - var image = a.image 72 - if(image && 'object' === typeof image && 'string' === typeof image.link) 73 - image = image.link 74 - 75 - if(!ref.isBlob(image)) 76 - return 77 - 78 - a.image = image 79 - 80 - //set image for avatar. 81 - //overwrite another avatar 82 - //you picked. 83 - if( 84 - //if there is no avatar 85 - (!avatars[a.id]) || 86 - //if i chose this avatar 87 - (a.by == self_id) || 88 - //they chose their own avatar, 89 - //and current avatar was not chosen by me 90 - (a.by === a.id && avatars[a.id].by != self_id) 91 - ) 92 - avatars[a.id] = a 93 - 94 - }) 95 - ) 96 - }, 97 - 98 - avatar_image: function (author, classes) { 99 - classes = classes || '' 100 - if(classes && 'string' === typeof classes) classes = '.avatar--'+classes 101 - 102 - var img = visualize(new Buffer(author.substring(1), 'base64'), 256) 103 - ;(classes || '').split('.').filter(Boolean).forEach(function (c) { 104 - img.classList.add(c) 105 - }) 106 - 107 - function go () { 108 - if(avatars[author]) img.src = api.blob_url(avatars[author].image) 109 - } 110 - 111 - if(!ready) 112 - waiting.push(go) 113 - else go() 114 - 115 - return img 116 - } 117 - } 118 - } 1 + module.exports = require('../../patchbay/modules_basic/avatar-image.js')
+1 -22
decent/modules_basic/avatar-link.js
··· 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 - 1 + module.exports = require('../../patchbay/modules_basic/avatar-link.js')
+1 -28
decent/modules_basic/avatar-name.js
··· 1 - 2 - var signifier = require('../plugs').first(exports.signifier = []) 3 - var h = require('hyperscript') 4 - 5 - exports.needs = { signifier: 'first' } 6 - 7 - exports.gives = 'avatar_name' 8 - 9 - exports.create = function (api) { 10 - 11 - return function name (id) { 12 - var n = h('span.avatar_name', 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 - } 1 + module.exports = require('../../patchbay/modules_basic/avatar-name.js')
+1 -89
decent/modules_basic/avatar-profile.js
··· 1 - var h = require('hyperscript') 2 - var pull = require('pull-stream') 3 - 4 - //var plugs = require('../plugs') 5 - //var avatar_image_link = plugs.first(exports.avatar_image_link = []) 6 - //var avatar_action = plugs.map(exports.avatar_action = []) 7 - //var avatar_edit = plugs.first(exports.avatar_edit = []) 8 - 9 - //var follows = plugs.first(exports.follows = []) 10 - //var followers = plugs.first(exports.followers = []) 11 - // 12 - exports.needs = { 13 - avatar_image_link: 'first', 14 - avatar_action: 'map', 15 - avatar_edit: 'first', 16 - follows: 'first', 17 - followers: 'first' 18 - } 19 - 20 - exports.gives = 'avatar_profile' 21 - 22 - function streamToList(stream, el) { 23 - pull( 24 - stream, 25 - pull.drain(function (item) { 26 - if(item) el.appendChild(item) 27 - }) 28 - ) 29 - return el 30 - } 31 - 32 - exports.create = function (api) { 33 - 34 - function image_link (id) { 35 - return api.avatar_image_link(id, 'thumbnail') 36 - } 37 - 38 - return function (id) { 39 - 40 - var follows_el = h('div.profile__follows.wrap') 41 - var friends_el = h('div.profile__friendss.wrap') 42 - var followers_el = h('div.profile__followers.wrap') 43 - var a, b 44 - 45 - pull(api.follows(id), pull.unique(), pull.collect(function (err, ary) { 46 - a = ary || []; next() 47 - })) 48 - pull(api.followers(id), pull.unique(), pull.collect(function (err, ary) { 49 - b = ary || {}; next() 50 - })) 51 - 52 - function next () { 53 - if(!(a && b)) return 54 - var _c = [], _a = [], _b = [] 55 - 56 - a.forEach(function (id) { 57 - if(!~b.indexOf(id)) _a.push(id) 58 - else _c.push(id) 59 - }) 60 - b.forEach(function (id) { 61 - if(!~_c.indexOf(id)) _b.push(id) 62 - }) 63 - function add (ary, el) { 64 - ary.forEach(function (id) { el.appendChild(image_link(id)) }) 65 - } 66 - 67 - add(_a, follows_el) 68 - add(_c, friends_el) 69 - add(_b, followers_el) 70 - } 71 - 72 - 73 - return h('div.column.profile', 74 - h('div.message.message-card', 75 - api.avatar_edit(id), 76 - api.avatar_action(id), 77 - h('div.profile__relationships.column', 78 - h('strong', 'follows'), 79 - follows_el, 80 - h('strong', 'friends'), 81 - friends_el, 82 - h('strong', 'followers'), 83 - followers_el 84 - ) 85 - ) 86 - ) 87 - } 88 - 89 - } 1 + module.exports = require('../../patchbay/modules_basic/avatar-profile.js')
+1 -37
decent/modules_basic/avatar.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - 4 - exports.needs = { 5 - avatar_name: 'first', 6 - avatar_image: 'first', 7 - avatar_link: 'first' 8 - } 9 - 10 - exports.gives = { 11 - avatar: true, 12 - avatar_image_name_link: true, 13 - avatar_image_link: true 14 - } 15 - 16 - exports.create = function (api) { 17 - 18 - var exports = {} 19 - exports.avatar = function (author, classes) { 20 - return exports.avatar_image_name_link(author, classes) 21 - } 22 - 23 - exports.avatar_image_name_link = function (author, classes) { 24 - return api.avatar_link(author, [ 25 - api.avatar_image(author, classes), 26 - api.avatar_name(author) 27 - ]) 28 - } 29 - 30 - exports.avatar_image_link = function (author, classes) { 31 - return api.avatar_link(author, api.avatar_image(author, classes)) 32 - } 33 - 34 - return exports 35 - } 36 - 37 - 1 + module.exports = require('../../patchbay/modules_basic/avatar.js')
+1 -343
decent/modules_basic/compose.js
··· 1 - 'use strict' 2 - var h = require('hyperscript') 3 - var selfId = require('../keys').id 4 - var suggest = require('suggest-box') 5 - var mentions = require('ssb-mentions') 6 - var lightbox = require('hyperlightbox') 7 - var cont = require('cont') 8 - 9 - //var plugs = require('../plugs') 10 - //var suggest_mentions= plugs.asyncConcat(exports.suggest_mentions = []) 11 - //var publish = plugs.first(exports.sbot_publish = []) 12 - //var message_content = plugs.first(exports.message_content = []) 13 - //var message_confirm = plugs.first(exports.message_confirm = []) 14 - //var file_input = plugs.first(exports.file_input = []) 15 - 16 - exports.needs = { 17 - suggest_mentions: 'map', //<-- THIS MUST BE REWRITTEN 18 - publish: 'first', 19 - message_content: 'first', 20 - message_confirm: 'first', 21 - file_input: 'first', 22 - message_link: 'first', 23 - avatar: 'first' 24 - } 25 - 26 - exports.gives = 'message_compose' 27 - 28 - function id (e) { return e } 29 - 30 - /* 31 - opts can take 32 - 33 - placeholder: string. placeholder text, defaults to "Write a message" 34 - prepublish: function. called before publishing a message. 35 - shrink: boolean. set to false, to make composer not shrink (or hide controls) when unfocused. 36 - */ 37 - 38 - exports.create = function (api) { 39 - 40 - return function (meta, opts, cb) { 41 - if('function' === typeof cb) { 42 - if('function' === typeof opts) 43 - opts = {prepublish: opts} 44 - } 45 - 46 - if(!opts) opts = {} 47 - opts.prepublish = opts.prepublish || id 48 - 49 - var accessories 50 - meta = meta || {} 51 - if(!meta.type) throw new Error('message must have type') 52 - var modal = !!opts.modal 53 - var lb = null 54 - var modalContent = null 55 - var replyHintEls = [] 56 - var modalTimer = null 57 - var onKeydown = null 58 - var trigger = null 59 - var baseSnapshot = null 60 - var replyActive = false 61 - var lastReplyMsg = null 62 - var inlineReplyHint = null 63 - 64 - function cloneMeta (src) { 65 - var out = {} 66 - for (var k in src) out[k] = src[k] 67 - return out 68 - } 69 - 70 - function applyMeta (target, source) { 71 - for (var k in target) delete target[k] 72 - for (var key in source) target[key] = source[key] 73 - } 74 - 75 - function captureBaseMeta () { 76 - baseSnapshot = cloneMeta(meta) 77 - } 78 - 79 - function clearReply () { 80 - if (replyActive && baseSnapshot) { 81 - applyMeta(meta, baseSnapshot) 82 - } 83 - replyActive = false 84 - lastReplyMsg = null 85 - updateReplyHint(null) 86 - } 87 - function createReplyHintEl (className) { 88 - var selector = 'div' + (className ? '.' + className : '') 89 - var el = h(selector, {style: {display: 'none'}}) 90 - replyHintEls.push(el) 91 - return el 92 - } 93 - if (!modal) { 94 - inlineReplyHint = h('div.compose-reply-hint', {style: {display: 'none'}}) 95 - replyHintEls.push(inlineReplyHint) 96 - } 97 - var ta = h('textarea', { 98 - placeholder: opts.placeholder || 'Write a message', 99 - style: {height: opts.shrink === false ? '200px' : ''} 100 - }) 101 - 102 - if(opts.shrink !== false) { 103 - var blur 104 - ta.addEventListener('focus', function () { 105 - clearTimeout(blur) 106 - if(!ta.value) { 107 - ta.style.height = '200px' 108 - } 109 - accessories.style.display = 'block' 110 - }) 111 - ta.addEventListener('blur', function () { 112 - //don't shrink right away, so there is time 113 - //to click the publish button. 114 - clearTimeout(blur) 115 - blur = setTimeout(function () { 116 - if(ta.value) return 117 - ta.style.height = '50px' 118 - accessories.style.display = 'none' 119 - }, 200) 120 - }) 121 - } 122 - 123 - ta.addEventListener('keydown', function (ev) { 124 - if(ev.keyCode === 13 && ev.ctrlKey) publish() 125 - }) 126 - 127 - var files = [] 128 - var filesById = {} 129 - 130 - function ensureLightbox () { 131 - if (lb) return 132 - lb = lightbox() 133 - document.body.appendChild(lb) 134 - lb.addEventListener('click', function (ev) { 135 - if (ev.target === lb) closeModal() 136 - }) 137 - } 138 - 139 - function showModal (fromEl) { 140 - if (!modal) return 141 - ensureLightbox() 142 - if (trigger) trigger.style.display = 'none' 143 - if (!modalContent) { 144 - modalContent = h('div.compose-modal', 145 - h('div.compose-modal__header', 146 - h('div.compose-modal__title', h('div.avatar', 147 - api.avatar(selfId, 'thumbnail') 148 - )), 149 - h('button.btn.compose-modal__close', 'Close', {onclick: closeModal}) 150 - ), 151 - createReplyHintEl('compose-modal__hint'), 152 - composer 153 - ) 154 - if (lastReplyMsg) updateReplyHint(lastReplyMsg) 155 - } 156 - 157 - lb.show(modalContent) 158 - document.body.classList.add('lightbox-open') 159 - window.requestAnimationFrame(function () { 160 - modalContent.classList.add('compose-modal--animate') 161 - ta.focus() 162 - }) 163 - 164 - onKeydown = function (ev) { 165 - if (ev.keyCode === 27) closeModal() 166 - } 167 - document.addEventListener('keydown', onKeydown) 168 - } 169 - 170 - function closeModal () { 171 - if (!lb || !modalContent) return 172 - modalContent.classList.remove('compose-modal--animate') 173 - if (modalTimer) clearTimeout(modalTimer) 174 - modalTimer = setTimeout(function () { 175 - if (lb) lb.close() 176 - document.body.classList.remove('lightbox-open') 177 - if (trigger) trigger.style.display = '' 178 - }, 160) 179 - if (onKeydown) { 180 - document.removeEventListener('keydown', onKeydown) 181 - onKeydown = null 182 - } 183 - } 184 - 185 - function publish() { 186 - publishBtn.disabled = true 187 - var content 188 - try { 189 - content = JSON.parse(ta.value) 190 - } catch (err) { 191 - meta.text = ta.value 192 - meta.mentions = mentions(ta.value).map(function (mention) { 193 - // merge markdown-detected mention with file info 194 - var file = filesById[mention.link] 195 - if (file) { 196 - if (file.type) mention.type = file.type 197 - if (file.size) mention.size = file.size 198 - } 199 - return mention 200 - }) 201 - try { 202 - meta = opts.prepublish(meta) 203 - } catch (err) { 204 - publishBtn.disabled = false 205 - if (cb) cb(err) 206 - else alert(err.message) 207 - } 208 - if (modal) closeModal() 209 - return api.message_confirm(meta, done) 210 - } 211 - 212 - if (modal) closeModal() 213 - api.message_confirm(content, done) 214 - 215 - function done (err, msg) { 216 - publishBtn.disabled = false 217 - if(err) return alert(err.stack) 218 - else if (msg) { 219 - ta.value = '' 220 - clearReply() 221 - } 222 - else if (modal) showModal(trigger) 223 - 224 - if (cb) cb(err, msg) 225 - } 226 - } 227 - 228 - 229 - var publishBtn = h('button.btn.btn-primary', 'Preview', {onclick: publish}) 230 - var composerChildren = [ta, 231 - accessories = h('div.row.compose__controls', 232 - //hidden until you focus the textarea 233 - {style: {display: opts.shrink === false ? '' : 'none'}}, 234 - api.file_input(function (file) { 235 - files.push(file) 236 - filesById[file.link] = file 237 - 238 - var embed = file.type.indexOf('image/') === 0 ? '!' : '' 239 - ta.value += embed + '['+file.name+']('+file.link+')' 240 - console.log('added:', file) 241 - }), 242 - publishBtn) 243 - ] 244 - if (inlineReplyHint) composerChildren.unshift(inlineReplyHint) 245 - var composer = 246 - h('div.message.message-card.compose', h('div.column', composerChildren)) 247 - 248 - suggest(ta, function (name, cb) { 249 - cont.para(api.suggest_mentions(name)) 250 - (function (err, ary) { 251 - cb(null, ary.reduce(function (a, b) { 252 - if(!b) return a 253 - return a.concat(b) 254 - }, [])) 255 - }) 256 - }, {}) 257 - 258 - function applyReply (msg) { 259 - if (!msg || !msg.key || !msg.value || !msg.value.content) return 260 - if (!replyActive) captureBaseMeta() 261 - var nextMeta = cloneMeta(baseSnapshot || meta) 262 - nextMeta.type = meta.type || 'post' 263 - var content = msg.value.content 264 - nextMeta.root = content.root || msg.key 265 - nextMeta.branch = msg.key 266 - if (content.channel) nextMeta.channel = content.channel 267 - else delete nextMeta.channel 268 - 269 - if (msg.value.private) { 270 - var selfId = require('../keys').id 271 - var recps = content.recps 272 - if (recps) nextMeta.recps = recps 273 - else nextMeta.recps = [msg.value.author, selfId] 274 - } else delete nextMeta.recps 275 - 276 - applyMeta(meta, nextMeta) 277 - replyActive = true 278 - lastReplyMsg = msg 279 - updateReplyHint(msg) 280 - } 281 - 282 - function updateReplyHint (msg) { 283 - if (!replyHintEls.length) return 284 - if (!msg || !msg.value || !msg.value.content) { 285 - replyHintEls.forEach(function (el) { 286 - el.textContent = '' 287 - el.style.display = 'none' 288 - }) 289 - return 290 - } 291 - var root = msg.value.content.root || msg.key 292 - var re = h('span', 're: ', api.message_link(root)) 293 - replyHintEls.forEach(function (el) { 294 - while (el.firstChild) el.removeChild(el.firstChild) 295 - el.appendChild(h('div.message_content', re)) 296 - el.style.display = '' 297 - }) 298 - } 299 - 300 - function handleReplyEvent (ev) { 301 - if (!trigger || !document.body.contains(trigger)) return 302 - var detail = ev && ev.detail 303 - var replyMsg = detail && detail.msg 304 - applyReply(replyMsg) 305 - showModal(trigger) 306 - } 307 - 308 - if (modal && opts.listenReplyEvents) { 309 - window.addEventListener('decent:reply', handleReplyEvent) 310 - } 311 - 312 - if (modal) { 313 - var label = opts.triggerLabel || 'Compose' 314 - trigger = h('button.btn.btn-primary.compose-trigger__button', { 315 - 'aria-label': label, 316 - title: label, 317 - onclick: function () { 318 - clearReply() 319 - showModal(trigger) 320 - } 321 - }, h('span.compose-trigger__icon.material-symbols-outlined', { 322 - 'aria-hidden': 'true' 323 - }, 'edit')) 324 - if (opts.autoOpen) { 325 - var attempts = 0 326 - var tryOpen = function () { 327 - if (document.body.contains(trigger) || attempts > 8) { 328 - showModal(trigger) 329 - return 330 - } 331 - attempts += 1 332 - setTimeout(tryOpen, 50) 333 - } 334 - setTimeout(tryOpen, 0) 335 - } 336 - return h('div.compose-trigger', trigger) 337 - } 338 - 339 - return composer 340 - 341 - } 342 - 343 - } 1 + module.exports = require('../../patchbay/modules_basic/compose.js')
+1 -62
decent/modules_basic/feed.js
··· 1 - var ref = require('ssb-ref') 2 - var Scroller = require('pull-scroll') 3 - var h = require('hyperscript') 4 - var pull = require('pull-stream') 5 - var u = require('../util') 6 - 7 - //var plugs = require('../plugs') 8 - //var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) 9 - //var message_render = plugs.first(exports.message_render = []) 10 - //var avatar_profile = plugs.first(exports.avatar_profile = []) 11 - //var signifier = plugs.first(exports.signifier = []) 12 - 13 - exports.needs = { 14 - sbot_user_feed: 'first', 15 - message_render: 'first', 16 - avatar_profile: 'first', 17 - signifier: 'first' 18 - } 19 - 20 - exports.gives = 'screen_view' 21 - 22 - 23 - exports.create = function (api) { 24 - 25 - return function (id) { 26 - //TODO: header of user info, avatars, names, follows. 27 - if(ref.isFeed(id)) { 28 - 29 - var content = h('div.column.scroller__content') 30 - var div = h('div.column.scroller', 31 - {style: {'overflow':'auto'}}, 32 - h('div.scroller__wrapper', 33 - h('div', api.avatar_profile(id)), 34 - content 35 - ) 36 - ) 37 - 38 - api.signifier(id, function (_, names) {}) 39 - 40 - 41 - pull( 42 - api.sbot_user_feed({id: id, old: false, live: true}), 43 - Scroller(div, content, api.message_render, true, false) 44 - ) 45 - 46 - //how to handle when have scrolled past the start??? 47 - 48 - pull( 49 - u.next(api.sbot_user_feed, { 50 - id: id, reverse: true, 51 - limit: 50, live: false 52 - }, ['value', 'sequence']), 53 - pull.through(console.log.bind(console)), 54 - Scroller(div, content, api.message_render, false, false) 55 - ) 56 - 57 - return div 58 - 59 - } 60 - } 61 - 62 - } 1 + module.exports = require('../../patchbay/modules_basic/feed.js')
+1 -99
decent/modules_basic/follow.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - var pull = require('pull-stream') 4 - 5 - //var plugs = require('../plugs') 6 - //var avatar = plugs.first(exports.avatar = []) 7 - //var avatar_name = plugs.first(exports.avatar_name = []) 8 - //var avatar_link = plugs.first(exports.avatar_link = []) 9 - //var message_confirm = plugs.first(exports.message_confirm = []) 10 - //var follower_of = plugs.first(exports.follower_of = []) 11 - 12 - //render a message when someone follows someone, 13 - //so you see new users 14 - function isRelated(value, name) { 15 - return value ? name : value === false ? 'un'+name : '' 16 - } 17 - 18 - exports.needs = { 19 - avatar: 'first', 20 - avatar_name: 'first', 21 - avatar_link: 'first', 22 - message_confirm: 'first', 23 - follower_of: 'first' 24 - } 25 - 26 - exports.gives = { 27 - message_content: true, 28 - message_content_mini: true, 29 - avatar_action: true, 30 - } 31 - 32 - exports.create = function (api) { 33 - var exports = {} 34 - exports.message_content = 35 - exports.message_content_mini = function (msg) { 36 - var content = msg.value.content 37 - if(content.type == 'contact' && content.contact) { 38 - var relation = isRelated(content.following, 'follows') 39 - if(content.blocking) relation = 'blocks' 40 - return [ 41 - relation, ' ', 42 - api.avatar_link(content.contact, api.avatar_name(content.contact), '') 43 - ] 44 - } 45 - } 46 - 47 - exports.message_content = function (msg) { 48 - 49 - var content = msg.value.content 50 - if(content.type == 'contact' && content.contact) { 51 - var relation = isRelated(content.following, 'follows') 52 - if(content.blocking) relation = 'blocks' 53 - return h('div.contact', relation, api.avatar(msg.value.content.contact, 'thumbnail')) 54 - } 55 - } 56 - 57 - exports.avatar_action = function (id) { 58 - var follows_you, you_follow 59 - 60 - var self_id = require('../keys').id 61 - api.follower_of(self_id, id, function (err, f) { 62 - you_follow = f 63 - update() 64 - }) 65 - api.follower_of(id, self_id, function (err, f) { 66 - follows_you = f 67 - update() 68 - }) 69 - 70 - var state = h('label') 71 - var label = h('span') 72 - 73 - function update () { 74 - state.textContent = ( 75 - follows_you && you_follow ? 'friend' 76 - : follows_you ? 'follows you' 77 - : you_follow ? 'you follow' 78 - : '' 79 - ) 80 - 81 - label.textContent = you_follow ? 'unfollow' : 'follow' 82 - } 83 - 84 - return h('div', state, 85 - h('a', {href:'#', onclick: function () { 86 - api.message_confirm({ 87 - type: 'contact', 88 - contact: id, 89 - following: !you_follow 90 - }, function (err, msg) { 91 - if (err) return console.error(err) 92 - you_follow = msg.value.content.following 93 - update() 94 - }) 95 - }}, h('br'), label) 96 - ) 97 - } 98 - return exports 99 - } 1 + module.exports = require('../../patchbay/modules_basic/follow.js')
+1 -30
decent/modules_basic/index.js
··· 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-link.js": require('./message-link.js'), 16 - "message-name.js": require('./message-name.js'), 17 - "message.js": require('./message.js'), 18 - "names.js": require('./names.js'), 19 - "post.js": require('./post.js'), 20 - "private.js": require('./private.js'), 21 - "pub.js": require('./pub.js'), 22 - "public.js": require('./public.js'), 23 - "relationships.js": require('./relationships.js'), 24 - "search-box.js": require('./search-box.js'), 25 - "setup.js": require('./setup'), 26 - "suggest-mentions.js": require('./suggest-mentions.js'), 27 - "thread.js": require('./thread.js'), 28 - "timestamp.js": require('./timestamp.js') 29 - } 30 - 1 + module.exports = require('../../patchbay/modules_basic/index.js')
+1 -116
decent/modules_basic/invite.js
··· 1 - 'use strict' 2 - var ref = require('ssb-ref') 3 - var ssbClient = require('ssb-client') 4 - var id = require('../keys').id 5 - var h = require('hyperscript') 6 - 7 - var Progress = require('hyperprogress') 8 - 9 - //var plugs = require('../plugs') 10 - //var sbot_publish = plugs.first(exports.sbot_publish = []) 11 - //var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = []) 12 - //var follower_of = plugs.first(exports.follower_of = []) 13 - 14 - exports.needs = { 15 - sbot_publish: 'first', 16 - sbot_gossip_connect: 'first', 17 - follower_of: 'first', 18 - invite_parse: 'first', 19 - } 20 - 21 - exports.gives = { 22 - invite_parse: true, 23 - invite_accept: true, 24 - screen_view: true 25 - } 26 - 27 - exports.create = function (api) { 28 - var self 29 - return self = { 30 - invite_parse: function (invite) { 31 - return ref.parseInvite(invite) 32 - }, 33 - 34 - invite_accept: function (invite, onProgress, cb) { 35 - var data = self.invite_parse(invite) 36 - if(!data) return cb(new Error('not a valid invite code:' + invite)) 37 - 38 - onProgress('connecting...') 39 - 40 - api.sbot_gossip_connect(data.remote, function (err) { 41 - if(err) console.log(err) 42 - }) 43 - 44 - ssbClient(null, { 45 - remote: data.invite, 46 - manifest: { invite: {use: 'async'}, getAddress: 'async' } 47 - }, function (err, sbot) { 48 - if(err) return cb(err) 49 - onProgress('requesting follow...') 50 - console.log(sbot) 51 - sbot.invite.use({feed: id}, function (err, msg) { 52 - 53 - //if they already follow us, just check we actually follow them. 54 - if(err) api.follower_of(id, data.key, function (_err, follows) { 55 - if(follows) cb(err) 56 - else next() 57 - }) 58 - else next() 59 - 60 - function next () { 61 - onProgress('following...') 62 - 63 - api.sbot_publish({ 64 - type: 'contact', 65 - contact: data.key, 66 - following: true, 67 - }, cb) 68 - } 69 - }) 70 - }) 71 - }, 72 - 73 - screen_view: function (invite) { 74 - 75 - var data = ref.parseInvite(invite) 76 - if(!data) return 77 - 78 - var progress = Progress(4) 79 - 80 - //connect to server 81 - //request follow 82 - //post pub announce 83 - //post follow pub 84 - var div = h('div.column', 85 - h('div', 86 - "you have been invited to join:", h('br'), 87 - h('code', data.invite) 88 - ), 89 - h('button', 'accept', {onclick: attempt}), 90 - progress 91 - ) 92 - 93 - function attempt () { 94 - self.invite_accept(invite, function (message) { 95 - progress.next(message) 96 - }, function (err) { 97 - if(err) return progress.fail(err) 98 - progress.complete() 99 - //check for redirect 100 - var parts = location.hash.substring(1).split('#') 101 - 102 - //TODO: handle in a consistent way with either hashrouting 103 - //or with tabs... 104 - if(parts[0] === data.invite) 105 - location.hash = '' 106 - else 107 - console.log("NO REDIRECT") 108 - }) 109 - } 110 - 111 - // If we are in the browser, the user can click "accept" to trigger the invite. 112 - 113 - return div 114 - } 115 - } 116 - } 1 + module.exports = require('../../patchbay/modules_basic/invite.js')
+1 -88
decent/modules_basic/like.js
··· 1 - 2 - var h = require('hyperscript') 3 - var u = require('../util') 4 - var pull = require('pull-stream') 5 - 6 - var plugs = require('../plugs') 7 - 8 - //var message_confirm = plugs.first(exports.message_confirm = []) 9 - //var message_link = plugs.first(exports.message_link = []) 10 - //var sbot_links = plugs.first(exports.sbot_links = []) 11 - 12 - exports.needs = { 13 - avatar_name: 'first', 14 - message_confirm: 'first', 15 - message_link: 'first', 16 - sbot_links: 'first' 17 - } 18 - 19 - exports.gives = { 20 - message_content: true, 21 - message_content_mini: true, 22 - message_meta: true, 23 - message_action: true 24 - } 25 - 26 - exports.create = function (api) { 27 - var exports = {} 28 - 29 - exports.message_content = 30 - exports.message_content_mini = function (msg, sbot) { 31 - if(msg.value.content.type !== 'vote') return 32 - var link = msg.value.content.vote.link 33 - return [ 34 - msg.value.content.vote.value > 0 ? 'dug' : 'undug', 35 - ' ', api.message_link(link) 36 - ] 37 - } 38 - 39 - exports.message_meta = function (msg, sbot) { 40 - var digs = h('a') 41 - 42 - var votes = [] 43 - for(var k in CACHE) { 44 - if(CACHE[k].content.type == 'vote' && 45 - (CACHE[k].content.vote == msg.key || 46 - CACHE[k].content.vote.link == msg.key 47 - )) 48 - votes.push({source: CACHE[k].author, dest: k, rel: 'vote'}) 49 - } 50 - 51 - if(votes.length === 1) 52 - digs.textContent = ' 1 Dig' 53 - else if(votes.length > 1) 54 - digs.textContent = ' ' + votes.length + ' Digs' 55 - 56 - pull( 57 - pull.values(votes.map(vote => { 58 - return api.avatar_name(vote.source) 59 - })), 60 - pull.collect(function (err, ary) { 61 - digs.title = ary.map(x => x.innerHTML).join(" ") 62 - }) 63 - ) 64 - 65 - return digs 66 - } 67 - 68 - exports.message_action = function (msg, sbot) { 69 - if(msg.value.content.type !== 'vote') 70 - return h('a.dig', {href: '#', onclick: function () { 71 - var dig = { 72 - type: 'vote', 73 - vote: { link: msg.key, value: 1, expression: 'Dig' } 74 - } 75 - if(msg.value.content.recps) { 76 - dig.recps = msg.value.content.recps.map(function (e) { 77 - return e && typeof e !== 'string' ? e.link : e 78 - }) 79 - dig.private = true 80 - } 81 - //TODO: actually publish... 82 - 83 - api.message_confirm(dig) 84 - }}, 'Dig') 85 - 86 - } 87 - return exports 88 - } 1 + module.exports = require('../../patchbay/modules_basic/like.js')
+1 -40
decent/modules_basic/markdown.js
··· 1 - var markdown = require('ssb-markdown') 2 - var h = require('hyperscript') 3 - var ref = require('ssb-ref') 4 - var emojis = require('emoji-named-characters') 5 - 6 - exports.needs = { 7 - blob_url: 'first' 8 - } 9 - 10 - exports.gives = 'markdown' 11 - 12 - exports.create = function (api) { 13 - 14 - function renderEmoji(emoji) { 15 - return emojis[emoji] || ':' + emoji + ':' 16 - } 17 - 18 - return function (content) { 19 - if('string' === typeof content) 20 - content = {text: content} 21 - //handle patchwork style mentions. 22 - var mentions = {} 23 - if(Array.isArray(content.mentions)) 24 - content.mentions.forEach(function (link) { 25 - if(link.name) mentions["@"+link.name] = link.link 26 - }) 27 - 28 - var md = h('div.markdown') 29 - md.innerHTML = markdown.block(content.text, { 30 - emoji: renderEmoji, 31 - toUrl: function (id) { 32 - if(ref.isBlob(id)) return api.blob_url(id) 33 - return '#'+(mentions[id]?mentions[id]:id) 34 - } 35 - }) 36 - 37 - return md 38 - 39 - } 40 - } 1 + module.exports = require('../../patchbay/modules_basic/markdown.js')
+1 -39
decent/modules_basic/message-link.js
··· 1 - var h = require('hyperscript') 2 - var ref = require('ssb-ref') 3 - 4 - //var first = require('../plugs').first 5 - //var sbot_get = first(exports.sbot_get = []) 6 - //var message_name = first(exports.message_name = []) 7 - 8 - exports.needs = { 9 - message_name: 'first' 10 - } 11 - 12 - exports.gives = 'message_link' 13 - 14 - exports.create = function (api) { 15 - 16 - return function (id) { 17 - 18 - if('string' !== typeof id) 19 - throw new Error('link must be to message id') 20 - 21 - var link = h('a', {href: '#'+id}, id.substring(0, 10)+'...') 22 - 23 - if(ref.isMsg(id)) 24 - api.message_name(id, function (err, name) { 25 - if(err) console.error(err) 26 - else link.textContent = name 27 - }) 28 - 29 - return link 30 - } 31 - } 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 1 + module.exports = require('../../patchbay/modules_basic/message-link.js')
+1 -26
decent/modules_basic/message-name.js
··· 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 - 1 + module.exports = require('../../patchbay/modules_basic/message-name.js')
+1 -141
decent/modules_basic/message.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - var pull = require('pull-stream') 4 - 5 - //var plugs = require('../plugs') 6 - // 7 - //var message_content = plugs.first(exports.message_content = []) 8 - //var message_content_mini = plugs.first(exports.message_content_mini = []) 9 - // 10 - //var avatar = plugs.first(exports.avatar = []) 11 - //var avatar_name = plugs.first(exports.avatar_name = []) 12 - //var avatar_link = plugs.first(exports.avatar_link = []) 13 - //var message_meta = plugs.map(exports.message_meta = []) 14 - //var message_action = plugs.map(exports.message_action = []) 15 - //var message_link = plugs.first(exports.message_link = []) 16 - // 17 - //var sbot_links = plugs.first(exports.sbot_links = []) 18 - 19 - exports.needs = { 20 - message_content: 'first', 21 - message_content_mini: 'first', 22 - avatar: 'first', 23 - avatar_name: 'first', 24 - avatar_link: 'first', 25 - message_meta: 'map', 26 - message_action: 'map', 27 - message_link: 'first', 28 - // sbot_links: 'first' 29 - } 30 - 31 - exports.gives = 'message_render' 32 - 33 - function message_content_mini_fallback(msg) { 34 - return h('code', msg.value.content.type) 35 - } 36 - 37 - exports.create = function (api) { 38 - 39 - function mini(msg, el) { 40 - var content = el 41 - if(content && content.nodeType === 1) 42 - content.classList.add('message_content') 43 - else 44 - content = h('span.message_content', el) 45 - 46 - var div = h('div.message.message--mini.message-card.row', 47 - api.avatar_link(msg.value.author, api.avatar_name(msg.value.author)), 48 - content, 49 - h('span.message_meta.row', api.message_meta(msg)) 50 - ) 51 - div.setAttribute('tabindex', '0') 52 - return div 53 - } 54 - 55 - return function (msg, sbot) { 56 - var el = api.message_content_mini(msg) 57 - if(el) return mini(msg, el) 58 - 59 - var el = api.message_content(msg) 60 - if(!el) return mini(msg, message_content_mini_fallback(msg)) 61 - 62 - var links = [] 63 - for(var k in CACHE) { 64 - var _msg = CACHE[k] 65 - if(Array.isArray(_msg.content.mentions)) { 66 - for(var i = 0; i < _msg.content.mentions.length; i++) 67 - if(_msg.content.mentions[i].link == msg.key) 68 - links.push(k) 69 - } 70 - } 71 - 72 - var backlinks = h('div.backlinks') 73 - if(links.length) 74 - backlinks.appendChild(h('label', 'backlinks:', 75 - h('div', links.map(function (key) { 76 - return api.message_link(key) 77 - })) 78 - )) 79 - 80 - 81 - // pull( 82 - // sbot_links({dest: msg.key, rel: 'mentions', keys: true}), 83 - // pull.collect(function (err, links) { 84 - // if(links.length) 85 - // backlinks.appendChild(h('label', 'backlinks:', 86 - // h('div', links.map(function (link) { 87 - // return message_link(link.key) 88 - // })) 89 - // )) 90 - // }) 91 - // ) 92 - 93 - var content = el 94 - if(content && content.nodeType === 1) 95 - content.classList.add('message_content') 96 - else 97 - content = h('div.message_content', el) 98 - 99 - var replyLink = h('button.btn', {type: 'button'}, 'Reply') 100 - replyLink.addEventListener('click', function () { 101 - var event 102 - try { 103 - event = new CustomEvent('decent:reply', {detail: {msg: msg}}) 104 - } catch (err) { 105 - event = document.createEvent('CustomEvent') 106 - event.initCustomEvent('decent:reply', false, false, {msg: msg}) 107 - } 108 - window.dispatchEvent(event) 109 - }) 110 - 111 - var msgEl = h('div.message.message-card', 112 - h('div.title.row', 113 - h('div.avatar', api.avatar(msg.value.author, 'thumbnail')), 114 - h('div.message_meta.row', api.message_meta(msg)) 115 - ), 116 - content, 117 - h('div.message_actions.row', 118 - h('div.actions', api.message_action(msg), 119 - replyLink 120 - ) 121 - ), 122 - backlinks, 123 - {onkeydown: function (ev) { 124 - //on enter, hit first meta. 125 - if(ev.keyCode == 13) { 126 - 127 - // unless in an input 128 - if (ev.target.nodeName === 'INPUT' 129 - || ev.target.nodeName === 'TEXTAREA') return 130 - 131 - msgEl.querySelector('.enter').click() 132 - } 133 - }} 134 - ) 135 - 136 - // ); hyperscript does not seem to set attributes correctly. 137 - msgEl.setAttribute('tabindex', '0') 138 - 139 - return msgEl 140 - } 141 - } 1 + module.exports = require('../../patchbay/modules_basic/message.js')
+1 -183
decent/modules_basic/names.js
··· 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 - //var plugs = require('../plugs') 10 - //var sbot_links2 = plugs.first(exports.sbot_links2 = []) 11 - //var sbot_query = plugs.first(exports.sbot_query = []) 12 - // 13 - exports.needs = { 14 - sbot_links2: 'first', 15 - sbot_query: 'first' 16 - } 17 - 18 - exports.gives = { 19 - connection_status: true, 20 - signifier: true, 21 - signified: true, 22 - } 23 - 24 - /* 25 - filter(rel: ['mentions', prefix('@')]) | reduce(name: rel[1], value: count()) 26 - */ 27 - 28 - var filter = { 29 - $filter: { 30 - rel: ["mentions", {$prefix: "@"}] 31 - } 32 - } 33 - var map = { 34 - $map: { 35 - name: ['rel', 1], 36 - id: 'dest', 37 - ts: 'ts', 38 - } 39 - } 40 - 41 - var reduce = { 42 - $reduce: { 43 - name: 'name', 44 - id: 'id', 45 - rank: {$count: true}, 46 - ts: {$max: 'ts'} 47 - } 48 - } 49 - 50 - var filter2 = { 51 - $filter: { 52 - value: { 53 - content: { 54 - type: "about", 55 - name: {"$prefix": ""}, 56 - about: {"$prefix": ""} 57 - } 58 - } 59 - } 60 - } 61 - 62 - var map2 = { 63 - $map: { 64 - name: ["value", "content", "name"], 65 - id: ['value', 'content', 'about'], 66 - ts: "timestamp" 67 - } 68 - } 69 - 70 - //union with this query... 71 - 72 - var names = NAMES = [] 73 - function update(name) { 74 - var n = names.find(function (e) { 75 - return e.id == name.id && e.name == e.name 76 - }) 77 - if(!n) { 78 - name.rank = 1 79 - //this should be inserted at the right place... 80 - names.push(name) 81 - } 82 - else 83 - n.rank = n.rank += (name.rank || 1) 84 - } 85 - 86 - var ready = false, waiting = [] 87 - 88 - var merge = { 89 - $reduce: { 90 - name: 'name', 91 - id: 'id', 92 - rank: {$sum: 'rank'}, 93 - ts: {$max: 'ts'} 94 - } 95 - } 96 - 97 - function add_sigil(stream) { 98 - return pull(stream, pull.map(function (e) { 99 - if (e && e.id && e.name && e.id[0] !== e.name[0]) 100 - e.name = e.id[0] + e.name 101 - return e 102 - }) 103 - ) 104 - } 105 - 106 - var queryNamedGitRepos = [ 107 - {$filter: { 108 - value: { 109 - content: { 110 - type: "git-repo", 111 - name: {"$prefix": ""} 112 - } 113 - } 114 - }}, 115 - {$map: { 116 - name: ["value", "content", "name"], 117 - id: ['key'], 118 - ts: "timestamp" 119 - }}, 120 - reduce 121 - ] 122 - exports.create = function (api) { 123 - 124 - var exports = {} 125 - exports.connection_status = function (err) { 126 - if(!err) { 127 - pull( 128 - many([ 129 - api.sbot_links2({query: [filter, map, reduce]}), 130 - add_sigil(api.sbot_query({query: [filter2, map2, reduce]})), 131 - add_sigil(api.sbot_query({query: queryNamedGitRepos})) 132 - ]), 133 - //reducing also ensures order by the lookup properties 134 - //in this case: [name, id] 135 - mfr.reduce(merge), 136 - pull.collect(function (err, ary) { 137 - if(!err) { 138 - NAMES = names = ary 139 - ready = true 140 - while(waiting.length) waiting.shift()() 141 - } 142 - }) 143 - ) 144 - 145 - pull(many([ 146 - api.sbot_links2({query: [filter, map], old: false}), 147 - add_sigil(api.sbot_query({query: [filter2, map2], old: false})), 148 - add_sigil(api.sbot_query({query: queryNamedGitRepos, old: false})) 149 - ]), 150 - pull.drain(update)) 151 - } 152 - } 153 - 154 - function async(fn) { 155 - return function (value, cb) { 156 - function go () { cb(null, fn(value)) } 157 - if(ready) go() 158 - else waiting.push(go) 159 - } 160 - } 161 - 162 - function rank(ary) { 163 - //sort by most used, or most recently used 164 - return ary.sort(function (a, b) { return b.rank - a.rank || b.ts - a.ts }) 165 - } 166 - 167 - //we are just iterating over the entire array. 168 - //if this becomes a problem, maintain two arrays 169 - //one of each sort order, but do not duplicate the objects. 170 - //that should mean the space required is just 2x object references, 171 - //not 2x objects, and we can use binary search to find matches. 172 - 173 - exports.signifier = async(function (id) { 174 - return rank(names.filter(function (e) { return e.id == id})) 175 - }) 176 - 177 - exports.signified = async(function (name) { 178 - var rx = new RegExp('^'+name) 179 - return rank(names.filter(function (e) { return rx.test(e.name) })) 180 - }) 181 - 182 - return exports 183 - } 1 + module.exports = require('../../patchbay/modules_basic/names.js')
+1 -44
decent/modules_basic/post.js
··· 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 - //var plugs = require('../plugs') 9 - //var message_link = plugs.first(exports.message_link = []) 10 - //var markdown = plugs.first(exports.markdown = []) 11 - // 12 - 13 - exports.needs = { message_link: 'first', markdown: 'first' } 14 - 15 - exports.gives = { 16 - message_content: true 17 - } 18 - 19 - exports.create = function (api) { 20 - var exports = {} 21 - 22 - exports.message_content = function (data) { 23 - if(!data.value.content || !data.value.content.text) return 24 - 25 - var root = data.value.content.root 26 - if(!root) return api.markdown(data.value.content) 27 - var re = h('span', 're: ', api.message_link(root)) 28 - 29 - return h('div', re, api.markdown(data.value.content)) 30 - 31 - } 32 - 33 - return exports 34 - } 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 1 + module.exports = require('../../patchbay/modules_basic/post.js')
+1 -117
decent/modules_basic/private.js
··· 1 - 'use strict' 2 - var h = require('hyperscript') 3 - var u = require('../util') 4 - var pull = require('pull-stream') 5 - var Scroller = require('pull-scroll') 6 - var ref = require('ssb-ref') 7 - var keys = require('../keys') 8 - 9 - function map(ary, iter) { 10 - if(Array.isArray(ary)) return ary.map(iter) 11 - } 12 - 13 - exports.needs = { 14 - message_render: 'first', 15 - message_compose: 'first', 16 - message_unbox: 'first', 17 - sbot_log: 'first', 18 - avatar_image_link: 'first' 19 - } 20 - 21 - exports.gives = { 22 - builtin_tabs: true, 23 - screen_view: true, 24 - message_meta: true, 25 - message_content_mini: true 26 - } 27 - 28 - exports.create = function (api) { 29 - var id = keys.id 30 - 31 - function privateStream (opts) { 32 - return pull( 33 - u.next(api.sbot_log, opts), 34 - pull.map(function (msg) { 35 - if(!msg || !msg.value || 'string' != typeof msg.value.content) return null 36 - var unboxed = api.message_unbox(msg) 37 - if(unboxed) { 38 - unboxed.value.private = true 39 - return unboxed 40 - } 41 - return null 42 - }), 43 - pull.filter(Boolean) 44 - ) 45 - } 46 - 47 - return { 48 - builtin_tabs: function () { 49 - return ['private'] 50 - }, 51 - 52 - screen_view: function (path) { 53 - if(path !== 'private') return 54 - 55 - var div = h('div.column.scroller', 56 - {style: {'overflow':'auto'}}) 57 - div.setAttribute('data-icon', 'lock') 58 - 59 - var compose = api.message_compose( 60 - {type: 'post', recps: [], private: true}, 61 - { 62 - prepublish: function (msg) { 63 - msg.recps = [id].concat(msg.mentions).filter(function (e) { 64 - return ref.isFeed('string' === typeof e ? e : e.link) 65 - }) 66 - if(!msg.recps.length) 67 - throw new Error('cannot make private message without recipients - just mention the user in an at reply in the message you send') 68 - return msg 69 - }, 70 - placeholder: 'Write a private message' 71 - } 72 - ) 73 - 74 - var content = h('div.column.scroller__content') 75 - div.appendChild(h('div.scroller__wrapper', compose, content)) 76 - 77 - pull( 78 - privateStream({old: false, limit: 100}), 79 - Scroller(div, content, api.message_render, true, false) 80 - ) 81 - 82 - pull( 83 - privateStream({reverse: true, limit: 1000}), 84 - Scroller(div, content, api.message_render, false, false, function (err) { 85 - if(err) throw err 86 - }) 87 - ) 88 - 89 - return div 90 - }, 91 - 92 - message_meta: function (msg) { 93 - var content = msg.value.content 94 - var recps = content && content.recps 95 - var isEncryptedString = 'string' === typeof content 96 - var isPrivate = msg.value.private || 97 - (content && content.private) || 98 - (Array.isArray(recps) && recps.length) || 99 - isEncryptedString 100 - 101 - if(!isPrivate) return 102 - 103 - return h('span.message_meta.row', map(recps, function (id) { 104 - return api.avatar_image_link('string' == typeof id ? id : id.link, 'thumbnail') 105 - })) 106 - }, 107 - 108 - message_content_mini: function (msg, sbot) { 109 - var isEncrypted = typeof msg.value.content === 'string' 110 - if(!msg.value.private && !isEncrypted) return 111 - if(msg.value.content && typeof msg.value.content === 'object' && msg.value.content.text) 112 - return 113 - return '🔒' 114 - } 115 - } 116 - 117 - } 1 + module.exports = require('../../patchbay/modules_basic/private.js')
+1 -48
decent/modules_basic/pub.js
··· 1 - var h = require('hyperscript') 2 - //var plugs = require('../plugs') 3 - //var avatar_name = plugs.first(exports.avatar_name = []) 4 - //var avatar_link = plugs.first(exports.avatar_link = []) 5 - // 6 - exports.needs = { 7 - avatar_name: 'first', 8 - avatar_link: 'first' 9 - } 10 - 11 - exports.gives = { 12 - message_content: true, 13 - message_content_mini: true 14 - } 15 - 16 - exports.create = function (api) { 17 - var exports = {} 18 - 19 - exports.message_content = function (msg, sbot) { 20 - var c = msg.value.content 21 - if (c.type === 'pub') { 22 - var address = c.address || {} 23 - return [ 24 - h('p', 'announced an address for ', 25 - api.avatar_link(address.key, api.avatar_name(address.key)), ':'), 26 - h('blockquote', 27 - h('code', address.host + ':' + address.port) 28 - ) 29 - ] 30 - } 31 - } 32 - 33 - exports.message_content_mini = function (msg) { 34 - var c = msg.value.content 35 - if (c.type !== 'pub') return 36 - var address = c.address || {} 37 - if(!address.key || !address.host || !address.port) return 38 - return [ 39 - 'announced ', 40 - api.avatar_link(address.key, api.avatar_name(address.key)), 41 - ': ', 42 - h('code', address.host + ':' + address.port) 43 - ] 44 - } 45 - 46 - return exports 47 - 48 - } 1 + module.exports = require('../../patchbay/modules_basic/pub.js')
+1 -88
decent/modules_basic/public.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - var pull = require('pull-stream') 4 - var Scroller = require('pull-scroll') 5 - 6 - //var plugs = require('../plugs') 7 - //var message_render = plugs.first(exports.message_render = []) 8 - //var message_compose = plugs.first(exports.message_compose = []) 9 - //var sbot_log = plugs.first(exports.sbot_log = []) 10 - 11 - exports.needs = { 12 - message_render: 'first', 13 - message_compose: 'first', 14 - sbot_query: 'first', 15 - } 16 - 17 - exports.gives = { 18 - builtin_tabs: true, screen_view: true 19 - } 20 - 21 - exports.create = function (api) { 22 - 23 - function publicFilter (opts) { 24 - opts = opts || {} 25 - var filter = { 26 - $filter: { 27 - rts: {} 28 - } 29 - } 30 - if(opts.lt != null) filter.$filter.rts.$lt = opts.lt 31 - if(opts.gt != null) filter.$filter.rts.$gt = opts.gt 32 - if(!filter.$filter.rts.$lt && !filter.$filter.rts.$gt) 33 - filter.$filter.rts.$gt = 0 34 - return filter 35 - } 36 - 37 - function publicQuery (opts) { 38 - opts = opts || {} 39 - return api.sbot_query({ 40 - query: [publicFilter(opts)], 41 - reverse: opts.reverse, 42 - limit: opts.limit, 43 - live: opts.live, 44 - old: opts.old 45 - }) 46 - } 47 - 48 - return { 49 - builtin_tabs: function () { 50 - return ['public'] 51 - }, 52 - 53 - screen_view: function (path, sbot) { 54 - if(path === 'public') { 55 - 56 - var content = h('div.column.scroller__content') 57 - var div = h('div.column.scroller', 58 - {style: {'overflow':'auto'}}, 59 - h('div.scroller__wrapper', 60 - api.message_compose( 61 - {type: 'post'}, 62 - {placeholder: 'Write a public message', modal: true, triggerLabel: 'Compose', listenReplyEvents: true} 63 - ), 64 - content 65 - ) 66 - ) 67 - div.setAttribute('data-icon', 'key') 68 - 69 - pull( 70 - publicQuery({old: false, live: true}), 71 - Scroller(div, content, api.message_render, true, false) 72 - ) 73 - 74 - pull( 75 - u.next(publicQuery, { 76 - reverse: true, 77 - limit: 100, 78 - live: false, 79 - old: true 80 - }, ['rts']), 81 - Scroller(div, content, api.message_render, false, false) 82 - ) 83 - 84 - return div 85 - } 86 - } 87 - } 88 - } 1 + module.exports = require('../../patchbay/modules_basic/public.js')
+1 -62
decent/modules_basic/relationships.js
··· 1 - var pull = require('pull-stream') 2 - //var plugs = require('../plugs') 3 - 4 - //var sbot_query = plugs.first(exports.sbot_query = []) 5 - 6 - //this is a bit crude, and doesn't actually show unfollows yet. 7 - 8 - function makeQuery (a, b) { 9 - return {"$filter": { 10 - value: { 11 - author: a, 12 - content: { 13 - type: 'contact', 14 - contact: b, 15 - following: true 16 - } 17 - }, 18 - }} 19 - } 20 - 21 - 22 - exports.needs = { sbot_query: 'first' } 23 - 24 - exports.gives = { 25 - follows: true, 26 - followers: true, 27 - follower_of: true 28 - } 29 - 30 - exports.create = function (api) { 31 - 32 - return { 33 - follows: function (id, cb) { 34 - return api.sbot_query({query: [ 35 - makeQuery(id, {$prefix:"@"}), 36 - {"$map": ['value', 'content', 'contact']} 37 - ]}) 38 - }, 39 - 40 - followers: function (id) { 41 - return api.sbot_query({query: [ 42 - makeQuery({$prefix:"@"}, id), 43 - {"$map": ['value', 'author']} 44 - ]}) 45 - }, 46 - 47 - follower_of: function (source, dest, cb) { 48 - pull( 49 - api.sbot_query({query: [ 50 - makeQuery(source, dest), 51 - {$map: ['value', 'content', 'following']} 52 - ]}), 53 - pull.collect(function (err, ary) { 54 - if(err) return cb(err) 55 - else cb(null, ary.pop()) //will be true, or undefined/false 56 - }) 57 - ) 58 - } 59 - } 60 - 61 - } 62 - 1 + module.exports = require('../../patchbay/modules_basic/relationships.js')
+1 -78
decent/modules_basic/search-box.js
··· 1 - 'use strict' 2 - var cont = require('cont') 3 - var h = require('hyperscript') 4 - var suggest = require('suggest-box') 5 - var pull = require('pull-stream') 6 - 7 - //var plugs = require('../plugs') 8 - //var sbot_query = plugs.first(exports.sbot_query = []) 9 - //var sbot_links2 = plugs.first(exports.sbot_links2 = []) 10 - //var suggest_search = plugs.asyncConcat(exports.suggest_search = []) 11 - 12 - exports.needs = { 13 - sbot_query: 'first', sbot_links2: 'first', 14 - suggest_search: 'map' //REWRITE 15 - } 16 - 17 - var channels = [] 18 - 19 - exports.gives = 'search_box' 20 - 21 - exports.create = function (api) { 22 - 23 - return function (go) { 24 - 25 - var suggestBox 26 - var search = h('input.searchprompt', { 27 - type: 'search', 28 - placeholder: 'Commands', 29 - onkeydown: function (ev) { 30 - switch (ev.keyCode) { 31 - case 13: // enter 32 - if (suggestBox && suggestBox.active) { 33 - suggestBox.complete() 34 - ev.stopPropagation() 35 - } 36 - if (go(search.value.trim(), !ev.ctrlKey)) 37 - search.blur() 38 - return 39 - case 27: // escape 40 - ev.preventDefault() 41 - search.blur() 42 - return 43 - } 44 - } 45 - }) 46 - 47 - search.activate = function (sigil, ev) { 48 - search.focus() 49 - ev.preventDefault() 50 - if (search.value[0] === sigil) { 51 - search.selectionStart = 1 52 - search.selectionEnd = search.value.length 53 - } else { 54 - search.value = sigil 55 - } 56 - } 57 - 58 - var suggestions = {} 59 - 60 - // delay until the element has a parent 61 - setTimeout(function () { 62 - suggestBox = suggest(search, function (word, cb) { 63 - cont.para(api.suggest_search(word)) 64 - (function (err, ary) { 65 - if(err) return cb(err) 66 - 67 - cb(null, ary.filter(Boolean).reduce(function (a, b) { 68 - return a.concat(b) 69 - }, [])) 70 - }) 71 - }, {}) 72 - }, 10) 73 - 74 - return search 75 - } 76 - 77 - } 78 - 1 + module.exports = require('../../patchbay/modules_basic/search-box.js')
+1 -160
decent/modules_basic/setup.js
··· 1 - 2 - var h = require('hyperscript') 3 - var pull = require('pull-stream') 4 - 5 - //var plugs = require('../plugs') 6 - // 7 - //var avatar_edit = plugs.first(exports.avatar_edit = []) 8 - //var invite_parse = plugs.first(exports.invite_parse = []) 9 - //var invite_accept = plugs.first(exports.invite_accept = []) 10 - //var sbot_progress = plugs.first(exports.sbot_progress = []) 11 - //var sbot_query = plugs.first(exports.sbot_query = []) 12 - //var avatar = plugs.first(exports.avatar = []) 13 - 14 - exports.needs = { 15 - avatar: 'first', 16 - avatar_edit: 'first', 17 - invite_parse: 'first', 18 - invite_accept: 'first', 19 - sbot_progress: 'first', 20 - sbot_query: 'first' 21 - } 22 - 23 - //maybe this could show the pubs, or 24 - //if someone locally follows you, 25 - //it could show the second degree pubs? 26 - 27 - //maybe show the network as animated graph? 28 - 29 - function followers_query (id) { 30 - return [{$filter: { 31 - value: {content: { 32 - type: "contact", 33 - contact: id, 34 - following: true, 35 - // autofollow: true 36 - }} 37 - }}] 38 - } 39 - 40 - exports.create = function (api) { 41 - 42 - var exports = {} 43 - 44 - //test whether we are connected to the ssb network. 45 - exports.setup_is_fresh_install = function (cb) { 46 - //test by checking whether you have any friends following you? 47 - pull( 48 - api.sbot_query({query: followers_query(id), limit: 1, live: false}), 49 - pull.collect(function (err, ary) { 50 - cb(err, !!ary.length) 51 - }) 52 - ) 53 - } 54 - 55 - function invite_form () { 56 - var accept = h('button', 'enter code', {disabled: true, onclick: function () { 57 - api.invite_accept(input.value, function (msg) { 58 - status.textContent = msg 59 - }, function (err) { 60 - if(err) { 61 - accept.textContent = 'error:'+(err.message || err.stack || error.type) 62 - console.error(err) 63 - } 64 - else { 65 - input.value = '' 66 - accept.textContent = 'success!' 67 - } 68 - }) 69 - }}) 70 - 71 - function parseInput () { 72 - if(!input.value) { 73 - accept.disabled = true 74 - accept.textContent = 'enter code' 75 - } 76 - else if(!invite_parse(input.value)) { 77 - accept.disabled = true 78 - accept.textContent = 'invalid code' 79 - } 80 - else { 81 - accept.disabled = false 82 - accept.textContent = 'accept' 83 - } 84 - } 85 - 86 - var input = h('input.wide', {placeholder: 'invite code', oninput: parseInput, onchange: parseInput}) 87 - 88 - return h('div.invite-form.row', input, accept) 89 - } 90 - 91 - exports.progress_bar = function () { 92 - var liquid = h('div.hyperprogress__liquid', '.') 93 - var bar = h('div.hyperprogress__bar', liquid) 94 - liquid.style.width = '0%' 95 - 96 - pull( 97 - api.sbot_progress(), 98 - pull.drain(function (e) { 99 - liquid.style.width = Math.round((e.progress/e.total)*100)+'%' 100 - }) 101 - ) 102 - 103 - return bar 104 - } 105 - 106 - //show the first 5 followers, and how they join you to the network. 107 - //so this will show if a local peer follows you. 108 - 109 - //when you join the network, I want this to show as people follow you. 110 - //that could be when a pub accepts the invite, or when a local peer accepts. 111 - 112 - exports.setup_joined_network = function (id) { 113 - var followers = h('div.column') 114 - var label = h('label', 'not connected to a network') 115 - var joined = h('div.setup__joined', label, followers) 116 - 117 - pull( 118 - api.sbot_query({query: followers_query(id), limit: 5, live: true, sync: false}), 119 - pull.drain(function (follower) { 120 - if(follower.sync) return 121 - label.textContent = 'connected to network via...' 122 - followers.appendChild( 123 - api.avatar(follower.value.author, 'thumbnail') 124 - ) 125 - }) 126 - ) 127 - 128 - return joined 129 - } 130 - 131 - exports.screen_view = function (path) { 132 - 133 - if(path !== 'setup') return 134 - 135 - var id = require('../keys').id 136 - 137 - //set up an avatar 138 - 139 - 140 - var status = h('span') 141 - var invite = h('input', {placeholder: 'invite code'}) 142 - return h('div.scroller', 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. 152 - 153 - exports.progress_bar(), 154 - exports.setup_joined_network(require('../keys').id) 155 - )) 156 - } 157 - 158 - return exports 159 - 160 - } 1 + module.exports = require('../../patchbay/modules_basic/setup.js')
+1 -72
decent/modules_basic/suggest-mentions.js
··· 1 - var pull = require('pull-stream') 2 - function isImage (filename) { 3 - return /\.(gif|jpg|png|svg)$/i.test(filename) 4 - } 5 - 6 - //var sbot_links2 = require('../plugs').first(exports.sbot_links2 = []) 7 - //var blob_url = require('../plugs').first(exports.blob_url = []) 8 - //var signified = require('../plugs').first(exports.signified = []) 9 - //var builtin_tabs = require('../plugs').map(exports.builtin_tabs = []) 10 - 11 - exports.needs = { 12 - sbot_links2: 'first', 13 - blob_url: 'first', 14 - signified: 'first', 15 - builtin_tabs: 'map' 16 - } 17 - 18 - exports.gives = { 19 - suggest_mentions: true, 20 - suggest_search: true 21 - } 22 - 23 - exports.create = function (api) { 24 - 25 - return { 26 - suggest_mentions: function (word) { 27 - return function (cb) { 28 - if(!/^[%&@]\w/.test(word)) return cb() 29 - 30 - api.signified(word, function (err, names) { 31 - if(err) cb(err) 32 - else cb(null, names.map(function (e) { 33 - return { 34 - title: e.name + ': ' + e.id.substring(0,10)+' ('+e.rank+')', 35 - value: '['+e.name+']('+e.id+')', 36 - rank: e.rank, 37 - //TODO: avatar images... 38 - } 39 - })) 40 - }) 41 - } 42 - }, 43 - 44 - suggest_search: function (query) { 45 - return function (cb) { 46 - if(/^[@%]\w/.test(query)) { 47 - api.signified(query, function (_, names) { 48 - cb(null, names.map(function (e) { 49 - return { 50 - title: e.name + ':'+e.id.substring(0, 10), 51 - value: e.id, 52 - subtitle: e.rank, 53 - rank: e.rank 54 - } 55 - })) 56 - }) 57 - 58 - } else if(/^\//.test(query)) { 59 - var tabs = [].concat.apply([], api.builtin_tabs()) 60 - cb(null, tabs.filter(function (name) { 61 - return name.substr(0, query.length) === query 62 - }).map(function (name) { 63 - return { 64 - title: name, 65 - value: name, 66 - } 67 - })) 68 - } else cb() 69 - } 70 - } 71 - } 72 - } 1 + module.exports = require('../../patchbay/modules_basic/suggest-mentions.js')
+1 -157
decent/modules_basic/thread.js
··· 1 - var pull = require('pull-stream') 2 - var Cat = require('pull-cat') 3 - var sort = require('ssb-sort') 4 - var ref = require('ssb-ref') 5 - var h = require('hyperscript') 6 - var u = require('../util') 7 - var Scroller = require('pull-scroll') 8 - var self_id = require('../keys').id 9 - 10 - function once (cont) { 11 - var ended = false 12 - return function (abort, cb) { 13 - if(abort) return cb(abort) 14 - else if (ended) return cb(ended) 15 - else 16 - cont(function (err, data) { 17 - if(err) return cb(ended = err) 18 - ended = true 19 - cb(null, data) 20 - }) 21 - } 22 - } 23 - 24 - //var plugs = require('../plugs') 25 - // 26 - //var message_render = plugs.first(exports.message_render = []) 27 - //var message_name = plugs.first(exports.message_name = []) 28 - //var message_compose = plugs.first(exports.message_compose = []) 29 - //var message_unbox = plugs.first(exports.message_unbox = []) 30 - // 31 - //var sbot_get = plugs.first(exports.sbot_get = []) 32 - //var sbot_links = plugs.first(exports.sbot_links = []) 33 - 34 - exports.needs = { 35 - message_render: 'first', 36 - message_name: 'first', 37 - message_compose: 'first', 38 - message_unbox: 'first', 39 - sbot_get: 'first', 40 - sbot_links: 'first' 41 - } 42 - 43 - exports.gives = 'screen_view' 44 - 45 - 46 - exports.create = function (api) { 47 - 48 - function getThread (root, cb) { 49 - //in this case, it's inconvienent that panel only takes 50 - //a stream. maybe it would be better to accept an array? 51 - 52 - api.sbot_get(root, function (err, value) { 53 - if (err) return cb(err) 54 - var msg = {key: root, value: value} 55 - // if(value.content.root) return getThread(value.content.root, cb) 56 - 57 - pull( 58 - api.sbot_links({rel: 'root', dest: root, values: true, keys: true}), 59 - pull.collect(function (err, ary) { 60 - if(err) return cb(err) 61 - ary.unshift(msg) 62 - cb(null, ary) 63 - }) 64 - ) 65 - }) 66 - 67 - } 68 - 69 - return function (id) { 70 - if(ref.isMsg(id)) { 71 - function normalizeId (value) { 72 - try { return decodeURIComponent(value) } catch (err) { return value } 73 - } 74 - var normalizedId = normalizeId(id) 75 - var autoOpen = false 76 - try { 77 - autoOpen = window.sessionStorage.getItem('decent_reply_intent') === normalizedId 78 - if (autoOpen) window.sessionStorage.removeItem('decent_reply_intent') 79 - } catch (err) {} 80 - 81 - var meta = { 82 - type: 'post', 83 - root: id, 84 - branch: id //mutated when thread is loaded. 85 - } 86 - 87 - var content = h('div.column.scroller__content') 88 - var div = h('div.column.scroller', 89 - {style: {'overflow-y': 'auto'}}, 90 - h('div.scroller__wrapper', 91 - content, 92 - api.message_compose( 93 - meta, 94 - { 95 - shrink: false, 96 - placeholder: 'Write a reply', 97 - modal: true, 98 - triggerLabel: 'Reply', 99 - title: 'Reply', 100 - autoOpen: autoOpen, 101 - listenReplyEvents: true 102 - } 103 - ) 104 - ) 105 - ) 106 - 107 - api.message_name(id, function (err, name) {}) 108 - 109 - pull( 110 - api.sbot_links({ 111 - rel: 'root', dest: id, keys: true, old: false 112 - }), 113 - pull.drain(function (msg) { 114 - loadThread() //redraw thread 115 - }, function () {} ) 116 - ) 117 - 118 - 119 - function loadThread () { 120 - getThread(id, function (err, thread) { 121 - //would probably be better keep an id for each message element 122 - //(i.e. message key) and then update it if necessary. 123 - //also, it may have moved (say, if you received a missing message) 124 - content.innerHTML = '' 125 - if(err) return content.appendChild(h('pre', err.stack)) 126 - 127 - //decrypt 128 - thread = thread.map(function (msg) { 129 - return 'string' === typeof msg.value.content ? api.message_unbox(msg) : msg 130 - }) 131 - 132 - if(err) return content.appendChild(h('pre', err.stack)) 133 - sort(thread).map(api.message_render).filter(Boolean).forEach(function (el) { 134 - content.appendChild(el) 135 - }) 136 - 137 - var branches = sort.heads(thread) 138 - meta.branch = branches.length > 1 ? branches : branches[0] 139 - meta.root = thread[0].value.content.root || thread[0].key 140 - meta.channel = thread[0].value.content.channel 141 - 142 - var recps = thread[0].value.content.recps 143 - var private = thread[0].value.private 144 - if(private) { 145 - if(recps) 146 - meta.recps = recps 147 - else 148 - meta.recps = [thread[0].value.author, self_id] 149 - } 150 - }) 151 - } 152 - 153 - loadThread() 154 - return div 155 - } 156 - } 157 - } 1 + module.exports = require('../../patchbay/modules_basic/thread.js')
+1 -28
decent/modules_basic/timestamp.js
··· 1 - var h = require('hyperscript') 2 - var human = require('human-time') 3 - 4 - exports.needs = {} 5 - 6 - exports.gives = 'message_meta' 7 - 8 - exports.create = function () { 9 - 10 - function updateTimestampEl(el) { 11 - el.firstChild.nodeValue = human(new Date(el.timestamp)) 12 - return el 13 - } 14 - 15 - setInterval(function () { 16 - var els = [].slice.call(document.querySelectorAll('.timestamp')) 17 - els.forEach(updateTimestampEl) 18 - }, 60e3) 19 - 20 - return function (msg) { 21 - return updateTimestampEl(h('a.enter.timestamp', { 22 - href: '#'+msg.key, 23 - timestamp: msg.value.timestamp, 24 - title: new Date(msg.value.timestamp) 25 - }, '')) 26 - } 27 - 28 - } 1 + module.exports = require('../../patchbay/modules_basic/timestamp.js')
+1 -10
decent/modules_core/_screen_view.js
··· 1 - 2 - //this is just an UGLY HACK, because depject does not 3 - //support recursion... 4 - 5 - //used by tabs and split views 6 - 7 - var sv = require('../plugs').first(exports.screen_view = []) 8 - exports._screen_view = function (value) { 9 - return sv(value) 10 - } 1 + module.exports = require('../../patchbay/modules_core/_screen_view.js')
+1 -15
decent/modules_core/blob-url.js
··· 1 - var config = require('../config') 2 - 3 - module.exports = { 4 - gives: 'blob_url', 5 - create: function () { 6 - return function (link) { 7 - // unwrap common { link: '&...' } shapes, possibly nested 8 - while (link && typeof link === 'object' && typeof link.link !== 'undefined') { 9 - link = link.link 10 - } 11 - 12 - return config().blobsUrl + '/' + String(link) 13 - } 14 - } 15 - } 1 + module.exports = require('../../patchbay/modules_core/blob-url.js')
+1 -11
decent/modules_core/blobs-url.js
··· 1 - var config = require('../config') 2 - 3 - module.exports = { 4 - gives: 'blobs_url', 5 - create: function () { 6 - return function () { 7 - return config().blobsUrl 8 - } 9 - } 10 - } 11 - 1 + module.exports = require('../../patchbay/modules_core/blobs-url.js')
+1 -60
decent/modules_core/crypto.js
··· 1 - var ref = require('ssb-ref') 2 - var keys = require('../keys') 3 - var ssbKeys = require('ssb-keys') 4 - 5 - function unbox_value(msg) { 6 - var plaintext = ssbKeys.unbox(msg.content, keys) 7 - if(!plaintext) return null 8 - return { 9 - previous: msg.previous, 10 - author: msg.author, 11 - sequence: msg.sequence, 12 - timestamp: msg.timestamp, 13 - hash: msg.hash, 14 - content: plaintext, 15 - private: true 16 - } 17 - } 18 - 19 - 20 - module.exports = { 21 - 22 - needs: {sbot_publish: 'first'}, 23 - gives: { 24 - message_unbox: true, message_box: true, publish: true 25 - }, 26 - create: function (api) { 27 - 28 - var exports = {} 29 - exports.message_unbox = function (msg) { 30 - if(msg.value) { 31 - var value = unbox_value(msg.value) 32 - if(value) 33 - return { 34 - key: msg.key, value: value, timestamp: msg.timestamp 35 - } 36 - } 37 - else 38 - return unbox_value(msg) 39 - } 40 - 41 - exports.message_box = function (content) { 42 - return ssbKeys.box(content, content.recps.map(function (e) { 43 - return ref.isFeed(e) ? e : e.link 44 - })) 45 - } 46 - 47 - exports.publish = function (content, cb) { 48 - if(content.recps) 49 - content = exports.message_box(content) 50 - api.sbot_publish(content, function (err, msg) { 51 - if(err) throw err 52 - console.log('PUBLISHED', msg) 53 - if(cb) cb(err, msg) 54 - }) 55 - } 56 - 57 - return exports 58 - } 59 - } 60 - 1 + module.exports = require('../../patchbay/modules_core/crypto.js')
+1 -74
decent/modules_core/file-input.js
··· 1 - var u = require('../util') 2 - var h = require('hyperscript') 3 - var pull = require('pull-stream') 4 - var mime = require('simple-mime')('application/octect-stream') 5 - var split = require('split-buffer') 6 - 7 - var fileInputId = 0 8 - 9 - module.exports = { 10 - needs: {blobs_url: 'first'}, 11 - gives: 'file_input', 12 - create: function (api) { 13 - 14 - function blobsAddUrl () { 15 - var url = api.blobs_url && api.blobs_url() 16 - if (!url) return '/blobs/add' 17 - var addUrl = url.replace(/\/blobs\/get\/?$/, '/blobs/add') 18 - return addUrl === url ? '/blobs/add' : addUrl 19 - } 20 - 21 - function uploadViaHttp (file, cb) { 22 - var xhr = new XMLHttpRequest() 23 - xhr.open('POST', blobsAddUrl(), true) 24 - xhr.responseType = 'text' 25 - xhr.onload = function () { 26 - if (xhr.status >= 200 && xhr.status < 300) 27 - cb(null, (xhr.responseText || '').trim()) 28 - else 29 - cb(new Error('blob upload failed: status ' + xhr.status)) 30 - } 31 - xhr.onerror = function () { 32 - cb(new Error('blob upload network error')) 33 - } 34 - xhr.send(file) 35 - } 36 - 37 - return function FileInput(onAdded) { 38 - var id = 'file-input-' + (fileInputId++) 39 - var input = h('input', { 40 - id: id, 41 - type: 'file', 42 - style: { 43 - position: 'absolute', 44 - left: '-9999px' 45 - }, 46 - onchange: function (ev) { 47 - var file = ev.target.files[0] 48 - if (!file) return 49 - 50 - uploadViaHttp(file, function (err, hash) { 51 - if (err) { 52 - console.error(err) 53 - return 54 - } 55 - onAdded({ 56 - link: hash, 57 - name: file.name, 58 - size: file.size, 59 - type: file.type || mime(file.name) 60 - }) 61 - }) 62 - } 63 - }) 64 - 65 - return h('span', [ 66 - h('label.btn', {htmlFor: id}, [ 67 - h('span.material-symbols-outlined.nav__icon', 'attach_file'), 68 - 'Browse' 69 - ]), 70 - input 71 - ]) 72 - } 73 - } 74 - } 1 + module.exports = require('../../patchbay/modules_core/file-input.js')
+7 -11
decent/modules_core/index.js
··· 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 - "blobs-url.js": require('./blobs-url.js'), 6 - "crypto.js": require('./crypto.js'), 7 - "file-input.js": require('./file-input.js'), 8 - "menu.js": require('./menu.js'), 9 - "message-confirm.js": require('./message-confirm.js'), 10 - "sbot.js": require('./sbot.js') 11 - } 1 + var patchbayCore = require('../../patchbay/modules_core') 2 + 3 + module.exports = Object.assign({}, patchbayCore, { 4 + 'app.js': require('./app.js') 5 + }) 6 + 7 + delete module.exports['tabs.js']
+1 -24
decent/modules_core/menu.js
··· 1 - var h = require('hyperscript') 2 - 3 - module.exports = { 4 - needs: {}, 5 - gives: {connection_status: true, menu: true}, 6 - create: function () { 7 - var status = h('span.menu', h('span.status.error')) //start off disconnected 8 - 9 - return { 10 - connection_status: function (err) { 11 - var dot = status.firstChild 12 - if (err) dot.classList.add('error') 13 - else dot.classList.remove('error') 14 - }, 15 - menu: function () { 16 - return status 17 - } 18 - } 19 - } 20 - } 21 - 22 - 23 - 24 - 1 + module.exports = require('../../patchbay/modules_core/menu.js')
+1 -68
decent/modules_core/message-confirm.js
··· 1 - var lightbox = require('hyperlightbox') 2 - var h = require('hyperscript') 3 - var u = require('../util') 4 - var self_id = require('../keys').id 5 - //publish or add 6 - 7 - var plugs = require('../plugs') 8 - 9 - exports.needs = { 10 - publish: 'first', message_content: 'first', avatar: 'first', 11 - message_meta: 'map' 12 - } 13 - 14 - exports.gives = 'message_confirm' 15 - 16 - //var publish = plugs.first(exports.sbot_publish = []) 17 - //var message_content = plugs.first(exports.message_content = []) 18 - //var avatar = plugs.first(exports.avatar = []) 19 - //var message_meta = plugs.map(exports.message_meta = []) 20 - // 21 - exports.create = function (api) { 22 - return function (content, cb) { 23 - 24 - cb = cb || function () {} 25 - 26 - var lb = lightbox() 27 - document.body.appendChild(lb) 28 - 29 - var msg = { 30 - key: "DRAFT", 31 - value: { 32 - author: self_id, 33 - previous: null, 34 - sequence: null, 35 - timestamp: Date.now(), 36 - content: content 37 - } 38 - } 39 - 40 - var okay = h('button.btn.btn-primary', 'Publish', {onclick: function () { 41 - lb.remove() 42 - api.publish(content, cb) 43 - }}) 44 - 45 - var cancel = h('button.btn', 'Cancel', {onclick: function () { 46 - lb.remove() 47 - cb(null) 48 - }}) 49 - 50 - okay.addEventListener('keydown', function (ev) { 51 - if(ev.keyCode === 27) cancel.click() //escape 52 - }) 53 - 54 - lb.show(h('div.column.message-confirm', 55 - h('div.message.message-card', 56 - h('div.title.row', 57 - h('div.avatar', api.avatar(msg.value.author, 'thumbnail')), 58 - h('div.message_meta.row', api.message_meta(msg)) 59 - ), 60 - h('div.message_content', api.message_content(msg) 61 - || h('pre', JSON.stringify(msg, null, 2))), 62 - h('div.row.message-confirm__controls', okay, cancel) 63 - ), 64 - )) 65 - 66 - okay.focus() 67 - } 68 - } 1 + module.exports = require('../../patchbay/modules_core/message-confirm.js')
+1 -165
decent/modules_core/sbot.js
··· 1 - var pull = require('pull-stream') 2 - var ssbKeys = require('ssb-keys') 3 - var ref = require('ssb-ref') 4 - var Reconnect = require('pull-reconnect') 5 - var path = require('path') 6 - var config = require('ssb-config/inject')(process.env.ssb_appname) 7 - config.keys = ssbKeys.loadOrCreateSync(path.join(config.path, 'secret')) 8 - 9 - function Hash (onHash) { 10 - var buffers = [] 11 - return pull.through(function (data) { 12 - buffers.push('string' === typeof data 13 - ? new Buffer(data, 'utf8') 14 - : data 15 - ) 16 - }, function (err) { 17 - if(err && !onHash) throw err 18 - var b = buffers.length > 1 ? Buffer.concat(buffers) : buffers[0] 19 - var h = '&'+ssbKeys.hash(b) 20 - onHash && onHash(err, h) 21 - }) 22 - } 23 - //uncomment this to use from browser... 24 - //also depends on having ssb-ws installed. 25 - //var createClient = require('ssb-lite') 26 - var createClient = require('ssb-client') 27 - 28 - var createConfig = require('ssb-config/inject') 29 - 30 - var createFeed = require('ssb-feed') 31 - var keys = require('../keys') 32 - var ssbKeys = require('ssb-keys') 33 - 34 - var cache = CACHE = {} 35 - 36 - module.exports = { 37 - needs: { 38 - connection_status: 'map' 39 - }, 40 - gives: { 41 - // connection_status: true, 42 - sbot_links: true, 43 - sbot_links2: true, 44 - sbot_query: true, 45 - sbot_get: true, 46 - sbot_log: true, 47 - sbot_user_feed: true, 48 - sbot_gossip_peers: true, 49 - sbot_gossip_connect: true, 50 - sbot_progress: true, 51 - sbot_publish: true, 52 - sbot_whoami: true 53 - }, 54 - 55 - //module.exports = { 56 - create: function (api) { 57 - 58 - var opts = createConfig() 59 - var sbot = null 60 - var connection_status = [] 61 - 62 - var rec = Reconnect(function (isConn) { 63 - function notify (value) { 64 - isConn(value); api.connection_status(value) //.forEach(function (fn) { fn(value) }) 65 - } 66 - 67 - createClient(keys, { 68 - manifest: require('../manifest.json'), 69 - remote: require('../config')().remote, 70 - caps: config.caps 71 - }, function (err, _sbot) { 72 - if(err) 73 - return notify(err) 74 - 75 - sbot = _sbot 76 - sbot.on('closed', function () { 77 - sbot = null 78 - notify(new Error('closed')) 79 - }) 80 - 81 - notify() 82 - }) 83 - }) 84 - 85 - var internal = { 86 - getLatest: rec.async(function (id, cb) { 87 - sbot.getLatest(id, cb) 88 - }), 89 - add: rec.async(function (msg, cb) { 90 - sbot.add(msg, cb) 91 - }) 92 - } 93 - 94 - var feed = createFeed(internal, keys, {remote: true}) 95 - 96 - return { 97 - connection_status: connection_status, 98 - sbot_links: rec.source(function (query) { 99 - return sbot.links(query) 100 - }), 101 - sbot_links2: rec.source(function (query) { 102 - return sbot.links2.read(query) 103 - }), 104 - sbot_query: rec.source(function (query) { 105 - return sbot.query.read(query) 106 - }), 107 - sbot_log: rec.source(function (opts) { 108 - return pull( 109 - sbot.createLogStream(opts), 110 - pull.through(function (e) { 111 - CACHE[e.key] = CACHE[e.key] || e.value 112 - }) 113 - ) 114 - }), 115 - sbot_user_feed: rec.source(function (opts) { 116 - return sbot.createUserStream(opts) 117 - }), 118 - sbot_get: rec.async(function (key, cb) { 119 - if('function' !== typeof cb) 120 - throw new Error('cb must be function') 121 - if(CACHE[key]) cb(null, CACHE[key]) 122 - else sbot.get(key, function (err, value) { 123 - if(err) return cb(err) 124 - cb(null, CACHE[key] = value) 125 - }) 126 - }), 127 - sbot_gossip_peers: rec.async(function (cb) { 128 - sbot.gossip.peers(cb) 129 - }), 130 - //liteclient won't have permissions for this 131 - sbot_gossip_connect: rec.async(function (opts, cb) { 132 - sbot.gossip.connect(opts, cb) 133 - }), 134 - sbot_progress: rec.source(function () { 135 - return sbot.replicate.changes() 136 - }), 137 - sbot_publish: rec.async(function (content, cb) { 138 - if(content.recps) 139 - content = ssbKeys.box(content, content.recps.map(function (e) { 140 - return ref.isFeed(e) ? e : e.link 141 - })) 142 - else if(content.mentions) 143 - content.mentions.forEach(function (mention) { 144 - if(ref.isBlob(mention.link)) { 145 - sbot.blobs.push(mention.link, function (err) { 146 - if(err) console.error(err) 147 - }) 148 - } 149 - }) 150 - 151 - feed.add(content, function (err, msg) { 152 - if(err) console.error(err) 153 - else if(!cb) console.log(msg) 154 - cb && cb(err, msg) 155 - }) 156 - }), 157 - sbot_whoami: rec.async(function (cb) { 158 - sbot.whoami(cb) 159 - }) 160 - } 161 - } 162 - } 163 - 164 - 165 - 1 + module.exports = require('../../patchbay/modules_core/sbot.js')
+1 -11
decent/modules_embedded/index.js
··· 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') 11 - } 1 + module.exports = require('../../patchbay/modules_embedded/index.js')
+1 -3
decent/modules_embedded/sbot.js
··· 1 - module.exports = require('../modules_core/sbot') 2 - 3 - 1 + module.exports = require('../../patchbay/modules_embedded/sbot.js')
+1 -78
decent/modules_extra/audio-mp3.js
··· 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 - //var plugs = require('../plugs'); 9 - //var message_link = plugs.first(exports.message_link = []); 10 - //var message_confirm = plugs.first(exports.message_confirm = []); 11 - //var sbot_links = plugs.first(exports.sbot_links = []); 12 - //var blob_url = plugs.first(exports.blob_url = []); 13 - // 14 - exports.needs = { 15 - // message_link: 'first', 16 - // message_confirm: 'first', 17 - // sbot_links: 'first', 18 - blobs_url: 'first' 19 - } 20 - 21 - exports.gives = { 22 - message_content: true, 23 - message_content_mini: true 24 - } 25 - 26 - exports.create = function (api) { 27 - var exports = {} 28 - 29 - exports.message_content = function (msg) { 30 - if (msg.value.content.type !== 'audio-mp3') 31 - return; 32 - 33 - var v = msg.value.content; 34 - return h('div', 35 - h('h2', "(" + v.Track + ") " + v.Title), 36 - // h('img', { "src" : api.blob_url(v.cover) }), 37 - h('audio', { 38 - "controls" : true, 39 - "src" : api.blob_url(v.link) 40 - })) 41 - // h('dl', 42 - // Object.keys(v).map(function(k) { 43 - // return [ 44 - // h("dt", k), 45 - // h("dd", v[k]), 46 - // ] 47 - // }))) 48 - 49 - // "Album": "the fall of", 50 - // "Crc32": "038becab", 51 - // "Creator": "bleupulp", 52 - // "Format": "VBR MP3", 53 - // "Height": "0", 54 - // "Length": "375.23", 55 - // "Md5": "2c517c8e813da5f940c8c7e77d4b7f3f", 56 - // "Mtime": "1399498698", 57 - // "Name": "2_bleupulp_-_clouds.mp3", 58 - // "Sha1": "9f6a96a3d5571ed1ec2a7da38ffebdcd5f181482", 59 - // "Size": "15009000", 60 - 61 - // "Title": "clouds", 62 - // "Track": "2", 63 - // "Width": "0", 64 - } 65 - 66 - exports.message_content_mini = function (msg) { 67 - if (msg.value.content.type !== 'audio-mp3') 68 - return 69 - 70 - var v = msg.value.content 71 - var title = v.Title || v.Name || 'audio' 72 - var track = v.Track ? '(' + v.Track + ') ' : '' 73 - var link = v.link && api.blob_url ? h('a', {href: api.blob_url(v.link)}, 'play') : null 74 - return ['audio ', track, title, link ? [' ', link] : null] 75 - } 76 - 77 - return exports 78 - } 1 + module.exports = require('../../patchbay/modules_extra/audio-mp3.js')
+1 -117
decent/modules_extra/channel.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - var pull = require('pull-stream') 4 - var Scroller = require('pull-scroll') 5 - var mfr = require('map-filter-reduce') 6 - 7 - exports.needs = { 8 - message_render: 'first', 9 - message_compose: 'first', 10 - sbot_log: 'first', 11 - sbot_query: 'first', 12 - } 13 - 14 - exports.gives = { 15 - message_meta: true, screen_view: true, 16 - connection_status: true, suggest_search: true 17 - } 18 - 19 - exports.create = function (api) { 20 - 21 - var channels 22 - 23 - var filter = {$filter: {value: {content: {channel: {$gt: ''}}}}} 24 - var map = {$map: {'name': ['value', 'content', 'channel']}} 25 - var reduce = {$reduce: { 26 - name: 'name', 27 - rank: {$count: true} 28 - }} 29 - 30 - return { 31 - message_meta: function (msg) { 32 - var chan = msg.value.content.channel 33 - if (chan) 34 - return h('a', {href: '##'+chan}, '#'+chan) 35 - }, 36 - screen_view: function (path) { 37 - if(path[0] === '#') { 38 - var channel = path.substr(1) 39 - 40 - var content = h('div.column.scroller__content') 41 - var div = h('div.column.scroller', 42 - {style: {'overflow':'auto'}}, 43 - h('div.scroller__wrapper', 44 - api.message_compose({type: 'post', channel: channel}), 45 - content 46 - ) 47 - ) 48 - 49 - function matchesChannel(msg) { 50 - if (msg.sync) console.error('SYNC', msg) 51 - var c = msg && msg.value && msg.value.content 52 - return c && c.channel === channel 53 - } 54 - 55 - pull( 56 - api.sbot_log({old: false}), 57 - pull.filter(matchesChannel), 58 - Scroller(div, content, api.message_render, true, false) 59 - ) 60 - 61 - pull( 62 - api.sbot_query({reverse: true, query: [ 63 - {$filter: {value: {content: {channel: channel}}}} 64 - ]}), 65 - Scroller(div, content, api.message_render, false, false) 66 - ) 67 - 68 - return div 69 - } 70 - }, 71 - 72 - connection_status: function (err) { 73 - if(err) return 74 - 75 - channels = [] 76 - 77 - pull( 78 - api.sbot_query({query: [filter, map, reduce]}), 79 - pull.collect(function (err, chans) { 80 - if (err) return console.error(err) 81 - channels = chans.concat(channels) 82 - }) 83 - ) 84 - 85 - pull( 86 - api.sbot_log({old: false}), 87 - mfr.filter(filter), 88 - mfr.map(map), 89 - pull.drain(function (chan) { 90 - var c = channels.find(function (e) { 91 - return e.name === chan.name 92 - }) 93 - if (c) c.rank++ 94 - else channels.push(chan) 95 - }) 96 - ) 97 - }, 98 - 99 - suggest_search: function (query) { 100 - return function (cb) { 101 - if(!/^#\w/.test(query)) return cb() 102 - cb(null, channels.filter(function (chan) { 103 - return ('#'+chan.name).substring(0, query.length) === query 104 - }) 105 - .map(function (chan) { 106 - var name = '#'+chan.name 107 - return { 108 - title: name, 109 - value: name, 110 - subtitle: chan.rank 111 - } 112 - })) 113 - } 114 - } 115 - } 116 - } 117 - 1 + module.exports = require('../../patchbay/modules_extra/channel.js')
+1 -51
decent/modules_extra/dns.js
··· 1 - var h = require('hyperscript') 2 - 3 - function array(obj) { 4 - return !obj ? [] : Array.isArray(obj) ? obj : [obj] 5 - } 6 - 7 - function idLink(id) { 8 - return h('a', {href: '#'+id}, id.substring(0, 10)+'…') 9 - } 10 - 11 - exports.gives = { 12 - message_content: true, 13 - message_content_mini: true 14 - } 15 - 16 - exports.create = function () { 17 - var exports = {} 18 - 19 - exports.message_content = function (msg, sbot) { 20 - var c = msg.value.content 21 - 22 - if(c.type === 'ssb-dns') { 23 - var record = c.record || {} 24 - return h('div', 25 - h('p', 26 - h('ins', {title: 'name'}, record.name), ' ', 27 - h('em', {title: 'ttl'}, record.ttl), ' ', 28 - h('span', {title: 'class'}, record.class), ' ', 29 - h('span', {title: 'type'}, record.type), 30 - h('pre', {title: 'data'}, 31 - JSON.stringify(record.data || record.value, null, 2)), 32 - !c.branch ? null : h('div', h('span', 33 - 'replaces: ', array(c.branch).map(idLink))) 34 - )) 35 - } 36 - } 37 - 38 - exports.message_content_mini = function (msg) { 39 - var c = msg.value.content 40 - if(c.type !== 'ssb-dns') return 41 - var record = c.record || {} 42 - var summary = [record.name, record.type, record.class] 43 - .filter(Boolean) 44 - .join(' ') 45 - if(!summary) summary = 'dns record' 46 - return summary 47 - } 48 - 49 - return exports 50 - 51 - } 1 + module.exports = require('../../patchbay/modules_extra/dns.js')
+1 -22
decent/modules_extra/emoji.js
··· 1 - var emojis = require('emoji-named-characters') 2 - var emojiNames = Object.keys(emojis) 3 - 4 - //var plugs = require('../plugs') 5 - //var blob_url = plugs.first(exports.blob_url = []) 6 - // 7 - 8 - exports.needs = { blob_url: 'first' } 9 - exports.gives = { emoji_names: true, emoji_url: true } 10 - 11 - exports.create = function (api) { 12 - return { 13 - emoji_names: function () { 14 - return emojiNames 15 - }, 16 - emoji_url: function (emoji) { 17 - return emoji in emojis && 18 - api.blob_url(emoji).replace(/\/blobs\/get/, '/img/emoji') + '.png' 19 - } 20 - } 21 - } 22 - 1 + module.exports = require('../../patchbay/modules_extra/emoji.js')
+1 -54
decent/modules_extra/git-ssb.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - var pull = require('pull-stream') 4 - var Scroller = require('pull-scroll') 5 - 6 - //var plugs = require('../plugs') 7 - //var message_render = plugs.first(exports.message_render = []) 8 - //var message_compose = plugs.first(exports.message_compose = []) 9 - //var sbot_log = plugs.first(exports.sbot_log = []) 10 - 11 - exports.needs = { 12 - message_render: 'first', 13 - message_compose: 'first', 14 - sbot_log: 'first' 15 - } 16 - 17 - exports.gives = { 18 - menu_items: true, screen_view: true 19 - } 20 - 21 - exports.create = function (api) { 22 - return { 23 - menu_items: function () { 24 - return h('a', {href: '#git-ssb'}, 'Git SSB') 25 - }, 26 - 27 - screen_view: function (path, sbot) { 28 - if(path === 'git-ssb') { 29 - 30 - var content = h('div.column.scroller__content') 31 - var div = h('div.column.scroller', 32 - {style: {'overflow':'auto'}}, 33 - h('div.scroller__wrapper', content) 34 - ) 35 - 36 - pull( 37 - u.next(api.sbot_log, {old: false, limit: 100}), 38 - Scroller(div, content, api.message_render, true, false) 39 - ) 40 - 41 - pull( 42 - u.next(api.sbot_log, {reverse: true, limit: 100, live: false}), 43 - pull.filter(function(msg) { return msg.value.content.type }), 44 - pull.filter(function(msg) { 45 - return msg.value.content.type.match(/^git/) 46 - }), 47 - Scroller(div, content, api.message_render, false, false) 48 - ) 49 - 50 - return div 51 - } 52 - } 53 - } 54 - } 1 + module.exports = require('../../patchbay/modules_extra/git-ssb.js')
+1 -519
decent/modules_extra/git.js
··· 1 - 'use strict' 2 - var h = require('hyperscript') 3 - var pull = require('pull-stream') 4 - var paramap = require('pull-paramap') 5 - var cat = require('pull-cat') 6 - var human = require('human-time') 7 - var combobox = require('hypercombo') 8 - 9 - var getAvatar = require('ssb-avatar') 10 - var KVGraph = require('kvgraph') 11 - var mergeRepo = require('ssb-git/merge') 12 - 13 - //var plugs = require('../plugs') 14 - //var message_link = plugs.first(exports.message_link = []) 15 - //var message_confirm = plugs.first(exports.message_confirm = []) 16 - //var message_compose = plugs.first(exports.message_compose = []) 17 - //var sbot_links = plugs.first(exports.sbot_links = []) 18 - //var sbot_links2 = plugs.first(exports.sbot_links2 = []) 19 - //var sbot_get = plugs.first(exports.sbot_get = []) 20 - //var avatar_name = plugs.first(exports.avatar_name = []) 21 - //var markdown = plugs.first(exports.markdown = []) 22 - 23 - exports.needs = { 24 - message_link: 'first', 25 - message_confirm: 'first', 26 - message_compose: 'first', 27 - sbot_links: 'first', 28 - sbot_links2: 'first', 29 - sbot_get: 'first', 30 - avatar_name: 'first', 31 - markdown: 'first' 32 - } 33 - 34 - exports.gives = { 35 - message_action: true, 36 - message_meta: true, 37 - message_content: true 38 - } 39 - 40 - 41 - var self_id = require('../keys').id 42 - 43 - function shortRefName(ref) { 44 - return ref.replace(/^refs\/(heads|tags)\//, '') 45 - } 46 - 47 - exports.create = function (api) { 48 - 49 - function getRefs(msg) { 50 - var updates = new KVGraph('key') 51 - var _cb, _refs 52 - pull( 53 - api.sbot_links({ 54 - reverse: true, 55 - // source: msg.value.author, 56 - dest: msg.key, 57 - rel: 'repo', 58 - values: true 59 - }), 60 - pull.drain(function (link) { 61 - if (link.value.content.type === 'git-update') { 62 - updates.add(link) 63 - } 64 - }, function (err) { 65 - var refs = updates.reduceRight(mergeRepo).refs 66 - var cb = _cb 67 - if (cb) _cb = null, cb(err, refs) 68 - else _refs = refs 69 - }) 70 - ) 71 - 72 - return pull( 73 - function fn(end, cb) { 74 - if (end || fn.ended) cb(true) 75 - fn.ended = true 76 - if (_refs) cb(_refs) 77 - else _cb = cb 78 - }, 79 - pull.flatten() 80 - ) 81 - } 82 - 83 - function getForks(id) { 84 - return pull( 85 - api.sbot_links({ 86 - reverse: true, 87 - dest: id, 88 - rel: 'upstream' 89 - }), 90 - pull.map(function (link) { 91 - return { 92 - id: link.key, 93 - author: link.source 94 - } 95 - }) 96 - ) 97 - } 98 - 99 - function repoText(id) { 100 - var text = document.createTextNode(id.substr(0, 10) + '…') 101 - getAvatar({links: api.sbot_links, get: api.sbot_get}, self_id, id, 102 - function (err, avatar) { 103 - if(err) return console.error(err) 104 - if (avatar.name[0] !== '%') avatar.name = '%' + avatar.name 105 - text.nodeValue = avatar.name 106 - }) 107 - return text 108 - } 109 - 110 - function repoLink(id) { 111 - return h('a', {href: '#'+id}, repoText(id)) 112 - } 113 - 114 - function repoName(id) { 115 - return h('ins', repoText(id)) 116 - } 117 - 118 - function getIssueState(id, cb) { 119 - pull( 120 - api.sbot_links({dest: id, rel: 'issues', values: true, reverse: true}), 121 - pull.map(function (msg) { 122 - return msg.value.content.issues 123 - }), 124 - pull.flatten(), 125 - pull.filter(function (issue) { 126 - return issue.link === id 127 - }), 128 - pull.map(function (issue) { 129 - return issue.merged ? 'merged' : issue.open ? 'open' : 'closed' 130 - }), 131 - pull.take(1), 132 - pull.collect(function (err, updates) { 133 - cb(err, updates && updates[0] || 'open') 134 - }) 135 - ) 136 - } 137 - 138 - //todo: 139 - function messageTimestampLink(msg) { 140 - var date = new Date(msg.value.timestamp) 141 - return h('a.timestamp', { 142 - timestamp: msg.value.timestamp, 143 - title: date, 144 - href: '#'+msg.key 145 - }, human(date)) 146 - } 147 - 148 - // a thead+tbody where the thead only is added when the first row is added 149 - function tableRows(headerRow) { 150 - var thead = h('thead'), tbody = h('tbody') 151 - var first = true 152 - var t = [thead, tbody] 153 - t.append = function (row) { 154 - if (first) { 155 - first = false 156 - thead.appendChild(headerRow) 157 - } 158 - tbody.appendChild(row) 159 - } 160 - return t 161 - } 162 - 163 - function renderIssueEdit(c) { 164 - var id = c.issue || c.link 165 - return [ 166 - c.title ? h('p', 'renamed issue ', api.message_link(id), 167 - ' to ', h('ins', c.title)) : null, 168 - c.open === false ? h('p', 'closed issue ', api.message_link(id)) : null, 169 - c.open === true ? h('p', 'reopened issue ', api.message_link(id)) : null] 170 - } 171 - 172 - function findMessageContent(el) { 173 - for(; el; el = el.parentNode) { 174 - if(el.classList.contains('message')) { 175 - return el.querySelector('.message_content') 176 - } 177 - } 178 - } 179 - 180 - function issueForm(msg, contentEl) { 181 - var form = h('form', 182 - h('strong', 'New Issue:'), 183 - api.message_compose( 184 - {type: 'issue', project: msg.key}, 185 - function (value) { return value }, 186 - function (err, issue) { 187 - if(err) return alert(err) 188 - if(!issue) return 189 - var title = issue.value.content.text 190 - if(title.length > 70) title = title.substr(0, 70) + '…' 191 - form.appendChild(h('div', 192 - h('a', {href: '#'+issue.key}, title) 193 - )) 194 - } 195 - ) 196 - ) 197 - return form 198 - } 199 - 200 - function branchMenu(msg, full) { 201 - return combobox({ 202 - style: {'max-width': '14ex'}, 203 - placeholder: 'branch…', 204 - default: 'master', 205 - read: msg && pull(getRefs(msg), pull.map(function (ref) { 206 - var m = /^refs\/heads\/(.*)$/.exec(ref.name) 207 - if(!m) return 208 - var branch = m[1] 209 - var label = branch 210 - if(full) { 211 - var updated = new Date(ref.link.value.timestamp) 212 - label = branch + 213 - ' · ' + human(updated) + 214 - ' · ' + ref.hash.substr(1, 8) + 215 - (ref.title ? ' · "' + ref.title + '"' : '') 216 - } 217 - return h('option', {value: branch}, label) 218 - })) 219 - }) 220 - } 221 - 222 - function newPullRequestButton(msg) { 223 - return h('div', [ 224 - h('a', { 225 - href: '#', 226 - onclick: function (e) { 227 - e.preventDefault() 228 - this.parentNode.replaceChild(pullRequestForm(msg), this) 229 - }}, 230 - 'New Pull Request…' 231 - ) 232 - ]) 233 - } 234 - 235 - function pullRequestForm(msg) { 236 - var headRepoInput 237 - var headBranchInput = branchMenu() 238 - var branchInput = branchMenu(msg) 239 - var form = h('form', 240 - h('strong', 'New Pull Request:'), 241 - h('div', 242 - 'from ', 243 - headRepoInput = combobox({ 244 - style: {'max-width': '26ex'}, 245 - onchange: function () { 246 - // list branches for selected repo 247 - var repoId = this.value 248 - if(repoId) api.sbot_get(repoId, function (err, value) { 249 - if(err) console.error(err) 250 - var msg = value && {key: repoId, value: value} 251 - headBranchInput = headBranchInput.swap(branchMenu(msg, true)) 252 - }) 253 - else headBranchInput = headBranchInput.swap(branchMenu()) 254 - }, 255 - read: pull(cat([ 256 - pull.once({id: msg.key, author: msg.value.author}), 257 - getForks(msg.key) 258 - ]), pull.map(function (fork) { 259 - return h('option', {value: fork.id}, 260 - repoLink(fork.id), ' by ', api.avatar_name(fork.author)) 261 - })) 262 - }), 263 - ':', 264 - headBranchInput, 265 - ' to ', 266 - repoName(msg.key), 267 - ':', 268 - branchInput), 269 - api.message_compose( 270 - { 271 - type: 'pull-request', 272 - project: msg.key, 273 - repo: msg.key, 274 - }, 275 - function (value) { 276 - value.branch = branchInput.value 277 - value.head_repo = headRepoInput.value 278 - value.head_branch = headBranchInput.value 279 - return value 280 - }, 281 - function (err, issue) { 282 - if(err) return alert(err) 283 - if(!issue) return 284 - var title = issue.value.content.text 285 - if(title.length > 70) title = title.substr(0, 70) + '…' 286 - form.appendChild(h('div', 287 - h('a', {href: '#'+issue.key}, title) 288 - )) 289 - } 290 - ) 291 - ) 292 - return form 293 - } 294 - 295 - 296 - 297 - return { 298 - message_content: function (msg, sbot) { 299 - var c = msg.value.content 300 - 301 - if(c.type === 'git-repo') { 302 - var branchesT, tagsT, openIssuesT, closedIssuesT, openPRsT, closedPRsT 303 - var forksT 304 - var div = h('div', 305 - h('p', 'git repo ', repoName(msg.key)), 306 - c.upstream ? h('p', 'fork of ', repoLink(c.upstream)) : '', 307 - h('p', h('code', 'ssb://' + msg.key)), 308 - h('div.git-table-wrapper', {style: {'max-height': '12em'}}, 309 - h('table', 310 - branchesT = tableRows(h('tr', 311 - h('th', 'branch'), 312 - h('th', 'commit'), 313 - h('th', 'last update'))), 314 - tagsT = tableRows(h('tr', 315 - h('th', 'tag'), 316 - h('th', 'commit'), 317 - h('th', 'last update'))))), 318 - h('div.git-table-wrapper', {style: {'max-height': '16em'}}, 319 - h('table', 320 - openIssuesT = tableRows(h('tr', 321 - h('th', 'open issues'))), 322 - closedIssuesT = tableRows(h('tr', 323 - h('th', 'closed issues'))))), 324 - h('div.git-table-wrapper', {style: {'max-height': '16em'}}, 325 - h('table', 326 - openPRsT = tableRows(h('tr', 327 - h('th', 'open pull requests'))), 328 - closedPRsT = tableRows(h('tr', 329 - h('th', 'closed pull requests'))))), 330 - h('div.git-table-wrapper', 331 - h('table', 332 - forksT = tableRows(h('tr', 333 - h('th', 'forks'))))), 334 - h('div', h('a', {href: '#', onclick: function (e) { 335 - e.preventDefault() 336 - this.parentNode.replaceChild(issueForm(msg), this) 337 - }}, 'New Issue…')), 338 - newPullRequestButton.call(this, msg) 339 - ) 340 - 341 - pull(getRefs(msg), pull.drain(function (ref) { 342 - var name = ref.realname || ref.name 343 - var author = ref.link && ref.link.value.author 344 - var parts = /^refs\/(heads|tags)\/(.*)$/.exec(name) || [] 345 - var shortName = parts[2] 346 - var t 347 - if(parts[1] === 'heads') t = branchesT 348 - else if(parts[1] === 'tags') t = tagsT 349 - if(t) t.append(h('tr', 350 - h('td', shortName, 351 - ref.conflict ? [ 352 - h('br'), 353 - h('a', {href: '#'+author}, api.avatar_name(author)) 354 - ] : ''), 355 - h('td', h('code', ref.hash)), 356 - h('td', messageTimestampLink(ref.link)))) 357 - }, function (err) { 358 - if(err) console.error(err) 359 - })) 360 - 361 - // list issues and pull requests 362 - pull( 363 - api.sbot_links({ 364 - reverse: true, 365 - dest: msg.key, 366 - rel: 'project', 367 - values: true 368 - }), 369 - paramap(function (link, cb) { 370 - getIssueState(link.key, function (err, state) { 371 - if(err) return cb(err) 372 - link.state = state 373 - cb(null, link) 374 - }) 375 - }), 376 - pull.drain(function (link) { 377 - var c = link.value.content 378 - var title = c.title || (c.text ? c.text.length > 70 379 - ? c.text.substr(0, 70) + '…' 380 - : c.text : link.key) 381 - var author = link.value.author 382 - var t = c.type === 'pull-request' 383 - ? link.state === 'open' ? openPRsT : closedPRsT 384 - : link.state === 'open' ? openIssuesT : closedIssuesT 385 - t.append(h('tr', 386 - h('td', 387 - h('a', {href: '#'+link.key}, title), h('br'), 388 - h('small', 389 - 'opened ', messageTimestampLink(link), 390 - ' by ', h('a', {href: '#'+author}, api.avatar_name(author)))))) 391 - }, function (err) { 392 - if (err) console.error(err) 393 - }) 394 - ) 395 - 396 - // list forks 397 - pull( 398 - getForks(msg.key), 399 - pull.drain(function (fork) { 400 - forksT.append(h('tr', h('td', 401 - repoLink(fork.id), 402 - ' by ', h('a', {href: '#'+fork.author}, api.avatar_name(fork.author))))) 403 - }, function (err) { 404 - if (err) console.error(err) 405 - }) 406 - ) 407 - 408 - return div 409 - } 410 - 411 - if(c.type === 'git-update') { 412 - return [ 413 - h('p', 'pushed to ', repoLink(c.repo)), 414 - c.refs ? h('ul', Object.keys(c.refs).map(function (ref) { 415 - var rev = c.refs[ref] 416 - return h('li', 417 - shortRefName(ref) + ': ', 418 - rev ? h('code', rev) : h('em', 'deleted')) 419 - })) : null, 420 - Array.isArray(c.commits) ? [ 421 - h('ul', 422 - c.commits.map(function (commit) { 423 - return h('li', 424 - typeof commit.sha1 === 'string' ? 425 - [h('code', commit.sha1.substr(0, 8)), ' '] : null, 426 - commit.title ? 427 - h('q', commit.title) : null) 428 - }), 429 - c.commits_more > 0 ? 430 - h('li', '+ ', c.commits_more, ' more') : null) 431 - ] : null, 432 - Array.isArray(c.issues) ? c.issues.map(function (issue) { 433 - if (issue.merged === true) 434 - return h('p', 'Merged ', api.message_link(issue.link), ' in ', 435 - h('code', issue.object), ' ', h('q', issue.label)) 436 - if (issue.open === false) 437 - return h('p', 'Closed ', api.message_link(issue.link), ' in ', 438 - h('code', issue.object), ' ', h('q', issue.label)) 439 - }) : null, 440 - newPullRequestButton.call(this, msg) 441 - ] 442 - } 443 - 444 - if(c.type === 'issue-edit' 445 - || (c.type === 'post' && c.text === '')) { 446 - return h('div', 447 - c.issue ? renderIssueEdit(c) : null, 448 - c.issues ? c.issues.map(renderIssueEdit) : null) 449 - } 450 - 451 - if(c.type === 'issue') { 452 - return h('div', 453 - h('p', 'opened issue on ', repoLink(c.project)), 454 - c.title ? h('h4', c.title) : '', 455 - api.markdown(c) 456 - ) 457 - } 458 - 459 - if(c.type === 'pull-request') { 460 - return h('div', 461 - h('p', 'opened pull-request ', 462 - 'to ', repoLink(c.repo), ':', c.branch, ' ', 463 - 'from ', repoLink(c.head_repo), ':', c.head_branch), 464 - c.title ? h('h4', c.title) : '', 465 - api.markdown(c) 466 - ) 467 - } 468 - }, 469 - 470 - message_meta: function (msg, sbot) { 471 - var type = msg.value.content.type 472 - if (type === 'issue' || type === 'pull-request') { 473 - var el = h('em', '...') 474 - // TODO: update if issue is changed 475 - getIssueState(msg.key, function (err, state) { 476 - if (err) return console.error(err) 477 - el.textContent = state 478 - }) 479 - return el 480 - } 481 - }, 482 - 483 - message_action: function (msg, sbot) { 484 - var c = msg.value.content 485 - if(c.type === 'issue' || c.type === 'pull-request') { 486 - var isOpen 487 - var a = h('a', {href: '#', onclick: function (e) { 488 - e.preventDefault() 489 - api.message_confirm({ 490 - type: 'issue-edit', 491 - root: msg.key, 492 - issues: [{ 493 - link: msg.key, 494 - open: !isOpen 495 - }] 496 - }, function (err, msg) { 497 - if(err) return alert(err) 498 - if(!msg) return 499 - isOpen = msg.value.content.open 500 - update() 501 - }) 502 - }}) 503 - getIssueState(msg.key, function (err, state) { 504 - if (err) return console.error(err) 505 - isOpen = state === 'open' 506 - update() 507 - }) 508 - function update() { 509 - a.textContent = c.type === 'pull-request' 510 - ? isOpen ? 'Close Pull Request' : 'Reopen Pull Request' 511 - : isOpen ? 'Close Issue' : 'Reopen Issue' 512 - } 513 - return a 514 - } 515 - } 516 - } 517 - } 518 - 519 - 1 + module.exports = require('../../patchbay/modules_extra/git.js')
+1 -22
decent/modules_extra/index.js
··· 1 - module.exports = { 2 - "audio-mp3.js": require('./audio-mp3.js'), 3 - "channel.js": require('./channel.js'), 4 - "emoji.js": require('./emoji.js'), 5 - "suggest-emoji.js": require('./suggest-emoji.js'), 6 - "dns.js": require('./dns.js'), 7 - "git.js": require('./git.js'), 8 - "git-ssb.js": require('./git-ssb.js'), 9 - "key.js": require('./key.js'), 10 - "notifications.js": require('./notifications.js'), 11 - "meta-image.js": require('./meta-image.js'), 12 - "music-release-cc.js": require('./music-release-cc.js'), 13 - "music-release.js": require('./music-release.js'), 14 - "network.js": require('./network.js'), 15 - "query.js": require('./query.js'), 16 - "raw.js": require('./raw.js'), 17 - "search.js": require('./search'), 18 - "split.js": require('./split.js'), 19 - "versions.js": require('./versions.js') 20 - } 21 - 22 - 1 + module.exports = require('../../patchbay/modules_extra/index.js')
+1 -63
decent/modules_extra/key.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - var pull = require('pull-stream') 4 - var Scroller = require('pull-scroll') 5 - var keys 6 - try { keys = require('../keys') } catch (_) {} 7 - 8 - //var plugs = require('../plugs') 9 - // var message_render = plugs.first(exports.message_render = []) 10 - // var message_compose = plugs.first(exports.message_compose = []) 11 - // var sbot_log = plugs.first(exports.sbot_log = []) 12 - 13 - 14 - exports.gives = { 15 - menu_items: true, screen_view: true 16 - } 17 - 18 - exports.create = function (api) { 19 - return { 20 - menu_items: function () { 21 - return h('a', {href: '#key'}, 'Key') 22 - }, 23 - screen_view: function (path, sbot) { 24 - if(path === 'key') { 25 - if(process.title === 'browser') { 26 - var storedSecret = null 27 - try { storedSecret = localStorage['browser/.ssb/secret'] } catch (_) {} 28 - 29 - if((!storedSecret || storedSecret === 'undefined') && keys && keys.id) { 30 - try { 31 - storedSecret = JSON.stringify(keys, null, 2) 32 - localStorage['browser/.ssb/secret'] = storedSecret 33 - } catch (_) {} 34 - } 35 - 36 - var importKey = h('textarea', {placeholder: 'import an existing public/private key', name: 'textarea'}) 37 - var content = h('div.column.scroller__content') 38 - var div = h('div.column.scroller', 39 - {style: {'overflow':'auto'}}, 40 - h('div.scroller__wrapper', 41 - h('div.column.scroller__content', 42 - h('div.message.message-card', 43 - h('p', {innerHTML: 'Your secret key is: <pre><code>' + (storedSecret || '') + '</code></pre>'}), 44 - h('form', 45 - importKey, 46 - h('button.btn.btn-primary', {onclick: function (e){ 47 - localStorage['browser/.ssb/secret'] = importKey.value.replace(/\s+/g, ' ') 48 - alert('Your public/private key has been updated') 49 - e.preventDefault() 50 - }}, 'Import') 51 - ) 52 - ) 53 - ) 54 - ) 55 - ) 56 - return div 57 - } else { 58 - return h('div.message.message-card', 'Your key is saved at .ssb/secret') 59 - } 60 - } 61 - } 62 - } 63 - } 1 + module.exports = require('../../patchbay/modules_extra/key.js')
+1 -68
decent/modules_extra/meta-image.js
··· 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 - //var plugs = require('../plugs'); 9 - //var message_link = plugs.first(exports.message_link = []); 10 - //var message_confirm = plugs.first(exports.message_confirm = []); 11 - //var sbot_links = plugs.first(exports.sbot_links = []); 12 - //var blob_url = plugs.first(exports.blob_url = []); 13 - // 14 - exports.needs = { blob_url: 'first' } 15 - exports.gives = { 16 - message_content: true, 17 - message_content_mini: true 18 - } 19 - 20 - exports.create = function (api) { 21 - var exports = {} 22 - 23 - exports.message_content = function (msg, sbot) { 24 - if (msg.value.content.type !== 'meta-image') 25 - return; 26 - 27 - var v = msg.value.content; 28 - return h('div', 29 - // h('h2', "(" + v.Track + ") " + v.Title), 30 - h('img', { "src" : api.blob_url(v.link) })) 31 - 32 - // h('dl', 33 - // Object.keys(v).map(function(k) { 34 - // return [ 35 - // h("dt", k), 36 - // h("dd", v[k]), 37 - // ] 38 - // }))) 39 - 40 - // "Album": "the fall of", 41 - // "Crc32": "038becab", 42 - // "Creator": "bleupulp", 43 - // "Format": "VBR MP3", 44 - // "Height": "0", 45 - // "Length": "375.23", 46 - // "Md5": "2c517c8e813da5f940c8c7e77d4b7f3f", 47 - // "Mtime": "1399498698", 48 - // "Name": "2_bleupulp_-_clouds.mp3", 49 - // "Sha1": "9f6a96a3d5571ed1ec2a7da38ffebdcd5f181482", 50 - // "Size": "15009000", 51 - 52 - // "Title": "clouds", 53 - // "Track": "2", 54 - // "Width": "0", 55 - 56 - } 57 - 58 - exports.message_content_mini = function (msg) { 59 - if (msg.value.content.type !== 'meta-image') 60 - return; 61 - 62 - var v = msg.value.content; 63 - if(!api.blob_url || !v.link) return 'image' 64 - return ['image: ', h('a', { href: api.blob_url(v.link) }, 'view')] 65 - } 66 - 67 - return exports 68 - } 1 + module.exports = require('../../patchbay/modules_extra/meta-image.js')
+1 -101
decent/modules_extra/music-release-cc.js
··· 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 - //var plugs = require('../plugs'); 9 - //var message_link = plugs.first(exports.message_link = []); 10 - //var message_confirm = plugs.first(exports.message_confirm = []); 11 - //var sbot_links = plugs.first(exports.sbot_links = []); 12 - //var blob_url = plugs.first(exports.blob_url = []); 13 - // 14 - 15 - exports.needs = { blob_url: 'first' } 16 - exports.gives = { 17 - message_content: true, 18 - message_content_mini: true 19 - } 20 - 21 - exports.create = function (api) { 22 - var exports = {} 23 - 24 - exports.message_content = function (msg, sbot) { 25 - if (msg.value.content.type !== 'music-release-cc') 26 - return; 27 - 28 - var tracks = msg.value.content.tracks; 29 - return h('div', 30 - h('img', { "src" : api.blob_url(msg.value.content.cover) }), 31 - h('h1', msg.value.content.title), 32 - h('ol', 33 - Object.keys(tracks).map(function(k) { 34 - var t = tracks[k]; 35 - return h('li', t.fname, 36 - h("br"), 37 - h('audio', { 38 - "controls" : true, 39 - "src" : api.blob_url(t.link) 40 - })) 41 - })), 42 - h('p', 43 - "More info:", h('a', { href : msg.value.content.archivedotorg }, "archive.org"), 44 - h("br"), 45 - "License:", h('a', { href : msg.value.content.license }, "Link"))) 46 - } 47 - 48 - exports.message_content_mini = function (msg) { 49 - if (msg.value.content.type !== 'music-release-cc') 50 - return; 51 - 52 - var v = msg.value.content 53 - return ['music release: ', v.title || 'untitled'] 54 - } 55 - 56 - return exports 57 - } 58 - 59 - // copied from like.js 60 - 61 - // inspiration for waveform range selection 62 - 63 - // idea: handout invite codes for upload of tracks to be cached by the pub 64 - 65 - // exports.message_meta = function (msg, sbot) { 66 - 67 - // var yupps = h('a') 68 - 69 - // pull( 70 - // sbot_links({dest: msg.key, rel: 'vote'}), 71 - // pull.collect(function (err, votes) { 72 - // if(votes.length === 1) 73 - // yupps.textContent = ' 1 yup' 74 - // if(votes.length) 75 - // yupps.textContent = ' ' + votes.length + ' yupps' 76 - // }) 77 - // ) 78 - 79 - // return yupps 80 - // } 81 - 82 - // exports.message_action = function (msg, sbot) { 83 - // if(msg.value.content.type !== 'vote') 84 - // return h('a', {href: '#', onclick: function () { 85 - // var yup = { 86 - // type: 'vote', 87 - // vote: { link: msg.key, value: 1, expression: 'yup' } 88 - // } 89 - // if(msg.value.content.recps) { 90 - // yup.recps = msg.value.content.recps.map(function (e) { 91 - // return e && typeof e !== 'string' ? e.link : e 92 - // }) 93 - // yup.private = true 94 - // } 95 - // //TODO: actually publish... 96 - 97 - // message_confirm(yup) 98 - // }}, 'yup') 99 - 100 - // } 101 - 1 + module.exports = require('../../patchbay/modules_extra/music-release-cc.js')
+1 -61
decent/modules_extra/music-release.js
··· 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 - //var plugs = require('../plugs'); 9 - //var message_link = plugs.first(exports.message_link = []); 10 - //var message_confirm = plugs.first(exports.message_confirm = []); 11 - //var sbot_links = plugs.first(exports.sbot_links = []); 12 - // 13 - exports.gives = { 14 - message_content: true, 15 - message_content_mini: true 16 - } 17 - 18 - exports.create = function () { 19 - var exports = {} 20 - 21 - exports.message_content = function (msg, sbot) { 22 - if (msg.value.content.type !== 'music-release') 23 - return; 24 - 25 - var v = msg.value.content; 26 - return h('div', 27 - // h('img', { "src" : "http://localhost:7777/" + encodeURIComponent(v.cover) }), 28 - h('h1', v.Title), 29 - h("p", v.Description), 30 - h("dl", 31 - 32 - h("dt", "Creator"), 33 - h("dd", v.Creator), 34 - 35 - h("dt", "Identifier"), 36 - h("dd", v.Identifier), 37 - 38 - h("dt", "Published"), 39 - h("dd", v.Publicdate), 40 - 41 - h("dt", "Runtime"), 42 - h("dd", v.Runtime), 43 - 44 - h("dt", "Source"), 45 - h("dd", v.Source), 46 - 47 - h("dt", "License"), 48 - h("dd", h('a', { href : v.Licenseurl }, "Link")))) 49 - } 50 - 51 - exports.message_content_mini = function (msg) { 52 - if (msg.value.content.type !== 'music-release') 53 - return; 54 - 55 - var v = msg.value.content; 56 - var title = v.Title || v.Identifier || 'music release' 57 - return ['music release: ', title] 58 - } 59 - 60 - return exports 61 - } 1 + module.exports = require('../../patchbay/modules_extra/music-release.js')
+1 -175
decent/modules_extra/network.js
··· 1 - var isVisible = require('is-visible').isVisible 2 - var h = require('hyperscript') 3 - 4 - //var avatar = plugs.first(exports.avatar = []) 5 - //var sbot_gossip_peers = plugs.first(exports.sbot_gossip_peers = []) 6 - //var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = []) 7 - 8 - exports.needs = { 9 - avatar: 'first', 10 - sbot_gossip_peers: 'first', 11 - sbot_gossip_connect: 'first' 12 - } 13 - 14 - exports.gives = { 15 - menu_items: true, 16 - builtin_tabs: true, 17 - screen_view: true 18 - } 19 - 20 - //sbot_gossip_connect 21 - //sbot_gossip_add 22 - 23 - var human = require('human-time') 24 - 25 - function legacyToMultiServer(addr) { 26 - return 'net:'+addr.host + ':'+addr.port + '~shs:'+addr.key.substring(1).replace('.ed25519','') 27 - } 28 - 29 - //types of peers 30 - 31 - 32 - //on the same wifi network 33 - function isLocal (e) { 34 - // don't rely on private ip address, because 35 - // cjdns creates fake private ip addresses. 36 - return ip.isPrivate(e.host) && e.type === 'local' 37 - } 38 - 39 - 40 - //pub is running scuttlebot >=8 41 - //have connected successfully. 42 - function isLongterm (e) { 43 - return e.ping && e.ping.rtt && e.ping.rtt.mean > 0 44 - } 45 - 46 - //pub is running scuttlebot < 8 47 - //have connected sucessfully 48 - function isLegacy (peer) { 49 - return /connect/.test(peer.state) || (peer.duration && peer.duration.mean) > 0 && !isLongterm(peer) 50 - } 51 - 52 - //tried to connect, but failed. 53 - function isInactive (e) { 54 - return e.stateChange && (e.duration && e.duration.mean == 0) 55 - } 56 - 57 - //havn't tried to connect peer yet. 58 - function isUnattempted (e) { 59 - return !e.stateChange 60 - } 61 - 62 - function getType (e) { 63 - return ( 64 - isLongterm(e) ? 'modern' 65 - : isLegacy(e) ? 'legacy' 66 - : isInactive(e) ? 'inactive' 67 - : isUnattempted(e) ? 'unattempted' 68 - : 'other' //should never happen 69 - ) 70 - } 71 - 72 - function origin (e) { 73 - return e.source === 'local' ? 0 : 1 74 - } 75 - 76 - var states = { 77 - connected: 3, 78 - connecting: 2 79 - } 80 - 81 - var types = { 82 - modern: 4, 83 - legacy: 3, 84 - inactive: 2, 85 - unattempted: 1, 86 - other: 0 87 - } 88 - 89 - function round(n) { 90 - return Math.round(n*100)/100 91 - } 92 - 93 - function duration (s) { 94 - if(!s) return s 95 - if (Math.abs(s) > 30000) 96 - return round(s/60000)+'m' 97 - else if (Math.abs(s) > 500) 98 - return round(s/1000)+'s' 99 - else 100 - return round(s)+'ms' 101 - } 102 - 103 - 104 - 105 - exports.create = function (api) { 106 - 107 - return { 108 - menu_items: function () { 109 - return h('a', {href: '#network'}, 'Network') 110 - }, 111 - 112 - builtin_tabs: function () { 113 - return ['network'] 114 - }, 115 - 116 - screen_view: function (path) { 117 - 118 - if(path !== 'network') return 119 - 120 - var ol = h('ul.network') 121 - 122 - ;(function poll () { 123 - 124 - //if this tab isn't open, don't update. 125 - //todo: make a better way to do this... 126 - if(!isVisible(ol)) 127 - return setTimeout(poll, 1000) 128 - 129 - api.sbot_gossip_peers(function (err, list) { 130 - ol.innerHTML = '' 131 - list.sort(function (a, b) { 132 - return ( 133 - (states[b.state] || 0) - (states[a.state] || 0) 134 - || origin(b) - origin(a) 135 - || types[getType(b)] - types[getType(a)] 136 - || b.stateChange - a.stateChange 137 - ) 138 - }).forEach(function (peer) { 139 - ol.appendChild(h('div', 140 - api.avatar(peer.key, 'thumbnail'), 141 - h('div', 142 - peer.state || 'not connected', 143 - ' ', 144 - getType(peer), 145 - ' ', 146 - //TODO: show nicer details, with labels. etc. 147 - (peer.ping && peer.ping.rtt) ? duration(peer.ping.rtt.mean) : '', 148 - ' ', 149 - (peer.ping && peer.ping.skew) ? duration(peer.ping.skew.mean) : '', 150 - h('label', 151 - {title: new Date(peer.stateChange).toString()}, 152 - peer.stateChange && ('(' + human(new Date(peer.stateChange))) + ')') 153 - ), 154 - 'source:'+peer.source, 155 - h('pre', legacyToMultiServer(peer)), 156 - h('button', 'connect', {onclick: function () { 157 - api.sbot_gossip_connect(peer, function (err) { 158 - if(err) console.error(err) 159 - else console.log('connected to', peer) 160 - }) 161 - }}) 162 - ) 163 - ) 164 - }) 165 - 166 - setTimeout(poll, 5000) 167 - }) 168 - 169 - })() 170 - 171 - return h('div.column.scroll-y', ol) 172 - } 173 - } 174 - } 175 - 1 + module.exports = require('../../patchbay/modules_extra/network.js')
+1 -170
decent/modules_extra/notifications.js
··· 1 - 'use strict' 2 - var h = require('hyperscript') 3 - var u = require('../util') 4 - var pull = require('pull-stream') 5 - var Scroller = require('pull-scroll') 6 - var paramap = require('pull-paramap') 7 - var plugs = require('../plugs') 8 - var cont = require('cont') 9 - var ref = require('ssb-ref') 10 - 11 - //var message_render = plugs.first(exports.message_render = []) 12 - //var sbot_log = plugs.first(exports.sbot_log = []) 13 - //var sbot_get = plugs.first(exports.sbot_get = []) 14 - //var sbot_user_feed = plugs.first(exports.sbot_user_feed = []) 15 - //var message_unbox = plugs.first(exports.message_unbox = []) 16 - 17 - exports.needs = { 18 - message_render: 'first', 19 - sbot_log: 'first', 20 - sbot_get: 'first', 21 - sbot_user_feed: 'first', 22 - message_unbox: 'first' 23 - } 24 - 25 - 26 - exports.gives = { 27 - builtin_tabs: true, 28 - screen_view: true 29 - } 30 - 31 - exports.create = function (api) { 32 - function unbox() { 33 - return pull( 34 - pull.map(function (msg) { 35 - return msg.value && 'string' === typeof msg.value.content ? 36 - api.message_unbox(msg) : msg 37 - }), 38 - pull.filter(Boolean) 39 - ) 40 - } 41 - 42 - function notifications(ourIds) { 43 - 44 - function linksToUs(link) { 45 - return link && link.link in ourIds 46 - } 47 - 48 - function isOurMsg(id, cb) { 49 - if (!id) return cb(null, false) 50 - if (typeof id === 'object' && typeof id.link === 'string') id = id.link 51 - if (!ref.isMsg(id)) return cb(null, false) 52 - api.sbot_get(id, function (err, msg) { 53 - if (err && err.name == 'NotFoundError') cb(null, false) 54 - else if (err) cb(err) 55 - else if (msg.content.type === 'issue' || msg.content.type === 'pull-request') 56 - isOurMsg(msg.content.repo || msg.content.project, cb) 57 - else cb(err, msg.author in ourIds) 58 - }) 59 - } 60 - 61 - function isAnyOurMessage(msg, ids, cb) { 62 - cont.para(ids.map(function (id) { 63 - return function (cb) { isOurMsg(id, cb) } 64 - })) 65 - (function (err, results) { 66 - if (err) cb(err) 67 - else if (results.some(Boolean)) cb(null, msg) 68 - else cb() 69 - }) 70 - } 71 - 72 - return paramap(function (msg, cb) { 73 - var c = msg.value && msg.value.content 74 - if (!c || typeof c !== 'object') return cb() 75 - if (msg.value.author in ourIds) return cb() 76 - 77 - if (c.mentions && Array.isArray(c.mentions) && c.mentions.some(linksToUs)) 78 - return cb(null, msg) 79 - 80 - if (msg.private) 81 - return cb(null, msg) 82 - 83 - switch (c.type) { 84 - case 'post': 85 - if (c.branch || c.root) 86 - return isAnyOurMessage(msg, [].concat(c.branch, c.root), cb) 87 - else return cb() 88 - 89 - case 'contact': 90 - return cb(null, c.contact in ourIds ? msg : null) 91 - 92 - case 'vote': 93 - if (c.vote && c.vote.link) 94 - return isOurMsg(c.vote.link, function (err, isOurs) { 95 - cb(err, isOurs ? msg : null) 96 - }) 97 - else return cb() 98 - 99 - case 'issue': 100 - case 'pull-request': 101 - return isOurMsg(c.project || c.repo, function (err, isOurs) { 102 - cb(err, isOurs ? msg : null) 103 - }) 104 - 105 - case 'issue-edit': 106 - return isAnyOurMessage(msg, [c.issue].concat(c.issues), cb) 107 - 108 - default: 109 - cb() 110 - } 111 - }, 4) 112 - } 113 - 114 - function getFirstMessage(feedId, cb) { 115 - api.sbot_user_feed({id: feedId, gte: 0, limit: 1})(null, cb) 116 - } 117 - 118 - return { 119 - builtin_tabs: function () { 120 - return ['notifications'] 121 - }, 122 - 123 - screen_view: function (path) { 124 - if(path === 'notifications') { 125 - var ids = {} 126 - var oldest 127 - 128 - var id = require('../keys').id 129 - ids[id] = true 130 - getFirstMessage(id, function (err, msg) { 131 - if (err) return console.error(err) 132 - if (!oldest || msg.value.timestamp < oldest) { 133 - oldest = msg.value.timestamp 134 - } 135 - }) 136 - 137 - var content = h('div.column.scroller__content') 138 - var div = h('div.column.scroller', 139 - {style: {'overflow':'auto'}}, 140 - h('div.scroller__wrapper', 141 - content 142 - ) 143 - ) 144 - div.setAttribute('data-icon', 'notifications') 145 - 146 - pull( 147 - u.next(api.sbot_log, {old: false, limit: 100}), 148 - unbox(), 149 - notifications(ids), 150 - pull.filter(), 151 - Scroller(div, content, api.message_render, true, false) 152 - ) 153 - 154 - pull( 155 - u.next(api.sbot_log, {reverse: true, limit: 100, live: false}), 156 - unbox(), 157 - notifications(ids), 158 - pull.filter(), 159 - pull.take(function (msg) { 160 - // abort stream after we pass the oldest messages of our feeds 161 - return !oldest ? true : msg.value.timestamp > oldest 162 - }), 163 - Scroller(div, content, api.message_render, false, false) 164 - ) 165 - 166 - return div 167 - } 168 - } 169 - } 170 - } 1 + module.exports = require('../../patchbay/modules_extra/notifications.js')
+1 -66
decent/modules_extra/query.js
··· 1 - var h = require('hyperscript') 2 - var pull = require('pull-stream') 3 - var HJSON = require('hjson') 4 - 5 - //var sbot_query = require('../plugs').first(exports.sbot_query = []) 6 - 7 - exports.needs = { sbot_query: 'first' } 8 - 9 - exports.gives = { 10 - menu_items: true, 11 - builtin_tabs: true, 12 - screen_view: true 13 - } 14 - 15 - exports.create = function (api) { 16 - 17 - return { 18 - menu_items: function () { 19 - return h('a', {href:'#query'}, 'Query') 20 - }, 21 - 22 - builtin_tabs: function () { 23 - return ['query'] 24 - }, 25 - 26 - screen_view: function (path) { 27 - if(path != 'query') return 28 - var output, status, editor, stream, query 29 - 30 - function parse () { 31 - try { 32 - query = HJSON.parse(editor.value) 33 - } catch (err) { 34 - return status.textContent = err.message 35 - } 36 - status.textContent = 'okay' 37 - } 38 - 39 - return h('div.column.scroll', 40 - editor = h('textarea', {style: 'min-height:100px;', oninput: parse, onkeydown: function (e) { 41 - if(!(e.keyCode === 13 && e.ctrlKey)) return 42 - 43 - status.textContent = 'running...' 44 - parse() 45 - output.innerHTML = '' 46 - if(stream) stream.abort() 47 - 48 - console.log(query) 49 - 50 - stream = pull( 51 - api.sbot_query({query: query, limit: 100}), 52 - pull.drain(function (data) { 53 - output.appendChild(h('pre.query__data', 54 - JSON.stringify(data, null, 2) 55 - )) 56 - }, function (err) { 57 - if(err) status.textContent = err.stack 58 - }) 59 - ) 60 - }}), 61 - status = h('div.query__status'), 62 - output = h('div.column.query__output', {style: 'overflow-y: scroll;'}) 63 - ) 64 - } 65 - } 66 - } 1 + module.exports = require('../../patchbay/modules_extra/query.js')
+1 -82
decent/modules_extra/raw.js
··· 1 - var h = require('hyperscript') 2 - var u = require('../util') 3 - var pull = require('pull-stream') 4 - var Scroller = require('pull-scroll') 5 - 6 - var plugs = require('../plugs') 7 - //var message_render = plugs.first(exports.message_render = []) 8 - //var message_compose = plugs.first(exports.message_compose = []) 9 - 10 - // from ssb-ref 11 - var refRegex = /((?:@|%|&)[A-Za-z0-9\/+]{43}=\.[\w\d]+)/g 12 - 13 - exports.gives = 'message_meta' 14 - 15 - function linkify (text) { 16 - var arr = text.split(refRegex) 17 - for (var i = 1; i < arr.length; i += 2) { 18 - arr[i] = h('a', {href: '#' + arr[i]}, arr[i]) 19 - } 20 - return arr 21 - } 22 - 23 - exports.create = function (api) { 24 - return function (msg) { 25 - var tmp = h('div') 26 - var el 27 - var pre 28 - var raw 29 - function findMessageEl (node) { 30 - while (node && node.classList) { 31 - if (node.classList.contains('message')) return node 32 - node = node.parentNode 33 - } 34 - return null 35 - } 36 - return h('input', { 37 - type: 'checkbox', 38 - title: 'View Data', 39 - onclick: function () { 40 - var msgEl = findMessageEl(this) 41 - if (!msgEl) return 42 - var msgContentEl = msgEl.querySelector('.message_content') 43 - var isMini = msgEl.classList.contains('message--mini') 44 - if (this.checked) { 45 - if (isMini) { 46 - if (!raw) { 47 - raw = h('div.message_raw', 48 - h('pre', h('code', 49 - linkify(JSON.stringify({ 50 - key: msg.key, 51 - value: msg.value 52 - }, 0, 2)) 53 - )) 54 - ) 55 - } 56 - msgEl.appendChild(raw) 57 - } else { 58 - // move away the content 59 - while (el = msgContentEl.firstChild) 60 - tmp.appendChild(el) 61 - // show the raw stuff 62 - if (!pre) pre = h('pre', linkify(JSON.stringify({ 63 - key: msg.key, 64 - value: msg.value 65 - }, 0, 2))) 66 - msgContentEl.appendChild(pre) 67 - } 68 - } else { 69 - if (isMini) { 70 - if (raw && raw.parentNode) raw.parentNode.removeChild(raw) 71 - } else { 72 - // hide the raw stuff 73 - msgContentEl.removeChild(pre) 74 - // put back the content 75 - while (el = tmp.firstChild) 76 - msgContentEl.appendChild(el) 77 - } 78 - } 79 - } 80 - }) 81 - } 82 - } 1 + module.exports = require('../../patchbay/modules_extra/raw.js')
+1 -108
decent/modules_extra/search.js
··· 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') 6 - 7 - //var plugs = require('../plugs') 8 - //var message_render = plugs.first(exports.message_render = []) 9 - //var sbot_log = plugs.first(exports.sbot_log = []) 10 - 11 - exports.needs = { 12 - message_render: 'first', 13 - sbot_log: 'first' 14 - } 15 - 16 - exports.gives = 'screen_view' 17 - 18 - var whitespace = /\s+/ 19 - 20 - function andSearch(terms, inputs) { 21 - for(var i = 0; i < terms.length; i++) { 22 - var match = false 23 - for(var j = 0; j < inputs.length; j++) { 24 - if(terms[i].test(inputs[j])) match = true 25 - } 26 - //if a term was not matched by anything, filter this one 27 - if(!match) return false 28 - } 29 - return true 30 - } 31 - 32 - function searchFilter(terms) { 33 - return function (msg) { 34 - var c = msg && msg.value && msg.value.content 35 - return c && ( 36 - msg.key == terms[0] || 37 - andSearch(terms.map(function (term) { 38 - return new RegExp('\\b'+term+'\\b', 'i') 39 - }), [c.text, c.name, c.title]) 40 - ) 41 - } 42 - } 43 - 44 - function createOrRegExp(ary) { 45 - return new RegExp(ary.map(function (e) { 46 - return '\\b'+e+'\\b' 47 - }).join('|'), 'i') 48 - } 49 - 50 - function highlight(el, query) { 51 - var searcher = new TextNodeSearcher({container: el}) 52 - searcher.query = query 53 - searcher.highlight() 54 - return el 55 - } 56 - 57 - exports.create = function (api) { 58 - 59 - return function (path) { 60 - if(path[0] === '?') { 61 - var query = path.substr(1).trim().split(whitespace) 62 - var _matches = searchFilter(query) 63 - 64 - var total = 0, matches = 0 65 - 66 - var header = h('div.search_header', '') 67 - var content = h('div.column.scroller__content') 68 - var div = h('div.column.scroller', 69 - {style: {'overflow':'auto'}}, 70 - h('div.scroller__wrapper', 71 - header, 72 - content 73 - ) 74 - ) 75 - 76 - function matchesQuery (data) { 77 - total++ 78 - var m = _matches(data) 79 - if(m) matches++ 80 - header.textContent = 'searched:'+total+', found:'+matches 81 - return m 82 - } 83 - 84 - 85 - 86 - function renderMsg(msg) { 87 - var el = api.message_render(msg) 88 - highlight(el, createOrRegExp(query)) 89 - return el 90 - } 91 - 92 - pull( 93 - api.sbot_log({old: false}), 94 - pull.filter(matchesQuery), 95 - Scroller(div, content, renderMsg, true, false) 96 - ) 97 - 98 - pull( 99 - u.next(api.sbot_log, {reverse: true, limit: 500, live: false}), 100 - pull.filter(matchesQuery), 101 - Scroller(div, content, renderMsg, false, false) 102 - ) 103 - 104 - return div 105 - } 106 - } 107 - 108 - } 1 + module.exports = require('../../patchbay/modules_extra/search.js')
+1 -27
decent/modules_extra/split.js
··· 1 - var h = require('hyperscript') 2 - 3 - //var screen_view = 4 - // require('../plugs').first(exports._screen_view = []) 5 - 6 - 7 - exports.needs = { 8 - screen_view: 'first' 9 - } 10 - 11 - exports.gives = 'screen_view' 12 - 13 - exports.create = function (api) { 14 - 15 - return function (path) { 16 - var m = /^split\s*\((.*)\)$/.exec(path) 17 - if(!m) 18 - return 19 - 20 - return h('div.row', 21 - m[1].split(',').map(function (e) { 22 - return api.screen_view(e.trim()) 23 - }).filter(Boolean) 24 - ) 25 - } 26 - 27 - } 1 + module.exports = require('../../patchbay/modules_extra/split.js')
+1 -32
decent/modules_extra/suggest-emoji.js
··· 1 - //var plugs = require('../plugs') 2 - //var emoji_url = plugs.first(exports.emoji_url = []) 3 - //var emoji_names = plugs.first(exports.emoji_names = []) 4 - 5 - exports.needs = { 6 - emoji_url: 'first', 7 - emoji_names: 'first' 8 - } 9 - 10 - exports.gives = 'suggest_mentions' 11 - 12 - exports.create = function (api) { 13 - 14 - return function (word) { 15 - return function (cb) { 16 - if (word[0] !== ':' || word.length < 2) return cb() 17 - word = word.substr(1) 18 - if (word[word.length-1] === ':') word = word.substr(0, word.length-1) 19 - cb(null, api.emoji_names().filter(function (name) { 20 - return name.substr(0, word.length) === word 21 - }).slice(0, 50).map(function (emoji) { 22 - return { 23 - image: api.emoji_url(emoji), 24 - title: emoji, 25 - subtitle: emoji, 26 - value: ':' + emoji + ':' 27 - } 28 - })) 29 - } 30 - } 31 - 32 - } 1 + module.exports = require('../../patchbay/modules_extra/suggest-emoji.js')
+1 -50
decent/modules_extra/versions.js
··· 1 - var h = require('hyperscript') 2 - 3 - exports.gives = { 4 - menu_items: true, 5 - builtin_tabs: true, 6 - screen_view: true 7 - } 8 - 9 - exports.create = function () { 10 - 11 - return { 12 - menu_items: function () { 13 - return h('a', {href: '#versions'}, 'Versions') 14 - }, 15 - 16 - builtin_tabs: function () { 17 - return ['versions'] 18 - }, 19 - 20 - screen_view: function (path) { 21 - if(path !== 'versions') return 22 - 23 - if('undefined' === typeof WebBoot) 24 - return h('h1', 'must run with web-boot enabled enviroment') 25 - 26 - var content = h('div.column') 27 - 28 - WebBoot.versions(function (err, log) { 29 - log.forEach(function (e, i) { 30 - content.appendChild( 31 - h('div.row', 32 - h('a', { 33 - href: '#run:'+e.value, 34 - onclick: function () { 35 - WebBoot.run(e.value, function () { 36 - console.log('rebooting to:', e.value) 37 - }) 38 - } 39 - }, ' ', e.value, ' ', new Date(e.ts)), 40 - !i && h('label', '(current)') 41 - ) 42 - ) 43 - }) 44 - 45 - }) 46 - 47 - return content 48 - } 49 - } 50 - } 1 + module.exports = require('../../patchbay/modules_extra/versions.js')
+1 -40
decent/plugs.js
··· 1 - exports.first = function first(plug) { 2 - return function () { 3 - var args = [].slice.call(arguments) 4 - for(var i = 0; i < plug.length; i++) { 5 - var val = plug[i].apply(null, args) 6 - if(val) return val 7 - } 8 - } 9 - } 10 - 11 - exports.map = function (plug) { 12 - return function () { 13 - var args = [].slice.call(arguments) 14 - return plug.map(function (fn) { 15 - if(fn) return fn.apply(null, args) 16 - }).filter(Boolean) 17 - } 18 - } 19 - 20 - exports.asyncConcat = function (plug) { 21 - return function () { 22 - var args = [].slice.call(arguments) 23 - var cb = args.pop() 24 - var allResults = [] 25 - var waiting = plug.length 26 - plug.forEach(function (fn) { 27 - if (!fn) return next() 28 - fn.apply(null, args.concat(next)) 29 - }) 30 - function next(err, results) { 31 - if (err) { 32 - waiting = 0 33 - return cb(err) 34 - } 35 - if (results) allResults = allResults.concat(results) 36 - if (--waiting === 0) cb(null, allResults) 37 - } 38 - } 39 - } 40 - 1 + module.exports = require('../patchbay/plugs.js')
+1 -51
decent/util.js
··· 1 - var pull = require('pull-stream') 2 - var Next = require('pull-next') 3 - 4 - function get (obj, path) { 5 - if(!obj) return undefined 6 - if('string' === typeof path) return obj[path] 7 - if(Array.isArray(path)) { 8 - for(var i = 0; obj && i < path.length; i++) 9 - obj = obj[path[i]] 10 - return obj 11 - } 12 - } 13 - 14 - function clone (obj) { 15 - var _obj = {} 16 - for(var k in obj) _obj[k] = obj[k] 17 - return _obj 18 - } 19 - 20 - exports.next = function (createStream, opts, property, range) { 21 - 22 - range = range || (opts.reverse ? 'lt' : 'gt') 23 - property = property || 'timestamp' 24 - 25 - var last = null, count = -1 26 - return Next(function () { 27 - if(last) { 28 - if(count === 0) return 29 - var value = opts[range] = get(last, property) 30 - if(value == null) return 31 - last = null 32 - } 33 - return pull( 34 - createStream(clone(opts)), 35 - pull.through(function (msg) { 36 - count ++ 37 - if(!msg.sync) { 38 - last = msg 39 - } 40 - }, function (err) { 41 - //retry on errors... 42 - if(err) return count = -1 43 - //end stream if there were no results 44 - if(last == null) last = {} 45 - }) 46 - ) 47 - }) 48 - } 49 - 50 - 51 - 1 + module.exports = require('../patchbay/util.js')