Explorar o código

Merge pull request #6485 from weseek/feat/optimize-rendering-modal-performance

feat: Optimize rendering modal performance
Yuki Takei %!s(int64=3) %!d(string=hai) anos
pai
achega
4d04868d0a

+ 12 - 12
packages/app/src/components/Layout/BasicLayout.tsx

@@ -7,6 +7,18 @@ import Sidebar from '../Sidebar';
 
 import { RawLayout } from './RawLayout';
 
+// const HotkeysManager = dynamic(() => import('../client/js/components/Hotkeys/HotkeysManager'), { ssr: false });
+// const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
+const GrowiNavbarBottom = dynamic(() => import('../Navbar/GrowiNavbarBottom').then(mod => mod.GrowiNavbarBottom), { ssr: false });
+const ShortcutsModal = dynamic(() => import('../ShortcutsModal'), { ssr: false });
+const SystemVersion = dynamic(() => import('../SystemVersion'), { ssr: false });
+// Page modals
+const PageCreateModal = dynamic(() => import('../PageCreateModal'), { ssr: false });
+const PageDuplicateModal = dynamic(() => import('../PageDuplicateModal'), { ssr: false });
+const PageDeleteModal = dynamic(() => import('../PageDeleteModal'), { ssr: false });
+const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
+const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
+
 
 type Props = {
   title: string
@@ -19,18 +31,6 @@ export const BasicLayout = ({
   children, title, className, expandContainer,
 }: Props): JSX.Element => {
 
-  // const HotkeysManager = dynamic(() => import('../client/js/components/Hotkeys/HotkeysManager'), { ssr: false });
-  // const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
-  const GrowiNavbarBottom = dynamic(() => import('../Navbar/GrowiNavbarBottom').then(mod => mod.GrowiNavbarBottom), { ssr: false });
-  const ShortcutsModal = dynamic(() => import('../ShortcutsModal'), { ssr: false });
-  const SystemVersion = dynamic(() => import('../SystemVersion'), { ssr: false });
-  // Page modals
-  const PageCreateModal = dynamic(() => import('../PageCreateModal'), { ssr: false });
-  const PageDuplicateModal = dynamic(() => import('../PageDuplicateModal'), { ssr: false });
-  const PageDeleteModal = dynamic(() => import('../PageDeleteModal'), { ssr: false });
-  const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
-  const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
-
   const myClassName = `${className ?? ''} ${expandContainer ? 'growi-layout-fluid' : ''}`;
 
   return (

+ 9 - 9
packages/app/src/components/PageComment/DeleteCommentModal.tsx

@@ -25,7 +25,7 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): JSX.Element
     isShown, comment, errorMessage, cancelToDelete, confirmToDelete,
   } = props;
 
-  const HeaderContent = useMemo(() => {
+  const headerContent = () => {
     if (comment == null || isShown === false) {
       return <></>;
     }
@@ -35,9 +35,9 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): JSX.Element
         Delete comment?
       </span>
     );
-  }, [comment, isShown]);
+  };
 
-  const BodyContent = useMemo(() => {
+  const bodyContent = () => {
     if (comment == null || isShown === false) {
       return <></>;
     }
@@ -59,9 +59,9 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): JSX.Element
         <p className="card well comment-body mt-2 p-2">{commentBodyElement}</p>
       </>
     );
-  }, [comment, isShown]);
+  };
 
-  const FooterContent = useMemo(() => {
+  const footerContent = () => {
     if (comment == null || isShown === false) {
       return <></>;
     }
@@ -75,18 +75,18 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): JSX.Element
         </Button>
       </>
     );
-  }, [cancelToDelete, comment, confirmToDelete, errorMessage, isShown]);
+  };
 
   return (
     <Modal isOpen={isShown} toggle={cancelToDelete} className={`${styles['page-comment-delete-modal']}`}>
       <ModalHeader tag="h4" toggle={cancelToDelete} className="bg-danger text-light">
-        {HeaderContent}
+        {headerContent()}
       </ModalHeader>
       <ModalBody>
-        {BodyContent}
+        {bodyContent()}
       </ModalBody>
       <ModalFooter>
-        {FooterContent}
+        {footerContent()}
       </ModalFooter>
     </Modal>
   );

+ 18 - 5
packages/app/src/components/PageCreateModal.jsx

@@ -19,7 +19,7 @@ const {
   userPageRoot, isCreatablePage, generateEditorPath, isUsersHomePage,
 } = pagePathUtils;
 
-const PageCreateModal = (props) => {
+const PageCreateModal = () => {
   const { t } = useTranslation();
 
   const { data: currentUser } = useCurrentUser();
@@ -42,8 +42,10 @@ const PageCreateModal = (props) => {
 
   // ensure pageNameInput is synced with selectedPagePath || currentPagePath
   useEffect(() => {
-    setPageNameInput(isCreatable ? pathUtils.addTrailingSlash(pathname) : '/');
-  }, [pathname, isCreatable]);
+    if (isOpened) {
+      setPageNameInput(isCreatable ? pathUtils.addTrailingSlash(pathname) : '/');
+    }
+  }, [isOpened, pathname, isCreatable]);
 
   const checkIsUsersHomePageDebounce = useMemo(() => {
     const checkIsUsersHomePage = () => {
@@ -54,8 +56,10 @@ const PageCreateModal = (props) => {
   }, [pageNameInput]);
 
   useEffect(() => {
-    checkIsUsersHomePageDebounce(pageNameInput);
-  }, [checkIsUsersHomePageDebounce, pageNameInput]);
+    if (isOpened) {
+      checkIsUsersHomePageDebounce(pageNameInput);
+    }
+  }, [isOpened, checkIsUsersHomePageDebounce, pageNameInput]);
 
   function transitBySubmitEvent(e, transitHandler) {
     // prevent page transition by submit
@@ -132,6 +136,9 @@ const PageCreateModal = (props) => {
   }
 
   function renderCreateTodayForm() {
+    if (!isOpened) {
+      return <></>;
+    }
     return (
       <div className="row">
         <fieldset className="col-12 mb-4">
@@ -183,6 +190,9 @@ const PageCreateModal = (props) => {
   }
 
   function renderInputPageForm() {
+    if (!isOpened) {
+      return <></>;
+    }
     return (
       <div className="row" data-testid="row-create-page-under-below">
         <fieldset className="col-12 mb-4">
@@ -237,6 +247,9 @@ const PageCreateModal = (props) => {
   }
 
   function renderTemplatePageForm() {
+    if (!isOpened) {
+      return <></>;
+    }
     return (
       <div className="row">
         <fieldset className="col-12">

+ 43 - 7
packages/app/src/components/PageDeleteModal.tsx

@@ -228,13 +228,26 @@ const PageDeleteModal: FC = () => {
     return <></>;
   };
 
-  return (
-    <Modal size="lg" isOpen={isOpened} toggle={closeDeleteModal} data-testid="page-delete-modal" className="grw-create-page">
-      <ModalHeader tag="h4" toggle={closeDeleteModal} className={`bg-${deleteIconAndKey[deleteMode].color} text-light`}>
+  const headerContent = () => {
+    if (!isOpened) {
+      return <></>;
+    }
+
+    return (
+      <>
         <i className={`icon-fw icon-${deleteIconAndKey[deleteMode].icon}`}></i>
         { t(`modal_delete.delete_${deleteIconAndKey[deleteMode].translationKey}`) }
-      </ModalHeader>
-      <ModalBody>
+      </>
+    );
+  };
+
+  const bodyContent = () => {
+    if (!isOpened) {
+      return <></>;
+    }
+
+    return (
+      <>
         <div className="form-group grw-scrollable-modal-body pb-1">
           <label>{ t('modal_delete.deleting_page') }:</label><br />
           {/* Todo: change the way to show path on modal when too many pages are selected */}
@@ -242,8 +255,17 @@ const PageDeleteModal: FC = () => {
         </div>
         { isDeletable && renderDeleteRecursivelyForm()}
         { isDeletable && !forceDeleteCompletelyMode && renderDeleteCompletelyForm() }
-      </ModalBody>
-      <ModalFooter>
+      </>
+    );
+  };
+
+  const footerContent = () => {
+    if (!isOpened) {
+      return <></>;
+    }
+
+    return (
+      <>
         <ApiErrorMessageList errs={errs} />
         <button
           type="button"
@@ -254,6 +276,20 @@ const PageDeleteModal: FC = () => {
           <i className={`mr-1 icon-${deleteIconAndKey[deleteMode].icon}`} aria-hidden="true"></i>
           { t(`modal_delete.delete_${deleteIconAndKey[deleteMode].translationKey}`) }
         </button>
+      </>
+    );
+  };
+
+  return (
+    <Modal size="lg" isOpen={isOpened} toggle={closeDeleteModal} data-testid="page-delete-modal" className="grw-create-page">
+      <ModalHeader tag="h4" toggle={closeDeleteModal} className={`bg-${deleteIconAndKey[deleteMode].color} text-light`}>
+        {headerContent()}
+      </ModalHeader>
+      <ModalBody>
+        {bodyContent()}
+      </ModalBody>
+      <ModalFooter>
+        {footerContent()}
       </ModalFooter>
     </Modal>
 

+ 39 - 17
packages/app/src/components/PageDuplicateModal.tsx

@@ -75,10 +75,10 @@ const PageDuplicateModal = (): JSX.Element => {
   }, [checkExistPaths]);
 
   useEffect(() => {
-    if (page != null && pageNameInput !== page.path) {
+    if (isOpened && page != null && pageNameInput !== page.path) {
       checkExistPathsDebounce(page.path, pageNameInput);
     }
-  }, [pageNameInput, subordinatedPages, checkExistPathsDebounce, page]);
+  }, [isOpened, pageNameInput, subordinatedPages, checkExistPathsDebounce, page]);
 
   /**
    * change pageNameInput for PagePathAutoComplete
@@ -150,22 +150,17 @@ const PageDuplicateModal = (): JSX.Element => {
 
   }, [isOpened]);
 
-  if (page == null) {
-    return <></>;
-  }
 
-  const { path } = page;
-  const isTargetPageDuplicate = existingPaths.includes(pageNameInput);
+  const bodyContent = () => {
+    if (!isOpened || page == null) {
+      return <></>;
+    }
 
-  const submitButtonEnabled = existingPaths.length === 0
-    || (isDuplicateRecursively && isDuplicateRecursivelyWithoutExistPath);
+    const { path } = page;
+    const isTargetPageDuplicate = existingPaths.includes(pageNameInput);
 
-  return (
-    <Modal size="lg" isOpen={isOpened} toggle={closeDuplicateModal} data-testid="page-duplicate-modal" className="grw-duplicate-page" autoFocus={false}>
-      <ModalHeader tag="h4" toggle={closeDuplicateModal} className="bg-primary text-light">
-        { t('modal_duplicate.label.Duplicate page') }
-      </ModalHeader>
-      <ModalBody>
+    return (
+      <>
         <div className="form-group"><label>{t('modal_duplicate.label.Current page name')}</label><br />
           <code>{path}</code>
         </div>
@@ -239,9 +234,20 @@ const PageDuplicateModal = (): JSX.Element => {
             ) }
           </div>
         </div>
+      </>
+    );
+  };
 
-      </ModalBody>
-      <ModalFooter>
+  const footerContent = () => {
+    if (!isOpened || page == null) {
+      return <></>;
+    }
+
+    const submitButtonEnabled = existingPaths.length === 0
+    || (isDuplicateRecursively && isDuplicateRecursivelyWithoutExistPath);
+
+    return (
+      <>
         <ApiErrorMessageList errs={errs} targetPath={pageNameInput} />
         <button
           type="button"
@@ -251,6 +257,22 @@ const PageDuplicateModal = (): JSX.Element => {
         >
           { t('modal_duplicate.label.Duplicate page') }
         </button>
+      </>
+    );
+  };
+
+
+  return (
+    <Modal size="lg" isOpen={isOpened} toggle={closeDuplicateModal} data-testid="page-duplicate-modal" className="grw-duplicate-page" autoFocus={false}>
+      <ModalHeader tag="h4" toggle={closeDuplicateModal} className="bg-primary text-light">
+        { t('modal_duplicate.label.Duplicate page') }
+      </ModalHeader>
+      <ModalBody>
+        {bodyContent()}
+      </ModalBody>
+      <ModalFooter>
+        <ApiErrorMessageList errs={errs} targetPath={pageNameInput} />
+        {footerContent()}
       </ModalFooter>
     </Modal>
   );

+ 55 - 35
packages/app/src/components/PageRenameModal.tsx

@@ -151,15 +151,17 @@ const PageRenameModal = (): JSX.Element => {
   }, [isUsersHomePage, pageNameInput]);
 
   useEffect(() => {
-    if (page != null && pageNameInput !== page.data.path) {
+    if (isOpened && page != null && pageNameInput !== page.data.path) {
       checkExistPathsDebounce(page.data.path, pageNameInput);
       checkIsUsersHomePageDebounce(pageNameInput);
     }
-  }, [pageNameInput, subordinatedPages, checkExistPathsDebounce, page, checkIsUsersHomePageDebounce]);
+  }, [isOpened, pageNameInput, subordinatedPages, checkExistPathsDebounce, page, checkIsUsersHomePageDebounce]);
 
   useEffect(() => {
-    setCanRename(false);
-  }, [pageNameInput]);
+    if (isOpened && page != null) {
+      setCanRename(false);
+    }
+  }, [isOpened, page, pageNameInput]);
 
 
   function ppacInputChangeHandler(value) {
@@ -177,7 +179,7 @@ const PageRenameModal = (): JSX.Element => {
   }
 
   useEffect(() => {
-    if (isOpened) {
+    if (isOpened || page == null) {
       return;
     }
 
@@ -193,37 +195,18 @@ const PageRenameModal = (): JSX.Element => {
       setExpandOtherOptions(false);
     }, 1000);
 
-  }, [isOpened]);
-
-  if (page == null) {
-    return <></>;
-  }
-
-  const { path } = page.data;
-  const isTargetPageDuplicate = existingPaths.includes(pageNameInput);
-
-  let submitButtonDisabled = false;
+  }, [isOpened, page]);
 
-  if (isMatchedWithUserHomePagePath) {
-    submitButtonDisabled = true;
-  }
-  else if (!canRename) {
-    submitButtonDisabled = true;
-  }
-  else if (isV5Compatible(page.meta)) {
-    submitButtonDisabled = existingPaths.length !== 0; // v5 data
-  }
-  else {
-    submitButtonDisabled = !isRenameRecursively; // v4 data
-  }
+  const bodyContent = () => {
+    if (!isOpened || page == null) {
+      return <></>;
+    }
 
+    const { path } = page.data;
+    const isTargetPageDuplicate = existingPaths.includes(pageNameInput);
 
-  return (
-    <Modal size="lg" isOpen={isOpened} toggle={closeRenameModal} data-testid="page-rename-modal" autoFocus={false}>
-      <ModalHeader tag="h4" toggle={closeRenameModal} className="bg-primary text-light">
-        { t('modal_rename.label.Move/Rename page') }
-      </ModalHeader>
-      <ModalBody>
+    return (
+      <>
         <div className="form-group">
           <label>{ t('modal_rename.label.Current page name') }</label><br />
           <code>{ path }</code>
@@ -338,9 +321,31 @@ const PageRenameModal = (): JSX.Element => {
           </div>
           <div> {subordinatedError} </div>
         </Collapse>
+      </>
+    );
+  };
 
-      </ModalBody>
-      <ModalFooter>
+  const footerContent = () => {
+    if (!isOpened || page == null) {
+      return <></>;
+    }
+
+    let submitButtonDisabled = false;
+
+    if (isMatchedWithUserHomePagePath) {
+      submitButtonDisabled = true;
+    }
+    else if (!canRename) {
+      submitButtonDisabled = true;
+    }
+    else if (isV5Compatible(page.meta)) {
+      submitButtonDisabled = existingPaths.length !== 0; // v5 data
+    }
+    else {
+      submitButtonDisabled = !isRenameRecursively; // v4 data
+    }
+    return (
+      <>
         <ApiErrorMessageList errs={errs} targetPath={pageNameInput} />
         <button
           type="button"
@@ -349,6 +354,21 @@ const PageRenameModal = (): JSX.Element => {
           disabled={submitButtonDisabled}
         >Rename
         </button>
+      </>
+    );
+  };
+
+
+  return (
+    <Modal size="lg" isOpen={isOpened} toggle={closeRenameModal} data-testid="page-rename-modal" autoFocus={false}>
+      <ModalHeader tag="h4" toggle={closeRenameModal} className="bg-primary text-light">
+        { t('modal_rename.label.Move/Rename page') }
+      </ModalHeader>
+      <ModalBody>
+        {bodyContent()}
+      </ModalBody>
+      <ModalFooter>
+        {footerContent()}
       </ModalFooter>
     </Modal>
   );

+ 39 - 8
packages/app/src/components/PutbackPageModal.jsx

@@ -53,13 +53,22 @@ const PutBackPageModal = () => {
     }
   }
 
-
-  return (
-    <Modal isOpen={isOpened} toggle={closePutBackPageModal} className="grw-create-page">
-      <ModalHeader tag="h4" toggle={closePutBackPageModal} className="bg-info text-light">
+  const HeaderContent = () => {
+    if (!isOpened) {
+      return <></>;
+    }
+    return (
+      <>
         <i className="icon-action-undo mr-2" aria-hidden="true"></i> { t('modal_putback.label.Put Back Page') }
-      </ModalHeader>
-      <ModalBody>
+      </>
+    );
+  };
+  const BodyContent = () => {
+    if (!isOpened) {
+      return <></>;
+    }
+    return (
+      <>
         <div className="form-group">
           <label>{t('modal_putback.label.Put Back Page')}:</label><br />
           <code>{path}</code>
@@ -79,12 +88,34 @@ const PutBackPageModal = () => {
             <code>{ path }</code>{ t('modal_putback.help.recursively') }
           </p>
         </div>
-      </ModalBody>
-      <ModalFooter>
+      </>
+    );
+
+  };
+  const FooterContent = () => {
+    if (!isOpened) {
+      return <></>;
+    }
+    return (
+      <>
         <ApiErrorMessageList errs={errs} targetPath={targetPath} />
         <button type="button" className="btn btn-info" onClick={putbackPageButtonHandler}>
           <i className="icon-action-undo mr-2" aria-hidden="true"></i> { t('Put Back') }
         </button>
+      </>
+    );
+  };
+
+  return (
+    <Modal isOpen={isOpened} toggle={closePutBackPageModal} className="grw-create-page">
+      <ModalHeader tag="h4" toggle={closePutBackPageModal} className="bg-info text-light">
+        <HeaderContent/>
+      </ModalHeader>
+      <ModalBody>
+        <BodyContent/>
+      </ModalBody>
+      <ModalFooter>
+        <FooterContent/>
       </ModalFooter>
     </Modal>
   );

+ 146 - 136
packages/app/src/components/ShortcutsModal.tsx

@@ -14,10 +14,151 @@ const ShortcutsModal = (): JSX.Element => {
 
   const { data: status, close } = useShortcutsModal();
 
-  // add classes to cmd-key by OS
-  const platform = window.navigator.platform.toLowerCase();
-  const isMac = (platform.indexOf('mac') > -1);
-  const additionalClassByOs = isMac ? 'mac' : 'key-longer win';
+  const bodyContent = () => {
+    if (status == null || !status.isOpened) {
+      return <></>;
+    }
+
+    // add classes to cmd-key by OS
+    const platform = window.navigator.platform.toLowerCase();
+    const isMac = (platform.indexOf('mac') > -1);
+    const additionalClassByOs = isMac ? 'mac' : 'key-longer win';
+
+    return (
+      <div className="container">
+        <div className="row">
+          <div className="col-lg-6">
+            <h3>
+              <strong>{t('modal_shortcuts.global.title')}</strong>
+            </h3>
+
+            <table className="table">
+              <tbody>
+                <tr>
+                  <th>
+                    {/* eslint-disable-next-line react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('modal_shortcuts.global.Open/Close shortcut help') }} />:
+                  </th>
+                  <td>
+                    <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">/</span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.global.Create Page')}:</th>
+                  <td>
+                    <span className="key">C</span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.global.Edit Page')}:</th>
+                  <td>
+                    <span className="key">E</span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.global.Search')}:</th>
+                  <td><span className="key">/</span></td>
+                </tr>
+                <tr>
+                  <th>
+                    {/* eslint-disable-next-line react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('modal_shortcuts.global.Show Contributors') }} />:
+                  </th>
+                  <td className='text-nowrap'>
+                    <a href="{ t('modal_shortcuts.global.konami_code_url') }" target="_blank">
+                      {t('modal_shortcuts.global.Konami Code')}
+                    </a>
+                    <br />
+                    <span className="key key-small">&uarr;</span>&nbsp;<span className="key key-small">&uarr;</span>
+                    <span className="key key-small">&darr;</span>&nbsp;<span className="key key-small">&darr;</span>
+                    <br />
+                    <span className="key key-small">&larr;</span>&nbsp;<span className="key key-small">&rarr;</span>
+                    <span className="key key-small">&larr;</span>&nbsp;<span className="key key-small">&rarr;</span>
+                    <br />
+                    <span className="key key-small">B</span>&nbsp;<span className="key key-small">A</span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.global.MirrorMode')}:</th>
+                  <td className='text-nowrap'>
+                    <a href="{ t('modal_shortcuts.global.konami_code_url') }" target="_blank">
+                      {t('modal_shortcuts.global.Konami Code')}
+                    </a>
+                    <br />
+                    <span className="key key-small">X</span>&nbsp;<span className="key key-small">X</span>
+                    <span className="key key-small">B</span>&nbsp;<span className="key key-small">B</span>
+                    <br />
+                    <span className="key key-small">A</span>&nbsp;<span className="key key-small">Y</span>
+                    <span className="key key-small">A</span>&nbsp;<span className="key key-small">Y</span>
+                    <br />
+                    <span className="key key-small">&darr;</span>&nbsp;<span className="key key-small">&larr;</span>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+
+          <div className="col-lg-6">
+            <h3>
+              <strong>{t('modal_shortcuts.editor.title')}</strong>
+            </h3>
+            <table className="table">
+              <tbody>
+                <tr>
+                  <th>{t('modal_shortcuts.editor.Indent')}:</th>
+                  <td>
+                    <span className="key key-longer">Tab</span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.editor.Outdent')}:</th>
+                  <td className="text-nowrap">
+                    <span className="key key-long">Shift</span> + <span className="key key-longer">Tab</span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.editor.Save Page')}:</th>
+                  <td>
+                    <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">S</span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.editor.Delete Line')}:</th>
+                  <td>
+                    <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">D</span>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+
+            <h3>
+              <strong>{t('modal_shortcuts.commentform.title')}</strong>
+            </h3>
+
+            <table className="table">
+              <tbody>
+                <tr>
+                  <th>{t('modal_shortcuts.commentform.Post')}:</th>
+                  <td className="text-nowrap">
+                    <span className={`key cmd-key ${additionalClassByOs}`}></span> +
+                    <span className="key key-longer">
+                      <KeyboardReturnEnterIcon />
+                    </span>
+                  </td>
+                </tr>
+                <tr>
+                  <th>{t('modal_shortcuts.editor.Delete Line')}:</th>
+                  <td>
+                    <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">D</span>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    );
+  };
 
   return (
     <>
@@ -27,138 +168,7 @@ const ShortcutsModal = (): JSX.Element => {
             {t('Shortcuts')}
           </ModalHeader>
           <ModalBody>
-            <div className="container">
-              <div className="row">
-                <div className="col-lg-6">
-                  <h3>
-                    <strong>{t('modal_shortcuts.global.title')}</strong>
-                  </h3>
-
-                  <table className="table">
-                    <tbody>
-                      <tr>
-                        <th>
-                          {/* eslint-disable-next-line react/no-danger */}
-                          <span dangerouslySetInnerHTML={{ __html: t('modal_shortcuts.global.Open/Close shortcut help') }} />:
-                        </th>
-                        <td>
-                          <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">/</span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.global.Create Page')}:</th>
-                        <td>
-                          <span className="key">C</span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.global.Edit Page')}:</th>
-                        <td>
-                          <span className="key">E</span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.global.Search')}:</th>
-                        <td><span className="key">/</span></td>
-                      </tr>
-                      <tr>
-                        <th>
-                          {/* eslint-disable-next-line react/no-danger */}
-                          <span dangerouslySetInnerHTML={{ __html: t('modal_shortcuts.global.Show Contributors') }} />:
-                        </th>
-                        <td className='text-nowrap'>
-                          <a href="{ t('modal_shortcuts.global.konami_code_url') }" target="_blank">
-                            {t('modal_shortcuts.global.Konami Code')}
-                          </a>
-                          <br />
-                          <span className="key key-small">&uarr;</span>&nbsp;<span className="key key-small">&uarr;</span>
-                          <span className="key key-small">&darr;</span>&nbsp;<span className="key key-small">&darr;</span>
-                          <br />
-                          <span className="key key-small">&larr;</span>&nbsp;<span className="key key-small">&rarr;</span>
-                          <span className="key key-small">&larr;</span>&nbsp;<span className="key key-small">&rarr;</span>
-                          <br />
-                          <span className="key key-small">B</span>&nbsp;<span className="key key-small">A</span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.global.MirrorMode')}:</th>
-                        <td className='text-nowrap'>
-                          <a href="{ t('modal_shortcuts.global.konami_code_url') }" target="_blank">
-                            {t('modal_shortcuts.global.Konami Code')}
-                          </a>
-                          <br />
-                          <span className="key key-small">X</span>&nbsp;<span className="key key-small">X</span>
-                          <span className="key key-small">B</span>&nbsp;<span className="key key-small">B</span>
-                          <br />
-                          <span className="key key-small">A</span>&nbsp;<span className="key key-small">Y</span>
-                          <span className="key key-small">A</span>&nbsp;<span className="key key-small">Y</span>
-                          <br />
-                          <span className="key key-small">&darr;</span>&nbsp;<span className="key key-small">&larr;</span>
-                        </td>
-                      </tr>
-                    </tbody>
-                  </table>
-                </div>
-
-                <div className="col-lg-6">
-                  <h3>
-                    <strong>{t('modal_shortcuts.editor.title')}</strong>
-                  </h3>
-                  <table className="table">
-                    <tbody>
-                      <tr>
-                        <th>{t('modal_shortcuts.editor.Indent')}:</th>
-                        <td>
-                          <span className="key key-longer">Tab</span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.editor.Outdent')}:</th>
-                        <td className="text-nowrap">
-                          <span className="key key-long">Shift</span> + <span className="key key-longer">Tab</span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.editor.Save Page')}:</th>
-                        <td>
-                          <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">S</span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.editor.Delete Line')}:</th>
-                        <td>
-                          <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">D</span>
-                        </td>
-                      </tr>
-                    </tbody>
-                  </table>
-
-                  <h3>
-                    <strong>{t('modal_shortcuts.commentform.title')}</strong>
-                  </h3>
-
-                  <table className="table">
-                    <tbody>
-                      <tr>
-                        <th>{t('modal_shortcuts.commentform.Post')}:</th>
-                        <td className="text-nowrap">
-                          <span className={`key cmd-key ${additionalClassByOs}`}></span> +
-                          <span className="key key-longer">
-                            <KeyboardReturnEnterIcon />
-                          </span>
-                        </td>
-                      </tr>
-                      <tr>
-                        <th>{t('modal_shortcuts.editor.Delete Line')}:</th>
-                        <td>
-                          <span className={`key cmd-key ${additionalClassByOs}`}></span> + <span className="key">D</span>
-                        </td>
-                      </tr>
-                    </tbody>
-                  </table>
-                </div>
-              </div>
-            </div>
+            {bodyContent()}
           </ModalBody>
         </Modal>
       ) }

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

@@ -71,6 +71,11 @@ import {
 // import { useCurrentPageSWR } from '../stores/page';
 
 
+const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
+const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
+const UnsavedAlertDialog = dynamic(() => import('./UnsavedAlertDialog'), { ssr: false });
+const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
+
 const logger = loggerFactory('growi:pages:all');
 
 const {
@@ -168,11 +173,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // const { t } = useTranslation();
   const router = useRouter();
 
-  const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
-  const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
-  const UnsavedAlertDialog = dynamic(() => import('./UnsavedAlertDialog'), { ssr: false });
-  const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
-
   const { data: currentUser } = useCurrentUser(props.currentUser ?? null);
 
   // register global EventEmitter