diff options
author | Harrison Healey <harrisonmhealey@gmail.com> | 2016-07-05 11:58:18 -0400 |
---|---|---|
committer | Joram Wilander <jwawilander@gmail.com> | 2016-07-05 11:58:18 -0400 |
commit | dc2f2a800105b77e665ec2a00c6290f35b1a2ba3 (patch) | |
tree | 82f23c2e72a7c785f55c2d6c1c35c10c16994918 /webapp | |
parent | a65f1fc266f15eaa8f79541d4d11440c3d356bb6 (diff) | |
download | chat-dc2f2a800105b77e665ec2a00c6290f35b1a2ba3.tar.gz chat-dc2f2a800105b77e665ec2a00c6290f35b1a2ba3.tar.bz2 chat-dc2f2a800105b77e665ec2a00c6290f35b1a2ba3.zip |
PLT-3145 Custom Emojis (#3381)
* Reorganized Backstage code to use a view controller and separated it from integrations code
* Renamed InstalledIntegrations component to BackstageList
* Added EmojiList page
* Added AddEmoji page
* Added custom emoji to autocomplete and text formatter
* Moved system emoji to EmojiStore
* Stopped trying to get emoji before logging in
* Rerender posts when emojis change
* Fixed submit handler on backstage pages to properly support enter
* Removed debugging code
* Updated javascript driver
* Fixed unit tests
* Fixed backstage routes
* Added clientside validation to prevent users from creating an emoji with the same name as a system one
* Fixed AddEmoji page to properly redirect when an emoji is created successfully
* Fixed updating emoji list when an emoji is deleted
* Added type prop to BackstageList to properly support using a table for the list
* Added help text to EmojiList
* Fixed backstage on smaller screen sizes
* Disable custom emoji by default
* Improved restrictions on creating emojis
* Fixed non-admin users seeing the option to delete each other's emojis
* Fixing gofmt
* Fixed emoji unit tests
* Fixed trying to get emoji from the server when it's disabled
Diffstat (limited to 'webapp')
49 files changed, 1643 insertions, 8879 deletions
diff --git a/webapp/components/admin_console/custom_emoji_settings.jsx b/webapp/components/admin_console/custom_emoji_settings.jsx index 332c7b216..738afa3cd 100644 --- a/webapp/components/admin_console/custom_emoji_settings.jsx +++ b/webapp/components/admin_console/custom_emoji_settings.jsx @@ -27,7 +27,10 @@ export default class CustomEmojiSettings extends AdminSettings { getConfigFromState(config) { config.ServiceSettings.EnableCustomEmoji = this.state.enableCustomEmoji; - config.ServiceSettings.RestrictCustomEmojiCreation = this.state.restrictCustomEmojiCreation; + + if (global.window.mm_license.IsLicensed === 'true') { + config.ServiceSettings.RestrictCustomEmojiCreation = this.state.restrictCustomEmojiCreation; + } return config; } @@ -44,29 +47,14 @@ export default class CustomEmojiSettings extends AdminSettings { } renderSettings() { - return ( - <SettingsGroup> - <BooleanSetting - id='enableCustomEmoji' - label={ - <FormattedMessage - id='admin.customization.enableCustomEmojiTitle' - defaultMessage='Enable Custom Emoji:' - /> - } - helpText={ - <FormattedMessage - id='admin.customization.enableCustomEmojiDesc' - defaultMessage='Enable users to create custom emoji for use in chat messages.' - /> - } - value={this.state.enableCustomEmoji} - onChange={this.handleChange} - /> + let restrictSetting = null; + if (global.window.mm_license.IsLicensed === 'true') { + restrictSetting = ( <DropdownSetting id='restrictCustomEmojiCreation' values={[ {value: 'all', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationAll', 'Allow everyone to create custom emoji')}, + {value: 'admin', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationAdmin', 'Allow system and team admins to create custom emoji')}, {value: 'system_admin', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationSystemAdmin', 'Only allow system admins to create custom emoji')} ]} label={ @@ -85,6 +73,29 @@ export default class CustomEmojiSettings extends AdminSettings { onChange={this.handleChange} disabled={!this.state.enableCustomEmoji} /> + ); + } + + return ( + <SettingsGroup> + <BooleanSetting + id='enableCustomEmoji' + label={ + <FormattedMessage + id='admin.customization.enableCustomEmojiTitle' + defaultMessage='Enable Custom Emoji:' + /> + } + helpText={ + <FormattedMessage + id='admin.customization.enableCustomEmojiDesc' + defaultMessage='Enable users to create custom emoji for use in chat messages.' + /> + } + value={this.state.enableCustomEmoji} + onChange={this.handleChange} + /> + {restrictSetting} </SettingsGroup> ); } diff --git a/webapp/components/backstage/backstage_controller.jsx b/webapp/components/backstage/backstage_controller.jsx new file mode 100644 index 000000000..690880071 --- /dev/null +++ b/webapp/components/backstage/backstage_controller.jsx @@ -0,0 +1,71 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import TeamStore from 'stores/team_store.jsx'; + +import BackstageSidebar from './components/backstage_sidebar.jsx'; +import BackstageNavbar from './components/backstage_navbar.jsx'; +import ErrorBar from 'components/error_bar.jsx'; + +export default class BackstageController extends React.Component { + static get propTypes() { + return { + children: React.PropTypes.node.isRequired, + params: React.PropTypes.object.isRequired, + user: React.PropTypes.user.isRequired + }; + } + + constructor(props) { + super(props); + + this.onTeamChange = this.onTeamChange.bind(this); + + this.state = { + team: props.params.team ? TeamStore.getByName(props.params.team) : TeamStore.getCurrent() + }; + } + + componentDidMount() { + TeamStore.addChangeListener(this.onTeamChange); + } + + componentWillUnmount() { + TeamStore.removeChangeListener(this.onTeamChange); + } + + onTeamChange() { + this.state = { + team: this.props.params.team ? TeamStore.getByName(this.props.params.team) : TeamStore.getCurrent() + }; + } + + render() { + return ( + <div className='backstage'> + <ErrorBar/> + <BackstageNavbar team={this.state.team}/> + <div className='backstage-body'> + <BackstageSidebar + team={this.state.team} + user={this.props.user} + /> + { + React.Children.map(this.props.children, (child) => { + if (!child) { + return child; + } + + return React.cloneElement(child, { + team: this.state.team, + user: this.props.user + }); + }) + } + </div> + </div> + ); + } +}
\ No newline at end of file diff --git a/webapp/components/backstage/backstage_category.jsx b/webapp/components/backstage/components/backstage_category.jsx index 1d4b11ca3..74dcf3476 100644 --- a/webapp/components/backstage/backstage_category.jsx +++ b/webapp/components/backstage/components/backstage_category.jsx @@ -59,6 +59,7 @@ export default class BackstageCategory extends React.Component { to={link} className='category-title' activeClassName='category-title--active' + onlyActiveOnIndex={true} > <i className={'fa ' + icon}/> <span className='category-title__text'> diff --git a/webapp/components/backstage/backstage_header.jsx b/webapp/components/backstage/components/backstage_header.jsx index 37b4be349..37b4be349 100644 --- a/webapp/components/backstage/backstage_header.jsx +++ b/webapp/components/backstage/components/backstage_header.jsx diff --git a/webapp/components/backstage/components/backstage_list.jsx b/webapp/components/backstage/components/backstage_list.jsx new file mode 100644 index 000000000..81b8ec4d9 --- /dev/null +++ b/webapp/components/backstage/components/backstage_list.jsx @@ -0,0 +1,108 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import * as Utils from 'utils/utils.jsx'; + +import {Link} from 'react-router'; +import LoadingScreen from 'components/loading_screen.jsx'; + +export default class BackstageList extends React.Component { + static propTypes = { + children: React.PropTypes.node, + header: React.PropTypes.node.isRequired, + addLink: React.PropTypes.string, + addText: React.PropTypes.node, + emptyText: React.PropTypes.node, + loading: React.PropTypes.bool.isRequired, + searchPlaceholder: React.PropTypes.string + } + + static defaultProps = { + searchPlaceholder: Utils.localizeMessage('backstage.search', 'Search') + } + + constructor(props) { + super(props); + + this.updateFilter = this.updateFilter.bind(this); + + this.state = { + filter: '' + }; + } + + updateFilter(e) { + this.setState({ + filter: e.target.value + }); + } + + render() { + const filter = this.state.filter.toLowerCase(); + + let children; + if (this.props.loading) { + children = <LoadingScreen/>; + } else { + children = React.Children.map(this.props.children, (child) => { + return React.cloneElement(child, {filter}); + }); + + if (children.length === 0 && this.props.emptyText) { + children = ( + <span className='backstage-list__item backstage-list__empty'> + {this.props.emptyText} + </span> + ); + } + } + + let addLink = null; + if (this.props.addLink && this.props.addText) { + addLink = ( + <Link + className='add-link' + to={this.props.addLink} + > + <button + type='button' + className='btn btn-primary' + > + <span> + {this.props.addText} + </span> + </button> + </Link> + ); + } + + return ( + <div className='backstage-content'> + <div className='backstage-header'> + <h1> + {this.props.header} + </h1> + {addLink} + </div> + <div className='backstage-filters'> + <div className='backstage-filter__search'> + <i className='fa fa-search'></i> + <input + type='search' + className='form-control' + placeholder={this.props.searchPlaceholder} + value={this.state.filter} + onChange={this.updateFilter} + style={{flexGrow: 0, flexShrink: 0}} + /> + </div> + </div> + <div className='backstage-list'> + {children} + </div> + </div> + ); + } +} diff --git a/webapp/components/backstage/backstage_navbar.jsx b/webapp/components/backstage/components/backstage_navbar.jsx index 26ab44c87..7bccfc9f7 100644 --- a/webapp/components/backstage/backstage_navbar.jsx +++ b/webapp/components/backstage/components/backstage_navbar.jsx @@ -1,52 +1,28 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; - import React from 'react'; -import TeamStore from 'stores/team_store.jsx'; - import {FormattedMessage} from 'react-intl'; import {Link} from 'react-router/es6'; export default class BackstageNavbar extends React.Component { - constructor(props) { - super(props); - - this.handleChange = this.handleChange.bind(this); - - this.state = { - team: TeamStore.getCurrent() + static get propTypes() { + return { + team: React.propTypes.object.isRequired }; } - componentDidMount() { - TeamStore.addChangeListener(this.handleChange); - $('body').addClass('backstage'); - } - - componentWillUnmount() { - TeamStore.removeChangeListener(this.handleChange); - $('body').removeClass('backstage'); - } - - handleChange() { - this.setState({ - team: TeamStore.getCurrent() - }); - } - render() { - if (!this.state.team) { + if (!this.props.team) { return null; } return ( - <div className='backstage-navbar row'> + <div className='backstage-navbar'> <Link className='backstage-navbar__back' - to={`/${this.state.team.name}/channels/town-square`} + to={`/${this.props.team.name}/channels/town-square`} > <i className='fa fa-angle-left'/> <span> diff --git a/webapp/components/backstage/backstage_section.jsx b/webapp/components/backstage/components/backstage_section.jsx index c8b63af18..c8b63af18 100644 --- a/webapp/components/backstage/backstage_section.jsx +++ b/webapp/components/backstage/components/backstage_section.jsx diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/components/backstage_sidebar.jsx index 4d8d8337d..a17d830b0 100644 --- a/webapp/components/backstage/backstage_sidebar.jsx +++ b/webapp/components/backstage/components/backstage_sidebar.jsx @@ -3,13 +3,51 @@ import React from 'react'; -import * as Utils from 'utils/utils.jsx'; +import TeamStore from 'stores/team_store.jsx'; + import BackstageCategory from './backstage_category.jsx'; import BackstageSection from './backstage_section.jsx'; import {FormattedMessage} from 'react-intl'; export default class BackstageSidebar extends React.Component { - render() { + static get propTypes() { + return { + team: React.PropTypes.object.isRequired, + user: React.PropTypes.object.isRequired + }; + } + + renderCustomEmoji() { + if (window.mm_config.EnableCustomEmoji !== 'true') { + return null; + } + + return ( + <BackstageCategory + name='emoji' + parentLink={'/' + this.props.team.name} + icon='fa-smile-o' + title={ + <FormattedMessage + id='backstage_sidebar.emoji' + defaultMessage='Custom Emoji' + /> + } + /> + ); + } + + renderIntegrations() { + if (window.mm_config.EnableIncomingWebhooks !== 'true' && + window.mm_config.EnableOutgoingWebhooks !== 'true' && + window.mm_config.EnableCommands !== 'true') { + return null; + } + + if (window.mm_config.RestrictCustomEmojiCreation !== 'all' && !TeamStore.isTeamAdmin(this.props.user.id, this.props.team.id)) { + return null; + } + let incomingWebhooks = null; if (window.mm_config.EnableIncomingWebhooks === 'true') { incomingWebhooks = ( @@ -56,23 +94,30 @@ export default class BackstageSidebar extends React.Component { } return ( + <BackstageCategory + name='integrations' + parentLink={'/' + this.props.team.name} + icon='fa-link' + title={ + <FormattedMessage + id='backstage_sidebar.integrations' + defaultMessage='Integrations' + /> + } + > + {incomingWebhooks} + {outgoingWebhooks} + {commands} + </BackstageCategory> + ); + } + + render() { + return ( <div className='backstage-sidebar'> <ul> - <BackstageCategory - name='integrations' - parentLink={'/' + Utils.getTeamNameFromUrl() + '/settings'} - icon='fa-link' - title={ - <FormattedMessage - id='backstage_sidebar.integrations' - defaultMessage='Integrations' - /> - } - > - {incomingWebhooks} - {outgoingWebhooks} - {commands} - </BackstageCategory> + {this.renderCustomEmoji()} + {this.renderIntegrations()} </ul> </div> ); diff --git a/webapp/components/backstage/installed_integrations.jsx b/webapp/components/backstage/installed_integrations.jsx deleted file mode 100644 index f6de8bc11..000000000 --- a/webapp/components/backstage/installed_integrations.jsx +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import * as Utils from 'utils/utils.jsx'; - -import {Link} from 'react-router/es6'; -import LoadingScreen from 'components/loading_screen.jsx'; - -export default class InstalledIntegrations extends React.Component { - static get propTypes() { - return { - children: React.PropTypes.node, - header: React.PropTypes.node.isRequired, - addLink: React.PropTypes.string.isRequired, - addText: React.PropTypes.node.isRequired, - emptyText: React.PropTypes.node.isRequired, - loading: React.PropTypes.bool.isRequired - }; - } - - constructor(props) { - super(props); - - this.updateFilter = this.updateFilter.bind(this); - - this.state = { - filter: '' - }; - } - - updateFilter(e) { - this.setState({ - filter: e.target.value - }); - } - - render() { - const filter = this.state.filter.toLowerCase(); - - let children; - - if (this.props.loading) { - children = <LoadingScreen/>; - } else { - children = React.Children.map(this.props.children, (child) => { - return React.cloneElement(child, {filter}); - }); - - if (children.length === 0) { - children = ( - <span className='backstage-list__item backstage-list_empty'> - {this.props.emptyText} - </span> - ); - } - } - - return ( - <div className='backstage-content'> - <div className='installed-integrations'> - <div className='backstage-header'> - <h1> - {this.props.header} - </h1> - <Link - className='add-integrations-link' - to={this.props.addLink} - > - <button - type='button' - className='btn btn-primary' - > - <span> - {this.props.addText} - </span> - </button> - </Link> - </div> - <div className='backstage-filters'> - <div className='backstage-filter__search'> - <i className='fa fa-search'></i> - <input - type='search' - className='form-control' - placeholder={Utils.localizeMessage('installed_integrations.search', 'Search Integrations')} - value={this.state.filter} - onChange={this.updateFilter} - style={{flexGrow: 0, flexShrink: 0}} - /> - </div> - </div> - <div className='backstage-list'> - {children} - </div> - </div> - </div> - ); - } -} diff --git a/webapp/components/emoji/components/add_emoji.jsx b/webapp/components/emoji/components/add_emoji.jsx new file mode 100644 index 000000000..46f345476 --- /dev/null +++ b/webapp/components/emoji/components/add_emoji.jsx @@ -0,0 +1,307 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import * as AsyncClient from 'utils/async_client.jsx'; +import EmojiStore from 'stores/emoji_store.jsx'; + +import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; +import {FormattedMessage} from 'react-intl'; +import FormError from 'components/form_error.jsx'; +import {Link} from 'react-router'; +import SpinnerButton from 'components/spinner_button.jsx'; + +export default class AddEmoji extends React.Component { + static propTypes = { + team: React.PropTypes.object.isRequired, + user: React.PropTypes.object.isRequired + } + + static contextTypes = { + router: React.PropTypes.object.isRequired + } + + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + + this.updateName = this.updateName.bind(this); + this.updateImage = this.updateImage.bind(this); + + this.state = { + name: '', + image: null, + imageUrl: '', + saving: false, + error: null + }; + } + + handleSubmit(e) { + e.preventDefault(); + + if (this.state.saving) { + return; + } + + this.setState({ + saving: true, + error: null + }); + + const emoji = { + creator_id: this.props.user.id, + name: this.state.name.trim().toLowerCase() + }; + + if (!emoji.name) { + this.setState({ + saving: false, + error: ( + <FormattedMessage + id='add_emoji.nameRequired' + defaultMessage='A name is required for the emoji' + /> + ) + }); + + return; + } else if (/[^a-z0-9_-]/.test(emoji.name)) { + this.setState({ + saving: false, + error: ( + <FormattedMessage + id='add_emoji.nameInvalid' + defaultMessage="An emoji's name can only contain lowercase letters, numbers, and the symbols '-' and '_'." + /> + ) + }); + + return; + } else if (EmojiStore.getSystemEmojis().has(emoji.name)) { + this.setState({ + saving: false, + error: ( + <FormattedMessage + id='add_emoji.nameTaken' + defaultMessage='This name is already in use by a system emoji. Please choose another name.' + /> + ) + }); + + return; + } + + if (!this.state.image) { + this.setState({ + saving: false, + error: ( + <FormattedMessage + id='add_emoji.imageRequired' + defaultMessage='An image is required for the emoji' + /> + ) + }); + + return; + } + + AsyncClient.addEmoji( + emoji, + this.state.image, + () => { + // for some reason, browserHistory.push doesn't trigger a state change even though the url changes + this.context.router.push('/' + this.props.team.name + '/emoji'); + }, + (err) => { + this.setState({ + saving: false, + error: err.message + }); + } + ); + } + + updateName(e) { + this.setState({ + name: e.target.value + }); + } + + updateImage(e) { + if (e.target.files.length === 0) { + this.setState({ + image: null, + imageUrl: '' + }); + + return; + } + + const image = e.target.files[0]; + + const reader = new FileReader(); + reader.onload = () => { + this.setState({ + image, + imageUrl: reader.result + }); + }; + reader.readAsDataURL(image); + } + + render() { + let filename = null; + if (this.state.image) { + filename = ( + <span className='add-emoji__filename'> + {this.state.image.name} + </span> + ); + } + + let preview = null; + if (this.state.imageUrl) { + preview = ( + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='preview' + > + <FormattedMessage + id='add_emoji.preview' + defaultMessage='Preview' + /> + </label> + <div className='col-md-5 col-sm-8 add-emoji__preview'> + <FormattedMessage + id='add_emoji.preview.sentence' + defaultMessage='This is a sentence with {image} in it.' + values={{ + image: ( + <img + className='emoticon' + src={this.state.imageUrl} + /> + ) + }} + /> + </div> + </div> + ); + } + + return ( + <div className='backstage-content row'> + <BackstageHeader> + <Link to={'/' + this.props.team.name + '/emoji'}> + <FormattedMessage + id='emoji_list.header' + defaultMessage='Custom Emoji' + /> + </Link> + <FormattedMessage + id='add_emoji.header' + defaultMessage='Add' + /> + </BackstageHeader> + <div className='backstage-form'> + <form + className='form-horizontal' + onSubmit={this.handleSubmit} + > + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='name' + > + <FormattedMessage + id='add_emoji.name' + defaultMessage='Name' + /> + </label> + <div className='col-md-5 col-sm-8'> + <input + id='name' + type='text' + maxLength='64' + className='form-control' + value={this.state.name} + onChange={this.updateName} + /> + <div className='form__help'> + <FormattedMessage + id='add_emoji.name.help' + defaultMessage="Choose a name for your emoji made of up to 64 characters consisting of lowercase letters, numbers, and the symbols '-' and '_'." + /> + </div> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='image' + > + <FormattedMessage + id='add_emoji.image' + defaultMessage='Image' + /> + </label> + <div className='col-md-5 col-sm-8'> + <div> + <div className='add-emoji__upload'> + <button className='btn btn-primary'> + <FormattedMessage + id='add_emoji.image.button' + defaultMessage='Select' + /> + </button> + <input + type='file' + accept='.jpg,.png,.gif' + multiple={false} + onChange={this.updateImage} + /> + </div> + {filename} + <div className='form__help'> + <FormattedMessage + id='add_emoji.image.help' + defaultMessage='Choose the image for your emoji. The image can be a gif, png, or jpeg file with a max size of 64 KB and dimensions up to 128 by 128 pixels.' + /> + </div> + </div> + </div> + </div> + {preview} + <div className='backstage-form__footer'> + <FormError error={this.state.error}/> + <Link + className='btn btn-sm' + to={'/' + this.props.team.name + '/emoji'} + > + <FormattedMessage + id='add_emoji.cancel' + defaultMessage='Cancel' + /> + </Link> + <SpinnerButton + className='btn btn-primary' + type='submit' + spinning={this.state.saving} + onClick={this.handleSubmit} + > + <FormattedMessage + id='add_emoji.save' + defaultMessage='Save' + /> + </SpinnerButton> + </div> + </form> + </div> + </div> + ); + } +} diff --git a/webapp/components/emoji/components/emoji_list.jsx b/webapp/components/emoji/components/emoji_list.jsx new file mode 100644 index 000000000..5795a57b2 --- /dev/null +++ b/webapp/components/emoji/components/emoji_list.jsx @@ -0,0 +1,218 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import * as AsyncClient from 'utils/async_client.jsx'; +import EmojiStore from 'stores/emoji_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import {FormattedMessage} from 'react-intl'; +import EmojiListItem from './emoji_list_item.jsx'; +import {Link} from 'react-router'; +import LoadingScreen from 'components/loading_screen.jsx'; + +export default class EmojiList extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired, + user: React.propTypes.object.isRequired + }; + } + + constructor(props) { + super(props); + + this.canCreateEmojis = this.canCreateEmojis.bind(this); + + this.handleEmojiChange = this.handleEmojiChange.bind(this); + + this.deleteEmoji = this.deleteEmoji.bind(this); + + this.updateFilter = this.updateFilter.bind(this); + + this.state = { + emojis: EmojiStore.getCustomEmojiMap(), + loading: !EmojiStore.hasReceivedCustomEmojis(), + filter: '' + }; + } + + componentDidMount() { + EmojiStore.addChangeListener(this.handleEmojiChange); + + if (window.mm_config.EnableCustomEmoji === 'true') { + AsyncClient.listEmoji(); + } + } + + componentWillUnmount() { + EmojiStore.removeChangeListener(this.handleEmojiChange); + } + + handleEmojiChange() { + this.setState({ + emojis: EmojiStore.getCustomEmojiMap(), + loading: !EmojiStore.hasReceivedCustomEmojis() + }); + } + + updateFilter(e) { + this.setState({ + filter: e.target.value + }); + } + + deleteEmoji(emoji) { + AsyncClient.deleteEmoji(emoji.id); + } + + canCreateEmojis() { + if (global.window.mm_license.IsLicensed !== 'true') { + return true; + } + + if (Utils.isSystemAdmin(this.props.user.roles)) { + return true; + } + + if (window.mm_config.RestrictCustomEmojiCreation === 'all') { + return true; + } + + if (window.mm_config.RestrictCustomEmojiCreation === 'admin') { + // check whether the user is an admin on any of their teams + for (const member of TeamStore.getTeamMembers()) { + if (Utils.isAdmin(member.roles)) { + return true; + } + } + } + + return false; + } + + render() { + const filter = this.state.filter.toLowerCase(); + const isSystemAdmin = Utils.isSystemAdmin(this.props.user.roles); + + let emojis = []; + if (this.state.loading) { + emojis.push( + <LoadingScreen key='loading'/> + ); + } else if (this.state.emojis.length === 0) { + emojis.push( + <tr className='backstage-list__item backstage-list__empty'> + <td colSpan='4'> + <FormattedMessage + id='emoji_list.empty' + defaultMessage='No custom emoji found' + /> + </td> + </tr> + ); + } else { + for (const [, emoji] of this.state.emojis) { + let onDelete = null; + if (isSystemAdmin || this.props.user.id === emoji.creator_id) { + onDelete = this.deleteEmoji; + } + + emojis.push( + <EmojiListItem + key={emoji.id} + emoji={emoji} + onDelete={onDelete} + filter={filter} + /> + ); + } + } + + let addLink = null; + if (this.canCreateEmojis()) { + addLink = ( + <Link + className='add-link' + to={'/' + this.props.team.name + '/emoji/add'} + > + <button + type='button' + className='btn btn-primary' + > + <FormattedMessage + id='emoji_list.add' + defaultMessage='Add Custom Emoji' + /> + </button> + </Link> + ); + } + + return ( + <div className='backstage-content emoji-list'> + <div className='backstage-header'> + <h1> + <FormattedMessage + id='emoji_list.header' + defaultMessage='Custom Emoji' + /> + </h1> + {addLink} + </div> + <div className='backstage-filters'> + <div className='backstage-filter__search'> + <i className='fa fa-search'></i> + <input + type='search' + className='form-control' + placeholder={Utils.localizeMessage('emoji_list.search', 'Search Custom Emoji')} + value={this.state.filter} + onChange={this.updateFilter} + style={{flexGrow: 0, flexShrink: 0}} + /> + </div> + </div> + <span className='emoji-list__help'> + <FormattedMessage + id='emoji_list.help' + defaultMessage='Custom emoji are available to everyone on your server and will show up in the emoji autocomplete menu.' + /> + </span> + <div className='backstage-list'> + <table className='emoji-list__table'> + <tr className='backstage-list__item emoji-list__table-header'> + <th className='emoji-list__name'> + <FormattedMessage + id='emoji_list.name' + defaultMessage='Name' + /> + </th> + <th className='emoji-list__image'> + <FormattedMessage + id='emoji_list.image' + defaultMessage='Image' + /> + </th> + <th className='emoji-list__creator'> + <FormattedMessage + id='emoji_list.creator' + defaultMessage='Creator' + /> + </th> + <th className='emoji-list_actions'> + <FormattedMessage + id='emoji_list.actions' + defaultMessage='Actions' + /> + </th> + </tr> + {emojis} + </table> + </div> + </div> + ); + } +} diff --git a/webapp/components/emoji/components/emoji_list_item.jsx b/webapp/components/emoji/components/emoji_list_item.jsx new file mode 100644 index 000000000..50a4bacb1 --- /dev/null +++ b/webapp/components/emoji/components/emoji_list_item.jsx @@ -0,0 +1,118 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import EmojiStore from 'stores/emoji_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import {FormattedMessage} from 'react-intl'; + +export default class EmojiListItem extends React.Component { + static get propTypes() { + return { + emoji: React.PropTypes.object.isRequired, + onDelete: React.PropTypes.func.isRequired, + filter: React.PropTypes.string + }; + } + + constructor(props) { + super(props); + + this.handleDelete = this.handleDelete.bind(this); + + this.state = { + creator: UserStore.getProfile(this.props.emoji.creator_id) + }; + } + + handleDelete(e) { + e.preventDefault(); + + this.props.onDelete(this.props.emoji); + } + + matchesFilter(emoji, creator, filter) { + if (!filter) { + return true; + } + + if (emoji.name.toLowerCase().indexOf(filter) !== -1) { + return true; + } + + if (creator) { + if (creator.username.toLowerCase().indexOf(filter) !== -1 || + (creator.first_name && creator.first_name.toLowerCase().indexOf(filter)) || + (creator.last_name && creator.last_name.toLowerCase().indexOf(filter)) || + (creator.nickname && creator.nickname.toLowerCase().indexOf(filter))) { + return true; + } + } + + return false; + } + + render() { + const emoji = this.props.emoji; + const creator = this.state.creator; + const filter = this.props.filter ? this.props.filter.toLowerCase() : ''; + + if (!this.matchesFilter(emoji, creator, filter)) { + return null; + } + + let creatorName; + if (creator) { + creatorName = Utils.displayUsernameForUser(creator); + + if (creatorName !== creator.username) { + creatorName += ' (@' + creator.username + ')'; + } + } else { + creatorName = ( + <FormattedMessage + id='emoji_list.somebody' + defaultMessage='Somebody on another team' + /> + ); + } + + let deleteButton = null; + if (this.props.onDelete) { + deleteButton = ( + <a + href='#' + onClick={this.handleDelete} + > + <FormattedMessage + id='emoji_list.delete' + defaultMessage='Delete' + /> + </a> + ); + } + + return ( + <tr className='backstage-list__item'> + <td className='emoji-list__name'> + {':' + emoji.name + ':'} + </td> + <td className='emoji-list__image'> + <img + className='emoticon' + src={EmojiStore.getEmojiImageUrl(emoji)} + /> + </td> + <td className='emoji-list__creator'> + {creatorName} + </td> + <td className='emoji-list-item_actions'> + {deleteButton} + </td> + </tr> + ); + } +} diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/integrations/components/add_command.jsx index 91af0416b..e72670e47 100644 --- a/webapp/components/backstage/add_command.jsx +++ b/webapp/components/integrations/components/add_command.jsx @@ -6,7 +6,7 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Utils from 'utils/utils.jsx'; -import BackstageHeader from './backstage_header.jsx'; +import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; import {FormattedMessage} from 'react-intl'; import FormError from 'components/form_error.jsx'; import {browserHistory, Link} from 'react-router/es6'; @@ -17,6 +17,12 @@ const REQUEST_POST = 'P'; const REQUEST_GET = 'G'; export default class AddCommand extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired + }; + } + constructor(props) { super(props); @@ -155,7 +161,7 @@ export default class AddCommand extends React.Component { AsyncClient.addCommand( command, () => { - browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'); + browserHistory.push('/' + this.props.team.name + '/integrations/commands'); }, (err) => { this.setState({ @@ -300,7 +306,7 @@ export default class AddCommand extends React.Component { return ( <div className='backstage-content row'> <BackstageHeader> - <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'}> + <Link to={'/' + this.props.team.name + '/integrations/commands'}> <FormattedMessage id='installed_command.header' defaultMessage='Slash Commands' @@ -312,7 +318,10 @@ export default class AddCommand extends React.Component { /> </BackstageHeader> <div className='backstage-form'> - <form className='form-horizontal'> + <form + className='form-horizontal' + onSubmit={this.handleSubmit} + > <div className='form-group'> <label className='control-label col-sm-4' @@ -531,7 +540,7 @@ export default class AddCommand extends React.Component { <FormError errors={[this.state.serverError, this.state.clientError]}/> <Link className='btn btn-sm' - to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'} + to={'/' + this.props.team.name + '/integrations/commands'} > <FormattedMessage id='add_command.cancel' diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/integrations/components/add_incoming_webhook.jsx index 528f03377..122600c90 100644 --- a/webapp/components/backstage/add_incoming_webhook.jsx +++ b/webapp/components/integrations/components/add_incoming_webhook.jsx @@ -4,9 +4,8 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Utils from 'utils/utils.jsx'; -import BackstageHeader from './backstage_header.jsx'; +import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; import ChannelSelect from 'components/channel_select.jsx'; import {FormattedMessage} from 'react-intl'; import FormError from 'components/form_error.jsx'; @@ -14,6 +13,12 @@ import {browserHistory, Link} from 'react-router/es6'; import SpinnerButton from 'components/spinner_button.jsx'; export default class AddIncomingWebhook extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired + }; + } + constructor(props) { super(props); @@ -69,7 +74,7 @@ export default class AddIncomingWebhook extends React.Component { AsyncClient.addIncomingHook( hook, () => { - browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'); + browserHistory.push('/' + this.props.team.name + '/integrations/incoming_webhooks'); }, (err) => { this.setState({ @@ -102,7 +107,7 @@ export default class AddIncomingWebhook extends React.Component { return ( <div className='backstage-content'> <BackstageHeader> - <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'}> + <Link to={'/' + this.props.team.name + '/integrations/incoming_webhooks'}> <FormattedMessage id='installed_incoming_webhooks.header' defaultMessage='Incoming Webhooks' @@ -114,7 +119,10 @@ export default class AddIncomingWebhook extends React.Component { /> </BackstageHeader> <div className='backstage-form'> - <form className='form-horizontal'> + <form + className='form-horizontal' + onSubmit={this.handleSubmit} + > <div className='form-group'> <label className='control-label col-sm-4' @@ -181,7 +189,7 @@ export default class AddIncomingWebhook extends React.Component { <FormError errors={[this.state.serverError, this.state.clientError]}/> <Link className='btn btn-sm' - to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'} + to={'/' + this.props.team.name + '/integrations/incoming_webhooks'} > <FormattedMessage id='add_incoming_webhook.cancel' diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/integrations/components/add_outgoing_webhook.jsx index 5f9d96249..bd49fedc9 100644 --- a/webapp/components/backstage/add_outgoing_webhook.jsx +++ b/webapp/components/integrations/components/add_outgoing_webhook.jsx @@ -4,9 +4,8 @@ import React from 'react'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Utils from 'utils/utils.jsx'; -import BackstageHeader from './backstage_header.jsx'; +import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; import ChannelSelect from 'components/channel_select.jsx'; import {FormattedMessage} from 'react-intl'; import FormError from 'components/form_error.jsx'; @@ -14,6 +13,12 @@ import {browserHistory, Link} from 'react-router/es6'; import SpinnerButton from 'components/spinner_button.jsx'; export default class AddOutgoingWebhook extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired + }; + } + constructor(props) { super(props); @@ -112,7 +117,7 @@ export default class AddOutgoingWebhook extends React.Component { AsyncClient.addOutgoingHook( hook, () => { - browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'); + browserHistory.push('/' + this.props.team.name + '/integrations/outgoing_webhooks'); }, (err) => { this.setState({ @@ -165,7 +170,7 @@ export default class AddOutgoingWebhook extends React.Component { return ( <div className='backstage-content'> <BackstageHeader> - <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'}> + <Link to={'/' + this.props.team.name + '/integrations/outgoing_webhooks'}> <FormattedMessage id='installed_outgoing_webhooks.header' defaultMessage='Outgoing Webhooks' @@ -177,7 +182,10 @@ export default class AddOutgoingWebhook extends React.Component { /> </BackstageHeader> <div className='backstage-form'> - <form className='form-horizontal'> + <form + className='form-horizontal' + onSubmit={this.handleSubmit} + > <div className='form-group'> <label className='control-label col-sm-4' @@ -314,7 +322,7 @@ export default class AddOutgoingWebhook extends React.Component { <FormError errors={[this.state.serverError, this.state.clientError]}/> <Link className='btn btn-sm' - to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'} + to={'/' + this.props.team.name + '/integrations/outgoing_webhooks'} > <FormattedMessage id='add_outgoing_webhook.cancel' diff --git a/webapp/components/backstage/installed_command.jsx b/webapp/components/integrations/components/installed_command.jsx index 88f43f674..658126f19 100644 --- a/webapp/components/backstage/installed_command.jsx +++ b/webapp/components/integrations/components/installed_command.jsx @@ -50,8 +50,9 @@ export default class InstalledCommand extends React.Component { render() { const command = this.props.command; + const filter = this.props.filter ? this.props.filter.toLowerCase() : ''; - if (!this.matchesFilter(command, this.props.filter)) { + if (!this.matchesFilter(command, filter)) { return null; } @@ -61,7 +62,7 @@ export default class InstalledCommand extends React.Component { } else { name = ( <FormattedMessage - id='installed_integraions.unnamed_command' + id='installed_commands.unnamed_command' defaultMessage='Unnamed Slash Command' /> ); diff --git a/webapp/components/backstage/installed_commands.jsx b/webapp/components/integrations/components/installed_commands.jsx index df1f56687..597ba7005 100644 --- a/webapp/components/backstage/installed_commands.jsx +++ b/webapp/components/integrations/components/installed_commands.jsx @@ -8,11 +8,17 @@ import IntegrationStore from 'stores/integration_store.jsx'; import TeamStore from 'stores/team_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import BackstageList from 'components/backstage/components/backstage_list.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledCommand from './installed_command.jsx'; -import InstalledIntegrations from './installed_integrations.jsx'; export default class InstalledCommands extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired + }; + } + constructor(props) { super(props); @@ -71,7 +77,7 @@ export default class InstalledCommands extends React.Component { }); return ( - <InstalledIntegrations + <BackstageList header={ <FormattedMessage id='installed_commands.header' @@ -84,17 +90,18 @@ export default class InstalledCommands extends React.Component { defaultMessage='Add Slash Command' /> } - addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands/add'} + addLink={'/' + this.props.team.name + '/integrations/commands/add'} emptyText={ <FormattedMessage id='installed_commands.empty' defaultMessage='No slash commands found' /> } + searchPlaceholder={Utils.localizeMessage('installed_commands.search', 'Search Slash Commands')} loading={this.state.loading} > {commands} - </InstalledIntegrations> + </BackstageList> ); } } diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/integrations/components/installed_incoming_webhook.jsx index afa6e9958..2cf3f24b8 100644 --- a/webapp/components/backstage/installed_incoming_webhook.jsx +++ b/webapp/components/integrations/components/installed_incoming_webhook.jsx @@ -51,8 +51,9 @@ export default class InstalledIncomingWebhook extends React.Component { render() { const incomingWebhook = this.props.incomingWebhook; const channel = ChannelStore.get(incomingWebhook.channel_id); + const filter = this.props.filter ? this.props.filter.toLowerCase() : ''; - if (!this.matchesFilter(incomingWebhook, channel, this.props.filter)) { + if (!this.matchesFilter(incomingWebhook, channel, filter)) { return null; } diff --git a/webapp/components/backstage/installed_incoming_webhooks.jsx b/webapp/components/integrations/components/installed_incoming_webhooks.jsx index 0a38a6ab5..a3bcf904e 100644 --- a/webapp/components/backstage/installed_incoming_webhooks.jsx +++ b/webapp/components/integrations/components/installed_incoming_webhooks.jsx @@ -8,11 +8,17 @@ import IntegrationStore from 'stores/integration_store.jsx'; import TeamStore from 'stores/team_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import BackstageList from 'components/backstage/components/backstage_list.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledIncomingWebhook from './installed_incoming_webhook.jsx'; -import InstalledIntegrations from './installed_integrations.jsx'; export default class InstalledIncomingWebhooks extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired + }; + } + constructor(props) { super(props); @@ -65,7 +71,7 @@ export default class InstalledIncomingWebhooks extends React.Component { }); return ( - <InstalledIntegrations + <BackstageList header={ <FormattedMessage id='installed_incoming_webhooks.header' @@ -78,17 +84,18 @@ export default class InstalledIncomingWebhooks extends React.Component { defaultMessage='Add Incoming Webhook' /> } - addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks/add'} + addLink={'/' + this.props.team.name + '/integrations/incoming_webhooks/add'} emptyText={ <FormattedMessage id='installed_incoming_webhooks.empty' defaultMessage='No incoming webhooks found' /> } + searchPlaceholder={Utils.localizeMessage('installed_incoming_webhooks.search', 'Search Incoming Webhooks')} loading={this.state.loading} > {incomingWebhooks} - </InstalledIntegrations> + </BackstageList> ); } } diff --git a/webapp/components/backstage/installed_outgoing_webhook.jsx b/webapp/components/integrations/components/installed_outgoing_webhook.jsx index 99f2439ec..852231823 100644 --- a/webapp/components/backstage/installed_outgoing_webhook.jsx +++ b/webapp/components/integrations/components/installed_outgoing_webhook.jsx @@ -65,8 +65,9 @@ export default class InstalledOutgoingWebhook extends React.Component { render() { const outgoingWebhook = this.props.outgoingWebhook; const channel = ChannelStore.get(outgoingWebhook.channel_id); + const filter = this.props.filter ? this.props.filter.toLowerCase() : ''; - if (!this.matchesFilter(outgoingWebhook, channel, this.props.filter)) { + if (!this.matchesFilter(outgoingWebhook, channel, filter)) { return null; } diff --git a/webapp/components/backstage/installed_outgoing_webhooks.jsx b/webapp/components/integrations/components/installed_outgoing_webhooks.jsx index b79bc3530..ebc9a6fc1 100644 --- a/webapp/components/backstage/installed_outgoing_webhooks.jsx +++ b/webapp/components/integrations/components/installed_outgoing_webhooks.jsx @@ -8,11 +8,17 @@ import IntegrationStore from 'stores/integration_store.jsx'; import TeamStore from 'stores/team_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import BackstageList from 'components/backstage/components/backstage_list.jsx'; import {FormattedMessage} from 'react-intl'; import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx'; -import InstalledIntegrations from './installed_integrations.jsx'; export default class InstalledOutgoingWebhooks extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired + }; + } + constructor(props) { super(props); @@ -71,7 +77,7 @@ export default class InstalledOutgoingWebhooks extends React.Component { }); return ( - <InstalledIntegrations + <BackstageList header={ <FormattedMessage id='installed_outgoing_webhooks.header' @@ -84,17 +90,18 @@ export default class InstalledOutgoingWebhooks extends React.Component { defaultMessage='Add Outgoing Webhook' /> } - addLink={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks/add'} + addLink={'/' + this.props.team.name + '/integrations/outgoing_webhooks/add'} emptyText={ <FormattedMessage id='installed_outgoing_webhooks.empty' defaultMessage='No outgoing webhooks found' /> } + searchPlaceholder={Utils.localizeMessage('installed_outgoing_webhooks.search', 'Search Outgoing Webhooks')} loading={this.state.loading} > {outgoingWebhooks} - </InstalledIntegrations> + </BackstageList> ); } } diff --git a/webapp/components/backstage/integration_option.jsx b/webapp/components/integrations/components/integration_option.jsx index 483e6a888..483e6a888 100644 --- a/webapp/components/backstage/integration_option.jsx +++ b/webapp/components/integrations/components/integration_option.jsx diff --git a/webapp/components/backstage/integrations.jsx b/webapp/components/integrations/components/integrations.jsx index fdd75026a..7894ced5d 100644 --- a/webapp/components/backstage/integrations.jsx +++ b/webapp/components/integrations/components/integrations.jsx @@ -5,11 +5,16 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import IntegrationOption from './integration_option.jsx'; -import * as Utils from 'utils/utils.jsx'; import WebhookIcon from 'images/webhook_icon.jpg'; export default class Integrations extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired + }; + } + render() { const options = []; @@ -30,7 +35,7 @@ export default class Integrations extends React.Component { defaultMessage='Incoming webhooks allow external integrations to send messages' /> } - link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'} + link={'/' + this.props.team.name + '/integrations/incoming_webhooks'} /> ); } @@ -52,7 +57,7 @@ export default class Integrations extends React.Component { defaultMessage='Outgoing webhooks allow external integrations to receive and respond to messages' /> } - link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'} + link={'/' + this.props.team.name + '/integrations/outgoing_webhooks'} /> ); } @@ -74,7 +79,7 @@ export default class Integrations extends React.Component { defaultMessage='Slash commands send events to an external integration' /> } - link={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'} + link={'/' + this.props.team.name + '/integrations/commands'} /> ); } diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx index 6752b56cd..484164e56 100644 --- a/webapp/components/logged_in.jsx +++ b/webapp/components/logged_in.jsx @@ -156,6 +156,11 @@ export default class LoggedIn extends React.Component { e.preventDefault(); } }); + + // Get custom emoji from the server + if (window.mm_config.EnableCustomEmoji === 'true') { + AsyncClient.listEmoji(); + } } componentWillUnmount() { @@ -187,4 +192,4 @@ export default class LoggedIn extends React.Component { LoggedIn.propTypes = { children: React.PropTypes.object -};
\ No newline at end of file +}; diff --git a/webapp/components/navbar_dropdown.jsx b/webapp/components/navbar_dropdown.jsx index c3b646e52..4f137979e 100644 --- a/webapp/components/navbar_dropdown.jsx +++ b/webapp/components/navbar_dropdown.jsx @@ -85,6 +85,7 @@ export default class NavbarDropdown extends React.Component { var isSystemAdmin = false; var teamSettings = null; let integrationsLink = null; + let customEmojiLink = null; if (currentUser != null) { isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser(); @@ -166,7 +167,7 @@ export default class NavbarDropdown extends React.Component { if (integrationsEnabled && (isAdmin || window.mm_config.EnableOnlyAdminIntegrations !== 'true')) { integrationsLink = ( <li> - <Link to={'/' + Utils.getTeamNameFromUrl() + '/settings/integrations'}> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/integrations'}> <FormattedMessage id='navbar_dropdown.integrations' defaultMessage='Integrations' @@ -176,6 +177,19 @@ export default class NavbarDropdown extends React.Component { ); } + if (window.mm_config.EnableCustomEmoji === 'true') { + customEmojiLink = ( + <li> + <Link to={'/' + Utils.getTeamNameFromUrl() + '/emoji'}> + <FormattedMessage + id='navbar_dropdown.emoji' + defaultMessage='Custom Emoji' + /> + </Link> + </li> + ); + } + if (isSystemAdmin) { sysAdminLink = ( <li> @@ -327,8 +341,10 @@ export default class NavbarDropdown extends React.Component { </a> </li> <li className='divider'></li> - {teamSettings} {integrationsLink} + {customEmojiLink} + <li className='divider'></li> + {teamSettings} {manageLink} {sysAdminLink} {teams} diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx index c445ad9f3..6633bd9b9 100644 --- a/webapp/components/post_view/components/post.jsx +++ b/webapp/components/post_view/components/post.jsx @@ -84,6 +84,10 @@ export default class Post extends React.Component { return true; } + if (nextProps.emojis !== this.props.emojis) { + return true; + } + return false; } render() { @@ -200,6 +204,7 @@ export default class Post extends React.Component { handleCommentClick={this.handleCommentClick} compactDisplay={this.props.compactDisplay} previewCollapsed={this.props.previewCollapsed} + emojis={this.props.emojis} /> </div> </div> @@ -225,5 +230,6 @@ Post.propTypes = { compactDisplay: React.PropTypes.bool, previewCollapsed: React.PropTypes.string, commentCount: React.PropTypes.number, - useMilitaryTime: React.PropTypes.bool.isRequired + useMilitaryTime: React.PropTypes.bool.isRequired, + emojis: React.PropTypes.object.isRequired }; diff --git a/webapp/components/post_view/components/post_body.jsx b/webapp/components/post_view/components/post_body.jsx index 2a2be75a9..561860b65 100644 --- a/webapp/components/post_view/components/post_body.jsx +++ b/webapp/components/post_view/components/post_body.jsx @@ -37,6 +37,10 @@ export default class PostBody extends React.Component { return true; } + if (nextProps.emojis !== this.props.emojis) { + return true; + } + return false; } @@ -151,7 +155,7 @@ export default class PostBody extends React.Component { message = ( <span onClick={TextFormatting.handleClick} - dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message)}} + dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, {emojis: this.props.emojis})}} /> ); } @@ -199,5 +203,6 @@ PostBody.propTypes = { retryPost: React.PropTypes.func.isRequired, handleCommentClick: React.PropTypes.func.isRequired, compactDisplay: React.PropTypes.bool, - previewCollapsed: React.PropTypes.string + previewCollapsed: React.PropTypes.string, + emojis: React.PropTypes.object.isRequired }; diff --git a/webapp/components/post_view/components/post_body_additional_content.jsx b/webapp/components/post_view/components/post_body_additional_content.jsx index 0ab015ced..a51a557b9 100644 --- a/webapp/components/post_view/components/post_body_additional_content.jsx +++ b/webapp/components/post_view/components/post_body_additional_content.jsx @@ -35,6 +35,9 @@ export default class PostBodyAdditionalContent extends React.Component { if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) { return true; } + if (!Utils.areObjectsEqual(nextProps.message, this.props.message)) { + return true; + } if (nextState.embedVisible !== this.state.embedVisible) { return true; } diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx index 288a2d5e0..bcd763d58 100644 --- a/webapp/components/post_view/components/post_list.jsx +++ b/webapp/components/post_view/components/post_list.jsx @@ -265,6 +265,7 @@ export default class PostList extends React.Component { compactDisplay={this.props.compactDisplay} previewCollapsed={this.props.previewsCollapsed} useMilitaryTime={this.props.useMilitaryTime} + emojis={this.props.emojis} /> ); @@ -527,5 +528,6 @@ PostList.propTypes = { compactDisplay: React.PropTypes.bool, previewsCollapsed: React.PropTypes.string, useMilitaryTime: React.PropTypes.bool.isRequired, - isFocusPost: React.PropTypes.bool + isFocusPost: React.PropTypes.bool, + emojis: React.PropTypes.object.isRequired }; diff --git a/webapp/components/post_view/post_focus_view_controller.jsx b/webapp/components/post_view/post_focus_view_controller.jsx index c70ebb0f5..f8738e056 100644 --- a/webapp/components/post_view/post_focus_view_controller.jsx +++ b/webapp/components/post_view/post_focus_view_controller.jsx @@ -4,6 +4,7 @@ import PostList from './components/post_list.jsx'; import LoadingScreen from 'components/loading_screen.jsx'; +import EmojiStore from 'stores/emoji_store.jsx'; import PostStore from 'stores/post_store.jsx'; import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; @@ -20,6 +21,7 @@ export default class PostFocusView extends React.Component { this.onChannelChange = this.onChannelChange.bind(this); this.onPostsChange = this.onPostsChange.bind(this); this.onUserChange = this.onUserChange.bind(this); + this.onEmojiChange = this.onEmojiChange.bind(this); this.onPostListScroll = this.onPostListScroll.bind(this); const focusedPostId = PostStore.getFocusedPostId(); @@ -38,7 +40,8 @@ export default class PostFocusView extends React.Component { currentChannel: ChannelStore.getCurrentId().slice(), scrollPostId: focusedPostId, atTop: PostStore.getVisibilityAtTop(focusedPostId), - atBottom: PostStore.getVisibilityAtBottom(focusedPostId) + atBottom: PostStore.getVisibilityAtBottom(focusedPostId), + emojis: EmojiStore.getEmojis() }; } @@ -46,12 +49,14 @@ export default class PostFocusView extends React.Component { ChannelStore.addChangeListener(this.onChannelChange); PostStore.addChangeListener(this.onPostsChange); UserStore.addChangeListener(this.onUserChange); + EmojiStore.addChangeListener(this.onEmojiChange); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onChannelChange); PostStore.removeChangeListener(this.onPostsChange); UserStore.removeChangeListener(this.onUserChange); + EmojiStore.removeChangeListener(this.onEmojiChange); } onChannelChange() { @@ -87,6 +92,12 @@ export default class PostFocusView extends React.Component { this.setState({currentUser: UserStore.getCurrentUser(), profiles: JSON.parse(JSON.stringify(profiles))}); } + onEmojiChange() { + this.setState({ + emojis: EmojiStore.getEmojis() + }); + } + onPostListScroll() { this.setState({scrollType: ScrollTypes.FREE}); } @@ -116,6 +127,7 @@ export default class PostFocusView extends React.Component { showMoreMessagesBottom={!this.state.atBottom} postsToHighlight={postsToHighlight} isFocusPost={true} + emojis={this.state.emojis} /> ); } diff --git a/webapp/components/post_view/post_view_controller.jsx b/webapp/components/post_view/post_view_controller.jsx index 6d724f659..17c3e94ae 100644 --- a/webapp/components/post_view/post_view_controller.jsx +++ b/webapp/components/post_view/post_view_controller.jsx @@ -4,6 +4,7 @@ import PostList from './components/post_list.jsx'; import LoadingScreen from 'components/loading_screen.jsx'; +import EmojiStore from 'stores/emoji_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import UserStore from 'stores/user_store.jsx'; import PostStore from 'stores/post_store.jsx'; @@ -24,6 +25,7 @@ export default class PostViewController extends React.Component { this.onPreferenceChange = this.onPreferenceChange.bind(this); this.onUserChange = this.onUserChange.bind(this); this.onPostsChange = this.onPostsChange.bind(this); + this.onEmojisChange = this.onEmojisChange.bind(this); this.onPostsViewJumpRequest = this.onPostsViewJumpRequest.bind(this); this.onPostListScroll = this.onPostListScroll.bind(this); this.onActivate = this.onActivate.bind(this); @@ -53,7 +55,8 @@ export default class PostViewController extends React.Component { displayPostsInCenter: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED, compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT, previewsCollapsed: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false'), - useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false) + useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false), + emojis: EmojiStore.getEmojis() }; } @@ -102,11 +105,18 @@ export default class PostViewController extends React.Component { }); } + onEmojisChange() { + this.setState({ + emojis: EmojiStore.getEmojis() + }); + } + onActivate() { PreferenceStore.addChangeListener(this.onPreferenceChange); UserStore.addChangeListener(this.onUserChange); PostStore.addChangeListener(this.onPostsChange); PostStore.addPostsViewJumpListener(this.onPostsViewJumpRequest); + EmojiStore.addChangeListener(this.onEmojisChange); } onDeactivate() { @@ -114,6 +124,7 @@ export default class PostViewController extends React.Component { UserStore.removeChangeListener(this.onUserChange); PostStore.removeChangeListener(this.onPostsChange); PostStore.removePostsViewJumpListener(this.onPostsViewJumpRequest); + EmojiStore.removeChangeListener(this.onEmojisChange); } componentWillReceiveProps(nextProps) { @@ -265,6 +276,7 @@ export default class PostViewController extends React.Component { previewsCollapsed={this.state.previewsCollapsed} useMilitaryTime={this.state.useMilitaryTime} lastViewed={this.state.lastViewed} + emojis={this.state.emojis} /> ); } diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx index 977652e99..f98ed4c3d 100644 --- a/webapp/components/root.jsx +++ b/webapp/components/root.jsx @@ -1,9 +1,6 @@ // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -//import $ from 'jquery'; -//import Client from 'utils/web_client.jsx'; - import * as GlobalActions from 'actions/global_actions.jsx'; import LocalizationStore from 'stores/localization_store.jsx'; import Client from 'utils/web_client.jsx'; diff --git a/webapp/components/suggestion/emoticon_provider.jsx b/webapp/components/suggestion/emoticon_provider.jsx index 7a45c5b4e..af8cac070 100644 --- a/webapp/components/suggestion/emoticon_provider.jsx +++ b/webapp/components/suggestion/emoticon_provider.jsx @@ -3,6 +3,7 @@ import React from 'react'; +import EmojiStore from 'stores/emoji_store.jsx'; import * as Emoticons from 'utils/emoticons.jsx'; import SuggestionStore from 'stores/suggestion_store.jsx'; @@ -29,7 +30,7 @@ class EmoticonSuggestion extends Suggestion { <img alt={text} className='emoticon-suggestion__image' - src={emoticon.path} + src={EmojiStore.getEmojiImageUrl(emoticon)} title={text} /> </div> @@ -53,21 +54,19 @@ export default class EmoticonProvider { const matched = []; - const emoticons = Emoticons.getEmoticonsByName(); - // check for text emoticons for (const emoticon of Object.keys(Emoticons.emoticonPatterns)) { if (Emoticons.emoticonPatterns[emoticon].test(text)) { - SuggestionStore.addSuggestion(suggestionId, text, emoticons.get(emoticon), EmoticonSuggestion, text); + SuggestionStore.addSuggestion(suggestionId, text, EmojiStore.get(emoticon), EmoticonSuggestion, text); hasSuggestions = true; } } - // checked for named emoji - for (const [name, emoticon] of emoticons) { + // check for named emoji + for (const [name, emoji] of EmojiStore.getEmojis()) { if (name.indexOf(partialName) !== -1) { - matched.push(emoticon); + matched.push(emoji); if (matched.length >= MAX_EMOTICON_SUGGESTIONS) { break; @@ -77,11 +76,11 @@ export default class EmoticonProvider { // sort the emoticons so that emoticons starting with the entered text come first matched.sort((a, b) => { - const aPrefix = a.alias.startsWith(partialName); - const bPrefix = b.alias.startsWith(partialName); + const aPrefix = a.name.startsWith(partialName); + const bPrefix = b.name.startsWith(partialName); if (aPrefix === bPrefix) { - return a.alias.localeCompare(b.alias); + return a.name.localeCompare(b.name); } else if (aPrefix) { return -1; } @@ -89,7 +88,7 @@ export default class EmoticonProvider { return 1; }); - const terms = matched.map((emoticon) => ':' + emoticon.alias + ':'); + const terms = matched.map((emoticon) => ':' + emoticon.name + ':'); if (terms.length > 0) { SuggestionStore.addSuggestions(suggestionId, terms, matched, EmoticonSuggestion, text); diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 574ea6ecc..afe32ae0e 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -64,6 +64,20 @@ "add_command.username": "Response Username", "add_command.username.help": "Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols \"-\", \"_\", and \".\" .", "add_command.username.placeholder": "Username", + "add_emoji.cancel": "Cancel", + "add_emoji.header": "Add", + "add_emoji.image": "Image", + "add_emoji.image.button": "Select", + "add_emoji.image.help": "Choose the image for your emoji. The image can be a gif, png, or jpeg file with a max size of 64 KB and dimensions up to 128 by 128 pixels.", + "add_emoji.imageRequired": "An image is required for the emoji", + "add_emoji.name": "Name", + "add_emoji.name.help": "Choose a name for your emoji made of up to 64 characters consisting of lowercase letters, numbers, and the symbols '-' and '_'.", + "add_emoji.nameRequired": "A name is required for the emoji", + "add_emoji.nameInvalid": "An emoji's name can only contain numbers, letters, and the symbols '-' and '_'.", + "add_emoji.nameTaken": "This name is already in use by a system emoji. Please choose another name.", + "add_emoji.preview": "Preview", + "add_emoji.preview.sentence": "This is a sentence with {image} in it.", + "add_emoji.save": "Save", "add_incoming_webhook.cancel": "Cancel", "add_incoming_webhook.channel": "Channel", "add_incoming_webhook.channelRequired": "A valid channel is required", @@ -137,6 +151,7 @@ "admin.customization.enableCustomEmojiTitle": "Enable Custom Emoji:", "admin.customization.restrictCustomEmojiCreationAll": "Allow everyone to create custom emoji", "admin.customization.restrictCustomEmojiCreationDesc": "Restrict the creation of custom emoji to certain users.", + "admin.customization.restrictCustomEmojiCreationAdmin": "Allow system and team admins to create custom emoji", "admin.customization.restrictCustomEmojiCreationSystemAdmin": "Only allow system admins to create custom emoji", "admin.customization.restrictCustomEmojiCreationTitle": "Restrict Custom Emoji Creation:", "admin.customization.support": "Legal and Support", @@ -701,6 +716,7 @@ "authorize.app": "The app <strong>{appName}</strong> would like the ability to access and modify your basic information.", "authorize.deny": "Deny", "authorize.title": "An application would like to connect to your {teamName} account", + "backstage_list.search": "Search", "backstage_navbar.backToMattermost": "Back to {siteName}", "backstage_sidebar.integrations": "Integrations", "backstage_sidebar.integrations.commands": "Slash Commands", @@ -858,6 +874,9 @@ "create_team.team_url.teamUrl": "Team URL", "create_team.team_url.unavailable": "This URL is unavailable. Please try another.", "create_team.team_url.webAddress": "Choose the web address of your new team:", + "custom_emoji.empty": "No custom emoji found", + "custom_emoji.header": "Custom Emoji", + "custom_emoji.search": "Search Custom Emoji", "delete_channel.cancel": "Cancel", "delete_channel.channel": "channel", "delete_channel.confirm": "Confirm DELETE Channel", @@ -901,6 +920,15 @@ "email_verify.verified": "{siteName} Email Verified", "email_verify.verifiedBody": "<p>Your email has been verified! Click <a href={url}>here</a> to log in.</p>", "email_verify.verifyFailed": "Failed to verify your email.", + "emoji_list.actions": "Actions", + "emoji_list.add": "Add Custom Emoji", + "emoji_list.creator": "Creator", + "emoji_list.delete": "Delete", + "emoji_list.empty": "No Custom Emoji Found", + "emoji_list.image": "Image", + "emoji_list.name": "Name", + "emoji_list.search": "Search Custom Emoji", + "emoji_list.somebody": "Somebody on another team", "error.not_found.link_message": "Back to Mattermost", "error.not_found.message": "The page you were trying to reach does not exist", "error.not_found.title": "Page not found", @@ -960,23 +988,25 @@ "installed_commands.add": "Add Slash Command", "installed_commands.empty": "No commands found", "installed_commands.header": "Slash Commands", + "installed_commands.search": "Search Slash Commands", + "installed_commands.unnamed_command": "Unnamed Slash Command", "installed_incoming_webhooks.add": "Add Incoming Webhook", "installed_incoming_webhooks.empty": "No incoming webhooks found", "installed_incoming_webhooks.header": "Incoming Webhooks", + "installed_incoming_webhooks.search": "Search Incoming Webhooks", "installed_incoming_webhooks.unknown_channel": "A Private Webhook", - "installed_integraions.unnamed_command": "Unnamed Slash Command", "installed_integrations.callback_urls": "Callback URLs: {urls}", "installed_integrations.content_type": "Content-Type: {contentType}", "installed_integrations.creation": "Created by {creator} on {createAt, date, full}", "installed_integrations.delete": "Delete", "installed_integrations.regenToken": "Regenerate Token", - "installed_integrations.search": "Search Integrations", "installed_integrations.token": "Token: {token}", "installed_integrations.triggerWords": "Trigger Words: {triggerWords}", "installed_integrations.url": "URL: {url}", "installed_outgoing_webhooks.add": "Add Outgoing Webhook", "installed_outgoing_webhooks.empty": "No outgoing webhooks found", "installed_outgoing_webhooks.header": "Outgoing Webhooks", + "installed_outgoing_webhooks.search": "Search Outgoing Webhooks", "installed_outgoing_webhooks.unknown_channel": "A Private Webhook", "integrations.command.description": "Slash commands send events to external integrations", "integrations.command.title": "Slash Command", @@ -1091,6 +1121,7 @@ "navbar_dropdown.accountSettings": "Account Settings", "navbar_dropdown.console": "System Console", "navbar_dropdown.create": "Create a New Team", + "navbar_dropdown.emoji": "Custom Emoji", "navbar_dropdown.help": "Help", "navbar_dropdown.integrations": "Integrations", "navbar_dropdown.inviteMember": "Invite New Member", diff --git a/webapp/package.json b/webapp/package.json index c3bca0e2e..69d91e345 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -18,7 +18,7 @@ "keymirror": "0.1.1", "marked": "mattermost/marked#12d2be4cdf54d4ec95fead934e18840b6a2c1a7b", "match-at": "0.1.0", - "mattermost": "mattermost/mattermost-javascript#release-3.1", + "mattermost": "mattermost/mattermost-javascript#8e4c320d5af653eacb248455d77057a76ec28830", "object-assign": "4.1.0", "perfect-scrollbar": "0.6.11", "react": "15.0.2", diff --git a/webapp/routes/route_emoji.jsx b/webapp/routes/route_emoji.jsx new file mode 100644 index 000000000..207c81172 --- /dev/null +++ b/webapp/routes/route_emoji.jsx @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as RouteUtils from 'routes/route_utils.jsx'; + +export default { + path: 'emoji', + getComponents: (location, callback) => { + System.import('components/backstage/backstage_controller.jsx').then(RouteUtils.importComponentSuccess(callback)); + }, + indexRoute: { + getComponents: (location, callback) => { + System.import('components/emoji/components/emoji_list.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + }, + childRoutes: [ + { + path: 'add', + getComponents: (location, callback) => { + System.import('components/emoji/components/add_emoji.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + } + ] +}; diff --git a/webapp/routes/route_integrations.jsx b/webapp/routes/route_integrations.jsx index 6ebd09a72..fdfb5d947 100644 --- a/webapp/routes/route_integrations.jsx +++ b/webapp/routes/route_integrations.jsx @@ -2,83 +2,65 @@ // See License.txt for license information. import * as RouteUtils from 'routes/route_utils.jsx'; -import {Route, IndexRoute, Redirect} from 'react-router/es6'; -import React from 'react'; -import BackstageNavbar from 'components/backstage/backstage_navbar.jsx'; -import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx'; -import Integrations from 'components/backstage/integrations.jsx'; -import InstalledIncomingWebhooks from 'components/backstage/installed_incoming_webhooks.jsx'; -import InstalledOutgoingWebhooks from 'components/backstage/installed_outgoing_webhooks.jsx'; -import InstalledCommands from 'components/backstage/installed_commands.jsx'; -import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx'; -import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx'; -import AddCommand from 'components/backstage/add_command.jsx'; - -export default ( - <Route path='integrations'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: Integrations - }} - /> - <Route path='incoming_webhooks'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: InstalledIncomingWebhooks - }} - /> - <Route - path='add' - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: AddIncomingWebhook - }} - /> - </Route> - <Route path='outgoing_webhooks'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: InstalledOutgoingWebhooks - }} - /> - <Route - path='add' - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: AddOutgoingWebhook - }} - /> - </Route> - <Route path='commands'> - <IndexRoute - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: InstalledCommands - }} - /> - <Route - path='add' - components={{ - navbar: BackstageNavbar, - sidebar: BackstageSidebar, - center: AddCommand - }} - /> - </Route> - <Redirect - from='*' - to='/error' - query={RouteUtils.notFoundParams} - /> - </Route> -); +export default { + path: 'integrations', + getComponents: (location, callback) => { + System.import('components/backstage/backstage_controller.jsx').then(RouteUtils.importComponentSuccess(callback)); + }, + indexRoute: { + getComponents: (location, callback) => { + System.import('components/integrations/components/integrations.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + }, + childRoutes: [ + { + path: 'incoming_webhooks', + indexRoute: { + getComponents: (location, callback) => { + System.import('components/integrations/components/installed_incoming_webhooks.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + }, + childRoutes: [ + { + path: 'add', + getComponents: (location, callback) => { + System.import('components/integrations/components/add_incoming_webhook.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + } + ] + }, + { + path: 'outgoing_webhooks', + indexRoute: { + getComponents: (location, callback) => { + System.import('components/integrations/components/installed_outgoing_webhooks.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + }, + childRoutes: [ + { + path: 'add', + getComponents: (location, callback) => { + System.import('components/integrations/components/add_outgoing_webhook.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + } + ] + }, + { + path: 'commands', + indexRoute: { + getComponents: (location, callback) => { + System.import('components/integrations/components/installed_commands.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + }, + childRoutes: [ + { + path: 'add', + getComponents: (location, callback) => { + System.import('components/integrations/components/add_command.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + } + ] + } + ] +}; diff --git a/webapp/routes/route_team.jsx b/webapp/routes/route_team.jsx index 7025ecb99..cb4f09ae4 100644 --- a/webapp/routes/route_team.jsx +++ b/webapp/routes/route_team.jsx @@ -15,6 +15,9 @@ import Client from 'utils/web_client.jsx'; import * as Utils from 'utils/utils.jsx'; import ChannelStore from 'stores/channel_store.jsx'; +import emojiRoute from 'routes/route_emoji.jsx'; +import integrationsRoute from 'routes/route_integrations.jsx'; + function onChannelEnter(nextState, replace, callback) { doChannelChange(nextState, replace, callback); } @@ -120,52 +123,52 @@ function onPermalinkEnter(nextState) { export default { path: ':team', - getComponents: (location, callback) => { - System.import('components/needs_team.jsx').then(RouteUtils.importComponentSuccess(callback)); - }, onEnter: preNeedsTeam, indexRoute: {onEnter: (nextState, replace) => replace('/' + nextState.params.team + '/channels/town-square')}, childRoutes: [ + integrationsRoute, + emojiRoute, { - path: 'channels/:channel', - onEnter: onChannelEnter, - getComponents: (location, callback) => { - Promise.all([ - System.import('components/sidebar.jsx'), - System.import('components/channel_view.jsx') - ]).then( - (comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) - ); - } - }, - { - path: 'pl/:postid', - onEnter: onPermalinkEnter, getComponents: (location, callback) => { - Promise.all([ - System.import('components/sidebar.jsx'), - System.import('components/permalink_view.jsx') - ]).then( - (comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) - ); - } - }, - { - path: 'tutorial', - getComponents: (location, callback) => { - Promise.all([ - System.import('components/sidebar.jsx'), - System.import('components/tutorial/tutorial_view.jsx') - ]).then( - (comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) - ); - } - }, - { - path: 'settings', - getChildRoutes: (location, callback) => { - System.import('routes/route_integrations.jsx').then((comp) => callback(null, [comp.default])); - } + System.import('components/needs_team.jsx').then(RouteUtils.importComponentSuccess(callback)); + }, + childRoutes: [ + { + path: 'channels/:channel', + onEnter: onChannelEnter, + getComponents: (location, callback) => { + Promise.all([ + System.import('components/sidebar.jsx'), + System.import('components/channel_view.jsx') + ]).then( + (comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) + ); + } + }, + { + path: 'pl/:postid', + onEnter: onPermalinkEnter, + getComponents: (location, callback) => { + Promise.all([ + System.import('components/sidebar.jsx'), + System.import('components/permalink_view.jsx') + ]).then( + (comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) + ); + } + }, + { + path: 'tutorial', + getComponents: (location, callback) => { + Promise.all([ + System.import('components/sidebar.jsx'), + System.import('components/tutorial/tutorial_view.jsx') + ]).then( + (comarr) => callback(null, {sidebar: comarr[0].default, center: comarr[1].default}) + ); + } + } + ] } ] }; diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss index 884c69d74..ba72a8119 100644 --- a/webapp/sass/responsive/_mobile.scss +++ b/webapp/sass/responsive/_mobile.scss @@ -19,7 +19,6 @@ display: block; .backstage-filter__search { - border-bottom: 1px solid $light-gray; margin: 10px 0; width: 100%; } diff --git a/webapp/sass/routes/_backstage.scss b/webapp/sass/routes/_backstage.scss index 244aad043..5d7942184 100644 --- a/webapp/sass/routes/_backstage.scss +++ b/webapp/sass/routes/_backstage.scss @@ -1,35 +1,11 @@ -body { - &.backstage { - height: auto; - overflow: auto; - - .inner-wrap { - @include translateX(0); - margin-right: 0 !important; - - &:before { - display: none; - } - } - - .sidebar--right, - .sidebar--menu, - .navbar { - display: none; - } - } -} - -.backstage-content { - background-color: $bg--gray; - height: 100%; - margin: 46px auto; - max-width: 960px; - padding-left: 135px; +.backstage { + height: 100vh; + width: 100vw; } .backstage-navbar { background: $white; + height: 41px; border-bottom: 1px solid $light-gray; padding: 10px 20px; z-index: 10; @@ -51,13 +27,29 @@ body { } } +.backstage-body { + background-color: $bg--gray; + bottom: 0; + display: inline-block; + height: calc(100vh - 41px); + overflow: auto; + top: 0; + width: 100%; +} + +.backstage-content { + background-color: $bg--gray; + margin: 46px auto; + max-width: 960px; + padding-left: 135px; + vertical-align: top; +} + .backstage-sidebar { - height: 100%; - left: 0; - padding: 50px 20px; + padding: 46px 20px; position: absolute; + vertical-align: top; width: 260px; - z-index: 5; ul { list-style: none; @@ -69,6 +61,7 @@ body { border: 1px solid $light-gray; .category-title { + color: $black; display: block; line-height: 36px; padding: 0 10px; @@ -76,7 +69,8 @@ body { } .category-title--active { - color: $black; + background-color: $primary-color; + color: $white; } .category-title__text { @@ -110,8 +104,8 @@ body { } } -.backstage__sidebar__category + .backstage__sidebar__category { - border-top-width: 0; +.backstage-sidebar__category + .backstage-sidebar__category { + margin-top: 1em; } .backstage-header__divider { @@ -130,7 +124,7 @@ body { margin: 5px 0; } - .add-integrations-link { + .add-link { float: right; } } @@ -139,45 +133,45 @@ body { display: flex; flex-direction: row; width: 100%; +} - .backstage-filters__sort { - flex-grow: 1; - flex-shrink: 0; - line-height: 30px; +.backstage-filters__sort { + flex-grow: 1; + flex-shrink: 0; + line-height: 30px; - .filter-sort { - text-decoration: none; + .filter-sort { + text-decoration: none; - &.filter-sort--active { - color: inherit; - cursor: default; - } + &.filter-sort--active { + color: inherit; + cursor: default; } + } - .divider { - margin-left: 8px; - margin-right: 8px; - } + .divider { + margin-left: 8px; + margin-right: 8px; } +} - .backstage-filter__search { - flex-grow: 0; - flex-shrink: 0; - position: relative; - width: 270px; +.backstage-filter__search { + flex-grow: 0; + flex-shrink: 0; + position: relative; + width: 270px; - .fa { - @include opacity(.4); - left: 11px; - position: absolute; - top: 11px; - } + .fa { + @include opacity(.4); + left: 11px; + position: absolute; + top: 11px; + } - input { - background: $white; - border-bottom: none; - padding-left: 30px; - } + input { + background: $white; + border-bottom-width: 0; + padding-left: 30px; } } @@ -332,3 +326,73 @@ body { .integration-option__description { color: $dark-gray; } + +.emoji-list .backstage-filter__search input { + border-bottom-width: 1px; +} + +.emoji-list__help { + display: block; + padding: 1em 0; +} + +.emoji-list__table { + width: 100%; + + .backstage-list__item { + display: table-row; + } + + .backstage-list__empty td { + padding: 15px 20px; + } +} + +.emoji-list__table-header { + font-weight: bold; +} + +.emoji-list__name { + padding: 20px 0px 20px 15px; + width: 30%; +} + +.emoji-list__image { + padding: 15px 0px; + width: 15%; +} + +&.emoji-list__creator { + padding: 15px 0px; + width: 40%; +} + +&.emoji-list__actions { + padding: 20px 15px 20px 0px; + width: 15%; +} + +.add-emoji__upload { + display: inline-block; + margin: 0 10px 10px 0; + position: relative; + + input { + @include opacity(0); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 5; + } +} + +.add-emoji__filename, +.add-emoji__preview { + padding-top: 7px; + + .emoticon { + margin-bottom: 0; + } +}
\ No newline at end of file diff --git a/webapp/stores/emoji_store.jsx b/webapp/stores/emoji_store.jsx new file mode 100644 index 000000000..5e1d81dd3 --- /dev/null +++ b/webapp/stores/emoji_store.jsx @@ -0,0 +1,135 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; +import Constants from 'utils/constants.jsx'; +import EventEmitter from 'events'; + +import EmojiJson from 'utils/emoji.json'; + +const ActionTypes = Constants.ActionTypes; + +const CHANGE_EVENT = 'changed'; + +class EmojiStore extends EventEmitter { + constructor() { + super(); + + this.dispatchToken = AppDispatcher.register(this.handleEventPayload.bind(this)); + + this.emojis = new Map(EmojiJson); + this.systemEmojis = new Map(EmojiJson); + + this.unicodeEmojis = new Map(); + for (const [, emoji] of this.systemEmojis) { + if (emoji.unicode) { + this.unicodeEmojis.set(emoji.unicode, emoji); + } + } + + this.receivedCustomEmojis = false; + this.customEmojis = new Map(); + } + + addChangeListener(callback) { + this.on(CHANGE_EVENT, callback); + } + + removeChangeListener(callback) { + this.removeListener(CHANGE_EVENT, callback); + } + + emitChange() { + this.emit(CHANGE_EVENT); + } + + hasReceivedCustomEmojis() { + return this.receivedCustomEmojis; + } + + setCustomEmojis(customEmojis) { + this.customEmojis = new Map(); + + for (const emoji of customEmojis) { + this.addCustomEmoji(emoji); + } + + // add custom emojis to the map first so that they can't override system ones + this.emojis = new Map([...this.customEmojis, ...this.systemEmojis]); + } + + addCustomEmoji(emoji) { + this.customEmojis.set(emoji.name, emoji); + } + + removeCustomEmoji(id) { + for (const [name, emoji] of this.customEmojis) { + if (emoji.id === id) { + this.customEmojis.delete(name); + break; + } + } + } + + getSystemEmojis() { + return this.systemEmojis; + } + + getCustomEmojiMap() { + return this.customEmojis; + } + + getEmojis() { + return this.emojis; + } + + has(name) { + return this.emojis.has(name); + } + + get(name) { + // prioritize system emojis so that custom ones can't override them + return this.emojis.get(name); + } + + hasUnicode(codepoint) { + return this.unicodeEmojis.has(codepoint); + } + + getUnicode(codepoint) { + return this.unicodeEmojis.get(codepoint); + } + + getEmojiImageUrl(emoji) { + if (emoji.id) { + // must match Client.getCustomEmojiImageUrl + return `/api/v3/emoji/${emoji.id}`; + } + + const filename = emoji.unicode || emoji.filename || emoji.name; + + return Constants.EMOJI_PATH + '/' + filename + '.png'; + } + + handleEventPayload(payload) { + const action = payload.action; + + switch (action.type) { + case ActionTypes.RECEIVED_CUSTOM_EMOJIS: + this.setCustomEmojis(action.emojis); + this.receivedCustomEmojis = true; + this.emitChange(); + break; + case ActionTypes.RECEIVED_CUSTOM_EMOJI: + this.addCustomEmoji(action.emoji); + this.emitChange(); + break; + case ActionTypes.REMOVED_CUSTOM_EMOJI: + this.removeCustomEmoji(action.id); + this.emitChange(); + break; + } + } +} + +export default new EmojiStore(); diff --git a/webapp/stores/team_store.jsx b/webapp/stores/team_store.jsx index f4d60ba74..d81863aba 100644 --- a/webapp/stores/team_store.jsx +++ b/webapp/stores/team_store.jsx @@ -160,13 +160,16 @@ class TeamStoreClass extends EventEmitter { } isTeamAdminForCurrentTeam() { + return this.isTeamAdmin(UserStore.getCurrentId(), this.getCurrentId()); + } + + isTeamAdmin(userId, teamId) { if (!Utils) { Utils = require('utils/utils.jsx'); //eslint-disable-line global-require } - const userId = UserStore.getCurrentId(); var teamMembers = this.getTeamMembers(); - const teamMember = teamMembers.find((m) => m.user_id === userId && m.team_id === this.getCurrentId()); + const teamMember = teamMembers.find((m) => m.user_id === userId && m.team_id === teamId); if (teamMember) { return Utils.isAdmin(teamMember.roles); diff --git a/webapp/tests/emoticons.test.jsx b/webapp/tests/emoticons.test.jsx index bb0421651..bf025e6b2 100644 --- a/webapp/tests/emoticons.test.jsx +++ b/webapp/tests/emoticons.test.jsx @@ -3,42 +3,45 @@ import assert from 'assert'; +import EmojiStore from 'stores/emoji_store.jsx'; import * as Emoticons from 'utils/emoticons.jsx'; describe('Emoticons', function() { this.timeout(100000); it('handleEmoticons', function(done) { + const emojis = EmojiStore.getEmojis(); + assert.equal( - Emoticons.handleEmoticons(':goat: :dash:', new Map()), + Emoticons.handleEmoticons(':goat: :dash:', new Map(), emojis), 'MM_EMOTICON0 MM_EMOTICON1', 'should replace emoticons with tokens' ); assert.equal( - Emoticons.handleEmoticons(':goat::dash:', new Map()), + Emoticons.handleEmoticons(':goat::dash:', new Map(), emojis), 'MM_EMOTICON0MM_EMOTICON1', 'should replace emoticons not separated by whitespace' ); assert.equal( - Emoticons.handleEmoticons('/:goat:..:dash:)', new Map()), + Emoticons.handleEmoticons('/:goat:..:dash:)', new Map(), emojis), '/MM_EMOTICON0..MM_EMOTICON1)', 'should replace emoticons separated by punctuation' ); assert.equal( - Emoticons.handleEmoticons('asdf:goat:asdf:dash:asdf', new Map()), + Emoticons.handleEmoticons('asdf:goat:asdf:dash:asdf', new Map(), emojis), 'asdfMM_EMOTICON0asdfMM_EMOTICON1asdf', 'should replace emoticons separated by text' ); assert.equal( - Emoticons.handleEmoticons(':asdf: :goat : : dash:', new Map()), + Emoticons.handleEmoticons(':asdf: :goat : : dash:', new Map(), emojis), ':asdf: :goat : : dash:', 'shouldn\'t replace invalid emoticons' ); done(); }); -});
\ No newline at end of file +}); diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx index 65cb2d258..04e101aa9 100644 --- a/webapp/utils/async_client.jsx +++ b/webapp/utils/async_client.jsx @@ -1384,3 +1384,88 @@ export function getPublicLink(filename, success, error) { } ); } + +export function listEmoji() { + if (isCallInProgress('listEmoji')) { + return; + } + + callTracker.listEmoji = utils.getTimestamp(); + + Client.listEmoji( + (data) => { + callTracker.listEmoji = 0; + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_CUSTOM_EMOJIS, + emojis: data + }); + }, + (err) => { + callTracker.listEmoji = 0; + dispatchError(err, 'listEmoji'); + } + ); +} + +export function addEmoji(emoji, image, success, error) { + const callName = 'addEmoji' + emoji.name; + + if (isCallInProgress(callName)) { + return; + } + + callTracker[callName] = utils.getTimestamp(); + + Client.addEmoji( + emoji, + image, + (data) => { + callTracker[callName] = 0; + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_CUSTOM_EMOJI, + emoji: data + }); + + if (success) { + success(); + } + }, + (err) => { + callTracker[callName] = 0; + + if (error) { + error(err); + } else { + dispatchError(err, 'addEmoji'); + } + } + ); +} + +export function deleteEmoji(id) { + const callName = 'deleteEmoji' + id; + + if (isCallInProgress(callName)) { + return; + } + + callTracker[callName] = utils.getTimestamp(); + + Client.deleteEmoji( + id, + () => { + callTracker[callName] = 0; + + AppDispatcher.handleServerAction({ + type: ActionTypes.REMOVED_CUSTOM_EMOJI, + id + }); + }, + (err) => { + callTracker[callName] = 0; + dispatchError(err, 'deleteEmoji'); + } + ); +}
\ No newline at end of file diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index f6e929270..ac00262c3 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -86,6 +86,11 @@ export default { UPDATED_COMMAND: null, REMOVED_COMMAND: null, + RECEIVED_CUSTOM_EMOJIS: null, + RECEIVED_CUSTOM_EMOJI: null, + UPDATED_CUSTOM_EMOJI: null, + REMOVED_CUSTOM_EMOJI: null, + RECEIVED_MSG: null, RECEIVED_MY_TEAM: null, diff --git a/webapp/utils/emoji.json b/webapp/utils/emoji.json index 8d6d7c7a6..03b56a18a 100644 --- a/webapp/utils/emoji.json +++ b/webapp/utils/emoji.json @@ -1,8333 +1 @@ -[ - { - "emoji": "😄" - , "description": "smiling face with open mouth and smiling eyes" - , "aliases": [ - "smile" - ] - , "tags": [ - "happy" - , "joy" - , "pleased" - ] - } -, { - "emoji": "😃" - , "description": "smiling face with open mouth" - , "aliases": [ - "smiley" - ] - , "tags": [ - "happy" - , "joy" - , "haha" - ] - } -, { - "emoji": "😀" - , "description": "grinning face" - , "aliases": [ - "grinning" - ] - , "tags": [ - "smile" - , "happy" - ] - } -, { - "emoji": "😊" - , "description": "smiling face with smiling eyes" - , "aliases": [ - "blush" - ] - , "tags": [ - "proud" - ] - } -, { - "emoji": "☺️" - , "description": "white smiling face" - , "aliases": [ - "relaxed" - ] - , "tags": [ - "blush" - , "pleased" - ] - } -, { - "emoji": "😉" - , "description": "winking face" - , "aliases": [ - "wink" - ] - , "tags": [ - "flirt" - ] - } -, { - "emoji": "😍" - , "description": "smiling face with heart-shaped eyes" - , "aliases": [ - "heart_eyes" - ] - , "tags": [ - "love" - , "crush" - ] - } -, { - "emoji": "😘" - , "description": "face throwing a kiss" - , "aliases": [ - "kissing_heart" - ] - , "tags": [ - "flirt" - ] - } -, { - "emoji": "😚" - , "description": "kissing face with closed eyes" - , "aliases": [ - "kissing_closed_eyes" - ] - , "tags": [ - ] - } -, { - "emoji": "😗" - , "description": "kissing face" - , "aliases": [ - "kissing" - ] - , "tags": [ - ] - } -, { - "emoji": "😙" - , "description": "kissing face with smiling eyes" - , "aliases": [ - "kissing_smiling_eyes" - ] - , "tags": [ - ] - } -, { - "emoji": "😜" - , "description": "face with stuck-out tongue and winking eye" - , "aliases": [ - "stuck_out_tongue_winking_eye" - ] - , "tags": [ - "prank" - , "silly" - ] - } -, { - "emoji": "😝" - , "description": "face with stuck-out tongue and tightly-closed eyes" - , "aliases": [ - "stuck_out_tongue_closed_eyes" - ] - , "tags": [ - "prank" - ] - } -, { - "emoji": "😛" - , "description": "face with stuck-out tongue" - , "aliases": [ - "stuck_out_tongue" - ] - , "tags": [ - ] - } -, { - "emoji": "😳" - , "description": "flushed face" - , "aliases": [ - "flushed" - ] - , "tags": [ - ] - } -, { - "emoji": "😁" - , "description": "grinning face with smiling eyes" - , "aliases": [ - "grin" - ] - , "tags": [ - ] - } -, { - "emoji": "😔" - , "description": "pensive face" - , "aliases": [ - "pensive" - ] - , "tags": [ - ] - } -, { - "emoji": "😌" - , "description": "relieved face" - , "aliases": [ - "relieved" - ] - , "tags": [ - "whew" - ] - } -, { - "emoji": "😒" - , "description": "unamused face" - , "aliases": [ - "unamused" - ] - , "tags": [ - "meh" - ] - } -, { - "emoji": "😞" - , "description": "disappointed face" - , "aliases": [ - "disappointed" - ] - , "tags": [ - "sad" - ] - } -, { - "emoji": "😣" - , "description": "persevering face" - , "aliases": [ - "persevere" - ] - , "tags": [ - "struggling" - ] - } -, { - "emoji": "😢" - , "description": "crying face" - , "aliases": [ - "cry" - ] - , "tags": [ - "sad" - , "tear" - ] - } -, { - "emoji": "😂" - , "description": "face with tears of joy" - , "aliases": [ - "joy" - ] - , "tags": [ - "tears" - ] - } -, { - "emoji": "😭" - , "description": "loudly crying face" - , "aliases": [ - "sob" - ] - , "tags": [ - "sad" - , "cry" - , "bawling" - ] - } -, { - "emoji": "😪" - , "description": "sleepy face" - , "aliases": [ - "sleepy" - ] - , "tags": [ - "tired" - ] - } -, { - "emoji": "😥" - , "description": "disappointed but relieved face" - , "aliases": [ - "disappointed_relieved" - ] - , "tags": [ - "phew" - , "sweat" - , "nervous" - ] - } -, { - "emoji": "😰" - , "description": "face with open mouth and cold sweat" - , "aliases": [ - "cold_sweat" - ] - , "tags": [ - "nervous" - ] - } -, { - "emoji": "😅" - , "description": "smiling face with open mouth and cold sweat" - , "aliases": [ - "sweat_smile" - ] - , "tags": [ - "hot" - ] - } -, { - "emoji": "😓" - , "description": "face with cold sweat" - , "aliases": [ - "sweat" - ] - , "tags": [ - ] - } -, { - "emoji": "😩" - , "description": "weary face" - , "aliases": [ - "weary" - ] - , "tags": [ - "tired" - ] - } -, { - "emoji": "😫" - , "description": "tired face" - , "aliases": [ - "tired_face" - ] - , "tags": [ - "upset" - , "whine" - ] - } -, { - "emoji": "😨" - , "description": "fearful face" - , "aliases": [ - "fearful" - ] - , "tags": [ - "scared" - , "shocked" - , "oops" - ] - } -, { - "emoji": "😱" - , "description": "face screaming in fear" - , "aliases": [ - "scream" - ] - , "tags": [ - "horror" - , "shocked" - ] - } -, { - "emoji": "😠" - , "description": "angry face" - , "aliases": [ - "angry" - ] - , "tags": [ - "mad" - , "annoyed" - ] - } -, { - "emoji": "😡" - , "description": "pouting face" - , "aliases": [ - "rage" - , "pout" - ] - , "tags": [ - "angry" - ] - } -, { - "emoji": "😤" - , "description": "face with look of triumph" - , "aliases": [ - "triumph" - ] - , "tags": [ - "smug" - ] - } -, { - "emoji": "😖" - , "description": "confounded face" - , "aliases": [ - "confounded" - ] - , "tags": [ - ] - } -, { - "emoji": "😆" - , "description": "smiling face with open mouth and tightly-closed eyes" - , "aliases": [ - "laughing" - , "satisfied" - ] - , "tags": [ - "happy" - , "haha" - ] - } -, { - "emoji": "😋" - , "description": "face savouring delicious food" - , "aliases": [ - "yum" - ] - , "tags": [ - "tongue" - , "lick" - ] - } -, { - "emoji": "😷" - , "description": "face with medical mask" - , "aliases": [ - "mask" - ] - , "tags": [ - "sick" - , "ill" - ] - } -, { - "emoji": "😎" - , "description": "smiling face with sunglasses" - , "aliases": [ - "sunglasses" - ] - , "tags": [ - "cool" - ] - } -, { - "emoji": "😴" - , "description": "sleeping face" - , "aliases": [ - "sleeping" - ] - , "tags": [ - "zzz" - ] - } -, { - "emoji": "😵" - , "description": "dizzy face" - , "aliases": [ - "dizzy_face" - ] - , "tags": [ - ] - } -, { - "emoji": "😲" - , "description": "astonished face" - , "aliases": [ - "astonished" - ] - , "tags": [ - "amazed" - , "gasp" - ] - } -, { - "emoji": "😟" - , "description": "worried face" - , "aliases": [ - "worried" - ] - , "tags": [ - "nervous" - ] - } -, { - "emoji": "😦" - , "description": "frowning face with open mouth" - , "aliases": [ - "frowning" - ] - , "tags": [ - ] - } -, { - "emoji": "😧" - , "description": "anguished face" - , "aliases": [ - "anguished" - ] - , "tags": [ - "stunned" - ] - } -, { - "emoji": "😈" - , "description": "smiling face with horns" - , "aliases": [ - "smiling_imp" - ] - , "tags": [ - "devil" - , "evil" - , "horns" - ] - } -, { - "emoji": "👿" - , "description": "imp" - , "aliases": [ - "imp" - ] - , "tags": [ - "angry" - , "devil" - , "evil" - , "horns" - ] - } -, { - "emoji": "😮" - , "description": "face with open mouth" - , "aliases": [ - "open_mouth" - ] - , "tags": [ - "surprise" - , "impressed" - , "wow" - ] - } -, { - "emoji": "😬" - , "description": "grimacing face" - , "aliases": [ - "grimacing" - ] - , "tags": [ - ] - } -, { - "emoji": "😐" - , "description": "neutral face" - , "aliases": [ - "neutral_face" - ] - , "tags": [ - "meh" - ] - } -, { - "emoji": "😕" - , "description": "confused face" - , "aliases": [ - "confused" - ] - , "tags": [ - ] - } -, { - "emoji": "😯" - , "description": "hushed face" - , "aliases": [ - "hushed" - ] - , "tags": [ - "silence" - , "speechless" - ] - } -, { - "emoji": "😶" - , "description": "face without mouth" - , "aliases": [ - "no_mouth" - ] - , "tags": [ - "mute" - , "silence" - ] - } -, { - "emoji": "😇" - , "description": "smiling face with halo" - , "aliases": [ - "innocent" - ] - , "tags": [ - "angel" - ] - } -, { - "emoji": "😏" - , "description": "smirking face" - , "aliases": [ - "smirk" - ] - , "tags": [ - "smug" - ] - } -, { - "emoji": "😑" - , "description": "expressionless face" - , "aliases": [ - "expressionless" - ] - , "tags": [ - ] - } -, { - "emoji": "👲" - , "description": "man with gua pi mao" - , "aliases": [ - "man_with_gua_pi_mao" - ] - , "tags": [ - ] - } -, { - "emoji": "👳" - , "description": "man with turban" - , "aliases": [ - "man_with_turban" - ] - , "tags": [ - ] - } -, { - "emoji": "👮" - , "description": "police officer" - , "aliases": [ - "cop" - ] - , "tags": [ - "police" - , "law" - ] - } -, { - "emoji": "👷" - , "description": "construction worker" - , "aliases": [ - "construction_worker" - ] - , "tags": [ - "helmet" - ] - } -, { - "emoji": "💂" - , "description": "guardsman" - , "aliases": [ - "guardsman" - ] - , "tags": [ - ] - } -, { - "emoji": "👶" - , "description": "baby" - , "aliases": [ - "baby" - ] - , "tags": [ - "child" - , "newborn" - ] - } -, { - "emoji": "👦" - , "description": "boy" - , "aliases": [ - "boy" - ] - , "tags": [ - "child" - ] - } -, { - "emoji": "👧" - , "description": "girl" - , "aliases": [ - "girl" - ] - , "tags": [ - "child" - ] - } -, { - "emoji": "👨" - , "description": "man" - , "aliases": [ - "man" - ] - , "tags": [ - "mustache" - , "father" - , "dad" - ] - } -, { - "emoji": "👩" - , "description": "woman" - , "aliases": [ - "woman" - ] - , "tags": [ - "girls" - ] - } -, { - "emoji": "👴" - , "description": "older man" - , "aliases": [ - "older_man" - ] - , "tags": [ - ] - } -, { - "emoji": "👵" - , "description": "older woman" - , "aliases": [ - "older_woman" - ] - , "tags": [ - ] - } -, { - "emoji": "👱" - , "description": "person with blond hair" - , "aliases": [ - "person_with_blond_hair" - ] - , "tags": [ - "boy" - ] - } -, { - "emoji": "👼" - , "description": "baby angel" - , "aliases": [ - "angel" - ] - , "tags": [ - ] - } -, { - "emoji": "👸" - , "description": "princess" - , "aliases": [ - "princess" - ] - , "tags": [ - "blonde" - , "crown" - , "royal" - ] - } -, { - "emoji": "😺" - , "description": "smiling cat face with open mouth" - , "aliases": [ - "smiley_cat" - ] - , "tags": [ - ] - } -, { - "emoji": "😸" - , "description": "grinning cat face with smiling eyes" - , "aliases": [ - "smile_cat" - ] - , "tags": [ - ] - } -, { - "emoji": "😻" - , "description": "smiling cat face with heart-shaped eyes" - , "aliases": [ - "heart_eyes_cat" - ] - , "tags": [ - ] - } -, { - "emoji": "😽" - , "description": "kissing cat face with closed eyes" - , "aliases": [ - "kissing_cat" - ] - , "tags": [ - ] - } -, { - "emoji": "😼" - , "description": "cat face with wry smile" - , "aliases": [ - "smirk_cat" - ] - , "tags": [ - ] - } -, { - "emoji": "🙀" - , "description": "weary cat face" - , "aliases": [ - "scream_cat" - ] - , "tags": [ - "horror" - ] - } -, { - "emoji": "😿" - , "description": "crying cat face" - , "aliases": [ - "crying_cat_face" - ] - , "tags": [ - "sad" - , "tear" - ] - } -, { - "emoji": "😹" - , "description": "cat face with tears of joy" - , "aliases": [ - "joy_cat" - ] - , "tags": [ - ] - } -, { - "emoji": "😾" - , "description": "pouting cat face" - , "aliases": [ - "pouting_cat" - ] - , "tags": [ - ] - } -, { - "emoji": "👹" - , "description": "japanese ogre" - , "aliases": [ - "japanese_ogre" - ] - , "tags": [ - "monster" - ] - } -, { - "emoji": "👺" - , "description": "japanese goblin" - , "aliases": [ - "japanese_goblin" - ] - , "tags": [ - ] - } -, { - "emoji": "🙈" - , "description": "see-no-evil monkey" - , "aliases": [ - "see_no_evil" - ] - , "tags": [ - "monkey" - , "blind" - , "ignore" - ] - } -, { - "emoji": "🙉" - , "description": "hear-no-evil monkey" - , "aliases": [ - "hear_no_evil" - ] - , "tags": [ - "monkey" - , "deaf" - ] - } -, { - "emoji": "🙊" - , "description": "speak-no-evil monkey" - , "aliases": [ - "speak_no_evil" - ] - , "tags": [ - "monkey" - , "mute" - , "hush" - ] - } -, { - "emoji": "💀" - , "description": "skull" - , "aliases": [ - "skull" - ] - , "tags": [ - "dead" - , "danger" - , "poison" - ] - } -, { - "emoji": "👽" - , "description": "extraterrestrial alien" - , "aliases": [ - "alien" - ] - , "tags": [ - "ufo" - ] - } -, { - "emoji": "💩" - , "description": "pile of poo" - , "aliases": [ - "hankey" - , "poop" - , "shit" - ] - , "tags": [ - "crap" - ] - } -, { - "emoji": "🔥" - , "description": "fire" - , "aliases": [ - "fire" - ] - , "tags": [ - "burn" - ] - } -, { - "emoji": "✨" - , "description": "sparkles" - , "aliases": [ - "sparkles" - ] - , "tags": [ - "shiny" - ] - } -, { - "emoji": "🌟" - , "description": "glowing star" - , "aliases": [ - "star2" - ] - , "tags": [ - ] - } -, { - "emoji": "💫" - , "description": "dizzy symbol" - , "aliases": [ - "dizzy" - ] - , "tags": [ - "star" - ] - } -, { - "emoji": "💥" - , "description": "collision symbol" - , "aliases": [ - "boom" - , "collision" - ] - , "tags": [ - "explode" - ] - } -, { - "emoji": "💢" - , "description": "anger symbol" - , "aliases": [ - "anger" - ] - , "tags": [ - "angry" - ] - } -, { - "emoji": "💦" - , "description": "splashing sweat symbol" - , "aliases": [ - "sweat_drops" - ] - , "tags": [ - "water" - , "workout" - ] - } -, { - "emoji": "💧" - , "description": "droplet" - , "aliases": [ - "droplet" - ] - , "tags": [ - "water" - ] - } -, { - "emoji": "💤" - , "description": "sleeping symbol" - , "aliases": [ - "zzz" - ] - , "tags": [ - "sleeping" - ] - } -, { - "emoji": "💨" - , "description": "dash symbol" - , "aliases": [ - "dash" - ] - , "tags": [ - "wind" - , "blow" - , "fast" - ] - } -, { - "emoji": "👂" - , "description": "ear" - , "aliases": [ - "ear" - ] - , "tags": [ - "hear" - , "sound" - , "listen" - ] - } -, { - "emoji": "👀" - , "description": "eyes" - , "aliases": [ - "eyes" - ] - , "tags": [ - "look" - , "see" - , "watch" - ] - } -, { - "emoji": "👃" - , "description": "nose" - , "aliases": [ - "nose" - ] - , "tags": [ - "smell" - ] - } -, { - "emoji": "👅" - , "description": "tongue" - , "aliases": [ - "tongue" - ] - , "tags": [ - "taste" - ] - } -, { - "emoji": "👄" - , "description": "mouth" - , "aliases": [ - "lips" - ] - , "tags": [ - "kiss" - ] - } -, { - "emoji": "👍" - , "description": "thumbs up sign" - , "aliases": [ - "+1" - , "thumbsup" - ] - , "tags": [ - "approve" - , "ok" - ] - } -, { - "emoji": "👎" - , "description": "thumbs down sign" - , "aliases": [ - "-1" - , "thumbsdown" - ] - , "tags": [ - "disapprove" - , "bury" - ] - } -, { - "emoji": "👌" - , "description": "ok hand sign" - , "aliases": [ - "ok_hand" - ] - , "tags": [ - ] - } -, { - "emoji": "👊" - , "description": "fisted hand sign" - , "aliases": [ - "facepunch" - , "punch" - ] - , "tags": [ - "attack" - ] - } -, { - "emoji": "✊" - , "description": "raised fist" - , "aliases": [ - "fist" - ] - , "tags": [ - "power" - ] - } -, { - "emoji": "✌️" - , "description": "victory hand" - , "aliases": [ - "v" - ] - , "tags": [ - "victory" - , "peace" - ] - } -, { - "emoji": "👋" - , "description": "waving hand sign" - , "aliases": [ - "wave" - ] - , "tags": [ - "goodbye" - ] - } -, { - "emoji": "✋" - , "description": "raised hand" - , "aliases": [ - "hand" - , "raised_hand" - ] - , "tags": [ - "highfive" - , "stop" - ] - } -, { - "emoji": "👐" - , "description": "open hands sign" - , "aliases": [ - "open_hands" - ] - , "tags": [ - ] - } -, { - "emoji": "👆" - , "description": "white up pointing backhand index" - , "aliases": [ - "point_up_2" - ] - , "tags": [ - ] - } -, { - "emoji": "👇" - , "description": "white down pointing backhand index" - , "aliases": [ - "point_down" - ] - , "tags": [ - ] - } -, { - "emoji": "👉" - , "description": "white right pointing backhand index" - , "aliases": [ - "point_right" - ] - , "tags": [ - ] - } -, { - "emoji": "👈" - , "description": "white left pointing backhand index" - , "aliases": [ - "point_left" - ] - , "tags": [ - ] - } -, { - "emoji": "🙌" - , "description": "person raising both hands in celebration" - , "aliases": [ - "raised_hands" - ] - , "tags": [ - "hooray" - ] - } -, { - "emoji": "🙏" - , "description": "person with folded hands" - , "aliases": [ - "pray" - ] - , "tags": [ - "please" - , "hope" - , "wish" - ] - } -, { - "emoji": "☝️" - , "description": "white up pointing index" - , "aliases": [ - "point_up" - ] - , "tags": [ - ] - } -, { - "emoji": "👏" - , "description": "clapping hands sign" - , "aliases": [ - "clap" - ] - , "tags": [ - "praise" - , "applause" - ] - } -, { - "emoji": "💪" - , "description": "flexed biceps" - , "aliases": [ - "muscle" - ] - , "tags": [ - "flex" - , "bicep" - , "strong" - , "workout" - ] - } -, { - "emoji": "🚶" - , "description": "pedestrian" - , "aliases": [ - "walking" - ] - , "tags": [ - ] - } -, { - "emoji": "🏃" - , "description": "runner" - , "aliases": [ - "runner" - , "running" - ] - , "tags": [ - "exercise" - , "workout" - , "marathon" - ] - } -, { - "emoji": "💃" - , "description": "dancer" - , "aliases": [ - "dancer" - ] - , "tags": [ - "dress" - ] - } -, { - "emoji": "👫" - , "description": "man and woman holding hands" - , "aliases": [ - "couple" - ] - , "tags": [ - "date" - ] - } -, { - "emoji": "👪" - , "description": "family" - , "aliases": [ - "family" - ] - , "tags": [ - "home" - , "parents" - , "child" - ] - } -, { - "emoji": "👬" - , "description": "two men holding hands" - , "aliases": [ - "two_men_holding_hands" - ] - , "tags": [ - "couple" - , "date" - ] - } -, { - "emoji": "👭" - , "description": "two women holding hands" - , "aliases": [ - "two_women_holding_hands" - ] - , "tags": [ - "couple" - , "date" - ] - } -, { - "emoji": "💏" - , "description": "kiss" - , "aliases": [ - "couplekiss" - ] - , "tags": [ - ] - } -, { - "emoji": "💑" - , "description": "couple with heart" - , "aliases": [ - "couple_with_heart" - ] - , "tags": [ - ] - } -, { - "emoji": "👯" - , "description": "woman with bunny ears" - , "aliases": [ - "dancers" - ] - , "tags": [ - "bunny" - ] - } -, { - "emoji": "🙆" - , "description": "face with ok gesture" - , "aliases": [ - "ok_woman" - ] - , "tags": [ - ] - } -, { - "emoji": "🙅" - , "description": "face with no good gesture" - , "aliases": [ - "no_good" - , "ng_woman" - ] - , "tags": [ - "stop" - , "halt" - ] - } -, { - "emoji": "💁" - , "description": "information desk person" - , "aliases": [ - "information_desk_person" - ] - , "tags": [ - ] - } -, { - "emoji": "🙋" - , "description": "happy person raising one hand" - , "aliases": [ - "raising_hand" - ] - , "tags": [ - ] - } -, { - "emoji": "💆" - , "description": "face massage" - , "aliases": [ - "massage" - ] - , "tags": [ - "spa" - ] - } -, { - "emoji": "💇" - , "description": "haircut" - , "aliases": [ - "haircut" - ] - , "tags": [ - "beauty" - ] - } -, { - "emoji": "💅" - , "description": "nail polish" - , "aliases": [ - "nail_care" - ] - , "tags": [ - "beauty" - , "manicure" - ] - } -, { - "emoji": "👰" - , "description": "bride with veil" - , "aliases": [ - "bride_with_veil" - ] - , "tags": [ - "marriage" - , "wedding" - ] - } -, { - "emoji": "🙎" - , "description": "person with pouting face" - , "aliases": [ - "person_with_pouting_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🙍" - , "description": "person frowning" - , "aliases": [ - "person_frowning" - ] - , "tags": [ - "sad" - ] - } -, { - "emoji": "🙇" - , "description": "person bowing deeply" - , "aliases": [ - "bow" - ] - , "tags": [ - "respect" - , "thanks" - ] - } -, { - "emoji": "🎩" - , "description": "top hat" - , "aliases": [ - "tophat" - ] - , "tags": [ - "hat" - , "classy" - ] - } -, { - "emoji": "👑" - , "description": "crown" - , "aliases": [ - "crown" - ] - , "tags": [ - "king" - , "queen" - , "royal" - ] - } -, { - "emoji": "👒" - , "description": "womans hat" - , "aliases": [ - "womans_hat" - ] - , "tags": [ - ] - } -, { - "emoji": "👟" - , "description": "athletic shoe" - , "aliases": [ - "athletic_shoe" - ] - , "tags": [ - "sneaker" - , "sport" - , "running" - ] - } -, { - "emoji": "👞" - , "description": "mans shoe" - , "aliases": [ - "mans_shoe" - , "shoe" - ] - , "tags": [ - ] - } -, { - "emoji": "👡" - , "description": "womans sandal" - , "aliases": [ - "sandal" - ] - , "tags": [ - "shoe" - ] - } -, { - "emoji": "👠" - , "description": "high-heeled shoe" - , "aliases": [ - "high_heel" - ] - , "tags": [ - "shoe" - ] - } -, { - "emoji": "👢" - , "description": "womans boots" - , "aliases": [ - "boot" - ] - , "tags": [ - ] - } -, { - "emoji": "👕" - , "description": "t-shirt" - , "aliases": [ - "shirt" - , "tshirt" - ] - , "tags": [ - ] - } -, { - "emoji": "👔" - , "description": "necktie" - , "aliases": [ - "necktie" - ] - , "tags": [ - "shirt" - , "formal" - ] - } -, { - "emoji": "👚" - , "description": "womans clothes" - , "aliases": [ - "womans_clothes" - ] - , "tags": [ - ] - } -, { - "emoji": "👗" - , "description": "dress" - , "aliases": [ - "dress" - ] - , "tags": [ - ] - } -, { - "emoji": "🎽" - , "description": "running shirt with sash" - , "aliases": [ - "running_shirt_with_sash" - ] - , "tags": [ - "marathon" - ] - } -, { - "emoji": "👖" - , "description": "jeans" - , "aliases": [ - "jeans" - ] - , "tags": [ - "pants" - ] - } -, { - "emoji": "👘" - , "description": "kimono" - , "aliases": [ - "kimono" - ] - , "tags": [ - ] - } -, { - "emoji": "👙" - , "description": "bikini" - , "aliases": [ - "bikini" - ] - , "tags": [ - "beach" - ] - } -, { - "emoji": "💼" - , "description": "briefcase" - , "aliases": [ - "briefcase" - ] - , "tags": [ - "business" - ] - } -, { - "emoji": "👜" - , "description": "handbag" - , "aliases": [ - "handbag" - ] - , "tags": [ - "bag" - ] - } -, { - "emoji": "👝" - , "description": "pouch" - , "aliases": [ - "pouch" - ] - , "tags": [ - "bag" - ] - } -, { - "emoji": "👛" - , "description": "purse" - , "aliases": [ - "purse" - ] - , "tags": [ - ] - } -, { - "emoji": "👓" - , "description": "eyeglasses" - , "aliases": [ - "eyeglasses" - ] - , "tags": [ - "glasses" - ] - } -, { - "emoji": "🎀" - , "description": "ribbon" - , "aliases": [ - "ribbon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌂" - , "description": "closed umbrella" - , "aliases": [ - "closed_umbrella" - ] - , "tags": [ - "weather" - , "rain" - ] - } -, { - "emoji": "💄" - , "description": "lipstick" - , "aliases": [ - "lipstick" - ] - , "tags": [ - "makeup" - ] - } -, { - "emoji": "💛" - , "description": "yellow heart" - , "aliases": [ - "yellow_heart" - ] - , "tags": [ - ] - } -, { - "emoji": "💙" - , "description": "blue heart" - , "aliases": [ - "blue_heart" - ] - , "tags": [ - ] - } -, { - "emoji": "💜" - , "description": "purple heart" - , "aliases": [ - "purple_heart" - ] - , "tags": [ - ] - } -, { - "emoji": "💚" - , "description": "green heart" - , "aliases": [ - "green_heart" - ] - , "tags": [ - ] - } -, { - "emoji": "❤️" - , "description": "heavy black heart" - , "aliases": [ - "heart" - ] - , "tags": [ - "love" - ] - } -, { - "emoji": "💔" - , "description": "broken heart" - , "aliases": [ - "broken_heart" - ] - , "tags": [ - ] - } -, { - "emoji": "💗" - , "description": "growing heart" - , "aliases": [ - "heartpulse" - ] - , "tags": [ - ] - } -, { - "emoji": "💓" - , "description": "beating heart" - , "aliases": [ - "heartbeat" - ] - , "tags": [ - ] - } -, { - "emoji": "💕" - , "description": "two hearts" - , "aliases": [ - "two_hearts" - ] - , "tags": [ - ] - } -, { - "emoji": "💖" - , "description": "sparkling heart" - , "aliases": [ - "sparkling_heart" - ] - , "tags": [ - ] - } -, { - "emoji": "💞" - , "description": "revolving hearts" - , "aliases": [ - "revolving_hearts" - ] - , "tags": [ - ] - } -, { - "emoji": "💘" - , "description": "heart with arrow" - , "aliases": [ - "cupid" - ] - , "tags": [ - "love" - , "heart" - ] - } -, { - "emoji": "💌" - , "description": "love letter" - , "aliases": [ - "love_letter" - ] - , "tags": [ - "email" - , "envelope" - ] - } -, { - "emoji": "💋" - , "description": "kiss mark" - , "aliases": [ - "kiss" - ] - , "tags": [ - "lipstick" - ] - } -, { - "emoji": "💍" - , "description": "ring" - , "aliases": [ - "ring" - ] - , "tags": [ - "wedding" - , "marriage" - , "engaged" - ] - } -, { - "emoji": "💎" - , "description": "gem stone" - , "aliases": [ - "gem" - ] - , "tags": [ - "diamond" - ] - } -, { - "emoji": "👤" - , "description": "bust in silhouette" - , "aliases": [ - "bust_in_silhouette" - ] - , "tags": [ - "user" - ] - } -, { - "emoji": "👥" - , "description": "busts in silhouette" - , "aliases": [ - "busts_in_silhouette" - ] - , "tags": [ - "users" - , "group" - , "team" - ] - } -, { - "emoji": "💬" - , "description": "speech balloon" - , "aliases": [ - "speech_balloon" - ] - , "tags": [ - "comment" - ] - } -, { - "emoji": "👣" - , "description": "footprints" - , "aliases": [ - "footprints" - ] - , "tags": [ - "feet" - , "tracks" - ] - } -, { - "emoji": "💭" - , "description": "thought balloon" - , "aliases": [ - "thought_balloon" - ] - , "tags": [ - "thinking" - ] - } -, { - "emoji": "🐶" - , "description": "dog face" - , "aliases": [ - "dog" - ] - , "tags": [ - "pet" - ] - } -, { - "emoji": "🐺" - , "description": "wolf face" - , "aliases": [ - "wolf" - ] - , "tags": [ - ] - } -, { - "emoji": "🐱" - , "description": "cat face" - , "aliases": [ - "cat" - ] - , "tags": [ - "pet" - ] - } -, { - "emoji": "🐭" - , "description": "mouse face" - , "aliases": [ - "mouse" - ] - , "tags": [ - ] - } -, { - "emoji": "🐹" - , "description": "hamster face" - , "aliases": [ - "hamster" - ] - , "tags": [ - "pet" - ] - } -, { - "emoji": "🐰" - , "description": "rabbit face" - , "aliases": [ - "rabbit" - ] - , "tags": [ - "bunny" - ] - } -, { - "emoji": "🐸" - , "description": "frog face" - , "aliases": [ - "frog" - ] - , "tags": [ - ] - } -, { - "emoji": "🐯" - , "description": "tiger face" - , "aliases": [ - "tiger" - ] - , "tags": [ - ] - } -, { - "emoji": "🐨" - , "description": "koala" - , "aliases": [ - "koala" - ] - , "tags": [ - ] - } -, { - "emoji": "🐻" - , "description": "bear face" - , "aliases": [ - "bear" - ] - , "tags": [ - ] - } -, { - "emoji": "🐷" - , "description": "pig face" - , "aliases": [ - "pig" - ] - , "tags": [ - ] - } -, { - "emoji": "🐽" - , "description": "pig nose" - , "aliases": [ - "pig_nose" - ] - , "tags": [ - ] - } -, { - "emoji": "🐮" - , "description": "cow face" - , "aliases": [ - "cow" - ] - , "tags": [ - ] - } -, { - "emoji": "🐗" - , "description": "boar" - , "aliases": [ - "boar" - ] - , "tags": [ - ] - } -, { - "emoji": "🐵" - , "description": "monkey face" - , "aliases": [ - "monkey_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🐒" - , "description": "monkey" - , "aliases": [ - "monkey" - ] - , "tags": [ - ] - } -, { - "emoji": "🐴" - , "description": "horse face" - , "aliases": [ - "horse" - ] - , "tags": [ - ] - } -, { - "emoji": "🐑" - , "description": "sheep" - , "aliases": [ - "sheep" - ] - , "tags": [ - ] - } -, { - "emoji": "🐘" - , "description": "elephant" - , "aliases": [ - "elephant" - ] - , "tags": [ - ] - } -, { - "emoji": "🐼" - , "description": "panda face" - , "aliases": [ - "panda_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🐧" - , "description": "penguin" - , "aliases": [ - "penguin" - ] - , "tags": [ - ] - } -, { - "emoji": "🐦" - , "description": "bird" - , "aliases": [ - "bird" - ] - , "tags": [ - ] - } -, { - "emoji": "🐤" - , "description": "baby chick" - , "aliases": [ - "baby_chick" - ] - , "tags": [ - ] - } -, { - "emoji": "🐥" - , "description": "front-facing baby chick" - , "aliases": [ - "hatched_chick" - ] - , "tags": [ - ] - } -, { - "emoji": "🐣" - , "description": "hatching chick" - , "aliases": [ - "hatching_chick" - ] - , "tags": [ - ] - } -, { - "emoji": "🐔" - , "description": "chicken" - , "aliases": [ - "chicken" - ] - , "tags": [ - ] - } -, { - "emoji": "🐍" - , "description": "snake" - , "aliases": [ - "snake" - ] - , "tags": [ - ] - } -, { - "emoji": "🐢" - , "description": "turtle" - , "aliases": [ - "turtle" - ] - , "tags": [ - "slow" - ] - } -, { - "emoji": "🐛" - , "description": "bug" - , "aliases": [ - "bug" - ] - , "tags": [ - ] - } -, { - "emoji": "🐝" - , "description": "honeybee" - , "aliases": [ - "bee" - , "honeybee" - ] - , "tags": [ - ] - } -, { - "emoji": "🐜" - , "description": "ant" - , "aliases": [ - "ant" - ] - , "tags": [ - ] - } -, { - "emoji": "🐞" - , "description": "lady beetle" - , "aliases": [ - "beetle" - ] - , "tags": [ - "bug" - ] - } -, { - "emoji": "🐌" - , "description": "snail" - , "aliases": [ - "snail" - ] - , "tags": [ - "slow" - ] - } -, { - "emoji": "🐙" - , "description": "octopus" - , "aliases": [ - "octopus" - ] - , "tags": [ - ] - } -, { - "emoji": "🐚" - , "description": "spiral shell" - , "aliases": [ - "shell" - ] - , "tags": [ - "sea" - , "beach" - ] - } -, { - "emoji": "🐠" - , "description": "tropical fish" - , "aliases": [ - "tropical_fish" - ] - , "tags": [ - ] - } -, { - "emoji": "🐟" - , "description": "fish" - , "aliases": [ - "fish" - ] - , "tags": [ - ] - } -, { - "emoji": "🐬" - , "description": "dolphin" - , "aliases": [ - "dolphin" - , "flipper" - ] - , "tags": [ - ] - } -, { - "emoji": "🐳" - , "description": "spouting whale" - , "aliases": [ - "whale" - ] - , "tags": [ - "sea" - ] - } -, { - "emoji": "🐋" - , "description": "whale" - , "aliases": [ - "whale2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐄" - , "description": "cow" - , "aliases": [ - "cow2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐏" - , "description": "ram" - , "aliases": [ - "ram" - ] - , "tags": [ - ] - } -, { - "emoji": "🐀" - , "description": "rat" - , "aliases": [ - "rat" - ] - , "tags": [ - ] - } -, { - "emoji": "🐃" - , "description": "water buffalo" - , "aliases": [ - "water_buffalo" - ] - , "tags": [ - ] - } -, { - "emoji": "🐅" - , "description": "tiger" - , "aliases": [ - "tiger2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐇" - , "description": "rabbit" - , "aliases": [ - "rabbit2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐉" - , "description": "dragon" - , "aliases": [ - "dragon" - ] - , "tags": [ - ] - } -, { - "emoji": "🐎" - , "description": "horse" - , "aliases": [ - "racehorse" - ] - , "tags": [ - "speed" - ] - } -, { - "emoji": "🐐" - , "description": "goat" - , "aliases": [ - "goat" - ] - , "tags": [ - ] - } -, { - "emoji": "🐓" - , "description": "rooster" - , "aliases": [ - "rooster" - ] - , "tags": [ - ] - } -, { - "emoji": "🐕" - , "description": "dog" - , "aliases": [ - "dog2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐖" - , "description": "pig" - , "aliases": [ - "pig2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐁" - , "description": "mouse" - , "aliases": [ - "mouse2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐂" - , "description": "ox" - , "aliases": [ - "ox" - ] - , "tags": [ - ] - } -, { - "emoji": "🐲" - , "description": "dragon face" - , "aliases": [ - "dragon_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🐡" - , "description": "blowfish" - , "aliases": [ - "blowfish" - ] - , "tags": [ - ] - } -, { - "emoji": "🐊" - , "description": "crocodile" - , "aliases": [ - "crocodile" - ] - , "tags": [ - ] - } -, { - "emoji": "🐫" - , "description": "bactrian camel" - , "aliases": [ - "camel" - ] - , "tags": [ - ] - } -, { - "emoji": "🐪" - , "description": "dromedary camel" - , "aliases": [ - "dromedary_camel" - ] - , "tags": [ - "desert" - ] - } -, { - "emoji": "🐆" - , "description": "leopard" - , "aliases": [ - "leopard" - ] - , "tags": [ - ] - } -, { - "emoji": "🐈" - , "description": "cat" - , "aliases": [ - "cat2" - ] - , "tags": [ - ] - } -, { - "emoji": "🐩" - , "description": "poodle" - , "aliases": [ - "poodle" - ] - , "tags": [ - "dog" - ] - } -, { - "emoji": "🐾" - , "description": "paw prints" - , "aliases": [ - "feet" - , "paw_prints" - ] - , "tags": [ - ] - } -, { - "emoji": "💐" - , "description": "bouquet" - , "aliases": [ - "bouquet" - ] - , "tags": [ - "flowers" - ] - } -, { - "emoji": "🌸" - , "description": "cherry blossom" - , "aliases": [ - "cherry_blossom" - ] - , "tags": [ - "flower" - , "spring" - ] - } -, { - "emoji": "🌷" - , "description": "tulip" - , "aliases": [ - "tulip" - ] - , "tags": [ - "flower" - ] - } -, { - "emoji": "🍀" - , "description": "four leaf clover" - , "aliases": [ - "four_leaf_clover" - ] - , "tags": [ - "luck" - ] - } -, { - "emoji": "🌹" - , "description": "rose" - , "aliases": [ - "rose" - ] - , "tags": [ - "flower" - ] - } -, { - "emoji": "🌻" - , "description": "sunflower" - , "aliases": [ - "sunflower" - ] - , "tags": [ - ] - } -, { - "emoji": "🌺" - , "description": "hibiscus" - , "aliases": [ - "hibiscus" - ] - , "tags": [ - ] - } -, { - "emoji": "🍁" - , "description": "maple leaf" - , "aliases": [ - "maple_leaf" - ] - , "tags": [ - "canada" - ] - } -, { - "emoji": "🍃" - , "description": "leaf fluttering in wind" - , "aliases": [ - "leaves" - ] - , "tags": [ - "leaf" - ] - } -, { - "emoji": "🍂" - , "description": "fallen leaf" - , "aliases": [ - "fallen_leaf" - ] - , "tags": [ - "autumn" - ] - } -, { - "emoji": "🌿" - , "description": "herb" - , "aliases": [ - "herb" - ] - , "tags": [ - ] - } -, { - "emoji": "🌾" - , "description": "ear of rice" - , "aliases": [ - "ear_of_rice" - ] - , "tags": [ - ] - } -, { - "emoji": "🍄" - , "description": "mushroom" - , "aliases": [ - "mushroom" - ] - , "tags": [ - ] - } -, { - "emoji": "🌵" - , "description": "cactus" - , "aliases": [ - "cactus" - ] - , "tags": [ - ] - } -, { - "emoji": "🌴" - , "description": "palm tree" - , "aliases": [ - "palm_tree" - ] - , "tags": [ - ] - } -, { - "emoji": "🌲" - , "description": "evergreen tree" - , "aliases": [ - "evergreen_tree" - ] - , "tags": [ - "wood" - ] - } -, { - "emoji": "🌳" - , "description": "deciduous tree" - , "aliases": [ - "deciduous_tree" - ] - , "tags": [ - "wood" - ] - } -, { - "emoji": "🌰" - , "description": "chestnut" - , "aliases": [ - "chestnut" - ] - , "tags": [ - ] - } -, { - "emoji": "🌱" - , "description": "seedling" - , "aliases": [ - "seedling" - ] - , "tags": [ - "plant" - ] - } -, { - "emoji": "🌼" - , "description": "blossom" - , "aliases": [ - "blossom" - ] - , "tags": [ - ] - } -, { - "emoji": "🌐" - , "description": "globe with meridians" - , "aliases": [ - "globe_with_meridians" - ] - , "tags": [ - "world" - , "global" - , "international" - ] - } -, { - "emoji": "🌞" - , "description": "sun with face" - , "aliases": [ - "sun_with_face" - ] - , "tags": [ - "summer" - ] - } -, { - "emoji": "🌝" - , "description": "full moon with face" - , "aliases": [ - "full_moon_with_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🌚" - , "description": "new moon with face" - , "aliases": [ - "new_moon_with_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🌑" - , "description": "new moon symbol" - , "aliases": [ - "new_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌒" - , "description": "waxing crescent moon symbol" - , "aliases": [ - "waxing_crescent_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌓" - , "description": "first quarter moon symbol" - , "aliases": [ - "first_quarter_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌔" - , "description": "waxing gibbous moon symbol" - , "aliases": [ - "moon" - , "waxing_gibbous_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌕" - , "description": "full moon symbol" - , "aliases": [ - "full_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌖" - , "description": "waning gibbous moon symbol" - , "aliases": [ - "waning_gibbous_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌗" - , "description": "last quarter moon symbol" - , "aliases": [ - "last_quarter_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌘" - , "description": "waning crescent moon symbol" - , "aliases": [ - "waning_crescent_moon" - ] - , "tags": [ - ] - } -, { - "emoji": "🌜" - , "description": "last quarter moon with face" - , "aliases": [ - "last_quarter_moon_with_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🌛" - , "description": "first quarter moon with face" - , "aliases": [ - "first_quarter_moon_with_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🌙" - , "description": "crescent moon" - , "aliases": [ - "crescent_moon" - ] - , "tags": [ - "night" - ] - } -, { - "emoji": "🌍" - , "description": "earth globe europe-africa" - , "aliases": [ - "earth_africa" - ] - , "tags": [ - "globe" - , "world" - , "international" - ] - } -, { - "emoji": "🌎" - , "description": "earth globe americas" - , "aliases": [ - "earth_americas" - ] - , "tags": [ - "globe" - , "world" - , "international" - ] - } -, { - "emoji": "🌏" - , "description": "earth globe asia-australia" - , "aliases": [ - "earth_asia" - ] - , "tags": [ - "globe" - , "world" - , "international" - ] - } -, { - "emoji": "🌋" - , "description": "volcano" - , "aliases": [ - "volcano" - ] - , "tags": [ - ] - } -, { - "emoji": "🌌" - , "description": "milky way" - , "aliases": [ - "milky_way" - ] - , "tags": [ - ] - } -, { - "emoji": "🌠" - , "description": "shooting star" - , "aliases": [ - "stars" - ] - , "tags": [ - ] - } -, { - "emoji": "⭐" - , "description": "white medium star" - , "aliases": [ - "star" - ] - , "tags": [ - ] - } -, { - "emoji": "☀️" - , "description": "black sun with rays" - , "aliases": [ - "sunny" - ] - , "tags": [ - "weather" - ] - } -, { - "emoji": "⛅" - , "description": "sun behind cloud" - , "aliases": [ - "partly_sunny" - ] - , "tags": [ - "weather" - , "cloud" - ] - } -, { - "emoji": "☁️" - , "description": "cloud" - , "aliases": [ - "cloud" - ] - , "tags": [ - ] - } -, { - "emoji": "⚡" - , "description": "high voltage sign" - , "aliases": [ - "zap" - ] - , "tags": [ - "lightning" - , "thunder" - ] - } -, { - "emoji": "☔" - , "description": "umbrella with rain drops" - , "aliases": [ - "umbrella" - ] - , "tags": [ - "rain" - , "weather" - ] - } -, { - "emoji": "❄️" - , "description": "snowflake" - , "aliases": [ - "snowflake" - ] - , "tags": [ - "winter" - , "cold" - , "weather" - ] - } -, { - "emoji": "⛄" - , "description": "snowman without snow" - , "aliases": [ - "snowman" - ] - , "tags": [ - "winter" - , "christmas" - ] - } -, { - "emoji": "🌀" - , "description": "cyclone" - , "aliases": [ - "cyclone" - ] - , "tags": [ - "swirl" - ] - } -, { - "emoji": "🌁" - , "description": "foggy" - , "aliases": [ - "foggy" - ] - , "tags": [ - "karl" - ] - } -, { - "emoji": "🌈" - , "description": "rainbow" - , "aliases": [ - "rainbow" - ] - , "tags": [ - "pride" - ] - } -, { - "emoji": "🌊" - , "description": "water wave" - , "aliases": [ - "ocean" - ] - , "tags": [ - "sea" - ] - } -, { - "emoji": "🎍" - , "description": "pine decoration" - , "aliases": [ - "bamboo" - ] - , "tags": [ - ] - } -, { - "emoji": "💝" - , "description": "heart with ribbon" - , "aliases": [ - "gift_heart" - ] - , "tags": [ - "chocolates" - ] - } -, { - "emoji": "🎎" - , "description": "japanese dolls" - , "aliases": [ - "dolls" - ] - , "tags": [ - ] - } -, { - "emoji": "🎒" - , "description": "school satchel" - , "aliases": [ - "school_satchel" - ] - , "tags": [ - ] - } -, { - "emoji": "🎓" - , "description": "graduation cap" - , "aliases": [ - "mortar_board" - ] - , "tags": [ - "education" - , "college" - , "university" - , "graduation" - ] - } -, { - "emoji": "🎏" - , "description": "carp streamer" - , "aliases": [ - "flags" - ] - , "tags": [ - ] - } -, { - "emoji": "🎆" - , "description": "fireworks" - , "aliases": [ - "fireworks" - ] - , "tags": [ - "festival" - , "celebration" - ] - } -, { - "emoji": "🎇" - , "description": "firework sparkler" - , "aliases": [ - "sparkler" - ] - , "tags": [ - ] - } -, { - "emoji": "🎐" - , "description": "wind chime" - , "aliases": [ - "wind_chime" - ] - , "tags": [ - ] - } -, { - "emoji": "🎑" - , "description": "moon viewing ceremony" - , "aliases": [ - "rice_scene" - ] - , "tags": [ - ] - } -, { - "emoji": "🎃" - , "description": "jack-o-lantern" - , "aliases": [ - "jack_o_lantern" - ] - , "tags": [ - "halloween" - ] - } -, { - "emoji": "👻" - , "description": "ghost" - , "aliases": [ - "ghost" - ] - , "tags": [ - "halloween" - ] - } -, { - "emoji": "🎅" - , "description": "father christmas" - , "aliases": [ - "santa" - ] - , "tags": [ - "christmas" - ] - } -, { - "emoji": "🎄" - , "description": "christmas tree" - , "aliases": [ - "christmas_tree" - ] - , "tags": [ - ] - } -, { - "emoji": "🎁" - , "description": "wrapped present" - , "aliases": [ - "gift" - ] - , "tags": [ - "present" - , "birthday" - , "christmas" - ] - } -, { - "emoji": "🎋" - , "description": "tanabata tree" - , "aliases": [ - "tanabata_tree" - ] - , "tags": [ - ] - } -, { - "emoji": "🎉" - , "description": "party popper" - , "aliases": [ - "tada" - ] - , "tags": [ - "party" - ] - } -, { - "emoji": "🎊" - , "description": "confetti ball" - , "aliases": [ - "confetti_ball" - ] - , "tags": [ - ] - } -, { - "emoji": "🎈" - , "description": "balloon" - , "aliases": [ - "balloon" - ] - , "tags": [ - "party" - , "birthday" - ] - } -, { - "emoji": "🎌" - , "description": "crossed flags" - , "aliases": [ - "crossed_flags" - ] - , "tags": [ - ] - } -, { - "emoji": "🔮" - , "description": "crystal ball" - , "aliases": [ - "crystal_ball" - ] - , "tags": [ - "fortune" - ] - } -, { - "emoji": "🎥" - , "description": "movie camera" - , "aliases": [ - "movie_camera" - ] - , "tags": [ - "film" - , "video" - ] - } -, { - "emoji": "📷" - , "description": "camera" - , "aliases": [ - "camera" - ] - , "tags": [ - "photo" - ] - } -, { - "emoji": "📹" - , "description": "video camera" - , "aliases": [ - "video_camera" - ] - , "tags": [ - ] - } -, { - "emoji": "📼" - , "description": "videocassette" - , "aliases": [ - "vhs" - ] - , "tags": [ - ] - } -, { - "emoji": "💿" - , "description": "optical disc" - , "aliases": [ - "cd" - ] - , "tags": [ - ] - } -, { - "emoji": "📀" - , "description": "dvd" - , "aliases": [ - "dvd" - ] - , "tags": [ - ] - } -, { - "emoji": "💽" - , "description": "minidisc" - , "aliases": [ - "minidisc" - ] - , "tags": [ - ] - } -, { - "emoji": "💾" - , "description": "floppy disk" - , "aliases": [ - "floppy_disk" - ] - , "tags": [ - "save" - ] - } -, { - "emoji": "💻" - , "description": "personal computer" - , "aliases": [ - "computer" - ] - , "tags": [ - "desktop" - , "screen" - ] - } -, { - "emoji": "📱" - , "description": "mobile phone" - , "aliases": [ - "iphone" - ] - , "tags": [ - "smartphone" - , "mobile" - ] - } -, { - "emoji": "☎️" - , "description": "black telephone" - , "aliases": [ - "phone" - , "telephone" - ] - , "tags": [ - ] - } -, { - "emoji": "📞" - , "description": "telephone receiver" - , "aliases": [ - "telephone_receiver" - ] - , "tags": [ - "phone" - , "call" - ] - } -, { - "emoji": "📟" - , "description": "pager" - , "aliases": [ - "pager" - ] - , "tags": [ - ] - } -, { - "emoji": "📠" - , "description": "fax machine" - , "aliases": [ - "fax" - ] - , "tags": [ - ] - } -, { - "emoji": "📡" - , "description": "satellite antenna" - , "aliases": [ - "satellite" - ] - , "tags": [ - "signal" - ] - } -, { - "emoji": "📺" - , "description": "television" - , "aliases": [ - "tv" - ] - , "tags": [ - ] - } -, { - "emoji": "📻" - , "description": "radio" - , "aliases": [ - "radio" - ] - , "tags": [ - "podcast" - ] - } -, { - "emoji": "🔊" - , "description": "speaker with three sound waves" - , "aliases": [ - "loud_sound" - ] - , "tags": [ - "volume" - ] - } -, { - "emoji": "🔉" - , "description": "speaker with one sound wave" - , "aliases": [ - "sound" - ] - , "tags": [ - "volume" - ] - } -, { - "emoji": "🔈" - , "description": "speaker" - , "aliases": [ - "speaker" - ] - , "tags": [ - ] - } -, { - "emoji": "🔇" - , "description": "speaker with cancellation stroke" - , "aliases": [ - "mute" - ] - , "tags": [ - "sound" - , "volume" - ] - } -, { - "emoji": "🔔" - , "description": "bell" - , "aliases": [ - "bell" - ] - , "tags": [ - "sound" - , "notification" - ] - } -, { - "emoji": "🔕" - , "description": "bell with cancellation stroke" - , "aliases": [ - "no_bell" - ] - , "tags": [ - "volume" - , "off" - ] - } -, { - "emoji": "📢" - , "description": "public address loudspeaker" - , "aliases": [ - "loudspeaker" - ] - , "tags": [ - "announcement" - ] - } -, { - "emoji": "📣" - , "description": "cheering megaphone" - , "aliases": [ - "mega" - ] - , "tags": [ - ] - } -, { - "emoji": "⏳" - , "description": "hourglass with flowing sand" - , "aliases": [ - "hourglass_flowing_sand" - ] - , "tags": [ - "time" - ] - } -, { - "emoji": "⌛" - , "description": "hourglass" - , "aliases": [ - "hourglass" - ] - , "tags": [ - "time" - ] - } -, { - "emoji": "⏰" - , "description": "alarm clock" - , "aliases": [ - "alarm_clock" - ] - , "tags": [ - "morning" - ] - } -, { - "emoji": "⌚" - , "description": "watch" - , "aliases": [ - "watch" - ] - , "tags": [ - "time" - ] - } -, { - "emoji": "🔓" - , "description": "open lock" - , "aliases": [ - "unlock" - ] - , "tags": [ - "security" - ] - } -, { - "emoji": "🔒" - , "description": "lock" - , "aliases": [ - "lock" - ] - , "tags": [ - "security" - , "private" - ] - } -, { - "emoji": "🔏" - , "description": "lock with ink pen" - , "aliases": [ - "lock_with_ink_pen" - ] - , "tags": [ - ] - } -, { - "emoji": "🔐" - , "description": "closed lock with key" - , "aliases": [ - "closed_lock_with_key" - ] - , "tags": [ - "security" - ] - } -, { - "emoji": "🔑" - , "description": "key" - , "aliases": [ - "key" - ] - , "tags": [ - "lock" - , "password" - ] - } -, { - "emoji": "🔎" - , "description": "right-pointing magnifying glass" - , "aliases": [ - "mag_right" - ] - , "tags": [ - ] - } -, { - "emoji": "💡" - , "description": "electric light bulb" - , "aliases": [ - "bulb" - ] - , "tags": [ - "idea" - , "light" - ] - } -, { - "emoji": "🔦" - , "description": "electric torch" - , "aliases": [ - "flashlight" - ] - , "tags": [ - ] - } -, { - "emoji": "🔆" - , "description": "high brightness symbol" - , "aliases": [ - "high_brightness" - ] - , "tags": [ - ] - } -, { - "emoji": "🔅" - , "description": "low brightness symbol" - , "aliases": [ - "low_brightness" - ] - , "tags": [ - ] - } -, { - "emoji": "🔌" - , "description": "electric plug" - , "aliases": [ - "electric_plug" - ] - , "tags": [ - ] - } -, { - "emoji": "🔋" - , "description": "battery" - , "aliases": [ - "battery" - ] - , "tags": [ - "power" - ] - } -, { - "emoji": "🔍" - , "description": "left-pointing magnifying glass" - , "aliases": [ - "mag" - ] - , "tags": [ - "search" - , "zoom" - ] - } -, { - "emoji": "🛁" - , "description": "bathtub" - , "aliases": [ - "bathtub" - ] - , "tags": [ - ] - } -, { - "emoji": "🛀" - , "description": "bath" - , "aliases": [ - "bath" - ] - , "tags": [ - "shower" - ] - } -, { - "emoji": "🚿" - , "description": "shower" - , "aliases": [ - "shower" - ] - , "tags": [ - "bath" - ] - } -, { - "emoji": "🚽" - , "description": "toilet" - , "aliases": [ - "toilet" - ] - , "tags": [ - "wc" - ] - } -, { - "emoji": "🔧" - , "description": "wrench" - , "aliases": [ - "wrench" - ] - , "tags": [ - "tool" - ] - } -, { - "emoji": "🔩" - , "description": "nut and bolt" - , "aliases": [ - "nut_and_bolt" - ] - , "tags": [ - ] - } -, { - "emoji": "🔨" - , "description": "hammer" - , "aliases": [ - "hammer" - ] - , "tags": [ - "tool" - ] - } -, { - "emoji": "🚪" - , "description": "door" - , "aliases": [ - "door" - ] - , "tags": [ - ] - } -, { - "emoji": "🚬" - , "description": "smoking symbol" - , "aliases": [ - "smoking" - ] - , "tags": [ - "cigarette" - ] - } -, { - "emoji": "💣" - , "description": "bomb" - , "aliases": [ - "bomb" - ] - , "tags": [ - "boom" - ] - } -, { - "emoji": "🔫" - , "description": "pistol" - , "aliases": [ - "gun" - ] - , "tags": [ - "shoot" - , "weapon" - ] - } -, { - "emoji": "🔪" - , "description": "hocho" - , "aliases": [ - "hocho" - , "knife" - ] - , "tags": [ - "cut" - , "chop" - ] - } -, { - "emoji": "💊" - , "description": "pill" - , "aliases": [ - "pill" - ] - , "tags": [ - "health" - , "medicine" - ] - } -, { - "emoji": "💉" - , "description": "syringe" - , "aliases": [ - "syringe" - ] - , "tags": [ - "health" - , "hospital" - , "needle" - ] - } -, { - "emoji": "💰" - , "description": "money bag" - , "aliases": [ - "moneybag" - ] - , "tags": [ - "dollar" - , "cream" - ] - } -, { - "emoji": "💴" - , "description": "banknote with yen sign" - , "aliases": [ - "yen" - ] - , "tags": [ - ] - } -, { - "emoji": "💵" - , "description": "banknote with dollar sign" - , "aliases": [ - "dollar" - ] - , "tags": [ - "money" - ] - } -, { - "emoji": "💷" - , "description": "banknote with pound sign" - , "aliases": [ - "pound" - ] - , "tags": [ - ] - } -, { - "emoji": "💶" - , "description": "banknote with euro sign" - , "aliases": [ - "euro" - ] - , "tags": [ - ] - } -, { - "emoji": "💳" - , "description": "credit card" - , "aliases": [ - "credit_card" - ] - , "tags": [ - "subscription" - ] - } -, { - "emoji": "💸" - , "description": "money with wings" - , "aliases": [ - "money_with_wings" - ] - , "tags": [ - "dollar" - ] - } -, { - "emoji": "📲" - , "description": "mobile phone with rightwards arrow at left" - , "aliases": [ - "calling" - ] - , "tags": [ - "call" - , "incoming" - ] - } -, { - "emoji": "📧" - , "description": "e-mail symbol" - , "aliases": [ - "e-mail" - ] - , "tags": [ - ] - } -, { - "emoji": "📥" - , "description": "inbox tray" - , "aliases": [ - "inbox_tray" - ] - , "tags": [ - ] - } -, { - "emoji": "📤" - , "description": "outbox tray" - , "aliases": [ - "outbox_tray" - ] - , "tags": [ - ] - } -, { - "emoji": "✉️" - , "description": "envelope" - , "aliases": [ - "email" - , "envelope" - ] - , "tags": [ - "letter" - ] - } -, { - "emoji": "📩" - , "description": "envelope with downwards arrow above" - , "aliases": [ - "envelope_with_arrow" - ] - , "tags": [ - ] - } -, { - "emoji": "📨" - , "description": "incoming envelope" - , "aliases": [ - "incoming_envelope" - ] - , "tags": [ - ] - } -, { - "emoji": "📯" - , "description": "postal horn" - , "aliases": [ - "postal_horn" - ] - , "tags": [ - ] - } -, { - "emoji": "📫" - , "description": "closed mailbox with raised flag" - , "aliases": [ - "mailbox" - ] - , "tags": [ - ] - } -, { - "emoji": "📪" - , "description": "closed mailbox with lowered flag" - , "aliases": [ - "mailbox_closed" - ] - , "tags": [ - ] - } -, { - "emoji": "📬" - , "description": "open mailbox with raised flag" - , "aliases": [ - "mailbox_with_mail" - ] - , "tags": [ - ] - } -, { - "emoji": "📭" - , "description": "open mailbox with lowered flag" - , "aliases": [ - "mailbox_with_no_mail" - ] - , "tags": [ - ] - } -, { - "emoji": "📮" - , "description": "postbox" - , "aliases": [ - "postbox" - ] - , "tags": [ - ] - } -, { - "emoji": "📦" - , "description": "package" - , "aliases": [ - "package" - ] - , "tags": [ - "shipping" - ] - } -, { - "emoji": "📝" - , "description": "memo" - , "aliases": [ - "memo" - , "pencil" - ] - , "tags": [ - "document" - , "note" - ] - } -, { - "emoji": "📄" - , "description": "page facing up" - , "aliases": [ - "page_facing_up" - ] - , "tags": [ - "document" - ] - } -, { - "emoji": "📃" - , "description": "page with curl" - , "aliases": [ - "page_with_curl" - ] - , "tags": [ - ] - } -, { - "emoji": "📑" - , "description": "bookmark tabs" - , "aliases": [ - "bookmark_tabs" - ] - , "tags": [ - ] - } -, { - "emoji": "📊" - , "description": "bar chart" - , "aliases": [ - "bar_chart" - ] - , "tags": [ - "stats" - , "metrics" - ] - } -, { - "emoji": "📈" - , "description": "chart with upwards trend" - , "aliases": [ - "chart_with_upwards_trend" - ] - , "tags": [ - "graph" - , "metrics" - ] - } -, { - "emoji": "📉" - , "description": "chart with downwards trend" - , "aliases": [ - "chart_with_downwards_trend" - ] - , "tags": [ - "graph" - , "metrics" - ] - } -, { - "emoji": "📜" - , "description": "scroll" - , "aliases": [ - "scroll" - ] - , "tags": [ - "document" - ] - } -, { - "emoji": "📋" - , "description": "clipboard" - , "aliases": [ - "clipboard" - ] - , "tags": [ - ] - } -, { - "emoji": "📅" - , "description": "calendar" - , "aliases": [ - "date" - ] - , "tags": [ - "calendar" - , "schedule" - ] - } -, { - "emoji": "📆" - , "description": "tear-off calendar" - , "aliases": [ - "calendar" - ] - , "tags": [ - "schedule" - ] - } -, { - "emoji": "📇" - , "description": "card index" - , "aliases": [ - "card_index" - ] - , "tags": [ - ] - } -, { - "emoji": "📁" - , "description": "file folder" - , "aliases": [ - "file_folder" - ] - , "tags": [ - "directory" - ] - } -, { - "emoji": "📂" - , "description": "open file folder" - , "aliases": [ - "open_file_folder" - ] - , "tags": [ - ] - } -, { - "emoji": "✂️" - , "description": "black scissors" - , "aliases": [ - "scissors" - ] - , "tags": [ - "cut" - ] - } -, { - "emoji": "📌" - , "description": "pushpin" - , "aliases": [ - "pushpin" - ] - , "tags": [ - "location" - ] - } -, { - "emoji": "📎" - , "description": "paperclip" - , "aliases": [ - "paperclip" - ] - , "tags": [ - ] - } -, { - "emoji": "✒️" - , "description": "black nib" - , "aliases": [ - "black_nib" - ] - , "tags": [ - ] - } -, { - "emoji": "✏️" - , "description": "pencil" - , "aliases": [ - "pencil2" - ] - , "tags": [ - ] - } -, { - "emoji": "📏" - , "description": "straight ruler" - , "aliases": [ - "straight_ruler" - ] - , "tags": [ - ] - } -, { - "emoji": "📐" - , "description": "triangular ruler" - , "aliases": [ - "triangular_ruler" - ] - , "tags": [ - ] - } -, { - "emoji": "📕" - , "description": "closed book" - , "aliases": [ - "closed_book" - ] - , "tags": [ - ] - } -, { - "emoji": "📗" - , "description": "green book" - , "aliases": [ - "green_book" - ] - , "tags": [ - ] - } -, { - "emoji": "📘" - , "description": "blue book" - , "aliases": [ - "blue_book" - ] - , "tags": [ - ] - } -, { - "emoji": "📙" - , "description": "orange book" - , "aliases": [ - "orange_book" - ] - , "tags": [ - ] - } -, { - "emoji": "📓" - , "description": "notebook" - , "aliases": [ - "notebook" - ] - , "tags": [ - ] - } -, { - "emoji": "📔" - , "description": "notebook with decorative cover" - , "aliases": [ - "notebook_with_decorative_cover" - ] - , "tags": [ - ] - } -, { - "emoji": "📒" - , "description": "ledger" - , "aliases": [ - "ledger" - ] - , "tags": [ - ] - } -, { - "emoji": "📚" - , "description": "books" - , "aliases": [ - "books" - ] - , "tags": [ - "library" - ] - } -, { - "emoji": "📖" - , "description": "open book" - , "aliases": [ - "book" - , "open_book" - ] - , "tags": [ - ] - } -, { - "emoji": "🔖" - , "description": "bookmark" - , "aliases": [ - "bookmark" - ] - , "tags": [ - ] - } -, { - "emoji": "📛" - , "description": "name badge" - , "aliases": [ - "name_badge" - ] - , "tags": [ - ] - } -, { - "emoji": "🔬" - , "description": "microscope" - , "aliases": [ - "microscope" - ] - , "tags": [ - "science" - , "laboratory" - , "investigate" - ] - } -, { - "emoji": "🔭" - , "description": "telescope" - , "aliases": [ - "telescope" - ] - , "tags": [ - ] - } -, { - "emoji": "📰" - , "description": "newspaper" - , "aliases": [ - "newspaper" - ] - , "tags": [ - "press" - ] - } -, { - "emoji": "🎨" - , "description": "artist palette" - , "aliases": [ - "art" - ] - , "tags": [ - "design" - , "paint" - ] - } -, { - "emoji": "🎬" - , "description": "clapper board" - , "aliases": [ - "clapper" - ] - , "tags": [ - "film" - ] - } -, { - "emoji": "🎤" - , "description": "microphone" - , "aliases": [ - "microphone" - ] - , "tags": [ - "sing" - ] - } -, { - "emoji": "🎧" - , "description": "headphone" - , "aliases": [ - "headphones" - ] - , "tags": [ - "music" - , "earphones" - ] - } -, { - "emoji": "🎼" - , "description": "musical score" - , "aliases": [ - "musical_score" - ] - , "tags": [ - ] - } -, { - "emoji": "🎵" - , "description": "musical note" - , "aliases": [ - "musical_note" - ] - , "tags": [ - ] - } -, { - "emoji": "🎶" - , "description": "multiple musical notes" - , "aliases": [ - "notes" - ] - , "tags": [ - "music" - ] - } -, { - "emoji": "🎹" - , "description": "musical keyboard" - , "aliases": [ - "musical_keyboard" - ] - , "tags": [ - "piano" - ] - } -, { - "emoji": "🎻" - , "description": "violin" - , "aliases": [ - "violin" - ] - , "tags": [ - ] - } -, { - "emoji": "🎺" - , "description": "trumpet" - , "aliases": [ - "trumpet" - ] - , "tags": [ - ] - } -, { - "emoji": "🎷" - , "description": "saxophone" - , "aliases": [ - "saxophone" - ] - , "tags": [ - ] - } -, { - "emoji": "🎸" - , "description": "guitar" - , "aliases": [ - "guitar" - ] - , "tags": [ - "rock" - ] - } -, { - "emoji": "👾" - , "description": "alien monster" - , "aliases": [ - "space_invader" - ] - , "tags": [ - "game" - , "retro" - ] - } -, { - "emoji": "🎮" - , "description": "video game" - , "aliases": [ - "video_game" - ] - , "tags": [ - "play" - , "controller" - , "console" - ] - } -, { - "emoji": "🃏" - , "description": "playing card black joker" - , "aliases": [ - "black_joker" - ] - , "tags": [ - ] - } -, { - "emoji": "🎴" - , "description": "flower playing cards" - , "aliases": [ - "flower_playing_cards" - ] - , "tags": [ - ] - } -, { - "emoji": "🀄" - , "description": "mahjong tile red dragon" - , "aliases": [ - "mahjong" - ] - , "tags": [ - ] - } -, { - "emoji": "🎲" - , "description": "game die" - , "aliases": [ - "game_die" - ] - , "tags": [ - "dice" - , "gambling" - ] - } -, { - "emoji": "🎯" - , "description": "direct hit" - , "aliases": [ - "dart" - ] - , "tags": [ - "target" - ] - } -, { - "emoji": "🏈" - , "description": "american football" - , "aliases": [ - "football" - ] - , "tags": [ - "sports" - ] - } -, { - "emoji": "🏀" - , "description": "basketball and hoop" - , "aliases": [ - "basketball" - ] - , "tags": [ - "sports" - ] - } -, { - "emoji": "⚽" - , "description": "soccer ball" - , "aliases": [ - "soccer" - ] - , "tags": [ - "sports" - ] - } -, { - "emoji": "⚾️" - , "description": "baseball" - , "aliases": [ - "baseball" - ] - , "tags": [ - "sports" - ] - } -, { - "emoji": "🎾" - , "description": "tennis racquet and ball" - , "aliases": [ - "tennis" - ] - , "tags": [ - "sports" - ] - } -, { - "emoji": "🎱" - , "description": "billiards" - , "aliases": [ - "8ball" - ] - , "tags": [ - "pool" - , "billiards" - ] - } -, { - "emoji": "🏉" - , "description": "rugby football" - , "aliases": [ - "rugby_football" - ] - , "tags": [ - ] - } -, { - "emoji": "🎳" - , "description": "bowling" - , "aliases": [ - "bowling" - ] - , "tags": [ - ] - } -, { - "emoji": "⛳" - , "description": "flag in hole" - , "aliases": [ - "golf" - ] - , "tags": [ - ] - } -, { - "emoji": "🚵" - , "description": "mountain bicyclist" - , "aliases": [ - "mountain_bicyclist" - ] - , "tags": [ - ] - } -, { - "emoji": "🚴" - , "description": "bicyclist" - , "aliases": [ - "bicyclist" - ] - , "tags": [ - ] - } -, { - "emoji": "🏁" - , "description": "chequered flag" - , "aliases": [ - "checkered_flag" - ] - , "tags": [ - "milestone" - , "finish" - ] - } -, { - "emoji": "🏇" - , "description": "horse racing" - , "aliases": [ - "horse_racing" - ] - , "tags": [ - ] - } -, { - "emoji": "🏆" - , "description": "trophy" - , "aliases": [ - "trophy" - ] - , "tags": [ - "award" - , "contest" - , "winner" - ] - } -, { - "emoji": "🎿" - , "description": "ski and ski boot" - , "aliases": [ - "ski" - ] - , "tags": [ - ] - } -, { - "emoji": "🏂" - , "description": "snowboarder" - , "aliases": [ - "snowboarder" - ] - , "tags": [ - ] - } -, { - "emoji": "🏊" - , "description": "swimmer" - , "aliases": [ - "swimmer" - ] - , "tags": [ - ] - } -, { - "emoji": "🏄" - , "description": "surfer" - , "aliases": [ - "surfer" - ] - , "tags": [ - ] - } -, { - "emoji": "🎣" - , "description": "fishing pole and fish" - , "aliases": [ - "fishing_pole_and_fish" - ] - , "tags": [ - ] - } -, { - "emoji": "☕" - , "description": "hot beverage" - , "aliases": [ - "coffee" - ] - , "tags": [ - "cafe" - , "espresso" - ] - } -, { - "emoji": "🍵" - , "description": "teacup without handle" - , "aliases": [ - "tea" - ] - , "tags": [ - "green" - , "breakfast" - ] - } -, { - "emoji": "🍶" - , "description": "sake bottle and cup" - , "aliases": [ - "sake" - ] - , "tags": [ - ] - } -, { - "emoji": "🍼" - , "description": "baby bottle" - , "aliases": [ - "baby_bottle" - ] - , "tags": [ - "milk" - ] - } -, { - "emoji": "🍺" - , "description": "beer mug" - , "aliases": [ - "beer" - ] - , "tags": [ - "drink" - ] - } -, { - "emoji": "🍻" - , "description": "clinking beer mugs" - , "aliases": [ - "beers" - ] - , "tags": [ - "drinks" - ] - } -, { - "emoji": "🍸" - , "description": "cocktail glass" - , "aliases": [ - "cocktail" - ] - , "tags": [ - "drink" - ] - } -, { - "emoji": "🍹" - , "description": "tropical drink" - , "aliases": [ - "tropical_drink" - ] - , "tags": [ - "summer" - , "vacation" - ] - } -, { - "emoji": "🍷" - , "description": "wine glass" - , "aliases": [ - "wine_glass" - ] - , "tags": [ - ] - } -, { - "emoji": "🍴" - , "description": "fork and knife" - , "aliases": [ - "fork_and_knife" - ] - , "tags": [ - "cutlery" - ] - } -, { - "emoji": "🍕" - , "description": "slice of pizza" - , "aliases": [ - "pizza" - ] - , "tags": [ - ] - } -, { - "emoji": "🍔" - , "description": "hamburger" - , "aliases": [ - "hamburger" - ] - , "tags": [ - "burger" - ] - } -, { - "emoji": "🍟" - , "description": "french fries" - , "aliases": [ - "fries" - ] - , "tags": [ - ] - } -, { - "emoji": "🍗" - , "description": "poultry leg" - , "aliases": [ - "poultry_leg" - ] - , "tags": [ - "meat" - , "chicken" - ] - } -, { - "emoji": "🍖" - , "description": "meat on bone" - , "aliases": [ - "meat_on_bone" - ] - , "tags": [ - ] - } -, { - "emoji": "🍝" - , "description": "spaghetti" - , "aliases": [ - "spaghetti" - ] - , "tags": [ - "pasta" - ] - } -, { - "emoji": "🍛" - , "description": "curry and rice" - , "aliases": [ - "curry" - ] - , "tags": [ - ] - } -, { - "emoji": "🍤" - , "description": "fried shrimp" - , "aliases": [ - "fried_shrimp" - ] - , "tags": [ - "tempura" - ] - } -, { - "emoji": "🍱" - , "description": "bento box" - , "aliases": [ - "bento" - ] - , "tags": [ - ] - } -, { - "emoji": "🍣" - , "description": "sushi" - , "aliases": [ - "sushi" - ] - , "tags": [ - ] - } -, { - "emoji": "🍥" - , "description": "fish cake with swirl design" - , "aliases": [ - "fish_cake" - ] - , "tags": [ - ] - } -, { - "emoji": "🍙" - , "description": "rice ball" - , "aliases": [ - "rice_ball" - ] - , "tags": [ - ] - } -, { - "emoji": "🍘" - , "description": "rice cracker" - , "aliases": [ - "rice_cracker" - ] - , "tags": [ - ] - } -, { - "emoji": "🍚" - , "description": "cooked rice" - , "aliases": [ - "rice" - ] - , "tags": [ - ] - } -, { - "emoji": "🍜" - , "description": "steaming bowl" - , "aliases": [ - "ramen" - ] - , "tags": [ - "noodle" - ] - } -, { - "emoji": "🍲" - , "description": "pot of food" - , "aliases": [ - "stew" - ] - , "tags": [ - ] - } -, { - "emoji": "🍢" - , "description": "oden" - , "aliases": [ - "oden" - ] - , "tags": [ - ] - } -, { - "emoji": "🍡" - , "description": "dango" - , "aliases": [ - "dango" - ] - , "tags": [ - ] - } -, { - "emoji": "🍳" - , "description": "cooking" - , "aliases": [ - "egg" - ] - , "tags": [ - "breakfast" - ] - } -, { - "emoji": "🍞" - , "description": "bread" - , "aliases": [ - "bread" - ] - , "tags": [ - "toast" - ] - } -, { - "emoji": "🍩" - , "description": "doughnut" - , "aliases": [ - "doughnut" - ] - , "tags": [ - ] - } -, { - "emoji": "🍮" - , "description": "custard" - , "aliases": [ - "custard" - ] - , "tags": [ - ] - } -, { - "emoji": "🍦" - , "description": "soft ice cream" - , "aliases": [ - "icecream" - ] - , "tags": [ - ] - } -, { - "emoji": "🍨" - , "description": "ice cream" - , "aliases": [ - "ice_cream" - ] - , "tags": [ - ] - } -, { - "emoji": "🍧" - , "description": "shaved ice" - , "aliases": [ - "shaved_ice" - ] - , "tags": [ - ] - } -, { - "emoji": "🎂" - , "description": "birthday cake" - , "aliases": [ - "birthday" - ] - , "tags": [ - "party" - ] - } -, { - "emoji": "🍰" - , "description": "shortcake" - , "aliases": [ - "cake" - ] - , "tags": [ - "dessert" - ] - } -, { - "emoji": "🍪" - , "description": "cookie" - , "aliases": [ - "cookie" - ] - , "tags": [ - ] - } -, { - "emoji": "🍫" - , "description": "chocolate bar" - , "aliases": [ - "chocolate_bar" - ] - , "tags": [ - ] - } -, { - "emoji": "🍬" - , "description": "candy" - , "aliases": [ - "candy" - ] - , "tags": [ - "sweet" - ] - } -, { - "emoji": "🍭" - , "description": "lollipop" - , "aliases": [ - "lollipop" - ] - , "tags": [ - ] - } -, { - "emoji": "🍯" - , "description": "honey pot" - , "aliases": [ - "honey_pot" - ] - , "tags": [ - ] - } -, { - "emoji": "🍎" - , "description": "red apple" - , "aliases": [ - "apple" - ] - , "tags": [ - ] - } -, { - "emoji": "🍏" - , "description": "green apple" - , "aliases": [ - "green_apple" - ] - , "tags": [ - "fruit" - ] - } -, { - "emoji": "🍊" - , "description": "tangerine" - , "aliases": [ - "tangerine" - , "orange" - , "mandarin" - ] - , "tags": [ - ] - } -, { - "emoji": "🍋" - , "description": "lemon" - , "aliases": [ - "lemon" - ] - , "tags": [ - ] - } -, { - "emoji": "🍒" - , "description": "cherries" - , "aliases": [ - "cherries" - ] - , "tags": [ - "fruit" - ] - } -, { - "emoji": "🍇" - , "description": "grapes" - , "aliases": [ - "grapes" - ] - , "tags": [ - ] - } -, { - "emoji": "🍉" - , "description": "watermelon" - , "aliases": [ - "watermelon" - ] - , "tags": [ - ] - } -, { - "emoji": "🍓" - , "description": "strawberry" - , "aliases": [ - "strawberry" - ] - , "tags": [ - "fruit" - ] - } -, { - "emoji": "🍑" - , "description": "peach" - , "aliases": [ - "peach" - ] - , "tags": [ - ] - } -, { - "emoji": "🍈" - , "description": "melon" - , "aliases": [ - "melon" - ] - , "tags": [ - ] - } -, { - "emoji": "🍌" - , "description": "banana" - , "aliases": [ - "banana" - ] - , "tags": [ - "fruit" - ] - } -, { - "emoji": "🍐" - , "description": "pear" - , "aliases": [ - "pear" - ] - , "tags": [ - ] - } -, { - "emoji": "🍍" - , "description": "pineapple" - , "aliases": [ - "pineapple" - ] - , "tags": [ - ] - } -, { - "emoji": "🍠" - , "description": "roasted sweet potato" - , "aliases": [ - "sweet_potato" - ] - , "tags": [ - ] - } -, { - "emoji": "🍆" - , "description": "aubergine" - , "aliases": [ - "eggplant" - ] - , "tags": [ - "aubergine" - ] - } -, { - "emoji": "🍅" - , "description": "tomato" - , "aliases": [ - "tomato" - ] - , "tags": [ - ] - } -, { - "emoji": "🌽" - , "description": "ear of maize" - , "aliases": [ - "corn" - ] - , "tags": [ - ] - } -, { - "emoji": "🏠" - , "description": "house building" - , "aliases": [ - "house" - ] - , "tags": [ - ] - } -, { - "emoji": "🏡" - , "description": "house with garden" - , "aliases": [ - "house_with_garden" - ] - , "tags": [ - ] - } -, { - "emoji": "🏫" - , "description": "school" - , "aliases": [ - "school" - ] - , "tags": [ - ] - } -, { - "emoji": "🏢" - , "description": "office building" - , "aliases": [ - "office" - ] - , "tags": [ - ] - } -, { - "emoji": "🏣" - , "description": "japanese post office" - , "aliases": [ - "post_office" - ] - , "tags": [ - ] - } -, { - "emoji": "🏥" - , "description": "hospital" - , "aliases": [ - "hospital" - ] - , "tags": [ - ] - } -, { - "emoji": "🏦" - , "description": "bank" - , "aliases": [ - "bank" - ] - , "tags": [ - ] - } -, { - "emoji": "🏪" - , "description": "convenience store" - , "aliases": [ - "convenience_store" - ] - , "tags": [ - ] - } -, { - "emoji": "🏩" - , "description": "love hotel" - , "aliases": [ - "love_hotel" - ] - , "tags": [ - ] - } -, { - "emoji": "🏨" - , "description": "hotel" - , "aliases": [ - "hotel" - ] - , "tags": [ - ] - } -, { - "emoji": "💒" - , "description": "wedding" - , "aliases": [ - "wedding" - ] - , "tags": [ - "marriage" - ] - } -, { - "emoji": "⛪" - , "description": "church" - , "aliases": [ - "church" - ] - , "tags": [ - ] - } -, { - "emoji": "🏬" - , "description": "department store" - , "aliases": [ - "department_store" - ] - , "tags": [ - ] - } -, { - "emoji": "🏤" - , "description": "european post office" - , "aliases": [ - "european_post_office" - ] - , "tags": [ - ] - } -, { - "emoji": "🌇" - , "description": "sunset over buildings" - , "aliases": [ - "city_sunrise" - ] - , "tags": [ - ] - } -, { - "emoji": "🌆" - , "description": "cityscape at dusk" - , "aliases": [ - "city_sunset" - ] - , "tags": [ - ] - } -, { - "emoji": "🏯" - , "description": "japanese castle" - , "aliases": [ - "japanese_castle" - ] - , "tags": [ - ] - } -, { - "emoji": "🏰" - , "description": "european castle" - , "aliases": [ - "european_castle" - ] - , "tags": [ - ] - } -, { - "emoji": "⛺" - , "description": "tent" - , "aliases": [ - "tent" - ] - , "tags": [ - "camping" - ] - } -, { - "emoji": "🏭" - , "description": "factory" - , "aliases": [ - "factory" - ] - , "tags": [ - ] - } -, { - "emoji": "🗼" - , "description": "tokyo tower" - , "aliases": [ - "tokyo_tower" - ] - , "tags": [ - ] - } -, { - "emoji": "🗾" - , "description": "silhouette of japan" - , "aliases": [ - "japan" - ] - , "tags": [ - ] - } -, { - "emoji": "🗻" - , "description": "mount fuji" - , "aliases": [ - "mount_fuji" - ] - , "tags": [ - ] - } -, { - "emoji": "🌄" - , "description": "sunrise over mountains" - , "aliases": [ - "sunrise_over_mountains" - ] - , "tags": [ - ] - } -, { - "emoji": "🌅" - , "description": "sunrise" - , "aliases": [ - "sunrise" - ] - , "tags": [ - ] - } -, { - "emoji": "🌃" - , "description": "night with stars" - , "aliases": [ - "night_with_stars" - ] - , "tags": [ - ] - } -, { - "emoji": "🗽" - , "description": "statue of liberty" - , "aliases": [ - "statue_of_liberty" - ] - , "tags": [ - ] - } -, { - "emoji": "🌉" - , "description": "bridge at night" - , "aliases": [ - "bridge_at_night" - ] - , "tags": [ - ] - } -, { - "emoji": "🎠" - , "description": "carousel horse" - , "aliases": [ - "carousel_horse" - ] - , "tags": [ - ] - } -, { - "emoji": "🎡" - , "description": "ferris wheel" - , "aliases": [ - "ferris_wheel" - ] - , "tags": [ - ] - } -, { - "emoji": "⛲" - , "description": "fountain" - , "aliases": [ - "fountain" - ] - , "tags": [ - ] - } -, { - "emoji": "🎢" - , "description": "roller coaster" - , "aliases": [ - "roller_coaster" - ] - , "tags": [ - ] - } -, { - "emoji": "🚢" - , "description": "ship" - , "aliases": [ - "ship" - ] - , "tags": [ - ] - } -, { - "emoji": "⛵" - , "description": "sailboat" - , "aliases": [ - "boat" - , "sailboat" - ] - , "tags": [ - ] - } -, { - "emoji": "🚤" - , "description": "speedboat" - , "aliases": [ - "speedboat" - ] - , "tags": [ - "ship" - ] - } -, { - "emoji": "🚣" - , "description": "rowboat" - , "aliases": [ - "rowboat" - ] - , "tags": [ - ] - } -, { - "emoji": "⚓" - , "description": "anchor" - , "aliases": [ - "anchor" - ] - , "tags": [ - "ship" - ] - } -, { - "emoji": "🚀" - , "description": "rocket" - , "aliases": [ - "rocket" - ] - , "tags": [ - "ship" - , "launch" - ] - } -, { - "emoji": "✈️" - , "description": "airplane" - , "aliases": [ - "airplane" - ] - , "tags": [ - "flight" - ] - } -, { - "emoji": "💺" - , "description": "seat" - , "aliases": [ - "seat" - ] - , "tags": [ - ] - } -, { - "emoji": "🚁" - , "description": "helicopter" - , "aliases": [ - "helicopter" - ] - , "tags": [ - ] - } -, { - "emoji": "🚂" - , "description": "steam locomotive" - , "aliases": [ - "steam_locomotive" - ] - , "tags": [ - "train" - ] - } -, { - "emoji": "🚊" - , "description": "tram" - , "aliases": [ - "tram" - ] - , "tags": [ - ] - } -, { - "emoji": "🚉" - , "description": "station" - , "aliases": [ - "station" - ] - , "tags": [ - ] - } -, { - "emoji": "🚞" - , "description": "mountain railway" - , "aliases": [ - "mountain_railway" - ] - , "tags": [ - ] - } -, { - "emoji": "🚆" - , "description": "train" - , "aliases": [ - "train2" - ] - , "tags": [ - ] - } -, { - "emoji": "🚄" - , "description": "high-speed train" - , "aliases": [ - "bullettrain_side" - ] - , "tags": [ - "train" - ] - } -, { - "emoji": "🚅" - , "description": "high-speed train with bullet nose" - , "aliases": [ - "bullettrain_front" - ] - , "tags": [ - "train" - ] - } -, { - "emoji": "🚈" - , "description": "light rail" - , "aliases": [ - "light_rail" - ] - , "tags": [ - ] - } -, { - "emoji": "🚇" - , "description": "metro" - , "aliases": [ - "metro" - ] - , "tags": [ - ] - } -, { - "emoji": "🚝" - , "description": "monorail" - , "aliases": [ - "monorail" - ] - , "tags": [ - ] - } -, { - "emoji": "🚋" - , "description": "tram car" - , "aliases": [ - "train" - ] - , "tags": [ - ] - } -, { - "emoji": "🚃" - , "description": "railway car" - , "aliases": [ - "railway_car" - ] - , "tags": [ - ] - } -, { - "emoji": "🚎" - , "description": "trolleybus" - , "aliases": [ - "trolleybus" - ] - , "tags": [ - ] - } -, { - "emoji": "🚌" - , "description": "bus" - , "aliases": [ - "bus" - ] - , "tags": [ - ] - } -, { - "emoji": "🚍" - , "description": "oncoming bus" - , "aliases": [ - "oncoming_bus" - ] - , "tags": [ - ] - } -, { - "emoji": "🚙" - , "description": "recreational vehicle" - , "aliases": [ - "blue_car" - ] - , "tags": [ - ] - } -, { - "emoji": "🚘" - , "description": "oncoming automobile" - , "aliases": [ - "oncoming_automobile" - ] - , "tags": [ - ] - } -, { - "emoji": "🚗" - , "description": "automobile" - , "aliases": [ - "car" - , "red_car" - ] - , "tags": [ - ] - } -, { - "emoji": "🚕" - , "description": "taxi" - , "aliases": [ - "taxi" - ] - , "tags": [ - ] - } -, { - "emoji": "🚖" - , "description": "oncoming taxi" - , "aliases": [ - "oncoming_taxi" - ] - , "tags": [ - ] - } -, { - "emoji": "🚛" - , "description": "articulated lorry" - , "aliases": [ - "articulated_lorry" - ] - , "tags": [ - ] - } -, { - "emoji": "🚚" - , "description": "delivery truck" - , "aliases": [ - "truck" - ] - , "tags": [ - ] - } -, { - "emoji": "🚨" - , "description": "police cars revolving light" - , "aliases": [ - "rotating_light" - ] - , "tags": [ - "911" - , "emergency" - ] - } -, { - "emoji": "🚓" - , "description": "police car" - , "aliases": [ - "police_car" - ] - , "tags": [ - ] - } -, { - "emoji": "🚔" - , "description": "oncoming police car" - , "aliases": [ - "oncoming_police_car" - ] - , "tags": [ - ] - } -, { - "emoji": "🚒" - , "description": "fire engine" - , "aliases": [ - "fire_engine" - ] - , "tags": [ - ] - } -, { - "emoji": "🚑" - , "description": "ambulance" - , "aliases": [ - "ambulance" - ] - , "tags": [ - ] - } -, { - "emoji": "🚐" - , "description": "minibus" - , "aliases": [ - "minibus" - ] - , "tags": [ - ] - } -, { - "emoji": "🚲" - , "description": "bicycle" - , "aliases": [ - "bike" - ] - , "tags": [ - "bicycle" - ] - } -, { - "emoji": "🚡" - , "description": "aerial tramway" - , "aliases": [ - "aerial_tramway" - ] - , "tags": [ - ] - } -, { - "emoji": "🚟" - , "description": "suspension railway" - , "aliases": [ - "suspension_railway" - ] - , "tags": [ - ] - } -, { - "emoji": "🚠" - , "description": "mountain cableway" - , "aliases": [ - "mountain_cableway" - ] - , "tags": [ - ] - } -, { - "emoji": "🚜" - , "description": "tractor" - , "aliases": [ - "tractor" - ] - , "tags": [ - ] - } -, { - "emoji": "💈" - , "description": "barber pole" - , "aliases": [ - "barber" - ] - , "tags": [ - ] - } -, { - "emoji": "🚏" - , "description": "bus stop" - , "aliases": [ - "busstop" - ] - , "tags": [ - ] - } -, { - "emoji": "🎫" - , "description": "ticket" - , "aliases": [ - "ticket" - ] - , "tags": [ - ] - } -, { - "emoji": "🚦" - , "description": "vertical traffic light" - , "aliases": [ - "vertical_traffic_light" - ] - , "tags": [ - "semaphore" - ] - } -, { - "emoji": "🚥" - , "description": "horizontal traffic light" - , "aliases": [ - "traffic_light" - ] - , "tags": [ - ] - } -, { - "emoji": "⚠️" - , "description": "warning sign" - , "aliases": [ - "warning" - ] - , "tags": [ - "wip" - ] - } -, { - "emoji": "🚧" - , "description": "construction sign" - , "aliases": [ - "construction" - ] - , "tags": [ - "wip" - ] - } -, { - "emoji": "🔰" - , "description": "japanese symbol for beginner" - , "aliases": [ - "beginner" - ] - , "tags": [ - ] - } -, { - "emoji": "⛽" - , "description": "fuel pump" - , "aliases": [ - "fuelpump" - ] - , "tags": [ - ] - } -, { - "emoji": "🏮" - , "description": "izakaya lantern" - , "aliases": [ - "izakaya_lantern" - , "lantern" - ] - , "tags": [ - ] - } -, { - "emoji": "🎰" - , "description": "slot machine" - , "aliases": [ - "slot_machine" - ] - , "tags": [ - ] - } -, { - "emoji": "♨️" - , "description": "hot springs" - , "aliases": [ - "hotsprings" - ] - , "tags": [ - ] - } -, { - "emoji": "🗿" - , "description": "moyai" - , "aliases": [ - "moyai" - ] - , "tags": [ - "stone" - ] - } -, { - "emoji": "🎪" - , "description": "circus tent" - , "aliases": [ - "circus_tent" - ] - , "tags": [ - ] - } -, { - "emoji": "🎭" - , "description": "performing arts" - , "aliases": [ - "performing_arts" - ] - , "tags": [ - "theater" - , "drama" - ] - } -, { - "emoji": "📍" - , "description": "round pushpin" - , "aliases": [ - "round_pushpin" - ] - , "tags": [ - "location" - ] - } -, { - "emoji": "🚩" - , "description": "triangular flag on post" - , "aliases": [ - "triangular_flag_on_post" - ] - , "tags": [ - ] - } -, { - "emoji": "🇯🇵" - , "description": "regional indicator symbol letter j + regional indicator symbol letter p" - , "aliases": [ - "jp" - ] - , "tags": [ - "japan" - ] - } -, { - "emoji": "🇰🇷" - , "description": "regional indicator symbol letter k + regional indicator symbol letter r" - , "aliases": [ - "kr" - ] - , "tags": [ - "korea" - ] - } -, { - "emoji": "🇩🇪" - , "description": "regional indicator symbol letter d + regional indicator symbol letter e" - , "aliases": [ - "de" - ] - , "tags": [ - "flag" - , "germany" - ] - } -, { - "emoji": "🇨🇳" - , "description": "regional indicator symbol letter c + regional indicator symbol letter n" - , "aliases": [ - "cn" - ] - , "tags": [ - "china" - ] - } -, { - "emoji": "🇺🇸" - , "description": "regional indicator symbol letter u + regional indicator symbol letter s" - , "aliases": [ - "us" - ] - , "tags": [ - "flag" - , "united" - , "america" - ] - } -, { - "emoji": "🇫🇷" - , "description": "regional indicator symbol letter f + regional indicator symbol letter r" - , "aliases": [ - "fr" - ] - , "tags": [ - "france" - , "french" - ] - } -, { - "emoji": "🇪🇸" - , "description": "regional indicator symbol letter e + regional indicator symbol letter s" - , "aliases": [ - "es" - ] - , "tags": [ - "spain" - ] - } -, { - "emoji": "🇮🇹" - , "description": "regional indicator symbol letter i + regional indicator symbol letter t" - , "aliases": [ - "it" - ] - , "tags": [ - "italy" - ] - } -, { - "emoji": "🇷🇺" - , "description": "regional indicator symbol letter r + regional indicator symbol letter u" - , "aliases": [ - "ru" - ] - , "tags": [ - "russia" - ] - } -, { - "emoji": "🇬🇧" - , "description": "regional indicator symbol letter g + regional indicator symbol letter b" - , "aliases": [ - "gb" - , "uk" - ] - , "tags": [ - "flag" - , "british" - ] - } -, { - "emoji": "1️⃣" - , "description": "digit one + combining enclosing keycap" - , "aliases": [ - "one" - ] - , "tags": [ - ] - } -, { - "emoji": "2️⃣" - , "description": "digit two + combining enclosing keycap" - , "aliases": [ - "two" - ] - , "tags": [ - ] - } -, { - "emoji": "3️⃣" - , "description": "digit three + combining enclosing keycap" - , "aliases": [ - "three" - ] - , "tags": [ - ] - } -, { - "emoji": "4️⃣" - , "description": "digit four + combining enclosing keycap" - , "aliases": [ - "four" - ] - , "tags": [ - ] - } -, { - "emoji": "5️⃣" - , "description": "digit five + combining enclosing keycap" - , "aliases": [ - "five" - ] - , "tags": [ - ] - } -, { - "emoji": "6️⃣" - , "description": "digit six + combining enclosing keycap" - , "aliases": [ - "six" - ] - , "tags": [ - ] - } -, { - "emoji": "7️⃣" - , "description": "digit seven + combining enclosing keycap" - , "aliases": [ - "seven" - ] - , "tags": [ - ] - } -, { - "emoji": "8️⃣" - , "description": "digit eight + combining enclosing keycap" - , "aliases": [ - "eight" - ] - , "tags": [ - ] - } -, { - "emoji": "9️⃣" - , "description": "digit nine + combining enclosing keycap" - , "aliases": [ - "nine" - ] - , "tags": [ - ] - } -, { - "emoji": "0️⃣" - , "description": "digit zero + combining enclosing keycap" - , "aliases": [ - "zero" - ] - , "tags": [ - ] - } -, { - "emoji": "🔟" - , "description": "keycap ten" - , "aliases": [ - "keycap_ten" - ] - , "tags": [ - ] - } -, { - "emoji": "🔢" - , "description": "input symbol for numbers" - , "aliases": [ - "1234" - ] - , "tags": [ - "numbers" - ] - } -, { - "emoji": "#️⃣" - , "description": "number sign + combining enclosing keycap" - , "aliases": [ - "hash" - ] - , "tags": [ - "number" - ] - } -, { - "emoji": "🔣" - , "description": "input symbol for symbols" - , "aliases": [ - "symbols" - ] - , "tags": [ - ] - } -, { - "emoji": "⬆️" - , "description": "upwards black arrow" - , "aliases": [ - "arrow_up" - ] - , "tags": [ - ] - } -, { - "emoji": "⬇️" - , "description": "downwards black arrow" - , "aliases": [ - "arrow_down" - ] - , "tags": [ - ] - } -, { - "emoji": "⬅️" - , "description": "leftwards black arrow" - , "aliases": [ - "arrow_left" - ] - , "tags": [ - ] - } -, { - "emoji": "➡️" - , "description": "black rightwards arrow" - , "aliases": [ - "arrow_right" - ] - , "tags": [ - ] - } -, { - "emoji": "🔠" - , "description": "input symbol for latin capital letters" - , "aliases": [ - "capital_abcd" - ] - , "tags": [ - "letters" - ] - } -, { - "emoji": "🔡" - , "description": "input symbol for latin small letters" - , "aliases": [ - "abcd" - ] - , "tags": [ - ] - } -, { - "emoji": "🔤" - , "description": "input symbol for latin letters" - , "aliases": [ - "abc" - ] - , "tags": [ - "alphabet" - ] - } -, { - "emoji": "↗️" - , "description": "north east arrow" - , "aliases": [ - "arrow_upper_right" - ] - , "tags": [ - ] - } -, { - "emoji": "↖️" - , "description": "north west arrow" - , "aliases": [ - "arrow_upper_left" - ] - , "tags": [ - ] - } -, { - "emoji": "↘️" - , "description": "south east arrow" - , "aliases": [ - "arrow_lower_right" - ] - , "tags": [ - ] - } -, { - "emoji": "↙️" - , "description": "south west arrow" - , "aliases": [ - "arrow_lower_left" - ] - , "tags": [ - ] - } -, { - "emoji": "↔️" - , "description": "left right arrow" - , "aliases": [ - "left_right_arrow" - ] - , "tags": [ - ] - } -, { - "emoji": "↕️" - , "description": "up down arrow" - , "aliases": [ - "arrow_up_down" - ] - , "tags": [ - ] - } -, { - "emoji": "🔄" - , "description": "anticlockwise downwards and upwards open circle arrows" - , "aliases": [ - "arrows_counterclockwise" - ] - , "tags": [ - "sync" - ] - } -, { - "emoji": "◀️" - , "description": "black left-pointing triangle" - , "aliases": [ - "arrow_backward" - ] - , "tags": [ - ] - } -, { - "emoji": "▶️" - , "description": "black right-pointing triangle" - , "aliases": [ - "arrow_forward" - ] - , "tags": [ - ] - } -, { - "emoji": "🔼" - , "description": "up-pointing small red triangle" - , "aliases": [ - "arrow_up_small" - ] - , "tags": [ - ] - } -, { - "emoji": "🔽" - , "description": "down-pointing small red triangle" - , "aliases": [ - "arrow_down_small" - ] - , "tags": [ - ] - } -, { - "emoji": "↩️" - , "description": "leftwards arrow with hook" - , "aliases": [ - "leftwards_arrow_with_hook" - ] - , "tags": [ - "return" - ] - } -, { - "emoji": "↪️" - , "description": "rightwards arrow with hook" - , "aliases": [ - "arrow_right_hook" - ] - , "tags": [ - ] - } -, { - "emoji": "ℹ️" - , "description": "information source" - , "aliases": [ - "information_source" - ] - , "tags": [ - ] - } -, { - "emoji": "⏪" - , "description": "black left-pointing double triangle" - , "aliases": [ - "rewind" - ] - , "tags": [ - ] - } -, { - "emoji": "⏩" - , "description": "black right-pointing double triangle" - , "aliases": [ - "fast_forward" - ] - , "tags": [ - ] - } -, { - "emoji": "⏫" - , "description": "black up-pointing double triangle" - , "aliases": [ - "arrow_double_up" - ] - , "tags": [ - ] - } -, { - "emoji": "⏬" - , "description": "black down-pointing double triangle" - , "aliases": [ - "arrow_double_down" - ] - , "tags": [ - ] - } -, { - "emoji": "⤵️" - , "description": "arrow pointing rightwards then curving downwards" - , "aliases": [ - "arrow_heading_down" - ] - , "tags": [ - ] - } -, { - "emoji": "⤴️" - , "description": "arrow pointing rightwards then curving upwards" - , "aliases": [ - "arrow_heading_up" - ] - , "tags": [ - ] - } -, { - "emoji": "🆗" - , "description": "squared ok" - , "aliases": [ - "ok" - ] - , "tags": [ - "yes" - ] - } -, { - "emoji": "🔀" - , "description": "twisted rightwards arrows" - , "aliases": [ - "twisted_rightwards_arrows" - ] - , "tags": [ - "shuffle" - ] - } -, { - "emoji": "🔁" - , "description": "clockwise rightwards and leftwards open circle arrows" - , "aliases": [ - "repeat" - ] - , "tags": [ - "loop" - ] - } -, { - "emoji": "🔂" - , "description": "clockwise rightwards and leftwards open circle arrows with circled one overlay" - , "aliases": [ - "repeat_one" - ] - , "tags": [ - ] - } -, { - "emoji": "🆕" - , "description": "squared new" - , "aliases": [ - "new" - ] - , "tags": [ - "fresh" - ] - } -, { - "emoji": "🆙" - , "description": "squared up with exclamation mark" - , "aliases": [ - "up" - ] - , "tags": [ - ] - } -, { - "emoji": "🆒" - , "description": "squared cool" - , "aliases": [ - "cool" - ] - , "tags": [ - ] - } -, { - "emoji": "🆓" - , "description": "squared free" - , "aliases": [ - "free" - ] - , "tags": [ - ] - } -, { - "emoji": "🆖" - , "description": "squared ng" - , "aliases": [ - "ng" - ] - , "tags": [ - ] - } -, { - "emoji": "📶" - , "description": "antenna with bars" - , "aliases": [ - "signal_strength" - ] - , "tags": [ - "wifi" - ] - } -, { - "emoji": "🎦" - , "description": "cinema" - , "aliases": [ - "cinema" - ] - , "tags": [ - "film" - , "movie" - ] - } -, { - "emoji": "🈁" - , "description": "squared katakana koko" - , "aliases": [ - "koko" - ] - , "tags": [ - ] - } -, { - "emoji": "🈯" - , "description": "squared cjk unified ideograph-6307" - , "aliases": [ - "u6307" - ] - , "tags": [ - ] - } -, { - "emoji": "🈳" - , "description": "squared cjk unified ideograph-7a7a" - , "aliases": [ - "u7a7a" - ] - , "tags": [ - ] - } -, { - "emoji": "🈵" - , "description": "squared cjk unified ideograph-6e80" - , "aliases": [ - "u6e80" - ] - , "tags": [ - ] - } -, { - "emoji": "🈴" - , "description": "squared cjk unified ideograph-5408" - , "aliases": [ - "u5408" - ] - , "tags": [ - ] - } -, { - "emoji": "🈲" - , "description": "squared cjk unified ideograph-7981" - , "aliases": [ - "u7981" - ] - , "tags": [ - ] - } -, { - "emoji": "🉐" - , "description": "circled ideograph advantage" - , "aliases": [ - "ideograph_advantage" - ] - , "tags": [ - ] - } -, { - "emoji": "🈹" - , "description": "squared cjk unified ideograph-5272" - , "aliases": [ - "u5272" - ] - , "tags": [ - ] - } -, { - "emoji": "🈺" - , "description": "squared cjk unified ideograph-55b6" - , "aliases": [ - "u55b6" - ] - , "tags": [ - ] - } -, { - "emoji": "🈶" - , "description": "squared cjk unified ideograph-6709" - , "aliases": [ - "u6709" - ] - , "tags": [ - ] - } -, { - "emoji": "🈚" - , "description": "squared cjk unified ideograph-7121" - , "aliases": [ - "u7121" - ] - , "tags": [ - ] - } -, { - "emoji": "🚻" - , "description": "restroom" - , "aliases": [ - "restroom" - ] - , "tags": [ - "toilet" - ] - } -, { - "emoji": "🚹" - , "description": "mens symbol" - , "aliases": [ - "mens" - ] - , "tags": [ - ] - } -, { - "emoji": "🚺" - , "description": "womens symbol" - , "aliases": [ - "womens" - ] - , "tags": [ - ] - } -, { - "emoji": "🚼" - , "description": "baby symbol" - , "aliases": [ - "baby_symbol" - ] - , "tags": [ - ] - } -, { - "emoji": "🚾" - , "description": "water closet" - , "aliases": [ - "wc" - ] - , "tags": [ - "toilet" - , "restroom" - ] - } -, { - "emoji": "🚰" - , "description": "potable water symbol" - , "aliases": [ - "potable_water" - ] - , "tags": [ - ] - } -, { - "emoji": "🚮" - , "description": "put litter in its place symbol" - , "aliases": [ - "put_litter_in_its_place" - ] - , "tags": [ - ] - } -, { - "emoji": "🅿️" - , "description": "negative squared latin capital letter p" - , "aliases": [ - "parking" - ] - , "tags": [ - ] - } -, { - "emoji": "♿" - , "description": "wheelchair symbol" - , "aliases": [ - "wheelchair" - ] - , "tags": [ - "accessibility" - ] - } -, { - "emoji": "🚭" - , "description": "no smoking symbol" - , "aliases": [ - "no_smoking" - ] - , "tags": [ - ] - } -, { - "emoji": "🈷️" - , "description": "squared cjk unified ideograph-6708" - , "aliases": [ - "u6708" - ] - , "tags": [ - ] - } -, { - "emoji": "🈸" - , "description": "squared cjk unified ideograph-7533" - , "aliases": [ - "u7533" - ] - , "tags": [ - ] - } -, { - "emoji": "🈂️" - , "description": "squared katakana sa" - , "aliases": [ - "sa" - ] - , "tags": [ - ] - } -, { - "emoji": "Ⓜ️" - , "description": "circled latin capital letter m" - , "aliases": [ - "m" - ] - , "tags": [ - ] - } -, { - "emoji": "🛂" - , "description": "passport control" - , "aliases": [ - "passport_control" - ] - , "tags": [ - ] - } -, { - "emoji": "🛄" - , "description": "baggage claim" - , "aliases": [ - "baggage_claim" - ] - , "tags": [ - "airport" - ] - } -, { - "emoji": "🛅" - , "description": "left luggage" - , "aliases": [ - "left_luggage" - ] - , "tags": [ - ] - } -, { - "emoji": "🛃" - , "description": "customs" - , "aliases": [ - "customs" - ] - , "tags": [ - ] - } -, { - "emoji": "🉑" - , "description": "circled ideograph accept" - , "aliases": [ - "accept" - ] - , "tags": [ - ] - } -, { - "emoji": "㊙️" - , "description": "circled ideograph secret" - , "aliases": [ - "secret" - ] - , "tags": [ - ] - } -, { - "emoji": "㊗️" - , "description": "circled ideograph congratulation" - , "aliases": [ - "congratulations" - ] - , "tags": [ - ] - } -, { - "emoji": "🆑" - , "description": "squared cl" - , "aliases": [ - "cl" - ] - , "tags": [ - ] - } -, { - "emoji": "🆘" - , "description": "squared sos" - , "aliases": [ - "sos" - ] - , "tags": [ - "help" - , "emergency" - ] - } -, { - "emoji": "🆔" - , "description": "squared id" - , "aliases": [ - "id" - ] - , "tags": [ - ] - } -, { - "emoji": "🚫" - , "description": "no entry sign" - , "aliases": [ - "no_entry_sign" - ] - , "tags": [ - "block" - , "forbidden" - ] - } -, { - "emoji": "🔞" - , "description": "no one under eighteen symbol" - , "aliases": [ - "underage" - ] - , "tags": [ - ] - } -, { - "emoji": "📵" - , "description": "no mobile phones" - , "aliases": [ - "no_mobile_phones" - ] - , "tags": [ - ] - } -, { - "emoji": "🚯" - , "description": "do not litter symbol" - , "aliases": [ - "do_not_litter" - ] - , "tags": [ - ] - } -, { - "emoji": "🚱" - , "description": "non-potable water symbol" - , "aliases": [ - "non-potable_water" - ] - , "tags": [ - ] - } -, { - "emoji": "🚳" - , "description": "no bicycles" - , "aliases": [ - "no_bicycles" - ] - , "tags": [ - ] - } -, { - "emoji": "🚷" - , "description": "no pedestrians" - , "aliases": [ - "no_pedestrians" - ] - , "tags": [ - ] - } -, { - "emoji": "🚸" - , "description": "children crossing" - , "aliases": [ - "children_crossing" - ] - , "tags": [ - ] - } -, { - "emoji": "⛔" - , "description": "no entry" - , "aliases": [ - "no_entry" - ] - , "tags": [ - "limit" - ] - } -, { - "emoji": "✳️" - , "description": "eight spoked asterisk" - , "aliases": [ - "eight_spoked_asterisk" - ] - , "tags": [ - ] - } -, { - "emoji": "❇️" - , "description": "sparkle" - , "aliases": [ - "sparkle" - ] - , "tags": [ - ] - } -, { - "emoji": "❎" - , "description": "negative squared cross mark" - , "aliases": [ - "negative_squared_cross_mark" - ] - , "tags": [ - ] - } -, { - "emoji": "✅" - , "description": "white heavy check mark" - , "aliases": [ - "white_check_mark" - ] - , "tags": [ - ] - } -, { - "emoji": "✴️" - , "description": "eight pointed black star" - , "aliases": [ - "eight_pointed_black_star" - ] - , "tags": [ - ] - } -, { - "emoji": "💟" - , "description": "heart decoration" - , "aliases": [ - "heart_decoration" - ] - , "tags": [ - ] - } -, { - "emoji": "🆚" - , "description": "squared vs" - , "aliases": [ - "vs" - ] - , "tags": [ - ] - } -, { - "emoji": "📳" - , "description": "vibration mode" - , "aliases": [ - "vibration_mode" - ] - , "tags": [ - ] - } -, { - "emoji": "📴" - , "description": "mobile phone off" - , "aliases": [ - "mobile_phone_off" - ] - , "tags": [ - "mute" - , "off" - ] - } -, { - "emoji": "🅰️" - , "description": "negative squared latin capital letter a" - , "aliases": [ - "a" - ] - , "tags": [ - ] - } -, { - "emoji": "🅱️" - , "description": "negative squared latin capital letter b" - , "aliases": [ - "b" - ] - , "tags": [ - ] - } -, { - "emoji": "🆎" - , "description": "negative squared ab" - , "aliases": [ - "ab" - ] - , "tags": [ - ] - } -, { - "emoji": "🅾️" - , "description": "negative squared latin capital letter o" - , "aliases": [ - "o2" - ] - , "tags": [ - ] - } -, { - "emoji": "💠" - , "description": "diamond shape with a dot inside" - , "aliases": [ - "diamond_shape_with_a_dot_inside" - ] - , "tags": [ - ] - } -, { - "emoji": "➿" - , "description": "double curly loop" - , "aliases": [ - "loop" - ] - , "tags": [ - ] - } -, { - "emoji": "♻️" - , "description": "black universal recycling symbol" - , "aliases": [ - "recycle" - ] - , "tags": [ - "environment" - , "green" - ] - } -, { - "emoji": "♈" - , "description": "aries" - , "aliases": [ - "aries" - ] - , "tags": [ - ] - } -, { - "emoji": "♉" - , "description": "taurus" - , "aliases": [ - "taurus" - ] - , "tags": [ - ] - } -, { - "emoji": "♊" - , "description": "gemini" - , "aliases": [ - "gemini" - ] - , "tags": [ - ] - } -, { - "emoji": "♋" - , "description": "cancer" - , "aliases": [ - "cancer" - ] - , "tags": [ - ] - } -, { - "emoji": "♌" - , "description": "leo" - , "aliases": [ - "leo" - ] - , "tags": [ - ] - } -, { - "emoji": "♍" - , "description": "virgo" - , "aliases": [ - "virgo" - ] - , "tags": [ - ] - } -, { - "emoji": "♎" - , "description": "libra" - , "aliases": [ - "libra" - ] - , "tags": [ - ] - } -, { - "emoji": "♏" - , "description": "scorpius" - , "aliases": [ - "scorpius" - ] - , "tags": [ - ] - } -, { - "emoji": "♐" - , "description": "sagittarius" - , "aliases": [ - "sagittarius" - ] - , "tags": [ - ] - } -, { - "emoji": "♑" - , "description": "capricorn" - , "aliases": [ - "capricorn" - ] - , "tags": [ - ] - } -, { - "emoji": "♒" - , "description": "aquarius" - , "aliases": [ - "aquarius" - ] - , "tags": [ - ] - } -, { - "emoji": "♓" - , "description": "pisces" - , "aliases": [ - "pisces" - ] - , "tags": [ - ] - } -, { - "emoji": "⛎" - , "description": "ophiuchus" - , "aliases": [ - "ophiuchus" - ] - , "tags": [ - ] - } -, { - "emoji": "🔯" - , "description": "six pointed star with middle dot" - , "aliases": [ - "six_pointed_star" - ] - , "tags": [ - ] - } -, { - "emoji": "🏧" - , "description": "automated teller machine" - , "aliases": [ - "atm" - ] - , "tags": [ - ] - } -, { - "emoji": "💹" - , "description": "chart with upwards trend and yen sign" - , "aliases": [ - "chart" - ] - , "tags": [ - ] - } -, { - "emoji": "💲" - , "description": "heavy dollar sign" - , "aliases": [ - "heavy_dollar_sign" - ] - , "tags": [ - ] - } -, { - "emoji": "💱" - , "description": "currency exchange" - , "aliases": [ - "currency_exchange" - ] - , "tags": [ - ] - } -, { - "emoji": "©️" - , "description": "copyright sign" - , "aliases": [ - "copyright" - ] - , "tags": [ - ] - } -, { - "emoji": "®️" - , "description": "registered sign" - , "aliases": [ - "registered" - ] - , "tags": [ - ] - } -, { - "emoji": "™️" - , "description": "trade mark sign" - , "aliases": [ - "tm" - ] - , "tags": [ - "trademark" - ] - } -, { - "emoji": "❌" - , "description": "cross mark" - , "aliases": [ - "x" - ] - , "tags": [ - ] - } -, { - "emoji": "‼️" - , "description": "double exclamation mark" - , "aliases": [ - "bangbang" - ] - , "tags": [ - ] - } -, { - "emoji": "⁉️" - , "description": "exclamation question mark" - , "aliases": [ - "interrobang" - ] - , "tags": [ - ] - } -, { - "emoji": "❗" - , "description": "heavy exclamation mark symbol" - , "aliases": [ - "exclamation" - , "heavy_exclamation_mark" - ] - , "tags": [ - "bang" - ] - } -, { - "emoji": "❓" - , "description": "black question mark ornament" - , "aliases": [ - "question" - ] - , "tags": [ - "confused" - ] - } -, { - "emoji": "❕" - , "description": "white exclamation mark ornament" - , "aliases": [ - "grey_exclamation" - ] - , "tags": [ - ] - } -, { - "emoji": "❔" - , "description": "white question mark ornament" - , "aliases": [ - "grey_question" - ] - , "tags": [ - ] - } -, { - "emoji": "⭕" - , "description": "heavy large circle" - , "aliases": [ - "o" - ] - , "tags": [ - ] - } -, { - "emoji": "🔝" - , "description": "top with upwards arrow above" - , "aliases": [ - "top" - ] - , "tags": [ - ] - } -, { - "emoji": "🔚" - , "description": "end with leftwards arrow above" - , "aliases": [ - "end" - ] - , "tags": [ - ] - } -, { - "emoji": "🔙" - , "description": "back with leftwards arrow above" - , "aliases": [ - "back" - ] - , "tags": [ - ] - } -, { - "emoji": "🔛" - , "description": "on with exclamation mark with left right arrow above" - , "aliases": [ - "on" - ] - , "tags": [ - ] - } -, { - "emoji": "🔜" - , "description": "soon with rightwards arrow above" - , "aliases": [ - "soon" - ] - , "tags": [ - ] - } -, { - "emoji": "🔃" - , "description": "clockwise downwards and upwards open circle arrows" - , "aliases": [ - "arrows_clockwise" - ] - , "tags": [ - ] - } -, { - "emoji": "🕛" - , "description": "clock face twelve oclock" - , "aliases": [ - "clock12" - ] - , "tags": [ - ] - } -, { - "emoji": "🕧" - , "description": "clock face twelve-thirty" - , "aliases": [ - "clock1230" - ] - , "tags": [ - ] - } -, { - "emoji": "🕐" - , "description": "clock face one oclock" - , "aliases": [ - "clock1" - ] - , "tags": [ - ] - } -, { - "emoji": "🕜" - , "description": "clock face one-thirty" - , "aliases": [ - "clock130" - ] - , "tags": [ - ] - } -, { - "emoji": "🕑" - , "description": "clock face two oclock" - , "aliases": [ - "clock2" - ] - , "tags": [ - ] - } -, { - "emoji": "🕝" - , "description": "clock face two-thirty" - , "aliases": [ - "clock230" - ] - , "tags": [ - ] - } -, { - "emoji": "🕒" - , "description": "clock face three oclock" - , "aliases": [ - "clock3" - ] - , "tags": [ - ] - } -, { - "emoji": "🕞" - , "description": "clock face three-thirty" - , "aliases": [ - "clock330" - ] - , "tags": [ - ] - } -, { - "emoji": "🕓" - , "description": "clock face four oclock" - , "aliases": [ - "clock4" - ] - , "tags": [ - ] - } -, { - "emoji": "🕟" - , "description": "clock face four-thirty" - , "aliases": [ - "clock430" - ] - , "tags": [ - ] - } -, { - "emoji": "🕔" - , "description": "clock face five oclock" - , "aliases": [ - "clock5" - ] - , "tags": [ - ] - } -, { - "emoji": "🕠" - , "description": "clock face five-thirty" - , "aliases": [ - "clock530" - ] - , "tags": [ - ] - } -, { - "emoji": "🕕" - , "description": "clock face six oclock" - , "aliases": [ - "clock6" - ] - , "tags": [ - ] - } -, { - "emoji": "🕖" - , "description": "clock face seven oclock" - , "aliases": [ - "clock7" - ] - , "tags": [ - ] - } -, { - "emoji": "🕗" - , "description": "clock face eight oclock" - , "aliases": [ - "clock8" - ] - , "tags": [ - ] - } -, { - "emoji": "🕘" - , "description": "clock face nine oclock" - , "aliases": [ - "clock9" - ] - , "tags": [ - ] - } -, { - "emoji": "🕙" - , "description": "clock face ten oclock" - , "aliases": [ - "clock10" - ] - , "tags": [ - ] - } -, { - "emoji": "🕚" - , "description": "clock face eleven oclock" - , "aliases": [ - "clock11" - ] - , "tags": [ - ] - } -, { - "emoji": "🕡" - , "description": "clock face six-thirty" - , "aliases": [ - "clock630" - ] - , "tags": [ - ] - } -, { - "emoji": "🕢" - , "description": "clock face seven-thirty" - , "aliases": [ - "clock730" - ] - , "tags": [ - ] - } -, { - "emoji": "🕣" - , "description": "clock face eight-thirty" - , "aliases": [ - "clock830" - ] - , "tags": [ - ] - } -, { - "emoji": "🕤" - , "description": "clock face nine-thirty" - , "aliases": [ - "clock930" - ] - , "tags": [ - ] - } -, { - "emoji": "🕥" - , "description": "clock face ten-thirty" - , "aliases": [ - "clock1030" - ] - , "tags": [ - ] - } -, { - "emoji": "🕦" - , "description": "clock face eleven-thirty" - , "aliases": [ - "clock1130" - ] - , "tags": [ - ] - } -, { - "emoji": "✖️" - , "description": "heavy multiplication x" - , "aliases": [ - "heavy_multiplication_x" - ] - , "tags": [ - ] - } -, { - "emoji": "➕" - , "description": "heavy plus sign" - , "aliases": [ - "heavy_plus_sign" - ] - , "tags": [ - ] - } -, { - "emoji": "➖" - , "description": "heavy minus sign" - , "aliases": [ - "heavy_minus_sign" - ] - , "tags": [ - ] - } -, { - "emoji": "➗" - , "description": "heavy division sign" - , "aliases": [ - "heavy_division_sign" - ] - , "tags": [ - ] - } -, { - "emoji": "♠️" - , "description": "black spade suit" - , "aliases": [ - "spades" - ] - , "tags": [ - ] - } -, { - "emoji": "♥️" - , "description": "black heart suit" - , "aliases": [ - "hearts" - ] - , "tags": [ - ] - } -, { - "emoji": "♣️" - , "description": "black club suit" - , "aliases": [ - "clubs" - ] - , "tags": [ - ] - } -, { - "emoji": "♦️" - , "description": "black diamond suit" - , "aliases": [ - "diamonds" - ] - , "tags": [ - ] - } -, { - "emoji": "💮" - , "description": "white flower" - , "aliases": [ - "white_flower" - ] - , "tags": [ - ] - } -, { - "emoji": "💯" - , "description": "hundred points symbol" - , "aliases": [ - "100" - ] - , "tags": [ - "score" - , "perfect" - ] - } -, { - "emoji": "✔️" - , "description": "heavy check mark" - , "aliases": [ - "heavy_check_mark" - ] - , "tags": [ - ] - } -, { - "emoji": "☑️" - , "description": "ballot box with check" - , "aliases": [ - "ballot_box_with_check" - ] - , "tags": [ - ] - } -, { - "emoji": "🔘" - , "description": "radio button" - , "aliases": [ - "radio_button" - ] - , "tags": [ - ] - } -, { - "emoji": "🔗" - , "description": "link symbol" - , "aliases": [ - "link" - ] - , "tags": [ - ] - } -, { - "emoji": "➰" - , "description": "curly loop" - , "aliases": [ - "curly_loop" - ] - , "tags": [ - ] - } -, { - "emoji": "〰️" - , "description": "wavy dash" - , "aliases": [ - "wavy_dash" - ] - , "tags": [ - ] - } -, { - "emoji": "〽️" - , "description": "part alternation mark" - , "aliases": [ - "part_alternation_mark" - ] - , "tags": [ - ] - } -, { - "emoji": "🔱" - , "description": "trident emblem" - , "aliases": [ - "trident" - ] - , "tags": [ - ] - } -, { - "emoji": "◼️" - , "description": "black medium square" - , "aliases": [ - "black_medium_square" - ] - , "tags": [ - ] - } -, { - "emoji": "◻️" - , "description": "white medium square" - , "aliases": [ - "white_medium_square" - ] - , "tags": [ - ] - } -, { - "emoji": "◾" - , "description": "black medium small square" - , "aliases": [ - "black_medium_small_square" - ] - , "tags": [ - ] - } -, { - "emoji": "◽" - , "description": "white medium small square" - , "aliases": [ - "white_medium_small_square" - ] - , "tags": [ - ] - } -, { - "emoji": "▪️" - , "description": "black small square" - , "aliases": [ - "black_small_square" - ] - , "tags": [ - ] - } -, { - "emoji": "▫️" - , "description": "white small square" - , "aliases": [ - "white_small_square" - ] - , "tags": [ - ] - } -, { - "emoji": "🔺" - , "description": "up-pointing red triangle" - , "aliases": [ - "small_red_triangle" - ] - , "tags": [ - ] - } -, { - "emoji": "🔲" - , "description": "black square button" - , "aliases": [ - "black_square_button" - ] - , "tags": [ - ] - } -, { - "emoji": "🔳" - , "description": "white square button" - , "aliases": [ - "white_square_button" - ] - , "tags": [ - ] - } -, { - "emoji": "⚫" - , "description": "medium black circle" - , "aliases": [ - "black_circle" - ] - , "tags": [ - ] - } -, { - "emoji": "⚪" - , "description": "medium white circle" - , "aliases": [ - "white_circle" - ] - , "tags": [ - ] - } -, { - "emoji": "🔴" - , "description": "large red circle" - , "aliases": [ - "red_circle" - ] - , "tags": [ - ] - } -, { - "emoji": "🔵" - , "description": "large blue circle" - , "aliases": [ - "large_blue_circle" - ] - , "tags": [ - ] - } -, { - "emoji": "🔻" - , "description": "down-pointing red triangle" - , "aliases": [ - "small_red_triangle_down" - ] - , "tags": [ - ] - } -, { - "emoji": "⬜" - , "description": "white large square" - , "aliases": [ - "white_large_square" - ] - , "tags": [ - ] - } -, { - "emoji": "⬛" - , "description": "black large square" - , "aliases": [ - "black_large_square" - ] - , "tags": [ - ] - } -, { - "emoji": "🔶" - , "description": "large orange diamond" - , "aliases": [ - "large_orange_diamond" - ] - , "tags": [ - ] - } -, { - "emoji": "🔷" - , "description": "large blue diamond" - , "aliases": [ - "large_blue_diamond" - ] - , "tags": [ - ] - } -, { - "emoji": "🔸" - , "description": "small orange diamond" - , "aliases": [ - "small_orange_diamond" - ] - , "tags": [ - ] - } -, { - "emoji": "🔹" - , "description": "small blue diamond" - , "aliases": [ - "small_blue_diamond" - ] - , "tags": [ - ] - } -, { - "emoji": "🇨🇦" - , "description": "regional indicator symbol letter c + regional indicator symbol letter a" - , "aliases": [ - "ca", - "eh" - ] - , "tags": [ - "canada" - ] - } -, { - "emoji": "🇵🇰" - , "description": "regional indicator symbol letter p + regional indicator symbol letter k" - , "aliases": [ - "pk" - ] - , "tags": [ - "pakistan" - ] - } -, { - "emoji": "🇿🇦" - , "description": "regional indicator symbol letter z + regional indicator symbol letter a" - , "aliases": [ - "za" - ] - , "tags": [ - "south_africa" - ] - } -, { - "emoji": "🙂" - , "description": "slightly smiling face" - , "aliases": [ - "slightly_smiling_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🙁" - , "description": "slightly frowning face" - , "aliases": [ - "slightly_frowning_face" - ] - , "tags": [ - ] - } -, { - "emoji": "🙃" - , "description": "upside-down face" - , "aliases": [ - "upside_down_face" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "mm", - "mattermost" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "basecamp" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "basecampy" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "bowtie" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "feelsgood" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "finnadie" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "fu" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "goberserk" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "godmode" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "hurtrealbad" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "metal" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "neckbeard" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "octocat" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "rage1" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "rage2" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "rage3" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "rage4" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "shipit" - , "squirrel" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "suspect" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "taco" - ] - , "tags": [ - ] - } -, { - "aliases": [ - "trollface" - ] - , "tags": [ - ] - } -] +[["smile",{"name":"smile","unicode":"1f604"}],["smiley",{"name":"smiley","unicode":"1f603"}],["grinning",{"name":"grinning","unicode":"1f600"}],["blush",{"name":"blush","unicode":"1f60a"}],["relaxed",{"name":"relaxed","unicode":"263a"}],["wink",{"name":"wink","unicode":"1f609"}],["heart_eyes",{"name":"heart_eyes","unicode":"1f60d"}],["kissing_heart",{"name":"kissing_heart","unicode":"1f618"}],["kissing_closed_eyes",{"name":"kissing_closed_eyes","unicode":"1f61a"}],["kissing",{"name":"kissing","unicode":"1f617"}],["kissing_smiling_eyes",{"name":"kissing_smiling_eyes","unicode":"1f619"}],["stuck_out_tongue_winking_eye",{"name":"stuck_out_tongue_winking_eye","unicode":"1f61c"}],["stuck_out_tongue_closed_eyes",{"name":"stuck_out_tongue_closed_eyes","unicode":"1f61d"}],["stuck_out_tongue",{"name":"stuck_out_tongue","unicode":"1f61b"}],["flushed",{"name":"flushed","unicode":"1f633"}],["grin",{"name":"grin","unicode":"1f601"}],["pensive",{"name":"pensive","unicode":"1f614"}],["relieved",{"name":"relieved","unicode":"1f60c"}],["unamused",{"name":"unamused","unicode":"1f612"}],["disappointed",{"name":"disappointed","unicode":"1f61e"}],["persevere",{"name":"persevere","unicode":"1f623"}],["cry",{"name":"cry","unicode":"1f622"}],["joy",{"name":"joy","unicode":"1f602"}],["sob",{"name":"sob","unicode":"1f62d"}],["sleepy",{"name":"sleepy","unicode":"1f62a"}],["disappointed_relieved",{"name":"disappointed_relieved","unicode":"1f625"}],["cold_sweat",{"name":"cold_sweat","unicode":"1f630"}],["sweat_smile",{"name":"sweat_smile","unicode":"1f605"}],["sweat",{"name":"sweat","unicode":"1f613"}],["weary",{"name":"weary","unicode":"1f629"}],["tired_face",{"name":"tired_face","unicode":"1f62b"}],["fearful",{"name":"fearful","unicode":"1f628"}],["scream",{"name":"scream","unicode":"1f631"}],["angry",{"name":"angry","unicode":"1f620"}],["rage",{"name":"rage","unicode":"1f621"}],["pout",{"name":"pout","unicode":"1f621"}],["triumph",{"name":"triumph","unicode":"1f624"}],["confounded",{"name":"confounded","unicode":"1f616"}],["laughing",{"name":"laughing","unicode":"1f606"}],["satisfied",{"name":"satisfied","unicode":"1f606"}],["yum",{"name":"yum","unicode":"1f60b"}],["mask",{"name":"mask","unicode":"1f637"}],["sunglasses",{"name":"sunglasses","unicode":"1f60e"}],["sleeping",{"name":"sleeping","unicode":"1f634"}],["dizzy_face",{"name":"dizzy_face","unicode":"1f635"}],["astonished",{"name":"astonished","unicode":"1f632"}],["worried",{"name":"worried","unicode":"1f61f"}],["frowning",{"name":"frowning","unicode":"1f626"}],["anguished",{"name":"anguished","unicode":"1f627"}],["smiling_imp",{"name":"smiling_imp","unicode":"1f608"}],["imp",{"name":"imp","unicode":"1f47f"}],["open_mouth",{"name":"open_mouth","unicode":"1f62e"}],["grimacing",{"name":"grimacing","unicode":"1f62c"}],["neutral_face",{"name":"neutral_face","unicode":"1f610"}],["confused",{"name":"confused","unicode":"1f615"}],["hushed",{"name":"hushed","unicode":"1f62f"}],["no_mouth",{"name":"no_mouth","unicode":"1f636"}],["innocent",{"name":"innocent","unicode":"1f607"}],["smirk",{"name":"smirk","unicode":"1f60f"}],["expressionless",{"name":"expressionless","unicode":"1f611"}],["man_with_gua_pi_mao",{"name":"man_with_gua_pi_mao","unicode":"1f472"}],["man_with_turban",{"name":"man_with_turban","unicode":"1f473"}],["cop",{"name":"cop","unicode":"1f46e"}],["construction_worker",{"name":"construction_worker","unicode":"1f477"}],["guardsman",{"name":"guardsman","unicode":"1f482"}],["baby",{"name":"baby","unicode":"1f476"}],["boy",{"name":"boy","unicode":"1f466"}],["girl",{"name":"girl","unicode":"1f467"}],["man",{"name":"man","unicode":"1f468"}],["woman",{"name":"woman","unicode":"1f469"}],["older_man",{"name":"older_man","unicode":"1f474"}],["older_woman",{"name":"older_woman","unicode":"1f475"}],["person_with_blond_hair",{"name":"person_with_blond_hair","unicode":"1f471"}],["angel",{"name":"angel","unicode":"1f47c"}],["princess",{"name":"princess","unicode":"1f478"}],["smiley_cat",{"name":"smiley_cat","unicode":"1f63a"}],["smile_cat",{"name":"smile_cat","unicode":"1f638"}],["heart_eyes_cat",{"name":"heart_eyes_cat","unicode":"1f63b"}],["kissing_cat",{"name":"kissing_cat","unicode":"1f63d"}],["smirk_cat",{"name":"smirk_cat","unicode":"1f63c"}],["scream_cat",{"name":"scream_cat","unicode":"1f640"}],["crying_cat_face",{"name":"crying_cat_face","unicode":"1f63f"}],["joy_cat",{"name":"joy_cat","unicode":"1f639"}],["pouting_cat",{"name":"pouting_cat","unicode":"1f63e"}],["japanese_ogre",{"name":"japanese_ogre","unicode":"1f479"}],["japanese_goblin",{"name":"japanese_goblin","unicode":"1f47a"}],["see_no_evil",{"name":"see_no_evil","unicode":"1f648"}],["hear_no_evil",{"name":"hear_no_evil","unicode":"1f649"}],["speak_no_evil",{"name":"speak_no_evil","unicode":"1f64a"}],["skull",{"name":"skull","unicode":"1f480"}],["alien",{"name":"alien","unicode":"1f47d"}],["hankey",{"name":"hankey","unicode":"1f4a9"}],["poop",{"name":"poop","unicode":"1f4a9"}],["shit",{"name":"shit","unicode":"1f4a9"}],["fire",{"name":"fire","unicode":"1f525"}],["sparkles",{"name":"sparkles","unicode":"2728"}],["star2",{"name":"star2","unicode":"1f31f"}],["dizzy",{"name":"dizzy","unicode":"1f4ab"}],["boom",{"name":"boom","unicode":"1f4a5"}],["collision",{"name":"collision","unicode":"1f4a5"}],["anger",{"name":"anger","unicode":"1f4a2"}],["sweat_drops",{"name":"sweat_drops","unicode":"1f4a6"}],["droplet",{"name":"droplet","unicode":"1f4a7"}],["zzz",{"name":"zzz","unicode":"1f4a4"}],["dash",{"name":"dash","unicode":"1f4a8"}],["ear",{"name":"ear","unicode":"1f442"}],["eyes",{"name":"eyes","unicode":"1f440"}],["nose",{"name":"nose","unicode":"1f443"}],["tongue",{"name":"tongue","unicode":"1f445"}],["lips",{"name":"lips","unicode":"1f444"}],["+1",{"name":"+1","unicode":"1f44d"}],["thumbsup",{"name":"thumbsup","unicode":"1f44d"}],["-1",{"name":"-1","unicode":"1f44e"}],["thumbsdown",{"name":"thumbsdown","unicode":"1f44e"}],["ok_hand",{"name":"ok_hand","unicode":"1f44c"}],["facepunch",{"name":"facepunch","unicode":"1f44a"}],["punch",{"name":"punch","unicode":"1f44a"}],["fist",{"name":"fist","unicode":"270a"}],["v",{"name":"v","unicode":"270c"}],["wave",{"name":"wave","unicode":"1f44b"}],["hand",{"name":"hand","unicode":"270b"}],["raised_hand",{"name":"raised_hand","unicode":"270b"}],["open_hands",{"name":"open_hands","unicode":"1f450"}],["point_up_2",{"name":"point_up_2","unicode":"1f446"}],["point_down",{"name":"point_down","unicode":"1f447"}],["point_right",{"name":"point_right","unicode":"1f449"}],["point_left",{"name":"point_left","unicode":"1f448"}],["raised_hands",{"name":"raised_hands","unicode":"1f64c"}],["pray",{"name":"pray","unicode":"1f64f"}],["point_up",{"name":"point_up","unicode":"261d"}],["clap",{"name":"clap","unicode":"1f44f"}],["muscle",{"name":"muscle","unicode":"1f4aa"}],["walking",{"name":"walking","unicode":"1f6b6"}],["runner",{"name":"runner","unicode":"1f3c3"}],["running",{"name":"running","unicode":"1f3c3"}],["dancer",{"name":"dancer","unicode":"1f483"}],["couple",{"name":"couple","unicode":"1f46b"}],["family",{"name":"family","unicode":"1f46a"}],["two_men_holding_hands",{"name":"two_men_holding_hands","unicode":"1f46c"}],["two_women_holding_hands",{"name":"two_women_holding_hands","unicode":"1f46d"}],["couplekiss",{"name":"couplekiss","unicode":"1f48f"}],["couple_with_heart",{"name":"couple_with_heart","unicode":"1f491"}],["dancers",{"name":"dancers","unicode":"1f46f"}],["ok_woman",{"name":"ok_woman","unicode":"1f646"}],["no_good",{"name":"no_good","unicode":"1f645"}],["ng_woman",{"name":"ng_woman","unicode":"1f645"}],["information_desk_person",{"name":"information_desk_person","unicode":"1f481"}],["raising_hand",{"name":"raising_hand","unicode":"1f64b"}],["massage",{"name":"massage","unicode":"1f486"}],["haircut",{"name":"haircut","unicode":"1f487"}],["nail_care",{"name":"nail_care","unicode":"1f485"}],["bride_with_veil",{"name":"bride_with_veil","unicode":"1f470"}],["person_with_pouting_face",{"name":"person_with_pouting_face","unicode":"1f64e"}],["person_frowning",{"name":"person_frowning","unicode":"1f64d"}],["bow",{"name":"bow","unicode":"1f647"}],["tophat",{"name":"tophat","unicode":"1f3a9"}],["crown",{"name":"crown","unicode":"1f451"}],["womans_hat",{"name":"womans_hat","unicode":"1f452"}],["athletic_shoe",{"name":"athletic_shoe","unicode":"1f45f"}],["mans_shoe",{"name":"mans_shoe","unicode":"1f45e"}],["shoe",{"name":"shoe","unicode":"1f45e"}],["sandal",{"name":"sandal","unicode":"1f461"}],["high_heel",{"name":"high_heel","unicode":"1f460"}],["boot",{"name":"boot","unicode":"1f462"}],["shirt",{"name":"shirt","unicode":"1f455"}],["tshirt",{"name":"tshirt","unicode":"1f455"}],["necktie",{"name":"necktie","unicode":"1f454"}],["womans_clothes",{"name":"womans_clothes","unicode":"1f45a"}],["dress",{"name":"dress","unicode":"1f457"}],["running_shirt_with_sash",{"name":"running_shirt_with_sash","unicode":"1f3bd"}],["jeans",{"name":"jeans","unicode":"1f456"}],["kimono",{"name":"kimono","unicode":"1f458"}],["bikini",{"name":"bikini","unicode":"1f459"}],["briefcase",{"name":"briefcase","unicode":"1f4bc"}],["handbag",{"name":"handbag","unicode":"1f45c"}],["pouch",{"name":"pouch","unicode":"1f45d"}],["purse",{"name":"purse","unicode":"1f45b"}],["eyeglasses",{"name":"eyeglasses","unicode":"1f453"}],["ribbon",{"name":"ribbon","unicode":"1f380"}],["closed_umbrella",{"name":"closed_umbrella","unicode":"1f302"}],["lipstick",{"name":"lipstick","unicode":"1f484"}],["yellow_heart",{"name":"yellow_heart","unicode":"1f49b"}],["blue_heart",{"name":"blue_heart","unicode":"1f499"}],["purple_heart",{"name":"purple_heart","unicode":"1f49c"}],["green_heart",{"name":"green_heart","unicode":"1f49a"}],["heart",{"name":"heart","unicode":"2764"}],["broken_heart",{"name":"broken_heart","unicode":"1f494"}],["heartpulse",{"name":"heartpulse","unicode":"1f497"}],["heartbeat",{"name":"heartbeat","unicode":"1f493"}],["two_hearts",{"name":"two_hearts","unicode":"1f495"}],["sparkling_heart",{"name":"sparkling_heart","unicode":"1f496"}],["revolving_hearts",{"name":"revolving_hearts","unicode":"1f49e"}],["cupid",{"name":"cupid","unicode":"1f498"}],["love_letter",{"name":"love_letter","unicode":"1f48c"}],["kiss",{"name":"kiss","unicode":"1f48b"}],["ring",{"name":"ring","unicode":"1f48d"}],["gem",{"name":"gem","unicode":"1f48e"}],["bust_in_silhouette",{"name":"bust_in_silhouette","unicode":"1f464"}],["busts_in_silhouette",{"name":"busts_in_silhouette","unicode":"1f465"}],["speech_balloon",{"name":"speech_balloon","unicode":"1f4ac"}],["footprints",{"name":"footprints","unicode":"1f463"}],["thought_balloon",{"name":"thought_balloon","unicode":"1f4ad"}],["dog",{"name":"dog","unicode":"1f436"}],["wolf",{"name":"wolf","unicode":"1f43a"}],["cat",{"name":"cat","unicode":"1f431"}],["mouse",{"name":"mouse","unicode":"1f42d"}],["hamster",{"name":"hamster","unicode":"1f439"}],["rabbit",{"name":"rabbit","unicode":"1f430"}],["frog",{"name":"frog","unicode":"1f438"}],["tiger",{"name":"tiger","unicode":"1f42f"}],["koala",{"name":"koala","unicode":"1f428"}],["bear",{"name":"bear","unicode":"1f43b"}],["pig",{"name":"pig","unicode":"1f437"}],["pig_nose",{"name":"pig_nose","unicode":"1f43d"}],["cow",{"name":"cow","unicode":"1f42e"}],["boar",{"name":"boar","unicode":"1f417"}],["monkey_face",{"name":"monkey_face","unicode":"1f435"}],["monkey",{"name":"monkey","unicode":"1f412"}],["horse",{"name":"horse","unicode":"1f434"}],["sheep",{"name":"sheep","unicode":"1f411"}],["elephant",{"name":"elephant","unicode":"1f418"}],["panda_face",{"name":"panda_face","unicode":"1f43c"}],["penguin",{"name":"penguin","unicode":"1f427"}],["bird",{"name":"bird","unicode":"1f426"}],["baby_chick",{"name":"baby_chick","unicode":"1f424"}],["hatched_chick",{"name":"hatched_chick","unicode":"1f425"}],["hatching_chick",{"name":"hatching_chick","unicode":"1f423"}],["chicken",{"name":"chicken","unicode":"1f414"}],["snake",{"name":"snake","unicode":"1f40d"}],["turtle",{"name":"turtle","unicode":"1f422"}],["bug",{"name":"bug","unicode":"1f41b"}],["bee",{"name":"bee","unicode":"1f41d"}],["honeybee",{"name":"honeybee","unicode":"1f41d"}],["ant",{"name":"ant","unicode":"1f41c"}],["beetle",{"name":"beetle","unicode":"1f41e"}],["snail",{"name":"snail","unicode":"1f40c"}],["octopus",{"name":"octopus","unicode":"1f419"}],["shell",{"name":"shell","unicode":"1f41a"}],["tropical_fish",{"name":"tropical_fish","unicode":"1f420"}],["fish",{"name":"fish","unicode":"1f41f"}],["dolphin",{"name":"dolphin","unicode":"1f42c"}],["flipper",{"name":"flipper","unicode":"1f42c"}],["whale",{"name":"whale","unicode":"1f433"}],["whale2",{"name":"whale2","unicode":"1f40b"}],["cow2",{"name":"cow2","unicode":"1f404"}],["ram",{"name":"ram","unicode":"1f40f"}],["rat",{"name":"rat","unicode":"1f400"}],["water_buffalo",{"name":"water_buffalo","unicode":"1f403"}],["tiger2",{"name":"tiger2","unicode":"1f405"}],["rabbit2",{"name":"rabbit2","unicode":"1f407"}],["dragon",{"name":"dragon","unicode":"1f409"}],["racehorse",{"name":"racehorse","unicode":"1f40e"}],["goat",{"name":"goat","unicode":"1f410"}],["rooster",{"name":"rooster","unicode":"1f413"}],["dog2",{"name":"dog2","unicode":"1f415"}],["pig2",{"name":"pig2","unicode":"1f416"}],["mouse2",{"name":"mouse2","unicode":"1f401"}],["ox",{"name":"ox","unicode":"1f402"}],["dragon_face",{"name":"dragon_face","unicode":"1f432"}],["blowfish",{"name":"blowfish","unicode":"1f421"}],["crocodile",{"name":"crocodile","unicode":"1f40a"}],["camel",{"name":"camel","unicode":"1f42b"}],["dromedary_camel",{"name":"dromedary_camel","unicode":"1f42a"}],["leopard",{"name":"leopard","unicode":"1f406"}],["cat2",{"name":"cat2","unicode":"1f408"}],["poodle",{"name":"poodle","unicode":"1f429"}],["feet",{"name":"feet","unicode":"1f43e"}],["paw_prints",{"name":"paw_prints","unicode":"1f43e"}],["bouquet",{"name":"bouquet","unicode":"1f490"}],["cherry_blossom",{"name":"cherry_blossom","unicode":"1f338"}],["tulip",{"name":"tulip","unicode":"1f337"}],["four_leaf_clover",{"name":"four_leaf_clover","unicode":"1f340"}],["rose",{"name":"rose","unicode":"1f339"}],["sunflower",{"name":"sunflower","unicode":"1f33b"}],["hibiscus",{"name":"hibiscus","unicode":"1f33a"}],["maple_leaf",{"name":"maple_leaf","unicode":"1f341"}],["leaves",{"name":"leaves","unicode":"1f343"}],["fallen_leaf",{"name":"fallen_leaf","unicode":"1f342"}],["herb",{"name":"herb","unicode":"1f33f"}],["ear_of_rice",{"name":"ear_of_rice","unicode":"1f33e"}],["mushroom",{"name":"mushroom","unicode":"1f344"}],["cactus",{"name":"cactus","unicode":"1f335"}],["palm_tree",{"name":"palm_tree","unicode":"1f334"}],["evergreen_tree",{"name":"evergreen_tree","unicode":"1f332"}],["deciduous_tree",{"name":"deciduous_tree","unicode":"1f333"}],["chestnut",{"name":"chestnut","unicode":"1f330"}],["seedling",{"name":"seedling","unicode":"1f331"}],["blossom",{"name":"blossom","unicode":"1f33c"}],["globe_with_meridians",{"name":"globe_with_meridians","unicode":"1f310"}],["sun_with_face",{"name":"sun_with_face","unicode":"1f31e"}],["full_moon_with_face",{"name":"full_moon_with_face","unicode":"1f31d"}],["new_moon_with_face",{"name":"new_moon_with_face","unicode":"1f31a"}],["new_moon",{"name":"new_moon","unicode":"1f311"}],["waxing_crescent_moon",{"name":"waxing_crescent_moon","unicode":"1f312"}],["first_quarter_moon",{"name":"first_quarter_moon","unicode":"1f313"}],["moon",{"name":"moon","unicode":"1f314"}],["waxing_gibbous_moon",{"name":"waxing_gibbous_moon","unicode":"1f314"}],["full_moon",{"name":"full_moon","unicode":"1f315"}],["waning_gibbous_moon",{"name":"waning_gibbous_moon","unicode":"1f316"}],["last_quarter_moon",{"name":"last_quarter_moon","unicode":"1f317"}],["waning_crescent_moon",{"name":"waning_crescent_moon","unicode":"1f318"}],["last_quarter_moon_with_face",{"name":"last_quarter_moon_with_face","unicode":"1f31c"}],["first_quarter_moon_with_face",{"name":"first_quarter_moon_with_face","unicode":"1f31b"}],["crescent_moon",{"name":"crescent_moon","unicode":"1f319"}],["earth_africa",{"name":"earth_africa","unicode":"1f30d"}],["earth_americas",{"name":"earth_americas","unicode":"1f30e"}],["earth_asia",{"name":"earth_asia","unicode":"1f30f"}],["volcano",{"name":"volcano","unicode":"1f30b"}],["milky_way",{"name":"milky_way","unicode":"1f30c"}],["stars",{"name":"stars","unicode":"1f320"}],["star",{"name":"star","unicode":"2b50"}],["sunny",{"name":"sunny","unicode":"2600"}],["partly_sunny",{"name":"partly_sunny","unicode":"26c5"}],["cloud",{"name":"cloud","unicode":"2601"}],["zap",{"name":"zap","unicode":"26a1"}],["umbrella",{"name":"umbrella","unicode":"2614"}],["snowflake",{"name":"snowflake","unicode":"2744"}],["snowman",{"name":"snowman","unicode":"26c4"}],["cyclone",{"name":"cyclone","unicode":"1f300"}],["foggy",{"name":"foggy","unicode":"1f301"}],["rainbow",{"name":"rainbow","unicode":"1f308"}],["ocean",{"name":"ocean","unicode":"1f30a"}],["bamboo",{"name":"bamboo","unicode":"1f38d"}],["gift_heart",{"name":"gift_heart","unicode":"1f49d"}],["dolls",{"name":"dolls","unicode":"1f38e"}],["school_satchel",{"name":"school_satchel","unicode":"1f392"}],["mortar_board",{"name":"mortar_board","unicode":"1f393"}],["flags",{"name":"flags","unicode":"1f38f"}],["fireworks",{"name":"fireworks","unicode":"1f386"}],["sparkler",{"name":"sparkler","unicode":"1f387"}],["wind_chime",{"name":"wind_chime","unicode":"1f390"}],["rice_scene",{"name":"rice_scene","unicode":"1f391"}],["jack_o_lantern",{"name":"jack_o_lantern","unicode":"1f383"}],["ghost",{"name":"ghost","unicode":"1f47b"}],["santa",{"name":"santa","unicode":"1f385"}],["christmas_tree",{"name":"christmas_tree","unicode":"1f384"}],["gift",{"name":"gift","unicode":"1f381"}],["tanabata_tree",{"name":"tanabata_tree","unicode":"1f38b"}],["tada",{"name":"tada","unicode":"1f389"}],["confetti_ball",{"name":"confetti_ball","unicode":"1f38a"}],["balloon",{"name":"balloon","unicode":"1f388"}],["crossed_flags",{"name":"crossed_flags","unicode":"1f38c"}],["crystal_ball",{"name":"crystal_ball","unicode":"1f52e"}],["movie_camera",{"name":"movie_camera","unicode":"1f3a5"}],["camera",{"name":"camera","unicode":"1f4f7"}],["video_camera",{"name":"video_camera","unicode":"1f4f9"}],["vhs",{"name":"vhs","unicode":"1f4fc"}],["cd",{"name":"cd","unicode":"1f4bf"}],["dvd",{"name":"dvd","unicode":"1f4c0"}],["minidisc",{"name":"minidisc","unicode":"1f4bd"}],["floppy_disk",{"name":"floppy_disk","unicode":"1f4be"}],["computer",{"name":"computer","unicode":"1f4bb"}],["iphone",{"name":"iphone","unicode":"1f4f1"}],["phone",{"name":"phone","unicode":"260e"}],["telephone",{"name":"telephone","unicode":"260e"}],["telephone_receiver",{"name":"telephone_receiver","unicode":"1f4de"}],["pager",{"name":"pager","unicode":"1f4df"}],["fax",{"name":"fax","unicode":"1f4e0"}],["satellite",{"name":"satellite","unicode":"1f4e1"}],["tv",{"name":"tv","unicode":"1f4fa"}],["radio",{"name":"radio","unicode":"1f4fb"}],["loud_sound",{"name":"loud_sound","unicode":"1f50a"}],["sound",{"name":"sound","unicode":"1f509"}],["speaker",{"name":"speaker","unicode":"1f508"}],["mute",{"name":"mute","unicode":"1f507"}],["bell",{"name":"bell","unicode":"1f514"}],["no_bell",{"name":"no_bell","unicode":"1f515"}],["loudspeaker",{"name":"loudspeaker","unicode":"1f4e2"}],["mega",{"name":"mega","unicode":"1f4e3"}],["hourglass_flowing_sand",{"name":"hourglass_flowing_sand","unicode":"23f3"}],["hourglass",{"name":"hourglass","unicode":"231b"}],["alarm_clock",{"name":"alarm_clock","unicode":"23f0"}],["watch",{"name":"watch","unicode":"231a"}],["unlock",{"name":"unlock","unicode":"1f513"}],["lock",{"name":"lock","unicode":"1f512"}],["lock_with_ink_pen",{"name":"lock_with_ink_pen","unicode":"1f50f"}],["closed_lock_with_key",{"name":"closed_lock_with_key","unicode":"1f510"}],["key",{"name":"key","unicode":"1f511"}],["mag_right",{"name":"mag_right","unicode":"1f50e"}],["bulb",{"name":"bulb","unicode":"1f4a1"}],["flashlight",{"name":"flashlight","unicode":"1f526"}],["high_brightness",{"name":"high_brightness","unicode":"1f506"}],["low_brightness",{"name":"low_brightness","unicode":"1f505"}],["electric_plug",{"name":"electric_plug","unicode":"1f50c"}],["battery",{"name":"battery","unicode":"1f50b"}],["mag",{"name":"mag","unicode":"1f50d"}],["bathtub",{"name":"bathtub","unicode":"1f6c1"}],["bath",{"name":"bath","unicode":"1f6c0"}],["shower",{"name":"shower","unicode":"1f6bf"}],["toilet",{"name":"toilet","unicode":"1f6bd"}],["wrench",{"name":"wrench","unicode":"1f527"}],["nut_and_bolt",{"name":"nut_and_bolt","unicode":"1f529"}],["hammer",{"name":"hammer","unicode":"1f528"}],["door",{"name":"door","unicode":"1f6aa"}],["smoking",{"name":"smoking","unicode":"1f6ac"}],["bomb",{"name":"bomb","unicode":"1f4a3"}],["gun",{"name":"gun","unicode":"1f52b"}],["hocho",{"name":"hocho","unicode":"1f52a"}],["knife",{"name":"knife","unicode":"1f52a"}],["pill",{"name":"pill","unicode":"1f48a"}],["syringe",{"name":"syringe","unicode":"1f489"}],["moneybag",{"name":"moneybag","unicode":"1f4b0"}],["yen",{"name":"yen","unicode":"1f4b4"}],["dollar",{"name":"dollar","unicode":"1f4b5"}],["pound",{"name":"pound","unicode":"1f4b7"}],["euro",{"name":"euro","unicode":"1f4b6"}],["credit_card",{"name":"credit_card","unicode":"1f4b3"}],["money_with_wings",{"name":"money_with_wings","unicode":"1f4b8"}],["calling",{"name":"calling","unicode":"1f4f2"}],["e-mail",{"name":"e-mail","unicode":"1f4e7"}],["inbox_tray",{"name":"inbox_tray","unicode":"1f4e5"}],["outbox_tray",{"name":"outbox_tray","unicode":"1f4e4"}],["email",{"name":"email","unicode":"2709"}],["envelope",{"name":"envelope","unicode":"2709"}],["envelope_with_arrow",{"name":"envelope_with_arrow","unicode":"1f4e9"}],["incoming_envelope",{"name":"incoming_envelope","unicode":"1f4e8"}],["postal_horn",{"name":"postal_horn","unicode":"1f4ef"}],["mailbox",{"name":"mailbox","unicode":"1f4eb"}],["mailbox_closed",{"name":"mailbox_closed","unicode":"1f4ea"}],["mailbox_with_mail",{"name":"mailbox_with_mail","unicode":"1f4ec"}],["mailbox_with_no_mail",{"name":"mailbox_with_no_mail","unicode":"1f4ed"}],["postbox",{"name":"postbox","unicode":"1f4ee"}],["package",{"name":"package","unicode":"1f4e6"}],["memo",{"name":"memo","unicode":"1f4dd"}],["pencil",{"name":"pencil","unicode":"1f4dd"}],["page_facing_up",{"name":"page_facing_up","unicode":"1f4c4"}],["page_with_curl",{"name":"page_with_curl","unicode":"1f4c3"}],["bookmark_tabs",{"name":"bookmark_tabs","unicode":"1f4d1"}],["bar_chart",{"name":"bar_chart","unicode":"1f4ca"}],["chart_with_upwards_trend",{"name":"chart_with_upwards_trend","unicode":"1f4c8"}],["chart_with_downwards_trend",{"name":"chart_with_downwards_trend","unicode":"1f4c9"}],["scroll",{"name":"scroll","unicode":"1f4dc"}],["clipboard",{"name":"clipboard","unicode":"1f4cb"}],["date",{"name":"date","unicode":"1f4c5"}],["calendar",{"name":"calendar","unicode":"1f4c6"}],["card_index",{"name":"card_index","unicode":"1f4c7"}],["file_folder",{"name":"file_folder","unicode":"1f4c1"}],["open_file_folder",{"name":"open_file_folder","unicode":"1f4c2"}],["scissors",{"name":"scissors","unicode":"2702"}],["pushpin",{"name":"pushpin","unicode":"1f4cc"}],["paperclip",{"name":"paperclip","unicode":"1f4ce"}],["black_nib",{"name":"black_nib","unicode":"2712"}],["pencil2",{"name":"pencil2","unicode":"270f"}],["straight_ruler",{"name":"straight_ruler","unicode":"1f4cf"}],["triangular_ruler",{"name":"triangular_ruler","unicode":"1f4d0"}],["closed_book",{"name":"closed_book","unicode":"1f4d5"}],["green_book",{"name":"green_book","unicode":"1f4d7"}],["blue_book",{"name":"blue_book","unicode":"1f4d8"}],["orange_book",{"name":"orange_book","unicode":"1f4d9"}],["notebook",{"name":"notebook","unicode":"1f4d3"}],["notebook_with_decorative_cover",{"name":"notebook_with_decorative_cover","unicode":"1f4d4"}],["ledger",{"name":"ledger","unicode":"1f4d2"}],["books",{"name":"books","unicode":"1f4da"}],["book",{"name":"book","unicode":"1f4d6"}],["open_book",{"name":"open_book","unicode":"1f4d6"}],["bookmark",{"name":"bookmark","unicode":"1f516"}],["name_badge",{"name":"name_badge","unicode":"1f4db"}],["microscope",{"name":"microscope","unicode":"1f52c"}],["telescope",{"name":"telescope","unicode":"1f52d"}],["newspaper",{"name":"newspaper","unicode":"1f4f0"}],["art",{"name":"art","unicode":"1f3a8"}],["clapper",{"name":"clapper","unicode":"1f3ac"}],["microphone",{"name":"microphone","unicode":"1f3a4"}],["headphones",{"name":"headphones","unicode":"1f3a7"}],["musical_score",{"name":"musical_score","unicode":"1f3bc"}],["musical_note",{"name":"musical_note","unicode":"1f3b5"}],["notes",{"name":"notes","unicode":"1f3b6"}],["musical_keyboard",{"name":"musical_keyboard","unicode":"1f3b9"}],["violin",{"name":"violin","unicode":"1f3bb"}],["trumpet",{"name":"trumpet","unicode":"1f3ba"}],["saxophone",{"name":"saxophone","unicode":"1f3b7"}],["guitar",{"name":"guitar","unicode":"1f3b8"}],["space_invader",{"name":"space_invader","unicode":"1f47e"}],["video_game",{"name":"video_game","unicode":"1f3ae"}],["black_joker",{"name":"black_joker","unicode":"1f0cf"}],["flower_playing_cards",{"name":"flower_playing_cards","unicode":"1f3b4"}],["mahjong",{"name":"mahjong","unicode":"1f004"}],["game_die",{"name":"game_die","unicode":"1f3b2"}],["dart",{"name":"dart","unicode":"1f3af"}],["football",{"name":"football","unicode":"1f3c8"}],["basketball",{"name":"basketball","unicode":"1f3c0"}],["soccer",{"name":"soccer","unicode":"26bd"}],["baseball",{"name":"baseball","unicode":"26be"}],["tennis",{"name":"tennis","unicode":"1f3be"}],["8ball",{"name":"8ball","unicode":"1f3b1"}],["rugby_football",{"name":"rugby_football","unicode":"1f3c9"}],["bowling",{"name":"bowling","unicode":"1f3b3"}],["golf",{"name":"golf","unicode":"26f3"}],["mountain_bicyclist",{"name":"mountain_bicyclist","unicode":"1f6b5"}],["bicyclist",{"name":"bicyclist","unicode":"1f6b4"}],["checkered_flag",{"name":"checkered_flag","unicode":"1f3c1"}],["horse_racing",{"name":"horse_racing","unicode":"1f3c7"}],["trophy",{"name":"trophy","unicode":"1f3c6"}],["ski",{"name":"ski","unicode":"1f3bf"}],["snowboarder",{"name":"snowboarder","unicode":"1f3c2"}],["swimmer",{"name":"swimmer","unicode":"1f3ca"}],["surfer",{"name":"surfer","unicode":"1f3c4"}],["fishing_pole_and_fish",{"name":"fishing_pole_and_fish","unicode":"1f3a3"}],["coffee",{"name":"coffee","unicode":"2615"}],["tea",{"name":"tea","unicode":"1f375"}],["sake",{"name":"sake","unicode":"1f376"}],["baby_bottle",{"name":"baby_bottle","unicode":"1f37c"}],["beer",{"name":"beer","unicode":"1f37a"}],["beers",{"name":"beers","unicode":"1f37b"}],["cocktail",{"name":"cocktail","unicode":"1f378"}],["tropical_drink",{"name":"tropical_drink","unicode":"1f379"}],["wine_glass",{"name":"wine_glass","unicode":"1f377"}],["fork_and_knife",{"name":"fork_and_knife","unicode":"1f374"}],["pizza",{"name":"pizza","unicode":"1f355"}],["hamburger",{"name":"hamburger","unicode":"1f354"}],["fries",{"name":"fries","unicode":"1f35f"}],["poultry_leg",{"name":"poultry_leg","unicode":"1f357"}],["meat_on_bone",{"name":"meat_on_bone","unicode":"1f356"}],["spaghetti",{"name":"spaghetti","unicode":"1f35d"}],["curry",{"name":"curry","unicode":"1f35b"}],["fried_shrimp",{"name":"fried_shrimp","unicode":"1f364"}],["bento",{"name":"bento","unicode":"1f371"}],["sushi",{"name":"sushi","unicode":"1f363"}],["fish_cake",{"name":"fish_cake","unicode":"1f365"}],["rice_ball",{"name":"rice_ball","unicode":"1f359"}],["rice_cracker",{"name":"rice_cracker","unicode":"1f358"}],["rice",{"name":"rice","unicode":"1f35a"}],["ramen",{"name":"ramen","unicode":"1f35c"}],["stew",{"name":"stew","unicode":"1f372"}],["oden",{"name":"oden","unicode":"1f362"}],["dango",{"name":"dango","unicode":"1f361"}],["egg",{"name":"egg","unicode":"1f373"}],["bread",{"name":"bread","unicode":"1f35e"}],["doughnut",{"name":"doughnut","unicode":"1f369"}],["custard",{"name":"custard","unicode":"1f36e"}],["icecream",{"name":"icecream","unicode":"1f366"}],["ice_cream",{"name":"ice_cream","unicode":"1f368"}],["shaved_ice",{"name":"shaved_ice","unicode":"1f367"}],["birthday",{"name":"birthday","unicode":"1f382"}],["cake",{"name":"cake","unicode":"1f370"}],["cookie",{"name":"cookie","unicode":"1f36a"}],["chocolate_bar",{"name":"chocolate_bar","unicode":"1f36b"}],["candy",{"name":"candy","unicode":"1f36c"}],["lollipop",{"name":"lollipop","unicode":"1f36d"}],["honey_pot",{"name":"honey_pot","unicode":"1f36f"}],["apple",{"name":"apple","unicode":"1f34e"}],["green_apple",{"name":"green_apple","unicode":"1f34f"}],["tangerine",{"name":"tangerine","unicode":"1f34a"}],["orange",{"name":"orange","unicode":"1f34a"}],["mandarin",{"name":"mandarin","unicode":"1f34a"}],["lemon",{"name":"lemon","unicode":"1f34b"}],["cherries",{"name":"cherries","unicode":"1f352"}],["grapes",{"name":"grapes","unicode":"1f347"}],["watermelon",{"name":"watermelon","unicode":"1f349"}],["strawberry",{"name":"strawberry","unicode":"1f353"}],["peach",{"name":"peach","unicode":"1f351"}],["melon",{"name":"melon","unicode":"1f348"}],["banana",{"name":"banana","unicode":"1f34c"}],["pear",{"name":"pear","unicode":"1f350"}],["pineapple",{"name":"pineapple","unicode":"1f34d"}],["sweet_potato",{"name":"sweet_potato","unicode":"1f360"}],["eggplant",{"name":"eggplant","unicode":"1f346"}],["tomato",{"name":"tomato","unicode":"1f345"}],["corn",{"name":"corn","unicode":"1f33d"}],["house",{"name":"house","unicode":"1f3e0"}],["house_with_garden",{"name":"house_with_garden","unicode":"1f3e1"}],["school",{"name":"school","unicode":"1f3eb"}],["office",{"name":"office","unicode":"1f3e2"}],["post_office",{"name":"post_office","unicode":"1f3e3"}],["hospital",{"name":"hospital","unicode":"1f3e5"}],["bank",{"name":"bank","unicode":"1f3e6"}],["convenience_store",{"name":"convenience_store","unicode":"1f3ea"}],["love_hotel",{"name":"love_hotel","unicode":"1f3e9"}],["hotel",{"name":"hotel","unicode":"1f3e8"}],["wedding",{"name":"wedding","unicode":"1f492"}],["church",{"name":"church","unicode":"26ea"}],["department_store",{"name":"department_store","unicode":"1f3ec"}],["european_post_office",{"name":"european_post_office","unicode":"1f3e4"}],["city_sunrise",{"name":"city_sunrise","unicode":"1f307"}],["city_sunset",{"name":"city_sunset","unicode":"1f306"}],["japanese_castle",{"name":"japanese_castle","unicode":"1f3ef"}],["european_castle",{"name":"european_castle","unicode":"1f3f0"}],["tent",{"name":"tent","unicode":"26fa"}],["factory",{"name":"factory","unicode":"1f3ed"}],["tokyo_tower",{"name":"tokyo_tower","unicode":"1f5fc"}],["japan",{"name":"japan","unicode":"1f5fe"}],["mount_fuji",{"name":"mount_fuji","unicode":"1f5fb"}],["sunrise_over_mountains",{"name":"sunrise_over_mountains","unicode":"1f304"}],["sunrise",{"name":"sunrise","unicode":"1f305"}],["night_with_stars",{"name":"night_with_stars","unicode":"1f303"}],["statue_of_liberty",{"name":"statue_of_liberty","unicode":"1f5fd"}],["bridge_at_night",{"name":"bridge_at_night","unicode":"1f309"}],["carousel_horse",{"name":"carousel_horse","unicode":"1f3a0"}],["ferris_wheel",{"name":"ferris_wheel","unicode":"1f3a1"}],["fountain",{"name":"fountain","unicode":"26f2"}],["roller_coaster",{"name":"roller_coaster","unicode":"1f3a2"}],["ship",{"name":"ship","unicode":"1f6a2"}],["boat",{"name":"boat","unicode":"26f5"}],["sailboat",{"name":"sailboat","unicode":"26f5"}],["speedboat",{"name":"speedboat","unicode":"1f6a4"}],["rowboat",{"name":"rowboat","unicode":"1f6a3"}],["anchor",{"name":"anchor","unicode":"2693"}],["rocket",{"name":"rocket","unicode":"1f680"}],["airplane",{"name":"airplane","unicode":"2708"}],["seat",{"name":"seat","unicode":"1f4ba"}],["helicopter",{"name":"helicopter","unicode":"1f681"}],["steam_locomotive",{"name":"steam_locomotive","unicode":"1f682"}],["tram",{"name":"tram","unicode":"1f68a"}],["station",{"name":"station","unicode":"1f689"}],["mountain_railway",{"name":"mountain_railway","unicode":"1f69e"}],["train2",{"name":"train2","unicode":"1f686"}],["bullettrain_side",{"name":"bullettrain_side","unicode":"1f684"}],["bullettrain_front",{"name":"bullettrain_front","unicode":"1f685"}],["light_rail",{"name":"light_rail","unicode":"1f688"}],["metro",{"name":"metro","unicode":"1f687"}],["monorail",{"name":"monorail","unicode":"1f69d"}],["train",{"name":"train","unicode":"1f68b"}],["railway_car",{"name":"railway_car","unicode":"1f683"}],["trolleybus",{"name":"trolleybus","unicode":"1f68e"}],["bus",{"name":"bus","unicode":"1f68c"}],["oncoming_bus",{"name":"oncoming_bus","unicode":"1f68d"}],["blue_car",{"name":"blue_car","unicode":"1f699"}],["oncoming_automobile",{"name":"oncoming_automobile","unicode":"1f698"}],["car",{"name":"car","unicode":"1f697"}],["red_car",{"name":"red_car","unicode":"1f697"}],["taxi",{"name":"taxi","unicode":"1f695"}],["oncoming_taxi",{"name":"oncoming_taxi","unicode":"1f696"}],["articulated_lorry",{"name":"articulated_lorry","unicode":"1f69b"}],["truck",{"name":"truck","unicode":"1f69a"}],["rotating_light",{"name":"rotating_light","unicode":"1f6a8"}],["police_car",{"name":"police_car","unicode":"1f693"}],["oncoming_police_car",{"name":"oncoming_police_car","unicode":"1f694"}],["fire_engine",{"name":"fire_engine","unicode":"1f692"}],["ambulance",{"name":"ambulance","unicode":"1f691"}],["minibus",{"name":"minibus","unicode":"1f690"}],["bike",{"name":"bike","unicode":"1f6b2"}],["aerial_tramway",{"name":"aerial_tramway","unicode":"1f6a1"}],["suspension_railway",{"name":"suspension_railway","unicode":"1f69f"}],["mountain_cableway",{"name":"mountain_cableway","unicode":"1f6a0"}],["tractor",{"name":"tractor","unicode":"1f69c"}],["barber",{"name":"barber","unicode":"1f488"}],["busstop",{"name":"busstop","unicode":"1f68f"}],["ticket",{"name":"ticket","unicode":"1f3ab"}],["vertical_traffic_light",{"name":"vertical_traffic_light","unicode":"1f6a6"}],["traffic_light",{"name":"traffic_light","unicode":"1f6a5"}],["warning",{"name":"warning","unicode":"26a0"}],["construction",{"name":"construction","unicode":"1f6a7"}],["beginner",{"name":"beginner","unicode":"1f530"}],["fuelpump",{"name":"fuelpump","unicode":"26fd"}],["izakaya_lantern",{"name":"izakaya_lantern","unicode":"1f3ee"}],["lantern",{"name":"lantern","unicode":"1f3ee"}],["slot_machine",{"name":"slot_machine","unicode":"1f3b0"}],["hotsprings",{"name":"hotsprings","unicode":"2668"}],["moyai",{"name":"moyai","unicode":"1f5ff"}],["circus_tent",{"name":"circus_tent","unicode":"1f3aa"}],["performing_arts",{"name":"performing_arts","unicode":"1f3ad"}],["round_pushpin",{"name":"round_pushpin","unicode":"1f4cd"}],["triangular_flag_on_post",{"name":"triangular_flag_on_post","unicode":"1f6a9"}],["jp",{"name":"jp","unicode":"1f1ef-1f1f5"}],["kr",{"name":"kr","unicode":"1f1f0-1f1f7"}],["de",{"name":"de","unicode":"1f1e9-1f1ea"}],["cn",{"name":"cn","unicode":"1f1e8-1f1f3"}],["us",{"name":"us","unicode":"1f1fa-1f1f8"}],["fr",{"name":"fr","unicode":"1f1eb-1f1f7"}],["es",{"name":"es","unicode":"1f1ea-1f1f8"}],["it",{"name":"it","unicode":"1f1ee-1f1f9"}],["ru",{"name":"ru","unicode":"1f1f7-1f1fa"}],["gb",{"name":"gb","unicode":"1f1ec-1f1e7"}],["uk",{"name":"uk","unicode":"1f1ec-1f1e7"}],["one",{"name":"one","unicode":"0031-20e3"}],["two",{"name":"two","unicode":"0032-20e3"}],["three",{"name":"three","unicode":"0033-20e3"}],["four",{"name":"four","unicode":"0034-20e3"}],["five",{"name":"five","unicode":"0035-20e3"}],["six",{"name":"six","unicode":"0036-20e3"}],["seven",{"name":"seven","unicode":"0037-20e3"}],["eight",{"name":"eight","unicode":"0038-20e3"}],["nine",{"name":"nine","unicode":"0039-20e3"}],["zero",{"name":"zero","unicode":"0030-20e3"}],["keycap_ten",{"name":"keycap_ten","unicode":"1f51f"}],["1234",{"name":"1234","unicode":"1f522"}],["hash",{"name":"hash","unicode":"0023-20e3"}],["symbols",{"name":"symbols","unicode":"1f523"}],["arrow_up",{"name":"arrow_up","unicode":"2b06"}],["arrow_down",{"name":"arrow_down","unicode":"2b07"}],["arrow_left",{"name":"arrow_left","unicode":"2b05"}],["arrow_right",{"name":"arrow_right","unicode":"27a1"}],["capital_abcd",{"name":"capital_abcd","unicode":"1f520"}],["abcd",{"name":"abcd","unicode":"1f521"}],["abc",{"name":"abc","unicode":"1f524"}],["arrow_upper_right",{"name":"arrow_upper_right","unicode":"2197"}],["arrow_upper_left",{"name":"arrow_upper_left","unicode":"2196"}],["arrow_lower_right",{"name":"arrow_lower_right","unicode":"2198"}],["arrow_lower_left",{"name":"arrow_lower_left","unicode":"2199"}],["left_right_arrow",{"name":"left_right_arrow","unicode":"2194"}],["arrow_up_down",{"name":"arrow_up_down","unicode":"2195"}],["arrows_counterclockwise",{"name":"arrows_counterclockwise","unicode":"1f504"}],["arrow_backward",{"name":"arrow_backward","unicode":"25c0"}],["arrow_forward",{"name":"arrow_forward","unicode":"25b6"}],["arrow_up_small",{"name":"arrow_up_small","unicode":"1f53c"}],["arrow_down_small",{"name":"arrow_down_small","unicode":"1f53d"}],["leftwards_arrow_with_hook",{"name":"leftwards_arrow_with_hook","unicode":"21a9"}],["arrow_right_hook",{"name":"arrow_right_hook","unicode":"21aa"}],["information_source",{"name":"information_source","unicode":"2139"}],["rewind",{"name":"rewind","unicode":"23ea"}],["fast_forward",{"name":"fast_forward","unicode":"23e9"}],["arrow_double_up",{"name":"arrow_double_up","unicode":"23eb"}],["arrow_double_down",{"name":"arrow_double_down","unicode":"23ec"}],["arrow_heading_down",{"name":"arrow_heading_down","unicode":"2935"}],["arrow_heading_up",{"name":"arrow_heading_up","unicode":"2934"}],["ok",{"name":"ok","unicode":"1f197"}],["twisted_rightwards_arrows",{"name":"twisted_rightwards_arrows","unicode":"1f500"}],["repeat",{"name":"repeat","unicode":"1f501"}],["repeat_one",{"name":"repeat_one","unicode":"1f502"}],["new",{"name":"new","unicode":"1f195"}],["up",{"name":"up","unicode":"1f199"}],["cool",{"name":"cool","unicode":"1f192"}],["free",{"name":"free","unicode":"1f193"}],["ng",{"name":"ng","unicode":"1f196"}],["signal_strength",{"name":"signal_strength","unicode":"1f4f6"}],["cinema",{"name":"cinema","unicode":"1f3a6"}],["koko",{"name":"koko","unicode":"1f201"}],["u6307",{"name":"u6307","unicode":"1f22f"}],["u7a7a",{"name":"u7a7a","unicode":"1f233"}],["u6e80",{"name":"u6e80","unicode":"1f235"}],["u5408",{"name":"u5408","unicode":"1f234"}],["u7981",{"name":"u7981","unicode":"1f232"}],["ideograph_advantage",{"name":"ideograph_advantage","unicode":"1f250"}],["u5272",{"name":"u5272","unicode":"1f239"}],["u55b6",{"name":"u55b6","unicode":"1f23a"}],["u6709",{"name":"u6709","unicode":"1f236"}],["u7121",{"name":"u7121","unicode":"1f21a"}],["restroom",{"name":"restroom","unicode":"1f6bb"}],["mens",{"name":"mens","unicode":"1f6b9"}],["womens",{"name":"womens","unicode":"1f6ba"}],["baby_symbol",{"name":"baby_symbol","unicode":"1f6bc"}],["wc",{"name":"wc","unicode":"1f6be"}],["potable_water",{"name":"potable_water","unicode":"1f6b0"}],["put_litter_in_its_place",{"name":"put_litter_in_its_place","unicode":"1f6ae"}],["parking",{"name":"parking","unicode":"1f17f"}],["wheelchair",{"name":"wheelchair","unicode":"267f"}],["no_smoking",{"name":"no_smoking","unicode":"1f6ad"}],["u6708",{"name":"u6708","unicode":"1f237"}],["u7533",{"name":"u7533","unicode":"1f238"}],["sa",{"name":"sa","unicode":"1f202"}],["m",{"name":"m","unicode":"24c2"}],["passport_control",{"name":"passport_control","unicode":"1f6c2"}],["baggage_claim",{"name":"baggage_claim","unicode":"1f6c4"}],["left_luggage",{"name":"left_luggage","unicode":"1f6c5"}],["customs",{"name":"customs","unicode":"1f6c3"}],["accept",{"name":"accept","unicode":"1f251"}],["secret",{"name":"secret","unicode":"3299"}],["congratulations",{"name":"congratulations","unicode":"3297"}],["cl",{"name":"cl","unicode":"1f191"}],["sos",{"name":"sos","unicode":"1f198"}],["id",{"name":"id","unicode":"1f194"}],["no_entry_sign",{"name":"no_entry_sign","unicode":"1f6ab"}],["underage",{"name":"underage","unicode":"1f51e"}],["no_mobile_phones",{"name":"no_mobile_phones","unicode":"1f4f5"}],["do_not_litter",{"name":"do_not_litter","unicode":"1f6af"}],["non-potable_water",{"name":"non-potable_water","unicode":"1f6b1"}],["no_bicycles",{"name":"no_bicycles","unicode":"1f6b3"}],["no_pedestrians",{"name":"no_pedestrians","unicode":"1f6b7"}],["children_crossing",{"name":"children_crossing","unicode":"1f6b8"}],["no_entry",{"name":"no_entry","unicode":"26d4"}],["eight_spoked_asterisk",{"name":"eight_spoked_asterisk","unicode":"2733"}],["sparkle",{"name":"sparkle","unicode":"2747"}],["negative_squared_cross_mark",{"name":"negative_squared_cross_mark","unicode":"274e"}],["white_check_mark",{"name":"white_check_mark","unicode":"2705"}],["eight_pointed_black_star",{"name":"eight_pointed_black_star","unicode":"2734"}],["heart_decoration",{"name":"heart_decoration","unicode":"1f49f"}],["vs",{"name":"vs","unicode":"1f19a"}],["vibration_mode",{"name":"vibration_mode","unicode":"1f4f3"}],["mobile_phone_off",{"name":"mobile_phone_off","unicode":"1f4f4"}],["a",{"name":"a","unicode":"1f170"}],["b",{"name":"b","unicode":"1f171"}],["ab",{"name":"ab","unicode":"1f18e"}],["o2",{"name":"o2","unicode":"1f17e"}],["diamond_shape_with_a_dot_inside",{"name":"diamond_shape_with_a_dot_inside","unicode":"1f4a0"}],["loop",{"name":"loop","unicode":"27bf"}],["recycle",{"name":"recycle","unicode":"267b"}],["aries",{"name":"aries","unicode":"2648"}],["taurus",{"name":"taurus","unicode":"2649"}],["gemini",{"name":"gemini","unicode":"264a"}],["cancer",{"name":"cancer","unicode":"264b"}],["leo",{"name":"leo","unicode":"264c"}],["virgo",{"name":"virgo","unicode":"264d"}],["libra",{"name":"libra","unicode":"264e"}],["scorpius",{"name":"scorpius","unicode":"264f"}],["sagittarius",{"name":"sagittarius","unicode":"2650"}],["capricorn",{"name":"capricorn","unicode":"2651"}],["aquarius",{"name":"aquarius","unicode":"2652"}],["pisces",{"name":"pisces","unicode":"2653"}],["ophiuchus",{"name":"ophiuchus","unicode":"26ce"}],["six_pointed_star",{"name":"six_pointed_star","unicode":"1f52f"}],["atm",{"name":"atm","unicode":"1f3e7"}],["chart",{"name":"chart","unicode":"1f4b9"}],["heavy_dollar_sign",{"name":"heavy_dollar_sign","unicode":"1f4b2"}],["currency_exchange",{"name":"currency_exchange","unicode":"1f4b1"}],["copyright",{"name":"copyright","unicode":"00a9"}],["registered",{"name":"registered","unicode":"00ae"}],["tm",{"name":"tm","unicode":"2122"}],["x",{"name":"x","unicode":"274c"}],["bangbang",{"name":"bangbang","unicode":"203c"}],["interrobang",{"name":"interrobang","unicode":"2049"}],["exclamation",{"name":"exclamation","unicode":"2757"}],["heavy_exclamation_mark",{"name":"heavy_exclamation_mark","unicode":"2757"}],["question",{"name":"question","unicode":"2753"}],["grey_exclamation",{"name":"grey_exclamation","unicode":"2755"}],["grey_question",{"name":"grey_question","unicode":"2754"}],["o",{"name":"o","unicode":"2b55"}],["top",{"name":"top","unicode":"1f51d"}],["end",{"name":"end","unicode":"1f51a"}],["back",{"name":"back","unicode":"1f519"}],["on",{"name":"on","unicode":"1f51b"}],["soon",{"name":"soon","unicode":"1f51c"}],["arrows_clockwise",{"name":"arrows_clockwise","unicode":"1f503"}],["clock12",{"name":"clock12","unicode":"1f55b"}],["clock1230",{"name":"clock1230","unicode":"1f567"}],["clock1",{"name":"clock1","unicode":"1f550"}],["clock130",{"name":"clock130","unicode":"1f55c"}],["clock2",{"name":"clock2","unicode":"1f551"}],["clock230",{"name":"clock230","unicode":"1f55d"}],["clock3",{"name":"clock3","unicode":"1f552"}],["clock330",{"name":"clock330","unicode":"1f55e"}],["clock4",{"name":"clock4","unicode":"1f553"}],["clock430",{"name":"clock430","unicode":"1f55f"}],["clock5",{"name":"clock5","unicode":"1f554"}],["clock530",{"name":"clock530","unicode":"1f560"}],["clock6",{"name":"clock6","unicode":"1f555"}],["clock7",{"name":"clock7","unicode":"1f556"}],["clock8",{"name":"clock8","unicode":"1f557"}],["clock9",{"name":"clock9","unicode":"1f558"}],["clock10",{"name":"clock10","unicode":"1f559"}],["clock11",{"name":"clock11","unicode":"1f55a"}],["clock630",{"name":"clock630","unicode":"1f561"}],["clock730",{"name":"clock730","unicode":"1f562"}],["clock830",{"name":"clock830","unicode":"1f563"}],["clock930",{"name":"clock930","unicode":"1f564"}],["clock1030",{"name":"clock1030","unicode":"1f565"}],["clock1130",{"name":"clock1130","unicode":"1f566"}],["heavy_multiplication_x",{"name":"heavy_multiplication_x","unicode":"2716"}],["heavy_plus_sign",{"name":"heavy_plus_sign","unicode":"2795"}],["heavy_minus_sign",{"name":"heavy_minus_sign","unicode":"2796"}],["heavy_division_sign",{"name":"heavy_division_sign","unicode":"2797"}],["spades",{"name":"spades","unicode":"2660"}],["hearts",{"name":"hearts","unicode":"2665"}],["clubs",{"name":"clubs","unicode":"2663"}],["diamonds",{"name":"diamonds","unicode":"2666"}],["white_flower",{"name":"white_flower","unicode":"1f4ae"}],["100",{"name":"100","unicode":"1f4af"}],["heavy_check_mark",{"name":"heavy_check_mark","unicode":"2714"}],["ballot_box_with_check",{"name":"ballot_box_with_check","unicode":"2611"}],["radio_button",{"name":"radio_button","unicode":"1f518"}],["link",{"name":"link","unicode":"1f517"}],["curly_loop",{"name":"curly_loop","unicode":"27b0"}],["wavy_dash",{"name":"wavy_dash","unicode":"3030"}],["part_alternation_mark",{"name":"part_alternation_mark","unicode":"303d"}],["trident",{"name":"trident","unicode":"1f531"}],["black_medium_square",{"name":"black_medium_square","unicode":"25fc"}],["white_medium_square",{"name":"white_medium_square","unicode":"25fb"}],["black_medium_small_square",{"name":"black_medium_small_square","unicode":"25fe"}],["white_medium_small_square",{"name":"white_medium_small_square","unicode":"25fd"}],["black_small_square",{"name":"black_small_square","unicode":"25aa"}],["white_small_square",{"name":"white_small_square","unicode":"25ab"}],["small_red_triangle",{"name":"small_red_triangle","unicode":"1f53a"}],["black_square_button",{"name":"black_square_button","unicode":"1f532"}],["white_square_button",{"name":"white_square_button","unicode":"1f533"}],["black_circle",{"name":"black_circle","unicode":"26ab"}],["white_circle",{"name":"white_circle","unicode":"26aa"}],["red_circle",{"name":"red_circle","unicode":"1f534"}],["large_blue_circle",{"name":"large_blue_circle","unicode":"1f535"}],["small_red_triangle_down",{"name":"small_red_triangle_down","unicode":"1f53b"}],["white_large_square",{"name":"white_large_square","unicode":"2b1c"}],["black_large_square",{"name":"black_large_square","unicode":"2b1b"}],["large_orange_diamond",{"name":"large_orange_diamond","unicode":"1f536"}],["large_blue_diamond",{"name":"large_blue_diamond","unicode":"1f537"}],["small_orange_diamond",{"name":"small_orange_diamond","unicode":"1f538"}],["small_blue_diamond",{"name":"small_blue_diamond","unicode":"1f539"}],["ca",{"name":"ca","unicode":"1f1e8-1f1e6"}],["eh",{"name":"eh","unicode":"1f1e8-1f1e6"}],["pk",{"name":"pk","unicode":"1f1f5-1f1f0"}],["za",{"name":"za","unicode":"1f1ff-1f1e6"}],["slightly_smiling_face",{"name":"slightly_smiling_face","unicode":"1f642"}],["slightly_frowning_face",{"name":"slightly_frowning_face","unicode":"1f641"}],["upside_down_face",{"name":"upside_down_face","unicode":"1f643"}],["mm",{"name":"mm"}],["mattermost",{"name":"mattermost","filename":"mm"}],["basecamp",{"name":"basecamp"}],["basecampy",{"name":"basecampy"}],["bowtie",{"name":"bowtie"}],["feelsgood",{"name":"feelsgood"}],["finnadie",{"name":"finnadie"}],["fu",{"name":"fu"}],["goberserk",{"name":"goberserk"}],["godmode",{"name":"godmode"}],["hurtrealbad",{"name":"hurtrealbad"}],["metal",{"name":"metal"}],["neckbeard",{"name":"neckbeard"}],["octocat",{"name":"octocat"}],["rage1",{"name":"rage1"}],["rage2",{"name":"rage2"}],["rage3",{"name":"rage3"}],["rage4",{"name":"rage4"}],["shipit",{"name":"shipit"}],["squirrel",{"name":"squirrel","filename":"shipit"}],["suspect",{"name":"suspect"}],["taco",{"name":"taco"}],["trollface",{"name":"trollface"}]]
\ No newline at end of file diff --git a/webapp/utils/emoticons.jsx b/webapp/utils/emoticons.jsx index 1f3fdff8e..a7d6d84b1 100644 --- a/webapp/utils/emoticons.jsx +++ b/webapp/utils/emoticons.jsx @@ -1,8 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import Constants from './constants.jsx'; -import emojis from './emoji.json'; +import EmojiStore from 'stores/emoji_store.jsx'; export const emoticonPatterns = { slightly_smiling_face: /(^|\s)(:-?\))(?=$|\s)/g, // :) @@ -27,117 +26,17 @@ export const emoticonPatterns = { thumbsdown: /(^|\s)(:\-1:)(?=$|\s)/g // :-1: }; -let emoticonsByName; -let emoticonsByCodePoint; - -function initializeEmoticons() { - emoticonsByName = new Map(); - emoticonsByCodePoint = new Set(); - - for (const emoji of emojis) { - const unicode = emoji.emoji; - - let filename = ''; - if (unicode) { - // this is a unicode emoji so the character code determines the file name - let codepoint = ''; - - for (let i = 0; i < unicode.length; i += 2) { - const code = fixedCharCodeAt(unicode, i); - - // ignore variation selector characters - if (code >= 0xfe00 && code <= 0xfe0f) { - continue; - } - - // some emoji (such as country flags) span multiple unicode characters - if (i !== 0) { - codepoint += '-'; - } - - codepoint += pad(code.toString(16)); - } - - filename = codepoint; - emoticonsByCodePoint.add(codepoint); - } else { - // this isn't a unicode emoji so the first alias determines the file name - filename = emoji.aliases[0]; - } - - for (const alias of emoji.aliases) { - emoticonsByName.set(alias, { - alias, - path: getImagePathForEmoticon(filename) - }); - } - } -} - -// Pads a hexadecimal number with zeroes to be at least 4 digits long -function pad(n) { - if (n.length >= 4) { - return n; - } - - // http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript - return ('0000' + n).slice(-4); -} - -// Gets the unicode character code of a character starting at the given index in the string -// Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt -function fixedCharCodeAt(str, idx = 0) { - // ex. fixedCharCodeAt('\uD800\uDC00', 0); // 65536 - // ex. fixedCharCodeAt('\uD800\uDC00', 1); // false - const code = str.charCodeAt(idx); - - // High surrogate (could change last hex to 0xDB7F to treat high - // private surrogates as single characters) - if (code >= 0xD800 && code <= 0xDBFF) { - const hi = code; - const low = str.charCodeAt(idx + 1); - - if (isNaN(low)) { - console.log('High surrogate not followed by low surrogate in fixedCharCodeAt()'); // eslint-disable-line - } - - return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; - } - - if (code >= 0xDC00 && code <= 0xDFFF) { // Low surrogate - // We return false to allow loops to skip this iteration since should have - // already handled high surrogate above in the previous iteration - return false; - } - - return code; -} - -export function getEmoticonsByName() { - if (!emoticonsByName) { - initializeEmoticons(); - } - - return emoticonsByName; -} - -export function getEmoticonsByCodePoint() { - if (!emoticonsByCodePoint) { - initializeEmoticons(); - } - - return emoticonsByCodePoint; -} - -export function handleEmoticons(text, tokens) { +export function handleEmoticons(text, tokens, emojis) { let output = text; function replaceEmoticonWithToken(fullMatch, prefix, matchText, name) { - if (getEmoticonsByName().has(name)) { - const index = tokens.size; - const alias = `MM_EMOTICON${index}`; - const path = getEmoticonsByName().get(name).path; + const index = tokens.size; + const alias = `MM_EMOTICON${index}`; + + if (emojis.has(name)) { + const path = EmojiStore.getEmojiImageUrl(emojis.get(name)); + // we have an image path so we found a matching emoticon tokens.set(alias, { value: `<img align="absmiddle" alt="${matchText}" class="emoticon" src="${path}" title="${matchText}" />`, originalText: fullMatch @@ -163,7 +62,3 @@ export function handleEmoticons(text, tokens) { return output; } - -export function getImagePathForEmoticon(name) { - return Constants.EMOJI_PATH + '/' + name + '.png'; -} diff --git a/webapp/utils/text_formatting.jsx b/webapp/utils/text_formatting.jsx index 91be443fc..0b46edaeb 100644 --- a/webapp/utils/text_formatting.jsx +++ b/webapp/utils/text_formatting.jsx @@ -4,6 +4,7 @@ import Autolinker from 'autolinker'; import {browserHistory} from 'react-router/es6'; import Constants from './constants.jsx'; +import EmojiStore from 'stores/emoji_store.jsx'; import * as Emoticons from './emoticons.jsx'; import * as Markdown from './markdown.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; @@ -61,7 +62,7 @@ export function doFormatText(text, options) { output = autolinkHashtags(output, tokens); if (!('emoticons' in options) || options.emoticon) { - output = Emoticons.handleEmoticons(output, tokens); + output = Emoticons.handleEmoticons(output, tokens, options.emojis || EmojiStore.getEmojis()); } if (options.searchTerm) { @@ -75,15 +76,13 @@ export function doFormatText(text, options) { if (!('emoticons' in options) || options.emoticon) { output = twemoji.parse(output, { className: 'emoticon', - base: '', - folder: Constants.EMOJI_PATH, - callback: (icon, twemojiOptions) => { - if (!Emoticons.getEmoticonsByCodePoint().has(icon)) { + callback: (icon) => { + if (!EmojiStore.hasUnicode(icon)) { // just leave the unicode characters and hope the browser can handle it return null; } - return ''.concat(twemojiOptions.base, twemojiOptions.size, '/', icon, twemojiOptions.ext); + return EmojiStore.getEmojiImageUrl(EmojiStore.getUnicode(icon)); } }); } diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index 2792b144c..9fc342cb1 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -982,7 +982,10 @@ export function getDisplayName(user) { } export function displayUsername(userId) { - const user = UserStore.getProfile(userId); + return displayUsernameForUser(UserStore.getProfile(userId)); +} + +export function displayUsernameForUser(user) { const nameFormat = PreferenceStore.get(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false'); let username = ''; |