aboutsummaryrefslogtreecommitdiff
path: root/xP
diff options
context:
space:
mode:
Diffstat (limited to 'xP')
-rw-r--r--xP/public/xP.js252
1 files changed, 139 insertions, 113 deletions
diff --git a/xP/public/xP.js b/xP/public/xP.js
index 4d2c439..abef57a 100644
--- a/xP/public/xP.js
+++ b/xP/public/xP.js
@@ -226,28 +226,46 @@ for (let i = 0; i < 24; i++) {
palette[232 + i] = `#${g}${g}${g}`
}
-function applyColor(fg, bg, inverse) {
- if (inverse)
- [fg, bg] = [bg >= 0 ? bg : 15, fg >= 0 ? fg : 0]
-
- let style = {}
- if (fg >= 0)
- style.color = palette[fg]
- if (bg >= 0)
- style.backgroundColor = palette[bg]
- if (style)
- return style
-}
-
// ---- UI ---------------------------------------------------------------------
+let Toolbar = {
+ toggleAutoscroll: () => {
+ bufferAutoscroll = !bufferAutoscroll
+ },
+
+ toggleLog: () => {
+ if (bufferLog) {
+ bufferLog = undefined
+ return
+ }
+
+ rpc.send({
+ command: 'BufferLog',
+ bufferName: bufferCurrent,
+ }).then(resp => {
+ bufferLog = rpc.base64decode(resp.log)
+ m.redraw()
+ })
+ },
+
+ view: vnode => {
+ return m('.toolbar', {}, [
+ m('button', {onclick: Toolbar.toggleAutoscroll},
+ bufferAutoscroll ? 'Pause autoscroll' : 'Unpause autoscroll'),
+ m('button', {onclick: Toolbar.toggleLog},
+ bufferLog === undefined ? 'Show log' : 'Hide log'),
+ ])
+ },
+}
+
let BufferList = {
+ activate: name => {
+ rpc.send({command: 'BufferActivate', bufferName: name})
+ },
+
view: vnode => {
let items = Array.from(buffers, ([name, b]) => {
- let attrs = {
- onclick: event =>
- rpc.send({command: 'BufferActivate', bufferName: name}),
- }
+ let attrs = {onclick: event => BufferList.activate(name)}
if (name == bufferCurrent)
attrs.class = 'active'
return m('.item', attrs, name)
@@ -256,35 +274,49 @@ let BufferList = {
},
}
-function linkify(text, attrs, a) {
- let re = new RegExp([
- /https?:\/\//,
- /([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
- /[^\[\](){}<>"'\s,.:]/,
- ].map(r => r.source).join(''), 'g')
-
- let end = 0, match
- while ((match = re.exec(text)) !== null) {
- if (end < match.index)
- a.push(m('span', attrs, text.substring(end, match.index)))
- a.push(m('a', {href: match[0], ...attrs}, match[0]))
- end = re.lastIndex
- }
- if (end < text.length)
- a.push(m('span', attrs, text.substring(end)))
-}
-
let Content = {
+ applyColor: (fg, bg, inverse) => {
+ if (inverse)
+ [fg, bg] = [bg >= 0 ? bg : 15, fg >= 0 ? fg : 0]
+
+ let style = {}
+ if (fg >= 0)
+ style.color = palette[fg]
+ if (bg >= 0)
+ style.backgroundColor = palette[bg]
+ if (style)
+ return style
+ },
+
+ linkify: (text, attrs) => {
+ let re = new RegExp([
+ /https?:\/\//,
+ /([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
+ /[^\[\](){}<>"'\s,.:]/,
+ ].map(r => r.source).join(''), 'g')
+
+ let a = [], end = 0, match
+ while ((match = re.exec(text)) !== null) {
+ if (end < match.index)
+ a.push(m('span', attrs, text.substring(end, match.index)))
+ a.push(m('a', {href: match[0], ...attrs}, match[0]))
+ end = re.lastIndex
+ }
+ if (end < text.length)
+ a.push(m('span', attrs, text.substring(end)))
+ return a
+ },
+
view: vnode => {
let line = vnode.children[0]
- let content = []
+ let mark = undefined
switch (line.rendition) {
- case 'Indent': content.push(m('span.mark', {}, '')); break
- case 'Status': content.push(m('span.mark', {}, '–')); break
- case 'Error': content.push(m('span.mark.error', {}, '⚠')); break
- case 'Join': content.push(m('span.mark.join', {}, '→')); break
- case 'Part': content.push(m('span.mark.part', {}, '←')); break
- case 'Action': content.push(m('span.mark.action', {}, '✶')); break
+ case 'Indent': mark = m('span.mark', {}, ''); break
+ case 'Status': mark = m('span.mark', {}, '–'); break
+ case 'Error': mark = m('span.mark.error', {}, '⚠'); break
+ case 'Join': mark = m('span.mark.join', {}, '→'); break
+ case 'Part': mark = m('span.mark.part', {}, '←'); break
+ case 'Action': mark = m('span.mark.action', {}, '✶'); break
}
let classes = new Set()
@@ -295,14 +327,13 @@ let Content = {
classes.add(c)
}
let fg = -1, bg = -1, inverse = false
- line.items.forEach(item => {
+ return m('.content', {}, [mark, line.items.flatMap(item => {
switch (item.kind) {
case 'Text':
- linkify(item.text, {
+ return Content.linkify(item.text, {
class: Array.from(classes.keys()).join(' '),
- style: applyColor(fg, bg, inverse),
- }, content)
- break
+ style: Content.applyColor(fg, bg, inverse),
+ })
case 'Reset':
classes.clear()
fg = bg = -1
@@ -317,26 +348,30 @@ let Content = {
case 'FlipInverse':
inverse = !inverse
break
- case 'FlipBold': flip('b'); break
- case 'FlipItalic': flip('i'); break
- case 'FlipUnderline': flip('u'); break
- case 'FlipCrossedOut': flip('s'); break
- case 'FlipMonospace': flip('m'); break
+ case 'FlipBold':
+ flip('b')
+ break
+ case 'FlipItalic':
+ flip('i')
+ break
+ case 'FlipUnderline':
+ flip('u')
+ break
+ case 'FlipCrossedOut':
+ flip('s')
+ break
+ case 'FlipMonospace':
+ flip('m')
+ break
}
- })
- return m('.content', {}, content)
+ })])
},
}
let Buffer = {
oncreate: vnode => {
- if (vnode.dom === undefined ||
- bufferLog !== undefined || !bufferAutoscroll)
- return
-
- let el = vnode.dom.children[1]
- if (el !== null)
- el.scrollTop = el.scrollHeight
+ if (vnode.dom !== undefined && bufferAutoscroll)
+ vnode.dom.scrollTop = vnode.dom.scrollHeight
},
onupdate: vnode => {
@@ -361,57 +396,35 @@ let Buffer = {
lines.push(m('.time', {}, date.toLocaleTimeString()))
lines.push(m(Content, {}, line))
})
-
- return m('.buffer-container', {}, [
- m('.filler'),
- bufferLog !== undefined
- ? m(".log", {}, bufferLog)
- : m('.buffer', {}, lines),
- ])
+ return m('.buffer', {}, lines)
},
}
-let Toolbar = {
- toggleAutoscroll: () => {
- bufferAutoscroll = !bufferAutoscroll
+let Log = {
+ oncreate: vnode => {
+ if (vnode.dom !== undefined)
+ vnode.dom.scrollTop = vnode.dom.scrollHeight
},
- toggleLog: () => {
- if (bufferLog) {
- bufferLog = undefined
- return
- }
-
- rpc.send({
- command: 'BufferLog',
- bufferName: bufferCurrent,
- }).then(resp => {
- bufferLog = rpc.base64decode(resp.log)
- m.redraw()
- })
+ view: vnode => {
+ return m(".log", {}, bufferLog)
},
+}
+let BufferContainer = {
view: vnode => {
- return m('.toolbar', {}, [
- m('button', {onclick: Toolbar.toggleAutoscroll},
- bufferAutoscroll ? 'Pause autoscroll' : 'Unpause autoscroll'),
- m('button', {onclick: Toolbar.toggleLog},
- bufferLog === undefined ? 'Show log' : 'Hide log'),
+ return m('.buffer-container', {}, [
+ m('.filler'),
+ bufferLog !== undefined ? m(Log) : m(Buffer),
])
},
}
-function onKeyDown(event) {
- // TODO: And perhaps on other actions, too.
- rpc.send({command: 'Active'})
-
- // TODO: Cancel any current autocomplete.
-
- let textarea = event.currentTarget
- switch (event.keyCode) {
- case 9:
+let Input = {
+ complete: textarea => {
if (textarea.selectionStart !== textarea.selectionEnd)
- return
+ return false
+
rpc.send({
command: 'BufferComplete',
bufferName: bufferCurrent,
@@ -426,28 +439,41 @@ function onKeyDown(event) {
textarea.setRangeText(' ',
textarea.selectionStart, textarea.selectionEnd, 'end')
})
- break;
- case 13:
+ return true
+ },
+
+ submit: textarea => {
rpc.send({
command: 'BufferInput',
bufferName: bufferCurrent,
text: textarea.value,
})
textarea.value = ''
- break;
- default:
- return
- }
+ return true
+ },
- event.preventDefault()
-}
+ onKeyDown: event => {
+ // TODO: And perhaps on other actions, too.
+ rpc.send({command: 'Active'})
+
+ // TODO: Cancel any current autocomplete.
+
+ let textarea = event.currentTarget
+ let handled = false
+ switch (event.keyCode) {
+ case 9:
+ handled = Input.complete(textarea)
+ break
+ case 13:
+ handled = Input.submit(textarea)
+ break
+ }
+ if (handled)
+ event.preventDefault()
+ },
-let Input = {
view: vnode => {
- return m('textarea#input', {
- rows: 1,
- onkeydown: onKeyDown,
- })
+ return m('textarea#input', {rows: 1, onkeydown: Input.onKeyDown})
},
}
@@ -461,7 +487,7 @@ let Main = {
return m('.xP', {}, [
m('.title', {}, [`xP (${state})`, m(Toolbar)]),
- m('.middle', {}, [m(BufferList), m(Buffer)]),
+ m('.middle', {}, [m(BufferList), m(BufferContainer)]),
m('.status', {}, bufferCurrent),
m(Input),
])