mobile support! readme!! branding!!!
This commit is contained in:
parent
33b16ad70f
commit
5f2051bd2b
27
README.md
Normal file
27
README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# OpenTerminal
|
||||||
|
An online terminal and communal text buffer
|
||||||
|
|
||||||
|
![openterminal thumbnail image](public/img/thumbnail.png)
|
||||||
|
|
||||||
|
## how it works
|
||||||
|
|
||||||
|
a simple static web server pushes out the html, css and javascript needed to make this function well and look pretty on client devices. from there, clients connect back to the server's websocket, which they then exchange keystroke information with, back and forth, all passing through the server's own saved buffer. (if you refresh the page, everything's still there!)
|
||||||
|
|
||||||
|
### wouldn't this be really easy to grief?
|
||||||
|
|
||||||
|
yes!
|
||||||
|
|
||||||
|
jokes aside- while i do absolutely see how an open, self-moderated text buffer is hilariously easy to grief, i could also imagine users taking the concept and pushing it quite a bit further than what i'm doing here. who knows? maybe a anti-spam bot could come in, read through the buffer for any *nefarious* material, and backpedal through the buffer just enough to remove it, before replacing the otherwise above-board text from memory.
|
||||||
|
|
||||||
|
...or maybe it'll just become a garbage-posting haven. regardless, it's a fun little idea, so i made it anyway.
|
||||||
|
|
||||||
|
## roadmap
|
||||||
|
|
||||||
|
- rewrite backend in go/rust (me no like javascript raaaahhh)
|
||||||
|
- colour palette switcher in the UI (rather than in console)
|
||||||
|
- multiple "channels" (at least if one gets flooded, there's somewhere else you can go)
|
||||||
|
|
||||||
|
### "maybe" roadmap
|
||||||
|
|
||||||
|
- master server (anyone can host a channel and post to the MS)
|
||||||
|
|
BIN
public/img/thumbnail.png
Normal file
BIN
public/img/thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 351 KiB |
|
@ -2,6 +2,20 @@
|
||||||
<html lang="ie">
|
<html lang="ie">
|
||||||
<head>
|
<head>
|
||||||
<title>OpenTerminal</title>
|
<title>OpenTerminal</title>
|
||||||
|
<meta name="description" content="An online terminal and communal text buffer :)">
|
||||||
|
|
||||||
|
<meta property="og:url" content="https://term.arimelody.me">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:title" content="OpenTerminal">
|
||||||
|
<meta property="og:description" content="An online terminal and communal text buffer :)">
|
||||||
|
<meta property="og:image" content="https://term.arimelody.me/img/thumbnail.png">
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta property="twitter:domain" content="term.arimelody.me">
|
||||||
|
<meta property="twitter:url" content="https://term.arimelody.me">
|
||||||
|
<meta name="twitter:title" content="OpenTerminal">
|
||||||
|
<meta name="twitter:description" content="An online terminal and communal text buffer :)">
|
||||||
|
<meta name="twitter:image" content="https://term.arimelody.me/img/thumbnail.png">
|
||||||
|
|
||||||
<link rel="stylesheet" href="/styles/main.css">
|
<link rel="stylesheet" href="/styles/main.css">
|
||||||
<link rel="icon" type="image/png" href="/favicon.png">
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
|
@ -12,7 +26,14 @@
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<pre id="content"></pre>
|
<pre id="content"></pre>
|
||||||
|
<input type="text" id="mobile-input">
|
||||||
</main>
|
</main>
|
||||||
|
<footer>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://arimelody.me" target="_blank">made with <3 by ari melody</a></li>
|
||||||
|
<li><a href="https://github.com/mellodoot/openterminal" target="_blank">source</a></li>
|
||||||
|
</ul>
|
||||||
|
</footer>
|
||||||
<div id="overlay"></div>
|
<div id="overlay"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
var buffer = "";
|
var buffer = "";
|
||||||
var send_buffer = "";
|
var send_buffer = "";
|
||||||
var content;
|
var content;
|
||||||
|
var mobile_input;
|
||||||
var client;
|
var client;
|
||||||
var pre_buffer_chars = 0;
|
var pre_buffer_chars = 0;
|
||||||
var term_interval = 10;
|
var term_interval = 10;
|
||||||
|
@ -10,7 +11,7 @@ function start() {
|
||||||
console.log("%chello, world!", "color: #b7fd49; font-size: 3rem; font-weight: bold");
|
console.log("%chello, world!", "color: #b7fd49; font-size: 3rem; font-weight: bold");
|
||||||
console.log(
|
console.log(
|
||||||
`welcome to OpenTerminal!
|
`welcome to OpenTerminal!
|
||||||
home to a little shared text buffer.
|
home to an online terminal and communal text buffer.
|
||||||
|
|
||||||
i hope you enjoy your stay here!
|
i hope you enjoy your stay here!
|
||||||
to help you feel a little more comfortable, i've prepared some commands for you:
|
to help you feel a little more comfortable, i've prepared some commands for you:
|
||||||
|
@ -31,6 +32,11 @@ to help you feel a little more comfortable, i've prepared some commands for you:
|
||||||
}
|
}
|
||||||
|
|
||||||
content = document.getElementById("content");
|
content = document.getElementById("content");
|
||||||
|
mobile_input = document.getElementById("mobile-input");
|
||||||
|
|
||||||
|
content.addEventListener("click", () => {
|
||||||
|
mobile_input.focus();
|
||||||
|
});
|
||||||
|
|
||||||
buffer += "Connecting to the server...";
|
buffer += "Connecting to the server...";
|
||||||
|
|
||||||
|
@ -52,6 +58,8 @@ function loop() {
|
||||||
send_buffer = send_buffer.substring(1);
|
send_buffer = send_buffer.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mobile_input.value = content.innerText;
|
||||||
|
|
||||||
setTimeout(loop, term_interval);
|
setTimeout(loop, term_interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +80,7 @@ function connect() {
|
||||||
});
|
});
|
||||||
|
|
||||||
client.addEventListener('close', () => {
|
client.addEventListener('close', () => {
|
||||||
insert_text("\n\n[CONNECTION LOST]");
|
insert_text("\n\n[CONNECTION LOST, PLEASE REFRESH]");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,8 +91,10 @@ function insert_text(text) {
|
||||||
if (text == "\x00") {
|
if (text == "\x00") {
|
||||||
content.innerText = "";
|
content.innerText = "";
|
||||||
pre_buffer_chars = 0;
|
pre_buffer_chars = 0;
|
||||||
} else if (text == "\b" && content.innerText.length > pre_buffer_chars) {
|
} else if (text == "\b") {
|
||||||
|
if (content.innerText.length > pre_buffer_chars) {
|
||||||
content.innerText = content.innerText.slice(0, content.innerText.length - 1);
|
content.innerText = content.innerText.slice(0, content.innerText.length - 1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
content.innerText += text;
|
content.innerText += text;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,12 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: 1rem;
|
margin: 1rem 1rem 0 1rem;
|
||||||
border: 1px solid var(--colour);
|
border: 1px solid var(--colour);
|
||||||
}
|
}
|
||||||
|
|
||||||
pre#content {
|
pre#content {
|
||||||
height: calc(100vh - 66px);
|
height: calc(100vh - 5rem);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
@ -46,6 +46,37 @@ div#carat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
height: 18px;
|
||||||
|
padding: .5em 2em;
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer ul {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer li {
|
||||||
|
list-style: none;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer li:hover {
|
||||||
|
text-shadow: 0 0 1em, 0 0 3em;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: var(--colour);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
div#overlay {
|
div#overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -69,3 +100,15 @@ div#overlay {
|
||||||
opacity: .6;
|
opacity: .6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mobile-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 1.2em;
|
||||||
|
left: 1.2em;
|
||||||
|
width: calc(100vw - 3em);
|
||||||
|
height: calc(100vh - 3.9em);
|
||||||
|
opacity: 0;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
BIN
res/thumbnail.afphoto
Normal file
BIN
res/thumbnail.afphoto
Normal file
Binary file not shown.
|
@ -29,13 +29,15 @@ const motds = [
|
||||||
"how's the weather?",
|
"how's the weather?",
|
||||||
"with each web request, my server room grows hotter.",
|
"with each web request, my server room grows hotter.",
|
||||||
"mobile support coming later probably!",
|
"mobile support coming later probably!",
|
||||||
|
"there is science to do...",
|
||||||
|
"now fully open-source!",
|
||||||
|
"somehow not the worst communication app!",
|
||||||
];
|
];
|
||||||
|
|
||||||
const STATIC_PATH = path.join(process.cwd(), "public");
|
const STATIC_PATH = path.join(process.cwd(), "public");
|
||||||
|
|
||||||
const banner =
|
const banner =
|
||||||
`OpenTerminal v0.1.0
|
`Welcome to OpenTerminal!
|
||||||
made with <3 by ari melody
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -43,7 +45,8 @@ const PORT = process.env.PORT || 8080;
|
||||||
let sockets = [];
|
let sockets = [];
|
||||||
|
|
||||||
let buffer = "";
|
let buffer = "";
|
||||||
let MAX_BUFFER_SIZE = 10240;
|
const MAX_BUFFER_SIZE = 10240;
|
||||||
|
const MAX_MESSAGE_LENGTH = 64;
|
||||||
|
|
||||||
async function get_file(url) {
|
async function get_file(url) {
|
||||||
const paths = [STATIC_PATH, url];
|
const paths = [STATIC_PATH, url];
|
||||||
|
@ -73,22 +76,25 @@ const server = https.createServer(config, async (req, res) => {
|
||||||
|
|
||||||
const wss = new Websocket.Server({ server });
|
const wss = new Websocket.Server({ server });
|
||||||
wss.on('connection', socket => {
|
wss.on('connection', socket => {
|
||||||
socket.send(banner + motds[Math.floor(Math.random() * motds.length)] + "\n\n");
|
socket.send(`${banner}/* ${motds[Math.floor(Math.random() * motds.length)]} */\n\n`);
|
||||||
socket.send(buffer);
|
socket.send(buffer);
|
||||||
|
|
||||||
sockets.push(socket);
|
sockets.push(socket);
|
||||||
|
|
||||||
console.log(`new connection.\n\tcurrent connections: ${sockets.length}`);
|
// console.log(`new connection.\n\tcurrent connections: ${sockets.length}`);
|
||||||
|
|
||||||
socket.on('message', handle_message);
|
socket.on('message', handle_message);
|
||||||
|
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
sockets = sockets.filter(s => s !== socket);
|
sockets = sockets.filter(s => s !== socket);
|
||||||
console.log(`connection closed.\n\tcurrent connections: ${sockets.length}`);
|
// console.log(`connection closed.\n\tcurrent connections: ${sockets.length}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function handle_message(msg) {
|
function handle_message(msg) {
|
||||||
|
if (msg.length > MAX_MESSAGE_LENGTH) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (msg == '\b') {
|
if (msg == '\b') {
|
||||||
buffer = buffer.slice(0, buffer.length - 1);
|
buffer = buffer.slice(0, buffer.length - 1);
|
||||||
send_text('\b');
|
send_text('\b');
|
||||||
|
@ -101,9 +107,6 @@ function handle_message(msg) {
|
||||||
send_text('\n');
|
send_text('\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.length > 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += msg.toString();
|
buffer += msg.toString();
|
||||||
send_text(msg.toString());
|
send_text(msg.toString());
|
||||||
|
|
Loading…
Reference in a new issue