fix: deprecate inconsistent config param case (#4172)
This commit is contained in:
parent
f1376aa5c3
commit
88a5a8098e
@ -34,8 +34,8 @@ collections: # A list of collections the CMS should be able to edit
|
||||
label: 'Publish Date',
|
||||
name: 'date',
|
||||
widget: 'datetime',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: 'HH:mm',
|
||||
date_format: 'YYYY-MM-DD',
|
||||
time_format: 'HH:mm',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
}
|
||||
- label: 'Cover Image'
|
||||
@ -108,9 +108,9 @@ collections: # A list of collections the CMS should be able to edit
|
||||
name: 'post'
|
||||
widget: 'relationKitchenSinkPost'
|
||||
collection: 'posts'
|
||||
displayFields: ['title', 'date']
|
||||
searchFields: ['title', 'body']
|
||||
valueField: 'title'
|
||||
display_fields: ['title', 'date']
|
||||
search_fields: ['title', 'body']
|
||||
value_field: 'title'
|
||||
- { label: 'Title', name: 'title', widget: 'string' }
|
||||
- { label: 'Boolean', name: 'boolean', widget: 'boolean', default: true }
|
||||
- { label: 'Map', name: 'map', widget: 'map' }
|
||||
@ -139,8 +139,8 @@ collections: # A list of collections the CMS should be able to edit
|
||||
name: 'post'
|
||||
widget: 'relationKitchenSinkPost'
|
||||
collection: 'posts'
|
||||
searchFields: ['title', 'body']
|
||||
valueField: 'title'
|
||||
search_fields: ['title', 'body']
|
||||
value_field: 'title'
|
||||
- { label: 'String', name: 'string', widget: 'string' }
|
||||
- { label: 'Boolean', name: 'boolean', widget: 'boolean', default: false }
|
||||
- { label: 'Text', name: 'text', widget: 'text' }
|
||||
@ -187,8 +187,8 @@ collections: # A list of collections the CMS should be able to edit
|
||||
name: 'post'
|
||||
widget: 'relationKitchenSinkPost'
|
||||
collection: 'posts'
|
||||
searchFields: ['title', 'body']
|
||||
valueField: 'title'
|
||||
search_fields: ['title', 'body']
|
||||
value_field: 'title'
|
||||
- { label: 'String', name: 'string', widget: 'string' }
|
||||
- { label: 'Boolean', name: 'boolean', widget: 'boolean' }
|
||||
- { label: 'Text', name: 'text', widget: 'text' }
|
||||
|
@ -172,9 +172,9 @@
|
||||
render: function() {
|
||||
// When a post is selected from the relation field, all of it's data
|
||||
// will be available in the field's metadata nested under the collection
|
||||
// name, and then further nested under the value specified in `valueField`.
|
||||
// name, and then further nested under the value specified in `value_field`.
|
||||
// In this case, the post would be nested under "posts" and then under
|
||||
// the title of the selected post, since our `valueField` in the config
|
||||
// the title of the selected post, since our `value_field` in the config
|
||||
// is "title".
|
||||
const { value, fieldsMetaData } = this.props;
|
||||
const post = fieldsMetaData && fieldsMetaData.getIn(['posts', value]);
|
||||
|
@ -34,8 +34,8 @@ collections: # A list of collections the CMS should be able to edit
|
||||
label: 'Publish Date',
|
||||
name: 'date',
|
||||
widget: 'datetime',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: 'HH:mm',
|
||||
date_format: 'YYYY-MM-DD',
|
||||
time_format: 'HH:mm',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
}
|
||||
- label: 'Cover Image'
|
||||
@ -108,9 +108,9 @@ collections: # A list of collections the CMS should be able to edit
|
||||
name: 'post'
|
||||
widget: 'relationKitchenSinkPost'
|
||||
collection: 'posts'
|
||||
displayFields: ['title', 'date']
|
||||
searchFields: ['title', 'body']
|
||||
valueField: 'title'
|
||||
display_fields: ['title', 'date']
|
||||
search_fields: ['title', 'body']
|
||||
value_field: 'title'
|
||||
- { label: 'Title', name: 'title', widget: 'string' }
|
||||
- { label: 'Boolean', name: 'boolean', widget: 'boolean', default: true }
|
||||
- { label: 'Map', name: 'map', widget: 'map' }
|
||||
@ -139,8 +139,8 @@ collections: # A list of collections the CMS should be able to edit
|
||||
name: 'post'
|
||||
widget: 'relationKitchenSinkPost'
|
||||
collection: 'posts'
|
||||
searchFields: ['title', 'body']
|
||||
valueField: 'title'
|
||||
search_fields: ['title', 'body']
|
||||
value_field: 'title'
|
||||
- { label: 'String', name: 'string', widget: 'string' }
|
||||
- { label: 'Boolean', name: 'boolean', widget: 'boolean', default: false }
|
||||
- { label: 'Text', name: 'text', widget: 'text' }
|
||||
@ -187,8 +187,8 @@ collections: # A list of collections the CMS should be able to edit
|
||||
name: 'post'
|
||||
widget: 'relationKitchenSinkPost'
|
||||
collection: 'posts'
|
||||
searchFields: ['title', 'body']
|
||||
valueField: 'title'
|
||||
search_fields: ['title', 'body']
|
||||
value_field: 'title'
|
||||
- { label: 'String', name: 'string', widget: 'string' }
|
||||
- { label: 'Boolean', name: 'boolean', widget: 'boolean' }
|
||||
- { label: 'Text', name: 'text', widget: 'text' }
|
||||
|
@ -172,9 +172,9 @@
|
||||
render: function() {
|
||||
// When a post is selected from the relation field, all of it's data
|
||||
// will be available in the field's metadata nested under the collection
|
||||
// name, and then further nested under the value specified in `valueField`.
|
||||
// name, and then further nested under the value specified in `value_field`.
|
||||
// In this case, the post would be nested under "posts" and then under
|
||||
// the title of the selected post, since our `valueField` in the config
|
||||
// the title of the selected post, since our `value_field` in the config
|
||||
// is "title".
|
||||
const { value, fieldsMetaData } = this.props;
|
||||
const post = fieldsMetaData && fieldsMetaData.getIn(['posts', value]);
|
||||
|
51
packages/netlify-cms-core/index.d.ts
vendored
51
packages/netlify-cms-core/index.d.ts
vendored
@ -61,8 +61,21 @@ declare module 'netlify-cms-core' {
|
||||
|
||||
/** If widget === "datetime" */
|
||||
format?: string;
|
||||
date_format?: boolean | string;
|
||||
time_format?: boolean | string;
|
||||
picker_utc?: boolean;
|
||||
|
||||
/**
|
||||
* @deprecated Use date_format instead
|
||||
*/
|
||||
dateFormat?: boolean | string;
|
||||
/**
|
||||
* @deprecated Use time_format instead
|
||||
*/
|
||||
timeFormat?: boolean | string;
|
||||
/**
|
||||
* @deprecated Use picker_utc instead
|
||||
*/
|
||||
pickerUtc?: boolean;
|
||||
|
||||
/** If widget === "file" || widget === "image" */
|
||||
@ -89,12 +102,22 @@ declare module 'netlify-cms-core' {
|
||||
/** If widget === "markdown" */
|
||||
minimal?: boolean;
|
||||
buttons?: CmsMarkdownWidgetButton[];
|
||||
editor_components?: string[];
|
||||
|
||||
/**
|
||||
* @deprecated Use editor_components instead
|
||||
*/
|
||||
editorComponents?: string[];
|
||||
|
||||
/** If widget === "number" */
|
||||
valueType?: 'int' | 'float' | string;
|
||||
value_type?: 'int' | 'float' | string;
|
||||
step?: number;
|
||||
|
||||
/**
|
||||
* @deprecated Use valueType instead
|
||||
*/
|
||||
valueType?: 'int' | 'float' | string;
|
||||
|
||||
/** If widget === "number" || widget === "select" */
|
||||
min?: number;
|
||||
max?: number;
|
||||
@ -104,10 +127,27 @@ declare module 'netlify-cms-core' {
|
||||
|
||||
/** If widget === "relation" */
|
||||
collection?: string;
|
||||
valueField?: string;
|
||||
searchFields?: string[];
|
||||
value_field?: string;
|
||||
search_fields?: string[];
|
||||
file?: string;
|
||||
display_fields?: string[];
|
||||
options_length?: number;
|
||||
|
||||
/**
|
||||
* @deprecated Use value_field instead
|
||||
*/
|
||||
valueField?: string;
|
||||
/**
|
||||
* @deprecated Use search_fields instead
|
||||
*/
|
||||
searchFields?: string[];
|
||||
/**
|
||||
* @deprecated Use display_fields instead
|
||||
*/
|
||||
displayFields?: string[];
|
||||
/**
|
||||
* @deprecated Use options_length instead
|
||||
*/
|
||||
optionsLength?: number;
|
||||
|
||||
/** If widget === "select" */
|
||||
@ -155,6 +195,11 @@ declare module 'netlify-cms-core' {
|
||||
path?: string;
|
||||
media_folder?: string;
|
||||
public_folder?: string;
|
||||
sortable_fields?: string[];
|
||||
|
||||
/**
|
||||
* @deprecated Use sortable_fields instead
|
||||
*/
|
||||
sortableFields?: string[];
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,15 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { stripIndent } from 'common-tags';
|
||||
import { parseConfig, applyDefaults, detectProxyServer, handleLocalBackend } from '../config';
|
||||
import {
|
||||
parseConfig,
|
||||
normalizeConfig,
|
||||
applyDefaults,
|
||||
detectProxyServer,
|
||||
handleLocalBackend,
|
||||
} from '../config';
|
||||
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
jest.mock('coreSrc/backend', () => {
|
||||
return {
|
||||
resolveBackend: jest.fn(() => ({ isGitBackend: jest.fn(() => true) })),
|
||||
@ -413,6 +420,119 @@ describe('config', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should convert camel case to snake case', () => {
|
||||
expect(
|
||||
applyDefaults(
|
||||
normalizeConfig(
|
||||
fromJS({
|
||||
collections: [
|
||||
{
|
||||
sortableFields: ['title'],
|
||||
folder: 'src',
|
||||
identifier_field: 'datetime',
|
||||
fields: [
|
||||
{
|
||||
name: 'datetime',
|
||||
widget: 'datetime',
|
||||
dateFormat: 'YYYY/MM/DD',
|
||||
timeFormat: 'HH:mm',
|
||||
pickerUtc: true,
|
||||
},
|
||||
{
|
||||
widget: 'number',
|
||||
valueType: 'float',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sortableFields: [],
|
||||
files: [
|
||||
{
|
||||
name: 'file',
|
||||
file: 'src/file.json',
|
||||
fields: [
|
||||
{
|
||||
widget: 'markdown',
|
||||
editorComponents: ['code'],
|
||||
},
|
||||
{
|
||||
widget: 'relation',
|
||||
valueField: 'title',
|
||||
searchFields: ['title'],
|
||||
displayFields: ['title'],
|
||||
optionsLength: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
).toJS(),
|
||||
).toEqual({
|
||||
public_folder: '/',
|
||||
publish_mode: 'simple',
|
||||
slug: { clean_accents: false, encoding: 'unicode', sanitize_replacement: '-' },
|
||||
collections: [
|
||||
{
|
||||
sortable_fields: ['title'],
|
||||
folder: 'src',
|
||||
identifier_field: 'datetime',
|
||||
fields: [
|
||||
{
|
||||
name: 'datetime',
|
||||
widget: 'datetime',
|
||||
date_format: 'YYYY/MM/DD',
|
||||
dateFormat: 'YYYY/MM/DD',
|
||||
time_format: 'HH:mm',
|
||||
timeFormat: 'HH:mm',
|
||||
picker_utc: true,
|
||||
pickerUtc: true,
|
||||
},
|
||||
{
|
||||
widget: 'number',
|
||||
value_type: 'float',
|
||||
valueType: 'float',
|
||||
},
|
||||
],
|
||||
meta: {},
|
||||
publish: true,
|
||||
view_filters: [],
|
||||
},
|
||||
{
|
||||
sortable_fields: [],
|
||||
files: [
|
||||
{
|
||||
name: 'file',
|
||||
file: 'src/file.json',
|
||||
fields: [
|
||||
{
|
||||
widget: 'markdown',
|
||||
editor_components: ['code'],
|
||||
editorComponents: ['code'],
|
||||
},
|
||||
{
|
||||
widget: 'relation',
|
||||
value_field: 'title',
|
||||
valueField: 'title',
|
||||
search_fields: ['title'],
|
||||
searchFields: ['title'],
|
||||
display_fields: ['title'],
|
||||
displayFields: ['title'],
|
||||
options_length: 5,
|
||||
optionsLength: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
publish: true,
|
||||
view_filters: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectProxyServer', () => {
|
||||
|
@ -31,10 +31,79 @@ const setDefaultPublicFolder = map => {
|
||||
return map;
|
||||
};
|
||||
|
||||
const setSnakeCaseConfig = field => {
|
||||
// Mapping between existing camelCase and its snake_case counterpart
|
||||
const widgetKeyMap = {
|
||||
dateFormat: 'date_format',
|
||||
timeFormat: 'time_format',
|
||||
pickerUtc: 'picker_utc',
|
||||
editorComponents: 'editor_components',
|
||||
valueType: 'value_type',
|
||||
valueField: 'value_field',
|
||||
searchFields: 'search_fields',
|
||||
displayFields: 'display_fields',
|
||||
optionsLength: 'options_length',
|
||||
};
|
||||
|
||||
Object.entries(widgetKeyMap).forEach(([camel, snake]) => {
|
||||
if (field.has(camel)) {
|
||||
field = field.set(snake, field.get(camel));
|
||||
console.warn(
|
||||
`Field ${field.get(
|
||||
'name',
|
||||
)} is using a deprecated configuration '${camel}'. Please use '${snake}'`,
|
||||
);
|
||||
}
|
||||
});
|
||||
return field;
|
||||
};
|
||||
|
||||
const defaults = {
|
||||
publish_mode: publishModes.SIMPLE,
|
||||
};
|
||||
|
||||
export function normalizeConfig(config) {
|
||||
return Map(config).withMutations(map => {
|
||||
map.set(
|
||||
'collections',
|
||||
map.get('collections').map(collection => {
|
||||
const folder = collection.get('folder');
|
||||
if (folder) {
|
||||
collection = collection.set(
|
||||
'fields',
|
||||
traverseFields(collection.get('fields'), setSnakeCaseConfig),
|
||||
);
|
||||
}
|
||||
|
||||
const files = collection.get('files');
|
||||
if (files) {
|
||||
collection = collection.set(
|
||||
'files',
|
||||
files.map(file => {
|
||||
file = file.set('fields', traverseFields(file.get('fields'), setSnakeCaseConfig));
|
||||
return file;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (collection.has('sortableFields')) {
|
||||
collection = collection
|
||||
.set('sortable_fields', collection.get('sortableFields'))
|
||||
.delete('sortableFields');
|
||||
|
||||
console.warn(
|
||||
`Collection ${collection.get(
|
||||
'name',
|
||||
)} is using a deprecated configuration 'sortableFields'. Please use 'sortable_fields'`,
|
||||
);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function applyDefaults(config) {
|
||||
return Map(defaults)
|
||||
.mergeDeep(config)
|
||||
@ -118,10 +187,10 @@ export function applyDefaults(config) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!collection.has('sortableFields')) {
|
||||
if (!collection.has('sortable_fields')) {
|
||||
const backend = resolveBackend(config);
|
||||
const defaultSortable = selectDefaultSortableFields(collection, backend);
|
||||
collection = collection.set('sortableFields', fromJS(defaultSortable));
|
||||
collection = collection.set('sortable_fields', fromJS(defaultSortable));
|
||||
}
|
||||
|
||||
if (!collection.has('view_filters')) {
|
||||
@ -283,7 +352,7 @@ export function loadConfig() {
|
||||
|
||||
mergedConfig = await handleLocalBackend(mergedConfig);
|
||||
|
||||
const config = applyDefaults(mergedConfig);
|
||||
const config = applyDefaults(normalizeConfig(mergedConfig));
|
||||
|
||||
dispatch(configDidLoad(config));
|
||||
dispatch(authenticateUser());
|
||||
|
@ -21,7 +21,7 @@ const renderWithRedux = (component, { store } = {}) => {
|
||||
};
|
||||
|
||||
describe('Collection', () => {
|
||||
const collection = fromJS({ name: 'pages', sortableFields: [], view_filters: [] });
|
||||
const collection = fromJS({ name: 'pages', sortable_fields: [], view_filters: [] });
|
||||
const props = {
|
||||
collections: fromJS([collection]).toOrderedMap(),
|
||||
collection,
|
||||
|
@ -14,8 +14,8 @@ exports[`Collection should render connected component 1`] = `
|
||||
class="emotion-2 emotion-3"
|
||||
>
|
||||
<mock-sidebar
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [] }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [] } }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [] }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [] } }"
|
||||
filterterm=""
|
||||
searchterm=""
|
||||
/>
|
||||
@ -23,7 +23,7 @@ exports[`Collection should render connected component 1`] = `
|
||||
class="emotion-0 emotion-1"
|
||||
>
|
||||
<mock-collection-top
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [] }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [] }"
|
||||
newentryurl=""
|
||||
/>
|
||||
<mock-collection-controls
|
||||
@ -32,7 +32,7 @@ exports[`Collection should render connected component 1`] = `
|
||||
viewfilters=""
|
||||
/>
|
||||
<mock-entries-collection
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [] }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [] }"
|
||||
filterterm=""
|
||||
/>
|
||||
</main>
|
||||
@ -54,19 +54,19 @@ exports[`Collection should render with collection with create url 1`] = `
|
||||
class="emotion-2 emotion-3"
|
||||
>
|
||||
<mock-sidebar
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [] } }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [] } }"
|
||||
/>
|
||||
<main
|
||||
class="emotion-0 emotion-1"
|
||||
>
|
||||
<mock-collection-top
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
newentryurl="/collections/pages/new"
|
||||
/>
|
||||
<mock-collection-controls />
|
||||
<mock-entries-collection
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
@ -87,20 +87,20 @@ exports[`Collection should render with collection with create url and path 1`] =
|
||||
class="emotion-2 emotion-3"
|
||||
>
|
||||
<mock-sidebar
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [] } }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [] } }"
|
||||
filterterm="dir1/dir2"
|
||||
/>
|
||||
<main
|
||||
class="emotion-0 emotion-1"
|
||||
>
|
||||
<mock-collection-top
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
newentryurl="/collections/pages/new?path=dir1/dir2"
|
||||
/>
|
||||
<mock-collection-controls />
|
||||
<mock-entries-collection
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": true }"
|
||||
filterterm="dir1/dir2"
|
||||
/>
|
||||
</main>
|
||||
@ -122,19 +122,19 @@ exports[`Collection should render with collection without create url 1`] = `
|
||||
class="emotion-2 emotion-3"
|
||||
>
|
||||
<mock-sidebar
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": false }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [] } }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": false }"
|
||||
collections="OrderedMap { 0: Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [] } }"
|
||||
/>
|
||||
<main
|
||||
class="emotion-0 emotion-1"
|
||||
>
|
||||
<mock-collection-top
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": false }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": false }"
|
||||
newentryurl=""
|
||||
/>
|
||||
<mock-collection-controls />
|
||||
<mock-entries-collection
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortableFields\\": List [], \\"view_filters\\": List [], \\"create\\": false }"
|
||||
collection="Map { \\"name\\": \\"pages\\", \\"sortable_fields\\": List [], \\"view_filters\\": List [], \\"create\\": false }"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
|
@ -183,24 +183,38 @@ describe('config', () => {
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
||||
it('should throw if collections sortableFields is not a boolean or a string array', () => {
|
||||
it('should throw if collections sortable_fields is not a boolean or a string array', () => {
|
||||
expect(() => {
|
||||
validateConfig(merge({}, validConfig, { collections: [{ sortableFields: 'title' }] }));
|
||||
}).toThrowError("'collections[0].sortableFields' should be array");
|
||||
validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: 'title' }] }));
|
||||
}).toThrowError("'collections[0].sortable_fields' should be array");
|
||||
});
|
||||
|
||||
it('should allow sortableFields to be a string array', () => {
|
||||
it('should allow sortable_fields to be a string array', () => {
|
||||
expect(() => {
|
||||
validateConfig(merge({}, validConfig, { collections: [{ sortableFields: ['title'] }] }));
|
||||
validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: ['title'] }] }));
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should allow sortableFields to be a an empty array', () => {
|
||||
it('should allow sortable_fields to be a an empty array', () => {
|
||||
expect(() => {
|
||||
validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: [] }] }));
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should allow sortableFields instead of sortable_fields', () => {
|
||||
expect(() => {
|
||||
validateConfig(merge({}, validConfig, { collections: [{ sortableFields: [] }] }));
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw if both sortable_fields and sortableFields exist', () => {
|
||||
expect(() => {
|
||||
validateConfig(
|
||||
merge({}, validConfig, { collections: [{ sortable_fields: [], sortableFields: [] }] }),
|
||||
);
|
||||
}).toThrowError("'collections[0]' should NOT be valid");
|
||||
});
|
||||
|
||||
it('should throw if collection names are not unique', () => {
|
||||
expect(() => {
|
||||
validateConfig(
|
||||
@ -285,14 +299,14 @@ describe('config', () => {
|
||||
name: 'relation',
|
||||
schema: {
|
||||
properties: {
|
||||
searchFields: { type: 'array', items: { type: 'string' } },
|
||||
displayFields: { type: 'array', items: { type: 'string' } },
|
||||
search_fields: { type: 'array', items: { type: 'string' } },
|
||||
display_fields: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
it('should throw if nested relation displayFields and searchFields are not arrays', () => {
|
||||
it('should throw if nested relation display_fields and search_fields are not arrays', () => {
|
||||
expect(() => {
|
||||
validateConfig(
|
||||
merge({}, validConfig, {
|
||||
@ -310,8 +324,8 @@ describe('config', () => {
|
||||
name: 'relation',
|
||||
label: 'relation',
|
||||
widget: 'relation',
|
||||
displayFields: 'title',
|
||||
searchFields: 'title',
|
||||
display_fields: 'title',
|
||||
search_fields: 'title',
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -320,10 +334,10 @@ describe('config', () => {
|
||||
],
|
||||
}),
|
||||
);
|
||||
}).toThrowError("'searchFields' should be array\n'displayFields' should be array");
|
||||
}).toThrowError("'search_fields' should be array\n'display_fields' should be array");
|
||||
});
|
||||
|
||||
it('should not throw if nested relation displayFields and searchFields are arrays', () => {
|
||||
it('should not throw if nested relation display_fields and search_fields are arrays', () => {
|
||||
expect(() => {
|
||||
validateConfig(
|
||||
merge({}, validConfig, {
|
||||
@ -341,8 +355,8 @@ describe('config', () => {
|
||||
name: 'relation',
|
||||
label: 'relation',
|
||||
widget: 'relation',
|
||||
displayFields: ['title'],
|
||||
searchFields: ['title'],
|
||||
display_fields: ['title'],
|
||||
search_fields: ['title'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -181,6 +181,12 @@ const getConfigSchema = () => ({
|
||||
},
|
||||
},
|
||||
fields: fieldsConfig(),
|
||||
sortable_fields: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
sortableFields: {
|
||||
type: 'array',
|
||||
items: {
|
||||
@ -215,6 +221,9 @@ const getConfigSchema = () => ({
|
||||
},
|
||||
required: ['name', 'label'],
|
||||
oneOf: [{ required: ['files'] }, { required: ['folder', 'fields'] }],
|
||||
not: {
|
||||
required: ['sortable_fields', 'sortableFields'],
|
||||
},
|
||||
if: { required: ['extension'] },
|
||||
then: {
|
||||
// Cannot infer format from extension.
|
||||
|
@ -245,6 +245,7 @@ export const traverseFields = (
|
||||
if (done()) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
fields = fields
|
||||
.map(f => {
|
||||
const field = updater(f as EntryField);
|
||||
@ -395,7 +396,7 @@ export const selectDefaultSortableFields = (collection: Collection, backend: Bac
|
||||
|
||||
export const selectSortableFields = (collection: Collection, t: (key: string) => string) => {
|
||||
const fields = collection
|
||||
.get('sortableFields')
|
||||
.get('sortable_fields')
|
||||
.toArray()
|
||||
.map(key => {
|
||||
if (key === COMMIT_DATE) {
|
||||
|
@ -183,7 +183,7 @@ type CollectionObject = {
|
||||
slug?: string;
|
||||
label_singular?: string;
|
||||
label: string;
|
||||
sortableFields: List<string>;
|
||||
sortable_fields: List<string>;
|
||||
view_filters: List<StaticallyTypedRecord<ViewFilter>>;
|
||||
nested?: Nested;
|
||||
meta?: Meta;
|
||||
|
@ -36,9 +36,9 @@ export default class DateControl extends React.Component {
|
||||
|
||||
// dateFormat and timeFormat are strictly for modifying
|
||||
// input field with the date/time pickers
|
||||
const dateFormat = field.get('dateFormat');
|
||||
const dateFormat = field.get('date_format');
|
||||
// show time-picker? false hides it, true shows it using default format
|
||||
let timeFormat = field.get('timeFormat');
|
||||
let timeFormat = field.get('time_format');
|
||||
if (typeof timeFormat === 'undefined') {
|
||||
timeFormat = !!includeTime;
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ export default class DateTimeControl extends React.Component {
|
||||
|
||||
// dateFormat and timeFormat are strictly for modifying
|
||||
// input field with the date/time pickers
|
||||
const dateFormat = field.get('dateFormat');
|
||||
const dateFormat = field.get('date_format');
|
||||
// show time-picker? false hides it, true shows it using default format
|
||||
let timeFormat = field.get('timeFormat');
|
||||
let timeFormat = field.get('time_format');
|
||||
if (typeof timeFormat === 'undefined') {
|
||||
timeFormat = true;
|
||||
}
|
||||
@ -46,7 +46,7 @@ export default class DateTimeControl extends React.Component {
|
||||
|
||||
getPickerUtc() {
|
||||
const { field } = this.props;
|
||||
const pickerUtc = field.get('pickerUtc');
|
||||
const pickerUtc = field.get('picker_utc');
|
||||
return pickerUtc;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
export default {
|
||||
properties: {
|
||||
format: { type: 'string' },
|
||||
dateFormat: { oneOf: [{ type: 'string' }, { type: 'boolean' }] },
|
||||
timeFormat: { oneOf: [{ type: 'string' }, { type: 'boolean' }] },
|
||||
pickerUtc: { type: 'boolean' },
|
||||
date_format: { oneOf: [{ type: 'string' }, { type: 'boolean' }] },
|
||||
time_format: { oneOf: [{ type: 'string' }, { type: 'boolean' }] },
|
||||
picker_utc: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
|
@ -195,7 +195,7 @@ export default class Editor extends React.Component {
|
||||
onAddAsset={onAddAsset}
|
||||
getAsset={getAsset}
|
||||
buttons={field.get('buttons')}
|
||||
editorComponents={field.get('editorComponents')}
|
||||
editorComponents={field.get('editor_components')}
|
||||
hasMark={this.hasMark}
|
||||
hasInline={this.hasInline}
|
||||
hasBlock={this.hasBlock}
|
||||
|
@ -22,6 +22,6 @@ export default {
|
||||
],
|
||||
},
|
||||
},
|
||||
editorComponents: { type: 'array', items: { type: 'string' } },
|
||||
editor_components: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ export default class NumberControl extends React.Component {
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
const valueType = this.props.field.get('valueType');
|
||||
const valueType = this.props.field.get('value_type');
|
||||
const { onChange } = this.props;
|
||||
const value = valueType === 'float' ? parseFloat(e.target.value) : parseInt(e.target.value, 10);
|
||||
|
||||
@ -99,7 +99,7 @@ export default class NumberControl extends React.Component {
|
||||
const { field, value, classNameWrapper, forID, setActiveStyle, setInactiveStyle } = this.props;
|
||||
const min = field.get('min', '');
|
||||
const max = field.get('max', '');
|
||||
const step = field.get('step', field.get('valueType') === 'int' ? 1 : '');
|
||||
const step = field.get('step', field.get('value_type') === 'int' ? 1 : '');
|
||||
return (
|
||||
<input
|
||||
type="number"
|
||||
|
@ -10,7 +10,7 @@ const fieldSettings = {
|
||||
min: -20,
|
||||
max: 20,
|
||||
step: 1,
|
||||
valueType: 'int',
|
||||
value_type: 'int',
|
||||
};
|
||||
|
||||
class NumberController extends React.Component {
|
||||
@ -120,7 +120,7 @@ describe('Number widget', () => {
|
||||
});
|
||||
|
||||
it('should parse float numbers as float', () => {
|
||||
const field = fromJS({ ...fieldSettings, valueType: 'float' });
|
||||
const field = fromJS({ ...fieldSettings, value_type: 'float' });
|
||||
const testValue = (Math.random() * (20 - -20 + 1) + -20).toFixed(2);
|
||||
const { input, onChangeSpy } = setup({ field });
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
export default {
|
||||
properties: {
|
||||
step: { type: 'number' },
|
||||
valueType: { type: 'string' },
|
||||
value_type: { type: 'string' },
|
||||
min: { type: 'number' },
|
||||
max: { type: 'number' },
|
||||
},
|
||||
|
@ -113,7 +113,7 @@ export default class RelationControl extends React.Component {
|
||||
const metadata = {};
|
||||
const allOptions = await Promise.all(
|
||||
initialSearchValues.map((v, index) => {
|
||||
return query(forID, collection, [field.get('valueField')], v, file, 1).then(
|
||||
return query(forID, collection, [field.get('value_field')], v, file, 1).then(
|
||||
({ payload }) => {
|
||||
const hits = payload.response?.hits || [];
|
||||
const options = this.parseHitOptions(hits);
|
||||
@ -189,8 +189,8 @@ export default class RelationControl extends React.Component {
|
||||
|
||||
parseHitOptions = hits => {
|
||||
const { field } = this.props;
|
||||
const valueField = field.get('valueField');
|
||||
const displayField = field.get('displayFields') || List([field.get('valueField')]);
|
||||
const valueField = field.get('value_field');
|
||||
const displayField = field.get('display_fields') || List([field.get('value_field')]);
|
||||
const options = hits.reduce((acc, hit) => {
|
||||
const valuesPaths = stringTemplate.expandPath({ data: hit.data, path: valueField });
|
||||
for (let i = 0; i < valuesPaths.length; i++) {
|
||||
@ -214,8 +214,8 @@ export default class RelationControl extends React.Component {
|
||||
loadOptions = debounce((term, callback) => {
|
||||
const { field, query, forID } = this.props;
|
||||
const collection = field.get('collection');
|
||||
const searchFields = field.get('searchFields');
|
||||
const optionsLength = field.get('optionsLength') || 20;
|
||||
const searchFields = field.get('search_fields');
|
||||
const optionsLength = field.get('options_length') || 20;
|
||||
const searchFieldsArray = List.isList(searchFields) ? searchFields.toJS() : [searchFields];
|
||||
const file = field.get('file');
|
||||
|
||||
|
@ -15,34 +15,34 @@ const RelationControl = NetlifyCmsWidgetRelation.controlComponent;
|
||||
const fieldConfig = {
|
||||
name: 'post',
|
||||
collection: 'posts',
|
||||
displayFields: ['title', 'slug'],
|
||||
searchFields: ['title', 'body'],
|
||||
valueField: 'title',
|
||||
display_fields: ['title', 'slug'],
|
||||
search_fields: ['title', 'body'],
|
||||
value_field: 'title',
|
||||
};
|
||||
|
||||
const customizedOptionsLengthConfig = {
|
||||
name: 'post',
|
||||
collection: 'posts',
|
||||
displayFields: ['title', 'slug'],
|
||||
searchFields: ['title', 'body'],
|
||||
valueField: 'title',
|
||||
optionsLength: 10,
|
||||
display_fields: ['title', 'slug'],
|
||||
search_fields: ['title', 'body'],
|
||||
value_field: 'title',
|
||||
options_length: 10,
|
||||
};
|
||||
|
||||
const deeplyNestedFieldConfig = {
|
||||
name: 'post',
|
||||
collection: 'posts',
|
||||
displayFields: ['title', 'slug', 'deeply.nested.post.field'],
|
||||
searchFields: ['deeply.nested.post.field'],
|
||||
valueField: 'title',
|
||||
display_fields: ['title', 'slug', 'deeply.nested.post.field'],
|
||||
search_fields: ['deeply.nested.post.field'],
|
||||
value_field: 'title',
|
||||
};
|
||||
|
||||
const nestedFieldConfig = {
|
||||
name: 'post',
|
||||
collection: 'posts',
|
||||
displayFields: ['title', 'slug', 'nested.field_1'],
|
||||
searchFields: ['nested.field_1', 'nested.field_2'],
|
||||
valueField: 'title',
|
||||
display_fields: ['title', 'slug', 'nested.field_1'],
|
||||
search_fields: ['nested.field_1', 'nested.field_2'],
|
||||
value_field: 'title',
|
||||
};
|
||||
|
||||
const generateHits = length => {
|
||||
@ -327,9 +327,9 @@ describe('Relation widget', () => {
|
||||
const stringTemplateConfig = {
|
||||
name: 'post',
|
||||
collection: 'posts',
|
||||
displayFields: ['{{slug}}', '{{filename}}', '{{extension}}'],
|
||||
searchFields: ['slug'],
|
||||
valueField: '{{slug}}',
|
||||
display_fields: ['{{slug}}', '{{filename}}', '{{extension}}'],
|
||||
search_fields: ['slug'],
|
||||
value_field: '{{slug}}',
|
||||
};
|
||||
|
||||
const field = fromJS(stringTemplateConfig);
|
||||
@ -348,8 +348,8 @@ describe('Relation widget', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should default displayFields to valueField', async () => {
|
||||
const field = fromJS(fieldConfig).delete('displayFields');
|
||||
it('should default display_fields to value_field', async () => {
|
||||
const field = fromJS(fieldConfig).delete('display_fields');
|
||||
const { getAllByText, input } = setup({ field });
|
||||
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
||||
|
||||
@ -361,9 +361,9 @@ describe('Relation widget', () => {
|
||||
const fieldConfig = {
|
||||
name: 'numbers',
|
||||
collection: 'numbers_collection',
|
||||
valueField: 'index',
|
||||
searchFields: ['index'],
|
||||
displayFields: ['title'],
|
||||
value_field: 'index',
|
||||
search_fields: ['index'],
|
||||
display_fields: ['title'],
|
||||
};
|
||||
|
||||
const field = fromJS(fieldConfig);
|
||||
@ -442,8 +442,8 @@ describe('Relation widget', () => {
|
||||
name: 'categories',
|
||||
collection: 'file',
|
||||
file: 'simple_file',
|
||||
valueField: 'categories.*',
|
||||
displayFields: ['categories.*'],
|
||||
value_field: 'categories.*',
|
||||
display_fields: ['categories.*'],
|
||||
};
|
||||
|
||||
it('should handle simple list', async () => {
|
||||
@ -462,8 +462,8 @@ describe('Relation widget', () => {
|
||||
const field = fromJS({
|
||||
...fileFieldConfig,
|
||||
file: 'nested_file',
|
||||
valueField: 'nested.categories.*.id',
|
||||
displayFields: ['nested.categories.*.name'],
|
||||
value_field: 'nested.categories.*.id',
|
||||
display_fields: ['nested.categories.*.name'],
|
||||
});
|
||||
const { getAllByText, input, getByText } = setup({ field });
|
||||
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
||||
|
@ -1,12 +1,19 @@
|
||||
export default {
|
||||
properties: {
|
||||
collection: { type: 'string' },
|
||||
valueField: { type: 'string' },
|
||||
searchFields: { type: 'array', minItems: 1, items: { type: 'string' } },
|
||||
value_field: { type: 'string' },
|
||||
search_fields: { type: 'array', minItems: 1, items: { type: 'string' } },
|
||||
file: { type: 'string' },
|
||||
multiple: { type: 'boolean' },
|
||||
displayFields: { type: 'array', minItems: 1, items: { type: 'string' } },
|
||||
optionsLength: { type: 'integer' },
|
||||
display_fields: { type: 'array', minItems: 1, items: { type: 'string' } },
|
||||
options_length: { type: 'integer' },
|
||||
},
|
||||
oneOf: [
|
||||
{
|
||||
required: ['collection', 'value_field', 'search_fields'],
|
||||
},
|
||||
{
|
||||
required: ['collection', 'valueField', 'searchFields'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -209,7 +209,7 @@ The `collections` setting is the heart of your Netlify CMS configuration, as it
|
||||
* `fields` (required): see detailed description below
|
||||
* `editor`: see detailed description below
|
||||
* `summary`: see detailed description below
|
||||
* `sortableFields`: see detailed description below
|
||||
* `sortable_fields`: see detailed description below
|
||||
* `view_filters`: see detailed description below
|
||||
|
||||
The last few options require more detailed information.
|
||||
@ -384,7 +384,7 @@ Template tags are the same as those for [slug](#slug), with the following additi
|
||||
summary: "Version: {{version}} - {{title}}"
|
||||
```
|
||||
|
||||
### `sortableFields`
|
||||
### `sortable_fields`
|
||||
|
||||
An optional list of sort fields to show in the UI.
|
||||
|
||||
@ -396,7 +396,7 @@ When `author` field can't be inferred commit author will be used.
|
||||
|
||||
```yaml
|
||||
# use dot notation for nested fields
|
||||
sortableFields: ['commit_date', 'title', 'commit_author', 'language.en']
|
||||
sortable_fields: ['commit_date', 'title', 'commit_author', 'language.en']
|
||||
```
|
||||
|
||||
### `view_filters`
|
||||
|
@ -201,9 +201,9 @@ fields:
|
||||
name: 'author',
|
||||
widget: 'relation',
|
||||
collection: 'authors',
|
||||
displayFields: [display_name],
|
||||
searchFields: [display_name],
|
||||
valueField: 'name',
|
||||
display_fields: [display_name],
|
||||
search_fields: [display_name],
|
||||
value_field: 'name',
|
||||
}
|
||||
- { label: 'Body', name: 'body', widget: 'markdown' }
|
||||
```
|
||||
|
@ -13,8 +13,8 @@ The date widget translates a date picker input to a date string. For saving date
|
||||
- **Options:**
|
||||
- `default`: accepts a date string, or an empty string to accept blank input; otherwise defaults to current date
|
||||
- `format`: optional; accepts Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/); defaults to raw Date object (if supported by output format)
|
||||
- `dateFormat`: optional; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format.
|
||||
- `timeFormat`: optional; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format, `false` hides time-picker. Defaults to false.
|
||||
- `date_format`: optional; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format.
|
||||
- `time_format`: optional; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format, `false` hides time-picker. Defaults to false.
|
||||
- **Example:**
|
||||
```yaml
|
||||
- label: 'Birthdate'
|
||||
|
@ -11,17 +11,17 @@ The datetime widget translates a datetime picker to a datetime string.
|
||||
- **Options:**
|
||||
- `default`: accepts a datetime string, or an empty string to accept blank input; otherwise defaults to current datetime
|
||||
- `format`: sets storage format; accepts Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/); defaults to raw Date object (if supported by output format)
|
||||
- `dateFormat`: sets date display format in UI; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format.
|
||||
- `timeFormat`: sets time display format in UI; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format, `false` hides time-picker.
|
||||
- `pickerUtc`: _(default: `false`)_ when set to `true`, the datetime picker will display times in UTC. When `false`, the datetime picker will display times in the user's local timezone. When using date-only formats, it can be helpful to set this to `true` so users in all timezones will see the same date in the datetime picker.
|
||||
- `date_format`: sets date display format in UI; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format.
|
||||
- `time_format`: sets time display format in UI; boolean or Moment.js [tokens](https://momentjs.com/docs/#/parsing/string-format/). If `true` use default locale format, `false` hides time-picker.
|
||||
- `picker_utc`: _(default: `false`)_ when set to `true`, the datetime picker will display times in UTC. When `false`, the datetime picker will display times in the user's local timezone. When using date-only formats, it can be helpful to set this to `true` so users in all timezones will see the same date in the datetime picker.
|
||||
- **Example:**
|
||||
```yaml
|
||||
- label: "Start time"
|
||||
name: "start"
|
||||
widget: "datetime"
|
||||
default: ""
|
||||
dateFormat: "DD.MM.YYYY" # e.g. 24.12.2021
|
||||
timeFormat: "HH:mm" # e.g. 21:07
|
||||
date_format: "DD.MM.YYYY" # e.g. 24.12.2021
|
||||
time_format: "HH:mm" # e.g. 21:07
|
||||
format: "LLL"
|
||||
pickerUtc: false
|
||||
picker_utc: false
|
||||
```
|
||||
|
@ -14,7 +14,7 @@ _Please note:_ If you want to use your markdown editor to fill a markdown file c
|
||||
- `default`: accepts markdown content
|
||||
- `minimal`: accepts a boolean value, `false` by default. Sets the widget height to minimum possible.
|
||||
- `buttons`: an array of strings representing the formatting buttons to display (all shown by default). Buttons include: `bold`, `italic`, `code`, `link`, `heading-one`, `heading-two`, `heading-three`, `heading-four`, `heading-five`, `heading-six`, `quote`, `bulleted-list`, and `numbered-list`.
|
||||
- `editorComponents`: an array of strings representing the names of editor components to display (all shown by default). The `image` and `code-block` editor components are included with Netlify CMS by default, but others may be [created and registered](/docs/custom-widgets/#registereditorcomponent).
|
||||
- `editor_components`: an array of strings representing the names of editor components to display (all shown by default). The `image` and `code-block` editor components are included with Netlify CMS by default, but others may be [created and registered](/docs/custom-widgets/#registereditorcomponent).
|
||||
- **Example:**
|
||||
|
||||
```yaml
|
||||
|
@ -7,10 +7,10 @@ The number widget uses an HTML number input, saving the value as a string, integ
|
||||
|
||||
- **Name:** `number`
|
||||
- **UI:** HTML [number input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number)
|
||||
- **Data type:** string by default; configured by `valueType` option
|
||||
- **Data type:** string by default; configured by `value_type` option
|
||||
- **Options:**
|
||||
- `default`: accepts string or number value; defaults to empty string
|
||||
- `valueType`: accepts `int` or `float`; any other value results in saving as a string
|
||||
- `value_type`: accepts `int` or `float`; any other value results in saving as a string
|
||||
- `min`: accepts a number for minimum value accepted; unset by default
|
||||
- `max`: accepts a number for maximum value accepted; unset by default
|
||||
- `step`: accepts a number for stepping up/down values in the input; 1 by default
|
||||
@ -20,7 +20,7 @@ The number widget uses an HTML number input, saving the value as a string, integ
|
||||
name: "puppies"
|
||||
widget: "number"
|
||||
default: 2
|
||||
valueType: "int"
|
||||
value_type: "int"
|
||||
min: 1
|
||||
max: 101
|
||||
step: 2
|
||||
|
@ -10,13 +10,13 @@ The relation widget allows you to reference items from another collection. It pr
|
||||
- **Data type:** data type of the value pulled from the related collection item
|
||||
- **Options:**
|
||||
- `collection`: (**required**) name of the collection being referenced (string)
|
||||
- `valueField`: (**required**) name of the field from the referenced collection whose value will be stored for the relation. For nested fields, separate each subfield with a `.` (e.g. `name.first`). For list fields use a wildcard `*` to target all list items (e.g. `categories.*`).
|
||||
- `searchFields`: (**required**) list of one or more names of fields in the referenced collection to search for the typed value. Syntax to reference nested fields is similar to that of *valueField*.
|
||||
- `value_field`: (**required**) name of the field from the referenced collection whose value will be stored for the relation. For nested fields, separate each subfield with a `.` (e.g. `name.first`). For list fields use a wildcard `*` to target all list items (e.g. `categories.*`).
|
||||
- `search_fields`: (**required**) list of one or more names of fields in the referenced collection to search for the typed value. Syntax to reference nested fields is similar to that of *value_field*.
|
||||
- `file`: allows referencing a specific file when the collection being referenced is a files collection (string)
|
||||
- `displayFields`: list of one or more names of fields in the referenced collection that will render in the autocomplete menu of the control. Defaults to `valueField`. Syntax to reference nested fields is similar to that of *valueField*.
|
||||
- `display_fields`: list of one or more names of fields in the referenced collection that will render in the autocomplete menu of the control. Defaults to `value_field`. Syntax to reference nested fields is similar to that of *value_field*.
|
||||
- `default`: accepts any widget data type; defaults to an empty string
|
||||
- `multiple` : accepts a boolean, defaults to `false`
|
||||
- `optionsLength`: accepts integer to override number of options presented to user. Defaults to `20`.
|
||||
- `options_length`: accepts integer to override number of options presented to user. Defaults to `20`.
|
||||
- **Referencing a folder collection example** (assuming a separate "authors" collection with "name" and "twitterHandle" fields with subfields "first" and "last" for the "name" field):
|
||||
|
||||
```yaml
|
||||
@ -24,9 +24,9 @@ The relation widget allows you to reference items from another collection. It pr
|
||||
name: "author"
|
||||
widget: "relation"
|
||||
collection: "authors"
|
||||
searchFields: ["name.first", "twitterHandle"]
|
||||
valueField: "name.first"
|
||||
displayFields: ["twitterHandle", "followerCount"]
|
||||
search_fields: ["name.first", "twitterHandle"]
|
||||
value_field: "name.first"
|
||||
display_fields: ["twitterHandle", "followerCount"]
|
||||
```
|
||||
|
||||
The generated UI input will search the authors collection by name and twitterHandle, and display each author's handle and follower count. On selection, the author name will be saved for the field.
|
||||
@ -38,9 +38,9 @@ The generated UI input will search the authors collection by name and twitterHan
|
||||
name: "author"
|
||||
widget: "relation"
|
||||
collection: "authors"
|
||||
searchFields: ['name.first']
|
||||
valueField: "{{slug}}"
|
||||
displayFields: ["{{twitterHandle}} - {{followerCount}}"]
|
||||
search_fields: ['name.first']
|
||||
value_field: "{{slug}}"
|
||||
display_fields: ["{{twitterHandle}} - {{followerCount}}"]
|
||||
```
|
||||
|
||||
The generated UI input will search the authors collection by name, and display each author's handle and follower count. On selection, the author entry slug will be saved for the field.
|
||||
@ -53,9 +53,9 @@ The generated UI input will search the authors collection by name, and display e
|
||||
widget: "relation"
|
||||
collection: "relation_files"
|
||||
file: "cities"
|
||||
searchFields: ["cities.*.name"]
|
||||
displayFields: ["cities.*.name"]
|
||||
valueField: "cities.*.id"
|
||||
search_fields: ["cities.*.name"]
|
||||
display_fields: ["cities.*.name"]
|
||||
value_field: "cities.*.id"
|
||||
```
|
||||
|
||||
The generated UI input will search the cities file by city name, and display each city's name. On selection, the city id will be saved for the field.
|
||||
|
Loading…
x
Reference in New Issue
Block a user