Action Message: Refactoring and fixes

This commit is contained in:
Alexander Zinchuk 2022-02-02 22:48:22 +01:00
parent 8baad38a37
commit c6672612bf
16 changed files with 263 additions and 286 deletions

View File

@ -46,7 +46,7 @@ const WebLink: FC<OwnProps> = ({
siteName: domain.replace(/^www./, ''),
url: url.includes('://') ? url : url.includes('@') ? `mailto:${url}` : `http://${url}`,
formattedDescription: getMessageText(message) !== url
? renderMessageSummary(lang, message, undefined, undefined, MAX_TEXT_LENGTH, true)
? renderMessageSummary(lang, message, undefined, undefined, MAX_TEXT_LENGTH)
: undefined,
} as ApiWebPageWithFormatted;
}

View File

@ -6,12 +6,12 @@ import {
import { LangFn } from '../../../hooks/useLang';
import {
getChatTitle,
getMessageContent, getMessageSummaryText,
getMessageSummaryText,
getUserFullName,
} from '../../../modules/helpers';
import trimText from '../../../util/trimText';
import { formatCurrency } from '../../../util/formatCurrency';
import { renderMessageSummary, TextPart } from './renderMessageText';
import { TextPart, renderMessageSummary } from './renderMessageText';
import renderText from './renderText';
import UserLink from '../UserLink';
@ -19,12 +19,12 @@ import MessageLink from '../MessageLink';
import ChatLink from '../ChatLink';
import GroupCallLink from '../GroupCallLink';
interface ActionMessageTextOptions {
maxTextLength?: number;
asPlain?: boolean;
isEmbedded?: boolean;
interface RenderOptions {
asPlainText?: boolean;
asTextWithSpoilers?: boolean;
}
const MAX_LENGTH = 32;
const NBSP = '\u00A0';
export function renderActionMessageText(
@ -35,21 +35,22 @@ export function renderActionMessageText(
targetUsers?: ApiUser[],
targetMessage?: ApiMessage,
targetChatId?: string,
options: ActionMessageTextOptions = {},
options: RenderOptions = {},
) {
if (!message.content.action) {
return [];
}
const {
text, translationValues, amount, currency, call,
} = message.content.action;
const content: TextPart[] = [];
const textOptions: ActionMessageTextOptions = { ...options, maxTextLength: 32 };
const noLinks = options.asPlainText || options.asTextWithSpoilers;
const translationKey = text === 'Chat.Service.Group.UpdatedPinnedMessage1' && !targetMessage
? 'Message.PinnedGenericMessage'
: text;
let unprocessed = lang(translationKey, translationValues && translationValues.length ? translationValues : undefined);
let unprocessed = lang(translationKey, translationValues?.length ? translationValues : undefined);
let processed: TextPart[];
if (unprocessed.includes('%payment_amount%')) {
@ -66,10 +67,9 @@ export function renderActionMessageText(
unprocessed,
'%action_origin%',
actionOriginUser ? (
(!options.isEmbedded && renderUserContent(actionOriginUser, options.asPlain)) || NBSP
renderUserContent(actionOriginUser, noLinks) || NBSP
) : actionOriginChat ? (
(!options.isEmbedded && renderChatContent(lang, actionOriginChat, options.asPlain)) || NBSP
renderChatContent(lang, actionOriginChat, noLinks) || NBSP
) : 'User',
);
@ -80,7 +80,7 @@ export function renderActionMessageText(
unprocessed,
'%target_user%',
targetUsers
? targetUsers.map((user) => renderUserContent(user, options.asPlain)).filter<TextPart>(Boolean as any)
? targetUsers.map((user) => renderUserContent(user, noLinks)).filter<TextPart>(Boolean as any)
: 'User',
);
@ -91,7 +91,7 @@ export function renderActionMessageText(
unprocessed,
'%message%',
targetMessage
? renderMessageContent(lang, targetMessage, textOptions)
? renderMessageContent(lang, targetMessage, options)
: 'a message',
);
unprocessed = processed.pop() as string;
@ -111,12 +111,12 @@ export function renderActionMessageText(
unprocessed,
'%target_chat%',
targetChatId
? renderMigratedContent(targetChatId, options.asPlain)
? renderMigratedContent(targetChatId, noLinks)
: 'another chat',
);
content.push(...processed);
if (options.asPlain) {
if (options.asPlainText) {
return content.join('').trim();
}
@ -133,47 +133,23 @@ function renderProductContent(message: ApiMessage) {
: 'a product';
}
function renderMessageContent(lang: LangFn, message: ApiMessage, options: ActionMessageTextOptions = {}) {
const { maxTextLength, isEmbedded, asPlain } = options;
function renderMessageContent(lang: LangFn, message: ApiMessage, options: RenderOptions = {}) {
const { asPlainText, asTextWithSpoilers } = options;
const text = asPlain
? [trimText(getMessageSummaryText(lang, message), maxTextLength)]
: renderMessageSummary(lang, message, undefined, undefined, maxTextLength, true);
const {
photo, video, document, sticker,
} = getMessageContent(message);
const showQuotes = isEmbedded && text && !photo && !video && !document && !sticker;
let messageText = text;
if (isEmbedded) {
if (photo) {
messageText = ['a photo'];
} else if (video) {
messageText = [video.isGif ? 'a GIF' : 'a video'];
} else if (document) {
messageText = ['a document'];
} else if (sticker) {
messageText = text;
}
if (asPlainText) {
return getMessageSummaryText(lang, message, undefined, MAX_LENGTH);
}
if (asPlain && messageText) {
return (showQuotes ? ['«', ...messageText, '»'] : messageText).join('');
}
const messageSummary = renderMessageSummary(lang, message, undefined, undefined, MAX_LENGTH);
if (showQuotes) {
if (asTextWithSpoilers) {
return (
<span>
&laquo;
<MessageLink className="action-link" message={message}>{messageText}</MessageLink>
&raquo;
</span>
<span>{messageSummary}</span>
);
}
return (
<MessageLink className="action-link" message={message}>{messageText}</MessageLink>
<MessageLink className="action-link" message={message}>{messageSummary}</MessageLink>
);
}
@ -185,30 +161,30 @@ function renderGroupCallContent(groupCall: Partial<ApiGroupCall>, text: TextPart
);
}
function renderUserContent(sender: ApiUser, asPlain?: boolean): string | TextPart | undefined {
const text = trimText(getUserFullName(sender));
function renderUserContent(sender: ApiUser, noLinks?: boolean): string | TextPart | undefined {
const text = trimText(getUserFullName(sender), MAX_LENGTH);
if (asPlain) {
if (noLinks) {
return text;
}
return <UserLink className="action-link" sender={sender}>{sender && renderText(text!)}</UserLink>;
}
function renderChatContent(lang: LangFn, chat: ApiChat, asPlain?: boolean): string | TextPart | undefined {
const text = trimText(getChatTitle(lang, chat));
function renderChatContent(lang: LangFn, chat: ApiChat, noLinks?: boolean): string | TextPart | undefined {
const text = trimText(getChatTitle(lang, chat), MAX_LENGTH);
if (asPlain) {
if (noLinks) {
return text;
}
return <ChatLink className="action-link" chatId={chat.id}>{chat && renderText(text!)}</ChatLink>;
}
function renderMigratedContent(chatId: string, asPlain?: boolean): string | TextPart | undefined {
function renderMigratedContent(chatId: string, noLinks?: boolean): string | TextPart | undefined {
const text = 'another chat';
if (asPlain) {
if (noLinks) {
return text;
}

View File

@ -0,0 +1,71 @@
import { ApiMessage, ApiMessageEntityTypes } from '../../../api/types';
import {
getMessageSummaryDescription,
getMessageSummaryEmoji,
getMessageSummaryText,
getMessageText,
TRUNCATED_SUMMARY_LENGTH,
} from '../../../modules/helpers';
import { LangFn } from '../../../hooks/useLang';
import renderText from './renderText';
import { renderTextWithEntities, TextPart } from './renderTextWithEntities';
import trimText from '../../../util/trimText';
export type { TextPart };
export function renderMessageText(
message: ApiMessage,
highlight?: string,
shouldRenderHqEmoji?: boolean,
isSimple?: boolean,
truncateLength?: number,
) {
const { text, entities } = message.content.text || {};
if (!text) {
const contentNotSupportedText = getMessageText(message);
return contentNotSupportedText ? [trimText(contentNotSupportedText, truncateLength)] : undefined;
}
return renderTextWithEntities(
trimText(text, truncateLength),
entities,
highlight,
shouldRenderHqEmoji,
undefined,
message.id,
isSimple,
);
}
export function renderMessageSummary(
lang: LangFn,
message: ApiMessage,
noEmoji = false,
highlight?: string,
truncateLength = TRUNCATED_SUMMARY_LENGTH,
): TextPart[] {
let { entities } = message.content.text || {};
const hasSpoilers = entities?.some((e) => e.type === ApiMessageEntityTypes.Spoiler);
if (!hasSpoilers) {
const text = trimText(getMessageSummaryText(lang, message, noEmoji), truncateLength);
if (highlight) {
return renderText(text, ['emoji', 'highlight'], { highlight });
} else {
return renderText(text);
}
}
const emoji = !noEmoji && getMessageSummaryEmoji(message);
const emojiWithSpace = emoji ? `${emoji} ` : '';
const text = renderMessageText(message, highlight, undefined, true, truncateLength);
const description = getMessageSummaryDescription(lang, message, text);
return [
emojiWithSpace,
...(Array.isArray(description) ? description : [description]),
].filter<TextPart>(Boolean);
}

View File

@ -2,159 +2,23 @@ import { MouseEvent } from 'react';
import React from '../../../lib/teact/teact';
import { getDispatch } from '../../../lib/teact/teactn';
import { ApiMessageEntity, ApiMessageEntityTypes, ApiMessage } from '../../../api/types';
import {
getMessageSummaryText,
getMessageSummaryDescription,
getMessageSummaryEmoji,
getMessageText,
TRUNCATED_SUMMARY_LENGTH,
} from '../../../modules/helpers';
import { ApiFormattedText, ApiMessageEntity, ApiMessageEntityTypes } from '../../../api/types';
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 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<TextPart>(Boolean);
}
export function renderMessageText(
message: ApiMessage,
highlight?: string,
shouldRenderHqEmoji?: boolean,
isSimple?: boolean,
truncateLength?: number,
) {
const formattedText = message.content.text;
if (!formattedText || !formattedText.text) {
const rawText = getMessageText(message);
return rawText ? [rawText] : undefined;
}
const { text, entities } = formattedText;
return renderTextWithEntities(
truncateLength ? text.substr(0, truncateLength) : text,
entities,
highlight,
shouldRenderHqEmoji,
undefined,
message.id,
isSimple,
);
}
interface IOrganizedEntity {
entity: ApiMessageEntity;
organizedIndexes: Set<number>;
nestedEntities: IOrganizedEntity[];
}
function organizeEntity(
entity: ApiMessageEntity,
index: number,
entities: ApiMessageEntity[],
organizedEntityIndexes: Set<number>,
): IOrganizedEntity | undefined {
const { offset, length } = entity;
const organizedIndexes = new Set([index]);
if (organizedEntityIndexes.has(index)) {
return undefined;
}
// Determine any nested entities inside current entity
const nestedEntities: IOrganizedEntity[] = [];
const parsedNestedEntities = entities
.filter((e, i) => i > index && e.offset >= offset && e.offset < offset + length)
.map((e) => organizeEntity(e, entities.indexOf(e), entities, organizedEntityIndexes))
.filter<IOrganizedEntity>(Boolean as any);
parsedNestedEntities.forEach((parsedEntity) => {
let isChanged = false;
parsedEntity.organizedIndexes.forEach((organizedIndex) => {
if (!isChanged && !organizedIndexes.has(organizedIndex)) {
isChanged = true;
}
organizedIndexes.add(organizedIndex);
});
if (isChanged) {
nestedEntities.push(parsedEntity);
}
});
return {
entity,
organizedIndexes,
nestedEntities,
};
}
// Organize entities in a tree-like structure to better represent how the text will be displayed
function organizeEntities(entities: ApiMessageEntity[]) {
const organizedEntityIndexes: Set<number> = new Set();
const organizedEntities: IOrganizedEntity[] = [];
entities.forEach((entity, index) => {
if (organizedEntityIndexes.has(index)) {
return;
}
const organizedEntity = organizeEntity(entity, index, entities, organizedEntityIndexes);
if (organizedEntity) {
organizedEntity.organizedIndexes.forEach((organizedIndex) => {
organizedEntityIndexes.add(organizedIndex);
});
organizedEntities.push(organizedEntity);
}
});
return organizedEntities;
}
export function renderTextWithEntities(
text: string,
entities?: ApiMessageEntity[],
@ -283,6 +147,128 @@ export function renderTextWithEntities(
return result;
}
export function getTextWithEntitiesAsHtml(formattedText?: ApiFormattedText) {
const { text, entities } = formattedText || {};
if (!text) {
return '';
}
const result = renderTextWithEntities(
text,
entities,
undefined,
undefined,
true,
);
if (Array.isArray(result)) {
return result.join('');
}
return result;
}
function renderMessagePart(
content: TextPart | TextPart[],
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, isSimple));
});
return result;
}
if (shouldRenderAsHtml) {
return renderText(content, ['escape_html', 'emoji_html', 'br_html']);
}
const emojiFilter = shouldRenderHqEmoji ? 'hq_emoji' : 'emoji';
const filters: TextFilter[] = [emojiFilter];
if (!isSimple) {
filters.push('br');
}
if (highlight) {
return renderText(content, filters.concat('highlight'), { highlight });
} else {
return renderText(content, filters);
}
}
// Organize entities in a tree-like structure to better represent how the text will be displayed
function organizeEntities(entities: ApiMessageEntity[]) {
const organizedEntityIndexes: Set<number> = new Set();
const organizedEntities: IOrganizedEntity[] = [];
entities.forEach((entity, index) => {
if (organizedEntityIndexes.has(index)) {
return;
}
const organizedEntity = organizeEntity(entity, index, entities, organizedEntityIndexes);
if (organizedEntity) {
organizedEntity.organizedIndexes.forEach((organizedIndex) => {
organizedEntityIndexes.add(organizedIndex);
});
organizedEntities.push(organizedEntity);
}
});
return organizedEntities;
}
function organizeEntity(
entity: ApiMessageEntity,
index: number,
entities: ApiMessageEntity[],
organizedEntityIndexes: Set<number>,
): IOrganizedEntity | undefined {
const { offset, length } = entity;
const organizedIndexes = new Set([index]);
if (organizedEntityIndexes.has(index)) {
return undefined;
}
// Determine any nested entities inside current entity
const nestedEntities: IOrganizedEntity[] = [];
const parsedNestedEntities = entities
.filter((e, i) => i > index && e.offset >= offset && e.offset < offset + length)
.map((e) => organizeEntity(e, entities.indexOf(e), entities, organizedEntityIndexes))
.filter<IOrganizedEntity>(Boolean as any);
parsedNestedEntities.forEach((parsedEntity) => {
let isChanged = false;
parsedEntity.organizedIndexes.forEach((organizedIndex) => {
if (!isChanged && !organizedIndexes.has(organizedIndex)) {
isChanged = true;
}
organizedIndexes.add(organizedIndex);
});
if (isChanged) {
nestedEntities.push(parsedEntity);
}
});
return {
entity,
organizedIndexes,
nestedEntities,
};
}
function processEntity(
entity: ApiMessageEntity,
entityContent: TextPart,
@ -408,55 +394,6 @@ function processEntity(
}
}
function renderMessagePart(
content: TextPart | TextPart[],
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, isSimple));
});
return result;
}
if (shouldRenderAsHtml) {
return renderText(content, ['escape_html', 'emoji_html', 'br_html']);
}
const emojiFilter = shouldRenderHqEmoji ? 'hq_emoji' : 'emoji';
const filters: TextFilter[] = [emojiFilter];
if (!isSimple) {
filters.push('br');
}
if (highlight) {
return renderText(content, filters.concat('highlight'), { highlight });
} else {
return renderText(content, filters);
}
}
function getLinkUrl(entityContent: string, entity: ApiMessageEntity) {
const { type, url } = entity;
return type === ApiMessageEntityTypes.TextUrl && url ? url : entityContent;
}
function handleBotCommandClick(e: MouseEvent<HTMLAnchorElement>) {
getDispatch().sendBotCommand({ command: e.currentTarget.innerText });
}
function handleHashtagClick(e: MouseEvent<HTMLAnchorElement>) {
getDispatch().setLocalTextSearchQuery({ query: e.currentTarget.innerText });
getDispatch().searchTextMessagesLocal();
}
function processEntityAsHtml(
entity: ApiMessageEntity,
entityContent: TextPart,
@ -510,3 +447,17 @@ function processEntityAsHtml(
return renderedContent;
}
}
function getLinkUrl(entityContent: string, entity: ApiMessageEntity) {
const { type, url } = entity;
return type === ApiMessageEntityTypes.TextUrl && url ? url : entityContent;
}
function handleBotCommandClick(e: MouseEvent<HTMLAnchorElement>) {
getDispatch().sendBotCommand({ command: e.currentTarget.innerText });
}
function handleHashtagClick(e: MouseEvent<HTMLAnchorElement>) {
getDispatch().setLocalTextSearchQuery({ query: e.currentTarget.innerText });
getDispatch().searchTextMessagesLocal();
}

View File

@ -247,6 +247,7 @@ const Chat: FC<OwnProps & StateProps> = ({
actionTargetUsers,
actionTargetMessage,
actionTargetChatId,
{ asTextWithSpoilers: true },
)}
</p>
);

View File

@ -1,10 +1,10 @@
import React, { FC, useEffect, useState } from '../../lib/teact/teact';
import { throttle } from '../../util/schedulers';
import { TextPart } from '../common/helpers/renderMessageText';
import buildClassName from '../../util/buildClassName';
import { REM } from '../common/helpers/mediaDimensions';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
import { throttle } from '../../util/schedulers';
import buildClassName from '../../util/buildClassName';
import { TextPart } from '../common/helpers/renderMessageText';
import { REM } from '../common/helpers/mediaDimensions';
import './MediaViewerFooter.scss';

View File

@ -98,7 +98,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
targetUsers,
targetMessage,
targetChatId,
isEmbedded ? { isEmbedded: true } : undefined,
{ asTextWithSpoilers: isEmbedded },
);
const {
isContextMenuOpen, contextMenuPosition,

View File

@ -1,23 +0,0 @@
import { ApiFormattedText } from '../../../../api/types';
import { renderTextWithEntities } from '../../../common/helpers/renderMessageText';
export default function getMessageTextAsHtml(formattedText?: ApiFormattedText) {
const { text, entities } = formattedText || {};
if (!text) {
return '';
}
const result = renderTextWithEntities(
text,
entities,
undefined,
undefined,
true,
);
if (Array.isArray(result)) {
return result.join('');
}
return result;
}

View File

@ -8,10 +8,10 @@ import usePrevious from '../../../../hooks/usePrevious';
import { debounce } from '../../../../util/schedulers';
import focusEditableElement from '../../../../util/focusEditableElement';
import parseMessageInput from '../../../../util/parseMessageInput';
import getMessageTextAsHtml from '../helpers/getMessageTextAsHtml';
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
import useBeforeUnload from '../../../../hooks/useBeforeUnload';
import { IS_TOUCH_ENV } from '../../../../util/environment';
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
// Used to avoid running debounced callbacks when chat changes.
let currentChatId: string | undefined;
@ -65,7 +65,7 @@ const useDraft = (
return;
}
setHtml(getMessageTextAsHtml(draft));
setHtml(getTextWithEntitiesAsHtml(draft));
if (!IS_TOUCH_ENV) {
requestAnimationFrame(() => {

View File

@ -5,9 +5,9 @@ import { ApiMessage } from '../../../../api/types';
import { EDITABLE_INPUT_ID } from '../../../../config';
import parseMessageInput from '../../../../util/parseMessageInput';
import getMessageTextAsHtml from '../helpers/getMessageTextAsHtml';
import focusEditableElement from '../../../../util/focusEditableElement';
import { hasMessageMedia } from '../../../../modules/helpers';
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
const useEditing = (
htmlRef: { current: string },
@ -26,7 +26,7 @@ const useEditing = (
return;
}
setHtml(getMessageTextAsHtml(editedMessage.content.text));
setHtml(getTextWithEntitiesAsHtml(editedMessage.content.text));
requestAnimationFrame(() => {
const messageInput = document.getElementById(EDITABLE_INPUT_ID)!;

View File

@ -14,7 +14,7 @@ import {
} from '../../../api/types';
import renderText from '../../common/helpers/renderText';
import { renderTextWithEntities } from '../../common/helpers/renderMessageText';
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
import { formatMediaDuration } from '../../../util/dateFormat';
import useLang, { LangFn } from '../../../hooks/useLang';

View File

@ -6,7 +6,7 @@ import { getDispatch, withGlobal } from '../../../lib/teact/teactn';
import { ApiChat, ApiSponsoredMessage, ApiUser } from '../../../api/types';
import { renderTextWithEntities } from '../../common/helpers/renderMessageText';
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
import { selectChat, selectSponsoredMessage, selectUser } from '../../../modules/selectors';
import { getChatTitle, getUserFullName } from '../../../modules/helpers';
import renderText from '../../common/helpers/renderText';

View File

@ -8,9 +8,9 @@ import React, {
import { ANIMATION_END_DELAY } from '../../config';
import useShowTransition from '../../hooks/useShowTransition';
import { TextPart } from '../common/helpers/renderMessageText';
import buildClassName from '../../util/buildClassName';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import { TextPart } from '../common/helpers/renderMessageText';
import Portal from './Portal';

View File

@ -1,8 +1,10 @@
import type { TextPart } from '../../components/common/helpers/renderTextWithEntities';
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';
import trimText from '../../util/trimText';
const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
export const TRUNCATED_SUMMARY_LENGTH = 80;
@ -15,7 +17,7 @@ export function getMessageSummaryText(
) {
const emoji = !noEmoji && getMessageSummaryEmoji(message);
const emojiWithSpace = emoji ? `${emoji} ` : '';
const text = getMessageTextWithSpoilers(message)?.substr(0, truncateLength);
const text = trimText(getMessageTextWithSpoilers(message), truncateLength);
const description = getMessageSummaryDescription(lang, message, text);
return `${emojiWithSpace}${description}`;
@ -39,6 +41,7 @@ export function getMessageTextWithSpoilers(message: ApiMessage) {
const spoiler = generateBrailleSpoiler(length);
return `${accText.substr(0, offset)}${spoiler}${accText.substr(offset + length, accText.length)}`;
}, text);
}

View File

@ -287,7 +287,7 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage) {
actionTargetUsers,
actionTargetMessage,
actionTargetChatId,
{ asPlain: true },
{ asPlainText: true },
) as string;
} else {
const senderName = getMessageSenderName(getTranslation, chat.id, messageSender);

View File

@ -1,9 +1,7 @@
const DEFAULT_MAX_TEXT_LENGTH = 30;
export default function trimText(text: string | undefined, length = DEFAULT_MAX_TEXT_LENGTH) {
if (!text || text.length <= length) {
export default function trimText<T extends string | undefined>(text: T, length?: number) {
if (!text || !length || text.length <= length) {
return text;
}
return `${text.substr(0, length)}...`;
return `${text.substring(0, length)}...`;
}