sbot/ui/lib/util.js

313 lines
7.9 KiB
JavaScript

var pull = require('pull-stream')
var mlib = require('ssb-msgs')
var mime = require('mime-types')
var multicb = require('multicb')
var app = require('./app')
var social = require('./social-graph')
exports.debounce = function (fn, wait) {
var timeout
return function() {
clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
exports.getJson = function(path, cb) {
var xhr = new XMLHttpRequest()
xhr.open('GET', path, true)
xhr.responseType = 'json'
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var err
if (xhr.status < 200 || xhr.status >= 400)
err = new Error(xhr.status + ' ' + xhr.statusText)
cb(err, xhr.response)
}
}
xhr.send()
}
exports.postJson = function(path, obj, cb) {
var xhr = new XMLHttpRequest()
xhr.open('POST', path, true)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.responseType = 'json'
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var err
if (xhr.status < 200 || xhr.status >= 400)
err = new Error(xhr.status + ' ' + xhr.statusText)
cb(err, xhr.response)
}
}
xhr.send(JSON.stringify(obj))
}
exports.prettydate = require('nicedate')
var escapePlain =
exports.escapePlain = function(str) {
return (str||'')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
exports.shortString = function(str, len) {
len = len || 6
if (str.length - 3 > len)
return str.slice(0, len) + '...'
return str
}
var dataSizes = ['kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
exports.bytesHuman = function (nBytes) {
var str = nBytes + 'b'
for (var i = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, i++) {
str = nApprox.toFixed(2) + dataSizes[i]
}
return str
}
// http://stackoverflow.com/a/23329386
exports.stringByteLength = function (str) {
// returns the byte length of an utf8 string
var s = str.length;
for (var i=str.length-1; i>=0; i--) {
var code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) s++;
else if (code > 0x7ff && code <= 0xffff) s+=2;
}
return s;
}
// https://stackoverflow.com/questions/2541481/get-average-color-of-image-via-javascript
exports.getAverageRGB = function (imgEl) {
var blockSize = 5, // only visit every 5 pixels
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {r:0,g:0,b:0},
count = 0
if (!context) {
return null
}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width
context.drawImage(imgEl, 0, 0)
try {
data = context.getImageData(0, 0, width, height)
} catch(e) {
return null
}
length = data.data.length
while ( (i += blockSize * 4) < length ) {
++count
rgb.r += data.data[i]
rgb.g += data.data[i+1]
rgb.b += data.data[i+2]
}
rgb.r = (rgb.r/count)|0
rgb.g = (rgb.g/count)|0
rgb.b = (rgb.b/count)|0
return rgb
}
function votesFetcher (fetchFn) {
return function (voteTopic, cb) {
var stats = { uservote: 0, voteTally: 0, votes: {}, upvoters: [], downvoters: [] }
pull(
app.ssb[fetchFn]({ id: voteTopic, rel: 'voteTopic' }),
pull.asyncMap(function (link, cb2) { app.ssb.get(link.message, cb2) }),
pull.collect(function (err, voteMsgs) {
if (err)
return cb(err)
// collect final votes
voteMsgs.forEach(function (m) {
if (m.content.type !== 'vote')
return
if (m.content.vote === 1 || m.content.vote === 0 || m.content.vote === -1)
stats.votes[m.author] = m.content.vote
})
// tally the votes
for (var author in stats.votes) {
var v = stats.votes[author]
if (v === 1) {
stats.upvoters.push(author)
stats.voteTally++
}
else if (v === -1) {
stats.downvoters.push(author)
stats.voteTally--
}
}
stats.uservote = stats.votes[app.user.id] || 0
cb(null, stats)
})
)
}
}
// :TODO: cant do fetchMsgVotes because the messagesLinkedToMessage fetcher works differently than all the others
// see https://github.com/ssbc/secure-scuttlebutt/issues/99
exports.fetchFeedVotes = votesFetcher('messagesLinkedToFeed')
exports.fetchExtVotes = votesFetcher('feedsLinkedToExternal')
exports.calcMessageStats = function (thread, opts) {
var stats = { comments: 0, uservote: 0, voteTally: 0, votes: {} }
function process (t, depth) {
if (!t.related)
return
t.related.forEach(function (r) {
var c = r.value.content
// only process votes for immediate children
if (depth === 0 && c.type === 'vote') {
// track latest choice, dont tally yet in case multiple votes by one user
stats.votes[r.value.author] = c.vote
}
else if (c.type !== 'vote') {
// count non-votes as a comment
stats.comments++
}
// recurse
if (opts && opts.recursive)
process(r)
})
}
process(thread, 0)
// now tally the votes
for (var author in stats.votes) {
var v = stats.votes[author]
if (v === 1) {
stats.voteTally++
}
else if (v === -1) {
stats.voteTally--
}
}
stats.uservote = stats.votes[app.user.id] || 0
return stats
}
exports.getOtherNames = function (profile) {
// todo - replace with ranked names
var name = app.users.names[profile.id] || profile.id
var names = []
function add(n) {
if (n && n !== name && !~names.indexOf(n))
names.push(n)
}
// get 3 of the given or self-assigned names
add(profile.self.name)
for (var k in profile.assignedBy) {
if (names.length >= 3)
break
add(profile.assignedBy[k].name)
}
return names
}
exports.getParentThread = function (mid, cb) {
up()
function up () {
app.ssb.get(mid, function (err, msg) {
if (err)
return cb(err)
// not found? finish here
if (!msg)
return finish()
// root link? go straight to that
if (mlib.link(msg.content.root, 'msg')) {
mid = mlib.link(msg.content.root).link
return finish()
}
// branch link? ascend
if (mlib.link(msg.content.branch, 'msg')) {
mid = mlib.link(msg.content.branch).link
return up()
}
// topmost, finish
finish()
})
}
function finish () {
app.ssb.relatedMessages({ id: mid, count: true, parent: true }, cb)
}
}
exports.decryptThread = function (thread, cb) {
var done = multicb()
if (thread.related)
iterate(thread.related)
done(cb)
function iterate (msgs) {
msgs.forEach(function (msg) {
msg.plaintext = (typeof msg.value.content != 'string')
if (!msg.plaintext)
decrypt(msg)
if (msg.related)
iterate(msg.related)
})
}
function decrypt (msg) {
var cb2 = done()
app.ssb.private.unbox(msg.value.content, function (err, decrypted) {
if (decrypted)
msg.value.content = decrypted
cb2()
})
}
}
exports.getPubStats = function () {
var membersof=0, active=0, untried=0
app.peers.forEach(function (peer) {
// filter out LAN peers
if (peer.host == 'localhost' || peer.host.indexOf('192.168.') === 0)
return
if (social.follows(peer.key, app.user.id)) {
membersof++
if (peer.time && peer.time.connect && (peer.time.connect > peer.time.attempt) || peer.connected)
active++
if (!peer.time || !peer.time.attempt)
untried++
}
})
return { membersof: membersof, active: active, untried: untried, hasSyncIssue: (!membersof || (!untried && !active)) }
}
exports.getExtLinkName = function (link) {
if (link.name && typeof link.name == 'string')
return link.name
if (link.type) {
var ext = mime.extension(link.type)
if (ext)
return 'untitled.'+ext
}
return 'untitled'
}