// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import React from 'react'; import PropTypes from 'prop-types'; import {postListScrollChange} from 'actions/global_actions.jsx'; import {updatePost} from 'actions/post_actions.jsx'; import * as Utils from 'utils/utils.jsx'; import * as CommonUtils from 'utils/commons.jsx'; import {PostTypes} from 'utils/constants.jsx'; export default class PostAttachmentOpenGraph extends React.PureComponent { static propTypes = { /** * The link to display the open graph data for */ link: PropTypes.string.isRequired, /** * The current user viewing the post */ currentUser: PropTypes.object, /** * The post where this link is included */ post: PropTypes.object, /** * The open graph data to render */ openGraphData: PropTypes.object, /** * Set to collapse the preview */ previewCollapsed: PropTypes.string, actions: PropTypes.shape({ /** * The function to get open graph data for a link */ getOpenGraphMetadata: PropTypes.func.isRequired }).isRequired } constructor(props) { super(props); this.largeImageMinWidth = 150; this.imageDimentions = { // Image dimentions in pixels. height: 80, width: 80 }; this.textMaxLenght = 300; this.textEllipsis = '...'; this.largeImageMinRatio = 16 / 9; this.smallImageContainerLeftPadding = 15; this.imageRatio = null; this.smallImageContainer = null; this.smallImageElement = null; this.IMAGE_LOADED = { LOADING: 'loading', YES: 'yes', ERROR: 'error' }; this.fetchData = this.fetchData.bind(this); this.toggleImageVisibility = this.toggleImageVisibility.bind(this); this.onImageLoad = this.onImageLoad.bind(this); this.onImageError = this.onImageError.bind(this); this.handleRemovePreview = this.handleRemovePreview.bind(this); } componentWillMount() { const removePreview = this.isRemovePreview(this.props.post, this.props.currentUser); this.setState({ imageLoaded: this.IMAGE_LOADED.LOADING, imageVisible: this.props.previewCollapsed.startsWith('false'), hasLargeImage: false, removePreview }); this.fetchData(this.props.link); } componentWillReceiveProps(nextProps) { if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) { const removePreview = this.isRemovePreview(nextProps.post, nextProps.currentUser); this.setState({ removePreview }); } if (nextProps.link !== this.props.link) { this.fetchData(nextProps.link); } if (nextProps.previewCollapsed !== this.props.previewCollapsed) { this.setState({ imageVisible: nextProps.previewCollapsed.startsWith('false') }); } } componentDidUpdate() { postListScrollChange(); } fetchData(url) { if (!this.props.openGraphData) { this.props.actions.getOpenGraphMetadata(url); } } getBestImageUrl(data) { if (Utils.isEmptyObject(data.images)) { return null; } const bestImage = CommonUtils.getNearestPoint(this.imageDimentions, data.images, 'width', 'height'); return bestImage.secure_url || bestImage.url; } toggleImageVisibility() { this.setState({imageVisible: !this.state.imageVisible}); } onImageLoad(image) { this.imageRatio = image.target.naturalWidth / image.target.naturalHeight; if ( image.target.naturalWidth >= this.largeImageMinWidth && this.imageRatio >= this.largeImageMinRatio && !this.state.hasLargeImage ) { this.setState({ hasLargeImage: true }); } this.setState({ imageLoaded: this.IMAGE_LOADED.YES }); } onImageError() { this.setState({imageLoaded: this.IMAGE_LOADED.ERROR}); } loadImage(src) { const img = new Image(); img.onload = this.onImageLoad; img.onerror = this.onImageError; img.src = src; } imageToggleAnchoreTag(imageUrl) { if (imageUrl && this.state.hasLargeImage) { return ( ); } return null; } wrapInSmallImageContainer(imageElement) { return (
{ this.smallImageContainer = div; }} > {imageElement}
); } imageTag(imageUrl, renderingForLargeImage = false) { var element = null; if ( imageUrl && renderingForLargeImage === this.state.hasLargeImage && (!renderingForLargeImage || (renderingForLargeImage && this.state.imageVisible)) ) { if (this.state.imageLoaded === this.IMAGE_LOADED.LOADING) { if (renderingForLargeImage) { element = ; } else { element = this.wrapInSmallImageContainer( ); } } else if (this.state.imageLoaded === this.IMAGE_LOADED.YES) { if (renderingForLargeImage) { element = ( ); } else { element = this.wrapInSmallImageContainer( { this.smallImageElement = img; }} /> ); } } else if (this.state.imageLoaded === this.IMAGE_LOADED.ERROR) { return null; } } return element; } truncateText(text, maxLength = this.textMaxLenght, ellipsis = this.textEllipsis) { if (text.length > maxLength) { return text.substring(0, maxLength - ellipsis.length) + ellipsis; } return text; } handleRemovePreview() { const props = Object.assign({}, this.props.post.props); props[PostTypes.REMOVE_LINK_PREVIEW] = 'true'; const patchedPost = ({ id: this.props.post.id, props }); updatePost(patchedPost, () => { this.setState({removePreview: true}); }); } isRemovePreview(post, currentUser) { if (post && post.props && currentUser.id === post.user_id) { return post.props[PostTypes.REMOVE_LINK_PREVIEW] && post.props[PostTypes.REMOVE_LINK_PREVIEW] === 'true'; } return false; } render() { const data = this.props.openGraphData; if (!data || Utils.isEmptyObject(data.description) || this.state.removePreview) { return null; } let removePreviewButton; if (this.props.currentUser.id === this.props.post.user_id) { removePreviewButton = ( ); } const imageUrl = this.getBestImageUrl(data); if (imageUrl) { this.loadImage(imageUrl); } return (
{this.truncateText(data.site_name)} {removePreviewButton}

{this.truncateText(data.title || data.url || this.props.link)}

{this.truncateText(data.description)}   {this.imageToggleAnchoreTag(imageUrl)}
{this.imageTag(imageUrl, true)}
{this.imageTag(imageUrl, false)}
); } }