Procházet zdrojové kódy

Merge branch 'dev/7.0.x' into imprv/139425-141028-fix-switch

4kin0ri před 2 roky
rodič
revize
31a6e867cc
67 změnil soubory, kde provedl 368 přidání a 424 odebrání
  1. 29 1
      CHANGELOG.md
  2. 0 4
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.module.scss
  3. 2 4
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  4. 2 2
      apps/app/public/static/locales/en_US/translation.json
  5. 2 2
      apps/app/public/static/locales/ja_JP/translation.json
  6. 4 4
      apps/app/public/static/locales/zh_CN/translation.json
  7. 7 7
      apps/app/src/components/Admin/Security/LdapAuthTest.tsx
  8. 1 1
      apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  9. 23 17
      apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  10. 17 4
      apps/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  11. 12 3
      apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  12. 2 1
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.module.scss
  13. 2 4
      apps/app/src/components/CustomNavigation/CustomNav.module.scss
  14. 1 1
      apps/app/src/components/CustomNavigation/CustomNav.tsx
  15. 0 19
      apps/app/src/components/Icons/CompressIcon.tsx
  16. 0 22
      apps/app/src/components/Icons/CreatePageIcon.tsx
  17. 0 19
      apps/app/src/components/Icons/ExpandIcon.tsx
  18. 3 21
      apps/app/src/components/Icons/FolderIcon.tsx
  19. 0 16
      apps/app/src/components/Icons/FolderPlusIcon.tsx
  20. 0 13
      apps/app/src/components/Icons/KeyboardReturnEnterIcon.tsx
  21. 0 20
      apps/app/src/components/Icons/MoonIcon.jsx
  22. 0 16
      apps/app/src/components/Icons/PagePreviewIcon.jsx
  23. 0 15
      apps/app/src/components/Icons/ReturnTopIcon.tsx
  24. 0 28
      apps/app/src/components/Icons/SunIcon.jsx
  25. 2 2
      apps/app/src/components/Me/ApiSettings.tsx
  26. 1 1
      apps/app/src/components/Me/AssociateModal.tsx
  27. 2 2
      apps/app/src/components/Me/ColorModeSettings.tsx
  28. 10 10
      apps/app/src/components/Me/ExternalAccountLinkedMe.jsx
  29. 3 4
      apps/app/src/components/Me/InAppNotificationSettings.tsx
  30. 6 6
      apps/app/src/components/Me/PasswordSettings.jsx
  31. 2 2
      apps/app/src/components/Me/ProfileImageSettings.tsx
  32. 9 9
      apps/app/src/components/Me/QuestionnaireSettings.tsx
  33. 2 2
      apps/app/src/components/Me/UISettings.tsx
  34. 1 1
      apps/app/src/components/PageEditor/Cheatsheet.tsx
  35. 1 2
      apps/app/src/components/PageEditor/LinkEditModal.tsx
  36. 6 6
      apps/app/src/components/PageSideContents/PageAccessoriesControl.module.scss
  37. 1 1
      apps/app/src/components/PageSideContents/PageAccessoriesControl.tsx
  38. 2 2
      apps/app/src/components/PageTags/PageTags.tsx
  39. 1 1
      apps/app/src/components/PageTags/RenderTagLabels.tsx
  40. 1 1
      apps/app/src/components/PageTags/TagEditModal.tsx
  41. 9 8
      apps/app/src/components/PageTags/TagLabels.module.scss
  42. 23 0
      apps/app/src/components/PageTags/TagsInput.module.scss
  43. 12 2
      apps/app/src/components/PageTags/TagsInput.tsx
  44. 3 2
      apps/app/src/components/ReactMarkdownComponents/NextLink.tsx
  45. 1 2
      apps/app/src/components/ShortcutsModal.tsx
  46. 1 2
      apps/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx
  47. 33 0
      apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.module.scss
  48. 28 20
      apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.tsx
  49. 10 10
      apps/app/src/components/UsersHomepageFooter.module.scss
  50. 5 15
      apps/app/src/components/UsersHomepageFooter.tsx
  51. 18 3
      apps/app/src/features/external-user-group/client/components/ExternalUserGroup/ExternalUserGroupManagement.tsx
  52. 8 6
      apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts
  53. 1 1
      apps/app/src/pages/installer.page.tsx
  54. 1 1
      apps/app/src/pages/me/[[...path]].page.tsx
  55. 8 6
      apps/app/src/server/routes/apiv3/user-group.js
  56. 2 3
      apps/app/src/styles/_fonts.scss
  57. 34 34
      apps/app/src/styles/_override-rbt.scss
  58. 4 0
      apps/app/src/styles/molecules/_list-group-item.scss
  59. 1 0
      packages/custom-icons/svg/drawer_io.svg
  60. 1 0
      packages/custom-icons/svg/external_link.svg
  61. 1 0
      packages/custom-icons/svg/format_quote.svg
  62. 1 0
      packages/custom-icons/svg/header.svg
  63. 1 8
      packages/custom-icons/svg/recently_created.svg
  64. 1 1
      packages/editor/package.json
  65. 1 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx
  66. 2 2
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx
  67. 1 1
      yarn.lock

+ 29 - 1
CHANGELOG.md

@@ -1,9 +1,36 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.3.0...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.3.1...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.3.1](https://github.com/weseek/growi/compare/v6.3.0...v6.3.1) - 2024-02-01
+
+### 💎 Features
+
+* feat: Normalize duplicated root pages to valid paths when server startup (#8414) @miya
+
+### 🚀 Improvement
+
+* imprv: Use unzip stream instead of unzipper (#8378) @ryu-sato
+* imprv: Allow plugin that contain slashes in the branch name to be installed (#8359) @ryu-sato
+
+### 🐛 Bug Fixes
+
+* fix: Page being able to delete completely when not allowed (#8374) @arafubeatbox
+* fix: Logs are not saved when viewing the page (#8406) @miya
+* fix: Preventing duplication of `/user/username` pages (#8413) @WNomunomu
+* fix: Non-admin user cannot rename pages v63x (#8410) @jam411
+* fix: Duplicate root pages are created unintentionally (#8404) @miya
+* fix: Configured auditlog environment variables are not reflected in the administration screen (#8383) @miya
+* fix: plugin is broken after unzipping (#8358) @ryu-sato
+* fix: Keycloak group sync config not loaded on sync execution (#8339) @arafubeatbox
+
+### 🧰 Maintenance
+
+* support: React Testing Library (#8393) @miya
+* ci(deps-dev): bump vite from 4.5.1 to 4.5.2 (#8392) @dependabot
+
 ## [v6.3.0](https://github.com/weseek/growi/compare/v6.2.5...v6.3.0) - 2023-12-14
 
 ### BREAKING CHANGES
@@ -50,6 +77,7 @@
 * imprv: Allow deletion of user homepage when the user is deleted (#8224) @jam411
 
 ### 🐛 Bug Fixes
+
 * fix: Certify shared page attachment middleware (6.2.x) (#8256) @yuki-takei
 
 ### 🧰 Maintenance

+ 0 - 4
apps/app/_obsolete/src/components/Navbar/GrowiNavbar.module.scss

@@ -62,10 +62,6 @@
     background: rgba(0, 0, 0, 0.2);
   }
 
-  .grw-email-sm {
-    font-size: 0.75em;
-  }
-
   .grw-notification-dropdown {
     .dropdown-menu {
       max-width: 70vw;

+ 2 - 4
apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx

@@ -10,10 +10,8 @@ import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
 import { Themes, useNextThemes } from '~/stores/use-next-themes';
 
-import MoonIcon from '../Icons/MoonIcon';
 import SidebarDockIcon from '../Icons/SidebarDockIcon';
 import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
-import SunIcon from '../Icons/SunIcon';
 
 type AppearanceModeDropdownProps = {
   isAuthenticated: boolean,
@@ -132,7 +130,7 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
               <div className="justify-content-center">
                 <div className="col-auto d-flex align-items-center">
                   <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
-                    <SunIcon />
+                  <span className="material-symbols-outlined">light_mode</span>
                   </IconWithTooltip>
                   <div className="form-check form-switch form-check-secondary ms-2">
                     <input
@@ -146,7 +144,7 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
                     <label className="form-label form-check-label" htmlFor="swUserPreference"></label>
                   </div>
                   <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
-                    <MoonIcon />
+                  <span className="material-symbols-outlined">dark_mode</span>
                   </IconWithTooltip>
                 </div>
               </div>

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

@@ -112,7 +112,7 @@
   "Create under": "Create page under below:",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
-  "See_more_detail_on_new_schema": "See more detail on <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <i class='icon-share-alt'></i> ",
+  "See_more_detail_on_new_schema": "See more detail on <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <span className='growi-custom-icons'>external_link</span> ",
   "external_account_management": "External Account Management",
   "UserGroup": "UserGroup",
   "Basic Settings": "Basic Settings",
@@ -559,7 +559,7 @@
     "alert_desc1": "On this page, you can select pages with the checkbox and batch convert to the new v5 compatible format from the \"Bulk operation\" button at the top of the screen.",
     "nopages_title": "Congratulations. Ready to use GROWI v5!",
     "nopages_desc1": "Now all the pages you can manage seem to be in v5 compatible format.",
-    "detail_info": "See the detail information from <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>Upgrading GROWI to v5.0.x <i class='icon-share-alt'></i></a>.",
+    "detail_info": "See the detail information from <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>Upgrading GROWI to v5.0.x <span className='growi-custom-icons'>external_link</span></a>.",
     "modal": {
       "title": "Convert to new v5 compatible format",
       "converting_pages": "Converting pages",

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

@@ -111,7 +111,7 @@
   "Create under": "ページを以下に作成",
   "V5 Page Migration": "V5 互換形式 への変換",
   "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて",
-  "See_more_detail_on_new_schema": "詳しくは<a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html#新しい-v5-互換形式について' target='_blank'>{{title}}</a><i class='icon-share-alt'></i>を参照ください。",
+  "See_more_detail_on_new_schema": "詳しくは<a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html#新しい-v5-互換形式について' target='_blank'>{{title}}</a><span className='growi-custom-icons'>external_link</span>を参照ください。",
   "external_account_management": "外部アカウント管理",
   "UserGroup": "グループ",
   "Basic Settings": "基本設定",
@@ -592,7 +592,7 @@
     "alert_desc1": "このページでは、チェックボックスでページを選択し、画面上部の「一括操作」ボタンから新しい v5 互換形式に一括変換できます。",
     "nopages_title": "おめでとうございます。GROWI v5 を使う準備が完了しました!",
     "nopages_desc1": "今あなたが管理可能なページはすべて v5 互換形式になっているようです。",
-    "detail_info": "詳しくは <a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>GROWI v5.0.x へのアップグレード  <i class='icon-share-alt'></i></a> を参照ください。",
+    "detail_info": "詳しくは <a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>GROWI v5.0.x へのアップグレード <span className='growi-custom-icons'>external_link</span></a> を参照ください。",
     "modal": {
       "title": "新しい v5 互換形式への変換",
       "converting_pages": "以下のページを変換します",

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

@@ -117,9 +117,9 @@
   "Create under": "Create page under below:",
   "V5 Page Migration": "转换为V5的兼容性",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
-  "See_more_detail_on_new_schema": "更多详情请见<a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'> {{title}}</a> <i class='icon-share-alt'></i> ",
-  "Markdown Settings": "Markdown设置",
-  "external_account_management": "外部账户管理",
+  "See_more_detail_on_new_schema": "更多详情请见<a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'> {{title}}</a> <span className='growi-custom-icons'>external_link</span> ",
+	"Markdown Settings": "Markdown设置",
+	"external_account_management": "外部账户管理",
   "UserGroup": "用户组",
   "ChildUserGroup": "儿童用户组",
   "Basic Settings": "基础设置",
@@ -562,7 +562,7 @@
     "alert_desc1": "在这一页,你可以用复选框选择页面,并通过屏幕上方的批量操作按钮批量转换为新的v5兼容格式。",
     "nopages_title": "恭喜你。准备使用GROWI v5!",
     "nopages_desc1": "现在你能管理的所有页面似乎都是v5兼容的格式。",
-    "detail_info": "请参见 <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>升级GROWI到v5.0.x <i class='icon-share-alt'></i></a>.的详细内容。",
+    "detail_info": "请参见 <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>升级GROWI到v5.0.x <span className='growi-custom-icons'>external_link</span></a>.的详细内容。",
     "modal": {
       "title": "转换为新的v5兼容格式",
       "converting_pages": "转换页面",

+ 7 - 7
apps/app/src/components/Admin/Security/LdapAuthTest.tsx

@@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next';
 
 import { apiPost } from '~/client/util/apiv1-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { IResTestLdap } from '~/interfaces/ldap';
+import type { IResTestLdap } from '~/interfaces/ldap';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:security:AdminLdapSecurityContainer');
@@ -89,8 +89,8 @@ export const LdapAuthTest = (props: LdapAuthTestProps): JSX.Element => {
     <React.Fragment>
       {successMessage !== '' && <div className="alert alert-success">{successMessage}</div>}
       {errorMessage !== '' && <div className="alert alert-warning">{errorMessage}</div>}
-      <div className="row">
-        <label htmlFor="username" className="col-3 col-form-label">{t('username')}</label>
+      <div className="row mt-3">
+        <label htmlFor="username" className="col-3 col-form-label text-end">{t('username')}</label>
         <div className="col-6">
           <input
             className="form-control"
@@ -101,8 +101,8 @@ export const LdapAuthTest = (props: LdapAuthTestProps): JSX.Element => {
           />
         </div>
       </div>
-      <div className="row">
-        <label htmlFor="password" className="col-3 col-form-label">{t('Password')}</label>
+      <div className="row mt-3">
+        <label htmlFor="password" className="col-3 col-form-label text-end">{t('Password')}</label>
         <div className="col-6">
           <input
             className="form-control"
@@ -115,12 +115,12 @@ export const LdapAuthTest = (props: LdapAuthTestProps): JSX.Element => {
         </div>
       </div>
 
-      <div>
+      <div className="mt-4">
         <label className="form-label"><h5>Logs</h5></label>
         <textarea id="taLogs" className="col form-control" rows={4} value={logs} readOnly />
       </div>
 
-      <div>
+      <div className="mt-4">
         <button type="button" className="btn btn-outline-secondary offset-5 col-2" onClick={testLdapCredentials}>Test</button>
       </div>
     </React.Fragment>

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

@@ -471,7 +471,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                           target="_blank"
                           rel="noreferer noreferrer"
                         >
-                          Apache Lucene - Query Parser Syntax <i className="icon-share-alt"></i>
+                          Apache Lucene - Query Parser Syntax <span className="growi-custom-icons">external_link</span>
                         </a>.
                       </p>
                       <div className="accordion" id="accordionId">

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

@@ -1,7 +1,9 @@
 import type { FC } from 'react';
 import React, { useCallback, useState, useMemo } from 'react';
 
-import type { IUserGroupHasId } from '@growi/core';
+import {
+  getIdForRef, isPopulated, type IGrantedGroup, type IUserGroupHasId,
+} from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
@@ -18,9 +20,9 @@ import { PageActionOnGroupDelete } from '~/interfaces/user-group';
  * @extends {React.Component}
  */
 type Props = {
-  userGroups: IUserGroupHasId[],
+  userGroups: IGrantedGroup[],
   deleteUserGroup?: IUserGroupHasId,
-  onDelete?: (deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroupId: string) => Promise<void> | void,
+  onDelete?: (deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroup: IGrantedGroup | null) => Promise<void> | void,
   isShow: boolean,
   onHide?: () => Promise<void> | void,
 };
@@ -71,14 +73,14 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
    * State
    */
   const [actionName, setActionName] = useState<PageActionOnGroupDelete | null>(null);
-  const [transferToUserGroupId, setTransferToUserGroupId] = useState<string>('');
+  const [transferToUserGroup, setTransferToUserGroup] = useState<IGrantedGroup | null>(null);
 
   /*
    * Function
    */
   const resetStates = useCallback(() => {
     setActionName(null);
-    setTransferToUserGroupId('');
+    setTransferToUserGroup(null);
   }, []);
 
   const toggleHandler = useCallback(() => {
@@ -97,8 +99,9 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
 
   const handleGroupChange = useCallback((e) => {
     const transferToUserGroupId = e.target.value;
-    setTransferToUserGroupId(transferToUserGroupId);
-  }, []);
+    const selectedGroup = userGroups.find(group => getIdForRef(group.item) === transferToUserGroupId) ?? null;
+    setTransferToUserGroup(selectedGroup);
+  }, [userGroups]);
 
   const handleSubmit = useCallback((e) => {
     if (onDelete == null || deleteUserGroup == null || actionName == null) {
@@ -110,9 +113,9 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
     onDelete(
       deleteUserGroup._id,
       actionName,
-      transferToUserGroupId,
+      transferToUserGroup,
     );
-  }, [onDelete, deleteUserGroup, actionName, transferToUserGroupId]);
+  }, [onDelete, deleteUserGroup, actionName, transferToUserGroup]);
 
   const renderPageActionSelector = useCallback(() => {
     const options = availableOptions.map((opt) => {
@@ -139,28 +142,31 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
     }
 
     const groups = userGroups.filter((group) => {
-      return group._id !== deleteUserGroup._id;
+      return getIdForRef(group.item) !== deleteUserGroup._id;
     });
 
     const options = groups.map((group) => {
-      return <option key={group._id} value={group._id}>{group.name}</option>;
-    });
+      const groupId = getIdForRef(group.item);
+      const groupName = isPopulated(group.item) ? group.item.name : null;
+      return { id: groupId, name: groupName };
+    }).filter(obj => obj.name != null)
+      .map(obj => <option key={obj.id} value={obj.id}>{obj.name}</option>);
 
     const defaultOptionText = groups.length === 0 ? t('admin:user_group_management.delete_modal.no_groups')
       : t('admin:user_group_management.delete_modal.select_group');
 
     return (
       <select
-        name="transferToUserGroupId"
+        name="transferToUserGroup"
         className={`form-control ${actionName === PageActionOnGroupDelete.transfer ? '' : 'd-none'}`}
-        value={transferToUserGroupId}
+        value={transferToUserGroup != null ? getIdForRef(transferToUserGroup.item) : ''}
         onChange={handleGroupChange}
       >
         <option value="" disabled>{defaultOptionText}</option>
         {options}
       </select>
     );
-  }, [deleteUserGroup, userGroups, t, actionName, transferToUserGroupId, handleGroupChange]);
+  }, [deleteUserGroup, userGroups, t, actionName, transferToUserGroup, handleGroupChange]);
 
   const validateForm = useCallback(() => {
     let isValid = true;
@@ -169,11 +175,11 @@ export const UserGroupDeleteModal: FC<Props> = (props: Props) => {
       isValid = false;
     }
     else if (actionName === PageActionOnGroupDelete.transfer) {
-      isValid = transferToUserGroupId !== '';
+      isValid = transferToUserGroup != null;
     }
 
     return isValid;
-  }, [actionName, transferToUserGroupId]);
+  }, [actionName, transferToUserGroup]);
 
   return (
     <Modal className="modal-md" isOpen={props.isShow} toggle={toggleHandler}>

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

@@ -1,16 +1,19 @@
 import type { FC } from 'react';
 import React, { useState, useCallback } from 'react';
 
-import type { IUserGroup, IUserGroupHasId } from '@growi/core';
+import {
+  GroupType, getIdForRef, type IGrantedGroup, type IUserGroup, type IUserGroupHasId,
+} from '@growi/core';
 import dynamic from 'next/dynamic';
 import { useTranslation } from 'react-i18next';
 
 import { apiv3Delete, apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { ExternalGroupManagement } from '~/features/external-user-group/client/components/ExternalUserGroup/ExternalUserGroupManagement';
+import { useSWRxExternalUserGroupList } from '~/features/external-user-group/client/stores/external-user-group';
+import type { PageActionOnGroupDelete } from '~/interfaces/user-group';
 import { useIsAclEnabled } from '~/stores/context';
 import { useSWRxUserGroupList, useSWRxChildUserGroupList, useSWRxUserGroupRelationList } from '~/stores/user-group';
-import { PageActionOnGroupDelete } from '~/interfaces/user-group';
 
 
 const UserGroupDeleteModal = dynamic(() => import('./UserGroupDeleteModal').then(mod => mod.UserGroupDeleteModal), { ssr: false });
@@ -26,7 +29,14 @@ export const UserGroupPage: FC = () => {
    * Fetch
    */
   const { data: userGroupList, mutate: mutateUserGroups } = useSWRxUserGroupList();
+  const { data: externalUserGroupList } = useSWRxExternalUserGroupList();
   const userGroups = userGroupList != null ? userGroupList : [];
+  const userGroupsForDeleteModal: IGrantedGroup[] = userGroups.map((group) => {
+    return { item: group, type: GroupType.userGroup };
+  });
+  const externalUserGroupsForDeleteModal: IGrantedGroup[] = externalUserGroupList != null ? externalUserGroupList.map((group) => {
+    return { item: group, type: GroupType.externalUserGroup };
+  }) : [];
   const userGroupIds = userGroups.map(group => group._id);
 
   const { data: userGroupRelationList } = useSWRxUserGroupRelationList(userGroupIds);
@@ -128,11 +138,14 @@ export const UserGroupPage: FC = () => {
     }
   }, [t, mutateUserGroups, hideUpdateModal]);
 
-  const deleteUserGroupById = useCallback(async(deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroupId: string) => {
+  const deleteUserGroupById = useCallback(async(deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroup: IGrantedGroup | null) => {
+    const transferToUserGroupId = transferToUserGroup != null ? getIdForRef(transferToUserGroup.item) : null;
+    const transferToUserGroupType = transferToUserGroup != null ? transferToUserGroup.type : null;
     try {
       await apiv3Delete(`/user-groups/${deleteGroupId}`, {
         actionName,
         transferToUserGroupId,
+        transferToUserGroupType,
       });
 
       // sync
@@ -189,7 +202,7 @@ export const UserGroupPage: FC = () => {
       />
 
       <UserGroupDeleteModal
-        userGroups={userGroups}
+        userGroups={userGroupsForDeleteModal.concat(externalUserGroupsForDeleteModal)}
         deleteUserGroup={selectedUserGroup}
         onDelete={deleteUserGroupById}
         isShow={isDeleteModalShown}

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

@@ -2,7 +2,9 @@ import React, {
   useState, useCallback, useEffect, useMemo,
 } from 'react';
 
-import type { IUserGroup, IUserGroupHasId } from '@growi/core';
+import {
+  GroupType, getIdForRef, type IGrantedGroup, type IUserGroup, type IUserGroupHasId,
+} from '@growi/core';
 import { objectIdUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
@@ -85,6 +87,10 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
 
   const { data: childUserGroupsList, mutate: mutateChildUserGroups, updateChild } = useChildUserGroupList(currentUserGroupId, isExternalGroup);
   const childUserGroups = childUserGroupsList != null ? childUserGroupsList.childUserGroups : [];
+  const childUserGroupsForDeleteModal: IGrantedGroup[] = childUserGroups.map((group) => {
+    const groupType = isExternalGroup ? GroupType.externalUserGroup : GroupType.userGroup;
+    return { item: group, type: groupType };
+  });
   const grandChildUserGroups = childUserGroupsList != null ? childUserGroupsList.grandChildUserGroups : [];
   const childUserGroupIds = childUserGroups.map(group => group._id);
 
@@ -297,12 +303,15 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
     setDeleteModalShown(false);
   }, [setSelectedUserGroup, setDeleteModalShown]);
 
-  const deleteChildUserGroupById = useCallback(async(deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroupId: string) => {
+  const deleteChildUserGroupById = useCallback(async(deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroup: IGrantedGroup | null) => {
     const url = isExternalGroup ? `/external-user-groups/${deleteGroupId}` : `/user-groups/${deleteGroupId}`;
+    const transferToUserGroupId = transferToUserGroup != null ? getIdForRef(transferToUserGroup.item) : null;
+    const transferToUserGroupType = transferToUserGroup != null ? transferToUserGroup.type : null;
     try {
       const res = await apiv3Delete(url, {
         actionName,
         transferToUserGroupId,
+        transferToUserGroupType,
       });
 
       // sync
@@ -449,7 +458,7 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
       />
 
       <UserGroupDeleteModal
-        userGroups={childUserGroups}
+        userGroups={childUserGroupsForDeleteModal}
         deleteUserGroup={selectedUserGroup}
         onDelete={deleteChildUserGroupById}
         isShow={isDeleteModalShown}

+ 2 - 1
apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.module.scss

@@ -4,5 +4,6 @@
 }
 
 .material-symbols-outlined {
-  font-size: inherit;
+  font-size: 1em;
+  line-height: inherit;
 }

+ 2 - 4
apps/app/src/components/CustomNavigation/CustomNav.module.scss

@@ -1,3 +1,5 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
 .grw-custom-nav-tab :global {
   .nav-title {
     flex-wrap: nowrap;
@@ -13,8 +15,4 @@
     transition: 0.3s ease-in-out;
   }
 
-  .material-symbols-outlined {
-    margin-right: 6px;
-    font-size: 18px;
-  }
 }

+ 1 - 1
apps/app/src/components/CustomNavigation/CustomNav.tsx

@@ -183,7 +183,7 @@ export const CustomNavTab = (props: CustomNavTabProps): JSX.Element => {
                 className={`p-0 ${isActive ? 'active' : inactiveClassnames.join(' ')}`}
               >
                 <NavLink type="button" key={key} innerRef={elm => registerNavLink(key, elm)} disabled={!isLinkEnabled} onClick={() => navLinkClickHandler(key)}>
-                  { Icon != null && <Icon /> } {i18n}
+                  { Icon != null && <span className="me-1"><Icon /></span> } {i18n}
                 </NavLink>
               </NavItem>
             );

+ 0 - 19
apps/app/src/components/Icons/CompressIcon.tsx

@@ -1,19 +0,0 @@
-import React from 'react';
-
-export const CompressIcon = ():JSX.Element => {
-  return (
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      width="18"
-      height="18"
-      viewBox="0 0 45 45"
-    >
-      <path
-        fill="currentColor"
-        d="M22.45 44v-7.9l-3.85 3.8-2.1-2.1 7.45-7.4 7.35 7.4-2.1
-            2.1-3.75-3.8V44ZM8.05 27.5v-3H40v3Zm0-6.05v-3H40v3Zm15.9-5.85-7.4-7.4 2.1-2.1
-            3.75 3.8V2h3v7.9l3.85-3.8 2.1 2.1Z"
-      />
-    </svg>
-  );
-};

+ 0 - 22
apps/app/src/components/Icons/CreatePageIcon.tsx

@@ -1,22 +0,0 @@
-import React from 'react';
-
-export const CreatePageIcon = (): JSX.Element => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 27 30"
-  >
-    <path
-      d="M22.81,8.2a4.2,4.2,0,0,0,1.36-2.95,4,4,0,0,0-1.43-2.81,4.53,4.53,0,0,0-1.28-.89,3.26,3.26,0,
-      0,0-1.37-.31,4,4,0,0,0-2.91,1.29q-.42.4-14.83,14.84a.7.7,0,0,0-.26.33c-.07.26-.72,2.46-2,6.58a.73.73,0,
-      0,0,.3,1,.78.78,0,0,0,.7,0c3.3-1.08,5.45-1.76,6.47-2.06A.57.57,0,0,0,7.91,23l8.5-8.42Q22.25,8.81,22.81,8.2ZM1.93,
-      23.44c.16-.44,1.39-4.39,1.5-4.78A4.93,4.93,0,0,1,5.59,20a4.53,4.53,0,0,1,1.12,1.87Zm15-18.52a4.7,4.7,0,0,1,2.16,1.31,5.08,5.08,
-      0,0,1,.72,1,5.3,5.3,0,0,1,.37.8c.05.17.09.34.13.51Q17.19,11.65,8,20.79a6.42,6.42,0,0,0-1.29-1.92,6.67,6.67,0,0,0-2.2-1.48Zm4.64,
-      2.37a6.36,6.36,0,0,0-1.36-2.13,6.61,6.61,0,0,0-2.12-1.43s.29-.28.41-.38A3,3,0,0,1,19.17,3a2,2,0,0,1,.9-.21A1.87,1.87,0,0,1,20.9,3a2.53,2.53,0,0,
-      1,.79.56,3.81,3.81,0,0,1,.71.89,1.87,1.87,0,0,1,.25.87,2.75,2.75,0,0,1-.94,1.83Z"
-    />
-    <path d="M26.41,20.05H22.84V16.48a.72.72,0,0,0-1.43,0v3.57H17.84a.72.72,0,0,0,0,1.43h3.57v3.57a.72.72,0,0,0,
-    1.43.17V21.48h3.57a.72.72,0,1,0,.17-1.43A.48.48,0,0,0,26.41,20.05Z"
-    />
-    <rect fillOpacity="0" width="27" height="27" />
-  </svg>
-);

+ 0 - 19
apps/app/src/components/Icons/ExpandIcon.tsx

@@ -1,19 +0,0 @@
-import React from 'react';
-
-export const ExpandIcon = (): JSX.Element => {
-  return (
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      width="18"
-      height="18"
-      viewBox="0 0 45 45"
-    >
-      <path
-        fill="currentColor"
-        d="M8.1 44v-3h31.8v3Zm16-4.5-7.6-7.6 2.15-2.15
-            3.95 3.95V14.3l-3.95 3.95-2.15-2.15 7.6-7.6 7.6 7.6-2.15
-            2.15-3.95-3.95v19.4l3.95-3.95 2.15 2.15ZM8.1 7V4h31.8v3Z"
-      />
-    </svg>
-  );
-};

+ 3 - 21
apps/app/src/components/Icons/FolderIcon.tsx

@@ -9,28 +9,10 @@ export const FolderIcon = (props: Props): JSX.Element => {
   return (
     <>
       {!isOpen ? (
-        <svg
-          width="20"
-          height="20"
-          viewBox="0 0 24 24"
-        >
-          <path
-            fill="currentColor"
-            d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z"
-          />
-        </svg>
+        <span className="material-symbols-outlined">folder_open</span>
+
       ) : (
-        <svg
-          width="20"
-          height="20"
-          viewBox="0 0 24 24"
-        >
-          <path
-            fill="currentColor"
-            d="M6.1,10L4,18V8H21A2,2 0 0,0 19,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,
-            20H19C19.9,20 20.7,19.4 20.9,18.5L23.2,10H6.1M19,18H6L7.6,12H20.6L19,18Z"
-          />
-        </svg>
+        <span className="material-symbols-outlined">folder</span>
       )
       }
     </>

+ 0 - 16
apps/app/src/components/Icons/FolderPlusIcon.tsx

@@ -1,16 +0,0 @@
-import React from 'react';
-
-export const FolderPlusIcon = (): JSX.Element => (
-  <svg
-    width="18"
-    height="18"
-    viewBox="0 0 24 24"
-  >
-    <path
-      fill="currentColor"
-      d="M13 19C13 19.34 13.04 19.67 13.09 20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.89 4 4 4H10L12 6H20C21.1 6 22
-      6.89 22 8V13.81C21.39 13.46 20.72 13.22 20 13.09V8H4V18H13.09C13.04 18.33 13 18.66 13 19M20 18V15H18V18H15V20H18V23H20V20H23V18H20Z"
-    />
-
-  </svg>
-);

+ 0 - 13
apps/app/src/components/Icons/KeyboardReturnEnterIcon.tsx

@@ -1,13 +0,0 @@
-import React from 'react';
-
-const KeyboardReturnEnterIcon = ():JSX.Element => (
-  <svg xmlns="http://www.w3.org/2000/svg" width="20px" viewBox="0 0 34 21">
-    <g id="ba5f4106-f870-416b-bb0c-2580c9a76268">
-      <g id="1def15e1-5198-4ca2-9457-3b509e83053f">
-        <polygon points="31 0 31 9 5 9 11.8 1.8 10 0 0 10.5 10 21 11.8 19.2 5 12 34 12 34 0 31 0" />
-      </g>
-    </g>
-  </svg>
-);
-
-export default KeyboardReturnEnterIcon;

+ 0 - 20
apps/app/src/components/Icons/MoonIcon.jsx

@@ -1,20 +0,0 @@
-import React from 'react';
-
-const MoonIcon = () => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 23 23"
-  >
-    <g transform="translate(-923.5 -688.5)">
-      <rect width="23" height="23" fill="none" transform="translate(923.5 688.5)" />
-      <path d="M934.893,710.532a10.646,10.646,0,0,1-10.378-8.416.7.7,0,0,1,1.138-.686,
-       7.621,7.621,0,0,0,10.721-10.744.7.7,0,0,1,.683-1.14,10.6,10.6,0,0,1-2.164,
-        20.986Zm-8.417-6.9A9.2,9.2,0,1,0,938.583,691.5a9.028,9.028,0,0,1-12.107,12.133Z"
-      />
-    </g>
-  </svg>
-
-);
-
-
-export default MoonIcon;

+ 0 - 16
apps/app/src/components/Icons/PagePreviewIcon.jsx

@@ -1,16 +0,0 @@
-import React from 'react';
-
-const PagePreviewIcon = () => (
-  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
-    <defs></defs>
-    <rect width="23" height="23" fillOpacity="0" />
-    <path d="M10.94,20.33H3.4V1.38H8.82V8.82h7.44v1.35a6.16,6.16,0,0,1,1.35.47V6.79L10.85,0H3.4a1.3,1.3,0,0,0-1,.39,1.3,1.3,0,0,0-.39,1v19A1.33,
-  1.33,0,0,0,3.4,21.68h9.84A5.94,5.94,0,0,1,10.94,20.33ZM10.17,1.38h.13l6,6v.11H10.17Z"
-    />
-    <path d="M21.87,22.14,18.75,19a4.74,4.74,0,0,0,1.1-3,4.89,4.89,0,1,0-1.8,3.73l3.11,3.11a.5.5,0,0,0,.35.15.51.51,0,0,0,.36-.15A.5.5,
-  0,0,0,21.87,22.14ZM15,19.57A3.57,3.57,0,1,1,18.59,16,3.58,3.58,0,0,1,15,19.57Z"
-    />
-  </svg>
-);
-
-export default PagePreviewIcon;

+ 0 - 15
apps/app/src/components/Icons/ReturnTopIcon.tsx

@@ -1,15 +0,0 @@
-import React from 'react';
-
-export const ReturnTopIcon = (): JSX.Element => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 23 23"
-  >
-    <path d="M.41,18.71a.82.82,0,0,0,0,.26.71.71,0,0,0,0,.29.5.5,0,0,0,.16.22.66.66,0,0,0,.51.21.67.67,0,0,0,
-    .51-.21l9.57-9.56,9.43,9.43a.71.71,0,0,0,.51.21.68.68,0,0,0,.51-.21.72.72,
-    0,0,0,0-1l-9.94-10a.78.78,0,0,0-.51-.19.76.76,0,0,0-.5.19L.58,18.46A.85.85,0,0,0,.41,18.71Z"
-    />
-    <path d="M22.35,4.61H.65a.65.65,0,0,1,0-1.3h21.7a.65.65,0,1,1,0,1.3Z" />
-    <rect fillOpacity="0" width="23" height="23" />
-  </svg>
-);

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

@@ -1,28 +0,0 @@
-import React from 'react';
-
-const SunIcon = () => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    viewBox="0 0 23 23"
-  >
-    <g transform="translate(-888.497 -688.492)">
-      <rect width="23" height="23" transform="translate(888.503 688.509)" fillOpacity="0" />
-      <path d="M900,695.489a4.5,4.5,0,1,1-4.5,4.5,4.5,4.5,0,0,1,4.5-4.5m0-1.408a5.9,5.9,0,1,0,5.9,5.9,5.91,5.91,0,0,0-5.9-5.9Z" />
-      <path d="M893.968,694.573a.6.6,0,0,1-.426-.176l-1.681-1.681a.6.6,0,0,1,.853-.852l1.681,1.68a.6.6,0,0,1-.427,1.029Z" />
-      <path d="M907.707,708.295a.6.6,0,0,1-.427-.177l-1.681-1.68a.6.6,0,0,1,.854-.853l1.68,1.681a.6.6,0,0,1-.426,1.029Z" />
-
-      <path d="M899.991,692.074a.6.6,0,0,1-.6-.6v-2.377a.6.6,0,0,1,1.206,0v2.377A.6.6,0,0,1,899.991,692.074Z" />
-      <path d="M900,711.491a.6.6,0,0,1-.6-.6v-2.377a.6.6,0,1,1,1.206,0v2.377A.6.6,0,0,1,900,711.491Z" />
-
-      <path d="M906.017,694.564a.6.6,0,0,1-.426-1.029l1.68-1.68a.6.6,0,0,1,.853.854l-1.68,1.68A.6.6,0,0,1,906.017,694.564Z" />
-      <path d="M892.3,708.3a.6.6,0,0,1-.426-1.029l1.68-1.681a.6.6,0,1,1,.853.852l-1.68,1.681A.6.6,0,0,1,892.3,708.3Z" />
-
-      <path d="M910.894,700.587h-2.377a.6.6,0,1,1,0-1.2h2.377a.6.6,0,1,1,0,1.2Z" />
-      <path d="M891.477,700.6H889.1a.6.6,0,1,1,0-1.2h2.377a.6.6,0,1,1,0,1.2Z" />
-    </g>
-  </svg>
-
-);
-
-
-export default SunIcon;

+ 2 - 2
apps/app/src/components/Me/ApiSettings.tsx

@@ -30,10 +30,10 @@ const ApiSettings = React.memo((): JSX.Element => {
   return (
     <>
 
-      <h2 className="border-bottom my-4">{ t('API Token Settings') }</h2>
+      <h2 className="border-bottom pb-2 my-4 fs-4">{ t('API Token Settings') }</h2>
 
       <div className="row mb-3">
-        <label htmlFor="apiToken" className="col-md-3 text-md-end form-label">{t('Current API Token')}</label>
+        <label htmlFor="apiToken" className="col-md-3 text-md-end col-form-label">{t('Current API Token')}</label>
         <div className="col-md-6">
           {personalSettingsData?.apiToken != null
             ? (

+ 1 - 1
apps/app/src/components/Me/AssociateModal.tsx

@@ -66,7 +66,7 @@ const AssociateModal = (props: Props): JSX.Element => {
               className={activeTab === 1 ? 'active' : ''}
               onClick={() => setActiveTab(1)}
             >
-              <span className="material-symbols-outlined">network_node</span> LDAP
+              <span className="material-symbols-outlined fs-5">network_node</span> LDAP
             </NavLink>
             <NavLink
               className={activeTab === 2 ? 'active' : ''}

+ 2 - 2
apps/app/src/components/Me/ColorModeSettings.tsx

@@ -37,7 +37,7 @@ export const ColorModeSettings = (): JSX.Element => {
 
   return (
     <div>
-      <h2 className="border-bottom mb-4">{t('color_mode_settings.settings')}</h2>
+      <h2 className="border-bottom pb-2 mb-4 fs-4">{t('color_mode_settings.settings')}</h2>
 
       <div className="offset-md-3">
 
@@ -60,7 +60,7 @@ export const ColorModeSettings = (): JSX.Element => {
 
         </div>
 
-        <div className="mt-3 text-muted">
+        <div className="mt-3 text-muted small">
           {/* eslint-disable-next-line react/no-danger */}
           <span dangerouslySetInnerHTML={{ __html: t('color_mode_settings.description') }} />
         </div>

+ 10 - 10
apps/app/src/components/Me/ExternalAccountLinkedMe.jsx

@@ -57,18 +57,18 @@ class ExternalAccountLinkedMe extends React.Component {
 
     return (
       <Fragment>
-        <h2 className="border-bottom my-4">
-          <button
-            type="button"
-            data-testid="grw-external-account-add-button"
-            className="btn btn-outline-secondary btn-sm pull-right"
-            onClick={this.openAssociateModal}
-          >
-            <span className="material-symbols-outlined" aria-hidden="true">add_circle</span>
-            Add
-          </button>
+        <h2 className="border-bottom mt-4 pb-2 fs-4">
           { t('admin:user_management.external_accounts') }
         </h2>
+        <button
+          type="button"
+          data-testid="grw-external-account-add-button"
+          className="btn btn-outline-secondary btn-sm pull-right mb-2"
+          onClick={this.openAssociateModal}
+        >
+          <span className="material-symbols-outlined" aria-hidden="true">add_circle</span>
+          Add
+        </button>
 
         <table className="table table-bordered table-user-list">
           <thead>

+ 3 - 4
apps/app/src/components/Me/InAppNotificationSettings.tsx

@@ -1,6 +1,5 @@
-import React, {
-  FC, useState, useEffect, useCallback,
-} from 'react';
+import type { FC } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
 
 import pullAllBy from 'lodash/pullAllBy';
 import { useTranslation } from 'next-i18next';
@@ -67,7 +66,7 @@ const InAppNotificationSettings: FC = () => {
 
   return (
     <>
-      <h2 className="border-bottom my-4">{t('in_app_notification_settings.subscribe_settings')}</h2>
+      <h2 className="border-bottom pb-2 my-4 fs-4">{t('in_app_notification_settings.subscribe_settings')}</h2>
 
       <div className="row">
         <div className="offset-md-3 col-md-6 text-start">

+ 6 - 6
apps/app/src/components/Me/PasswordSettings.jsx

@@ -87,12 +87,12 @@ class PasswordSettings extends React.Component {
         ) }
 
         {(this.state.isPasswordSet)
-          ? <h2 className="border-bottom my-4">{t('personal_settings.update_password')}</h2>
-          : <h2 className="border-bottom my-4">{t('personal_settings.set_new_password')}</h2>}
+          ? <h2 className="border-bottom mt-4 mb-5 pb-2 fs-4">{t('personal_settings.update_password')}</h2>
+          : <h2 className="border-bottom mt-4 mb-5 pb-2 fs-4">{t('personal_settings.set_new_password')}</h2>}
         {(this.state.isPasswordSet)
         && (
           <div className="row mb-3">
-            <label htmlFor="oldPassword" className="col-md-3 text-md-end form-label">{ t('personal_settings.current_password') }</label>
+            <label htmlFor="oldPassword" className="col-md-3 text-md-end col-form-label">{ t('personal_settings.current_password') }</label>
             <div className="col-md-5">
               <input
                 className="form-control"
@@ -105,7 +105,7 @@ class PasswordSettings extends React.Component {
           </div>
         )}
         <div className="row mb-3">
-          <label htmlFor="newPassword" className="col-md-3 text-md-end form-label">{t('personal_settings.new_password') }</label>
+          <label htmlFor="newPassword" className="col-md-3 text-md-end col-form-label">{t('personal_settings.new_password') }</label>
           <div className="col-md-5">
             {/* to prevent autocomplete username into userForm[email] in BasicInfoSettings component */}
             {/* https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion */}
@@ -120,7 +120,7 @@ class PasswordSettings extends React.Component {
           </div>
         </div>
         <div className={`row mb-3 ${isIncorrectConfirmPassword && 'has-error'}`}>
-          <label htmlFor="newPasswordConfirm" className="col-md-3 text-md-end form-label">{t('personal_settings.new_password_confirm') }</label>
+          <label htmlFor="newPasswordConfirm" className="col-md-3 text-md-end col-form-label">{t('personal_settings.new_password_confirm') }</label>
           <div className="col-md-5">
             <input
               className="form-control"
@@ -135,7 +135,7 @@ class PasswordSettings extends React.Component {
         </div>
 
         <div className="row my-3">
-          <div className="offset-5">
+          <div className="text-center">
             <button
               data-testid="grw-password-settings-update-button"
               type="button"

+ 2 - 2
apps/app/src/components/Me/ProfileImageSettings.tsx

@@ -115,7 +115,7 @@ const ProfileImageSettings = (): JSX.Element => {
           <img src={generateGravatarSrc(currentUser.email)} className="rounded-pill" width="64" data-vrt-blackout-profile />
         </div>
 
-        <div className="col-md-7 mt-5">
+        <div className="col-md-7 mt-5 mt-md-0">
           <h5>
             <div className="form-check radio-primary">
               <input
@@ -138,7 +138,7 @@ const ProfileImageSettings = (): JSX.Element => {
             </label>
             <div className="col-md-6 col-lg-8">
               <p className="mb-0"><img src={uploadedPictureSrc ?? DEFAULT_IMAGE} className="picture picture-lg rounded-circle" id="settingUserPicture" /></p>
-              {uploadedPictureSrc && <button type="button" className="btn btn-danger" onClick={deleteImageHandler}>{ t('Delete Image') }</button>}
+              {uploadedPictureSrc && <button type="button" className="btn btn-danger mt-2" onClick={deleteImageHandler}>{ t('Delete Image') }</button>}
             </div>
           </div>
           <div className="row align-items-center mt-3 mt-md-5">

+ 9 - 9
apps/app/src/components/Me/QuestionnaireSettings.tsx

@@ -41,7 +41,7 @@ export const QuestionnaireSettings = (): JSX.Element => {
 
   return (
     <>
-      <h2 className="border-bottom mb-4">{t('questionnaire.settings')}</h2>
+      <h2 className="border-bottom pb-2 mb-4 fs-4">{t('questionnaire.settings')}</h2>
 
       {isLoadingCurrentUser && (
         <div className="text-muted text-center mb-5">
@@ -49,9 +49,9 @@ export const QuestionnaireSettings = (): JSX.Element => {
         </div>
       )}
 
-      <div className="row">
-        <div className="offset-md-3 col-md-6 text-start">
-          {!isLoadingCurrentUser && (
+      <div className="container">
+        {!isLoadingCurrentUser && (
+          <div className="offset-md-3 col-md-6 text-start row">
             <div className="form-check form-switch">
               <span id="grw-questionnaire-settings-toggle-wrapper">
                 <input
@@ -66,17 +66,17 @@ export const QuestionnaireSettings = (): JSX.Element => {
                   {t('questionnaire.enable_questionnaire')}
                 </label>
               </span>
-              <p className="form-text text-muted small">
-                {t('questionnaire.personal_settings_explanation')}
-              </p>
               {!growiIsQuestionnaireEnabled && (
                 <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-toggle-wrapper">
                   {t('questionnaire.disabled_by_admin')}
                 </UncontrolledTooltip>
               ) }
             </div>
-          )}
-        </div>
+            <p className="form-text text-muted small">
+              {t('questionnaire.personal_settings_explanation')}
+            </p>
+          </div>
+        )}
       </div>
 
       <div className="row my-3">

+ 2 - 2
apps/app/src/components/Me/UISettings.tsx

@@ -78,16 +78,16 @@ export const UISettings = (): JSX.Element => {
             <label className="form-label form-check-label" htmlFor="swSidebarMode">
               {t('ui_settings.side_bar_mode.side_bar_mode_setting')}
             </label>
-            <p className="form-text text-muted small">{t('ui_settings.side_bar_mode.description')}</p>
           </div>
         </div>
+        <p className="form-text text-muted small">{t('ui_settings.side_bar_mode.description')}</p>
       </>
     );
   };
 
   return (
     <>
-      <h2 className="border-bottom mb-4">{t('ui_settings.ui_settings')}</h2>
+      <h2 className="border-bottom pb- mb-4 fs-4">{t('ui_settings.ui_settings')}</h2>
 
       <div className="row justify-content-center">
         <div className="col-md-6">

+ 1 - 1
apps/app/src/components/PageEditor/Cheatsheet.tsx

@@ -105,7 +105,7 @@ export const Cheatsheet = (): JSX.Element => {
 
         <hr />
         <a href="/Sandbox" className="btn btn-info" target="_blank">
-          <i className="icon-share-alt" /> {t('sandbox.open_sandbox')}
+          <span className="growi-custom-icons">external_link</span> {t('sandbox.open_sandbox')}
         </a>
       </div>
     </div>

+ 1 - 2
apps/app/src/components/PageEditor/LinkEditModal.tsx

@@ -20,7 +20,6 @@ import { useCurrentPagePath } from '~/stores/page';
 import { usePreviewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 
-import PagePreviewIcon from '../Icons/PagePreviewIcon';
 import SearchTypeahead from '../SearchTypeahead';
 
 import Preview from './Preview';
@@ -256,7 +255,7 @@ export const LinkEditModal = (): JSX.Element => {
               />
               <div className="d-none d-sm-block">
                 <button type="button" id="preview-btn" className={`btn btn-info btn-page-preview ${styles['btn-page-preview']}`}>
-                  <PagePreviewIcon />
+                  <span className="material-symbols-outlined">find_in_page</span>
                 </button>
                 <Popover trigger="focus" placement="right" isOpen={isPreviewOpen} target="preview-btn" toggle={toggleIsPreviewOpen}>
                   <PopoverBody>

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

@@ -18,12 +18,12 @@
   }
 }
 
-// apply larger font when smaller than lg
-@include bs.media-breakpoint-down(lg) {
-  .btn-page-accessories :global {
-    .material-symbols-outlined {
-      font-size: 2em;
-    }
+// apply font-size
+.btn-page-accessories :global {
+  --bs-btn-font-size: 14px;
+
+  @include bs.media-breakpoint-down(lg) {
+    --bs-btn-font-size: 16px;
   }
 }
 

+ 1 - 1
apps/app/src/components/PageSideContents/PageAccessoriesControl.tsx

@@ -27,7 +27,7 @@ export const PageAccessoriesControl = memo((props: Props): JSX.Element => {
   return (
     <button
       type="button"
-      className={`btn btn-sm btn-outline-neutral-secondary ${moduleClass} ${className} rounded-pill`}
+      className={`btn btn-outline-neutral-secondary ${moduleClass} ${className} rounded-pill`}
       onClick={onClick}
     >
       <span className="grw-icon d-flex">{icon}</span>

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

@@ -45,7 +45,7 @@ export const PageTags:FC<Props> = (props: Props) => {
           <NotAvailableForReadOnlyUser>
             <button
               type="button"
-              className={`btn btn-sm btn-outline-secondary rounded-pill ${styles['grw-tag-icon-button']}`}
+              className={`btn btn-edit-tags btn-outline-neutral-secondary rounded-pill ${styles['grw-tag-icon-button']}`}
               onClick={onClickEditTagsButton}
             >
               <span className="material-symbols-outlined">local_offer</span>
@@ -58,7 +58,7 @@ export const PageTags:FC<Props> = (props: Props) => {
           <button
             id="edit-tags-btn-wrapper-for-tooltip"
             type="button"
-            className="btn btn-link text-secondary p-0 border-0"
+            className="btn btn-link btn-edit-tags text-secondary p-0 border-0"
             onMouseEnter={onMouseEnterHandler}
             onMouseLeave={onMouseLeaveHandler}
             onClick={onClickEditTagsButton}

+ 1 - 1
apps/app/src/components/PageTags/RenderTagLabels.tsx

@@ -20,7 +20,7 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
         <a
           key={tag}
           type="button"
-          className="grw-tag badge me-1 mb-1 text-truncate"
+          className="grw-tag badge me-1 mb-1 text-truncate mw-100"
           onClick={() => pushState(`tag:${tag}`)}
         >
           {tag}

+ 1 - 1
apps/app/src/components/PageTags/TagEditModal.tsx

@@ -52,7 +52,7 @@ const TagEditModalSubstance: React.FC<TagEditModalSubstanceProps> = (props: TagE
 
   return (
     <Modal isOpen={isOpen} toggle={closeTagEditModal} id="edit-tag-modal" autoFocus={false}>
-      <ModalHeader tag="h4" toggle={closeTagEditModal} className="bg-primary text-light">
+      <ModalHeader tag="h4" toggle={closeTagEditModal}>
         {t('tag_edit_modal.edit_tags')}
       </ModalHeader>
       <ModalBody>

+ 9 - 8
apps/app/src/components/PageTags/TagLabels.module.scss

@@ -12,20 +12,21 @@ $grw-tag-label-font-size: 12px;
   .grw-tag-simple-bar {
     width: 15.5rem;
     max-height: 5rem;
-    .grw-tag{
-      max-width: 15rem;
-    }
   }
 
-  // apply larger font when smaller than lg
-  @include bs.media-breakpoint-down(lg) {
-    .material-symbols-outlined {
-      font-size: 2em;
+}
+
+// apply font-size
+.grw-tag-labels :global {
+  .btn-edit-tags {
+    --bs-btn-font-size: 14px;
+
+    @include bs.media-breakpoint-down(lg) {
+      --bs-btn-font-size: 16px;
     }
   }
 }
 
-
 .grw-tag-labels-skeleton :global {
   width: 137px;
   height: calc(#{$grw-tag-label-font-size} + #{bs.$badge-padding-y} * 2);

+ 23 - 0
apps/app/src/components/PageTags/TagsInput.module.scss

@@ -0,0 +1,23 @@
+.tags-input :global {
+  .rbt-token {
+    .rbt-token-label {
+      // override to text-truncate
+      overflow: hidden;
+      font-size: 1rem; // adjust font-size
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+}
+
+// == Colors
+.tags-input :global {
+  .rbt-token {
+    // override to .badge color
+    color: var(--bs-badge-color);
+  }
+
+  .rbt-token-active {
+    border-color: var(--grw-primary-400) !important;
+  }
+}

+ 12 - 2
apps/app/src/components/PageTags/TagsInput.tsx

@@ -3,10 +3,12 @@ import React, { useRef, useState, useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import type { TypeaheadRef } from 'react-bootstrap-typeahead';
-import { AsyncTypeahead } from 'react-bootstrap-typeahead';
+import { AsyncTypeahead, Token } from 'react-bootstrap-typeahead';
 
 import { useSWRxTagsSearch } from '~/stores/tag';
 
+import styles from './TagsInput.module.scss';
+
 type Props = {
   tags: string[],
   autoFocus: boolean,
@@ -50,7 +52,7 @@ export const TagsInput: FC<Props> = (props: Props) => {
   }, []);
 
   return (
-    <div className="tag-typeahead">
+    <div className={`${styles['tags-input']}`}>
       <AsyncTypeahead
         id="tag-typeahead-asynctypeahead"
         ref={tagsInputRef}
@@ -64,6 +66,14 @@ export const TagsInput: FC<Props> = (props: Props) => {
         options={resultTags} // Search result (Some tag names)
         placeholder={t('tag_edit_modal.tags_input.tag_name')}
         autoFocus={autoFocus}
+        // option is tag name
+        renderToken={(option: string, { onRemove }, idx) => {
+          return (
+            <Token key={idx} className="grw-tag badge mw-100 d-inline-flex p-0" option={option} onRemove={onRemove}>
+              {option}
+            </Token>
+          );
+        }}
       />
     </div>
   );

+ 3 - 2
apps/app/src/components/ReactMarkdownComponents/NextLink.tsx

@@ -1,5 +1,6 @@
 import { pagePathUtils } from '@growi/core/dist/utils';
-import Link, { LinkProps } from 'next/link';
+import type { LinkProps } from 'next/link';
+import Link from 'next/link';
 
 import { useSiteUrl } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
@@ -61,7 +62,7 @@ export const NextLink = (props: Props): JSX.Element => {
   if (isExternalLink(href, siteUrl)) {
     return (
       <a id={id} href={href} className={className} target="_blank" rel="noopener noreferrer" {...dataAttributes}>
-        {children}&nbsp;<i className="icon-share-alt small"></i>
+        {children}&nbsp;<span className="growi-custom-icons">external_link</span>
       </a>
     );
   }

+ 1 - 2
apps/app/src/components/ShortcutsModal.tsx

@@ -3,7 +3,6 @@ import React from 'react';
 import { useTranslation } from 'next-i18next';
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 
-import KeyboardReturnEnterIcon from '~/components/Icons/KeyboardReturnEnterIcon';
 import { useShortcutsModal } from '~/stores/modal';
 
 import styles from './ShortcutsModal.module.scss';
@@ -142,7 +141,7 @@ const ShortcutsModal = (): JSX.Element => {
                   <td className="text-nowrap">
                     <span className={`key cmd-key ${additionalClassByOs}`}></span> +
                     <span className="key key-longer">
-                      <KeyboardReturnEnterIcon />
+                      <span className="material-symbols-outlined">keyboard_return</span>
                     </span>
                   </td>
                 </tr>

+ 1 - 2
apps/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx

@@ -6,7 +6,6 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
 import { BookmarkFolderNameInput } from '~/components/Bookmarks/BookmarkFolderNameInput';
 import { BookmarkFolderTree } from '~/components/Bookmarks/BookmarkFolderTree';
-import { FolderPlusIcon } from '~/components/Icons/FolderPlusIcon';
 import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { useCurrentUser } from '~/stores/context';
 
@@ -47,7 +46,7 @@ export const BookmarkContents = (): JSX.Element => {
         >
 
           <div className="d-flex align-items-center">
-            <FolderPlusIcon />
+            <span className="material-symbols-outlined">create_new_folder</span>
             <span className="ms-2">{t('bookmark_folder.new_folder')}</span>
           </div>
         </button>

+ 33 - 0
apps/app/src/components/Sidebar/SidebarNav/PersonalDropdown.module.scss

@@ -0,0 +1,33 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@include bs.color-mode(light) {
+  .personal-dropdown-header :global {
+    color: var(--bs-gray-600);
+  }
+
+  .personal-dropdown-item :global {
+    --bs-link-color-rgb:var(--bs-gray-600);
+    color: var(--bs-gray-600);
+  }
+}
+
+@include bs.color-mode(dark) {
+  .personal-dropdown-header :global {
+    color: var(--bs-gray-500);
+  }
+
+  .personal-dropdown-item :global {
+    --bs-link-color-rgb:var(--bs-gray-500);
+    color: var(--bs-gray-500);
+  }
+}
+
+.personal-dropdown-menu :global {
+  --bs-dropdown-font-size: 14px;
+}
+
+.personal-dropdown-header :global {
+  .item-text-email {
+    font-size: 10.5px;
+  }
+}

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

@@ -15,6 +15,8 @@ import { useCurrentUser } from '~/stores/context';
 
 import { SkeletonItem } from './SkeletonItem';
 
+import styles from './PersonalDropdown.module.scss';
+
 const ProactiveQuestionnaireModal = dynamic(() => import('~/features/questionnaire/client/components/ProactiveQuestionnaireModal'), { ssr: false });
 
 export const PersonalDropdown = (): JSX.Element => {
@@ -52,42 +54,45 @@ export const PersonalDropdown = (): JSX.Element => {
         <DropdownMenu
           container="body"
           data-testid="personal-dropdown-menu"
+          className={styles['personal-dropdown-menu']}
         >
-          <DropdownItem header>
-            <div className="mt-2">
+          <DropdownItem className={styles['personal-dropdown-header']}>
+            <div className="mt-2 mb-3">
               <UserPicture user={currentUser} size="lg" noLink noTooltip />
             </div>
-            <div className="mt-3 ms-1 fs-5">{currentUser.name}</div>
-            <div className="mt-2 d-flex align-items-center">
-              <span className="material-symbols-outlined me-1">person</span>
-              {currentUser.username}
+            <div className="ms-1 fs-6">{currentUser.name}</div>
+            <div className="d-flex align-items-center my-2">
+              <small className="material-symbols-outlined me-1 pb-0 fs-6">person</small>
+              <span>{currentUser.username}</span>
             </div>
             <div className="d-flex align-items-center">
-              <span className="material-symbols-outlined me-1">mail</span>
-              <span className="grw-email-sm">{currentUser.email}</span>
+              <span className="material-symbols-outlined me-1 pb-0 fs-6">mail</span>
+              <span className="item-text-email">{currentUser.email}</span>
             </div>
           </DropdownItem>
 
-          <DropdownItem divider />
+          <DropdownItem className="my-3" divider />
 
-          <DropdownItem>
+          <DropdownItem className={`my-1 ${styles['personal-dropdown-item']}`}>
             <Link
               href={pagePathUtils.userHomepagePath(currentUser)}
               data-testid="grw-personal-dropdown-menu-user-home"
             >
-              <span className="text-muted">
-                <span className="material-symbols-outlined me-1">home</span>{t('personal_dropdown.home')}
+              <span className="d-flex align-items-center">
+                <span className="item-icon material-symbols-outlined me-2 pb-0 fs-6">home</span>
+                <span className="item-text">{t('personal_dropdown.home')}</span>
               </span>
             </Link>
           </DropdownItem>
 
-          <DropdownItem>
+          <DropdownItem className={`my-1 ${styles['personal-dropdown-item']}`}>
             <Link
               href="/me"
               data-testid="grw-personal-dropdown-menu-user-settings"
             >
-              <span className="text-muted">
-                <span className="material-symbols-outlined me-1">build</span>{t('personal_dropdown.settings')}
+              <span className="d-flex align-items-center">
+                <span className="item-icon material-symbols-outlined me-2 pb-0 fs-6">discover_tune</span>
+                <span className="item-text">{t('personal_dropdown.settings')}</span>
               </span>
             </Link>
           </DropdownItem>
@@ -95,15 +100,18 @@ export const PersonalDropdown = (): JSX.Element => {
           <DropdownItem
             data-testid="grw-proactive-questionnaire-modal-toggle-btn"
             onClick={() => setQuestionnaireModalOpen(true)}
+            className={`my-1 ${styles['personal-dropdown-item']}`}
           >
-            <span className="text-muted">
-              <span className="material-symbols-outlined me-1">edit</span>{t('personal_dropdown.feedback')}
+            <span className="d-flex align-items-center">
+              <span className="item-icon material-symbols-outlined me-2 pb-0 fs-6">edit_note</span>
+              <span className="item-text">{t('personal_dropdown.feedback')}</span>
             </span>
           </DropdownItem>
 
-          <DropdownItem onClick={logoutHandler}>
-            <span className="text-muted">
-              <span className="material-symbols-outlined me-1">logout</span>{t('Sign out')}
+          <DropdownItem onClick={logoutHandler} className={`my-1 ${styles['personal-dropdown-item']}`}>
+            <span className="d-flex align-items-center">
+              <span className="item-icon material-symbols-outlined me-2 pb-0 fs-6">logout</span>
+              <span className="item-text">{t('Sign out')}</span>
             </span>
           </DropdownItem>
         </DropdownMenu>

+ 10 - 10
apps/app/src/components/UsersHomepageFooter.module.scss

@@ -4,7 +4,10 @@ $grw-sidebar-content-footer-height: 50px;
 
 .user-page-footer :global {
   .grw-user-page-list-m {
-    .list-group{
+    .growi-custom-icons {
+      font-size: 1.1em;
+    }
+    .list-group {
       .list-group-item {
         .grw-visible-on-hover {
           display: none;
@@ -15,20 +18,19 @@ $grw-sidebar-content-footer-height: 50px;
             display: block;
           }
         }
-        .grw-triangle-container{
+        .grw-triangle-container {
           svg {
             width: 12px;
             height: 12px;
           }
         }
-        svg{
+        svg {
           width: 20px;
           height: 20px;
         }
         min-height: 40px;
         border-radius: 0px;
 
-
         &.grw-bookmark-item-list {
           .picture {
             width: 16px;
@@ -40,17 +42,15 @@ $grw-sidebar-content-footer-height: 50px;
               height: 20px;
             }
           }
-          svg{
+          svg {
             width: 14px;
             height: 14px;
           }
-          .grw-foldertree-control{
+          .grw-foldertree-control {
             margin-left: 1rem;
           }
         }
       }
-
-
     }
 
     .grw-foldertree-item-container {
@@ -58,7 +58,7 @@ $grw-sidebar-content-footer-height: 50px;
         max-width: 25%;
       }
     }
-    .grw-foldertree-title-anchor{
+    .grw-foldertree-title-anchor {
       width: fit-content !important;
       margin-right: 20px;
     }
@@ -67,7 +67,7 @@ $grw-sidebar-content-footer-height: 50px;
       height: 35px;
       margin-bottom: 6px;
     }
-    .new-bookmark-folder{
+    .new-bookmark-folder {
       max-height: 30px;
       svg {
         width: 18px;

+ 5 - 15
apps/app/src/components/UsersHomepageFooter.tsx

@@ -2,19 +2,16 @@ import React, { useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 import { RecentCreated } from '~/components/RecentCreated/RecentCreated';
 import styles from '~/components/UsersHomepageFooter.module.scss';
 import { useCurrentUser } from '~/stores/context';
 
 import { BookmarkFolderTree } from './Bookmarks/BookmarkFolderTree';
-import { CompressIcon } from './Icons/CompressIcon';
-import { ExpandIcon } from './Icons/ExpandIcon';
 
 export type UsersHomepageFooterProps = {
-  creatorId: string,
-}
+  creatorId: string;
+};
 
 export const UsersHomepageFooter = (props: UsersHomepageFooterProps): JSX.Element => {
   const { t } = useTranslation();
@@ -30,15 +27,8 @@ export const UsersHomepageFooter = (props: UsersHomepageFooterProps): JSX.Elemen
           <span style={{ fontSize: '1.3em' }} className="material-symbols-outlined">bookmark</span>
           {t('footer.bookmarks')}
           <span className="ms-auto ps-2 ">
-            <button
-              type="button"
-              className={`btn btn-sm grw-expand-compress-btn ${isExpanded ? 'active' : ''}`}
-              onClick={() => setIsExpanded(!isExpanded)}
-            >
-              { isExpanded
-                ? <ExpandIcon />
-                : <CompressIcon />
-              }
+            <button type="button" className={`btn btn-sm grw-expand-compress-btn ${isExpanded ? 'active' : ''}`} onClick={() => setIsExpanded(!isExpanded)}>
+              {isExpanded ? <span className="material-symbols-outlined">expand</span> : <span className="material-symbols-outlined">compress</span>}
             </button>
           </span>
         </h2>
@@ -49,7 +39,7 @@ export const UsersHomepageFooter = (props: UsersHomepageFooterProps): JSX.Elemen
       </div>
       <div className="grw-user-page-list-m mt-5 d-edit-none">
         <h2 id="recently-created-list" className="grw-user-page-header border-bottom pb-2 mb-3">
-          <i id="recent-created-icon" className="me-1"><RecentlyCreatedIcon /></i>
+          <span className="growi-custom-icons me-1">recently_created</span>
           {t('footer.recently_created')}
         </h2>
         <div id="user-created-list" className={`page-list ${styles['page-list']}`}>

+ 18 - 3
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/ExternalUserGroupManagement.tsx

@@ -1,6 +1,8 @@
 import type { FC } from 'react';
 import { useCallback, useMemo, useState } from 'react';
 
+import type { IGrantedGroup } from '@growi/core';
+import { GroupType, getIdForRef } from '@growi/core';
 import { useTranslation } from 'react-i18next';
 import { TabContent, TabPane } from 'reactstrap';
 
@@ -11,17 +13,25 @@ import { UserGroupModal } from '~/components/Admin/UserGroup/UserGroupModal';
 import { UserGroupTable } from '~/components/Admin/UserGroup/UserGroupTable';
 import CustomNav from '~/components/CustomNavigation/CustomNav';
 import type { IExternalUserGroupHasId } from '~/features/external-user-group/interfaces/external-user-group';
+import type { PageActionOnGroupDelete } from '~/interfaces/user-group';
 import { useIsAclEnabled } from '~/stores/context';
+import { useSWRxUserGroupList } from '~/stores/user-group';
 
 import { useSWRxChildExternalUserGroupList, useSWRxExternalUserGroupList, useSWRxExternalUserGroupRelationList } from '../../stores/external-user-group';
 
 import { KeycloakGroupManagement } from './KeycloakGroupManagement';
 import { LdapGroupManagement } from './LdapGroupManagement';
-import { PageActionOnGroupDelete } from '~/interfaces/user-group';
 
 export const ExternalGroupManagement: FC = () => {
   const { data: externalUserGroupList, mutate: mutateExternalUserGroups } = useSWRxExternalUserGroupList();
+  const { data: userGroupList } = useSWRxUserGroupList();
   const externalUserGroups = externalUserGroupList != null ? externalUserGroupList : [];
+  const externalUserGroupsForDeleteModal: IGrantedGroup[] = externalUserGroups.map((group) => {
+    return { item: group, type: GroupType.externalUserGroup };
+  });
+  const userGroupsForDeleteModal: IGrantedGroup[] = userGroupList != null ? userGroupList.map((group) => {
+    return { item: group, type: GroupType.userGroup };
+  }) : [];
   const externalUserGroupIds = externalUserGroups.map(group => group._id);
 
   const { data: externalUserGroupRelationList } = useSWRxExternalUserGroupRelationList(externalUserGroupIds);
@@ -93,11 +103,16 @@ export const ExternalGroupManagement: FC = () => {
     }
   }, [t, mutateExternalUserGroups, hideUpdateModal]);
 
-  const deleteExternalUserGroupById = useCallback(async(deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroupId: string) => {
+  const deleteExternalUserGroupById = useCallback(async(
+      deleteGroupId: string, actionName: PageActionOnGroupDelete, transferToUserGroup: IGrantedGroup | null,
+  ) => {
+    const transferToUserGroupId = transferToUserGroup != null ? getIdForRef(transferToUserGroup.item) : null;
+    const transferToUserGroupType = transferToUserGroup != null ? transferToUserGroup.type : null;
     try {
       await apiv3Delete(`/external-user-groups/${deleteGroupId}`, {
         actionName,
         transferToUserGroupId,
+        transferToUserGroupType,
       });
 
       // sync
@@ -154,7 +169,7 @@ export const ExternalGroupManagement: FC = () => {
       />
 
       <UserGroupDeleteModal
-        userGroups={externalUserGroups}
+        userGroups={userGroupsForDeleteModal.concat(externalUserGroupsForDeleteModal)}
         deleteUserGroup={selectedExternalUserGroup}
         onDelete={deleteExternalUserGroupById}
         isShow={isDeleteModalShown}

+ 8 - 6
apps/app/src/features/external-user-group/server/routes/apiv3/external-user-group.ts

@@ -150,17 +150,19 @@ module.exports = (crowi: Crowi): Router => {
   router.delete('/:id', loginRequiredStrictly, adminRequired, validators.delete, apiV3FormValidator, addActivity,
     async(req: AuthorizedRequest, res: ApiV3Response) => {
       const { id: deleteGroupId } = req.params;
-      const { transferToUserGroupId } = req.query;
+      const { transferToUserGroupId, transferToUserGroupType } = req.query;
       const actionName = req.query.actionName as PageActionOnGroupDelete;
 
-      const transferGroupInfo = transferToUserGroupId != null ? {
-        item: transferToUserGroupId as string,
-        type: GroupType.externalUserGroup,
-      } : undefined;
+      const transferToUserGroup = typeof transferToUserGroupId === 'string'
+        && (transferToUserGroupType === GroupType.userGroup || transferToUserGroupType === GroupType.externalUserGroup)
+        ? {
+          item: transferToUserGroupId,
+          type: transferToUserGroupType,
+        } : undefined;
 
       try {
         const userGroups = await (crowi.userGroupService as UserGroupService)
-          .removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferGroupInfo, ExternalUserGroup, ExternalUserGroupRelation);
+          .removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferToUserGroup, ExternalUserGroup, ExternalUserGroupRelation);
 
         const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE };
         activityEvent.emit('update', res.locals.activity._id, parameters);

+ 1 - 1
apps/app/src/pages/installer.page.tsx

@@ -47,7 +47,7 @@ const InstallerPage: NextPage<Props> = (props: Props) => {
         i18n: t('installer.tab'),
       },
       external_accounts: {
-        Icon: () => <i className="icon-fw icon-share-alt"></i>,
+        Icon: () => <span className="growi-custom-icons">external_link</span>,
         Content: DataTransferForm,
         i18n: tCommons('g2g_data_transfer.tab'),
       },

+ 1 - 1
apps/app/src/pages/me/[[...path]].page.tsx

@@ -122,7 +122,7 @@ const MePage: NextPageWithLayout<Props> = (props: Props) => {
       <Head>
         <title>{title}</title>
       </Head>
-      <div className="dynamic-layout-root">
+      <div className="dynamic-layout-root mx-md-3">
         <header className="py-3">
           <div className="container">
             <h1 className="title fs-3 mt-5">{ targetPage.title }</h1>

+ 8 - 6
apps/app/src/server/routes/apiv3/user-group.js

@@ -429,15 +429,17 @@ module.exports = (crowi) => {
    */
   router.delete('/:id', loginRequiredStrictly, adminRequired, validator.delete, apiV3FormValidator, addActivity, async(req, res) => {
     const { id: deleteGroupId } = req.params;
-    const { actionName, transferToUserGroupId } = req.query;
+    const { actionName, transferToUserGroupId, transferToUserGroupType } = req.query;
 
-    const transferGroupInfo = transferToUserGroupId != null ? {
-      item: transferToUserGroupId,
-      type: GroupType.userGroup,
-    } : undefined;
+    const transferToUserGroup = typeof transferToUserGroupId === 'string'
+        && (transferToUserGroupType === GroupType.userGroup || transferToUserGroupType === GroupType.externalUserGroup)
+      ? {
+        item: transferToUserGroupId,
+        type: transferToUserGroupType,
+      } : undefined;
 
     try {
-      const userGroups = await crowi.userGroupService.removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferGroupInfo);
+      const userGroups = await crowi.userGroupService.removeCompletelyByRootGroupId(deleteGroupId, actionName, req.user, transferToUserGroup);
 
       const parameters = { action: SupportedAction.ACTION_ADMIN_USER_GROUP_DELETE };
       activityEvent.emit('update', res.locals.activity._id, parameters);

+ 2 - 3
apps/app/src/styles/_fonts.scss

@@ -6,9 +6,8 @@
 
 .material-symbols-outlined {
   display: inline-block;
-  padding-bottom: 3px;
   font-family: var(--grw-font-family-material-symbols-outlined);
-  font-size: 24px;  /* Preferred icon size */
+  font-size: 1.5em;  /* Preferred icon size */
   font-style: normal;
   font-weight: normal;
   line-height: 1;
@@ -16,7 +15,7 @@
   letter-spacing: normal;
   word-wrap: normal;
   white-space: nowrap;
-  vertical-align: middle;
+  vertical-align: bottom;
   direction: ltr;
 
   &.fill {

+ 34 - 34
apps/app/src/styles/_override-rbt.scss

@@ -1,37 +1,37 @@
-// TODO: .form-group dropped in bootstrap v5
-// https://redmine.weseek.co.jp/issues/129103
-// override react-bootstrap-typeahead styles
-// see: https://github.com/ericgio/react-bootstrap-typeahead
-.form-group:not(.has-error) {
-  .rbt-input.form-control {
-    // focus
-    &.focus {
-      border-color: inherit;
-    }
-  }
-}
+// // TODO: .form-group dropped in bootstrap v5
+// // https://redmine.weseek.co.jp/issues/129103
+// // override react-bootstrap-typeahead styles
+// // see: https://github.com/ericgio/react-bootstrap-typeahead
+// .form-group:not(.has-error) {
+//   .rbt-input.form-control {
+//     // focus
+//     &.focus {
+//       border-color: inherit;
+//     }
+//   }
+// }
 
-// TODO: check padding when upgrade react-bootstrap-typeahead v6
-// .close to .btn-close in bootstrap v5
-// https://redmine.weseek.co.jp/issues/129103
-.rbt-input-wrapper {
-  .close.rbt-close {
-    // default bootstrap .close has padding 0
-    padding: 3px 7px;
-  }
-}
+// // TODO: check padding when upgrade react-bootstrap-typeahead v6
+// // .close to .btn-close in bootstrap v5
+// // https://redmine.weseek.co.jp/issues/129103
+// .rbt-input-wrapper {
+//   .close.rbt-close {
+//     // default bootstrap .close has padding 0
+//     padding: 3px 7px;
+//   }
+// }
 
-// hide loading icon
-.rbt-aux {
-  display: none;
-}
+// // hide loading icon
+// .rbt-aux {
+//   display: none;
+// }
 
-// TODO: .input-group-prepend dropped in bootstrap v5
-// https://redmine.weseek.co.jp/issues/129103
-// seamless border for .input-group-prepend
-.input-group-prepend + div {
-  .rbt .rbt-input-main {
-    border-top-left-radius: 0;
-    border-bottom-left-radius: 0;
-  }
-}
+// // TODO: .input-group-prepend dropped in bootstrap v5
+// // https://redmine.weseek.co.jp/issues/129103
+// // seamless border for .input-group-prepend
+// .input-group-prepend + div {
+//   .rbt .rbt-input-main {
+//     border-top-left-radius: 0;
+//     border-bottom-left-radius: 0;
+//   }
+// }

+ 4 - 0
apps/app/src/styles/molecules/_list-group-item.scss

@@ -1,5 +1,9 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 
+.list-group-item.active {
+  --bs-list-group-active-color: var(--bs-list-group-color);
+}
+
 @include bs.color-mode(light) {
   .list-group-item-action {
     --bs-list-group-action-hover-bg: var(--grw-primary-100);

+ 1 - 0
packages/custom-icons/svg/drawer_io.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a,.c{fill:none;}.a{stroke:#707070;}.b{clip-path:url(#a);}</style><clipPath id="a"><rect class="a" width="24" height="24" transform="translate(-14953 -18037)"/></clipPath></defs><g class="b" transform="translate(14953 18037)"><path class="c" d="M70.158,9.041H67.433L65.921,6.46a.909.909,0,0,0,.35-.706V1.477a.942.942,0,0,0-.95-.932H60.932a.942.942,0,0,0-.95.932V5.755a.911.911,0,0,0,.35.706l-1.511,2.58H56.057a.942.942,0,0,0-.949.932v4.277a.942.942,0,0,0,.949.932h4.389a.941.941,0,0,0,.949-.932V9.972a.941.941,0,0,0-.949-.932h-.107l1.38-2.354h2.815l1.379,2.354H65.77a.942.942,0,0,0-.95.932v4.277a.942.942,0,0,0,.95.932h4.388a.942.942,0,0,0,.95-.932V9.972a.942.942,0,0,0-.95-.932m-10.08,4.848H56.425V10.333h3.653ZM61.3,1.838h3.653V5.394H61.3Zm8.491,12.051H66.137V10.333h3.653Z" transform="translate(-15004.108 -18032.863)"/></g></svg>

+ 1 - 0
packages/custom-icons/svg/external_link.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="21.999" height="21.999" viewBox="0 0 21.999 21.999"><defs><style>.a{fill:none;}</style></defs><path class="a" d="M18.533,13.9h1.787v5.981A2.114,2.114,0,0,1,18.2,22H2.116A2.115,2.115,0,0,1,0,19.883V3.8A2.115,2.115,0,0,1,2.116,1.68H8.1V3.467H2.116a.329.329,0,0,0-.328.328V19.883a.329.329,0,0,0,.328.328H18.2a.329.329,0,0,0,.328-.328ZM11.788,0V1.7h7.252L7.094,13.641l1.264,1.265L20.3,2.96v7.253H22V0Z"/></svg>

+ 1 - 0
packages/custom-icons/svg/format_quote.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a{fill:none;}.b{clip-path:url(#a);}</style><clipPath id="a"><rect class="a" width="24" height="24" transform="translate(376 36)"/></clipPath></defs><g class="b" transform="translate(-376 -36)"><path class="a" d="M32.359,16.068v1.823H47.21V16.068Zm0-5.327H47.21V8.919H32.359Zm0-7.152H47.21V1.767H32.359ZM27.1,19.659h1.822V0H27.1Z" transform="translate(350.789 38.172)"/></g></svg>

+ 1 - 0
packages/custom-icons/svg/header.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a{fill:#fff;}.b{clip-path:url(#a);}.c{fill:none;}</style><clipPath id="a"><rect class="a" width="24" height="24" transform="translate(-14908 -18037)"/></clipPath></defs><g class="b" transform="translate(14908 18037)"><path class="c" d="M10.813.26v7H2.008v-7H0v16H2.008v-7h8.805v7h2.008V.26Z" transform="translate(-14902.41 -18033.26)"/></g></svg>

+ 1 - 8
packages/custom-icons/svg/recently_created.svg

@@ -1,8 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21.569" viewBox="0 0 21 21.569">
-  <g id="8883" data-name="8883" transform="translate(-288.73 -162.502)">
-    <path id="14245" data-name="14245" d="M18.841,15.3a4.123,4.123,0,1,1-1.028-2.714H16.271v1.158h3.213V10.53H18.326v.929A5.261,5.261,0,1,0,20,15.3Z" transform="translate(289.23 163.002)" fill="#C4C2BD" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1"/>
-    <path id="14246" data-name="14246" d="M14.151,12.351v3.165l2.021,2.312.872-.762L15.31,15.081v-2.73Z" transform="translate(289.23 163.002)" fill="#C4C2BD" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1"/>
-    <path id="14247" data-name="14247" d="M16.933,4.241a1.645,1.645,0,0,0,.489-1.19,1.582,1.582,0,0,0-.469-1.19L15.6.5a1.631,1.631,0,0,0-2.36,0L12.075,1.667l3.706,3.726Z" transform="translate(289.23 163.002)" fill="#C4C2BD" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1"/>
-    <path id="14248" data-name="14248" d="M10.932,2.819,0,13.751v3.706H3.706L14.638,6.525ZM3,15.81H1.647V14.459l9.3-9.3.682.683.667.667Z" transform="translate(289.23 163.002)" fill="#C4C2BD" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1"/>
-  </g>
-</svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="20.001" height="20.568" viewBox="0 0 20.001 20.568"><defs><style>.a{fill:none;}</style></defs><path class="a" d="M15437.46,17968.3a5.262,5.262,0,0,1,8.868-3.844v-.93h1.154v3.217h-3.212v-1.158h1.543a4.109,4.109,0,1,0,1.028,2.715H15448a5.271,5.271,0,0,1-10.541,0Zm4.69.217v-3.168h1.159v2.734l1.732,1.98-.87.762Zm-14.15,1.939v-3.7l10.93-10.936,3.708,3.709-10.934,10.93Zm1.646-3v1.354h1.35l9.3-9.3-.668-.668-.681-.682Zm10.43-12.787,1.159-1.164a1.633,1.633,0,0,1,2.363,0l1.354,1.357a1.578,1.578,0,0,1,.469,1.186,1.647,1.647,0,0,1-.487,1.191l-1.154,1.15Z" transform="translate(-15428 -17953)"/></svg>

+ 1 - 1
packages/editor/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/editor",
-  "version": "6.2.0-RC.0",
+  "version": "7.0.0-RC.0",
   "license": "MIT",
   "type": "module",
   "module": "dist/index.js",

+ 1 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx

@@ -14,7 +14,7 @@ export const DiagramButton = (props: Props): JSX.Element => {
   }, [editorKey, openDrawioModal]);
   return (
     <button type="button" className="btn btn-toolbar-button" onClick={onClickDiagramButton}>
-      <span className="material-symbols-outlined fs-5">lan</span>
+      <span className="growi-custom-icons">drawer_io</span>
     </button>
   );
 };

+ 2 - 2
packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx

@@ -69,7 +69,7 @@ export const TextFormatTools = (props: TextFormatToolsType): JSX.Element => {
             <span className="material-symbols-outlined fs-5">format_strikethrough</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('#', true)}>
-            <span className="material-symbols-outlined fs-5">block</span>
+            <span className="growi-custom-icons">header</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertMarkdownElements('`', '`')}>
             <span className="material-symbols-outlined fs-5">code</span>
@@ -81,7 +81,7 @@ export const TextFormatTools = (props: TextFormatToolsType): JSX.Element => {
             <span className="material-symbols-outlined fs-5">format_list_numbered</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('>')}>
-            <span className="material-symbols-outlined fs-5">block</span>
+            <span className="growi-custom-icons">format_quote</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('- [ ]')}>
             <span className="material-symbols-outlined fs-5">checklist</span>

+ 1 - 1
yarn.lock

@@ -1840,7 +1840,7 @@
   version "7.0.0-RC.0"
 
 "@growi/editor@link:packages/editor":
-  version "6.2.0-RC.0"
+  version "7.0.0-RC.0"
   dependencies:
     markdown-table "^3.0.3"
     react "^18.2.0"