mirror of
https://github.com/strapi/strapi.git
synced 2025-11-09 06:40:42 +00:00
enhancement: cmd+enter and ctrl+enter to save entry (#22311)
* enhancement: keyboard shortcuts to save entry * chore: add e2e test * chore: restore validate fn * fix: don't register cmd+s for arc browser * fix: remove cmd+s and ctrl+s
This commit is contained in:
parent
d6bba97c7e
commit
03640aa70e
@ -768,6 +768,139 @@ const UpdateAction: DocumentActionComponent = ({
|
|||||||
const setErrors = useForm('UpdateAction', (state) => state.setErrors);
|
const setErrors = useForm('UpdateAction', (state) => state.setErrors);
|
||||||
const resetForm = useForm('PublishAction', ({ resetForm }) => resetForm);
|
const resetForm = useForm('PublishAction', ({ resetForm }) => resetForm);
|
||||||
|
|
||||||
|
const handleUpdate = React.useCallback(async () => {
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!modified) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { errors } = await validate(true, {
|
||||||
|
status: 'draft',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
toggleNotification({
|
||||||
|
type: 'danger',
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'content-manager.validation.error',
|
||||||
|
defaultMessage:
|
||||||
|
'There are validation errors in your document. Please fix them before saving.',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCloning) {
|
||||||
|
const res = await clone(
|
||||||
|
{
|
||||||
|
model,
|
||||||
|
documentId: cloneMatch.params.origin!,
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
transformData(document)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('data' in res) {
|
||||||
|
navigate(
|
||||||
|
{
|
||||||
|
pathname: `../${res.data.documentId}`,
|
||||||
|
search: rawQuery,
|
||||||
|
},
|
||||||
|
{ relative: 'path' }
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
'error' in res &&
|
||||||
|
isBaseQueryError(res.error) &&
|
||||||
|
res.error.name === 'ValidationError'
|
||||||
|
) {
|
||||||
|
setErrors(formatValidationErrors(res.error));
|
||||||
|
}
|
||||||
|
} else if (documentId || collectionType === SINGLE_TYPES) {
|
||||||
|
const res = await update(
|
||||||
|
{
|
||||||
|
collectionType,
|
||||||
|
model,
|
||||||
|
documentId,
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
transformData(document)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('error' in res && isBaseQueryError(res.error) && res.error.name === 'ValidationError') {
|
||||||
|
setErrors(formatValidationErrors(res.error));
|
||||||
|
} else {
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const res = await create(
|
||||||
|
{
|
||||||
|
model,
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
transformData(document)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('data' in res && collectionType !== SINGLE_TYPES) {
|
||||||
|
navigate(
|
||||||
|
{
|
||||||
|
pathname: `../${res.data.documentId}`,
|
||||||
|
search: rawQuery,
|
||||||
|
},
|
||||||
|
{ replace: true, relative: 'path' }
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
'error' in res &&
|
||||||
|
isBaseQueryError(res.error) &&
|
||||||
|
res.error.name === 'ValidationError'
|
||||||
|
) {
|
||||||
|
setErrors(formatValidationErrors(res.error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
clone,
|
||||||
|
cloneMatch?.params.origin,
|
||||||
|
collectionType,
|
||||||
|
create,
|
||||||
|
document,
|
||||||
|
documentId,
|
||||||
|
formatMessage,
|
||||||
|
formatValidationErrors,
|
||||||
|
isCloning,
|
||||||
|
model,
|
||||||
|
modified,
|
||||||
|
navigate,
|
||||||
|
params,
|
||||||
|
rawQuery,
|
||||||
|
resetForm,
|
||||||
|
setErrors,
|
||||||
|
setSubmitting,
|
||||||
|
toggleNotification,
|
||||||
|
update,
|
||||||
|
validate,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Auto-save on CMD+S or CMD+Enter on macOS, and CTRL+S or CTRL+Enter on Windows/Linux
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [handleUpdate]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Disabled when:
|
* Disabled when:
|
||||||
@ -780,101 +913,7 @@ const UpdateAction: DocumentActionComponent = ({
|
|||||||
id: 'global.save',
|
id: 'global.save',
|
||||||
defaultMessage: 'Save',
|
defaultMessage: 'Save',
|
||||||
}),
|
}),
|
||||||
onClick: async () => {
|
onClick: handleUpdate,
|
||||||
setSubmitting(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { errors } = await validate(true, {
|
|
||||||
status: 'draft',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors) {
|
|
||||||
toggleNotification({
|
|
||||||
type: 'danger',
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'content-manager.validation.error',
|
|
||||||
defaultMessage:
|
|
||||||
'There are validation errors in your document. Please fix them before saving.',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCloning) {
|
|
||||||
const res = await clone(
|
|
||||||
{
|
|
||||||
model,
|
|
||||||
documentId: cloneMatch.params.origin!,
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
transformData(document)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ('data' in res) {
|
|
||||||
navigate(
|
|
||||||
{
|
|
||||||
pathname: `../${res.data.documentId}`,
|
|
||||||
search: rawQuery,
|
|
||||||
},
|
|
||||||
{ relative: 'path' }
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
'error' in res &&
|
|
||||||
isBaseQueryError(res.error) &&
|
|
||||||
res.error.name === 'ValidationError'
|
|
||||||
) {
|
|
||||||
setErrors(formatValidationErrors(res.error));
|
|
||||||
}
|
|
||||||
} else if (documentId || collectionType === SINGLE_TYPES) {
|
|
||||||
const res = await update(
|
|
||||||
{
|
|
||||||
collectionType,
|
|
||||||
model,
|
|
||||||
documentId,
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
transformData(document)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
'error' in res &&
|
|
||||||
isBaseQueryError(res.error) &&
|
|
||||||
res.error.name === 'ValidationError'
|
|
||||||
) {
|
|
||||||
setErrors(formatValidationErrors(res.error));
|
|
||||||
} else {
|
|
||||||
resetForm();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const res = await create(
|
|
||||||
{
|
|
||||||
model,
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
transformData(document)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ('data' in res && collectionType !== SINGLE_TYPES) {
|
|
||||||
navigate(
|
|
||||||
{
|
|
||||||
pathname: `../${res.data.documentId}`,
|
|
||||||
search: rawQuery,
|
|
||||||
},
|
|
||||||
{ replace: true, relative: 'path' }
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
'error' in res &&
|
|
||||||
isBaseQueryError(res.error) &&
|
|
||||||
res.error.name === 'ValidationError'
|
|
||||||
) {
|
|
||||||
setErrors(formatValidationErrors(res.error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setSubmitting(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -250,6 +250,11 @@ test.describe('Edit View', () => {
|
|||||||
await page.getByRole('button', { name: 'Save' }).click();
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
await findAndClose(page, 'Saved Document');
|
await findAndClose(page, 'Saved Document');
|
||||||
|
|
||||||
|
// Check that we can save with keyboard shortcuts
|
||||||
|
await page.getByRole('textbox', { name: 'title' }).fill('Being an American...');
|
||||||
|
await page.keyboard.press('Control+Enter');
|
||||||
|
await findAndClose(page, 'Saved Document');
|
||||||
|
|
||||||
await expect(page.getByRole('tab', { name: 'Draft' })).toHaveAttribute(
|
await expect(page.getByRole('tab', { name: 'Draft' })).toHaveAttribute(
|
||||||
'aria-selected',
|
'aria-selected',
|
||||||
'true'
|
'true'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user