@@ -0,0 +1,361 @@
|
||||
/** Tabs, history, bookmarks, about: pages. */
|
||||
|
||||
function bareSummonHome(ctx) {
|
||||
var env = ctx && ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||||
if (env.HOME) return String(env.HOME).replace(/\/+$/, '')
|
||||
return '/home/guest'
|
||||
}
|
||||
|
||||
function bareSummonPaths(ctx) {
|
||||
var h = bareSummonHome(ctx)
|
||||
return {
|
||||
dir: h + '/.summon',
|
||||
config: h + '/.summon/config.json',
|
||||
cookies: h + '/.summon/cookies.json',
|
||||
bookmarks: h + '/.summon/bookmarks.json',
|
||||
history: h + '/.summon/history.jsonl'
|
||||
}
|
||||
}
|
||||
|
||||
function bareSummonCreateTab(url) {
|
||||
return {
|
||||
url: url || 'about:summon',
|
||||
title: '',
|
||||
loading: false,
|
||||
status: 0,
|
||||
error: '',
|
||||
doc: null,
|
||||
layout: null,
|
||||
source: '',
|
||||
reader: false,
|
||||
find: '',
|
||||
history: { back: [], forward: [] },
|
||||
scriptsBlocked: 0,
|
||||
stylesBlocked: 0
|
||||
}
|
||||
}
|
||||
|
||||
function bareSummonCreateSession(ctx, opts) {
|
||||
opts = opts || {}
|
||||
var jar = bareSummonCreateCookieJar()
|
||||
return {
|
||||
ctx: ctx,
|
||||
jar: jar,
|
||||
tabs: [bareSummonCreateTab(opts.url || 'about:summon')],
|
||||
current: 0,
|
||||
bookmarks: Array.isArray(opts.bookmarks) ? opts.bookmarks.slice() : [],
|
||||
config: opts.config || { javascript: false, images: 'alt', numbers: true },
|
||||
cols: opts.cols || 80
|
||||
}
|
||||
}
|
||||
|
||||
function bareSummonCurrent(session) {
|
||||
return session.tabs[session.current] || session.tabs[0]
|
||||
}
|
||||
|
||||
function bareSummonAboutBody(kind, session) {
|
||||
if (kind === 'blank') return '<html><body></body></html>'
|
||||
if (kind === 'bookmarks') {
|
||||
var items = (session.bookmarks || [])
|
||||
.map(function (b) {
|
||||
return (
|
||||
'<li><a href="' +
|
||||
String(b.url || '') +
|
||||
'">' +
|
||||
String(b.title || b.url || '') +
|
||||
'</a></li>'
|
||||
)
|
||||
})
|
||||
.join('')
|
||||
return (
|
||||
'<html><body><h1>Bookmarks</h1><ul>' +
|
||||
(items || '<li>(none)</li>') +
|
||||
'</ul></body></html>'
|
||||
)
|
||||
}
|
||||
if (kind === 'history') {
|
||||
var t = bareSummonCurrent(session)
|
||||
var h = (t.history.back || [])
|
||||
.slice(-20)
|
||||
.map(function (u) {
|
||||
return '<li><a href="' + u + '">' + u + '</a></li>'
|
||||
})
|
||||
.join('')
|
||||
return (
|
||||
'<html><body><h1>History</h1><ul>' +
|
||||
(h || '<li>(empty)</li>') +
|
||||
'</ul></body></html>'
|
||||
)
|
||||
}
|
||||
if (kind === 'cookies') {
|
||||
var rows = session.jar
|
||||
.toJSON()
|
||||
.map(function (c) {
|
||||
return '<li>' + c.name + ' @ ' + c.domain + c.path + '</li>'
|
||||
})
|
||||
.join('')
|
||||
return (
|
||||
'<html><body><h1>Cookies</h1><ul>' +
|
||||
(rows || '<li>(none)</li>') +
|
||||
'</ul></body></html>'
|
||||
)
|
||||
}
|
||||
if (kind === 'net') {
|
||||
return '<html><body><h1>Net</h1><p>HTTP via ctx.httpFetch. Allow/deny: BARE_OS_HTTP_ALLOWLIST / DENYLIST.</p></body></html>'
|
||||
}
|
||||
return (
|
||||
'<html><body><h1>summon</h1><p>Text browser for Bare OS. JS is off. Default engine is first-party HTML/CSS.</p>' +
|
||||
'<p>Type a URL (g) or open a bookmark. Try <a href="about:bookmarks">about:bookmarks</a>.</p></body></html>'
|
||||
)
|
||||
}
|
||||
|
||||
function bareSummonApplyDocument(session, tab, html, url) {
|
||||
var doc = bareSummonParseHtml(html, {})
|
||||
var inline = []
|
||||
function collectStyle(n) {
|
||||
if (n && n.type === 'element' && n.name === 'style') {
|
||||
inline.push(bareSummonParseStylesheet(bareSummonTextContent(n)))
|
||||
}
|
||||
var ch = (n && n.children) || []
|
||||
for (var i = 0; i < ch.length; i++) collectStyle(ch[i])
|
||||
}
|
||||
collectStyle(doc.head)
|
||||
tab.sheets = inline
|
||||
var layout = bareSummonLayout(doc, {
|
||||
cols: session.cols,
|
||||
sheets: inline,
|
||||
numbers: session.config.numbers !== false
|
||||
})
|
||||
tab.doc = doc
|
||||
tab.layout = layout
|
||||
tab.source = html
|
||||
tab.title = layout.title || doc.title || url
|
||||
tab.scriptsBlocked = doc.scriptsBlocked || 0
|
||||
tab.url = url
|
||||
}
|
||||
|
||||
function bareSummonRelayout(session) {
|
||||
var tab = bareSummonCurrent(session)
|
||||
if (!tab || !tab.doc) return tab
|
||||
tab.layout = bareSummonLayout(tab.doc, {
|
||||
cols: session.cols,
|
||||
sheets: tab.sheets || [],
|
||||
numbers: session.config.numbers !== false
|
||||
})
|
||||
return tab
|
||||
}
|
||||
|
||||
async function bareSummonNavigate(session, urlStr, opts) {
|
||||
opts = opts || {}
|
||||
var tab = bareSummonCurrent(session)
|
||||
var resolved = bareSummonResolveUrl(urlStr, tab.url)
|
||||
if (!resolved || !bareSummonUrlOk(resolved)) {
|
||||
tab.error = 'bad URL'
|
||||
tab.loading = false
|
||||
return tab
|
||||
}
|
||||
if (!opts.replace && tab.url && tab.url !== resolved.href) {
|
||||
tab.history.back.push(tab.url)
|
||||
tab.history.forward = []
|
||||
}
|
||||
tab.loading = true
|
||||
tab.error = ''
|
||||
tab.reader = false
|
||||
if (resolved.protocol === 'about:') {
|
||||
var kind = resolved.pathname || 'summon'
|
||||
if (kind.charAt(0) === ':') kind = kind.slice(1)
|
||||
bareSummonApplyDocument(
|
||||
session,
|
||||
tab,
|
||||
bareSummonAboutBody(kind, session),
|
||||
resolved.href
|
||||
)
|
||||
tab.status = 200
|
||||
tab.loading = false
|
||||
return tab
|
||||
}
|
||||
if (resolved.protocol === 'file:') {
|
||||
var ctx = session.ctx
|
||||
var path = resolved.pathname || '/'
|
||||
try {
|
||||
var buf =
|
||||
ctx.vfs && ctx.vfs.readFile ? await ctx.vfs.readFile(path) : null
|
||||
var text = ''
|
||||
if (buf) {
|
||||
text =
|
||||
ctx.b4a && typeof ctx.b4a.toString === 'function'
|
||||
? ctx.b4a.toString(buf)
|
||||
: String(buf)
|
||||
}
|
||||
bareSummonApplyDocument(
|
||||
session,
|
||||
tab,
|
||||
text || '<html><body>(empty file)</body></html>',
|
||||
resolved.href
|
||||
)
|
||||
tab.status = buf ? 200 : 404
|
||||
} catch (e) {
|
||||
tab.error = (e && e.message) || String(e)
|
||||
tab.status = 0
|
||||
}
|
||||
tab.loading = false
|
||||
return tab
|
||||
}
|
||||
var fetched = await bareSummonFetch(session.ctx, resolved.href, {
|
||||
jar: session.jar,
|
||||
method: opts.method || 'GET',
|
||||
body: opts.body,
|
||||
headers: opts.headers
|
||||
})
|
||||
if (!fetched.ok && !fetched.body) {
|
||||
tab.error = fetched.error || 'fetch failed'
|
||||
tab.status = fetched.status || 0
|
||||
tab.loading = false
|
||||
return tab
|
||||
}
|
||||
tab.status = fetched.status
|
||||
var body = fetched.body || ''
|
||||
var ct = (fetched.headers && fetched.headers['content-type']) || ''
|
||||
if (!/html/i.test(ct) && body && body.charAt(0) !== '<') {
|
||||
body =
|
||||
'<html><body><pre>' +
|
||||
body.replace(/&/g, '&').replace(/</g, '<') +
|
||||
'</pre></body></html>'
|
||||
}
|
||||
bareSummonApplyDocument(session, tab, body, fetched.url || resolved.href)
|
||||
tab.loading = false
|
||||
return tab
|
||||
}
|
||||
|
||||
function bareSummonBack(session) {
|
||||
var tab = bareSummonCurrent(session)
|
||||
if (!tab.history.back.length) return Promise.resolve(tab)
|
||||
tab.history.forward.unshift(tab.url)
|
||||
var prev = tab.history.back.pop()
|
||||
return bareSummonNavigate(session, prev, { replace: true })
|
||||
}
|
||||
|
||||
function bareSummonForward(session) {
|
||||
var tab = bareSummonCurrent(session)
|
||||
if (!tab.history.forward.length) return Promise.resolve(tab)
|
||||
var next = tab.history.forward.shift()
|
||||
return bareSummonNavigate(session, next, { replace: true })
|
||||
}
|
||||
|
||||
function bareSummonAddBookmark(session, url, title) {
|
||||
session.bookmarks.push({ url: url, title: title || url })
|
||||
}
|
||||
|
||||
function bareSummonReload(session) {
|
||||
var tab = bareSummonCurrent(session)
|
||||
return bareSummonNavigate(session, tab.url, { replace: true })
|
||||
}
|
||||
|
||||
function bareSummonFollowLink(session, id) {
|
||||
var tab = bareSummonCurrent(session)
|
||||
var links = (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) return Promise.resolve(tab)
|
||||
return bareSummonNavigate(session, hit.href)
|
||||
}
|
||||
|
||||
function bareSummonNewTab(session, url) {
|
||||
session.tabs.push(bareSummonCreateTab(url || 'about:summon'))
|
||||
session.current = session.tabs.length - 1
|
||||
return bareSummonCurrent(session)
|
||||
}
|
||||
|
||||
function bareSummonCloseTab(session) {
|
||||
if (session.tabs.length <= 1) {
|
||||
session.tabs[0] = bareSummonCreateTab('about:summon')
|
||||
session.current = 0
|
||||
return session.tabs[0]
|
||||
}
|
||||
session.tabs.splice(session.current, 1)
|
||||
if (session.current >= session.tabs.length)
|
||||
session.current = session.tabs.length - 1
|
||||
return bareSummonCurrent(session)
|
||||
}
|
||||
|
||||
function bareSummonSelectTab(session, index) {
|
||||
if (index < 0 || index >= session.tabs.length)
|
||||
return bareSummonCurrent(session)
|
||||
session.current = index
|
||||
return bareSummonCurrent(session)
|
||||
}
|
||||
|
||||
function bareSummonCycleTab(session, dir) {
|
||||
var n = session.tabs.length
|
||||
if (!n) return bareSummonCurrent(session)
|
||||
session.current = (session.current + dir + n) % n
|
||||
return bareSummonCurrent(session)
|
||||
}
|
||||
|
||||
function bareSummonBufToString(ctx, buf) {
|
||||
if (buf == null) return ''
|
||||
if (typeof buf === 'string') return buf
|
||||
if (ctx && ctx.b4a && typeof ctx.b4a.toString === 'function') {
|
||||
return ctx.b4a.toString(buf)
|
||||
}
|
||||
return String(buf)
|
||||
}
|
||||
|
||||
async function bareSummonReadJson(ctx, path) {
|
||||
if (!ctx || !ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
|
||||
try {
|
||||
var buf = ctx.vfs.readFile(path)
|
||||
if (buf && typeof buf.then === 'function') buf = await buf
|
||||
var t = String(bareSummonBufToString(ctx, buf) || '').trim()
|
||||
if (!t) return null
|
||||
return JSON.parse(t)
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function bareSummonWriteJson(ctx, path, value) {
|
||||
if (!ctx || !ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
|
||||
try {
|
||||
var dir = path.slice(0, path.lastIndexOf('/'))
|
||||
if (dir && ctx.vfs.mkdir) {
|
||||
try {
|
||||
var mk = ctx.vfs.mkdir(dir, { recursive: true })
|
||||
if (mk && typeof mk.then === 'function') await mk
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
var body = JSON.stringify(value, null, 2)
|
||||
var w = ctx.vfs.writeFile(path, body)
|
||||
if (w && typeof w.then === 'function') await w
|
||||
return true
|
||||
} catch (e2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function bareSummonLoadPersisted(session) {
|
||||
var ctx = session.ctx
|
||||
var paths = bareSummonPaths(ctx)
|
||||
var cfg = await bareSummonReadJson(ctx, paths.config)
|
||||
if (cfg && typeof cfg === 'object') {
|
||||
session.config = Object.assign({}, session.config, cfg)
|
||||
}
|
||||
var marks = await bareSummonReadJson(ctx, paths.bookmarks)
|
||||
if (Array.isArray(marks)) session.bookmarks = marks
|
||||
var cookies = await bareSummonReadJson(ctx, paths.cookies)
|
||||
if (Array.isArray(cookies)) session.jar.load(cookies)
|
||||
return session
|
||||
}
|
||||
|
||||
async function bareSummonSavePersisted(session) {
|
||||
var ctx = session.ctx
|
||||
var paths = bareSummonPaths(ctx)
|
||||
await bareSummonWriteJson(ctx, paths.bookmarks, session.bookmarks || [])
|
||||
await bareSummonWriteJson(ctx, paths.cookies, session.jar.toJSON())
|
||||
await bareSummonWriteJson(ctx, paths.config, session.config || {})
|
||||
}
|
||||
Reference in New Issue
Block a user