mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-28 05:14:12 +03:00
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:
parent
8bc653802d
commit
223a1d7767
@ -43,6 +43,7 @@ export interface ModalProps {
|
|||||||
dirty?: boolean;
|
dirty?: boolean;
|
||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
formSheet?: boolean;
|
formSheet?: boolean;
|
||||||
|
enableCMDS?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const topLevelBackdropClasses = 'bg-[rgba(98,109,121,0.2)] backdrop-blur-[3px]';
|
export const topLevelBackdropClasses = 'bg-[rgba(98,109,121,0.2)] backdrop-blur-[3px]';
|
||||||
@ -74,7 +75,8 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
scrolling = true,
|
scrolling = true,
|
||||||
dirty = false,
|
dirty = false,
|
||||||
animate = true,
|
animate = true,
|
||||||
formSheet = false
|
formSheet = false,
|
||||||
|
enableCMDS = true
|
||||||
}) => {
|
}) => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const {setGlobalDirtyState} = useGlobalDirtyState();
|
const {setGlobalDirtyState} = useGlobalDirtyState();
|
||||||
@ -116,6 +118,21 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
return () => clearTimeout(timeout);
|
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 buttons: ButtonProps[] = [];
|
||||||
|
|
||||||
let footerClasses, contentClasses;
|
let footerClasses, contentClasses;
|
||||||
|
@ -39,6 +39,7 @@ export interface PreviewModalProps {
|
|||||||
sidebarHeader?: React.ReactNode;
|
sidebarHeader?: React.ReactNode;
|
||||||
sidebarPadding?: boolean;
|
sidebarPadding?: boolean;
|
||||||
sidebarContentClasses?: string;
|
sidebarContentClasses?: string;
|
||||||
|
enableCMDS?: boolean;
|
||||||
|
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
onOk?: () => void;
|
onOk?: () => void;
|
||||||
@ -73,6 +74,7 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
|
|||||||
sidebarHeader,
|
sidebarHeader,
|
||||||
sidebarPadding = true,
|
sidebarPadding = true,
|
||||||
sidebarContentClasses,
|
sidebarContentClasses,
|
||||||
|
enableCMDS = true,
|
||||||
|
|
||||||
onCancel,
|
onCancel,
|
||||||
onOk,
|
onOk,
|
||||||
@ -88,6 +90,21 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
|
|||||||
setGlobalDirtyState(dirty);
|
setGlobalDirtyState(dirty);
|
||||||
}, [dirty, setGlobalDirtyState]);
|
}, [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');
|
const [view, setView] = useState('desktop');
|
||||||
|
|
||||||
if (view === 'mobile' && deviceSelector) {
|
if (view === 'mobile' && deviceSelector) {
|
||||||
|
@ -39,6 +39,7 @@ interface SettingGroupProps {
|
|||||||
onEditingChange?: (isEditing: boolean) => void
|
onEditingChange?: (isEditing: boolean) => void
|
||||||
onSave?: () => void
|
onSave?: () => void
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
|
enableCMDS?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingGroup: React.FC<SettingGroupProps> = ({
|
const SettingGroup: React.FC<SettingGroupProps> = ({
|
||||||
@ -59,7 +60,8 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||||||
styles,
|
styles,
|
||||||
onEditingChange,
|
onEditingChange,
|
||||||
onSave,
|
onSave,
|
||||||
onCancel
|
onCancel,
|
||||||
|
enableCMDS = true
|
||||||
}) => {
|
}) => {
|
||||||
const {checkVisible} = useSearch();
|
const {checkVisible} = useSearch();
|
||||||
const {route} = useRouting();
|
const {route} = useRouting();
|
||||||
@ -148,6 +150,21 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||||||
}
|
}
|
||||||
}, [highlight]);
|
}, [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(
|
const containerClasses = clsx(
|
||||||
'relative flex-col gap-6 rounded',
|
'relative flex-col gap-6 rounded',
|
||||||
border && 'border p-5 md:p-7',
|
border && 'border p-5 md:p-7',
|
||||||
|
@ -442,7 +442,6 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
|
|||||||
initialState: newsletter,
|
initialState: newsletter,
|
||||||
onSave: async () => {
|
onSave: async () => {
|
||||||
const {newsletters, meta} = await editNewsletter(formState);
|
const {newsletters, meta} = await editNewsletter(formState);
|
||||||
|
|
||||||
if (meta?.sent_email_verification) {
|
if (meta?.sent_email_verification) {
|
||||||
NiceModal.show(ConfirmationModal, {
|
NiceModal.show(ConfirmationModal, {
|
||||||
title: 'Confirm newsletter email address',
|
title: 'Confirm newsletter email address',
|
||||||
@ -458,8 +457,6 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
|
|||||||
modal.remove();
|
modal.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
modal.remove();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSaveError: handleError,
|
onSaveError: handleError,
|
||||||
@ -491,10 +488,11 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
|
|||||||
|
|
||||||
return <PreviewModalContent
|
return <PreviewModalContent
|
||||||
afterClose={() => updateRoute('newsletters')}
|
afterClose={() => updateRoute('newsletters')}
|
||||||
|
buttonsDisabled={saveState === 'saving'}
|
||||||
cancelLabel='Close'
|
cancelLabel='Close'
|
||||||
deviceSelector={false}
|
deviceSelector={false}
|
||||||
dirty={saveState === 'unsaved'}
|
dirty={saveState === 'unsaved'}
|
||||||
okLabel='Save'
|
okLabel={saveState === 'saved' ? 'Saved' : (saveState === 'saving' ? 'Saving...' : 'Save')}
|
||||||
preview={preview}
|
preview={preview}
|
||||||
previewBgColor={'grey'}
|
previewBgColor={'grey'}
|
||||||
previewToolbar={false}
|
previewToolbar={false}
|
||||||
@ -503,9 +501,7 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
|
|||||||
testId='newsletter-modal'
|
testId='newsletter-modal'
|
||||||
title='Newsletter'
|
title='Newsletter'
|
||||||
onOk={async () => {
|
onOk={async () => {
|
||||||
if (await handleSave()) {
|
if (!(await handleSave())) {
|
||||||
updateRoute('newsletters');
|
|
||||||
} else {
|
|
||||||
showToast({
|
showToast({
|
||||||
type: 'pageError',
|
type: 'pageError',
|
||||||
message: 'Can\'t save newsletter, please double check that you\'ve filled all mandatory fields.'
|
message: 'Can\'t save newsletter, please double check that you\'ve filled all mandatory fields.'
|
||||||
|
@ -115,7 +115,6 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
|
|
||||||
const AnnouncementBarModal: React.FC = () => {
|
const AnnouncementBarModal: React.FC = () => {
|
||||||
const {siteData} = useGlobalData();
|
const {siteData} = useGlobalData();
|
||||||
const modal = NiceModal.useModal();
|
|
||||||
const {localSettings, updateSetting, handleSave} = useSettingGroup();
|
const {localSettings, updateSetting, handleSave} = useSettingGroup();
|
||||||
const [announcementContent] = getSettingValues<string>(localSettings, ['announcement_content']);
|
const [announcementContent] = getSettingValues<string>(localSettings, ['announcement_content']);
|
||||||
const [accentColor] = getSettingValues<string>(localSettings, ['accent_color']);
|
const [accentColor] = getSettingValues<string>(localSettings, ['accent_color']);
|
||||||
@ -207,11 +206,10 @@ const AnnouncementBarModal: React.FC = () => {
|
|||||||
|
|
||||||
return <PreviewModalContent
|
return <PreviewModalContent
|
||||||
afterClose={() => {
|
afterClose={() => {
|
||||||
modal.remove();
|
|
||||||
updateRoute('announcement-bar');
|
updateRoute('announcement-bar');
|
||||||
}}
|
}}
|
||||||
cancelLabel='Close'
|
cancelLabel='Close'
|
||||||
deviceSelector={false}
|
deviceSelector={true}
|
||||||
dirty={false}
|
dirty={false}
|
||||||
okLabel='Save'
|
okLabel='Save'
|
||||||
preview={preview}
|
preview={preview}
|
||||||
@ -223,10 +221,8 @@ const AnnouncementBarModal: React.FC = () => {
|
|||||||
title='Announcement'
|
title='Announcement'
|
||||||
titleHeadingLevel={5}
|
titleHeadingLevel={5}
|
||||||
onOk={async () => {
|
onOk={async () => {
|
||||||
if (await handleSave()) {
|
if (!(await handleSave())) {
|
||||||
modal.remove();
|
|
||||||
updateRoute('announcement-bar');
|
updateRoute('announcement-bar');
|
||||||
} else {
|
|
||||||
showToast({
|
showToast({
|
||||||
type: 'pageError',
|
type: 'pageError',
|
||||||
message: 'An error occurred while saving your changes. Please try again.'
|
message: 'An error occurred while saving your changes. Please try again.'
|
||||||
|
@ -66,17 +66,15 @@ const AnnouncementBarPreview: React.FC<AnnouncementBarSettings> = ({announcement
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-screen w-screen overflow-hidden'>
|
<IframeBuffering
|
||||||
<IframeBuffering
|
addDelay={true}
|
||||||
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]"
|
||||||
className="absolute left-0 top-0 h-full w-full"
|
generateContent={injectContentIntoIframe}
|
||||||
generateContent={injectContentIntoIframe}
|
height='100%'
|
||||||
height='100%'
|
parentClassName="relative h-full w-full"
|
||||||
parentClassName="relative h-full w-full"
|
testId='announcement-bar-preview-iframe'
|
||||||
testId='announcement-bar-preview-iframe'
|
width='100%'
|
||||||
width='100%'
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,7 +98,7 @@ export default memo(AnnouncementBarPreview, (prevProps, nextProps) => {
|
|||||||
if (prevProps.announcementBackgroundColor !== nextProps.announcementBackgroundColor) {
|
if (prevProps.announcementBackgroundColor !== nextProps.announcementBackgroundColor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if announcementContent changed
|
// Check if announcementContent changed
|
||||||
if (prevProps.announcementContent !== nextProps.announcementContent) {
|
if (prevProps.announcementContent !== nextProps.announcementContent) {
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
Reference in New Issue
Block a user