feat(media): add external media library support, Uploadcare integration (#1602)
This commit is contained in:
@ -31,6 +31,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"emotion": "^9.2.6",
|
||||
"immutable": "^3.7.6",
|
||||
"netlify-cms-ui-default": "^2.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^16.4.1",
|
||||
|
@ -1,11 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'react-emotion';
|
||||
import { List } from 'immutable';
|
||||
import { WidgetPreviewContainer } from 'netlify-cms-ui-default';
|
||||
|
||||
const FilePreview = ({ value, getAsset }) => (
|
||||
<WidgetPreviewContainer>
|
||||
{value ? <a href={getAsset(value)}>{value}</a> : null}
|
||||
</WidgetPreviewContainer>
|
||||
const FileLink = styled(({ value, getAsset }) => (
|
||||
<a href={getAsset(value)} rel="noopener noreferrer" target="_blank">
|
||||
{value}
|
||||
</a>
|
||||
))`
|
||||
display: block;
|
||||
`;
|
||||
|
||||
function FileLinkList({ values, getAsset }) {
|
||||
return (
|
||||
<div>
|
||||
{values.map(value => (
|
||||
<FileLink key={value} value={value} getAsset={getAsset} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FileContent({ value, getAsset }) {
|
||||
if (Array.isArray(value) || List.isList(value)) {
|
||||
return <FileLinkList values={value} getAsset={getAsset} />;
|
||||
}
|
||||
return <FileLink value={value} getAsset={getAsset} />;
|
||||
}
|
||||
|
||||
const FilePreview = props => (
|
||||
<WidgetPreviewContainer>{props.value ? <FileContent {...props} /> : null}</WidgetPreviewContainer>
|
||||
);
|
||||
|
||||
FilePreview.propTypes = {
|
||||
|
@ -2,19 +2,18 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import styled from 'react-emotion';
|
||||
import { List } from 'immutable';
|
||||
import uuid from 'uuid/v4';
|
||||
import { lengths, components, buttons } from 'netlify-cms-ui-default';
|
||||
|
||||
const MAX_DISPLAY_LENGTH = 50;
|
||||
|
||||
const FileContent = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
flex-basis: 155px;
|
||||
width: 155px;
|
||||
height: 100px;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const Image = styled.img`
|
||||
@ -24,16 +23,31 @@ const Image = styled.img`
|
||||
border-radius: ${lengths.borderRadius};
|
||||
`;
|
||||
|
||||
const MultiImageWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const FileInfo = styled.div`
|
||||
button:not(:first-child) {
|
||||
margin-top: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
const FileName = styled.span`
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
const FileLink = styled.a`
|
||||
margin-bottom: 20px;
|
||||
font-weight: normal;
|
||||
color: inherit;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
const FileLinkList = styled.ul`
|
||||
list-style-type: none;
|
||||
`;
|
||||
|
||||
const FileWidgetButton = styled.button`
|
||||
@ -46,6 +60,10 @@ const FileWidgetButtonRemove = styled.button`
|
||||
${components.badgeDanger};
|
||||
`;
|
||||
|
||||
function isMultiple(value) {
|
||||
return Array.isArray(value) || List.isList(value);
|
||||
}
|
||||
|
||||
export default function withFileControl({ forImage } = {}) {
|
||||
return class FileControl extends React.Component {
|
||||
static propTypes = {
|
||||
@ -56,8 +74,10 @@ export default function withFileControl({ forImage } = {}) {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onRemoveInsertedMedia: PropTypes.func.isRequired,
|
||||
onOpenMediaLibrary: PropTypes.func.isRequired,
|
||||
onClearMediaControl: PropTypes.func.isRequired,
|
||||
onRemoveMediaControl: PropTypes.func.isRequired,
|
||||
classNameWrapper: PropTypes.string.isRequired,
|
||||
value: PropTypes.node,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -99,56 +119,94 @@ export default function withFileControl({ forImage } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.onRemoveMediaControl(this.controlID);
|
||||
}
|
||||
|
||||
handleChange = e => {
|
||||
const { field, onOpenMediaLibrary } = this.props;
|
||||
const { field, onOpenMediaLibrary, value } = this.props;
|
||||
e.preventDefault();
|
||||
return onOpenMediaLibrary({
|
||||
controlID: this.controlID,
|
||||
forImage,
|
||||
privateUpload: field.get('private'),
|
||||
value,
|
||||
config: field.getIn(['options', 'media_library', 'config']),
|
||||
});
|
||||
};
|
||||
|
||||
handleRemove = e => {
|
||||
e.preventDefault();
|
||||
this.props.onClearMediaControl(this.controlID);
|
||||
return this.props.onChange('');
|
||||
};
|
||||
|
||||
renderFileName = () => {
|
||||
const { value } = this.props;
|
||||
renderFileLink = value => {
|
||||
const size = MAX_DISPLAY_LENGTH;
|
||||
if (!value || value.length <= size) {
|
||||
return value;
|
||||
}
|
||||
return `${value.substring(0, size / 2)}\u2026${value.substring(
|
||||
const text = `${value.substring(0, size / 2)}\u2026${value.substring(
|
||||
value.length - size / 2 + 1,
|
||||
value.length,
|
||||
)}`;
|
||||
};
|
||||
|
||||
renderSelection = subject => {
|
||||
const fileName = this.renderFileName();
|
||||
const { getAsset, value } = this.props;
|
||||
return (
|
||||
<FileContent>
|
||||
{forImage ? (
|
||||
<ImageWrapper>
|
||||
<Image src={getAsset(value)} />
|
||||
</ImageWrapper>
|
||||
) : null}
|
||||
<FileInfo>
|
||||
<FileName>{fileName}</FileName>
|
||||
<FileWidgetButton onClick={this.handleChange}>
|
||||
Choose different {subject}
|
||||
</FileWidgetButton>
|
||||
<FileWidgetButtonRemove onClick={this.handleRemove}>
|
||||
Remove {subject}
|
||||
</FileWidgetButtonRemove>
|
||||
</FileInfo>
|
||||
</FileContent>
|
||||
<FileLink href={value} rel="noopener" target="_blank">
|
||||
{text}
|
||||
</FileLink>
|
||||
);
|
||||
};
|
||||
|
||||
renderFileLinks = () => {
|
||||
const { value } = this.props;
|
||||
|
||||
if (isMultiple(value)) {
|
||||
return (
|
||||
<FileLinkList>
|
||||
{value.map(val => (
|
||||
<li key={val}>{this.renderFileLink(val)}</li>
|
||||
))}
|
||||
</FileLinkList>
|
||||
);
|
||||
}
|
||||
return this.renderFileLink(value);
|
||||
};
|
||||
|
||||
renderImages = () => {
|
||||
const { getAsset, value } = this.props;
|
||||
if (isMultiple(value)) {
|
||||
return (
|
||||
<MultiImageWrapper>
|
||||
{value.map(val => (
|
||||
<ImageWrapper key={val}>
|
||||
<Image src={getAsset(val)} />
|
||||
</ImageWrapper>
|
||||
))}
|
||||
</MultiImageWrapper>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ImageWrapper>
|
||||
<Image src={getAsset(value)} />
|
||||
</ImageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
renderSelection = subject => (
|
||||
<div>
|
||||
{forImage ? this.renderImages() : null}
|
||||
<FileInfo>
|
||||
{forImage ? null : this.renderFileLinks()}
|
||||
<FileWidgetButton onClick={this.handleChange}>
|
||||
Choose different {subject}
|
||||
</FileWidgetButton>
|
||||
<FileWidgetButtonRemove onClick={this.handleRemove}>
|
||||
Remove {subject}
|
||||
</FileWidgetButtonRemove>
|
||||
</FileInfo>
|
||||
</div>
|
||||
);
|
||||
|
||||
renderNoSelection = (subject, article) => (
|
||||
<FileWidgetButton onClick={this.handleChange}>
|
||||
Choose {article} {subject}
|
||||
|
Reference in New Issue
Block a user