328 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import React from 'react';
 | 
						|
import PropTypes from 'prop-types';
 | 
						|
import { defineMessages, injectIntl } from 'react-intl';
 | 
						|
import { Picker, Emoji } from 'emoji-mart';
 | 
						|
import { Overlay } from 'react-overlays';
 | 
						|
import classNames from 'classnames';
 | 
						|
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
						|
import detectPassiveEvents from 'detect-passive-events';
 | 
						|
 | 
						|
const messages = defineMessages({
 | 
						|
  emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
 | 
						|
  emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
 | 
						|
  emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' },
 | 
						|
  custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
 | 
						|
  recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
 | 
						|
  search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
 | 
						|
  people: { id: 'emoji_button.people', defaultMessage: 'People' },
 | 
						|
  nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
 | 
						|
  food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
 | 
						|
  activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
 | 
						|
  travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
 | 
						|
  objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
 | 
						|
  symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
 | 
						|
  flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
 | 
						|
});
 | 
						|
 | 
						|
const assetHost = process.env.CDN_HOST || '';
 | 
						|
const backgroundImageFn = () => `${assetHost}/emoji/sheet.png`;
 | 
						|
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
 | 
						|
 | 
						|
class ModifierPickerMenu extends React.PureComponent {
 | 
						|
 | 
						|
  static propTypes = {
 | 
						|
    active: PropTypes.bool,
 | 
						|
    onSelect: PropTypes.func.isRequired,
 | 
						|
    onClose: PropTypes.func.isRequired,
 | 
						|
  };
 | 
						|
 | 
						|
  handleClick = (e) => {
 | 
						|
    const modifier = [].slice.call(e.currentTarget.parentNode.children).indexOf(e.target) + 1;
 | 
						|
    this.props.onSelect(modifier);
 | 
						|
  }
 | 
						|
 | 
						|
  componentWillReceiveProps (nextProps) {
 | 
						|
    if (nextProps.active) {
 | 
						|
      this.attachListeners();
 | 
						|
    } else {
 | 
						|
      this.removeListeners();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  componentWillUnmount () {
 | 
						|
    this.removeListeners();
 | 
						|
  }
 | 
						|
 | 
						|
  handleDocumentClick = e => {
 | 
						|
    if (this.node && !this.node.contains(e.target)) {
 | 
						|
      this.props.onClose();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  attachListeners () {
 | 
						|
    document.addEventListener('click', this.handleDocumentClick, false);
 | 
						|
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
						|
  }
 | 
						|
 | 
						|
  removeListeners () {
 | 
						|
    document.removeEventListener('click', this.handleDocumentClick, false);
 | 
						|
    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
						|
  }
 | 
						|
 | 
						|
  setRef = c => {
 | 
						|
    this.node = c;
 | 
						|
  }
 | 
						|
 | 
						|
  render () {
 | 
						|
    const { active } = this.props;
 | 
						|
 | 
						|
    return (
 | 
						|
      <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
 | 
						|
        <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
 | 
						|
        <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
 | 
						|
        <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
 | 
						|
        <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
 | 
						|
        <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
 | 
						|
        <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
 | 
						|
      </div>
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
class ModifierPicker extends React.PureComponent {
 | 
						|
 | 
						|
  static propTypes = {
 | 
						|
    active: PropTypes.bool,
 | 
						|
    modifier: PropTypes.number,
 | 
						|
    onChange: PropTypes.func,
 | 
						|
    onClose: PropTypes.func,
 | 
						|
    onOpen: PropTypes.func,
 | 
						|
  };
 | 
						|
 | 
						|
  handleClick = () => {
 | 
						|
    if (this.props.active) {
 | 
						|
      this.props.onClose();
 | 
						|
    } else {
 | 
						|
      this.props.onOpen();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  handleSelect = modifier => {
 | 
						|
    this.props.onChange(modifier);
 | 
						|
    this.props.onClose();
 | 
						|
  }
 | 
						|
 | 
						|
  render () {
 | 
						|
    const { active, modifier } = this.props;
 | 
						|
 | 
						|
    return (
 | 
						|
      <div className='emoji-picker-dropdown__modifiers'>
 | 
						|
        <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
 | 
						|
        <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
 | 
						|
      </div>
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@injectIntl
 | 
						|
class EmojiPickerMenu extends React.PureComponent {
 | 
						|
 | 
						|
  static propTypes = {
 | 
						|
    custom_emojis: ImmutablePropTypes.list,
 | 
						|
    onClose: PropTypes.func.isRequired,
 | 
						|
    onPick: PropTypes.func.isRequired,
 | 
						|
    style: PropTypes.object,
 | 
						|
    placement: PropTypes.string,
 | 
						|
    arrowOffsetLeft: PropTypes.string,
 | 
						|
    arrowOffsetTop: PropTypes.string,
 | 
						|
    intl: PropTypes.object.isRequired,
 | 
						|
  };
 | 
						|
 | 
						|
  static defaultProps = {
 | 
						|
    style: {},
 | 
						|
    placement: 'bottom',
 | 
						|
  };
 | 
						|
 | 
						|
  state = {
 | 
						|
    modifierOpen: false,
 | 
						|
    modifier: 1,
 | 
						|
  };
 | 
						|
 | 
						|
  handleDocumentClick = e => {
 | 
						|
    if (this.node && !this.node.contains(e.target)) {
 | 
						|
      this.props.onClose();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  componentDidMount () {
 | 
						|
    document.addEventListener('click', this.handleDocumentClick, false);
 | 
						|
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
						|
  }
 | 
						|
 | 
						|
  componentWillUnmount () {
 | 
						|
    document.removeEventListener('click', this.handleDocumentClick, false);
 | 
						|
    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
						|
  }
 | 
						|
 | 
						|
  setRef = c => {
 | 
						|
    this.node = c;
 | 
						|
  }
 | 
						|
 | 
						|
  getI18n = () => {
 | 
						|
    const { intl } = this.props;
 | 
						|
 | 
						|
    return {
 | 
						|
      search: intl.formatMessage(messages.emoji_search),
 | 
						|
      notfound: intl.formatMessage(messages.emoji_not_found),
 | 
						|
      categories: {
 | 
						|
        search: intl.formatMessage(messages.search_results),
 | 
						|
        recent: intl.formatMessage(messages.recent),
 | 
						|
        people: intl.formatMessage(messages.people),
 | 
						|
        nature: intl.formatMessage(messages.nature),
 | 
						|
        foods: intl.formatMessage(messages.food),
 | 
						|
        activity: intl.formatMessage(messages.activity),
 | 
						|
        places: intl.formatMessage(messages.travel),
 | 
						|
        objects: intl.formatMessage(messages.objects),
 | 
						|
        symbols: intl.formatMessage(messages.symbols),
 | 
						|
        flags: intl.formatMessage(messages.flags),
 | 
						|
        custom: intl.formatMessage(messages.custom),
 | 
						|
      },
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  handleClick = emoji => {
 | 
						|
    if (!emoji.native) {
 | 
						|
      emoji.native = emoji.colons;
 | 
						|
    }
 | 
						|
 | 
						|
    this.props.onClose();
 | 
						|
    this.props.onPick(emoji);
 | 
						|
  }
 | 
						|
 | 
						|
  handleModifierOpen = () => {
 | 
						|
    this.setState({ modifierOpen: true });
 | 
						|
  }
 | 
						|
 | 
						|
  handleModifierClose = () => {
 | 
						|
    this.setState({ modifierOpen: false });
 | 
						|
  }
 | 
						|
 | 
						|
  handleModifierChange = modifier => {
 | 
						|
    if (modifier !== this.state.modifier) {
 | 
						|
      this.setState({ modifier });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  render () {
 | 
						|
    const { style, intl } = this.props;
 | 
						|
    const title = intl.formatMessage(messages.emoji);
 | 
						|
    const { modifierOpen, modifier } = this.state;
 | 
						|
 | 
						|
    return (
 | 
						|
      <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
 | 
						|
        <Picker
 | 
						|
          perLine={8}
 | 
						|
          emojiSize={22}
 | 
						|
          sheetSize={32}
 | 
						|
          color=''
 | 
						|
          emoji=''
 | 
						|
          set='twitter'
 | 
						|
          title={title}
 | 
						|
          i18n={this.getI18n()}
 | 
						|
          onClick={this.handleClick}
 | 
						|
          skin={modifier}
 | 
						|
          backgroundImageFn={backgroundImageFn}
 | 
						|
        />
 | 
						|
 | 
						|
        <ModifierPicker
 | 
						|
          active={modifierOpen}
 | 
						|
          modifier={modifier}
 | 
						|
          onOpen={this.handleModifierOpen}
 | 
						|
          onClose={this.handleModifierClose}
 | 
						|
          onChange={this.handleModifierChange}
 | 
						|
        />
 | 
						|
      </div>
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
@injectIntl
 | 
						|
export default class EmojiPickerDropdown extends React.PureComponent {
 | 
						|
 | 
						|
  static propTypes = {
 | 
						|
    custom_emojis: ImmutablePropTypes.list,
 | 
						|
    intl: PropTypes.object.isRequired,
 | 
						|
    onPickEmoji: PropTypes.func.isRequired,
 | 
						|
  };
 | 
						|
 | 
						|
  state = {
 | 
						|
    active: false,
 | 
						|
  };
 | 
						|
 | 
						|
  setRef = (c) => {
 | 
						|
    this.dropdown = c;
 | 
						|
  }
 | 
						|
 | 
						|
  onShowDropdown = () => {
 | 
						|
    this.setState({ active: true });
 | 
						|
  }
 | 
						|
 | 
						|
  onHideDropdown = () => {
 | 
						|
    this.setState({ active: false });
 | 
						|
  }
 | 
						|
 | 
						|
  onToggle = (e) => {
 | 
						|
    if (!e.key || e.key === 'Enter') {
 | 
						|
      if (this.state.active) {
 | 
						|
        this.onHideDropdown();
 | 
						|
      } else {
 | 
						|
        this.onShowDropdown();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  handleKeyDown = e => {
 | 
						|
    if (e.key === 'Escape') {
 | 
						|
      this.onHideDropdown();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  setTargetRef = c => {
 | 
						|
    this.target = c;
 | 
						|
  }
 | 
						|
 | 
						|
  findTarget = () => {
 | 
						|
    return this.target;
 | 
						|
  }
 | 
						|
 | 
						|
  render () {
 | 
						|
    const { intl, onPickEmoji } = this.props;
 | 
						|
    const title = intl.formatMessage(messages.emoji);
 | 
						|
    const { active } = this.state;
 | 
						|
 | 
						|
    return (
 | 
						|
      <div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
 | 
						|
        <div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
 | 
						|
          <img
 | 
						|
            className='emojione'
 | 
						|
            alt='🙂'
 | 
						|
            src={`${assetHost}/emoji/1f602.svg`}
 | 
						|
          />
 | 
						|
        </div>
 | 
						|
 | 
						|
        <Overlay show={active} placement='bottom' target={this.findTarget}>
 | 
						|
          <EmojiPickerMenu
 | 
						|
            custom_emojis={this.props.custom_emojis}
 | 
						|
            onClose={this.onHideDropdown}
 | 
						|
            onPick={onPickEmoji}
 | 
						|
          />
 | 
						|
        </Overlay>
 | 
						|
      </div>
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
}
 |