Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
#4257 Added functionality to create previews for post links using op…
Browse files Browse the repository at this point in the history
…en graph data from those links. (#4890)
  • Loading branch information
Debanshu Kundu authored and enahum committed Jan 20, 2017
1 parent cfe7e35 commit 913fbe6
Show file tree
Hide file tree
Showing 15 changed files with 460 additions and 532 deletions.
24 changes: 24 additions & 0 deletions actions/global_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -596,3 +596,27 @@ export function redirectUserToDefaultTeam() {
browserHistory.push('/select_team');
}
}

requestOpenGraphMetadata.openGraphMetadataOnGoingRequests = {}; // Format: {<url>: true}
export function requestOpenGraphMetadata(url) {
const onself = requestOpenGraphMetadata;

if (!onself.openGraphMetadataOnGoingRequests[url]) {
onself.openGraphMetadataOnGoingRequests[url] = true;

Client.getOpenGraphMetadata(url,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIVED_OPEN_GRAPH_METADATA,
url,
data
});
delete onself.openGraphMetadataOnGoingRequests[url];
},
(err) => {
AsyncClient.dispatchError(err, 'getOpenGraphMetadata');
delete onself.openGraphMetadataOnGoingRequests[url];
}
);
}
}
10 changes: 10 additions & 0 deletions client/client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,16 @@ export default class Client {
end(this.handleResponse.bind(this, 'getFileInfosForPost', success, error));
}

getOpenGraphMetadata(url, success, error) {
request.
post(`${this.getBaseRoute()}/get_opengraph_metadata`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
send({url}).
end(this.handleResponse.bind(this, 'getOpenGraphMetadata', success, error));
}

// Routes for Files

uploadFile(file, filename, channelId, clientId, success, error) {
Expand Down
4 changes: 3 additions & 1 deletion components/post_view/components/post.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ export default class Post extends React.Component {
compactDisplay={this.props.compactDisplay}
previewCollapsed={this.props.previewCollapsed}
isCommentMention={this.props.isCommentMention}
childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
/>
</div>
</div>
Expand Down Expand Up @@ -317,5 +318,6 @@ Post.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool,
status: React.PropTypes.string,
isBusy: React.PropTypes.bool
isBusy: React.PropTypes.bool,
childComponentDidUpdateFunction: React.PropTypes.func
};
108 changes: 0 additions & 108 deletions components/post_view/components/post_attachment_oembed.jsx

This file was deleted.

212 changes: 212 additions & 0 deletions components/post_view/components/post_attachment_opengraph.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

import React from 'react';

import OpenGraphStore from 'stores/opengraph_store.jsx';
import * as Utils from 'utils/utils.jsx';
import * as CommonUtils from 'utils/commons.jsx';
import {requestOpenGraphMetadata} from 'actions/global_actions.jsx';

export default class PostAttachmentOpenGraph extends React.Component {
constructor(props) {
super(props);
this.imageDimentions = { // Image dimentions in pixels.
height: 150,
width: 150
};
this.maxDescriptionLength = 300;
this.descriptionEllipsis = '...';
this.fetchData = this.fetchData.bind(this);
this.onOpenGraphMetadataChange = this.onOpenGraphMetadataChange.bind(this);
this.toggleImageVisibility = this.toggleImageVisibility.bind(this);
this.onImageLoad = this.onImageLoad.bind(this);
}

componentWillMount() {
this.setState({
data: {},
imageLoaded: false,
imageVisible: this.props.previewCollapsed.startsWith('false')
});
this.fetchData(this.props.link);
}

componentWillReceiveProps(nextProps) {
this.setState({imageVisible: nextProps.previewCollapsed.startsWith('false')});
if (!Utils.areObjectsEqual(nextProps.link, this.props.link)) {
this.fetchData(nextProps.link);
}
}

shouldComponentUpdate(nextProps, nextState) {
if (nextState.imageVisible !== this.state.imageVisible) {
return true;
}
if (nextState.imageLoaded !== this.state.imageLoaded) {
return true;
}
if (!Utils.areObjectsEqual(nextState.data, this.state.data)) {
return true;
}
return false;
}

componentDidMount() {
OpenGraphStore.addUrlDataChangeListener(this.onOpenGraphMetadataChange);
}

componentDidUpdate() {
if (this.props.childComponentDidUpdateFunction) {
this.props.childComponentDidUpdateFunction();
}
}

componentWillUnmount() {
OpenGraphStore.removeUrlDataChangeListener(this.onOpenGraphMetadataChange);
}

onOpenGraphMetadataChange(url) {
if (url === this.props.link) {
this.fetchData(url);
}
}

fetchData(url) {
const data = OpenGraphStore.getOgInfo(url);
this.setState({data, imageLoaded: false});
if (Utils.isEmptyObject(data)) {
requestOpenGraphMetadata(url);
}
}

getBestImageUrl() {
const nearestPointData = CommonUtils.getNearestPoint(this.imageDimentions, this.state.data.images, 'width', 'height');

const bestImage = nearestPointData.nearestPoint;
const bestImageLte = nearestPointData.nearestPointLte; // Best image <= 150px height and width

let finalBestImage;

if (
!Utils.isEmptyObject(bestImageLte) &&
bestImageLte.height <= this.imageDimentions.height &&
bestImageLte.width <= this.imageDimentions.width
) {
finalBestImage = bestImageLte;
} else {
finalBestImage = bestImage;
}

return finalBestImage.secure_url || finalBestImage.url;
}

toggleImageVisibility() {
this.setState({imageVisible: !this.state.imageVisible});
}

onImageLoad() {
this.setState({imageLoaded: true});
}

loadImage(src) {
const img = new Image();
img.onload = this.onImageLoad;
img.src = src;
}

imageToggleAnchoreTag(imageUrl) {
if (imageUrl) {
return (
<a
className={'post__embed-visibility'}
data-expanded={this.state.imageVisible}
aria-label='Toggle Embed Visibility'
onClick={this.toggleImageVisibility}
/>
);
}
return null;
}

imageTag(imageUrl) {
if (imageUrl && this.state.imageVisible) {
return (
<img
className={this.state.imageLoaded ? 'attachment__image' : 'attachment__image loading'}
src={this.state.imageLoaded ? imageUrl : null}
/>
);
}
return null;
}

render() {
if (Utils.isEmptyObject(this.state.data) || Utils.isEmptyObject(this.state.data.description)) {
return null;
}

const data = this.state.data;
const imageUrl = this.getBestImageUrl();
var description = data.description;

if (description.length > this.maxDescriptionLength) {
description = description.substring(0, this.maxDescriptionLength - this.descriptionEllipsis.length) + this.descriptionEllipsis;
}

if (imageUrl && this.state.imageVisible) {
this.loadImage(imageUrl);
}

return (
<div
className='attachment attachment--oembed'
ref='attachment'
>
<div className='attachment__content'>
<div
className={'clearfix attachment__container'}
>
<span className='sitename'>{data.site_name}</span>
<h1
className='attachment__title has-link'
>
<a
className='attachment__title-link'
href={data.url || this.props.link}
target='_blank'
rel='noopener noreferrer'
title={data.title || data.url || this.props.link}
>
{data.title || data.url || this.props.link}
</a>
</h1>
<div >
<div
className={'attachment__body attachment__body--no_thumb'}
>
<div>
<div>
{description} &nbsp;
{this.imageToggleAnchoreTag(imageUrl)}
</div>
{this.imageTag(imageUrl)}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}

PostAttachmentOpenGraph.defaultProps = {
previewCollapsed: 'false'
};

PostAttachmentOpenGraph.propTypes = {
link: React.PropTypes.string.isRequired,
childComponentDidUpdateFunction: React.PropTypes.func,
previewCollapsed: React.PropTypes.string
};
4 changes: 3 additions & 1 deletion components/post_view/components/post_body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export default class PostBody extends React.Component {
message={messageWrapper}
compactDisplay={this.props.compactDisplay}
previewCollapsed={this.props.previewCollapsed}
childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
/>
);
}
Expand Down Expand Up @@ -221,5 +222,6 @@ PostBody.propTypes = {
handleCommentClick: React.PropTypes.func.isRequired,
compactDisplay: React.PropTypes.bool,
previewCollapsed: React.PropTypes.string,
isCommentMention: React.PropTypes.bool
isCommentMention: React.PropTypes.bool,
childComponentDidUpdateFunction: React.PropTypes.func
};
Loading

0 comments on commit 913fbe6

Please sign in to comment.