| @@ -130,6 +130,32 @@ const prettyTime = (ts) => { | |||
| const S = `0${dt.getSeconds()}`.slice(-2) | |||
| return `${H}:${M}:${S}` | |||
| } | |||
| const blockIndent = (text, iStart, iEnd, nLevels) => { | |||
| // prop | |||
| 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)] | |||
| } | |||
| const hotKey = (e) => { | |||
| // if isDesktop, Enter posts, unless Shift+Enter | |||
| // use isLandscape as proxy for isDesktop | |||
| @@ -137,6 +163,22 @@ const hotKey = (e) => { | |||
| e.preventDefault() | |||
| Chat.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] = blockIndent(text, A, B, nLevels) | |||
| textbox.value = newText | |||
| textbox.setSelectionRange(newA, newB) | |||
| } | |||
| } | |||
| const Video = { | |||
| appendStream: ({username, stream}) => ({dom}) => { | |||