Update.
This commit is contained in:
parent
75f12c1971
commit
52538d0181
226
src/snek/static/editor.js
Normal file
226
src/snek/static/editor.js
Normal file
@ -0,0 +1,226 @@
|
||||
import { NjetComponent} from "/njext.ks"
|
||||
|
||||
class NjetEditor extends NjetComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#editor {
|
||||
padding: 1rem;
|
||||
outline: none;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
#command-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 0.2rem 1rem;
|
||||
background: #333;
|
||||
color: #0f0;
|
||||
display: none;
|
||||
font-family: monospace;
|
||||
}
|
||||
`;
|
||||
|
||||
this.editor = document.createElement('div');
|
||||
this.editor.id = 'editor';
|
||||
this.editor.contentEditable = true;
|
||||
this.editor.innerText = `Welcome to VimEditor Component
|
||||
Line 2 here
|
||||
Another line
|
||||
Try i, Esc, v, :, yy, dd, 0, $, gg, G, and p`;
|
||||
|
||||
this.cmdLine = document.createElement('div');
|
||||
this.cmdLine.id = 'command-line';
|
||||
this.shadowRoot.append(style, this.editor, this.cmdLine);
|
||||
|
||||
this.mode = 'normal'; // normal | insert | visual | command
|
||||
this.keyBuffer = '';
|
||||
this.lastDeletedLine = '';
|
||||
this.yankedLine = '';
|
||||
|
||||
this.editor.addEventListener('keydown', this.handleKeydown.bind(this));
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
getCaretOffset() {
|
||||
let caretOffset = 0;
|
||||
const sel = this.shadowRoot.getSelection();
|
||||
if (!sel || sel.rangeCount === 0) return 0;
|
||||
|
||||
const range = sel.getRangeAt(0);
|
||||
const preCaretRange = range.cloneRange();
|
||||
preCaretRange.selectNodeContents(this.editor);
|
||||
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
||||
caretOffset = preCaretRange.toString().length;
|
||||
return caretOffset;
|
||||
}
|
||||
|
||||
setCaretOffset(offset) {
|
||||
const range = document.createRange();
|
||||
const sel = this.shadowRoot.getSelection();
|
||||
const walker = document.createTreeWalker(this.editor, NodeFilter.SHOW_TEXT, null, false);
|
||||
|
||||
let currentOffset = 0;
|
||||
let node;
|
||||
while ((node = walker.nextNode())) {
|
||||
if (currentOffset + node.length >= offset) {
|
||||
range.setStart(node, offset - currentOffset);
|
||||
range.collapse(true);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
return;
|
||||
}
|
||||
currentOffset += node.length;
|
||||
}
|
||||
}
|
||||
|
||||
handleKeydown(e) {
|
||||
const key = e.key;
|
||||
|
||||
if (this.mode === 'insert') {
|
||||
if (key === 'Escape') {
|
||||
e.preventDefault();
|
||||
this.mode = 'normal';
|
||||
this.editor.blur();
|
||||
this.editor.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mode === 'command') {
|
||||
if (key === 'Enter' || key === 'Escape') {
|
||||
e.preventDefault();
|
||||
this.cmdLine.style.display = 'none';
|
||||
this.mode = 'normal';
|
||||
this.keyBuffer = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mode === 'visual') {
|
||||
if (key === 'Escape') {
|
||||
e.preventDefault();
|
||||
this.mode = 'normal';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle normal mode
|
||||
this.keyBuffer += key;
|
||||
|
||||
const text = this.editor.innerText;
|
||||
const caretPos = this.getCaretOffset();
|
||||
const lines = text.split('\n');
|
||||
|
||||
let charCount = 0, lineIdx = 0;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (caretPos <= charCount + lines[i].length) {
|
||||
lineIdx = i;
|
||||
break;
|
||||
}
|
||||
charCount += lines[i].length + 1;
|
||||
}
|
||||
|
||||
const offsetToLine = idx =>
|
||||
text.split('\n').slice(0, idx).reduce((acc, l) => acc + l.length + 1, 0);
|
||||
|
||||
switch (this.keyBuffer) {
|
||||
case 'i':
|
||||
e.preventDefault();
|
||||
this.mode = 'insert';
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
e.preventDefault();
|
||||
this.mode = 'visual';
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case ':':
|
||||
e.preventDefault();
|
||||
this.mode = 'command';
|
||||
this.cmdLine.style.display = 'block';
|
||||
this.cmdLine.textContent = ':';
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case 'yy':
|
||||
e.preventDefault();
|
||||
this.yankedLine = lines[lineIdx];
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case 'dd':
|
||||
e.preventDefault();
|
||||
this.lastDeletedLine = lines[lineIdx];
|
||||
lines.splice(lineIdx, 1);
|
||||
this.editor.innerText = lines.join('\n');
|
||||
this.setCaretOffset(offsetToLine(lineIdx));
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
e.preventDefault();
|
||||
const lineToPaste = this.yankedLine || this.lastDeletedLine;
|
||||
if (lineToPaste) {
|
||||
lines.splice(lineIdx + 1, 0, lineToPaste);
|
||||
this.editor.innerText = lines.join('\n');
|
||||
this.setCaretOffset(offsetToLine(lineIdx + 1));
|
||||
}
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case '0':
|
||||
e.preventDefault();
|
||||
this.setCaretOffset(offsetToLine(lineIdx));
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case '$':
|
||||
e.preventDefault();
|
||||
this.setCaretOffset(offsetToLine(lineIdx) + lines[lineIdx].length);
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case 'gg':
|
||||
e.preventDefault();
|
||||
this.setCaretOffset(0);
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case 'G':
|
||||
e.preventDefault();
|
||||
this.setCaretOffset(text.length);
|
||||
this.keyBuffer = '';
|
||||
break;
|
||||
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
this.mode = 'normal';
|
||||
this.keyBuffer = '';
|
||||
this.cmdLine.style.display = 'none';
|
||||
break;
|
||||
|
||||
default:
|
||||
// allow up to 2 chars for combos
|
||||
if (this.keyBuffer.length > 2) this.keyBuffer = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('njet-editor', NjetEditor);
|
||||
export {NjetEditor}
|
Loading…
Reference in New Issue
Block a user