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

Merge branch 'master' into imprv/frequent-swr-calling

Yuki Takei 3 лет назад
Родитель
Сommit
d04f994da3
24 измененных файлов с 138 добавлено и 65 удалено
  1. 2 3
      packages/app/src/client/services/layout.ts
  2. 1 1
      packages/app/src/components/Admin/AdminHome/SystemInfomationTable.tsx
  3. 1 1
      packages/app/src/components/Layout/MainPane.tsx
  4. 1 1
      packages/app/src/components/Me/ApiSettings.tsx
  5. 2 2
      packages/app/src/components/Me/ProfileImageSettings.tsx
  6. 1 1
      packages/app/src/components/Navbar/AuthorInfo.tsx
  7. 12 5
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  8. 4 1
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx
  9. 16 8
      packages/app/src/components/Page.tsx
  10. 2 2
      packages/app/src/components/Page/DisplaySwitcher.tsx
  11. 1 1
      packages/app/src/components/PageAlert/TrashPageAlert.tsx
  12. 1 1
      packages/app/src/components/SearchPage.tsx
  13. 1 1
      packages/app/src/components/Sidebar/RecentChanges.tsx
  14. 1 1
      packages/app/src/components/Sidebar/SidebarNav.tsx
  15. 1 1
      packages/app/src/components/User/UserDate.jsx
  16. 2 2
      packages/app/src/pages/[[...path]].page.tsx
  17. 24 4
      packages/app/src/pages/share/[[...path]].page.tsx
  18. 2 2
      packages/app/src/stores/context.tsx
  19. 18 7
      packages/app/src/stores/page.tsx
  20. 1 3
      packages/app/test/cypress/integration/10-install/10-install--install.spec.ts
  21. 14 12
      packages/app/test/cypress/integration/50-sidebar/50-sidebar--access-to-side-bar.spec.ts
  22. 10 4
      packages/app/test/cypress/integration/50-sidebar/50-sidebar--switching-sidebar-mode.spec.ts
  23. 14 0
      packages/app/test/cypress/support/blackout.ts
  24. 6 1
      packages/app/test/cypress/support/screenshot.ts

+ 2 - 3
packages/app/src/client/services/layout.ts

@@ -1,4 +1,4 @@
-import { useIsContainerFluid, useShareLinkId } from '~/stores/context';
+import { useIsContainerFluid } from '~/stores/context';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useEditorMode } from '~/stores/ui';
 import { useEditorMode } from '~/stores/ui';
 
 
@@ -17,8 +17,7 @@ export const useEditorModeClassName = (): string => {
 };
 };
 
 
 export const useCurrentGrowiLayoutFluidClassName = (): string => {
 export const useCurrentGrowiLayoutFluidClassName = (): string => {
-  const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { data: currentPage } = useSWRxCurrentPage();
 
 
   const { data: dataIsContainerFluid } = useIsContainerFluid();
   const { data: dataIsContainerFluid } = useIsContainerFluid();
 
 

+ 1 - 1
packages/app/src/components/Admin/AdminHome/SystemInfomationTable.tsx

@@ -25,7 +25,7 @@ const SystemInformationTable = (props: Props) => {
       <tbody>
       <tbody>
         <tr>
         <tr>
           <th>GROWI</th>
           <th>GROWI</th>
-          <td data-hide-in-vrt>{ growiVersion }</td>
+          <td data-vrt-blackout>{ growiVersion }</td>
         </tr>
         </tr>
         <tr>
         <tr>
           <th>node.js</th>
           <th>node.js</th>

+ 1 - 1
packages/app/src/components/Layout/MainPane.tsx

@@ -24,7 +24,7 @@ export const MainPane = (props: Props): JSX.Element => {
                   <div className="flex-grow-1 flex-basis-0 mw-0">
                   <div className="flex-grow-1 flex-basis-0 mw-0">
                     {children}
                     {children}
                   </div>
                   </div>
-                  <div className="grw-side-contents-container d-edit-none">
+                  <div className="grw-side-contents-container d-edit-none" data-vrt-blackout-side-contents>
                     <div className="grw-side-contents-sticky-container">
                     <div className="grw-side-contents-sticky-container">
                       {sideContents}
                       {sideContents}
                     </div>
                     </div>

+ 1 - 1
packages/app/src/components/Me/ApiSettings.tsx

@@ -39,7 +39,7 @@ const ApiSettings = React.memo((): JSX.Element => {
             ? (
             ? (
               <input
               <input
                 data-testid="grw-api-settings-input"
                 data-testid="grw-api-settings-input"
-                data-hide-in-vrt
+                data-vrt-blackout
                 className="form-control"
                 className="form-control"
                 type="text"
                 type="text"
                 name="apiToken"
                 name="apiToken"

+ 2 - 2
packages/app/src/components/Me/ProfileImageSettings.tsx

@@ -105,14 +105,14 @@ const ProfileImageSettings = (): JSX.Element => {
                 onChange={() => setGravatarEnabled(true)}
                 onChange={() => setGravatarEnabled(true)}
               />
               />
               <label className="custom-control-label" htmlFor="radioGravatar">
               <label className="custom-control-label" htmlFor="radioGravatar">
-                <img src={GRAVATAR_DEFAULT} data-hide-in-vrt /> Gravatar
+                <img src={GRAVATAR_DEFAULT} data-vrt-blackout-profile /> Gravatar
               </label>
               </label>
               <a href="https://gravatar.com/">
               <a href="https://gravatar.com/">
                 <small><i className="icon-arrow-right-circle" aria-hidden="true"></i></small>
                 <small><i className="icon-arrow-right-circle" aria-hidden="true"></i></small>
               </a>
               </a>
             </div>
             </div>
           </h4>
           </h4>
-          <img src={generateGravatarSrc(currentUser.email)} width="64" data-hide-in-vrt />
+          <img src={generateGravatarSrc(currentUser.email)} width="64" data-vrt-blackout-profile />
         </div>
         </div>
 
 
         <div className="col-md-6 col-12">
         <div className="col-md-6 col-12">

+ 1 - 1
packages/app/src/components/Navbar/AuthorInfo.tsx

@@ -66,7 +66,7 @@ export const AuthorInfo = (props: AuthorInfoProps): JSX.Element => {
       </div>
       </div>
       <div>
       <div>
         <div>{infoLabelForSubNav} {userLabel}</div>
         <div>{infoLabelForSubNav} {userLabel}</div>
-        <div className="text-muted text-date" data-hide-in-vrt>
+        <div className="text-muted text-date" data-vrt-blackout-datetime>
           {renderParsedDate()}
           {renderParsedDate()}
         </div>
         </div>
       </div>
       </div>

+ 12 - 5
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -1,6 +1,8 @@
 import React, { useState, useEffect, useCallback } from 'react';
 import React, { useState, useEffect, useCallback } from 'react';
 
 
-import { isPopulated, IUser, pagePathUtils } from '@growi/core';
+import {
+  isPopulated, IUser, pagePathUtils, IPagePopulatedToShowRevision,
+} from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
@@ -182,16 +184,19 @@ const CreateTemplateMenuItems = (props: CreateTemplateMenuItemsProps): JSX.Eleme
 };
 };
 
 
 type GrowiContextualSubNavigationProps = {
 type GrowiContextualSubNavigationProps = {
+  currentPage?: IPagePopulatedToShowRevision,
   isCompactMode?: boolean,
   isCompactMode?: boolean,
   isLinkSharingDisabled: boolean,
   isLinkSharingDisabled: boolean,
 };
 };
 
 
 const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
 const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
 
 
+  const { currentPage } = props;
+
   const router = useRouter();
   const router = useRouter();
 
 
   const { data: shareLinkId } = useShareLinkId();
   const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { mutate: mutateCurrentPage } = useSWRxCurrentPage();
 
 
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPathname } = useCurrentPathname();
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
@@ -314,9 +319,11 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   }, [currentPathname, openDeleteModal, router]);
   }, [currentPathname, openDeleteModal, router]);
 
 
   const switchContentWidthHandler = useCallback(async(pageId: string, value: boolean) => {
   const switchContentWidthHandler = useCallback(async(pageId: string, value: boolean) => {
-    await updateContentWidth(pageId, value);
-    mutateCurrentPage();
-  }, [mutateCurrentPage]);
+    if (!isSharedPage) {
+      await updateContentWidth(pageId, value);
+      mutateCurrentPage();
+    }
+  }, [isSharedPage, mutateCurrentPage]);
 
 
   const templateMenuItemClickHandler = useCallback(() => {
   const templateMenuItemClickHandler = useCallback(() => {
     setIsPageTempleteModalShown(true);
     setIsPageTempleteModalShown(true);

+ 4 - 1
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx

@@ -9,6 +9,8 @@ import { debounce } from 'throttle-debounce';
 import { useSidebarCollapsed } from '~/stores/ui';
 import { useSidebarCollapsed } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import { useSWRxCurrentPage } from '~/stores/page';
+
 import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
 import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
 
 
 import styles from './GrowiSubNavigationSwitcher.module.scss';
 import styles from './GrowiSubNavigationSwitcher.module.scss';
@@ -27,6 +29,7 @@ const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
  */
  */
 const GrowiSubNavigationSwitcher = (props) => {
 const GrowiSubNavigationSwitcher = (props) => {
 
 
+  const { data: currentPage } = useSWRxCurrentPage();
   const { data: isSidebarCollapsed } = useSidebarCollapsed();
   const { data: isSidebarCollapsed } = useSidebarCollapsed();
 
 
   const [isVisible, setVisible] = useState(false);
   const [isVisible, setVisible] = useState(false);
@@ -125,7 +128,7 @@ const GrowiSubNavigationSwitcher = (props) => {
         ref={fixedContainerRef}
         ref={fixedContainerRef}
         style={{ width }}
         style={{ width }}
       >
       >
-        <GrowiContextualSubNavigation isCompactMode isLinkSharingDisabled />
+        <GrowiContextualSubNavigation currentPage isCompactMode isLinkSharingDisabled />
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 16 - 8
packages/app/src/components/Page.tsx

@@ -1,11 +1,11 @@
 import React, {
 import React, {
-  useCallback,
+  FC, useCallback,
   useEffect, useRef,
   useEffect, useRef,
 } from 'react';
 } from 'react';
 
 
 import EventEmitter from 'events';
 import EventEmitter from 'events';
 
 
-import { pagePathUtils } from '@growi/core';
+import { pagePathUtils, IPagePopulatedToShowRevision } from '@growi/core';
 import { DrawioEditByViewerProps } from '@growi/remark-drawio';
 import { DrawioEditByViewerProps } from '@growi/remark-drawio';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
@@ -48,9 +48,13 @@ const LinkEditModal = dynamic(() => import('./PageEditor/LinkEditModal'), { ssr:
 
 
 const logger = loggerFactory('growi:Page');
 const logger = loggerFactory('growi:Page');
 
 
+type Props = {
+  currentPage?: IPagePopulatedToShowRevision,
+}
 
 
-export const Page = (props) => {
+export const Page: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { currentPage } = props;
 
 
   // Pass tocRef to generateViewOptions (=> rehypePlugin => customizeTOC) to call mutateCurrentPageTocNode when tocRef.current changes.
   // Pass tocRef to generateViewOptions (=> rehypePlugin => customizeTOC) to call mutateCurrentPageTocNode when tocRef.current changes.
   // The toc node passed by customizeTOC is assigned to tocRef.current.
   // The toc node passed by customizeTOC is assigned to tocRef.current.
@@ -64,7 +68,7 @@ export const Page = (props) => {
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
 
 
   const { data: shareLinkId } = useShareLinkId();
   const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { mutate: mutateCurrentPage } = useSWRxCurrentPage();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { data: tagsInfo } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
   const { data: tagsInfo } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
@@ -128,14 +132,16 @@ export const Page = (props) => {
       toastSuccess(t('toaster.save_succeeded'));
       toastSuccess(t('toaster.save_succeeded'));
 
 
       // rerender
       // rerender
-      mutateCurrentPage();
+      if (!isSharedPage) {
+        mutateCurrentPage();
+      }
       mutateEditingMarkdown(newMarkdown);
       mutateEditingMarkdown(newMarkdown);
     }
     }
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
       toastError(error);
       toastError(error);
     }
     }
-  }, [currentPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
+  }, [currentPage, isSharedPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
 
 
   // set handler to open DrawioModal
   // set handler to open DrawioModal
   useEffect(() => {
   useEffect(() => {
@@ -182,14 +188,16 @@ export const Page = (props) => {
       toastSuccess(t('toaster.save_succeeded'));
       toastSuccess(t('toaster.save_succeeded'));
 
 
       // rerender
       // rerender
-      mutateCurrentPage();
+      if (!isSharedPage) {
+        mutateCurrentPage();
+      }
       mutateEditingMarkdown(newMarkdown);
       mutateEditingMarkdown(newMarkdown);
     }
     }
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
       toastError(error);
       toastError(error);
     }
     }
-  }, [currentPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
+  }, [currentPage, isSharedPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
 
 
   // set handler to open HandsonTableModal
   // set handler to open HandsonTableModal
   useEffect(() => {
   useEffect(() => {

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

@@ -34,7 +34,7 @@ const PageView = React.memo((): JSX.Element => {
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: shareLinkId } = useShareLinkId();
   const { data: shareLinkId } = useShareLinkId();
   const { data: isNotFound } = useIsNotFound();
   const { data: isNotFound } = useIsNotFound();
-  const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { data: currentPage } = useSWRxCurrentPage();
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
 
 
   const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
   const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
@@ -92,7 +92,7 @@ const PageView = React.memo((): JSX.Element => {
   return (
   return (
     <>
     <>
       { isUsersHomePagePath && <UserInfo author={currentPage?.creator} /> }
       { isUsersHomePagePath && <UserInfo author={currentPage?.creator} /> }
-      { !isNotFound && <Page /> }
+      { !isNotFound && <Page currentPage={currentPage ?? undefined} /> }
       { isNotFound && <NotFoundPage /> }
       { isNotFound && <NotFoundPage /> }
     </>
     </>
   );
   );

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

@@ -114,7 +114,7 @@ export const TrashPageAlert = (): JSX.Element => {
           <br />
           <br />
           <UserPicture user={deleteUser} />
           <UserPicture user={deleteUser} />
           <span className="ml-2">
           <span className="ml-2">
-            Deleted by { deleteUser?.name } at {deletedAt || pageData?.updatedAt}
+            Deleted by { deleteUser?.name } at <span data-vrt-blackout-datetime>{deletedAt || pageData?.updatedAt}</span>
           </span>
           </span>
         </div>
         </div>
         <div className="pt-1 d-flex align-items-end align-items-lg-center">
         <div className="pt-1 d-flex align-items-end align-items-lg-center">

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

@@ -63,7 +63,7 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
         <span className="ml-3">{`${leftNum}-${rightNum}`} / {total}</span>
         <span className="ml-3">{`${leftNum}-${rightNum}`} / {total}</span>
         { took != null && (
         { took != null && (
           // blackout 70px rectangle in VRT
           // blackout 70px rectangle in VRT
-          <span data-hide-in-vrt className="ml-3 text-muted d-inline-block" style={{ minWidth: '70px' }}>({took}ms)</span>
+          <span data-vrt-blackout className="ml-3 text-muted d-inline-block" style={{ minWidth: '70px' }}>({took}ms)</span>
         ) }
         ) }
       </div>
       </div>
       <div className="input-group flex-nowrap search-result-select-group ml-auto d-md-flex d-none">
       <div className="input-group flex-nowrap search-result-select-group ml-auto d-md-flex d-none">

+ 1 - 1
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -42,7 +42,7 @@ const PageItemLower = memo(({ page }: PageItemLowerProps): JSX.Element => {
         <div className="icon-bubble mr-1 d-inline-block"></div>
         <div className="icon-bubble mr-1 d-inline-block"></div>
         <div className="mr-2 grw-list-counts d-inline-block">{page.commentCount}</div>
         <div className="mr-2 grw-list-counts d-inline-block">{page.commentCount}</div>
       </div>
       </div>
-      <div className="grw-formatted-distance-date small mt-auto" data-hide-in-vrt>
+      <div className="grw-formatted-distance-date small mt-auto" data-vrt-blackout-datetime>
         <FormattedDistanceDate id={page._id} date={page.updatedAt} />
         <FormattedDistanceDate id={page._id} date={page.updatedAt} />
       </div>
       </div>
     </div>
     </div>

+ 1 - 1
packages/app/src/components/Sidebar/SidebarNav.tsx

@@ -85,7 +85,7 @@ export const SidebarNav: FC<Props> = (props: Props) => {
   const { onItemSelected } = props;
   const { onItemSelected } = props;
 
 
   return (
   return (
-    <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`}>
+    <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`} data-vrt-blackout-sidebar-nav>
       <div className="grw-sidebar-nav-primary-container">
       <div className="grw-sidebar-nav-primary-container">
         {/* eslint-disable max-len */}
         {/* eslint-disable max-len */}
         <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onItemSelected={onItemSelected} />
         <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onItemSelected={onItemSelected} />

+ 1 - 1
packages/app/src/components/User/UserDate.jsx

@@ -16,7 +16,7 @@ export default class UserDate extends React.Component {
     const dt = format(date, this.props.format);
     const dt = format(date, this.props.format);
 
 
     return (
     return (
-      <span className={this.props.className} data-hide-in-vrt>
+      <span className={this.props.className} data-vrt-blackout-datetime>
         {dt}
         {dt}
       </span>
       </span>
     );
     );

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

@@ -265,7 +265,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useHasDraftOnHackmd(pageWithMeta?.data.hasDraftOnHackmd ?? false);
   useHasDraftOnHackmd(pageWithMeta?.data.hasDraftOnHackmd ?? false);
   useCurrentPathname(props.currentPathname);
   useCurrentPathname(props.currentPathname);
 
 
-  useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
+  useSWRxCurrentPage(pageWithMeta?.data ?? null); // store initial data
 
 
   useEditingMarkdown(pageWithMeta?.data.revision?.body);
   useEditingMarkdown(pageWithMeta?.data.revision?.body);
 
 
@@ -328,7 +328,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
         <header className="py-0 position-relative">
         <header className="py-0 position-relative">
           <div id="grw-subnav-container">
           <div id="grw-subnav-container">
-            <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
+            <GrowiContextualSubNavigation currentPage={pageWithMeta?.data} isLinkSharingDisabled={props.disableLinkSharing} />
           </div>
           </div>
         </header>
         </header>
         <div className="d-edit-none">
         <div className="d-edit-none">

+ 24 - 4
packages/app/src/pages/share/[[...path]].page.tsx

@@ -1,12 +1,13 @@
 import React from 'react';
 import React from 'react';
 
 
-import { IUserHasId } from '@growi/core';
+import { IUserHasId, IPagePopulatedToShowRevision } from '@growi/core';
 import {
 import {
   GetServerSideProps, GetServerSidePropsContext,
   GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 } from 'next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import Head from 'next/head';
+import superjson from 'superjson';
 
 
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { MainPane } from '~/components/Layout/MainPane';
 import { MainPane } from '~/components/Layout/MainPane';
@@ -19,6 +20,7 @@ import { SupportedAction, SupportedActionType } from '~/interfaces/activity';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { IShareLinkHasId } from '~/interfaces/share-link';
 import { IShareLinkHasId } from '~/interfaces/share-link';
+import type { PageDocument } from '~/server/models/page';
 import {
 import {
   useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
@@ -38,6 +40,7 @@ const ShareLinkAlert = dynamic(() => import('~/components/Page/ShareLinkAlert'),
 const ForbiddenPage = dynamic(() => import('~/components/ForbiddenPage'), { ssr: false });
 const ForbiddenPage = dynamic(() => import('~/components/ForbiddenPage'), { ssr: false });
 
 
 type Props = CommonProps & {
 type Props = CommonProps & {
+  shareLinkRelatedPage?: IShareLinkRelatedPage,
   shareLink?: IShareLinkHasId,
   shareLink?: IShareLinkHasId,
   isExpired: boolean,
   isExpired: boolean,
   disableLinkSharing: boolean,
   disableLinkSharing: boolean,
@@ -48,6 +51,23 @@ type Props = CommonProps & {
   rendererConfig: RendererConfig,
   rendererConfig: RendererConfig,
 };
 };
 
 
+type IShareLinkRelatedPage = IPagePopulatedToShowRevision & PageDocument;
+
+superjson.registerCustom<IShareLinkRelatedPage, string>(
+  {
+    isApplicable: (v): v is IShareLinkRelatedPage => {
+      return v != null
+        && v.toObject != null
+        && v.lastUpdateUser != null
+        && v.creator != null
+        && v.revision != null;
+    },
+    serialize: (v) => { return superjson.stringify(v.toObject()) },
+    deserialize: (v) => { return superjson.parse(v) },
+  },
+  'IShareLinkRelatedPageTransformer',
+);
+
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useIsSearchPage(false);
   useIsSearchPage(false);
   useShareLinkId(props.shareLink?._id);
   useShareLinkId(props.shareLink?._id);
@@ -91,7 +111,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
 
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
         <header className="py-0 position-relative">
         <header className="py-0 position-relative">
-          {isShowSharedPage && <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />}
+          {isShowSharedPage && <GrowiContextualSubNavigation currentPage={props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />}
         </header>
         </header>
 
 
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
@@ -128,7 +148,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
           {(isShowSharedPage && shareLink != null) && (
           {(isShowSharedPage && shareLink != null) && (
             <>
             <>
               <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
               <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
-              <Page />
+              <Page currentPage={props.shareLinkRelatedPage} />
             </>
             </>
           )}
           )}
         </MainPane>
         </MainPane>
@@ -227,7 +247,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
     const ShareLinkModel = crowi.model('ShareLink');
     const ShareLinkModel = crowi.model('ShareLink');
     const shareLink = await ShareLinkModel.findOne({ _id: params.linkId }).populate('relatedPage');
     const shareLink = await ShareLinkModel.findOne({ _id: params.linkId }).populate('relatedPage');
     if (shareLink != null) {
     if (shareLink != null) {
-      await shareLink.relatedPage.populateDataToShowRevision();
+      props.shareLinkRelatedPage = await shareLink.relatedPage.populateDataToShowRevision();
       props.isExpired = shareLink.isExpired();
       props.isExpired = shareLink.isExpired();
       props.shareLink = shareLink.toObject();
       props.shareLink = shareLink.toObject();
     }
     }

+ 2 - 2
packages/app/src/stores/context.tsx

@@ -76,8 +76,8 @@ export const useIsSharedUser = (initialData?: boolean): SWRResponse<boolean, Err
   return useContextSWR<boolean, Error>('isSharedUser', initialData);
   return useContextSWR<boolean, Error>('isSharedUser', initialData);
 };
 };
 
 
-export const useShareLinkId = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
-  return useContextSWR<Nullable<string>, Error>('shareLinkId', initialData);
+export const useShareLinkId = (initialData?: string): SWRResponse<string, Error> => {
+  return useContextSWR('shareLinkId', initialData);
 };
 };
 
 
 export const useDisableLinkSharing = (initialData?: Nullable<boolean>): SWRResponse<Nullable<boolean>, Error> => {
 export const useDisableLinkSharing = (initialData?: Nullable<boolean>): SWRResponse<Nullable<boolean>, Error> => {

+ 18 - 7
packages/app/src/stores/page.tsx

@@ -4,7 +4,7 @@ import type {
   IPageInfoForEntity, IPagePopulatedToShowRevision, Nullable,
   IPageInfoForEntity, IPagePopulatedToShowRevision, Nullable,
 } from '@growi/core';
 } from '@growi/core';
 import { isClient, pagePathUtils } from '@growi/core';
 import { isClient, pagePathUtils } from '@growi/core';
-import useSWR, { Key, SWRResponse } from 'swr';
+import useSWR, { Key, SWRConfiguration, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
 import { apiGet } from '~/client/util/apiv1-client';
 import { apiGet } from '~/client/util/apiv1-client';
@@ -17,20 +17,22 @@ import { IRevisionsForPagination } from '~/interfaces/revision';
 
 
 import { IPageTagsInfo } from '../interfaces/tag';
 import { IPageTagsInfo } from '../interfaces/tag';
 
 
-import { useCurrentPageId, useCurrentPathname } from './context';
+import { useCurrentPageId, useCurrentPathname, useShareLinkId } from './context';
 import { ITermNumberManagerUtil, useTermNumberManager } from './use-static-swr';
 import { ITermNumberManagerUtil, useTermNumberManager } from './use-static-swr';
 
 
 const { isPermalink: _isPermalink } = pagePathUtils;
 const { isPermalink: _isPermalink } = pagePathUtils;
 
 
-
 export const useSWRxPage = (
 export const useSWRxPage = (
     pageId?: string|null,
     pageId?: string|null,
     shareLinkId?: string,
     shareLinkId?: string,
     revisionId?: string,
     revisionId?: string,
     initialData?: IPagePopulatedToShowRevision|null,
     initialData?: IPagePopulatedToShowRevision|null,
+    config?: SWRConfiguration,
 ): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
 ): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
   const swrResponse = useSWRImmutable<IPagePopulatedToShowRevision|null, Error>(
   const swrResponse = useSWRImmutable<IPagePopulatedToShowRevision|null, Error>(
     pageId != null ? ['/page', pageId, shareLinkId, revisionId] : null,
     pageId != null ? ['/page', pageId, shareLinkId, revisionId] : null,
+    // TODO: upgrade SWR to v2 and use useSWRMutation
+    //        in order to trigger mutation manually
     (endpoint, pageId, shareLinkId, revisionId) => apiv3Get<{ page: IPagePopulatedToShowRevision }>(endpoint, { pageId, shareLinkId, revisionId })
     (endpoint, pageId, shareLinkId, revisionId) => apiv3Get<{ page: IPagePopulatedToShowRevision }>(endpoint, { pageId, shareLinkId, revisionId })
       .then(result => result.data.page)
       .then(result => result.data.page)
       .catch((errs) => {
       .catch((errs) => {
@@ -42,6 +44,7 @@ export const useSWRxPage = (
         }
         }
         throw Error('failed to get page');
         throw Error('failed to get page');
       }),
       }),
+    config,
   );
   );
 
 
   useEffect(() => {
   useEffect(() => {
@@ -61,10 +64,9 @@ export const useSWRxPageByPath = (path?: string): SWRResponse<IPagePopulatedToSh
   );
   );
 };
 };
 
 
-export const useSWRxCurrentPage = (
-    shareLinkId?: string, initialData?: IPagePopulatedToShowRevision|null,
-): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
+export const useSWRxCurrentPage = (initialData?: IPagePopulatedToShowRevision|null): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
   const { data: currentPageId } = useCurrentPageId();
   const { data: currentPageId } = useCurrentPageId();
+  const { data: shareLinkId } = useShareLinkId();
 
 
   // Get URL parameter for specific revisionId
   // Get URL parameter for specific revisionId
   let revisionId: string|undefined;
   let revisionId: string|undefined;
@@ -74,7 +76,16 @@ export const useSWRxCurrentPage = (
     revisionId = requestRevisionId != null ? requestRevisionId : undefined;
     revisionId = requestRevisionId != null ? requestRevisionId : undefined;
   }
   }
 
 
-  const swrResult = useSWRxPage(currentPageId, shareLinkId, revisionId, initialData);
+  const swrResult = useSWRxPage(
+    currentPageId, shareLinkId, revisionId,
+    initialData,
+    // overwrite fetcher if the current page is share link
+    shareLinkId == null
+      ? undefined
+      : {
+        fetcher: () => null,
+      },
+  );
 
 
   return swrResult;
   return swrResult;
 };
 };

+ 1 - 3
packages/app/test/cypress/integration/10-install/10-install--install.spec.ts

@@ -53,8 +53,6 @@ describe('Install', () => {
     cy.getByTestid('grw-pagetree-item-container', { timeout: 20000 }).should('be.visible');
     cy.getByTestid('grw-pagetree-item-container', { timeout: 20000 }).should('be.visible');
 
 
     cy.waitUntilSkeletonDisappear();
     cy.waitUntilSkeletonDisappear();
-    cy.screenshot(`${ssPrefix}-installed-redirect-to-root-page`, {
-      blackout: ['[data-hide-in-vrt=true]']
-    });
+    cy.screenshot(`${ssPrefix}-installed-redirect-to-root-page`);
   });
   });
 });
 });

+ 14 - 12
packages/app/test/cypress/integration/50-sidebar/50-sidebar--access-to-side-bar.spec.ts

@@ -1,3 +1,11 @@
+import { BlackoutGroup } from "../../support/blackout";
+
+// Blackout for recalculation of toc content hight
+const blackoutOverride = [
+  ...BlackoutGroup.BASIS,
+  ...BlackoutGroup.SIDE_CONTENTS,
+];
+
 describe('Access to sidebar', () => {
 describe('Access to sidebar', () => {
   const ssPrefix = 'access-to-sidebar-';
   const ssPrefix = 'access-to-sidebar-';
 
 
@@ -29,8 +37,7 @@ describe('Access to sidebar', () => {
           cy.waitUntilSkeletonDisappear();
           cy.waitUntilSkeletonDisappear();
           cy.screenshot(`${ssPrefix}1-sidebar-shown`, {
           cy.screenshot(`${ssPrefix}1-sidebar-shown`, {
             capture: 'viewport',
             capture: 'viewport',
-            // Blackout for recalculation of toc content hight
-            blackout: ['.grw-side-contents-container', '[data-hide-in-vrt=true]'],
+            blackout: blackoutOverride,
           });
           });
         });
         });
 
 
@@ -42,8 +49,7 @@ describe('Access to sidebar', () => {
           cy.waitUntilSkeletonDisappear();
           cy.waitUntilSkeletonDisappear();
           cy.screenshot(`${ssPrefix}2-sidebar-collapsed`, {
           cy.screenshot(`${ssPrefix}2-sidebar-collapsed`, {
             capture: 'viewport',
             capture: 'viewport',
-            // Blackout for recalculation of toc content hight
-            blackout: ['.grw-side-contents-container', '[data-hide-in-vrt=true]'],
+            blackout: blackoutOverride,
           });
           });
         });
         });
       });
       });
@@ -92,8 +98,7 @@ describe('Access to sidebar', () => {
           });
           });
 
 
           cy.screenshot(`${ssPrefix}page-tree-3-before-click-button`, {
           cy.screenshot(`${ssPrefix}page-tree-3-before-click-button`, {
-            // Blackout for recalculation of toc content hight
-            blackout: ['.grw-side-contents-container', '[data-hide-in-vrt=true]'],
+            blackout: blackoutOverride,
           });
           });
 
 
           // click add remove bookmark btn
           // click add remove bookmark btn
@@ -113,8 +118,7 @@ describe('Access to sidebar', () => {
           });
           });
 
 
           cy.screenshot(`${ssPrefix}page-tree-4-after-click-button`, {
           cy.screenshot(`${ssPrefix}page-tree-4-after-click-button`, {
-            // Blackout for recalculation of toc content hight
-            blackout: ['.grw-side-contents-container', '[data-hide-in-vrt=true]'],
+            blackout: blackoutOverride,
           });
           });
         });
         });
 
 
@@ -250,8 +254,7 @@ describe('Access to sidebar', () => {
 
 
           // The scope of the screenshot is not narrowed because the blackout is shifted
           // The scope of the screenshot is not narrowed because the blackout is shifted
           cy.screenshot(`${ssPrefix}recent-changes-1-access-to-recent-changes`, {
           cy.screenshot(`${ssPrefix}recent-changes-1-access-to-recent-changes`, {
-            // Blackout for recalculation of toc content hight
-            blackout: ['.grw-side-contents-container', '[data-hide-in-vrt=true]'],
+            blackout: blackoutOverride,
           });
           });
         });
         });
 
 
@@ -263,8 +266,7 @@ describe('Access to sidebar', () => {
 
 
           // The scope of the screenshot is not narrowed because the blackout is shifted
           // The scope of the screenshot is not narrowed because the blackout is shifted
           cy.screenshot(`${ssPrefix}recent-changes-2-switch-content-size`, {
           cy.screenshot(`${ssPrefix}recent-changes-2-switch-content-size`, {
-            // Blackout for recalculation of toc content hight
-            blackout: ['.grw-side-contents-container', '[data-hide-in-vrt=true]'],
+            blackout: blackoutOverride,
           });
           });
         });
         });
       });
       });

+ 10 - 4
packages/app/test/cypress/integration/50-sidebar/50-sidebar--switching-sidebar-mode.spec.ts

@@ -1,3 +1,11 @@
+import { BlackoutGroup } from "../../support/blackout";
+
+// Blackout for recalculation of toc content hight
+const blackoutOverride = [
+  ...BlackoutGroup.BASIS,
+  ...BlackoutGroup.SIDE_CONTENTS,
+];
+
 context('Switch sidebar mode', () => {
 context('Switch sidebar mode', () => {
   const ssPrefix = 'switch-sidebar-mode-';
   const ssPrefix = 'switch-sidebar-mode-';
 
 
@@ -28,15 +36,13 @@ context('Switch sidebar mode', () => {
     cy.get('[for="swSidebarMode"]').click({force: true});
     cy.get('[for="swSidebarMode"]').click({force: true});
     cy.get('.grw-sidebar-nav').should('not.be.visible');
     cy.get('.grw-sidebar-nav').should('not.be.visible');
     cy.screenshot(`${ssPrefix}-switch-sidebar-mode`, {
     cy.screenshot(`${ssPrefix}-switch-sidebar-mode`, {
-      // Blackout for recalculation of toc content hight
-      blackout: ['.grw-side-contents-container', '[data-hide-in-vrt=true]'],
+      blackout: blackoutOverride,
     });
     });
 
 
     cy.get('[for="swSidebarMode"]').click({force: true});
     cy.get('[for="swSidebarMode"]').click({force: true});
     cy.get('.grw-sidebar-nav').should('be.visible');
     cy.get('.grw-sidebar-nav').should('be.visible');
     cy.screenshot(`${ssPrefix}-switch-sidebar-mode-back`, {
     cy.screenshot(`${ssPrefix}-switch-sidebar-mode-back`, {
-      // Blackout for recalculation of toc content hight
-      blackout: ['.grw-side-contents-container','[data-hide-in-vrt=true]'],
+      blackout: blackoutOverride,
     });
     });
   });
   });
 
 

+ 14 - 0
packages/app/test/cypress/support/blackout.ts

@@ -0,0 +1,14 @@
+export const BlackoutGroup = {
+  BASIS: [
+    '[data-vrt-blackout=true]',
+    '[data-vrt-blackout-hash=true]',
+    '[data-vrt-blackout-profile=true]',
+    '[data-vrt-blackout-datetime=true]',
+  ],
+  SIDEBAR_NAV: [
+    '[data-vrt-blackout-sidebar-nav=true]',
+  ],
+  SIDE_CONTENTS: [
+    '[data-vrt-blackout-side-contents=true]',
+  ],
+} as const;

+ 6 - 1
packages/app/test/cypress/support/screenshot.ts

@@ -1,4 +1,9 @@
+import { BlackoutGroup } from "./blackout";
+
 Cypress.Screenshot.defaults({
 Cypress.Screenshot.defaults({
-  blackout: ['[data-hide-in-vrt=true]'],
+  blackout: [
+    ...BlackoutGroup.BASIS,
+    ...BlackoutGroup.SIDEBAR_NAV,
+  ],
   capture: 'viewport',
   capture: 'viewport',
 })
 })