summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webapp/components/post_view/components/post_attachment_opengraph.jsx221
-rw-r--r--webapp/sass/layout/_post.scss2
-rw-r--r--webapp/sass/layout/_webhooks.scss69
-rw-r--r--webapp/sass/responsive/_mobile.scss45
-rw-r--r--webapp/sass/responsive/_tablet.scss13
-rw-r--r--webapp/tests/utils_get_nearest_point.test.jsx8
-rw-r--r--webapp/utils/commons.jsx18
7 files changed, 281 insertions, 95 deletions
diff --git a/webapp/components/post_view/components/post_attachment_opengraph.jsx b/webapp/components/post_view/components/post_attachment_opengraph.jsx
index b83150839..12437e672 100644
--- a/webapp/components/post_view/components/post_attachment_opengraph.jsx
+++ b/webapp/components/post_view/components/post_attachment_opengraph.jsx
@@ -11,29 +11,47 @@ import {requestOpenGraphMetadata} from 'actions/global_actions.jsx';
export default class PostAttachmentOpenGraph extends React.Component {
constructor(props) {
super(props);
+ this.largeImageMinWidth = 150;
this.imageDimentions = { // Image dimentions in pixels.
- height: 150,
- width: 150
+ height: 80,
+ width: 80
};
- this.maxDescriptionLength = 300;
- this.descriptionEllipsis = '...';
+ this.textMaxLenght = 300;
+ this.textEllipsis = '...';
+ this.largeImageMinRatio = 16 / 9;
+ this.smallImageContainerLeftPadding = 15;
+
+ this.imageRatio = null;
+
+ this.smallImageContainer = null;
+ this.smallImageElement = null;
+
this.fetchData = this.fetchData.bind(this);
this.onOpenGraphMetadataChange = this.onOpenGraphMetadataChange.bind(this);
this.toggleImageVisibility = this.toggleImageVisibility.bind(this);
this.onImageLoad = this.onImageLoad.bind(this);
+ this.onImageError = this.onImageError.bind(this);
+ this.truncateText = this.truncateText.bind(this);
+ this.setImageWidth = this.setImageWidth.bind(this);
+ }
+
+ IMAGE_LOADED = {
+ LOADING: 'loading',
+ YES: 'yes',
+ ERROR: 'error'
}
componentWillMount() {
this.setState({
data: {},
- imageLoaded: false,
- imageVisible: this.props.previewCollapsed.startsWith('false')
+ imageLoaded: this.IMAGE_LOADED.LOADING,
+ imageVisible: this.props.previewCollapsed.startsWith('false'),
+ hasLargeImage: 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);
}
@@ -43,6 +61,9 @@ export default class PostAttachmentOpenGraph extends React.Component {
if (nextState.imageVisible !== this.state.imageVisible) {
return true;
}
+ if (nextState.hasLargeImage !== this.state.hasLargeImage) {
+ return true;
+ }
if (nextState.imageLoaded !== this.state.imageLoaded) {
return true;
}
@@ -54,16 +75,20 @@ export default class PostAttachmentOpenGraph extends React.Component {
componentDidMount() {
OpenGraphStore.addUrlDataChangeListener(this.onOpenGraphMetadataChange);
+ this.setImageWidth();
+ window.addEventListener('resize', this.setImageWidth);
}
componentDidUpdate() {
if (this.props.childComponentDidUpdateFunction) {
this.props.childComponentDidUpdateFunction();
}
+ this.setImageWidth();
}
componentWillUnmount() {
OpenGraphStore.removeUrlDataChangeListener(this.onOpenGraphMetadataChange);
+ window.removeEventListener('resize', this.setImageWidth);
}
onOpenGraphMetadataChange(url) {
@@ -74,53 +99,54 @@ export default class PostAttachmentOpenGraph extends React.Component {
fetchData(url) {
const data = OpenGraphStore.getOgInfo(url);
- this.setState({data, imageLoaded: false});
+ this.setState({data, imageLoaded: this.IMAGE_LOADED.LOADING});
if (Utils.isEmptyObject(data)) {
requestOpenGraphMetadata(url);
}
}
getBestImageUrl() {
- if (this.state.data.images == null) {
+ if (Utils.isEmptyObject(this.state.data.images)) {
return null;
}
- 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
+ const bestImage = CommonUtils.getNearestPoint(this.imageDimentions, this.state.data.images, 'width', 'height');
+ return bestImage.secure_url || bestImage.url;
+ }
- let finalBestImage;
+ toggleImageVisibility() {
+ this.setState({imageVisible: !this.state.imageVisible});
+ }
+ onImageLoad(image) {
+ this.imageRatio = image.target.naturalWidth / image.target.naturalHeight;
if (
- !Utils.isEmptyObject(bestImageLte) &&
- bestImageLte.height <= this.imageDimentions.height &&
- bestImageLte.width <= this.imageDimentions.width
+ image.target.naturalWidth >= this.largeImageMinWidth &&
+ this.imageRatio >= this.largeImageMinRatio &&
+ !this.state.hasLargeImage
) {
- finalBestImage = bestImageLte;
- } else {
- finalBestImage = bestImage;
+ this.setState({
+ hasLargeImage: true
+ });
}
-
- return finalBestImage.secure_url || finalBestImage.url;
- }
-
- toggleImageVisibility() {
- this.setState({imageVisible: !this.state.imageVisible});
+ this.setState({
+ imageLoaded: this.IMAGE_LOADED.YES
+ });
}
- onImageLoad() {
- this.setState({imageLoaded: true});
+ 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) {
+ if (imageUrl && this.state.hasLargeImage) {
return (
<a
className={'post__embed-visibility'}
@@ -133,16 +159,81 @@ export default class PostAttachmentOpenGraph extends React.Component {
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}
- />
+ wrapInSmallImageContainer(imageElement) {
+ return (
+ <div
+ className='attachment__image__container--openraph'
+ style={{
+ width: (this.imageDimentions.height * this.imageRatio) + this.smallImageContainerLeftPadding
+ }} // Initially set the width accordinly to max image heigh, ie 80px. Later on it would be modified according to actul height of image.
+ ref={(div) => {
+ this.smallImageContainer = div;
+ }}
+ >
+ {imageElement}
+ </div>
+ );
+ }
+
+ 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 = <img className={'attachment__image attachment__image--openraph loading large_image'}/>;
+ } else {
+ element = this.wrapInSmallImageContainer(
+ <img className={'attachment__image attachment__image--openraph loading '}/>
+ );
+ }
+ } else if (this.state.imageLoaded === this.IMAGE_LOADED.YES) {
+ if (renderingForLargeImage) {
+ element = (
+ <img
+ className={'attachment__image attachment__image--openraph large_image'}
+ src={imageUrl}
+ />
+ );
+ } else {
+ element = this.wrapInSmallImageContainer(
+ <img
+ className={'attachment__image attachment__image--openraph'}
+ src={imageUrl}
+ ref={(img) => {
+ this.smallImageElement = img;
+ }}
+ />
+ );
+ }
+ } else if (this.state.imageLoaded === this.IMAGE_LOADED.ERROR) {
+ return null;
+ }
+ }
+ return element;
+ }
+
+ setImageWidth() {
+ if (
+ this.state.imageLoaded === this.IMAGE_LOADED.YES &&
+ this.smallImageContainer &&
+ this.smallImageElement
+ ) {
+ this.smallImageContainer.style.width = (
+ (this.smallImageElement.offsetHeight * this.imageRatio) +
+ this.smallImageContainerLeftPadding +
+ 'px'
);
}
- return null;
+ }
+
+ truncateText(text, maxLength = this.textMaxLenght, ellipsis = this.textEllipsis) {
+ if (text.length > maxLength) {
+ return text.substring(0, maxLength - ellipsis.length) + ellipsis;
+ }
+ return text;
}
render() {
@@ -152,52 +243,52 @@ export default class PostAttachmentOpenGraph extends React.Component {
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) {
+ if (imageUrl) {
this.loadImage(imageUrl);
}
return (
<div
- className='attachment attachment--oembed'
+ className='attachment attachment--opengraph'
ref='attachment'
>
<div className='attachment__content'>
<div
- className={'clearfix attachment__container'}
+ className={'clearfix attachment__container attachment__container--opengraph'}
>
- <span className='sitename'>{data.site_name}</span>
- <h1
- className='attachment__title has-link'
+ <div
+ className={'attachment__body__wrap attachment__body__wrap--opengraph'}
>
- <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'}
+ <span className='sitename'>{this.truncateText(data.site_name)}</span>
+ <h1
+ className={'attachment__title attachment__title--opengraph' + (data.title ? '' : ' is-url')}
>
- <div>
+ <a
+ className='attachment__title-link attachment__title-link--opengraph'
+ href={data.url || this.props.link}
+ target='_blank'
+ rel='noopener noreferrer'
+ title={data.title || data.url || this.props.link}
+ >
+ {this.truncateText(data.title || data.url || this.props.link)}
+ </a>
+ </h1>
+ <div >
+ <div
+ className={'attachment__body attachment__body--opengraph'}
+ >
<div>
- {description} &nbsp;
- {this.imageToggleAnchoreTag(imageUrl)}
+ <div>
+ {this.truncateText(data.description)} &nbsp;
+ {this.imageToggleAnchoreTag(imageUrl)}
+ </div>
+ {this.imageTag(imageUrl, true)}
</div>
- {this.imageTag(imageUrl)}
</div>
</div>
</div>
+ {this.imageTag(imageUrl, false)}
</div>
</div>
</div>
diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss
index bca10cae2..ab391fa1d 100644
--- a/webapp/sass/layout/_post.scss
+++ b/webapp/sass/layout/_post.scss
@@ -1191,7 +1191,7 @@
cursor: pointer;
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
- margin: 0 0 10px;
+ margin: 0;
text-rendering: auto;
&.pull-left {
diff --git a/webapp/sass/layout/_webhooks.scss b/webapp/sass/layout/_webhooks.scss
index 904c50ccc..f3a8c6fd3 100644
--- a/webapp/sass/layout/_webhooks.scss
+++ b/webapp/sass/layout/_webhooks.scss
@@ -38,6 +38,9 @@
.post {
.attachment {
+ &.attachment--opengraph {
+ max-width: 800px;
+ }
.attachment__content {
border-radius: 4px;
border-style: solid;
@@ -68,11 +71,29 @@
&.attachment__container--danger {
border-left-color: #e40303;
}
+ &.attachment__container--opengraph {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ margin: 0;
+ padding-bottom: 13px;
+ div {
+ margin: 0;
+ }
+ }
.sitename {
color: #A3A3A3;
}
}
+ .attachment__body__wrap {
+ &.attachment__body__wrap--opengraph {
+ display: table-cell;
+ width: 100%;
+ vertical-align: top;
+ }
+ }
+
.attachment__body {
float: left;
overflow-x: auto;
@@ -83,13 +104,11 @@
&.attachment__body--no_thumb {
width: 100%;
}
- .attachment__image {
- margin-bottom: 0;
- max-height: 150px;
- max-width: 150px;
- &.loading {
- height: 150px;
- }
+ &.attachment__body--opengraph {
+ float: none;
+ padding-right: 0;
+ width: 100%;
+ word-wrap: break-word;
}
}
@@ -97,10 +116,38 @@
display: inline-block;
}
+ .attachment__image__container--openraph {
+ display: table-cell;
+ vertical-align: top;
+ padding-top: 3px;
+ padding-left: 15px;
+ }
+
.attachment__image {
margin-bottom: 1em;
max-height: 300px;
max-width: 500px;
+
+ &.attachment__image--openraph {
+ margin-bottom: 0;
+ max-height: 80px;
+ max-width: 200px;
+
+ &.loading {
+ height: 80px;
+ }
+
+ &.large_image {
+ border-radius: 3px;
+ margin-top: 10px;
+ max-height: 200px;
+ max-width: 400px;
+
+ &.loading {
+ height: 150px;
+ }
+ }
+ }
}
.attachment__author-name {
@@ -121,6 +168,14 @@
overflow: hidden;
white-space: nowrap;
}
+
+ &.attachment__title--opengraph {
+ height: auto;
+ word-wrap: break-word;
+ &.is-url {
+ word-break: break-all
+ }
+ }
}
.attachment-link-more {
diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss
index d1fc10428..3170fb0d4 100644
--- a/webapp/sass/responsive/_mobile.scss
+++ b/webapp/sass/responsive/_mobile.scss
@@ -1210,6 +1210,15 @@
}
}
}
+ .post {
+ .attachment {
+ .attachment__image {
+ &.attachment__image--openraph {
+ max-width: 200px;
+ }
+ }
+ }
+ }
}
@media screen and (max-width: 640px) {
@@ -1385,6 +1394,16 @@
text-align: left;
}
}
+
+ .post {
+ .attachment {
+ .attachment__image {
+ &.attachment__image--openraph {
+ max-width: 200px;
+ }
+ }
+ }
+ }
}
@media screen and (max-width: 550px) {
@@ -1415,6 +1434,15 @@
top: 60px;
width: calc(100% - 30px);
}
+ .post {
+ .attachment {
+ .attachment__image {
+ &.attachment__image--openraph {
+ max-width: 180px;
+ }
+ }
+ }
+ }
}
@media screen and (max-width: 480px) {
@@ -1521,6 +1549,16 @@
.integration__icon {
display: none;
}
+
+ .post {
+ .attachment {
+ .attachment__image {
+ &.attachment__image--openraph {
+ max-width: 120px;
+ }
+ }
+ }
+ }
}
@media screen and (max-height: 640px) {
@@ -1553,6 +1591,13 @@
}
}
}
+ .attachment {
+ .attachment__image {
+ &.attachment__image--openraph {
+ max-width: 80px;
+ }
+ }
+ }
}
.tutorial-steps__container {
diff --git a/webapp/sass/responsive/_tablet.scss b/webapp/sass/responsive/_tablet.scss
index 96a71694f..06a725a31 100644
--- a/webapp/sass/responsive/_tablet.scss
+++ b/webapp/sass/responsive/_tablet.scss
@@ -128,6 +128,19 @@
}
}
}
+ .post {
+ .attachment {
+ .attachment__image {
+ &.attachment__image--openraph {
+ max-height: 70px;
+ max-width: 300px;
+ &.loading {
+ height: 70px;
+ }
+ }
+ }
+ }
+ }
}
// Tablet and desktop
diff --git a/webapp/tests/utils_get_nearest_point.test.jsx b/webapp/tests/utils_get_nearest_point.test.jsx
index b0b0a2e0e..02ca29cc3 100644
--- a/webapp/tests/utils_get_nearest_point.test.jsx
+++ b/webapp/tests/utils_get_nearest_point.test.jsx
@@ -24,12 +24,10 @@ describe('CommonUtils.getNearestPoint', function() {
nearestPointLte: {x: 1, y: 1}
}
]) {
- const nearestPointData = CommonUtils.getNearestPoint(data.pivotPoint, data.points);
+ const nearestPoint = CommonUtils.getNearestPoint(data.pivotPoint, data.points);
- assert.equal(nearestPointData.nearestPoint.x, data.nearestPoint.x);
- assert.equal(nearestPointData.nearestPoint.y, data.nearestPoint.y);
- assert.equal(nearestPointData.nearestPointLte.x, data.nearestPointLte.x);
- assert.equal(nearestPointData.nearestPointLte.y, data.nearestPointLte.y);
+ assert.equal(nearestPoint.x, data.nearestPoint.x);
+ assert.equal(nearestPoint.y, data.nearestPoint.y);
}
});
});
diff --git a/webapp/utils/commons.jsx b/webapp/utils/commons.jsx
index 1888869dc..224653df7 100644
--- a/webapp/utils/commons.jsx
+++ b/webapp/utils/commons.jsx
@@ -8,7 +8,6 @@ export function getDistanceBW2Points(point1, point2, xAttr = 'x', yAttr = 'y') {
*/
export function getNearestPoint(pivotPoint, points, xAttr = 'x', yAttr = 'y') {
var nearestPoint = {};
- var nearestPointLte = {}; // Nearest point smaller than or equal to point
for (const point of points) {
if (typeof nearestPoint[xAttr] === 'undefined' || typeof nearestPoint[yAttr] === 'undefined') {
nearestPoint = point;
@@ -16,21 +15,6 @@ export function getNearestPoint(pivotPoint, points, xAttr = 'x', yAttr = 'y') {
// Check for bestImage
nearestPoint = point;
}
-
- if (typeof nearestPointLte[xAttr] === 'undefined' || typeof nearestPointLte[yAttr] === 'undefined') {
- if (point[xAttr] <= pivotPoint[xAttr] && point[yAttr] <= pivotPoint[yAttr]) {
- nearestPointLte = point;
- }
- } else if (
- // Check for bestImageLte
- getDistanceBW2Points(point, pivotPoint, xAttr, yAttr) < getDistanceBW2Points(nearestPointLte, pivotPoint, xAttr, yAttr) &&
- point[xAttr] <= pivotPoint[xAttr] && point[yAttr] <= pivotPoint[yAttr]
- ) {
- nearestPointLte = point;
- }
}
- return {
- nearestPoint,
- nearestPointLte
- };
+ return nearestPoint;
}