Преглед изворни кода

Merge pull request #6158 from weseek/feat/implement-page-stale-alert

feat: Implement page stale alert
Yohei Shiina пре 3 година
родитељ
комит
8381402cf2

+ 2 - 2
packages/app/src/components/PageAlert/PageAlerts.tsx

@@ -1,6 +1,6 @@
 import { FixPageGrantAlert } from "./FixPageGrantAlert";
 import { PageGrantAlert } from "./PageGrantAlert";
-
+import { PageStaleAlert } from "./PageStaleAlert";
 
 export const PageAlerts = (): JSX.Element => {
 
@@ -10,8 +10,8 @@ export const PageAlerts = (): JSX.Element => {
       <div className="col-sm-12">
         {/* alerts */}
         <FixPageGrantAlert/>
-
         <PageGrantAlert/>
+        <PageStaleAlert/>
       </div>
     </div>
   );

+ 41 - 0
packages/app/src/components/PageAlert/PageStaleAlert.tsx

@@ -0,0 +1,41 @@
+import { useIsEnabledStaleNotification } from '../../stores/context'
+import { useSWRxCurrentPage, useSWRxPageInfo } from '../../stores/page'
+import { useTranslation } from 'react-i18next';
+
+export const PageStaleAlert = ():JSX.Element => {
+  const { t } = useTranslation()
+  const { data: isEnabledStaleNotification } = useIsEnabledStaleNotification();
+
+  // Todo: determine if it should fetch or not like useSWRxPageInfo below after https://redmine.weseek.co.jp/issues/96788
+  const { data: pageData } = useSWRxCurrentPage();
+  const { data: pageInfo } = useSWRxPageInfo(isEnabledStaleNotification ? pageData?._id : null);
+
+  const contentAge = pageInfo?.contentAge;
+
+  if (!isEnabledStaleNotification) {
+    return <></>
+  }
+
+  if( pageInfo == null || contentAge == null || contentAge === 0) {
+    return <></>
+  }
+
+  let alertClass;
+  switch (contentAge) {
+    case 1:
+      alertClass = "alert-info";
+      break;
+    case 2:
+      alertClass = "alert-warning";
+      break;
+    default:
+      alertClass = "alert-danger";
+  }
+
+  return (
+    <div className={`alert ${alertClass}`}>
+      <i className="icon-fw icon-hourglass"></i>
+      <strong>{ t('page_page.notice.stale', { count: pageInfo.contentAge }) }</strong>
+    </div>
+  )
+}

+ 1 - 0
packages/app/src/interfaces/page.ts

@@ -53,6 +53,7 @@ export type IPageInfo = {
   isDeletable: boolean,
   isAbleToDeleteCompletely: boolean,
   isRevertible: boolean,
+  contentAge?: number,
 }
 
 export type IPageInfoForEntity = IPageInfo & {

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

@@ -79,7 +79,7 @@ type Props = CommonProps & {
   // isAllReplyShown: boolean,
   // isContainerFluid: boolean,
   // editorConfig: any,
-  // isEnabledStaleNotification: boolean,
+  isEnabledStaleNotification: boolean,
   // isEnabledLinebreaks: boolean,
   // isEnabledLinebreaksInComments: boolean,
   // adminPreferredIndentSize: number,
@@ -110,7 +110,7 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // useShareLinkId(props.shareLinkId);
   // useIsAbleToDeleteCompletely(props.isAbleToDeleteCompletely);
   // useIsSharedUser(props.currentUser == null && isSharedPage(props.currentPagePath));
-  // useIsEnabledStaleNotification(props.isEnabledStaleNotification);
+  useIsEnabledStaleNotification(props.isEnabledStaleNotification);
 
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
@@ -301,7 +301,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   // props.highlightJsStyle = configManager.getConfig('crowi', 'customize:highlightJsStyle');
   // props.isAllReplyShown = configManager.getConfig('crowi', 'customize:isAllReplyShown');
   // props.isContainerFluid = configManager.getConfig('crowi', 'customize:isContainerFluid');
-  // props.isEnabledStaleNotification = configManager.getConfig('crowi', 'customize:isEnabledStaleNotification');
+  props.isEnabledStaleNotification = configManager.getConfig('crowi', 'customize:isEnabledStaleNotification');
   // props.isEnabledLinebreaks = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks');
   // props.isEnabledLinebreaksInComments = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments');
   // props.editorConfig = {

+ 2 - 1
packages/app/src/server/service/page.ts

@@ -2176,7 +2176,7 @@ class PageService {
     });
   }
 
-  constructBasicPageInfo(page: IPage, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
+  constructBasicPageInfo(page: PageDocument, isGuestUser?: boolean): IPageInfo | IPageInfoForEntity {
     const isMovable = isGuestUser ? false : isMovablePage(page.path);
 
     if (page.isEmpty) {
@@ -2204,6 +2204,7 @@ class PageService {
       isDeletable: isMovable,
       isAbleToDeleteCompletely: false,
       isRevertible: isTrashPage(page.path),
+      contentAge: page.getContentAge(),
     };
 
   }

+ 5 - 0
packages/app/src/stores/context.tsx

@@ -187,6 +187,11 @@ export const useGrowiVersion = (initialData?: string): SWRResponse<string, any>
   return useStaticSWR('growiVersion', initialData);
 };
 
+export const useIsEnabledStaleNotification = (initialData?: boolean): SWRResponse<boolean, any> => {
+  return useStaticSWR('isEnabledStaleNotification', initialData);
+};
+
+
 
 /** **********************************************************
  *                     Computed contexts