TrashPageList.tsx 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  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 type { 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 type { DescendantsPageListProps } from './DescendantsPageList';
  13. import EmptyTrashButton from './EmptyTrashButton';
  14. const DescendantsPageList = dynamic<DescendantsPageListProps>(() => import('./DescendantsPageList').then(mod => mod.DescendantsPageList), { ssr: false });
  15. const convertToIDataWithMeta = (page) => {
  16. return { data: page };
  17. };
  18. const useEmptyTrashButton = () => {
  19. const { t } = useTranslation();
  20. const { data: limit } = useShowPageLimitationXL();
  21. const { data: isReadOnlyUser } = useIsReadOnlyUser();
  22. const { data: pagingResult, mutate: mutatePageLists } = useSWRxPageList('/trash', 1, limit);
  23. const { open: openEmptyTrashModal } = useEmptyTrashModal();
  24. const pageIds = pagingResult?.items?.map(page => page._id);
  25. const { injectTo } = useSWRxPageInfoForList(pageIds, null, true, true);
  26. const calculateDeletablePages = useCallback((pagingResult?: IPagingResult<IPageHasId>) => {
  27. if (pagingResult == null) { return undefined }
  28. const dataWithMetas = pagingResult.items.map(page => convertToIDataWithMeta(page));
  29. const pageWithMetas = injectTo(dataWithMetas);
  30. return pageWithMetas.filter(page => page.meta?.isAbleToDeleteCompletely);
  31. }, [injectTo]);
  32. const deletablePages = calculateDeletablePages(pagingResult);
  33. const onEmptiedTrashHandler = useCallback(() => {
  34. toastSuccess(t('empty_trash'));
  35. mutatePageLists();
  36. }, [t, mutatePageLists]);
  37. const emptyTrashClickHandler = useCallback(() => {
  38. if (deletablePages == null) { return }
  39. openEmptyTrashModal(deletablePages, { onEmptiedTrash: onEmptiedTrashHandler, canDeleteAllPages: pagingResult?.totalCount === deletablePages.length });
  40. }, [deletablePages, onEmptiedTrashHandler, openEmptyTrashModal, pagingResult?.totalCount]);
  41. const emptyTrashButton = useMemo(() => {
  42. return <EmptyTrashButton onEmptyTrashButtonClick={emptyTrashClickHandler} disableEmptyButton={deletablePages?.length === 0 || !!isReadOnlyUser} />;
  43. }, [emptyTrashClickHandler, deletablePages?.length, isReadOnlyUser]);
  44. return emptyTrashButton;
  45. };
  46. const DescendantsPageListForTrash = (): JSX.Element => {
  47. const { data: limit } = useShowPageLimitationXL();
  48. return (
  49. <DescendantsPageList
  50. path="/trash"
  51. limit={limit}
  52. forceHideMenuItems={[MenuItemType.RENAME]}
  53. />
  54. );
  55. };
  56. export const TrashPageList = (): JSX.Element => {
  57. const { t } = useTranslation();
  58. const emptyTrashButton = useEmptyTrashButton();
  59. const navTabMapping = useMemo(() => {
  60. return {
  61. pagelist: {
  62. Icon: () => <span className="material-symbols-outlined">subject</span>,
  63. Content: DescendantsPageListForTrash,
  64. i18n: t('page_list'),
  65. },
  66. };
  67. }, [t]);
  68. return (
  69. <div data-testid="trash-page-list" className="mt-5 d-edit-none">
  70. <CustomNavAndContents navTabMapping={navTabMapping} navRightElement={emptyTrashButton} />
  71. </div>
  72. );
  73. };
  74. TrashPageList.displayName = 'TrashPageList';