@@ -13,6 +13,7 @@ import type {
1313 UserOrBot ,
1414 Dispatch ,
1515 Dimensions ,
16+ Subscription ,
1617} from '../types' ;
1718import { connect } from '../react-redux' ;
1819import {
@@ -27,18 +28,27 @@ import * as api from '../api';
2728import { FloatingActionButton , Input } from '../common' ;
2829import { showErrorAlert } from '../utils/info' ;
2930import { IconDone , IconSend } from '../common/Icons' ;
30- import { isStreamNarrow , isStreamOrTopicNarrow , topicNarrow } from '../utils/narrow' ;
31+ import {
32+ isStreamNarrow ,
33+ isStreamOrTopicNarrow ,
34+ topicNarrow ,
35+ isPrivateNarrow ,
36+ } from '../utils/narrow' ;
3137import ComposeMenu from './ComposeMenu' ;
3238import getComposeInputPlaceholder from './getComposeInputPlaceholder' ;
3339import NotSubscribed from '../message/NotSubscribed' ;
3440import AnnouncementOnly from '../message/AnnouncementOnly' ;
41+ import MentionedUserNotSubscribed from '../message/MentionedUserNotSubscribed' ;
42+ import AnimatedScaleComponent from '../animation/AnimatedScaleComponent' ;
3543
3644import {
3745 getAuth ,
3846 getIsAdmin ,
3947 getSession ,
4048 getLastMessageTopic ,
4149 getActiveUsersByEmail ,
50+ getStreamInNarrow ,
51+ getSubscriptionForId ,
4252} from '../selectors' ;
4353import {
4454 getIsActiveStreamSubscribed ,
@@ -60,6 +70,7 @@ type SelectorProps = {|
6070 editMessage : ?EditMessage ,
6171 draft : string ,
6272 lastMessageTopic : string ,
73+ subscription : Subscription | void ,
6374| } ;
6475
6576type Props = $ReadOnly < { |
@@ -83,6 +94,7 @@ type State = {|
8394 message : string ,
8495 height : number ,
8596 selection : InputSelection ,
97+ unsubscribedMentions : UserOrBot [ ] ,
8698| } ;
8799
88100export const updateTextInput = ( textInput : ?TextInput , text : string ) : void => {
@@ -122,6 +134,7 @@ class ComposeBox extends PureComponent<Props, State> {
122134 topic : this . props . lastMessageTopic ,
123135 message : this . props . draft ,
124136 selection : { start : 0 , end : 0 } ,
137+ unsubscribedMentions : [ ] ,
125138 } ;
126139
127140 componentWillUnmount ( ) {
@@ -184,6 +197,64 @@ class ComposeBox extends PureComponent<Props, State> {
184197 dispatch ( draftUpdate ( narrow , message ) ) ;
185198 } ;
186199
200+ handleMentionSubscribedCheck = ( message : string ) => {
201+ const { usersByEmail, subscription, narrow } = this . props ;
202+ if ( isPrivateNarrow ( narrow ) ) {
203+ return ;
204+ }
205+
206+ const unformattedMessage = message . split ( '**' ) [ 1 ] ;
207+
208+ // We skip user groups, for which autocompletes are of the form
209+ // `*<user_group_name>*`, and therefore, message.split('**')[1]
210+ // is undefined.
211+ if ( unformattedMessage === undefined ) {
212+ return ;
213+ }
214+ const [ userFullName , userId ] = unformattedMessage . split ( '|' ) ;
215+ const unsubscribedMentions = this . state . unsubscribedMentions . slice ( ) ;
216+ let mentionedUser ;
217+ // eslint-disable-next-line no-unused-vars
218+ for ( const [ email , user ] of usersByEmail ) {
219+ if ( userId !== undefined ) {
220+ if ( user . user_id === userId ) {
221+ mentionedUser = user ;
222+ break ;
223+ }
224+ } else if ( user . full_name === userFullName ) {
225+ mentionedUser = user ;
226+ break ;
227+ }
228+ }
229+ if (
230+ mentionedUser
231+ && subscription
232+ && ! subscription . subscribers . includes ( mentionedUser . user_id )
233+ && ! unsubscribedMentions . includes ( mentionedUser )
234+ ) {
235+ unsubscribedMentions . push ( mentionedUser ) ;
236+ this . setState ( { unsubscribedMentions } ) ;
237+ }
238+ } ;
239+
240+ handleMentionWarningDismiss = ( user : UserOrBot ) => {
241+ this . setState ( prevState => ( {
242+ unsubscribedMentions : prevState . unsubscribedMentions . filter (
243+ ( x : UserOrBot ) => x . user_id !== user . user_id ,
244+ ) ,
245+ } ) ) ;
246+ } ;
247+
248+ clearMentionWarnings = ( ) => {
249+ this . setState ( { unsubscribedMentions : [ ] } ) ;
250+ } ;
251+
252+ processAutocomplete = ( completion : string , completionType : string ) => {
253+ if ( completionType === '@' ) {
254+ this . handleMentionSubscribedCheck ( completion ) ;
255+ }
256+ } ;
257+
187258 handleMessageAutocomplete = ( message : string ) => {
188259 this . setMessageInputValue ( message ) ;
189260 } ;
@@ -250,6 +321,7 @@ class ComposeBox extends PureComponent<Props, State> {
250321 dispatch ( addToOutbox ( this . getDestinationNarrow ( ) , message ) ) ;
251322
252323 this . setMessageInputValue ( '' ) ;
324+ this . clearMentionWarnings ( ) ;
253325 dispatch ( sendTypingStop ( narrow ) ) ;
254326 } ;
255327
@@ -332,7 +404,15 @@ class ComposeBox extends PureComponent<Props, State> {
332404 } ;
333405
334406 render ( ) {
335- const { isTopicFocused, isMenuExpanded, height, message, topic, selection } = this . state ;
407+ const {
408+ isTopicFocused,
409+ isMenuExpanded,
410+ height,
411+ message,
412+ topic,
413+ selection,
414+ unsubscribedMentions,
415+ } = this . state ;
336416 const {
337417 ownEmail,
338418 narrow,
@@ -344,6 +424,18 @@ class ComposeBox extends PureComponent<Props, State> {
344424 isSubscribed,
345425 } = this . props ;
346426
427+ const mentionWarnings = [ ] ;
428+ for ( const user of unsubscribedMentions ) {
429+ mentionWarnings . push (
430+ < MentionedUserNotSubscribed
431+ narrow = { narrow }
432+ user = { user }
433+ onDismiss = { this . handleMentionWarningDismiss }
434+ key = { user . user_id }
435+ /> ,
436+ ) ;
437+ }
438+
347439 if ( ! isSubscribed ) {
348440 return < NotSubscribed narrow = { narrow } /> ;
349441 } else if ( isAnnouncementOnly && ! isAdmin ) {
@@ -358,6 +450,9 @@ class ComposeBox extends PureComponent<Props, State> {
358450
359451 return (
360452 < View style = { this . styles . wrapper } >
453+ < AnimatedScaleComponent visible = { mentionWarnings . length !== 0 } >
454+ { mentionWarnings }
455+ </ AnimatedScaleComponent >
361456 < View style = { [ this . styles . autocompleteWrapper , { marginBottom : height } ] } >
362457 < TopicAutocomplete
363458 isFocused = { isTopicFocused }
@@ -370,6 +465,7 @@ class ComposeBox extends PureComponent<Props, State> {
370465 selection = { selection }
371466 text = { message }
372467 onAutocomplete = { this . handleMessageAutocomplete }
468+ processAutoComplete = { this . processAutocomplete }
373469 />
374470 </ View >
375471 < View style = { [ this . styles . composeBox , style ] } onLayout = { this . handleLayoutChange } >
@@ -435,4 +531,7 @@ export default connect<SelectorProps, _, _>((state, props) => ({
435531 editMessage : getSession ( state ) . editMessage ,
436532 draft : getDraftForNarrow ( state , props . narrow ) ,
437533 lastMessageTopic : getLastMessageTopic ( state , props . narrow ) ,
534+ subscription : isPrivateNarrow ( props . narrow )
535+ ? undefined
536+ : getSubscriptionForId ( state , getStreamInNarrow ( state , props . narrow ) . stream_id ) ,
438537} ) ) ( ComposeBox ) ;
0 commit comments