hashtag.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. // @ts-check
  2. import React from 'react';
  3. import { Sparklines, SparklinesCurve } from 'react-sparklines';
  4. import { FormattedMessage } from 'react-intl';
  5. import PropTypes from 'prop-types';
  6. import ImmutablePropTypes from 'react-immutable-proptypes';
  7. import Permalink from './permalink';
  8. import ShortNumber from 'mastodon/components/short_number';
  9. import Skeleton from 'mastodon/components/skeleton';
  10. import classNames from 'classnames';
  11. class SilentErrorBoundary extends React.Component {
  12. static propTypes = {
  13. children: PropTypes.node,
  14. };
  15. state = {
  16. error: false,
  17. };
  18. componentDidCatch () {
  19. this.setState({ error: true });
  20. }
  21. render () {
  22. if (this.state.error) {
  23. return null;
  24. }
  25. return this.props.children;
  26. }
  27. }
  28. /**
  29. * Used to render counter of how much people are talking about hashtag
  30. *
  31. * @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
  32. */
  33. export const accountsCountRenderer = (displayNumber, pluralReady) => (
  34. <FormattedMessage
  35. id='trends.counter_by_accounts'
  36. defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}'
  37. values={{
  38. count: pluralReady,
  39. counter: <strong>{displayNumber}</strong>,
  40. days: 2,
  41. }}
  42. />
  43. );
  44. export const ImmutableHashtag = ({ hashtag }) => (
  45. <Hashtag
  46. name={hashtag.get('name')}
  47. href={hashtag.get('url')}
  48. to={`/tags/${hashtag.get('name')}`}
  49. people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
  50. history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
  51. />
  52. );
  53. ImmutableHashtag.propTypes = {
  54. hashtag: ImmutablePropTypes.map.isRequired,
  55. };
  56. const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => (
  57. <div className={classNames('trends__item', className)}>
  58. <div className='trends__item__name'>
  59. <Permalink href={href} to={to}>
  60. {name ? <React.Fragment>#<span>{name}</span></React.Fragment> : <Skeleton width={50} />}
  61. </Permalink>
  62. {description ? (
  63. <span>{description}</span>
  64. ) : (
  65. typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
  66. )}
  67. </div>
  68. {typeof uses !== 'undefined' && (
  69. <div className='trends__item__current'>
  70. <ShortNumber value={uses} />
  71. </div>
  72. )}
  73. {withGraph && (
  74. <div className='trends__item__sparkline'>
  75. <SilentErrorBoundary>
  76. <Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
  77. <SparklinesCurve style={{ fill: 'none' }} />
  78. </Sparklines>
  79. </SilentErrorBoundary>
  80. </div>
  81. )}
  82. </div>
  83. );
  84. Hashtag.propTypes = {
  85. name: PropTypes.string,
  86. href: PropTypes.string,
  87. to: PropTypes.string,
  88. people: PropTypes.number,
  89. description: PropTypes.node,
  90. uses: PropTypes.number,
  91. history: PropTypes.arrayOf(PropTypes.number),
  92. className: PropTypes.string,
  93. withGraph: PropTypes.bool,
  94. };
  95. Hashtag.defaultProps = {
  96. withGraph: true,
  97. };
  98. export default Hashtag;