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

Merge pull request #8804 from weseek/master

Release v7.0.5
Yuki Takei 1 год назад
Родитель
Сommit
7ef125b02f
78 измененных файлов с 558 добавлено и 479 удалено
  1. 1 1
      apps/app/package.json
  2. 1 1
      apps/app/src/components/Admin/App/AppSetting.jsx
  3. 4 5
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  4. 1 1
      apps/app/src/components/Admin/App/MailSetting.tsx
  5. 8 11
      apps/app/src/components/Admin/App/MaintenanceMode.tsx
  6. 6 6
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  7. 46 50
      apps/app/src/components/Admin/App/SiteUrlSetting.tsx
  8. 1 1
      apps/app/src/components/Admin/Customize/CustomizeCssSetting.tsx
  9. 6 6
      apps/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx
  10. 2 2
      apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  11. 2 2
      apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  12. 2 2
      apps/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx
  13. 2 2
      apps/app/src/components/Admin/Customize/CustomizeThemeOptions.tsx
  14. 4 3
      apps/app/src/components/Admin/Customize/CustomizeTitle.tsx
  15. 1 1
      apps/app/src/components/Admin/Customize/PagingSizeUncontrolledDropdown.jsx
  16. 1 1
      apps/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx
  17. 8 6
      apps/app/src/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx
  18. 10 9
      apps/app/src/components/Admin/ElasticsearchManagement/StatusTable.jsx
  19. 2 2
      apps/app/src/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx
  20. 1 1
      apps/app/src/components/Admin/ExportArchiveDataPage.tsx
  21. 1 1
      apps/app/src/components/Admin/FullTextSearchManagement.tsx
  22. 1 1
      apps/app/src/components/Admin/ImportData/GrowiArchive/UploadForm.jsx
  23. 2 2
      apps/app/src/components/Admin/ImportData/GrowiArchiveSection.jsx
  24. 11 11
      apps/app/src/components/Admin/ImportData/ImportDataPageContents.jsx
  25. 6 6
      apps/app/src/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx
  26. 6 6
      apps/app/src/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx
  27. 2 2
      apps/app/src/components/Admin/MarkdownSetting/WhitelistInput.jsx
  28. 4 4
      apps/app/src/components/Admin/MarkdownSetting/XssForm.jsx
  29. 10 9
      apps/app/src/components/Admin/Notification/GlobalNotification.jsx
  30. 4 4
      apps/app/src/components/Admin/Notification/NotificationSetting.jsx
  31. 16 14
      apps/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx
  32. 17 15
      apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx
  33. 30 30
      apps/app/src/components/Admin/Security/LdapSecuritySettingContents.jsx
  34. 11 11
      apps/app/src/components/Admin/Security/LocalSecuritySettingContents.jsx
  35. 30 28
      apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx
  36. 41 45
      apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  37. 3 3
      apps/app/src/components/Admin/Security/SecurityManagementContents.jsx
  38. 12 12
      apps/app/src/components/Admin/Security/SecuritySetting.jsx
  39. 11 12
      apps/app/src/components/Admin/Security/ShareLinkSetting.tsx
  40. 9 9
      apps/app/src/components/Admin/UserGroup/UserGroupForm.tsx
  41. 3 3
      apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  42. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx
  43. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx
  44. 1 2
      apps/app/src/components/Bookmarks/BookmarkFolderTree.tsx
  45. 24 8
      apps/app/src/components/Bookmarks/BookmarkItem.tsx
  46. 1 1
      apps/app/src/components/Common/PagePathNav/PagePathNav.tsx
  47. 8 0
      apps/app/src/components/Layout/Admin.module.scss
  48. 3 3
      apps/app/src/components/Layout/AdminLayout.tsx
  49. 2 2
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx
  50. 8 0
      apps/app/src/components/ShareLinkPageView.tsx
  51. 16 14
      apps/app/src/components/Sidebar/PageTree/PageTreeSubstance.tsx
  52. 24 2
      apps/app/src/components/Sidebar/SidebarNav/SidebarNav.tsx
  53. 1 1
      apps/app/src/features/external-user-group/client/components/ExternalUserGroup/ExternalUserGroupManagement.tsx
  54. 22 23
      apps/app/src/features/external-user-group/client/components/ExternalUserGroup/KeycloakGroupSyncSettingsForm.tsx
  55. 23 23
      apps/app/src/features/external-user-group/client/components/ExternalUserGroup/LdapGroupSyncSettingsForm.tsx
  56. 7 7
      apps/app/src/features/growi-plugin/server/services/growi-plugin/growi-plugin.integ.ts
  57. 3 2
      apps/app/src/features/questionnaire/client/stores/questionnaire.tsx
  58. 1 1
      apps/app/src/interfaces/bookmark-info.ts
  59. 6 0
      apps/app/src/server/service/config-loader.ts
  60. 28 3
      apps/app/src/server/service/file-uploader/aws.ts
  61. 3 3
      apps/app/src/server/service/page/index.ts
  62. 7 14
      apps/app/src/stores/bookmark.ts
  63. 3 1
      apps/app/test/integration/service/page.test.js
  64. 2 2
      apps/app/test/integration/service/v5.public-page.test.ts
  65. 1 1
      apps/slackbot-proxy/package.json
  66. 1 1
      package.json
  67. 1 1
      packages/core/package.json
  68. 1 1
      packages/editor/package.json
  69. 1 1
      packages/presentation/package.json
  70. 1 1
      packages/preset-templates/package.json
  71. 1 1
      packages/preset-themes/package.json
  72. 1 1
      packages/remark-attachment-refs/package.json
  73. 1 1
      packages/remark-drawio/package.json
  74. 1 1
      packages/remark-growi-directive/package.json
  75. 1 1
      packages/remark-lsx/package.json
  76. 1 1
      packages/slack/package.json
  77. 1 1
      packages/ui/package.json
  78. 11 11
      yarn.lock

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",

+ 1 - 1
apps/app/src/components/Admin/App/AppSetting.jsx

@@ -138,7 +138,7 @@ const AppSetting = (props) => {
         </div>
       </div>
 
-      <div className="row mb-5">
+      <div className="row mb-2">
         <label
           className="text-start text-md-end col-md-3 col-form-label"
         >

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

@@ -1,4 +1,5 @@
-import React, { ChangeEvent, useCallback } from 'react';
+import type { ChangeEvent } from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
@@ -30,11 +31,9 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
 
   return (
     <>
-      <p className="card custom-card my-3">
+      <p className="card custom-card bg-warning-subtle my-3">
         {t('admin:app_setting.file_upload')}
-        <br />
-        <br />
-        <span className="text-danger">
+        <span className="text-danger mt-1">
           <span className="material-symbols-outlined">link_off</span>
           {t('admin:app_setting.change_setting')}
         </span>

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

@@ -62,7 +62,7 @@ const MailSetting = (props: Props) => {
         </div>
       </div>
 
-      <div className="row mb-5">
+      <div className="row mb-2">
         <label className="form-label text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.transmission_method')}
         </label>

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

@@ -1,4 +1,5 @@
-import React, { FC, useState, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useState, useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
@@ -54,21 +55,17 @@ export const MaintenanceMode: FC = () => {
         onConfirm={onConfirmHandler}
         onCancel={() => closeModal()}
       />
-      <p className="card custom-card">
+      <p className="card custom-card bg-warning-subtle">
         {t('admin:maintenance_mode.description')}
-        <br />
-        <br />
-        <span className="text-warning">
+        <span className="text-warning mt-1">
           <span className="material-symbols-outlined">error</span>
           {t('admin:maintenance_mode.supplymentary_message_to_start')}
         </span>
       </p>
-      <div className="row my-3">
-        <div className="mx-auto">
-          <button type="button" className="btn btn-success" onClick={() => openModal()}>
-            {isMaintenanceMode ? t('admin:maintenance_mode.end_maintenance_mode') : t('admin:maintenance_mode.start_maintenance_mode')}
-          </button>
-        </div>
+      <div className="mx-auto my-3">
+        <button type="button" className="btn btn-success" onClick={() => openModal()}>
+          {isMaintenanceMode ? t('admin:maintenance_mode.end_maintenance_mode') : t('admin:maintenance_mode.start_maintenance_mode')}
+        </button>
       </div>
     </div>
   );

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

@@ -50,8 +50,8 @@ const QuestionnaireSettings = (): JSX.Element => {
 
   return (
     <div id="questionnaire-settings" className="mb-5">
-      <p className="card custom-card">
-        <div className="mb-4">{t('app_setting.questionnaire_settings_explanation')}</div>
+      <p className="card custom-card bg-info-subtle">
+        <div className="mb-3">{t('app_setting.questionnaire_settings_explanation')}</div>
         <span>
           <div className="mb-2">
             <span className="text-info me-2"><span className="material-symbols-outlined">info</span>{t('app_setting.about_data_sent')}</span>
@@ -72,8 +72,8 @@ const QuestionnaireSettings = (): JSX.Element => {
 
       {!isLoading && (
         <>
-          <div className="row my-3">
-            <div className="form-check form-switch form-check-info col-md-5 offset-md-5">
+          <div className="my-4">
+            <div className="form-check form-switch form-check-info">
               <input
                 type="checkbox"
                 className="form-check-input"
@@ -87,8 +87,8 @@ const QuestionnaireSettings = (): JSX.Element => {
             </div>
           </div>
 
-          <div className="row my-4">
-            <div className="form-check form-check-info col-md-5 offset-md-5">
+          <div className="my-4">
+            <div className="form-check form-check-info">
               <input
                 type="checkbox"
                 className="form-check-input"

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

@@ -35,65 +35,61 @@ const SiteUrlSetting = (props: Props) => {
 
   return (
     <React.Fragment>
-      <p className="card custom-card">{t('site_url.desc')}</p>
+      <p className="card custom-card bg-body-tertiary">{t('site_url.desc')}</p>
       {!adminAppContainer.state.isSetSiteUrl
           && (<p className="alert alert-danger"><span className="material-symbols-outlined">error</span> {t('site_url.warn')}</p>)}
 
       { adminAppContainer.state.siteUrlUseOnlyEnvVars && (
         <div className="row">
-          <div className="col-md-9 offset-md-3">
-            <p
-              className="alert alert-info"
-              // eslint-disable-next-line react/no-danger
-              dangerouslySetInnerHTML={{
-                __html: t('site_url.note_for_the_only_env_option', { env: 'APP_SITE_URL_USES_ONLY_ENV_VARS' }),
-              }}
-            />
-          </div>
+          <p
+            className="alert alert-info"
+            // eslint-disable-next-line react/no-danger
+            dangerouslySetInnerHTML={{
+              __html: t('site_url.note_for_the_only_env_option', { env: 'APP_SITE_URL_USES_ONLY_ENV_VARS' }),
+            }}
+          />
         </div>
       ) }
 
       <div className="row">
-        <div className="col-md-9 offset-md-3">
-          <table className="table settings-table">
-            <colgroup>
-              <col className="from-db" />
-              <col className="from-env-vars" />
-            </colgroup>
-            <thead>
-              <tr>
-                <th>Database</th>
-                <th>Environment variables</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr>
-                <td>
-                  <input
-                    className="form-control"
-                    type="text"
-                    name="settingForm[app:siteUrl]"
-                    defaultValue={adminAppContainer.state.siteUrl || ''}
-                    disabled={adminAppContainer.state.siteUrlUseOnlyEnvVars ?? true}
-                    onChange={(e) => { adminAppContainer.changeSiteUrl(e.target.value) }}
-                    placeholder="e.g. https://my.growi.org"
-                  />
-                  <p className="form-text text-muted">
-                    {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('site_url.help') }} />
-                  </p>
-                </td>
-                <td>
-                  <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
-                  <p className="form-text text-muted">
-                    {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
-                  </p>
-                </td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
+        <table className="table settings-table">
+          <colgroup>
+            <col className="from-db" />
+            <col className="from-env-vars" />
+          </colgroup>
+          <thead>
+            <tr>
+              <th>Database</th>
+              <th>Environment variables</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td>
+                <input
+                  className="form-control"
+                  type="text"
+                  name="settingForm[app:siteUrl]"
+                  defaultValue={adminAppContainer.state.siteUrl || ''}
+                  disabled={adminAppContainer.state.siteUrlUseOnlyEnvVars ?? true}
+                  onChange={(e) => { adminAppContainer.changeSiteUrl(e.target.value) }}
+                  placeholder="e.g. https://my.growi.org"
+                />
+                <p className="form-text text-muted">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <span dangerouslySetInnerHTML={{ __html: t('site_url.help') }} />
+                </p>
+              </td>
+              <td>
+                <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
+                <p className="form-text text-muted">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <span dangerouslySetInnerHTML={{ __html: t('use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
+                </p>
+              </td>
+            </tr>
+          </tbody>
+        </table>
       </div>
 
       <AdminUpdateButtonRow onClick={submitHandler} disabled={adminAppContainer.state.retrieveError != null} />

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

@@ -34,7 +34,7 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
         <div className="col-12">
           <h2 className="admin-setting-header">{t('admin:customize_settings.custom_css')}</h2>
 
-          <Card className="card custom-card my-3">
+          <Card className="card custom-card bg-body-tertiary my-3">
             <CardBody className="px-0 py-2">
               { t('admin:customize_settings.write_css') }<br />
               { t('admin:customize_settings.reflect_change') }

+ 6 - 6
apps/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx

@@ -37,15 +37,15 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
       <div className="row">
         <div className="col-12">
           <h2 className="admin-setting-header">{t('admin:customize_settings.function')}</h2>
-          <Card className="card custom-card my-3">
+          <Card className="card custom-card bg-body-tertiary my-3">
             <CardBody className="px-0 py-2">
               {t('admin:customize_settings.function_desc')}
             </CardBody>
           </Card>
 
 
-          <div className="row">
-            <div className="offset-md-3 col-md-6 text-start">
+          <div className="row mt-4">
+            <div className="offset-md-2 col-md-7 text-start">
               <CustomizeFunctionOption
                 optionId="isEnabledAttachTitleHeader"
                 label={t('admin:customize_settings.function_options.attach_title_header')}
@@ -89,7 +89,7 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
           />
 
           <div className="row">
-            <div className="offset-md-3 col-md-6 text-start">
+            <div className="offset-md-2 col-md-7 text-start">
               <CustomizeFunctionOption
                 optionId="isEnabledStaleNotification"
                 label={t('admin:customize_settings.function_options.stale_notification')}
@@ -104,7 +104,7 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
           </div>
 
           <div className="row">
-            <div className="offset-md-3 col-md-6 text-start">
+            <div className="offset-md-2 col-md-7 text-start">
               <CustomizeFunctionOption
                 optionId="isAllReplyShown"
                 label={t('admin:customize_settings.function_options.show_all_reply_comments')}
@@ -119,7 +119,7 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
           </div>
 
           <div className="row">
-            <div className="offset-md-3 col-md-6 text-start">
+            <div className="offset-md-2 col-md-7 text-start">
               <CustomizeFunctionOption
                 optionId="isSearchScopeChildrenAsDefault"
                 label={t('admin:customize_settings.function_options.select_search_scope_children_as_default')}

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

@@ -36,7 +36,7 @@ const CustomizeNoscriptSetting = (props: Props): JSX.Element => {
         <div className="col-12">
           <h2 className="admin-setting-header">{t('admin:customize_settings.custom_noscript')}</h2>
 
-          <Card className="card custom-card my-3">
+          <Card className="card custom-card bg-body-tertiary my-3">
             <CardBody className="px-0 py-2">
               <span
                 // eslint-disable-next-line react/no-danger
@@ -47,7 +47,7 @@ const CustomizeNoscriptSetting = (props: Props): JSX.Element => {
 
           <div>
             <textarea
-              className="form-control"
+              className="form-control mb-2"
               name="customizeNoscript"
               rows={8}
               defaultValue={adminCustomizeContainer.state.currentCustomizeNoscript || ''}

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

@@ -35,7 +35,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
       <div className="row">
         <div className="col-12">
           <h2 className="admin-setting-header">{t('admin:customize_settings.custom_script')}</h2>
-          <Card className="card custom-card">
+          <Card className="card custom-card bg-body-tertiary mb-3">
             <CardBody className="px-0 py-2">
               {t('admin:customize_settings.write_java')}<br />
               {t('admin:customize_settings.reflect_change')}
@@ -44,7 +44,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
 
           <div>
             <textarea
-              className="form-control"
+              className="form-control mb-2"
               name="customizeScript"
               rows={8}
               defaultValue={adminCustomizeContainer.state.currentCustomizeScript || ''}

+ 2 - 2
apps/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx

@@ -42,7 +42,7 @@ const CustomizeSidebarsetting = (): JSX.Element => {
 
           <h2 className="admin-setting-header">{t('customize_settings.default_sidebar_mode.title')}</h2>
 
-          <Card className="card custom-card my-3">
+          <Card className="card custom-card bg-body-tertiary my-3">
             <CardBody className="px-0 py-2">
               {t('customize_settings.default_sidebar_mode.desc')}
             </CardBody>
@@ -79,7 +79,7 @@ const CustomizeSidebarsetting = (): JSX.Element => {
             </div>
           </div>
 
-          <Card className="card custom-card my-5">
+          <Card className="card custom-card bg-body-tertiary my-5">
             <CardBody className="px-0 py-2">
               {t('customize_settings.default_sidebar_mode.dock_mode_default_desc')}
             </CardBody>

+ 2 - 2
apps/app/src/components/Admin/Customize/CustomizeThemeOptions.tsx

@@ -29,7 +29,7 @@ const CustomizeThemeOptions = (props: Props): JSX.Element => {
 
       {/* Light and Dark Themes */}
       <div>
-        <h3>{t('customize_settings.theme_desc.light_and_dark')}</h3>
+        <h3 className="mb-3">{t('customize_settings.theme_desc.light_and_dark')}</h3>
         <div className="hstack gap-3">
           {lightNDarkThemes.map((theme) => {
             return (
@@ -46,7 +46,7 @@ const CustomizeThemeOptions = (props: Props): JSX.Element => {
 
       {/* Only one mode Theme */}
       <div className="mt-3">
-        <h3>{t('customize_settings.theme_desc.unique')}</h3>
+        <h3 className="mb-3">{t('customize_settings.theme_desc.unique')}</h3>
         <div className="hstack gap-3">
           {oneModeThemes.map((theme) => {
             return (

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

@@ -1,4 +1,5 @@
-import React, { FC, useState } from 'react';
+import type { FC } from 'react';
+import React, { useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
@@ -37,7 +38,7 @@ export const CustomizeTitle: FC = () => {
         </div>
 
         <div className="col-12">
-          <Card className="card custom-card">
+          <Card className="card custom-card bg-body-tertiary mb-3">
             <CardBody className="px-0 py-2">
               {/* eslint-disable react/no-danger */}
               <p dangerouslySetInnerHTML={{ __html: t('admin:customize_settings.custom_title_detail') }} />
@@ -58,7 +59,7 @@ export const CustomizeTitle: FC = () => {
         </div>
 
         {/* TODO i18n */}
-        <div className="form-text text-muted col-12">
+        <div className="form-text text-muted col-12 mb-3">
           Default Value: <code>&#123;&#123;pagename&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
           <br />
           Default Output Example: <code className="xml">&lt;title&gt;Page name - My GROWI&lt;&#047;title&gt;</code>

+ 1 - 1
apps/app/src/components/Admin/Customize/PagingSizeUncontrolledDropdown.jsx

@@ -18,7 +18,7 @@ const PagingSizeUncontrolledDropdown = (props) => {
   return (
     <React.Fragment>
       <div className="row">
-        <div className="offset-md-3 col-md-6 text-start">
+        <div className="offset-md-2 col-md-7 text-start">
           <div className="my-0 w-100">
             <label className="form-label">{props.label}</label>
           </div>

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

@@ -97,7 +97,7 @@ const ElasticsearchManagement = () => {
       socket.off(SocketEventName.FinishAddPage);
       socket.off(SocketEventName.RebuildingFailed);
     };
-  }, [socket]);
+  }, [retrieveIndicesStatus, socket]);
 
 
   const reconnect = async() => {

+ 8 - 6
apps/app/src/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx

@@ -71,12 +71,14 @@ class RebuildIndexControls extends React.Component {
     const header = isRebuildingCompleted ? getCompletedLabel() : getSkipLabel();
 
     return (
-      <LabeledProgressBar
-        header={header}
-        currentCount={current}
-        errorsCount={skip}
-        totalCount={total}
-      />
+      <div className="mb-3">
+        <LabeledProgressBar
+          header={header}
+          currentCount={current}
+          errorsCount={skip}
+          totalCount={total}
+        />
+      </div>
     );
   }
 

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

@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
 class StatusTable extends React.PureComponent {
 
   renderPreInitializedLabel() {
-    return <span className="badge rounded-pill bg-default">――</span>;
+    return <span className="badge text-bg-default">――</span>;
   }
 
   renderConnectionStatusLabels() {
@@ -17,13 +17,13 @@ class StatusTable extends React.PureComponent {
     } = this.props;
 
     const errorOccuredLabel = isErrorOccuredOnSearchService
-      ? <span className="badge rounded-pill bg-danger ms-2">{ t('full_text_search_management.connection_status_label_erroroccured') }</span>
+      ? <span className="badge text-bg-danger ms-2">{ t('full_text_search_management.connection_status_label_erroroccured') }</span>
       : null;
 
     let connectionStatusLabel = null;
     if (!isConfigured) {
       connectionStatusLabel = (
-        <span className="badge rounded-pill bg-default">
+        <span className="badge text-bg-default">
           { t('full_text_search_management.connection_status_label_unconfigured') }
         </span>
       );
@@ -31,8 +31,8 @@ class StatusTable extends React.PureComponent {
     else {
       connectionStatusLabel = isConnected
         // eslint-disable-next-line max-len
-        ? <span data-testid="connection-status-badge-connected" className="badge rounded-pill bg-success">{ t('full_text_search_management.connection_status_label_connected') }</span>
-        : <span className="badge rounded-pill bg-danger">{ t('full_text_search_management.connection_status_label_disconnected') }</span>;
+        ? <span data-testid="connection-status-badge-connected" className="badge text-bg-success">{ t('full_text_search_management.connection_status_label_connected') }</span>
+        : <span className="badge text-bg-danger">{ t('full_text_search_management.connection_status_label_disconnected') }</span>;
     }
 
     return (
@@ -46,8 +46,8 @@ class StatusTable extends React.PureComponent {
     const { t, isNormalized } = this.props;
 
     return isNormalized
-      ? <span className="badge rounded-pill bg-info">{ t('full_text_search_management.indices_status_label_normalized') }</span>
-      : <span className="badge rounded-pill bg-warning text-dark">{ t('full_text_search_management.indices_status_label_unnormalized') }</span>;
+      ? <span className="badge text-bg-info">{ t('full_text_search_management.indices_status_label_normalized') }</span>
+      : <span className="badge text-bg-warning">{ t('full_text_search_management.indices_status_label_unnormalized') }</span>;
   }
 
   renderIndexInfoPanel(indexName, body = {}, aliases = []) {
@@ -55,8 +55,9 @@ class StatusTable extends React.PureComponent {
 
     const aliasLabels = aliases.map((aliasName) => {
       return (
-        <span key={`badge-${indexName}-${aliasName}`} className="badge rounded-pill bg-primary me-2">
-          <span className="material-symbols-outlined">sell</span> {aliasName}
+        <span key={`badge-${indexName}-${aliasName}`} className="badge text-bg-primary me-2">
+          <span className="material-symbols-outlined">sell</span>
+          <span>{aliasName}</span>
         </span>
       );
     });

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

@@ -103,7 +103,7 @@ const SelectCollectionsModal = (props: Props): JSX.Element => {
     const html = t('admin:export_management.desc_password_seed');
 
     // eslint-disable-next-line react/no-danger
-    return <div className="card custom-card" dangerouslySetInnerHTML={{ __html: html }}></div>;
+    return <div className="card custom-card bg-body-tertiary" dangerouslySetInnerHTML={{ __html: html }}></div>;
   }, [selectedCollections, t]);
 
   const renderCheckboxes = useCallback((collectionNames, color?) => {
@@ -156,7 +156,7 @@ const SelectCollectionsModal = (props: Props): JSX.Element => {
   }, [isAllChecked, checkAll]);
 
   return (
-    <Modal isOpen={isOpen} toggle={onClose}>
+    <Modal size="lg" isOpen={isOpen} toggle={onClose}>
       <ModalHeader tag="h4" toggle={onClose} className="text-info">
         {t('admin:export_management.export_collections')}
       </ModalHeader>

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

@@ -151,7 +151,7 @@ const ExportArchiveDataPage = (): JSX.Element => {
       ) }
 
       <div className="mt-5">
-        <h3>{t('export_management.exported_data_list')}</h3>
+        <h3 className="mb-3">{t('export_management.exported_data_list')}</h3>
         <ArchiveFilesTable
           zipFileStats={zipFileStats}
           onZipFileStatRemove={onZipFileStatRemove}

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

@@ -9,7 +9,7 @@ export const FullTextSearchManagement = (): JSX.Element => {
 
   return (
     <div data-testid="admin-full-text-search">
-      <h2> { t('full_text_search_management.elasticsearch_management') } </h2>
+      <h2 className="mb-4"> { t('full_text_search_management.elasticsearch_management') } </h2>
       <ElasticsearchManagement />
     </div>
   );

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

@@ -75,7 +75,7 @@ class UploadForm extends React.Component {
             </div>
           </div>
           <div className="row">
-            <div className="mx-auto">
+            <div className="mx-auto mt-3">
               <button type="submit" className="btn btn-primary" disabled={!this.validateForm()}>
                 {t('admin:importer_management.growi_settings.upload')}
               </button>

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

@@ -92,8 +92,8 @@ class GrowiArchiveSection extends React.Component {
 
     return (
       <Fragment>
-        <h2>{t('importer_management.import_growi_archive')}</h2>
-        <div className="card custom-card mb-4 small">
+        <h2 className="mb-3">{t('importer_management.import_growi_archive')}</h2>
+        <div className="card custom-card bg-body-tertiary mb-4 small">
           <ul>
             <li>{t('importer_management.skip_username_and_email_when_overlapped')}</li>
             <li>{t('importer_management.prepare_new_account_for_migration')}</li>

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

@@ -57,13 +57,13 @@ class ImportDataPageContents extends React.Component {
               </tbody>
             </table>
 
-            <div className="card custom-card mb-0 small">
+            <div className="card custom-card bg-body-tertiary mb-0 small">
               <ul>
                 <li>{t('importer_management.page_skip')}</li>
               </ul>
             </div>
 
-            <div className="row">
+            <div className="row mt-4">
               <input type="password" name="dummypass" style={{ display: 'none', top: '-100px', left: '-100px' }} />
             </div>
 
@@ -83,7 +83,7 @@ class ImportDataPageContents extends React.Component {
 
             </div>
 
-            <div className="row">
+            <div className="row mt-3">
               <label htmlFor="settingForm[importer:esa:access_token]" className="text-start text-md-end col-md-3 col-form-label">
                 {t('importer_management.esa_settings.access_token')}
               </label>
@@ -98,12 +98,12 @@ class ImportDataPageContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row mt-3">
               <div className="offset-md-3 col-md-6">
                 <input
                   id="testConnectionToEsa"
                   type="button"
-                  className="btn btn-primary btn-esa"
+                  className="btn btn-primary btn-esa me-3"
                   name="Esa"
                   onClick={adminImportContainer.esaHandleSubmit}
                   value={t('importer_management.import')}
@@ -163,16 +163,16 @@ class ImportDataPageContents extends React.Component {
                 </tr>
               </tbody>
             </table>
-            <div className="card custom-card mb-0 small">
+            <div className="card custom-card bg-body-tertiary mb-3 small">
               <ul>
                 <li>{t('importer_management.page_skip')}</li>
               </ul>
             </div>
 
-            <div className="row">
+            <div className="row mt-3">
               <input type="password" name="dummypass" style={{ display: 'none', top: '-100px', left: '-100px' }} />
             </div>
-            <div className="row">
+            <div className="row mt-3">
               <label htmlFor="settingForm[importer:qiita:team_name]" className="text-start text-md-end col-md-3 col-form-label">
                 {t('importer_management.qiita_settings.team_name')}
               </label>
@@ -187,7 +187,7 @@ class ImportDataPageContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row mt-3">
               <label htmlFor="settingForm[importer:qiita:access_token]" className="text-start text-md-end col-md-3 col-form-label">
                 {t('importer_management.qiita_settings.access_token')}
               </label>
@@ -203,12 +203,12 @@ class ImportDataPageContents extends React.Component {
             </div>
 
 
-            <div className="row">
+            <div className="row mt-3">
               <div className="offset-md-3 col-md-6">
                 <input
                   id="testConnectionToQiita"
                   type="button"
-                  className="btn btn-primary btn-qiita"
+                  className="btn btn-primary btn-qiita me-3"
                   name="Qiita"
                   onClick={adminImportContainer.qiitaHandleSubmit}
                   value={t('importer_management.import')}

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

@@ -99,9 +99,9 @@ class SlackConfiguration extends React.Component {
         )
           : (
             <React.Fragment>
-              <h2 className="border-bottom mb-5">{t('notification_settings.slack_app_configuration')}</h2>
+              <h2 className="border-bottom mb-3">{t('notification_settings.slack_app_configuration')}</h2>
 
-              <div className="card custom-card">
+              <div className="card custom-card bg-danger-subtle">
                 <span className="text-danger"><span className="material-symbols-outlined">error</span>NOT RECOMMENDED</span>
                 <br />
                 {/* eslint-disable-next-line react/no-danger */}
@@ -116,7 +116,7 @@ class SlackConfiguration extends React.Component {
                 </a>
               </div>
 
-              <div className="row mb-5">
+              <div className="row mb-5 mt-4">
                 <label className="form-label col-md-3 text-start text-md-end">OAuth access token</label>
                 <div className="col-md-6">
                   <input
@@ -144,8 +144,8 @@ class SlackConfiguration extends React.Component {
           <a href="#collapseHelpForIwh" data-bs-toggle="collapse">{t('notification_settings.how_to.header')}</a>
         </h3>
 
-        <ol id="collapseHelpForIwh" className="collapse">
-          <li>
+        <ol id="collapseHelpForIwh" className="collapse card custom-card bg-body-tertiary">
+          <li className="ms-3">
             {t('notification_settings.how_to.workspace')}
             <ol>
               {/* eslint-disable-next-line react/no-danger */}
@@ -154,7 +154,7 @@ class SlackConfiguration extends React.Component {
               <li>{t('notification_settings.how_to.workspace_desc3')}</li>
             </ol>
           </li>
-          <li>
+          <li className="ms-3">
             {t('notification_settings.how_to.at_growi')}
             <ol>
               {/* eslint-disable-next-line react/no-danger */}

+ 6 - 6
apps/app/src/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx

@@ -40,24 +40,24 @@ const MarkDownSettingContents = React.memo((props: Props): JSX.Element => {
   }, [adminMarkDownContainer]);
 
   return (
-    <div data-testid="admin-markdown">
+    <div data-testid="admin-markdown" className="mb-5">
       {/* Line Break Setting */}
       <h2 className="admin-setting-header">{t('markdown_settings.lineBreak_header')}</h2>
-      <Card className="card custom-card my-3">
+      <Card className="card custom-card bg-body-tertiary my-3">
         <CardBody className="px-0 py-2">{ t('markdown_settings.lineBreak_desc') }</CardBody>
       </Card>
       <LineBreakForm />
 
       {/* Indent Setting */}
-      <h2 className="admin-setting-header">{t('markdown_settings.indent_header')}</h2>
-      <Card className="card custom-card my-3">
+      <h2 className="admin-setting-header mt-5">{t('markdown_settings.indent_header')}</h2>
+      <Card className="card custom-card bg-body-tertiary my-3">
         <CardBody className="px-0 py-2">{t('markdown_settings.indent_desc') }</CardBody>
       </Card>
       <IndentForm />
 
       {/* XSS Setting */}
-      <h2 className="admin-setting-header">{ t('markdown_settings.xss_header') }</h2>
-      <Card className="card custom-card my-3">
+      <h2 className="admin-setting-header mt-5">{ t('markdown_settings.xss_header') }</h2>
+      <Card className="card custom-card bg-body-tertiary my-3">
         <CardBody className="px-0 py-2">{ t('markdown_settings.xss_desc') }</CardBody>
       </Card>
       <XssForm />

+ 2 - 2
apps/app/src/components/Admin/MarkdownSetting/WhitelistInput.jsx

@@ -41,7 +41,7 @@ class WhitelistInput extends React.Component {
         <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('markdown_settings.xss_options.tag_names')}
-            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendTagButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary" onClick={this.onClickRecommendTagButton}>
               {t('markdown_settings.xss_options.import_recommended', { target: 'Tags' })}
             </p>
           </div>
@@ -58,7 +58,7 @@ class WhitelistInput extends React.Component {
         <div className="mt-4">
           <div className="d-flex justify-content-between">
             {t('markdown_settings.xss_options.tag_attributes')}
-            <p id="btn-import-tags" className="btn btn-sm btn-primary mb-0" onClick={this.onClickRecommendAttrButton}>
+            <p id="btn-import-tags" className="btn btn-sm btn-primary" onClick={this.onClickRecommendAttrButton}>
               {t('markdown_settings.xss_options.import_recommended', { target: 'Attrs' })}
             </p>
           </div>

+ 4 - 4
apps/app/src/components/Admin/MarkdownSetting/XssForm.jsx

@@ -45,10 +45,10 @@ class XssForm extends React.Component {
     const rehypeRecommendedAttributes = JSON.stringify(sanitizeDefaultSchema.attributes);
 
     return (
-      <div className="col-12 my-3">
+      <div className="col-12 mt-3">
         <div className="row">
 
-          <div className="col-md-6 col-sm-12 align-self-start mb-4">
+          <div className="col-md-6 col-sm-12 align-self-start">
             <div className="form-check">
               <input
                 type="radio"
@@ -90,7 +90,7 @@ class XssForm extends React.Component {
             </div>
           </div>
 
-          <div className="col-md-6 col-sm-12 align-self-start mb-4">
+          <div className="col-md-6 col-sm-12 align-self-start">
             <div className="form-check">
               <input
                 type="radio"
@@ -119,7 +119,7 @@ class XssForm extends React.Component {
       <React.Fragment>
         <fieldset className="col-12">
           <div>
-            <div className="col-8 offset-4 my-3">
+            <div className="col-8 offset-4 mt-3">
               <div className="form-check form-switch form-check-success">
                 <input
                   type="checkbox"

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

@@ -36,7 +36,7 @@ const GlobalNotification = (props) => {
     <>
       <h2 className="border-bottom my-4">{t('notification_settings.valid_page')}</h2>
 
-      <p className="card custom-card">
+      <p className="card custom-card bg-body-tertiary">
         {/* eslint-disable-next-line react/no-danger */}
         <span dangerouslySetInnerHTML={{ __html: t('notification_settings.link_notification_help') }} />
       </p>
@@ -75,7 +75,7 @@ const GlobalNotification = (props) => {
           </div>
         </div>
       </div>
-      <div className="row my-3">
+      <div className="row mt-3 mb-5">
         <div className="col-sm-5 offset-sm-4">
           <button
             type="button"
@@ -87,14 +87,15 @@ const GlobalNotification = (props) => {
         </div>
       </div>
 
-      <h2 className="border-bottom mb-5">{t('notification_settings.notification_list')}
-        <button
-          className="btn btn-outline-secondary pull-right"
-          type="button"
-          onClick={() => router.push('/admin/global-notification/new')}
-        >{t('notification_settings.add_notification')}
-        </button>
+      <h2 className="border-bottom mb-3">
+        {t('notification_settings.notification_list')}
       </h2>
+      <button
+        className="btn btn-outline-secondary mb-3"
+        type="button"
+        onClick={() => router.push('/admin/global-notification/new')}
+      >{t('notification_settings.add_notification')}
+      </button>
       <table className="table table-bordered">
         <thead>
           <tr>

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

@@ -31,14 +31,14 @@ const Badge = ({ isEnabled }) => {
   const { t } = useTranslation('admin');
 
   return isEnabled
-    ? <span className="badge bg-success">{t('external_notification.enabled')}</span>
-    : <span className="badge bg-primary">{t('external_notification.disabled')}</span>;
+    ? <span className="badge text-bg-success">{t('external_notification.enabled')}</span>
+    : <span className="badge text-bg-primary">{t('external_notification.disabled')}</span>;
 };
 
 const SkeletonListItem = () => (
   <li className="list-group-item">
     <h4 className="mb-2">
-      <span className="badge bg-primary">――</span>
+      <span className="badge text-bg-primary">――</span>
       <span className="ms-2">...</span>
     </h4>
   </li>
@@ -51,7 +51,7 @@ const SlackIntegrationListItem = ({ isEnabled, currentBotType }) => {
   const isCautionVisible = currentBotType === SlackbotType.OFFICIAL || currentBotType === SlackbotType.CUSTOM_WITH_PROXY;
 
   return (
-    <li data-testid="slack-integration-list-item" className="list-group-item">
+    <li data-testid="slack-integration-list-item" className="list-group-item bg-body-tertiary">
       <h4>
         <Badge isEnabled={isEnabled} />
         <a href="/admin/slack-integration" className="ms-2">{t('slack_integration.slack_integration')}</a>

+ 16 - 14
apps/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx

@@ -56,7 +56,7 @@ class GitHubSecurityManagementContents extends React.Component {
           </div>
         )}
 
-        <div className="row">
+        <div className="row my-4">
           <div className="col-6 offset-3">
             <div className="form-check form-switch form-check-success">
               <input
@@ -71,11 +71,11 @@ class GitHubSecurityManagementContents extends React.Component {
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('github') && isGitHubEnabled)
-              && <div className="badge bg-warning text-dark">{t('security_settings.setup_is_not_yet_complete')}</div>}
+              && <div className="badge text-bg-warning">{t('security_settings.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
-        <div className="row mb-5">
+        <div className="row mb-4">
           <label className="form-label col-12 col-md-3 text-start text-md-end py-2">{t('security_settings.callback_URL')}</label>
           <div className="col-12 col-md-6">
             <input
@@ -100,9 +100,9 @@ class GitHubSecurityManagementContents extends React.Component {
         {isGitHubEnabled && (
           <React.Fragment>
 
-            <h3 className="border-bottom">{t('security_settings.configuration')}</h3>
+            <h3 className="border-bottom mb-4">{t('security_settings.configuration')}</h3>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="githubClientId" className="col-3 text-end py-2 form-label">{t('security_settings.clientID')}</label>
               <div className="col-6">
                 <input
@@ -118,7 +118,7 @@ class GitHubSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-3">
               <label htmlFor="githubClientSecret" className="col-3 text-end py-2 form-label">{t('security_settings.client_secret')}</label>
               <div className="col-6">
                 <input
@@ -134,7 +134,7 @@ class GitHubSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-3">
               <div className="offset-3 col-6 text-start">
                 <div className="form-check form-check-success">
                   <input
@@ -156,7 +156,7 @@ class GitHubSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row my-3">
+            <div className="row mb-4">
               <div className="offset-3 col-5">
                 <div className="btn btn-primary" disabled={adminGitHubSecurityContainer.state.retrieveError != null} onClick={this.onClickSubmit}>
                   {t('Update')}
@@ -174,12 +174,14 @@ class GitHubSecurityManagementContents extends React.Component {
             <span className="material-symbols-outlined" aria-hidden="true">help</span>
             <a href="#collapseHelpForGitHubOauth" data-bs-toggle="collapse"> {t('security_settings.OAuth.how_to.github')}</a>
           </h4>
-          <ol id="collapseHelpForGitHubOauth" className="collapse">
-            {/* eslint-disable-next-line max-len */}
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.GitHub.register_1', { link: '<a href="https://github.com/settings/developers" target=_blank>GitHub Developer Settings</a>' }) }} />
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.GitHub.register_2', { url: gitHubCallbackUrl }) }} />
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.GitHub.register_3') }} />
-          </ol>
+          <div className="card custom-card bg-body-tertiary">
+            <ol id="collapseHelpForGitHubOauth" className="collapse mb-0">
+              {/* eslint-disable-next-line max-len */}
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.GitHub.register_1', { link: '<a href="https://github.com/settings/developers" target=_blank>GitHub Developer Settings</a>' }) }} />
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.GitHub.register_2', { url: gitHubCallbackUrl }) }} />
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.GitHub.register_3') }} />
+            </ol>
+          </div>
         </div>
 
       </React.Fragment>

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

@@ -54,7 +54,7 @@ class GoogleSecurityManagementContents extends React.Component {
           </div>
         )}
 
-        <div className="row">
+        <div className="row my-4">
           <div className="col-6 offset-3">
             <div className="form-check form-switch form-check-success">
               <input
@@ -69,7 +69,7 @@ class GoogleSecurityManagementContents extends React.Component {
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('google') && isGoogleEnabled)
-              && <div className="badge bg-warning text-dark">{t('security_settings.setup_is_not_yet_complete')}</div>}
+              && <div className="badge text-bg-warning">{t('security_settings.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -99,9 +99,9 @@ class GoogleSecurityManagementContents extends React.Component {
         {isGoogleEnabled && (
           <React.Fragment>
 
-            <h3 className="border-bottom">{t('security_settings.configuration')}</h3>
+            <h3 className="border-bottom mb-4">{t('security_settings.configuration')}</h3>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="googleClientId" className="col-3 text-end py-2 form-label">{t('security_settings.clientID')}</label>
               <div className="col-6">
                 <input
@@ -117,7 +117,7 @@ class GoogleSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="googleClientSecret" className="col-3 text-end py-2 form-label">{t('security_settings.client_secret')}</label>
               <div className="col-6">
                 <input
@@ -133,7 +133,7 @@ class GoogleSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-3">
               <div className="offset-3 col-6">
                 <div className="form-check form-check-success">
                   <input
@@ -155,7 +155,7 @@ class GoogleSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row my-3">
+            <div className="row mb-4">
               <div className="offset-3 col-5">
                 <button
                   type="button"
@@ -178,14 +178,16 @@ class GoogleSecurityManagementContents extends React.Component {
             <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">
-            {/* eslint-disable-next-line max-len */}
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_1', { link: '<a href="https://console.cloud.google.com/apis/credentials" target=_blank>Google Cloud Platform API Manager</a>' }) }} />
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_2') }} />
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_3') }} />
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_4', { url: googleCallbackUrl }) }} />
-            <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_5') }} />
-          </ol>
+          <div className="card custom-card bg-body-tertiary">
+            <ol id="collapseHelpForGoogleOauth" className="collapse mb-0">
+              {/* eslint-disable-next-line max-len */}
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_1', { link: '<a href="https://console.cloud.google.com/apis/credentials" target=_blank>Google Cloud Platform API Manager</a>' }) }} />
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_2') }} />
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_3') }} />
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_4', { url: googleCallbackUrl }) }} />
+              <li dangerouslySetInnerHTML={{ __html: t('security_settings.OAuth.Google.register_5') }} />
+            </ol>
+          </div>
         </div>
 
       </React.Fragment>

+ 30 - 30
apps/app/src/components/Admin/Security/LdapSecuritySettingContents.jsx

@@ -54,11 +54,11 @@ class LdapSecuritySettingContents extends React.Component {
     return (
       <React.Fragment>
 
-        <h2 className="alert-anchor border-bottom">
+        <h2 className="alert-anchor border-bottom mb-4">
           LDAP
         </h2>
 
-        <div className="row">
+        <div className="row my-4">
           <div className="col-6 offset-3">
             <div className="form-check form-switch form-check-success">
               <input
@@ -73,7 +73,7 @@ class LdapSecuritySettingContents extends React.Component {
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isLdapEnabled)
-              && <div className="badge bg-warning text-dark">{t('security_settings.setup_is_not_yet_complete')}</div>}
+              && <div className="badge text-bg-warning">{t('security_settings.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -81,13 +81,13 @@ class LdapSecuritySettingContents extends React.Component {
         {isLdapEnabled && (
           <React.Fragment>
 
-            <h3 className="border-bottom">{t('security_settings.configuration')}</h3>
+            <h3 className="border-bottom mb-4">{t('security_settings.configuration')}</h3>
 
-            <div className="row">
+            <div className="row my-3">
               <label htmlFor="serverUrl" className="text-start text-md-end col-md-3 col-form-label">
                 Server URL
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -106,11 +106,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong>{t('security_settings.ldap.bind_mode')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <div className="dropdown">
                   <button
                     className="btn btn-outline-secondary dropdown-toggle"
@@ -136,11 +136,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong>Bind DN</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -171,11 +171,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <div htmlFor="bindDNPassword" className="text-start text-md-end col-md-3 col-form-label">
                 <strong>{t('security_settings.ldap.bind_DN_password')}</strong>
               </div>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 {(adminLdapSecurityContainer.state.isUserBind) ? (
                   <p className="card custom-card passport-ldap-userbind">
                     <small>
@@ -202,11 +202,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong>{t('security_settings.ldap.search_filter')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -234,15 +234,15 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <h3 className="alert-anchor border-bottom">
+            <h3 className="alert-anchor border-bottom mb-4">
               Attribute Mapping ({t('optional')})
             </h3>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong htmlFor="attrMapUsername">{t('username')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -258,8 +258,8 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
-              <div className="offset-md-3 col-md-6">
+            <div className="row my-3">
+              <div className="offset-md-3 col-md-9">
                 <div className="form-check form-check-success">
                   <input
                     type="checkbox"
@@ -282,11 +282,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong htmlFor="attrMapMail">{t('Email')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -303,11 +303,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong htmlFor="attrMapName">{t('Name')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -324,15 +324,15 @@ class LdapSecuritySettingContents extends React.Component {
             </div>
 
 
-            <h3 className="alert-anchor border-bottom">
+            <h3 className="alert-anchor border-bottom mb-4">
               {t('security_settings.ldap.group_search_filter')} ({t('optional')})
             </h3>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong htmlFor="groupSearchBase">{t('security_settings.ldap.group_search_base_DN')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -350,11 +350,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong htmlFor="groupSearchFilter">{t('security_settings.ldap.group_search_filter')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"
@@ -381,11 +381,11 @@ class LdapSecuritySettingContents extends React.Component {
               </div>
             </div>
 
-            <div className="row">
+            <div className="row my-3">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">
                 <strong htmlFor="groupDnProperty">{t('security_settings.ldap.group_search_user_DN_property')}</strong>
               </label>
-              <div className="col-md-6">
+              <div className="col-md-9">
                 <input
                   className="form-control"
                   type="text"

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

@@ -62,7 +62,7 @@ class LocalSecuritySettingContents extends React.Component {
           />
         )}
 
-        <div className="row mb-5">
+        <div className="row mt-4 mb-5">
           <div className="col-6 offset-3">
             <div className="form-check form-switch form-check-success">
               <input
@@ -88,10 +88,10 @@ class LocalSecuritySettingContents extends React.Component {
             <h3 className="border-bottom">{t('security_settings.configuration')}</h3>
 
             <div className="row">
-              <div className="col-12 col-md-3 text-start text-md-end py-2">
+              <div className="col-12 col-md-4 text-start text-md-end py-2">
                 <strong>{t('security_settings.register_limitation')}</strong>
               </div>
-              <div className="col-12 col-md-6">
+              <div className="col-12 col-md-8">
                 <div className="dropdown">
                   <button
                     className="btn btn-outline-secondary dropdown-toggle"
@@ -139,10 +139,10 @@ class LocalSecuritySettingContents extends React.Component {
               </div>
             </div>
             <div className="row">
-              <div className="col-12 col-md-3 text-start text-md-end">
+              <div className="col-12 col-md-4 text-start text-md-end">
                 <strong dangerouslySetInnerHTML={{ __html: t('security_settings.The whitelist of registration permission E-mail address') }} />
               </div>
-              <div className="col-12 col-md-6">
+              <div className="col-12 col-md-8">
                 <textarea
                   className="form-control"
                   type="textarea"
@@ -163,8 +163,8 @@ class LocalSecuritySettingContents extends React.Component {
             </div>
 
             <div className="row">
-              <label className="col-12 col-md-3 text-start text-md-end  col-form-label">{t('security_settings.Local.password_reset_by_users')}</label>
-              <div className="col-12 col-md-6">
+              <label className="col-12 col-md-4 text-start text-md-end  col-form-label">{t('security_settings.Local.password_reset_by_users')}</label>
+              <div className="col-12 col-md-8">
                 <div className="form-check form-switch form-check-success">
                   <input
                     type="checkbox"
@@ -178,7 +178,7 @@ class LocalSecuritySettingContents extends React.Component {
                   </label>
                 </div>
                 {!isMailerSetup && (
-                  <div className="alert alert-warning p-1 my-1 small d-inline-block">
+                  <div className="alert alert-warning p-2 my-1 small d-inline-block">
                     <span>{t('commons:alert.password_reset_please_enable_mailer')}</span>
                     <Link href="/admin/app#mail-settings">
                       <span className="material-symbols-outlined">link</span> {t('app_setting.mail_settings')}
@@ -192,8 +192,8 @@ class LocalSecuritySettingContents extends React.Component {
             </div>
 
             <div className="row">
-              <label className="col-12 col-md-3 text-start text-md-end  col-form-label">{t('security_settings.Local.email_authentication')}</label>
-              <div className="col-12 col-md-6">
+              <label className="col-12 col-md-4 text-start text-md-end  col-form-label">{t('security_settings.Local.email_authentication')}</label>
+              <div className="col-12 col-md-8">
                 <div className="form-check form-switch form-check-success">
                   <input
                     type="checkbox"
@@ -207,7 +207,7 @@ class LocalSecuritySettingContents extends React.Component {
                   </label>
                 </div>
                 {!isMailerSetup && (
-                  <div className="alert alert-warning p-1 my-1 small d-inline-block">
+                  <div className="alert alert-warning p-2 my-1 small d-inline-block">
                     <span>{t('commons:alert.please_enable_mailer')}</span>
                     <Link href="/admin/app#mail-settings">
                       <span className="material-symbols-outlined">link</span> {t('app_setting.mail_settings')}

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

@@ -48,7 +48,7 @@ class OidcSecurityManagementContents extends React.Component {
           {t('security_settings.OAuth.OIDC.name')}
         </h2>
 
-        <div className="row mb-5">
+        <div className="row  my-4">
           <div className="offset-3 col-6">
             <div className="form-check form-switch form-check-success">
               <input
@@ -63,7 +63,7 @@ class OidcSecurityManagementContents extends React.Component {
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('oidc') && isOidcEnabled)
-              && <div className="badge bg-warning text-dark">{t('security_settings.setup_is_not_yet_complete')}</div>}
+              && <div className="badge text-bg-warning">{t('security_settings.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -92,9 +92,9 @@ class OidcSecurityManagementContents extends React.Component {
         {isOidcEnabled && (
           <>
 
-            <h3 className="border-bottom">{t('security_settings.configuration')}</h3>
+            <h3 className="border-bottom mb-4">{t('security_settings.configuration')}</h3>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcProviderName" className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.providerName')}</label>
               <div className="col-md-6">
                 <input
@@ -107,7 +107,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcIssuerHost" className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.issuerHost')}</label>
               <div className="col-md-6">
                 <input
@@ -123,7 +123,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcClientId" className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.clientID')}</label>
               <div className="col-md-6">
                 <input
@@ -139,7 +139,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcClientSecret" className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.client_secret')}</label>
               <div className="col-md-6">
                 <input
@@ -155,7 +155,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcAuthorizationEndpoint" className="text-start text-md-end col-md-3 col-form-label">
                 {t('security_settings.authorization_endpoint')}
               </label>
@@ -173,7 +173,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcTokenEndpoint" className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.token_endpoint')}</label>
               <div className="col-md-6">
                 <input
@@ -189,7 +189,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcRevocationEndpoint" className="text-start text-md-end col-md-3 col-form-label">
                 {t('security_settings.revocation_endpoint')}
               </label>
@@ -207,7 +207,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcIntrospectionEndpoint" className="text-start text-md-end col-md-3 col-form-label">
                 {t('security_settings.introspection_endpoint')}
               </label>
@@ -225,7 +225,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcUserInfoEndpoint" className="text-start text-md-end col-md-3 col-form-label">
                 {t('security_settings.userinfo_endpoint')}
               </label>
@@ -243,7 +243,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcEndSessionEndpoint" className="text-start text-md-end col-md-3 col-form-label">
                 {t('security_settings.end_session_endpoint')}
               </label>
@@ -261,7 +261,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcRegistrationEndpoint" className="text-start text-md-end col-md-3 col-form-label">
                 {t('security_settings.registration_endpoint')}
               </label>
@@ -279,7 +279,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcJWKSUri" className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.jwks_uri')}</label>
               <div className="col-md-6">
                 <input
@@ -295,11 +295,11 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <h3 className="alert-anchor border-bottom">
+            <h3 className="alert-anchor border-bottom mb-4">
               Attribute Mapping ({t('optional')})
             </h3>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcAttrMapId" className="text-start text-md-end col-md-3 col-form-label">Identifier</label>
               <div className="col-md-6">
                 <input
@@ -315,7 +315,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcAttrMapUserName" className="text-start text-md-end col-md-3 col-form-label">{t('username')}</label>
               <div className="col-md-6">
                 <input
@@ -331,7 +331,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcAttrMapName" className="text-start text-md-end col-md-3 col-form-label">{t('Name')}</label>
               <div className="col-md-6">
                 <input
@@ -347,7 +347,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label htmlFor="oidcAttrMapEmail" className="text-start text-md-end col-md-3 col-form-label">{t('Email')}</label>
               <div className="col-md-6">
                 <input
@@ -363,7 +363,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <label className="form-label text-start text-md-end col-md-3 col-form-label">{t('security_settings.callback_URL')}</label>
               <div className="col-md-6">
                 <input
@@ -385,7 +385,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <div className="offset-md-3 col-md-6">
                 <div className="form-check form-check-success">
                   <input
@@ -407,7 +407,7 @@ class OidcSecurityManagementContents extends React.Component {
               </div>
             </div>
 
-            <div className="row mb-5">
+            <div className="row mb-4">
               <div className="offset-md-3 col-md-6">
                 <div className="form-check form-check-success">
                   <input
@@ -452,11 +452,13 @@ class OidcSecurityManagementContents extends React.Component {
             <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">
-            <li>{t('security_settings.OAuth.OIDC.register_1')}</li>
-            <li>{t('security_settings.OAuth.OIDC.register_2')}</li>
-            <li>{t('security_settings.OAuth.OIDC.register_3')}</li>
-          </ol>
+          <div className=" card custom-card bg-body-tertiary">
+            <ol id="collapseHelpForOidcOauth" className="collapse mb-0">
+              <li>{t('security_settings.OAuth.OIDC.register_1')}</li>
+              <li>{t('security_settings.OAuth.OIDC.register_2')}</li>
+              <li>{t('security_settings.OAuth.OIDC.register_3')}</li>
+            </ol>
+          </div>
         </div>
 
       </>

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

@@ -64,7 +64,7 @@ class SamlSecurityManagementContents extends React.Component {
           />
         )}
 
-        <div className="row mb-5">
+        <div className="row mt-4 mb-5">
           <div className="col-6 offset-3">
             <div className="form-check form-switch form-check-success">
               <input
@@ -80,7 +80,7 @@ class SamlSecurityManagementContents extends React.Component {
               </label>
             </div>
             {(!adminGeneralSecurityContainer.state.setupStrategies.includes('saml') && isSamlEnabled)
-              && <div className="badge bg-warning text-dark">{t('security_settings.setup_is_not_yet_complete')}</div>}
+              && <div className="badge text-bg-warning">{t('security_settings.setup_is_not_yet_complete')}</div>}
           </div>
         </div>
 
@@ -112,7 +112,7 @@ class SamlSecurityManagementContents extends React.Component {
             {(adminSamlSecurityContainer.state.missingMandatoryConfigKeys.length !== 0) && (
               <div className="alert alert-danger">
                 {t('security_settings.missing mandatory configs')}
-                <ul>
+                <ul className="mb-0">
                   {adminSamlSecurityContainer.state.missingMandatoryConfigKeys.map((configKey) => {
                     const key = configKey.replace('security:passport-saml:', '');
                     return <li key={configKey}>{t(`security_settings.form_item_name.${key}`)}</li>;
@@ -122,7 +122,7 @@ class SamlSecurityManagementContents extends React.Component {
             )}
 
 
-            <h3 className="alert-anchor border-bottom">
+            <h3 className="alert-anchor border-bottom mb-3">
               Basic Settings
             </h3>
 
@@ -232,7 +232,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
               </tbody>
             </table>
 
-            <h3 className="alert-anchor border-bottom">
+            <h3 className="alert-anchor border-bottom mt-5 mb-3">
               Attribute Mapping
             </h3>
 
@@ -386,55 +386,51 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
               </tbody>
             </table>
 
-            <h3 className="alert-anchor border-bottom">
+            <h3 className="alert-anchor border-bottom mt-5 mb-4">
               Attribute Mapping Options
             </h3>
 
-            <div className="row mb-5">
-              <div className="offset-md-3 col-md-6 text-start">
-                <div className="form-check form-check-success">
-                  <input
-                    id="bindByUserName-SAML"
-                    className="form-check-input"
-                    type="checkbox"
-                    checked={adminSamlSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
-                    onChange={() => { adminSamlSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
-                  />
-                  <label
-                    className="form-label form-check-label"
-                    htmlFor="bindByUserName-SAML"
-                    dangerouslySetInnerHTML={{ __html: t('security_settings.Treat username matching as identical') }}
-                  />
-                </div>
-                <p className="form-text text-muted">
-                  <small dangerouslySetInnerHTML={{ __html: t('security_settings.Treat username matching as identical_warn') }} />
-                </p>
+            <div className="row ms-3">
+              <div className="form-check form-check-success">
+                <input
+                  id="bindByUserName-SAML"
+                  className="form-check-input"
+                  type="checkbox"
+                  checked={adminSamlSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser || false}
+                  onChange={() => { adminSamlSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
+                />
+                <label
+                  className="form-label form-check-label"
+                  htmlFor="bindByUserName-SAML"
+                  dangerouslySetInnerHTML={{ __html: t('security_settings.Treat username matching as identical') }}
+                />
               </div>
+              <p className="form-text text-muted">
+                <small dangerouslySetInnerHTML={{ __html: t('security_settings.Treat username matching as identical_warn') }} />
+              </p>
             </div>
 
-            <div className="row mb-5">
-              <div className="offset-md-3 col-md-6 text-start">
-                <div className="form-check form-check-success">
-                  <input
-                    id="bindByEmail-SAML"
-                    className="form-check-input"
-                    type="checkbox"
-                    checked={adminSamlSecurityContainer.state.isSameEmailTreatedAsIdenticalUser || false}
-                    onChange={() => { adminSamlSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
-                  />
-                  <label
-                    className="form-label form-check-label"
-                    htmlFor="bindByEmail-SAML"
-                    dangerouslySetInnerHTML={{ __html: t('security_settings.Treat email matching as identical') }}
-                  />
-                </div>
-                <p className="form-text text-muted">
-                  <small dangerouslySetInnerHTML={{ __html: t('security_settings.Treat email matching as identical_warn') }} />
-                </p>
+            <div className="row mb-5 ms-3">
+              <div className="form-check form-check-success">
+                <input
+                  id="bindByEmail-SAML"
+                  className="form-check-input"
+                  type="checkbox"
+                  checked={adminSamlSecurityContainer.state.isSameEmailTreatedAsIdenticalUser || false}
+                  onChange={() => { adminSamlSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
+                />
+                <label
+                  className="form-label form-check-label"
+                  htmlFor="bindByEmail-SAML"
+                  dangerouslySetInnerHTML={{ __html: t('security_settings.Treat email matching as identical') }}
+                />
               </div>
+              <p className="form-text text-muted">
+                <small dangerouslySetInnerHTML={{ __html: t('security_settings.Treat email matching as identical_warn') }} />
+              </p>
             </div>
 
-            <h3 className="alert-anchor border-bottom">
+            <h3 className="alert-anchor border-bottom mb-4">
               Attribute-based Login Control
             </h3>
 

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

@@ -75,8 +75,8 @@ const SecurityManagementContents = () => {
 
       {/* XSS configuration link */}
       <div className="mb-5">
-        <h2 className="border-bottom">{t('security_settings.xss_prevent_setting')}</h2>
-        <div className="text-center">
+        <h2 className="border-bottom pb-2">{t('security_settings.xss_prevent_setting')}</h2>
+        <div className="mt-4">
           <Link
             href="/admin/markdown/#preventXSS"
             style={{ fontSize: 'large' }}
@@ -87,7 +87,7 @@ const SecurityManagementContents = () => {
       </div>
 
       <div className="auth-mechanism-configurations">
-        <h2 className="border-bottom">{t('security_settings.Authentication mechanism settings')}</h2>
+        <h2 className="border-bottom pb-2">{t('security_settings.Authentication mechanism settings')}</h2>
         <CustomNav
           activeTab={activeTab}
           navTabMapping={navTabMapping}

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

@@ -252,7 +252,7 @@ class SecuritySetting extends React.Component {
     return (
       <div key={`page-delete-permission-dropdown-${deletionType}`} className="row">
 
-        <div className="col-md-3 text-md-end">
+        <div className="col-md-4 text-md-end">
           {!isRecursiveDeletion(deletionType) && isTypeDeletion(deletionType) && (
             <strong>{t('security_settings.page_delete')}</strong>
           )}
@@ -261,7 +261,7 @@ class SecuritySetting extends React.Component {
           )}
         </div>
 
-        <div className="col-md-6">
+        <div className="col-md-8">
           {
             !isRecursiveDeletion(deletionType)
               ? (
@@ -301,7 +301,7 @@ class SecuritySetting extends React.Component {
                   </button>
                   <Collapse isOpen={expantDeleteOptionsState}>
                     <div className="pb-4">
-                      <p className="card custom-card">
+                      <p className="card custom-card bg-warning-sublte">
                         <span className="text-warning">
                           <span className="material-symbols-outlined">info</span>
                           {/* eslint-disable-next-line react/no-danger */}
@@ -412,12 +412,12 @@ class SecuritySetting extends React.Component {
           </table>
         </div>
 
-        <h4>{t('security_settings.page_access_rights')}</h4>
+        <h4 className="mb-3">{t('security_settings.page_access_rights')}</h4>
         <div className="row mb-4">
-          <div className="col-md-3 text-md-end py-2">
+          <div className="col-md-4 text-md-end py-2">
             <strong>{t('security_settings.Guest Users Access')}</strong>
           </div>
-          <div className="col-md-9">
+          <div className="col-md-8">
             <div className="dropdown">
               <button
                 className={`btn btn-outline-secondary dropdown-toggle text-end col-12
@@ -457,7 +457,7 @@ class SecuritySetting extends React.Component {
           </div>
         </div>
 
-        <h4>{t('security_settings.page_delete_rights')}</h4>
+        <h4 className="mb-3">{t('security_settings.page_delete_rights')}</h4>
         {/* Render PageDeletePermission */}
         {
           [
@@ -474,9 +474,9 @@ class SecuritySetting extends React.Component {
           ].map(arr => this.renderPageDeletePermission(arr[0], arr[1], arr[2], arr[3]))
         }
 
-        <h4>{t('security_settings.user_homepage_deletion.user_homepage_deletion')}</h4>
+        <h4 className="mb-3">{t('security_settings.user_homepage_deletion.user_homepage_deletion')}</h4>
         <div className="row mb-4">
-          <div className="col-6 offset-3">
+          <div className="col-md-10 offset-md-2">
             <div className="form-check form-switch form-check-success">
               <input
                 type="checkbox"
@@ -512,9 +512,9 @@ class SecuritySetting extends React.Component {
         <h4>{t('security_settings.session')}</h4>
         <div className="row">
           <label className="text-start text-md-end col-md-3 col-form-label">{t('security_settings.max_age')}</label>
-          <div className="col-md-6">
+          <div className="col-md-8">
             <input
-              className="form-control col-md-3"
+              className="form-control col-md-4"
               type="text"
               defaultValue={adminGeneralSecurityContainer.state.sessionMaxAge || ''}
               onChange={(e) => {
@@ -524,7 +524,7 @@ class SecuritySetting extends React.Component {
             />
             {/* eslint-disable-next-line react/no-danger */}
             <p className="form-text text-muted" dangerouslySetInnerHTML={{ __html: t('security_settings.max_age_desc') }} />
-            <p className="card custom-card">
+            <p className="card custom-card bg-warning-subtle">
               <span className="text-warning">
                 <span className="material-symbols-outlined">info</span> {t('security_settings.max_age_caution')}
               </span>

+ 11 - 12
apps/app/src/components/Admin/Security/ShareLinkSetting.tsx

@@ -102,19 +102,9 @@ const ShareLinkSetting = (props: ShareLinkSettingProps) => {
 
   return (
     <>
-      <div className="mb-3">
-        <button
-          className="pull-right btn btn-danger"
-          disabled={shareLinks.length === 0}
-          type="button"
-          onClick={() => setIsDeleteConfirmModalShown(true)}
-        >
-          {t('security_settings.delete_all_share_links')}
-        </button>
-        <h2 className="alert-anchor border-bottom">{t('security_settings.share_link_management')}</h2>
-      </div>
+      <h2 className="alert-anchor border-bottom mb-4">{t('security_settings.share_link_management')}</h2>
       <h4>{t('security_settings.share_link_rights')}</h4>
-      <div className="row mb-5">
+      <div className="row mt-4 mb-5">
         <div className="col-6 offset-3">
           <div className="form-check form-switch form-check-success">
             <input
@@ -154,6 +144,15 @@ const ShareLinkSetting = (props: ShareLinkSettingProps) => {
         )
       }
 
+      <button
+        className="pull-right btn btn-danger mt-2"
+        disabled={shareLinks.length === 0}
+        type="button"
+        onClick={() => setIsDeleteConfirmModalShown(true)}
+      >
+        {t('security_settings.delete_all_share_links')}
+      </button>
+
       <DeleteAllShareLinksModal
         isOpen={isDeleteConfirmModalShown}
         onClose={() => setIsDeleteConfirmModalShown(false)}

+ 9 - 9
apps/app/src/components/Admin/UserGroup/UserGroupForm.tsx

@@ -74,18 +74,18 @@ export const UserGroupForm: FC<Props> = (props: Props) => {
         )}
         {
           userGroup?.createdAt != null && (
-            <div className="row">
+            <div className="row mb-3">
               <p className="col-md-2 col-form-label">{t('Created')}</p>
-              <p className="col-md-4 my-auto">{dateFnsFormat(new Date(userGroup.createdAt), 'yyyy-MM-dd')}</p>
+              <p className="col-md-6 my-auto">{dateFnsFormat(new Date(userGroup.createdAt), 'yyyy-MM-dd')}</p>
             </div>
           )
         }
 
-        <div className="row">
+        <div className="row mb-3">
           <label htmlFor="name" className="col-md-2 col-form-label">
             {t('user_group_management.group_name')}
           </label>
-          <div className="col-md-4 my-auto">
+          <div className="col-md-6 my-auto">
             <input
               className="form-control"
               type="text"
@@ -99,20 +99,20 @@ export const UserGroupForm: FC<Props> = (props: Props) => {
           </div>
         </div>
 
-        <div className="row">
+        <div className="row mb-3">
           <label htmlFor="description" className="col-md-2 col-form-label">
             {t('Description')}
           </label>
-          <div className="col-md-4">
+          <div className="col-md-6">
             <textarea className="form-control" name="description" value={currentDescription} onChange={onChangeDescriptionHandler} />
           </div>
         </div>
 
-        <div className="row">
+        <div className="row mb-3">
           <label htmlFor="parent" className="col-md-2 col-form-label">
             {t('user_group_management.parent_group')}
           </label>
-          <div className="dropdown col-md-4">
+          <div className="dropdown col-md-6">
             <button
               type="button"
               id="dropdownMenuButton"
@@ -154,7 +154,7 @@ export const UserGroupForm: FC<Props> = (props: Props) => {
           </div>
         </div>
 
-        <div className="row">
+        <div className="row mb-5">
           <div className="offset-md-2 col-md-10">
             <button type="submit" className="btn btn-primary">
               {submitButtonLabel}

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

@@ -138,7 +138,7 @@ export const UserGroupTable: FC<Props> = ({
   }, [userGroupRelations, childUserGroups]);
 
   return (
-    <div data-testid="grw-user-group-table">
+    <div data-testid="grw-user-group-table" className="mb-5">
       <h3>{headerLabel}</h3>
 
       <table className="table table-bordered table-user-list">
@@ -179,7 +179,7 @@ export const UserGroupTable: FC<Props> = ({
                 <td>
                   <ul className="list-inline">
                     {users != null && users.map((user) => {
-                      return <li key={user._id} className="list-inline-item badge rounded-pill bg-warning text-dark">{user.username}</li>;
+                      return <li key={user._id} className="list-inline-item badge text-bg-warning">{user.username}</li>;
                     })}
                   </ul>
                 </td>
@@ -187,7 +187,7 @@ export const UserGroupTable: FC<Props> = ({
                   <ul className="list-inline">
                     {groupIdToChildGroupsMap[group._id] != null && groupIdToChildGroupsMap[group._id].map((group) => {
                       return (
-                        <li key={group._id} className="list-inline-item badge badge-success">
+                        <li key={group._id} className="list-inline-item badge text-bg-success">
                           {isAclEnabled
                             ? (
                               <Link href={`/admin/user-group-detail/${group._id}?isExternalGroup=${isExternalGroup}`}>{group.name}</Link>

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

@@ -17,7 +17,7 @@ export const UserGroupUserTable = (props: Props): JSX.Element => {
   const { t } = useTranslation('admin');
 
   return (
-    <table className="table table-bordered table-user-list">
+    <table className="table table-bordered table-user-list mb-5">
       <thead>
         <tr>
           <th style={{ width: '100px' }}>#</th>

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

@@ -67,7 +67,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
     if (isOpen && bookmarkFolders != null) {
       bookmarkFolders.forEach((bookmarkFolder) => {
         bookmarkFolder.bookmarks.forEach((bookmark) => {
-          if (bookmark.page._id === pageId) {
+          if (bookmark.page?._id === pageId) {
             setSelectedItem(bookmarkFolder._id);
           }
         });

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

@@ -121,9 +121,8 @@ export const BookmarkFolderTree: React.FC<Props> = (props: Props) => {
           );
         })}
         {userBookmarks?.map(userBookmark => (
-          <div key={userBookmark._id} className="grw-foldertree-item-container grw-root-bookmarks">
+          <div key={userBookmark?._id} className="grw-foldertree-item-container grw-root-bookmarks">
             <BookmarkItem
-              key={userBookmark._id}
               isReadOnlyUser={!!isReadOnlyUser}
               isOperable={props.isOperable}
               bookmarkedPage={userBookmark}

+ 24 - 8
apps/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -28,7 +28,7 @@ import { DragAndDropWrapper } from './DragAndDropWrapper';
 type Props = {
   isReadOnlyUser: boolean
   isOperable: boolean,
-  bookmarkedPage: IPageHasId,
+  bookmarkedPage: IPageHasId | null,
   level: number,
   parentFolder: BookmarkFolderItems | null,
   canMoveToRoot: boolean,
@@ -50,17 +50,17 @@ export const BookmarkItem = (props: Props): JSX.Element => {
   const { open: openPutBackPageModal } = usePutBackPageModal();
   const [isRenameInputShown, setRenameInputShown] = useState(false);
 
-  const { data: pageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(bookmarkedPage._id);
+  const { data: pageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(bookmarkedPage?._id);
   const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
-  const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
-  const { latter: pageTitle, former: formerPagePath } = dPagePath;
-  const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
+
   const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level));
   const dragItem: Partial<DragItemDataType> = {
     ...bookmarkedPage, parentFolder,
   };
 
   const onClickMoveToRootHandler = useCallback(async() => {
+    if (bookmarkedPage == null) return;
+
     try {
       await addBookmarkToFolder(bookmarkedPage._id, null);
       bookmarkFolderTreeMutation();
@@ -68,7 +68,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
     catch (err) {
       toastError(err);
     }
-  }, [bookmarkFolderTreeMutation, bookmarkedPage._id]);
+  }, [bookmarkFolderTreeMutation, bookmarkedPage]);
 
   const bookmarkMenuItemClickHandler = useCallback(async(pageId: string, shouldBookmark: boolean) => {
     if (shouldBookmark) {
@@ -90,6 +90,9 @@ export const BookmarkItem = (props: Props): JSX.Element => {
   }, []);
 
   const rename = useCallback(async(inputText: string) => {
+    if (bookmarkedPage == null) return;
+
+
     if (inputText.trim() === '') {
       return cancel();
     }
@@ -111,9 +114,11 @@ export const BookmarkItem = (props: Props): JSX.Element => {
       setRenameInputShown(true);
       toastError(err);
     }
-  }, [bookmarkedPage.path, bookmarkedPage._id, bookmarkedPage.revision, cancel, bookmarkFolderTreeMutation, mutatePageInfo]);
+  }, [bookmarkedPage, cancel, bookmarkFolderTreeMutation, mutatePageInfo]);
 
   const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise<void> => {
+    if (bookmarkedPage == null) return;
+
     if (bookmarkedPage._id == null || bookmarkedPage.path == null) {
       throw Error('_id and path must not be null.');
     }
@@ -128,9 +133,11 @@ export const BookmarkItem = (props: Props): JSX.Element => {
     };
 
     onClickDeleteMenuItemHandler(pageToDelete);
-  }, [bookmarkedPage._id, bookmarkedPage.path, bookmarkedPage.revision, onClickDeleteMenuItemHandler]);
+  }, [bookmarkedPage, onClickDeleteMenuItemHandler]);
 
   const putBackClickHandler = useCallback(() => {
+    if (bookmarkedPage == null) return;
+
     const { _id: pageId, path } = bookmarkedPage;
     const putBackedHandler = async() => {
       try {
@@ -148,6 +155,15 @@ export const BookmarkItem = (props: Props): JSX.Element => {
     openPutBackPageModal({ pageId, path }, { onPutBacked: putBackedHandler });
   }, [bookmarkedPage, openPutBackPageModal, bookmarkFolderTreeMutation, router, mutateCurrentPage, t]);
 
+  if (bookmarkedPage == null) {
+    return <></>;
+  }
+
+  const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
+  const { latter: pageTitle, former: formerPagePath } = dPagePath;
+
+  const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
+
   return (
     <DragAndDropWrapper
       item={dragItem}

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

@@ -98,7 +98,7 @@ export const PagePathNav = (props: Props): JSX.Element => {
         { pageId != null && !isNotFound && (
           <div className="d-flex align-items-center ms-2">
             { isWipPage && (
-              <span className="badge rounded-pill text-bg-secondary ms-1 me-1">WIP</span>
+              <span className="badge text-bg-secondary ms-1 me-1">WIP</span>
             )}
             <CopyDropdown pageId={pageId} pagePath={pagePath} dropdownToggleId={copyDropdownId} dropdownToggleClassName="p-2">
               <span className="material-symbols-outlined">content_paste</span>

+ 8 - 0
apps/app/src/components/Layout/Admin.module.scss

@@ -34,9 +34,17 @@ $slack-work-space-name-card-border: #efc1f6;
   }
 
   .admin-setting-header {
+    margin-bottom: 20px;
     border-bottom: 1px solid transparent;
   }
 
+  .custom-card {
+    padding: 8px 16px;
+    ul {
+      margin-bottom: 0;
+    }
+  }
+
   .admin-security {
     .passport-logo {
       height: 32px;

+ 3 - 3
apps/app/src/components/Layout/AdminLayout.tsx

@@ -31,7 +31,7 @@ const AdminLayout = ({
     <RawLayout>
       <div className={`admin-page ${styles['admin-page']}`}>
 
-        <header className="py-0 container-fluid">
+        <header className="py-0 container">
           <h1 className="p-3 fs-2 d-flex align-items-center">
             <Link href="/" className="d-block mb-1 me-2">
               <GrowiLogo />
@@ -40,12 +40,12 @@ const AdminLayout = ({
           </h1>
         </header>
         <div className="main">
-          <div className="container-fluid">
+          <div className="container">
             <div className="row">
               <div className="col-lg-3">
                 <AdminNavigation />
               </div>
-              <div className="col-lg-9">
+              <div className="col-lg-9 mb-5">
                 {children}
               </div>
             </div>

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

@@ -52,8 +52,8 @@ const ShareLinkTr = (props: ShareLinkTrProps): JSX.Element => {
       <td style={{ verticalAlign: 'middle' }}>
         {shareLink.expiredAt && <span>{dateFnsFormat(new Date(shareLink.expiredAt), 'yyyy-MM-dd HH:mm')}</span>}
       </td>
-      <td style={{ maxWidth: '0', textAlign: 'center' }}>
-        <button className="btn btn-outline-warning" type="button" onClick={onDelete}>
+      <td style={{ maxWidth: '50', textAlign: 'center' }}>
+        <button className="btn btn-outline-danger" type="button" onClick={onDelete}>
           <span className="material-symbols-outlined">delete</span>{t('Delete')}
         </button>
       </td>

+ 8 - 0
apps/app/src/components/ShareLinkPageView.tsx

@@ -17,6 +17,7 @@ import { PagePathNavSticky } from './Common/PagePathNav';
 import { PageViewLayout } from './Common/PageViewLayout';
 import RevisionRenderer from './Page/RevisionRenderer';
 import ShareLinkAlert from './Page/ShareLinkAlert';
+import { PageContentFooter } from './PageContentFooter';
 import type { PageSideContentsProps } from './PageSideContents';
 
 
@@ -72,6 +73,12 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
     : null;
 
 
+  const footerContents = !isNotFound
+    ? (
+      <PageContentFooter page={page} />
+    )
+    : null;
+
   const Contents = () => {
     if (isNotFound || page.revision == null) {
       return <></>;
@@ -101,6 +108,7 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
       headerContents={headerContents}
       sideContents={sideContents}
       expandContentWidth={shouldExpandContent}
+      footerContents={footerContents}
     >
       { specialContents }
       { specialContents == null && (

+ 16 - 14
apps/app/src/components/Sidebar/PageTree/PageTreeSubstance.tsx

@@ -3,9 +3,6 @@ import React, {
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
-import {
-  UncontrolledButtonDropdown, DropdownMenu, DropdownToggle, DropdownItem,
-} from 'reactstrap';
 import { debounce } from 'throttle-debounce';
 
 import { useTargetAndAncestors, useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
@@ -44,28 +41,33 @@ export const PageTreeHeader = memo(({ isWipPageShown, onWipPageShownChange }: He
     <>
       <SidebarHeaderReloadButton onClick={() => mutate()} />
 
-      <UncontrolledButtonDropdown className="me-1">
-        <DropdownToggle color="transparent" className="p-0 border-0">
+      <div className="me-1">
+        <button
+          color="transparent"
+          className="btn p-0 border-0"
+          type="button"
+          data-bs-toggle="dropdown"
+          data-bs-auto-close="outside"
+          aria-expanded="false"
+        >
           <span className="material-symbols-outlined">more_horiz</span>
-        </DropdownToggle>
+        </button>
 
-        <DropdownMenu container="body">
-          <DropdownItem onClick={onWipPageShownChange} className="">
+        <ul className="dropdown-menu">
+          <li className="dropdown-item" onClick={onWipPageShownChange}>
             <div className="form-check form-switch">
               <input
-                id="wipPageVisibility"
                 className="form-check-input"
                 type="checkbox"
                 checked={isWipPageShown}
-                onChange={() => {}}
               />
-              <label className="form-label form-check-label text-muted mb-0" htmlFor="wipPageVisibility">
+              <label className="form-label form-check-label text-muted mb-0">
                 {t('sidebar_header.show_wip_page')}
               </label>
             </div>
-          </DropdownItem>
-        </DropdownMenu>
-      </UncontrolledButtonDropdown>
+          </li>
+        </ul>
+      </div>
     </>
   );
 });

+ 24 - 2
apps/app/src/components/Sidebar/SidebarNav/SidebarNav.tsx

@@ -1,7 +1,9 @@
-import React, { memo } from 'react';
+import React, { memo, useMemo } from 'react';
 
 import type { SidebarContentsType } from '~/interfaces/ui';
+import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 
+import { NotAvailableForReadOnlyUser } from '../../NotAvailableForReadOnlyUser';
 import { PageCreateButton } from '../PageCreateButton';
 
 import { PrimaryItems } from './PrimaryItems';
@@ -16,9 +18,29 @@ export type SidebarNavProps = {
 export const SidebarNav = memo((props: SidebarNavProps) => {
   const { onPrimaryItemHover } = props;
 
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isReadOnlyUser } = useIsReadOnlyUser();
+
+  const renderedPageCreateButton = useMemo(() => {
+    if (isGuestUser) {
+      return <></>;
+    }
+
+    if (isReadOnlyUser) {
+      return (
+        <NotAvailableForReadOnlyUser>
+          <PageCreateButton />
+        </NotAvailableForReadOnlyUser>
+      );
+    }
+
+    return <PageCreateButton />;
+  }, [isGuestUser, isReadOnlyUser]);
+
   return (
     <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`}>
-      <PageCreateButton />
+
+      {renderedPageCreateButton}
 
       <div className="grw-sidebar-nav-primary-container" data-vrt-blackout-sidebar-nav>
         <PrimaryItems onItemHover={onPrimaryItemHover} />

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

@@ -147,7 +147,7 @@ export const ExternalGroupManagement: FC = () => {
 
   return (
     <>
-      <h2 className="border-bottom">{t('external_user_group.management')}</h2>
+      <h2 className="border-bottom mb-4">{t('external_user_group.management')}</h2>
       <UserGroupTable
         headerLabel={t('admin:user_group_management.group_list')}
         userGroups={externalUserGroups}

+ 22 - 23
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/KeycloakGroupSyncSettingsForm.tsx

@@ -1,13 +1,12 @@
-import {
-  FC, useCallback, useEffect, useState,
-} from 'react';
+import type { FC } from 'react';
+import { useCallback, useEffect, useState } from 'react';
 
 import { useTranslation } from 'react-i18next';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { useSWRxKeycloakGroupSyncSettings } from '~/features/external-user-group/client/stores/external-user-group';
-import { KeycloakGroupSyncSettings } from '~/features/external-user-group/interfaces/external-user-group';
+import type { KeycloakGroupSyncSettings } from '~/features/external-user-group/interfaces/external-user-group';
 
 export const KeycloakGroupSyncSettingsForm: FC = () => {
   const { t } = useTranslation('admin');
@@ -49,11 +48,11 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
         <div className="row form-group">
           <label
             htmlFor="keycloakHost"
-            className="text-left text-md-right col-md-3 col-form-label"
+            className="text-left text-md-end col-md-3 col-form-label"
           >
             {t('external_user_group.keycloak.host')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               type="text"
@@ -68,10 +67,10 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="keycloakGroupRealm" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="keycloakGroupRealm" className="text-left text-md-end col-md-3 col-form-label">
             {t('external_user_group.keycloak.group_realm')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               required
@@ -89,10 +88,10 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="keycloakGroupSyncClientRealm" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="keycloakGroupSyncClientRealm" className="text-left text-md-end col-md-3 col-form-label">
             {t('external_user_group.keycloak.group_sync_client_realm')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               required
@@ -110,10 +109,10 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="keycloakGroupSyncClientID" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="keycloakGroupSyncClientID" className="text-left text-md-end col-md-3 col-form-label">
             {t('external_user_group.keycloak.group_sync_client_id')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               required
@@ -131,10 +130,10 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="keycloakGroupSyncClientSecret" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="keycloakGroupSyncClientSecret" className="text-left text-md-end col-md-3 col-form-label">
             {t('external_user_group.keycloak.group_sync_client_secret')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               required
@@ -153,15 +152,15 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
         </div>
         <div className="row form-group">
           <label
-            className="text-left text-md-right col-md-3 col-form-label"
+            className="text-left text-md-end col-md-3 col-form-label"
           >
             {/* {t('external_user_group.auto_generate_user_on_sync')} */}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <div className="custom-control custom-checkbox custom-checkbox-info">
               <input
                 type="checkbox"
-                className="custom-control-input"
+                className="custom-control-input me-2"
                 name="autoGenerateUserOnKeycloakGroupSync"
                 id="autoGenerateUserOnKeycloakGroupSync"
                 checked={formValues.autoGenerateUserOnKeycloakGroupSync}
@@ -178,15 +177,15 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
         </div>
         <div className="row form-group">
           <label
-            className="text-left text-md-right col-md-3 col-form-label"
+            className="text-left text-md-end col-md-3 col-form-label"
           >
             {/* {t('external_user_group.keycloak.preserve_deleted_keycloak_groups')} */}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <div className="custom-control custom-checkbox custom-checkbox-info">
               <input
                 type="checkbox"
-                className="custom-control-input"
+                className="custom-control-input me-2"
                 name="preserveDeletedKeycloakGroups"
                 id="preserveDeletedKeycloakGroups"
                 checked={formValues.preserveDeletedKeycloakGroups}
@@ -201,14 +200,14 @@ export const KeycloakGroupSyncSettingsForm: FC = () => {
             </div>
           </div>
         </div>
-        <div className="px-5">
+        <div className="mt-5 mb-4">
           <h4 className="border-bottom mb-3">Attribute Mapping ({t('optional')})</h4>
         </div>
         <div className="row form-group">
-          <label htmlFor="keycloakGroupDescriptionAttribute" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="keycloakGroupDescriptionAttribute" className="text-left text-md-end col-md-3 col-form-label">
             {t('Description')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               type="text"

+ 23 - 23
apps/app/src/features/external-user-group/client/components/ExternalUserGroup/LdapGroupSyncSettingsForm.tsx

@@ -1,13 +1,13 @@
-import {
-  FC, useCallback, useEffect, useState,
-} from 'react';
+import type { FC } from 'react';
+import { useCallback, useEffect, useState } from 'react';
 
 import { useTranslation } from 'react-i18next';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { useSWRxLdapGroupSyncSettings } from '~/features/external-user-group/client/stores/external-user-group';
-import { LdapGroupMembershipAttributeType, LdapGroupSyncSettings } from '~/features/external-user-group/interfaces/external-user-group';
+import type { LdapGroupSyncSettings } from '~/features/external-user-group/interfaces/external-user-group';
+import { LdapGroupMembershipAttributeType } from '~/features/external-user-group/interfaces/external-user-group';
 
 export const LdapGroupSyncSettingsForm: FC = () => {
   const { t } = useTranslation('admin');
@@ -49,11 +49,11 @@ export const LdapGroupSyncSettingsForm: FC = () => {
         <div className="row form-group">
           <label
             htmlFor="ldapGroupSearchBase"
-            className="text-left text-md-right col-md-3 col-form-label"
+            className="text-left text-md-end col-md-3 col-form-label"
           >
             {t('external_user_group.ldap.group_search_base_DN')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               type="text"
@@ -68,10 +68,10 @@ export const LdapGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="ldapGroupMembershipAttribute" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="ldapGroupMembershipAttribute" className="text-left text-md-end col-md-3 col-form-label">
             {t('external_user_group.ldap.membership_attribute')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               required
@@ -90,10 +90,10 @@ export const LdapGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="ldapGroupMembershipAttributeType" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="ldapGroupMembershipAttributeType" className="text-left text-md-end col-md-3 col-form-label">
             {t('external_user_group.ldap.membership_attribute_type')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <select
               className="form-control"
               required
@@ -117,10 +117,10 @@ export const LdapGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="ldapGroupChildGroupAttribute" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="ldapGroupChildGroupAttribute" className="text-left text-md-end col-md-3 col-form-label">
             {t('external_user_group.ldap.child_group_attribute')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               required
@@ -140,15 +140,15 @@ export const LdapGroupSyncSettingsForm: FC = () => {
         </div>
         <div className="row form-group">
           <label
-            className="text-left text-md-right col-md-3 col-form-label"
+            className="text-left text-md-end col-md-3 col-form-label"
           >
             {/* {t('external_user_group.auto_generate_user_on_sync')} */}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <div className="custom-control custom-checkbox custom-checkbox-info">
               <input
                 type="checkbox"
-                className="custom-control-input"
+                className="custom-control-input me-2"
                 name="autoGenerateUserOnLdapGroupSync"
                 id="autoGenerateUserOnLdapGroupSync"
                 checked={formValues.autoGenerateUserOnLdapGroupSync}
@@ -165,15 +165,15 @@ export const LdapGroupSyncSettingsForm: FC = () => {
         </div>
         <div className="row form-group">
           <label
-            className="text-left text-md-right col-md-3 col-form-label"
+            className="text-left text-md-end col-md-3 col-form-label"
           >
             {/* {t('external_user_group.ldap.preserve_deleted_ldap_groups')} */}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <div className="custom-control custom-checkbox custom-checkbox-info">
               <input
                 type="checkbox"
-                className="custom-control-input"
+                className="custom-control-input me-2"
                 name="preserveDeletedLdapGroups"
                 id="preserveDeletedLdapGroups"
                 checked={formValues.preserveDeletedLdapGroups}
@@ -188,12 +188,12 @@ export const LdapGroupSyncSettingsForm: FC = () => {
             </div>
           </div>
         </div>
-        <div className="px-5">
+        <div className="mt-5 mb-4">
           <h4 className="border-bottom mb-3">Attribute Mapping ({t('optional')})</h4>
         </div>
         <div className="row form-group">
-          <label htmlFor="ldapGroupNameAttribute" className="text-left text-md-right col-md-3 col-form-label">{t('Name')}</label>
-          <div className="col-md-6">
+          <label htmlFor="ldapGroupNameAttribute" className="text-left text-md-end col-md-3 col-form-label">{t('Name')}</label>
+          <div className="col-md-9">
             <input
               className="form-control"
               type="text"
@@ -211,10 +211,10 @@ export const LdapGroupSyncSettingsForm: FC = () => {
           </div>
         </div>
         <div className="row form-group">
-          <label htmlFor="ldapGroupDescriptionAttribute" className="text-left text-md-right col-md-3 col-form-label">
+          <label htmlFor="ldapGroupDescriptionAttribute" className="text-left text-md-end col-md-3 col-form-label">
             {t('Description')}
           </label>
-          <div className="col-md-6">
+          <div className="col-md-9">
             <input
               className="form-control"
               type="text"

+ 7 - 7
apps/app/src/features/growi-plugin/server/services/growi-plugin/growi-plugin.integ.ts

@@ -59,27 +59,27 @@ describe('Installing a GROWI theme plugin', () => {
   it('install() should success', async() => {
     // when
     const result = await growiPluginService.install({
-      url: 'https://github.com/weseek/growi-plugin-theme-welcome-to-fumiya-room',
+      url: 'https://github.com/weseek/growi-plugin-theme-vivid-internet',
     });
-    const count = await GrowiPlugin.count({ 'meta.name': 'growi-plugin-theme-welcome-to-fumiya-room' });
+    const count = await GrowiPlugin.count({ 'meta.name': 'growi-plugin-theme-vivid-internet' });
 
     // expect
-    expect(result).toEqual('growi-plugin-theme-welcome-to-fumiya-room');
+    expect(result).toEqual('growi-plugin-theme-vivid-internet');
     expect(count).toBe(1);
     expect(fs.existsSync(path.join(
       PLUGIN_STORING_PATH,
       'weseek',
-      'growi-plugin-theme-welcome-to-fumiya-room',
+      'growi-plugin-theme-vivid-internet',
     ))).toBeTruthy();
   });
 
   it('findThemePlugin() should return data with metadata and manifest', async() => {
     // confirm
-    const count = await GrowiPlugin.count({ 'meta.name': 'growi-plugin-theme-welcome-to-fumiya-room' });
+    const count = await GrowiPlugin.count({ 'meta.name': 'growi-plugin-theme-vivid-internet' });
     expect(count).toBe(1);
 
     // when
-    const results = await growiPluginService.findThemePlugin('welcome-to-fumiya-room');
+    const results = await growiPluginService.findThemePlugin('vivid-internet');
 
     // expect
     expect(results).not.toBeNull();
@@ -88,7 +88,7 @@ describe('Installing a GROWI theme plugin', () => {
     expect(results.themeMetadata).not.toBeNull();
     expect(results.themeHref).not.toBeNull();
     expect(results.themeHref
-      .startsWith('/static/plugins/weseek/growi-plugin-theme-welcome-to-fumiya-room/dist/assets/style.')).toBeTruthy();
+      .startsWith('/static/plugins/weseek/growi-plugin-theme-vivid-internet/dist/assets/style.')).toBeTruthy();
   });
 
 });

+ 3 - 2
apps/app/src/features/questionnaire/client/stores/questionnaire.tsx

@@ -1,8 +1,9 @@
-import useSWR, { SWRResponse } from 'swr';
+import type { SWRResponse } from 'swr';
+import useSWR from 'swr';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 
-import { IQuestionnaireOrderHasId } from '../../interfaces/questionnaire-order';
+import type { IQuestionnaireOrderHasId } from '../../interfaces/questionnaire-order';
 
 export const useSWRxQuestionnaireOrders = (): SWRResponse<IQuestionnaireOrderHasId[], Error> => {
   return useSWR(

+ 1 - 1
apps/app/src/interfaces/bookmark-info.ts

@@ -9,7 +9,7 @@ export interface IBookmarkInfo {
 
 export interface BookmarkedPage {
   _id: string,
-  page: IPageHasId,
+  page: IPageHasId | null,
   user: Ref<IUser>,
   createdAt: Date,
 }

+ 6 - 0
apps/app/src/server/service/config-loader.ts

@@ -471,6 +471,12 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    ValueType.NUMBER,
     default: 120,
   },
+  S3_OBJECT_ACL: {
+    ns:      'crowi',
+    key:     'aws:s3ObjectCannedACL',
+    type:    ValueType.STRING,
+    default: 'public-read',
+  },
   GCS_API_KEY_JSON_PATH: {
     ns:      'crowi',
     key:     'gcs:apiKeyJsonPath',

+ 28 - 3
apps/app/src/server/service/file-uploader/aws.ts

@@ -48,6 +48,30 @@ const isFileExists = async(s3: S3Client, params: HeadObjectCommandInput) => {
   return true;
 };
 
+const ObjectCannedACLs = [
+  ObjectCannedACL.authenticated_read,
+  ObjectCannedACL.aws_exec_read,
+  ObjectCannedACL.bucket_owner_full_control,
+  ObjectCannedACL.bucket_owner_read,
+  ObjectCannedACL.private,
+  ObjectCannedACL.public_read,
+  ObjectCannedACL.public_read_write,
+];
+const isValidObjectCannedACL = (acl: string | null): acl is ObjectCannedACL => {
+  return ObjectCannedACLs.includes(acl as ObjectCannedACL);
+};
+/**
+ * @see: https://dev.growi.org/5d091f611fe336003eec5bfdz
+ * @returns ObjectCannedACL
+ */
+const getS3PutObjectCannedAcl = (): ObjectCannedACL | undefined => {
+  const s3ObjectCannedACL = configManager.getConfig('crowi', 'aws:s3ObjectCannedACL');
+  if (isValidObjectCannedACL(s3ObjectCannedACL)) {
+    return s3ObjectCannedACL;
+  }
+  return undefined;
+};
+
 const getS3Bucket = (): string | undefined => {
   return configManager.getConfig('crowi', 'aws:s3Bucket') ?? undefined; // return undefined when getConfig() returns null
 };
@@ -212,7 +236,8 @@ module.exports = (crowi) => {
         configManager.getConfig('crowi', 'aws:s3Region') != null
           || configManager.getConfig('crowi', 'aws:s3CustomEndpoint') != null
       )
-      && configManager.getConfig('crowi', 'aws:s3Bucket') != null;
+      && configManager.getConfig('crowi', 'aws:s3Bucket') != null
+      && configManager.getConfig('crowi', 'aws:s3BucketAclsDisable') != null;
   };
 
   (lib as any).deleteFile = async function(attachment) {
@@ -274,7 +299,7 @@ module.exports = (crowi) => {
       Bucket: getS3Bucket(),
       Key: filePath,
       Body: fileStream,
-      ACL: ObjectCannedACL.public_read,
+      ACL: getS3PutObjectCannedAcl(),
       // put type and the file name for reference information when uploading
       ContentType: contentHeaders.contentType?.value.toString(),
       ContentDisposition: contentHeaders.contentDisposition?.value.toString(),
@@ -289,7 +314,7 @@ module.exports = (crowi) => {
       ContentType: contentType,
       Key: filePath,
       Body: data,
-      ACL: ObjectCannedACL.public_read,
+      ACL: getS3PutObjectCannedAcl(),
     }));
   };
 

+ 3 - 3
apps/app/src/server/service/page/index.ts

@@ -1869,8 +1869,7 @@ class PageService implements IPageService {
   }
 
   async deleteCompletelyOperation(pageIds, pagePaths): Promise<void> {
-    // Delete Bookmarks, Attachments, Revisions, Pages and emit delete
-    const Bookmark = this.crowi.model('Bookmark');
+    // Delete Attachments, Revisions, Pages and emit delete
     const Page = this.crowi.model('Page');
     const Revision = this.crowi.model('Revision');
 
@@ -1878,7 +1877,6 @@ class PageService implements IPageService {
     const attachments = await Attachment.find({ page: { $in: pageIds } });
 
     await Promise.all([
-      Bookmark.deleteMany({ page: { $in: pageIds } }),
       Comment.deleteMany({ page: { $in: pageIds } }),
       PageTagRelation.deleteMany({ relatedPage: { $in: pageIds } }),
       ShareLink.deleteMany({ relatedPage: { $in: pageIds } }),
@@ -1886,6 +1884,8 @@ class PageService implements IPageService {
       Page.deleteMany({ _id: { $in: pageIds } }),
       PageRedirect.deleteMany({ $or: [{ fromPath: { $in: pagePaths } }, { toPath: { $in: pagePaths } }] }),
       attachmentService.removeAllAttachments(attachments),
+
+      // Leave bookmarks without deleting -- 2024.05.17 Yuki Takei
     ]);
   }
 

+ 7 - 14
apps/app/src/stores/bookmark.ts

@@ -1,11 +1,12 @@
 import type { IUser, IPageHasId } from '@growi/core';
-import useSWR, { SWRResponse } from 'swr';
+import type { SWRResponse } from 'swr';
+import useSWR from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRMutation, { type SWRMutationResponse } from 'swr/mutation';
 
 
 import { apiv3Get } from '../client/util/apiv3-client';
-import { IBookmarkInfo } from '../interfaces/bookmark-info';
+import type { IBookmarkInfo } from '../interfaces/bookmark-info';
 
 import { useCurrentUser } from './context';
 
@@ -16,32 +17,24 @@ export const useSWRxBookmarkedUsers = (pageId: string | null): SWRResponse<IUser
   );
 };
 
-export const useSWRxUserBookmarks = (userId: string | null): SWRResponse<IPageHasId[], Error> => {
+export const useSWRxUserBookmarks = (userId: string | null): SWRResponse<(IPageHasId | null)[], Error> => {
   return useSWRImmutable(
     userId != null ? `/bookmarks/${userId}` : null,
     endpoint => apiv3Get(endpoint).then((response) => {
       const { userRootBookmarks } = response.data;
-      return userRootBookmarks.map((item) => {
-        return {
-          ...item.page,
-        };
-      });
+      return userRootBookmarks.map(item => item.page); // page will be null if the page is deleted
     }),
   );
 };
 
-export const useSWRMUTxCurrentUserBookmarks = (): SWRMutationResponse<IPageHasId[], Error> => {
+export const useSWRMUTxCurrentUserBookmarks = (): SWRMutationResponse<(IPageHasId | null)[], Error> => {
   const { data: currentUser } = useCurrentUser();
 
   return useSWRMutation(
     currentUser != null ? `/bookmarks/${currentUser?._id}` : null,
     endpoint => apiv3Get(endpoint).then((response) => {
       const { userRootBookmarks } = response.data;
-      return userRootBookmarks.map((item) => {
-        return {
-          ...item.page,
-        };
-      });
+      return userRootBookmarks.map(item => item.page); // page will be null if the page is deleted
     }),
   );
 };

+ 3 - 1
apps/app/test/integration/service/page.test.js

@@ -870,7 +870,9 @@ describe('PageService', () => {
       test('deleteCompletelyOperation', async() => {
         await crowi.pageService.deleteCompletelyOperation([parentForDeleteCompletely._id], [parentForDeleteCompletely.path], { });
 
-        expect(deleteManyBookmarkSpy).toHaveBeenCalledWith({ page: { $in: [parentForDeleteCompletely._id] } });
+        // bookmarks should not be deleted
+        expect(deleteManyBookmarkSpy).not.toHaveBeenCalled();
+
         expect(deleteManyCommentSpy).toHaveBeenCalledWith({ page: { $in: [parentForDeleteCompletely._id] } });
         expect(deleteManyPageTagRelationSpy).toHaveBeenCalledWith({ relatedPage: { $in: [parentForDeleteCompletely._id] } });
         expect(deleteManyShareLinkSpy).toHaveBeenCalledWith({ relatedPage: { $in: [parentForDeleteCompletely._id] } });

+ 2 - 2
apps/app/test/integration/service/v5.public-page.test.ts

@@ -2103,7 +2103,7 @@ describe('PageService page operations with only public pages', () => {
       const deletedRevisions = await Revision.find({ pageId: { $in: [parentPage._id, grandchildPage._id] } });
       const tags = await Tag.find({ _id: { $in: [tag1?._id, tag2?._id] } });
       const deletedPageTagRelations = await PageTagRelation.find({ _id: { $in: [pageTagRelation1?._id, pageTagRelation2?._id] } });
-      const deletedBookmarks = await Bookmark.find({ _id: bookmark._id });
+      const remainingBookmarks = await Bookmark.find({ _id: bookmark._id });
       const deletedComments = await Comment.find({ _id: comment._id });
       const deletedPageRedirects = await PageRedirect.find({ _id: { $in: [pageRedirect1._id, pageRedirect2._id] } });
       const deletedShareLinks = await ShareLink.find({ _id: { $in: [shareLink1._id, shareLink2._id] } });
@@ -2117,7 +2117,7 @@ describe('PageService page operations with only public pages', () => {
       // PageTagRelation should be null
       expect(deletedPageTagRelations.length).toBe(0);
       // bookmark should be null
-      expect(deletedBookmarks.length).toBe(0);
+      expect(remainingBookmarks.length).toBe(1);
       // comment should be null
       expect(deletedComments.length).toBe(0);
       // pageRedirect should be null

+ 1 - 1
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "7.0.4-slackbot-proxy.0",
+  "version": "7.0.5-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/editor/package.json

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

+ 1 - 1
packages/presentation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/presentation",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "GROWI plugin for presentation",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/preset-templates/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/preset-templates",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "scripts": {
     "test": "vitest run",
     "version": "yarn version --no-git-tag-version --preid=RC"

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "license": "MIT",
   "main": "dist/libs/preset-themes.umd.js",
   "module": "dist/libs/preset-themes.mjs",

+ 1 - 1
packages/remark-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-attachment-refs",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-drawio/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-drawio",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "remark plugin to draw diagrams with draw.io (diagrams.net)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-growi-directive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-growi-directive",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "remark plugin to support GROWI plugin (forked from remark-directive@2.0.1)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-lsx",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "license": "MIT",
   "type": "module",
   "main": "dist/index.cjs",

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "7.0.4",
+  "version": "7.0.5-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [

+ 11 - 11
yarn.lock

@@ -1854,7 +1854,7 @@
     xdg-basedir "^4.0.0"
 
 "@growi/core@link:packages/core":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     bson-objectid "^2.0.4"
     escape-string-regexp "^4.0.0"
@@ -1863,7 +1863,7 @@
   version "7.0.0-RC.0"
 
 "@growi/editor@link:packages/editor":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     markdown-table "^3.0.3"
     react "^18.2.0"
@@ -1876,18 +1876,18 @@
     extensible-custom-error "^0.0.7"
 
 "@growi/presentation@link:packages/presentation":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
 
 "@growi/preset-templates@link:packages/preset-templates":
-  version "7.0.4"
+  version "7.0.5-RC.0"
 
 "@growi/preset-themes@link:packages/preset-themes":
-  version "7.0.4"
+  version "7.0.5-RC.0"
 
 "@growi/remark-attachment-refs@link:packages/remark-attachment-refs":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -1900,10 +1900,10 @@
     universal-bunyan "^0.9.2"
 
 "@growi/remark-drawio@link:packages/remark-drawio":
-  version "7.0.4"
+  version "7.0.5-RC.0"
 
 "@growi/remark-growi-directive@link:packages/remark-growi-directive":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     "@types/mdast" "^3.0.0"
     "@types/unist" "^2.0.0"
@@ -1920,7 +1920,7 @@
     uvu "^0.5.0"
 
 "@growi/remark-lsx@link:packages/remark-lsx":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -1932,7 +1932,7 @@
     swr "^2.2.2"
 
 "@growi/slack@link:packages/slack":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     "@slack/oauth" "^2.0.1"
     "@slack/web-api" "^6.2.4"
@@ -1951,7 +1951,7 @@
     url-join "^4.0.0"
 
 "@growi/ui@link:packages/ui":
-  version "7.0.4"
+  version "7.0.5-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"