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

Merge branch 'dev/7.0.x' into feat/138055-implementation-of-the-only-unread-switch

Shun Miyazawa 2 лет назад
Родитель
Сommit
85f2042dc8
100 измененных файлов с 583 добавлено и 519 удалено
  1. 39 2
      CHANGELOG.md
  2. 2 2
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx
  3. 1 1
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx
  4. 4 4
      apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx
  5. 9 9
      apps/app/_obsolete/src/components/PageEditorByHackmd.tsx
  6. 2 0
      apps/app/package.json
  7. 1 1
      apps/app/public/static/locales/en_US/admin.json
  8. 1 1
      apps/app/public/static/locales/ja_JP/admin.json
  9. 1 1
      apps/app/public/static/locales/zh_CN/admin.json
  10. 2 2
      apps/app/src/components/Admin/App/ConfirmModal.tsx
  11. 3 3
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  12. 1 1
      apps/app/src/components/Admin/App/MailSetting.tsx
  13. 1 1
      apps/app/src/components/Admin/App/MaintenanceMode.tsx
  14. 2 2
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  15. 1 1
      apps/app/src/components/Admin/App/SiteUrlSetting.tsx
  16. 1 1
      apps/app/src/components/Admin/App/V5PageMigration.tsx
  17. 2 1
      apps/app/src/components/Admin/AuditLog/AuditLogDisableMode.tsx
  18. 3 3
      apps/app/src/components/Admin/AuditLog/AuditLogSettings.tsx
  19. 1 1
      apps/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx
  20. 3 3
      apps/app/src/components/Admin/AuditLogManagement.tsx
  21. 17 17
      apps/app/src/components/Admin/Common/AdminNavigation.tsx
  22. 2 2
      apps/app/src/components/Admin/ElasticsearchManagement/StatusTable.jsx
  23. 3 3
      apps/app/src/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx
  24. 1 1
      apps/app/src/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx
  25. 3 3
      apps/app/src/components/Admin/ImportData/ImportDataPageContents.jsx
  26. 2 2
      apps/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx
  27. 2 2
      apps/app/src/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx
  28. 1 1
      apps/app/src/components/Admin/ManageExternalAccount.tsx
  29. 9 10
      apps/app/src/components/Admin/Notification/GlobalNotificationList.jsx
  30. 10 10
      apps/app/src/components/Admin/Notification/ManageGlobalNotification.tsx
  31. 2 2
      apps/app/src/components/Admin/Notification/NotificationDeleteModal.jsx
  32. 2 2
      apps/app/src/components/Admin/Notification/NotificationSetting.jsx
  33. 2 2
      apps/app/src/components/Admin/Security/DeleteAllShareLinksModal.jsx
  34. 1 1
      apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx
  35. 1 1
      apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx
  36. 4 4
      apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx
  37. 1 1
      apps/app/src/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx
  38. 2 2
      apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  39. 1 1
      apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  40. 3 3
      apps/app/src/components/Admin/UserManagement.tsx
  41. 1 1
      apps/app/src/components/Admin/Users/ExternalAccountTable.tsx
  42. 1 1
      apps/app/src/components/Admin/Users/UserRemoveButton.jsx
  43. 2 2
      apps/app/src/components/AlertSiteUrlUndefined.tsx
  44. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  45. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx
  46. 1 1
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx
  47. 5 0
      apps/app/src/components/Common/PageViewLayout.module.scss
  48. 1 1
      apps/app/src/components/CompleteUserRegistration.tsx
  49. 6 6
      apps/app/src/components/CompleteUserRegistrationForm.tsx
  50. 1 1
      apps/app/src/components/ContentLinkButtons.tsx
  51. 2 2
      apps/app/src/components/DeleteBookmarkFolderModal.tsx
  52. 1 1
      apps/app/src/components/EmptyTrashButton.tsx
  53. 2 2
      apps/app/src/components/EmptyTrashModal.tsx
  54. 2 2
      apps/app/src/components/ForbiddenPage.tsx
  55. 1 1
      apps/app/src/components/InAppNotification/InAppNotificationDropdown.tsx
  56. 6 6
      apps/app/src/components/InstallerForm.tsx
  57. 4 4
      apps/app/src/components/InvitedForm.tsx
  58. 16 14
      apps/app/src/components/LoginForm.tsx
  59. 1 1
      apps/app/src/components/Me/PersonalSettings.jsx
  60. 1 1
      apps/app/src/components/NotCreatablePage.tsx
  61. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx
  62. 2 2
      apps/app/src/components/PageAlert/TrashPageAlert.tsx
  63. 1 1
      apps/app/src/components/PageComment.tsx
  64. 1 1
      apps/app/src/components/PageComment/CommentControl.tsx
  65. 2 2
      apps/app/src/components/PageComment/DeleteCommentModal.tsx
  66. 3 3
      apps/app/src/components/PageCreateModal.jsx
  67. 1 1
      apps/app/src/components/PageDeleteModal.tsx
  68. 7 15
      apps/app/src/components/PageHistory/PageRevisionTable.tsx
  69. 3 3
      apps/app/src/components/PageSelectModal/TreeItemForModal.tsx
  70. 6 0
      apps/app/src/components/PageSideContents/PageAccessoriesControl.module.scss
  71. 5 5
      apps/app/src/components/PageStatusAlert.tsx
  72. 14 5
      apps/app/src/components/PageTags/PageTags.tsx
  73. 10 0
      apps/app/src/components/PageTags/TagLabels.module.scss
  74. 1 1
      apps/app/src/components/PasswordResetExecutionForm.tsx
  75. 3 2
      apps/app/src/components/PasswordResetRequestForm.tsx
  76. 3 3
      apps/app/src/components/PrivateLegacyPages.tsx
  77. 1 1
      apps/app/src/components/PrivateLegacyPagesMigrationModal.tsx
  78. 2 2
      apps/app/src/components/PutbackPageModal.jsx
  79. 1 1
      apps/app/src/components/ReactMarkdownComponents/RichAttachment.tsx
  80. 1 1
      apps/app/src/components/SearchForm.tsx
  81. 1 1
      apps/app/src/components/SearchPage.tsx
  82. 1 1
      apps/app/src/components/SearchTypeahead.tsx
  83. 2 2
      apps/app/src/components/ShareLinkPageView.tsx
  84. 1 1
      apps/app/src/components/Sidebar/Custom/CustomSidebar.tsx
  85. 1 1
      apps/app/src/components/Sidebar/Custom/CustomSidebarNotFound.tsx
  86. 17 13
      apps/app/src/components/Sidebar/PageCreateButton/DropendMenu.tsx
  87. 12 5
      apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx
  88. 3 5
      apps/app/src/components/Sidebar/PageCreateButton/hooks.tsx
  89. 3 3
      apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx
  90. 1 1
      apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx
  91. 0 69
      apps/app/src/components/TreeItem/NewPageCreateButton.tsx
  92. 0 104
      apps/app/src/components/TreeItem/NewPageInput.tsx
  93. 37 0
      apps/app/src/components/TreeItem/NewPageInput/NewPageCreateButton.tsx
  94. 81 0
      apps/app/src/components/TreeItem/NewPageInput/NewPageInput.tsx
  95. 1 0
      apps/app/src/components/TreeItem/NewPageInput/index.ts
  96. 110 0
      apps/app/src/components/TreeItem/NewPageInput/use-new-page-input.tsx
  97. 13 0
      apps/app/src/components/TreeItem/NotDraggableForClosableTextInput.tsx
  98. 20 58
      apps/app/src/components/TreeItem/SimpleItem.tsx
  99. 0 43
      apps/app/src/components/TreeItem/UseNewPageInput.tsx
  100. 5 2
      apps/app/src/components/TreeItem/index.ts

+ 39 - 2
CHANGELOG.md

@@ -1,12 +1,47 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.2.4...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.3.0...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.3.0](https://github.com/weseek/growi/compare/v6.2.5...v6.3.0) - 2023-12-14
+
+### BREAKING CHANGES
+
+* support: Remove obsolete route for attachment on MongoDB GridFS (#8239) @yuki-takei
+
+### 💎 Features
+
+* feat: LDAP/Keycloak group sync (#7857) @arafubeatbox
+
+### 🚀 Improvement
+
+* imprv: Refactor DrawioViewer re-rendering by the resizing trigger (#8314) @yuki-takei
+* imprv: Apply content headers for attachment response (#8245) @yuki-takei
+
+### 🐛 Bug Fixes
+
+* fix: SAML callback action throws the field is undefined error when the ACL Rule string is only white space (#8322) @yuki-takei
+* fix: Remove groups not related to the user from the user groups that are specified automatically when creating child pages (#8266) @arafubeatbox
+* fix: Certify shared page attachment middleware (#8255) @yuki-takei
+
+### 🧰 Maintenance
+
+* support: Add test for delete-completely-user-home-by-system.ts (#8323) @jam411
+* ci(deps-dev): bump vite from 4.5.0 to 4.5.1 (#8302) @dependabot
+* support: TypeScriptize attachment codes (#8243) @yuki-takei
+* support: Remove obsolete route for attachment on MongoDB GridFS (#8239) @yuki-takei
+
+## [v6.2.5](https://github.com/weseek/growi/compare/v6.2.4...v6.2.5) - 2023-12-14
+
+### 🐛 Bug Fixes
+
+* fix: Update deleteCompletelyUserHomeBySystem for v4 process (#8289) @jam411
+
 ## [v6.2.4](https://github.com/weseek/growi/compare/v6.2.3...v6.2.4) - 2023-11-29
 
 ### 💎 Features
+
 * feat: Show create date in Attachment Data list (#8229) @sakazuki
 
 ### 🚀 Improvement
@@ -14,11 +49,13 @@
 * imprv: Add Marp preset template for ja_JP and zh_CN (#8179) @AikaHiyama
 * imprv: Allow deletion of user homepage when the user is deleted (#8224) @jam411
 
+### 🐛 Bug Fixes
+* fix: Certify shared page attachment middleware (6.2.x) (#8256) @yuki-takei
+
 ### 🧰 Maintenance
 
 * support: Refactor deleteCompletelyUserHomeBySystem (#8262) @jam411
 
-
 ## [v6.2.3](https://github.com/weseek/growi/compare/v6.2.2...v6.2.3) - 2023-11-13
 
 ### 🚀 Improvement

+ 2 - 2
apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx

@@ -47,7 +47,7 @@ const NavbarRight = memo((): JSX.Element => {
                 data-testid="newPageBtn"
                 onClick={() => openCreateModal(currentPagePath || '')}
               >
-                <i className="icon-pencil me-2"></i>
+                <span className="material-symbols-outlined">edit</span>
                 <span className="d-none d-lg-block">{ t('commons:New') }</span>
               </button>
             </li>
@@ -85,7 +85,7 @@ const Confidential: FC<ConfidentialProps> = memo((props: ConfidentialProps): JSX
 
   return (
     <li className="nav-item confidential text-light">
-      <i id="confidentialTooltip" className="icon-info d-md-none" />
+      <i id="confidentialTooltip"></i><span className="material-symbols-outlined d-md-none">info</span>
       <span className="d-none d-md-inline">
         {confidential}
       </span>

+ 1 - 1
apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -727,7 +727,7 @@ class CodeMirrorEditor extends AbstractEditor {
   renderCheatsheetModalButton() {
     return (
       <button type="button" className="btn-link gfm-cheatsheet-modal-link small" onClick={() => { this.markdownHelpButtonClickedHandler() }}>
-        <i className="icon-question" /> Markdown
+        <span className="material-symbols-outlined">help</span> Markdown
       </button>
     );
   }

+ 4 - 4
apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx

@@ -156,7 +156,7 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
     >
       {/* <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3" close={resizeAndCloseButtons}> */}
       <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3">
-        <i className="icon-fw icon-exclamation" />{t('modal_resolve_conflict.resolve_conflict')}
+        <span className="material-symbols-outlined">error</span>{t('modal_resolve_conflict.resolve_conflict')}
       </ModalHeader>
       <ModalBody className="mx-4 my-1">
         { isOpen
@@ -212,7 +212,7 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
                     setResolvedRevision(request.revisionBody);
                   }}
                 >
-                  <i className="icon-fw icon-arrow-down-circle"></i>
+                  <span className="material-symbols-outlined">arrow_circle_down</span>
                   {t('modal_resolve_conflict.select_revision', { revision: 'mine' })}
                 </button>
               </div>
@@ -227,7 +227,7 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
                     setResolvedRevision(origin.revisionBody);
                   }}
                 >
-                  <i className="icon-fw icon-arrow-down-circle"></i>
+                  <span className="material-symbols-outlined">arrow_circle_down</span>
                   {t('modal_resolve_conflict.select_revision', { revision: 'origin' })}
                 </button>
               </div>
@@ -242,7 +242,7 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
                     setResolvedRevision(latest.revisionBody);
                   }}
                 >
-                  <i className="icon-fw icon-arrow-down-circle"></i>
+                  <span className="material-symbols-outlined">arrow_circle_down</span>
                   {t('modal_resolve_conflict.select_revision', { revision: 'theirs' })}
                 </button>
               </div>

+ 9 - 9
apps/app/_obsolete/src/components/PageEditorByHackmd.tsx

@@ -347,7 +347,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
     if (hackmdUri == null) {
       content = (
         <div>
-          <p className="text-center hackmd-status-label"><i className="fa fa-file-text"></i> { t('hackmd.not_set_up')}</p>
+          <p className="text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> { t('hackmd.not_set_up')}</p>
           {/* eslint-disable-next-line react/no-danger */}
           <p dangerouslySetInnerHTML={{ __html: t('hackmd.need_to_associate_with_growi_to_use_hackmd_refer_to_this') }} />
         </div>
@@ -361,7 +361,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
       content = (
         <div className="text-center">
           <p className="hackmd-status-label">
-            <i className="fa fa-file-text me-2" />
+            <span className="material-symbols-outlined">description</span>
             { t('hackmd.used_for_not_found') }
           </p>
           {/* eslint-disable-next-line react/no-danger */}
@@ -377,12 +377,12 @@ export const PageEditorByHackmd = (): JSX.Element => {
 
       content = (
         <div>
-          <p className="text-center hackmd-status-label"><i className="fa fa-file-text"></i> HackMD is READY!</p>
+          <p className="text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> HackMD is READY!</p>
           <p className="text-center"><strong>{t('hackmd.unsaved_draft')}</strong></p>
 
           { isHackmdDocumentOutdated && (
             <div className="card border-warning">
-              <div className="card-header bg-warning text-dark"><i className="icon-fw icon-info"></i> {t('hackmd.draft_outdated')}</div>
+              <div className="card-header bg-warning text-dark"><span className="material-symbols-outlined">info</span> {t('hackmd.draft_outdated')}</div>
               <div className="card-body text-center">
                 {t('hackmd.based_on_revision')}&nbsp;
                 { pageData != null && (
@@ -412,7 +412,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
                 disabled={isInitializing}
                 onClick={resumeToEdit}
               >
-                <span className="btn-label"><i className="icon-fw icon-control-end"></i></span>
+                <span className="btn-label"></span><span className="material-symbols-outlined">skip_next</span>
                 <span className="btn-text">{t('hackmd.resume_to_edit')}</span>
               </button>
             </div>
@@ -424,7 +424,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
               type="button"
               onClick={discardChanges}
             >
-              <span className="btn-label"><i className="icon-fw icon-control-start"></i></span>
+              <span className="btn-label"></span><span className="material-symbols-outlined">play_arrow</span>
               <span className="btn-text">{t('hackmd.discard_changes')}</span>
             </button>
           </div>
@@ -440,7 +440,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
 
       content = (
         <div>
-          <p className="text-muted text-center hackmd-status-label"><i className="fa fa-file-text"></i> HackMD is READY!</p>
+          <p className="text-muted text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> HackMD is READY!</p>
           <div className="text-center hackmd-start-button-container mb-3">
             <button
               className="btn btn-info btn-lg waves-effect waves-light"
@@ -448,7 +448,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
               disabled={isRevisionOutdated || isInitializing}
               onClick={startToEdit}
             >
-              <span className="btn-label"><i className="icon-fw icon-paper-plane"></i></span>
+              <span className="btn-label"></span><span className="material-symbols-outlined">send</span>
               {t('hackmd.start_to_edit')}
             </button>
           </div>
@@ -504,7 +504,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
       { hasError && (
         <div className="hackmd-error position-absolute d-flex flex-column justify-content-center align-items-center">
           <div className="bg-box p-5 text-center">
-            <h2 className="text-warning"><i className="icon-fw icon-exclamation"></i> {t('hackmd.integration_failed')}</h2>
+            <h2 className="text-warning"><span className="material-symbols-outlined">error</span> {t('hackmd.integration_failed')}</h2>
             <h4>{errorMessage}</h4>
             <p className="card custom-card text-danger">
               {errorReason}

+ 2 - 0
apps/app/package.json

@@ -205,6 +205,7 @@
     "uglifycss": "^0.0.29",
     "universal-bunyan": "^0.9.2",
     "unstated": "^2.1.1",
+    "unzip-stream": "^0.3.1",
     "unzipper": "^0.10.5",
     "url-join": "^4.0.0",
     "usehooks-ts": "^2.6.0",
@@ -230,6 +231,7 @@
     "@types/react-scroll": "^1.8.4",
     "@types/throttle-debounce": "^5.0.1",
     "@types/url-join": "^4.0.2",
+    "@types/unzip-stream": "^0.3.4",
     "@vitest/coverage-v8": "^0.34.6",
     "autoprefixer": "^9.0.0",
     "babel-loader": "^8.2.5",

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

@@ -1107,7 +1107,7 @@
       "group_sync_client_secret_detail": "Id of the secret used to authenticate to request to Keycloak admin API",
       "updated_group_sync_settings": "Updated Keycloak group sync settings",
       "preserve_deleted_keycloak_groups": "Preserve Deleted Keycloak Groups",
-      "auth_not_set": "Enable and configure OIDC or SAML with Keycloak in security settings before sync"
+      "auth_not_set": "Enable OIDC or SAML host that includes 'Host' and 'Group Realm' of group sync settings"
     },
     "auto_generate_user_on_sync": "Auto Generate User on Sync",
     "description_mapper_detail": "Attribute to map as group description. Description can be edited after sync. However, when a mapper is set, the edited value can possibly be overwritten by the next sync."

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

@@ -1117,7 +1117,7 @@
       "group_sync_client_secret_detail": "Keycloak admin API にリクエストするための認証に使う client の secret",
       "updated_group_sync_settings": "Keycloak グループ同期設定を更新しました",
       "preserve_deleted_keycloak_groups": "Keycloak から削除されたグループを GROWI に残す",
-      "auth_not_set": "同期実行前にセキュリティ設定で Keycloak を使った OIDC または SAML 認証を有効にし、設定してください"
+      "auth_not_set": "グループ同期設定の Host と Group Realm が発行ホストに含まれる OIDC または SAML 認証をセキュリティ設定で有効にしてください"
     },
     "auto_generate_user_on_sync": "作成されていない GROWI アカウントを自動生成する",
     "description_mapper_detail": "グループの「説明」として読み込む属性。「説明」は同期後に編集可能です。ただし、mapper が設定されている場合、編集内容は再同期によって上書きされます。"

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

@@ -1116,7 +1116,7 @@
       "group_sync_client_secret_detail": "Id of the secret used to authenticate to request to Keycloak admin API",
       "updated_group_sync_settings": "Updated Keycloak group sync settings",
       "preserve_deleted_keycloak_groups": "Preserve Deleted Keycloak Groups",
-      "auth_not_set": "Enable and configure OIDC or SAML with Keycloak in security settings before sync"
+      "auth_not_set": "Enable OIDC or SAML host that includes 'Host' and 'Group Realm' of group sync settings"
     },
     "auto_generate_user_on_sync": "Auto Generate User on Sync",
     "description_mapper_detail": "Attribute to map as group description. Description can be edited after sync. However, when a mapper is set, the edited value can possibly be overwritten by the next sync."

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

@@ -32,7 +32,7 @@ export const ConfirmModal: FC<ConfirmModalProps> = (props: ConfirmModalProps) =>
   return (
     <Modal isOpen={props.isModalOpen} toggle={onCancel}>
       <ModalHeader tag="h4" toggle={onCancel} className="bg-danger">
-        <i className="icon-fw icon-question" />
+        <span className="material-symbols-outlined">help</span>
         {t('Warning')}
       </ModalHeader>
       <ModalBody>
@@ -44,7 +44,7 @@ export const ConfirmModal: FC<ConfirmModalProps> = (props: ConfirmModalProps) =>
               <br />
               <span className="text-warning">
                 <>
-                  <i className="icon-exclamation icon-fw"></i>
+                  <span className="material-symbols-outlined">error</span>
                   {props.supplymentaryMessage}
                 </>
               </span>

+ 3 - 3
apps/app/src/components/Admin/App/FileUploadSetting.tsx

@@ -35,7 +35,7 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
         <br />
         <br />
         <span className="text-danger">
-          <i className="ti ti-unlink"></i>
+          <span className="material-symbols-outlined">link_off</span>
           {t('admin:app_setting.change_setting')}
         </span>
       </p>
@@ -65,8 +65,8 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
         </div>
         {props.isFixedFileUploadByEnvVar && (
           <p className="alert alert-warning mt-2 text-start offset-3 col-6">
-            <i className="icon-exclamation icon-fw">
-            </i><b>FIXED</b><br />
+            <span className="material-symbols-outlined">help</span>
+            <b>FIXED</b><br />
             {/* eslint-disable-next-line react/no-danger */}
             <b dangerouslySetInnerHTML={{ __html: t('admin:app_setting.fixed_by_env_var', { fileUploadType: props.envFileUploadType }) }} />
           </p>

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

@@ -47,7 +47,7 @@ const MailSetting = (props: Props) => {
   return (
     <React.Fragment>
       {!adminAppContainer.state.isMailerSetup && (
-        <div className="alert alert-danger"><i className="icon-exclamation"></i> {t('admin:app_setting.mailer_is_not_set_up')}</div>
+        <div className="alert alert-danger"><span className="material-symbols-outlined">error</span> {t('admin:app_setting.mailer_is_not_set_up')}</div>
       )}
       <div className="row mb-5">
         <label className="col-md-3 col-form-label text-end">{t('admin:app_setting.from_e-mail_address')}</label>

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

@@ -59,7 +59,7 @@ export const MaintenanceMode: FC = () => {
         <br />
         <br />
         <span className="text-warning">
-          <i className="icon-exclamation icon-fw"></i>
+          <span className="material-symbols-outlined">error</span>
           {t('admin:maintenance_mode.supplymentary_message_to_start')}
         </span>
       </p>

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

@@ -53,9 +53,9 @@ const QuestionnaireSettings = (): JSX.Element => {
         <div className="mb-4">{t('app_setting.questionnaire_settings_explanation')}</div>
         <span>
           <div className="mb-2">
-            <span className="text-info me-2"><i className="icon-info icon-fw"></i>{t('app_setting.about_data_sent')}</span>
+            <span className="text-info me-2"><span className="material-symbols-outlined">info</span>{t('app_setting.about_data_sent')}</span>
             <a href={t('app_setting.docs_link')} rel="noreferrer" target="_blank" className="d-inline">
-              {t('app_setting.learn_more')} <i className="icon-share-alt"></i>
+              {t('app_setting.learn_more')} <span className="material-symbols-outlined">share</span>
             </a>
           </div>
           {t('app_setting.other_info_will_be_sent')}<br />

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

@@ -37,7 +37,7 @@ const SiteUrlSetting = (props: Props) => {
     <React.Fragment>
       <p className="card custom-card">{t('site_url.desc')}</p>
       {!adminAppContainer.state.isSetSiteUrl
-          && (<p className="alert alert-danger"><i className="icon-exclamation"></i> {t('site_url.warn')}</p>)}
+          && (<p className="alert alert-danger"><span className="material-symbols-outlined">error</span> {t('site_url.warn')}</p>)}
 
       { adminAppContainer.state.siteUrlUseOnlyEnvVars && (
         <div className="row">

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

@@ -138,7 +138,7 @@ const V5PageMigration: FC<Props> = (props: Props) => {
         <br />
         <br />
         <span className="text-danger">
-          <i className="icon-exclamation icon-fw"></i>
+          <span className="material-symbols-outlined">error</span>
           {t('admin:v5_page_migration.migration_note')}
         </span>
       </p>

+ 2 - 1
apps/app/src/components/Admin/AuditLog/AuditLogDisableMode.tsx

@@ -11,7 +11,8 @@ export const AuditLogDisableMode: FC = () => {
         <div className="row justify-content-md-center">
           <div className="col-md-6 mt-5">
             <div className="text-center">
-              <h1><i className="icon-exclamation large"></i></h1>
+              {/* error icon large */}
+              <h1><span className="material-symbols-outlined">error</span></h1>
               <h1 className="text-center">{t('audit_log_management.audit_log')}</h1>
               <h3
                 // eslint-disable-next-line react/no-danger

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

@@ -24,8 +24,8 @@ export const AuditLogSettings: FC = () => {
         {t('admin:audit_log_management.activity_expiration_date_explain')}
       </p>
       <p className="alert alert-warning col-6">
-        <i className="icon-exclamation icon-fw">
-        </i><b>FIXED</b><br />
+        <span className="material-symbols-outlined">error</span>
+        <b>FIXED</b><br />
         <b
           // eslint-disable-next-line react/no-danger
           dangerouslySetInnerHTML={{
@@ -46,7 +46,7 @@ export const AuditLogSettings: FC = () => {
           target="_blank"
           rel="noopener noreferrer"
         >
-          <i className="icon-fw icon-question" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">help</span>
         </a>
       </h4>
       <p className="form-text text-muted">

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

@@ -111,7 +111,7 @@ const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Pro
   return (
     <div className="input-group me-2">
       <span className="input-group-text">
-        <i className="icon-people" />
+        <span className="material-symbols-outlined">person</span>
       </span>
       <AsyncTypeahead
         ref={typeaheadRef}

+ 3 - 3
apps/app/src/components/Admin/AuditLogManagement.tsx

@@ -152,8 +152,8 @@ export const AuditLogManagement: FC = () => {
       <button type="button" className="btn btn-outline-secondary mb-4" onClick={() => setIsSettingPage(!isSettingPage)}>
         {
           isSettingPage
-            ? <><i className="fa fa-hand-o-left me-1" />{t('admin:audit_log_management.return')}</>
-            : <><i className="fa icon-settings me-1" />{t('admin:audit_log_management.settings')}</>
+            ? <><span className="material-symbols-outlined">arrow_left_alt</span>{t('admin:audit_log_management.return')}</>
+            : <><span className="material-symbols-outlined">settings</span>{t('admin:audit_log_management.settings')}</>
         }
       </button>
 
@@ -163,7 +163,7 @@ export const AuditLogManagement: FC = () => {
         </span>
         { !isSettingPage && (
           <button type="button" className="btn btn-sm ms-auto grw-btn-reload" onClick={reloadButtonPushedHandler}>
-            <i className="icon icon-reload"></i>
+            <span className="material-symbols-outlined">refresh</span>
           </button>
         )}
       </h2>

+ 17 - 17
apps/app/src/components/Admin/Common/AdminNavigation.tsx

@@ -13,23 +13,23 @@ const MenuLabel = ({ menu }: { menu: string }) => {
 
   switch (menu) {
     /* eslint-disable no-multi-spaces, max-len */
-    case 'app':                      return <><i className="me-1 icon-fw icon-settings"></i>{        t('headers.app_settings', { ns: 'commons' }) }</>;
-    case 'security':                 return <><i className="me-1 icon-fw icon-shield"></i>{          t('security_settings.security_settings') }</>;
-    case 'markdown':                 return <><i className="me-1 icon-fw icon-note"></i>{            t('markdown_settings.markdown_settings') }</>;
-    case 'customize':                return <><i className="me-1 icon-fw icon-wrench"></i>{          t('customize_settings.customize_settings') }</>;
-    case 'importer':                 return <><i className="me-1 icon-fw icon-cloud-upload"></i>{    t('importer_management.import_data') }</>;
-    case 'export':                   return <><i className="me-1 icon-fw icon-cloud-download"></i>{  t('export_management.export_archive_data') }</>;
-    case 'data-transfer':            return <><i className="me-1 icon-fw icon-plane"></i>{           t('g2g_data_transfer.data_transfer', { ns: 'commons' })}</>;
-    case 'notification':             return <><i className="me-1 icon-fw icon-bell"></i>{            t('external_notification.external_notification')}</>;
-    case 'slack-integration':        return <><i className="me-1 icon-fw icon-shuffle"></i>{         t('slack_integration.slack_integration') }</>;
-    case 'slack-integration-legacy': return <><i className="me-1 icon-fw icon-shuffle"></i>{         t('slack_integration_legacy.slack_integration_legacy')}</>;
-    case 'users':                    return <><i className="me-1 icon-fw icon-user"></i>{            t('user_management.user_management') }</>;
-    case 'user-groups':              return <><i className="me-1 icon-fw icon-people"></i>{          t('user_group_management.user_group_management') }</>;
-    case 'audit-log':                return <><i className="me-1 icon-fw icon-feed"></i>{            t('audit_log_management.audit_log')}</>;
-    case 'plugins':                  return <><i className="me-1 icon-fw icon-puzzle"></i>{          t('plugins.plugins')}</>;
-    case 'search':                   return <><i className="me-1 icon-fw icon-magnifier"></i>{       t('full_text_search_management.full_text_search_management') }</>;
-    case 'cloud':                    return <><i className="me-1 icon-fw icon-share-alt"></i>{       t('cloud_setting_management.to_cloud_settings')} </>;
-    default:                         return <><i className="me-1 icon-fw icon-home"></i>{            t('wiki_management_homepage') }</>;
+    case 'app':                      return <><span className="material-symbols-outlined me-1">settings</span>{        t('headers.app_settings', { ns: 'commons' }) }</>;
+    case 'security':                 return <><span className="material-symbols-outlined me-1">shield</span>{          t('security_settings.security_settings') }</>;
+    case 'markdown':                 return <><span className="material-symbols-outlined me-1">note</span>{            t('markdown_settings.markdown_settings') }</>;
+    case 'customize':                return <><span className="material-symbols-outlined me-1">construction</span>{          t('customize_settings.customize_settings') }</>;
+    case 'importer':                 return <><span className="material-symbols-outlined me-1">cloud_upload</span>{    t('importer_management.import_data') }</>;
+    case 'export':                   return <><span className="material-symbols-outlined me-1">cloud_download</span>{  t('export_management.export_archive_data') }</>;
+    case 'data-transfer':            return <><span className="material-symbols-outlined me-1">flight</span>{           t('g2g_data_transfer.data_transfer', { ns: 'commons' })}</>;
+    case 'notification':             return <><span className="material-symbols-outlined me-1">notifications</span>{            t('external_notification.external_notification')}</>;
+    case 'slack-integration':        return <><span className="material-symbols-outlined me-1">shuffle</span>{         t('slack_integration.slack_integration') }</>;
+    case 'slack-integration-legacy': return <><span className="material-symbols-outlined me-1">shuffle</span>{         t('slack_integration_legacy.slack_integration_legacy')}</>;
+    case 'users':                    return <><span className="material-symbols-outlined me-1">person</span>{            t('user_management.user_management') }</>;
+    case 'user-groups':              return <><span className="material-symbols-outlined me-1">group</span>{          t('user_group_management.user_group_management') }</>;
+    case 'audit-log':                return <><span className="material-symbols-outlined me-1">feed</span>{            t('audit_log_management.audit_log')}</>;
+    case 'plugins':                  return <><span className="material-symbols-outlined me-1">extension</span>{          t('plugins.plugins')}</>;
+    case 'search':                   return <><span className="material-symbols-outlined me-1">search</span>{       t('full_text_search_management.full_text_search_management') }</>;
+    case 'cloud':                    return <><span className="material-symbols-outlined me-1">share</span>{       t('cloud_setting_management.to_cloud_settings')} </>;
+    default:                         return <><span className="material-symbols-outlined me-1">home</span>{            t('wiki_management_homepage') }</>;
       /* eslint-enable no-multi-spaces, max-len */
   }
 };

+ 2 - 2
apps/app/src/components/Admin/ElasticsearchManagement/StatusTable.jsx

@@ -56,7 +56,7 @@ class StatusTable extends React.PureComponent {
     const aliasLabels = aliases.map((aliasName) => {
       return (
         <span key={`badge-${indexName}-${aliasName}`} className="badge rounded-pill bg-primary me-2">
-          <i className="icon-tag"></i> {aliasName}
+          <span className="material-symbols-outlined">sell</span> {aliasName}
         </span>
       );
     });
@@ -66,7 +66,7 @@ class StatusTable extends React.PureComponent {
         <div className="card-header">
 
           <a role="button" className="text-nowrap me-2" data-bs-toggle="collapse" href={`#${collapseId}`} aria-expanded="true" aria-controls={collapseId}>
-            <i className="fa fa-fw fa-database"></i> {indexName}
+            <span className="material-symbols-outlined">database</span> {indexName}
           </a>
           <span className="ms-md-3">{aliasLabels}</span>
         </div>

+ 3 - 3
apps/app/src/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx

@@ -15,15 +15,15 @@ const ArchiveFilesTableMenu = (props: ArchiveFilesTableMenuProps):JSX.Element =>
   return (
     <div className="btn-group admin-user-menu dropdown">
       <button type="button" className="btn btn-sm btn-outline-secondary 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>
       <ul className="dropdown-menu" role="menu">
         <li className="dropdown-header">{t('admin:export_management.export_menu')}</li>
         <button type="button" className="dropdown-item" onClick={() => { window.location.href = `/admin/export/${props.fileName}` }}>
-          <i className="icon-cloud-download" /> {t('admin:export_management.download')}
+          <span className="material-symbols-outlined">cloud_download</span> {t('admin:export_management.download')}
         </button>
         <button type="button" className="dropdown-item" role="button" onClick={() => props.onZipFileStatRemove(props.fileName)}>
-          <span className="text-danger"><i className="icon-trash" /> {t('admin:export_management.delete')}</span>
+          <span className="text-danger"><span className="material-symbols-outlined">delete</span> {t('admin:export_management.delete')}</span>
         </button>
       </ul>
     </div>

+ 1 - 1
apps/app/src/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx

@@ -151,7 +151,7 @@ export default class ImportCollectionItem extends React.Component {
         disabled={isImporting || !isConfigButtonAvailable}
         onClick={isConfigButtonAvailable ? this.configButtonClickedHandler : null}
       >
-        <i className="icon-settings"></i>
+        <span className="material-symbols-outlined">settings</span>
       </button>
     );
   }

+ 3 - 3
apps/app/src/components/Admin/ImportData/ImportDataPageContents.jsx

@@ -41,12 +41,12 @@ class ImportDataPageContents extends React.Component {
               <tbody>
                 <tr>
                   <th>{t('importer_management.article')}</th>
-                  <th><i className="icon-arrow-right-circle text-success"></i></th>
+                  <th><span className="material-symbols-outlined text-success">arrow_circle_right</span></th>
                   <th>{t('importer_management.page')}</th>
                 </tr>
                 <tr>
                   <th>{t('importer_management.category')}</th>
-                  <th><i className="icon-arrow-right-circle text-success"></i></th>
+                  <th><span className="material-symbols-outlined text-success">arrow_circle_right</span></th>
                   <th>{t('importer_management.page_path')}</th>
                 </tr>
                 <tr>
@@ -143,7 +143,7 @@ class ImportDataPageContents extends React.Component {
               <tbody>
                 <tr>
                   <th>{t('importer_management.article')}</th>
-                  <th><i className="icon-arrow-right-circle text-success"></i></th>
+                  <th><span className="material-symbols-outlined">arrow_circle_right</span></th>
                   <th>{t('importer_management.page')}</th>
                 </tr>
                 <tr>

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

@@ -42,14 +42,14 @@ const LegacySlackIntegration = (props) => {
     <div data-testid="admin-slack-integration-legacy">
       { isDisabled && (
         <div className="alert alert-danger">
-          <i className="icon-minus icon-fw"></i>
+          <span className="material-symbols-outlined">remove</span>
           {/* eslint-disable-next-line react/no-danger */}
           <span dangerouslySetInnerHTML={{ __html: t('admin:slack_integration_legacy.alert_disabled') }}></span>
         </div>
       ) }
 
       <div className="alert alert-warning">
-        <i className="icon-info icon-fw"></i>
+        <span className="material-symbols-outlined">info</span>
         {/* eslint-disable-next-line react/no-danger */}
         <span dangerouslySetInnerHTML={{ __html: t('admin:slack_integration_legacy.alert_deplicated') }}></span>
       </div>

+ 2 - 2
apps/app/src/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx

@@ -102,7 +102,7 @@ class SlackConfiguration extends React.Component {
               <h2 className="border-bottom mb-5">{t('notification_settings.slack_app_configuration')}</h2>
 
               <div className="card custom-card">
-                <span className="text-danger"><i className="icon-fw icon-exclamation"></i>NOT RECOMMENDED</span>
+                <span className="text-danger"><span className="material-symbols-outlined">error</span>NOT RECOMMENDED</span>
                 <br />
                 {/* eslint-disable-next-line react/no-danger */}
                 <span dangerouslySetInnerHTML={{ __html: t('notification_settings.slack_app_configuration_desc') }} />
@@ -140,7 +140,7 @@ class SlackConfiguration extends React.Component {
         <hr />
 
         <h3>
-          <i className="icon-question" aria-hidden="true"></i>{' '}
+          <span className="material-symbols-outlined" aria-hidden="true">help</span>{' '}
           <a href="#collapseHelpForIwh" data-bs-toggle="collapse">{t('notification_settings.how_to.header')}</a>
         </h3>
 

+ 1 - 1
apps/app/src/components/Admin/ManageExternalAccount.tsx

@@ -54,7 +54,7 @@ const ManageExternalAccount = (props: ManageExternalAccountProps): JSX.Element =
           prefetch={false}
           className="btn btn-outline-secondary"
         >
-          <i className="icon-fw ti ti-arrow-left" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">arrow_back</span>
           {t('admin:user_management.back_to_user_management')}
         </Link>
       </p>

+ 9 - 10
apps/app/src/components/Admin/Notification/GlobalNotificationList.jsx

@@ -98,32 +98,32 @@ class GlobalNotificationList extends React.Component {
                 <ul className="list-inline mb-0">
                   {notification.triggerEvents.includes('pageCreate') && (
                     <li className="list-inline-item badge rounded-pill bg-success">
-                      <i className="icon-doc"></i> CREATE
+                      <span className=" material-symbols-outlined">description</span> CREATE
                     </li>
                   )}
                   {notification.triggerEvents.includes('pageEdit') && (
                     <li className="list-inline-item badge rounded-pill bg-warning text-dark">
-                      <i className="icon-pencil"></i> EDIT
+                      <span className="material-symbols-outlined">edit</span> EDIT
                     </li>
                   )}
                   {notification.triggerEvents.includes('pageMove') && (
                     <li className="list-inline-item badge rounded-pill bg-pink">
-                      <i className="icon-action-redo"></i> MOVE
+                      <span className="material-symbols-outlined">redo</span> MOVE
                     </li>
                   )}
                   {notification.triggerEvents.includes('pageDelete') && (
                     <li className="list-inline-item badge rounded-pill bg-danger">
-                      <i className="icon-fire"></i> DELETE
+                      <span className="material-symbols-outlined">delete_forever</span>DELETE
                     </li>
                   )}
                   {notification.triggerEvents.includes('pageLike') && (
                     <li className="list-inline-item badge rounded-pill bg-info">
-                      <i className="fa fa-heart-o"></i> LIKE
+                      <span className="material-symbols-outlined">favorite</span> LIKE
                     </li>
                   )}
                   {notification.triggerEvents.includes('comment') && (
                     <li className="list-inline-item badge rounded-pill bg-primary">
-                      <i className="icon-fw icon-bubble"></i> POST
+                      <span className="material-symbols-outlined">bubble_chart</span> POST
                     </li>
                   )}
                 </ul>
@@ -143,14 +143,14 @@ class GlobalNotificationList extends React.Component {
                     aria-haspopup="true"
                     aria-expanded="false"
                   >
-                    <i className="icon-settings"></i> <span className="caret"></span>
+                    <span className="material-symbols-outlined">settings</span> <span className="caret"></span>
                   </button>
                   <div className="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
                     <a className="dropdown-item" href={urljoin('/admin/global-notification/', notification._id)}>
-                      <i className="icon-fw icon-note"></i> {t('Edit')}
+                      <span className="material-symbols-outlined">note</span> {t('Edit')}
                     </a>
                     <button className="dropdown-item" type="button" onClick={() => this.openConfirmationModal(notification)}>
-                      <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+                      <span className="material-symbols-outlined text-danger">delete_forever</span> {t('Delete')}
                     </button>
                   </div>
                 </div>
@@ -168,7 +168,6 @@ class GlobalNotificationList extends React.Component {
         )}
       </React.Fragment>
     );
-
   }
 
 }

+ 10 - 10
apps/app/src/components/Admin/Notification/ManageGlobalNotification.tsx

@@ -113,7 +113,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
     <>
       <div className="my-3">
         <Link href="/admin/notification" className="btn btn-outline-secondary">
-          <i className="icon-fw ti ti-arrow-left" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">arrow_left_alt</span>
           {t('notification_settings.back_to_list')}
         </Link>
       </div>
@@ -179,7 +179,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
               <>
                 <div className="input-group notify-to-option" id="mail-input">
                   <div>
-                    <span className="input-group-text" id="mail-addon"><i className="ti ti-email" /></span>
+                    <span className="input-group-text" id="mail-addon"></span><span className="material-symbols-outlined">mail</span>
                   </div>
                   <input
                     className="form-control"
@@ -198,7 +198,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
                   {!isMailerSetup && <span className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('admin:mailer_setup_required') }} />}
                   <b>Hint: </b>
                   <a href="https://ifttt.com/create" target="blank">{t('notification_settings.email.ifttt_link')}
-                    <i className="icon-share-alt" />
+                    <span className="material-symbols-outlined">share</span>
                   </a>
                 </p>
               </>
@@ -207,7 +207,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
               <>
                 <div className="input-group notify-to-option" id="slack-input">
                   <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><span className="material-symbols-outlined">tag</span>
                   </div>
                   <input
                     className="form-control"
@@ -238,7 +238,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
                 onChange={() => onChangeTriggerEvents(TriggerEventType.CREATE)}
               >
                 <span className="badge rounded-pill bg-success">
-                  <i className="icon-doc me-1" /> CREATE
+                  <span className="material-symbols-outlined">edit_note</span> CREATE
                 </span>
               </TriggerEventCheckBox>
             </div>
@@ -250,7 +250,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
                 onChange={() => onChangeTriggerEvents(TriggerEventType.EDIT)}
               >
                 <span className="badge rounded-pill bg-warning text-dark">
-                  <i className="icon-pencil me-1" />EDIT
+                  <span className="imaterial-symbols-outlined">edit</span> EDIT
                 </span>
               </TriggerEventCheckBox>
             </div>
@@ -262,7 +262,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
                 onChange={() => onChangeTriggerEvents(TriggerEventType.MOVE)}
               >
                 <span className="badge rounded-pill bg-pink">
-                  <i className="icon-action-redo me-1" />MOVE
+                  <span className="material-symbols-outlined">redo</span>MOVE
                 </span>
               </TriggerEventCheckBox>
             </div>
@@ -274,7 +274,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
                 onChange={() => onChangeTriggerEvents(TriggerEventType.DELETE)}
               >
                 <span className="badge rounded-pill bg-danger">
-                  <i className="icon-fire me-1" />DELETE
+                  <span className="material-symbols-outlined">delete_forever</span>DELETE
                 </span>
               </TriggerEventCheckBox>
             </div>
@@ -286,7 +286,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
                 onChange={() => onChangeTriggerEvents(TriggerEventType.LIKE)}
               >
                 <span className="badge rounded-pill bg-info">
-                  <i className="fa fa-heart-o me-1" />LIKE
+                  <span className="material-symbols-outlined">favorite</span>LIKE
                 </span>
               </TriggerEventCheckBox>
             </div>
@@ -298,7 +298,7 @@ const ManageGlobalNotification = (props: Props): JSX.Element => {
                 onChange={() => onChangeTriggerEvents(TriggerEventType.POST)}
               >
                 <span className="badge rounded-pill bg-primary">
-                  <i className="icon-bubble me-1" />POST
+                  <span className="material-symbols-outlined">language</span>POST
                 </span>
               </TriggerEventCheckBox>
             </div>

+ 2 - 2
apps/app/src/components/Admin/Notification/NotificationDeleteModal.jsx

@@ -13,7 +13,7 @@ class NotificationDeleteModal extends React.PureComponent {
     return (
       <Modal isOpen={this.props.isOpen} toggle={this.props.onClose}>
         <ModalHeader tag="h4" toggle={this.props.onClose} className="bg-danger text-light">
-          <i className="icon icon-fire"></i> Delete Global Notification Setting
+          <span className="material-symbols-outlined">delete_forever</span>Delete Global Notification Setting
         </ModalHeader>
         <ModalBody>
           <p>
@@ -25,7 +25,7 @@ class NotificationDeleteModal extends React.PureComponent {
         </ModalBody>
         <ModalFooter>
           <button type="button" className="btn btn-sm btn-danger" onClick={this.props.onClickSubmit}>
-            <i className="icon icon-fire"></i> {t('Delete')}
+            <span className="material-symbols-outlined">delete_forever</span> {t('Delete')}
           </button>
         </ModalFooter>
       </Modal>

+ 2 - 2
apps/app/src/components/Admin/Notification/NotificationSetting.jsx

@@ -124,11 +124,11 @@ function NotificationSetting(props) {
   const navTabMapping = useMemo(() => {
     return {
       user_trigger_notification: {
-        Icon: () => <i className="icon-settings" />,
+        Icon: () => <span className="material-symbols-outlined">settings</span>,
         i18n: 'User trigger notification',
       },
       global_notification: {
-        Icon: () => <i className="icon-settings" />,
+        Icon: () => <span className="material-symbols-outlined">settings</span>,
         i18n: 'Global notification',
       },
     };

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

@@ -22,7 +22,7 @@ const DeleteAllShareLinksModal = React.memo((props) => {
     <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
       <ModalHeader tag="h4" toggle={closeButtonHandler} className="bg-danger text-light">
         <span>
-          <i className="icon-fw icon-fire"></i>
+          <span className="material-symbols-outlined">delete_forever</span>
           {t('security_settings.delete_all_share_links')}
         </span>
       </ModalHeader>
@@ -32,7 +32,7 @@ const DeleteAllShareLinksModal = React.memo((props) => {
       <ModalFooter>
         <Button onClick={closeButtonHandler}>{t('Cancel')}</Button>
         <Button color="danger" onClick={deleteAllLinkHandler}>
-          <i className="icon icon-fire"></i>
+          <span className="material-symbols-outlined">delete_forever</span>
           {t('Delete')}
         </Button>
       </ModalFooter>

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

@@ -175,7 +175,7 @@ class GoogleSecurityManagementContents extends React.Component {
 
         <div style={{ minHeight: '300px' }}>
           <h4>
-            <i className="icon-question" aria-hidden="true"></i>
+            <span className="material-symbols-outlined" aria-hidden="true">help</span>
             <a href="#collapseHelpForGoogleOauth" data-bs-toggle="collapse"> {t('security_settings.OAuth.how_to.google')}</a>
           </h4>
           <ol id="collapseHelpForGoogleOauth" className="collapse">

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

@@ -449,7 +449,7 @@ class OidcSecurityManagementContents extends React.Component {
 
         <div style={{ minHeight: '300px' }}>
           <h4>
-            <i className="icon-question" aria-hidden="true" />
+            <span className="material-symbols-outlined" aria-hidden="true">help</span>
             <a href="#collapseHelpForOidcOauth" data-bs-toggle="collapse"> {t('security_settings.OAuth.how_to.oidc')}</a>
           </h4>
           <ol id="collapseHelpForOidcOauth" className="collapse">

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

@@ -24,13 +24,13 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
         <span>
           {props.isResetAll && (
             <>
-              <i className="icon-fw icon-fire" />
+              <span className="material-symbols-outlined">delete_forever</span>
               {t('admin:slack_integration.reset_all_settings')}
             </>
           )}
           {!props.isResetAll && (
             <>
-              <i className="icon-trash me-1" />
+              <span className="material-symbols-outlined">delete</span>
               {t('admin:slack_integration.delete_slackbot_settings')}
             </>
           )}
@@ -55,13 +55,13 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
         <Button color="danger" onClick={deleteSlackCredentialsHandler}>
           {props.isResetAll && (
             <>
-              <i className="icon icon-fire"></i>
+              <span className="material-symbols-outlined">delete_forever</span>
               {t('admin:slack_integration.reset')}
             </>
           )}
           {!props.isResetAll && (
             <>
-              <i className="icon-trash me-1" />
+              <span className="material-symbols-outlined">delete</span>
               {t('admin:slack_integration.delete')}
             </>
           )}

+ 1 - 1
apps/app/src/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx

@@ -46,7 +46,7 @@ export const SlackAppIntegrationControl: FC<Props> = (props: Props) => {
           }
         }}
       >
-        <i className="icon-trash me-1" />
+        <span className="material-symbols-outlined">delete</span>
         {t('admin:slack_integration.delete')}
       </button>
     </div>

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

@@ -184,7 +184,7 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   return (
     <Modal className="modal-md" isOpen={props.isShow} toggle={toggleHandler}>
       <ModalHeader tag="h4" toggle={toggleHandler} className="bg-danger text-light">
-        <i className="icon icon-fire"></i> {t('admin:user_group_management.delete_modal.header')}
+        <span className="material-symbols-outlined">delete_forever</span> {t('admin:user_group_management.delete_modal.header')}
       </ModalHeader>
       <ModalBody>
         <div>
@@ -201,7 +201,7 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
             {renderGroupSelector()}
           </div>
           <button type="submit" value="" className="btn btn-sm btn-danger text-nowrap" disabled={!validateForm()}>
-            <i className="icon icon-fire"></i> {t('Delete')}
+            <span className="material-symbols-outlined">delete_forever</span> {t('Delete')}
           </button>
         </form>
       </ModalFooter>

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

@@ -219,7 +219,7 @@ export const UserGroupTable: FC<Props> = ({
                             </button>
                           )}
                           <button className="dropdown-item" type="button" role="button" onClick={onClickDelete} data-user-group-id={group._id}>
-                            <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+                            <span className="material-symbols-outlined text-danger">delete_forever</span> {t('Delete')}
                           </button>
                         </div>
                       </div>

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

@@ -128,7 +128,7 @@ const UserManagement = (props: UserManagementProps) => {
           className="btn btn-outline-secondary ms-2"
           role="button"
         >
-          <i className="icon-user-follow me-1" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">person_add</span>
           {t('admin:user_management.external_account')}
         </Link>
       </p>
@@ -138,7 +138,7 @@ const UserManagement = (props: UserManagementProps) => {
 
         <div className="row d-flex justify-content-start align-items-center my-2">
           <div className="col-md-3 d-flex align-items-center my-2">
-            <i className="icon-magnifier me-1"></i>
+            <span className="material-symbols-outlined">search</span>
             <span className={`search-typeahead ${styles['search-typeahead']}`}>
               <input
                 className="w-100"
@@ -183,7 +183,7 @@ const UserManagement = (props: UserManagementProps) => {
               className="btn btn-outline-secondary btn-sm"
               onClick={resetButtonClickHandler}
             >
-              <span className="icon-refresh me-1"></span>
+              <span className="material-symbols-outlined">refresh</span>
               {t('commons:Reset')}
             </button>
           </div>

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

@@ -102,7 +102,7 @@ const ExternalAccountTable = (props: ExternalAccountTableProps): JSX.Element =>
                         role="button"
                         onClick={() => removeExtenalAccount(ea._id)}
                       >
-                        <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+                        <span className="material-symbols-outlined text-danger">delete_forever</span> {t('Delete')}
                       </button>
                     </ul>
                   </div>

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

@@ -34,7 +34,7 @@ class UserRemoveButton extends React.Component {
 
     return (
       <button className="dropdown-item" type="button" onClick={() => { this.onClickDeleteBtn() }}>
-        <i className="icon-fw icon-fire text-danger"></i> {t('Delete')}
+        <span className="material-symbols-outlined text-danger">delete_forever</span> {t('Delete')}
       </button>
     );
   }

+ 2 - 2
apps/app/src/components/AlertSiteUrlUndefined.tsx

@@ -28,10 +28,10 @@ export const AlertSiteUrlUndefined = (): JSX.Element => {
 
   return (
     <div className="alert alert-danger rounded-0 d-edit-none mb-0 px-4 py-2">
-      <i className="icon-exclamation"></i>
+      <span className="material-symbols-outlined">error</span>
       {
         t('alert.siteUrl_is_not_set', { link: t('headers.app_settings') })
-      } &gt;&gt; <a href="/admin/app">{t('headers.app_settings')}<i className="icon-login"></i></a>
+      } &gt;&gt; <a href="/admin/app">{t('headers.app_settings')}<span className="material-symbols-outlined">login</span></a>
     </div>
   );
 };

+ 2 - 2
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -266,7 +266,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
               >
                 <div onClick={e => e.stopPropagation()}>
                   <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">more_vert</span>
                   </DropdownToggle>
                 </div>
               </BookmarkFolderItemControl>
@@ -278,7 +278,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
                   className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
                   onClick={onClickPlusButton}
                 >
-                  <i className="icon-plus d-block p-0" />
+                  <span className="material-symbols-outlined">add_circle</span>
                 </button>
               )}
             </div>

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

@@ -53,7 +53,7 @@ export const BookmarkFolderItemControl: React.FC<{
           className="pt-2 grw-page-control-dropdown-item text-danger"
           onClick={onClickDelete}
         >
-          <i className="icon-fw icon-trash grw-page-control-dropdown-icon"></i>
+          <span className="material-symbols-outlined grw-page-control-dropdown-icon">delete</span>
           {t('Delete')}
         </DropdownItem>
       </DropdownMenu>

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

@@ -41,7 +41,7 @@ export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkPro
         <RootElm>
           <span className="path-segment">
             <Link href="/trash" prefetch={false}>
-              <i className="icon-trash"></i>
+              <span className="material-symbols-outlined">delete</span>
             </Link>
           </span>
           <span className={`separator ${styles.separator}`}><a href="/">/</a></span>

+ 5 - 0
apps/app/src/components/Common/PageViewLayout.module.scss

@@ -45,6 +45,11 @@ $page-view-layout-margin-top: 32px;
       min-width: 250px;
       margin-left: 30px;
     }
+
+    @include bs.media-breakpoint-down(sm) {
+      position: fixed;
+      right: 1rem;
+    }
   }
 }
 

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

@@ -14,7 +14,7 @@ export const CompleteUserRegistration: FC = () => {
           </p>
           {/* If the transition source is "/login", use <a /> tag since the transition will not occur if next/link is used. */}
           <a href="/login">
-            <i className="icon-login me-1" />{t('Sign in is here')}
+            <span className="material-symbols-outlined">login</span>{t('Sign in is here')}
           </a>
         </div>
       </div>

+ 6 - 6
apps/app/src/components/CompleteUserRegistrationForm.tsx

@@ -111,12 +111,12 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               <input type="hidden" name="token" value={token} />
 
               <div className="input-group">
-                <span className="input-group-text"><i className="icon-envelope"></i></span>
+                <span className="input-group-text"></span><span className="material-symbols-outlined">mail</span>
                 <input type="text" className="form-control" placeholder={t('Email')} disabled value={email} />
               </div>
 
               <div className="input-group" id="input-group-username">
-                <span className="input-group-text"><i className="icon-user"></i></span>
+                <span className="input-group-text"></span><span className="material-symbols-outlined">person</span>
                 <input
                   type="text"
                   className="form-control"
@@ -129,12 +129,12 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
               {!usernameAvailable && (
                 <p className="form-text text-red">
-                  <span id="help-block-username"><i className="icon-fw icon-ban"></i>{t('installer.unavaliable_user_id')}</span>
+                  <span id="help-block-username"><span className="material-symbols-outlined">block</span>{t('installer.unavaliable_user_id')}</span>
                 </p>
               )}
 
               <div className="input-group">
-                <span className="input-group-text"><i className="icon-tag"></i></span>
+                <span className="input-group-text"></span><span className="material-symbols-outlined">sell</span>
                 <input
                   type="text"
                   className="form-control"
@@ -148,7 +148,7 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
 
               <div className="input-group">
-                <span className="input-group-text"><i className="icon-lock"></i></span>
+                <span className="input-group-text"></span><span className="material-symbols-outlined">lock</span>
                 <input
                   type="password"
                   className="form-control"
@@ -164,7 +164,7 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               <div className="input-group justify-content-center d-flex mt-5">
                 <button type="button" disabled={forceDisableForm || disableForm} className="btn btn-fill" id="register">
                   <div className="eff"></div>
-                  <span className="btn-label"><i className="icon-user-follow"></i></span>
+                  <span className="btn-label"></span><span className="material-symbols-outlined">person_add</span>
                   <span className="btn-label-text">{t('Create')}</span>
                 </button>
               </div>

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

@@ -15,7 +15,7 @@ const BookMarkLinkButton = React.memo(() => {
         type="button"
         className="btn btn-outline-secondary btn-sm px-2"
       >
-        <i className="fa fa-fw fa-bookmark-o"></i>
+        <span className="material-symbols-outlined">bookmark</span>
         <span>Bookmarks</span>
       </button>
     </ScrollLink>

+ 2 - 2
apps/app/src/components/DeleteBookmarkFolderModal.tsx

@@ -42,7 +42,7 @@ const DeleteBookmarkFolderModal: FC = () => {
   return (
     <Modal size="md" isOpen={isOpened} toggle={closeBookmarkFolderDeleteModal} data-testid="page-delete-modal" className="grw-create-page">
       <ModalHeader tag="h4" toggle={closeBookmarkFolderDeleteModal} className="bg-danger text-light">
-        <i className="icon-fw icon-trash"></i>
+        <span className="material-symbols-outlined">delete</span>
         {t('bookmark_folder.delete_modal.modal_header_label')}
       </ModalHeader>
       <ModalBody>
@@ -58,7 +58,7 @@ const DeleteBookmarkFolderModal: FC = () => {
           className="btn btn-danger"
           onClick={onClickDeleteButton}
         >
-          <i className="me-1 icon-trash" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">delete</span>
           {t('bookmark_folder.delete_modal.modal_footer_button')}
         </button>
       </ModalFooter>

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

@@ -24,7 +24,7 @@ const EmptyTrashButton = (props: EmptyTrashButtonProps): JSX.Element => {
         disabled={disableEmptyButton}
         onClick={emptyTrashButtonHandler}
       >
-        <i className="icon-fw icon-trash"></i>
+        <span className="material-symbols-outlined">delete</span>
         <div>{t('modal_empty.empty_the_trash')}</div>
       </button>
     </div>

+ 2 - 2
apps/app/src/components/EmptyTrashModal.tsx

@@ -61,7 +61,7 @@ const EmptyTrashModal: FC = () => {
   return (
     <Modal size="lg" isOpen={isOpened} toggle={closeEmptyTrashModal} data-testid="page-delete-modal">
       <ModalHeader tag="h4" toggle={closeEmptyTrashModal} className="bg-danger text-light">
-        <i className="icon-fw icon-fire"></i>
+        <span className="material-symbols-outlined">delete_forever</span>
         {t('modal_empty.empty_the_trash')}
       </ModalHeader>
       <ModalBody>
@@ -80,7 +80,7 @@ const EmptyTrashModal: FC = () => {
           className="btn btn-danger"
           onClick={emptyTrashButtonHandler}
         >
-          <i className="me-1 icon-fire" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">delete_forever</span>
           {t('modal_empty.empty_the_trash_button')}
         </button>
       </ModalFooter>

+ 2 - 2
apps/app/src/components/ForbiddenPage.tsx

@@ -14,7 +14,7 @@ const ForbiddenPage = React.memo((props: Props): JSX.Element => {
       <div className="row not-found-message-row mb-4">
         <div className="col-lg-12">
           <h2 className="text-muted">
-            <i className="icon-ban me-2" aria-hidden="true" />
+            <span className="material-symbols-outlined" aria-hidden="true">block</span>
             Forbidden
           </h2>
         </div>
@@ -23,7 +23,7 @@ const ForbiddenPage = React.memo((props: Props): JSX.Element => {
       <div className="row row-alerts d-edit-none">
         <div className="col-sm-12">
           <p className="alert alert-primary py-3 px-4">
-            <i className="icon-fw icon-lock" aria-hidden="true" />
+            <span className="material-symbols-outlined" aria-hidden="true">lock</span>
             { props.isLinkSharingDisabled ? t('share_links.link_sharing_is_disabled') : t('Browsing of this page is restricted')}
           </p>
         </div>

+ 1 - 1
apps/app/src/components/InAppNotification/InAppNotificationDropdown.tsx

@@ -84,7 +84,7 @@ export const InAppNotificationDropdown = (): JSX.Element => {
   return (
     <Dropdown className="notification-wrapper grw-notification-dropdown" isOpen={isOpen} toggle={toggleDropdownHandler} direction="end">
       <DropdownToggle className="px-3" color="primary" innerRef={buttonRef}>
-        <i className="icon-bell" /> {badge}
+        <span className="material-symbols-outlined">notifications</span> {badge}
       </DropdownToggle>
       <DropdownMenu end>
         { inAppNotificationData != null && inAppNotificationData.docs.length === 0

+ 6 - 6
apps/app/src/components/InstallerForm.tsx

@@ -84,7 +84,7 @@ const InstallerForm = memo((): JSX.Element => {
   const hasErrorClass = isValidUserName ? '' : ' has-error';
   const unavailableUserId = isValidUserName
     ? ''
-    : <span><i className="icon-fw icon-ban" />{ t('installer.unavaliable_user_id') }</span>;
+    : <span><span className="material-symbols-outlined">block</span>{ t('installer.unavaliable_user_id') }</span>;
 
   return (
     <div data-testid="installerForm" className={`nologin-dialog p-3 mx-auto${hasErrorClass}`}>
@@ -100,7 +100,7 @@ const InstallerForm = memo((): JSX.Element => {
         <form role="form" id="register-form" className="col-md-12" onSubmit={submitHandler}>
           <div className="dropdown mb-3">
             <div className="input-group dropdown-with-icon">
-              <span className="input-group-text"><i className="icon-bubbles" /></span>
+              <span className="input-group-text"></span><span className="material-symbols-outlined">language</span>
               <button
                 type="button"
                 className="btn btn-secondary dropdown-toggle form-control text-end rounded-end"
@@ -145,7 +145,7 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className={`input-group mb-3${hasErrorClass}`}>
-            <span className="input-group-text"><i className="icon-user" /></span>
+            <span className="input-group-text"></span><span className="material-symbols-outlined">person</span>
             <input
               data-testid="tiUsername"
               type="text"
@@ -159,7 +159,7 @@ const InstallerForm = memo((): JSX.Element => {
           <p className="form-text">{ unavailableUserId }</p>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"><i className="icon-tag" /></span>
+            <span className="input-group-text"></span><span className="material-symbols-outlined">sell</span>
             <input
               data-testid="tiName"
               type="text"
@@ -171,7 +171,7 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"><i className="icon-envelope" /></span>
+            <span className="input-group-text"></span><span className="material-symbols-outlined">mail</span>
             <input
               data-testid="tiEmail"
               type="email"
@@ -183,7 +183,7 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"><i className="icon-lock" /></span>
+            <span className="input-group-text"></span> <span className="material-symbols-outlined">lock</span>
             <input
               data-testid="tiPassword"
               type="password"

+ 4 - 4
apps/app/src/components/InvitedForm.tsx

@@ -83,7 +83,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         {/* Email Form */}
         <div className="input-group">
           <span className="input-group-text">
-            <i className="icon-envelope"></i>
+            <span className="material-symbols-outlined">mail</span>
           </span>
           <input
             type="text"
@@ -98,7 +98,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         {/* UserID Form */}
         <div className="input-group" id="input-group-username">
           <span className="input-group-text">
-            <i className="icon-user"></i>
+            <span className="material-symbols-outlined">person</span>
           </span>
           <input
             type="text"
@@ -112,7 +112,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         {/* Name Form */}
         <div className="input-group">
           <span className="input-group-text">
-            <i className="icon-tag"></i>
+            <span className="material-symbols-outlined">sell</span>
           </span>
           <input
             type="text"
@@ -126,7 +126,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         {/* Password Form */}
         <div className="input-group">
           <span className="input-group-text">
-            <i className="icon-lock"></i>
+            <span className="material-symbols-outlined">lock</span>
           </span>
           <input
             type="password"

+ 16 - 14
apps/app/src/components/LoginForm.tsx

@@ -181,12 +181,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
         {/* !! - DO NOT DELETE HIDDEN ELEMENT - !! -- 7.12 ryoji-s */}
         {/* Import font-awesome to prevent MongoStore.js "Unable to find the session to touch" error */}
         <div className="visually-hidden">
+          {/* Unsettled 11.17 meiri-k */}
           <i className="fa fa-spinner fa-pulse" />
         </div>
         {/* !! - END OF HIDDEN ELEMENT - !! */}
         {isLdapSetupFailed && (
           <div className="alert alert-warning small">
-            <strong><i className="icon-fw icon-info"></i>{t('login.enabled_ldap_has_configuration_problem')}</strong><br />
+            <strong><span className="material-symbols-outlined">info</span>{t('login.enabled_ldap_has_configuration_problem')}</strong><br />
             <span dangerouslySetInnerHTML={{ __html: t('login.set_env_var_for_logs') }}></span>
           </div>
         )}
@@ -196,7 +197,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
         <form role="form" onSubmit={handleLoginWithLocalSubmit} id="login-form">
           <div className="input-group">
             <span className="input-group-text">
-              <i className="icon-user"></i>
+              <span className="material-symbols-outlined">person</span>
             </span>
             <input
               type="text"
@@ -208,14 +209,14 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
             />
             {isLdapStrategySetup && (
               <small className="input-group-text text-success">
-                <i className="icon-fw icon-check"></i> LDAP
+                <span className="material-symbols-outlined">select_check_box</span>LDAP
               </small>
             )}
           </div>
 
           <div className="input-group">
             <span className="input-group-text">
-              <i className="icon-lock"></i>
+              <span className="material-symbols-outlined">lock</span>
             </span>
             <input
               type="password"
@@ -237,7 +238,8 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
             >
               <div className="eff"></div>
               <span className="btn-label">
-                <i className={isLoading ? 'fa fa-spinner fa-pulse me-1' : 'icon-login'} />
+                {/* spinner.Tentative decision meiri-k 11.17 */}
+                <span className="material-symbols-outlined">{isLoading ? 'hoge' : 'login'}</span>
               </span>
               <span className="btn-label-text">{t('Sign in')}</span>
             </button>
@@ -416,7 +418,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
             <div>
               <div className="input-group" id="input-group-username">
                 <span className="input-group-text">
-                  <i className="icon-user"></i>
+                  <span className="material-symbols-outlined">person</span>
                 </span>
                 {/* username */}
                 <input
@@ -434,7 +436,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
               </p>
               <div className="input-group">
                 <span className="input-group-text">
-                  <i className="icon-tag"></i>
+                  <span className="material-symbols-outlined">sell</span>
                 </span>
                 {/* name */}
                 <input
@@ -452,7 +454,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
           <div className="input-group">
             <span className="input-group-text">
-              <i className="icon-envelope"></i>
+              <span className="material-symbols-outlined">mail</span>
             </span>
             {/* email */}
             <input
@@ -486,7 +488,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
             <div>
               <div className="input-group">
                 <span className="input-group-text">
-                  <i className="icon-lock"></i>
+                  <span className="material-symbols-outlined">lock</span>
                 </span>
                 {/* Password */}
                 <input
@@ -511,7 +513,8 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
             >
               <div className="eff"></div>
               <span className="btn-label">
-                <i className={isLoading ? 'fa fa-spinner fa-pulse me-1' : 'icon-user-follow'} />
+                {/* spinner.Tentative decision meiri-k 11.17 */}
+                <span className="material-symbols-outlined">{isLoading ? 'hoge' : 'login'}</span>
               </span>
               <span className="btn-label-text">{submitText}</span>
             </button>
@@ -529,8 +532,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
               style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
               onClick={switchForm}
             >
-              <i className="icon-fw icon-login"></i>
-              {t('Sign in is here')}
+              <span className="material-symbols-outlined">login</span>{t('Sign in is here')}
             </a>
           </div>
         </div>
@@ -557,7 +559,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 {isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
                   <div className="text-end mb-2">
                     <a href="/forgot-password" className="d-block link-switch">
-                      <i className="icon-key"></i> {t('forgot_password.forgot_password')}
+                      <span className="material-symbols-outlined">vpn_key</span>{t('forgot_password.forgot_password')}
                     </a>
                   </div>
                 )}
@@ -571,7 +573,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                       style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
                       onClick={switchForm}
                     >
-                      <i className="ti ti-check-box"></i> {t('Sign up is here')}
+                      <span className="material-symbols-outlined">check_box</span> {t('Sign up is here')}
                     </a>
                   </div>
                 )}

+ 1 - 1
apps/app/src/components/Me/PersonalSettings.jsx

@@ -40,7 +40,7 @@ const PersonalSettings = () => {
         i18n: t('API Settings'),
       },
       // editor_settings: {
-      //   Icon: () => <i className="icon-fw icon-pencil"></i>,
+      //   Icon: () => <span className="material-symbols-outlined">edit</span>,
       //   Content: EditorSettings,
       //   i18n: t('editor_settings.editor_settings'),
       // },

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

@@ -9,7 +9,7 @@ export const NotCreatablePage: FC = () => {
     <div className="row not-found-message-row">
       <div className="col-md-12">
         <h2 className="text-muted">
-          <i className="icon-ban me-1" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">block</span>
           { t('not_creatable_page.message') }
         </h2>
       </div>

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx

@@ -54,7 +54,7 @@ const ShareLinkTr = (props: ShareLinkTrProps): JSX.Element => {
       </td>
       <td style={{ maxWidth: '0', textAlign: 'center' }}>
         <button className="btn btn-outline-warning" type="button" onClick={onDelete}>
-          <i className="icon-trash"></i>{t('Delete')}
+          <span className="material-symbols-outlined">delete</span>{t('Delete')}
         </button>
       </td>
     </tr>

+ 2 - 2
apps/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -99,7 +99,7 @@ export const TrashPageAlert = (): JSX.Element => {
           disabled={!(pageInfo?.isAbleToDeleteCompletely ?? false)}
           onClick={openPageDeleteModalHandler}
         >
-          <i className="icon-fire" aria-hidden="true"></i> {t('Delete Completely')}
+          <span className="material-symbols-outlined" aria-hidden="true">delete_forever</span> {t('Delete Completely')}
         </button>
       </>
     );
@@ -114,7 +114,7 @@ export const TrashPageAlert = (): JSX.Element => {
     <>
       <div className="alert alert-warning py-3 ps-4 d-flex flex-column flex-lg-row" data-testid="trash-page-alert">
         <div className="flex-grow-1">
-          This page is in the trash <i className="icon-trash" aria-hidden="true"></i>.
+          This page is in the trash <span className="material-symbols-outlined" aria-hidden="true">delete</span>.
           <br />
           <UserPicture user={deleteUser} />
           <span className="ms-2">

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

@@ -178,7 +178,7 @@ export const PageComment: FC<PageCommentProps> = memo((props: PageCommentProps):
                           className="btn-comment-reply"
                           onClick={() => onReplyButtonClickHandler(comment._id)}
                         >
-                          <i className="icon-fw icon-action-undo"></i> Reply
+                          <span className="material-symbols-outlined">replay</span> Reply
                         </Button>
                       </NotAvailableForReadOnlyUser>
                     </NotAvailableForGuest>

+ 1 - 1
apps/app/src/components/PageComment/CommentControl.tsx

@@ -14,7 +14,7 @@ export const CommentControl = (props: CommentControlProps): JSX.Element => {
     // The page-comment-control class is imported from Comment.module.scss
     <div className="page-comment-control">
       <button type="button" className="btn btn-link p-2" onClick={onClickEditBtn}>
-        <i className="ti ti-pencil"></i>
+        <span className="material-symbols-outlined">edit</span>
       </button>
       <button
         data-testid="comment-delete-button"

+ 2 - 2
apps/app/src/components/PageComment/DeleteCommentModal.tsx

@@ -32,7 +32,7 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): JSX.Element
     }
     return (
       <span>
-        <i className="icon-fw icon-fire"></i>
+        <span className="material-symbols-outlined">delete_forever</span>
         Delete comment?
       </span>
     );
@@ -73,7 +73,7 @@ export const DeleteCommentModal = (props: DeleteCommentModalProps): JSX.Element
         <span className="text-danger">{errorMessage}</span>&nbsp;
         <Button onClick={cancelToDelete}>Cancel</Button>
         <Button color="danger" onClick={confirmToDelete}>
-          <i className="icon icon-fire"></i>
+          <span className="material-symbols-outlined">delete_forever</span>
           Delete
         </Button>
       </>

+ 3 - 3
apps/app/src/components/PageCreateModal.jsx

@@ -194,7 +194,7 @@ const PageCreateModal = () => {
                 className="grw-btn-create-page btn btn-outline-primary rounded-pill text-nowrap ms-3"
                 onClick={createTodayPage}
               >
-                <i className="icon-fw icon-doc"></i>{t('Create')}
+                <span className="material-symbols-outlined">description</span>{t('Create')}
               </button>
             </div>
 
@@ -248,7 +248,7 @@ const PageCreateModal = () => {
                 onClick={createInputPage}
                 disabled={isMatchedWithUserHomepagePath}
               >
-                <i className="icon-fw icon-doc"></i>{t('Create')}
+                <span className="material-symbols-outlined">description</span>{t('Create')}
               </button>
             </div>
 
@@ -303,7 +303,7 @@ const PageCreateModal = () => {
                 onClick={createTemplatePage}
                 disabled={template == null}
               >
-                <i className="icon-fw icon-doc"></i>{t('Edit')}
+                <span className="material-symbols-outlined">description</span>{t('Edit')}
               </button>
             </div>
 

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

@@ -218,7 +218,7 @@ const PageDeleteModal: FC = () => {
         {!isAbleToDeleteCompletely
         && (
           <p className="alert alert-warning p-2 my-0">
-            <i className="icon-ban icon-fw"></i>{ t('modal_delete.delete_completely_restriction') }
+            <span className="material-symbols-outlined">block</span>{ t('modal_delete.delete_completely_restriction') }
           </p>
         )}
       </div>

+ 7 - 15
apps/app/src/components/PageHistory/PageRevisionTable.tsx

@@ -2,7 +2,7 @@ import React, {
   useEffect, useRef, useState,
 } from 'react';
 
-import type { IRevisionHasId, IRevisionHasPageId } from '@growi/core';
+import type { IRevisionHasPageId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
 import { useSWRxInfinitePageRevisions } from '~/stores/page';
@@ -96,27 +96,19 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
   }, [isLoadingMore, isReachingEnd, setSize, size]);
 
 
-  const onChangeSourceInvoked: React.Dispatch<React.SetStateAction<IRevisionHasId | undefined>> = (revision: IRevisionHasPageId) => {
-    setSourceRevision(revision);
-  };
-  const onChangeTargetInvoked: React.Dispatch<React.SetStateAction<IRevisionHasId | undefined>> = (revision: IRevisionHasPageId) => {
-    setTargetRevision(revision);
-  };
-
-
   const renderRow = (revision: IRevisionHasPageId, previousRevision: IRevisionHasPageId, latestRevision: IRevisionHasPageId,
       isOldestRevision: boolean, hasDiff: boolean) => {
 
     const revisionId = revision._id;
 
     const handleCompareLatestRevisionButton = () => {
-      onChangeSourceInvoked(revision);
-      onChangeTargetInvoked(latestRevision);
+      setSourceRevision(revision);
+      setTargetRevision(latestRevision);
     };
 
     const handleComparePreviousRevisionButton = () => {
-      onChangeSourceInvoked(previousRevision);
-      onChangeTargetInvoked(revision);
+      setSourceRevision(previousRevision);
+      setTargetRevision(revision);
     };
 
     return (
@@ -165,7 +157,7 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
                 name="compareSource"
                 value={revisionId}
                 checked={revisionId === sourceRevision?._id}
-                onChange={() => onChangeSourceInvoked(revision)}
+                onChange={() => setSourceRevision(revision)}
               />
               <label className="form-label form-check-label" htmlFor={`compareSource-${revisionId}`} />
             </div>
@@ -181,7 +173,7 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
                 name="compareTarget"
                 value={revisionId}
                 checked={revisionId === targetRevision?._id}
-                onChange={() => onChangeTargetInvoked(revision)}
+                onChange={() => setTargetRevision(revision)}
               />
               <label className="form-label form-check-label" htmlFor={`compareTarget-${revisionId}`} />
             </div>

+ 3 - 3
apps/app/src/components/PageSelectModal/TreeItemForModal.tsx

@@ -9,7 +9,7 @@ type PageTreeItemProps = Omit<SimpleItemProps, Optional> & {key};
 
 export const TreeItemForModal: FC<PageTreeItemProps> = (props) => {
 
-  const { NewPageInputWrapper, NewPageCreateButtonWrapper } = useNewPageInput();
+  const { Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
 
   return (
     <SimpleItem
@@ -22,9 +22,9 @@ export const TreeItemForModal: FC<PageTreeItemProps> = (props) => {
       onRenamed={props.onRenamed}
       onClickDuplicateMenuItem={props.onClickDuplicateMenuItem}
       onClickDeleteMenuItem={props.onClickDeleteMenuItem}
-      customNextComponents={[NewPageInputWrapper]}
+      customNextComponents={[NewPageInput]}
       itemClass={TreeItemForModal}
-      customEndComponents={[SimpleItemTool, NewPageCreateButtonWrapper]}
+      customEndComponents={[SimpleItemTool, NewPageCreateButton]}
     />
   );
 };

+ 6 - 0
apps/app/src/components/PageSideContents/PageAccessoriesControl.module.scss

@@ -12,6 +12,12 @@
   }
 }
 
+@include bs.media-breakpoint-down(sm) {
+  .btn-page-accessories :global {
+    box-shadow: 0px 3px 6px rgba(black, 0.15);
+  }
+}
+
 // apply larger font when smaller than lg
 @include bs.media-breakpoint-down(lg) {
   .btn-page-accessories :global {

+ 5 - 5
apps/app/src/components/PageStatusAlert.tsx

@@ -52,12 +52,12 @@ export const PageStatusAlert = (): JSX.Element => {
   //     additionalClasses: ['bg-success', 'd-hackmd-none'],
   //     label:
   // <>
-  //   <i className="icon-fw icon-people"></i>
+  //   <span className="material-symbols-outlined">person</span>
   //   {t('hackmd.someone_editing')}
   // </>,
   //     btn:
   // <a href="#hackmd" key="btnOpenHackmdSomeoneEditing" className="btn btn-outline-white">
-  //   <i className="fa fa-fw fa-file-text-o me-1"></i>
+  //   <span class="material-symbols-outlined">description</span>
   //   Open HackMD Editor
   // </a>,
   //   };
@@ -76,13 +76,13 @@ export const PageStatusAlert = (): JSX.Element => {
       additionalClasses: ['bg-warning text-dark'],
       label:
   <>
-    <i className="icon-fw icon-bulb"></i>
+    <span className="material-symbols-outlined">lightbulb</span>
     {label1}
   </>,
       btn:
   <>
     <button type="button" onClick={() => refreshPage()} className="btn btn-outline-white me-4">
-      <i className="icon-fw icon-reload me-1"></i>
+      <span className="material-symbols-outlined">refresh</span>
       {t('Load latest')}
     </button>
     { isConflict && (
@@ -91,7 +91,7 @@ export const PageStatusAlert = (): JSX.Element => {
         onClick={onClickResolveConflict}
         className="btn btn-outline-white"
       >
-        <i className="fa fa-fw fa-file-text-o me-1"></i>
+        <span className="material-symbols-outlined">description</span>
         {t('modal_resolve_conflict.resolve_conflict')}
       </button>
     )}

+ 14 - 5
apps/app/src/components/PageTags/PageTags.tsx

@@ -31,11 +31,20 @@ export const PageTags:FC<Props> = (props: Props) => {
   return (
     <>
       <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center ${printNoneClass}`} data-testid="grw-tag-labels">
-        <RenderTagLabels
-          tags={tags}
-          isTagLabelsDisabled={isTagLabelsDisabled}
-          onClickEditTagsButton={onClickEditTagsButton}
-        />
+        <button
+          type="button"
+          className={`btn btn-sm btn-outline-secondary rounded-pill mb-2 d-flex d-lg-none ${styles['grw-tag-icon-button']}`}
+          onClick={onClickEditTagsButton}
+        >
+          <span className="material-symbols-outlined">local_offer</span>
+        </button>
+        <div className="d-none d-lg-flex">
+          <RenderTagLabels
+            tags={tags}
+            isTagLabelsDisabled={isTagLabelsDisabled}
+            onClickEditTagsButton={onClickEditTagsButton}
+          />
+        </div>
       </div>
     </>
   );

+ 10 - 0
apps/app/src/components/PageTags/TagLabels.module.scss

@@ -8,6 +8,9 @@ $grw-tag-label-font-size: 12px;
     font-weight: normal;
     border-radius: bs.$border-radius;
   }
+  .material-symbols-outlined {
+    font-size: 2em;
+  }
 }
 
 
@@ -16,3 +19,10 @@ $grw-tag-label-font-size: 12px;
   height: calc(#{$grw-tag-label-font-size} + #{bs.$badge-padding-y} * 2);
   font-size: $grw-tag-label-font-size; // set font-size to use the same em value in bs.$badge-padding-y(https://getbootstrap.jp/docs/5.0/components/badge/#variables)
 }
+
+.grw-tag-icon-button {
+  padding: 6px 8px;
+  @include bs.media-breakpoint-down(sm) {
+    box-shadow: 0px 3px 6px rgba(black, 0.15);
+  }
+}

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

@@ -81,7 +81,7 @@ const PasswordResetExecutionForm: FC = () => {
         <input name="reset-password-btn" className="btn btn-lg btn-primary" value={t('forgot_password.reset_password')} type="submit" />
       </div>
       <Link href="/login" prefetch={false}>
-        <i className="icon-login me-1"></i>{t('forgot_password.sign_in_instead')}
+        <span className="material-symbols-outlined">login</span>{t('forgot_password.sign_in_instead')}
       </Link>
     </form>
   );

+ 3 - 2
apps/app/src/components/PasswordResetRequestForm.tsx

@@ -40,7 +40,8 @@ const PasswordResetRequestForm: FC = () => {
         </div>
       ) : (
         <>
-          <h1><i className="icon-lock large"></i></h1>
+          {/* lock-icon large */}
+          <h1><span className="material-symbols-outlined">lock</span></h1>
           <h1 className="text-center">{ t('forgot_password.forgot_password') }</h1>
           <h3>{t('forgot_password.password_reset_request_desc')}</h3>
           <div>
@@ -67,7 +68,7 @@ const PasswordResetRequestForm: FC = () => {
         </>
       )}
       <Link href="/login" prefetch={false}>
-        <i className="icon-login me-1" />{t('forgot_password.return_to_login')}
+        <span className="material-symbols-outlined">login</span>{t('forgot_password.return_to_login')}
       </Link>
     </form>
   );

+ 3 - 3
apps/app/src/components/PrivateLegacyPages.tsx

@@ -175,7 +175,7 @@ const ConvertByPathModal = React.memo((props: ConvertByPathModalProps): JSX.Elem
           disabled={!checked}
           onClick={() => props.onSubmit?.(currentInput)}
         >
-          <i className="icon-fw icon-refresh" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">refresh</span>
           { t('private_legacy_pages.by_path_modal.button_label') }
         </button>
       </ModalFooter>
@@ -363,12 +363,12 @@ const PrivateLegacyPages = (): JSX.Element => {
               </DropdownToggle>
               <DropdownMenu>
                 <DropdownItem onClick={convertMenuItemClickedHandler}>
-                  <i className="icon-fw icon-refresh"></i>
+                  <span className="material-symbols-outlined">refresh</span>
                   {t('private_legacy_pages.convert_all_selected_pages')}
                 </DropdownItem>
                 <DropdownItem onClick={deleteAllButtonClickedHandler}>
                   <span className="text-danger">
-                    <i className="icon-fw icon-trash"></i>
+                    <span className="material-symbols-outlined">delete</span>
                     {t('search_result.delete_all_selected_page')}
                   </span>
                 </DropdownItem>

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

@@ -89,7 +89,7 @@ export const PrivateLegacyPagesMigrationModal = (): JSX.Element => {
       <ModalFooter>
         <ApiErrorMessageList errs={errs} />
         <button type="button" className="btn btn-primary" onClick={submit}>
-          <i className="icon-fw icon-refresh" aria-hidden="true"></i>
+          <span className="material-symbols-outlined" aria-hidden="true">refresh</span>
           { t('private_legacy_pages.modal.button_label') }
         </button>
       </ModalFooter>

+ 2 - 2
apps/app/src/components/PutbackPageModal.jsx

@@ -60,7 +60,7 @@ const PutBackPageModal = () => {
     }
     return (
       <>
-        <i className="icon-action-undo me-2" aria-hidden="true"></i> { t('modal_putback.label.Put Back Page') }
+        <span className="material-symbols-outlined" aria-hidden="true">undo</span> { t('modal_putback.label.Put Back Page') }
       </>
     );
   };
@@ -102,7 +102,7 @@ const PutBackPageModal = () => {
       <>
         <ApiErrorMessageList errs={errs} targetPath={targetPath} />
         <button type="button" className="btn btn-info" onClick={putbackPageButtonHandler} data-testid="put-back-execution-button">
-          <i className="icon-action-undo me-2" aria-hidden="true"></i> { t('Put Back') }
+          <span className="material-symbols-outlined" aria-hidden="true">undo</span> { t('Put Back') }
         </button>
       </>
     );

+ 1 - 1
apps/app/src/components/ReactMarkdownComponents/RichAttachment.tsx

@@ -70,7 +70,7 @@ export const RichAttachment = React.memo((props: RichAttachmentProps) => {
                 <i className="icon-cloud-download" />
               </a>
               <a className="ml-2 text-danger attachment-delete d-share-link-none" type="button" onClick={onClickTrashButtonHandler}>
-                <i className="icon-trash" />
+                <span className="material-symbols-outlined">delete</span>
               </a>
             </div>
             <div className="d-flex align-items-center">

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

@@ -35,7 +35,7 @@ const SearchFormHelp: FC<SearchFormHelpProps> = React.memo((props: SearchFormHel
   return (
     <table className={`${styles['grw-search-table']} table grw-search-table search-help m-0`}>
       <caption className="text-start text-primary p-2">
-        <h5 className="h6"><i className="icon-magnifier pe-2 mb-2" />{ t('search_help.title') }</h5>
+        <h5 className="h6"><span className="material-symbols-outlined">search</span>{ t('search_help.title') }</h5>
       </caption>
       <tbody>
         <tr>

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

@@ -189,7 +189,7 @@ export const SearchPage = (): JSX.Element => {
               disabled={isDisabled}
               onClick={deleteAllButtonClickedHandler}
             >
-              <i className="icon-fw icon-trash"></i>
+              <span className="material-symbols-outlined">delete</span>
               {t('search_result.delete_all_selected_page')}
             </button>
           </OperateAllControl>

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

@@ -29,7 +29,7 @@ const ResetFormButton: FC<ResetFormButtonProps> = (props: ResetFormButtonProps)
     <span />
   ) : (
     <button type="button" className="btn btn-outline-secondary search-clear text-muted border-0" onMouseDown={onReset}>
-      <i className="icon-close" />
+      <span className="material-symbols-outlined">close</span>
     </button>
   );
 };

+ 2 - 2
apps/app/src/components/ShareLinkPageView.tsx

@@ -75,7 +75,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
       return (
         <>
           <h2 className="text-muted mt-4">
-            <i className="icon-ban" aria-hidden="true" />
+            <span className="material-symbols-outlined" aria-hidden="true">block</span>
             <span> Page is expired</span>
           </h2>
         </>
@@ -103,7 +103,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
         <>
           { isNotFound && (
             <h2 className="text-muted mt-4">
-              <i className="icon-ban" aria-hidden="true" />
+              <span className="material-symbols-outlined" aria-hidden="true">block</span>
               <span> Page is not found</span>
             </h2>
           ) }

+ 1 - 1
apps/app/src/components/Sidebar/Custom/CustomSidebar.tsx

@@ -22,7 +22,7 @@ export const CustomSidebar = (): JSX.Element => {
       <div className="grw-sidebar-content-header py-3 d-flex">
         <h3 className="mb-0">
           {t('CustomSidebar')}
-          { !isLoading && <Link href="/Sidebar#edit" className="h6 ms-2"><i className="icon-pencil"></i></Link> }
+          { !isLoading && <Link href="/Sidebar#edit" className="h6 ms-2"><span className="material-symbols-outlined">edit</span></Link> }
         </h3>
         { !isLoading && <SidebarHeaderReloadButton onClick={() => mutate()} /> }
       </div>

+ 1 - 1
apps/app/src/components/Sidebar/Custom/CustomSidebarNotFound.tsx

@@ -7,7 +7,7 @@ export const SidebarNotFound = (): JSX.Element => {
   return (
     <div className="grw-sidebar-content-header h5 text-center py-3">
       <Link href="/Sidebar#edit">
-        <i className="icon-fw icon-magic-wand"></i>
+        <span className="material-symbols-outlined">edit_note</span>
         {/* eslint-disable-next-line react/no-danger */}
         <span dangerouslySetInnerHTML={{ __html: t('Create Sidebar Page') }}></span>
       </Link>

+ 17 - 13
apps/app/src/components/Sidebar/PageCreateButton/DropendMenu.tsx

@@ -5,18 +5,18 @@ import { useTranslation } from 'react-i18next';
 import { LabelType } from '~/interfaces/template';
 
 type DropendMenuProps = {
-  todaysPath: string,
   onClickCreateNewPageButtonHandler: () => Promise<void>
   onClickCreateTodaysButtonHandler: () => Promise<void>
   onClickTemplateButtonHandler: (label: LabelType) => Promise<void>
+  todaysPath: string | null,
 }
 
 export const DropendMenu = React.memo((props: DropendMenuProps): JSX.Element => {
   const {
-    todaysPath,
     onClickCreateNewPageButtonHandler,
     onClickCreateTodaysButtonHandler,
     onClickTemplateButtonHandler,
+    todaysPath,
   } = props;
 
   const { t } = useTranslation('commons');
@@ -32,17 +32,21 @@ export const DropendMenu = React.memo((props: DropendMenuProps): JSX.Element =>
           {t('create_page_dropdown.new_page')}
         </button>
       </li>
-      <li><hr className="dropdown-divider" /></li>
-      <li><span className="text-muted px-3">{t('create_page_dropdown.todays.desc')}</span></li>
-      <li>
-        <button
-          className="dropdown-item"
-          onClick={onClickCreateTodaysButtonHandler}
-          type="button"
-        >
-          {todaysPath}
-        </button>
-      </li>
+      {todaysPath != null && (
+        <>
+          <li><hr className="dropdown-divider" /></li>
+          <li><span className="text-muted px-3">{t('create_page_dropdown.todays.desc')}</span></li>
+          <li>
+            <button
+              className="dropdown-item"
+              onClick={onClickCreateTodaysButtonHandler}
+              type="button"
+            >
+              {todaysPath}
+            </button>
+          </li>
+        </>
+      )}
       <li><hr className="dropdown-divider" /></li>
       <li><span className="text-muted text-nowrap px-3">{t('create_page_dropdown.template.desc')}</span></li>
       <li>

+ 12 - 5
apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx

@@ -1,5 +1,6 @@
 import React, { useState, useCallback } from 'react';
 
+import type { IUserHasId } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
@@ -15,6 +16,12 @@ import { DropendMenu } from './DropendMenu';
 import { DropendToggle } from './DropendToggle';
 import { useOnNewButtonClicked, useOnTodaysButtonClicked } from './hooks';
 
+const generateTodaysPath = (currentUser: IUserHasId, parentDirName: string) => {
+  const now = format(new Date(), 'yyyy/MM/dd');
+  const userHomepagePath = pagePathUtils.userHomepagePath(currentUser);
+  return `${userHomepagePath}/${parentDirName}/${now}`;
+};
+
 export const PageCreateButton = React.memo((): JSX.Element => {
   const { t } = useTranslation('commons');
 
@@ -23,12 +30,12 @@ export const PageCreateButton = React.memo((): JSX.Element => {
 
   const [isHovered, setIsHovered] = useState(false);
 
-  const now = format(new Date(), 'yyyy/MM/dd');
-  const userHomepagePath = pagePathUtils.userHomepagePath(currentUser);
-  const todaysPath = `${userHomepagePath}/${t('create_page_dropdown.todays.memo')}/${now}`;
+  const todaysPath = currentUser == null
+    ? null
+    : generateTodaysPath(currentUser, t('create_page_dropdown.todays.memo'));
 
   const { onClickHandler: onClickNewButton, isPageCreating: isNewPageCreating } = useOnNewButtonClicked(currentPagePath, isLoading);
-  const { onClickHandler: onClickTodaysButton, isPageCreating: isTodaysPageCreating } = useOnTodaysButtonClicked(todaysPath, currentUser);
+  const { onClickHandler: onClickTodaysButton, isPageCreating: isTodaysPageCreating } = useOnTodaysButtonClicked(todaysPath);
   const { onClickHandler: onClickTemplateButton, isPageCreating: isTemplatePageCreating } = useOnTemplateButtonClicked(currentPagePath, isLoading);
 
   const onClickTemplateButtonHandler = useCallback(async(label: LabelType) => {
@@ -69,10 +76,10 @@ export const PageCreateButton = React.memo((): JSX.Element => {
             aria-expanded="false"
           />
           <DropendMenu
-            todaysPath={todaysPath}
             onClickCreateNewPageButtonHandler={onClickNewButton}
             onClickCreateTodaysButtonHandler={onClickTodaysButton}
             onClickTemplateButtonHandler={onClickTemplateButtonHandler}
+            todaysPath={todaysPath}
           />
         </div>
       )}

+ 3 - 5
apps/app/src/components/Sidebar/PageCreateButton/hooks.tsx

@@ -1,6 +1,5 @@
 import { useCallback, useState } from 'react';
 
-import type { Nullable, IUserHasId } from '@growi/core';
 import { useRouter } from 'next/router';
 
 import { createPage, exist } from '~/client/services/page-operation';
@@ -51,8 +50,7 @@ export const useOnNewButtonClicked = (
 };
 
 export const useOnTodaysButtonClicked = (
-    todaysPath: string,
-    currentUser?: Nullable<IUserHasId> | undefined,
+    todaysPath: string | null,
 ): {
   onClickHandler: () => Promise<void>,
   isPageCreating: boolean
@@ -61,7 +59,7 @@ export const useOnTodaysButtonClicked = (
   const [isPageCreating, setIsPageCreating] = useState(false);
 
   const onClickHandler = useCallback(async() => {
-    if (currentUser == null) {
+    if (todaysPath == null) {
       return;
     }
 
@@ -89,7 +87,7 @@ export const useOnTodaysButtonClicked = (
     finally {
       setIsPageCreating(false);
     }
-  }, [currentUser, router, todaysPath]);
+  }, [router, todaysPath]);
 
   return { onClickHandler, isPageCreating };
 };

+ 3 - 3
apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx

@@ -154,7 +154,7 @@ export const PageTreeItem: FC<PageTreeItemProps> = (props) => {
 
   const mainClassName = `${isOver ? 'grw-pagetree-is-over' : ''} ${shouldHide ? 'd-none' : ''}`;
 
-  const { NewPageInputWrapper, NewPageCreateButtonWrapper } = useNewPageInput();
+  const { Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
 
   return (
     <SimpleItem
@@ -169,8 +169,8 @@ export const PageTreeItem: FC<PageTreeItemProps> = (props) => {
       itemRef={itemRef}
       itemClass={PageTreeItem}
       mainClassName={mainClassName}
-      customEndComponents={[Ellipsis, NewPageCreateButtonWrapper]}
-      customNextComponents={[NewPageInputWrapper]}
+      customEndComponents={[Ellipsis, NewPageCreateButton]}
+      customNextComponents={[NewPageInput]}
     />
   );
 };

+ 1 - 1
apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx

@@ -90,7 +90,7 @@ export const PersonalDropdown = (): JSX.Element => {
               className="dropdown-item"
               onClick={() => setQuestionnaireModalOpen(true)}
             >
-              <i className="icon-fw icon-pencil"></i>{t('personal_dropdown.feedback')}
+              <span className="material-symbols-outlined">edit</span>{t('personal_dropdown.feedback')}
             </button>
           </li>
           <li>

+ 0 - 69
apps/app/src/components/TreeItem/NewPageCreateButton.tsx

@@ -1,69 +0,0 @@
-import React, {
-  useCallback, FC,
-} from 'react';
-
-import { pagePathUtils } from '@growi/core/dist/utils';
-
-import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
-import { NotAvailableForReadOnlyUser } from '~/components/NotAvailableForReadOnlyUser';
-import { IPageForItem } from '~/interfaces/page';
-import { usePageTreeDescCountMap } from '~/stores/ui';
-
-import { ItemNode } from './ItemNode';
-
-type StateHandlersType = {
-  isOpen: boolean,
-  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>,
-  isCreating: boolean,
-  setCreating: React.Dispatch<React.SetStateAction<boolean>>,
-};
-
-export type NewPageCreateButtonProps = {
-  page: IPageForItem,
-  currentChildren: ItemNode[],
-  stateHandlers: StateHandlersType,
-  isNewPageInputShown?: boolean,
-  setNewPageInputShown: React.Dispatch<React.SetStateAction<boolean>>,
-};
-
-export const NewPageCreateButton: FC<NewPageCreateButtonProps> = (props) => {
-  const {
-    page, currentChildren, stateHandlers, setNewPageInputShown,
-  } = props;
-
-  const { setIsOpen } = stateHandlers;
-
-  // descendantCount
-  const { getDescCount } = usePageTreeDescCountMap();
-  const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
-
-  const isChildrenLoaded = currentChildren?.length > 0;
-  const hasDescendants = descendantCount > 0 || isChildrenLoaded;
-
-  const onClickPlusButton = useCallback(() => {
-    setNewPageInputShown(true);
-
-    if (hasDescendants) {
-      setIsOpen(true);
-    }
-  }, [hasDescendants, setIsOpen]);
-
-  return (
-    <>
-      {!pagePathUtils.isUsersTopPage(page.path ?? '') && (
-        <NotAvailableForGuest>
-          <NotAvailableForReadOnlyUser>
-            <button
-              id="page-create-button-in-page-tree"
-              type="button"
-              className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
-              onClick={onClickPlusButton}
-            >
-              <i className="icon-plus d-block p-0" />
-            </button>
-          </NotAvailableForReadOnlyUser>
-        </NotAvailableForGuest>
-      )}
-    </>
-  );
-};

+ 0 - 104
apps/app/src/components/TreeItem/NewPageInput.tsx

@@ -1,104 +0,0 @@
-import React, { FC, useCallback, useEffect } from 'react';
-
-import nodePath from 'path';
-
-
-import { pathUtils, pagePathUtils } from '@growi/core/dist/utils';
-import { useTranslation } from 'next-i18next';
-
-import { apiv3Post } from '~/client/util/apiv3-client';
-import { ValidationTarget } from '~/client/util/input-validator';
-import { toastWarning, toastError, toastSuccess } from '~/client/util/toastr';
-import ClosableTextInput from '~/components/Common/ClosableTextInput';
-import { useSWRxPageChildren } from '~/stores/page-listing';
-import { usePageTreeDescCountMap } from '~/stores/ui';
-
-import { NewPageCreateButtonProps } from './NewPageCreateButton';
-import { NotDraggableForClosableTextInput } from './SimpleItem';
-
-type NewPageInputProps = NewPageCreateButtonProps & {isEnableActions: boolean};
-
-export const NewPageInput: FC<NewPageInputProps> = (props) => {
-  const { t } = useTranslation();
-
-  const {
-    page, isEnableActions, currentChildren, stateHandlers, isNewPageInputShown, setNewPageInputShown,
-  } = props;
-
-  const { isOpen, setIsOpen, setCreating } = stateHandlers;
-
-  const { mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
-
-  const { getDescCount } = usePageTreeDescCountMap();
-  const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
-
-  const isChildrenLoaded = currentChildren?.length > 0;
-  const hasDescendants = descendantCount > 0 || isChildrenLoaded;
-
-  const onPressEnterForCreateHandler = async(inputText: string) => {
-    setNewPageInputShown(false);
-    // closeNewPageInput();
-    const parentPath = pathUtils.addTrailingSlash(page.path as string);
-    const newPagePath = nodePath.resolve(parentPath, inputText);
-    const isCreatable = pagePathUtils.isCreatablePage(newPagePath);
-
-    if (!isCreatable) {
-      toastWarning(t('you_can_not_create_page_with_this_name'));
-      return;
-    }
-
-    try {
-      setCreating(true);
-
-      await apiv3Post('/pages/', {
-        path: newPagePath,
-        body: undefined,
-        grant: page.grant,
-        // grantUserGroupId: page.grantedGroup,
-        grantUserGroupIds: page.grantedGroups,
-      });
-
-      mutateChildren();
-
-      if (!hasDescendants) {
-        setIsOpen(true);
-      }
-
-      toastSuccess(t('successfully_saved_the_page'));
-    }
-    catch (err) {
-      toastError(err);
-    }
-    finally {
-      setCreating(false);
-    }
-  };
-
-  const onPressEscHandler = useCallback((event) => {
-    if (event.keyCode === 27) {
-      setNewPageInputShown(false);
-    }
-  }, []);
-
-  useEffect(() => {
-    document.addEventListener('keydown', onPressEscHandler, false);
-    return () => {
-      document.removeEventListener('keydown', onPressEscHandler, false);
-    };
-  }, [onPressEscHandler]);
-
-  return (
-    <>
-      {isEnableActions && isNewPageInputShown && (
-        <NotDraggableForClosableTextInput>
-          <ClosableTextInput
-            placeholder={t('Input page name')}
-            onClickOutside={() => { setNewPageInputShown(false) }}
-            onPressEnter={onPressEnterForCreateHandler}
-            validationTarget={ValidationTarget.PAGE}
-          />
-        </NotDraggableForClosableTextInput>
-      )}
-    </>
-  );
-};

+ 37 - 0
apps/app/src/components/TreeItem/NewPageInput/NewPageCreateButton.tsx

@@ -0,0 +1,37 @@
+import React, { type FC } from 'react';
+
+import { pagePathUtils } from '@growi/core/dist/utils';
+
+import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
+import { NotAvailableForReadOnlyUser } from '~/components/NotAvailableForReadOnlyUser';
+import type { IPageForItem } from '~/interfaces/page';
+
+type NewPageCreateButtonProps = {
+  page: IPageForItem,
+  onClick?: () => void,
+};
+
+export const NewPageCreateButton: FC<NewPageCreateButtonProps> = (props) => {
+  const {
+    page, onClick,
+  } = props;
+
+  return (
+    <>
+      {!pagePathUtils.isUsersTopPage(page.path ?? '') && (
+        <NotAvailableForGuest>
+          <NotAvailableForReadOnlyUser>
+            <button
+              id="page-create-button-in-page-tree"
+              type="button"
+              className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
+              onClick={onClick}
+            >
+              <i className="icon-plus d-block p-0" />
+            </button>
+          </NotAvailableForReadOnlyUser>
+        </NotAvailableForGuest>
+      )}
+    </>
+  );
+};

+ 81 - 0
apps/app/src/components/TreeItem/NewPageInput/NewPageInput.tsx

@@ -0,0 +1,81 @@
+import React, { type FC, useCallback, useEffect } from 'react';
+
+import nodePath from 'path';
+
+import { pathUtils, pagePathUtils } from '@growi/core/dist/utils';
+import { useTranslation } from 'next-i18next';
+
+import { ValidationTarget } from '~/client/util/input-validator';
+import { toastWarning, toastError, toastSuccess } from '~/client/util/toastr';
+import ClosableTextInput from '~/components/Common/ClosableTextInput';
+import type { IPageForItem } from '~/interfaces/page';
+
+import { NotDraggableForClosableTextInput } from '../NotDraggableForClosableTextInput';
+
+type Props = {
+  page: IPageForItem,
+  isEnableActions: boolean,
+  onSubmit?: (newPagePath: string) => Promise<void>,
+  onSubmittionFailed?: () => void,
+  onCanceled?: () => void,
+};
+
+export const NewPageInput: FC<Props> = (props) => {
+  const { t } = useTranslation();
+
+  const {
+    page, isEnableActions,
+    onSubmit, onSubmittionFailed,
+    onCanceled,
+  } = props;
+
+  const onPressEnterForCreateHandler = async(inputText: string) => {
+    const parentPath = pathUtils.addTrailingSlash(page.path as string);
+    const newPagePath = nodePath.resolve(parentPath, inputText);
+    const isCreatable = pagePathUtils.isCreatablePage(newPagePath);
+
+    if (!isCreatable) {
+      toastWarning(t('you_can_not_create_page_with_this_name'));
+      return;
+    }
+
+    try {
+      onSubmit?.(newPagePath);
+      toastSuccess(t('successfully_saved_the_page'));
+    }
+    catch (err) {
+      toastError(err);
+    }
+    finally {
+      onSubmittionFailed?.();
+    }
+  };
+
+  const onPressEscHandler = useCallback((event) => {
+    if (event.keyCode === 27) {
+      onCanceled?.();
+    }
+  }, [onCanceled]);
+
+  useEffect(() => {
+    document.addEventListener('keydown', onPressEscHandler, false);
+    return () => {
+      document.removeEventListener('keydown', onPressEscHandler, false);
+    };
+  }, [onPressEscHandler]);
+
+  return (
+    <>
+      {isEnableActions && (
+        <NotDraggableForClosableTextInput>
+          <ClosableTextInput
+            placeholder={t('Input page name')}
+            onClickOutside={onCanceled}
+            onPressEnter={onPressEnterForCreateHandler}
+            validationTarget={ValidationTarget.PAGE}
+          />
+        </NotDraggableForClosableTextInput>
+      )}
+    </>
+  );
+};

+ 1 - 0
apps/app/src/components/TreeItem/NewPageInput/index.ts

@@ -0,0 +1 @@
+export * from './use-new-page-input';

+ 110 - 0
apps/app/src/components/TreeItem/NewPageInput/use-new-page-input.tsx

@@ -0,0 +1,110 @@
+import React, { useState, type FC, useCallback } from 'react';
+
+import { apiv3Post } from '~/client/util/apiv3-client';
+import { useSWRxPageChildren } from '~/stores/page-listing';
+import { usePageTreeDescCountMap } from '~/stores/ui';
+
+import type { SimpleItemContentProps } from '../interfaces';
+
+import { NewPageCreateButton } from './NewPageCreateButton';
+import { NewPageInput } from './NewPageInput';
+
+type UseNewPageInput = {
+  Input: FC<SimpleItemContentProps>,
+  CreateButton: FC<SimpleItemContentProps>,
+  isProcessingSubmission: boolean,
+}
+
+export const useNewPageInput = (): UseNewPageInput => {
+
+  const [showInput, setShowInput] = useState(false);
+  const [isProcessingSubmission, setProcessingSubmission] = useState(false);
+
+  const { getDescCount } = usePageTreeDescCountMap();
+
+  const CreateButton: FC<SimpleItemContentProps> = (props) => {
+
+    const { page, children, stateHandlers } = props;
+    const { setIsOpen } = stateHandlers;
+
+    // descendantCount
+    const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
+
+    const isChildrenLoaded = children?.length > 0;
+    const hasDescendants = descendantCount > 0 || isChildrenLoaded;
+
+    const onClick = useCallback(() => {
+      setShowInput(true);
+
+      if (hasDescendants) {
+        setIsOpen(true);
+      }
+    }, [hasDescendants, setIsOpen]);
+
+    return (
+      <NewPageCreateButton
+        page={props.page}
+        onClick={onClick}
+      />
+    );
+  };
+
+  const Input: FC<SimpleItemContentProps> = (props) => {
+
+    const {
+      page, children, stateHandlers,
+    } = props;
+
+    const { isOpen, setIsOpen } = stateHandlers;
+
+    const { mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
+
+    const { getDescCount } = usePageTreeDescCountMap();
+    const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
+
+    const isChildrenLoaded = children?.length > 0;
+    const hasDescendants = descendantCount > 0 || isChildrenLoaded;
+
+    const submitHandler = useCallback(async(newPagePath: string) => {
+      setProcessingSubmission(true);
+
+      setShowInput(false);
+
+      await apiv3Post('/pages/', {
+        path: newPagePath,
+        body: undefined,
+        grant: page.grant,
+        // grantUserGroupId: page.grantedGroup,
+        grantUserGroupIds: page.grantedGroups,
+      });
+
+      mutateChildren();
+
+      if (!hasDescendants) {
+        setIsOpen(true);
+      }
+    }, [hasDescendants, mutateChildren, page.grant, page.grantedGroups, setIsOpen]);
+
+    const submittionFailedHandler = useCallback(() => {
+      setProcessingSubmission(false);
+    }, []);
+
+    return showInput
+      ? (
+        <NewPageInput
+          page={props.page}
+          isEnableActions={props.isEnableActions}
+          onSubmit={submitHandler}
+          onSubmittionFailed={submittionFailedHandler}
+          onCanceled={() => setShowInput(false)}
+        />
+      )
+      : <></>;
+  };
+
+  return {
+    Input,
+    CreateButton,
+    isProcessingSubmission,
+  };
+};

+ 13 - 0
apps/app/src/components/TreeItem/NotDraggableForClosableTextInput.tsx

@@ -0,0 +1,13 @@
+import type { ReactNode } from 'react';
+
+type NotDraggableProps = {
+  children: ReactNode,
+};
+
+/**
+ * Component wrapper to make a child element not draggable
+ * @see https://github.com/react-dnd/react-dnd/issues/335
+ */
+export const NotDraggableForClosableTextInput = (props: NotDraggableProps): JSX.Element => {
+  return <div draggable onDragStart={e => e.preventDefault()}>{props.children}</div>;
+};

+ 20 - 58
apps/app/src/components/TreeItem/SimpleItem.tsx

@@ -1,17 +1,15 @@
 import React, {
-  useCallback, useState, FC, useEffect, ReactNode,
+  useCallback, useState, FC, useEffect,
 } from 'react';
 
 import nodePath from 'path';
 
-import type { Nullable, IPageToDeleteWithMeta } from '@growi/core';
+import type { Nullable } from '@growi/core';
 import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import { UncontrolledTooltip } from 'reactstrap';
 
-import { IPageForItem } from '~/interfaces/page';
-import { IPageForPageDuplicateModal } from '~/stores/modal';
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { usePageTreeDescCountMap } from '~/stores/ui';
 import { shouldRecoverPagePaths } from '~/utils/page-operation';
@@ -19,24 +17,10 @@ import { shouldRecoverPagePaths } from '~/utils/page-operation';
 import CountBadge from '../Common/CountBadge';
 
 import { ItemNode } from './ItemNode';
+import { useNewPageInput } from './NewPageInput';
+import type { SimpleItemContentProps, SimpleItemProps, SimpleItemToolProps } from './interfaces';
 
 
-export type SimpleItemProps = {
-  isEnableActions: boolean
-  isReadOnlyUser: boolean
-  itemNode: ItemNode
-  targetPathOrId?: Nullable<string>
-  isOpen?: boolean
-  onRenamed?(fromPath: string | undefined, toPath: string): void
-  onClickDuplicateMenuItem?(pageToDuplicate: IPageForPageDuplicateModal): void
-  onClickDeleteMenuItem?(pageToDelete: IPageToDeleteWithMeta): void
-  itemRef?
-  itemClass?: React.FunctionComponent<SimpleItemProps>
-  mainClassName?: string
-  customEndComponents?: Array<React.FunctionComponent<SimpleItemToolProps>>
-  customNextComponents?: Array<React.FunctionComponent<SimpleItemToolProps>>
-};
-
 // Utility to mark target
 const markTarget = (children: ItemNode[], targetPathOrId?: Nullable<string>): void => {
   if (targetPathOrId == null) {
@@ -69,21 +53,10 @@ const markTarget = (children: ItemNode[], targetPathOrId?: Nullable<string>): vo
  * @returns
  */
 
-// Component wrapper to make a child element not draggable
-// https://github.com/react-dnd/react-dnd/issues/335
-type NotDraggableProps = {
-  children: ReactNode,
-};
-export const NotDraggableForClosableTextInput = (props: NotDraggableProps): JSX.Element => {
-  return <div draggable onDragStart={e => e.preventDefault()}>{props.children}</div>;
-};
-
-type SimpleItemToolPropsOptional = 'itemNode' | 'targetPathOrId' | 'isOpen' | 'itemRef' | 'itemClass' | 'mainClassName';
-export type SimpleItemToolProps = Omit<SimpleItemProps, SimpleItemToolPropsOptional> & {page: IPageForItem};
-
 export const SimpleItemTool: FC<SimpleItemToolProps> = (props) => {
   const { t } = useTranslation();
   const router = useRouter();
+
   const { getDescCount } = usePageTreeDescCountMap();
 
   const page = props.page;
@@ -139,19 +112,13 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
 
   const { page, children } = itemNode;
 
+  const { isProcessingSubmission } = useNewPageInput();
+
   const [currentChildren, setCurrentChildren] = useState(children);
   const [isOpen, setIsOpen] = useState(_isOpen);
-  const [isCreating, setCreating] = useState(false);
 
   const { data } = useSWRxPageChildren(isOpen ? page._id : null);
 
-  const stateHandlers = {
-    isOpen,
-    setIsOpen,
-    isCreating,
-    setCreating,
-  };
-
   // descendantCount
   const { getDescCount } = usePageTreeDescCountMap();
   const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
@@ -197,7 +164,12 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
 
   const ItemClassFixed = itemClass ?? SimpleItem;
 
-  const commonProps = {
+  const CustomEndComponents = props.customEndComponents;
+
+  const SimpleItemContent = CustomEndComponents ?? [SimpleItemTool];
+
+  const simpleItemProps: SimpleItemProps = {
+    itemNode,
     isEnableActions,
     isReadOnlyUser,
     isOpen: false,
@@ -205,23 +177,13 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
     onRenamed,
     onClickDuplicateMenuItem,
     onClickDeleteMenuItem,
-    stateHandlers,
   };
 
-  const CustomEndComponents = props.customEndComponents;
-
-  const SimpleItemContent = CustomEndComponents ?? [SimpleItemTool];
-
-  const SimpleItemContentProps = {
-    itemNode,
+  const simpleItemContentProps: SimpleItemContentProps = {
+    ...simpleItemProps,
     page,
-    onRenamed,
-    onClickDuplicateMenuItem,
-    onClickDeleteMenuItem,
-    isEnableActions,
-    isReadOnlyUser,
     children,
-    stateHandlers,
+    stateHandlers: { isOpen, setIsOpen },
   };
 
   const CustomNextComponents = props.customNextComponents;
@@ -254,20 +216,20 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
         </div>
         {SimpleItemContent.map((ItemContent, index) => (
           // eslint-disable-next-line react/no-array-index-key
-          <ItemContent key={index} {...SimpleItemContentProps} />
+          <ItemContent key={index} {...simpleItemContentProps} />
         ))}
       </li>
 
       {CustomNextComponents?.map((UnderItemContent, index) => (
         // eslint-disable-next-line react/no-array-index-key
-        <UnderItemContent key={index} {...SimpleItemContentProps} />
+        <UnderItemContent key={index} {...simpleItemContentProps} />
       ))}
 
       {
         isOpen && hasChildren() && currentChildren.map((node, index) => (
           <div key={node.page._id} className="grw-pagetree-item-children">
-            <ItemClassFixed itemNode={node} {...commonProps} />
-            {isCreating && (currentChildren.length - 1 === index) && (
+            <ItemClassFixed {...simpleItemProps} />
+            {isProcessingSubmission && (currentChildren.length - 1 === index) && (
               <div className="text-muted text-center">
                 <i className="fa fa-spinner fa-pulse mr-1"></i>
               </div>

+ 0 - 43
apps/app/src/components/TreeItem/UseNewPageInput.tsx

@@ -1,43 +0,0 @@
-import React, { useState, FC } from 'react';
-
-import { ItemNode } from './ItemNode';
-import { NewPageCreateButton } from './NewPageCreateButton';
-import { NewPageInput } from './NewPageInput';
-import { SimpleItemToolProps } from './SimpleItem';
-
-type UseNewPageInputProps = SimpleItemToolProps & {children: ItemNode[], stateHandlers};
-
-export const useNewPageInput = () => {
-
-  const [isNewPageInputShown, setNewPageInputShown] = useState(false);
-
-  const NewPageCreateButtonWrapper: FC<UseNewPageInputProps> = (props) => {
-    return (
-      <NewPageCreateButton
-        page={props.page}
-        currentChildren={props.children}
-        stateHandlers={props.stateHandlers}
-        setNewPageInputShown={setNewPageInputShown}
-      />
-    );
-  };
-
-  const NewPageInputWrapper = (props) => {
-    return (
-      <NewPageInput
-        page={props.page}
-        isEnableActions={props.isEnableActions}
-        currentChildren={props.chilren}
-        stateHandlers={props.stateHandlers}
-        isNewPageInputShown={isNewPageInputShown}
-        setNewPageInputShown={setNewPageInputShown}
-      />
-    );
-  };
-
-
-  return {
-    NewPageInputWrapper,
-    NewPageCreateButtonWrapper,
-  };
-};

+ 5 - 2
apps/app/src/components/TreeItem/index.ts

@@ -1,3 +1,6 @@
-export { useNewPageInput } from './UseNewPageInput';
-export * from './SimpleItem';
+export * from './interfaces';
+
+export * from './NewPageInput';
 export * from './ItemNode';
+export * from './SimpleItem';
+export * from './NotDraggableForClosableTextInput';

Некоторые файлы не были показаны из-за большого количества измененных файлов