<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
.editor { font-family: 'Roboto Mono', monospace; font-size: 12px; outline: none; overflow-y: auto; padding-left: 48px; counter-reset: line; } | |
.editor div { display: block; position: relative; white-space: pre-wrap; } | |
.editor div::before { content: counter(line); counter-increment: line; position: absolute; right: calc(100% + 16px); opacity: 0.5; } | |
</style> | |
</head> | |
<body> | |
<div class="editor" contenteditable="true" spellcheck="false"> | |
<div>function example() {</div> | |
<div> return 42;</div> | |
<div>}</div> | |
</div> | |
<script> | |
// Syntax highlight for JS | |
const js = el => { | |
for (const node of el.children) { | |
const s = node.innerText | |
.replace(/(\/\/.*)/g, '<em>$1</em>') | |
.replace( | |
/\b(new|if|else|do|while|switch|for|in|of|continue|break|return|typeof|function|var|const|let|\.length|\.\w+)(?=[^\w])/g, | |
'<strong>$1</strong>', | |
) | |
.replace(/(".*?"|'.*?'|`.*?`)/g, '<strong><em>$1</em></strong>') | |
.replace(/\b(\d+)/g, '<em><strong>$1</strong></em>'); | |
node.innerHTML = s.split('\n').join('<br/>'); | |
} | |
}; | |
const editor = (el, highlight = js, tab = ' ') => { | |
const caret = () => { | |
const range = window.getSelection().getRangeAt(0); | |
const prefix = range.cloneRange(); | |
prefix.selectNodeContents(el); | |
prefix.setEnd(range.endContainer, range.endOffset); | |
return prefix.toString().length; | |
}; | |
const setCaret = (pos, parent = el) => { | |
for (const node of parent.childNodes) { | |
if (node.nodeType == Node.TEXT_NODE) { | |
if (node.length >= pos) { | |
const range = document.createRange(); | |
const sel = window.getSelection(); | |
range.setStart(node, pos); | |
range.collapse(true); | |
sel.removeAllRanges(); | |
sel.addRange(range); | |
return -1; | |
} else { | |
pos = pos - node.length; | |
} | |
} else { | |
pos = setCaret(pos, node); | |
if (pos < 0) { | |
return pos; | |
} | |
} | |
} | |
return pos; | |
}; | |
highlight(el); | |
el.addEventListener('keydown', e => { | |
if (e.which === 9) { | |
const pos = caret() + tab.length; | |
const range = window.getSelection().getRangeAt(0); | |
range.deleteContents(); | |
range.insertNode(document.createTextNode(tab)); | |
highlight(el); | |
setCaret(pos); | |
e.preventDefault(); | |
} | |
}); | |
el.addEventListener('keyup', e => { | |
if (e.keyCode >= 0x30 || e.keyCode == 0x20) { | |
const pos = caret(); | |
highlight(el); | |
setCaret(pos); | |
} | |
}); | |
}; | |
// Turn div into an editor | |
const el = document.querySelector('.editor'); | |
el.focus(); | |
editor(el); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment