From 223a1d7767899bd9ae5cae98ab46f286b4106614 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 4 Oct 2023 13:36:06 +0200 Subject: [PATCH] Adding CMD/CTRL+S to AdminX (#18466) refs. https://github.com/TryGhost/Product/issues/3949 - it wasn't possible to save setting values in AdminX with CMD/CTR+S --- .../src/admin-x-ds/global/modal/Modal.tsx | 19 +++++++++++++++- .../admin-x-ds/global/modal/PreviewModal.tsx | 17 ++++++++++++++ .../src/admin-x-ds/settings/SettingGroup.tsx | 19 +++++++++++++++- .../newsletters/NewsletterDetailModal.tsx | 10 +++------ .../settings/site/AnnouncementBarModal.tsx | 8 ++----- .../AnnouncementBarPreview.tsx | 22 +++++++++---------- 6 files changed, 68 insertions(+), 27 deletions(-) diff --git a/apps/admin-x-settings/src/admin-x-ds/global/modal/Modal.tsx b/apps/admin-x-settings/src/admin-x-ds/global/modal/Modal.tsx index 8596e2dbf1..57cd647516 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/modal/Modal.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/modal/Modal.tsx @@ -43,6 +43,7 @@ export interface ModalProps { dirty?: boolean; animate?: boolean; formSheet?: boolean; + enableCMDS?: boolean; } export const topLevelBackdropClasses = 'bg-[rgba(98,109,121,0.2)] backdrop-blur-[3px]'; @@ -74,7 +75,8 @@ const Modal: React.FC = ({ scrolling = true, dirty = false, animate = true, - formSheet = false + formSheet = false, + enableCMDS = true }) => { const modal = useModal(); const {setGlobalDirtyState} = useGlobalDirtyState(); @@ -116,6 +118,21 @@ const Modal: React.FC = ({ return () => clearTimeout(timeout); }, []); + useEffect(() => { + const handleCMDS = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 's') { + e.preventDefault(); + onOk!(); + } + }; + if (enableCMDS) { + window.addEventListener('keydown', handleCMDS); + return () => { + window.removeEventListener('keydown', handleCMDS); + }; + } + }); + let buttons: ButtonProps[] = []; let footerClasses, contentClasses; diff --git a/apps/admin-x-settings/src/admin-x-ds/global/modal/PreviewModal.tsx b/apps/admin-x-settings/src/admin-x-ds/global/modal/PreviewModal.tsx index 55cedf63a8..95e8f00a4b 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/modal/PreviewModal.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/modal/PreviewModal.tsx @@ -39,6 +39,7 @@ export interface PreviewModalProps { sidebarHeader?: React.ReactNode; sidebarPadding?: boolean; sidebarContentClasses?: string; + enableCMDS?: boolean; onCancel?: () => void; onOk?: () => void; @@ -73,6 +74,7 @@ export const PreviewModalContent: React.FC = ({ sidebarHeader, sidebarPadding = true, sidebarContentClasses, + enableCMDS = true, onCancel, onOk, @@ -88,6 +90,21 @@ export const PreviewModalContent: React.FC = ({ setGlobalDirtyState(dirty); }, [dirty, setGlobalDirtyState]); + useEffect(() => { + const handleCMDS = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 's') { + e.preventDefault(); + onOk!(); + } + }; + if (enableCMDS) { + window.addEventListener('keydown', handleCMDS); + return () => { + window.removeEventListener('keydown', handleCMDS); + }; + } + }); + const [view, setView] = useState('desktop'); if (view === 'mobile' && deviceSelector) { diff --git a/apps/admin-x-settings/src/admin-x-ds/settings/SettingGroup.tsx b/apps/admin-x-settings/src/admin-x-ds/settings/SettingGroup.tsx index 88cc90bf8f..253db27792 100644 --- a/apps/admin-x-settings/src/admin-x-ds/settings/SettingGroup.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/settings/SettingGroup.tsx @@ -39,6 +39,7 @@ interface SettingGroupProps { onEditingChange?: (isEditing: boolean) => void onSave?: () => void onCancel?: () => void + enableCMDS?: boolean } const SettingGroup: React.FC = ({ @@ -59,7 +60,8 @@ const SettingGroup: React.FC = ({ styles, onEditingChange, onSave, - onCancel + onCancel, + enableCMDS = true }) => { const {checkVisible} = useSearch(); const {route} = useRouting(); @@ -148,6 +150,21 @@ const SettingGroup: React.FC = ({ } }, [highlight]); + useEffect(() => { + const handleCMDS = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 's') { + e.preventDefault(); + handleSave(); + } + }; + if (enableCMDS) { + window.addEventListener('keydown', handleCMDS); + return () => { + window.removeEventListener('keydown', handleCMDS); + }; + } + }); + const containerClasses = clsx( 'relative flex-col gap-6 rounded', border && 'border p-5 md:p-7', diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx index 85feb03cad..4cd85980bb 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx @@ -442,7 +442,6 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b initialState: newsletter, onSave: async () => { const {newsletters, meta} = await editNewsletter(formState); - if (meta?.sent_email_verification) { NiceModal.show(ConfirmationModal, { title: 'Confirm newsletter email address', @@ -458,8 +457,6 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b modal.remove(); } }); - } else { - modal.remove(); } }, onSaveError: handleError, @@ -491,10 +488,11 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b return updateRoute('newsletters')} + buttonsDisabled={saveState === 'saving'} cancelLabel='Close' deviceSelector={false} dirty={saveState === 'unsaved'} - okLabel='Save' + okLabel={saveState === 'saved' ? 'Saved' : (saveState === 'saving' ? 'Saving...' : 'Save')} preview={preview} previewBgColor={'grey'} previewToolbar={false} @@ -503,9 +501,7 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b testId='newsletter-modal' title='Newsletter' onOk={async () => { - if (await handleSave()) { - updateRoute('newsletters'); - } else { + if (!(await handleSave())) { showToast({ type: 'pageError', message: 'Can\'t save newsletter, please double check that you\'ve filled all mandatory fields.' diff --git a/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx b/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx index f66fa6e2df..abbb19cecd 100644 --- a/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/AnnouncementBarModal.tsx @@ -115,7 +115,6 @@ const Sidebar: React.FC = ({ const AnnouncementBarModal: React.FC = () => { const {siteData} = useGlobalData(); - const modal = NiceModal.useModal(); const {localSettings, updateSetting, handleSave} = useSettingGroup(); const [announcementContent] = getSettingValues(localSettings, ['announcement_content']); const [accentColor] = getSettingValues(localSettings, ['accent_color']); @@ -207,11 +206,10 @@ const AnnouncementBarModal: React.FC = () => { return { - modal.remove(); updateRoute('announcement-bar'); }} cancelLabel='Close' - deviceSelector={false} + deviceSelector={true} dirty={false} okLabel='Save' preview={preview} @@ -223,10 +221,8 @@ const AnnouncementBarModal: React.FC = () => { title='Announcement' titleHeadingLevel={5} onOk={async () => { - if (await handleSave()) { - modal.remove(); + if (!(await handleSave())) { updateRoute('announcement-bar'); - } else { showToast({ type: 'pageError', message: 'An error occurred while saving your changes. Please try again.' diff --git a/apps/admin-x-settings/src/components/settings/site/announcementBar/AnnouncementBarPreview.tsx b/apps/admin-x-settings/src/components/settings/site/announcementBar/AnnouncementBarPreview.tsx index f48e27306b..b2dc3f902d 100644 --- a/apps/admin-x-settings/src/components/settings/site/announcementBar/AnnouncementBarPreview.tsx +++ b/apps/admin-x-settings/src/components/settings/site/announcementBar/AnnouncementBarPreview.tsx @@ -66,17 +66,15 @@ const AnnouncementBarPreview: React.FC = ({announcement }; return ( -
- -
+ ); }; @@ -100,7 +98,7 @@ export default memo(AnnouncementBarPreview, (prevProps, nextProps) => { if (prevProps.announcementBackgroundColor !== nextProps.announcementBackgroundColor) { return false; } - + // Check if announcementContent changed if (prevProps.announcementContent !== nextProps.announcementContent) { return false;