Explorar o código

Merge branch 'support/apply-nextjs-2' into feat/integrate-next-show-sidebar

yohei0125 %!s(int64=3) %!d(string=hai) anos
pai
achega
dfd2bd80ea
Modificáronse 31 ficheiros con 243 adicións e 157 borrados
  1. 12 0
      .eslintrc.js
  2. 46 22
      .vscode/launch.json
  3. 20 3
      packages/app/next.config.js
  4. 1 1
      packages/app/package.json
  5. 2 0
      packages/app/src/components/Common/Dropdown/PageItemControl.tsx
  6. 2 0
      packages/app/src/components/ForbiddenPage.tsx
  7. 4 1
      packages/app/src/components/Hotkeys/Subscribers/CreatePage.jsx
  8. 2 0
      packages/app/src/components/Icons/GrowiLogo.jsx
  9. 1 1
      packages/app/src/components/Navbar/GlobalSearch.module.scss
  10. 2 2
      packages/app/src/components/Navbar/GlobalSearch.tsx
  11. 41 35
      packages/app/src/components/Navbar/GrowiNavbar.module.scss
  12. 3 3
      packages/app/src/components/Navbar/GrowiNavbar.tsx
  13. 4 2
      packages/app/src/components/Navbar/GrowiNavbarBottom.module.scss
  14. 2 2
      packages/app/src/components/Navbar/GrowiNavbarBottom.tsx
  15. 1 1
      packages/app/src/components/SearchTypeahead.module.scss
  16. 3 3
      packages/app/src/components/SearchTypeahead.tsx
  17. 1 1
      packages/app/src/components/ShortcutsModal.module.scss
  18. 2 2
      packages/app/src/components/ShortcutsModal.tsx
  19. 16 9
      packages/app/src/components/Sidebar.module.scss
  20. 49 46
      packages/app/src/components/Sidebar.tsx
  21. 1 1
      packages/app/src/components/Sidebar/SidebarNav.module.scss
  22. 3 2
      packages/app/src/components/Sidebar/SidebarNav.tsx
  23. 0 0
      packages/app/src/components/SystemVersion.module.scss
  24. 2 2
      packages/app/src/components/SystemVersion.tsx
  25. 0 4
      packages/app/src/server/crowi/express-init.js
  26. 9 7
      packages/app/src/server/routes/index.js
  27. 6 2
      packages/app/src/styles/_layout.scss
  28. 2 0
      packages/app/src/styles/_variables.scss
  29. 0 1
      packages/app/src/styles/bootstrap/_variables.scss
  30. 2 2
      packages/app/src/styles/style-next.scss
  31. 4 2
      packages/app/src/utils/next.config.utils.js

+ 12 - 0
.eslintrc.js

@@ -35,6 +35,18 @@ module.exports = {
             group: 'parent',
             position: 'before',
           },
+          {
+            pattern: '*.css',
+            group: 'type',
+            patternOptions: { matchBase: true },
+            position: 'after',
+          },
+          {
+            pattern: '*.scss',
+            group: 'type',
+            patternOptions: { matchBase: true },
+            position: 'after',
+          },
         ],
         alphabetize: {
           order: 'asc',

+ 46 - 22
.vscode/launch.json

@@ -1,37 +1,57 @@
 {
-    // IntelliSense を使用して利用可能な属性を学べます。
-    // 既存の属性の説明をホバーして表示します。
-    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
     "version": "0.2.0",
     "configurations": [
       {
-        "type": "node",
+        "type": "pwa-node",
         "request": "attach",
         "name": "Debug: Attach Debugger to Server",
-        "port": 9229
+        "port": 9229,
+        "cwd": "${workspaceFolder}/packages/app",
+        "sourceMapPathOverrides": {
+          "webpack://@growi/app/*": "${workspaceFolder}/packages/app/*"
+        }
       },
       {
-        "type": "node",
+        "type": "pwa-node",
+        "request": "launch",
+        "name": "Debug: Current File",
+        "skipFiles": [
+          "<node_internals>/**"
+        ],
+        "console": "integratedTerminal",
+        "cwd": "${fileDirname}",
+        "runtimeExecutable": "yarn",
+        "runtimeArgs": [
+          "ts-node",
+          "${file}"
+        ]
+      },
+      {
+        "type": "pwa-node",
         "request": "launch",
         "name": "Debug: Server",
         "cwd": "${workspaceFolder}/packages/app",
-        "runtimeExecutable": "npm",
+        "runtimeExecutable": "yarn",
         "runtimeArgs": [
-          "run",
-          "dev:server"
+          "dev"
+        ],
+        "skipFiles": [
+          "<node_internals>/**"
         ],
-        "port": 9229,
         "restart": true,
         "console": "integratedTerminal",
-        "internalConsoleOptions": "neverOpen"
+        "internalConsoleOptions": "neverOpen",
+        "sourceMapPathOverrides": {
+          "webpack://@growi/app/*": "${workspaceFolder}/packages/app/*"
+        }
       },
       {
-        "type": "chrome",
+        "type": "pwa-chrome",
         "request": "launch",
         "name": "Debug: Chrome",
         "sourceMaps": true,
         "sourceMapPathOverrides": {
-          "webpack:///*": "${workspaceFolder}/packages/app/*"
+          "webpack://_N_E/*": "${workspaceFolder}/packages/app/*"
         },
         "webRoot": "${workspaceFolder}/packages/app/public",
         "url": "http://localhost:3000"
@@ -41,32 +61,36 @@
         "request": "launch",
         "name": "Debug: Firefox",
         "reAttach": true,
-        "url": "http://localhost:3000",
         "webRoot": "${workspaceFolder}/packages/app/public",
+        "url": "http://localhost:3000",
         "pathMappings": [
           {
-            "url": "webpack:///core",
+            "url": "webpack://_n_e/src",
+            "path": "${workspaceFolder}/packages/app/src"
+          },
+          {
+            "url": "webpack://_n_e/core",
             "path": "${workspaceFolder}/packages/core"
           },
           {
-            "url": "webpack:///plugin-attachment-refs",
+            "url": "webpack://_n_e/plugin-attachment-refs",
             "path": "${workspaceFolder}/packages/plugin-attachment-refs"
           },
           {
-            "url": "webpack:///plugin-pukiwiki-like-linker",
+            "url": "webpack://_n_e/plugin-pukiwiki-like-linker",
             "path": "${workspaceFolder}/packages/plugin-pukiwiki-like-linker"
           },
           {
-            "url": "webpack:///plugin-lsx",
+            "url": "webpack://_n_e/plugin-lsx",
             "path": "${workspaceFolder}/packages/plugin-lsx"
           },
           {
-            "url": "webpack:///ui",
-            "path": "${workspaceFolder}/packages/ui"
+            "url": "webpack://_n_e/slack",
+            "path": "${workspaceFolder}/packages/app/slack"
           },
           {
-            "url": "webpack:///src",
-            "path": "${workspaceFolder}/packages/app/src"
+            "url": "webpack://_n_e/ui",
+            "path": "${workspaceFolder}/packages/ui"
           },
           {
             "url": "http://localhost:3000",

+ 20 - 3
packages/app/next.config.js

@@ -1,12 +1,29 @@
+import eazyLogger from 'eazy-logger';
 import { I18NextHMRPlugin } from 'i18next-hmr/plugin';
 import { WebpackManifestPlugin } from 'webpack-manifest-plugin';
 
 import { i18n, localePath } from './src/next-i18next.config';
 import { listScopedPackages } from './src/utils/next.config.utils';
 
-// define transpiled packages for '@growi/*'
-const scopedPackages = listScopedPackages(['@growi']);
-const withTM = require('next-transpile-modules')(scopedPackages);
+
+// setup logger
+const logger = eazyLogger.Logger({
+  prefix: '[{green:next.config.js}] ',
+  useLevelPrefixes: false,
+});
+
+
+const setupWithTM = () => {
+  // define transpiled packages for '@growi/*'
+  const scopedPackages = listScopedPackages(['@growi'], { ignorePackageNames: '@growi/app' });
+
+  logger.info('{bold:Listing scoped packages for transpiling:}');
+  logger.unprefixed('info', `{grey:${JSON.stringify(scopedPackages, null, 2)}}`);
+
+  return require('next-transpile-modules')(scopedPackages);
+};
+const withTM = setupWithTM();
+
 
 // define additional entries
 const additionalWebpackEntries = {

+ 1 - 1
packages/app/package.json

@@ -19,7 +19,7 @@
     "preserver": "yarn cross-env NODE_ENV=production yarn migrate",
     "migrate": "node -r dotenv-flow/config node_modules/.bin/migrate-mongo up",
     "//// for development": "",
-    "dev": "yarn cross-env NODE_ENV=development ts-node-dev -r tsconfig-paths/register -r dotenv-flow/config --transpile-only src/server/app.ts",
+    "dev": "yarn cross-env NODE_ENV=development ts-node-dev -r tsconfig-paths/register -r dotenv-flow/config --inspect --transpile-only src/server/app.ts",
     "predev": "yarn cross-env NODE_ENV=development run-p resources:* dev:migrate:up",
     "dev:migrate-mongo": "yarn cross-env NODE_ENV=development yarn ts-node node_modules/.bin/migrate-mongo",
     "dev:migrate": "yarn dev:migrate:up",

+ 2 - 0
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -239,6 +239,8 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
   );
 });
 
+PageItemControlDropdownMenu.displayName = 'PageItemControl';
+
 
 type PageItemControlSubstanceProps = CommonProps & {
   pageId: string,

+ 2 - 0
packages/app/src/components/ForbiddenPage.tsx

@@ -55,4 +55,6 @@ const ForbiddenPage = React.memo((props: Props): JSX.Element => {
   );
 });
 
+ForbiddenPage.displayName = 'ForbiddenPage';
+
 export default ForbiddenPage;

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

@@ -1,8 +1,9 @@
 import React, { useEffect } from 'react';
+
 import PropTypes from 'prop-types';
 
-import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/context';
+import { usePageCreateModal } from '~/stores/modal';
 
 const CreatePage = React.memo((props) => {
 
@@ -28,4 +29,6 @@ CreatePage.getHotkeyStrokes = () => {
   return [['c']];
 };
 
+CreatePage.displayName = 'CreatePage';
+
 export default CreatePage;

+ 2 - 0
packages/app/src/components/Icons/GrowiLogo.jsx

@@ -31,4 +31,6 @@ const GrowiLogo = memo(() => (
   </svg>
 ));
 
+GrowiLogo.displayName = 'GrowiLogo';
+
 export default GrowiLogo;

+ 1 - 1
packages/app/src/components/Navbar/GlobalSearch.scss → packages/app/src/components/Navbar/GlobalSearch.module.scss

@@ -1,7 +1,7 @@
 @use '~/styles/bootstrap/init' as bs;
 
 // input styles
-.grw-global-search {
+.grw-global-search :global {
   .dropdown-toggle {
     min-width: 95px;
     padding-left: 1.5rem;

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

@@ -15,7 +15,7 @@ import { useGlobalSearchFormRef } from '~/stores/ui';
 import SearchForm from '../SearchForm';
 
 
-import './GlobalSearch.scss';
+import styles from './GlobalSearch.module.scss';
 
 
 type Props = {
@@ -76,7 +76,7 @@ export const GlobalSearch = (props: Props): JSX.Element => {
   }
 
   return (
-    <div className={`form-group mb-0 d-print-none ${isSearchServiceReachable ? '' : 'has-error'}`}>
+    <div className={`grw-global-search ${styles['grw-global-search']} form-group mb-0 d-print-none ${isSearchServiceReachable ? '' : 'has-error'}`}>
       <div className="input-group flex-nowrap">
         <div className={`input-group-prepend ${dropup ? 'dropup' : ''}`}>
           <button className="btn btn-secondary dropdown-toggle py-0" type="button" data-toggle="dropdown" aria-haspopup="true">

+ 41 - 35
packages/app/src/components/Navbar/GrowiNavbar.scss → packages/app/src/components/Navbar/GrowiNavbar.module.scss

@@ -2,19 +2,23 @@
 @use '~/styles/bootstrap/init' as bs;
 @use '~/styles/mixins';
 
-.grw-logo {
-  svg {
-    width: var.$grw-logo-width;
-    height: var.$grw-navbar-height;
-    padding: (var.$grw-logo-width - var.$grw-logomark-width) / 2;
+.grw-navbar :global {
+
+  .grw-logo {
+    svg {
+      width: var.$grw-logo-width;
+      height: var.$grw-navbar-height;
+      padding: (var.$grw-logo-width - var.$grw-logomark-width) / 2;
+    }
+  }
+
+  .confidential {
+    font-weight: bold;
   }
-}
 
-.confidential {
-  font-weight: bold;
 }
 
-.grw-navbar {
+.grw-navbar :global {
   top: #{-1 * var.$grw-navbar-height} !important;
 
   z-index: var.$grw-navbar-z-index !important;
@@ -89,36 +93,38 @@
 }
 
 // layout for GlobalSearch
-.grw-global-search-top {
-  // centering on navbar
-  top: var.$grw-navbar-height / 2;
-  left: 50vw;
-  z-index: bs.$zindex-fixed + 1;
-  transform: translate(-50%, -50%);
-
-  .rbt-input.form-control {
-    width: 200px;
-    transition: 0.3s ease-out;
-
-    // focus
-    &.focus {
-      width: 300px;
-    }
+.grw-navbar :global {
+  .grw-global-search-container {
+    // centering on navbar
+    top: var.$grw-navbar-height / 2;
+    left: 50vw;
+    z-index: bs.$zindex-fixed + 1;
+    transform: translate(-50%, -50%);
+
+    .rbt-input.form-control {
+      width: 200px;
+      transition: 0.3s ease-out;
 
-    @include bs.media-breakpoint-up(md) {
-      width: 300px;
-    }
-    @include bs.media-breakpoint-up(lg) {
       // focus
       &.focus {
-        width: 400px;
+        width: 300px;
       }
-    }
-    @include bs.media-breakpoint-up(xl) {
-      width: 350px;
-      // focus
-      &.focus {
-        width: 450px;
+
+      @include bs.media-breakpoint-up(md) {
+        width: 300px;
+      }
+      @include bs.media-breakpoint-up(lg) {
+        // focus
+        &.focus {
+          width: 400px;
+        }
+      }
+      @include bs.media-breakpoint-up(xl) {
+        width: 350px;
+        // focus
+        &.focus {
+          width: 450px;
+        }
       }
     }
   }

+ 3 - 3
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -19,7 +19,7 @@ import GrowiLogo from '../Icons/GrowiLogo';
 
 import PersonalDropdown from './PersonalDropdown';
 
-import './GrowiNavbar.scss';
+import styles from './GrowiNavbar.module.scss';
 
 
 const ShowSkeltonInSSR = memo(({ children }: HasChildren): JSX.Element => {
@@ -138,7 +138,7 @@ export const GrowiNavbar = (): JSX.Element => {
   const { data: isSearchPage } = useIsSearchPage();
 
   return (
-    <nav id="grw-navbar" className="navbar grw-navbar navbar-expand navbar-dark sticky-top mb-0 px-0">
+    <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
       {/* Brand Logo  */}
       <div className="navbar-brand mr-0">
         <a className="grw-logo d-block" href="/">
@@ -158,7 +158,7 @@ export const GrowiNavbar = (): JSX.Element => {
       </ul>
 
       { isSearchServiceConfigured && !isDeviceSmallerThanMd && !isSearchPage && (
-        <div className="grw-global-search grw-global-search-top position-absolute">
+        <div className="grw-global-search-container position-absolute">
           <GlobalSearch />
         </div>
       ) }

+ 4 - 2
packages/app/src/components/Navbar/GrowiNavbarBottom.scss → packages/app/src/components/Navbar/GrowiNavbarBottom.module.scss

@@ -1,14 +1,16 @@
 @use '~/styles/variables' as var;
 @use '~/styles/mixins';
 
-.grw-navbar-bottom {
+.grw-navbar-bottom :global {
   height: var.$grw-navbar-bottom-height;
 
   // apply transition
   transition-property: bottom;
   @include mixins.apply-navigation-transition();
+}
 
-  &.grw-navbar-bottom-drawer-opened {
+.grw-navbar-bottom {
+  &:global(.grw-navbar-bottom-drawer-opened) {
     bottom: #{-1 * var.$grw-navbar-bottom-height};
   }
 }

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

@@ -6,7 +6,7 @@ import { useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
 
 import { GlobalSearch } from './GlobalSearch';
 
-import './GrowiNavbarBottom.scss';
+import styles from './GrowiNavbarBottom.module.scss';
 
 
 export const GrowiNavbarBottom = (): JSX.Element => {
@@ -17,7 +17,7 @@ export const GrowiNavbarBottom = (): JSX.Element => {
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: isSearchPage } = useIsSearchPage();
 
-  const additionalClasses = ['grw-navbar-bottom'];
+  const additionalClasses = ['grw-navbar-bottom', styles['grw-navbar-bottom']];
   if (isDrawerOpened) {
     additionalClasses.push('grw-navbar-bottom-drawer-opened');
   }

+ 1 - 1
packages/app/src/components/SearchTypeahead.scss → packages/app/src/components/SearchTypeahead.module.scss

@@ -1,6 +1,6 @@
 @use '~/styles/bootstrap/init' as bs;
 
-.search-typeahead {
+.search-typeahead :global {
   position: relative;
   width: 100%;
   // corner radius

+ 3 - 3
packages/app/src/components/SearchTypeahead.tsx

@@ -13,7 +13,7 @@ import { IPageSearchMeta } from '~/interfaces/search';
 import { useSWRxSearch } from '~/stores/search';
 
 
-import './SearchTypeahead.scss';
+import styles from './SearchTypeahead.module.scss';
 
 
 type ResetFormButtonProps = {
@@ -208,11 +208,11 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
     );
   }, [disableIncrementalSearch, helpElement, input, isForcused]);
 
-  const isLoading = searchResult == null && searchError == null;
+  const isLoading = searchResult !== undefined && searchError == null;
   const isOpenAlways = helpElement != null;
 
   return (
-    <div className="search-typeahead">
+    <div className={`search-typeahead ${styles['search-typeahead']}`}>
       <AsyncTypeahead
         {...props}
         id="search-typeahead-asynctypeahead"

+ 1 - 1
packages/app/src/components/ShortcutsModal.scss → packages/app/src/components/ShortcutsModal.module.scss

@@ -1,6 +1,6 @@
 @use '~/styles/bootstrap/init' as bs;
 
-#shortcuts-modal {
+.shortcuts-modal :global {
   h3 {
     margin-bottom: 1em;
   }

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

@@ -6,7 +6,7 @@ import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 import KeyboardReturnEnterIcon from '~/components/Icons/KeyboardReturnEnterIcon';
 import { useShortcutsModal } from '~/stores/modal';
 
-import './ShortcutsModal.scss';
+import styles from './ShortcutsModal.module.scss';
 
 
 const ShortcutsModal = (): JSX.Element => {
@@ -22,7 +22,7 @@ const ShortcutsModal = (): JSX.Element => {
   return (
     <>
       { status != null && (
-        <Modal id="shortcuts-modal" size="lg" isOpen={status.isOpened} toggle={close} className="grw-create-page">
+        <Modal id="shortcuts-modal" size="lg" isOpen={status.isOpened} toggle={close} className={`shortcuts-modal ${styles['shortcuts-modal']}`}>
           <ModalHeader tag="h4" toggle={close} className="bg-primary text-light">
             {t('Shortcuts')}
           </ModalHeader>

+ 16 - 9
packages/app/src/components/Sidebar.scss → packages/app/src/components/Sidebar.module.scss

@@ -2,7 +2,7 @@
 @use '~/styles/mixins';
 @use '~/styles/bootstrap/init' as bs;
 
-.grw-sidebar {
+.grw-sidebar :global {
   // sticky
   position: sticky;
   top: var.$grw-navbar-border-width;
@@ -53,7 +53,8 @@
           animation-duration: 0.22s;
           animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
           animation-fill-mode: forwards;
-          .grw-contextual-navigation-sub {
+
+          :global .grw-contextual-navigation-sub {
             box-sizing: border-box;
             display: flex;
             flex-direction: column;
@@ -239,20 +240,26 @@
   }
 }
 
-.grw-sidebar {
-  @include bs.media-breakpoint-down(sm) {
+// '&' could not be set after :global
+// workaround from https://github.com/css-modules/css-modules/issues/295#issuecomment-404873976
+.grw-sidebar :global {
+  .grw-sidebar-drawer {
     @include drawer();
   }
-  @include bs.media-breakpoint-up(md) {
-    &.grw-sidebar-drawer {
+  .grw-sidebar-dock {
+    @include bs.media-breakpoint-down(sm) {
       @include drawer();
     }
-    &:not(.grw-sidebar-drawer) {
+    @include bs.media-breakpoint-up(md) {
       @include dock();
     }
   }
 }
 
-.grw-sidebar-backdrop.modal-backdrop {
-  z-index: bs.$zindex-fixed + 1;
+// '&' could not be set after :global
+// workaround from https://github.com/css-modules/css-modules/issues/295#issuecomment-952885628
+.grw-sidebar-backdrop {
+  &:global(.modal-backdrop) {
+    z-index: bs.$zindex-fixed + 1;
+  }
 }

+ 49 - 46
packages/app/src/components/Sidebar.tsx

@@ -18,7 +18,8 @@ import SidebarContents from './Sidebar/SidebarContents';
 import SidebarNav from './Sidebar/SidebarNav';
 import { StickyStretchableScroller } from './StickyStretchableScroller';
 
-import './Sidebar.scss';
+import styles from './Sidebar.module.scss';
+
 
 const sidebarMinWidth = 240;
 const sidebarMinimizeWidth = 20;
@@ -291,61 +292,63 @@ const Sidebar = (): JSX.Element => {
 
   return (
     <>
-      <div className={`grw-sidebar d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
-        <div className="data-layout-container">
-          <div
-            className={`navigation ${isTransitionEnabled ? 'transition-enabled' : ''}`}
-            onMouseEnter={hoverOnHandler}
-            onMouseLeave={hoverOutHandler}
-          >
-            <div className="grw-navigation-wrap">
-              <div className="grw-global-navigation">
-                {/* <GlobalNavigation></GlobalNavigation> */}
-              </div>
-              <div
-                ref={resizableContainer}
-                className="grw-contextual-navigation"
-                onMouseEnter={hoverOnResizableContainerHandler}
-                onMouseLeave={hoverOutResizableContainerHandler}
-                style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
-              >
-                <div className="grw-contextual-navigation-child">
-                  <div role="group" data-testid="grw-contextual-navigation-sub" className={`grw-contextual-navigation-sub ${showContents ? '' : 'd-none'}`}>
-                    {/* <SidebarContentsWrapper></SidebarContentsWrapper> */}
-                  </div>
+      <div className={`grw-sidebar ${styles['grw-sidebar']}`}>
+        <div className={`d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : 'grw-sidebar-dock'} ${isDrawerOpened ? 'open' : ''}`}>
+          <div className="data-layout-container">
+            <div
+              className={`navigation ${isTransitionEnabled ? 'transition-enabled' : ''}`}
+              onMouseEnter={hoverOnHandler}
+              onMouseLeave={hoverOutHandler}
+            >
+              <div className="grw-navigation-wrap">
+                <div className="grw-global-navigation">
+                  {/* <GlobalNavigation></GlobalNavigation> */}
                 </div>
-              </div>
-            </div>
-            <div className="grw-navigation-draggable">
-              { isResizableByDrag && (
                 <div
-                  className="grw-navigation-draggable-hitarea"
-                  onMouseDown={dragableAreaMouseDownHandler}
+                  ref={resizableContainer}
+                  className="grw-contextual-navigation"
+                  onMouseEnter={hoverOnResizableContainerHandler}
+                  onMouseLeave={hoverOutResizableContainerHandler}
+                  style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
                 >
-                  <div className="grw-navigation-draggable-hitarea-child"></div>
+                  <div className="grw-contextual-navigation-child">
+                    <div role="group" data-testid="grw-contextual-navigation-sub" className={`grw-contextual-navigation-sub ${showContents ? '' : 'd-none'}`}>
+                      {/* <SidebarContentsWrapper></SidebarContentsWrapper> */}
+                    </div>
+                  </div>
                 </div>
-              ) }
-              <button
-                data-testid="grw-navigation-resize-button"
-                className={`grw-navigation-resize-button ${!isDrawerMode ? 'resizable' : ''} ${isCollapsed ? 'collapsed' : ''} `}
-                type="button"
-                aria-expanded="true"
-                aria-label="Toggle navigation"
-                disabled={isDrawerMode}
-                onClick={toggleNavigationBtnClickHandler}
-              >
-                <span className="hexagon-container" role="presentation">
-                  {/* <NavigationResizeHexagon /> */}
-                </span>
-                <span className="hitarea" role="presentation"></span>
-              </button>
+              </div>
+              <div className="grw-navigation-draggable">
+                { isResizableByDrag && (
+                  <div
+                    className="grw-navigation-draggable-hitarea"
+                    onMouseDown={dragableAreaMouseDownHandler}
+                  >
+                    <div className="grw-navigation-draggable-hitarea-child"></div>
+                  </div>
+                ) }
+                <button
+                  data-testid="grw-navigation-resize-button"
+                  className={`grw-navigation-resize-button ${!isDrawerMode ? 'resizable' : ''} ${isCollapsed ? 'collapsed' : ''} `}
+                  type="button"
+                  aria-expanded="true"
+                  aria-label="Toggle navigation"
+                  disabled={isDrawerMode}
+                  onClick={toggleNavigationBtnClickHandler}
+                >
+                  <span className="hexagon-container" role="presentation">
+                    {/* <NavigationResizeHexagon /> */}
+                  </span>
+                  <span className="hitarea" role="presentation"></span>
+                </button>
+              </div>
             </div>
           </div>
         </div>
       </div>
 
       { isDrawerOpened && (
-        <div className="grw-sidebar-backdrop modal-backdrop show" onClick={backdropClickedHandler}></div>
+        <div className={`${styles['grw-sidebar-backdrop']} modal-backdrop show`} onClick={backdropClickedHandler}></div>
       ) }
     </>
   );

+ 1 - 1
packages/app/src/components/Sidebar/SidebarNav.scss → packages/app/src/components/Sidebar/SidebarNav.module.scss

@@ -1,6 +1,6 @@
 @use '~/styles/variables' as var;
 
-.grw-sidebar-nav {
+.grw-sidebar-nav :global {
   $sidebar-nav-button-height: 55px;
 
   %fukidashi-for-active {

+ 3 - 2
packages/app/src/components/Sidebar/SidebarNav.tsx

@@ -5,7 +5,7 @@ import { SidebarContentsType } from '~/interfaces/ui';
 import { useCurrentUser } from '~/stores/context';
 import { useCurrentSidebarContents } from '~/stores/ui';
 
-import './SidebarNav.scss';
+import styles from './SidebarNav.module.scss';
 
 
 type PrimaryItemProps = {
@@ -65,6 +65,7 @@ const SecondaryItem: FC<SecondaryItemProps> = memo((props: SecondaryItemProps) =
     </a>
   );
 });
+SecondaryItem.displayName = 'SecondaryItem';
 
 
 type Props = {
@@ -80,7 +81,7 @@ const SidebarNav: FC<Props> = (props: Props) => {
   const { onItemSelected } = props;
 
   return (
-    <div className="grw-sidebar-nav">
+    <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`}>
       <div className="grw-sidebar-nav-primary-container">
         {/* eslint-disable max-len */}
         <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onItemSelected={onItemSelected} />

+ 0 - 0
packages/app/src/components/SystemVersion.scss → packages/app/src/components/SystemVersion.module.scss


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

@@ -3,7 +3,7 @@ import React from 'react';
 import { useGrowiVersion } from '~/stores/context';
 import { useShortcutsModal } from '~/stores/modal';
 
-import './SystemVersion.scss';
+import styles from './SystemVersion.module.scss';
 
 const SystemVersion = (): JSX.Element => {
 
@@ -17,7 +17,7 @@ const SystemVersion = (): JSX.Element => {
 
   return (
     <>
-      <div className="system-version d-none d-md-flex d-edit-none d-print-none align-items-center">
+      <div className={`${styles['system-version']} d-none d-md-flex d-edit-none d-print-none align-items-center`}>
         <span>
           <a href="https://growi.org">GROWI</a> {growiVersion}
         </span>

+ 0 - 4
packages/app/src/server/crowi/express-init.js

@@ -88,10 +88,6 @@ module.exports = function(crowi, app) {
   const staticOption = (crowi.node_env === 'production') ? { maxAge: '30d' } : {};
   app.use(express.static(crowi.publicDir, staticOption));
   app.engine('html', swig.renderFile);
-  app.use(webpackAssets(
-    path.join(crowi.publicDir, 'manifest.json'),
-    { devMode: (crowi.node_env === 'development') },
-  ));
   // app.set('view cache', false);  // Default: true in production, otherwise undefined. -- 2017.07.04 Yuki Takei
   app.set('view engine', 'html');
   app.set('views', crowi.viewsDir);

+ 9 - 7
packages/app/src/server/routes/index.js

@@ -1,5 +1,5 @@
-import express from 'express';
 import csrf from 'csurf';
+import express from 'express';
 
 import apiV1FormValidator from '../middlewares/apiv1-form-validator';
 import injectResetOrderByTokenMiddleware from '../middlewares/inject-reset-order-by-token-middleware';
@@ -54,7 +54,8 @@ module.exports = function(crowi, app) {
   const comment = require('./comment')(crowi, app);
   const tag = require('./tag')(crowi, app);
   const search = require('./search')(crowi, app);
-  const hackmd = require('./hackmd')(crowi, app);
+  // == TODO: Replace the code in hackmd.js getting the script path from manifest.json
+  // const hackmd = require('./hackmd')(crowi, app);
   const ogp = require('./ogp')(crowi);
 
   const next = nextFactory(crowi);
@@ -223,11 +224,12 @@ module.exports = function(crowi, app) {
   app.get('/trash/$'                  , loginRequired, (req, res) => res.redirect('/trash'));
   app.get('/trash/*/$'                , loginRequired, injectUserUISettings, page.deletedPageListShowWrapper);
 
-  app.get('/_hackmd/load-agent'          , hackmd.loadAgent);
-  app.get('/_hackmd/load-styles'         , hackmd.loadStyles);
-  app.post('/_api/hackmd.integrate'      , accessTokenParser , loginRequiredStrictly , hackmd.validateForApi, hackmd.integrate);
-  app.post('/_api/hackmd.discard'        , accessTokenParser , loginRequiredStrictly , hackmd.validateForApi, hackmd.discard);
-  app.post('/_api/hackmd.saveOnHackmd'   , accessTokenParser , loginRequiredStrictly , hackmd.validateForApi, hackmd.saveOnHackmd);
+  // == TODO: Replace the code in hackmd.js getting the script path from manifest.json
+  // app.get('/_hackmd/load-agent'          , hackmd.loadAgent);
+  // app.get('/_hackmd/load-styles'         , hackmd.loadStyles);
+  // app.post('/_api/hackmd.integrate'      , accessTokenParser , loginRequiredStrictly , hackmd.validateForApi, hackmd.integrate);
+  // app.post('/_api/hackmd.discard'        , accessTokenParser , loginRequiredStrictly , hackmd.validateForApi, hackmd.discard);
+  // app.post('/_api/hackmd.saveOnHackmd'   , accessTokenParser , loginRequiredStrictly , hackmd.validateForApi, hackmd.saveOnHackmd);
 
   app.use('/forgot-password', express.Router()
     .use(forgotPassword.checkForgotPasswordEnabledMiddlewareFactory(crowi))

+ 6 - 2
packages/app/src/styles/_layout.scss

@@ -1,6 +1,10 @@
-@use './variables';
+@use './variables' as var;
 @use './bootstrap/init' as bs;
 
+:root {
+  font-size: var.$font-size-root;
+}
+
 body {
   overflow-y: scroll !important;
   overscroll-behavior-y: none;
@@ -30,7 +34,7 @@ body.growi-layout-fluid .grw-container-convertible {
 
 // padding settings for GrowiNavbarBottom
 .page-wrapper {
-  padding-bottom: variables.$grw-navbar-bottom-height;
+  padding-bottom: var.$grw-navbar-bottom-height;
 
   @include bs.media-breakpoint-up(md) {
     padding-bottom: unset;

+ 2 - 0
packages/app/src/styles/_variables.scss

@@ -1,3 +1,5 @@
+$font-size-root: 14px;
+
 //== GROWI Official Color
 $growi-green: #74bc46;
 $growi-blue: #175fa5;

+ 0 - 1
packages/app/src/styles/bootstrap/_variables.scss

@@ -70,7 +70,6 @@ $font-family-serif: Georgia, 'Times New Roman', Times, serif;
 $font-family-monospace: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
 $font-family-base: $font-family-sans-serif;
 
-$font-size-root: 14px;
 $line-height-base: 1.42857;
 
 $blockquote-small-color: $gray-500;

+ 2 - 2
packages/app/src/styles/style-next.scss

@@ -3,8 +3,8 @@
 // // override codemirror
 // @import 'override-codemirror';
 
-// // override react-bootstrap-typeahead styles
-// @import 'override-rbt';
+@import '~react-bootstrap-typeahead/css/Typeahead';
+@import 'override-rbt';
 
 // // override simplebar-react styles
 // @import 'override-simplebar';

+ 4 - 2
packages/app/src/utils/next.config.utils.js

@@ -6,8 +6,10 @@ const path = require('path');
 const nodeModulesPath = path.resolve(__dirname, '../../../../node_modules');
 
 
+const defaultOpts = { ignorePackageNames: [] };
+
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-export const listScopedPackages = (scopes) => {
+export const listScopedPackages = (scopes, opts = defaultOpts) => {
   const scopedPackages = [];
 
   fs.readdirSync(nodeModulesPath)
@@ -22,7 +24,7 @@ export const listScopedPackages = (scopes) => {
             folderName,
             'package.json',
           ));
-          if (!ignoreTranspileModules) {
+          if (!ignoreTranspileModules && !opts.ignorePackageNames.includes(name)) {
             scopedPackages.push(name);
           }
         });