diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 319eb0ca1..654ec9502 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -3,6 +3,7 @@
diff --git a/android/gradle.properties b/android/gradle.properties
index 1fd964e90..732c56e3e 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -18,3 +18,4 @@
# org.gradle.parallel=true
android.useDeprecatedNdk=true
+# VERSIONCODE=999999999
diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index b4123946b..4e67c7478 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -55,7 +55,8 @@ export const MESSAGES = createRequestTypes('MESSAGES', [
'TOGGLE_PIN_SUCCESS',
'TOGGLE_PIN_FAILURE',
'SET_INPUT',
- 'CLEAR_INPUT'
+ 'CLEAR_INPUT',
+ 'TOGGLE_REACTION_PICKER'
]);
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [
...defaultTypes,
diff --git a/app/actions/messages.js b/app/actions/messages.js
index 29c1b4ca9..ed9bf4971 100644
--- a/app/actions/messages.js
+++ b/app/actions/messages.js
@@ -176,3 +176,10 @@ export function clearInput() {
type: types.MESSAGES.CLEAR_INPUT
};
}
+
+export function toggleReactionPicker(message) {
+ return {
+ type: types.MESSAGES.TOGGLE_REACTION_PICKER,
+ message
+ };
+}
diff --git a/app/containers/CustomEmoji.js b/app/containers/EmojiPicker/CustomEmoji.js
similarity index 77%
rename from app/containers/CustomEmoji.js
rename to app/containers/EmojiPicker/CustomEmoji.js
index 166e065ab..5988296f3 100644
--- a/app/containers/CustomEmoji.js
+++ b/app/containers/EmojiPicker/CustomEmoji.js
@@ -6,19 +6,21 @@ import { connect } from 'react-redux';
@connect(state => ({
baseUrl: state.settings.Site_Url
}))
-export default class extends React.PureComponent {
+export default class extends React.Component {
static propTypes = {
baseUrl: PropTypes.string.isRequired,
emoji: PropTypes.object.isRequired,
style: PropTypes.object
}
-
+ shouldComponentUpdate() {
+ return false;
+ }
render() {
const { baseUrl, emoji, style } = this.props;
return (
);
}
diff --git a/app/containers/EmojiPicker/EmojiCategory.js b/app/containers/EmojiPicker/EmojiCategory.js
new file mode 100644
index 000000000..0fc3c65d7
--- /dev/null
+++ b/app/containers/EmojiPicker/EmojiCategory.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Text, View, TouchableOpacity, Platform } from 'react-native';
+import { emojify } from 'react-emojione';
+import { responsive } from 'react-native-responsive-ui';
+import styles from './styles';
+import CustomEmoji from './CustomEmoji';
+
+
+const emojisPerRow = Platform.OS === 'ios' ? 8 : 9;
+
+const renderEmoji = (emoji, size) => {
+ if (emoji.isCustom) {
+ return ;
+ }
+ return (
+
+ {emojify(`:${ emoji }:`, { output: 'unicode' })}
+
+ );
+};
+
+
+const nextFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
+
+@responsive
+export default class EmojiCategory extends React.Component {
+ static propTypes = {
+ emojis: PropTypes.any,
+ window: PropTypes.any,
+ onEmojiSelected: PropTypes.func,
+ emojisPerRow: PropTypes.number,
+ width: PropTypes.number
+ };
+ constructor(props) {
+ super(props);
+ const { width, height } = this.props.window;
+
+ this.size = Math.min(this.props.width || width, height) / (this.props.emojisPerRow || emojisPerRow);
+ this.emojis = [];
+ }
+ componentWillMount() {
+ this.emojis = this.props.emojis.slice(0, emojisPerRow * 3).map(item => this.renderItem(item, this.size));
+ }
+ async componentDidMount() {
+ const array = this.props.emojis;
+ const temparray = [];
+ let i;
+ let j;
+ const chunk = emojisPerRow * 3;
+ for (i = chunk, j = array.length; i < j; i += chunk) {
+ temparray.push(array.slice(i, i + chunk));
+ }
+ temparray.forEach(async(items) => {
+ await nextFrame();
+ this.emojis = this.emojis.concat(items.map(item => this.renderItem(item, this.size)));
+ this.forceUpdate();
+ await nextFrame();
+ });
+ }
+
+ shouldComponentUpdate() {
+ return false;
+ }
+
+ renderItem(emoji, size) {
+ return (
+ this.props.onEmojiSelected(emoji)}
+ >
+ {renderEmoji(emoji, size)}
+ );
+ }
+
+ render() {
+ return {this.emojis};
+ }
+}
diff --git a/app/containers/MessageBox/EmojiPicker/TabBar.js b/app/containers/EmojiPicker/TabBar.js
similarity index 66%
rename from app/containers/MessageBox/EmojiPicker/TabBar.js
rename to app/containers/EmojiPicker/TabBar.js
index 9bc131a46..fb23b30ea 100644
--- a/app/containers/MessageBox/EmojiPicker/TabBar.js
+++ b/app/containers/EmojiPicker/TabBar.js
@@ -7,15 +7,21 @@ export default class extends React.PureComponent {
static propTypes = {
goToPage: PropTypes.func,
activeTab: PropTypes.number,
- tabs: PropTypes.array
+ tabs: PropTypes.array,
+ tabEmojiStyle: PropTypes.object
}
render() {
return (
{this.props.tabs.map((tab, i) => (
- this.props.goToPage(i)} style={styles.tab}>
- {tab}
+ this.props.goToPage(i)}
+ style={styles.tab}
+ >
+ {tab}
{this.props.activeTab === i ? : }
))}
diff --git a/app/containers/MessageBox/EmojiPicker/categories.js b/app/containers/EmojiPicker/categories.js
similarity index 75%
rename from app/containers/MessageBox/EmojiPicker/categories.js
rename to app/containers/EmojiPicker/categories.js
index 341c6a83e..a95f67cf6 100644
--- a/app/containers/MessageBox/EmojiPicker/categories.js
+++ b/app/containers/EmojiPicker/categories.js
@@ -1,4 +1,4 @@
-const list = ['Frequently Used', 'Custom', 'Smileys & People', 'Animals & Nature', 'Food & Drink', 'Activities', 'Travel & Places', 'Objects', 'Symbols', 'Flags'];
+const list = ['frequentlyUsed', 'custom', 'people', 'nature', 'food', 'activity', 'travel', 'objects', 'symbols', 'flags'];
const tabs = [
{
tabLabel: '🕒',
diff --git a/app/containers/MessageBox/EmojiPicker/index.js b/app/containers/EmojiPicker/index.js
similarity index 50%
rename from app/containers/MessageBox/EmojiPicker/index.js
rename to app/containers/EmojiPicker/index.js
index fbddf4c78..4209b5cc8 100644
--- a/app/containers/MessageBox/EmojiPicker/index.js
+++ b/app/containers/EmojiPicker/index.js
@@ -1,35 +1,32 @@
-import 'string.fromcodepoint';
-import React, { PureComponent } from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { ScrollView, View } from 'react-native';
+import { ScrollView } from 'react-native';
import ScrollableTabView from 'react-native-scrollable-tab-view';
-import emojiDatasource from 'emoji-datasource/emoji.json';
import _ from 'lodash';
-import { groupBy, orderBy } from 'lodash/collection';
-import { mapValues } from 'lodash/object';
+import { emojify } from 'react-emojione';
import TabBar from './TabBar';
import EmojiCategory from './EmojiCategory';
import styles from './styles';
import categories from './categories';
-import scrollPersistTaps from '../../../utils/scrollPersistTaps';
-import database from '../../../lib/realm';
+import scrollPersistTaps from '../../utils/scrollPersistTaps';
+import database from '../../lib/realm';
+import { emojisByCategory } from '../../emojis';
-const charFromUtf16 = utf16 => String.fromCodePoint(...utf16.split('-').map(u => `0x${ u }`));
-const charFromEmojiObj = obj => charFromUtf16(obj.unified);
+const scrollProps = {
+ keyboardShouldPersistTaps: 'always'
+};
-const filteredEmojis = emojiDatasource.filter(e => parseFloat(e.added_in) < 10.0);
-const groupedAndSorted = groupBy(orderBy(filteredEmojis, 'sort_order'), 'category');
-const emojisByCategory = mapValues(groupedAndSorted, group => group.map(charFromEmojiObj));
-
-export default class extends PureComponent {
+export default class extends Component {
static propTypes = {
- onEmojiSelected: PropTypes.func
+ onEmojiSelected: PropTypes.func,
+ tabEmojiStyle: PropTypes.object,
+ emojisPerRow: PropTypes.number,
+ width: PropTypes.number
};
constructor(props) {
super(props);
this.state = {
- categories: categories.list.slice(0, 1),
frequentlyUsed: [],
customEmojis: []
};
@@ -38,16 +35,22 @@ export default class extends PureComponent {
this.updateFrequentlyUsed = this.updateFrequentlyUsed.bind(this);
this.updateCustomEmojis = this.updateCustomEmojis.bind(this);
}
+ //
+ // shouldComponentUpdate(nextProps) {
+ // return false;
+ // }
componentWillMount() {
this.frequentlyUsed.addListener(this.updateFrequentlyUsed);
this.customEmojis.addListener(this.updateCustomEmojis);
this.updateFrequentlyUsed();
this.updateCustomEmojis();
+ setTimeout(() => this.setState({ show: true }), 100);
}
componentWillUnmount() {
- clearTimeout(this._timeout);
+ this.frequentlyUsed.removeAllListeners();
+ this.customEmojis.removeAllListeners();
}
onEmojiSelected(emoji) {
@@ -58,10 +61,11 @@ export default class extends PureComponent {
});
this.props.onEmojiSelected(`:${ emoji.content }:`);
} else {
- const content = emoji.codePointAt(0).toString();
+ const content = emoji;
const count = this._getFrequentlyUsedCount(content);
this._addFrequentlyUsed({ content, count, isCustom: false });
- this.props.onEmojiSelected(emoji);
+ const shortname = `:${ emoji }:`;
+ this.props.onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
}
}
_addFrequentlyUsed = (emoji) => {
@@ -78,22 +82,17 @@ export default class extends PureComponent {
if (item.isCustom) {
return item;
}
- return String.fromCodePoint(item.content);
+ return emojify(`${ item.content }`, { output: 'unicode' });
});
this.setState({ frequentlyUsed });
}
updateCustomEmojis() {
- const customEmojis = _.map(this.customEmojis.slice(), item => ({ content: item.name, extension: item.extension, isCustom: true }));
+ const customEmojis = _.map(this.customEmojis.slice(), item =>
+ ({ content: item.name, extension: item.extension, isCustom: true }));
this.setState({ customEmojis });
}
- loadNextCategory() {
- if (this.state.categories.length < categories.list.length) {
- this.setState({ categories: categories.list.slice(0, this.state.categories.length + 1) });
- }
- }
-
renderCategory(category, i) {
let emojis = [];
if (i === 0) {
@@ -104,40 +103,39 @@ export default class extends PureComponent {
emojis = emojisByCategory[category];
}
return (
-
- this.onEmojiSelected(emoji)}
- finishedLoading={() => { this._timeout = setTimeout(this.loadNextCategory.bind(this), 100); }}
- />
-
+ this.onEmojiSelected(emoji)}
+ style={styles.categoryContainer}
+ size={this.props.emojisPerRow}
+ width={this.props.width}
+ />
);
}
render() {
- const scrollProps = {
- keyboardShouldPersistTaps: 'always'
- };
+ if (!this.state.show) {
+ return null;
+ }
return (
-
- }
- contentProps={scrollProps}
- >
- {
- _.map(categories.tabs, (tab, i) => (
-
- {this.renderCategory(tab.category, i)}
-
- ))
- }
-
-
+ //
+ }
+ contentProps={scrollProps}
+ >
+ {
+ categories.tabs.map((tab, i) => (
+
+ {this.renderCategory(tab.category, i)}
+
+ ))
+ }
+
+ //
);
}
}
diff --git a/app/containers/MessageBox/EmojiPicker/styles.js b/app/containers/EmojiPicker/styles.js
similarity index 70%
rename from app/containers/MessageBox/EmojiPicker/styles.js
rename to app/containers/EmojiPicker/styles.js
index a4ffdb598..038d11b20 100644
--- a/app/containers/MessageBox/EmojiPicker/styles.js
+++ b/app/containers/EmojiPicker/styles.js
@@ -1,7 +1,4 @@
-import { StyleSheet, Dimensions, Platform } from 'react-native';
-
-const { width } = Dimensions.get('window');
-const EMOJI_SIZE = width / (Platform.OS === 'ios' ? 8 : 9);
+import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
@@ -45,18 +42,16 @@ export default StyleSheet.create({
categoryInner: {
flexWrap: 'wrap',
flexDirection: 'row',
- alignItems: 'center'
+ alignItems: 'center',
+ justifyContent: 'flex-start',
+ flex: 1
},
categoryEmoji: {
- fontSize: EMOJI_SIZE - 14,
color: 'black',
- height: EMOJI_SIZE,
- width: EMOJI_SIZE,
+ backgroundColor: 'transparent',
textAlign: 'center'
},
customCategoryEmoji: {
- height: EMOJI_SIZE - 8,
- width: EMOJI_SIZE - 8,
margin: 4
}
});
diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js
index 56b33fcd5..c7ed5d2c4 100644
--- a/app/containers/MessageActions.js
+++ b/app/containers/MessageActions.js
@@ -13,7 +13,8 @@ import {
permalinkClear,
togglePinRequest,
setInput,
- actionsHide
+ actionsHide,
+ toggleReactionPicker
} from '../actions/messages';
import { showToast } from '../utils/info';
@@ -39,7 +40,8 @@ import { showToast } from '../utils/info';
permalinkRequest: message => dispatch(permalinkRequest(message)),
permalinkClear: () => dispatch(permalinkClear()),
togglePinRequest: message => dispatch(togglePinRequest(message)),
- setInput: message => dispatch(setInput(message))
+ setInput: message => dispatch(setInput(message)),
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
})
)
export default class MessageActions extends React.Component {
@@ -58,6 +60,7 @@ export default class MessageActions extends React.Component {
togglePinRequest: PropTypes.func.isRequired,
setInput: PropTypes.func.isRequired,
permalink: PropTypes.string,
+ toggleReactionPicker: PropTypes.func.isRequired,
Message_AllowDeleting: PropTypes.bool,
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
Message_AllowEditing: PropTypes.bool,
@@ -119,6 +122,11 @@ export default class MessageActions extends React.Component {
this.options.push(actionMessage.pinned ? 'Unpin' : 'Pin');
this.PIN_INDEX = this.options.length - 1;
}
+ // Reaction
+ if (!this.isRoomReadOnly()) {
+ this.options.push('Add Reaction');
+ this.REACTION_INDEX = this.options.length - 1;
+ }
// Delete
if (this.allowDelete(nextProps)) {
this.options.push('Delete');
@@ -275,6 +283,10 @@ export default class MessageActions extends React.Component {
this.props.permalinkRequest(this.props.actionMessage);
}
+ handleReaction() {
+ this.props.toggleReactionPicker(this.props.actionMessage);
+ }
+
handleActionPress = (actionIndex) => {
switch (actionIndex) {
case this.REPLY_INDEX:
@@ -298,6 +310,9 @@ export default class MessageActions extends React.Component {
case this.PIN_INDEX:
this.handlePin();
break;
+ case this.REACTION_INDEX:
+ this.handleReaction();
+ break;
case this.DELETE_INDEX:
this.handleDelete();
break;
diff --git a/app/containers/MessageBox/EmojiPicker/EmojiCategory.js b/app/containers/MessageBox/EmojiPicker/EmojiCategory.js
deleted file mode 100644
index e4c648b1b..000000000
--- a/app/containers/MessageBox/EmojiPicker/EmojiCategory.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
-import styles from './styles';
-import CustomEmoji from '../../CustomEmoji';
-
-export default class extends React.PureComponent {
- static propTypes = {
- emojis: PropTypes.any,
- finishedLoading: PropTypes.func,
- onEmojiSelected: PropTypes.func
- };
-
- componentDidMount() {
- this.props.finishedLoading();
- }
-
- renderEmoji = (emoji) => {
- if (emoji.isCustom) {
- const style = StyleSheet.flatten(styles.customCategoryEmoji);
- return ;
- }
- return (
-
- {emoji}
-
- );
- }
-
- render() {
- const { emojis } = this.props;
- return (
-
-
- {emojis.map(emoji =>
- (
- this.props.onEmojiSelected(emoji)}
- >
- {this.renderEmoji(emoji)}
-
- ))}
-
-
- );
- }
-}
diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js
index 9fd862569..b5f8aa3f2 100644
--- a/app/containers/MessageBox/index.js
+++ b/app/containers/MessageBox/index.js
@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { View, TextInput, SafeAreaView, FlatList, Text, TouchableOpacity, Keyboard } from 'react-native';
+import { View, TextInput, SafeAreaView, FlatList, Text, TouchableOpacity, Keyboard, StyleSheet } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ImagePicker from 'react-native-image-picker';
import { connect } from 'react-redux';
+import { emojify } from 'react-emojione';
import { userTyping } from '../../actions/room';
import RocketChat from '../../lib/rocketchat';
import { editRequest, editCancel, clearInput } from '../../actions/messages';
@@ -11,11 +12,14 @@ import styles from './style';
import MyIcon from '../icons';
import database from '../../lib/realm';
import Avatar from '../Avatar';
+import CustomEmoji from '../EmojiPicker/CustomEmoji';
import AnimatedContainer from './AnimatedContainer';
-import EmojiPicker from './EmojiPicker';
+import EmojiPicker from '../EmojiPicker';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
+import { emojis } from '../../emojis';
const MENTIONS_TRACKING_TYPE_USERS = '@';
+const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
const onlyUnique = function onlyUnique(value, index, self) {
return self.indexOf(({ _id }) => value._id === _id) === index;
@@ -54,10 +58,13 @@ export default class MessageBox extends React.PureComponent {
text: '',
mentions: [],
showMentionsContainer: false,
- showEmojiContainer: false
+ showEmojiContainer: false,
+ trackingType: ''
};
this.users = [];
this.rooms = [];
+ this.emojis = [];
+ this.customEmojis = [];
}
componentWillReceiveProps(nextProps) {
if (this.props.message !== nextProps.message && nextProps.message.msg) {
@@ -80,7 +87,7 @@ export default class MessageBox extends React.PureComponent {
const lastNativeText = this.component._lastNativeText;
- const regexp = /(#|@)([a-z._-]+)$/im;
+ const regexp = /(#|@|:)([a-z0-9._-]+)$/im;
const result = lastNativeText.substr(0, cursor).match(regexp);
@@ -176,7 +183,10 @@ export default class MessageBox extends React.PureComponent {
this.setState({ text: '' });
}
async openEmoji() {
- await this.setState({ showEmojiContainer: !this.state.showEmojiContainer });
+ await this.setState({
+ showEmojiContainer: true,
+ showMentionsContainer: false
+ });
Keyboard.dismiss();
}
closeEmoji() {
@@ -292,25 +302,41 @@ export default class MessageBox extends React.PureComponent {
}
}
+ _getEmojis(keyword) {
+ if (keyword) {
+ this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, 4);
+ this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, 4);
+ const mergedEmojis = [...this.customEmojis, ...this.emojis];
+ this.setState({ mentions: mergedEmojis });
+ }
+ }
+
stopTrackingMention() {
this.setState({
showMentionsContainer: false,
- mentions: []
+ mentions: [],
+ trackingType: ''
});
this.users = [];
this.rooms = [];
+ this.customEmojis = [];
+ this.emojis = [];
}
identifyMentionKeyword(keyword, type) {
- this.updateMentions(keyword, type);
this.setState({
- showMentionsContainer: true
+ showMentionsContainer: true,
+ showEmojiContainer: false,
+ trackingType: type
});
+ this.updateMentions(keyword, type);
}
updateMentions = (keyword, type) => {
if (type === MENTIONS_TRACKING_TYPE_USERS) {
this._getUsers(keyword);
+ } else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
+ this._getEmojis(keyword);
} else {
this._getRooms(keyword);
}
@@ -323,10 +349,12 @@ export default class MessageBox extends React.PureComponent {
const cursor = Math.max(start, end);
- const regexp = /([a-z._-]+)$/im;
+ const regexp = /([a-z0-9._-]+)$/im;
const result = msg.substr(0, cursor).replace(regexp, '');
- const text = `${ result }${ item.username || item.name } ${ msg.slice(cursor) }`;
+ const mentionName = this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
+ `${ item.name || item }:` : (item.username || item.name);
+ const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
this.component.setNativeProps({ text });
this.setState({ text });
this.component.focus();
@@ -357,6 +385,26 @@ export default class MessageBox extends React.PureComponent {
Notify {item.desc} in this room
)
+ renderMentionEmoji = (item) => {
+ if (item.name) {
+ return (
+
+ );
+ }
+ return (
+
+ {emojify(`:${ item }:`, { output: 'unicode' })}
+
+ );
+ }
renderMentionItem = (item) => {
if (item.username === 'all' || item.username === 'here') {
return this.renderFixedMentionItem(item);
@@ -366,13 +414,22 @@ export default class MessageBox extends React.PureComponent {
style={styles.mentionItem}
onPress={() => this._onPressMention(item)}
>
-
- {item.username || item.name }
+ {this.state.trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ?
+ [
+ this.renderMentionEmoji(item),
+ :{ item.name || item }:
+ ]
+ : [
+ ,
+ { item.username || item.name }
+ ]
+ }
);
}
@@ -386,17 +443,17 @@ export default class MessageBox extends React.PureComponent {
return ;
}
renderMentions() {
- const usersList = (
+ const list = (
this.renderMentionItem(item)}
- keyExtractor={item => item._id}
+ keyExtractor={item => item._id || item}
{...scrollPersistTaps}
/>
);
const { showMentionsContainer, messageboxHeight } = this.state;
- return ;
+ return ;
}
render() {
return (
diff --git a/app/containers/MessageBox/style.js b/app/containers/MessageBox/style.js
index 298a69ee6..98395ce4f 100644
--- a/app/containers/MessageBox/style.js
+++ b/app/containers/MessageBox/style.js
@@ -1,4 +1,4 @@
-import { StyleSheet } from 'react-native';
+import { StyleSheet, Platform } from 'react-native';
const MENTION_HEIGHT = 50;
@@ -83,6 +83,17 @@ export default StyleSheet.create({
borderTopWidth: 1,
backgroundColor: '#fff'
},
+ mentionItemCustomEmoji: {
+ margin: 8,
+ width: 30,
+ height: 30
+ },
+ mentionItemEmoji: {
+ width: 46,
+ height: 36,
+ fontSize: Platform.OS === 'ios' ? 30 : 25,
+ textAlign: 'center'
+ },
fixedMentionAvatar: {
fontWeight: 'bold',
textAlign: 'center',
diff --git a/app/containers/message/Emoji.js b/app/containers/message/Emoji.js
new file mode 100644
index 000000000..95c0489a9
--- /dev/null
+++ b/app/containers/message/Emoji.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Text } from 'react-native';
+import PropTypes from 'prop-types';
+import { emojify } from 'react-emojione';
+import CustomEmoji from '../EmojiPicker/CustomEmoji';
+
+export default class Emoji extends React.PureComponent {
+ static propTypes = {
+ content: PropTypes.string,
+ standardEmojiStyle: PropTypes.object,
+ customEmojiStyle: PropTypes.object,
+ customEmojis: PropTypes.object.isRequired
+ };
+ render() {
+ const {
+ content, standardEmojiStyle, customEmojiStyle, customEmojis
+ } = this.props;
+ const parsedContent = content.replace(/^:|:$/g, '');
+ const emojiExtension = customEmojis[parsedContent];
+ if (emojiExtension) {
+ const emoji = { extension: emojiExtension, content: parsedContent };
+ return ;
+ }
+ return { emojify(`${ content }`, { output: 'unicode' }) };
+ }
+}
diff --git a/app/containers/message/Markdown.js b/app/containers/message/Markdown.js
index 3fab10a45..14b82f6d7 100644
--- a/app/containers/message/Markdown.js
+++ b/app/containers/message/Markdown.js
@@ -5,7 +5,7 @@ import EasyMarkdown from 'react-native-easy-markdown'; // eslint-disable-line
import SimpleMarkdown from 'simple-markdown';
import { emojify } from 'react-emojione';
import styles from './styles';
-import CustomEmoji from '../CustomEmoji';
+import CustomEmoji from '../EmojiPicker/CustomEmoji';
const BlockCode = ({ node, state }) => (
{
+ const count = item.usernames.length;
+ let usernames = item.usernames.slice(0, 3)
+ .map(username => (username.value === this.props.user.username ? 'you' : username.value)).join(', ');
+ if (count > 3) {
+ usernames = `${ usernames } and more ${ count - 3 }`;
+ } else {
+ usernames = usernames.replace(/,(?=[^,]*$)/, ' and');
+ }
+ return (
+
+
+
+
+
+
+ {count === 1 ? '1 person' : `${ count } people`} reacted
+
+ { usernames }
+
+
+ );
+ }
+
+ render() {
+ const {
+ isVisible, onClose, reactions
+ } = this.props;
+ return (
+
+
+
+
+ Reactions
+
+
+
+ this.renderItem(item)}
+ keyExtractor={item => item.emoji}
+ />
+
+
+ );
+ }
+}
diff --git a/app/containers/message/Url.js b/app/containers/message/Url.js
index 6fe6cdddb..5ec48a999 100644
--- a/app/containers/message/Url.js
+++ b/app/containers/message/Url.js
@@ -52,10 +52,13 @@ const Url = ({ url }) => {
return (
onPress(url.url)} style={styles.button}>
-
+ {url.image ?
+
+ : null
+ }
{url.title}
{url.description}
diff --git a/app/containers/message/index.js b/app/containers/message/index.js
index f57bb5e14..840cfa0b2 100644
--- a/app/containers/message/index.js
+++ b/app/containers/message/index.js
@@ -1,11 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { View, TouchableHighlight, Text, TouchableOpacity, Animated, Keyboard } from 'react-native';
+import { View, TouchableHighlight, Text, TouchableOpacity, Animated, Keyboard, StyleSheet, Vibration } from 'react-native';
import { connect } from 'react-redux';
import Icon from 'react-native-vector-icons/MaterialIcons';
import moment from 'moment';
+import equal from 'deep-equal';
-import { actionsShow, errorActionsShow } from '../../actions/messages';
+import { actionsShow, errorActionsShow, toggleReactionPicker } from '../../actions/messages';
import Image from './Image';
import User from './User';
import Avatar from '../Avatar';
@@ -14,6 +15,8 @@ import Video from './Video';
import Markdown from './Markdown';
import Url from './Url';
import Reply from './Reply';
+import ReactionsModal from './ReactionsModal';
+import Emoji from './Emoji';
import messageStatus from '../../constants/messagesStatus';
import styles from './styles';
@@ -26,11 +29,13 @@ const flex = { flexDirection: 'row', flex: 1 };
customEmojis: state.customEmojis
}), dispatch => ({
actionsShow: actionMessage => dispatch(actionsShow(actionMessage)),
- errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage))
+ errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)),
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
}))
export default class Message extends React.Component {
static propTypes = {
item: PropTypes.object.isRequired,
+ reactions: PropTypes.object.isRequired,
baseUrl: PropTypes.string.isRequired,
Message_TimeFormat: PropTypes.string.isRequired,
message: PropTypes.object.isRequired,
@@ -39,7 +44,15 @@ export default class Message extends React.Component {
actionsShow: PropTypes.func,
errorActionsShow: PropTypes.func,
animate: PropTypes.bool,
- customEmojis: PropTypes.object
+ customEmojis: PropTypes.object,
+ toggleReactionPicker: PropTypes.func,
+ onReactionPress: PropTypes.func
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = { reactionsModal: false };
+ this.onClose = this.onClose.bind(this);
}
componentWillMount() {
@@ -60,7 +73,13 @@ export default class Message extends React.Component {
}
}
- shouldComponentUpdate(nextProps) {
+ shouldComponentUpdate(nextProps, nextState) {
+ if (!equal(this.props.reactions, nextProps.reactions)) {
+ return true;
+ }
+ if (this.state.reactionsModal !== nextState.reactionsModal) {
+ return true;
+ }
return this.props.item._updatedAt.toGMTString() !== nextProps.item._updatedAt.toGMTString() || this.props.item.status !== nextProps.item.status;
}
@@ -69,13 +88,22 @@ export default class Message extends React.Component {
}
onLongPress() {
- const { item } = this.props;
- this.props.actionsShow(JSON.parse(JSON.stringify(item)));
+ this.props.actionsShow(this.parseMessage());
}
onErrorPress() {
- const { item } = this.props;
- this.props.errorActionsShow(JSON.parse(JSON.stringify(item)));
+ this.props.errorActionsShow(this.parseMessage());
+ }
+
+ onReactionPress(emoji) {
+ this.props.onReactionPress(emoji, this.props.item._id);
+ }
+ onClose() {
+ this.setState({ reactionsModal: false });
+ }
+ onReactionLongPress() {
+ this.setState({ reactionsModal: true });
+ Vibration.vibrate(50);
}
getInfoMessage() {
@@ -105,11 +133,12 @@ export default class Message extends React.Component {
return message;
}
+ parseMessage = () => JSON.parse(JSON.stringify(this.props.item));
+
isInfoMessage() {
return ['r', 'au', 'ru', 'ul', 'uj', 'rm', 'user-muted', 'user-unmuted', 'message_pinned'].includes(this.props.item.t);
}
-
isDeleted() {
return this.props.item.t === 'rm';
}
@@ -165,9 +194,50 @@ export default class Message extends React.Component {
);
}
+ renderReaction(reaction) {
+ const reacted = reaction.usernames.findIndex(item => item.value === this.props.user.username) !== -1;
+ const reactedContainerStyle = reacted ? { borderColor: '#bde1fe', backgroundColor: '#f3f9ff' } : {};
+ const reactedCount = reacted ? { color: '#4fb0fc' } : {};
+ return (
+ this.onReactionPress(reaction.emoji)}
+ onLongPress={() => this.onReactionLongPress()}
+ key={reaction.emoji}
+ >
+
+
+ { reaction.usernames.length }
+
+
+ );
+ }
+
+ renderReactions() {
+ if (this.props.item.reactions.length === 0) {
+ return null;
+ }
+ return (
+
+ {this.props.item.reactions.map(reaction => this.renderReaction(reaction))}
+ this.props.toggleReactionPicker(this.parseMessage())}
+ key='add-reaction'
+ style={styles.reactionContainer}
+ >
+
+
+
+ );
+ }
+
render() {
const {
- item, message, editing, baseUrl
+ item, message, editing, baseUrl, customEmojis
} = this.props;
const marginLeft = this._visibility.interpolate({
@@ -181,7 +251,7 @@ export default class Message extends React.Component {
const username = item.alias || item.u.username;
const isEditing = message._id === item._id && editing;
- const accessibilityLabel = `Message from ${ item.alias || item.u.username } at ${ moment(item.ts).format(this.props.Message_TimeFormat) }, ${ this.props.item.msg }`;
+ const accessibilityLabel = `Message from ${ username } at ${ moment(item.ts).format(this.props.Message_TimeFormat) }, ${ this.props.item.msg }`;
return (
+ {this.state.reactionsModal ?
+
+ : null
+ }
);
diff --git a/app/containers/message/styles.js b/app/containers/message/styles.js
index 6236adf06..7752dea76 100644
--- a/app/containers/message/styles.js
+++ b/app/containers/message/styles.js
@@ -33,5 +33,35 @@ export default StyleSheet.create({
borderWidth: 1,
borderRadius: 5,
padding: 5
+ },
+ reactionsContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap'
+ },
+ reactionContainer: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ padding: 3,
+ borderWidth: 1,
+ borderColor: '#cccccc',
+ borderRadius: 4,
+ marginRight: 5,
+ marginBottom: 5,
+ height: 23,
+ width: 35
+ },
+ reactionCount: {
+ fontSize: 12,
+ marginLeft: 2,
+ fontWeight: '600',
+ color: '#aaaaaa'
+ },
+ reactionEmoji: {
+ fontSize: 12
+ },
+ reactionCustomEmoji: {
+ width: 15,
+ height: 15
}
});
diff --git a/app/emojis.js b/app/emojis.js
new file mode 100644
index 000000000..b7bff1957
--- /dev/null
+++ b/app/emojis.js
@@ -0,0 +1,2833 @@
+export const emojisByCategory = {
+ people: [
+ 'grinning',
+ 'grimacing',
+ 'grin',
+ 'joy',
+ 'smiley',
+ 'smile',
+ 'sweat_smile',
+ 'laughing',
+ 'innocent',
+ 'wink',
+ 'blush',
+ 'slight_smile',
+ 'upside_down',
+ 'relaxed',
+ 'yum',
+ 'relieved',
+ 'heart_eyes',
+ 'kissing_heart',
+ 'kissing',
+ 'kissing_smiling_eyes',
+ 'kissing_closed_eyes',
+ 'stuck_out_tongue_winking_eye',
+ 'stuck_out_tongue_closed_eyes',
+ 'stuck_out_tongue',
+ 'money_mouth',
+ 'nerd',
+ 'sunglasses',
+ 'hugging',
+ 'smirk',
+ 'no_mouth',
+ 'neutral_face',
+ 'expressionless',
+ 'unamused',
+ 'rolling_eyes',
+ 'thinking',
+ 'flushed',
+ 'disappointed',
+ 'worried',
+ 'angry',
+ 'rage',
+ 'pensive',
+ 'confused',
+ 'slight_frown',
+ 'frowning2',
+ 'persevere',
+ 'confounded',
+ 'tired_face',
+ 'weary',
+ 'triumph',
+ 'open_mouth',
+ 'scream',
+ 'fearful',
+ 'cold_sweat',
+ 'hushed',
+ 'frowning',
+ 'anguished',
+ 'cry',
+ 'disappointed_relieved',
+ 'sleepy',
+ 'sweat',
+ 'sob',
+ 'dizzy_face',
+ 'astonished',
+ 'zipper_mouth',
+ 'mask',
+ 'thermometer_face',
+ 'head_bandage',
+ 'sleeping',
+ 'zzz',
+ 'poop',
+ 'smiling_imp',
+ 'imp',
+ 'japanese_ogre',
+ 'japanese_goblin',
+ 'skull',
+ 'ghost',
+ 'alien',
+ 'robot',
+ 'smiley_cat',
+ 'smile_cat',
+ 'joy_cat',
+ 'heart_eyes_cat',
+ 'smirk_cat',
+ 'kissing_cat',
+ 'scream_cat',
+ 'crying_cat_face',
+ 'pouting_cat',
+ 'raised_hands',
+ 'clap',
+ 'wave',
+ 'thumbsup',
+ 'thumbsdown',
+ 'punch',
+ 'fist',
+ 'v',
+ 'ok_hand',
+ 'raised_hand',
+ 'open_hands',
+ 'muscle',
+ 'pray',
+ 'point_up',
+ 'point_up_2',
+ 'point_down',
+ 'point_left',
+ 'point_right',
+ 'middle_finger',
+ 'hand_splayed',
+ 'metal',
+ 'vulcan',
+ 'writing_hand',
+ 'nail_care',
+ 'lips',
+ 'tongue',
+ 'ear',
+ 'nose',
+ 'eye',
+ 'eyes',
+ 'bust_in_silhouette',
+ 'busts_in_silhouette',
+ 'speaking_head',
+ 'baby',
+ 'boy',
+ 'girl',
+ 'man',
+ 'woman',
+ 'person_with_blond_hair',
+ 'older_man',
+ 'older_woman',
+ 'man_with_gua_pi_mao',
+ 'man_with_turban',
+ 'cop',
+ 'construction_worker',
+ 'guardsman',
+ 'spy',
+ 'santa',
+ 'angel',
+ 'princess',
+ 'bride_with_veil',
+ 'walking',
+ 'runner',
+ 'dancer',
+ 'dancers',
+ 'couple',
+ 'two_men_holding_hands',
+ 'two_women_holding_hands',
+ 'bow',
+ 'information_desk_person',
+ 'no_good',
+ 'ok_woman',
+ 'raising_hand',
+ 'person_with_pouting_face',
+ 'person_frowning',
+ 'haircut',
+ 'massage',
+ 'couple_with_heart',
+ 'couple_ww',
+ 'couple_mm',
+ 'couplekiss',
+ 'kiss_ww',
+ 'kiss_mm',
+ 'family',
+ 'family_mwg',
+ 'family_mwgb',
+ 'family_mwbb',
+ 'family_mwgg',
+ 'family_wwb',
+ 'family_wwg',
+ 'family_wwgb',
+ 'family_wwbb',
+ 'family_wwgg',
+ 'family_mmb',
+ 'family_mmg',
+ 'family_mmgb',
+ 'family_mmbb',
+ 'family_mmgg',
+ 'womans_clothes',
+ 'shirt',
+ 'jeans',
+ 'necktie',
+ 'dress',
+ 'bikini',
+ 'kimono',
+ 'lipstick',
+ 'kiss',
+ 'footprints',
+ 'high_heel',
+ 'sandal',
+ 'boot',
+ 'mans_shoe',
+ 'athletic_shoe',
+ 'womans_hat',
+ 'tophat',
+ 'helmet_with_cross',
+ 'mortar_board',
+ 'crown',
+ 'school_satchel',
+ 'pouch',
+ 'purse',
+ 'handbag',
+ 'briefcase',
+ 'eyeglasses',
+ 'dark_sunglasses',
+ 'ring',
+ 'closed_umbrella',
+ 'cowboy',
+ 'clown',
+ 'nauseated_face',
+ 'rofl',
+ 'drooling_face',
+ 'lying_face',
+ 'sneezing_face',
+ 'prince',
+ 'man_in_tuxedo',
+ 'mrs_claus',
+ 'face_palm',
+ 'shrug',
+ 'selfie',
+ 'man_dancing',
+ 'call_me',
+ 'raised_back_of_hand',
+ 'left_facing_fist',
+ 'right_facing_fist',
+ 'handshake',
+ 'fingers_crossed',
+ 'pregnant_woman'
+ ],
+ nature: [
+ 'dog',
+ 'cat',
+ 'mouse',
+ 'hamster',
+ 'rabbit',
+ 'bear',
+ 'panda_face',
+ 'koala',
+ 'tiger',
+ 'lion_face',
+ 'cow',
+ 'pig',
+ 'pig_nose',
+ 'frog',
+ 'octopus',
+ 'monkey_face',
+ 'see_no_evil',
+ 'hear_no_evil',
+ 'speak_no_evil',
+ 'monkey',
+ 'chicken',
+ 'penguin',
+ 'bird',
+ 'baby_chick',
+ 'hatching_chick',
+ 'hatched_chick',
+ 'wolf',
+ 'boar',
+ 'horse',
+ 'unicorn',
+ 'bee',
+ 'bug',
+ 'snail',
+ 'beetle',
+ 'ant',
+ 'spider',
+ 'scorpion',
+ 'crab',
+ 'snake',
+ 'turtle',
+ 'tropical_fish',
+ 'fish',
+ 'blowfish',
+ 'dolphin',
+ 'whale',
+ 'whale2',
+ 'crocodile',
+ 'leopard',
+ 'tiger2',
+ 'water_buffalo',
+ 'ox',
+ 'cow2',
+ 'dromedary_camel',
+ 'camel',
+ 'elephant',
+ 'goat',
+ 'ram',
+ 'sheep',
+ 'racehorse',
+ 'pig2',
+ 'rat',
+ 'mouse2',
+ 'rooster',
+ 'turkey',
+ 'dove',
+ 'dog2',
+ 'poodle',
+ 'cat2',
+ 'rabbit2',
+ 'chipmunk',
+ 'feet',
+ 'dragon',
+ 'dragon_face',
+ 'cactus',
+ 'christmas_tree',
+ 'evergreen_tree',
+ 'deciduous_tree',
+ 'palm_tree',
+ 'seedling',
+ 'herb',
+ 'shamrock',
+ 'four_leaf_clover',
+ 'bamboo',
+ 'tanabata_tree',
+ 'leaves',
+ 'fallen_leaf',
+ 'maple_leaf',
+ 'ear_of_rice',
+ 'hibiscus',
+ 'sunflower',
+ 'rose',
+ 'tulip',
+ 'blossom',
+ 'cherry_blossom',
+ 'bouquet',
+ 'mushroom',
+ 'chestnut',
+ 'jack_o_lantern',
+ 'shell',
+ 'spider_web',
+ 'earth_americas',
+ 'earth_africa',
+ 'earth_asia',
+ 'full_moon',
+ 'waning_gibbous_moon',
+ 'last_quarter_moon',
+ 'waning_crescent_moon',
+ 'new_moon',
+ 'waxing_crescent_moon',
+ 'first_quarter_moon',
+ 'waxing_gibbous_moon',
+ 'new_moon_with_face',
+ 'full_moon_with_face',
+ 'first_quarter_moon_with_face',
+ 'last_quarter_moon_with_face',
+ 'sun_with_face',
+ 'crescent_moon',
+ 'star',
+ 'star2',
+ 'dizzy',
+ 'sparkles',
+ 'comet',
+ 'sunny',
+ 'white_sun_small_cloud',
+ 'partly_sunny',
+ 'white_sun_cloud',
+ 'white_sun_rain_cloud',
+ 'cloud',
+ 'cloud_rain',
+ 'thunder_cloud_rain',
+ 'cloud_lightning',
+ 'zap',
+ 'fire',
+ 'boom',
+ 'snowflake',
+ 'cloud_snow',
+ 'snowman2',
+ 'snowman',
+ 'wind_blowing_face',
+ 'dash',
+ 'cloud_tornado',
+ 'fog',
+ 'umbrella2',
+ 'umbrella',
+ 'droplet',
+ 'sweat_drops',
+ 'ocean',
+ 'eagle',
+ 'duck',
+ 'bat',
+ 'shark',
+ 'owl',
+ 'fox',
+ 'butterfly',
+ 'deer',
+ 'gorilla',
+ 'lizard',
+ 'rhino',
+ 'wilted_rose',
+ 'shrimp',
+ 'squid'
+ ],
+ food: [
+ 'green_apple',
+ 'apple',
+ 'pear',
+ 'tangerine',
+ 'lemon',
+ 'banana',
+ 'watermelon',
+ 'grapes',
+ 'strawberry',
+ 'melon',
+ 'cherries',
+ 'peach',
+ 'pineapple',
+ 'tomato',
+ 'eggplant',
+ 'hot_pepper',
+ 'corn',
+ 'sweet_potato',
+ 'honey_pot',
+ 'bread',
+ 'cheese',
+ 'poultry_leg',
+ 'meat_on_bone',
+ 'fried_shrimp',
+ 'cooking',
+ 'hamburger',
+ 'fries',
+ 'hotdog',
+ 'pizza',
+ 'spaghetti',
+ 'taco',
+ 'burrito',
+ 'ramen',
+ 'stew',
+ 'fish_cake',
+ 'sushi',
+ 'bento',
+ 'curry',
+ 'rice_ball',
+ 'rice',
+ 'rice_cracker',
+ 'oden',
+ 'dango',
+ 'shaved_ice',
+ 'ice_cream',
+ 'icecream',
+ 'cake',
+ 'birthday',
+ 'custard',
+ 'candy',
+ 'lollipop',
+ 'chocolate_bar',
+ 'popcorn',
+ 'doughnut',
+ 'cookie',
+ 'beer',
+ 'beers',
+ 'wine_glass',
+ 'cocktail',
+ 'tropical_drink',
+ 'champagne',
+ 'sake',
+ 'tea',
+ 'coffee',
+ 'baby_bottle',
+ 'fork_and_knife',
+ 'fork_knife_plate',
+ 'croissant',
+ 'avocado',
+ 'cucumber',
+ 'bacon',
+ 'potato',
+ 'carrot',
+ 'french_bread',
+ 'salad',
+ 'shallow_pan_of_food',
+ 'stuffed_flatbread',
+ 'champagne_glass',
+ 'tumbler_glass',
+ 'spoon',
+ 'egg',
+ 'milk',
+ 'peanuts',
+ 'kiwi',
+ 'pancakes'
+ ],
+ activity: [
+ 'soccer',
+ 'basketball',
+ 'football',
+ 'baseball',
+ 'tennis',
+ 'volleyball',
+ 'rugby_football',
+ '8ball',
+ 'golf',
+ 'golfer',
+ 'ping_pong',
+ 'badminton',
+ 'hockey',
+ 'field_hockey',
+ 'cricket',
+ 'ski',
+ 'skier',
+ 'snowboarder',
+ 'ice_skate',
+ 'bow_and_arrow',
+ 'fishing_pole_and_fish',
+ 'rowboat',
+ 'swimmer',
+ 'surfer',
+ 'bath',
+ 'basketball_player',
+ 'lifter',
+ 'bicyclist',
+ 'mountain_bicyclist',
+ 'horse_racing',
+ 'levitate',
+ 'trophy',
+ 'running_shirt_with_sash',
+ 'medal',
+ 'military_medal',
+ 'reminder_ribbon',
+ 'rosette',
+ 'ticket',
+ 'tickets',
+ 'performing_arts',
+ 'art',
+ 'circus_tent',
+ 'microphone',
+ 'headphones',
+ 'musical_score',
+ 'musical_keyboard',
+ 'saxophone',
+ 'trumpet',
+ 'guitar',
+ 'violin',
+ 'clapper',
+ 'video_game',
+ 'space_invader',
+ 'dart',
+ 'game_die',
+ 'slot_machine',
+ 'bowling',
+ 'cartwheel',
+ 'juggling',
+ 'wrestlers',
+ 'boxing_glove',
+ 'martial_arts_uniform',
+ 'water_polo',
+ 'handball',
+ 'goal',
+ 'fencer',
+ 'first_place',
+ 'second_place',
+ 'third_place',
+ 'drum'
+ ],
+ travel: [
+ 'red_car',
+ 'taxi',
+ 'blue_car',
+ 'bus',
+ 'trolleybus',
+ 'race_car',
+ 'police_car',
+ 'ambulance',
+ 'fire_engine',
+ 'minibus',
+ 'truck',
+ 'articulated_lorry',
+ 'tractor',
+ 'motorcycle',
+ 'bike',
+ 'rotating_light',
+ 'oncoming_police_car',
+ 'oncoming_bus',
+ 'oncoming_automobile',
+ 'oncoming_taxi',
+ 'aerial_tramway',
+ 'mountain_cableway',
+ 'suspension_railway',
+ 'railway_car',
+ 'train',
+ 'monorail',
+ 'bullettrain_side',
+ 'bullettrain_front',
+ 'light_rail',
+ 'mountain_railway',
+ 'steam_locomotive',
+ 'train2',
+ 'metro',
+ 'tram',
+ 'station',
+ 'helicopter',
+ 'airplane_small',
+ 'airplane',
+ 'airplane_departure',
+ 'airplane_arriving',
+ 'sailboat',
+ 'motorboat',
+ 'speedboat',
+ 'ferry',
+ 'cruise_ship',
+ 'rocket',
+ 'satellite_orbital',
+ 'seat',
+ 'anchor',
+ 'construction',
+ 'fuelpump',
+ 'busstop',
+ 'vertical_traffic_light',
+ 'traffic_light',
+ 'checkered_flag',
+ 'ship',
+ 'ferris_wheel',
+ 'roller_coaster',
+ 'carousel_horse',
+ 'construction_site',
+ 'foggy',
+ 'tokyo_tower',
+ 'factory',
+ 'fountain',
+ 'rice_scene',
+ 'mountain',
+ 'mountain_snow',
+ 'mount_fuji',
+ 'volcano',
+ 'japan',
+ 'camping',
+ 'tent',
+ 'park',
+ 'motorway',
+ 'railway_track',
+ 'sunrise',
+ 'sunrise_over_mountains',
+ 'desert',
+ 'beach',
+ 'island',
+ 'city_sunset',
+ 'city_dusk',
+ 'cityscape',
+ 'night_with_stars',
+ 'bridge_at_night',
+ 'milky_way',
+ 'stars',
+ 'sparkler',
+ 'fireworks',
+ 'rainbow',
+ 'homes',
+ 'european_castle',
+ 'japanese_castle',
+ 'stadium',
+ 'statue_of_liberty',
+ 'house',
+ 'house_with_garden',
+ 'house_abandoned',
+ 'office',
+ 'department_store',
+ 'post_office',
+ 'european_post_office',
+ 'hospital',
+ 'bank',
+ 'hotel',
+ 'convenience_store',
+ 'school',
+ 'love_hotel',
+ 'wedding',
+ 'classical_building',
+ 'church',
+ 'mosque',
+ 'synagogue',
+ 'kaaba',
+ 'shinto_shrine',
+ 'shopping_cart',
+ 'scooter',
+ 'motor_scooter',
+ 'canoe'
+ ],
+ objects: [
+ 'watch',
+ 'iphone',
+ 'calling',
+ 'computer',
+ 'keyboard',
+ 'desktop',
+ 'printer',
+ 'mouse_three_button',
+ 'trackball',
+ 'joystick',
+ 'compression',
+ 'minidisc',
+ 'floppy_disk',
+ 'cd',
+ 'dvd',
+ 'vhs',
+ 'camera',
+ 'camera_with_flash',
+ 'video_camera',
+ 'movie_camera',
+ 'projector',
+ 'film_frames',
+ 'telephone_receiver',
+ 'telephone',
+ 'pager',
+ 'fax',
+ 'tv',
+ 'radio',
+ 'microphone2',
+ 'level_slider',
+ 'control_knobs',
+ 'stopwatch',
+ 'timer',
+ 'alarm_clock',
+ 'clock',
+ 'hourglass_flowing_sand',
+ 'hourglass',
+ 'satellite',
+ 'battery',
+ 'electric_plug',
+ 'bulb',
+ 'flashlight',
+ 'candle',
+ 'wastebasket',
+ 'oil',
+ 'money_with_wings',
+ 'dollar',
+ 'yen',
+ 'euro',
+ 'pound',
+ 'moneybag',
+ 'credit_card',
+ 'gem',
+ 'scales',
+ 'wrench',
+ 'hammer',
+ 'hammer_pick',
+ 'tools',
+ 'pick',
+ 'nut_and_bolt',
+ 'gear',
+ 'chains',
+ 'gun',
+ 'bomb',
+ 'knife',
+ 'dagger',
+ 'crossed_swords',
+ 'shield',
+ 'smoking',
+ 'skull_crossbones',
+ 'coffin',
+ 'urn',
+ 'amphora',
+ 'crystal_ball',
+ 'prayer_beads',
+ 'barber',
+ 'alembic',
+ 'telescope',
+ 'microscope',
+ 'hole',
+ 'pill',
+ 'syringe',
+ 'thermometer',
+ 'label',
+ 'bookmark',
+ 'toilet',
+ 'shower',
+ 'bathtub',
+ 'key',
+ 'key2',
+ 'couch',
+ 'sleeping_accommodation',
+ 'bed',
+ 'door',
+ 'bellhop',
+ 'frame_photo',
+ 'map',
+ 'beach_umbrella',
+ 'moyai',
+ 'shopping_bags',
+ 'balloon',
+ 'flags',
+ 'ribbon',
+ 'gift',
+ 'confetti_ball',
+ 'tada',
+ 'dolls',
+ 'wind_chime',
+ 'crossed_flags',
+ 'izakaya_lantern',
+ 'envelope',
+ 'envelope_with_arrow',
+ 'incoming_envelope',
+ 'e-mail',
+ 'love_letter',
+ 'postbox',
+ 'mailbox_closed',
+ 'mailbox',
+ 'mailbox_with_mail',
+ 'mailbox_with_no_mail',
+ 'package',
+ 'postal_horn',
+ 'inbox_tray',
+ 'outbox_tray',
+ 'scroll',
+ 'page_with_curl',
+ 'bookmark_tabs',
+ 'bar_chart',
+ 'chart_with_upwards_trend',
+ 'chart_with_downwards_trend',
+ 'page_facing_up',
+ 'date',
+ 'calendar',
+ 'calendar_spiral',
+ 'card_index',
+ 'card_box',
+ 'ballot_box',
+ 'file_cabinet',
+ 'clipboard',
+ 'notepad_spiral',
+ 'file_folder',
+ 'open_file_folder',
+ 'dividers',
+ 'newspaper2',
+ 'newspaper',
+ 'notebook',
+ 'closed_book',
+ 'green_book',
+ 'blue_book',
+ 'orange_book',
+ 'notebook_with_decorative_cover',
+ 'ledger',
+ 'books',
+ 'book',
+ 'link',
+ 'paperclip',
+ 'paperclips',
+ 'scissors',
+ 'triangular_ruler',
+ 'straight_ruler',
+ 'pushpin',
+ 'round_pushpin',
+ 'triangular_flag_on_post',
+ 'flag_white',
+ 'flag_black',
+ 'closed_lock_with_key',
+ 'lock',
+ 'unlock',
+ 'lock_with_ink_pen',
+ 'pen_ballpoint',
+ 'pen_fountain',
+ 'black_nib',
+ 'pencil',
+ 'pencil2',
+ 'crayon',
+ 'paintbrush',
+ 'mag',
+ 'mag_right'
+ ],
+ symbols: [
+ '100',
+ '1234',
+ 'heart',
+ 'yellow_heart',
+ 'green_heart',
+ 'blue_heart',
+ 'purple_heart',
+ 'broken_heart',
+ 'heart_exclamation',
+ 'two_hearts',
+ 'revolving_hearts',
+ 'heartbeat',
+ 'heartpulse',
+ 'sparkling_heart',
+ 'cupid',
+ 'gift_heart',
+ 'heart_decoration',
+ 'peace',
+ 'cross',
+ 'star_and_crescent',
+ 'om_symbol',
+ 'wheel_of_dharma',
+ 'star_of_david',
+ 'six_pointed_star',
+ 'menorah',
+ 'yin_yang',
+ 'orthodox_cross',
+ 'place_of_worship',
+ 'ophiuchus',
+ 'aries',
+ 'taurus',
+ 'gemini',
+ 'cancer',
+ 'leo',
+ 'virgo',
+ 'libra',
+ 'scorpius',
+ 'sagittarius',
+ 'capricorn',
+ 'aquarius',
+ 'pisces',
+ 'id',
+ 'atom',
+ 'u7a7a',
+ 'u5272',
+ 'radioactive',
+ 'biohazard',
+ 'mobile_phone_off',
+ 'vibration_mode',
+ 'u6709',
+ 'u7121',
+ 'u7533',
+ 'u55b6',
+ 'u6708',
+ 'eight_pointed_black_star',
+ 'vs',
+ 'accept',
+ 'white_flower',
+ 'ideograph_advantage',
+ 'secret',
+ 'congratulations',
+ 'u5408',
+ 'u6e80',
+ 'u7981',
+ 'a',
+ 'b',
+ 'ab',
+ 'cl',
+ 'o2',
+ 'sos',
+ 'no_entry',
+ 'name_badge',
+ 'no_entry_sign',
+ 'x',
+ 'o',
+ 'anger',
+ 'hotsprings',
+ 'no_pedestrians',
+ 'do_not_litter',
+ 'no_bicycles',
+ 'non-potable_water',
+ 'underage',
+ 'no_mobile_phones',
+ 'exclamation',
+ 'grey_exclamation',
+ 'question',
+ 'grey_question',
+ 'bangbang',
+ 'interrobang',
+ 'low_brightness',
+ 'high_brightness',
+ 'trident',
+ 'fleur-de-lis',
+ 'part_alternation_mark',
+ 'warning',
+ 'children_crossing',
+ 'beginner',
+ 'recycle',
+ 'u6307',
+ 'chart',
+ 'sparkle',
+ 'eight_spoked_asterisk',
+ 'negative_squared_cross_mark',
+ 'white_check_mark',
+ 'diamond_shape_with_a_dot_inside',
+ 'cyclone',
+ 'loop',
+ 'globe_with_meridians',
+ 'm',
+ 'atm',
+ 'sa',
+ 'passport_control',
+ 'customs',
+ 'baggage_claim',
+ 'left_luggage',
+ 'wheelchair',
+ 'no_smoking',
+ 'wc',
+ 'parking',
+ 'potable_water',
+ 'mens',
+ 'womens',
+ 'baby_symbol',
+ 'restroom',
+ 'put_litter_in_its_place',
+ 'cinema',
+ 'signal_strength',
+ 'koko',
+ 'ng',
+ 'ok',
+ 'up',
+ 'cool',
+ 'new',
+ 'free',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'keycap_ten',
+ 'arrow_forward',
+ 'pause_button',
+ 'play_pause',
+ 'stop_button',
+ 'record_button',
+ 'track_next',
+ 'track_previous',
+ 'fast_forward',
+ 'rewind',
+ 'twisted_rightwards_arrows',
+ 'repeat',
+ 'repeat_one',
+ 'arrow_backward',
+ 'arrow_up_small',
+ 'arrow_down_small',
+ 'arrow_double_up',
+ 'arrow_double_down',
+ 'arrow_right',
+ 'arrow_left',
+ 'arrow_up',
+ 'arrow_down',
+ 'arrow_upper_right',
+ 'arrow_lower_right',
+ 'arrow_lower_left',
+ 'arrow_upper_left',
+ 'arrow_up_down',
+ 'left_right_arrow',
+ 'arrows_counterclockwise',
+ 'arrow_right_hook',
+ 'leftwards_arrow_with_hook',
+ 'arrow_heading_up',
+ 'arrow_heading_down',
+ 'hash',
+ 'asterisk',
+ 'information_source',
+ 'abc',
+ 'abcd',
+ 'capital_abcd',
+ 'symbols',
+ 'musical_note',
+ 'notes',
+ 'wavy_dash',
+ 'curly_loop',
+ 'heavy_check_mark',
+ 'arrows_clockwise',
+ 'heavy_plus_sign',
+ 'heavy_minus_sign',
+ 'heavy_division_sign',
+ 'heavy_multiplication_x',
+ 'heavy_dollar_sign',
+ 'currency_exchange',
+ 'copyright',
+ 'registered',
+ 'tm',
+ 'end',
+ 'back',
+ 'on',
+ 'top',
+ 'soon',
+ 'ballot_box_with_check',
+ 'radio_button',
+ 'white_circle',
+ 'black_circle',
+ 'red_circle',
+ 'large_blue_circle',
+ 'small_orange_diamond',
+ 'small_blue_diamond',
+ 'large_orange_diamond',
+ 'large_blue_diamond',
+ 'small_red_triangle',
+ 'black_small_square',
+ 'white_small_square',
+ 'black_large_square',
+ 'white_large_square',
+ 'small_red_triangle_down',
+ 'black_medium_square',
+ 'white_medium_square',
+ 'black_medium_small_square',
+ 'white_medium_small_square',
+ 'black_square_button',
+ 'white_square_button',
+ 'speaker',
+ 'sound',
+ 'loud_sound',
+ 'mute',
+ 'mega',
+ 'loudspeaker',
+ 'bell',
+ 'no_bell',
+ 'black_joker',
+ 'mahjong',
+ 'spades',
+ 'clubs',
+ 'hearts',
+ 'diamonds',
+ 'flower_playing_cards',
+ 'thought_balloon',
+ 'anger_right',
+ 'speech_balloon',
+ 'clock1',
+ 'clock2',
+ 'clock3',
+ 'clock4',
+ 'clock5',
+ 'clock6',
+ 'clock7',
+ 'clock8',
+ 'clock9',
+ 'clock10',
+ 'clock11',
+ 'clock12',
+ 'clock130',
+ 'clock230',
+ 'clock330',
+ 'clock430',
+ 'clock530',
+ 'clock630',
+ 'clock730',
+ 'clock830',
+ 'clock930',
+ 'clock1030',
+ 'clock1130',
+ 'clock1230',
+ 'eye_in_speech_bubble',
+ 'speech_left',
+ 'eject',
+ 'black_heart',
+ 'octagonal_sign',
+ 'asterisk_symbol',
+ 'pound_symbol',
+ 'digit_nine',
+ 'digit_eight',
+ 'digit_seven',
+ 'digit_six',
+ 'digit_five',
+ 'digit_four',
+ 'digit_three',
+ 'digit_two',
+ 'digit_one',
+ 'digit_zero',
+ 'regional_indicator_z',
+ 'regional_indicator_y',
+ 'regional_indicator_x',
+ 'regional_indicator_w',
+ 'regional_indicator_v',
+ 'regional_indicator_u',
+ 'regional_indicator_t',
+ 'regional_indicator_s',
+ 'regional_indicator_r',
+ 'regional_indicator_q',
+ 'regional_indicator_p',
+ 'regional_indicator_o',
+ 'regional_indicator_n',
+ 'regional_indicator_m',
+ 'regional_indicator_l',
+ 'regional_indicator_k',
+ 'regional_indicator_j',
+ 'regional_indicator_i',
+ 'regional_indicator_h',
+ 'regional_indicator_g',
+ 'regional_indicator_f',
+ 'regional_indicator_e',
+ 'regional_indicator_d',
+ 'regional_indicator_c',
+ 'regional_indicator_b',
+ 'regional_indicator_a'
+ ],
+ flags: [
+ 'flag_ac',
+ 'flag_af',
+ 'flag_al',
+ 'flag_dz',
+ 'flag_ad',
+ 'flag_ao',
+ 'flag_ai',
+ 'flag_ag',
+ 'flag_ar',
+ 'flag_am',
+ 'flag_aw',
+ 'flag_au',
+ 'flag_at',
+ 'flag_az',
+ 'flag_bs',
+ 'flag_bh',
+ 'flag_bd',
+ 'flag_bb',
+ 'flag_by',
+ 'flag_be',
+ 'flag_bz',
+ 'flag_bj',
+ 'flag_bm',
+ 'flag_bt',
+ 'flag_bo',
+ 'flag_ba',
+ 'flag_bw',
+ 'flag_br',
+ 'flag_bn',
+ 'flag_bg',
+ 'flag_bf',
+ 'flag_bi',
+ 'flag_cv',
+ 'flag_kh',
+ 'flag_cm',
+ 'flag_ca',
+ 'flag_ky',
+ 'flag_cf',
+ 'flag_td',
+ 'flag_cl',
+ 'flag_cn',
+ 'flag_co',
+ 'flag_km',
+ 'flag_cg',
+ 'flag_cd',
+ 'flag_cr',
+ 'flag_hr',
+ 'flag_cu',
+ 'flag_cy',
+ 'flag_cz',
+ 'flag_dk',
+ 'flag_dj',
+ 'flag_dm',
+ 'flag_do',
+ 'flag_ec',
+ 'flag_eg',
+ 'flag_sv',
+ 'flag_gq',
+ 'flag_er',
+ 'flag_ee',
+ 'flag_et',
+ 'flag_fk',
+ 'flag_fo',
+ 'flag_fj',
+ 'flag_fi',
+ 'flag_fr',
+ 'flag_pf',
+ 'flag_ga',
+ 'flag_gm',
+ 'flag_ge',
+ 'flag_de',
+ 'flag_gh',
+ 'flag_gi',
+ 'flag_gr',
+ 'flag_gl',
+ 'flag_gd',
+ 'flag_gu',
+ 'flag_gt',
+ 'flag_gn',
+ 'flag_gw',
+ 'flag_gy',
+ 'flag_ht',
+ 'flag_hn',
+ 'flag_hk',
+ 'flag_hu',
+ 'flag_is',
+ 'flag_in',
+ 'flag_id',
+ 'flag_ir',
+ 'flag_iq',
+ 'flag_ie',
+ 'flag_il',
+ 'flag_it',
+ 'flag_ci',
+ 'flag_jm',
+ 'flag_jp',
+ 'flag_je',
+ 'flag_jo',
+ 'flag_kz',
+ 'flag_ke',
+ 'flag_ki',
+ 'flag_xk',
+ 'flag_kw',
+ 'flag_kg',
+ 'flag_la',
+ 'flag_lv',
+ 'flag_lb',
+ 'flag_ls',
+ 'flag_lr',
+ 'flag_ly',
+ 'flag_li',
+ 'flag_lt',
+ 'flag_lu',
+ 'flag_mo',
+ 'flag_mk',
+ 'flag_mg',
+ 'flag_mw',
+ 'flag_my',
+ 'flag_mv',
+ 'flag_ml',
+ 'flag_mt',
+ 'flag_mh',
+ 'flag_mr',
+ 'flag_mu',
+ 'flag_mx',
+ 'flag_fm',
+ 'flag_md',
+ 'flag_mc',
+ 'flag_mn',
+ 'flag_me',
+ 'flag_ms',
+ 'flag_ma',
+ 'flag_mz',
+ 'flag_mm',
+ 'flag_na',
+ 'flag_nr',
+ 'flag_np',
+ 'flag_nl',
+ 'flag_nc',
+ 'flag_nz',
+ 'flag_ni',
+ 'flag_ne',
+ 'flag_ng',
+ 'flag_nu',
+ 'flag_kp',
+ 'flag_no',
+ 'flag_om',
+ 'flag_pk',
+ 'flag_pw',
+ 'flag_ps',
+ 'flag_pa',
+ 'flag_pg',
+ 'flag_py',
+ 'flag_pe',
+ 'flag_ph',
+ 'flag_pl',
+ 'flag_pt',
+ 'flag_pr',
+ 'flag_qa',
+ 'flag_ro',
+ 'flag_ru',
+ 'flag_rw',
+ 'flag_sh',
+ 'flag_kn',
+ 'flag_lc',
+ 'flag_vc',
+ 'flag_ws',
+ 'flag_sm',
+ 'flag_st',
+ 'flag_sa',
+ 'flag_sn',
+ 'flag_rs',
+ 'flag_sc',
+ 'flag_sl',
+ 'flag_sg',
+ 'flag_sk',
+ 'flag_si',
+ 'flag_sb',
+ 'flag_so',
+ 'flag_za',
+ 'flag_kr',
+ 'flag_es',
+ 'flag_lk',
+ 'flag_sd',
+ 'flag_sr',
+ 'flag_sz',
+ 'flag_se',
+ 'flag_ch',
+ 'flag_sy',
+ 'flag_tw',
+ 'flag_tj',
+ 'flag_tz',
+ 'flag_th',
+ 'flag_tl',
+ 'flag_tg',
+ 'flag_to',
+ 'flag_tt',
+ 'flag_tn',
+ 'flag_tr',
+ 'flag_tm',
+ 'flag_tv',
+ 'flag_ug',
+ 'flag_ua',
+ 'flag_ae',
+ 'flag_gb',
+ 'flag_us',
+ 'flag_vi',
+ 'flag_uy',
+ 'flag_uz',
+ 'flag_vu',
+ 'flag_va',
+ 'flag_ve',
+ 'flag_vn',
+ 'flag_wf',
+ 'flag_eh',
+ 'flag_ye',
+ 'flag_zm',
+ 'flag_zw',
+ 'flag_re',
+ 'flag_ax',
+ 'flag_ta',
+ 'flag_io',
+ 'flag_bq',
+ 'flag_cx',
+ 'flag_cc',
+ 'flag_gg',
+ 'flag_im',
+ 'flag_yt',
+ 'flag_nf',
+ 'flag_pn',
+ 'flag_bl',
+ 'flag_pm',
+ 'flag_gs',
+ 'flag_tk',
+ 'flag_bv',
+ 'flag_hm',
+ 'flag_sj',
+ 'flag_um',
+ 'flag_ic',
+ 'flag_ea',
+ 'flag_cp',
+ 'flag_dg',
+ 'flag_as',
+ 'flag_aq',
+ 'flag_vg',
+ 'flag_ck',
+ 'flag_cw',
+ 'flag_eu',
+ 'flag_gf',
+ 'flag_tf',
+ 'flag_gp',
+ 'flag_mq',
+ 'flag_mp',
+ 'flag_sx',
+ 'flag_ss',
+ 'flag_tc',
+ 'flag_mf'
+ ]
+};
+
+export const emojis = [
+ 'grinning',
+ 'grimacing',
+ 'grin',
+ 'joy',
+ 'smiley',
+ 'smile',
+ 'sweat_smile',
+ 'laughing',
+ 'innocent',
+ 'wink',
+ 'blush',
+ 'slight_smile',
+ 'upside_down',
+ 'relaxed',
+ 'yum',
+ 'relieved',
+ 'heart_eyes',
+ 'kissing_heart',
+ 'kissing',
+ 'kissing_smiling_eyes',
+ 'kissing_closed_eyes',
+ 'stuck_out_tongue_winking_eye',
+ 'stuck_out_tongue_closed_eyes',
+ 'stuck_out_tongue',
+ 'money_mouth',
+ 'nerd',
+ 'sunglasses',
+ 'hugging',
+ 'smirk',
+ 'no_mouth',
+ 'neutral_face',
+ 'expressionless',
+ 'unamused',
+ 'rolling_eyes',
+ 'thinking',
+ 'flushed',
+ 'disappointed',
+ 'worried',
+ 'angry',
+ 'rage',
+ 'pensive',
+ 'confused',
+ 'slight_frown',
+ 'frowning2',
+ 'persevere',
+ 'confounded',
+ 'tired_face',
+ 'weary',
+ 'triumph',
+ 'open_mouth',
+ 'scream',
+ 'fearful',
+ 'cold_sweat',
+ 'hushed',
+ 'frowning',
+ 'anguished',
+ 'cry',
+ 'disappointed_relieved',
+ 'sleepy',
+ 'sweat',
+ 'sob',
+ 'dizzy_face',
+ 'astonished',
+ 'zipper_mouth',
+ 'mask',
+ 'thermometer_face',
+ 'head_bandage',
+ 'sleeping',
+ 'zzz',
+ 'poop',
+ 'smiling_imp',
+ 'imp',
+ 'japanese_ogre',
+ 'japanese_goblin',
+ 'skull',
+ 'ghost',
+ 'alien',
+ 'robot',
+ 'smiley_cat',
+ 'smile_cat',
+ 'joy_cat',
+ 'heart_eyes_cat',
+ 'smirk_cat',
+ 'kissing_cat',
+ 'scream_cat',
+ 'crying_cat_face',
+ 'pouting_cat',
+ 'raised_hands',
+ 'clap',
+ 'wave',
+ 'thumbsup',
+ 'thumbsdown',
+ 'punch',
+ 'fist',
+ 'v',
+ 'ok_hand',
+ 'raised_hand',
+ 'open_hands',
+ 'muscle',
+ 'pray',
+ 'point_up',
+ 'point_up_2',
+ 'point_down',
+ 'point_left',
+ 'point_right',
+ 'middle_finger',
+ 'hand_splayed',
+ 'metal',
+ 'vulcan',
+ 'writing_hand',
+ 'nail_care',
+ 'lips',
+ 'tongue',
+ 'ear',
+ 'nose',
+ 'eye',
+ 'eyes',
+ 'bust_in_silhouette',
+ 'busts_in_silhouette',
+ 'speaking_head',
+ 'baby',
+ 'boy',
+ 'girl',
+ 'man',
+ 'woman',
+ 'person_with_blond_hair',
+ 'older_man',
+ 'older_woman',
+ 'man_with_gua_pi_mao',
+ 'man_with_turban',
+ 'cop',
+ 'construction_worker',
+ 'guardsman',
+ 'spy',
+ 'santa',
+ 'angel',
+ 'princess',
+ 'bride_with_veil',
+ 'walking',
+ 'runner',
+ 'dancer',
+ 'dancers',
+ 'couple',
+ 'two_men_holding_hands',
+ 'two_women_holding_hands',
+ 'bow',
+ 'information_desk_person',
+ 'no_good',
+ 'ok_woman',
+ 'raising_hand',
+ 'person_with_pouting_face',
+ 'person_frowning',
+ 'haircut',
+ 'massage',
+ 'couple_with_heart',
+ 'couple_ww',
+ 'couple_mm',
+ 'couplekiss',
+ 'kiss_ww',
+ 'kiss_mm',
+ 'family',
+ 'family_mwg',
+ 'family_mwgb',
+ 'family_mwbb',
+ 'family_mwgg',
+ 'family_wwb',
+ 'family_wwg',
+ 'family_wwgb',
+ 'family_wwbb',
+ 'family_wwgg',
+ 'family_mmb',
+ 'family_mmg',
+ 'family_mmgb',
+ 'family_mmbb',
+ 'family_mmgg',
+ 'womans_clothes',
+ 'shirt',
+ 'jeans',
+ 'necktie',
+ 'dress',
+ 'bikini',
+ 'kimono',
+ 'lipstick',
+ 'kiss',
+ 'footprints',
+ 'high_heel',
+ 'sandal',
+ 'boot',
+ 'mans_shoe',
+ 'athletic_shoe',
+ 'womans_hat',
+ 'tophat',
+ 'helmet_with_cross',
+ 'mortar_board',
+ 'crown',
+ 'school_satchel',
+ 'pouch',
+ 'purse',
+ 'handbag',
+ 'briefcase',
+ 'eyeglasses',
+ 'dark_sunglasses',
+ 'ring',
+ 'closed_umbrella',
+ 'cowboy',
+ 'clown',
+ 'nauseated_face',
+ 'rofl',
+ 'drooling_face',
+ 'lying_face',
+ 'sneezing_face',
+ 'prince',
+ 'man_in_tuxedo',
+ 'mrs_claus',
+ 'face_palm',
+ 'shrug',
+ 'selfie',
+ 'man_dancing',
+ 'call_me',
+ 'raised_back_of_hand',
+ 'left_facing_fist',
+ 'right_facing_fist',
+ 'handshake',
+ 'fingers_crossed',
+ 'pregnant_woman',
+ 'dog',
+ 'cat',
+ 'mouse',
+ 'hamster',
+ 'rabbit',
+ 'bear',
+ 'panda_face',
+ 'koala',
+ 'tiger',
+ 'lion_face',
+ 'cow',
+ 'pig',
+ 'pig_nose',
+ 'frog',
+ 'octopus',
+ 'monkey_face',
+ 'see_no_evil',
+ 'hear_no_evil',
+ 'speak_no_evil',
+ 'monkey',
+ 'chicken',
+ 'penguin',
+ 'bird',
+ 'baby_chick',
+ 'hatching_chick',
+ 'hatched_chick',
+ 'wolf',
+ 'boar',
+ 'horse',
+ 'unicorn',
+ 'bee',
+ 'bug',
+ 'snail',
+ 'beetle',
+ 'ant',
+ 'spider',
+ 'scorpion',
+ 'crab',
+ 'snake',
+ 'turtle',
+ 'tropical_fish',
+ 'fish',
+ 'blowfish',
+ 'dolphin',
+ 'whale',
+ 'whale2',
+ 'crocodile',
+ 'leopard',
+ 'tiger2',
+ 'water_buffalo',
+ 'ox',
+ 'cow2',
+ 'dromedary_camel',
+ 'camel',
+ 'elephant',
+ 'goat',
+ 'ram',
+ 'sheep',
+ 'racehorse',
+ 'pig2',
+ 'rat',
+ 'mouse2',
+ 'rooster',
+ 'turkey',
+ 'dove',
+ 'dog2',
+ 'poodle',
+ 'cat2',
+ 'rabbit2',
+ 'chipmunk',
+ 'feet',
+ 'dragon',
+ 'dragon_face',
+ 'cactus',
+ 'christmas_tree',
+ 'evergreen_tree',
+ 'deciduous_tree',
+ 'palm_tree',
+ 'seedling',
+ 'herb',
+ 'shamrock',
+ 'four_leaf_clover',
+ 'bamboo',
+ 'tanabata_tree',
+ 'leaves',
+ 'fallen_leaf',
+ 'maple_leaf',
+ 'ear_of_rice',
+ 'hibiscus',
+ 'sunflower',
+ 'rose',
+ 'tulip',
+ 'blossom',
+ 'cherry_blossom',
+ 'bouquet',
+ 'mushroom',
+ 'chestnut',
+ 'jack_o_lantern',
+ 'shell',
+ 'spider_web',
+ 'earth_americas',
+ 'earth_africa',
+ 'earth_asia',
+ 'full_moon',
+ 'waning_gibbous_moon',
+ 'last_quarter_moon',
+ 'waning_crescent_moon',
+ 'new_moon',
+ 'waxing_crescent_moon',
+ 'first_quarter_moon',
+ 'waxing_gibbous_moon',
+ 'new_moon_with_face',
+ 'full_moon_with_face',
+ 'first_quarter_moon_with_face',
+ 'last_quarter_moon_with_face',
+ 'sun_with_face',
+ 'crescent_moon',
+ 'star',
+ 'star2',
+ 'dizzy',
+ 'sparkles',
+ 'comet',
+ 'sunny',
+ 'white_sun_small_cloud',
+ 'partly_sunny',
+ 'white_sun_cloud',
+ 'white_sun_rain_cloud',
+ 'cloud',
+ 'cloud_rain',
+ 'thunder_cloud_rain',
+ 'cloud_lightning',
+ 'zap',
+ 'fire',
+ 'boom',
+ 'snowflake',
+ 'cloud_snow',
+ 'snowman2',
+ 'snowman',
+ 'wind_blowing_face',
+ 'dash',
+ 'cloud_tornado',
+ 'fog',
+ 'umbrella2',
+ 'umbrella',
+ 'droplet',
+ 'sweat_drops',
+ 'ocean',
+ 'eagle',
+ 'duck',
+ 'bat',
+ 'shark',
+ 'owl',
+ 'fox',
+ 'butterfly',
+ 'deer',
+ 'gorilla',
+ 'lizard',
+ 'rhino',
+ 'wilted_rose',
+ 'shrimp',
+ 'squid',
+ 'green_apple',
+ 'apple',
+ 'pear',
+ 'tangerine',
+ 'lemon',
+ 'banana',
+ 'watermelon',
+ 'grapes',
+ 'strawberry',
+ 'melon',
+ 'cherries',
+ 'peach',
+ 'pineapple',
+ 'tomato',
+ 'eggplant',
+ 'hot_pepper',
+ 'corn',
+ 'sweet_potato',
+ 'honey_pot',
+ 'bread',
+ 'cheese',
+ 'poultry_leg',
+ 'meat_on_bone',
+ 'fried_shrimp',
+ 'cooking',
+ 'hamburger',
+ 'fries',
+ 'hotdog',
+ 'pizza',
+ 'spaghetti',
+ 'taco',
+ 'burrito',
+ 'ramen',
+ 'stew',
+ 'fish_cake',
+ 'sushi',
+ 'bento',
+ 'curry',
+ 'rice_ball',
+ 'rice',
+ 'rice_cracker',
+ 'oden',
+ 'dango',
+ 'shaved_ice',
+ 'ice_cream',
+ 'icecream',
+ 'cake',
+ 'birthday',
+ 'custard',
+ 'candy',
+ 'lollipop',
+ 'chocolate_bar',
+ 'popcorn',
+ 'doughnut',
+ 'cookie',
+ 'beer',
+ 'beers',
+ 'wine_glass',
+ 'cocktail',
+ 'tropical_drink',
+ 'champagne',
+ 'sake',
+ 'tea',
+ 'coffee',
+ 'baby_bottle',
+ 'fork_and_knife',
+ 'fork_knife_plate',
+ 'croissant',
+ 'avocado',
+ 'cucumber',
+ 'bacon',
+ 'potato',
+ 'carrot',
+ 'french_bread',
+ 'salad',
+ 'shallow_pan_of_food',
+ 'stuffed_flatbread',
+ 'champagne_glass',
+ 'tumbler_glass',
+ 'spoon',
+ 'egg',
+ 'milk',
+ 'peanuts',
+ 'kiwi',
+ 'pancakes',
+ 'soccer',
+ 'basketball',
+ 'football',
+ 'baseball',
+ 'tennis',
+ 'volleyball',
+ 'rugby_football',
+ '8ball',
+ 'golf',
+ 'golfer',
+ 'ping_pong',
+ 'badminton',
+ 'hockey',
+ 'field_hockey',
+ 'cricket',
+ 'ski',
+ 'skier',
+ 'snowboarder',
+ 'ice_skate',
+ 'bow_and_arrow',
+ 'fishing_pole_and_fish',
+ 'rowboat',
+ 'swimmer',
+ 'surfer',
+ 'bath',
+ 'basketball_player',
+ 'lifter',
+ 'bicyclist',
+ 'mountain_bicyclist',
+ 'horse_racing',
+ 'levitate',
+ 'trophy',
+ 'running_shirt_with_sash',
+ 'medal',
+ 'military_medal',
+ 'reminder_ribbon',
+ 'rosette',
+ 'ticket',
+ 'tickets',
+ 'performing_arts',
+ 'art',
+ 'circus_tent',
+ 'microphone',
+ 'headphones',
+ 'musical_score',
+ 'musical_keyboard',
+ 'saxophone',
+ 'trumpet',
+ 'guitar',
+ 'violin',
+ 'clapper',
+ 'video_game',
+ 'space_invader',
+ 'dart',
+ 'game_die',
+ 'slot_machine',
+ 'bowling',
+ 'cartwheel',
+ 'juggling',
+ 'wrestlers',
+ 'boxing_glove',
+ 'martial_arts_uniform',
+ 'water_polo',
+ 'handball',
+ 'goal',
+ 'fencer',
+ 'first_place',
+ 'second_place',
+ 'third_place',
+ 'drum',
+ 'red_car',
+ 'taxi',
+ 'blue_car',
+ 'bus',
+ 'trolleybus',
+ 'race_car',
+ 'police_car',
+ 'ambulance',
+ 'fire_engine',
+ 'minibus',
+ 'truck',
+ 'articulated_lorry',
+ 'tractor',
+ 'motorcycle',
+ 'bike',
+ 'rotating_light',
+ 'oncoming_police_car',
+ 'oncoming_bus',
+ 'oncoming_automobile',
+ 'oncoming_taxi',
+ 'aerial_tramway',
+ 'mountain_cableway',
+ 'suspension_railway',
+ 'railway_car',
+ 'train',
+ 'monorail',
+ 'bullettrain_side',
+ 'bullettrain_front',
+ 'light_rail',
+ 'mountain_railway',
+ 'steam_locomotive',
+ 'train2',
+ 'metro',
+ 'tram',
+ 'station',
+ 'helicopter',
+ 'airplane_small',
+ 'airplane',
+ 'airplane_departure',
+ 'airplane_arriving',
+ 'sailboat',
+ 'motorboat',
+ 'speedboat',
+ 'ferry',
+ 'cruise_ship',
+ 'rocket',
+ 'satellite_orbital',
+ 'seat',
+ 'anchor',
+ 'construction',
+ 'fuelpump',
+ 'busstop',
+ 'vertical_traffic_light',
+ 'traffic_light',
+ 'checkered_flag',
+ 'ship',
+ 'ferris_wheel',
+ 'roller_coaster',
+ 'carousel_horse',
+ 'construction_site',
+ 'foggy',
+ 'tokyo_tower',
+ 'factory',
+ 'fountain',
+ 'rice_scene',
+ 'mountain',
+ 'mountain_snow',
+ 'mount_fuji',
+ 'volcano',
+ 'japan',
+ 'camping',
+ 'tent',
+ 'park',
+ 'motorway',
+ 'railway_track',
+ 'sunrise',
+ 'sunrise_over_mountains',
+ 'desert',
+ 'beach',
+ 'island',
+ 'city_sunset',
+ 'city_dusk',
+ 'cityscape',
+ 'night_with_stars',
+ 'bridge_at_night',
+ 'milky_way',
+ 'stars',
+ 'sparkler',
+ 'fireworks',
+ 'rainbow',
+ 'homes',
+ 'european_castle',
+ 'japanese_castle',
+ 'stadium',
+ 'statue_of_liberty',
+ 'house',
+ 'house_with_garden',
+ 'house_abandoned',
+ 'office',
+ 'department_store',
+ 'post_office',
+ 'european_post_office',
+ 'hospital',
+ 'bank',
+ 'hotel',
+ 'convenience_store',
+ 'school',
+ 'love_hotel',
+ 'wedding',
+ 'classical_building',
+ 'church',
+ 'mosque',
+ 'synagogue',
+ 'kaaba',
+ 'shinto_shrine',
+ 'shopping_cart',
+ 'scooter',
+ 'motor_scooter',
+ 'canoe',
+ 'watch',
+ 'iphone',
+ 'calling',
+ 'computer',
+ 'keyboard',
+ 'desktop',
+ 'printer',
+ 'mouse_three_button',
+ 'trackball',
+ 'joystick',
+ 'compression',
+ 'minidisc',
+ 'floppy_disk',
+ 'cd',
+ 'dvd',
+ 'vhs',
+ 'camera',
+ 'camera_with_flash',
+ 'video_camera',
+ 'movie_camera',
+ 'projector',
+ 'film_frames',
+ 'telephone_receiver',
+ 'telephone',
+ 'pager',
+ 'fax',
+ 'tv',
+ 'radio',
+ 'microphone2',
+ 'level_slider',
+ 'control_knobs',
+ 'stopwatch',
+ 'timer',
+ 'alarm_clock',
+ 'clock',
+ 'hourglass_flowing_sand',
+ 'hourglass',
+ 'satellite',
+ 'battery',
+ 'electric_plug',
+ 'bulb',
+ 'flashlight',
+ 'candle',
+ 'wastebasket',
+ 'oil',
+ 'money_with_wings',
+ 'dollar',
+ 'yen',
+ 'euro',
+ 'pound',
+ 'moneybag',
+ 'credit_card',
+ 'gem',
+ 'scales',
+ 'wrench',
+ 'hammer',
+ 'hammer_pick',
+ 'tools',
+ 'pick',
+ 'nut_and_bolt',
+ 'gear',
+ 'chains',
+ 'gun',
+ 'bomb',
+ 'knife',
+ 'dagger',
+ 'crossed_swords',
+ 'shield',
+ 'smoking',
+ 'skull_crossbones',
+ 'coffin',
+ 'urn',
+ 'amphora',
+ 'crystal_ball',
+ 'prayer_beads',
+ 'barber',
+ 'alembic',
+ 'telescope',
+ 'microscope',
+ 'hole',
+ 'pill',
+ 'syringe',
+ 'thermometer',
+ 'label',
+ 'bookmark',
+ 'toilet',
+ 'shower',
+ 'bathtub',
+ 'key',
+ 'key2',
+ 'couch',
+ 'sleeping_accommodation',
+ 'bed',
+ 'door',
+ 'bellhop',
+ 'frame_photo',
+ 'map',
+ 'beach_umbrella',
+ 'moyai',
+ 'shopping_bags',
+ 'balloon',
+ 'flags',
+ 'ribbon',
+ 'gift',
+ 'confetti_ball',
+ 'tada',
+ 'dolls',
+ 'wind_chime',
+ 'crossed_flags',
+ 'izakaya_lantern',
+ 'envelope',
+ 'envelope_with_arrow',
+ 'incoming_envelope',
+ 'e-mail',
+ 'love_letter',
+ 'postbox',
+ 'mailbox_closed',
+ 'mailbox',
+ 'mailbox_with_mail',
+ 'mailbox_with_no_mail',
+ 'package',
+ 'postal_horn',
+ 'inbox_tray',
+ 'outbox_tray',
+ 'scroll',
+ 'page_with_curl',
+ 'bookmark_tabs',
+ 'bar_chart',
+ 'chart_with_upwards_trend',
+ 'chart_with_downwards_trend',
+ 'page_facing_up',
+ 'date',
+ 'calendar',
+ 'calendar_spiral',
+ 'card_index',
+ 'card_box',
+ 'ballot_box',
+ 'file_cabinet',
+ 'clipboard',
+ 'notepad_spiral',
+ 'file_folder',
+ 'open_file_folder',
+ 'dividers',
+ 'newspaper2',
+ 'newspaper',
+ 'notebook',
+ 'closed_book',
+ 'green_book',
+ 'blue_book',
+ 'orange_book',
+ 'notebook_with_decorative_cover',
+ 'ledger',
+ 'books',
+ 'book',
+ 'link',
+ 'paperclip',
+ 'paperclips',
+ 'scissors',
+ 'triangular_ruler',
+ 'straight_ruler',
+ 'pushpin',
+ 'round_pushpin',
+ 'triangular_flag_on_post',
+ 'flag_white',
+ 'flag_black',
+ 'closed_lock_with_key',
+ 'lock',
+ 'unlock',
+ 'lock_with_ink_pen',
+ 'pen_ballpoint',
+ 'pen_fountain',
+ 'black_nib',
+ 'pencil',
+ 'pencil2',
+ 'crayon',
+ 'paintbrush',
+ 'mag',
+ 'mag_right',
+ '100',
+ '1234',
+ 'heart',
+ 'yellow_heart',
+ 'green_heart',
+ 'blue_heart',
+ 'purple_heart',
+ 'broken_heart',
+ 'heart_exclamation',
+ 'two_hearts',
+ 'revolving_hearts',
+ 'heartbeat',
+ 'heartpulse',
+ 'sparkling_heart',
+ 'cupid',
+ 'gift_heart',
+ 'heart_decoration',
+ 'peace',
+ 'cross',
+ 'star_and_crescent',
+ 'om_symbol',
+ 'wheel_of_dharma',
+ 'star_of_david',
+ 'six_pointed_star',
+ 'menorah',
+ 'yin_yang',
+ 'orthodox_cross',
+ 'place_of_worship',
+ 'ophiuchus',
+ 'aries',
+ 'taurus',
+ 'gemini',
+ 'cancer',
+ 'leo',
+ 'virgo',
+ 'libra',
+ 'scorpius',
+ 'sagittarius',
+ 'capricorn',
+ 'aquarius',
+ 'pisces',
+ 'id',
+ 'atom',
+ 'u7a7a',
+ 'u5272',
+ 'radioactive',
+ 'biohazard',
+ 'mobile_phone_off',
+ 'vibration_mode',
+ 'u6709',
+ 'u7121',
+ 'u7533',
+ 'u55b6',
+ 'u6708',
+ 'eight_pointed_black_star',
+ 'vs',
+ 'accept',
+ 'white_flower',
+ 'ideograph_advantage',
+ 'secret',
+ 'congratulations',
+ 'u5408',
+ 'u6e80',
+ 'u7981',
+ 'a',
+ 'b',
+ 'ab',
+ 'cl',
+ 'o2',
+ 'sos',
+ 'no_entry',
+ 'name_badge',
+ 'no_entry_sign',
+ 'x',
+ 'o',
+ 'anger',
+ 'hotsprings',
+ 'no_pedestrians',
+ 'do_not_litter',
+ 'no_bicycles',
+ 'non-potable_water',
+ 'underage',
+ 'no_mobile_phones',
+ 'exclamation',
+ 'grey_exclamation',
+ 'question',
+ 'grey_question',
+ 'bangbang',
+ 'interrobang',
+ 'low_brightness',
+ 'high_brightness',
+ 'trident',
+ 'fleur-de-lis',
+ 'part_alternation_mark',
+ 'warning',
+ 'children_crossing',
+ 'beginner',
+ 'recycle',
+ 'u6307',
+ 'chart',
+ 'sparkle',
+ 'eight_spoked_asterisk',
+ 'negative_squared_cross_mark',
+ 'white_check_mark',
+ 'diamond_shape_with_a_dot_inside',
+ 'cyclone',
+ 'loop',
+ 'globe_with_meridians',
+ 'm',
+ 'atm',
+ 'sa',
+ 'passport_control',
+ 'customs',
+ 'baggage_claim',
+ 'left_luggage',
+ 'wheelchair',
+ 'no_smoking',
+ 'wc',
+ 'parking',
+ 'potable_water',
+ 'mens',
+ 'womens',
+ 'baby_symbol',
+ 'restroom',
+ 'put_litter_in_its_place',
+ 'cinema',
+ 'signal_strength',
+ 'koko',
+ 'ng',
+ 'ok',
+ 'up',
+ 'cool',
+ 'new',
+ 'free',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'keycap_ten',
+ 'arrow_forward',
+ 'pause_button',
+ 'play_pause',
+ 'stop_button',
+ 'record_button',
+ 'track_next',
+ 'track_previous',
+ 'fast_forward',
+ 'rewind',
+ 'twisted_rightwards_arrows',
+ 'repeat',
+ 'repeat_one',
+ 'arrow_backward',
+ 'arrow_up_small',
+ 'arrow_down_small',
+ 'arrow_double_up',
+ 'arrow_double_down',
+ 'arrow_right',
+ 'arrow_left',
+ 'arrow_up',
+ 'arrow_down',
+ 'arrow_upper_right',
+ 'arrow_lower_right',
+ 'arrow_lower_left',
+ 'arrow_upper_left',
+ 'arrow_up_down',
+ 'left_right_arrow',
+ 'arrows_counterclockwise',
+ 'arrow_right_hook',
+ 'leftwards_arrow_with_hook',
+ 'arrow_heading_up',
+ 'arrow_heading_down',
+ 'hash',
+ 'asterisk',
+ 'information_source',
+ 'abc',
+ 'abcd',
+ 'capital_abcd',
+ 'symbols',
+ 'musical_note',
+ 'notes',
+ 'wavy_dash',
+ 'curly_loop',
+ 'heavy_check_mark',
+ 'arrows_clockwise',
+ 'heavy_plus_sign',
+ 'heavy_minus_sign',
+ 'heavy_division_sign',
+ 'heavy_multiplication_x',
+ 'heavy_dollar_sign',
+ 'currency_exchange',
+ 'copyright',
+ 'registered',
+ 'tm',
+ 'end',
+ 'back',
+ 'on',
+ 'top',
+ 'soon',
+ 'ballot_box_with_check',
+ 'radio_button',
+ 'white_circle',
+ 'black_circle',
+ 'red_circle',
+ 'large_blue_circle',
+ 'small_orange_diamond',
+ 'small_blue_diamond',
+ 'large_orange_diamond',
+ 'large_blue_diamond',
+ 'small_red_triangle',
+ 'black_small_square',
+ 'white_small_square',
+ 'black_large_square',
+ 'white_large_square',
+ 'small_red_triangle_down',
+ 'black_medium_square',
+ 'white_medium_square',
+ 'black_medium_small_square',
+ 'white_medium_small_square',
+ 'black_square_button',
+ 'white_square_button',
+ 'speaker',
+ 'sound',
+ 'loud_sound',
+ 'mute',
+ 'mega',
+ 'loudspeaker',
+ 'bell',
+ 'no_bell',
+ 'black_joker',
+ 'mahjong',
+ 'spades',
+ 'clubs',
+ 'hearts',
+ 'diamonds',
+ 'flower_playing_cards',
+ 'thought_balloon',
+ 'anger_right',
+ 'speech_balloon',
+ 'clock1',
+ 'clock2',
+ 'clock3',
+ 'clock4',
+ 'clock5',
+ 'clock6',
+ 'clock7',
+ 'clock8',
+ 'clock9',
+ 'clock10',
+ 'clock11',
+ 'clock12',
+ 'clock130',
+ 'clock230',
+ 'clock330',
+ 'clock430',
+ 'clock530',
+ 'clock630',
+ 'clock730',
+ 'clock830',
+ 'clock930',
+ 'clock1030',
+ 'clock1130',
+ 'clock1230',
+ 'eye_in_speech_bubble',
+ 'speech_left',
+ 'eject',
+ 'black_heart',
+ 'octagonal_sign',
+ 'asterisk_symbol',
+ 'pound_symbol',
+ 'digit_nine',
+ 'digit_eight',
+ 'digit_seven',
+ 'digit_six',
+ 'digit_five',
+ 'digit_four',
+ 'digit_three',
+ 'digit_two',
+ 'digit_one',
+ 'digit_zero',
+ 'regional_indicator_z',
+ 'regional_indicator_y',
+ 'regional_indicator_x',
+ 'regional_indicator_w',
+ 'regional_indicator_v',
+ 'regional_indicator_u',
+ 'regional_indicator_t',
+ 'regional_indicator_s',
+ 'regional_indicator_r',
+ 'regional_indicator_q',
+ 'regional_indicator_p',
+ 'regional_indicator_o',
+ 'regional_indicator_n',
+ 'regional_indicator_m',
+ 'regional_indicator_l',
+ 'regional_indicator_k',
+ 'regional_indicator_j',
+ 'regional_indicator_i',
+ 'regional_indicator_h',
+ 'regional_indicator_g',
+ 'regional_indicator_f',
+ 'regional_indicator_e',
+ 'regional_indicator_d',
+ 'regional_indicator_c',
+ 'regional_indicator_b',
+ 'regional_indicator_a',
+ 'flag_ac',
+ 'flag_af',
+ 'flag_al',
+ 'flag_dz',
+ 'flag_ad',
+ 'flag_ao',
+ 'flag_ai',
+ 'flag_ag',
+ 'flag_ar',
+ 'flag_am',
+ 'flag_aw',
+ 'flag_au',
+ 'flag_at',
+ 'flag_az',
+ 'flag_bs',
+ 'flag_bh',
+ 'flag_bd',
+ 'flag_bb',
+ 'flag_by',
+ 'flag_be',
+ 'flag_bz',
+ 'flag_bj',
+ 'flag_bm',
+ 'flag_bt',
+ 'flag_bo',
+ 'flag_ba',
+ 'flag_bw',
+ 'flag_br',
+ 'flag_bn',
+ 'flag_bg',
+ 'flag_bf',
+ 'flag_bi',
+ 'flag_cv',
+ 'flag_kh',
+ 'flag_cm',
+ 'flag_ca',
+ 'flag_ky',
+ 'flag_cf',
+ 'flag_td',
+ 'flag_cl',
+ 'flag_cn',
+ 'flag_co',
+ 'flag_km',
+ 'flag_cg',
+ 'flag_cd',
+ 'flag_cr',
+ 'flag_hr',
+ 'flag_cu',
+ 'flag_cy',
+ 'flag_cz',
+ 'flag_dk',
+ 'flag_dj',
+ 'flag_dm',
+ 'flag_do',
+ 'flag_ec',
+ 'flag_eg',
+ 'flag_sv',
+ 'flag_gq',
+ 'flag_er',
+ 'flag_ee',
+ 'flag_et',
+ 'flag_fk',
+ 'flag_fo',
+ 'flag_fj',
+ 'flag_fi',
+ 'flag_fr',
+ 'flag_pf',
+ 'flag_ga',
+ 'flag_gm',
+ 'flag_ge',
+ 'flag_de',
+ 'flag_gh',
+ 'flag_gi',
+ 'flag_gr',
+ 'flag_gl',
+ 'flag_gd',
+ 'flag_gu',
+ 'flag_gt',
+ 'flag_gn',
+ 'flag_gw',
+ 'flag_gy',
+ 'flag_ht',
+ 'flag_hn',
+ 'flag_hk',
+ 'flag_hu',
+ 'flag_is',
+ 'flag_in',
+ 'flag_id',
+ 'flag_ir',
+ 'flag_iq',
+ 'flag_ie',
+ 'flag_il',
+ 'flag_it',
+ 'flag_ci',
+ 'flag_jm',
+ 'flag_jp',
+ 'flag_je',
+ 'flag_jo',
+ 'flag_kz',
+ 'flag_ke',
+ 'flag_ki',
+ 'flag_xk',
+ 'flag_kw',
+ 'flag_kg',
+ 'flag_la',
+ 'flag_lv',
+ 'flag_lb',
+ 'flag_ls',
+ 'flag_lr',
+ 'flag_ly',
+ 'flag_li',
+ 'flag_lt',
+ 'flag_lu',
+ 'flag_mo',
+ 'flag_mk',
+ 'flag_mg',
+ 'flag_mw',
+ 'flag_my',
+ 'flag_mv',
+ 'flag_ml',
+ 'flag_mt',
+ 'flag_mh',
+ 'flag_mr',
+ 'flag_mu',
+ 'flag_mx',
+ 'flag_fm',
+ 'flag_md',
+ 'flag_mc',
+ 'flag_mn',
+ 'flag_me',
+ 'flag_ms',
+ 'flag_ma',
+ 'flag_mz',
+ 'flag_mm',
+ 'flag_na',
+ 'flag_nr',
+ 'flag_np',
+ 'flag_nl',
+ 'flag_nc',
+ 'flag_nz',
+ 'flag_ni',
+ 'flag_ne',
+ 'flag_ng',
+ 'flag_nu',
+ 'flag_kp',
+ 'flag_no',
+ 'flag_om',
+ 'flag_pk',
+ 'flag_pw',
+ 'flag_ps',
+ 'flag_pa',
+ 'flag_pg',
+ 'flag_py',
+ 'flag_pe',
+ 'flag_ph',
+ 'flag_pl',
+ 'flag_pt',
+ 'flag_pr',
+ 'flag_qa',
+ 'flag_ro',
+ 'flag_ru',
+ 'flag_rw',
+ 'flag_sh',
+ 'flag_kn',
+ 'flag_lc',
+ 'flag_vc',
+ 'flag_ws',
+ 'flag_sm',
+ 'flag_st',
+ 'flag_sa',
+ 'flag_sn',
+ 'flag_rs',
+ 'flag_sc',
+ 'flag_sl',
+ 'flag_sg',
+ 'flag_sk',
+ 'flag_si',
+ 'flag_sb',
+ 'flag_so',
+ 'flag_za',
+ 'flag_kr',
+ 'flag_es',
+ 'flag_lk',
+ 'flag_sd',
+ 'flag_sr',
+ 'flag_sz',
+ 'flag_se',
+ 'flag_ch',
+ 'flag_sy',
+ 'flag_tw',
+ 'flag_tj',
+ 'flag_tz',
+ 'flag_th',
+ 'flag_tl',
+ 'flag_tg',
+ 'flag_to',
+ 'flag_tt',
+ 'flag_tn',
+ 'flag_tr',
+ 'flag_tm',
+ 'flag_tv',
+ 'flag_ug',
+ 'flag_ua',
+ 'flag_ae',
+ 'flag_gb',
+ 'flag_us',
+ 'flag_vi',
+ 'flag_uy',
+ 'flag_uz',
+ 'flag_vu',
+ 'flag_va',
+ 'flag_ve',
+ 'flag_vn',
+ 'flag_wf',
+ 'flag_eh',
+ 'flag_ye',
+ 'flag_zm',
+ 'flag_zw',
+ 'flag_re',
+ 'flag_ax',
+ 'flag_ta',
+ 'flag_io',
+ 'flag_bq',
+ 'flag_cx',
+ 'flag_cc',
+ 'flag_gg',
+ 'flag_im',
+ 'flag_yt',
+ 'flag_nf',
+ 'flag_pn',
+ 'flag_bl',
+ 'flag_pm',
+ 'flag_gs',
+ 'flag_tk',
+ 'flag_bv',
+ 'flag_hm',
+ 'flag_sj',
+ 'flag_um',
+ 'flag_ic',
+ 'flag_ea',
+ 'flag_cp',
+ 'flag_dg',
+ 'flag_as',
+ 'flag_aq',
+ 'flag_vg',
+ 'flag_ck',
+ 'flag_cw',
+ 'flag_eu',
+ 'flag_gf',
+ 'flag_tf',
+ 'flag_gp',
+ 'flag_mq',
+ 'flag_mp',
+ 'flag_sx',
+ 'flag_ss',
+ 'flag_tc',
+ 'flag_mf'
+];
diff --git a/app/lib/realm.js b/app/lib/realm.js
index 17d30e26a..147660455 100644
--- a/app/lib/realm.js
+++ b/app/lib/realm.js
@@ -142,6 +142,21 @@ const url = {
}
};
+const messagesReactionsUsernamesSchema = {
+ name: 'messagesReactionsUsernames',
+ properties: {
+ value: 'string'
+ }
+};
+
+const messagesReactionsSchema = {
+ name: 'messagesReactions',
+ primaryKey: 'emoji',
+ properties: {
+ emoji: 'string',
+ usernames: { type: 'list', objectType: 'messagesReactionsUsernames' }
+ }
+};
const messagesEditedBySchema = {
name: 'messagesEditedBy',
@@ -173,7 +188,8 @@ const messagesSchema = {
status: { type: 'int', optional: true },
pinned: { type: 'bool', optional: true },
starred: { type: 'bool', optional: true },
- editedBy: 'messagesEditedBy'
+ editedBy: 'messagesEditedBy',
+ reactions: { type: 'list', objectType: 'messagesReactions' }
}
};
@@ -222,7 +238,9 @@ const schema = [
url,
frequentlyUsedEmojiSchema,
customEmojiAliasesSchema,
- customEmojisSchema
+ customEmojisSchema,
+ messagesReactionsSchema,
+ messagesReactionsUsernamesSchema
];
class DB {
databases = {
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js
index dc0bf1bd6..f56eb1c90 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat.js
@@ -1,6 +1,7 @@
import Random from 'react-native-meteor/lib/Random';
import { AsyncStorage, Platform } from 'react-native';
import { hashPassword } from 'react-native-meteor/lib/utils';
+import _ from 'lodash';
import RNFetchBlob from 'react-native-fetch-blob';
import reduxStore from './createStore';
@@ -264,6 +265,8 @@ const RocketChat = {
// loadHistory returns message.starred as object
// stream-room-messages returns message.starred as an array
message.starred = message.starred && (Array.isArray(message.starred) ? message.starred.length > 0 : !!message.starred);
+ message.reactions = _.map(message.reactions, (value, key) =>
+ ({ emoji: key, usernames: value.usernames.map(username => ({ value: username })) }));
return message;
},
loadMessagesForRoom(rid, end, cb) {
@@ -586,6 +589,9 @@ const RocketChat = {
},
setUserPresenceDefaultStatus(status) {
return call('UserPresence:setDefaultStatus', status);
+ },
+ setReaction(emoji, messageId) {
+ return call('setReaction', emoji, messageId);
}
};
diff --git a/app/reducers/messages.js b/app/reducers/messages.js
index 7c4022375..ae17f395e 100644
--- a/app/reducers/messages.js
+++ b/app/reducers/messages.js
@@ -8,7 +8,8 @@ const initialState = {
editing: false,
permalink: '',
showActions: false,
- showErrorActions: false
+ showErrorActions: false,
+ showReactionPicker: false
};
export default function messages(state = initialState, action) {
@@ -96,6 +97,12 @@ export default function messages(state = initialState, action) {
...state,
message: {}
};
+ case types.MESSAGES.TOGGLE_REACTION_PICKER:
+ return {
+ ...state,
+ showReactionPicker: !state.showReactionPicker,
+ actionMessage: action.message
+ };
default:
return state;
}
diff --git a/app/views/RoomView/ListView.js b/app/views/RoomView/ListView.js
index 3eacad9cb..9c89b46f5 100644
--- a/app/views/RoomView/ListView.js
+++ b/app/views/RoomView/ListView.js
@@ -4,10 +4,16 @@ import cloneReferencedElement from 'react-clone-referenced-element';
import { ScrollView, ListView as OldList2 } from 'react-native';
import moment from 'moment';
import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+
import DateSeparator from './DateSeparator';
import UnreadSeparator from './UnreadSeparator';
+import styles from './styles';
+import debounce from '../../utils/debounce';
+import Typing from '../../containers/Typing';
+import database from '../../lib/realm';
-const DEFAULT_SCROLL_CALLBACK_THROTTLE = 50;
+const DEFAULT_SCROLL_CALLBACK_THROTTLE = 100;
export class DataSource extends OldList.DataSource {
getRowData(sectionIndex: number, rowIndex: number): any {
@@ -20,9 +26,58 @@ export class DataSource extends OldList.DataSource {
}
}
+const ds = new DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
+
@connect(state => ({
lastOpen: state.room.lastOpen
}))
+
+export class List extends React.Component {
+ static propTypes = {
+ onEndReached: PropTypes.func,
+ renderFooter: PropTypes.func,
+ renderRow: PropTypes.func,
+ room: PropTypes.string,
+ end: PropTypes.bool
+ };
+ constructor(props) {
+ super(props);
+ this.data = database
+ .objects('messages')
+ .filtered('rid = $0', props.room)
+ .sorted('ts', true);
+ this.dataSource = ds.cloneWithRows(this.data);
+ }
+ componentDidMount() {
+ this.data.addListener(this.updateState);
+ }
+ shouldComponentUpdate(nextProps) {
+ return this.props.end !== nextProps.end;
+ }
+ updateState = debounce(() => {
+ // this.setState({
+ this.dataSource = this.dataSource.cloneWithRows(this.data);
+ this.forceUpdate();
+ // });
+ }, 100);
+
+ render() {
+ return ( }
+ onEndReached={() => this.props.onEndReached(this.data)}
+ dataSource={this.dataSource}
+ renderRow={item => this.props.renderRow(item)}
+ initialListSize={10}
+ keyboardShouldPersistTaps='always'
+ keyboardDismissMode='none'
+ />);
+ }
+}
+
export class ListView extends OldList2 {
constructor(props) {
super(props);
diff --git a/app/views/RoomView/ReactionPicker.js b/app/views/RoomView/ReactionPicker.js
new file mode 100644
index 000000000..96ff5fd2d
--- /dev/null
+++ b/app/views/RoomView/ReactionPicker.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { View, Platform } from 'react-native';
+import { connect } from 'react-redux';
+import Modal from 'react-native-modal';
+import { responsive } from 'react-native-responsive-ui';
+import EmojiPicker from '../../containers/EmojiPicker';
+import { toggleReactionPicker } from '../../actions/messages';
+import styles from './styles';
+
+const margin = Platform.OS === 'android' ? 40 : 20;
+const tabEmojiStyle = { fontSize: 15 };
+
+@connect(state => ({
+ showReactionPicker: state.messages.showReactionPicker
+}), dispatch => ({
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message))
+}))
+@responsive
+export default class extends React.Component {
+ static propTypes = {
+ window: PropTypes.any,
+ showReactionPicker: PropTypes.bool,
+ toggleReactionPicker: PropTypes.func,
+ onEmojiSelected: PropTypes.func
+ };
+
+ shouldComponentUpdate(nextProps) {
+ return nextProps.showReactionPicker !== this.props.showReactionPicker || this.props.window.width !== nextProps.window.width;
+ }
+
+ onEmojiSelected(emoji, shortname) {
+ // standard emojis: `emoji` is unicode and `shortname` is :joy:
+ // custom emojis: only `emoji` is returned with shortname type (:joy:)
+ // to set reactions, we need shortname type
+ this.props.onEmojiSelected(shortname || emoji);
+ }
+
+ render() {
+ const { width, height } = this.props.window;
+ return (
+ this.props.toggleReactionPicker()}
+ onBackButtonPress={() => this.props.toggleReactionPicker()}
+ animationIn='fadeIn'
+ animationOut='fadeOut'
+ >
+
+ this.onEmojiSelected(emoji, shortname)}
+ />
+
+
+ );
+ }
+}
diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js
index 94bbd4f12..dd76d5d43 100644
--- a/app/views/RoomView/index.js
+++ b/app/views/RoomView/index.js
@@ -1,44 +1,45 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Text, View, Button, SafeAreaView, Platform } from 'react-native';
+import { Text, View, Button, SafeAreaView, Platform, Keyboard } from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import equal from 'deep-equal';
import KeyboardSpacer from 'react-native-keyboard-spacer';
-import { ListView } from './ListView';
+import { List } from './ListView';
import * as actions from '../../actions';
import { openRoom, setLastOpen } from '../../actions/room';
-import { editCancel } from '../../actions/messages';
+import { editCancel, toggleReactionPicker } from '../../actions/messages';
+import { setKeyboardOpen, setKeyboardClosed } from '../../actions/keyboard';
import database from '../../lib/realm';
import RocketChat from '../../lib/rocketchat';
import Message from '../../containers/message';
import MessageActions from '../../containers/MessageActions';
import MessageErrorActions from '../../containers/MessageErrorActions';
import MessageBox from '../../containers/MessageBox';
-import Typing from '../../containers/Typing';
+
import Header from '../../containers/Header';
import RoomsHeader from './Header';
+import ReactionPicker from './ReactionPicker';
import Banner from './banner';
import styles from './styles';
-import debounce from '../../utils/debounce';
-
-const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1._id !== r2._id });
-
-const typing = () => ;
@connect(
state => ({
Site_Url: state.settings.Site_Url || state.server ? state.server.server : '',
Message_TimeFormat: state.settings.Message_TimeFormat,
loading: state.messages.isFetching,
- user: state.login.user
+ user: state.login.user,
+ actionMessage: state.messages.actionMessage
}),
dispatch => ({
actions: bindActionCreators(actions, dispatch),
openRoom: room => dispatch(openRoom(room)),
editCancel: () => dispatch(editCancel()),
- setLastOpen: date => dispatch(setLastOpen(date))
+ setLastOpen: date => dispatch(setLastOpen(date)),
+ toggleReactionPicker: message => dispatch(toggleReactionPicker(message)),
+ setKeyboardOpen: () => dispatch(setKeyboardOpen()),
+ setKeyboardClosed: () => dispatch(setKeyboardClosed())
})
)
export default class RoomView extends React.Component {
@@ -51,7 +52,12 @@ export default class RoomView extends React.Component {
rid: PropTypes.string,
name: PropTypes.string,
Site_Url: PropTypes.string,
- Message_TimeFormat: PropTypes.string
+ Message_TimeFormat: PropTypes.string,
+ loading: PropTypes.bool,
+ actionMessage: PropTypes.object,
+ toggleReactionPicker: PropTypes.func.isRequired,
+ setKeyboardOpen: PropTypes.func,
+ setKeyboardClosed: PropTypes.func
};
static navigationOptions = ({ navigation }) => ({
@@ -63,22 +69,17 @@ export default class RoomView extends React.Component {
this.rid =
props.rid ||
props.navigation.state.params.room.rid;
- this.name = this.props.name ||
- this.props.navigation.state.params.name ||
- this.props.navigation.state.params.room.name;
+ this.name = props.name ||
+ props.navigation.state.params.name ||
+ props.navigation.state.params.room.name;
this.opened = new Date();
- this.data = database
- .objects('messages')
- .filtered('rid = $0', this.rid)
- .sorted('ts', true);
- const rowIds = this.data.map((row, index) => index);
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
this.state = {
- dataSource: ds.cloneWithRows(this.data, rowIds),
loaded: true,
joined: typeof props.rid === 'undefined',
- readOnly: false
+ room: {}
};
+ this.onReactionPress = this.onReactionPress.bind(this);
}
componentWillMount() {
@@ -86,59 +87,58 @@ export default class RoomView extends React.Component {
title: this.name
});
this.updateRoom();
- this.props.openRoom({ rid: this.rid, name: this.name, ls: this.room.ls });
- if (this.room.alert || this.room.unread || this.room.userMentions) {
- this.props.setLastOpen(this.room.ls);
+ this.props.openRoom({ rid: this.rid, name: this.name, ls: this.state.room.ls });
+ if (this.state.room.alert || this.state.room.unread || this.state.room.userMentions) {
+ this.props.setLastOpen(this.state.room.ls);
} else {
this.props.setLastOpen(null);
}
- this.data.addListener(this.updateState);
+
this.rooms.addListener(this.updateRoom);
+ this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => this.props.setKeyboardOpen());
+ this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => this.props.setKeyboardClosed());
}
shouldComponentUpdate(nextProps, nextState) {
return !(equal(this.props, nextProps) && equal(this.state, nextState));
}
componentWillUnmount() {
clearTimeout(this.timer);
- this.data.removeAllListeners();
+ this.rooms.removeAllListeners();
+ this.keyboardDidShowListener.remove();
+ this.keyboardDidHideListener.remove();
this.props.editCancel();
}
- onEndReached = () => {
- if (
- // rowCount &&
- this.state.loaded &&
- this.state.loadingMore !== true &&
- this.state.end !== true
- ) {
- this.setState({
- loadingMore: true
- });
- requestAnimationFrame(() => {
- const lastRowData = this.data[this.data.length - 1];
- if (!lastRowData) {
- return;
- }
- RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => {
- this.setState({
- loadingMore: false,
- end
- });
- });
- });
+ onEndReached = (data) => {
+ if (this.props.loading || this.state.end) {
+ return;
}
+ if (!this.state.loaded) {
+ alert(2);
+ return;
+ }
+
+ requestAnimationFrame(() => {
+ const lastRowData = data[data.length - 1];
+ if (!lastRowData) {
+ return;
+ }
+ RocketChat.loadMessagesForRoom(this.rid, lastRowData.ts, ({ end }) => end && this.setState({
+ end
+ }));
+ });
}
- updateState = debounce(() => {
- const rowIds = this.data.map((row, index) => index);
- this.setState({
- dataSource: this.state.dataSource.cloneWithRows(this.data, rowIds)
- });
- }, 50);
+ onReactionPress = (shortname, messageId) => {
+ if (!messageId) {
+ RocketChat.setReaction(shortname, this.props.actionMessage._id);
+ return this.props.toggleReactionPicker();
+ }
+ RocketChat.setReaction(shortname, messageId);
+ };
updateRoom = () => {
- [this.room] = this.rooms;
- this.setState({ readOnly: this.room.ro });
+ this.setState({ room: this.rooms[0] });
}
sendMessage = message => RocketChat.sendMessage(this.rid, message).then(() => {
@@ -156,10 +156,12 @@ export default class RoomView extends React.Component {
);
@@ -174,7 +176,7 @@ export default class RoomView extends React.Component {
);
}
- if (this.state.readOnly) {
+ if (this.state.room.ro) {
return (
This room is read only
@@ -185,36 +187,28 @@ export default class RoomView extends React.Component {
};
renderHeader = () => {
- if (this.state.loadingMore) {
- return Loading more messages...;
- }
-
if (this.state.end) {
return Start of conversation;
}
+ return Loading more messages...;
}
render() {
return (
- this.renderItem(item)}
- initialListSize={10}
- keyboardShouldPersistTaps='always'
- keyboardDismissMode='none'
/>
{this.renderFooter()}
-
+ {this.state.room._id ? : null}
+
{Platform.OS === 'ios' ? : null}
);
diff --git a/app/views/RoomView/styles.js b/app/views/RoomView/styles.js
index 2e2713d05..6b68d2333 100644
--- a/app/views/RoomView/styles.js
+++ b/app/views/RoomView/styles.js
@@ -1,4 +1,4 @@
-import { StyleSheet } from 'react-native';
+import { StyleSheet, Platform } from 'react-native';
export default StyleSheet.create({
typing: { fontWeight: 'bold', paddingHorizontal: 15, height: 25 },
@@ -33,5 +33,13 @@ export default StyleSheet.create({
},
readOnly: {
padding: 10
+ },
+ reactionPickerContainer: {
+ // width: width - 20,
+ // height: width - 20,
+ paddingHorizontal: Platform.OS === 'android' ? 11 : 10,
+ backgroundColor: '#F7F7F7',
+ borderRadius: 4,
+ flexDirection: 'column'
}
});
diff --git a/package-lock.json b/package-lock.json
index df02c28ad..fb9a3f7ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1481,6 +1481,16 @@
"babel-helper-is-void-0": "0.2.0"
}
},
+ "babel-plugin-module-resolver": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-2.7.1.tgz",
+ "integrity": "sha1-GL48Qt31n3pFbJ4FEs2ROU9uS+E=",
+ "requires": {
+ "find-babel-config": "1.1.0",
+ "glob": "7.1.2",
+ "resolve": "1.5.0"
+ }
+ },
"babel-plugin-react-transform": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-3.0.0.tgz",
@@ -2172,6 +2182,18 @@
"semver": "5.4.1"
}
},
+ "babel-preset-expo": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-4.0.0.tgz",
+ "integrity": "sha512-EWFC6WJzZX5t2zZfLNdJXUkNMusUkxP5V+GrXaSk8pKbWGjE3TD2i33ncpF/4aQM9QGDm+SH6pImZJOqIDlRUw==",
+ "requires": {
+ "babel-plugin-module-resolver": "2.7.1",
+ "babel-plugin-transform-decorators-legacy": "1.3.4",
+ "babel-plugin-transform-exponentiation-operator": "6.24.1",
+ "babel-plugin-transform-export-extensions": "6.22.0",
+ "babel-preset-react-native": "4.0.0"
+ }
+ },
"babel-preset-fbjs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz",
@@ -4610,11 +4632,6 @@
"resolved": "https://registry.npmjs.org/email-validator/-/email-validator-1.1.1.tgz",
"integrity": "sha512-vkcJJZEb7JXDY883Nx1Lkmb6noM3j1SfSt8L9tVFhZPnPQiFq+Nkd5evc77+tRVS4ChTUSr34voThsglI/ja/A=="
},
- "emoji-datasource": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-4.0.3.tgz",
- "integrity": "sha1-1gDnDwVoMnyyjPp79B1T88SGeQE="
- },
"emoji-regex": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
@@ -5771,6 +5788,15 @@
}
}
},
+ "find-babel-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.1.0.tgz",
+ "integrity": "sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=",
+ "requires": {
+ "json5": "0.5.1",
+ "path-exists": "3.0.0"
+ }
+ },
"find-cache-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
@@ -12610,6 +12636,14 @@
"resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-3.0.1.tgz",
"integrity": "sha1-DiPbMC0Du0o/KNwHLcryqaEXjtg="
},
+ "react-native-responsive-ui": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/react-native-responsive-ui/-/react-native-responsive-ui-1.1.1.tgz",
+ "integrity": "sha1-60GDnU85Uf8CVmAYXDapqc4zdZ8=",
+ "requires": {
+ "lodash": "4.17.4"
+ }
+ },
"react-native-scrollable-tab-view": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/react-native-scrollable-tab-view/-/react-native-scrollable-tab-view-0.8.0.tgz",
@@ -14445,11 +14479,6 @@
"strip-ansi": "4.0.0"
}
},
- "string.fromcodepoint": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
- "integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM="
- },
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
diff --git a/package.json b/package.json
index 71bf39267..7dd888867 100644
--- a/package.json
+++ b/package.json
@@ -27,9 +27,9 @@
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-remove-console": "^6.8.5",
"babel-polyfill": "^6.26.0",
+ "babel-preset-expo": "^4.0.0",
"deep-equal": "^1.0.1",
"ejson": "^2.1.2",
- "emoji-datasource": "^4.0.3",
"lodash": "^4.17.4",
"moment": "^2.20.1",
"prop-types": "^15.6.0",
@@ -51,6 +51,7 @@
"react-native-modal": "^4.1.1",
"react-native-optimized-flatlist": "^1.0.3",
"react-native-push-notification": "^3.0.1",
+ "react-native-responsive-ui": "^1.1.1",
"react-native-scrollable-tab-view": "^0.8.0",
"react-native-slider": "^0.11.0",
"react-native-splash-screen": "^3.0.6",
@@ -72,7 +73,6 @@
"remote-redux-devtools": "^0.5.12",
"simple-markdown": "^0.3.1",
"snyk": "^1.61.1",
- "string.fromcodepoint": "^0.2.1",
"strip-ansi": "^4.0.0"
},
"devDependencies": {