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 zcmV-b465^qP)Px>4@pEpRCr#soCT=1SrNzoyRN&oYqw&Hfq{h>ASSW~c3>kGcA{V)iiydZtO^P) zi?S*f2qKEzg^jDI*d1%~dpZBhIQPUn@BMt>a_{#%=ggTiJ#*%K$Ep4tC+R4yiu!{|UQ}#e8JO@hCV~=AIzwpOQ|I^Z-c@k@S~_;<+UKEuss+{G2H1 z?8Z30!Bif<|GuOvNqVxRS4cVp^cYdj8+nnWr?;P%20V;A_{G0jzDFqV{CC^iRuqB9 z5A!qqSpVXZZYSxToiQVPj+eyGzZ#9b--kdaxwF2M*(Tz~i@)PL}jYNpBcz z=!71F0Rqxytck^Y0K?c@OS-S5KLp~Qvq@Gag4d47RS0Z>`kw8^<#x-8l$-WJ+bPjPaZoN_wWGH%WR#*su3;jz(vM;GU9hA?fRb zMUKx5j))og*=BphTA8f)o%Q(ndl0A|uIH2Vr=mNE@XcuU@(D@bXeKQRz>EQgb!kcG zm-Ov4*z|h7q-QiHf2e6%Ptbv92IX}l-WO=f69v>7&pS-?N(guGdK&DBBS~i=HujG; z5KZUZV^9C2Yqp5uh1CY{-O8RR1h<*Rv z!oJx5-c8cGCH=nn1xvPn1MAn4ekkcG8#Xx;HWPr~Fo4Y=`Hmm5i@BwwpEU+m%yb-s zo4B&18wR%&;p3jrSo_mxaxtY3Yz+Q<*!#%(`Mu$rhEZ7vqO&eK5OnQz+o)WJn+HaL6-@XtwV*m6|=pM z)yeTph0HygFwe^6*e1qJ2XG^Uaq8EVbYrXc!Y_`rYahS7atkA3R@O3X1b`>|uL_Yp zH)`liP%1)H8M6{JzDM(X$nV<+j}~KNND3w}Y6O7o^dT7YZKg~><}wCfZ9m5Bv3vl4)nCd`=Nzy-k_rGPL3y2bBD)zIk2XZZiIeI$6a z;@RSy9T^;f*k9g7(oKS%-Bgyn#noQ zyxU0n=zuW@1CP!4%3NPG6a1$MMz{`=*Up47^V51N#3s}-)Tv|;a_!y4eV<0UUgzINs}D}h5r z2wvCnZ}RAfWS+Z)0delLT3(EXjmno>VaM_DZ}Md7^rL-veQE-ClJu@YMT+^*_mYyH zE9q5|zEv#F9C9@y6Kx{#9W&9ZysXSA-$$koxGww?ecKd`Bz!9qj2mk}EXsqK@JKn| zt7#KB;C^~#)fIj*n1r$SYRr3HhlnmPy+9HIO58!}I*O{b$?<(ockp}zy%oOj#khq^{PS})l?I`Utg00rC zFkx#=I^$FIsaeY+oSwh6nU3@FyNrwX!bud0gWEd`;CX?OuQEWi;ZyC*a_a^&QjSn7 zK0Pyxf3yC~lFX`ChPN_(Ga>7pxUH;MRI0r1xJL)Ru1kz^@R_h292YwBf>@Z}d^m2V zUt7}GTB?L;HG`sbWh6(4tu%0Cl0B~?6eD7rF*Fl!%*Q!1so= z`!*87T+3%IpN;R*t0+wd%mBT8tUZT+W?-(o|Sm7Rnd3RRRWE_2F@W`J) zJbWpVlOxTFPp@nYI6g3UFhN@j!ZtIPJ-1^*=ilJL_#DivVguLh$vAo?OKJ+V+02A~ zi_5Fj!Vg8!J?W}Q+^lY033e*XjZ%xW(rkuVg~3$KMjOt-T2MG1j1mN}@P&KS>`hc5vji_%}ZY<7HbBgq)Vnj_8lK2zR!g z4l;k)&caI!wn>hG(+mmcZJ%uk_Nyg5zL{&Z#oZj3?( zYgoH}r^U6mUQ7{Yf!IFgvuFN*lcQVkrpsmZSSWfLY}|W(e#Ja0e5*s4rTd#Pm~YDZ@pKOfQc{I2n#37qRS?)V?2Pg_!wp73}+Ot_S5znye# zs$l^z<1AEkwq8flHJWSd6*MZ=aA$%I*e5U8etc>^+^;aSv}hW{br0&M_iwGYd?s+? z#D%Vl7dE562KzR(&2Mt7a6M2l;B52LxW8Z~fc0aMT4_uN$uuzD7& zhhL9rm5N|@uVyfnEBceT=TeoHkp}GJ_p-GB#;C2O=aVYL%$jF{8()3{$HFly-b5VG zM&@8?qS8dBWP$rYyXC>j&&rallqK?uOK=oZXptf=*j+vYL2A$BQFP|@7;FT&viKHB zk8N!{+5E-1^mB0wkpqgOJ|m&&;Tnr7d0oSy?##n)2+U(4ab91+d8BPUrkRGA^dEQV z&3cvCr-zqeUr*AP+6@wDf1-Ubw*|@hDEak>=^{J_)ewcd!B6d+zL??UkkpEKE;y-R z8QF>yZ#8E#Q)fb2K;qbHK;voYT=Th5 z>r5da>RGjsW@hU#6JmeE^rCPGM|3Y;p)wP*Chg-#M-2BGqyRn-oe9T?DV{SDJWIDP zM&D5qS2_1NlxHpAahVO*)vJ*kPf{|>E;Z};jPPVk^;SiX4`!tAkWG&`vx$Njv)V4> zXHj&Fz(+mL8R_Wr%p|0g`YIQzD^|q}L4ZEe?pRUO=c;EfE3j)S6$={IdT!*XiU4j{ zMDUDHJT%Aw+H0DC@ZV_gjADk}`>es0yD%%$90}pS(F87Fqft*QGzwVLV>cx0u6$d@ zZDjIB-I1PeqSElj-CpiA(?8v_1{E`#eRIB8fI!oGp6Q=`Z*M=Nw01}P%;2L+WG1l9 z2iHzM-2yrMVh);m97|U;O__Mge1Fo|!_k zJST6!r+T%jK_KAnvWb`GhCym(@Jc)L_gVCtrJcihiW;9;1P8|whwKx3!OkqOxqLfk v#P6fHVfG*1ARL}|(7y9uE@1?DpK@uV00000NkvXXu0mjfjOi+N 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;