Przeglądaj źródła

Merge remote-tracking branch 'origin/dev/7.0.x' into imprv/container-padding

Yuki Takei 2 lat temu
rodzic
commit
98b599a627
38 zmienionych plików z 342 dodań i 256 usunięć
  1. 1 1
      apps/app/public/static/locales/en_US/translation.json
  2. 1 1
      apps/app/public/static/locales/ja_JP/commons.json
  3. 1 1
      apps/app/public/static/locales/ja_JP/translation.json
  4. 1 1
      apps/app/public/static/locales/zh_CN/translation.json
  5. 20 11
      apps/app/src/client/services/layout.ts
  6. 18 21
      apps/app/src/components/Comments.tsx
  7. 8 7
      apps/app/src/components/Common/PageViewLayout.module.scss
  8. 10 5
      apps/app/src/components/Common/PageViewLayout.tsx
  9. 0 76
      apps/app/src/components/CreateTemplateModal.jsx
  10. 91 0
      apps/app/src/components/CreateTemplateModal.tsx
  11. 6 6
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  12. 47 2
      apps/app/src/components/Navbar/hooks.tsx
  13. 8 2
      apps/app/src/components/Page/PageView.tsx
  14. 49 55
      apps/app/src/components/PageComment.tsx
  15. 9 0
      apps/app/src/components/PageControls/PageControls.tsx
  16. 4 4
      apps/app/src/components/PageCreateModal.jsx
  17. 5 1
      apps/app/src/components/PageEditor/PageEditor.tsx
  18. 9 20
      apps/app/src/components/PageEditor/Preview.module.scss
  19. 6 2
      apps/app/src/components/PageEditor/Preview.tsx
  20. 8 0
      apps/app/src/components/SearchPage/SearchResultContent.module.scss
  21. 13 13
      apps/app/src/components/SearchPage/SearchResultContent.tsx
  22. 4 0
      apps/app/src/components/ShareLinkPageView.tsx
  23. 1 1
      apps/app/src/components/Sidebar/PageCreateButton/DropendMenu.tsx
  24. 0 2
      apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx
  25. 1 1
      apps/app/src/components/Sidebar/Sidebar.tsx
  26. 1 1
      apps/app/src/components/Sidebar/Tag.tsx
  27. 2 0
      apps/app/src/interfaces/template.ts
  28. 3 5
      apps/app/src/pages/[[...path]].page.tsx
  29. 1 1
      apps/app/src/pages/me/[[...path]].page.tsx
  30. 2 5
      apps/app/src/pages/share/[[...path]].page.tsx
  31. 1 1
      apps/app/src/pages/tags.page.tsx
  32. 1 1
      apps/app/src/pages/trash.page.tsx
  33. 0 5
      apps/app/src/styles/_layout.scss
  34. 1 0
      apps/app/src/styles/_mixins.scss
  35. 4 0
      apps/app/src/styles/mixins/_fluid-layout.scss
  36. 1 1
      apps/app/test/cypress/e2e/20-basic-features/20-basic-features--access-to-page.cy.ts
  37. 3 1
      apps/app/test/cypress/e2e/50-sidebar/50-sidebar--access-to-side-bar.cy.ts
  38. 1 2
      packages/core/scss/_flex-expand.scss

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

@@ -464,7 +464,7 @@
       "label": "Template for children",
       "desc": "Applies only to the same level pages which the template exists"
     },
-    "decendants": {
+    "descendants": {
       "label": "Template for descendants",
       "desc": "Applies to all decendant pages"
     }

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

@@ -79,7 +79,7 @@
     "template": {
       "desc": "テンプレートページの作成/編集",
       "children": "同一階層テンプレート",
-      "decendants": "下位層テンプレート"
+      "descendants": "下位層テンプレート"
     }
   },
 

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

@@ -497,7 +497,7 @@
       "label": "同一階層テンプレート",
       "desc": "テンプレートページが存在する階層にのみ適用されます"
     },
-    "decendants": {
+    "descendants": {
       "label": "下位層テンプレート",
       "desc": "テンプレートページが存在する下位層のすべてのページに適用されます"
     }

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

@@ -451,7 +451,7 @@
 			"label": "子模板",
 			"desc": "仅应用于模板存在的同一级别页"
 		},
-		"decendants": {
+		"descendants": {
 			"label": "子代模板",
 			"desc": "适用于所有分散页"
 		}

+ 20 - 11
apps/app/src/client/services/layout.ts

@@ -9,20 +9,29 @@ export const useEditorModeClassName = (): string => {
   return `${getClassNamesByEditorMode().join(' ') ?? ''}`;
 };
 
-export const useLayoutFluidClassName = (expandContentWidth?: boolean | null): string => {
+const useDetermineExpandContent = (expandContentWidth?: boolean | null): boolean => {
   const { data: dataIsContainerFluid } = useIsContainerFluid();
 
   const isContainerFluidDefault = dataIsContainerFluid;
-  const isContainerFluid = expandContentWidth ?? isContainerFluidDefault;
-
-  return isContainerFluid ? 'growi-layout-fluid' : '';
+  return expandContentWidth ?? isContainerFluidDefault ?? false;
 };
 
-export const useLayoutFluidClassNameByPage = (initialPage?: IPage): string => {
-  const page = initialPage;
-  const expandContentWidth = page == null || !('expandContentWidth' in page)
-    ? null
-    : page.expandContentWidth;
-
-  return useLayoutFluidClassName(expandContentWidth);
+export const useShouldExpandContent = (data?: IPage | boolean | null): boolean => {
+  const expandContentWidth = (() => {
+    // when data is null
+    if (data == null) {
+      return null;
+    }
+    // when data is boolean
+    if (data === true || data === false) {
+      return data;
+    }
+    // when IPage does not have expandContentWidth
+    if (!('expandContentWidth' in data)) {
+      return null;
+    }
+    return data.expandContentWidth;
+  })();
+
+  return useDetermineExpandContent(expandContentWidth);
 };

+ 18 - 21
apps/app/src/components/Comments.tsx

@@ -55,7 +55,7 @@ export const Comments = (props: CommentsProps): JSX.Element => {
     // see: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe
     // > You can call observe() multiple times on the same MutationObserver
     // > to watch for changes to different parts of the DOM tree and/or different types of changes.
-  }, [onLoaded]);
+  }, [onLoadedDebounced]);
 
   const isTopPagePath = isTopPage(pagePath);
 
@@ -69,29 +69,26 @@ export const Comments = (props: CommentsProps): JSX.Element => {
   };
 
   return (
-    <div className="page-comments-row mt-5 py-4 d-edit-none d-print-none">
-      <div className="container-lg">
-        <div id="page-comments-list" className="page-comments-list" ref={pageCommentParentRef}>
-          <PageComment
+    <div className="page-comments-row mt-5 py-4 border-top border-3 d-edit-none d-print-none">
+      <div id="page-comments-list" className="page-comments-list" ref={pageCommentParentRef}>
+        <PageComment
+          pageId={pageId}
+          pagePath={pagePath}
+          revision={revision}
+          currentUser={currentUser}
+          isReadOnly={false}
+        />
+      </div>
+      {!isDeleted && (
+        <div id="page-comment-write">
+          <CommentEditor
             pageId={pageId}
-            pagePath={pagePath}
-            revision={revision}
-            currentUser={currentUser}
-            isReadOnly={false}
-            titleAlign="left"
+            isForNewComment
+            onCommentButtonClicked={onCommentButtonClickHandler}
+            revisionId={revision._id}
           />
         </div>
-        {!isDeleted && (
-          <div id="page-comment-write">
-            <CommentEditor
-              pageId={pageId}
-              isForNewComment
-              onCommentButtonClicked={onCommentButtonClickHandler}
-              revisionId={revision._id}
-            />
-          </div>
-        )}
-      </div>
+      )}
     </div>
   );
 

+ 8 - 7
apps/app/src/components/Common/PageViewLayout.module.scss

@@ -1,5 +1,6 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+@use '~/styles/mixins';
 @use '~/styles/variables' as var;
 
 
@@ -8,14 +9,7 @@ $page-view-layout-margin-top: 32px;
 
 .page-view-layout :global {
   $page-content-footer-min-heigh: 130px;
-
-  display: flex;
-  flex-direction: column;
   min-height: calc(100vh - #{$subnavigation-height + $page-view-layout-margin-top + $page-content-footer-min-heigh});
-
-  .content-main {
-    flex-grow: 1;
-  }
 }
 
 // md/lg layout padding
@@ -35,6 +29,13 @@ $page-view-layout-margin-top: 32px;
   }
 }
 
+// fluid layout
+.fluid-layout :global {
+  .grw-container-convertible {
+    @include mixins.fluid-layout();
+  }
+}
+
 .page-view-layout :global {
   .grw-side-contents-container {
     margin-bottom: 1rem;

+ 10 - 5
apps/app/src/components/Common/PageViewLayout.tsx

@@ -4,28 +4,33 @@ import styles from './PageViewLayout.module.scss';
 
 const pageViewLayoutClass = styles['page-view-layout'] ?? '';
 const footerLayoutClass = styles['footer-layout'] ?? '';
+const _fluidLayoutClass = styles['fluid-layout'] ?? '';
 
 type Props = {
   children?: ReactNode,
   headerContents?: ReactNode,
   sideContents?: ReactNode,
   footerContents?: ReactNode,
+  expandContentWidth?: boolean,
 }
 
 export const PageViewLayout = (props: Props): JSX.Element => {
   const {
     children, headerContents, sideContents, footerContents,
+    expandContentWidth,
   } = props;
 
+  const fluidLayoutClass = expandContentWidth ? _fluidLayoutClass : '';
+
   return (
     <>
-      <div id="main" className={`main ${pageViewLayoutClass}`}>
-        <div id="content-main" className="content-main container-lg grw-container-convertible">
+      <div id="main" className={`main ${pageViewLayoutClass} ${fluidLayoutClass} flex-expand-vert`}>
+        <div id="content-main" className="content-main container-lg grw-container-convertible flex-expand-vert">
           { headerContents != null && headerContents }
           { sideContents != null
             ? (
-              <div className="d-flex gap-3">
-                <div className="flex-grow-1 flex-basis-0 mw-0">
+              <div className="flex-expand-horiz gap-3">
+                <div className="flex-expand-vert flex-basis-0 mw-0">
                   {children}
                 </div>
                 <div className="grw-side-contents-container col-lg-3  d-edit-none d-print-none" data-vrt-blackout-side-contents>
@@ -43,7 +48,7 @@ export const PageViewLayout = (props: Props): JSX.Element => {
       </div>
 
       { footerContents != null && (
-        <footer className={`footer d-edit-none ${footerLayoutClass}`}>
+        <footer className={`footer d-edit-none ${footerLayoutClass} ${fluidLayoutClass}`}>
           {footerContents}
         </footer>
       ) }

+ 0 - 76
apps/app/src/components/CreateTemplateModal.jsx

@@ -1,76 +0,0 @@
-import React from 'react';
-
-import { pathUtils } from '@growi/core/dist/utils';
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-import { Modal, ModalHeader, ModalBody } from 'reactstrap';
-import urljoin from 'url-join';
-
-const CreateTemplateModal = (props) => {
-  const { t } = useTranslation();
-  const { path } = props;
-
-  const parentPath = pathUtils.addTrailingSlash(path);
-
-  function generateUrl(label) {
-    return encodeURI(urljoin(parentPath, label, '#edit'));
-  }
-
-  /**
-   * @param {string} target Which hierarchy to create [children, decendants]
-   */
-  function renderTemplateCard(target, label) {
-    return (
-      <div className="card card-select-template">
-        <div className="card-header">{ t(`template.${target}.label`) }</div>
-        <div className="card-body">
-          <p className="text-center"><code>{label}</code></p>
-          <p className="form-text text-muted text-center"><small>{t(`template.${target}.desc`) }</small></p>
-        </div>
-        <div className="card-footer text-center">
-          <a
-            data-testid={`template-button-${target}`}
-            href={generateUrl(label)}
-            className="btn btn-sm btn-primary"
-            id={`template-button-${target}`}
-          >
-            { t('Edit') }
-          </a>
-        </div>
-      </div>
-    );
-  }
-
-  return (
-    <Modal isOpen={props.isOpen} toggle={props.onClose} data-testid="page-template-modal">
-      <ModalHeader tag="h4" toggle={props.onClose} className="bg-primary text-light">
-        {t('template.modal_label.Create/Edit Template Page')}
-      </ModalHeader>
-      <ModalBody>
-        <div>
-          <label className="form-label mb-4">
-            <code>{parentPath}</code><br />
-            { t('template.modal_label.Create template under') }
-          </label>
-          <div className="row row-cols-2">
-            <div className="col">
-              {renderTemplateCard('children', '_template')}
-            </div>
-            <div className="col">
-              {renderTemplateCard('decendants', '__template')}
-            </div>
-          </div>
-        </div>
-      </ModalBody>
-    </Modal>
-
-  );
-};
-
-CreateTemplateModal.propTypes = {
-  path: PropTypes.string.isRequired,
-  isOpen: PropTypes.bool.isRequired,
-  onClose: PropTypes.func.isRequired,
-};
-
-export default CreateTemplateModal;

+ 91 - 0
apps/app/src/components/CreateTemplateModal.tsx

@@ -0,0 +1,91 @@
+import React from 'react';
+
+import { pathUtils } from '@growi/core/dist/utils';
+import { useTranslation } from 'next-i18next';
+import { Modal, ModalHeader, ModalBody } from 'reactstrap';
+
+import { TargetType, LabelType } from '~/interfaces/template';
+
+import { useOnTemplateButtonClicked } from './Navbar/hooks';
+
+type TemplateCardProps = {
+  target: TargetType;
+  label: LabelType;
+  isPageCreating: boolean;
+  onClickHandler: () => void;
+};
+
+const TemplateCard: React.FC<TemplateCardProps> = ({
+  target, label, isPageCreating, onClickHandler,
+}) => {
+  const { t } = useTranslation();
+
+  return (
+    <div className="card card-select-template">
+      <div className="card-header">{t(`template.${target}.label`)}</div>
+      <div className="card-body">
+        <p className="text-center"><code>{label}</code></p>
+        <p className="form-text text-muted text-center"><small>{t(`template.${target}.desc`)}</small></p>
+      </div>
+      <div className="card-footer text-center">
+        <button
+          disabled={isPageCreating}
+          data-testid={`template-button-${target}`}
+          className="btn btn-sm btn-primary"
+          id={`template-button-${target}`}
+          onClick={onClickHandler}
+          type="button"
+        >
+          {t('Edit')}
+        </button>
+      </div>
+    </div>
+  );
+};
+
+type CreateTemplateModalProps = {
+  path: string;
+  isOpen: boolean;
+  onClose: () => void;
+};
+
+export const CreateTemplateModal: React.FC<CreateTemplateModalProps> = ({
+  path, isOpen, onClose,
+}) => {
+  const { t } = useTranslation();
+
+  const { onClickHandler: onClickTemplateButton, isPageCreating } = useOnTemplateButtonClicked(path);
+
+  const parentPath = pathUtils.addTrailingSlash(path);
+
+  const renderTemplateCard = (target: TargetType, label: LabelType) => (
+    <div className="col">
+      <TemplateCard
+        target={target}
+        label={label}
+        isPageCreating={isPageCreating}
+        onClickHandler={() => onClickTemplateButton(label)}
+      />
+    </div>
+  );
+
+  return (
+    <Modal isOpen={isOpen} toggle={onClose} data-testid="page-template-modal">
+      <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light">
+        {t('template.modal_label.Create/Edit Template Page')}
+      </ModalHeader>
+      <ModalBody>
+        <div>
+          <label className="form-label mb-4">
+            <code>{parentPath}</code><br />
+            {t('template.modal_label.Create template under')}
+          </label>
+          <div className="row row-cols-2">
+            {renderTemplateCard('children', '_template')}
+            {renderTemplateCard('descendants', '__template')}
+          </div>
+        </div>
+      </ModalBody>
+    </Modal>
+  );
+};

+ 6 - 6
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -11,11 +11,12 @@ import dynamic from 'next/dynamic';
 import { useRouter } from 'next/router';
 import { DropdownItem } from 'reactstrap';
 
+import { useShouldExpandContent } from '~/client/services/layout';
 import { exportAsMarkdown, updateContentWidth } from '~/client/services/page-operation';
 import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
   useCurrentPathname,
-  useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId, useIsContainerFluid,
+  useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId,
 } from '~/stores/context';
 import {
   usePageAccessoriesModal, PageAccessoriesModalContents, type IPageForPageDuplicateModal,
@@ -28,10 +29,9 @@ import { mutatePageTree } from '~/stores/page-listing';
 import {
   useEditorMode, useIsAbleToShowPageManagement,
   useIsAbleToChangeEditorMode,
-  useSelectedGrant,
 } from '~/stores/ui';
 
-import CreateTemplateModal from '../CreateTemplateModal';
+import { CreateTemplateModal } from '../CreateTemplateModal';
 import AttachmentIcon from '../Icons/AttachmentIcon';
 import HistoryIcon from '../Icons/HistoryIcon';
 import PresentationIcon from '../Icons/PresentationIcon';
@@ -196,8 +196,8 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: isSharedUser } = useIsSharedUser();
-  const { data: isContainerFluid } = useIsContainerFluid();
-  const { data: grantData } = useSelectedGrant();
+
+  const shouldExpandContent = useShouldExpandContent(currentPage);
 
   const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
   const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
@@ -309,7 +309,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
             revisionId={revisionId}
             shareLinkId={shareLinkId}
             path={path ?? currentPathname} // If the page is empty, "path" is undefined
-            expandContentWidth={currentPage?.expandContentWidth ?? isContainerFluid}
+            expandContentWidth={shouldExpandContent}
             disableSeenUserInfoPopover={isSharedUser}
             showPageControlDropdown={isAbleToShowPageManagement}
             additionalMenuItemRenderer={additionalMenuItemsRenderer}

+ 47 - 2
apps/app/src/components/Navbar/hooks.tsx

@@ -1,10 +1,11 @@
-import { useCallback } from 'react';
+import { useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 
-import { createPage } from '~/client/services/page-operation';
+import { createPage, exist } from '~/client/services/page-operation';
 import { toastError } from '~/client/util/toastr';
+import { LabelType } from '~/interfaces/template';
 import { useIsNotFound } from '~/stores/page';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
@@ -56,3 +57,47 @@ export const useOnPageEditorModeButtonClicked = (
     mutateEditorMode(editorMode);
   }, [isNotFound, mutateEditorMode, path, router, setIsCreating, t]);
 };
+
+export const useOnTemplateButtonClicked = (
+    currentPagePath: string,
+): {
+  onClickHandler: (label: LabelType) => Promise<void>,
+  isPageCreating: boolean
+} => {
+  const router = useRouter();
+  const [isPageCreating, setIsPageCreating] = useState(false);
+
+  const onClickHandler = useCallback(async(label: LabelType) => {
+    try {
+      setIsPageCreating(true);
+
+      const path = currentPagePath == null || currentPagePath === '/'
+        ? `/${label}`
+        : `${currentPagePath}/${label}`;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: 4,
+        // grant: currentPage?.grant || 1,
+        // grantUserGroupId: currentPage?.grantedGroup?._id,
+      };
+
+      const res = await exist(JSON.stringify([path]));
+      if (!res.pages[path]) {
+        await createPage(path, '', params);
+      }
+
+      router.push(`${path}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsPageCreating(false);
+    }
+  }, [currentPagePath, router]);
+
+  return { onClickHandler, isPageCreating };
+};

+ 8 - 2
apps/app/src/components/Page/PageView.tsx

@@ -6,6 +6,7 @@ import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
 import dynamic from 'next/dynamic';
 
+import { useShouldExpandContent } from '~/client/services/layout';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import { generateSSRViewOptions } from '~/services/renderer/renderer';
 import {
@@ -70,6 +71,8 @@ export const PageView = (props: Props): JSX.Element => {
   const isNotFound = isNotFoundMeta || page?.revision == null;
   const isUsersHomepagePath = isUsersHomepage(pagePath);
 
+  const shouldExpandContent = useShouldExpandContent(page);
+
 
   // ***************************  Auto Scroll  ***************************
   useEffect(() => {
@@ -132,7 +135,7 @@ export const PageView = (props: Props): JSX.Element => {
       <>
         <PageContentsUtilities />
 
-        <div>
+        <div className="flex-expand-vert justify-content-between">
           <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
 
           { !isIdenticalPathPage && !isNotFound && (
@@ -150,11 +153,14 @@ export const PageView = (props: Props): JSX.Element => {
     );
   };
 
+  const mobileClass = isMobile ? styles['page-mobile'] : '';
+
   return (
     <PageViewLayout
       headerContents={headerContents}
       sideContents={sideContents}
       footerContents={footerContents}
+      expandContentWidth={shouldExpandContent}
     >
       <PageAlerts />
 
@@ -162,7 +168,7 @@ export const PageView = (props: Props): JSX.Element => {
       {specialContents == null && (
         <>
           {(isUsersHomepagePath && page?.creator != null) && <UserInfo author={page.creator} />}
-          <div className={`${isMobile ? `page-mobile ${styles['page-mobile']}` : ''}`}>
+          <div className={`flex-expand-vert ${mobileClass}`}>
             <Contents />
           </div>
         </>

+ 49 - 55
apps/app/src/components/PageComment.tsx

@@ -31,14 +31,13 @@ export type PageCommentProps = {
   revision: string | IRevisionHasId,
   currentUser: any,
   isReadOnly: boolean,
-  titleAlign?: 'center' | 'left' | 'right',
 }
 
 export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps): JSX.Element => {
 
   const {
     rendererOptions: rendererOptionsByProps,
-    pageId, pagePath, revision, currentUser, isReadOnly, titleAlign,
+    pageId, pagePath, revision, currentUser, isReadOnly,
   } = props;
 
   const { data: comments, mutate } = useSWRxPageComment(pageId);
@@ -112,9 +111,6 @@ export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps):
     return <></>;
   }
 
-  let commentTitleClasses = 'border-bottom py-3 mb-3';
-  commentTitleClasses = titleAlign != null ? `${commentTitleClasses} text-${titleAlign}` : `${commentTitleClasses} text-center`;
-
   const rendererOptions = rendererOptionsByProps ?? rendererOptionsForCurrentPage;
 
   if (commentsFromOldest == null || commentsExceptReply == null || rendererOptions == null) {
@@ -156,58 +152,56 @@ export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps):
 
   return (
     <div className={`${styles['page-comment-styles']} page-comments-row comment-list`}>
-      <div className="container-lg">
-        <div className="page-comments">
-          <h2 className={commentTitleClasses}><i className="icon-fw icon-bubbles"></i>Comments</h2>
-          <div className="page-comments-list" id="page-comments-list">
-            {commentsExceptReply.map((comment) => {
-
-              const defaultCommentThreadClasses = 'page-comment-thread pb-5';
-              const hasReply: boolean = Object.keys(allReplies).includes(comment._id);
-
-              let commentThreadClasses = '';
-              commentThreadClasses = hasReply ? `${defaultCommentThreadClasses} page-comment-thread-no-replies` : defaultCommentThreadClasses;
-
-              return (
-                <div key={comment._id} className={commentThreadClasses}>
-                  {commentElement(comment)}
-                  {hasReply && replyCommentsElement(allReplies[comment._id])}
-                  {(!isReadOnly && !showEditorIds.has(comment._id)) && (
-                    <div className="d-flex flex-row-reverse">
-                      <NotAvailableForGuest>
-                        <NotAvailableForReadOnlyUser>
-                          <Button
-                            data-testid="comment-reply-button"
-                            outline
-                            color="secondary"
-                            size="sm"
-                            className="btn-comment-reply"
-                            onClick={() => onReplyButtonClickHandler(comment._id)}
-                          >
-                            <i className="icon-fw icon-action-undo"></i> Reply
-                          </Button>
-                        </NotAvailableForReadOnlyUser>
-                      </NotAvailableForGuest>
-                    </div>
-                  )}
-                  {(!isReadOnly && showEditorIds.has(comment._id)) && (
-                    <CommentEditor
-                      pageId={pageId}
-                      replyTo={comment._id}
-                      onCancelButtonClicked={() => {
-                        removeShowEditorId(comment._id);
-                      }}
-                      onCommentButtonClicked={() => onCommentButtonClickHandler(comment._id)}
-                      revisionId={revisionId}
-                    />
-                  )}
-                </div>
-              );
-
-            })}
-          </div>
+      <div className="page-comments">
+        <div className="page-comments-list" id="page-comments-list">
+          {commentsExceptReply.map((comment) => {
+
+            const defaultCommentThreadClasses = 'page-comment-thread pb-5';
+            const hasReply: boolean = Object.keys(allReplies).includes(comment._id);
+
+            let commentThreadClasses = '';
+            commentThreadClasses = hasReply ? `${defaultCommentThreadClasses} page-comment-thread-no-replies` : defaultCommentThreadClasses;
+
+            return (
+              <div key={comment._id} className={commentThreadClasses}>
+                {commentElement(comment)}
+                {hasReply && replyCommentsElement(allReplies[comment._id])}
+                {(!isReadOnly && !showEditorIds.has(comment._id)) && (
+                  <div className="d-flex flex-row-reverse">
+                    <NotAvailableForGuest>
+                      <NotAvailableForReadOnlyUser>
+                        <Button
+                          data-testid="comment-reply-button"
+                          outline
+                          color="secondary"
+                          size="sm"
+                          className="btn-comment-reply"
+                          onClick={() => onReplyButtonClickHandler(comment._id)}
+                        >
+                          <i className="icon-fw icon-action-undo"></i> Reply
+                        </Button>
+                      </NotAvailableForReadOnlyUser>
+                    </NotAvailableForGuest>
+                  </div>
+                )}
+                {(!isReadOnly && showEditorIds.has(comment._id)) && (
+                  <CommentEditor
+                    pageId={pageId}
+                    replyTo={comment._id}
+                    onCancelButtonClicked={() => {
+                      removeShowEditorId(comment._id);
+                    }}
+                    onCommentButtonClicked={() => onCommentButtonClickHandler(comment._id)}
+                    revisionId={revisionId}
+                  />
+                )}
+              </div>
+            );
+
+          })}
         </div>
       </div>
+
       {!isReadOnly && (
         <DeleteCommentModal
           isShown={isDeleteConfirmModalShown}

+ 9 - 0
apps/app/src/components/PageControls/PageControls.tsx

@@ -16,6 +16,7 @@ import { toastError } from '~/client/util/toastr';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 import { useTagEditModal, type IPageForPageDuplicateModal } from '~/stores/modal';
 import { EditorMode, useEditorMode } from '~/stores/ui';
+import loggerFactory from '~/utils/logger';
 
 import { useSWRxPageInfo, useSWRxTagsInfo } from '../../stores/page';
 import { useSWRxUsersList } from '../../stores/user';
@@ -32,6 +33,9 @@ import SubscribeButton from './SubscribeButton';
 
 import styles from './PageControls.module.scss';
 
+const logger = loggerFactory('growi:components/PageControls');
+
+
 type TagsProps = {
   onClickEditTagsButton: () => void,
 }
@@ -199,6 +203,11 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
 
   const switchContentWidthClickHandler = useCallback(async(newValue: boolean) => {
     if (onClickSwitchContentWidth == null || (isGuestUser ?? true) || (isReadOnlyUser ?? true)) {
+      logger.warn('Could not switch content width', {
+        onClickSwitchContentWidth: onClickSwitchContentWidth == null ? 'null' : 'not null',
+        isGuestUser,
+        isReadOnlyUser,
+      });
       return;
     }
     if (!isIPageInfoForEntity(pageInfo)) {

+ 4 - 4
apps/app/src/components/PageCreateModal.jsx

@@ -281,16 +281,16 @@ const PageCreateModal = () => {
               <DropdownToggle id="template-type" caret>
                 {template == null && t('template.option_label.select')}
                 {template === 'children' && t('template.children.label')}
-                {template === 'decendants' && t('template.decendants.label')}
+                {template === 'descendants' && t('template.descendants.label')}
               </DropdownToggle>
               <DropdownMenu>
                 <DropdownItem onClick={() => onChangeTemplateHandler('children')}>
                   {t('template.children.label')} (_template)<br className="d-block d-md-none" />
                   <small className="text-muted text-wrap">- {t('template.children.desc')}</small>
                 </DropdownItem>
-                <DropdownItem onClick={() => onChangeTemplateHandler('decendants')}>
-                  {t('template.decendants.label')} (__template) <br className="d-block d-md-none" />
-                  <small className="text-muted">- {t('template.decendants.desc')}</small>
+                <DropdownItem onClick={() => onChangeTemplateHandler('descendants')}>
+                  {t('template.descendants.label')} (__template) <br className="d-block d-md-none" />
+                  <small className="text-muted">- {t('template.descendants.desc')}</small>
                 </DropdownItem>
               </DropdownMenu>
             </UncontrolledButtonDropdown>

+ 5 - 1
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -16,6 +16,7 @@ import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import { throttle, debounce } from 'throttle-debounce';
 
+import { useShouldExpandContent } from '~/client/services/layout';
 import { useUpdateStateAfterSave, useSaveOrUpdate } from '~/client/services/page-operation';
 import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
@@ -57,11 +58,11 @@ import loggerFactory from '~/utils/logger';
 // import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
 // import { ConflictDiffModal } from './ConflictDiffModal';
 // import Editor from './Editor';
+import EditorNavbarBottom from './EditorNavbarBottom';
 import Preview from './Preview';
 import scrollSyncHelper from './ScrollSyncHelper';
 
 import '@growi/editor/dist/style.css';
-import EditorNavbarBottom from './EditorNavbarBottom';
 
 
 const logger = loggerFactory('growi:PageEditor');
@@ -127,6 +128,8 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
   const { mutate: mutateResolvedTheme } = useResolvedThemeForEditor();
 
+  const shouldExpandContent = useShouldExpandContent(currentPage);
+
   const saveOrUpdate = useSaveOrUpdate();
   const updateStateAfterSave = useUpdateStateAfterSave(pageId, { supressEditingMarkdownMutation: true });
 
@@ -597,6 +600,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
             rendererOptions={rendererOptions}
             markdown={markdownToPreview}
             pagePath={currentPagePath}
+            expandContentWidth={shouldExpandContent}
             // TODO: implement
             // refs: https://redmine.weseek.co.jp/issues/126519
             // onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}

+ 9 - 20
apps/app/src/components/PageEditor/Preview.module.scss

@@ -1,29 +1,18 @@
-@use '~/styles/variables' as var;
 @use '~/styles/mixins';
 
-@include mixins.editing(true) {
-  .page-editor-preview-body :global {
+.page-editor-preview-body :global {
+  .wiki {
+    max-width: 980px;
+    margin: 0 auto;
   }
 }
 
 // modify width for fluid layout
-@include mixins.editing(true) {
-  .dynamic-layout-root:not(.growi-layout-fluid) {
-    :local {
-      .page-editor-preview-body :global {
-        .wiki {
-          max-width: 980px;
-          margin: 0 auto;
-        }
-      }
-    }
-  }
-  .dynamic-layout-root.growi-layout-fluid {
-    :local {
-      .page-editor-preview-body :global {
-        .wiki {
-          margin: 0 auto;
-        }
+.page-editor-preview-body {
+  &:global {
+    &.fluid-layout {
+      .wiki {
+        @include mixins.fluid-layout();
       }
     }
   }

+ 6 - 2
apps/app/src/components/PageEditor/Preview.tsx

@@ -9,13 +9,14 @@ import RevisionRenderer from '../Page/RevisionRenderer';
 
 import styles from './Preview.module.scss';
 
-const moduleClass = styles['page-editor-preview-body'];
+const moduleClass = styles['page-editor-preview-body'] ?? '';
 
 
 type Props = {
   rendererOptions: RendererOptions,
   markdown?: string,
   pagePath?: string | null,
+  expandContentWidth?: boolean,
   onScroll?: (scrollTop: number) => void,
 }
 
@@ -24,11 +25,14 @@ const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>):
   const {
     rendererOptions,
     markdown, pagePath,
+    expandContentWidth,
   } = props;
 
+  const fluidLayoutClass = expandContentWidth ? 'fluid-layout' : '';
+
   return (
     <div
-      className={`${moduleClass} ${pagePath === '/Sidebar' ? 'preview-sidebar' : ''}`}
+      className={`${moduleClass} ${fluidLayoutClass} ${pagePath === '/Sidebar' ? 'preview-sidebar' : ''}`}
       ref={ref}
       onScroll={(event: SyntheticEvent<HTMLDivElement>) => {
         if (props.onScroll != null) {

+ 8 - 0
apps/app/src/components/SearchPage/SearchResultContent.module.scss

@@ -1,2 +1,10 @@
+@use '~/styles/mixins';
+
 .search-result-content :global {
 }
+
+.fluid-layout :global {
+  .grw-container-convertible {
+    @include mixins.fluid-layout();
+  }
+}

+ 13 - 13
apps/app/src/components/SearchPage/SearchResultContent.tsx

@@ -10,12 +10,12 @@ import { animateScroll } from 'react-scroll';
 import { DropdownItem } from 'reactstrap';
 import { debounce } from 'throttle-debounce';
 
-import { useLayoutFluidClassName } from '~/client/services/layout';
+import { useShouldExpandContent } from '~/client/services/layout';
 import { exportAsMarkdown, updateContentWidth } from '~/client/services/page-operation';
 import { toastSuccess } from '~/client/util/toastr';
 import type { IPageWithSearchMeta } from '~/interfaces/search';
 import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
-import { useCurrentUser, useIsContainerFluid } from '~/stores/context';
+import { useCurrentUser } from '~/stores/context';
 import {
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
 } from '~/stores/modal';
@@ -32,9 +32,10 @@ import type { PageContentFooterProps } from '../PageContentFooter';
 import styles from './SearchResultContent.module.scss';
 
 const moduleClass = styles['search-result-content'];
+const _fluidLayoutClass = styles['fluid-layout'];
 
 
-const SubNavButtons = dynamic(() => import('../PageControls').then(mod => mod.PageControls), { ssr: false });
+const PageControls = dynamic(() => import('../PageControls').then(mod => mod.PageControls), { ssr: false });
 const RevisionLoader = dynamic<RevisionLoaderProps>(() => import('../Page/RevisionLoader').then(mod => mod.RevisionLoader), { ssr: false });
 const PageComment = dynamic<PageCommentProps>(() => import('../PageComment').then(mod => mod.PageComment), { ssr: false });
 const PageContentFooter = dynamic<PageContentFooterProps>(() => import('../PageContentFooter').then(mod => mod.PageContentFooter), { ssr: false });
@@ -123,12 +124,8 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   const { open: openDeleteModal } = usePageDeleteModal();
   const { data: rendererOptions } = useSearchResultOptions(pageWithMeta.data.path, highlightKeywords);
   const { data: currentUser } = useCurrentUser();
-  const { data: isContainerFluid } = useIsContainerFluid();
 
-  const [isExpandContentWidth, setIsExpandContentWidth] = useState(page.expandContentWidth);
-
-  // TODO: determine className by the 'expandContentWidth' from the updated page
-  const growiLayoutFluidClass = useLayoutFluidClassName(isExpandContentWidth);
+  const shouldExpandContent = useShouldExpandContent(page);
 
   const duplicateItemClickedHandler = useCallback(async(pageToDuplicate) => {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -176,7 +173,8 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
 
   const switchContentWidthHandler = useCallback(async(pageId: string, value: boolean) => {
     await updateContentWidth(pageId, value);
-    setIsExpandContentWidth(value);
+
+    // TODO: revalidate page data and update shouldExpandContent
   }, []);
 
   const RightComponent = useCallback(() => {
@@ -188,11 +186,11 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
 
     return (
       <div className="d-flex flex-column align-items-end justify-content-center px-2 py-1">
-        <SubNavButtons
+        <PageControls
           pageId={page._id}
           revisionId={revisionId}
           path={page.path}
-          expandContentWidth={isExpandContentWidth ?? isContainerFluid}
+          expandContentWidth={shouldExpandContent}
           showPageControlDropdown={showPageControlDropdown}
           forceHideMenuItems={forceHideMenuItems}
           additionalMenuItemRenderer={props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />}
@@ -203,16 +201,18 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
         />
       </div>
     );
-  }, [page, isExpandContentWidth, showPageControlDropdown, forceHideMenuItems, isContainerFluid,
+  }, [page, shouldExpandContent, showPageControlDropdown, forceHideMenuItems,
       duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler, switchContentWidthHandler]);
 
   const isRenderable = page != null && rendererOptions != null;
 
+  const fluidLayoutClass = shouldExpandContent ? _fluidLayoutClass : '';
+
   return (
     <div
       key={page._id}
       data-testid="search-result-content"
-      className={`dynamic-layout-root ${growiLayoutFluidClass} ${moduleClass}`}
+      className={`dynamic-layout-root ${moduleClass} ${fluidLayoutClass}`}
     >
       <RightComponent />
 

+ 4 - 0
apps/app/src/components/ShareLinkPageView.tsx

@@ -3,6 +3,7 @@ import React, { useMemo } from 'react';
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import dynamic from 'next/dynamic';
 
+import { useShouldExpandContent } from '~/client/services/layout';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import { generateSSRViewOptions } from '~/services/renderer/renderer';
@@ -44,6 +45,8 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
 
   const { data: viewOptions } = useViewOptions();
 
+  const shouldExpandContent = useShouldExpandContent(page);
+
   const isNotFound = isNotFoundMeta || page == null || shareLink == null;
 
   const specialContents = useMemo(() => {
@@ -93,6 +96,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     <PageViewLayout
       headerContents={headerContents}
       sideContents={sideContents}
+      expandContentWidth={shouldExpandContent}
     >
       { specialContents }
       { specialContents == null && (

+ 1 - 1
apps/app/src/components/Sidebar/PageCreateButton/DropendMenu.tsx

@@ -60,7 +60,7 @@ export const DropendMenu = React.memo((props: DropendMenuProps): JSX.Element =>
           onClick={onClickTemplateForDescendantsButtonHandler}
           type="button"
         >
-          {t('create_page_dropdown.template.decendants')}
+          {t('create_page_dropdown.template.descendants')}
         </button>
       </li>
     </ul>

+ 0 - 2
apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx

@@ -168,8 +168,6 @@ export const PageCreateButton = React.memo((): JSX.Element => {
     }
   }, [currentPage, isLoading, router]);
 
-  // TODO: update button design
-  // https://redmine.weseek.co.jp/issues/132683
   return (
     <div
       className="d-flex flex-row"

+ 1 - 1
apps/app/src/components/Sidebar/Sidebar.tsx

@@ -201,7 +201,7 @@ export const Sidebar = (): JSX.Element => {
         </DrawerToggler>
       ) }
       { sidebarMode != null && !isDockMode() && <AppTitleOnSubnavigation /> }
-      <DrawableContainer className={`${grwSidebarClass} ${modeClass} border-end vh-100`} data-testid="grw-sidebar">
+      <DrawableContainer className={`${grwSidebarClass} ${modeClass} border-end flex-expand-vh-100`} data-testid="grw-sidebar">
         <ResizableContainer>
           { sidebarMode != null && !isCollapsedMode() && <AppTitleOnSidebarHead /> }
           <SidebarHead />

+ 1 - 1
apps/app/src/components/Sidebar/Tag.tsx

@@ -44,7 +44,7 @@ const Tag: FC = () => {
 
   // todo: adjust design by XD
   return (
-    <div className="grw-container-convertible container-lg px-4 mb-5 pb-5" data-testid="grw-sidebar-content-tags">
+    <div className="container-lg px-4 mb-5 pb-5" data-testid="grw-sidebar-content-tags">
       <div className="grw-sidebar-content-header py-3 d-flex">
         <h3 className="mb-0">{t('Tags')}</h3>
         <SidebarHeaderReloadButton onClick={() => onReload()} />

+ 2 - 0
apps/app/src/interfaces/template.ts

@@ -0,0 +1,2 @@
+export type TargetType = 'children' | 'descendants';
+export type LabelType = '_template' | '__template';

+ 3 - 5
apps/app/src/pages/[[...path]].page.tsx

@@ -5,7 +5,7 @@ import EventEmitter from 'events';
 
 import { isIPageInfoForEntity } from '@growi/core';
 import type {
-  IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision, IUserHasId,
+  IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision,
 } from '@growi/core';
 import {
   isClient, pagePathUtils, pathUtils,
@@ -20,7 +20,7 @@ import Head from 'next/head';
 import { useRouter } from 'next/router';
 import superjson from 'superjson';
 
-import { useEditorModeClassName, useLayoutFluidClassNameByPage } from '~/client/services/layout';
+import { useEditorModeClassName } from '~/client/services/layout';
 import { PageView } from '~/components/Page/PageView';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript'; import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { EditorConfig } from '~/interfaces/editor-settings';
@@ -249,8 +249,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useSetupGlobalSocket();
   useSetupGlobalSocketForPage(pageId);
 
-  const growiLayoutFluidClass = useLayoutFluidClassNameByPage(pageWithMeta?.data);
-
   // Store initial data (When revisionBody is not SSR)
   useEffect(() => {
     if (!props.skipSSR) {
@@ -323,7 +321,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
       <Head>
         <title>{title}</title>
       </Head>
-      <div className={`dynamic-layout-root ${growiLayoutFluidClass} justify-content-between`}>
+      <div className="dynamic-layout-root justify-content-between">
         <nav className="sticky-top">
           <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
         </nav>

+ 1 - 1
apps/app/src/pages/me/[[...path]].page.tsx

@@ -125,7 +125,7 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 
         <div id="main" className="main">
-          <div id="content-main" className="content-main container-lg grw-container-convertible">
+          <div id="content-main" className="content-main container-lg">
             {targetPage.component}
           </div>
         </div>

+ 2 - 5
apps/app/src/pages/share/[[...path]].page.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect } from 'react';
 
-import { type IUserHasId, type IPagePopulatedToShowRevision, getIdForRef } from '@growi/core';
+import { type IPagePopulatedToShowRevision, getIdForRef } from '@growi/core';
 import type {
   GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
@@ -8,7 +8,6 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import Head from 'next/head';
 import superjson from 'superjson';
 
-import { useLayoutFluidClassNameByPage } from '~/client/services/layout';
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
 import GrowiContextualSubNavigationSubstance from '~/components/Navbar/GrowiContextualSubNavigation';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
@@ -108,8 +107,6 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   }, [mutateCurrentPage, props.isNotFound, props.shareLink?.relatedPage._id, props.skipSSR]);
 
 
-  const growiLayoutFluidClass = useLayoutFluidClassNameByPage(props.shareLinkRelatedPage);
-
   const pagePath = props.shareLinkRelatedPage?.path ?? '';
 
   const title = generateCustomTitleForPage(props, pagePath);
@@ -120,7 +117,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
         <title>{title}</title>
       </Head>
 
-      <div className={`dynamic-layout-root ${growiLayoutFluidClass} justify-content-between`}>
+      <div className="dynamic-layout-root justify-content-between">
         <nav className="sticky-top">
           <GrowiContextualSubNavigationForSharedPage page={currentPage ?? props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />
         </nav>

+ 1 - 1
apps/app/src/pages/tags.page.tsx

@@ -73,7 +73,7 @@ const TagPage: NextPageWithLayout<CommonProps> = (props: Props) => {
         <title>{title}</title>
       </Head>
       <div className="dynamic-layout-root">
-        <div className="grw-container-convertible container-lg mb-5 pb-5" data-testid="tags-page">
+        <div className="container-lg mb-5 pb-5" data-testid="tags-page">
           <h2 className="my-3">{`${t('Tags')}(${totalCount})`}</h2>
           <div className="px-3 mb-5 text-center">
             <TagCloudBox tags={tagData} minSize={20} />

+ 1 - 1
apps/app/src/pages/trash.page.tsx

@@ -68,7 +68,7 @@ const TrashPage: NextPageWithLayout<CommonProps> = (props: Props) => {
           TODO: implement navigation for /trash
         </nav>
 
-        <div className="content-main container-lg grw-container-convertible mb-5 pb-5">
+        <div className="content-main container-lg mb-5 pb-5">
           <PagePathNavSticky pagePath="/trash" />
           <TrashPageList />
         </div>

+ 0 - 5
apps/app/src/styles/_layout.scss

@@ -6,11 +6,6 @@
   @extend .flex-expand-vert;
 }
 
-.dynamic-layout-root.growi-layout-fluid .grw-container-convertible {
-  width: 100%;
-  max-width: none;
-}
-
 .grw-bg-image-wrapper {
   position: fixed;
   width: 100%;

+ 1 - 0
apps/app/src/styles/_mixins.scss

@@ -2,6 +2,7 @@
 @use './variables' as var;
 
 @import './mixins/editing';
+@import './mixins/fluid-layout';
 @import './mixins/share-link';
 
 @mixin variable-font-size($basesize) {

+ 4 - 0
apps/app/src/styles/mixins/_fluid-layout.scss

@@ -0,0 +1,4 @@
+@mixin fluid-layout() {
+  width: 100%;
+  max-width: none;
+}

+ 1 - 1
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--access-to-page.cy.ts

@@ -303,7 +303,7 @@ context('Access to Template Editing Mode', () => {
     cy.getByTestid('open-page-template-modal-btn').filter(':visible').click({force: true});
     cy.getByTestid('page-template-modal').should('be.visible');
 
-    cy.getByTestid('template-button-decendants').click(({force: true}))
+    cy.getByTestid('template-button-descendants').click(({force: true}))
     cy.waitUntilSkeletonDisappear();
 
     cy.getByTestid('navbar-editor').should('be.visible').then(()=>{

+ 3 - 1
apps/app/test/cypress/e2e/50-sidebar/50-sidebar--access-to-side-bar.cy.ts

@@ -269,7 +269,9 @@ describe('Access to sidebar', () => {
         });
 
         it('Succesfully click all tags button', () => {
-          cy.get('.grw-container-convertible > div > .btn-primary').click({force: true});
+          cy.getByTestid('grw-sidebar-content-tags').within(() => {
+            cy.get('.btn-primary').click({force: true});
+          });
           cy.collapseSidebar(true);
           cy.getByTestid('grw-tags-list').should('be.visible');
 

+ 1 - 2
packages/core/scss/_flex-expand.scss

@@ -2,14 +2,12 @@
   display: flex;
   flex-direction: row;
   flex-grow: 1;
-  height: 100%;
 }
 
 .flex-expand-vert {
   display: flex;
   flex: 1;
   flex-direction: column;
-  height: 100%;
 }
 
 .flex-expand-vh-100 {
@@ -17,6 +15,7 @@
 
   .flex-expand-horiz,
   .flex-expand-vert {
+    height: 100%;
     overflow-y: auto;
   }
 }