diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 93e522427a..725023ec4b 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -469,7 +469,7 @@ define('forum/chats', [ Chats.addGlobalEventListeners = function () { $(window).on('mousemove keypress click', function () { if (newMessage && ajaxify.data.roomId) { - socket.emit('modules.chats.markRead', ajaxify.data.roomId); + api.del(`/chats/${ajaxify.data.roomId}/state`, {}); newMessage = false; } }); @@ -530,7 +530,7 @@ define('forum/chats', [ const roomEls = document.querySelectorAll(`[component="chat/recent"] [data-roomid="${roomId}"], [component="chat/list"] [data-roomid="${roomId}"]`); roomEls.forEach((roomEl) => { - roomEl.classList.add('unread'); + roomEl.classList[state ? 'add' : 'remove']('unread'); const markEl = roomEl.querySelector('.mark-read'); if (markEl) { @@ -543,8 +543,12 @@ define('forum/chats', [ Chats.setActive = function () { if (ajaxify.data.roomId) { - socket.emit('modules.chats.markRead', ajaxify.data.roomId); - $('[data-roomid="' + ajaxify.data.roomId + '"]').toggleClass('unread', false); + const chatEl = document.querySelector(`[component="chat/recent"] [data-roomid="${ajaxify.data.roomId}"]`); + if (chatEl.classList.contains('unread')) { + api.del(`/chats/${ajaxify.data.roomId}/state`, {}); + chatEl.classList.remove('unread'); + } + if (!utils.isMobile()) { $('.expanded-chat [component="chat/input"]').focus(); } diff --git a/public/src/client/chats/recent.js b/public/src/client/chats/recent.js index 65521682bf..720523dcc0 100644 --- a/public/src/client/chats/recent.js +++ b/public/src/client/chats/recent.js @@ -1,7 +1,7 @@ 'use strict'; -define('forum/chats/recent', ['alerts'], function (alerts) { +define('forum/chats/recent', ['alerts', 'api'], function (alerts, api) { const recent = {}; recent.init = function () { @@ -16,14 +16,21 @@ define('forum/chats/recent', ['alerts'], function (alerts) { .on('click', '.mark-read', function (e) { e.stopPropagation(); const chatEl = this.closest('[data-roomid]'); - const unread = chatEl.classList.contains('unread'); - if (unread) { - const roomId = chatEl.getAttribute('data-roomid'); - socket.emit('modules.chats.markRead', roomId); - chatEl.classList.remove('unread'); - this.querySelector('.unread').classList.add('hidden'); - this.querySelector('.read').classList.remove('hidden'); - } + const state = !chatEl.classList.contains('unread'); // this is the new state + const roomId = chatEl.getAttribute('data-roomid'); + api[state ? 'put' : 'del'](`/chats/${roomId}/state`, {}).catch((err) => { + alerts.error(err); + + // Revert on failure + chatEl.classList[state ? 'remove' : 'add']('unread'); + this.querySelector('.unread').classList[state ? 'add' : 'remove']('hidden'); + this.querySelector('.read').classList[!state ? 'add' : 'remove']('hidden'); + }); + + // Immediate feedback + chatEl.classList[state ? 'add' : 'remove']('unread'); + this.querySelector('.unread').classList[!state ? 'add' : 'remove']('hidden'); + this.querySelector('.read').classList[state ? 'add' : 'remove']('hidden'); }); $('[component="chat/recent"]').on('scroll', function () { diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index e4ec35c79a..29468c63f9 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -131,10 +131,19 @@ define('chat', [ const unread = chatEl.classList.contains('unread'); if (unread) { const roomId = chatEl.getAttribute('data-roomid'); - socket.emit('modules.chats.markRead', roomId); + api.del(`/chats/${roomId}/state`, {}).catch((err) => { + alerts.error(err); + + // Revert on failure + chatEl.classList.add('unread'); + this.querySelector('.unread').classList.remove('hidden'); + this.querySelector('.read').classList.add('hidden'); + }); + + // Immediate feedback chatEl.classList.remove('unread'); - subselector.querySelector('.unread').classList.add('hidden'); - subselector.querySelector('.read').classList.remove('hidden'); + this.querySelector('.unread').classList.add('hidden'); + this.querySelector('.read').classList.remove('hidden'); } }); @@ -304,7 +313,7 @@ define('chat', [ chatModal.on('mousemove keypress click', function () { if (newMessage) { - socket.emit('modules.chats.markRead', data.roomId); + api.del(`/chats/${data.roomId}/state`, {}); newMessage = false; } }); @@ -400,7 +409,7 @@ define('chat', [ taskbar.updateActive(uuid); ChatsMessages.scrollToBottom(chatModal.find('.chat-content')); module.focusInput(chatModal); - socket.emit('modules.chats.markRead', chatModal.attr('data-roomid')); + api.del(`/chats/${chatModal.attr('data-roomid')}/state`, {}); const env = utils.findBootstrapEnvironment(); if (env === 'xs' || env === 'sm') { diff --git a/src/api/chats.js b/src/api/chats.js index 401fccb472..c51aa31ec1 100644 --- a/src/api/chats.js +++ b/src/api/chats.js @@ -5,6 +5,7 @@ const validator = require('validator'); const user = require('../user'); const meta = require('../meta'); const messaging = require('../messaging'); +const notifications = require('../notifications'); const plugins = require('../plugins'); // const websockets = require('../socket.io'); @@ -79,9 +80,24 @@ chatsAPI.mark = async (caller, data) => { await messaging.markUnread([caller.uid], roomId); } else { await messaging.markRead(caller.uid, roomId); + socketHelpers.emitToUids('event:chats.markedAsRead', { roomId: roomId }, [caller.uid]); + + const uidsInRoom = await messaging.getUidsInRoom(roomId, 0, -1); + if (!uidsInRoom.includes(String(caller.uid))) { + return; + } + + // Mark notification read + const nids = uidsInRoom.filter(uid => parseInt(uid, 10) !== caller.uid) + .map(uid => `chat_${uid}_${roomId}`); + + await notifications.markReadMultiple(nids, caller.uid); + await user.notifications.pushCount(caller.uid); } socketHelpers.emitToUids('event:chats.mark', { roomId, state }, [caller.uid]); + messaging.pushUnreadCount(caller.uid); + return messaging.loadRoom(caller.uid, { roomId }); }; diff --git a/src/controllers/write/chats.js b/src/controllers/write/chats.js index 647e81525f..0c780866c6 100644 --- a/src/controllers/write/chats.js +++ b/src/controllers/write/chats.js @@ -56,7 +56,6 @@ Chats.rename = async (req, res) => { Chats.mark = async (req, res) => { const state = req.method === 'PUT' ? 1 : 0; const roomObj = await api.chats.mark(req, { - ...req.body, roomId: req.params.roomId, state, }); diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 1a23fc1067..a7f7626f90 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -171,27 +171,13 @@ SocketModules.chats.canMessage = async function (socket, roomId) { }; SocketModules.chats.markRead = async function (socket, roomId) { + sockets.warnDeprecated(socket, 'PUT/DELETE /api/v3/chats/:roomId/state'); + if (!socket.uid || !roomId) { throw new Error('[[error:invalid-data]]'); } - const [uidsInRoom] = await Promise.all([ - Messaging.getUidsInRoom(roomId, 0, -1), - Messaging.markRead(socket.uid, roomId), - ]); - Messaging.pushUnreadCount(socket.uid); - server.in(`uid_${socket.uid}`).emit('event:chats.markedAsRead', { roomId: roomId }); - - if (!uidsInRoom.includes(String(socket.uid))) { - return; - } - - // Mark notification read - const nids = uidsInRoom.filter(uid => parseInt(uid, 10) !== socket.uid) - .map(uid => `chat_${uid}_${roomId}`); - - await notifications.markReadMultiple(nids, socket.uid); - await user.notifications.pushCount(socket.uid); + api.chats.mark(socket, { state: 0, roomId }); }; SocketModules.chats.markAllRead = async function (socket) {