mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-02-16 11:37:37 +01:00
fix: closes #12458, on socket.io reconnect
load messages after last data-index
This commit is contained in:
@@ -297,7 +297,7 @@ define('forum/chats', [
|
|||||||
let loading = false;
|
let loading = false;
|
||||||
let previousScrollTop = el.scrollTop();
|
let previousScrollTop = el.scrollTop();
|
||||||
let currentScrollTop = previousScrollTop;
|
let currentScrollTop = previousScrollTop;
|
||||||
el.off('scroll').on('scroll', utils.debounce(function () {
|
el.off('scroll').on('scroll', utils.debounce(async function () {
|
||||||
if (parseInt(el.attr('data-ignore-next-scroll'), 10) === 1) {
|
if (parseInt(el.attr('data-ignore-next-scroll'), 10) === 1) {
|
||||||
el.removeAttr('data-ignore-next-scroll');
|
el.removeAttr('data-ignore-next-scroll');
|
||||||
previousScrollTop = el.scrollTop();
|
previousScrollTop = el.scrollTop();
|
||||||
@@ -323,41 +323,13 @@ define('forum/chats', [
|
|||||||
if ((scrollPercent < top && direction === -1) || (scrollPercent > bottom && direction === 1)) {
|
if ((scrollPercent < top && direction === -1) || (scrollPercent > bottom && direction === 1)) {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
const msgEls = el.children('[data-mid]').not('.new');
|
try {
|
||||||
const afterEl = direction > 0 ? msgEls.last() : msgEls.first();
|
await messages.loadMoreMessages(el, uid, roomId, direction);
|
||||||
const start = parseInt(afterEl.attr('data-index'), 10) || 0;
|
} catch (err) {
|
||||||
|
alerts.error(err);
|
||||||
api.get(`/chats/${roomId}/messages`, { uid, start, direction }).then((data) => {
|
} finally {
|
||||||
let messageData = data.messages;
|
loading = false;
|
||||||
if (!messageData) {
|
}
|
||||||
loading = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
messageData = messageData.filter(function (chatMsg) {
|
|
||||||
const msgOnDom = el.find('[component="chat/message"][data-mid="' + chatMsg.messageId + '"]');
|
|
||||||
msgOnDom.removeClass('new');
|
|
||||||
return !msgOnDom.length;
|
|
||||||
});
|
|
||||||
if (!messageData.length) {
|
|
||||||
loading = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
messages.parseMessage(messageData, function (html) {
|
|
||||||
el.attr('data-ignore-next-scroll', 1);
|
|
||||||
if (direction > 0) {
|
|
||||||
html.insertAfter(afterEl);
|
|
||||||
messages.onMessagesAddedToDom(html);
|
|
||||||
} else {
|
|
||||||
const currentScrollTop = el.scrollTop();
|
|
||||||
const previousHeight = el[0].scrollHeight;
|
|
||||||
el.prepend(html);
|
|
||||||
messages.onMessagesAddedToDom(html);
|
|
||||||
el.scrollTop((el[0].scrollHeight - previousHeight) + currentScrollTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
loading = false;
|
|
||||||
});
|
|
||||||
}).catch(alerts.error);
|
|
||||||
}
|
}
|
||||||
}, 100));
|
}, 100));
|
||||||
};
|
};
|
||||||
|
|||||||
113
public/src/client/chats/events.js
Normal file
113
public/src/client/chats/events.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
define('forum/chats/events', [
|
||||||
|
'forum/chats/messages',
|
||||||
|
'chat',
|
||||||
|
'components',
|
||||||
|
], function (messages, chatModule, components) {
|
||||||
|
const Events = {};
|
||||||
|
|
||||||
|
const events = {
|
||||||
|
'event:chats.receive': chatsReceive,
|
||||||
|
'event:chats.public.unread': publicChatUnread,
|
||||||
|
'event:user_status_change': onUserStatusChange,
|
||||||
|
'event:chats.roomRename': onRoomRename,
|
||||||
|
'event:chats.mark': markChatState,
|
||||||
|
'event:chats.typing': onChatTyping,
|
||||||
|
};
|
||||||
|
let chatNavWrapper = null;
|
||||||
|
let Chats = null;
|
||||||
|
Events.init = async function () {
|
||||||
|
Chats = await require('forum/chats');
|
||||||
|
chatNavWrapper = $('[component="chat/nav-wrapper"]');
|
||||||
|
Events.removeListeners();
|
||||||
|
for (const [eventName, handler] of Object.entries(events)) {
|
||||||
|
socket.on(eventName, handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Events.removeListeners = function () {
|
||||||
|
for (const [eventName, handler] of Object.entries(events)) {
|
||||||
|
socket.removeListener(eventName, handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function chatsReceive(data) {
|
||||||
|
if (chatModule.isFromBlockedUser(data.fromUid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parseInt(data.roomId, 10) === parseInt(ajaxify.data.roomId, 10)) {
|
||||||
|
data.self = parseInt(app.user.uid, 10) === parseInt(data.fromUid, 10) ? 1 : 0;
|
||||||
|
if (!Chats.newMessage) {
|
||||||
|
Chats.newMessage = data.self === 0;
|
||||||
|
}
|
||||||
|
data.message.self = data.self;
|
||||||
|
data.message.timestamp = Math.min(Date.now(), data.message.timestamp);
|
||||||
|
data.message.timestampISO = utils.toISOString(data.message.timestamp);
|
||||||
|
messages.appendChatMessage($('[component="chat/message/content"]'), data.message);
|
||||||
|
|
||||||
|
Chats.updateTeaser(data.roomId, {
|
||||||
|
content: utils.stripHTMLTags(utils.decodeHTMLEntities(data.message.content)),
|
||||||
|
user: data.message.fromUser,
|
||||||
|
timestampISO: data.message.timestampISO,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicChatUnread(data) {
|
||||||
|
if (
|
||||||
|
!ajaxify.data.template.chats ||
|
||||||
|
chatModule.isFromBlockedUser(data.fromUid) ||
|
||||||
|
chatModule.isLookingAtRoom(data.roomId) ||
|
||||||
|
app.user.uid === parseInt(data.fromUid, 10)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Chats.markChatPageElUnread(data);
|
||||||
|
Chats.increasePublicRoomUnreadCount(chatNavWrapper.find('[data-roomid=' + data.roomId + ']'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUserStatusChange(data) {
|
||||||
|
app.updateUserStatus(
|
||||||
|
$(`.chats-list [data-uid="${data.uid}"] [component="user/status"]`), data.status
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRoomRename(data) {
|
||||||
|
const roomEl = components.get('chat/recent/room', data.roomId);
|
||||||
|
if (roomEl.length) {
|
||||||
|
const titleEl = roomEl.find('[component="chat/room/title"]');
|
||||||
|
ajaxify.data.roomName = data.newName;
|
||||||
|
titleEl.translateText(data.newName ? data.newName : ajaxify.data.usernames);
|
||||||
|
}
|
||||||
|
const titleEl = $(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"] [component="chat/header/title"]`);
|
||||||
|
if (titleEl.length) {
|
||||||
|
titleEl.html(
|
||||||
|
data.newName ?
|
||||||
|
`<i class="fa ${ajaxify.data.icon} text-muted"></i> ${data.newName}` :
|
||||||
|
ajaxify.data.chatWithMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function markChatState({ roomId, state }) {
|
||||||
|
const roomEls = $(`[component="chat/recent"] [data-roomid="${roomId}"], [component="chat/list"] [data-roomid="${roomId}"], [component="chat/public"] [data-roomid="${roomId}"]`);
|
||||||
|
roomEls.each((idx, el) => {
|
||||||
|
const roomEl = $(el);
|
||||||
|
chatModule.markChatElUnread(roomEl, state === 1);
|
||||||
|
if (state === 0) {
|
||||||
|
Chats.updatePublicRoomUnreadCount(roomEl, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChatTyping(data) {
|
||||||
|
if (data.uid === app.user.uid || chatModule.isFromBlockedUser(data.uid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chatModule.updateTypingUserList($(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"]`), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Events;
|
||||||
|
});
|
||||||
@@ -79,7 +79,7 @@ define('forum/chats/messages', [
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.appendChatMessage = function (chatContentEl, data) {
|
messages.appendChatMessage = async function (chatContentEl, data) {
|
||||||
const lastMsgEl = chatContentEl.find('.chat-message').last();
|
const lastMsgEl = chatContentEl.find('.chat-message').last();
|
||||||
const lastSpeaker = parseInt(lastMsgEl.attr('data-uid'), 10);
|
const lastSpeaker = parseInt(lastMsgEl.attr('data-uid'), 10);
|
||||||
const lasttimestamp = parseInt(lastMsgEl.attr('data-timestamp'), 10);
|
const lasttimestamp = parseInt(lastMsgEl.attr('data-timestamp'), 10);
|
||||||
@@ -89,9 +89,8 @@ define('forum/chats/messages', [
|
|||||||
data.index = parseInt(lastMsgEl.attr('data-index'), 10) + 1;
|
data.index = parseInt(lastMsgEl.attr('data-index'), 10) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.parseMessage(data, function (html) {
|
const html = await messages.parseMessage(data);
|
||||||
onMessagesParsed(chatContentEl, html, data);
|
onMessagesParsed(chatContentEl, html, data);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function onMessagesParsed(chatContentEl, html, msgData) {
|
function onMessagesParsed(chatContentEl, html, msgData) {
|
||||||
@@ -125,16 +124,15 @@ define('forum/chats/messages', [
|
|||||||
hooks.fire('action:chat.onMessagesAddedToDom', { messageEls });
|
hooks.fire('action:chat.onMessagesAddedToDom', { messageEls });
|
||||||
};
|
};
|
||||||
|
|
||||||
messages.parseMessage = function (data, callback) {
|
messages.parseMessage = function (data) {
|
||||||
const tplData = {
|
const tplData = {
|
||||||
messages: data,
|
messages: data,
|
||||||
isAdminOrGlobalMod: app.user.isAdmin || app.user.isGlobalMod,
|
isAdminOrGlobalMod: app.user.isAdmin || app.user.isGlobalMod,
|
||||||
};
|
};
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
app.parseAndTranslate('partials/chats/messages', tplData).then(callback);
|
return app.parseAndTranslate('partials/chats/messages', tplData);
|
||||||
} else {
|
|
||||||
app.parseAndTranslate('partials/chats/' + (data.system ? 'system-message' : 'message'), tplData).then(callback);
|
|
||||||
}
|
}
|
||||||
|
return app.parseAndTranslate('partials/chats/' + (data.system ? 'system-message' : 'message'), tplData);
|
||||||
};
|
};
|
||||||
|
|
||||||
messages.isAtBottom = function (containerEl, threshold) {
|
messages.isAtBottom = function (containerEl, threshold) {
|
||||||
@@ -273,35 +271,37 @@ define('forum/chats/messages', [
|
|||||||
|
|
||||||
socket.removeListener('event:chats.restore', onChatMessageRestored);
|
socket.removeListener('event:chats.restore', onChatMessageRestored);
|
||||||
socket.on('event:chats.restore', onChatMessageRestored);
|
socket.on('event:chats.restore', onChatMessageRestored);
|
||||||
|
|
||||||
|
socket.removeListener('connect', onChatReconnect);
|
||||||
|
socket.on('connect', onChatReconnect);
|
||||||
};
|
};
|
||||||
|
|
||||||
function onChatMessageEdited(data) {
|
async function onChatMessageEdited(data) {
|
||||||
data.messages.forEach(function (message) {
|
await Promise.all(data.messages.map(async (message) => {
|
||||||
const self = parseInt(message.fromuid, 10) === parseInt(app.user.uid, 10);
|
const self = parseInt(message.fromuid, 10) === parseInt(app.user.uid, 10);
|
||||||
message.self = self ? 1 : 0;
|
message.self = self ? 1 : 0;
|
||||||
messages.parseMessage(message, function (html) {
|
const html = await messages.parseMessage(message);
|
||||||
const msgEl = components.get('chat/message', message.mid);
|
const msgEl = components.get('chat/message', message.mid);
|
||||||
if (msgEl.length) {
|
if (msgEl.length) {
|
||||||
const componentsToReplace = [
|
const componentsToReplace = [
|
||||||
'[component="chat/message/body"]',
|
'[component="chat/message/body"]',
|
||||||
'[component="chat/message/edited"]',
|
'[component="chat/message/edited"]',
|
||||||
];
|
];
|
||||||
componentsToReplace.forEach((cmp) => {
|
componentsToReplace.forEach((cmp) => {
|
||||||
msgEl.find(cmp).replaceWith(html.find(cmp));
|
msgEl.find(cmp).replaceWith(html.find(cmp));
|
||||||
});
|
});
|
||||||
messages.onMessagesAddedToDom(components.get('chat/message', message.mid));
|
messages.onMessagesAddedToDom(components.get('chat/message', message.mid));
|
||||||
}
|
}
|
||||||
const parentEl = $(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`);
|
const parentEl = $(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`);
|
||||||
if (parentEl.length) {
|
if (parentEl.length) {
|
||||||
parentEl.find('[component="chat/message/parent/content"]').html(
|
parentEl.find('[component="chat/message/parent/content"]').html(
|
||||||
html.find('[component="chat/message/body"]').html()
|
html.find('[component="chat/message/body"]').html()
|
||||||
);
|
);
|
||||||
messages.onMessagesAddedToDom(
|
messages.onMessagesAddedToDom(
|
||||||
$(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`)
|
$(`[component="chat/message/parent"][data-parent-mid="${message.mid}"]`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChatMessageDeleted(messageId) {
|
function onChatMessageDeleted(messageId) {
|
||||||
@@ -341,6 +341,53 @@ define('forum/chats/messages', [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onChatReconnect() {
|
||||||
|
$('[component="chat/message/content"]').each(function () {
|
||||||
|
const chatContentEl = $(this);
|
||||||
|
const roomId = chatContentEl.attr('data-roomid');
|
||||||
|
const uid = (ajaxify.template.chats && ajaxify.data.uid) || app.user.uid;
|
||||||
|
const isAtBottom = messages.isAtBottom(chatContentEl);
|
||||||
|
messages.loadMoreMessages(chatContentEl, uid, roomId, 1)
|
||||||
|
.then(() => {
|
||||||
|
if (isAtBottom) {
|
||||||
|
messages.scrollToBottomAfterImageLoad(chatContentEl);
|
||||||
|
}
|
||||||
|
}).catch(alerts.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.loadMoreMessages = async function (chatContentEl, uid, roomId, direction) {
|
||||||
|
const msgEls = chatContentEl.children('[data-mid]').not('.new');
|
||||||
|
const afterEl = direction > 0 ? msgEls.last() : msgEls.first();
|
||||||
|
const start = parseInt(afterEl.attr('data-index'), 10) || 0;
|
||||||
|
|
||||||
|
const data = await api.get(`/chats/${roomId}/messages`, { uid, start, direction });
|
||||||
|
let messageData = data.messages;
|
||||||
|
if (!messageData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
messageData = messageData.filter(function (chatMsg) {
|
||||||
|
const msgOnDom = chatContentEl.find(`[component="chat/message"][data-mid="${chatMsg.messageId}"]`);
|
||||||
|
msgOnDom.removeClass('new');
|
||||||
|
return !msgOnDom.length;
|
||||||
|
});
|
||||||
|
if (!messageData.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const html = await messages.parseMessage(messageData);
|
||||||
|
chatContentEl.attr('data-ignore-next-scroll', 1);
|
||||||
|
if (direction > 0) {
|
||||||
|
html.insertAfter(afterEl);
|
||||||
|
messages.onMessagesAddedToDom(html);
|
||||||
|
} else {
|
||||||
|
const currentScrollTop = chatContentEl.scrollTop();
|
||||||
|
const previousHeight = chatContentEl[0].scrollHeight;
|
||||||
|
chatContentEl.prepend(html);
|
||||||
|
messages.onMessagesAddedToDom(html);
|
||||||
|
chatContentEl.scrollTop((chatContentEl[0].scrollHeight - previousHeight) + currentScrollTop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
messages.delete = function (messageId, roomId) {
|
messages.delete = function (messageId, roomId) {
|
||||||
bootbox.confirm('[[modules:chat.delete-message-confirm]]', function (ok) {
|
bootbox.confirm('[[modules:chat.delete-message-confirm]]', function (ok) {
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<div class="d-flex flex-grow-1 gap-1 overflow-auto" style="min-width: 0px;">
|
<div class="d-flex flex-grow-1 gap-1 overflow-auto" style="min-width: 0px;">
|
||||||
<div component="chat/messages" class="expanded-chat d-flex flex-column flex-grow-1" data-roomid="{roomId}" style="min-width: 0px;">
|
<div component="chat/messages" class="expanded-chat d-flex flex-column flex-grow-1" data-roomid="{roomId}" style="min-width: 0px;">
|
||||||
|
|
||||||
<ul component="chat/message/content" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1">
|
<ul component="chat/message/content" data-roomid="{roomId}" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1">
|
||||||
<!-- IMPORT partials/chats/messages.tpl -->
|
<!-- IMPORT partials/chats/messages.tpl -->
|
||||||
</ul>
|
</ul>
|
||||||
<ul component="chat/message/search/results" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 hidden">
|
<ul component="chat/message/search/results" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 hidden">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<hr class="my-1"/>
|
<hr class="my-1"/>
|
||||||
<div class="d-flex flex-grow-1 gap-1 overflow-auto" style="min-width: 0px;">
|
<div class="d-flex flex-grow-1 gap-1 overflow-auto" style="min-width: 0px;">
|
||||||
<div component="chat/messages" class="expanded-chat d-flex flex-column flex-grow-1" data-roomid="{roomId}" style="min-width: 0px;">
|
<div component="chat/messages" class="expanded-chat d-flex flex-column flex-grow-1" data-roomid="{roomId}" style="min-width: 0px;">
|
||||||
<ul component="chat/message/content" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 ghost-scrollbar">
|
<ul component="chat/message/content" data-roomid="{roomId}" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 ghost-scrollbar">
|
||||||
<!-- IMPORT partials/chats/messages.tpl -->
|
<!-- IMPORT partials/chats/messages.tpl -->
|
||||||
</ul>
|
</ul>
|
||||||
<ul component="chat/message/search/results" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 hidden">
|
<ul component="chat/message/search/results" class="chat-content p-0 m-0 list-unstyled overflow-auto flex-grow-1 hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user