Change embedded posts to use web UI (#31766)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
		
					parent
					
						
							
								f2a92c2d22
							
						
					
				
			
			
				commit
				
					
						3d46f47817
					
				
			
		
					 115 changed files with 710 additions and 1928 deletions
				
			
		|  | @ -1,322 +0,0 @@ | |||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import { FormattedDate, FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| import classNames from 'classnames'; | ||||
| import { Link, withRouter } from 'react-router-dom'; | ||||
| 
 | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; | ||||
| import { AnimatedNumber } from 'mastodon/components/animated_number'; | ||||
| import { ContentWarning } from 'mastodon/components/content_warning'; | ||||
| import EditedTimestamp from 'mastodon/components/edited_timestamp'; | ||||
| import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar'; | ||||
| import { Icon }  from 'mastodon/components/icon'; | ||||
| import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder'; | ||||
| import { VisibilityIcon } from 'mastodon/components/visibility_icon'; | ||||
| import { WithRouterPropTypes } from 'mastodon/utils/react_router'; | ||||
| 
 | ||||
| import { Avatar } from '../../../components/avatar'; | ||||
| import { DisplayName } from '../../../components/display_name'; | ||||
| import MediaGallery from '../../../components/media_gallery'; | ||||
| import StatusContent from '../../../components/status_content'; | ||||
| import Audio from '../../audio'; | ||||
| import scheduleIdleTask from '../../ui/util/schedule_idle_task'; | ||||
| import Video from '../../video'; | ||||
| 
 | ||||
| import Card from './card'; | ||||
| 
 | ||||
| class DetailedStatus extends ImmutablePureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     status: ImmutablePropTypes.map, | ||||
|     onOpenMedia: PropTypes.func.isRequired, | ||||
|     onOpenVideo: PropTypes.func.isRequired, | ||||
|     onToggleHidden: PropTypes.func.isRequired, | ||||
|     onTranslate: PropTypes.func.isRequired, | ||||
|     measureHeight: PropTypes.bool, | ||||
|     onHeightChange: PropTypes.func, | ||||
|     domain: PropTypes.string.isRequired, | ||||
|     compact: PropTypes.bool, | ||||
|     showMedia: PropTypes.bool, | ||||
|     pictureInPicture: ImmutablePropTypes.contains({ | ||||
|       inUse: PropTypes.bool, | ||||
|       available: PropTypes.bool, | ||||
|     }), | ||||
|     onToggleMediaVisibility: PropTypes.func, | ||||
|     ...WithRouterPropTypes, | ||||
|   }; | ||||
| 
 | ||||
|   state = { | ||||
|     height: null, | ||||
|   }; | ||||
| 
 | ||||
|   handleAccountClick = (e) => { | ||||
|     if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) { | ||||
|       e.preventDefault(); | ||||
|       this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); | ||||
|     } | ||||
| 
 | ||||
|     e.stopPropagation(); | ||||
|   }; | ||||
| 
 | ||||
|   handleOpenVideo = (options) => { | ||||
|     this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options); | ||||
|   }; | ||||
| 
 | ||||
|   handleExpandedToggle = () => { | ||||
|     this.props.onToggleHidden(this.props.status); | ||||
|   }; | ||||
| 
 | ||||
|   _measureHeight (heightJustChanged) { | ||||
|     if (this.props.measureHeight && this.node) { | ||||
|       scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 })); | ||||
| 
 | ||||
|       if (this.props.onHeightChange && heightJustChanged) { | ||||
|         this.props.onHeightChange(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   setRef = c => { | ||||
|     this.node = c; | ||||
|     this._measureHeight(); | ||||
|   }; | ||||
| 
 | ||||
|   componentDidUpdate (prevProps, prevState) { | ||||
|     this._measureHeight(prevState.height !== this.state.height); | ||||
|   } | ||||
| 
 | ||||
|   handleModalLink = e => { | ||||
|     e.preventDefault(); | ||||
| 
 | ||||
|     let href; | ||||
| 
 | ||||
|     if (e.target.nodeName !== 'A') { | ||||
|       href = e.target.parentNode.href; | ||||
|     } else { | ||||
|       href = e.target.href; | ||||
|     } | ||||
| 
 | ||||
|     window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); | ||||
|   }; | ||||
| 
 | ||||
|   handleTranslate = () => { | ||||
|     const { onTranslate, status } = this.props; | ||||
|     onTranslate(status); | ||||
|   }; | ||||
| 
 | ||||
|   _properStatus () { | ||||
|     const { status } = this.props; | ||||
| 
 | ||||
|     if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { | ||||
|       return status.get('reblog'); | ||||
|     } else { | ||||
|       return status; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   getAttachmentAspectRatio () { | ||||
|     const attachments = this._properStatus().get('media_attachments'); | ||||
| 
 | ||||
|     if (attachments.getIn([0, 'type']) === 'video') { | ||||
|       return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`; | ||||
|     } else if (attachments.getIn([0, 'type']) === 'audio') { | ||||
|       return '16 / 9'; | ||||
|     } else { | ||||
|       return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const status = this._properStatus(); | ||||
|     const outerStyle = { boxSizing: 'border-box' }; | ||||
|     const { compact, pictureInPicture } = this.props; | ||||
| 
 | ||||
|     if (!status) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     let media           = ''; | ||||
|     let applicationLink = ''; | ||||
|     let reblogLink = ''; | ||||
|     let favouriteLink = ''; | ||||
| 
 | ||||
|     if (this.props.measureHeight) { | ||||
|       outerStyle.height = `${this.state.height}px`; | ||||
|     } | ||||
| 
 | ||||
|     const language = status.getIn(['translation', 'language']) || status.get('language'); | ||||
| 
 | ||||
|     if (pictureInPicture.get('inUse')) { | ||||
|       media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />; | ||||
|     } else if (status.get('media_attachments').size > 0) { | ||||
|       if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { | ||||
|         const attachment = status.getIn(['media_attachments', 0]); | ||||
|         const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); | ||||
| 
 | ||||
|         media = ( | ||||
|           <Audio | ||||
|             src={attachment.get('url')} | ||||
|             alt={description} | ||||
|             lang={language} | ||||
|             duration={attachment.getIn(['meta', 'original', 'duration'], 0)} | ||||
|             poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} | ||||
|             backgroundColor={attachment.getIn(['meta', 'colors', 'background'])} | ||||
|             foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])} | ||||
|             accentColor={attachment.getIn(['meta', 'colors', 'accent'])} | ||||
|             sensitive={status.get('sensitive')} | ||||
|             visible={this.props.showMedia} | ||||
|             blurhash={attachment.get('blurhash')} | ||||
|             height={150} | ||||
|             onToggleVisibility={this.props.onToggleMediaVisibility} | ||||
|           /> | ||||
|         ); | ||||
|       } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { | ||||
|         const attachment = status.getIn(['media_attachments', 0]); | ||||
|         const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); | ||||
| 
 | ||||
|         media = ( | ||||
|           <Video | ||||
|             preview={attachment.get('preview_url')} | ||||
|             frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])} | ||||
|             aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`} | ||||
|             blurhash={attachment.get('blurhash')} | ||||
|             src={attachment.get('url')} | ||||
|             alt={description} | ||||
|             lang={language} | ||||
|             width={300} | ||||
|             height={150} | ||||
|             onOpenVideo={this.handleOpenVideo} | ||||
|             sensitive={status.get('sensitive')} | ||||
|             visible={this.props.showMedia} | ||||
|             onToggleVisibility={this.props.onToggleMediaVisibility} | ||||
|           /> | ||||
|         ); | ||||
|       } else { | ||||
|         media = ( | ||||
|           <MediaGallery | ||||
|             standalone | ||||
|             sensitive={status.get('sensitive')} | ||||
|             media={status.get('media_attachments')} | ||||
|             lang={language} | ||||
|             height={300} | ||||
|             onOpenMedia={this.props.onOpenMedia} | ||||
|             visible={this.props.showMedia} | ||||
|             onToggleVisibility={this.props.onToggleMediaVisibility} | ||||
|           /> | ||||
|         ); | ||||
|       } | ||||
|     } else if (status.get('spoiler_text').length === 0) { | ||||
|       media = <Card sensitive={status.get('sensitive')} onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />; | ||||
|     } | ||||
| 
 | ||||
|     if (status.get('application')) { | ||||
|       applicationLink = <>·<a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></>; | ||||
|     } | ||||
| 
 | ||||
|     const visibilityLink = <>·<VisibilityIcon visibility={status.get('visibility')} /></>; | ||||
| 
 | ||||
|     if (['private', 'direct'].includes(status.get('visibility'))) { | ||||
|       reblogLink = ''; | ||||
|     } else if (this.props.history) { | ||||
|       reblogLink = ( | ||||
|         <Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link'> | ||||
|           <span className='detailed-status__reblogs'> | ||||
|             <AnimatedNumber value={status.get('reblogs_count')} /> | ||||
|           </span> | ||||
|           <FormattedMessage id='status.reblogs' defaultMessage='{count, plural, one {boost} other {boosts}}' values={{ count: status.get('reblogs_count') }} /> | ||||
|         </Link> | ||||
|       ); | ||||
|     } else { | ||||
|       reblogLink = ( | ||||
|         <a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}> | ||||
|           <span className='detailed-status__reblogs'> | ||||
|             <AnimatedNumber value={status.get('reblogs_count')} /> | ||||
|           </span> | ||||
|           <FormattedMessage id='status.reblogs' defaultMessage='{count, plural, one {boost} other {boosts}}' values={{ count: status.get('reblogs_count') }} /> | ||||
|         </a> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (this.props.history) { | ||||
|       favouriteLink = ( | ||||
|         <Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'> | ||||
|           <span className='detailed-status__favorites'> | ||||
|             <AnimatedNumber value={status.get('favourites_count')} /> | ||||
|           </span> | ||||
|           <FormattedMessage id='status.favourites' defaultMessage='{count, plural, one {favorite} other {favorites}}' values={{ count: status.get('favourites_count') }} /> | ||||
|         </Link> | ||||
|       ); | ||||
|     } else { | ||||
|       favouriteLink = ( | ||||
|         <a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}> | ||||
|           <span className='detailed-status__favorites'> | ||||
|             <AnimatedNumber value={status.get('favourites_count')} /> | ||||
|           </span> | ||||
|           <FormattedMessage id='status.favourites' defaultMessage='{count, plural, one {favorite} other {favorites}}' values={{ count: status.get('favourites_count') }} /> | ||||
|         </a> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); | ||||
|     const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0; | ||||
| 
 | ||||
|     return ( | ||||
|       <div style={outerStyle}> | ||||
|         <div ref={this.setRef} className={classNames('detailed-status', { compact })}> | ||||
|           {status.get('visibility') === 'direct' && ( | ||||
|             <div className='status__prepend'> | ||||
|               <div className='status__prepend-icon-wrapper'><Icon id='at' icon={AlternateEmailIcon} className='status__prepend-icon' /></div> | ||||
|               <FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' /> | ||||
|             </div> | ||||
|           )} | ||||
|           <a href={`/@${status.getIn(['account', 'acct'])}`} data-hover-card-account={status.getIn(['account', 'id'])} onClick={this.handleAccountClick} className='detailed-status__display-name'> | ||||
|             <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div> | ||||
|             <DisplayName account={status.get('account')} localDomain={this.props.domain} /> | ||||
|           </a> | ||||
| 
 | ||||
|           {status.get('spoiler_text').length > 0 && <ContentWarning text={status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml')} expanded={expanded} onClick={this.handleExpandedToggle} />} | ||||
| 
 | ||||
|           {expanded && ( | ||||
|             <> | ||||
|               <StatusContent | ||||
|                 status={status} | ||||
|                 onTranslate={this.handleTranslate} | ||||
|                 {...statusContentProps} | ||||
|               /> | ||||
| 
 | ||||
|               {media} | ||||
|               {hashtagBar} | ||||
|             </> | ||||
|           )} | ||||
| 
 | ||||
|           <div className='detailed-status__meta'> | ||||
|             <div className='detailed-status__meta__line'> | ||||
|               <a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'> | ||||
|                 <FormattedDate value={new Date(status.get('created_at'))} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /> | ||||
|               </a> | ||||
| 
 | ||||
|               {visibilityLink} | ||||
| 
 | ||||
|               {applicationLink} | ||||
|             </div> | ||||
| 
 | ||||
|             {status.get('edited_at') && <div className='detailed-status__meta__line'><EditedTimestamp statusId={status.get('id')} timestamp={status.get('edited_at')} /></div>} | ||||
| 
 | ||||
|             <div className='detailed-status__meta__line'> | ||||
|               {reblogLink} | ||||
|               {reblogLink && <>·</>} | ||||
|               {favouriteLink} | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export default withRouter(DetailedStatus); | ||||
|  | @ -0,0 +1,390 @@ | |||
| /* eslint-disable @typescript-eslint/no-unsafe-member-access, | ||||
|                   @typescript-eslint/no-unsafe-call, | ||||
|                   @typescript-eslint/no-explicit-any, | ||||
|                   @typescript-eslint/no-unsafe-assignment */ | ||||
| 
 | ||||
| import type { CSSProperties } from 'react'; | ||||
| import { useState, useRef, useCallback } from 'react'; | ||||
| 
 | ||||
| import { FormattedDate, FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| import classNames from 'classnames'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| 
 | ||||
| import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; | ||||
| import { AnimatedNumber } from 'mastodon/components/animated_number'; | ||||
| import { ContentWarning } from 'mastodon/components/content_warning'; | ||||
| import EditedTimestamp from 'mastodon/components/edited_timestamp'; | ||||
| import type { StatusLike } from 'mastodon/components/hashtag_bar'; | ||||
| import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar'; | ||||
| import { Icon } from 'mastodon/components/icon'; | ||||
| import { IconLogo } from 'mastodon/components/logo'; | ||||
| import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder'; | ||||
| import { VisibilityIcon } from 'mastodon/components/visibility_icon'; | ||||
| 
 | ||||
| import { Avatar } from '../../../components/avatar'; | ||||
| import { DisplayName } from '../../../components/display_name'; | ||||
| import MediaGallery from '../../../components/media_gallery'; | ||||
| import StatusContent from '../../../components/status_content'; | ||||
| import Audio from '../../audio'; | ||||
| import scheduleIdleTask from '../../ui/util/schedule_idle_task'; | ||||
| import Video from '../../video'; | ||||
| 
 | ||||
| import Card from './card'; | ||||
| 
 | ||||
| interface VideoModalOptions { | ||||
|   startTime: number; | ||||
|   autoPlay?: boolean; | ||||
|   defaultVolume: number; | ||||
|   componentIndex: number; | ||||
| } | ||||
| 
 | ||||
| export const DetailedStatus: React.FC<{ | ||||
|   status: any; | ||||
|   onOpenMedia?: (status: any, index: number, lang: string) => void; | ||||
|   onOpenVideo?: (status: any, lang: string, options: VideoModalOptions) => void; | ||||
|   onTranslate?: (status: any) => void; | ||||
|   measureHeight?: boolean; | ||||
|   onHeightChange?: () => void; | ||||
|   domain: string; | ||||
|   showMedia?: boolean; | ||||
|   withLogo?: boolean; | ||||
|   pictureInPicture: any; | ||||
|   onToggleHidden?: (status: any) => void; | ||||
|   onToggleMediaVisibility?: () => void; | ||||
| }> = ({ | ||||
|   status, | ||||
|   onOpenMedia, | ||||
|   onOpenVideo, | ||||
|   onTranslate, | ||||
|   measureHeight, | ||||
|   onHeightChange, | ||||
|   domain, | ||||
|   showMedia, | ||||
|   withLogo, | ||||
|   pictureInPicture, | ||||
|   onToggleMediaVisibility, | ||||
|   onToggleHidden, | ||||
| }) => { | ||||
|   const properStatus = status?.get('reblog') ?? status; | ||||
|   const [height, setHeight] = useState(0); | ||||
|   const nodeRef = useRef<HTMLDivElement>(); | ||||
| 
 | ||||
|   const handleOpenVideo = useCallback( | ||||
|     (options: VideoModalOptions) => { | ||||
|       const lang = (status.getIn(['translation', 'language']) || | ||||
|         status.get('language')) as string; | ||||
|       if (onOpenVideo) | ||||
|         onOpenVideo(status.getIn(['media_attachments', 0]), lang, options); | ||||
|     }, | ||||
|     [onOpenVideo, status], | ||||
|   ); | ||||
| 
 | ||||
|   const handleExpandedToggle = useCallback(() => { | ||||
|     if (onToggleHidden) onToggleHidden(status); | ||||
|   }, [onToggleHidden, status]); | ||||
| 
 | ||||
|   const _measureHeight = useCallback( | ||||
|     (heightJustChanged?: boolean) => { | ||||
|       if (measureHeight && nodeRef.current) { | ||||
|         scheduleIdleTask(() => { | ||||
|           if (nodeRef.current) | ||||
|             setHeight(Math.ceil(nodeRef.current.scrollHeight) + 1); | ||||
|         }); | ||||
| 
 | ||||
|         if (onHeightChange && heightJustChanged) { | ||||
|           onHeightChange(); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     [onHeightChange, measureHeight, setHeight], | ||||
|   ); | ||||
| 
 | ||||
|   const handleRef = useCallback( | ||||
|     (c: HTMLDivElement) => { | ||||
|       nodeRef.current = c; | ||||
|       _measureHeight(); | ||||
|     }, | ||||
|     [_measureHeight], | ||||
|   ); | ||||
| 
 | ||||
|   const handleTranslate = useCallback(() => { | ||||
|     if (onTranslate) onTranslate(status); | ||||
|   }, [onTranslate, status]); | ||||
| 
 | ||||
|   if (!properStatus) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   let media; | ||||
|   let applicationLink; | ||||
|   let reblogLink; | ||||
|   let attachmentAspectRatio; | ||||
| 
 | ||||
|   if (properStatus.get('media_attachments').getIn([0, 'type']) === 'video') { | ||||
|     attachmentAspectRatio = `${properStatus.get('media_attachments').getIn([0, 'meta', 'original', 'width'])} / ${properStatus.get('media_attachments').getIn([0, 'meta', 'original', 'height'])}`; | ||||
|   } else if ( | ||||
|     properStatus.get('media_attachments').getIn([0, 'type']) === 'audio' | ||||
|   ) { | ||||
|     attachmentAspectRatio = '16 / 9'; | ||||
|   } else { | ||||
|     attachmentAspectRatio = | ||||
|       properStatus.get('media_attachments').size === 1 && | ||||
|       properStatus | ||||
|         .get('media_attachments') | ||||
|         .getIn([0, 'meta', 'small', 'aspect']) | ||||
|         ? properStatus | ||||
|             .get('media_attachments') | ||||
|             .getIn([0, 'meta', 'small', 'aspect']) | ||||
|         : '3 / 2'; | ||||
|   } | ||||
| 
 | ||||
|   const outerStyle = { boxSizing: 'border-box' } as CSSProperties; | ||||
| 
 | ||||
|   if (measureHeight) { | ||||
|     outerStyle.height = height; | ||||
|   } | ||||
| 
 | ||||
|   const language = | ||||
|     status.getIn(['translation', 'language']) || status.get('language'); | ||||
| 
 | ||||
|   if (pictureInPicture.get('inUse')) { | ||||
|     media = <PictureInPicturePlaceholder aspectRatio={attachmentAspectRatio} />; | ||||
|   } else if (status.get('media_attachments').size > 0) { | ||||
|     if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { | ||||
|       const attachment = status.getIn(['media_attachments', 0]); | ||||
|       const description = | ||||
|         attachment.getIn(['translation', 'description']) || | ||||
|         attachment.get('description'); | ||||
| 
 | ||||
|       media = ( | ||||
|         <Audio | ||||
|           src={attachment.get('url')} | ||||
|           alt={description} | ||||
|           lang={language} | ||||
|           duration={attachment.getIn(['meta', 'original', 'duration'], 0)} | ||||
|           poster={ | ||||
|             attachment.get('preview_url') || | ||||
|             status.getIn(['account', 'avatar_static']) | ||||
|           } | ||||
|           backgroundColor={attachment.getIn(['meta', 'colors', 'background'])} | ||||
|           foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])} | ||||
|           accentColor={attachment.getIn(['meta', 'colors', 'accent'])} | ||||
|           sensitive={status.get('sensitive')} | ||||
|           visible={showMedia} | ||||
|           blurhash={attachment.get('blurhash')} | ||||
|           height={150} | ||||
|           onToggleVisibility={onToggleMediaVisibility} | ||||
|         /> | ||||
|       ); | ||||
|     } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { | ||||
|       const attachment = status.getIn(['media_attachments', 0]); | ||||
|       const description = | ||||
|         attachment.getIn(['translation', 'description']) || | ||||
|         attachment.get('description'); | ||||
| 
 | ||||
|       media = ( | ||||
|         <Video | ||||
|           preview={attachment.get('preview_url')} | ||||
|           frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])} | ||||
|           aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`} | ||||
|           blurhash={attachment.get('blurhash')} | ||||
|           src={attachment.get('url')} | ||||
|           alt={description} | ||||
|           lang={language} | ||||
|           width={300} | ||||
|           height={150} | ||||
|           onOpenVideo={handleOpenVideo} | ||||
|           sensitive={status.get('sensitive')} | ||||
|           visible={showMedia} | ||||
|           onToggleVisibility={onToggleMediaVisibility} | ||||
|         /> | ||||
|       ); | ||||
|     } else { | ||||
|       media = ( | ||||
|         <MediaGallery | ||||
|           standalone | ||||
|           sensitive={status.get('sensitive')} | ||||
|           media={status.get('media_attachments')} | ||||
|           lang={language} | ||||
|           height={300} | ||||
|           onOpenMedia={onOpenMedia} | ||||
|           visible={showMedia} | ||||
|           onToggleVisibility={onToggleMediaVisibility} | ||||
|         /> | ||||
|       ); | ||||
|     } | ||||
|   } else if (status.get('spoiler_text').length === 0) { | ||||
|     media = ( | ||||
|       <Card | ||||
|         sensitive={status.get('sensitive')} | ||||
|         onOpenMedia={onOpenMedia} | ||||
|         card={status.get('card', null)} | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   if (status.get('application')) { | ||||
|     applicationLink = ( | ||||
|       <> | ||||
|         · | ||||
|         <a | ||||
|           className='detailed-status__application' | ||||
|           href={status.getIn(['application', 'website'])} | ||||
|           target='_blank' | ||||
|           rel='noopener noreferrer' | ||||
|         > | ||||
|           {status.getIn(['application', 'name'])} | ||||
|         </a> | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   const visibilityLink = ( | ||||
|     <> | ||||
|       ·<VisibilityIcon visibility={status.get('visibility')} /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   if (['private', 'direct'].includes(status.get('visibility') as string)) { | ||||
|     reblogLink = ''; | ||||
|   } else { | ||||
|     reblogLink = ( | ||||
|       <Link | ||||
|         to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} | ||||
|         className='detailed-status__link' | ||||
|       > | ||||
|         <span className='detailed-status__reblogs'> | ||||
|           <AnimatedNumber value={status.get('reblogs_count')} /> | ||||
|         </span> | ||||
|         <FormattedMessage | ||||
|           id='status.reblogs' | ||||
|           defaultMessage='{count, plural, one {boost} other {boosts}}' | ||||
|           values={{ count: status.get('reblogs_count') }} | ||||
|         /> | ||||
|       </Link> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   const favouriteLink = ( | ||||
|     <Link | ||||
|       to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} | ||||
|       className='detailed-status__link' | ||||
|     > | ||||
|       <span className='detailed-status__favorites'> | ||||
|         <AnimatedNumber value={status.get('favourites_count')} /> | ||||
|       </span> | ||||
|       <FormattedMessage | ||||
|         id='status.favourites' | ||||
|         defaultMessage='{count, plural, one {favorite} other {favorites}}' | ||||
|         values={{ count: status.get('favourites_count') }} | ||||
|       /> | ||||
|     </Link> | ||||
|   ); | ||||
| 
 | ||||
|   const { statusContentProps, hashtagBar } = getHashtagBarForStatus( | ||||
|     status as StatusLike, | ||||
|   ); | ||||
|   const expanded = | ||||
|     !status.get('hidden') || status.get('spoiler_text').length === 0; | ||||
| 
 | ||||
|   return ( | ||||
|     <div style={outerStyle}> | ||||
|       <div ref={handleRef} className={classNames('detailed-status')}> | ||||
|         {status.get('visibility') === 'direct' && ( | ||||
|           <div className='status__prepend'> | ||||
|             <div className='status__prepend-icon-wrapper'> | ||||
|               <Icon | ||||
|                 id='at' | ||||
|                 icon={AlternateEmailIcon} | ||||
|                 className='status__prepend-icon' | ||||
|               /> | ||||
|             </div> | ||||
|             <FormattedMessage | ||||
|               id='status.direct_indicator' | ||||
|               defaultMessage='Private mention' | ||||
|             /> | ||||
|           </div> | ||||
|         )} | ||||
|         <Link | ||||
|           to={`/@${status.getIn(['account', 'acct'])}`} | ||||
|           data-hover-card-account={status.getIn(['account', 'id'])} | ||||
|           className='detailed-status__display-name' | ||||
|         > | ||||
|           <div className='detailed-status__display-avatar'> | ||||
|             <Avatar account={status.get('account')} size={46} /> | ||||
|           </div> | ||||
|           <DisplayName account={status.get('account')} localDomain={domain} /> | ||||
|           {withLogo && ( | ||||
|             <> | ||||
|               <div className='spacer' /> | ||||
|               <IconLogo /> | ||||
|             </> | ||||
|           )} | ||||
|         </Link> | ||||
| 
 | ||||
|         {status.get('spoiler_text').length > 0 && ( | ||||
|           <ContentWarning | ||||
|             text={ | ||||
|               status.getIn(['translation', 'spoilerHtml']) || | ||||
|               status.get('spoilerHtml') | ||||
|             } | ||||
|             expanded={expanded} | ||||
|             onClick={handleExpandedToggle} | ||||
|           /> | ||||
|         )} | ||||
| 
 | ||||
|         {expanded && ( | ||||
|           <> | ||||
|             <StatusContent | ||||
|               status={status} | ||||
|               onTranslate={handleTranslate} | ||||
|               {...(statusContentProps as any)} | ||||
|             /> | ||||
| 
 | ||||
|             {media} | ||||
|             {hashtagBar} | ||||
|           </> | ||||
|         )} | ||||
| 
 | ||||
|         <div className='detailed-status__meta'> | ||||
|           <div className='detailed-status__meta__line'> | ||||
|             <a | ||||
|               className='detailed-status__datetime' | ||||
|               href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} | ||||
|               target='_blank' | ||||
|               rel='noopener noreferrer' | ||||
|             > | ||||
|               <FormattedDate | ||||
|                 value={new Date(status.get('created_at') as string)} | ||||
|                 year='numeric' | ||||
|                 month='short' | ||||
|                 day='2-digit' | ||||
|                 hour='2-digit' | ||||
|                 minute='2-digit' | ||||
|               /> | ||||
|             </a> | ||||
| 
 | ||||
|             {visibilityLink} | ||||
|             {applicationLink} | ||||
|           </div> | ||||
| 
 | ||||
|           {status.get('edited_at') && ( | ||||
|             <div className='detailed-status__meta__line'> | ||||
|               <EditedTimestamp | ||||
|                 statusId={status.get('id')} | ||||
|                 timestamp={status.get('edited_at')} | ||||
|               /> | ||||
|             </div> | ||||
|           )} | ||||
| 
 | ||||
|           <div className='detailed-status__meta__line'> | ||||
|             {reblogLink} | ||||
|             {reblogLink && <>·</>} | ||||
|             {favouriteLink} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | @ -1,140 +0,0 @@ | |||
| import { injectIntl } from 'react-intl'; | ||||
| 
 | ||||
| import { connect } from 'react-redux'; | ||||
| 
 | ||||
| import { showAlertForError } from '../../../actions/alerts'; | ||||
| import { initBlockModal } from '../../../actions/blocks'; | ||||
| import { | ||||
|   replyCompose, | ||||
|   mentionCompose, | ||||
|   directCompose, | ||||
| } from '../../../actions/compose'; | ||||
| import { | ||||
|   toggleReblog, | ||||
|   toggleFavourite, | ||||
|   pin, | ||||
|   unpin, | ||||
| } from '../../../actions/interactions'; | ||||
| import { openModal } from '../../../actions/modal'; | ||||
| import { initMuteModal } from '../../../actions/mutes'; | ||||
| import { initReport } from '../../../actions/reports'; | ||||
| import { | ||||
|   muteStatus, | ||||
|   unmuteStatus, | ||||
|   deleteStatus, | ||||
|   toggleStatusSpoilers, | ||||
| } from '../../../actions/statuses'; | ||||
| import { deleteModal } from '../../../initial_state'; | ||||
| import { makeGetStatus, makeGetPictureInPicture } from '../../../selectors'; | ||||
| import DetailedStatus from '../components/detailed_status'; | ||||
| 
 | ||||
| const makeMapStateToProps = () => { | ||||
|   const getStatus = makeGetStatus(); | ||||
|   const getPictureInPicture = makeGetPictureInPicture(); | ||||
| 
 | ||||
|   const mapStateToProps = (state, props) => ({ | ||||
|     status: getStatus(state, props), | ||||
|     domain: state.getIn(['meta', 'domain']), | ||||
|     pictureInPicture: getPictureInPicture(state, props), | ||||
|   }); | ||||
| 
 | ||||
|   return mapStateToProps; | ||||
| }; | ||||
| 
 | ||||
| const mapDispatchToProps = (dispatch) => ({ | ||||
| 
 | ||||
|   onReply (status) { | ||||
|     dispatch((_, getState) => { | ||||
|       let state = getState(); | ||||
|       if (state.getIn(['compose', 'text']).trim().length !== 0) { | ||||
|         dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } })); | ||||
|       } else { | ||||
|         dispatch(replyCompose(status)); | ||||
|       } | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   onReblog (status, e) { | ||||
|     dispatch(toggleReblog(status.get('id'), e.shiftKey)); | ||||
|   }, | ||||
| 
 | ||||
|   onFavourite (status) { | ||||
|     dispatch(toggleFavourite(status.get('id'))); | ||||
|   }, | ||||
| 
 | ||||
|   onPin (status) { | ||||
|     if (status.get('pinned')) { | ||||
|       dispatch(unpin(status)); | ||||
|     } else { | ||||
|       dispatch(pin(status)); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   onEmbed (status) { | ||||
|     dispatch(openModal({ | ||||
|       modalType: 'EMBED', | ||||
|       modalProps: { | ||||
|         id: status.get('id'), | ||||
|         onError: error => dispatch(showAlertForError(error)), | ||||
|       }, | ||||
|     })); | ||||
|   }, | ||||
| 
 | ||||
|   onDelete (status, withRedraft = false) { | ||||
|     if (!deleteModal) { | ||||
|       dispatch(deleteStatus(status.get('id'), withRedraft)); | ||||
|     } else { | ||||
|       dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } })); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   onDirect (account) { | ||||
|     dispatch(directCompose(account)); | ||||
|   }, | ||||
| 
 | ||||
|   onMention (account) { | ||||
|     dispatch(mentionCompose(account)); | ||||
|   }, | ||||
| 
 | ||||
|   onOpenMedia (media, index, lang) { | ||||
|     dispatch(openModal({ | ||||
|       modalType: 'MEDIA', | ||||
|       modalProps: { media, index, lang }, | ||||
|     })); | ||||
|   }, | ||||
| 
 | ||||
|   onOpenVideo (media, lang, options) { | ||||
|     dispatch(openModal({ | ||||
|       modalType: 'VIDEO', | ||||
|       modalProps: { media, lang, options }, | ||||
|     })); | ||||
|   }, | ||||
| 
 | ||||
|   onBlock (status) { | ||||
|     const account = status.get('account'); | ||||
|     dispatch(initBlockModal(account)); | ||||
|   }, | ||||
| 
 | ||||
|   onReport (status) { | ||||
|     dispatch(initReport(status.get('account'), status)); | ||||
|   }, | ||||
| 
 | ||||
|   onMute (account) { | ||||
|     dispatch(initMuteModal(account)); | ||||
|   }, | ||||
| 
 | ||||
|   onMuteConversation (status) { | ||||
|     if (status.get('muted')) { | ||||
|       dispatch(unmuteStatus(status.get('id'))); | ||||
|     } else { | ||||
|       dispatch(muteStatus(status.get('id'))); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   onToggleHidden (status) { | ||||
|     dispatch(toggleStatusSpoilers(status.get('id'))); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus)); | ||||
|  | @ -69,7 +69,7 @@ import Column from '../ui/components/column'; | |||
| import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; | ||||
| 
 | ||||
| import ActionBar from './components/action_bar'; | ||||
| import DetailedStatus from './components/detailed_status'; | ||||
| import { DetailedStatus } from './components/detailed_status'; | ||||
| 
 | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue