MM-23523 Migrate "components/markdown_image/*" to typescript (#23524)
Этот коммит содержится в:
@@ -5,7 +5,7 @@ exports[`components/MarkdownImage should match snapshot 1`] = `
|
||||
imageMetadata={
|
||||
Object {
|
||||
"format": "png",
|
||||
"frame_count": 0,
|
||||
"frameCount": 0,
|
||||
"height": 90,
|
||||
"width": 1041,
|
||||
}
|
||||
@@ -23,22 +23,32 @@ exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimen
|
||||
dimensions={
|
||||
Object {
|
||||
"format": "jpg",
|
||||
"frame_count": 0,
|
||||
"frameCount": 0,
|
||||
"height": 90,
|
||||
"width": 100,
|
||||
}
|
||||
}
|
||||
height=""
|
||||
onClick={[Function]}
|
||||
onImageLoadFail={[Function]}
|
||||
onImageLoaded={[Function]}
|
||||
showLoader={false}
|
||||
src="safeSrc"
|
||||
title="test title"
|
||||
width=""
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/MarkdownImage should match snapshot for broken link 1`] = `
|
||||
<Connect(ExternalImage)
|
||||
imageMetadata={Object {}}
|
||||
imageMetadata={
|
||||
Object {
|
||||
"format": "png",
|
||||
"frameCount": 0,
|
||||
"height": 10,
|
||||
"width": 10,
|
||||
}
|
||||
}
|
||||
src="brokenLink"
|
||||
>
|
||||
<Component />
|
||||
@@ -53,7 +63,7 @@ exports[`components/MarkdownImage should provide image src as an alt text for Ma
|
||||
postId="post_id"
|
||||
>
|
||||
<SizeAwareImage
|
||||
alt={null}
|
||||
alt=""
|
||||
className="markdown-inline-img markdown-inline-img--hover cursor--pointer a11y--active"
|
||||
dimensions={Object {}}
|
||||
height="250"
|
||||
@@ -93,16 +103,19 @@ exports[`components/MarkdownImage should render an image with no preview if the
|
||||
dimensions={
|
||||
Object {
|
||||
"format": "png",
|
||||
"frame_count": 0,
|
||||
"frameCount": 0,
|
||||
"height": 90,
|
||||
"width": 1041,
|
||||
}
|
||||
}
|
||||
height=""
|
||||
onClick={[Function]}
|
||||
onImageLoadFail={[Function]}
|
||||
onImageLoaded={[Function]}
|
||||
showLoader={false}
|
||||
src="https://example.com/image.png"
|
||||
title="test title"
|
||||
width=""
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -115,16 +128,19 @@ exports[`components/MarkdownImage should render an image with preview modal if t
|
||||
dimensions={
|
||||
Object {
|
||||
"format": "png",
|
||||
"frame_count": 0,
|
||||
"frameCount": 0,
|
||||
"height": 90,
|
||||
"width": 1041,
|
||||
}
|
||||
}
|
||||
height=""
|
||||
onClick={[Function]}
|
||||
onImageLoadFail={[Function]}
|
||||
onImageLoaded={[Function]}
|
||||
showLoader={false}
|
||||
src="https://example.com/image.png"
|
||||
title="test title"
|
||||
width=""
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -2,15 +2,16 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {ActionCreatorsMapObject, bindActionCreators, Dispatch} from 'redux';
|
||||
import {Action, GenericAction} from 'mattermost-redux/types/actions';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import MarkdownImage from './markdown_image';
|
||||
import MarkdownImage, {Props} from './markdown_image';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
actions: bindActionCreators<ActionCreatorsMapObject<Action>, Props['actions']>({
|
||||
openModal,
|
||||
}, dispatch),
|
||||
};
|
||||
@@ -18,13 +18,18 @@ describe('components/MarkdownImage', () => {
|
||||
format: 'png',
|
||||
height: 90,
|
||||
width: 1041,
|
||||
frame_count: 0,
|
||||
frameCount: 0,
|
||||
},
|
||||
alt: 'test image',
|
||||
height: '',
|
||||
width: '',
|
||||
title: 'test title',
|
||||
className: 'markdown-inline-img',
|
||||
postId: 'post_id',
|
||||
imageIsLink: false,
|
||||
onImageLoaded: jest.fn(),
|
||||
onImageHeightChanged: jest.fn(),
|
||||
postType: 'system_generic',
|
||||
actions: {
|
||||
openModal: jest.fn(),
|
||||
},
|
||||
@@ -40,7 +45,14 @@ describe('components/MarkdownImage', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot for broken link', () => {
|
||||
const props = {...baseProps, imageMetadata: {}, src: 'brokenLink'};
|
||||
const props = {...baseProps,
|
||||
imageMetadata: {
|
||||
format: 'png',
|
||||
height: 10,
|
||||
width: 10,
|
||||
frameCount: 0,
|
||||
},
|
||||
src: 'brokenLink'};
|
||||
const wrapper = shallow(
|
||||
<MarkdownImage {...props}/>,
|
||||
);
|
||||
@@ -49,20 +61,36 @@ describe('components/MarkdownImage', () => {
|
||||
});
|
||||
|
||||
test('should handle load failure properly', () => {
|
||||
const props = {...baseProps, imageMetadata: {}, src: 'brokenLink'};
|
||||
const props = {...baseProps,
|
||||
imageMetadata: {
|
||||
format: 'png',
|
||||
height: 10,
|
||||
width: 10,
|
||||
frameCount: 0,
|
||||
|
||||
},
|
||||
src: 'brokenLink'};
|
||||
const wrapper = shallow(
|
||||
<MarkdownImage {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state('loadFailed')).toBe(false);
|
||||
|
||||
wrapper.instance().handleLoadFail();
|
||||
(wrapper.instance() as MarkdownImage).handleLoadFail();
|
||||
|
||||
expect(wrapper.state('loadFailed')).toBe(true);
|
||||
});
|
||||
|
||||
test('should reset loadFailed state after image source is updated', () => {
|
||||
const props = {...baseProps, imageMetadata: {}, src: 'brokenLink'};
|
||||
const props = {...baseProps,
|
||||
imageMetadata: {
|
||||
format: 'png',
|
||||
height: 10,
|
||||
width: 10,
|
||||
frameCount: 0,
|
||||
|
||||
},
|
||||
src: 'brokenLink'};
|
||||
const nextProps = {...baseProps, src: 'https://example.com/image.png'};
|
||||
const wrapper = shallow(
|
||||
<MarkdownImage {...props}/>,
|
||||
@@ -133,7 +161,7 @@ describe('components/MarkdownImage', () => {
|
||||
|
||||
expect(wrapper.state('loaded')).toBe(false);
|
||||
|
||||
wrapper.instance().handleImageLoaded(dimensions);
|
||||
(wrapper.instance() as MarkdownImage).handleImageLoaded(dimensions);
|
||||
|
||||
expect(wrapper.state('loaded')).toBe(true);
|
||||
|
||||
@@ -143,7 +171,7 @@ describe('components/MarkdownImage', () => {
|
||||
|
||||
it('should match snapshot for SizeAwareImage dimensions', () => {
|
||||
const props = {...baseProps,
|
||||
imageMetadata: {format: 'jpg', frame_count: 0, width: 100, height: 90},
|
||||
imageMetadata: {format: 'jpg', frameCount: 0, width: 100, height: 90},
|
||||
src: 'path/image',
|
||||
};
|
||||
const wrapper = shallow(
|
||||
@@ -198,8 +226,10 @@ describe('components/MarkdownImage', () => {
|
||||
const wrapper = shallow(
|
||||
<MarkdownImage {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().showModal({preventDefault: () => {}}, 'https://example.com/image.png');
|
||||
const mockEvent = {
|
||||
preventDefault: () => {},
|
||||
};
|
||||
(wrapper.instance() as MarkdownImage).showModal(mockEvent as unknown as React.MouseEvent<HTMLImageElement>, 'https://example.com/image.png');
|
||||
expect(props.actions.openModal).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -281,7 +311,7 @@ describe('components/MarkdownImage', () => {
|
||||
|
||||
test('should provide image src as an alt text for MarkdownImageExpand if image has no own alt text', () => {
|
||||
const props = {
|
||||
alt: null,
|
||||
alt: '',
|
||||
title: 'test title',
|
||||
className: 'markdown-inline-img',
|
||||
postId: 'post_id',
|
||||
@@ -1,48 +1,53 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import React, {PureComponent, MouseEvent} from 'react';
|
||||
|
||||
import Constants, {ModalIdentifiers} from 'utils/constants';
|
||||
import {Post, PostImage} from '@mattermost/types/posts';
|
||||
|
||||
import {ModalData} from 'types/actions';
|
||||
|
||||
import MarkdownImageExpand from 'components/markdown_image_expand';
|
||||
import ExternalImage from 'components/external_image';
|
||||
import SizeAwareImage from 'components/size_aware_image';
|
||||
import FilePreviewModal from 'components/file_preview_modal';
|
||||
|
||||
import brokenImageIcon from 'images/icons/brokenimage.png';
|
||||
import ExternalLink from 'components/external_link';
|
||||
|
||||
export default class MarkdownImage extends React.PureComponent {
|
||||
static defaultProps = {
|
||||
imageMetadata: {},
|
||||
import brokenImageIcon from 'images/icons/brokenimage.png';
|
||||
import Constants, {ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
export type Props = {
|
||||
alt: string;
|
||||
imageMetadata?: PostImage;
|
||||
src: string;
|
||||
|
||||
// height and width come from the Markdown renderer as either "auto" or a string containing a number.
|
||||
height: string;
|
||||
width: string;
|
||||
title: string;
|
||||
className: string;
|
||||
postId: Post['id'];
|
||||
imageIsLink: boolean;
|
||||
onImageLoaded?: ({height, width}: {height: number; width: number}) => void;
|
||||
onImageHeightChanged?: (isExpanded: boolean) => void;
|
||||
postType?: string;
|
||||
actions: {
|
||||
openModal: <P>(modalData: ModalData<P>) => void;
|
||||
};
|
||||
hideUtilities?: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
loadFailed: boolean;
|
||||
loaded: boolean;
|
||||
};
|
||||
|
||||
export default class MarkdownImage extends PureComponent<Props, State> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
imageMetadata: {} as PostImage,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
alt: PropTypes.string,
|
||||
imageMetadata: PropTypes.object,
|
||||
src: PropTypes.string.isRequired,
|
||||
|
||||
// height and width come from the Markdown renderer as either "auto" or a string containing a number.
|
||||
height: PropTypes.string,
|
||||
width: PropTypes.string,
|
||||
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string.isRequired,
|
||||
postId: PropTypes.string.isRequired,
|
||||
imageIsLink: PropTypes.bool.isRequired,
|
||||
onImageLoaded: PropTypes.func,
|
||||
onImageHeightChanged: PropTypes.func,
|
||||
postType: PropTypes.string,
|
||||
|
||||
actions: PropTypes.shape({
|
||||
openModal: PropTypes.func,
|
||||
}).isRequired,
|
||||
hideUtilities: PropTypes.bool,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -58,6 +63,10 @@ export default class MarkdownImage extends React.PureComponent {
|
||||
width,
|
||||
} = this.props;
|
||||
|
||||
if (!imageMetadata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!height) {
|
||||
return imageMetadata.height;
|
||||
}
|
||||
@@ -71,12 +80,12 @@ export default class MarkdownImage extends React.PureComponent {
|
||||
return parseInt(height, 10);
|
||||
};
|
||||
|
||||
getFileExtensionFromUrl = (url) => {
|
||||
getFileExtensionFromUrl = (url: string) => {
|
||||
const index = url.lastIndexOf('.');
|
||||
return index > 0 ? url.substring(index + 1) : null;
|
||||
};
|
||||
|
||||
showModal = (e, link) => {
|
||||
showModal = (e: MouseEvent<HTMLImageElement>, link: string) => {
|
||||
const extension = this.getFileExtensionFromUrl(link);
|
||||
|
||||
if (!this.props.imageIsLink && extension) {
|
||||
@@ -86,11 +95,12 @@ export default class MarkdownImage extends React.PureComponent {
|
||||
modalId: ModalIdentifiers.FILE_PREVIEW_MODAL,
|
||||
dialogType: FilePreviewModal,
|
||||
dialogProps: {
|
||||
startIndex: 0,
|
||||
postId: this.props.postId,
|
||||
fileInfos: [{
|
||||
has_preview_image: false,
|
||||
link,
|
||||
extension: this.props.imageMetadata.format || extension,
|
||||
extension: this.props?.imageMetadata?.format ?? extension,
|
||||
name: this.props.alt,
|
||||
}],
|
||||
},
|
||||
@@ -107,17 +117,17 @@ export default class MarkdownImage extends React.PureComponent {
|
||||
this.props.postType === Constants.PostTypes.HEADER_CHANGE;
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
this.onUpdated(prevProps.src);
|
||||
}
|
||||
|
||||
onUpdated = (prevSrc) => {
|
||||
onUpdated = (prevSrc: string) => {
|
||||
if (this.props.src && this.props.src !== prevSrc) {
|
||||
this.setState({loadFailed: false});
|
||||
}
|
||||
};
|
||||
|
||||
handleImageLoaded = ({height, width}) => {
|
||||
handleImageLoaded = ({height, width}: {height: number; width: number}) => {
|
||||
this.setState({
|
||||
loaded: true,
|
||||
}, () => { // Call onImageLoaded prop only after state has already been set
|
||||
Ссылка в новой задаче
Block a user