Add custom emojis to the emoji picker (#5052)
This commit is contained in:
		
							parent
							
								
									293972f716
								
							
						
					
					
						commit
						66126f3021
					
				
					 8 changed files with 91 additions and 2 deletions
				
			
		| 
						 | 
				
			
			@ -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;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 (
 | 
			
		||||
      <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
 | 
			
		||||
        <EmojiPicker
 | 
			
		||||
          custom={buildCustomEmojis(this.props.custom_emojis)}
 | 
			
		||||
          perLine={8}
 | 
			
		||||
          emojiSize={22}
 | 
			
		||||
          sheetSize={32}
 | 
			
		||||
| 
						 | 
				
			
			@ -255,6 +263,7 @@ class EmojiPickerMenu extends React.PureComponent {
 | 
			
		|||
export default class EmojiPickerDropdown extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    custom_emojis: ImmutablePropTypes.list,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    onPickEmoji: PropTypes.func.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
| 
						 | 
				
			
			@ -328,7 +337,12 @@ export default class EmojiPickerDropdown extends React.PureComponent {
 | 
			
		|||
        </div>
 | 
			
		||||
 | 
			
		||||
        <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>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
							
								
								
									
										13
									
								
								app/javascript/mastodon/reducers/custom_emojis.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/javascript/mastodon/reducers/custom_emojis.js
									
										
									
									
									
										Normal 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;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Reference in a new issue