TrashPageList.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import React, { useMemo, useCallback } from 'react';
  2. import type { IPageHasId } from '@growi/core';
  3. import { useTranslation } from 'next-i18next';
  4. import dynamic from 'next/dynamic';
  5. import { toastSuccess } from '~/client/util/toastr';
  6. import { IPagingResult } from '~/interfaces/paging-result';
  7. import { useIsReadOnlyUser, useShowPageLimitationXL } from '~/stores/context';
  8. import { useEmptyTrashModal } from '~/stores/modal';
  9. import { useSWRxPageInfoForList, useSWRxPageList } from '~/stores/page-listing';
  10. import { MenuItemType } from './Common/Dropdown/PageItemControl';
  11. import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
  12. import { DescendantsPageListProps } from './DescendantsPageList';
  13. import EmptyTrashButton from './EmptyTrashButton';
  14. import PageListIcon from './Icons/PageListIcon';
  15. const DescendantsPageList = dynamic<DescendantsPageListProps>(() => import('./DescendantsPageList').then(mod => mod.DescendantsPageList), { ssr: false });
  16. const convertToIDataWithMeta = (page) => {
  17. return { data: page };
  18. };
  19. const useEmptyTrashButton = () => {
  20. const { t } = useTranslation();
  21. const { data: limit } = useShowPageLimitationXL();
  22. const { data: isReadOnlyUser } = useIsReadOnlyUser();
  23. const { data: pagingResult, mutate: mutatePageLists } = useSWRxPageList('/trash', 1, limit);
  24. const { open: openEmptyTrashModal } = useEmptyTrashModal();
  25. const pageIds = pagingResult?.items?.map(page => page._id);
  26. const { injectTo } = useSWRxPageInfoForList(pageIds, null, true, true);
  27. const calculateDeletablePages = useCallback((pagingResult?: IPagingResult<IPageHasId>) => {
  28. if (pagingResult == null) { return undefined }
  29. const dataWithMetas = pagingResult.items.map(page => convertToIDataWithMeta(page));
  30. const pageWithMetas = injectTo(dataWithMetas);
  31. return pageWithMetas.filter(page => page.meta?.isAbleToDeleteCompletely);
  32. }, [injectTo]);
  33. const deletablePages = calculateDeletablePages(pagingResult);
  34. const onEmptiedTrashHandler = useCallback(() => {
  35. toastSuccess(t('empty_trash'));
  36. mutatePageLists();
  37. }, [t, mutatePageLists]);
  38. const emptyTrashClickHandler = useCallback(() => {
  39. if (deletablePages == null) { return }
  40. openEmptyTrashModal(deletablePages, { onEmptiedTrash: onEmptiedTrashHandler, canDeleteAllPages: pagingResult?.totalCount === deletablePages.length });
  41. }, [deletablePages, onEmptiedTrashHandler, openEmptyTrashModal, pagingResult?.totalCount]);
  42. const emptyTrashButton = useMemo(() => {
  43. return <EmptyTrashButton onEmptyTrashButtonClick={emptyTrashClickHandler} disableEmptyButton={deletablePages?.length === 0 || !!isReadOnlyUser} />;
  44. }, [emptyTrashClickHandler, deletablePages?.length, isReadOnlyUser]);
  45. return emptyTrashButton;
  46. };
  47. const DescendantsPageListForTrash = (): JSX.Element => {
  48. const { data: limit } = useShowPageLimitationXL();
  49. return (
  50. <DescendantsPageList
  51. path="/trash"
  52. limit={limit}
  53. forceHideMenuItems={[MenuItemType.RENAME]}
  54. />
  55. );
  56. };
  57. export const TrashPageList = (): JSX.Element => {
  58. const { t } = useTranslation();
  59. const emptyTrashButton = useEmptyTrashButton();
  60. const navTabMapping = useMemo(() => {
  61. return {
  62. pagelist: {
  63. Icon: PageListIcon,
  64. Content: DescendantsPageListForTrash,
  65. i18n: t('page_list'),
  66. },
  67. };
  68. }, [t]);
  69. return (
  70. <div data-testid="trash-page-list" className="mt-5 d-edit-none">
  71. <CustomNavAndContents navTabMapping={navTabMapping} navRightElement={emptyTrashButton} />
  72. </div>
  73. );
  74. };
  75. TrashPageList.displayName = 'TrashPageList';