| 
							- const TextBox = {
 -     autoSize: () => {
 -         textbox.style.height = `0px`
 -         textbox.style.height = `${textbox.scrollHeight}px`
 -     },
 -     sendPost: () => {
 -         if(textbox.value) {
 -             wire({kind: 'post', value: textbox.value})
 -             textbox.value = ''
 -             textbox.focus()
 -             TextBox.autoSize()
 -         }
 -     },
 -     blockIndent: (text, iStart, iEnd, nLevels) => {
 -         const startLine = text.slice(0, iStart).split('\n').length - 1
 -         const endLine = text.slice(0, iEnd).split('\n').length - 1
 -         const newText = text
 -             .split('\n')
 -             .map((line, i) => {
 -                 if(i < startLine || i > endLine || nLevels === 0) {
 -                     newLine = line
 -                 }
 -                 else if(nLevels > 0) {
 -                     newLine = line.replace(/^/, '    ')
 -                 }
 -                 else if(nLevels < 0) {
 -                     newLine = line.replace(/^    /, '')
 -                 }
 - 
 -                 if(i === startLine) {
 -                     iStart = iStart + newLine.length - line.length
 -                 }
 -                 iEnd = iEnd + newLine.length - line.length
 -                 return newLine
 -             })
 -             .join('\n')
 -         return [newText, Math.max(0, iStart), Math.max(0, iEnd)]
 -     },
 -     hotKey: (e) => {
 -         // if isDesktop, Enter posts, unless Shift+Enter
 -         isDesktop = true
 -         if(e.key === 'Enter' && isDesktop && !e.shiftKey) {
 -             e.preventDefault()
 -             TextBox.sendPost()
 -         }
 -         // indent and dedent
 -         const modKey = e.ctrlKey || e.metaKey
 -         const {value: text, selectionStart: A, selectionEnd: B} = textbox
 -         if(e.key === 'Tab') {
 -             e.preventDefault()
 -             const regex = new RegExp(`([\\s\\S]{${A}})([\\s\\S]{${B - A}})`)
 -             textbox.value = text.replace(regex, (m, a, b) => a + ' '.repeat(4))
 -             textbox.setSelectionRange(A + 4, A + 4)
 -         }
 -         if(']['.includes(e.key) && modKey) {
 -             e.preventDefault()
 -             const nLevels = {']': 1, '[': -1}[e.key]
 -             const [newText, newA, newB] = TextBox.blockIndent(text, A, B, nLevels)
 -             textbox.value = newText
 -             textbox.setSelectionRange(newA, newB)
 -         }
 -     },
 -     view() {
 -         return m('.actions',
 -             m('textarea#textbox', {
 -                 oncreate: (vnode) => {
 -                     TextBox.autoSize()
 -                     autoFocus(vnode)
 -                 },
 -                 onkeydown: TextBox.hotKey,
 -                 oninput: TextBox.autoSize,
 -             }),
 -             m('button', {onclick: TextBox.sendPost}, 'Send'),
 -         )
 -     },
 - }
 - const Post = {
 -     oncreate: ({dom}) => {
 -         dom.scrollIntoView()
 -     },
 -     prettifyTime: (ts) => {
 -         const dt = new Date(ts)
 -         const H = `0${dt.getHours()}`.slice(-2)
 -         const M = `0${dt.getMinutes()}`.slice(-2)
 -         const S = `0${dt.getSeconds()}`.slice(-2)
 -         return `${H}:${M}:${S}`
 -     },
 -     fixPost: ({dom}) => {
 -         dom.querySelectorAll('a').forEach(anchor => {
 -             anchor.target = '_blank'
 -             anchor.rel = 'noopener'
 -         })
 -     },
 -     view({attrs: {post}}) {
 -         return m('.post',
 -             m('.ts', this.prettifyTime(post.ts)),
 -             m('.source', post.source || '~'),
 -             m('.text', {oncreate: this.fixPost}, m.trust(DOMPurify.sanitize(marked(post.value)))),
 -         )
 -     }
 - }
 - const ChatConfig = {
 -     isOn: false,
 -     view() {
 -         return m('button', {onclick: () => {ChatConfig.isOn = !ChatConfig.isOn}}, 'chat')
 -     }
 - }
 - const Chat = {
 -     posts: [],
 -     oncreate() {
 -         listen('post', ({detail}) => {
 -             this.posts.push(detail)
 -             m.redraw()
 -         })
 -         listen('logout', () => {
 -             this.posts = []
 -         })
 -         marked.setOptions({
 -             breaks: true,
 -         })
 -     },
 -     view() {
 -         return ChatConfig.isOn ? m('.chat',
 -             m('.posts', this.posts.map(post => m(Post, {post}))),
 -             m(TextBox),
 -         ) : null
 -     },
 - }
 
 
  |