diff --git a/lib/shared/markdown.js b/lib/shared/markdown.js --- a/lib/shared/markdown.js +++ b/lib/shared/markdown.js @@ -1,11 +1,13 @@ // @flow import invariant from 'invariant'; +import * as React from 'react'; import { markdownUserMentionRegex, decodeChatMentionText, } from './mention-utils.js'; +import { useENSNames } from '../hooks/ens-cache.js'; import type { ChatMentionCandidates, RelativeMemberInfo, @@ -197,15 +199,42 @@ }; } -function createMemberMapForUserMentions( +const useENSNamesOptions = { allAtOnce: true }; +function useMemberMapForUserMentions( members: $ReadOnlyArray, ): $ReadOnlyMap { - const membersMap = new Map(); - members.forEach(member => { - if (member.role && member.username) { - membersMap.set(member.username.toLowerCase(), member.id); + const membersWithRole = React.useMemo( + () => members.filter(member => member.role), + [members], + ); + + const resolvedMembers = useENSNames(membersWithRole, useENSNamesOptions); + const resolvedMembersMap: $ReadOnlyMap = + React.useMemo( + () => new Map(resolvedMembers.map(member => [member.id, member])), + [resolvedMembers], + ); + + const membersMap = React.useMemo(() => { + const map = new Map(); + + for (const member of membersWithRole) { + const rawUsername = member.username; + + if (rawUsername) { + map.set(rawUsername.toLowerCase(), member.id); + } + + const resolvedMember = resolvedMembersMap.get(member.id); + const resolvedUsername = resolvedMember?.username; + + if (resolvedUsername && resolvedUsername !== rawUsername) { + map.set(resolvedUsername.toLowerCase(), member.id); + } } - }); + + return map; + }, [membersWithRole, resolvedMembersMap]); return membersMap; } @@ -368,7 +397,7 @@ jsonPrint, matchList, parseList, - createMemberMapForUserMentions, + useMemberMapForUserMentions, matchUserMentions, parseUserMentions, stripSpoilersFromNotifications, diff --git a/native/markdown/rules.react.js b/native/markdown/rules.react.js --- a/native/markdown/rules.react.js +++ b/native/markdown/rules.react.js @@ -9,7 +9,6 @@ import { chatMentionRegex } from 'lib/shared/mention-utils.js'; import type { ChatMentionCandidates, - RelativeMemberInfo, ThreadInfo, } from 'lib/types/thread-types.js'; @@ -363,22 +362,23 @@ chatMentionCandidates: ChatMentionCandidates, ): (useDarkStyle: boolean) => MarkdownRules { const { members } = threadInfo; + const membersMap = SharedMarkdown.useMemberMapForUserMentions(members); + return React.useMemo( () => _memoize<[boolean], MarkdownRules>((useDarkStyle: boolean) => - textMessageRules(members, chatMentionCandidates, useDarkStyle), + textMessageRules(chatMentionCandidates, useDarkStyle, membersMap), ), - [members, chatMentionCandidates], + [chatMentionCandidates, membersMap], ); } function textMessageRules( - members: $ReadOnlyArray, chatMentionCandidates: ChatMentionCandidates, useDarkStyle: boolean, + membersMap: $ReadOnlyMap, ): MarkdownRules { const baseRules = fullMarkdownRules(useDarkStyle); - const membersMap = SharedMarkdown.createMemberMapForUserMentions(members); return { ...baseRules, @@ -428,15 +428,20 @@ } let defaultTextMessageRules = null; +const defaultMembersMap = new Map(); function getDefaultTextMessageRules( overrideDefaultChatMentionCandidates: ChatMentionCandidates = {}, ): MarkdownRules { if (Object.keys(overrideDefaultChatMentionCandidates).length > 0) { - return textMessageRules([], overrideDefaultChatMentionCandidates, false); + return textMessageRules( + overrideDefaultChatMentionCandidates, + false, + defaultMembersMap, + ); } if (!defaultTextMessageRules) { - defaultTextMessageRules = textMessageRules([], {}, false); + defaultTextMessageRules = textMessageRules({}, false, defaultMembersMap); } return defaultTextMessageRules; } diff --git a/web/markdown/rules.react.js b/web/markdown/rules.react.js --- a/web/markdown/rules.react.js +++ b/web/markdown/rules.react.js @@ -8,7 +8,6 @@ import { chatMentionRegex } from 'lib/shared/mention-utils.js'; import type { ChatMentionCandidates, - RelativeMemberInfo, ThreadInfo, } from 'lib/types/thread-types.js'; @@ -169,22 +168,23 @@ chatMentionCandidates: ChatMentionCandidates, ): boolean => MarkdownRules { const { members } = threadInfo; + const membersMap = SharedMarkdown.useMemberMapForUserMentions(members); + return React.useMemo( () => _memoize<[boolean], MarkdownRules>((useDarkStyle: boolean) => - textMessageRules(members, chatMentionCandidates, useDarkStyle), + textMessageRules(chatMentionCandidates, useDarkStyle, membersMap), ), - [chatMentionCandidates, members], + [chatMentionCandidates, membersMap], ); } function textMessageRules( - members: $ReadOnlyArray, chatMentionCandidates: ChatMentionCandidates, useDarkStyle: boolean, + membersMap: $ReadOnlyMap, ): MarkdownRules { const baseRules = markdownRules(useDarkStyle); - const membersMap = SharedMarkdown.createMemberMapForUserMentions(members); return { ...baseRules, @@ -232,15 +232,20 @@ } let defaultTextMessageRules = null; +const defaultMembersMap = new Map(); function getDefaultTextMessageRules( overrideDefaultChatMentionCandidates: ChatMentionCandidates = {}, ): MarkdownRules { if (Object.keys(overrideDefaultChatMentionCandidates).length > 0) { - return textMessageRules([], overrideDefaultChatMentionCandidates, false); + return textMessageRules( + overrideDefaultChatMentionCandidates, + false, + defaultMembersMap, + ); } if (!defaultTextMessageRules) { - defaultTextMessageRules = textMessageRules([], {}, false); + defaultTextMessageRules = textMessageRules({}, false, defaultMembersMap); } return defaultTextMessageRules; }