diff --git a/lib/shared/messages/text-message-spec.js b/lib/shared/messages/text-message-spec.js --- a/lib/shared/messages/text-message-spec.js +++ b/lib/shared/messages/text-message-spec.js @@ -9,6 +9,10 @@ type RawMessageInfoFromServerDBRowParams, } from './message-spec.js'; import { assertSingleMessageInfo } from './utils.js'; +import { + changeThreadSettingsActionTypes, + changeThreadSettings, +} from '../../actions/thread-actions.js'; import { messageTypes } from '../../types/message-types.js'; import type { MessageInfo, @@ -20,8 +24,13 @@ TextMessageInfo, } from '../../types/messages/text.js'; import type { NotifTexts } from '../../types/notif-types.js'; +import { threadTypes } from '../../types/thread-types.js'; import type { ThreadInfo } from '../../types/thread-types.js'; import type { RelativeUserInfo } from '../../types/user-types.js'; +import { + useDispatchActionPromise, + useServerCall, +} from '../../utils/action-utils.js'; import { ET } from '../../utils/entity-text.js'; import { type ASTNode, @@ -29,7 +38,7 @@ stripSpoilersFromNotifications, stripSpoilersFromMarkdownAST, } from '../markdown.js'; -import { threadIsGroupChat } from '../thread-utils.js'; +import { threadIsGroupChat, extractMentionedMembers } from '../thread-utils.js'; /** * most of the markdown leaves contain `content` field @@ -198,4 +207,46 @@ generatesNotifs: async () => pushTypes.NOTIF, includedInRepliesCount: true, + + useCreationSideEffectsFunc: () => { + const dispatchActionPromise = useDispatchActionPromise(); + const callChangeThreadSettings = useServerCall(changeThreadSettings); + return async ( + messageInfo: RawTextMessageInfo, + threadInfo: ThreadInfo, + parentThreadInfo: ?ThreadInfo, + ) => { + if (threadInfo.type !== threadTypes.SIDEBAR) { + return; + } + invariant(parentThreadInfo, 'all sidebars should have a parent thread'); + + const mentionedMembersOfParent = extractMentionedMembers( + messageInfo.text, + parentThreadInfo, + ); + if (mentionedMembersOfParent.size === 0) { + return; + } + + for (const member of threadInfo.members) { + if (member.role) { + mentionedMembersOfParent.delete(member.id); + } + } + + const mentionedToAdd = [...mentionedMembersOfParent.keys()]; + if (mentionedToAdd.length === 0) { + return; + } + + const addMembersPromise = callChangeThreadSettings({ + threadID: threadInfo.id, + changes: { newMemberIDs: mentionedToAdd }, + }); + + dispatchActionPromise(changeThreadSettingsActionTypes, addMembersPromise); + await addMembersPromise; + }; + }, }); diff --git a/native/input/input-state-container.react.js b/native/input/input-state-container.react.js --- a/native/input/input-state-container.react.js +++ b/native/input/input-state-container.react.js @@ -35,7 +35,9 @@ import { createMediaMessageInfo, localIDPrefix, + useMessageCreationSideEffectsFunc, } from 'lib/shared/message-utils.js'; +import type { CreationSideEffectsFunc } from 'lib/shared/messages/message-spec.js'; import { createRealThreadFromPendingThread, threadIsPending, @@ -138,6 +140,7 @@ text: string, ) => Promise, +newThread: (request: ClientNewThreadRequest) => Promise, + +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc, }; type State = { +pendingUploads: PendingMultimediaUploads, @@ -466,12 +469,15 @@ async sendTextMessageAction( messageInfo: RawTextMessageInfo, - // eslint-disable-next-line no-unused-vars threadInfo: ThreadInfo, - // eslint-disable-next-line no-unused-vars parentThreadInfo: ?ThreadInfo, ): Promise { try { + await this.props.textMessageCreationSideEffectsFunc( + messageInfo, + threadInfo, + parentThreadInfo, + ); const { localID } = messageInfo; invariant( localID !== null && localID !== undefined, @@ -1422,6 +1428,8 @@ const dispatch = useDispatch(); const mediaReportsEnabled = useIsReportEnabled('mediaReports'); const staffCanSee = useStaffCanSee(); + const textMessageCreationSideEffectsFunc = + useMessageCreationSideEffectsFunc(messageTypes.TEXT); return ( ); }); diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js --- a/web/input/input-state-container.react.js +++ b/web/input/input-state-container.react.js @@ -37,7 +37,9 @@ import { createMediaMessageInfo, localIDPrefix, + useMessageCreationSideEffectsFunc, } from 'lib/shared/message-utils.js'; +import type { CreationSideEffectsFunc } from 'lib/shared/messages/message-spec.js'; import { createRealThreadFromPendingThread, draftKeyFromThreadID, @@ -125,6 +127,7 @@ +sendCallbacks: $ReadOnlyArray<() => mixed>, +registerSendCallback: (() => mixed) => void, +unregisterSendCallback: (() => mixed) => void, + +textMessageCreationSideEffectsFunc: CreationSideEffectsFunc, }; type State = { +pendingUploads: { @@ -1046,12 +1049,15 @@ async sendTextMessageAction( messageInfo: RawTextMessageInfo, - // eslint-disable-next-line no-unused-vars threadInfo: ThreadInfo, - // eslint-disable-next-line no-unused-vars parentThreadInfo: ?ThreadInfo, ): Promise { try { + await this.props.textMessageCreationSideEffectsFunc( + messageInfo, + threadInfo, + parentThreadInfo, + ); const { localID } = messageInfo; invariant( localID !== null && localID !== undefined, @@ -1363,6 +1369,8 @@ }, [], ); + const textMessageCreationSideEffectsFunc = + useMessageCreationSideEffectsFunc(messageTypes.TEXT); return ( ); });