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:
parent
170ef360c1
commit
f0598dde54
10 changed files with 147 additions and 15 deletions
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
59
detox/e2e/test/messaging/recent_emojis.e2e.js
Normal file
59
detox/e2e/test/messaging/recent_emojis.e2e.js
Normal 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();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue