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

Merge branch 'dev/7.0.x' into imprv/137432-make-markdown-list-util

kosei-n 2 лет назад
Родитель
Сommit
50dfcdecaa
100 измененных файлов с 588 добавлено и 462 удалено
  1. 81 0
      .github/workflows/release-rc-v7.yml
  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. 3 1
      apps/app/package.json
  7. 6 0
      apps/app/public/static/locales/en_US/commons.json
  8. 6 0
      apps/app/public/static/locales/ja_JP/commons.json
  9. 6 0
      apps/app/public/static/locales/zh_CN/commons.json
  10. 2 2
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  11. 1 1
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  12. 15 9
      apps/app/src/client/services/use-on-template-button-clicked.ts
  13. 2 2
      apps/app/src/components/Admin/App/ConfirmModal.tsx
  14. 3 3
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  15. 1 1
      apps/app/src/components/Admin/App/MailSetting.tsx
  16. 1 1
      apps/app/src/components/Admin/App/MaintenanceMode.tsx
  17. 2 2
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  18. 1 1
      apps/app/src/components/Admin/App/SiteUrlSetting.tsx
  19. 1 1
      apps/app/src/components/Admin/App/V5PageMigration.tsx
  20. 2 1
      apps/app/src/components/Admin/AuditLog/AuditLogDisableMode.tsx
  21. 3 3
      apps/app/src/components/Admin/AuditLog/AuditLogSettings.tsx
  22. 1 1
      apps/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx
  23. 3 3
      apps/app/src/components/Admin/AuditLogManagement.tsx
  24. 17 17
      apps/app/src/components/Admin/Common/AdminNavigation.tsx
  25. 2 2
      apps/app/src/components/Admin/ElasticsearchManagement/StatusTable.jsx
  26. 3 3
      apps/app/src/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx
  27. 1 1
      apps/app/src/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx
  28. 3 3
      apps/app/src/components/Admin/ImportData/ImportDataPageContents.jsx
  29. 2 2
      apps/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx
  30. 2 2
      apps/app/src/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx
  31. 1 1
      apps/app/src/components/Admin/ManageExternalAccount.tsx
  32. 9 10
      apps/app/src/components/Admin/Notification/GlobalNotificationList.jsx
  33. 10 10
      apps/app/src/components/Admin/Notification/ManageGlobalNotification.tsx
  34. 2 2
      apps/app/src/components/Admin/Notification/NotificationDeleteModal.jsx
  35. 2 2
      apps/app/src/components/Admin/Notification/NotificationSetting.jsx
  36. 2 2
      apps/app/src/components/Admin/Security/DeleteAllShareLinksModal.jsx
  37. 1 1
      apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx
  38. 1 1
      apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx
  39. 4 4
      apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx
  40. 1 1
      apps/app/src/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx
  41. 2 2
      apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  42. 1 1
      apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  43. 3 3
      apps/app/src/components/Admin/UserManagement.tsx
  44. 1 1
      apps/app/src/components/Admin/Users/ExternalAccountTable.tsx
  45. 1 1
      apps/app/src/components/Admin/Users/UserRemoveButton.jsx
  46. 2 2
      apps/app/src/components/AlertSiteUrlUndefined.tsx
  47. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  48. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx
  49. 1 1
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx
  50. 1 1
      apps/app/src/components/CompleteUserRegistration.tsx
  51. 6 6
      apps/app/src/components/CompleteUserRegistrationForm.tsx
  52. 1 1
      apps/app/src/components/ContentLinkButtons.tsx
  53. 2 2
      apps/app/src/components/DeleteBookmarkFolderModal.tsx
  54. 1 1
      apps/app/src/components/EmptyTrashButton.tsx
  55. 2 2
      apps/app/src/components/EmptyTrashModal.tsx
  56. 3 1
      apps/app/src/components/FontFamily/GlobalFonts.tsx
  57. 17 0
      apps/app/src/components/FontFamily/use-growi-custom-icons.tsx
  58. 2 2
      apps/app/src/components/ForbiddenPage.tsx
  59. 1 1
      apps/app/src/components/InAppNotification/InAppNotificationDropdown.tsx
  60. 6 6
      apps/app/src/components/InstallerForm.tsx
  61. 4 4
      apps/app/src/components/InvitedForm.tsx
  62. 2 0
      apps/app/src/components/Layout/BasicLayout.tsx
  63. 16 14
      apps/app/src/components/LoginForm.tsx
  64. 1 1
      apps/app/src/components/Me/PersonalSettings.jsx
  65. 0 25
      apps/app/src/components/Me/SidebarCollapsedIcon.jsx
  66. 0 20
      apps/app/src/components/Me/SidebarDockIcon.jsx
  67. 2 0
      apps/app/src/components/Me/UISettings.module.scss
  68. 3 6
      apps/app/src/components/Me/UISettings.tsx
  69. 4 15
      apps/app/src/components/Navbar/GrowiNavbarBottom.tsx
  70. 1 1
      apps/app/src/components/NotCreatablePage.tsx
  71. 21 0
      apps/app/src/components/Page/markdown-drawio-util-for-view.ts
  72. 0 0
      apps/app/src/components/Page/markdown-table-util-for-view.ts
  73. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx
  74. 2 2
      apps/app/src/components/PageAlert/TrashPageAlert.tsx
  75. 1 1
      apps/app/src/components/PageComment.tsx
  76. 1 1
      apps/app/src/components/PageComment/CommentControl.tsx
  77. 2 2
      apps/app/src/components/PageComment/DeleteCommentModal.tsx
  78. 6 1
      apps/app/src/components/PageControls/PageControls.tsx
  79. 14 0
      apps/app/src/components/PageControls/SearchButton.module.scss
  80. 28 0
      apps/app/src/components/PageControls/SearchButton.tsx
  81. 3 3
      apps/app/src/components/PageCreateModal.jsx
  82. 1 1
      apps/app/src/components/PageDeleteModal.tsx
  83. 1 1
      apps/app/src/components/PageEditor/DrawioCommunicationHelper.ts
  84. 22 9
      apps/app/src/components/PageEditor/DrawioModal.tsx
  85. 0 179
      apps/app/src/components/PageEditor/MarkdownDrawioUtil.js
  86. 134 0
      apps/app/src/components/PageEditor/markdown-drawio-util-for-editor.ts
  87. 5 5
      apps/app/src/components/PageStatusAlert.tsx
  88. 14 5
      apps/app/src/components/PageTags/PageTags.tsx
  89. 7 0
      apps/app/src/components/PageTags/TagLabels.module.scss
  90. 1 1
      apps/app/src/components/PasswordResetExecutionForm.tsx
  91. 3 2
      apps/app/src/components/PasswordResetRequestForm.tsx
  92. 3 3
      apps/app/src/components/PrivateLegacyPages.tsx
  93. 1 1
      apps/app/src/components/PrivateLegacyPagesMigrationModal.tsx
  94. 2 2
      apps/app/src/components/PutbackPageModal.jsx
  95. 1 1
      apps/app/src/components/ReactMarkdownComponents/RichAttachment.tsx
  96. 1 1
      apps/app/src/components/SearchForm.tsx
  97. 1 1
      apps/app/src/components/SearchPage.tsx
  98. 1 1
      apps/app/src/components/SearchTypeahead.tsx
  99. 2 2
      apps/app/src/components/ShareLinkPageView.tsx
  100. 1 1
      apps/app/src/components/Sidebar/AppTitle/AppTitle.module.scss

+ 81 - 0
.github/workflows/release-rc-v7.yml

@@ -0,0 +1,81 @@
+name: Release Docker Images for RC (for dev/7.0.x)
+
+on:
+  push:
+    branches:
+      - dev/7.0.x
+
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+jobs:
+
+  determine-tags:
+    runs-on: ubuntu-latest
+
+    outputs:
+      TAGS: ${{ steps.meta.outputs.tags }}
+      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@1.2.0
+      id: package-json
+
+    - name: Docker meta for docker.io
+      uses: docker/metadata-action@v4
+      id: meta
+      with:
+        images: docker.io/weseek/growi
+        sep-tags: ','
+        tags: |
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
+
+    - name: Docker meta for ghcr.io
+      uses: docker/metadata-action@v4
+      id: meta-ghcr
+      with:
+        images: ghcr.io/weseek/growi
+        sep-tags: ','
+        tags: |
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
+
+
+  build-image-rc:
+    uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
+    with:
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+
+
+  publish-image-rc:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS }}
+      registry: docker.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+
+  publish-image-rc-ghcr:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
+      registry: ghcr.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+

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

+ 3 - 1
apps/app/package.json

@@ -68,6 +68,7 @@
     "@elastic/elasticsearch8": "npm:@elastic/elasticsearch@^8.7.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
+    "@growi/custom-icons": "link:../../packages/custom-icons",
     "@growi/core": "link:../../packages/core",
     "@growi/pluginkit": "link:../../packages/pluginkit",
     "@growi/preset-templates": "link:../../packages/preset-templates",
@@ -174,6 +175,7 @@
     "react-markdown": "^8.0.7",
     "react-multiline-clamp": "^2.0.0",
     "react-scroll": "^1.8.7",
+    "react-stickynode": "^4.1.0",
     "react-syntax-highlighter": "^15.5.0",
     "react-toastify": "^9.1.3",
     "react-use-ripple": "^1.5.2",
@@ -234,6 +236,7 @@
     "bootstrap": "^5.3.1",
     "connect-browser-sync": "^2.1.0",
     "diff2html": "^3.4.35",
+    "downshift": "^8.2.3",
     "eazy-logger": "^3.1.0",
     "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
     "eslint-plugin-cypress": "^2.12.1",
@@ -258,7 +261,6 @@
     "react-copy-to-clipboard": "^5.0.1",
     "react-dropzone": "^11.2.4",
     "react-hotkeys": "^2.0.0",
-    "react-stickynode": "^4.1.0",
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "sass": "^1.53.0",

+ 6 - 0
apps/app/public/static/locales/en_US/commons.json

@@ -42,6 +42,12 @@
     }
   },
 
+  "search_method_menu_item": {
+    "search_in_all": "Search in all",
+    "only_children_of_this_tree": "Only children of this tree",
+    "exact_mutch": "Exact match"
+  },
+
   "share_links": {
     "Share Link": "Share Link",
     "Page Path": "Page Path",

+ 6 - 0
apps/app/public/static/locales/ja_JP/commons.json

@@ -44,6 +44,12 @@
     }
   },
 
+  "search_method_menu_item": {
+    "search_in_all": "全てのページ",
+    "only_children_of_this_tree": "この階層下の子ページのみ",
+    "exact_mutch": "キーワードに完全一致した文字を含むページのみ"
+  },
+
   "share_links": {
     "Share Link": "共有用リンク",
     "Page Path": "ページパス",

+ 6 - 0
apps/app/public/static/locales/zh_CN/commons.json

@@ -45,6 +45,12 @@
 		}
   },
 
+  "search_method_menu_item": {
+    "search_in_all": "所有页面",
+    "only_children_of_this_tree": "当前分支以下内容",
+    "exact_mutch": "完全匹配"
+  },
+
   "share_links": {
     "Share Link": "Share Link",
     "Page Path": "Page Path",

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

@@ -5,7 +5,7 @@ import EventEmitter from 'events';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
 import { useSaveOrUpdate } from '~/client/services/page-operation';
-import mdu from '~/components/PageEditor/MarkdownDrawioUtil';
+import { replaceDrawioInMarkdown } from '~/components/Page/markdown-drawio-util-for-view';
 import type { OptionsToSave } from '~/interfaces/page-operation';
 import { useShareLinkId } from '~/stores/context';
 import { useDrawioModal } from '~/stores/modal';
@@ -41,7 +41,7 @@ export const useDrawioModalLauncherForView = (opts?: {
     }
 
     const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = mdu.replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
+    const newMarkdown = replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
 
     const grantUserGroupIds = currentPage.grantedGroups.map((g) => {
       return {

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

@@ -4,7 +4,7 @@ import EventEmitter from 'events';
 
 import MarkdownTable from '~/client/models/MarkdownTable';
 import { useSaveOrUpdate } from '~/client/services/page-operation';
-import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/PageEditor/markdown-table-util-for-view';
+import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/Page/markdown-table-util-for-view';
 import type { OptionsToSave } from '~/interfaces/page-operation';
 import { useShareLinkId } from '~/stores/context';
 import { useHandsontableModal } from '~/stores/modal';

+ 15 - 9
apps/app/src/client/services/use-on-template-button-clicked.ts

@@ -1,5 +1,6 @@
 import { useCallback, useState } from 'react';
 
+import { isCreatablePage } from '@growi/core/dist/utils/page-path-utils';
 import { useRouter } from 'next/router';
 
 import { createPage, exist } from '~/client/services/page-operation';
@@ -7,6 +8,7 @@ import { LabelType } from '~/interfaces/template';
 
 export const useOnTemplateButtonClicked = (
     currentPagePath?: string,
+    isLoading?: boolean,
 ): {
   onClickHandler: (label: LabelType) => Promise<void>,
   isPageCreating: boolean
@@ -15,23 +17,27 @@ export const useOnTemplateButtonClicked = (
   const [isPageCreating, setIsPageCreating] = useState(false);
 
   const onClickHandler = useCallback(async(label: LabelType) => {
+    if (isLoading) return;
+
     try {
       setIsPageCreating(true);
 
-      const path = currentPagePath == null || currentPagePath === '/'
+      const targetPath = currentPagePath == null || currentPagePath === '/'
         ? `/${label}`
         : `${currentPagePath}/${label}`;
 
-      const params = {
-        isSlackEnabled: false,
-        slackChannels: '',
-        grant: 4,
-      // grant: currentPage?.grant || 1,
-      // grantUserGroupId: currentPage?.grantedGroup?._id,
-      };
+      const path = isCreatablePage(targetPath) ? targetPath : `/${label}`;
 
       const res = await exist(JSON.stringify([path]));
       if (!res.pages[path]) {
+        const params = {
+          isSlackEnabled: false,
+          slackChannels: '',
+          grant: 4,
+        // grant: currentPage?.grant || 1,
+        // grantUserGroupId: currentPage?.grantedGroup?._id,
+        };
+
         await createPage(path, '', params);
       }
 
@@ -43,7 +49,7 @@ export const useOnTemplateButtonClicked = (
     finally {
       setIsPageCreating(false);
     }
-  }, [currentPagePath, router]);
+  }, [currentPagePath, isLoading, router]);
 
   return { onClickHandler, isPageCreating };
 };

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

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

+ 3 - 1
apps/app/src/components/FontFamily/GlobalFonts.tsx

@@ -1,5 +1,6 @@
 import { memo } from 'react';
 
+import { useGrowiCustomIcon } from './use-growi-custom-icons';
 import { useLatoFontFamily } from './use-lato';
 import { useMaterialSymbolsOutlined } from './use-material-symbols-outlined';
 import { useSourceHanCodeJP } from './use-source-han-code-jp';
@@ -8,16 +9,17 @@ import { useSourceHanCodeJP } from './use-source-han-code-jp';
  * Define prefixed by '--grw-font-family'
  */
 export const GlobalFonts = memo((): JSX.Element => {
-
   const latoFontFamily = useLatoFontFamily();
   const sourceHanCodeJPFontFamily = useSourceHanCodeJP();
   const materialSymbolsOutlinedFontFamily = useMaterialSymbolsOutlined();
+  const customSvgFontFamily = useGrowiCustomIcon();
 
   return (
     <>
       {latoFontFamily}
       {sourceHanCodeJPFontFamily}
       {materialSymbolsOutlinedFontFamily}
+      {customSvgFontFamily}
     </>
   );
 });

+ 17 - 0
apps/app/src/components/FontFamily/use-growi-custom-icons.tsx

@@ -0,0 +1,17 @@
+import localFont from 'next/font/local';
+
+import { DefineStyle } from './types';
+
+const growiCustomIconFont = localFont({
+  src: '../../../../../packages/custom-icons/dist/growi-custom-icons.woff2',
+});
+
+export const useGrowiCustomIcon: DefineStyle = () => (
+  <style jsx global>
+    {`
+      :root {
+        --grw-font-family-custom-icon: ${growiCustomIconFont.style.fontFamily};
+      }
+    `}
+  </style>
+);

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

+ 2 - 0
apps/app/src/components/Layout/BasicLayout.tsx

@@ -23,6 +23,7 @@ const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false
 const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
 const PageAccessoriesModal = dynamic(() => import('../PageAccessoriesModal').then(mod => mod.PageAccessoriesModal), { ssr: false });
 const DeleteBookmarkFolderModal = dynamic(() => import('../DeleteBookmarkFolderModal').then(mod => mod.DeleteBookmarkFolderModal), { ssr: false });
+const SearchModal = dynamic(() => import('../../features/search/client/components/SearchModal'), { ssr: false });
 
 
 type Props = {
@@ -57,6 +58,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
         <DeleteAttachmentModal />
         <DeleteBookmarkFolderModal />
         <PutbackPageModal />
+        <SearchModal />
       </DndProvider>
 
       <PagePresentationModal />

+ 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'),
       // },

+ 0 - 25
apps/app/src/components/Me/SidebarCollapsedIcon.jsx

@@ -1,25 +0,0 @@
-import React from 'react';
-
-const SidebarCollapsedIcon = () => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 23 23"
-  >
-    <rect width="23" height="23" fillOpacity="0" />
-    <path d="M20.9,3.9c0.3,0,0.6,0.3,0.6,0.6v13.9c0,0.3-0.3,0.6-0.6,0.6H2.1c-0.3,0-0.6-0.3-0.6-0.6V4.5c0-0.3,0.3-0.6,0.6-0.6H20.9
-      M20.9,2.4H2.1C1,2.4,0,3.4,0,4.5c0,0,0,0,0,0v13.9c0,1.2,1,2.1,2.1,2.1c0,0,0,0,0,0h18.7c1.2,0,2.1-0.9,2.1-2.1c0,0,0,0,0,0V4.5
-      C23,3.4,22,2.4,20.9,2.4C20.9,2.4,20.9,2.4,20.9,2.4z"
-    />
-    <rect x="7.5" y="3.9" width="1.2" height="0.8" />
-    <rect x="7.5" y="15.3" width="1.2" height="1.5" />
-    <rect x="7.5" y="12.3" width="1.2" height="1.5" />
-    <rect x="7.5" y="9.2" width="1.2" height="1.6" />
-    <rect x="7.5" y="6.1" width="1.2" height="1.6" />
-    <rect x="7.5" y="18.4" width="1.2" height="0.8" />
-    <path d="M15.1,14.9c-0.2,0-0.3-0.1-0.4-0.2l-2.8-2.8c-0.2-0.2-0.2-0.6,0-0.8l2.8-2.8c0.2-0.2,0.6-0.2,0.9,0s0.2,0.6,0,0.9l-2.4,2.4
-      l2.4,2.4c0.2,0.2,0.2,0.6,0,0.9C15.4,14.8,15.3,14.9,15.1,14.9z"
-    />
-  </svg>
-);
-
-export default SidebarCollapsedIcon;

+ 0 - 20
apps/app/src/components/Me/SidebarDockIcon.jsx

@@ -1,20 +0,0 @@
-import React from 'react';
-
-const SidebarDockIcon = () => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 23 23"
-  >
-    <rect width="23" height="23" fillOpacity="0" />
-    <path
-      d="M20.86,3.92a.64.64,0,0,1,.64.63v13.9a.64.64,0,0,1-.64.63H2.14a.64.64,0,0,
-      1-.64-.63V4.55a.64.64,0,0,1,.64-.63H20.86m0-1.5H2.14A2.13,2.13,0,0,0,0,4.55v13.9a2.13,
-      2.13,0,0,0,2.14,2.13H20.86A2.13,2.13,0,0,0,23,18.45V4.55a2.13,2.13,0,0,0-2.14-2.13Z"
-    />
-    <rect x="7.49" y="3.05" width="1.2" height="16.91" />
-  </svg>
-
-);
-
-
-export default SidebarDockIcon;

+ 2 - 0
apps/app/src/components/Me/UISettings.module.scss

@@ -1,6 +1,8 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
 .grw-sidebar-mode-icon {
+  display: flex;
+  align-items: center;
   width: 20px;
   height: 20px;
   color: bs.$secondary;

+ 3 - 6
apps/app/src/components/Me/UISettings.tsx

@@ -7,9 +7,6 @@ import { updateUserUISettings } from '~/client/services/user-ui-settings';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { useCollapsedContentsOpened, usePreferCollapsedMode, useSidebarMode } from '~/stores/ui';
 
-import SidebarCollapsedIcon from './SidebarCollapsedIcon';
-import SidebarDockIcon from './SidebarDockIcon';
-
 import styles from './UISettings.module.scss';
 
 const IconWithTooltip = ({
@@ -60,9 +57,9 @@ export const UISettings = (): JSX.Element => {
               label="Collapsed"
               additionalClasses={styles['grw-sidebar-mode-icon']}
             >
-              <SidebarCollapsedIcon />
+              <span className="growi-custom-icons">sidebar-collapsed</span>
             </IconWithTooltip>
-            <div className="form-check form-switch ms-2">
+            <div className="form-check form-switch ms-1">
 
               <input
                 id="swSidebarMode"
@@ -74,7 +71,7 @@ export const UISettings = (): JSX.Element => {
               <label className="form-label form-check-label" htmlFor="swSidebarMode"></label>
             </div>
             <IconWithTooltip id="iwt-sidebar-dock" label="Dock" additionalClasses={styles['grw-sidebar-mode-icon']}>
-              <SidebarDockIcon />
+              <span className="growi-custom-icons">sidebar-dock</span>
             </IconWithTooltip>
           </div>
           <div className="ms-2">

+ 4 - 15
apps/app/src/components/Navbar/GrowiNavbarBottom.tsx

@@ -1,11 +1,10 @@
 import React from 'react';
 
+import { useSearchModal } from '~/features/search/client/stores/search';
 import { useIsSearchPage } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
-import { useIsDeviceLargerThanMd, useDrawerOpened } from '~/stores/ui';
-
-import { GlobalSearch } from './GlobalSearch';
+import { useDrawerOpened } from '~/stores/ui';
 
 import styles from './GrowiNavbarBottom.module.scss';
 
@@ -13,10 +12,10 @@ import styles from './GrowiNavbarBottom.module.scss';
 export const GrowiNavbarBottom = (): JSX.Element => {
 
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
-  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
   const { open: openCreateModal } = usePageCreateModal();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: isSearchPage } = useIsSearchPage();
+  const { open: openSearchModal } = useSearchModal();
 
   return (
     <div className={`
@@ -24,15 +23,6 @@ export const GrowiNavbarBottom = (): JSX.Element => {
       ${isDrawerOpened ? styles['grw-navbar-bottom-drawer-opened'] : ''}
       d-md-none d-edit-none d-print-none fixed-bottom`}
     >
-
-      { !isDeviceLargerThanMd && !isSearchPage && (
-        <div id="grw-global-search-collapse" className="grw-global-search collapse bg-dark">
-          <div className="p-3">
-            <GlobalSearch dropup />
-          </div>
-        </div>
-      ) }
-
       <div className="navbar navbar-expand px-4 px-sm-5">
 
         <ul className="navbar-nav flex-grow-1 d-flex align-items-center justify-content-between">
@@ -62,8 +52,7 @@ export const GrowiNavbarBottom = (): JSX.Element => {
                 <a
                   role="button"
                   className="nav-link btn-lg"
-                  data-bs-target="#grw-global-search-collapse"
-                  data-bs-toggle="collapse"
+                  onClick={openSearchModal}
                 >
                   <span className="material-symbols-outlined fs-2">search</span>
                 </a>

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

+ 21 - 0
apps/app/src/components/Page/markdown-drawio-util-for-view.ts

@@ -0,0 +1,21 @@
+/**
+ * return markdown where the drawioData specified by line number params is replaced to the drawioData specified by drawioData param
+ */
+export const replaceDrawioInMarkdown = (drawioData: string, markdown: string, beginLineNumber: number, endLineNumber: number): string => {
+  const splitMarkdown = markdown.split(/\r\n|\r|\n/);
+  const markdownBeforeDrawio = splitMarkdown.slice(0, beginLineNumber - 1);
+  const markdownAfterDrawio = splitMarkdown.slice(endLineNumber);
+
+  let newMarkdown = '';
+  if (markdownBeforeDrawio.length > 0) {
+    newMarkdown += `${markdownBeforeDrawio.join('\n')}\n`;
+  }
+  newMarkdown += '``` drawio\n';
+  newMarkdown += drawioData;
+  newMarkdown += '\n```';
+  if (markdownAfterDrawio.length > 0) {
+    newMarkdown += `\n${markdownAfterDrawio.join('\n')}`;
+  }
+
+  return newMarkdown;
+};

+ 0 - 0
apps/app/src/components/PageEditor/markdown-table-util-for-view.ts → apps/app/src/components/Page/markdown-table-util-for-view.ts


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

+ 6 - 1
apps/app/src/components/PageControls/PageControls.tsx

@@ -15,7 +15,7 @@ import {
 import { toastError } from '~/client/util/toastr';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 import { useTagEditModal, type IPageForPageDuplicateModal } from '~/stores/modal';
-import { EditorMode, useEditorMode } from '~/stores/ui';
+import { EditorMode, useEditorMode, useIsDeviceLargerThanMd } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
 import { useSWRxPageInfo, useSWRxTagsInfo } from '../../stores/page';
@@ -27,6 +27,7 @@ import {
 
 import { BookmarkButtons } from './BookmarkButtons';
 import LikeButtons from './LikeButtons';
+import SearchButton from './SearchButton';
 import SeenUserInfo from './SeenUserInfo';
 import SubscribeButton from './SubscribeButton';
 
@@ -123,6 +124,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: editorMode } = useEditorMode();
+  const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
 
   const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId, shareLinkId);
 
@@ -250,6 +252,9 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
 
   return (
     <div className={`grw-page-controls ${styles['grw-page-controls']} d-flex`} style={{ gap: '2px' }}>
+      { isDeviceLargerThanMd && (
+        <SearchButton />
+      )}
       {revisionId != null && !isViewMode && (
         <Tags
           onClickEditTagsButton={onClickEditTagsButton}

+ 14 - 0
apps/app/src/components/PageControls/SearchButton.module.scss

@@ -0,0 +1,14 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
+@use './button-styles';
+
+.btn-search :global {
+  @extend %btn-basis;
+}
+
+// == Colors
+.btn-search {
+  @include btn-muted.colorize(bs.$success);
+}

+ 28 - 0
apps/app/src/components/PageControls/SearchButton.tsx

@@ -0,0 +1,28 @@
+import React, { useCallback } from 'react';
+
+import { useSearchModal } from '../../features/search/client/stores/search';
+
+import styles from './SearchButton.module.scss';
+
+
+const SearchButton = (): JSX.Element => {
+
+  const { open: openSearchModal } = useSearchModal();
+
+  const searchButtonClickHandler = useCallback(() => {
+    openSearchModal();
+  }, [openSearchModal]);
+
+
+  return (
+    <button
+      type="button"
+      className={`me-3 btn btn-search ${styles['btn-search']}`}
+      onClick={searchButtonClickHandler}
+    >
+      <span className="material-symbols-outlined">search</span>
+    </button>
+  );
+};
+
+export default SearchButton;

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

+ 1 - 1
apps/app/src/components/PageEditor/DrawioCommunicationHelper.ts

@@ -29,7 +29,7 @@ export class DrawioCommunicationHelper {
     this.callbackOpts = callbackOpts;
   }
 
-  onReceiveMessage(event: MessageEvent, drawioMxFile: string): void {
+  onReceiveMessage(event: MessageEvent, drawioMxFile: string | null): void {
 
     // check origin
     if (event.origin != null && this.drawioUri != null) {

+ 22 - 9
apps/app/src/components/PageEditor/DrawioModal.tsx

@@ -4,12 +4,15 @@ import React, {
   useMemo,
 } from 'react';
 
+import { useCodeMirrorEditorIsolated } from '@growi/editor';
+import { useDrawioModalForEditor } from '@growi/editor/src/stores/use-drawio';
 import {
   Modal,
   ModalBody,
 } from 'reactstrap';
 
 import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
+import { replaceFocusedDrawioWithEditor, getMarkdownDrawioMxfile } from '~/components/PageEditor/markdown-drawio-util-for-editor';
 import { useRendererConfig } from '~/stores/context';
 import { useDrawioModal } from '~/stores/modal';
 import { usePersonalSettings } from '~/stores/personal-settings';
@@ -47,6 +50,11 @@ export const DrawioModal = (): JSX.Element => {
   });
 
   const { data: drawioModalData, close: closeDrawioModal } = useDrawioModal();
+  const { data: drawioModalDataInEditor, close: closeDrawioModalInEditor } = useDrawioModalForEditor();
+  const editorKey = drawioModalDataInEditor?.editorKey ?? null;
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(editorKey);
+  const editor = codeMirrorEditor?.view;
+  const isOpenedInEditor = (drawioModalDataInEditor?.isOpened ?? false) && (editor != null);
   const isOpened = drawioModalData?.isOpened ?? false;
 
   const drawioUriWithParams = useMemo(() => {
@@ -78,23 +86,28 @@ export const DrawioModal = (): JSX.Element => {
       return undefined;
     }
 
+    const save = editor != null ? (drawioMxFile: string) => {
+      replaceFocusedDrawioWithEditor(editor, drawioMxFile);
+    } : drawioModalData?.onSave;
+
     return new DrawioCommunicationHelper(
       rendererConfig.drawioUri,
       drawioConfig,
-      { onClose: closeDrawioModal, onSave: drawioModalData?.onSave },
+      { onClose: isOpened ? closeDrawioModal : closeDrawioModalInEditor, onSave: save },
     );
-  }, [closeDrawioModal, drawioModalData?.onSave, rendererConfig]);
+  }, [closeDrawioModal, closeDrawioModalInEditor, drawioModalData?.onSave, editor, isOpened, rendererConfig]);
 
   const receiveMessageHandler = useCallback((event: MessageEvent) => {
     if (drawioModalData == null) {
       return;
     }
 
-    drawioCommunicationHelper?.onReceiveMessage(event, drawioModalData.drawioMxFile);
-  }, [drawioCommunicationHelper, drawioModalData]);
+    const drawioMxFile = editor != null ? getMarkdownDrawioMxfile(editor) : drawioModalData.drawioMxFile;
+    drawioCommunicationHelper?.onReceiveMessage(event, drawioMxFile);
+  }, [drawioCommunicationHelper, drawioModalData, editor]);
 
   useEffect(() => {
-    if (isOpened) {
+    if (isOpened || isOpenedInEditor) {
       window.addEventListener('message', receiveMessageHandler);
     }
     else {
@@ -105,12 +118,12 @@ export const DrawioModal = (): JSX.Element => {
     return function() {
       window.removeEventListener('message', receiveMessageHandler);
     };
-  }, [isOpened, receiveMessageHandler]);
+  }, [isOpened, isOpenedInEditor, receiveMessageHandler]);
 
   return (
     <Modal
-      isOpen={isOpened}
-      toggle={() => closeDrawioModal()}
+      isOpen={isOpened || isOpenedInEditor}
+      toggle={() => (isOpened ? closeDrawioModal() : closeDrawioModalInEditor())}
       backdrop="static"
       className="drawio-modal grw-body-only-modal-expanded"
       size="xl"
@@ -126,7 +139,7 @@ export const DrawioModal = (): JSX.Element => {
         {/* iframe */}
         { drawioUriWithParams != null && (
           <div className="w-100 h-100 position-absolute d-flex">
-            { isOpened && (
+            { (isOpened || isOpenedInEditor) && (
               <iframe
                 src={drawioUriWithParams.href}
                 className="border-0 flex-grow-1"

+ 0 - 179
apps/app/src/components/PageEditor/MarkdownDrawioUtil.js

@@ -1,179 +0,0 @@
-/**
- * Utility for markdown drawio
- */
-class MarkdownDrawioUtil {
-
-  constructor() {
-    this.lineBeginPartOfDrawioRE = /^```(\s.*)drawio$/;
-    this.lineEndPartOfDrawioRE = /^```$/;
-  }
-
-  /**
-   * return the postion of the BOD(beginning of drawio)
-   * (If the BOD is not found after the cursor or the EOD is found before the BOD, return null)
-   */
-  getBod(editor) {
-    const curPos = editor.getCursor();
-    const firstLine = editor.getDoc().firstLine();
-
-    if (this.lineBeginPartOfDrawioRE.test(editor.getDoc().getLine(curPos.line))) {
-      return { line: curPos.line, ch: 0 };
-    }
-
-    let line = curPos.line - 1;
-    let isFound = false;
-    for (; line >= firstLine; line--) {
-      const strLine = editor.getDoc().getLine(line);
-      if (this.lineBeginPartOfDrawioRE.test(strLine)) {
-        isFound = true;
-        break;
-      }
-
-      if (this.lineEndPartOfDrawioRE.test(strLine)) {
-        isFound = false;
-        break;
-      }
-    }
-
-    if (!isFound) {
-      return null;
-    }
-
-    const bodLine = Math.max(firstLine, line);
-    return { line: bodLine, ch: 0 };
-  }
-
-  /**
-   * return the postion of the EOD(end of drawio)
-   * (If the EOD is not found after the cursor or the BOD is found before the EOD, return null)
-   */
-  getEod(editor) {
-    const curPos = editor.getCursor();
-    const lastLine = editor.getDoc().lastLine();
-
-    if (this.lineEndPartOfDrawioRE.test(editor.getDoc().getLine(curPos.line))) {
-      return { line: curPos.line, ch: editor.getDoc().getLine(curPos.line).length };
-    }
-
-    let line = curPos.line + 1;
-    let isFound = false;
-    for (; line <= lastLine; line++) {
-      const strLine = editor.getDoc().getLine(line);
-      if (this.lineEndPartOfDrawioRE.test(strLine)) {
-        isFound = true;
-        break;
-      }
-
-      if (this.lineBeginPartOfDrawioRE.test(strLine)) {
-        isFound = false;
-        break;
-      }
-    }
-
-    if (!isFound) {
-      return null;
-    }
-
-    const eodLine = Math.min(line, lastLine);
-    const lineLength = editor.getDoc().getLine(eodLine).length;
-    return { line: eodLine, ch: lineLength };
-  }
-
-  /**
-   * return boolean value whether the cursor position is in a drawio
-   */
-  isInDrawioBlock(editor) {
-    const bod = this.getBod(editor);
-    const eod = this.getEod(editor);
-    if (bod === null || eod === null) {
-      return false;
-    }
-    return JSON.stringify(bod) !== JSON.stringify(eod);
-  }
-
-  /**
-   * return drawioData instance where the cursor is
-   * (If the cursor is not in a drawio block, return null)
-   */
-  getMarkdownDrawioMxfile(editor) {
-    if (this.isInDrawioBlock(editor)) {
-      const bod = this.getBod(editor);
-      const eod = this.getEod(editor);
-
-      // skip block begin sesion("``` drawio")
-      bod.line++;
-      // skip block end sesion("```")
-      eod.line--;
-      eod.ch = editor.getDoc().getLine(eod.line).length;
-
-      return editor.getDoc().getRange(bod, eod);
-    }
-    return null;
-  }
-
-  replaceFocusedDrawioWithEditor(editor, drawioData) {
-    const curPos = editor.getCursor();
-    const drawioBlock = ['``` drawio', drawioData.toString(), '```'].join('\n');
-    let beginPos;
-    let endPos;
-
-    if (this.isInDrawioBlock(editor)) {
-      beginPos = this.getBod(editor);
-      endPos = this.getEod(editor);
-    }
-    else {
-      beginPos = { line: curPos.line, ch: curPos.ch };
-      endPos = { line: curPos.line, ch: curPos.ch };
-    }
-
-    editor.getDoc().replaceRange(drawioBlock, beginPos, endPos);
-  }
-
-  /**
-   * return markdown where the drawioData specified by line number params is replaced to the drawioData specified by drawioData param
-   * @param {string} drawioData
-   * @param {string} markdown
-   * @param beginLineNumber
-   * @param endLineNumber
-   */
-  replaceDrawioInMarkdown(drawioData, markdown, beginLineNumber, endLineNumber) {
-    const splitMarkdown = markdown.split(/\r\n|\r|\n/);
-    const markdownBeforeDrawio = splitMarkdown.slice(0, beginLineNumber - 1);
-    const markdownAfterDrawio = splitMarkdown.slice(endLineNumber);
-
-    let newMarkdown = '';
-    if (markdownBeforeDrawio.length > 0) {
-      newMarkdown += `${markdownBeforeDrawio.join('\n')}\n`;
-    }
-    newMarkdown += '``` drawio\n';
-    newMarkdown += drawioData;
-    newMarkdown += '\n```';
-    if (markdownAfterDrawio.length > 0) {
-      newMarkdown += `\n${markdownAfterDrawio.join('\n')}`;
-    }
-
-    return newMarkdown;
-  }
-
-  /**
-   * return an array of the starting line numbers of the drawio sections found in markdown
-   */
-  findAllDrawioSection(editor) {
-    const lineNumbers = [];
-    // refs: https://github.com/codemirror/CodeMirror/blob/5.64.0/addon/fold/foldcode.js#L106-L111
-    for (let i = editor.firstLine(), e = editor.lastLine(); i <= e; i++) {
-      const line = editor.getLine(i);
-      const match = this.lineBeginPartOfDrawioRE.exec(line);
-      if (match) {
-        lineNumbers.push(i);
-      }
-    }
-    return lineNumbers;
-  }
-
-}
-
-// singleton pattern
-const instance = new MarkdownDrawioUtil();
-Object.freeze(instance);
-export default instance;

+ 134 - 0
apps/app/src/components/PageEditor/markdown-drawio-util-for-editor.ts

@@ -0,0 +1,134 @@
+import { EditorView } from '@codemirror/view';
+
+const lineBeginPartOfDrawioRE = /^```(\s.*)drawio$/;
+const lineEndPartOfDrawioRE = /^```$/;
+const firstLineNum = 1;
+
+const curPos = (editor: EditorView) => {
+  return editor.state.selection.main.head;
+};
+
+const doc = (editor: EditorView) => {
+  return editor.state.doc;
+};
+
+const lastLineNum = (editor: EditorView) => {
+  return doc(editor).lines;
+};
+
+const getCursorLine = (editor: EditorView) => {
+  return doc(editor).lineAt(curPos(editor));
+};
+
+const getLine = (editor: EditorView, lineNum: number) => {
+  return doc(editor).line(lineNum);
+};
+
+/**
+ * return the postion of the BOD(beginning of drawio)
+ * (If the BOD is not found after the cursor or the EOD is found before the BOD, return null)
+ */
+const getBod = (editor: EditorView) => {
+  const strLine = getCursorLine(editor).text;
+  if (lineBeginPartOfDrawioRE.test(strLine)) {
+    // get the beginning of the line where the cursor is located
+    return getCursorLine(editor).from;
+  }
+
+  let line = getCursorLine(editor).number - 1;
+  let isFound = false;
+  for (; line >= firstLineNum; line--) {
+    const strLine = getLine(editor, line).text;
+    if (lineBeginPartOfDrawioRE.test(strLine)) {
+      isFound = true;
+      break;
+    }
+
+    if (lineEndPartOfDrawioRE.test(strLine)) {
+      isFound = false;
+      break;
+    }
+  }
+
+  if (!isFound) {
+    return null;
+  }
+
+  const botLine = Math.max(firstLineNum, line);
+  return getLine(editor, botLine).from;
+};
+
+/**
+ * return the postion of the EOD(end of drawio)
+ * (If the EOD is not found after the cursor or the BOD is found before the EOD, return null)
+ */
+const getEod = (editor: EditorView) => {
+  const lastLine = lastLineNum(editor);
+
+  const strLine = getCursorLine(editor).text;
+  if (lineEndPartOfDrawioRE.test(strLine)) {
+    // get the end of the line where the cursor is located
+    return getCursorLine(editor).to;
+  }
+
+  let line = getCursorLine(editor).number + 1;
+  let isFound = false;
+  for (; line <= lastLine; line++) {
+    const strLine = getLine(editor, line).text;
+    if (lineEndPartOfDrawioRE.test(strLine)) {
+      isFound = true;
+      break;
+    }
+
+    if (lineBeginPartOfDrawioRE.test(strLine)) {
+      isFound = false;
+      break;
+    }
+  }
+
+  if (!isFound) {
+    return null;
+  }
+
+  const eodLine = Math.min(line, lastLine);
+  return getLine(editor, eodLine).to;
+};
+
+/**
+ * return drawioData instance where the cursor is
+ * (If the cursor is not in a drawio block, return null)
+ */
+export const getMarkdownDrawioMxfile = (editor: EditorView): string | null => {
+  const bod = getBod(editor);
+  const eod = getEod(editor);
+  if (bod == null || eod == null || JSON.stringify(bod) === JSON.stringify(eod)) {
+    return null;
+  }
+
+  // skip block begin sesion("``` drawio")
+  const bodLineNum = doc(editor).lineAt(bod).number + 1;
+  const bodLine = getLine(editor, bodLineNum).from;
+  // skip block end sesion("```")
+  const eodLineNum = doc(editor).lineAt(eod).number - 1;
+  const eodLine = getLine(editor, eodLineNum).to;
+
+  return editor.state.sliceDoc(bodLine, eodLine);
+};
+
+export const replaceFocusedDrawioWithEditor = (editor: EditorView, drawioData: string): void => {
+  const drawioBlock = ['``` drawio', drawioData.toString(), '```'].join('\n');
+  let bod = getBod(editor);
+  let eod = getEod(editor);
+  if (bod == null || eod == null || JSON.stringify(bod) === JSON.stringify(eod)) {
+    bod = curPos(editor);
+    eod = curPos(editor);
+  }
+
+  editor.dispatch({
+    changes: {
+      from: bod,
+      to: eod,
+      insert: drawioBlock,
+    },
+  });
+};

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

+ 7 - 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,7 @@ $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;
+}

+ 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/AppTitle/AppTitle.module.scss

@@ -38,7 +38,7 @@
 // == App title truncation
 .on-subnavigation {
   // set width for truncation
-  $grw-page-controls-width: 226px;
+  $grw-page-controls-width: 280px;
   $grw-page-editor-mode-manager-width: 90px;
   $grw-contextual-subnavigation-padding-right: 12px;
   $gap: 8px;

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