From eca03bdae912a69b7c92a951092a7843cc21b1e0 Mon Sep 17 00:00:00 2001 From: Joshua Jung Date: Fri, 16 Aug 2024 10:56:05 -0500 Subject: [PATCH 1/4] Adding customization of SidebarSearchModal search item renderers Signed-off-by: Joshua Jung --- .changeset/proud-rice-exist.md | 5 +++ plugins/search/README.md | 2 +- .../components/SearchModal/SearchModal.tsx | 34 ++++++++++++++----- .../components/SearchType/SearchType.Tabs.tsx | 2 +- .../SidebarSearchModal/SidebarSearchModal.tsx | 4 ++- plugins/search/src/plugin.ts | 22 +++++++++++- 6 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 .changeset/proud-rice-exist.md diff --git a/.changeset/proud-rice-exist.md b/.changeset/proud-rice-exist.md new file mode 100644 index 0000000000..e13619234f --- /dev/null +++ b/.changeset/proud-rice-exist.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Added ability to customize the search items within the SidebarSearchModal diff --git a/plugins/search/README.md b/plugins/search/README.md index e6b664338d..0aab198994 100644 --- a/plugins/search/README.md +++ b/plugins/search/README.md @@ -30,7 +30,7 @@ This search plugin is primarily responsible for the following: - Providing a `` routable extension. - Exposing various search-related components (like ``, - ``, etc), which can be composed by a Backstage App or by + ``, etc.), which can be composed by a Backstage App or by other Backstage Plugins to power search experiences of all kinds. Don't forget, a lot of functionality is available in web libraries and backend plugins: diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 07eec4438e..422197462d 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -37,12 +37,12 @@ import CloseIcon from '@material-ui/icons/Close'; import React, { useCallback, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; -import { rootRouteRef } from '../../plugin'; +import { rootRouteRef, SearchResultChildrenProvider } from '../../plugin'; /** * @public */ -export interface SearchModalChildrenProps { +export interface SearchModalChildrenProps extends SearchResultChildrenProvider { /** * A function that should be invoked when navigating away from the modal. */ @@ -52,7 +52,7 @@ export interface SearchModalChildrenProps { /** * @public */ -export interface SearchModalProps { +export interface SearchModalProps extends SearchResultChildrenProvider { /** * If true, it renders the modal. */ @@ -100,7 +100,10 @@ const useStyles = makeStyles(theme => ({ viewResultsLink: { verticalAlign: '0.5em' }, })); -export const Modal = ({ toggleModal }: SearchModalChildrenProps) => { +export const Modal = ({ + toggleModal, + searchResultChildren, +}: SearchModalChildrenProps) => { const classes = useStyles(); const navigate = useNavigate(); const { transitions } = useTheme(); @@ -163,7 +166,9 @@ export const Modal = ({ toggleModal }: SearchModalChildrenProps) => { + > + {searchResultChildren} + @@ -180,7 +185,13 @@ export const Modal = ({ toggleModal }: SearchModalChildrenProps) => { * @public */ export const SearchModal = (props: SearchModalProps) => { - const { open = true, hidden, toggleModal, children } = props; + const { + open = true, + hidden, + toggleModal, + children, + searchResultChildren, + } = props; const classes = useStyles(); @@ -199,8 +210,15 @@ export const SearchModal = (props: SearchModalProps) => { > {open && ( - {(children && children({ toggleModal })) ?? ( - + {(children && + children({ + toggleModal, + searchResultChildren: searchResultChildren || [], + })) ?? ( + )} )} diff --git a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx index d48420bdac..0ee20e48ed 100644 --- a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx @@ -20,7 +20,7 @@ import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import { makeStyles } from '@material-ui/core/styles'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme: any) => ({ tabs: { borderBottom: `1px solid ${theme.palette.textVerySubtle}`, }, diff --git a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx index fb4afc75fd..c4826cdd61 100644 --- a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx +++ b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx @@ -23,13 +23,14 @@ import { SearchModalProvider, useSearchModal, } from '../SearchModal'; +import { SearchResultChildrenProvider } from '../../plugin'; /** * Props for {@link SidebarSearchModal}. * * @public */ -export type SidebarSearchModalProps = { +export type SidebarSearchModalProps = SearchResultChildrenProvider & { icon?: IconComponent; children?: (props: SearchModalChildrenProps) => JSX.Element; }; @@ -49,6 +50,7 @@ const SidebarSearchModalContent = (props: SidebarSearchModalProps) => { diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 1adfd2e684..03ed040917 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -25,6 +25,24 @@ import { createComponentExtension, fetchApiRef, } from '@backstage/core-plugin-api'; +import { ReactNode } from 'react'; +import { SearchResultSet } from '@backstage/plugin-search-common'; + +/** + * This type allows us to pass children to the component via the . + * + * This allows us to customize the search result items displayed in the SidebarSearchModal, like so: + * + * , + * } /> + * ]} /> + */ +export type SearchResultChildrenProvider = { + searchResultChildren?: + | ReactNode + | ((resultSet: SearchResultSet) => JSX.Element); +}; export const rootRouteRef = createRouteRef({ id: 'search', @@ -63,7 +81,9 @@ export const SearchPage = searchPlugin.provide( /** * @public */ -export const SidebarSearchModal = searchPlugin.provide( +export const SidebarSearchModal = searchPlugin.provide< + (props: SearchResultChildrenProvider) => JSX.Element | null +>( createComponentExtension({ name: 'SidebarSearchModal', component: { From 950534e5b5a7d344bc0e46c219a77f0b30470288 Mon Sep 17 00:00:00 2001 From: Joshua Jung Date: Fri, 16 Aug 2024 11:16:42 -0500 Subject: [PATCH 2/4] Updating documentation for customization of SidebarSearchModal children Signed-off-by: Joshua Jung --- docs/features/search/how-to-guides.md | 49 +++++++++++++++++++++++---- plugins/search/api-report.md | 18 ++++++---- plugins/search/src/index.ts | 1 + plugins/search/src/plugin.ts | 12 ++----- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index b0df9440f3..e918dacbc1 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -160,11 +160,14 @@ const highlightOverride = { ## How to render search results using extensions -Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages: +Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages. ### 1. Providing an extension in your plugin package -Using the example below, you can provide an extension to be used as a default result item: +> Note: You must use the `plugin.provide()` function to make a search item renderer available. Unlike rendering a list in a standard MUI Table or similar, you cannot simply provide +> a rendering function to the `` component. + +Using the example below, you can provide an extension to be used as a search result item: ```tsx title="plugins/your-plugin/src/plugin.ts" import { createPlugin } from '@backstage/core-plugin-api'; @@ -214,7 +217,7 @@ export const YourSearchResultListItemExtension = plugin.provide( ); ``` -Remember to export your new extension: +Remember to export your new extension via your plugin's `index.ts` so that it is available from within your app: ```tsx title="plugins/your-plugin/src/index.ts" export { YourSearchResultListItem } from './plugin.ts'; @@ -222,9 +225,12 @@ export { YourSearchResultListItem } from './plugin.ts'; For more details, see the [createSearchResultListItemExtension](https://backstage.io/docs/reference/plugin-search-react.createsearchresultlistitemextension) API reference. -### 2. Using an extension in your Backstage app +### 2. Custom search result extension in the SearchPage -Now that you know how a search result item is provided, let's finally see how they can be used, for example, to compose a page in your application: +Once you have exposed your item renderer via the `plugin.provide()` function, you can now override the default search item renderers and tell the `` component +which renderers to use. Note that the order of the renderers matters! The first one that matches via its predicate function will be used. + +Here is an example of customizing your `SearchPage`: ```tsx title="packages/app/src/components/searchPage.tsx" import React from 'react'; @@ -276,9 +282,38 @@ const SearchPage = () => ( export const searchPage = ; ``` -> **Important**: A default result item extension should be placed as the last child, so it can be used only when no other extensions match the result being rendered. If a non-default extension is specified, the `DefaultResultListItem` component will be used. +> **Important**: A default result item extension (one that does not have a predicate) should be placed as the last child, so it can be used only when no other extensions match the result being rendered. +> If a non-default extension is specified, the `DefaultResultListItem` component will be used. -As another example, here's a search modal that renders results with extensions: +### 2. Custom search result extension in the SidebarSearchModal + +You may be using the SidebarSearchModal component. In this case, you can customize the search items in this component as follows: + +```tsx title="packages/app/src/components/Root/Root.tsx" +import { SidebarSearchModal } from '@backstage/plugin-search'; +... +export const Root = ({ children }: PropsWithChildren<{}>) => { + const styles = useStyles(); + + return + + ... + } />, + /* Provide an existing search item renderer */ + } /> + ]} /> + ... + + {children} + ; +}; +``` + +### 3. Custom search result extension in a custom SearchModal + +Assuming you have completely customized your SearchModal, here's an example that renders results with extensions: ```tsx title="packages/app/src/components/searchModal.tsx" import React from 'react'; diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md index 64f596ab11..d9435d5e61 100644 --- a/plugins/search/api-report.md +++ b/plugins/search/api-report.md @@ -3,8 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// - import { BackstagePlugin } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -12,6 +10,7 @@ import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SearchBarBaseProps } from '@backstage/plugin-search-react'; +import { SearchResultSet } from '@backstage/plugin-search-common'; // @public (undocumented) export const HomePageSearchBar: ( @@ -30,12 +29,12 @@ export const Router: () => React_2.JSX.Element; export const SearchModal: (props: SearchModalProps) => React_2.JSX.Element; // @public (undocumented) -export interface SearchModalChildrenProps { +export interface SearchModalChildrenProps extends SearchResultChildrenProvider { toggleModal: () => void; } // @public (undocumented) -export interface SearchModalProps { +export interface SearchModalProps extends SearchResultChildrenProvider { children?: (props: SearchModalChildrenProps) => JSX.Element; hidden?: boolean; open?: boolean; @@ -76,6 +75,13 @@ const searchPlugin: BackstagePlugin< export { searchPlugin as plugin }; export { searchPlugin }; +// @public (undocumented) +export type SearchResultChildrenProvider = { + searchResultChildren?: + | ReactNode + | ((resultSet: SearchResultSet) => JSX.Element); +}; + // @public (undocumented) export const SearchType: { (props: SearchTypeProps): React_2.JSX.Element; @@ -118,10 +124,10 @@ export const SidebarSearch: (props: SidebarSearchProps) => React_2.JSX.Element; // @public (undocumented) export const SidebarSearchModal: ( props: SidebarSearchModalProps, -) => JSX_2.Element; +) => JSX.Element | null; // @public -export type SidebarSearchModalProps = { +export type SidebarSearchModalProps = SearchResultChildrenProvider & { icon?: IconComponent; children?: (props: SearchModalChildrenProps) => JSX.Element; }; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 66b032c83c..720797608f 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -43,6 +43,7 @@ export type { export { SidebarSearch } from './components/SidebarSearch'; export type { SidebarSearchProps } from './components/SidebarSearch'; export type { SidebarSearchModalProps } from './components/SidebarSearchModal'; +export type { SearchResultChildrenProvider } from './plugin'; export { HomePageSearchBar, diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 03ed040917..9ee66759ed 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -27,16 +27,10 @@ import { } from '@backstage/core-plugin-api'; import { ReactNode } from 'react'; import { SearchResultSet } from '@backstage/plugin-search-common'; +import { SidebarSearchModalProps } from './components/SidebarSearchModal'; /** - * This type allows us to pass children to the component via the . - * - * This allows us to customize the search result items displayed in the SidebarSearchModal, like so: - * - * , - * } /> - * ]} /> + * @public */ export type SearchResultChildrenProvider = { searchResultChildren?: @@ -82,7 +76,7 @@ export const SearchPage = searchPlugin.provide( * @public */ export const SidebarSearchModal = searchPlugin.provide< - (props: SearchResultChildrenProvider) => JSX.Element | null + (props: SidebarSearchModalProps) => JSX.Element | null >( createComponentExtension({ name: 'SidebarSearchModal', From c429c92efffed7fbd6022219738ca1b6a19df976 Mon Sep 17 00:00:00 2001 From: Joshua Jung Date: Fri, 6 Sep 2024 17:39:21 -0500 Subject: [PATCH 3/4] Addressing SidebarSearchModal suggestions in PR Signed-off-by: Joshua Jung --- docs/features/search/how-to-guides.md | 2 +- plugins/search/api-report.md | 23 +++++++------- .../components/SearchModal/SearchModal.tsx | 31 +++++++++++++------ .../SidebarSearchModal/SidebarSearchModal.tsx | 11 ++++--- plugins/search/src/index.ts | 1 - plugins/search/src/plugin.ts | 11 ------- 6 files changed, 41 insertions(+), 38 deletions(-) diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index e918dacbc1..93c04c7af4 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -298,7 +298,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => { return ... - } />, /* Provide an existing search item renderer */ diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md index d9435d5e61..a47c11b194 100644 --- a/plugins/search/api-report.md +++ b/plugins/search/api-report.md @@ -3,6 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// + import { BackstagePlugin } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -29,15 +31,19 @@ export const Router: () => React_2.JSX.Element; export const SearchModal: (props: SearchModalProps) => React_2.JSX.Element; // @public (undocumented) -export interface SearchModalChildrenProps extends SearchResultChildrenProvider { +export interface SearchModalChildrenProps { + resultItemComponents?: + | ReactNode + | ((resultSet: SearchResultSet) => JSX.Element); toggleModal: () => void; } // @public (undocumented) -export interface SearchModalProps extends SearchResultChildrenProvider { +export interface SearchModalProps { children?: (props: SearchModalChildrenProps) => JSX.Element; hidden?: boolean; open?: boolean; + resultItemComponents?: SearchModalChildrenProps['resultItemComponents']; toggleModal: () => void; } @@ -75,13 +81,6 @@ const searchPlugin: BackstagePlugin< export { searchPlugin as plugin }; export { searchPlugin }; -// @public (undocumented) -export type SearchResultChildrenProvider = { - searchResultChildren?: - | ReactNode - | ((resultSet: SearchResultSet) => JSX.Element); -}; - // @public (undocumented) export const SearchType: { (props: SearchTypeProps): React_2.JSX.Element; @@ -127,9 +126,11 @@ export const SidebarSearchModal: ( ) => JSX.Element | null; // @public -export type SidebarSearchModalProps = SearchResultChildrenProvider & { +export type SidebarSearchModalProps = Pick< + SearchModalProps, + 'children' | 'resultItemComponents' +> & { icon?: IconComponent; - children?: (props: SearchModalChildrenProps) => JSX.Element; }; // @public diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 422197462d..871d177078 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -34,25 +34,33 @@ import IconButton from '@material-ui/core/IconButton'; import { makeStyles } from '@material-ui/core/styles'; import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; import CloseIcon from '@material-ui/icons/Close'; -import React, { useCallback, useEffect, useRef } from 'react'; +import React, { ReactNode, useCallback, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; -import { rootRouteRef, SearchResultChildrenProvider } from '../../plugin'; +import { rootRouteRef } from '../../plugin'; +import { SearchResultSet } from '@backstage/plugin-search-common'; /** * @public */ -export interface SearchModalChildrenProps extends SearchResultChildrenProvider { +export interface SearchModalChildrenProps { /** * A function that should be invoked when navigating away from the modal. */ toggleModal: () => void; + + /** + * Ability to provide custom components to render the result items + */ + resultItemComponents?: + | ReactNode + | ((resultSet: SearchResultSet) => JSX.Element); } /** * @public */ -export interface SearchModalProps extends SearchResultChildrenProvider { +export interface SearchModalProps { /** * If true, it renders the modal. */ @@ -74,6 +82,11 @@ export interface SearchModalProps extends SearchResultChildrenProvider { * place of the default. */ children?: (props: SearchModalChildrenProps) => JSX.Element; + + /** + * Optional ability to pass in result item component renderers. + */ + resultItemComponents?: SearchModalChildrenProps['resultItemComponents']; } const useStyles = makeStyles(theme => ({ @@ -102,7 +115,7 @@ const useStyles = makeStyles(theme => ({ export const Modal = ({ toggleModal, - searchResultChildren, + resultItemComponents, }: SearchModalChildrenProps) => { const classes = useStyles(); const navigate = useNavigate(); @@ -167,7 +180,7 @@ export const Modal = ({ onClick={handleSearchResultClick} onKeyDown={handleSearchResultClick} > - {searchResultChildren} + {resultItemComponents} @@ -190,7 +203,7 @@ export const SearchModal = (props: SearchModalProps) => { hidden, toggleModal, children, - searchResultChildren, + resultItemComponents, } = props; const classes = useStyles(); @@ -213,11 +226,11 @@ export const SearchModal = (props: SearchModalProps) => { {(children && children({ toggleModal, - searchResultChildren: searchResultChildren || [], + resultItemComponents: resultItemComponents || [], })) ?? ( )} diff --git a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx index c4826cdd61..e385a4f1fd 100644 --- a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx +++ b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx @@ -19,20 +19,21 @@ import { SidebarItem } from '@backstage/core-components'; import { IconComponent } from '@backstage/core-plugin-api'; import { SearchModal, - SearchModalChildrenProps, + SearchModalProps, SearchModalProvider, useSearchModal, } from '../SearchModal'; -import { SearchResultChildrenProvider } from '../../plugin'; /** * Props for {@link SidebarSearchModal}. * * @public */ -export type SidebarSearchModalProps = SearchResultChildrenProvider & { +export type SidebarSearchModalProps = Pick< + SearchModalProps, + 'children' | 'resultItemComponents' +> & { icon?: IconComponent; - children?: (props: SearchModalChildrenProps) => JSX.Element; }; const SidebarSearchModalContent = (props: SidebarSearchModalProps) => { @@ -50,7 +51,7 @@ const SidebarSearchModalContent = (props: SidebarSearchModalProps) => { diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 720797608f..66b032c83c 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -43,7 +43,6 @@ export type { export { SidebarSearch } from './components/SidebarSearch'; export type { SidebarSearchProps } from './components/SidebarSearch'; export type { SidebarSearchModalProps } from './components/SidebarSearchModal'; -export type { SearchResultChildrenProvider } from './plugin'; export { HomePageSearchBar, diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 9ee66759ed..d4efc3748d 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -25,19 +25,8 @@ import { createComponentExtension, fetchApiRef, } from '@backstage/core-plugin-api'; -import { ReactNode } from 'react'; -import { SearchResultSet } from '@backstage/plugin-search-common'; import { SidebarSearchModalProps } from './components/SidebarSearchModal'; -/** - * @public - */ -export type SearchResultChildrenProvider = { - searchResultChildren?: - | ReactNode - | ((resultSet: SearchResultSet) => JSX.Element); -}; - export const rootRouteRef = createRouteRef({ id: 'search', }); From b4bf424d2de82763a29741386c394f70f58bc872 Mon Sep 17 00:00:00 2001 From: Joshua Jung Date: Tue, 10 Sep 2024 08:12:34 -0500 Subject: [PATCH 4/4] Fixing typing issues per PR requests Signed-off-by: Joshua Jung --- plugins/search/src/components/SearchModal/SearchModal.tsx | 2 +- plugins/search/src/components/SearchType/SearchType.Tabs.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 871d177078..b5d932ffdb 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -230,7 +230,7 @@ export const SearchModal = (props: SearchModalProps) => { })) ?? ( )} diff --git a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx index 0ee20e48ed..077883f649 100644 --- a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx @@ -19,8 +19,9 @@ import { useSearch } from '@backstage/plugin-search-react'; import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import { makeStyles } from '@material-ui/core/styles'; +import { Theme } from '@material-ui/core/styles'; -const useStyles = makeStyles((theme: any) => ({ +const useStyles = makeStyles((theme: Theme) => ({ tabs: { borderBottom: `1px solid ${theme.palette.textVerySubtle}`, },