764 lines
15 KiB
JavaScript
764 lines
15 KiB
JavaScript
$(function() {
|
|
var socket = io();
|
|
var commands = [
|
|
"/close",
|
|
"/connect",
|
|
"/deop",
|
|
"/devoice",
|
|
"/disconnect",
|
|
"/invite",
|
|
"/join",
|
|
"/kick",
|
|
"/leave",
|
|
"/mode",
|
|
"/msg",
|
|
"/nick",
|
|
"/notice",
|
|
"/op",
|
|
"/part",
|
|
"/query",
|
|
"/quit",
|
|
"/raw",
|
|
"/say",
|
|
"/send",
|
|
"/server",
|
|
"/slap",
|
|
"/topic",
|
|
"/voice",
|
|
"/whois"
|
|
];
|
|
|
|
var sidebar = $("#sidebar, #footer");
|
|
var chat = $("#chat");
|
|
|
|
try {
|
|
var pop = new Audio();
|
|
pop.src = "/audio/pop.ogg";
|
|
} catch(e) {
|
|
var pop = {
|
|
play: $.noop
|
|
};
|
|
}
|
|
|
|
$("#play").on("click", function() { pop.play(); });
|
|
$("#footer .icon").tooltip();
|
|
|
|
$(".tse-scrollable").TrackpadScrollEmulator();
|
|
|
|
var favico = new Favico({
|
|
animation: "none"
|
|
});
|
|
|
|
function render(name, data) {
|
|
return Handlebars.templates[name](data);
|
|
}
|
|
|
|
Handlebars.registerHelper(
|
|
"partial", function(id) {
|
|
return new Handlebars.SafeString(render(id, this));
|
|
}
|
|
);
|
|
|
|
socket.on("error", function(e) {
|
|
console.log(e);
|
|
});
|
|
|
|
$.each(["connect_error", "disconnect"], function(i, e) {
|
|
socket.on(e, function() {
|
|
refresh();
|
|
});
|
|
});
|
|
|
|
socket.on("auth", function(data) {
|
|
var body = $("body");
|
|
var login = $("#sign-in");
|
|
if (!login.length) {
|
|
refresh();
|
|
return;
|
|
}
|
|
var token = $.cookie("token");
|
|
if (token) {
|
|
$.removeCookie("token");
|
|
socket.emit("auth", {token: token});
|
|
}
|
|
if (body.hasClass("signed-out")) {
|
|
var error = login.find(".error");
|
|
error.show().closest("form").one("submit", function() {
|
|
error.hide();
|
|
});
|
|
}
|
|
body.addClass("signed-out");
|
|
var input = login.find("input[name='user']");
|
|
if (input.val() === "") {
|
|
input.val($.cookie("user") || "");
|
|
}
|
|
setTimeout(function() {
|
|
if (!body.hasClass("signed-out")) {
|
|
return;
|
|
}
|
|
sidebar.find(".sign-in")
|
|
.click()
|
|
.end()
|
|
.find(".networks")
|
|
.html("")
|
|
.next()
|
|
.show();
|
|
}, token ? 200 : 0);
|
|
});
|
|
|
|
socket.on("init", function(data) {
|
|
if (data.networks.length === 0) {
|
|
$("#footer").find(".connect").trigger("click");
|
|
} else {
|
|
sidebar.find(".empty").hide();
|
|
sidebar.find(".networks").html(
|
|
render("network", {
|
|
networks: data.networks
|
|
})
|
|
);
|
|
var channels = $.map(data.networks, function(n) {
|
|
return n.channels;
|
|
});
|
|
chat.html(
|
|
render("chat", {
|
|
channels: channels
|
|
})
|
|
);
|
|
confirmExit();
|
|
}
|
|
|
|
if (data.token) {
|
|
$.cookie(
|
|
"token",
|
|
data.token, {
|
|
expires: expire(30)
|
|
}
|
|
);
|
|
}
|
|
|
|
$("body").removeClass("signed-out");
|
|
$("#sign-in").detach();
|
|
|
|
var id = data.active;
|
|
var target = sidebar.find("[data-id='" + id + "']").trigger("click");
|
|
if (target.length === 0) {
|
|
var first = sidebar.find(".chan")
|
|
.eq(0)
|
|
.trigger("click");
|
|
if (first.length === 0) {
|
|
$("#footer").find(".connect").trigger("click");
|
|
}
|
|
}
|
|
|
|
sortable();
|
|
});
|
|
|
|
socket.on("join", function(data) {
|
|
var id = data.network;
|
|
var network = sidebar.find("#network-" + id);
|
|
network.append(
|
|
render("chan", {
|
|
channels: [data.chan]
|
|
})
|
|
);
|
|
chat.append(
|
|
render("chat", {
|
|
channels: [data.chan]
|
|
})
|
|
);
|
|
var chan = sidebar.find(".chan")
|
|
.sort(function(a, b) { return $(a).data("id") - $(b).data("id"); })
|
|
.last();
|
|
if (!whois) {
|
|
chan = chan.filter(":not(.query)");
|
|
}
|
|
whois = false;
|
|
chan.click();
|
|
});
|
|
|
|
socket.on("msg", function(data) {
|
|
var target = data.chan;
|
|
if (data.msg.type == "error") {
|
|
target = chat.find(".active").data("id");
|
|
}
|
|
target = "#chan-" + target;
|
|
chat.find(target)
|
|
.find(".messages")
|
|
.append(render("msg", {messages: [data.msg]}))
|
|
.trigger("msg", [
|
|
target,
|
|
data.msg
|
|
]);
|
|
});
|
|
|
|
socket.on("more", function(data) {
|
|
var target = data.chan;
|
|
var chan = chat
|
|
.find("#chan-" + target)
|
|
.find(".messages")
|
|
.prepend(render("msg", {messages: data.messages}))
|
|
.end();
|
|
if (data.messages.length != 100) {
|
|
var more = chan
|
|
.find(".show-more")
|
|
.addClass("hidden");
|
|
}
|
|
});
|
|
|
|
socket.on("network", function(data) {
|
|
sidebar.find(".empty").hide();
|
|
sidebar.find(".networks").append(
|
|
render("network", {
|
|
networks: [data.network]
|
|
})
|
|
);
|
|
chat.append(
|
|
render("chat", {
|
|
channels: data.network.channels
|
|
})
|
|
);
|
|
sidebar.find(".chan")
|
|
.last()
|
|
.trigger("click");
|
|
$("#connect")
|
|
.find(".btn")
|
|
.prop("disabled", false)
|
|
.end();
|
|
confirmExit();
|
|
sortable();
|
|
});
|
|
|
|
socket.on("nick", function(data) {
|
|
var id = data.network;
|
|
var nick = data.nick;
|
|
var network = sidebar.find("#network-" + id).data("nick", nick);
|
|
if (network.find(".active").length) {
|
|
setNick(nick);
|
|
}
|
|
});
|
|
|
|
socket.on("part", function(data) {
|
|
var id = data.chan;
|
|
sidebar.find(".chan[data-id='" + id + "']").remove();
|
|
$("#chan-" + id).remove();
|
|
|
|
var next = null;
|
|
var highest = -1;
|
|
chat.find(".chan").each(function() {
|
|
var self = $(this);
|
|
var z = parseInt(self.css("z-index"));
|
|
if (z > highest) {
|
|
highest = z;
|
|
next = self;
|
|
}
|
|
});
|
|
|
|
if (next !== null) {
|
|
var id = next.data("id");
|
|
sidebar.find("[data-id=" + id + "]").click();
|
|
} else {
|
|
sidebar.find(".chan")
|
|
.eq(0)
|
|
.click();
|
|
}
|
|
});
|
|
|
|
socket.on("quit", function(data) {
|
|
var id = data.network;
|
|
sidebar.find("#network-" + id)
|
|
.remove()
|
|
.end();
|
|
var chan = sidebar.find(".chan")
|
|
.eq(0)
|
|
.trigger("click");
|
|
if (chan.length === 0) {
|
|
sidebar.find(".empty").show();
|
|
}
|
|
});
|
|
|
|
socket.on("toggle", function(data) {
|
|
$("#toggle-" + data.id).parent().after(render("toggle", data));
|
|
});
|
|
|
|
socket.on("users", function(data) {
|
|
var users = chat.find("#chan-" + data.chan)
|
|
.find(".users")
|
|
.html(render("user", data));
|
|
});
|
|
|
|
$.cookie.json = true;
|
|
|
|
var settings = $("#settings");
|
|
var options = $.extend({
|
|
badge: false,
|
|
join: true,
|
|
mode: true,
|
|
motd: false,
|
|
nick: true,
|
|
notification: true,
|
|
part: true,
|
|
quit: true,
|
|
}, $.cookie("settings"));
|
|
|
|
for (var i in options) {
|
|
if (options[i]) {
|
|
settings.find("input[name=" + i + "]").prop("checked", true);
|
|
}
|
|
}
|
|
|
|
settings.on("change", "input", function() {
|
|
var self = $(this);
|
|
var name = self.attr("name");
|
|
options[name] = self.prop("checked");
|
|
$.cookie(
|
|
"settings",
|
|
options, {
|
|
expires: expire(365)
|
|
}
|
|
);
|
|
if ([
|
|
"join",
|
|
"mode",
|
|
"motd",
|
|
"nick",
|
|
"part",
|
|
"quit",
|
|
].indexOf(name) !== -1) {
|
|
chat.toggleClass("hide-" + name, !self.prop("checked"));
|
|
}
|
|
}).find("input")
|
|
.trigger("change");
|
|
|
|
$("#badge").on("change", function() {
|
|
var self = $(this);
|
|
if (self.prop("checked")) {
|
|
if (Notification.permission != "granted") {
|
|
Notification.requestPermission();
|
|
}
|
|
}
|
|
});
|
|
|
|
var viewport = $("#viewport");
|
|
|
|
viewport.on("click", ".lt, .rt", function(e) {
|
|
var self = $(this);
|
|
viewport.toggleClass(self.attr("class"));
|
|
if (viewport.is(".lt, .rt")) {
|
|
e.stopPropagation();
|
|
chat.find(".chat").one("click", function() {
|
|
viewport.removeClass("lt rt");
|
|
});
|
|
}
|
|
});
|
|
|
|
var input = $("#input")
|
|
.history()
|
|
.tab(complete, {hint: false});
|
|
|
|
var form = $("#form");
|
|
var submit = $("#submit");
|
|
|
|
form.on("submit", function(e) {
|
|
e.preventDefault();
|
|
var text = input.val();
|
|
input.val("");
|
|
if (text.indexOf("/clear") === 0) {
|
|
clear();
|
|
return;
|
|
}
|
|
socket.emit("input", {
|
|
target: chat.data("id"),
|
|
text: text
|
|
});
|
|
});
|
|
|
|
chat.on("click", ".messages", focus);
|
|
$(window).on("focus", focus);
|
|
|
|
function focus() {
|
|
var chan = chat.find(".active");
|
|
if (screen.width > 768 && chan.hasClass("chan")) {
|
|
input.focus();
|
|
}
|
|
}
|
|
|
|
var top = 1;
|
|
sidebar.on("click", ".chan, button", function() {
|
|
var self = $(this);
|
|
var target = self.data("target");
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
chat.data(
|
|
"id",
|
|
self.data("id")
|
|
);
|
|
socket.emit(
|
|
"open",
|
|
self.data("id")
|
|
);
|
|
|
|
sidebar.find(".active").removeClass("active");
|
|
self.addClass("active")
|
|
.find(".badge")
|
|
.removeClass("highlight")
|
|
.data("count", "")
|
|
.empty();
|
|
|
|
if (sidebar.find(".highlight").length === 0) {
|
|
favico.badge("");
|
|
}
|
|
|
|
viewport.removeClass();
|
|
$("#windows .active").removeClass("active");
|
|
|
|
var chan = $(target)
|
|
.addClass("active")
|
|
.trigger("show")
|
|
.css("z-index", top++)
|
|
.find(".chat")
|
|
.sticky()
|
|
.end();
|
|
|
|
if (self.hasClass("chan")) {
|
|
var nick = self
|
|
.closest(".network")
|
|
.data("nick");
|
|
if (nick) {
|
|
setNick(nick);
|
|
}
|
|
}
|
|
|
|
if (screen.width > 768 && chan.hasClass("chan")) {
|
|
input.focus();
|
|
}
|
|
});
|
|
|
|
sidebar.on("click", "#sign-out", function() {
|
|
$.removeCookie("token");
|
|
location.reload();
|
|
});
|
|
|
|
sidebar.on("click", ".close", function() {
|
|
var cmd = "/close";
|
|
var chan = $(this).closest(".chan");
|
|
if (chan.hasClass("lobby")) {
|
|
cmd = "/quit";
|
|
var server = chan.find(".name").html();
|
|
if (!confirm("Disconnect from " + server + "?")) {
|
|
return false;
|
|
}
|
|
}
|
|
socket.emit("input", {
|
|
target: chan.data("id"),
|
|
text: cmd
|
|
});
|
|
chan.css({
|
|
transition: "none",
|
|
opacity: 0.4
|
|
});
|
|
return false;
|
|
});
|
|
|
|
chat.on("input", ".search", function() {
|
|
var value = $(this).val().toLowerCase();
|
|
var names = $(this).closest(".users").find(".names");
|
|
names.find("button").each(function() {
|
|
var btn = $(this);
|
|
var name = btn.text().toLowerCase().replace(/[+%@~]/, "");
|
|
if (name.indexOf(value) === 0) {
|
|
btn.show();
|
|
} else {
|
|
btn.hide();
|
|
}
|
|
});
|
|
});
|
|
|
|
var whois = false;
|
|
chat.on("click", ".user", function() {
|
|
var user = $(this).html().trim().replace(/[+%@~]/, "");
|
|
if (user.indexOf("#") !== -1) {
|
|
return;
|
|
}
|
|
whois = true;
|
|
var text = "/whois " + user;
|
|
socket.emit("input", {
|
|
target: chat.data("id"),
|
|
text: text
|
|
});
|
|
});
|
|
|
|
chat.on("click", ".close", function() {
|
|
var id = $(this)
|
|
.closest(".chan")
|
|
.data("id");
|
|
sidebar.find(".chan[data-id='" + id + "']")
|
|
.find(".close")
|
|
.click();
|
|
});
|
|
|
|
chat.on("msg", ".messages", function(e, target, msg) {
|
|
var button = sidebar.find(".chan[data-target=" + target + "]");
|
|
var isQuery = button.hasClass("query");
|
|
var type = msg.type;
|
|
var highlight = type.contains("highlight");
|
|
if (highlight || isQuery) {
|
|
if (!document.hasFocus() || !$(target).hasClass("active")) {
|
|
var settings = $.cookie("settings") || {};
|
|
if (settings.notification) {
|
|
pop.play();
|
|
}
|
|
favico.badge("!");
|
|
if (settings.badge && Notification.permission == "granted") {
|
|
var notify = new Notification(msg.from + " says:", {
|
|
body: msg.text.trim(),
|
|
icon: "/img/logo-64.png"
|
|
});
|
|
notify.onclick = function() {
|
|
window.focus();
|
|
button.click();
|
|
this.close();
|
|
};
|
|
window.setTimeout(function() {
|
|
notify.close();
|
|
}, 5 * 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
button = button.filter(":not(.active)");
|
|
if (button.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var ignore = [
|
|
"join",
|
|
"part",
|
|
"quit",
|
|
"nick"
|
|
];
|
|
if ($.inArray(type, ignore) !== -1){
|
|
return;
|
|
}
|
|
|
|
var badge = button.find(".badge");
|
|
if (badge.length !== 0) {
|
|
var i = (badge.data("count") || 0) + 1;
|
|
badge.data("count", i);
|
|
badge.html(i > 999 ? (i / 1000).toFixed(1) + "k" : i);
|
|
if (highlight || isQuery) {
|
|
badge.addClass("highlight");
|
|
}
|
|
}
|
|
});
|
|
|
|
chat.on("click", ".show-more", function() {
|
|
var self = $(this);
|
|
var id = self.data("id");
|
|
var count = self.next(".messages").children().length;
|
|
socket.emit("more", {
|
|
target: id,
|
|
count: count
|
|
});
|
|
});
|
|
|
|
chat.on("click", ".toggle-button", function() {
|
|
var self = $(this);
|
|
self.parent().next(".toggle-content").toggleClass("show");
|
|
});
|
|
|
|
var windows = $("#windows");
|
|
var forms = $("#sign-in, #connect");
|
|
|
|
windows.on("show", "#sign-in", function() {
|
|
var self = $(this);
|
|
var inputs = self.find("input");
|
|
inputs.each(function() {
|
|
var self = $(this);
|
|
if (self.val() === "") {
|
|
self.focus();
|
|
return false;
|
|
}
|
|
});
|
|
});
|
|
|
|
windows.on("click", ".input", function() {
|
|
$(this).select();
|
|
});
|
|
|
|
forms.on("submit", "form", function(e) {
|
|
e.preventDefault();
|
|
var event = "auth";
|
|
var form = $(this);
|
|
if (form.closest(".window").attr("id") == "connect") {
|
|
event = "conn";
|
|
form.find(".btn")
|
|
.attr("disabled", true)
|
|
.end();
|
|
}
|
|
var values = {};
|
|
$.each(form.serializeArray(), function(i, obj) {
|
|
if (obj.value !== "") {
|
|
values[obj.name] = obj.value;
|
|
}
|
|
});
|
|
if (values.user) {
|
|
$.cookie(
|
|
"user",
|
|
values.user, {
|
|
expires: expire(30)
|
|
}
|
|
);
|
|
}
|
|
socket.emit(
|
|
event, values
|
|
);
|
|
});
|
|
|
|
Mousetrap.bind([
|
|
"command+up",
|
|
"command+down",
|
|
"ctrl+up",
|
|
"ctrl+down"
|
|
], function(e, keys) {
|
|
var channels = sidebar.find(".chan");
|
|
var index = channels.index(channels.filter(".active"));
|
|
|
|
var direction = keys.split("+").pop();
|
|
switch (direction) {
|
|
case "up":
|
|
// Wrap around!
|
|
var upTarget = (channels.length + (index - 1 + channels.length)) % channels.length;
|
|
channels.eq(upTarget).click();
|
|
break;
|
|
|
|
case "down":
|
|
// Wrap aroud!
|
|
var downTarget = (channels.length + (index + 1 + channels.length)) % channels.length;
|
|
channels.eq(downTarget).click();
|
|
break;
|
|
}
|
|
});
|
|
|
|
Mousetrap.bind([
|
|
"command+k",
|
|
"ctrl+l",
|
|
"ctrl+shift+l"
|
|
], function (e) {
|
|
if(e.target === input[0]) {
|
|
clear();
|
|
}
|
|
});
|
|
|
|
function clear() {
|
|
chat.find(".active .messages").empty();
|
|
chat.find(".active .show-more").removeClass("hidden");
|
|
}
|
|
|
|
function complete(word) {
|
|
var words = commands.slice();
|
|
var users = chat.find(".active")
|
|
.find(".names")
|
|
.children()
|
|
.each(function() {
|
|
words.push($(this).text().replace(/[+%@~]/, ""));
|
|
});
|
|
var channels = sidebar.find(".channel")
|
|
.each(function() {
|
|
var chan = $(this).clone().remove("span").text().trim();
|
|
words.push(chan);
|
|
});
|
|
return $.grep(
|
|
words,
|
|
function(w) {
|
|
return !w.toLowerCase().indexOf(word.toLowerCase());
|
|
}
|
|
);
|
|
}
|
|
|
|
function confirmExit() {
|
|
window.onbeforeunload = function() {
|
|
return "Are you sure you want to navigate away from this page?";
|
|
};
|
|
}
|
|
|
|
function refresh() {
|
|
window.onbeforeunload = null;
|
|
location.reload();
|
|
}
|
|
|
|
function expire(days) {
|
|
var date = new Date();
|
|
date.setTime(date.getTime() + ((3600 * 1000 * 24) * days));
|
|
return date;
|
|
}
|
|
|
|
function sortable() {
|
|
sidebar.sortable({
|
|
axis: "y",
|
|
containment: "parent",
|
|
cursor: "grabbing",
|
|
items: ".network",
|
|
handle: ".lobby",
|
|
placeholder: "network-placeholder",
|
|
forcePlaceholderSize: true,
|
|
update: function() {
|
|
var order = [];
|
|
sidebar.find(".network").each(function() {
|
|
var id = $(this).data("id");
|
|
order.push(id);
|
|
});
|
|
socket.emit(
|
|
"sort", {
|
|
type: "networks",
|
|
order: order
|
|
}
|
|
);
|
|
}
|
|
});
|
|
sidebar.find(".network").sortable({
|
|
axis: "y",
|
|
containment: "parent",
|
|
cursor: "grabbing",
|
|
items: ".chan:not(.lobby)",
|
|
placeholder: "chan-placeholder",
|
|
forcePlaceholderSize: true,
|
|
update: function(e, ui) {
|
|
var order = [];
|
|
var network = ui.item.parent();
|
|
network.find(".chan").each(function() {
|
|
var id = $(this).data("id");
|
|
order.push(id);
|
|
});
|
|
socket.emit(
|
|
"sort", {
|
|
type: "channels",
|
|
target: network.data("id"),
|
|
order: order
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
function setNick(nick) {
|
|
var width = $("#nick")
|
|
.html(nick + ":")
|
|
.width();
|
|
if (width) {
|
|
width += 31;
|
|
input.css("padding-left", width);
|
|
}
|
|
}
|
|
|
|
document.addEventListener(
|
|
"visibilitychange",
|
|
function() {
|
|
if (sidebar.find(".highlight").length === 0) {
|
|
favico.badge("");
|
|
}
|
|
}
|
|
);
|
|
});
|