Initial commit

This commit is contained in:
Simon Ser
2020-04-24 19:01:02 +02:00
commit f7569a43b7
5 changed files with 1080 additions and 0 deletions

212
assets/client.js Normal file
View File

@@ -0,0 +1,212 @@
var server = {
name: "chat.freenode.net",
url: "ws://localhost:8080",
username: "soju-test-user/irc.freenode.net",
realname: "soju-test-user",
nick: "soju-test-user",
pass: "soju-test-user",
};
var buffers = {};
var activeBuffer = null;
var bufferListElt = document.getElementById("buffer-list");
var logElt = document.getElementById("log");
var composerElt = document.getElementById("composer");
var composerInputElt = document.getElementById("composer-input");
function createMessageElement(msg) {
var date = new Date();
var line = document.createElement("div");
line.className = "logline";
var timestamp = document.createElement("a");
timestamp.href = "#";
timestamp.className = "timestamp";
timestamp.innerText = date.toLocaleTimeString(undefined, {
timeStyle: "short",
hour12: false,
});
timestamp.onclick = function(event) {
event.preventDefault();
};
line.appendChild(timestamp);
switch (msg.command) {
case "NOTICE":
case "PRIVMSG":
var text = msg.params[1];
var nick = document.createElement("a");
nick.href = "#";
nick.className = "nick";
nick.innerText = msg.prefix.name;
nick.onclick = function(event) {
event.preventDefault();
switchBuffer(createBuffer(msg.prefix.name));
};
line.appendChild(document.createTextNode(" <"));
line.appendChild(nick);
line.appendChild(document.createTextNode("> "));
line.appendChild(document.createTextNode(text));
break;
default:
line.appendChild(document.createTextNode(" " + msg.command + " " + msg.params.join(" ")));
}
return line;
}
function createBuffer(name) {
if (buffers[name]) {
return buffers[name];
}
var a = document.createElement("a");
a.href = "#";
a.onclick = function(event) {
event.preventDefault();
switchBuffer(name);
};
a.innerText = name;
var li = document.createElement("li");
li.appendChild(a);
buf = {
name: name,
li: li,
messages: [],
readOnly: false,
addMessage: function(msg) {
buf.messages.push(msg);
if (activeBuffer == buf) {
logElt.appendChild(createMessageElement(msg));
}
},
};
buffers[name] = buf;
bufferListElt.appendChild(li);
return buf;
}
function switchBuffer(buf) {
if (typeof buf == "string") {
buf = buffers[buf];
}
if (activeBuffer && buf === activeBuffer) {
return;
}
if (activeBuffer) {
activeBuffer.li.classList.remove("active");
}
activeBuffer = buf;
if (!buf) {
return;
}
buf.li.classList.add("active");
logElt.innerHTML = "";
for (var msg of buf.messages) {
logElt.appendChild(createMessageElement(msg));
}
composerElt.classList.toggle("read-only", buf.readOnly);
if (!buf.readOnly) {
composerInputElt.focus();
}
}
function appendMessage(target, date, nickname, text) {
var timestamp = document.createElement("a");
timestamp.href = "#";
timestamp.className = "timestamp";
timestamp.innerText = date.toLocaleTimeString(undefined, {
timeStyle: "short",
hour12: false,
});
var nick = document.createElement("a");
nick.href = "#";
nick.className = "nick";
nick.innerText = nickname;
var line = document.createElement("div");
line.className = "logline";
line.appendChild(timestamp);
line.appendChild(document.createTextNode(" <"));
line.appendChild(nick);
line.appendChild(document.createTextNode("> "));
line.appendChild(document.createTextNode(text));
logElt.appendChild(line);
}
var serverBuffer = createBuffer(server.name);
serverBuffer.readOnly = true;
switchBuffer(serverBuffer);
var ws = new WebSocket(server.url);
ws.onopen = function() {
console.log("Connection opened");
if (server.pass) {
ws.send(formatMessage({ command: "PASS", params: [server.pass] }));
}
ws.send(formatMessage({ command: "NICK", params: [server.nick] }));
ws.send(formatMessage({
command: "USER",
params: [server.username, "0", "*", server.realname],
}));
};
ws.onmessage = function(event) {
var msg = parseMessage(event.data);
console.log(msg);
switch (msg.command) {
case "NOTICE":
case "PRIVMSG":
var target = msg.params[0];
if (target == server.nick) {
target = msg.prefix.name;
}
createBuffer(target).addMessage(msg);
break;
case "JOIN":
var channel = msg.params[0];
if (msg.prefix.name == server.nick) {
createBuffer(channel);
}
break;
default:
serverBuffer.addMessage(msg);
}
};
ws.onclose = function() {
console.log("Connection closed");
};
composerElt.onsubmit = function(event) {
event.preventDefault();
if (!activeBuffer || activeBuffer.readOnly) {
return;
}
var target = activeBuffer.name;
var text = composerInputElt.value;
var msg = { command: "PRIVMSG", params: [target, text] };
ws.send(formatMessage(msg));
msg.prefix = { name: server.nick };
activeBuffer.addMessage(msg);
composerInputElt.value = "";
};

104
assets/irc.js Normal file
View File

@@ -0,0 +1,104 @@
function parsePrefix(s) {
var prefix = {
name: null,
user: null,
host: null,
};
var i = s.indexOf("@");
if (i < 0) {
prefix.name = s;
return prefix;
}
prefix.host = s.slice(i + 1);
s = s.slice(0, i);
var i = s.indexOf("!");
if (i < 0) {
prefix.name = s;
return prefix;
}
prefix.name = s.slice(0, i);
prefix.user = s.slice(i + 1);
return prefix;
}
function formatPrefix(prefix) {
if (!prefix.host) {
return prefix.name;
}
if (!prefix.user) {
return prefix.name + "@" + prefix.host;
}
return prefix.name + "!" + prefix.user + "@" + prefix.host;
}
function parseMessage(s) {
if (s.endsWith("\r\n")) {
s = s.slice(0, s.length - 2);
}
var msg = {
prefix: null,
command: null,
params: [],
};
if (s.startsWith("@")) {
// TODO: parse tags
}
if (s.startsWith(":")) {
var parts = s.split(" ", 2);
var i = s.indexOf(" ");
if (i < 0) {
throw new Error("expected a space after prefix");
}
msg.prefix = parsePrefix(s.slice(1, i));
s = s.slice(i + 1);
}
var i = s.indexOf(" ");
if (i < 0) {
msg.command = s;
return msg;
}
msg.command = s.slice(0, i);
s = s.slice(i + 1);
while (true) {
if (s.startsWith(":")) {
msg.params.push(s.slice(1));
break;
}
i = s.indexOf(" ");
if (i < 0) {
msg.params.push(s);
break;
}
msg.params.push(s.slice(0, i));
s = s.slice(i + 1);
}
return msg;
}
function formatMessage(msg) {
var s = "";
// TODO: format tags
if (msg.prefix) {
s += ":" + formatPrefix(msg.prefix) + " ";
}
s += msg.command;
if (msg.params && msg.params.length > 0) {
var last = msg.params[msg.params.length - 1];
if (msg.params.length > 1) {
s += " " + msg.params.slice(0, -1).join(" ");
}
s += " :" + last;
}
s += "\r\n";
return s;
}

75
assets/style.css Normal file
View File

@@ -0,0 +1,75 @@
html, body {
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}
body {
display: grid;
grid-template-rows: auto 40px;
grid-template-columns: 200px auto;
font-family: monospace;
}
#sidebar, #log {
height: 100%;
overflow: auto;
}
#sidebar {
background-color: #e3e3e3;
grid-row: 1 / 3;
}
#sidebar ul {
list-style-type: none;
margin: 0;
padding: 10px;
}
#sidebar ul a {
display: inline-block;
width: 100%;
}
#log {
box-sizing: border-box;
padding: 10px;
}
#composer {
grid-column: 2;
border-top: 1px solid #e3e3e3;
}
#composer input {
display: block;
height: 100%;
width: 100%;
box-sizing: border-box;
padding: 10px;
border: none;
background: none;
}
#composer.read-only {
display: none;
}
a {
color: green;
}
#sidebar a, a.timestamp, a.nick {
color: #4a4a4a;
text-decoration: none;
}
#sidebar .active a {
color: black;
}
#sidebar a:hover, #sidebar a:active,
a.timestamp:hover, a.timestamp:active,
a.nick:hover, a.nick:active {
text-decoration: underline;
}
a.nick {
color: #f25e0d;
}