[MM-54819] Convert ./components/external_image/external_image.tsx from Class Component to Function Component (#24941)
Этот коммит содержится в:
@@ -23,7 +23,7 @@ describe('ExternalImage', () => {
|
||||
};
|
||||
|
||||
test('should render an image', () => {
|
||||
const wrapper = shallow<ExternalImage>(<ExternalImage {...baseProps}/>);
|
||||
const wrapper = shallow(<ExternalImage {...baseProps}/>);
|
||||
|
||||
expect(baseProps.children).toHaveBeenCalledWith(baseProps.src);
|
||||
expect(wrapper.find('img').exists()).toBe(true);
|
||||
@@ -35,7 +35,7 @@ describe('ExternalImage', () => {
|
||||
imageMetadata: undefined,
|
||||
};
|
||||
|
||||
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>);
|
||||
const wrapper = shallow(<ExternalImage {...props}/>);
|
||||
|
||||
expect(baseProps.children).toHaveBeenCalledWith(baseProps.src);
|
||||
expect(wrapper.find('img').exists()).toBe(true);
|
||||
@@ -53,7 +53,7 @@ describe('ExternalImage', () => {
|
||||
src: 'https://example.com/logo.svg',
|
||||
};
|
||||
|
||||
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>);
|
||||
const wrapper = shallow(<ExternalImage {...props}/>);
|
||||
|
||||
expect(props.children).toHaveBeenCalledWith(props.src);
|
||||
expect(wrapper.find('img').exists()).toBe(true);
|
||||
@@ -72,7 +72,7 @@ describe('ExternalImage', () => {
|
||||
src: 'https://example.com/logo.svg',
|
||||
};
|
||||
|
||||
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>);
|
||||
const wrapper = shallow(<ExternalImage {...props}/>);
|
||||
|
||||
expect(props.children).toHaveBeenCalledWith('');
|
||||
expect(wrapper.find('img').exists()).toBe(true);
|
||||
@@ -84,85 +84,9 @@ describe('ExternalImage', () => {
|
||||
hasImageProxy: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>);
|
||||
const wrapper = shallow(<ExternalImage {...props}/>);
|
||||
|
||||
expect(props.children).toHaveBeenCalledWith(Client4.getBaseRoute() + '/image?url=' + encodeURIComponent(props.src));
|
||||
expect(wrapper.find('img').exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('isSVGImage', () => {
|
||||
for (const testCase of [
|
||||
{
|
||||
name: 'no metadata, no extension',
|
||||
src: 'https://example.com/image.png',
|
||||
imageMetadata: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, svg extension',
|
||||
src: 'https://example.com/image.svg',
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, svg extension with query parameter',
|
||||
src: 'https://example.com/image.svg?a=1',
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, svg extension with hash',
|
||||
src: 'https://example.com/image.svg#abc',
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, proxied image',
|
||||
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.png'),
|
||||
imageMetadata: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, proxied svg image',
|
||||
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.svg'),
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'with metadata, not an SVG',
|
||||
src: 'https://example.com/image.png',
|
||||
imageMetadata: {
|
||||
format: 'png',
|
||||
frameCount: 40,
|
||||
width: 100,
|
||||
height: 200,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'with metadata, SVG',
|
||||
src: 'https://example.com/image.svg',
|
||||
imageMetadata: {
|
||||
format: 'svg',
|
||||
frameCount: 30,
|
||||
width: 10,
|
||||
height: 20,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
]) {
|
||||
test(testCase.name, () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
src: testCase.src,
|
||||
imageMetadata: testCase.imageMetadata,
|
||||
};
|
||||
|
||||
const wrapper = shallow<ExternalImage>(<ExternalImage {...props}/>);
|
||||
|
||||
expect(wrapper.instance().isSVGImage()).toBe(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {memo} from 'react';
|
||||
|
||||
import type {PostImage} from '@mattermost/types/posts';
|
||||
|
||||
import {getImageSrc} from 'utils/post_utils';
|
||||
|
||||
interface Props {
|
||||
import {isSVGImage} from './is_svg_image';
|
||||
|
||||
type Props = {
|
||||
children: (src: string) => React.ReactNode;
|
||||
enableSVGs: boolean;
|
||||
hasImageProxy: boolean;
|
||||
@@ -15,29 +17,13 @@ interface Props {
|
||||
src: string;
|
||||
}
|
||||
|
||||
export default class ExternalImage extends React.PureComponent<Props> {
|
||||
isSVGImage = () => {
|
||||
if (!this.props.imageMetadata) {
|
||||
// Just check if the string contains an svg extension instead of if it ends with one because it avoids
|
||||
// having to deal with query strings and proxied image URLs
|
||||
return this.props.src.indexOf('.svg') !== -1;
|
||||
}
|
||||
|
||||
return this.props.imageMetadata.format === 'svg';
|
||||
};
|
||||
|
||||
shouldRenderImage = () => {
|
||||
// Return true unless the image is an SVG and we have SVG rendering disabled
|
||||
return this.props.enableSVGs || !this.isSVGImage();
|
||||
};
|
||||
|
||||
render() {
|
||||
let src = getImageSrc(this.props.src, this.props.hasImageProxy);
|
||||
|
||||
if (!this.shouldRenderImage()) {
|
||||
src = '';
|
||||
}
|
||||
|
||||
return this.props.children(src);
|
||||
const ExternalImage = (props: Props) => {
|
||||
const shouldRenderImage = props.enableSVGs || !isSVGImage(props.imageMetadata, props.src);
|
||||
let src = getImageSrc(props.src, props.hasImageProxy);
|
||||
if (!shouldRenderImage) {
|
||||
src = '';
|
||||
}
|
||||
}
|
||||
return (<>{props.children(src)}</>);
|
||||
};
|
||||
|
||||
export default memo(ExternalImage);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {isSVGImage} from './is_svg_image';
|
||||
|
||||
describe('ExternalIImage isSVGImage', () => {
|
||||
for (const testCase of [
|
||||
{
|
||||
name: 'no metadata, no extension',
|
||||
src: 'https://example.com/image.png',
|
||||
imageMetadata: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, svg extension',
|
||||
src: 'https://example.com/image.svg',
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, svg extension with query parameter',
|
||||
src: 'https://example.com/image.svg?a=1',
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, svg extension with hash',
|
||||
src: 'https://example.com/image.svg#abc',
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, proxied image',
|
||||
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.png'),
|
||||
imageMetadata: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'no metadata, proxied svg image',
|
||||
src: 'https://mattermost.example.com/api/v4/image?url=' + encodeURIComponent('https://example.com/image.svg'),
|
||||
imageMetadata: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'with metadata, not an SVG',
|
||||
src: 'https://example.com/image.png',
|
||||
imageMetadata: {
|
||||
format: 'png',
|
||||
frameCount: 40,
|
||||
width: 100,
|
||||
height: 200,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: 'with metadata, SVG',
|
||||
src: 'https://example.com/image.svg',
|
||||
imageMetadata: {
|
||||
format: 'svg',
|
||||
frameCount: 30,
|
||||
width: 10,
|
||||
height: 20,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
]) {
|
||||
test(testCase.name, () => {
|
||||
const {imageMetadata, src} = testCase;
|
||||
|
||||
expect(isSVGImage(imageMetadata, src)).toBe(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
13
webapp/channels/src/components/external_image/is_svg_image.ts
Обычный файл
13
webapp/channels/src/components/external_image/is_svg_image.ts
Обычный файл
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {PostImage} from '@mattermost/types/posts';
|
||||
|
||||
export const isSVGImage = (imageMetadata: PostImage | undefined, src: string) => {
|
||||
if (!imageMetadata) {
|
||||
// Just check if the string contains an svg extension instead of if it ends with one because it avoids
|
||||
// having to deal with query strings and proxied image URLs
|
||||
return src.indexOf('.svg') !== -1;
|
||||
}
|
||||
return imageMetadata.format === 'svg';
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/MarkdownImage should match snapshot 1`] = `
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"format": "png",
|
||||
@@ -13,7 +13,7 @@ exports[`components/MarkdownImage should match snapshot 1`] = `
|
||||
src="/images/logo.png"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
`;
|
||||
|
||||
exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimensions 1`] = `
|
||||
@@ -40,7 +40,7 @@ exports[`components/MarkdownImage should match snapshot for SizeAwareImage dimen
|
||||
`;
|
||||
|
||||
exports[`components/MarkdownImage should match snapshot for broken link 1`] = `
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"format": "png",
|
||||
@@ -52,7 +52,7 @@ exports[`components/MarkdownImage should match snapshot for broken link 1`] = `
|
||||
src="brokenLink"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
`;
|
||||
|
||||
exports[`components/MarkdownImage should provide image src as an alt text for MarkdownImageExpand if image has no own alt text 1`] = `
|
||||
|
||||
@@ -29,12 +29,12 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
|
||||
key="attachment__author-name"
|
||||
location="message_attachment"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
key="attachment__author-icon"
|
||||
src="author_icon"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
<span
|
||||
className="attachment__author-name"
|
||||
key="attachment__author-name"
|
||||
@@ -77,7 +77,7 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
|
||||
<div
|
||||
className="attachment__image-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"height": 200,
|
||||
@@ -87,16 +87,16 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
|
||||
src="image_url"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
</div>
|
||||
<div
|
||||
className="attachment__footer-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
src="footer_icon"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
<span>
|
||||
footer
|
||||
</span>
|
||||
@@ -121,7 +121,7 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
|
||||
<div
|
||||
className="attachment__thumb-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"height": 200,
|
||||
@@ -131,7 +131,7 @@ exports[`components/post_view/MessageAttachment should call actions.doPostAction
|
||||
src="thumb_url"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
@@ -175,12 +175,12 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
|
||||
key="attachment__author-name"
|
||||
location="message_attachment"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
key="attachment__author-icon"
|
||||
src="author_icon"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
<span
|
||||
className="attachment__author-name"
|
||||
key="attachment__author-name"
|
||||
@@ -223,7 +223,7 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
|
||||
<div
|
||||
className="attachment__image-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"height": 200,
|
||||
@@ -233,16 +233,16 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
|
||||
src="image_url"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
</div>
|
||||
<div
|
||||
className="attachment__footer-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
src="footer_icon"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
<span>
|
||||
footer
|
||||
</span>
|
||||
@@ -251,7 +251,7 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
|
||||
<div
|
||||
className="attachment__thumb-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"height": 200,
|
||||
@@ -261,7 +261,7 @@ exports[`components/post_view/MessageAttachment should match snapshot 1`] = `
|
||||
src="thumb_url"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
@@ -317,12 +317,12 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
|
||||
key="attachment__author-name"
|
||||
location="message_attachment"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
key="attachment__author-icon"
|
||||
src="author_icon"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
<span
|
||||
className="attachment__author-name"
|
||||
key="attachment__author-name"
|
||||
@@ -365,7 +365,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
|
||||
<div
|
||||
className="attachment__image-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"height": 200,
|
||||
@@ -375,13 +375,13 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
|
||||
src="image_url"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="attachment__thumb-container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"height": 200,
|
||||
@@ -391,7 +391,7 @@ exports[`components/post_view/MessageAttachment should match snapshot when no fo
|
||||
src="thumb_url"
|
||||
>
|
||||
<Component />
|
||||
</Connect(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
|
||||
@@ -182,7 +182,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
|
||||
<AutoHeightSwitcher
|
||||
showSlot={1}
|
||||
slot1={
|
||||
<Memo(Connect(ExternalImage))
|
||||
<Memo(Connect(Component))
|
||||
imageMetadata={
|
||||
Object {
|
||||
"format": "png",
|
||||
@@ -197,7 +197,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
|
||||
src="http://localhost:8065/api/v4/image?url=http%3A%2F%2Fmattermo…t.com%2Fwp-content%2Fuploads%2F2021%2F09%2FHomepage%402x.png"
|
||||
>
|
||||
[Function]
|
||||
</Memo(Connect(ExternalImage))>
|
||||
</Memo(Connect(Component))>
|
||||
}
|
||||
slot2={
|
||||
<button
|
||||
@@ -240,7 +240,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
imageMetadata={
|
||||
Object {
|
||||
"format": "png",
|
||||
@@ -254,7 +254,7 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
|
||||
}
|
||||
src="http://localhost:8065/api/v4/image?url=http%3A%2F%2Fmattermo…t.com%2Fwp-content%2Fuploads%2F2021%2F09%2FHomepage%402x.png"
|
||||
>
|
||||
<ExternalImage
|
||||
<Memo(ExternalImage)
|
||||
dispatch={[Function]}
|
||||
enableSVGs={true}
|
||||
hasImageProxy={true}
|
||||
@@ -299,8 +299,8 @@ exports[`PostAttachmentOpenGraphImage should match snapshot 1`] = `
|
||||
src="/api/v4/image?url=http%3A%2F%2Flocalhost%3A8065%2Fapi%2Fv4%2Fimage%3Furl%3Dhttp%253A%252F%252Fmattermo%E2%80%A6t.com%252Fwp-content%252Fuploads%252F2021%252F09%252FHomepage%25402x.png"
|
||||
/>
|
||||
</figure>
|
||||
</ExternalImage>
|
||||
</Connect(ExternalImage)>
|
||||
</Memo(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@@ -70,10 +70,10 @@ exports[`YoutubeVideo should match init snapshot 1`] = `
|
||||
<div
|
||||
className="video-thumbnail__container"
|
||||
>
|
||||
<Connect(ExternalImage)
|
||||
<Connect(Component)
|
||||
src="linkForThumbnail"
|
||||
>
|
||||
<ExternalImage
|
||||
<Memo(ExternalImage)
|
||||
dispatch={[Function]}
|
||||
enableSVGs={false}
|
||||
hasImageProxy={false}
|
||||
@@ -84,8 +84,8 @@ exports[`YoutubeVideo should match init snapshot 1`] = `
|
||||
className="video-thumbnail"
|
||||
src="linkForThumbnail"
|
||||
/>
|
||||
</ExternalImage>
|
||||
</Connect(ExternalImage)>
|
||||
</Memo(ExternalImage)>
|
||||
</Connect(Component)>
|
||||
<div
|
||||
className="block"
|
||||
>
|
||||
|
||||
Ссылка в новой задаче
Block a user