Updated to highlight searched keywords in AdminX settings (#18112)

refs https://github.com/TryGhost/Product/issues/3832

---

<!-- Leave the line below if you'd like GitHub Copilot to generate a
summary from your commit -->
<!--
copilot:summary
-->
### <samp>🤖 Generated by Copilot at 1347a85</samp>

Added search functionality to the settings page using a custom hook and
a service. The `useSearch` hook uses the `useSearchService` function to
create a search service object that provides the filter and highlight
logic. The `highlightKeywords` function from the search service is
passed to the `SettingsGroupHeader` component to render the settings
with the matching keywords.
This commit is contained in:
Jono M 2023-10-02 09:33:24 +01:00 committed by GitHub
parent 2ad0e73c42
commit bb0dff3571
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 32 deletions

View File

@ -1,5 +1,6 @@
import Heading from '../global/Heading';
import React from 'react';
import {useSearch} from '../../components/providers/ServiceProvider';
interface Props {
title?: string;
@ -8,12 +9,14 @@ interface Props {
}
const SettingGroupHeader: React.FC<Props> = ({title, description, children}) => {
const {highlightKeywords} = useSearch();
return (
<div className="flex items-start justify-between gap-4">
{(title || description) &&
<div>
<Heading level={5}>{title}</Heading>
{description && <p className="mt-0.5 hidden max-w-lg text-sm group-[.is-not-editing]/setting-group:!visible group-[.is-not-editing]/setting-group:!block md:!visible md:!block">{description}</p>}
<Heading level={5}>{highlightKeywords(title || '')}</Heading>
{description && <p className="mt-0.5 hidden max-w-lg text-sm group-[.is-not-editing]/setting-group:!visible group-[.is-not-editing]/setting-group:!block md:!visible md:!block">{highlightKeywords(description)}</p>}
</div>
}
<div className='-mt-0.5'>
@ -23,4 +26,4 @@ const SettingGroupHeader: React.FC<Props> = ({title, description, children}) =>
);
};
export default SettingGroupHeader;
export default SettingGroupHeader;

View File

@ -44,7 +44,7 @@ const ServicesContext = createContext<ServicesContextProps>({
ghostVersion: '',
officialThemes: [],
zapierTemplates: [],
search: {filter: '', setFilter: () => {}, checkVisible: () => true},
search: {filter: '', setFilter: () => {}, checkVisible: () => true, highlightKeywords: () => ''},
unsplashConfig: {
Authorization: '',
'Accept-Version': '',

View File

@ -1,27 +0,0 @@
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;

View File

@ -0,0 +1,54 @@
import React, {ReactNode, useState} from 'react';
export interface SearchService {
filter: string;
setFilter: (value: string) => void;
checkVisible: (keywords: string[]) => boolean;
highlightKeywords: (text: ReactNode) => ReactNode;
}
const useSearchService = () => {
const [filter, setFilter] = useState('');
const checkVisible = (keywords: string[]) => {
if (!keywords.length) {
return true;
}
return keywords.some(keyword => keyword.toLowerCase().includes(filter.toLowerCase()));
};
const highlightKeywords = (text: ReactNode): ReactNode => {
if (!filter) {
return text;
}
if (typeof text === 'string') {
const words = filter.split(/\s+/).map(word => word.toLowerCase());
const wordsPattern = words.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
const parts = text.split(new RegExp(`(${wordsPattern})`, 'gi'));
return parts.map(part => (words.includes(part.toLowerCase()) ? <span className='bg-yellow-500/40'>{part}</span> : part));
} else if (Array.isArray(text)) {
return text.map(part => highlightKeywords(part));
} else if (text && typeof text === 'object' && text) {
return React.Children.map(text, (child) => {
if (child && typeof child === 'object' && 'props' in child) {
return highlightKeywords(child.props.children);
}
return child;
});
} else {
return text;
}
};
return {
filter,
setFilter,
checkVisible,
highlightKeywords
};
};
export default useSearchService;

@ -1 +1 @@
Subproject commit 276e2c9d0140c902e1c8d3760bc194790722fa71
Subproject commit 4d3319d05ce92e7b0244e5608d3fc6cc9c86e735