Pulled konsole out into it's own module
This commit is contained in:
		@@ -1,5 +1,5 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import '../misc/konsole';
 | 
			
		||||
import '../modules/konsole';
 | 
			
		||||
import {onMounted, ref} from 'vue';
 | 
			
		||||
 | 
			
		||||
const animate = ref(true);
 | 
			
		||||
@@ -28,48 +28,6 @@ onMounted(async () => {
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.cli-container {
 | 
			
		||||
	padding: 1rem;
 | 
			
		||||
	background: #333;
 | 
			
		||||
	font-family: monospace !important;
 | 
			
		||||
	overflow-y: auto;
 | 
			
		||||
	min-height: 150px;
 | 
			
		||||
	max-height: 300px;
 | 
			
		||||
 | 
			
		||||
	.cli-stdout {
 | 
			
		||||
		flex-grow: 1;
 | 
			
		||||
		color: #0f0;
 | 
			
		||||
 | 
			
		||||
		.cli-stdout-line {
 | 
			
		||||
			padding: 0;
 | 
			
		||||
			min-height: 1.25rem;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.cli-stdin {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		margin: 0;
 | 
			
		||||
 | 
			
		||||
		.cli-stdin-prompt {
 | 
			
		||||
			padding-right: 0.55em;
 | 
			
		||||
			text-wrap: nowrap;
 | 
			
		||||
			color: #0f0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.cli-stdin-input {
 | 
			
		||||
			border: none;
 | 
			
		||||
			outline: none;
 | 
			
		||||
			font-size: 1rem;
 | 
			
		||||
			background-color: rgba(0, 0, 0, 0);
 | 
			
		||||
			color: #0f0;
 | 
			
		||||
			flex-grow: 1;
 | 
			
		||||
			padding: 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<div id="konsole" @click="animate = false"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,344 +0,0 @@
 | 
			
		||||
window.cli = {
 | 
			
		||||
	// Element references
 | 
			
		||||
	_input: null,
 | 
			
		||||
	_parent: null,
 | 
			
		||||
	_prompt: null,
 | 
			
		||||
	_output: null,
 | 
			
		||||
 | 
			
		||||
	// CLI State
 | 
			
		||||
	_history: [],
 | 
			
		||||
	_index: 0,
 | 
			
		||||
	pwd: '/',
 | 
			
		||||
	hostname: 'virtual',
 | 
			
		||||
	env: {},
 | 
			
		||||
	exec: {},
 | 
			
		||||
	filesystem: {},
 | 
			
		||||
	user: 'root',
 | 
			
		||||
	version: '0.3.0',
 | 
			
		||||
 | 
			
		||||
	build: (elementId) => {
 | 
			
		||||
		window.cli._parent = document.querySelector(elementId);
 | 
			
		||||
		if(!window.cli._parent)
 | 
			
		||||
			throw new Error(`Could not create konsole, element "${elementId}" does not exist`);
 | 
			
		||||
 | 
			
		||||
		window.cli._parent.innerHTML = `
 | 
			
		||||
            <div class="cli-container">
 | 
			
		||||
                <div class="cli-stdout"></div>
 | 
			
		||||
                <div class="cli-stdin" onclick="window.cli._input.focus()">
 | 
			
		||||
                    <label for="${elementId}-cli-stdin-input" class="cli-stdin-prompt">${window.cli.prompt()}</label>
 | 
			
		||||
                    <input id="${elementId}-cli-stdin-input" class="cli-stdin-input" type="text" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>`;
 | 
			
		||||
 | 
			
		||||
		window.cli._input = document.querySelector(elementId + ' .cli-stdin-input');
 | 
			
		||||
		window.cli._prompt = document.querySelector(elementId + ' .cli-stdin-prompt');
 | 
			
		||||
		window.cli._output = document.querySelector(elementId + ' .cli-stdout');
 | 
			
		||||
		window.cli._input.addEventListener('keyup', (e) => {
 | 
			
		||||
			if(e.key == "Enter") {
 | 
			
		||||
				window.cli.disable();
 | 
			
		||||
				if(!!window.cli._input.value) {
 | 
			
		||||
					window.cli._history.push(window.cli._input.value);
 | 
			
		||||
					window.cli._index = window.cli._history.length;
 | 
			
		||||
					window.cli.stdIn(window.cli._input.value)
 | 
			
		||||
				}
 | 
			
		||||
				window.cli._input.value = '';
 | 
			
		||||
				window.cli.enable();
 | 
			
		||||
			} else if(e.key == 'Up' || e.key == 'ArrowUp') {
 | 
			
		||||
				if(window.cli._index > 0) window.cli._index--;
 | 
			
		||||
				window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					const end = window.cli._input.value.length;
 | 
			
		||||
					window.cli._input.setSelectionRange(end, end);
 | 
			
		||||
					window.cli._input.focus();
 | 
			
		||||
				}, 1)
 | 
			
		||||
			} else if(e.key == 'Down' || e.key == 'ArrowDown') {
 | 
			
		||||
				if(window.cli._index < window.cli._history.length) window.cli._index++;
 | 
			
		||||
				window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					const end = window.cli._input.value.length;
 | 
			
		||||
					window.cli._input.setSelectionRange(end, end);
 | 
			
		||||
					window.cli._input.focus();
 | 
			
		||||
				}, 1)
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		setTimeout(() => window.cli.exec['banner'].run(), 1);
 | 
			
		||||
	},
 | 
			
		||||
	disable: () => {
 | 
			
		||||
		window.cli._input.disabled = true;
 | 
			
		||||
		window.cli._prompt.style.visibility = 'hidden';
 | 
			
		||||
	},
 | 
			
		||||
	enable: () => {
 | 
			
		||||
		window.cli._input.disabled = false;
 | 
			
		||||
		window.cli._input.focus();
 | 
			
		||||
		window.cli._prompt.style.visibility = 'visible';
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	path: (path=window.cli.pwd) => {
 | 
			
		||||
		let p = path[0] == '/'? path : (window.cli.pwd + (window.cli.pwd.endsWith('/') ? '' : '/') + path.replace('./', ''))
 | 
			
		||||
			.replaceAll('//', '/');
 | 
			
		||||
		const parts = p.split('/').filter(p => !!p);
 | 
			
		||||
		for(let i = 0; i < parts.length; i++) {
 | 
			
		||||
			if(parts[i] == '..') {
 | 
			
		||||
				i--;
 | 
			
		||||
				parts.splice(i, 2);
 | 
			
		||||
				i--;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return '/' + (parts.length ? parts.join('/') : '');
 | 
			
		||||
	},
 | 
			
		||||
	prompt: () => `${window.cli.user}@${window.cli.hostname}:${window.cli.pwd}${window.cli.user == 'root' ? '#' : '$'}`,
 | 
			
		||||
	fs: (path, set) => {
 | 
			
		||||
		return window.cli.path(path).split('/').filter(p => !!p).reduce((t, p,  i, arr) => {
 | 
			
		||||
			if(!t?.hasOwnProperty(p)) {
 | 
			
		||||
				if(set == undefined) return undefined;
 | 
			
		||||
				t[p] = {};
 | 
			
		||||
			}
 | 
			
		||||
			if(set !== undefined && i == arr.length - 1) {
 | 
			
		||||
				if(set == null) delete t[p];
 | 
			
		||||
				else t[p] = set;
 | 
			
		||||
			}
 | 
			
		||||
			return t[p];
 | 
			
		||||
		}, window.cli.filesystem);
 | 
			
		||||
	},
 | 
			
		||||
	stdErr: (text) => {
 | 
			
		||||
		const p = document.createElement('p');
 | 
			
		||||
		p.classList.add('cli-stdout-line');
 | 
			
		||||
		p.classList.add('cli-stdout-error');
 | 
			
		||||
		p.innerText = text;
 | 
			
		||||
		window.cli._output.appendChild(p);
 | 
			
		||||
	},
 | 
			
		||||
	stdIn:(command, silent=false) => {
 | 
			
		||||
		(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
 | 
			
		||||
			const parts = c.match(/(?:[^\s"]+|"[^"]*")+/g);
 | 
			
		||||
			if(!parts) return;
 | 
			
		||||
 | 
			
		||||
			const exec = window.cli.exec[parts[0]];
 | 
			
		||||
			if(!exec?.run) {
 | 
			
		||||
				window.cli.stdErr(`${window.cli.prompt()} ${command}\n${parts[0]}: command not found`);
 | 
			
		||||
			} else {
 | 
			
		||||
				try {
 | 
			
		||||
					const args = parts.slice(1).map(a => (a[0] == '"' || a[0] == "'") ? a.slice(1, -1) : a);
 | 
			
		||||
					const out = exec.run(args);
 | 
			
		||||
					if(!silent) window.cli.stdOut(`${window.cli.prompt()} ${command}${out ? '\n' + out : ''}`);
 | 
			
		||||
				} catch(err) {
 | 
			
		||||
					console.error(err);
 | 
			
		||||
					window.cli.stdErr(`${window.cli.prompt()} ${command}\n${err.message || `${parts[0]}: exited with a non-zero status`}`);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	stdOut: (text, html=false) => {
 | 
			
		||||
		const p = document.createElement('p');
 | 
			
		||||
		p.classList.add('cli-stdout-line');
 | 
			
		||||
		p[html ? 'innerHTML' : 'innerText'] = text;
 | 
			
		||||
		window.cli._output.appendChild(p);
 | 
			
		||||
	},
 | 
			
		||||
	type: (text, speed=150) => {
 | 
			
		||||
		let counter = 0;
 | 
			
		||||
		return new Promise(res => {
 | 
			
		||||
			let typing = setInterval(() => {
 | 
			
		||||
				if(counter < text.length) {
 | 
			
		||||
					window.cli._input.value += text[counter];
 | 
			
		||||
				} else {
 | 
			
		||||
					clearInterval(typing);
 | 
			
		||||
					setTimeout(() => {
 | 
			
		||||
						window.cli.stdIn(text);
 | 
			
		||||
						window.cli._input.value = '';
 | 
			
		||||
						res();
 | 
			
		||||
					}, 750);
 | 
			
		||||
				}
 | 
			
		||||
				counter++;
 | 
			
		||||
			}, speed);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.cli.exec['banner'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display login banner';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		window.cli.stdOut(`Konsole ${window.cli.version} LTS ${window.cli.hostname} tty1\n\n${window.cli.hostname} login: ${window.cli.user}\npassword:\n\n`);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['cd'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Clear console output';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const path = window.cli.fs(args[0]);
 | 
			
		||||
		if(!path) throw new Error(`cd: \'${args[0]}\': No such file or directory`)
 | 
			
		||||
 | 
			
		||||
		window.cli.pwd = window.cli.path(args[0]);
 | 
			
		||||
		window.cli._prompt.innerText = window.cli.prompt();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['clear'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Clear console output';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		setTimeout(() => window.cli._output.innerHTML = '', 1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['date'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get current date & time';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return (new Date()).toLocaleString();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['echo'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Output text to console';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return args.join(' ');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['exit'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'End session';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		window.cli.stdIn('clear');
 | 
			
		||||
		window.cli._history = [];
 | 
			
		||||
		window.cli._index = 0;
 | 
			
		||||
		window.cli.stdIn('banner');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['help'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display all commands';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return `Konsole v${window.cli.version} - A prototype bash emulator written by Zakary Timson\n\n` +
 | 
			
		||||
			Object.keys(window.cli.exec).map(command => `${command} - ${window.cli.exec[command].help()}`).join('\n');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['hostname'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get computer hostname';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.hostname;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['ls'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display directory contents';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const target = window.cli.fs(args[0]);
 | 
			
		||||
		if(!target) throw new Error(`ls: cannot access \'${args[0]}\': No such file or directory`)
 | 
			
		||||
		return Object.keys(target).reduce((acc, p) => acc + `${p}\n`, '');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['man'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'View command\'s manual';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.exec[args[0]].help();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['mkdir'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Create new directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		if(!args[0]) throw new Error('mkdir: missing operand');
 | 
			
		||||
		window.cli.fs(args[0], {});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['pwd'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get present working directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.pwd;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['rm'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Delete file or directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		if(!args[0]) throw new Error('rm: missing operand');
 | 
			
		||||
		window.cli.fs(args[0], null);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['shower-thought'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Random shower thought';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const motd = [
 | 
			
		||||
			'Why do kamikaze pilots wear helmets?',
 | 
			
		||||
			'How are giraffes real, but unicorns made up',
 | 
			
		||||
			'When you are a kid you don\'t realize you are also watching your parents grow up',
 | 
			
		||||
			'Some one at Google was like "Yea, just have someone drive down every road on earth!"',
 | 
			
		||||
			'The number of people older than you never goes up',
 | 
			
		||||
			'When you brush your teeth you are cleaning your skeleton',
 | 
			
		||||
			'Pregnancy is like a group project where one person get\'s stuck with all the work',
 | 
			
		||||
			'If the universe wasn\'t infinite it would be even scarier',
 | 
			
		||||
			'Either we are alone in the universe or we are not. both are terrifying',
 | 
			
		||||
			'The object of golf is to play the least amount of golf.'
 | 
			
		||||
		];
 | 
			
		||||
		return motd[~~(Math.random() * motd.length)];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['whoami'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get current user account';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.user;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/banner.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/banner.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['banner'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display login banner';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return `Konsole ${window.cli.version} LTS ${window.cli.hostname} tty1\n\n${window.cli.hostname} login: ${window.cli.user}\npassword:\n\n`;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/modules/konsole/commands/cd.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/modules/konsole/commands/cd.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
window.cli.exec['cd'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Clear console output';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const path = window.cli.fs(args[0]);
 | 
			
		||||
		if(!path) throw new Error(`cd: \'${args[0]}\': No such file or directory`)
 | 
			
		||||
 | 
			
		||||
		window.cli.pwd = window.cli.path(args[0]);
 | 
			
		||||
		window.cli._prompt.innerText = window.cli._buildPrompt();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/clear.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/clear.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['clear'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Clear console output';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		setTimeout(() => window.cli._output.innerHTML = '', 1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/date.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/date.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['date'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get current date & time';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return new Date().toLocaleString();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/echo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/echo.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['echo'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Output text to console';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return args.join(' ');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								src/modules/konsole/commands/exit.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/modules/konsole/commands/exit.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
window.cli.exec['exit'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'End session';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		setTimeout(() => {
 | 
			
		||||
			window.cli._history = [];
 | 
			
		||||
			window.cli._index = 0;
 | 
			
		||||
			window.cli._output.innerHTML = ''
 | 
			
		||||
			window.cli.stdOut(window.cli.exec['banner'].run());
 | 
			
		||||
		}, 1);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/modules/konsole/commands/help.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/modules/konsole/commands/help.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
window.cli.exec['help'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display all commands';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return `Konsole v${window.cli.version} - an experimental bash emulator written in JavaScript\nCreated By: Zakary Timson\n\n` +
 | 
			
		||||
			Object.keys(window.cli.exec).map(command => `${command} - ${window.cli.exec[command].help()}`).join('\n');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/hostname.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/hostname.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['hostname'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get computer hostname';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.hostname;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/modules/konsole/commands/ls.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/modules/konsole/commands/ls.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
window.cli.exec['ls'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display directory contents';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const target = window.cli.fs(args[0]);
 | 
			
		||||
		if(!target) throw new Error(`ls: cannot access \'${args[0]}\': No such file or directory`)
 | 
			
		||||
		return Object.keys(target).reduce((acc, p) => acc + `${p}\n`, '');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/man.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/man.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['man'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'View command\'s manual';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.exec[args[0]].help();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/modules/konsole/commands/mkdir.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/modules/konsole/commands/mkdir.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
window.cli.exec['mkdir'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Create new directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		if(!args[0]) throw new Error('mkdir: missing operand');
 | 
			
		||||
		window.cli.fs(args[0], {});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/pwd.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/pwd.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['pwd'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get present working directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.pwd;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/modules/konsole/commands/rm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/modules/konsole/commands/rm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
window.cli.exec['rm'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Delete file or directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		if(!args[0]) throw new Error('rm: missing operand');
 | 
			
		||||
		window.cli.fs(args[0], null);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								src/modules/konsole/commands/shower-thoughts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/modules/konsole/commands/shower-thoughts.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
window.cli.exec['shower-thought'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Random shower thought';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const motd = [
 | 
			
		||||
			'Why do kamikaze pilots wear helmets?',
 | 
			
		||||
			'How are giraffes real, but unicorns made up',
 | 
			
		||||
			'When you are a kid you don\'t realize you are also watching your parents grow up',
 | 
			
		||||
			'Some one at Google was like "Yea, just have someone drive down every road on earth!"',
 | 
			
		||||
			'The number of people older than you never goes up',
 | 
			
		||||
			'When you brush your teeth you are cleaning your skeleton',
 | 
			
		||||
			'Pregnancy is like a group project where one person get\'s stuck with all the work',
 | 
			
		||||
			'If the universe wasn\'t infinite it would be even scarier',
 | 
			
		||||
			'Either we are alone in the universe or we are not. both are terrifying',
 | 
			
		||||
			'The object of golf is to play the least amount of golf.'
 | 
			
		||||
		];
 | 
			
		||||
		return motd[~~(Math.random() * motd.length)];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/modules/konsole/commands/whoami.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/modules/konsole/commands/whoami.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
window.cli.exec['whoami'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get current user account';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.user;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/modules/konsole/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/modules/konsole/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import './konsole.js';
 | 
			
		||||
import './konsole.css';
 | 
			
		||||
 | 
			
		||||
// CLI Commands
 | 
			
		||||
import './commands/banner.js';
 | 
			
		||||
import './commands/cd.js'
 | 
			
		||||
import './commands/clear.js';
 | 
			
		||||
import './commands/date.js';
 | 
			
		||||
import './commands/echo.js';
 | 
			
		||||
import './commands/exit.js';
 | 
			
		||||
import './commands/help.js';
 | 
			
		||||
import './commands/hostname.js';
 | 
			
		||||
import './commands/ls.js';
 | 
			
		||||
import './commands/man.js';
 | 
			
		||||
import './commands/mkdir.js';
 | 
			
		||||
import './commands/pwd.js';
 | 
			
		||||
import './commands/rm.js';
 | 
			
		||||
import './commands/shower-thoughts.js';
 | 
			
		||||
import './commands/whoami.js';
 | 
			
		||||
							
								
								
									
										39
									
								
								src/modules/konsole/konsole.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/modules/konsole/konsole.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
.cli-container {
 | 
			
		||||
	padding: 1em;
 | 
			
		||||
	background: #333;
 | 
			
		||||
	font-family: monospace !important;
 | 
			
		||||
	overflow-y: auto;
 | 
			
		||||
	min-height: 150px;
 | 
			
		||||
	max-height: 300px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cli-stdout {
 | 
			
		||||
	flex-grow: 1;
 | 
			
		||||
	color: #0f0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cli-stdout-line {
 | 
			
		||||
	padding: 0;
 | 
			
		||||
	margin: 0;
 | 
			
		||||
	min-height: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cli-stdin {
 | 
			
		||||
	display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cli-stdin-prompt {
 | 
			
		||||
	padding-right: 0.5em;
 | 
			
		||||
	text-wrap: nowrap;
 | 
			
		||||
	color: #0f0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cli-stdin-input {
 | 
			
		||||
	border: none;
 | 
			
		||||
	outline: none;
 | 
			
		||||
	font-size: 1em;
 | 
			
		||||
	background-color: rgba(0, 0, 0, 0);
 | 
			
		||||
	color: #0f0;
 | 
			
		||||
	flex-grow: 1;
 | 
			
		||||
	padding: 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								src/modules/konsole/konsole.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/modules/konsole/konsole.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
window.cli = {
 | 
			
		||||
	// Element references
 | 
			
		||||
	_input: null,
 | 
			
		||||
	_parent: null,
 | 
			
		||||
	_prompt: null,
 | 
			
		||||
	_output: null,
 | 
			
		||||
 | 
			
		||||
	// CLI State
 | 
			
		||||
	_history: [],
 | 
			
		||||
	_index: 0,
 | 
			
		||||
	pwd: '/',
 | 
			
		||||
	hostname: 'virtual',
 | 
			
		||||
	env: {},
 | 
			
		||||
	exec: {},
 | 
			
		||||
	filesystem: {},
 | 
			
		||||
	user: 'root',
 | 
			
		||||
	version: '0.3.0',
 | 
			
		||||
 | 
			
		||||
	_buildPrompt: () => `${window.cli.user}@${window.cli.hostname}:${window.cli.pwd}${window.cli.user == 'root' ? '#' : '$'}`,
 | 
			
		||||
 | 
			
		||||
	build: (elementId) => {
 | 
			
		||||
		window.cli._parent = document.querySelector(elementId);
 | 
			
		||||
		if(!window.cli._parent)
 | 
			
		||||
			throw new Error(`Could not create konsole, element "${elementId}" does not exist`);
 | 
			
		||||
 | 
			
		||||
		window.cli._parent.innerHTML = `
 | 
			
		||||
            <div class="cli-container">
 | 
			
		||||
                <div class="cli-stdout"></div>
 | 
			
		||||
                <div class="cli-stdin" onclick="window.cli._input.focus()">
 | 
			
		||||
                    <label for="${elementId}-cli-stdin-input" class="cli-stdin-prompt">${window.cli._buildPrompt()}</label>
 | 
			
		||||
                    <input id="${elementId}-cli-stdin-input" class="cli-stdin-input" type="text" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>`;
 | 
			
		||||
 | 
			
		||||
		window.cli._input = document.querySelector(elementId + ' .cli-stdin-input');
 | 
			
		||||
		window.cli._prompt = document.querySelector(elementId + ' .cli-stdin-prompt');
 | 
			
		||||
		window.cli._output = document.querySelector(elementId + ' .cli-stdout');
 | 
			
		||||
		window.cli._input.addEventListener('keyup', (e) => {
 | 
			
		||||
			if(e.key == "Enter") {
 | 
			
		||||
				window.cli.disable();
 | 
			
		||||
				if(!!window.cli._input.value) {
 | 
			
		||||
					window.cli._history.push(window.cli._input.value);
 | 
			
		||||
					window.cli._index = window.cli._history.length;
 | 
			
		||||
					window.cli.stdIn(window.cli._input.value)
 | 
			
		||||
				}
 | 
			
		||||
				window.cli._input.value = '';
 | 
			
		||||
				window.cli.enable();
 | 
			
		||||
			} else if(e.key == 'Up' || e.key == 'ArrowUp') {
 | 
			
		||||
				if(window.cli._index > 0) window.cli._index--;
 | 
			
		||||
				window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					const end = window.cli._input.value.length;
 | 
			
		||||
					window.cli._input.setSelectionRange(end, end);
 | 
			
		||||
					window.cli._input.focus();
 | 
			
		||||
				}, 1)
 | 
			
		||||
			} else if(e.key == 'Down' || e.key == 'ArrowDown') {
 | 
			
		||||
				if(window.cli._index < window.cli._history.length) window.cli._index++;
 | 
			
		||||
				window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					const end = window.cli._input.value.length;
 | 
			
		||||
					window.cli._input.setSelectionRange(end, end);
 | 
			
		||||
					window.cli._input.focus();
 | 
			
		||||
				}, 1)
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		setTimeout(() => window.cli.stdOut(window.cli.exec['banner'].run()), 1);
 | 
			
		||||
	},
 | 
			
		||||
	disable: () => {
 | 
			
		||||
		window.cli._input.disabled = true;
 | 
			
		||||
		window.cli._prompt.style.visibility = 'hidden';
 | 
			
		||||
	},
 | 
			
		||||
	enable: () => {
 | 
			
		||||
		window.cli._input.disabled = false;
 | 
			
		||||
		window.cli._input.focus();
 | 
			
		||||
		window.cli._prompt.style.visibility = 'visible';
 | 
			
		||||
	},
 | 
			
		||||
	path: (path=window.cli.pwd) => {
 | 
			
		||||
		let p = path[0] == '/'? path : (window.cli.pwd + (window.cli.pwd.endsWith('/') ? '' : '/') + path.replace('./', ''))
 | 
			
		||||
			.replaceAll('//', '/');
 | 
			
		||||
		const parts = p.split('/').filter(p => !!p);
 | 
			
		||||
		for(let i = 0; i < parts.length; i++) {
 | 
			
		||||
			if(parts[i] == '..') {
 | 
			
		||||
				i--;
 | 
			
		||||
				parts.splice(i, 2);
 | 
			
		||||
				i--;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return '/' + (parts.length ? parts.join('/') : '');
 | 
			
		||||
	},
 | 
			
		||||
	fs: (path, set) => {
 | 
			
		||||
		return window.cli.path(path).split('/').filter(p => !!p).reduce((t, p,  i, arr) => {
 | 
			
		||||
			if(!t?.hasOwnProperty(p)) {
 | 
			
		||||
				if(set == undefined) return undefined;
 | 
			
		||||
				t[p] = {};
 | 
			
		||||
			}
 | 
			
		||||
			if(set !== undefined && i == arr.length - 1) {
 | 
			
		||||
				if(set == null) delete t[p];
 | 
			
		||||
				else t[p] = set;
 | 
			
		||||
			}
 | 
			
		||||
			return t[p];
 | 
			
		||||
		}, window.cli.filesystem);
 | 
			
		||||
	},
 | 
			
		||||
	stdErr: (text) => {
 | 
			
		||||
		const p = document.createElement('p');
 | 
			
		||||
		p.classList.add('cli-stdout-line');
 | 
			
		||||
		p.classList.add('cli-stdout-error');
 | 
			
		||||
		p.innerText = text;
 | 
			
		||||
		window.cli._output.appendChild(p);
 | 
			
		||||
	},
 | 
			
		||||
	stdIn:(command, suppress=false) => {
 | 
			
		||||
		(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
 | 
			
		||||
			const parts = c.match(/(?:[^\s"]+|"[^"]*")+/g);
 | 
			
		||||
			if(!parts) return;
 | 
			
		||||
 | 
			
		||||
			const exec = window.cli.exec[parts[0]];
 | 
			
		||||
			if(!exec?.run) {
 | 
			
		||||
				if(!suppress) window.cli.stdErr(`${window.cli._buildPrompt()} ${command}\n${parts[0]}: command not found`);
 | 
			
		||||
			} else {
 | 
			
		||||
				try {
 | 
			
		||||
					const args = parts.slice(1).map(a => (a[0] == '"' || a[0] == "'") ? a.slice(1, -1) : a);
 | 
			
		||||
					const out = exec.run(args);
 | 
			
		||||
					if(!suppress) window.cli.stdOut(`${window.cli._buildPrompt()} ${command}${out ? '\n' + out : ''}`);
 | 
			
		||||
				} catch(err) {
 | 
			
		||||
					console.error(err);
 | 
			
		||||
					if(!suppress) {
 | 
			
		||||
						window.cli._output.removeChild(window.cli._output.children[window.cli._output.children.length - 1]);
 | 
			
		||||
						window.cli.stdErr(`${window.cli._buildPrompt()} ${command}\n${err.message || `${parts[0]}: exited with a non-zero status`}`);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	stdOut: (text='', html=false) => {
 | 
			
		||||
		const p = document.createElement('p');
 | 
			
		||||
		p.classList.add('cli-stdout-line');
 | 
			
		||||
		p[html ? 'innerHTML' : 'innerText'] = text;
 | 
			
		||||
		window.cli._output.appendChild(p);
 | 
			
		||||
	},
 | 
			
		||||
	type: (text, speed=150) => {
 | 
			
		||||
		let counter = 0;
 | 
			
		||||
		return new Promise(res => {
 | 
			
		||||
			let typing = setInterval(() => {
 | 
			
		||||
				if(counter < text.length) {
 | 
			
		||||
					window.cli._input.value += text[counter];
 | 
			
		||||
				} else {
 | 
			
		||||
					clearInterval(typing);
 | 
			
		||||
					setTimeout(() => {
 | 
			
		||||
						window.cli.stdIn(text);
 | 
			
		||||
						window.cli._input.value = '';
 | 
			
		||||
						res();
 | 
			
		||||
					}, 750);
 | 
			
		||||
				}
 | 
			
		||||
				counter++;
 | 
			
		||||
			}, speed);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user