2017-05-18 20:08:54 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const $ = require("jquery");
|
|
|
|
const templates = require("../views");
|
|
|
|
const options = require("./options");
|
2017-07-06 06:16:01 +00:00
|
|
|
const renderPreview = require("./renderPreview");
|
2017-05-18 20:08:54 +00:00
|
|
|
const utils = require("./utils");
|
|
|
|
const sorting = require("./sorting");
|
2017-06-22 20:08:36 +00:00
|
|
|
const constants = require("./constants");
|
2017-08-19 18:47:23 +00:00
|
|
|
const condensed = require("./condensed");
|
2017-05-18 20:08:54 +00:00
|
|
|
|
|
|
|
const chat = $("#chat");
|
|
|
|
const sidebar = $("#sidebar");
|
|
|
|
|
2017-06-29 09:19:37 +00:00
|
|
|
const historyObserver = window.IntersectionObserver ?
|
|
|
|
new window.IntersectionObserver(loadMoreHistory, {
|
|
|
|
root: chat.get(0)
|
|
|
|
}) : null;
|
|
|
|
|
2017-05-18 20:08:54 +00:00
|
|
|
module.exports = {
|
2017-06-22 20:08:36 +00:00
|
|
|
appendMessage,
|
2017-05-18 20:08:54 +00:00
|
|
|
buildChannelMessages,
|
|
|
|
renderChannel,
|
|
|
|
renderChannelUsers,
|
2017-07-06 06:16:01 +00:00
|
|
|
renderNetworks,
|
2017-05-18 20:08:54 +00:00
|
|
|
};
|
|
|
|
|
2017-08-24 13:50:47 +00:00
|
|
|
function buildChannelMessages(chanId, chanType, messages) {
|
|
|
|
return messages.reduce((docFragment, message) => {
|
|
|
|
appendMessage(docFragment, chanId, chanType, message);
|
2017-05-18 20:08:54 +00:00
|
|
|
return docFragment;
|
|
|
|
}, $(document.createDocumentFragment()));
|
|
|
|
}
|
|
|
|
|
2017-08-24 13:50:47 +00:00
|
|
|
function appendMessage(container, chanId, chanType, msg) {
|
|
|
|
const renderedMessage = buildChatMessage(chanId, msg);
|
|
|
|
|
|
|
|
// Check if date changed
|
|
|
|
let lastChild = container.find(".msg").last();
|
|
|
|
const msgTime = new Date(msg.time);
|
|
|
|
|
|
|
|
// It's the first message in a window,
|
|
|
|
// then just append the message and do nothing else
|
|
|
|
if (lastChild.length === 0) {
|
|
|
|
container
|
|
|
|
.append(templates.date_marker({msgDate: msgTime}))
|
|
|
|
.append(renderedMessage);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const prevMsgTime = new Date(lastChild.attr("data-time"));
|
|
|
|
const parent = lastChild.parent();
|
|
|
|
|
|
|
|
// If this message is condensed, we have to work on the wrapper
|
|
|
|
if (parent.hasClass("condensed")) {
|
|
|
|
lastChild = parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert date marker if date changed compared to previous message
|
|
|
|
if (prevMsgTime.toDateString() !== msgTime.toDateString()) {
|
|
|
|
lastChild.after(templates.date_marker({msgDate: msgTime}));
|
|
|
|
|
|
|
|
// If date changed, we don't need to do condensed logic
|
|
|
|
container.append(renderedMessage);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If current window is not a channel or this message is not condensable,
|
|
|
|
// then just append the message to container and be done with it
|
2017-08-30 12:43:31 +00:00
|
|
|
if (constants.condensedTypes.indexOf(msg.type) === -1 || chanType !== "channel") {
|
2017-08-24 13:50:47 +00:00
|
|
|
container.append(renderedMessage);
|
2017-08-19 18:47:23 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-24 13:50:47 +00:00
|
|
|
// If the previous message is already condensed,
|
|
|
|
// we just append to it and update text
|
|
|
|
if (lastChild.hasClass("condensed")) {
|
|
|
|
lastChild.append(renderedMessage);
|
|
|
|
condensed.updateText(lastChild, [msg.type]);
|
2017-08-30 12:43:31 +00:00
|
|
|
return;
|
2017-06-22 20:08:36 +00:00
|
|
|
}
|
2017-08-30 12:43:31 +00:00
|
|
|
|
|
|
|
const newCondensed = buildChatMessage(chanId, {
|
|
|
|
type: "condensed",
|
|
|
|
time: msg.time,
|
|
|
|
previews: []
|
|
|
|
});
|
|
|
|
|
|
|
|
condensed.updateText(newCondensed, [msg.type]);
|
|
|
|
newCondensed.append(renderedMessage);
|
|
|
|
container.append(newCondensed);
|
2017-06-22 20:08:36 +00:00
|
|
|
}
|
|
|
|
|
2017-08-24 13:50:47 +00:00
|
|
|
function buildChatMessage(chanId, msg) {
|
|
|
|
const type = msg.type;
|
|
|
|
let target = "#chan-" + chanId;
|
2017-05-18 20:08:54 +00:00
|
|
|
if (type === "error") {
|
|
|
|
target = "#chan-" + chat.find(".active").data("id");
|
|
|
|
}
|
|
|
|
|
|
|
|
const chan = chat.find(target);
|
|
|
|
let template = "msg";
|
|
|
|
|
2017-07-21 08:35:05 +00:00
|
|
|
// See if any of the custom highlight regexes match
|
2017-08-24 13:50:47 +00:00
|
|
|
if (!msg.highlight && !msg.self
|
2017-07-21 08:35:05 +00:00
|
|
|
&& options.highlightsRE
|
|
|
|
&& (type === "message" || type === "notice")
|
2017-08-24 13:50:47 +00:00
|
|
|
&& options.highlightsRE.exec(msg.text)) {
|
|
|
|
msg.highlight = true;
|
2017-05-18 20:08:54 +00:00
|
|
|
}
|
|
|
|
|
2017-06-22 20:08:36 +00:00
|
|
|
if (constants.actionTypes.indexOf(type) !== -1) {
|
2017-08-24 13:50:47 +00:00
|
|
|
msg.template = "actions/" + type;
|
2017-05-18 20:08:54 +00:00
|
|
|
template = "msg_action";
|
|
|
|
} else if (type === "unhandled") {
|
|
|
|
template = "msg_unhandled";
|
2017-06-22 20:08:36 +00:00
|
|
|
} else if (type === "condensed") {
|
|
|
|
template = "msg_condensed";
|
2017-05-18 20:08:54 +00:00
|
|
|
}
|
|
|
|
|
2017-08-24 13:50:47 +00:00
|
|
|
const renderedMessage = $(templates[template](msg));
|
|
|
|
const content = renderedMessage.find(".content");
|
2017-05-18 20:08:54 +00:00
|
|
|
|
|
|
|
if (template === "msg_action") {
|
2017-08-24 13:50:47 +00:00
|
|
|
content.html(templates.actions[type](msg));
|
2017-05-18 20:08:54 +00:00
|
|
|
}
|
|
|
|
|
2017-08-24 13:50:47 +00:00
|
|
|
msg.previews.forEach((preview) => {
|
|
|
|
renderPreview(preview, renderedMessage);
|
2017-07-18 05:27:18 +00:00
|
|
|
});
|
|
|
|
|
2017-07-10 10:56:37 +00:00
|
|
|
if ((type === "message" || type === "action" || type === "notice") && chan.hasClass("channel")) {
|
2017-05-18 20:08:54 +00:00
|
|
|
const nicks = chan.find(".users").data("nicks");
|
|
|
|
if (nicks) {
|
2017-08-24 13:50:47 +00:00
|
|
|
const find = nicks.indexOf(msg.from);
|
2017-05-18 20:08:54 +00:00
|
|
|
if (find !== -1) {
|
|
|
|
nicks.splice(find, 1);
|
2017-08-24 13:50:47 +00:00
|
|
|
nicks.unshift(msg.from);
|
2017-05-18 20:08:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 13:50:47 +00:00
|
|
|
return renderedMessage;
|
2017-05-18 20:08:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function renderChannel(data) {
|
|
|
|
renderChannelMessages(data);
|
|
|
|
|
|
|
|
if (data.type === "channel") {
|
|
|
|
renderChannelUsers(data);
|
|
|
|
}
|
2017-06-29 09:19:37 +00:00
|
|
|
|
|
|
|
if (historyObserver) {
|
|
|
|
historyObserver.observe(chat.find("#chan-" + data.id + " .show-more").get(0));
|
|
|
|
}
|
2017-05-18 20:08:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function renderChannelMessages(data) {
|
2017-08-24 13:50:47 +00:00
|
|
|
const documentFragment = buildChannelMessages(data.id, data.type, data.messages);
|
2017-05-18 20:08:54 +00:00
|
|
|
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());
|
2017-06-22 20:08:36 +00:00
|
|
|
} else if (first.parent().hasClass("condensed")) {
|
|
|
|
first.parent().before(templates.unread_marker());
|
2017-05-18 20:08:54 +00:00
|
|
|
} else {
|
|
|
|
first.before(templates.unread_marker());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
channel.append(templates.unread_marker());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderChannelUsers(data) {
|
|
|
|
const users = chat.find("#chan-" + data.id).find(".users");
|
2017-07-10 10:56:37 +00:00
|
|
|
const nicks = data.users
|
2017-07-10 10:56:58 +00:00
|
|
|
.concat() // Make a copy of the user list, sort is applied in-place
|
2017-07-10 10:56:37 +00:00
|
|
|
.sort((a, b) => b.lastMessage - a.lastMessage)
|
|
|
|
.map((a) => a.nick);
|
2017-05-18 20:08:54 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
})
|
|
|
|
);
|
2017-07-10 10:56:58 +00:00
|
|
|
channels.forEach((channel) => {
|
|
|
|
renderChannel(channel);
|
|
|
|
|
|
|
|
if (channel.type === "channel") {
|
|
|
|
chat.find("#chan-" + channel.id).data("needsNamesRefresh", true);
|
|
|
|
}
|
|
|
|
});
|
2017-05-18 20:08:54 +00:00
|
|
|
|
|
|
|
utils.confirmExit();
|
|
|
|
sorting();
|
|
|
|
|
|
|
|
if (sidebar.find(".highlight").length) {
|
|
|
|
utils.toggleNotificationMarkers(true);
|
|
|
|
}
|
|
|
|
}
|
2017-06-29 09:19:37 +00:00
|
|
|
|
|
|
|
function loadMoreHistory(entries) {
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
if (!entry.isIntersecting) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var target = $(entry.target).find(".show-more-button");
|
|
|
|
|
|
|
|
if (target.attr("disabled")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
target.click();
|
|
|
|
});
|
|
|
|
}
|