feat(media-library): add copy to clipboard, allow avif (#4914)
This commit is contained in:
parent
51e301c594
commit
4b73e11db0
1
__mocks__/styleMock.js
Normal file
1
__mocks__/styleMock.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = {};
|
@ -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'],
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -128,6 +128,7 @@ const MediaLibraryModal = ({
|
||||
hasSelection={hasSelection}
|
||||
isPersisting={isPersisting}
|
||||
isDeleting={isDeleting}
|
||||
selectedFile={selectedFile}
|
||||
/>
|
||||
{!shouldShowEmptyMessage ? null : (
|
||||
<EmptyMessage content={emptyMessage} isPrivate={privateUpload} />
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
@ -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?',
|
||||
|
Loading…
x
Reference in New Issue
Block a user