MM-15719 Use metadata only to render post additional content (#4386)
* MM-15719 Use metadata only to render post additional content * Default link to empty instead of undefined * Feedback review
This commit is contained in:
parent
57719d7dcc
commit
0f72bb34c2
21 changed files with 93 additions and 279 deletions
|
|
@ -11,7 +11,6 @@ import {
|
|||
getChannelByNameAndTeamName,
|
||||
leaveChannel as serviceLeaveChannel,
|
||||
} from '@mm-redux/actions/channels';
|
||||
import {getFilesForPost} from '@mm-redux/actions/files';
|
||||
import {savePreferences} from '@mm-redux/actions/preferences';
|
||||
import {selectTeam} from '@mm-redux/actions/teams';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
|
|
@ -110,17 +109,6 @@ export function fetchPostActionWithRetry(action, maxTries = MAX_RETRIES) {
|
|||
};
|
||||
}
|
||||
|
||||
export function loadFilesForPostIfNecessary(postId) {
|
||||
return async (dispatch, getState) => {
|
||||
const {files} = getState().entities;
|
||||
const fileIdsForPost = files.fileIdsByPostId[postId];
|
||||
|
||||
if (!fileIdsForPost?.length) {
|
||||
await dispatch(getFilesForPost(postId));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function loadThreadIfNecessary(rootId) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ const MAX_VISIBLE_ROW_IMAGES = 4;
|
|||
|
||||
export default class FileAttachmentList extends ImageViewPort {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadFilesForPostIfNecessary: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
canDownloadFiles: PropTypes.bool.isRequired,
|
||||
fileIds: PropTypes.array.isRequired,
|
||||
files: PropTypes.array,
|
||||
|
|
@ -45,22 +42,12 @@ export default class FileAttachmentList extends ImageViewPort {
|
|||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
const {files} = this.props;
|
||||
|
||||
if (files.length === 0) {
|
||||
this.loadFilesForPost();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.files.length !== this.props.files.length) {
|
||||
this.filesForGallery = this.getFilesForGallery(this.props);
|
||||
this.buildGalleryFiles().then((results) => {
|
||||
this.galleryFiles = results;
|
||||
});
|
||||
this.loadFilesForPost();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,10 +126,6 @@ export default class FileAttachmentList extends ImageViewPort {
|
|||
|
||||
isSingleImage = (files) => (files.length === 1 && this.isImage(files[0]));
|
||||
|
||||
loadFilesForPost = async () => {
|
||||
await this.props.actions.loadFilesForPostIfNecessary(this.props.postId);
|
||||
}
|
||||
|
||||
renderItems = (items, moreImagesCount, includeGutter = false) => {
|
||||
const {canDownloadFiles, isReplyPost, onLongPress, theme} = this.props;
|
||||
const isSingleImage = this.isSingleImage(items);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ jest.mock('react-native-file-viewer', () => ({
|
|||
}));
|
||||
|
||||
describe('FileAttachmentList', () => {
|
||||
const loadFilesForPostIfNecessary = jest.fn().mockImplementationOnce(() => Promise.resolve({data: {}}));
|
||||
|
||||
const files = [{
|
||||
create_at: 1546893090093,
|
||||
delete_at: 0,
|
||||
|
|
@ -55,9 +53,6 @@ describe('FileAttachmentList', () => {
|
|||
};
|
||||
|
||||
const baseProps = {
|
||||
actions: {
|
||||
loadFilesForPostIfNecessary,
|
||||
},
|
||||
canDownloadFiles: true,
|
||||
deviceHeight: 680,
|
||||
deviceWidth: 660,
|
||||
|
|
@ -162,45 +157,9 @@ describe('FileAttachmentList', () => {
|
|||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call loadFilesForPostIfNecessary when files does not exist', async () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
files: undefined,
|
||||
};
|
||||
|
||||
shallow(
|
||||
<FileAttachment {...props}/>,
|
||||
);
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
|
||||
});
|
||||
|
||||
test('should call loadFilesForPostIfNecessary on componentUpdate and when files does not exist', async () => {
|
||||
const loadFilesForPostIfNecessaryMock = jest.fn().mockImplementationOnce(() => Promise.resolve({data: {}}));
|
||||
const props = {
|
||||
...baseProps,
|
||||
files: [],
|
||||
actions: {
|
||||
loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock,
|
||||
},
|
||||
};
|
||||
|
||||
shallow(
|
||||
<FileAttachment {...props}/>,
|
||||
);
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(1);
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
|
||||
await loadFilesForPostIfNecessaryMock();
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(2);
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
|
||||
});
|
||||
|
||||
test('should call getFilesForGallery on props change', async () => {
|
||||
const loadFilesForPostIfNecessaryMock = jest.fn().mockImplementationOnce(() => Promise.resolve({data: {}}));
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock,
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
|
|
@ -211,23 +170,4 @@ describe('FileAttachmentList', () => {
|
|||
wrapper.setProps({files: [files[0], files[1]]});
|
||||
expect(wrapper.instance().getFilesForGallery).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call loadFilesForPostIfNecessary when files object empty', async () => {
|
||||
const loadFilesForPostIfNecessaryMock = jest.fn().mockImplementationOnce(() => Promise.resolve({data: {}}));
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock,
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<FileAttachment {...props}/>,
|
||||
);
|
||||
wrapper.setProps({files: []});
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(1);
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
|
||||
wrapper.setProps({test: 'test'});
|
||||
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {canDownloadFilesOnMobile} from '@mm-redux/selectors/entities/general';
|
||||
import {makeGetFilesForPost} from '@mm-redux/selectors/entities/files';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import {loadFilesForPostIfNecessary} from 'app/actions/views/channel';
|
||||
|
||||
import FileAttachmentList from './file_attachment_list';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
|
|
@ -23,12 +20,4 @@ function makeMapStateToProps() {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
loadFilesForPostIfNecessary,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(FileAttachmentList);
|
||||
export default connect(makeMapStateToProps)(FileAttachmentList);
|
||||
|
|
|
|||
|
|
@ -179,10 +179,15 @@ export default class Markdown extends PureComponent {
|
|||
};
|
||||
|
||||
renderImage = ({linkDestination, reactChildren, context, src}) => {
|
||||
if (!this.props.imagesMetadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.indexOf('table') !== -1) {
|
||||
// We have enough problems rendering images as is, so just render a link inside of a table
|
||||
return (
|
||||
<MarkdownTableImage
|
||||
imagesMetadata={this.props.imagesMetadata}
|
||||
source={src}
|
||||
textStyle={[this.computeTextStyle(this.props.baseTextStyle, context), this.props.textStyles.link]}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import React from 'react';
|
|||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Clipboard,
|
||||
Image,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
|
|
@ -57,11 +56,6 @@ export default class MarkdownImage extends ImageViewPort {
|
|||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.loadImageSize(this.getSource());
|
||||
}
|
||||
|
||||
setImageRef = (ref) => {
|
||||
this.imageRef = ref;
|
||||
}
|
||||
|
|
@ -169,12 +163,6 @@ export default class MarkdownImage extends ImageViewPort {
|
|||
previewImageAtIndex([this.itemRef], 0, files);
|
||||
};
|
||||
|
||||
loadImageSize = (source) => {
|
||||
if (!this.state.originalWidth) {
|
||||
Image.getSize(source, this.handleSizeReceived, this.handleSizeFailed);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (isGifTooLarge(this.props.imagesMetadata?.[this.props.source])) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import MarkdownTableImage from './markdown_table_image';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
serverURL: getCurrentUrl(state),
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import {goToScreen} from 'app/actions/navigation';
|
|||
export default class MarkdownTableImage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
imagesMetadata: PropTypes.object,
|
||||
source: PropTypes.string.isRequired,
|
||||
textStyle: CustomPropTypes.Style.isRequired,
|
||||
serverURL: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -32,6 +32,7 @@ export default class MarkdownTableImage extends React.PureComponent {
|
|||
defaultMessage: 'Image',
|
||||
});
|
||||
const passProps = {
|
||||
imagesMetadata: this.props.imagesMetadata,
|
||||
imageSource: this.getImageSource(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, View} from 'react-native';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import ProgressiveImage from 'app/components/progressive_image';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
|
|
@ -110,13 +110,7 @@ export default class AttachmentImage extends PureComponent {
|
|||
|
||||
if (imageMetadata) {
|
||||
this.setImageDimensionsFromMeta(imageURL, imageMetadata);
|
||||
return;
|
||||
}
|
||||
|
||||
Image.getSize(imageURL, (width, height) => {
|
||||
const dimensions = calculateDimensions(height, width, this.maxImageWidth);
|
||||
this.setImageDimensions(imageURL, dimensions, width, height);
|
||||
}, () => null);
|
||||
};
|
||||
|
||||
setViewPortMaxWidth = () => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Image} from 'react-native';
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
const originalGetSizeFn = Image.getSize;
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import AttachmentImage from './index';
|
||||
|
|
@ -20,10 +17,6 @@ describe('AttachmentImage', () => {
|
|||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
Image.getSize = originalGetSizeFn;
|
||||
});
|
||||
|
||||
test('it matches snapshot', () => {
|
||||
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
|
@ -47,22 +40,6 @@ describe('AttachmentImage', () => {
|
|||
expect(state.imageUri).toBe(null);
|
||||
});
|
||||
|
||||
test('it calls Image.getSize if metadata is not present', () => {
|
||||
const getSizeFn = jest.fn((_, callback) => {
|
||||
callback(64, 64);
|
||||
});
|
||||
Image.getSize = getSizeFn;
|
||||
|
||||
const props = {...baseProps, imageMetadata: null};
|
||||
const wrapper = shallow(<AttachmentImage {...props}/>);
|
||||
|
||||
const state = wrapper.state();
|
||||
expect(state.hasImage).toBe(true);
|
||||
expect(state.imageUri).toBe('https://images.com/image.png');
|
||||
expect(state.originalWidth).toBe(64);
|
||||
expect(getSizeFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('it updates image when imageUrl prop changes', () => {
|
||||
const wrapper = shallow(<AttachmentImage {...baseProps}/>);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export default class MessageAttachment extends PureComponent {
|
|||
|
||||
const style = getStyleSheet(theme);
|
||||
const STATUS_COLORS = getStatusColors(theme);
|
||||
const hasImage = Boolean(metadata?.images?.[attachment.image_url]);
|
||||
|
||||
let borderStyle;
|
||||
if (attachment.color) {
|
||||
|
|
@ -111,6 +112,7 @@ export default class MessageAttachment extends PureComponent {
|
|||
actions={attachment.actions}
|
||||
postId={postId}
|
||||
/>
|
||||
{hasImage &&
|
||||
<AttachmentImage
|
||||
deviceHeight={deviceHeight}
|
||||
deviceWidth={deviceWidth}
|
||||
|
|
@ -118,6 +120,7 @@ export default class MessageAttachment extends PureComponent {
|
|||
imageMetadata={metadata?.images?.[attachment.image_url]}
|
||||
theme={theme}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {getOpenGraphMetadata} from '@mm-redux/actions/posts';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
|
||||
|
|
@ -16,12 +13,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getOpenGraphMetadata,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostAttachmentOpenGraph);
|
||||
export default connect(mapStateToProps)(PostAttachmentOpenGraph);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, Linking, Text, View} from 'react-native';
|
||||
import {Linking, Text, View} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import {TABLET_WIDTH} from '@components/sidebars/drawer_layout';
|
||||
|
|
@ -19,9 +19,6 @@ const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
|
|||
|
||||
export default class PostAttachmentOpenGraph extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getOpenGraphMetadata: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
imagesMetadata: PropTypes.object,
|
||||
|
|
@ -40,23 +37,11 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
|
||||
this.fetchData(this.props.link, this.props.openGraphData);
|
||||
|
||||
if (this.state.openGraphImageUrl) {
|
||||
this.getImageSize(this.state.openGraphImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.link !== this.props.link) {
|
||||
this.fetchData(this.props.link, this.props.openGraphData);
|
||||
}
|
||||
|
||||
if (prevState.openGraphImageUrl !== this.state.openGraphImageUrl) {
|
||||
this.getImageSize(this.state.openGraphImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
|
@ -65,12 +50,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
this.itemRef = ref;
|
||||
};
|
||||
|
||||
fetchData = (url, openGraphData) => {
|
||||
if (!openGraphData) {
|
||||
this.props.actions.getOpenGraphMetadata(url);
|
||||
}
|
||||
};
|
||||
|
||||
getBestImageUrlAndDimensions = (data) => {
|
||||
if (!data || !data.images) {
|
||||
return {
|
||||
|
|
@ -147,11 +126,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
|
||||
if (ogImage?.width && ogImage?.height) {
|
||||
this.setImageSize(imageUrl, ogImage.width, ogImage.height);
|
||||
} else {
|
||||
// if we get to this point there can be a scroll pop
|
||||
Image.getSize(imageUrl, (width, height) => {
|
||||
this.setImageSize(imageUrl, width, height);
|
||||
}, () => null);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ export default class PostBody extends PureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (metadata && !metadata.embeds) {
|
||||
if (!metadata?.embeds?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,25 @@ describe('PostBody', () => {
|
|||
};
|
||||
|
||||
test('should mount additional content for non-system messages', () => {
|
||||
const metadata = {
|
||||
embeds: [{
|
||||
type: 'opengraph',
|
||||
url: 'https://youtu.be/g-EXttb1C9A',
|
||||
}],
|
||||
images: {
|
||||
'https://i.ytimg.com/vi/g-EXttb1C9A/maxresdefault.jpg': {
|
||||
format: 'jpeg',
|
||||
frame_count: 0,
|
||||
height: 720,
|
||||
width: 1280,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
isSystemMessage: false,
|
||||
metadata,
|
||||
};
|
||||
|
||||
const wrapper = shallowWithIntl(<PostBody {...props}/>);
|
||||
|
|
|
|||
|
|
@ -7,30 +7,15 @@ import {bindActionCreators} from 'redux';
|
|||
import {getRedirectLocation} from '@mm-redux/actions/general';
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getOpenGraphMetadataForUrl, getExpandedLink} from '@mm-redux/selectors/entities/posts';
|
||||
import {getOpenGraphMetadataForUrl as selectOpenGraphMetadataForUrl, getExpandedLink as selectExpandedLink} from '@mm-redux/selectors/entities/posts';
|
||||
import {getBool, getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import {extractFirstLink} from 'app/utils/url';
|
||||
|
||||
import PostBodyAdditionalContent from './post_body_additional_content';
|
||||
|
||||
function makeGetFirstLink() {
|
||||
let link;
|
||||
let lastMessage;
|
||||
|
||||
return (message) => {
|
||||
if (message !== lastMessage) {
|
||||
link = extractFirstLink(message);
|
||||
lastMessage = message;
|
||||
}
|
||||
|
||||
return link;
|
||||
};
|
||||
}
|
||||
|
||||
function getOpenGraphData(metadata, url) {
|
||||
function selectOpenGraphData(metadata, url) {
|
||||
if (!metadata || !metadata.embeds) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -40,39 +25,35 @@ function getOpenGraphData(metadata, url) {
|
|||
});
|
||||
}
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getFirstLink = makeGetFirstLink();
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const config = getConfig(state);
|
||||
const link = ownProps.metadata.embeds[0]?.url || '';
|
||||
let expandedLink;
|
||||
if (link) {
|
||||
expandedLink = selectExpandedLink(state, link);
|
||||
}
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const config = getConfig(state);
|
||||
const link = getFirstLink(ownProps.message);
|
||||
let expandedLink;
|
||||
if (link) {
|
||||
expandedLink = getExpandedLink(state, link);
|
||||
}
|
||||
// Link previews used to be an advanced settings until server version 4.4 when it was changed to be a display setting.
|
||||
// We are checking both here until we bump the server requirement for the mobile apps.
|
||||
const previewsEnabled = (getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, `${ViewTypes.FEATURE_TOGGLE_PREFIX}${ViewTypes.EMBED_PREVIEW}`) ||
|
||||
getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true));
|
||||
|
||||
// Link previews used to be an advanced settings until server version 4.4 when it was changed to be a display setting.
|
||||
// We are checking both here until we bump the server requirement for the mobile apps.
|
||||
const previewsEnabled = (getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, `${ViewTypes.FEATURE_TOGGLE_PREFIX}${ViewTypes.EMBED_PREVIEW}`) ||
|
||||
getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true));
|
||||
const removeLinkPreview = ownProps.postProps.remove_link_preview === 'true';
|
||||
|
||||
const removeLinkPreview = ownProps.postProps.remove_link_preview === 'true';
|
||||
let openGraphData = selectOpenGraphMetadataForUrl(state, ownProps.postId, link);
|
||||
if (!openGraphData) {
|
||||
const data = selectOpenGraphData(ownProps.metadata, link);
|
||||
openGraphData = data?.data;
|
||||
}
|
||||
|
||||
let openGraphData = getOpenGraphMetadataForUrl(state, ownProps.postId, link);
|
||||
if (!openGraphData) {
|
||||
const data = getOpenGraphData(ownProps.metadata, link);
|
||||
openGraphData = data?.data;
|
||||
}
|
||||
|
||||
return {
|
||||
...getDimensions(state),
|
||||
googleDeveloperKey: config.GoogleDeveloperKey,
|
||||
link,
|
||||
expandedLink,
|
||||
openGraphData,
|
||||
showLinkPreviews: previewsEnabled && config.EnableLinkPreviews === 'true' && !removeLinkPreview,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
return {
|
||||
...getDimensions(state),
|
||||
googleDeveloperKey: config.GoogleDeveloperKey,
|
||||
link,
|
||||
expandedLink,
|
||||
openGraphData,
|
||||
showLinkPreviews: previewsEnabled && config.EnableLinkPreviews === 'true' && !removeLinkPreview,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -84,4 +65,4 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(PostBodyAdditionalContent);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostBodyAdditionalContent);
|
||||
|
|
|
|||
|
|
@ -34,24 +34,24 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
actions: PropTypes.shape({
|
||||
getRedirectLocation: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
link: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
blockStyles: PropTypes.object,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
expandedLink: PropTypes.string,
|
||||
googleDeveloperKey: PropTypes.string,
|
||||
isReplyPost: PropTypes.bool,
|
||||
link: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
metadata: PropTypes.object,
|
||||
onHashtagPress: PropTypes.func,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
openGraphData: PropTypes.object,
|
||||
postId: PropTypes.string.isRequired,
|
||||
postProps: PropTypes.object.isRequired,
|
||||
showLinkPreviews: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
blockStyles: PropTypes.object,
|
||||
googleDeveloperKey: PropTypes.string,
|
||||
metadata: PropTypes.object,
|
||||
isReplyPost: PropTypes.bool,
|
||||
onHashtagPress: PropTypes.func,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
openGraphData: PropTypes.object,
|
||||
textStyles: PropTypes.object,
|
||||
expandedLink: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -122,10 +122,6 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
|
||||
if (img && img.height && img.width) {
|
||||
this.setImageSize(path, img.width, img.height);
|
||||
} else {
|
||||
Image.getSize(path, (width, height) => {
|
||||
this.setImageSize(path, width, height);
|
||||
}, () => this.setState({linkLoadError: true}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -137,7 +133,7 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
imageUrl = link;
|
||||
} else if (isYoutubeLink(link)) {
|
||||
const videoId = getYouTubeVideoId(link);
|
||||
imageUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
|
||||
imageUrl = Object.keys(this.props.metadata.images)[0] || `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
|
||||
}
|
||||
|
||||
return imageUrl;
|
||||
|
|
@ -221,12 +217,12 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
const {link, expandedLink, actions} = this.props;
|
||||
|
||||
if (link) {
|
||||
let imageUrl = this.getImageUrl(link);
|
||||
|
||||
if (isYoutubeLink(link)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let imageUrl = this.getImageUrl(link);
|
||||
|
||||
if (!imageUrl) {
|
||||
if (!expandedLink || linkChanged) {
|
||||
actions.getRedirectLocation(link);
|
||||
|
|
@ -295,6 +291,10 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
const imageMetadata = this.props.metadata?.images?.[link];
|
||||
const {width, height, uri} = this.state;
|
||||
|
||||
if (!imageMetadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PostAttachmentImage
|
||||
height={height || MAX_IMAGE_HEIGHT}
|
||||
|
|
@ -360,11 +360,11 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
return attachments;
|
||||
}
|
||||
|
||||
if (!openGraphData && metadata) {
|
||||
if (!openGraphData || !showLinkPreviews) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (link && showLinkPreviews) {
|
||||
if (link) {
|
||||
if (!PostAttachmentOpenGraph) {
|
||||
PostAttachmentOpenGraph = require('app/components/post_attachment_opengraph').default;
|
||||
}
|
||||
|
|
@ -374,7 +374,7 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
isReplyPost={isReplyPost}
|
||||
link={link}
|
||||
openGraphData={openGraphData}
|
||||
imagesMetadata={metadata && metadata.images}
|
||||
imagesMetadata={metadata.images}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
|
@ -390,8 +390,8 @@ export default class PostBodyAdditionalContent extends ImageViewPort {
|
|||
MAX_YOUTUBE_IMAGE_WIDTH,
|
||||
getViewPortWidth(this.props.isReplyPost, this.hasPermanentSidebar()),
|
||||
);
|
||||
const imgUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
|
||||
|
||||
const imgUrl = Object.keys(this.props.metadata.images)[0] || `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
style={[styles.imageContainer, {height: dimensions.height}]}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {getReactionsForPost, removeReaction} from '@mm-redux/actions/posts';
|
||||
import {removeReaction} from '@mm-redux/actions/posts';
|
||||
import {makeGetReactionsForPost, getPost} from '@mm-redux/selectors/entities/posts';
|
||||
import {haveIChannelPermission} from '@mm-redux/selectors/entities/roles';
|
||||
import {hasNewPermissions} from '@mm-redux/selectors/entities/general';
|
||||
|
|
@ -74,7 +74,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
addReaction,
|
||||
getReactionsForPost,
|
||||
removeReaction,
|
||||
}, dispatch),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export default class Reactions extends PureComponent {
|
|||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
addReaction: PropTypes.func.isRequired,
|
||||
getReactionsForPost: PropTypes.func.isRequired,
|
||||
removeReaction: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
canAddReaction: PropTypes.bool,
|
||||
|
|
@ -43,13 +42,6 @@ export default class Reactions extends PureComponent {
|
|||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const {actions, postId, reactions} = this.props;
|
||||
if (!reactions) {
|
||||
actions.getReactionsForPost(postId);
|
||||
}
|
||||
}
|
||||
|
||||
handleAddReaction = preventDoubleTap(() => {
|
||||
const {theme} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ describe('Reactions', () => {
|
|||
const baseProps = {
|
||||
actions: {
|
||||
addReaction: jest.fn(),
|
||||
getReactionsForPost: jest.fn(),
|
||||
removeReaction: jest.fn(),
|
||||
},
|
||||
canAddReaction: true,
|
||||
|
|
|
|||
|
|
@ -14,23 +14,21 @@ import FastImage from 'react-native-fast-image';
|
|||
export default class TableImage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
imagesMetadata: PropTypes.object,
|
||||
imageSource: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const dimensions = props.imagesMetadata?.[props.imageSource];
|
||||
this.state = {
|
||||
imageSource: '',
|
||||
width: -1,
|
||||
height: -1,
|
||||
width: dimensions?.width || -1,
|
||||
height: dimensions?.height || -1,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getImageSize(this.props.imageSource);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, state) {
|
||||
if (nextProps.imageSource !== state.imageSource) {
|
||||
return {
|
||||
|
|
@ -44,16 +42,16 @@ export default class TableImage extends React.PureComponent {
|
|||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.imageSource !== this.props.imageSource) {
|
||||
this.getImageSize(this.props.imageSource);
|
||||
this.getImageSize();
|
||||
}
|
||||
}
|
||||
|
||||
getImageSize = (imageSource) => {
|
||||
Image.getSize(imageSource, (width, height) => {
|
||||
this.setState({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
getImageSize = () => {
|
||||
const {imageSource, imagesMetadata} = this.props;
|
||||
const dimensions = imagesMetadata?.[imageSource];
|
||||
this.setState({
|
||||
width: dimensions?.width || -1,
|
||||
height: dimensions?.height || -1,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue