improve: handle attachment actions in a quote and how to jump to message (#5363)
* improve: handle attachment actions in a quote * actions with video * actions with audio * show alert when trying to jump to a message inside a not allowed room * jump to message from long press * disable the reply onPress when is a quote or forward * update tests * fix 02-broadcast e2e * fix the e2e tests * remove the await from handleResumeDownload and remove the esline-disable
This commit is contained in:
parent
2989b3c2ee
commit
b217435ffe
File diff suppressed because one or more lines are too long
|
@ -16,7 +16,7 @@ import { TActionSheetOptionsItem, useActionSheet, ACTION_SHEET_ANIMATION_DURATIO
|
|||
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||
import events from '../../lib/methods/helpers/log/events';
|
||||
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { getPermalinkMessage } from '../../lib/methods';
|
||||
import { getPermalinkMessage, getQuoteMessageLink } from '../../lib/methods';
|
||||
import { compareServerVersion, getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||
import { Services } from '../../lib/services';
|
||||
|
||||
|
@ -28,6 +28,7 @@ export interface IMessageActionsProps {
|
|||
reactionInit: (message: TAnyMessageModel) => void;
|
||||
onReactionPress: (shortname: IEmoji, messageId: string) => void;
|
||||
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
|
||||
jumpToMessage?: (messageUrl?: string, isFromReply?: boolean) => Promise<void>;
|
||||
isMasterDetail: boolean;
|
||||
isReadOnly: boolean;
|
||||
serverVersion?: string | null;
|
||||
|
@ -62,6 +63,7 @@ const MessageActions = React.memo(
|
|||
reactionInit,
|
||||
onReactionPress,
|
||||
replyInit,
|
||||
jumpToMessage,
|
||||
isReadOnly,
|
||||
Message_AllowDeleting,
|
||||
Message_AllowDeleting_BlockDeleteInMinutes,
|
||||
|
@ -374,6 +376,16 @@ const MessageActions = React.memo(
|
|||
const options: TActionSheetOptionsItem[] = [];
|
||||
const videoConfBlock = message.t === 'videoconf';
|
||||
|
||||
// Jump to message
|
||||
const quoteMessageLink = getQuoteMessageLink(message.attachments);
|
||||
if (quoteMessageLink && jumpToMessage) {
|
||||
options.push({
|
||||
title: I18n.t('Jump_to_message'),
|
||||
icon: 'jump-to-message',
|
||||
onPress: () => jumpToMessage(quoteMessageLink, true)
|
||||
});
|
||||
}
|
||||
|
||||
// Quote
|
||||
if (!isReadOnly && !videoConfBlock) {
|
||||
options.push({
|
||||
|
|
|
@ -116,7 +116,15 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
}
|
||||
|
||||
return (
|
||||
<Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} msg={msg} />
|
||||
<Reply
|
||||
key={index}
|
||||
index={index}
|
||||
attachment={file}
|
||||
timeFormat={timeFormat}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
msg={msg}
|
||||
showAttachment={showAttachment}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <>{attachmentsElements}</>;
|
||||
|
|
|
@ -5,7 +5,13 @@ import Markdown from '../markdown';
|
|||
import MessageContext from './Context';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IAttachment, IUserMessage } from '../../definitions';
|
||||
import { TDownloadState, downloadMediaFile, getMediaCache } from '../../lib/methods/handleMediaDownload';
|
||||
import {
|
||||
TDownloadState,
|
||||
downloadMediaFile,
|
||||
getMediaCache,
|
||||
isDownloadActive,
|
||||
resumeMediaFile
|
||||
} from '../../lib/methods/handleMediaDownload';
|
||||
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
||||
import AudioPlayer from '../AudioPlayer';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
@ -23,9 +29,7 @@ interface IMessageAudioProps {
|
|||
const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMessageAudioProps) => {
|
||||
const [downloadState, setDownloadState] = useState<TDownloadState>('loading');
|
||||
const [fileUri, setFileUri] = useState('');
|
||||
|
||||
const { baseUrl, user, id, rid } = useContext(MessageContext);
|
||||
|
||||
const { cdnPrefix } = useAppSelector(state => ({
|
||||
cdnPrefix: state.settings.CDN_PREFIX as string
|
||||
}));
|
||||
|
@ -38,8 +42,12 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
|
|||
return url;
|
||||
};
|
||||
|
||||
const onPlayButtonPress = () => {
|
||||
const onPlayButtonPress = async () => {
|
||||
if (downloadState === 'to-download') {
|
||||
const isAudioCached = await handleGetMediaCache();
|
||||
if (isAudioCached) {
|
||||
return;
|
||||
}
|
||||
handleDownload();
|
||||
}
|
||||
};
|
||||
|
@ -79,20 +87,44 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
|
|||
}
|
||||
};
|
||||
|
||||
const handleGetMediaCache = async () => {
|
||||
const cachedAudioResult = await getMediaCache({
|
||||
type: 'audio',
|
||||
mimeType: file.audio_type,
|
||||
urlToCache: getUrl()
|
||||
});
|
||||
if (cachedAudioResult?.exists) {
|
||||
setFileUri(cachedAudioResult.uri);
|
||||
setDownloadState('downloaded');
|
||||
}
|
||||
return !!cachedAudioResult?.exists;
|
||||
};
|
||||
|
||||
const handleResumeDownload = async () => {
|
||||
try {
|
||||
setDownloadState('loading');
|
||||
const url = getUrl();
|
||||
if (url) {
|
||||
const videoUri = await resumeMediaFile({
|
||||
downloadUrl: url
|
||||
});
|
||||
setFileUri(videoUri);
|
||||
setDownloadState('downloaded');
|
||||
}
|
||||
} catch (e) {
|
||||
setDownloadState('to-download');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleCache = async () => {
|
||||
const cachedAudioResult = await getMediaCache({
|
||||
type: 'audio',
|
||||
mimeType: file.audio_type,
|
||||
urlToCache: getUrl()
|
||||
});
|
||||
if (cachedAudioResult?.exists) {
|
||||
setFileUri(cachedAudioResult.uri);
|
||||
setDownloadState('downloaded');
|
||||
const isAudioCached = await handleGetMediaCache();
|
||||
if (isAudioCached) {
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setDownloadState('to-download');
|
||||
const audioUrl = getUrl();
|
||||
if (audioUrl && isDownloadActive(audioUrl)) {
|
||||
handleResumeDownload();
|
||||
return;
|
||||
}
|
||||
await handleAutoDownload();
|
||||
|
@ -106,14 +138,7 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
|
|||
return (
|
||||
<>
|
||||
<Markdown msg={msg} style={[isReply && style]} username={user.username} getCustomEmoji={getCustomEmoji} />
|
||||
<AudioPlayer
|
||||
msgId={id}
|
||||
fileUri={fileUri}
|
||||
downloadState={downloadState}
|
||||
disabled={isReply}
|
||||
onPlayButtonPress={onPlayButtonPress}
|
||||
rid={rid}
|
||||
/>
|
||||
<AudioPlayer msgId={id} fileUri={fileUri} downloadState={downloadState} onPlayButtonPress={onPlayButtonPress} rid={rid} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,13 @@ import FastImage from 'react-native-fast-image';
|
|||
import { IAttachment, IUserMessage } from '../../definitions';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
||||
import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../lib/methods/handleMediaDownload';
|
||||
import {
|
||||
cancelDownload,
|
||||
downloadMediaFile,
|
||||
getMediaCache,
|
||||
isDownloadActive,
|
||||
resumeMediaFile
|
||||
} from '../../lib/methods/handleMediaDownload';
|
||||
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
||||
import { useTheme } from '../../theme';
|
||||
import Markdown from '../markdown';
|
||||
|
@ -86,25 +92,12 @@ const ImageContainer = ({
|
|||
useEffect(() => {
|
||||
const handleCache = async () => {
|
||||
if (img) {
|
||||
const cachedImageResult = await getMediaCache({
|
||||
type: 'image',
|
||||
mimeType: imageCached.image_type,
|
||||
urlToCache: imgUrlToCache
|
||||
});
|
||||
if (cachedImageResult?.exists) {
|
||||
setImageCached(prev => ({
|
||||
...prev,
|
||||
title_link: cachedImageResult?.uri
|
||||
}));
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
const isImageCached = await handleGetMediaCache();
|
||||
if (isImageCached) {
|
||||
return;
|
||||
}
|
||||
if (isDownloadActive(imgUrlToCache)) {
|
||||
handleResumeDownload();
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
|
@ -131,6 +124,41 @@ const ImageContainer = ({
|
|||
}
|
||||
};
|
||||
|
||||
const updateImageCached = (imgUri: string) => {
|
||||
setImageCached(prev => ({
|
||||
...prev,
|
||||
title_link: imgUri
|
||||
}));
|
||||
setCached(true);
|
||||
};
|
||||
|
||||
const handleGetMediaCache = async () => {
|
||||
const cachedImageResult = await getMediaCache({
|
||||
type: 'image',
|
||||
mimeType: imageCached.image_type,
|
||||
urlToCache: imgUrlToCache
|
||||
});
|
||||
if (cachedImageResult?.exists) {
|
||||
updateImageCached(cachedImageResult.uri);
|
||||
setLoading(false);
|
||||
}
|
||||
return !!cachedImageResult?.exists;
|
||||
};
|
||||
|
||||
const handleResumeDownload = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const imageUri = await resumeMediaFile({
|
||||
downloadUrl: imgUrlToCache
|
||||
});
|
||||
updateImageCached(imageUri);
|
||||
} catch (e) {
|
||||
setCached(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
@ -139,11 +167,7 @@ const ImageContainer = ({
|
|||
type: 'image',
|
||||
mimeType: imageCached.image_type
|
||||
});
|
||||
setImageCached(prev => ({
|
||||
...prev,
|
||||
title_link: imageUri
|
||||
}));
|
||||
setCached(true);
|
||||
updateImageCached(imageUri);
|
||||
} catch (e) {
|
||||
setCached(false);
|
||||
} finally {
|
||||
|
@ -151,7 +175,7 @@ const ImageContainer = ({
|
|||
}
|
||||
};
|
||||
|
||||
const onPress = () => {
|
||||
const onPress = async () => {
|
||||
if (loading && isDownloadActive(imgUrlToCache)) {
|
||||
cancelDownload(imgUrlToCache);
|
||||
setLoading(false);
|
||||
|
@ -159,6 +183,15 @@ const ImageContainer = ({
|
|||
return;
|
||||
}
|
||||
if (!cached && !loading) {
|
||||
const isImageCached = await handleGetMediaCache();
|
||||
if (isImageCached && showAttachment) {
|
||||
showAttachment(imageCached);
|
||||
return;
|
||||
}
|
||||
if (isDownloadActive(imgUrlToCache)) {
|
||||
handleResumeDownload();
|
||||
return;
|
||||
}
|
||||
handleDownload();
|
||||
return;
|
||||
}
|
||||
|
@ -172,7 +205,7 @@ const ImageContainer = ({
|
|||
return (
|
||||
<View>
|
||||
<Markdown msg={msg} style={[isReply && style]} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
<Button disabled={isReply} onPress={onPress}>
|
||||
<Button onPress={onPress}>
|
||||
<MessageImage imgUri={img} cached={cached} loading={loading} />
|
||||
</Button>
|
||||
</View>
|
||||
|
@ -180,7 +213,7 @@ const ImageContainer = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<Button disabled={isReply} onPress={onPress}>
|
||||
<Button onPress={onPress}>
|
||||
<MessageImage imgUri={img} cached={cached} loading={loading} />
|
||||
</Button>
|
||||
);
|
||||
|
|
|
@ -92,6 +92,7 @@ interface IMessageReply {
|
|||
index: number;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
msg?: string;
|
||||
showAttachment?: (file: IAttachment) => void;
|
||||
}
|
||||
|
||||
const Title = React.memo(
|
||||
|
@ -198,10 +199,10 @@ const Fields = React.memo(
|
|||
);
|
||||
|
||||
const Reply = React.memo(
|
||||
({ attachment, timeFormat, index, getCustomEmoji, msg }: IMessageReply) => {
|
||||
({ attachment, timeFormat, index, getCustomEmoji, msg, showAttachment }: IMessageReply) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
|
||||
if (!attachment) {
|
||||
return null;
|
||||
|
@ -209,9 +210,6 @@ const Reply = React.memo(
|
|||
|
||||
const onPress = async () => {
|
||||
let url = attachment.title_link || attachment.author_link;
|
||||
if (attachment.message_link) {
|
||||
return jumpToMessage(attachment.message_link);
|
||||
}
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
@ -245,7 +243,7 @@ const Reply = React.memo(
|
|||
}
|
||||
]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
disabled={loading}
|
||||
disabled={loading || attachment.message_link}
|
||||
>
|
||||
<View style={styles.attachmentContainer}>
|
||||
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
|
||||
|
@ -257,6 +255,7 @@ const Reply = React.memo(
|
|||
timeFormat={timeFormat}
|
||||
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14, marginBottom: 8 }]}
|
||||
isReply
|
||||
showAttachment={showAttachment}
|
||||
/>
|
||||
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||
{loading ? (
|
||||
|
|
|
@ -7,7 +7,13 @@ import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
|||
import I18n from '../../i18n';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
||||
import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../lib/methods/handleMediaDownload';
|
||||
import {
|
||||
cancelDownload,
|
||||
downloadMediaFile,
|
||||
getMediaCache,
|
||||
isDownloadActive,
|
||||
resumeMediaFile
|
||||
} from '../../lib/methods/handleMediaDownload';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
||||
|
@ -93,26 +99,12 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
|
|||
useEffect(() => {
|
||||
const handleVideoSearchAndDownload = async () => {
|
||||
if (video) {
|
||||
const cachedVideoResult = await getMediaCache({
|
||||
type: 'video',
|
||||
mimeType: file.video_type,
|
||||
urlToCache: video
|
||||
});
|
||||
const downloadActive = isDownloadActive(video);
|
||||
if (cachedVideoResult?.exists) {
|
||||
setVideoCached(prev => ({
|
||||
...prev,
|
||||
video_url: cachedVideoResult?.uri
|
||||
}));
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
if (downloadActive) {
|
||||
cancelDownload(video);
|
||||
}
|
||||
const isVideoCached = await handleGetMediaCache();
|
||||
if (isVideoCached) {
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
if (isDownloadActive(video)) {
|
||||
handleResumeDownload();
|
||||
return;
|
||||
}
|
||||
await handleAutoDownload();
|
||||
|
@ -134,6 +126,41 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
|
|||
setLoading(false);
|
||||
};
|
||||
|
||||
const updateVideoCached = (videoUri: string) => {
|
||||
setVideoCached(prev => ({
|
||||
...prev,
|
||||
video_url: videoUri
|
||||
}));
|
||||
setCached(true);
|
||||
};
|
||||
|
||||
const handleGetMediaCache = async () => {
|
||||
const cachedVideoResult = await getMediaCache({
|
||||
type: 'video',
|
||||
mimeType: file.video_type,
|
||||
urlToCache: video
|
||||
});
|
||||
if (cachedVideoResult?.exists) {
|
||||
updateVideoCached(cachedVideoResult.uri);
|
||||
setLoading(false);
|
||||
}
|
||||
return !!cachedVideoResult?.exists;
|
||||
};
|
||||
|
||||
const handleResumeDownload = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const videoUri = await resumeMediaFile({
|
||||
downloadUrl: video
|
||||
});
|
||||
updateVideoCached(videoUri);
|
||||
} catch (e) {
|
||||
setCached(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
|
@ -142,11 +169,7 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
|
|||
type: 'video',
|
||||
mimeType: file.video_type
|
||||
});
|
||||
setVideoCached(prev => ({
|
||||
...prev,
|
||||
video_url: videoUri
|
||||
}));
|
||||
setCached(true);
|
||||
updateVideoCached(videoUri);
|
||||
} catch {
|
||||
setCached(false);
|
||||
} finally {
|
||||
|
@ -160,6 +183,15 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
|
|||
return;
|
||||
}
|
||||
if (!loading && !cached && file.video_type && isTypeSupported(file.video_type)) {
|
||||
const isVideoCached = await handleGetMediaCache();
|
||||
if (isVideoCached && showAttachment) {
|
||||
showAttachment(videoCached);
|
||||
return;
|
||||
}
|
||||
if (isDownloadActive(video)) {
|
||||
handleResumeDownload();
|
||||
return;
|
||||
}
|
||||
handleDownload();
|
||||
return;
|
||||
}
|
||||
|
@ -197,7 +229,6 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
|
|||
<>
|
||||
<Markdown msg={msg} username={user.username} getCustomEmoji={getCustomEmoji} style={[isReply && style]} theme={theme} />
|
||||
<Touchable
|
||||
disabled={isReply}
|
||||
onPress={onPress}
|
||||
style={[styles.button, messageStyles.mustWrapBlur, { backgroundColor: themes[theme].videoBackground }]}
|
||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
|
|
|
@ -761,5 +761,6 @@
|
|||
"Enable_writing_in_room": "Enable writing in room",
|
||||
"Disable_writing_in_room": "Disable writing in room",
|
||||
"Pinned_a_message": "Pinned a message:",
|
||||
"Jump_to_message": "Jump to message",
|
||||
"Missed_call": "Missed call"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import { getQuoteMessageLink } from './getQuoteMessageLink';
|
||||
|
||||
const imageAttachment = [
|
||||
{
|
||||
ts: '1970-01-01T00:00:00.000Z',
|
||||
title: 'IMG_0058.MP4',
|
||||
title_link: '/file-upload/34q5BbCRW3wCauiDt/IMG_0058.MP4',
|
||||
title_link_download: true,
|
||||
video_url: '/file-upload/34q5BbCRW3wCauiDt/IMG_0058.MP4',
|
||||
video_type: 'video/mp4',
|
||||
video_size: 4867328,
|
||||
type: 'file',
|
||||
fields: [],
|
||||
attachments: []
|
||||
}
|
||||
];
|
||||
|
||||
const imageAttachmentWithAQuote = [
|
||||
...imageAttachment,
|
||||
|
||||
{
|
||||
text: '[ ](https://mobile.rocket.chat/group/channel-etc?msg=cIqhbvkOSgiCOK4Wh) \nhttps://www.youtube.com/watch?v=5yx6BWlEVcY',
|
||||
md: [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'LINK',
|
||||
value: {
|
||||
src: { type: 'PLAIN_TEXT', value: 'https://mobile.rocket.chat/group/channel-etc?msg=cIqhbvkOSgiCOK4Wh' },
|
||||
label: [{ type: 'PLAIN_TEXT', value: ' ' }]
|
||||
}
|
||||
},
|
||||
{ type: 'PLAIN_TEXT', value: ' ' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'LINK',
|
||||
value: {
|
||||
src: { type: 'PLAIN_TEXT', value: 'https://www.youtube.com/watch?v=5yx6BWlEVcY' },
|
||||
label: [{ type: 'PLAIN_TEXT', value: 'https://www.youtube.com/watch?v=5yx6BWlEVcY' }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
message_link: 'https://mobile.rocket.chat/group/channel-etc?msg=n5WaK5NRJN42Hg26w',
|
||||
author_name: 'user-two',
|
||||
author_icon: '/avatar/user-two',
|
||||
attachments: [
|
||||
{
|
||||
text: 'https://www.youtube.com/watch?v=5yx6BWlEVcY',
|
||||
md: [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'LINK',
|
||||
value: {
|
||||
src: { type: 'PLAIN_TEXT', value: 'https://www.youtube.com/watch?v=5yx6BWlEVcY' },
|
||||
label: [{ type: 'PLAIN_TEXT', value: 'https://www.youtube.com/watch?v=5yx6BWlEVcY' }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
message_link: 'https://mobile.rocket.chat/group/channel-etc?msg=cIqhbvkOSgiCOK4Wh',
|
||||
author_name: 'user-two',
|
||||
author_icon: '/avatar/user-two',
|
||||
ts: '2023-11-23T14:10:18.520Z',
|
||||
fields: [],
|
||||
attachments: []
|
||||
}
|
||||
],
|
||||
ts: '2023-11-23T17:47:51.676Z',
|
||||
fields: []
|
||||
}
|
||||
];
|
||||
|
||||
describe('Test the getQuoteMessageLink', () => {
|
||||
it('return undefined from a message without attachment', () => {
|
||||
expect(getQuoteMessageLink([])).toBe(undefined);
|
||||
});
|
||||
it('return undefined from a message with image attachment', () => {
|
||||
expect(getQuoteMessageLink(imageAttachment)).toBe(undefined);
|
||||
});
|
||||
it('return the message link from an image message with a quote', () => {
|
||||
const expectedResult = 'https://mobile.rocket.chat/group/channel-etc?msg=n5WaK5NRJN42Hg26w';
|
||||
expect(getQuoteMessageLink(imageAttachmentWithAQuote)).toBe(expectedResult);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
import { IAttachment } from '../../definitions/IAttachment';
|
||||
|
||||
// https://github.com/RocketChat/Rocket.Chat/blame/edb4e2c91f4e8f90b0420be61270a75d49709732/packages/core-typings/src/IMessage/MessageAttachment/MessageQuoteAttachment.ts#L16
|
||||
export const getQuoteMessageLink = (attachments?: IAttachment[]) => {
|
||||
const attachmentWithMessageLink = attachments?.find(attachment => 'message_link' in attachment);
|
||||
return attachmentWithMessageLink?.message_link;
|
||||
};
|
|
@ -10,7 +10,7 @@ const getSingleMessage = (messageId: string): Promise<IMessage> =>
|
|||
}
|
||||
return reject();
|
||||
} catch (e) {
|
||||
return reject();
|
||||
return reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -217,3 +217,21 @@ export function downloadMediaFile({
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function resumeMediaFile({ downloadUrl }: { downloadUrl: string }): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let downloadKey = '';
|
||||
try {
|
||||
downloadKey = mediaDownloadKey(downloadUrl);
|
||||
const result = await downloadQueue[downloadKey].resumeAsync();
|
||||
if (result?.uri) {
|
||||
return resolve(result.uri);
|
||||
}
|
||||
return reject();
|
||||
} catch {
|
||||
return reject();
|
||||
} finally {
|
||||
delete downloadQueue[downloadKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -42,3 +42,4 @@ export * from './isRoomFederated';
|
|||
export * from './checkSupportedVersions';
|
||||
export * from './getServerInfo';
|
||||
export * from './isImageBase64';
|
||||
export * from './getQuoteMessageLink';
|
||||
|
|
|
@ -972,7 +972,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
return true;
|
||||
};
|
||||
|
||||
jumpToMessageByUrl = async (messageUrl?: string) => {
|
||||
jumpToMessageByUrl = async (messageUrl?: string, isFromReply?: boolean) => {
|
||||
if (!messageUrl) {
|
||||
return;
|
||||
}
|
||||
|
@ -980,14 +980,14 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
const parsedUrl = parse(messageUrl, true);
|
||||
const messageId = parsedUrl.query.msg;
|
||||
if (messageId) {
|
||||
await this.jumpToMessage(messageId);
|
||||
await this.jumpToMessage(messageId, isFromReply);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
jumpToMessage = async (messageId: string) => {
|
||||
jumpToMessage = async (messageId: string, isFromReply?: boolean) => {
|
||||
try {
|
||||
sendLoadingEvent({ visible: true, onCancel: this.cancelJumpToMessage });
|
||||
const message = await RoomServices.getMessageInfo(messageId);
|
||||
|
@ -1019,8 +1019,12 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
await Promise.race([this.list.current?.jumpToMessage(message.id), new Promise(res => setTimeout(res, 5000))]);
|
||||
this.cancelJumpToMessage();
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
} catch (error: any) {
|
||||
if (isFromReply && error.data?.errorType === 'error-not-allowed') {
|
||||
showErrorAlert(I18n.t('The_room_does_not_exist'), I18n.t('Room_not_found'));
|
||||
} else {
|
||||
log(error);
|
||||
}
|
||||
this.cancelJumpToMessage();
|
||||
}
|
||||
};
|
||||
|
@ -1505,6 +1509,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
replyInit={this.onReplyInit}
|
||||
reactionInit={this.onReactionInit}
|
||||
onReactionPress={this.onReactionPress}
|
||||
jumpToMessage={this.jumpToMessageByUrl}
|
||||
isReadOnly={readOnly}
|
||||
/>
|
||||
<MessageErrorActions ref={ref => (this.messageErrorActions = ref)} tmid={this.tmid} />
|
||||
|
|
|
@ -165,6 +165,13 @@ async function tryTapping(
|
|||
}
|
||||
}
|
||||
|
||||
async function jumpToQuotedMessage(theElement: Detox.IndexableNativeElement | Detox.NativeElement): Promise<void> {
|
||||
const deviceType = device.getPlatform();
|
||||
const { textMatcher } = platformTypes[deviceType];
|
||||
await tryTapping(theElement, 2000, true);
|
||||
await element(by[textMatcher]('Jump to message')).atIndex(0).tap();
|
||||
}
|
||||
|
||||
async function tapAndWaitFor(
|
||||
elementToTap: Detox.IndexableNativeElement | Detox.NativeElement,
|
||||
elementToWaitFor: Detox.IndexableNativeElement | Detox.NativeElement,
|
||||
|
@ -255,5 +262,6 @@ export {
|
|||
checkRoomTitle,
|
||||
checkServer,
|
||||
platformTypes,
|
||||
expectValidRegisterOrRetry
|
||||
expectValidRegisterOrRetry,
|
||||
jumpToQuotedMessage
|
||||
};
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
TTextMatcher,
|
||||
sleep,
|
||||
checkRoomTitle,
|
||||
mockMessage
|
||||
mockMessage,
|
||||
jumpToQuotedMessage
|
||||
} from '../../helpers/app';
|
||||
import { createRandomUser, ITestUser } from '../../helpers/data_setup';
|
||||
import random from '../../helpers/random';
|
||||
|
@ -144,7 +145,7 @@ describe('Broadcast room', () => {
|
|||
await waitFor(element(by[textMatcher](message)))
|
||||
.toExist()
|
||||
.withTimeout(10000);
|
||||
await element(by[textMatcher](message)).tap();
|
||||
await jumpToQuotedMessage(element(by[textMatcher](message)));
|
||||
await sleep(300); // wait for animation
|
||||
await checkRoomTitle(room);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import { device, waitFor, element, by, expect } from 'detox';
|
||||
|
||||
import data from '../../data';
|
||||
import { navigateToLogin, tapBack, login, sleep, platformTypes, TTextMatcher, navigateToRoom } from '../../helpers/app';
|
||||
import {
|
||||
navigateToLogin,
|
||||
tapBack,
|
||||
login,
|
||||
sleep,
|
||||
platformTypes,
|
||||
TTextMatcher,
|
||||
navigateToRoom,
|
||||
jumpToQuotedMessage
|
||||
} from '../../helpers/app';
|
||||
|
||||
let textMatcher: TTextMatcher;
|
||||
let alertButtonType: string;
|
||||
|
@ -74,7 +83,7 @@ describe('Room', () => {
|
|||
.toExist()
|
||||
.withTimeout(5000);
|
||||
await sleep(2000);
|
||||
await element(by[textMatcher]('1')).atIndex(0).tap();
|
||||
await jumpToQuotedMessage(element(by[textMatcher]('1')).atIndex(0));
|
||||
await waitForLoading();
|
||||
await waitFor(element(by[textMatcher]('1')).atIndex(0))
|
||||
.toExist()
|
||||
|
@ -230,7 +239,7 @@ describe('Threads', () => {
|
|||
await waitFor(element(by[textMatcher]("Go to jumping-thread's thread")).atIndex(0))
|
||||
.toExist()
|
||||
.withTimeout(5000);
|
||||
await element(by[textMatcher]("Go to jumping-thread's thread")).atIndex(0).tap();
|
||||
await jumpToQuotedMessage(element(by[textMatcher]("Go to jumping-thread's thread")).atIndex(0));
|
||||
await expectThreadMessages("Go to jumping-thread's thread");
|
||||
await tapBack();
|
||||
});
|
||||
|
@ -260,7 +269,7 @@ describe('Threads', () => {
|
|||
await waitFor(element(by[textMatcher]('quoted')))
|
||||
.toExist()
|
||||
.withTimeout(5000);
|
||||
await element(by[textMatcher]('quoted')).atIndex(0).tap();
|
||||
await jumpToQuotedMessage(element(by[textMatcher]('quoted')).atIndex(0));
|
||||
await expectThreadMessages('quoted');
|
||||
await tapBack();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue