Browse Source

Merge branch 'dev/7.0.x' into feat/139718-141055-sidebar-account-modal-design

yukendev 2 years ago
parent
commit
91b75af4bb
100 changed files with 532 additions and 543 deletions
  1. 1 1
      .devcontainer/Dockerfile
  2. 7 7
      .github/workflows/ci-app-prod.yml
  3. 3 3
      .github/workflows/ci-app.yml
  4. 3 3
      .github/workflows/ci-slackbot-proxy.yml
  5. 1 1
      .github/workflows/list-unhealthy-branches.yml
  6. 1 1
      .github/workflows/release-slackbot-proxy.yml
  7. 2 2
      .github/workflows/release.yml
  8. 4 4
      .mergify.yml
  9. 1 1
      README.md
  10. 1 1
      README_JP.md
  11. 0 0
      apps/app/_obsolete/src/components/PageEditor/AbstractEditor.tsx
  12. 0 0
      apps/app/_obsolete/src/components/PageEditor/Editor.module.scss
  13. 8 7
      apps/app/_obsolete/src/components/PageEditor/Editor.tsx
  14. 0 0
      apps/app/_obsolete/src/components/PageEditor/EditorIcon.jsx
  15. 0 0
      apps/app/_obsolete/src/components/PageEditor/PasteHelper.js
  16. 0 0
      apps/app/_obsolete/src/components/PageEditor/PreventMarkdownListInterceptor.js
  17. 0 0
      apps/app/_obsolete/src/components/PageEditor/TextAreaEditor.jsx
  18. 4 4
      apps/app/docker/Dockerfile
  19. 6 6
      apps/app/package.json
  20. 4 3
      apps/app/public/static/locales/en_US/admin.json
  21. 21 3
      apps/app/public/static/locales/en_US/translation.json
  22. 4 3
      apps/app/public/static/locales/ja_JP/admin.json
  23. 21 3
      apps/app/public/static/locales/ja_JP/translation.json
  24. 4 3
      apps/app/public/static/locales/zh_CN/admin.json
  25. 21 3
      apps/app/public/static/locales/zh_CN/translation.json
  26. 1 1
      apps/app/src/client/models/MarkdownTable.js
  27. 3 1
      apps/app/src/client/services/create-page/use-create-template-page.ts
  28. 12 1
      apps/app/src/client/services/page-operation.ts
  29. 3 1
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  30. 6 1
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  31. 18 0
      apps/app/src/client/services/use-toastr-on-error.tsx
  32. 2 2
      apps/app/src/client/util/bookmark-utils.ts
  33. 2 2
      apps/app/src/components/Admin/AdminHome/AdminHome.jsx
  34. 1 1
      apps/app/src/components/Admin/App/AppSettingsPageContents.tsx
  35. 2 2
      apps/app/src/components/Admin/App/MaskedInput.tsx
  36. 4 3
      apps/app/src/components/Admin/AuditLog/ActivityTable.tsx
  37. 3 2
      apps/app/src/components/Admin/AuditLog/AuditLogSettings.tsx
  38. 3 2
      apps/app/src/components/Admin/AuditLog/DateRangePicker.tsx
  39. 9 10
      apps/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx
  40. 5 3
      apps/app/src/components/Admin/AuditLog/SelectActionDropdown.tsx
  41. 1 1
      apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  42. 1 1
      apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  43. 21 10
      apps/app/src/components/Admin/Customize/ThemeColorBox.tsx
  44. 2 2
      apps/app/src/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx
  45. 2 2
      apps/app/src/components/Admin/G2GDataTransferExportForm.tsx
  46. 4 4
      apps/app/src/components/Admin/G2GDataTransferStatusIcon.tsx
  47. 2 2
      apps/app/src/components/Admin/ImportData/GrowiArchive/ImportForm.jsx
  48. 7 2
      apps/app/src/components/Admin/Notification/NotificationTypeIcon.tsx
  49. 1 1
      apps/app/src/components/Admin/Notification/UserTriggerNotification.jsx
  50. 4 5
      apps/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx
  51. 4 4
      apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx
  52. 2 2
      apps/app/src/components/Admin/Security/LocalSecuritySettingContents.jsx
  53. 6 6
      apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx
  54. 8 4
      apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  55. 5 5
      apps/app/src/components/Admin/Security/SecurityManagementContents.jsx
  56. 7 7
      apps/app/src/components/Admin/Security/SecuritySetting.jsx
  57. 11 5
      apps/app/src/components/Admin/SlackIntegration/Bridge.jsx
  58. 4 4
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  59. 1 1
      apps/app/src/components/Admin/SlackIntegration/SlackIntegration.jsx
  60. 3 3
      apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  61. 22 23
      apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  62. 2 3
      apps/app/src/components/Admin/UserGroup/UserGroupModal.tsx
  63. 4 2
      apps/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  64. 6 7
      apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  65. 4 3
      apps/app/src/components/Admin/UserGroupDetail/UpdateParentConfirmModal.tsx
  66. 4 3
      apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  67. 0 169
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  68. 122 0
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.tsx
  69. 6 5
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserModal.tsx
  70. 3 3
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx
  71. 1 1
      apps/app/src/components/Admin/UserManagement.module.scss
  72. 4 3
      apps/app/src/components/Admin/UserManagement.tsx
  73. 2 2
      apps/app/src/components/Admin/Users/ExternalAccountTable.tsx
  74. 1 1
      apps/app/src/components/Admin/Users/GrantAdminButton.tsx
  75. 1 1
      apps/app/src/components/Admin/Users/GrantReadOnlyButton.tsx
  76. 2 2
      apps/app/src/components/Admin/Users/RevokeAdminButton.tsx
  77. 2 2
      apps/app/src/components/Admin/Users/RevokeAdminMenuItem.tsx
  78. 1 1
      apps/app/src/components/Admin/Users/RevokeReadOnlyMenuItem.tsx
  79. 1 1
      apps/app/src/components/Admin/Users/SendInvitationEmailButton.jsx
  80. 8 4
      apps/app/src/components/Admin/Users/SortIcons.tsx
  81. 1 1
      apps/app/src/components/Admin/Users/StatusActivateButton.jsx
  82. 2 2
      apps/app/src/components/Admin/Users/StatusSuspendMenuItem.tsx
  83. 2 2
      apps/app/src/components/Admin/Users/UserMenu.module.scss
  84. 7 3
      apps/app/src/components/Admin/Users/UserMenu.tsx
  85. 4 4
      apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx
  86. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx
  87. 0 4
      apps/app/src/components/Bookmarks/BookmarkFolderTree.module.scss
  88. 1 1
      apps/app/src/components/Bookmarks/BookmarkItem.tsx
  89. 1 1
      apps/app/src/components/Bookmarks/BookmarkMoveToRootBtn.tsx
  90. 3 2
      apps/app/src/components/Common/CustomCopyToClipBoard.tsx
  91. 7 7
      apps/app/src/components/Common/Dropdown/PageItemControl.tsx
  92. 4 0
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.module.scss
  93. 3 3
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx
  94. 8 4
      apps/app/src/components/Common/PagePathNav/PagePathNav.tsx
  95. 1 1
      apps/app/src/components/ContentLinkButtons.tsx
  96. 4 2
      apps/app/src/components/ExpandOrContractButton.tsx
  97. 0 28
      apps/app/src/components/Icons/AttachmentIcon.jsx
  98. 0 22
      apps/app/src/components/Icons/HistoryIcon.jsx
  99. 0 22
      apps/app/src/components/Icons/PresentationIcon.jsx
  100. 0 35
      apps/app/src/components/Icons/ShareLinkIcon.jsx

+ 1 - 1
.devcontainer/Dockerfile

@@ -3,7 +3,7 @@
 # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
 # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
 #-------------------------------------------------------------------------------------------------------------
 #-------------------------------------------------------------------------------------------------------------
 
 
-FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-18
+FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-20
 
 
 # The node image includes a non-root user with sudo access. Use the
 # The node image includes a non-root user with sudo access. Use the
 # "remoteUser" property in devcontainer.json to use it. On Linux, update
 # "remoteUser" property in devcontainer.json to use it. On Linux, update

+ 7 - 7
.github/workflows/ci-app-prod.yml

@@ -48,19 +48,19 @@ concurrency:
 
 
 jobs:
 jobs:
 
 
-  test-prod-node16:
+  test-prod-node18:
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
     with:
     with:
-      node-version: 16.x
+      node-version: 18.x
       skip-cypress: true
       skip-cypress: true
     secrets:
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
 
 
-  test-prod-node18:
+  test-prod-node20:
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
     with:
     with:
-      node-version: 18.x
+      node-version: 20.x
       skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       cypress-report-artifact-name: Cypress report
       cypress-report-artifact-name: Cypress report
       cypress-config-video: ${{ inputs.cypress-config-video || false }}
       cypress-config-video: ${{ inputs.cypress-config-video || false }}
@@ -68,15 +68,15 @@ jobs:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
 
 
-  run-reg-suit-node18:
-    needs: [test-prod-node18]
+  run-reg-suit-node20:
+    needs: [test-prod-node20]
 
 
     uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@dev/7.0.x
     uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@dev/7.0.x
 
 
     if: always()
     if: always()
 
 
     with:
     with:
-      node-version: 18.x
+      node-version: 20.x
       skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       cypress-report-artifact-name: Cypress report
       cypress-report-artifact-name: Cypress report
     secrets:
     secrets:

+ 3 - 3
.github/workflows/ci-app.yml

@@ -27,7 +27,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
 
     steps:
     steps:
       - uses: actions/checkout@v3
       - uses: actions/checkout@v3
@@ -92,7 +92,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
 
     services:
     services:
       mongodb:
       mongodb:
@@ -174,7 +174,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
 
     services:
     services:
       mongodb:
       mongodb:

+ 3 - 3
.github/workflows/ci-slackbot-proxy.yml

@@ -29,7 +29,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
 
     steps:
     steps:
     - uses: actions/checkout@v3
     - uses: actions/checkout@v3
@@ -94,7 +94,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
 
     services:
     services:
       mysql:
       mysql:
@@ -179,7 +179,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
 
     services:
     services:
       mysql:
       mysql:

+ 1 - 1
.github/workflows/list-unhealthy-branches.yml

@@ -16,7 +16,7 @@ jobs:
 
 
     - uses: actions/setup-node@v3
     - uses: actions/setup-node@v3
       with:
       with:
-        node-version: '16'
+        node-version: '18'
 
 
     - name: List branches
     - name: List branches
       id: list-branches
       id: list-branches

+ 1 - 1
.github/workflows/release-slackbot-proxy.yml

@@ -102,7 +102,7 @@ jobs:
 
 
     - uses: actions/setup-node@v3
     - uses: actions/setup-node@v3
       with:
       with:
-        node-version: '16'
+        node-version: '18'
         cache: 'yarn'
         cache: 'yarn'
         cache-dependency-path: '**/yarn.lock'
         cache-dependency-path: '**/yarn.lock'
 
 

+ 2 - 2
.github/workflows/release.yml

@@ -24,7 +24,7 @@ jobs:
 
 
     - uses: actions/setup-node@v3
     - uses: actions/setup-node@v3
       with:
       with:
-        node-version: '18'
+        node-version: '20'
         cache: 'yarn'
         cache: 'yarn'
         cache-dependency-path: '**/yarn.lock'
         cache-dependency-path: '**/yarn.lock'
 
 
@@ -189,7 +189,7 @@ jobs:
 
 
     - uses: actions/setup-node@v3
     - uses: actions/setup-node@v3
       with:
       with:
-        node-version: '18'
+        node-version: '20'
         cache: 'yarn'
         cache: 'yarn'
         cache-dependency-path: '**/yarn.lock'
         cache-dependency-path: '**/yarn.lock'
 
 

+ 4 - 4
.mergify.yml

@@ -3,11 +3,11 @@ pull_request_rules:
     conditions:
     conditions:
       - author = dependabot[bot]
       - author = dependabot[bot]
       - '#approved-reviews-by >= 1'
       - '#approved-reviews-by >= 1'
-      - check-success = "lint (18.x)"
-      - check-success = "test (18.x)"
-      - check-success = "launch-dev (18.x)"
-      - check-success = "test-prod-node16 / launch-prod"
+      - check-success = "lint (20.x)"
+      - check-success = "test (20.x)"
+      - check-success = "launch-dev (20.x)"
       - check-success = "test-prod-node18 / launch-prod"
       - check-success = "test-prod-node18 / launch-prod"
+      - check-success = "test-prod-node20 / launch-prod"
     actions:
     actions:
       merge:
       merge:
         method: merge
         method: merge

+ 1 - 1
README.md

@@ -79,7 +79,7 @@ See [GROWI Docs: Environment Variables](https://docs.growi.org/en/admin-guide/ad
 
 
 ## Dependencies
 ## Dependencies
 
 
-- Node.js v16.x or v18.x
+- Node.js v18.x or v20.x
 - npm 6.x
 - npm 6.x
 - yarn
 - yarn
 - [Turborepo](https://turbo.build/repo)
 - [Turborepo](https://turbo.build/repo)

+ 1 - 1
README_JP.md

@@ -78,7 +78,7 @@ Crowi からの移行は **[こちら](https://docs.growi.org/en/admin-guide/mig
 
 
 ## 依存関係
 ## 依存関係
 
 
-- Node.js v16.x or v18.x
+- Node.js v18.x or v20.x
 - npm 6.x
 - npm 6.x
 - yarn
 - yarn
 - [Turborepo](https://turbo.build/repo)
 - [Turborepo](https://turbo.build/repo)

+ 0 - 0
apps/app/src/components/PageEditor/AbstractEditor.tsx → apps/app/_obsolete/src/components/PageEditor/AbstractEditor.tsx


+ 0 - 0
apps/app/src/components/PageEditor/Editor.module.scss → apps/app/_obsolete/src/components/PageEditor/Editor.module.scss


+ 8 - 7
apps/app/src/components/PageEditor/Editor.tsx → apps/app/_obsolete/src/components/PageEditor/Editor.tsx

@@ -1,9 +1,11 @@
+import type { ForwardRefRenderFunction } from 'react';
 import React, {
 import React, {
-  useState, useRef, useImperativeHandle, useCallback, ForwardRefRenderFunction, forwardRef,
+  useState, useRef, useImperativeHandle, useCallback, forwardRef,
   memo,
   memo,
   useEffect,
   useEffect,
 } from 'react';
 } from 'react';
 
 
+import type { EditorSettings } from '@growi/editor';
 import Dropzone from 'react-dropzone';
 import Dropzone from 'react-dropzone';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import {
 import {
@@ -11,14 +13,13 @@ import {
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import { IEditorSettings } from '~/interfaces/editor-settings';
 import { useDefaultIndentSize } from '~/stores/context';
 import { useDefaultIndentSize } from '~/stores/context';
 import { useEditorSettings } from '~/stores/editor';
 import { useEditorSettings } from '~/stores/editor';
 import { useIsMobile } from '~/stores/ui';
 import { useIsMobile } from '~/stores/ui';
 
 
-import { IEditorMethods } from '../../interfaces/editor-methods';
+import type { IEditorMethods } from '../../interfaces/editor-methods';
 
 
-import AbstractEditor from './AbstractEditor';
+import type AbstractEditor from './AbstractEditor';
 import { Cheatsheet } from './Cheatsheet';
 import { Cheatsheet } from './Cheatsheet';
 // import CodeMirrorEditor from './CodeMirrorEditor';
 // import CodeMirrorEditor from './CodeMirrorEditor';
 import pasteHelper from './PasteHelper';
 import pasteHelper from './PasteHelper';
@@ -35,7 +36,7 @@ export type EditorPropsType = {
   isUploadAllFileAllowed?: boolean,
   isUploadAllFileAllowed?: boolean,
   onChange?: (newValue: string, isClean?: boolean) => void,
   onChange?: (newValue: string, isClean?: boolean) => void,
   onUpload?: (file) => void,
   onUpload?: (file) => void,
-  editorSettings?: IEditorSettings,
+  editorSettings?: EditorSettings,
   indentSize?: number,
   indentSize?: number,
   onDragEnter?: (event: any) => void,
   onDragEnter?: (event: any) => void,
   onMarkdownHelpButtonClicked?: () => void,
   onMarkdownHelpButtonClicked?: () => void,
@@ -249,7 +250,7 @@ const Editor: ForwardRefRenderFunction<IEditorMethods, EditorPropsType> = (props
     return (
     return (
       <Modal isOpen={isCheatsheetModalShown} toggle={hideCheatsheetModal} className={`modal-gfm-cheatsheet ${styles['modal-gfm-cheatsheet']}`} size="lg">
       <Modal isOpen={isCheatsheetModalShown} toggle={hideCheatsheetModal} className={`modal-gfm-cheatsheet ${styles['modal-gfm-cheatsheet']}`} size="lg">
         <ModalHeader tag="h4" toggle={hideCheatsheetModal} className="bg-primary text-light">
         <ModalHeader tag="h4" toggle={hideCheatsheetModal} className="bg-primary text-light">
-          <i className="icon-fw icon-question" />Markdown help
+          <span className="material-symbols-outlined me-1">help</span>Markdown help
         </ModalHeader>
         </ModalHeader>
         <ModalBody>
         <ModalBody>
           <Cheatsheet />
           <Cheatsheet />
@@ -342,7 +343,7 @@ const Editor: ForwardRefRenderFunction<IEditorMethods, EditorPropsType> = (props
               className="btn btn-outline-secondary btn-open-dropzone"
               className="btn btn-outline-secondary btn-open-dropzone"
               onClick={addAttachmentHandler}
               onClick={addAttachmentHandler}
             >
             >
-              <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
+              <span className="material-symbols-outlined" aria-hidden="true">attachment</span>&nbsp;
               Attach files
               Attach files
               <span className="d-none d-sm-inline">
               <span className="d-none d-sm-inline">
               &nbsp;by dragging &amp; dropping,&nbsp;
               &nbsp;by dragging &amp; dropping,&nbsp;

+ 0 - 0
apps/app/src/components/PageEditor/EditorIcon.jsx → apps/app/_obsolete/src/components/PageEditor/EditorIcon.jsx


+ 0 - 0
apps/app/src/components/PageEditor/PasteHelper.js → apps/app/_obsolete/src/components/PageEditor/PasteHelper.js


+ 0 - 0
apps/app/src/components/PageEditor/PreventMarkdownListInterceptor.js → apps/app/_obsolete/src/components/PageEditor/PreventMarkdownListInterceptor.js


+ 0 - 0
apps/app/src/components/PageEditor/TextAreaEditor.jsx → apps/app/_obsolete/src/components/PageEditor/TextAreaEditor.jsx


+ 4 - 4
apps/app/docker/Dockerfile

@@ -4,7 +4,7 @@
 ##
 ##
 ## base
 ## base
 ##
 ##
-FROM node:18-slim AS base
+FROM node:20-slim AS base
 
 
 ENV optDir /opt
 ENV optDir /opt
 
 
@@ -18,7 +18,7 @@ RUN turbo prune --scope=@growi/app --docker
 ##
 ##
 ## deps-resolver
 ## deps-resolver
 ##
 ##
-FROM node:18-slim AS deps-resolver
+FROM node:20-slim AS deps-resolver
 
 
 ENV optDir /opt
 ENV optDir /opt
 
 
@@ -62,7 +62,7 @@ RUN tar -cf node_modules.tar \
 ##
 ##
 ## builder
 ## builder
 ##
 ##
-FROM node:18-slim AS builder
+FROM node:20-slim AS builder
 
 
 ENV optDir /opt
 ENV optDir /opt
 
 
@@ -107,7 +107,7 @@ RUN tar -cf packages.tar \
 ##
 ##
 ## release
 ## release
 ##
 ##
-FROM node:18-slim
+FROM node:20-slim
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 
 
 ENV NODE_ENV production
 ENV NODE_ENV production

+ 6 - 6
apps/app/package.json

@@ -62,7 +62,7 @@
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
     "@aws-sdk/client-s3": "3.454.0",
     "@aws-sdk/client-s3": "3.454.0",
     "@aws-sdk/s3-request-presigner": "3.454.0",
     "@aws-sdk/s3-request-presigner": "3.454.0",
-    "@azure/identity": "^3.3.2",
+    "@azure/identity": "^4.0.1",
     "@azure/storage-blob": "^12.16.0",
     "@azure/storage-blob": "^12.16.0",
     "@browser-bunyan/console-formatted-stream": "^1.8.0",
     "@browser-bunyan/console-formatted-stream": "^1.8.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
@@ -101,7 +101,7 @@
     "connect-redis": "^4.0.4",
     "connect-redis": "^4.0.4",
     "cookie-parser": "^1.4.5",
     "cookie-parser": "^1.4.5",
     "csurf": "^1.11.0",
     "csurf": "^1.11.0",
-    "csv-to-markdown-table": "^1.1.0",
+    "csv-to-markdown-table": "^1.4.1",
     "date-fns": "^2.23.0",
     "date-fns": "^2.23.0",
     "dayjs": "^1.11.7",
     "dayjs": "^1.11.7",
     "detect-indent": "^7.0.0",
     "detect-indent": "^7.0.0",
@@ -131,7 +131,7 @@
     "is-iso-date": "^0.0.1",
     "is-iso-date": "^0.0.1",
     "ldapjs": "^3.0.2",
     "ldapjs": "^3.0.2",
     "lucene-query-parser": "^1.2.0",
     "lucene-query-parser": "^1.2.0",
-    "markdown-table": "^1.1.1",
+    "markdown-table": "^3.0.3",
     "md5": "^2.2.1",
     "md5": "^2.2.1",
     "mermaid": "^10.1.0",
     "mermaid": "^10.1.0",
     "method-override": "^3.0.0",
     "method-override": "^3.0.0",
@@ -163,7 +163,7 @@
     "qs": "^6.11.1",
     "qs": "^6.11.1",
     "rate-limiter-flexible": "^2.3.7",
     "rate-limiter-flexible": "^2.3.7",
     "react": "^18.2.0",
     "react": "^18.2.0",
-    "react-bootstrap-typeahead": "^5.2.2",
+    "react-bootstrap-typeahead": "^6.3.2",
     "react-card-flip": "^1.0.10",
     "react-card-flip": "^1.0.10",
     "react-datepicker": "^4.7.0",
     "react-datepicker": "^4.7.0",
     "react-disable": "^0.1.1",
     "react-disable": "^0.1.1",
@@ -214,7 +214,7 @@
     "xss": "^1.0.14",
     "xss": "^1.0.14",
     "y-mongodb-provider": "^0.1.7",
     "y-mongodb-provider": "^0.1.7",
     "y-socket.io": "^1.1.0",
     "y-socket.io": "^1.1.0",
-    "yjs": "^13.6.7"
+    "yjs": "^13.6.12"
   },
   },
   "// comments for defDependencies": {
   "// comments for defDependencies": {
     "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
     "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
@@ -268,7 +268,7 @@
     "pretty-bytes": "^6.1.1",
     "pretty-bytes": "^6.1.1",
     "react-codemirror2": "^6.0.0",
     "react-codemirror2": "^6.0.0",
     "react-copy-to-clipboard": "^5.0.1",
     "react-copy-to-clipboard": "^5.0.1",
-    "react-dropzone": "^11.2.4",
+    "react-dropzone": "^14.2.3",
     "react-hotkeys": "^2.0.0",
     "react-hotkeys": "^2.0.0",
     "react-input-autosize": "^3.0.0",
     "react-input-autosize": "^3.0.0",
     "rehype-rewrite": "^3.0.6",
     "rehype-rewrite": "^3.0.6",

+ 4 - 3
apps/app/public/static/locales/en_US/admin.json

@@ -746,7 +746,7 @@
       "description1":"Temporarily issue new users by email addresses.",
       "description1":"Temporarily issue new users by email addresses.",
       "description2":"A temporary password will be generated for the first login.",
       "description2":"A temporary password will be generated for the first login.",
       "invite_thru_email": "Send invitation email",
       "invite_thru_email": "Send invitation email",
-      "mail_setting_link":"<i class='icon-settings me-2'></i><a href='/admin/app'>Email settings</a>",
+      "mail_setting_link":"<span className='material-symbols-outlined me-2'>settings</span><a href='/admin/app'>Email settings</a>",
       "valid_email": "Valid email address is required",
       "valid_email": "Valid email address is required",
       "temporary_password": "The created user has a temporary password",
       "temporary_password": "The created user has a temporary password",
       "send_new_password": "Please send the new password to the user.",
       "send_new_password": "Please send the new password to the user.",
@@ -834,9 +834,10 @@
       "dropdown_desc": "Choose an action for private pages",
       "dropdown_desc": "Choose an action for private pages",
       "select_group": "Select a group",
       "select_group": "Select a group",
       "no_groups": "No groups to select",
       "no_groups": "No groups to select",
-      "publish_pages": "Publish all",
+      "publish_pages": "Publish pages that are publishable",
       "delete_pages": "Delete all",
       "delete_pages": "Delete all",
-      "transfer_pages": "Transfer to another group"
+      "transfer_pages": "Transfer to another group",
+      "option_explanation": "A \"publishable\" page is a page visible only to the group you want to delete. Pages that can be viewed by other groups will not be published."
     },
     },
     "update_parent_confirm_modal": {
     "update_parent_confirm_modal": {
       "header": "The parent of the group will be changed",
       "header": "The parent of the group will be changed",

+ 21 - 3
apps/app/public/static/locales/en_US/translation.json

@@ -311,6 +311,11 @@
     }
     }
   },
   },
   "page_edit": {
   "page_edit": {
+    "input_channels": "Input channels",
+    "theme": "Theme",
+    "keymap": "Keymap",
+    "indent": "Indent",
+    "editor_config": "Editor Config",
     "Show active line": "Show active line",
     "Show active line": "Show active line",
     "auto_format_table": "Auto format table",
     "auto_format_table": "Auto format table",
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",
@@ -391,11 +396,11 @@
       "Recursively": "Recursively",
       "Recursively": "Recursively",
       "Duplicate without exist path": "Duplicate without exist path",
       "Duplicate without exist path": "Duplicate without exist path",
       "Same page already exists": "Same page already exists",
       "Same page already exists": "Same page already exists",
-      "Only duplicate user related resources": "Only duplicate user related resources"
+      "Only duplicate user related pages": "Only duplicate pages you can access"
     },
     },
     "help": {
     "help": {
       "recursive": "Duplicate children of under this path recursively",
       "recursive": "Duplicate children of under this path recursively",
-      "only_user_related_resources": "This will only duplicate pages that the user has permission to view. If the page permission is set to \"Only specific groups\", only user related groups will be set to the page duplicate."
+      "only_inherit_user_related_groups": "If the page privilege is set to \"Only inside the group\", groups you do not belong to will lose access to the duplicated page"
     }
     }
   },
   },
   "duplicated_pages": "{{fromPath}} has been duplicated",
   "duplicated_pages": "{{fromPath}} has been duplicated",
@@ -538,6 +543,7 @@
     "search_again" : "Search again",
     "search_again" : "Search again",
     "number_of_list_to_display" : "Display",
     "number_of_list_to_display" : "Display",
     "page_number_unit" : "pages",
     "page_number_unit" : "pages",
+    "hit_number_unit" : "hit",
     "sort_axis": {
     "sort_axis": {
       "relationScore": "Sort by relevance",
       "relationScore": "Sort by relevance",
       "createdAt": "Creation date",
       "createdAt": "Creation date",
@@ -640,7 +646,7 @@
     "Username or E-mail has invalid characters": "Username or E-mail has invalid characters.",
     "Username or E-mail has invalid characters": "Username or E-mail has invalid characters.",
     "Password minimum character should be more than 6 characters": "Password minimum character should be more than 6 characters.",
     "Password minimum character should be more than 6 characters": "Password minimum character should be more than 6 characters.",
     "user_not_found": "User not found.",
     "user_not_found": "User not found.",
-    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>DuplicatedUsernameException occured</strong></p><p class='mb-0'> Your {{ failedProviderForDuplicatedUsernameException }} authentication was succeeded, but a new user could not be created. See the issue <a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
+    "provider_duplicated_username_exception": "<p><strong><span class='material-symbols-outlined me-1'>cancel</span>DuplicatedUsernameException occured</strong></p><p class='mb-0'> Your {{ failedProviderForDuplicatedUsernameException }} authentication was succeeded, but a new user could not be created. See the issue <a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
   },
   },
   "grid_edit":{
   "grid_edit":{
     "create_bootstrap_4_grid":"Create Bootstrap 4 Grid",
     "create_bootstrap_4_grid":"Create Bootstrap 4 Grid",
@@ -825,5 +831,17 @@
   },
   },
   "page_select_modal": {
   "page_select_modal": {
     "select_page_location": "Select page location"
     "select_page_location": "Select page location"
+  },
+  "wip_page": {
+    "save_as_wip": "Save as WIP (still being written)",
+    "success_save_as_wip": "Successfully saved as a WIP page",
+    "fail_save_as_wip": "Failed to save as a WIP page",
+    "alert": "This page is still being written",
+    "publish_page": "Publish page",
+    "success_publish_page": "Page has been published",
+    "fail_publish_page": "Failed to publish the Page"
+  },
+  "sidebar_header": {
+    "show_wip_page": "Show WIP"
   }
   }
 }
 }

+ 4 - 3
apps/app/public/static/locales/ja_JP/admin.json

@@ -756,7 +756,7 @@
       "description1": "メールアドレスを使用して新規ユーザーを仮発行します。",
       "description1": "メールアドレスを使用して新規ユーザーを仮発行します。",
       "description2": "初回のログイン時に使用する仮パスワードが生成されます。",
       "description2": "初回のログイン時に使用する仮パスワードが生成されます。",
       "invite_thru_email": "招待メールを送信する",
       "invite_thru_email": "招待メールを送信する",
-      "mail_setting_link": "<i class='icon-settings me-2'></i><a href='/admin/app'>メールの設定</a>",
+      "mail_setting_link": "<span className='material-symbols-outlined me-2'>settings</span><a href='/admin/app'>メールの設定</a>",
       "valid_email": "メールアドレスを入力してください。",
       "valid_email": "メールアドレスを入力してください。",
       "temporary_password": "作成したユーザーは仮パスワードが設定されています。",
       "temporary_password": "作成したユーザーは仮パスワードが設定されています。",
       "send_new_password": "新規発行したパスワードを、対象ユーザーへ連絡してください。",
       "send_new_password": "新規発行したパスワードを、対象ユーザーへ連絡してください。",
@@ -844,9 +844,10 @@
       "dropdown_desc": "削除するグループの限定公開ページの処理を選択してください",
       "dropdown_desc": "削除するグループの限定公開ページの処理を選択してください",
       "select_group": "グループを選択してください",
       "select_group": "グループを選択してください",
       "no_groups": "グループがありません",
       "no_groups": "グループがありません",
-      "publish_pages": "全て公開する",
+      "publish_pages": "公開可能なページを公開する",
       "delete_pages": "全て削除する",
       "delete_pages": "全て削除する",
-      "transfer_pages": "全て他のグループに移譲する"
+      "transfer_pages": "全て他のグループに移譲する",
+      "option_explanation": "「公開可能なページ」とは、削除するグループにのみ限定公開されているページを指します。他のグループも閲覧可能なページは公開対象となりません。"
     },
     },
     "update_parent_confirm_modal": {
     "update_parent_confirm_modal": {
       "header": "グループの親が変更されます",
       "header": "グループの親が変更されます",

+ 21 - 3
apps/app/public/static/locales/ja_JP/translation.json

@@ -344,6 +344,11 @@
     }
     }
   },
   },
   "page_edit": {
   "page_edit": {
+    "input_channels": "チャンネル名",
+    "theme": "テーマ",
+    "keymap": "キーマップ",
+    "indent": "インデント",
+    "editor_config": "エディタ設定",
     "Show active line": "アクティブ行をハイライト",
     "Show active line": "アクティブ行をハイライト",
     "auto_format_table": "表の自動整形",
     "auto_format_table": "表の自動整形",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
@@ -424,11 +429,11 @@
       "Recursively": "再帰的に複製",
       "Recursively": "再帰的に複製",
       "Duplicate without exist path": "存在するパス以外を複製する",
       "Duplicate without exist path": "存在するパス以外を複製する",
       "Same page already exists": "同じページがすでに存在します",
       "Same page already exists": "同じページがすでに存在します",
-      "Only duplicate user related resources": "ユーザに関連のあるリソースのみを複製する"
+      "Only duplicate user related pages": "自分が閲覧可能なページのみを複製する"
     },
     },
     "help": {
     "help": {
       "recursive": "配下のページも複製します",
       "recursive": "配下のページも複製します",
-      "only_user_related_resources": "ユーザが閲覧可能なページのみを複製します。また、閲覧権限が「特定グループのみ」で設定されている場合、複製後のページにはユーザが所属するグループのみを閲覧可能なグループとして設定します。"
+      "only_inherit_user_related_groups": "閲覧権限が「特定グループのみ」で設定されている場合、複製されたページを閲覧可能なグループ一覧から、自分が所属していないものは取り除かれます"
     }
     }
   },
   },
   "duplicated_pages": "{{fromPath}} を複製しました",
   "duplicated_pages": "{{fromPath}} を複製しました",
@@ -571,6 +576,7 @@
     "search_again" : "再検索",
     "search_again" : "再検索",
     "number_of_list_to_display" : "表示件数",
     "number_of_list_to_display" : "表示件数",
     "page_number_unit" : "件",
     "page_number_unit" : "件",
+    "hit_number_unit" : "件",
     "sort_axis": {
     "sort_axis": {
       "relationScore": "関連度順",
       "relationScore": "関連度順",
       "createdAt": "作成日時",
       "createdAt": "作成日時",
@@ -673,7 +679,7 @@
     "Username or E-mail has invalid characters": "ユーザー名または、メールアドレスに無効な文字があります",
     "Username or E-mail has invalid characters": "ユーザー名または、メールアドレスに無効な文字があります",
     "Password minimum character should be more than 6 characters": "パスワードの最小文字数は6文字以上です",
     "Password minimum character should be more than 6 characters": "パスワードの最小文字数は6文字以上です",
     "user_not_found": "ユーザーが見つかりません",
     "user_not_found": "ユーザーが見つかりません",
-    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>エラー: DuplicatedUsernameException</strong></p><p class='mb-0'> {{ failedProviderForDuplicatedUsernameException }} 認証は成功しましたが、新しいユーザーを作成できませんでした。詳しくは<a href='https://github.com/weseek/growi/issues/193'>こちら: #193</a>.</p>"
+    "provider_duplicated_username_exception": "<p><strong><span class='material-symbols-outlined me-1'>cancel</span>エラー: DuplicatedUsernameException</strong></p><p class='mb-0'> {{ failedProviderForDuplicatedUsernameException }} 認証は成功しましたが、新しいユーザーを作成できませんでした。詳しくは<a href='https://github.com/weseek/growi/issues/193'>こちら: #193</a>.</p>"
   },
   },
   "grid_edit":{
   "grid_edit":{
     "create_bootstrap_4_grid":"Bootstrap 4 グリッドを作成",
     "create_bootstrap_4_grid":"Bootstrap 4 グリッドを作成",
@@ -858,5 +864,17 @@
   },
   },
   "page_select_modal": {
   "page_select_modal": {
     "select_page_location": "ページの場所を選択"
     "select_page_location": "ページの場所を選択"
+  },
+  "wip_page": {
+    "save_as_wip": "WIP (執筆途中) として保存",
+    "success_save_as_wip": "WIP ページとして保存しました",
+    "fail_save_as_wip": "WIP ページとして保存できませんでした",
+    "alert": "このページは執筆途中です",
+    "publish_page": "WIP を解除",
+    "success_publish_page": "WIP を解除しました",
+    "fail_publish_page": "WIP を解除できませんでした"
+  },
+  "sidebar_header": {
+    "show_wip_page": "WIP を表示"
   }
   }
 }
 }

+ 4 - 3
apps/app/public/static/locales/zh_CN/admin.json

@@ -754,7 +754,7 @@
       "emails": "电子邮件",
       "emails": "电子邮件",
       "description1": "通过电子邮件地址临时发布新用户。",
       "description1": "通过电子邮件地址临时发布新用户。",
       "description2": "将为首次登录生成一个临时密码。",
       "description2": "将为首次登录生成一个临时密码。",
-      "mail_setting_link": "<i class='icon-settings me-2'></i><a href='/admin/app'>Email settings</a>",
+      "mail_setting_link": "<span className='material-symbols-outlined me-2'>settings</span><a href='/admin/app'>Email settings</a>",
       "valid_email": "需要有效的电子邮件地址",
       "valid_email": "需要有效的电子邮件地址",
       "invite_thru_email": "发送邀请电子邮件",
       "invite_thru_email": "发送邀请电子邮件",
       "temporary_password": "创建的用户具有临时密码",
       "temporary_password": "创建的用户具有临时密码",
@@ -843,9 +843,10 @@
       "dropdown_desc": "为私人页选择操作",
       "dropdown_desc": "为私人页选择操作",
       "select_group": "选择组",
       "select_group": "选择组",
       "no_groups": "没有可选择的组",
       "no_groups": "没有可选择的组",
-      "publish_pages": "全部发布",
+      "publish_pages": "发布可以发布的页面",
       "delete_pages": "全部删除",
       "delete_pages": "全部删除",
-      "transfer_pages": "转移到另一组"
+      "transfer_pages": "转移到另一组",
+      "option_explanation": "\"可发布页面\"是指仅对您要删除的群组可见的页面。其他群组可以查看的页面将不会被发布。"
     },
     },
     "update_parent_confirm_modal": {
     "update_parent_confirm_modal": {
       "header": "该组的父组被改变",
       "header": "该组的父组被改变",

+ 21 - 3
apps/app/public/static/locales/zh_CN/translation.json

@@ -301,6 +301,11 @@
 		}
 		}
 	},
 	},
 	"page_edit": {
 	"page_edit": {
+    "input_channels": "频道名",
+    "theme": "主题",
+    "keymap": "键表",
+    "indent": "缩进",
+    "editor_config": "编辑器配置",
 		"Show active line": "显示活动行",
 		"Show active line": "显示活动行",
 		"auto_format_table": "自动格式化表格",
 		"auto_format_table": "自动格式化表格",
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
@@ -381,11 +386,11 @@
       "Recursively": "Recursively",
       "Recursively": "Recursively",
       "Duplicate without exist path": "Duplicate without exist path",
       "Duplicate without exist path": "Duplicate without exist path",
       "Same page already exists": "Same page already exists",
       "Same page already exists": "Same page already exists",
-      "Only duplicate user related resources": "Only duplicate user related resources"
+      "Only duplicate user related pages": "Only duplicate pages you can access"
     },
     },
     "help": {
     "help": {
       "recursive": "Duplicate children of under this path recursively",
       "recursive": "Duplicate children of under this path recursively",
-      "only_user_related_resources": "This will only duplicate pages that the user has permission to view. If the page permission is set to \"Only specific groups\", only user related groups will be set to the page duplicate."
+      "only_inherit_user_related_groups": "If the page privilege is set to \"Only inside the group\", groups you do not belong to will lose access to the duplicated page"
     }
     }
   },
   },
   "duplicated_pages": "{{fromPath}} 已重复",
   "duplicated_pages": "{{fromPath}} 已重复",
@@ -541,6 +546,7 @@
     "search_again" : "再次搜索",
     "search_again" : "再次搜索",
     "number_of_list_to_display" : "显示器的数量",
     "number_of_list_to_display" : "显示器的数量",
     "page_number_unit" : "例",
     "page_number_unit" : "例",
+    "hit_number_unit" : "例",
     "sort_axis": {
     "sort_axis": {
       "relationScore": "按相关性排序",
       "relationScore": "按相关性排序",
       "createdAt": "按创建日期排序",
       "createdAt": "按创建日期排序",
@@ -643,7 +649,7 @@
     "Username or E-mail has invalid characters": "用户名或电子邮件有无效的字符",
     "Username or E-mail has invalid characters": "用户名或电子邮件有无效的字符",
     "Password minimum character should be more than 6 characters": "密码最小字符应超过6个字符",
     "Password minimum character should be more than 6 characters": "密码最小字符应超过6个字符",
     "user_not_found": "未找到用户",
     "user_not_found": "未找到用户",
-    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>发生了重复用户名异常</strong></p><p class='mb-0'> 你的 {{ failedProviderForDuplicatedUsernameException }} 认证成功了,但不能创建新的用户。参见问题<a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
+    "provider_duplicated_username_exception": "<p><strong><span class='material-symbols-outlined me-1'>cancel</span>发生了重复用户名异常</strong></p><p class='mb-0'> 你的 {{ failedProviderForDuplicatedUsernameException }} 认证成功了,但不能创建新的用户。参见问题<a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
 	},
 	},
   "grid_edit":{
   "grid_edit":{
     "create_bootstrap_4_grid":"创建Bootstrap 4网格",
     "create_bootstrap_4_grid":"创建Bootstrap 4网格",
@@ -828,5 +834,17 @@
   },
   },
   "page_select_modal": {
   "page_select_modal": {
     "select_page_location": "选择页面位置"
     "select_page_location": "选择页面位置"
+  },
+  "wip_page": {
+    "save_as_wip": "保存为 WIP(仍在撰写中)",
+    "success_save_as_wip": "成功保存为 WIP 页面",
+    "fail_save_as_wip": "保存为 WIP 页失败",
+    "alert": "本页仍在编写中",
+    "publish_page": "发布 WIP",
+    "success_publish_page": "WIP 已停用",
+    "fail_publish_page": "无法停用 WIP"
+  },
+  "sidebar_header": {
+    "show_wip_page": "显示 WIP"
   }
   }
 }
 }

+ 1 - 1
apps/app/src/client/models/MarkdownTable.js

@@ -1,5 +1,5 @@
 import csvToMarkdown from 'csv-to-markdown-table';
 import csvToMarkdown from 'csv-to-markdown-table';
-import markdownTable from 'markdown-table';
+import { markdownTable } from 'markdown-table';
 import stringWidth from 'string-width';
 import stringWidth from 'string-width';
 
 
 // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
 // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83

+ 3 - 1
apps/app/src/client/services/create-page/use-create-template-page.ts

@@ -1,11 +1,13 @@
 import { useCallback } from 'react';
 import { useCallback } from 'react';
 
 
+import { Origin } from '@growi/core';
 import { isCreatablePage } from '@growi/core/dist/utils/page-path-utils';
 import { isCreatablePage } from '@growi/core/dist/utils/page-path-utils';
 import { normalizePath } from '@growi/core/dist/utils/path-utils';
 import { normalizePath } from '@growi/core/dist/utils/path-utils';
 
 
 import type { LabelType } from '~/interfaces/template';
 import type { LabelType } from '~/interfaces/template';
 import { useCurrentPagePath } from '~/stores/page';
 import { useCurrentPagePath } from '~/stores/page';
 
 
+
 import { useCreatePageAndTransit } from './use-create-page-and-transit';
 import { useCreatePageAndTransit } from './use-create-page-and-transit';
 
 
 type UseCreateTemplatePage = () => {
 type UseCreateTemplatePage = () => {
@@ -25,7 +27,7 @@ export const useCreateTemplatePage: UseCreateTemplatePage = () => {
     if (isLoadingPagePath || !isCreatable) return;
     if (isLoadingPagePath || !isCreatable) return;
 
 
     return createAndTransit(
     return createAndTransit(
-      { path: normalizePath(`${currentPagePath}/${label}`) },
+      { path: normalizePath(`${currentPagePath}/${label}`), wip: false, origin: Origin.View },
       { shouldCheckPageExists: true },
       { shouldCheckPageExists: true },
     );
     );
   }, [currentPagePath, isCreatable, isLoadingPagePath, createAndTransit]);
   }, [currentPagePath, isCreatable, isLoadingPagePath, createAndTransit]);

+ 12 - 1
apps/app/src/client/services/page-operation.ts

@@ -1,5 +1,6 @@
 import { useCallback } from 'react';
 import { useCallback } from 'react';
 
 
+import type { IPageHasId } from '@growi/core';
 import { SubscriptionStatusType } from '@growi/core';
 import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
@@ -124,7 +125,7 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
     await mutateCurrentPageId(pageId);
     await mutateCurrentPageId(pageId);
     const updatedPage = await mutateCurrentPage();
     const updatedPage = await mutateCurrentPage();
 
 
-    if (updatedPage == null) { return }
+    if (updatedPage == null || updatedPage.revision == null) { return }
 
 
     // supress to mutate only when updated from built-in editor
     // supress to mutate only when updated from built-in editor
     // and see: https://github.com/weseek/growi/pull/7118
     // and see: https://github.com/weseek/growi/pull/7118
@@ -159,3 +160,13 @@ export const exist = async(path: string): Promise<PageExistResponse> => {
   const res = await apiv3Get<PageExistResponse>('/page/exist', { path });
   const res = await apiv3Get<PageExistResponse>('/page/exist', { path });
   return res.data;
   return res.data;
 };
 };
+
+export const publish = async(pageId: string): Promise<IPageHasId> => {
+  const res = await apiv3Put(`/page/${pageId}/publish`);
+  return res.data;
+};
+
+export const unpublish = async(pageId: string): Promise<IPageHasId> => {
+  const res = await apiv3Put(`/page/${pageId}/unpublish`);
+  return res.data;
+};

+ 3 - 1
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -2,6 +2,7 @@ import { useCallback, useEffect } from 'react';
 
 
 import type EventEmitter from 'events';
 import type EventEmitter from 'events';
 
 
+import { Origin } from '@growi/core';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
 
 import { replaceDrawioInMarkdown } from '~/components/Page/markdown-drawio-util-for-view';
 import { replaceDrawioInMarkdown } from '~/components/Page/markdown-drawio-util-for-view';
@@ -34,7 +35,7 @@ export const useDrawioModalLauncherForView = (opts?: {
   const { open: openDrawioModal } = useDrawioModal();
   const { open: openDrawioModal } = useDrawioModal();
 
 
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
-    if (currentPage == null || shareLinkId != null) {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
       return;
     }
     }
 
 
@@ -47,6 +48,7 @@ export const useDrawioModalLauncherForView = (opts?: {
         pageId: currentPage._id,
         pageId: currentPage._id,
         revisionId: currentRevisionId,
         revisionId: currentRevisionId,
         body: newMarkdown,
         body: newMarkdown,
+        origin: Origin.View,
       });
       });
 
 
       opts?.onSaveSuccess?.();
       opts?.onSaveSuccess?.();

+ 6 - 1
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -2,6 +2,8 @@ import { useCallback, useEffect } from 'react';
 
 
 import type EventEmitter from 'events';
 import type EventEmitter from 'events';
 
 
+import { Origin } from '@growi/core';
+
 import type MarkdownTable from '~/client/models/MarkdownTable';
 import type MarkdownTable from '~/client/models/MarkdownTable';
 import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/Page/markdown-table-util-for-view';
 import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/Page/markdown-table-util-for-view';
 import { useShareLinkId } from '~/stores/context';
 import { useShareLinkId } from '~/stores/context';
@@ -33,7 +35,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
   const { open: openHandsontableModal } = useHandsontableModal();
   const { open: openHandsontableModal } = useHandsontableModal();
 
 
   const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
   const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
-    if (currentPage == null || shareLinkId != null) {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
       return;
     }
     }
 
 
@@ -46,6 +48,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
         pageId: currentPage._id,
         pageId: currentPage._id,
         revisionId: currentRevisionId,
         revisionId: currentRevisionId,
         body: newMarkdown,
         body: newMarkdown,
+        origin: Origin.View,
       });
       });
 
 
       opts?.onSaveSuccess?.();
       opts?.onSaveSuccess?.();
@@ -64,6 +67,8 @@ export const useHandsontableModalLauncherForView = (opts?: {
     }
     }
 
 
     const handler = (bol: number, eol: number) => {
     const handler = (bol: number, eol: number) => {
+      if (currentPage.revision == null) return;
+
       const markdown = currentPage.revision.body;
       const markdown = currentPage.revision.body;
       const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
       const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
       openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));
       openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));

+ 18 - 0
apps/app/src/client/services/use-toastr-on-error.tsx

@@ -0,0 +1,18 @@
+import { useCallback } from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+import { toastError } from '~/client/util/toastr';
+
+export const useToastrOnError = <P, R>(method?: (param?: P) => Promise<R|undefined>): (param?: P) => Promise<R|undefined> => {
+  const { t } = useTranslation('commons');
+
+  return useCallback(async(param) => {
+    try {
+      return await method?.(param);
+    }
+    catch (err) {
+      toastError(t('toaster.create_failed', { target: 'a page' }));
+    }
+  }, [method, t]);
+};

+ 2 - 2
apps/app/src/client/util/bookmark-utils.ts

@@ -1,6 +1,6 @@
 import type { IRevision, Ref } from '@growi/core';
 import type { IRevision, Ref } from '@growi/core';
 
 
-import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+import type { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 
 
 import { apiv3Delete, apiv3Post, apiv3Put } from './apiv3-client';
 import { apiv3Delete, apiv3Post, apiv3Put } from './apiv3-client';
 
 
@@ -31,7 +31,7 @@ export const deleteBookmarkFolder = async(bookmarkFolderId: string): Promise<voi
 };
 };
 
 
 // Rename page from bookmark item control
 // Rename page from bookmark item control
-export const renamePage = async(pageId: string, revisionId: Ref<IRevision>, newPagePath: string): Promise<void> => {
+export const renamePage = async(pageId: string, revisionId: Ref<IRevision> | undefined, newPagePath: string): Promise<void> => {
   await apiv3Put('/pages/rename', { pageId, revisionId, newPagePath });
   await apiv3Put('/pages/rename', { pageId, revisionId, newPagePath });
 };
 };
 
 

+ 2 - 2
apps/app/src/components/Admin/AdminHome/AdminHome.jsx

@@ -52,7 +52,7 @@ const AdminHome = (props) => {
             </p>
             </p>
             <hr />
             <hr />
             <a className="btn-link" href="/admin/app" rel="noopener noreferrer">
             <a className="btn-link" href="/admin/app" rel="noopener noreferrer">
-              <i className="fa fa-link ms-1" aria-hidden="true"></i>
+              <span className="material-symbols-outlined ms-1" aria-hidden="true">link</span>
               <strong>{t('admin:maintenance_mode.end_maintenance_mode')}</strong>
               <strong>{t('admin:maintenance_mode.end_maintenance_mode')}</strong>
             </a>
             </a>
           </div>
           </div>
@@ -65,7 +65,7 @@ const AdminHome = (props) => {
           <div className={`alert ${migrationStatus.isV5Compatible == null ? 'alert-warning' : 'alert-info'}`}>
           <div className={`alert ${migrationStatus.isV5Compatible == null ? 'alert-warning' : 'alert-info'}`}>
             {t('admin:v5_page_migration.migration_desc')}
             {t('admin:v5_page_migration.migration_desc')}
             <a className="btn-link" href="/admin/app" rel="noopener noreferrer">
             <a className="btn-link" href="/admin/app" rel="noopener noreferrer">
-              <i className="fa fa-link ms-1" aria-hidden="true"></i>
+              <span className="material-symbols-outlined ms-1" aria-hidden="true">link</span>
               <strong>{t('admin:v5_page_migration.upgrade_to_v5')}</strong>
               <strong>{t('admin:v5_page_migration.upgrade_to_v5')}</strong>
             </a>
             </a>
           </div>
           </div>

+ 1 - 1
apps/app/src/components/Admin/App/AppSettingsPageContents.tsx

@@ -62,7 +62,7 @@ const AppSettingsPageContents = (props: Props) => {
             </p>
             </p>
             <hr />
             <hr />
             <a className="btn-link" href="#maintenance-mode" rel="noopener noreferrer">
             <a className="btn-link" href="#maintenance-mode" rel="noopener noreferrer">
-              <i className="fa fa-fw fa-arrow-down ms-1" aria-hidden="true"></i>
+              <span className="material-symbols-outlined ms-1" aria-hidden="true">expand_more</span>
               <strong>{t('admin:maintenance_mode.end_maintenance_mode')}</strong>
               <strong>{t('admin:maintenance_mode.end_maintenance_mode')}</strong>
             </a>
             </a>
           </div>
           </div>

+ 2 - 2
apps/app/src/components/Admin/App/MaskedInput.tsx

@@ -33,9 +33,9 @@ export default function MaskedInput(props: Props): JSX.Element {
       />
       />
       <span onClick={togglePassword} className={styles.PasswordReveal}>
       <span onClick={togglePassword} className={styles.PasswordReveal}>
         {passwordShown ? (
         {passwordShown ? (
-          <i className="fa fa-eye" />
+          <span className="material-symbols-outlined">visibility</span>
         ) : (
         ) : (
-          <i className="fa fa-eye-slash" />
+          <span className="material-symbols-outlined">visibility_off</span>
         )}
         )}
       </span>
       </span>
     </div>
     </div>

+ 4 - 3
apps/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useState, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useState, useCallback } from 'react';
 
 
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { UserPicture } from '@growi/ui/dist/components';
 import { UserPicture } from '@growi/ui/dist/components';
@@ -7,7 +8,7 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { Tooltip } from 'reactstrap';
 import { Tooltip } from 'reactstrap';
 
 
-import { IActivityHasId } from '~/interfaces/activity';
+import type { IActivityHasId } from '~/interfaces/activity';
 
 
 type Props = {
 type Props = {
   activityList: IActivityHasId[]
   activityList: IActivityHasId[]
@@ -64,7 +65,7 @@ export const ActivityTable : FC<Props> = (props: Props) => {
                   {activity.endpoint}
                   {activity.endpoint}
                   <CopyToClipboard text={activity.endpoint} onCopy={showToolTip}>
                   <CopyToClipboard text={activity.endpoint} onCopy={showToolTip}>
                     <button type="button" className="btn btn-outline-secondary border-0 pull-right" id="tooltipTarget">
                     <button type="button" className="btn btn-outline-secondary border-0 pull-right" id="tooltipTarget">
-                      <i className="fa fa-clipboard" aria-hidden="true"></i>
+                      <span className="material-symbols-outlined" aria-hidden="true">content_paste</span>
                     </button>
                     </button>
                   </CopyToClipboard>
                   </CopyToClipboard>
                   <Tooltip placement="top" isOpen={tooltopOpen} fade={false} target="tooltipTarget">
                   <Tooltip placement="top" isOpen={tooltopOpen} fade={false} target="tooltipTarget">

+ 3 - 2
apps/app/src/components/Admin/AuditLog/AuditLogSettings.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useState } from 'react';
+import type { FC } from 'react';
+import React, { useState } from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { Collapse } from 'reactstrap';
 import { Collapse } from 'reactstrap';
@@ -54,7 +55,7 @@ export const AuditLogSettings: FC = () => {
       </p>
       </p>
       <p className="mt-1">
       <p className="mt-1">
         <button type="button" className="btn btn-link p-0" aria-expanded="false" onClick={() => setIsExpandActionList(!isExpandActionList)}>
         <button type="button" className="btn btn-link p-0" aria-expanded="false" onClick={() => setIsExpandActionList(!isExpandActionList)}>
-          <i className={`fa fa-fw fa-arrow-right ${isExpandActionList ? 'fa-rotate-90' : ''}`}></i>
+          <span className={`material-symbols-outlined me-1 ${isExpandActionList ? 'fa-rotate-90' : ''}`}>navigate_next</span>
           { t('admin:audit_log_management.action_list') }
           { t('admin:audit_log_management.action_list') }
         </button>
         </button>
       </p>
       </p>

+ 3 - 2
apps/app/src/components/Admin/AuditLog/DateRangePicker.tsx

@@ -1,4 +1,5 @@
-import React, { FC, forwardRef, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { forwardRef, useCallback } from 'react';
 
 
 import { addDays, format } from 'date-fns';
 import { addDays, format } from 'date-fns';
 import DatePicker from 'react-datepicker';
 import DatePicker from 'react-datepicker';
@@ -19,7 +20,7 @@ const CustomInput = forwardRef<HTMLInputElement, CustomInputProps>((props: Custo
   return (
   return (
     <div className="input-group admin-audit-log">
     <div className="input-group admin-audit-log">
       <span className="input-group-text">
       <span className="input-group-text">
-        <i className="fa fa-fw fa-calendar" />
+        <span className="material-symbols-outlined me-1">calendar_month</span>
       </span>
       </span>
       <input
       <input
         ref={ref}
         ref={ref}

+ 9 - 10
apps/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx

@@ -1,11 +1,13 @@
+import type { ForwardRefRenderFunction } from 'react';
 import React, {
 import React, {
-  Fragment, useState, useCallback, useRef, ForwardRefRenderFunction, forwardRef, useImperativeHandle,
+  Fragment, useState, useCallback, forwardRef, useRef, useImperativeHandle,
 } from 'react';
 } from 'react';
 
 
+import type { TypeaheadRef } from 'react-bootstrap-typeahead';
 import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
-import { IClearable } from '~/client/interfaces/clearable';
+import type { IClearable } from '~/client/interfaces/clearable';
 import { useSWRxUsernames } from '~/stores/user';
 import { useSWRxUsernames } from '~/stores/user';
 
 
 
 
@@ -30,7 +32,7 @@ const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Pro
   const { onChange } = props;
   const { onChange } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  const typeaheadRef = useRef<IClearable>(null);
+  const typeaheadRef = useRef<TypeaheadRef>(null);
 
 
   /*
   /*
    * State
    * State
@@ -41,11 +43,11 @@ const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Pro
    * Fetch
    * Fetch
    */
    */
   const requestOptions = { isIncludeActiveUser: true, isIncludeInactiveUser: true, isIncludeActivitySnapshotUser: true };
   const requestOptions = { isIncludeActiveUser: true, isIncludeInactiveUser: true, isIncludeActivitySnapshotUser: true };
-  const { data: usernameData, error } = useSWRxUsernames(searchKeyword, 0, 5, requestOptions);
+  const { data: usernameData, error, isLoading: _isLoading } = useSWRxUsernames(searchKeyword, 0, 5, requestOptions);
   const activeUsernames = usernameData?.activeUser?.usernames != null ? usernameData.activeUser.usernames : [];
   const activeUsernames = usernameData?.activeUser?.usernames != null ? usernameData.activeUser.usernames : [];
   const inactiveUsernames = usernameData?.inactiveUser?.usernames != null ? usernameData.inactiveUser.usernames : [];
   const inactiveUsernames = usernameData?.inactiveUser?.usernames != null ? usernameData.inactiveUser.usernames : [];
   const activitySnapshotUsernames = usernameData?.activitySnapshotUser?.usernames != null ? usernameData.activitySnapshotUser.usernames : [];
   const activitySnapshotUsernames = usernameData?.activitySnapshotUser?.usernames != null ? usernameData.activitySnapshotUser.usernames : [];
-  const isLoading = usernameData === undefined && error == null;
+  const isLoading = _isLoading === true && error == null;
 
 
   const allUser: UserDataType[] = [];
   const allUser: UserDataType[] = [];
   const pushToAllUser = (usernames: string[], category: CategoryType) => {
   const pushToAllUser = (usernames: string[], category: CategoryType) => {
@@ -59,10 +61,8 @@ const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Pro
    * Functions
    * Functions
    */
    */
   const changeHandler = useCallback((userData: UserDataType[]) => {
   const changeHandler = useCallback((userData: UserDataType[]) => {
-    if (onChange != null) {
-      const usernames = userData.map(user => user.username);
-      onChange(usernames);
-    }
+    const usernames = userData.map(user => user.username);
+    onChange(usernames);
   }, [onChange]);
   }, [onChange]);
 
 
   const searchHandler = useCallback((text: string) => {
   const searchHandler = useCallback((text: string) => {
@@ -120,7 +120,6 @@ const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Pro
         delay={400}
         delay={400}
         minLength={0}
         minLength={0}
         placeholder={t('admin:audit_log_management.username')}
         placeholder={t('admin:audit_log_management.username')}
-        caseSensitive={false}
         isLoading={isLoading}
         isLoading={isLoading}
         options={allUser}
         options={allUser}
         onSearch={searchHandler}
         onSearch={searchHandler}

+ 5 - 3
apps/app/src/components/Admin/AuditLog/SelectActionDropdown.tsx

@@ -1,9 +1,11 @@
-import React, { FC, useMemo, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useMemo, useCallback } from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
+import type { SupportedActionType, SupportedActionCategoryType } from '~/interfaces/activity';
 import {
 import {
-  SupportedActionType, SupportedActionCategoryType, SupportedActionCategory,
+  SupportedActionCategory,
   PageActions, CommentActions, TagActions, ShareLinkActions, AttachmentActions, InAppNotificationActions, SearchActions, UserActions, AdminActions,
   PageActions, CommentActions, TagActions, ShareLinkActions, AttachmentActions, InAppNotificationActions, SearchActions, UserActions, AdminActions,
 } from '~/interfaces/activity';
 } from '~/interfaces/activity';
 
 
@@ -78,7 +80,7 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
   return (
   return (
     <div className="btn-group me-2 admin-audit-log">
     <div className="btn-group me-2 admin-audit-log">
       <button className="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown">
       <button className="btn btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown">
-        <i className="fa fa-fw fa-bolt" />{t('admin:audit_log_management.action')}
+        <span className="material-symbols-outlined me-1">bolt</span>{t('admin:audit_log_management.action')}
       </button>
       </button>
       <ul className="dropdown-menu select-action-dropdown" aria-labelledby="dropdownMenuButton">
       <ul className="dropdown-menu select-action-dropdown" aria-labelledby="dropdownMenuButton">
         {dropdownItems.map(item => (
         {dropdownItems.map(item => (

+ 1 - 1
apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx

@@ -69,7 +69,7 @@ const CustomizeNoscriptSetting = (props: Props): JSX.Element => {
             aria-expanded="false"
             aria-expanded="false"
             aria-controls="collapseExampleHtml"
             aria-controls="collapseExampleHtml"
           >
           >
-            <i className="fa fa-fw fa-chevron-right" aria-hidden="true"></i>
+            <span className="material-symbols-outlined me-1" aria-hidden="true">navigate_next</span>
             Example for Google Tag Manager
             Example for Google Tag Manager
           </a>
           </a>
           <div className="collapse" id="collapseExampleHtml">
           <div className="collapse" id="collapseExampleHtml">

+ 1 - 1
apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx

@@ -66,7 +66,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
             aria-expanded="false"
             aria-expanded="false"
             aria-controls="collapseExampleScript"
             aria-controls="collapseExampleScript"
           >
           >
-            <i className="fa fa-fw fa-chevron-right" aria-hidden="true"></i>
+            <span className="material-symbols-outlined me-1" aria-hidden="true">navigate_next</span>
             Example for Google Tag Manager
             Example for Google Tag Manager
           </a>
           </a>
           <div className="collapse" id="collapseExampleScript">
           <div className="collapse" id="collapseExampleScript">

+ 21 - 10
apps/app/src/components/Admin/Customize/ThemeColorBox.tsx

@@ -15,26 +15,37 @@ export const ThemeColorBox = (props: Props): JSX.Element => {
     isSelected, metadata, onSelected,
     isSelected, metadata, onSelected,
   } = props;
   } = props;
   const {
   const {
-    name, bg, topbar, sidebar, accent, isPresetTheme,
+    name, lightBg, darkBg, lightSidebar, darkSidebar, lightIcon, darkIcon, createBtn, isPresetTheme,
   } = metadata;
   } = metadata;
 
 
   return (
   return (
+    // TODO: Display a primary color border when icon is selected
     <div
     <div
       id={`theme-option-${name}`}
       id={`theme-option-${name}`}
       className={`theme-option-container d-flex flex-column align-items-center ${isSelected ? 'active' : ''}`}
       className={`theme-option-container d-flex flex-column align-items-center ${isSelected ? 'active' : ''}`}
       onClick={onSelected}
       onClick={onSelected}
     >
     >
-      <a id={name} role="button" className={`m-0 ${name} theme-button`}>
-        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
-          <g>
-            <path d="M -1 -1 L65 -1 L65 65 L-1 65 L-1 -1 Z" fill={bg}></path>
-            <path d="M -1 -1 L65 -1 L65 15 L-1 15 L-1 -1 Z" fill={topbar}></path>
-            <path d="M -1 15 L15 15 L15 65 L-1 65 L-1 15 Z" fill={sidebar}></path>
-            <path d="M 65 45 L65 65 L45 65 L65 45 Z" fill={accent}></path>
-          </g>
+      <a id={name} role="button" className={`m-0 rounded ${name} theme-button`}>
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" className="rounded">
+          <path d="M32.5,0V36.364L64,20.437V0Z" fill={lightBg} />
+          <path d="M32.5,36.364V64H64V20.438Z" fill={darkBg} />
+          <path
+            d="M4.077,20.648,10.164,10.1H22.338l6.087,10.544L22.338,31.19H10.164ZM0,0V52.8l6.436-3.255v-1.8H10L17.189,44.1H6.436V42.044H21.267L32.5,36.364V0Z"
+            fill={lightSidebar}
+          />
+          <path
+            d="M6.436,53.44H26.065V55.5H6.436Zm14.831-11.4h4.8v2.061H17.189L10,47.743H26.065V49.8l-19.629,0v-.259L0,52.8V64H32.5V36.364Z"
+            fill={darkSidebar}
+          />
+          <path d="M22.338,31.19l6.087-10.543L22.338,10.1H10.163L4.077,20.647,10.163,31.19Z" fill={createBtn} />
+          <path d="M6.436,49.543,10,47.742H6.436Z" fill={lightIcon} />
+          <path d="M6.436,44.106H17.189l4.078-2.062H6.436Z" fill={lightIcon} />
+          <path d="M6.436,49.8l19.629,0V47.742H10l-3.561,1.8Z" fill={darkIcon} />
+          <path d="M26.065,44.106V42.044h-4.8L17.19,44.106Z" fill={darkIcon} />
+          <rect width="19.629" height="2.062" transform="translate(6.436 53.439)" fill={darkIcon} />
         </svg>
         </svg>
       </a>
       </a>
-      <span className="theme-option-name"><b>{ name }</b></span>
+      <span className="theme-option-name mt-2"><b>{ name }</b></span>
       { !isPresetTheme && <span className="theme-option-badge badge bg-primary mt-1">Plugin</span> }
       { !isPresetTheme && <span className="theme-option-badge badge bg-primary mt-1">Plugin</span> }
     </div>
     </div>
   );
   );

+ 2 - 2
apps/app/src/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx

@@ -166,10 +166,10 @@ const SelectCollectionsModal = (props: Props): JSX.Element => {
           <div className="row">
           <div className="row">
             <div className="col-sm-12">
             <div className="col-sm-12">
               <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={checkAll}>
               <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={checkAll}>
-                <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}
+                <span className="material-symbols-outlined">check_box</span> {t('admin:export_management.check_all')}
               </button>
               </button>
               <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={uncheckAll}>
               <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={uncheckAll}>
-                <i className="fa fa-square-o"></i> {t('admin:export_management.uncheck_all')}
+                <span className="material-symbols-outlined">check_box_outline_blank</span> {t('admin:export_management.uncheck_all')}
               </button>
               </button>
             </div>
             </div>
           </div>
           </div>

+ 2 - 2
apps/app/src/components/Admin/G2GDataTransferExportForm.tsx

@@ -202,12 +202,12 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
       <form className="mt-3 row row-cols-lg-auto g-3 align-items-center">
       <form className="mt-3 row row-cols-lg-auto g-3 align-items-center">
         <div className="col-12">
         <div className="col-12">
           <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={checkAll}>
           <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={checkAll}>
-            <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}
+            <span className="material-symbols-outlined">check_box</span>, {t('admin:export_management.check_all')}
           </button>
           </button>
         </div>
         </div>
         <div className="col-12">
         <div className="col-12">
           <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={uncheckAll}>
           <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={uncheckAll}>
-            <i className="fa fa-square-o"></i> {t('admin:export_management.uncheck_all')}
+            <span className="material-symbols-outlined">check_box_outline_blank</span> {t('admin:export_management.uncheck_all')}
           </button>
           </button>
         </div>
         </div>
       </form>
       </form>

+ 4 - 4
apps/app/src/components/Admin/G2GDataTransferStatusIcon.tsx

@@ -21,23 +21,23 @@ const G2GDataTransferStatusIcon = ({ status, className, ...props }: Props): JSX.
 
 
   if (status === G2G_PROGRESS_STATUS.COMPLETED) {
   if (status === G2G_PROGRESS_STATUS.COMPLETED) {
     return (
     return (
-      <i className={`fa fa-check-circle-o fa-fw text-info ${className}`} aria-label="completed" {...props} />
+      <span className={`material-symbols-outlined text-info ${className}`} aria-label="completed" {...props}>check_circle</span>
     );
     );
   }
   }
 
 
   if (status === G2G_PROGRESS_STATUS.ERROR) {
   if (status === G2G_PROGRESS_STATUS.ERROR) {
     return (
     return (
-      <i className={`fa fa-exclamation-circle fa-fw text-danger ${className}`} aria-label="error" {...props} />
+      <span className={`material-symbols-outlined text-danger ${className}`} aria-label="error" {...props}>error</span>
     );
     );
   }
   }
 
 
   if (status === G2G_PROGRESS_STATUS.SKIPPED) {
   if (status === G2G_PROGRESS_STATUS.SKIPPED) {
     return (
     return (
-      <i className={`fa fa-ban fa-fw ${className}`} aria-label="skipped" {...props} />
+      <span className={`material-symbols-outlined ${className}`} aria-label="skipped" {...props}>block</span>
     );
     );
   }
   }
 
 
-  return <i className={`fa fa-circle-o fa-fw ${className}`} aria-label="pending" {...props} />;
+  return <span className={`material-symbols-outlined ${className}`} aria-label="pending" {...props}>circle</span>;
 };
 };
 
 
 export default G2GDataTransferStatusIcon;
 export default G2GDataTransferStatusIcon;

+ 2 - 2
apps/app/src/components/Admin/ImportData/GrowiArchive/ImportForm.jsx

@@ -449,12 +449,12 @@ class ImportForm extends React.Component {
         <form className="row row-cols-lg-auto g-3 align-items-center">
         <form className="row row-cols-lg-auto g-3 align-items-center">
           <div className="col-12">
           <div className="col-12">
             <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={this.checkAll}>
             <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={this.checkAll}>
-              <i className="fa fa-check-square-o"></i> {t('admin:export_management.check_all')}
+              <span className="material-symbols-outlined">check_box</span> {t('admin:export_management.check_all')}
             </button>
             </button>
           </div>
           </div>
           <div className="col-12">
           <div className="col-12">
             <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={this.uncheckAll}>
             <button type="button" className="btn btn-sm btn-outline-secondary me-2" onClick={this.uncheckAll}>
-              <i className="fa fa-square-o"></i> {t('admin:export_management.uncheck_all')}
+              <span className="material-symbols-outlined">check_box_outline_blank</span> {t('admin:export_management.uncheck_all')}
             </button>
             </button>
           </div>
           </div>
         </form>
         </form>

+ 7 - 2
apps/app/src/components/Admin/Notification/NotificationTypeIcon.tsx

@@ -23,8 +23,13 @@ export const NotificationTypeIcon = (props: NotificationTypeIconProps): JSX.Elem
   }
   }
 
 
   const elemId = `notification-${type}-${_id}`;
   const elemId = `notification-${type}-${_id}`;
-  const className = type === 'mail' ? 'icon-fw fa fa-envelope-o' : 'icon-fw fa fa-hashtag';
+  const iconName = type === 'mail' ? 'mail' : 'tag';
   const toolChip = type === 'mail' ? 'Mail' : 'Slack';
   const toolChip = type === 'mail' ? 'Mail' : 'Slack';
 
 
-  return <><i id={elemId} className={className}></i><UncontrolledTooltip target={elemId}>{toolChip}</UncontrolledTooltip></>;
+  return (
+    <>
+      <span id={elemId} className="material-symbols-outlined me-1">{iconName}</span>
+      <UncontrolledTooltip target={elemId}>{toolChip}</UncontrolledTooltip>
+    </>
+  );
 };
 };

+ 1 - 1
apps/app/src/components/Admin/Notification/UserTriggerNotification.jsx

@@ -112,7 +112,7 @@ class UserTriggerNotification extends React.Component {
               <td>
               <td>
                 <div className="input-group notify-to-option" id="slack-input">
                 <div className="input-group notify-to-option" id="slack-input">
                   <div>
                   <div>
-                    <span className="input-group-text"><i className="fa fa-hashtag" /></span>
+                    <span className="input-group-text"><span className="material-symbols-outlined">tag</span></span>
                   </div>
                   </div>
                   <input
                   <input
                     className="form-control"
                     className="form-control"

+ 4 - 5
apps/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx

@@ -87,10 +87,9 @@ class GitHubSecurityManagementContents extends React.Component {
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
             {(siteUrl == null || siteUrl === '') && (
             {(siteUrl == null || siteUrl === '') && (
               <div className="alert alert-danger">
               <div className="alert alert-danger">
-                <i
-                  className="icon-exclamation"
-                  // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<i class="icon-login"></i></a>`, ns: 'commons' }) }}
+                <span className="material-symbols-outlined">error</span>
+                <span // eslint-disable-next-line max-len
+                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`, ns: 'commons' }) }}
                 />
                 />
               </div>
               </div>
             )}
             )}
@@ -172,7 +171,7 @@ class GitHubSecurityManagementContents extends React.Component {
 
 
         <div style={{ minHeight: '300px' }}>
         <div style={{ minHeight: '300px' }}>
           <h4>
           <h4>
-            <i className="icon-question" aria-hidden="true"></i>
+            <span className="material-symbols-outlined" aria-hidden="true">help</span>
             <a href="#collapseHelpForGitHubOauth" data-bs-toggle="collapse"> {t('security_settings.OAuth.how_to.github')}</a>
             <a href="#collapseHelpForGitHubOauth" data-bs-toggle="collapse"> {t('security_settings.OAuth.how_to.github')}</a>
           </h4>
           </h4>
           <ol id="collapseHelpForGitHubOauth" className="collapse">
           <ol id="collapseHelpForGitHubOauth" className="collapse">

+ 4 - 4
apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx

@@ -85,10 +85,10 @@ class GoogleSecurityManagementContents extends React.Component {
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
             {(siteUrl == null || siteUrl === '') && (
             {(siteUrl == null || siteUrl === '') && (
               <div className="alert alert-danger">
               <div className="alert alert-danger">
-                <i
-                  className="icon-exclamation"
-                  // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<i class="icon-login"></i></a>`, ns: 'commons' }) }}
+                <span className="material-symbols-outlined">error</span>
+                <span
+                // eslint-disable-next-line max-len
+                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`, ns: 'commons' }) }}
                 />
                 />
               </div>
               </div>
             )}
             )}

+ 2 - 2
apps/app/src/components/Admin/Security/LocalSecuritySettingContents.jsx

@@ -181,7 +181,7 @@ class LocalSecuritySettingContents extends React.Component {
                   <div className="alert alert-warning p-1 my-1 small d-inline-block">
                   <div className="alert alert-warning p-1 my-1 small d-inline-block">
                     <span>{t('commons:alert.password_reset_please_enable_mailer')}</span>
                     <span>{t('commons:alert.password_reset_please_enable_mailer')}</span>
                     <Link href="/admin/app#mail-settings">
                     <Link href="/admin/app#mail-settings">
-                      <i className="fa fa-link"></i> {t('app_setting.mail_settings')}
+                      <span className="material-symbols-outlined">link</span> {t('app_setting.mail_settings')}
                     </Link>
                     </Link>
                   </div>
                   </div>
                 )}
                 )}
@@ -210,7 +210,7 @@ class LocalSecuritySettingContents extends React.Component {
                   <div className="alert alert-warning p-1 my-1 small d-inline-block">
                   <div className="alert alert-warning p-1 my-1 small d-inline-block">
                     <span>{t('commons:alert.please_enable_mailer')}</span>
                     <span>{t('commons:alert.please_enable_mailer')}</span>
                     <Link href="/admin/app#mail-settings">
                     <Link href="/admin/app#mail-settings">
-                      <i className="fa fa-link"></i> {t('app_setting.mail_settings')}
+                      <span className="material-symbols-outlined">link</span> {t('app_setting.mail_settings')}
                     </Link>
                     </Link>
                   </div>
                   </div>
                 )}
                 )}

+ 6 - 6
apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx

@@ -79,10 +79,10 @@ class OidcSecurityManagementContents extends React.Component {
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
             {(siteUrl == null || siteUrl === '') && (
             {(siteUrl == null || siteUrl === '') && (
               <div className="alert alert-danger">
               <div className="alert alert-danger">
-                <i
-                  className="icon-exclamation"
+                <span className="material-symbols-outlined">error</span>
+                <span
                   // eslint-disable-next-line max-len
                   // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<i class="icon-login"></i></a>`, ns: 'commons' }) }}
+                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`, ns: 'commons' }) }}
                 />
                 />
               </div>
               </div>
             )}
             )}
@@ -375,10 +375,10 @@ class OidcSecurityManagementContents extends React.Component {
                 <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
                 <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
                 {(siteUrl == null || siteUrl === '') && (
                 {(siteUrl == null || siteUrl === '') && (
                   <div className="alert alert-danger">
                   <div className="alert alert-danger">
-                    <i
-                      className="icon-exclamation"
+                    <span className="material-symbols-outlined">error</span>
+                    <span
                       // eslint-disable-next-line max-len
                       // eslint-disable-next-line max-len
-                      dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<i class="icon-login"></i></a>`, ns: 'commons' }) }}
+                      dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`, ns: 'commons' }) }}
                     />
                     />
                   </div>
                   </div>
                 )}
                 )}

+ 8 - 4
apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx

@@ -96,10 +96,10 @@ class SamlSecurityManagementContents extends React.Component {
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'SAML Identity' })}</p>
             <p className="form-text text-muted small">{t('security_settings.desc_of_callback_URL', { AuthName: 'SAML Identity' })}</p>
             {(siteUrl == null || siteUrl === '') && (
             {(siteUrl == null || siteUrl === '') && (
               <div className="alert alert-danger">
               <div className="alert alert-danger">
-                <i
-                  className="icon-exclamation"
+                <span className="material-symbols-outlined">error</span>
+                <span
                   // eslint-disable-next-line max-len
                   // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<i class="icon-login"></i></a>`, ns: 'commons' }) }}
+                  dangerouslySetInnerHTML={{ __html: t('alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('headers.app_settings', { ns: 'commons' })}<span class="material-symbols-outlined">login</span></a>`, ns: 'commons' }) }}
                 />
                 />
               </div>
               </div>
             )}
             )}
@@ -484,7 +484,11 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                               aria-expanded="true"
                               aria-expanded="true"
                               aria-controls="ablchelp"
                               aria-controls="ablchelp"
                             >
                             >
-                              <i className={`icon-fw ${this.state.isHelpOpened ? 'icon-arrow-down' : 'icon-arrow-right'} small`}></i> Show more...
+                              <span
+                                className="material-symbols-outlined me-1"
+                                small
+                              >{this.state.isHelpOpened ? 'expand_more' : 'chevron_right'}
+                              </span> Show more...
                             </button>
                             </button>
                           </h2>
                           </h2>
                           <Collapse isOpen={this.state.isHelpOpened}>
                           <Collapse isOpen={this.state.isHelpOpened}>

+ 5 - 5
apps/app/src/components/Admin/Security/SecurityManagementContents.jsx

@@ -30,19 +30,19 @@ const SecurityManagementContents = () => {
   const navTabMapping = useMemo(() => {
   const navTabMapping = useMemo(() => {
     return {
     return {
       passport_local: {
       passport_local: {
-        Icon: () => <i className="fa fa-users" />,
+        Icon: () => <span className="material-symbols-outlined">groups</span>,
         i18n: 'ID/Pass',
         i18n: 'ID/Pass',
       },
       },
       passport_ldap: {
       passport_ldap: {
-        Icon: () => <i className="fa fa-sitemap" />,
+        Icon: () => <span className="material-symbols-outlined">network_node</span>,
         i18n: 'LDAP',
         i18n: 'LDAP',
       },
       },
       passport_saml: {
       passport_saml: {
-        Icon: () => <i className="fa fa-key" />,
+        Icon: () => <span className="material-symbols-outlined">key</span>,
         i18n: 'SAML',
         i18n: 'SAML',
       },
       },
       passport_oidc: {
       passport_oidc: {
-        Icon: () => <i className="fa fa-key" />,
+        Icon: () => <span className="material-symbols-outlined">key</span>,
         i18n: 'OIDC',
         i18n: 'OIDC',
       },
       },
       passport_google: {
       passport_google: {
@@ -81,7 +81,7 @@ const SecurityManagementContents = () => {
             href="/admin/markdown/#preventXSS"
             href="/admin/markdown/#preventXSS"
             style={{ fontSize: 'large' }}
             style={{ fontSize: 'large' }}
           >
           >
-            <i className="fa-fw icon-login"></i> {t('security_settings.xss_prevent_setting_link')}
+            <span className="material-symbols-outlined me-1">login</span> {t('security_settings.xss_prevent_setting_link')}
           </Link>
           </Link>
         </div>
         </div>
       </div>
       </div>

+ 7 - 7
apps/app/src/components/Admin/Security/SecuritySetting.jsx

@@ -296,14 +296,14 @@ class SecuritySetting extends React.Component {
                     aria-expanded="false"
                     aria-expanded="false"
                     onClick={() => this.setExpantOtherDeleteOptionsState(deletionType, !expantDeleteOptionsState)}
                     onClick={() => this.setExpantOtherDeleteOptionsState(deletionType, !expantDeleteOptionsState)}
                   >
                   >
-                    <i className={`fa fa-fw fa-arrow-right ${expantDeleteOptionsState ? 'fa-rotate-90' : ''}`}></i>
+                    <span className={`material-symbols-outlined me-1 ${expantDeleteOptionsState ? 'fa-rotate-90' : ''}`}>navigate_next</span>
                     { t('security_settings.other_options') }
                     { t('security_settings.other_options') }
                   </button>
                   </button>
                   <Collapse isOpen={expantDeleteOptionsState}>
                   <Collapse isOpen={expantDeleteOptionsState}>
                     <div className="pb-4">
                     <div className="pb-4">
                       <p className="card custom-card">
                       <p className="card custom-card">
                         <span className="text-warning">
                         <span className="text-warning">
-                          <i className="icon-info"></i>
+                          <span className="material-symbols-outlined">info</span>
                           {/* eslint-disable-next-line react/no-danger */}
                           {/* eslint-disable-next-line react/no-danger */}
                           <span dangerouslySetInnerHTML={{ __html: t('security_settings.page_delete_rights_caution') }} />
                           <span dangerouslySetInnerHTML={{ __html: t('security_settings.page_delete_rights_caution') }} />
                         </span>
                         </span>
@@ -368,11 +368,11 @@ class SecuritySetting extends React.Component {
             <tbody>
             <tbody>
               <tr>
               <tr>
                 <th scope="row">{ t('public') }</th>
                 <th scope="row">{ t('public') }</th>
-                <td><i className="icon-fw icon-check text-success"></i>{ t('security_settings.always_displayed') }</td>
+                <td><span className="material-symbols-outlined text-success me-1">check_circle</span>{ t('security_settings.always_displayed') }</td>
               </tr>
               </tr>
               <tr>
               <tr>
                 <th scope="row">{ t('anyone_with_the_link') }</th>
                 <th scope="row">{ t('anyone_with_the_link') }</th>
-                <td><i className="icon-fw icon-ban text-danger"></i>{ t('security_settings.always_hidden') }</td>
+                <td><span className="material-symbols-outlined text-danger me-1">cancel</span>{ t('security_settings.always_hidden') }</td>
               </tr>
               </tr>
               <tr>
               <tr>
                 <th scope="row">{ t('only_me') }</th>
                 <th scope="row">{ t('only_me') }</th>
@@ -444,8 +444,8 @@ class SecuritySetting extends React.Component {
             </div>
             </div>
             {adminGeneralSecurityContainer.isWikiModeForced && (
             {adminGeneralSecurityContainer.isWikiModeForced && (
               <p className="alert alert-warning mt-2 col-6">
               <p className="alert alert-warning mt-2 col-6">
-                <i className="icon-exclamation icon-fw">
-                </i><b>FIXED</b><br />
+                <span className="material-symbols-outlined me-1">error</span>
+                <b>FIXED</b><br />
                 <b
                 <b
                   dangerouslySetInnerHTML={{
                   dangerouslySetInnerHTML={{
                     __html: t('security_settings.Fixed by env var',
                     __html: t('security_settings.Fixed by env var',
@@ -526,7 +526,7 @@ class SecuritySetting extends React.Component {
             <p className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('security_settings.max_age_desc') }} />
             <p className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('security_settings.max_age_desc') }} />
             <p className="card custom-card">
             <p className="card custom-card">
               <span className="text-warning">
               <span className="text-warning">
-                <i className="icon-info"></i> {t('security_settings.max_age_caution')}
+                <span className="material-symbols-outlined">info</span> {t('security_settings.max_age_caution')}
               </span>
               </span>
             </p>
             </p>
           </div>
           </div>

+ 11 - 5
apps/app/src/components/Admin/SlackIntegration/Bridge.jsx

@@ -15,14 +15,14 @@ const ProxyCircle = () => (
 
 
 const BridgeCore = (props) => {
 const BridgeCore = (props) => {
   const {
   const {
-    description, iconClass, hrClass, withProxy,
+    description, iconClass, iconName, hrClass, withProxy,
   } = props;
   } = props;
 
 
   return (
   return (
     <>
     <>
       <div id="grw-bridge-container" className={`grw-bridge-container ${withProxy ? 'with-proxy' : ''}`}>
       <div id="grw-bridge-container" className={`grw-bridge-container ${withProxy ? 'with-proxy' : ''}`}>
         <p className={`${withProxy ? 'mt-0' : 'mt-2'}`}>
         <p className={`${withProxy ? 'mt-0' : 'mt-2'}`}>
-          <i className={iconClass} />
+          <span className={iconClass}>{iconName}</span>
           <small
           <small
             className="ms-2 d-none d-lg-inline"
             className="ms-2 d-none d-lg-inline"
             // eslint-disable-next-line react/no-danger
             // eslint-disable-next-line react/no-danger
@@ -47,6 +47,7 @@ const BridgeCore = (props) => {
 BridgeCore.propTypes = {
 BridgeCore.propTypes = {
   description: PropTypes.string.isRequired,
   description: PropTypes.string.isRequired,
   iconClass: PropTypes.string.isRequired,
   iconClass: PropTypes.string.isRequired,
+  iconName: PropTypes.string.isRequired,
   hrClass: PropTypes.string.isRequired,
   hrClass: PropTypes.string.isRequired,
   withProxy: PropTypes.bool,
   withProxy: PropTypes.bool,
 };
 };
@@ -58,24 +59,28 @@ const Bridge = (props) => {
 
 
   let description;
   let description;
   let iconClass;
   let iconClass;
+  let iconName;
   let hrClass;
   let hrClass;
 
 
   // empty or all failed
   // empty or all failed
   if (totalCount === 0 || errorCount === totalCount) {
   if (totalCount === 0 || errorCount === totalCount) {
     description = t('admin:slack_integration.integration_sentence.integration_is_not_complete');
     description = t('admin:slack_integration.integration_sentence.integration_is_not_complete');
-    iconClass = 'icon-info text-danger';
+    iconClass = 'material-symbols-outlined text-danger';
+    iconName = 'info';
     hrClass = 'border-danger admin-border-failed';
     hrClass = 'border-danger admin-border-failed';
   }
   }
   // all green
   // all green
   else if (errorCount === 0) {
   else if (errorCount === 0) {
     description = t('admin:slack_integration.integration_sentence.integration_successful');
     description = t('admin:slack_integration.integration_sentence.integration_successful');
-    iconClass = 'fa fa-check text-success';
+    iconClass = 'material-symbols-outlined text-success';
+    iconName = 'check';
     hrClass = 'border-success admin-border-success';
     hrClass = 'border-success admin-border-success';
   }
   }
   // some of them failed
   // some of them failed
   else {
   else {
     description = t('admin:slack_integration.integration_sentence.integration_some_ws_is_not_complete');
     description = t('admin:slack_integration.integration_sentence.integration_some_ws_is_not_complete');
-    iconClass = 'fa fa-check text-warning';
+    iconClass = 'material-symbols-outlined text-warning';
+    iconName = 'check';
     hrClass = 'border-warning admin-border-failed';
     hrClass = 'border-warning admin-border-failed';
   }
   }
 
 
@@ -83,6 +88,7 @@ const Bridge = (props) => {
     <BridgeCore
     <BridgeCore
       description={description}
       description={description}
       iconClass={iconClass}
       iconClass={iconClass}
+      iconName={iconName}
       hrClass={hrClass}
       hrClass={hrClass}
       withProxy={withProxy}
       withProxy={withProxy}
     />
     />

+ 4 - 4
apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx

@@ -115,7 +115,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
       <Accordion
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.REGISTER_SLACK_CONFIGURATION)}
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.REGISTER_SLACK_CONFIGURATION)}
         // eslint-disable-next-line max-len
         // eslint-disable-next-line max-len
-        title={<><span className="me-3">3</span>{t('admin:slack_integration.accordion.register_secret_and_token')}{isEnterdSecretAndToken && <i className="ms-3 text-success fa fa-check"></i>}</>}
+        title={<><span className="me-3">3</span>{t('admin:slack_integration.accordion.register_secret_and_token')}{isEnterdSecretAndToken && <span className="material-symbols-outlined ms-3 text-success">check</span>}</>}
       >
       >
         <CustomBotWithoutProxySecretTokenSection
         <CustomBotWithoutProxySecretTokenSection
           onUpdatedSecretToken={props.onUpdatedSecretToken}
           onUpdatedSecretToken={props.onUpdatedSecretToken}
@@ -138,17 +138,17 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
       <Accordion
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CONNECTION_TEST)}
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CONNECTION_TEST)}
         // eslint-disable-next-line max-len
         // eslint-disable-next-line max-len
-        title={<><span className="me-3">5</span>{t('admin:slack_integration.accordion.test_connection')}{isLatestConnectionSuccess && <i className="ms-3 text-success fa fa-check"></i>}</>}
+        title={<><span className="me-3">5</span>{t('admin:slack_integration.accordion.test_connection')}{isLatestConnectionSuccess && <span className="material-symbols-outlined ms-3 text-success">check</span>}</>}
       >
       >
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <p className="text-center text-warning">
         <p className="text-center text-warning">
-          <i className="icon-info">{t('admin:slack_integration.accordion.test_connection_only_public_channel')}</i>
+          <span className="material-symbols-outlined">info</span>{t('admin:slack_integration.accordion.test_connection_only_public_channel')}
         </p>
         </p>
         <div className="d-flex justify-content-center">
         <div className="d-flex justify-content-center">
           <form className="align-items-center" onSubmit={e => submitForm(e)}>
           <form className="align-items-center" onSubmit={e => submitForm(e)}>
             <div className="input-group col-8">
             <div className="input-group col-8">
               <div>
               <div>
-                <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+                <span className="input-group-text" id="slack-channel-addon"><span className="material-symbols-outlined">tag</span></span>
               </div>
               </div>
               <input
               <input
                 className="form-control"
                 className="form-control"

+ 1 - 1
apps/app/src/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -211,7 +211,7 @@ const SlackIntegration = () => {
         <h2 className="admin-setting-header mb-4">
         <h2 className="admin-setting-header mb-4">
           {t('admin:slack_integration.selecting_bot_types.slack_bot')}
           {t('admin:slack_integration.selecting_bot_types.slack_bot')}
           <a className="ms-2 btn-link small" href={t('admin:slack_integration.docs_url.slack_integration')} target="_blank" rel="noopener noreferrer">
           <a className="ms-2 btn-link small" href={t('admin:slack_integration.docs_url.slack_integration')} target="_blank" rel="noopener noreferrer">
-            <i className="icon icon-question ms-1" aria-hidden="true"></i>
+            <span className="material-symbols-outlined ms-1" aria-hidden="true">help</span>
           </a>
           </a>
         </h2>
         </h2>
 
 

+ 3 - 3
apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -240,13 +240,13 @@ const TestProcess = ({
     <>
     <>
       <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
       <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
       <p className="text-center text-warning">
       <p className="text-center text-warning">
-        <i className="icon-info">{t('admin:slack_integration.accordion.test_connection_only_public_channel')}</i>
+        <span className="material-symbols-outlined me-1">info</span>{t('admin:slack_integration.accordion.test_connection_only_public_channel')}
       </p>
       </p>
       <div className="d-flex justify-content-center">
       <div className="d-flex justify-content-center">
         <form className="justify-content-center" onSubmit={e => submitForm(e)}>
         <form className="justify-content-center" onSubmit={e => submitForm(e)}>
           <div className="input-group col-8">
           <div className="input-group col-8">
             <div>
             <div>
-              <span className="input-group-text" id="slack-channel-addon"><i className="fa fa-hashtag" /></span>
+              <span className="input-group-text" id="slack-channel-addon"><span className="material-symbols-outlined">tag</span></span>
             </div>
             </div>
             <input
             <input
               className="form-control"
               className="form-control"
@@ -391,7 +391,7 @@ const WithProxyAccordions = (props) => {
               <>
               <>
                 <span className="me-3">{key}</span>
                 <span className="me-3">{key}</span>
                 {t(`admin:slack_integration.accordion.${value.title}`)}
                 {t(`admin:slack_integration.accordion.${value.title}`)}
-                {value.title === 'test_connection' && isLatestConnectionSuccess && <i className="ms-3 text-success fa fa-check"></i>}
+                {value.title === 'test_connection' && isLatestConnectionSuccess && <span className="material-symbols-outlined ms-3 text-success">check</span>}
               </>
               </>
             )}
             )}
             key={key}
             key={key}

+ 22 - 23
apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx

@@ -1,6 +1,5 @@
-import React, {
-  FC, useCallback, useState, useMemo,
-} from 'react';
+import type { FC } from 'react';
+import React, { useCallback, useState, useMemo } from 'react';
 
 
 import type { IUserGroupHasId } from '@growi/core';
 import type { IUserGroupHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
@@ -8,6 +7,8 @@ import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
+import { PageActionOnGroupDelete } from '~/interfaces/user-group';
+
 
 
 /**
 /**
  * Delete User Group Select component
  * Delete User Group Select component
@@ -19,26 +20,19 @@ import {
 type Props = {
 type Props = {
   userGroups: IUserGroupHasId[],
   userGroups: IUserGroupHasId[],
   deleteUserGroup?: IUserGroupHasId,
   deleteUserGroup?: IUserGroupHasId,
-  onDelete?: (deleteGroupId: string, actionName: string, transferToUserGroupId: string) => Promise<void> | void,
+  onDelete?: (deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroupId: string) => Promise<void> | void,
   isShow: boolean,
   isShow: boolean,
   onHide?: () => Promise<void> | void,
   onHide?: () => Promise<void> | void,
 };
 };
 
 
 type AvailableOption = {
 type AvailableOption = {
   id: number,
   id: number,
-  actionForPages: string,
+  actionForPages: PageActionOnGroupDelete,
   iconClass: string,
   iconClass: string,
   styleClass: string,
   styleClass: string,
   label: string,
   label: string,
 };
 };
 
 
-// actionName master constants
-const actionForPages = {
-  public: 'public',
-  delete: 'delete',
-  transfer: 'transfer',
-};
-
 export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
 export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
@@ -51,21 +45,21 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
     return [
     return [
       {
       {
         id: 1,
         id: 1,
-        actionForPages: actionForPages.public,
+        actionForPages: PageActionOnGroupDelete.publicize,
         iconClass: 'icon-people',
         iconClass: 'icon-people',
         styleClass: '',
         styleClass: '',
         label: t('admin:user_group_management.delete_modal.publish_pages'),
         label: t('admin:user_group_management.delete_modal.publish_pages'),
       },
       },
       {
       {
         id: 2,
         id: 2,
-        actionForPages: actionForPages.delete,
+        actionForPages: PageActionOnGroupDelete.delete,
         iconClass: 'icon-trash',
         iconClass: 'icon-trash',
         styleClass: 'text-danger',
         styleClass: 'text-danger',
         label: t('admin:user_group_management.delete_modal.delete_pages'),
         label: t('admin:user_group_management.delete_modal.delete_pages'),
       },
       },
       {
       {
         id: 3,
         id: 3,
-        actionForPages: actionForPages.transfer,
+        actionForPages: PageActionOnGroupDelete.transfer,
         iconClass: 'icon-options',
         iconClass: 'icon-options',
         styleClass: '',
         styleClass: '',
         label: t('admin:user_group_management.delete_modal.transfer_pages'),
         label: t('admin:user_group_management.delete_modal.transfer_pages'),
@@ -76,14 +70,14 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   /*
   /*
    * State
    * State
    */
    */
-  const [actionName, setActionName] = useState<string>('');
+  const [actionName, setActionName] = useState<PageActionOnGroupDelete | null>(null);
   const [transferToUserGroupId, setTransferToUserGroupId] = useState<string>('');
   const [transferToUserGroupId, setTransferToUserGroupId] = useState<string>('');
 
 
   /*
   /*
    * Function
    * Function
    */
    */
   const resetStates = useCallback(() => {
   const resetStates = useCallback(() => {
-    setActionName('');
+    setActionName(null);
     setTransferToUserGroupId('');
     setTransferToUserGroupId('');
   }, []);
   }, []);
 
 
@@ -107,7 +101,7 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   }, []);
   }, []);
 
 
   const handleSubmit = useCallback((e) => {
   const handleSubmit = useCallback((e) => {
-    if (onDelete == null || deleteUserGroup == null) {
+    if (onDelete == null || deleteUserGroup == null || actionName == null) {
       return;
       return;
     }
     }
 
 
@@ -130,7 +124,7 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         name="actionName"
         name="actionName"
         className="form-control"
         className="form-control"
         placeholder="select"
         placeholder="select"
-        value={actionName}
+        value={actionName ?? ''}
         onChange={handleActionChange}
         onChange={handleActionChange}
       >
       >
         <option value="" disabled>{t('admin:user_group_management.delete_modal.dropdown_desc')}</option>
         <option value="" disabled>{t('admin:user_group_management.delete_modal.dropdown_desc')}</option>
@@ -158,7 +152,7 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
     return (
     return (
       <select
       <select
         name="transferToUserGroupId"
         name="transferToUserGroupId"
-        className={`form-control ${actionName === actionForPages.transfer ? '' : 'd-none'}`}
+        className={`form-control ${actionName === PageActionOnGroupDelete.transfer ? '' : 'd-none'}`}
         value={transferToUserGroupId}
         value={transferToUserGroupId}
         onChange={handleGroupChange}
         onChange={handleGroupChange}
       >
       >
@@ -171,10 +165,10 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   const validateForm = useCallback(() => {
   const validateForm = useCallback(() => {
     let isValid = true;
     let isValid = true;
 
 
-    if (actionName === '') {
+    if (actionName === null) {
       isValid = false;
       isValid = false;
     }
     }
-    else if (actionName === actionForPages.transfer) {
+    else if (actionName === PageActionOnGroupDelete.transfer) {
       isValid = transferToUserGroupId !== '';
       isValid = transferToUserGroupId !== '';
     }
     }
 
 
@@ -196,7 +190,7 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>
         <form className="d-flex justify-content-between w-100" onSubmit={handleSubmit}>
         <form className="d-flex justify-content-between w-100" onSubmit={handleSubmit}>
-          <div className="d-flex mb-0">
+          <div className="d-flex mb-0 me-3">
             {renderPageActionSelector()}
             {renderPageActionSelector()}
             {renderGroupSelector()}
             {renderGroupSelector()}
           </div>
           </div>
@@ -204,6 +198,11 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
             <span className="material-symbols-outlined">delete_forever</span> {t('Delete')}
             <span className="material-symbols-outlined">delete_forever</span> {t('Delete')}
           </button>
           </button>
         </form>
         </form>
+        {actionName === PageActionOnGroupDelete.publicize && (
+          <div className="form-text text-muted">
+            <small>{t('admin:user_group_management.delete_modal.option_explanation')}</small>
+          </div>
+        )}
       </ModalFooter>
       </ModalFooter>
     </Modal>
     </Modal>
   );
   );

+ 2 - 3
apps/app/src/components/Admin/UserGroup/UserGroupModal.tsx

@@ -1,6 +1,5 @@
-import React, {
-  FC, useState, useEffect, useCallback,
-} from 'react';
+import type { FC } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
 
 
 import type { Ref, IUserGroup, IUserGroupHasId } from '@growi/core';
 import type { Ref, IUserGroup, IUserGroupHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 4 - 2
apps/app/src/components/Admin/UserGroup/UserGroupPage.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useState, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useState, useCallback } from 'react';
 
 
 import type { IUserGroup, IUserGroupHasId } from '@growi/core';
 import type { IUserGroup, IUserGroupHasId } from '@growi/core';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
@@ -9,6 +10,7 @@ import { toastSuccess, toastError } from '~/client/util/toastr';
 import { ExternalGroupManagement } from '~/features/external-user-group/client/components/ExternalUserGroup/ExternalUserGroupManagement';
 import { ExternalGroupManagement } from '~/features/external-user-group/client/components/ExternalUserGroup/ExternalUserGroupManagement';
 import { useIsAclEnabled } from '~/stores/context';
 import { useIsAclEnabled } from '~/stores/context';
 import { useSWRxUserGroupList, useSWRxChildUserGroupList, useSWRxUserGroupRelationList } from '~/stores/user-group';
 import { useSWRxUserGroupList, useSWRxChildUserGroupList, useSWRxUserGroupRelationList } from '~/stores/user-group';
+import { PageActionOnGroupDelete } from '~/interfaces/user-group';
 
 
 
 
 const UserGroupDeleteModal = dynamic(() => import('./UserGroupDeleteModal').then(mod => mod.UserGroupDeleteModal), { ssr: false });
 const UserGroupDeleteModal = dynamic(() => import('./UserGroupDeleteModal').then(mod => mod.UserGroupDeleteModal), { ssr: false });
@@ -126,7 +128,7 @@ export const UserGroupPage: FC = () => {
     }
     }
   }, [t, mutateUserGroups, hideUpdateModal]);
   }, [t, mutateUserGroups, hideUpdateModal]);
 
 
-  const deleteUserGroupById = useCallback(async(deleteGroupId: string, actionName: string, transferToUserGroupId: string) => {
+  const deleteUserGroupById = useCallback(async(deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroupId: string) => {
     try {
     try {
       await apiv3Delete(`/user-groups/${deleteGroupId}`, {
       await apiv3Delete(`/user-groups/${deleteGroupId}`, {
         actionName,
         actionName,

+ 6 - 7
apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx

@@ -1,13 +1,12 @@
-import React, {
-  FC, useState, useEffect,
-} from 'react';
+import type { FC } from 'react';
+import React, { useState, useEffect } from 'react';
 
 
 import type { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '@growi/core';
 import type { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '@growi/core';
 import dateFnsFormat from 'date-fns/format';
 import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import Link from 'next/link';
 
 
-import { IExternalUserGroupHasId } from '~/features/external-user-group/interfaces/external-user-group';
+import type { IExternalUserGroupHasId } from '~/features/external-user-group/interfaces/external-user-group';
 
 
 
 
 type Props = {
 type Props = {
@@ -206,16 +205,16 @@ export const UserGroupTable: FC<Props> = ({
                           className="btn btn-outline-secondary btn-sm dropdown-toggle"
                           className="btn btn-outline-secondary btn-sm dropdown-toggle"
                           data-bs-toggle="dropdown"
                           data-bs-toggle="dropdown"
                         >
                         >
-                          <i className="icon-settings"></i>
+                          <span className="material-symbols-outlined fs-5">settings</span>
                         </button>
                         </button>
                         <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${group._id}`}>
                         <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${group._id}`}>
                           <button className="dropdown-item" type="button" role="button" onClick={onClickEdit} data-user-group-id={group._id}>
                           <button className="dropdown-item" type="button" role="button" onClick={onClickEdit} data-user-group-id={group._id}>
-                            <i className="icon-fw icon-note"></i> {t('Edit')}
+                            <span className="material-symbols-outlined me-1">edit_square</span> {t('Edit')}
                           </button>
                           </button>
                           {onRemove != null
                           {onRemove != null
                           && (
                           && (
                             <button className="dropdown-item" type="button" role="button" onClick={onClickRemove} data-user-group-id={group._id}>
                             <button className="dropdown-item" type="button" role="button" onClick={onClickRemove} data-user-group-id={group._id}>
-                              <i className="icon-fw fa fa-chain-broken"></i> {t('admin:user_group_management.remove_child_group')}
+                              <span className="material-symbols-outlined me-1">group_remove</span> {t('admin:user_group_management.remove_child_group')}
                             </button>
                             </button>
                           )}
                           )}
                           <button className="dropdown-item" type="button" role="button" onClick={onClickDelete} data-user-group-id={group._id}>
                           <button className="dropdown-item" type="button" role="button" onClick={onClickDelete} data-user-group-id={group._id}>

+ 4 - 3
apps/app/src/components/Admin/UserGroupDetail/UpdateParentConfirmModal.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useState } from 'react';
+import type { FC } from 'react';
+import React, { useState } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
@@ -27,7 +28,7 @@ export const UpdateParentConfirmModal: FC = () => {
   return (
   return (
     <Modal className="modal-md" isOpen={isOpened} toggle={closeModal}>
     <Modal className="modal-md" isOpen={isOpened} toggle={closeModal}>
       <ModalHeader tag="h4" toggle={closeModal} className="bg-warning text-light">
       <ModalHeader tag="h4" toggle={closeModal} className="bg-warning text-light">
-        <i className="icon icon-warning"></i> {t('admin:user_group_management.update_parent_confirm_modal.header')}
+        <span className="material-symbols-outlined">warning</span> {t('admin:user_group_management.update_parent_confirm_modal.header')}
       </ModalHeader>
       </ModalHeader>
       {
       {
         targetGroup != null && updateData != null ? (
         targetGroup != null && updateData != null ? (
@@ -39,7 +40,7 @@ export const UpdateParentConfirmModal: FC = () => {
                 {t('admin:user_group_management.update_parent_confirm_modal.caution_change_parent', { groupName: targetGroup.name })}
                 {t('admin:user_group_management.update_parent_confirm_modal.caution_change_parent', { groupName: targetGroup.name })}
               </div>
               </div>
               <div className="text-danger mb-3">
               <div className="text-danger mb-3">
-                <i className="icon-exclamation"></i>
+                <span className="material-symbols-outlined">error</span>
                 {t('admin:user_group_management.update_parent_confirm_modal.danger_message')}
                 {t('admin:user_group_management.update_parent_confirm_modal.danger_message')}
               </div>
               </div>
 
 

+ 4 - 3
apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx

@@ -13,8 +13,9 @@ import {
   apiv3Get, apiv3Put, apiv3Delete, apiv3Post,
   apiv3Get, apiv3Put, apiv3Delete, apiv3Post,
 } from '~/client/util/apiv3-client';
 } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { IExternalUserGroupHasId } from '~/features/external-user-group/interfaces/external-user-group';
-import { SearchTypes, SearchType } from '~/interfaces/user-group';
+import type { IExternalUserGroupHasId } from '~/features/external-user-group/interfaces/external-user-group';
+import type { PageActionOnGroupDelete, SearchType } from '~/interfaces/user-group';
+import { SearchTypes } from '~/interfaces/user-group';
 import Xss from '~/services/xss';
 import Xss from '~/services/xss';
 import { useIsAclEnabled } from '~/stores/context';
 import { useIsAclEnabled } from '~/stores/context';
 import { useUpdateUserGroupConfirmModal } from '~/stores/modal';
 import { useUpdateUserGroupConfirmModal } from '~/stores/modal';
@@ -296,7 +297,7 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
     setDeleteModalShown(false);
     setDeleteModalShown(false);
   }, [setSelectedUserGroup, setDeleteModalShown]);
   }, [setSelectedUserGroup, setDeleteModalShown]);
 
 
-  const deleteChildUserGroupById = useCallback(async(deleteGroupId: string, actionName: string, transferToUserGroupId: string) => {
+  const deleteChildUserGroupById = useCallback(async(deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroupId: string) => {
     const url = isExternalGroup ? `/external-user-groups/${deleteGroupId}` : `/user-groups/${deleteGroupId}`;
     const url = isExternalGroup ? `/external-user-groups/${deleteGroupId}` : `/user-groups/${deleteGroupId}`;
     try {
     try {
       const res = await apiv3Delete(url, {
       const res = await apiv3Delete(url, {

+ 0 - 169
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -1,169 +0,0 @@
-import React from 'react';
-
-import { UserPicture } from '@growi/ui/dist/components';
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-import { AsyncTypeahead } from 'react-bootstrap-typeahead';
-import { debounce } from 'throttle-debounce';
-
-import { toastSuccess, toastError } from '~/client/util/toastr';
-import Xss from '~/services/xss';
-
-class UserGroupUserFormByInput extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      keyword: '',
-      inputUser: '',
-      applicableUsers: [],
-      isLoading: false,
-      searchError: null,
-    };
-
-    this.xss = new Xss();
-
-    this.addUserBySubmit = this.addUserBySubmit.bind(this);
-    this.validateForm = this.validateForm.bind(this);
-    this.handleChange = this.handleChange.bind(this);
-    this.handleSearch = this.handleSearch.bind(this);
-    this.onKeyDown = this.onKeyDown.bind(this);
-    this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
-
-    this.searhApplicableUsersDebounce = debounce(1000, this.searhApplicableUsers);
-  }
-
-  async addUserBySubmit() {
-    const { userGroup, onClickAddUserBtn } = this.props;
-
-    if (this.state.inputUser.length === 0) { return }
-    const userName = this.state.inputUser[0].username;
-
-    try {
-      await onClickAddUserBtn(userName);
-      toastSuccess(`Added "${this.xss.process(userName)}" to "${this.xss.process(userGroup.name)}"`);
-      this.setState({ inputUser: '' });
-    }
-    catch (err) {
-      toastError(new Error(`Unable to add "${this.xss.process(userName)}" to "${this.xss.process(userGroup.name)}"`));
-    }
-
-
-  }
-
-  validateForm() {
-    return this.state.inputUser !== '';
-  }
-
-  async searhApplicableUsers() {
-    const { onSearchApplicableUsers } = this.props;
-
-    try {
-      const users = await onSearchApplicableUsers(this.state.keyword);
-      this.setState({ applicableUsers: users, isLoading: false });
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-  /**
-   * Reflect when forecast is clicked
-   * @param {object} inputUser
-   */
-  handleChange(inputUser) {
-    this.setState({ inputUser });
-  }
-
-  handleSearch(keyword) {
-    if (keyword === '') {
-      return;
-    }
-
-    this.setState({ keyword, isLoading: true });
-    this.searhApplicableUsersDebounce();
-  }
-
-  onKeyDown(event) {
-    // 13 is Enter key
-    if (event.keyCode === 13) {
-      this.addUserBySubmit();
-    }
-  }
-
-  renderMenuItemChildren(option) {
-    const { isAlsoNameSearched, isAlsoMailSearched } = this.props;
-    const user = option;
-    return (
-      <>
-        <UserPicture user={user} size="sm" noLink noTooltip />
-        <strong className="ms-2">{user.username}</strong>
-        {isAlsoNameSearched && <span className="ms-2">{user.name}</span>}
-        {isAlsoMailSearched && <span className="ms-2">{user.email}</span>}
-      </>
-    );
-  }
-
-  getEmptyLabel() {
-    return (this.state.searchError !== null) && 'Error on searching.';
-  }
-
-  render() {
-    const { t } = this.props;
-
-    const inputProps = { autoComplete: 'off' };
-
-    return (
-      <div className="row">
-        <div className="col-8 pe-0">
-          <AsyncTypeahead
-            {...this.props}
-            id="name-typeahead-asynctypeahead"
-            ref={(c) => { this.typeahead = c }}
-            inputProps={inputProps}
-            isLoading={this.state.isLoading}
-            labelKey={user => `${user.username} ${user.name} ${user.email}`}
-            minLength={0}
-            options={this.state.applicableUsers} // Search result
-            searchText={(this.state.isLoading ? 'Searching...' : this.getEmptyLabel())}
-            renderMenuItemChildren={this.renderMenuItemChildren}
-            align="left"
-            onChange={this.handleChange}
-            onSearch={this.handleSearch}
-            onKeyDown={this.onKeyDown}
-            caseSensitive={false}
-            clearButton
-          />
-        </div>
-        <div className="col-2 ps-0">
-          <button
-            type="button"
-            className="btn btn-success"
-            disabled={!this.validateForm()}
-            onClick={this.addUserBySubmit}
-          >
-            {t('add')}
-          </button>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-UserGroupUserFormByInput.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  isAlsoMailSearched: PropTypes.bool.isRequired,
-  isAlsoNameSearched: PropTypes.bool.isRequired,
-  onClickAddUserBtn: PropTypes.func,
-  onSearchApplicableUsers: PropTypes.func,
-  userGroup: PropTypes.object,
-};
-
-const UserGroupUserFormByInputWrapperFC = (props) => {
-  const { t } = useTranslation();
-  return <UserGroupUserFormByInput t={t} {...props} />;
-};
-
-export default UserGroupUserFormByInputWrapperFC;

+ 122 - 0
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.tsx

@@ -0,0 +1,122 @@
+import type { FC, KeyboardEvent } from 'react';
+import React, { useState, useRef } from 'react';
+
+import type { IUserGroupHasId, IUserHasId } from '@growi/core';
+import { UserPicture } from '@growi/ui/dist/components';
+import { useTranslation } from 'next-i18next';
+import { AsyncTypeahead } from 'react-bootstrap-typeahead';
+
+import { toastSuccess, toastError } from '~/client/util/toastr';
+import type { SearchType } from '~/interfaces/user-group';
+import Xss from '~/services/xss';
+
+type Props = {
+  userGroup: IUserGroupHasId,
+  onClickAddUserBtn: (username: string) => Promise<void>,
+  onSearchApplicableUsers: (searchWord: string) => Promise<IUserHasId[]>,
+  isAlsoNameSearched: boolean,
+  isAlsoMailSearched: boolean,
+  searchType: SearchType,
+}
+
+export const UserGroupUserFormByInput: FC<Props> = (props) => {
+  const {
+    userGroup, onClickAddUserBtn, onSearchApplicableUsers, isAlsoNameSearched, isAlsoMailSearched, searchType,
+  } = props;
+
+  const { t } = useTranslation();
+  const typeaheadRef = useRef(null);
+  const [inputUser, setInputUser] = useState<IUserHasId[]>([]);
+  const [applicableUsers, setApplicableUsers] = useState<IUserHasId[]>([]);
+  const [isLoading, setIsLoading] = useState(false);
+  const [isSearchError, setIsSearchError] = useState(false);
+
+  const xss = new Xss();
+
+  const addUserBySubmit = async() => {
+    if (inputUser.length === 0) { return }
+    const userName = inputUser[0].username;
+
+    try {
+      await onClickAddUserBtn(userName);
+      toastSuccess(`Added "${xss.process(userName)}" to "${xss.process(userGroup.name)}"`);
+      setInputUser([]);
+    }
+    catch (err) {
+      toastError(new Error(`Unable to add "${xss.process(userName)}" to "${xss.process(userGroup.name)}"`));
+    }
+  };
+
+  const searchApplicableUsers = async(keyword: string) => {
+    try {
+      const users = await onSearchApplicableUsers(keyword);
+      setApplicableUsers(users);
+      setIsLoading(false);
+    }
+    catch (err) {
+      setIsSearchError(true);
+      toastError(err);
+    }
+  };
+
+  const handleChange = (inputUser: IUserHasId[]) => {
+    setInputUser(inputUser);
+  };
+
+  const handleSearch = async(keyword: string) => {
+    setIsLoading(true);
+    await searchApplicableUsers(keyword);
+  };
+
+  const onKeyDown = (event: KeyboardEvent) => {
+    if (event.key === 'Enter') {
+      addUserBySubmit();
+    }
+  };
+
+  const renderMenuItemChildren = (option: IUserHasId) => {
+    const user = option;
+
+    return (
+      <>
+        <UserPicture user={user} size="sm" noLink noTooltip />
+        <strong className="ms-2">{user.username}</strong>
+        {isAlsoNameSearched && <span className="ms-2">{user.name}</span>}
+        {isAlsoMailSearched && <span className="ms-2">{user.email}</span>}
+      </>
+    );
+  };
+
+  return (
+    <div className="row">
+      <div className="col-8 pe-0">
+        <AsyncTypeahead
+          key={`${searchType}-${isAlsoNameSearched}-${isAlsoMailSearched}`} // The searched keywords are not re-searched, so re-rendered by key.
+          id="name-typeahead-asynctypeahead"
+          inputProps={{ autoComplete: 'off' }}
+          isLoading={isLoading}
+          labelKey={(user: IUserHasId) => `${user.username} ${user.name} ${user.email}`}
+          options={applicableUsers} // Search result
+          onSearch={handleSearch}
+          onChange={handleChange}
+          onKeyDown={onKeyDown}
+          minLength={1}
+          searchText={isLoading ? 'Searching...' : (isSearchError && 'Error on searching.')}
+          renderMenuItemChildren={renderMenuItemChildren}
+          align="left"
+          clearButton
+        />
+      </div>
+      <div className="col-2 ps-0">
+        <button
+          type="button"
+          className="btn btn-success"
+          disabled={inputUser.length === 0}
+          onClick={addUserBySubmit}
+        >
+          {t('add')}
+        </button>
+      </div>
+    </div>
+  );
+};

+ 6 - 5
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserModal.tsx

@@ -1,16 +1,17 @@
 import React from 'react';
 import React from 'react';
 
 
-import type { IUserGroupHasId } from '@growi/core';
+import type { IUserGroupHasId, IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
   Modal, ModalHeader, ModalBody,
   Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-import { SearchTypes, SearchType } from '~/interfaces/user-group';
+import type { SearchType } from '~/interfaces/user-group';
+import { SearchTypes } from '~/interfaces/user-group';
 
 
 import CheckBoxForSerchUserOption from './CheckBoxForSerchUserOption';
 import CheckBoxForSerchUserOption from './CheckBoxForSerchUserOption';
 import RadioButtonForSerchUserOption from './RadioButtonForSerchUserOption';
 import RadioButtonForSerchUserOption from './RadioButtonForSerchUserOption';
-import UserGroupUserFormByInput from './UserGroupUserFormByInput';
+import { UserGroupUserFormByInput } from './UserGroupUserFormByInput';
 
 
 type Props = {
 type Props = {
   isOpen: boolean,
   isOpen: boolean,
@@ -19,7 +20,7 @@ type Props = {
   isAlsoMailSearched: boolean,
   isAlsoMailSearched: boolean,
   isAlsoNameSearched: boolean,
   isAlsoNameSearched: boolean,
   onClickAddUserBtn: (username: string) => Promise<void>,
   onClickAddUserBtn: (username: string) => Promise<void>,
-  onSearchApplicableUsers: (searchWord: string) => Promise<void>,
+  onSearchApplicableUsers: (searchWord: string) => Promise<IUserHasId[]>,
   onSwitchSearchType: (searchType: SearchType) => void
   onSwitchSearchType: (searchType: SearchType) => void
   onClose: () => void,
   onClose: () => void,
   onToggleIsAlsoMailSearched: () => void,
   onToggleIsAlsoMailSearched: () => void,
@@ -54,9 +55,9 @@ const UserGroupUserModal = (props: Props): JSX.Element => {
             userGroup={userGroup}
             userGroup={userGroup}
             onClickAddUserBtn={onClickAddUserBtn}
             onClickAddUserBtn={onClickAddUserBtn}
             onSearchApplicableUsers={onSearchApplicableUsers}
             onSearchApplicableUsers={onSearchApplicableUsers}
-            onClose={onClose}
             isAlsoNameSearched={isAlsoNameSearched}
             isAlsoNameSearched={isAlsoNameSearched}
             isAlsoMailSearched={isAlsoMailSearched}
             isAlsoMailSearched={isAlsoMailSearched}
+            searchType={searchType}
           />
           />
         </div>
         </div>
         <h2 className="border-bottom">{t('admin:user_group_management.add_modal.search_option')}</h2>
         <h2 className="border-bottom">{t('admin:user_group_management.add_modal.search_option')}</h2>

+ 3 - 3
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx

@@ -52,9 +52,9 @@ export const UserGroupUserTable = (props: Props): JSX.Element => {
                       type="button"
                       type="button"
                       id={`admin-group-menu-button-${relatedUser._id}`}
                       id={`admin-group-menu-button-${relatedUser._id}`}
                       className="btn btn-outline-secondary btn-sm dropdown-toggle"
                       className="btn btn-outline-secondary btn-sm dropdown-toggle"
-                      data-toggle="dropdown"
+                      data-bs-toggle="dropdown"
                     >
                     >
-                      <i className="icon-settings"></i>
+                      <span className="material-symbols-outlined fs-5">settings</span>
                     </button>
                     </button>
                     <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${relatedUser._id}`}>
                     <div className="dropdown-menu" role="menu" aria-labelledby={`admin-group-menu-button-${relatedUser._id}`}>
                       <button
                       <button
@@ -62,7 +62,7 @@ export const UserGroupUserTable = (props: Props): JSX.Element => {
                         type="button"
                         type="button"
                         onClick={() => props.onClickRemoveUserBtn(relatedUser.username)}
                         onClick={() => props.onClickRemoveUserBtn(relatedUser.username)}
                       >
                       >
-                        <i className="icon-fw icon-user-unfollow"></i> {t('admin:user_group_management.remove_from_group')}
+                        <span className="material-symbols-outlined me-1">person_remove</span>{t('admin:user_group_management.remove_from_group')}
                       </button>
                       </button>
                     </div>
                     </div>
                   </div>
                   </div>

+ 1 - 1
apps/app/src/components/Admin/UserManagement.module.scss

@@ -12,7 +12,7 @@
   }
   }
   .search-clear {
   .search-clear {
     position: absolute;
     position: absolute;
-    top: 12px;
+    top: 15px;
     right: 1px;
     right: 1px;
     z-index: 3;
     z-index: 3;
     width: 24px;
     width: 24px;

+ 4 - 3
apps/app/src/components/Admin/UserManagement.tsx

@@ -149,15 +149,16 @@ const UserManagement = (props: UserManagementProps) => {
               {
               {
                 adminUsersContainer.state.searchText.length > 0
                 adminUsersContainer.state.searchText.length > 0
                   ? (
                   ? (
-                    <i
-                      className="icon-close search-clear"
+                    <span
+                      className="material-symbols-outlined me-1 search-clear"
                       onClick={async() => {
                       onClick={async() => {
                         await adminUsersContainer.clearSearchText();
                         await adminUsersContainer.clearSearchText();
                         if (inputRef.current != null) {
                         if (inputRef.current != null) {
                           inputRef.current.value = '';
                           inputRef.current.value = '';
                         }
                         }
                       }}
                       }}
-                    />
+                    >cancel
+                    </span>
                   )
                   )
                   : ''
                   : ''
               }
               }

+ 2 - 2
apps/app/src/components/Admin/Users/ExternalAccountTable.tsx

@@ -63,7 +63,7 @@ const ExternalAccountTable = (props: ExternalAccountTableProps): JSX.Element =>
                   data-html="true"
                   data-html="true"
                   title={t('user_management.password_setting_help')}
                   title={t('user_management.password_setting_help')}
                 >
                 >
-                  <small><i className="icon-question" aria-hidden="true"></i></small>
+                  <small><span className="material-symbols-outlined" aria-hidden="true">help</span></small>
                 </span>
                 </span>
               </div>
               </div>
             </th>
             </th>
@@ -92,7 +92,7 @@ const ExternalAccountTable = (props: ExternalAccountTableProps): JSX.Element =>
                 <td>
                 <td>
                   <div className="btn-group admin-user-menu">
                   <div className="btn-group admin-user-menu">
                     <button type="button" className="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown">
                     <button type="button" className="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown">
-                      <i className="icon-settings"></i> <span className="caret"></span>
+                      <span className="material-symbols-outlined">settings</span> <span className="caret"></span>
                     </button>
                     </button>
                     <ul className="dropdown-menu" role="menu">
                     <ul className="dropdown-menu" role="menu">
                       <li className="dropdown-header">{t('user_management.user_table.edit_menu')}</li>
                       <li className="dropdown-header">{t('user_management.user_table.edit_menu')}</li>

+ 1 - 1
apps/app/src/components/Admin/Users/GrantAdminButton.tsx

@@ -30,7 +30,7 @@ const GrantAdminButton = (props: GrantAdminButtonProps): JSX.Element => {
 
 
   return (
   return (
     <button className="dropdown-item" type="button" onClick={() => onClickGrantAdminBtnHandler()}>
     <button className="dropdown-item" type="button" onClick={() => onClickGrantAdminBtnHandler()}>
-      <i className="icon-fw icon-user-following"></i> {t('user_management.user_table.grant_admin_access')}
+      <span className="material-symbols-outlined me-1">person_add</span>{t('user_management.user_table.grant_admin_access')}
     </button>
     </button>
   );
   );
 
 

+ 1 - 1
apps/app/src/components/Admin/Users/GrantReadOnlyButton.tsx

@@ -26,7 +26,7 @@ const GrantReadOnlyButton: React.FC<{
 
 
   return (
   return (
     <button className="dropdown-item" type="button" onClick={onClickGrantReadOnlyBtnHandler}>
     <button className="dropdown-item" type="button" onClick={onClickGrantReadOnlyBtnHandler}>
-      <i className="icon-fw icon-user-following"></i> {t('user_management.user_table.grant_read_only_access')}
+      <span className="material-symbols-outlined me-1">person_add</span>{t('user_management.user_table.grant_read_only_access')}
     </button>
     </button>
   );
   );
 };
 };

+ 2 - 2
apps/app/src/components/Admin/Users/RevokeAdminButton.tsx

@@ -33,7 +33,7 @@ const RevokeAdminButton = (props: RevokeAdminButtonProps): JSX.Element => {
   const renderRevokeAdminBtn = () => {
   const renderRevokeAdminBtn = () => {
     return (
     return (
       <button className="dropdown-item" type="button" onClick={() => onClickRevokeAdminBtnHandler()}>
       <button className="dropdown-item" type="button" onClick={() => onClickRevokeAdminBtnHandler()}>
-        <i className="icon-fw icon-user-unfollow"></i>{t('user_management.user_table.revoke_admin_access')}
+        <span className="material-symbols-outlined me-1">person_remove</span>{t('user_management.user_table.revoke_admin_access')}
       </button>
       </button>
     );
     );
   };
   };
@@ -41,7 +41,7 @@ const RevokeAdminButton = (props: RevokeAdminButtonProps): JSX.Element => {
   const renderRevokeAdminAlert = () => {
   const renderRevokeAdminAlert = () => {
     return (
     return (
       <div className="px-4">
       <div className="px-4">
-        <i className="icon-fw icon-user-unfollow mb-2"></i>{t('user_management.user_table.revoke_admin_access')}
+        <span className="material-symbols-outlined me-1 mb-2">person_remove</span>{t('user_management.user_table.revoke_admin_access')}
         <p className="alert alert-danger">{t('user_management.user_table.cannot_revoke')}</p>
         <p className="alert alert-danger">{t('user_management.user_table.cannot_revoke')}</p>
       </div>
       </div>
     );
     );

+ 2 - 2
apps/app/src/components/Admin/Users/RevokeAdminMenuItem.tsx

@@ -15,7 +15,7 @@ const RevokeAdminAlert = React.memo((): JSX.Element => {
 
 
   return (
   return (
     <div className="px-4">
     <div className="px-4">
-      <i className="icon-fw icon-user-unfollow mb-2"></i>{t('admin:user_management.user_table.revoke_admin_access')}
+      <span className="material-symbols-outlined me-1 mb-2">person_remove</span>{t('admin:user_management.user_table.revoke_admin_access')}
       <p className="alert alert-danger">{t('admin:user_management.user_table.cannot_revoke')}</p>
       <p className="alert alert-danger">{t('admin:user_management.user_table.cannot_revoke')}</p>
     </div>
     </div>
   );
   );
@@ -49,7 +49,7 @@ const RevokeAdminMenuItem = (props: Props): JSX.Element => {
   return user.username !== currentUser?.username
   return user.username !== currentUser?.username
     ? (
     ? (
       <button className="dropdown-item" type="button" onClick={clickRevokeAdminBtnHandler}>
       <button className="dropdown-item" type="button" onClick={clickRevokeAdminBtnHandler}>
-        <i className="icon-fw icon-user-unfollow"></i> {t('user_management.user_table.revoke_admin_access')}
+        <span className="material-symbols-outlined me-1">person_remove</span> {t('user_management.user_table.revoke_admin_access')}
       </button>
       </button>
     )
     )
     : <RevokeAdminAlert />;
     : <RevokeAdminAlert />;

+ 1 - 1
apps/app/src/components/Admin/Users/RevokeReadOnlyMenuItem.tsx

@@ -26,7 +26,7 @@ const RevokeReadOnlyMenuItem: React.FC<{
 
 
   return (
   return (
     <button className="dropdown-item" type="button" onClick={clickRevokeReadOnlyBtnHandler}>
     <button className="dropdown-item" type="button" onClick={clickRevokeReadOnlyBtnHandler}>
-      <i className="icon-fw icon-user-unfollow"></i> {t('user_management.user_table.revoke_read_only_access')}
+      <span className="material-symbols-outlined me-1">person_remove</span> {t('user_management.user_table.revoke_read_only_access')}
     </button>
     </button>
   );
   );
 };
 };

+ 1 - 1
apps/app/src/components/Admin/Users/SendInvitationEmailButton.jsx

@@ -38,7 +38,7 @@ const SendInvitationEmailButton = (props) => {
 
 
   return (
   return (
     <button className={`dropdown-item ${textColor}`} type="button" onClick={() => { onClickSendInvitationEmailButton() }}>
     <button className={`dropdown-item ${textColor}`} type="button" onClick={() => { onClickSendInvitationEmailButton() }}>
-      <i className="icon-fw icon-envelope"></i>
+      <span className="material-symbols-outlined me-1">mail</span>
       {isInvitationEmailSended && (<>{t('admin:user_management.user_table.resend_invitation_email')}</>)}
       {isInvitationEmailSended && (<>{t('admin:user_management.user_table.resend_invitation_email')}</>)}
       {!isInvitationEmailSended && (<>{t('admin:user_management.user_table.send_invitation_email')}</>)}
       {!isInvitationEmailSended && (<>{t('admin:user_management.user_table.send_invitation_email')}</>)}
     </button>
     </button>

+ 8 - 4
apps/app/src/components/Admin/Users/SortIcons.tsx

@@ -13,15 +13,19 @@ export const SortIcons = (props: SortIconsProps): JSX.Element => {
   return (
   return (
     <div className="d-flex flex-column text-center">
     <div className="d-flex flex-column text-center">
       <a
       <a
-        className={`fa ${isSelected && isAsc ? 'fa-chevron-up' : 'fa-angle-up'}`}
+        className={`${isSelected && isAsc ? 'text-primary' : 'text-muted'}`}
         aria-hidden="true"
         aria-hidden="true"
         onClick={() => onClick('asc')}
         onClick={() => onClick('asc')}
-      />
+      >
+        <span className="material-symbols-outlined">expand_less</span>
+      </a>
       <a
       <a
-        className={`fa ${isSelected && !isAsc ? 'fa-chevron-down' : 'fa-angle-down'}`}
+        className={`${isSelected && !isAsc ? 'text-primary' : 'text-muted'}`}
         aria-hidden="true"
         aria-hidden="true"
         onClick={() => onClick('desc')}
         onClick={() => onClick('desc')}
-      />
+      >
+        <span className="material-symbols-outlined">expand_more</span>
+      </a>
     </div>
     </div>
   );
   );
 };
 };

+ 1 - 1
apps/app/src/components/Admin/Users/StatusActivateButton.jsx

@@ -33,7 +33,7 @@ class StatusActivateButton extends React.Component {
 
 
     return (
     return (
       <button className="dropdown-item" type="button" onClick={() => { this.onClickAcceptBtn() }}>
       <button className="dropdown-item" type="button" onClick={() => { this.onClickAcceptBtn() }}>
-        <i className="icon-fw icon-user-following"></i> {t('user_management.user_table.accept')}
+        <span className="material-symbols-outlined me-1">person_add</span>{t('user_management.user_table.accept')}
       </button>
       </button>
     );
     );
   }
   }

+ 2 - 2
apps/app/src/components/Admin/Users/StatusSuspendMenuItem.tsx

@@ -14,7 +14,7 @@ const SuspendAlert = React.memo((): JSX.Element => {
 
 
   return (
   return (
     <div className="px-4">
     <div className="px-4">
-      <i className="icon-fw icon-ban mb-2"></i>{t('admin:user_management.user_table.deactivate_account')}
+      <span className="material-symbols-outlined me-1 mb-2">cancel</span>{t('admin:user_management.user_table.deactivate_account')}
       <p className="alert alert-danger">{t('admin:user_management.user_table.your_own')}</p>
       <p className="alert alert-danger">{t('admin:user_management.user_table.your_own')}</p>
     </div>
     </div>
   );
   );
@@ -47,7 +47,7 @@ const StatusSuspendMenuItem = (props: Props): JSX.Element => {
   return user.username !== currentUser?.username
   return user.username !== currentUser?.username
     ? (
     ? (
       <button className="dropdown-item" type="button" onClick={clickDeactiveBtnHandler}>
       <button className="dropdown-item" type="button" onClick={clickDeactiveBtnHandler}>
-        <i className="icon-fw icon-ban"></i> {t('user_management.user_table.deactivate_account')}
+        <span className="material-symbols-outlined me-1">cancel</span> {t('user_management.user_table.deactivate_account')}
       </button>
       </button>
     )
     )
     : <SuspendAlert />;
     : <SuspendAlert />;

+ 2 - 2
apps/app/src/components/Admin/Users/UserMenu.module.scss

@@ -1,5 +1,5 @@
 .grw-usermenu-notification-icon :global {
 .grw-usermenu-notification-icon :global {
   position: absolute;
   position: absolute;
-  top: -4px;
-  left: 30px;
+  top: -6px;
+  left: 3px;
 }
 }

+ 7 - 3
apps/app/src/components/Admin/Users/UserMenu.tsx

@@ -48,7 +48,7 @@ const UserMenu = (props: UserMenuProps) => {
         <li className="dropdown-header">{t('user_management.user_table.edit_menu')}</li>
         <li className="dropdown-header">{t('user_management.user_table.edit_menu')}</li>
         <li>
         <li>
           <button className="dropdown-item" type="button" onClick={onClickPasswordResetHandler}>
           <button className="dropdown-item" type="button" onClick={onClickPasswordResetHandler}>
-            <i className="icon-fw icon-key"></i>{ t('user_management.reset_password') }
+            <span className="material-symbols-outlined me-1">key</span>{ t('user_management.reset_password') }
           </button>
           </button>
         </li>
         </li>
       </>
       </>
@@ -95,9 +95,13 @@ const UserMenu = (props: UserMenuProps) => {
   return (
   return (
     <UncontrolledDropdown id="userMenu" size="sm">
     <UncontrolledDropdown id="userMenu" size="sm">
       <DropdownToggle caret color="secondary" outline>
       <DropdownToggle caret color="secondary" outline>
-        <i className="icon-settings" />
+        <span className="material-symbols-outlined fs-5">settings</span>
         {(user.status === USER_STATUS.INVITED && !isInvitationEmailSended)
         {(user.status === USER_STATUS.INVITED && !isInvitationEmailSended)
-        && <i className={`fa fa-circle text-danger grw-usermenu-notification-icon ${styles['grw-usermenu-notification-icon']}`} />}
+        && (
+          <span className={`material-symbols-outlined fill fs-6 text-danger grw-usermenu-notification-icon ${styles['grw-usermenu-notification-icon']}`}>
+            circle
+          </span>
+        )}
       </DropdownToggle>
       </DropdownToggle>
       <DropdownMenu strategy="fixed">
       <DropdownMenu strategy="fixed">
         {renderEditMenu()}
         {renderEditMenu()}

+ 4 - 4
apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx

@@ -23,7 +23,7 @@ export const BookmarkFolderItemControl: React.FC<{
     <Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)}>
     <Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)}>
       { children ?? (
       { children ?? (
         <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control d-flex align-items-center justify-content-center">
         <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control d-flex align-items-center justify-content-center">
-          <i className="icon-options"></i>
+          <span className="material-symbols-outlined">more_horiz</span>
         </DropdownToggle>
         </DropdownToggle>
       ) }
       ) }
       <DropdownMenu
       <DropdownMenu
@@ -35,7 +35,7 @@ export const BookmarkFolderItemControl: React.FC<{
             onClick={onClickMoveToRoot}
             onClick={onClickMoveToRoot}
             className="grw-page-control-dropdown-item"
             className="grw-page-control-dropdown-item"
           >
           >
-            <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
+            <span className="material-symbols-outlined grw-page-control-dropdown-icon">bookmark</span>
             {t('bookmark_folder.move_to_root')}
             {t('bookmark_folder.move_to_root')}
           </DropdownItem>
           </DropdownItem>
         )}
         )}
@@ -43,7 +43,7 @@ export const BookmarkFolderItemControl: React.FC<{
           onClick={onClickRename}
           onClick={onClickRename}
           className="grw-page-control-dropdown-item"
           className="grw-page-control-dropdown-item"
         >
         >
-          <i className="icon-fw icon-action-redo grw-page-control-dropdown-icon"></i>
+          <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">redo</span>
           {t('Rename')}
           {t('Rename')}
         </DropdownItem>
         </DropdownItem>
 
 
@@ -53,7 +53,7 @@ export const BookmarkFolderItemControl: React.FC<{
           className="pt-2 grw-page-control-dropdown-item text-danger"
           className="pt-2 grw-page-control-dropdown-item text-danger"
           onClick={onClickDelete}
           onClick={onClickDelete}
         >
         >
-          <span className="material-symbols-outlined grw-page-control-dropdown-icon">delete</span>
+          <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">delete</span>
           {t('Delete')}
           {t('Delete')}
         </DropdownItem>
         </DropdownItem>
       </DropdownMenu>
       </DropdownMenu>

+ 1 - 1
apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -118,7 +118,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
           onClick={onUnbookmarkHandler}
           onClick={onUnbookmarkHandler}
           className="grw-bookmark-folder-menu-item text-danger"
           className="grw-bookmark-folder-menu-item text-danger"
         >
         >
-          <i className="fa fa-bookmark"></i>{' '}
+          <span className="material-symbols-outlined">bookmark</span>{' '}
           <span className="mx-2">
           <span className="mx-2">
             {t('bookmark_folder.cancel_bookmark')}
             {t('bookmark_folder.cancel_bookmark')}
           </span>
           </span>

+ 0 - 4
apps/app/src/components/Bookmarks/BookmarkFolderTree.module.scss

@@ -24,10 +24,6 @@ $grw-bookmark-item-padding-left: 35px;
 
 
 .grw-foldertree :global {
 .grw-foldertree :global {
 
 
-  .btn-page-item-control .icon-plus::before {
-    font-size: 18px;
-  }
-
   .list-group-item {
   .list-group-item {
     .grw-visible-on-hover {
     .grw-visible-on-hover {
       display: none;
       display: none;

+ 1 - 1
apps/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -181,7 +181,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
               : undefined}
               : undefined}
           >
           >
             <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover me-1">
             <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover me-1">
-              <i className="icon-options fa fa-rotate-90 p-1"></i>
+              <span className="material-symbols-outlined p-1">more_vert</span>
             </DropdownToggle>
             </DropdownToggle>
           </PageItemControl>
           </PageItemControl>
         </div>
         </div>

+ 1 - 1
apps/app/src/components/Bookmarks/BookmarkMoveToRootBtn.tsx

@@ -15,7 +15,7 @@ export const BookmarkMoveToRootBtn: React.FC<{
       className="grw-page-control-dropdown-item"
       className="grw-page-control-dropdown-item"
       data-testid="add-remove-bookmark-btn"
       data-testid="add-remove-bookmark-btn"
     >
     >
-      <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
+      <span className="material-symbols-outlined grw-page-control-dropdown-icon">bookmark</span>
       {t('bookmark_folder.move_to_root')}
       {t('bookmark_folder.move_to_root')}
     </DropdownItem>
     </DropdownItem>
   );
   );

+ 3 - 2
apps/app/src/components/Common/CustomCopyToClipBoard.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useState, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useState, useCallback } from 'react';
 
 
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
@@ -25,7 +26,7 @@ const CustomCopyToClipBoard: FC<Props> = (props: Props) => {
     <>
     <>
       <CopyToClipboard text={props.textToBeCopied || ''} onCopy={showToolTip}>
       <CopyToClipboard text={props.textToBeCopied || ''} onCopy={showToolTip}>
         <div className="btn input-group-text" id="tooltipTarget">
         <div className="btn input-group-text" id="tooltipTarget">
-          <i className="fa fa-clipboard mx-1" aria-hidden="true"></i>
+          <span className="material-symbols-outlined mx-1" aria-hidden="true">content_paste</span>
         </div>
         </div>
       </CopyToClipboard>
       </CopyToClipboard>
       <Tooltip target="tooltipTarget" fade={false} isOpen={tooltipOpen}>
       <Tooltip target="tooltipTarget" fade={false} isOpen={tooltipOpen}>

+ 7 - 7
apps/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -11,7 +11,7 @@ import {
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
 import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
-import { IPageOperationProcessData } from '~/interfaces/page-operation';
+import type { IPageOperationProcessData } from '~/interfaces/page-operation';
 import { useSWRxPageInfo } from '~/stores/page';
 import { useSWRxPageInfo } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 import { shouldRecoverPagePaths } from '~/utils/page-operation';
 import { shouldRecoverPagePaths } from '~/utils/page-operation';
@@ -170,7 +170,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
             className="grw-page-control-dropdown-item"
             className="grw-page-control-dropdown-item"
             data-testid="add-remove-bookmark-btn"
             data-testid="add-remove-bookmark-btn"
           >
           >
-            <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
+            <span className="material-symbols-outlined grw-page-control-dropdown-icon">bookmark</span>
             { pageInfo.isBookmarked ? t('remove_bookmark') : t('add_bookmark') }
             { pageInfo.isBookmarked ? t('remove_bookmark') : t('add_bookmark') }
           </DropdownItem>
           </DropdownItem>
         ) }
         ) }
@@ -182,7 +182,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
             data-testid="open-page-move-rename-modal-btn"
             data-testid="open-page-move-rename-modal-btn"
             className="grw-page-control-dropdown-item"
             className="grw-page-control-dropdown-item"
           >
           >
-            <i className="icon-fw icon-action-redo grw-page-control-dropdown-icon"></i>
+            <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">redo</span>
             {t(isInstantRename ? 'Rename' : 'Move/Rename')}
             {t(isInstantRename ? 'Rename' : 'Move/Rename')}
           </DropdownItem>
           </DropdownItem>
         ) }
         ) }
@@ -194,7 +194,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
             data-testid="open-page-duplicate-modal-btn"
             data-testid="open-page-duplicate-modal-btn"
             className="grw-page-control-dropdown-item"
             className="grw-page-control-dropdown-item"
           >
           >
-            <i className="icon-fw icon-docs grw-page-control-dropdown-icon"></i>
+            <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">file_copy</span>
             {t('Duplicate')}
             {t('Duplicate')}
           </DropdownItem>
           </DropdownItem>
         ) }
         ) }
@@ -205,7 +205,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
             onClick={revertItemClickedHandler}
             onClick={revertItemClickedHandler}
             className="grw-page-control-dropdown-item"
             className="grw-page-control-dropdown-item"
           >
           >
-            <i className="icon-fw icon-action-undo grw-page-control-dropdown-icon"></i>
+            <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">undo</span>
             {t('modal_putback.label.Put Back Page')}
             {t('modal_putback.label.Put Back Page')}
           </DropdownItem>
           </DropdownItem>
         ) }
         ) }
@@ -223,7 +223,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
             onClick={pathRecoveryItemClickedHandler}
             onClick={pathRecoveryItemClickedHandler}
             className="grw-page-control-dropdown-item"
             className="grw-page-control-dropdown-item"
           >
           >
-            <i className="icon-fw icon-wrench grw-page-control-dropdown-icon"></i>
+            <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">build</span>
             {t('PathRecovery')}
             {t('PathRecovery')}
           </DropdownItem>
           </DropdownItem>
         ) }
         ) }
@@ -239,7 +239,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
               onClick={deleteItemClickedHandler}
               onClick={deleteItemClickedHandler}
               data-testid="open-page-delete-modal-btn"
               data-testid="open-page-delete-modal-btn"
             >
             >
-              <i className="icon-fw icon-trash grw-page-control-dropdown-icon"></i>
+              <span className="material-symbols-outlined me-1 grw-page-control-dropdown-icon">delete</span>
               {t('Delete')}
               {t('Delete')}
             </DropdownItem>
             </DropdownItem>
           </>
           </>

+ 4 - 0
apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.module.scss

@@ -2,3 +2,7 @@
   margin-right: 0.2em;
   margin-right: 0.2em;
   margin-left: 0.2em;
   margin-left: 0.2em;
 }
 }
+
+.material-symbols-outlined {
+  font-size: inherit;
+}

+ 3 - 3
apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx

@@ -3,7 +3,7 @@ import React, { memo, useCallback } from 'react';
 import Link from 'next/link';
 import Link from 'next/link';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
-import LinkedPagePath from '../../../models/linked-page-path';
+import type LinkedPagePath from '../../../models/linked-page-path';
 
 
 import styles from './PagePathHierarchicalLink.module.scss';
 import styles from './PagePathHierarchicalLink.module.scss';
 
 
@@ -41,7 +41,7 @@ export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkPro
         <RootElm>
         <RootElm>
           <span className="path-segment">
           <span className="path-segment">
             <Link href="/trash" prefetch={false}>
             <Link href="/trash" prefetch={false}>
-              <span className="material-symbols-outlined">delete</span>
+              <span className={`material-symbols-outlined ${styles['material-symbols-outlined']}`}>delete</span>
             </Link>
             </Link>
           </span>
           </span>
           <span className={`separator ${styles.separator}`}><a href="/">/</a></span>
           <span className={`separator ${styles.separator}`}><a href="/">/</a></span>
@@ -51,7 +51,7 @@ export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkPro
         <RootElm>
         <RootElm>
           <span className="path-segment">
           <span className="path-segment">
             <Link href="/" prefetch={false}>
             <Link href="/" prefetch={false}>
-              <i className="icon-home"></i>
+              <span className={`material-symbols-outlined ${styles['material-symbols-outlined']}`}>home</span>
               <span className={`separator ${styles.separator}`}>/</span>
               <span className={`separator ${styles.separator}`}>/</span>
             </Link>
             </Link>
           </span>
           </span>

+ 8 - 4
apps/app/src/components/Common/PagePathNav/PagePathNav.tsx

@@ -20,6 +20,7 @@ const { isTrashPage } = pagePathUtils;
 type Props = {
 type Props = {
   pagePath: string,
   pagePath: string,
   pageId?: string | null,
   pageId?: string | null,
+  isWipPage?: boolean,
   isSingleLineMode?: boolean,
   isSingleLineMode?: boolean,
   isCollapseParents?: boolean,
   isCollapseParents?: boolean,
   formerLinkClassName?: string,
   formerLinkClassName?: string,
@@ -37,7 +38,7 @@ const Separator = (): JSX.Element => {
 
 
 export const PagePathNav: FC<Props> = (props: Props) => {
 export const PagePathNav: FC<Props> = (props: Props) => {
   const {
   const {
-    pageId, pagePath, isSingleLineMode, isCollapseParents,
+    pageId, pagePath, isWipPage, isSingleLineMode, isCollapseParents,
     formerLinkClassName, latterLinkClassName,
     formerLinkClassName, latterLinkClassName,
   } = props;
   } = props;
   const dPagePath = new DevidedPagePath(pagePath, false, true);
   const dPagePath = new DevidedPagePath(pagePath, false, true);
@@ -71,10 +72,10 @@ export const PagePathNav: FC<Props> = (props: Props) => {
     const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
     const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
     const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
     const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
     formerLink = (
     formerLink = (
-      <>
+      <div className="fs-5">
         <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} isInTrash={isInTrash} />
         <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} isInTrash={isInTrash} />
         <Separator />
         <Separator />
-      </>
+      </div>
     );
     );
     latterLink = (
     latterLink = (
       <>
       <>
@@ -94,7 +95,10 @@ export const PagePathNav: FC<Props> = (props: Props) => {
           {latterLink}
           {latterLink}
         </h1>
         </h1>
         { pageId != null && !isNotFound && (
         { pageId != null && !isNotFound && (
-          <div className="mx-2">
+          <div className="d-flex align-items-center ms-2">
+            { isWipPage && (
+              <span className="badge rounded-pill text-bg-secondary ms-1 me-1">WIP</span>
+            )}
             <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName="p-2">
             <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName="p-2">
               <i className="ti ti-clipboard"></i>
               <i className="ti ti-clipboard"></i>
             </CopyDropdown>
             </CopyDropdown>

+ 1 - 1
apps/app/src/components/ContentLinkButtons.tsx

@@ -40,7 +40,7 @@ RecentlyCreatedLinkButton.displayName = 'RecentlyCreatedLinkButton';
 
 
 
 
 export type ContentLinkButtonsProps = {
 export type ContentLinkButtonsProps = {
-  author?: IUserHasId,
+  author: IUserHasId | null,
 }
 }
 
 
 export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => {
 export const ContentLinkButtons = (props: ContentLinkButtonsProps): JSX.Element => {

+ 4 - 2
apps/app/src/components/ExpandOrContractButton.tsx

@@ -1,4 +1,5 @@
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 
 type Props = {
 type Props = {
   isWindowExpanded: boolean,
   isWindowExpanded: boolean,
@@ -24,9 +25,10 @@ const ExpandOrContractButton: FC<Props> = (props: Props) => {
   return (
   return (
     <button
     <button
       type="button"
       type="button"
-      className={`btn ${isWindowExpanded ? 'icon-size-actual' : 'icon-size-fullscreen'}`}
+      className="btn material-symbols-outlined"
       onClick={isWindowExpanded ? clickContractButtonHandler : clickExpandButtonHandler}
       onClick={isWindowExpanded ? clickContractButtonHandler : clickExpandButtonHandler}
     >
     >
+      {isWindowExpanded ? 'close_fullscreen' : 'open_in_full'}
     </button>
     </button>
   );
   );
 };
 };

+ 0 - 28
apps/app/src/components/Icons/AttachmentIcon.jsx

@@ -1,28 +0,0 @@
-import React from 'react';
-
-const Attachment = () => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 14 14"
-    width="14px"
-    height="14px"
-  >
-    <rect width="14" height="14" fillOpacity="0" />
-    <g className="cls-1">
-      <path
-        d="M2.9,13a2,2,0,0,1-1.44-.63,2.28,2.28,0,0,1,0-3.23l7-7.38a2.48,2.48,0,0,1,1.22-.7,2.61,
-        2.61,0,0,1,1.41.09A3.46,3.46,0,0,1,12.37,2a3.94,3.94,0,0,1,.36.45A2.61,2.61,0,0,1,13,3a3.41,3.41,
-        0,0,1,.16.57,3.06,3.06,0,0,1-.82,2.75L7.07,11.86a.35.35,0,0,1-.26.13.4.4,0,0,1-.28-.1.47.47,0,0,
-        1-.12-.27.39.39,0,0,1,.11-.29l5.26-5.59a2.28,2.28,0,0,0,.65-1.62,2.07,2.07,0,0,0-.62-1.58A2.62,2.62,
-        0,0,0,11,1.93a2,2,0,0,0-1-.13,1.63,1.63,0,0,0-1,.5L2,9.67a1.52,1.52,0,0,0,0,2.16,1.28,1.28,0,0,0,
-        .44.3,1,1,0,0,0,.51.08,1.43,1.43,0,0,0,1-.49L9.49,5.84l.12-.13.11-.15a1.24,1.24,0,0,0,.1-.2,1.94,
-        1.94,0,0,0,0-.2.6.6,0,0,0,0-.22.66.66,0,0,0-.14-.2.57.57,0,0,0-.45-.22,1,1,0,0,0-.52.3L4.56,
-        9.25a.42.42,0,0,1-.17.1.34.34,0,0,1-.2,0A.4.4,0,0,1,4,9.26.34.34,0,0,1,3.89,9,.41.41,0,0,1,4,8.72L8.16,
-        4.28a1.7,1.7,0,0,1,1-.53,1.32,1.32,0,0,1,1.06.43,1.23,1.23,0,0,1,.4,1.05,1.8,1.8,0,0,1-.58,1.14L4.52,
-        12.26A2.3,2.3,0,0,1,3,13H2.9Z"
-      />
-    </g>
-  </svg>
-);
-
-export default Attachment;

+ 0 - 22
apps/app/src/components/Icons/HistoryIcon.jsx

@@ -1,22 +0,0 @@
-import React from 'react';
-
-const RecentChanges = () => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 14 14"
-    width="14px"
-    height="14px"
-  >
-    <rect width="14" height="14" fillOpacity="0" />
-    <path
-      d="M7.94.94A6.13,6.13,0,0,0,1.89,7v.1L.67,5.89a.38.38,0,0,0-.55,0,.39.39,0,0,0,0,.56L2.36,8.69,4.6,6.45a.4.4,0,0,0,0-.56.39.39,0,0,0-.56,
-      0L2.68,7.25V7A5.33,5.33,0,0,1,7.94,1.73,5.33,5.33,0,0,1,13.21,7a5.34,5.34,0,0,1-5.27,5.27H7.86A5,5,0,0,1,4,10.38a.4.4,0,0,0-.55-.07.4.4,0,
-      0,0-.07.56,5.83,5.83,0,0,0,4.52,2.19H8A6.13,6.13,0,0,0,14,7,6.13,6.13,0,0,0,7.94.94Z"
-    />
-    <path
-      d="M7.94,2.83a.4.4,0,0,0-.39.4V7.37L10,8.92a.37.37,0,0,0,.21.06.4.4,0,0,0,.21-.73L8.34,6.93V3.23A.4.4,0,0,0,7.94,2.83Z"
-    />
-  </svg>
-);
-
-export default RecentChanges;

+ 0 - 22
apps/app/src/components/Icons/PresentationIcon.jsx

@@ -1,22 +0,0 @@
-import React from 'react';
-
-const PresentationIcon = () => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    width="14"
-    height="14"
-    viewBox="0 0 12.25 14"
-  >
-    <path
-      d="M44.261,0H32.909a.448.448,0,0,0-.449.448V7.635a.449.449,0,0,0,.9,0V.9H43.812V7.635a.449.449,0,0,0,.9,0V.448A.448.448,0,0,0,44.261,0Z"
-      transform="translate(-32.46)"
-    />
-    <path
-      d="M90.959,287.182H82.315a.448.448,0,1,0,0,.9h3.873v1.115l-3.207,3.381a.449.449,0,0,0,.652.616l2.555-2.694v2.013a.449.449,0,0,0,.9,0V
-        290.5l2.555,2.694a.449.449,0,0,0,.652-.616l-3.208-3.382v-1.114h3.873a.448.448,0,1,0,0-.9Z"
-      transform="translate(-80.512 -279.329)"
-    />
-  </svg>
-);
-
-export default PresentationIcon;

+ 0 - 35
apps/app/src/components/Icons/ShareLinkIcon.jsx

@@ -1,35 +0,0 @@
-import React from 'react';
-
-const ShareLink = () => (
-  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 20 20">
-    <g transform="translate(-142 -502)">
-      <rect width="20" height="20" transform="translate(142 502)" fill="none" />
-      <g transform="translate(16 286.938)">
-        <path
-          d="M-1.813-3.563a2.711,2.711,0,0,0-1.274.308,2.8,2.8,0,0,0-.976.835L-11.48-6.2a2.676,2.676,
-          0,0,0,.105-.738,2.555,2.555,0,0,0-.044-.466,3.34,3.34,0,0,0-.114-.448l7.453-3.621a2.71,2.71,
-          0,0,0,.984.853,2.764,2.764,0,0,0,1.283.308,2.708,2.708,0,0,0,1.986-.826A2.708,2.708,0,0,
-          0,1-13.125a2.751,2.751,0,0,0-.378-1.406A2.793,2.793,0,0,0-.406-15.56a2.751,2.751,0,0,
-          0-1.406-.378,2.751,2.751,0,0,0-1.406.378,2.793,2.793,0,0,0-1.028,1.028,2.751,2.751,0,0,0-.378,
-          1.406v.105a.64.64,0,0,0,.009.105.641.641,0,0,1,.009.105A.641.641,0,0,0-4.6-12.7a.694.694,0,0,0,
-          .026.105.332.332,0,0,1,.018.105l-7.559,3.674a2.735,2.735,0,0,0-.923-.686,2.727,2.727,0,0,
-          0-1.151-.246,2.708,2.708,0,0,0-1.986.826A2.708,2.708,0,0,0-17-6.937a2.708,2.708,0,0,0,
-          .826,1.986,2.708,2.708,0,0,0,1.986.826A2.666,2.666,0,0,0-11.99-5.2l7.453,3.8a1.388,1.388,0,0,
-          0-.053.211q-.018.105-.026.22t-.009.22A2.751,2.751,0,0,0-4.247.656,2.792,2.792,0,0,0-3.219,
-          1.685a2.751,2.751,0,0,0,1.406.378A2.708,2.708,0,0,0,.174,1.236,2.708,2.708,0,0,0,1-.75,2.708,
-          2.708,0,0,0,.174-2.736,2.708,2.708,0,0,0-1.813-3.563Zm-1.2-10.758a1.627,1.627,0,0,1,1.2-.492,
-          1.627,1.627,0,0,1,1.2.492,1.627,1.627,0,0,1,.492,1.2,1.627,1.627,0,0,1-.492,1.2,1.627,1.627,
-          0,0,1-1.2.492,1.627,1.627,0,0,1-1.2-.492,1.627,1.627,0,0,1-.492-1.2A1.627,1.627,0,0,
-          1-3.008-14.32Zm-9.984,8.578a1.627,1.627,0,0,1-1.2.492,1.627,1.627,0,0,1-1.2-.492,1.627,
-          1.627,0,0,1-.492-1.2,1.627,1.627,0,0,1,.492-1.2,1.627,1.627,0,0,1,1.2-.492,1.627,1.627,
-          0,0,1,1.2.492,1.627,1.627,0,0,1,.492,1.2A1.627,1.627,0,0,1-12.992-5.742ZM-.617.445a1.627,
-          1.627,0,0,1-1.2.492,1.627,1.627,0,0,1-1.2-.492A1.627,1.627,0,0,1-3.5-.75a1.627,1.627,0,0,
-          1,.492-1.2,1.627,1.627,0,0,1,1.2-.492,1.627,1.627,0,0,1,1.2.492A1.627,1.627,0,0,1-.125-.75,1.627,1.627,0,0,1-.617.445Z"
-          transform="translate(144 232)"
-        />
-      </g>
-    </g>
-  </svg>
-);
-
-export default ShareLink;

Some files were not shown because too many files changed in this diff