From a55fbdfcc49cff2d341c782147ca872705ee6f0f Mon Sep 17 00:00:00 2001
From: Alexander Zinchuk
Date: Thu, 27 Jan 2022 04:08:18 +0100
Subject: [PATCH] Introduce Spoilers (#1670)
---
src/api/gramjs/apiBuilders/messages.ts | 7 -
src/api/types/messages.ts | 1 +
src/assets/spoiler-dots-black.png | Bin 0 -> 3157 bytes
src/assets/spoiler-dots-white.png | Bin 0 -> 3386 bytes
src/components/common/EmbeddedMessage.tsx | 4 +-
src/components/common/WebLink.tsx | 24 ++-
.../helpers/renderActionMessageText.tsx | 29 ++--
.../common/helpers/renderMessageText.tsx | 140 +++++++++++++----
src/components/common/helpers/renderText.tsx | 9 +-
src/components/common/spoiler/Spoiler.scss | 31 ++++
src/components/common/spoiler/Spoiler.tsx | 66 ++++++++
src/components/left/main/Chat.tsx | 15 +-
src/components/left/search/ChatMessage.tsx | 10 +-
.../left/search/ChatMessageResults.tsx | 4 +-
src/components/left/search/ChatResults.tsx | 8 +-
src/components/middle/ActionMessage.tsx | 5 +-
src/components/middle/HeaderPinnedMessage.tsx | 8 +-
src/components/right/RightSearch.tsx | 6 +-
src/modules/helpers/index.ts | 1 +
src/modules/helpers/messageSummary.ts | 141 ++++++++++++++++++
src/modules/helpers/messages.ts | 69 +--------
21 files changed, 423 insertions(+), 155 deletions(-)
create mode 100644 src/assets/spoiler-dots-black.png
create mode 100644 src/assets/spoiler-dots-white.png
create mode 100644 src/components/common/spoiler/Spoiler.scss
create mode 100644 src/components/common/spoiler/Spoiler.tsx
create mode 100644 src/modules/helpers/messageSummary.ts
diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts
index 0afd79e58..efad15291 100644
--- a/src/api/gramjs/apiBuilders/messages.ts
+++ b/src/api/gramjs/apiBuilders/messages.ts
@@ -31,7 +31,6 @@ import {
} from '../../types';
import {
- CONTENT_NOT_SUPPORTED,
DELETED_COMMENTS_CHANNEL_ID,
LOCAL_MESSAGE_ID_BASE,
SERVICE_NOTIFICATIONS_USER_ID,
@@ -264,12 +263,6 @@ export function buildMessageTextContent(
message: string,
entities?: GramJs.TypeMessageEntity[],
): ApiFormattedText {
- if (entities?.some((e) => e instanceof GramJs.MessageEntitySpoiler)) {
- return {
- text: CONTENT_NOT_SUPPORTED,
- };
- }
-
return {
text: message,
...(entities && { entities: entities.map(buildApiMessageEntity) }),
diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts
index 8acaa8cf8..9023cacc0 100644
--- a/src/api/types/messages.ts
+++ b/src/api/types/messages.ts
@@ -213,6 +213,7 @@ export enum ApiMessageEntityTypes {
TextUrl = 'MessageEntityTextUrl',
Url = 'MessageEntityUrl',
Underline = 'MessageEntityUnderline',
+ Spoiler = 'MessageEntitySpoiler',
Unknown = 'MessageEntityUnknown',
}
diff --git a/src/assets/spoiler-dots-black.png b/src/assets/spoiler-dots-black.png
new file mode 100644
index 0000000000000000000000000000000000000000..71c6a06c36f68aeb40b1ea10d8c4641c7fc518ea
GIT binary patch
literal 3157
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
literal 0
HcmV?d00001
diff --git a/src/assets/spoiler-dots-white.png b/src/assets/spoiler-dots-white.png
new file mode 100644
index 0000000000000000000000000000000000000000..9adee38d33edc98ee562c8d37bba7e39bdc6166f
GIT binary patch
literal 3386
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
z
z6Lor~TMu5Um6;YfI6DBq&kV@zkug&xQvSyPZYOzI$JLb(fAzLRPa%SUy^ebe099)a
zCHei4-y><%rB}iWR;gzB<81ybJ5?)>w^Nr*{IQi*s$B`AOm73Z%Y`STVy
zSgKH?dE`i5oCB1U(i3%O+U`URd$G!SRI5$-SvKg^wO`SRUMF;+tbXF*e`>+v>!}QA
QEC2ui07*qoM6N<$g0Pf+7XSbN
literal 0
HcmV?d00001
diff --git a/src/components/common/EmbeddedMessage.tsx b/src/components/common/EmbeddedMessage.tsx
index 8cbb16651..c2bf5bc01 100644
--- a/src/components/common/EmbeddedMessage.tsx
+++ b/src/components/common/EmbeddedMessage.tsx
@@ -5,7 +5,6 @@ import { ApiUser, ApiMessage, ApiChat } from '../../api/types';
import {
getMessageMediaHash,
isActionMessage,
- getMessageSummaryText,
getSenderTitle,
getMessageRoundVideo,
} from '../../modules/helpers';
@@ -16,6 +15,7 @@ import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserve
import useMedia from '../../hooks/useMedia';
import useWebpThumbnail from '../../hooks/useWebpThumbnail';
import useLang from '../../hooks/useLang';
+import { renderMessageSummary } from './helpers/renderMessageText';
import ActionMessage from '../middle/ActionMessage';
@@ -71,7 +71,7 @@ const EmbeddedMessage: FC = ({
) : isActionMessage(message) ? (
) : (
- renderText(getMessageSummaryText(lang, message, Boolean(mediaThumbnail)))
+ renderMessageSummary(lang, message, Boolean(mediaThumbnail))
)}
{renderText(senderTitle || title || NBSP)}
diff --git a/src/components/common/WebLink.tsx b/src/components/common/WebLink.tsx
index 3401e8dc3..9b685e125 100644
--- a/src/components/common/WebLink.tsx
+++ b/src/components/common/WebLink.tsx
@@ -2,12 +2,16 @@ import React, { FC, memo, useCallback } from '../../lib/teact/teact';
import { ApiMessage, ApiWebPage } from '../../api/types';
-import { getFirstLinkInMessage, getMessageSummaryText, getMessageWebPage } from '../../modules/helpers';
+import {
+ getFirstLinkInMessage, getMessageText,
+ getMessageWebPage,
+} from '../../modules/helpers';
import buildClassName from '../../util/buildClassName';
import trimText from '../../util/trimText';
import renderText from './helpers/renderText';
import { formatPastTimeShort } from '../../util/dateFormat';
import useLang from '../../hooks/useLang';
+import { renderMessageSummary, TextPart } from './helpers/renderMessageText';
import Media from './Media';
import Link from '../ui/Link';
@@ -24,24 +28,27 @@ type OwnProps = {
onMessageClick: (messageId: number, chatId: string) => void;
};
+type ApiWebPageWithFormatted = ApiWebPage & { formattedDescription?: TextPart[] };
+
const WebLink: FC = ({
message, senderTitle, isProtected, onMessageClick,
}) => {
const lang = useLang();
- let linkData: ApiWebPage | undefined = getMessageWebPage(message);
+ let linkData: ApiWebPageWithFormatted | undefined = getMessageWebPage(message);
if (!linkData) {
const link = getFirstLinkInMessage(message);
if (link) {
const { url, domain } = link;
- const messageText = getMessageSummaryText(lang, message);
linkData = {
siteName: domain.replace(/^www./, ''),
url: url.includes('://') ? url : url.includes('@') ? `mailto:${url}` : `http://${url}`,
- description: messageText !== url ? messageText : undefined,
- } as ApiWebPage;
+ formattedDescription: getMessageText(message) !== url
+ ? renderMessageSummary(lang, message, undefined, undefined, MAX_TEXT_LENGTH, true)
+ : undefined,
+ } as ApiWebPageWithFormatted;
}
}
@@ -59,11 +66,12 @@ const WebLink: FC = ({
displayUrl,
title,
description,
+ formattedDescription,
photo,
video,
} = linkData;
- const truncatedDescription = !senderTitle && trimText(description, MAX_TEXT_LENGTH);
+ const truncatedDescription = !senderTitle && description && trimText(description, MAX_TEXT_LENGTH);
const className = buildClassName(
'WebLink scroll-item',
@@ -83,9 +91,9 @@ const WebLink: FC = ({
{renderText(title || siteName || displayUrl)}
- {truncatedDescription && (
+ {(truncatedDescription || formattedDescription) && (
- {renderText(truncatedDescription)}
+ {formattedDescription || (truncatedDescription && renderText(truncatedDescription))}
)}
«
- {renderText(messageText)}
+ {messageText}
»
);
}
return (
- {renderText(messageText)}
+ {messageText}
);
}
diff --git a/src/components/common/helpers/renderMessageText.tsx b/src/components/common/helpers/renderMessageText.tsx
index 882ab2287..482129b21 100644
--- a/src/components/common/helpers/renderMessageText.tsx
+++ b/src/components/common/helpers/renderMessageText.tsx
@@ -4,15 +4,64 @@ import { getDispatch } from '../../../lib/teact/teactn';
import { ApiMessageEntity, ApiMessageEntityTypes, ApiMessage } from '../../../api/types';
-import { getMessageText } from '../../../modules/helpers';
-import renderText from './renderText';
+import {
+ getMessageSummaryText,
+ getMessageSummaryDescription,
+ getMessageSummaryEmoji,
+ getMessageText,
+ TRUNCATED_SUMMARY_LENGTH,
+} from '../../../modules/helpers';
+import renderText, { TextFilter } from './renderText';
import MentionLink from '../../middle/message/MentionLink';
import SafeLink from '../SafeLink';
+import Spoiler from '../spoiler/Spoiler';
+import { LangFn } from '../../../hooks/useLang';
export type TextPart = string | Element;
-export function renderMessageText(message: ApiMessage, highlight?: string, shouldRenderHqEmoji?: boolean) {
+export function renderMessageSummary(
+ lang: LangFn,
+ message: ApiMessage,
+ noEmoji = false,
+ highlight?: string,
+ truncateLength = TRUNCATED_SUMMARY_LENGTH,
+ shouldAddEllipsis?: boolean,
+): TextPart[] {
+ const hasSpoilers = message.content.text?.entities?.some((l) => l.type === ApiMessageEntityTypes.Spoiler);
+ if (!hasSpoilers) {
+ let text = getMessageSummaryText(lang, message, noEmoji, truncateLength);
+ if (shouldAddEllipsis) {
+ text += '...';
+ }
+
+ if (highlight) {
+ return renderText(text, ['emoji', 'highlight'], {
+ highlight,
+ });
+ } else {
+ return renderText(text);
+ }
+ }
+
+ const text = renderMessageText(message, highlight, undefined, true, truncateLength);
+ const emoji = !noEmoji && getMessageSummaryEmoji(message);
+ const emojiWithSpace = emoji ? `${emoji} ` : '';
+ const description = getMessageSummaryDescription(lang, message, text);
+ return [
+ emojiWithSpace,
+ ...(Array.isArray(description) ? description : [description]),
+ shouldAddEllipsis && '...',
+ ].filter(Boolean);
+}
+
+export function renderMessageText(
+ message: ApiMessage,
+ highlight?: string,
+ shouldRenderHqEmoji?: boolean,
+ isSimple?: boolean,
+ truncateLength?: number,
+) {
const formattedText = message.content.text;
if (!formattedText || !formattedText.text) {
@@ -21,7 +70,15 @@ export function renderMessageText(message: ApiMessage, highlight?: string, shoul
}
const { text, entities } = formattedText;
- return renderTextWithEntities(text, entities, highlight, shouldRenderHqEmoji);
+ return renderTextWithEntities(
+ truncateLength ? text.substr(0, truncateLength) : text,
+ entities,
+ highlight,
+ shouldRenderHqEmoji,
+ undefined,
+ message.id,
+ isSimple,
+ );
}
interface IOrganizedEntity {
@@ -102,9 +159,11 @@ export function renderTextWithEntities(
highlight?: string,
shouldRenderHqEmoji?: boolean,
shouldRenderAsHtml?: boolean,
+ messageId?: number,
+ isSimple?: boolean,
) {
if (!entities || !entities.length) {
- return renderMessagePart(text, highlight, shouldRenderHqEmoji, shouldRenderAsHtml);
+ return renderMessagePart(text, highlight, shouldRenderHqEmoji, shouldRenderAsHtml, isSimple);
}
const result: TextPart[] = [];
@@ -133,7 +192,7 @@ export function renderTextWithEntities(
}
if (textBefore) {
renderResult.push(...renderMessagePart(
- textBefore, highlight, shouldRenderHqEmoji, shouldRenderAsHtml,
+ textBefore, highlight, shouldRenderHqEmoji, shouldRenderAsHtml, isSimple,
) as TextPart[]);
}
}
@@ -176,7 +235,7 @@ export function renderTextWithEntities(
// Render the entity itself
const newEntity = shouldRenderAsHtml
? processEntityAsHtml(entity, entityContent, nestedEntityContent)
- : processEntity(entity, entityContent, nestedEntityContent);
+ : processEntity(entity, entityContent, nestedEntityContent, highlight, messageId, isSimple);
if (Array.isArray(newEntity)) {
renderResult.push(...newEntity);
@@ -193,7 +252,7 @@ export function renderTextWithEntities(
}
if (textAfter) {
renderResult.push(...renderMessagePart(
- textAfter, highlight, shouldRenderHqEmoji, shouldRenderAsHtml,
+ textAfter, highlight, shouldRenderHqEmoji, shouldRenderAsHtml, isSimple,
) as TextPart[]);
}
}
@@ -226,19 +285,36 @@ function processEntity(
entity: ApiMessageEntity,
entityContent: TextPart,
nestedEntityContent: TextPart[],
+ highlight?: string,
+ messageId?: number,
+ isSimple?: boolean,
) {
const entityText = typeof entityContent === 'string' && entityContent;
const renderedContent = nestedEntityContent.length ? nestedEntityContent : entityContent;
+ function renderNestedMessagePart() {
+ return renderMessagePart(
+ renderedContent, highlight, undefined, undefined, isSimple,
+ );
+ }
+
if (!entityText) {
- return renderMessagePart(renderedContent);
+ return renderNestedMessagePart();
+ }
+
+ if (isSimple) {
+ const text = renderNestedMessagePart();
+ if (entity.type === ApiMessageEntityTypes.Spoiler) {
+ return {text};
+ }
+ return text;
}
switch (entity.type) {
case ApiMessageEntityTypes.Bold:
- return {renderMessagePart(renderedContent)};
+ return {renderNestedMessagePart()};
case ApiMessageEntityTypes.Blockquote:
- return {renderMessagePart(renderedContent)}
;
+ return {renderNestedMessagePart()}
;
case ApiMessageEntityTypes.BotCommand:
return (
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Hashtag:
@@ -256,7 +332,7 @@ function processEntity(
className="text-entity-link"
dir="auto"
>
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Cashtag:
@@ -266,11 +342,11 @@ function processEntity(
className="text-entity-link"
dir="auto"
>
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Code:
- return {renderMessagePart(renderedContent)};
+ return {renderNestedMessagePart()};
case ApiMessageEntityTypes.Email:
return (
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Italic:
- return {renderMessagePart(renderedContent)};
+ return {renderNestedMessagePart()};
case ApiMessageEntityTypes.MentionName:
return (
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Mention:
return (
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Phone:
@@ -304,13 +380,13 @@ function processEntity(
className="text-entity-link"
dir="auto"
>
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Pre:
- return {renderMessagePart(renderedContent)};
+ return {renderNestedMessagePart()};
case ApiMessageEntityTypes.Strike:
- return {renderMessagePart(renderedContent)};
+ return {renderNestedMessagePart()};
case ApiMessageEntityTypes.TextUrl:
case ApiMessageEntityTypes.Url:
return (
@@ -318,13 +394,15 @@ function processEntity(
url={getLinkUrl(entityText, entity)}
text={entityText}
>
- {renderMessagePart(renderedContent)}
+ {renderNestedMessagePart()}
);
case ApiMessageEntityTypes.Underline:
- return {renderMessagePart(renderedContent)};
+ return {renderNestedMessagePart()};
+ case ApiMessageEntityTypes.Spoiler:
+ return {renderNestedMessagePart()};
default:
- return renderMessagePart(renderedContent);
+ return renderNestedMessagePart();
}
}
@@ -333,12 +411,13 @@ function renderMessagePart(
highlight?: string,
shouldRenderHqEmoji?: boolean,
shouldRenderAsHtml?: boolean,
+ isSimple?: boolean,
) {
if (Array.isArray(content)) {
const result: TextPart[] = [];
content.forEach((c) => {
- result.push(...renderMessagePart(c, highlight, shouldRenderHqEmoji, shouldRenderAsHtml));
+ result.push(...renderMessagePart(c, highlight, shouldRenderHqEmoji, shouldRenderAsHtml, isSimple));
});
return result;
@@ -350,10 +429,15 @@ function renderMessagePart(
const emojiFilter = shouldRenderHqEmoji ? 'hq_emoji' : 'emoji';
+ const filters: TextFilter[] = [emojiFilter];
+ if (!isSimple) {
+ filters.push('br');
+ }
+
if (highlight) {
- return renderText(content, [emojiFilter, 'br', 'highlight'], { highlight });
+ return renderText(content, filters.concat('highlight'), { highlight });
} else {
- return renderText(content, [emojiFilter, 'br']);
+ return renderText(content, filters);
}
}
diff --git a/src/components/common/helpers/renderText.tsx b/src/components/common/helpers/renderText.tsx
index 68cd3ba21..4c16d3198 100644
--- a/src/components/common/helpers/renderText.tsx
+++ b/src/components/common/helpers/renderText.tsx
@@ -11,16 +11,17 @@ import MentionLink from '../../middle/message/MentionLink';
import SafeLink from '../SafeLink';
type TextPart = string | Element;
+export type TextFilter = (
+ 'escape_html' | 'hq_emoji' | 'emoji' | 'emoji_html' | 'br' | 'br_html' | 'highlight' | 'links' |
+ 'simple_markdown' | 'simple_markdown_html'
+);
const RE_LETTER_OR_DIGIT = /^[\d\wа-яё]$/i;
const SIMPLE_MARKDOWN_REGEX = /(\*\*|__).+?\1/g;
export default function renderText(
part: TextPart,
- filters: Array<(
- 'escape_html' | 'hq_emoji' | 'emoji' | 'emoji_html' | 'br' | 'br_html' | 'highlight' | 'links' |
- 'simple_markdown' | 'simple_markdown_html'
- )> = ['emoji'],
+ filters: Array = ['emoji'],
params?: { highlight: string | undefined },
): TextPart[] {
if (typeof part !== 'string') {
diff --git a/src/components/common/spoiler/Spoiler.scss b/src/components/common/spoiler/Spoiler.scss
new file mode 100644
index 000000000..44b6c499e
--- /dev/null
+++ b/src/components/common/spoiler/Spoiler.scss
@@ -0,0 +1,31 @@
+.Spoiler {
+ transition: color 250ms ease;
+
+ &:not(.is-revealed) {
+ cursor: pointer;
+ color: transparent;
+ background-image: url('../../../assets/spoiler-dots-black.png');
+ background-size: auto 100%;
+ border-radius: 0.5rem;
+
+ &.animate {
+ animation: pulse-opacity-light 1.75s linear infinite;
+ }
+ }
+
+ html.theme-dark &:not(.is-revealed), html.theme-light .ListItem.selected &:not(.is-revealed) {
+ background-image: url('../../../assets/spoiler-dots-white.png');
+ }
+}
+
+@keyframes pulse-opacity-light {
+ 25% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.25;
+ }
+ 75% {
+ opacity: 1;
+ }
+}
diff --git a/src/components/common/spoiler/Spoiler.tsx b/src/components/common/spoiler/Spoiler.tsx
new file mode 100644
index 000000000..817beceb2
--- /dev/null
+++ b/src/components/common/spoiler/Spoiler.tsx
@@ -0,0 +1,66 @@
+import React, {
+ FC, memo, useCallback, useEffect,
+} from '../../../lib/teact/teact';
+
+import buildClassName from '../../../util/buildClassName';
+import useFlag from '../../../hooks/useFlag';
+
+import './Spoiler.scss';
+
+type OwnProps = {
+ children?: React.ReactNode;
+ messageId?: number;
+ isInactive?: boolean;
+};
+
+const spoilersByMessageId: Map = new Map();
+
+const Spoiler: FC = ({
+ children,
+ messageId,
+ isInactive,
+}) => {
+ const [isRevealed, reveal] = useFlag();
+
+ const handleClick = useCallback(() => {
+ if (!messageId) return;
+
+ spoilersByMessageId.get(messageId)?.forEach((_reveal) => _reveal());
+ }, [messageId]);
+
+ useEffect(() => {
+ if (isRevealed && messageId) {
+ spoilersByMessageId.delete(messageId);
+ return undefined;
+ }
+
+ if (!messageId) {
+ return undefined;
+ }
+
+ if (spoilersByMessageId.has(messageId)) {
+ spoilersByMessageId.get(messageId)!.push(reveal);
+ } else {
+ spoilersByMessageId.set(messageId, [reveal]);
+ }
+
+ return () => {
+ spoilersByMessageId.delete(messageId);
+ };
+ }, [handleClick, isRevealed, messageId, reveal]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default memo(Spoiler);
diff --git a/src/components/left/main/Chat.tsx b/src/components/left/main/Chat.tsx
index 685a5ba7b..fd4bb7770 100644
--- a/src/components/left/main/Chat.tsx
+++ b/src/components/left/main/Chat.tsx
@@ -20,7 +20,6 @@ import {
getMessageSenderName,
isChatChannel,
getMessageMediaHash,
- getMessageSummaryText,
getMessageMediaThumbDataUri,
getMessageVideo,
getMessageSticker,
@@ -40,6 +39,7 @@ import useChatContextActions from '../../../hooks/useChatContextActions';
import useFlag from '../../../hooks/useFlag';
import useMedia from '../../../hooks/useMedia';
import { ChatAnimationTypes } from './hooks';
+import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import Avatar from '../../common/Avatar';
import VerifiedIcon from '../../common/VerifiedIcon';
@@ -241,15 +241,14 @@ const Chat: FC = ({
return (
- {renderText(renderActionMessageText(
+ {renderActionMessageText(
lang,
lastMessage,
actionOrigin,
actionTargetUsers,
actionTargetMessage,
actionTargetChatId,
- { asPlain: true },
- ) as string)}
+ )}
);
}
@@ -264,7 +263,7 @@ const Chat: FC = ({
:
>
)}
- {renderMessageSummary(lang, lastMessage!, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
+ {renderSummary(lang, lastMessage!, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
);
}
@@ -333,16 +332,16 @@ const Chat: FC = ({
);
};
-function renderMessageSummary(lang: LangFn, message: ApiMessage, blobUrl?: string, isRoundVideo?: boolean) {
+function renderSummary(lang: LangFn, message: ApiMessage, blobUrl?: string, isRoundVideo?: boolean) {
if (!blobUrl) {
- return renderText(getMessageSummaryText(lang, message));
+ return renderMessageSummary(lang, message);
}
return (
{getMessageVideo(message) && }
- {renderText(getMessageSummaryText(lang, message, true))}
+ {renderMessageSummary(lang, message, true)}
);
}
diff --git a/src/components/left/search/ChatMessage.tsx b/src/components/left/search/ChatMessage.tsx
index fddd582f3..2121b5cb8 100644
--- a/src/components/left/search/ChatMessage.tsx
+++ b/src/components/left/search/ChatMessage.tsx
@@ -12,7 +12,6 @@ import {
getChatTitle,
getPrivateChatUserId,
getMessageMediaHash,
- getMessageSummaryText,
getMessageMediaThumbDataUri,
getMessageVideo,
getMessageRoundVideo,
@@ -23,6 +22,7 @@ import useMedia from '../../../hooks/useMedia';
import { formatPastTimeShort } from '../../../util/dateFormat';
import useLang, { LangFn } from '../../../hooks/useLang';
import useSelectWithEnter from '../../../hooks/useSelectWithEnter';
+import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import Avatar from '../../common/Avatar';
import VerifiedIcon from '../../common/VerifiedIcon';
@@ -98,7 +98,7 @@ const ChatMessage: FC = ({
- {renderMessageSummary(lang, message, mediaBlobUrl || mediaThumbnail, searchQuery, isRoundVideo)}
+ {renderSummary(lang, message, mediaBlobUrl || mediaThumbnail, searchQuery, isRoundVideo)}
@@ -106,18 +106,18 @@ const ChatMessage: FC = ({
);
};
-function renderMessageSummary(
+function renderSummary(
lang: LangFn, message: ApiMessage, blobUrl?: string, searchQuery?: string, isRoundVideo?: boolean,
) {
if (!blobUrl) {
- return renderText(getMessageSummaryText(lang, message));
+ return renderMessageSummary(lang, message, undefined, searchQuery);
}
return (
{getMessageVideo(message) && }
- {renderText(getMessageSummaryText(lang, message, true), ['emoji', 'highlight'], { highlight: searchQuery })}
+ {renderMessageSummary(lang, message, true, searchQuery)}
);
}
diff --git a/src/components/left/search/ChatMessageResults.tsx b/src/components/left/search/ChatMessageResults.tsx
index 65a736bae..0fa968e65 100644
--- a/src/components/left/search/ChatMessageResults.tsx
+++ b/src/components/left/search/ChatMessageResults.tsx
@@ -6,10 +6,10 @@ import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
import { ApiChat, ApiMessage } from '../../../api/types';
import { LoadMoreDirection } from '../../../types';
-import { getMessageSummaryText } from '../../../modules/helpers';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { throttle } from '../../../util/schedulers';
import useLang from '../../../hooks/useLang';
+import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import InfiniteScroll from '../../ui/InfiniteScroll';
import ChatMessage from './ChatMessage';
@@ -76,7 +76,7 @@ const ChatMessageResults: FC = ({
}, [foundIds, globalMessagesByChatId]);
function renderFoundMessage(message: ApiMessage) {
- const text = getMessageSummaryText(lang, message);
+ const text = renderMessageSummary(lang, message);
const chat = chatsById[message.chatId];
if (!text || !chat) {
diff --git a/src/components/left/search/ChatResults.tsx b/src/components/left/search/ChatResults.tsx
index fe6f0b4a8..18570577f 100644
--- a/src/components/left/search/ChatResults.tsx
+++ b/src/components/left/search/ChatResults.tsx
@@ -8,10 +8,14 @@ import { LoadMoreDirection } from '../../../types';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
import { unique } from '../../../util/iteratees';
-import { getMessageSummaryText, sortChatIds, filterUsersByName } from '../../../modules/helpers';
+import {
+ sortChatIds,
+ filterUsersByName,
+} from '../../../modules/helpers';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { throttle } from '../../../util/schedulers';
import useLang from '../../../hooks/useLang';
+import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import InfiniteScroll from '../../ui/InfiniteScroll';
import LeftSearchResultChat from './LeftSearchResultChat';
@@ -154,7 +158,7 @@ const ChatResults: FC = ({
}, [shouldShowMoreGlobal]);
function renderFoundMessage(message: ApiMessage) {
- const text = getMessageSummaryText(lang, message);
+ const text = renderMessageSummary(lang, message);
const chat = chatsById[message.chatId];
if (!text || !chat) {
diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx
index 72e232ead..934e8ba41 100644
--- a/src/components/middle/ActionMessage.tsx
+++ b/src/components/middle/ActionMessage.tsx
@@ -14,7 +14,6 @@ import {
} from '../../modules/selectors';
import { isChatChannel } from '../../modules/helpers';
import buildClassName from '../../util/buildClassName';
-import renderText from '../common/helpers/renderText';
import { renderActionMessageText } from '../common/helpers/renderActionMessageText';
import useEnsureMessage from '../../hooks/useEnsureMessage';
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
@@ -96,7 +95,7 @@ const ActionMessage: FC = ({
targetUsers,
targetMessage,
targetChatId,
- isEmbedded ? { isEmbedded: true, asPlain: true } : undefined,
+ isEmbedded ? { isEmbedded: true } : undefined,
);
const {
isContextMenuOpen, contextMenuPosition,
@@ -111,7 +110,7 @@ const ActionMessage: FC = ({
};
if (isEmbedded) {
- return {renderText(content as string)};
+ return {content};
}
const className = buildClassName(
diff --git a/src/components/middle/HeaderPinnedMessage.tsx b/src/components/middle/HeaderPinnedMessage.tsx
index 39661db72..c04cc477d 100644
--- a/src/components/middle/HeaderPinnedMessage.tsx
+++ b/src/components/middle/HeaderPinnedMessage.tsx
@@ -3,8 +3,7 @@ import React, { FC, memo, useCallback } from '../../lib/teact/teact';
import { ApiMessage } from '../../api/types';
import { getPictogramDimensions } from '../common/helpers/mediaDimensions';
-import { getMessageMediaHash, getMessageSummaryText } from '../../modules/helpers';
-import renderText from '../common/helpers/renderText';
+import { getMessageMediaHash } from '../../modules/helpers';
import useMedia from '../../hooks/useMedia';
import useWebpThumbnail from '../../hooks/useWebpThumbnail';
@@ -14,6 +13,7 @@ import RippleEffect from '../ui/RippleEffect';
import buildClassName from '../../util/buildClassName';
import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang';
+import { renderMessageSummary } from '../common/helpers/renderMessageText';
import PinnedMessageNavigation from './PinnedMessageNavigation';
@@ -35,7 +35,7 @@ const HeaderPinnedMessage: FC = ({
const mediaThumbnail = useWebpThumbnail(message);
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'pictogram'));
- const text = getMessageSummaryText(lang, message, Boolean(mediaThumbnail));
+ const text = renderMessageSummary(lang, message, Boolean(mediaThumbnail));
const [isUnpinDialogOpen, openUnpinDialog, closeUnpinDialog] = useFlag();
const handleUnpinMessage = useCallback(() => {
@@ -89,7 +89,7 @@ const HeaderPinnedMessage: FC = ({
{customTitle || `${lang('PinnedMessage')} ${index > 0 ? `#${count - index}` : ''}`}
- {renderText(text)}
+ {text}
diff --git a/src/components/right/RightSearch.tsx b/src/components/right/RightSearch.tsx
index 65f34ef1f..5ba7c834f 100644
--- a/src/components/right/RightSearch.tsx
+++ b/src/components/right/RightSearch.tsx
@@ -12,7 +12,6 @@ import {
selectCurrentTextSearch,
} from '../../modules/selectors';
import {
- getMessageSummaryText,
getChatTitle,
getUserFullName,
isChatChannel,
@@ -23,6 +22,7 @@ import { orderBy } from '../../util/iteratees';
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
import useHistoryBack from '../../hooks/useHistoryBack';
+import { renderMessageSummary } from '../common/helpers/renderMessageText';
import InfiniteScroll from '../ui/InfiniteScroll';
import ListItem from '../ui/ListItem';
@@ -109,7 +109,7 @@ const RightSearch: FC = ({
message, senderUser, senderChat, onClick,
}: Result) => {
const title = senderChat ? getChatTitle(lang, senderChat) : getUserFullName(senderUser);
- const text = getMessageSummaryText(lang, message);
+ const text = renderMessageSummary(lang, message, undefined, query);
return (
= ({
- {renderText(text, ['emoji', 'highlight'], { highlight: query })}
+ {text}
diff --git a/src/modules/helpers/index.ts b/src/modules/helpers/index.ts
index 563c05919..392e61a3d 100644
--- a/src/modules/helpers/index.ts
+++ b/src/modules/helpers/index.ts
@@ -1,6 +1,7 @@
export * from './users';
export * from './chats';
export * from './messages';
+export * from './messageSummary';
export * from './messageMedia';
export * from './localSearch';
export * from './payments';
diff --git a/src/modules/helpers/messageSummary.ts b/src/modules/helpers/messageSummary.ts
new file mode 100644
index 000000000..60ddab03d
--- /dev/null
+++ b/src/modules/helpers/messageSummary.ts
@@ -0,0 +1,141 @@
+import { LangFn } from '../../hooks/useLang';
+import { ApiMessage, ApiMessageEntityTypes } from '../../api/types';
+import type { TextPart } from '../../components/common/helpers/renderMessageText';
+import { CONTENT_NOT_SUPPORTED } from '../../config';
+import { getMessageText } from './messages';
+
+const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
+export const TRUNCATED_SUMMARY_LENGTH = 80;
+
+export function getMessageSummaryText(
+ lang: LangFn,
+ message: ApiMessage,
+ noEmoji = false,
+ truncateLength = TRUNCATED_SUMMARY_LENGTH,
+) {
+ const emoji = !noEmoji && getMessageSummaryEmoji(message);
+ const emojiWithSpace = emoji ? `${emoji} ` : '';
+
+ let text = getMessageText(message);
+ if (text) {
+ const { entities } = message.content.text || {};
+ if (entities?.length) {
+ text = entities.reduce((accText, { type, offset, length }) => {
+ if (type !== ApiMessageEntityTypes.Spoiler) {
+ return accText;
+ }
+
+ const spoiler = generateBrailleSpoiler(length);
+
+ return `${accText.substr(0, offset)}${spoiler}${accText.substr(offset + length, accText.length)}`;
+ }, text);
+ }
+
+ text = text.substr(0, truncateLength);
+ }
+
+ const description = getMessageSummaryDescription(lang, message, text);
+
+ return `${emojiWithSpace}${description}`;
+}
+
+export function getMessageSummaryEmoji(message: ApiMessage) {
+ const {
+ photo, video, audio, voice, document, sticker, poll,
+ } = message.content;
+
+ if (message.groupedId || photo) {
+ return '🖼';
+ }
+
+ if (video) {
+ return '📹';
+ }
+
+ if (sticker) {
+ return sticker.emoji;
+ }
+
+ if (audio) {
+ return '🎧';
+ }
+
+ if (voice) {
+ return '🎤';
+ }
+
+ if (document) {
+ return '📎';
+ }
+
+ if (poll) {
+ return '📊';
+ }
+
+ return undefined;
+}
+
+export function getMessageSummaryDescription(lang: LangFn, message: ApiMessage, truncatedText?: string | TextPart[]) {
+ const {
+ text, photo, video, audio, voice, document, sticker, contact, poll, invoice,
+ } = message.content;
+
+ if (message.groupedId) {
+ return truncatedText || lang('lng_in_dlg_album');
+ }
+
+ if (photo) {
+ return truncatedText || lang('AttachPhoto');
+ }
+
+ if (video) {
+ return truncatedText || lang(video.isGif ? 'AttachGif' : 'AttachVideo');
+ }
+
+ if (sticker) {
+ return lang('AttachSticker').trim();
+ }
+
+ if (audio) {
+ return getMessageAudioCaption(message) || lang('AttachMusic');
+ }
+
+ if (voice) {
+ return truncatedText || lang('AttachAudio');
+ }
+
+ if (document) {
+ return truncatedText || document.fileName;
+ }
+
+ if (contact) {
+ return lang('AttachContact');
+ }
+
+ if (poll) {
+ return poll.summary.question;
+ }
+
+ if (invoice) {
+ return 'Invoice';
+ }
+
+ if (text) {
+ return truncatedText;
+ }
+
+ return CONTENT_NOT_SUPPORTED;
+}
+
+export function generateBrailleSpoiler(length: number) {
+ return new Array(length)
+ .fill(undefined)
+ .map(() => SPOILER_CHARS[Math.floor(Math.random() * SPOILER_CHARS.length)])
+ .join('');
+}
+
+function getMessageAudioCaption(message: ApiMessage) {
+ const { audio, text } = message.content;
+
+ return (audio && [audio.title, audio.performer].filter(Boolean).join(' — ')) || (text?.text);
+}
diff --git a/src/modules/helpers/messages.ts b/src/modules/helpers/messages.ts
index fe95ec89f..a710238a4 100644
--- a/src/modules/helpers/messages.ts
+++ b/src/modules/helpers/messages.ts
@@ -4,18 +4,17 @@ import {
import { LangFn } from '../../hooks/useLang';
import {
- LOCAL_MESSAGE_ID_BASE,
- SERVICE_NOTIFICATIONS_USER_ID,
- RE_LINK_TEMPLATE,
CONTENT_NOT_SUPPORTED,
+ LOCAL_MESSAGE_ID_BASE,
+ RE_LINK_TEMPLATE,
+ SERVICE_NOTIFICATIONS_USER_ID,
} from '../../config';
import { getUserFullName } from './users';
-import { isWebpSupported, IS_OPUS_SUPPORTED } from '../../util/environment';
+import { IS_OPUS_SUPPORTED, isWebpSupported } from '../../util/environment';
import { getChatTitle, isUserId } from './chats';
import parseEmojiOnlyString from '../../components/common/helpers/parseEmojiOnlyString';
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
-const TRUNCATED_SUMMARY_LENGTH = 80;
export type MessageKey = `msg${string}-${number}`;
@@ -39,60 +38,6 @@ export function getMessageOriginalId(message: ApiMessage) {
return message.previousLocalId || message.id;
}
-export function getMessageSummaryText(lang: LangFn, message: ApiMessage, noEmoji = false) {
- const {
- text, photo, video, audio, voice, document, sticker, contact, poll, invoice,
- } = message.content;
-
- const truncatedText = text && text.text.substr(0, TRUNCATED_SUMMARY_LENGTH);
-
- if (message.groupedId) {
- return `${noEmoji ? '' : '🖼 '}${truncatedText || lang('lng_in_dlg_album')}`;
- }
-
- if (photo) {
- return `${noEmoji ? '' : '🖼 '}${truncatedText || lang('AttachPhoto')}`;
- }
-
- if (video) {
- return `${noEmoji ? '' : '📹 '}${truncatedText || lang(video.isGif ? 'AttachGif' : 'AttachVideo')}`;
- }
-
- if (sticker) {
- return `${sticker.emoji || ''} ${lang('AttachSticker')}`.trim();
- }
-
- if (audio) {
- return `${noEmoji ? '' : '🎧 '}${getMessageAudioCaption(message) || lang('AttachMusic')}`;
- }
-
- if (voice) {
- return `${noEmoji ? '' : '🎤 '}${truncatedText || lang('AttachAudio')}`;
- }
-
- if (document) {
- return `${noEmoji ? '' : '📎 '}${truncatedText || document.fileName}`;
- }
-
- if (contact) {
- return lang('AttachContact');
- }
-
- if (poll) {
- return `${noEmoji ? '' : '📊 '}${poll.summary.question}`;
- }
-
- if (invoice) {
- return 'Invoice';
- }
-
- if (text) {
- return truncatedText;
- }
-
- return CONTENT_NOT_SUPPORTED;
-}
-
export function getMessageText(message: ApiMessage) {
const {
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice,
@@ -230,12 +175,6 @@ export function isHistoryClearMessage(message: ApiMessage) {
return message.content.action && message.content.action.type === 'historyClear';
}
-export function getMessageAudioCaption(message: ApiMessage) {
- const { audio, text } = message.content;
-
- return (audio && [audio.title, audio.performer].filter(Boolean).join(' — ')) || (text?.text);
-}
-
export function getMessageContentFilename(message: ApiMessage) {
const { content } = message;