// SPDX-License-Identifier: AGPL-3.0-or-later import {AutocompleteItem} from '@app/features/channel/components/AutocompleteItem'; import styles from '@app/features/channel/components/AutocompleteMention.module.css'; import { type AutocompleteMentionMemberOption, type AutocompleteMentionRoleOption, type AutocompleteMentionUserOption, type AutocompleteOption, type AutocompleteSpecialMentionOption, isMentionMember, isMentionRole, isMentionUser, isSpecialMention, } from '@app/features/channel/components/AutocompleteTypes'; import Guilds from '@app/features/guild/state/Guilds'; import {useParams} from '@app/features/platform/components/router/RouterReact'; import * as ColorUtils from '@app/features/theme/utils/ColorUtils'; import {openRoleContextMenu} from '@app/features/ui/action_menu/RoleContextMenu'; import {StatusAwareAvatar} from '@app/features/ui/components/StatusAwareAvatar'; import * as DisplayNameUtils from '@app/features/user/utils/DisplayNameUtils'; import {msg} from '@lingui/core/macro'; import {useLingui} from '@lingui/react/macro'; import {observer} from 'mobx-react-lite'; import type React from 'react'; const NOTIFY_EVERYONE_WHO_HAS_PERMISSION_TO_VIEW_THIS_DESCRIPTOR = msg({ message: 'Notify everyone who has permission to view this channel.', comment: 'Description text in the channel and chat autocomplete mention. Keep the tone plain and specific.', }); const NOTIFY_EVERYONE_ONLINE_WHO_HAS_PERMISSION_TO_VIEW_DESCRIPTOR = msg({ message: 'Notify everyone online who has permission to view this channel.', comment: 'Description text in the channel and chat autocomplete mention. Keep the tone plain and specific.', }); const NOTIFY_USERS_WITH_THIS_ROLE_WHO_HAVE_PERMISSION_DESCRIPTOR = msg({ message: 'Notify users with this role who have permission to view this channel.', comment: 'Description text in the channel and chat autocomplete mention. Keep the tone plain and specific.', }); type RowRef = React.MutableRefObject> | undefined; type OptionIdGetter = ((index: number) => string) | undefined; function resolveOptionId(getOptionId: OptionIdGetter, index: number): string | undefined { if (getOptionId == null) { return undefined; } return getOptionId(index); } function resolveRowRef(rowRefs: RowRef, index: number): React.Ref | undefined { if (rowRefs == null) { return undefined; } return (node: HTMLButtonElement | null) => { rowRefs.current[index] = node; }; } function renderDivider(): React.ReactNode { return
; } export const AutocompleteMention = observer(function AutocompleteMention({ onSelect, keyboardFocusIndex, hoverIndex, options, onMouseEnter, onMouseLeave, rowRefs, getOptionId, }: { onSelect: (option: AutocompleteOption) => void; keyboardFocusIndex: number; hoverIndex: number; options: Array; onMouseEnter: (index: number) => void; onMouseLeave: () => void; rowRefs?: React.MutableRefObject>; getOptionId?: (index: number) => string; }) { const {i18n} = useLingui(); const routeParams = useParams() as {guildId?: string}; const guildId = routeParams.guildId; const guild = Guilds.getGuild(guildId == null ? '' : guildId); const nicknameGuildId = guild == null ? null : guild.id; const members = options.filter(isMentionMember); const users = options.filter(isMentionUser); const roles = options.filter(isMentionRole); const specialMentions = options.filter(isSpecialMention); const renderMember = (option: AutocompleteMentionMemberOption, index: number) => ( } name={DisplayNameUtils.getGuildMemberNickname(option.member)} description={DisplayNameUtils.formatUserTagForStreamerMode(option.member.user)} isKeyboardSelected={index === keyboardFocusIndex} isHovered={index === hoverIndex} onSelect={() => onSelect(option)} onMouseEnter={() => onMouseEnter(index)} onMouseLeave={onMouseLeave} innerRef={resolveRowRef(rowRefs, index)} data-flx="channel.autocomplete-mention.autocomplete-item.select" /> ); const renderUser = (option: AutocompleteMentionUserOption, index: number) => { const currentIndex = members.length + index; return ( } name={DisplayNameUtils.getNickname(option.user, nicknameGuildId)} description={DisplayNameUtils.formatUserTagForStreamerMode(option.user)} isKeyboardSelected={currentIndex === keyboardFocusIndex} isHovered={currentIndex === hoverIndex} onSelect={() => onSelect(option)} onMouseEnter={() => onMouseEnter(currentIndex)} onMouseLeave={onMouseLeave} innerRef={resolveRowRef(rowRefs, currentIndex)} data-flx="channel.autocomplete-mention.autocomplete-item.select--2" /> ); }; const renderSpecialMention = (option: AutocompleteSpecialMentionOption, index: number) => { const currentIndex = members.length + users.length + index; return ( onSelect(option)} onMouseEnter={() => onMouseEnter(currentIndex)} onMouseLeave={onMouseLeave} innerRef={resolveRowRef(rowRefs, currentIndex)} data-flx="channel.autocomplete-mention.autocomplete-item.select--3" /> ); }; const renderRole = (option: AutocompleteMentionRoleOption, index: number) => { const currentIndex = members.length + users.length + specialMentions.length + index; const roleColor = option.role.color === 0 || Number.isNaN(option.role.color) ? undefined : ColorUtils.int2rgb(option.role.color); return ( @{option.role.name} } description={i18n._(NOTIFY_USERS_WITH_THIS_ROLE_WHO_HAVE_PERMISSION_DESCRIPTOR)} isKeyboardSelected={currentIndex === keyboardFocusIndex} isHovered={currentIndex === hoverIndex} onSelect={() => onSelect(option)} onMouseEnter={() => onMouseEnter(currentIndex)} onMouseLeave={onMouseLeave} onContextMenu={(event) => openRoleContextMenu(event, option.role.id)} innerRef={resolveRowRef(rowRefs, currentIndex)} data-flx="channel.autocomplete-mention.autocomplete-item.select--4" /> ); }; let hasRowsAfterMembers = users.length > 0; if (!hasRowsAfterMembers) { hasRowsAfterMembers = specialMentions.length > 0; } if (!hasRowsAfterMembers) { hasRowsAfterMembers = roles.length > 0; } return ( <> {members.length > 0 && ( <> {members.map(renderMember)} {hasRowsAfterMembers && renderDivider()} )} {users.length > 0 && ( <> {users.map(renderUser)} {(specialMentions.length > 0 || roles.length > 0) && renderDivider()} )} {specialMentions.length > 0 && ( <> {specialMentions.map(renderSpecialMention)} {roles.length > 0 && renderDivider()} )} {roles.map(renderRole)} ); });