fix: respect useEmoji pref on messages (#28975)

pull/29334/head^2
Yash Rajpal 3 years ago committed by GitHub
parent 40cebcc0f1
commit 9ea8088f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .changeset/large-penguins-suffer.md
  2. 4
      apps/meteor/client/components/GazzodownText.tsx
  3. 107
      apps/meteor/client/views/room/providers/ComposerPopupProvider.tsx
  4. 86
      packages/gazzodown/src/Markup.spec.tsx
  5. 1
      packages/gazzodown/src/MarkupInteractionContext.ts
  6. 6
      packages/gazzodown/src/emoji/Emoji.tsx

@ -0,0 +1,6 @@
---
'@rocket.chat/gazzodown': minor
'@rocket.chat/meteor': minor
---
fix: respect useEmoji preference for messages

@ -46,6 +46,7 @@ const GazzodownText = ({ mentions, channels, searchText, children }: GazzodownTe
}, [searchText]);
const convertAsciiToEmoji = useUserPreference<boolean>('convertAsciiEmoji', true);
const useEmoji = Boolean(useUserPreference('useEmojis'));
const chat = useChat();
@ -106,11 +107,12 @@ const GazzodownText = ({ mentions, channels, searchText, children }: GazzodownTe
detectEmoji,
highlightRegex,
markRegex,
convertAsciiToEmoji,
resolveUserMention,
onUserMentionClick,
resolveChannelMention,
onChannelMentionClick,
convertAsciiToEmoji,
useEmoji,
}}
>
{children}

@ -2,7 +2,7 @@ import type { IRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
import { useLocalStorage } from '@rocket.chat/fuselage-hooks';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { useMethod, useSetting, useTranslation } from '@rocket.chat/ui-contexts';
import { useMethod, useSetting, useTranslation, useUserPreference } from '@rocket.chat/ui-contexts';
import React, { useMemo } from 'react';
import type { ReactNode } from 'react';
@ -31,7 +31,7 @@ const ComposerPopupProvider = ({ children, room }: { children: ReactNode; room:
const cannedResponseEnabled = useSetting<boolean>('Canned_Responses_Enable');
const [recentEmojis] = useLocalStorage('emoji.recent', []);
const isOmnichannel = isOmnichannelRoom(room);
const useEmoji = useUserPreference('useEmojis');
const t = useTranslation();
const call = useMethod('getSlashCommandPreviews');
@ -150,63 +150,64 @@ const ComposerPopupProvider = ({ children, room }: { children: ReactNode; room:
getValue: (item) => `${item.name || item.fname}`,
renderItem: ({ item }) => <ComposerBoxPopupRoom {...item} />,
}) as any,
createMessageBoxPopupConfig<ComposerBoxPopupEmojiProps>({
trigger: ':',
title: t('Emoji'),
getItemsFromLocal: async (filter: string) => {
const exactFinalTone = new RegExp('^tone[1-5]:*$');
const colorBlind = new RegExp('tone[1-5]:*$');
const seeColor = new RegExp('_t(?:o|$)(?:n|$)(?:e|$)(?:[1-5]|$)(?::|$)$');
useEmoji &&
createMessageBoxPopupConfig<ComposerBoxPopupEmojiProps>({
trigger: ':',
title: t('Emoji'),
getItemsFromLocal: async (filter: string) => {
const exactFinalTone = new RegExp('^tone[1-5]:*$');
const colorBlind = new RegExp('tone[1-5]:*$');
const seeColor = new RegExp('_t(?:o|$)(?:n|$)(?:e|$)(?:[1-5]|$)(?::|$)$');
const emojiSort = (recents: string[]) => (a: { _id: string }, b: { _id: string }) => {
const aExact = a._id === key ? 2 : 0;
const bExact = b._id === key ? 2 : 0;
const aPartial = a._id.startsWith(key) ? 1 : 0;
const bPartial = b._id.startsWith(key) ? 1 : 0;
const emojiSort = (recents: string[]) => (a: { _id: string }, b: { _id: string }) => {
const aExact = a._id === key ? 2 : 0;
const bExact = b._id === key ? 2 : 0;
const aPartial = a._id.startsWith(key) ? 1 : 0;
const bPartial = b._id.startsWith(key) ? 1 : 0;
let aScore = aExact + aPartial;
let bScore = bExact + bPartial;
let aScore = aExact + aPartial;
let bScore = bExact + bPartial;
if (recents.includes(a._id)) {
aScore += recents.indexOf(a._id) + 1;
}
if (recents.includes(b._id)) {
bScore += recents.indexOf(b._id) + 1;
}
if (recents.includes(a._id)) {
aScore += recents.indexOf(a._id) + 1;
}
if (recents.includes(b._id)) {
bScore += recents.indexOf(b._id) + 1;
}
if (aScore > bScore) {
return -1;
}
if (aScore < bScore) {
return 1;
}
return 0;
};
const filterRegex = new RegExp(escapeRegExp(filter), 'i');
const key = `:${filter}`;
if (aScore > bScore) {
return -1;
}
if (aScore < bScore) {
return 1;
}
return 0;
};
const filterRegex = new RegExp(escapeRegExp(filter), 'i');
const key = `:${filter}`;
const recents = recentEmojis.map((item) => `:${item}:`);
const recents = recentEmojis.map((item) => `:${item}:`);
const collection = emoji.list;
const collection = emoji.list;
return Object.keys(collection)
.map((_id) => {
const data = collection[key];
return { _id, data };
})
.filter(
({ _id }) =>
filterRegex.test(_id) && (exactFinalTone.test(_id.substring(key.length)) || seeColor.test(key) || !colorBlind.test(_id)),
)
.sort(emojiSort(recents))
.slice(0, 10);
},
getItemsFromServer: async () => {
return [];
},
getValue: (item) => `${item._id.substring(1)}`,
renderItem: ({ item }) => <ComposerPopupEmoji {...item} />,
}),
return Object.keys(collection)
.map((_id) => {
const data = collection[key];
return { _id, data };
})
.filter(
({ _id }) =>
filterRegex.test(_id) && (exactFinalTone.test(_id.substring(key.length)) || seeColor.test(key) || !colorBlind.test(_id)),
)
.sort(emojiSort(recents))
.slice(0, 10);
},
getItemsFromServer: async () => {
return [];
},
getValue: (item) => `${item._id.substring(1)}`,
renderItem: ({ item }) => <ComposerPopupEmoji {...item} />,
}),
createMessageBoxPopupConfig<ComposerBoxPopupEmojiProps>({
title: t('Emoji'),
trigger: '\\+:',
@ -352,7 +353,7 @@ const ComposerPopupProvider = ({ children, room }: { children: ReactNode; room:
},
}),
].filter(Boolean);
}, [t, cannedResponseEnabled, isOmnichannel, recentEmojis, suggestionsCount, userSpotlight, rid, call]);
}, [t, cannedResponseEnabled, isOmnichannel, recentEmojis, suggestionsCount, userSpotlight, rid, call, useEmoji]);
return <ComposerPopupContext.Provider value={value} children={children} />;
};

@ -14,18 +14,25 @@ it('renders empty', () => {
it('renders a big emoji block', () => {
render(
<Markup
tokens={[
{
type: 'BIG_EMOJI',
value: [
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: 'smile' }, shortCode: 'smile' },
{ type: 'EMOJI', value: undefined, unicode: '😀' },
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: 'smile' }, shortCode: 'smile' },
],
},
]}
/>,
<MarkupInteractionContext.Provider
value={{
convertAsciiToEmoji: true,
useEmoji: true,
}}
>
<Markup
tokens={[
{
type: 'BIG_EMOJI',
value: [
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: 'smile' }, shortCode: 'smile' },
{ type: 'EMOJI', value: undefined, unicode: '😀' },
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: 'smile' }, shortCode: 'smile' },
],
},
]}
/>
</MarkupInteractionContext.Provider>,
);
expect(screen.getByRole('presentation')).toHaveTextContent(':smile:😀:smile:');
@ -38,6 +45,7 @@ it('renders a big emoji block with ASCII emoji', () => {
<MarkupInteractionContext.Provider
value={{
convertAsciiToEmoji: false,
useEmoji: true,
}}
>
<Markup
@ -282,3 +290,57 @@ it('renders a line break', () => {
expect(container).toContainHTML('<br>');
});
it('renders plain text instead of emojis based on preference', () => {
render(
<MarkupInteractionContext.Provider
value={{
convertAsciiToEmoji: false,
useEmoji: false,
}}
>
<Markup
tokens={[
{
type: 'PARAGRAPH',
value: [
{ type: 'PLAIN_TEXT', value: 'Hey! ' },
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: 'smile' }, shortCode: 'smile' },
{ type: 'PLAIN_TEXT', value: ' ' },
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: ':)' }, shortCode: 'slight_smile' },
],
},
]}
/>
</MarkupInteractionContext.Provider>,
);
expect(screen.getByText('Hey! :smile: :)')).toBeInTheDocument();
});
it('renders plain text instead of ASCII emojis based on useEmojis preference', () => {
render(
<MarkupInteractionContext.Provider
value={{
convertAsciiToEmoji: true,
useEmoji: false,
}}
>
<Markup
tokens={[
{
type: 'PARAGRAPH',
value: [
{ type: 'PLAIN_TEXT', value: 'Hey! ' },
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: 'smile' }, shortCode: 'smile' },
{ type: 'PLAIN_TEXT', value: ' ' },
{ type: 'EMOJI', value: { type: 'PLAIN_TEXT', value: ':)' }, shortCode: 'slight_smile' },
],
},
]}
/>
</MarkupInteractionContext.Provider>,
);
expect(screen.getByText('Hey! :smile: :)')).toBeInTheDocument();
});

@ -15,6 +15,7 @@ type MarkupInteractionContextValue = {
resolveChannelMention?: (mention: string) => ChannelMention | undefined;
onChannelMentionClick?: (mentionedChannel: ChannelMention) => ((e: UIEvent) => void) | undefined;
convertAsciiToEmoji?: boolean;
useEmoji?: boolean;
};
export const MarkupInteractionContext = createContext<MarkupInteractionContextValue>({});

@ -11,13 +11,17 @@ type EmojiProps = MessageParser.Emoji & {
};
const Emoji = ({ big = false, preview = false, ...emoji }: EmojiProps): ReactElement => {
const { convertAsciiToEmoji } = useContext(MarkupInteractionContext);
const { convertAsciiToEmoji, useEmoji } = useContext(MarkupInteractionContext);
const asciiEmoji = useMemo(
() => ('shortCode' in emoji && emoji.value.value !== emoji.shortCode ? emoji.value.value : undefined),
[emoji],
);
if (!useEmoji && 'shortCode' in emoji) {
return <PlainSpan text={emoji.shortCode === emoji.value.value ? `:${emoji.shortCode}:` : emoji.value.value} />;
}
if (!convertAsciiToEmoji && asciiEmoji) {
return <PlainSpan text={asciiEmoji} />;
}

Loading…
Cancel
Save