diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2023-07-30 03:35:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-30 03:35:17 +0200 |
commit | d4807a5e645b9363c7f8d6702186df4325bc5a97 (patch) | |
tree | 7e9a323edf8aad5263ef0da0f65c71e9adb6e4c6 /app | |
parent | d76f79f64706f6e9f48d9a9cef2f049f1aed09e5 (diff) |
Change aspect ratios on link previews in web UI (#26250)
Diffstat (limited to 'app')
-rw-r--r-- | app/javascript/mastodon/features/status/components/card.jsx | 82 | ||||
-rw-r--r-- | app/javascript/styles/mastodon/components.scss | 76 |
2 files changed, 97 insertions, 61 deletions
diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index 29a12c87bfe..6ac3c1d0f29 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -5,7 +5,7 @@ import { PureComponent } from 'react'; import { FormattedMessage } from 'react-intl'; -import classnames from 'classnames'; +import classNames from 'classnames'; import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -71,6 +71,7 @@ export default class Card extends PureComponent { if (!Immutable.is(this.props.card, nextProps.card)) { this.setState({ embedded: false, previewLoaded: false }); } + if (this.props.sensitive !== nextProps.sensitive) { this.setState({ revealed: !nextProps.sensitive }); } @@ -84,35 +85,8 @@ export default class Card extends PureComponent { window.removeEventListener('resize', this.handleResize); } - handlePhotoClick = () => { - const { card, onOpenMedia } = this.props; - - onOpenMedia( - Immutable.fromJS([ - { - type: 'image', - url: card.get('embed_url'), - description: card.get('title'), - meta: { - original: { - width: card.get('width'), - height: card.get('height'), - }, - }, - }, - ]), - 0, - ); - }; - handleEmbedClick = () => { - const { card } = this.props; - - if (card.get('type') === 'photo') { - this.handlePhotoClick(); - } else { - this.setState({ embedded: true }); - } + this.setState({ embedded: true }); }; setRef = c => { @@ -130,15 +104,15 @@ export default class Card extends PureComponent { }; renderVideo () { - const { card } = this.props; - const content = { __html: addAutoPlay(card.get('html')) }; + const { card } = this.props; + const content = { __html: addAutoPlay(card.get('html')) }; return ( <div ref={this.setRef} className='status-card__image status-card-video' dangerouslySetInnerHTML={content} - style={{ aspectRatio: `${card.get('width')} / ${card.get('height')}` }} + style={{ aspectRatio: '16 / 9' }} /> ); } @@ -152,30 +126,40 @@ export default class Card extends PureComponent { } const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); - const interactive = card.get('type') !== 'link'; + const interactive = card.get('type') === 'video'; const language = card.get('language') || ''; + const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive; const description = ( <div className='status-card__content'> <span className='status-card__host'> <span lang={language}>{provider}</span> {card.get('published_at') && <> ยท <RelativeTimestamp timestamp={card.get('published_at')} /></>} - </span> + </span> + <strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong> - {card.get('author_name').length > 0 && <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span>} + + {card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description'>{card.get('description')}</span>} </div> ); const thumbnailStyle = { visibility: revealed ? null : 'hidden', - aspectRatio: `${card.get('width')} / ${card.get('height')}` }; + if (largeImage && card.get('type') === 'video') { + thumbnailStyle.aspectRatio = `16 / 9`; + } else if (largeImage) { + thumbnailStyle.aspectRatio = '1.91 / 1'; + } else { + thumbnailStyle.aspectRatio = 1; + } + let embed; let canvas = ( <Blurhash - className={classnames('status-card__image-preview', { + className={classNames('status-card__image-preview', { 'status-card__image-preview--hidden': revealed && this.state.previewLoaded, })} hash={card.get('blurhash')} @@ -195,7 +179,7 @@ export default class Card extends PureComponent { ); spoilerButton = ( - <div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}> + <div className={classNames('spoiler-button', { 'spoiler-button--minified': revealed })}> {spoilerButton} </div> ); @@ -204,33 +188,25 @@ export default class Card extends PureComponent { if (embedded) { embed = this.renderVideo(); } else { - let iconVariant = 'play'; - - if (card.get('type') === 'photo') { - iconVariant = 'search-plus'; - } - embed = ( <div className='status-card__image'> {canvas} {thumbnail} - {revealed && ( - <div className='status-card__actions'> + {revealed ? ( + <div className='status-card__actions' onClick={this.handleEmbedClick} role='none'> <div> - <button type='button' onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button> + <button type='button' onClick={this.handleEmbedClick}><Icon id='play' /></button> <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a> </div> </div> - )} - - {!revealed && spoilerButton} + ) : spoilerButton} </div> ); } return ( - <div className='status-card' ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}> + <div className={classNames('status-card', { expanded: largeImage })} ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}> {embed} <a href={card.get('url')} target='_blank' rel='noopener noreferrer'>{description}</a> </div> @@ -244,14 +220,14 @@ export default class Card extends PureComponent { ); } else { embed = ( - <div className='status-card__image' style={{ aspectRatio: '1.9 / 1' }}> + <div className='status-card__image'> <Icon id='file-text' /> </div> ); } return ( - <a href={card.get('url')} className='status-card' target='_blank' rel='noopener noreferrer' ref={this.setRef}> + <a href={card.get('url')} className={classNames('status-card', { expanded: largeImage })} target='_blank' rel='noopener noreferrer' ref={this.setRef}> {embed} {description} </a> diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index d16380de5bf..bc325edf6c4 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3510,13 +3510,16 @@ button.icon-button.active i.fa-retweet { } .status-card { - display: block; + display: flex; + align-items: center; position: relative; font-size: 14px; color: $darker-text-color; margin-top: 14px; text-decoration: none; overflow: hidden; + border: 1px solid lighten($ui-base-color, 8%); + border-radius: 8px; &__actions { bottom: 0; @@ -3527,11 +3530,13 @@ button.icon-button.active i.fa-retweet { display: flex; justify-content: center; align-items: center; + cursor: pointer; & > div { background: rgba($base-shadow-color, 0.6); border-radius: 8px; padding: 12px 9px; + backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%); flex: 0 0 auto; display: flex; justify-content: center; @@ -3572,7 +3577,8 @@ a.status-card { &:active { .status-card__title, .status-card__host, - .status-card__author { + .status-card__author, + .status-card__description { color: $highlight-text-color; } } @@ -3587,7 +3593,8 @@ a.status-card { &:active { .status-card__title, .status-card__host, - .status-card__author { + .status-card__author, + .status-card__description { color: $highlight-text-color; } } @@ -3620,19 +3627,30 @@ a.status-card { line-height: 24px; color: $primary-text-color; overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.status-card.expanded .status-card__title { + white-space: normal; + -webkit-line-clamp: 2; } .status-card__content { flex: 1 1 auto; overflow: hidden; - padding: 15px 0; - padding-bottom: 0; + padding: 15px; + box-sizing: border-box; + max-width: 100%; } .status-card__host { display: block; font-size: 14px; margin-bottom: 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .status-card__author { @@ -3640,17 +3658,33 @@ a.status-card { margin-top: 8px; font-size: 14px; color: $primary-text-color; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; strong { font-weight: 500; } } +.status-card__description { + display: block; + margin-top: 8px; + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .status-card__image { - width: 100%; + flex: 0 0 auto; + width: 120px; + aspect-ratio: 1; background: lighten($ui-base-color, 8%); position: relative; border-radius: 8px; + border-start-end-radius: 0; + border-end-end-radius: 0; & > .fa { font-size: 21px; @@ -3664,6 +3698,8 @@ a.status-card { .status-card__image-image { border-radius: 8px; + border-start-end-radius: 0; + border-end-end-radius: 0; display: block; margin: 0; width: 100%; @@ -3675,6 +3711,8 @@ a.status-card { .status-card__image-preview { border-radius: 8px; + border-start-end-radius: 0; + border-end-end-radius: 0; display: block; margin: 0; width: 100%; @@ -3691,6 +3729,28 @@ a.status-card { } } +.status-card.expanded { + flex-direction: column; + align-items: flex-start; +} + +.status-card.expanded .status-card__image { + width: 100%; + aspect-ratio: auto; +} + +.status-card.expanded .status-card__image, +.status-card.expanded .status-card__image-image, +.status-card.expanded .status-card__image-preview { + border-start-end-radius: 8px; + border-end-end-radius: 0; + border-end-start-radius: 0; +} + +.status-card.expanded > a { + width: 100%; +} + .load-more { display: block; color: $dark-text-color; @@ -4902,7 +4962,7 @@ a.status-card { width: 100%; background: $ui-base-color; border-radius: 0 0 4px 4px; - box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); + box-shadow: var(--dropdown-shadow); z-index: 99; font-size: 13px; padding: 15px 5px; @@ -8218,7 +8278,7 @@ noscript { flex: 0 0 auto; position: relative; width: 120px; - height: 120px; + aspect-ratio: 1; .skeleton { width: 100%; |