Quellcode durchsuchen

Merge remote-tracking branch 'origin/master' into imprv/page-delete-modal-fetching-pageinfo

Yuki Takei vor 4 Jahren
Ursprung
Commit
3a86767d7f

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

@@ -438,8 +438,9 @@
     "recursively": "Delete pages under this path recursively.",
     "completely": "Delete completely instead of putting it into trash."
   },
-  "deleted_pages": "Page(s) has been deleted",
-  "deleted_pages_completely": "Page(s) has been deleted completely",
+  "deleted_pages": "{{path}} has been deleted",
+  "deleted_pages_completely": "{{path}} has been deleted completely",
+  "renamed_pages": "{{path}} has been renamed",
   "modal_empty":{
     "empty_the_trash": "Empty The Trash",
     "notice": "The pages deleted completely are unrecoverable."
@@ -458,6 +459,7 @@
       "recursive": "Duplicate children of under this path recursively"
     }
   },
+  "duplicated_pages": "{{path}} has been duplicated",
   "modal_putback": {
     "label": {
       "Put Back Page": "Put back page",

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

@@ -437,8 +437,9 @@
     "recursively": "配下のページも削除します",
     "completely": "ゴミ箱を経由せず、完全に削除します"
   },
-  "deleted_pages": "ページをゴミ箱に入れました",
-  "deleted_pages_completely": "ページを完全に削除しました",
+  "deleted_pages": "{{path}} をゴミ箱に入れました",
+  "deleted_pages_completely": "{{path}} を完全に削除しました",
+  "renamed_pages": "{{path}} を移動/名前変更しました",
   "modal_empty":{
     "empty_the_trash": "ゴミ箱を空にする",
     "notice": "完全削除したページは元に戻すことができません"
@@ -457,6 +458,7 @@
       "recursive": "配下のページも複製します"
     }
   },
+  "duplicated_pages": "{{path}} を複製しました",
   "modal_putback": {
     "label": {
       "Put Back Page": "ページを元に戻す",

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

@@ -416,6 +416,9 @@
 		"recursively": "Delete children of <code>%s</code> recursively.",
 		"completely": "Delete completely instead of putting it into trash."
   },
+  "deleted_pages": "将 {{path}} 放入垃圾箱",
+  "deleted_pages_completely": "{{path}} 已被完全删除",
+  "renamed_pages": "移动/重命名 {{path}}",
 	"modal_empty": {
 		"empty_the_trash": "Empty The Trash",
 		"notice": "完全删除的页面是不可恢复的。"
@@ -433,7 +436,8 @@
     "help": {
       "recursive": "Duplicate children of under this path recursively"
     }
-	},
+  },
+  "duplicated_pages": "{{path}} 已重复",
 	"modal_putback": {
 		"label": {
 			"Put Back Page": "Put back page",

+ 5 - 1
packages/app/src/components/PageDuplicateModal.jsx

@@ -113,7 +113,11 @@ const PageDuplicateModal = (props) => {
 
     try {
       await appContainer.apiv3Post('/pages/duplicate', { pageId, pageNameInput, isRecursively: isDuplicateRecursively });
-      window.location.href = encodeURI(`${pageNameInput}?duplicated=${path}`);
+      const onDuplicated = duplicateModalData.opts?.onDuplicated;
+      if (onDuplicated != null) {
+        onDuplicated(path);
+      }
+      closeDuplicateModal();
     }
     catch (err) {
       setErrs(err);

+ 5 - 1
packages/app/src/components/PageRenameModal.jsx

@@ -134,7 +134,11 @@ const PageRenameModal = (props) => {
         url.searchParams.append('withRedirect', true);
       }
 
-      window.location.href = `${url.pathname}${url.search}`;
+      const onRenamed = renameModalData.opts?.onRenamed;
+      if (onRenamed != null) {
+        onRenamed(path);
+      }
+      closeRenameModal();
     }
     catch (err) {
       setErrs(err);

+ 21 - 2
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -7,8 +7,10 @@ import { DropdownItem } from 'reactstrap';
 
 import { IPageToDeleteWithMeta, IPageWithMeta } from '~/interfaces/page';
 import { IPageSearchMeta } from '~/interfaces/search';
+import { OnDeletedFunction } from '~/interfaces/ui';
 
 import { exportAsMarkdown } from '~/client/services/page-operation';
+import { toastSuccess } from '~/client/util/apiNotification';
 
 import RevisionLoader from '../Page/RevisionLoader';
 import AppContainer from '../../client/services/AppContainer';
@@ -99,6 +101,8 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
     forceHideMenuItems,
   } = props;
 
+  const { t } = useTranslation();
+
   const page = pageWithMeta?.pageData;
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
@@ -115,9 +119,24 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
     openRenameModal(pageToRename);
   }, [openRenameModal]);
 
+  const onDeletedHandler: OnDeletedFunction = useCallback((pathOrPathsToDelete, isRecursively, isCompletely) => {
+    if (typeof pathOrPathsToDelete !== 'string') {
+      return;
+    }
+    const path = pathOrPathsToDelete;
+
+    if (isCompletely) {
+      toastSuccess(t('deleted_pages_completely', { path }));
+    }
+    else {
+      toastSuccess(t('deleted_pages', { path }));
+    }
+
+  }, [t]);
+
   const deleteItemClickedHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
-    openDeleteModal([pageToDelete]);
-  }, [openDeleteModal]);
+    openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler });
+  }, [onDeletedHandler, openDeleteModal]);
 
   const ControlComponents = useCallback(() => {
     if (page == null) {

+ 17 - 3
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
 import { usePageTreeTermManager, useSWRxPageAncestorsChildren, useSWRxRootPage } from '~/stores/page-listing';
 import { TargetAndAncestors } from '~/interfaces/page-listing-results';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
-import { OnDeletedFunction } from '~/interfaces/ui';
+import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import {
   IPageForPageDuplicateModal, usePageDuplicateModal, IPageForPageRenameModal, usePageRenameModal, usePageDeleteModal,
@@ -145,11 +145,25 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
   }, []);
 
   const onClickDuplicateMenuItem = (pageToDuplicate: IPageForPageDuplicateModal) => {
-    openDuplicateModal(pageToDuplicate);
+    const duplicatedHandler: OnDuplicatedFunction = (path) => {
+      toastSuccess(t('duplicated_pages', { path }));
+
+      advancePt();
+      advanceFts();
+      advanceDpl();
+    };
+
+    openDuplicateModal(pageToDuplicate, { onDuplicated: duplicatedHandler });
   };
 
   const onClickRenameMenuItem = (pageToRename: IPageForPageRenameModal) => {
-    openRenameModal(pageToRename);
+    const renamedHandler: OnRenamedFunction = (path) => {
+      toastSuccess(t('renamed_pages', { path }));
+
+      // TODO: revalidation by https://redmine.weseek.co.jp/issues/89258
+    };
+
+    openRenameModal(pageToRename, { onRenamed: renamedHandler });
   };
 
   const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => {

+ 2 - 0
packages/app/src/interfaces/ui.ts

@@ -22,3 +22,5 @@ export type ICustomNavTabMappings = { [key: string]: ICustomTabContent };
 
 
 export type OnDeletedFunction = (idOrPaths: string | string[], isRecursively: Nullable<true>, isCompletely: Nullable<true>) => void;
+export type OnRenamedFunction = (path: string) => void;
+export type OnDuplicatedFunction = (path: string) => void;

+ 20 - 11
packages/app/src/server/routes/page.js

@@ -344,9 +344,16 @@ module.exports = function(crowi, app) {
       next();
     }
 
+    // empty page
     if (page.isEmpty) {
-      req.pagePath = page.path;
-      return next();
+      // redirect to page (path) url
+      const url = new URL('https://dummy.origin');
+      url.pathname = page.path;
+      Object.entries(req.query).forEach(([key, value], i) => {
+        url.searchParams.append(key, value);
+      });
+      return res.safeRedirect(urljoin(url.pathname, url.search));
+
     }
 
     const renderVars = {};
@@ -409,8 +416,13 @@ module.exports = function(crowi, app) {
 
     // empty page
     if (page.isEmpty) {
-      req.pagePath = page.path;
-      return _notFound(req, res);
+      // redirect to page (path) url
+      const url = new URL('https://dummy.origin');
+      url.pathname = page.path;
+      Object.entries(req.query).forEach(([key, value], i) => {
+        url.searchParams.append(key, value);
+      });
+      return res.safeRedirect(urljoin(url.pathname, url.search));
     }
 
     const { path } = page; // this must exist
@@ -484,8 +496,8 @@ module.exports = function(crowi, app) {
 
     const shareLink = await ShareLink.findOne({ _id: linkId }).populate('relatedPage');
 
-    if (shareLink == null || shareLink.relatedPage == null) {
-      // page or sharelink are not found
+    if (shareLink == null || shareLink.relatedPage == null || shareLink.relatedPage.isEmpty) {
+      // page or sharelink are not found (or page is empty: abnormaly)
       return res.render('layout-growi/not_found_shared_page');
     }
     if (crowi.configManager.getConfig('crowi', 'security:disableLinkSharing')) {
@@ -601,10 +613,6 @@ module.exports = function(crowi, app) {
     }
 
     if (pages.length === 1) {
-      if (pages[0].isEmpty) {
-        return _notFound(req, res);
-      }
-
       const url = new URL('https://dummy.origin');
       url.pathname = `/${pages[0]._id}`;
       Object.entries(req.query).forEach(([key, value], i) => {
@@ -613,7 +621,8 @@ module.exports = function(crowi, app) {
       return res.safeRedirect(urljoin(url.pathname, url.search));
     }
 
-    const isForbidden = await Page.exists({ path });
+    // Exclude isEmpty page to handle _notFound or forbidden
+    const isForbidden = await Page.exists({ path, isEmpty: false });
     if (isForbidden) {
       req.isForbidden = true;
       return _notFound(req, res);

+ 4 - 4
packages/app/src/stores/modal.tsx

@@ -1,6 +1,6 @@
 import { SWRResponse } from 'swr';
 import { useStaticSWR } from './use-static-swr';
-import { OnDeletedFunction } from '~/interfaces/ui';
+import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
   IPageInfoAll, IPageToDeleteWithMeta, IPageWithMeta,
 } from '~/interfaces/page';
@@ -87,7 +87,7 @@ export type IPageForPageDuplicateModal = {
 }
 
 export type IDuplicateModalOption = {
-  onDeleted?: OnDeletedFunction,
+  onDuplicated?: OnDuplicatedFunction,
 }
 
 type DuplicateModalStatus = {
@@ -99,7 +99,7 @@ type DuplicateModalStatus = {
 type DuplicateModalStatusUtils = {
   open(
     page?: IPageForPageDuplicateModal,
-    opts?: IRenameModalOption
+    opts?: IDuplicateModalOption
   ): Promise<DuplicateModalStatus | undefined>
   close(): Promise<DuplicateModalStatus | undefined>
 }
@@ -129,7 +129,7 @@ export type IPageForPageRenameModal = {
 }
 
 export type IRenameModalOption = {
-  onDeleted?: OnDeletedFunction,
+  onRenamed?: OnRenamedFunction,
 }
 
 type RenameModalStatus = {