Merge pull request #1452 from thelounge/xpaw/refactor-dates

Completely refactor how date markers are inserted
This commit is contained in:
Pavel Djundik 2017-08-29 11:59:35 +03:00 committed by GitHub
commit d8f2d7fc10
3 changed files with 77 additions and 120 deletions

View File

@ -15,57 +15,85 @@ const sidebar = $("#sidebar");
module.exports = { module.exports = {
appendMessage, appendMessage,
buildChannelMessages, buildChannelMessages,
buildChatMessage,
renderChannel, renderChannel,
renderChannelMessages,
renderChannelUsers, renderChannelUsers,
renderNetworks, renderNetworks,
}; };
function buildChannelMessages(data) { function buildChannelMessages(chanId, chanType, messages) {
return data.messages.reduce(function(docFragment, message) { return messages.reduce((docFragment, message) => {
appendMessage(docFragment, data.id, data.type, message.type, buildChatMessage({ appendMessage(docFragment, chanId, chanType, message);
chan: data.id,
msg: message
}));
return docFragment; return docFragment;
}, $(document.createDocumentFragment())); }, $(document.createDocumentFragment()));
} }
function appendMessage(container, chan, chanType, messageType, msg) { function appendMessage(container, chanId, chanType, msg) {
// TODO: To fix #1432, statusMessage option should entirely be implemented in CSS const renderedMessage = buildChatMessage(chanId, msg);
if (constants.condensedTypes.indexOf(messageType) === -1 || chanType !== "channel" || options.statusMessages !== "condensed") {
container.append(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; return;
} }
const lastChild = container.children("div.msg").last(); const prevMsgTime = new Date(lastChild.attr("data-time"));
const parent = lastChild.parent();
if (lastChild && $(lastChild).hasClass("condensed")) { // If this message is condensed, we have to work on the wrapper
lastChild.append(msg); if (parent.hasClass("condensed")) {
condensed.updateText(lastChild, [messageType]); lastChild = parent;
} else if (lastChild && $(lastChild).is(constants.condensedTypesQuery)) { }
const newCondensed = buildChatMessage({
chan: chan, // Insert date marker if date changed compared to previous message
msg: { if (prevMsgTime.toDateString() !== msgTime.toDateString()) {
type: "condensed", lastChild.after(templates.date_marker({msgDate: msgTime}));
time: msg.attr("data-time"),
previews: [] // If date changed, we don't need to do condensed logic
} container.append(renderedMessage);
return;
}
// TODO: To fix #1432, statusMessage option should entirely be implemented in CSS
// 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
if (constants.condensedTypes.indexOf(msg.type) === -1 || chanType !== "channel" || options.statusMessages !== "condensed") {
container.append(renderedMessage);
return;
}
// 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]);
// If the previous message can be condensed, we create a new condensed wrapper
} else if (lastChild.is(constants.condensedTypesQuery)) {
const newCondensed = buildChatMessage(chanId, {
type: "condensed",
time: msg.time,
previews: []
}); });
condensed.updateText(newCondensed, [messageType, lastChild.attr("data-type")]); condensed.updateText(newCondensed, [msg.type, lastChild.attr("data-type")]);
container.append(newCondensed); container.append(newCondensed);
newCondensed.append(lastChild); newCondensed.append(lastChild);
newCondensed.append(msg); newCondensed.append(renderedMessage);
} else { } else {
container.append(msg); container.append(renderedMessage);
} }
} }
function buildChatMessage(data) { function buildChatMessage(chanId, msg) {
const type = data.msg.type; const type = msg.type;
let target = "#chan-" + data.chan; let target = "#chan-" + chanId;
if (type === "error") { if (type === "error") {
target = "#chan-" + chat.find(".active").data("id"); target = "#chan-" + chat.find(".active").data("id");
} }
@ -74,15 +102,15 @@ function buildChatMessage(data) {
let template = "msg"; let template = "msg";
// See if any of the custom highlight regexes match // See if any of the custom highlight regexes match
if (!data.msg.highlight && !data.msg.self if (!msg.highlight && !msg.self
&& options.highlightsRE && options.highlightsRE
&& (type === "message" || type === "notice") && (type === "message" || type === "notice")
&& options.highlightsRE.exec(data.msg.text)) { && options.highlightsRE.exec(msg.text)) {
data.msg.highlight = true; msg.highlight = true;
} }
if (constants.actionTypes.indexOf(type) !== -1) { if (constants.actionTypes.indexOf(type) !== -1) {
data.msg.template = "actions/" + type; msg.template = "actions/" + type;
template = "msg_action"; template = "msg_action";
} else if (type === "unhandled") { } else if (type === "unhandled") {
template = "msg_unhandled"; template = "msg_unhandled";
@ -90,29 +118,29 @@ function buildChatMessage(data) {
template = "msg_condensed"; template = "msg_condensed";
} }
const msg = $(templates[template](data.msg)); const renderedMessage = $(templates[template](msg));
const content = msg.find(".content"); const content = renderedMessage.find(".content");
if (template === "msg_action") { if (template === "msg_action") {
content.html(templates.actions[type](data.msg)); content.html(templates.actions[type](msg));
} }
data.msg.previews.forEach((preview) => { msg.previews.forEach((preview) => {
renderPreview(preview, msg); renderPreview(preview, renderedMessage);
}); });
if ((type === "message" || type === "action" || type === "notice") && chan.hasClass("channel")) { if ((type === "message" || type === "action" || type === "notice") && chan.hasClass("channel")) {
const nicks = chan.find(".users").data("nicks"); const nicks = chan.find(".users").data("nicks");
if (nicks) { if (nicks) {
const find = nicks.indexOf(data.msg.from); const find = nicks.indexOf(msg.from);
if (find !== -1) { if (find !== -1) {
nicks.splice(find, 1); nicks.splice(find, 1);
nicks.unshift(data.msg.from); nicks.unshift(msg.from);
} }
} }
} }
return msg; return renderedMessage;
} }
function renderChannel(data) { function renderChannel(data) {
@ -124,7 +152,7 @@ function renderChannel(data) {
} }
function renderChannelMessages(data) { function renderChannelMessages(data) {
const documentFragment = buildChannelMessages(data); const documentFragment = buildChannelMessages(data.id, data.type, data.messages);
const channel = chat.find("#chan-" + data.id + " .messages").append(documentFragment); const channel = chat.find("#chan-" + data.id + " .messages").append(documentFragment);
if (data.firstUnread > 0) { if (data.firstUnread > 0) {
@ -141,30 +169,6 @@ function renderChannelMessages(data) {
} else { } else {
channel.append(templates.unread_marker()); 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()) {
var parent = msg.parent();
if (parent.hasClass("condensed")) {
msg.insertAfter(parent);
}
msg.before(templates.date_marker({msgDate: msgDate}));
}
lastDate = msgDate;
});
}
} }
function renderChannelUsers(data) { function renderChannelUsers(data) {

View File

@ -4,12 +4,11 @@ const $ = require("jquery");
const socket = require("../socket"); const socket = require("../socket");
const render = require("../render"); const render = require("../render");
const chat = $("#chat"); const chat = $("#chat");
const templates = require("../../views");
socket.on("more", function(data) { socket.on("more", function(data) {
const chan = chat let chan = chat.find("#chan-" + data.chan);
.find("#chan-" + data.chan) const type = chan.data("type");
.find(".messages"); chan = chan.find(".messages");
// get the scrollable wrapper around messages // get the scrollable wrapper around messages
const scrollable = chan.closest(".chat"); const scrollable = chan.closest(".chat");
@ -34,7 +33,7 @@ socket.on("more", function(data) {
} }
// Add the older messages // Add the older messages
const documentFragment = render.buildChannelMessages(data); const documentFragment = render.buildChannelMessages(data.chan, type, data.messages);
chan.prepend(documentFragment).end(); chan.prepend(documentFragment).end();
// restore scroll position // restore scroll position
@ -45,31 +44,6 @@ socket.on("more", function(data) {
scrollable.find(".show-more").removeClass("show"); scrollable.find(".show-more").removeClass("show");
} }
// Date change detect
// Have to use data instead 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()) {
var parent = msg.parent();
if (parent.hasClass("condensed")) {
msg.insertAfter(parent);
}
msg.before(templates.date_marker({msgDate: msgDate}));
}
lastDate = msgDate;
});
scrollable.find(".show-more-button") scrollable.find(".show-more-button")
.text("Show older messages") .text("Show older messages")
.prop("disabled", false); .prop("disabled", false);

View File

@ -4,7 +4,6 @@ const $ = require("jquery");
const socket = require("../socket"); const socket = require("../socket");
const render = require("../render"); const render = require("../render");
const chat = $("#chat"); const chat = $("#chat");
const templates = require("../../views");
socket.on("msg", function(data) { socket.on("msg", function(data) {
if (window.requestIdleCallback) { if (window.requestIdleCallback) {
@ -18,7 +17,6 @@ socket.on("msg", function(data) {
}); });
function processReceivedMessage(data) { function processReceivedMessage(data) {
const msg = render.buildChatMessage(data);
const targetId = data.chan; const targetId = data.chan;
const target = "#chan-" + targetId; const target = "#chan-" + targetId;
const channel = chat.find(target); const channel = chat.find(target);
@ -30,31 +28,12 @@ function processReceivedMessage(data) {
$(container).empty(); $(container).empty();
} }
// Check if date changed
let 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()) {
var parent = prevMsg.parent();
if (parent.hasClass("condensed")) {
prevMsg = parent;
}
prevMsg.after(templates.date_marker({msgDate: msgTime}));
}
// Add message to the container // Add message to the container
render.appendMessage( render.appendMessage(
container, container,
data.chan, targetId,
$(target).attr("data-type"), $(target).attr("data-type"),
data.msg.type, data.msg
msg
); );
container.trigger("msg", [ container.trigger("msg", [