Fix standalone emoji not rendering at jumbo 100px size.
Detect emoji-only messages more reliably (including mx-emoticon HTML), bypass folds Text sizing when jumbo, and force 100px emote dimensions via data-jumbo-emoji styles.
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { CSSProperties, ReactNode } from 'react';
|
|||||||
import { Box, Chip, Text, toRem } from 'folds';
|
import { Box, Chip, Text, toRem } from 'folds';
|
||||||
import { Icon, Icons } from '../icons';
|
import { Icon, Icons } from '../icons';
|
||||||
import { IContent } from 'matrix-js-sdk';
|
import { IContent } from 'matrix-js-sdk';
|
||||||
import { JUMBO_EMOJI_REG, URL_REG } from '../../utils/regex';
|
import { URL_REG, isJumboEmoji } from '../../utils/regex';
|
||||||
import { trimReplyFromBody } from '../../utils/room';
|
import { trimReplyFromBody } from '../../utils/room';
|
||||||
import { MessageTextBody } from './layout';
|
import { MessageTextBody } from './layout';
|
||||||
import {
|
import {
|
||||||
@@ -92,7 +92,7 @@ export function MText({ edited, content, renderBody, renderUrlsPreview, style }:
|
|||||||
<>
|
<>
|
||||||
<MessageTextBody
|
<MessageTextBody
|
||||||
preWrap={typeof customBody !== 'string'}
|
preWrap={typeof customBody !== 'string'}
|
||||||
jumboEmoji={JUMBO_EMOJI_REG.test(trimmedBody)}
|
jumboEmoji={isJumboEmoji(trimmedBody, trimmedCustomBody)}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{renderBody({
|
{renderBody({
|
||||||
@@ -135,7 +135,7 @@ export function MEmote({
|
|||||||
<MessageTextBody
|
<MessageTextBody
|
||||||
emote
|
emote
|
||||||
preWrap={typeof customBody !== 'string'}
|
preWrap={typeof customBody !== 'string'}
|
||||||
jumboEmoji={JUMBO_EMOJI_REG.test(trimmedBody)}
|
jumboEmoji={false}
|
||||||
>
|
>
|
||||||
<b>{`${displayName} `}</b>
|
<b>{`${displayName} `}</b>
|
||||||
{renderBody({
|
{renderBody({
|
||||||
@@ -171,7 +171,7 @@ export function MNotice({ edited, content, renderBody, renderUrlsPreview }: MNot
|
|||||||
<MessageTextBody
|
<MessageTextBody
|
||||||
notice
|
notice
|
||||||
preWrap={typeof customBody !== 'string'}
|
preWrap={typeof customBody !== 'string'}
|
||||||
jumboEmoji={JUMBO_EMOJI_REG.test(trimmedBody)}
|
jumboEmoji={isJumboEmoji(trimmedBody, trimmedCustomBody)}
|
||||||
>
|
>
|
||||||
{renderBody({
|
{renderBody({
|
||||||
body: trimmedBody,
|
body: trimmedBody,
|
||||||
|
|||||||
@@ -32,10 +32,11 @@ export const MessageTextBody = as<'div', css.MessageTextBodyVariants & { notice?
|
|||||||
({ as: asComp = 'div', className, preWrap, jumboEmoji, emote, notice, ...props }, ref) => (
|
({ as: asComp = 'div', className, preWrap, jumboEmoji, emote, notice, ...props }, ref) => (
|
||||||
<Text
|
<Text
|
||||||
as={asComp}
|
as={asComp}
|
||||||
size="T400"
|
size={jumboEmoji ? 'Inherit' : 'T400'}
|
||||||
priority={notice ? '300' : '400'}
|
priority={notice ? '300' : '400'}
|
||||||
className={classNames(css.MessageTextBody({ preWrap, jumboEmoji, emote }), className)}
|
className={classNames(css.MessageTextBody({ preWrap, jumboEmoji, emote }), className)}
|
||||||
data-allow-text-selection="true"
|
data-allow-text-selection="true"
|
||||||
|
data-jumbo-emoji={jumboEmoji ? 'true' : undefined}
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -232,6 +232,9 @@ export const MessageTextBody = recipe({
|
|||||||
true: {
|
true: {
|
||||||
fontSize: toRem(100),
|
fontSize: toRem(100),
|
||||||
lineHeight: toRem(100),
|
lineHeight: toRem(100),
|
||||||
|
// Don't clip oversized glyphs / custom emotes in jumbo mode
|
||||||
|
overflow: 'visible',
|
||||||
|
overflowY: 'visible',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emote: {
|
emote: {
|
||||||
@@ -245,24 +248,27 @@ export const MessageTextBody = recipe({
|
|||||||
|
|
||||||
export type MessageTextBodyVariants = RecipeVariants<typeof MessageTextBody>;
|
export type MessageTextBodyVariants = RecipeVariants<typeof MessageTextBody>;
|
||||||
|
|
||||||
const jumboEmojiClass = MessageTextBody.classNames.variants.jumboEmoji.true;
|
// Prefer data-attr so sizing wins even if recipe class order fights folds Text sizes.
|
||||||
|
const jumboEmojiSelector = '[data-jumbo-emoji="true"]';
|
||||||
|
|
||||||
globalStyle(`${jumboEmojiClass} .${htmlCss.EmoticonBase}`, {
|
globalStyle(`${jumboEmojiSelector} .${htmlCss.EmoticonBase}`, {
|
||||||
height: toRem(100),
|
height: `${toRem(100)} !important`,
|
||||||
padding: 0,
|
padding: '0 !important',
|
||||||
verticalAlign: 'bottom',
|
verticalAlign: 'bottom',
|
||||||
});
|
});
|
||||||
|
|
||||||
globalStyle(`${jumboEmojiClass} .${htmlCss.Emoticon.classNames.base}`, {
|
globalStyle(`${jumboEmojiSelector} .${htmlCss.Emoticon.classNames.base}`, {
|
||||||
fontSize: toRem(100),
|
fontSize: `${toRem(100)} !important`,
|
||||||
height: toRem(100),
|
height: `${toRem(100)} !important`,
|
||||||
minWidth: toRem(100),
|
minWidth: `${toRem(100)} !important`,
|
||||||
lineHeight: toRem(100),
|
lineHeight: `${toRem(100)} !important`,
|
||||||
top: 0,
|
top: '0 !important',
|
||||||
});
|
});
|
||||||
|
|
||||||
globalStyle(`${jumboEmojiClass} .${htmlCss.EmoticonImg}`, {
|
globalStyle(`${jumboEmojiSelector} .${htmlCss.EmoticonImg}`, {
|
||||||
height: toRem(100),
|
height: `${toRem(100)} !important`,
|
||||||
width: toRem(100),
|
width: `${toRem(100)} !important`,
|
||||||
|
maxHeight: `${toRem(100)} !important`,
|
||||||
|
maxWidth: `${toRem(100)} !important`,
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -552,10 +552,12 @@ export const getReactCustomHtmlParser = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (htmlSrc && 'data-mx-emoticon' in props) {
|
if (htmlSrc && 'data-mx-emoticon' in props) {
|
||||||
|
// Drop presentational height/width so jumbo CSS can size freely
|
||||||
|
const { height: _h, width: _w, ...imgProps } = props;
|
||||||
return (
|
return (
|
||||||
<span className={css.EmoticonBase}>
|
<span className={css.EmoticonBase}>
|
||||||
<span className={css.Emoticon()}>
|
<span className={css.Emoticon()}>
|
||||||
<AuthenticatedImg {...props} className={css.EmoticonImg} src={htmlSrc} />
|
<AuthenticatedImg {...imgProps} className={css.EmoticonImg} src={htmlSrc} />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,3 +24,28 @@ export const EMOJI_PATTERN = `[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u
|
|||||||
export const JUMBO_EMOJI_REG = new RegExp(
|
export const JUMBO_EMOJI_REG = new RegExp(
|
||||||
`^(((${EMOJI_PATTERN})|(:.+?:))(${VARIATION_SELECTOR_PATTERN}|\\s)*){1,10}$`
|
`^(((${EMOJI_PATTERN})|(:.+?:))(${VARIATION_SELECTOR_PATTERN}|\\s)*){1,10}$`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Zero-width / format chars that break naive emoji-only matching. */
|
||||||
|
const JUMBO_IGNORE_CHARS_REG = /[\u200B-\u200D\uFEFF\u00AD\u2060]/g;
|
||||||
|
|
||||||
|
const ONLY_MX_EMOTICON_HTML_REG =
|
||||||
|
/^(?:\s|<br\s*\/?>)*((?:<img\b[^>]*\bdata-mx-emoticon\b[^>]*\/?>)(?:\s|<br\s*\/?>)*){1,10}$/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when a message is emoji/custom-emote only (Discord-style jumbo).
|
||||||
|
* Strips zero-width chars and also accepts HTML that is only mx-emoticon imgs.
|
||||||
|
*/
|
||||||
|
export const isJumboEmoji = (body: string, customBody?: string): boolean => {
|
||||||
|
const plain = body.replace(JUMBO_IGNORE_CHARS_REG, '').trim();
|
||||||
|
if (plain.length > 0 && JUMBO_EMOJI_REG.test(plain)) return true;
|
||||||
|
|
||||||
|
if (typeof customBody === 'string') {
|
||||||
|
const html = customBody
|
||||||
|
.replace(JUMBO_IGNORE_CHARS_REG, '')
|
||||||
|
.trim()
|
||||||
|
.replace(/(<br\s*\/?>\s*)+$/gi, '');
|
||||||
|
if (html.length > 0 && ONLY_MX_EMOTICON_HTML_REG.test(html)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user