@@ -0,0 +1,451 @@
|
||||
/** 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, '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 scripts = tab && tab.scriptsBlocked ? ' js:' + 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' +
|
||||
'JS is off. HTTP via ctx.httpFetch (allow/deny lists).\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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user