Co-authored-by: Erez Rokah <erezrokah@users.noreply.github.com>
This commit is contained in:
parent
1e53d35db9
commit
80c577a462
@ -413,6 +413,7 @@ function mediaLibraryOpened(payload: {
|
|||||||
forImage?: boolean;
|
forImage?: boolean;
|
||||||
privateUpload?: boolean;
|
privateUpload?: boolean;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
replaceIndex?: number;
|
||||||
allowMultiple?: boolean;
|
allowMultiple?: boolean;
|
||||||
config?: Map<string, unknown>;
|
config?: Map<string, unknown>;
|
||||||
field?: EntryField;
|
field?: EntryField;
|
||||||
|
@ -45,6 +45,8 @@ const defaultState: {
|
|||||||
files?: MediaFile[];
|
files?: MediaFile[];
|
||||||
config: Map<string, unknown>;
|
config: Map<string, unknown>;
|
||||||
field?: EntryField;
|
field?: EntryField;
|
||||||
|
value?: string | string[];
|
||||||
|
replaceIndex?: number;
|
||||||
} = {
|
} = {
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
showMediaButton: true,
|
showMediaButton: true,
|
||||||
@ -62,7 +64,8 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
case MEDIA_LIBRARY_OPEN: {
|
case MEDIA_LIBRARY_OPEN: {
|
||||||
const { controlID, forImage, privateUpload, config, field } = action.payload;
|
const { controlID, forImage, privateUpload, config, field, value, replaceIndex } =
|
||||||
|
action.payload;
|
||||||
const libConfig = config || Map();
|
const libConfig = config || Map();
|
||||||
const privateUploadChanged = state.get('privateUpload') !== privateUpload;
|
const privateUploadChanged = state.get('privateUpload') !== privateUpload;
|
||||||
if (privateUploadChanged) {
|
if (privateUploadChanged) {
|
||||||
@ -76,6 +79,8 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
|||||||
controlMedia: Map(),
|
controlMedia: Map(),
|
||||||
displayURLs: Map(),
|
displayURLs: Map(),
|
||||||
field,
|
field,
|
||||||
|
value,
|
||||||
|
replaceIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
@ -86,6 +91,8 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
|||||||
map.set('privateUpload', privateUpload);
|
map.set('privateUpload', privateUpload);
|
||||||
map.set('config', libConfig);
|
map.set('config', libConfig);
|
||||||
map.set('field', field);
|
map.set('field', field);
|
||||||
|
map.set('value', value);
|
||||||
|
map.set('replaceIndex', replaceIndex);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +102,25 @@ function mediaLibrary(state = Map(defaultState), action: MediaLibraryAction) {
|
|||||||
case MEDIA_INSERT: {
|
case MEDIA_INSERT: {
|
||||||
const { mediaPath } = action.payload;
|
const { mediaPath } = action.payload;
|
||||||
const controlID = state.get('controlID');
|
const controlID = state.get('controlID');
|
||||||
|
const value = state.get('value');
|
||||||
|
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return state.withMutations(map => {
|
||||||
|
map.setIn(['controlMedia', controlID], mediaPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceIndex = state.get('replaceIndex');
|
||||||
|
const mediaArray = Array.isArray(mediaPath) ? mediaPath : [mediaPath];
|
||||||
|
const valueArray = value as string[];
|
||||||
|
if (typeof replaceIndex == 'number') {
|
||||||
|
valueArray[replaceIndex] = mediaArray[0];
|
||||||
|
} else {
|
||||||
|
valueArray.push(...mediaArray);
|
||||||
|
}
|
||||||
|
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.setIn(['controlMedia', controlID], mediaPath);
|
map.setIn(['controlMedia', controlID], valueArray);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,19 +169,25 @@ const en = {
|
|||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
choose: 'Choose an image',
|
choose: 'Choose an image',
|
||||||
|
chooseMultiple: 'Choose images',
|
||||||
chooseUrl: 'Insert from URL',
|
chooseUrl: 'Insert from URL',
|
||||||
replaceUrl: 'Replace with URL',
|
replaceUrl: 'Replace with URL',
|
||||||
promptUrl: 'Enter the URL of the image',
|
promptUrl: 'Enter the URL of the image',
|
||||||
chooseDifferent: 'Choose different image',
|
chooseDifferent: 'Choose different image',
|
||||||
|
addMore: 'Add more images',
|
||||||
remove: 'Remove image',
|
remove: 'Remove image',
|
||||||
|
removeAll: 'Remove all images',
|
||||||
},
|
},
|
||||||
file: {
|
file: {
|
||||||
choose: 'Choose a file',
|
choose: 'Choose a file',
|
||||||
chooseUrl: 'Insert from URL',
|
chooseUrl: 'Insert from URL',
|
||||||
|
chooseMultiple: 'Choose files',
|
||||||
replaceUrl: 'Replace with URL',
|
replaceUrl: 'Replace with URL',
|
||||||
promptUrl: 'Enter the URL of the file',
|
promptUrl: 'Enter the URL of the file',
|
||||||
chooseDifferent: 'Choose different file',
|
chooseDifferent: 'Choose different file',
|
||||||
|
addMore: 'Add more files',
|
||||||
remove: 'Remove file',
|
remove: 'Remove file',
|
||||||
|
removeAll: 'Remove all files',
|
||||||
},
|
},
|
||||||
unknownControl: {
|
unknownControl: {
|
||||||
noControl: "No control for widget '%{widget}'.",
|
noControl: "No control for widget '%{widget}'.",
|
||||||
|
@ -7,7 +7,15 @@ import { Map, List } from 'immutable';
|
|||||||
import { once } from 'lodash';
|
import { once } from 'lodash';
|
||||||
import uuid from 'uuid/v4';
|
import uuid from 'uuid/v4';
|
||||||
import { oneLine } from 'common-tags';
|
import { oneLine } from 'common-tags';
|
||||||
import { lengths, components, buttons, borders, effects, shadows } from 'netlify-cms-ui-default';
|
import {
|
||||||
|
lengths,
|
||||||
|
components,
|
||||||
|
buttons,
|
||||||
|
borders,
|
||||||
|
effects,
|
||||||
|
shadows,
|
||||||
|
IconButton,
|
||||||
|
} from 'netlify-cms-ui-default';
|
||||||
import { basename } from 'netlify-cms-lib-util';
|
import { basename } from 'netlify-cms-lib-util';
|
||||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||||
import { arrayMoveImmutable as arrayMove } from 'array-move';
|
import { arrayMoveImmutable as arrayMove } from 'array-move';
|
||||||
@ -28,6 +36,15 @@ const ImageWrapper = styled.div`
|
|||||||
cursor: ${props => (props.sortable ? 'pointer' : 'auto')};
|
cursor: ${props => (props.sortable ? 'pointer' : 'auto')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const SortableImageButtonsWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
column-gap: 10px;
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
const StyledImage = styled.img`
|
const StyledImage = styled.img`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -38,35 +55,55 @@ function Image(props) {
|
|||||||
return <StyledImage role="presentation" {...props} />;
|
return <StyledImage role="presentation" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SortableImage = SortableElement(({ itemValue, getAsset, field }) => {
|
function SortableImageButtons({ onRemove, onReplace }) {
|
||||||
return (
|
return (
|
||||||
<ImageWrapper sortable>
|
<SortableImageButtonsWrapper>
|
||||||
<Image src={getAsset(itemValue, field) || ''} />
|
<IconButton size="small" type="media" onClick={onReplace}></IconButton>
|
||||||
</ImageWrapper>
|
<IconButton size="small" type="close" onClick={onRemove}></IconButton>
|
||||||
|
</SortableImageButtonsWrapper>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
const SortableMultiImageWrapper = SortableContainer(({ items, getAsset, field }) => {
|
const SortableImage = SortableElement(({ itemValue, getAsset, field, onRemove, onReplace }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div>
|
||||||
css={css`
|
<ImageWrapper sortable>
|
||||||
display: flex;
|
<Image src={getAsset(itemValue, field) || ''} />
|
||||||
flex-wrap: wrap;
|
</ImageWrapper>
|
||||||
`}
|
<SortableImageButtons
|
||||||
>
|
item={itemValue}
|
||||||
{items.map((itemValue, index) => (
|
onRemove={onRemove}
|
||||||
<SortableImage
|
onReplace={onReplace}
|
||||||
key={`item-${itemValue}`}
|
></SortableImageButtons>
|
||||||
index={index}
|
|
||||||
itemValue={itemValue}
|
|
||||||
getAsset={getAsset}
|
|
||||||
field={field}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const SortableMultiImageWrapper = SortableContainer(
|
||||||
|
({ items, getAsset, field, onRemoveOne, onReplaceOne }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{items.map((itemValue, index) => (
|
||||||
|
<SortableImage
|
||||||
|
key={`item-${itemValue}`}
|
||||||
|
index={index}
|
||||||
|
itemValue={itemValue}
|
||||||
|
getAsset={getAsset}
|
||||||
|
field={field}
|
||||||
|
onRemove={onRemoveOne(index)}
|
||||||
|
onReplace={onReplaceOne(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const FileLink = styled.a`
|
const FileLink = styled.a`
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -102,6 +139,22 @@ function isMultiple(value) {
|
|||||||
return Array.isArray(value) || List.isList(value);
|
return Array.isArray(value) || List.isList(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sizeOfValue(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (List.isList(value)) {
|
||||||
|
return value.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueListToArray(value) {
|
||||||
|
return List.isList(value) ? value.toArray() : value;
|
||||||
|
}
|
||||||
|
|
||||||
const warnDeprecatedOptions = once(field =>
|
const warnDeprecatedOptions = once(field =>
|
||||||
console.warn(oneLine`
|
console.warn(oneLine`
|
||||||
Netlify CMS config: ${field.get('name')} field: property "options" has been deprecated for the
|
Netlify CMS config: ${field.get('name')} field: property "options" has been deprecated for the
|
||||||
@ -178,26 +231,13 @@ export default function withFileControl({ forImage } = {}) {
|
|||||||
handleChange = e => {
|
handleChange = e => {
|
||||||
const { field, onOpenMediaLibrary, value } = this.props;
|
const { field, onOpenMediaLibrary, value } = this.props;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let mediaLibraryFieldOptions;
|
const mediaLibraryFieldOptions = this.getMediaLibraryFieldOptions();
|
||||||
|
|
||||||
/**
|
|
||||||
* `options` hash as a general field property is deprecated, only used
|
|
||||||
* when external media libraries were first introduced. Not to be
|
|
||||||
* confused with `options` for the select widget, which serves a different
|
|
||||||
* purpose.
|
|
||||||
*/
|
|
||||||
if (field.hasIn(['options', 'media_library'])) {
|
|
||||||
warnDeprecatedOptions(field);
|
|
||||||
mediaLibraryFieldOptions = field.getIn(['options', 'media_library'], Map());
|
|
||||||
} else {
|
|
||||||
mediaLibraryFieldOptions = field.get('media_library', Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
return onOpenMediaLibrary({
|
return onOpenMediaLibrary({
|
||||||
controlID: this.controlID,
|
controlID: this.controlID,
|
||||||
forImage,
|
forImage,
|
||||||
privateUpload: field.get('private'),
|
privateUpload: field.get('private'),
|
||||||
value,
|
value: valueListToArray(value),
|
||||||
allowMultiple: !!mediaLibraryFieldOptions.get('allow_multiple', true),
|
allowMultiple: !!mediaLibraryFieldOptions.get('allow_multiple', true),
|
||||||
config: mediaLibraryFieldOptions.get('config'),
|
config: mediaLibraryFieldOptions.get('config'),
|
||||||
field,
|
field,
|
||||||
@ -218,6 +258,47 @@ export default function withFileControl({ forImage } = {}) {
|
|||||||
return this.props.onChange('');
|
return this.props.onChange('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onRemoveOne = index => () => {
|
||||||
|
const { value } = this.props;
|
||||||
|
value.splice(index, 1);
|
||||||
|
return this.props.onChange(sizeOfValue(value) > 0 ? [...value] : null);
|
||||||
|
};
|
||||||
|
|
||||||
|
onReplaceOne = index => () => {
|
||||||
|
const { field, onOpenMediaLibrary, value } = this.props;
|
||||||
|
const mediaLibraryFieldOptions = this.getMediaLibraryFieldOptions();
|
||||||
|
|
||||||
|
return onOpenMediaLibrary({
|
||||||
|
controlID: this.controlID,
|
||||||
|
forImage,
|
||||||
|
privateUpload: field.get('private'),
|
||||||
|
value: valueListToArray(value),
|
||||||
|
replaceIndex: index,
|
||||||
|
allowMultiple: false,
|
||||||
|
config: mediaLibraryFieldOptions.get('config'),
|
||||||
|
field,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getMediaLibraryFieldOptions = () => {
|
||||||
|
const { field } = this.props;
|
||||||
|
|
||||||
|
if (field.hasIn(['options', 'media_library'])) {
|
||||||
|
warnDeprecatedOptions(field);
|
||||||
|
return field.getIn(['options', 'media_library'], Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.get('media_library', Map());
|
||||||
|
};
|
||||||
|
|
||||||
|
allowsMultiple = () => {
|
||||||
|
const mediaLibraryFieldOptions = this.getMediaLibraryFieldOptions();
|
||||||
|
return (
|
||||||
|
mediaLibraryFieldOptions.get('config', false) &&
|
||||||
|
mediaLibraryFieldOptions.get('config').get('multiple', false)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
onSortEnd = ({ oldIndex, newIndex }) => {
|
onSortEnd = ({ oldIndex, newIndex }) => {
|
||||||
const { value } = this.props;
|
const { value } = this.props;
|
||||||
const newValue = arrayMove(value, oldIndex, newIndex);
|
const newValue = arrayMove(value, oldIndex, newIndex);
|
||||||
@ -274,6 +355,9 @@ export default function withFileControl({ forImage } = {}) {
|
|||||||
<SortableMultiImageWrapper
|
<SortableMultiImageWrapper
|
||||||
items={value}
|
items={value}
|
||||||
onSortEnd={this.onSortEnd}
|
onSortEnd={this.onSortEnd}
|
||||||
|
onRemoveOne={this.onRemoveOne}
|
||||||
|
onReplaceOne={this.onReplaceOne}
|
||||||
|
distance={4}
|
||||||
getAsset={getAsset}
|
getAsset={getAsset}
|
||||||
field={field}
|
field={field}
|
||||||
axis="xy"
|
axis="xy"
|
||||||
@ -292,21 +376,26 @@ export default function withFileControl({ forImage } = {}) {
|
|||||||
|
|
||||||
renderSelection = subject => {
|
renderSelection = subject => {
|
||||||
const { t, field } = this.props;
|
const { t, field } = this.props;
|
||||||
|
const allowsMultiple = this.allowsMultiple();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{forImage ? this.renderImages() : null}
|
{forImage ? this.renderImages() : null}
|
||||||
<div>
|
<div>
|
||||||
{forImage ? null : this.renderFileLinks()}
|
{forImage ? null : this.renderFileLinks()}
|
||||||
<FileWidgetButton onClick={this.handleChange}>
|
<FileWidgetButton onClick={this.handleChange}>
|
||||||
{t(`editor.editorWidgets.${subject}.chooseDifferent`)}
|
{t(
|
||||||
|
`editor.editorWidgets.${subject}.${
|
||||||
|
this.allowsMultiple() ? 'addMore' : 'chooseDifferent'
|
||||||
|
}`,
|
||||||
|
)}
|
||||||
</FileWidgetButton>
|
</FileWidgetButton>
|
||||||
{field.get('choose_url', true) ? (
|
{field.get('choose_url', true) && !this.allowsMultiple() ? (
|
||||||
<FileWidgetButton onClick={this.handleUrl(subject)}>
|
<FileWidgetButton onClick={this.handleUrl(subject)}>
|
||||||
{t(`editor.editorWidgets.${subject}.replaceUrl`)}
|
{t(`editor.editorWidgets.${subject}.replaceUrl`)}
|
||||||
</FileWidgetButton>
|
</FileWidgetButton>
|
||||||
) : null}
|
) : null}
|
||||||
<FileWidgetButtonRemove onClick={this.handleRemove}>
|
<FileWidgetButtonRemove onClick={this.handleRemove}>
|
||||||
{t(`editor.editorWidgets.${subject}.remove`)}
|
{t(`editor.editorWidgets.${subject}.remove${allowsMultiple ? 'All' : ''}`)}
|
||||||
</FileWidgetButtonRemove>
|
</FileWidgetButtonRemove>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -318,7 +407,7 @@ export default function withFileControl({ forImage } = {}) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FileWidgetButton onClick={this.handleChange}>
|
<FileWidgetButton onClick={this.handleChange}>
|
||||||
{t(`editor.editorWidgets.${subject}.choose`)}
|
{t(`editor.editorWidgets.${subject}.choose${this.allowsMultiple() ? 'Multiple' : ''}`)}
|
||||||
</FileWidgetButton>
|
</FileWidgetButton>
|
||||||
{field.get('choose_url', true) ? (
|
{field.get('choose_url', true) ? (
|
||||||
<FileWidgetButton onClick={this.handleUrl(subject)}>
|
<FileWidgetButton onClick={this.handleUrl(subject)}>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user