diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 9e4442cef..f99ccd39a 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -401,6 +401,7 @@ class Status extends ImmutablePureComponent { compact cacheWidth={this.props.cacheMediaWidth} defaultWidth={this.props.cachedMediaWidth} + sensitive={status.get('sensitive')} /> ); } diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index b8344a667..630e99f2c 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -2,9 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { FormattedMessage } from 'react-intl'; import punycode from 'punycode'; import classnames from 'classnames'; import Icon from 'mastodon/components/icon'; +import classNames from 'classnames'; +import { useBlurhash } from 'mastodon/initial_state'; +import { decode } from 'blurhash'; const IDNA_PREFIX = 'xn--'; @@ -63,6 +67,7 @@ export default class Card extends React.PureComponent { compact: PropTypes.bool, defaultWidth: PropTypes.number, cacheWidth: PropTypes.func, + sensitive: PropTypes.bool, }; static defaultProps = { @@ -72,12 +77,44 @@ export default class Card extends React.PureComponent { state = { width: this.props.defaultWidth || 280, + previewLoaded: false, embedded: false, + revealed: !this.props.sensitive, }; componentWillReceiveProps (nextProps) { if (!Immutable.is(this.props.card, nextProps.card)) { - this.setState({ embedded: false }); + this.setState({ embedded: false, previewLoaded: false }); + } + if (this.props.sensitive !== nextProps.sensitive) { + this.setState({ revealed: !nextProps.sensitive }); + } + } + + componentDidMount () { + if (this.props.card && this.props.card.get('blurhash')) { + this._decode(); + } + } + + componentDidUpdate (prevProps) { + const { card } = this.props; + if (card.get('blurhash') && (!prevProps.card || prevProps.card.get('blurhash') !== card.get('blurhash'))) { + this._decode(); + } + } + + _decode () { + if (!useBlurhash) return; + + const hash = this.props.card.get('blurhash'); + const pixels = decode(hash, 32, 32); + + if (pixels) { + const ctx = this.canvas.getContext('2d'); + const imageData = new ImageData(pixels, 32, 32); + + ctx.putImageData(imageData, 0, 0); } } @@ -119,6 +156,18 @@ export default class Card extends React.PureComponent { } } + setCanvasRef = c => { + this.canvas = c; + } + + handleImageLoad = () => { + this.setState({ previewLoaded: true }); + } + + handleReveal = () => { + this.setState({ revealed: true }); + } + renderVideo () { const { card } = this.props; const content = { __html: addAutoPlay(card.get('html')) }; @@ -138,7 +187,7 @@ export default class Card extends React.PureComponent { render () { const { card, maxDescription, compact } = this.props; - const { width, embedded } = this.state; + const { width, embedded, revealed } = this.state; if (card === null) { return null; @@ -153,7 +202,7 @@ export default class Card extends React.PureComponent { const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio); const description = ( -
{trim(card.get('description') || '', maxDescription)}
} {provider} @@ -161,7 +210,18 @@ export default class Card extends React.PureComponent { ); let embed = ''; - let thumbnail = ; + let canvas = ; + let thumbnail = ; + let spoilerButton = ( + + ); + spoilerButton = ( +