Yuki Takei пре 3 година
родитељ
комит
b0e2445e33

+ 21 - 48
packages/app/src/components/IdenticalPathPage.tsx

@@ -3,11 +3,9 @@ import React, { FC } from 'react';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
-import { useCurrentPathname, useIsSharedUser } from '~/stores/context';
-import { useDescendantsPageListModal } from '~/stores/modal';
+import { useCurrentPathname } from '~/stores/context';
 import { useSWRxPageInfoForList, useSWRxPagesByPath } from '~/stores/page-listing';
 import { useSWRxPageInfoForList, useSWRxPagesByPath } from '~/stores/page-listing';
 
 
-import PageListIcon from './Icons/PageListIcon';
 import { PageListItemL } from './PageList/PageListItemL';
 import { PageListItemL } from './PageList/PageListItemL';
 
 
 
 
@@ -50,16 +48,12 @@ const IdenticalPathAlert : FC<IdenticalPathAlertProps> = (props: IdenticalPathAl
 
 
 
 
 export const IdenticalPathPage = (): JSX.Element => {
 export const IdenticalPathPage = (): JSX.Element => {
-  const { t } = useTranslation();
 
 
   const { data: currentPath } = useCurrentPathname();
   const { data: currentPath } = useCurrentPathname();
-  const { data: isSharedUser } = useIsSharedUser();
 
 
   const { data: pages } = useSWRxPagesByPath(currentPath);
   const { data: pages } = useSWRxPagesByPath(currentPath);
   const { injectTo } = useSWRxPageInfoForList(null, currentPath, true, true);
   const { injectTo } = useSWRxPageInfoForList(null, currentPath, true, true);
 
 
-  const { open: openDescendantPageListModal } = useDescendantsPageListModal();
-
   if (pages == null) {
   if (pages == null) {
     return <></>;
     return <></>;
   }
   }
@@ -67,48 +61,27 @@ export const IdenticalPathPage = (): JSX.Element => {
   const injectedPages = injectTo(pages);
   const injectedPages = injectTo(pages);
 
 
   return (
   return (
-    <div className="d-flex flex-column flex-lg-row-reverse">
-
-      <div className="grw-side-contents-container">
-        <div className={`pb-1 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-          { currentPath != null && !isSharedUser && (
-            <button
-              type="button"
-              className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between"
-              onClick={() => openDescendantPageListModal(currentPath)}
-            >
-              <PageListIcon />
-              {t('page_list')}
-              <span></span> {/* for a count badge */}
-            </button>
-          ) }
-        </div>
+    <>
+      <IdenticalPathAlert path={currentPath} />
+
+      <div className={`page-list ${styles['page-list']}`}>
+        <ul className="page-list-ul list-group list-group-flush">
+          {injectedPages.map((pageWithMeta) => {
+            const pageId = pageWithMeta.data._id;
+
+            return (
+              <PageListItemL
+                key={pageId}
+                page={pageWithMeta}
+                isSelected={false}
+                isEnableActions
+                showPageUpdatedTime
+              />
+            );
+          })}
+        </ul>
       </div>
       </div>
 
 
-      <div className="flex-grow-1 flex-basis-0 mw-0">
-
-        <IdenticalPathAlert path={currentPath} />
-
-        <div className={`page-list ${styles['page-list']}`}>
-          <ul className="page-list-ul list-group list-group-flush">
-            {injectedPages.map((pageWithMeta) => {
-              const pageId = pageWithMeta.data._id;
-
-              return (
-                <PageListItemL
-                  key={pageId}
-                  page={pageWithMeta}
-                  isSelected={false}
-                  isEnableActions
-                  showPageUpdatedTime
-                />
-              );
-            })}
-          </ul>
-        </div>
-
-      </div>
-
-    </div>
+    </>
   );
   );
 };
 };

+ 49 - 0
packages/app/src/components/Layout/MainPane.tsx

@@ -0,0 +1,49 @@
+import { ReactNode } from 'react';
+
+
+type Props = {
+  className?: string,
+  children?: ReactNode,
+  sideContents?: ReactNode,
+  footerContents?: ReactNode,
+}
+
+export const MainPane = (props: Props): JSX.Element => {
+  const {
+    className, children, sideContents, footerContents,
+  } = props;
+
+  return (
+    <>
+      <div className="flex-grow-1">
+        <div id="main" className={`main ${className}`}>
+          <div id="content-main" className="content-main grw-container-convertible">
+            { sideContents != null
+              ? (
+                <div className="d-flex flex-column flex-lg-row">
+                  <div className="flex-grow-1 flex-basis-0 mw-0">
+                    {children}
+                  </div>
+                  <div className="grw-side-contents-container">
+                    <div className="grw-side-contents-sticky-container">
+                      {sideContents}
+                    </div>
+                  </div>
+                </div>
+              )
+              : (
+                <>{children}</>
+              )
+            }
+          </div>
+        </div>
+      </div>
+
+      { footerContents != null && (
+        <footer className="footer d-edit-none">
+          {footerContents}
+        </footer>
+      ) }
+    </>
+  );
+};

+ 2 - 6
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -322,7 +322,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   }, []);
   }, []);
 
 
 
 
-  const RightComponent = useCallback(() => {
+  const RightComponent = () => {
     const additionalMenuItemsRenderer = () => {
     const additionalMenuItemsRenderer = () => {
       if (revisionId == null || pageId == null) {
       if (revisionId == null || pageId == null) {
         return (
         return (
@@ -407,11 +407,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
         )}
         )}
       </>
       </>
     );
     );
-  // eslint-disable-next-line max-len
-  }, [isCompactMode, isViewMode, pageId, revisionId, shareLinkId, path, currentPathname, isSharedUser, isAbleToShowPageManagement,
-      duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler, isAbleToShowPageEditorModeManager, isGuestUser,
-      editorMode, isAbleToShowPageAuthors, currentPage, currentUser, isPageTemplateModalShown, isLinkSharingDisabled, templateMenuItemClickHandler,
-      mutateEditorMode, switchContentWidthHandler]);
+  };
 
 
 
 
   const pagePath = isIdenticalPath || isNotFound
   const pagePath = isIdenticalPath || isNotFound

+ 7 - 73
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -1,17 +1,13 @@
 import React, { useCallback, useEffect, useMemo } from 'react';
 import React, { useCallback, useEffect, useMemo } from 'react';
 
 
 import { pagePathUtils } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
-import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
-import { Link } from 'react-scroll';
 
 
-import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { SocketEventName } from '~/interfaces/websocket';
 import { SocketEventName } from '~/interfaces/websocket';
 import {
 import {
-  useIsSharedUser, useIsEditable, useShareLinkId, useIsNotFound,
+  useIsEditable, useShareLinkId, useIsNotFound,
 } from '~/stores/context';
 } from '~/stores/context';
 import { useIsHackmdDraftUpdatingInRealtime } from '~/stores/hackmd';
 import { useIsHackmdDraftUpdatingInRealtime } from '~/stores/hackmd';
-import { useDescendantsPageListModal } from '~/stores/modal';
 import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
 import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
 import {
 import {
   useSetRemoteLatestPageData,
   useSetRemoteLatestPageData,
@@ -19,42 +15,30 @@ import {
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import { useGlobalSocket } from '~/stores/websocket';
 import { useGlobalSocket } from '~/stores/websocket';
 
 
-import CountBadge from '../Common/CountBadge';
-import { ContentLinkButtonsProps } from '../ContentLinkButtons';
 import CustomTabContent from '../CustomNavigation/CustomTabContent';
 import CustomTabContent from '../CustomNavigation/CustomTabContent';
-import PageListIcon from '../Icons/PageListIcon';
 import { Page } from '../Page';
 import { Page } from '../Page';
-import TableOfContents from '../TableOfContents';
 import { UserInfoProps } from '../User/UserInfo';
 import { UserInfoProps } from '../User/UserInfo';
 
 
-import styles from './DisplaySwitcher.module.scss';
-
-const { isTopPage, isUsersHomePage } = pagePathUtils;
+const { isUsersHomePage } = pagePathUtils;
 
 
 
 
 const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
 const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
 const PageEditorByHackmd = dynamic(() => import('../PageEditorByHackmd').then(mod => mod.PageEditorByHackmd), { ssr: false });
 const PageEditorByHackmd = dynamic(() => import('../PageEditorByHackmd').then(mod => mod.PageEditorByHackmd), { ssr: false });
 const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
 const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
 const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
 const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
-const ContentLinkButtons = dynamic<ContentLinkButtonsProps>(() => import('../ContentLinkButtons').then(mod => mod.ContentLinkButtons), { ssr: false });
 const NotFoundPage = dynamic(() => import('../NotFoundPage'), { ssr: false });
 const NotFoundPage = dynamic(() => import('../NotFoundPage'), { ssr: false });
 const UserInfo = dynamic<UserInfoProps>(() => import('../User/UserInfo').then(mod => mod.UserInfo), { ssr: false });
 const UserInfo = dynamic<UserInfoProps>(() => import('../User/UserInfo').then(mod => mod.UserInfo), { ssr: false });
 
 
 
 
 const PageView = React.memo((): JSX.Element => {
 const PageView = React.memo((): JSX.Element => {
-  const { t } = useTranslation();
-
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
-  const { data: isSharedUser } = useIsSharedUser();
   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(shareLinkId ?? undefined);
-  const { open: openDescendantPageListModal } = useDescendantsPageListModal();
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
 
 
   const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
   const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
 
 
-  const isTopPagePath = isTopPage(currentPagePath ?? '');
   const isUsersHomePagePath = isUsersHomePage(currentPagePath ?? '');
   const isUsersHomePagePath = isUsersHomePage(currentPagePath ?? '');
 
 
   const { data: socket } = useGlobalSocket();
   const { data: socket } = useGlobalSocket();
@@ -106,61 +90,11 @@ const PageView = React.memo((): JSX.Element => {
   }, [setIsHackmdDraftUpdatingInRealtime, socket]);
   }, [setIsHackmdDraftUpdatingInRealtime, socket]);
 
 
   return (
   return (
-    <div className="d-flex flex-column flex-lg-row">
-
-      <div className="flex-grow-1 flex-basis-0 mw-0">
-        { isUsersHomePagePath && <UserInfo author={currentPage?.creator} /> }
-        { !isNotFound && <Page /> }
-        { isNotFound && <NotFoundPage /> }
-      </div>
-
-      { !isNotFound && (
-        <div className="grw-side-contents-container">
-          <div className="grw-side-contents-sticky-container">
-
-            {/* Page list */}
-            <div className={`grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-              { currentPagePath != null && !isSharedUser && (
-                <button
-                  type="button"
-                  className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
-                  onClick={() => openDescendantPageListModal(currentPagePath)}
-                  data-testid="pageListButton"
-                >
-                  <div className="grw-page-accessories-control-icon">
-                    <PageListIcon />
-                  </div>
-                  {t('page_list')}
-                  <CountBadge count={currentPage?.descendantCount} offset={1} />
-                </button>
-              ) }
-            </div>
-
-            {/* Comments */}
-            { !isTopPagePath && (
-              <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-                <Link to={'page-comments'} offset={-100} {...DEFAULT_AUTO_SCROLL_OPTS}>
-                  <button
-                    type="button"
-                    className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
-                  >
-                    <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
-                    <span>Comments</span>
-                    <CountBadge count={currentPage?.commentCount} />
-                  </button>
-                </Link>
-              </div>
-            ) }
-
-            <div className="d-none d-lg-block">
-              <TableOfContents />
-              { isUsersHomePagePath && <ContentLinkButtons author={currentPage?.creator} /> }
-            </div>
-
-          </div>
-        </div>
-      ) }
-    </div>
+    <>
+      { isUsersHomePagePath && <UserInfo author={currentPage?.creator} /> }
+      { !isNotFound && <Page /> }
+      { isNotFound && <NotFoundPage /> }
+    </>
   );
   );
 });
 });
 PageView.displayName = 'PageView';
 PageView.displayName = 'PageView';

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

@@ -39,7 +39,7 @@ const ShareLinkAlert: FC<Props> = (props: Props) => {
   const alertColor = getAlertColor(ratio);
   const alertColor = getAlertColor(ratio);
 
 
   return (
   return (
-    <p className={`alert alert-${alertColor} my-3 px-4 d-edit-none`}>
+    <p className={`alert alert-${alertColor} px-4 d-edit-none`}>
       <i className="icon-fw icon-link"></i>
       <i className="icon-fw icon-link"></i>
       {(expiredAt === null ? <span>{t('page_page.notice.no_deadline')}</span>
       {(expiredAt === null ? <span>{t('page_page.notice.no_deadline')}</span>
       // eslint-disable-next-line react/no-danger
       // eslint-disable-next-line react/no-danger

+ 0 - 12
packages/app/src/components/PageContentFooter.tsx

@@ -3,8 +3,6 @@ import React from 'react';
 import type { IPage, IUser } from '@growi/core';
 import type { IPage, IUser } from '@growi/core';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 
 
-import { useSWRxCurrentPage } from '~/stores/page';
-
 import type { AuthorInfoProps } from './Navbar/AuthorInfo';
 import type { AuthorInfoProps } from './Navbar/AuthorInfo';
 
 
 import styles from './PageContentFooter.module.scss';
 import styles from './PageContentFooter.module.scss';
@@ -34,13 +32,3 @@ export const PageContentFooter = (props: PageContentFooterProps): JSX.Element =>
     </div>
     </div>
   );
   );
 };
 };
-
-export const CurrentPageContentFooter = (): JSX.Element => {
-  const { data: currentPage } = useSWRxCurrentPage();
-
-  if (currentPage == null) {
-    return <></>;
-  }
-
-  return <PageContentFooter page={currentPage} />;
-};

+ 0 - 0
packages/app/src/components/Page/DisplaySwitcher.module.scss → packages/app/src/components/PageSideContents.module.scss


+ 81 - 0
packages/app/src/components/PageSideContents.tsx

@@ -0,0 +1,81 @@
+import React from 'react';
+
+import { IPageHasId, pagePathUtils } from '@growi/core';
+import { useTranslation } from 'next-i18next';
+import { Link } from 'react-scroll';
+
+import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
+import { useCurrentPathname } from '~/stores/context';
+import { useDescendantsPageListModal } from '~/stores/modal';
+
+import CountBadge from './Common/CountBadge';
+import { ContentLinkButtons } from './ContentLinkButtons';
+import PageListIcon from './Icons/PageListIcon';
+import TableOfContents from './TableOfContents';
+
+import styles from './PageSideContents.module.scss';
+
+
+const { isTopPage, isUsersHomePage } = pagePathUtils;
+
+
+type Props = {
+  page?: IPageHasId,
+  isSharedUser?: boolean,
+}
+
+export const PageSideContents = (props: Props): JSX.Element => {
+  const { t } = useTranslation();
+
+  const { data: currentPathname } = useCurrentPathname();
+  const { open: openDescendantPageListModal } = useDescendantsPageListModal();
+
+  const { page, isSharedUser } = props;
+
+  const pagePath = page?.path ?? currentPathname;
+  const isTopPagePath = isTopPage(pagePath ?? '');
+  const isUsersHomePagePath = isUsersHomePage(pagePath ?? '');
+
+  return (
+    <>
+      {/* Page list */}
+      <div className={`grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
+        { pagePath != null && !isSharedUser && (
+          <button
+            type="button"
+            className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
+            onClick={() => openDescendantPageListModal(pagePath)}
+            data-testid="pageListButton"
+          >
+            <div className="grw-page-accessories-control-icon">
+              <PageListIcon />
+            </div>
+            {t('page_list')}
+            <CountBadge count={page?.descendantCount} offset={1} />
+          </button>
+        ) }
+      </div>
+
+      {/* Comments */}
+      { page != null && !isTopPagePath && (
+        <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
+          <Link to={'page-comments'} offset={-100} {...DEFAULT_AUTO_SCROLL_OPTS}>
+            <button
+              type="button"
+              className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
+            >
+              <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
+              <span>Comments</span>
+              <CountBadge count={page.commentCount} />
+            </button>
+          </Link>
+        </div>
+      ) }
+
+      <div className="d-none d-lg-block">
+        <TableOfContents />
+        { isUsersHomePagePath && <ContentLinkButtons author={page?.creator} /> }
+      </div>
+    </>
+  );
+};

+ 2 - 3
packages/app/src/components/TableOfContents.tsx

@@ -1,13 +1,14 @@
 import React, { useCallback } from 'react';
 import React, { useCallback } from 'react';
 
 
 import { pagePathUtils } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
-import dynamic from 'next/dynamic';
 import ReactMarkdown from 'react-markdown';
 import ReactMarkdown from 'react-markdown';
 
 
 import { useCurrentPagePath } from '~/stores/page';
 import { useCurrentPagePath } from '~/stores/page';
 import { useTocOptions } from '~/stores/renderer';
 import { useTocOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import { StickyStretchableScroller } from './StickyStretchableScroller';
+
 import styles from './TableOfContents.module.scss';
 import styles from './TableOfContents.module.scss';
 
 
 const { isUserPage: _isUserPage } = pagePathUtils;
 const { isUserPage: _isUserPage } = pagePathUtils;
@@ -16,8 +17,6 @@ const { isUserPage: _isUserPage } = pagePathUtils;
 const logger = loggerFactory('growi:TableOfContents');
 const logger = loggerFactory('growi:TableOfContents');
 
 
 const TableOfContents = (): JSX.Element => {
 const TableOfContents = (): JSX.Element => {
-  const StickyStretchableScroller = dynamic(() => import('./StickyStretchableScroller').then(mod => mod.StickyStretchableScroller), { ssr: false });
-
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
 
 
   const isUserPage = currentPagePath != null && _isUserPage(currentPagePath);
   const isUserPage = currentPagePath != null && _isUserPage(currentPagePath);

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

@@ -21,9 +21,10 @@ import superjson from 'superjson';
 
 
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { Comments } from '~/components/Comments';
 import { Comments } from '~/components/Comments';
+import { MainPane } from '~/components/Layout/MainPane';
 import { PageAlerts } from '~/components/PageAlert/PageAlerts';
 import { PageAlerts } from '~/components/PageAlert/PageAlerts';
 // import { useTranslation } from '~/i18n';
 // import { useTranslation } from '~/i18n';
-import { CurrentPageContentFooter } from '~/components/PageContentFooter';
+import { PageContentFooter } from '~/components/PageContentFooter';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { UsersHomePageFooterProps } from '~/components/UsersHomePageFooter';
 import { UsersHomePageFooterProps } from '~/components/UsersHomePageFooter';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
@@ -69,7 +70,7 @@ import {
   useIsAclEnabled, useIsSearchPage, useTemplateTagData, useTemplateBodyData, useIsEnabledAttachTitleHeader,
   useIsAclEnabled, useIsSearchPage, useTemplateTagData, useTemplateBodyData, useIsEnabledAttachTitleHeader,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useIsSlackConfigured, useRendererConfig,
   useIsSlackConfigured, useRendererConfig,
-  useEditorConfig, useIsAllReplyShown, useIsUploadableFile, useIsUploadableImage, useCustomizedLogoSrc, useIsContainerFluid,
+  useEditorConfig, useIsAllReplyShown, useIsUploadableFile, useIsUploadableImage, useCustomizedLogoSrc, useIsContainerFluid, useIsNotCreatable,
 } from '../stores/context';
 } from '../stores/context';
 
 
 import { NextPageWithLayout } from './_app.page';
 import { NextPageWithLayout } from './_app.page';
@@ -87,6 +88,7 @@ declare global {
 const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
 const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
 const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
 const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
+const PageSideContents = dynamic(() => import('../components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
 const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
 const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
 const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../components/UsersHomePageFooter')
 const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../components/UsersHomePageFooter')
   .then(mod => mod.UsersHomePageFooter), { ssr: false });
   .then(mod => mod.UsersHomePageFooter), { ssr: false });
@@ -203,7 +205,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   // commons
   // commons
   useEditorConfig(props.editorConfig);
   useEditorConfig(props.editorConfig);
   useCsrfToken(props.csrfToken);
   useCsrfToken(props.csrfToken);
-  useCustomizedLogoSrc(props.customizedLogoSrc);
 
 
   // UserUISettings
   // UserUISettings
   usePreferDrawerModeByUser(props.userUISettings?.preferDrawerModeByUser ?? props.sidebarConfig.isSidebarDrawerMode);
   usePreferDrawerModeByUser(props.userUISettings?.preferDrawerModeByUser ?? props.sidebarConfig.isSidebarDrawerMode);
@@ -261,7 +262,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useRemoteRevisionId(pageWithMeta?.data.revision?._id);
   useRemoteRevisionId(pageWithMeta?.data.revision?._id);
   usePageIdOnHackmd(pageWithMeta?.data.pageIdOnHackmd);
   usePageIdOnHackmd(pageWithMeta?.data.pageIdOnHackmd);
   useHasDraftOnHackmd(pageWithMeta?.data.hasDraftOnHackmd ?? false);
   useHasDraftOnHackmd(pageWithMeta?.data.hasDraftOnHackmd ?? false);
-  // useIsNotCreatable(props.isForbidden || !isCreatablePage(pagePath)); // TODO: need to include props.isIdentical
   useCurrentPathname(props.currentPathname);
   useCurrentPathname(props.currentPathname);
 
 
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
@@ -298,6 +298,27 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
 
   const title = generateCustomTitle(props, 'GROWI');
   const title = generateCustomTitle(props, 'GROWI');
 
 
+
+  const sideContents = !props.isNotFound && !props.isNotCreatable
+    ? (
+      <PageSideContents page={pageWithMeta?.data} />
+    )
+    : <></>;
+
+  const footerContents = !props.isIdenticalPathPage && !props.isNotFound && pageWithMeta != null
+    ? (
+      <>
+        { pagePath != null && !isTopPagePath && (
+          <Comments pageId={pageId} pagePath={pagePath} revision={pageWithMeta.data.revision} />
+        ) }
+        { isUsersHomePage(pageWithMeta.data.path) && (
+          <UsersHomePageFooter creatorId={pageWithMeta.data.creator._id}/>
+        ) }
+        <PageContentFooter page={pageWithMeta.data} />
+      </>
+    )
+    : <></>;
+
   return (
   return (
     <>
     <>
       <Head>
       <Head>
@@ -316,34 +337,21 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
         <div id="grw-subnav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-subnav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 
 
-        <div className="flex-grow-1">
-          <div id="main" className={`main ${isUsersHomePage(props.currentPathname) && 'user-page'}`}>
-            <div id="content-main" className="content-main grw-container-convertible">
-              { props.isIdenticalPathPage && <IdenticalPathPage /> }
-
-              { !props.isIdenticalPathPage && (
-                <>
-                  <PageAlerts />
-                  { props.isForbidden && <ForbiddenPage /> }
-                  { props.isNotCreatablePage && <NotCreatablePage />}
-                  { !props.isForbidden && !props.isNotCreatablePage && <DisplaySwitcher />}
-                  <PageStatusAlert />
-                </>
-              ) }
-            </div>
-          </div>
-        </div>
-        { !props.isIdenticalPathPage && !props.isNotFound && (
-          <footer className="footer d-edit-none">
-            { pageWithMeta != null && pagePath != null && !isTopPagePath && (
-              <Comments pageId={pageId} pagePath={pagePath} revision={pageWithMeta.data.revision} />
-            ) }
-            { pageWithMeta != null && isUsersHomePage(pageWithMeta.data.path) && (
-              <UsersHomePageFooter creatorId={pageWithMeta.data.creator._id}/>
-            ) }
-            <CurrentPageContentFooter />
-          </footer>
-        )}
+        <MainPane
+          sideContents={sideContents}
+          footerContents={footerContents}
+        >
+          <PageAlerts />
+          { props.isIdenticalPathPage && <IdenticalPathPage />}
+          { !props.isIdenticalPathPage && (
+            <>
+              { props.isForbidden && <ForbiddenPage /> }
+              { props.isNotCreatable && <NotCreatablePage />}
+              { !props.isForbidden && !props.isNotCreatable && <DisplaySwitcher />}
+            </>
+          ) }
+          <PageStatusAlert />
+        </MainPane>
 
 
         {shouldRenderPutbackPageModal && <PutbackPageModal />}
         {shouldRenderPutbackPageModal && <PutbackPageModal />}
       </div>
       </div>

+ 55 - 77
packages/app/src/pages/share/[[...path]].page.tsx

@@ -2,22 +2,18 @@ import React from 'react';
 
 
 import { IUserHasId } from '@growi/core';
 import { IUserHasId } from '@growi/core';
 import {
 import {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 } from 'next';
-import { useTranslation } from 'next-i18next';
 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 { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
-import CountBadge from '~/components/Common/CountBadge';
-import PageListIcon from '~/components/Icons/PageListIcon';
+import { MainPane } from '~/components/Layout/MainPane';
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
 import GrowiContextualSubNavigation from '~/components/Navbar/GrowiContextualSubNavigation';
 import GrowiContextualSubNavigation from '~/components/Navbar/GrowiContextualSubNavigation';
 import { Page } from '~/components/Page';
 import { Page } from '~/components/Page';
-import styles from '~/components/Page/DisplaySwitcher.module.scss'; // for PageList toc style
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
-import TableOfContents from '~/components/TableOfContents';
 import { SupportedAction, SupportedActionType } from '~/interfaces/activity';
 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';
@@ -26,7 +22,6 @@ import {
   useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
 } from '~/stores/context';
 } from '~/stores/context';
-import { useDescendantsPageListModal } from '~/stores/modal';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { NextPageWithLayout } from '../_app.page';
 import { NextPageWithLayout } from '../_app.page';
@@ -36,6 +31,8 @@ import {
 
 
 const logger = loggerFactory('growi:next-page:share');
 const logger = loggerFactory('growi:next-page:share');
 
 
+const PageSideContents = dynamic(() => import('~/components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
+// const Comments = dynamic(() => import('~/components/Comments').then(mod => mod.Comments), { ssr: false });
 const ShareLinkAlert = dynamic(() => import('~/components/Page/ShareLinkAlert'), { ssr: false });
 const ShareLinkAlert = dynamic(() => import('~/components/Page/ShareLinkAlert'), { ssr: false });
 const ForbiddenPage = dynamic(() => import('~/components/ForbiddenPage'), { ssr: false });
 const ForbiddenPage = dynamic(() => import('~/components/ForbiddenPage'), { ssr: false });
 
 
@@ -63,8 +60,6 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useDrawioUri(props.drawioUri);
   useDrawioUri(props.drawioUri);
   useIsContainerFluid(props.isContainerFluid);
   useIsContainerFluid(props.isContainerFluid);
 
 
-  const { open: openDescendantPageListModal } = useDescendantsPageListModal();
-  const { t } = useTranslation();
 
 
   const growiLayoutFluidClass = useCurrentGrowiLayoutFluidClassName();
   const growiLayoutFluidClass = useCurrentGrowiLayoutFluidClassName();
 
 
@@ -74,6 +69,19 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
 
   const title = generateCustomTitle(props, 'GROWI');
   const title = generateCustomTitle(props, 'GROWI');
 
 
+
+  const sideContents = shareLink != null
+    ? <PageSideContents page={shareLink.relatedPage} />
+    : <></>;
+
+  // const footerContents = shareLink != null && isPopulated(shareLink.relatedPage.revision)
+  //   ? (
+  //     <>
+  //       <Comments pageId={shareLink._id} pagePath={shareLink.relatedPage.path} revision={shareLink.relatedPage.revision} />
+  //     </>
+  //   )
+  //   : <></>;
+
   return (
   return (
     <>
     <>
       <Head>
       <Head>
@@ -87,74 +95,43 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
 
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 
 
-        <div className="flex-grow-1">
-          <div id="content-main" className="content-main grw-container-convertible">
-            { props.disableLinkSharing && (
-              <div className="mt-4">
-                <ForbiddenPage isLinkSharingDisabled={props.disableLinkSharing} />
-              </div>
-            )}
-
-            { (isNotFound && !props.disableLinkSharing) && (
-              <div className="container-lg">
-                <h2 className="text-muted mt-4">
-                  <i className="icon-ban" aria-hidden="true" />
-                  <span> Page is not found</span>
-                </h2>
-              </div>
-            )}
-
-            { (props.isExpired && !props.disableLinkSharing && shareLink != null) && (
-              <div className="container-lg">
-                <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
-                <h2 className="text-muted mt-4">
-                  <i className="icon-ban" aria-hidden="true" />
-                  <span> Page is expired</span>
-                </h2>
-              </div>
-            )}
-
-            {(isShowSharedPage && shareLink != null) && (
-              <>
-                <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
-                <div className="d-flex flex-column flex-lg-row-reverse">
-
-                  <div className="grw-side-contents-container">
-                    <div className="grw-side-contents-sticky-container">
-
-                      {/* Page list */}
-                      <div className={`grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-                        { shareLink.relatedPage.path != null && (
-                          <button
-                            type="button"
-                            className="btn btn-block btn-outline-secondary grw-btn-page-accessories
-                            rounded-pill d-flex justify-content-between align-items-center"
-                            onClick={() => openDescendantPageListModal(shareLink.relatedPage.path)}
-                            data-testid="pageListButton"
-                          >
-                            <div className="grw-page-accessories-control-icon">
-                              <PageListIcon />
-                            </div>
-                            {t('page_list')}
-                            <CountBadge count={shareLink.relatedPage.descendantCount} offset={1} />
-                          </button>
-                        ) }
-                      </div>
-
-                      <div className="d-none d-lg-block">
-                        <TableOfContents />
-                      </div>
-                    </div>
-                  </div>
-
-                  <div className="flex-grow-1 flex-basis-0 mw-0">
-                    <Page />
-                  </div>
-                </div>
-              </>
-            )}
-          </div>
-        </div>
+        <MainPane
+          sideContents={sideContents}
+          // footerContents={footerContents}
+        >
+          { props.disableLinkSharing && (
+            <div className="mt-4">
+              <ForbiddenPage isLinkSharingDisabled={props.disableLinkSharing} />
+            </div>
+          )}
+
+          { (isNotFound && !props.disableLinkSharing) && (
+            <div className="container-lg">
+              <h2 className="text-muted mt-4">
+                <i className="icon-ban" aria-hidden="true" />
+                <span> Page is not found</span>
+              </h2>
+            </div>
+          )}
+
+          { (props.isExpired && !props.disableLinkSharing && shareLink != null) && (
+            <div className="container-lg">
+              <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
+              <h2 className="text-muted mt-4">
+                <i className="icon-ban" aria-hidden="true" />
+                <span> Page is expired</span>
+              </h2>
+            </div>
+          )}
+
+          {(isShowSharedPage && shareLink != null) && (
+            <>
+              <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
+              <Page />
+            </>
+          )}
+        </MainPane>
+
       </div>
       </div>
     </>
     </>
   );
   );
@@ -249,6 +226,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.isExpired = shareLink.isExpired();
       props.isExpired = shareLink.isExpired();
       props.shareLink = shareLink.toObject();
       props.shareLink = shareLink.toObject();
     }
     }