Просмотр исходного кода

Merge remote-tracking branch 'origin/support/apply-nextjs-2' into imprv/create-new-pages

Yuki Takei 3 лет назад
Родитель
Сommit
1bd0ff7e8f
31 измененных файлов с 364 добавлено и 137 удалено
  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. 3 1
      packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx
  22. 2 2
      packages/app/src/components/Admin/Security/SecurityManagementContents.jsx
  23. 2 3
      packages/app/src/components/Admin/UserManagement.jsx
  24. 6 6
      packages/app/src/components/Admin/Users/UserInviteModal.jsx
  25. 2 8
      packages/app/src/components/Layout/AdminLayout.tsx
  26. 0 59
      packages/app/src/components/TagPage.tsx
  27. 21 0
      packages/app/src/interfaces/customize.ts
  28. 0 28
      packages/app/src/interfaces/unstated-container.ts
  29. 91 19
      packages/app/src/pages/admin/[[...path]].page.tsx
  30. 137 0
      packages/app/src/pages/tags.page.tsx
  31. 2 1
      packages/app/src/server/routes/index.js

+ 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 { SearchPage } from '../components/SearchPage';
 import Sidebar from '../components/Sidebar';
-import TagPage from '../components/TagPage';
 import TrashPageList from '../components/TrashPageList';
 
 import { appContainer, componentMappings } from './base';
@@ -75,7 +74,6 @@ Object.assign(componentMappings, {
   'identical-path-page': <IdenticalPathPage />,
 
   // 'revision-history': <PageHistory pageId={pageId} />,
-  'tags-page': <TagPage />,
 
   '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 { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
@@ -11,6 +12,10 @@ export default class AdminAppContainer extends Container {
   constructor() {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
       retrieveError: null,
       title: '',

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

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -16,6 +17,10 @@ export default class AdminBasicSecurityContainer extends Container {
   constructor() {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
       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 loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminCustomizeContainer extends Container {
   constructor() {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
       retrieveError: null,
       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 loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminExternalAccountsContainer extends Container {
   constructor() {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
       externalAccounts: [],
       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 {
@@ -18,6 +19,10 @@ export default class AdminGeneralSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
       retrieveError: 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 urljoin from 'url-join';
 
@@ -18,6 +18,10 @@ export default class AdminGitHubSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.dummyGithubClientId = 0;
     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 urljoin from 'url-join';
 
@@ -18,6 +18,10 @@ export default class AdminGoogleSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.dummyGoogleClientId = 0;
     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 loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminHomeContainer extends Container {
   constructor() {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.copyStateValues = {
       DEFAULT: 'default',
       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 loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminImportContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 loggerFactory from '~/utils/logger';
@@ -16,6 +17,10 @@ export default class AdminLdapSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 loggerFactory from '~/utils/logger';
@@ -15,6 +16,10 @@ export default class AdminLocalSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.dummyRegistrationMode = 0;
     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 { apiv3Get, apiv3Put } from '../util/apiv3-client';
@@ -11,6 +12,10 @@ export default class AdminMarkDownContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 {
@@ -13,6 +14,10 @@ export default class AdminNotificationContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 urljoin from 'url-join';
 
@@ -18,6 +18,10 @@ export default class AdminOidcSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 urljoin from 'url-join';
 
@@ -18,6 +18,10 @@ export default class AdminSamlSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 { apiv3Get, apiv3Put } from '../util/apiv3-client';
@@ -11,6 +12,10 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 urljoin from 'url-join';
 
@@ -18,6 +18,10 @@ export default class AdminTwitterSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     this.state = {

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

@@ -2,15 +2,16 @@
  * TODO 85062: AdminUserGroupDetailContainer is under transplantation to UserGroupDetailPage.tsx
  */
 
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
+import {
+  apiv3Get, apiv3Delete, apiv3Put, apiv3Post,
+} from '~/client/util/apiv3-client';
 import loggerFactory from '~/utils/logger';
 
 import { toastError } from '../util/apiNotification';
 
-import {
-  apiv3Get, apiv3Delete, apiv3Put, apiv3Post,
-} from '~/client/util/apiv3-client';
 
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:services:AdminUserGroupDetailContainer');
@@ -24,6 +25,10 @@ export default class AdminUserGroupDetailContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     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 { Container } from 'unstated';
 
@@ -19,6 +20,10 @@ export default class AdminUsersContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     this.state = {

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

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

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

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

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

@@ -10,7 +10,7 @@ import PaginationWrapper from '../PaginationWrapper';
 import { withUnstatedContainers } from '../UnstatedUtils';
 
 
-// import InviteUserControl from './Users/InviteUserControl';
+import InviteUserControl from './Users/InviteUserControl';
 import PasswordResetModal from './Users/PasswordResetModal';
 import UserTable from './Users/UserTable';
 
@@ -150,8 +150,7 @@ class UserManagement extends React.Component {
           />
         )}
         <p>
-          {/* show  */}
-          {/* <InviteUserControl /> */}
+          <InviteUserControl />
           <a className="btn btn-outline-secondary ml-2" href="/admin/users/external-accounts" role="button">
             <i className="icon-user-follow" aria-hidden="true"></i>
             {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,
 } from 'reactstrap';
 
+
 import AdminUsersContainer from '~/client/services/AdminUsersContainer';
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/apiNotification';
+import { useIsMailerSetup } from '~/stores/context';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
@@ -99,9 +100,8 @@ class UserInviteModal extends React.Component {
   }
 
   renderModalFooter() {
-    const { t, appContainer } = this.props;
+    const { t, isMailerSetup } = this.props;
     const { isCreateUserButtonPushed } = this.state;
-    const { isMailerSetup } = appContainer.config;
 
     return (
       <>
@@ -282,18 +282,18 @@ class UserInviteModal extends React.Component {
 
 const UserInviteModalWrapperFC = (props) => {
   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
  */
-const UserInviteModalWrapper = withUnstatedContainers(UserInviteModalWrapperFC, [AppContainer, AdminUsersContainer]);
+const UserInviteModalWrapper = withUnstatedContainers(UserInviteModalWrapperFC, [AdminUsersContainer]);
 
 
 UserInviteModal.propTypes = {
   t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).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 dynamic from 'next/dynamic';
-import { Provider } from 'unstated';
-
-import { AdminInjectableContainers } from '~/interfaces/unstated-container';
 
 import { GrowiNavbar } from '../Navbar/GrowiNavbar';
 
@@ -21,12 +18,11 @@ type Props = {
    */
   selectedNavOpt: string
   children?: ReactNode
-  injectableContainers: AdminInjectableContainers
 }
 
 
 const AdminLayout = ({
-  children, title, selectedNavOpt, injectableContainers,
+  children, title, selectedNavOpt,
 }: Props): JSX.Element => {
 
   const AdminNavigation = dynamic(() => import('~/components/Admin/Common/AdminNavigation'), { ssr: false });
@@ -46,9 +42,7 @@ const AdminLayout = ({
               <AdminNavigation selected={selectedNavOpt} />
             </div>
             <div className="col-lg-9">
-              <Provider inject={injectableContainers}>
-                {children}
-              </Provider>
+              {children}
             </div>
           </div>
         </div>

+ 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;

+ 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 - 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[];

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

@@ -1,5 +1,6 @@
 import React from 'react';
 
+import { isClient } from '@growi/core';
 import {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
@@ -7,13 +8,32 @@ import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 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 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 PluginUtils from '~/server/plugins/plugin-utils';
 import ConfigLoader from '~/server/service/config-loader';
 import {
-  useCurrentUser, /* useSearchServiceConfigured, */ useIsMailerSetup, useIsSearchServiceReachable, useSiteUrl,
+  useCurrentUser, /* useSearchServiceConfigured, */ useIsAclEnabled, useIsMailerSetup, useIsSearchServiceReachable, useSiteUrl,
 } from '~/stores/context';
 
 import {
@@ -39,6 +59,7 @@ const ElasticsearchManagement = dynamic(() => import('../../components/Admin/Ela
 // named export
 const AuditLogManagement = dynamic(() => import('../../components/Admin/AuditLogManagement').then(module => module.AuditLogManagement));
 
+
 const AdminLayout = dynamic(() => import('../../components/Layout/AdminLayout'), { ssr: false });
 
 const pluginUtils = new PluginUtils();
@@ -51,7 +72,7 @@ type Props = CommonProps & {
   yarnVersion: string,
   installedPlugins: any,
   envVars: any,
-
+  isAclEnabled: boolean,
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,
   isMailerSetup: boolean,
@@ -144,30 +165,80 @@ const AdminMarkdownSettingsPage: NextPage<Props> = (props: Props) => {
   // useSearchServiceConfigured(props.isSearchServiceConfigured);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
 
+  useIsAclEnabled(props.isAclEnabled);
   useSiteUrl(props.siteUrl);
 
   // 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 (
-    <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 { crowi } = req;
   const {
-    appService, searchService,
+    appService, searchService, aclService,
   } = crowi;
 
   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.installedPlugins = pluginUtils.listPlugins();
   props.envVars = await ConfigLoader.getEnvVarsForDisplay(true);
+  props.isAclEnabled = aclService.isAclEnabled();
 
   props.isSearchServiceConfigured = searchService.isConfigured;
   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 - 1
packages/app/src/server/routes/index.js

@@ -204,7 +204,8 @@ module.exports = function(crowi, app) {
 
   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);
   // external-accounts