jam411 3 лет назад
Родитель
Сommit
9ddb3fbeda
63 измененных файлов с 745 добавлено и 556 удалено
  1. 0 2
      packages/app/_obsolete/src/client/app.jsx
  2. 5 0
      packages/app/src/client/services/AdminAppContainer.js
  3. 5 0
      packages/app/src/client/services/AdminBasicSecurityContainer.js
  4. 5 0
      packages/app/src/client/services/AdminCustomizeContainer.js
  5. 5 0
      packages/app/src/client/services/AdminExternalAccountsContainer.js
  6. 5 0
      packages/app/src/client/services/AdminGeneralSecurityContainer.js
  7. 5 1
      packages/app/src/client/services/AdminGitHubSecurityContainer.js
  8. 5 1
      packages/app/src/client/services/AdminGoogleSecurityContainer.js
  9. 5 0
      packages/app/src/client/services/AdminHomeContainer.js
  10. 5 0
      packages/app/src/client/services/AdminImportContainer.js
  11. 5 0
      packages/app/src/client/services/AdminLdapSecurityContainer.js
  12. 5 0
      packages/app/src/client/services/AdminLocalSecurityContainer.js
  13. 5 0
      packages/app/src/client/services/AdminMarkDownContainer.js
  14. 5 0
      packages/app/src/client/services/AdminNotificationContainer.js
  15. 5 1
      packages/app/src/client/services/AdminOidcSecurityContainer.js
  16. 5 1
      packages/app/src/client/services/AdminSamlSecurityContainer.js
  17. 5 0
      packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  18. 5 1
      packages/app/src/client/services/AdminTwitterSecurityContainer.js
  19. 8 3
      packages/app/src/client/services/AdminUserGroupDetailContainer.js
  20. 5 0
      packages/app/src/client/services/AdminUsersContainer.js
  21. 7 4
      packages/app/src/client/services/ContextExtractor.tsx
  22. 3 1
      packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx
  23. 2 2
      packages/app/src/components/Admin/Security/SecurityManagementContents.jsx
  24. 2 3
      packages/app/src/components/Admin/UserManagement.jsx
  25. 6 6
      packages/app/src/components/Admin/Users/UserInviteModal.jsx
  26. 2 8
      packages/app/src/components/Layout/AdminLayout.tsx
  27. 7 2
      packages/app/src/components/Layout/BasicLayout.tsx
  28. 5 4
      packages/app/src/components/Page/DisplaySwitcher.tsx
  29. 4 1
      packages/app/src/components/PageAlert/PageAlerts.tsx
  30. 1 1
      packages/app/src/components/PageAlert/TrashPageAlert.tsx
  31. 4 7
      packages/app/src/components/PageComment/CommentEditor.tsx
  32. 0 1
      packages/app/src/components/PageComment/CommentEditorLazyRenderer.tsx
  33. 104 102
      packages/app/src/components/PageEditor.tsx
  34. 26 22
      packages/app/src/components/PageEditor/CodeMirrorEditor.jsx
  35. 93 0
      packages/app/src/components/PageEditor/CodeMirrorEditor.module.scss
  36. 1 8
      packages/app/src/components/PageEditor/EditorNavbarBottom.tsx
  37. 6 69
      packages/app/src/components/PageEditor/Preview.tsx
  38. 5 7
      packages/app/src/components/PageEditorByHackmd.jsx
  39. 5 1
      packages/app/src/components/PagePathNav.tsx
  40. 35 32
      packages/app/src/components/SavePageControls.jsx
  41. 1 1
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  42. 0 59
      packages/app/src/components/TagPage.tsx
  43. 1 1
      packages/app/src/components/UncontrolledCodeMirror.tsx
  44. 1 0
      packages/app/src/components/UnstatedUtils.tsx
  45. 21 0
      packages/app/src/interfaces/customize.ts
  46. 0 7
      packages/app/src/interfaces/editor-settings.ts
  47. 0 28
      packages/app/src/interfaces/unstated-container.ts
  48. 21 23
      packages/app/src/pages/[[...path]].page.tsx
  49. 91 19
      packages/app/src/pages/admin/[[...path]].page.tsx
  50. 137 0
      packages/app/src/pages/tags.page.tsx
  51. 2 0
      packages/app/src/pages/utils/commons.ts
  52. 3 0
      packages/app/src/server/routes/apiv3/page.js
  53. 1 1
      packages/app/src/server/routes/apiv3/response.js
  54. 2 1
      packages/app/src/server/routes/index.js
  55. 1 1
      packages/app/src/server/service/page.ts
  56. 7 5
      packages/app/src/stores/context.tsx
  57. 14 4
      packages/app/src/stores/page.tsx
  58. 14 19
      packages/app/src/stores/ui.tsx
  59. 5 5
      packages/app/src/styles/_layout.scss
  60. 6 33
      packages/app/src/styles/_on-edit.scss
  61. 0 53
      packages/app/src/styles/_override-codemirror.scss
  62. 1 1
      packages/app/src/styles/style-next.scss
  63. 0 4
      packages/app/src/styles/theme/_apply-colors.scss

+ 0 - 2
packages/app/_obsolete/src/client/app.jsx

@@ -39,7 +39,6 @@ import { PageTimeline } from '../components/PageTimeline';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import { SearchPage } from '../components/SearchPage';
 import { SearchPage } from '../components/SearchPage';
 import Sidebar from '../components/Sidebar';
 import Sidebar from '../components/Sidebar';
-import TagPage from '../components/TagPage';
 import TrashPageList from '../components/TrashPageList';
 import TrashPageList from '../components/TrashPageList';
 
 
 import { appContainer, componentMappings } from './base';
 import { appContainer, componentMappings } from './base';
@@ -75,7 +74,6 @@ Object.assign(componentMappings, {
   'identical-path-page': <IdenticalPathPage />,
   'identical-path-page': <IdenticalPathPage />,
 
 
   // 'revision-history': <PageHistory pageId={pageId} />,
   // 'revision-history': <PageHistory pageId={pageId} />,
-  'tags-page': <TagPage />,
 
 
   'grw-page-status-alert-container': <PageStatusAlert />,
   'grw-page-status-alert-container': <PageStatusAlert />,
 
 

+ 5 - 0
packages/app/src/client/services/AdminAppContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
@@ -11,6 +12,10 @@ export default class AdminAppContainer extends Container {
   constructor() {
   constructor() {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
     this.state = {
       retrieveError: null,
       retrieveError: null,
       title: '',
       title: '',

+ 5 - 0
packages/app/src/client/services/AdminBasicSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -16,6 +17,10 @@ export default class AdminBasicSecurityContainer extends Container {
   constructor() {
   constructor() {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
     this.state = {
       isSameUsernameTreatedAsIdenticalUser: false,
       isSameUsernameTreatedAsIdenticalUser: false,
     };
     };

+ 5 - 0
packages/app/src/client/services/AdminCustomizeContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminCustomizeContainer extends Container {
   constructor() {
   constructor() {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
     this.state = {
       retrieveError: null,
       retrieveError: null,
       isEnabledTimeline: false,
       isEnabledTimeline: false,

+ 5 - 0
packages/app/src/client/services/AdminExternalAccountsContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminExternalAccountsContainer extends Container {
   constructor() {
   constructor() {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
     this.state = {
       externalAccounts: [],
       externalAccounts: [],
       totalAccounts: 0,
       totalAccounts: 0,

+ 5 - 0
packages/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import {
 import {
@@ -18,6 +19,10 @@ export default class AdminGeneralSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
     this.state = {
       retrieveError: null,
       retrieveError: null,
       sessionMaxAge: null,
       sessionMaxAge: null,

+ 5 - 1
packages/app/src/client/services/AdminGitHubSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
@@ -18,6 +18,10 @@ export default class AdminGitHubSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.dummyGithubClientId = 0;
     this.dummyGithubClientId = 0;
     this.dummyGithubClientIdForError = 1;
     this.dummyGithubClientIdForError = 1;
 
 

+ 5 - 1
packages/app/src/client/services/AdminGoogleSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
@@ -18,6 +18,10 @@ export default class AdminGoogleSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.dummyGoogleClientId = 0;
     this.dummyGoogleClientId = 0;
     this.dummyGoogleClientIdForError = 1;
     this.dummyGoogleClientIdForError = 1;
 
 

+ 5 - 0
packages/app/src/client/services/AdminHomeContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminHomeContainer extends Container {
   constructor() {
   constructor() {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.copyStateValues = {
     this.copyStateValues = {
       DEFAULT: 'default',
       DEFAULT: 'default',
       DONE: 'done',
       DONE: 'done',

+ 5 - 0
packages/app/src/client/services/AdminImportContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminImportContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 5 - 0
packages/app/src/client/services/AdminLdapSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -16,6 +17,10 @@ export default class AdminLdapSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 5 - 0
packages/app/src/client/services/AdminLocalSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -15,6 +16,10 @@ export default class AdminLocalSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
     this.dummyRegistrationMode = 0;
     this.dummyRegistrationMode = 0;
     this.dummyRegistrationModeForError = 1;
     this.dummyRegistrationModeForError = 1;

+ 5 - 0
packages/app/src/client/services/AdminMarkDownContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';
@@ -11,6 +12,10 @@ export default class AdminMarkDownContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 5 - 0
packages/app/src/client/services/AdminNotificationContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import {
 import {
@@ -13,6 +14,10 @@ export default class AdminNotificationContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 5 - 1
packages/app/src/client/services/AdminOidcSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
@@ -18,6 +18,10 @@ export default class AdminOidcSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 5 - 1
packages/app/src/client/services/AdminSamlSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
@@ -18,6 +18,10 @@ export default class AdminSamlSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 5 - 0
packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';
@@ -11,6 +12,10 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 5 - 1
packages/app/src/client/services/AdminTwitterSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
@@ -18,6 +18,10 @@ export default class AdminTwitterSecurityContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 8 - 3
packages/app/src/client/services/AdminUserGroupDetailContainer.js

@@ -2,15 +2,16 @@
  * TODO 85062: AdminUserGroupDetailContainer is under transplantation to UserGroupDetailPage.tsx
  * TODO 85062: AdminUserGroupDetailContainer is under transplantation to UserGroupDetailPage.tsx
  */
  */
 
 
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
+import {
+  apiv3Get, apiv3Delete, apiv3Put, apiv3Post,
+} from '~/client/util/apiv3-client';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { toastError } from '../util/apiNotification';
 import { toastError } from '../util/apiNotification';
 
 
-import {
-  apiv3Get, apiv3Delete, apiv3Put, apiv3Post,
-} from '~/client/util/apiv3-client';
 
 
 // eslint-disable-next-line no-unused-vars
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:services:AdminUserGroupDetailContainer');
 const logger = loggerFactory('growi:services:AdminUserGroupDetailContainer');
@@ -24,6 +25,10 @@ export default class AdminUserGroupDetailContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     const rootElem = document.getElementById('admin-user-group-detail');
     const rootElem = document.getElementById('admin-user-group-detail');

+ 5 - 0
packages/app/src/client/services/AdminUsersContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { debounce } from 'throttle-debounce';
 import { debounce } from 'throttle-debounce';
 import { Container } from 'unstated';
 import { Container } from 'unstated';
 
 
@@ -19,6 +20,10 @@ export default class AdminUsersContainer extends Container {
   constructor(appContainer) {
   constructor(appContainer) {
     super();
     super();
 
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.appContainer = appContainer;
 
 
     this.state = {
     this.state = {

+ 7 - 4
packages/app/src/client/services/ContextExtractor.tsx

@@ -9,7 +9,7 @@ import { IUserUISettings } from '~/interfaces/user-ui-settings';
 import {
 import {
   useIsDeviceSmallerThanMd, useIsDeviceSmallerThanLg,
   useIsDeviceSmallerThanMd, useIsDeviceSmallerThanLg,
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
-  useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
+  useSelectedGrant,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import { useSetupGlobalSocket, useSetupGlobalAdminSocket } from '~/stores/websocket';
 import { useSetupGlobalSocket, useSetupGlobalAdminSocket } from '~/stores/websocket';
 
 
@@ -133,6 +133,9 @@ const ContextExtractorOnce: FC = () => {
     plantumlUri: configByContextHydrate.env.PLANTUML_URI,
     plantumlUri: configByContextHydrate.env.PLANTUML_URI,
     blockdiagUri: configByContextHydrate.env.BLOCKDIAG_URI,
     blockdiagUri: configByContextHydrate.env.BLOCKDIAG_URI,
   });
   });
+  // useNoCdn(configByContextHydrate.env.NO_CDN);
+  // useUploadableImage(configByContextHydrate.upload.image);
+  // useUploadableFile(configByContextHydrate.upload.file);
 
 
   // Page
   // Page
   useDeleteUsername(deleteUsername);
   useDeleteUsername(deleteUsername);
@@ -170,9 +173,9 @@ const ContextExtractorOnce: FC = () => {
   useIsDeviceSmallerThanMd();
   useIsDeviceSmallerThanMd();
 
 
   // Editor
   // Editor
-  useSelectedGrant(grant);
-  useSelectedGrantGroupId(grantGroupId);
-  useSelectedGrantGroupName(grantGroupName);
+  // useSelectedGrant(grant);
+  // useSelectedGrantGroupId(grantGroupId);
+  // useSelectedGrantGroupName(grantGroupName);
 
 
   // SearchResult
   // SearchResult
   useIsDeviceSmallerThanLg();
   useIsDeviceSmallerThanLg();

+ 3 - 1
packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx

@@ -1,6 +1,7 @@
 /* eslint-disable no-useless-escape */
 /* eslint-disable no-useless-escape */
 import React, { useCallback, useState } from 'react';
 import React, { useCallback, useState } from 'react';
 
 
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
   Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
   Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
@@ -8,6 +9,7 @@ import {
 
 
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import { IHighlightJsCssSelectorOptions } from '~/interfaces/customize';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
@@ -48,7 +50,7 @@ const CustomizeHighlightSetting = (props: Props): JSX.Element => {
   const { adminCustomizeContainer } = props;
   const { adminCustomizeContainer } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
   const [isDropdownOpen, setIsDropdownOpen] = useState(false);
   const [isDropdownOpen, setIsDropdownOpen] = useState(false);
-  const options = adminCustomizeContainer.state.highlightJsCssSelectorOptions;
+  const options: IHighlightJsCssSelectorOptions = adminCustomizeContainer.state.highlightJsCssSelectorOptions;
 
 
   const onToggleDropdown = useCallback(() => {
   const onToggleDropdown = useCallback(() => {
     setIsDropdownOpen(!isDropdownOpen);
     setIsDropdownOpen(!isDropdownOpen);

+ 2 - 2
packages/app/src/components/Admin/Security/SecurityManagementContents.jsx

@@ -82,12 +82,12 @@ const SecurityManagementContents = () => {
   return (
   return (
     <div data-testid="admin-security">
     <div data-testid="admin-security">
       <div className="mb-5">
       <div className="mb-5">
-        {/* <SecuritySetting /> */}
+        <SecuritySetting />
       </div>
       </div>
 
 
       {/* Shared Link List */}
       {/* Shared Link List */}
       <div className="mb-5">
       <div className="mb-5">
-        {/* <ShareLinkSetting /> */}
+        <ShareLinkSetting />
       </div>
       </div>
 
 
 
 

+ 2 - 3
packages/app/src/components/Admin/UserManagement.jsx

@@ -10,7 +10,7 @@ import PaginationWrapper from '../PaginationWrapper';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 
 
 
 
-// import InviteUserControl from './Users/InviteUserControl';
+import InviteUserControl from './Users/InviteUserControl';
 import PasswordResetModal from './Users/PasswordResetModal';
 import PasswordResetModal from './Users/PasswordResetModal';
 import UserTable from './Users/UserTable';
 import UserTable from './Users/UserTable';
 
 
@@ -150,8 +150,7 @@ class UserManagement extends React.Component {
           />
           />
         )}
         )}
         <p>
         <p>
-          {/* show  */}
-          {/* <InviteUserControl /> */}
+          <InviteUserControl />
           <a className="btn btn-outline-secondary ml-2" href="/admin/users/external-accounts" role="button">
           <a className="btn btn-outline-secondary ml-2" href="/admin/users/external-accounts" role="button">
             <i className="icon-user-follow" aria-hidden="true"></i>
             <i className="icon-user-follow" aria-hidden="true"></i>
             {t('admin:user_management.external_account')}
             {t('admin:user_management.external_account')}

+ 6 - 6
packages/app/src/components/Admin/Users/UserInviteModal.jsx

@@ -8,9 +8,10 @@ import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
+
 import AdminUsersContainer from '~/client/services/AdminUsersContainer';
 import AdminUsersContainer from '~/client/services/AdminUsersContainer';
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/apiNotification';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/apiNotification';
+import { useIsMailerSetup } from '~/stores/context';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 
@@ -99,9 +100,8 @@ class UserInviteModal extends React.Component {
   }
   }
 
 
   renderModalFooter() {
   renderModalFooter() {
-    const { t, appContainer } = this.props;
+    const { t, isMailerSetup } = this.props;
     const { isCreateUserButtonPushed } = this.state;
     const { isCreateUserButtonPushed } = this.state;
-    const { isMailerSetup } = appContainer.config;
 
 
     return (
     return (
       <>
       <>
@@ -282,18 +282,18 @@ class UserInviteModal extends React.Component {
 
 
 const UserInviteModalWrapperFC = (props) => {
 const UserInviteModalWrapperFC = (props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  return <UserInviteModal t={t} {...props} />;
+  const { data: isMailerSetup } = useIsMailerSetup();
+  return <UserInviteModal t={t} isMailerSetup={isMailerSetup ?? false} {...props} />;
 };
 };
 
 
 /**
 /**
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
-const UserInviteModalWrapper = withUnstatedContainers(UserInviteModalWrapperFC, [AppContainer, AdminUsersContainer]);
+const UserInviteModalWrapper = withUnstatedContainers(UserInviteModalWrapperFC, [AdminUsersContainer]);
 
 
 
 
 UserInviteModal.propTypes = {
 UserInviteModal.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
   adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
 };
 };
 
 

+ 2 - 8
packages/app/src/components/Layout/AdminLayout.tsx

@@ -1,9 +1,6 @@
 import React, { ReactNode } from 'react';
 import React, { ReactNode } from 'react';
 
 
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
-import { Provider } from 'unstated';
-
-import { AdminInjectableContainers } from '~/interfaces/unstated-container';
 
 
 import { GrowiNavbar } from '../Navbar/GrowiNavbar';
 import { GrowiNavbar } from '../Navbar/GrowiNavbar';
 
 
@@ -21,12 +18,11 @@ type Props = {
    */
    */
   selectedNavOpt: string
   selectedNavOpt: string
   children?: ReactNode
   children?: ReactNode
-  injectableContainers: AdminInjectableContainers
 }
 }
 
 
 
 
 const AdminLayout = ({
 const AdminLayout = ({
-  children, title, selectedNavOpt, injectableContainers,
+  children, title, selectedNavOpt,
 }: Props): JSX.Element => {
 }: Props): JSX.Element => {
 
 
   const AdminNavigation = dynamic(() => import('~/components/Admin/Common/AdminNavigation'), { ssr: false });
   const AdminNavigation = dynamic(() => import('~/components/Admin/Common/AdminNavigation'), { ssr: false });
@@ -46,9 +42,7 @@ const AdminLayout = ({
               <AdminNavigation selected={selectedNavOpt} />
               <AdminNavigation selected={selectedNavOpt} />
             </div>
             </div>
             <div className="col-lg-9">
             <div className="col-lg-9">
-              <Provider inject={injectableContainers}>
-                {children}
-              </Provider>
+              {children}
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>

+ 7 - 2
packages/app/src/components/Layout/BasicLayout.tsx

@@ -11,10 +11,13 @@ import { RawLayout } from './RawLayout';
 type Props = {
 type Props = {
   title: string
   title: string
   className?: string,
   className?: string,
+  expandContainer?: boolean,
   children?: ReactNode
   children?: ReactNode
 }
 }
 
 
-export const BasicLayout = ({ children, title, className }: Props): JSX.Element => {
+export const BasicLayout = ({
+  children, title, className, expandContainer,
+}: Props): JSX.Element => {
 
 
   // const HotkeysManager = dynamic(() => import('../client/js/components/Hotkeys/HotkeysManager'), { ssr: false });
   // const HotkeysManager = dynamic(() => import('../client/js/components/Hotkeys/HotkeysManager'), { ssr: false });
   // const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
   // const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
@@ -28,8 +31,10 @@ export const BasicLayout = ({ children, title, className }: Props): JSX.Element
   const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
   const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
   const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
   const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
 
 
+  const myClassName = `${className ?? ''} ${expandContainer ? 'growi-layout-fluid' : ''}`;
+
   return (
   return (
-    <RawLayout title={title} className={className}>
+    <RawLayout title={title} className={myClassName}>
       <GrowiNavbar />
       <GrowiNavbar />
 
 
       <div className="page-wrapper d-flex d-print-block">
       <div className="page-wrapper d-flex d-print-block">

+ 5 - 4
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -7,7 +7,7 @@ import { TabContent, TabPane } from 'reactstrap';
 
 
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import {
 import {
-  useCurrentPagePath, useIsSharedUser, useIsEditable, useIsUserPage, usePageUser, useShareLinkId, useIsNotFound,
+  useCurrentPagePath, useIsSharedUser, useIsEditable, useIsUserPage, usePageUser, useShareLinkId, useIsNotFound, useIsNotCreatable,
 } from '~/stores/context';
 } from '~/stores/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxCurrentPage } from '~/stores/page';
@@ -17,7 +17,6 @@ import CountBadge from '../Common/CountBadge';
 import PageListIcon from '../Icons/PageListIcon';
 import PageListIcon from '../Icons/PageListIcon';
 import NotFoundPage from '../NotFoundPage';
 import NotFoundPage from '../NotFoundPage';
 import { Page } from '../Page';
 import { Page } from '../Page';
-// import PageEditor from '../PageEditor';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
 // import PageEditorByHackmd from '../PageEditorByHackmd';
 import TableOfContents from '../TableOfContents';
 import TableOfContents from '../TableOfContents';
 import UserInfo from '../User/UserInfo';
 import UserInfo from '../User/UserInfo';
@@ -33,6 +32,7 @@ const { isTopPage } = pagePathUtils;
 const DisplaySwitcher = (): JSX.Element => {
 const DisplaySwitcher = (): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const PageEditor = dynamic(() => import('../PageEditor'), { ssr: false });
   const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
   const EditorNavbarBottom = dynamic(() => import('../PageEditor/EditorNavbarBottom'), { ssr: false });
   const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
   const HashChanged = dynamic(() => import('../EventListeneres/HashChanged'), { ssr: false });
   const ContentLinkButtons = dynamic(() => import('../ContentLinkButtons'), { ssr: false });
   const ContentLinkButtons = dynamic(() => import('../ContentLinkButtons'), { ssr: false });
@@ -48,6 +48,7 @@ const DisplaySwitcher = (): JSX.Element => {
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();
   const { data: pageUser } = usePageUser();
   const { data: pageUser } = usePageUser();
   const { data: isNotFound } = useIsNotFound();
   const { data: isNotFound } = useIsNotFound();
+  const { data: isNotCreatable } = useIsNotCreatable();
   const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
   const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
 
 
   const { data: editorMode } = useEditorMode();
   const { data: editorMode } = useEditorMode();
@@ -71,7 +72,7 @@ const DisplaySwitcher = (): JSX.Element => {
               { isNotFound && <NotFoundPage /> }
               { isNotFound && <NotFoundPage /> }
             </div>
             </div>
 
 
-            { !isNotFound && !currentPage?.isEmpty && (
+            { !isNotFound && (
               <div className="grw-side-contents-container">
               <div className="grw-side-contents-container">
                 <div className="grw-side-contents-sticky-container">
                 <div className="grw-side-contents-sticky-container">
 
 
@@ -125,7 +126,7 @@ const DisplaySwitcher = (): JSX.Element => {
         { isEditable && (
         { isEditable && (
           <TabPane tabId={EditorMode.Editor}>
           <TabPane tabId={EditorMode.Editor}>
             <div data-testid="page-editor" id="page-editor">
             <div data-testid="page-editor" id="page-editor">
-              {/* <PageEditor /> */}
+              <PageEditor />
             </div>
             </div>
           </TabPane>
           </TabPane>
         ) }
         ) }

+ 4 - 1
packages/app/src/components/PageAlert/PageAlerts.tsx

@@ -2,6 +2,8 @@ import React from 'react';
 
 
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 
 
+import { useIsNotFound } from '~/stores/context';
+
 import { FixPageGrantAlert } from './FixPageGrantAlert';
 import { FixPageGrantAlert } from './FixPageGrantAlert';
 import { OldRevisionAlert } from './OldRevisionAlert';
 import { OldRevisionAlert } from './OldRevisionAlert';
 import { PageGrantAlert } from './PageGrantAlert';
 import { PageGrantAlert } from './PageGrantAlert';
@@ -12,12 +14,13 @@ const TrashPageAlert = dynamic(() => import('./TrashPageAlert').then(mod => mod.
 
 
 export const PageAlerts = (): JSX.Element => {
 export const PageAlerts = (): JSX.Element => {
 
 
+  const { data: isNotFound } = useIsNotFound();
 
 
   return (
   return (
     <div className="row d-edit-none">
     <div className="row d-edit-none">
       <div className="col-sm-12">
       <div className="col-sm-12">
         {/* alerts */}
         {/* alerts */}
-        <FixPageGrantAlert />
+        { !isNotFound && <FixPageGrantAlert /> }
         <PageGrantAlert />
         <PageGrantAlert />
         <TrashPageAlert />
         <TrashPageAlert />
         <PageStaleAlert />
         <PageStaleAlert />

+ 1 - 1
packages/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -34,7 +34,7 @@ export const TrashPageAlert = (): JSX.Element => {
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openPutBackPageModal } = usePutBackPageModal();
   const { open: openPutBackPageModal } = usePutBackPageModal();
 
 
-  const lastUpdateUserName = pageData?.lastUpdateUser.name;
+  const lastUpdateUserName = pageData?.lastUpdateUser?.name;
   const deletedAt = pageData?.deletedAt ? format(new Date(pageData?.deletedAt), 'yyyy/MM/dd HH:mm') : '';
   const deletedAt = pageData?.deletedAt ? format(new Date(pageData?.deletedAt), 'yyyy/MM/dd HH:mm') : '';
   const revisionId = pageData?.revision?._id;
   const revisionId = pageData?.revision?._id;
 
 

+ 4 - 7
packages/app/src/components/PageComment/CommentEditor.tsx

@@ -14,7 +14,7 @@ import { RendererOptions } from '~/services/renderer/renderer';
 import { useSWRxPageComment } from '~/stores/comment';
 import { useSWRxPageComment } from '~/stores/comment';
 import {
 import {
   useCurrentPagePath, useCurrentPageId, useCurrentUser, useRevisionId, useIsSlackConfigured,
   useCurrentPagePath, useCurrentPageId, useCurrentUser, useRevisionId, useIsSlackConfigured,
-  useEditorConfig,
+  useIsUploadableFile, useIsUploadableImage,
 } from '~/stores/context';
 } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import { useIsMobile } from '~/stores/ui';
 import { useIsMobile } from '~/stores/ui';
@@ -73,7 +73,8 @@ export const CommentEditor = (props: PropsType): JSX.Element => {
   const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
   const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
   const { data: isSlackConfigured } = useIsSlackConfigured();
   const { data: isSlackConfigured } = useIsSlackConfigured();
-  const { data: editorConfig } = useEditorConfig();
+  const { data: isUploadableFile } = useIsUploadableFile();
+  const { data: isUploadableImage } = useIsUploadableImage();
 
 
   const [isReadyToUse, setIsReadyToUse] = useState(!isForNewComment);
   const [isReadyToUse, setIsReadyToUse] = useState(!isForNewComment);
   // TODO: Refactor comment and markdown variable names or logic after presentation
   // TODO: Refactor comment and markdown variable names or logic after presentation
@@ -262,11 +263,7 @@ export const CommentEditor = (props: PropsType): JSX.Element => {
     // TODO: typescriptize Editor
     // TODO: typescriptize Editor
     const AnyEditor = Editor as any;
     const AnyEditor = Editor as any;
 
 
-    if (editorConfig === undefined) {
-      return <></>;
-    }
-    const isUploadable = editorConfig.upload.isImageUploaded || editorConfig.upload.isFileUploaded;
-    const isUploadableFile = editorConfig.upload.isFileUploaded;
+    const isUploadable = isUploadableImage || isUploadableFile;
 
 
     return (
     return (
       <>
       <>

+ 0 - 1
packages/app/src/components/PageComment/CommentEditorLazyRenderer.tsx

@@ -8,7 +8,6 @@ import { useSWRxPageComment } from '../../stores/comment';
 
 
 import { CommentEditor } from './CommentEditor';
 import { CommentEditor } from './CommentEditor';
 
 
-
 type Props = {
 type Props = {
   pageId?: Nullable<string>,
   pageId?: Nullable<string>,
   rendererOptions: RendererOptions,
   rendererOptions: RendererOptions,

+ 104 - 102
packages/app/src/components/PageEditor.tsx

@@ -8,31 +8,32 @@ import { envUtils } from '@growi/core';
 import detectIndent from 'detect-indent';
 import detectIndent from 'detect-indent';
 import { throttle, debounce } from 'throttle-debounce';
 import { throttle, debounce } from 'throttle-debounce';
 
 
-import AppContainer from '~/client/services/AppContainer';
-import EditorContainer from '~/client/services/EditorContainer';
-import PageContainer from '~/client/services/PageContainer';
+// import AppContainer from '~/client/services/AppContainer';
+// import EditorContainer from '~/client/services/EditorContainer';
+// import PageContainer from '~/client/services/PageContainer';
 import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
 import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
 import { getOptionsToSave } from '~/client/util/editor';
 import { getOptionsToSave } from '~/client/util/editor';
 import {
 import {
-  useIsEditable, useIsIndentSizeForced, useCurrentPagePath, useCurrentPageId,
+  useIsEditable, useIsIndentSizeForced, useCurrentPagePath, useCurrentPageId, useIsUploadableFile, useIsUploadableImage,
 } from '~/stores/context';
 } from '~/stores/context';
 import {
 import {
   useCurrentIndentSize, useSWRxSlackChannels, useIsSlackEnabled, useIsTextlintEnabled, usePageTagsForEditors,
   useCurrentIndentSize, useSWRxSlackChannels, useIsSlackEnabled, useIsTextlintEnabled, usePageTagsForEditors,
   useIsEnabledUnsavedWarning,
   useIsEnabledUnsavedWarning,
 } from '~/stores/editor';
 } from '~/stores/editor';
+import { useSWRxCurrentPage } from '~/stores/page';
 import { usePreviewOptions } from '~/stores/renderer';
 import { usePreviewOptions } from '~/stores/renderer';
 import {
 import {
   EditorMode,
   EditorMode,
-  useEditorMode, useIsMobile, useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
+  useEditorMode, useIsMobile, useSelectedGrant,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 
 
-import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
+// import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
 import Editor from './PageEditor/Editor';
 import Editor from './PageEditor/Editor';
 import Preview from './PageEditor/Preview';
 import Preview from './PageEditor/Preview';
 import scrollSyncHelper from './PageEditor/ScrollSyncHelper';
 import scrollSyncHelper from './PageEditor/ScrollSyncHelper';
-import { withUnstatedContainers } from './UnstatedUtils';
+// import { withUnstatedContainers } from './UnstatedUtils';
 
 
 
 
 // TODO: remove this when omitting unstated is completed
 // TODO: remove this when omitting unstated is completed
@@ -52,26 +53,26 @@ type EditorRef = {
 }
 }
 
 
 type Props = {
 type Props = {
-  appContainer: AppContainer,
-  pageContainer: PageContainer,
-  editorContainer: EditorContainer,
-
-  isEditable: boolean,
-
-  editorMode: string,
-  isSlackEnabled: boolean,
-  slackChannels: string,
-  isMobile?: boolean,
-
-  grant: number,
-  grantGroupId?: string,
-  grantGroupName?: string,
-  mutateGrant: (grant: number) => void,
-
-  isTextlintEnabled?: boolean,
-  isIndentSizeForced?: boolean,
-  indentSize?: number,
-  mutateCurrentIndentSize: (indent: number) => void,
+  // appContainer: AppContainer,
+  // pageContainer: PageContainer,
+  // editorContainer: EditorContainer,
+
+  // isEditable: boolean,
+
+  // editorMode: string,
+  // isSlackEnabled: boolean,
+  // slackChannels: string,
+  // isMobile?: boolean,
+
+  // grant: number,
+  // grantGroupId?: string,
+  // grantGroupName?: string,
+  // mutateGrant: (grant: number) => void,
+
+  // isTextlintEnabled?: boolean,
+  // isIndentSizeForced?: boolean,
+  // indentSize?: number,
+  // mutateCurrentIndentSize: (indent: number) => void,
 };
 };
 
 
 // for scrolling
 // for scrolling
@@ -80,9 +81,9 @@ let isOriginOfScrollSyncEditor = false;
 let isOriginOfScrollSyncPreview = false;
 let isOriginOfScrollSyncPreview = false;
 
 
 const PageEditor = (props: Props): JSX.Element => {
 const PageEditor = (props: Props): JSX.Element => {
-  const {
-    appContainer, pageContainer, editorContainer,
-  } = props;
+  // const {
+  //   appContainer, pageContainer, editorContainer,
+  // } = props;
 
 
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();
   const { data: editorMode } = useEditorMode();
   const { data: editorMode } = useEditorMode();
@@ -92,76 +93,75 @@ const PageEditor = (props: Props): JSX.Element => {
   const { data: pageTags } = usePageTagsForEditors(pageId);
   const { data: pageTags } = usePageTagsForEditors(pageId);
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
-  const { data: grant, mutate: mutateGrant } = useSelectedGrant();
-  const { data: grantGroupId } = useSelectedGrantGroupId();
-  const { data: grantGroupName } = useSelectedGrantGroupName();
+  const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
   const { data: isTextlintEnabled } = useIsTextlintEnabled();
   const { data: isTextlintEnabled } = useIsTextlintEnabled();
   const { data: isIndentSizeForced } = useIsIndentSizeForced();
   const { data: isIndentSizeForced } = useIsIndentSizeForced();
   const { data: indentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
   const { data: indentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
+  const { data: isUploadableFile } = useIsUploadableFile();
+  const { data: isUploadableImage } = useIsUploadableImage();
+  const { data: currentPage } = useSWRxCurrentPage();
 
 
   const { data: rendererOptions } = usePreviewOptions();
   const { data: rendererOptions } = usePreviewOptions();
 
 
-  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-  const [markdown, setMarkdown] = useState<string>(pageContainer.state.markdown!);
+  const [markdown, setMarkdown] = useState<string>('');
+
+  useEffect(() => {
+    if (currentPage != null) {
+      setMarkdown(currentPage.revision?.body);
+    }
+  }, [currentPage, currentPage?.revision?.body]);
 
 
 
 
   const editorRef = useRef<EditorRef>(null);
   const editorRef = useRef<EditorRef>(null);
   const previewRef = useRef<HTMLDivElement>(null);
   const previewRef = useRef<HTMLDivElement>(null);
 
 
   const setMarkdownWithDebounce = useMemo(() => debounce(50, throttle(100, value => setMarkdown(value))), []);
   const setMarkdownWithDebounce = useMemo(() => debounce(50, throttle(100, value => setMarkdown(value))), []);
-  const saveDraftWithDebounce = useMemo(() => debounce(800, () => {
-    editorContainer.saveDraft(pageContainer.state.path, markdown);
-  }), [editorContainer, markdown, pageContainer.state.path]);
+  // const saveDraftWithDebounce = useMemo(() => debounce(800, () => {
+  //   editorContainer.saveDraft(pageContainer.state.path, markdown);
+  // }), [editorContainer, markdown, pageContainer.state.path]);
 
 
   const markdownChangedHandler = useCallback((value: string): void => {
   const markdownChangedHandler = useCallback((value: string): void => {
     setMarkdownWithDebounce(value);
     setMarkdownWithDebounce(value);
     // only when the first time to edit
     // only when the first time to edit
-    if (!pageContainer.state.revisionId) {
-      saveDraftWithDebounce();
-    }
-  }, [pageContainer.state.revisionId, saveDraftWithDebounce, setMarkdownWithDebounce]);
+    // if (!pageContainer.state.revisionId) {
+    //   saveDraftWithDebounce();
+    // }
+  // }, [pageContainer.state.revisionId, saveDraftWithDebounce, setMarkdownWithDebounce]);
+  }, [setMarkdownWithDebounce]);
 
 
 
 
   const saveWithShortcut = useCallback(async() => {
   const saveWithShortcut = useCallback(async() => {
-    if (grant == null) {
+    if (grantData == null) {
       return;
       return;
     }
     }
 
 
     const slackChannels = slackChannelsData ? slackChannelsData.toString() : '';
     const slackChannels = slackChannelsData ? slackChannelsData.toString() : '';
 
 
-    const optionsToSave = getOptionsToSave(isSlackEnabled ?? false, slackChannels, grant, grantGroupId, grantGroupName, pageTags || []);
+    const optionsToSave = getOptionsToSave(
+      isSlackEnabled ?? false, slackChannels,
+      grantData.grant, grantData.grantedGroup?.id, grantData.grantedGroup?.name,
+      pageTags || [],
+    );
 
 
     try {
     try {
       // disable unsaved warning
       // disable unsaved warning
       mutateIsEnabledUnsavedWarning(false);
       mutateIsEnabledUnsavedWarning(false);
 
 
       // eslint-disable-next-line no-unused-vars
       // eslint-disable-next-line no-unused-vars
-      const { tags } = await pageContainer.save(markdown, editorMode, optionsToSave);
+      // const { tags } = await pageContainer.save(markdown, editorMode, optionsToSave);
       logger.debug('success to save');
       logger.debug('success to save');
 
 
-      pageContainer.showSuccessToastr();
+      // pageContainer.showSuccessToastr();
 
 
       // update state of EditorContainer
       // update state of EditorContainer
-      editorContainer.setState({ tags });
+      // editorContainer.setState({ tags });
     }
     }
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
-      pageContainer.showErrorToastr(error);
+      // pageContainer.showErrorToastr(error);
     }
     }
-  }, [
-    editorContainer,
-    editorMode,
-    grant,
-    grantGroupId,
-    grantGroupName,
-    isSlackEnabled,
-    slackChannelsData,
-    markdown,
-    pageContainer,
-    pageTags,
-    mutateIsEnabledUnsavedWarning,
-  ]);
+  }, [grantData, isSlackEnabled, slackChannelsData, pageTags, mutateIsEnabledUnsavedWarning]);
 
 
 
 
   /**
   /**
@@ -184,10 +184,10 @@ const PageEditor = (props: Props): JSX.Element => {
       }
       }
 
 
       const formData = new FormData();
       const formData = new FormData();
-      const { pageId, path } = pageContainer.state;
+      // const { pageId, path } = pageContainer.state;
       formData.append('file', file);
       formData.append('file', file);
-      if (path != null) {
-        formData.append('path', path);
+      if (currentPagePath != null) {
+        formData.append('path', currentPagePath);
       }
       }
       if (pageId != null) {
       if (pageId != null) {
         formData.append('page_id', pageId);
         formData.append('page_id', pageId);
@@ -208,18 +208,19 @@ const PageEditor = (props: Props): JSX.Element => {
       // when if created newly
       // when if created newly
       if (res.pageCreated) {
       if (res.pageCreated) {
         logger.info('Page is created', res.page._id);
         logger.info('Page is created', res.page._id);
-        pageContainer.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
+        // pageContainer.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
         mutateGrant(res.page.grant);
         mutateGrant(res.page.grant);
       }
       }
     }
     }
     catch (e) {
     catch (e) {
       logger.error('failed to upload', e);
       logger.error('failed to upload', e);
-      pageContainer.showErrorToastr(e);
+      // pageContainer.showErrorToastr(e);
     }
     }
     finally {
     finally {
       editorRef.current.terminateUploadingState();
       editorRef.current.terminateUploadingState();
     }
     }
-  }, [editorMode, mutateGrant, pageContainer]);
+  // }, [editorMode, mutateGrant, pageContainer]);
+  }, [editorMode, mutateGrant]);
 
 
 
 
   const scrollPreviewByEditorLine = useCallback((line: number) => {
   const scrollPreviewByEditorLine = useCallback((line: number) => {
@@ -274,7 +275,9 @@ const PageEditor = (props: Props): JSX.Element => {
 
 
     // turn on the flag
     // turn on the flag
     isOriginOfScrollSyncEditor = true;
     isOriginOfScrollSyncEditor = true;
-    scrollSyncHelper.scrollPreviewToRevealOverflowing(previewRef.current, line);
+    if (previewRef.current != null) {
+      scrollSyncHelper.scrollPreviewToRevealOverflowing(previewRef.current, line);
+    }
   }, []);
   }, []);
   const scrollPreviewByCursorMovingWithThrottle = useMemo(() => throttle(20, scrollPreviewByCursorMoving), [scrollPreviewByCursorMoving]);
   const scrollPreviewByCursorMovingWithThrottle = useMemo(() => throttle(20, scrollPreviewByCursorMoving), [scrollPreviewByCursorMoving]);
 
 
@@ -314,14 +317,14 @@ const PageEditor = (props: Props): JSX.Element => {
 
 
 
 
   // register dummy instance to get markdown
   // register dummy instance to get markdown
-  useEffect(() => {
-    const pageEditorInstance = {
-      getMarkdown: () => {
-        return markdown;
-      },
-    };
-    appContainer.registerComponentInstance('PageEditor', pageEditorInstance);
-  }, [appContainer, markdown]);
+  // useEffect(() => {
+  //   const pageEditorInstance = {
+  //     getMarkdown: () => {
+  //       return markdown;
+  //     },
+  //   };
+  //   appContainer.registerComponentInstance('PageEditor', pageEditorInstance);
+  // }, [appContainer, markdown]);
 
 
   // initial caret line
   // initial caret line
   useEffect(() => {
   useEffect(() => {
@@ -369,23 +372,23 @@ const PageEditor = (props: Props): JSX.Element => {
   }, []);
   }, []);
 
 
   // Displays an alert if there is a difference with pageContainer's markdown
   // Displays an alert if there is a difference with pageContainer's markdown
-  useEffect(() => {
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    if (pageContainer.state.markdown! !== markdown) {
-      mutateIsEnabledUnsavedWarning(true);
-    }
-  }, [editorContainer, markdown, mutateIsEnabledUnsavedWarning, pageContainer.state.markdown]);
+  // useEffect(() => {
+  //   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+  //   if (pageContainer.state.markdown! !== markdown) {
+  //     mutateIsEnabledUnsavedWarning(true);
+  //   }
+  // }, [editorContainer, markdown, mutateIsEnabledUnsavedWarning, pageContainer.state.markdown]);
 
 
   // Detect indent size from contents (only when users are allowed to change it)
   // Detect indent size from contents (only when users are allowed to change it)
-  useEffect(() => {
-    const currentPageMarkdown = pageContainer.state.markdown;
-    if (!isIndentSizeForced && currentPageMarkdown != null) {
-      const detectedIndent = detectIndent(currentPageMarkdown);
-      if (detectedIndent.type === 'space' && new Set([2, 4]).has(detectedIndent.amount)) {
-        mutateCurrentIndentSize(detectedIndent.amount);
-      }
-    }
-  }, [isIndentSizeForced, mutateCurrentIndentSize, pageContainer.state.markdown]);
+  // useEffect(() => {
+  //   const currentPageMarkdown = pageContainer.state.markdown;
+  //   if (!isIndentSizeForced && currentPageMarkdown != null) {
+  //     const detectedIndent = detectIndent(currentPageMarkdown);
+  //     if (detectedIndent.type === 'space' && new Set([2, 4]).has(detectedIndent.amount)) {
+  //       mutateCurrentIndentSize(detectedIndent.amount);
+  //     }
+  //   }
+  // }, [isIndentSizeForced, mutateCurrentIndentSize, pageContainer.state.markdown]);
 
 
 
 
   if (!isEditable) {
   if (!isEditable) {
@@ -396,12 +399,10 @@ const PageEditor = (props: Props): JSX.Element => {
     return <></>;
     return <></>;
   }
   }
 
 
-  const config = props.appContainer.getConfig();
-  const isUploadable = config.upload.image || config.upload.file;
-  const isUploadableFile = config.upload.file;
-  const isMathJaxEnabled = !!config.env.MATHJAX;
+  // const config = props.appContainer.getConfig();
+  // const isUploadable = config.upload.image || config.upload.file;
+  const isUploadable = isUploadableImage || isUploadableFile;
 
 
-  const noCdn = envUtils.toBoolean(config.env.NO_CDN);
 
 
   // TODO: omit no-explicit-any -- 2022.06.02 Yuki Takei
   // TODO: omit no-explicit-any -- 2022.06.02 Yuki Takei
   // It is impossible to avoid the error
   // It is impossible to avoid the error
@@ -410,13 +411,14 @@ const PageEditor = (props: Props): JSX.Element => {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   const EditorAny = Editor as any;
   const EditorAny = Editor as any;
 
 
+  // console.log('EditorAny', markdown);
+
   return (
   return (
     <div className="d-flex flex-wrap">
     <div className="d-flex flex-wrap">
       <div className="page-editor-editor-container flex-grow-1 flex-basis-0 mw-0">
       <div className="page-editor-editor-container flex-grow-1 flex-basis-0 mw-0">
         <EditorAny
         <EditorAny
           ref={editorRef}
           ref={editorRef}
           value={markdown}
           value={markdown}
-          noCdn={noCdn}
           isMobile={isMobile}
           isMobile={isMobile}
           isUploadable={isUploadable}
           isUploadable={isUploadable}
           isUploadableFile={isUploadableFile}
           isUploadableFile={isUploadableFile}
@@ -434,17 +436,16 @@ const PageEditor = (props: Props): JSX.Element => {
           markdown={markdown}
           markdown={markdown}
           rendererOptions={rendererOptions}
           rendererOptions={rendererOptions}
           ref={previewRef}
           ref={previewRef}
-          // isMathJaxEnabled={isMathJaxEnabled}
           renderMathJaxOnInit={false}
           renderMathJaxOnInit={false}
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
         />
         />
       </div>
       </div>
-      <ConflictDiffModal
+      {/* <ConflictDiffModal
         isOpen={pageContainer.state.isConflictDiffModalOpen}
         isOpen={pageContainer.state.isConflictDiffModalOpen}
         onClose={() => pageContainer.setState({ isConflictDiffModalOpen: false })}
         onClose={() => pageContainer.setState({ isConflictDiffModalOpen: false })}
         pageContainer={pageContainer}
         pageContainer={pageContainer}
         markdownOnEdit={markdown}
         markdownOnEdit={markdown}
-      />
+      /> */}
     </div>
     </div>
   );
   );
 };
 };
@@ -452,6 +453,7 @@ const PageEditor = (props: Props): JSX.Element => {
 /**
 /**
    * Wrapper component for using unstated
    * Wrapper component for using unstated
    */
    */
-const PageEditorWrapper = withUnstatedContainers(PageEditor, [AppContainer, PageContainer, EditorContainer]);
+// const PageEditorWrapper = withUnstatedContainers(PageEditor, [AppContainer, PageContainer, EditorContainer]);
 
 
-export default PageEditorWrapper;
+// export default PageEditorWrapper;
+export default PageEditor;

+ 26 - 22
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -16,14 +16,14 @@ import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
 
 
 import AbstractEditor from './AbstractEditor';
 import AbstractEditor from './AbstractEditor';
 import CommentMentionHelper from './CommentMentionHelper';
 import CommentMentionHelper from './CommentMentionHelper';
-import DrawioModal from './DrawioModal';
+// import DrawioModal from './DrawioModal';
 import EditorIcon from './EditorIcon';
 import EditorIcon from './EditorIcon';
 import EmojiPicker from './EmojiPicker';
 import EmojiPicker from './EmojiPicker';
 import EmojiPickerHelper from './EmojiPickerHelper';
 import EmojiPickerHelper from './EmojiPickerHelper';
-import GridEditModal from './GridEditModal';
+// import GridEditModal from './GridEditModal';
 import geu from './GridEditorUtil';
 import geu from './GridEditorUtil';
-import HandsontableModal from './HandsontableModal';
-import LinkEditModal from './LinkEditModal';
+// import HandsontableModal from './HandsontableModal';
+// import LinkEditModal from './LinkEditModal';
 import mdu from './MarkdownDrawioUtil';
 import mdu from './MarkdownDrawioUtil';
 import mlu from './MarkdownLinkUtil';
 import mlu from './MarkdownLinkUtil';
 import MarkdownTableInterceptor from './MarkdownTableInterceptor';
 import MarkdownTableInterceptor from './MarkdownTableInterceptor';
@@ -32,6 +32,8 @@ import pasteHelper from './PasteHelper';
 import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
 import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
 import SimpleCheatsheet from './SimpleCheatsheet';
 import SimpleCheatsheet from './SimpleCheatsheet';
 
 
+import styles from './CodeMirrorEditor.module.scss';
+
 // Textlint
 // Textlint
 window.JSHINT = JSHINT;
 window.JSHINT = JSHINT;
 window.kuromojin = { dicPath: '/static/dict' };
 window.kuromojin = { dicPath: '/static/dict' };
@@ -50,7 +52,6 @@ require('codemirror/addon/edit/matchtags');
 require('codemirror/addon/edit/closetag');
 require('codemirror/addon/edit/closetag');
 require('codemirror/addon/edit/continuelist');
 require('codemirror/addon/edit/continuelist');
 require('codemirror/addon/hint/show-hint');
 require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/hint/show-hint.css');
 require('codemirror/addon/search/searchcursor');
 require('codemirror/addon/search/searchcursor');
 require('codemirror/addon/search/match-highlighter');
 require('codemirror/addon/search/match-highlighter');
 require('codemirror/addon/selection/active-line');
 require('codemirror/addon/selection/active-line');
@@ -58,12 +59,10 @@ require('codemirror/addon/scroll/annotatescrollbar');
 require('codemirror/addon/scroll/scrollpastend');
 require('codemirror/addon/scroll/scrollpastend');
 require('codemirror/addon/fold/foldcode');
 require('codemirror/addon/fold/foldcode');
 require('codemirror/addon/fold/foldgutter');
 require('codemirror/addon/fold/foldgutter');
-require('codemirror/addon/fold/foldgutter.css');
 require('codemirror/addon/fold/markdown-fold');
 require('codemirror/addon/fold/markdown-fold');
 require('codemirror/addon/fold/brace-fold');
 require('codemirror/addon/fold/brace-fold');
 require('codemirror/addon/display/placeholder');
 require('codemirror/addon/display/placeholder');
 require('codemirror/addon/lint/lint');
 require('codemirror/addon/lint/lint');
-require('codemirror/addon/lint/lint.css');
 require('~/client/util/codemirror/autorefresh.ext');
 require('~/client/util/codemirror/autorefresh.ext');
 require('~/client/util/codemirror/drawio-fold.ext');
 require('~/client/util/codemirror/drawio-fold.ext');
 require('~/client/util/codemirror/gfm-growi.mode');
 require('~/client/util/codemirror/gfm-growi.mode');
@@ -790,19 +789,19 @@ class CodeMirrorEditor extends AbstractEditor {
   }
   }
 
 
   showGridEditorHandler() {
   showGridEditorHandler() {
-    this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
+    // this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
   }
   }
 
 
   showLinkEditHandler() {
   showLinkEditHandler() {
-    this.linkEditModal.current.show(mlu.getMarkdownLink(this.getCodeMirror()));
+    // this.linkEditModal.current.show(mlu.getMarkdownLink(this.getCodeMirror()));
   }
   }
 
 
   showHandsonTableHandler() {
   showHandsonTableHandler() {
-    this.handsontableModal.current.show(mtu.getMarkdownTable(this.getCodeMirror()));
+    // this.handsontableModal.current.show(mtu.getMarkdownTable(this.getCodeMirror()));
   }
   }
 
 
   showDrawioHandler() {
   showDrawioHandler() {
-    this.drawioModal.current.show(mdu.getMarkdownDrawioMxfile(this.getCodeMirror()));
+    // this.drawioModal.current.show(mdu.getMarkdownDrawioMxfile(this.getCodeMirror()));
   }
   }
 
 
 
 
@@ -988,8 +987,11 @@ class CodeMirrorEditor extends AbstractEditor {
       gutters.push('CodeMirror-lint-markers');
       gutters.push('CodeMirror-lint-markers');
     }
     }
 
 
+    console.log(' this.state.value', this.state.value);
+    console.log(' this.props.value', this.props.value);
+
     return (
     return (
-      <React.Fragment>
+      <div className={`grw-codemirror-editor ${styles['grw-codemirror-editor']}`}>
 
 
         <UncontrolledCodeMirror
         <UncontrolledCodeMirror
           ref={(c) => { this.cm = c }}
           ref={(c) => { this.cm = c }}
@@ -1000,7 +1002,9 @@ class CodeMirrorEditor extends AbstractEditor {
             editor.on('paste', this.pasteHandler);
             editor.on('paste', this.pasteHandler);
             editor.on('scrollCursorIntoView', this.scrollCursorIntoViewHandler);
             editor.on('scrollCursorIntoView', this.scrollCursorIntoViewHandler);
           }}
           }}
-          value={this.state.value}
+          // temporary set props.value
+          // value={this.state.value}
+          value={this.props.value}
           options={{
           options={{
             indentUnit: this.props.indentSize,
             indentUnit: this.props.indentSize,
             theme: this.props.editorSettings.theme ?? 'elegant',
             theme: this.props.editorSettings.theme ?? 'elegant',
@@ -1052,25 +1056,25 @@ class CodeMirrorEditor extends AbstractEditor {
         { this.renderCheatsheetOverlay() }
         { this.renderCheatsheetOverlay() }
         { this.renderEmojiPicker() }
         { this.renderEmojiPicker() }
 
 
-        <GridEditModal
+        {/* <GridEditModal
           ref={this.gridEditModal}
           ref={this.gridEditModal}
           onSave={(grid) => { return geu.replaceGridWithHtmlWithEditor(this.getCodeMirror(), grid) }}
           onSave={(grid) => { return geu.replaceGridWithHtmlWithEditor(this.getCodeMirror(), grid) }}
-        />
-        <LinkEditModal
+        /> */}
+        {/* <LinkEditModal
           ref={this.linkEditModal}
           ref={this.linkEditModal}
           onSave={(linkText) => { return mlu.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
           onSave={(linkText) => { return mlu.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}
-        />
-        <HandsontableModal
+        /> */}
+        {/* <HandsontableModal
           ref={this.handsontableModal}
           ref={this.handsontableModal}
           onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
           onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
           autoFormatMarkdownTable={this.props.editorSettings.autoFormatMarkdownTable}
           autoFormatMarkdownTable={this.props.editorSettings.autoFormatMarkdownTable}
-        />
-        <DrawioModal
+        /> */}
+        {/* <DrawioModal
           ref={this.drawioModal}
           ref={this.drawioModal}
           onSave={this.onSaveForDrawio}
           onSave={this.onSaveForDrawio}
-        />
+        /> */}
 
 
-      </React.Fragment>
+      </div>
     );
     );
   }
   }
 
 

+ 93 - 0
packages/app/src/components/PageEditor/CodeMirrorEditor.module.scss

@@ -0,0 +1,93 @@
+@use '~/styles/variables' as var;
+@use '~/styles/bootstrap/init' as bs;
+
+.grw-codemirror-editor :global {
+  @import '~codemirror/lib/codemirror';
+
+  // addons
+  @import '~codemirror/addon/hint/show-hint';
+  @import '~codemirror/addon/fold/foldgutter';
+  @import '~codemirror/addon/lint/lint';
+
+  // themes
+  @import '~codemirror/theme/elegant';
+  @import '~codemirror/theme/eclipse';
+
+  .CodeMirror {
+    pre.CodeMirror-line.grw-cm-header-line {
+      padding-top: 0.16em;
+      padding-bottom: 0.08em;
+      font-family: bs.$font-family-monospace;
+
+      // '#'
+      .cm-formatting-header {
+        font-style: italic;
+        font-weight: bold;
+        opacity: 0.5;
+      }
+
+      .cm-header-1 {
+        font-size: 1.9em;
+      }
+      .cm-header-2 {
+        font-size: 1.6em;
+      }
+      .cm-header-3 {
+        font-size: 1.4em;
+      }
+      .cm-header-4 {
+        font-size: 1.35em;
+      }
+      .cm-header-5 {
+        font-size: 1.25em;
+      }
+      .cm-header-6 {
+        font-size: 1.2em;
+      }
+    }
+
+    .cm-matchhighlight {
+      color: bs.$gray-900 !important;
+      background-color: cyan;
+    }
+
+    .CodeMirror-selection-highlight-scrollbar {
+      background-color: darkcyan;
+    }
+
+    // overwrite .CodeMirror-placeholder
+    pre.CodeMirror-line-like.CodeMirror-placeholder {
+      color: bs.$text-muted;
+    }
+  }
+
+  // patch to fix https://github.com/codemirror/CodeMirror/issues/4089
+  // see also https://github.com/codemirror/CodeMirror/commit/51a1e7da60a99e019f026a118dc7c98c2b1f9d62
+  .CodeMirror-wrap > div > textarea {
+    font-size: #{bs.$line-height-base}rem;
+  }
+
+  // overwrite .CodeMirror-hints
+  .CodeMirror-hints {
+    max-height: 30em !important;
+
+    .CodeMirror-hint.crowi-emoji-autocomplete {
+      font-family: var.$font-family-monospace-not-strictly;
+      line-height: 1.6em;
+
+      .img-container {
+        display: inline-block;
+        width: 30px;
+      }
+    }
+
+    // active line
+    .CodeMirror-hint-active.crowi-emoji-autocomplete {
+      .img-container {
+        padding-top: 0.3em;
+        padding-bottom: 0.3em;
+        font-size: 15px; // adjust to .wiki
+      }
+    }
+  }
+}

+ 1 - 8
packages/app/src/components/PageEditor/EditorNavbarBottom.tsx

@@ -1,10 +1,8 @@
 import React, { useCallback, useState, useEffect } from 'react';
 import React, { useCallback, useState, useEffect } from 'react';
 
 
-import PropTypes from 'prop-types';
 import { Collapse, Button } from 'reactstrap';
 import { Collapse, Button } from 'reactstrap';
 
 
 
 
-import EditorContainer from '~/client/services/EditorContainer';
 import { useCurrentPagePath, useIsSlackConfigured } from '~/stores/context';
 import { useCurrentPagePath, useIsSlackConfigured } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import {
 import {
@@ -14,7 +12,6 @@ import {
 import SavePageControls from '../SavePageControls';
 import SavePageControls from '../SavePageControls';
 import SlackLogo from '../SlackLogo';
 import SlackLogo from '../SlackLogo';
 import { SlackNotification } from '../SlackNotification';
 import { SlackNotification } from '../SlackNotification';
-import { withUnstatedContainers } from '../UnstatedUtils';
 
 
 
 
 import OptionsSelector from './OptionsSelector';
 import OptionsSelector from './OptionsSelector';
@@ -152,8 +149,4 @@ const EditorNavbarBottom = (props) => {
   );
   );
 };
 };
 
 
-EditorNavbarBottom.propTypes = {
-  editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
-};
-
-export default withUnstatedContainers(EditorNavbarBottom, [EditorContainer]);
+export default EditorNavbarBottom;

+ 6 - 69
packages/app/src/components/PageEditor/Preview.tsx

@@ -1,17 +1,16 @@
 import React, {
 import React, {
-  useCallback, useEffect, useMemo, useState, SyntheticEvent, RefObject,
+  SyntheticEvent, RefObject,
 } from 'react';
 } from 'react';
 
 
-import InterceptorManager from '~/services/interceptor-manager';
+import ReactMarkdown from 'react-markdown';
+
+
 import { RendererOptions } from '~/services/renderer/renderer';
 import { RendererOptions } from '~/services/renderer/renderer';
 import { useEditorSettings } from '~/stores/editor';
 import { useEditorSettings } from '~/stores/editor';
 
 
 import RevisionBody from '../Page/RevisionBody';
 import RevisionBody from '../Page/RevisionBody';
 
 
 
 
-declare const interceptorManager: InterceptorManager;
-
-
 type Props = {
 type Props = {
   rendererOptions: RendererOptions,
   rendererOptions: RendererOptions,
   markdown?: string,
   markdown?: string,
@@ -27,59 +26,8 @@ const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>):
     markdown, pagePath,
     markdown, pagePath,
   } = props;
   } = props;
 
 
-  const [html, setHtml] = useState('');
-
   const { data: editorSettings } = useEditorSettings();
   const { data: editorSettings } = useEditorSettings();
 
 
-  const context = useMemo(() => {
-    return {
-      markdown,
-      pagePath,
-      renderDrawioInRealtime: editorSettings?.renderDrawioInRealtime,
-      currentPathname: decodeURIComponent(window.location.pathname),
-      parsedHTML: null,
-    };
-  }, [markdown, pagePath, editorSettings?.renderDrawioInRealtime]);
-
-  const renderPreview = useCallback(async() => {
-
-    // TODO: use ReactMarkdown
-
-    // if (interceptorManager != null) {
-    //   await interceptorManager.process('preRenderPreview', context);
-    //   await interceptorManager.process('prePreProcess', context);
-    //   context.markdown = rendererOptions.preProcess(context.markdown, context);
-    //   await interceptorManager.process('postPreProcess', context);
-    //   context.parsedHTML = rendererOptions.process(context.markdown, context);
-    //   await interceptorManager.process('prePostProcess', context);
-    //   context.parsedHTML = rendererOptions.postProcess(context.parsedHTML, context);
-    //   await interceptorManager.process('postPostProcess', context);
-    //   await interceptorManager.process('preRenderPreviewHtml', context);
-    // }
-
-    // setHtml(context.parsedHTML ?? '');
-  }, [context, rendererOptions]);
-
-  useEffect(() => {
-    if (markdown == null) {
-      setHtml('');
-    }
-
-    renderPreview();
-  }, [markdown, renderPreview]);
-
-  useEffect(() => {
-    if (html == null) {
-      return;
-    }
-
-    if (interceptorManager != null) {
-      interceptorManager.process('postRenderPreviewHtml', {
-        ...context,
-        parsedHTML: html,
-      });
-    }
-  }, [context, html]);
 
 
   return (
   return (
     <div
     <div
@@ -91,11 +39,7 @@ const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>):
         }
         }
       }}
       }}
     >
     >
-      <RevisionBody
-        {...props}
-        html={html}
-        renderMathJaxInRealtime={editorSettings?.renderMathJaxInRealtime}
-      />
+      <ReactMarkdown {...rendererOptions} >{markdown || ''}</ReactMarkdown>
     </div>
     </div>
   );
   );
 
 
@@ -103,11 +47,4 @@ const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>):
 
 
 Preview.displayName = 'Preview';
 Preview.displayName = 'Preview';
 
 
-// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-const PreviewWrapper = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>): JSX.Element => {
-  return <Preview ref={ref} {...props} />;
-});
-
-PreviewWrapper.displayName = 'PreviewWrapper';
-
-export default PreviewWrapper;
+export default Preview;

+ 5 - 7
packages/app/src/components/PageEditorByHackmd.jsx

@@ -13,7 +13,7 @@ import {
   useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
   useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
 } from '~/stores/editor';
 } from '~/stores/editor';
 import {
 import {
-  useEditorMode, useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
+  useEditorMode, useSelectedGrant,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -456,9 +456,7 @@ const PageEditorByHackmdWrapper = (props) => {
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: pageId } = useCurrentPageId();
   const { data: pageId } = useCurrentPageId();
   const { data: pageTags } = usePageTagsForEditors(pageId);
   const { data: pageTags } = usePageTagsForEditors(pageId);
-  const { data: grant } = useSelectedGrant();
-  const { data: grantGroupId } = useSelectedGrantGroupId();
-  const { data: grantGroupName } = useSelectedGrantGroupName();
+  const { data: grantData } = useSelectedGrant();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
 
 
   if (editorMode == null) {
   if (editorMode == null) {
@@ -473,9 +471,9 @@ const PageEditorByHackmdWrapper = (props) => {
       isSlackEnabled={isSlackEnabled}
       isSlackEnabled={isSlackEnabled}
       slackChannels={slackChannelsData.toString()}
       slackChannels={slackChannelsData.toString()}
       pageTags={pageTags}
       pageTags={pageTags}
-      grant={grant}
-      grantGroupId={grantGroupId}
-      grantGroupName={grantGroupName}
+      grant={grantData.grant}
+      grantGroupId={grantData.grantGroup?.id}
+      grantGroupName={grantData.grantedGroup?.name}
       mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
       mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
     />
     />
   );
   );

+ 5 - 1
packages/app/src/components/PagePathNav.tsx

@@ -3,6 +3,8 @@ import React, { FC } from 'react';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 
 
+import { useIsNotFound } from '~/stores/context';
+
 import LinkedPagePath from '../models/linked-page-path';
 import LinkedPagePath from '../models/linked-page-path';
 
 
 import PagePathHierarchicalLink from './PagePathHierarchicalLink';
 import PagePathHierarchicalLink from './PagePathHierarchicalLink';
@@ -21,6 +23,8 @@ const PagePathNav: FC<Props> = (props: Props) => {
   } = props;
   } = props;
   const dPagePath = new DevidedPagePath(pagePath, false, true);
   const dPagePath = new DevidedPagePath(pagePath, false, true);
 
 
+  const { data: isNotFound } = useIsNotFound();
+
   const CopyDropdown = dynamic(() => import('./Page/CopyDropdown'), { ssr: false });
   const CopyDropdown = dynamic(() => import('./Page/CopyDropdown'), { ssr: false });
 
 
   let formerLink;
   let formerLink;
@@ -47,7 +51,7 @@ const PagePathNav: FC<Props> = (props: Props) => {
       {formerLink}
       {formerLink}
       <span className="d-flex align-items-center">
       <span className="d-flex align-items-center">
         <h1 className="m-0">{latterLink}</h1>
         <h1 className="m-0">{latterLink}</h1>
-        { pageId != null && (
+        { pageId != null && !isNotFound && (
           <div className="mx-2">
           <div className="mx-2">
             <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName={copyDropdownToggleClassName}>
             <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName={copyDropdownToggleClassName}>
               <i className="ti ti-clipboard"></i>
               <i className="ti ti-clipboard"></i>

+ 35 - 32
packages/app/src/components/SavePageControls.jsx

@@ -7,12 +7,12 @@ import {
   DropdownToggle, DropdownMenu, DropdownItem,
   DropdownToggle, DropdownMenu, DropdownItem,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-import PageContainer from '~/client/services/PageContainer';
+// import PageContainer from '~/client/services/PageContainer';
 import { getOptionsToSave } from '~/client/util/editor';
 import { getOptionsToSave } from '~/client/util/editor';
 import { useIsEditable, useCurrentPageId, useIsAclEnabled } from '~/stores/context';
 import { useIsEditable, useCurrentPageId, useIsAclEnabled } from '~/stores/context';
 import { usePageTagsForEditors, useIsEnabledUnsavedWarning } from '~/stores/editor';
 import { usePageTagsForEditors, useIsEnabledUnsavedWarning } from '~/stores/editor';
 import {
 import {
-  useEditorMode, useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
+  useEditorMode, useSelectedGrant,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -43,7 +43,7 @@ class SavePageControls extends React.Component {
 
 
   async save() {
   async save() {
     const {
     const {
-      isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageContainer, pageTags, mutateIsEnabledUnsavedWarning,
+      isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, /* pageContainer, */ pageTags, mutateIsEnabledUnsavedWarning,
     } = this.props;
     } = this.props;
     // disable unsaved warning
     // disable unsaved warning
     mutateIsEnabledUnsavedWarning(false);
     mutateIsEnabledUnsavedWarning(false);
@@ -51,25 +51,25 @@ class SavePageControls extends React.Component {
     try {
     try {
       // save
       // save
       const optionsToSave = getOptionsToSave(isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageTags);
       const optionsToSave = getOptionsToSave(isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageTags);
-      await pageContainer.saveAndReload(optionsToSave, this.props.editorMode);
+      // await pageContainer.saveAndReload(optionsToSave, this.props.editorMode);
     }
     }
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
-      pageContainer.showErrorToastr(error);
+      // pageContainer.showErrorToastr(error);
       if (error.code === 'conflict') {
       if (error.code === 'conflict') {
-        pageContainer.setState({
-          remoteRevisionId: error.data.revisionId,
-          remoteRevisionBody: error.data.revisionBody,
-          remoteRevisionUpdateAt: error.data.createdAt,
-          lastUpdateUser: error.data.user,
-        });
+        // pageContainer.setState({
+        //   remoteRevisionId: error.data.revisionId,
+        //   remoteRevisionBody: error.data.revisionBody,
+        //   remoteRevisionUpdateAt: error.data.createdAt,
+        //   lastUpdateUser: error.data.user,
+        // });
       }
       }
     }
     }
   }
   }
 
 
   saveAndOverwriteScopesOfDescendants() {
   saveAndOverwriteScopesOfDescendants() {
     const {
     const {
-      isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, pageContainer, pageTags, mutateIsEnabledUnsavedWarning,
+      isSlackEnabled, slackChannels, grant, grantGroupId, grantGroupName, /* pageContainer, */ pageTags, mutateIsEnabledUnsavedWarning,
     } = this.props;
     } = this.props;
     // disable unsaved warning
     // disable unsaved warning
     mutateIsEnabledUnsavedWarning(false);
     mutateIsEnabledUnsavedWarning(false);
@@ -78,18 +78,18 @@ class SavePageControls extends React.Component {
     const optionsToSave = Object.assign(currentOptionsToSave, {
     const optionsToSave = Object.assign(currentOptionsToSave, {
       overwriteScopesOfDescendants: true,
       overwriteScopesOfDescendants: true,
     });
     });
-    pageContainer.saveAndReload(optionsToSave, this.props.editorMode);
+    // pageContainer.saveAndReload(optionsToSave, this.props.editorMode);
   }
   }
 
 
   render() {
   render() {
 
 
     const {
     const {
-      t, pageContainer, isAclEnabled, grant, grantGroupId, grantGroupName,
+      t, /* pageContainer, */ isAclEnabled, grant, grantGroupId, grantGroupName,
     } = this.props;
     } = this.props;
 
 
-    const isRootPage = pageContainer.state.path === '/';
-    const labelSubmitButton = pageContainer.state.pageId == null ? t('Create') : t('Update');
-    const labelOverwriteScopes = t('page_edit.overwrite_scopes', { operation: labelSubmitButton });
+    // const isRootPage = pageContainer.state.path === '/';
+    // const labelSubmitButton = pageContainer.state.pageId == null ? t('Create') : t('Update');
+    // const labelOverwriteScopes = t('page_edit.overwrite_scopes', { operation: labelSubmitButton });
 
 
     return (
     return (
       <div className="d-flex align-items-center form-inline flex-nowrap">
       <div className="d-flex align-items-center form-inline flex-nowrap">
@@ -98,7 +98,7 @@ class SavePageControls extends React.Component {
           && (
           && (
             <div className="mr-2">
             <div className="mr-2">
               <GrantSelector
               <GrantSelector
-                disabled={isRootPage}
+                // disabled={isRootPage}
                 grant={grant}
                 grant={grant}
                 grantGroupId={grantGroupId}
                 grantGroupId={grantGroupId}
                 grantGroupName={grantGroupName}
                 grantGroupName={grantGroupName}
@@ -109,11 +109,15 @@ class SavePageControls extends React.Component {
         }
         }
 
 
         <UncontrolledButtonDropdown direction="up">
         <UncontrolledButtonDropdown direction="up">
-          <Button id="caret" color="primary" className="btn-submit" onClick={this.save}>{labelSubmitButton}</Button>
+          <Button id="caret" color="primary" className="btn-submit" onClick={this.save}>
+          labelSubmitButton
+            {/* {labelSubmitButton} */}
+          </Button>
           <DropdownToggle caret color="primary" />
           <DropdownToggle caret color="primary" />
           <DropdownMenu right>
           <DropdownMenu right>
             <DropdownItem onClick={this.saveAndOverwriteScopesOfDescendants}>
             <DropdownItem onClick={this.saveAndOverwriteScopesOfDescendants}>
-              {labelOverwriteScopes}
+            labelOverwriteScopes
+              {/* {labelOverwriteScopes} */}
             </DropdownItem>
             </DropdownItem>
           </DropdownMenu>
           </DropdownMenu>
         </UncontrolledButtonDropdown>
         </UncontrolledButtonDropdown>
@@ -127,22 +131,20 @@ class SavePageControls extends React.Component {
 /**
 /**
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
-const SavePageControlsHOCWrapper = withUnstatedContainers(SavePageControls, [PageContainer]);
+// const SavePageControlsHOCWrapper = withUnstatedContainers(SavePageControls, [PageContainer]);
 
 
 const SavePageControlsWrapper = (props) => {
 const SavePageControlsWrapper = (props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();
   const { data: editorMode } = useEditorMode();
   const { data: editorMode } = useEditorMode();
   const { data: isAclEnabled } = useIsAclEnabled();
   const { data: isAclEnabled } = useIsAclEnabled();
-  const { data: grant, mutate: mutateGrant } = useSelectedGrant();
-  const { data: grantGroupId, mutate: mutateGrantGroupId } = useSelectedGrantGroupId();
-  const { data: grantGroupName, mutate: mutateGrantGroupName } = useSelectedGrantGroupName();
+  const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
   const { data: pageId } = useCurrentPageId();
   const { data: pageId } = useCurrentPageId();
   const { data: pageTags } = usePageTagsForEditors(pageId);
   const { data: pageTags } = usePageTagsForEditors(pageId);
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
 
 
 
 
-  if (isEditable == null || editorMode == null || isAclEnabled == null) {
+  if (isEditable == null || editorMode == null || isAclEnabled == null || grantData == null) {
     return null;
     return null;
   }
   }
 
 
@@ -151,17 +153,18 @@ const SavePageControlsWrapper = (props) => {
   }
   }
 
 
   return (
   return (
-    <SavePageControlsHOCWrapper
+    // <SavePageControlsHOCWrapper
+    <SavePageControls
       t={t}
       t={t}
       {...props}
       {...props}
       editorMode={editorMode}
       editorMode={editorMode}
       isAclEnabled={isAclEnabled}
       isAclEnabled={isAclEnabled}
-      grant={grant}
-      grantGroupId={grantGroupId}
-      grantGroupName={grantGroupName}
+      grant={grantData.grant}
+      grantGroupId={grantData.grantGroup?.id}
+      grantGroupName={grantData.grantedGroup?.name}
       mutateGrant={mutateGrant}
       mutateGrant={mutateGrant}
-      mutateGrantGroupId={mutateGrantGroupId}
-      mutateGrantGroupName={mutateGrantGroupName}
+      // mutateGrantGroupId={mutateGrantGroupId}
+      // mutateGrantGroupName={mutateGrantGroupName}
       mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
       mutateIsEnabledUnsavedWarning={mutateIsEnabledUnsavedWarning}
       pageTags={pageTags}
       pageTags={pageTags}
     />
     />
@@ -171,7 +174,7 @@ const SavePageControlsWrapper = (props) => {
 SavePageControls.propTypes = {
 SavePageControls.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
 
 
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+  // pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
 
 
   // TODO: remove this when omitting unstated is completed
   // TODO: remove this when omitting unstated is completed
   editorMode: PropTypes.string.isRequired,
   editorMode: PropTypes.string.isRequired,

+ 1 - 1
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -214,7 +214,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           revisionId={page.revision}
           revisionId={page.revision}
           highlightKeywords={highlightKeywords}
           highlightKeywords={highlightKeywords}
         />
         />
-        <PageComment highlightKeywords={highlightKeywords} isReadOnly hideIfEmpty />
+        <PageComment pageId={page._id} highlightKeywords={highlightKeywords} isReadOnly hideIfEmpty />
         <PageContentFooter
         <PageContentFooter
           // createdAt={new Date(pageWithMeta.data.createdAt)}
           // createdAt={new Date(pageWithMeta.data.createdAt)}
           // updatedAt={new Date(pageWithMeta.data.updatedAt)}
           // updatedAt={new Date(pageWithMeta.data.updatedAt)}

+ 0 - 59
packages/app/src/components/TagPage.tsx

@@ -1,59 +0,0 @@
-import React, { FC, useState, useCallback } from 'react';
-
-import { useTranslation } from 'next-i18next';
-
-import { IDataTagCount } from '~/interfaces/tag';
-import { useSWRxTagsList } from '~/stores/tag';
-
-import TagCloudBox from './TagCloudBox';
-import TagList from './TagList';
-
-const PAGING_LIMIT = 10;
-
-const TagPage: FC = () => {
-  const [activePage, setActivePage] = useState<number>(1);
-  const [offset, setOffset] = useState<number>(0);
-
-  const { data: tagDataList, error } = useSWRxTagsList(PAGING_LIMIT, offset);
-  const tagData: IDataTagCount[] = tagDataList?.data || [];
-  const totalCount: number = tagDataList?.totalCount || 0;
-  const isLoading = tagDataList === undefined && error == null;
-
-  const { t } = useTranslation('');
-
-  const setOffsetByPageNumber = useCallback((selectedPageNumber: number) => {
-    setActivePage(selectedPageNumber);
-    setOffset((selectedPageNumber - 1) * PAGING_LIMIT);
-  }, []);
-
-  // todo: adjust margin and redesign tags page
-  return (
-    <div className="grw-container-convertible mb-5 pb-5">
-      <h2 className="my-3">{`${t('Tags')}(${totalCount})`}</h2>
-      <div className="px-3 mb-5 text-center">
-        <TagCloudBox tags={tagData} minSize={20} />
-      </div>
-      { isLoading
-        ? (
-          <div className="text-muted text-center">
-            <i className="fa fa-2x fa-spinner fa-pulse mt-3"></i>
-          </div>
-        )
-        : (
-          <div data-testid="grw-tags-list">
-            <TagList
-              tagData={tagData}
-              totalTags={totalCount}
-              activePage={activePage}
-              onChangePage={setOffsetByPageNumber}
-              pagingLimit={PAGING_LIMIT}
-            />
-          </div>
-        )
-      }
-    </div>
-  );
-
-};
-
-export default TagPage;

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

@@ -19,7 +19,7 @@ interface UncontrolledCodeMirrorCoreProps extends UncontrolledCodeMirrorProps {
   forwardedRef: Ref<UncontrolledCodeMirrorCore>;
   forwardedRef: Ref<UncontrolledCodeMirrorCore>;
 }
 }
 
 
-class UncontrolledCodeMirrorCore extends AbstractEditor<UncontrolledCodeMirrorCoreProps> {
+export class UncontrolledCodeMirrorCore extends AbstractEditor<UncontrolledCodeMirrorCoreProps> {
 
 
   override render(): ReactNode {
   override render(): ReactNode {
 
 

+ 1 - 0
packages/app/src/components/UnstatedUtils.tsx

@@ -4,6 +4,7 @@ import React from 'react';
 
 
 import { Provider, Subscribe } from 'unstated';
 import { Provider, Subscribe } from 'unstated';
 
 
+
 /**
 /**
  * generate K/V object by specified instances
  * generate K/V object by specified instances
  *
  *

+ 21 - 0
packages/app/src/interfaces/customize.ts

@@ -0,0 +1,21 @@
+const IHighlightJsCssSelectorThemes = {
+  GITHUB: 'github',
+  GITHUB_GIST: 'github-gist',
+  ATOM_ONE_LIGHT: 'atom-one-light',
+  XCIDE: 'xcode',
+  VS: 'vs',
+  ATOM_ONE_DARK: 'atom-one-dark',
+  HYBRID: 'hybrid',
+  MONOKAI: 'monokai',
+  TOMMORROW_NIGHT: 'tomorrow-night',
+  VS_2015: 'vs2015',
+} as const;
+
+type IHighlightJsCssSelectorThemes = typeof IHighlightJsCssSelectorThemes[keyof typeof IHighlightJsCssSelectorThemes];
+
+export type IHighlightJsCssSelectorOptions = {
+  [theme in IHighlightJsCssSelectorThemes]: {
+    name: string,
+    border: boolean
+  }
+}

+ 0 - 7
packages/app/src/interfaces/editor-settings.ts

@@ -29,10 +29,3 @@ export interface IEditorSettings {
   autoFormatMarkdownTable: boolean,
   autoFormatMarkdownTable: boolean,
   textlintSettings: undefined | ITextlintSettings;
   textlintSettings: undefined | ITextlintSettings;
 }
 }
-
-export type EditorConfig = {
-  upload: {
-    isImageUploaded: boolean,
-    isFileUploaded: boolean,
-  }
-}

+ 0 - 28
packages/app/src/interfaces/unstated-container.ts

@@ -1,28 +0,0 @@
-import AdminAppContainer from '~/client/services/AdminAppContainer';
-import AdminBasicSecurityContainer from '~/client/services/AdminBasicSecurityContainer';
-import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
-import AdminExternalAccountsContainer from '~/client/services/AdminExternalAccountsContainer';
-import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
-import AdminGitHubSecurityContainer from '~/client/services/AdminGitHubSecurityContainer';
-import AdminGoogleSecurityContainer from '~/client/services/AdminGoogleSecurityContainer';
-import AdminHomeContainer from '~/client/services/AdminHomeContainer';
-import AdminImportContainer from '~/client/services/AdminImportContainer';
-import AdminLdapSecurityContainer from '~/client/services/AdminLdapSecurityContainer';
-import AdminLocalSecurityContainer from '~/client/services/AdminLocalSecurityContainer';
-import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
-import AdminNotificationContainer from '~/client/services/AdminNotificationContainer';
-import AdminOidcSecurityContainer from '~/client/services/AdminOidcSecurityContainer';
-import AdminSamlSecurityContainer from '~/client/services/AdminSamlSecurityContainer';
-import AdminSlackIntegrationLegacyContainer from '~/client/services/AdminSlackIntegrationLegacyContainer';
-import AdminTwitterSecurityContainer from '~/client/services/AdminTwitterSecurityContainer';
-import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
-import AdminUsersContainer from '~/client/services/AdminUsersContainer';
-
-type AdminUnstatedContainers =
-  AdminAppContainer | AdminBasicSecurityContainer | AdminCustomizeContainer | AdminExternalAccountsContainer |
-  AdminGeneralSecurityContainer | AdminGitHubSecurityContainer | AdminGoogleSecurityContainer | AdminHomeContainer |
-  AdminImportContainer | AdminLdapSecurityContainer | AdminLocalSecurityContainer | AdminMarkDownContainer |
-  AdminNotificationContainer | AdminOidcSecurityContainer | AdminSamlSecurityContainer | AdminSlackIntegrationLegacyContainer |
-  AdminTwitterSecurityContainer | AdminUserGroupDetailContainer | AdminUsersContainer;
-
-export type AdminInjectableContainers = AdminUnstatedContainers[];

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

@@ -26,7 +26,6 @@ import { CrowiRequest } from '~/interfaces/crowi-request';
 // import { useIndentSize } from '~/stores/editor';
 // import { useIndentSize } from '~/stores/editor';
 // import { useRendererSettings } from '~/stores/renderer';
 // import { useRendererSettings } from '~/stores/renderer';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
-import { EditorConfig } from '~/interfaces/editor-settings';
 import { CustomWindow } from '~/interfaces/global';
 import { CustomWindow } from '~/interfaces/global';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { ISidebarConfig } from '~/interfaces/sidebar-config';
@@ -35,9 +34,9 @@ import { PageModel, PageDocument } from '~/server/models/page';
 import { PageRedirectModel } from '~/server/models/page-redirect';
 import { PageRedirectModel } from '~/server/models/page-redirect';
 import UserUISettings from '~/server/models/user-ui-settings';
 import UserUISettings from '~/server/models/user-ui-settings';
 import Xss from '~/services/xss';
 import Xss from '~/services/xss';
-import { useSWRxCurrentPage, useSWRxPageInfo } from '~/stores/page';
+import { useSWRxCurrentPage, useSWRxIsGrantNormalized, useSWRxPageInfo } from '~/stores/page';
 import {
 import {
-  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
+  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth, useSelectedGrant,
 } from '~/stores/ui';
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -65,7 +64,7 @@ import {
   useIsAclEnabled, useIsUserPage, useIsNotCreatable,
   useIsAclEnabled, useIsUserPage, useIsNotCreatable,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useIsSlackConfigured, useIsBlinkedHeaderAtBoot, useRendererConfig, useEditingMarkdown,
   useIsSlackConfigured, useIsBlinkedHeaderAtBoot, useRendererConfig, useEditingMarkdown,
-  useEditorConfig, useIsAllReplyShown,
+  useIsAllReplyShown,
 } from '../stores/context';
 } from '../stores/context';
 import { useXss } from '../stores/xss';
 import { useXss } from '../stores/xss';
 
 
@@ -152,7 +151,6 @@ type Props = CommonProps & {
   // highlightJsStyle: string,
   // highlightJsStyle: string,
   isAllReplyShown: boolean,
   isAllReplyShown: boolean,
   // isContainerFluid: boolean,
   // isContainerFluid: boolean,
-  editorConfig: EditorConfig,
   isEnabledStaleNotification: boolean,
   isEnabledStaleNotification: boolean,
   // isEnabledLinebreaks: boolean,
   // isEnabledLinebreaks: boolean,
   // isEnabledLinebreaksInComments: boolean,
   // isEnabledLinebreaksInComments: boolean,
@@ -184,7 +182,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
 
 
   // commons
   // commons
   useXss(new Xss());
   useXss(new Xss());
-  useEditorConfig(props.editorConfig);
   useCsrfToken(props.csrfToken);
   useCsrfToken(props.csrfToken);
 
 
   // UserUISettings
   // UserUISettings
@@ -239,20 +236,29 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
     shouldRenderPutbackPageModal = _isTrashPage(pageWithMeta.data.path);
     shouldRenderPutbackPageModal = _isTrashPage(pageWithMeta.data.path);
   }
   }
 
 
-  useCurrentPageId(pageWithMeta?.data._id);
+  const pageId = pageWithMeta?.data._id;
+
+  useCurrentPageId(pageId);
   useSWRxCurrentPage(undefined, pageWithMeta?.data); // store initial data
   useSWRxCurrentPage(undefined, pageWithMeta?.data); // store initial data
-  // useSWRxPage(pageWithMeta?.data._id);
-  useSWRxPageInfo(pageWithMeta?.data._id, undefined, pageWithMeta?.meta); // store initial data
+  useSWRxPageInfo(pageId, undefined, pageWithMeta?.meta); // store initial data
   useIsTrashPage(_isTrashPage(pageWithMeta?.data.path ?? ''));
   useIsTrashPage(_isTrashPage(pageWithMeta?.data.path ?? ''));
   useIsUserPage(isUserPage(pageWithMeta?.data.path ?? ''));
   useIsUserPage(isUserPage(pageWithMeta?.data.path ?? ''));
   useIsNotCreatable(props.isForbidden || !isCreatablePage(pageWithMeta?.data.path ?? '')); // TODO: need to include props.isIdentical
   useIsNotCreatable(props.isForbidden || !isCreatablePage(pageWithMeta?.data.path ?? '')); // TODO: need to include props.isIdentical
   useCurrentPagePath(pageWithMeta?.data.path);
   useCurrentPagePath(pageWithMeta?.data.path);
   useCurrentPathname(props.currentPathname);
   useCurrentPathname(props.currentPathname);
-  useEditingMarkdown(pageWithMeta?.data.revision.body);
+  useEditingMarkdown(pageWithMeta?.data.revision?.body);
+  const { data: grantData } = useSWRxIsGrantNormalized(pageId);
+  const { mutate: mutateSelectedGrant } = useSelectedGrant();
+
+  // sync grant data
+  useEffect(() => {
+    mutateSelectedGrant(grantData?.grantData.currentPageGrant);
+  }, [grantData?.grantData.currentPageGrant, mutateSelectedGrant]);
 
 
   // sync pathname by Shallow Routing https://nextjs.org/docs/routing/shallow-routing
   // sync pathname by Shallow Routing https://nextjs.org/docs/routing/shallow-routing
   useEffect(() => {
   useEffect(() => {
-    if (isClient() && window.location.pathname !== props.currentPathname) {
+    const decodedURI = decodeURI(window.location.pathname);
+    if (isClient() && decodedURI !== props.currentPathname) {
       router.replace(props.currentPathname, undefined, { shallow: true });
       router.replace(props.currentPathname, undefined, { shallow: true });
     }
     }
   }, [props.currentPathname, router]);
   }, [props.currentPathname, router]);
@@ -266,9 +272,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   //     classNames.push('on-edit', 'hackmd');
   //     classNames.push('on-edit', 'hackmd');
   //     break;
   //     break;
   // }
   // }
-  // if (props.isContainerFluid) {
-  //   classNames.push('growi-layout-fluid');
-  // }
   // if (page == null) {
   // if (page == null) {
   //   classNames.push('not-found-page');
   //   classNames.push('not-found-page');
   // }
   // }
@@ -284,7 +287,7 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
         */}
         */}
       </Head>
       </Head>
       {/* <BasicLayout title={useCustomTitle(props, t('GROWI'))} className={classNames.join(' ')}> */}
       {/* <BasicLayout title={useCustomTitle(props, t('GROWI'))} className={classNames.join(' ')}> */}
-      <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')}>
+      <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')} expandContainer={props.isContainerFluid}>
         <header className="py-0">
         <header className="py-0">
           <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
           <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
         </header>
         </header>
@@ -310,7 +313,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
                     {/* <DisplaySwitcher /> */}
                     {/* <DisplaySwitcher /> */}
                     <div id="page-editor-navbar-bottom-container" className="d-none d-edit-block"></div>
                     <div id="page-editor-navbar-bottom-container" className="d-none d-edit-block"></div>
                     {/* <PageStatusAlert /> */}
                     {/* <PageStatusAlert /> */}
-                    PageStatusAlert
                   </>
                   </>
                 ) }
                 ) }
 
 
@@ -326,7 +328,7 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
         </div>
         </div>
         <footer>
         <footer>
           {/* <PageComments /> */}
           {/* <PageComments /> */}
-          <PageComment pageId={useCurrentPageId().data} isReadOnly={false} titleAlign="left" />
+          <PageComment pageId={pageId} isReadOnly={false} titleAlign="left" />
           <PageContentFooter />
           <PageContentFooter />
         </footer>
         </footer>
 
 
@@ -433,6 +435,8 @@ async function injectRoutingInformation(context: GetServerSidePropsContext, prop
     props.isForbidden = count > 0;
     props.isForbidden = count > 0;
   }
   }
   else {
   else {
+    props.isNotFound = page.isEmpty;
+
     // /62a88db47fed8b2d94f30000 ==> /path/to/page
     // /62a88db47fed8b2d94f30000 ==> /path/to/page
     if (isPermalink && page.isEmpty) {
     if (isPermalink && page.isEmpty) {
       props.currentPathname = page.path;
       props.currentPathname = page.path;
@@ -488,12 +492,6 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P
   // props.isEnabledLinebreaks = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks');
   // props.isEnabledLinebreaks = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks');
   // props.isEnabledLinebreaksInComments = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments');
   // props.isEnabledLinebreaksInComments = configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments');
   props.disableLinkSharing = configManager.getConfig('crowi', 'security:disableLinkSharing');
   props.disableLinkSharing = configManager.getConfig('crowi', 'security:disableLinkSharing');
-  props.editorConfig = {
-    upload: {
-      isImageUploaded: crowi.fileUploadService.getIsUploadable(),
-      isFileUploaded: crowi.fileUploadService.getFileUploadEnabled(),
-    },
-  };
   // props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
   // props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
   // props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
   // props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
 
 

+ 91 - 19
packages/app/src/pages/admin/[[...path]].page.tsx

@@ -1,5 +1,6 @@
 import React from 'react';
 import React from 'react';
 
 
+import { isClient } from '@growi/core';
 import {
 import {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 } from 'next';
@@ -7,13 +8,32 @@ import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
+import { Container, Provider } from 'unstated';
 
 
+import AdminAppContainer from '~/client/services/AdminAppContainer';
+import AdminBasicSecurityContainer from '~/client/services/AdminBasicSecurityContainer';
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
+import AdminExternalAccountsContainer from '~/client/services/AdminExternalAccountsContainer';
+import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
+import AdminGitHubSecurityContainer from '~/client/services/AdminGitHubSecurityContainer';
+import AdminGoogleSecurityContainer from '~/client/services/AdminGoogleSecurityContainer';
+import AdminHomeContainer from '~/client/services/AdminHomeContainer';
+import AdminImportContainer from '~/client/services/AdminImportContainer';
+import AdminLdapSecurityContainer from '~/client/services/AdminLdapSecurityContainer';
+import AdminLocalSecurityContainer from '~/client/services/AdminLocalSecurityContainer';
+import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
+import AdminNotificationContainer from '~/client/services/AdminNotificationContainer';
+import AdminOidcSecurityContainer from '~/client/services/AdminOidcSecurityContainer';
+import AdminSamlSecurityContainer from '~/client/services/AdminSamlSecurityContainer';
+import AdminSlackIntegrationLegacyContainer from '~/client/services/AdminSlackIntegrationLegacyContainer';
+import AdminTwitterSecurityContainer from '~/client/services/AdminTwitterSecurityContainer';
+import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
+import AdminUsersContainer from '~/client/services/AdminUsersContainer';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import PluginUtils from '~/server/plugins/plugin-utils';
 import PluginUtils from '~/server/plugins/plugin-utils';
 import ConfigLoader from '~/server/service/config-loader';
 import ConfigLoader from '~/server/service/config-loader';
 import {
 import {
-  useCurrentUser, /* useSearchServiceConfigured, */ useIsMailerSetup, useIsSearchServiceReachable, useSiteUrl,
+  useCurrentUser, /* useSearchServiceConfigured, */ useIsAclEnabled, useIsMailerSetup, useIsSearchServiceReachable, useSiteUrl,
 } from '~/stores/context';
 } from '~/stores/context';
 
 
 import {
 import {
@@ -39,6 +59,7 @@ const ElasticsearchManagement = dynamic(() => import('../../components/Admin/Ela
 // named export
 // named export
 const AuditLogManagement = dynamic(() => import('../../components/Admin/AuditLogManagement').then(module => module.AuditLogManagement));
 const AuditLogManagement = dynamic(() => import('../../components/Admin/AuditLogManagement').then(module => module.AuditLogManagement));
 
 
+
 const AdminLayout = dynamic(() => import('../../components/Layout/AdminLayout'), { ssr: false });
 const AdminLayout = dynamic(() => import('../../components/Layout/AdminLayout'), { ssr: false });
 
 
 const pluginUtils = new PluginUtils();
 const pluginUtils = new PluginUtils();
@@ -51,7 +72,7 @@ type Props = CommonProps & {
   yarnVersion: string,
   yarnVersion: string,
   installedPlugins: any,
   installedPlugins: any,
   envVars: any,
   envVars: any,
-
+  isAclEnabled: boolean,
   isSearchServiceConfigured: boolean,
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,
   isSearchServiceReachable: boolean,
   isMailerSetup: boolean,
   isMailerSetup: boolean,
@@ -144,30 +165,80 @@ const AdminMarkdownSettingsPage: NextPage<Props> = (props: Props) => {
   // useSearchServiceConfigured(props.isSearchServiceConfigured);
   // useSearchServiceConfigured(props.isSearchServiceConfigured);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
 
 
+  useIsAclEnabled(props.isAclEnabled);
   useSiteUrl(props.siteUrl);
   useSiteUrl(props.siteUrl);
 
 
   // useEnvVars(props.envVars);
   // useEnvVars(props.envVars);
 
 
+  const injectableContainers: Container<any>[] = [];
+
+  if (isClient()) {
+    // Create unstated container instances (except Security)
+    const adminAppContainer = new AdminAppContainer();
+    const adminImportContainer = new AdminImportContainer();
+    const adminHomeContainer = new AdminHomeContainer();
+    const adminCustomizeContainer = new AdminCustomizeContainer();
+    const adminUsersContainer = new AdminUsersContainer();
+    const adminExternalAccountsContainer = new AdminExternalAccountsContainer();
+    const adminNotificationContainer = new AdminNotificationContainer();
+    const adminSlackIntegrationLegacyContainer = new AdminSlackIntegrationLegacyContainer();
+    const adminMarkDownContainer = new AdminMarkDownContainer();
+    const adminUserGroupDetailContainer = new AdminUserGroupDetailContainer();
+
+    injectableContainers.push(
+      adminAppContainer,
+      adminImportContainer,
+      adminHomeContainer,
+      adminCustomizeContainer,
+      adminUsersContainer,
+      adminExternalAccountsContainer,
+      adminNotificationContainer,
+      adminSlackIntegrationLegacyContainer,
+      adminMarkDownContainer,
+      adminUserGroupDetailContainer,
+    );
+  }
+
+
+  const adminSecurityContainers: Container<any>[] = [];
+
+  if (isClient()) {
+    const adminSecuritySettingElem = document.getElementById('admin-security-setting');
+
+    if (adminSecuritySettingElem != null) {
+      // Create unstated container instances (Security)
+      const adminGeneralSecurityContainer = new AdminGeneralSecurityContainer();
+      const adminLocalSecurityContainer = new AdminLocalSecurityContainer();
+      const adminLdapSecurityContainer = new AdminLdapSecurityContainer();
+      const adminSamlSecurityContainer = new AdminSamlSecurityContainer();
+      const adminOidcSecurityContainer = new AdminOidcSecurityContainer();
+      const adminBasicSecurityContainer = new AdminBasicSecurityContainer();
+      const adminGoogleSecurityContainer = new AdminGoogleSecurityContainer();
+      const adminGitHubSecurityContainer = new AdminGitHubSecurityContainer();
+      const adminTwitterSecurityContainer = new AdminTwitterSecurityContainer();
+
+      adminSecurityContainers.push(
+        adminGeneralSecurityContainer,
+        adminLocalSecurityContainer,
+        adminLdapSecurityContainer,
+        adminSamlSecurityContainer,
+        adminOidcSecurityContainer,
+        adminBasicSecurityContainer,
+        adminGoogleSecurityContainer,
+        adminGitHubSecurityContainer,
+        adminTwitterSecurityContainer,
+      );
+    }
 
 
-  const adminCustomizeContainer = new AdminCustomizeContainer();
+  }
 
 
-  const injectableContainers = [
-    // adminAppContainer,
-    // adminImportContainer,
-    // adminHomeContainer,
-    adminCustomizeContainer,
-    // adminUsersContainer,
-    // adminExternalAccountsContainer,
-    // adminNotificationContainer,
-    // adminSlackIntegrationLegacyContainer,
-    // adminMarkDownContainer,
-    // adminUserGroupDetailContainer,
-  ];
 
 
   return (
   return (
-    <AdminLayout title={title} selectedNavOpt={name} injectableContainers={injectableContainers}>
-      {content.component}
-    </AdminLayout>
+    <Provider inject={[...injectableContainers, ...adminSecurityContainers]}>
+      <AdminLayout title={title} selectedNavOpt={name}>
+        {content.component}
+      </AdminLayout>
+    </Provider>
   );
   );
 };
 };
 
 
@@ -195,7 +266,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { crowi } = req;
   const {
   const {
-    appService, searchService,
+    appService, searchService, aclService,
   } = crowi;
   } = crowi;
 
 
   const { user } = req;
   const { user } = req;
@@ -221,6 +292,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   props.yarnVersion = crowi.runtimeVersions.versions.yarn ? crowi.runtimeVersions.versions.yarn.version.version : null;
   props.yarnVersion = crowi.runtimeVersions.versions.yarn ? crowi.runtimeVersions.versions.yarn.version.version : null;
   props.installedPlugins = pluginUtils.listPlugins();
   props.installedPlugins = pluginUtils.listPlugins();
   props.envVars = await ConfigLoader.getEnvVarsForDisplay(true);
   props.envVars = await ConfigLoader.getEnvVarsForDisplay(true);
+  props.isAclEnabled = aclService.isAclEnabled();
 
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceConfigured = searchService.isConfigured;
   props.isSearchServiceReachable = searchService.isReachable;
   props.isSearchServiceReachable = searchService.isReachable;

+ 137 - 0
packages/app/src/pages/tags.page.tsx

@@ -0,0 +1,137 @@
+import React, { useState, useCallback } from 'react';
+
+import {
+  IUser, IUserHasId,
+} from '@growi/core';
+import { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
+import { useTranslation } from 'next-i18next';
+import Head from 'next/head';
+
+import TagCloudBox from '~/components/TagCloudBox';
+import TagList from '~/components/TagList';
+import { CrowiRequest } from '~/interfaces/crowi-request';
+import { IDataTagCount } from '~/interfaces/tag';
+import { IUserUISettings } from '~/interfaces/user-ui-settings';
+import UserUISettings from '~/server/models/user-ui-settings';
+import { useSWRxTagsList } from '~/stores/tag';
+
+import { BasicLayout } from '../components/Layout/BasicLayout';
+import {
+  useCurrentUser,
+  useIsSearchServiceConfigured, useIsSearchServiceReachable,
+  useIsSearchScopeChildrenAsDefault,
+} from '../stores/context';
+
+import {
+  CommonProps, getServerSideCommonProps, useCustomTitle,
+} from './utils/commons';
+
+const PAGING_LIMIT = 10;
+
+type Props = CommonProps & {
+  currentUser: IUser,
+  isSearchServiceConfigured: boolean,
+  isSearchServiceReachable: boolean,
+  isSearchScopeChildrenAsDefault: boolean,
+  userUISettings?: IUserUISettings
+};
+
+const TagPage: NextPage<CommonProps> = (props: Props) => {
+  const [activePage, setActivePage] = useState<number>(1);
+  const [offset, setOffset] = useState<number>(0);
+
+  useCurrentUser(props.currentUser ?? null);
+  const { data: tagDataList, error } = useSWRxTagsList(PAGING_LIMIT, offset);
+  const { t } = useTranslation('');
+  const setOffsetByPageNumber = useCallback((selectedPageNumber: number) => {
+    setActivePage(selectedPageNumber);
+    setOffset((selectedPageNumber - 1) * PAGING_LIMIT);
+  }, []);
+
+  const tagData: IDataTagCount[] = tagDataList?.data || [];
+  const totalCount: number = tagDataList?.totalCount || 0;
+  const isLoading = tagDataList === undefined && error == null;
+  const classNames: string[] = [];
+
+  useIsSearchServiceConfigured(props.isSearchServiceConfigured);
+  useIsSearchServiceReachable(props.isSearchServiceReachable);
+  useIsSearchScopeChildrenAsDefault(props.isSearchScopeChildrenAsDefault);
+
+  return (
+    <>
+      <Head>
+      </Head>
+      <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')}>
+        <div className="grw-container-convertible mb-5 pb-5">
+          <h2 className="my-3">{`${t('Tags')}(${totalCount})`}</h2>
+          <div className="px-3 mb-5 text-center">
+            <TagCloudBox tags={tagData} minSize={20} />
+          </div>
+          { isLoading
+            ? (
+              <div className="text-muted text-center">
+                <i className="fa fa-2x fa-spinner fa-pulse mt-3"></i>
+              </div>
+            )
+            : (
+              <div data-testid="grw-tags-list">
+                <TagList
+                  tagData={tagData}
+                  totalTags={totalCount}
+                  activePage={activePage}
+                  onChangePage={setOffsetByPageNumber}
+                  pagingLimit={PAGING_LIMIT}
+                />
+              </div>
+            )
+          }
+        </div>
+      </BasicLayout>
+    </>
+  );
+};
+
+async function injectUserUISettings(context: GetServerSidePropsContext, props: Props): Promise<void> {
+  const req = context.req as CrowiRequest<IUserHasId & any>;
+  const { user } = req;
+  const userUISettings = user == null ? null : await UserUISettings.findOne({ user: user._id }).exec();
+
+  if (userUISettings != null) {
+    props.userUISettings = userUISettings.toObject();
+  }
+}
+
+function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
+  const req: CrowiRequest = context.req as CrowiRequest;
+  const { crowi } = req;
+  const {
+    searchService, configManager,
+  } = crowi;
+
+  props.isSearchServiceConfigured = searchService.isConfigured;
+  props.isSearchServiceReachable = searchService.isReachable;
+  props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault');
+}
+
+export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
+  const req = context.req as CrowiRequest<IUserHasId & any>;
+  const { user } = req;
+  const result = await getServerSideCommonProps(context);
+
+  if (!('props' in result)) {
+    throw new Error('invalid getSSP result');
+  }
+  const props: Props = result.props as Props;
+
+  if (user != null) {
+    props.currentUser = user.toObject();
+  }
+  await injectUserUISettings(context, props);
+  injectServerConfigurations(context, props);
+
+  return {
+    props,
+  };
+};
+
+export default TagPage;

+ 2 - 0
packages/app/src/pages/utils/commons.ts

@@ -17,6 +17,7 @@ export type CommonProps = {
   theme: GrowiThemes,
   theme: GrowiThemes,
   customTitleTemplate: string,
   customTitleTemplate: string,
   csrfToken: string,
   csrfToken: string,
+  isContainerFluid: boolean,
   growiVersion: string,
   growiVersion: string,
 } & Partial<SSRConfig>;
 } & Partial<SSRConfig>;
 
 
@@ -41,6 +42,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
     theme: configManager.getConfig('crowi', 'customize:theme'),
     theme: configManager.getConfig('crowi', 'customize:theme'),
     customTitleTemplate: customizeService.customTitleTemplate,
     customTitleTemplate: customizeService.customTitleTemplate,
     csrfToken: req.csrfToken(),
     csrfToken: req.csrfToken(),
+    isContainerFluid: configManager.getConfig('crowi', 'customize:isContainerFluid') ?? false,
     growiVersion: crowi.version,
     growiVersion: crowi.version,
   };
   };
 
 

+ 3 - 0
packages/app/src/server/routes/apiv3/page.js

@@ -443,6 +443,7 @@ module.exports = (crowi) => {
     const page = await Page.findByIdAndViewer(pageId, req.user, null, false);
     const page = await Page.findByIdAndViewer(pageId, req.user, null, false);
 
 
     if (page == null) {
     if (page == null) {
+      // Empty page should not be related to grant API
       return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
       return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
     }
     }
 
 
@@ -519,6 +520,7 @@ module.exports = (crowi) => {
     const page = await Page.findByIdAndViewer(pageId, req.user, null);
     const page = await Page.findByIdAndViewer(pageId, req.user, null);
 
 
     if (page == null) {
     if (page == null) {
+      // Empty page should not be related to grant API
       return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
       return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
     }
     }
 
 
@@ -543,6 +545,7 @@ module.exports = (crowi) => {
     const page = await Page.findByIdAndViewer(pageId, req.user, null, false);
     const page = await Page.findByIdAndViewer(pageId, req.user, null, false);
 
 
     if (page == null) {
     if (page == null) {
+      // Empty page should not be related to grant API
       return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
       return res.apiv3Err(new ErrorV3('Page is unreachable or empty.', 'page_unreachable_or_empty'), 400);
     }
     }
 
 

+ 1 - 1
packages/app/src/server/routes/apiv3/response.js

@@ -27,7 +27,7 @@ const addCustomFunctionToResponse = (express, crowi) => {
         return new ErrorV3(e.message, null, e.stack);
         return new ErrorV3(e.message, null, e.stack);
       }
       }
       if (typeof e === 'string') {
       if (typeof e === 'string') {
-        return { message: e };
+        return { message: e, status };
       }
       }
 
 
       throw new Error('invalid error supplied to res.apiv3Err');
       throw new Error('invalid error supplied to res.apiv3Err');

+ 2 - 1
packages/app/src/server/routes/index.js

@@ -204,7 +204,8 @@ module.exports = function(crowi, app) {
 
 
   app.use(unavailableWhenMaintenanceMode);
   app.use(unavailableWhenMaintenanceMode);
 
 
-  app.get('/tags'                     , loginRequired, tag.showPage);
+  // app.get('/tags'                     , loginRequired, tag.showPage);
+  app.get('/tags', loginRequired, next.delegateToNext);
 
 
   app.get('/me'                                 , loginRequiredStrictly, injectUserUISettings, me.index);
   app.get('/me'                                 , loginRequiredStrictly, injectUserUISettings, me.index);
   // external-accounts
   // external-accounts

+ 1 - 1
packages/app/src/server/service/page.ts

@@ -227,7 +227,7 @@ class PageService {
       page = await Page.findByIdAndViewer(pageId, user, null, includeEmpty);
       page = await Page.findByIdAndViewer(pageId, user, null, includeEmpty);
     }
     }
     else {
     else {
-      page = await Page.findByPathAndViewer(path, user, null, includeEmpty);
+      page = await Page.findByPathAndViewer(path, user, null, true, includeEmpty);
     }
     }
 
 
     if (page == null) {
     if (page == null) {

+ 7 - 5
packages/app/src/stores/context.tsx

@@ -6,7 +6,6 @@ import useSWRImmutable from 'swr/immutable';
 
 
 
 
 import { SupportedActionType } from '~/interfaces/activity';
 import { SupportedActionType } from '~/interfaces/activity';
-import { EditorConfig } from '~/interfaces/editor-settings';
 // import { CustomWindow } from '~/interfaces/global';
 // import { CustomWindow } from '~/interfaces/global';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { GrowiThemes } from '~/interfaces/theme';
 import { GrowiThemes } from '~/interfaces/theme';
@@ -225,10 +224,6 @@ export const useIsLatestRevision = (initialData?: boolean): SWRResponse<boolean,
   return useStaticSWR('isLatestRevision', initialData);
   return useStaticSWR('isLatestRevision', initialData);
 };
 };
 
 
-export const useEditorConfig = (initialData?: EditorConfig): SWRResponse<EditorConfig, Error> => {
-  return useStaticSWR<EditorConfig, Error>('editorConfig', initialData);
-};
-
 export const useRendererConfig = (initialData?: RendererConfig): SWRResponse<RendererConfig, any> => {
 export const useRendererConfig = (initialData?: RendererConfig): SWRResponse<RendererConfig, any> => {
   return useStaticSWR('growiRendererConfig', initialData);
   return useStaticSWR('growiRendererConfig', initialData);
 };
 };
@@ -249,6 +244,13 @@ export const useEditingMarkdown = (initialData?: string): SWRResponse<string, Er
   return useStaticSWR('currentMarkdown', initialData);
   return useStaticSWR('currentMarkdown', initialData);
 };
 };
 
 
+export const useIsUploadableImage = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useStaticSWR('isUploadableImage', initialData);
+};
+
+export const useIsUploadableFile = (initialData?: boolean): SWRResponse<boolean, Error> => {
+  return useStaticSWR('isUploadableFile', initialData);
+};
 
 
 /** **********************************************************
 /** **********************************************************
  *                     Computed contexts
  *                     Computed contexts

+ 14 - 4
packages/app/src/stores/page.tsx

@@ -14,10 +14,20 @@ import { IPageTagsInfo } from '../interfaces/tag';
 
 
 import { useCurrentPageId } from './context';
 import { useCurrentPageId } from './context';
 
 
-export const useSWRxPage = (pageId?: string|null, shareLinkId?: string): SWRResponse<IPagePopulatedToShowRevision, Error> => {
-  return useSWR<IPagePopulatedToShowRevision, Error>(
+export const useSWRxPage = (pageId?: string|null, shareLinkId?: string): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
+  return useSWR<IPagePopulatedToShowRevision|null, Error>(
     pageId != null ? ['/page', pageId, shareLinkId] : null,
     pageId != null ? ['/page', pageId, shareLinkId] : null,
-    (endpoint, pageId, shareLinkId) => apiv3Get<{ page: IPagePopulatedToShowRevision }>(endpoint, { pageId, shareLinkId }).then(result => result.data.page),
+    (endpoint, pageId, shareLinkId) => apiv3Get<{ page: IPagePopulatedToShowRevision }>(endpoint, { pageId, shareLinkId })
+      .then(result => result.data.page)
+      .catch((errs) => {
+        if (!Array.isArray(errs)) { throw Error('error is not array') }
+        const statusCode = errs[0].status;
+        if (statusCode === 403 || statusCode === 404) {
+          // for NotFoundPage
+          return null;
+        }
+        throw Error('failed to get page');
+      }),
   );
   );
 };
 };
 
 
@@ -28,7 +38,7 @@ export const useSWRxPageByPath = (path?: string): SWRResponse<IPagePopulatedToSh
   );
   );
 };
 };
 
 
-export const useSWRxCurrentPage = (shareLinkId?: string, initialData?: IPagePopulatedToShowRevision): SWRResponse<IPagePopulatedToShowRevision, Error> => {
+export const useSWRxCurrentPage = (shareLinkId?: string, initialData?: IPagePopulatedToShowRevision): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
   const { data: currentPageId } = useCurrentPageId();
   const { data: currentPageId } = useCurrentPageId();
 
 
   const swrResult = useSWRxPage(currentPageId, shareLinkId);
   const swrResult = useSWRxPage(currentPageId, shareLinkId);

+ 14 - 19
packages/app/src/stores/ui.tsx

@@ -14,6 +14,7 @@ import useSWRImmutable from 'swr/immutable';
 import { IFocusable } from '~/client/interfaces/focusable';
 import { IFocusable } from '~/client/interfaces/focusable';
 import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
+import { IPageGrantData } from '~/interfaces/page';
 import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { UpdateDescCountData } from '~/interfaces/websocket';
 import { UpdateDescCountData } from '~/interfaces/websocket';
@@ -365,17 +366,8 @@ export const useSidebarResizeDisabled = (isDisabled?: boolean): SWRResponse<bool
   return useStaticSWR('isSidebarResizeDisabled', isDisabled, { fallbackData: false });
   return useStaticSWR('isSidebarResizeDisabled', isDisabled, { fallbackData: false });
 };
 };
 
 
-
-export const useSelectedGrant = (initialData?: number): SWRResponse<number, Error> => {
-  return useStaticSWR<number, Error>('grant', initialData);
-};
-
-export const useSelectedGrantGroupId = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
-  return useStaticSWR<Nullable<string>, Error>('grantGroupId', initialData);
-};
-
-export const useSelectedGrantGroupName = (initialData?: Nullable<string>): SWRResponse<Nullable<string>, Error> => {
-  return useStaticSWR<Nullable<string>, Error>('grantGroupName', initialData);
+export const useSelectedGrant = (initialData?: Nullable<IPageGrantData>): SWRResponse<Nullable<IPageGrantData>, Error> => {
+  return useStaticSWR<Nullable<IPageGrantData>, Error>('selectedGrant', initialData);
 };
 };
 
 
 export const useGlobalSearchFormRef = (initialData?: RefObject<IFocusable>): SWRResponse<RefObject<IFocusable>, Error> => {
 export const useGlobalSearchFormRef = (initialData?: RefObject<IFocusable>): SWRResponse<RefObject<IFocusable>, Error> => {
@@ -417,19 +409,21 @@ export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> =>
   const { data: currentPageId } = useCurrentPageId();
   const { data: currentPageId } = useCurrentPageId();
   const { data: isTrashPage } = useIsTrashPage();
   const { data: isTrashPage } = useIsTrashPage();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
+  const { data: isNotFound } = useIsNotFound();
 
 
   const pageId = currentPageId;
   const pageId = currentPageId;
-  const includesUndefined = [pageId, isTrashPage, isSharedUser].some(v => v === undefined);
-  const isPageExist = pageId != null;
+  const includesUndefined = [pageId, isTrashPage, isSharedUser, isNotFound].some(v => v === undefined);
+  const isPageExist = (pageId != null) && !isNotFound;
 
 
   return useSWRImmutable(
   return useSWRImmutable(
-    includesUndefined ? null : key,
+    includesUndefined ? null : [key, pageId],
     () => isPageExist && !isTrashPage && !isSharedUser,
     () => isPageExist && !isTrashPage && !isSharedUser,
   );
   );
 };
 };
 
 
 export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToShowTagLabel';
   const key = 'isAbleToShowTagLabel';
+  const { data: pageId } = useCurrentPageId();
   const { data: isUserPage } = useIsUserPage();
   const { data: isUserPage } = useIsUserPage();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: isIdenticalPath } = useIsIdenticalPath();
   const { data: isIdenticalPath } = useIsIdenticalPath();
@@ -442,7 +436,7 @@ export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
   const isViewMode = editorMode === EditorMode.View;
   const isViewMode = editorMode === EditorMode.View;
 
 
   return useSWRImmutable(
   return useSWRImmutable(
-    includesUndefined ? null : [key, editorMode],
+    includesUndefined ? null : [key, editorMode, pageId],
     // "/trash" page does not exist on page collection and unable to add tags
     // "/trash" page does not exist on page collection and unable to add tags
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     () => !isUserPage && !isTrashTopPage(currentPagePath!) && shareLinkId == null && !isIdenticalPath && !(isViewMode && isNotFound),
     () => !isUserPage && !isTrashTopPage(currentPagePath!) && shareLinkId == null && !isIdenticalPath && !(isViewMode && isNotFound),
@@ -466,14 +460,15 @@ export const useIsAbleToShowPageEditorModeManager = (): SWRResponse<boolean, Err
 
 
 export const useIsAbleToShowPageAuthors = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToShowPageAuthors = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToShowPageAuthors';
   const key = 'isAbleToShowPageAuthors';
-  const { data: currentPageId } = useCurrentPageId();
+  const { data: pageId } = useCurrentPageId();
   const { data: isUserPage } = useIsUserPage();
   const { data: isUserPage } = useIsUserPage();
+  const { data: isNotFound } = useIsNotFound();
 
 
-  const includesUndefined = [currentPageId, isUserPage].some(v => v === undefined);
-  const isPageExist = currentPageId != null;
+  const includesUndefined = [pageId, isUserPage, isNotFound].some(v => v === undefined);
+  const isPageExist = (pageId != null) && !isNotFound;
 
 
   return useSWRImmutable(
   return useSWRImmutable(
-    includesUndefined ? null : key,
+    includesUndefined ? null : [key, pageId],
     () => isPageExist && !isUserPage,
     () => isPageExist && !isUserPage,
   );
   );
 };
 };

+ 5 - 5
packages/app/src/styles/_layout.scss

@@ -6,16 +6,16 @@ body {
   overscroll-behavior-y: none;
   overscroll-behavior-y: none;
 }
 }
 
 
-body:not(.growi-layout-fluid) .grw-container-convertible {
+.wrapper:not(.growi-layout-fluid) .grw-container-convertible {
   @extend .container-lg;
   @extend .container-lg;
 }
 }
 
 
-body.not-found-page .grw-container-convertible {
-  @extend .container-lg;
+.wrapper.growi-layout-fluid .grw-container-convertible {
+  @extend .container-fluid;
 }
 }
 
 
-body.growi-layout-fluid .grw-container-convertible {
-  @extend .container-fluid;
+body.not-found-page .grw-container-convertible {
+  @extend .container-lg;
 }
 }
 
 
 .grw-modal-head {
 .grw-modal-head {

+ 6 - 33
packages/app/src/styles/_on-edit.scss

@@ -1,12 +1,9 @@
+@import './bootstrap/init' ;
+@import './variables' ;
+@import './mixins' ;
+@import 'sidebar-wiki';
 @import 'editor-overlay';
 @import 'editor-overlay';
 
 
-body:not(.on-edit) {
-  // hide .page-editor-footer
-  .page-editor-footer {
-    display: none !important;
-    border: none;
-  }
-}
 
 
 body.on-edit {
 body.on-edit {
   overflow-y: hidden !important;
   overflow-y: hidden !important;
@@ -296,43 +293,19 @@ body.on-edit {
 }
 }
 
 
 body.on-edit {
 body.on-edit {
-  &:not(.growi-layout-fluid) .page-editor-preview-body {
+  .wrapper:not(.growi-layout-fluid) .page-editor-preview-body {
     .wiki {
     .wiki {
       max-width: 980px;
       max-width: 980px;
       margin: 0 auto;
       margin: 0 auto;
     }
     }
   }
   }
-  &.growi-layout-fluid .page-editor-preview-body {
+  .wrapper.growi-layout-fluid .page-editor-preview-body {
     .wiki {
     .wiki {
       margin: 0 auto;
       margin: 0 auto;
     }
     }
   }
   }
 }
 }
 
 
-// overwrite .CodeMirror-hints
-.CodeMirror-hints {
-  max-height: 30em !important;
-
-  .CodeMirror-hint.crowi-emoji-autocomplete {
-    font-family: $font-family-monospace-not-strictly;
-    line-height: 1.6em;
-
-    .img-container {
-      display: inline-block;
-      width: 30px;
-    }
-  }
-
-  // active line
-  .CodeMirror-hint-active.crowi-emoji-autocomplete {
-    .img-container {
-      padding-top: 0.3em;
-      padding-bottom: 0.3em;
-      font-size: 15px; // adjust to .wiki
-    }
-  }
-}
-
 #tag-edit-button-tooltip {
 #tag-edit-button-tooltip {
   .tooltip-inner {
   .tooltip-inner {
     color: black;
     color: black;

+ 0 - 53
packages/app/src/styles/_override-codemirror.scss

@@ -1,53 +0,0 @@
-.CodeMirror {
-  pre.CodeMirror-line.grw-cm-header-line {
-    padding-top: 0.16em;
-    padding-bottom: 0.08em;
-    font-family: $font-family-monospace;
-
-    // '#'
-    .cm-formatting-header {
-      font-style: italic;
-      font-weight: bold;
-      opacity: 0.5;
-    }
-
-    .cm-header-1 {
-      font-size: 1.9em;
-    }
-    .cm-header-2 {
-      font-size: 1.6em;
-    }
-    .cm-header-3 {
-      font-size: 1.4em;
-    }
-    .cm-header-4 {
-      font-size: 1.35em;
-    }
-    .cm-header-5 {
-      font-size: 1.25em;
-    }
-    .cm-header-6 {
-      font-size: 1.2em;
-    }
-  }
-
-  .cm-matchhighlight {
-    color: $gray-900 !important;
-    background-color: cyan;
-  }
-
-  .CodeMirror-selection-highlight-scrollbar {
-    background-color: darkcyan;
-  }
-
-  // overwrite .CodeMirror-placeholder
-  pre.CodeMirror-line-like.CodeMirror-placeholder {
-    color: $text-muted;
-  }
-}
-
-// patch to fix https://github.com/codemirror/CodeMirror/issues/4089
-// see also https://github.com/codemirror/CodeMirror/commit/51a1e7da60a99e019f026a118dc7c98c2b1f9d62
-.CodeMirror-wrap > div > textarea {
-  font-size: #{$line-height-base}rem;
-}

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

@@ -52,7 +52,7 @@
 // @import 'modal';
 // @import 'modal';
 // @import 'navbar';
 // @import 'navbar';
 // @import 'old-ios';
 // @import 'old-ios';
-// @import 'on-edit';
+@import 'on-edit';
 // @import 'page-duplicate-modal';
 // @import 'page-duplicate-modal';
 // @import 'page_list';
 // @import 'page_list';
 // @import 'page-accessories-control';
 // @import 'page-accessories-control';

+ 0 - 4
packages/app/src/styles/theme/_apply-colors.scss

@@ -524,10 +524,6 @@ body.on-edit {
     .page-editor-preview-container {
     .page-editor-preview-container {
       background-color: $bgcolor-global;
       background-color: $bgcolor-global;
     }
     }
-
-    .page-editor-footer {
-      border-top-color: $border-color-theme;
-    }
   }
   }
 }
 }