Archive Conversation
This commit is contained in:
		
					parent
					
						
							
								d72f89d776
							
						
					
				
			
			
				commit
				
					
						6ffbc0ac06
					
				
			
		
					 20 changed files with 568 additions and 109 deletions
				
			
		| 
						 | 
					@ -187,6 +187,27 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  "archivedConversations": {
 | 
				
			||||||
 | 
					    "message": "Archived Conversations",
 | 
				
			||||||
 | 
					    "description":
 | 
				
			||||||
 | 
					      "Shown in place of the search box when showing archived conversation list"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "archiveHelperText": {
 | 
				
			||||||
 | 
					    "message":
 | 
				
			||||||
 | 
					      "These conversations are archived and will only appear in the Inbox if new messages are received.",
 | 
				
			||||||
 | 
					    "description":
 | 
				
			||||||
 | 
					      "Shown at the top of the archived converations list in the left pane"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "archiveConversation": {
 | 
				
			||||||
 | 
					    "message": "Archive Conversation",
 | 
				
			||||||
 | 
					    "description":
 | 
				
			||||||
 | 
					      "Shown in menu for conversation, and moves conversation out of main conversation list"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "moveConversationToInbox": {
 | 
				
			||||||
 | 
					    "message": "Move Converstion to Inbox",
 | 
				
			||||||
 | 
					    "description":
 | 
				
			||||||
 | 
					      "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "chooseDirectory": {
 | 
					  "chooseDirectory": {
 | 
				
			||||||
    "message": "Choose folder",
 | 
					    "message": "Choose folder",
 | 
				
			||||||
    "description": "Button to allow the user to find a folder on disk"
 | 
					    "description": "Button to allow the user to find a folder on disk"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -312,6 +312,7 @@
 | 
				
			||||||
      const result = {
 | 
					      const result = {
 | 
				
			||||||
        id: this.id,
 | 
					        id: this.id,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        isArchived: this.get('isArchived'),
 | 
				
			||||||
        activeAt: this.get('active_at'),
 | 
					        activeAt: this.get('active_at'),
 | 
				
			||||||
        avatarPath: this.getAvatarPath(),
 | 
					        avatarPath: this.getAvatarPath(),
 | 
				
			||||||
        color,
 | 
					        color,
 | 
				
			||||||
| 
						 | 
					@ -889,6 +890,7 @@
 | 
				
			||||||
          lastMessageStatus: 'sending',
 | 
					          lastMessageStatus: 'sending',
 | 
				
			||||||
          active_at: now,
 | 
					          active_at: now,
 | 
				
			||||||
          timestamp: now,
 | 
					          timestamp: now,
 | 
				
			||||||
 | 
					          isArchived: false,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        await window.Signal.Data.updateConversation(this.id, this.attributes, {
 | 
					        await window.Signal.Data.updateConversation(this.id, this.attributes, {
 | 
				
			||||||
          Conversation: Whisper.Conversation,
 | 
					          Conversation: Whisper.Conversation,
 | 
				
			||||||
| 
						 | 
					@ -1170,6 +1172,13 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async setArchived(isArchived) {
 | 
				
			||||||
 | 
					      this.set({ isArchived });
 | 
				
			||||||
 | 
					      await window.Signal.Data.updateConversation(this.id, this.attributes, {
 | 
				
			||||||
 | 
					        Conversation: Whisper.Conversation,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async updateExpirationTimer(
 | 
					    async updateExpirationTimer(
 | 
				
			||||||
      providedExpireTimer,
 | 
					      providedExpireTimer,
 | 
				
			||||||
      providedSource,
 | 
					      providedSource,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1645,10 +1645,10 @@
 | 
				
			||||||
                c.onReadMessage(message);
 | 
					                c.onReadMessage(message);
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              conversation.set(
 | 
					              conversation.set({
 | 
				
			||||||
                'unreadCount',
 | 
					                unreadCount: conversation.get('unreadCount') + 1,
 | 
				
			||||||
                conversation.get('unreadCount') + 1
 | 
					                isArchived: false,
 | 
				
			||||||
              );
 | 
					              });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
/* global Signal:false */
 | 
					/* global Signal:false */
 | 
				
			||||||
/* global Backbone: false */
 | 
					/* global Backbone: false */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* global ConversationController: false */
 | 
					 | 
				
			||||||
/* global drawAttention: false */
 | 
					/* global drawAttention: false */
 | 
				
			||||||
/* global i18n: false */
 | 
					/* global i18n: false */
 | 
				
			||||||
/* global isFocused: false */
 | 
					/* global isFocused: false */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -185,9 +185,12 @@
 | 
				
			||||||
          profileName: this.model.getProfileName(),
 | 
					          profileName: this.model.getProfileName(),
 | 
				
			||||||
          color: this.model.getColor(),
 | 
					          color: this.model.getColor(),
 | 
				
			||||||
          avatarPath: this.model.getAvatarPath(),
 | 
					          avatarPath: this.model.getAvatarPath(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          isVerified: this.model.isVerified(),
 | 
					          isVerified: this.model.isVerified(),
 | 
				
			||||||
          isMe: this.model.isMe(),
 | 
					          isMe: this.model.isMe(),
 | 
				
			||||||
          isGroup: !this.model.isPrivate(),
 | 
					          isGroup: !this.model.isPrivate(),
 | 
				
			||||||
 | 
					          isArchived: this.model.get('isArchived'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expirationSettingName,
 | 
					          expirationSettingName,
 | 
				
			||||||
          showBackButton: Boolean(this.panels && this.panels.length),
 | 
					          showBackButton: Boolean(this.panels && this.panels.length),
 | 
				
			||||||
          timerOptions: Whisper.ExpirationTimerOptions.map(item => ({
 | 
					          timerOptions: Whisper.ExpirationTimerOptions.map(item => ({
 | 
				
			||||||
| 
						 | 
					@ -217,6 +220,14 @@
 | 
				
			||||||
            this.resetPanel();
 | 
					            this.resetPanel();
 | 
				
			||||||
            this.updateHeader();
 | 
					            this.updateHeader();
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          onArchive: () => {
 | 
				
			||||||
 | 
					            this.unload();
 | 
				
			||||||
 | 
					            this.model.setArchived(true);
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          onMoveToInbox: () => {
 | 
				
			||||||
 | 
					            this.model.setArchived(false);
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      this.titleView = new Whisper.ReactWrapperView({
 | 
					      this.titleView = new Whisper.ReactWrapperView({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -220,7 +220,7 @@
 | 
				
			||||||
      window.location.reload();
 | 
					      window.location.reload();
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    async openConversation(id, messageId) {
 | 
					    async openConversation(id, messageId) {
 | 
				
			||||||
      const conversation = await window.ConversationController.getOrCreateAndWait(
 | 
					      const conversation = await ConversationController.getOrCreateAndWait(
 | 
				
			||||||
        id,
 | 
					        id,
 | 
				
			||||||
        'private'
 | 
					        'private'
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2986,15 +2986,74 @@
 | 
				
			||||||
  flex-grow: 0;
 | 
					  flex-grow: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.module-left-pane__archive-header {
 | 
				
			||||||
 | 
					  height: 48px;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  display: inline-flex;
 | 
				
			||||||
 | 
					  flex-direction: row;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  border-bottom: 1px solid $color-gray-15;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.module-left-pane__to-inbox-button {
 | 
				
			||||||
 | 
					  margin-left: 2px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  width: 35px;
 | 
				
			||||||
 | 
					  height: 35px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  @include color-svg('../images/back.svg', $color-gray-60);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.module-left-pane__archive-header-text {
 | 
				
			||||||
 | 
					  color: $color-gray-90;
 | 
				
			||||||
 | 
					  font-size: 16px;
 | 
				
			||||||
 | 
					  font-weight: 300px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.module-left-pane__list {
 | 
					.module-left-pane__list {
 | 
				
			||||||
  flex-grow: 1;
 | 
					  flex-grow: 1;
 | 
				
			||||||
  flex-shrink: 1;
 | 
					  flex-shrink: 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.module-left-pane__archive-helper-text {
 | 
				
			||||||
 | 
					  padding: 1em;
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
 | 
					  color: $color-gray-60;
 | 
				
			||||||
 | 
					  background-color: $color-gray-05;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.module-left-pane__virtual-list {
 | 
					.module-left-pane__virtual-list {
 | 
				
			||||||
  outline: none;
 | 
					  outline: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.module-left-pane__archived-button {
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					  height: 64px;
 | 
				
			||||||
 | 
					  line-height: 64px;
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  font-weight: 300;
 | 
				
			||||||
 | 
					  color: $color-gray-60;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  &:hover {
 | 
				
			||||||
 | 
					    background-color: $color-gray-05;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.module-left-pane__archived-button__archived-count {
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
 | 
					  font-weight: 300;
 | 
				
			||||||
 | 
					  color: $color-gray-60;
 | 
				
			||||||
 | 
					  background-color: $color-gray-05;
 | 
				
			||||||
 | 
					  padding: 6px;
 | 
				
			||||||
 | 
					  padding-top: 1px;
 | 
				
			||||||
 | 
					  padding-bottom: 1px;
 | 
				
			||||||
 | 
					  border-radius: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Module: Start New Conversation
 | 
					// Module: Start New Conversation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.module-start-new-conversation {
 | 
					.module-start-new-conversation {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1346,7 +1346,7 @@ body.dark-theme {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .module-main-header__search__cancel-icon {
 | 
					  .module-main-header__search__cancel-icon {
 | 
				
			||||||
    @include color-svg('../images/x.svg', $color-gray-25);
 | 
					    @include color-svg('../images/x-16.svg', $color-gray-25);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Module: Image
 | 
					  // Module: Image
 | 
				
			||||||
| 
						 | 
					@ -1382,7 +1382,7 @@ body.dark-theme {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .module-attachments__close-button {
 | 
					  .module-attachments__close-button {
 | 
				
			||||||
    @include color-svg('../images/x.svg', $color-gray-45);
 | 
					    @include color-svg('../images/x-16.svg', $color-gray-45);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Module: Staged Generic Attachment
 | 
					  // Module: Staged Generic Attachment
 | 
				
			||||||
| 
						 | 
					@ -1482,7 +1482,7 @@ body.dark-theme {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .module-caption-editor__close-button {
 | 
					  .module-caption-editor__close-button {
 | 
				
			||||||
    @include color-svg('../images/x.svg', $color-white);
 | 
					    @include color-svg('../images/x-16.svg', $color-white);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .module-caption-editor__media-container {
 | 
					  .module-caption-editor__media-container {
 | 
				
			||||||
| 
						 | 
					@ -1553,6 +1553,35 @@ body.dark-theme {
 | 
				
			||||||
    border-right: 1px solid $color-gray-75;
 | 
					    border-right: 1px solid $color-gray-75;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .module-left-pane__archive-header {
 | 
				
			||||||
 | 
					    border-bottom: 1px solid $color-gray-75;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .module-left-pane__to-inbox-button {
 | 
				
			||||||
 | 
					    background-color: $color-gray-25;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .module-left-pane__archive-header-text {
 | 
				
			||||||
 | 
					    color: $color-gray-05;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .module-left-pane__archive-helper-text {
 | 
				
			||||||
 | 
					    color: $color-gray-25;
 | 
				
			||||||
 | 
					    background-color: $color-gray-75;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .module-left-pane__archived-button {
 | 
				
			||||||
 | 
					    color: $color-gray-25;
 | 
				
			||||||
 | 
					    &:hover {
 | 
				
			||||||
 | 
					      background-color: $color-gray-75;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .module-left-pane__archived-button__archived-count {
 | 
				
			||||||
 | 
					    color: $color-gray-25;
 | 
				
			||||||
 | 
					    background-color: $color-gray-75;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Module: Start New Conversation
 | 
					  // Module: Start New Conversation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .module-start-new-conversation {
 | 
					  .module-start-new-conversation {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,8 +129,14 @@ window.searchResults.messages = [
 | 
				
			||||||
<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
 | 
					<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
 | 
				
			||||||
  <LeftPane
 | 
					  <LeftPane
 | 
				
			||||||
    searchResults={window.searchResults}
 | 
					    searchResults={window.searchResults}
 | 
				
			||||||
    openConversation={result => console.log('openConversation', result)}
 | 
					    startNewConversation={(query, options) =>
 | 
				
			||||||
    openMessage={result => console.log('onClickMessage', result)}
 | 
					      console.log('startNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    openConversationInternal={(id, messageId) =>
 | 
				
			||||||
 | 
					      console.log('openConversation', id, messageId)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    showArchivedConversations={() => console.log('showArchivedConversations')}
 | 
				
			||||||
 | 
					    showInbox={() => console.log('showInbox')}
 | 
				
			||||||
    renderMainHeader={() => (
 | 
					    renderMainHeader={() => (
 | 
				
			||||||
      <MainHeader
 | 
					      <MainHeader
 | 
				
			||||||
        searchTerm="Hi there!"
 | 
					        searchTerm="Hi there!"
 | 
				
			||||||
| 
						 | 
					@ -151,8 +157,74 @@ window.searchResults.messages = [
 | 
				
			||||||
<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
 | 
					<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
 | 
				
			||||||
  <LeftPane
 | 
					  <LeftPane
 | 
				
			||||||
    conversations={window.searchResults.conversations}
 | 
					    conversations={window.searchResults.conversations}
 | 
				
			||||||
    openConversation={result => console.log('openConversation', result)}
 | 
					    archivedConversations={[]}
 | 
				
			||||||
    openMessage={result => console.log('onClickMessage', result)}
 | 
					    startNewConversation={(query, options) =>
 | 
				
			||||||
 | 
					      console.log('startNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    openConversationInternal={(id, messageId) =>
 | 
				
			||||||
 | 
					      console.log('openConversation', id, messageId)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    showArchivedConversations={() => console.log('showArchivedConversations')}
 | 
				
			||||||
 | 
					    showInbox={() => console.log('showInbox')}
 | 
				
			||||||
 | 
					    renderMainHeader={() => (
 | 
				
			||||||
 | 
					      <MainHeader
 | 
				
			||||||
 | 
					        searchTerm="Hi there!"
 | 
				
			||||||
 | 
					        search={result => console.log('search', result)}
 | 
				
			||||||
 | 
					        updateSearch={result => console.log('updateSearch', result)}
 | 
				
			||||||
 | 
					        clearSearch={result => console.log('clearSearch', result)}
 | 
				
			||||||
 | 
					        i18n={util.i18n}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    i18n={util.i18n}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					</util.LeftPaneContext>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Showing inbox, with some archived
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```jsx
 | 
				
			||||||
 | 
					<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
 | 
				
			||||||
 | 
					  <LeftPane
 | 
				
			||||||
 | 
					    conversations={window.searchResults.conversations.slice(0, 2)}
 | 
				
			||||||
 | 
					    archivedConversations={window.searchResults.conversations.slice(2)}
 | 
				
			||||||
 | 
					    startNewConversation={(query, options) =>
 | 
				
			||||||
 | 
					      console.log('startNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    openConversationInternal={(id, messageId) =>
 | 
				
			||||||
 | 
					      console.log('openConversation', id, messageId)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    showArchivedConversations={() => console.log('showArchivedConversations')}
 | 
				
			||||||
 | 
					    showInbox={() => console.log('showInbox')}
 | 
				
			||||||
 | 
					    renderMainHeader={() => (
 | 
				
			||||||
 | 
					      <MainHeader
 | 
				
			||||||
 | 
					        searchTerm="Hi there!"
 | 
				
			||||||
 | 
					        search={result => console.log('search', result)}
 | 
				
			||||||
 | 
					        updateSearch={result => console.log('updateSearch', result)}
 | 
				
			||||||
 | 
					        clearSearch={result => console.log('clearSearch', result)}
 | 
				
			||||||
 | 
					        i18n={util.i18n}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    i18n={util.i18n}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					</util.LeftPaneContext>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Showing archived conversations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```jsx
 | 
				
			||||||
 | 
					<util.LeftPaneContext theme={util.theme} style={{ height: '200px' }}>
 | 
				
			||||||
 | 
					  <LeftPane
 | 
				
			||||||
 | 
					    conversations={window.searchResults.conversations.slice(0, 2)}
 | 
				
			||||||
 | 
					    archivedConversations={window.searchResults.conversations.slice(2)}
 | 
				
			||||||
 | 
					    showArchived={true}
 | 
				
			||||||
 | 
					    startNewConversation={(query, options) =>
 | 
				
			||||||
 | 
					      console.log('startNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    openConversationInternal={(id, messageId) =>
 | 
				
			||||||
 | 
					      console.log('openConversation', id, messageId)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    showArchivedConversations={() => console.log('showArchivedConversations')}
 | 
				
			||||||
 | 
					    showInbox={() => console.log('showInbox')}
 | 
				
			||||||
    renderMainHeader={() => (
 | 
					    renderMainHeader={() => (
 | 
				
			||||||
      <MainHeader
 | 
					      <MainHeader
 | 
				
			||||||
        searchTerm="Hi there!"
 | 
					        searchTerm="Hi there!"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,19 +13,27 @@ import { LocalizerType } from '../types/Util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Props {
 | 
					export interface Props {
 | 
				
			||||||
  conversations?: Array<ConversationListItemPropsType>;
 | 
					  conversations?: Array<ConversationListItemPropsType>;
 | 
				
			||||||
 | 
					  archivedConversations?: Array<ConversationListItemPropsType>;
 | 
				
			||||||
  searchResults?: SearchResultsProps;
 | 
					  searchResults?: SearchResultsProps;
 | 
				
			||||||
 | 
					  showArchived?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Action Creators
 | 
					  // Action Creators
 | 
				
			||||||
  startNewConversation: () => void;
 | 
					  startNewConversation: (
 | 
				
			||||||
 | 
					    query: string,
 | 
				
			||||||
 | 
					    options: { regionCode: string }
 | 
				
			||||||
 | 
					  ) => void;
 | 
				
			||||||
  openConversationInternal: (id: string, messageId?: string) => void;
 | 
					  openConversationInternal: (id: string, messageId?: string) => void;
 | 
				
			||||||
 | 
					  showArchivedConversations: () => void;
 | 
				
			||||||
 | 
					  showInbox: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Render Props
 | 
					  // Render Props
 | 
				
			||||||
  renderMainHeader: () => JSX.Element;
 | 
					  renderMainHeader: () => JSX.Element;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
 | 
					// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
 | 
				
			||||||
type RowRendererParams = {
 | 
					type RowRendererParamsType = {
 | 
				
			||||||
  index: number;
 | 
					  index: number;
 | 
				
			||||||
  isScrolling: boolean;
 | 
					  isScrolling: boolean;
 | 
				
			||||||
  isVisible: boolean;
 | 
					  isVisible: boolean;
 | 
				
			||||||
| 
						 | 
					@ -35,12 +43,51 @@ type RowRendererParams = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class LeftPane extends React.Component<Props> {
 | 
					export class LeftPane extends React.Component<Props> {
 | 
				
			||||||
  public renderRow = ({ index, key, style }: RowRendererParams) => {
 | 
					  public listRef: React.RefObject<any> = React.createRef();
 | 
				
			||||||
    const { conversations, i18n, openConversationInternal } = this.props;
 | 
					
 | 
				
			||||||
    if (!conversations) {
 | 
					  public scrollToTop() {
 | 
				
			||||||
      return null;
 | 
					    if (this.listRef && this.listRef.current) {
 | 
				
			||||||
 | 
					      const { current } = this.listRef;
 | 
				
			||||||
 | 
					      current.scrollToRow(0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const conversation = conversations[index];
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public componentDidUpdate(prevProps: Props) {
 | 
				
			||||||
 | 
					    const { showArchived, searchResults } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isNotShowingSearchResults = !searchResults;
 | 
				
			||||||
 | 
					    const hasArchiveViewChanged = showArchived !== prevProps.showArchived;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isNotShowingSearchResults && hasArchiveViewChanged) {
 | 
				
			||||||
 | 
					      this.scrollToTop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public renderRow = ({
 | 
				
			||||||
 | 
					    index,
 | 
				
			||||||
 | 
					    key,
 | 
				
			||||||
 | 
					    style,
 | 
				
			||||||
 | 
					  }: RowRendererParamsType): JSX.Element => {
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
 | 
					      archivedConversations,
 | 
				
			||||||
 | 
					      conversations,
 | 
				
			||||||
 | 
					      i18n,
 | 
				
			||||||
 | 
					      openConversationInternal,
 | 
				
			||||||
 | 
					      showArchived,
 | 
				
			||||||
 | 
					    } = this.props;
 | 
				
			||||||
 | 
					    if (!conversations || !archivedConversations) {
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'renderRow: Tried to render without conversations or archivedConversations'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!showArchived && index === conversations.length) {
 | 
				
			||||||
 | 
					      return this.renderArchivedButton({ key, style });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const conversation = showArchived
 | 
				
			||||||
 | 
					      ? archivedConversations[index]
 | 
				
			||||||
 | 
					      : conversations[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <ConversationListItem
 | 
					      <ConversationListItem
 | 
				
			||||||
| 
						 | 
					@ -53,13 +100,50 @@ export class LeftPane extends React.Component<Props> {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public renderList() {
 | 
					  public renderArchivedButton({
 | 
				
			||||||
 | 
					    key,
 | 
				
			||||||
 | 
					    style,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    key: string;
 | 
				
			||||||
 | 
					    style: Object;
 | 
				
			||||||
 | 
					  }): JSX.Element {
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
 | 
					      archivedConversations,
 | 
				
			||||||
 | 
					      i18n,
 | 
				
			||||||
 | 
					      showArchivedConversations,
 | 
				
			||||||
 | 
					    } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!archivedConversations || !archivedConversations.length) {
 | 
				
			||||||
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'renderArchivedButton: Tried to render without archivedConversations'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        key={key}
 | 
				
			||||||
 | 
					        className="module-left-pane__archived-button"
 | 
				
			||||||
 | 
					        style={style}
 | 
				
			||||||
 | 
					        role="button"
 | 
				
			||||||
 | 
					        onClick={showArchivedConversations}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {i18n('archivedConversations')}{' '}
 | 
				
			||||||
 | 
					        <span className="module-left-pane__archived-button__archived-count">
 | 
				
			||||||
 | 
					          {archivedConversations.length}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public renderList(): JSX.Element {
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
 | 
					      archivedConversations,
 | 
				
			||||||
      i18n,
 | 
					      i18n,
 | 
				
			||||||
      conversations,
 | 
					      conversations,
 | 
				
			||||||
      openConversationInternal,
 | 
					      openConversationInternal,
 | 
				
			||||||
      startNewConversation,
 | 
					      startNewConversation,
 | 
				
			||||||
      searchResults,
 | 
					      searchResults,
 | 
				
			||||||
 | 
					      showArchived,
 | 
				
			||||||
    } = this.props;
 | 
					    } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (searchResults) {
 | 
					    if (searchResults) {
 | 
				
			||||||
| 
						 | 
					@ -73,22 +157,35 @@ export class LeftPane extends React.Component<Props> {
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!conversations || !conversations.length) {
 | 
					    if (!conversations || !archivedConversations) {
 | 
				
			||||||
      return null;
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'render: must provided conversations and archivedConverstions if no search results are provided'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // That extra 1 element added to the list is the 'archived converastions' button
 | 
				
			||||||
 | 
					    const length = showArchived
 | 
				
			||||||
 | 
					      ? archivedConversations.length
 | 
				
			||||||
 | 
					      : conversations.length + (archivedConversations.length ? 1 : 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Note: conversations is not a known prop for List, but it is required to ensure that
 | 
					    // Note: conversations is not a known prop for List, but it is required to ensure that
 | 
				
			||||||
    //   it re-renders when our conversation data changes. Otherwise it would just render
 | 
					    //   it re-renders when our conversation data changes. Otherwise it would just render
 | 
				
			||||||
    //   on startup and scroll.
 | 
					    //   on startup and scroll.
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="module-left-pane__list">
 | 
					      <div className="module-left-pane__list">
 | 
				
			||||||
 | 
					        {showArchived ? (
 | 
				
			||||||
 | 
					          <div className="module-left-pane__archive-helper-text">
 | 
				
			||||||
 | 
					            {i18n('archiveHelperText')}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        ) : null}
 | 
				
			||||||
        <AutoSizer>
 | 
					        <AutoSizer>
 | 
				
			||||||
          {({ height, width }) => (
 | 
					          {({ height, width }) => (
 | 
				
			||||||
            <List
 | 
					            <List
 | 
				
			||||||
              className="module-left-pane__virtual-list"
 | 
					              className="module-left-pane__virtual-list"
 | 
				
			||||||
 | 
					              ref={this.listRef}
 | 
				
			||||||
              conversations={conversations}
 | 
					              conversations={conversations}
 | 
				
			||||||
              height={height}
 | 
					              height={height}
 | 
				
			||||||
              rowCount={conversations.length}
 | 
					              rowCount={length}
 | 
				
			||||||
              rowHeight={64}
 | 
					              rowHeight={64}
 | 
				
			||||||
              rowRenderer={this.renderRow}
 | 
					              rowRenderer={this.renderRow}
 | 
				
			||||||
              width={width}
 | 
					              width={width}
 | 
				
			||||||
| 
						 | 
					@ -99,12 +196,31 @@ export class LeftPane extends React.Component<Props> {
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public render() {
 | 
					  public renderArchivedHeader(): JSX.Element {
 | 
				
			||||||
    const { renderMainHeader } = this.props;
 | 
					    const { i18n, showInbox } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className="module-left-pane__archive-header">
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          role="button"
 | 
				
			||||||
 | 
					          onClick={showInbox}
 | 
				
			||||||
 | 
					          className="module-left-pane__to-inbox-button"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <div className="module-left-pane__archive-header-text">
 | 
				
			||||||
 | 
					          {i18n('archivedConversations')}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public render(): JSX.Element {
 | 
				
			||||||
 | 
					    const { renderMainHeader, showArchived } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="module-left-pane">
 | 
					      <div className="module-left-pane">
 | 
				
			||||||
        <div className="module-left-pane__header">{renderMainHeader()}</div>
 | 
					        <div className="module-left-pane__header">
 | 
				
			||||||
 | 
					          {showArchived ? this.renderArchivedHeader() : renderMainHeader()}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        {this.renderList()}
 | 
					        {this.renderList()}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,7 +113,9 @@ window.searchResults.messages = [
 | 
				
			||||||
    i18n={util.i18n}
 | 
					    i18n={util.i18n}
 | 
				
			||||||
    onClickMessage={id => console.log('onClickMessage', id)}
 | 
					    onClickMessage={id => console.log('onClickMessage', id)}
 | 
				
			||||||
    onClickConversation={id => console.log('onClickConversation', id)}
 | 
					    onClickConversation={id => console.log('onClickConversation', id)}
 | 
				
			||||||
    onStartNewConversation={() => console.log('onStartNewConversation')}
 | 
					    onStartNewConversation={(query, options) =>
 | 
				
			||||||
 | 
					      console.log('onStartNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
</util.LeftPaneContext>;
 | 
					</util.LeftPaneContext>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
| 
						 | 
					@ -131,7 +133,9 @@ window.searchResults.messages = [
 | 
				
			||||||
    i18n={util.i18n}
 | 
					    i18n={util.i18n}
 | 
				
			||||||
    onClickMessage={id => console.log('onClickMessage', id)}
 | 
					    onClickMessage={id => console.log('onClickMessage', id)}
 | 
				
			||||||
    onClickConversation={id => console.log('onClickConversation', id)}
 | 
					    onClickConversation={id => console.log('onClickConversation', id)}
 | 
				
			||||||
    onStartNewConversation={() => console.log('onStartNewConversation')}
 | 
					    onStartNewConversation={(query, options) =>
 | 
				
			||||||
 | 
					      console.log('onStartNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
</util.LeftPaneContext>
 | 
					</util.LeftPaneContext>
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
| 
						 | 
					@ -147,7 +151,9 @@ window.searchResults.messages = [
 | 
				
			||||||
    i18n={util.i18n}
 | 
					    i18n={util.i18n}
 | 
				
			||||||
    onClickMessage={id => console.log('onClickMessage', id)}
 | 
					    onClickMessage={id => console.log('onClickMessage', id)}
 | 
				
			||||||
    onClickConversation={id => console.log('onClickConversation', id)}
 | 
					    onClickConversation={id => console.log('onClickConversation', id)}
 | 
				
			||||||
    onStartNewConversation={() => console.log('onStartNewConversation')}
 | 
					    onStartNewConversation={(query, options) =>
 | 
				
			||||||
 | 
					      console.log('onStartNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
</util.LeftPaneContext>
 | 
					</util.LeftPaneContext>
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
| 
						 | 
					@ -163,7 +169,9 @@ window.searchResults.messages = [
 | 
				
			||||||
    i18n={util.i18n}
 | 
					    i18n={util.i18n}
 | 
				
			||||||
    onClickMessage={id => console.log('onClickMessage', id)}
 | 
					    onClickMessage={id => console.log('onClickMessage', id)}
 | 
				
			||||||
    onClickConversation={id => console.log('onClickConversation', id)}
 | 
					    onClickConversation={id => console.log('onClickConversation', id)}
 | 
				
			||||||
    onStartNewConversation={() => console.log('onStartNewConversation')}
 | 
					    onStartNewConversation={(query, options) =>
 | 
				
			||||||
 | 
					      console.log('onStartNewConversation', query, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
</util.LeftPaneContext>
 | 
					</util.LeftPaneContext>
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ export type PropsData = {
 | 
				
			||||||
  conversations: Array<ConversationListItemPropsType>;
 | 
					  conversations: Array<ConversationListItemPropsType>;
 | 
				
			||||||
  hideMessagesHeader: boolean;
 | 
					  hideMessagesHeader: boolean;
 | 
				
			||||||
  messages: Array<MessageSearchResultPropsType>;
 | 
					  messages: Array<MessageSearchResultPropsType>;
 | 
				
			||||||
 | 
					  regionCode: string;
 | 
				
			||||||
  searchTerm: string;
 | 
					  searchTerm: string;
 | 
				
			||||||
  showStartNewConversation: boolean;
 | 
					  showStartNewConversation: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -23,12 +24,21 @@ export type PropsData = {
 | 
				
			||||||
type PropsHousekeeping = {
 | 
					type PropsHousekeeping = {
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
  openConversation: (id: string, messageId?: string) => void;
 | 
					  openConversation: (id: string, messageId?: string) => void;
 | 
				
			||||||
  startNewConversation: (id: string) => void;
 | 
					  startNewConversation: (
 | 
				
			||||||
 | 
					    query: string,
 | 
				
			||||||
 | 
					    options: { regionCode: string }
 | 
				
			||||||
 | 
					  ) => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = PropsData & PropsHousekeeping;
 | 
					type Props = PropsData & PropsHousekeeping;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SearchResults extends React.Component<Props> {
 | 
					export class SearchResults extends React.Component<Props> {
 | 
				
			||||||
 | 
					  public handleStartNewConversation = () => {
 | 
				
			||||||
 | 
					    const { regionCode, searchTerm, startNewConversation } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    startNewConversation(searchTerm, { regionCode });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public render() {
 | 
					  public render() {
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
      conversations,
 | 
					      conversations,
 | 
				
			||||||
| 
						 | 
					@ -37,7 +47,6 @@ export class SearchResults extends React.Component<Props> {
 | 
				
			||||||
      i18n,
 | 
					      i18n,
 | 
				
			||||||
      messages,
 | 
					      messages,
 | 
				
			||||||
      openConversation,
 | 
					      openConversation,
 | 
				
			||||||
      startNewConversation,
 | 
					 | 
				
			||||||
      searchTerm,
 | 
					      searchTerm,
 | 
				
			||||||
      showStartNewConversation,
 | 
					      showStartNewConversation,
 | 
				
			||||||
    } = this.props;
 | 
					    } = this.props;
 | 
				
			||||||
| 
						 | 
					@ -62,7 +71,7 @@ export class SearchResults extends React.Component<Props> {
 | 
				
			||||||
          <StartNewConversation
 | 
					          <StartNewConversation
 | 
				
			||||||
            phoneNumber={searchTerm}
 | 
					            phoneNumber={searchTerm}
 | 
				
			||||||
            i18n={i18n}
 | 
					            i18n={i18n}
 | 
				
			||||||
            onClick={startNewConversation}
 | 
					            onClick={this.handleStartNewConversation}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        ) : null}
 | 
					        ) : null}
 | 
				
			||||||
        {haveConversations ? (
 | 
					        {haveConversations ? (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ import { LocalizerType } from '../types/Util';
 | 
				
			||||||
export interface Props {
 | 
					export interface Props {
 | 
				
			||||||
  phoneNumber: string;
 | 
					  phoneNumber: string;
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
  onClick: (id: string) => void;
 | 
					  onClick: () => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class StartNewConversation extends React.PureComponent<Props> {
 | 
					export class StartNewConversation extends React.PureComponent<Props> {
 | 
				
			||||||
| 
						 | 
					@ -18,9 +18,7 @@ export class StartNewConversation extends React.PureComponent<Props> {
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        role="button"
 | 
					        role="button"
 | 
				
			||||||
        className="module-start-new-conversation"
 | 
					        className="module-start-new-conversation"
 | 
				
			||||||
        onClick={() => {
 | 
					        onClick={onClick}
 | 
				
			||||||
          onClick(phoneNumber);
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <Avatar
 | 
					        <Avatar
 | 
				
			||||||
          color="grey"
 | 
					          color="grey"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,17 +16,19 @@ interface TimerOption {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					interface Props {
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					 | 
				
			||||||
  isVerified: boolean;
 | 
					 | 
				
			||||||
  name?: string;
 | 
					 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  phoneNumber: string;
 | 
					  phoneNumber: string;
 | 
				
			||||||
  profileName?: string;
 | 
					  profileName?: string;
 | 
				
			||||||
  color: string;
 | 
					  color: string;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  avatarPath?: string;
 | 
					  avatarPath?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isVerified: boolean;
 | 
				
			||||||
  isMe: boolean;
 | 
					  isMe: boolean;
 | 
				
			||||||
  isGroup: boolean;
 | 
					  isGroup: boolean;
 | 
				
			||||||
 | 
					  isArchived: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  expirationSettingName?: string;
 | 
					  expirationSettingName?: string;
 | 
				
			||||||
  showBackButton: boolean;
 | 
					  showBackButton: boolean;
 | 
				
			||||||
  timerOptions: Array<TimerOption>;
 | 
					  timerOptions: Array<TimerOption>;
 | 
				
			||||||
| 
						 | 
					@ -39,6 +41,11 @@ interface Props {
 | 
				
			||||||
  onShowAllMedia: () => void;
 | 
					  onShowAllMedia: () => void;
 | 
				
			||||||
  onShowGroupMembers: () => void;
 | 
					  onShowGroupMembers: () => void;
 | 
				
			||||||
  onGoBack: () => void;
 | 
					  onGoBack: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onArchive: () => void;
 | 
				
			||||||
 | 
					  onMoveToInbox: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ConversationHeader extends React.Component<Props> {
 | 
					export class ConversationHeader extends React.Component<Props> {
 | 
				
			||||||
| 
						 | 
					@ -184,12 +191,15 @@ export class ConversationHeader extends React.Component<Props> {
 | 
				
			||||||
      i18n,
 | 
					      i18n,
 | 
				
			||||||
      isMe,
 | 
					      isMe,
 | 
				
			||||||
      isGroup,
 | 
					      isGroup,
 | 
				
			||||||
 | 
					      isArchived,
 | 
				
			||||||
      onDeleteMessages,
 | 
					      onDeleteMessages,
 | 
				
			||||||
      onResetSession,
 | 
					      onResetSession,
 | 
				
			||||||
      onSetDisappearingMessages,
 | 
					      onSetDisappearingMessages,
 | 
				
			||||||
      onShowAllMedia,
 | 
					      onShowAllMedia,
 | 
				
			||||||
      onShowGroupMembers,
 | 
					      onShowGroupMembers,
 | 
				
			||||||
      onShowSafetyNumber,
 | 
					      onShowSafetyNumber,
 | 
				
			||||||
 | 
					      onArchive,
 | 
				
			||||||
 | 
					      onMoveToInbox,
 | 
				
			||||||
      timerOptions,
 | 
					      timerOptions,
 | 
				
			||||||
    } = this.props;
 | 
					    } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -223,6 +233,13 @@ export class ConversationHeader extends React.Component<Props> {
 | 
				
			||||||
        {!isGroup ? (
 | 
					        {!isGroup ? (
 | 
				
			||||||
          <MenuItem onClick={onResetSession}>{i18n('resetSession')}</MenuItem>
 | 
					          <MenuItem onClick={onResetSession}>{i18n('resetSession')}</MenuItem>
 | 
				
			||||||
        ) : null}
 | 
					        ) : null}
 | 
				
			||||||
 | 
					        {isArchived ? (
 | 
				
			||||||
 | 
					          <MenuItem onClick={onMoveToInbox}>
 | 
				
			||||||
 | 
					            {i18n('moveConversationToInbox')}
 | 
				
			||||||
 | 
					          </MenuItem>
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <MenuItem onClick={onArchive}>{i18n('archiveConversation')}</MenuItem>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
        <MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
 | 
					        <MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
 | 
				
			||||||
      </ContextMenu>
 | 
					      </ContextMenu>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,6 +34,7 @@ export type MessageType = {
 | 
				
			||||||
export type ConversationType = {
 | 
					export type ConversationType = {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  name?: string;
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  isArchived: boolean;
 | 
				
			||||||
  activeAt?: number;
 | 
					  activeAt?: number;
 | 
				
			||||||
  timestamp: number;
 | 
					  timestamp: number;
 | 
				
			||||||
  lastMessage?: {
 | 
					  lastMessage?: {
 | 
				
			||||||
| 
						 | 
					@ -55,6 +56,7 @@ export type ConversationLookupType = {
 | 
				
			||||||
export type ConversationsStateType = {
 | 
					export type ConversationsStateType = {
 | 
				
			||||||
  conversationLookup: ConversationLookupType;
 | 
					  conversationLookup: ConversationLookupType;
 | 
				
			||||||
  selectedConversation?: string;
 | 
					  selectedConversation?: string;
 | 
				
			||||||
 | 
					  showArchived: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Actions
 | 
					// Actions
 | 
				
			||||||
| 
						 | 
					@ -97,6 +99,14 @@ export type SelectedConversationChangedActionType = {
 | 
				
			||||||
    messageId?: string;
 | 
					    messageId?: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					type ShowInboxActionType = {
 | 
				
			||||||
 | 
					  type: 'SHOW_INBOX';
 | 
				
			||||||
 | 
					  payload: null;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					type ShowArchivedConversationsActionType = {
 | 
				
			||||||
 | 
					  type: 'SHOW_ARCHIVED_CONVERSATIONS';
 | 
				
			||||||
 | 
					  payload: null;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ConversationActionType =
 | 
					export type ConversationActionType =
 | 
				
			||||||
  | ConversationAddedActionType
 | 
					  | ConversationAddedActionType
 | 
				
			||||||
| 
						 | 
					@ -104,7 +114,11 @@ export type ConversationActionType =
 | 
				
			||||||
  | ConversationRemovedActionType
 | 
					  | ConversationRemovedActionType
 | 
				
			||||||
  | RemoveAllConversationsActionType
 | 
					  | RemoveAllConversationsActionType
 | 
				
			||||||
  | MessageExpiredActionType
 | 
					  | MessageExpiredActionType
 | 
				
			||||||
  | SelectedConversationChangedActionType;
 | 
					  | SelectedConversationChangedActionType
 | 
				
			||||||
 | 
					  | MessageExpiredActionType
 | 
				
			||||||
 | 
					  | SelectedConversationChangedActionType
 | 
				
			||||||
 | 
					  | ShowInboxActionType
 | 
				
			||||||
 | 
					  | ShowArchivedConversationsActionType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Action Creators
 | 
					// Action Creators
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,6 +130,8 @@ export const actions = {
 | 
				
			||||||
  messageExpired,
 | 
					  messageExpired,
 | 
				
			||||||
  openConversationInternal,
 | 
					  openConversationInternal,
 | 
				
			||||||
  openConversationExternal,
 | 
					  openConversationExternal,
 | 
				
			||||||
 | 
					  showInbox,
 | 
				
			||||||
 | 
					  showArchivedConversations,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function conversationAdded(
 | 
					function conversationAdded(
 | 
				
			||||||
| 
						 | 
					@ -156,6 +172,7 @@ function removeAllConversations(): RemoveAllConversationsActionType {
 | 
				
			||||||
    payload: null,
 | 
					    payload: null,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function messageExpired(
 | 
					function messageExpired(
 | 
				
			||||||
  id: string,
 | 
					  id: string,
 | 
				
			||||||
  conversationId: string
 | 
					  conversationId: string
 | 
				
			||||||
| 
						 | 
					@ -196,11 +213,25 @@ function openConversationExternal(
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showInbox() {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: 'SHOW_INBOX',
 | 
				
			||||||
 | 
					    payload: null,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function showArchivedConversations() {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: 'SHOW_ARCHIVED_CONVERSATIONS',
 | 
				
			||||||
 | 
					    payload: null,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Reducer
 | 
					// Reducer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getEmptyState(): ConversationsStateType {
 | 
					function getEmptyState(): ConversationsStateType {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    conversationLookup: {},
 | 
					    conversationLookup: {},
 | 
				
			||||||
 | 
					    showArchived: false,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -225,27 +256,38 @@ export function reducer(
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
 | 
					 | 
				
			||||||
    const { payload } = action;
 | 
					 | 
				
			||||||
    const { id } = payload;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      ...state,
 | 
					 | 
				
			||||||
      selectedConversation: id,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (action.type === 'CONVERSATION_CHANGED') {
 | 
					  if (action.type === 'CONVERSATION_CHANGED') {
 | 
				
			||||||
    const { payload } = action;
 | 
					    const { payload } = action;
 | 
				
			||||||
    const { id, data } = payload;
 | 
					    const { id, data } = payload;
 | 
				
			||||||
    const { conversationLookup } = state;
 | 
					    const { conversationLookup } = state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let showArchived = state.showArchived;
 | 
				
			||||||
 | 
					    let selectedConversation = state.selectedConversation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const existing = conversationLookup[id];
 | 
				
			||||||
    // In the change case we only modify the lookup if we already had that conversation
 | 
					    // In the change case we only modify the lookup if we already had that conversation
 | 
				
			||||||
    if (!conversationLookup[id]) {
 | 
					    if (!existing) {
 | 
				
			||||||
      return state;
 | 
					      return state;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (selectedConversation === id) {
 | 
				
			||||||
 | 
					      // Archived -> Inbox: we go back to the normal inbox view
 | 
				
			||||||
 | 
					      if (existing.isArchived && !data.isArchived) {
 | 
				
			||||||
 | 
					        showArchived = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Inbox -> Archived: no conversation is selected
 | 
				
			||||||
 | 
					      // Note: With today's stacked converastions architecture, this can result in weird
 | 
				
			||||||
 | 
					      //   behavior - no selected conversation in the left pane, but a conversation show
 | 
				
			||||||
 | 
					      //   in the right pane.
 | 
				
			||||||
 | 
					      if (!existing.isArchived && data.isArchived) {
 | 
				
			||||||
 | 
					        selectedConversation = undefined;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      ...state,
 | 
					      ...state,
 | 
				
			||||||
 | 
					      selectedConversation,
 | 
				
			||||||
 | 
					      showArchived,
 | 
				
			||||||
      conversationLookup: {
 | 
					      conversationLookup: {
 | 
				
			||||||
        ...conversationLookup,
 | 
					        ...conversationLookup,
 | 
				
			||||||
        [id]: data,
 | 
					        [id]: data,
 | 
				
			||||||
| 
						 | 
					@ -268,6 +310,27 @@ export function reducer(
 | 
				
			||||||
  if (action.type === 'MESSAGE_EXPIRED') {
 | 
					  if (action.type === 'MESSAGE_EXPIRED') {
 | 
				
			||||||
    // noop - for now this is only important for search
 | 
					    // noop - for now this is only important for search
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
 | 
				
			||||||
 | 
					    const { payload } = action;
 | 
				
			||||||
 | 
					    const { id } = payload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      ...state,
 | 
				
			||||||
 | 
					      selectedConversation: id,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (action.type === 'SHOW_INBOX') {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      ...state,
 | 
				
			||||||
 | 
					      showArchived: false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (action.type === 'SHOW_ARCHIVED_CONVERSATIONS') {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      ...state,
 | 
				
			||||||
 | 
					      showArchived: true,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return state;
 | 
					  return state;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,3 @@
 | 
				
			||||||
import { compact } from 'lodash';
 | 
					 | 
				
			||||||
import { createSelector } from 'reselect';
 | 
					import { createSelector } from 'reselect';
 | 
				
			||||||
import { format } from '../../types/PhoneNumber';
 | 
					import { format } from '../../types/PhoneNumber';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +28,13 @@ export const getSelectedConversation = createSelector(
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getShowArchived = createSelector(
 | 
				
			||||||
 | 
					  getConversations,
 | 
				
			||||||
 | 
					  (state: ConversationsStateType): boolean => {
 | 
				
			||||||
 | 
					    return Boolean(state.showArchived);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getConversationTitle(
 | 
					function getConversationTitle(
 | 
				
			||||||
  conversation: ConversationType,
 | 
					  conversation: ConversationType,
 | 
				
			||||||
  options: { i18n: LocalizerType; ourRegionCode: string }
 | 
					  options: { i18n: LocalizerType; ourRegionCode: string }
 | 
				
			||||||
| 
						 | 
					@ -83,37 +89,49 @@ export const getConversationComparator = createSelector(
 | 
				
			||||||
  _getConversationComparator
 | 
					  _getConversationComparator
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const _getLeftPaneList = (
 | 
					export const _getLeftPaneLists = (
 | 
				
			||||||
  lookup: ConversationLookupType,
 | 
					  lookup: ConversationLookupType,
 | 
				
			||||||
  comparator: (left: ConversationType, right: ConversationType) => number,
 | 
					  comparator: (left: ConversationType, right: ConversationType) => number,
 | 
				
			||||||
  selectedConversation?: string
 | 
					  selectedConversation?: string
 | 
				
			||||||
): Array<ConversationType> => {
 | 
					): {
 | 
				
			||||||
 | 
					  conversations: Array<ConversationType>;
 | 
				
			||||||
 | 
					  archivedConversations: Array<ConversationType>;
 | 
				
			||||||
 | 
					} => {
 | 
				
			||||||
  const values = Object.values(lookup);
 | 
					  const values = Object.values(lookup);
 | 
				
			||||||
  const filtered = compact(
 | 
					  const sorted = values.sort(comparator);
 | 
				
			||||||
    values.map(conversation => {
 | 
					 | 
				
			||||||
      if (!conversation.activeAt) {
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (selectedConversation === conversation.id) {
 | 
					  const conversations: Array<ConversationType> = [];
 | 
				
			||||||
        return {
 | 
					  const archivedConversations: Array<ConversationType> = [];
 | 
				
			||||||
          ...conversation,
 | 
					 | 
				
			||||||
          isSelected: true,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return conversation;
 | 
					  const max = sorted.length;
 | 
				
			||||||
    })
 | 
					  for (let i = 0; i < max; i += 1) {
 | 
				
			||||||
  );
 | 
					    let conversation = sorted[i];
 | 
				
			||||||
 | 
					    if (!conversation.activeAt) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return filtered.sort(comparator);
 | 
					    if (selectedConversation === conversation.id) {
 | 
				
			||||||
 | 
					      conversation = {
 | 
				
			||||||
 | 
					        ...conversation,
 | 
				
			||||||
 | 
					        isSelected: true,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (conversation.isArchived) {
 | 
				
			||||||
 | 
					      archivedConversations.push(conversation);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      conversations.push(conversation);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { conversations, archivedConversations };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getLeftPaneList = createSelector(
 | 
					export const getLeftPaneLists = createSelector(
 | 
				
			||||||
  getConversationLookup,
 | 
					  getConversationLookup,
 | 
				
			||||||
  getConversationComparator,
 | 
					  getConversationComparator,
 | 
				
			||||||
  getSelectedConversation,
 | 
					  getSelectedConversation,
 | 
				
			||||||
  _getLeftPaneList
 | 
					  _getLeftPaneLists
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getMe = createSelector(
 | 
					export const getMe = createSelector(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,14 +2,16 @@ import { compact } from 'lodash';
 | 
				
			||||||
import { createSelector } from 'reselect';
 | 
					import { createSelector } from 'reselect';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { StateType } from '../reducer';
 | 
					import { StateType } from '../reducer';
 | 
				
			||||||
import { SearchStateType } from '../ducks/search';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { SearchStateType } from '../ducks/search';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  getConversationLookup,
 | 
					  getConversationLookup,
 | 
				
			||||||
  getSelectedConversation,
 | 
					  getSelectedConversation,
 | 
				
			||||||
} from './conversations';
 | 
					} from './conversations';
 | 
				
			||||||
import { ConversationLookupType } from '../ducks/conversations';
 | 
					import { ConversationLookupType } from '../ducks/conversations';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { getRegionCode } from './user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getSearch = (state: StateType): SearchStateType => state.search;
 | 
					export const getSearch = (state: StateType): SearchStateType => state.search;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getQuery = createSelector(
 | 
					export const getQuery = createSelector(
 | 
				
			||||||
| 
						 | 
					@ -34,12 +36,14 @@ export const isSearching = createSelector(
 | 
				
			||||||
export const getSearchResults = createSelector(
 | 
					export const getSearchResults = createSelector(
 | 
				
			||||||
  [
 | 
					  [
 | 
				
			||||||
    getSearch,
 | 
					    getSearch,
 | 
				
			||||||
 | 
					    getRegionCode,
 | 
				
			||||||
    getConversationLookup,
 | 
					    getConversationLookup,
 | 
				
			||||||
    getSelectedConversation,
 | 
					    getSelectedConversation,
 | 
				
			||||||
    getSelectedMessage,
 | 
					    getSelectedMessage,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  (
 | 
					  (
 | 
				
			||||||
    state: SearchStateType,
 | 
					    state: SearchStateType,
 | 
				
			||||||
 | 
					    regionCode: string,
 | 
				
			||||||
    lookup: ConversationLookupType,
 | 
					    lookup: ConversationLookupType,
 | 
				
			||||||
    selectedConversation?: string,
 | 
					    selectedConversation?: string,
 | 
				
			||||||
    selectedMessage?: string
 | 
					    selectedMessage?: string
 | 
				
			||||||
| 
						 | 
					@ -84,6 +88,7 @@ export const getSearchResults = createSelector(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return message;
 | 
					        return message;
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
 | 
					      regionCode: regionCode,
 | 
				
			||||||
      searchTerm: state.query,
 | 
					      searchTerm: state.query,
 | 
				
			||||||
      showStartNewConversation: Boolean(
 | 
					      showStartNewConversation: Boolean(
 | 
				
			||||||
        state.normalizedPhoneNumber && !lookup[state.normalizedPhoneNumber]
 | 
					        state.normalizedPhoneNumber && !lookup[state.normalizedPhoneNumber]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,9 +4,9 @@ import { mapDispatchToProps } from '../actions';
 | 
				
			||||||
import { LeftPane } from '../../components/LeftPane';
 | 
					import { LeftPane } from '../../components/LeftPane';
 | 
				
			||||||
import { StateType } from '../reducer';
 | 
					import { StateType } from '../reducer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { getQuery, getSearchResults, isSearching } from '../selectors/search';
 | 
					import { getSearchResults, isSearching } from '../selectors/search';
 | 
				
			||||||
import { getIntl } from '../selectors/user';
 | 
					import { getIntl } from '../selectors/user';
 | 
				
			||||||
import { getLeftPaneList, getMe } from '../selectors/conversations';
 | 
					import { getLeftPaneLists, getShowArchived } from '../selectors/conversations';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { SmartMainHeader } from './MainHeader';
 | 
					import { SmartMainHeader } from './MainHeader';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,12 +17,14 @@ const FilteredSmartMainHeader = SmartMainHeader as any;
 | 
				
			||||||
const mapStateToProps = (state: StateType) => {
 | 
					const mapStateToProps = (state: StateType) => {
 | 
				
			||||||
  const showSearch = isSearching(state);
 | 
					  const showSearch = isSearching(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const lists = showSearch ? undefined : getLeftPaneLists(state);
 | 
				
			||||||
 | 
					  const searchResults = showSearch ? getSearchResults(state) : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
 | 
					    ...lists,
 | 
				
			||||||
 | 
					    searchResults,
 | 
				
			||||||
 | 
					    showArchived: getShowArchived(state),
 | 
				
			||||||
    i18n: getIntl(state),
 | 
					    i18n: getIntl(state),
 | 
				
			||||||
    me: getMe(state),
 | 
					 | 
				
			||||||
    query: getQuery(state),
 | 
					 | 
				
			||||||
    conversations: showSearch ? undefined : getLeftPaneList(state),
 | 
					 | 
				
			||||||
    searchResults: showSearch ? getSearchResults(state) : undefined,
 | 
					 | 
				
			||||||
    renderMainHeader: () => <FilteredSmartMainHeader />,
 | 
					    renderMainHeader: () => <FilteredSmartMainHeader />,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import { assert } from 'chai';
 | 
				
			||||||
import { ConversationLookupType } from '../../../state/ducks/conversations';
 | 
					import { ConversationLookupType } from '../../../state/ducks/conversations';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  _getConversationComparator,
 | 
					  _getConversationComparator,
 | 
				
			||||||
  _getLeftPaneList,
 | 
					  _getLeftPaneLists,
 | 
				
			||||||
} from '../../../state/selectors/conversations';
 | 
					} from '../../../state/selectors/conversations';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('state/selectors/conversations', () => {
 | 
					describe('state/selectors/conversations', () => {
 | 
				
			||||||
| 
						 | 
					@ -11,13 +11,14 @@ describe('state/selectors/conversations', () => {
 | 
				
			||||||
    it('sorts conversations based on timestamp then by intl-friendly title', () => {
 | 
					    it('sorts conversations based on timestamp then by intl-friendly title', () => {
 | 
				
			||||||
      const i18n = (key: string) => key;
 | 
					      const i18n = (key: string) => key;
 | 
				
			||||||
      const regionCode = 'US';
 | 
					      const regionCode = 'US';
 | 
				
			||||||
      const conversations: ConversationLookupType = {
 | 
					      const data: ConversationLookupType = {
 | 
				
			||||||
        id1: {
 | 
					        id1: {
 | 
				
			||||||
          id: 'id1',
 | 
					          id: 'id1',
 | 
				
			||||||
          activeAt: Date.now(),
 | 
					          activeAt: Date.now(),
 | 
				
			||||||
          name: 'No timestamp',
 | 
					          name: 'No timestamp',
 | 
				
			||||||
          timestamp: 0,
 | 
					          timestamp: 0,
 | 
				
			||||||
          phoneNumber: 'notused',
 | 
					          phoneNumber: 'notused',
 | 
				
			||||||
 | 
					          isArchived: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          type: 'direct',
 | 
					          type: 'direct',
 | 
				
			||||||
          isMe: false,
 | 
					          isMe: false,
 | 
				
			||||||
| 
						 | 
					@ -32,6 +33,7 @@ describe('state/selectors/conversations', () => {
 | 
				
			||||||
          name: 'B',
 | 
					          name: 'B',
 | 
				
			||||||
          timestamp: 20,
 | 
					          timestamp: 20,
 | 
				
			||||||
          phoneNumber: 'notused',
 | 
					          phoneNumber: 'notused',
 | 
				
			||||||
 | 
					          isArchived: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          type: 'direct',
 | 
					          type: 'direct',
 | 
				
			||||||
          isMe: false,
 | 
					          isMe: false,
 | 
				
			||||||
| 
						 | 
					@ -46,6 +48,7 @@ describe('state/selectors/conversations', () => {
 | 
				
			||||||
          name: 'C',
 | 
					          name: 'C',
 | 
				
			||||||
          timestamp: 20,
 | 
					          timestamp: 20,
 | 
				
			||||||
          phoneNumber: 'notused',
 | 
					          phoneNumber: 'notused',
 | 
				
			||||||
 | 
					          isArchived: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          type: 'direct',
 | 
					          type: 'direct',
 | 
				
			||||||
          isMe: false,
 | 
					          isMe: false,
 | 
				
			||||||
| 
						 | 
					@ -60,6 +63,7 @@ describe('state/selectors/conversations', () => {
 | 
				
			||||||
          name: 'Á',
 | 
					          name: 'Á',
 | 
				
			||||||
          timestamp: 20,
 | 
					          timestamp: 20,
 | 
				
			||||||
          phoneNumber: 'notused',
 | 
					          phoneNumber: 'notused',
 | 
				
			||||||
 | 
					          isArchived: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          type: 'direct',
 | 
					          type: 'direct',
 | 
				
			||||||
          isMe: false,
 | 
					          isMe: false,
 | 
				
			||||||
| 
						 | 
					@ -74,6 +78,7 @@ describe('state/selectors/conversations', () => {
 | 
				
			||||||
          name: 'First!',
 | 
					          name: 'First!',
 | 
				
			||||||
          timestamp: 30,
 | 
					          timestamp: 30,
 | 
				
			||||||
          phoneNumber: 'notused',
 | 
					          phoneNumber: 'notused',
 | 
				
			||||||
 | 
					          isArchived: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          type: 'direct',
 | 
					          type: 'direct',
 | 
				
			||||||
          isMe: false,
 | 
					          isMe: false,
 | 
				
			||||||
| 
						 | 
					@ -84,13 +89,13 @@ describe('state/selectors/conversations', () => {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      const comparator = _getConversationComparator(i18n, regionCode);
 | 
					      const comparator = _getConversationComparator(i18n, regionCode);
 | 
				
			||||||
      const list = _getLeftPaneList(conversations, comparator);
 | 
					      const { conversations } = _getLeftPaneLists(data, comparator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      assert.strictEqual(list[0].name, 'First!');
 | 
					      assert.strictEqual(conversations[0].name, 'First!');
 | 
				
			||||||
      assert.strictEqual(list[1].name, 'Á');
 | 
					      assert.strictEqual(conversations[1].name, 'Á');
 | 
				
			||||||
      assert.strictEqual(list[2].name, 'B');
 | 
					      assert.strictEqual(conversations[2].name, 'B');
 | 
				
			||||||
      assert.strictEqual(list[3].name, 'C');
 | 
					      assert.strictEqual(conversations[3].name, 'C');
 | 
				
			||||||
      assert.strictEqual(list[4].name, 'No timestamp');
 | 
					      assert.strictEqual(conversations[4].name, 'No timestamp');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -164,7 +164,7 @@
 | 
				
			||||||
    "rule": "jQuery-load(",
 | 
					    "rule": "jQuery-load(",
 | 
				
			||||||
    "path": "js/conversation_controller.js",
 | 
					    "path": "js/conversation_controller.js",
 | 
				
			||||||
    "line": "    async load() {",
 | 
					    "line": "    async load() {",
 | 
				
			||||||
    "lineNumber": 179,
 | 
					    "lineNumber": 177,
 | 
				
			||||||
    "reasonCategory": "falseMatch",
 | 
					    "reasonCategory": "falseMatch",
 | 
				
			||||||
    "updated": "2018-10-02T21:00:44.007Z"
 | 
					    "updated": "2018-10-02T21:00:44.007Z"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					@ -172,7 +172,7 @@
 | 
				
			||||||
    "rule": "jQuery-load(",
 | 
					    "rule": "jQuery-load(",
 | 
				
			||||||
    "path": "js/conversation_controller.js",
 | 
					    "path": "js/conversation_controller.js",
 | 
				
			||||||
    "line": "      this._initialPromise = load();",
 | 
					    "line": "      this._initialPromise = load();",
 | 
				
			||||||
    "lineNumber": 214,
 | 
					    "lineNumber": 212,
 | 
				
			||||||
    "reasonCategory": "falseMatch",
 | 
					    "reasonCategory": "falseMatch",
 | 
				
			||||||
    "updated": "2018-10-02T21:00:44.007Z"
 | 
					    "updated": "2018-10-02T21:00:44.007Z"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					@ -562,7 +562,7 @@
 | 
				
			||||||
    "rule": "jQuery-append(",
 | 
					    "rule": "jQuery-append(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "        .append(this.networkStatusView.render().el);",
 | 
					    "line": "        .append(this.networkStatusView.render().el);",
 | 
				
			||||||
    "lineNumber": 89,
 | 
					    "lineNumber": 88,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2018-09-19T18:13:29.628Z",
 | 
					    "updated": "2018-09-19T18:13:29.628Z",
 | 
				
			||||||
    "reasonDetail": "Interacting with already-existing DOM nodes"
 | 
					    "reasonDetail": "Interacting with already-existing DOM nodes"
 | 
				
			||||||
| 
						 | 
					@ -571,7 +571,7 @@
 | 
				
			||||||
    "rule": "jQuery-prependTo(",
 | 
					    "rule": "jQuery-prependTo(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "        banner.$el.prependTo(this.$el);",
 | 
					    "line": "        banner.$el.prependTo(this.$el);",
 | 
				
			||||||
    "lineNumber": 93,
 | 
					    "lineNumber": 92,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2018-09-19T18:13:29.628Z",
 | 
					    "updated": "2018-09-19T18:13:29.628Z",
 | 
				
			||||||
    "reasonDetail": "Interacting with already-existing DOM nodes"
 | 
					    "reasonDetail": "Interacting with already-existing DOM nodes"
 | 
				
			||||||
| 
						 | 
					@ -580,7 +580,7 @@
 | 
				
			||||||
    "rule": "jQuery-$(",
 | 
					    "rule": "jQuery-$(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "      this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
 | 
					    "line": "      this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
 | 
				
			||||||
    "lineNumber": 164,
 | 
					    "lineNumber": 166,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
| 
						 | 
					@ -589,7 +589,7 @@
 | 
				
			||||||
    "rule": "jQuery-append(",
 | 
					    "rule": "jQuery-append(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "      this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
 | 
					    "line": "      this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
 | 
				
			||||||
    "lineNumber": 164,
 | 
					    "lineNumber": 166,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
| 
						 | 
					@ -598,7 +598,7 @@
 | 
				
			||||||
    "rule": "jQuery-$(",
 | 
					    "rule": "jQuery-$(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "      if (e && this.$(e.target).closest('.placeholder').length) {",
 | 
					    "line": "      if (e && this.$(e.target).closest('.placeholder').length) {",
 | 
				
			||||||
    "lineNumber": 205,
 | 
					    "lineNumber": 207,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
| 
						 | 
					@ -607,7 +607,7 @@
 | 
				
			||||||
    "rule": "jQuery-$(",
 | 
					    "rule": "jQuery-$(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "      this.$('#header, .gutter').addClass('inactive');",
 | 
					    "line": "      this.$('#header, .gutter').addClass('inactive');",
 | 
				
			||||||
    "lineNumber": 209,
 | 
					    "lineNumber": 211,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
| 
						 | 
					@ -616,25 +616,25 @@
 | 
				
			||||||
    "rule": "jQuery-$(",
 | 
					    "rule": "jQuery-$(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "      this.$('.conversation-stack').addClass('inactive');",
 | 
					    "line": "      this.$('.conversation-stack').addClass('inactive');",
 | 
				
			||||||
    "lineNumber": 213,
 | 
					 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    "rule": "jQuery-$(",
 | 
					 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					 | 
				
			||||||
    "line": "      this.$('.conversation:first .menu').trigger('close');",
 | 
					 | 
				
			||||||
    "lineNumber": 215,
 | 
					    "lineNumber": 215,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "rule": "jQuery-$(",
 | 
				
			||||||
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
 | 
					    "line": "      this.$('.conversation:first .menu').trigger('close');",
 | 
				
			||||||
 | 
					    "lineNumber": 217,
 | 
				
			||||||
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    "rule": "jQuery-$(",
 | 
					    "rule": "jQuery-$(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "      if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
 | 
					    "line": "      if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
 | 
				
			||||||
    "lineNumber": 230,
 | 
					    "lineNumber": 236,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
| 
						 | 
					@ -643,7 +643,7 @@
 | 
				
			||||||
    "rule": "jQuery-$(",
 | 
					    "rule": "jQuery-$(",
 | 
				
			||||||
    "path": "js/views/inbox_view.js",
 | 
					    "path": "js/views/inbox_view.js",
 | 
				
			||||||
    "line": "      this.$('.conversation:first .recorder').trigger('close');",
 | 
					    "line": "      this.$('.conversation:first .recorder').trigger('close');",
 | 
				
			||||||
    "lineNumber": 233,
 | 
					    "lineNumber": 239,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-08T23:49:08.796Z",
 | 
					    "updated": "2019-03-08T23:49:08.796Z",
 | 
				
			||||||
    "reasonDetail": "Protected from arbitrary input"
 | 
					    "reasonDetail": "Protected from arbitrary input"
 | 
				
			||||||
| 
						 | 
					@ -5464,6 +5464,24 @@
 | 
				
			||||||
    "updated": "2019-03-09T00:08:44.242Z",
 | 
					    "updated": "2019-03-09T00:08:44.242Z",
 | 
				
			||||||
    "reasonDetail": "Used only to set focus"
 | 
					    "reasonDetail": "Used only to set focus"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "rule": "React-createRef",
 | 
				
			||||||
 | 
					    "path": "ts/components/LeftPane.js",
 | 
				
			||||||
 | 
					    "line": "        this.listRef = react_1.default.createRef();",
 | 
				
			||||||
 | 
					    "lineNumber": 13,
 | 
				
			||||||
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
 | 
					    "updated": "2019-03-12T23:33:50.889Z",
 | 
				
			||||||
 | 
					    "reasonDetail": "Used only to scroll to top on archive/inbox switch"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "rule": "React-createRef",
 | 
				
			||||||
 | 
					    "path": "ts/components/LeftPane.tsx",
 | 
				
			||||||
 | 
					    "line": "  public listRef: React.RefObject<any> = React.createRef();",
 | 
				
			||||||
 | 
					    "lineNumber": 46,
 | 
				
			||||||
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
 | 
					    "updated": "2019-03-12T23:33:50.889Z",
 | 
				
			||||||
 | 
					    "reasonDetail": "Used only to scroll to top on archive/inbox switch"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    "rule": "React-createRef",
 | 
					    "rule": "React-createRef",
 | 
				
			||||||
    "path": "ts/components/Lightbox.js",
 | 
					    "path": "ts/components/Lightbox.js",
 | 
				
			||||||
| 
						 | 
					@ -5513,9 +5531,9 @@
 | 
				
			||||||
    "rule": "React-createRef",
 | 
					    "rule": "React-createRef",
 | 
				
			||||||
    "path": "ts/components/conversation/ConversationHeader.tsx",
 | 
					    "path": "ts/components/conversation/ConversationHeader.tsx",
 | 
				
			||||||
    "line": "    this.menuTriggerRef = React.createRef();",
 | 
					    "line": "    this.menuTriggerRef = React.createRef();",
 | 
				
			||||||
    "lineNumber": 51,
 | 
					    "lineNumber": 58,
 | 
				
			||||||
    "reasonCategory": "usageTrusted",
 | 
					    "reasonCategory": "usageTrusted",
 | 
				
			||||||
    "updated": "2019-03-09T00:08:44.242Z",
 | 
					    "updated": "2019-03-09T00:08:44.242Z",
 | 
				
			||||||
    "reasonDetail": "Used only to trigger menu display"
 | 
					    "reasonDetail": "Used only to trigger menu display"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue