fix: deprecate inconsistent config param case (#4172)
This commit is contained in:
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' },
|
||||
},
|
||||
required: ['collection', 'valueField', 'searchFields'],
|
||||
oneOf: [
|
||||
{
|
||||
required: ['collection', 'value_field', 'search_fields'],
|
||||
},
|
||||
{
|
||||
required: ['collection', 'valueField', 'searchFields'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
Reference in New Issue
Block a user