diff --git a/packages/netlify-cms-core/src/__tests__/backend.spec.js b/packages/netlify-cms-core/src/__tests__/backend.spec.js index 35445848..e36fe869 100644 --- a/packages/netlify-cms-core/src/__tests__/backend.spec.js +++ b/packages/netlify-cms-core/src/__tests__/backend.spec.js @@ -203,6 +203,7 @@ describe('Backend', () => { raw: '---\ntitle: "Hello World"\n---\n', data: { title: 'Hello World' }, meta: {}, + i18n: {}, label: null, isModification: null, status: '', @@ -244,6 +245,7 @@ describe('Backend', () => { raw: '---\ntitle: "Hello World"\n---\n', data: { title: 'Hello World' }, meta: {}, + i18n: {}, label: null, isModification: null, status: '', @@ -391,6 +393,7 @@ describe('Backend', () => { raw: '---\ntitle: "Hello World"\n---\n', data: { title: 'Hello World' }, meta: { path: 'src/posts/index.md' }, + i18n: {}, label: null, isModification: true, mediaFiles: [{ id: '1', draft: true }], diff --git a/packages/netlify-cms-core/src/actions/__tests__/entries.spec.js b/packages/netlify-cms-core/src/actions/__tests__/entries.spec.js index 9c324733..95b39f39 100644 --- a/packages/netlify-cms-core/src/actions/__tests__/entries.spec.js +++ b/packages/netlify-cms-core/src/actions/__tests__/entries.spec.js @@ -49,6 +49,7 @@ describe('entries', () => { collection: undefined, data: {}, meta: {}, + i18n: {}, isModification: null, label: null, mediaFiles: [], @@ -81,6 +82,7 @@ describe('entries', () => { collection: undefined, data: { title: 'title', boolean: true }, meta: {}, + i18n: {}, isModification: null, label: null, mediaFiles: [], @@ -115,6 +117,7 @@ describe('entries', () => { collection: undefined, data: { title: '<script>alert('hello')</script>' }, meta: {}, + i18n: {}, isModification: null, label: null, mediaFiles: [], diff --git a/packages/netlify-cms-core/src/actions/entries.ts b/packages/netlify-cms-core/src/actions/entries.ts index 7472af76..f6820355 100644 --- a/packages/netlify-cms-core/src/actions/entries.ts +++ b/packages/netlify-cms-core/src/actions/entries.ts @@ -31,7 +31,7 @@ import { selectIsFetching, selectEntriesSortFields, selectEntryByPath } from '.. import { selectCustomPath } from '../reducers/entryDraft'; import { navigateToEntry } from '../routing/history'; import { getProcessSegment } from '../lib/formatters'; -import { hasI18n, serializeI18n } from '../lib/i18n'; +import { hasI18n, duplicateDefaultI18nFields, serializeI18n, I18N, I18N_FIELD } from '../lib/i18n'; const { notifSend } = notifActions; @@ -669,6 +669,9 @@ const processValue = (unsafe: string) => { return escapeHtml(unsafe); }; +const getDataFields = (fields: EntryFields) => fields.filter(f => !f!.get('meta')).toList(); +const getMetaFields = (fields: EntryFields) => fields.filter(f => f!.get('meta') === true).toList(); + export function createEmptyDraft(collection: Collection, search: string) { return async (dispatch: ThunkDispatch, getState: () => State) => { const params = new URLSearchParams(search); @@ -679,8 +682,12 @@ export function createEmptyDraft(collection: Collection, search: string) { }); const fields = collection.get('fields', List()); - const dataFields = createEmptyDraftData(fields.filter(f => !f!.get('meta')).toList()); - const metaFields = createEmptyDraftData(fields.filter(f => f!.get('meta') === true).toList()); + + const dataFields = getDataFields(fields); + const data = createEmptyDraftData(dataFields); + + const metaFields = getMetaFields(fields); + const meta = createEmptyDraftData(metaFields); const state = getState(); const backend = currentBackend(state.config); @@ -689,11 +696,14 @@ export function createEmptyDraft(collection: Collection, search: string) { await waitForMediaLibraryToLoad(dispatch, getState()); } + const i18nFields = createEmptyDraftI18nData(collection, dataFields); + let newEntry = createEntry(collection.get('name'), '', '', { - data: dataFields, + data, + i18n: i18nFields, mediaFiles: [], // eslint-disable-next-line @typescript-eslint/no-explicit-any - meta: metaFields as any, + meta: meta as any, }); newEntry = await backend.processEntry(state, collection, newEntry); dispatch(emptyDraftCreated(newEntry)); @@ -711,7 +721,11 @@ interface DraftEntryData { | (string | DraftEntryData | boolean | List)[]; } -export function createEmptyDraftData(fields: EntryFields, withNameKey = true) { +export function createEmptyDraftData( + fields: EntryFields, + withNameKey = true, + skipField: (field: EntryField) => boolean = () => false, +) { return fields.reduce( ( reduction: DraftEntryData | string | undefined | boolean | List, @@ -719,6 +733,11 @@ export function createEmptyDraftData(fields: EntryFields, withNameKey = true) { ) => { const acc = reduction as DraftEntryData; const item = value as EntryField; + + if (skipField(item)) { + return acc; + } + const subfields = item.get('field') || item.get('fields'); const list = item.get('widget') == 'list'; const name = item.get('name'); @@ -727,8 +746,8 @@ export function createEmptyDraftData(fields: EntryFields, withNameKey = true) { if (List.isList(subfields)) { const subDefaultValue = list - ? [createEmptyDraftData(subfields as EntryFields)] - : createEmptyDraftData(subfields as EntryFields); + ? [createEmptyDraftData(subfields as EntryFields, withNameKey, skipField)] + : createEmptyDraftData(subfields as EntryFields, withNameKey, skipField); if (!isEmptyDefaultValue(subDefaultValue)) { acc[name] = subDefaultValue; } else if (list && List.isList(defaultValue) && (defaultValue as List).isEmpty()) { @@ -740,8 +759,8 @@ export function createEmptyDraftData(fields: EntryFields, withNameKey = true) { if (Map.isMap(subfields)) { const subDefaultValue = list - ? [createEmptyDraftData(List([subfields as EntryField]), false)] - : createEmptyDraftData(List([subfields as EntryField])); + ? [createEmptyDraftData(List([subfields as EntryField]), false, skipField)] + : createEmptyDraftData(List([subfields as EntryField]), withNameKey, skipField); if (!isEmptyDefaultValue(subDefaultValue)) { acc[name] = subDefaultValue; } else if (list && List.isList(defaultValue) && (defaultValue as List).isEmpty()) { @@ -764,6 +783,19 @@ export function createEmptyDraftData(fields: EntryFields, withNameKey = true) { ); } +function createEmptyDraftI18nData(collection: Collection, dataFields: EntryFields) { + if (!hasI18n(collection)) { + return {}; + } + + const skipField = (field: EntryField) => { + return field.get(I18N) !== I18N_FIELD.DUPLICATE && field.get(I18N) !== I18N_FIELD.TRANSLATE; + }; + + const i18nData = createEmptyDraftData(dataFields, true, skipField); + return duplicateDefaultI18nFields(collection, i18nData); +} + export function getMediaAssets({ entry }: { entry: EntryMap }) { const filesArray = entry.get('mediaFiles').toArray(); const assets = filesArray diff --git a/packages/netlify-cms-core/src/lib/i18n.ts b/packages/netlify-cms-core/src/lib/i18n.ts index 5eb903b3..0b11b071 100644 --- a/packages/netlify-cms-core/src/lib/i18n.ts +++ b/packages/netlify-cms-core/src/lib/i18n.ts @@ -341,6 +341,19 @@ export const getI18nDataFiles = ( return dataFiles; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const duplicateDefaultI18nFields = (collection: Collection, dataFields: any) => { + const { locales, defaultLocale } = getI18nInfo(collection) as I18nInfo; + + const i18nFields = Object.fromEntries( + locales + .filter(locale => locale !== defaultLocale) + .map(locale => [locale, { data: dataFields }]), + ); + + return i18nFields; +}; + export const duplicateI18nFields = ( entryDraft: EntryDraft, field: EntryField, diff --git a/packages/netlify-cms-core/src/valueObjects/Entry.ts b/packages/netlify-cms-core/src/valueObjects/Entry.ts index 275c0b85..4c064041 100644 --- a/packages/netlify-cms-core/src/valueObjects/Entry.ts +++ b/packages/netlify-cms-core/src/valueObjects/Entry.ts @@ -13,6 +13,10 @@ interface Options { updatedOn?: string; status?: string; meta?: { path?: string }; + i18n?: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [locale: string]: any; + }; } export interface EntryValue { @@ -51,6 +55,7 @@ export function createEntry(collection: string, slug = '', path = '', options: O updatedOn: options.updatedOn || '', status: options.status || '', meta: options.meta || {}, + i18n: options.i18n || {}, }; return returnObj;