feat(media-library): add copy to clipboard, allow avif (#4914)

This commit is contained in:
Erez Rokah 2021-02-03 04:54:49 -08:00 committed by GitHub
parent 51e301c594
commit 4b73e11db0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 162 additions and 21 deletions

1
__mocks__/styleMock.js Normal file
View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -7,6 +7,7 @@ module.exports = {
'netlify-cms-backend-github': '<rootDir>/packages/netlify-cms-backend-github/src/index.ts',
'netlify-cms-lib-widgets': '<rootDir>/packages/netlify-cms-lib-widgets/src/index.ts',
'netlify-cms-widget-object': '<rootDir>/packages/netlify-cms-widget-object/src/index.js',
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js',
},
testURL: 'http://localhost:8080',
snapshotSerializers: ['jest-emotion'],

View File

@ -21,7 +21,17 @@ import MediaLibraryModal, { fileShape } from './MediaLibraryModal';
* Extensions used to determine which files to show when the media library is
* accessed from an image insertion field.
*/
const IMAGE_EXTENSIONS_VIEWABLE = ['jpg', 'jpeg', 'webp', 'gif', 'png', 'bmp', 'tiff', 'svg'];
const IMAGE_EXTENSIONS_VIEWABLE = [
'jpg',
'jpeg',
'webp',
'gif',
'png',
'bmp',
'tiff',
'svg',
'avif',
];
const IMAGE_EXTENSIONS = [...IMAGE_EXTENSIONS_VIEWABLE];
class MediaLibrary extends React.Component {

View File

@ -1,7 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { css } from '@emotion/core';
import styled from '@emotion/styled';
import { FileUploadButton } from 'UI';
import { buttons, colors, colorsRaw, shadows, zIndex } from 'netlify-cms-ui-default';
import copyToClipboard from 'copy-text-to-clipboard';
import { isAbsolutePath } from 'netlify-cms-lib-util';
import { buttons, shadows, zIndex } from 'netlify-cms-ui-default';
const styles = {
button: css`
@ -55,18 +59,77 @@ export const InsertButton = styled.button`
${buttons.green};
`;
export const DownloadButton = styled.button`
const ActionButton = styled.button`
${styles.button};
background-color: ${colors.button};
color: ${colors.buttonText};
${props =>
props.focused === true &&
!props.disabled &&
css`
&:focus,
&:hover {
color: ${colorsRaw.white};
background-color: #555a65;
}
${buttons.gray}
`}
`;
export const DownloadButton = ActionButton;
export class CopyToClipBoardButton extends React.Component {
mounted = false;
timeout;
state = {
copied: false,
};
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
handleCopy = () => {
clearTimeout(this.timeout);
const { path, draft, name } = this.props;
copyToClipboard(isAbsolutePath(path) || !draft ? path : name);
this.setState({ copied: true });
this.timeout = setTimeout(() => this.mounted && this.setState({ copied: false }), 1500);
};
getTitle = () => {
const { t, path, draft } = this.props;
if (this.state.copied) {
return t('mediaLibrary.mediaLibraryCard.copied');
}
if (!path) {
return t('mediaLibrary.mediaLibraryCard.copy');
}
if (isAbsolutePath(path)) {
return t('mediaLibrary.mediaLibraryCard.copyUrl');
}
if (draft) {
return t('mediaLibrary.mediaLibraryCard.copyName');
}
return t('mediaLibrary.mediaLibraryCard.copyPath');
};
render() {
const { disabled } = this.props;
return (
<ActionButton disabled={disabled} onClick={this.handleCopy}>
{this.getTitle()}
</ActionButton>
);
}
}
CopyToClipBoardButton.propTypes = {
disabled: PropTypes.bool.isRequired,
draft: PropTypes.bool,
path: PropTypes.string,
name: PropTypes.string,
t: PropTypes.func.isRequired,
};

View File

@ -128,6 +128,7 @@ const MediaLibraryModal = ({
hasSelection={hasSelection}
isPersisting={isPersisting}
isDeleting={isDeleting}
selectedFile={selectedFile}
/>
{!shouldShowEmptyMessage ? null : (
<EmptyMessage content={emptyMessage} isPrivate={privateUpload} />

View File

@ -3,7 +3,13 @@ import PropTypes from 'prop-types';
import styled from '@emotion/styled';
import MediaLibrarySearch from './MediaLibrarySearch';
import MediaLibraryHeader from './MediaLibraryHeader';
import { UploadButton, DeleteButton, DownloadButton, InsertButton } from './MediaLibraryButtons';
import {
UploadButton,
DeleteButton,
DownloadButton,
CopyToClipBoardButton,
InsertButton,
} from './MediaLibraryButtons';
const LibraryTop = styled.div`
position: relative;
@ -37,12 +43,11 @@ const MediaLibraryTop = ({
hasSelection,
isPersisting,
isDeleting,
selectedFile,
}) => {
const shouldShowButtonLoader = isPersisting || isDeleting;
const uploadEnabled = !shouldShowButtonLoader;
const deleteEnabled = !shouldShowButtonLoader && hasSelection;
const downloadEnabled = hasSelection;
const insertEnabled = hasSelection;
const uploadButtonLabel = isPersisting
? t('mediaLibrary.mediaLibraryModal.uploading')
@ -66,11 +71,14 @@ const MediaLibraryTop = ({
isPrivate={privateUpload}
/>
<ButtonsContainer>
<DownloadButton
onClick={onDownload}
disabled={!downloadEnabled}
focused={downloadEnabled}
>
<CopyToClipBoardButton
disabled={!hasSelection}
path={selectedFile.path}
name={selectedFile.name}
draft={selectedFile.draft}
t={t}
/>
<DownloadButton onClick={onDownload} disabled={!hasSelection}>
{downloadButtonLabel}
</DownloadButton>
<UploadButton
@ -94,7 +102,7 @@ const MediaLibraryTop = ({
{deleteButtonLabel}
</DeleteButton>
{!canInsert ? null : (
<InsertButton onClick={onInsert} disabled={!insertEnabled}>
<InsertButton onClick={onInsert} disabled={!hasSelection}>
{insertButtonLabel}
</InsertButton>
)}
@ -121,6 +129,14 @@ MediaLibraryTop.propTypes = {
hasSelection: PropTypes.bool.isRequired,
isPersisting: PropTypes.bool,
isDeleting: PropTypes.bool,
selectedFile: PropTypes.oneOfType([
PropTypes.shape({
path: PropTypes.string.isRequired,
draft: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
}),
PropTypes.shape({}),
]),
};
export default MediaLibraryTop;

View File

@ -0,0 +1,44 @@
import React from 'react';
import { CopyToClipBoardButton } from '../MediaLibraryButtons';
import { render } from '@testing-library/react';
describe('CopyToClipBoardButton', () => {
const props = {
disabled: false,
t: jest.fn(key => key),
};
it('should use copy text when no path is defined', () => {
const { container } = render(<CopyToClipBoardButton {...props} />);
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copy');
});
it('should use copyUrl text when path is absolute and is draft', () => {
const { container } = render(
<CopyToClipBoardButton {...props} path="https://www.images.com/image.png" draft />,
);
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyUrl');
});
it('should use copyUrl text when path is absolute and is not draft', () => {
const { container } = render(
<CopyToClipBoardButton {...props} path="https://www.images.com/image.png" />,
);
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyUrl');
});
it('should use copyName when path is not absolute and is draft', () => {
const { container } = render(<CopyToClipBoardButton {...props} path="image.png" draft />);
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyName');
});
it('should use copyPath when path is not absolute and is not draft', () => {
const { container } = render(<CopyToClipBoardButton {...props} path="image.png" />);
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyPath');
});
});

View File

@ -193,6 +193,11 @@ const en = {
mediaLibrary: {
mediaLibraryCard: {
draft: 'Draft',
copy: 'Copy',
copyUrl: 'Copy URL',
copyPath: 'Copy Path',
copyName: 'Copy Name',
copied: 'Copied',
},
mediaLibrary: {
onDelete: 'Are you sure you want to delete selected media?',