0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Updated AdminX search to use centralised configuration (#17106)

refs https://github.com/TryGhost/Team/issues/3349
This commit is contained in:
Jono M 2023-06-27 08:09:12 +12:00 committed by GitHub
parent fdef6a30ab
commit 0d8eb203bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 129 additions and 87 deletions

View file

@ -2,16 +2,16 @@ import ButtonGroup from '../global/ButtonGroup';
import React from 'react';
import SettingGroupHeader from './SettingGroupHeader';
import clsx from 'clsx';
import useSearchable from '../../hooks/useSearchable';
import {ButtonProps} from '../global/Button';
import {SaveState} from '../../hooks/useForm';
import {useSearch} from '../../components/providers/ServiceProvider';
interface SettingGroupProps {
navid?:string;
testId?: string;
title?: string;
description?: React.ReactNode;
searchKeywords?: string[];
keywords?: string[];
isEditing?: boolean;
saveState?: SaveState;
customHeader?: React.ReactNode;
@ -39,7 +39,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
testId,
title,
description,
searchKeywords,
keywords = [],
isEditing,
saveState,
customHeader,
@ -53,7 +53,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
onSave,
onCancel
}) => {
const {isVisible} = useSearchable({keywords: searchKeywords && [title || '', ...searchKeywords]});
const {checkVisible} = useSearch();
const handleEdit = () => {
onEditingChange?.(true);
@ -126,7 +126,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
}
return (
<div className={clsx('relative flex flex-col gap-6 rounded', !isVisible && 'hidden', border && 'border p-5 md:p-7', styles)} data-testid={testId}>
<div className={clsx('relative flex flex-col gap-6 rounded', border && 'border p-5 md:p-7', !checkVisible(keywords) && 'hidden', styles)} data-testid={testId}>
<div className='absolute top-[-60px]' id={navid && navid}></div>
{customHeader ? customHeader :
<SettingGroupHeader description={description} title={title!}>

View file

@ -17,11 +17,11 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
title: 'Section header',
children:
children:
<>
<SettingGroup {...SettingGroupStories.SingleColumn.args} />
<SettingGroup {...SettingGroupStories.Editing.args} />
<SettingGroup {...SettingGroupStories.Unsaved.args} />
</>
}
};
};

View file

@ -1,14 +1,18 @@
import React from 'react';
import SettingSectionHeader from './SettingSectionHeader';
import {useSearch} from '../../components/providers/ServiceProvider';
interface Props {
title?: string;
keywords?: string[];
children?: React.ReactNode;
}
const SettingSection: React.FC<Props> = ({title, children}) => {
const SettingSection: React.FC<Props> = ({title, keywords = [], children}) => {
const {checkVisible} = useSearch();
return (
<div className="!visible hidden [&:has(>div>:not(.hidden))]:!block">
<div className={checkVisible(keywords) ? '' : 'hidden'}>
{title && <SettingSectionHeader sticky={true} title={title} />}
{children &&
<div className="mb-[100px] flex flex-col gap-9">

View file

@ -1,5 +1,6 @@
import React, {createContext, useContext, useMemo} from 'react';
import setupGhostApi from '../../utils/api';
import useSearchService, {SearchService} from '../../utils/search';
import {OfficialTheme} from '../../models/themes';
export interface FileService {
@ -9,7 +10,7 @@ interface ServicesContextProps {
api: ReturnType<typeof setupGhostApi>;
fileService: FileService|null;
officialThemes: OfficialTheme[];
search: {filter: string, setFilter: (value: string) => void}
search: SearchService
}
interface ServicesProviderProps {
@ -22,7 +23,7 @@ const ServicesContext = createContext<ServicesContextProps>({
api: setupGhostApi({ghostVersion: ''}),
fileService: null,
officialThemes: [],
search: {filter: '', setFilter: () => {}}
search: {filter: '', setFilter: () => {}, checkVisible: () => true}
});
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, officialThemes}) => {
@ -33,15 +34,14 @@ const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersi
return response.images[0].url;
}
}), [apiService]);
const [filter, setFilter] = React.useState('');
const search = useSearchService();
return (
<ServicesContext.Provider value={{
api: apiService,
fileService,
officialThemes,
search: {filter, setFilter}
search
}}>
{children}
</ServicesContext.Provider>

View file

@ -60,7 +60,7 @@ function getDefaultRecipientValue({
return defaultEmailRecipients;
}
const DefaultRecipients: React.FC = () => {
const DefaultRecipients: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -198,9 +198,9 @@ const DefaultRecipients: React.FC = () => {
<SettingGroup
description='When you publish new content, who do you usually want to send it to?'
isEditing={isEditing}
keywords={keywords}
navid='default-recipients'
saveState={saveState}
searchKeywords={['newsletter', 'recipients', 'email']}
testId='default-recipients'
title='Default recipients'
onCancel={handleCancel}

View file

@ -3,13 +3,18 @@ import MailGun from './Mailgun';
import React from 'react';
import SettingSection from '../../../admin-x-ds/settings/SettingSection';
const searchKeywords = {
defaultRecipients: ['newsletter', 'default recipients', 'email'],
mailgun: ['mailgun', 'email']
};
const EmailSettings: React.FC = () => {
return (
<SettingSection title='Email newsletters'>
<DefaultRecipients />
<MailGun />
<SettingSection keywords={Object.values(searchKeywords).flat()} title='Email newsletters'>
<DefaultRecipients keywords={searchKeywords.defaultRecipients} />
<MailGun keywords={searchKeywords.mailgun} />
</SettingSection>
);
};
export default EmailSettings;
export default EmailSettings;

View file

@ -13,7 +13,7 @@ const MAILGUN_REGIONS = [
{label: '🇪🇺 EU', value: 'https://api.eu.mailgun.net/v3'}
];
const MailGun: React.FC = () => {
const MailGun: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -99,9 +99,9 @@ const MailGun: React.FC = () => {
<SettingGroup
description={groupDescription}
isEditing={isEditing}
keywords={keywords}
navid='mailgun'
saveState={saveState}
searchKeywords={['mailgun', 'email']}
testId='mailgun'
title='Mailgun'
onCancel={handleCancel}

View file

@ -9,7 +9,7 @@ import {ReactComponent as FacebookLogo} from '../../../admin-x-ds/assets/images/
import {FileService, ServicesContext} from '../../providers/ServiceProvider';
import {getSettingValues} from '../../../utils/helpers';
const Facebook: React.FC = () => {
const Facebook: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -100,9 +100,9 @@ const Facebook: React.FC = () => {
<SettingGroup
description='Customize structured data of your site'
isEditing={isEditing}
keywords={keywords}
navid='facebook'
saveState={saveState}
searchKeywords={['facebook card', 'structured data', 'rich cards']}
testId='facebook'
title='Facebook card'
onCancel={handleCancel}

View file

@ -11,22 +11,32 @@ import TitleAndDescription from './TitleAndDescription';
import Twitter from './Twitter';
import Users from './Users';
const searchKeywords = {
titleAndDescription: ['title and description', 'site title', 'site description'],
timeZone: ['time', 'date', 'site timezone', 'time zone'],
publicationLanguage: ['publication language', 'locale'],
metadata: ['metadata', 'title', 'description', 'search', 'engine', 'google'],
twitter: ['twitter card', 'structured data', 'rich cards'],
facebook: ['facebook card', 'structured data', 'rich cards'],
socialAccounts: ['social accounts', 'facebook', 'twitter', 'structured data', 'rich cards'],
lockSite: ['private', 'password', 'lock site'],
users: ['users and permissions', 'roles', 'staff']
};
const GeneralSettings: React.FC = () => {
return (
<>
<SettingSection title="General">
<TitleAndDescription />
<TimeZone />
<PublicationLanguage />
<Metadata />
<Twitter />
<Facebook />
<SocialAccounts />
<LockSite />
<Users />
</SettingSection>
</>
<SettingSection keywords={Object.values(searchKeywords).flat()} title="General">
<TitleAndDescription keywords={searchKeywords.titleAndDescription} />
<TimeZone keywords={searchKeywords.timeZone} />
<PublicationLanguage keywords={searchKeywords.publicationLanguage} />
<Metadata keywords={searchKeywords.metadata} />
<Twitter keywords={searchKeywords.twitter} />
<Facebook keywords={searchKeywords.facebook} />
<SocialAccounts keywords={searchKeywords.socialAccounts} />
<LockSite keywords={searchKeywords.lockSite} />
<Users keywords={searchKeywords.users} />
</SettingSection>
);
};
export default GeneralSettings;
export default GeneralSettings;

View file

@ -8,7 +8,7 @@ import Toggle from '../../../admin-x-ds/global/form/Toggle';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {getSettingValues} from '../../../utils/helpers';
const LockSite: React.FC = () => {
const LockSite: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -81,9 +81,9 @@ const LockSite: React.FC = () => {
<SettingGroup
description='Enable protection with a simple shared password.'
isEditing={isEditing}
keywords={keywords}
navid='locksite'
saveState={saveState}
searchKeywords={['private', 'password', 'lock']}
testId='locksite'
title='Make site private'
onCancel={handleCancel}

View file

@ -55,7 +55,7 @@ const SearchEnginePreview: React.FC<SearchEnginePreviewProps> = ({
);
};
const Metadata: React.FC = () => {
const Metadata: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -102,9 +102,9 @@ const Metadata: React.FC = () => {
<SettingGroup
description='Extra content for search engines'
isEditing={isEditing}
keywords={keywords}
navid='metadata'
saveState={saveState}
searchKeywords={['meta', 'title', 'description', 'search', 'engine', 'google']}
testId='metadata'
title='Metadata'
onCancel={handleCancel}

View file

@ -5,7 +5,7 @@ import TextField from '../../../admin-x-ds/global/form/TextField';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {getSettingValues} from '../../../utils/helpers';
const PublicationLanguage: React.FC = () => {
const PublicationLanguage: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -57,9 +57,9 @@ const PublicationLanguage: React.FC = () => {
<SettingGroup
description="Set the language/locale which is used on your site"
isEditing={isEditing}
keywords={keywords}
navid='publication-language'
saveState={saveState}
searchKeywords={['language', 'locale']}
testId='publication-language'
title="Publication Language"
onCancel={handleCancel}

View file

@ -59,7 +59,7 @@ function validateTwitterUrl(newUrl: string) {
}
}
const SocialAccounts: React.FC = () => {
const SocialAccounts: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -158,9 +158,9 @@ const SocialAccounts: React.FC = () => {
<SettingGroup
description='Link your social accounts for full structured data and rich card support'
isEditing={isEditing}
keywords={keywords}
navid='social-accounts'
saveState={saveState}
searchKeywords={['social', 'accounts', 'facebook', 'twitter', 'structured data', 'rich cards']}
testId='social-accounts'
title='Social accounts'
onCancel={handleCancel}

View file

@ -34,7 +34,7 @@ const Hint: React.FC<HintProps> = ({timezone}) => {
);
};
const TimeZone: React.FC = () => {
const TimeZone: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -85,9 +85,9 @@ const TimeZone: React.FC = () => {
<SettingGroup
description='Set the time and date of your publication, used for all published posts'
isEditing={isEditing}
keywords={keywords}
navid='timezone'
saveState={saveState}
searchKeywords={['time', 'date', 'timezone', 'time zone']}
testId='timezone'
title='Site timezone'
onCancel={handleCancel}

View file

@ -6,7 +6,7 @@ import TextField from '../../../admin-x-ds/global/form/TextField';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {getSettingValues} from '../../../utils/helpers';
const TitleAndDescription: React.FC = () => {
const TitleAndDescription: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -69,9 +69,9 @@ const TitleAndDescription: React.FC = () => {
<SettingGroup
description='The details used to identify your publication around the web'
isEditing={isEditing}
keywords={keywords}
navid='title-and-description'
saveState={saveState}
searchKeywords={['site title', 'site description']}
testId='title-and-description'
title='Title & description'
onCancel={handleCancel}

View file

@ -9,7 +9,7 @@ import {FileService, ServicesContext} from '../../providers/ServiceProvider';
import {ReactComponent as TwitterLogo} from '../../../admin-x-ds/assets/images/twitter-logo.svg';
import {getSettingValues} from '../../../utils/helpers';
const Twitter: React.FC = () => {
const Twitter: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -102,9 +102,9 @@ const Twitter: React.FC = () => {
<SettingGroup
description='Customize structured data of your site'
isEditing={isEditing}
keywords={keywords}
navid='twitter'
saveState={saveState}
searchKeywords={['twitter card', 'structured data', 'rich cards']}
testId='twitter'
title='Twitter card'
onCancel={handleCancel}

View file

@ -180,7 +180,7 @@ const InvitesUserList: React.FC<InviteListProps> = ({users}) => {
);
};
const Users: React.FC = () => {
const Users: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
ownerUser,
adminUsers,
@ -234,8 +234,8 @@ const Users: React.FC = () => {
return (
<SettingGroup
customButtons={buttons}
keywords={keywords}
navid='users'
searchKeywords={['users', 'permissions', 'roles', 'staff']}
testId='users'
title='Users and permissions'
>

View file

@ -28,7 +28,7 @@ const COMMENTS_ENABLED_OPTIONS = [
{value: 'off', label: 'Nobody'}
];
const Access: React.FC = () => {
const Access: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -143,9 +143,9 @@ const Access: React.FC = () => {
<SettingGroup
description='Set up default access options for subscription and posts'
isEditing={isEditing}
keywords={keywords}
navid='access'
saveState={saveState}
searchKeywords={['access', 'subscription', 'post']}
testId='access'
title='Access'
onCancel={handleCancel}

View file

@ -6,7 +6,7 @@ import Toggle from '../../../admin-x-ds/global/form/Toggle';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {getSettingValues} from '../../../utils/helpers';
const Analytics: React.FC = () => {
const Analytics: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
localSettings,
isEditing,
@ -76,9 +76,9 @@ const Analytics: React.FC = () => {
description='Decide what data you collect from your members'
hideEditButton={true}
isEditing={isEditing}
keywords={keywords}
navid='analytics'
saveState={saveState}
searchKeywords={['analytics', 'tracking', 'privacy']}
testId='analytics'
title='Analytics'
onCancel={handleCancel}

View file

@ -3,13 +3,18 @@ import Analytics from './Analytics';
import React from 'react';
import SettingSection from '../../../admin-x-ds/settings/SettingSection';
const searchKeywords = {
access: ['access', 'subscription', 'post'],
analytics: ['analytics', 'tracking', 'privacy']
};
const MembershipSettings: React.FC = () => {
return (
<SettingSection title='Membership'>
<Access />
<Analytics />
<SettingSection keywords={Object.values(searchKeywords).flat()} title='Membership'>
<Access keywords={searchKeywords.access} />
<Analytics keywords={searchKeywords.analytics} />
</SettingSection>
);
};
export default MembershipSettings;
export default MembershipSettings;

View file

@ -4,7 +4,7 @@ import NiceModal from '@ebay/nice-modal-react';
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
const DesignSetting: React.FC = () => {
const DesignSetting: React.FC<{ keywords: string[] }> = ({keywords}) => {
const openPreviewModal = () => {
NiceModal.show(DesignModal);
};
@ -13,8 +13,8 @@ const DesignSetting: React.FC = () => {
<SettingGroup
customButtons={<Button color='green' label='Customize' link onClick={openPreviewModal}/>}
description="Customize the look and feel of your site"
keywords={keywords}
navid='branding-and-design'
searchKeywords={['design', 'branding', 'logo', 'cover', 'colors', 'fonts', 'background']}
testId='design'
title="Branding and design"
/>

View file

@ -4,7 +4,7 @@ import NiceModal from '@ebay/nice-modal-react';
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
const Navigation: React.FC = () => {
const Navigation: React.FC<{ keywords: string[] }> = ({keywords}) => {
const openPreviewModal = () => {
NiceModal.show(NavigationModal);
};
@ -13,8 +13,8 @@ const Navigation: React.FC = () => {
<SettingGroup
customButtons={<Button color='green' label='Customize' link onClick={openPreviewModal}/>}
description="Set up primary and secondary menus"
keywords={keywords}
navid='navigation'
searchKeywords={['navigation', 'menus', 'primary', 'secondary', 'links']}
testId='navigation'
title="Navigation"
/>

View file

@ -4,13 +4,19 @@ import React from 'react';
import SettingSection from '../../../admin-x-ds/settings/SettingSection';
import Theme from './Theme';
const searchKeywords = {
theme: ['themes', 'design', 'appearance', 'style'],
design: ['design', 'branding', 'logo', 'cover', 'colors', 'fonts', 'background'],
navigation: ['navigation', 'menus', 'primary', 'secondary', 'links']
};
const SiteSettings: React.FC = () => {
return (
<>
<SettingSection title="Site">
<Theme />
<DesignSetting />
<Navigation />
<SettingSection keywords={Object.values(searchKeywords).flat()} title="Site">
<Theme keywords={searchKeywords.theme} />
<DesignSetting keywords={searchKeywords.design} />
<Navigation keywords={searchKeywords.navigation} />
</SettingSection>
</>
);

View file

@ -4,15 +4,15 @@ import NiceModal from '@ebay/nice-modal-react';
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
const Theme: React.FC = () => {
const Theme: React.FC<{ keywords: string[] }> = ({keywords}) => {
return (
<SettingGroup
customButtons={<Button color='green' label='Manage themes' link onClick={() => {
NiceModal.show(ChangeThemeModal);
}}/>}
description="Change or upload themes"
keywords={keywords}
navid='theme'
searchKeywords={['themes', 'design', 'appearance', 'style']}
testId='theme'
title="Theme"
/>

View file

@ -1,15 +0,0 @@
import {useSearch} from '../components/providers/ServiceProvider';
const useSearchable = ({keywords}: { keywords: string[] | undefined }) => {
const {filter} = useSearch();
if (!filter || !keywords) {
return {isVisible: true};
}
const isVisible = keywords.some(keyword => keyword.toLowerCase().includes(filter.toLowerCase()));
return {isVisible};
};
export default useSearchable;

View file

@ -0,0 +1,27 @@
import {useState} from 'react';
export interface SearchService {
filter: string;
setFilter: (value: string) => void;
checkVisible: (keywords: string[]) => boolean;
}
const useSearchService = () => {
const [filter, setFilter] = useState('');
const checkVisible = (keywords: string[]) => {
if (!keywords.length) {
return true;
}
return keywords.some(keyword => keyword.toLowerCase().includes(filter.toLowerCase()));
};
return {
filter,
setFilter,
checkVisible
};
};
export default useSearchService;