diff --git a/public/css/main.css b/public/css/main.css
index d1e6dab..8017350 100644
--- a/public/css/main.css
+++ b/public/css/main.css
@@ -131,6 +131,21 @@ body {
cursor: pointer;
font-weight: 600;
}
+.ircclient #chat .ircwrapper .toolbar .tabby .tab #unread {
+ display: inline-block;
+ min-width: 22px;
+ height: 22px;
+ background-color: #bf0000;
+ border: 1px solid #b00;
+ box-shadow: inset 2px 2px 3px #f00;
+ text-align: center;
+ font-size: 10px;
+ margin: 0 5px;
+ border-radius: 100%;
+}
+.ircclient #chat .ircwrapper .toolbar .tabby .tab #unread.none {
+ display: none;
+}
.ircclient #chat .ircwrapper .toolbar .tabby .tab #close {
display: inline-block;
width: 12px;
@@ -174,6 +189,9 @@ body {
bottom: 0;
left: 0;
right: 0;
+ overflow-y: auto;
+ white-space: pre-wrap;
+ word-wrap: break-word;
}
.ircclient #chat .ircwrapper .chatarea .nicklist {
width: 280px;
@@ -188,6 +206,9 @@ body {
.ircclient #chat .ircwrapper .chatarea.vnicks .nicklist {
display: block;
}
+.ircclient #chat .ircwrapper .chatarea.vnicks .nicklist.forceopen {
+ display: block !important;
+}
.ircclient #chat .ircwrapper .chatarea.vnicks .letterbox {
right: 301px;
}
@@ -234,3 +255,25 @@ body {
border-left: 0;
box-shadow: inset 4px 4px 8px #d8d8d8;
}
+.message.type_simple .timestamp {
+ margin-right: 5px;
+ color: #696969;
+}
+.message.type_simple .timestamp:before {
+ color: #607d8b;
+ content: "[";
+}
+.message.type_simple .timestamp:after {
+ color: #607d8b;
+ content: "]";
+}
+.message.type_simple .sender {
+ margin-right: 5px;
+ color: #3f51b5;
+}
+.message.type_simple .sender:before {
+ content: "<";
+}
+.message.type_simple .sender:after {
+ content: ">";
+}
diff --git a/public/js/main.js b/public/js/main.js
index 1f608a2..2afaf23 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -1,9 +1,22 @@
window.irc = {
socketUp: false,
primaryFrame: null,
- timestamps: true
+ timestamps: true,
+ timestampFormat: "HH:mm:ss",
+ supportedPrefixes: "@%+",
+ modeTranslation: {
+ "o": "@",
+ "h": "%",
+ "v": "+"
+ }
};
+/*********************\
+|** **|
+|** UTILITIES **|
+|** **|
+\*********************/
+
window.validators = {};
window.validators.iporhost = function(str) {
@@ -35,6 +48,85 @@ function remove_str(arr, str) {
return arr;
};
+Date.prototype.format = function (format, utc){
+ var date = this;
+ var MMMM = ["\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
+ var MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ var dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
+ var ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ function ii(i, len) { var s = i + ""; len = len || 2; while (s.length < len) s = "0" + s; return s; }
+
+ var y = utc ? date.getUTCFullYear() : date.getFullYear();
+ format = format.replace(/(^|[^\\])yyyy+/g, "$1" + y);
+ format = format.replace(/(^|[^\\])yy/g, "$1" + y.toString().substr(2, 2));
+ format = format.replace(/(^|[^\\])y/g, "$1" + y);
+
+ var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1;
+ format = format.replace(/(^|[^\\])MMMM+/g, "$1" + MMMM[0]);
+ format = format.replace(/(^|[^\\])MMM/g, "$1" + MMM[0]);
+ format = format.replace(/(^|[^\\])MM/g, "$1" + ii(M));
+ format = format.replace(/(^|[^\\])M/g, "$1" + M);
+
+ var d = utc ? date.getUTCDate() : date.getDate();
+ format = format.replace(/(^|[^\\])dddd+/g, "$1" + dddd[0]);
+ format = format.replace(/(^|[^\\])ddd/g, "$1" + ddd[0]);
+ format = format.replace(/(^|[^\\])dd/g, "$1" + ii(d));
+ format = format.replace(/(^|[^\\])d/g, "$1" + d);
+
+ var H = utc ? date.getUTCHours() : date.getHours();
+ format = format.replace(/(^|[^\\])HH+/g, "$1" + ii(H));
+ format = format.replace(/(^|[^\\])H/g, "$1" + H);
+
+ var h = H > 12 ? H - 12 : H == 0 ? 12 : H;
+ format = format.replace(/(^|[^\\])hh+/g, "$1" + ii(h));
+ format = format.replace(/(^|[^\\])h/g, "$1" + h);
+
+ var m = utc ? date.getUTCMinutes() : date.getMinutes();
+ format = format.replace(/(^|[^\\])mm+/g, "$1" + ii(m));
+ format = format.replace(/(^|[^\\])m/g, "$1" + m);
+
+ var s = utc ? date.getUTCSeconds() : date.getSeconds();
+ format = format.replace(/(^|[^\\])ss+/g, "$1" + ii(s));
+ format = format.replace(/(^|[^\\])s/g, "$1" + s);
+
+ var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds();
+ format = format.replace(/(^|[^\\])fff+/g, "$1" + ii(f, 3));
+ f = Math.round(f / 10);
+ format = format.replace(/(^|[^\\])ff/g, "$1" + ii(f));
+ f = Math.round(f / 10);
+ format = format.replace(/(^|[^\\])f/g, "$1" + f);
+
+ var T = H < 12 ? "AM" : "PM";
+ format = format.replace(/(^|[^\\])TT+/g, "$1" + T);
+ format = format.replace(/(^|[^\\])T/g, "$1" + T.charAt(0));
+
+ var t = T.toLowerCase();
+ format = format.replace(/(^|[^\\])tt+/g, "$1" + t);
+ format = format.replace(/(^|[^\\])t/g, "$1" + t.charAt(0));
+
+ var tz = -date.getTimezoneOffset();
+ var K = utc || !tz ? "Z" : tz > 0 ? "+" : "-";
+ if (!utc)
+ {
+ tz = Math.abs(tz);
+ var tzHrs = Math.floor(tz / 60);
+ var tzMin = tz % 60;
+ K += ii(tzHrs) + ":" + ii(tzMin);
+ }
+ format = format.replace(/(^|[^\\])K/g, "$1" + K);
+
+ var day = (utc ? date.getUTCDay() : date.getDay()) + 1;
+ format = format.replace(new RegExp(dddd[0], "g"), dddd[day]);
+ format = format.replace(new RegExp(ddd[0], "g"), ddd[day]);
+
+ format = format.replace(new RegExp(MMMM[0], "g"), MMMM[M]);
+ format = format.replace(new RegExp(MMM[0], "g"), MMM[M]);
+
+ format = format.replace(/\\(.)/g, "$1");
+
+ return format;
+};
+
function removeClass(element, cl) {
let classList = element.className.split(" ");
@@ -48,14 +140,127 @@ function addClass(element, cl) {
element.className = classList.join(" ");
}
+/*********************\
+|** **|
+|** CLASSES **|
+|** **|
+\*********************/
+
class Nicklist {
- constructor(buffer) {
+ constructor(buffer, frame) {
this.buffer = buffer;
+ this.frame = frame;
this.nicks = [];
}
- render(frame) {
+ sort() {
+ this.nicks.sort(function (a,b) {
+ let rex = new RegExp('^['+irc.supportedPrefixes+']');
+ let nicks = [a.prefix.replace(rex,'').toLowerCase(), b.prefix.replace(rex,'').toLowerCase()];
+ let prefix = [];
+ if (rex.test(a.prefix)) prefix.push(irc.supportedPrefixes.indexOf(a.prefix[0]));
+ else prefix.push(irc.supportedPrefixes.length+1);
+ if (rex.test(b.prefix)) prefix.push(irc.supportedPrefixes.indexOf(b.prefix[0]));
+ else prefix.push(irc.supportedPrefixes.length+1);
+ if (prefix[0] < prefix[1]) return -1;
+ if (prefix[0] > prefix[1]) return 1;
+ if (nicks[0] > nicks[1]) return 1;
+ if (nicks[0] < nicks[1]) return -1;
+ return 0;
+ });
+ return this.nicks;
+ }
+ appendToList(nick) {
+ if(!this.buffer.active) return;
+
+ let str = document.createElement("div");
+ str.className = "nick";
+ str.setAttribute('id', 'nick-'+nick.nickname);
+ let construct = ""+nick.prefix+""+nick.nickname+"";
+ str.innerHTML = construct;
+ this.frame.appendChild(str);
+ }
+
+ render() {
+ this.frame.innerHTML = "";
+ for(let n in this.nicks) {
+ let nick = this.nicks[n];
+ this.appendToList(nick);
+ }
+ }
+
+ nickAdd(nickname) {
+ let newbie = { nickname: nickname, prefix: "", modes: [] }
+ this.nicks.push(newbie);
+ this.render();
+ }
+
+ nickRemove(nickname) {
+ let nickIndex = this.getNickIndex(nickname);
+
+ if(nickIndex != null)
+ this.nicks.splice(nickIndex, 1);
+ else
+ return;
+
+ this.render();
+ }
+
+ nickChange(oldNickname, newNickname) {
+ let nickIndex = this.getNickIndex(oldNickname);
+
+ if(nickIndex != null)
+ this.nicks[nickIndex].nickname = newNickname;
+ else
+ return;
+
+ this.render();
+ }
+
+ getNickIndex(nickname) {
+ let result = null;
+
+ for(let n in this.nicks)
+ if(this.nicks[n].nickname == nickname) result = n;
+
+ return result;
+ }
+
+ modeAdded(nickname, newMode) {
+ let nickIndex = this.getNickIndex(nickname);
+ let nick = null;
+
+ if(nickIndex != null)
+ nick = this.nicks[nickIndex];
+ else
+ return;
+
+ for(let mode in irc.modeTranslation) {
+ let prefix = irc.modeTranslation[m];
+ if(newMode == mode)
+ this.nicks[nickIndex].prefix = prefix;
+ }
+
+ this.render();
+ }
+
+ modeRemoved(nickname, oldMode) {
+ let nickIndex = this.getNickIndex(nickname);
+ let nick = null;
+
+ if(nickIndex != null)
+ nick = this.nicks[nickIndex];
+ else
+ return;
+
+ for(let mode in irc.modeTranslation) {
+ let prefix = irc.modeTranslation[m];
+ if(newMode == mode)
+ this.nicks[nickIndex].prefix = "";
+ }
+
+ this.render();
}
}
@@ -94,10 +299,12 @@ class Tab {
setUnreadCount(count) {
if(this.element) {
let counter = this.element.querySelector('#unread');
- if(count == 0)
- counter.innerHTML = "";
- else
+ if(count == 0) {
+ counter.className = "none";
+ } else {
counter.innerHTML = count;
+ counter.className = "";
+ }
}
}
@@ -109,7 +316,7 @@ class Tab {
class Buffer {
constructor(servername, buffername, tabname, type) {
this.messages = [];
- this.nicklist = [];
+ this.nicklist = null;
this.topic = null;
this.input = "";
this.lastscroll = 0;
@@ -126,8 +333,10 @@ class Buffer {
this.tab = new Tab(this);
this.tab.renderTab(irc.primaryFrame.querySelector('.tabby'));
- if(type == "channel")
+ if(type == "channel") {
this.nicklistDisplayed = true;
+ this.nicklist = new Nicklist(this);
+ }
this.frame = irc.primaryFrame.querySelector('#chat');
this.letterbox = this.frame.querySelector('.letterbox');
@@ -157,6 +366,7 @@ class Buffer {
}
this.renderMessages();
+ this.letterbox.scrollTop = this.lastscroll;
}
renderMessages() {
@@ -172,11 +382,11 @@ class Buffer {
appendMessage(meta) {
let mesgConstr = document.createElement('div');
- mesgConstr.className = "message m_"+meta.type;
+ mesgConstr.className = "message type_simple m_"+meta.type;
let construct = "";
if(irc.timestamps)
- construct += ""+meta.time+"";
+ construct += ""+meta.time.format(irc.timestampFormat)+"";
if(meta.sender != null)
construct += ""+meta.sender+"";
@@ -187,10 +397,14 @@ class Buffer {
mesgConstr.innerHTML = construct;
this.letterbox.appendChild(mesgConstr);
+
+ let lFactor = this.letterbox.offsetHeight + this.letterbox.scrollTop
+ if(lFactor > this.letterbox.scrollHeight - 100)
+ this.letterbox.scrollTop = this.letterbox.scrollHeight;
}
addMessage(message, sender, type) {
- let mesg = {message: message, sender: sender, type: type, time: Date.now()}
+ let mesg = {message: message, sender: sender, type: type, time: new Date()}
this.messages.push(mesg);
if(this.active)
@@ -201,6 +415,12 @@ class Buffer {
this.tab.setUnreadCount(this.unreadCount);
}
+ switchOff() {
+ this.tab.setActive(false);
+ this.lastscroll = this.letterbox.scrollTop;
+ this.active = false;
+ }
+
close() {
}
@@ -310,10 +530,7 @@ class IRCChatWindow {
this.buffers = [];
this.frame.style.display = "none";
this.firstServer = true;
-
this.currentBuffer = null;
-
- irc.switchBuffer = this.render;
}
getBufferByName(buffername) {
@@ -392,25 +609,35 @@ class IRCChatWindow {
this.render(buf);
}
+ closeBuffer(buffer) {
+ // todo: close
+ }
+
messageBuffer(name, server, message) {
let buf = this.getBufferByNameServer(server, name);
+
if(buf == null)
- return;
+ buf = this.createBuffer(server, name, "message", false);
+
buf.addMessage(message.message, message.from, message.type);
}
render(buffer) {
let activeNow = this.getActiveBuffer();
- if(activeNow) {
- activeNow.tab.setActive(false);
- activeNow.active = false;
- }
+ if(activeNow)
+ activeNow.switchOff();
buffer.render();
}
}
+/**************************\
+|** **|
+|** INITIALIZATION **|
+|** **|
+\**************************/
+
window.onload = function() {
irc.socket = io.connect('http://localhost:8080');
diff --git a/public/main.styl b/public/main.styl
index 182bda8..ee394e5 100644
--- a/public/main.styl
+++ b/public/main.styl
@@ -120,6 +120,19 @@ body
color: #fff;
cursor: pointer;
font-weight: 600;
+ #unread
+ display: inline-block;
+ min-width: 22px;
+ height: 22px;
+ background-color: #bf0000;
+ border: 1px solid #b00;
+ box-shadow: inset 2px 2px 3px #f00;
+ text-align: center;
+ font-size: 10px;
+ margin: 0 5px;
+ border-radius: 100%;
+ &.none
+ display: none;
#close
display: inline-block;
width: 12px;
@@ -157,6 +170,9 @@ body
bottom: 0;
left: 0;
right: 0;
+ overflow-y: auto;
+ white-space: pre-wrap;
+ word-wrap: break-word;
.nicklist
width: 280px;
position: absolute;
@@ -169,6 +185,8 @@ body
&.vnicks
.nicklist
display: block;
+ &.forceopen
+ display: block !important;
.letterbox
right: 301px;
.topicbar
@@ -209,3 +227,21 @@ body
border-left: 0;
box-shadow: inset 4px 4px 8px #d8d8d8;
+
+.message.type_simple
+ .timestamp
+ margin-right: 5px;
+ color: #696969;
+ &:before
+ color: #607D8B;
+ content: "[";
+ &:after
+ color: #607D8B;
+ content: "]";
+ .sender
+ margin-right: 5px;
+ color: #3F51B5;
+ &:before
+ content: "<";
+ &:after
+ content: ">";
diff --git a/teemant.js b/teemant.js
index e882911..78f7b33 100755
--- a/teemant.js
+++ b/teemant.js
@@ -48,7 +48,7 @@ io.sockets.on('connection', function (socket) {
// Spam the client with messages (for testing)
setInterval(function() {
socket.emit('act_client', {type: 'server_message', messageType: "privmsg", server: connectiondata.server, to: "#channel", from: "horse", message: "I like ponies"});
- }, 4000);
+ }, 1000);
}, 4000);
});
});