diff --git a/lib/shared/dm-ops/leave-thread-spec.js b/lib/shared/dm-ops/leave-thread-spec.js --- a/lib/shared/dm-ops/leave-thread-spec.js +++ b/lib/shared/dm-ops/leave-thread-spec.js @@ -21,7 +21,6 @@ import type { ClientUpdateInfo } from '../../types/update-types.js'; import { values } from '../../utils/objects.js'; import { rawMessageInfoFromMessageData } from '../message-utils.js'; -import { userIsMember } from '../thread-utils.js'; function createMessageDataWithInfoFromDMOperation( dmOperation: DMLeaveThreadOperation, @@ -37,10 +36,9 @@ return { messageData, rawMessageInfo }; } -function createLeaveThreadSubthreadsUpdates( +function createDeleteSubthreadsUpdates( dmOperation: DMLeaveThreadOperation, threadInfo: ThickRawThreadInfo, - viewerID: string, threadInfos: RawThreadInfos, ): $ReadOnlyArray { const updates = []; @@ -60,6 +58,56 @@ return updates; } +function createLeaveSubthreadsUpdates( + dmOperation: DMLeaveThreadOperation, + threadInfo: ThickRawThreadInfo, + threadInfos: RawThreadInfos, +): $ReadOnlyArray { + const updates = []; + for (const thread of values(threadInfos)) { + if (thread.parentThreadID !== threadInfo.id || !thread.thick) { + continue; + } + + const userID = dmOperation.editorID; + let userTimestamps = thread.timestamps.members[userID]; + if (!userTimestamps) { + userTimestamps = { + isMember: thread.creationTime, + subscription: thread.creationTime, + }; + } + + if (userTimestamps.isMember > dmOperation.time) { + continue; + } + + const updatedThread = { + ...thread, + members: thread.members.filter(member => member.id !== userID), + timestamps: { + ...thread.timestamps, + members: { + ...thread.timestamps.members, + [userID]: { + ...userTimestamps, + isMember: dmOperation.time, + }, + }, + }, + }; + + updates.push({ + type: updateTypes.UPDATE_THREAD, + id: uuid.v4(), + time: dmOperation.time, + threadInfo: updatedThread, + }); + } + + return updates; +} + const leaveThreadSpec: DMOperationSpec = Object.freeze({ notificationsCreationData: async (dmOperation: DMLeaveThreadOperation) => { return { @@ -81,40 +129,6 @@ createMessageDataWithInfoFromDMOperation(dmOperation); const rawMessageInfos = [rawMessageInfo]; - if ( - viewerID === editorID && - userIsMember(threadInfo, editorID) && - (threadInfo.type !== threadTypes.THICK_SIDEBAR || - (threadInfo.parentThreadID && !threadInfos[threadInfo.parentThreadID])) - ) { - return { - rawMessageInfos, - updateInfos: [ - { - type: updateTypes.DELETE_THREAD, - id: uuid.v4(), - time, - threadID, - }, - ...createLeaveThreadSubthreadsUpdates( - dmOperation, - threadInfo, - viewerID, - threadInfos, - ), - ], - blobOps: [], - }; - } - - if (threadInfo.timestamps.members[editorID]?.isMember > time) { - return { - rawMessageInfos, - updateInfos: [], - blobOps: [], - }; - } - const memberTimestamps = { ...threadInfo.timestamps.members }; if (!memberTimestamps[editorID]) { memberTimestamps[editorID] = { @@ -127,8 +141,35 @@ isMember: time, }; - let currentUser = threadInfo.currentUser; - if (editorID === viewerID) { + if (viewerID === editorID) { + if (threadInfo.timestamps.members[editorID]?.isMember > time) { + return { + rawMessageInfos, + updateInfos: [], + blobOps: [], + }; + } + + if (threadInfo.type !== threadTypes.THICK_SIDEBAR) { + return { + rawMessageInfos, + updateInfos: [ + { + type: updateTypes.DELETE_THREAD, + id: uuid.v4(), + time, + threadID, + }, + ...createDeleteSubthreadsUpdates( + dmOperation, + threadInfo, + threadInfos, + ), + ], + blobOps: [], + }; + } + const parentThreadID = threadInfo.parentThreadID; const parentThreadInfo = parentThreadID ? utilities.threadInfos[parentThreadID] @@ -149,33 +190,71 @@ false, parentThreadInfo, ); - const { minimallyEncoded, permissions, ...currentUserInfo } = currentUser; - currentUser = minimallyEncodeThreadCurrentUserInfo({ + const { minimallyEncoded, permissions, ...currentUserInfo } = + threadInfo.currentUser; + const currentUser = minimallyEncodeThreadCurrentUserInfo({ ...currentUserInfo, role: null, permissions: viewerMembershipPermissions, }); + + const updatedThreadInfo = { + ...threadInfo, + members: threadInfo.members.filter(member => member.id !== editorID), + currentUser, + timestamps: { + ...threadInfo.timestamps, + members: memberTimestamps, + }, + }; + + return { + rawMessageInfos, + updateInfos: [ + { + type: updateTypes.UPDATE_THREAD, + id: uuid.v4(), + time, + threadInfo: updatedThreadInfo, + }, + ], + blobOps: [], + }; + } + + const updateInfos: Array = [ + ...createLeaveSubthreadsUpdates(dmOperation, threadInfo, threadInfos), + ]; + + // It is possible that the editor has joined this thread after leaving it, + // but regardless, we should possibly leave the sidebars. We need to do + // that because it isn't guaranteed that the editor rejoined them. + if (threadInfo.timestamps.members[editorID]?.isMember > time) { + return { + rawMessageInfos, + updateInfos, + blobOps: [], + }; } const updatedThreadInfo = { ...threadInfo, members: threadInfo.members.filter(member => member.id !== editorID), - currentUser, timestamps: { ...threadInfo.timestamps, members: memberTimestamps, }, }; + updateInfos.push({ + type: updateTypes.UPDATE_THREAD, + id: uuid.v4(), + time, + threadInfo: updatedThreadInfo, + }); + return { rawMessageInfos, - updateInfos: [ - { - type: updateTypes.UPDATE_THREAD, - id: uuid.v4(), - time, - threadInfo: updatedThreadInfo, - }, - ], + updateInfos, blobOps: [], }; },