Changeset View
Changeset View
Standalone View
Standalone View
lib/shared/mention-utils.js
// @flow | // @flow | ||||
import * as React from 'react'; | |||||
import { oldValidUsernameRegexString } from './account-utils.js'; | import { oldValidUsernameRegexString } from './account-utils.js'; | ||||
import SentencePrefixSearchIndex from './sentence-prefix-search-index.js'; | import SentencePrefixSearchIndex from './sentence-prefix-search-index.js'; | ||||
import { threadOtherMembers } from './thread-utils.js'; | import { threadOtherMembers } from './thread-utils.js'; | ||||
import { stringForUserExplicit } from './user-utils.js'; | import { stringForUserExplicit } from './user-utils.js'; | ||||
import { useENSNames } from '../hooks/ens-cache.js'; | |||||
import { useUserSearchIndex } from '../selectors/nav-selectors.js'; | |||||
import { threadTypes } from '../types/thread-types-enum.js'; | import { threadTypes } from '../types/thread-types-enum.js'; | ||||
import type { | import type { | ||||
ChatMentionCandidates, | ChatMentionCandidates, | ||||
RelativeMemberInfo, | RelativeMemberInfo, | ||||
ThreadInfo, | ThreadInfo, | ||||
ResolvedThreadInfo, | ResolvedThreadInfo, | ||||
} from '../types/thread-types.js'; | } from '../types/thread-types.js'; | ||||
import { idSchemaRegex, chatNameMaxLength } from '../utils/validation-utils.js'; | import { idSchemaRegex, chatNameMaxLength } from '../utils/validation-utils.js'; | ||||
▲ Show 20 Lines • Show All 80 Lines • ▼ Show 20 Lines | if ( | ||||
selection.start === selection.end && | selection.start === selection.end && | ||||
(selection.start === text.length || /\s/.test(text[selection.end])) | (selection.start === text.length || /\s/.test(text[selection.end])) | ||||
) { | ) { | ||||
return text.slice(0, selection.start).match(regex); | return text.slice(0, selection.start).match(regex); | ||||
} | } | ||||
return null; | return null; | ||||
} | } | ||||
function getMentionTypeaheadUserSuggestions( | const useENSNamesOptions = { allAtOnce: true }; | ||||
userSearchIndex: SentencePrefixSearchIndex, | function useMentionTypeaheadUserSuggestions( | ||||
threadMembers: $ReadOnlyArray<RelativeMemberInfo>, | threadMembers: $ReadOnlyArray<RelativeMemberInfo>, | ||||
viewerID: ?string, | viewerID: ?string, | ||||
usernamePrefix: string, | typeaheadMatchedStrings: ?TypeaheadMatchedStrings, | ||||
): $ReadOnlyArray<MentionTypeaheadUserSuggestionItem> { | ): $ReadOnlyArray<MentionTypeaheadUserSuggestionItem> { | ||||
const userSearchIndex = useUserSearchIndex(threadMembers); | |||||
const resolvedThredMembers = useENSNames(threadMembers, useENSNamesOptions); | |||||
const usernamePrefix: ?string = typeaheadMatchedStrings?.query; | |||||
return React.useMemo(() => { | |||||
// If typeaheadMatchedStrings is undefined, we want to return no results | |||||
if (usernamePrefix === undefined || usernamePrefix === null) { | |||||
return []; | |||||
} | |||||
const userIDs = userSearchIndex.getSearchResults(usernamePrefix); | const userIDs = userSearchIndex.getSearchResults(usernamePrefix); | ||||
const usersInThread = threadOtherMembers(threadMembers, viewerID); | const usersInThread = threadOtherMembers(resolvedThredMembers, viewerID); | ||||
return usersInThread | return usersInThread | ||||
.filter(user => usernamePrefix.length === 0 || userIDs.includes(user.id)) | .filter(user => usernamePrefix.length === 0 || userIDs.includes(user.id)) | ||||
.sort((userA, userB) => | .sort((userA, userB) => | ||||
stringForUserExplicit(userA).localeCompare(stringForUserExplicit(userB)), | stringForUserExplicit(userA).localeCompare( | ||||
stringForUserExplicit(userB), | |||||
), | |||||
) | ) | ||||
.map(userInfo => ({ type: 'user', userInfo })); | .map(userInfo => ({ type: 'user', userInfo })); | ||||
}, [userSearchIndex, resolvedThredMembers, usernamePrefix, viewerID]); | |||||
} | } | ||||
function getMentionTypeaheadChatSuggestions( | function getMentionTypeaheadChatSuggestions( | ||||
chatSearchIndex: SentencePrefixSearchIndex, | chatSearchIndex: SentencePrefixSearchIndex, | ||||
chatMentionCandidates: ChatMentionCandidates, | chatMentionCandidates: ChatMentionCandidates, | ||||
chatNamePrefix: string, | chatNamePrefix: string, | ||||
): $ReadOnlyArray<MentionTypeaheadChatSuggestionItem> { | ): $ReadOnlyArray<MentionTypeaheadChatSuggestionItem> { | ||||
const result = []; | const result = []; | ||||
Show All 26 Lines | ): { | ||||
const newText = textBeforeAtSymbol + suggestionText + newSuffixText; | const newText = textBeforeAtSymbol + suggestionText + newSuffixText; | ||||
const newSelectionStart = newText.length - newSuffixText.length + 1; | const newSelectionStart = newText.length - newSuffixText.length + 1; | ||||
return { newText, newSelectionStart }; | return { newText, newSelectionStart }; | ||||
} | } | ||||
function getUserMentionsCandidates( | function useUserMentionsCandidates( | ||||
threadInfo: ThreadInfo, | threadInfo: ThreadInfo, | ||||
parentThreadInfo: ?ThreadInfo, | parentThreadInfo: ?ThreadInfo, | ||||
): $ReadOnlyArray<RelativeMemberInfo> { | ): $ReadOnlyArray<RelativeMemberInfo> { | ||||
return React.useMemo(() => { | |||||
if (threadInfo.type !== threadTypes.SIDEBAR) { | if (threadInfo.type !== threadTypes.SIDEBAR) { | ||||
return threadInfo.members; | return threadInfo.members; | ||||
} | } | ||||
if (parentThreadInfo) { | if (parentThreadInfo) { | ||||
return parentThreadInfo.members; | return parentThreadInfo.members; | ||||
} | } | ||||
// This scenario should not occur unless the user logs out while looking at a | // This scenario should not occur unless the user logs out while looking at | ||||
// sidebar. In that scenario, the Redux store may be cleared before ReactNav | // a sidebar. In that scenario, the Redux store may be cleared before | ||||
// finishes transitioning away from the previous screen | // ReactNav finishes transitioning away from the previous screen | ||||
return []; | return []; | ||||
}, [threadInfo, parentThreadInfo]); | |||||
} | } | ||||
export { | export { | ||||
markdownUserMentionRegex, | markdownUserMentionRegex, | ||||
isUserMentioned, | isUserMentioned, | ||||
extractUserMentionsFromText, | extractUserMentionsFromText, | ||||
getMentionTypeaheadUserSuggestions, | useMentionTypeaheadUserSuggestions, | ||||
getMentionTypeaheadChatSuggestions, | getMentionTypeaheadChatSuggestions, | ||||
getNewTextAndSelection, | getNewTextAndSelection, | ||||
getTypeaheadRegexMatches, | getTypeaheadRegexMatches, | ||||
getUserMentionsCandidates, | useUserMentionsCandidates, | ||||
chatMentionRegex, | chatMentionRegex, | ||||
encodeChatMentionText, | encodeChatMentionText, | ||||
decodeChatMentionText, | decodeChatMentionText, | ||||
getRawChatMention, | getRawChatMention, | ||||
renderChatMentionsWithAltText, | renderChatMentionsWithAltText, | ||||
}; | }; |