const user = { id: crypto.randomUUID(), name: "", history: [] }; let pretext = "root@baipyr.us:~# "; // Initiating variables const length = pretext.length+1; let startPosition = length; let cursorPosition = 0; let cursorYOffset = 7; let history = {index: 0, list: []}; // Initiating constants const tbDiv = document.getElementById("textbox"); const textIn = document.getElementById("input"); const textCur = document.getElementById("current"); const cursor = document.getElementById("cursor"); // Initial cursor offset cursor.style.transform = `translate(${length-1}ch,${2.222*7-0.4}ch)`; // Handle user input textIn.oninput = () => { const text = textCur.textContent; // If cursor at end of text if (cursorPosition === text.length) textCur.textContent += textIn.value; else { // Insert input at cursor position const left = text.substring(0, cursorPosition); const right = text.substring(cursorPosition, text.length); textCur.textContent = left + textIn.value + right; } // Increment by input length cursorPosition += textIn.value.length; textIn.value = ""; updateCursor(); }; textIn.focus(); // If no ranged selection was made, focus on input field document.addEventListener("selectionchange", e => { if (e.target !== textIn) { const selection = window.getSelection(); if (selection.type === "Caret") textIn.focus(); } }); document.addEventListener("keydown", async e => { const text = textCur.textContent; switch (e.key) { case "Backspace": // Ein Zeichen nach links löschen if (cursorPosition === 0) return; if (cursorPosition <= textCur.textContent.length-1) { const a = text.substring(0, cursorPosition-1); const b = text.substring(cursorPosition, text.length); textCur.textContent=a+b; } else textCur.textContent = text.substring(0,text.length-1); cursorPosition--; updateCursor(); break; case "ArrowLeft": // Ein Zeichen nach links if (cursorPosition > 0) { cursorPosition--; updateCursor(); } break; case "ArrowRight": // Ein Zeichen nach reckts if (cursorPosition < textCur.textContent.length) { cursorPosition++; updateCursor(); } break; case "Delete": // Ein Zeichen nach rechts löschen if (cursorPosition >= textCur.textContent.length) return; if (cursorPosition === 0 && textCur.textContent.length === 0) return; const a = text.substring(0, cursorPosition); const b = text.substring(cursorPosition+1, text.length); textCur.textContent=a+b; break; case "Enter": // Befehl absenden / Zeilenumbruch tbDiv.removeChild(textIn); tbDiv.removeChild(textCur); const inputSet = new Set(text.split('')); if (text !== "" || inputSet.has(" ") && inputSet.size === 1) { history.list.push(text); history.index = history.list.length; } // Fix current input to page tbDiv.appendChild(createText(text)); tbDiv.innerHTML += `
`; // Run command and return output let commandOutput = await runCommand(text); if (commandOutput !== "") { // If output is given, display it as text with formatting tbDiv.append(createText(commandOutput, false)) tbDiv.innerHTML += `
`; // Offset cursor by at least one line, and as many more as there are line breaks cursorYOffset += commandOutput.split("
").length; } // Add new pretext to terminal tbDiv.innerHTML += pretext; // Add elements on top of div tbDiv.appendChild(textCur); tbDiv.appendChild(textIn); // Reset variables textCur.textContent = ""; cursorPosition = 0; cursorYOffset++; updateCursor(); break; case "ArrowUp": // Einen Befehl zurück in der History if (history.index > 0) { history.index--; textCur.textContent = history.list[history.index]; cursorPosition = textCur.textContent.length; } updateCursor(); break; case "ArrowDown": // Einen Befehl nach vorne in der History if (history.index < history.list.length-1) { history.index++; textCur.textContent = history.list[history.index]; cursorPosition = textCur.textContent.length; } else if (history.index < history.list.length) { history.index++; textCur.textContent = ""; cursorPosition = 0; } updateCursor(); break; case "Tab": // Autocomplete break; } }); // Create an 'a' element with input string as its contents function createText(str, safe=true) { const link = document.createElement("a"); link.style.color = "lightgrey"; link.style.whiteSpace = "pre"; // No "HTML injection" if (safe) link.textContent = str; // "Anything goes" and keep formatting else link.innerHTML = str; return link; } // Run input as command if possible async function runCommand(input) { // Return on no input if (input === "") return ""; let output = ""; const lowerIn = input.toLowerCase(); // Go through properties of window for (const func in window) { const splits = func.split("cmd_"); // If property is prefixed with 'cmd_' (a 'command', so an executable function) if (splits.length === 2) { const name = splits[1]; const lowerNm = name.toLowerCase(); // If command is called without parameters if (lowerIn === lowerNm) { if (window[func].constructor.name === "AsnycFunction") output = await window[func](); else output = window[func](); break; // If command is called with parameters } else if (lowerIn.startsWith(lowerNm + " ")) { // Parameters always follow the command name after first space const params = input.split(" ").filter((e,i)=>i!==0); if (window[func].constructor.name === "AsnycFunction") output = await window[func](params); else output = window[func](params); break; } } } // Standard output: if (output === "" && !lowerIn.startsWith("clear")) output = `${input.split(" ")[0]}: command not found`; // Return command output return output; } // Update cursor position based on coordinates function updateCursor() { let cursor = document.getElementById("cursor"); cursor.style.transform = `translate(${startPosition+cursorPosition-1}ch, ${2.222*cursorYOffset-0.4}ch)`; } // Toggle cursor visibility setInterval(()=>{ cursor.style.opacity = !parseInt(cursor.style.opacity)+0; },1000);