|
|
|
@ -12,6 +12,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; |
|
|
|
|
|
|
|
|
|
import { Blurhash } from 'mastodon/components/blurhash'; |
|
|
|
|
import { Icon } from 'mastodon/components/icon'; |
|
|
|
|
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; |
|
|
|
|
import { useBlurhash } from 'mastodon/initial_state'; |
|
|
|
|
|
|
|
|
|
const IDNA_PREFIX = 'xn--'; |
|
|
|
@ -57,14 +58,9 @@ export default class Card extends PureComponent { |
|
|
|
|
static propTypes = { |
|
|
|
|
card: ImmutablePropTypes.map, |
|
|
|
|
onOpenMedia: PropTypes.func.isRequired, |
|
|
|
|
compact: PropTypes.bool, |
|
|
|
|
sensitive: PropTypes.bool, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static defaultProps = { |
|
|
|
|
compact: false, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
state = { |
|
|
|
|
previewLoaded: false, |
|
|
|
|
embedded: false, |
|
|
|
@ -148,7 +144,7 @@ export default class Card extends PureComponent { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
render () { |
|
|
|
|
const { card, compact } = this.props; |
|
|
|
|
const { card } = this.props; |
|
|
|
|
const { embedded, revealed } = this.state; |
|
|
|
|
|
|
|
|
|
if (card === null) { |
|
|
|
@ -156,29 +152,24 @@ export default class Card extends PureComponent { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); |
|
|
|
|
const horizontal = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded; |
|
|
|
|
const interactive = card.get('type') !== 'link'; |
|
|
|
|
const className = classnames('status-card', { horizontal, compact, interactive }); |
|
|
|
|
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>; |
|
|
|
|
const language = card.get('language') || ''; |
|
|
|
|
|
|
|
|
|
const description = ( |
|
|
|
|
<div className='status-card__content' lang={language}> |
|
|
|
|
{title} |
|
|
|
|
{!(horizontal || compact) && <p className='status-card__description' title={card.get('description')}>{card.get('description')}</p>} |
|
|
|
|
<span className='status-card__host'>{provider}</span> |
|
|
|
|
<span className='status-card__host'>{provider}{card.get('published_at') && <> · <RelativeTimestamp timestamp={card.get('published_at')} /></>}</span> |
|
|
|
|
<strong className='status-card__title' title={card.get('title')}>{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>} |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const thumbnailStyle = { |
|
|
|
|
visibility: revealed? null : 'hidden', |
|
|
|
|
visibility: revealed ? null : 'hidden', |
|
|
|
|
aspectRatio: `${card.get('width')} / ${card.get('height')}` |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (horizontal) { |
|
|
|
|
thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`; |
|
|
|
|
} |
|
|
|
|
let embed; |
|
|
|
|
|
|
|
|
|
let embed = ''; |
|
|
|
|
let canvas = ( |
|
|
|
|
<Blurhash |
|
|
|
|
className={classnames('status-card__image-preview', { |
|
|
|
@ -188,12 +179,15 @@ export default class Card extends PureComponent { |
|
|
|
|
dummy={!useBlurhash} |
|
|
|
|
/> |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
let thumbnail = <img src={card.get('image')} alt='' style={thumbnailStyle} onLoad={this.handleImageLoad} className='status-card__image-image' />; |
|
|
|
|
|
|
|
|
|
let spoilerButton = ( |
|
|
|
|
<button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'> |
|
|
|
|
<span className='spoiler-button__overlay__label'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> |
|
|
|
|
</button> |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
spoilerButton = ( |
|
|
|
|
<div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}> |
|
|
|
|
{spoilerButton} |
|
|
|
@ -219,19 +213,20 @@ export default class Card extends PureComponent { |
|
|
|
|
<div className='status-card__actions'> |
|
|
|
|
<div> |
|
|
|
|
<button type='button' onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button> |
|
|
|
|
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>} |
|
|
|
|
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
{!revealed && spoilerButton} |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div className={className} ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}> |
|
|
|
|
<div className='status-card' ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}> |
|
|
|
|
{embed} |
|
|
|
|
{!compact && description} |
|
|
|
|
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'>{description}</a> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} else if (card.get('image')) { |
|
|
|
@ -243,14 +238,14 @@ export default class Card extends PureComponent { |
|
|
|
|
); |
|
|
|
|
} else { |
|
|
|
|
embed = ( |
|
|
|
|
<div className='status-card__image'> |
|
|
|
|
<div className='status-card__image' style={{ aspectRatio: '1.9 / 1' }}> |
|
|
|
|
<Icon id='file-text' /> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}> |
|
|
|
|
<a href={card.get('url')} className='status-card' target='_blank' rel='noopener noreferrer' ref={this.setRef}> |
|
|
|
|
{embed} |
|
|
|
|
{description} |
|
|
|
|
</a> |
|
|
|
|