diff --git a/src/components/common/MessageText.tsx b/src/components/common/MessageText.tsx
index 5cfc39eeb..5805309ee 100644
--- a/src/components/common/MessageText.tsx
+++ b/src/components/common/MessageText.tsx
@@ -10,6 +10,7 @@ import trimText from '../../util/trimText';
import { extractMessageText, getMessageText, stripCustomEmoji } from '../../global/helpers';
import { renderTextWithEntities } from './helpers/renderTextWithEntities';
import useSyncEffect from '../../hooks/useSyncEffect';
+import useUniqueId from '../../hooks/useUniqueId';
interface OwnProps {
messageOrStory: ApiMessage | ApiStory;
@@ -57,6 +58,8 @@ function MessageText({
const adaptedFormattedText = isForAnimation && formattedText ? stripCustomEmoji(formattedText) : formattedText;
const { text, entities } = adaptedFormattedText || {};
+ const containerId = useUniqueId();
+
useSyncEffect(() => {
textCacheBusterRef.current += 1;
}, [text, entities]);
@@ -87,7 +90,7 @@ function MessageText({
highlight,
emojiSize,
shouldRenderAsHtml,
- messageId: messageOrStory.id,
+ containerId,
isSimple,
isProtected,
observeIntersectionForLoading,
diff --git a/src/components/common/helpers/renderMessageText.ts b/src/components/common/helpers/renderMessageText.ts
index 964873ee6..6a417d6e8 100644
--- a/src/components/common/helpers/renderMessageText.ts
+++ b/src/components/common/helpers/renderMessageText.ts
@@ -4,6 +4,7 @@ import type { TextPart } from '../../../types';
import type { LangFn } from '../../../hooks/useLang';
import {
+ getMessageKey,
getMessageSummaryDescription,
getMessageSummaryEmoji,
getMessageSummaryText,
@@ -23,6 +24,7 @@ export function renderMessageText({
isProtected,
forcePlayback,
shouldRenderAsHtml,
+ isForMediaViewer,
} : {
message: ApiMessage;
highlight?: string;
@@ -32,6 +34,7 @@ export function renderMessageText({
isProtected?: boolean;
forcePlayback?: boolean;
shouldRenderAsHtml?: boolean;
+ isForMediaViewer?: boolean;
}) {
const { text, entities } = message.content.text || {};
@@ -40,13 +43,15 @@ export function renderMessageText({
return contentNotSupportedText ? [trimText(contentNotSupportedText, truncateLength)] : undefined;
}
+ const messageKey = getMessageKey(message);
+
return renderTextWithEntities({
text: trimText(text, truncateLength),
entities,
highlight,
emojiSize,
shouldRenderAsHtml,
- messageId: message.id,
+ containerId: `${isForMediaViewer ? 'mv-' : ''}${messageKey}`,
isSimple,
isProtected,
forcePlayback,
diff --git a/src/components/common/helpers/renderTextWithEntities.tsx b/src/components/common/helpers/renderTextWithEntities.tsx
index 2fbdf767f..e6cf7f19d 100644
--- a/src/components/common/helpers/renderTextWithEntities.tsx
+++ b/src/components/common/helpers/renderTextWithEntities.tsx
@@ -33,7 +33,7 @@ export function renderTextWithEntities({
highlight,
emojiSize,
shouldRenderAsHtml,
- messageId,
+ containerId,
isSimple,
isProtected,
observeIntersectionForLoading,
@@ -49,7 +49,7 @@ export function renderTextWithEntities({
highlight?: string;
emojiSize?: number;
shouldRenderAsHtml?: boolean;
- messageId?: number;
+ containerId?: string;
isSimple?: boolean;
isProtected?: boolean;
observeIntersectionForLoading?: ObserveFn;
@@ -138,7 +138,7 @@ export function renderTextWithEntities({
entityContent,
nestedEntityContent,
highlight,
- messageId,
+ containerId,
isSimple,
isProtected,
observeIntersectionForLoading,
@@ -320,7 +320,7 @@ function processEntity({
entityContent,
nestedEntityContent,
highlight,
- messageId,
+ containerId,
isSimple,
isProtected,
observeIntersectionForLoading,
@@ -336,7 +336,7 @@ function processEntity({
entityContent: TextPart;
nestedEntityContent: TextPart[];
highlight?: string;
- messageId?: number;
+ containerId?: string;
isSimple?: boolean;
isProtected?: boolean;
observeIntersectionForLoading?: ObserveFn;
@@ -492,7 +492,7 @@ function processEntity({
case ApiMessageEntityTypes.Underline:
return {renderNestedMessagePart()};
case ApiMessageEntityTypes.Spoiler:
- return {renderNestedMessagePart()};
+ return {renderNestedMessagePart()};
case ApiMessageEntityTypes.CustomEmoji:
return (
= new Map();
+const revealByContainerId: Map = new Map();
const buildClassName = createClassNameBuilder('Spoiler');
const Spoiler: FC = ({
children,
- messageId,
+ containerId,
}) => {
// eslint-disable-next-line no-null/no-null
const contentRef = useRef(null);
- const [isRevealed, reveal, conceal] = useFlag();
-
- const getContentLength = useLastCallback(() => {
- if (!contentRef.current) {
- return 0;
- }
-
- const textLength = contentRef.current.textContent?.length || 0;
- const emojiCount = contentRef.current.querySelectorAll('.emoji').length;
- // Optimization: ignore alt, assume that viewing emoji takes same time as viewing 4 characters
- return textLength + emojiCount * 4;
- });
+ const [isRevealed, revealSpoiler] = useFlag();
const handleClick = useLastCallback((e: React.MouseEvent) => {
+ if (!containerId) return;
+
e.preventDefault();
e.stopPropagation();
- actionsByMessageId.get(messageId!)?.forEach((actions) => actions.reveal());
-
- const totalContentLength = actionsByMessageId.get(messageId!)
- ?.reduce((acc, actions) => acc + actions.contentLength, 0) || 0;
- const readingMs = Math.round(totalContentLength / READING_SYMBOLS_PER_SECOND) * 1000;
- const timeoutMs = Math.max(MIN_HIDE_TIMEOUT, Math.min(readingMs, MAX_HIDE_TIMEOUT));
-
- setTimeout(() => {
- actionsByMessageId.get(messageId!)?.forEach((actions) => actions.conceal());
- conceal();
- }, timeoutMs);
+ revealByContainerId.get(containerId)?.forEach((reveal) => reveal());
});
useEffect(() => {
- if (!messageId) {
+ if (!containerId) {
return undefined;
}
- const contentLength = getContentLength();
-
- if (actionsByMessageId.has(messageId)) {
- actionsByMessageId.get(messageId)!.push({ reveal, conceal, contentLength });
+ if (revealByContainerId.has(containerId)) {
+ revealByContainerId.get(containerId)!.push(revealSpoiler);
} else {
- actionsByMessageId.set(messageId, [{ reveal, conceal, contentLength }]);
+ revealByContainerId.set(containerId, [revealSpoiler]);
}
return () => {
- actionsByMessageId.delete(messageId);
+ revealByContainerId.delete(containerId);
};
- }, [conceal, getContentLength, handleClick, isRevealed, messageId, reveal]);
+ }, [containerId]);
return (
diff --git a/src/components/mediaViewer/MediaViewer.tsx b/src/components/mediaViewer/MediaViewer.tsx
index 9b5d4d61d..828003852 100644
--- a/src/components/mediaViewer/MediaViewer.tsx
+++ b/src/components/mediaViewer/MediaViewer.tsx
@@ -207,7 +207,7 @@ const MediaViewer: FC = ({
const prevMediaId = usePrevious(mediaId);
const prevAvatarOwner = usePrevious(avatarOwner);
const prevBestImageData = usePrevious(bestImageData);
- const textParts = message ? renderMessageText({ message, forcePlayback: true }) : undefined;
+ const textParts = message ? renderMessageText({ message, forcePlayback: true, isForMediaViewer: true }) : undefined;
const hasFooter = Boolean(textParts);
const shouldAnimateOpening = prevIsHidden && prevMediaId !== mediaId;
diff --git a/src/components/mediaViewer/MediaViewerContent.tsx b/src/components/mediaViewer/MediaViewerContent.tsx
index 3805b8fb0..f6b1c74ba 100644
--- a/src/components/mediaViewer/MediaViewerContent.tsx
+++ b/src/components/mediaViewer/MediaViewerContent.tsx
@@ -147,7 +147,7 @@ const MediaViewerContent: FC = (props) => {
if (!message) return undefined;
const textParts = message.content.action?.type === 'suggestProfilePhoto'
? lang('Conversation.SuggestedPhotoTitle')
- : renderMessageText({ message, forcePlayback: true });
+ : renderMessageText({ message, forcePlayback: true, isForMediaViewer: true });
const hasFooter = Boolean(textParts);
const posterSize = message && calculateMediaViewerDimensions(dimensions!, hasFooter, isVideo);