This commit is contained in:
mgabdev
2020-03-27 11:29:52 -04:00
parent 3d0a85cde4
commit 2bd344a594
46 changed files with 545 additions and 1448 deletions

View File

@@ -1,245 +0,0 @@
import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import punycode from 'punycode';
import classnames from 'classnames';
import Icon from '../../../../components/icon';
const IDNA_PREFIX = 'xn--';
const decodeIDNA = domain => {
return domain
.split('.')
.map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part)
.join('.');
};
const getHostname = url => {
const parser = document.createElement('a');
parser.href = url;
return parser.hostname;
};
const trim = (text, len) => {
const cut = text.indexOf(' ', len);
if (cut === -1) {
return text;
}
return text.substring(0, cut) + (text.length > len ? '…' : '');
};
const domParser = new DOMParser();
const addAutoPlay = html => {
const document = domParser.parseFromString(html, 'text/html').documentElement;
const iframe = document.querySelector('iframe');
if (iframe) {
if (iframe.src.indexOf('?') !== -1) {
iframe.src += '&';
} else {
iframe.src += '?';
}
iframe.src += 'autoplay=1&auto_play=1';
// 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
return document.querySelector('body').innerHTML;
}
return html;
};
export default class Card extends ImmutablePureComponent {
static propTypes = {
card: ImmutablePropTypes.map,
onOpenMedia: PropTypes.func.isRequired,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
};
static defaultProps = {
};
state = {
width: this.props.defaultWidth || 280,
embedded: false,
};
componentWillReceiveProps (nextProps) {
if (!Immutable.is(this.props.card, nextProps.card)) {
this.setState({ embedded: false });
}
}
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 });
}
}
setRef = c => {
if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth);
this.setState({ width: c.offsetWidth });
}
}
renderVideo () {
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
return (
<div
ref={this.setRef}
className={[_s.default, _s.backgroundColorSecondary3, _s.positionAbsolute, _s.top0, _s.right0, _s.bottom0, _s.left0, _s.statusCardVideo].join(' ')}
dangerouslySetInnerHTML={content}
/>
)
}
render () {
const { card } = this.props
const { width, embedded } = this.state
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'
const title = interactive ?
(
<a
className={[_s.default, _s.displayFlex, _s.text, _s.noUnderline, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fontSize15PX, _s.fontWeightMedium].join(' ')}
href={card.get('url')}
title={card.get('title')}
rel='noopener'
target='_blank'
>
{card.get('title')}
</a>
)
: (
<span className={[_s.default, _s.displayFlex, _s.text, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fontSize15PX, _s.fontWeightMedium].join(' ')}>
{card.get('title')}
</span>
)
const description = (
<div className={[_s.default, _s.flexNormal, _s.px10, _s.py10, _s.borderColorSecondary, _s.borderLeft1PX].join(' ')}>
{title}
<p className={[_s.default, _s.displayFlex, _s.text, _s.my5, _s.overflowWrapBreakWord, _s.colorSecondary, _s.fontSize13PX, _s.fontWeightNormal].join(' ')}>
{trim(card.get('description') || '', maxDescription)}
</p>
<span className={[_s.default, _s.marginTopAuto, _s.flexRow, _s.alignItemsCenter, _s.colorSecondary, _s.text, _s.displayFlex, _s.textOverflowEllipsis, _s.fontSize13PX].join(' ')}>
<Icon id='link' width='10px' height='10px' className={[_s.fillColorSecondary, _s.mr5].join(' ')} fixedWidth />
{provider}
</span>
</div>
)
let embed = ''
let thumbnail = interactive ?
<img src={cardImg} className={[_s.default, _s.objectFitCover, _s.positionAbsolute, _s.width100PC, _s.height100PC, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')} />
:
<img src={cardImg} className={[_s.default, _s.objectFitCover, _s.width330PX, _s.height220PX].join(' ')} />
if (interactive) {
if (embedded) {
embed = this.renderVideo()
}
let iconVariant = 'play'
if (card.get('type') === 'photo') {
iconVariant = 'search-plus'
}
return (
<div className={[_s.default, _s.width100PC, _s.px10].join(' ')}>
<div className={[_s.default, _s.overflowHidden, _s.width100PC, _s.borderColorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}>
<div className={[_s.default, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.width100PC, _s.paddingTop5625PC].join(' ')}>
{ !!embed && embed}
{ !embed && thumbnail}
{ !embed &&
<div className={[_s.default, _s.positionAbsolute, _s.top0, _s.right0, _s.left0, _s.bottom0, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
<button
className={[_s.default, _s.cursorPointer, _s.backgroundColorOpaque, _s.radiusSmall, _s.py15, _s.px15].join(' ')}
onClick={this.handleEmbedClick}
>
<Icon id={iconVariant} className={[_s.fillColorWhite].join(' ')}/>
</button>
</div>
}
</div>
</div>
{description}
</div>
</div>
)
} else if (cardImg) {
embed = (
<div className={[_s.default].join(' ')}>
{thumbnail}
</div>
)
} else {
embed = (
<div className={[_s.default, _s.py15, _s.px15, _s.width72PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
<Icon id='file-text' width='22px' height='22px' className={_s.fillColorSecondary} />
</div>
)
}
return (
<div className={[_s.default, _s.width100PC, _s.px10].join(' ')}>
<a
href={card.get('url')}
className={[_s.default, _s.cursorPointer, _s.flexRow, _s.overflowHidden, _s.noUnderline, _s.width100PC, _s.backgroundSubtle_onHover, _s.borderColorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}
rel='noopener'
ref={this.setRef}
>
{embed}
{description}
</a>
</div>
)
}
}

View File

@@ -1,169 +0,0 @@
.status-card {
display: flex;
font-size: 14px;
color: $dark-text-color;
margin-top: 14px;
text-decoration: none;
overflow: hidden;
@include border-design(lighten($ui-base-color, 8%), 1px, 4px);
&__actions {
@include flex(center, center);
@include abs-position(0, 0, 0, 0);
&>div {
background: rgba($base-shadow-color, 0.6);
border-radius: 8px;
padding: 12px 9px;
flex: 0 0 auto;
@include flex(center, center);
}
button,
a {
display: inline;
color: $secondary-text-color;
background: transparent;
border: 0;
padding: 0 8px;
text-decoration: none;
font-size: 18px;
line-height: 18px;
&:hover,
&:active,
&:focus {
color: $primary-text-color;
}
}
a {
font-size: 19px;
position: relative;
bottom: -1px;
}
}
&__title {
display: block;
font-weight: 500;
margin-bottom: 5px;
color: $darker-text-color;
text-decoration: none;
@include text-overflow(nowrap);
}
&__content {
flex: 1 1 auto;
overflow: hidden;
padding: 14px 14px 14px 8px;
}
&__description {
color: $darker-text-color;
}
&__host {
display: block;
margin-top: 5px;
font-size: 13px;
@include text-overflow(nowrap);
}
&__image {
flex: 0 0 100px;
background: lighten($ui-base-color, 8%);
position: relative;
&>.fa {
font-size: 21px;
position: absolute;
transform-origin: 50% 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
&__image-image {
border-radius: 4px 0 0 4px;
display: block;
margin: 0;
object-fit: cover;
background-size: cover;
background-position: center center;
@include size(100%);
}
&.horizontal {
display: block;
}
&.horizontal & {
&__image {
width: 100%;
}
&__image-image {
border-radius: 4px 4px 0 0;
}
&__title {
white-space: inherit;
}
}
&.compact {
border-color: lighten($ui-base-color, 4%);
&.interactive {
border: 0;
}
}
&.compact & {
&__content {
padding: 10px 8px 8px 8px;
}
&__title {
white-space: nowrap;
}
&__image {
flex: 0 0 60px;
}
}
}
a.status-card {
cursor: pointer;
&:hover {
background: lighten($ui-base-color, 8%);
}
&.compact:hover {
background-color: lighten($ui-base-color, 4%);
}
}
.status-card-photo {
cursor: zoom-in;
display: block;
text-decoration: none;
margin: 0;
@include size(100%, auto);
}
.status-card-video {
iframe {
@include size(100%);
}
}

View File

@@ -1 +0,0 @@
export { default } from './card'