DescendantsPageList.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import React, { useCallback, useState } from 'react';
  2. import type {
  3. IDataWithMeta,
  4. IPageHasId,
  5. IPageInfoForOperation,
  6. } from '@growi/core';
  7. import { LoadingSpinner } from '@growi/ui/dist/components';
  8. import { useTranslation } from 'next-i18next';
  9. import { toastSuccess } from '~/client/util/toastr';
  10. import type { IPagingResult } from '~/interfaces/paging-result';
  11. import type { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
  12. import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/stores-universal/context';
  13. import {
  14. mutatePageTree,
  15. useSWRxPageInfoForList, useSWRxPageList, mutateRecentlyUpdated,
  16. } from '~/stores/page-listing';
  17. import type { ForceHideMenuItems } from './Common/Dropdown/PageItemControl';
  18. import PageList from './PageList/PageList';
  19. import PaginationWrapper from './PaginationWrapper';
  20. type SubstanceProps = {
  21. pagingResult: IPagingResult<IPageHasId> | undefined,
  22. activePage: number,
  23. setActivePage: (activePage: number) => void,
  24. forceHideMenuItems?: ForceHideMenuItems,
  25. onPagesDeleted?: OnDeletedFunction,
  26. onPagePutBacked?: OnPutBackedFunction,
  27. }
  28. const convertToIDataWithMeta = (page: IPageHasId): IDataWithMeta<IPageHasId> => {
  29. return { data: page };
  30. };
  31. const DescendantsPageListSubstance = (props: SubstanceProps): React.ReactElement => {
  32. const { t } = useTranslation();
  33. const {
  34. pagingResult, activePage, setActivePage, forceHideMenuItems, onPagesDeleted, onPagePutBacked,
  35. } = props;
  36. const { data: isGuestUser } = useIsGuestUser();
  37. const { data: isReadOnlyUser } = useIsReadOnlyUser();
  38. const pageIds = pagingResult?.items?.map(page => page._id);
  39. const { injectTo } = useSWRxPageInfoForList(pageIds, null, true, true);
  40. let pageWithMetas: IDataWithMeta<IPageHasId, IPageInfoForOperation>[] = [];
  41. // initial data
  42. if (pagingResult != null) {
  43. // convert without meta at first
  44. const dataWithMetas = pagingResult.items.map(page => convertToIDataWithMeta(page));
  45. // inject data for listing
  46. pageWithMetas = injectTo(dataWithMetas);
  47. }
  48. const pageDeletedHandler: OnDeletedFunction = useCallback((...args) => {
  49. const path = args[0];
  50. const isCompletely = args[2];
  51. if (path == null || isCompletely == null) {
  52. toastSuccess(t('deleted_page'));
  53. }
  54. else {
  55. toastSuccess(t('deleted_pages_completely', { path }));
  56. }
  57. mutateRecentlyUpdated();
  58. mutatePageTree();
  59. if (onPagesDeleted != null) {
  60. onPagesDeleted(...args);
  61. }
  62. }, [onPagesDeleted, t]);
  63. const pagePutBackedHandler: OnPutBackedFunction = useCallback((path) => {
  64. toastSuccess(t('page_has_been_reverted', { path }));
  65. mutateRecentlyUpdated();
  66. mutatePageTree();
  67. if (onPagePutBacked != null) {
  68. onPagePutBacked(path);
  69. }
  70. }, [onPagePutBacked, t]);
  71. if (pagingResult == null) {
  72. return (
  73. <div className="wiki">
  74. <div className="text-muted text-center">
  75. <LoadingSpinner className="me-1 fs-3" />
  76. </div>
  77. </div>
  78. );
  79. }
  80. const showPager = pagingResult.totalCount > pagingResult.limit;
  81. return (
  82. <>
  83. <PageList
  84. pages={pageWithMetas}
  85. isEnableActions={!isGuestUser}
  86. isReadOnlyUser={!!isReadOnlyUser}
  87. forceHideMenuItems={forceHideMenuItems}
  88. onPagesDeleted={pageDeletedHandler}
  89. onPagePutBacked={pagePutBackedHandler}
  90. />
  91. { showPager && (
  92. <div className="my-4">
  93. <PaginationWrapper
  94. activePage={activePage}
  95. changePage={selectedPageNumber => setActivePage(selectedPageNumber)}
  96. totalItemsCount={pagingResult.totalCount}
  97. pagingLimit={pagingResult.limit}
  98. align="center"
  99. />
  100. </div>
  101. ) }
  102. </>
  103. );
  104. };
  105. export type DescendantsPageListProps = {
  106. path: string,
  107. limit?: number,
  108. forceHideMenuItems?: ForceHideMenuItems,
  109. }
  110. export const DescendantsPageList = (props: DescendantsPageListProps): React.ReactElement => {
  111. const { path, limit, forceHideMenuItems } = props;
  112. const [activePage, setActivePage] = useState(1);
  113. const { data: isSharedUser } = useIsSharedUser();
  114. const { data: pagingResult, error, mutate } = useSWRxPageList(isSharedUser ? null : path, activePage, limit);
  115. if (error != null) {
  116. return (
  117. <div className="my-5">
  118. <div className="text-danger">{error.message}</div>
  119. </div>
  120. );
  121. }
  122. return (
  123. <DescendantsPageListSubstance
  124. pagingResult={pagingResult}
  125. activePage={activePage}
  126. setActivePage={setActivePage}
  127. forceHideMenuItems={forceHideMenuItems}
  128. onPagesDeleted={() => mutate()}
  129. onPagePutBacked={() => mutate()}
  130. />
  131. );
  132. };