Просмотр исходного кода

Merge pull request #4619 from weseek/imprv/jump-to-page-when-notification-clicked

Imprv/jump to page when notification clicked
cao 4 лет назад
Родитель
Сommit
fee0f81043

+ 2 - 1
packages/app/resource/locales/en_US/translation.json

@@ -258,7 +258,8 @@
   },
   "in_app_notification": {
     "notification_list": "In-App Notification List",
-    "see_all": "See All"
+    "see_all": "See All",
+    "no_notification": "You don't have any notificatios."
   },
   "in_app_notification_settings": {
     "in_app_notification_settings": "In-App Notification Settings",

+ 2 - 1
packages/app/resource/locales/ja_JP/translation.json

@@ -260,7 +260,8 @@
   },
   "in_app_notification": {
     "notification_list": "アプリ内通知一覧",
-    "see_all": "通知一覧を見る"
+    "see_all": "通知一覧を見る",
+    "no_notification": "通知は一つもありません。"
   },
   "in_app_notification_settings": {
     "in_app_notification_settings": "アプリ内通知設定",

+ 2 - 1
packages/app/resource/locales/zh_CN/translation.json

@@ -239,7 +239,8 @@
   },
   "in_app_notification": {
     "notification_list": "应用内通知列表",
-    "see_all": "查看通知列表"
+    "see_all": "查看通知列表",
+    "no_notification": "您没有任何通知"
   },
   "in_app_notification_settings": {
     "in_app_notification_settings": "在应用程序通知设置",

+ 0 - 85
packages/app/src/components/InAppNotification/InAppNotification.tsx

@@ -1,85 +0,0 @@
-import React from 'react';
-
-import { UserPicture } from '@growi/ui';
-import { IInAppNotification } from '../../interfaces/in-app-notification';
-import { PageUpdateNotification, PageCommentNotification } from './NotificationContent';
-
-interface Props {
-  notification: IInAppNotification
-}
-
-export const InAppNotification = (props: Props): JSX.Element => {
-
-  const { notification } = props;
-
-  // TODO get actionUsers with mongoose virtual method by #79077
-  const getActionUsers = () => {
-    const latestActionUsers = notification.actionUsers.slice(0, 3);
-    const latestUsers = latestActionUsers.map((user) => {
-      return `@${user.name}`;
-    });
-
-    let actionedUsers = '';
-    const latestUsersCount = latestUsers.length;
-    if (latestUsersCount === 1) {
-      actionedUsers = latestUsers[0];
-    }
-    else if (notification.actionUsers.length >= 4) {
-      actionedUsers = `${latestUsers.slice(0, 2).join(', ')} and ${notification.actionUsers.length - 2} others`;
-    }
-    else {
-      actionedUsers = latestUsers.join(', ');
-    }
-
-    return actionedUsers;
-  };
-
-  const renderUserPicture = (): JSX.Element => {
-    const actionUsers = notification.actionUsers;
-
-    if (actionUsers.length < 1) {
-      return <></>;
-    }
-    if (actionUsers.length === 1) {
-      return <UserPicture user={actionUsers[0]} size="md" noTooltip />;
-    }
-    return (
-      <div className="position-relative">
-        <UserPicture user={actionUsers[0]} size="md" noTooltip />
-        <div className="position-absolute" style={{ top: 10, left: 10 }}>
-          <UserPicture user={actionUsers[1]} size="md" noTooltip />
-        </div>
-
-      </div>
-    );
-  };
-
-  const renderInAppNotificationContent = (): JSX.Element => {
-    const propsNew = {
-      actionUsers: getActionUsers(),
-      ...props,
-    };
-    const action: string = notification.action;
-    switch (action) {
-      case 'PAGE_UPDATE':
-        return <PageUpdateNotification {...propsNew} />;
-      case 'COMMENT_CREATE':
-        return <PageCommentNotification {...propsNew} />;
-      default:
-        return <></>;
-    }
-  };
-
-  return (
-    <>
-      <div className="dropdown-item d-flex flex-row mb-3">
-        <div className="p-2 mr-2 d-flex align-items-center">
-          {renderUserPicture()}
-        </div>
-        <div className="p-2">
-          {renderInAppNotificationContent()}
-        </div>
-      </div>
-    </>
-  );
-};

+ 6 - 18
packages/app/src/components/InAppNotification/InAppNotificationDropdown.tsx

@@ -5,23 +5,20 @@ import {
 import { useTranslation } from 'react-i18next';
 import loggerFactory from '~/utils/logger';
 
-
-import AppContainer from '../../client/services/AppContainer';
+import { apiv3Get, apiv3Post } from '~/client/util/apiv3-client';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import InAppNotificationList from './InAppNotificationList';
-import SocketIoContainer from '../../client/services/SocketIoContainer';
-import { useSWRxInAppNotifications } from '../../stores/in-app-notification';
+import SocketIoContainer from '~/client/services/SocketIoContainer';
+import { useSWRxInAppNotifications } from '~/stores/in-app-notification';
 
 const logger = loggerFactory('growi:InAppNotificationDropdown');
 
 type Props = {
-  appContainer: AppContainer,
   socketIoContainer: SocketIoContainer,
 };
 
 const InAppNotificationDropdown: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
-  const { appContainer } = props;
 
   const [count, setCount] = useState(0);
   const [isOpen, setIsOpen] = useState(false);
@@ -37,14 +34,12 @@ const InAppNotificationDropdown: FC<Props> = (props: Props) => {
     const socket = props.socketIoContainer.getSocket();
     socket.on('notificationUpdated', (data: { userId: string, count: number }) => {
       setCount(data.count);
-      // eslint-disable-next-line no-console
-      console.log('socketData', data);
     });
   };
 
   const updateNotificationStatus = async() => {
     try {
-      await appContainer.apiv3Post('/in-app-notification/read');
+      await apiv3Post('/in-app-notification/read');
       setCount(0);
     }
     catch (err) {
@@ -54,7 +49,7 @@ const InAppNotificationDropdown: FC<Props> = (props: Props) => {
 
   const fetchNotificationStatus = async() => {
     try {
-      const res = await appContainer.apiv3Get('/in-app-notification/status');
+      const res = await apiv3Get('/in-app-notification/status');
       const { count } = res.data;
       setCount(count);
     }
@@ -72,16 +67,9 @@ const InAppNotificationDropdown: FC<Props> = (props: Props) => {
     setIsOpen(newIsOpenState);
   };
 
-  /**
-    * TODO: Jump to the page by clicking on the notification by GW-7472
-    */
-
-
   const badge = count > 0 ? <span className="badge badge-pill badge-danger grw-notification-badge">{count}</span> : '';
 
 
-  // const notifications = inAppNotificationData.docs;
-
   return (
     <Dropdown className="notification-wrapper" isOpen={isOpen} toggle={toggleDropdownHandler}>
       <DropdownToggle tag="a">
@@ -101,6 +89,6 @@ const InAppNotificationDropdown: FC<Props> = (props: Props) => {
 /**
  * Wrapper component for using unstated
  */
-const InAppNotificationDropdownWrapper = withUnstatedContainers(InAppNotificationDropdown, [AppContainer, SocketIoContainer]);
+const InAppNotificationDropdownWrapper = withUnstatedContainers(InAppNotificationDropdown, [SocketIoContainer]);
 
 export default InAppNotificationDropdownWrapper;

+ 107 - 0
packages/app/src/components/InAppNotification/InAppNotificationElm.tsx

@@ -0,0 +1,107 @@
+import React, { useCallback } from 'react';
+
+import { UserPicture, PagePathLabel } from '@growi/ui';
+import { IInAppNotification } from '~/interfaces/in-app-notification';
+import { apiv3Post } from '~/client/util/apiv3-client';
+import FormattedDistanceDate from '../FormattedDistanceDate';
+
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:InAppNotificationElm');
+
+
+interface Props {
+  notification: IInAppNotification
+}
+
+const InAppNotificationElm = (props: Props): JSX.Element => {
+
+  const { notification } = props;
+
+
+  const getActionUsers = () => {
+    const latestActionUsers = notification.actionUsers.slice(0, 3);
+    const latestUsers = latestActionUsers.map((user) => {
+      return `@${user.name}`;
+    });
+
+    let actionedUsers = '';
+    const latestUsersCount = latestUsers.length;
+    if (latestUsersCount === 1) {
+      actionedUsers = latestUsers[0];
+    }
+    else if (notification.actionUsers.length >= 4) {
+      actionedUsers = `${latestUsers.slice(0, 2).join(', ')} and ${notification.actionUsers.length - 2} others`;
+    }
+    else {
+      actionedUsers = latestUsers.join(', ');
+    }
+
+    return actionedUsers;
+  };
+
+  const renderActionUserPictures = (): JSX.Element => {
+    const actionUsers = notification.actionUsers;
+
+    if (actionUsers.length < 1) {
+      return <></>;
+    }
+    if (actionUsers.length === 1) {
+      return <UserPicture user={actionUsers[0]} size="md" noTooltip />;
+    }
+    return (
+      <div className="position-relative">
+        <UserPicture user={actionUsers[0]} size="md" noTooltip />
+        <div className="position-absolute" style={{ top: 10, left: 10 }}>
+          <UserPicture user={actionUsers[1]} size="md" noTooltip />
+        </div>
+
+      </div>
+    );
+  };
+
+  const notificationClickHandler = useCallback(() => {
+    // set notification status "OPEND"
+    apiv3Post('/in-app-notification/open', { id: notification._id });
+
+    // jump to target page
+    window.location.href = notification.target.path;
+  }, []);
+
+  const actionUsers = getActionUsers();
+  const pagePath = { path: props.notification.target.path };
+
+  const actionType: string = notification.action;
+  let actionMsg: string;
+
+  switch (actionType) {
+    case 'PAGE_UPDATE':
+      actionMsg = 'updated on';
+      break;
+    case 'COMMENT_CREATE':
+      actionMsg = 'commented on';
+      break;
+    default:
+      actionMsg = '';
+  }
+
+
+  return (
+    <div className="dropdown-item d-flex flex-row mb-3">
+      <div className="p-2 mr-2 d-flex align-items-center">
+        {renderActionUserPictures()}
+      </div>
+      <div className="p-2">
+        <div onClick={notificationClickHandler}>
+          <div>
+            <b>{actionUsers}</b> {actionMsg} <PagePathLabel page={pagePath} />
+          </div>
+          <i className="fa fa-file-o mr-2" />
+          <FormattedDistanceDate id={notification._id} date={notification.createdAt} isShowTooltip={false} />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default InAppNotificationElm;

+ 14 - 16
packages/app/src/components/InAppNotification/InAppNotificationList.tsx

@@ -1,14 +1,17 @@
 import React, { FC } from 'react';
 
 import { PaginateResult } from 'mongoose';
+import { useTranslation } from 'react-i18next';
 import { IInAppNotification } from '../../interfaces/in-app-notification';
-import { InAppNotification } from './InAppNotification';
+import InAppNotificationElm from './InAppNotificationElm';
+
 
 type Props = {
   inAppNotificationData: PaginateResult<IInAppNotification> | undefined;
 };
 
 const InAppNotificationList: FC<Props> = (props: Props) => {
+  const { t } = useTranslation();
   const { inAppNotificationData } = props;
 
   if (inAppNotificationData == null) {
@@ -23,28 +26,23 @@ const InAppNotificationList: FC<Props> = (props: Props) => {
 
   const notifications = inAppNotificationData.docs;
 
-  const RenderEmptyInAppNotification = (): JSX.Element => {
-    return (
-      // TODO: apply i18n by #78569
-      <>You had no notifications, yet.</>
-    );
-  };
-
-  const RenderInAppNotificationList = () => {
-    if (notifications.length === 0) {
-      return <RenderEmptyInAppNotification />;
-    }
-    const notificationList = notifications.map((notification: IInAppNotification) => {
+  const renderInAppNotificationList = () => {
+    const inAppNotificationList = notifications.map((notification: IInAppNotification) => {
       return (
         <div className="d-flex flex-row" key={notification._id}>
-          <InAppNotification notification={notification} />
+          <InAppNotificationElm notification={notification} />
         </div>
       );
     });
-    return <>{notificationList}</>;
+
+    return inAppNotificationList;
   };
 
-  return <RenderInAppNotificationList />;
+  return (
+    <>
+      {notifications.length === 0 ? <>{t('in_app_notification.no_notification')}</> : renderInAppNotificationList()}
+    </>
+  );
 };
 
 

+ 0 - 53
packages/app/src/components/InAppNotification/NotificationContent.tsx

@@ -1,53 +0,0 @@
-import React from 'react';
-import { PagePathLabel } from '@growi/ui';
-import { IInAppNotification } from '../../interfaces/in-app-notification';
-import { apiv3Post } from '../../client/util/apiv3-client';
-import FormattedDistanceDate from '../FormattedDistanceDate';
-
-interface Props {
-  actionUsers: string
-  notification: IInAppNotification
-}
-
-
-const notificationClickHandler = async(notification: IInAppNotification) => {
-  try {
-    await apiv3Post('/in-app-notification/open', { id: notification._id });
-
-    // jump to target page
-    // window.location.href = notification.target.path;
-  }
-  catch (err) {
-    // logger.error(err);
-  }
-};
-
-export const PageCommentNotification = (props: Props): JSX.Element => {
-
-  const pagePath = { path: props.notification.target.path };
-
-  return (
-    <div onClick={() => notificationClickHandler(props.notification)}>
-      <div>
-        <b>{props.actionUsers}</b> commented on  <PagePathLabel page={pagePath} />
-      </div>
-      <i className="fa fa-comment-o mr-2" />
-      <FormattedDistanceDate id={props.notification._id} date={props.notification.createdAt} isShowTooltip={false} />
-    </div>
-  );
-};
-
-export const PageUpdateNotification = (props: Props): JSX.Element => {
-
-  const pagePath = { path: props.notification.target.path };
-
-  return (
-    <div onClick={() => notificationClickHandler(props.notification)}>
-      <div>
-        <b>{props.actionUsers}</b> page updated on <PagePathLabel page={pagePath} />
-      </div>
-      <i className="fa fa-file-o mr-2" />
-      <FormattedDistanceDate id={props.notification._id} date={props.notification.createdAt} isShowTooltip={false} />
-    </div>
-  );
-};

+ 1 - 1
packages/app/src/server/service/in-app-notification.ts

@@ -2,7 +2,7 @@ import { Types } from 'mongoose';
 import { subDays } from 'date-fns';
 import Crowi from '../crowi';
 import {
-  InAppNotification, InAppNotificationDocument, STATUS_UNREAD, STATUS_UNOPENED, STATUS_OPENED,
+  InAppNotification, STATUS_UNREAD, STATUS_UNOPENED, STATUS_OPENED,
 } from '~/server/models/in-app-notification';
 import { ActivityDocument } from '~/server/models/activity';