mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 11:54:10 +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,22 +768,14 @@ const UpdateAction: DocumentActionComponent = ({
 | 
			
		||||
  const setErrors = useForm('UpdateAction', (state) => state.setErrors);
 | 
			
		||||
  const resetForm = useForm('PublishAction', ({ resetForm }) => resetForm);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    /**
 | 
			
		||||
     * Disabled when:
 | 
			
		||||
     * - the form is submitting
 | 
			
		||||
     * - the document is not modified & we're not cloning (you can save a clone entity straight away)
 | 
			
		||||
     * - the active tab is the published tab
 | 
			
		||||
     */
 | 
			
		||||
    disabled: isSubmitting || (!modified && !isCloning) || activeTab === 'published',
 | 
			
		||||
    label: formatMessage({
 | 
			
		||||
      id: 'global.save',
 | 
			
		||||
      defaultMessage: 'Save',
 | 
			
		||||
    }),
 | 
			
		||||
    onClick: async () => {
 | 
			
		||||
  const handleUpdate = React.useCallback(async () => {
 | 
			
		||||
    setSubmitting(true);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      if (!modified) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const { errors } = await validate(true, {
 | 
			
		||||
        status: 'draft',
 | 
			
		||||
      });
 | 
			
		||||
@ -837,11 +829,7 @@ const UpdateAction: DocumentActionComponent = ({
 | 
			
		||||
          transformData(document)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
          if (
 | 
			
		||||
            'error' in res &&
 | 
			
		||||
            isBaseQueryError(res.error) &&
 | 
			
		||||
            res.error.name === 'ValidationError'
 | 
			
		||||
          ) {
 | 
			
		||||
        if ('error' in res && isBaseQueryError(res.error) && res.error.name === 'ValidationError') {
 | 
			
		||||
          setErrors(formatValidationErrors(res.error));
 | 
			
		||||
        } else {
 | 
			
		||||
          resetForm();
 | 
			
		||||
@ -874,7 +862,58 @@ const UpdateAction: DocumentActionComponent = ({
 | 
			
		||||
    } 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 {
 | 
			
		||||
    /**
 | 
			
		||||
     * Disabled when:
 | 
			
		||||
     * - the form is submitting
 | 
			
		||||
     * - the document is not modified & we're not cloning (you can save a clone entity straight away)
 | 
			
		||||
     * - the active tab is the published tab
 | 
			
		||||
     */
 | 
			
		||||
    disabled: isSubmitting || (!modified && !isCloning) || activeTab === 'published',
 | 
			
		||||
    label: formatMessage({
 | 
			
		||||
      id: 'global.save',
 | 
			
		||||
      defaultMessage: 'Save',
 | 
			
		||||
    }),
 | 
			
		||||
    onClick: handleUpdate,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -250,6 +250,11 @@ test.describe('Edit View', () => {
 | 
			
		||||
      await page.getByRole('button', { name: 'Save' }).click();
 | 
			
		||||
      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(
 | 
			
		||||
        'aria-selected',
 | 
			
		||||
        'true'
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user