2020-08-17 21:07:16 +01:00
|
|
|
import React from 'react'
|
2020-08-17 21:59:29 +01:00
|
|
|
import PropTypes from 'prop-types'
|
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'
|
2020-09-11 23:55:05 +01:00
|
|
|
import {
|
|
|
|
CX,
|
|
|
|
DEFAULT_REL,
|
|
|
|
} from '../constants'
|
2020-05-06 05:33:54 +01:00
|
|
|
import ResponsiveClassesComponent from '../features/ui/util/responsive_classes_component'
|
2020-03-27 22:57:03 +00:00
|
|
|
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
|
|
|
|
2020-08-18 01:57:35 +01:00
|
|
|
class StatusCard extends ImmutablePureComponent {
|
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
|
|
|
|
2020-05-06 05:33:54 +01:00
|
|
|
componentWillReceiveProps(nextProps) {
|
2019-07-02 08:10:25 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 05:33:54 +01:00
|
|
|
renderVideo() {
|
|
|
|
const { card } = this.props
|
|
|
|
const content = { __html: addAutoPlay(card.get('html')) }
|
2020-02-14 00:40:04 +00:00
|
|
|
const { width } = this.state
|
2020-05-06 05:33:54 +01:00
|
|
|
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-08-18 21:49:11 +01:00
|
|
|
className={[_s.d, _s.bgTertiary, _s.posAbs, _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
|
|
|
}
|
|
|
|
|
2020-05-06 05:33:54 +01:00
|
|
|
render() {
|
2020-09-11 23:55:05 +01:00
|
|
|
const {
|
|
|
|
card,
|
|
|
|
isReduced,
|
|
|
|
isVertical,
|
|
|
|
} = this.props
|
2020-02-14 00:40:04 +00:00
|
|
|
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 interactive = card.get('type') !== 'link'
|
2019-07-02 08:10:25 +01:00
|
|
|
|
2020-10-13 16:07:38 +01:00
|
|
|
const imgContainerClasses = CX({
|
|
|
|
d: 1,
|
|
|
|
h220PX: 1,
|
|
|
|
w330PX: !isVertical,
|
|
|
|
})
|
|
|
|
|
2020-05-07 06:55:24 +01:00
|
|
|
const cardTitle = `${card.get('title')}`.trim()
|
2019-08-27 02:58:54 +01:00
|
|
|
const title = interactive ?
|
2020-02-14 00:40:04 +00:00
|
|
|
(
|
|
|
|
<a
|
2020-08-18 21:49:11 +01:00
|
|
|
className={[_s.d, _s.displayFlex, _s.text, _s.noUnderline, _s.overflowWrapBreakWord, _s.cPrimary, _s.fs15PX, _s.fw500].join(' ')}
|
2020-02-14 00:40:04 +00:00
|
|
|
href={card.get('url')}
|
2020-05-07 06:55:24 +01:00
|
|
|
title={cardTitle}
|
2020-05-01 06:50:27 +01:00
|
|
|
rel={DEFAULT_REL}
|
2020-02-14 00:40:04 +00:00
|
|
|
target='_blank'
|
|
|
|
>
|
2020-05-07 06:55:24 +01:00
|
|
|
{cardTitle}
|
2020-02-14 00:40:04 +00:00
|
|
|
</a>
|
|
|
|
)
|
|
|
|
: (
|
2020-08-18 21:49:11 +01:00
|
|
|
<span className={[_s.d, _s.displayFlex, _s.text, _s.overflowWrapBreakWord, _s.cPrimary, _s.fs15PX, _s.fw500].join(' ')}>
|
2020-05-07 06:55:24 +01:00
|
|
|
{cardTitle}
|
2020-02-14 00:40:04 +00:00
|
|
|
</span>
|
|
|
|
)
|
2019-07-02 08:10:25 +01:00
|
|
|
|
|
|
|
const description = (
|
2020-08-18 21:49:11 +01:00
|
|
|
<div className={[_s.d, _s.flexNormal, _s.px10, _s.py10, _s.borderColorSecondary, _s.borderLeft1PX].join(' ')}>
|
2019-07-02 08:10:25 +01:00
|
|
|
{title}
|
2020-08-18 21:49:11 +01:00
|
|
|
<p className={[_s.d, _s.displayFlex, _s.text, _s.mt5, _s.mb5, _s.overflowWrapBreakWord, _s.cSecondary, _s.fs13PX, _s.fw400].join(' ')}>
|
2020-02-14 00:40:04 +00:00
|
|
|
{trim(card.get('description') || '', maxDescription)}
|
|
|
|
</p>
|
2020-08-18 21:49:11 +01:00
|
|
|
<span className={[_s.d, _s.mtAuto, _s.flexRow, _s.aiCenter, _s.cSecondary, _s.text, _s.displayFlex, _s.textOverflowEllipsis, _s.fs13PX].join(' ')}>
|
2020-08-18 21:43:06 +01:00
|
|
|
<Icon id='link' size='10px' className={[_s.cSecondary, _s.mr5].join(' ')} fixedWidth />
|
2019-08-27 02:58:54 +01:00
|
|
|
{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 ?
|
2020-08-18 21:49:11 +01:00
|
|
|
<img loading='lazy' alt={''} src={cardImg} className={[_s.d, _s.objectFitCover, _s.posAbs, _s.w100PC, _s.h100PC, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')} />
|
2020-02-14 00:40:04 +00:00
|
|
|
:
|
2020-05-06 05:33:54 +01:00
|
|
|
(
|
|
|
|
<ResponsiveClassesComponent
|
2020-10-13 16:07:38 +01:00
|
|
|
classNames={imgContainerClasses}
|
2020-08-18 21:49:11 +01:00
|
|
|
classNamesSmall={[_s.d, _s.h260PX, _s.w100PC].join(' ')}
|
|
|
|
classNamesXS={[_s.d, _s.h200PX, _s.w100PC].join(' ')}
|
2020-05-06 05:33:54 +01:00
|
|
|
>
|
2020-08-18 21:49:11 +01:00
|
|
|
<img loading='lazy' alt={''} src={cardImg} className={[_s.d, _s.objectFitCover, _s.w100PC, _s.h100PC].join(' ')} />
|
2020-05-06 05:33:54 +01:00
|
|
|
</ResponsiveClassesComponent>
|
|
|
|
)
|
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-08-18 21:49:11 +01:00
|
|
|
<div className={[_s.d, _s.w100PC, _s.px10].join(' ')}>
|
|
|
|
<div className={[_s.d, _s.overflowHidden, _s.w100PC, _s.borderColorSecondary, _s.border1PX, _s.radiusSmall].join(' ')}>
|
2020-05-07 00:40:54 +01:00
|
|
|
{
|
|
|
|
!isReduced &&
|
2020-08-18 21:49:11 +01:00
|
|
|
<div className={[_s.d, _s.w100PC].join(' ')}>
|
|
|
|
<div className={[_s.d, _s.w100PC, _s.pt5625PC].join(' ')}>
|
2020-05-07 00:40:54 +01:00
|
|
|
{!!embed && embed}
|
|
|
|
{!embed && thumbnail}
|
|
|
|
{!embed &&
|
2020-08-18 21:49:11 +01:00
|
|
|
<div className={[_s.d, _s.posAbs, _s.top0, _s.right0, _s.left0, _s.bottom0, _s.aiCenter, _s.jcCenter].join(' ')}>
|
2020-05-07 00:40:54 +01:00
|
|
|
<button
|
2020-08-18 21:49:11 +01:00
|
|
|
className={[_s.d, _s.cursorPointer, _s.bgBlackOpaque, _s.radiusSmall, _s.py15, _s.px15].join(' ')}
|
2020-05-07 00:40:54 +01:00
|
|
|
onClick={this.handleEmbedClick}
|
|
|
|
>
|
2020-08-18 21:43:06 +01:00
|
|
|
<Icon id={iconVariant} size='22px' className={[_s.cWhite].join(' ')} />
|
2020-05-07 00:40:54 +01:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
</div>
|
2020-02-14 00:40:04 +00:00
|
|
|
</div>
|
2020-05-07 00:40:54 +01:00
|
|
|
}
|
2020-02-14 00:40:04 +00:00
|
|
|
{description}
|
|
|
|
</div>
|
2019-07-02 08:10:25 +01:00
|
|
|
</div>
|
2020-02-14 00:40:04 +00:00
|
|
|
)
|
2019-08-27 02:58:54 +01:00
|
|
|
} else if (cardImg) {
|
2019-07-02 08:10:25 +01:00
|
|
|
embed = (
|
2020-08-18 21:49:11 +01:00
|
|
|
<div className={[_s.d].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-08-18 21:49:11 +01:00
|
|
|
<div className={[_s.d, _s.py15, _s.px15, _s.w72PX, _s.aiCenter, _s.jcCenter].join(' ')}>
|
2020-08-18 21:43:06 +01:00
|
|
|
<Icon id='website' size='22px' className={_s.cSecondary} />
|
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-09-11 23:55:05 +01:00
|
|
|
const containerClasses = CX({
|
|
|
|
d: 1,
|
|
|
|
width100PC: 1,
|
|
|
|
flexRow: !isVertical,
|
|
|
|
})
|
|
|
|
|
2019-07-02 08:10:25 +01:00
|
|
|
return (
|
2020-08-18 21:49:11 +01:00
|
|
|
<div className={[_s.d, _s.w100PC, _s.px10].join(' ')}>
|
2020-02-14 00:40:04 +00:00
|
|
|
<a
|
|
|
|
href={card.get('url')}
|
2020-05-01 06:50:27 +01:00
|
|
|
rel={DEFAULT_REL}
|
2020-02-14 00:40:04 +00:00
|
|
|
ref={this.setRef}
|
2020-05-25 19:00:31 +01:00
|
|
|
target='_blank'
|
2020-08-18 21:49:11 +01:00
|
|
|
className={[_s.d, _s.cursorPointer, _s.overflowHidden, _s.noUnderline, _s.w100PC, _s.bgSubtle_onHover, _s.borderColorSecondary, _s.border1PX, _s.radiusSmall].join(' ')}
|
2020-04-08 02:06:59 +01:00
|
|
|
>
|
2020-05-06 05:33:54 +01:00
|
|
|
<ResponsiveClassesComponent
|
2020-09-11 23:55:05 +01:00
|
|
|
classNames={containerClasses}
|
2020-08-18 21:49:11 +01:00
|
|
|
classNamesSmall={!cardImg ? undefined : [_s.d, _s.w100PC].join(' ')}
|
2020-05-06 05:33:54 +01:00
|
|
|
>
|
2020-05-07 00:40:54 +01:00
|
|
|
{!isReduced && embed}
|
2020-05-06 05:33:54 +01:00
|
|
|
{description}
|
|
|
|
</ResponsiveClassesComponent>
|
2020-02-14 00:40:04 +00:00
|
|
|
</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
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-08-18 01:57:35 +01:00
|
|
|
|
|
|
|
StatusCard.propTypes = {
|
|
|
|
card: ImmutablePropTypes.map,
|
|
|
|
onOpenMedia: PropTypes.func.isRequired,
|
|
|
|
defaultWidth: PropTypes.number,
|
|
|
|
cacheWidth: PropTypes.func,
|
|
|
|
isReduced: PropTypes.bool,
|
2020-09-11 23:55:05 +01:00
|
|
|
isVertical: PropTypes.bool,
|
2020-08-18 01:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export default StatusCard
|