Live Demo
My First Terminal
Code
css
@import url('https://unpkg.com/xterminal/dist/xterminal.css');
.error {
color: rgb(248, 88, 88);
}
.spinner:after {
animation: changeContent 0.8s linear infinite;
content: "⠋";
}
@keyframes changeContent {
10% { content: "⠙"; }
20% { content: "⠹"; }
30% { content: "⠸"; }
40% { content: "⠼"; }
50% { content: "⠴"; }
60% { content: "⠦"; }
70% { content: "⠧"; }
80% { content: "⠇"; }
90% { content: "⠏"; }
}html
<link rel="stylesheet" href="styles.css">
<div id="app"></div>
<script src="https://unpkg.com/xterminal/dist/xterminal.umd.js"></script>
<script src="createShell.js"></script>
<script src="createTerminal.js"></script>
<script>
window.onload = () => createTerminal('#app');
</script>js
function createTerminal(target) {
const term = new XTerminal({ target });
const state = {
username: "user",
hostname: "web"
};
// input evaluator
const shell = createShell();
// print prompt and get ready for user input
function promptUser() {
term.write(`┌[${state.username}@${state.hostname}]\n`);
term.write("└$ ");
term.resume();
term.focus();
}
// user input handler
term.on("data", async (input) => {
// deactivate until the execution is done
term.pause();
// execute command
await shell
.execute(term, input)
.then((res) => res && term.writeln(res))
.catch((err) => {
if (err) {
// sanitize error to prevent xss attacks
// error may contain user input or HTML strings (like script tags)
term.writeln(
`<span class="error">${XTerminal.escapeHTML(err)}</span>\n`
);
}
})
.finally(promptUser);
});
// greeting message
term.writeln("Welcome to XTerminal (v" + XTerminal.version + ")");
term.writeln("Type `help` for available commands\n");
// kickstart
promptUser();
// remember to free resources
window.addEventListener("unload", () => term.dispose());
}js
/**
* Escape HTML special characters to prevent XSS attacks
*/
function escapeHTML(text) {
const tmp = document.createElement("div");
tmp.textContent = text;
return tmp.innerHTML;
}
function createShell() {
// Help
const manual = `XTerminal : version ${XTerminal.version}
Type 'help' to see this list
Commands:
gh (username) search for github users
js [expr] execute a JS expression
clear clear the terminal screen
help display this list
`;
// Get public github user information
async function fetchGitHubUser(username) {
return fetch("https://api.github.com/users/" + username)
.then((res) => res.json())
.then((res) => {
const escapedName = escapeHTML(res.name);
const escapedBio = escapeHTML(res.bio);
const escapedRepos = escapeHTML(res.public_repos);
const escapedAvatar = escapeHTML(res.avatar_url);
return (
'<table border="0">' +
"<tr>" +
`<td rowspan="3" width="100"><img width="75" src="${escapedAvatar}" alt="${escapedName}" /></td>` +
`<td>Name</td>` +
`<td>${escapedName}</td>` +
"</tr>" +
"<tr>" +
`<td>Bio</td>` +
`<td>${escapedBio}</td>` +
"</tr>" +
"<tr>" +
`<td>Repos</td>` +
`<td>${escapedRepos}</td>` +
"</tr>" +
"</table>"
);
});
}
// evaluate user input from the terminal
// -> can be shared among several terminal objects
function execute(term, command = "") {
let args = command.split(" ");
let cmd = args.shift();
// GitHub User Search
if (cmd == "gh") {
return new Promise(async (res, rej) => {
let output, error;
term.write('<span class="spinner"></span> Searching...');
await fetchGitHubUser(args.join(""))
.then((val) => (output = val))
.catch((err) => (error = ":( Not found!"))
.finally(() => term.clearLast());
if (error) rej(error);
else res(output);
});
}
// JavaScript Evaluation
else if (cmd == "js") {
return new Promise((res, rej) => {
try {
let output = eval(args.join(" ")) + "\n";
res(output);
} catch (error) {
rej(error);
}
});
}
// Help menu
else if (cmd == "help") {
return Promise.resolve(manual);
}
// Clear the terminal
else if (cmd == "clear") {
term.clear();
return Promise.resolve(null);
}
// Oopps!
else {
return Promise.reject(`sh: '${cmd}' command not found`);
}
}
return { execute };
}