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

Merge pull request #6873 from weseek/imprv/mutate-current-page-after-page-operation-by-pagetree

imprv: Mutate current page data after page operation by PageTree
Yuki Takei 3 лет назад
Родитель
Сommit
e41b9650b0
32 измененных файлов с 121 добавлено и 80 удалено
  1. 2 1
      packages/app/src/components/Comments.tsx
  2. 2 1
      packages/app/src/components/DescendantsPageList.tsx
  3. 2 1
      packages/app/src/components/Fab.tsx
  4. 1 1
      packages/app/src/components/Hotkeys/Subscribers/CreatePage.jsx
  5. 2 1
      packages/app/src/components/Navbar/GlobalSearch.tsx
  6. 2 1
      packages/app/src/components/Navbar/GrowiNavbar.tsx
  7. 2 1
      packages/app/src/components/Navbar/GrowiNavbarBottom.tsx
  8. 2 1
      packages/app/src/components/Page.tsx
  9. 2 2
      packages/app/src/components/Page/DisplaySwitcher.tsx
  10. 1 2
      packages/app/src/components/PageAlert/TrashPageAlert.tsx
  11. 2 1
      packages/app/src/components/PageComment/CommentEditor.tsx
  12. 2 2
      packages/app/src/components/PageEditor.tsx
  13. 2 1
      packages/app/src/components/PageEditor/EditorNavbarBottom.tsx
  14. 1 1
      packages/app/src/components/PageEditor/LinkEditModal.jsx
  15. 6 6
      packages/app/src/components/PageEditorByHackmd.tsx
  16. 1 1
      packages/app/src/components/PageTimeline.tsx
  17. 1 1
      packages/app/src/components/RevisionComparer/RevisionComparer.tsx
  18. 2 1
      packages/app/src/components/SavePageControls.tsx
  19. 2 1
      packages/app/src/components/Sidebar/PageTree.tsx
  20. 3 8
      packages/app/src/components/Sidebar/PageTree/Item.tsx
  21. 17 6
      packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx
  22. 1 1
      packages/app/src/components/TableOfContents.tsx
  23. 1 2
      packages/app/src/pages/[[...path]].page.tsx
  24. 1 5
      packages/app/src/pages/installer.page.tsx
  25. 1 2
      packages/app/src/pages/share/[[...path]].page.tsx
  26. 1 2
      packages/app/src/pages/trash.page.tsx
  27. 0 19
      packages/app/src/stores/context.tsx
  28. 1 1
      packages/app/src/stores/page-listing.tsx
  29. 1 1
      packages/app/src/stores/page-redirect.tsx
  30. 45 3
      packages/app/src/stores/page.tsx
  31. 3 1
      packages/app/src/stores/renderer.tsx
  32. 9 2
      packages/app/src/stores/ui.tsx

+ 2 - 1
packages/app/src/components/Comments.tsx

@@ -5,8 +5,9 @@ import dynamic from 'next/dynamic';
 
 import { PageComment } from '~/components/PageComment';
 import { useSWRxPageComment } from '~/stores/comment';
+import { useIsTrashPage } from '~/stores/page';
 
-import { useIsTrashPage, useCurrentUser } from '../stores/context';
+import { useCurrentUser } from '../stores/context';
 
 import { CommentEditorProps } from './PageComment/CommentEditor';
 

+ 2 - 1
packages/app/src/components/DescendantsPageList.tsx

@@ -11,8 +11,9 @@ import {
 import { IPagingResult } from '~/interfaces/paging-result';
 import { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 import {
-  useIsGuestUser, useIsSharedUser, useIsTrashPage, useShowPageLimitationXL,
+  useIsGuestUser, useIsSharedUser, useShowPageLimitationXL,
 } from '~/stores/context';
+import { useIsTrashPage } from '~/stores/page';
 import {
   usePageTreeTermManager, useDescendantsPageListForCurrentPathTermManager, useSWRxDescendantsPageListForCurrrentPath,
   useSWRxPageInfoForList, useSWRxPageList,

+ 2 - 1
packages/app/src/components/Fab.tsx

@@ -7,8 +7,9 @@ import { useRipple } from 'react-use-ripple';
 import StickyEvents from 'sticky-events';
 
 import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
-import { useCurrentPagePath, useCurrentUser } from '~/stores/context';
+import { useCurrentUser } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
+import { useCurrentPagePath } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 
 import { CreatePageIcon } from './Icons/CreatePageIcon';

+ 1 - 1
packages/app/src/components/Hotkeys/Subscribers/CreatePage.jsx

@@ -2,8 +2,8 @@ import React, { useEffect } from 'react';
 
 import PropTypes from 'prop-types';
 
-import { useCurrentPagePath } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
+import { useCurrentPagePath } from '~/stores/page';
 
 const CreatePage = React.memo((props) => {
 

+ 2 - 1
packages/app/src/components/Navbar/GlobalSearch.tsx

@@ -8,8 +8,9 @@ import { useRouter } from 'next/router';
 import { IFocusable } from '~/client/interfaces/focusable';
 import { IPageWithSearchMeta } from '~/interfaces/search';
 import {
-  useCurrentPagePath, useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
+  useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
 } from '~/stores/context';
+import { useCurrentPagePath } from '~/stores/page';
 import { useGlobalSearchFormRef } from '~/stores/ui';
 
 import SearchForm from '../SearchForm';

+ 2 - 1
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -11,9 +11,10 @@ import { useRipple } from 'react-use-ripple';
 import { UncontrolledTooltip } from 'reactstrap';
 
 import {
-  useIsSearchPage, useCurrentPagePath, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential, useCustomizedLogoSrc,
+  useIsSearchPage, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential, useCustomizedLogoSrc,
 } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
+import { useCurrentPagePath } from '~/stores/page';
 import { useIsDeviceSmallerThanMd } from '~/stores/ui';
 
 import { HasChildren } from '../../interfaces/common';

+ 2 - 1
packages/app/src/components/Navbar/GrowiNavbarBottom.tsx

@@ -1,7 +1,8 @@
 import React from 'react';
 
-import { useCurrentPagePath, useIsSearchPage } from '~/stores/context';
+import { useIsSearchPage } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
+import { useCurrentPagePath } from '~/stores/page';
 import { useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
 
 import { GlobalSearch } from './GlobalSearch';

+ 2 - 1
packages/app/src/components/Page.tsx

@@ -13,7 +13,7 @@ import { HtmlElementNode } from 'rehype-toc';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { getOptionsToSave } from '~/client/util/editor';
 import {
-  useIsGuestUser, useCurrentPageTocNode, useShareLinkId,
+  useIsGuestUser, useShareLinkId,
 } from '~/stores/context';
 import {
   useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
@@ -21,6 +21,7 @@ import {
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import {
+  useCurrentPageTocNode,
   useEditorMode, useIsMobile,
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';

+ 2 - 2
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -7,10 +7,10 @@ import { Link } from 'react-scroll';
 
 import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import {
-  useCurrentPagePath, useIsSharedUser, useIsEditable, useShareLinkId, useIsNotFound,
+  useIsSharedUser, useIsEditable, useShareLinkId, useIsNotFound,
 } from '~/stores/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
+import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 
 import CountBadge from '../Common/CountBadge';

+ 1 - 2
packages/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -5,9 +5,8 @@ import { format } from 'date-fns';
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 
-import { useIsTrashPage } from '~/stores/context';
 import { usePageDeleteModal, usePutBackPageModal } from '~/stores/modal';
-import { useSWRxPageInfo, useSWRxCurrentPage } from '~/stores/page';
+import { useSWRxPageInfo, useSWRxCurrentPage, useIsTrashPage } from '~/stores/page';
 import { useIsAbleToShowTrashPageManagementButtons } from '~/stores/ui';
 
 const onDeletedHandler = (pathOrPathsToDelete) => {

+ 2 - 1
packages/app/src/components/PageComment/CommentEditor.tsx

@@ -13,10 +13,11 @@ import { apiPostForm } from '~/client/util/apiv1-client';
 import { IEditorMethods } from '~/interfaces/editor-methods';
 import { useSWRxPageComment } from '~/stores/comment';
 import {
-  useCurrentPagePath, useCurrentUser, useRevisionId, useIsSlackConfigured,
+  useCurrentUser, useRevisionId, useIsSlackConfigured,
   useIsUploadableFile, useIsUploadableImage,
 } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
+import { useCurrentPagePath } from '~/stores/page';
 
 import { CustomNavTab } from '../CustomNavigation/CustomNav';
 import NotAvailableForGuest from '../NotAvailableForGuest';

+ 2 - 2
packages/app/src/components/PageEditor.tsx

@@ -15,14 +15,14 @@ import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
 import { getOptionsToSave } from '~/client/util/editor';
 import { IEditorMethods } from '~/interfaces/editor-methods';
 import {
-  useCurrentPagePath, useCurrentPathname, useCurrentPageId,
+  useCurrentPathname, useCurrentPageId,
   useIsEditable, useIsIndentSizeForced, useIsUploadableFile, useIsUploadableImage, useEditingMarkdown,
 } from '~/stores/context';
 import {
   useCurrentIndentSize, useSWRxSlackChannels, useIsSlackEnabled, useIsTextlintEnabled, usePageTagsForEditors,
   useIsEnabledUnsavedWarning,
 } from '~/stores/editor';
-import { useSWRxCurrentPage } from '~/stores/page';
+import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
 import { usePreviewOptions } from '~/stores/renderer';
 import {
   EditorMode,

+ 2 - 1
packages/app/src/components/PageEditor/EditorNavbarBottom.tsx

@@ -4,8 +4,9 @@ import dynamic from 'next/dynamic';
 import { Collapse, Button } from 'reactstrap';
 
 
-import { useCurrentPagePath, useIsSlackConfigured } from '~/stores/context';
+import { useIsSlackConfigured } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
+import { useCurrentPagePath } from '~/stores/page';
 import {
   EditorMode, useDrawerOpened, useEditorMode, useIsDeviceSmallerThanMd,
 } from '~/stores/ui';

+ 1 - 1
packages/app/src/components/PageEditor/LinkEditModal.jsx

@@ -17,7 +17,7 @@ import validator from 'validator';
 
 import Linker from '~/client/models/Linker';
 import { apiv3Get } from '~/client/util/apiv3-client';
-import { useCurrentPagePath } from '~/stores/context';
+import { useCurrentPagePath } from '~/stores/page';
 
 import PagePreviewIcon from '../Icons/PagePreviewIcon';
 import SearchTypeahead from '../SearchTypeahead';

+ 6 - 6
packages/app/src/components/PageEditorByHackmd.tsx

@@ -13,15 +13,15 @@ import { apiPost } from '~/client/util/apiv1-client';
 import { getOptionsToSave } from '~/client/util/editor';
 import { IResHackmdIntegrated, IResHackmdDiscard } from '~/interfaces/hackmd';
 import {
-  useCurrentPagePath, useCurrentPageId, useHackmdUri,
+  useCurrentPageId, useCurrentPathname, useHackmdUri,
 } from '~/stores/context';
-import {
-  usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useRemoteRevisionId,
-} from '~/stores/hackmd';
 import {
   useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
 } from '~/stores/editor';
-import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
+import {
+  usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useRemoteRevisionId,
+} from '~/stores/hackmd';
+import { useCurrentPagePath, useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import {
   EditorMode,
   useEditorMode, useSelectedGrant,
@@ -43,7 +43,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
   const { t } = useTranslation();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: currentPagePath } = useCurrentPagePath();
-  const { data: currentPathname } = useCurrentPagePath();
+  const { data: currentPathname } = useCurrentPathname();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: pageId } = useCurrentPageId();

+ 1 - 1
packages/app/src/components/PageTimeline.tsx

@@ -5,7 +5,7 @@ import Link from 'next/link';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { IPageHasId } from '~/interfaces/page';
-import { useCurrentPagePath } from '~/stores/context';
+import { useCurrentPagePath } from '~/stores/page';
 import { useTimelineOptions } from '~/stores/renderer';
 
 import { RevisionLoader } from './Page/RevisionLoader';

+ 1 - 1
packages/app/src/components/RevisionComparer/RevisionComparer.tsx

@@ -7,7 +7,7 @@ import {
   Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
 } from 'reactstrap';
 
-import { useCurrentPagePath } from '~/stores/context';
+import { useCurrentPagePath } from '~/stores/page';
 
 import { RevisionDiff } from '../PageHistory/RevisionDiff';
 

+ 2 - 1
packages/app/src/components/SavePageControls.tsx

@@ -11,9 +11,10 @@ import {
 import { CustomWindow } from '~/interfaces/global';
 import { IPageGrantData } from '~/interfaces/page';
 import {
-  useCurrentPagePath, useIsEditable, useCurrentPageId, useIsAclEnabled,
+  useIsEditable, useCurrentPageId, useIsAclEnabled,
 } from '~/stores/context';
 import { useIsEnabledUnsavedWarning } from '~/stores/editor';
+import { useCurrentPagePath } from '~/stores/page';
 import { useSelectedGrant } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/components/Sidebar/PageTree.tsx

@@ -3,8 +3,9 @@ import React, { FC, memo } from 'react';
 import { useTranslation } from 'next-i18next';
 
 import {
-  useCurrentPagePath, useCurrentPageId, useTargetAndAncestors, useIsGuestUser,
+  useCurrentPageId, useTargetAndAncestors, useIsGuestUser,
 } from '~/stores/context';
+import { useCurrentPagePath } from '~/stores/page';
 import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
 
 import ItemsTree from './PageTree/ItemsTree';

+ 3 - 8
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -39,7 +39,7 @@ interface ItemProps {
   targetPathOrId?: Nullable<string>
   isOpen?: boolean
   isEnabledAttachTitleHeader?: boolean
-  onRenamed?(): void
+  onRenamed?(fromPath: string | undefined, toPath: string): void
   onClickDuplicateMenuItem?(pageToDuplicate: IPageForPageDuplicateModal): void
   onClickDeleteMenuItem?(pageToDelete: IPageToDeleteWithMeta): void
 }
@@ -191,7 +191,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
       await mutateChildren();
 
       if (onRenamed != null) {
-        onRenamed();
+        onRenamed(page.path, newPagePath);
       }
 
       // force open
@@ -286,7 +286,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
       });
 
       if (onRenamed != null) {
-        onRenamed();
+        onRenamed(page.path, newPagePath);
       }
 
       toastSuccess(t('renamed_pages', { path: page.path }));
@@ -380,11 +380,6 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
   const pathRecoveryMenuItemClickHandler = async(pageId: string): Promise<void> => {
     try {
       await resumeRenameOperation(pageId);
-
-      if (onRenamed != null) {
-        onRenamed();
-      }
-
       toastSuccess(t('page_operation.paths_recovered'));
     }
     catch {

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

@@ -15,6 +15,7 @@ import { useIsEnabledAttachTitleHeader } from '~/stores/context';
 import {
   IPageForPageDuplicateModal, usePageDuplicateModal, usePageDeleteModal,
 } from '~/stores/modal';
+import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
 import {
   usePageTreeTermManager, useSWRxPageAncestorsChildren, useSWRxRootPage, useDescendantsPageListForCurrentPathTermManager,
 } from '~/stores/page-listing';
@@ -102,6 +103,7 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
 
   const { data: ancestorsChildrenResult, error: error1 } = useSWRxPageAncestorsChildren(targetPath);
   const { data: rootPageResult, error: error2 } = useSWRxRootPage();
+  const { data: currentPagePath } = useCurrentPagePath();
   const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openDeleteModal } = usePageDeleteModal();
@@ -111,6 +113,7 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   const { data: ptDescCountMap, update: updatePtDescCountMap } = usePageTreeDescCountMap();
 
   // for mutation
+  const { mutate: mutateCurrentPage } = useSWRxCurrentPage();
   const { advance: advancePt } = usePageTreeTermManager();
   const { advance: advanceFts } = useFullTextSearchTermManager();
   const { advance: advanceDpl } = useDescendantsPageListForCurrentPathTermManager();
@@ -142,13 +145,17 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
 
   }, [socket, ptDescCountMap, updatePtDescCountMap]);
 
-  const onRenamed = () => {
+  const onRenamed = useCallback((fromPath: string | undefined, toPath: string) => {
     advancePt();
     advanceFts();
     advanceDpl();
-  };
 
-  const onClickDuplicateMenuItem = (pageToDuplicate: IPageForPageDuplicateModal) => {
+    if (currentPagePath === fromPath || currentPagePath === toPath) {
+      mutateCurrentPage();
+    }
+  }, [advanceDpl, advanceFts, advancePt, currentPagePath, mutateCurrentPage]);
+
+  const onClickDuplicateMenuItem = useCallback((pageToDuplicate: IPageForPageDuplicateModal) => {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     const duplicatedHandler: OnDuplicatedFunction = (fromPath, toPath) => {
       toastSuccess(t('duplicated_pages', { fromPath }));
@@ -159,9 +166,9 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
     };
 
     openDuplicateModal(pageToDuplicate, { onDuplicated: duplicatedHandler });
-  };
+  }, [advanceDpl, advanceFts, advancePt, openDuplicateModal, t]);
 
-  const onClickDeleteMenuItem = (pageToDelete: IPageToDeleteWithMeta) => {
+  const onClickDeleteMenuItem = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
     const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, isRecursively, isCompletely) => {
       if (typeof pathOrPathsToDelete !== 'string') {
         return;
@@ -179,10 +186,14 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
       advancePt();
       advanceFts();
       advanceDpl();
+
+      if (currentPagePath === pathOrPathsToDelete) {
+        mutateCurrentPage();
+      }
     };
 
     openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler });
-  };
+  }, [advanceDpl, advanceFts, advancePt, currentPagePath, mutateCurrentPage, openDeleteModal, t]);
 
   // ***************************  Scroll on init ***************************
   const scrollOnInit = useCallback(() => {

+ 1 - 1
packages/app/src/components/TableOfContents.tsx

@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
 import { pagePathUtils } from '@growi/core';
 import ReactMarkdown from 'react-markdown';
 
-import { useCurrentPagePath } from '~/stores/context';
+import { useCurrentPagePath } from '~/stores/page';
 import { useTocOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 

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

@@ -56,7 +56,7 @@ import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 // import { serializeUserSecurely } from '../server/models/serializers/user-serializer';
 // import PageStatusAlert from '../client/js/components/PageStatusAlert';
 import {
-  useCurrentUser, useCurrentPagePath,
+  useCurrentUser,
   useIsLatestRevision,
   useIsForbidden, useIsNotFound, useIsSharedUser,
   useIsEnabledStaleNotification, useIsIdenticalPath,
@@ -239,7 +239,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
 
   useCurrentPageId(pageId ?? null);
   // useIsNotCreatable(props.isForbidden || !isCreatablePage(pagePath)); // TODO: need to include props.isIdentical
-  useCurrentPagePath(pagePath);
   useCurrentPathname(props.currentPathname);
 
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data

+ 1 - 5
packages/app/src/pages/installer.page.tsx

@@ -10,8 +10,7 @@ import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 
 import InstallerForm from '../components/InstallerForm';
 import {
-  useCurrentPagePath, useCsrfToken,
-  useAppTitle, useSiteUrl, useConfidential,
+  useCsrfToken, useAppTitle, useSiteUrl, useConfidential,
 } from '../stores/context';
 
 
@@ -40,9 +39,6 @@ const InstallerPage: NextPage<Props> = (props: Props) => {
   useConfidential(props.confidential);
   useCsrfToken(props.csrfToken);
 
-  // page
-  useCurrentPagePath(props.currentPathname);
-
   const classNames: string[] = [];
 
   return (

+ 1 - 2
packages/app/src/pages/share/[[...path]].page.tsx

@@ -20,7 +20,7 @@ import { CrowiRequest } from '~/interfaces/crowi-request';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { IShareLinkHasId } from '~/interfaces/share-link';
 import {
-  useCurrentUser, useCurrentPagePath, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
+  useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault,
 } from '~/stores/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
@@ -50,7 +50,6 @@ const SharedPage: NextPage<Props> = (props: Props) => {
   useIsSearchPage(false);
   useShareLinkId(props.shareLink?._id);
   useCurrentPageId(props.shareLink?.relatedPage._id);
-  useCurrentPagePath(props.shareLink?.relatedPage.path);
   useCurrentUser(props.currentUser);
   useCurrentPathname(props.currentPathname);
   useRendererConfig(props.rendererConfig);

+ 1 - 2
packages/app/src/pages/trash.page.tsx

@@ -16,7 +16,7 @@ import {
 
 import { BasicLayout } from '../components/Layout/BasicLayout';
 import {
-  useCurrentUser, useCurrentPageId, useCurrentPagePath, useCurrentPathname,
+  useCurrentUser, useCurrentPageId, useCurrentPathname,
   useIsSearchServiceConfigured, useIsSearchServiceReachable,
   useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL, useIsGuestUser,
 } from '../stores/context';
@@ -52,7 +52,6 @@ const TrashPage: NextPage<CommonProps> = (props: Props) => {
   useIsSearchPage(false);
   useCurrentPageId(null);
   useCurrentPathname('/trash');
-  useCurrentPagePath('/trash');
 
   // UserUISettings
   usePreferDrawerModeByUser(props.userUISettings?.preferDrawerModeByUser ?? props.sidebarConfig.isSidebarDrawerMode);

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

@@ -52,10 +52,6 @@ export const useRevisionId = (initialData?: Nullable<any>): SWRResponse<Nullable
   return useContextSWR<Nullable<any>, Error>('revisionId', initialData);
 };
 
-export const useCurrentPagePath = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
-  return useContextSWR<Nullable<string>, Error>('currentPagePath', initialData);
-};
-
 export const useCurrentPathname = (initialData?: string): SWRResponse<string, Error> => {
   return useContextSWR('currentPathname', initialData);
 };
@@ -251,18 +247,3 @@ export const useIsEditable = (): SWRResponse<boolean, Error> => {
     },
   );
 };
-
-export const useCurrentPageTocNode = (): SWRResponse<HtmlElementNode, any> => {
-  const { data: currentPagePath } = useCurrentPagePath();
-
-  return useStaticSWR(['currentPageTocNode', currentPagePath]);
-};
-
-export const useIsTrashPage = (): SWRResponse<boolean, Error> => {
-  const { data: pagePath } = useCurrentPagePath();
-
-  return useSWRImmutable(
-    pagePath == null ? null : ['isTrashPage', pagePath],
-    (key: Key, pagePath: string) => pagePathUtils.isTrashPage(pagePath),
-  );
-};

+ 1 - 1
packages/app/src/stores/page-listing.tsx

@@ -13,7 +13,7 @@ import {
   AncestorsChildrenResult, ChildrenResult, V5MigrationStatus, RootPageResult,
 } from '../interfaces/page-listing-results';
 
-import { useCurrentPagePath } from './context';
+import { useCurrentPagePath } from './page';
 import { ITermNumberManagerUtil, useTermNumberManager } from './use-static-swr';
 
 export const useSWRxPagesByPath = (path?: Nullable<string>): SWRResponse<IPageHasId[], Error> => {

+ 1 - 1
packages/app/src/stores/page-redirect.tsx

@@ -3,7 +3,7 @@ import { SWRResponse } from 'swr';
 
 import { apiPost } from '~/client/util/apiv1-client';
 
-import { useCurrentPagePath } from './context';
+import { useCurrentPagePath } from './page';
 import { useStaticSWR } from './use-static-swr';
 
 type RedirectFromUtil = {

+ 45 - 3
packages/app/src/stores/page.tsx

@@ -1,5 +1,8 @@
-import { IPageInfoForEntity, IPagePopulatedToShowRevision, Nullable } from '@growi/core';
-import useSWR, { SWRResponse } from 'swr';
+import type {
+  IPageInfoForEntity, IPagePopulatedToShowRevision, Nullable,
+} from '@growi/core';
+import { pagePathUtils } from '@growi/core';
+import useSWR, { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiGet } from '~/client/util/apiv1-client';
@@ -12,7 +15,10 @@ import { IRevisionsForPagination } from '~/interfaces/revision';
 
 import { IPageTagsInfo } from '../interfaces/tag';
 
-import { useCurrentPageId } from './context';
+import { useCurrentPageId, useCurrentPathname } from './context';
+
+
+const { isPermalink: _isPermalink } = pagePathUtils;
 
 
 export const useSWRxPage = (pageId?: string|null, shareLinkId?: string): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
@@ -140,3 +146,39 @@ export const useSWRxApplicableGrant = (
     (endpoint, pageId) => apiv3Get(endpoint, { pageId }).then(response => response.data),
   );
 };
+
+
+/** **********************************************************
+ *                     Computed states
+ *********************************************************** */
+
+export const useCurrentPagePath = (): SWRResponse<string | undefined, Error> => {
+  const { data: currentPage } = useSWRxCurrentPage();
+  const { data: currentPathname } = useCurrentPathname();
+
+  return useSWRImmutable(
+    ['currentPagePath', currentPage?.path, currentPathname],
+    (key: Key, pagePath: string|undefined, pathname: string|undefined) => {
+      if (currentPage?.path != null) {
+        return currentPage.path;
+      }
+      if (pathname != null && !_isPermalink(pathname)) {
+        return pathname;
+      }
+      return undefined;
+    },
+    // TODO: set fallbackData
+    // { fallbackData:  }
+  );
+};
+
+export const useIsTrashPage = (): SWRResponse<boolean, Error> => {
+  const { data: pagePath } = useCurrentPagePath();
+
+  return useSWRImmutable(
+    pagePath == null ? null : ['isTrashPage', pagePath],
+    (key: Key, pagePath: string) => pagePathUtils.isTrashPage(pagePath),
+    // TODO: set fallbackData
+    // { fallbackData:  }
+  );
+};

+ 3 - 1
packages/app/src/stores/renderer.tsx

@@ -11,8 +11,10 @@ import {
 
 
 import {
-  useCurrentPagePath, useCurrentPageTocNode, useRendererConfig,
+  useRendererConfig,
 } from './context';
+import { useCurrentPagePath } from './page';
+import { useCurrentPageTocNode } from './ui';
 
 interface ReactMarkdownOptionsGenerator {
   (config: RendererConfig): RendererOptions

+ 9 - 2
packages/app/src/stores/ui.tsx

@@ -5,6 +5,7 @@ import {
 } from '@growi/core';
 import { withUtils, SWRResponseWithUtils } from '@growi/core/src/utils/with-utils';
 import { Breakpoint, addBreakpointListener, cleanupBreakpointListener } from '@growi/ui';
+import { HtmlElementNode } from 'rehype-toc';
 import SimpleBar from 'simplebar-react';
 import {
   useSWRConfig, SWRResponse, Key, Fetcher,
@@ -21,10 +22,11 @@ import { UpdateDescCountData } from '~/interfaces/websocket';
 import loggerFactory from '~/utils/logger';
 
 import {
-  useCurrentPageId, useCurrentPagePath, useIsEditable, useIsTrashPage, useIsGuestUser,
+  useCurrentPageId, useIsEditable, useIsGuestUser,
   useIsSharedUser, useIsIdenticalPath, useCurrentUser, useIsNotFound, useShareLinkId,
 } from './context';
 import { localStorageMiddleware } from './middlewares/sync-to-storage';
+import { useCurrentPagePath, useIsTrashPage } from './page';
 import { useStaticSWR } from './use-static-swr';
 
 const { isTrashTopPage, isUsersTopPage } = pagePathUtils;
@@ -45,13 +47,18 @@ export type EditorMode = typeof EditorMode[keyof typeof EditorMode];
 
 
 /** **********************************************************
- *                     Storing RefObjects
+ *                     Storing objects to ref
  *********************************************************** */
 
 export const useSidebarScrollerRef = (initialData?: RefObject<SimpleBar>): SWRResponse<RefObject<SimpleBar>, Error> => {
   return useStaticSWR<RefObject<SimpleBar>, Error>('sidebarScrollerRef', initialData);
 };
 
+export const useCurrentPageTocNode = (): SWRResponse<HtmlElementNode, any> => {
+  const { data: currentPagePath } = useCurrentPagePath();
+
+  return useStaticSWR(['currentPageTocNode', currentPagePath]);
+};
 
 /** **********************************************************
  *                          SWR Hooks