0
Fork 0
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:
Ronald Langeveld 2023-10-03 12:33:08 +07:00 committed by GitHub
parent 965110f005
commit 514f36b4f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 2 deletions

View file

@ -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',

View file

@ -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();
}

View file

@ -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;
}