diff --git a/package.json b/package.json index 2237e2b60..f748de73d 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "express": "^4.17.1", "humanize-duration": "^3.27.0", "if-env": "^1.0.4", - "match-sorter": "^6.3.0", "parse-duration": "^1.0.0", "react-datetime-picker": "^3.2.1", "react-plastic": "^1.1.1", diff --git a/ui/component/blockList/index.js b/ui/component/blockList/index.js deleted file mode 100644 index e9f61035d..000000000 --- a/ui/component/blockList/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import BlockList from './view'; -export default BlockList; diff --git a/ui/component/blockList/view.jsx b/ui/component/blockList/view.jsx deleted file mode 100644 index 3df4fb6db..000000000 --- a/ui/component/blockList/view.jsx +++ /dev/null @@ -1,204 +0,0 @@ -// @flow -import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox'; -// import '@reach/combobox/styles.css'; --> 'scss/third-party.scss' -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 [searchList, setSearchList] = React.useState(null); // null: not searching; []: no results; - const [page, setPage] = React.useState(1); - - 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}
-
- -
-
- -
- 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 found')} - )} - - )} - -
- ); -} - -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 -} diff --git a/ui/component/common/paginate.jsx b/ui/component/common/paginate.jsx index dc1a8c67f..f082285e6 100644 --- a/ui/component/common/paginate.jsx +++ b/ui/component/common/paginate.jsx @@ -10,18 +10,16 @@ const PAGINATE_PARAM = 'page'; type Props = { totalPages: number, location: { search: string }, - history: { push: (string) => void }, - onPageChange?: (number) => void, - disableHistory?: boolean, // Disables the use of '&page=' param and history stack. + history: { push: string => void }, + onPageChange?: number => void, }; function Paginate(props: Props) { - const { totalPages = 1, location, history, onPageChange, disableHistory } = props; + const { totalPages = 1, location, history, onPageChange } = props; const { search } = location; const [textValue, setTextValue] = React.useState(''); const urlParams = new URLSearchParams(search); - const initialPage = disableHistory ? 1 : Number(urlParams.get(PAGINATE_PARAM)) || 1; - const [currentPage, setCurrentPage] = React.useState(initialPage); + const currentPage = Number(urlParams.get(PAGINATE_PARAM)) || 1; const isMobile = useIsMobile(); function handleChangePage(newPageNumber: number) { @@ -30,13 +28,9 @@ function Paginate(props: Props) { } if (currentPage !== newPageNumber) { - setCurrentPage(newPageNumber); - - if (!disableHistory) { - const params = new URLSearchParams(search); - params.set(PAGINATE_PARAM, newPageNumber.toString()); - history.push('?' + params.toString()); - } + const params = new URLSearchParams(search); + params.set(PAGINATE_PARAM, newPageNumber.toString()); + history.push('?' + params.toString()); } } @@ -64,7 +58,7 @@ function Paginate(props: Props) { nextClassName="pagination__item pagination__item--next" breakClassName="pagination__item pagination__item--break" marginPagesDisplayed={2} - onPageChange={(e) => handleChangePage(e.selected + 1)} + onPageChange={e => handleChangePage(e.selected + 1)} forcePage={currentPage - 1} initialPage={currentPage - 1} containerClassName="pagination" @@ -73,7 +67,7 @@ function Paginate(props: Props) { {!isMobile && ( setTextValue(e.target.value)} + onChange={e => setTextValue(e.target.value)} className="paginate-channel" label={__('Go to page:')} type="text" diff --git a/ui/page/listBlocked/view.jsx b/ui/page/listBlocked/view.jsx index 7e25a665b..8036a8f1f 100644 --- a/ui/page/listBlocked/view.jsx +++ b/ui/page/listBlocked/view.jsx @@ -5,7 +5,7 @@ import React from 'react'; import classnames from 'classnames'; import moment from 'moment'; import humanizeDuration from 'humanize-duration'; -import BlockList from 'component/blockList'; +import ClaimList from 'component/claimList'; import ClaimPreview from 'component/claimPreview'; import Page from 'component/page'; import Spinner from 'component/spinner'; @@ -13,6 +13,7 @@ import Button from 'component/button'; import usePersistedState from 'effects/use-persisted-state'; import ChannelBlockButton from 'component/channelBlockButton'; import ChannelMuteButton from 'component/channelMuteButton'; +import Yrbl from 'component/yrbl'; const VIEW = { BLOCKED: 'blocked', @@ -46,7 +47,7 @@ function ListBlocked(props: Props) { personalTimeoutMap, adminTimeoutMap, moderatorTimeoutMap, - moderatorBlockListDelegatorsMap: delegatorsMap, + moderatorBlockListDelegatorsMap, fetchingModerationBlockList, fetchModBlockedList, fetchModAmIList, @@ -55,36 +56,55 @@ function ListBlocked(props: Props) { } = props; const [viewMode, setViewMode] = usePersistedState('blocked-muted:display', VIEW.BLOCKED); - const [localDelegatorsMap, setLocalDelegatorsMap] = React.useState(undefined); + // Keep a local list to allow for undoing actions in this component + const [localPersonalList, setLocalPersonalList] = React.useState(undefined); + const [localAdminList, setLocalAdminList] = React.useState(undefined); + const [localModeratorList, setLocalModeratorList] = React.useState(undefined); + const [localModeratorListDelegatorsMap, setLocalModeratorListDelegatorsMap] = React.useState(undefined); + const [localMutedList, setLocalMutedList] = React.useState(undefined); - const stringifiedDelegatorsMap = JSON.stringify(delegatorsMap); - const stringifiedLocalDelegatorsMap = JSON.stringify(localDelegatorsMap); + const hasLocalMuteList = localMutedList && localMutedList.length > 0; + const hasLocalPersonalList = localPersonalList && localPersonalList.length > 0; + + const stringifiedMutedList = JSON.stringify(mutedUris); + const stringifiedPersonalList = JSON.stringify(personalBlockList); + const stringifiedAdminList = JSON.stringify(adminBlockList); + const stringifiedModeratorList = JSON.stringify(moderatorBlockList); + const stringifiedModeratorListDelegatorsMap = JSON.stringify(moderatorBlockListDelegatorsMap); + + const stringifiedLocalAdminList = JSON.stringify(localAdminList); + const stringifiedLocalModeratorList = JSON.stringify(localModeratorList); + const stringifiedLocalModeratorListDelegatorsMap = JSON.stringify(localModeratorListDelegatorsMap); + + const justMuted = localMutedList && mutedUris && localMutedList.length < mutedUris.length; + const justPersonalBlocked = + localPersonalList && personalBlockList && localPersonalList.length < personalBlockList.length; const isAdmin = myChannelClaims && myChannelClaims.some((c) => delegatorsById[c.claim_id] && delegatorsById[c.claim_id].global); - const isModerator = myChannelClaims && myChannelClaims.some( (c) => delegatorsById[c.claim_id] && Object.keys(delegatorsById[c.claim_id].delegators).length > 0 ); - // ************************************************************************** + const listForView = getLocalList(viewMode); + const showUris = listForView && listForView.length > 0; - function getList(view) { + function getLocalList(view) { switch (view) { case VIEW.BLOCKED: - return personalBlockList; + return localPersonalList; case VIEW.ADMIN: - return adminBlockList; + return localAdminList; case VIEW.MODERATOR: - return moderatorBlockList; + return localModeratorList; case VIEW.MUTED: - return mutedUris; + return localMutedList; } } - function getActionButtons(uri) { + function getButtons(view, uri) { const getDurationStr = (durationNs) => { const NANO_TO_MS = 1000000; return humanizeDuration(durationNs / NANO_TO_MS, { round: true }); @@ -107,7 +127,7 @@ function ListBlocked(props: Props) { ); }; - switch (viewMode) { + switch (view) { case VIEW.BLOCKED: return ( <> @@ -126,21 +146,18 @@ function ListBlocked(props: Props) { ); case VIEW.MODERATOR: - const delegatorUrisForBlockedUri = localDelegatorsMap && localDelegatorsMap[uri]; + const delegatorUrisForBlockedUri = localModeratorListDelegatorsMap && localModeratorListDelegatorsMap[uri]; if (!delegatorUrisForBlockedUri) return null; return ( <> {delegatorUrisForBlockedUri.map((delegatorUri) => { return (
- -
    -
    - - {moderatorTimeoutMap[uri] && getBanInfoElem(moderatorTimeoutMap[uri])} -
    - +
      +
    + + {moderatorTimeoutMap[uri] && getBanInfoElem(moderatorTimeoutMap[uri])}
); })} @@ -201,43 +218,52 @@ function ListBlocked(props: Props) { return source && (!local || local.length < source.length); } - function getViewElem(view, label, icon) { - return ( -