From 66126f302167d21e4bf247e660f595ff0beaaf20 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 23 Sep 2017 05:40:28 +0200 Subject: [PATCH] Add custom emojis to the emoji picker (#5052) --- app/javascript/mastodon/emoji.js | 40 +++++++++++++++++++ .../compose/components/compose_form.js | 2 +- .../components/emoji_picker_dropdown.js | 16 +++++++- .../emoji_picker_dropdown_container.js | 8 ++++ .../mastodon/reducers/custom_emojis.js | 13 ++++++ app/javascript/mastodon/reducers/index.js | 2 + app/javascript/styles/components.scss | 6 +++ app/serializers/initial_state_serializer.rb | 6 +++ 8 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js create mode 100644 app/javascript/mastodon/reducers/custom_emojis.js diff --git a/app/javascript/mastodon/emoji.js b/app/javascript/mastodon/emoji.js index 407b21b4b38..39123768a0d 100644 --- a/app/javascript/mastodon/emoji.js +++ b/app/javascript/mastodon/emoji.js @@ -47,3 +47,43 @@ const emojify = (str, customEmojis = {}) => { }; export default emojify; + +export const toCodePoint = (unicodeSurrogates, sep = '-') => { + let r = [], c = 0, p = 0, i = 0; + + while (i < unicodeSurrogates.length) { + c = unicodeSurrogates.charCodeAt(i++); + + if (p) { + r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16)); + p = 0; + } else if (0xD800 <= c && c <= 0xDBFF) { + p = c; + } else { + r.push(c.toString(16)); + } + } + + return r.join(sep); +}; + +export const buildCustomEmojis = customEmojis => { + const emojis = []; + + customEmojis.forEach(emoji => { + const shortcode = emoji.get('shortcode'); + const url = emoji.get('url'); + const name = shortcode.replace(':', ''); + + emojis.push({ + name, + short_names: [name], + text: '', + emoticons: [], + keywords: [name], + imageUrl: url, + }); + }); + + return emojis; +}; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index bb747b61134..d3104106115 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -12,7 +12,7 @@ import Collapsable from '../../../components/collapsable'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; -import EmojiPickerDropdown from './emoji_picker_dropdown'; +import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import UploadFormContainer from '../containers/upload_form_container'; import WarningContainer from '../containers/warning_container'; import { isMobile } from '../../../is_mobile'; diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index 43e175be50b..f55d59e038f 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -4,6 +4,8 @@ import { defineMessages, injectIntl } from 'react-intl'; import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; import { Overlay } from 'react-overlays'; import classNames from 'classnames'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { buildCustomEmojis } from '../../../emoji'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, @@ -130,6 +132,7 @@ class ModifierPicker extends React.PureComponent { class EmojiPickerMenu extends React.PureComponent { static propTypes = { + custom_emojis: ImmutablePropTypes.list, loading: PropTypes.bool, onClose: PropTypes.func.isRequired, onPick: PropTypes.func.isRequired, @@ -194,6 +197,10 @@ class EmojiPickerMenu extends React.PureComponent { } handleClick = emoji => { + if (!emoji.native) { + emoji.native = emoji.colons; + } + this.props.onClose(); this.props.onPick(emoji); } @@ -225,6 +232,7 @@ class EmojiPickerMenu extends React.PureComponent { return (
- +
); diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js new file mode 100644 index 00000000000..7a8026bbc66 --- /dev/null +++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js @@ -0,0 +1,8 @@ +import { connect } from 'react-redux'; +import EmojiPickerDropdown from '../components/emoji_picker_dropdown'; + +const mapStateToProps = state => ({ + custom_emojis: state.get('custom_emojis'), +}); + +export default connect(mapStateToProps)(EmojiPickerDropdown); diff --git a/app/javascript/mastodon/reducers/custom_emojis.js b/app/javascript/mastodon/reducers/custom_emojis.js new file mode 100644 index 00000000000..15bba7bccd3 --- /dev/null +++ b/app/javascript/mastodon/reducers/custom_emojis.js @@ -0,0 +1,13 @@ +import { List as ImmutableList } from 'immutable'; +import { STORE_HYDRATE } from '../actions/store'; + +const initialState = ImmutableList(); + +export default function statuses(state = initialState, action) { + switch(action.type) { + case STORE_HYDRATE: + return action.state.get('custom_emojis'); + default: + return state; + } +}; diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index 0a8c3ce6c43..e6514487118 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -20,6 +20,7 @@ import search from './search'; import media_attachments from './media_attachments'; import notifications from './notifications'; import height_cache from './height_cache'; +import custom_emojis from './custom_emojis'; const reducers = { timelines, @@ -43,6 +44,7 @@ const reducers = { media_attachments, notifications, height_cache, + custom_emojis, }; export default combineReducers(reducers); diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index abf5cfd5bc2..595ab365894 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -2629,6 +2629,12 @@ button.icon-button.active i.fa-retweet { } } +.emoji-mart-emoji { + span { + background-repeat: no-repeat; + } +} + .upload-area { align-items: center; background: rgba($base-overlay-background, 0.8); diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 88bbc0dff3c..e2f15a60100 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -4,6 +4,12 @@ class InitialStateSerializer < ActiveModel::Serializer attributes :meta, :compose, :accounts, :media_attachments, :settings, :push_subscription + has_many :custom_emojis, serializer: REST::CustomEmojiSerializer + + def custom_emojis + CustomEmoji.local + end + def meta store = { streaming_api_base_url: Rails.configuration.x.streaming_api_base_url,