move ssb-patchwork-api and ssb-patchwork-ui into this repo
This commit is contained in:
15
ui/lib/ui/anim.js
Normal file
15
ui/lib/ui/anim.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
exports.textDecoding = function (el, text) {
|
||||
var n = 1
|
||||
var text2 = btoa(text)
|
||||
el.innerText = text2.slice(0, text.length)
|
||||
setTimeout(function () {
|
||||
var eli = setInterval(function () {
|
||||
el.innerText = text.slice(0, n) + text2.slice(n, text.length)
|
||||
n++
|
||||
if (n > text.length)
|
||||
clearInterval(eli)
|
||||
}, 33)
|
||||
}, 1500)
|
||||
}
|
||||
340
ui/lib/ui/index.js
Normal file
340
ui/lib/ui/index.js
Normal file
@@ -0,0 +1,340 @@
|
||||
var h = require('hyperscript')
|
||||
var router = require('phoenix-router')
|
||||
var remote = require('remote')
|
||||
var Menu = remote.require('menu')
|
||||
var MenuItem = remote.require('menu-item')
|
||||
var dialog = remote.require('dialog')
|
||||
var ssbref = require('ssb-ref')
|
||||
var app = require('../app')
|
||||
var com = require('../com')
|
||||
var u = require('../util')
|
||||
var pages = require('../pages')
|
||||
|
||||
var _onPageTeardown
|
||||
var _teardownTasks = []
|
||||
var _hideNav = false
|
||||
|
||||
// re-renders the page
|
||||
var refreshPage =
|
||||
module.exports.refreshPage = function (e, cb) {
|
||||
e && e.preventDefault()
|
||||
var starttime = Date.now()
|
||||
|
||||
// run the router
|
||||
var route = router('#'+(location.href.split('#')[1]||''), 'home')
|
||||
app.page.id = route[0]
|
||||
app.page.param = route[1]
|
||||
app.page.qs = route[2] || {}
|
||||
|
||||
// update state
|
||||
app.fetchLatestState(function() {
|
||||
|
||||
// re-route to setup if needed
|
||||
if (!app.users.names[app.user.id]) {
|
||||
_hideNav = true
|
||||
if (window.location.hash != '#/setup') {
|
||||
window.location.hash = '#/setup'
|
||||
cb && (typeof cb == 'function') && cb()
|
||||
return
|
||||
}
|
||||
} else
|
||||
_hideNav = false
|
||||
|
||||
// cleanup the old page
|
||||
h.cleanup()
|
||||
window.onscroll = null // commonly used for infinite scroll
|
||||
_onPageTeardown && _onPageTeardown()
|
||||
_onPageTeardown = null
|
||||
_teardownTasks.forEach(function (task) { task() })
|
||||
_teardownTasks.length = 0
|
||||
|
||||
// render the new page
|
||||
var page = pages[app.page.id]
|
||||
if (!page)
|
||||
page = pages.notfound
|
||||
page()
|
||||
|
||||
// clear pending messages, if home
|
||||
if (app.page.id == 'home')
|
||||
app.observ.newPosts(0)
|
||||
|
||||
// metrics
|
||||
console.debug('page loaded in', (Date.now() - starttime), 'ms')
|
||||
cb && (typeof cb == 'function') && cb()
|
||||
})
|
||||
}
|
||||
|
||||
var renderNav =
|
||||
module.exports.renderNav = function () {
|
||||
var navEl = document.getElementById('page-nav')
|
||||
if (_hideNav) {
|
||||
navEl.style.display = 'none'
|
||||
} else {
|
||||
navEl.style.display = 'block'
|
||||
navEl.innerHTML = ''
|
||||
navEl.appendChild(com.pagenav())
|
||||
setNavAddress()
|
||||
}
|
||||
}
|
||||
|
||||
// render a new page
|
||||
module.exports.setPage = function (name, page, opts) {
|
||||
if (opts && opts.onPageTeardown)
|
||||
_onPageTeardown = opts.onPageTeardown
|
||||
|
||||
// render nav
|
||||
renderNav()
|
||||
|
||||
// render page
|
||||
var pageEl = document.getElementById('page-container')
|
||||
pageEl.innerHTML = ''
|
||||
if (!opts || !opts.noHeader)
|
||||
pageEl.appendChild(com.page(name, page))
|
||||
else
|
||||
pageEl.appendChild(h('#page.'+name+'-page', page))
|
||||
|
||||
// scroll to top
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
var setNavAddress =
|
||||
module.exports.setNavAddress = function (location) {
|
||||
if (!location) {
|
||||
// pull from current page
|
||||
var location = app.page.id
|
||||
if (location == 'profile' || location == 'webview' || location == 'search' || location == 'msg')
|
||||
location = app.page.param
|
||||
}
|
||||
document.body.querySelector('#page-nav input').value = location
|
||||
}
|
||||
module.exports.onTeardown = function (cb) {
|
||||
_teardownTasks.push(cb)
|
||||
}
|
||||
|
||||
module.exports.navBack = function (e) {
|
||||
e && e.preventDefault()
|
||||
e && e.stopPropagation()
|
||||
window.history.back()
|
||||
}
|
||||
module.exports.navForward = function (e) {
|
||||
e && e.preventDefault()
|
||||
e && e.stopPropagation()
|
||||
window.history.forward()
|
||||
}
|
||||
module.exports.navRefresh = function (e) {
|
||||
e && e.preventDefault()
|
||||
e && e.stopPropagation()
|
||||
refreshPage()
|
||||
}
|
||||
|
||||
module.exports.contextMenu = function (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
var menu = new Menu()
|
||||
menu.append(new MenuItem({ label: 'Copy', click: oncopy }))
|
||||
menu.append(new MenuItem({ label: 'Select All', click: onselectall }))
|
||||
menu.append(new MenuItem({ type: 'separator' }))
|
||||
if (app.page.id == 'webview' && ssbref.isBlobId(app.page.param)) {
|
||||
menu.append(new MenuItem({ label: 'Save As...', click: onsaveas }))
|
||||
menu.append(new MenuItem({ type: 'separator' }))
|
||||
}
|
||||
menu.append(new MenuItem({ label: 'Open Devtools', click: function() { openDevTools() } }))
|
||||
menu.popup(remote.getCurrentWindow())
|
||||
|
||||
function oncopy () {
|
||||
var webview = document.querySelector('webview')
|
||||
if (webview)
|
||||
webview.copy()
|
||||
else
|
||||
require('clipboard').writeText(window.getSelection().toString())
|
||||
}
|
||||
function onselectall () {
|
||||
var webview = document.querySelector('webview')
|
||||
if (webview)
|
||||
webview.selectAll()
|
||||
else {
|
||||
var selection = window.getSelection()
|
||||
var range = document.createRange()
|
||||
range.selectNodeContents(document.getElementById('page'))
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
}
|
||||
function onsaveas () {
|
||||
var path = dialog.showSaveDialog(remote.getCurrentWindow())
|
||||
if (path) {
|
||||
app.ssb.patchwork.saveBlobToFile(app.page.param, path, function (err) {
|
||||
if (err) {
|
||||
alert('Error: '+err.message)
|
||||
console.error(err)
|
||||
} else
|
||||
notice('success', 'Saved to '+path, 5e3)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var openDevTools =
|
||||
module.exports.openDevTools = function () {
|
||||
var webview = document.querySelector('webview')
|
||||
if (webview)
|
||||
webview.openDevTools()
|
||||
else
|
||||
remote.getCurrentWindow().openDevTools()
|
||||
}
|
||||
var toggleDevTools =
|
||||
module.exports.toggleDevTools = function () {
|
||||
var webview = document.querySelector('webview')
|
||||
if (webview) {
|
||||
if (webview.isDevToolsOpened())
|
||||
webview.closeDevTools()
|
||||
else
|
||||
webview.openDevTools()
|
||||
} else
|
||||
remote.getCurrentWindow().toggleDevTools()
|
||||
}
|
||||
|
||||
var oldScrollTop
|
||||
module.exports.disableScrolling = function () {
|
||||
oldScrollTop = document.body.scrollTop
|
||||
document.querySelector('html').style.overflow = 'hidden'
|
||||
window.scrollTo(0, oldScrollTop)
|
||||
}
|
||||
module.exports.enableScrolling = function () {
|
||||
document.querySelector('html').style.overflow = 'auto'
|
||||
window.scrollTo(0, oldScrollTop)
|
||||
}
|
||||
|
||||
|
||||
var setStatus =
|
||||
module.exports.setStatus = function (message) {
|
||||
var status = document.getElementById('app-status')
|
||||
status.innerHTML = ''
|
||||
if (message) {
|
||||
if (message.indexOf('/profile/') === 0) {
|
||||
var id = message.slice('/profile/'.length)
|
||||
message = [h('strong', com.userName(id)), ' ', com.userRelationship(id)]
|
||||
} else if (message.indexOf('/msg/') === 0) {
|
||||
message = message.slice('/msg/'.length)
|
||||
}
|
||||
|
||||
status.appendChild(h('div', message))
|
||||
}
|
||||
}
|
||||
var numNotices = 0
|
||||
var notice =
|
||||
module.exports.notice = function (type, message, duration) {
|
||||
var notices = document.getElementById('app-notices')
|
||||
var el = h('.alert.alert-'+type, message)
|
||||
notices.appendChild(el)
|
||||
function remove () {
|
||||
notices.removeChild(el)
|
||||
}
|
||||
setTimeout(remove, duration || 15e3)
|
||||
return remove
|
||||
}
|
||||
|
||||
|
||||
var pleaseWaitTimer, uhohTimer, tooLongTimer, noticeRemove
|
||||
var pleaseWait =
|
||||
module.exports.pleaseWait = function (enabled, after) {
|
||||
function doit() {
|
||||
// clear main timer
|
||||
clearTimeout(pleaseWaitTimer); pleaseWaitTimer = null
|
||||
noticeRemove && noticeRemove()
|
||||
noticeRemove = null
|
||||
|
||||
if (enabled === false) {
|
||||
// hide spinner
|
||||
document.querySelector('#please-wait').style.display = 'none'
|
||||
setStatus(false)
|
||||
|
||||
// clear secondary timers
|
||||
clearTimeout(uhohTimer); uhohTimer = null
|
||||
clearTimeout(tooLongTimer); tooLongTimer = null
|
||||
}
|
||||
else {
|
||||
// show spinner
|
||||
document.querySelector('#please-wait').style.display = 'block'
|
||||
|
||||
// setup secondary timers
|
||||
uhohTimer = setTimeout(function () {
|
||||
noticeRemove = notice('warning', 'Hmm, this seems to be taking a while...')
|
||||
}, 5e3)
|
||||
tooLongTimer = setTimeout(function () {
|
||||
noticeRemove = notice('danger', 'I think something broke :(. Please restart Patchwork and let us know if this keeps happening!')
|
||||
}, 20e3)
|
||||
}
|
||||
}
|
||||
|
||||
// disable immediately
|
||||
if (!enabled)
|
||||
return doit()
|
||||
|
||||
// enable immediately, or after a timer (if not already waiting)
|
||||
if (!after)
|
||||
doit()
|
||||
else if (!pleaseWaitTimer)
|
||||
pleaseWaitTimer = setTimeout(doit, after)
|
||||
}
|
||||
|
||||
|
||||
module.exports.dropdown = function (el, options, opts, cb) {
|
||||
if (typeof opts == 'function') {
|
||||
cb = opts
|
||||
opts = null
|
||||
}
|
||||
opts = opts || {}
|
||||
|
||||
// render
|
||||
var dropdown = h('.dropdown'+(opts.cls||'')+(opts.right?'.right':''),
|
||||
{ onmouseleave: die },
|
||||
options.map(function (o) {
|
||||
if (o instanceof HTMLElement)
|
||||
return o
|
||||
if (o.separator)
|
||||
return h('hr')
|
||||
return h('a.item', { href: '#', onclick: onselect(o.value), title: o.title||'' }, o.label)
|
||||
})
|
||||
)
|
||||
if (opts.width)
|
||||
dropdown.style.width = opts.width + 'px'
|
||||
|
||||
// position off the parent element
|
||||
var rect = el.getClientRects()[0]
|
||||
dropdown.style.top = (rect.bottom + document.body.scrollTop + 10 + (opts.offsetY||0)) + 'px'
|
||||
if (opts.right)
|
||||
dropdown.style.left = (rect.right + document.body.scrollLeft - (opts.width||200) + 5 + (opts.offsetX||0)) + 'px'
|
||||
else
|
||||
dropdown.style.left = (rect.left + document.body.scrollLeft - 20 + (opts.offsetX||0)) + 'px'
|
||||
|
||||
// add to page
|
||||
document.body.appendChild(dropdown)
|
||||
document.body.addEventListener('click', die)
|
||||
|
||||
// handler
|
||||
function onselect (value) {
|
||||
return function (e) {
|
||||
e.preventDefault()
|
||||
cb(value)
|
||||
die()
|
||||
}
|
||||
}
|
||||
function die () {
|
||||
document.body.removeEventListener('click', die)
|
||||
if (dropdown)
|
||||
document.body.removeChild(dropdown)
|
||||
dropdown = null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.triggerFind = function () {
|
||||
var finder = document.body.querySelector('#finder')
|
||||
if (!finder) {
|
||||
document.body.appendChild(finder = com.finder())
|
||||
finder.querySelector('input').focus()
|
||||
} else {
|
||||
finder.find()
|
||||
}
|
||||
}
|
||||
325
ui/lib/ui/modals.js
Normal file
325
ui/lib/ui/modals.js
Normal file
@@ -0,0 +1,325 @@
|
||||
var h = require('hyperscript')
|
||||
var schemas = require('ssb-msg-schemas')
|
||||
var clipboard = require('clipboard')
|
||||
var app = require('../app')
|
||||
var ui = require('./index')
|
||||
var com = require('../com')
|
||||
var social = require('../social-graph')
|
||||
var ref = require('ssb-ref')
|
||||
|
||||
var modal =
|
||||
module.exports.default = function (el) {
|
||||
// create a context so we can release this modal on close
|
||||
var h2 = h.context()
|
||||
var canclose = true
|
||||
|
||||
// markup
|
||||
|
||||
var inner = h2('.modal-inner', el)
|
||||
var modal = h2('.modal', { onclick: onmodalclick }, inner)
|
||||
document.body.appendChild(modal)
|
||||
|
||||
modal.enableClose = function () { canclose = true }
|
||||
modal.disableClose = function () { canclose = false }
|
||||
|
||||
modal.close = function () {
|
||||
if (!canclose)
|
||||
return
|
||||
|
||||
// remove
|
||||
document.body.removeChild(modal)
|
||||
window.removeEventListener('hashchange', modal.close)
|
||||
window.removeEventListener('keyup', onkeyup)
|
||||
h2.cleanup()
|
||||
ui.enableScrolling()
|
||||
modal = null
|
||||
}
|
||||
|
||||
// handlers
|
||||
|
||||
function onmodalclick (e) {
|
||||
if (e.target == modal)
|
||||
modal.close()
|
||||
}
|
||||
function onkeyup (e) {
|
||||
// close on escape
|
||||
if (e.which == 27)
|
||||
modal.close()
|
||||
}
|
||||
window.addEventListener('hashchange', modal.close)
|
||||
window.addEventListener('keyup', onkeyup)
|
||||
ui.disableScrolling()
|
||||
|
||||
return modal
|
||||
}
|
||||
|
||||
var errorModal =
|
||||
module.exports.error = function (title, err, extraIssueInfo) {
|
||||
var message = err.message || err.toString()
|
||||
var stack = err.stack || ''
|
||||
|
||||
var issueDesc = message + '\n\n' + stack + '\n\n' + (extraIssueInfo||'')
|
||||
var issueUrl = 'https://github.com/ssbc/patchwork/issues/new?body='+encodeURIComponent(issueDesc)
|
||||
|
||||
var m = modal(h('.error-form',
|
||||
h('.error-form-title', title),
|
||||
h('.error-form-message', message),
|
||||
h('.error-form-actions',
|
||||
h('a.btn.btn-primary', { onclick: function() { m.close() } }, 'Dismiss'),
|
||||
h('a.btn.btn-info.noicon', { href: issueUrl, target: '_blank' }, 'File an Issue')
|
||||
),
|
||||
(stack) ? h('pre.error-form-stack', h('code', stack)) : ''
|
||||
))
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
module.exports.prompt = function (text, placeholder, submitText, cb) {
|
||||
var input = h('input.form-control', { placeholder: placeholder })
|
||||
function onsubmit (e) {
|
||||
e.preventDefault()
|
||||
m.close()
|
||||
cb(null, input.value)
|
||||
}
|
||||
var m = modal(h('.modal-form',
|
||||
h('p', text),
|
||||
h('form', { onsubmit: onsubmit }, h('p', input), h('button.btn.btn-3d', submitText))
|
||||
))
|
||||
input.focus()
|
||||
return m
|
||||
}
|
||||
|
||||
module.exports.invite = function (e) {
|
||||
e.preventDefault()
|
||||
|
||||
// render
|
||||
|
||||
var form = com.inviteForm({ onsubmit: onsubmit })
|
||||
var m = modal(form)
|
||||
form.querySelector('input').focus()
|
||||
|
||||
// handlers
|
||||
|
||||
function onsubmit (code) {
|
||||
form.disable()
|
||||
m.disableClose()
|
||||
|
||||
// surrounded by quotes?
|
||||
// (the scuttlebot cli ouputs invite codes with quotes, so this could happen)
|
||||
if (code.charAt(0) == '"' && code.charAt(code.length - 1) == '"')
|
||||
code = code.slice(1, -1) // strip em
|
||||
|
||||
if (ref.isInvite(code)) {
|
||||
form.setProcessingText('Contacting server with invite code, this may take a few moments...')
|
||||
app.ssb.invite.accept(code, addMeNext)
|
||||
}
|
||||
else
|
||||
m.enableClose(), form.enable(), form.setErrorText('Invalid invite code')
|
||||
|
||||
function addMeNext (err) {
|
||||
m.enableClose()
|
||||
if (err) {
|
||||
console.error(err)
|
||||
form.setErrorText(userFriendlyInviteError(err.stack || err.message))
|
||||
form.enable()
|
||||
return
|
||||
}
|
||||
|
||||
// trigger sync with the pub
|
||||
app.ssb.gossip.connect(code.split('~')[0])
|
||||
|
||||
// nav to the newsfeed for the livestream
|
||||
m.close()
|
||||
if (window.location.hash != '#/home')
|
||||
window.location.hash = '#/home'
|
||||
else
|
||||
ui.refreshPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.lookup = function (e) {
|
||||
e.preventDefault()
|
||||
|
||||
// render
|
||||
|
||||
var form = com.lookupForm({ onsubmit: onsubmit })
|
||||
var m = modal(form)
|
||||
form.querySelector('input').focus()
|
||||
|
||||
// create user's code
|
||||
|
||||
app.ssb.gossip.peers(function (err, peers) {
|
||||
if (!peers) {
|
||||
m.close()
|
||||
errorModal('Error Fetching Peer Information', err)
|
||||
return
|
||||
}
|
||||
|
||||
var addrs = []
|
||||
peers.forEach(function (peer) {
|
||||
if (social.follows(peer.key, app.user.id))
|
||||
addrs.push(peer.host + ':' + peer.port + ':' + peer.key)
|
||||
})
|
||||
var code = app.user.id
|
||||
if (addrs.length)
|
||||
code += '[via]'+addrs.join(',')
|
||||
|
||||
form.setYourLookupCode(code)
|
||||
})
|
||||
|
||||
// handlers
|
||||
|
||||
function onsubmit (code) {
|
||||
var id, seq, err
|
||||
form.disable()
|
||||
pull(app.ssb.patchwork.useLookupCode(code), pull.drain(
|
||||
function (e) {
|
||||
if (e.type == 'connecting')
|
||||
form.setProcessingText('Connecting...')
|
||||
if (e.type == 'syncing') {
|
||||
id = e.id
|
||||
form.setProcessingText('Connected, syncing user data...')
|
||||
}
|
||||
if (e.type == 'finished')
|
||||
seq = e.seq
|
||||
if (e.type == 'error')
|
||||
err = e
|
||||
},
|
||||
function () {
|
||||
form.enable()
|
||||
if (id && seq) {
|
||||
form.setProcessingText('Profile synced, redirecting...')
|
||||
setTimeout(function () {
|
||||
window.location.hash = '#/profile/'+id
|
||||
}, 1e3)
|
||||
} else {
|
||||
if (err) {
|
||||
form.setErrorText('Error: '+err.message+ ' :(')
|
||||
console.error(err)
|
||||
} else
|
||||
form.setErrorText('Error: User not found :(')
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.getLookup = function (e) {
|
||||
e.preventDefault()
|
||||
|
||||
// render
|
||||
|
||||
var codesEl = h('div')
|
||||
var m = modal(h('.lookup-code-form', codesEl))
|
||||
|
||||
// collect codes
|
||||
|
||||
app.ssb.gossip.peers(function (err, peers) {
|
||||
if (!peers) {
|
||||
m.close()
|
||||
errorModal('Error Getting Lookup Codes', err)
|
||||
return
|
||||
}
|
||||
|
||||
var addrs = []
|
||||
peers.forEach(function (peer) {
|
||||
if (social.follows(peer.key, app.user.id))
|
||||
addrs.push(peer.host + ':' + peer.port + ':' + (peer.key || peer.link))
|
||||
})
|
||||
var code = app.user.id
|
||||
if (addrs.length)
|
||||
code += '[via]'+addrs.join(',')
|
||||
|
||||
codesEl.appendChild(h('.code',
|
||||
h('p',
|
||||
h('strong', 'Your Lookup Code'),
|
||||
' ',
|
||||
h('a.btn.btn-3d.btn-xs.pull-right', { href: '#', onclick: oncopy(code) }, com.icon('copy'), ' Copy to clipboard')
|
||||
),
|
||||
h('p', h('input.form-control', { value: code }))
|
||||
))
|
||||
})
|
||||
|
||||
// handlers
|
||||
|
||||
function oncopy (text) {
|
||||
return function (e) {
|
||||
e.preventDefault()
|
||||
var btn = e.target
|
||||
if (btn.tagName == 'SPAN')
|
||||
btn = e.path[1]
|
||||
clipboard.writeText(text)
|
||||
btn.innerText = 'Copied!'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.setName = function (userId) {
|
||||
userId = userId || app.user.id
|
||||
|
||||
// render
|
||||
|
||||
var oldname = com.userName(userId)
|
||||
var form = com.renameForm(userId, { onsubmit: onsubmit })
|
||||
var m = modal(form)
|
||||
form.querySelector('input').focus()
|
||||
|
||||
// handlers
|
||||
|
||||
function onsubmit (name) {
|
||||
if (!name)
|
||||
return
|
||||
if (name === oldname)
|
||||
return m.close()
|
||||
|
||||
app.ssb.publish(schemas.name(userId, name), function (err) {
|
||||
if (err)
|
||||
console.error(err), errorModal('Error While Publishing', err)
|
||||
else {
|
||||
m.close()
|
||||
ui.refreshPage()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.flag = function (userId) {
|
||||
|
||||
// render
|
||||
|
||||
var form = com.flagForm(userId, { onsubmit: onsubmit })
|
||||
var m = modal(form)
|
||||
|
||||
// handlers
|
||||
|
||||
function onsubmit () {
|
||||
m.close()
|
||||
ui.refreshPage()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.post = function (rootMsg, branchMsg, opts) {
|
||||
|
||||
// render
|
||||
|
||||
var _onpost = opts.onpost
|
||||
var _oncancel = opts.oncancel
|
||||
|
||||
opts = opts || {}
|
||||
opts.onpost = onpost
|
||||
opts.oncancel = oncancel
|
||||
var m = modal(com.postForm(rootMsg, branchMsg, opts))
|
||||
|
||||
// handlers
|
||||
|
||||
function onpost (msg) {
|
||||
m.close()
|
||||
_onpost && _onpost(msg)
|
||||
}
|
||||
|
||||
function oncancel () {
|
||||
m.close()
|
||||
_oncancel && _oncancel()
|
||||
}
|
||||
}
|
||||
167
ui/lib/ui/subwindows.js
Normal file
167
ui/lib/ui/subwindows.js
Normal file
@@ -0,0 +1,167 @@
|
||||
var h = require('hyperscript')
|
||||
var com = require('../com')
|
||||
var u = require('../util')
|
||||
var social = require('../social-graph')
|
||||
|
||||
var subwindows = []
|
||||
var makeSubwindow =
|
||||
module.exports.subwindow = function (el, title, opts) {
|
||||
// create a context so we can release this window on close
|
||||
opts = opts || {}
|
||||
var icon = com.icon(opts.icon || 'th-large')
|
||||
var h2 = h.context()
|
||||
var canclose = true
|
||||
|
||||
// markup
|
||||
|
||||
var collapseToggleIcon = com.icon('chevron-down')
|
||||
var subwindow = h2('.subwindow',
|
||||
h2('.subwindow-toolbar',
|
||||
h2('.title', icon, ' ', h('span.title-text', title)),
|
||||
(opts.help) ? h2('a.help', { href: '#', onclick: onhelp }, com.icon('question-sign')) : '',
|
||||
(opts.url) ? h2('a.goto', { href: '#', onclick: ongoto, title: 'Go to page for '+title }, com.icon('arrow-right')) : '',
|
||||
h2('a', { href: '#', onclick: oncollapsetoggle }, collapseToggleIcon),
|
||||
h2('a.close', { href: '#', onclick: onclose }, com.icon('remove'))
|
||||
),
|
||||
h2('.subwindow-body', el)
|
||||
)
|
||||
document.body.appendChild(subwindow)
|
||||
|
||||
subwindow.enableClose = function () { canclose = true }
|
||||
subwindow.disableClose = function () { canclose = false }
|
||||
|
||||
subwindow.collapse = function () {
|
||||
subwindow.classList.add('collapsed')
|
||||
collapseToggleIcon.classList.remove('glyphicon-chevron-down')
|
||||
collapseToggleIcon.classList.add('glyphicon-'+(opts.icon || 'chevron-up'))
|
||||
reflow()
|
||||
}
|
||||
subwindow.expand = function () {
|
||||
subwindow.classList.remove('collapsed')
|
||||
collapseToggleIcon.classList.remove('glyphicon-'+(opts.icon || 'chevron-up'))
|
||||
collapseToggleIcon.classList.add('glyphicon-chevron-down')
|
||||
reflow()
|
||||
}
|
||||
|
||||
subwindow.close = function (force) {
|
||||
if (!canclose)
|
||||
return
|
||||
|
||||
// check if there are any forms in progress
|
||||
if (!force) {
|
||||
var els = Array.prototype.slice.call(subwindow.querySelectorAll('textarea'))
|
||||
for (var i=0; i < els.length; i++) {
|
||||
if (els[i].value) {
|
||||
if (!confirm('Lose changes to your draft?'))
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove
|
||||
document.body.removeChild(subwindow)
|
||||
h2.cleanup()
|
||||
subwindows.splice(subwindows.indexOf(subwindow), 1)
|
||||
reflow()
|
||||
subwindow = null
|
||||
}
|
||||
|
||||
// handlers
|
||||
|
||||
function onclose (e) {
|
||||
e.preventDefault()
|
||||
subwindow.close()
|
||||
}
|
||||
|
||||
function ongoto (e) {
|
||||
e.preventDefault()
|
||||
window.location = opts.url
|
||||
}
|
||||
|
||||
function oncollapsetoggle (e) {
|
||||
e.preventDefault()
|
||||
if (subwindow.classList.contains('collapsed'))
|
||||
subwindow.expand()
|
||||
else
|
||||
subwindow.collapse()
|
||||
}
|
||||
|
||||
function onhelp (e) {
|
||||
e.preventDefault()
|
||||
makeSubwindow(com.help.helpBody(opts.help), [com.icon('question-sign'), ' ', com.help.helpTitle(opts.help)])
|
||||
}
|
||||
|
||||
// manage
|
||||
|
||||
subwindows.push(subwindow)
|
||||
reflow()
|
||||
|
||||
return subwindow
|
||||
}
|
||||
|
||||
module.exports.pm = function (opts) {
|
||||
|
||||
// render
|
||||
|
||||
opts = opts || {}
|
||||
opts.onpost = onpost
|
||||
var form = com.pmForm(opts)
|
||||
var sw = makeSubwindow(form, 'Secret Message', { icon: 'lock', help: 'secret-messages' })
|
||||
try { form.querySelector('input').focus() } catch (e) {}
|
||||
|
||||
// handlers
|
||||
|
||||
function onpost () {
|
||||
sw.close(true)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.help = function (topic) {
|
||||
return makeSubwindow(com.help.helpBody(topic), com.help.helpTitle(topic), { icon: 'question-sign' })
|
||||
}
|
||||
|
||||
module.exports.message = function (key) {
|
||||
app.ssb.get(key, function (err, msg) {
|
||||
if (err || !msg)
|
||||
require('../ui/subwindows').subwindow(h('p', 'Message Not Found'), 'Message')
|
||||
else {
|
||||
msg = { key: key, value: msg }
|
||||
var title = u.shortString(msg.value.content.text || 'Message Thread', 30)
|
||||
var sw = makeSubwindow(
|
||||
com.message(msg, { fullview: true, markread: true, live: true }),
|
||||
title,
|
||||
{ icon: 'envelope', url: '#/msg/'+msg.key }
|
||||
)
|
||||
sw.querySelector('.subwindow-body').style.background = '#eee'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.inbox = function () {
|
||||
var feedEl = com.messageFeed({
|
||||
feed: app.ssb.patchwork.createInboxStream,
|
||||
live: app.ssb.patchwork.createInboxStream({ gt: [Date.now(), null], live: true }),
|
||||
onempty: onempty
|
||||
})
|
||||
|
||||
function onempty (feedEl) {
|
||||
feedEl.appendChild(h('p.text-center', { style: 'margin: 25px 0; padding: 10px; color: gray' }, 'Your inbox is empty!'))
|
||||
}
|
||||
|
||||
var sw = makeSubwindow(feedEl, 'Inbox', { icon: 'inbox' })
|
||||
sw.querySelector('.subwindow-body').style.background = '#eee'
|
||||
}
|
||||
|
||||
// reposition subwindows
|
||||
var SPACING = 10
|
||||
function reflow () {
|
||||
var right = SPACING
|
||||
subwindows.forEach(function (sw) {
|
||||
sw.style.right = right + 'px'
|
||||
if (sw.classList.contains('collapsed'))
|
||||
right += 50 + SPACING
|
||||
else
|
||||
right += 500 + SPACING
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user