gab-social/app/javascript/gabsocial/components/status_card.js

243 lines
7.5 KiB
JavaScript
Raw Normal View History

2020-03-27 22:57:03 +00:00
import Immutable from 'immutable'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import punycode from 'punycode'
import Icon from './icon'
2019-08-13 16:54:29 +01:00
2020-03-27 22:57:03 +00:00
const IDNA_PREFIX = 'xn--'
2019-07-02 08:10:25 +01:00
const decodeIDNA = domain => {
return domain
.split('.')
.map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part)
2020-03-27 22:57:03 +00:00
.join('.')
}
2019-07-02 08:10:25 +01:00
const getHostname = url => {
2020-03-27 22:57:03 +00:00
const parser = document.createElement('a')
parser.href = url
return parser.hostname
}
2019-07-02 08:10:25 +01:00
const trim = (text, len) => {
2020-03-27 22:57:03 +00:00
const cut = text.indexOf(' ', len)
2019-07-02 08:10:25 +01:00
if (cut === -1) {
2020-03-27 22:57:03 +00:00
return text
2019-07-02 08:10:25 +01:00
}
2020-03-27 22:57:03 +00:00
return text.substring(0, cut) + (text.length > len ? '…' : '')
}
2019-07-02 08:10:25 +01:00
2020-03-27 22:57:03 +00:00
const domParser = new DOMParser()
2019-07-02 08:10:25 +01:00
const addAutoPlay = html => {
2020-03-27 22:57:03 +00:00
const document = domParser.parseFromString(html, 'text/html').documentElement
const iframe = document.querySelector('iframe')
2019-07-02 08:10:25 +01:00
if (iframe) {
if (iframe.src.indexOf('?') !== -1) {
2020-03-27 22:57:03 +00:00
iframe.src += '&'
2019-07-02 08:10:25 +01:00
} else {
2020-03-27 22:57:03 +00:00
iframe.src += '?'
2019-07-02 08:10:25 +01:00
}
2020-03-27 22:57:03 +00:00
iframe.src += 'autoplay=1&auto_play=1'
2019-07-02 08:10:25 +01:00
// DOM parser creates html/body elements around original HTML fragment,
// so we need to get innerHTML out of the body and not the entire document
2020-03-27 22:57:03 +00:00
return document.querySelector('body').innerHTML
2019-07-02 08:10:25 +01:00
}
2020-03-27 22:57:03 +00:00
return html
}
2019-07-02 08:10:25 +01:00
2019-08-13 16:54:29 +01:00
export default class Card extends ImmutablePureComponent {
2019-07-02 08:10:25 +01:00
static propTypes = {
card: ImmutablePropTypes.map,
onOpenMedia: PropTypes.func.isRequired,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
2020-03-27 22:57:03 +00:00
}
2019-07-02 08:10:25 +01:00
state = {
width: this.props.defaultWidth || 280,
embedded: false,
2020-03-27 22:57:03 +00:00
}
2019-07-02 08:10:25 +01:00
componentWillReceiveProps (nextProps) {
if (!Immutable.is(this.props.card, nextProps.card)) {
2020-03-27 22:57:03 +00:00
this.setState({ embedded: false })
2019-07-02 08:10:25 +01:00
}
}
handlePhotoClick = () => {
2020-03-27 22:57:03 +00:00
const { card, onOpenMedia } = this.props
2019-07-02 08:10:25 +01:00
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
2020-03-27 22:57:03 +00:00
)
}
2019-07-02 08:10:25 +01:00
handleEmbedClick = () => {
2020-03-27 22:57:03 +00:00
const { card } = this.props
2019-07-02 08:10:25 +01:00
if (card.get('type') === 'photo') {
2020-03-27 22:57:03 +00:00
this.handlePhotoClick()
2019-07-02 08:10:25 +01:00
} else {
2020-03-27 22:57:03 +00:00
this.setState({ embedded: true })
2019-07-02 08:10:25 +01:00
}
}
setRef = c => {
if (c) {
2020-03-27 22:57:03 +00:00
if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth)
this.setState({ width: c.offsetWidth })
2019-07-02 08:10:25 +01:00
}
}
renderVideo () {
2020-02-14 00:40:04 +00:00
const { card } = this.props
const content = { __html: addAutoPlay(card.get('html')) }
const { width } = this.state
const ratio = card.get('width') / card.get('height')
const height = width / ratio
2019-07-02 08:10:25 +01:00
return (
<div
ref={this.setRef}
2020-03-04 03:45:16 +00:00
className={[_s.default, _s.backgroundColorSecondary3, _s.positionAbsolute, _s.top0, _s.right0, _s.bottom0, _s.left0, _s.statusCardVideo].join(' ')}
2019-07-02 08:10:25 +01:00
dangerouslySetInnerHTML={content}
/>
2020-02-14 00:40:04 +00:00
)
2019-07-02 08:10:25 +01:00
}
render () {
2020-02-14 00:40:04 +00:00
const { card } = this.props
const { width, embedded } = this.state
2019-07-02 08:10:25 +01:00
2020-02-14 00:40:04 +00:00
if (card === null) return null
const maxDescription = 160
const cardImg = card.get('image')
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name')
const horizontal = (card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded
const interactive = card.get('type') !== 'link'
2019-07-02 08:10:25 +01:00
const title = interactive ?
2020-02-14 00:40:04 +00:00
(
<a
2020-02-19 23:57:07 +00:00
className={[_s.default, _s.displayFlex, _s.text, _s.noUnderline, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fontSize15PX, _s.fontWeightMedium].join(' ')}
2020-02-14 00:40:04 +00:00
href={card.get('url')}
title={card.get('title')}
2020-03-31 17:04:50 +01:00
rel='noopener noreferrer'
2020-02-14 00:40:04 +00:00
target='_blank'
>
{card.get('title')}
</a>
)
: (
2020-02-19 23:57:07 +00:00
<span className={[_s.default, _s.displayFlex, _s.text, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fontSize15PX, _s.fontWeightMedium].join(' ')}>
2020-02-14 00:40:04 +00:00
{card.get('title')}
</span>
)
2019-07-02 08:10:25 +01:00
const description = (
2020-03-11 23:56:18 +00:00
<div className={[_s.default, _s.flexNormal, _s.px10, _s.py10, _s.borderColorSecondary, _s.borderLeft1PX].join(' ')}>
2019-07-02 08:10:25 +01:00
{title}
2020-03-11 23:56:18 +00:00
<p className={[_s.default, _s.displayFlex, _s.text, _s.my5, _s.overflowWrapBreakWord, _s.colorSecondary, _s.fontSize13PX, _s.fontWeightNormal].join(' ')}>
2020-02-14 00:40:04 +00:00
{trim(card.get('description') || '', maxDescription)}
</p>
2020-02-19 23:57:07 +00:00
<span className={[_s.default, _s.marginTopAuto, _s.flexRow, _s.alignItemsCenter, _s.colorSecondary, _s.text, _s.displayFlex, _s.textOverflowEllipsis, _s.fontSize13PX].join(' ')}>
2020-03-11 23:56:18 +00:00
<Icon id='link' width='10px' height='10px' className={[_s.fillColorSecondary, _s.mr5].join(' ')} fixedWidth />
{provider}
</span>
2019-07-02 08:10:25 +01:00
</div>
2020-02-14 00:40:04 +00:00
)
2019-07-02 08:10:25 +01:00
2020-04-08 02:06:59 +01:00
// : todo : use <Image />
2020-02-14 00:40:04 +00:00
let embed = ''
2020-04-08 02:06:59 +01:00
const thumbnail = interactive ?
<img alt={''} src={cardImg} className={[_s.default, _s.objectFitCover, _s.positionAbsolute, _s.width100PC, _s.height100PC, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')} />
2020-02-14 00:40:04 +00:00
:
2020-04-08 02:06:59 +01:00
<img alt={''} src={cardImg} className={[_s.default, _s.objectFitCover, _s.width330PX, _s.height220PX].join(' ')} />
2019-07-02 08:10:25 +01:00
if (interactive) {
if (embedded) {
2020-02-14 00:40:04 +00:00
embed = this.renderVideo()
}
let iconVariant = 'play'
if (card.get('type') === 'photo') {
iconVariant = 'search-plus'
2019-07-02 08:10:25 +01:00
}
return (
2020-03-11 23:56:18 +00:00
<div className={[_s.default, _s.width100PC, _s.px10].join(' ')}>
2020-02-21 00:57:29 +00:00
<div className={[_s.default, _s.overflowHidden, _s.width100PC, _s.borderColorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}>
2020-02-19 23:57:07 +00:00
<div className={[_s.default, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.width100PC, _s.paddingTop5625PC].join(' ')}>
2020-02-14 00:40:04 +00:00
{ !!embed && embed}
{ !embed && thumbnail}
{ !embed &&
2020-02-19 23:57:07 +00:00
<div className={[_s.default, _s.positionAbsolute, _s.top0, _s.right0, _s.left0, _s.bottom0, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
2020-02-14 00:40:04 +00:00
<button
2020-03-11 23:56:18 +00:00
className={[_s.default, _s.cursorPointer, _s.backgroundColorOpaque, _s.radiusSmall, _s.py15, _s.px15].join(' ')}
2020-02-14 00:40:04 +00:00
onClick={this.handleEmbedClick}
>
2020-04-08 02:06:59 +01:00
<Icon id={iconVariant} className={[_s.fillColorWhite].join(' ')} />
2020-02-14 00:40:04 +00:00
</button>
</div>
}
</div>
</div>
{description}
</div>
2019-07-02 08:10:25 +01:00
</div>
2020-02-14 00:40:04 +00:00
)
} else if (cardImg) {
2019-07-02 08:10:25 +01:00
embed = (
2020-02-19 23:57:07 +00:00
<div className={[_s.default].join(' ')}>
2019-07-02 08:10:25 +01:00
{thumbnail}
</div>
2020-02-14 00:40:04 +00:00
)
2019-07-02 08:10:25 +01:00
} else {
embed = (
2020-03-11 23:56:18 +00:00
<div className={[_s.default, _s.py15, _s.px15, _s.width72PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
2020-04-08 04:22:24 +01:00
<Icon id='website' width='22px' height='22px' className={_s.fillColorSecondary} />
2019-07-02 08:10:25 +01:00
</div>
2020-02-14 00:40:04 +00:00
)
2019-07-02 08:10:25 +01:00
}
return (
2020-03-11 23:56:18 +00:00
<div className={[_s.default, _s.width100PC, _s.px10].join(' ')}>
2020-02-14 00:40:04 +00:00
<a
href={card.get('url')}
2020-02-29 15:42:47 +00:00
className={[_s.default, _s.cursorPointer, _s.flexRow, _s.overflowHidden, _s.noUnderline, _s.width100PC, _s.backgroundSubtle_onHover, _s.borderColorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}
2020-03-31 17:04:50 +01:00
rel='noopener noreferrer'
2020-02-14 00:40:04 +00:00
ref={this.setRef}
2020-04-08 02:06:59 +01:00
>
2020-02-14 00:40:04 +00:00
{embed}
{description}
</a>
2020-04-08 02:06:59 +01:00
</div>
2020-02-14 00:40:04 +00:00
)
2019-07-02 08:10:25 +01:00
}
}