MM-18998 Add emojis in the post to recent emojis (#4986)

* add emojis available in the post to recent emojis

* made add recent reactions more performant

* lint fix

* added support for namedicons, update only if send post is success

* updated emojiUnicode function

* limit the number of recent emojis

* added e2e tests for MM-T3495

* filter out aliases

* Typo fix

* return data:true when success false when fails

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
A C SREEDHAR REDDY 2020-12-11 04:50:40 +05:30 committed by GitHub
parent 170ef360c1
commit f0598dde54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 147 additions and 15 deletions

View file

@ -9,6 +9,8 @@ import {Client4} from '@mm-redux/client';
import {getPostIdsInCurrentChannel, makeGetPostIdsForThread} from '@mm-redux/selectors/entities/posts';
import {ViewTypes} from 'app/constants';
import {EmojiIndicesByAlias, EmojiIndicesByUnicode, Emojis} from '@utils/emojis';
import emojiRegex from 'emoji-regex';
const getPostIdsForThread = makeGetPostIdsForThread();
@ -98,3 +100,41 @@ async function getCustomEmojiByName(name) {
return null;
}
export function addRecentUsedEmojisInMessage(message) {
return (dispatch) => {
const RE_UNICODE_EMOJI = emojiRegex();
const RE_NAMED_EMOJI = /(:([a-zA-Z0-9_-]+):)/g;
const emojis = message.match(RE_UNICODE_EMOJI);
const namedEmojis = message.match(RE_NAMED_EMOJI);
function emojiUnicode(input) {
const emoji = [];
for (const i of input) {
emoji.push(i.codePointAt(0).toString(16));
}
return emoji.join('-');
}
const emojisAvailableWithMattermost = [];
if (emojis) {
for (const emoji of emojis) {
const unicode = emojiUnicode(emoji);
const index = EmojiIndicesByUnicode.get(unicode || '');
if (index) {
emojisAvailableWithMattermost.push(Emojis[index].aliases[0]);
}
}
}
if (namedEmojis) {
for (const emoji of namedEmojis) {
const index = EmojiIndicesByAlias.get(emoji.slice(1, -1));
if (index) {
emojisAvailableWithMattermost.push(Emojis[index].aliases[0]);
}
}
}
dispatch({
type: ViewTypes.ADD_RECENT_EMOJI_ARRAY,
emojis: emojisAvailableWithMattermost,
});
};
}

View file

@ -232,15 +232,17 @@ export default class EmojiPicker extends PureComponent {
return Math.floor(Number(((deviceWidth - (SECTION_MARGIN * shorten)) / ((EMOJI_SIZE + 7) + (EMOJI_GUTTER * shorten)))));
};
renderItem = ({item}) => {
renderItem = ({item, section}) => {
return (
<EmojiPickerRow
key={item.key}
emojiGutter={EMOJI_GUTTER}
emojiSize={EMOJI_SIZE}
items={item.items}
onEmojiPress={this.props.onEmojiPress}
/>
<View testID={section.defaultMessage}>
<EmojiPickerRow
key={item.key}
emojiGutter={EMOJI_GUTTER}
emojiSize={EMOJI_SIZE}
items={item.items}
onEmojiPress={this.props.onEmojiPress}
/>
</View>
);
};

View file

@ -59,6 +59,7 @@ export default class DraftInput extends PureComponent {
useGroupMentions: PropTypes.bool.isRequired,
channelMemberCountsByGroup: PropTypes.object,
groupsWithAllowReference: PropTypes.object,
addRecentUsedEmojisInMessage: PropTypes.func.isRequired,
};
static defaultProps = {
@ -162,7 +163,11 @@ export default class DraftInput extends PureComponent {
message: value,
};
createPost(post, postFiles);
createPost(post, postFiles).then(({data}) => {
if (data) {
this.props.addRecentUsedEmojisInMessage(message);
}
});
if (postFiles.length) {
handleClearFiles(channelId, rootId);

View file

@ -10,11 +10,15 @@ import DraftInput from './draft_input';
jest.useFakeTimers();
async function createPost() {
return {data: true, failed: false};
}
describe('DraftInput', () => {
const baseProps = {
registerTypingAnimation: jest.fn(),
addReactionToLatestPost: jest.fn(),
createPost: jest.fn(),
createPost: jest.fn(createPost),
executeCommand: jest.fn(),
handleCommentDraftChanged: jest.fn(),
handlePostDraftChanged: jest.fn(),
@ -75,6 +79,7 @@ describe('DraftInput', () => {
},
},
membersCount: 10,
addRecentUsedEmojisInMessage: jest.fn(),
};
const ref = React.createRef();

View file

@ -4,7 +4,7 @@
import {connect} from 'react-redux';
import {executeCommand} from '@actions/views/command';
import {addReactionToLatestPost} from '@actions/views/emoji';
import {addReactionToLatestPost, addRecentUsedEmojisInMessage} from '@actions/views/emoji';
import {handleClearFiles, handleClearFailedFiles} from '@actions/views/file_upload';
import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft';
import {getChannelTimezones, getChannelMemberCountsByGroup} from '@mm-redux/actions/channels';
@ -101,6 +101,7 @@ const mapDispatchToProps = {
handleClearFailedFiles,
setStatus,
getChannelMemberCountsByGroup,
addRecentUsedEmojisInMessage,
};
export default connect(mapStateToProps, mapDispatchToProps, null, {forwardRef: true})(PostDraft);

View file

@ -18,6 +18,7 @@ import {
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import ReactionButton from './reaction_button';
import {EmojiIndicesByAlias} from '@utils/emojis';
const ReactionPicker = (props) => {
const {theme} = props;
@ -37,7 +38,11 @@ const ReactionPicker = (props) => {
iconSize = SMALL_ICON_SIZE;
}
const emojis = Array.from(new Set(props.recentEmojis.concat(DEFAULT_EMOJIS))).splice(0, 6);
const recentEmojisIndices = props.recentEmojis.map((recentEmoji) => EmojiIndicesByAlias.get(recentEmoji));
const emojis = props.recentEmojis.
concat(DEFAULT_EMOJIS.filter((defaultEmoji) => !recentEmojisIndices.includes(EmojiIndicesByAlias.get(defaultEmoji)))).
splice(0, 6);
const list = emojis.map((emoji) => {
return (
<ReactionButton

View file

@ -90,6 +90,7 @@ const ViewTypes = keyMirror({
SET_LAST_UPGRADE_CHECK: null,
ADD_RECENT_EMOJI: null,
ADD_RECENT_EMOJI_ARRAY: null,
ANNOUNCEMENT_BANNER: null,
INCREMENT_EMOJI_PICKER_PAGE: null,

View file

@ -246,6 +246,7 @@ export function createPost(post: Post, files: any[] = []) {
}
dispatch(batchActions(actions, 'BATCH_CREATE_POST'));
return {data: true};
} catch (error) {
const data = {
...newPost,
@ -267,9 +268,8 @@ export function createPost(post: Post, files: any[] = []) {
}
dispatch(batchActions(actions, 'BATCH_CREATE_POST_FAILED'));
return {data: false};
}
return {data: true};
};
}

View file

@ -3,6 +3,8 @@
import {ViewTypes} from 'app/constants';
const MAXIMUM_RECENT_EMOJI = 27;
export default function recentEmojis(state = [], action) {
switch (action.type) {
case ViewTypes.ADD_RECENT_EMOJI: {
@ -14,9 +16,21 @@ export default function recentEmojis(state = [], action) {
}
nextState.unshift(action.emoji);
if (nextState.length > MAXIMUM_RECENT_EMOJI) {
nextState.splice(MAXIMUM_RECENT_EMOJI);
}
return nextState;
}
case ViewTypes.ADD_RECENT_EMOJI_ARRAY: {
const nextRecentEmojis = action.emojis.reduce((currentState, emoji) => {
return [emoji, ...currentState.filter((currentEmoji) => currentEmoji !== emoji)];
}, state);
if (nextRecentEmojis.length > MAXIMUM_RECENT_EMOJI) {
nextRecentEmojis.splice(MAXIMUM_RECENT_EMOJI);
}
return nextRecentEmojis;
}
default:
return state;
}

View file

@ -0,0 +1,59 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// *******************************************************************
// - [#] indicates a test step (e.g. # Go to a screen)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {ChannelScreen, AddReactionScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
import {PostOptions} from '@support/ui/component';
describe('Messaging', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
// # Open channel screen
await ChannelScreen.open(user);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T3495 should include post message emojis in Recent Emojis section and Recently Used section', async () => {
// * Verify channel screen is visible
await ChannelScreen.toBeVisible();
const {
postMessage,
} = ChannelScreen;
// # Type message on post input
const message = 'The quick brown fox :fox_face: jumps over the lazy dog :dog:';
await postMessage(message);
// * Verify message is posted
await expect(element(by.text('The quick brown fox 🦊 jumps over the lazy dog 🐶'))).toBeVisible();
// # Open PostOptions
await element(by.text('The quick brown fox 🦊 jumps over the lazy dog 🐶')).longPress();
await PostOptions.toBeVisible();
// * Verify emojis exist
await expect(element(by.text('🦊'))).toExist();
await expect(element(by.text('🐶'))).toExist();
// # Open AddReaction screen
await AddReactionScreen.open();
// * Verify Emojis exist in recently used section
await expect(element(by.text('🦊').withAncestor(by.id('RECENTLY USED')))).toExist();
await expect(element(by.text('🐶').withAncestor(by.id('RECENTLY USED')))).toExist();
// # Close AddReaction Screen
await AddReactionScreen.close();
});
});