// @flow import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox'; import { matchSorter } from 'match-sorter'; import React from 'react'; import classnames from 'classnames'; import Button from 'component/button'; import ClaimList from 'component/claimList'; import Icon from 'component/common/icon'; import Paginate from 'component/common/paginate'; import Yrbl from 'component/yrbl'; import * as ICONS from 'constants/icons'; import useThrottle from 'effects/use-throttle'; const PAGE_SIZE = 10; function reduceUriToChannelName(uri: string) { // 'parseURI' is too slow to handle a large list. Since our list should be // kosher in the first place, just do a quick substring call. Add a // try-catch just in case. try { return uri.substring(uri.indexOf('@') + 1, uri.indexOf('#')); } catch { return uri; } } // **************************************************************************** // BlockList // **************************************************************************** type Props = { uris: Array, help: string, titleEmptyList: string, subtitleEmptyList: string, getActionButtons?: (url: string) => React$Node, className: ?string, }; export default function BlockList(props: Props) { const { uris: list, help, titleEmptyList, subtitleEmptyList, getActionButtons, className } = props; // Keep a local list to allow for undoing actions in this component const [localList, setLocalList] = React.useState(undefined); const stringifiedList = JSON.stringify(list); const hasLocalList = localList && localList.length > 0; const justBlocked = list && localList && localList.length < list.length; const [page, setPage] = React.useState(1); const [searchList, setSearchList] = React.useState(null); // null: not searching; []: no results; const isShowingSearchResults = searchList !== null; let totalPages = 0; let paginatedLocalList; if (localList) { paginatedLocalList = localList.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); totalPages = Math.ceil(localList.length / PAGE_SIZE); } // ************************************************************************** // ************************************************************************** function getRenderActions() { if (getActionButtons) { return (claim) =>
{getActionButtons(claim.permanent_url)}
; } return undefined; } function formatSearchSuggestion(suggestion: string) { return reduceUriToChannelName(suggestion); } function filterSearchResults(results: ?Array) { setSearchList(results); } // ************************************************************************** // ************************************************************************** React.useEffect(() => { const list = stringifiedList && JSON.parse(stringifiedList); if (!hasLocalList) { setLocalList(list && list.length > 0 ? list : []); } }, [stringifiedList, hasLocalList]); React.useEffect(() => { if (justBlocked && stringifiedList) { setLocalList(JSON.parse(stringifiedList)); } }, [stringifiedList, justBlocked, setLocalList]); // ************************************************************************** // ************************************************************************** if (paginatedLocalList === undefined) { return null; } if (!hasLocalList) { return (
} /> ); } return ( <>
{help}
{!isShowingSearchResults && setPage(p)} />} ); } // **************************************************************************** // SearchList // **************************************************************************** type LsbProps = { list: ?Array, placeholder?: string, formatter?: (suggestion: string) => string, onResultsUpdated?: (?Array) => void, }; function SearchList(props: LsbProps) { const { list, placeholder, formatter, onResultsUpdated } = props; const [term, setTerm] = React.useState(''); const results = useAuthorMatch(term, list); const handleChange = (event) => setTerm(event.target.value); const handleSelect = (e) => setTerm(e); React.useEffect(() => { if (onResultsUpdated) { onResultsUpdated(results); } }, [results]); // eslint-disable-line react-hooks/exhaustive-deps return (
{results && ( {results.length > 0 ? ( {results.slice(0, 10).map((result, index) => ( ))} ) : ( {__('No results')} )} )}
); } function useAuthorMatch(term, list) { const throttledTerm = useThrottle(term, 200); return React.useMemo(() => { return !throttledTerm || throttledTerm.trim() === '' ? null : matchSorter(list, throttledTerm, { keys: [(item) => reduceUriToChannelName(item)], threshold: matchSorter.rankings.CONTAINS, }); }, [throttledTerm]); // eslint-disable-line react-hooks/exhaustive-deps }