The code powering m.abunchtell.com https://m.abunchtell.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

327 lines
9.4 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { defineMessages, injectIntl } from 'react-intl';
  4. import { Picker, Emoji } from 'emoji-mart';
  5. import { Overlay } from 'react-overlays';
  6. import classNames from 'classnames';
  7. import ImmutablePropTypes from 'react-immutable-proptypes';
  8. const messages = defineMessages({
  9. emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
  10. emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
  11. emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' },
  12. custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
  13. recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
  14. search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
  15. people: { id: 'emoji_button.people', defaultMessage: 'People' },
  16. nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
  17. food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
  18. activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
  19. travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
  20. objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
  21. symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
  22. flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' },
  23. });
  24. const assetHost = process.env.CDN_HOST || '';
  25. const backgroundImageFn = () => `${assetHost}/emoji/sheet.png`;
  26. class ModifierPickerMenu extends React.PureComponent {
  27. static propTypes = {
  28. active: PropTypes.bool,
  29. onSelect: PropTypes.func.isRequired,
  30. onClose: PropTypes.func.isRequired,
  31. };
  32. handleClick = (e) => {
  33. const modifier = [].slice.call(e.currentTarget.parentNode.children).indexOf(e.target) + 1;
  34. this.props.onSelect(modifier);
  35. }
  36. componentWillReceiveProps (nextProps) {
  37. if (nextProps.active) {
  38. this.attachListeners();
  39. } else {
  40. this.removeListeners();
  41. }
  42. }
  43. componentWillUnmount () {
  44. this.removeListeners();
  45. }
  46. handleDocumentClick = e => {
  47. if (this.node && !this.node.contains(e.target)) {
  48. this.props.onClose();
  49. }
  50. }
  51. attachListeners () {
  52. document.addEventListener('click', this.handleDocumentClick, false);
  53. document.addEventListener('touchend', this.handleDocumentClick, false);
  54. }
  55. removeListeners () {
  56. document.removeEventListener('click', this.handleDocumentClick, false);
  57. document.removeEventListener('touchend', this.handleDocumentClick, false);
  58. }
  59. setRef = c => {
  60. this.node = c;
  61. }
  62. render () {
  63. const { active } = this.props;
  64. return (
  65. <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
  66. <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
  67. <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
  68. <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
  69. <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
  70. <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
  71. <button onClick={this.handleClick}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
  72. </div>
  73. );
  74. }
  75. }
  76. class ModifierPicker extends React.PureComponent {
  77. static propTypes = {
  78. active: PropTypes.bool,
  79. modifier: PropTypes.number,
  80. onChange: PropTypes.func,
  81. onClose: PropTypes.func,
  82. onOpen: PropTypes.func,
  83. };
  84. handleClick = () => {
  85. if (this.props.active) {
  86. this.props.onClose();
  87. } else {
  88. this.props.onOpen();
  89. }
  90. }
  91. handleSelect = modifier => {
  92. this.props.onChange(modifier);
  93. this.props.onClose();
  94. }
  95. render () {
  96. const { active, modifier } = this.props;
  97. return (
  98. <div className='emoji-picker-dropdown__modifiers'>
  99. <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
  100. <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
  101. </div>
  102. );
  103. }
  104. }
  105. @injectIntl
  106. class EmojiPickerMenu extends React.PureComponent {
  107. static propTypes = {
  108. custom_emojis: ImmutablePropTypes.list,
  109. onClose: PropTypes.func.isRequired,
  110. onPick: PropTypes.func.isRequired,
  111. style: PropTypes.object,
  112. placement: PropTypes.string,
  113. arrowOffsetLeft: PropTypes.string,
  114. arrowOffsetTop: PropTypes.string,
  115. intl: PropTypes.object.isRequired,
  116. };
  117. static defaultProps = {
  118. style: {},
  119. placement: 'bottom',
  120. };
  121. state = {
  122. modifierOpen: false,
  123. modifier: 1,
  124. };
  125. handleDocumentClick = e => {
  126. if (this.node && !this.node.contains(e.target)) {
  127. this.props.onClose();
  128. }
  129. }
  130. componentDidMount () {
  131. document.addEventListener('click', this.handleDocumentClick, false);
  132. document.addEventListener('touchend', this.handleDocumentClick, false);
  133. }
  134. componentWillUnmount () {
  135. document.removeEventListener('click', this.handleDocumentClick, false);
  136. document.removeEventListener('touchend', this.handleDocumentClick, false);
  137. }
  138. setRef = c => {
  139. this.node = c;
  140. }
  141. getI18n = () => {
  142. const { intl } = this.props;
  143. return {
  144. search: intl.formatMessage(messages.emoji_search),
  145. notfound: intl.formatMessage(messages.emoji_not_found),
  146. categories: {
  147. search: intl.formatMessage(messages.search_results),
  148. recent: intl.formatMessage(messages.recent),
  149. people: intl.formatMessage(messages.people),
  150. nature: intl.formatMessage(messages.nature),
  151. foods: intl.formatMessage(messages.food),
  152. activity: intl.formatMessage(messages.activity),
  153. places: intl.formatMessage(messages.travel),
  154. objects: intl.formatMessage(messages.objects),
  155. symbols: intl.formatMessage(messages.symbols),
  156. flags: intl.formatMessage(messages.flags),
  157. custom: intl.formatMessage(messages.custom),
  158. },
  159. };
  160. }
  161. handleClick = emoji => {
  162. if (!emoji.native) {
  163. emoji.native = emoji.colons;
  164. }
  165. this.props.onClose();
  166. this.props.onPick(emoji);
  167. }
  168. handleModifierOpen = () => {
  169. this.setState({ modifierOpen: true });
  170. }
  171. handleModifierClose = () => {
  172. this.setState({ modifierOpen: false });
  173. }
  174. handleModifierChange = modifier => {
  175. if (modifier !== this.state.modifier) {
  176. this.setState({ modifier });
  177. }
  178. }
  179. render () {
  180. const { style, intl } = this.props;
  181. const title = intl.formatMessage(messages.emoji);
  182. const { modifierOpen, modifier } = this.state;
  183. return (
  184. <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
  185. <Picker
  186. perLine={8}
  187. emojiSize={22}
  188. sheetSize={32}
  189. color=''
  190. emoji=''
  191. set='twitter'
  192. title={title}
  193. i18n={this.getI18n()}
  194. onClick={this.handleClick}
  195. skin={modifier}
  196. backgroundImageFn={backgroundImageFn}
  197. />
  198. <ModifierPicker
  199. active={modifierOpen}
  200. modifier={modifier}
  201. onOpen={this.handleModifierOpen}
  202. onClose={this.handleModifierClose}
  203. onChange={this.handleModifierChange}
  204. />
  205. </div>
  206. );
  207. }
  208. }
  209. @injectIntl
  210. export default class EmojiPickerDropdown extends React.PureComponent {
  211. static propTypes = {
  212. custom_emojis: ImmutablePropTypes.list,
  213. intl: PropTypes.object.isRequired,
  214. onPickEmoji: PropTypes.func.isRequired,
  215. };
  216. state = {
  217. active: false,
  218. };
  219. setRef = (c) => {
  220. this.dropdown = c;
  221. }
  222. onShowDropdown = () => {
  223. this.setState({ active: true });
  224. }
  225. onHideDropdown = () => {
  226. this.setState({ active: false });
  227. }
  228. onToggle = (e) => {
  229. if (!e.key || e.key === 'Enter') {
  230. if (this.state.active) {
  231. this.onHideDropdown();
  232. } else {
  233. this.onShowDropdown();
  234. }
  235. }
  236. }
  237. handleKeyDown = e => {
  238. if (e.key === 'Escape') {
  239. this.onHideDropdown();
  240. }
  241. }
  242. setTargetRef = c => {
  243. this.target = c;
  244. }
  245. findTarget = () => {
  246. return this.target;
  247. }
  248. render () {
  249. const { intl, onPickEmoji } = this.props;
  250. const title = intl.formatMessage(messages.emoji);
  251. const { active } = this.state;
  252. return (
  253. <div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
  254. <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}>
  255. <img
  256. className='emojione'
  257. alt='🙂'
  258. src={`${assetHost}/emoji/1f602.svg`}
  259. />
  260. </div>
  261. <Overlay show={active} placement='bottom' target={this.findTarget}>
  262. <EmojiPickerMenu
  263. custom_emojis={this.props.custom_emojis}
  264. onClose={this.onHideDropdown}
  265. onPick={onPickEmoji}
  266. />
  267. </Overlay>
  268. </div>
  269. );
  270. }
  271. }