import React, { useCallback, useMemo, useRef, useState, useEffect, } from 'react'; import { useTranslation } from 'react-i18next'; import { UncontrolledButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem, Modal, ModalHeader, ModalBody, ModalFooter, } from 'reactstrap'; import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all'; import AppContainer from '~/client/services/AppContainer'; import { toastSuccess, toastError } from '~/client/util/apiNotification'; import { apiv3Post } from '~/client/util/apiv3-client'; import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error'; import { V5MigrationStatus } from '~/interfaces/page-listing-results'; import { IFormattedSearchResult } from '~/interfaces/search'; import { PageMigrationErrorData, SocketEventName } from '~/interfaces/websocket'; import { useCurrentUser } from '~/stores/context'; import { ILegacyPrivatePage, usePrivateLegacyPagesMigrationModal, } from '~/stores/modal'; import { useSWRxV5MigrationStatus } from '~/stores/page-listing'; import { useSWRxSearch, } from '~/stores/search'; import { useGlobalSocket } from '~/stores/websocket'; import { MenuItemType } from './Common/Dropdown/PageItemControl'; import PaginationWrapper from './PaginationWrapper'; import { PrivateLegacyPagesMigrationModal } from './PrivateLegacyPagesMigrationModal'; import { OperateAllControl } from './SearchPage/OperateAllControl'; import SearchControl from './SearchPage/SearchControl'; import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage2/SearchPageBase'; // TODO: replace with "customize:showPageLimitationS" const INITIAL_PAGING_SIZE = 20; const initQ = '/'; /** * SearchResultListHead */ type SearchResultListHeadProps = { searchResult: IFormattedSearchResult, offset: number, pagingSize: number, onPagingSizeChanged: (size: number) => void, migrationStatus?: V5MigrationStatus, } const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.Element => { const { t } = useTranslation(); const { searchResult, offset, pagingSize, onPagingSizeChanged, migrationStatus, } = props; if (migrationStatus == null) { return (
); } const { took, total, hitsCount } = searchResult.meta; const leftNum = offset + 1; const rightNum = offset + hitsCount; const isSuccess = migrationStatus.migratablePagesCount === 0; if (isSuccess) { return (

{t('private_legacy_pages.nopages_title')}

{t('private_legacy_pages.nopages_desc1')}
{/* eslint-disable-next-line react/no-danger */}

); } return ( <>
{t('search_result.result_meta')} {`${leftNum}-${rightNum}`} / {total} { took != null && ( ({took}ms) ) }

{t('private_legacy_pages.alert_title')}

{t('private_legacy_pages.alert_desc1', { delete_all_selected_page: t('search_result.delete_all_selected_page') })}
{/* eslint-disable-next-line react/no-danger */}

); }); /* * ConvertByPathModal */ type ConvertByPathModalProps = { isOpen: boolean, close?: () => void, onSubmit?: (convertPath: string) => Promise | void, } const ConvertByPathModal = React.memo((props: ConvertByPathModalProps): JSX.Element => { const { t } = useTranslation(); const [currentInput, setInput] = useState(''); const [checked, setChecked] = useState(false); useEffect(() => { setChecked(false); }, [props.isOpen]); return ( { t('private_legacy_pages.by_path_modal.title') }

{t('private_legacy_pages.by_path_modal.description')}

setInput(e.target.value)} />
{ t('private_legacy_pages.by_path_modal.alert') }
setChecked(e.target.checked)} />
); }); /** * LegacyPage */ type Props = { appContainer: AppContainer, } const PrivateLegacyPages = (props: Props): JSX.Element => { const { t } = useTranslation(); const { data: currentUser } = useCurrentUser(); const isAdmin = currentUser?.admin; const { appContainer, } = props; const [keyword, setKeyword] = useState(initQ); const [offset, setOffset] = useState(0); const [limit, setLimit] = useState(INITIAL_PAGING_SIZE); const [isOpenConvertModal, setOpenConvertModal] = useState(false); const [isControlEnabled, setControlEnabled] = useState(false); const selectAllControlRef = useRef(null); const searchPageBaseRef = useRef(null); const { data, conditions, mutate } = useSWRxSearch(keyword, 'PrivateLegacyPages', { offset, limit, includeUserPages: true, includeTrashPages: false, }); const { data: migrationStatus, mutate: mutateMigrationStatus } = useSWRxV5MigrationStatus(); const searchInvokedHandler = useCallback((_keyword: string) => { mutateMigrationStatus(); setKeyword(_keyword); setOffset(0); }, []); const { open: openModal, close: closeModal } = usePrivateLegacyPagesMigrationModal(); const { data: socket } = useGlobalSocket(); useEffect(() => { socket?.on(SocketEventName.PageMigrationSuccess, () => { toastSuccess(t('private_legacy_pages.toaster.page_migration_succeeded')); }); socket?.on(SocketEventName.PageMigrationError, (data?: PageMigrationErrorData) => { if (data == null || data.paths.length === 0) { toastError(t('private_legacy_pages.toaster.page_migration_failed')); } else { const errorPaths = data.paths.length > 3 ? `${data.paths.slice(0, 3).join(', ')}...` : data.paths.join(', '); toastError(t('private_legacy_pages.toaster.page_migration_failed_with_paths', { paths: errorPaths })); } }); return () => { socket?.off(SocketEventName.PageMigrationSuccess); socket?.off(SocketEventName.PageMigrationError); }; }, [socket]); const selectAllCheckboxChangedHandler = useCallback((isChecked: boolean) => { const instance = searchPageBaseRef.current; if (instance == null) { return; } if (isChecked) { instance.selectAll(); setControlEnabled(true); } else { instance.deselectAll(); setControlEnabled(false); } }, []); const selectedPagesByCheckboxesChangedHandler = useCallback((selectedCount: number, totalCount: number) => { const instance = selectAllControlRef.current; if (instance == null) { return; } if (selectedCount === 0) { instance.deselect(); setControlEnabled(false); } else if (selectedCount === totalCount) { instance.select(); setControlEnabled(true); } else { instance.setIndeterminate(); setControlEnabled(true); } }, []); // for bulk deletion const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate()); const convertMenuItemClickedHandler = useCallback(() => { if (data == null) { return; } const instance = searchPageBaseRef.current; if (instance == null || instance.getSelectedPageIds == null) { return; } const selectedPageIds = instance.getSelectedPageIds(); if (selectedPageIds.size === 0) { return; } const selectedPages = data.data .filter(pageWithMeta => selectedPageIds.has(pageWithMeta.data._id)) .map(pageWithMeta => ({ pageId: pageWithMeta.data._id, path: pageWithMeta.data.path } as ILegacyPrivatePage)); openModal( selectedPages, () => { toastSuccess(t('Successfully requested')); closeModal(); mutateMigrationStatus(); mutate(); }, ); }, [data, mutate, openModal, closeModal, mutateMigrationStatus]); const pagingSizeChangedHandler = useCallback((pagingSize: number) => { setOffset(0); setLimit(pagingSize); mutate(); }, [mutate]); const pagingNumberChangedHandler = useCallback((activePage: number) => { setOffset((activePage - 1) * limit); mutate(); }, [limit, mutate]); const openConvertModalHandler = useCallback(() => { if (!isAdmin) { return } setOpenConvertModal(true); }, [isAdmin]); const hitsCount = data?.meta.hitsCount; const renderOpenModalButton = useCallback(() => { return (
); }, [t, openConvertModalHandler]); const searchControlAllAction = useMemo(() => { const isCheckboxDisabled = hitsCount === 0; return (
{t('private_legacy_pages.bulk_operation')} {t('private_legacy_pages.convert_all_selected_pages')} {t('search_result.delete_all_selected_page')}
{isAdmin && renderOpenModalButton()}
); }, [convertMenuItemClickedHandler, deleteAllButtonClickedHandler, hitsCount, isControlEnabled, selectAllCheckboxChangedHandler, t]); const searchControl = useMemo(() => { return ( ); }, [searchInvokedHandler, searchControlAllAction]); const searchResultListHead = useMemo(() => { if (data == null) { return <>; } return ( ); }, [data, limit, offset, pagingSizeChangedHandler, migrationStatus]); const searchPager = useMemo(() => { // when pager is not needed if (data == null || data.meta.hitsCount === data.meta.total) { return <>; } const { total } = data.meta; const { offset, limit } = conditions; return ( ); }, [conditions, data, pagingNumberChangedHandler]); return ( <> setOpenConvertModal(false)} onSubmit={async(convertPath: string) => { try { await apiv3Post('/pages/convert-pages-by-path', { convertPath, }); toastSuccess(t('private_legacy_pages.by_path_modal.success')); setOpenConvertModal(false); } catch (errs) { if (errs.length === 1) { switch (errs[0].code) { case V5ConversionErrCode.GRANT_INVALID: toastError(t('private_legacy_pages.by_path_modal.error_grant_invalid')); break; case V5ConversionErrCode.PAGE_NOT_FOUND: toastError(t('private_legacy_pages.by_path_modal.error_page_not_found')); break; case V5ConversionErrCode.DUPLICATE_PAGES_FOUND: toastError(t('private_legacy_pages.by_path_modal.error_duplicate_pages_found')); break; default: toastError(t('private_legacy_pages.by_path_modal.error')); } } else { toastError(t('private_legacy_pages.by_path_modal.error')); } } }} /> ); }; export default PrivateLegacyPages;