feat(media): add external media library support, Uploadcare integration (#1602)

This commit is contained in:
Shawn Erquhart
2018-08-30 16:24:28 -04:00
committed by GitHub
parent ae28f6301e
commit 0596904e0b
34 changed files with 715 additions and 135 deletions

View File

@ -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",

View File

@ -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 = {

View File

@ -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}