fix: reapply defaults on discard (#864)

This commit is contained in:
Daniel Lautzenheiser 2023-09-06 12:52:13 -04:00 committed by GitHub
parent 5602812774
commit 6bcf451a18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 75 deletions

View File

@ -1,5 +1,3 @@
import isEqual from 'lodash/isEqual';
import { currentBackend } from '../backend';
import {
ADD_DRAFT_ENTRY_MEDIA_FILE,
@ -40,16 +38,11 @@ import {
SORT_ENTRIES_SUCCESS,
} from '../constants';
import ValidationErrorTypes from '../constants/validationErrorTypes';
import {
I18N_FIELD_DUPLICATE,
I18N_FIELD_TRANSLATE,
duplicateDefaultI18nFields,
hasI18n,
serializeI18n,
} from '../lib/i18n';
import { hasI18n, serializeI18n } from '../lib/i18n';
import { serializeValues } from '../lib/serializeEntryValues';
import { Cursor } from '../lib/util';
import { selectFields, updateFieldByKey } from '../lib/util/collection.util';
import { createEmptyDraftData, createEmptyDraftI18nData } from '../lib/util/entry.util';
import { selectCollectionEntriesCursor } from '../reducers/selectors/cursors';
import {
selectEntriesSortField,
@ -77,7 +70,6 @@ import type {
FieldError,
I18nSettings,
ImplementationMediaFile,
ObjectValue,
SortDirection,
ValueOrNestedValue,
ViewFilter,
@ -439,10 +431,10 @@ export function emptyDraftCreated(entry: Entry) {
/*
* Exported simple Action Creators
*/
export function createDraftFromEntry(entry: Entry) {
export function createDraftFromEntry(collection: Collection, entry: Entry) {
return {
type: DRAFT_CREATE_FROM_ENTRY,
payload: { entry },
payload: { collection, entry },
} as const;
}
@ -625,7 +617,7 @@ export function loadEntry(collection: Collection, slug: string, silent = false)
await dispatch(loadMedia());
const loadedEntry = await tryLoadEntry(getState(), collection, slug);
dispatch(entryLoaded(collection, loadedEntry));
dispatch(createDraftFromEntry(loadedEntry));
dispatch(createDraftFromEntry(collection, loadedEntry));
} catch (error: unknown) {
console.error(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 }) {
const filesArray = entry.mediaFiles;
const assets = filesArray

View File

@ -327,6 +327,10 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
await dispatch(loadScroll());
}, [dispatch]);
const handleDiscardDraft = useCallback(() => {
setVersion(version => version + 1);
}, []);
if (entry && entry.error) {
return (
<div>
@ -356,6 +360,7 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
toggleScroll={handleToggleScroll}
scrollSyncActive={scrollSyncActive}
loadScroll={handleLoadScroll}
onDiscardDraft={handleDiscardDraft}
submitted={submitted}
slug={slug}
t={t}

View File

@ -76,6 +76,7 @@ interface EditorInterfaceProps {
loadScroll: () => void;
submitted: boolean;
slug: string | undefined;
onDiscardDraft: () => void;
}
const EditorInterface = ({
@ -97,6 +98,7 @@ const EditorInterface = ({
toggleScroll,
submitted,
slug,
onDiscardDraft,
}: TranslatedProps<EditorInterfaceProps>) => {
const config = useAppSelector(selectConfig);
@ -413,6 +415,7 @@ const EditorInterface = ({
showMobilePreview={showMobilePreview}
onMobilePreviewToggle={toggleMobilePreview}
className="flex"
onDiscardDraft={onDiscardDraft}
/>
}
>

View File

@ -49,6 +49,7 @@ export interface EditorToolbarProps {
className?: string;
showMobilePreview: boolean;
onMobilePreviewToggle: () => void;
onDiscardDraft: () => void;
}
const EditorToolbar = ({
@ -75,6 +76,7 @@ const EditorToolbar = ({
className,
showMobilePreview,
onMobilePreviewToggle,
onDiscardDraft,
}: TranslatedProps<EditorToolbarProps>) => {
const canCreate = useMemo(
() => ('folder' in collection && collection.create) ?? false,
@ -100,10 +102,11 @@ const EditorToolbar = ({
color: 'warning',
})
) {
dispatch(deleteLocalBackup(collection, slug));
dispatch(loadEntry(collection, slug));
await dispatch(deleteLocalBackup(collection, slug));
await dispatch(loadEntry(collection, slug));
onDiscardDraft();
}
}, [collection, dispatch, slug]);
}, [collection, dispatch, onDiscardDraft, slug]);
const menuItems: JSX.Element[][] = useMemo(() => {
const items: JSX.Element[] = [];

View 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);
}

View File

@ -21,6 +21,8 @@ import {
REMOVE_DRAFT_ENTRY_MEDIA_FILE,
} from '../constants';
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 type { EntriesAction } from '../actions/entries';
@ -56,10 +58,18 @@ function entryDraftReducer(
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
return {
...newState,
entry,
entry: {
...entry,
data: applyDefaultsToDraftData(fields, undefined, entry.data),
},
original: entry,
fieldsErrors: {},
hasChanged: false,