/** * @typedef Command * @property {string} command * @property {string} responseType * @property {string?} value * @property {string[]?} headers * @property {string[]?} rows */ /** * @type {Command[]} commands */ import commands from "./resources/commands.json"; import { getCV, pif, rmRf, setDarkMode } from "./custom-comands"; import { dragElement } from "./draggable"; // Table containing the orders (useful for the completion of the orders) let commandsList = []; commands.forEach((c) => { commandsList.push(c.command); }); // Commands that require JS processing const customCommands = ["clear", "dark", "light", "get cv"]; commandsList = commandsList.concat(customCommands); // Eyster eggs' commands not available for autocompletion const hiddenCommands = ["pif", "rm -rf /"]; // Added the ability to move the window for PCs if (window.innerWidth > 1024) { dragElement(document.querySelector(".terminal")); } // Order history table const commandsHistory = []; let historyMode = false; let historyIndex = -1; const terminalBody = document.querySelector(".terminal__body"); // Adding the default line addNewLine(); // December Easter egg, adding snowflakes const now = new Date(); if (now.getMonth() === 11) { let htmlFlakes = ""; for (let i = 0; i < 6; i++) { htmlFlakes += `
help command to display a list of available commands.`;
} else {
if (commandObj.responseType === "list" && Array.isArray(commandObj.value)) {
html = "${commandObj.value.join("\n")}`;
}
}
return html;
}
/**
* Adds a new command input line and disables the previous one.
* @param {string|null} previousUid uid de la ligne précédente.
*/
function addNewLine(previousUid = null) {
const uid = Math.random().toString(36).replace("0.", "");
// terminal__line
const terminalLineEl = document.createElement("div");
terminalLineEl.classList.add("terminal__line");
// terminal__response
const terminalResponseEl = document.createElement("div");
terminalResponseEl.classList.add("terminal__response");
terminalResponseEl.id = `response-${uid}`;
// input text
const inputEl = document.createElement("input");
inputEl.type = "text";
inputEl.id = `input-${uid}`;
inputEl.autocapitalize = "off";
inputEl.dataset.uid = uid;
inputEl.dataset.active = "1"; // Needed for focus
inputEl.addEventListener("keydown", onCommandInput);
terminalLineEl.appendChild(inputEl);
if (previousUid) {
const previousInputEl = document.getElementById(previousUid);
if (previousInputEl) {
previousInputEl.setAttribute("disabled", "true");
previousInputEl.removeEventListener("keydown", onCommandInput);
delete previousInputEl.dataset.active;
}
}
document.getElementById("terminal").appendChild(terminalLineEl);
document.getElementById("terminal").appendChild(terminalResponseEl);
inputEl.focus(); // Adds the focus as soon as the field is created
}
/**
* Manages the keydown on the command input.
* @param e
*/
function onCommandInput(e) {
const commandValue = e.target.value.trim().toLowerCase();
if (e.keyCode === 13) {
// ENTER
if (commandValue !== "") {
historyMode = false;
const idResponse = `response-${e.target.dataset.uid}`;
const responseEl = document.getElementById(idResponse);
let html;
if (
hiddenCommands.includes(commandValue) ||
customCommands.includes(commandValue)
) {
html = handleCustomCommands(commandValue);
} else {
html = getDomForCommand(commandValue);
}
if (responseEl) {
responseEl.innerHTML = html;
commandsHistory.push(commandValue);
addNewLine(e.target.id);
}
}
} else if (e.keyCode === 9) {
// TAB
e.preventDefault();
if (commandValue === "") {
this.value = "help";
} else {
const matchingCommand = commandsList.find((c) =>
c.startsWith(commandValue)
);
if (matchingCommand) {
this.value = matchingCommand;
}
}
historyMode = false;
} else if (e.keyCode === 38 || e.keyCode === 40) {
// UP / DOWN
// History management
if (commandsHistory.length > 0) {
if (historyMode === false) {
historyIndex = commandsHistory.length - 1;
} else {
if (e.keyCode === 38 && historyIndex !== 0) {
// UP
historyIndex--;
} else if (
e.keyCode === 40 &&
historyIndex !== commandsHistory.length - 1
) {
historyIndex++;
}
}
this.value = commandsHistory[historyIndex];
}
historyMode = true;
}
}
/**
* Allows to manage hidden commands (not proposed in the autocompletion)
* @param {string} command
* @returns {string|void} Html to be displayed in the response of the command
*/
function handleCustomCommands(command) {
switch (command) {
case "pif":
pif();
return "Let's go !";
case "light":
if (!document.body.classList.contains("dark-mode"))
return "You are already in light mode.";
setDarkMode(false);
return "Your are now in light mode.";
case "dark":
if (document.body.classList.contains("dark-mode"))
return "You are already in dark mode.";
setDarkMode(true);
return "You are now in dark mode.";
case "get cv":
getCV();
return "The CV will be downloaded.";
case "rm -rf /":
rmRf();
return "🎆";
case "clear":
terminalBody.innerHTML = ``;
return;
}
}
/**
* Convert HTML to DOM object
* @param html
* @returns {DocumentFragment}
*/
function stringToDom(html) {
return document.createRange().createContextualFragment(html);
}
// ------------------------------------------------------------------------------------
// EVENT LISTENNER
// ------------------------------------------------------------------------------------
// Added focus on the input even if you click on the body (to keep the cursor)
document.body.addEventListener("click", function (e) {
if (e.target.tagName !== "INPUT") {
const activeInput = document.querySelector("input[data-active]");
activeInput.focus();
}
});
document.querySelector(".fake-close").addEventListener("click", function (e) {
const terminalEl = document.querySelector(".terminal");
terminalEl.parentElement.removeChild(terminalEl);
});