mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Added install theme from URL in AdminX (#18435)
refs https://www.notion.so/ghost/AdminX-feedback-27fc7f549bbf4a53bfa2e7b6e5643963?p=6fc16caf7f0d42f2933e28e1519c3623&pm=s - added ability to install a theme directly from the Ghost Marketplace. - uses the existing URL pattern / route to ensure they remain compatible with the existing Ghost Marketplace. --- <!-- Leave the line below if you'd like GitHub Copilot to generate a summary from your commit --> <!-- copilot:summary --> ### <samp>🤖 Generated by Copilot at b3b6f1b</samp> This pull request enables users to install and activate themes from the Ghost Marketplace, a curated collection of themes for the Ghost platform. It adds a new route and modal component to handle the theme installation from a URL, and modifies the existing modal components to support the theme activation and confirmation. It affects the files `ThemeModal.tsx`, `RoutingProvider.tsx`, and `DesignAndThemeModal.tsx`.
This commit is contained in:
parent
965110f005
commit
514f36b4f2
3 changed files with 80 additions and 2 deletions
|
@ -37,6 +37,8 @@ export type RoutingModalProps = {
|
|||
const modalPaths: {[key: string]: ModalName} = {
|
||||
'design/edit/themes': 'DesignAndThemeModal',
|
||||
'design/edit': 'DesignAndThemeModal',
|
||||
// this is a special route, because it can install a theme directly from the Ghost Marketplace
|
||||
'design/change-theme/install': 'DesignAndThemeModal',
|
||||
'navigation/edit': 'NavigationModal',
|
||||
'users/invite': 'InviteUserModal',
|
||||
'users/show/:slug': 'UserDetailModal',
|
||||
|
|
|
@ -10,6 +10,17 @@ const DesignAndThemeModal: React.FC<RoutingModalProps> = ({pathName}) => {
|
|||
return <DesignModal />;
|
||||
} else if (pathName === 'design/edit/themes') {
|
||||
return <ChangeThemeModal />;
|
||||
} else if (pathName === 'design/change-theme/install') {
|
||||
const url = window.location.href;
|
||||
const fragment = url.split('#')[1];
|
||||
const queryParams = fragment.split('?')[1];
|
||||
|
||||
const searchParams = new URLSearchParams(queryParams);
|
||||
|
||||
const ref = searchParams.get('ref') || null;
|
||||
const source = searchParams.get('source') || null;
|
||||
|
||||
return <ChangeThemeModal source={source} themeRef={ref} />;
|
||||
} else {
|
||||
modal.remove();
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ import ThemePreview from './theme/ThemePreview';
|
|||
import useHandleError from '../../../utils/api/handleError';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {HostLimitError, useLimiter} from '../../../hooks/useLimiter';
|
||||
import {InstalledTheme, Theme, ThemesInstallResponseType, useBrowseThemes, useInstallTheme, useUploadTheme} from '../../../api/themes';
|
||||
import {InstalledTheme, Theme, ThemesInstallResponseType, useActivateTheme, useBrowseThemes, useInstallTheme, useUploadTheme} from '../../../api/themes';
|
||||
import {OfficialTheme} from '../../providers/ServiceProvider';
|
||||
import {showToast} from '../../../admin-x-ds/global/Toast';
|
||||
|
||||
interface ThemeToolbarProps {
|
||||
selectedTheme: OfficialTheme|null;
|
||||
|
@ -270,22 +271,86 @@ const ThemeModalContent: React.FC<ThemeModalContentProps> = ({
|
|||
return null;
|
||||
};
|
||||
|
||||
const ChangeThemeModal = () => {
|
||||
type ChangeThemeModalProps = {
|
||||
source?: string | null;
|
||||
themeRef?: string | null;
|
||||
};
|
||||
|
||||
const ChangeThemeModal: React.FC<ChangeThemeModalProps> = ({source, themeRef}) => {
|
||||
const [currentTab, setCurrentTab] = useState('official');
|
||||
const [selectedTheme, setSelectedTheme] = useState<OfficialTheme|null>(null);
|
||||
const [previewMode, setPreviewMode] = useState('desktop');
|
||||
const [isInstalling, setInstalling] = useState(false);
|
||||
const [installedFromMarketplace, setInstalledFromMarketplace] = useState(false);
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
const modal = useModal();
|
||||
const {data: {themes} = {}} = useBrowseThemes();
|
||||
const {mutateAsync: installTheme} = useInstallTheme();
|
||||
const {mutateAsync: activateTheme} = useActivateTheme();
|
||||
const handleError = useHandleError();
|
||||
|
||||
const onSelectTheme = (theme: OfficialTheme|null) => {
|
||||
setSelectedTheme(theme);
|
||||
};
|
||||
|
||||
// probably not the best place to handle the logic here, something for cleanup.
|
||||
useEffect(() => {
|
||||
// this grabs the theme ref from the url and installs it
|
||||
if (source && themeRef && !installedFromMarketplace) {
|
||||
const themeName = themeRef.split('/')[1];
|
||||
let titleText = 'Install Theme';
|
||||
const existingThemeNames = themes?.map(t => t.name) || [];
|
||||
let willOverwrite = existingThemeNames.includes(themeName.toLowerCase());
|
||||
const index = existingThemeNames.indexOf(themeName.toLowerCase());
|
||||
// get the theme that will be overwritten
|
||||
const themeToOverwrite = themes?.[index];
|
||||
let prompt = <>By clicking below, <strong>{themeName}</strong> will automatically be activated as the theme for your site.
|
||||
{willOverwrite &&
|
||||
<>
|
||||
<br/>
|
||||
<br/>
|
||||
This will overwrite your existing version of <strong>Liebling</strong>{themeToOverwrite?.active ? ' which is your active theme' : ''}. All custom changes will be lost.
|
||||
</>
|
||||
}
|
||||
</>;
|
||||
NiceModal.show(ConfirmationModal, {
|
||||
title: titleText,
|
||||
prompt,
|
||||
okLabel: 'Install',
|
||||
cancelLabel: 'Cancel',
|
||||
okRunningLabel: 'Installing...',
|
||||
okColor: 'black',
|
||||
onOk: async (confirmModal) => {
|
||||
let data: ThemesInstallResponseType | undefined;
|
||||
setInstalledFromMarketplace(true);
|
||||
try {
|
||||
if (willOverwrite) {
|
||||
if (themes) {
|
||||
themes.splice(index, 1);
|
||||
}
|
||||
}
|
||||
data = await installTheme(themeRef);
|
||||
if (data?.themes[0]) {
|
||||
await activateTheme(data.themes[0].name);
|
||||
showToast({
|
||||
type: 'success',
|
||||
message: <div><span className='capitalize'>{data.themes[0].name}</span> is now your active theme.</div>
|
||||
});
|
||||
}
|
||||
confirmModal?.remove();
|
||||
updateRoute('design/edit');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [themeRef, source, installTheme, handleError, activateTheme, updateRoute, themes, installedFromMarketplace]);
|
||||
|
||||
if (!themes) {
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue