Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
hasFieldType,
isEntryReferenceField,
} from './fieldFormatting';
import { linkChildToParentEntry, withUpdatedReferenceGraph } from './linkChildToParent';
import { EditModal } from './edit-modals/EditModal';
import { RichTextSelectionPreview } from './edit-modals/RichTextSelectionPreview';

Expand Down Expand Up @@ -433,6 +434,7 @@ export const MappingView = ({
return {
tempId: entry.tempId ?? `${entry.contentTypeId}-${idx}`,
label: `${contentTypeName} (${entryTitle})`,
contentTypeId: entry.contentTypeId,
};
}),
[entryBlockGraph.entries, payload.contentTypes]
Expand All @@ -445,29 +447,30 @@ export const MappingView = ({
const newEntryIndex = entryBlockGraph.entries.length;
const tempId = crypto.randomUUID();

const parentEntryIndex = isLinkedReference
? entryBlockGraph.entries.findIndex(
(entry, idx) =>
(entry.tempId ?? `${entry.contentTypeId}-${idx}`) === params.referenceEntryId
)
: -1;
const parentEntry =
parentEntryIndex >= 0 ? entryBlockGraph.entries[parentEntryIndex] : undefined;
const parentContentType = parentEntry
? payload.contentTypes.find((ct) => ct.sys.id === parentEntry.contentTypeId)
: undefined;

const refField = isLinkedReference
? contentType?.fields?.find(
? parentContentType?.fields?.find(
(f) =>
f.id === params.referenceFieldId ||
(!params.referenceFieldId && isEntryReferenceField(f))
)
: undefined;

const newEntryFields: Record<string, Record<string, unknown>> = refField?.id
? {
[refField.id]: {
[defaultLocale]:
refField.type === 'Array'
? [{ __ref: params.referenceEntryId }]
: { __ref: params.referenceEntryId },
},
}
: {};

const newEntry = {
contentTypeId,
tempId,
fields: newEntryFields,
fields: {} as Record<string, Record<string, unknown>>,
fieldMappings: [],
};

Expand All @@ -476,6 +479,27 @@ export const MappingView = ({
entries: [...entryBlockGraph.entries, newEntry],
};

if (isLinkedReference && parentEntry && refField?.id && refField.type) {
const { parentEntry: updatedParent, edges: nextEdges } = linkChildToParentEntry({
parentEntry,
childTempId: tempId,
refField: { id: refField.id, type: refField.type },
defaultLocale,
previousEdges: referenceGraph.edges ?? [],
});

next = {
...next,
entries: next.entries.map((entry, idx) =>
idx === parentEntryIndex ? updatedParent : entry
),
};

if (onReferenceGraphChange) {
onReferenceGraphChange(withUpdatedReferenceGraph(referenceGraph, nextEdges));
}
}

if (fieldIds.length > 0) {
const resolvedTargets = fieldIds.flatMap((fieldId) => {
const field = contentType?.fields?.find((f) => hasFieldId(f) && f.id === fieldId);
Expand Down Expand Up @@ -527,16 +551,6 @@ export const MappingView = ({

onEntryBlockGraphChange(next);

if (isLinkedReference && onReferenceGraphChange) {
onReferenceGraphChange({
...referenceGraph,
edges: [
...(referenceGraph.edges ?? []),
{ from: tempId, to: params.referenceEntryId!, fieldId: refField?.id ?? '' },
],
});
}

closeEditModal();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { FieldSelectionDropdown } from './FieldSelectionDropdown';
export interface AddEntryFormState {
contentTypeId: string;
isReference: boolean | null;
/** Parent entry tempId when linking as a child reference. */
referenceEntryId: string;
/** Reference field on the parent content type. */
referenceFieldId: string;
selectedFieldIds: string[];
}
Expand All @@ -23,6 +25,7 @@ export const INITIAL_ADD_ENTRY_FORM_STATE: AddEntryFormState = {
export interface ExistingEntryOption {
tempId: string;
label: string;
contentTypeId: string;
}

const getReferenceFieldOptions = (
Expand All @@ -38,17 +41,43 @@ const getReferenceFieldOptions = (
});
};

const getParentContentTypeId = (
state: AddEntryFormState,
existingEntries: ExistingEntryOption[]
): string =>
existingEntries.find((entry) => entry.tempId === state.referenceEntryId)?.contentTypeId ?? '';

/** Existing entries whose content type has at least one Entry reference field. */
export const getParentEntriesThatAcceptChildren = (
contentTypes: WorkflowContentType[],
existingEntries: ExistingEntryOption[]
): ExistingEntryOption[] =>
existingEntries.filter(
(entry) => getReferenceFieldOptions(contentTypes, entry.contentTypeId).length > 0
);

/** True when at least one existing entry's content type can accept a child reference. */
export const canLinkAsChildReference = (
contentTypes: WorkflowContentType[],
existingEntries: ExistingEntryOption[]
): boolean => getParentEntriesThatAcceptChildren(contentTypes, existingEntries).length > 0;

/** Returns true when the form lacks enough input to save. */
export const isAddEntrySaveDisabled = (
state: AddEntryFormState,
contentTypes: WorkflowContentType[]
contentTypes: WorkflowContentType[],
existingEntries: ExistingEntryOption[] = []
): boolean => {
if (!state.contentTypeId) return true;
if (state.isReference === null) return true;
const canBeReference = canLinkAsChildReference(contentTypes, existingEntries);
// Reference Yes/No is hidden when no parent can accept a child — treat as No.
if (canBeReference && state.isReference === null) return true;
if (state.isReference) {
const referenceFieldOptions = getReferenceFieldOptions(contentTypes, state.contentTypeId);
if (referenceFieldOptions.length === 0) return true;
// Child link lives on the parent — require a parent, then its reference field when ambiguous.
if (!state.referenceEntryId) return true;
const parentContentTypeId = getParentContentTypeId(state, existingEntries);
const referenceFieldOptions = getReferenceFieldOptions(contentTypes, parentContentTypeId);
if (referenceFieldOptions.length === 0) return true;
if (referenceFieldOptions.length > 1 && !state.referenceFieldId) return true;
}
return state.selectedFieldIds.length === 0;
Expand All @@ -57,9 +86,11 @@ export const isAddEntrySaveDisabled = (
/** Maps form state to the payload expected by onAddEntry. */
export const toAddEntryFormParams = (
state: AddEntryFormState,
contentTypes: WorkflowContentType[]
contentTypes: WorkflowContentType[],
existingEntries: ExistingEntryOption[] = []
): AddEntryFormParams => {
const referenceFieldOptions = getReferenceFieldOptions(contentTypes, state.contentTypeId);
const parentContentTypeId = getParentContentTypeId(state, existingEntries);
const referenceFieldOptions = getReferenceFieldOptions(contentTypes, parentContentTypeId);
return {
contentTypeId: state.contentTypeId,
isReference: state.isReference ?? false,
Expand Down Expand Up @@ -96,12 +127,22 @@ export const AddEntryForm = ({
() => buildFieldOptionsForContentType(selectedContentType),
[selectedContentType]
);
const parentContentTypeId = useMemo(
() =>
existingEntries.find((entry) => entry.tempId === state.referenceEntryId)?.contentTypeId ?? '',
[existingEntries, state.referenceEntryId]
);
const referenceFieldOptions = useMemo(
() => getReferenceFieldOptions(contentTypes, state.contentTypeId),
[contentTypes, state.contentTypeId]
() => getReferenceFieldOptions(contentTypes, parentContentTypeId),
[contentTypes, parentContentTypeId]
);
const showReferenceFieldSelect =
Boolean(state.referenceEntryId) && referenceFieldOptions.length > 1;
const parentEntryOptions = useMemo(
() => getParentEntriesThatAcceptChildren(contentTypes, existingEntries),
[contentTypes, existingEntries]
);
const showReferenceFieldSelect = referenceFieldOptions.length > 1;
const canBeReference = referenceFieldOptions.length > 0;
const canBeReference = parentEntryOptions.length > 0;
const hasContentType = Boolean(state.contentTypeId);

const handleContentTypeChange = (contentTypeId: string) => {
Expand All @@ -126,6 +167,13 @@ export const AddEntryForm = ({
});
};

const handleParentEntryChange = (referenceEntryId: string) => {
onChange({
referenceEntryId,
referenceFieldId: '',
});
};

return (
<Flex flexDirection="column" gap="spacingS">
<Text as="p" fontWeight="fontWeightDemiBold">
Expand All @@ -150,39 +198,44 @@ export const AddEntryForm = ({

{hasContentType && (
<>
<FormControl marginBottom="none">
<FormControl.Label>Should this entry be a reference entry?</FormControl.Label>
<Flex flexDirection="column" gap="spacingXs">
<Radio
id="ref-yes"
name="is-reference"
value="yes"
isChecked={state.isReference === true}
isDisabled={!canBeReference}
onChange={() => handleReferenceChange(true)}>
Yes
</Radio>
<Radio
id="ref-no"
name="is-reference"
value="no"
isChecked={state.isReference === false}
onChange={() => handleReferenceChange(false)}>
No
</Radio>
</Flex>
</FormControl>
{canBeReference && (
<FormControl marginBottom="none">
<FormControl.Label>
Should this new entry be a reference of an existing entry?
</FormControl.Label>
<Flex flexDirection="column" gap="spacingXs">
<Radio
id="ref-yes"
name="is-reference"
value="yes"
isChecked={state.isReference === true}
onChange={() => handleReferenceChange(true)}>
Yes
</Radio>
<Radio
id="ref-no"
name="is-reference"
value="no"
isChecked={state.isReference === false}
onChange={() => handleReferenceChange(false)}>
No
</Radio>
</Flex>
</FormControl>
)}

{state.isReference === true && (
{canBeReference && state.isReference === true && (
<FormControl marginBottom="none">
<FormControl.Label>Select the entry this should reference</FormControl.Label>
<FormControl.Label>
Which existing entry should this new entry be a reference to?
</FormControl.Label>
<Select
value={state.referenceEntryId}
onChange={(e) => onChange({ referenceEntryId: e.target.value })}>
onChange={(e) => handleParentEntryChange(e.target.value)}>
<Select.Option value="" isDisabled>
Select an entry
</Select.Option>
{existingEntries.map((entry) => (
{parentEntryOptions.map((entry) => (
<Select.Option key={entry.tempId} value={entry.tempId}>
{entry.label}
</Select.Option>
Expand All @@ -191,9 +244,11 @@ export const AddEntryForm = ({
</FormControl>
)}

{state.isReference === true && showReferenceFieldSelect && (
{canBeReference && state.isReference === true && showReferenceFieldSelect && (
<FormControl marginBottom="none">
<FormControl.Label>Which field should connect to this reference?</FormControl.Label>
<FormControl.Label>
Which field on the parent should link to this entry?
</FormControl.Label>
<Select
value={state.referenceFieldId}
onChange={(e) => onChange({ referenceFieldId: e.target.value })}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ export const EditModal = ({

const handleAddEntrySave = () => {
if (!addEntryFormState) return;
onAddEntry?.(toAddEntryFormParams(addEntryFormState, contentTypes));
onAddEntry?.(toAddEntryFormParams(addEntryFormState, contentTypes, existingEntries));
setAddEntryFormState(null);
};

const isAddEntryFormSaveDisabled =
!addEntryFormState || isAddEntrySaveDisabled(addEntryFormState, contentTypes);
!addEntryFormState || isAddEntrySaveDisabled(addEntryFormState, contentTypes, existingEntries);

const previewSectionTitle = viewModel.previewSectionTitle ?? 'Selected content';
const previewText = (viewModel.contentPreview ?? viewModel.selectedText).trim();
Expand Down
Loading
Loading