@@ -26,18 +26,26 @@ import * as api from '../api';
2626import  {  FloatingActionButton ,  Input  }  from  '../common' ; 
2727import  {  showErrorAlert  }  from  '../utils/info' ; 
2828import  {  IconDone ,  IconSend  }  from  '../common/Icons' ; 
29- import  {  isStreamNarrow ,  isStreamOrTopicNarrow ,  topicNarrow  }  from  '../utils/narrow' ; 
29+ import  { 
30+   isStreamNarrow , 
31+   isStreamOrTopicNarrow , 
32+   topicNarrow , 
33+   isPrivateNarrow , 
34+ }  from  '../utils/narrow' ; 
3035import  ComposeMenu  from  './ComposeMenu' ; 
3136import  getComposeInputPlaceholder  from  './getComposeInputPlaceholder' ; 
3237import  NotSubscribed  from  '../message/NotSubscribed' ; 
3338import  AnnouncementOnly  from  '../message/AnnouncementOnly' ; 
39+ import  MentionedUserNotSubscribed  from  '../message/MentionedUserNotSubscribed' ; 
40+ import  AnimatedScaleComponent  from  '../animation/AnimatedScaleComponent' ; 
3441
3542import  { 
3643  getAuth , 
3744  getIsAdmin , 
3845  getSession , 
3946  getLastMessageTopic , 
4047  getActiveUsersByEmail , 
48+   getStreamInNarrow , 
4149}  from  '../selectors' ; 
4250import  { 
4351  getIsActiveStreamSubscribed , 
@@ -58,6 +66,7 @@ type SelectorProps = {|
5866  isSubscribed : boolean , 
5967  draft : string , 
6068  lastMessageTopic : string , 
69+   streamId : number , 
6170| } ; 
6271
6372type  Props  =  $ReadOnly < { | 
@@ -83,6 +92,7 @@ type State = {|
8392  message : string , 
8493  height : number , 
8594  selection : InputSelection , 
95+   unsubscribedMentions : number [ ] , 
8696| } ; 
8797
8898export  const  updateTextInput  =  ( textInput : ?TextInput ,  text : string ) : void  =>  { 
@@ -122,6 +132,7 @@ class ComposeBox extends PureComponent<Props, State> {
122132    topic : this . props . lastMessageTopic , 
123133    message : this . props . draft , 
124134    selection : {  start : 0 ,  end : 0  } , 
135+     unsubscribedMentions : [ ] , 
125136  } ; 
126137
127138  componentWillUnmount ( ) { 
@@ -184,6 +195,64 @@ class ComposeBox extends PureComponent<Props, State> {
184195    dispatch ( draftUpdate ( narrow ,  message ) ) ; 
185196  } ; 
186197
198+   handleMentionSubscribedCheck  =  async  ( message : string )  =>  { 
199+     const  {  usersByEmail,  narrow,  auth,  streamId }  =  this . props ; 
200+ 
201+     if  ( isPrivateNarrow ( narrow ) )  { 
202+       return ; 
203+     } 
204+     const  unformattedMessage  =  message . split ( '**' ) [ 1 ] ; 
205+ 
206+     // We skip user groups, for which autocompletes are of the form 
207+     // `*<user_group_name>*`, and therefore, message.split('**')[1] 
208+     // is undefined. 
209+     if  ( unformattedMessage  ===  undefined )  { 
210+       return ; 
211+     } 
212+     const  [ userFullName ,  userId ]  =  unformattedMessage . split ( '|' ) ; 
213+     const  unsubscribedMentions  =  this . state . unsubscribedMentions . slice ( ) ; 
214+     let mentionedUser : UserOrBot ; 
215+ 
216+     // eslint-disable-next-line no-unused-vars 
217+     for  ( const  [ email ,  user ]  of  usersByEmail )  { 
218+       if  ( userId  !==  undefined )  { 
219+         if  ( user . user_id  ===  userId )  { 
220+           mentionedUser  =  user ; 
221+           break ; 
222+         } 
223+       }  else  if  ( user . full_name  ===  userFullName )  { 
224+         mentionedUser  =  user ; 
225+         break ; 
226+       } 
227+     } 
228+     if  ( ! mentionedUser  ||  unsubscribedMentions . includes ( mentionedUser ) )  { 
229+       return ; 
230+     } 
231+ 
232+     if  ( ! ( await  api . getSubscriptionToStream ( auth ,  mentionedUser . user_id ,  streamId ) ) . is_subscribed )  { 
233+       unsubscribedMentions . push ( mentionedUser . user_id ) ; 
234+       this . setState ( {  unsubscribedMentions } ) ; 
235+     } 
236+   } ; 
237+ 
238+   handleMentionWarningDismiss  =  ( user : UserOrBot )  =>  { 
239+     this . setState ( prevState  =>  ( { 
240+       unsubscribedMentions : prevState . unsubscribedMentions . filter ( 
241+         ( x : number )  =>  x  !==  user . user_id , 
242+       ) , 
243+     } ) ) ; 
244+   } ; 
245+ 
246+   clearMentionWarnings  =  ( )  =>  { 
247+     this . setState ( {  unsubscribedMentions : [ ]  } ) ; 
248+   } ; 
249+ 
250+   processAutocomplete  =  ( completion : string ,  completionType : string )  =>  { 
251+     if  ( completionType  ===  '@' )  { 
252+       this . handleMentionSubscribedCheck ( completion ) ; 
253+     } 
254+   } ; 
255+ 
187256  handleMessageAutocomplete  =  ( message : string )  =>  { 
188257    this . setMessageInputValue ( message ) ; 
189258  } ; 
@@ -250,6 +319,7 @@ class ComposeBox extends PureComponent<Props, State> {
250319    dispatch ( addToOutbox ( this . getDestinationNarrow ( ) ,  message ) ) ; 
251320
252321    this . setMessageInputValue ( '' ) ; 
322+     this . clearMentionWarnings ( ) ; 
253323    dispatch ( sendTypingStop ( narrow ) ) ; 
254324  } ; 
255325
@@ -335,7 +405,15 @@ class ComposeBox extends PureComponent<Props, State> {
335405  } ; 
336406
337407  render ( ) { 
338-     const  {  isTopicFocused,  isMenuExpanded,  height,  message,  topic,  selection }  =  this . state ; 
408+     const  { 
409+       isTopicFocused, 
410+       isMenuExpanded, 
411+       height, 
412+       message, 
413+       topic, 
414+       selection, 
415+       unsubscribedMentions, 
416+     }  =  this . state ; 
339417    const  { 
340418      ownEmail, 
341419      narrow, 
@@ -347,6 +425,18 @@ class ComposeBox extends PureComponent<Props, State> {
347425      isSubscribed, 
348426    }  =  this . props ; 
349427
428+     const  mentionWarnings  =  [ ] ; 
429+     for  ( const  userId  of  unsubscribedMentions )  { 
430+       mentionWarnings . push ( 
431+         < MentionedUserNotSubscribed 
432+           narrow = { narrow } 
433+           userId = { userId } 
434+           onDismiss = { this . handleMentionWarningDismiss } 
435+           key = { userId } 
436+         /> , 
437+       ) ; 
438+     } 
439+ 
350440    if  ( ! isSubscribed )  { 
351441      return  < NotSubscribed  narrow = { narrow }  /> ; 
352442    }  else  if  ( isAnnouncementOnly  &&  ! isAdmin )  { 
@@ -361,6 +451,9 @@ class ComposeBox extends PureComponent<Props, State> {
361451
362452    return  ( 
363453      < View  style = { this . styles . wrapper } > 
454+         < AnimatedScaleComponent  visible = { mentionWarnings . length  !==  0 } > 
455+           { mentionWarnings } 
456+         </ AnimatedScaleComponent > 
364457        < View  style = { [ this . styles . autocompleteWrapper ,  {  marginBottom : height  } ] } > 
365458          < TopicAutocomplete 
366459            isFocused = { isTopicFocused } 
@@ -373,6 +466,7 @@ class ComposeBox extends PureComponent<Props, State> {
373466            selection = { selection } 
374467            text = { message } 
375468            onAutocomplete = { this . handleMessageAutocomplete } 
469+             processAutoComplete = { this . processAutocomplete } 
376470          /> 
377471        </ View > 
378472        < View  style = { [ this . styles . composeBox ,  style ] }  onLayout = { this . handleLayoutChange } > 
@@ -437,4 +531,5 @@ export default connect<SelectorProps, _, _>((state, props) => ({
437531  isSubscribed : getIsActiveStreamSubscribed ( state ,  props . narrow ) , 
438532  draft : getDraftForNarrow ( state ,  props . narrow ) , 
439533  lastMessageTopic : getLastMessageTopic ( state ,  props . narrow ) , 
534+   streamId : getStreamInNarrow ( state ,  props . narrow ) . stream_id , 
440535} ) ) ( ComposeBox ) ; 
0 commit comments