move ssb-patchwork-api and ssb-patchwork-ui into this repo

This commit is contained in:
Paul Frazee
2015-09-22 13:44:28 -05:00
parent ca4830cec8
commit 0f60126e9d
1094 changed files with 22959 additions and 8 deletions

25
ui/lib/pages/drive.js Normal file
View File

@@ -0,0 +1,25 @@
'use strict'
var h = require('hyperscript')
var app = require('../app')
var ui = require('../ui')
var com = require('../com')
module.exports = function (opts) {
// markup
ui.setPage('drive', h('.layout-onecol',
h('.layout-main',
(opts && opts.download) ?
h('.well.white', { style: 'margin-top: 5px' },
h('p', h('strong', opts.download)),
h('a.btn.btn-3d', 'Save to your files as...'), ' ', h('a.btn.btn-3d', 'Download...')) :
'',
h('.pull-right',
h('a.btn.btn-3d', 'Upload File')
),
h('h3', 'Your Drive ', h('small', 'Non-functional Mockup Interface')),
com.files(app.user.id)
)
))
}

29
ui/lib/pages/feed.js Normal file
View File

@@ -0,0 +1,29 @@
'use strict'
var h = require('hyperscript')
var mlib = require('ssb-msgs')
var pull = require('pull-stream')
var multicb = require('multicb')
var app = require('../app')
var ui = require('../ui')
var com = require('../com')
module.exports = function (pid) {
// markup
var feed = app.ssb.createFeedStream
if (pid) {
feed = function (opts) {
opts = opts || {}
opts.id = pid
return app.ssb.createUserStream(opts)
}
}
ui.setPage('feed', h('.layout-onecol',
h('.layout-main',
h('h3.text-center', 'Behind the Scenes ', h('small', 'Raw Data Feed')),
com.messageFeed({ feed: feed, render: com.messageSummary.raw, infinite: true })
)
))
}

56
ui/lib/pages/friends.js Normal file
View File

@@ -0,0 +1,56 @@
'use strict'
var h = require('hyperscript')
var ref = require('ssb-ref')
var app = require('../app')
var ui = require('../ui')
var com = require('../com')
var social = require('../social-graph')
module.exports = function () {
var queryStr = app.page.qs.q || ''
var queryRegex
// filters
function stdfilter (prof) {
if (prof.id == app.user.id) // is self
return true
if (app.users.names[prof.id] || social.follows(app.user.id, prof.id)) // has a name, or is a friend
return true
}
function isRecommended (prof) {
var nfollowers = social.followedFollowers(app.user.id, prof.id).length
var nflaggers = social.followedFlaggers(app.user.id, prof.id, true).length
if (prof.id != app.user.id && !social.follows(app.user.id, prof.id) && nfollowers && !nflaggers)
return true
}
function recommendFilterFn (prof) {
if (!stdfilter(prof))
return false
return isRecommended(prof)
}
function othersFilterFn (prof) {
if (!stdfilter(prof))
return false
return (prof.id != app.user.id && !social.follows(app.user.id, prof.id) && !isRecommended(prof))
}
// markup
var newFollowersToShow = Math.max(app.indexCounts.followsUnread, 30)
ui.setPage('followers', h('.layout-onecol',
h('.layout-main',
h('h3', 'Following'),
h('div', { style: 'width: 850px; margin: 0 auto' }, com.friendsHexagrid({ size: 80, nrow: 10 })),
h('h3', 'Activity'),
com.messageFeed({ render: com.messageSummary, feed: app.ssb.patchwork.createFollowStream, markread: true, limit: newFollowersToShow }),
h('h3', { style: 'margin-top: 40px' }, 'Recommendations'),
com.contactFeed({ filter: recommendFilterFn }),
h('h3', { style: 'margin-top: 40px' }, 'Others'),
com.contactFeed({ filter: othersFilterFn })
)
))
}

71
ui/lib/pages/home.js Normal file
View File

@@ -0,0 +1,71 @@
'use strict'
var h = require('hyperscript')
var o = require('observable')
var mlib = require('ssb-msgs')
var pull = require('pull-stream')
var app = require('../app')
var ui = require('../ui')
var com = require('../com')
var social = require('../social-graph')
module.exports = function () {
var hlf // home live feed
// filters
var p = app.user.profile
function homeFilter (m) {
var a = m.value.author
if (app.users.profiles[a] && app.users.profiles[a].flagged) // flagged by user
return false
if (app.homeMode.view == 'all')
return true
if (app.homeMode.view == 'friends')
return a == app.user.id || social.follows(app.user.id, a)
return social.follows(app.homeMode.view, a) // `view` is the id of a pub
}
// live-mode
if (app.homeMode.live)
hlf = app.ssb.patchwork.createHomeStream({ gt: [Date.now(), null], live: true })
// markup
function render (msg) {
return com.message(msg, { markread: true })
}
function notification (vo, href, title, icon) {
return o.transform(vo, function (v) {
var cls = (v>0) ? '.highlight' : ''
return h('a'+cls, { href: href, title: title }, com.icon(icon), ' ', v)
})
}
ui.setPage('home', h('.layout-twocol',
h('.layout-main',
com.notifications(),
com.composer(null, null, { placeholder: 'Share a message with the world...' }),
com.messageFeed({ feed: app.ssb.patchwork.createHomeStream, render: render, onempty: onempty, filter: homeFilter, limit: 100, infinite: true, live: hlf })
),
h('.layout-rightnav',
h('.shortcuts',
notification(app.observ.indexCounts.inboxUnread, '#/inbox', 'Your inbox', 'inbox'),
notification(app.observ.indexCounts.votesUnread, '#/stars', 'Stars on your posts, and stars by you', 'star'),
notification(app.observ.indexCounts.followsUnread, '#/friends', 'Friends, followers, and other users', 'user')
),
com.notifications.side(),
com.friendsHexagrid({ size: 80 }),
com.help.side()
)
), { onPageTeardown: function () {
// abort streams
hlf && hlf(true, function(){})
}})
function onempty (el) {
if (app.homeMode.view == 'all')
el.appendChild(com.help.welcome())
}
}

32
ui/lib/pages/inbox.js Normal file
View File

@@ -0,0 +1,32 @@
'use strict'
var h = require('hyperscript')
var mlib = require('ssb-msgs')
var pull = require('pull-stream')
var multicb = require('multicb')
var app = require('../app')
var ui = require('../ui')
var com = require('../com')
var social = require('../social-graph')
var subwindows = require('../ui/subwindows')
module.exports = function () {
// markup
ui.setPage('inbox', h('.layout-onecol',
h('.layout-main',
h('a.btn.btn-3d.pull-right', { onclick: function (e) { e.preventDefault(); subwindows.pm() } }, com.icon('envelope'), ' Secret Message'),
h('h3', 'Inbox'),
com.messageFeed({ render: com.messageOneline, feed: app.ssb.patchwork.createInboxStream, filter: filter, onempty: onempty, infinite: true })
)
))
function onempty (feedEl) {
feedEl.appendChild(h('p.text-center', { style: 'margin: 25px 0; padding: 10px; color: gray' }, 'Your inbox is empty!'))
}
function filter (msg) {
var a = msg.value.author
return a == app.user.id || social.follows(app.user.id, a)
}
}

31
ui/lib/pages/index.js Normal file
View File

@@ -0,0 +1,31 @@
'use strict'
var h = require('hyperscript')
var ui = require('../ui')
var com = require('../com')
function notfound () {
ui.setPage('notfound', [
h('img', { src: 'img/lick-the-door.gif', style: 'display: block; margin: 10px auto; border-radius: 3px;' }),
h('h2.text-center', 'Page Not Found'),
h('div.text-center', { style: 'margin-top: 20px' },
'Sorry, that page wasn\'t found. Maybe you typed the name wrong? Or maybe somebody gave you a bad link.'
)
])
}
module.exports = {
drive: require('./drive'),
feed: require('./feed'),
friends: require('./friends'),
home: require('./home'),
inbox: require('./inbox'),
msg: require('./message'),
notfound: notfound,
profile: require('./profile'),
publisher: require('./publisher'),
search: require('./search'),
setup: require('./setup'),
stars: require('./stars'),
sync: require('./sync'),
webview: require('./webview')
}

54
ui/lib/pages/message.js Normal file
View File

@@ -0,0 +1,54 @@
'use strict'
var h = require('hyperscript')
var mlib = require('ssb-msgs')
var app = require('../app')
var ui = require('../ui')
var anim = require('../ui/anim')
var com = require('../com')
var util = require('../util')
module.exports = function () {
app.ssb.get(app.page.param, function (err, msg) {
var content
var isEncrypted = false
var secretMessageLabel
if (msg) {
msg = { key: app.page.param, value: msg }
content = com.message(msg, { markread: true, fullview: true, live: true })
// if encrypted, add the animated 'secret message' label
if (typeof msg.value.content == 'string') {
isEncrypted = true
secretMessageLabel = h('span')
anim.textDecoding(secretMessageLabel, 'Secret Thread')
}
} else {
content = 'Message not found.'
}
ui.setPage('message', h('.layout-twocol',
h('.layout-main', content),
h('.layout-rightnav',
(isEncrypted) ?
h('.text-center',
h('p', h('code', { style: 'font-size: 18px' }, secretMessageLabel, ' ', com.icon('lock'))),
h('p', 'All messages in this thread are encrypted.')
) :
''
)
))
if (app.page.qs.jumpto) {
setTimeout(function (){
var el = document.querySelector('.message[data-key="'+app.page.qs.jumpto+'"]')
if (el) {
el.scrollIntoView()
if ((window.innerHeight + window.scrollY) < document.body.offsetHeight)
window.scrollBy(0, -100) // show a little above, if not at the bottom of the page
}
}, 50)
}
})
}

200
ui/lib/pages/profile.js Normal file
View File

@@ -0,0 +1,200 @@
'use strict'
var h = require('hyperscript')
var refs = require('ssb-ref')
var mlib = require('ssb-msgs')
var multicb = require('multicb')
var schemas = require('ssb-msg-schemas')
var pull = require('pull-stream')
var app = require('../app')
var ui = require('../ui')
var modals = require('../ui/modals')
var subwindows = require('../ui/subwindows')
var com = require('../com')
var u = require('../util')
var markdown = require('../markdown')
var mentions = require('../mentions')
var social = require('../social-graph')
module.exports = function () {
var pid = app.page.param
var profile = app.users.profiles[pid]
var name = com.userName(pid)
// user not found
if (!profile) {
if (refs.isFeedId(pid)) {
profile = {
assignedBy: {},
id: pid,
isEmpty: true
}
} else {
ui.setPage('profile', h('.layout-twocol',
h('.layout-main',
h('.well', { style: 'margin-top: 5px; background: #fff' },
h('h3', { style: 'margin-top: 0' }, 'Invalid user ID'),
h('p',
h('em', pid),
' is not a valid user ID. ',
h('img.emoji', { src: './img/emoji/disappointed.png', title: 'disappointed', width: 20, height: 20, style: 'vertical-align: top' })
)
)
)
))
return
}
}
var isSelf = (pid == app.user.id)
var isFollowing = social.follows(app.user.id, pid)
var followsYou = social.follows(pid, app.user.id)
var hasFlagged = social.flags(app.user.id, pid)
var hasBlocked = social.blocks(app.user.id, pid)
var followers1 = social.followedFollowers(app.user.id, pid, true)
var followers2 = social.unfollowedFollowers(app.user.id, pid)
var followeds = social.followeds(pid)
var flaggers = social.followedFlaggers(app.user.id, pid, true)
// name conflict controls
var nameConflictDlg
var nameConflicts = []
for (var id in app.users.names) {
if (id != pid && app.users.names[id] == app.users.names[pid])
nameConflicts.push(id)
}
if (nameConflicts.length) {
nameConflictDlg = h('.well.white', { style: 'margin: -10px 15px 15px' },
h('p', { style: 'margin-bottom: 10px' }, h('strong', 'Other users named "'+app.users.names[pid]+'":')),
h('ul.list-inline', nameConflicts.map(function (id) { return h('li', com.user(id)) })),
h('p', h('small', 'ProTip: You can rename users to avoid getting confused!'))
)
}
// flag controls
var flagMsgs
if (flaggers.length) {
flagMsgs = h('.profile-flags.message-feed')
flaggers.forEach(function (id) {
var flag = social.flags(id, pid)
if (flag.reason && flag.key) {
app.ssb.get(flag.key, function (err, flagMsg) {
if (err) console.error(err)
if (flagMsg) flagMsgs.appendChild(com.message({ key: flag.key, value: flagMsg }))
})
}
})
}
var hasMsgs = false
var content = com.messageFeed({ feed: feedFn, cursor: feedCursor, filter: feedFilter, infinite: true, onempty: onNoMsgs })
function feedFn (opts) {
opts = opts || {}
opts.id = pid
return app.ssb.createUserStream(opts)
}
function feedCursor (msg) {
if (msg)
return msg.value.sequence
}
function feedFilter (msg) {
hasMsgs = true
// post by this user
var c = msg.value.content
if (msg.value.author == pid && c.type == 'post')
return true
}
function onNoMsgs (feedEl) {
if (hasMsgs) {
feedEl.appendChild(h('p.text-center.text-muted', h('br'), 'No posts...yet!'))
} else {
feedEl.appendChild(h('div', { style: 'margin: 12px 1px; background: #fff; padding: 15px 15px 10px' },
h('h3', { style: 'margin-top: 0' }, 'Umm... who is this?'),
h('p', 'This user\'s data hasn\'t been fetched yet, so we don\'t know anything about them!'),
h('p', com.userDownloader(pid))
))
}
}
// render page
ui.setPage('profile', h('.layout-twocol',
h('.layout-main',
h('.profile-header',
h('h1', h('strong', name)),
h('a.btn.btn-3d', { href: '#', onclick: privateMessage, title: 'Send an encrypted message to '+name }, com.icon('envelope'), ' Secret Message')
),
flagMsgs ? h('.message-feed-container', flagMsgs) : '',
content),
h('.layout-rightnav',
h('.profile-controls',
com.contactPlaque(profile, followers1.length + followers2.length, flaggers.length),
(hasBlocked) ? h('.block', 'BLOCKED') : '',
(!isSelf) ?
[
(followsYou) ? h('.follows-you', 'Follows You') : '',
h('.btns',
h('.btns-group',
(hasBlocked) ? '' : h('a.btn.btn-3d', { href: '#', onclick: toggleFollow }, com.icon('user'), ((isFollowing) ? ' Unfollow' : ' Follow')),
' ',
h('a.btn.btn-3d', { href: '#', onclick: renameModal }, com.icon('pencil'), ' Rename'),
' ',
h('a.btn.btn-3d', { href: '#', onclick: flagModal }, com.icon('flag'), ((!!hasFlagged) ? ' Unflag' : ' Flag'))))
] :
h('.btns.text-center', { style: 'padding-right: 10px' },
h('a.btn.btn-3d', { href: '#/setup', title: 'Update your name or image' }, com.icon('pencil'), ' Edit Your Profile')),
nameConflictDlg,
(!isSelf) ?
com.connectionGraph(app.user.id, pid, { w: 5.5, drawLabels: false, touchEnabled: false, mouseEnabled: false, mouseWheelEnabled: false }) :
'',
(flaggers.length) ? h('.relations', h('h4', 'flagged by'), com.userHexagrid(flaggers, { nrow: 4 })) : '',
(followers1.length) ? h('.relations', h('h4', 'followers you follow'), com.userHexagrid(followers1, { nrow: 4 })) : '',
(followers2.length) ? h('.relations', h('h4', 'followers you don\'t follow'), com.userHexagrid(followers2, { nrow: 4 })) : '',
(followeds.length) ? h('.relations', h('h4', name, ' is following'), com.userHexagrid(followeds, { nrow: 4 })) : ''
)
)
))
// handlers
function privateMessage (e) {
e.preventDefault()
subwindows.pm({ recipients: [pid] })
}
function renameModal (e) {
e.preventDefault()
modals.setName(pid)
}
function toggleFollow (e) {
e.preventDefault()
if (isSelf)
return
ui.pleaseWait(true, 500)
if (isFollowing)
app.ssb.publish(schemas.unfollow(pid), done)
else
app.ssb.publish(schemas.follow(pid), done)
function done (err) {
ui.pleaseWait(false)
if (err) modals.error('Error While Publishing', err, 'This error occured while trying to un/follow somebody on their profile page.')
else ui.refreshPage()
}
}
function flagModal (e) {
e.preventDefault()
if (isSelf)
return
if (!hasFlagged)
modals.flag(pid)
else {
var done = multicb()
app.ssb.publish(schemas.unblock(pid), done())
app.ssb.publish(schemas.unflag(pid), done())
done(function (err) {
if (err) modals.error('Error While Publishing', err, 'This error occured while trying to un/flag somebody on their profile page.')
else ui.refreshPage()
})
}
}
}

289
ui/lib/pages/publisher.js Normal file
View File

@@ -0,0 +1,289 @@
'use strict'
var h = require('hyperscript')
var o = require('observable')
var ref = require('ssb-ref')
var mime = require('mime-types')
var pwt = require('published-working-tree')
var multicb = require('multicb')
var app = require('../app')
var ui = require('../ui')
var u = require('../util')
var modals = require('../ui/modals')
var com = require('../com')
var social = require('../social-graph')
// symbols, used to avoid collisions with filenames
var ISROOT = Symbol('isroot')
var ISOPEN = Symbol('isopen')
module.exports = function () {
var done = multicb({ pluck: 1 })
app.ssb.patchwork.getPaths(done())
app.ssb.patchwork.getSite(app.user.id, done())
done(function (err, res) {
if (err)
return modals.error('Error Loading User Info', err, 'This error occurred while loading the publisher page')
// markup
var folderPath = o(res[0].site)
var publishedTree = res[1] ? toTree(res[1]) : null
var folderData = o()
var publishBtn
var folderInput = h('input.hidden', { type: 'file', webkitdirectory: true, directory: true, onchange: onfolderchange })
ui.setPage('publisher', h('.layout-onecol',
h('.layout-main',
h('h3', h('strong', 'Your Site')),
h('form',
h('p',
folderInput,
h('a.btn.btn-3d', { onclick: folderInput.click.bind(folderInput) }, 'Select Folder'),
' ',
h('span.files-view-pathctrl', folderPath),
publishBtn = o.transform(folderData, function (d) {
var c = pwt.changes(d)
if (!c.adds.length && !c.dels.length && !c.mods.length)
return h('a.pull-right.btn.btn-primary.disabled', 'No Changes')
var changes = []
if (c.adds.length) changes.push('+'+c.adds.length)
if (c.dels.length) changes.push('-'+c.dels.length)
if (c.mods.length) changes.push('^'+c.mods.length)
changes = changes.join(' / ')
return h('a.pull-right.btn.btn-primary', { onclick: onpublish }, 'Publish ('+changes+')')
})
),
o.transform(folderData, function (fd) {
if (!fd)
return
return h('table.files-view',
h('tbody', render(-1, fd))
)
})
)
)
))
function render (depth, item) {
if (item[pwt.TYPE] == 'file') {
return h('tr',
h('td', getchange(item)),
h('td', h('input', { type: 'checkbox', checked: item[pwt.ACTIVE], onchange: oncheck(item) })),
h('td',
h('a', { style: 'padding-left: '+(+depth*20)+'px' }, com.icon('file'), item[pwt.NAME]), ' ',
getstate(item)
),
h('td', mime.lookup(item[pwt.NAME])||''),
h('td', item[pwt.STAT] && u.bytesHuman(item[pwt.STAT].size))
)
}
var rows = []
if (!item[ISROOT]) {
var col = h('td.folder',
{ onclick: ontoggle(item) },
h('span',
{ style: 'padding-left: '+(+depth*20)+'px' },
com.icon('folder-'+(item[ISOPEN]?'open':'close')), item[pwt.NAME], ' ',
getstate(item)
)
)
col.setAttribute('colspan', 3)
rows.push(h('tr',
h('td', getchange(item)),
h('td', h('input', { type: 'checkbox', checked: item[pwt.ACTIVE], onchange: oncheck(item) })),
col
))
}
// render folders, then files
if (item[ISOPEN]) {
for (var k in item)
if (item[k][pwt.TYPE] == 'directory')
rows.push(render(depth + 1, item[k]))
for (var k in item)
if (item[k][pwt.TYPE] == 'file')
rows.push(render(depth + 1, item[k]))
}
return rows
}
function setactive (item, v) {
item[pwt.ACTIVE] = v
for (var k in item)
setactive(item[k], v)
}
function getstate (item) {
if (item[pwt.DELETED])
return h('em.text-muted', 'not on disk')
if (item[pwt.MODIFIED])
return h('em.text-muted', 'modified')
if (item[pwt.PUBLISHED])
return h('em.text-muted', 'published')
}
function getchange (item) {
return ({ add: 'add', mod: 'update', del: 'remove' })[pwt.change(item)] || ''
}
// handlers
function onfolderchange () {
folderPath(folderInput.files[0].path)
}
folderPath(function (path) {
if (!path)
return
ui.pleaseWait(true, 100)
// :TEMP HACK: create the directory if it does not exist
var fs = require('fs')
if (!fs.existsSync(path))
fs.mkdirSync(path)
pwt.loadworking(path, publishedTree, function (err, data) {
ui.pleaseWait(false)
data[ISROOT] = true
data[ISOPEN] = true
folderData(data)
})
})
function oncheck (item) {
return function () {
var newcheck = !item[pwt.ACTIVE]
if (newcheck && item[pwt.TYPE] == 'directory' && !item[pwt.DELETED]) {
// read all of the directory first
ui.pleaseWait(true, 100)
pwt.readall(item[pwt.PATH], item, next)
}
else next()
function next() {
ui.pleaseWait(false)
setactive(item, newcheck)
folderData(folderData())
}
}
}
function ontoggle (item) {
return function () {
item[ISOPEN] = !item[ISOPEN]
if (item[pwt.DIRREAD] || item[pwt.DELETED])
folderData(folderData())
else {
pwt.read(item[pwt.PATH], item, true, function () {
folderData(folderData())
})
}
}
}
function onpublish () {
var m
var c = pwt.changes(folderData())
var basepathLen = folderPath().length + 1
function renderChange (type, label) {
return function (item) {
return h(type,
h('.action', label),
h('.path', item[pwt.PATH].slice(basepathLen)),
h('.size', item[pwt.STAT] && u.bytesHuman(item[pwt.STAT].size)),
h('.type', item[pwt.STAT] && mime.lookup(item[pwt.NAME])||'')
)
}
}
function onconfirmpublish () {
var done = multicb()
var msg = {
type: 'site',
includes: c.adds.concat(c.mods).map(function (item) {
// create link
var link = {
link: null,
path: item[pwt.PATH].slice(basepathLen),
mtime: item[pwt.STAT].mtime.getTime(),
size: item[pwt.STAT].size,
type: mime.lookup(item[pwt.NAME]) || undefined
}
// add blob
var cb = done()
app.ssb.patchwork.addFileToBlobs(item[pwt.PATH], function (err, res) {
if (err) {
modals.error('Failed to Publish File', err, 'This error occurred while adding files to the blobstore in the publisher interface.')
cb(err)
} else {
link.link = res.hash
if (res.width && res.height) {
link.width = res.width
link.height = res.height
}
cb()
}
})
return link
}),
excludes: c.dels.map(function (item) {
return {
link: item.link,
path: item[pwt.PATH].slice(basepathLen)
}
})
}
m.close()
ui.pleaseWait(true, 100)
done(function (err) {
ui.pleaseWait(false)
if (err) return
app.ssb.publish(msg, function (err) {
if (err)
return modals.error('Failed to Publish Files', err, 'This error occurred while publishing the `site` message in the publisher interface.')
ui.refreshPage()
})
})
}
m = modals.default(h('.modal-form',
h('h3', 'Review Changes'),
h('.files-view-changes',
c.adds.map(renderChange('.add', '+')),
c.mods.map(renderChange('.mod', '^')),
c.dels.map(renderChange('.del', '-'))
),
h('a.btn.btn-primary', { onclick: onconfirmpublish }, 'Publish')
))
}
})
}
function set (obj, path, value) {
var k
while (true) {
k = path.shift()
if (!path.length)
break
if (!obj[k])
obj[k] = {}
obj = obj[k]
}
obj[k] = value
}
function toTree (site) {
var tree = {}
for (var path in site)
set(tree, path.split('/'), site[path])
return tree
}

66
ui/lib/pages/search.js Normal file
View File

@@ -0,0 +1,66 @@
'use strict'
var h = require('hyperscript')
var mlib = require('ssb-msgs')
var pull = require('pull-stream')
var app = require('../app')
var ui = require('../ui')
var com = require('../com')
var social = require('../social-graph')
module.exports = function () {
// filters
if (!app.page.param) {
window.location.hash = '#/'
return
}
var regex = new RegExp(app.page.param.split(' ').join('|'), 'i')
function postFeed (opts) {
opts = opts || {}
opts.type = 'post'
return app.ssb.messagesByType(opts)
}
function postFilter (m) {
var a = m.value.author, c = m.value.content
if (app.users.profiles[a] && app.users.profiles[a].flagged) // flagged by user
return false
if (c.text && regex.test(c.text))
return true
// if (app.homeMode.view == 'all')
// return true
// if (app.homeMode.view == 'friends')
// return a == app.user.id || social.follows(app.user.id, a)
// return social.follows(app.homeMode.view, a) // `view` is the id of a pub
}
function contactFilter (p) {
if (p.self.name && regex.test(p.self.name))
return true
}
function cursor (msg) {
if (msg)
return msg.ts
}
// markup
ui.setPage('home', h('.layout-twocol',
h('.layout-main',
h('h3', 'Search, "', app.page.param, '"'),
com.messageFeed({ feed: postFeed, cursor: cursor, filter: postFilter, onempty: onempty, infinite: true })
),
h('.layout-rightnav',
h('h3', 'People'),
com.contactFeed({ filter: contactFilter, compact: true, onempty: onempty })
)
))
function onempty (el) {
el.appendChild(h('p', 'No results found'))
}
}

108
ui/lib/pages/setup.js Normal file
View File

@@ -0,0 +1,108 @@
'ust strict'
var h = require('hyperscript')
var schemas = require('ssb-msg-schemas')
var app = require('../app')
var ui = require('../ui')
var modals = require('../ui/modals')
var com = require('../com')
module.exports = function () {
var name = app.users.names[app.user.id] || ''
var profilePic = null
var is_new = !name
// markup
var nameInput = h('input.form-control', { type: 'text', name: 'name', placeholder: 'Nickname', value: name, onkeyup: checkInput })
var imageUploader = com.imageUploader({ onupload: onImageUpload, existing: com.profilePicUrl(app.user.id) })
var issue = h('.text-danger.hide')
var postBtn = h('button.btn.btn-primary', { onclick: post, disabled: is_new }, 'Save')
ui.setPage('setup', h('.layout-setup',
h('.layout-setup-left',
h('h2', (is_new) ? 'New account' : 'Edit Your Profile'),
h('.panel.panel-default', { style: 'border: 0' },
h('.panel-body',
h('.form-group',
h('label.control-label', 'Your nickname'),
nameInput
)
)
),
h('.panel.panel-default', { style: 'border: 0' },
h('.panel-body',
h('label.control-label', 'Your profile pic'),
imageUploader
)
)
),
h('.layout-setup-right', h('.layout-setup-right-inner',
(is_new) ?
[
h('p', 'Welcome to ', h('strong', 'Secure Scuttlebutt!')),
h('p', 'Fill out your profile and then click ', h('strong', 'Save'), ' to get started.')
] :
h('p', 'Update your profile and then click ', h('strong', 'Save'), ' to publish the changes.'),
h('.panel.panel-default', { style: 'border: 0; display: inline-block' },
h('.panel-body', issue, postBtn)),
(!is_new) ? h('div', { style: 'padding: 0 22px' }, h('a.text-muted', { href: '#', onclick: oncancel }, 'Cancel')) : ''
))
))
// handlers
var badNameCharsRegex = /[^A-z0-9\._-]/
function checkInput (e) {
if (!nameInput.value) {
postBtn.setAttribute('disabled', true)
postBtn.classList.remove('hide')
issue.classList.add('hide')
} else if (badNameCharsRegex.test(nameInput.value)) {
issue.innerHTML = 'We\'re sorry, your name can only include A-z 0-9 . _ - and cannot have spaces.'
postBtn.setAttribute('disabled', true)
postBtn.classList.add('hide')
issue.classList.remove('hide')
} else if (nameInput.value.slice(-1) == '.') {
issue.innerHTML = 'We\'re sorry, your name cannot end with a period.'
postBtn.setAttribute('disabled', true)
postBtn.classList.add('hide')
issue.classList.remove('hide')
} else {
postBtn.removeAttribute('disabled')
postBtn.classList.remove('hide')
issue.classList.add('hide')
}
}
function post (e) {
e.preventDefault()
if (!nameInput.value)
return
// close out image uploader if the user didnt
imageUploader.forceDone(function () {
// publish
ui.pleaseWait(true, 500)
app.ssb.publish(schemas.about(app.user.id, nameInput.value, profilePic), function (err) {
ui.pleaseWait(false)
if (err) modals.error('Error While Publishing', err, 'This error occurred while trying to post a new profile in setup.')
else window.location = '#/'
})
})
}
function oncancel (e) {
e.preventDefault()
window.location = '#/profile/'+app.user.id
}
function onImageUpload (hasher) {
profilePic = {
link: '&'+hasher.digest,
size: hasher.size,
type: 'image/png',
width: 275,
height: 275
}
}
}

27
ui/lib/pages/stars.js Normal file
View File

@@ -0,0 +1,27 @@
'use strict'
var h = require('hyperscript')
var app = require('../app')
var ui = require('../ui')
var com = require('../com')
module.exports = function () {
var p = app.page.param || 'onyours'
var render = (p == 'onyours') ? com.messageSummary : com.message
var feed = (p == 'onyours') ? app.ssb.patchwork.createVoteStream : app.ssb.patchwork.createMyvoteStream
// markup
ui.setPage('stars', h('.layout-onecol',
h('.layout-main',
h('h3.text-center',
h((p == 'onyours' ? 'strong' : 'a'), { href: '#/stars/onyours'}, 'Stars on Your Posts'),
' / ',
h((p == 'byyou' ? 'strong' : 'a'), { href: '#/stars/byyou'}, 'Starred by You')
),
com.messageFeed({ render: render, feed: feed, markread: true, onempty: onempty, infinite: true }))
))
function onempty (feedEl) {
feedEl.appendChild(h('p.text-center', { style: 'margin: 25px 0; padding: 10px; color: gray' }, 'No stars... yet!'))
}
}

113
ui/lib/pages/sync.js Normal file
View File

@@ -0,0 +1,113 @@
'use strict'
var h = require('hyperscript')
var o = require('observable')
var mlib = require('ssb-msgs')
var pull = require('pull-stream')
var multicb = require('multicb')
var app = require('../app')
var ui = require('../ui')
var modals = require('../ui/modals')
var com = require('../com')
var u = require('../util')
var social = require('../social-graph')
module.exports = function () {
// markup
var pubStatusEl = h('.pub-status')
var peersEl = h('.peers',
h('h3', 'Mesh Network', h('a.pull-right.btn.btn-3d.btn-sm', { onclick: addnode }, com.icon('plus'), ' Add Node...')),
o.transform(app.observ.peers, function (peers) {
return h('div', peers.map(renderPeer))
})
)
ui.setPage('sync', h('.layout-onecol',
h('.layout-main',
o.transform(app.observ.peers, function () {
var stats = u.getPubStats()
var danger1 = (stats.membersof === 0) ? '.text-danger' : ''
var danger2 = (stats.active === 0) ? '.text-danger' : ''
var warning
if (stats.membersof === 0)
warning = h('p', com.icon('warning-sign'), ' You need to join a pub if you want to communicate across the Internet!')
else if (stats.active === 0)
warning = h('p', com.icon('warning-sign'), ' None of your pubs are responding! Are you connected to the Internet?')
return h('.pub-status',
h('h3'+danger1, 'You\'re followed by ', stats.membersof,' public node', (stats.membersof==1?'':'s'), ' ', h('small'+danger2, stats.active, ' connected')),
warning,
h('p', h('a.btn.btn-3d', { href: '#', onclick: modals.invite }, com.icon('cloud'), ' Join a Public Node'))
)
}),
peersEl
)
))
function setprogress (el, p, label) {
el.querySelector('.progress-bar').style.width = p + '%'
el.querySelector('.progress-bar span').innerText = label
if (label)
el.querySelector('.progress-bar').style.minWidth = '12%'
else
el.querySelector('.progress-bar').style.minWidth = '2%'
}
function renderPeer (peer) {
function onsync (e) {
e.preventDefault()
app.ssb.gossip.connect({ host: peer.host, port: peer.port, key: peer.key }, function (){})
}
var lastConnect
if (peer.time) {
if (peer.time.connect > peer.time.attempt)
lastConnect = [h('span.text-success', com.icon('ok')), ' Synced '+(new Date(peer.time.connect).toLocaleString())]
else if (peer.time.attempt) {
lastConnect = [h('span.text-danger', com.icon('remove')), ' Attempted (but failed) to connect at '+(new Date(peer.time.attempt).toLocaleString())]
}
}
var el = h('.peer' + ((peer.connected)?'.connected':''), { 'data-id': peer.key },
com.userHexagon(peer.key, 80),
h('.details',
social.follows(peer.key, app.user.id) ? h('small.pull-right.label.label-success', 'Follows You') : '',
h('h3',
com.userName(peer.key),
' ',
((peer.connected) ?
h('a.btn.btn-3d.btn-xs.disabled', 'Syncing') :
h('a.btn.btn-3d.btn-xs', { href: '#', onclick: onsync }, 'Sync')),
' ',
h('br'), h('small', peer.host+':'+peer.port+':'+peer.key)
),
h('.progress', h('.progress-bar.progress-bar-striped.active', h('span'))),
h('p.last-connect', lastConnect)
)
)
if (peer.connected) {
if (!peer.progress)
setprogress(el, 0, ' Connecting... ')
else if (peer.progress.sync)
setprogress(el, 100, 'Live-streaming')
else
setprogress(el, Math.round(peer.progress.current / peer.progress.total * 100), 'Syncing...')
}
return el
}
// handlers
function addnode () {
modals.prompt('Nodes full address:', 'host:port@key', 'Connect', function (err, addr) {
app.ssb.gossip.connect(addr, function (err) {
if (err)
modals.error('Failed to Connect', err, 'Error occurred while trying to manually add a node to the network mesh.')
})
})
}
}

42
ui/lib/pages/webview.js Normal file
View File

@@ -0,0 +1,42 @@
'use strict'
var h = require('hyperscript')
var o = require('observable')
var com = require('../com')
var app = require('../app')
var ui = require('../ui')
var ssbref = require('ssb-ref')
module.exports = function (opts) {
var param = (opts && opts.param) ? opts.param : app.page.param
var port = (ssbref.isLink(param)) ? 7777 : 7778
var url = 'http://localhost:' + port + '/' + param
// markup
var webview = com.webview({ url: url })
ui.setPage('webview', h('.layout-grid',
h('.layout-grid-col.webview-left', webview),
(opts && opts.sideview) ? h('.layout-grid-col.webview-right', { style: showhide(app.observ.sideview) }, opts.sideview) : ''
), { onPageTeardown: function () {
window.removeEventListener('resize', resize)
}})
function showhide (input) {
return { display: o.transform(input, function (v) { return (v) ? 'block' : 'none' }) }
}
// dynamically size various controls
resize()
window.addEventListener('resize', resize)
function resize () {
[
[webview.querySelector('::shadow object'), 0],
[document.querySelector('.webview-page .layout-grid'), 0],
[document.querySelector('.webview-page .webview-left'), 0],
[document.querySelector('.webview-page .webview-right'), 0]
].forEach(function (entry) {
if (entry[0])
entry[0].style.height = (window.innerHeight - 40 - entry[1]) + 'px'
})
}
}