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

Merge branch 'master' into feat/163220-171243-enable-page-bulk-export-for-growi-cloud

Futa Arai 4 месяцев назад
Родитель
Сommit
1459790583
28 измененных файлов с 400 добавлено и 518 удалено
  1. 22 1
      CHANGELOG.md
  2. 2 2
      apps/app/package.json
  3. 3 2
      apps/app/src/client/components/Admin/AdminHome/AdminHome.jsx
  4. 9 2
      apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx
  5. 3 1
      apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx
  6. 5 3
      apps/app/src/client/components/Admin/Security/GitHubSecuritySettingContents.jsx
  7. 5 3
      apps/app/src/client/components/Admin/Security/GoogleSecuritySettingContents.jsx
  8. 14 11
      apps/app/src/client/components/Admin/Security/LdapSecuritySettingContents.tsx
  9. 6 2
      apps/app/src/client/components/Admin/Security/LocalSecuritySettingContents.tsx
  10. 20 17
      apps/app/src/client/components/Admin/Security/OidcSecuritySettingContents.tsx
  11. 13 11
      apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.tsx
  12. 15 4
      apps/app/src/client/components/Admin/Security/SecuritySetting/index.tsx
  13. 16 3
      apps/app/src/client/services/AdminGeneralSecurityContainer.js
  14. 10 18
      apps/app/src/client/services/AdminGitHubSecurityContainer.js
  15. 9 19
      apps/app/src/client/services/AdminGoogleSecurityContainer.js
  16. 27 89
      apps/app/src/client/services/AdminLdapSecurityContainer.js
  17. 12 14
      apps/app/src/client/services/AdminLocalSecurityContainer.js
  18. 39 153
      apps/app/src/client/services/AdminOidcSecurityContainer.js
  19. 15 66
      apps/app/src/client/services/AdminSamlSecurityContainer.js
  20. 1 0
      apps/pdf-converter/.env
  21. 2 2
      apps/pdf-converter/docker/README.md
  22. 1 1
      apps/pdf-converter/package.json
  23. 48 11
      apps/pdf-converter/src/service/pdf-convert.ts
  24. 1 1
      apps/slackbot-proxy/package.json
  25. 1 1
      package.json
  26. 22 11
      packages/editor/index.html
  27. 9 0
      packages/editor/src/main.scss
  28. 70 70
      pnpm-lock.yaml

+ 22 - 1
CHANGELOG.md

@@ -1,9 +1,30 @@
 # Changelog
 
-## [Unreleased](https://github.com/growilabs/compare/v7.3.5...HEAD)
+## [Unreleased](https://github.com/growilabs/compare/v7.3.7...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v7.3.7](https://github.com/growilabs/compare/v7.3.6...v7.3.7) - 2025-11-25
+
+### 💎 Features
+
+* feat(pdf-converter): Enable puppeteer-cluster config of pdf-converter from env var (#10516) @arafubeatbox
+
+### 🐛 Bug Fixes
+
+* fix: Admin form degradation (#10540) @yuki-takei
+
+## [v7.3.6](https://github.com/growilabs/compare/v7.3.5...v7.3.6) - 2025-11-18
+
+### 🐛 Bug Fixes
+
+* fix: Printing styles (#10505) @yuki-takei
+
+### 🧰 Maintenance
+
+* ci(deps): bump js-yaml from 4.1.0 to 4.1.1 (#10511) @[dependabot[bot]](https://github.com/apps/dependabot)
+* support: Configure biome for app routes excluding apiv3 (#10496) @arafubeatbox
+
 ## [v7.3.5](https://github.com/growilabs/compare/v7.3.4...v7.3.5) - 2025-11-10
 
 ### 💎 Features

+ 2 - 2
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.3.6-RC.0",
+  "version": "7.3.8-RC.0",
   "license": "MIT",
   "private": "true",
   "scripts": {
@@ -148,7 +148,7 @@
     "is-absolute-url": "^4.0.1",
     "is-iso-date": "^0.0.1",
     "js-tiktoken": "^1.0.15",
-    "js-yaml": "^4.1.0",
+    "js-yaml": "^4.1.1",
     "jsonrepair": "^3.12.0",
     "katex": "^0.16.21",
     "ldapjs": "^3.0.2",

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

@@ -17,6 +17,7 @@ import { withUnstatedContainers } from '../../UnstatedUtils';
 import { EnvVarsTable } from './EnvVarsTable';
 import SystemInfomationTable from './SystemInfomationTable';
 
+
 const logger = loggerFactory('growi:admin');
 
 const AdminHome = (props) => {
@@ -59,7 +60,7 @@ const AdminHome = (props) => {
         )
       }
       {
-      // Alert message will be displayed in case that V5 migration has not been compleated
+        // Alert message will be displayed in case that V5 migration has not been compleated
         (migrationStatus != null && !migrationStatus.isV5Compatible)
         && (
           <div className={`alert ${migrationStatus.isV5Compatible == null ? 'alert-warning' : 'alert-info'}`}>
@@ -90,7 +91,7 @@ const AdminHome = (props) => {
           <p>{t('admin:admin_top.env_var_priority')}</p>
           {/* eslint-disable-next-line react/no-danger */}
           <p dangerouslySetInnerHTML={{ __html: t('admin:admin_top.about_security') }} />
-          {adminHomeContainer.state.envVars && <EnvVarsTable envVars={adminHomeContainer.state.envVars} />}
+          <EnvVarsTable envVars={adminHomeContainer.state.envVars} />
         </div>
       </div>
 

+ 9 - 2
apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx

@@ -1,13 +1,20 @@
 import React, { type JSX } from 'react';
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
+
 type EnvVarsTableProps = {
-  envVars: Record<string, string | number | boolean>,
+  envVars?: Record<string, string | number | boolean>,
 }
 
 export const EnvVarsTable: React.FC<EnvVarsTableProps> = (props: EnvVarsTableProps) => {
+  const { envVars } = props;
+  if (envVars == null) {
+    return <LoadingSpinner />;
+  }
+
   const envVarRows: JSX.Element[] = [];
 
-  for (const [key, value] of Object.entries(props.envVars)) {
+  for (const [key, value] of Object.entries(envVars ?? {})) {
     if (value != null) {
       envVarRows.push(
         <tr key={key}>

+ 3 - 1
apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx

@@ -1,5 +1,7 @@
 import React from 'react';
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
+
 import AdminHomeContainer from '~/client/services/AdminHomeContainer';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
@@ -17,7 +19,7 @@ const SystemInformationTable = (props: Props) => {
   } = adminHomeContainer.state;
 
   if (growiVersion == null || nodeVersion == null || npmVersion == null || pnpmVersion == null) {
-    return <></>;
+    return <LoadingSpinner />;
   }
 
   return (

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

@@ -36,9 +36,11 @@ const GitHubSecurityManagementContents = (props) => {
 
   const onClickSubmit = useCallback(async(data) => {
     try {
-      await adminGitHubSecurityContainer.changeGitHubClientId(data.githubClientId ?? '');
-      await adminGitHubSecurityContainer.changeGitHubClientSecret(data.githubClientSecret ?? '');
-      await adminGitHubSecurityContainer.updateGitHubSetting();
+      await adminGitHubSecurityContainer.updateGitHubSetting({
+        githubClientId: data.githubClientId ?? '',
+        githubClientSecret: data.githubClientSecret ?? '',
+        isSameUsernameTreatedAsIdenticalUser: adminGitHubSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser,
+      });
       await adminGeneralSecurityContainer.retrieveSetupStratedies();
       toastSuccess(t('security_settings.OAuth.GitHub.updated_github'));
     }

+ 5 - 3
apps/app/src/client/components/Admin/Security/GoogleSecuritySettingContents.jsx

@@ -34,9 +34,11 @@ const GoogleSecurityManagementContents = (props) => {
 
   const onClickSubmit = useCallback(async(data) => {
     try {
-      await adminGoogleSecurityContainer.changeGoogleClientId(data.googleClientId ?? '');
-      await adminGoogleSecurityContainer.changeGoogleClientSecret(data.googleClientSecret ?? '');
-      await adminGoogleSecurityContainer.updateGoogleSetting();
+      await adminGoogleSecurityContainer.updateGoogleSetting({
+        googleClientId: data.googleClientId ?? '',
+        googleClientSecret: data.googleClientSecret ?? '',
+        isSameEmailTreatedAsIdenticalUser: adminGoogleSecurityContainer.state.isSameEmailTreatedAsIdenticalUser,
+      });
       await adminGeneralSecurityContainer.retrieveSetupStratedies();
       toastSuccess(t('security_settings.OAuth.Google.updated_google'));
     }

+ 14 - 11
apps/app/src/client/components/Admin/Security/LdapSecuritySettingContents.tsx

@@ -56,17 +56,20 @@ const LdapSecuritySettingContents = (props: Props) => {
 
   const onSubmit = useCallback(async(data) => {
     try {
-      await adminLdapSecurityContainer.changeServerUrl(data.serverUrl);
-      await adminLdapSecurityContainer.changeBindDN(data.ldapBindDN);
-      await adminLdapSecurityContainer.changeBindDNPassword(data.ldapBindDNPassword);
-      await adminLdapSecurityContainer.changeSearchFilter(data.ldapSearchFilter);
-      await adminLdapSecurityContainer.changeAttrMapUsername(data.ldapAttrMapUsername);
-      await adminLdapSecurityContainer.changeAttrMapMail(data.ldapAttrMapMail);
-      await adminLdapSecurityContainer.changeAttrMapName(data.ldapAttrMapName);
-      await adminLdapSecurityContainer.changeGroupSearchBase(data.ldapGroupSearchBase);
-      await adminLdapSecurityContainer.changeGroupSearchFilter(data.ldapGroupSearchFilter);
-      await adminLdapSecurityContainer.changeGroupDnProperty(data.ldapGroupDnProperty);
-      await adminLdapSecurityContainer.updateLdapSetting();
+      await adminLdapSecurityContainer.updateLdapSetting({
+        serverUrl: data.serverUrl,
+        isUserBind: adminLdapSecurityContainer.state.isUserBind,
+        ldapBindDN: data.ldapBindDN,
+        ldapBindDNPassword: data.ldapBindDNPassword,
+        ldapSearchFilter: data.ldapSearchFilter,
+        ldapAttrMapUsername: data.ldapAttrMapUsername,
+        isSameUsernameTreatedAsIdenticalUser: adminLdapSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser,
+        ldapAttrMapMail: data.ldapAttrMapMail,
+        ldapAttrMapName: data.ldapAttrMapName,
+        ldapGroupSearchBase: data.ldapGroupSearchBase,
+        ldapGroupSearchFilter: data.ldapGroupSearchFilter,
+        ldapGroupDnProperty: data.ldapGroupDnProperty,
+      });
       await adminGeneralSecurityContainer.retrieveSetupStratedies();
       toastSuccess(t('security_settings.ldap.updated_ldap'));
     }

+ 6 - 2
apps/app/src/client/components/Admin/Security/LocalSecuritySettingContents.tsx

@@ -38,8 +38,12 @@ const LocalSecuritySettingContents = (props: Props): JSX.Element => {
 
   const onSubmit = useCallback(async(data) => {
     try {
-      await adminLocalSecurityContainer.changeRegistrationWhitelist(data.registrationWhitelist);
-      await adminLocalSecurityContainer.updateLocalSecuritySetting();
+      await adminLocalSecurityContainer.updateLocalSecuritySetting({
+        registrationMode: adminLocalSecurityContainer.state.registrationMode,
+        registrationWhitelist: data.registrationWhitelist.split('\n'),
+        isPasswordResetEnabled: adminLocalSecurityContainer.state.isPasswordResetEnabled,
+        isEmailAuthenticationEnabled: adminLocalSecurityContainer.state.isEmailAuthenticationEnabled,
+      });
       await adminGeneralSecurityContainer.retrieveSetupStratedies();
       toastSuccess(t('security_settings.updated_general_security_setting'));
     }

+ 20 - 17
apps/app/src/client/components/Admin/Security/OidcSecuritySettingContents.tsx

@@ -68,23 +68,26 @@ const OidcSecurityManagementContents = (props: Props) => {
 
   const onSubmit = useCallback(async(data) => {
     try {
-      await adminOidcSecurityContainer.changeOidcProviderName(data.oidcProviderName);
-      await adminOidcSecurityContainer.changeOidcIssuerHost(data.oidcIssuerHost);
-      await adminOidcSecurityContainer.changeOidcClientId(data.oidcClientId);
-      await adminOidcSecurityContainer.changeOidcClientSecret(data.oidcClientSecret);
-      await adminOidcSecurityContainer.changeOidcAuthorizationEndpoint(data.oidcAuthorizationEndpoint);
-      await adminOidcSecurityContainer.changeOidcTokenEndpoint(data.oidcTokenEndpoint);
-      await adminOidcSecurityContainer.changeOidcRevocationEndpoint(data.oidcRevocationEndpoint);
-      await adminOidcSecurityContainer.changeOidcIntrospectionEndpoint(data.oidcIntrospectionEndpoint);
-      await adminOidcSecurityContainer.changeOidcUserInfoEndpoint(data.oidcUserInfoEndpoint);
-      await adminOidcSecurityContainer.changeOidcEndSessionEndpoint(data.oidcEndSessionEndpoint);
-      await adminOidcSecurityContainer.changeOidcRegistrationEndpoint(data.oidcRegistrationEndpoint);
-      await adminOidcSecurityContainer.changeOidcJWKSUri(data.oidcJWKSUri);
-      await adminOidcSecurityContainer.changeOidcAttrMapId(data.oidcAttrMapId);
-      await adminOidcSecurityContainer.changeOidcAttrMapUserName(data.oidcAttrMapUserName);
-      await adminOidcSecurityContainer.changeOidcAttrMapName(data.oidcAttrMapName);
-      await adminOidcSecurityContainer.changeOidcAttrMapEmail(data.oidcAttrMapEmail);
-      await adminOidcSecurityContainer.updateOidcSetting();
+      await adminOidcSecurityContainer.updateOidcSetting({
+        oidcProviderName: data.oidcProviderName,
+        oidcIssuerHost: data.oidcIssuerHost,
+        oidcClientId: data.oidcClientId,
+        oidcClientSecret: data.oidcClientSecret,
+        oidcAuthorizationEndpoint: data.oidcAuthorizationEndpoint,
+        oidcTokenEndpoint: data.oidcTokenEndpoint,
+        oidcRevocationEndpoint: data.oidcRevocationEndpoint,
+        oidcIntrospectionEndpoint: data.oidcIntrospectionEndpoint,
+        oidcUserInfoEndpoint: data.oidcUserInfoEndpoint,
+        oidcEndSessionEndpoint: data.oidcEndSessionEndpoint,
+        oidcRegistrationEndpoint: data.oidcRegistrationEndpoint,
+        oidcJWKSUri: data.oidcJWKSUri,
+        oidcAttrMapId: data.oidcAttrMapId,
+        oidcAttrMapUserName: data.oidcAttrMapUserName,
+        oidcAttrMapName: data.oidcAttrMapName,
+        oidcAttrMapEmail: data.oidcAttrMapEmail,
+        isSameUsernameTreatedAsIdenticalUser: adminOidcSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser,
+        isSameEmailTreatedAsIdenticalUser: adminOidcSecurityContainer.state.isSameEmailTreatedAsIdenticalUser,
+      });
       await adminGeneralSecurityContainer.retrieveSetupStratedies();
       toastSuccess(t('security_settings.OAuth.OIDC.updated_oidc'));
     }

+ 13 - 11
apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.tsx

@@ -46,18 +46,20 @@ const SamlSecurityManagementContents = (props: Props) => {
   }, [adminSamlSecurityContainer.state, reset]);
 
   const onSubmit = useCallback(async(data) => {
-    adminSamlSecurityContainer.changeSamlEntryPoint(data.samlEntryPoint);
-    adminSamlSecurityContainer.changeSamlIssuer(data.samlIssuer);
-    adminSamlSecurityContainer.changeSamlCert(data.samlCert);
-    adminSamlSecurityContainer.changeSamlAttrMapId(data.samlAttrMapId);
-    adminSamlSecurityContainer.changeSamlAttrMapUserName(data.samlAttrMapUsername);
-    adminSamlSecurityContainer.changeSamlAttrMapMail(data.samlAttrMapMail);
-    adminSamlSecurityContainer.changeSamlAttrMapFirstName(data.samlAttrMapFirstName);
-    adminSamlSecurityContainer.changeSamlAttrMapLastName(data.samlAttrMapLastName);
-    adminSamlSecurityContainer.changeSamlABLCRule(data.samlABLCRule);
-
     try {
-      await adminSamlSecurityContainer.updateSamlSetting();
+      await adminSamlSecurityContainer.updateSamlSetting({
+        samlEntryPoint: data.samlEntryPoint,
+        samlIssuer: data.samlIssuer,
+        samlCert: data.samlCert,
+        samlAttrMapId: data.samlAttrMapId,
+        samlAttrMapUsername: data.samlAttrMapUsername,
+        samlAttrMapMail: data.samlAttrMapMail,
+        samlAttrMapFirstName: data.samlAttrMapFirstName,
+        samlAttrMapLastName: data.samlAttrMapLastName,
+        isSameUsernameTreatedAsIdenticalUser: adminSamlSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser,
+        isSameEmailTreatedAsIdenticalUser: adminSamlSecurityContainer.state.isSameEmailTreatedAsIdenticalUser,
+        samlABLCRule: data.samlABLCRule,
+      });
       toastSuccess(t('security_settings.SAML.updated_saml'));
     }
     catch (err) {

+ 15 - 4
apps/app/src/client/components/Admin/Security/SecuritySetting/index.tsx

@@ -36,10 +36,21 @@ const SecuritySettingComponent: React.FC<Props> = ({ adminGeneralSecurityContain
 
   const onSubmit = useCallback(async(data: FormData) => {
     try {
-      // Update sessionMaxAge from form data
-      await adminGeneralSecurityContainer.setSessionMaxAge(data.sessionMaxAge);
-      // Save all security settings
-      await adminGeneralSecurityContainer.updateGeneralSecuritySetting();
+      // Save all security settings with form data
+      await adminGeneralSecurityContainer.updateGeneralSecuritySetting({
+        sessionMaxAge: data.sessionMaxAge,
+        restrictGuestMode: adminGeneralSecurityContainer.state.currentRestrictGuestMode,
+        pageDeletionAuthority: adminGeneralSecurityContainer.state.currentPageDeletionAuthority,
+        pageCompleteDeletionAuthority: adminGeneralSecurityContainer.state.currentPageCompleteDeletionAuthority,
+        pageRecursiveDeletionAuthority: adminGeneralSecurityContainer.state.currentPageRecursiveDeletionAuthority,
+        pageRecursiveCompleteDeletionAuthority: adminGeneralSecurityContainer.state.currentPageRecursiveCompleteDeletionAuthority,
+        isAllGroupMembershipRequiredForPageCompleteDeletion: adminGeneralSecurityContainer.state.isAllGroupMembershipRequiredForPageCompleteDeletion,
+        hideRestrictedByGroup: adminGeneralSecurityContainer.state.currentGroupRestrictionDisplayMode === 'Hidden',
+        hideRestrictedByOwner: adminGeneralSecurityContainer.state.currentOwnerRestrictionDisplayMode === 'Hidden',
+        isUsersHomepageDeletionEnabled: adminGeneralSecurityContainer.state.isUsersHomepageDeletionEnabled,
+        isForceDeleteUserHomepageOnUserDeletion: adminGeneralSecurityContainer.state.isForceDeleteUserHomepageOnUserDeletion,
+        isRomUserAllowedToComment: adminGeneralSecurityContainer.state.isRomUserAllowedToComment,
+      });
       toastSuccess(t('security_settings.updated_general_security_setting'));
     }
     catch (err) {

+ 16 - 3
apps/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -239,9 +239,22 @@ export default class AdminGeneralSecurityContainer extends Container {
    * @memberOf AdminGeneralSecuritySContainer
    * @return {string} Appearance
    */
-  async updateGeneralSecuritySetting() {
-
-    let requestParams = {
+  async updateGeneralSecuritySetting(formData) {
+
+    let requestParams = formData != null ? {
+      sessionMaxAge: formData.sessionMaxAge,
+      restrictGuestMode: formData.restrictGuestMode,
+      pageDeletionAuthority: formData.pageDeletionAuthority,
+      pageCompleteDeletionAuthority: formData.pageCompleteDeletionAuthority,
+      pageRecursiveDeletionAuthority: formData.pageRecursiveDeletionAuthority,
+      pageRecursiveCompleteDeletionAuthority: formData.pageRecursiveCompleteDeletionAuthority,
+      isAllGroupMembershipRequiredForPageCompleteDeletion: formData.isAllGroupMembershipRequiredForPageCompleteDeletion,
+      hideRestrictedByGroup: formData.hideRestrictedByGroup,
+      hideRestrictedByOwner: formData.hideRestrictedByOwner,
+      isUsersHomepageDeletionEnabled: formData.isUsersHomepageDeletionEnabled,
+      isForceDeleteUserHomepageOnUserDeletion: formData.isForceDeleteUserHomepageOnUserDeletion,
+      isRomUserAllowedToComment: formData.isRomUserAllowedToComment,
+    } : {
       sessionMaxAge: this.state.sessionMaxAge,
       restrictGuestMode: this.state.currentRestrictGuestMode,
       pageDeletionAuthority: this.state.currentPageDeletionAuthority,

+ 10 - 18
apps/app/src/client/services/AdminGitHubSecurityContainer.js

@@ -61,20 +61,6 @@ export default class AdminGitHubSecurityContainer extends Container {
     return 'AdminGitHubSecurityContainer';
   }
 
-  /**
-   * Change githubClientId
-   */
-  changeGitHubClientId(value) {
-    this.setState({ githubClientId: value });
-  }
-
-  /**
-   * Change githubClientSecret
-   */
-  changeGitHubClientSecret(value) {
-    this.setState({ githubClientSecret: value });
-  }
-
   /**
    * Switch isSameUsernameTreatedAsIdenticalUser
    */
@@ -85,10 +71,16 @@ export default class AdminGitHubSecurityContainer extends Container {
   /**
    * Update githubSetting
    */
-  async updateGitHubSetting() {
-    const { githubClientId, githubClientSecret, isSameUsernameTreatedAsIdenticalUser } = this.state;
-
-    let requestParams = { githubClientId, githubClientSecret, isSameUsernameTreatedAsIdenticalUser };
+  async updateGitHubSetting(formData) {
+    let requestParams = formData != null ? {
+      githubClientId: formData.githubClientId,
+      githubClientSecret: formData.githubClientSecret,
+      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
+    } : {
+      githubClientId: this.state.githubClientId,
+      githubClientSecret: this.state.githubClientSecret,
+      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
+    };
 
     requestParams = await removeNullPropertyFromObject(requestParams);
     const response = await apiv3Put('/security-setting/github-oauth', requestParams);

+ 9 - 19
apps/app/src/client/services/AdminGoogleSecurityContainer.js

@@ -62,20 +62,6 @@ export default class AdminGoogleSecurityContainer extends Container {
     return 'AdminGoogleSecurityContainer';
   }
 
-  /**
-   * Change googleClientId
-   */
-  changeGoogleClientId(value) {
-    this.setState({ googleClientId: value });
-  }
-
-  /**
-   * Change googleClientSecret
-   */
-  changeGoogleClientSecret(value) {
-    this.setState({ googleClientSecret: value });
-  }
-
   /**
    * Switch isSameEmailTreatedAsIdenticalUser
    */
@@ -87,11 +73,15 @@ export default class AdminGoogleSecurityContainer extends Container {
   /**
    * Update googleSetting
    */
-  async updateGoogleSetting() {
-    const { googleClientId, googleClientSecret, isSameEmailTreatedAsIdenticalUser } = this.state;
-
-    let requestParams = {
-      googleClientId, googleClientSecret, isSameEmailTreatedAsIdenticalUser,
+  async updateGoogleSetting(formData) {
+    let requestParams = formData != null ? {
+      googleClientId: formData.googleClientId,
+      googleClientSecret: formData.googleClientSecret,
+      isSameEmailTreatedAsIdenticalUser: formData.isSameEmailTreatedAsIdenticalUser,
+    } : {
+      googleClientId: this.state.googleClientId,
+      googleClientSecret: this.state.googleClientSecret,
+      isSameEmailTreatedAsIdenticalUser: this.state.isSameEmailTreatedAsIdenticalUser,
     };
 
     requestParams = await removeNullPropertyFromObject(requestParams);

+ 27 - 89
apps/app/src/client/services/AdminLdapSecurityContainer.js

@@ -78,13 +78,6 @@ export default class AdminLdapSecurityContainer extends Container {
     return 'AdminLdapSecurityContainer';
   }
 
-  /**
-   * Change serverUrl
-   */
-  changeServerUrl(serverUrl) {
-    this.setState({ serverUrl });
-  }
-
   /**
    * Change ldapBindMode
    * @param {boolean} isUserBind true: User Bind, false: Admin Bind
@@ -93,34 +86,6 @@ export default class AdminLdapSecurityContainer extends Container {
     this.setState({ isUserBind });
   }
 
-  /**
-   * Change bindDN
-   */
-  changeBindDN(ldapBindDN) {
-    this.setState({ ldapBindDN });
-  }
-
-  /**
-   * Change bindDNPassword
-   */
-  changeBindDNPassword(ldapBindDNPassword) {
-    this.setState({ ldapBindDNPassword });
-  }
-
-  /**
-   * Change ldapSearchFilter
-   */
-  changeSearchFilter(ldapSearchFilter) {
-    this.setState({ ldapSearchFilter });
-  }
-
-  /**
-   * Change ldapAttrMapUsername
-   */
-  changeAttrMapUsername(ldapAttrMapUsername) {
-    this.setState({ ldapAttrMapUsername });
-  }
-
   /**
    * Switch is same username treated as identical user
    */
@@ -128,63 +93,36 @@ export default class AdminLdapSecurityContainer extends Container {
     this.setState({ isSameUsernameTreatedAsIdenticalUser: !this.state.isSameUsernameTreatedAsIdenticalUser });
   }
 
-  /**
-   * Change ldapAttrMapMail
-   */
-  changeAttrMapMail(ldapAttrMapMail) {
-    this.setState({ ldapAttrMapMail });
-  }
-
-  /**
-   * Change ldapAttrMapName
-   */
-  changeAttrMapName(ldapAttrMapName) {
-    this.setState({ ldapAttrMapName });
-  }
-
-  /**
-   * Change ldapGroupSearchBase
-   */
-  changeGroupSearchBase(ldapGroupSearchBase) {
-    this.setState({ ldapGroupSearchBase });
-  }
-
-  /**
-   * Change ldapGroupSearchFilter
-   */
-  changeGroupSearchFilter(ldapGroupSearchFilter) {
-    this.setState({ ldapGroupSearchFilter });
-  }
-
-  /**
-   * Change ldapGroupDnProperty
-   */
-  changeGroupDnProperty(ldapGroupDnProperty) {
-    this.setState({ ldapGroupDnProperty });
-  }
-
   /**
    * Update ldap option
    */
-  async updateLdapSetting() {
-    const {
-      serverUrl, isUserBind, ldapBindDN, ldapBindDNPassword, ldapSearchFilter, ldapAttrMapUsername, isSameUsernameTreatedAsIdenticalUser,
-      ldapAttrMapMail, ldapAttrMapName, ldapGroupSearchBase, ldapGroupSearchFilter, ldapGroupDnProperty,
-    } = this.state;
-
-    let requestParams = {
-      serverUrl,
-      isUserBind,
-      ldapBindDN,
-      ldapBindDNPassword,
-      ldapSearchFilter,
-      ldapAttrMapUsername,
-      isSameUsernameTreatedAsIdenticalUser,
-      ldapAttrMapMail,
-      ldapAttrMapName,
-      ldapGroupSearchBase,
-      ldapGroupSearchFilter,
-      ldapGroupDnProperty,
+  async updateLdapSetting(formData) {
+    let requestParams = formData != null ? {
+      serverUrl: formData.serverUrl,
+      isUserBind: formData.isUserBind,
+      ldapBindDN: formData.ldapBindDN,
+      ldapBindDNPassword: formData.ldapBindDNPassword,
+      ldapSearchFilter: formData.ldapSearchFilter,
+      ldapAttrMapUsername: formData.ldapAttrMapUsername,
+      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
+      ldapAttrMapMail: formData.ldapAttrMapMail,
+      ldapAttrMapName: formData.ldapAttrMapName,
+      ldapGroupSearchBase: formData.ldapGroupSearchBase,
+      ldapGroupSearchFilter: formData.ldapGroupSearchFilter,
+      ldapGroupDnProperty: formData.ldapGroupDnProperty,
+    } : {
+      serverUrl: this.state.serverUrl,
+      isUserBind: this.state.isUserBind,
+      ldapBindDN: this.state.ldapBindDN,
+      ldapBindDNPassword: this.state.ldapBindDNPassword,
+      ldapSearchFilter: this.state.ldapSearchFilter,
+      ldapAttrMapUsername: this.state.ldapAttrMapUsername,
+      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
+      ldapAttrMapMail: this.state.ldapAttrMapMail,
+      ldapAttrMapName: this.state.ldapAttrMapName,
+      ldapGroupSearchBase: this.state.ldapGroupSearchBase,
+      ldapGroupSearchFilter: this.state.ldapGroupSearchFilter,
+      ldapGroupDnProperty: this.state.ldapGroupDnProperty,
     };
 
     requestParams = await removeNullPropertyFromObject(requestParams);

+ 12 - 14
apps/app/src/client/services/AdminLocalSecurityContainer.js

@@ -71,13 +71,6 @@ export default class AdminLocalSecurityContainer extends Container {
     this.setState({ registrationMode: value });
   }
 
-  /**
-   * Change registration whitelist
-   */
-  changeRegistrationWhitelist(value) {
-    this.setState({ registrationWhitelist: value.split('\n') });
-  }
-
   /**
    * Switch password reset enabled
    */
@@ -95,14 +88,19 @@ export default class AdminLocalSecurityContainer extends Container {
   /**
    * update local security setting
    */
-  async updateLocalSecuritySetting() {
-    const { registrationWhitelist, isPasswordResetEnabled, isEmailAuthenticationEnabled } = this.state;
-    const response = await apiv3Put('/security-setting/local-setting', {
+  async updateLocalSecuritySetting(formData) {
+    const requestParams = formData != null ? {
+      registrationMode: formData.registrationMode,
+      registrationWhitelist: formData.registrationWhitelist,
+      isPasswordResetEnabled: formData.isPasswordResetEnabled,
+      isEmailAuthenticationEnabled: formData.isEmailAuthenticationEnabled,
+    } : {
       registrationMode: this.state.registrationMode,
-      registrationWhitelist,
-      isPasswordResetEnabled,
-      isEmailAuthenticationEnabled,
-    });
+      registrationWhitelist: this.state.registrationWhitelist,
+      isPasswordResetEnabled: this.state.isPasswordResetEnabled,
+      isEmailAuthenticationEnabled: this.state.isEmailAuthenticationEnabled,
+    };
+    const response = await apiv3Put('/security-setting/local-setting', requestParams);
 
     const { localSettingParams } = response.data;
 

+ 39 - 153
apps/app/src/client/services/AdminOidcSecurityContainer.js

@@ -89,118 +89,6 @@ export default class AdminOidcSecurityContainer extends Container {
     return 'AdminOidcSecurityContainer';
   }
 
-  /**
-   * Change oidcProviderName
-   */
-  changeOidcProviderName(inputValue) {
-    this.setState({ oidcProviderName: inputValue });
-  }
-
-  /**
-   * Change oidcIssuerHost
-   */
-  changeOidcIssuerHost(inputValue) {
-    this.setState({ oidcIssuerHost: inputValue });
-  }
-
-  /**
-   * Change oidcAuthorizationEndpoint
-   */
-  changeOidcAuthorizationEndpoint(inputValue) {
-    this.setState({ oidcAuthorizationEndpoint: inputValue });
-  }
-
-  /**
-   * Change oidcTokenEndpoint
-   */
-  changeOidcTokenEndpoint(inputValue) {
-    this.setState({ oidcTokenEndpoint: inputValue });
-  }
-
-  /**
-   * Change oidcRevocationEndpoint
-   */
-  changeOidcRevocationEndpoint(inputValue) {
-    this.setState({ oidcRevocationEndpoint: inputValue });
-  }
-
-  /**
-   * Change oidcIntrospectionEndpoint
-   */
-  changeOidcIntrospectionEndpoint(inputValue) {
-    this.setState({ oidcIntrospectionEndpoint: inputValue });
-  }
-
-  /**
-   * Change oidcUserInfoEndpoint
-   */
-  changeOidcUserInfoEndpoint(inputValue) {
-    this.setState({ oidcUserInfoEndpoint: inputValue });
-  }
-
-  /**
-   * Change oidcEndSessionEndpoint
-   */
-  changeOidcEndSessionEndpoint(inputValue) {
-    this.setState({ oidcEndSessionEndpoint: inputValue });
-  }
-
-  /**
-   * Change oidcRegistrationEndpoint
-   */
-  changeOidcRegistrationEndpoint(inputValue) {
-    this.setState({ oidcRegistrationEndpoint: inputValue });
-  }
-
-  /**
-   * Change oidcJWKSUri
-   */
-  changeOidcJWKSUri(inputValue) {
-    this.setState({ oidcJWKSUri: inputValue });
-  }
-
-  /**
-   * Change oidcClientId
-   */
-  changeOidcClientId(inputValue) {
-    this.setState({ oidcClientId: inputValue });
-  }
-
-  /**
-   * Change oidcClientSecret
-   */
-  changeOidcClientSecret(inputValue) {
-    this.setState({ oidcClientSecret: inputValue });
-  }
-
-  /**
-   * Change oidcAttrMapId
-   */
-  changeOidcAttrMapId(inputValue) {
-    this.setState({ oidcAttrMapId: inputValue });
-  }
-
-  /**
-   * Change oidcAttrMapUserName
-   */
-  changeOidcAttrMapUserName(inputValue) {
-    this.setState({ oidcAttrMapUserName: inputValue });
-  }
-
-  /**
-   * Change oidcAttrMapName
-   */
-  changeOidcAttrMapName(inputValue) {
-    this.setState({ oidcAttrMapName: inputValue });
-  }
-
-  /**
-   * Change oidcAttrMapEmail
-   */
-  changeOidcAttrMapEmail(inputValue) {
-    this.setState({ oidcAttrMapEmail: inputValue });
-  }
-
   /**
    * Switch sameUsernameTreatedAsIdenticalUser
    */
@@ -218,47 +106,45 @@ export default class AdminOidcSecurityContainer extends Container {
   /**
    * Update OpenID Connect
    */
-  async updateOidcSetting() {
-    const {
-      oidcProviderName,
-      oidcIssuerHost,
-      oidcAuthorizationEndpoint,
-      oidcTokenEndpoint,
-      oidcRevocationEndpoint,
-      oidcIntrospectionEndpoint,
-      oidcUserInfoEndpoint,
-      oidcEndSessionEndpoint,
-      oidcRegistrationEndpoint,
-      oidcJWKSUri,
-      oidcClientId,
-      oidcClientSecret,
-      oidcAttrMapId,
-      oidcAttrMapUserName,
-      oidcAttrMapName,
-      oidcAttrMapEmail,
-      isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser,
-    } = this.state;
-
-    let requestParams = {
-      oidcProviderName,
-      oidcIssuerHost,
-      oidcAuthorizationEndpoint,
-      oidcTokenEndpoint,
-      oidcRevocationEndpoint,
-      oidcIntrospectionEndpoint,
-      oidcUserInfoEndpoint,
-      oidcEndSessionEndpoint,
-      oidcRegistrationEndpoint,
-      oidcJWKSUri,
-      oidcClientId,
-      oidcClientSecret,
-      oidcAttrMapId,
-      oidcAttrMapUserName,
-      oidcAttrMapName,
-      oidcAttrMapEmail,
-      isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser,
+  async updateOidcSetting(formData) {
+    let requestParams = formData != null ? {
+      oidcProviderName: formData.oidcProviderName,
+      oidcIssuerHost: formData.oidcIssuerHost,
+      oidcAuthorizationEndpoint: formData.oidcAuthorizationEndpoint,
+      oidcTokenEndpoint: formData.oidcTokenEndpoint,
+      oidcRevocationEndpoint: formData.oidcRevocationEndpoint,
+      oidcIntrospectionEndpoint: formData.oidcIntrospectionEndpoint,
+      oidcUserInfoEndpoint: formData.oidcUserInfoEndpoint,
+      oidcEndSessionEndpoint: formData.oidcEndSessionEndpoint,
+      oidcRegistrationEndpoint: formData.oidcRegistrationEndpoint,
+      oidcJWKSUri: formData.oidcJWKSUri,
+      oidcClientId: formData.oidcClientId,
+      oidcClientSecret: formData.oidcClientSecret,
+      oidcAttrMapId: formData.oidcAttrMapId,
+      oidcAttrMapUserName: formData.oidcAttrMapUserName,
+      oidcAttrMapName: formData.oidcAttrMapName,
+      oidcAttrMapEmail: formData.oidcAttrMapEmail,
+      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser: formData.isSameEmailTreatedAsIdenticalUser,
+    } : {
+      oidcProviderName: this.state.oidcProviderName,
+      oidcIssuerHost: this.state.oidcIssuerHost,
+      oidcAuthorizationEndpoint: this.state.oidcAuthorizationEndpoint,
+      oidcTokenEndpoint: this.state.oidcTokenEndpoint,
+      oidcRevocationEndpoint: this.state.oidcRevocationEndpoint,
+      oidcIntrospectionEndpoint: this.state.oidcIntrospectionEndpoint,
+      oidcUserInfoEndpoint: this.state.oidcUserInfoEndpoint,
+      oidcEndSessionEndpoint: this.state.oidcEndSessionEndpoint,
+      oidcRegistrationEndpoint: this.state.oidcRegistrationEndpoint,
+      oidcJWKSUri: this.state.oidcJWKSUri,
+      oidcClientId: this.state.oidcClientId,
+      oidcClientSecret: this.state.oidcClientSecret,
+      oidcAttrMapId: this.state.oidcAttrMapId,
+      oidcAttrMapUserName: this.state.oidcAttrMapUserName,
+      oidcAttrMapName: this.state.oidcAttrMapName,
+      oidcAttrMapEmail: this.state.oidcAttrMapEmail,
+      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser: this.state.isSameEmailTreatedAsIdenticalUser,
     };
 
     requestParams = await removeNullPropertyFromObject(requestParams);

+ 15 - 66
apps/app/src/client/services/AdminSamlSecurityContainer.js

@@ -98,62 +98,6 @@ export default class AdminSamlSecurityContainer extends Container {
     return 'AdminSamlSecurityContainer';
   }
 
-  /**
-   * Change samlEntryPoint
-   */
-  changeSamlEntryPoint(inputValue) {
-    this.setState({ samlEntryPoint: inputValue });
-  }
-
-  /**
-   * Change samlIssuer
-   */
-  changeSamlIssuer(inputValue) {
-    this.setState({ samlIssuer: inputValue });
-  }
-
-  /**
-   * Change samlCert
-   */
-  changeSamlCert(inputValue) {
-    this.setState({ samlCert: inputValue });
-  }
-
-  /**
-   * Change samlAttrMapId
-   */
-  changeSamlAttrMapId(inputValue) {
-    this.setState({ samlAttrMapId: inputValue });
-  }
-
-  /**
-   * Change samlAttrMapUsername
-   */
-  changeSamlAttrMapUserName(inputValue) {
-    this.setState({ samlAttrMapUsername: inputValue });
-  }
-
-  /**
-   * Change samlAttrMapMail
-   */
-  changeSamlAttrMapMail(inputValue) {
-    this.setState({ samlAttrMapMail: inputValue });
-  }
-
-  /**
-   * Change samlAttrMapFirstName
-   */
-  changeSamlAttrMapFirstName(inputValue) {
-    this.setState({ samlAttrMapFirstName: inputValue });
-  }
-
-  /**
-   * Change samlAttrMapLastName
-   */
-  changeSamlAttrMapLastName(inputValue) {
-    this.setState({ samlAttrMapLastName: inputValue });
-  }
-
   /**
    * Switch isSameUsernameTreatedAsIdenticalUser
    */
@@ -168,19 +112,24 @@ export default class AdminSamlSecurityContainer extends Container {
     this.setState({ isSameEmailTreatedAsIdenticalUser: !this.state.isSameEmailTreatedAsIdenticalUser });
   }
 
-  /**
-   * Change samlABLCRule
-   */
-  changeSamlABLCRule(inputValue) {
-    this.setState({ samlABLCRule: inputValue });
-  }
-
   /**
    * Update saml option
    */
-  async updateSamlSetting() {
-
-    let requestParams = {
+  async updateSamlSetting(formData) {
+
+    let requestParams = formData != null ? {
+      entryPoint: formData.samlEntryPoint,
+      issuer: formData.samlIssuer,
+      cert: formData.samlCert,
+      attrMapId: formData.samlAttrMapId,
+      attrMapUsername: formData.samlAttrMapUsername,
+      attrMapMail: formData.samlAttrMapMail,
+      attrMapFirstName: formData.samlAttrMapFirstName,
+      attrMapLastName: formData.samlAttrMapLastName,
+      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser: formData.isSameEmailTreatedAsIdenticalUser,
+      ABLCRule: formData.samlABLCRule,
+    } : {
       entryPoint: this.state.samlEntryPoint,
       issuer: this.state.samlIssuer,
       cert: this.state.samlCert,

+ 1 - 0
apps/pdf-converter/.env

@@ -1 +1,2 @@
 PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
+PUPPETEER_CLUSTER_CONFIG={"maxConcurrency":1, "concurrency": 2}

+ 2 - 2
apps/pdf-converter/docker/README.md

@@ -5,10 +5,10 @@ GROWI PDF Converter Official docker image
 [![Node CI for pdf-converter](https://github.com/growilabs/growi/actions/workflows/ci-pdf-converter.yml/badge.svg)](https://github.com/growilabs/growi/actions/workflows/ci-pdf-converter.yml) [![docker-pulls](https://img.shields.io/docker/pulls/growilabs/pdf-converter.svg)](https://hub.docker.com/r/growilabs/pdf-converter/)
 
 
-Supported tags and respective Dockerfile links
+Dockerfile link
 ------------------------------------------------
 
-* [`1.0.0`, `latest` (Dockerfile)](https://github.com/growilabs/growi/blob/master/apps/pdf-converter/docker/Dockerfile)
+https://github.com/growilabs/growi/blob/master/apps/pdf-converter/docker/Dockerfile
 
 
 What is GROWI PDF Converter used for?

+ 1 - 1
apps/pdf-converter/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/pdf-converter",
-  "version": "1.1.3-RC.0",
+  "version": "1.2.1-RC.0",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
   "license": "MIT",

+ 48 - 11
apps/pdf-converter/src/service/pdf-convert.ts

@@ -5,6 +5,7 @@ import { pipeline as pipelinePromise } from 'node:stream/promises';
 import { OnInit } from '@tsed/common';
 import { Service } from '@tsed/di';
 import { Logger } from '@tsed/logger';
+import type { PuppeteerNodeLaunchOptions } from 'puppeteer';
 import { Cluster } from 'puppeteer-cluster';
 
 interface PageInfo {
@@ -37,8 +38,6 @@ interface JobInfo {
 class PdfConvertService implements OnInit {
   private puppeteerCluster: Cluster | undefined;
 
-  private maxConcurrency = 1;
-
   private convertRetryLimit = 5;
 
   private tmpOutputRootDir = '/tmp/page-bulk-export';
@@ -292,15 +291,8 @@ class PdfConvertService implements OnInit {
   private async initPuppeteerCluster(): Promise<void> {
     if (process.env.SKIP_PUPPETEER_INIT === 'true') return;
 
-    this.puppeteerCluster = await Cluster.launch({
-      concurrency: Cluster.CONCURRENCY_PAGE,
-      maxConcurrency: this.maxConcurrency,
-      workerCreationDelay: 10000,
-      puppeteerOptions: {
-        // ref) https://github.com/growilabs/growi/pull/10192
-        args: ['--no-sandbox'],
-      },
-    });
+    const config = this.getPuppeteerClusterConfig();
+    this.puppeteerCluster = await Cluster.launch(config);
 
     await this.puppeteerCluster.task(async ({ page, data: htmlString }) => {
       await page.setContent(htmlString, { waitUntil: 'domcontentloaded' });
@@ -326,6 +318,51 @@ class PdfConvertService implements OnInit {
     });
   }
 
+  /**
+   * Get puppeteer cluster configuration from environment variable
+   * @returns merged cluster configuration
+   */
+  private getPuppeteerClusterConfig(): Record<string, any> {
+    // Default cluster configuration
+    const defaultConfig = {
+      concurrency: Cluster.CONCURRENCY_CONTEXT,
+      maxConcurrency: 1,
+      workerCreationDelay: 10000,
+      // Puppeteer options (not configurable for security reasons)
+      // ref) https://github.com/growilabs/growi/pull/10192
+      puppeteerOptions: {
+        args: ['--no-sandbox'],
+      },
+    };
+
+    // Parse configuration from environment variable
+    let customConfig: Record<string, any> = {};
+    if (process.env.PUPPETEER_CLUSTER_CONFIG) {
+      try {
+        customConfig = JSON.parse(process.env.PUPPETEER_CLUSTER_CONFIG);
+      } catch (err) {
+        this.logger.warn(
+          'Failed to parse PUPPETEER_CLUSTER_CONFIG, using default values',
+          err,
+        );
+      }
+    }
+
+    // Remove puppeteerOptions from custom config if present (not allowed for security)
+    if (customConfig.puppeteerOptions) {
+      this.logger.warn(
+        'puppeteerOptions configuration is not allowed for security reasons and will be ignored',
+      );
+      delete customConfig.puppeteerOptions;
+    }
+
+    // Merge configurations (customConfig overrides defaultConfig, except puppeteerOptions)
+    return {
+      ...defaultConfig,
+      ...customConfig,
+    };
+  }
+
   /**
    * Get parent path from given path
    * @param path target path

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

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "7.3.6-slackbot-proxy.0",
+  "version": "7.3.8-slackbot-proxy.0",
   "license": "MIT",
   "private": "true",
   "scripts": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "7.3.6-RC.0",
+  "version": "7.3.8-RC.0",
   "description": "Team collaboration software using markdown",
   "license": "MIT",
   "private": "true",

+ 22 - 11
packages/editor/index.html

@@ -1,14 +1,25 @@
 <!DOCTYPE html>
 <html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
-    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL@20..48,300,0..1" rel="stylesheet" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vite + React + TS</title>
-  </head>
-  <body>
-    <div id="root"></div>
-    <script type="module" src="/src/main.tsx"></script>
-  </body>
+
+<head>
+  <meta charset="UTF-8" />
+  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+  <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL@20..48,300,0..1"
+    rel="stylesheet" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Vite + React + TS</title>
+  <style>
+    @font-face {
+      font-family: 'growi-custom-icons';
+      src: url('../custom-icons/dist/growi-custom-icons.woff2') format('woff2');
+      font-display: block;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="root"></div>
+  <script type="module" src="/src/main.tsx"></script>
+</body>
+
 </html>

+ 9 - 0
packages/editor/src/main.scss

@@ -8,4 +8,13 @@
   --font-family-sans-serif: -apple-system, blinkmacsystemfont, 'Hiragino Kaku Gothic ProN', meiryo, sans-serif;
   --font-family-serif: georgia, 'Times New Roman', times, serif;
   --font-family-monospace: Menlo, Consolas, DejaVu Sans Mono, monospace;
+  --grw-font-family-custom-icon: 'growi-custom-icons';
+}
+
+.growi-custom-icons {
+  font-family: var(--grw-font-family-custom-icon);
+  font-size: 0.8em;
+  font-style: normal;
+  -webkit-font-smoothing: auto;
+  -moz-osx-font-smoothing: auto;
 }

+ 70 - 70
pnpm-lock.yaml

@@ -72,7 +72,7 @@ importers:
         version: 5.59.7(eslint@8.41.0)(typescript@5.0.4)
       '@vitejs/plugin-react':
         specifier: ^4.3.1
-        version: 4.3.1(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0))
+        version: 4.3.1(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1))
       '@vitest/coverage-v8':
         specifier: ^2.1.1
         version: 2.1.1(vitest@2.1.1)
@@ -189,16 +189,16 @@ importers:
         version: 3.4.7(typescript@5.0.4)
       vite:
         specifier: ^5.4.21
-        version: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
+        version: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
       vite-plugin-dts:
         specifier: ^3.9.1
-        version: 3.9.1(@types/node@20.19.17)(rollup@4.52.5)(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0))
+        version: 3.9.1(@types/node@20.19.17)(rollup@4.52.5)(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1))
       vite-tsconfig-paths:
         specifier: ^5.0.1
-        version: 5.0.1(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0))
+        version: 5.0.1(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1))
       vitest:
         specifier: ^2.1.1
-        version: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.0)
+        version: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.1)
       vitest-mock-extended:
         specifier: ^2.0.2
         version: 2.0.2(typescript@5.0.4)(vitest@2.1.1)
@@ -467,8 +467,8 @@ importers:
         specifier: ^1.0.15
         version: 1.0.15
       js-yaml:
-        specifier: ^4.1.0
-        version: 4.1.0
+        specifier: ^4.1.1
+        version: 4.1.1
       jsonrepair:
         specifier: ^3.12.0
         version: 3.12.0
@@ -2734,8 +2734,8 @@ packages:
   '@codemirror/view@6.38.1':
     resolution: {integrity: sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==}
 
-  '@codemirror/view@6.38.6':
-    resolution: {integrity: sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==}
+  '@codemirror/view@6.38.7':
+    resolution: {integrity: sha512-+b0imJTgzehmMToqT9DWPBdeRj7/qDsJj7MzQ+1+do2KK2UkxKuLaHlUVeZk855wO6my6cfbF1c+Qozs8B3YqA==}
 
   '@colors/colors@1.5.0':
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
@@ -6557,8 +6557,8 @@ packages:
     resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==}
     engines: {node: '>=6.0.0'}
 
-  baseline-browser-mapping@2.8.20:
-    resolution: {integrity: sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==}
+  baseline-browser-mapping@2.8.28:
+    resolution: {integrity: sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==}
     hasBin: true
 
   basic-auth@2.0.1:
@@ -6665,8 +6665,8 @@ packages:
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
 
-  browserslist@4.27.0:
-    resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
+  browserslist@4.28.0:
+    resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
 
@@ -6813,8 +6813,8 @@ packages:
   caniuse-lite@1.0.30001735:
     resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==}
 
-  caniuse-lite@1.0.30001751:
-    resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==}
+  caniuse-lite@1.0.30001754:
+    resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==}
 
   capital-case@1.0.4:
     resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@@ -8339,8 +8339,8 @@ packages:
   electron-to-chromium@1.5.207:
     resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==}
 
-  electron-to-chromium@1.5.240:
-    resolution: {integrity: sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==}
+  electron-to-chromium@1.5.252:
+    resolution: {integrity: sha512-53uTpjtRgS7gjIxZ4qCgFdNO2q+wJt/Z8+xAvxbCqXPJrY6h7ighUkadQmNMXH96crtpa6gPFNP7BF4UBGDuaA==}
 
   emittery@0.13.1:
     resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
@@ -10315,8 +10315,8 @@ packages:
     resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
     hasBin: true
 
-  js-yaml@4.1.0:
-    resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+  js-yaml@4.1.1:
+    resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
     hasBin: true
 
   jsbn@0.1.1:
@@ -11624,8 +11624,8 @@ packages:
   node-releases@2.0.19:
     resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
 
-  node-releases@2.0.26:
-    resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==}
+  node-releases@2.0.27:
+    resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
 
   nodemailer-ses-transport@1.5.1:
     resolution: {integrity: sha512-JwL93Lc7KEWbH4a9Ehm6XCJgNhf6QNleSDkIsCvEyViKzqvYsf+8rF2PG8OzI1xDyxvtgsaWAmJWMqABOZmnWg==}
@@ -13947,8 +13947,8 @@ packages:
       uglify-js:
         optional: true
 
-  terser@5.44.0:
-    resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==}
+  terser@5.44.1:
+    resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -15310,14 +15310,14 @@ snapshots:
     dependencies:
       '@jsdevtools/ono': 7.1.3
       '@types/json-schema': 7.0.15
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
 
   '@apidevtools/json-schema-ref-parser@9.1.2':
     dependencies:
       '@jsdevtools/ono': 7.1.3
       '@types/json-schema': 7.0.15
       call-me-maybe: 1.0.2
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
 
   '@apidevtools/openapi-schemas@2.1.0': {}
 
@@ -17101,7 +17101,7 @@ snapshots:
     dependencies:
       '@codemirror/language': 6.11.3
       '@codemirror/state': 6.5.2
-      '@codemirror/view': 6.38.6
+      '@codemirror/view': 6.38.7
       '@lezer/highlight': 1.2.3
 
   '@codemirror/view@6.38.1':
@@ -17111,7 +17111,7 @@ snapshots:
       style-mod: 4.1.2
       w3c-keyname: 2.2.8
 
-  '@codemirror/view@6.38.6':
+  '@codemirror/view@6.38.7':
     dependencies:
       '@codemirror/state': 6.5.2
       crelt: 1.0.6
@@ -17401,7 +17401,7 @@ snapshots:
       globals: 13.24.0
       ignore: 5.3.1
       import-fresh: 3.3.0
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       minimatch: 3.1.2
       strip-json-comments: 3.1.1
     transitivePeerDependencies:
@@ -17994,7 +17994,7 @@ snapshots:
     dependencies:
       color-string: 1.9.1
       cssesc: 3.0.0
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       lodash.kebabcase: 4.1.1
       markdown-it: 13.0.2
       markdown-it-front-matter: 0.2.4
@@ -19154,7 +19154,7 @@ snapshots:
       colorette: 1.4.0
       https-proxy-agent: 7.0.6(supports-color@10.0.0)
       js-levenshtein: 1.1.6
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       minimatch: 5.1.6
       pluralize: 8.0.0
       yaml-ast-parser: 0.0.43
@@ -20355,7 +20355,7 @@ snapshots:
       handlebars-utils: 1.0.6
       inquirer: 9.3.7
       inquirer-autocomplete-prompt: 3.0.1(inquirer@9.3.7)
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       listr2: 8.2.5
       read-pkg-up: 11.0.0
       registry-url: 6.0.1
@@ -21504,14 +21504,14 @@ snapshots:
 
   '@unts/get-tsconfig@4.1.1': {}
 
-  '@vitejs/plugin-react@4.3.1(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0))':
+  '@vitejs/plugin-react@4.3.1(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1))':
     dependencies:
       '@babel/core': 7.24.6
       '@babel/plugin-transform-react-jsx-self': 7.24.6(@babel/core@7.24.6)
       '@babel/plugin-transform-react-jsx-source': 7.24.6(@babel/core@7.24.6)
       '@types/babel__core': 7.20.5
       react-refresh: 0.14.2
-      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
+      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -21529,7 +21529,7 @@ snapshots:
       std-env: 3.7.0
       test-exclude: 7.0.1
       tinyrainbow: 1.2.0
-      vitest: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.0)
+      vitest: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -21540,13 +21540,13 @@ snapshots:
       chai: 5.1.1
       tinyrainbow: 1.2.0
 
-  '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0))':
+  '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1))':
     dependencies:
       '@vitest/spy': 2.1.1
       estree-walker: 3.0.3
       magic-string: 0.30.11
     optionalDependencies:
-      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
+      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
 
   '@vitest/pretty-format@2.1.1':
     dependencies:
@@ -21576,7 +21576,7 @@ snapshots:
       sirv: 2.0.4
       tinyglobby: 0.2.6
       tinyrainbow: 1.2.0
-      vitest: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.0)
+      vitest: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.1)
 
   '@vitest/utils@2.1.1':
     dependencies:
@@ -22260,7 +22260,7 @@ snapshots:
 
   base64url@3.0.1: {}
 
-  baseline-browser-mapping@2.8.20: {}
+  baseline-browser-mapping@2.8.28: {}
 
   basic-auth@2.0.1:
     dependencies:
@@ -22432,13 +22432,13 @@ snapshots:
       node-releases: 2.0.19
       update-browserslist-db: 1.1.3(browserslist@4.25.3)
 
-  browserslist@4.27.0:
+  browserslist@4.28.0:
     dependencies:
-      baseline-browser-mapping: 2.8.20
-      caniuse-lite: 1.0.30001751
-      electron-to-chromium: 1.5.240
-      node-releases: 2.0.26
-      update-browserslist-db: 1.1.4(browserslist@4.27.0)
+      baseline-browser-mapping: 2.8.28
+      caniuse-lite: 1.0.30001754
+      electron-to-chromium: 1.5.252
+      node-releases: 2.0.27
+      update-browserslist-db: 1.1.4(browserslist@4.28.0)
 
   bs-recipes@1.3.4: {}
 
@@ -22618,7 +22618,7 @@ snapshots:
 
   caniuse-lite@1.0.30001735: {}
 
-  caniuse-lite@1.0.30001751: {}
+  caniuse-lite@1.0.30001754: {}
 
   capital-case@1.0.4:
     dependencies:
@@ -23160,7 +23160,7 @@ snapshots:
     dependencies:
       env-paths: 2.2.1
       import-fresh: 3.3.0
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       parse-json: 5.2.0
     optionalDependencies:
       typescript: 5.0.4
@@ -23169,7 +23169,7 @@ snapshots:
     dependencies:
       env-paths: 2.2.1
       import-fresh: 3.3.0
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       parse-json: 5.2.0
     optionalDependencies:
       typescript: 5.4.2
@@ -23880,7 +23880,7 @@ snapshots:
 
   electron-to-chromium@1.5.207: {}
 
-  electron-to-chromium@1.5.240: {}
+  electron-to-chromium@1.5.252: {}
 
   emittery@0.13.1: {}
 
@@ -24454,7 +24454,7 @@ snapshots:
       imurmurhash: 0.1.4
       is-glob: 4.0.3
       is-path-inside: 3.0.3
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       json-stable-stringify-without-jsonify: 1.0.1
       levn: 0.4.1
       lodash.merge: 4.6.2
@@ -26467,7 +26467,7 @@ snapshots:
       argparse: 1.0.10
       esprima: 4.0.1
 
-  js-yaml@4.1.0:
+  js-yaml@4.1.1:
     dependencies:
       argparse: 2.0.1
 
@@ -28150,7 +28150,7 @@ snapshots:
 
   node-releases@2.0.19: {}
 
-  node-releases@2.0.26: {}
+  node-releases@2.0.27: {}
 
   nodemailer-ses-transport@1.5.1:
     dependencies:
@@ -31018,12 +31018,12 @@ snapshots:
       jest-worker: 27.5.1
       schema-utils: 4.3.3
       serialize-javascript: 6.0.2
-      terser: 5.44.0
+      terser: 5.44.1
       webpack: 5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15))
     optionalDependencies:
       '@swc/core': 1.10.7(@swc/helpers@0.5.15)
 
-  terser@5.44.0:
+  terser@5.44.1:
     dependencies:
       '@jridgewell/source-map': 0.3.11
       acorn: 8.15.0
@@ -31440,7 +31440,7 @@ snapshots:
       debug: 4.4.1(supports-color@5.5.0)
       dotenv: 8.6.0
       glob: 7.2.3
-      js-yaml: 4.1.0
+      js-yaml: 4.1.1
       mkdirp: 1.0.4
       reflect-metadata: 0.1.14
       sha.js: 2.4.11
@@ -31648,9 +31648,9 @@ snapshots:
       escalade: 3.2.0
       picocolors: 1.1.1
 
-  update-browserslist-db@1.1.4(browserslist@4.27.0):
+  update-browserslist-db@1.1.4(browserslist@4.28.0):
     dependencies:
-      browserslist: 4.27.0
+      browserslist: 4.28.0
       escalade: 3.2.0
       picocolors: 1.1.1
 
@@ -31813,12 +31813,12 @@ snapshots:
       '@types/unist': 3.0.3
       vfile-message: 4.0.2
 
-  vite-node@2.1.1(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0):
+  vite-node@2.1.1(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1):
     dependencies:
       cac: 6.7.14
       debug: 4.4.1(supports-color@5.5.0)
       pathe: 1.1.2
-      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
+      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -31830,7 +31830,7 @@ snapshots:
       - supports-color
       - terser
 
-  vite-plugin-dts@3.9.1(@types/node@20.19.17)(rollup@4.52.5)(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)):
+  vite-plugin-dts@3.9.1(@types/node@20.19.17)(rollup@4.52.5)(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)):
     dependencies:
       '@microsoft/api-extractor': 7.43.0(@types/node@20.19.17)
       '@rollup/pluginutils': 5.1.4(rollup@4.52.5)
@@ -31841,24 +31841,24 @@ snapshots:
       typescript: 5.0.4
       vue-tsc: 1.8.27(typescript@5.0.4)
     optionalDependencies:
-      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
+      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
     transitivePeerDependencies:
       - '@types/node'
       - rollup
       - supports-color
 
-  vite-tsconfig-paths@5.0.1(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)):
+  vite-tsconfig-paths@5.0.1(typescript@5.0.4)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)):
     dependencies:
       debug: 4.4.1(supports-color@5.5.0)
       globrex: 0.1.2
       tsconfck: 3.0.3(typescript@5.0.4)
     optionalDependencies:
-      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
+      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
     transitivePeerDependencies:
       - supports-color
       - typescript
 
-  vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0):
+  vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.5.6
@@ -31867,18 +31867,18 @@ snapshots:
       '@types/node': 20.19.17
       fsevents: 2.3.3
       sass: 1.77.6
-      terser: 5.44.0
+      terser: 5.44.1
 
   vitest-mock-extended@2.0.2(typescript@5.0.4)(vitest@2.1.1):
     dependencies:
       ts-essentials: 10.0.2(typescript@5.0.4)
       typescript: 5.0.4
-      vitest: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.0)
+      vitest: 2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.1)
 
-  vitest@2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.0):
+  vitest@2.1.1(@types/node@20.19.17)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.44.1):
     dependencies:
       '@vitest/expect': 2.1.1
-      '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0))
+      '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1))
       '@vitest/pretty-format': 2.1.1
       '@vitest/runner': 2.1.1
       '@vitest/snapshot': 2.1.1
@@ -31893,8 +31893,8 @@ snapshots:
       tinyexec: 0.3.0
       tinypool: 1.0.1
       tinyrainbow: 1.2.0
-      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
-      vite-node: 2.1.1(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.0)
+      vite: 5.4.21(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
+      vite-node: 2.1.1(@types/node@20.19.17)(sass@1.77.6)(terser@5.44.1)
       why-is-node-running: 2.3.0
     optionalDependencies:
       '@types/node': 20.19.17
@@ -32026,7 +32026,7 @@ snapshots:
       '@webassemblyjs/wasm-parser': 1.14.1
       acorn: 8.15.0
       acorn-import-attributes: 1.9.5(acorn@8.15.0)
-      browserslist: 4.27.0
+      browserslist: 4.28.0
       chrome-trace-event: 1.0.4
       enhanced-resolve: 5.18.3
       es-module-lexer: 1.7.0