diff --git a/public/language/en-GB/modules.json b/public/language/en-GB/modules.json index c242b12119..a0f3dfe641 100644 --- a/public/language/en-GB/modules.json +++ b/public/language/en-GB/modules.json @@ -12,6 +12,7 @@ "chat.recent-chats": "Recent Chats", "chat.contacts": "Contacts", "chat.message-history": "Message History", + "chat.message-deleted": "Message Deleted", "chat.options": "Chat options", "chat.pop-out": "Pop out chat", "chat.minimize": "Minimize", diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 1d7fb2646e..3e4c825189 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -485,7 +485,7 @@ define('forum/chats', [ app.updateUserStatus($('.chats-list [data-uid="' + data.uid + '"] [component="user/status"]'), data.status); }); - messages.onChatMessageEdit(); + messages.addSocketListeners(); socket.on('event:chats.roomRename', function (data) { var roomEl = components.get('chat/recent/room', data.roomId); diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index 4f87bf5d14..8e280e830c 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -129,14 +129,20 @@ define('forum/chats/messages', ['components', 'sounds', 'translator', 'benchpres // By setting the `data-mid` attribute, I tell the chat code that I am editing a // message, instead of posting a new one. inputEl.attr('data-mid', messageId).addClass('editing'); - inputEl.val(raw); + inputEl.val(raw).focus(); } }); }; - messages.onChatMessageEdit = function () { + messages.addSocketListeners = function () { socket.removeListener('event:chats.edit', onChatMessageEdited); socket.on('event:chats.edit', onChatMessageEdited); + + socket.removeListener('event:chats.delete', onChatMessageDeleted); + socket.on('event:chats.delete', onChatMessageDeleted); + + socket.removeListener('event:chats.restore', onChatMessageRestored); + socket.on('event:chats.restore', onChatMessageRestored); }; function onChatMessageEdited(data) { @@ -153,6 +159,18 @@ define('forum/chats/messages', ['components', 'sounds', 'translator', 'benchpres }); } + function onChatMessageDeleted(messageId) { + components.get('chat/message', messageId) + .toggleClass('deleted', true) + .find('[component="chat/message/body"]').translateHtml('[[modules:chat.message-deleted]]'); + } + + function onChatMessageRestored(message) { + components.get('chat/message', message.messageId) + .toggleClass('deleted', false) + .find('[component="chat/message/body"]').html(message.content); + } + messages.delete = function (messageId, roomId) { translator.translate('[[modules:chat.delete_message_confirm]]', function (translated) { bootbox.confirm(translated, function (ok) { diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index 46ad454585..d9dbfa59ea 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -242,7 +242,7 @@ define('chat', [ Chats.addCharactersLeftHandler(chatModal); Chats.addIPHandler(chatModal); - ChatsMessages.onChatMessageEdit(); + ChatsMessages.addSocketListeners(); taskbar.push('chat', chatModal.attr('data-uuid'), { title: '[[modules:chat.chatting_with]] ' + (data.roomName || (data.users.length ? data.users[0].username : '')), diff --git a/src/messaging/delete.js b/src/messaging/delete.js index 9a4c551d2f..a48550db26 100644 --- a/src/messaging/delete.js +++ b/src/messaging/delete.js @@ -1,16 +1,33 @@ 'use strict'; +const sockets = require('../socket.io'); + module.exports = function (Messaging) { - Messaging.deleteMessage = async mid => await doDeleteRestore(mid, 1); - Messaging.restoreMessage = async mid => await doDeleteRestore(mid, 0); + Messaging.deleteMessage = async (mid, uid) => await doDeleteRestore(mid, 1, uid); + Messaging.restoreMessage = async (mid, uid) => await doDeleteRestore(mid, 0, uid); - async function doDeleteRestore(mid, state) { + async function doDeleteRestore(mid, state, uid) { const field = state ? 'deleted' : 'restored'; - const cur = await Messaging.getMessageField(mid, 'deleted'); - if (cur === state) { + const { deleted, roomId } = await Messaging.getMessageFields(mid, ['deleted', 'roomId']); + if (deleted === state) { throw new Error('[[error:chat-' + field + '-already]]'); } await Messaging.setMessageField(mid, 'deleted', state); + + const [uids, messages] = await Promise.all([ + Messaging.getUidsInRoom(roomId, 0, -1), + Messaging.getMessagesData([mid], uid, roomId, true), + ]); + + uids.forEach(function (_uid) { + if (parseInt(_uid, 10) !== parseInt(uid, 10)) { + if (state === 1) { + sockets.in('uid_' + _uid).emit('event:chats.delete', mid); + } else if (state === 0) { + sockets.in('uid_' + _uid).emit('event:chats.restore', messages[0]); + } + } + }); } }; diff --git a/src/messaging/index.js b/src/messaging/index.js index a29abfe733..96887fd2b7 100644 --- a/src/messaging/index.js +++ b/src/messaging/index.js @@ -40,14 +40,13 @@ Messaging.getMessages = async (params) => { }); mids.reverse(); - let messageData = await Messaging.getMessagesData(mids, params.uid, params.roomId, isNew); + const messageData = await Messaging.getMessagesData(mids, params.uid, params.roomId, isNew); messageData.forEach(function (messageData) { messageData.index = indices[messageData.messageId.toString()]; - }); - - // Filter out deleted messages unless you're the sender of said message - messageData = messageData.filter(function (messageData) { - return (!messageData.deleted || messageData.fromuid === parseInt(params.uid, 10)); + messageData.isOwner = messageData.fromuid === parseInt(params.uid, 10); + if (messageData.deleted && !messageData.isOwner) { + messageData.content = '[[modules:chat.message-deleted]]'; + } }); return messageData; @@ -100,9 +99,7 @@ Messaging.getRecentChats = async (callerUid, uid, start, stop) => { unread: db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds), users: Promise.all(roomIds.map(async (roomId) => { let uids = await db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9); - uids = uids.filter(function (value) { - return value && parseInt(value, 10) !== parseInt(uid, 10); - }); + uids = uids.filter(_uid => _uid && parseInt(_uid, 10) !== parseInt(uid, 10)); return await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline']); })), teasers: Promise.all(roomIds.map(async roomId => Messaging.getTeaser(uid, roomId))), diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index f44da3ad68..fed03c3260 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -196,7 +196,7 @@ SocketModules.chats.delete = async function (socket, data) { throw new Error('[[error:invalid-data]]'); } await Messaging.canDelete(data.messageId, socket.uid); - await Messaging.deleteMessage(data.messageId); + await Messaging.deleteMessage(data.messageId, socket.uid); }; SocketModules.chats.restore = async function (socket, data) { @@ -204,7 +204,7 @@ SocketModules.chats.restore = async function (socket, data) { throw new Error('[[error:invalid-data]]'); } await Messaging.canDelete(data.messageId, socket.uid); - await Messaging.restoreMessage(data.messageId); + await Messaging.restoreMessage(data.messageId, socket.uid); }; SocketModules.chats.canMessage = async function (socket, roomId) { diff --git a/test/messaging.js b/test/messaging.js index cc2b101676..e663993a6a 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -692,14 +692,9 @@ describe('Messaging Library', function () { it('should not show deleted message to other users', function (done) { socketModules.chats.getMessages({ uid: herpUid }, { uid: herpUid, roomId: roomId, start: 0 }, function (err, messages) { assert.ifError(err); - - // Reduce messages to their mids - var mids = messages.reduce(function (mids, cur) { - mids.push(cur.messageId); - return mids; - }, []); - - assert(!mids.includes(mid)); + messages.forEach(function (msg) { + assert(!msg.deleted || msg.content === '[[modules:chat.message-deleted]]', msg.content); + }); done(); }); });