Message: Allow quote message (#4067)
This commit is contained in:
parent
df84448372
commit
89df119add
@ -886,6 +886,7 @@ function buildReplyInfo(inputInfo: ApiInputReplyInfo, isForum?: boolean): ApiRep
|
||||
replyToPeerId: inputInfo.replyToPeerId,
|
||||
quoteText: inputInfo.quoteText,
|
||||
isForumTopic: isForum && inputInfo.replyToTopId ? true : undefined,
|
||||
...(Boolean(inputInfo.quoteText) && { isQuote: true }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
1
src/assets/font-icons/quote-text.svg
Normal file
1
src/assets/font-icons/quote-text.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" fill-rule="evenodd" d="M20 2.667a2.667 2.667 0 0 0-.306 5.316c-.141.498-.329.984-.562 1.45l-.32.64a1.328 1.328 0 0 0 2.376 1.188l.32-.64a10.928 10.928 0 0 0 1.153-4.887v-.23A2.667 2.667 0 0 0 20 2.666Zm6.667 0a2.667 2.667 0 0 0-.307 5.316c-.14.498-.328.984-.561 1.45l-.32.64a1.328 1.328 0 0 0 2.376 1.188l.32-.64a10.928 10.928 0 0 0 1.153-4.887v-.23a2.667 2.667 0 0 0-2.661-2.837ZM5.333 6.672a1.328 1.328 0 1 0 0 2.656H14a1.328 1.328 0 1 0 0-2.656H5.333Zm0 8a1.328 1.328 0 1 0 0 2.656h21.334a1.328 1.328 0 0 0 0-2.656H5.333ZM4.005 24c0-.733.595-1.328 1.328-1.328h21.334a1.328 1.328 0 1 1 0 2.656H5.333A1.328 1.328 0 0 1 4.005 24Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 752 B |
@ -85,7 +85,7 @@ import { formatMediaDuration, formatVoiceRecordDuration } from '../../util/dateF
|
||||
import deleteLastCharacterOutsideSelection from '../../util/deleteLastCharacterOutsideSelection';
|
||||
import focusEditableElement from '../../util/focusEditableElement';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
import parseMessageInput from '../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../util/parseHtmlAsFormattedText';
|
||||
import { insertHtmlInSelection } from '../../util/selection';
|
||||
import { getServerTime } from '../../util/serverTime';
|
||||
import { IS_IOS, IS_VOICE_RECORDING_SUPPORTED } from '../../util/windowEnvironment';
|
||||
@ -869,7 +869,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const { text, entities } = parseMessageInput(getHtml());
|
||||
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
||||
if (!text && !attachmentsToSend.length) {
|
||||
return;
|
||||
}
|
||||
@ -931,7 +931,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const { text, entities } = parseMessageInput(getHtml());
|
||||
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
||||
|
||||
if (currentAttachments.length) {
|
||||
sendAttachments({
|
||||
@ -1398,7 +1398,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
showCustomEmojiPremiumNotification();
|
||||
return;
|
||||
}
|
||||
const customEmojiMessage = parseMessageInput(buildCustomEmojiHtml(sticker));
|
||||
const customEmojiMessage = parseHtmlAsFormattedText(buildCustomEmojiHtml(sticker));
|
||||
text = customEmojiMessage.text;
|
||||
entities = customEmojiMessage.entities;
|
||||
}
|
||||
|
||||
@ -127,7 +127,14 @@ const CustomEmoji: FC<OwnProps> = ({
|
||||
data-alt={customEmoji?.emoji}
|
||||
style={style}
|
||||
>
|
||||
<img className={styles.highlightCatch} src={blankImg} alt={customEmoji?.emoji} draggable={false} />
|
||||
<img
|
||||
className={styles.highlightCatch}
|
||||
src={blankImg}
|
||||
alt={customEmoji?.emoji}
|
||||
data-entity-type={ApiMessageEntityTypes.CustomEmoji}
|
||||
data-document-id={documentId}
|
||||
draggable={false}
|
||||
/>
|
||||
{!customEmoji ? (
|
||||
<img className={styles.thumb} src={svgPlaceholder} alt="Emoji" draggable={false} />
|
||||
) : (
|
||||
|
||||
@ -51,6 +51,7 @@ type OwnProps = {
|
||||
customText?: string;
|
||||
noUserColors?: boolean;
|
||||
isProtected?: boolean;
|
||||
isInComposer?: boolean;
|
||||
chatTranslations?: ChatTranslatedMessages;
|
||||
requestedChatTranslationLanguage?: string;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
@ -71,6 +72,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
title,
|
||||
customText,
|
||||
isProtected,
|
||||
isInComposer,
|
||||
noUserColors,
|
||||
chatTranslations,
|
||||
requestedChatTranslationLanguage,
|
||||
@ -118,6 +120,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
return renderTextWithEntities({
|
||||
text: replyInfo.quoteText.text,
|
||||
entities: replyInfo.quoteText.entities,
|
||||
noLineBreaks: isInComposer,
|
||||
});
|
||||
}
|
||||
|
||||
@ -173,7 +176,11 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isChatSender && <span className="embedded-sender">{renderText(senderTitle)}</span>}
|
||||
{!isChatSender && (
|
||||
<span className="embedded-sender">
|
||||
{renderText(isInComposer ? lang('ReplyToQuote', senderTitle) : senderTitle)}
|
||||
</span>
|
||||
)}
|
||||
{icon && <Icon name={icon} className="embedded-chat-icon" />}
|
||||
{icon && senderChatTitle && renderText(senderChatTitle)}
|
||||
</>
|
||||
|
||||
@ -37,6 +37,7 @@ export function renderTextWithEntities({
|
||||
containerId,
|
||||
isSimple,
|
||||
isProtected,
|
||||
noLineBreaks,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
withTranslucentThumbs,
|
||||
@ -54,6 +55,7 @@ export function renderTextWithEntities({
|
||||
containerId?: string;
|
||||
isSimple?: boolean;
|
||||
isProtected?: boolean;
|
||||
noLineBreaks?: boolean;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
withTranslucentThumbs?: boolean;
|
||||
@ -64,7 +66,15 @@ export function renderTextWithEntities({
|
||||
focusedQuote?: string;
|
||||
}) {
|
||||
if (!entities?.length) {
|
||||
return renderMessagePart(text, highlight, focusedQuote, emojiSize, shouldRenderAsHtml, isSimple);
|
||||
return renderMessagePart({
|
||||
content: text,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
emojiSize,
|
||||
shouldRenderAsHtml,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
});
|
||||
}
|
||||
|
||||
const result: TextPart[] = [];
|
||||
@ -92,9 +102,15 @@ export function renderTextWithEntities({
|
||||
deleteLineBreakAfterPre = false;
|
||||
}
|
||||
if (textBefore) {
|
||||
renderResult.push(...renderMessagePart(
|
||||
textBefore, highlight, focusedQuote, emojiSize, shouldRenderAsHtml, isSimple,
|
||||
) as TextPart[]);
|
||||
renderResult.push(...renderMessagePart({
|
||||
content: textBefore,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
emojiSize,
|
||||
shouldRenderAsHtml,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
}) as TextPart[]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,8 +157,10 @@ export function renderTextWithEntities({
|
||||
entityContent,
|
||||
nestedEntityContent,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
containerId,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
isProtected,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
@ -168,9 +186,15 @@ export function renderTextWithEntities({
|
||||
textAfter = textAfter.substring(1);
|
||||
}
|
||||
if (textAfter) {
|
||||
renderResult.push(...renderMessagePart(
|
||||
textAfter, highlight, focusedQuote, emojiSize, shouldRenderAsHtml, isSimple,
|
||||
) as TextPart[]);
|
||||
renderResult.push(...renderMessagePart({
|
||||
content: textAfter,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
emojiSize,
|
||||
shouldRenderAsHtml,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
}) as TextPart[]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,19 +241,36 @@ export function getTextWithEntitiesAsHtml(formattedText?: ApiFormattedText) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function renderMessagePart(
|
||||
content: TextPart | TextPart[],
|
||||
highlight?: string,
|
||||
focusedQuote?: string,
|
||||
emojiSize?: number,
|
||||
shouldRenderAsHtml?: boolean,
|
||||
isSimple?: boolean,
|
||||
) {
|
||||
function renderMessagePart({
|
||||
content,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
emojiSize,
|
||||
shouldRenderAsHtml,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
} : {
|
||||
content: TextPart | TextPart[];
|
||||
highlight?: string;
|
||||
focusedQuote?: string;
|
||||
emojiSize?: number;
|
||||
shouldRenderAsHtml?: boolean;
|
||||
isSimple?: boolean;
|
||||
noLineBreaks?: boolean;
|
||||
}) {
|
||||
if (Array.isArray(content)) {
|
||||
const result: TextPart[] = [];
|
||||
|
||||
content.forEach((c) => {
|
||||
result.push(...renderMessagePart(c, highlight, focusedQuote, emojiSize, shouldRenderAsHtml, isSimple));
|
||||
result.push(...renderMessagePart({
|
||||
content: c,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
emojiSize,
|
||||
shouldRenderAsHtml,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
}));
|
||||
});
|
||||
|
||||
return result;
|
||||
@ -243,7 +284,7 @@ function renderMessagePart(
|
||||
|
||||
const filters: TextFilter[] = [emojiFilter];
|
||||
const params: RenderTextParams = {};
|
||||
if (!isSimple) {
|
||||
if (!isSimple && !noLineBreaks) {
|
||||
filters.push('br');
|
||||
}
|
||||
|
||||
@ -330,8 +371,10 @@ function processEntity({
|
||||
entityContent,
|
||||
nestedEntityContent,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
containerId,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
isProtected,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
@ -346,8 +389,10 @@ function processEntity({
|
||||
entityContent: TextPart;
|
||||
nestedEntityContent: TextPart[];
|
||||
highlight?: string;
|
||||
focusedQuote?: string;
|
||||
containerId?: string;
|
||||
isSimple?: boolean;
|
||||
noLineBreaks?: boolean;
|
||||
isProtected?: boolean;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
@ -362,9 +407,14 @@ function processEntity({
|
||||
const renderedContent = nestedEntityContent.length ? nestedEntityContent : entityContent;
|
||||
|
||||
function renderNestedMessagePart() {
|
||||
return renderMessagePart(
|
||||
renderedContent, highlight, undefined, emojiSize, undefined, isSimple,
|
||||
);
|
||||
return renderMessagePart({
|
||||
content: renderedContent,
|
||||
highlight,
|
||||
focusedQuote,
|
||||
emojiSize,
|
||||
isSimple,
|
||||
noLineBreaks,
|
||||
});
|
||||
}
|
||||
|
||||
if (!entityText) {
|
||||
@ -402,11 +452,11 @@ function processEntity({
|
||||
return <strong data-entity-type={entity.type}>{renderNestedMessagePart()}</strong>;
|
||||
case ApiMessageEntityTypes.Blockquote:
|
||||
return (
|
||||
<div className="text-entity-blockquote-wrapper">
|
||||
<span className="text-entity-blockquote-wrapper">
|
||||
<blockquote data-entity-type={entity.type}>
|
||||
{renderNestedMessagePart()}
|
||||
</blockquote>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
case ApiMessageEntityTypes.BotCommand:
|
||||
return (
|
||||
@ -585,10 +635,10 @@ function processEntityAsHtml(
|
||||
case ApiMessageEntityTypes.CustomEmoji:
|
||||
return buildCustomEmojiHtmlFromEntity(rawEntityText, entity);
|
||||
case ApiMessageEntityTypes.Blockquote:
|
||||
return `<span
|
||||
return `<blockquote
|
||||
class="blockquote"
|
||||
data-entity-type="${ApiMessageEntityTypes.Blockquote}"
|
||||
>${renderedContent}</span>`;
|
||||
>${renderedContent}</blockquote>`;
|
||||
default:
|
||||
return renderedContent;
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
}
|
||||
|
||||
& .embedded-left-icon {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
background: none !important;
|
||||
height: 2.625rem;
|
||||
@ -63,4 +64,15 @@
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.quote-reply {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
font-size: 0.5rem;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
right: 0.625rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
|
||||
import EmbeddedMessage from '../../common/embedded/EmbeddedMessage';
|
||||
import Icon from '../../common/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
@ -173,13 +174,13 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const leftIcon = useMemo(() => {
|
||||
if (isShowingReply) {
|
||||
return 'icon-reply';
|
||||
return 'reply';
|
||||
}
|
||||
if (editingId) {
|
||||
return 'icon-edit';
|
||||
return 'edit';
|
||||
}
|
||||
if (isForwarding) {
|
||||
return 'icon-forward';
|
||||
return 'forward';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@ -210,11 +211,15 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||
<div className={className} ref={ref} onContextMenu={handleContextMenu} onClick={handleContextMenu}>
|
||||
<div className={innerClassName}>
|
||||
<div className="embedded-left-icon">
|
||||
<i className={buildClassName('icon', leftIcon)} />
|
||||
{leftIcon && <Icon name={leftIcon} />}
|
||||
{Boolean(replyInfo?.quoteText) && (
|
||||
<Icon name="quote" className="quote-reply" />
|
||||
)}
|
||||
</div>
|
||||
<EmbeddedMessage
|
||||
className="inside-input"
|
||||
replyInfo={replyInfo}
|
||||
isInComposer
|
||||
message={strippedMessage}
|
||||
sender={!noAuthors ? sender : undefined}
|
||||
customText={customText}
|
||||
@ -339,7 +344,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
sender = selectForwardedSender(global, message);
|
||||
}
|
||||
|
||||
if (!sender && !forwardInfo?.hiddenUserName) {
|
||||
if (!sender && (!forwardInfo?.hiddenUserName || Boolean(replyInfo.quoteText))) {
|
||||
sender = selectSender(global, message);
|
||||
}
|
||||
} else if (isForwarding) {
|
||||
@ -352,8 +357,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
if (!sender) {
|
||||
sender = selectPeer(global, fromChatId!);
|
||||
}
|
||||
} else if (editingId) {
|
||||
sender = selectSender(global, message!);
|
||||
} else if (editingId && message) {
|
||||
sender = selectSender(global, message);
|
||||
}
|
||||
|
||||
const forwardsHaveCaptions = forwardedMessages?.some((forward) => (
|
||||
|
||||
@ -8,7 +8,7 @@ import type { ApiNewPoll } from '../../../api/types';
|
||||
|
||||
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
import parseMessageInput from '../../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../../util/parseHtmlAsFormattedText';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
@ -142,7 +142,8 @@ const PollModal: FC<OwnProps> = ({
|
||||
};
|
||||
|
||||
if (isQuizMode) {
|
||||
const { text, entities } = (solution && parseMessageInput(solution.substring(0, MAX_SOLUTION_LENGTH))) || {};
|
||||
const { text, entities } = (solution && parseHtmlAsFormattedText(solution.substring(0, MAX_SOLUTION_LENGTH)))
|
||||
|| {};
|
||||
|
||||
payload.quiz = {
|
||||
correctAnswers: [String(correctOption)],
|
||||
|
||||
@ -12,7 +12,7 @@ import { ApiMessageEntityTypes } from '../../../api/types';
|
||||
import { RE_LINK_TEMPLATE } from '../../../config';
|
||||
import { selectNoWebPage, selectTabState, selectTheme } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import parseMessageInput from '../../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../../util/parseHtmlAsFormattedText';
|
||||
|
||||
import { useDebouncedResolver } from '../../../hooks/useAsyncResolvers';
|
||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||
@ -61,7 +61,7 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
|
||||
const formattedTextWithLinkRef = useRef<ApiFormattedText>();
|
||||
|
||||
const detectLinkDebounced = useDebouncedResolver(() => {
|
||||
const formattedText = parseMessageInput(getHtml());
|
||||
const formattedText = parseHtmlAsFormattedText(getHtml());
|
||||
const linkEntity = formattedText.entities?.find((entity): entity is ApiMessageEntityTextUrl => (
|
||||
entity.type === ApiMessageEntityTypes.TextUrl
|
||||
));
|
||||
|
||||
@ -2,7 +2,7 @@ import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||
|
||||
import { DEBUG } from '../../../../config';
|
||||
import cleanDocsHtml from '../../../../lib/cleanDocsHtml';
|
||||
import { ENTITY_CLASS_BY_NODE_NAME } from '../../../../util/parseMessageInput';
|
||||
import { ENTITY_CLASS_BY_NODE_NAME } from '../../../../util/parseHtmlAsFormattedText';
|
||||
|
||||
const STYLE_TAG_REGEX = /<style>(.*?)<\/style>/gs;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { fixImageContent } from '../../../../util/parseMessageInput';
|
||||
import { fixImageContent } from '../../../../util/parseHtmlAsFormattedText';
|
||||
|
||||
const div = document.createElement('div');
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
EDITABLE_INPUT_ID, EDITABLE_INPUT_MODAL_ID, EDITABLE_STORY_INPUT_ID,
|
||||
} from '../../../../config';
|
||||
import { containsCustomEmoji, stripCustomEmoji } from '../../../../global/helpers/symbols';
|
||||
import parseMessageInput from '../../../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText';
|
||||
import buildAttachment from '../helpers/buildAttachment';
|
||||
import { preparePastedHtml } from '../helpers/cleanHtml';
|
||||
import getFilesFromDataTransferItems from '../helpers/getFilesFromDataTransferItems';
|
||||
@ -45,7 +45,7 @@ const useClipboardPaste = (
|
||||
const pastedText = e.clipboardData.getData('text').substring(0, MAX_MESSAGE_LENGTH);
|
||||
const html = e.clipboardData.getData('text/html');
|
||||
|
||||
let pastedFormattedText = html ? parseMessageInput(
|
||||
let pastedFormattedText = html ? parseHtmlAsFormattedText(
|
||||
preparePastedHtml(html), undefined, true,
|
||||
) : undefined;
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
requestMeasure, requestNextMutation,
|
||||
} from '../../../../lib/fasterdom/fasterdom';
|
||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||
import parseMessageInput from '../../../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText';
|
||||
import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment';
|
||||
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
|
||||
|
||||
@ -77,7 +77,7 @@ const useDraft = ({
|
||||
saveDraft({
|
||||
chatId: prevState.chatId ?? chatId,
|
||||
threadId: prevState.threadId ?? threadId,
|
||||
text: parseMessageInput(html),
|
||||
text: parseHtmlAsFormattedText(html),
|
||||
});
|
||||
} else {
|
||||
clearDraft({
|
||||
|
||||
@ -10,7 +10,7 @@ import { EDITABLE_INPUT_CSS_SELECTOR } from '../../../../config';
|
||||
import { requestMeasure, requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
||||
import { hasMessageMedia } from '../../../../global/helpers';
|
||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||
import parseMessageInput from '../../../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText';
|
||||
import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import { useDebouncedResolver } from '../../../../hooks/useAsyncResolvers';
|
||||
@ -87,7 +87,7 @@ const useEditing = (
|
||||
useEffect(() => {
|
||||
if (!editedMessage) return undefined;
|
||||
return () => {
|
||||
const edited = parseMessageInput(getHtml());
|
||||
const edited = parseHtmlAsFormattedText(getHtml());
|
||||
const update = edited.text.length ? edited : undefined;
|
||||
|
||||
setEditingDraft({
|
||||
@ -99,7 +99,7 @@ const useEditing = (
|
||||
const detectLinkDebounced = useDebouncedResolver(() => {
|
||||
if (!editedMessage) return false;
|
||||
|
||||
const edited = parseMessageInput(getHtml());
|
||||
const edited = parseHtmlAsFormattedText(getHtml());
|
||||
return !('webPage' in editedMessage.content)
|
||||
&& editedMessage.content.text?.entities?.some((entity) => URL_ENTITIES.has(entity.type))
|
||||
&& !(edited.entities?.some((entity) => URL_ENTITIES.has(entity.type)));
|
||||
@ -144,7 +144,7 @@ const useEditing = (
|
||||
});
|
||||
|
||||
const handleEditComplete = useLastCallback(() => {
|
||||
const { text, entities } = parseMessageInput(getHtml());
|
||||
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
||||
|
||||
if (!editedMessage) {
|
||||
return;
|
||||
@ -167,7 +167,7 @@ const useEditing = (
|
||||
|
||||
const handleBlur = useLastCallback(() => {
|
||||
if (!editedMessage) return;
|
||||
const edited = parseMessageInput(getHtml());
|
||||
const edited = parseHtmlAsFormattedText(getHtml());
|
||||
const update = edited.text.length ? edited : undefined;
|
||||
|
||||
setEditingDraft({
|
||||
|
||||
@ -43,6 +43,8 @@ import {
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||
import { getSelectionAsFormattedText } from './helpers/getSelectionAsFormattedText';
|
||||
import { isSelectionRangeInsideMessage } from './helpers/isSelectionRangeInsideMessage';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
@ -93,6 +95,7 @@ type StateProps = {
|
||||
canCopy?: boolean;
|
||||
canTranslate?: boolean;
|
||||
canShowOriginal?: boolean;
|
||||
isMessageTranslated?: boolean;
|
||||
canSelectLanguage?: boolean;
|
||||
isPrivate?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
@ -113,6 +116,8 @@ type StateProps = {
|
||||
messageLink?: string;
|
||||
};
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
||||
const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
availableReactions,
|
||||
topReactions,
|
||||
@ -158,6 +163,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
canShowSeenBy,
|
||||
canScheduleUntilOnline,
|
||||
canTranslate,
|
||||
isMessageTranslated,
|
||||
canShowOriginal,
|
||||
canSelectLanguage,
|
||||
isReactionPickerOpen,
|
||||
@ -202,7 +208,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
|
||||
const [isPinModalOpen, setIsPinModalOpen] = useState(false);
|
||||
const [isClosePollDialogOpen, openClosePollDialog, closeClosePollDialog] = useFlag();
|
||||
|
||||
const [canQuoteSelection, setCanQuoteSelection] = useState(false);
|
||||
const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, onClose, message.date);
|
||||
|
||||
// `undefined` indicates that emoji are present and loading
|
||||
@ -265,6 +271,35 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
return activeDownloads?.[message.isScheduled ? 'scheduledIds' : 'ids']?.includes(message.id);
|
||||
}, [activeDownloads, album, message]);
|
||||
|
||||
const selectionRange = canReply && selection?.rangeCount ? selection.getRangeAt(0) : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (isMessageTranslated) {
|
||||
setCanQuoteSelection(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const isMessageTextSelected = selectionRange
|
||||
&& !selectionRange.collapsed
|
||||
&& Boolean(message.content.text?.text)
|
||||
&& isSelectionRangeInsideMessage(selectionRange);
|
||||
|
||||
if (!isMessageTextSelected) {
|
||||
setCanQuoteSelection(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectionText = getSelectionAsFormattedText(selectionRange);
|
||||
|
||||
setCanQuoteSelection(
|
||||
selectionText.text.trim().length > 0
|
||||
&& message.content.text!.text!.includes(selectionText.text),
|
||||
);
|
||||
}, [
|
||||
selectionRange, selectionRange?.collapsed, selectionRange?.startOffset, selectionRange?.endOffset,
|
||||
isMessageTranslated, message.content.text,
|
||||
]);
|
||||
|
||||
const handleDelete = useLastCallback(() => {
|
||||
setIsMenuOpen(false);
|
||||
setIsDeleteModalOpen(true);
|
||||
@ -296,7 +331,10 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleReply = useLastCallback(() => {
|
||||
updateDraftReplyInfo({ replyToMsgId: message.id });
|
||||
updateDraftReplyInfo({
|
||||
replyToMsgId: message.id,
|
||||
quoteText: canQuoteSelection && selectionRange ? getSelectionAsFormattedText(selectionRange) : undefined,
|
||||
});
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
@ -493,6 +531,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
canSendNow={canSendNow}
|
||||
canReschedule={canReschedule}
|
||||
canReply={canReply}
|
||||
canQuote={canQuoteSelection}
|
||||
canDelete={canDelete}
|
||||
canReport={canReport}
|
||||
canPin={canPin}
|
||||
@ -679,6 +718,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canTranslate,
|
||||
canShowOriginal: hasTranslation && !isChatTranslated,
|
||||
canSelectLanguage: hasTranslation && !isChatTranslated,
|
||||
isMessageTranslated: hasTranslation,
|
||||
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
||||
isReactionPickerOpen: selectIsReactionPickerOpen(global),
|
||||
messageLink,
|
||||
|
||||
@ -95,6 +95,7 @@ import {
|
||||
import { isAnimatingScroll } from '../../../util/animateScroll';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { isElementInViewport } from '../../../util/isElementInViewport';
|
||||
import stopEvent from '../../../util/stopEvent';
|
||||
import { IS_ANDROID, IS_ELECTRON, IS_TRANSLATION_SUPPORTED } from '../../../util/windowEnvironment';
|
||||
import {
|
||||
calculateDimensionsForMessageMedia,
|
||||
@ -522,6 +523,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
||||
const messageColorPeer = originSender || sender;
|
||||
const senderPeer = (forwardInfo || message.content.storyData) ? originSender : messageSender;
|
||||
const hasText = hasMessageText(message);
|
||||
|
||||
const {
|
||||
handleMouseDown,
|
||||
@ -605,7 +607,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
const containerClassName = buildClassName(
|
||||
'Message message-list-item',
|
||||
isFirstInGroup && 'first-in-group',
|
||||
isProtected ? 'is-protected' : 'allow-selection',
|
||||
isProtected && !hasText ? 'is-protected' : 'allow-selection',
|
||||
isLastInGroup && 'last-in-group',
|
||||
isFirstInDocumentGroup && 'first-in-document-group',
|
||||
isLastInDocumentGroup && 'last-in-document-group',
|
||||
@ -686,7 +688,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const withAppendix = contentClassName.includes('has-appendix');
|
||||
const hasText = hasMessageText(message);
|
||||
const emojiSize = getCustomEmojiSize(message.emojiOnlyCount);
|
||||
|
||||
let metaPosition!: MetaPosition;
|
||||
@ -1319,6 +1320,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
id={getMessageHtmlId(message.id)}
|
||||
className={containerClassName}
|
||||
data-message-id={messageId}
|
||||
onCopy={isProtected ? stopEvent : undefined}
|
||||
onMouseDown={handleMouseDown}
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
|
||||
@ -53,6 +53,7 @@ type OwnProps = {
|
||||
maxUniqueReactions?: number;
|
||||
canReschedule?: boolean;
|
||||
canReply?: boolean;
|
||||
canQuote?: boolean;
|
||||
repliesThreadInfo?: ApiThreadInfo;
|
||||
canPin?: boolean;
|
||||
canUnpin?: boolean;
|
||||
@ -140,6 +141,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
canReschedule,
|
||||
canBuyPremium,
|
||||
canReply,
|
||||
canQuote,
|
||||
canEdit,
|
||||
noReplies,
|
||||
canPin,
|
||||
@ -361,7 +363,11 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
{canReschedule && (
|
||||
<MenuItem icon="schedule" onClick={onReschedule}>{lang('MessageScheduleEditTime')}</MenuItem>
|
||||
)}
|
||||
{canReply && <MenuItem icon="reply" onClick={onReply}>{lang('Reply')}</MenuItem>}
|
||||
{canReply && (
|
||||
<MenuItem icon="reply" onClick={onReply}>
|
||||
{lang(canQuote ? 'lng_context_quote_and_reply' : 'Reply')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!noReplies && Boolean(repliesThreadInfo?.messagesCount) && (
|
||||
<MenuItem icon="replies" onClick={onOpenThread}>
|
||||
{lang('Conversation.ContextViewReplies', repliesThreadInfo!.messagesCount, 'i')}
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
selectPeerStory, selectTabState,
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import parseMessageInput from '../../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../../util/parseHtmlAsFormattedText';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
import { buildCustomEmojiHtml } from '../composer/helpers/customEmoji';
|
||||
|
||||
@ -162,7 +162,7 @@ const ReactionPicker: FC<OwnProps & StateProps> = ({
|
||||
if ('emoticon' in item) {
|
||||
text = item.emoticon;
|
||||
} else {
|
||||
const customEmojiMessage = parseMessageInput(buildCustomEmojiHtml(sticker!));
|
||||
const customEmojiMessage = parseHtmlAsFormattedText(buildCustomEmojiHtml(sticker!));
|
||||
text = customEmojiMessage.text;
|
||||
entities = customEmojiMessage.entities;
|
||||
}
|
||||
|
||||
@ -953,6 +953,12 @@
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// Remove extra bottom padding from `blockquote`
|
||||
.text-entity-blockquote-wrapper {
|
||||
display: inline-block;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
blockquote, .blockquote {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import type { ApiFormattedText } from '../../../../api/types';
|
||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||
|
||||
import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText';
|
||||
|
||||
const div = document.createElement('div');
|
||||
const ALLOWED_QUOTE_ENTITIES = new Set([
|
||||
ApiMessageEntityTypes.Bold,
|
||||
ApiMessageEntityTypes.Italic,
|
||||
ApiMessageEntityTypes.Underline,
|
||||
ApiMessageEntityTypes.Strike,
|
||||
ApiMessageEntityTypes.Spoiler,
|
||||
ApiMessageEntityTypes.CustomEmoji,
|
||||
]);
|
||||
|
||||
export function getSelectionAsFormattedText(range: Range) {
|
||||
const html = getSelectionAsHtml(range);
|
||||
const formattedText = parseHtmlAsFormattedText(html, false, true);
|
||||
|
||||
return stripEntitiesForQuote(formattedText);
|
||||
}
|
||||
|
||||
function getSelectionAsHtml(range: Range) {
|
||||
const clonedSelection = range.cloneContents();
|
||||
div.appendChild(clonedSelection);
|
||||
|
||||
const html = wrapHtmlWithMarkupTags(range, div.innerHTML);
|
||||
div.innerHTML = '';
|
||||
|
||||
return html
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/ /gi, ' ') // Convert nbsp's to spaces
|
||||
.replace(/\u00a0/gi, ' ');
|
||||
}
|
||||
|
||||
function stripEntitiesForQuote(text: ApiFormattedText): ApiFormattedText {
|
||||
if (!text.entities) return text;
|
||||
|
||||
const entities = text.entities.filter((entity) => ALLOWED_QUOTE_ENTITIES.has(entity.type as ApiMessageEntityTypes));
|
||||
return { ...text, entities: entities.length ? entities : undefined };
|
||||
}
|
||||
|
||||
function wrapHtmlWithMarkupTags(range: Range, html: string) {
|
||||
const container = range.commonAncestorContainer;
|
||||
if (container.nodeType === Node.ELEMENT_NODE && (container as Element).classList.contains('text-content')) {
|
||||
return html;
|
||||
}
|
||||
let currentElement = range.commonAncestorContainer.parentElement;
|
||||
while (currentElement && !currentElement.classList.contains('text-content')) {
|
||||
const tag = currentElement.tagName.toLowerCase();
|
||||
const entityType = currentElement.dataset.entityType;
|
||||
html = `<${tag} ${entityType ? `data-entity-type="${entityType}"` : ''}>${html}</${tag}>`;
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
export function isSelectionRangeInsideMessage(range: Range) {
|
||||
const ancestor = range.commonAncestorContainer;
|
||||
const el = ancestor.nodeType === Node.TEXT_NODE
|
||||
? ancestor.parentNode! as Element
|
||||
: ancestor as Element;
|
||||
|
||||
return Boolean(el.closest('.message-content-wrapper .text-content'))
|
||||
&& !(Boolean(el.closest('.EmbeddedMessage')) || Boolean(el.closest('.WebPage')));
|
||||
}
|
||||
@ -16,7 +16,7 @@ import { copyHtmlToClipboard } from '../../../util/clipboard';
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { compact, findLast } from '../../../util/iteratees';
|
||||
import * as langProvider from '../../../util/langProvider';
|
||||
import parseMessageInput from '../../../util/parseMessageInput';
|
||||
import parseHtmlAsFormattedText from '../../../util/parseHtmlAsFormattedText';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||
import versionNotification from '../../../versionNotification.txt';
|
||||
@ -689,7 +689,7 @@ addActionHandler('checkVersionNotification', (global, actions): ActionReturnType
|
||||
chatId: SERVICE_NOTIFICATIONS_USER_ID,
|
||||
date: getServerTime(),
|
||||
content: {
|
||||
text: parseMessageInput(versionNotification, true),
|
||||
text: parseHtmlAsFormattedText(versionNotification, true),
|
||||
},
|
||||
isOutgoing: false,
|
||||
};
|
||||
|
||||
@ -175,80 +175,81 @@ $icons-map: (
|
||||
"premium": "\f190",
|
||||
"previous": "\f191",
|
||||
"privacy-policy": "\f192",
|
||||
"quote": "\f193",
|
||||
"readchats": "\f194",
|
||||
"recent": "\f195",
|
||||
"reload": "\f196",
|
||||
"remove": "\f197",
|
||||
"reopen-topic": "\f198",
|
||||
"replace": "\f199",
|
||||
"replies": "\f19a",
|
||||
"reply-filled": "\f19b",
|
||||
"reply": "\f19c",
|
||||
"revote": "\f19d",
|
||||
"save-story": "\f19e",
|
||||
"saved-messages": "\f19f",
|
||||
"schedule": "\f1a0",
|
||||
"search": "\f1a1",
|
||||
"select": "\f1a2",
|
||||
"send-outline": "\f1a3",
|
||||
"send": "\f1a4",
|
||||
"settings-filled": "\f1a5",
|
||||
"settings": "\f1a6",
|
||||
"share-filled": "\f1a7",
|
||||
"share-screen-outlined": "\f1a8",
|
||||
"share-screen-stop": "\f1a9",
|
||||
"share-screen": "\f1aa",
|
||||
"sidebar": "\f1ab",
|
||||
"skip-next": "\f1ac",
|
||||
"skip-previous": "\f1ad",
|
||||
"smallscreen": "\f1ae",
|
||||
"smile": "\f1af",
|
||||
"sort": "\f1b0",
|
||||
"speaker-muted-story": "\f1b1",
|
||||
"speaker-outline": "\f1b2",
|
||||
"speaker-story": "\f1b3",
|
||||
"speaker": "\f1b4",
|
||||
"spoiler-disable": "\f1b5",
|
||||
"spoiler": "\f1b6",
|
||||
"sport": "\f1b7",
|
||||
"stats": "\f1b8",
|
||||
"stealth-future": "\f1b9",
|
||||
"stealth-past": "\f1ba",
|
||||
"stickers": "\f1bb",
|
||||
"stop-raising-hand": "\f1bc",
|
||||
"stop": "\f1bd",
|
||||
"story-caption": "\f1be",
|
||||
"story-expired": "\f1bf",
|
||||
"story-priority": "\f1c0",
|
||||
"story-reply": "\f1c1",
|
||||
"strikethrough": "\f1c2",
|
||||
"timer": "\f1c3",
|
||||
"transcribe": "\f1c4",
|
||||
"truck": "\f1c5",
|
||||
"unarchive": "\f1c6",
|
||||
"underlined": "\f1c7",
|
||||
"unlock-badge": "\f1c8",
|
||||
"unlock": "\f1c9",
|
||||
"unmute": "\f1ca",
|
||||
"unpin": "\f1cb",
|
||||
"unread": "\f1cc",
|
||||
"up": "\f1cd",
|
||||
"user-filled": "\f1ce",
|
||||
"user-online": "\f1cf",
|
||||
"user": "\f1d0",
|
||||
"video-outlined": "\f1d1",
|
||||
"video-stop": "\f1d2",
|
||||
"video": "\f1d3",
|
||||
"voice-chat": "\f1d4",
|
||||
"volume-1": "\f1d5",
|
||||
"volume-2": "\f1d6",
|
||||
"volume-3": "\f1d7",
|
||||
"web": "\f1d8",
|
||||
"webapp": "\f1d9",
|
||||
"word-wrap": "\f1da",
|
||||
"zoom-in": "\f1db",
|
||||
"zoom-out": "\f1dc",
|
||||
"quote-text": "\f193",
|
||||
"quote": "\f194",
|
||||
"readchats": "\f195",
|
||||
"recent": "\f196",
|
||||
"reload": "\f197",
|
||||
"remove": "\f198",
|
||||
"reopen-topic": "\f199",
|
||||
"replace": "\f19a",
|
||||
"replies": "\f19b",
|
||||
"reply-filled": "\f19c",
|
||||
"reply": "\f19d",
|
||||
"revote": "\f19e",
|
||||
"save-story": "\f19f",
|
||||
"saved-messages": "\f1a0",
|
||||
"schedule": "\f1a1",
|
||||
"search": "\f1a2",
|
||||
"select": "\f1a3",
|
||||
"send-outline": "\f1a4",
|
||||
"send": "\f1a5",
|
||||
"settings-filled": "\f1a6",
|
||||
"settings": "\f1a7",
|
||||
"share-filled": "\f1a8",
|
||||
"share-screen-outlined": "\f1a9",
|
||||
"share-screen-stop": "\f1aa",
|
||||
"share-screen": "\f1ab",
|
||||
"sidebar": "\f1ac",
|
||||
"skip-next": "\f1ad",
|
||||
"skip-previous": "\f1ae",
|
||||
"smallscreen": "\f1af",
|
||||
"smile": "\f1b0",
|
||||
"sort": "\f1b1",
|
||||
"speaker-muted-story": "\f1b2",
|
||||
"speaker-outline": "\f1b3",
|
||||
"speaker-story": "\f1b4",
|
||||
"speaker": "\f1b5",
|
||||
"spoiler-disable": "\f1b6",
|
||||
"spoiler": "\f1b7",
|
||||
"sport": "\f1b8",
|
||||
"stats": "\f1b9",
|
||||
"stealth-future": "\f1ba",
|
||||
"stealth-past": "\f1bb",
|
||||
"stickers": "\f1bc",
|
||||
"stop-raising-hand": "\f1bd",
|
||||
"stop": "\f1be",
|
||||
"story-caption": "\f1bf",
|
||||
"story-expired": "\f1c0",
|
||||
"story-priority": "\f1c1",
|
||||
"story-reply": "\f1c2",
|
||||
"strikethrough": "\f1c3",
|
||||
"timer": "\f1c4",
|
||||
"transcribe": "\f1c5",
|
||||
"truck": "\f1c6",
|
||||
"unarchive": "\f1c7",
|
||||
"underlined": "\f1c8",
|
||||
"unlock-badge": "\f1c9",
|
||||
"unlock": "\f1ca",
|
||||
"unmute": "\f1cb",
|
||||
"unpin": "\f1cc",
|
||||
"unread": "\f1cd",
|
||||
"up": "\f1ce",
|
||||
"user-filled": "\f1cf",
|
||||
"user-online": "\f1d0",
|
||||
"user": "\f1d1",
|
||||
"video-outlined": "\f1d2",
|
||||
"video-stop": "\f1d3",
|
||||
"video": "\f1d4",
|
||||
"voice-chat": "\f1d5",
|
||||
"volume-1": "\f1d6",
|
||||
"volume-2": "\f1d7",
|
||||
"volume-3": "\f1d8",
|
||||
"web": "\f1d9",
|
||||
"webapp": "\f1da",
|
||||
"word-wrap": "\f1db",
|
||||
"zoom-in": "\f1dc",
|
||||
"zoom-out": "\f1dd",
|
||||
);
|
||||
|
||||
.icon-active-sessions::before {
|
||||
@ -689,6 +690,9 @@ $icons-map: (
|
||||
.icon-privacy-policy::before {
|
||||
content: map.get($icons-map, "privacy-policy");
|
||||
}
|
||||
.icon-quote-text::before {
|
||||
content: map.get($icons-map, "quote-text");
|
||||
}
|
||||
.icon-quote::before {
|
||||
content: map.get($icons-map, "quote");
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -145,6 +145,7 @@ export type FontIconName =
|
||||
| 'premium'
|
||||
| 'previous'
|
||||
| 'privacy-policy'
|
||||
| 'quote-text'
|
||||
| 'quote'
|
||||
| 'readchats'
|
||||
| 'recent'
|
||||
|
||||
@ -21,7 +21,7 @@ export const ENTITY_CLASS_BY_NODE_NAME: Record<string, ApiMessageEntityTypes> =
|
||||
|
||||
const MAX_TAG_DEEPNESS = 3;
|
||||
|
||||
export default function parseMessageInput(
|
||||
export default function parseHtmlAsFormattedText(
|
||||
html: string, withMarkdownLinks = false, skipMarkdown = false,
|
||||
): ApiFormattedText {
|
||||
const fragment = document.createElement('div');
|
||||
Loading…
x
Reference in New Issue
Block a user