Skip to content

Commit

Permalink
feat: option to remove empty image field from content #7120 #7186
Browse files Browse the repository at this point in the history
  • Loading branch information
hip3r committed Oct 22, 2024
1 parent 51eb7e8 commit 4de43fd
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/decap-cms-core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ declare module 'decap-cms-core' {
slug?: CmsSlug;
i18n?: CmsI18nConfig;
local_backend?: boolean | CmsLocalBackend;
remove_empty_image_field?: boolean;
editor?: {
preview?: boolean;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/decap-cms-core/src/actions/editorialWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export function persistUnpublishedEntry(collection: Collection, existingUnpublis
entry,
});

const serializedEntry = getSerializedEntry(collection, entry);
const serializedEntry = getSerializedEntry(collection, entry, state.config);
const serializedEntryDraft = entryDraft.set('entry', serializedEntry);

dispatch(unpublishedEntryPersisting(collection, entry.get('slug')));
Expand Down
7 changes: 4 additions & 3 deletions packages/decap-cms-core/src/actions/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
ViewFilter,
ViewGroup,
Entry,
CmsConfig,
} from '../types/redux';
import type { EntryValue } from '../valueObjects/Entry';
import type { Backend } from '../backend';
Expand Down Expand Up @@ -856,7 +857,7 @@ export function getMediaAssets({ entry }: { entry: EntryMap }) {
return assets;
}

export function getSerializedEntry(collection: Collection, entry: Entry) {
export function getSerializedEntry(collection: Collection, entry: Entry, config: CmsConfig) {
/**
* Serialize the values of any fields with registered serializers, and
* update the entry and entryDraft with the serialized values.
Expand All @@ -865,7 +866,7 @@ export function getSerializedEntry(collection: Collection, entry: Entry) {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function serializeData(data: any) {
return serializeValues(data, fields);
return serializeValues(data, fields, config);
}

const serializedData = serializeData(entry.get('data'));
Expand Down Expand Up @@ -910,7 +911,7 @@ export function persistEntry(collection: Collection) {
entry,
});

const serializedEntry = getSerializedEntry(collection, entry);
const serializedEntry = getSerializedEntry(collection, entry, state.config);
const serializedEntryDraft = entryDraft.set('entry', serializedEntry);
dispatch(entryPersisting(collection, serializedEntry));
return backend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ describe('config', () => {
}).not.toThrowError();
});

it('should throw if remove_empty_image_field is not a boolean', () => {
expect(() => {
validateConfig(merge({}, validConfig, { remove_empty_image_field: 'false' }));
}).toThrowError("'remove_empty_image_field' must be boolean");
});

it('should not throw if remove_empty_image_field is a boolean', () => {
expect(() => {
validateConfig(merge({}, validConfig, { remove_empty_image_field: false }));
}).not.toThrowError();
});

it('should throw if collection publish is not a boolean', () => {
expect(() => {
validateConfig(merge({}, validConfig, { collections: [{ publish: 'false' }] }));
Expand Down
1 change: 1 addition & 0 deletions packages/decap-cms-core/src/constants/configSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ function getConfigSchema() {
},
],
},
remove_empty_image_field: { type: 'boolean' },
locale: { type: 'string', examples: ['en', 'fr', 'de'] },
i18n: i18nRoot,
site_url: { type: 'string', examples: ['https://example.com'] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import { fromJS } from 'immutable';

import { serializeValues, deserializeValues } from '../serializeEntryValues';

const values = fromJS({ title: 'New Post', unknown: 'Unknown Field' });
const fields = fromJS([{ name: 'title', widget: 'string' }]);
const values = fromJS({ title: 'New Post', unknown: 'Unknown Field', removed_image: '' });
const fields = fromJS([{ name: 'title', widget: 'string' }, { name: 'removed_image', widget: 'image' }]);

describe('serializeValues', () => {
it('should retain unknown fields', () => {
expect(serializeValues(values, fields)).toEqual(
fromJS({ title: 'New Post', unknown: 'Unknown Field', removed_image: '' }),
);
});

it('should remove image field', () => {
expect(serializeValues(values, fields, { remove_empty_image_field: true })).toEqual(
fromJS({ title: 'New Post', unknown: 'Unknown Field' }),
);
});
Expand All @@ -16,7 +22,7 @@ describe('serializeValues', () => {
describe('deserializeValues', () => {
it('should retain unknown fields', () => {
expect(deserializeValues(values, fields)).toEqual(
fromJS({ title: 'New Post', unknown: 'Unknown Field' }),
fromJS({ title: 'New Post', unknown: 'Unknown Field', removed_image: '' }),
);
});
});
41 changes: 35 additions & 6 deletions packages/decap-cms-core/src/lib/serializeEntryValues.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Map, List } from 'immutable';

import { getWidgetValueSerializer } from './registry';

const FLAG_REMOVE_ENTRY = '!~FLAG_REMOVE_ENTRY~!';
/**
* Methods for serializing/deserializing entry field values. Most widgets don't
* require this for their values, and those that do can typically serialize/
Expand All @@ -21,7 +22,7 @@ import { getWidgetValueSerializer } from './registry';
* registered deserialization handlers run on entry load, and serialization
* handlers run on persist.
*/
function runSerializer(values, fields, method) {
function runSerializer(values, fields, method, config = {}, isRecursive = false) {
/**
* Reduce the list of fields to a map where keys are field names and values
* are field values, serializing the values of fields whose widgets have
Expand All @@ -38,20 +39,25 @@ function runSerializer(values, fields, method) {
if (nestedFields && List.isList(value)) {
return acc.set(
fieldName,
value.map(val => runSerializer(val, nestedFields, method)),
value.map(val => runSerializer(val, nestedFields, method, config, true)),
);
}

// Call recursively for fields within objects
if (nestedFields && Map.isMap(value)) {
return acc.set(fieldName, runSerializer(value, nestedFields, method));
return acc.set(fieldName, runSerializer(value, nestedFields, method, config, true));
}

// Run serialization method on value if not null or undefined
if (serializer && !isNil(value)) {
return acc.set(fieldName, serializer[method](value));
}

// If widget is image with no value set, flag field for removal
if (config.remove_empty_image_field && !value && field.get('widget') === 'image') {
return acc.set(fieldName, FLAG_REMOVE_ENTRY);
}

// If no serializer is registered for the field's widget, use the field as is
if (!isNil(value)) {
return acc.set(fieldName, value);
Expand All @@ -60,14 +66,37 @@ function runSerializer(values, fields, method) {
return acc;
}, Map());

//preserve unknown fields value
// preserve unknown fields value
serializedData = values.mergeDeep(serializedData);

// Remove only on the top level, otherwise `mergeDeep` will reinsert them.
if (config.remove_empty_image_field && !isRecursive) {
serializedData = serializedData.map(v => removeEntriesRecursive(v))
.filter(v => v !== FLAG_REMOVE_ENTRY);
}

return serializedData;
}

export function serializeValues(values, fields) {
return runSerializer(values, fields, 'serialize');
function removeEntriesRecursive(entry) {
if (List.isList(entry)) {
return entry.map(v => removeEntriesRecursive(v)).filter(v => v !== FLAG_REMOVE_ENTRY);
} else if (Map.isMap(entry)) {
let updatedEntry = entry;
entry.forEach((v, k) => {
if (Map.isMap(v) || List.isList(v)) {
updatedEntry = updatedEntry.set(k, removeEntriesRecursive(v));
} else if (v === FLAG_REMOVE_ENTRY) {
updatedEntry = updatedEntry.delete(k);
}
});
return updatedEntry;
}
return entry;
}

export function serializeValues(values, fields, config) {
return runSerializer(values, fields, 'serialize', config);
}

export function deserializeValues(values, fields) {
Expand Down
1 change: 1 addition & 0 deletions packages/decap-cms-core/src/types/redux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ export interface CmsConfig {
slug?: CmsSlug;
i18n?: CmsI18nConfig;
local_backend?: boolean | CmsLocalBackend;
remove_empty_image_field?: boolean;
editor?: {
preview?: boolean;
};
Expand Down

0 comments on commit 4de43fd

Please sign in to comment.