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
This commit is contained in:
Peter Zimon 2023-10-04 13:36:06 +02:00 committed by GitHub
parent 8bc653802d
commit 223a1d7767
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 27 deletions

View File

@ -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<ModalProps> = ({
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<ModalProps> = ({
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;

View File

@ -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<PreviewModalProps> = ({
sidebarHeader,
sidebarPadding = true,
sidebarContentClasses,
enableCMDS = true,
onCancel,
onOk,
@ -88,6 +90,21 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
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) {

View File

@ -39,6 +39,7 @@ interface SettingGroupProps {
onEditingChange?: (isEditing: boolean) => void
onSave?: () => void
onCancel?: () => void
enableCMDS?: boolean
}
const SettingGroup: React.FC<SettingGroupProps> = ({
@ -59,7 +60,8 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
styles,
onEditingChange,
onSave,
onCancel
onCancel,
enableCMDS = true
}) => {
const {checkVisible} = useSearch();
const {route} = useRouting();
@ -148,6 +150,21 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
}
}, [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',

View File

@ -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 <PreviewModalContent
afterClose={() => 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.'

View File

@ -115,7 +115,6 @@ const Sidebar: React.FC<SidebarProps> = ({
const AnnouncementBarModal: React.FC = () => {
const {siteData} = useGlobalData();
const modal = NiceModal.useModal();
const {localSettings, updateSetting, handleSave} = useSettingGroup();
const [announcementContent] = getSettingValues<string>(localSettings, ['announcement_content']);
const [accentColor] = getSettingValues<string>(localSettings, ['accent_color']);
@ -207,11 +206,10 @@ const AnnouncementBarModal: React.FC = () => {
return <PreviewModalContent
afterClose={() => {
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.'

View File

@ -66,17 +66,15 @@ const AnnouncementBarPreview: React.FC<AnnouncementBarSettings> = ({announcement
};
return (
<div className='h-screen w-screen overflow-hidden'>
<IframeBuffering
addDelay={true}
className="absolute left-0 top-0 h-full w-full"
generateContent={injectContentIntoIframe}
height='100%'
parentClassName="relative h-full w-full"
testId='announcement-bar-preview-iframe'
width='100%'
/>
</div>
<IframeBuffering
addDelay={true}
className="absolute h-[110%] w-[110%] origin-top-left scale-[.90909] max-[1600px]:h-[130%] max-[1600px]:w-[130%] max-[1600px]:scale-[.76923]"
generateContent={injectContentIntoIframe}
height='100%'
parentClassName="relative h-full w-full"
testId='announcement-bar-preview-iframe'
width='100%'
/>
);
};
@ -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;