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/stickyscroll");
|
||||
require("./libs/jquery/tabcomplete");
|
||||
const helpers_parse = require("./libs/handlebars/parse");
|
||||
const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber");
|
||||
const slideoutMenu = require("./libs/slideout");
|
||||
const templates = require("../views");
|
||||
const socket = require("./socket");
|
||||
require("./socket-events");
|
||||
const constants = require("./constants");
|
||||
const storage = require("./localStorage");
|
||||
const utils = require("./utils");
|
||||
|
||||
$(function() {
|
||||
var sidebar = $("#sidebar, #footer");
|
||||
@ -28,8 +29,6 @@ $(function() {
|
||||
|
||||
$(document.body).data("app-name", document.title);
|
||||
|
||||
var ignoreSortSync = false;
|
||||
|
||||
var pop;
|
||||
try {
|
||||
pop = new Audio();
|
||||
@ -44,8 +43,6 @@ $(function() {
|
||||
pop.play();
|
||||
});
|
||||
|
||||
var favicon = $("#favicon");
|
||||
|
||||
// Autocompletion Strategies
|
||||
|
||||
const emojiStrategy = {
|
||||
@ -152,507 +149,6 @@ $(function() {
|
||||
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 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) {
|
||||
e.preventDefault();
|
||||
forceFocus();
|
||||
utils.forceFocus();
|
||||
var text = input.val();
|
||||
|
||||
if (text.length === 0) {
|
||||
@ -830,7 +320,7 @@ $(function() {
|
||||
resetInputHeight(input.get(0));
|
||||
|
||||
if (text.indexOf("/clear") === 0) {
|
||||
clear();
|
||||
utils.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -853,7 +343,7 @@ $(function() {
|
||||
}
|
||||
|
||||
$("button#set-nick").on("click", function() {
|
||||
toggleNickEditor(true);
|
||||
utils.toggleNickEditor(true);
|
||||
|
||||
// Selects existing nick in the editable text field
|
||||
var element = document.querySelector("#nick-value");
|
||||
@ -868,11 +358,6 @@ $(function() {
|
||||
$("button#cancel-nick").on("click", cancelNick);
|
||||
$("button#submit-nick").on("click", submitNick);
|
||||
|
||||
function toggleNickEditor(toggle) {
|
||||
$("#nick").toggleClass("editable", toggle);
|
||||
$("#nick-value").attr("contenteditable", toggle);
|
||||
}
|
||||
|
||||
function submitNick() {
|
||||
var newNick = $("#nick-value").text().trim();
|
||||
|
||||
@ -881,7 +366,7 @@ $(function() {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleNickEditor(false);
|
||||
utils.toggleNickEditor(false);
|
||||
|
||||
socket.emit("input", {
|
||||
target: chat.data("id"),
|
||||
@ -890,7 +375,7 @@ $(function() {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -993,7 +478,7 @@ $(function() {
|
||||
.empty();
|
||||
|
||||
if (sidebar.find(".highlight").length === 0) {
|
||||
toggleNotificationMarkers(false);
|
||||
utils.toggleNotificationMarkers(false);
|
||||
}
|
||||
|
||||
sidebarSlide.toggle(false);
|
||||
@ -1031,7 +516,7 @@ $(function() {
|
||||
|
||||
if (self.hasClass("chan")) {
|
||||
$("#chat-container").addClass("active");
|
||||
setNick(self.closest(".network").data("nick"));
|
||||
utils.setNick(self.closest(".network").data("nick"));
|
||||
}
|
||||
|
||||
var chanChat = chan.find(".chat");
|
||||
@ -1133,7 +618,7 @@ $(function() {
|
||||
// On mobile, sounds can not be played without user interaction.
|
||||
}
|
||||
}
|
||||
toggleNotificationMarkers(true);
|
||||
utils.toggleNotificationMarkers(true);
|
||||
|
||||
if (options.desktopNotifications && Notification.permission === "granted") {
|
||||
var title;
|
||||
@ -1359,7 +844,7 @@ $(function() {
|
||||
"ctrl+shift+l"
|
||||
], function(e) {
|
||||
if (e.target === input[0]) {
|
||||
clear();
|
||||
utils.clear();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
@ -1422,12 +907,6 @@ $(function() {
|
||||
});
|
||||
}, 1000 * 10);
|
||||
|
||||
function clear() {
|
||||
chat.find(".active")
|
||||
.find(".show-more").addClass("show").end()
|
||||
.find(".messages .msg, .date-marker-container").remove();
|
||||
}
|
||||
|
||||
function completeNicks(word) {
|
||||
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", () => {
|
||||
if (sidebar.find(".highlight").length === 0) {
|
||||
toggleNotificationMarkers(false);
|
||||
utils.toggleNotificationMarkers(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const $ = require("jquery");
|
||||
const settings = $("#settings");
|
||||
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