Change design of media tab on profiles in web UI (#31967)
| @ -11,6 +11,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; | |||||||
| import { debounce } from 'lodash'; | import { debounce } from 'lodash'; | ||||||
| 
 | 
 | ||||||
| import { Blurhash } from 'mastodon/components/blurhash'; | import { Blurhash } from 'mastodon/components/blurhash'; | ||||||
|  | import { formatTime } from 'mastodon/features/video'; | ||||||
| 
 | 
 | ||||||
| import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state'; | import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state'; | ||||||
| 
 | 
 | ||||||
| @ -57,7 +58,7 @@ class Item extends PureComponent { | |||||||
| 
 | 
 | ||||||
|   hoverToPlay () { |   hoverToPlay () { | ||||||
|     const { attachment } = this.props; |     const { attachment } = this.props; | ||||||
|     return !this.getAutoPlay() && attachment.get('type') === 'gifv'; |     return !this.getAutoPlay() && ['gifv', 'video'].includes(attachment.get('type')); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleClick = (e) => { |   handleClick = (e) => { | ||||||
| @ -150,10 +151,15 @@ class Item extends PureComponent { | |||||||
|           /> |           /> | ||||||
|         </a> |         </a> | ||||||
|       ); |       ); | ||||||
|     } else if (attachment.get('type') === 'gifv') { |     } else if (['gifv', 'video'].includes(attachment.get('type'))) { | ||||||
|       const autoPlay = this.getAutoPlay(); |       const autoPlay = this.getAutoPlay(); | ||||||
|  |       const duration = attachment.getIn(['meta', 'original', 'duration']); | ||||||
| 
 | 
 | ||||||
|  |       if (attachment.get('type') === 'gifv') { | ||||||
|         badges.push(<span key='gif' className='media-gallery__gifv__label'>GIF</span>); |         badges.push(<span key='gif' className='media-gallery__gifv__label'>GIF</span>); | ||||||
|  |       } else { | ||||||
|  |         badges.push(<span key='video' className='media-gallery__gifv__label'>{formatTime(Math.floor(duration))}</span>); | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       thumbnail = ( |       thumbnail = ( | ||||||
|         <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> |         <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> | ||||||
| @ -167,6 +173,7 @@ class Item extends PureComponent { | |||||||
|             onClick={this.handleClick} |             onClick={this.handleClick} | ||||||
|             onMouseEnter={this.handleMouseEnter} |             onMouseEnter={this.handleMouseEnter} | ||||||
|             onMouseLeave={this.handleMouseLeave} |             onMouseLeave={this.handleMouseLeave} | ||||||
|  |             onLoadedData={this.handleImageLoad} | ||||||
|             autoPlay={autoPlay} |             autoPlay={autoPlay} | ||||||
|             playsInline |             playsInline | ||||||
|             loop |             loop | ||||||
|  | |||||||
| @ -449,7 +449,25 @@ class Status extends ImmutablePureComponent { | |||||||
|     } else if (status.get('media_attachments').size > 0) { |     } else if (status.get('media_attachments').size > 0) { | ||||||
|       const language = status.getIn(['translation', 'language']) || status.get('language'); |       const language = status.getIn(['translation', 'language']) || status.get('language'); | ||||||
| 
 | 
 | ||||||
|       if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { |       if (['image', 'gifv'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) { | ||||||
|  |         media = ( | ||||||
|  |           <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}> | ||||||
|  |             {Component => ( | ||||||
|  |               <Component | ||||||
|  |                 media={status.get('media_attachments')} | ||||||
|  |                 lang={language} | ||||||
|  |                 sensitive={status.get('sensitive')} | ||||||
|  |                 height={110} | ||||||
|  |                 onOpenMedia={this.handleOpenMedia} | ||||||
|  |                 cacheWidth={this.props.cacheMediaWidth} | ||||||
|  |                 defaultWidth={this.props.cachedMediaWidth} | ||||||
|  |                 visible={this.state.showMedia} | ||||||
|  |                 onToggleVisibility={this.handleToggleMediaVisibility} | ||||||
|  |               /> | ||||||
|  |             )} | ||||||
|  |           </Bundle> | ||||||
|  |         ); | ||||||
|  |       } else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { | ||||||
|         const attachment = status.getIn(['media_attachments', 0]); |         const attachment = status.getIn(['media_attachments', 0]); | ||||||
|         const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); |         const description = attachment.getIn(['translation', 'description']) || attachment.get('description'); | ||||||
| 
 | 
 | ||||||
| @ -501,24 +519,6 @@ class Status extends ImmutablePureComponent { | |||||||
|             )} |             )} | ||||||
|           </Bundle> |           </Bundle> | ||||||
|         ); |         ); | ||||||
|       } else { |  | ||||||
|         media = ( |  | ||||||
|           <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}> |  | ||||||
|             {Component => ( |  | ||||||
|               <Component |  | ||||||
|                 media={status.get('media_attachments')} |  | ||||||
|                 lang={language} |  | ||||||
|                 sensitive={status.get('sensitive')} |  | ||||||
|                 height={110} |  | ||||||
|                 onOpenMedia={this.handleOpenMedia} |  | ||||||
|                 cacheWidth={this.props.cacheMediaWidth} |  | ||||||
|                 defaultWidth={this.props.cachedMediaWidth} |  | ||||||
|                 visible={this.state.showMedia} |  | ||||||
|                 onToggleVisibility={this.handleToggleMediaVisibility} |  | ||||||
|               /> |  | ||||||
|             )} |  | ||||||
|           </Bundle> |  | ||||||
|         ); |  | ||||||
|       } |       } | ||||||
|     } else if (status.get('spoiler_text').length === 0 && status.get('card')) { |     } else if (status.get('spoiler_text').length === 0 && status.get('card')) { | ||||||
|       media = ( |       media = ( | ||||||
|  | |||||||
| @ -1,158 +0,0 @@ | |||||||
| import PropTypes from 'prop-types'; |  | ||||||
| 
 |  | ||||||
| import classNames from 'classnames'; |  | ||||||
| 
 |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; |  | ||||||
| 
 |  | ||||||
| import AudiotrackIcon from '@/material-icons/400-24px/music_note.svg?react'; |  | ||||||
| import PlayArrowIcon from '@/material-icons/400-24px/play_arrow.svg?react'; |  | ||||||
| import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; |  | ||||||
| import { Blurhash } from 'mastodon/components/blurhash'; |  | ||||||
| import { Icon }  from 'mastodon/components/icon'; |  | ||||||
| import { autoPlayGif, displayMedia, useBlurhash } from 'mastodon/initial_state'; |  | ||||||
| 
 |  | ||||||
| export default class MediaItem extends ImmutablePureComponent { |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     attachment: ImmutablePropTypes.map.isRequired, |  | ||||||
|     displayWidth: PropTypes.number.isRequired, |  | ||||||
|     onOpenMedia: PropTypes.func.isRequired, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   state = { |  | ||||||
|     visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all', |  | ||||||
|     loaded: false, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   handleImageLoad = () => { |  | ||||||
|     this.setState({ loaded: true }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   handleMouseEnter = e => { |  | ||||||
|     if (this.hoverToPlay()) { |  | ||||||
|       e.target.play(); |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   handleMouseLeave = e => { |  | ||||||
|     if (this.hoverToPlay()) { |  | ||||||
|       e.target.pause(); |  | ||||||
|       e.target.currentTime = 0; |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   hoverToPlay () { |  | ||||||
|     return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleClick = e => { |  | ||||||
|     if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { |  | ||||||
|       e.preventDefault(); |  | ||||||
| 
 |  | ||||||
|       if (this.state.visible) { |  | ||||||
|         this.props.onOpenMedia(this.props.attachment); |  | ||||||
|       } else { |  | ||||||
|         this.setState({ visible: true }); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { attachment, displayWidth } = this.props; |  | ||||||
|     const { visible, loaded } = this.state; |  | ||||||
| 
 |  | ||||||
|     const width  = `${Math.floor((displayWidth - 4) / 3) - 4}px`; |  | ||||||
|     const height = width; |  | ||||||
|     const status = attachment.get('status'); |  | ||||||
|     const title  = status.get('spoiler_text') || attachment.get('description'); |  | ||||||
| 
 |  | ||||||
|     let thumbnail, label, icon, content; |  | ||||||
| 
 |  | ||||||
|     if (!visible) { |  | ||||||
|       icon = ( |  | ||||||
|         <span className='account-gallery__item__icons'> |  | ||||||
|           <Icon id='eye-slash' icon={VisibilityOffIcon} /> |  | ||||||
|         </span> |  | ||||||
|       ); |  | ||||||
|     } else { |  | ||||||
|       if (['audio', 'video'].includes(attachment.get('type'))) { |  | ||||||
|         content = ( |  | ||||||
|           <img |  | ||||||
|             src={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} |  | ||||||
|             alt={attachment.get('description')} |  | ||||||
|             lang={status.get('language')} |  | ||||||
|             onLoad={this.handleImageLoad} |  | ||||||
|           /> |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         if (attachment.get('type') === 'audio') { |  | ||||||
|           label = <Icon id='music' icon={AudiotrackIcon} />; |  | ||||||
|         } else { |  | ||||||
|           label = <Icon id='play' icon={PlayArrowIcon} />; |  | ||||||
|         } |  | ||||||
|       } else if (attachment.get('type') === 'image') { |  | ||||||
|         const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0; |  | ||||||
|         const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0; |  | ||||||
|         const x      = ((focusX /  2) + .5) * 100; |  | ||||||
|         const y      = ((focusY / -2) + .5) * 100; |  | ||||||
| 
 |  | ||||||
|         content = ( |  | ||||||
|           <img |  | ||||||
|             src={attachment.get('preview_url')} |  | ||||||
|             alt={attachment.get('description')} |  | ||||||
|             lang={status.get('language')} |  | ||||||
|             style={{ objectPosition: `${x}% ${y}%` }} |  | ||||||
|             onLoad={this.handleImageLoad} |  | ||||||
|           /> |  | ||||||
|         ); |  | ||||||
|       } else if (attachment.get('type') === 'gifv') { |  | ||||||
|         content = ( |  | ||||||
|           <video |  | ||||||
|             className='media-gallery__item-gifv-thumbnail' |  | ||||||
|             aria-label={attachment.get('description')} |  | ||||||
|             title={attachment.get('description')} |  | ||||||
|             lang={status.get('language')} |  | ||||||
|             role='application' |  | ||||||
|             src={attachment.get('url')} |  | ||||||
|             onMouseEnter={this.handleMouseEnter} |  | ||||||
|             onMouseLeave={this.handleMouseLeave} |  | ||||||
|             autoPlay={autoPlayGif} |  | ||||||
|             playsInline |  | ||||||
|             loop |  | ||||||
|             muted |  | ||||||
|           /> |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         label = 'GIF'; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       thumbnail = ( |  | ||||||
|         <div className='media-gallery__gifv'> |  | ||||||
|           {content} |  | ||||||
| 
 |  | ||||||
|           {label && ( |  | ||||||
|             <div className='media-gallery__item__badges'> |  | ||||||
|               <span className='media-gallery__gifv__label'>{label}</span> |  | ||||||
|             </div> |  | ||||||
|           )} |  | ||||||
|         </div> |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       <div className='account-gallery__item' style={{ width, height }}> |  | ||||||
|         <a className='media-gallery__item-thumbnail' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'> |  | ||||||
|           <Blurhash |  | ||||||
|             hash={attachment.get('blurhash')} |  | ||||||
|             className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} |  | ||||||
|             dummy={!useBlurhash} |  | ||||||
|           /> |  | ||||||
| 
 |  | ||||||
|           {visible ? thumbnail : icon} |  | ||||||
|         </a> |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -0,0 +1,197 @@ | |||||||
|  | import { useState, useCallback } from 'react'; | ||||||
|  | 
 | ||||||
|  | import classNames from 'classnames'; | ||||||
|  | 
 | ||||||
|  | import HeadphonesIcon from '@/material-icons/400-24px/headphones-fill.svg?react'; | ||||||
|  | import MovieIcon from '@/material-icons/400-24px/movie-fill.svg?react'; | ||||||
|  | import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; | ||||||
|  | import { Blurhash } from 'mastodon/components/blurhash'; | ||||||
|  | import { Icon } from 'mastodon/components/icon'; | ||||||
|  | import { formatTime } from 'mastodon/features/video'; | ||||||
|  | import { autoPlayGif, displayMedia, useBlurhash } from 'mastodon/initial_state'; | ||||||
|  | import type { Status, MediaAttachment } from 'mastodon/models/status'; | ||||||
|  | 
 | ||||||
|  | export const MediaItem: React.FC<{ | ||||||
|  |   attachment: MediaAttachment; | ||||||
|  |   onOpenMedia: (arg0: MediaAttachment) => void; | ||||||
|  | }> = ({ attachment, onOpenMedia }) => { | ||||||
|  |   const [visible, setVisible] = useState( | ||||||
|  |     (displayMedia !== 'hide_all' && | ||||||
|  |       !attachment.getIn(['status', 'sensitive'])) || | ||||||
|  |       displayMedia === 'show_all', | ||||||
|  |   ); | ||||||
|  |   const [loaded, setLoaded] = useState(false); | ||||||
|  | 
 | ||||||
|  |   const handleImageLoad = useCallback(() => { | ||||||
|  |     setLoaded(true); | ||||||
|  |   }, [setLoaded]); | ||||||
|  | 
 | ||||||
|  |   const handleMouseEnter = useCallback( | ||||||
|  |     (e: React.MouseEvent<HTMLVideoElement>) => { | ||||||
|  |       if (e.target instanceof HTMLVideoElement) { | ||||||
|  |         void e.target.play(); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     [], | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const handleMouseLeave = useCallback( | ||||||
|  |     (e: React.MouseEvent<HTMLVideoElement>) => { | ||||||
|  |       if (e.target instanceof HTMLVideoElement) { | ||||||
|  |         e.target.pause(); | ||||||
|  |         e.target.currentTime = 0; | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     [], | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const handleClick = useCallback( | ||||||
|  |     (e: React.MouseEvent<HTMLAnchorElement>) => { | ||||||
|  |       if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { | ||||||
|  |         e.preventDefault(); | ||||||
|  | 
 | ||||||
|  |         if (visible) { | ||||||
|  |           onOpenMedia(attachment); | ||||||
|  |         } else { | ||||||
|  |           setVisible(true); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     [attachment, visible, onOpenMedia, setVisible], | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const status = attachment.get('status') as Status; | ||||||
|  |   const description = (attachment.getIn(['translation', 'description']) || | ||||||
|  |     attachment.get('description')) as string | undefined; | ||||||
|  |   const previewUrl = attachment.get('preview_url') as string; | ||||||
|  |   const fullUrl = attachment.get('url') as string; | ||||||
|  |   const avatarUrl = status.getIn(['account', 'avatar_static']) as string; | ||||||
|  |   const lang = status.get('language') as string; | ||||||
|  |   const blurhash = attachment.get('blurhash') as string; | ||||||
|  |   const statusId = status.get('id') as string; | ||||||
|  |   const acct = status.getIn(['account', 'acct']) as string; | ||||||
|  |   const type = attachment.get('type') as string; | ||||||
|  | 
 | ||||||
|  |   let thumbnail; | ||||||
|  | 
 | ||||||
|  |   const badges = []; | ||||||
|  | 
 | ||||||
|  |   if (description && description.length > 0) { | ||||||
|  |     badges.push( | ||||||
|  |       <span key='alt' className='media-gallery__alt__label'> | ||||||
|  |         ALT | ||||||
|  |       </span>, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!visible) { | ||||||
|  |     thumbnail = ( | ||||||
|  |       <div className='media-gallery__item__overlay'> | ||||||
|  |         <Icon id='eye-slash' icon={VisibilityOffIcon} /> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } else if (type === 'audio') { | ||||||
|  |     thumbnail = ( | ||||||
|  |       <> | ||||||
|  |         <img | ||||||
|  |           src={previewUrl || avatarUrl} | ||||||
|  |           alt={description} | ||||||
|  |           title={description} | ||||||
|  |           lang={lang} | ||||||
|  |           onLoad={handleImageLoad} | ||||||
|  |         /> | ||||||
|  | 
 | ||||||
|  |         <div className='media-gallery__item__overlay media-gallery__item__overlay--corner'> | ||||||
|  |           <Icon id='music' icon={HeadphonesIcon} /> | ||||||
|  |         </div> | ||||||
|  |       </> | ||||||
|  |     ); | ||||||
|  |   } else if (type === 'image') { | ||||||
|  |     const focusX = (attachment.getIn(['meta', 'focus', 'x']) || 0) as number; | ||||||
|  |     const focusY = (attachment.getIn(['meta', 'focus', 'y']) || 0) as number; | ||||||
|  |     const x = (focusX / 2 + 0.5) * 100; | ||||||
|  |     const y = (focusY / -2 + 0.5) * 100; | ||||||
|  | 
 | ||||||
|  |     thumbnail = ( | ||||||
|  |       <img | ||||||
|  |         src={previewUrl} | ||||||
|  |         alt={description} | ||||||
|  |         title={description} | ||||||
|  |         lang={lang} | ||||||
|  |         style={{ objectPosition: `${x}% ${y}%` }} | ||||||
|  |         onLoad={handleImageLoad} | ||||||
|  |       /> | ||||||
|  |     ); | ||||||
|  |   } else if (['video', 'gifv'].includes(type)) { | ||||||
|  |     const duration = attachment.getIn([ | ||||||
|  |       'meta', | ||||||
|  |       'original', | ||||||
|  |       'duration', | ||||||
|  |     ]) as number; | ||||||
|  | 
 | ||||||
|  |     thumbnail = ( | ||||||
|  |       <div className='media-gallery__gifv'> | ||||||
|  |         <video | ||||||
|  |           className='media-gallery__item-gifv-thumbnail' | ||||||
|  |           aria-label={description} | ||||||
|  |           title={description} | ||||||
|  |           lang={lang} | ||||||
|  |           src={fullUrl} | ||||||
|  |           onMouseEnter={handleMouseEnter} | ||||||
|  |           onMouseLeave={handleMouseLeave} | ||||||
|  |           onLoadedData={handleImageLoad} | ||||||
|  |           autoPlay={autoPlayGif} | ||||||
|  |           playsInline | ||||||
|  |           loop | ||||||
|  |           muted | ||||||
|  |         /> | ||||||
|  | 
 | ||||||
|  |         {type === 'video' && ( | ||||||
|  |           <div className='media-gallery__item__overlay media-gallery__item__overlay--corner'> | ||||||
|  |             <Icon id='play' icon={MovieIcon} /> | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     if (type === 'gifv') { | ||||||
|  |       badges.push( | ||||||
|  |         <span key='gif' className='media-gallery__gifv__label'> | ||||||
|  |           GIF | ||||||
|  |         </span>, | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       badges.push( | ||||||
|  |         <span key='video' className='media-gallery__gifv__label'> | ||||||
|  |           {formatTime(Math.floor(duration))} | ||||||
|  |         </span>, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <div className='media-gallery__item media-gallery__item--square'> | ||||||
|  |       <Blurhash | ||||||
|  |         hash={blurhash} | ||||||
|  |         className={classNames('media-gallery__preview', { | ||||||
|  |           'media-gallery__preview--hidden': visible && loaded, | ||||||
|  |         })} | ||||||
|  |         dummy={!useBlurhash} | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |       <a | ||||||
|  |         className='media-gallery__item-thumbnail' | ||||||
|  |         href={`/@${acct}/${statusId}`} | ||||||
|  |         onClick={handleClick} | ||||||
|  |         target='_blank' | ||||||
|  |         rel='noopener noreferrer' | ||||||
|  |       > | ||||||
|  |         {thumbnail} | ||||||
|  |       </a> | ||||||
|  | 
 | ||||||
|  |       {badges.length > 0 && ( | ||||||
|  |         <div className='media-gallery__item__badges'>{badges}</div> | ||||||
|  |       )} | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -20,7 +20,7 @@ import { expandAccountMediaTimeline } from '../../actions/timelines'; | |||||||
| import HeaderContainer from '../account_timeline/containers/header_container'; | import HeaderContainer from '../account_timeline/containers/header_container'; | ||||||
| import Column from '../ui/components/column'; | import Column from '../ui/components/column'; | ||||||
| 
 | 
 | ||||||
| import MediaItem from './components/media_item'; | import { MediaItem } from './components/media_item'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, { params: { acct, id } }) => { | const mapStateToProps = (state, { params: { acct, id } }) => { | ||||||
|   const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]); |   const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]); | ||||||
|  | |||||||
| @ -151,7 +151,25 @@ export const DetailedStatus: React.FC<{ | |||||||
|   if (pictureInPicture.get('inUse')) { |   if (pictureInPicture.get('inUse')) { | ||||||
|     media = <PictureInPicturePlaceholder aspectRatio={attachmentAspectRatio} />; |     media = <PictureInPicturePlaceholder aspectRatio={attachmentAspectRatio} />; | ||||||
|   } else if (status.get('media_attachments').size > 0) { |   } else if (status.get('media_attachments').size > 0) { | ||||||
|     if (status.getIn(['media_attachments', 0, 'type']) === 'audio') { |     if ( | ||||||
|  |       ['image', 'gifv'].includes( | ||||||
|  |         status.getIn(['media_attachments', 0, 'type']) as string, | ||||||
|  |       ) || | ||||||
|  |       status.get('media_attachments').size > 1 | ||||||
|  |     ) { | ||||||
|  |       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.getIn(['media_attachments', 0, 'type']) === 'audio') { | ||||||
|       const attachment = status.getIn(['media_attachments', 0]); |       const attachment = status.getIn(['media_attachments', 0]); | ||||||
|       const description = |       const description = | ||||||
|         attachment.getIn(['translation', 'description']) || |         attachment.getIn(['translation', 'description']) || | ||||||
| @ -200,19 +218,6 @@ export const DetailedStatus: React.FC<{ | |||||||
|           onToggleVisibility={onToggleMediaVisibility} |           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) { |   } else if (status.get('spoiler_text').length === 0) { | ||||||
|     media = ( |     media = ( | ||||||
|  | |||||||
| @ -10,3 +10,5 @@ export type Status = Immutable.Map<string, unknown>; | |||||||
| type CardShape = Required<ApiPreviewCardJSON>; | type CardShape = Required<ApiPreviewCardJSON>; | ||||||
| 
 | 
 | ||||||
| export type Card = RecordOf<CardShape>; | export type Card = RecordOf<CardShape>; | ||||||
|  | 
 | ||||||
|  | export type MediaAttachment = Immutable.Map<string, unknown>; | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M612-516q25 0 42.5-17.5T672-576q0-25-17.5-42.5T612-636q-25 0-42.5 17.5T552-576q0 25 17.5 42.5T612-516Zm-264 0q25 0 42.5-17.5T408-576q0-25-17.5-42.5T348-636q-25 0-42.5 17.5T288-576q0 25 17.5 42.5T348-516Zm132 228q62 0 114.5-29t75.5-86H290q23 57 75.5 86T480-288Zm.276 192Q401-96 331-126q-70-30-122.5-82.5T126-330.958q-30-69.959-30-149.5Q96-560 126-629.5t82.5-122Q261-804 330.958-834q69.959-30 149.5-30Q560-864 629.5-834t122 82.5Q804-699 834-629.276q30 69.725 30 149Q864-401 834-331q-30 70-82.5 122.5T629.276-126q-69.725 30-149 30Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M612-516q25 0 42.5-17.5T672-576q0-25-17.5-42.5T612-636q-25 0-42.5 17.5T552-576q0 25 17.5 42.5T612-516Zm-264 0q25 0 42.5-17.5T408-576q0-25-17.5-42.5T348-636q-25 0-42.5 17.5T288-576q0 25 17.5 42.5T348-516Zm132 228q62 0 114.5-29t75.5-86H290q23 57 75.5 86T480-288Zm.28 192Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30Z"/></svg> | ||||||
| Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 612 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M612-516q25 0 42.5-17.5T672-576q0-25-17.5-42.5T612-636q-25 0-42.5 17.5T552-576q0 25 17.5 42.5T612-516Zm-264 0q25 0 42.5-17.5T408-576q0-25-17.5-42.5T348-636q-25 0-42.5 17.5T288-576q0 25 17.5 42.5T348-516Zm132 228q60 0 110.5-31t79.5-84H290q29 53 79.5 84T480-288Zm.276 192Q401-96 331-126q-70-30-122.5-82.5T126-330.958q-30-69.959-30-149.5Q96-560 126-629.5t82.5-122Q261-804 330.958-834q69.959-30 149.5-30Q560-864 629.5-834t122 82.5Q804-699 834-629.276q30 69.725 30 149Q864-401 834-331q-30 70-82.5 122.5T629.276-126q-69.725 30-149 30ZM480-480Zm0 312q130 0 221-91t91-221q0-130-91-221t-221-91q-130 0-221 91t-91 221q0 130 91 221t221 91Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M612-516q25 0 42.5-17.5T672-576q0-25-17.5-42.5T612-636q-25 0-42.5 17.5T552-576q0 25 17.5 42.5T612-516Zm-264 0q25 0 42.5-17.5T408-576q0-25-17.5-42.5T348-636q-25 0-42.5 17.5T288-576q0 25 17.5 42.5T348-516Zm132 228q60 0 110.5-31t79.5-84H290q29 53 79.5 84T480-288Zm.28 192Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30ZM480-480Zm0 312q130 0 221-91t91-221q0-130-91-221t-221-91q-130 0-221 91t-91 221q0 130 91 221t221 91Z"/></svg> | ||||||
| Before Width: | Height: | Size: 733 B After Width: | Height: | Size: 711 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="m48-144 432-720 432 720H48Zm431.789-120Q495-264 505.5-274.289q10.5-10.29 10.5-25.5Q516-315 505.711-325.5q-10.29-10.5-25.5-10.5Q465-336 454.5-325.711q-10.5 10.29-10.5 25.5Q444-285 454.289-274.5q10.29 10.5 25.5 10.5ZM444-384h72v-192h-72v192Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="m48-144 432-720 432 720H48Zm431.79-120q15.21 0 25.71-10.29t10.5-25.5q0-15.21-10.29-25.71t-25.5-10.5q-15.21 0-25.71 10.29t-10.5 25.5q0 15.21 10.29 25.71t25.5 10.5ZM444-384h72v-192h-72v192Z"/></svg> | ||||||
| Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 293 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="m48-144 432-720 432 720H48Zm127-72h610L480-724 175-216Zm304.789-48Q495-264 505.5-274.289q10.5-10.29 10.5-25.5Q516-315 505.711-325.5q-10.29-10.5-25.5-10.5Q465-336 454.5-325.711q-10.5 10.29-10.5 25.5Q444-285 454.289-274.5q10.29 10.5 25.5 10.5ZM444-384h72v-192h-72v192Zm36-86Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="m48-144 432-720 432 720H48Zm127-72h610L480-724 175-216Zm304.79-48q15.21 0 25.71-10.29t10.5-25.5q0-15.21-10.29-25.71t-25.5-10.5q-15.21 0-25.71 10.29t-10.5 25.5q0 15.21 10.29 25.71t25.5 10.5ZM444-384h72v-192h-72v192Zm36-86Z"/></svg> | ||||||
| Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 327 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h360v200h80v80h200v360q0 33-23.5 56.5T760-120H200Zm40-160h480L570-480 450-320l-90-120-120 160Zm440-320v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h360q-20 26-30 57t-10 63q0 83 58.5 141.5T720-520q32 0 63-10t57-30v360q0 33-23.5 56.5T760-120H200Zm40-160h480L570-480 450-320l-90-120-120 160Zm440-320v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80Z"/></svg> | ||||||
| Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 358 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h360v80H200v560h560v-360h80v360q0 33-23.5 56.5T760-120H200Zm480-480v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80ZM240-280h480L570-480 450-320l-90-120-120 160Zm-40-480v560-560Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-480ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h320v80H200v560h560v-320h80v320q0 33-23.5 56.5T760-120H200Zm40-160h480L570-480 450-320l-90-120-120 160Zm440-320v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80Z"/></svg> | ||||||
| Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 329 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M120-40v-640q0-33 23.5-56.5T200-760h400q33 0 56.5 23.5T680-680v640L400-160 120-40Zm640-120v-680H240v-80h520q33 0 56.5 23.5T840-840v680h-80Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-80v-560q0-33 23.5-56.5T240-720h320q33 0 56.5 23.5T640-640v560L400-200 160-80Zm560-160v-560H280v-80h440q33 0 56.5 23.5T800-800v560h-80Z"/></svg> | ||||||
| Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M120-40v-640q0-33 23.5-56.5T200-760h400q33 0 56.5 23.5T680-680v640L400-160 120-40Zm80-122 200-86 200 86v-518H200v518Zm560 2v-680H240v-80h520q33 0 56.5 23.5T840-840v680h-80ZM200-680h400-400Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-80v-560q0-33 23.5-56.5T240-720h320q33 0 56.5 23.5T640-640v560L400-200 160-80Zm80-121 160-86 160 86v-439H240v439Zm480-39v-560H280v-80h440q33 0 56.5 23.5T800-800v560h-80ZM240-640h320-320Z"/></svg> | ||||||
| Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 296 B | 
| @ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M360-120H200q-33 0-56.5-23.5T120-200v-280q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480v280q0 33-23.5 56.5T760-120H600v-320h160v-40q0-117-81.5-198.5T480-760q-117 0-198.5 81.5T200-480v40h160v320Z"/></svg> | ||||||
| After Width: | Height: | Size: 350 B | 
							
								
								
									
										1
									
								
								app/javascript/material-icons/400-24px/headphones.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M360-120H200q-33 0-56.5-23.5T120-200v-280q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480v280q0 33-23.5 56.5T760-120H600v-320h160v-40q0-117-81.5-198.5T480-760q-117 0-198.5 81.5T200-480v40h160v320Zm-80-240h-80v160h80v-160Zm400 0v160h80v-160h-80Zm-400 0h-80 80Zm400 0h80-80Z"/></svg> | ||||||
| After Width: | Height: | Size: 426 B | 
| @ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m234-480-12-60q-12-5-22.5-10.5T178-564l-58 18-40-68 46-40q-2-13-2-26t2-26l-46-40 40-68 58 18q11-8 21.5-13.5T222-820l12-60h80l12 60q12 5 22.5 10.5T370-796l58-18 40 68-46 40q2 13 2 26t-2 26l46 40-40 68-58-18q-11 8-21.5 13.5T326-540l-12 60h-80Zm40-120q33 0 56.5-23.5T354-680q0-33-23.5-56.5T274-760q-33 0-56.5 23.5T194-680q0 33 23.5 56.5T274-600ZM592-40l-18-84q-17-6-31.5-14.5T514-158l-80 26-56-96 64-56q-2-18-2-36t2-36l-64-56 56-96 80 26q14-11 28.5-19.5T574-516l18-84h112l18 84q17 6 31.5 14.5T782-482l80-26 56 96-64 56q2 18 2 36t-2 36l64 56-56 96-80-26q-14 11-28.5 19.5T722-124l-18 84H592Zm56-160q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Z"/></svg> | ||||||
| After Width: | Height: | Size: 771 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m234-480-12-60q-12-5-22.5-10.5T178-564l-58 18-40-68 46-40q-2-13-2-26t2-26l-46-40 40-68 58 18q11-8 21.5-13.5T222-820l12-60h80l12 60q12 5 22.5 10.5T370-796l58-18 40 68-46 40q2 13 2 26t-2 26l46 40-40 68-58-18q-11 8-21.5 13.5T326-540l-12 60h-80Zm40-120q33 0 56.5-23.5T354-680q0-33-23.5-56.5T274-760q-33 0-56.5 23.5T194-680q0 33 23.5 56.5T274-600ZM592-40l-18-84q-17-6-31.5-14.5T514-158l-80 26-56-96 64-56q-2-18-2-36t2-36l-64-56 56-96 80 26q14-11 28.5-19.5T574-516l18-84h112l18 84q17 6 31.5 14.5T782-482l80-26 56 96-64 56q2 18 2 36t-2 36l64 56-56 96-80-26q-14 11-28.5 19.5T722-124l-18 84H592Zm56-160q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m234-480-12-60q-12-5-22.5-10.5T178-564l-58 18-40-68 46-40q-2-13-2-26t2-26l-46-40 40-68 58 18q11-8 21.5-13.5T222-820l12-60h80l12 60q12 5 22.5 10.5T370-796l58-18 40 68-46 40q2 13 2 26t-2 26l46 40-40 68-58-18q-11 8-21.5 13.5T326-540l-12 60h-80Zm40-120q33 0 56.5-23.5T354-680q0-33-23.5-56.5T274-760q-33 0-56.5 23.5T194-680q0 33 23.5 56.5T274-600ZM592-40l-18-84q-17-6-31.5-14.5T514-158l-80 26-56-96 64-56q-2-18-2-36t2-36l-64-56 56-96 80 26q14-11 28.5-19.5T574-516l18-84h112l18 84q17 6 31.5 14.5T782-482l80-26 56 96-64 56q2 18 2 36t-2 36l64 56-56 96-80-26q-14 11-28.5 19.5T722-124l-18 84H592Zm56-160q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Z"/></svg> | ||||||
| Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 771 B | 
							
								
								
									
										1
									
								
								app/javascript/material-icons/400-24px/movie-fill.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m160-800 80 160h120l-80-160h80l80 160h120l-80-160h80l80 160h120l-80-160h120q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800Z"/></svg> | ||||||
| After Width: | Height: | Size: 287 B | 
							
								
								
									
										1
									
								
								app/javascript/material-icons/400-24px/movie.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m160-800 80 160h120l-80-160h80l80 160h120l-80-160h80l80 160h120l-80-160h120q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800Zm0 240v320h640v-320H160Zm0 0v320-320Z"/></svg> | ||||||
| After Width: | Height: | Size: 324 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M524-40q-84 0-157.5-32t-128-86.5Q184-213 152-286.5T120-444q0-146 93-257.5T450-840q-18 99 11 193.5T561-481q71 71 165.5 100T920-370q-26 144-138 237T524-40Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M484-80q-84 0-157.5-32t-128-86.5Q144-253 112-326.5T80-484q0-146 93-257.5T410-880q-18 99 11 193.5T521-521q71 71 165.5 100T880-410q-26 144-138 237T484-80Z"/></svg> | ||||||
| Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 258 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M524-40q-84 0-157.5-32t-128-86.5Q184-213 152-286.5T120-444q0-146 93-257.5T450-840q-18 99 11 193.5T561-481q71 71 165.5 100T920-370q-26 144-138 237T524-40Zm0-80q88 0 163-44t118-121q-86-8-163-43.5T504-425q-61-61-97-138t-43-163q-77 43-120.5 118.5T200-444q0 135 94.5 229.5T524-120Zm-20-305Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M484-80q-84 0-157.5-32t-128-86.5Q144-253 112-326.5T80-484q0-146 93-257.5T410-880q-18 99 11 193.5T521-521q71 71 165.5 100T880-410q-26 144-138 237T484-80Zm0-80q88 0 163-44t118-121q-86-8-163-43.5T464-465q-61-61-97-138t-43-163q-77 43-120.5 118.5T160-484q0 135 94.5 229.5T484-160Zm-20-305Z"/></svg> | ||||||
| Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 390 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M720-80q-50 0-85-35t-35-85q0-7 1-14.5t3-13.5L322-392q-17 15-38 23.5t-44 8.5q-50 0-85-35t-35-85q0-50 35-85t85-35q23 0 44 8.5t38 23.5l282-164q-2-6-3-13.5t-1-14.5q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35q-23 0-44-8.5T638-672L356-508q2 6 3 13.5t1 14.5q0 7-1 14.5t-3 13.5l282 164q17-15 38-23.5t44-8.5q50 0 85 35t35 85q0 50-35 85t-85 35Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M680-80q-50 0-85-35t-35-85q0-6 3-28L282-392q-16 15-37 23.5t-45 8.5q-50 0-85-35t-35-85q0-50 35-85t85-35q24 0 45 8.5t37 23.5l281-164q-2-7-2.5-13.5T560-760q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35q-24 0-45-8.5T598-672L317-508q2 7 2.5 13.5t.5 14.5q0 8-.5 14.5T317-452l281 164q16-15 37-23.5t45-8.5q50 0 85 35t35 85q0 50-35 85t-85 35Z"/></svg> | ||||||
| Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 445 B | 
| @ -1 +1 @@ | |||||||
| <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M720-80q-50 0-85-35t-35-85q0-7 1-14.5t3-13.5L322-392q-17 15-38 23.5t-44 8.5q-50 0-85-35t-35-85q0-50 35-85t85-35q23 0 44 8.5t38 23.5l282-164q-2-6-3-13.5t-1-14.5q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35q-23 0-44-8.5T638-672L356-508q2 6 3 13.5t1 14.5q0 7-1 14.5t-3 13.5l282 164q17-15 38-23.5t44-8.5q50 0 85 35t35 85q0 50-35 85t-85 35Zm0-640q17 0 28.5-11.5T760-760q0-17-11.5-28.5T720-800q-17 0-28.5 11.5T680-760q0 17 11.5 28.5T720-720ZM240-440q17 0 28.5-11.5T280-480q0-17-11.5-28.5T240-520q-17 0-28.5 11.5T200-480q0 17 11.5 28.5T240-440Zm480 280q17 0 28.5-11.5T760-200q0-17-11.5-28.5T720-240q-17 0-28.5 11.5T680-200q0 17 11.5 28.5T720-160Zm0-600ZM240-480Zm480 280Z"/></svg> | <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M680-80q-50 0-85-35t-35-85q0-6 3-28L282-392q-16 15-37 23.5t-45 8.5q-50 0-85-35t-35-85q0-50 35-85t85-35q24 0 45 8.5t37 23.5l281-164q-2-7-2.5-13.5T560-760q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35q-24 0-45-8.5T598-672L317-508q2 7 2.5 13.5t.5 14.5q0 8-.5 14.5T317-452l281 164q16-15 37-23.5t45-8.5q50 0 85 35t35 85q0 50-35 85t-85 35Zm0-80q17 0 28.5-11.5T720-200q0-17-11.5-28.5T680-240q-17 0-28.5 11.5T640-200q0 17 11.5 28.5T680-160ZM200-440q17 0 28.5-11.5T240-480q0-17-11.5-28.5T200-520q-17 0-28.5 11.5T160-480q0 17 11.5 28.5T200-440Zm480-280q17 0 28.5-11.5T720-760q0-17-11.5-28.5T680-800q-17 0-28.5 11.5T640-760q0 17 11.5 28.5T680-720Zm0 520ZM200-480Zm480-280Z"/></svg> | ||||||
| Before Width: | Height: | Size: 777 B After Width: | Height: | Size: 773 B | 
| @ -242,6 +242,7 @@ | |||||||
|   flex: 0 0 auto; |   flex: 0 0 auto; | ||||||
| 
 | 
 | ||||||
|   a { |   a { | ||||||
|  |     display: flex; | ||||||
|     color: inherit; |     color: inherit; | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
|   } |   } | ||||||
| @ -5821,6 +5822,7 @@ a.status-card { | |||||||
|       .icon { |       .icon { | ||||||
|         width: 24px; |         width: 24px; | ||||||
|         height: 24px; |         height: 24px; | ||||||
|  |         filter: var(--overlay-icon-shadow); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       &:hover, |       &:hover, | ||||||
| @ -5915,6 +5917,10 @@ a.status-card { | |||||||
|     .icon-button { |     .icon-button { | ||||||
|       color: $white; |       color: $white; | ||||||
| 
 | 
 | ||||||
|  |       .icon { | ||||||
|  |         filter: var(--overlay-icon-shadow); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       &:hover, |       &:hover, | ||||||
|       &:focus, |       &:focus, | ||||||
|       &:active { |       &:active { | ||||||
| @ -5973,6 +5979,7 @@ a.status-card { | |||||||
| .media-modal__page-dot { | .media-modal__page-dot { | ||||||
|   flex: 0 0 auto; |   flex: 0 0 auto; | ||||||
|   background-color: $white; |   background-color: $white; | ||||||
|  |   filter: var(--overlay-icon-shadow); | ||||||
|   opacity: 0.4; |   opacity: 0.4; | ||||||
|   height: 6px; |   height: 6px; | ||||||
|   width: 6px; |   width: 6px; | ||||||
| @ -7053,8 +7060,8 @@ a.status-card { | |||||||
|   width: 100%; |   width: 100%; | ||||||
|   min-height: 64px; |   min-height: 64px; | ||||||
|   display: grid; |   display: grid; | ||||||
|   grid-template-columns: 50% 50%; |   grid-template-columns: 1fr 1fr; | ||||||
|   grid-template-rows: 50% 50%; |   grid-template-rows: 1fr 1fr; | ||||||
|   gap: 2px; |   gap: 2px; | ||||||
| 
 | 
 | ||||||
|   &--layout-2 { |   &--layout-2 { | ||||||
| @ -7123,6 +7130,9 @@ a.status-card { | |||||||
|   position: relative; |   position: relative; | ||||||
|   border-radius: 8px; |   border-radius: 8px; | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|  |   outline: 1px solid var(--media-outline-color); | ||||||
|  |   outline-offset: -1px; | ||||||
|  |   z-index: 1; | ||||||
| 
 | 
 | ||||||
|   &--tall { |   &--tall { | ||||||
|     grid-row: span 2; |     grid-row: span 2; | ||||||
| @ -7131,15 +7141,44 @@ a.status-card { | |||||||
|   &--wide { |   &--wide { | ||||||
|     grid-column: span 2; |     grid-column: span 2; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   &--square { | ||||||
|  |     aspect-ratio: 1; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   &__overlay { | ||||||
|  |     position: absolute; | ||||||
|  |     top: 0; | ||||||
|  |     inset-inline-start: 0; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: center; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100%; | ||||||
|  |     pointer-events: none; | ||||||
|  |     padding: 8px; | ||||||
|  |     z-index: 1; | ||||||
|  | 
 | ||||||
|  |     &--corner { | ||||||
|  |       align-items: flex-start; | ||||||
|  |       justify-content: flex-end; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .icon { | ||||||
|  |       color: $white; | ||||||
|  |       filter: var(--overlay-icon-shadow); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .media-gallery__item-thumbnail { | .media-gallery__item-thumbnail { | ||||||
|   cursor: zoom-in; |   cursor: pointer; | ||||||
|   display: block; |   display: block; | ||||||
|   text-decoration: none; |   text-decoration: none; | ||||||
|   color: $secondary-text-color; |   color: $secondary-text-color; | ||||||
|   position: relative; |   position: relative; | ||||||
|   z-index: 1; |   z-index: -1; | ||||||
| 
 | 
 | ||||||
|   &, |   &, | ||||||
|   img { |   img { | ||||||
| @ -7159,7 +7198,7 @@ a.status-card { | |||||||
|   position: absolute; |   position: absolute; | ||||||
|   top: 0; |   top: 0; | ||||||
|   inset-inline-start: 0; |   inset-inline-start: 0; | ||||||
|   z-index: 0; |   z-index: -2; | ||||||
|   background: $base-overlay-background; |   background: $base-overlay-background; | ||||||
| 
 | 
 | ||||||
|   &--hidden { |   &--hidden { | ||||||
| @ -7172,22 +7211,16 @@ a.status-card { | |||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|   position: relative; |   position: relative; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|  |   z-index: -1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .media-gallery__item-gifv-thumbnail { | .media-gallery__item-gifv-thumbnail { | ||||||
|   cursor: zoom-in; |   cursor: pointer; | ||||||
|   height: 100%; |   height: 100%; | ||||||
|   object-fit: cover; |   object-fit: cover; | ||||||
|   width: 100%; |   width: 100%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .media-gallery__item-thumbnail-label { |  | ||||||
|   clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ |  | ||||||
|   clip: rect(1px, 1px, 1px, 1px); |  | ||||||
|   overflow: hidden; |  | ||||||
|   position: absolute; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* End Media Gallery */ | /* End Media Gallery */ | ||||||
| 
 | 
 | ||||||
| .detailed, | .detailed, | ||||||
| @ -7210,6 +7243,8 @@ a.status-card { | |||||||
|   border-radius: 8px; |   border-radius: 8px; | ||||||
|   padding-bottom: 44px; |   padding-bottom: 44px; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|  |   outline: 1px solid var(--media-outline-color); | ||||||
|  |   outline-offset: -1px; | ||||||
| 
 | 
 | ||||||
|   &.editable { |   &.editable { | ||||||
|     border-radius: 0; |     border-radius: 0; | ||||||
| @ -7266,6 +7301,7 @@ a.status-card { | |||||||
|   .video-player__controls { |   .video-player__controls { | ||||||
|     padding-top: 10px; |     padding-top: 10px; | ||||||
|     background: transparent; |     background: transparent; | ||||||
|  |     z-index: 1; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -7279,19 +7315,18 @@ a.status-card { | |||||||
|   color: $white; |   color: $white; | ||||||
|   display: flex; |   display: flex; | ||||||
|   align-items: center; |   align-items: center; | ||||||
|  |   outline: 1px solid var(--media-outline-color); | ||||||
|  |   outline-offset: -1px; | ||||||
|  |   z-index: 2; | ||||||
| 
 | 
 | ||||||
|   &.editable { |   &.editable { | ||||||
|     border-radius: 0; |     border-radius: 0; | ||||||
|     height: 100% !important; |     height: 100% !important; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &:focus { |  | ||||||
|     outline: 0; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   video { |   video { | ||||||
|     display: block; |     display: block; | ||||||
|     z-index: 1; |     z-index: -2; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &.fullscreen { |   &.fullscreen { | ||||||
| @ -7310,7 +7345,7 @@ a.status-card { | |||||||
|   &__controls { |   &__controls { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     direction: ltr; |     direction: ltr; | ||||||
|     z-index: 2; |     z-index: -1; | ||||||
|     bottom: 0; |     bottom: 0; | ||||||
|     inset-inline-start: 0; |     inset-inline-start: 0; | ||||||
|     inset-inline-end: 0; |     inset-inline-end: 0; | ||||||
| @ -7625,26 +7660,16 @@ a.status-card { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .account-gallery__container { | .account-gallery__container { | ||||||
|   display: flex; |   display: grid; | ||||||
|   flex-wrap: wrap; |   grid-template-columns: 1fr 1fr 1fr; | ||||||
|   padding: 4px 2px; |   gap: 2px; | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .account-gallery__item { |   .media-gallery__item { | ||||||
|   border: 0; |     border-radius: 0; | ||||||
|   box-sizing: border-box; |   } | ||||||
|   display: block; |  | ||||||
|   position: relative; |  | ||||||
|   border-radius: 4px; |  | ||||||
|   overflow: hidden; |  | ||||||
|   margin: 2px; |  | ||||||
| 
 | 
 | ||||||
|   &__icons { |   .load-more { | ||||||
|     position: absolute; |     grid-column: span 3; | ||||||
|     top: 50%; |  | ||||||
|     inset-inline-start: 50%; |  | ||||||
|     transform: translate(-50%, -50%); |  | ||||||
|     font-size: 24px; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -111,6 +111,8 @@ $font-monospace: 'mastodon-font-monospace' !default; | |||||||
|   --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)}; |   --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)}; | ||||||
|   --on-surface-color: #{transparentize($ui-base-color, 0.5)}; |   --on-surface-color: #{transparentize($ui-base-color, 0.5)}; | ||||||
|   --avatar-border-radius: 8px; |   --avatar-border-radius: 8px; | ||||||
|  |   --media-outline-color: #{rgba(#fcf8ff, 0.15)}; | ||||||
|  |   --overlay-icon-shadow: drop-shadow(0 0 8px #{rgba($base-shadow-color, 0.25)}); | ||||||
|   --error-background-color: #{darken($error-red, 16%)}; |   --error-background-color: #{darken($error-red, 16%)}; | ||||||
|   --error-active-background-color: #{darken($error-red, 12%)}; |   --error-active-background-color: #{darken($error-red, 12%)}; | ||||||
|   --on-error-color: #fff; |   --on-error-color: #fff; | ||||||
|  | |||||||