import { Localized } from "@fluent/react/compat" ; import { Match , Router , withRouter } from "found" ; import React , { KeyboardEvent , MouseEvent , useCallback , useRef , useState , } from "react" ; import { graphql } from "react-relay" ; import { getModerationLink , QUEUE_NAME } from "coral-framework/helpers" ; import { useEffectWhenChanged } from "coral-framework/hooks" ; import { useCoralContext } from "coral-framework/lib/bootstrap/CoralContext" ; import { useFetch , withFragmentContainer } from "coral-framework/lib/relay" ; import { GQLFEATURE_FLAG } from "coral-framework/schema" ; import { PropTypesOf } from "coral-framework/types" ; import { Flex , Spinner } from "coral-ui/components/v2" ; import { blur } from "coral-ui/helpers" ; import { ListBoxOptionClickOrEnterHandler , ListBoxOptionElement , } from "coral-ui/hooks/useComboBox" ; import { ModerateSearchBarContainer_settings as SettingsData } from "coral-admin/__generated__/ModerateSearchBarContainer_settings.graphql" ; import { ModerateSearchBarContainer_story as ModerationQueuesData } from "coral-admin/__generated__/ModerateSearchBarContainer_story.graphql" ; import { SearchStoryFetchQueryResponse } from "coral-admin/__generated__/SearchStoryFetchQuery.graphql" ; import Bar from "./Bar" ; import GoToAriaInfo from "./GoToAriaInfo" ; import ModerateAllOption from "./ModerateAllOption" ; import Option from "./Option" ; import OptionDetail from "./OptionDetail" ; import SearchStoryFetch from "./SearchStoryFetch" ; import SeeAllOption from "./SeeAllOption" ; interface Props { router : Router ; match : Match ; story : ModerationQueuesData | null ; settings : SettingsData | null ; allStories : boolean ; siteSelector : React . ReactNode ; sectionSelector ? : React . ReactNode ; siteID : string | null ; queueName : QUEUE_NAME | undefined ; } type SearchBarOptions = PropTypesOf < typeof Bar > [ "options" ] ; param router returns function useLinkNavHandler ( router ) { const { window } = useCoralContext ( ) ; return useCallback ( ( evt , element ) => { if ( element . props . href ) { router . push ( element . props . href ) ; if ( evt . preventDefault ) { evt . preventDefault ( ) ; } blur ( window ) ; } } , [ router , window ] ) ; } function getStoryDetails ( { multisite , featureFlags , } , { site , metadata , } ) { return ( < Flex itemGutter > { } { multisite && < OptionDetail variant = "bold" > { site . name } < /OptionDetail>} { } { ! multisite && featureFlags . includes ( GQLFEATURE_FLAG . SECTIONS ) && ( metadata ?. section ? ( < OptionDetail variant = "bold" > { metadata . section } < /OptionDetail>) : (<Localized id="moderate-section-uncategorized"> < OptionDetail variant = "muted" > Uncategorized < /OptionDetail> < /Localized>))} { } { metadata ?. author && < OptionDetail > { metadata . author } < /OptionDetail>} < /Flex>); } function getContextOptionsWhenModeratingAll ( onClickOrEnter , siteID , queue ) { return [ { element : ( < Option href = { getModerationLink ( { queue , siteID } ) } > < GoToAriaInfo /> < Localized id = "moderate-searchBar-allStories" > < span > All stories < / span > < /Localized> < /Option>), onClickOrEnter , group : "CONTEXT" , } , ] ; } function getContextOptionsWhenModeratingStory ( onClickOrEnter , settings , story , siteID , queue ) { if ( story === null || settings === null ) { return [ ] ; } return [ { element : ( < Option href = { getModerationLink ( { queue , storyID : story . id } ) } details = { getStoryDetails ( settings , story ) } > < GoToAriaInfo /> {story.metadata && story.metadata.title} < /Option>), onClickOrEnter , group : "CONTEXT" , } , { element : ( < ModerateAllOption href = { getModerationLink ( { queue , siteID : siteID || ( story && story . site . id ) , } ) } />), onClickOrEnter , group : "CONTEXT" , } , ] ; } type OnSearchCallback = ( search : string ) => void ; interface SearchParams { query : string ; limit : number ; siteID ? : string ; } param onClickOrEnter param story function useSearchOptions ( onClickOrEnter , story , siteID , queue ) { const searchStory = useFetch ( SearchStoryFetch ) ; const [ searchOptions , setSearchOptions ] = useState < SearchBarOptions > ( [ ] ) ; useEffectWhenChanged ( ( ) => { setSearchOptions ( [ ] ) ; } , [ story ] ) ; const searchCountRef = useRef ( 0 ) ; const onSearch = useCallback ( async ( search ) => { const nextSearchOptions = [ ] ; const searchCount = ++ searchCountRef . current ; setSearchOptions ( [ { element : ( < Option > < Spinner size = "xs" / > < /Option>), group : "SEARCH" , } , ] ) ; const searchParams = { query : search , limit : 5 , } ; if ( siteID ) { searchParams . siteID = siteID ; } const { settings , stories } = await searchStory ( searchParams ) ; if ( searchCount !== searchCountRef . current ) { return ; } if ( stories . edges . length > 0 ) { stories . edges . forEach ( ( e ) => { if ( story && story . id === e . node . id ) { return ; } nextSearchOptions . push ( { element : ( < Option href = { getModerationLink ( { queue , storyID : e . node . id , siteID : e . node . site . id , } ) } details = { getStoryDetails ( settings , e . node ) } > < GoToAriaInfo /> {e.node.metadata && e.node.metadata.title} < /Option>), onClickOrEnter , group : "SEARCH" , } ) ; } ) ; } else { nextSearchOptions . push ( { element : ( < Option > < Flex justifyContent = "center" > < Localized id = "moderate-searchBar-noStories" > < span > We could not find any stories matching your criteria < / span > < /Localized> < /Flex> < /Option>), group : "SEARCH" , } ) ; } if ( stories . pageInfo . hasNextPage ) { nextSearchOptions . push ( { element : ( < SeeAllOption href = { `/admin/stories?q=${ encodeURIComponent ( search ) }` } / > ) , onClickOrEnter , group : "SEARCH" , } ) ; } setSearchOptions ( nextSearchOptions ) ; } , [ siteID , searchStory , story , queue , onClickOrEnter ] ) ; return [ searchOptions , onSearch ] ; } const ModerateSearchBarContainer = ( props ) => { const linkNavHandler = useLinkNavHandler ( props . router ) ; const contextOptions = props . allStories ? getContextOptionsWhenModeratingAll ( linkNavHandler , props . siteID , props . queueName ) : getContextOptionsWhenModeratingStory ( linkNavHandler , props . settings , props . story , props . siteID , props . queueName ) ; const [ searchOptions , onSearch ] = useSearchOptions ( linkNavHandler , props . story , props . siteID , props . queueName ) ; const options = [ ... contextOptions , ... searchOptions ] ; const childProps = { options , onSearch , } ; if ( props . allStories ) { return ( < Localized id = "moderate-searchBar-allStories" attrs = { { title : true } } > < Bar siteSelector = { props . siteSelector } sectionSelector = { props . sectionSelector } multisite = { props . settings ? props . settings . multisite : false } title = "All stories" { ... childProps } /> < /Localized>); } if ( ! props . story ) { return ( < Bar multisite = { props . settings ? props . settings . multisite : false } siteSelector = { props . siteSelector } sectionSelector = { props . sectionSelector } title = { "" } { ... childProps } />); } const title = props . story . metadata && props . story . metadata . title ; if ( title ) { return ( < Bar multisite = { props . settings ? props . settings . multisite : false } siteSelector = { props . siteSelector } sectionSelector = { props . sectionSelector } title = { title } { ... childProps } />); } return ( < Localized id = "moderate-searchBar-titleNotAvailable" attrs = { { title : true } } > < Bar siteSelector = { props . siteSelector } sectionSelector = { props . sectionSelector } multisite = { props . settings ? props . settings . multisite : false } title = { "Title not available" } options = { options } onSearch = { onSearch } / > < /Localized>); } ; const enhanced = withRouter ( withFragmentContainer < Props > ( { settings : graphql `
      fragment ModerateSearchBarContainer_settings on Settings {
        multisite
        featureFlags
      }
    ` , story : graphql `
      fragment ModerateSearchBarContainer_story on Story {
        id
        site {
          name
          id
        }
        metadata {
          title
          author
          section
        }
      }
    ` , } ) ( ModerateSearchBarContainer ) ) ; export default enhanced ;