fix: reapply defaults on discard (#864)
This commit is contained in:
parent
5602812774
commit
6bcf451a18
@ -1,5 +1,3 @@
|
|||||||
import isEqual from 'lodash/isEqual';
|
|
||||||
|
|
||||||
import { currentBackend } from '../backend';
|
import { currentBackend } from '../backend';
|
||||||
import {
|
import {
|
||||||
ADD_DRAFT_ENTRY_MEDIA_FILE,
|
ADD_DRAFT_ENTRY_MEDIA_FILE,
|
||||||
@ -40,16 +38,11 @@ import {
|
|||||||
SORT_ENTRIES_SUCCESS,
|
SORT_ENTRIES_SUCCESS,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import ValidationErrorTypes from '../constants/validationErrorTypes';
|
import ValidationErrorTypes from '../constants/validationErrorTypes';
|
||||||
import {
|
import { hasI18n, serializeI18n } from '../lib/i18n';
|
||||||
I18N_FIELD_DUPLICATE,
|
|
||||||
I18N_FIELD_TRANSLATE,
|
|
||||||
duplicateDefaultI18nFields,
|
|
||||||
hasI18n,
|
|
||||||
serializeI18n,
|
|
||||||
} from '../lib/i18n';
|
|
||||||
import { serializeValues } from '../lib/serializeEntryValues';
|
import { serializeValues } from '../lib/serializeEntryValues';
|
||||||
import { Cursor } from '../lib/util';
|
import { Cursor } from '../lib/util';
|
||||||
import { selectFields, updateFieldByKey } from '../lib/util/collection.util';
|
import { selectFields, updateFieldByKey } from '../lib/util/collection.util';
|
||||||
|
import { createEmptyDraftData, createEmptyDraftI18nData } from '../lib/util/entry.util';
|
||||||
import { selectCollectionEntriesCursor } from '../reducers/selectors/cursors';
|
import { selectCollectionEntriesCursor } from '../reducers/selectors/cursors';
|
||||||
import {
|
import {
|
||||||
selectEntriesSortField,
|
selectEntriesSortField,
|
||||||
@ -77,7 +70,6 @@ import type {
|
|||||||
FieldError,
|
FieldError,
|
||||||
I18nSettings,
|
I18nSettings,
|
||||||
ImplementationMediaFile,
|
ImplementationMediaFile,
|
||||||
ObjectValue,
|
|
||||||
SortDirection,
|
SortDirection,
|
||||||
ValueOrNestedValue,
|
ValueOrNestedValue,
|
||||||
ViewFilter,
|
ViewFilter,
|
||||||
@ -439,10 +431,10 @@ export function emptyDraftCreated(entry: Entry) {
|
|||||||
/*
|
/*
|
||||||
* Exported simple Action Creators
|
* Exported simple Action Creators
|
||||||
*/
|
*/
|
||||||
export function createDraftFromEntry(entry: Entry) {
|
export function createDraftFromEntry(collection: Collection, entry: Entry) {
|
||||||
return {
|
return {
|
||||||
type: DRAFT_CREATE_FROM_ENTRY,
|
type: DRAFT_CREATE_FROM_ENTRY,
|
||||||
payload: { entry },
|
payload: { collection, entry },
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,7 +617,7 @@ export function loadEntry(collection: Collection, slug: string, silent = false)
|
|||||||
await dispatch(loadMedia());
|
await dispatch(loadMedia());
|
||||||
const loadedEntry = await tryLoadEntry(getState(), collection, slug);
|
const loadedEntry = await tryLoadEntry(getState(), collection, slug);
|
||||||
dispatch(entryLoaded(collection, loadedEntry));
|
dispatch(entryLoaded(collection, loadedEntry));
|
||||||
dispatch(createDraftFromEntry(loadedEntry));
|
dispatch(createDraftFromEntry(collection, loadedEntry));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@ -878,64 +870,6 @@ export function createEmptyDraft(collection: Collection, search: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmptyDraftData(
|
|
||||||
fields: Field[],
|
|
||||||
skipField: (field: Field) => boolean = () => false,
|
|
||||||
) {
|
|
||||||
const ddd = fields.reduce((acc, item) => {
|
|
||||||
if (skipField(item)) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subfields = 'fields' in item && item.fields;
|
|
||||||
const list = item.widget === 'list';
|
|
||||||
const name = item.name;
|
|
||||||
const defaultValue = (('default' in item ? item.default : null) ?? null) as EntryData;
|
|
||||||
|
|
||||||
function isEmptyDefaultValue(val: EntryData | EntryData[]) {
|
|
||||||
return [[{}], {}].some(e => isEqual(val, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subfields) {
|
|
||||||
if (list && Array.isArray(defaultValue)) {
|
|
||||||
acc[name] = defaultValue;
|
|
||||||
} else {
|
|
||||||
const asList = Array.isArray(subfields) ? subfields : [subfields];
|
|
||||||
|
|
||||||
const subDefaultValue = list
|
|
||||||
? [createEmptyDraftData(asList, skipField)]
|
|
||||||
: createEmptyDraftData(asList, skipField);
|
|
||||||
|
|
||||||
if (!isEmptyDefaultValue(subDefaultValue)) {
|
|
||||||
acc[name] = subDefaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultValue !== null) {
|
|
||||||
acc[name] = defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {} as ObjectValue);
|
|
||||||
|
|
||||||
return ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEmptyDraftI18nData(collection: Collection, dataFields: Field[]) {
|
|
||||||
if (!hasI18n(collection)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function skipField(field: Field) {
|
|
||||||
return field.i18n !== I18N_FIELD_DUPLICATE && field.i18n !== I18N_FIELD_TRANSLATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const i18nData = createEmptyDraftData(dataFields, skipField);
|
|
||||||
return duplicateDefaultI18nFields(collection, i18nData);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMediaAssets({ entry }: { entry: Entry }) {
|
export function getMediaAssets({ entry }: { entry: Entry }) {
|
||||||
const filesArray = entry.mediaFiles;
|
const filesArray = entry.mediaFiles;
|
||||||
const assets = filesArray
|
const assets = filesArray
|
||||||
|
@ -327,6 +327,10 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
await dispatch(loadScroll());
|
await dispatch(loadScroll());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleDiscardDraft = useCallback(() => {
|
||||||
|
setVersion(version => version + 1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (entry && entry.error) {
|
if (entry && entry.error) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -356,6 +360,7 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
toggleScroll={handleToggleScroll}
|
toggleScroll={handleToggleScroll}
|
||||||
scrollSyncActive={scrollSyncActive}
|
scrollSyncActive={scrollSyncActive}
|
||||||
loadScroll={handleLoadScroll}
|
loadScroll={handleLoadScroll}
|
||||||
|
onDiscardDraft={handleDiscardDraft}
|
||||||
submitted={submitted}
|
submitted={submitted}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
t={t}
|
t={t}
|
||||||
|
@ -76,6 +76,7 @@ interface EditorInterfaceProps {
|
|||||||
loadScroll: () => void;
|
loadScroll: () => void;
|
||||||
submitted: boolean;
|
submitted: boolean;
|
||||||
slug: string | undefined;
|
slug: string | undefined;
|
||||||
|
onDiscardDraft: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditorInterface = ({
|
const EditorInterface = ({
|
||||||
@ -97,6 +98,7 @@ const EditorInterface = ({
|
|||||||
toggleScroll,
|
toggleScroll,
|
||||||
submitted,
|
submitted,
|
||||||
slug,
|
slug,
|
||||||
|
onDiscardDraft,
|
||||||
}: TranslatedProps<EditorInterfaceProps>) => {
|
}: TranslatedProps<EditorInterfaceProps>) => {
|
||||||
const config = useAppSelector(selectConfig);
|
const config = useAppSelector(selectConfig);
|
||||||
|
|
||||||
@ -413,6 +415,7 @@ const EditorInterface = ({
|
|||||||
showMobilePreview={showMobilePreview}
|
showMobilePreview={showMobilePreview}
|
||||||
onMobilePreviewToggle={toggleMobilePreview}
|
onMobilePreviewToggle={toggleMobilePreview}
|
||||||
className="flex"
|
className="flex"
|
||||||
|
onDiscardDraft={onDiscardDraft}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -49,6 +49,7 @@ export interface EditorToolbarProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
showMobilePreview: boolean;
|
showMobilePreview: boolean;
|
||||||
onMobilePreviewToggle: () => void;
|
onMobilePreviewToggle: () => void;
|
||||||
|
onDiscardDraft: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditorToolbar = ({
|
const EditorToolbar = ({
|
||||||
@ -75,6 +76,7 @@ const EditorToolbar = ({
|
|||||||
className,
|
className,
|
||||||
showMobilePreview,
|
showMobilePreview,
|
||||||
onMobilePreviewToggle,
|
onMobilePreviewToggle,
|
||||||
|
onDiscardDraft,
|
||||||
}: TranslatedProps<EditorToolbarProps>) => {
|
}: TranslatedProps<EditorToolbarProps>) => {
|
||||||
const canCreate = useMemo(
|
const canCreate = useMemo(
|
||||||
() => ('folder' in collection && collection.create) ?? false,
|
() => ('folder' in collection && collection.create) ?? false,
|
||||||
@ -100,10 +102,11 @@ const EditorToolbar = ({
|
|||||||
color: 'warning',
|
color: 'warning',
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
dispatch(deleteLocalBackup(collection, slug));
|
await dispatch(deleteLocalBackup(collection, slug));
|
||||||
dispatch(loadEntry(collection, slug));
|
await dispatch(loadEntry(collection, slug));
|
||||||
|
onDiscardDraft();
|
||||||
}
|
}
|
||||||
}, [collection, dispatch, slug]);
|
}, [collection, dispatch, onDiscardDraft, slug]);
|
||||||
|
|
||||||
const menuItems: JSX.Element[][] = useMemo(() => {
|
const menuItems: JSX.Element[][] = useMemo(() => {
|
||||||
const items: JSX.Element[] = [];
|
const items: JSX.Element[] = [];
|
||||||
|
75
packages/core/src/lib/util/entry.util.ts
Normal file
75
packages/core/src/lib/util/entry.util.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
|
import { isNotNullish } from './null.util';
|
||||||
|
import {
|
||||||
|
I18N_FIELD_DUPLICATE,
|
||||||
|
I18N_FIELD_TRANSLATE,
|
||||||
|
duplicateDefaultI18nFields,
|
||||||
|
hasI18n,
|
||||||
|
} from '../i18n';
|
||||||
|
|
||||||
|
import type { Collection, EntryData, Field, ObjectValue } from '@staticcms/core/interface';
|
||||||
|
|
||||||
|
export function applyDefaultsToDraftData(
|
||||||
|
fields: Field[],
|
||||||
|
skipField: (field: Field) => boolean = () => false,
|
||||||
|
initialValue?: ObjectValue | null,
|
||||||
|
) {
|
||||||
|
const emptyDraftData = fields.reduce((acc, item) => {
|
||||||
|
const name = item.name;
|
||||||
|
|
||||||
|
if (skipField(item) || isNotNullish(acc[name])) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subfields = 'fields' in item && item.fields;
|
||||||
|
const list = item.widget === 'list';
|
||||||
|
const defaultValue = (('default' in item ? item.default : null) ?? null) as EntryData;
|
||||||
|
|
||||||
|
function isEmptyDefaultValue(val: EntryData | EntryData[]) {
|
||||||
|
return [[{}], {}].some(e => isEqual(val, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subfields) {
|
||||||
|
if (list && Array.isArray(defaultValue)) {
|
||||||
|
acc[name] = defaultValue;
|
||||||
|
} else {
|
||||||
|
const asList = Array.isArray(subfields) ? subfields : [subfields];
|
||||||
|
|
||||||
|
const subDefaultValue = list
|
||||||
|
? [applyDefaultsToDraftData(asList, skipField)]
|
||||||
|
: applyDefaultsToDraftData(asList, skipField);
|
||||||
|
|
||||||
|
if (!isEmptyDefaultValue(subDefaultValue)) {
|
||||||
|
acc[name] = subDefaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue !== null) {
|
||||||
|
acc[name] = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, (initialValue ?? {}) as ObjectValue);
|
||||||
|
|
||||||
|
return emptyDraftData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmptyDraftData(fields: Field[], skipField?: (field: Field) => boolean) {
|
||||||
|
return applyDefaultsToDraftData(fields, skipField);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmptyDraftI18nData(collection: Collection, dataFields: Field[]) {
|
||||||
|
if (!hasI18n(collection)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipField(field: Field) {
|
||||||
|
return field.i18n !== I18N_FIELD_DUPLICATE && field.i18n !== I18N_FIELD_TRANSLATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18nData = createEmptyDraftData(dataFields, skipField);
|
||||||
|
return duplicateDefaultI18nFields(collection, i18nData);
|
||||||
|
}
|
@ -21,6 +21,8 @@ import {
|
|||||||
REMOVE_DRAFT_ENTRY_MEDIA_FILE,
|
REMOVE_DRAFT_ENTRY_MEDIA_FILE,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { duplicateI18nFields, getDataPath } from '../lib/i18n';
|
import { duplicateI18nFields, getDataPath } from '../lib/i18n';
|
||||||
|
import { fileForEntry } from '../lib/util/collection.util';
|
||||||
|
import { applyDefaultsToDraftData } from '../lib/util/entry.util';
|
||||||
import { set } from '../lib/util/object.util';
|
import { set } from '../lib/util/object.util';
|
||||||
|
|
||||||
import type { EntriesAction } from '../actions/entries';
|
import type { EntriesAction } from '../actions/entries';
|
||||||
@ -56,10 +58,18 @@ function entryDraftReducer(
|
|||||||
newRecord: false,
|
newRecord: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const collection = action.payload.collection;
|
||||||
|
|
||||||
|
const file = fileForEntry(collection, entry.slug);
|
||||||
|
const fields = file ? file.fields : 'fields' in collection ? collection.fields : [];
|
||||||
|
|
||||||
// Existing Entry
|
// Existing Entry
|
||||||
return {
|
return {
|
||||||
...newState,
|
...newState,
|
||||||
entry,
|
entry: {
|
||||||
|
...entry,
|
||||||
|
data: applyDefaultsToDraftData(fields, undefined, entry.data),
|
||||||
|
},
|
||||||
original: entry,
|
original: entry,
|
||||||
fieldsErrors: {},
|
fieldsErrors: {},
|
||||||
hasChanged: false,
|
hasChanged: false,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user