Merge pull request #1175 from thelounge/yamanickill/socket-modules
Add modules for socket events
This commit is contained in:
commit
0b85582744
@ -14,13 +14,14 @@ const emojiMap = require("./libs/simplemap.json");
|
|||||||
require("./libs/jquery/inputhistory");
|
require("./libs/jquery/inputhistory");
|
||||||
require("./libs/jquery/stickyscroll");
|
require("./libs/jquery/stickyscroll");
|
||||||
require("./libs/jquery/tabcomplete");
|
require("./libs/jquery/tabcomplete");
|
||||||
const helpers_parse = require("./libs/handlebars/parse");
|
|
||||||
const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber");
|
const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber");
|
||||||
const slideoutMenu = require("./libs/slideout");
|
const slideoutMenu = require("./libs/slideout");
|
||||||
const templates = require("../views");
|
const templates = require("../views");
|
||||||
const socket = require("./socket");
|
const socket = require("./socket");
|
||||||
|
require("./socket-events");
|
||||||
const constants = require("./constants");
|
const constants = require("./constants");
|
||||||
const storage = require("./localStorage");
|
const storage = require("./localStorage");
|
||||||
|
const utils = require("./utils");
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
var sidebar = $("#sidebar, #footer");
|
var sidebar = $("#sidebar, #footer");
|
||||||
@ -28,8 +29,6 @@ $(function() {
|
|||||||
|
|
||||||
$(document.body).data("app-name", document.title);
|
$(document.body).data("app-name", document.title);
|
||||||
|
|
||||||
var ignoreSortSync = false;
|
|
||||||
|
|
||||||
var pop;
|
var pop;
|
||||||
try {
|
try {
|
||||||
pop = new Audio();
|
pop = new Audio();
|
||||||
@ -44,8 +43,6 @@ $(function() {
|
|||||||
pop.play();
|
pop.play();
|
||||||
});
|
});
|
||||||
|
|
||||||
var favicon = $("#favicon");
|
|
||||||
|
|
||||||
// Autocompletion Strategies
|
// Autocompletion Strategies
|
||||||
|
|
||||||
const emojiStrategy = {
|
const emojiStrategy = {
|
||||||
@ -152,507 +149,6 @@ $(function() {
|
|||||||
index: 2
|
index: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.on("auth", function(data) {
|
|
||||||
var login = $("#sign-in");
|
|
||||||
var token;
|
|
||||||
|
|
||||||
login.find(".btn").prop("disabled", false);
|
|
||||||
|
|
||||||
if (!data.success) {
|
|
||||||
storage.remove("token");
|
|
||||||
|
|
||||||
var error = login.find(".error");
|
|
||||||
error.show().closest("form").one("submit", function() {
|
|
||||||
error.hide();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
token = storage.get("token");
|
|
||||||
if (token) {
|
|
||||||
$("#loading-page-message").text("Authorizing…");
|
|
||||||
socket.emit("auth", {token: token});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var input = login.find("input[name='user']");
|
|
||||||
if (input.val() === "") {
|
|
||||||
input.val(storage.get("user") || "");
|
|
||||||
}
|
|
||||||
if (token) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sidebar.find(".sign-in")
|
|
||||||
.trigger("click", {
|
|
||||||
pushState: false,
|
|
||||||
})
|
|
||||||
.end()
|
|
||||||
.find(".networks")
|
|
||||||
.html("")
|
|
||||||
.next()
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("change-password", function(data) {
|
|
||||||
var passwordForm = $("#change-password");
|
|
||||||
if (data.error || data.success) {
|
|
||||||
var message = data.success ? data.success : data.error;
|
|
||||||
var feedback = passwordForm.find(".feedback");
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
feedback.addClass("success").removeClass("error");
|
|
||||||
} else {
|
|
||||||
feedback.addClass("error").removeClass("success");
|
|
||||||
}
|
|
||||||
|
|
||||||
feedback.text(message).show();
|
|
||||||
feedback.closest("form").one("submit", function() {
|
|
||||||
feedback.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.token && storage.get("token") !== null) {
|
|
||||||
storage.set("token", data.token);
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordForm
|
|
||||||
.find("input")
|
|
||||||
.val("")
|
|
||||||
.end()
|
|
||||||
.find(".btn")
|
|
||||||
.prop("disabled", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("init", function(data) {
|
|
||||||
$("#loading-page-message").text("Rendering…");
|
|
||||||
|
|
||||||
if (data.networks.length === 0) {
|
|
||||||
$("#footer").find(".connect").trigger("click", {
|
|
||||||
pushState: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
renderNetworks(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.token && $("#sign-in-remember").is(":checked")) {
|
|
||||||
storage.set("token", data.token);
|
|
||||||
} else {
|
|
||||||
storage.remove("token");
|
|
||||||
}
|
|
||||||
|
|
||||||
$("body").removeClass("signed-out");
|
|
||||||
$("#loading").remove();
|
|
||||||
$("#sign-in").remove();
|
|
||||||
|
|
||||||
var id = data.active;
|
|
||||||
var target = sidebar.find("[data-id='" + id + "']").trigger("click", {
|
|
||||||
replaceHistory: true
|
|
||||||
});
|
|
||||||
if (target.length === 0) {
|
|
||||||
var first = sidebar.find(".chan")
|
|
||||||
.eq(0)
|
|
||||||
.trigger("click");
|
|
||||||
if (first.length === 0) {
|
|
||||||
$("#footer").find(".connect").trigger("click", {
|
|
||||||
pushState: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("open", function(id) {
|
|
||||||
// Another client opened the channel, clear the unread counter
|
|
||||||
sidebar.find(".chan[data-id='" + id + "'] .badge")
|
|
||||||
.removeClass("highlight")
|
|
||||||
.empty();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("join", function(data) {
|
|
||||||
var id = data.network;
|
|
||||||
var network = sidebar.find("#network-" + id);
|
|
||||||
network.append(
|
|
||||||
templates.chan({
|
|
||||||
channels: [data.chan]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
chat.append(
|
|
||||||
templates.chat({
|
|
||||||
channels: [data.chan]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
renderChannel(data.chan);
|
|
||||||
|
|
||||||
// Queries do not automatically focus, unless the user did a whois
|
|
||||||
if (data.chan.type === "query" && !data.shouldOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sidebar.find(".chan")
|
|
||||||
.sort(function(a, b) {
|
|
||||||
return $(a).data("id") - $(b).data("id");
|
|
||||||
})
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
function buildChatMessage(data) {
|
|
||||||
var type = data.msg.type;
|
|
||||||
var target = "#chan-" + data.chan;
|
|
||||||
if (type === "error") {
|
|
||||||
target = "#chan-" + chat.find(".active").data("id");
|
|
||||||
}
|
|
||||||
|
|
||||||
var chan = chat.find(target);
|
|
||||||
var template = "msg";
|
|
||||||
|
|
||||||
if (!data.msg.highlight && !data.msg.self && (type === "message" || type === "notice") && options.highlights.some(function(h) {
|
|
||||||
return data.msg.text.toLocaleLowerCase().indexOf(h.toLocaleLowerCase()) > -1;
|
|
||||||
})) {
|
|
||||||
data.msg.highlight = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([
|
|
||||||
"invite",
|
|
||||||
"join",
|
|
||||||
"mode",
|
|
||||||
"kick",
|
|
||||||
"nick",
|
|
||||||
"part",
|
|
||||||
"quit",
|
|
||||||
"topic",
|
|
||||||
"topic_set_by",
|
|
||||||
"action",
|
|
||||||
"whois",
|
|
||||||
"ctcp",
|
|
||||||
"channel_list",
|
|
||||||
"ban_list",
|
|
||||||
].indexOf(type) !== -1) {
|
|
||||||
template = "msg_action";
|
|
||||||
} else if (type === "unhandled") {
|
|
||||||
template = "msg_unhandled";
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg = $(templates[template](data.msg));
|
|
||||||
var text = msg.find(".text");
|
|
||||||
|
|
||||||
if (template === "msg_action") {
|
|
||||||
text.html(templates.actions[type](data.msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((type === "message" || type === "action") && chan.hasClass("channel")) {
|
|
||||||
var nicks = chan.find(".users").data("nicks");
|
|
||||||
if (nicks) {
|
|
||||||
var find = nicks.indexOf(data.msg.from);
|
|
||||||
if (find !== -1) {
|
|
||||||
nicks.splice(find, 1);
|
|
||||||
nicks.unshift(data.msg.from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildChannelMessages(channel, messages) {
|
|
||||||
return messages.reduce(function(docFragment, message) {
|
|
||||||
docFragment.append(buildChatMessage({
|
|
||||||
chan: channel,
|
|
||||||
msg: message
|
|
||||||
}));
|
|
||||||
return docFragment;
|
|
||||||
}, $(document.createDocumentFragment()));
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderChannel(data) {
|
|
||||||
renderChannelMessages(data);
|
|
||||||
|
|
||||||
if (data.type === "channel") {
|
|
||||||
renderChannelUsers(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderChannelMessages(data) {
|
|
||||||
var documentFragment = buildChannelMessages(data.id, data.messages);
|
|
||||||
var channel = chat.find("#chan-" + data.id + " .messages").append(documentFragment);
|
|
||||||
|
|
||||||
if (data.firstUnread > 0) {
|
|
||||||
var first = channel.find("#msg-" + data.firstUnread);
|
|
||||||
|
|
||||||
// TODO: If the message is far off in the history, we still need to append the marker into DOM
|
|
||||||
if (!first.length) {
|
|
||||||
channel.prepend(templates.unread_marker());
|
|
||||||
} else {
|
|
||||||
first.before(templates.unread_marker());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.append(templates.unread_marker());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type !== "lobby") {
|
|
||||||
var lastDate;
|
|
||||||
$(chat.find("#chan-" + data.id + " .messages .msg[data-time]")).each(function() {
|
|
||||||
var msg = $(this);
|
|
||||||
var msgDate = new Date(msg.attr("data-time"));
|
|
||||||
|
|
||||||
// Top-most message in a channel
|
|
||||||
if (!lastDate) {
|
|
||||||
lastDate = msgDate;
|
|
||||||
msg.before(templates.date_marker({msgDate: msgDate}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastDate.toDateString() !== msgDate.toDateString()) {
|
|
||||||
msg.before(templates.date_marker({msgDate: msgDate}));
|
|
||||||
}
|
|
||||||
|
|
||||||
lastDate = msgDate;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderChannelUsers(data) {
|
|
||||||
var users = chat.find("#chan-" + data.id).find(".users");
|
|
||||||
var nicks = users.data("nicks") || [];
|
|
||||||
var i, oldSortOrder = {};
|
|
||||||
|
|
||||||
for (i in nicks) {
|
|
||||||
oldSortOrder[nicks[i]] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
nicks = [];
|
|
||||||
|
|
||||||
for (i in data.users) {
|
|
||||||
nicks.push(data.users[i].nick);
|
|
||||||
}
|
|
||||||
|
|
||||||
nicks = nicks.sort(function(a, b) {
|
|
||||||
return (oldSortOrder[a] || Number.MAX_VALUE) - (oldSortOrder[b] || Number.MAX_VALUE);
|
|
||||||
});
|
|
||||||
|
|
||||||
const search = users
|
|
||||||
.find(".search")
|
|
||||||
.attr("placeholder", nicks.length + " " + (nicks.length === 1 ? "user" : "users"));
|
|
||||||
|
|
||||||
users
|
|
||||||
.data("nicks", nicks)
|
|
||||||
.find(".names-original")
|
|
||||||
.html(templates.user(data));
|
|
||||||
|
|
||||||
// Refresh user search
|
|
||||||
if (search.val().length) {
|
|
||||||
search.trigger("input");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderNetworks(data) {
|
|
||||||
sidebar.find(".empty").hide();
|
|
||||||
sidebar.find(".networks").append(
|
|
||||||
templates.network({
|
|
||||||
networks: data.networks
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
var channels = $.map(data.networks, function(n) {
|
|
||||||
return n.channels;
|
|
||||||
});
|
|
||||||
chat.append(
|
|
||||||
templates.chat({
|
|
||||||
channels: channels
|
|
||||||
})
|
|
||||||
);
|
|
||||||
channels.forEach(renderChannel);
|
|
||||||
|
|
||||||
confirmExit();
|
|
||||||
sortable();
|
|
||||||
|
|
||||||
if (sidebar.find(".highlight").length) {
|
|
||||||
toggleNotificationMarkers(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on("msg", function(data) {
|
|
||||||
var msg = buildChatMessage(data);
|
|
||||||
var target = "#chan-" + data.chan;
|
|
||||||
var container = chat.find(target + " .messages");
|
|
||||||
|
|
||||||
if (data.msg.type === "channel_list" || data.msg.type === "ban_list") {
|
|
||||||
$(container).empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if date changed
|
|
||||||
var prevMsg = $(container.find(".msg")).last();
|
|
||||||
var prevMsgTime = new Date(prevMsg.attr("data-time"));
|
|
||||||
var msgTime = new Date(msg.attr("data-time"));
|
|
||||||
|
|
||||||
// It's the first message in a channel/query
|
|
||||||
if (prevMsg.length === 0) {
|
|
||||||
container.append(templates.date_marker({msgDate: msgTime}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevMsgTime.toDateString() !== msgTime.toDateString()) {
|
|
||||||
prevMsg.after(templates.date_marker({msgDate: msgTime}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add message to the container
|
|
||||||
container
|
|
||||||
.append(msg)
|
|
||||||
.trigger("msg", [
|
|
||||||
target,
|
|
||||||
data
|
|
||||||
]);
|
|
||||||
var lastVisible = container.find("div:visible").last();
|
|
||||||
if (data.msg.self
|
|
||||||
|| lastVisible.hasClass("unread-marker")
|
|
||||||
|| (lastVisible.hasClass("date-marker")
|
|
||||||
&& lastVisible.prev().hasClass("unread-marker"))) {
|
|
||||||
container
|
|
||||||
.find(".unread-marker")
|
|
||||||
.appendTo(container);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("more", function(data) {
|
|
||||||
var documentFragment = buildChannelMessages(data.chan, data.messages);
|
|
||||||
var chan = chat
|
|
||||||
.find("#chan-" + data.chan)
|
|
||||||
.find(".messages");
|
|
||||||
|
|
||||||
// get the scrollable wrapper around messages
|
|
||||||
var scrollable = chan.closest(".chat");
|
|
||||||
var heightOld = chan.height();
|
|
||||||
|
|
||||||
// Remove the date-change marker we put at the top, because it may
|
|
||||||
// not actually be a date change now
|
|
||||||
var children = $(chan).children();
|
|
||||||
if (children.eq(0).hasClass("date-marker-container")) { // Check top most child
|
|
||||||
children.eq(0).remove();
|
|
||||||
} else if (children.eq(1).hasClass("date-marker-container")) {
|
|
||||||
// The unread-marker could be at index 0, which will cause the date-marker to become "stuck"
|
|
||||||
children.eq(1).remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the older messages
|
|
||||||
chan.prepend(documentFragment).end();
|
|
||||||
|
|
||||||
// restore scroll position
|
|
||||||
var position = chan.height() - heightOld;
|
|
||||||
scrollable.scrollTop(position);
|
|
||||||
|
|
||||||
if (data.messages.length !== 100) {
|
|
||||||
scrollable.find(".show-more").removeClass("show");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date change detect
|
|
||||||
// Have to use data instaid of the documentFragment because it's being weird
|
|
||||||
var lastDate;
|
|
||||||
$(data.messages).each(function() {
|
|
||||||
var msgData = this;
|
|
||||||
var msgDate = new Date(msgData.time);
|
|
||||||
var msg = $(chat.find("#chan-" + data.chan + " .messages #msg-" + msgData.id));
|
|
||||||
|
|
||||||
// Top-most message in a channel
|
|
||||||
if (!lastDate) {
|
|
||||||
lastDate = msgDate;
|
|
||||||
msg.before(templates.date_marker({msgDate: msgDate}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastDate.toDateString() !== msgDate.toDateString()) {
|
|
||||||
msg.before(templates.date_marker({msgDate: msgDate}));
|
|
||||||
}
|
|
||||||
|
|
||||||
lastDate = msgDate;
|
|
||||||
});
|
|
||||||
|
|
||||||
scrollable.find(".show-more-button").prop("disabled", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("network", function(data) {
|
|
||||||
renderNetworks(data);
|
|
||||||
|
|
||||||
sidebar.find(".chan")
|
|
||||||
.last()
|
|
||||||
.trigger("click");
|
|
||||||
|
|
||||||
$("#connect")
|
|
||||||
.find(".btn")
|
|
||||||
.prop("disabled", false)
|
|
||||||
.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("network_changed", function(data) {
|
|
||||||
sidebar.find("#network-" + data.network).data("options", data.serverOptions);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 chanMenuItem = sidebar.find(".chan[data-id='" + data.chan + "']");
|
|
||||||
|
|
||||||
// When parting from the active channel/query, jump to the network's lobby
|
|
||||||
if (chanMenuItem.hasClass("active")) {
|
|
||||||
chanMenuItem.parent(".network").find(".lobby").click();
|
|
||||||
}
|
|
||||||
|
|
||||||
chanMenuItem.remove();
|
|
||||||
$("#chan-" + data.chan).remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
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) {
|
|
||||||
var toggle = $("#toggle-" + data.id);
|
|
||||||
toggle.parent().after(templates.toggle({toggle: data}));
|
|
||||||
switch (data.type) {
|
|
||||||
case "link":
|
|
||||||
if (options.links) {
|
|
||||||
toggle.click();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "image":
|
|
||||||
if (options.thumbnails) {
|
|
||||||
toggle.click();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("topic", function(data) {
|
|
||||||
var topic = $("#chan-" + data.chan).find(".header .topic");
|
|
||||||
topic.html(helpers_parse(data.topic));
|
|
||||||
// .attr() is safe escape-wise but consider the capabilities of the attribute
|
|
||||||
topic.attr("title", data.topic);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("users", function(data) {
|
|
||||||
var chan = chat.find("#chan-" + data.chan);
|
|
||||||
|
|
||||||
if (chan.hasClass("active")) {
|
|
||||||
socket.emit("names", {
|
|
||||||
target: data.chan
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
chan.data("needsNamesRefresh", true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("names", renderChannelUsers);
|
|
||||||
|
|
||||||
var options = require("./options");
|
var options = require("./options");
|
||||||
|
|
||||||
var windows = $("#windows");
|
var windows = $("#windows");
|
||||||
@ -811,15 +307,9 @@ $(function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triggering click event opens the virtual keyboard on mobile
|
|
||||||
// This can only be called from another interactive event (e.g. button click)
|
|
||||||
var forceFocus = function() {
|
|
||||||
input.trigger("click").focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
$("#form").on("submit", function(e) {
|
$("#form").on("submit", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
forceFocus();
|
utils.forceFocus();
|
||||||
var text = input.val();
|
var text = input.val();
|
||||||
|
|
||||||
if (text.length === 0) {
|
if (text.length === 0) {
|
||||||
@ -830,7 +320,7 @@ $(function() {
|
|||||||
resetInputHeight(input.get(0));
|
resetInputHeight(input.get(0));
|
||||||
|
|
||||||
if (text.indexOf("/clear") === 0) {
|
if (text.indexOf("/clear") === 0) {
|
||||||
clear();
|
utils.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -853,7 +343,7 @@ $(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$("button#set-nick").on("click", function() {
|
$("button#set-nick").on("click", function() {
|
||||||
toggleNickEditor(true);
|
utils.toggleNickEditor(true);
|
||||||
|
|
||||||
// Selects existing nick in the editable text field
|
// Selects existing nick in the editable text field
|
||||||
var element = document.querySelector("#nick-value");
|
var element = document.querySelector("#nick-value");
|
||||||
@ -868,11 +358,6 @@ $(function() {
|
|||||||
$("button#cancel-nick").on("click", cancelNick);
|
$("button#cancel-nick").on("click", cancelNick);
|
||||||
$("button#submit-nick").on("click", submitNick);
|
$("button#submit-nick").on("click", submitNick);
|
||||||
|
|
||||||
function toggleNickEditor(toggle) {
|
|
||||||
$("#nick").toggleClass("editable", toggle);
|
|
||||||
$("#nick-value").attr("contenteditable", toggle);
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitNick() {
|
function submitNick() {
|
||||||
var newNick = $("#nick-value").text().trim();
|
var newNick = $("#nick-value").text().trim();
|
||||||
|
|
||||||
@ -881,7 +366,7 @@ $(function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleNickEditor(false);
|
utils.toggleNickEditor(false);
|
||||||
|
|
||||||
socket.emit("input", {
|
socket.emit("input", {
|
||||||
target: chat.data("id"),
|
target: chat.data("id"),
|
||||||
@ -890,7 +375,7 @@ $(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancelNick() {
|
function cancelNick() {
|
||||||
setNick(sidebar.find(".chan.active").closest(".network").data("nick"));
|
utils.setNick(sidebar.find(".chan.active").closest(".network").data("nick"));
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#nick-value").keypress(function(e) {
|
$("#nick-value").keypress(function(e) {
|
||||||
@ -993,7 +478,7 @@ $(function() {
|
|||||||
.empty();
|
.empty();
|
||||||
|
|
||||||
if (sidebar.find(".highlight").length === 0) {
|
if (sidebar.find(".highlight").length === 0) {
|
||||||
toggleNotificationMarkers(false);
|
utils.toggleNotificationMarkers(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebarSlide.toggle(false);
|
sidebarSlide.toggle(false);
|
||||||
@ -1031,7 +516,7 @@ $(function() {
|
|||||||
|
|
||||||
if (self.hasClass("chan")) {
|
if (self.hasClass("chan")) {
|
||||||
$("#chat-container").addClass("active");
|
$("#chat-container").addClass("active");
|
||||||
setNick(self.closest(".network").data("nick"));
|
utils.setNick(self.closest(".network").data("nick"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var chanChat = chan.find(".chat");
|
var chanChat = chan.find(".chat");
|
||||||
@ -1133,7 +618,7 @@ $(function() {
|
|||||||
// On mobile, sounds can not be played without user interaction.
|
// On mobile, sounds can not be played without user interaction.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggleNotificationMarkers(true);
|
utils.toggleNotificationMarkers(true);
|
||||||
|
|
||||||
if (options.desktopNotifications && Notification.permission === "granted") {
|
if (options.desktopNotifications && Notification.permission === "granted") {
|
||||||
var title;
|
var title;
|
||||||
@ -1359,7 +844,7 @@ $(function() {
|
|||||||
"ctrl+shift+l"
|
"ctrl+shift+l"
|
||||||
], function(e) {
|
], function(e) {
|
||||||
if (e.target === input[0]) {
|
if (e.target === input[0]) {
|
||||||
clear();
|
utils.clear();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1422,12 +907,6 @@ $(function() {
|
|||||||
});
|
});
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
|
|
||||||
function clear() {
|
|
||||||
chat.find(".active")
|
|
||||||
.find(".show-more").addClass("show").end()
|
|
||||||
.find(".messages .msg, .date-marker-container").remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
function completeNicks(word) {
|
function completeNicks(word) {
|
||||||
const users = chat.find(".active .users");
|
const users = chat.find(".active .users");
|
||||||
|
|
||||||
@ -1470,141 +949,9 @@ $(function() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmExit() {
|
|
||||||
if ($("body").hasClass("public")) {
|
|
||||||
window.onbeforeunload = function() {
|
|
||||||
return "Are you sure you want to navigate away from this page?";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortable() {
|
|
||||||
sidebar.find(".networks").sortable({
|
|
||||||
axis: "y",
|
|
||||||
containment: "parent",
|
|
||||||
cursor: "move",
|
|
||||||
distance: 12,
|
|
||||||
items: ".network",
|
|
||||||
handle: ".lobby",
|
|
||||||
placeholder: "network-placeholder",
|
|
||||||
forcePlaceholderSize: true,
|
|
||||||
tolerance: "pointer", // Use the pointer to figure out where the network is in the list
|
|
||||||
|
|
||||||
update: function() {
|
|
||||||
var order = [];
|
|
||||||
sidebar.find(".network").each(function() {
|
|
||||||
var id = $(this).data("id");
|
|
||||||
order.push(id);
|
|
||||||
});
|
|
||||||
socket.emit(
|
|
||||||
"sort", {
|
|
||||||
type: "networks",
|
|
||||||
order: order
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ignoreSortSync = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sidebar.find(".network").sortable({
|
|
||||||
axis: "y",
|
|
||||||
containment: "parent",
|
|
||||||
cursor: "move",
|
|
||||||
distance: 12,
|
|
||||||
items: ".chan:not(.lobby)",
|
|
||||||
placeholder: "chan-placeholder",
|
|
||||||
forcePlaceholderSize: true,
|
|
||||||
tolerance: "pointer", // Use the pointer to figure out where the channel is in the list
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ignoreSortSync = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on("sync_sort", function(data) {
|
|
||||||
// Syncs the order of channels or networks when they are reordered
|
|
||||||
if (ignoreSortSync) {
|
|
||||||
ignoreSortSync = false;
|
|
||||||
return; // Ignore syncing because we 'caused' it
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = data.type;
|
|
||||||
var order = data.order;
|
|
||||||
|
|
||||||
if (type === "networks") {
|
|
||||||
var container = $(".networks");
|
|
||||||
|
|
||||||
$.each(order, function(index, value) {
|
|
||||||
var position = $(container.children()[index]);
|
|
||||||
|
|
||||||
if (position.data("id") === value) { // Network in correct place
|
|
||||||
return true; // No point in continuing
|
|
||||||
}
|
|
||||||
|
|
||||||
var network = container.find("#network-" + value);
|
|
||||||
|
|
||||||
$(network).insertBefore(position);
|
|
||||||
});
|
|
||||||
} else if (type === "channels") {
|
|
||||||
var network = $("#network-" + data.target);
|
|
||||||
|
|
||||||
$.each(order, function(index, value) {
|
|
||||||
if (index === 0) { // Shouldn't attempt to move lobby
|
|
||||||
return true; // same as `continue` -> skip to next item
|
|
||||||
}
|
|
||||||
|
|
||||||
var position = $(network.children()[index]); // Target channel at position
|
|
||||||
|
|
||||||
if (position.data("id") === value) { // Channel in correct place
|
|
||||||
return true; // No point in continuing
|
|
||||||
}
|
|
||||||
|
|
||||||
var channel = network.find(".chan[data-id=" + value + "]"); // Channel at position
|
|
||||||
|
|
||||||
$(channel).insertBefore(position);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function setNick(nick) {
|
|
||||||
// Closes the nick editor when canceling, changing channel, or when a nick
|
|
||||||
// is set in a different tab / browser / device.
|
|
||||||
toggleNickEditor(false);
|
|
||||||
|
|
||||||
$("#nick-value").text(nick);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleNotificationMarkers(newState) {
|
|
||||||
// Toggles the favicon to red when there are unread notifications
|
|
||||||
if (favicon.data("toggled") !== newState) {
|
|
||||||
var old = favicon.attr("href");
|
|
||||||
favicon.attr("href", favicon.data("other"));
|
|
||||||
favicon.data("other", old);
|
|
||||||
favicon.data("toggled", newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggles a dot on the menu icon when there are unread notifications
|
|
||||||
$("#viewport .lt").toggleClass("notified", newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).on("visibilitychange focus click", () => {
|
$(document).on("visibilitychange focus click", () => {
|
||||||
if (sidebar.find(".highlight").length === 0) {
|
if (sidebar.find(".highlight").length === 0) {
|
||||||
toggleNotificationMarkers(false);
|
utils.toggleNotificationMarkers(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const $ = require("jquery");
|
const $ = require("jquery");
|
||||||
const settings = $("#settings");
|
const settings = $("#settings");
|
||||||
const userStyles = $("#user-specified-css");
|
const userStyles = $("#user-specified-css");
|
||||||
|
193
client/js/render.js
Normal file
193
client/js/render.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const templates = require("../views");
|
||||||
|
const options = require("./options");
|
||||||
|
const utils = require("./utils");
|
||||||
|
const sorting = require("./sorting");
|
||||||
|
|
||||||
|
const chat = $("#chat");
|
||||||
|
const sidebar = $("#sidebar");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildChannelMessages,
|
||||||
|
buildChatMessage,
|
||||||
|
renderChannel,
|
||||||
|
renderChannelMessages,
|
||||||
|
renderChannelUsers,
|
||||||
|
renderNetworks
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildChannelMessages(channel, messages) {
|
||||||
|
return messages.reduce(function(docFragment, message) {
|
||||||
|
docFragment.append(buildChatMessage({
|
||||||
|
chan: channel,
|
||||||
|
msg: message
|
||||||
|
}));
|
||||||
|
return docFragment;
|
||||||
|
}, $(document.createDocumentFragment()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildChatMessage(data) {
|
||||||
|
const type = data.msg.type;
|
||||||
|
let target = "#chan-" + data.chan;
|
||||||
|
if (type === "error") {
|
||||||
|
target = "#chan-" + chat.find(".active").data("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
const chan = chat.find(target);
|
||||||
|
let template = "msg";
|
||||||
|
|
||||||
|
if (!data.msg.highlight && !data.msg.self && (type === "message" || type === "notice") && options.highlights.some(function(h) {
|
||||||
|
return data.msg.text.toLocaleLowerCase().indexOf(h.toLocaleLowerCase()) > -1;
|
||||||
|
})) {
|
||||||
|
data.msg.highlight = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([
|
||||||
|
"invite",
|
||||||
|
"join",
|
||||||
|
"mode",
|
||||||
|
"kick",
|
||||||
|
"nick",
|
||||||
|
"part",
|
||||||
|
"quit",
|
||||||
|
"topic",
|
||||||
|
"topic_set_by",
|
||||||
|
"action",
|
||||||
|
"whois",
|
||||||
|
"ctcp",
|
||||||
|
"channel_list",
|
||||||
|
"ban_list",
|
||||||
|
].indexOf(type) !== -1) {
|
||||||
|
template = "msg_action";
|
||||||
|
} else if (type === "unhandled") {
|
||||||
|
template = "msg_unhandled";
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = $(templates[template](data.msg));
|
||||||
|
const text = msg.find(".text");
|
||||||
|
|
||||||
|
if (template === "msg_action") {
|
||||||
|
text.html(templates.actions[type](data.msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((type === "message" || type === "action") && chan.hasClass("channel")) {
|
||||||
|
const nicks = chan.find(".users").data("nicks");
|
||||||
|
if (nicks) {
|
||||||
|
const find = nicks.indexOf(data.msg.from);
|
||||||
|
if (find !== -1) {
|
||||||
|
nicks.splice(find, 1);
|
||||||
|
nicks.unshift(data.msg.from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderChannel(data) {
|
||||||
|
renderChannelMessages(data);
|
||||||
|
|
||||||
|
if (data.type === "channel") {
|
||||||
|
renderChannelUsers(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderChannelMessages(data) {
|
||||||
|
const documentFragment = buildChannelMessages(data.id, data.messages);
|
||||||
|
const channel = chat.find("#chan-" + data.id + " .messages").append(documentFragment);
|
||||||
|
|
||||||
|
if (data.firstUnread > 0) {
|
||||||
|
const first = channel.find("#msg-" + data.firstUnread);
|
||||||
|
|
||||||
|
// TODO: If the message is far off in the history, we still need to append the marker into DOM
|
||||||
|
if (!first.length) {
|
||||||
|
channel.prepend(templates.unread_marker());
|
||||||
|
} else {
|
||||||
|
first.before(templates.unread_marker());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.append(templates.unread_marker());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type !== "lobby") {
|
||||||
|
let lastDate;
|
||||||
|
$(chat.find("#chan-" + data.id + " .messages .msg[data-time]")).each(function() {
|
||||||
|
const msg = $(this);
|
||||||
|
const msgDate = new Date(msg.attr("data-time"));
|
||||||
|
|
||||||
|
// Top-most message in a channel
|
||||||
|
if (!lastDate) {
|
||||||
|
lastDate = msgDate;
|
||||||
|
msg.before(templates.date_marker({msgDate: msgDate}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDate.toDateString() !== msgDate.toDateString()) {
|
||||||
|
msg.before(templates.date_marker({msgDate: msgDate}));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastDate = msgDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderChannelUsers(data) {
|
||||||
|
const users = chat.find("#chan-" + data.id).find(".users");
|
||||||
|
let nicks = users.data("nicks") || [];
|
||||||
|
const oldSortOrder = {};
|
||||||
|
|
||||||
|
for (const i in nicks) {
|
||||||
|
oldSortOrder[nicks[i]] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
nicks = [];
|
||||||
|
|
||||||
|
for (const i in data.users) {
|
||||||
|
nicks.push(data.users[i].nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
nicks = nicks.sort(function(a, b) {
|
||||||
|
return (oldSortOrder[a] || Number.MAX_VALUE) - (oldSortOrder[b] || Number.MAX_VALUE);
|
||||||
|
});
|
||||||
|
|
||||||
|
const search = users
|
||||||
|
.find(".search")
|
||||||
|
.attr("placeholder", nicks.length + " " + (nicks.length === 1 ? "user" : "users"));
|
||||||
|
|
||||||
|
users
|
||||||
|
.data("nicks", nicks)
|
||||||
|
.find(".names-original")
|
||||||
|
.html(templates.user(data));
|
||||||
|
|
||||||
|
// Refresh user search
|
||||||
|
if (search.val().length) {
|
||||||
|
search.trigger("input");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNetworks(data) {
|
||||||
|
sidebar.find(".empty").hide();
|
||||||
|
sidebar.find(".networks").append(
|
||||||
|
templates.network({
|
||||||
|
networks: data.networks
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const channels = $.map(data.networks, function(n) {
|
||||||
|
return n.channels;
|
||||||
|
});
|
||||||
|
chat.append(
|
||||||
|
templates.chat({
|
||||||
|
channels: channels
|
||||||
|
})
|
||||||
|
);
|
||||||
|
channels.forEach(renderChannel);
|
||||||
|
|
||||||
|
utils.confirmExit();
|
||||||
|
sorting();
|
||||||
|
|
||||||
|
if (sidebar.find(".highlight").length) {
|
||||||
|
utils.toggleNotificationMarkers(true);
|
||||||
|
}
|
||||||
|
}
|
44
client/js/socket-events/auth.js
Normal file
44
client/js/socket-events/auth.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const storage = require("../localStorage");
|
||||||
|
|
||||||
|
socket.on("auth", function(data) {
|
||||||
|
const login = $("#sign-in");
|
||||||
|
let token;
|
||||||
|
|
||||||
|
login.find(".btn").prop("disabled", false);
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
storage.remove("token");
|
||||||
|
|
||||||
|
const error = login.find(".error");
|
||||||
|
error.show().closest("form").one("submit", function() {
|
||||||
|
error.hide();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
token = storage.get("token");
|
||||||
|
if (token) {
|
||||||
|
$("#loading-page-message").text("Authorizing…");
|
||||||
|
socket.emit("auth", {token: token});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = login.find("input[name='user']");
|
||||||
|
if (input.val() === "") {
|
||||||
|
input.val(storage.get("user") || "");
|
||||||
|
}
|
||||||
|
if (token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#sidebar, #footer").find(".sign-in")
|
||||||
|
.trigger("click", {
|
||||||
|
pushState: false,
|
||||||
|
})
|
||||||
|
.end()
|
||||||
|
.find(".networks")
|
||||||
|
.html("")
|
||||||
|
.next()
|
||||||
|
.show();
|
||||||
|
});
|
35
client/js/socket-events/change_password.js
Normal file
35
client/js/socket-events/change_password.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const storage = require("../localStorage");
|
||||||
|
|
||||||
|
socket.on("change-password", function(data) {
|
||||||
|
const passwordForm = $("#change-password");
|
||||||
|
if (data.error || data.success) {
|
||||||
|
const message = data.success ? data.success : data.error;
|
||||||
|
const feedback = passwordForm.find(".feedback");
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
feedback.addClass("success").removeClass("error");
|
||||||
|
} else {
|
||||||
|
feedback.addClass("error").removeClass("success");
|
||||||
|
}
|
||||||
|
|
||||||
|
feedback.text(message).show();
|
||||||
|
feedback.closest("form").one("submit", function() {
|
||||||
|
feedback.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.token && storage.get("token") !== null) {
|
||||||
|
storage.set("token", data.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordForm
|
||||||
|
.find("input")
|
||||||
|
.val("")
|
||||||
|
.end()
|
||||||
|
.find(".btn")
|
||||||
|
.prop("disabled", false);
|
||||||
|
});
|
18
client/js/socket-events/index.js
Normal file
18
client/js/socket-events/index.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
require("./auth");
|
||||||
|
require("./change_password");
|
||||||
|
require("./init");
|
||||||
|
require("./join");
|
||||||
|
require("./more");
|
||||||
|
require("./msg");
|
||||||
|
require("./names");
|
||||||
|
require("./network");
|
||||||
|
require("./nick");
|
||||||
|
require("./open");
|
||||||
|
require("./part");
|
||||||
|
require("./quit");
|
||||||
|
require("./sync_sort");
|
||||||
|
require("./toggle");
|
||||||
|
require("./topic");
|
||||||
|
require("./users");
|
44
client/js/socket-events/init.js
Normal file
44
client/js/socket-events/init.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const render = require("../render");
|
||||||
|
const sidebar = $("#sidebar");
|
||||||
|
const storage = require("../localStorage");
|
||||||
|
|
||||||
|
socket.on("init", function(data) {
|
||||||
|
$("#loading-page-message").text("Rendering…");
|
||||||
|
|
||||||
|
if (data.networks.length === 0) {
|
||||||
|
$("#footer").find(".connect").trigger("click", {
|
||||||
|
pushState: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
render.renderNetworks(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.token && $("#sign-in-remember").is(":checked")) {
|
||||||
|
storage.set("token", data.token);
|
||||||
|
} else {
|
||||||
|
storage.remove("token");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("body").removeClass("signed-out");
|
||||||
|
$("#loading").remove();
|
||||||
|
$("#sign-in").remove();
|
||||||
|
|
||||||
|
const id = data.active;
|
||||||
|
const target = sidebar.find("[data-id='" + id + "']").trigger("click", {
|
||||||
|
replaceHistory: true
|
||||||
|
});
|
||||||
|
if (target.length === 0) {
|
||||||
|
const first = sidebar.find(".chan")
|
||||||
|
.eq(0)
|
||||||
|
.trigger("click");
|
||||||
|
if (first.length === 0) {
|
||||||
|
$("#footer").find(".connect").trigger("click", {
|
||||||
|
pushState: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
36
client/js/socket-events/join.js
Normal file
36
client/js/socket-events/join.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const render = require("../render");
|
||||||
|
const chat = $("#chat");
|
||||||
|
const templates = require("../../views");
|
||||||
|
const sidebar = $("#sidebar");
|
||||||
|
|
||||||
|
socket.on("join", function(data) {
|
||||||
|
const id = data.network;
|
||||||
|
const network = sidebar.find("#network-" + id);
|
||||||
|
network.append(
|
||||||
|
templates.chan({
|
||||||
|
channels: [data.chan]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
chat.append(
|
||||||
|
templates.chat({
|
||||||
|
channels: [data.chan]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
render.renderChannel(data.chan);
|
||||||
|
|
||||||
|
// Queries do not automatically focus, unless the user did a whois
|
||||||
|
if (data.chan.type === "query" && !data.shouldOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebar.find(".chan")
|
||||||
|
.sort(function(a, b) {
|
||||||
|
return $(a).data("id") - $(b).data("id");
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
.click();
|
||||||
|
});
|
62
client/js/socket-events/more.js
Normal file
62
client/js/socket-events/more.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const render = require("../render");
|
||||||
|
const chat = $("#chat");
|
||||||
|
const templates = require("../../views");
|
||||||
|
|
||||||
|
socket.on("more", function(data) {
|
||||||
|
const documentFragment = render.buildChannelMessages(data.chan, data.messages);
|
||||||
|
const chan = chat
|
||||||
|
.find("#chan-" + data.chan)
|
||||||
|
.find(".messages");
|
||||||
|
|
||||||
|
// get the scrollable wrapper around messages
|
||||||
|
const scrollable = chan.closest(".chat");
|
||||||
|
const heightOld = chan.height();
|
||||||
|
|
||||||
|
// Remove the date-change marker we put at the top, because it may
|
||||||
|
// not actually be a date change now
|
||||||
|
const children = $(chan).children();
|
||||||
|
if (children.eq(0).hasClass("date-marker-container")) { // Check top most child
|
||||||
|
children.eq(0).remove();
|
||||||
|
} else if (children.eq(1).hasClass("date-marker-container")) {
|
||||||
|
// The unread-marker could be at index 0, which will cause the date-marker to become "stuck"
|
||||||
|
children.eq(1).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the older messages
|
||||||
|
chan.prepend(documentFragment).end();
|
||||||
|
|
||||||
|
// restore scroll position
|
||||||
|
const position = chan.height() - heightOld;
|
||||||
|
scrollable.scrollTop(position);
|
||||||
|
|
||||||
|
if (data.messages.length !== 100) {
|
||||||
|
scrollable.find(".show-more").removeClass("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date change detect
|
||||||
|
// Have to use data instaid of the documentFragment because it's being weird
|
||||||
|
let lastDate;
|
||||||
|
$(data.messages).each(function() {
|
||||||
|
const msgData = this;
|
||||||
|
const msgDate = new Date(msgData.time);
|
||||||
|
const msg = $(chat.find("#chan-" + data.chan + " .messages #msg-" + msgData.id));
|
||||||
|
|
||||||
|
// Top-most message in a channel
|
||||||
|
if (!lastDate) {
|
||||||
|
lastDate = msgDate;
|
||||||
|
msg.before(templates.date_marker({msgDate: msgDate}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDate.toDateString() !== msgDate.toDateString()) {
|
||||||
|
msg.before(templates.date_marker({msgDate: msgDate}));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastDate = msgDate;
|
||||||
|
});
|
||||||
|
|
||||||
|
scrollable.find(".show-more-button").prop("disabled", false);
|
||||||
|
});
|
49
client/js/socket-events/msg.js
Normal file
49
client/js/socket-events/msg.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const render = require("../render");
|
||||||
|
const chat = $("#chat");
|
||||||
|
const templates = require("../../views");
|
||||||
|
|
||||||
|
socket.on("msg", function(data) {
|
||||||
|
const msg = render.buildChatMessage(data);
|
||||||
|
const target = "#chan-" + data.chan;
|
||||||
|
const container = chat.find(target + " .messages");
|
||||||
|
|
||||||
|
if (data.msg.type === "channel_list" || data.msg.type === "ban_list") {
|
||||||
|
$(container).empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if date changed
|
||||||
|
const prevMsg = $(container.find(".msg")).last();
|
||||||
|
const prevMsgTime = new Date(prevMsg.attr("data-time"));
|
||||||
|
const msgTime = new Date(msg.attr("data-time"));
|
||||||
|
|
||||||
|
// It's the first message in a channel/query
|
||||||
|
if (prevMsg.length === 0) {
|
||||||
|
container.append(templates.date_marker({msgDate: msgTime}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevMsgTime.toDateString() !== msgTime.toDateString()) {
|
||||||
|
prevMsg.after(templates.date_marker({msgDate: msgTime}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add message to the container
|
||||||
|
container
|
||||||
|
.append(msg)
|
||||||
|
.trigger("msg", [
|
||||||
|
target,
|
||||||
|
data
|
||||||
|
]);
|
||||||
|
|
||||||
|
var lastVisible = container.find("div:visible").last();
|
||||||
|
if (data.msg.self
|
||||||
|
|| lastVisible.hasClass("unread-marker")
|
||||||
|
|| (lastVisible.hasClass("date-marker")
|
||||||
|
&& lastVisible.prev().hasClass("unread-marker"))) {
|
||||||
|
container
|
||||||
|
.find(".unread-marker")
|
||||||
|
.appendTo(container);
|
||||||
|
}
|
||||||
|
});
|
6
client/js/socket-events/names.js
Normal file
6
client/js/socket-events/names.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const socket = require("../socket");
|
||||||
|
const render = require("../render");
|
||||||
|
|
||||||
|
socket.on("names", render.renderChannelUsers);
|
24
client/js/socket-events/network.js
Normal file
24
client/js/socket-events/network.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const render = require("../render");
|
||||||
|
const sidebar = $("#sidebar");
|
||||||
|
|
||||||
|
socket.on("network", function(data) {
|
||||||
|
render.renderNetworks(data);
|
||||||
|
|
||||||
|
sidebar.find(".chan")
|
||||||
|
.last()
|
||||||
|
.trigger("click");
|
||||||
|
|
||||||
|
$("#connect")
|
||||||
|
.find(".btn")
|
||||||
|
.prop("disabled", false)
|
||||||
|
.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("network_changed", function(data) {
|
||||||
|
sidebar.find("#network-" + data.network).data("options", data.serverOptions);
|
||||||
|
});
|
||||||
|
|
15
client/js/socket-events/nick.js
Normal file
15
client/js/socket-events/nick.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const utils = require("../utils");
|
||||||
|
const sidebar = $("#sidebar");
|
||||||
|
|
||||||
|
socket.on("nick", function(data) {
|
||||||
|
const id = data.network;
|
||||||
|
const nick = data.nick;
|
||||||
|
const network = sidebar.find("#network-" + id).data("nick", nick);
|
||||||
|
if (network.find(".active").length) {
|
||||||
|
utils.setNick(nick);
|
||||||
|
}
|
||||||
|
});
|
11
client/js/socket-events/open.js
Normal file
11
client/js/socket-events/open.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
|
||||||
|
socket.on("open", function(id) {
|
||||||
|
// Another client opened the channel, clear the unread counter
|
||||||
|
$("#sidebar").find(".chan[data-id='" + id + "'] .badge")
|
||||||
|
.removeClass("highlight")
|
||||||
|
.empty();
|
||||||
|
});
|
17
client/js/socket-events/part.js
Normal file
17
client/js/socket-events/part.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const sidebar = $("#sidebar");
|
||||||
|
|
||||||
|
socket.on("part", function(data) {
|
||||||
|
const chanMenuItem = sidebar.find(".chan[data-id='" + data.chan + "']");
|
||||||
|
|
||||||
|
// When parting from the active channel/query, jump to the network's lobby
|
||||||
|
if (chanMenuItem.hasClass("active")) {
|
||||||
|
chanMenuItem.parent(".network").find(".lobby").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
chanMenuItem.remove();
|
||||||
|
$("#chan-" + data.chan).remove();
|
||||||
|
});
|
18
client/js/socket-events/quit.js
Normal file
18
client/js/socket-events/quit.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const sidebar = $("#sidebar");
|
||||||
|
|
||||||
|
socket.on("quit", function(data) {
|
||||||
|
const id = data.network;
|
||||||
|
sidebar.find("#network-" + id)
|
||||||
|
.remove()
|
||||||
|
.end();
|
||||||
|
const chan = sidebar.find(".chan")
|
||||||
|
.eq(0)
|
||||||
|
.trigger("click");
|
||||||
|
if (chan.length === 0) {
|
||||||
|
sidebar.find(".empty").show();
|
||||||
|
}
|
||||||
|
});
|
50
client/js/socket-events/sync_sort.js
Normal file
50
client/js/socket-events/sync_sort.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const options = require("../options");
|
||||||
|
|
||||||
|
socket.on("sync_sort", function(data) {
|
||||||
|
// Syncs the order of channels or networks when they are reordered
|
||||||
|
if (options.ignoreSortSync) {
|
||||||
|
options.ignoreSortSync = false;
|
||||||
|
return; // Ignore syncing because we 'caused' it
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = data.type;
|
||||||
|
const order = data.order;
|
||||||
|
|
||||||
|
if (type === "networks") {
|
||||||
|
const container = $(".networks");
|
||||||
|
|
||||||
|
$.each(order, function(index, value) {
|
||||||
|
const position = $(container.children()[index]);
|
||||||
|
|
||||||
|
if (position.data("id") === value) { // Network in correct place
|
||||||
|
return true; // No point in continuing
|
||||||
|
}
|
||||||
|
|
||||||
|
const network = container.find("#network-" + value);
|
||||||
|
|
||||||
|
$(network).insertBefore(position);
|
||||||
|
});
|
||||||
|
} else if (type === "channels") {
|
||||||
|
const network = $("#network-" + data.target);
|
||||||
|
|
||||||
|
$.each(order, function(index, value) {
|
||||||
|
if (index === 0) { // Shouldn't attempt to move lobby
|
||||||
|
return true; // same as `continue` -> skip to next item
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = $(network.children()[index]); // Target channel at position
|
||||||
|
|
||||||
|
if (position.data("id") === value) { // Channel in correct place
|
||||||
|
return true; // No point in continuing
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = network.find(".chan[data-id=" + value + "]"); // Channel at position
|
||||||
|
|
||||||
|
$(channel).insertBefore(position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
24
client/js/socket-events/toggle.js
Normal file
24
client/js/socket-events/toggle.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const templates = require("../../views");
|
||||||
|
const options = require("../options");
|
||||||
|
|
||||||
|
socket.on("toggle", function(data) {
|
||||||
|
const toggle = $("#toggle-" + data.id);
|
||||||
|
toggle.parent().after(templates.toggle({toggle: data}));
|
||||||
|
switch (data.type) {
|
||||||
|
case "link":
|
||||||
|
if (options.links) {
|
||||||
|
toggle.click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "image":
|
||||||
|
if (options.thumbnails) {
|
||||||
|
toggle.click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
12
client/js/socket-events/topic.js
Normal file
12
client/js/socket-events/topic.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const helpers_parse = require("../libs/handlebars/parse");
|
||||||
|
|
||||||
|
socket.on("topic", function(data) {
|
||||||
|
const topic = $("#chan-" + data.chan).find(".header .topic");
|
||||||
|
topic.html(helpers_parse(data.topic));
|
||||||
|
// .attr() is safe escape-wise but consider the capabilities of the attribute
|
||||||
|
topic.attr("title", data.topic);
|
||||||
|
});
|
17
client/js/socket-events/users.js
Normal file
17
client/js/socket-events/users.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const socket = require("../socket");
|
||||||
|
const chat = $("#chat");
|
||||||
|
|
||||||
|
socket.on("users", function(data) {
|
||||||
|
const chan = chat.find("#chan-" + data.chan);
|
||||||
|
|
||||||
|
if (chan.hasClass("active")) {
|
||||||
|
socket.emit("names", {
|
||||||
|
target: data.chan
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
chan.data("needsNamesRefresh", true);
|
||||||
|
}
|
||||||
|
});
|
64
client/js/sorting.js
Normal file
64
client/js/sorting.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const sidebar = $("#sidebar, #footer");
|
||||||
|
const socket = require("./socket");
|
||||||
|
const options = require("./options");
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
sidebar.find(".networks").sortable({
|
||||||
|
axis: "y",
|
||||||
|
containment: "parent",
|
||||||
|
cursor: "move",
|
||||||
|
distance: 12,
|
||||||
|
items: ".network",
|
||||||
|
handle: ".lobby",
|
||||||
|
placeholder: "network-placeholder",
|
||||||
|
forcePlaceholderSize: true,
|
||||||
|
tolerance: "pointer", // Use the pointer to figure out where the network is in the list
|
||||||
|
|
||||||
|
update: function() {
|
||||||
|
const order = [];
|
||||||
|
sidebar.find(".network").each(function() {
|
||||||
|
const id = $(this).data("id");
|
||||||
|
order.push(id);
|
||||||
|
});
|
||||||
|
socket.emit(
|
||||||
|
"sort", {
|
||||||
|
type: "networks",
|
||||||
|
order: order
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
options.ignoreSortSync = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sidebar.find(".network").sortable({
|
||||||
|
axis: "y",
|
||||||
|
containment: "parent",
|
||||||
|
cursor: "move",
|
||||||
|
distance: 12,
|
||||||
|
items: ".chan:not(.lobby)",
|
||||||
|
placeholder: "chan-placeholder",
|
||||||
|
forcePlaceholderSize: true,
|
||||||
|
tolerance: "pointer", // Use the pointer to figure out where the channel is in the list
|
||||||
|
|
||||||
|
update: function(e, ui) {
|
||||||
|
const order = [];
|
||||||
|
const network = ui.item.parent();
|
||||||
|
network.find(".chan").each(function() {
|
||||||
|
const id = $(this).data("id");
|
||||||
|
order.push(id);
|
||||||
|
});
|
||||||
|
socket.emit(
|
||||||
|
"sort", {
|
||||||
|
type: "channels",
|
||||||
|
target: network.data("id"),
|
||||||
|
order: order
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
options.ignoreSortSync = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
79
client/js/utils.js
Normal file
79
client/js/utils.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const $ = require("jquery");
|
||||||
|
const chat = $("#chat");
|
||||||
|
const input = $("#input");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
clear,
|
||||||
|
confirmExit,
|
||||||
|
forceFocus,
|
||||||
|
move,
|
||||||
|
resetHeight,
|
||||||
|
setNick,
|
||||||
|
toggleNickEditor,
|
||||||
|
toggleNotificationMarkers
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetHeight(element) {
|
||||||
|
element.style.height = element.style.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triggering click event opens the virtual keyboard on mobile
|
||||||
|
// This can only be called from another interactive event (e.g. button click)
|
||||||
|
function forceFocus() {
|
||||||
|
input.trigger("click").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
chat.find(".active")
|
||||||
|
.find(".show-more").addClass("show").end()
|
||||||
|
.find(".messages .msg, .date-marker-container").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleNickEditor(toggle) {
|
||||||
|
$("#nick").toggleClass("editable", toggle);
|
||||||
|
$("#nick-value").attr("contenteditable", toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNick(nick) {
|
||||||
|
// Closes the nick editor when canceling, changing channel, or when a nick
|
||||||
|
// is set in a different tab / browser / device.
|
||||||
|
toggleNickEditor(false);
|
||||||
|
|
||||||
|
$("#nick-value").text(nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
const favicon = $("#favicon");
|
||||||
|
|
||||||
|
function toggleNotificationMarkers(newState) {
|
||||||
|
// Toggles the favicon to red when there are unread notifications
|
||||||
|
if (favicon.data("toggled") !== newState) {
|
||||||
|
var old = favicon.attr("href");
|
||||||
|
favicon.attr("href", favicon.data("other"));
|
||||||
|
favicon.data("other", old);
|
||||||
|
favicon.data("toggled", newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggles a dot on the menu icon when there are unread notifications
|
||||||
|
$("#viewport .lt").toggleClass("notified", newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmExit() {
|
||||||
|
if ($("body").hasClass("public")) {
|
||||||
|
window.onbeforeunload = function() {
|
||||||
|
return "Are you sure you want to navigate away from this page?";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function move(array, old_index, new_index) {
|
||||||
|
if (new_index >= array.length) {
|
||||||
|
let k = new_index - array.length;
|
||||||
|
while ((k--) + 1) {
|
||||||
|
this.push(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
array.splice(new_index, 0, array.splice(old_index, 1)[0]);
|
||||||
|
return array;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user