Skip to content

Commit

Permalink
fix: clear field error in Editor after the field value is changed (#7216
Browse files Browse the repository at this point in the history
)

* fix: remove field error in Editor after the field is changed

* fix: add px to control hint

* fix: add ControlTopbar for correct error display at widget

* chore: update after tests

* chore: update caniuse-lite

* fix: child list element should have key prop

* chore: update formatting

* fix: add ControlTopbar margin and padding

* fix: add changes to the tests

* fix: remove the comments

---------

Co-authored-by: Martin Jagodic <[email protected]>
Co-authored-by: Anze Demsar <[email protected]>
  • Loading branch information
3 people authored Nov 12, 2024
1 parent 1d0cd61 commit d9655ea
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 42 deletions.
3 changes: 2 additions & 1 deletion cypress/e2e/markdown_widget_code_block_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ function codeBlock(content) {
<div>
<div></div>
<div>
<div><label>Code Block</label>
<div>
<div><label>Code Block</label></div>
<div><button><span><svg>
<path></path>
</svg></span></button>
Expand Down
23 changes: 23 additions & 0 deletions cypress/utils/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function assertColorOn(cssProperty, color, opts) {
} else {
(opts.scope ? opts.scope : cy)
.contains('label', opts.label)
.parents()
.next()
.should(assertion);
}
Expand Down Expand Up @@ -518,23 +519,31 @@ function validateNestedListFields() {
cy.contains('button', 'hotel locations').click();
cy.contains('button', 'cities').click();
cy.contains('label', 'City')
.parents()
.next()
.first()
.type('Washington DC');
cy.contains('label', 'Number of Hotels in City')
.parents()
.next()
.first()
.type('5');
cy.contains('button', 'city locations').click();

// add second city list item
cy.contains('button', 'cities').click();
cy.contains('label', 'Cities')
.parents()
.next()
.first()
.find('div[class*=SortableListItem]')
.eq(2)
.as('secondCitiesListControl');
cy.get('@secondCitiesListControl')
.contains('label', 'City')
.parents()
.next()
.first()
.type('Boston');
cy.get('@secondCitiesListControl')
.contains('button', 'city locations')
Expand All @@ -561,21 +570,25 @@ function validateNestedListFields() {

// list control aliases
cy.contains('label', 'Hotel Locations')
.parents()
.next()
.find('div[class*=SortableListItem]')
.first()
.as('hotelLocationsListControl');
cy.contains('label', 'Cities')
.parents()
.next()
.find('div[class*=SortableListItem]')
.eq(0)
.as('firstCitiesListControl');
cy.contains('label', 'City Locations')
.parents()
.next()
.find('div[class*=SortableListItem]')
.eq(0)
.as('firstCityLocationsListControl');
cy.contains('label', 'Cities')
.parents()
.next()
.find('div[class*=SortableListItem]')
.eq(3)
Expand All @@ -589,7 +602,9 @@ function validateNestedListFields() {
assertListControlErrorStatus([colorError, colorError], '@secondCityLocationsListControl');

cy.contains('label', 'Hotel Name')
.parents()
.next()
.first()
.type('The Ritz Carlton');
cy.contains('button', 'Save').click();
assertNotification(notifications.error.missingField);
Expand All @@ -598,12 +613,20 @@ function validateNestedListFields() {
// fill out rest of form and save
cy.get('@secondCitiesListControl')
.contains('label', 'Number of Hotels in City')
.parents()
.next()
.first()
.type(3);
cy.get('@secondCitiesListControl')
.contains('label', 'Hotel Name')
.parents()
.next()
.first()
.type('Grand Hyatt');
cy.contains('label', 'Country')
.parents()
.next()
.first()
.type('United States');
flushClockAndSave();
assertNotification(notifications.saved);
Expand Down
17 changes: 9 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions packages/decap-cms-core/src/actions/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,11 @@ export function changeDraftFieldValidation(
};
}

export function clearFieldErrors() {
return { type: DRAFT_CLEAR_ERRORS };
export function clearFieldErrors(uniqueFieldId: string) {
return {
type: DRAFT_CLEAR_ERRORS,
payload: { uniqueFieldId },
};
}

export function localBackupRetrieved(entry: EntryValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const styleStrings = {
disabled: `
pointer-events: none;
opacity: 0.5;
background: #ccc;
`,
hidden: `
visibility: hidden;
Expand All @@ -77,18 +76,26 @@ const ControlContainer = styled.div`
}
`;

const ControlTopbar = styled.div`
display: flex;
justify-content: space-between;
gap: 20px;
align-items: end;
`;
const ControlErrorsList = styled.ul`
list-style-type: none;
font-size: 12px;
color: ${colors.errorText};
margin-bottom: 8px;
text-align: right;
text-transform: uppercase;
font-weight: 600;
margin: 0;
padding: 2px 0 3px;
`;

export const ControlHint = styled.p`
margin-bottom: 0;
padding: 3px 0;
padding: 6px 0 0;
font-size: 12px;
color: ${props =>
props.error ? colors.errorText : props.active ? colors.active : colors.controlLabel};
Expand Down Expand Up @@ -238,28 +245,30 @@ class EditorControl extends React.Component {
${isHidden && styleStrings.hidden};
`}
>
{widget.globalStyles && <Global styles={coreCss`${widget.globalStyles}`} />}
{errors && (
<ControlErrorsList>
{errors.map(
error =>
error.message &&
typeof error.message === 'string' && (
<li key={error.message.trim().replace(/[^a-z0-9]+/gi, '-')}>
{error.message}
</li>
),
)}
</ControlErrorsList>
)}
<LabelComponent
field={field}
isActive={isSelected || this.state.styleActive}
hasErrors={hasErrors}
uniqueFieldId={this.uniqueFieldId}
isFieldOptional={isFieldOptional}
t={t}
/>
<ControlTopbar>
{widget.globalStyles && <Global styles={coreCss`${widget.globalStyles}`} />}
<LabelComponent
field={field}
isActive={isSelected || this.state.styleActive}
hasErrors={hasErrors}
uniqueFieldId={this.uniqueFieldId}
isFieldOptional={isFieldOptional}
t={t}
/>
{errors && (
<ControlErrorsList>
{errors.map(
error =>
error.message &&
typeof error.message === 'string' && (
<li key={error.message.trim().replace(/[^a-z0-9]+/gi, '-')}>
{error.message}
</li>
),
)}
</ControlErrorsList>
)}
</ControlTopbar>
<Widget
classNameWrapper={cx(
css`
Expand Down Expand Up @@ -302,7 +311,10 @@ class EditorControl extends React.Component {
value={value}
mediaPaths={mediaPaths}
metadata={metadata}
onChange={(newValue, newMetadata) => onChange(field, newValue, newMetadata)}
onChange={(newValue, newMetadata) => {
onChange(field, newValue, newMetadata);
clearFieldErrors(this.uniqueFieldId); // Видаляємо помилки лише для цього поля
}}
onValidate={onValidate && partial(onValidate, this.uniqueFieldId)}
onOpenMediaLibrary={openMediaLibrary}
onClearMediaControl={clearMediaControl}
Expand Down
17 changes: 13 additions & 4 deletions packages/decap-cms-core/src/components/Editor/EditorToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -594,11 +594,20 @@ export class EditorToolbar extends React.Component {
</SaveButton>,
currentStatus
? [
this.renderWorkflowStatusControls(),
this.renderNewEntryWorkflowPublishControls({ canCreate, canPublish }),
<React.Fragment key="workflow-status-controls">
{this.renderWorkflowStatusControls()}
{!hasChanged && this.renderNewEntryWorkflowPublishControls({ canCreate, canPublish })}
</React.Fragment>,
]
: !isNewEntry &&
this.renderExistingEntryWorkflowPublishControls({ canCreate, canPublish, canDelete }),
: !isNewEntry && (
<React.Fragment key="existing-entry-workflow-publish-controls">
{this.renderExistingEntryWorkflowPublishControls({
canCreate,
canPublish,
canDelete,
})}
</React.Fragment>
),
(!showDelete || useOpenAuthoring) && !hasUnpublishedChanges && !isModification ? null : (
<DeleteButton
key="delete-button"
Expand Down
3 changes: 2 additions & 1 deletion packages/decap-cms-core/src/reducers/entryDraft.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ function entryDraftReducer(state = Map(), action) {
}

case DRAFT_CLEAR_ERRORS: {
return state.set('fieldsErrors', Map());
const { uniqueFieldId } = action.payload;
return state.deleteIn(['fieldsErrors', uniqueFieldId]);
}

case ENTRY_PERSIST_REQUEST:
Expand Down

0 comments on commit d9655ea

Please sign in to comment.