TrashPageList.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import React, { type JSX, useCallback, useMemo } from 'react';
  2. import dynamic from 'next/dynamic';
  3. import type { IPageHasId } from '@growi/core';
  4. import { useAtomValue } from 'jotai';
  5. import { useTranslation } from 'next-i18next';
  6. import { toastSuccess } from '~/client/util/toastr';
  7. import type { IPagingResult } from '~/interfaces/paging-result';
  8. import { useIsReadOnlyUser } from '~/states/context';
  9. import { showPageLimitationXLAtom } from '~/states/server-configurations';
  10. import { useEmptyTrashModalActions } from '~/states/ui/modal/empty-trash';
  11. import { useSWRxPageInfoForList, useSWRxPageList } from '~/stores/page-listing';
  12. import { MenuItemType } from './Common/Dropdown/PageItemControl';
  13. import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
  14. import type { DescendantsPageListProps } from './DescendantsPageList';
  15. import EmptyTrashButton from './EmptyTrashButton';
  16. const DescendantsPageList = dynamic<DescendantsPageListProps>(
  17. () => import('./DescendantsPageList').then((mod) => mod.DescendantsPageList),
  18. { ssr: false },
  19. );
  20. const convertToIDataWithMeta = (page) => {
  21. return { data: page };
  22. };
  23. const useEmptyTrashButton = () => {
  24. const { t } = useTranslation();
  25. const limit = useAtomValue(showPageLimitationXLAtom);
  26. const isReadOnlyUser = useIsReadOnlyUser();
  27. const { data: pagingResult, mutate: mutatePageLists } = useSWRxPageList(
  28. '/trash',
  29. 1,
  30. limit,
  31. );
  32. const { open: openEmptyTrashModal } = useEmptyTrashModalActions();
  33. const pageIds = pagingResult?.items?.map((page) => page._id);
  34. const { injectTo } = useSWRxPageInfoForList(pageIds, null, true, true);
  35. const calculateDeletablePages = useCallback(
  36. (pagingResult?: IPagingResult<IPageHasId>) => {
  37. if (pagingResult == null) {
  38. return undefined;
  39. }
  40. const dataWithMetas = pagingResult.items.map((page) =>
  41. convertToIDataWithMeta(page),
  42. );
  43. const pageWithMetas = injectTo(dataWithMetas);
  44. return pageWithMetas.filter(
  45. (page) => page.meta?.isAbleToDeleteCompletely,
  46. );
  47. },
  48. [injectTo],
  49. );
  50. const deletablePages = calculateDeletablePages(pagingResult);
  51. const onEmptiedTrashHandler = useCallback(() => {
  52. toastSuccess(t('empty_trash'));
  53. mutatePageLists();
  54. }, [t, mutatePageLists]);
  55. const emptyTrashClickHandler = useCallback(() => {
  56. if (deletablePages == null) {
  57. return;
  58. }
  59. openEmptyTrashModal(deletablePages, {
  60. onEmptiedTrash: onEmptiedTrashHandler,
  61. canDeleteAllPages: pagingResult?.totalCount === deletablePages.length,
  62. });
  63. }, [
  64. deletablePages,
  65. onEmptiedTrashHandler,
  66. openEmptyTrashModal,
  67. pagingResult?.totalCount,
  68. ]);
  69. const emptyTrashButton = useMemo(() => {
  70. return (
  71. <EmptyTrashButton
  72. onEmptyTrashButtonClick={emptyTrashClickHandler}
  73. disableEmptyButton={deletablePages?.length === 0 || !!isReadOnlyUser}
  74. />
  75. );
  76. }, [emptyTrashClickHandler, deletablePages?.length, isReadOnlyUser]);
  77. return emptyTrashButton;
  78. };
  79. const DescendantsPageListForTrash = (): JSX.Element => {
  80. const limit = useAtomValue(showPageLimitationXLAtom);
  81. return (
  82. <DescendantsPageList
  83. path="/trash"
  84. limit={limit}
  85. forceHideMenuItems={[MenuItemType.RENAME]}
  86. />
  87. );
  88. };
  89. const PageListIcon = () => (
  90. <span className="material-symbols-outlined">subject</span>
  91. );
  92. export const TrashPageList = (): JSX.Element => {
  93. const { t } = useTranslation();
  94. const emptyTrashButton = useEmptyTrashButton();
  95. const navTabMapping = useMemo(() => {
  96. return {
  97. pagelist: {
  98. Icon: PageListIcon,
  99. Content: DescendantsPageListForTrash,
  100. i18n: t('page_list'),
  101. },
  102. };
  103. }, [t]);
  104. return (
  105. <div data-testid="trash-page-list" className="d-edit-none">
  106. <CustomNavAndContents
  107. navTabMapping={navTabMapping}
  108. navRightElement={emptyTrashButton}
  109. />
  110. </div>
  111. );
  112. };
  113. TrashPageList.displayName = 'TrashPageList';