Browse Source

Merge branch 'master' into feat/sticky-event-features

jam411 3 years ago
parent
commit
4717316bdf
64 changed files with 525 additions and 524 deletions
  1. 14 1
      CHANGELOG.md
  2. 1 1
      lerna.json
  3. 1 1
      package.json
  4. 10 10
      packages/app/package.json
  5. 12 7
      packages/app/src/components/Admin/MarkdownSetting/WhiteListInput.jsx
  6. 4 1
      packages/app/src/components/Admin/MarkdownSetting/XssForm.jsx
  7. 1 1
      packages/app/src/components/CompleteUserRegistration.tsx
  8. 1 1
      packages/app/src/components/CompleteUserRegistrationForm.tsx
  9. 21 48
      packages/app/src/components/IdenticalPathPage.tsx
  10. 1 1
      packages/app/src/components/InstallerForm.tsx
  11. 1 1
      packages/app/src/components/InvitedForm.tsx
  12. 0 27
      packages/app/src/components/Layout/Login.module.scss
  13. 49 0
      packages/app/src/components/Layout/MainPane.tsx
  14. 32 46
      packages/app/src/components/Layout/NoLoginLayout.module.scss
  15. 19 17
      packages/app/src/components/Layout/NoLoginLayout.tsx
  16. 12 0
      packages/app/src/components/LoginForm.module.scss
  17. 37 31
      packages/app/src/components/LoginForm.tsx
  18. 5 8
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  19. 7 73
      packages/app/src/components/Page/DisplaySwitcher.tsx
  20. 1 1
      packages/app/src/components/Page/ShareLinkAlert.tsx
  21. 0 12
      packages/app/src/components/PageContentFooter.tsx
  22. 2 2
      packages/app/src/components/PageEditor/Editor.tsx
  23. 0 0
      packages/app/src/components/PageSideContents.module.scss
  24. 81 0
      packages/app/src/components/PageSideContents.tsx
  25. 2 3
      packages/app/src/components/TableOfContents.tsx
  26. 1 0
      packages/app/src/interfaces/plugin.ts
  27. 0 5
      packages/app/src/interfaces/rehype.ts
  28. 1 4
      packages/app/src/interfaces/services/renderer.ts
  29. 49 49
      packages/app/src/pages/[[...path]].page.tsx
  30. 1 1
      packages/app/src/pages/_private-legacy-pages.page.tsx
  31. 1 1
      packages/app/src/pages/_search.page.tsx
  32. 1 1
      packages/app/src/pages/login/error/[message].page.tsx
  33. 9 0
      packages/app/src/pages/login/index.module.scss
  34. 4 1
      packages/app/src/pages/login/index.page.tsx
  35. 3 3
      packages/app/src/pages/me/[[...path]].page.tsx
  36. 57 78
      packages/app/src/pages/share/[[...path]].page.tsx
  37. 2 2
      packages/app/src/pages/tags.page.tsx
  38. 1 1
      packages/app/src/pages/trash.page.tsx
  39. 1 0
      packages/app/src/server/models/growi-plugin.ts
  40. 9 0
      packages/app/src/server/service/plugin.ts
  41. 8 3
      packages/app/src/stores/context.tsx
  42. 1 1
      packages/app/src/stores/page-redirect.tsx
  43. 1 1
      packages/app/src/stores/ui.tsx
  44. 2 2
      packages/app/src/styles/theme/_apply-colors-dark.scss
  45. 2 2
      packages/app/src/styles/theme/_apply-colors-light.scss
  46. 1 1
      packages/codemirror-textlint/package.json
  47. 1 1
      packages/core/package.json
  48. 1 1
      packages/hackmd/package.json
  49. 1 1
      packages/preset-themes/package.json
  50. 6 7
      packages/preset-themes/src/styles/antarctic.scss
  51. 4 3
      packages/preset-themes/src/styles/christmas.scss
  52. 16 20
      packages/preset-themes/src/styles/hufflepuff.scss
  53. 1 5
      packages/preset-themes/src/styles/island.scss
  54. 0 9
      packages/preset-themes/src/styles/nature.scss
  55. 7 9
      packages/preset-themes/src/styles/spring.scss
  56. 2 2
      packages/preset-themes/src/styles/theme/_apply-colors-dark.scss
  57. 2 2
      packages/preset-themes/src/styles/theme/_apply-colors-light.scss
  58. 4 4
      packages/preset-themes/src/styles/wood.scss
  59. 1 1
      packages/remark-drawio/package.json
  60. 1 1
      packages/remark-growi-directive/package.json
  61. 4 4
      packages/remark-lsx/package.json
  62. 1 1
      packages/slack/package.json
  63. 2 2
      packages/slackbot-proxy/package.json
  64. 2 2
      packages/ui/package.json

+ 14 - 1
CHANGELOG.md

@@ -1,9 +1,22 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v5.1.7...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.0.0...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.0.0](https://github.com/weseek/growi/compare/v5.1.7...v6.0.0) - 2022-12-27
+
+### 💎 Features
+
+- feat: Apply Next.js
+- feat: New plugin (#7034) @yuki-takei
+- feat: Save prevent xss custom settings in new format (#7124) @miya
+
+### 🧰 Maintenance
+
+- support: Request scoped SWR (#6742) @yuki-takei
+- support: Build preset themes within external package (#7057) @yuki-takei
+
 ## [v5.1.7](https://github.com/weseek/growi/compare/v5.1.6...v5.1.7) - 2022-10-26
 
 ### 🐛 Bug Fixes

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
   "npmClient": "yarn",
   "useWorkspaces": true,
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "packages": [
     "packages/*"
   ]

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 10 - 10
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -65,14 +65,14 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^6.0.0-RC.14",
-    "@growi/core": "^6.0.0-RC.14",
-    "@growi/hackmd": "^6.0.0-RC.14",
-    "@growi/preset-themes": "^6.0.0-RC.14",
-    "@growi/remark-drawio": "^6.0.0-RC.14",
-    "@growi/remark-growi-directive": "^6.0.0-RC.14",
-    "@growi/remark-lsx": "^6.0.0-RC.14",
-    "@growi/slack": "^6.0.0-RC.14",
+    "@growi/codemirror-textlint": "^6.0.1-RC.0",
+    "@growi/core": "^6.0.1-RC.0",
+    "@growi/hackmd": "^6.0.1-RC.0",
+    "@growi/preset-themes": "^6.0.1-RC.0",
+    "@growi/remark-drawio": "^6.0.1-RC.0",
+    "@growi/remark-growi-directive": "^6.0.1-RC.0",
+    "@growi/remark-lsx": "^6.0.1-RC.0",
+    "@growi/slack": "^6.0.1-RC.0",
     "@promster/express": "^7.0.2",
     "@promster/server": "^7.0.4",
     "@slack/web-api": "^6.2.4",
@@ -201,7 +201,7 @@
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   "devDependencies": {
-    "@growi/ui": "^6.0.0-RC.14",
+    "@growi/ui": "^6.0.1-RC.0",
     "@handsontable/react": "=2.1.0",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@next/bundle-analyzer": "^12.2.3",

+ 12 - 7
packages/app/src/components/Admin/MarkdownSetting/WhiteListInput.jsx

@@ -2,9 +2,9 @@ import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
+import { defaultSchema as sanitizeDefaultSchema } from 'rehype-sanitize';
 
 import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
-import { tags, attrs } from '~/services/xss/recommended-whitelist';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
@@ -16,18 +16,21 @@ class WhiteListInput extends React.Component {
     this.tagWhiteList = React.createRef();
     this.attrWhiteList = React.createRef();
 
+    this.tags = sanitizeDefaultSchema.tagNames;
+    this.attrs = JSON.stringify(sanitizeDefaultSchema.attributes);
+
     this.onClickRecommendTagButton = this.onClickRecommendTagButton.bind(this);
     this.onClickRecommendAttrButton = this.onClickRecommendAttrButton.bind(this);
   }
 
   onClickRecommendTagButton() {
-    this.tagWhiteList.current.value = tags;
-    this.props.adminMarkDownContainer.setState({ tagWhiteList: tags });
+    // this.tagWhiteList.current.value = this.tags;
+    // this.props.adminMarkDownContainer.setState({ tagWhiteList: this.tags });
   }
 
   onClickRecommendAttrButton() {
-    this.attrWhiteList.current.value = attrs;
-    this.props.adminMarkDownContainer.setState({ attrWhiteList: attrs });
+    // this.attrWhiteList.current.value = this.attrs;
+    // this.props.adminMarkDownContainer.setState({ attrWhiteList: this.attrs });
   }
 
   render() {
@@ -38,11 +41,12 @@ class WhiteListInput extends React.Component {
         <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('markdown_settings.xss_options.tag_names')}
-            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendTagButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0 disabled" onClick={this.onClickRecommendTagButton}>
               {t('markdown_settings.xss_options.import_recommended', { target: 'Tags' })}
             </p>
           </div>
           <textarea
+            disabled
             className="form-control xss-list"
             name="recommendedTags"
             rows="6"
@@ -55,11 +59,12 @@ class WhiteListInput extends React.Component {
         <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('markdown_settings.xss_options.tag_attributes')}
-            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendAttrButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0 disabled" onClick={this.onClickRecommendAttrButton}>
               {t('markdown_settings.xss_options.import_recommended', { target: 'Attrs' })}
             </p>
           </div>
           <textarea
+            disabled
             className="form-control xss-list"
             name="recommendedAttrs"
             rows="6"

+ 4 - 1
packages/app/src/components/Admin/MarkdownSetting/XssForm.jsx

@@ -93,6 +93,7 @@ class XssForm extends React.Component {
           <div className="col-md-6 col-sm-12 align-self-start mb-4">
             <div className="custom-control custom-radio">
               <input
+                disabled
                 type="radio"
                 className="custom-control-input"
                 id="xssOption2"
@@ -101,7 +102,9 @@ class XssForm extends React.Component {
                 onChange={() => { adminMarkDownContainer.setState({ xssOption: RehypeSanitizeOption.CUSTOM }) }}
               />
               <label className="custom-control-label w-100" htmlFor="xssOption2">
-                <p className="font-weight-bold">{t('markdown_settings.xss_options.custom_whitelist')}</p>
+                <p className="font-weight-bold">{t('markdown_settings.xss_options.custom_whitelist')}
+                  <span className='text-warning'> (TBD: Currently unavailable)</span>
+                </p>
                 <WhiteListInput customizable />
               </label>
             </div>

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

@@ -7,7 +7,7 @@ export const CompleteUserRegistration: FC = () => {
   const { t } = useTranslation();
 
   return (
-    <div className="noLogin-dialog mx-auto" id="noLogin-dialog">
+    <div className="nologin-dialog mx-auto" id="nologin-dialog">
       <div className="row mx-0">
         <div className="col-12 mb-3 text-center">
           <p className="alert alert-success">

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

@@ -85,7 +85,7 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
 
   return (
     <>
-      <div className="noLogin-dialog mx-auto" id="noLogin-dialog">
+      <div className="nologin-dialog mx-auto" id="nologin-dialog">
         <div className="row mx-0">
           <div className="col-12">
 

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

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

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

@@ -94,7 +94,7 @@ const InstallerForm = memo((): JSX.Element => {
     : <span><i className="icon-fw icon-ban" />{ t('installer.unavaliable_user_id') }</span>;
 
   return (
-    <div data-testid="installerForm" className={`noLogin-dialog p-3 mx-auto${hasErrorClass}`}>
+    <div data-testid="installerForm" className={`nologin-dialog p-3 mx-auto${hasErrorClass}`}>
       <div className="row">
         <div className="col-md-12">
           <p className="alert alert-success">

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

@@ -84,7 +84,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
   }
 
   return (
-    <div className="noLogin-dialog px-3 pb-3 mx-auto" id="noLogin-dialog">
+    <div className="nologin-dialog px-3 pb-3 mx-auto" id="nologin-dialog">
       { formNotification() }
       <form role="form" onSubmit={submitHandler} id="invited-form">
         {/* Email Form */}

+ 0 - 27
packages/app/src/components/Layout/Login.module.scss

@@ -1,27 +0,0 @@
-@use '~/styles/bootstrap/init' as bs;
-
-
-.login-page {
-  // layout
-  .main .row .login-header,
-  .login-dialog {
-    width: 320px;
-  }
-
-  .link-growi-org {
-    position: absolute;
-    bottom: 9px;
-    z-index: 3;
-  }
-
-  // To adjust the behavior, this problem is not solved.
-  // See https://github.com/AaronCCWong/react-card-flip/issues/56
-  .react-card-front,
-  .react-card-back {
-    height: 0% !important;
-  }
-}
-
-.collapse-external-auth {
-  overflow: hidden;
-}

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

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

+ 32 - 46
packages/app/src/components/Layout/NoLoginLayout.module.scss

@@ -2,67 +2,52 @@
 
 
 .nologin :global {
-  #page-wrapper {
-    background: none;
-  }
+  height: 100vh;
 
   // layout
-  #wrapper {
+  .page-wrapper {
+    display: flex;
+    align-items: center;
     height: 100vh;
+    margin-top: 0px;
+
+    .main {
+      width: 100vw;
 
-    #page-wrapper {
-      display: flex;
-      align-items: center;
-      height: 100vh;
-      margin-top: 0px;
-
-      .main {
-        width: 100vw;
-
-        > .row {
-          margin-right: 20px;
-          margin-left: 20px;
-        }
-
-        .noLogin-header {
-          display: flex;
-          flex-direction: column;
-          align-items: center;
-          padding-top: 30px;
-          padding-bottom: 10px;
-        }
-
-        .noLogin-form-errors {
-          width: 100%;
-
-          .alert {
-            padding: 5px;
-            margin-top: 10px;
-            margin-bottom: 0;
-
-            ul {
-              padding-left: 1.5em;
-            }
-          }
-        }
+      > .row {
+        margin-right: 20px;
+        margin-left: 20px;
       }
 
-      // .main
+      .nologin-header {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        padding-top: 30px;
+        padding-bottom: 10px;
+      }
     }
 
-    // #page-wrapper
-  }
+    .link-growi-org {
+      position: absolute;
+      bottom: 9px;
+      z-index: 3;
+    }
 
-  // #wrapper
+  }
 
   // styles
-  .noLogin-header {
+  .nologin-header {
     h1 {
       font-size: 22px;
       line-height: 1em;
     }
   }
 
+  .alert {
+    padding: 0.5em 1em 0.5em 2em;
+  }
+
   .input-group {
     margin-bottom: 10px;
 
@@ -132,10 +117,11 @@
       transition: color 0.8s;
     }
   }
-  .noLogin-header,
-  .noLogin-dialog {
+  .nologin-header,
+  .nologin-dialog {
     max-width: 480px;
   }
+
 }
 
 .link-switch {

+ 19 - 17
packages/app/src/components/Layout/NoLoginLayout.tsx

@@ -1,5 +1,7 @@
 import React, { ReactNode } from 'react';
 
+import { useAppTitle } from '~/stores/context';
+
 import GrowiLogo from '../Icons/GrowiLogo';
 
 import { RawLayout } from './RawLayout';
@@ -14,30 +16,30 @@ type Props = {
 export const NoLoginLayout = ({
   children, className,
 }: Props): JSX.Element => {
-  const classNames: string[] = ['wrapper'];
+
+  const { data: appTitle } = useAppTitle();
+
+  const classNames: string[] = [''];
   if (className != null) {
     classNames.push(className);
   }
+
   return (
-    <RawLayout className={`${commonStyles.nologin}`}>
-      <div className="nologin">
-        <div id="wrapper">
-          <div id="page-wrapper">
-            <div className="main container-fluid">
-
-              <div className="row">
-
-                <div className="col-md-12">
-                  <div className="noLogin-header mx-auto">
-                    <GrowiLogo />
-                    <h1 className="my-3">GROWI</h1>
-                    <div className="noLogin-form-errors px-3"></div>
-                  </div>
-                  {children}
-                </div>
+    <RawLayout className={`nologin ${commonStyles.nologin} ${classNames}`}>
+      <div className="page-wrapper">
+        <div className="main container-fluid">
+
+          <div className="row">
 
+            <div className="col-md-12">
+              <div className="nologin-header mx-auto">
+                <GrowiLogo />
+                <h1 className="my-3">{ appTitle ?? 'GROWI' }</h1>
+                <div className="noLogin-form-errors px-3"></div>
               </div>
+              {children}
             </div>
+
           </div>
         </div>
       </div>

+ 12 - 0
packages/app/src/components/LoginForm.module.scss

@@ -0,0 +1,12 @@
+.login-form :global {
+  // To adjust the behavior, this problem is not solved.
+  // See https://github.com/AaronCCWong/react-card-flip/issues/56
+  .react-card-front,
+  .react-card-back {
+    height: 0% !important;
+  }
+
+  .collapse-external-auth {
+    overflow: hidden;
+  }
+}

+ 37 - 31
packages/app/src/components/LoginForm.tsx

@@ -16,6 +16,10 @@ import { toArrayIfNot } from '~/utils/array-utils';
 
 import { CompleteUserRegistration } from './CompleteUserRegistration';
 
+
+import styles from './LoginForm.module.scss';
+
+
 type LoginFormProps = {
   username?: string,
   name?: string,
@@ -505,39 +509,41 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
   }
 
   return (
-    <div className="noLogin-dialog mx-auto" id="noLogin-dialog" data-testid="login-form">
-      <div className="row mx-0">
-        <div className="col-12">
-          <ReactCardFlip isFlipped={isRegistering} flipDirection="horizontal" cardZIndex="3">
-            <div className="front">
-              {isLocalOrLdapStrategiesEnabled && renderLocalOrLdapLoginForm()}
-              {isSomeExternalAuthEnabled && renderExternalAuthLoginForm()}
-              {isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
-                <div className="text-right mb-2">
-                  <a href="/forgot-password" className="d-block link-switch">
-                    <i className="icon-key"></i> {t('forgot_password.forgot_password')}
-                  </a>
-                </div>
-              )}
-              {/* Sign up link */}
-              {isRegistrationEnabled && (
-                <div className="text-right mb-2">
-                  <a href="#register" id="register" className="link-switch" onClick={switchForm}>
-                    <i className="ti ti-check-box"></i> {t('Sign up is here')}
-                  </a>
-                </div>
-              )}
-            </div>
-            <div className="back">
-              {/* Register form for /login#register */}
-              {isRegistrationEnabled && renderRegisterForm()}
-            </div>
-          </ReactCardFlip>
+    <div className={`login-form ${styles['login-form']}`}>
+      <div className="nologin-dialog mx-auto" id="nologin-dialog" data-testid="login-form">
+        <div className="row mx-0">
+          <div className="col-12">
+            <ReactCardFlip isFlipped={isRegistering} flipDirection="horizontal" cardZIndex="3">
+              <div className="front">
+                {isLocalOrLdapStrategiesEnabled && renderLocalOrLdapLoginForm()}
+                {isSomeExternalAuthEnabled && renderExternalAuthLoginForm()}
+                {isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
+                  <div className="text-right mb-2">
+                    <a href="/forgot-password" className="d-block link-switch">
+                      <i className="icon-key"></i> {t('forgot_password.forgot_password')}
+                    </a>
+                  </div>
+                )}
+                {/* Sign up link */}
+                {isRegistrationEnabled && (
+                  <div className="text-right mb-2">
+                    <a href="#register" id="register" className="link-switch" onClick={switchForm}>
+                      <i className="ti ti-check-box"></i> {t('Sign up is here')}
+                    </a>
+                  </div>
+                )}
+              </div>
+              <div className="back">
+                {/* Register form for /login#register */}
+                {isRegistrationEnabled && renderRegisterForm()}
+              </div>
+            </ReactCardFlip>
+          </div>
         </div>
+        <a href="https://growi.org" className="link-growi-org pl-3">
+          <span className="growi">GROWI</span>.<span className="org">ORG</span>
+        </a>
       </div>
-      <a href="https://growi.org" className="link-growi-org pl-3">
-        <span className="growi">GROWI</span>.<span className="org">ORG</span>
-      </a>
     </div>
   );
 

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

@@ -15,7 +15,7 @@ import {
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
   useCurrentPageId, useCurrentPathname, useIsNotFound,
-  useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId, useTemplateTagData, useIsContainerFluid,
+  useCurrentUser, useIsGuestUser, useIsSharedUser, useShareLinkId, useTemplateTagData, useIsContainerFluid, useIsIdenticalPath,
 } from '~/stores/context';
 import { usePageTagsForEditors } from '~/stores/editor';
 import {
@@ -201,6 +201,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { data: pageId } = useCurrentPageId();
   const { data: currentUser } = useCurrentUser();
   const { data: isNotFound } = useIsNotFound();
+  const { data: isIdenticalPath } = useIsIdenticalPath();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isContainerFluid } = useIsContainerFluid();
@@ -319,7 +320,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   }, []);
 
 
-  const RightComponent = useCallback(() => {
+  const RightComponent = () => {
     const additionalMenuItemsRenderer = () => {
       if (revisionId == null || pageId == null) {
         return (
@@ -404,14 +405,10 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
         )}
       </>
     );
-  // eslint-disable-next-line max-len
-  }, [isCompactMode, isViewMode, pageId, revisionId, shareLinkId, path, currentPathname, isSharedUser, isAbleToShowPageManagement,
-      duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler, isAbleToShowPageEditorModeManager, isGuestUser,
-      editorMode, isAbleToShowPageAuthors, currentPage, currentUser, isPageTemplateModalShown, isLinkSharingDisabled, templateMenuItemClickHandler,
-      mutateEditorMode, switchContentWidthHandler]);
+  };
 
 
-  const pagePath = isNotFound
+  const pagePath = isIdenticalPath || isNotFound
     ? currentPathname
     : currentPage?.path;
 

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

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

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

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

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

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

+ 2 - 2
packages/app/src/components/PageEditor/Editor.tsx

@@ -261,15 +261,15 @@ const Editor: ForwardRefRenderFunction<IEditorMethods, EditorPropsType> = (props
   }, [isCheatsheetModalShown]);
 
   const isReadyToRenderEditor = editorSettings != null;
-  const editorRef = editorSubstance();
 
   // https://redmine.weseek.co.jp/issues/111731
   useEffect(() => {
+    const editorRef = editorSubstance();
     if (isReadyToRenderEditor && editorRef != null) {
       const editorNavBarItems = editorRef.getNavbarItems() ?? [];
       setNavBarItems(editorNavBarItems);
     }
-  }, [editorRef, isReadyToRenderEditor]);
+  }, [editorSubstance, isReadyToRenderEditor]);
 
   if (!isReadyToRenderEditor) {
     return <></>;

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


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

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

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

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

+ 1 - 0
packages/app/src/interfaces/plugin.ts

@@ -17,6 +17,7 @@ export type GrowiPluginOrigin = {
 export type GrowiPlugin<M extends GrowiPluginMeta = GrowiPluginMeta> = {
   isEnabled: boolean,
   installedPath: string,
+  organizationName: string,
   origin: GrowiPluginOrigin,
   meta: M,
 }

+ 0 - 5
packages/app/src/interfaces/rehype.ts

@@ -4,8 +4,3 @@ export const RehypeSanitizeOption = {
 } as const;
 
 export type RehypeSanitizeOption = typeof RehypeSanitizeOption[keyof typeof RehypeSanitizeOption];
-
-export type RehypeSanitizeOptionConfig = {
-  isEnabledXssPrevention: boolean,
-  // Todo add types for custom sanitize option at https://redmine.weseek.co.jp/issues/109763
-}

+ 1 - 4
packages/app/src/interfaces/services/renderer.ts

@@ -1,8 +1,5 @@
 import { XssOptionConfig } from '~/services/xss/xssOption';
 
-import { RehypeSanitizeOptionConfig } from '../rehype';
-
-
 export type RendererConfig = {
   isEnabledLinebreaks: boolean,
   isEnabledLinebreaksInComments: boolean,
@@ -12,4 +9,4 @@ export type RendererConfig = {
 
   plantumlUri: string | null,
   blockdiagUri: string | null,
-} & XssOptionConfig & RehypeSanitizeOptionConfig;
+} & XssOptionConfig;

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

@@ -21,9 +21,10 @@ import superjson from 'superjson';
 
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { Comments } from '~/components/Comments';
+import { MainPane } from '~/components/Layout/MainPane';
 import { PageAlerts } from '~/components/PageAlert/PageAlerts';
 // import { useTranslation } from '~/i18n';
-import { CurrentPageContentFooter } from '~/components/PageContentFooter';
+import { PageContentFooter } from '~/components/PageContentFooter';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { UsersHomePageFooterProps } from '~/components/UsersHomePageFooter';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
@@ -59,6 +60,7 @@ import GrowiContextualSubNavigation from '../components/Navbar/GrowiContextualSu
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 // import { serializeUserSecurely } from '../server/models/serializers/user-serializer';
 // import PageStatusAlert from '../client/js/components/PageStatusAlert';
+import type { PageSideContentsProps } from '../components/PageSideContents';
 import {
   useCurrentUser,
   useIsLatestRevision,
@@ -69,7 +71,7 @@ import {
   useIsAclEnabled, useIsSearchPage, useTemplateTagData, useTemplateBodyData, useIsEnabledAttachTitleHeader,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useIsSlackConfigured, useRendererConfig,
-  useEditorConfig, useIsAllReplyShown, useIsUploadableFile, useIsUploadableImage, useCustomizedLogoSrc, useIsContainerFluid,
+  useEditorConfig, useIsAllReplyShown, useIsUploadableFile, useIsUploadableImage, useCustomizedLogoSrc, useIsContainerFluid, useIsNotCreatable,
 } from '../stores/context';
 
 import { NextPageWithLayout } from './_app.page';
@@ -87,6 +89,7 @@ declare global {
 const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').then(mod => mod.NotCreatablePage), { ssr: false });
 const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
+const PageSideContents = dynamic<PageSideContentsProps>(() => import('../components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
 const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
 const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../components/UsersHomePageFooter')
   .then(mod => mod.UsersHomePageFooter), { ssr: false });
@@ -97,7 +100,7 @@ const PageStatusAlert = dynamic(() => import('../components/PageStatusAlert').th
 const logger = loggerFactory('growi:pages:all');
 
 const {
-  isPermalink: _isPermalink, isUsersHomePage, isTrashPage: _isTrashPage, isUserPage, isCreatablePage, isTopPage,
+  isPermalink: _isPermalink, isUsersHomePage, isTrashPage: _isTrashPage, isCreatablePage, isTopPage,
 } = pagePathUtils;
 const { removeHeadingSlash } = pathUtils;
 
@@ -152,7 +155,7 @@ type Props = CommonProps & {
   isIdenticalPathPage?: boolean,
   isForbidden: boolean,
   isNotFound: boolean,
-  isNotCreatablePage: boolean,
+  isNotCreatable: boolean,
   // isAbleToDeleteCompletely: boolean,
 
   templateTagData?: string[],
@@ -203,7 +206,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   // commons
   useEditorConfig(props.editorConfig);
   useCsrfToken(props.csrfToken);
-  useCustomizedLogoSrc(props.customizedLogoSrc);
 
   // UserUISettings
   usePreferDrawerModeByUser(props.userUISettings?.preferDrawerModeByUser ?? props.sidebarConfig.isSidebarDrawerMode);
@@ -218,13 +220,10 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   // useOwnerOfCurrentPage(props.pageUser != null ? JSON.parse(props.pageUser) : null);
   useIsForbidden(props.isForbidden);
   useIsNotFound(props.isNotFound);
-  // useIsNotCreatable(props.IsNotCreatable);
-  useRedirectFrom(props.redirectFrom);
-  // useShared();
-  // useShareLinkId(props.shareLinkId);
+  useIsNotCreatable(props.isNotCreatable);
+  useRedirectFrom(props.redirectFrom ?? null);
   useIsSharedUser(false); // this page cann't be routed for '/share'
-  useIsIdenticalPath(false); // TODO: need to initialize from props
-  // useIsAbleToDeleteCompletely(props.isAbleToDeleteCompletely);
+  useIsIdenticalPath(props.isIdenticalPathPage ?? false);
   useIsEnabledStaleNotification(props.isEnabledStaleNotification);
   useIsSearchPage(false);
 
@@ -264,7 +263,6 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useRemoteRevisionId(pageWithMeta?.data.revision?._id);
   usePageIdOnHackmd(pageWithMeta?.data.pageIdOnHackmd);
   useHasDraftOnHackmd(pageWithMeta?.data.hasDraftOnHackmd ?? false);
-  // useIsNotCreatable(props.isForbidden || !isCreatablePage(pagePath)); // TODO: need to include props.isIdentical
   useCurrentPathname(props.currentPathname);
 
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
@@ -301,6 +299,27 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
   const title = generateCustomTitle(props, 'GROWI');
 
+
+  const sideContents = !props.isNotFound && !props.isNotCreatable
+    ? (
+      <PageSideContents page={pageWithMeta?.data} />
+    )
+    : <></>;
+
+  const footerContents = !props.isIdenticalPathPage && !props.isNotFound && pageWithMeta != null
+    ? (
+      <>
+        { pagePath != null && !isTopPagePath && (
+          <Comments pageId={pageId} pagePath={pagePath} revision={pageWithMeta.data.revision} />
+        ) }
+        { isUsersHomePage(pageWithMeta.data.path) && (
+          <UsersHomePageFooter creatorId={pageWithMeta.data.creator._id}/>
+        ) }
+        <PageContentFooter page={pageWithMeta.data} />
+      </>
+    )
+    : <></>;
+
   return (
     <>
       <Head>
@@ -319,41 +338,21 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
         <div id="grw-subnav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 
-        <div className="flex-grow-1">
-          <div id="main" className={`main ${isUsersHomePage(props.currentPathname) && 'user-page'}`}>
-            <div id="content-main" className="content-main grw-container-convertible">
-              { props.isIdenticalPathPage && <IdenticalPathPage /> }
-
-              { !props.isIdenticalPathPage && (
-                <>
-                  <PageAlerts />
-                  { props.isForbidden && <ForbiddenPage /> }
-                  { props.isNotCreatablePage && <NotCreatablePage />}
-                  { !props.isForbidden && !props.isNotCreatablePage && <DisplaySwitcher />}
-                  {/* <DisplaySwitcher /> */}
-                  <PageStatusAlert />
-                </>
-              ) }
-
-              {/* <div className="col-xl-2 col-lg-3 d-none d-lg-block revision-toc-container">
-                <div id="revision-toc" className="revision-toc mt-3 sps sps--abv" data-sps-offset="123">
-                  <div id="revision-toc-content" className="revision-toc-content"></div>
-                </div>
-              </div> */}
-            </div>
-          </div>
-        </div>
-        { !props.isIdenticalPathPage && !props.isNotFound && (
-          <footer className="footer d-edit-none">
-            { pageWithMeta != null && pagePath != null && !isTopPagePath && (
-              <Comments pageId={pageId} pagePath={pagePath} revision={pageWithMeta.data.revision} />
-            ) }
-            { pageWithMeta != null && isUsersHomePage(pageWithMeta.data.path) && (
-              <UsersHomePageFooter creatorId={pageWithMeta.data.creator._id}/>
-            ) }
-            <CurrentPageContentFooter />
-          </footer>
-        )}
+        <MainPane
+          sideContents={sideContents}
+          footerContents={footerContents}
+        >
+          <PageAlerts />
+          { props.isIdenticalPathPage && <IdenticalPathPage />}
+          { !props.isIdenticalPathPage && (
+            <>
+              { props.isForbidden && <ForbiddenPage /> }
+              { props.isNotCreatable && <NotCreatablePage />}
+              { !props.isForbidden && !props.isNotCreatable && <DisplaySwitcher />}
+            </>
+          ) }
+          <PageStatusAlert />
+        </MainPane>
 
         {shouldRenderPutbackPageModal && <PutbackPageModal />}
       </div>
@@ -480,17 +479,18 @@ async function injectRoutingInformation(context: GetServerSidePropsContext, prop
   const page = props.pageWithMeta?.data;
 
   if (props.isIdenticalPathPage) {
-    // TBD
+    props.isNotCreatable = true;
   }
   else if (page == null) {
     props.isNotFound = true;
-    props.isNotCreatablePage = !isCreatablePage(currentPathname);
+    props.isNotCreatable = !isCreatablePage(currentPathname);
     // check the page is forbidden or just does not exist.
     const count = isPermalink ? await Page.count({ _id: pageId }) : await Page.count({ path: currentPathname });
     props.isForbidden = count > 0;
   }
   else {
     props.isNotFound = page.isEmpty;
+    props.isNotCreatable = false;
     // /62a88db47fed8b2d94f30000 ==> /path/to/page
     if (isPermalink && page.isEmpty) {
       props.currentPathname = page.path;

+ 1 - 1
packages/app/src/pages/_private-legacy-pages.page.tsx

@@ -135,7 +135,7 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
     blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
     attrWhiteList: crowi.xssService.getAttrWhiteList(),
     tagWhiteList: crowi.xssService.getTagWhiteList(),
     highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),

+ 1 - 1
packages/app/src/pages/_search.page.tsx

@@ -154,7 +154,7 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
     blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
     attrWhiteList: crowi.xssService.getAttrWhiteList(),
     tagWhiteList: crowi.xssService.getTagWhiteList(),
     highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),

+ 1 - 1
packages/app/src/pages/login/error/[message].page.tsx

@@ -86,7 +86,7 @@ const LoginPage: NextPage<CommonProps> = () => {
   return (
     <NoLoginLayout className={classNames.join(' ')}>
       <div className="mb-4 login-form-errors text-center">
-        <div className='noLogin-dialog pb-4 mx-auto'>
+        <div className='nologin-dialog pb-4 mx-auto'>
           <div className="col-12">
             {loginErrorElm}
           </div>

+ 9 - 0
packages/app/src/pages/login/index.module.scss

@@ -0,0 +1,9 @@
+// layout
+.login-page :global {
+
+  .nologin-header,
+  .nologin-dialog {
+    width: 320px;
+  }
+
+}

+ 4 - 1
packages/app/src/pages/login/index.page.tsx

@@ -20,6 +20,9 @@ import {
 } from '~/stores/context';
 
 
+import styles from './index.module.scss';
+
+
 type Props = CommonProps & {
   registrationMode: RegistrationMode,
   pageWithMetaStr: string,
@@ -43,7 +46,7 @@ const LoginPage: NextPage<Props> = (props: Props) => {
   useCurrentPathname(props.currentPathname);
 
   const title = generateCustomTitle(props, 'GROWI');
-  const classNames: string[] = ['login-page'];
+  const classNames: string[] = ['login-page', styles['login-page']];
 
   return (
     <NoLoginLayout className={classNames.join(' ')}>

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

@@ -3,7 +3,7 @@ import React, { useMemo } from 'react';
 import { IUserHasId } from '@growi/core';
 import { model as mongooseModel } from 'mongoose';
 import {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
@@ -28,10 +28,10 @@ import {
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
+import { NextPageWithLayout } from '../_app.page';
 import {
   CommonProps, getNextI18NextConfig, getServerSideCommonProps, generateCustomTitle,
 } from '../utils/commons';
-import { NextPageWithLayout } from '../_app.page';
 
 
 const logger = loggerFactory('growi:pages:me');
@@ -185,7 +185,7 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
     blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
     attrWhiteList: crowi.xssService.getAttrWhiteList(),
     tagWhiteList: crowi.xssService.getTagWhiteList(),
     highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),

+ 57 - 78
packages/app/src/pages/share/[[...path]].page.tsx

@@ -2,22 +2,19 @@ import React from 'react';
 
 import { IUserHasId } from '@growi/core';
 import {
-  NextPage, GetServerSideProps, GetServerSidePropsContext,
+  GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
-import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
-import CountBadge from '~/components/Common/CountBadge';
-import PageListIcon from '~/components/Icons/PageListIcon';
+import { MainPane } from '~/components/Layout/MainPane';
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
 import GrowiContextualSubNavigation from '~/components/Navbar/GrowiContextualSubNavigation';
 import { Page } from '~/components/Page';
-import styles from '~/components/Page/DisplaySwitcher.module.scss'; // for PageList toc style
+import type { PageSideContentsProps } from '~/components/PageSideContents';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
-import TableOfContents from '~/components/TableOfContents';
 import { SupportedAction, SupportedActionType } from '~/interfaces/activity';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { RendererConfig } from '~/interfaces/services/renderer';
@@ -26,7 +23,6 @@ import {
   useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
 } from '~/stores/context';
-import { useDescendantsPageListModal } from '~/stores/modal';
 import loggerFactory from '~/utils/logger';
 
 import { NextPageWithLayout } from '../_app.page';
@@ -36,6 +32,8 @@ import {
 
 const logger = loggerFactory('growi:next-page:share');
 
+const PageSideContents = dynamic<PageSideContentsProps>(() => import('~/components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
+// const Comments = dynamic(() => import('~/components/Comments').then(mod => mod.Comments), { ssr: false });
 const ShareLinkAlert = dynamic(() => import('~/components/Page/ShareLinkAlert'), { ssr: false });
 const ForbiddenPage = dynamic(() => import('~/components/ForbiddenPage'), { ssr: false });
 
@@ -63,8 +61,6 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useDrawioUri(props.drawioUri);
   useIsContainerFluid(props.isContainerFluid);
 
-  const { open: openDescendantPageListModal } = useDescendantsPageListModal();
-  const { t } = useTranslation();
 
   const growiLayoutFluidClass = useCurrentGrowiLayoutFluidClassName();
 
@@ -74,6 +70,19 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
   const title = generateCustomTitle(props, 'GROWI');
 
+
+  const sideContents = shareLink != null
+    ? <PageSideContents page={shareLink.relatedPage} />
+    : <></>;
+
+  // const footerContents = shareLink != null && isPopulated(shareLink.relatedPage.revision)
+  //   ? (
+  //     <>
+  //       <Comments pageId={shareLink._id} pagePath={shareLink.relatedPage.path} revision={shareLink.relatedPage.revision} />
+  //     </>
+  //   )
+  //   : <></>;
+
   return (
     <>
       <Head>
@@ -87,74 +96,43 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 
-        <div className="flex-grow-1">
-          <div id="content-main" className="content-main grw-container-convertible">
-            { props.disableLinkSharing && (
-              <div className="mt-4">
-                <ForbiddenPage isLinkSharingDisabled={props.disableLinkSharing} />
-              </div>
-            )}
-
-            { (isNotFound && !props.disableLinkSharing) && (
-              <div className="container-lg">
-                <h2 className="text-muted mt-4">
-                  <i className="icon-ban" aria-hidden="true" />
-                  <span> Page is not found</span>
-                </h2>
-              </div>
-            )}
-
-            { (props.isExpired && !props.disableLinkSharing && shareLink != null) && (
-              <div className="container-lg">
-                <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
-                <h2 className="text-muted mt-4">
-                  <i className="icon-ban" aria-hidden="true" />
-                  <span> Page is expired</span>
-                </h2>
-              </div>
-            )}
-
-            {(isShowSharedPage && shareLink != null) && (
-              <>
-                <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
-                <div className="d-flex flex-column flex-lg-row-reverse">
-
-                  <div className="grw-side-contents-container">
-                    <div className="grw-side-contents-sticky-container">
-
-                      {/* Page list */}
-                      <div className={`grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-                        { shareLink.relatedPage.path != null && (
-                          <button
-                            type="button"
-                            className="btn btn-block btn-outline-secondary grw-btn-page-accessories
-                            rounded-pill d-flex justify-content-between align-items-center"
-                            onClick={() => openDescendantPageListModal(shareLink.relatedPage.path)}
-                            data-testid="pageListButton"
-                          >
-                            <div className="grw-page-accessories-control-icon">
-                              <PageListIcon />
-                            </div>
-                            {t('page_list')}
-                            <CountBadge count={shareLink.relatedPage.descendantCount} offset={1} />
-                          </button>
-                        ) }
-                      </div>
-
-                      <div className="d-none d-lg-block">
-                        <TableOfContents />
-                      </div>
-                    </div>
-                  </div>
-
-                  <div className="flex-grow-1 flex-basis-0 mw-0">
-                    <Page />
-                  </div>
-                </div>
-              </>
-            )}
-          </div>
-        </div>
+        <MainPane
+          sideContents={sideContents}
+          // footerContents={footerContents}
+        >
+          { props.disableLinkSharing && (
+            <div className="mt-4">
+              <ForbiddenPage isLinkSharingDisabled={props.disableLinkSharing} />
+            </div>
+          )}
+
+          { (isNotFound && !props.disableLinkSharing) && (
+            <div className="container-lg">
+              <h2 className="text-muted mt-4">
+                <i className="icon-ban" aria-hidden="true" />
+                <span> Page is not found</span>
+              </h2>
+            </div>
+          )}
+
+          { (props.isExpired && !props.disableLinkSharing && shareLink != null) && (
+            <div className="container-lg">
+              <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
+              <h2 className="text-muted mt-4">
+                <i className="icon-ban" aria-hidden="true" />
+                <span> Page is expired</span>
+              </h2>
+            </div>
+          )}
+
+          {(isShowSharedPage && shareLink != null) && (
+            <>
+              <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
+              <Page />
+            </>
+          )}
+        </MainPane>
+
       </div>
     </>
   );
@@ -192,7 +170,7 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
     blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
     attrWhiteList: xssService.getAttrWhiteList(),
     tagWhiteList: xssService.getTagWhiteList(),
     highlightJsStyleBorder: configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
@@ -249,6 +227,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
     const ShareLinkModel = crowi.model('ShareLink');
     const shareLink = await ShareLinkModel.findOne({ _id: params.linkId }).populate('relatedPage');
     if (shareLink != null) {
+      await shareLink.relatedPage.populateDataToShowRevision();
       props.isExpired = shareLink.isExpired();
       props.shareLink = shareLink.toObject();
     }

+ 2 - 2
packages/app/src/pages/tags.page.tsx

@@ -25,10 +25,10 @@ import {
   useIsSearchScopeChildrenAsDefault, useRendererConfig,
 } from '../stores/context';
 
+import { NextPageWithLayout } from './_app.page';
 import {
   CommonProps, getServerSideCommonProps, getNextI18NextConfig, generateCustomTitle,
 } from './utils/commons';
-import { NextPageWithLayout } from './_app.page';
 
 const PAGING_LIMIT = 10;
 
@@ -164,7 +164,7 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
     blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
     attrWhiteList: crowi.xssService.getAttrWhiteList(),
     tagWhiteList: crowi.xssService.getTagWhiteList(),
     highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),

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

@@ -156,7 +156,7 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
     blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
 
     // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+    isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'),
     attrWhiteList: crowi.xssService.getAttrWhiteList(),
     tagWhiteList: crowi.xssService.getTagWhiteList(),
     highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),

+ 1 - 0
packages/app/src/server/models/growi-plugin.ts

@@ -53,6 +53,7 @@ const growiPluginOriginSchema = new Schema<GrowiPluginOrigin>({
 const growiPluginSchema = new Schema<GrowiPluginDocument, GrowiPluginModel>({
   isEnabled: { type: Boolean },
   installedPath: { type: String },
+  organizationName: { type: String },
   origin: growiPluginOriginSchema,
   meta: growiPluginMetaSchema,
 });

+ 9 - 0
packages/app/src/server/service/plugin.ts

@@ -54,10 +54,15 @@ export class PluginService implements IPluginService {
       // if not exists repository in file system, download latest plugin repository
       for await (const growiPlugin of growiPlugins) {
         const pluginPath = path.join(pluginStoringPath, growiPlugin.installedPath);
+        const organizationName = path.join(pluginStoringPath, growiPlugin.organizationName);
         if (fs.existsSync(pluginPath)) {
           continue;
         }
         else {
+          if (!fs.existsSync(organizationName)) {
+            fs.mkdirSync(organizationName);
+          }
+
           // TODO: imprv Document version and repository version possibly different.
           const ghUrl = new URL(growiPlugin.origin.url);
           const ghPathname = ghUrl.pathname;
@@ -122,6 +127,7 @@ export class PluginService implements IPluginService {
     const unzippedReposPath = path.join(pluginStoringPath, `${ghReposName}-${ghBranch}`);
     const temporaryReposPath = path.join(pluginStoringPath, ghReposName);
     const reposStoringPath = path.join(pluginStoringPath, `${installedPath}`);
+    const organizationPath = path.join(pluginStoringPath, ghOrganizationName);
 
 
     let plugins: GrowiPlugin<GrowiPluginMeta>[];
@@ -135,6 +141,8 @@ export class PluginService implements IPluginService {
       // detect plugins
       plugins = await PluginService.detectPlugins(origin, ghOrganizationName, ghReposName);
 
+      if (!fs.existsSync(organizationPath)) fs.mkdirSync(organizationPath);
+
       // remove the old repository from the storing path
       if (fs.existsSync(reposStoringPath)) await fs.promises.rm(reposStoringPath, { recursive: true });
 
@@ -252,6 +260,7 @@ export class PluginService implements IPluginService {
     const plugin = {
       isEnabled: true,
       installedPath: `${ghOrganizationName}/${ghReposName}`,
+      organizationName: ghOrganizationName,
       origin,
       meta: {
         name: growiPlugin.name ?? packageName,

+ 8 - 3
packages/app/src/stores/context.tsx

@@ -56,6 +56,10 @@ export const useIsForbidden = (initialData?: boolean): SWRResponse<boolean, Erro
   return useContextSWR<boolean, Error>('isForbidden', initialData, { fallbackData: false });
 };
 
+export const useIsNotCreatable = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useContextSWR<boolean, Error>('isNotCreatable', initialData, { fallbackData: false });
+};
+
 export const useIsNotFound = (initialData?: boolean): SWRResponse<boolean, Error> => {
   return useContextSWR<boolean, Error>('isNotFound', initialData, { fallbackData: false });
 };
@@ -230,12 +234,13 @@ export const useIsGuestUser = (): SWRResponse<boolean, Error> => {
 export const useIsEditable = (): SWRResponse<boolean, Error> => {
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isForbidden } = useIsForbidden();
+  const { data: isNotCreatable } = useIsNotCreatable();
   const { data: isIdenticalPath } = useIsIdenticalPath();
 
   return useSWRImmutable(
-    ['isEditable', isGuestUser, isForbidden, isIdenticalPath],
-    (key: Key, isGuestUser: boolean, isForbidden: boolean, isIdenticalPath: boolean) => {
-      return (!isForbidden && !isIdenticalPath && !isGuestUser);
+    ['isEditable', isGuestUser, isForbidden, isNotCreatable, isIdenticalPath],
+    (key: Key, isGuestUser: boolean, isForbidden: boolean, isNotCreatable: boolean, isIdenticalPath: boolean) => {
+      return (!isForbidden && !isIdenticalPath && !isNotCreatable && !isGuestUser);
     },
   );
 };

+ 1 - 1
packages/app/src/stores/page-redirect.tsx

@@ -3,6 +3,6 @@ import { SWRResponse } from 'swr';
 import { useStaticSWR } from './use-static-swr';
 
 
-export const useRedirectFrom = (initialData?: string): SWRResponse<string, Error> => {
+export const useRedirectFrom = (initialData?: string | null): SWRResponse<string | null, Error> => {
   return useStaticSWR('redirectFrom', initialData);
 };

+ 1 - 1
packages/app/src/stores/ui.tsx

@@ -473,7 +473,7 @@ export const useIsAbleToShowPageEditorModeManager = (): SWRResponse<boolean, Err
   const includesUndefined = [isEditable, isSharedUser].some(v => v === undefined);
 
   return useSWRImmutable(
-    includesUndefined ? null : key,
+    includesUndefined ? null : [key, isEditable, isSharedUser],
     () => !!isEditable && !isSharedUser,
   );
 };

+ 2 - 2
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -141,7 +141,7 @@ ul.pagination {
     linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
     linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
-  .noLogin-header {
+  .nologin-header {
     background-color: rgba(black, 0.5);
 
     .logo {
@@ -154,7 +154,7 @@ ul.pagination {
     }
   }
 
-  .noLogin-dialog {
+  .nologin-dialog {
     background-color: rgba(black, 0.5);
   }
 

+ 2 - 2
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -84,7 +84,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
     linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
     linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
-  .noLogin-header {
+  .nologin-header {
     background-color: rgba(white, 0.5);
 
     .logo {
@@ -97,7 +97,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
     }
   }
 
-  .noLogin-dialog {
+  .nologin-dialog {
     background-color: rgba(white, 0.5);
   }
 

+ 1 - 1
packages/codemirror-textlint/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/codemirror-textlint",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "license": "MIT",
   "main": "dist/index.js",
   "scripts": {

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/hackmd/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/hackmd",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "description": "GROWI js and css files to use hackmd",
   "license": "MIT",
   "main": "dist/index.js",

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "license": "MIT",
   "main": "dist/libs/index.js",
   "files": [

+ 6 - 7
packages/preset-themes/src/styles/antarctic.scss

@@ -24,13 +24,12 @@
   }
 }
 
-.growi.login-page {
-  #page-wrapper {
-    background-image: url('/images/themes/antarctic/topimage.svg');
-    background-attachment: fixed;
-    background-position: center center;
-    background-size: cover;
-  }
+.nologin {
+  background: unset !important;
+  background-image: url('/images/themes/antarctic/topimage.svg');
+  background-attachment: fixed;
+  background-position: center center;
+  background-size: cover;
 }
 
 $themecolor: #000080;

+ 4 - 3
packages/preset-themes/src/styles/christmas.scss

@@ -120,7 +120,6 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
     background: $themelight;
   }
 
-  #wrapper > #page-wrapper,
   .page-editor-preview-container {
     background-image: url('/images/themes/christmas/christmas.jpg');
     background-attachment: fixed;
@@ -129,6 +128,8 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
 
   // login page
   .nologin {
+    background: unset !important;
+
     .input-group {
       .input-group-text {
         color: $gray-700;
@@ -140,8 +141,8 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
       }
     }
 
-    .noLogin-header,
-    .noLogin-dialog {
+    .nologin-header,
+    .nologin-dialog {
       background-color: rgba(#ccc, 0.5);
     }
     .link-switch {

+ 16 - 20
packages/preset-themes/src/styles/hufflepuff.scss

@@ -121,16 +121,14 @@
 
   // login and register
   .nologin {
-    #page-wrapper {
-      background-color: $themelight;
-      background-image: url('../images/themes/hufflepuff/badger-light.png');
-      background-attachment: fixed;
-      background-position: bottom;
-      background-size: cover;
-    }
-
-    .noLogin-header,
-    .noLogin-dialog {
+    background: unset !important;
+    background-image: url('../images/themes/hufflepuff/badger-light.png');
+    background-attachment: fixed;
+    background-position: bottom;
+    background-size: cover;
+
+    .nologin-header,
+    .nologin-dialog {
       background-color: rgba(black, 0.1);
     }
 
@@ -292,16 +290,14 @@
 
   // login and register
   .nologin {
-    #page-wrapper {
-      background-color: $themedark;
-      background-image: url('../images/themes/hufflepuff/badger-light.png');
-      background-attachment: fixed;
-      background-position: bottom;
-      background-size: cover;
-    }
-
-    .noLogin-header,
-    .noLogin-dialog {
+    background: unset !important;
+    background-image: url('../images/themes/hufflepuff/badger-light.png');
+    background-attachment: fixed;
+    background-position: bottom;
+    background-size: cover;
+
+    .nologin-header,
+    .nologin-dialog {
       background-color: rgba(black, 0.1);
     }
 

+ 1 - 5
packages/preset-themes/src/styles/island.scss

@@ -101,11 +101,7 @@ $color-themelight: rgba(183, 226, 219, 1);
 
   // login page
   .nologin {
-    &.login-page {
-      > #wrapper > #page-wrapper {
-        background-image: unset;
-      }
-    }
+    background-image: unset !important;
   }
 
   // Button

+ 0 - 9
packages/preset-themes/src/styles/nature.scss

@@ -16,7 +16,6 @@
 
 .growi:not(.login-page) {
   // add background-image
-  #page-wrapper,
   .page-editor-preview-container {
     background-attachment: fixed;
     background-position: center center;
@@ -24,14 +23,6 @@
   }
 }
 
-.growi.login-page {
-  #page-wrapper {
-    background-attachment: fixed;
-    background-position: center center;
-    background-size: cover;
-  }
-}
-
 $themecolor: #12b105;
 
 //== Light Mode

+ 7 - 9
packages/preset-themes/src/styles/spring.scss

@@ -124,16 +124,14 @@ $accentcolor: #e08dbc;
 
   // login and register
   .nologin {
-    #page-wrapper {
-      background-color: $themelight;
-      background-image: url('/images/themes/spring/spring.svg');
-      background-attachment: fixed;
-      background-position: bottom;
-      background-size: cover;
-    }
+    background: unset !important;
+    background-image: url('/images/themes/spring/spring.svg');
+    background-attachment: fixed;
+    background-position: bottom;
+    background-size: cover;
 
-    .noLogin-header,
-    .noLogin-dialog {
+    .nologin-header,
+    .nologin-dialog {
       background-color: rgba(black, 0.1);
     }
 

+ 2 - 2
packages/preset-themes/src/styles/theme/_apply-colors-dark.scss

@@ -141,7 +141,7 @@ ul.pagination {
     linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
     linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
-  .noLogin-header {
+  .nologin-header {
     background-color: rgba(black, 0.5);
 
     .logo {
@@ -154,7 +154,7 @@ ul.pagination {
     }
   }
 
-  .noLogin-dialog {
+  .nologin-dialog {
     background-color: rgba(black, 0.5);
   }
 

+ 2 - 2
packages/preset-themes/src/styles/theme/_apply-colors-light.scss

@@ -84,7 +84,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
     linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
     linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
-  .noLogin-header {
+  .nologin-header {
     background-color: rgba(white, 0.5);
 
     .logo {
@@ -97,7 +97,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
     }
   }
 
-  .noLogin-dialog {
+  .nologin-dialog {
     background-color: rgba(white, 0.5);
   }
 

+ 4 - 4
packages/preset-themes/src/styles/wood.scss

@@ -23,7 +23,7 @@
 }
 
 .growi.login-page {
-  #page-wrapper {
+  .page-wrapper {
     background-image: url('/images/themes/wood/wood.jpg');
     background-attachment: fixed;
     background-position: center center;
@@ -151,10 +151,10 @@ $themelight: #f5f3ee;
 
   // login and register
   .nologin {
-    background: white;
+    background: unset !important;
 
-    .noLogin-header,
-    .noLogin-dialog {
+    .nologin-header,
+    .nologin-dialog {
       background-color: rgba(black, 0.1);
     }
 

+ 1 - 1
packages/remark-drawio/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-drawio",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "description": "remark plugin to draw diagrams with draw.io (diagrams.net)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-growi-directive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-growi-directive",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "description": "remark plugin to support GROWI plugin (forked from remark-directive@2.0.1)",
   "license": "MIT",
   "keywords": [

+ 4 - 4
packages/remark-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-lsx",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": ["growi", "growi-plugin"],
@@ -23,9 +23,9 @@
     "test": ""
   },
   "dependencies": {
-    "@growi/core": "^6.0.0-RC.14",
-    "@growi/remark-growi-directive": "^6.0.0-RC.14",
-    "@growi/ui": "^6.0.0-RC.14",
+    "@growi/core": "^6.0.1-RC.0",
+    "@growi/remark-growi-directive": "^6.0.1-RC.0",
+    "@growi/ui": "^6.0.1-RC.0",
     "swr": "^1.3.0"
   },
   "devDependencies": {

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "license": "MIT",
   "main": "dist/index.js",
   "typings": "dist/index.d.ts",

+ 2 - 2
packages/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "6.0.0-slackbot-proxy.0",
+  "version": "6.0.1-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
@@ -26,7 +26,7 @@
   },
   "dependencies": {
     "@godaddy/terminus": "^4.9.0",
-    "@growi/slack": "^6.0.0-RC.14",
+    "@growi/slack": "^6.0.1-RC.0",
     "@slack/oauth": "^2.0.1",
     "@slack/web-api": "^6.2.4",
     "@tsed/common": "^6.43.0",

+ 2 - 2
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.1-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": ["growi"],
@@ -17,7 +17,7 @@
     "test": "jest --verbose"
   },
   "dependencies": {
-    "@growi/core": "^6.0.0-RC.14"
+    "@growi/core": "^6.0.1-RC.0"
   },
   "devDependencies": {
     "eslint-plugin-regex": "^1.8.0",