Fix self-mention in groups

This commit is contained in:
Fedor Indutny 2024-12-20 10:33:01 -08:00 committed by GitHub
parent 6f1d767c72
commit 4312d03db0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 44 additions and 19 deletions

View file

@ -198,6 +198,7 @@ export type Props = Pick<
| 'getPreferredBadge' | 'getPreferredBadge'
| 'onEditorStateChange' | 'onEditorStateChange'
| 'onTextTooLong' | 'onTextTooLong'
| 'ourConversationId'
| 'quotedMessageId' | 'quotedMessageId'
| 'sendCounter' | 'sendCounter'
| 'sortedGroupMembers' | 'sortedGroupMembers'
@ -280,6 +281,7 @@ export const CompositionArea = memo(function CompositionArea({
isFormattingEnabled, isFormattingEnabled,
onEditorStateChange, onEditorStateChange,
onTextTooLong, onTextTooLong,
ourConversationId,
sendCounter, sendCounter,
sortedGroupMembers, sortedGroupMembers,
// EmojiButton // EmojiButton
@ -947,6 +949,7 @@ export const CompositionArea = memo(function CompositionArea({
}} }}
onPickEmoji={onPickEmoji} onPickEmoji={onPickEmoji}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
ourConversationId={ourConversationId}
platform={platform} platform={platform}
recentStickers={recentStickers} recentStickers={recentStickers}
skinTone={skinTone} skinTone={skinTone}
@ -1042,6 +1045,7 @@ export const CompositionArea = memo(function CompositionArea({
onPickEmoji={onPickEmoji} onPickEmoji={onPickEmoji}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
ourConversationId={ourConversationId}
platform={platform} platform={platform}
quotedMessageId={quotedMessageId} quotedMessageId={quotedMessageId}
sendCounter={sendCounter} sendCounter={sendCounter}

View file

@ -43,6 +43,7 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => {
onPickEmoji: action('onPickEmoji'), onPickEmoji: action('onPickEmoji'),
onSubmit: action('onSubmit'), onSubmit: action('onSubmit'),
onTextTooLong: action('onTextTooLong'), onTextTooLong: action('onTextTooLong'),
ourConversationId: 'me',
platform: 'darwin', platform: 'darwin',
quotedMessageId: null, quotedMessageId: null,
sendCounter: 0, sendCounter: 0,

View file

@ -139,6 +139,7 @@ export type Props = Readonly<{
timestamp: number timestamp: number
): unknown; ): unknown;
onScroll?: (ev: React.UIEvent<HTMLElement>) => void; onScroll?: (ev: React.UIEvent<HTMLElement>) => void;
ourConversationId: string | undefined;
platform: string; platform: string;
quotedMessageId: string | null; quotedMessageId: string | null;
shouldHidePopovers: boolean | null; shouldHidePopovers: boolean | null;
@ -173,6 +174,7 @@ export function CompositionInput(props: Props): React.ReactElement {
onPickEmoji, onPickEmoji,
onScroll, onScroll,
onSubmit, onSubmit,
ourConversationId,
placeholder, placeholder,
platform, platform,
quotedMessageId, quotedMessageId,
@ -781,11 +783,9 @@ export function CompositionInput(props: Props): React.ReactElement {
}, },
mentionCompletion: { mentionCompletion: {
getPreferredBadge, getPreferredBadge,
me: sortedGroupMembers
? sortedGroupMembers.find(foo => foo.isMe)
: undefined,
memberRepositoryRef, memberRepositoryRef,
setMentionPickerElement: setMentionCompletionElement, setMentionPickerElement: setMentionCompletionElement,
ourConversationId,
i18n, i18n,
theme, theme,
}, },

View file

@ -39,6 +39,7 @@ export type CompositionTextAreaProps = {
timestamp: number timestamp: number
) => void; ) => void;
onTextTooLong: () => void; onTextTooLong: () => void;
ourConversationId: string | undefined;
platform: string; platform: string;
getPreferredBadge: PreferredBadgeSelectorType; getPreferredBadge: PreferredBadgeSelectorType;
draftText: string; draftText: string;
@ -66,6 +67,7 @@ export function CompositionTextArea({
onSetSkinTone, onSetSkinTone,
onSubmit, onSubmit,
onTextTooLong, onTextTooLong,
ourConversationId,
placeholder, placeholder,
platform, platform,
recentEmojis, recentEmojis,
@ -147,6 +149,7 @@ export function CompositionTextArea({
onScroll={onScroll} onScroll={onScroll}
onSubmit={onSubmit} onSubmit={onSubmit}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
ourConversationId={ourConversationId}
placeholder={placeholder} placeholder={placeholder}
platform={platform} platform={platform}
quotedMessageId={null} quotedMessageId={null}

View file

@ -67,6 +67,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
onPickEmoji={action('onPickEmoji')} onPickEmoji={action('onPickEmoji')}
onSetSkinTone={action('onSetSkinTone')} onSetSkinTone={action('onSetSkinTone')}
onTextTooLong={action('onTextTooLong')} onTextTooLong={action('onTextTooLong')}
ourConversationId="me"
platform="darwin" platform="darwin"
skinTone={0} skinTone={0}
/> />

View file

@ -89,6 +89,7 @@ export type PropsType = {
| 'isFormattingEnabled' | 'isFormattingEnabled'
| 'onPickEmoji' | 'onPickEmoji'
| 'onTextTooLong' | 'onTextTooLong'
| 'ourConversationId'
| 'platform' | 'platform'
| 'sortedGroupMembers' | 'sortedGroupMembers'
> & > &
@ -157,6 +158,7 @@ export function MediaEditor({
isFormattingEnabled, isFormattingEnabled,
onPickEmoji, onPickEmoji,
onTextTooLong, onTextTooLong,
ourConversationId,
platform, platform,
sortedGroupMembers, sortedGroupMembers,
@ -1315,6 +1317,7 @@ export function MediaEditor({
onPickEmoji={onPickEmoji} onPickEmoji={onPickEmoji}
onSubmit={noop} onSubmit={noop}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
ourConversationId={ourConversationId}
placeholder={i18n('icu:MediaEditor__input-placeholder')} placeholder={i18n('icu:MediaEditor__input-placeholder')}
platform={platform} platform={platform}
quotedMessageId={null} quotedMessageId={null}

View file

@ -274,6 +274,7 @@ export function StoryCreator({
}} }}
onPickEmoji={onPickEmoji} onPickEmoji={onPickEmoji}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
ourConversationId={ourConversationId}
platform={platform} platform={platform}
recentStickers={recentStickers} recentStickers={recentStickers}
skinTone={skinTone} skinTone={skinTone}

View file

@ -103,6 +103,7 @@ export type PropsType = {
) => unknown; ) => unknown;
onUseEmoji: (_: EmojiPickDataType) => unknown; onUseEmoji: (_: EmojiPickDataType) => unknown;
onMediaPlaybackStart: () => void; onMediaPlaybackStart: () => void;
ourConversationId: string | undefined;
platform: string; platform: string;
preferredReactionEmoji: ReadonlyArray<string>; preferredReactionEmoji: ReadonlyArray<string>;
queueStoryDownload: (storyId: string) => unknown; queueStoryDownload: (storyId: string) => unknown;
@ -159,6 +160,7 @@ export function StoryViewer({
onTextTooLong, onTextTooLong,
onUseEmoji, onUseEmoji,
onMediaPlaybackStart, onMediaPlaybackStart,
ourConversationId,
platform, platform,
preferredReactionEmoji, preferredReactionEmoji,
queueStoryDownload, queueStoryDownload,
@ -978,6 +980,7 @@ export function StoryViewer({
onSetSkinTone={onSetSkinTone} onSetSkinTone={onSetSkinTone}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
onUseEmoji={onUseEmoji} onUseEmoji={onUseEmoji}
ourConversationId={ourConversationId}
preferredReactionEmoji={preferredReactionEmoji} preferredReactionEmoji={preferredReactionEmoji}
recentEmojis={recentEmojis} recentEmojis={recentEmojis}
renderEmojiPicker={renderEmojiPicker} renderEmojiPicker={renderEmojiPicker}

View file

@ -107,6 +107,7 @@ export type PropsType = {
onSetSkinTone: (tone: number) => unknown; onSetSkinTone: (tone: number) => unknown;
onTextTooLong: () => unknown; onTextTooLong: () => unknown;
onUseEmoji: (_: EmojiPickDataType) => unknown; onUseEmoji: (_: EmojiPickDataType) => unknown;
ourConversationId: string | undefined;
preferredReactionEmoji: ReadonlyArray<string>; preferredReactionEmoji: ReadonlyArray<string>;
recentEmojis?: ReadonlyArray<string>; recentEmojis?: ReadonlyArray<string>;
renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element; renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element;
@ -138,6 +139,7 @@ export function StoryViewsNRepliesModal({
onSetSkinTone, onSetSkinTone,
onTextTooLong, onTextTooLong,
onUseEmoji, onUseEmoji,
ourConversationId,
preferredReactionEmoji, preferredReactionEmoji,
recentEmojis, recentEmojis,
renderEmojiPicker, renderEmojiPicker,
@ -254,6 +256,7 @@ export function StoryViewsNRepliesModal({
onReply(...args); onReply(...args);
}} }}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
ourConversationId={ourConversationId}
placeholder={ placeholder={
group group
? i18n('icu:StoryViewer__reply-group') ? i18n('icu:StoryViewer__reply-group')

View file

@ -82,9 +82,9 @@ export class MemberRepository {
this.isFuseReady = false; this.isFuseReady = false;
} }
getMembers(omit?: Pick<MemberType, 'id'>): ReadonlyArray<MemberType> { getMembers(omitId?: string): ReadonlyArray<MemberType> {
if (omit) { if (omitId) {
return this.members.filter(({ id }) => id !== omit.id); return this.members.filter(({ id }) => id !== omitId);
} }
return this.members; return this.members;
@ -102,10 +102,7 @@ export class MemberRepository {
: undefined; : undefined;
} }
search( search(pattern: string, omitId?: string): ReadonlyArray<MemberType> {
pattern: string,
omit?: Pick<MemberType, 'id'>
): ReadonlyArray<MemberType> {
if (!this.isFuseReady) { if (!this.isFuseReady) {
this.fuse.setCollection(this.members); this.fuse.setCollection(this.members);
this.isFuseReady = true; this.isFuseReady = true;
@ -115,8 +112,8 @@ export class MemberRepository {
.search(removeDiacritics(pattern)) .search(removeDiacritics(pattern))
.map(result => result.item); .map(result => result.item);
if (omit) { if (omitId) {
return results.filter(({ id }) => id !== omit.id); return results.filter(({ id }) => id !== omitId);
} }
return results; return results;

View file

@ -10,7 +10,6 @@ import React from 'react';
import { Popper } from 'react-popper'; import { Popper } from 'react-popper';
import classNames from 'classnames'; import classNames from 'classnames';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import type { ConversationType } from '../../state/ducks/conversations';
import { Avatar, AvatarSize } from '../../components/Avatar'; import { Avatar, AvatarSize } from '../../components/Avatar';
import type { LocalizerType, ThemeType } from '../../types/Util'; import type { LocalizerType, ThemeType } from '../../types/Util';
import type { MemberType, MemberRepository } from '../memberRepository'; import type { MemberType, MemberRepository } from '../memberRepository';
@ -26,7 +25,7 @@ export type MentionCompletionOptions = {
i18n: LocalizerType; i18n: LocalizerType;
memberRepositoryRef: RefObject<MemberRepository>; memberRepositoryRef: RefObject<MemberRepository>;
setMentionPickerElement: (element: JSX.Element | null) => void; setMentionPickerElement: (element: JSX.Element | null) => void;
me?: ConversationType; ourConversationId: string | undefined;
theme: ThemeType; theme: ThemeType;
}; };
@ -128,10 +127,15 @@ export class MentionCompletion {
if (memberRepository) { if (memberRepository) {
if (leftTokenText === '') { if (leftTokenText === '') {
results = memberRepository.getMembers(this.options.me); results = memberRepository.getMembers(
this.options.ourConversationId
);
} else { } else {
const fullMentionText = leftTokenText; const fullMentionText = leftTokenText;
results = memberRepository.search(fullMentionText, this.options.me); results = memberRepository.search(
fullMentionText,
this.options.ourConversationId
);
} }
} }

View file

@ -255,6 +255,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
lastEditableMessageId={lastEditableMessageId ?? null} lastEditableMessageId={lastEditableMessageId ?? null}
messageCompositionId={messageCompositionId} messageCompositionId={messageCompositionId}
platform={platform} platform={platform}
ourConversationId={ourConversationId}
sendCounter={sendCounter} sendCounter={sendCounter}
shouldHidePopovers={shouldHidePopovers} shouldHidePopovers={shouldHidePopovers}
theme={theme} theme={theme}

View file

@ -4,7 +4,7 @@ import React, { memo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import type { CompositionTextAreaProps } from '../../components/CompositionTextArea'; import type { CompositionTextAreaProps } from '../../components/CompositionTextArea';
import { CompositionTextArea } from '../../components/CompositionTextArea'; import { CompositionTextArea } from '../../components/CompositionTextArea';
import { getIntl, getPlatform } from '../selectors/user'; import { getIntl, getPlatform, getUserConversationId } from '../selectors/user';
import { useEmojisActions as useEmojiActions } from '../ducks/emojis'; import { useEmojisActions as useEmojiActions } from '../ducks/emojis';
import { useItemsActions } from '../ducks/items'; import { useItemsActions } from '../ducks/items';
import { getPreferredBadgeSelector } from '../selectors/badges'; import { getPreferredBadgeSelector } from '../selectors/badges';
@ -31,6 +31,7 @@ export const SmartCompositionTextArea = memo(function SmartCompositionTextArea(
) { ) {
const i18n = useSelector(getIntl); const i18n = useSelector(getIntl);
const platform = useSelector(getPlatform); const platform = useSelector(getPlatform);
const ourConversationId = useSelector(getUserConversationId);
const { onUseEmoji: onPickEmoji } = useEmojiActions(); const { onUseEmoji: onPickEmoji } = useEmojiActions();
const { onSetSkinTone } = useItemsActions(); const { onSetSkinTone } = useItemsActions();
@ -50,6 +51,7 @@ export const SmartCompositionTextArea = memo(function SmartCompositionTextArea(
onSetSkinTone={onSetSkinTone} onSetSkinTone={onSetSkinTone}
onTextTooLong={onTextTooLong} onTextTooLong={onTextTooLong}
platform={platform} platform={platform}
ourConversationId={ourConversationId}
/> />
); );
}); });

View file

@ -14,7 +14,7 @@ import {
getTextFormattingEnabled, getTextFormattingEnabled,
isInternalUser, isInternalUser,
} from '../selectors/items'; } from '../selectors/items';
import { getIntl, getPlatform } from '../selectors/user'; import { getIntl, getPlatform, getUserConversationId } from '../selectors/user';
import { getPreferredBadgeSelector } from '../selectors/badges'; import { getPreferredBadgeSelector } from '../selectors/badges';
import { import {
getSelectedStoryData, getSelectedStoryData,
@ -64,6 +64,7 @@ export const SmartStoryViewer = memo(function SmartStoryViewer() {
const i18n = useSelector(getIntl); const i18n = useSelector(getIntl);
const platform = useSelector(getPlatform); const platform = useSelector(getPlatform);
const ourConversationId = useSelector(getUserConversationId);
const getPreferredBadge = useSelector(getPreferredBadgeSelector); const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const preferredReactionEmoji = useSelector(getPreferredReactionEmoji); const preferredReactionEmoji = useSelector(getPreferredReactionEmoji);
const selectedStoryData = useSelector(getSelectedStoryData); const selectedStoryData = useSelector(getSelectedStoryData);
@ -154,6 +155,7 @@ export const SmartStoryViewer = memo(function SmartStoryViewer() {
onSetSkinTone={onSetSkinTone} onSetSkinTone={onSetSkinTone}
onTextTooLong={handleTextTooLong} onTextTooLong={handleTextTooLong}
onUseEmoji={onUseEmoji} onUseEmoji={onUseEmoji}
ourConversationId={ourConversationId}
platform={platform} platform={platform}
preferredReactionEmoji={preferredReactionEmoji} preferredReactionEmoji={preferredReactionEmoji}
queueStoryDownload={queueStoryDownload} queueStoryDownload={queueStoryDownload}

View file

@ -82,7 +82,7 @@ describe('MentionCompletion', () => {
const options: MentionCompletionOptions = { const options: MentionCompletionOptions = {
getPreferredBadge: () => undefined, getPreferredBadge: () => undefined,
i18n: setupI18n('en', {}), i18n: setupI18n('en', {}),
me, ourConversationId: me.id,
memberRepositoryRef, memberRepositoryRef,
setMentionPickerElement: sinon.stub(), setMentionPickerElement: sinon.stub(),
theme: ThemeType.dark, theme: ThemeType.dark,