A mostly usable client, with fora tabs and activity indication
This commit is contained in:
parent
b450b67d67
commit
8273e03b72
|
@ -0,0 +1,29 @@
|
||||||
|
Woozle IRC
|
||||||
|
=========
|
||||||
|
|
||||||
|
This is a sort of bouncer for clients with transient network connections,
|
||||||
|
like cell phones and laptops.
|
||||||
|
It's a lot like [tapchat](https://github.com/tapchat/tapchat) but is a whole lot simpler
|
||||||
|
while being (at time of writing) much more feature-complete.
|
||||||
|
|
||||||
|
It supports (currently) an JavaScript browser-based client,
|
||||||
|
and can also be worked from the command-line using Unix tools like "tail" and "echo".
|
||||||
|
|
||||||
|
Ironically, it doesn't currently work with any existing IRC clients,
|
||||||
|
although we are kicking around ideas for such a thing.
|
||||||
|
Honestly, though, if you want a bouncer for a traditional IRC client,
|
||||||
|
you are better off using something like znc.
|
||||||
|
|
||||||
|
We have an [architectural diagram](https://docs.google.com/drawings/d/1am_RTUh89kul-318GoYK73AbjOE_jMYi4vI4NyEgKrY/edit?usp=sharing) if you care about such things.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* Gracefully handles clients with transient networking, such as cell phones and laptops.
|
||||||
|
|
||||||
|
|
||||||
|
Todo
|
||||||
|
-----
|
||||||
|
|
||||||
|
I need to make this document suck less.
|
52
index.html
52
index.html
|
@ -3,58 +3,16 @@
|
||||||
<head>
|
<head>
|
||||||
<title>#tron</title>
|
<title>#tron</title>
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="chat.png">
|
<link rel="icon" type="image/png" sizes="64x64" href="chat.png">
|
||||||
<script type="application/javascript" src="irc.js">
|
<script type="application/javascript" src="irc.js"></script>
|
||||||
</script>
|
<link rel="stylesheet" type="text/css" href="irc.css">
|
||||||
<style type="text/css">
|
|
||||||
#a, #kiboze {
|
|
||||||
max-height: 20em;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
#a p, #kiboze p {
|
|
||||||
margin: 0em 0em 0em 4em;
|
|
||||||
text-indent: -4em;
|
|
||||||
}
|
|
||||||
#kiboze {
|
|
||||||
max-height: 7em;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
.timestamp {
|
|
||||||
color: silver;
|
|
||||||
}
|
|
||||||
.forum {
|
|
||||||
color: darkblue;
|
|
||||||
}
|
|
||||||
.sender {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
.sender:before {
|
|
||||||
color: green;
|
|
||||||
content: "<";
|
|
||||||
}
|
|
||||||
.sender:after {
|
|
||||||
content: ">";
|
|
||||||
}
|
|
||||||
.raw {
|
|
||||||
color: purple;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[name~=target] {
|
|
||||||
width: 7em;
|
|
||||||
}
|
|
||||||
input[name~=text] {
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="a"></div>
|
<div id="foraButtons"></div>
|
||||||
|
<div id="foraText"></div>
|
||||||
<form id="command">
|
<form id="command">
|
||||||
<input type="hidden" name="auth" value="" id="authtok">
|
<input type="hidden" name="auth" value="" id="authtok">
|
||||||
<input type="hidden" name="type" value="command">
|
<input type="hidden" name="type" value="command">
|
||||||
<input name="target" value="#tron">
|
<input type="hidden" name="target" id="target">
|
||||||
<input name="text" autofocus>
|
<input name="text" autofocus>
|
||||||
<input type="Submit" value="Send">
|
<input type="Submit" value="Send">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
#foraText, #kiboze {
|
||||||
|
max-height: 20em;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
#foraText p, #kiboze p {
|
||||||
|
margin: 0em 0em 0em 4em;
|
||||||
|
text-indent: -4em;
|
||||||
|
}
|
||||||
|
#kiboze {
|
||||||
|
max-height: 7em;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: silver;
|
||||||
|
}
|
||||||
|
.forum {
|
||||||
|
color: darkblue;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.sender {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.sender:before {
|
||||||
|
content: "<";
|
||||||
|
}
|
||||||
|
.sender:after {
|
||||||
|
content: ">";
|
||||||
|
}
|
||||||
|
.sender.notice {
|
||||||
|
color: olive;
|
||||||
|
}
|
||||||
|
.sender.notice:before {
|
||||||
|
content: "-";
|
||||||
|
}
|
||||||
|
.sender.notice:after {
|
||||||
|
content: "-";
|
||||||
|
}
|
||||||
|
.raw {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
.current {
|
||||||
|
background-color: aquamarine;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[name~=target] {
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
input[name~=text] {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
7
irc.go
7
irc.go
|
@ -123,9 +123,12 @@ func parse(v string) (Message, error) {
|
||||||
m.Command = strings.ToUpper(parts[0])
|
m.Command = strings.ToUpper(parts[0])
|
||||||
switch m.Command {
|
switch m.Command {
|
||||||
case "PRIVMSG", "NOTICE":
|
case "PRIVMSG", "NOTICE":
|
||||||
if isChannel(parts[1]) {
|
switch {
|
||||||
|
case isChannel(parts[1]):
|
||||||
m.Forum = parts[1]
|
m.Forum = parts[1]
|
||||||
} else {
|
case m.FullSender == ".":
|
||||||
|
m.Forum = parts[1]
|
||||||
|
default:
|
||||||
m.Forum = m.Sender
|
m.Forum = m.Sender
|
||||||
}
|
}
|
||||||
case "PART", "MODE", "TOPIC", "KICK":
|
case "PART", "MODE", "TOPIC", "KICK":
|
||||||
|
|
109
irc.js
109
irc.js
|
@ -1,10 +1,59 @@
|
||||||
var msgRe = /([^ ]+) (<[^>]+>) (.*)/;
|
var msgRe = /([^ ]+) (<[^>]+>) (.*)/;
|
||||||
var kibozeRe = "[Nn]eal";
|
var kibozeRe = /[Nn]eal/;
|
||||||
|
var urlRe = /[a-z]+:\/\/[^ ]*/;
|
||||||
|
|
||||||
|
var nick = "Mme. M";
|
||||||
|
|
||||||
function isinView(oObject) {
|
function isinView(oObject) {
|
||||||
return (oObject.offsetParent.clientHeight <= oObject.offsetTop);
|
return (oObject.offsetParent.clientHeight <= oObject.offsetTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectForum(fe) {
|
||||||
|
var kids = document.getElementById("foraText").childNodes;
|
||||||
|
|
||||||
|
for (i = 0; i < kids.length; i += 1) {
|
||||||
|
e = kids[i];
|
||||||
|
console.log(i, e);
|
||||||
|
if (e == fe) {
|
||||||
|
e.style.display = "block";
|
||||||
|
} else {
|
||||||
|
e.style.display = "none";
|
||||||
|
if (e.button.className == "current") {
|
||||||
|
e.button.className = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fe.button.className = "current";
|
||||||
|
if (fe.lastChild) {
|
||||||
|
fe.lastChild.scrollIntoView(false);
|
||||||
|
}
|
||||||
|
document.getElementById("target").value = fe.forum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getForumElement(forum) {
|
||||||
|
var id = "a:" + forum;
|
||||||
|
var fe = document.getElementById(id);
|
||||||
|
|
||||||
|
if (! fe) {
|
||||||
|
var button = document.createElement("button");
|
||||||
|
button.appendChild(document.createTextNode(forum));
|
||||||
|
button.onclick = function() { selectForum(fe); }
|
||||||
|
document.getElementById("foraButtons").appendChild(button);
|
||||||
|
|
||||||
|
fe = document.createElement("div");
|
||||||
|
fe.id = id
|
||||||
|
fe.forum = forum
|
||||||
|
fe.button = button
|
||||||
|
document.getElementById("foraText").appendChild(fe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fe.button.className != "current") {
|
||||||
|
fe.button.className = "active";
|
||||||
|
}
|
||||||
|
return fe;
|
||||||
|
}
|
||||||
|
|
||||||
function addMessagePart(p, className, text) {
|
function addMessagePart(p, className, text) {
|
||||||
var e = document.createElement("span");
|
var e = document.createElement("span");
|
||||||
e.className = className;
|
e.className = className;
|
||||||
|
@ -12,11 +61,48 @@ function addMessagePart(p, className, text) {
|
||||||
p.appendChild(e);
|
p.appendChild(e);
|
||||||
p.appendChild(document.createTextNode(" "));
|
p.appendChild(document.createTextNode(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addText(p, text, kiboze) {
|
||||||
|
// Look for a URL
|
||||||
|
var txtElement = document.createElement("span");
|
||||||
|
txtElement.className = "text";
|
||||||
|
var rhs = text;
|
||||||
|
var match;
|
||||||
|
|
||||||
|
while ((match = urlRe.exec(rhs)) != null) {
|
||||||
|
var before = rhs.substr(0, match.index);
|
||||||
|
var a = document.createElement("a");
|
||||||
|
var href = match[0];
|
||||||
|
|
||||||
|
if (href.indexOf("hxx") == 0) {
|
||||||
|
href = "htt" + href.substr(3);
|
||||||
|
}
|
||||||
|
a.href = href
|
||||||
|
a.target = "_blank";
|
||||||
|
a.appendChild(document.createTextNode(match[0]));
|
||||||
|
txtElement.appendChild(document.createTextNode(before));
|
||||||
|
txtElement.appendChild(a);
|
||||||
|
rhs = rhs.substr(match.index + match[0].length);
|
||||||
|
}
|
||||||
|
txtElement.appendChild(document.createTextNode(rhs));
|
||||||
|
p.appendChild(txtElement);
|
||||||
|
|
||||||
|
if ((kiboze) || (-1 != text.search(kibozeRe))) {
|
||||||
|
var k = document.getElementById("kiboze");
|
||||||
|
var p2 = p.cloneNode(true);
|
||||||
|
k.insertBefore(p2, k.firstChild);
|
||||||
|
p2.onclick = function() { focus(p); }
|
||||||
|
|
||||||
|
// Setting title makes the tab flash sorta
|
||||||
|
document.title = document.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function focus(e) {
|
function focus(e) {
|
||||||
var pct = 1;
|
var pct = 1;
|
||||||
var timeout;
|
var timeout;
|
||||||
|
|
||||||
|
selectForum(e.parentNode);
|
||||||
e.scrollIntoView(false);
|
e.scrollIntoView(false);
|
||||||
e.style.backgroundColor = "yellow";
|
e.style.backgroundColor = "yellow";
|
||||||
|
|
||||||
|
@ -41,7 +127,7 @@ function addMessage(txt) {
|
||||||
var args = parts.slice(5);
|
var args = parts.slice(5);
|
||||||
var msg = txt.substr(lhs.length + 2)
|
var msg = txt.substr(lhs.length + 2)
|
||||||
|
|
||||||
var a = document.getElementById("a");
|
var forumElement = getForumElement(forum);
|
||||||
var p = document.createElement("p");
|
var p = document.createElement("p");
|
||||||
|
|
||||||
addMessagePart(p, "timestamp", ts.toLocaleTimeString());
|
addMessagePart(p, "timestamp", ts.toLocaleTimeString());
|
||||||
|
@ -54,17 +140,12 @@ function addMessage(txt) {
|
||||||
case "PRIVMSG":
|
case "PRIVMSG":
|
||||||
addMessagePart(p, "forum", forum);
|
addMessagePart(p, "forum", forum);
|
||||||
addMessagePart(p, "sender", sender);
|
addMessagePart(p, "sender", sender);
|
||||||
addMessagePart(p, "text", msg);
|
addText(p, msg, (sender == forum));
|
||||||
if ((sender == forum) || (-1 != msg.search(kibozeRe))) {
|
break;
|
||||||
var k = document.getElementById("kiboze");
|
case "NOTICE":
|
||||||
var p2 = p.cloneNode(true);
|
addMessagePart(p, "forum", forum);
|
||||||
k.insertBefore(p2, k.firstChild);
|
addMessagePart(p, "sender notice", sender);
|
||||||
p2.onclick = function() { focus(p); }
|
addText(p, msg, (sender == forum));
|
||||||
// Supposedly changing title makes the tab flash sorta
|
|
||||||
t = document.title
|
|
||||||
document.title = "!"
|
|
||||||
document.title = t
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
addMessagePart(p, "forum", forum);
|
addMessagePart(p, "forum", forum);
|
||||||
|
@ -72,7 +153,7 @@ function addMessage(txt) {
|
||||||
addMessagePart(p, "raw", command + " " + args + " " + msg);
|
addMessagePart(p, "raw", command + " " + args + " " + msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
a.appendChild(p);
|
forumElement.appendChild(p);
|
||||||
p.scrollIntoView(false);
|
p.scrollIntoView(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue