fix: allow any default list as default value for list widgets (#5030)

This commit is contained in:
Pearce 2021-04-05 08:54:07 -07:00 committed by GitHub
parent 9d049ca604
commit 83c235423e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 193 additions and 147 deletions

View File

@ -146,6 +146,28 @@ describe('entries', () => {
expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) }); expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) });
}); });
it('should allow a complex array as list default for a single field list', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [
{
url: 'https://image.png',
},
],
field: { name: 'url', widget: 'text' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({
images: fromJS([
{
url: 'https://image.png',
},
]),
});
});
it('should allow an empty array as list default for a fields list', () => { it('should allow an empty array as list default for a fields list', () => {
const fields = fromJS([ const fields = fromJS([
{ {
@ -161,78 +183,49 @@ describe('entries', () => {
expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) }); expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) });
}); });
it('should not allow setting a non empty array as a default value for a single field list', () => { it('should allow a complex array as list default for a fields list', () => {
const fields = fromJS([ const fields = fromJS([
{ {
name: 'images', name: 'images',
widget: 'list', widget: 'list',
default: [{ name: 'url' }, { other: 'field' }], default: [
field: { name: 'url', widget: 'text' }, {
title: 'default image',
url: 'https://image.png',
}, },
]); ],
expect(createEmptyDraftData(fields)).toEqual({});
});
it('should not allow setting a non empty array as a default value for a fields list', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [{ name: 'url' }, { other: 'field' }],
fields: [ fields: [
{ name: 'title', widget: 'text' }, { name: 'title', widget: 'text' },
{ name: 'url', widget: 'text' }, { name: 'url', widget: 'text' },
], ],
}, },
]); ]);
expect(createEmptyDraftData(fields)).toEqual({});
});
it('should set default value for list field widget', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
field: { name: 'url', widget: 'text', default: 'https://image.png' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({ images: ['https://image.png'] });
});
it('should override list default with field default', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [],
field: { name: 'url', widget: 'text', default: 'https://image.png' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({ images: ['https://image.png'] });
});
it('should set default values for list fields widget', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
fields: [
{ name: 'title', widget: 'text', default: 'default image' },
{ name: 'url', widget: 'text', default: 'https://image.png' },
],
},
]);
expect(createEmptyDraftData(fields)).toEqual({ expect(createEmptyDraftData(fields)).toEqual({
images: [{ title: 'default image', url: 'https://image.png' }], images: fromJS([
{
title: 'default image',
url: 'https://image.png',
},
]),
}); });
}); });
it('should override list default with fields default', () => { it('should use field default when no list default is provided', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
field: { name: 'url', widget: 'text', default: 'https://image.png' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({ images: [{ url: 'https://image.png' }] });
});
it('should use fields default when no list default is provided', () => {
const fields = fromJS([ const fields = fromJS([
{ {
name: 'images', name: 'images',
widget: 'list', widget: 'list',
default: [],
fields: [ fields: [
{ name: 'title', widget: 'text', default: 'default image' }, { name: 'title', widget: 'text', default: 'default image' },
{ name: 'url', widget: 'text', default: 'https://image.png' }, { name: 'url', widget: 'text', default: 'https://image.png' },
@ -298,6 +291,26 @@ describe('entries', () => {
]); ]);
expect(createEmptyDraftData(fields)).toEqual({}); expect(createEmptyDraftData(fields)).toEqual({});
}); });
it('should populate nested fields', () => {
const fields = fromJS([
{
name: 'names',
widget: 'list',
field: {
name: 'object',
widget: 'object',
fields: [
{ name: 'first', widget: 'string', default: 'first' },
{ name: 'second', widget: 'string', default: 'second' },
],
},
},
]);
expect(createEmptyDraftData(fields)).toEqual({
names: [{ object: { first: 'first', second: 'second' } }],
});
});
}); });
describe('persistLocalBackup', () => { describe('persistLocalBackup', () => {

View File

@ -771,7 +771,6 @@ interface DraftEntryData {
export function createEmptyDraftData( export function createEmptyDraftData(
fields: EntryFields, fields: EntryFields,
withNameKey = true,
skipField: (field: EntryField) => boolean = () => false, skipField: (field: EntryField) => boolean = () => false,
) { ) {
return fields.reduce( return fields.reduce(
@ -795,36 +794,27 @@ export function createEmptyDraftData(
return [[{}], {}].some(e => isEqual(val, e)); return [[{}], {}].some(e => isEqual(val, e));
} }
if (List.isList(subfields)) { const hasSubfields = List.isList(subfields) || Map.isMap(subfields);
const subDefaultValue = list if (hasSubfields) {
? [createEmptyDraftData(subfields as EntryFields, withNameKey, skipField)] if (list && List.isList(defaultValue)) {
: createEmptyDraftData(subfields as EntryFields, withNameKey, skipField);
if (!isEmptyDefaultValue(subDefaultValue)) {
acc[name] = subDefaultValue;
} else if (list && List.isList(defaultValue) && (defaultValue as List<unknown>).isEmpty()) {
// allow setting an empty list as a default
acc[name] = defaultValue; acc[name] = defaultValue;
} } else {
return acc; const asList = List.isList(subfields)
} ? (subfields as EntryFields)
: List([subfields as EntryField]);
if (Map.isMap(subfields)) {
const subDefaultValue = list const subDefaultValue = list
? [createEmptyDraftData(List([subfields as EntryField]), false, skipField)] ? [createEmptyDraftData(asList, skipField)]
: createEmptyDraftData(List([subfields as EntryField]), withNameKey, skipField); : createEmptyDraftData(asList, skipField);
if (!isEmptyDefaultValue(subDefaultValue)) { if (!isEmptyDefaultValue(subDefaultValue)) {
acc[name] = subDefaultValue; acc[name] = subDefaultValue;
} else if (list && List.isList(defaultValue) && (defaultValue as List<unknown>).isEmpty()) { }
// allow setting an empty list as a default
acc[name] = defaultValue;
} }
return acc; return acc;
} }
if (defaultValue !== null) { if (defaultValue !== null) {
if (!withNameKey) {
return defaultValue;
}
acc[name] = defaultValue; acc[name] = defaultValue;
} }
@ -843,7 +833,7 @@ function createEmptyDraftI18nData(collection: Collection, dataFields: EntryField
return field.get(I18N) !== I18N_FIELD.DUPLICATE && field.get(I18N) !== I18N_FIELD.TRANSLATE; return field.get(I18N) !== I18N_FIELD.DUPLICATE && field.get(I18N) !== I18N_FIELD.TRANSLATE;
} }
const i18nData = createEmptyDraftData(dataFields, true, skipField); const i18nData = createEmptyDraftData(dataFields, skipField);
return duplicateDefaultI18nFields(collection, i18nData); return duplicateDefaultI18nFields(collection, i18nData);
} }

View File

@ -76,7 +76,7 @@ export class PreviewPane extends React.Component {
// We retrieve the field by name so that this function can also be used in // We retrieve the field by name so that this function can also be used in
// custom preview templates, where the field object can't be passed in. // custom preview templates, where the field object can't be passed in.
let field = fields && fields.find(f => f.get('name') === name); let field = fields && fields.find(f => f.get('name') === name);
let value = values && values.get(field.get('name')); let value = Map.isMap(values) && values.get(field.get('name'));
if (field.get('meta')) { if (field.get('meta')) {
value = this.props.entry.getIn(['meta', field.get('name')]); value = this.props.entry.getIn(['meta', field.get('name')]);
} }

View File

@ -80,6 +80,17 @@ function handleSummary(summary, entry, label, item) {
return stringTemplate.compileStringTemplate(summary, null, '', data); return stringTemplate.compileStringTemplate(summary, null, '', data);
} }
function validateItem(field, item) {
if (!Map.isMap(item)) {
console.warn(
`'${field.get('name')}' field item value value should be a map but is a '${typeof item}'`,
);
return false;
}
return true;
}
export default class ListControl extends React.Component { export default class ListControl extends React.Component {
validations = []; validations = [];
@ -386,6 +397,9 @@ export default class ListControl extends React.Component {
const valueType = this.getValueType(); const valueType = this.getValueType();
switch (valueType) { switch (valueType) {
case valueTypes.MIXED: { case valueTypes.MIXED: {
if (!validateItem(field, item)) {
return;
}
const itemType = getTypedFieldForValue(field, item); const itemType = getTypedFieldForValue(field, item);
const label = itemType.get('label', itemType.get('name')); const label = itemType.get('label', itemType.get('name'));
// each type can have its own summary, but default to the list summary if exists // each type can have its own summary, but default to the list summary if exists
@ -402,6 +416,9 @@ export default class ListControl extends React.Component {
return labelReturn; return labelReturn;
} }
case valueTypes.MULTIPLE: { case valueTypes.MULTIPLE: {
if (!validateItem(field, item)) {
return;
}
const multiFields = field.get('fields'); const multiFields = field.get('fields');
const labelField = multiFields && multiFields.first(); const labelField = multiFields && multiFields.first();
const value = item.get(labelField.get('name')); const value = item.get(labelField.get('name'));

View File

@ -9,7 +9,10 @@ The list widget allows you to create a repeatable item in the UI which saves as
* **Data type:** list of widget values * **Data type:** list of widget values
* **Options:** * **Options:**
* `default`: you may specify a list of strings to populate the basic text field, but declare defaults on the child widgets if you are specifying `fields`; * `default`: you may specify a list of strings to populate the basic text
field, or an array of list items for lists using the `fields` option. If no
default is declared when using `field` or `fields`, will default to a single
list item using the defaults on the child widgets
* `allow_add`: `false` hides the button to add additional items * `allow_add`: `false` hides the button to add additional items
* `collapsed`: when `true`, the entries collapse by default * `collapsed`: when `true`, the entries collapse by default
* `summary`: specify the label displayed on collapsed entries * `summary`: specify the label displayed on collapsed entries
@ -20,6 +23,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
* `max`: maximum number of items in the list * `max`: maximum number of items in the list
* `min`: minimum number of items in the list * `min`: minimum number of items in the list
* `add_to_top`: when `true`, new entries will be added to the top of the list * `add_to_top`: when `true`, new entries will be added to the top of the list
* **Example** (`field`/`fields` not specified): * **Example** (`field`/`fields` not specified):
```yaml ```yaml
@ -28,6 +32,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
widget: "list" widget: "list"
default: ["news"] default: ["news"]
``` ```
* **Example** (`allow_add` marked `false`): * **Example** (`allow_add` marked `false`):
```yaml ```yaml
@ -37,6 +42,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
allow_add: false allow_add: false
default: ["news"] default: ["news"]
``` ```
* **Example** (with `field`): * **Example** (with `field`):
```yaml ```yaml
@ -46,6 +52,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
summary: '{{fields.image}}' summary: '{{fields.image}}'
field: {label: Image, name: image, widget: image} field: {label: Image, name: image, widget: image}
``` ```
* **Example** (with `fields`): * **Example** (with `fields`):
```yaml ```yaml
@ -62,6 +69,21 @@ The list widget allows you to create a repeatable item in the UI which saves as
- {label: Name, name: name, widget: string, default: "Emmet"} - {label: Name, name: name, widget: string, default: "Emmet"}
- {label: Avatar, name: avatar, widget: image, default: "/img/emmet.jpg"} - {label: Avatar, name: avatar, widget: image, default: "/img/emmet.jpg"}
``` ```
* **Example** (with `default`):
```yaml
- label: "Gallery"
name: "galleryImages"
widget: "list"
fields:
- { label: "Source", name: "src", widget: "string" }
- { label: "Alt Text", name: "alt", widget: "string" }
default:
- { src: "/img/tenis.jpg", alt: "Tenis" }
- { src: "/img/footbar.jpg", alt: "Football" }
```
* **Example** (`collapsed` marked `false`): * **Example** (`collapsed` marked `false`):
```yaml ```yaml
@ -73,6 +95,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"} - {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
- {label: Author, name: author, widget: string } - {label: Author, name: author, widget: string }
``` ```
* **Example** (`minimize_collapsed` marked `true`): * **Example** (`minimize_collapsed` marked `true`):
```yaml ```yaml
@ -86,6 +109,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
``` ```
* **Example** (with `max` & `min`): * **Example** (with `max` & `min`):
```yaml ```yaml
- label: "Tags" - label: "Tags"
name: "tags" name: "tags"
@ -94,7 +118,9 @@ The list widget allows you to create a repeatable item in the UI which saves as
min: 1 min: 1
default: ["news"] default: ["news"]
``` ```
* **Example** (`add_to_top` marked `true`): * **Example** (`add_to_top` marked `true`):
```yaml ```yaml
- label: "Tags" - label: "Tags"
name: "tags" name: "tags"