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.
 
 
 
 

234 lines
7.7 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import Avatar from '../../../components/avatar';
  5. import DisplayName from '../../../components/display_name';
  6. import StatusContent from '../../../components/status_content';
  7. import MediaGallery from '../../../components/media_gallery';
  8. import { Link } from 'react-router-dom';
  9. import { FormattedDate } from 'react-intl';
  10. import Card from './card';
  11. import ImmutablePureComponent from 'react-immutable-pure-component';
  12. import Video from '../../video';
  13. import Audio from '../../audio';
  14. import scheduleIdleTask from '../../ui/util/schedule_idle_task';
  15. import classNames from 'classnames';
  16. import Icon from 'mastodon/components/icon';
  17. import AnimatedNumber from 'mastodon/components/animated_number';
  18. export default class DetailedStatus extends ImmutablePureComponent {
  19. static contextTypes = {
  20. router: PropTypes.object,
  21. };
  22. static propTypes = {
  23. status: ImmutablePropTypes.map,
  24. onOpenMedia: PropTypes.func.isRequired,
  25. onOpenVideo: PropTypes.func.isRequired,
  26. onToggleHidden: PropTypes.func.isRequired,
  27. measureHeight: PropTypes.bool,
  28. onHeightChange: PropTypes.func,
  29. domain: PropTypes.string.isRequired,
  30. compact: PropTypes.bool,
  31. showMedia: PropTypes.bool,
  32. onToggleMediaVisibility: PropTypes.func,
  33. };
  34. state = {
  35. height: null,
  36. };
  37. handleAccountClick = (e) => {
  38. if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
  39. e.preventDefault();
  40. this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
  41. }
  42. e.stopPropagation();
  43. }
  44. handleOpenVideo = (media, startTime) => {
  45. this.props.onOpenVideo(media, startTime);
  46. }
  47. handleExpandedToggle = () => {
  48. this.props.onToggleHidden(this.props.status);
  49. }
  50. _measureHeight (heightJustChanged) {
  51. if (this.props.measureHeight && this.node) {
  52. scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 }));
  53. if (this.props.onHeightChange && heightJustChanged) {
  54. this.props.onHeightChange();
  55. }
  56. }
  57. }
  58. setRef = c => {
  59. this.node = c;
  60. this._measureHeight();
  61. }
  62. componentDidUpdate (prevProps, prevState) {
  63. this._measureHeight(prevState.height !== this.state.height);
  64. }
  65. handleModalLink = e => {
  66. e.preventDefault();
  67. let href;
  68. if (e.target.nodeName !== 'A') {
  69. href = e.target.parentNode.href;
  70. } else {
  71. href = e.target.href;
  72. }
  73. window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
  74. }
  75. render () {
  76. const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
  77. const outerStyle = { boxSizing: 'border-box' };
  78. const { compact } = this.props;
  79. if (!status) {
  80. return null;
  81. }
  82. let media = '';
  83. let applicationLink = '';
  84. let reblogLink = '';
  85. let reblogIcon = 'retweet';
  86. let favouriteLink = '';
  87. if (this.props.measureHeight) {
  88. outerStyle.height = `${this.state.height}px`;
  89. }
  90. if (status.get('media_attachments').size > 0) {
  91. if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
  92. const attachment = status.getIn(['media_attachments', 0]);
  93. media = (
  94. <Audio
  95. src={attachment.get('url')}
  96. alt={attachment.get('description')}
  97. duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
  98. height={110}
  99. preload
  100. />
  101. );
  102. } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
  103. const attachment = status.getIn(['media_attachments', 0]);
  104. media = (
  105. <Video
  106. preview={attachment.get('preview_url')}
  107. blurhash={attachment.get('blurhash')}
  108. src={attachment.get('url')}
  109. alt={attachment.get('description')}
  110. width={300}
  111. height={150}
  112. inline
  113. onOpenVideo={this.handleOpenVideo}
  114. sensitive={status.get('sensitive')}
  115. visible={this.props.showMedia}
  116. onToggleVisibility={this.props.onToggleMediaVisibility}
  117. />
  118. );
  119. } else {
  120. media = (
  121. <MediaGallery
  122. standalone
  123. sensitive={status.get('sensitive')}
  124. media={status.get('media_attachments')}
  125. height={300}
  126. onOpenMedia={this.props.onOpenMedia}
  127. visible={this.props.showMedia}
  128. onToggleVisibility={this.props.onToggleMediaVisibility}
  129. />
  130. );
  131. }
  132. } else if (status.get('spoiler_text').length === 0) {
  133. media = <Card onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />;
  134. }
  135. if (status.get('application')) {
  136. applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></span>;
  137. }
  138. if (status.get('visibility') === 'direct') {
  139. reblogIcon = 'envelope';
  140. } else if (status.get('visibility') === 'private') {
  141. reblogIcon = 'lock';
  142. }
  143. if (status.get('visibility') === 'private') {
  144. reblogLink = <Icon id={reblogIcon} />;
  145. } else if (this.context.router) {
  146. reblogLink = (
  147. <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
  148. <Icon id={reblogIcon} />
  149. <span className='detailed-status__reblogs'>
  150. <AnimatedNumber value={status.get('reblogs_count')} />
  151. </span>
  152. </Link>
  153. );
  154. } else {
  155. reblogLink = (
  156. <a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
  157. <Icon id={reblogIcon} />
  158. <span className='detailed-status__reblogs'>
  159. <AnimatedNumber value={status.get('reblogs_count')} />
  160. </span>
  161. </a>
  162. );
  163. }
  164. if (this.context.router) {
  165. favouriteLink = (
  166. <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
  167. <Icon id='star' />
  168. <span className='detailed-status__favorites'>
  169. <AnimatedNumber value={status.get('favourites_count')} />
  170. </span>
  171. </Link>
  172. );
  173. } else {
  174. favouriteLink = (
  175. <a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
  176. <Icon id='star' />
  177. <span className='detailed-status__favorites'>
  178. <AnimatedNumber value={status.get('favourites_count')} />
  179. </span>
  180. </a>
  181. );
  182. }
  183. return (
  184. <div style={outerStyle}>
  185. <div ref={this.setRef} className={classNames('detailed-status', { compact })}>
  186. <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
  187. <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
  188. <DisplayName account={status.get('account')} localDomain={this.props.domain} />
  189. </a>
  190. <StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
  191. {media}
  192. <div className='detailed-status__meta'>
  193. <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'>
  194. <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
  195. </a>{applicationLink} · {reblogLink} · {favouriteLink}
  196. </div>
  197. </div>
  198. </div>
  199. );
  200. }
  201. }