Skip to content

Commit

Permalink
Feat: Allow using subfields as identifier field (#3219)
Browse files Browse the repository at this point in the history
  • Loading branch information
erezrokah committed Feb 12, 2020
1 parent e7589a9 commit c412562
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
import { colors, colorsRaw, components, lengths, Asset } from 'netlify-cms-ui-default';
import { VIEW_STYLE_LIST, VIEW_STYLE_GRID } from 'Constants/collectionViews';
import { summaryFormatter } from 'Lib/formatters';
import { keyToPathArray } from 'Lib/stringTemplate';

const ListCard = styled.li`
${components.card};
Expand Down Expand Up @@ -128,7 +129,7 @@ const mapStateToProps = (state, ownProps) => {
const { entry, inferedFields, collection } = ownProps;
const label = entry.get('label');
const entryData = entry.get('data');
const defaultTitle = label || entryData.get(inferedFields.titleField);
const defaultTitle = label || entryData.getIn(keyToPathArray(inferedFields.titleField));
const summaryTemplate = collection.get('summary');
const summary = summaryTemplate
? summaryFormatter(summaryTemplate, entry, collection)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Frame from 'react-frame-component';
import { lengths } from 'netlify-cms-ui-default';
import { resolveWidget, getPreviewTemplate, getPreviewStyles } from 'Lib/registry';
import { ErrorBoundary } from 'UI';
import { selectTemplateName, selectInferedField } from 'Reducers/collections';
import { selectTemplateName, selectInferedField, selectField } from 'Reducers/collections';
import { INFERABLE_FIELDS } from 'Constants/fieldInference';
import EditorPreviewContent from './EditorPreviewContent.js';
import PreviewHOC from './PreviewHOC';
Expand Down Expand Up @@ -87,8 +87,15 @@ export default class PreviewPane extends React.Component {
}

const labelledWidgets = ['string', 'text', 'number'];
if (Object.keys(this.inferedFields).indexOf(name) !== -1) {
value = this.inferedFields[name].defaultPreview(value);
const inferedField = Object.entries(this.inferedFields)
.filter(([key]) => {
const fieldToMatch = selectField(this.props.collection, key);
return fieldToMatch === field;
})
.map(([, value]) => value)[0];

if (inferedField) {
value = inferedField.defaultPreview(value);
} else if (
value &&
labelledWidgets.indexOf(field.get('widget')) !== -1 &&
Expand Down
9 changes: 4 additions & 5 deletions packages/netlify-cms-core/src/lib/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
compileStringTemplate,
parseDateFromEntry,
SLUG_MISSING_REQUIRED_DATE,
keyToPathArray,
} from './stringTemplate';
import { selectIdentifier } from '../reducers/collections';
import { Collection, SlugConfig, Config, EntryMap } from '../types/redux';
Expand Down Expand Up @@ -100,9 +101,7 @@ export const slugFormatter = (
) => {
const slugTemplate = collection.get('slug') || '{{slug}}';

// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
const identifier = entryData.get(selectIdentifier(collection)) as string;
const identifier = entryData.getIn(keyToPathArray(selectIdentifier(collection) as string));
if (!identifier) {
throw new Error(
'Collection must have a field name that is a valid entry identifier, or must have `identifier_field` set',
Expand Down Expand Up @@ -203,7 +202,7 @@ export const summaryFormatter = (
) => {
const entryData = entry.get('data');
const date = parseDateFromEntry(entry, collection) || null;
const identifier = entryData.get(selectIdentifier(collection));
const identifier = entryData.getIn(keyToPathArray(selectIdentifier(collection) as string));
const summary = compileStringTemplate(summaryTemplate, date, identifier, entryData);
return summary;
};
Expand All @@ -223,7 +222,7 @@ export const folderFormatter = (
fields = addFileTemplateFields(entry.get('path'), fields);

const date = parseDateFromEntry(entry, collection) || null;
const identifier = fields.get(selectIdentifier(collection) as string);
const identifier = fields.getIn(keyToPathArray(selectIdentifier(collection) as string));
const processSegment = getProcessSegment(slugConfig);

const mediaFolder = compileStringTemplate(
Expand Down
5 changes: 4 additions & 1 deletion packages/netlify-cms-core/src/lib/stringTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ const FIELD_PREFIX = 'fields.';
const templateContentPattern = '[^}{]+';
const templateVariablePattern = `{{(${templateContentPattern})}}`;

export const keyToPathArray = (key: string) => {
export const keyToPathArray = (key?: string) => {
if (!key) {
return [];
}
const parts = [];
const separator = '';
const chars = key.split(separator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import collections, {
selectEntrySlug,
selectFieldsMediaFolders,
selectMediaFolders,
getFieldsNames,
selectField,
} from '../collections';
import { FILES, FOLDER } from 'Constants/collectionTypes';

Expand Down Expand Up @@ -237,4 +239,70 @@ describe('collections', () => {
).toEqual(['file_media_folder', 'image_media_folder']);
});
});

describe('getFieldsNames', () => {
it('should get flat fields names', () => {
const collection = fromJS({
fields: [{ name: 'en' }, { name: 'es' }],
});
expect(getFieldsNames(collection.get('fields').toArray())).toEqual(['en', 'es']);
});

it('should get nested fields names', () => {
const collection = fromJS({
fields: [
{ name: 'en', fields: [{ name: 'title' }, { name: 'body' }] },
{ name: 'es', fields: [{ name: 'title' }, { name: 'body' }] },
{ name: 'it', field: { name: 'title', fields: [{ name: 'subTitle' }] } },
],
});
expect(getFieldsNames(collection.get('fields').toArray())).toEqual([
'en',
'es',
'it',
'en.title',
'en.body',
'es.title',
'es.body',
'it.title',
'it.title.subTitle',
]);
});
});

describe('selectField', () => {
it('should return top field by key', () => {
const collection = fromJS({
fields: [{ name: 'en' }, { name: 'es' }],
});
expect(selectField(collection, 'en')).toBe(collection.get('fields').get(0));
});

it('should return nested field by key', () => {
const collection = fromJS({
fields: [
{ name: 'en', fields: [{ name: 'title' }, { name: 'body' }] },
{ name: 'es', fields: [{ name: 'title' }, { name: 'body' }] },
{ name: 'it', field: { name: 'title', fields: [{ name: 'subTitle' }] } },
],
});

expect(selectField(collection, 'en.title')).toBe(
collection
.get('fields')
.get(0)
.get('fields')
.get(0),
);

expect(selectField(collection, 'it.title.subTitle')).toBe(
collection
.get('fields')
.get(2)
.get('field')
.get('fields')
.get(0),
);
});
});
});
39 changes: 38 additions & 1 deletion packages/netlify-cms-core/src/reducers/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EntryMap,
} from '../types/redux';
import { selectMediaFolder } from './entries';
import { keyToPathArray } from '../lib/stringTemplate';

const collections = (state = null, action: CollectionsAction) => {
switch (action.type) {
Expand Down Expand Up @@ -186,10 +187,46 @@ export const selectAllowDeletion = (collection: Collection) =>
selectors[collection.get('type')].allowDeletion(collection);
export const selectTemplateName = (collection: Collection, slug: string) =>
selectors[collection.get('type')].templateName(collection, slug);

export const getFieldsNames = (fields: EntryField[], prefix = '') => {
let names = fields.map(f => `${prefix}${f.get('name')}`);

fields.forEach((f, index) => {
if (f.has('fields')) {
const fields = f.get('fields')?.toArray() as EntryField[];
names = [...names, ...getFieldsNames(fields, `${names[index]}.`)];
}
if (f.has('field')) {
const field = f.get('field') as EntryField;
names = [...names, ...getFieldsNames([field], `${names[index]}.`)];
}
});

return names;
};

export const selectField = (collection: Collection, key: string) => {
const array = keyToPathArray(key);
let name: string | undefined;
let field;
let fields = collection.get('fields', List<EntryField>()).toArray();
while ((name = array.shift()) && fields) {
field = fields.find(f => f.get('name') === name);
if (field?.has('fields')) {
fields = field?.get('fields')?.toArray() as EntryField[];
}
if (field?.has('field')) {
fields = [field?.get('field') as EntryField];
}
}

return field;
};

export const selectIdentifier = (collection: Collection) => {
const identifier = collection.get('identifier_field');
const identifierFields = identifier ? [identifier, ...IDENTIFIER_FIELDS] : IDENTIFIER_FIELDS;
const fieldNames = collection.get('fields', List<EntryField>()).map(field => field?.get('name'));
const fieldNames = getFieldsNames(collection.get('fields', List<EntryField>()).toArray());
return identifierFields.find(id =>
fieldNames.find(name => name?.toLowerCase().trim() === id.toLowerCase().trim()),
);
Expand Down

0 comments on commit c412562

Please sign in to comment.