You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			249 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			249 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
import { createSelector } from 'reselect';
 | 
						|
import { format } from '../../types/PhoneNumber';
 | 
						|
 | 
						|
import { LocalizerType } from '../../types/Util';
 | 
						|
import { StateType } from '../reducer';
 | 
						|
import {
 | 
						|
  ConversationLookupType,
 | 
						|
  ConversationsStateType,
 | 
						|
  ConversationType,
 | 
						|
} from '../ducks/conversations';
 | 
						|
 | 
						|
import { getIntl, getRegionCode, getUserNumber } from './user';
 | 
						|
import { ConversationListItemProps } from '../../components/ConversationListItem';
 | 
						|
import { BlockedNumberController } from '../../util';
 | 
						|
 | 
						|
export const getConversations = (state: StateType): ConversationsStateType =>
 | 
						|
  state.conversations;
 | 
						|
 | 
						|
export const getConversationLookup = createSelector(
 | 
						|
  getConversations,
 | 
						|
  (state: ConversationsStateType): ConversationLookupType => {
 | 
						|
    return state.conversationLookup;
 | 
						|
  }
 | 
						|
);
 | 
						|
 | 
						|
export const getSelectedConversation = createSelector(
 | 
						|
  getConversations,
 | 
						|
  (state: ConversationsStateType): string | undefined => {
 | 
						|
    return state.selectedConversation;
 | 
						|
  }
 | 
						|
);
 | 
						|
 | 
						|
export const getOurPrimaryConversation = createSelector(
 | 
						|
  getConversations,
 | 
						|
  (state: ConversationsStateType): ConversationType =>
 | 
						|
    state.conversationLookup[window.storage.get('primaryDevicePubKey')]
 | 
						|
);
 | 
						|
 | 
						|
function getConversationTitle(
 | 
						|
  conversation: ConversationType,
 | 
						|
  options: { i18n: LocalizerType; ourRegionCode: string }
 | 
						|
): string {
 | 
						|
  if (conversation.name) {
 | 
						|
    return conversation.name;
 | 
						|
  }
 | 
						|
 | 
						|
  if (conversation.type === 'group') {
 | 
						|
    const { i18n } = options;
 | 
						|
    return i18n('unknown');
 | 
						|
  }
 | 
						|
  return format(conversation.phoneNumber, options);
 | 
						|
}
 | 
						|
 | 
						|
const collator = new Intl.Collator();
 | 
						|
 | 
						|
export const _getConversationComparator = (
 | 
						|
  i18n: LocalizerType,
 | 
						|
  ourRegionCode: string
 | 
						|
) => {
 | 
						|
  return (left: ConversationType, right: ConversationType): number => {
 | 
						|
    const leftTimestamp = left.timestamp;
 | 
						|
    const rightTimestamp = right.timestamp;
 | 
						|
    if (leftTimestamp && !rightTimestamp) {
 | 
						|
      return -1;
 | 
						|
    }
 | 
						|
    if (rightTimestamp && !leftTimestamp) {
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
    if (leftTimestamp && rightTimestamp && leftTimestamp !== rightTimestamp) {
 | 
						|
      return rightTimestamp - leftTimestamp;
 | 
						|
    }
 | 
						|
    const leftTitle = getConversationTitle(left, {
 | 
						|
      i18n,
 | 
						|
      ourRegionCode,
 | 
						|
    }).toLowerCase();
 | 
						|
    const rightTitle = getConversationTitle(right, {
 | 
						|
      i18n,
 | 
						|
      ourRegionCode,
 | 
						|
    }).toLowerCase();
 | 
						|
 | 
						|
    return collator.compare(leftTitle, rightTitle);
 | 
						|
  };
 | 
						|
};
 | 
						|
export const getConversationComparator = createSelector(
 | 
						|
  getIntl,
 | 
						|
  getRegionCode,
 | 
						|
  _getConversationComparator
 | 
						|
);
 | 
						|
 | 
						|
export const _getLeftPaneLists = (
 | 
						|
  lookup: ConversationLookupType,
 | 
						|
  comparator: (left: ConversationType, right: ConversationType) => number,
 | 
						|
  selectedConversation?: string
 | 
						|
): {
 | 
						|
  conversations: Array<ConversationType>;
 | 
						|
  archivedConversations: Array<ConversationType>;
 | 
						|
  contacts: Array<ConversationType>;
 | 
						|
  unreadCount: number;
 | 
						|
} => {
 | 
						|
  const values = Object.values(lookup);
 | 
						|
  const sorted = values.sort(comparator);
 | 
						|
 | 
						|
  const conversations: Array<ConversationType> = [];
 | 
						|
  const archivedConversations: Array<ConversationType> = [];
 | 
						|
  const allContacts: Array<ConversationType> = [];
 | 
						|
 | 
						|
  let unreadCount = 0;
 | 
						|
  for (let conversation of sorted) {
 | 
						|
    if (selectedConversation === conversation.id) {
 | 
						|
      conversation = {
 | 
						|
        ...conversation,
 | 
						|
        isSelected: true,
 | 
						|
      };
 | 
						|
    }
 | 
						|
    const isBlocked =
 | 
						|
      BlockedNumberController.isBlocked(conversation.primaryDevice) ||
 | 
						|
      BlockedNumberController.isGroupBlocked(conversation.id);
 | 
						|
 | 
						|
    if (isBlocked) {
 | 
						|
      conversation = {
 | 
						|
        ...conversation,
 | 
						|
        isBlocked: true,
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    // Add Open Group to list as soon as the name has been set
 | 
						|
    if (
 | 
						|
      conversation.isPublic &&
 | 
						|
      (!conversation.name || conversation.name === 'Unknown group')
 | 
						|
    ) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Show loading icon while fetching messages
 | 
						|
    if (conversation.isPublic && !conversation.timestamp) {
 | 
						|
      conversation.lastMessage = {
 | 
						|
        status: 'sending',
 | 
						|
        text: '',
 | 
						|
        isRss: false,
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    // Remove all invalid conversations and conversatons of devices associated
 | 
						|
    //  with cancelled attempted links
 | 
						|
    if (!conversation.isPublic && !conversation.activeAt) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (conversation.activeAt !== undefined) {
 | 
						|
      allContacts.push(conversation);
 | 
						|
    }
 | 
						|
 | 
						|
    if (unreadCount < 9 && conversation.unreadCount > 0) {
 | 
						|
      unreadCount += conversation.unreadCount;
 | 
						|
    }
 | 
						|
 | 
						|
    if (conversation.isArchived) {
 | 
						|
      archivedConversations.push(conversation);
 | 
						|
    } else {
 | 
						|
      conversations.push(conversation);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const filterToPrimary = <
 | 
						|
    T extends Array<ConversationType | ConversationListItemProps>
 | 
						|
  >(
 | 
						|
    group: Array<ConversationType | ConversationListItemProps>
 | 
						|
  ): T => {
 | 
						|
    const secondariesToRemove: Array<string> = [];
 | 
						|
 | 
						|
    group.forEach(device => {
 | 
						|
      if (!device.isSecondary) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      const devicePrimary = group.find(c => c.id === device.primaryDevice);
 | 
						|
 | 
						|
      // Remove secondary where primary already exists in group
 | 
						|
      if (group.some(c => c === devicePrimary)) {
 | 
						|
        secondariesToRemove.push(device.id);
 | 
						|
      }
 | 
						|
    });
 | 
						|
 | 
						|
    const filteredGroup = [
 | 
						|
      ...new Set(group.filter(c => !secondariesToRemove.find(s => s === c.id))),
 | 
						|
    ];
 | 
						|
 | 
						|
    return filteredGroup as T;
 | 
						|
  };
 | 
						|
 | 
						|
  const contacts: Array<ConversationType> = filterToPrimary(allContacts);
 | 
						|
  return {
 | 
						|
    conversations,
 | 
						|
    archivedConversations,
 | 
						|
    contacts,
 | 
						|
    unreadCount,
 | 
						|
  };
 | 
						|
};
 | 
						|
 | 
						|
export const _getSessionConversationInfo = (
 | 
						|
  lookup: ConversationLookupType,
 | 
						|
  comparator: (left: ConversationType, right: ConversationType) => number,
 | 
						|
  selectedConversation?: string
 | 
						|
): {
 | 
						|
  conversation: ConversationType | undefined;
 | 
						|
  selectedConversation?: string;
 | 
						|
} => {
 | 
						|
  const values = Object.values(lookup);
 | 
						|
  const sorted = values.sort(comparator);
 | 
						|
 | 
						|
  let conversation;
 | 
						|
  const max = sorted.length;
 | 
						|
 | 
						|
  for (let i = 0; i < max; i += 1) {
 | 
						|
    const conv = sorted[i];
 | 
						|
 | 
						|
    if (conv.id === selectedConversation) {
 | 
						|
      conversation = conv;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return {
 | 
						|
    conversation,
 | 
						|
    selectedConversation,
 | 
						|
  };
 | 
						|
};
 | 
						|
 | 
						|
export const getLeftPaneLists = createSelector(
 | 
						|
  getConversationLookup,
 | 
						|
  getConversationComparator,
 | 
						|
  getSelectedConversation,
 | 
						|
  _getLeftPaneLists
 | 
						|
);
 | 
						|
 | 
						|
export const getSessionConversationInfo = createSelector(
 | 
						|
  getConversationLookup,
 | 
						|
  getConversationComparator,
 | 
						|
  getSelectedConversation,
 | 
						|
  _getSessionConversationInfo
 | 
						|
);
 | 
						|
 | 
						|
export const getMe = createSelector(
 | 
						|
  [getConversationLookup, getUserNumber],
 | 
						|
  (lookup: ConversationLookupType, ourNumber: string): ConversationType => {
 | 
						|
    return lookup[ourNumber];
 | 
						|
  }
 | 
						|
);
 |