Add custom emojis to the emoji picker (#5052)

lolsob-rspec
Eugen Rochko 2017-09-23 05:40:28 +02:00 committed by GitHub
parent f621e05356
commit 4a0212bf2f
8 changed files with 91 additions and 2 deletions

View File

@ -47,3 +47,43 @@ const emojify = (str, customEmojis = {}) => {
}; };
export default emojify; 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;
};

View File

@ -12,7 +12,7 @@ import Collapsable from '../../../components/collapsable';
import SpoilerButtonContainer from '../containers/spoiler_button_container'; import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import SensitiveButtonContainer from '../containers/sensitive_button_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 UploadFormContainer from '../containers/upload_form_container';
import WarningContainer from '../containers/warning_container'; import WarningContainer from '../containers/warning_container';
import { isMobile } from '../../../is_mobile'; import { isMobile } from '../../../is_mobile';

View File

@ -4,6 +4,8 @@ import { defineMessages, injectIntl } from 'react-intl';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
import { Overlay } from 'react-overlays'; import { Overlay } from 'react-overlays';
import classNames from 'classnames'; import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { buildCustomEmojis } from '../../../emoji';
const messages = defineMessages({ const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
@ -130,6 +132,7 @@ class ModifierPicker extends React.PureComponent {
class EmojiPickerMenu extends React.PureComponent { class EmojiPickerMenu extends React.PureComponent {
static propTypes = { static propTypes = {
custom_emojis: ImmutablePropTypes.list,
loading: PropTypes.bool, loading: PropTypes.bool,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
onPick: PropTypes.func.isRequired, onPick: PropTypes.func.isRequired,
@ -194,6 +197,10 @@ class EmojiPickerMenu extends React.PureComponent {
} }
handleClick = emoji => { handleClick = emoji => {
if (!emoji.native) {
emoji.native = emoji.colons;
}
this.props.onClose(); this.props.onClose();
this.props.onPick(emoji); this.props.onPick(emoji);
} }
@ -225,6 +232,7 @@ class EmojiPickerMenu extends React.PureComponent {
return ( return (
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}> <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
<EmojiPicker <EmojiPicker
custom={buildCustomEmojis(this.props.custom_emojis)}
perLine={8} perLine={8}
emojiSize={22} emojiSize={22}
sheetSize={32} sheetSize={32}
@ -255,6 +263,7 @@ class EmojiPickerMenu extends React.PureComponent {
export default class EmojiPickerDropdown extends React.PureComponent { export default class EmojiPickerDropdown extends React.PureComponent {
static propTypes = { static propTypes = {
custom_emojis: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
onPickEmoji: PropTypes.func.isRequired, onPickEmoji: PropTypes.func.isRequired,
}; };
@ -328,7 +337,12 @@ export default class EmojiPickerDropdown extends React.PureComponent {
</div> </div>
<Overlay show={active} placement='bottom' target={this.findTarget}> <Overlay show={active} placement='bottom' target={this.findTarget}>
<EmojiPickerMenu loading={loading} onClose={this.onHideDropdown} onPick={onPickEmoji} /> <EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
/>
</Overlay> </Overlay>
</div> </div>
); );

View File

@ -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);

View File

@ -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;
}
};

View File

@ -20,6 +20,7 @@ import search from './search';
import media_attachments from './media_attachments'; import media_attachments from './media_attachments';
import notifications from './notifications'; import notifications from './notifications';
import height_cache from './height_cache'; import height_cache from './height_cache';
import custom_emojis from './custom_emojis';
const reducers = { const reducers = {
timelines, timelines,
@ -43,6 +44,7 @@ const reducers = {
media_attachments, media_attachments,
notifications, notifications,
height_cache, height_cache,
custom_emojis,
}; };
export default combineReducers(reducers); export default combineReducers(reducers);

View File

@ -2629,6 +2629,12 @@ button.icon-button.active i.fa-retweet {
} }
} }
.emoji-mart-emoji {
span {
background-repeat: no-repeat;
}
}
.upload-area { .upload-area {
align-items: center; align-items: center;
background: rgba($base-overlay-background, 0.8); background: rgba($base-overlay-background, 0.8);

View File

@ -4,6 +4,12 @@ class InitialStateSerializer < ActiveModel::Serializer
attributes :meta, :compose, :accounts, attributes :meta, :compose, :accounts,
:media_attachments, :settings, :push_subscription :media_attachments, :settings, :push_subscription
has_many :custom_emojis, serializer: REST::CustomEmojiSerializer
def custom_emojis
CustomEmoji.local
end
def meta def meta
store = { store = {
streaming_api_base_url: Rails.configuration.x.streaming_api_base_url, streaming_api_base_url: Rails.configuration.x.streaming_api_base_url,