465 lines
14 KiB
JavaScript
465 lines
14 KiB
JavaScript
/** TEA summon TUI. Cell buffer; engine lives in summon-*.js. */
|
|
|
|
function bareSummonTuiRunOpts() {
|
|
return { buffer: 'cell' }
|
|
}
|
|
|
|
function bareSummonCreateTuiApp(ctx, session) {
|
|
var tui = ctx.tui
|
|
var size0 = tui && typeof tui.size === 'function' ? tui.size() : {}
|
|
return {
|
|
session: session,
|
|
width: size0.width || 80,
|
|
height: size0.height || 24,
|
|
mode: 'browse',
|
|
help: false,
|
|
quitConfirm: false,
|
|
status: '',
|
|
num: '',
|
|
linkFocus: 0,
|
|
input: tui.textinput.create({ prompt: '', focused: true, charLimit: 800 }),
|
|
viewport: tui.viewport.create({
|
|
width: size0.width || 80,
|
|
height: Math.max(6, (size0.height || 24) - 5)
|
|
}),
|
|
init: function () {
|
|
var self = this
|
|
self.session.cols = Math.max(20, (self.width || 80) - 2)
|
|
var tab = bareSummonCurrent(self.session)
|
|
if (tab && tab.url) self._go(tab.url, { replace: true })
|
|
else self._sync()
|
|
return null
|
|
},
|
|
_tab: function () {
|
|
return bareSummonCurrent(this.session)
|
|
},
|
|
_sync: function () {
|
|
var tab = this._tab()
|
|
var cols = Math.max(20, (this.width || 80) - 2)
|
|
this.session.cols = cols
|
|
if (tab && tab.doc && (!tab.layout || tab.layout.cols !== cols)) {
|
|
bareSummonRelayout(this.session)
|
|
tab = this._tab()
|
|
}
|
|
this.viewport.width = cols
|
|
this.viewport.height = Math.max(4, (this.height || 24) - 5)
|
|
var lines = []
|
|
if (this.mode === 'source') {
|
|
lines = String((tab && tab.source) || '').split('\n')
|
|
} else if (tab && tab.reader && tab.doc) {
|
|
lines = String(bareSummonReaderText(tab.doc) || '').split('\n')
|
|
} else if (tab && tab.layout && tab.layout.plain) {
|
|
lines = tab.layout.plain.slice()
|
|
}
|
|
if (tab && tab.error) lines.unshift('error: ' + tab.error)
|
|
if (tab && tab.loading) lines.unshift('loading…')
|
|
this.viewport.setContent(lines.join('\n'))
|
|
var nlinks =
|
|
tab && tab.layout && tab.layout.links ? tab.layout.links.length : 0
|
|
if (this.linkFocus >= nlinks) this.linkFocus = Math.max(0, nlinks - 1)
|
|
},
|
|
_go: function (url, opts) {
|
|
var self = this
|
|
var tab = this._tab()
|
|
if (tab) tab.loading = true
|
|
this.status = 'loading…'
|
|
this.mode = 'browse'
|
|
this.num = ''
|
|
bareSummonNavigate(this.session, url, opts || {})
|
|
.then(function () {
|
|
self.status = ''
|
|
self._sync()
|
|
if (tui && typeof tui.send === 'function') {
|
|
tui.send({ type: 'summon.loaded' })
|
|
}
|
|
})
|
|
.catch(function (err) {
|
|
var cur = self._tab()
|
|
if (cur) {
|
|
cur.error = (err && err.message) || String(err)
|
|
cur.loading = false
|
|
}
|
|
self.status = 'error'
|
|
self._sync()
|
|
if (tui && typeof tui.send === 'function') {
|
|
tui.send({ type: 'summon.loaded' })
|
|
}
|
|
})
|
|
},
|
|
_follow: function (id) {
|
|
var tab = this._tab()
|
|
var links = (tab && tab.layout && tab.layout.links) || []
|
|
var hit = null
|
|
var i
|
|
for (i = 0; i < links.length; i++) {
|
|
if (links[i].id === id) hit = links[i]
|
|
}
|
|
if (!hit) {
|
|
this.status = 'no link ' + (id + 1)
|
|
return
|
|
}
|
|
this._go(hit.href)
|
|
},
|
|
_promptSubmit: function () {
|
|
var line = String(this.input.value || '').trim()
|
|
this.input.reset()
|
|
var mode = this.mode
|
|
this.mode = 'browse'
|
|
if (!line) return
|
|
if (mode === 'goto') this._go(line)
|
|
else if (mode === 'find') {
|
|
var tab = this._tab()
|
|
if (tab) tab.find = line
|
|
this._find(1)
|
|
}
|
|
},
|
|
_find: function (dir) {
|
|
var tab = this._tab()
|
|
var q = tab && tab.find ? String(tab.find) : ''
|
|
if (!q) {
|
|
this.status = 'no find'
|
|
return
|
|
}
|
|
var lines = this.viewport.lines || []
|
|
var start = this.viewport.yOffset + (dir > 0 ? 1 : -1)
|
|
var i
|
|
if (dir > 0) {
|
|
for (i = start; i < lines.length; i++) {
|
|
if (
|
|
String(lines[i] || '')
|
|
.toLowerCase()
|
|
.indexOf(q.toLowerCase()) >= 0
|
|
) {
|
|
this.viewport.setYOffset(i)
|
|
this.status = 'find ' + q
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
for (i = start; i >= 0; i--) {
|
|
if (
|
|
String(lines[i] || '')
|
|
.toLowerCase()
|
|
.indexOf(q.toLowerCase()) >= 0
|
|
) {
|
|
this.viewport.setYOffset(i)
|
|
this.status = 'find ' + q
|
|
return
|
|
}
|
|
}
|
|
}
|
|
this.status = 'not found: ' + q
|
|
},
|
|
update: function (msg) {
|
|
if (msg && msg.type === 'resize') {
|
|
this.width = msg.width || this.width
|
|
this.height = msg.height || this.height
|
|
this._sync()
|
|
return [this, null]
|
|
}
|
|
if (msg && msg.type === 'summon.loaded') {
|
|
this._sync()
|
|
return [this, null]
|
|
}
|
|
if (this.help) {
|
|
if (msg && msg.type === 'key') this.help = false
|
|
return [this, null]
|
|
}
|
|
if (this.quitConfirm) {
|
|
if (tui.key.matches(msg, 'y', 'Y')) return [this, tui.quit]
|
|
if (msg && msg.type === 'key') this.quitConfirm = false
|
|
return [this, null]
|
|
}
|
|
if (this.mode === 'goto' || this.mode === 'find') {
|
|
if (tui.key.matches(msg, 'escape', 'ctrl+c')) {
|
|
this.mode = 'browse'
|
|
this.input.reset()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'enter')) {
|
|
this._promptSubmit()
|
|
return [this, null]
|
|
}
|
|
var pair = this.input.update(msg)
|
|
this.input = pair[0]
|
|
return [this, pair[1]]
|
|
}
|
|
if (tui.key.matches(msg, 'ctrl+c', 'q')) {
|
|
this.quitConfirm = true
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'f10')) return [this, tui.quit]
|
|
if (tui.key.matches(msg, '?')) {
|
|
this.help = true
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'g')) {
|
|
this.mode = 'goto'
|
|
this.input.reset()
|
|
this.input.setValue && this.input.setValue('')
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, '/')) {
|
|
this.mode = 'find'
|
|
this.input.reset()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'n')) {
|
|
this._find(1)
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'N')) {
|
|
this._find(-1)
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'r')) {
|
|
this._go(this._tab().url, { replace: true })
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'R')) {
|
|
var tabR = this._tab()
|
|
tabR.reader = !tabR.reader
|
|
this._sync()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'J')) {
|
|
this.session.config.javascript =
|
|
this.session.config.javascript === false
|
|
this.status =
|
|
this.session.config.javascript === false ? 'js off' : 'js on'
|
|
if (this._tab().url) this._go(this._tab().url, { replace: true })
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 's')) {
|
|
this.mode = this.mode === 'source' ? 'browse' : 'source'
|
|
this._sync()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'H')) {
|
|
this._go('about:summon')
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'o')) {
|
|
this._go('about:bookmarks')
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'a')) {
|
|
var tabA = this._tab()
|
|
bareSummonAddBookmark(this.session, tabA.url, tabA.title)
|
|
this.status = 'bookmarked'
|
|
if (typeof bareSummonSavePersisted === 'function') {
|
|
bareSummonSavePersisted(this.session)
|
|
}
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'left', 'h')) {
|
|
var selfB = this
|
|
bareSummonBack(this.session).then(function () {
|
|
selfB._sync()
|
|
if (tui && typeof tui.send === 'function')
|
|
tui.send({ type: 'summon.loaded' })
|
|
})
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'u')) {
|
|
var selfF = this
|
|
bareSummonForward(this.session).then(function () {
|
|
selfF._sync()
|
|
if (tui && typeof tui.send === 'function')
|
|
tui.send({ type: 'summon.loaded' })
|
|
})
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 't')) {
|
|
bareSummonNewTab(this.session, 'about:summon')
|
|
this._go('about:summon', { replace: true })
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'w')) {
|
|
bareSummonCloseTab(this.session)
|
|
this._sync()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, ']')) {
|
|
bareSummonCycleTab(this.session, 1)
|
|
this._sync()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, '[')) {
|
|
bareSummonCycleTab(this.session, -1)
|
|
this._sync()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'tab')) {
|
|
var tabT = this._tab()
|
|
var n =
|
|
tabT && tabT.layout && tabT.layout.links
|
|
? tabT.layout.links.length
|
|
: 0
|
|
if (n) this.linkFocus = (this.linkFocus + 1) % n
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'enter')) {
|
|
if (this.num) {
|
|
var id = parseInt(this.num, 10) - 1
|
|
this.num = ''
|
|
if (id >= 0) this._follow(id)
|
|
return [this, null]
|
|
}
|
|
this._follow(this.linkFocus)
|
|
return [this, null]
|
|
}
|
|
if (
|
|
msg &&
|
|
msg.type === 'key' &&
|
|
msg.name &&
|
|
msg.name.length === 1 &&
|
|
msg.name >= '0' &&
|
|
msg.name <= '9'
|
|
) {
|
|
this.num += msg.name
|
|
this.status = '#' + this.num
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'j', 'down')) {
|
|
this.viewport.scrollDown(1)
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'k', 'up')) {
|
|
this.viewport.scrollUp(1)
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, ' ', 'pagedown')) {
|
|
this.viewport.scrollDown(this.viewport.height || 10)
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'pageup')) {
|
|
this.viewport.scrollUp(this.viewport.height || 10)
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'home')) {
|
|
this.viewport.gotoTop()
|
|
return [this, null]
|
|
}
|
|
if (tui.key.matches(msg, 'end')) {
|
|
this.viewport.gotoBottom()
|
|
return [this, null]
|
|
}
|
|
return [this, null]
|
|
},
|
|
view: function () {
|
|
var cols = Math.max(40, this.width || 80)
|
|
var rows = Math.max(12, this.height || 24)
|
|
var st = tui.style
|
|
var tab = this._tab()
|
|
var ntab = this.session.tabs.length
|
|
var idx = this.session.current + 1
|
|
var url = (tab && tab.url) || ''
|
|
var title = (tab && tab.title) || ''
|
|
var jsOn = this.session.config && this.session.config.javascript !== false
|
|
var scripts =
|
|
' js:' +
|
|
(jsOn ? 'on' : 'off') +
|
|
(tab && tab.js && tab.js.ran ? '+' + tab.js.ran : '') +
|
|
(tab && tab.scriptsBlocked ? '/' + tab.scriptsBlocked : '')
|
|
var titleRaw =
|
|
' summon [' +
|
|
idx +
|
|
'/' +
|
|
ntab +
|
|
'] ' +
|
|
(title || url) +
|
|
(tab && tab.reader ? ' reader' : '') +
|
|
(this.mode === 'source' ? ' source' : '') +
|
|
scripts +
|
|
' ' +
|
|
cols +
|
|
'x' +
|
|
rows +
|
|
' '
|
|
var bar = st
|
|
? st()
|
|
.foreground('brightwhite')
|
|
.background('blue')
|
|
.width(cols)
|
|
.render(titleRaw)
|
|
: titleRaw
|
|
var locRaw =
|
|
' ' + url + ' ' + (tab && tab.status ? tab.status : '') + ' '
|
|
var loc = st ? st().dim().width(cols).render(locRaw) : locRaw
|
|
var body = this.viewport.view()
|
|
var prompt = ''
|
|
if (this.mode === 'goto') {
|
|
prompt =
|
|
'Go: ' +
|
|
(this.input.view ? this.input.view() : this.input.value || '')
|
|
} else if (this.mode === 'find') {
|
|
prompt =
|
|
'Find: ' +
|
|
(this.input.view ? this.input.view() : this.input.value || '')
|
|
} else {
|
|
prompt =
|
|
(this.status || '') +
|
|
(this.num ? ' #' + this.num : '') +
|
|
(this.linkFocus >= 0 &&
|
|
tab &&
|
|
tab.layout &&
|
|
tab.layout.links[this.linkFocus]
|
|
? ' → ' + tab.layout.links[this.linkFocus].href
|
|
: '')
|
|
}
|
|
var footRaw =
|
|
'g go Enter follow 12+Enter link h back j/k /find a mark o marks q quit ?'
|
|
var foot = st ? st().dim().width(cols).render(footRaw) : footRaw
|
|
var promptLine = st
|
|
? st()
|
|
.width(cols)
|
|
.render(prompt || ' ')
|
|
: prompt || ' '
|
|
var lines = [bar, loc]
|
|
.concat(String(body).split('\n'))
|
|
.concat([promptLine, foot])
|
|
while (lines.length < rows) lines.push('')
|
|
var out = []
|
|
var i
|
|
for (i = 0; i < rows; i++) {
|
|
out.push(
|
|
st
|
|
? st.truncate(lines[i] || '', cols)
|
|
: String(lines[i] || '').slice(0, cols)
|
|
)
|
|
}
|
|
return out.join('\n')
|
|
},
|
|
overlay: function (size) {
|
|
if (!this.help && !this.quitConfirm) return null
|
|
var cols = (size && size.width) || this.width || 80
|
|
var rows = (size && size.height) || this.height || 24
|
|
var st = tui.style
|
|
var body = this.quitConfirm
|
|
? 'Leave summon?\n\nY yes any other key cancel'
|
|
: 'summon — text web browser\n\n' +
|
|
'g URL go Enter follow focused / numbered link\n' +
|
|
'h/left back u forward r reload R reader s source\n' +
|
|
'j/k scroll / find n/N next/prev a bookmark o marks\n' +
|
|
't new tab w close [ ] tabs H home digits+Enter link #\n' +
|
|
'J toggle page JS (Bare VM / sandbox) HTTP via ctx.httpFetch\n\n' +
|
|
'Press any key to close.'
|
|
var boxed = st
|
|
? st()
|
|
.border(st.borders.rounded)
|
|
.padding(1, 2)
|
|
.background('black')
|
|
.render(body)
|
|
: body
|
|
var h = st ? st.height(boxed) : boxed.split('\n').length
|
|
var w = st ? st.width(boxed) : 56
|
|
return {
|
|
row: Math.max(0, Math.floor((rows - h) / 2)),
|
|
col: Math.max(0, Math.floor((cols - w) / 2)),
|
|
text: boxed
|
|
}
|
|
}
|
|
}
|
|
}
|