yuken 3 лет назад
Родитель
Сommit
34940719b4

+ 5 - 0
packages/app/src/components/PageAlert/PageAlerts.tsx

@@ -1,6 +1,9 @@
+import dynamic from 'next/dynamic'
 import { FixPageGrantAlert } from "./FixPageGrantAlert";
 import { FixPageGrantAlert } from "./FixPageGrantAlert";
 import { PageGrantAlert } from "./PageGrantAlert";
 import { PageGrantAlert } from "./PageGrantAlert";
 
 
+const TrashPageAlert = dynamic(() => import('./TrashPageAlert').then(mod => mod.TrashPageAlert), {ssr: false})
+
 
 
 export const PageAlerts = (): JSX.Element => {
 export const PageAlerts = (): JSX.Element => {
 
 
@@ -12,6 +15,8 @@ export const PageAlerts = (): JSX.Element => {
         <FixPageGrantAlert/>
         <FixPageGrantAlert/>
 
 
         <PageGrantAlert/>
         <PageGrantAlert/>
+
+        <TrashPageAlert/>
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 114 - 0
packages/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -0,0 +1,114 @@
+import React from 'react';
+
+import { UserPicture } from '@growi/ui';
+import { useTranslation } from 'react-i18next';
+
+import { useCurrentUpdatedAt, useIsTrashPage, useShareLinkId, useLastUpdateUsername, useDeletedAt, useRevisionId } from '~/stores/context';
+import { usePageDeleteModal, usePutBackPageModal } from '~/stores/modal';
+import { useSWRxPageInfo, useSWRxCurrentPage } from '~/stores/page';
+import { useIsAbleToShowTrashPageManagementButtons } from '~/stores/ui';
+
+const onDeletedHandler = (pathOrPathsToDelete, isRecursively, isCompletely) => {
+  if (typeof pathOrPathsToDelete !== 'string') {
+    return;
+  }
+
+  // window.location.href = '/';
+};
+
+export const TrashPageAlert = () => {
+  const { t } = useTranslation();
+
+  const { data: isAbleToShowTrashPageManagementButtons } = useIsAbleToShowTrashPageManagementButtons();
+  const { data: shareLinkId } = useShareLinkId();
+  const { data: pageData } = useSWRxCurrentPage();
+  const { data: isTrashPage } = useIsTrashPage();
+  const { data: lastUpdateUserName } = useLastUpdateUsername();
+  const { data: deletedAt } = useDeletedAt();
+  const { data: revisionId } = useRevisionId();
+  const pageId  = pageData?._id;
+  const pagePath = pageData?.path
+
+  /*
+  * TODO: Do not use useSWRxPageInfo on this component
+  * Ideal: use useSWRxPageInfo on TrashPage after applying Next.js
+  * Reference: https://github.com/weseek/growi/pull/5359#discussion_r808381329
+  */
+  const { data: pageInfo } = useSWRxPageInfo(pageId ?? null, shareLinkId);
+
+  const { data: updatedAt } = useCurrentUpdatedAt();
+
+  const { open: openDeleteModal } = usePageDeleteModal();
+  const { open: openPutBackPageModal } = usePutBackPageModal();
+
+  if (!isTrashPage) {
+    return <></>
+  }
+
+  function openPutbackPageModalHandler() {
+    if ( pageId == undefined || pagePath == undefined ) {
+      return
+    }
+    const putBackedHandler = (path) => {
+      // window.location.reload();
+    };
+    openPutBackPageModal({pageId, path: pagePath}, { onPutBacked: putBackedHandler });
+  }
+
+  function openPageDeleteModalHandler() {
+    if ( pageId == undefined || revisionId == undefined || pagePath == undefined ) {
+      return
+    }
+    const pageToDelete = {
+      data: {
+        _id: pageId,
+        revision: revisionId,
+        path: pagePath,
+      },
+      meta: pageInfo,
+    };
+    openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler });
+  }
+
+  function renderTrashPageManagementButtons() {
+    return (
+      <>
+        <button
+          type="button"
+          className="btn btn-info rounded-pill btn-sm ml-auto mr-2"
+          onClick={openPutbackPageModalHandler}
+          data-toggle="modal"
+        >
+          <i className="icon-action-undo" aria-hidden="true"></i> { t('Put Back') }
+        </button>
+        <button
+          type="button"
+          className="btn btn-danger rounded-pill btn-sm"
+          disabled={!(pageInfo?.isAbleToDeleteCompletely ?? false)}
+          onClick={openPageDeleteModalHandler}
+        >
+          <i className="icon-fire" aria-hidden="true"></i> { t('Delete Completely') }
+        </button>
+      </>
+    );
+  }
+
+  return (
+    <>
+      <div className="alert alert-warning py-3 pl-4 d-flex flex-column flex-lg-row">
+        <div className="flex-grow-1">
+          This page is in the trash <i className="icon-trash" aria-hidden="true"></i>.
+          <br />
+          <UserPicture user={{ username: lastUpdateUserName }} />
+          <span className="ml-2">
+            Deleted by { lastUpdateUserName } at {deletedAt || updatedAt}
+          </span>
+        </div>
+        <div className="pt-1 d-flex align-items-end align-items-lg-center">
+          { isAbleToShowTrashPageManagementButtons && renderTrashPageManagementButtons()}
+        </div>
+      </div>
+    </>
+  );
+};
+

+ 18 - 4
packages/app/src/pages/[[...path]].page.tsx

@@ -17,9 +17,10 @@ import { CrowiRequest } from '~/interfaces/crowi-request';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IPageWithMeta } from '~/interfaces/page';
 import { serializeUserSecurely } from '~/server/models/serializers/user-serializer';
 import { serializeUserSecurely } from '~/server/models/serializers/user-serializer';
-import { useSWRxCurrentPage, useSWRxPageInfo } from '~/stores/page';
+import { useSWRxCurrentPage, useSWRxPage, useSWRxPageInfo } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 import Xss from '~/services/xss';
 import Xss from '~/services/xss';
+import { format } from 'date-fns';
 
 
 // import { isUserPage, isTrashPage, isSharedPage } from '~/utils/path-utils';
 // import { isUserPage, isTrashPage, isSharedPage } from '~/utils/path-utils';
 
 
@@ -39,7 +40,7 @@ import {
   useAppTitle, useSiteUrl, useConfidential, useIsEnabledStaleNotification,
   useAppTitle, useSiteUrl, useConfidential, useIsEnabledStaleNotification,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsMailerSetup,
   useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsMailerSetup,
   useAclEnabled, useHasSlackConfig, useDrawioUri, useHackmdUri, useMathJax, useNoCdn, useEditorConfig, useCsrfToken,
   useAclEnabled, useHasSlackConfig, useDrawioUri, useHackmdUri, useMathJax, useNoCdn, useEditorConfig, useCsrfToken,
-  useCurrentPageId
+  useCurrentPageId, useLastUpdateUsername, useDeletedAt, useRevisionId
 } from '../stores/context';
 } from '../stores/context';
 
 
 import { useXss } from '../stores/xss'
 import { useXss } from '../stores/xss'
@@ -57,6 +58,9 @@ type Props = CommonProps & {
   currentUser: string,
   currentUser: string,
 
 
   pageWithMetaStr: string,
   pageWithMetaStr: string,
+  lastUpdateUserName: string,
+  deletedAt: string,
+  revisionId: string,
   // pageUser?: any,
   // pageUser?: any,
   // redirectTo?: string;
   // redirectTo?: string;
   // redirectFrom?: string;
   // redirectFrom?: string;
@@ -105,12 +109,15 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // useOwnerOfCurrentPage(props.pageUser != null ? JSON.parse(props.pageUser) : null);
   // useOwnerOfCurrentPage(props.pageUser != null ? JSON.parse(props.pageUser) : null);
   // useIsForbidden(props.isForbidden);
   // useIsForbidden(props.isForbidden);
   // useNotFound(props.isNotFound);
   // useNotFound(props.isNotFound);
-  // useIsTrashPage(_isTrashPage(props.currentPagePath));
+  useIsTrashPage(_isTrashPage(props.currentPathname));
   // useShared(isSharedPage(props.currentPagePath));
   // useShared(isSharedPage(props.currentPagePath));
   // useShareLinkId(props.shareLinkId);
   // useShareLinkId(props.shareLinkId);
   // useIsAbleToDeleteCompletely(props.isAbleToDeleteCompletely);
   // useIsAbleToDeleteCompletely(props.isAbleToDeleteCompletely);
   // useIsSharedUser(props.currentUser == null && isSharedPage(props.currentPagePath));
   // useIsSharedUser(props.currentUser == null && isSharedPage(props.currentPagePath));
   // useIsEnabledStaleNotification(props.isEnabledStaleNotification);
   // useIsEnabledStaleNotification(props.isEnabledStaleNotification);
+  useLastUpdateUsername(props.lastUpdateUserName);
+  useDeletedAt(props.deletedAt);
+  useRevisionId(props.revisionId);
 
 
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
@@ -138,7 +145,9 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   }
   }
   useCurrentPageId(pageWithMeta?.data._id)
   useCurrentPageId(pageWithMeta?.data._id)
   useSWRxCurrentPage(undefined, pageWithMeta?.data); // store initial data
   useSWRxCurrentPage(undefined, pageWithMeta?.data); // store initial data
+  // useSWRxPage(pageWithMeta?.data._id);
   useSWRxPageInfo(pageWithMeta?.data._id, undefined, pageWithMeta?.meta); // store initial data
   useSWRxPageInfo(pageWithMeta?.data._id, undefined, pageWithMeta?.meta); // store initial data
+  // need to init useIsDeleted
 
 
   const classNames: string[] = [];
   const classNames: string[] = [];
   // switch (editorMode) {
   // switch (editorMode) {
@@ -239,6 +248,7 @@ async function injectPageInformation(context: GetServerSidePropsContext, props:
   const result: IPageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
   const result: IPageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
   const page = result.data;
   const page = result.data;
 
 
+
   if (page == null) {
   if (page == null) {
     const count = pageId != null ?  await Page.count({ _id: pageId }) : await Page.count({ path: currentPathname }) ;
     const count = pageId != null ?  await Page.count({ _id: pageId }) : await Page.count({ path: currentPathname }) ;
     // check the page is forbidden or just does not exist.
     // check the page is forbidden or just does not exist.
@@ -247,8 +257,12 @@ async function injectPageInformation(context: GetServerSidePropsContext, props:
     logger.warn(`Page is ${props.isForbidden ? 'forbidden' : 'not found'}`, currentPathname);
     logger.warn(`Page is ${props.isForbidden ? 'forbidden' : 'not found'}`, currentPathname);
   }
   }
 
 
-  await (page as unknown as PageModel).populateDataToShowRevision();
   props.pageWithMetaStr = JSON.stringify(result);
   props.pageWithMetaStr = JSON.stringify(result);
+
+  const populatedPage = await (page as unknown as PageModel).populateDataToShowRevision();
+  props.lastUpdateUserName = populatedPage.lastUpdateUser.name;
+  props.deletedAt = page.deletedAt ? format(page.deletedAt, 'yyyy/MM/dd HH:mm') : ''
+  props.revisionId = populatedPage.revision._id.toString();
 }
 }
 
 
 // async function injectPageUserInformation(context: GetServerSidePropsContext, props: Props): Promise<void> {
 // async function injectPageUserInformation(context: GetServerSidePropsContext, props: Props): Promise<void> {