diff --git a/public/src/client/chats.js b/public/src/client/chats.js index 316a6a4007..91169f7f22 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -11,6 +11,7 @@ define('forum/chats', [ 'forum/chats/user-list', 'forum/chats/message-search', 'forum/chats/pinned-messages', + 'forum/chats/events', 'autocomplete', 'hooks', 'bootbox', @@ -21,15 +22,14 @@ define('forum/chats', [ ], function ( components, mousetrap, recentChats, create, manage, messages, userList, messageSearch, pinnedMessages, - autocomplete, hooks, bootbox, alerts, chatModule, api, - uploadHelpers + events, autocomplete, hooks, bootbox, alerts, chatModule, + api, uploadHelpers ) { const Chats = { - initialised: false, activeAutocomplete: {}, + newMessage: false, }; - let newMessage = false; let chatNavWrapper = null; $(window).on('action:ajaxify.start', function () { @@ -54,10 +54,9 @@ define('forum/chats', [ socket.emit('modules.chats.enterPublic', ajaxify.data.publicRooms.map(r => r.roomId)); const env = utils.findBootstrapEnvironment(); chatNavWrapper = $('[component="chat/nav-wrapper"]'); - if (!Chats.initialised) { - Chats.addSocketListeners(); - Chats.addGlobalEventListeners(); - } + + Chats.addSocketListeners(); + Chats.addGlobalEventListeners(); recentChats.init(); @@ -68,7 +67,6 @@ define('forum/chats', [ Chats.addHotkeys(); } - Chats.initialised = true; const chatContentEl = $('[component="chat/message/content"]'); messages.wrapImagesInLinks(chatContentEl); if (ajaxify.data.scrollToIndex) { @@ -647,89 +645,22 @@ define('forum/chats', [ }; Chats.addGlobalEventListeners = function () { - $(window).on('mousemove keypress click', function () { - if (newMessage && ajaxify.data.roomId) { - api.del(`/chats/${ajaxify.data.roomId}/state`, {}); - newMessage = false; - } - }); + $(window).off('mousemove keypress click', onUserInteraction) + .on('mousemove keypress click', onUserInteraction); }; + function onUserInteraction() { + if (Chats.newMessage && ajaxify.data.roomId) { + // mark current room read on user interaction + api.del(`/chats/${ajaxify.data.roomId}/state`, {}); + Chats.newMessage = false; + } + } + Chats.addSocketListeners = function () { - socket.on('event:chats.receive', function (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 (!newMessage) { - 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, - }); - } - }); - - socket.on('event:chats.public.unread', function (data) { - if ( - 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 + ']')); - }); - - socket.on('event:user_status_change', function (data) { - app.updateUserStatus($('.chats-list [data-uid="' + data.uid + '"] [component="user/status"]'), data.status); - }); + events.init(); messages.addSocketListeners(); - - socket.on('event:chats.roomRename', function (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 ? - ` ${data.newName}` : - ajaxify.data.chatWithMessage - ); - } - }); - - socket.on('event:chats.mark', ({ 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); - } - }); - }); - - socket.on('event:chats.typing', async (data) => { - if (data.uid === app.user.uid || chatModule.isFromBlockedUser(data.uid)) { - return; - } - chatModule.updateTypingUserList($(`[component="chat/main-wrapper"][data-roomid="${data.roomId}"]`), data); - }); }; Chats.updateTeaser = async function (roomId, teaser) { diff --git a/public/src/utils.common.js b/public/src/utils.common.js index 873292d22e..a35c6a2ea0 100644 --- a/public/src/utils.common.js +++ b/public/src/utils.common.js @@ -301,7 +301,7 @@ const utils = { return String(str).replace(new RegExp('<(\\/)?(' + (pattern || '[^\\s>]+') + ')(\\s+[^<>]*?)?\\s*(\\/)?>', 'gi'), ''); }, stripBidiControls: function (input) { - return input.replace(/[\u202A-\u202E\u2066-\u2069]/g, ''); + return input.replace(/[\u202A-\u202E\u2066-\u2069]/gi, ''); }, cleanUpTag: function (tag, maxLength) { if (typeof tag !== 'string' || !tag.length) { diff --git a/src/categories/delete.js b/src/categories/delete.js index c129cddbd2..243b310106 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -17,12 +17,14 @@ module.exports = function (Categories) { await async.eachLimit(tids, 10, async (tid) => { await topics.purgePostsAndTopic(tid, uid); }); + await db.sortedSetRemove(`cid:${cid}:tids`, tids); }, { alwaysStartAt: 0 }); const pinnedTids = await db.getSortedSetRevRange(`cid:${cid}:tids:pinned`, 0, -1); await async.eachLimit(pinnedTids, 10, async (tid) => { await topics.purgePostsAndTopic(tid, uid); }); + await db.sortedSetRemove(`cid:${cid}:tids:pinned`, pinnedTids); const categoryData = await Categories.getCategoryData(cid); await purgeCategory(cid, categoryData); plugins.hooks.fire('action:category.delete', { cid: cid, uid: uid, category: categoryData }); diff --git a/src/events.js b/src/events.js index 6a293c1bdd..6bd65c1b21 100644 --- a/src/events.js +++ b/src/events.js @@ -261,6 +261,7 @@ events.deleteEvents = async function (eids) { events.deleteAll = async function () { await batch.processSortedSet('events:time', async (eids) => { await events.deleteEvents(eids); + await db.sortedSetRemove('events:time', eids); }, { alwaysStartAt: 0, batch: 500 }); }; diff --git a/src/search.js b/src/search.js index baf4d3c340..b8909b1b41 100644 --- a/src/search.js +++ b/src/search.js @@ -376,9 +376,9 @@ function sortPosts(posts, data) { } else { posts.sort((p1, p2) => { if (p1[fields[0]][fields[1]] > p2[fields[0]][fields[1]]) { - return direction; - } else if (p1[fields[0]][fields[1]] < p2[fields[0]][fields[1]]) { return -direction; + } else if (p1[fields[0]][fields[1]] < p2[fields[0]][fields[1]]) { + return direction; } return 0; }); diff --git a/src/topics/delete.js b/src/topics/delete.js index 03e756e1fd..9a6110fffc 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -73,14 +73,11 @@ module.exports = function (Topics) { }; Topics.purge = async function (tid, uid) { - const [deletedTopic, tags] = await Promise.all([ - Topics.getTopicData(tid), - Topics.getTopicTags(tid), - ]); + const deletedTopic = await Topics.getTopicData(tid); if (!deletedTopic) { return; } - deletedTopic.tags = tags; + deletedTopic.tags = deletedTopic.tags.map(tag => tag.value); await deleteFromFollowersIgnorers(tid); await Promise.all([ diff --git a/src/user/data.js b/src/user/data.js index cd2326281d..0620e159a2 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -333,7 +333,7 @@ module.exports = function (User) { user.displayname = validator.escape(String( meta.config.showFullnameAsDisplayName && showfullname && user.fullname ? - user.fullname : + utils.stripBidiControls(user.fullname) : user.username )); } diff --git a/src/user/delete.js b/src/user/delete.js index 8b084b184b..65d8ccea65 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -43,15 +43,17 @@ module.exports = function (User) { async function deletePosts(callerUid, uid) { await batch.processSortedSet(`uid:${uid}:posts`, async (pids) => { await posts.purge(pids, callerUid); + await db.sortedSetRemove(`uid:${uid}:posts`, pids); }, { alwaysStartAt: 0, batch: 500 }); } async function deleteTopics(callerUid, uid) { - await batch.processSortedSet(`uid:${uid}:topics`, async (ids) => { - await async.eachSeries(ids, async (tid) => { + await batch.processSortedSet(`uid:${uid}:topics`, async (tids) => { + await async.eachSeries(tids, async (tid) => { await topics.purge(tid, callerUid); }); - }, { alwaysStartAt: 0 }); + await db.sortedSetRemove(`uid:${uid}:topics`, tids); + }, { alwaysStartAt: 0, batch: 100 }); } async function deleteUploads(callerUid, uid) { diff --git a/test/utils.js b/test/utils.js index 2e0ce72e8a..cd5ec12a4b 100644 --- a/test/utils.js +++ b/test/utils.js @@ -51,6 +51,12 @@ describe('Utility Methods', () => { assert.strictEqual(out, 'Hello World Dwellers'); }); + it('should remove common bidi embedding and override controls if they are lowercase', () => { + const input = '\u202aHello\u202c \u202bWorld\u202c \u202dDwellers\u202e'; + const out = utils.stripBidiControls(input); + assert.strictEqual(out, 'Hello World Dwellers'); + }); + it('should remove bidirectional isolate formatting characters', () => { const input = '\u2066abc\u2067def\u2068ghi\u2069'; const out = utils.stripBidiControls(input);