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

show message if bulk export download expired

Futa Arai 1 год назад
Родитель
Сommit
9d5360d9ce

+ 2 - 1
apps/app/public/static/locales/en_US/translation.json

@@ -625,7 +625,8 @@
     "bulk_export_notice": "Once a download link is ready, a notification will be sent. If the number of pages is large, it may take a while for preparation.",
     "bulk_export_notice": "Once a download link is ready, a notification will be sent. If the number of pages is large, it may take a while for preparation.",
     "markdown": "Markdown",
     "markdown": "Markdown",
     "choose_export_format": "Select export format",
     "choose_export_format": "Select export format",
-    "bulk_export_started": "Please wait a moment..."
+    "bulk_export_started": "Please wait a moment...",
+    "bulk_export_download_expired": "Download period has expired"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "Successfully Connected!",
     "successfully_connected": "Successfully Connected!",

+ 8 - 2
apps/app/public/static/locales/fr_FR/translation.json

@@ -616,11 +616,17 @@
     "discription_heading": "Créer un compte",
     "discription_heading": "Créer un compte",
     "discription": "Créer un compte avec votre adresse courriel invitée"
     "discription": "Créer un compte avec votre adresse courriel invitée"
   },
   },
-  "export_bulk": {
+  "page_export": {
     "failed_to_export": "Échec de l'export",
     "failed_to_export": "Échec de l'export",
     "failed_to_count_pages": "Échec du compte des pages",
     "failed_to_count_pages": "Échec du compte des pages",
     "export_page_markdown": "Exporter la page en Markdown",
     "export_page_markdown": "Exporter la page en Markdown",
-    "export_page_pdf": "Exporter la page en PDF"
+    "export_page_pdf": "Exporter la page en PDF",
+    "bulk_export": "Exporter la page et toutes les pages enfants",
+    "bulk_export_notice": "Une fois qu'un lien de téléchargement est prêt, une notification sera envoyée. Si le nombre de pages est important, la préparation peut prendre un certain temps.",
+    "markdown": "Markdown",
+    "choose_export_format": "Sélectionnez le format d'exportation",
+    "bulk_export_started": "Patientez s'il-vous-plait...",
+    "bulk_export_download_expired": "La période de téléchargement a expiré"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "Connecté!",
     "successfully_connected": "Connecté!",

+ 2 - 1
apps/app/public/static/locales/ja_JP/translation.json

@@ -658,7 +658,8 @@
     "bulk_export_notice": "ダウンロードの準備が完了すると、通知が届きます。ページ数が多いと、準備に時間がかかる場合があります。",
     "bulk_export_notice": "ダウンロードの準備が完了すると、通知が届きます。ページ数が多いと、準備に時間がかかる場合があります。",
     "markdown": "マークダウン",
     "markdown": "マークダウン",
     "choose_export_format": "エクスポート形式を選択してください",
     "choose_export_format": "エクスポート形式を選択してください",
-    "bulk_export_started": "ただいま準備中です..."
+    "bulk_export_started": "ただいま準備中です...",
+    "bulk_export_download_expired": "ダウンロード期限が切れました"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "接続に成功しました!",
     "successfully_connected": "接続に成功しました!",

+ 2 - 1
apps/app/public/static/locales/zh_CN/translation.json

@@ -628,7 +628,8 @@
     "bulk_export_notice": "下载链接准备好后,将发送通知。如果页数较多,则可能需要一段时间准备。",
     "bulk_export_notice": "下载链接准备好后,将发送通知。如果页数较多,则可能需要一段时间准备。",
     "markdown": "Markdown",
     "markdown": "Markdown",
     "choose_export_format": "选择导出格式",
     "choose_export_format": "选择导出格式",
-    "bulk_export_started": "目前我们正在准备..."
+    "bulk_export_started": "目前我们正在准备...",
+    "bulk_export_download_expired": "下载期限已过"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "连接成功!",
     "successfully_connected": "连接成功!",

+ 23 - 20
apps/app/src/components/InAppNotification/InAppNotificationElm.tsx

@@ -24,6 +24,7 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
   const Notification = modelNotificationUtils?.Notification;
   const Notification = modelNotificationUtils?.Notification;
   const publishOpen = modelNotificationUtils?.publishOpen;
   const publishOpen = modelNotificationUtils?.publishOpen;
   const clickLink = modelNotificationUtils?.clickLink;
   const clickLink = modelNotificationUtils?.clickLink;
+  const isDisabled = modelNotificationUtils?.isDisabled;
 
 
   if (Notification == null) {
   if (Notification == null) {
     return <></>;
     return <></>;
@@ -36,6 +37,8 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
       onUnopenedNotificationOpend?.();
       onUnopenedNotificationOpend?.();
     }
     }
 
 
+    if (isDisabled) return;
+
     publishOpen?.();
     publishOpen?.();
   };
   };
 
 
@@ -59,27 +62,27 @@ const InAppNotificationElm: FC<Props> = (props: Props) => {
   };
   };
 
 
   return (
   return (
-    <a
-      className="list-group-item list-group-item-action"
-      href={clickLink}
-      onClick={() => clickHandler(notification)}
-      style={{ cursor: 'pointer' }}
-    >
-      <div className="d-flex align-items-center">
-        <span
-          className={`${notification.status === InAppNotificationStatuses.STATUS_UNOPENED
-            ? 'grw-unopend-notification'
-            : 'ms-2'
-          } rounded-circle me-3`}
-        >
-        </span>
-
-        {renderActionUserPictures()}
-
-        <Notification />
+    <div className="list-group-item list-group-item-action" style={{ cursor: 'pointer' }}>
+      <a
+        href={isDisabled ? undefined : clickLink}
+        onClick={() => clickHandler(notification)}
+      >
+        <div className="d-flex align-items-center">
+          <span
+            className={`${notification.status === InAppNotificationStatuses.STATUS_UNOPENED
+              ? 'grw-unopend-notification'
+              : 'ms-2'
+            } rounded-circle me-3`}
+          >
+          </span>
+
+          {renderActionUserPictures()}
+
+          <Notification />
 
 
-      </div>
-    </a>
+        </div>
+      </a>
+    </div>
   );
   );
 };
 };
 
 

+ 7 - 1
apps/app/src/components/InAppNotification/ModelNotification/ModelNotification.tsx

@@ -3,7 +3,9 @@ import React from 'react';
 
 
 import type { HasObjectId } from '@growi/core';
 import type { HasObjectId } from '@growi/core';
 import { PagePathLabel } from '@growi/ui/dist/components';
 import { PagePathLabel } from '@growi/ui/dist/components';
+import { useTranslation } from 'react-i18next';
 
 
+import { SupportedTargetModel } from '~/interfaces/activity';
 import type { IInAppNotification } from '~/interfaces/in-app-notification';
 import type { IInAppNotification } from '~/interfaces/in-app-notification';
 
 
 import FormattedDistanceDate from '../../FormattedDistanceDate';
 import FormattedDistanceDate from '../../FormattedDistanceDate';
@@ -20,13 +22,17 @@ export const ModelNotification: FC<Props> = (props) => {
     notification, actionMsg, actionIcon, actionUsers,
     notification, actionMsg, actionIcon, actionUsers,
   } = props;
   } = props;
 
 
+  const { t } = useTranslation();
+
   return (
   return (
     <div className="p-2 overflow-hidden">
     <div className="p-2 overflow-hidden">
       <div className="text-truncate">
       <div className="text-truncate">
-        <b>{actionUsers}</b>
+        {notification.targetModel !== SupportedTargetModel.MODEL_PAGE_BULK_EXPORT_JOB ? <b>{actionUsers}</b> : <></>}
         {` ${actionMsg}`}
         {` ${actionMsg}`}
         <PagePathLabel path={notification.parsedSnapshot?.path ?? ''} />
         <PagePathLabel path={notification.parsedSnapshot?.path ?? ''} />
       </div>
       </div>
+      { (notification.targetModel === SupportedTargetModel.MODEL_PAGE_BULK_EXPORT_JOB && notification.target == null)
+        ? <div className="text-danger"><small>{t('page_export.bulk_export_download_expired')}</small></div> : <></> }
       <span className="material-symbols-outlined me-2">{actionIcon}</span>
       <span className="material-symbols-outlined me-2">{actionIcon}</span>
       <FormattedDistanceDate
       <FormattedDistanceDate
         id={notification._id}
         id={notification._id}

+ 1 - 2
apps/app/src/components/InAppNotification/ModelNotification/PageBulkExportJobModelNotification.tsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import React from 'react';
 
 
 import { isPopulated, type HasObjectId } from '@growi/core';
 import { isPopulated, type HasObjectId } from '@growi/core';
-import { useTranslation } from 'react-i18next';
 
 
 import type { IPageBulkExportJobHasId } from '~/features/page-bulk-export/interfaces/page-bulk-export';
 import type { IPageBulkExportJobHasId } from '~/features/page-bulk-export/interfaces/page-bulk-export';
 import { SupportedTargetModel } from '~/interfaces/activity';
 import { SupportedTargetModel } from '~/interfaces/activity';
@@ -17,7 +16,6 @@ import type { ModelNotificationUtils } from '.';
 export const usePageBulkExportJobModelNotification = (notification: IInAppNotification & HasObjectId): ModelNotificationUtils | null => {
 export const usePageBulkExportJobModelNotification = (notification: IInAppNotification & HasObjectId): ModelNotificationUtils | null => {
 
 
   const { actionMsg, actionIcon } = useActionMsgAndIconForModelNotification(notification);
   const { actionMsg, actionIcon } = useActionMsgAndIconForModelNotification(notification);
-  const { t } = useTranslation();
 
 
   const isPageBulkExportJobModelNotification = (
   const isPageBulkExportJobModelNotification = (
       notification: IInAppNotification & HasObjectId,
       notification: IInAppNotification & HasObjectId,
@@ -50,6 +48,7 @@ export const usePageBulkExportJobModelNotification = (notification: IInAppNotifi
   return {
   return {
     Notification,
     Notification,
     clickLink,
     clickLink,
+    isDisabled: notification.target == null,
   };
   };
 
 
 };
 };

+ 3 - 0
apps/app/src/components/InAppNotification/ModelNotification/index.tsx

@@ -13,6 +13,9 @@ export interface ModelNotificationUtils {
   Notification: FC
   Notification: FC
   publishOpen?: () => void
   publishOpen?: () => void
   clickLink?: string
   clickLink?: string
+  // Whether actions from clicking notification is disabled or not.
+  // User can still open the notification when true.
+  isDisabled?: boolean
 }
 }
 
 
 export const useModelNotification = (notification: IInAppNotification & HasObjectId): ModelNotificationUtils | null => {
 export const useModelNotification = (notification: IInAppNotification & HasObjectId): ModelNotificationUtils | null => {

+ 1 - 2
apps/app/src/features/page-bulk-export/server/routes/apiv3/page-bulk-export.ts

@@ -1,7 +1,7 @@
 import { ErrorV3 } from '@growi/core/dist/models';
 import { ErrorV3 } from '@growi/core/dist/models';
 import type { Request } from 'express';
 import type { Request } from 'express';
 import { Router } from 'express';
 import { Router } from 'express';
-import { body, param, validationResult } from 'express-validator';
+import { body, validationResult } from 'express-validator';
 
 
 import type Crowi from '~/server/crowi';
 import type Crowi from '~/server/crowi';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
 import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
@@ -25,7 +25,6 @@ module.exports = (crowi: Crowi): Router => {
       body('path').exists({ checkFalsy: true }).isString(),
       body('path').exists({ checkFalsy: true }).isString(),
       body('format').exists({ checkFalsy: true }).isString(),
       body('format').exists({ checkFalsy: true }).isString(),
     ],
     ],
-    downloadResult: param('jobId').isMongoId(),
   };
   };
 
 
   router.post('/', loginRequiredStrictly, validators.pageBulkExport, async(req: AuthorizedRequest, res: ApiV3Response) => {
   router.post('/', loginRequiredStrictly, validators.pageBulkExport, async(req: AuthorizedRequest, res: ApiV3Response) => {

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

@@ -13,7 +13,7 @@ const isIPageBulkExportJob = (targetModel: string, target: IUser | IPage | IPage
   return targetModel === SupportedTargetModel.MODEL_PAGE_BULK_EXPORT_JOB;
   return targetModel === SupportedTargetModel.MODEL_PAGE_BULK_EXPORT_JOB;
 };
 };
 
 
-// snapshots are infos about the target that are displayed in the notification, which should not change on target update
+// snapshots are infos about the target that are displayed in the notification, which should not change on target update/deletion
 export const generateSnapshot = async(targetModel: string, target: IUser | IPage | IPageBulkExportJob): Promise<string | undefined> => {
 export const generateSnapshot = async(targetModel: string, target: IUser | IPage | IPageBulkExportJob): Promise<string | undefined> => {
 
 
   let snapshot: string | undefined;
   let snapshot: string | undefined;