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

Merge branch 'master' into support/156162-167198-remark-attachment-refs-biome

Futa Arai 9 месяцев назад
Родитель
Сommit
1893226758
73 измененных файлов с 1278 добавлено и 570 удалено
  1. 46 1
      CHANGELOG.md
  2. 9 0
      apps/app/bin/openapi/definition-apiv1.js
  3. 9 0
      apps/app/bin/openapi/definition-apiv3.js
  4. 1 1
      apps/app/bin/openapi/generate-spec-apiv3.sh
  5. 2 2
      apps/app/package.json
  6. 2 2
      apps/app/src/client/components/Admin/App/AppSetting.jsx
  7. 4 4
      apps/app/src/client/components/Admin/App/AwsSetting.tsx
  8. 8 8
      apps/app/src/client/components/Admin/App/AzureSetting.tsx
  9. 3 3
      apps/app/src/client/components/Admin/App/GcsSetting.tsx
  10. 1 1
      apps/app/src/client/components/Admin/App/MailSetting.tsx
  11. 3 3
      apps/app/src/client/components/Admin/App/MaskedInput.tsx
  12. 2 2
      apps/app/src/client/components/Admin/App/SesSetting.tsx
  13. 1 1
      apps/app/src/client/components/Admin/App/SiteUrlSetting.tsx
  14. 4 4
      apps/app/src/client/components/Admin/App/SmtpSetting.tsx
  15. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx
  16. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  17. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx
  18. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeTitle.tsx
  19. 2 2
      apps/app/src/client/components/Admin/LegacySlackIntegration/SlackConfiguration.jsx
  20. 2 2
      apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx
  21. 1 1
      apps/app/src/client/components/Admin/Security/GitHubSecuritySettingContents.jsx
  22. 3 3
      apps/app/src/client/components/Admin/Security/GoogleSecuritySettingContents.jsx
  23. 10 10
      apps/app/src/client/components/Admin/Security/LdapSecuritySettingContents.jsx
  24. 1 1
      apps/app/src/client/components/Admin/Security/LocalSecuritySettingContents.jsx
  25. 16 16
      apps/app/src/client/components/Admin/Security/OidcSecuritySettingContents.jsx
  26. 9 9
      apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx
  27. 1 1
      apps/app/src/client/components/Admin/Security/SecuritySetting.jsx
  28. 1 1
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  29. 2 2
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  30. 1 1
      apps/app/src/client/components/Admin/SlackIntegration/ManageCommandsProcess.jsx
  31. 0 1
      apps/app/src/client/components/Sidebar/InAppNotification/PrimaryItemForNotification.tsx
  32. 15 4
      apps/app/src/client/components/Sidebar/SidebarContents.tsx
  33. 3 2
      apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx
  34. 4 2
      apps/app/src/client/services/page-operation.ts
  35. 1 1
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx
  36. 0 0
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/MessageCard.module.scss
  37. 13 10
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/MessageCard.tsx
  38. 25 0
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/ReactMarkdownComponents/Header.tsx
  39. 13 0
      apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/ReactMarkdownComponents/NextLinkWrapper.tsx
  40. 11 6
      apps/app/src/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton.tsx
  41. 57 23
      apps/app/src/features/openai/server/routes/edit/index.ts
  42. 0 20
      apps/app/src/server/models/openapi/v1-response.js
  43. 131 0
      apps/app/src/server/models/openapi/v1-response.ts
  44. 79 31
      apps/app/src/server/routes/apiv3/app-settings.js
  45. 4 4
      apps/app/src/server/routes/apiv3/attachment.js
  46. 2 2
      apps/app/src/server/routes/apiv3/page/index.ts
  47. 10 10
      apps/app/src/server/routes/apiv3/security-settings/index.js
  48. 2 2
      apps/app/src/server/routes/apiv3/users.js
  49. 15 18
      apps/app/src/server/routes/attachment/api.js
  50. 32 31
      apps/app/src/server/routes/comment.js
  51. 332 117
      apps/app/src/server/routes/page.js
  52. 18 16
      apps/app/src/server/routes/search.ts
  53. 29 23
      apps/app/src/server/routes/tag.js
  54. 20 16
      apps/app/src/server/service/config-manager/config-definition.ts
  55. 8 5
      apps/app/src/server/service/file-uploader/aws/index.ts
  56. 4 3
      apps/app/src/server/service/file-uploader/azure.ts
  57. 3 2
      apps/app/src/server/service/file-uploader/gcs/index.ts
  58. 1 1
      apps/slackbot-proxy/package.json
  59. 1 3
      biome.json
  60. 1 1
      package.json
  61. 1 0
      packages/preset-templates/.eslintignore
  62. 0 5
      packages/preset-templates/.eslintrc.js
  63. 2 1
      packages/preset-templates/package.json
  64. 7 8
      packages/preset-templates/test/index.test.ts
  65. 1 3
      packages/preset-templates/tsconfig.json
  66. 1 1
      packages/preset-themes/.eslintignore
  67. 0 2
      packages/preset-themes/.eslintrc.js
  68. 1 1
      packages/preset-themes/package.json
  69. 21 11
      packages/preset-themes/src/consts/preset-themes.ts
  70. 2 5
      packages/preset-themes/tsconfig.json
  71. 2 5
      packages/preset-themes/vite.libs.config.ts
  72. 1 3
      packages/preset-themes/vite.themes.config.ts
  73. 257 86
      pnpm-lock.yaml

+ 46 - 1
CHANGELOG.md

@@ -1,9 +1,54 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.2.5...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.2.7...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v7.2.7](https://github.com/weseek/growi/compare/v7.2.6...v7.2.7) - 2025-06-11
+
+### 🐛 Bug Fixes
+
+* fix: Input values ​​in the admin settings form are sometimes not reflected (#10051) @yuki-takei
+* fix: Hide Google OAuth client secret field (#10049) @yuki-takei
+* fix: Prevent unnecessary API request when the user is guest (#10046) @yuki-takei
+* fix(ai): Prevent unnecessary API request when GROWI AI is disabled (#10044) @yuki-takei
+
+### 🧰 Maintenance
+
+* support: Configure biome for preset-templates package (#10058) @arafubeatbox
+* support: Configure biome for preset-themes package (#10055) @arafubeatbox
+* support: Configure biome for remark-drawio package (#10033) @arafubeatbox
+
+## [v7.2.6](https://github.com/weseek/growi/compare/v7.2.5...v7.2.6) - 2025-06-10
+
+### 💎 Features
+
+* feat(ai): Display spinner while creating diff (#9991) @miya
+
+### 🚀 Improvement
+
+* imprv: Message card markdown header size (#10038) @miya
+* imprv: Type safe configuration for file uploading (#10032) @yuki-takei
+* imprv: EditorAssistant instruction (#10030) @miya
+* imprv: Add NonEmptyString type (#10031) @yuki-takei
+* imprv: Security settings search results redesign (#9992) @arvid-e
+* imprv: OpenAPI spec properties ref (#10023) @yuki-takei
+* imprv(ai): Make input form position sticky (#10002) @miya
+* imprv: Prevent path traversal attack in pdf converter (#9993) @arafubeatbox
+* imprv: Discard when form is submitted without Accept/Discard after showing diff (#9980) @miya
+
+### 🐛 Bug Fixes
+
+* imprv:  The delete button on the user home page is now hidden for unauthorized users. (#9915) @taikou-m
+* fix: OpenAI threads can be retrieved regardless of assistant's public permissions (#9994) @miya
+* fix: Editor assistant button is being displayed when ai functionality is not enabled (#9985) @miya
+* fix: Improve attribute handling in Lsx (#9989) @yuki-takei
+
+### 🧰 Maintenance
+
+* support: OpenAPI operationId generation (#10009) @yuki-takei
+* support: Configure biome for remark-growi-directive (#9999) @arafubeatbox
+
 ## [v7.2.5](https://github.com/weseek/growi/compare/v7.2.4...v7.2.5) - 2025-05-28
 
 ### 💎 Features

+ 9 - 0
apps/app/bin/openapi/definition-apiv1.js

@@ -7,6 +7,15 @@ module.exports = {
     version: pkg.version,
   },
   servers: [
+    {
+      url: '{server}/_api',
+      variables: {
+        server: {
+          default: 'https://demo.growi.org',
+          description: 'The base URL for the GROWI API except for the version path (/_api). This can be set to your GROWI instance URL.',
+        },
+      },
+    },
     {
       url: 'https://demo.growi.org/_api',
     },

+ 9 - 0
apps/app/bin/openapi/definition-apiv3.js

@@ -7,6 +7,15 @@ module.exports = {
     version: pkg.version,
   },
   servers: [
+    {
+      url: '{server}/_api/v3',
+      variables: {
+        server: {
+          default: 'https://demo.growi.org',
+          description: 'The base URL for the GROWI API except for the version path (/_api/v3). This can be set to your GROWI instance URL.',
+        },
+      },
+    },
     {
       url: 'https://demo.growi.org/_api/v3',
     },

+ 1 - 1
apps/app/bin/openapi/generate-spec-apiv3.sh

@@ -19,6 +19,6 @@ swagger-jsdoc \
   "${APP_PATH}/src/server/models/openapi/**/*.{js,ts}"
 
 if [ $? -eq 0 ]; then
-  pnpm dlx tsx "${APP_PATH}/bin/openapi/generate-operation-ids/cli.ts" "${OUT}" --out "${OUT}" --overwrite-existing
+  npx tsx "${APP_PATH}/bin/openapi/generate-operation-ids/cli.ts" "${OUT}" --out "${OUT}" --overwrite-existing
   echo "OpenAPI spec generated and transformed: ${OUT}"
 fi

+ 2 - 2
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.2.6-RC.0",
+  "version": "7.2.8-RC.0",
   "license": "MIT",
   "private": "true",
   "scripts": {
@@ -170,7 +170,7 @@
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
     "mustache": "^4.2.0",
-    "next": "^14.2.26",
+    "next": "^14.2.30",
     "next-dynamic-loading-props": "^0.1.1",
     "next-i18next": "^15.3.1",
     "next-superjson": "^0.0.4",

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

@@ -40,7 +40,7 @@ const AppSetting = (props) => {
           <input
             className="form-control"
             type="text"
-            defaletValue={adminAppContainer.state.title || ''}
+            value={adminAppContainer.state.title || ''}
             onChange={(e) => {
               adminAppContainer.changeTitle(e.target.value);
             }}
@@ -60,7 +60,7 @@ const AppSetting = (props) => {
           <input
             className="form-control"
             type="text"
-            defaultValue={adminAppContainer.state.confidential || ''}
+            value={adminAppContainer.state.confidential || ''}
             onChange={(e) => {
               adminAppContainer.changeConfidential(e.target.value);
             }}

+ 4 - 4
apps/app/src/client/components/Admin/App/AwsSetting.tsx

@@ -76,7 +76,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
           <input
             className="form-control"
             placeholder={`${t('eg')} ap-northeast-1`}
-            defaultValue={props.s3Region || ''}
+            value={props.s3Region || ''}
             onChange={(e) => {
               props?.onChangeS3Region(e.target.value);
             }}
@@ -93,7 +93,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
             className="form-control"
             type="text"
             placeholder={`${t('eg')} http://localhost:9000`}
-            defaultValue={props.s3CustomEndpoint || ''}
+            value={props.s3CustomEndpoint || ''}
             onChange={(e) => {
               props?.onChangeS3CustomEndpoint(e.target.value);
             }}
@@ -111,7 +111,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
             className="form-control"
             type="text"
             placeholder={`${t('eg')} crowi`}
-            defaultValue={props.s3Bucket || ''}
+            value={props.s3Bucket || ''}
             onChange={(e) => {
               props.onChangeS3Bucket(e.target.value);
             }}
@@ -127,7 +127,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
           <input
             className="form-control"
             type="text"
-            defaultValue={props.s3AccessKeyId || ''}
+            value={props.s3AccessKeyId || ''}
             onChange={(e) => {
               props?.onChangeS3AccessKeyId(e.target.value);
             }}

+ 8 - 8
apps/app/src/client/components/Admin/App/AzureSetting.tsx

@@ -118,12 +118,12 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
               <MaskedInput
                 name="azureTenantId"
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureTenantId}
+                value={azureTenantId}
                 onChange={e => props?.onChangeAzureTenantId(e.target.value)}
               />
             </td>
             <td>
-              <MaskedInput name="envAzureTenantId" defaultValue={envAzureTenantId || ''} readOnly tabIndex={-1} />
+              <MaskedInput name="envAzureTenantId" value={envAzureTenantId || ''} readOnly tabIndex={-1} />
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_TENANT_ID' }) }} />
@@ -136,12 +136,12 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
               <MaskedInput
                 name="azureClientId"
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureClientId}
+                value={azureClientId}
                 onChange={e => props?.onChangeAzureClientId(e.target.value)}
               />
             </td>
             <td>
-              <MaskedInput name="envAzureClientId" defaultValue={envAzureClientId || ''} readOnly tabIndex={-1} />
+              <MaskedInput name="envAzureClientId" value={envAzureClientId || ''} readOnly tabIndex={-1} />
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_CLIENT_ID' }) }} />
@@ -154,12 +154,12 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
               <MaskedInput
                 name="azureClientSecret"
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureClientSecret}
+                value={azureClientSecret}
                 onChange={e => props?.onChangeAzureClientSecret(e.target.value)}
               />
             </td>
             <td>
-              <MaskedInput name="envAzureClientSecret" defaultValue={envAzureClientSecret || ''} readOnly tabIndex={-1} />
+              <MaskedInput name="envAzureClientSecret" value={envAzureClientSecret || ''} readOnly tabIndex={-1} />
               <p className="form-text text-muted">
                 {/* eslint-disable-next-line react/no-danger */}
                 <small dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'AZURE_CLIENT_SECRET' }) }} />
@@ -174,7 +174,7 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
                 type="text"
                 name="azureStorageAccountName"
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureStorageAccountName}
+                value={azureStorageAccountName}
                 onChange={e => props?.onChangeAzureStorageAccountName(e.target.value)}
               />
             </td>
@@ -194,7 +194,7 @@ export const AzureSettingMolecule = (props: AzureSettingMoleculeProps): JSX.Elem
                 type="text"
                 name="azureStorageContainerName"
                 readOnly={azureUseOnlyEnvVars}
-                defaultValue={azureStorageContainerName}
+                value={azureStorageContainerName}
                 onChange={e => props?.onChangeAzureStorageContainerName(e.target.value)}
               />
             </td>

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

@@ -108,7 +108,7 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
                 type="text"
                 name="gcsApiKeyJsonPath"
                 readOnly={gcsUseOnlyEnvVars}
-                defaultValue={gcsApiKeyJsonPath}
+                value={gcsApiKeyJsonPath}
                 onChange={e => props?.onChangeGcsApiKeyJsonPath(e.target.value)}
               />
             </td>
@@ -128,7 +128,7 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
                 type="text"
                 name="gcsBucket"
                 readOnly={gcsUseOnlyEnvVars}
-                defaultValue={gcsBucket}
+                value={gcsBucket}
                 onChange={e => props?.onChangeGcsBucket(e.target.value)}
               />
             </td>
@@ -148,7 +148,7 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
                 type="text"
                 name="gcsUploadNamespace"
                 readOnly={gcsUseOnlyEnvVars}
-                defaultValue={gcsUploadNamespace}
+                value={gcsUploadNamespace}
                 onChange={e => props?.onChangeGcsUploadNamespace(e.target.value)}
               />
             </td>

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

@@ -56,7 +56,7 @@ const MailSetting = (props: Props) => {
             className="form-control"
             type="text"
             placeholder={`${t('eg')} mail@growi.org`}
-            defaultValue={adminAppContainer.state.fromAddress || ''}
+            value={adminAppContainer.state.fromAddress || ''}
             onChange={(e) => { adminAppContainer.changeFromAddress(e.target.value) }}
           />
         </div>

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

@@ -5,7 +5,7 @@ import styles from './MaskedInput.module.scss';
 type Props = {
   name: string
   readOnly: boolean
-  defaultValue: string
+  value: string
   onChange?: (e: any) => void
   tabIndex?: number | undefined
 };
@@ -17,7 +17,7 @@ export default function MaskedInput(props: Props): JSX.Element {
   };
 
   const {
-    name, readOnly, defaultValue, onChange, tabIndex,
+    name, readOnly, value, onChange, tabIndex,
   } = props;
 
   return (
@@ -27,7 +27,7 @@ export default function MaskedInput(props: Props): JSX.Element {
         type={passwordShown ? 'text' : 'password'}
         name={name}
         readOnly={readOnly}
-        defaultValue={defaultValue}
+        value={value}
         onChange={onChange}
         tabIndex={tabIndex}
       />

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

@@ -24,7 +24,7 @@ const SmtpSetting = (props: Props) => {
             <input
               className="form-control"
               type="text"
-              defaultValue={adminAppContainer.state.sesAccessKeyId || ''}
+              value={adminAppContainer.state.sesAccessKeyId || ''}
               onChange={(e) => {
                 adminAppContainer.changeSesAccessKeyId(e.target.value);
               }}
@@ -40,7 +40,7 @@ const SmtpSetting = (props: Props) => {
             <input
               className="form-control"
               type="text"
-              defaultValue={adminAppContainer.state.sesSecretAccessKey || ''}
+              value={adminAppContainer.state.sesSecretAccessKey || ''}
               onChange={(e) => {
                 adminAppContainer.changeSesSecretAccessKey(e.target.value);
               }}

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

@@ -70,7 +70,7 @@ const SiteUrlSetting = (props: Props) => {
                   className="form-control"
                   type="text"
                   name="settingForm[app:siteUrl]"
-                  defaultValue={adminAppContainer.state.siteUrl || ''}
+                  value={adminAppContainer.state.siteUrl || ''}
                   disabled={adminAppContainer.state.siteUrlUseOnlyEnvVars ?? true}
                   onChange={(e) => { adminAppContainer.changeSiteUrl(e.target.value) }}
                   placeholder="e.g. https://my.growi.org"

+ 4 - 4
apps/app/src/client/components/Admin/App/SmtpSetting.tsx

@@ -27,7 +27,7 @@ const SmtpSetting = (props: Props) => {
             <input
               className="form-control"
               type="text"
-              defaultValue={adminAppContainer.state.smtpHost || ''}
+              value={adminAppContainer.state.smtpHost || ''}
               onChange={(e) => { adminAppContainer.changeSmtpHost(e.target.value) }}
             />
           </div>
@@ -40,7 +40,7 @@ const SmtpSetting = (props: Props) => {
           <div className="col-md-6">
             <input
               className="form-control"
-              defaultValue={adminAppContainer.state.smtpPort || ''}
+              value={adminAppContainer.state.smtpPort || ''}
               onChange={(e) => { adminAppContainer.changeSmtpPort(e.target.value) }}
             />
           </div>
@@ -54,7 +54,7 @@ const SmtpSetting = (props: Props) => {
             <input
               className="form-control"
               type="text"
-              defaultValue={adminAppContainer.state.smtpUser || ''}
+              value={adminAppContainer.state.smtpUser || ''}
               onChange={(e) => { adminAppContainer.changeSmtpUser(e.target.value) }}
             />
           </div>
@@ -68,7 +68,7 @@ const SmtpSetting = (props: Props) => {
             <input
               className="form-control"
               type="password"
-              defaultValue={adminAppContainer.state.smtpPassword || ''}
+              value={adminAppContainer.state.smtpPassword || ''}
               onChange={(e) => { adminAppContainer.changeSmtpPassword(e.target.value) }}
             />
           </div>

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

@@ -46,7 +46,7 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
               className="form-control"
               name="customizeCss"
               rows={8}
-              defaultValue={adminCustomizeContainer.state.currentCustomizeCss || ''}
+              value={adminCustomizeContainer.state.currentCustomizeCss || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeCss(e.target.value) }}
             />
           </div>

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

@@ -50,7 +50,7 @@ const CustomizeNoscriptSetting = (props: Props): JSX.Element => {
               className="form-control mb-2"
               name="customizeNoscript"
               rows={8}
-              defaultValue={adminCustomizeContainer.state.currentCustomizeNoscript || ''}
+              value={adminCustomizeContainer.state.currentCustomizeNoscript || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeNoscript(e.target.value) }}
             />
           </div>

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

@@ -47,7 +47,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
               className="form-control mb-2"
               name="customizeScript"
               rows={8}
-              defaultValue={adminCustomizeContainer.state.currentCustomizeScript || ''}
+              value={adminCustomizeContainer.state.currentCustomizeScript || ''}
               onChange={(e) => { adminCustomizeContainer.changeCustomizeScript(e.target.value) }}
             />
           </div>

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

@@ -67,7 +67,7 @@ export const CustomizeTitle: FC = () => {
         <div className="col-12">
           <input
             className="form-control"
-            defaultValue={currentCustomizeTitle}
+            value={currentCustomizeTitle}
             onChange={(e) => { setCrrentCustomizeTitle(e.target.value) }}
           />
         </div>

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

@@ -70,7 +70,7 @@ class SlackConfiguration extends React.Component {
                 <input
                   className="form-control"
                   type="text"
-                  defaultValue={adminSlackIntegrationLegacyContainer.state.webhookUrl || ''}
+                  value={adminSlackIntegrationLegacyContainer.state.webhookUrl || ''}
                   onChange={e => adminSlackIntegrationLegacyContainer.changeWebhookUrl(e.target.value)}
                 />
               </div>
@@ -122,7 +122,7 @@ class SlackConfiguration extends React.Component {
                   <input
                     className="form-control"
                     type="text"
-                    defaultValue={adminSlackIntegrationLegacyContainer.state.slackToken || ''}
+                    value={adminSlackIntegrationLegacyContainer.state.slackToken || ''}
                     onChange={e => adminSlackIntegrationLegacyContainer.changeSlackToken(e.target.value)}
                   />
                 </div>

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

@@ -52,7 +52,7 @@ export const WhitelistInput = (props: Props): JSX.Element => {
           name="recommendedTags"
           rows={6}
           cols={40}
-          defaultValue={adminMarkDownContainer.state.tagWhitelist}
+          value={adminMarkDownContainer.state.tagWhitelist}
           onChange={(e) => { adminMarkDownContainer.setState({ tagWhitelist: e.target.value }) }}
         />
       </div>
@@ -69,7 +69,7 @@ export const WhitelistInput = (props: Props): JSX.Element => {
           name="recommendedAttrs"
           rows={6}
           cols={40}
-          defaultValue={adminMarkDownContainer.state.attrWhitelist}
+          value={adminMarkDownContainer.state.attrWhitelist}
           onChange={(e) => { adminMarkDownContainer.setState({ attrWhitelist: e.target.value }) }}
         />
       </div>

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

@@ -125,7 +125,7 @@ class GitHubSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="githubClientSecret"
-                  defaultValue={adminGitHubSecurityContainer.state.githubClientSecret || ''}
+                  value={adminGitHubSecurityContainer.state.githubClientSecret || ''}
                   onChange={e => adminGitHubSecurityContainer.changeGitHubClientSecret(e.target.value)}
                 />
                 <p className="form-text text-muted">

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

@@ -108,7 +108,7 @@ class GoogleSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="googleClientId"
-                  defaultValue={adminGoogleSecurityContainer.state.googleClientId || ''}
+                  value={adminGoogleSecurityContainer.state.googleClientId || ''}
                   onChange={e => adminGoogleSecurityContainer.changeGoogleClientId(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -122,9 +122,9 @@ class GoogleSecurityManagementContents extends React.Component {
               <div className="col-6">
                 <input
                   className="form-control"
-                  type="text"
+                  type="password"
                   name="googleClientSecret"
-                  defaultValue={adminGoogleSecurityContainer.state.googleClientSecret || ''}
+                  value={adminGoogleSecurityContainer.state.googleClientSecret || ''}
                   onChange={e => adminGoogleSecurityContainer.changeGoogleClientSecret(e.target.value)}
                 />
                 <p className="form-text text-muted">

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

@@ -92,7 +92,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="serverUrl"
-                  defaultValue={adminLdapSecurityContainer.state.serverUrl || ''}
+                  value={adminLdapSecurityContainer.state.serverUrl || ''}
                   onChange={e => adminLdapSecurityContainer.changeServerUrl(e.target.value)}
                 />
                 <small>
@@ -145,7 +145,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="bindDN"
-                  defaultValue={adminLdapSecurityContainer.state.ldapBindDN || ''}
+                  value={adminLdapSecurityContainer.state.ldapBindDN || ''}
                   onChange={e => adminLdapSecurityContainer.changeBindDN(e.target.value)}
                 />
                 {(adminLdapSecurityContainer.state.isUserBind === true) ? (
@@ -194,7 +194,7 @@ class LdapSecuritySettingContents extends React.Component {
                         className="form-control passport-ldap-managerbind"
                         type="password"
                         name="bindDNPassword"
-                        defaultValue={adminLdapSecurityContainer.state.ldapBindDNPassword || ''}
+                        value={adminLdapSecurityContainer.state.ldapBindDNPassword || ''}
                         onChange={e => adminLdapSecurityContainer.changeBindDNPassword(e.target.value)}
                       />
                     </>
@@ -211,7 +211,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="searchFilter"
-                  defaultValue={adminLdapSecurityContainer.state.ldapSearchFilter || ''}
+                  value={adminLdapSecurityContainer.state.ldapSearchFilter || ''}
                   onChange={e => adminLdapSecurityContainer.changeSearchFilter(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -248,7 +248,7 @@ class LdapSecuritySettingContents extends React.Component {
                   type="text"
                   placeholder="Default: uid"
                   name="attrMapUsername"
-                  defaultValue={adminLdapSecurityContainer.state.ldapAttrMapUsername || ''}
+                  value={adminLdapSecurityContainer.state.ldapAttrMapUsername || ''}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapUsername(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -292,7 +292,7 @@ class LdapSecuritySettingContents extends React.Component {
                   type="text"
                   placeholder="Default: mail"
                   name="attrMapMail"
-                  defaultValue={adminLdapSecurityContainer.state.ldapAttrMapMail || ''}
+                  value={adminLdapSecurityContainer.state.ldapAttrMapMail || ''}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapMail(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -312,7 +312,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="attrMapName"
-                  defaultValue={adminLdapSecurityContainer.state.ldapAttrMapName || ''}
+                  value={adminLdapSecurityContainer.state.ldapAttrMapName || ''}
                   onChange={e => adminLdapSecurityContainer.changeAttrMapName(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -337,7 +337,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="groupSearchBase"
-                  defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchBase || ''}
+                  value={adminLdapSecurityContainer.state.ldapGroupSearchBase || ''}
                   onChange={e => adminLdapSecurityContainer.changeGroupSearchBase(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -359,7 +359,7 @@ class LdapSecuritySettingContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="groupSearchFilter"
-                  defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchFilter || ''}
+                  value={adminLdapSecurityContainer.state.ldapGroupSearchFilter || ''}
                   onChange={e => adminLdapSecurityContainer.changeGroupSearchFilter(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -391,7 +391,7 @@ class LdapSecuritySettingContents extends React.Component {
                   type="text"
                   placeholder="Default: uid"
                   name="groupDnProperty"
-                  defaultValue={adminLdapSecurityContainer.state.ldapGroupDnProperty || ''}
+                  value={adminLdapSecurityContainer.state.ldapGroupDnProperty || ''}
                   onChange={e => adminLdapSecurityContainer.changeGroupDnProperty(e.target.value)}
                 />
                 <p className="form-text text-muted">

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

@@ -147,7 +147,7 @@ class LocalSecuritySettingContents extends React.Component {
                   className="form-control"
                   type="textarea"
                   name="registrationWhitelist"
-                  defaultValue={adminLocalSecurityContainer.state.registrationWhitelist.join('\n')}
+                  value={adminLocalSecurityContainer.state.registrationWhitelist.join('\n')}
                   onChange={e => adminLocalSecurityContainer.changeRegistrationWhitelist(e.target.value)}
                 />
                 <p className="form-text text-muted small">

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

@@ -101,7 +101,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcProviderName"
-                  defaultValue={adminOidcSecurityContainer.state.oidcProviderName || ''}
+                  value={adminOidcSecurityContainer.state.oidcProviderName || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcProviderName(e.target.value)}
                 />
               </div>
@@ -114,7 +114,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcIssuerHost"
-                  defaultValue={adminOidcSecurityContainer.state.oidcIssuerHost || ''}
+                  value={adminOidcSecurityContainer.state.oidcIssuerHost || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcIssuerHost(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -130,7 +130,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcClientId"
-                  defaultValue={adminOidcSecurityContainer.state.oidcClientId || ''}
+                  value={adminOidcSecurityContainer.state.oidcClientId || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcClientId(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -146,7 +146,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcClientSecret"
-                  defaultValue={adminOidcSecurityContainer.state.oidcClientSecret || ''}
+                  value={adminOidcSecurityContainer.state.oidcClientSecret || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcClientSecret(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -164,7 +164,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcAuthorizationEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAuthorizationEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcAuthorizationEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAuthorizationEndpoint(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -180,7 +180,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcTokenEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcTokenEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcTokenEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcTokenEndpoint(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -198,7 +198,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcRevocationEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcRevocationEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcRevocationEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcRevocationEndpoint(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -216,7 +216,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcIntrospectionEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcIntrospectionEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcIntrospectionEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcIntrospectionEndpoint(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -234,7 +234,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcUserInfoEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcUserInfoEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcUserInfoEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcUserInfoEndpoint(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -252,7 +252,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcEndSessionEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcEndSessionEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcEndSessionEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcEndSessionEndpoint(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -270,7 +270,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcRegistrationEndpoint"
-                  defaultValue={adminOidcSecurityContainer.state.oidcRegistrationEndpoint || ''}
+                  value={adminOidcSecurityContainer.state.oidcRegistrationEndpoint || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcRegistrationEndpoint(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -286,7 +286,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcJWKSUri"
-                  defaultValue={adminOidcSecurityContainer.state.oidcJWKSUri || ''}
+                  value={adminOidcSecurityContainer.state.oidcJWKSUri || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcJWKSUri(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -306,7 +306,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcAttrMapId"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapId || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapId || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapId(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -322,7 +322,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcAttrMapUserName"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapUserName || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapUserName || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapUserName(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -338,7 +338,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcAttrMapName"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapName || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapName || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapName(e.target.value)}
                 />
                 <p className="form-text text-muted">
@@ -354,7 +354,7 @@ class OidcSecurityManagementContents extends React.Component {
                   className="form-control"
                   type="text"
                   name="oidcAttrMapEmail"
-                  defaultValue={adminOidcSecurityContainer.state.oidcAttrMapEmail || ''}
+                  value={adminOidcSecurityContainer.state.oidcAttrMapEmail || ''}
                   onChange={e => adminOidcSecurityContainer.changeOidcAttrMapEmail(e.target.value)}
                 />
                 <p className="form-text text-muted">

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

@@ -150,7 +150,7 @@ class SamlSecurityManagementContents extends React.Component {
                       type="text"
                       name="samlEntryPoint"
                       readOnly={useOnlyEnvVars}
-                      defaultValue={adminSamlSecurityContainer.state.samlEntryPoint}
+                      value={adminSamlSecurityContainer.state.samlEntryPoint}
                       onChange={e => adminSamlSecurityContainer.changeSamlEntryPoint(e.target.value)}
                     />
                   </td>
@@ -174,7 +174,7 @@ class SamlSecurityManagementContents extends React.Component {
                       type="text"
                       name="samlEnvVarissuer"
                       readOnly={useOnlyEnvVars}
-                      defaultValue={adminSamlSecurityContainer.state.samlIssuer}
+                      value={adminSamlSecurityContainer.state.samlIssuer}
                       onChange={e => adminSamlSecurityContainer.changeSamlIssuer(e.target.value)}
                     />
                   </td>
@@ -199,7 +199,7 @@ class SamlSecurityManagementContents extends React.Component {
                       rows="5"
                       name="samlCert"
                       readOnly={useOnlyEnvVars}
-                      defaultValue={adminSamlSecurityContainer.state.samlCert}
+                      value={adminSamlSecurityContainer.state.samlCert}
                       onChange={e => adminSamlSecurityContainer.changeSamlCert(e.target.value)}
                     />
                     <p>
@@ -258,7 +258,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapId}
+                      value={adminSamlSecurityContainer.state.samlAttrMapId}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapId(e.target.value)}
                     />
                     <p className="form-text text-muted">
@@ -285,7 +285,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapUsername}
+                      value={adminSamlSecurityContainer.state.samlAttrMapUsername}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapUserName(e.target.value)}
                     />
                     <p className="form-text text-muted">
@@ -310,7 +310,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapMail}
+                      value={adminSamlSecurityContainer.state.samlAttrMapMail}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapMail(e.target.value)}
                     />
                     <p className="form-text text-muted">
@@ -335,7 +335,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapFirstName}
+                      value={adminSamlSecurityContainer.state.samlAttrMapFirstName}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapFirstName(e.target.value)}
                     />
                     <p className="form-text text-muted">
@@ -365,7 +365,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapLastName}
+                      value={adminSamlSecurityContainer.state.samlAttrMapLastName}
                       onChange={e => adminSamlSecurityContainer.changeSamlAttrMapLastName(e.target.value)}
                     />
                     <p className="form-text text-muted">
@@ -462,7 +462,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <textarea
                       className="form-control"
                       type="text"
-                      defaultValue={adminSamlSecurityContainer.state.samlABLCRule || ''}
+                      value={adminSamlSecurityContainer.state.samlABLCRule || ''}
                       onChange={(e) => { adminSamlSecurityContainer.changeSamlABLCRule(e.target.value) }}
                     />
                     <div className="mt-2">

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

@@ -591,7 +591,7 @@ class SecuritySetting extends React.Component {
             <input
               className="form-control col-md-4"
               type="text"
-              defaultValue={adminGeneralSecurityContainer.state.sessionMaxAge || ''}
+              value={adminGeneralSecurityContainer.state.sessionMaxAge || ''}
               onChange={(e) => {
                 adminGeneralSecurityContainer.setSessionMaxAge(e.target.value);
               }}

+ 1 - 1
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx

@@ -111,7 +111,7 @@ const CustomBotWithProxySettings = (props) => {
                 className="form-control"
                 type="text"
                 name="settingForm[proxyUrl]"
-                defaultValue={newProxyServerUri}
+                value={newProxyServerUri}
                 onChange={(e) => { setNewProxyServerUri(e.target.value) }}
               />
             </div>

+ 2 - 2
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx

@@ -65,7 +65,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
           <input
             className="form-control"
             type="text"
-            defaultValue={slackSigningSecretEnv}
+            value={slackSigningSecretEnv}
             readOnly
           />
           <p className="form-text text-muted">
@@ -94,7 +94,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
           <input
             className="form-control"
             type="text"
-            defaultValue={slackBotTokenEnv}
+            value={slackBotTokenEnv}
             readOnly
           />
           <p className="form-text text-muted">

+ 1 - 1
apps/app/src/client/components/Admin/SlackIntegration/ManageCommandsProcess.jsx

@@ -147,7 +147,7 @@ const PermissionSettingForEachPermissionTypeComponent = ({
             className="form-control"
             type="textarea"
             name={keyName}
-            defaultValue={textareaDefaultValue}
+            value={textareaDefaultValue}
             onChange={onUpdateChannels}
           />
           <p className="form-text text-muted small">

+ 0 - 1
apps/app/src/client/components/Sidebar/InAppNotification/PrimaryItemForNotification.tsx

@@ -8,7 +8,6 @@ import { PrimaryItem, type PrimaryItemProps } from '../SidebarNav/PrimaryItem';
 
 type PrimaryItemForNotificationProps = Omit<PrimaryItemProps, 'onClick' | 'label' | 'iconName' | 'contents' | 'badgeContents' >
 
-// TODO(after v7 release): https://redmine.weseek.co.jp/issues/138463
 export const PrimaryItemForNotification = memo((props: PrimaryItemForNotificationProps) => {
   const { sidebarMode, onHover } = props;
 

+ 15 - 4
apps/app/src/client/components/Sidebar/SidebarContents.tsx

@@ -2,6 +2,7 @@ import React, { memo, useMemo } from 'react';
 
 import { AiAssistant } from '~/features/openai/client/components/AiAssistant/Sidebar/AiAssistant';
 import { SidebarContentsType } from '~/interfaces/ui';
+import { useIsAiEnabled, useIsGuestUser } from '~/stores-universal/context';
 import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui';
 
 
@@ -17,8 +18,10 @@ import styles from './SidebarContents.module.scss';
 
 export const SidebarContents = memo(() => {
   const { isCollapsedMode } = useSidebarMode();
-  const { data: isCollapsedContentsOpened } = useCollapsedContentsOpened();
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isAiEnabled } = useIsAiEnabled();
 
+  const { data: isCollapsedContentsOpened } = useCollapsedContentsOpened();
   const { data: currentSidebarContents } = useCurrentSidebarContents();
 
   const Contents = useMemo(() => {
@@ -32,13 +35,21 @@ export const SidebarContents = memo(() => {
       case SidebarContentsType.BOOKMARKS:
         return Bookmarks;
       case SidebarContentsType.NOTIFICATION:
-        return InAppNotification;
+        if (isGuestUser == null) return () => <></>; // wait for isGuestUser to be determined
+        if (!isGuestUser) {
+          return InAppNotification;
+        }
+        return PageTree;
       case SidebarContentsType.AI_ASSISTANT:
-        return AiAssistant;
+        if (isAiEnabled == null) return () => <></>; // wait for isAiEnabled to be determined
+        if (isAiEnabled) {
+          return AiAssistant;
+        }
+        return PageTree;
       default:
         return PageTree;
     }
-  }, [currentSidebarContents]);
+  }, [currentSidebarContents, isAiEnabled, isGuestUser]);
 
   const isHidden = isCollapsedMode() && !isCollapsedContentsOpened;
   const classToHide = isHidden ? 'd-none' : '';

+ 3 - 2
apps/app/src/client/components/Sidebar/SidebarNav/PrimaryItems.tsx

@@ -3,7 +3,7 @@ import { memo } from 'react';
 import dynamic from 'next/dynamic';
 
 import { SidebarContentsType } from '~/interfaces/ui';
-import { useIsAiEnabled } from '~/stores-universal/context';
+import { useIsAiEnabled, useIsGuestUser } from '~/stores-universal/context';
 import { useSidebarMode } from '~/stores/ui';
 
 import { PrimaryItem } from './PrimaryItem';
@@ -24,6 +24,7 @@ export const PrimaryItems = memo((props: Props) => {
 
   const { data: sidebarMode } = useSidebarMode();
   const { data: isAiEnabled } = useIsAiEnabled();
+  const { data: isGuestUser } = useIsGuestUser();
 
   if (sidebarMode == null) {
     return <></>;
@@ -36,7 +37,7 @@ export const PrimaryItems = memo((props: Props) => {
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.RECENT} label="Recent Changes" iconName="update" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmarks" onHover={onItemHover} />
       <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" onHover={onItemHover} />
-      <PrimaryItemForNotification sidebarMode={sidebarMode} onHover={onItemHover} />
+      {isGuestUser === false && <PrimaryItemForNotification sidebarMode={sidebarMode} onHover={onItemHover} />}
       {isAiEnabled && (
         <PrimaryItem
           sidebarMode={sidebarMode}

+ 4 - 2
apps/app/src/client/services/page-operation.ts

@@ -5,6 +5,7 @@ import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 
 import type { SyncLatestRevisionBody } from '~/interfaces/yjs';
+import { useIsGuestUser } from '~/stores-universal/context';
 import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
 import {
   useCurrentPageId, useSWRMUTxCurrentPage, useSWRxApplicableGrant, useSWRxTagsInfo,
@@ -103,8 +104,9 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
   const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
   const { sync: syncTagsInfoForEditor } = usePageTagsForEditors(pageId);
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
-  const { mutate: mutateCurrentGrantData } = useSWRxCurrentGrantData(pageId);
-  const { mutate: mutateApplicableGrant } = useSWRxApplicableGrant(pageId);
+  const { data: isGuestUser } = useIsGuestUser();
+  const { mutate: mutateCurrentGrantData } = useSWRxCurrentGrantData(isGuestUser ? null : pageId);
+  const { mutate: mutateApplicableGrant } = useSWRxApplicableGrant(isGuestUser ? null : pageId);
 
   // update swr 'currentPageId', 'currentPage', remote states
   return useCallback(async() => {

+ 1 - 1
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx

@@ -29,7 +29,7 @@ import {
 import { useAiAssistantSidebar } from '../../../stores/ai-assistant';
 import { useSWRxThreads } from '../../../stores/thread';
 
-import { MessageCard } from './MessageCard';
+import { MessageCard } from './MessageCard/MessageCard';
 import { ResizableTextarea } from './ResizableTextArea';
 
 import styles from './AiAssistantSidebar.module.scss';

+ 0 - 0
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard.module.scss → apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/MessageCard.module.scss


+ 13 - 10
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard.tsx → apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/MessageCard.tsx

@@ -1,10 +1,10 @@
 import { type JSX } from 'react';
 
-import type { LinkProps } from 'next/link';
 import { useTranslation } from 'react-i18next';
 import ReactMarkdown from 'react-markdown';
 
-import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
+import { Header } from './ReactMarkdownComponents/Header';
+import { NextLinkWrapper } from './ReactMarkdownComponents/NextLinkWrapper';
 
 import styles from './MessageCard.module.scss';
 
@@ -24,13 +24,6 @@ const UserMessageCard = ({ children }: { children: string }): JSX.Element => (
 
 const assistantMessageCardModuleClass = styles['assistant-message-card'] ?? '';
 
-const NextLinkWrapper = (props: LinkProps & {children: string, href: string}): JSX.Element => {
-  return (
-    <NextLink href={props.href} className="link-primary">
-      {props.children}
-    </NextLink>
-  );
-};
 
 const AssistantMessageCard = ({
   children,
@@ -51,7 +44,17 @@ const AssistantMessageCard = ({
           { children.length > 0
             ? (
               <>
-                <ReactMarkdown components={{ a: NextLinkWrapper }}>{children}</ReactMarkdown>
+                <ReactMarkdown components={{
+                  a: NextLinkWrapper,
+                  h1: ({ children }) => <Header level={1}>{children}</Header>,
+                  h2: ({ children }) => <Header level={2}>{children}</Header>,
+                  h3: ({ children }) => <Header level={3}>{children}</Header>,
+                  h4: ({ children }) => <Header level={4}>{children}</Header>,
+                  h5: ({ children }) => <Header level={5}>{children}</Header>,
+                  h6: ({ children }) => <Header level={6}>{children}</Header>,
+                }}
+                >{children}
+                </ReactMarkdown>
                 { additionalItem }
               </>
             )

+ 25 - 0
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/ReactMarkdownComponents/Header.tsx

@@ -0,0 +1,25 @@
+type Level = 1 | 2 | 3 | 4 | 5 | 6;
+
+const fontSizes: Record<Level, string> = {
+  1: '1.5rem',
+  2: '1.25rem',
+  3: '1rem',
+  4: '0.875rem',
+  5: '0.75rem',
+  6: '0.625rem',
+};
+
+export const Header = ({ children, level }: { children: React.ReactNode, level: Level}): JSX.Element => {
+  const Tag = `h${level}` as keyof JSX.IntrinsicElements;
+
+  return (
+    <Tag
+      style={{
+        fontSize: fontSizes[level],
+        lineHeight: 1.4,
+      }}
+    >
+      {children}
+    </Tag>
+  );
+};

+ 13 - 0
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/MessageCard/ReactMarkdownComponents/NextLinkWrapper.tsx

@@ -0,0 +1,13 @@
+import React from 'react';
+
+import type { LinkProps } from 'next/link';
+
+import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
+
+export const NextLinkWrapper = (props: LinkProps & {children: React.ReactNode, href: string}): JSX.Element => {
+  return (
+    <NextLink href={props.href} className="link-primary">
+      {props.children}
+    </NextLink>
+  );
+};

+ 11 - 6
apps/app/src/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton.tsx

@@ -10,9 +10,8 @@ import { useAiAssistantSidebar, useSWRxAiAssistants } from '../../stores/ai-assi
 
 import styles from './OpenDefaultAiAssistantButton.module.scss';
 
-const OpenDefaultAiAssistantButton = (): JSX.Element => {
+const OpenDefaultAiAssistantButtonSubstance = (): JSX.Element => {
   const { t } = useTranslation();
-  const { data: isAiEnabled } = useIsAiEnabled();
   const { data: aiAssistantData } = useSWRxAiAssistants();
   const { openChat } = useAiAssistantSidebar();
 
@@ -33,10 +32,6 @@ const OpenDefaultAiAssistantButton = (): JSX.Element => {
     openChat(defaultAiAssistant);
   }, [defaultAiAssistant, openChat]);
 
-  if (!isAiEnabled) {
-    return <></>;
-  }
-
   return (
     <NotAvailableForGuest>
       <NotAvailable isDisabled={defaultAiAssistant == null} title={t('default_ai_assistant.not_set')}>
@@ -52,4 +47,14 @@ const OpenDefaultAiAssistantButton = (): JSX.Element => {
   );
 };
 
+const OpenDefaultAiAssistantButton = (): JSX.Element => {
+  const { data: isAiEnabled } = useIsAiEnabled();
+
+  if (!isAiEnabled) {
+    return <></>;
+  }
+
+  return <OpenDefaultAiAssistantButtonSubstance />;
+};
+
 export default OpenDefaultAiAssistantButton;

+ 57 - 23
apps/app/src/features/openai/server/routes/edit/index.ts

@@ -69,29 +69,63 @@ const withMarkdownCaution = `# IMPORTANT:
 `;
 
 function instruction(withMarkdown: boolean): string {
-  return `# RESPONSE FORMAT:
-You must respond with a JSON object in the following format example:
-{
-  "contents": [
-    { "message": "Your brief message about the upcoming change or proposal.\n\n" },
-    { "replace": "New text 1" },
-    { "message": "Additional explanation if needed" },
-    { "replace": "New text 2" },
-    ...more items if needed
-    { "message": "Your friendly message explaining what changes were made or suggested." }
-  ]
-}
-
-The array should contain:
-- [At the beginning of the list] A "message" object that has your brief message about the upcoming change or proposal. Be sure to add two consecutive line feeds ('\n\n') at the end.
-- Objects with a "message" key for explanatory text to the user if needed.
-- Edit markdown according to user instructions and include it line by line in the 'replace' object. ${withMarkdown ? 'Return original text for lines that do not need editing.' : ''}
-- [At the end of the list] A "message" object that contains your friendly message explaining that the operation was completed and what changes were made.
-
-${withMarkdown ? withMarkdownCaution : ''}
-
-# Multilingual Support:
-Always provide messages in the same language as the user's request.`;
+  return `
+  # USER INTENT DETECTION:
+  First, analyze the user's message to determine their intent:
+  - **Consultation Type**: Questions, discussions, explanations, or advice seeking WITHOUT explicit request to edit/modify/generate text
+  - **Edit Type**: Clear requests to edit, modify, fix, generate, create, or write content
+
+  ## EXAMPLES OF USER INTENT:
+  ### Consultation Type Examples:
+  - "What do you think about this code?"
+  - "Please give me advice on this text structure"
+  - "Why is this error occurring?"
+  - "Is there a better approach?"
+  - "Can you explain how this works?"
+  - "What are the pros and cons of this method?"
+  - "How should I organize this document?"
+
+  ### Edit Type Examples:
+  - "Please fix the following"
+  - "Add a function that..."
+  - "Rewrite this section to..."
+  - "Correct the errors in this code"
+  - "Generate a new paragraph about..."
+  - "Modify this to include..."
+  - "Create a template for..."
+
+  # RESPONSE FORMAT:
+  ## For Consultation Type (discussion/advice only):
+  Respond with a JSON object containing ONLY message objects:
+  {
+    "contents": [
+      { "message": "Your thoughtful response to the user's question or consultation.\n\nYou can use multiple paragraphs as needed." }
+    ]
+  }
+
+  ## For Edit Type (explicit editing request):
+  Respond with a JSON object in the following format:
+  {
+    "contents": [
+      { "message": "Your brief message about the upcoming change or proposal.\n\n" },
+      { "replace": "New text 1" },
+      { "message": "Additional explanation if needed" },
+      { "replace": "New text 2" },
+      ...more items if needed
+      { "message": "Your friendly message explaining what changes were made or suggested." }
+    ]
+  }
+
+  The array should contain:
+  - [At the beginning of the list] A "message" object that has your brief message about the upcoming change or proposal. Be sure to add two consecutive line feeds ('\n\n') at the end.
+  - Objects with a "message" key for explanatory text to the user if needed.
+  - Edit markdown according to user instructions and include it line by line in the 'replace' object. ${withMarkdown ? 'Return original text for lines that do not need editing.' : ''}
+  - [At the end of the list] A "message" object that contains your friendly message explaining that the operation was completed and what changes were made.
+
+  ${withMarkdown ? withMarkdownCaution : ''}
+
+  # Multilingual Support:
+  Always provide messages in the same language as the user's request.`;
 }
 /* eslint-disable max-len */
 

+ 0 - 20
apps/app/src/server/models/openapi/v1-response.js

@@ -1,20 +0,0 @@
-/**
- * @swagger
- *
- *  components:
- *    schemas:
- *      V1ResponseOK:
- *        description: API is succeeded
- *        type: boolean
- *      V1Response:
- *        description: Response v1
- *        type: object
- *        properties:
- *          ok:
- *            $ref: '#/components/schemas/V1ResponseOK'
- *    responses:
- *      403:
- *        description: 'Forbidden'
- *      500:
- *        description: 'Internal Server Error'
- */

+ 131 - 0
apps/app/src/server/models/openapi/v1-response.ts

@@ -0,0 +1,131 @@
+/**
+ * @swagger
+ *
+ *  components:
+ *    schemas:
+ *
+ *      # Common API Response Schemas (modern pattern)
+ *      ApiResponseBase:
+ *        type: object
+ *        required:
+ *          - ok
+ *        properties:
+ *          ok:
+ *            type: boolean
+ *            description: Indicates if the request was successful
+ *
+ *      ApiResponseSuccess:
+ *        description: Successful API response
+ *        allOf:
+ *          - $ref: '#/components/schemas/ApiResponseBase'
+ *          - type: object
+ *            properties:
+ *              ok:
+ *                type: boolean
+ *                enum: [true]
+ *                example: true
+ *                description: Success indicator (always true for successful responses)
+ *
+ *      ApiResponseError:
+ *        description: Error API response
+ *        allOf:
+ *          - $ref: '#/components/schemas/ApiResponseBase'
+ *          - type: object
+ *            properties:
+ *              ok:
+ *                type: boolean
+ *                enum: [false]
+ *                example: false
+ *                description: Success indicator (always false for error responses)
+ *              error:
+ *                oneOf:
+ *                  - type: string
+ *                    description: Simple error message
+ *                    example: "Invalid parameter"
+ *                  - type: object
+ *                    description: Detailed error object
+ *                    example: { "code": "VALIDATION_ERROR", "message": "Field validation failed" }
+ *                description: Error message or error object containing details about the failure
+ *
+ *    responses:
+ *      # Common error responses
+ *      BadRequest:
+ *        description: Bad request
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            examples:
+ *              missingParameter:
+ *                summary: Missing required parameter
+ *                value:
+ *                  ok: false
+ *                  error: "Invalid parameter"
+ *              validationError:
+ *                summary: Validation error
+ *                value:
+ *                  ok: false
+ *                  error: "Validation failed"
+ *
+ *      Forbidden:
+ *        description: Forbidden - insufficient permissions
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            example:
+ *              ok: false
+ *              error: "Access denied"
+ *
+ *      NotFound:
+ *        description: Resource not found
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            examples:
+ *              resourceNotFound:
+ *                summary: Resource not found
+ *                value:
+ *                  ok: false
+ *                  error: "Resource not found"
+ *              notFoundOrForbidden:
+ *                summary: Resource not found or forbidden
+ *                value:
+ *                  ok: false
+ *                  error: "notfound_or_forbidden"
+ *
+ *      Conflict:
+ *        description: Conflict
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            examples:
+ *              resourceConflict:
+ *                summary: Resource conflict
+ *                value:
+ *                  ok: false
+ *                  error: "Resource conflict"
+ *              outdated:
+ *                summary: Resource was updated by someone else
+ *                value:
+ *                  ok: false
+ *                  error: "outdated"
+ *              alreadyExists:
+ *                summary: Resource already exists
+ *                value:
+ *                  ok: false
+ *                  error: "already_exists"
+ *
+ *      InternalServerError:
+ *        description: Internal server error
+ *        content:
+ *          application/json:
+ *            schema:
+ *              $ref: '#/components/schemas/ApiResponseError'
+ *            example:
+ *              ok: false
+ *              error: "Internal server error"
+ *
+ */

+ 79 - 31
apps/app/src/server/routes/apiv3/app-settings.js

@@ -1,4 +1,6 @@
-import { ConfigSource } from '@growi/core/dist/interfaces';
+import {
+  ConfigSource, toNonBlankString, toNonBlankStringOrUndefined,
+} from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import { body } from 'express-validator';
 
@@ -367,6 +369,7 @@ module.exports = (crowi) => {
       body('gcsBucket').trim(),
       body('gcsUploadNamespace').trim(),
       body('gcsReferenceFileWithRelayMode').if(value => value != null).isBoolean(),
+      body('s3Bucket').trim(),
       body('s3Region')
         .trim()
         .if(value => value !== '')
@@ -387,7 +390,6 @@ module.exports = (crowi) => {
           }
           return true;
         }),
-      body('s3Bucket').trim(),
       body('s3AccessKeyId').trim().if(value => value !== '').matches(/^[\da-zA-Z]+$/),
       body('s3SecretAccessKey').trim(),
       body('s3ReferenceFileWithRelayMode').if(value => value != null).isBoolean(),
@@ -888,42 +890,88 @@ module.exports = (crowi) => {
   router.put('/file-upload-setting', loginRequiredStrictly, adminRequired, addActivity, validator.fileUploadSetting, apiV3FormValidator, async(req, res) => {
     const { fileUploadType } = req.body;
 
-    const requestParams = {
-      'app:fileUploadType': fileUploadType,
-    };
-
-    if (fileUploadType === 'gcs') {
-      requestParams['gcs:apiKeyJsonPath'] = req.body.gcsApiKeyJsonPath;
-      requestParams['gcs:bucket'] = req.body.gcsBucket;
-      requestParams['gcs:uploadNamespace'] = req.body.gcsUploadNamespace;
-      requestParams['gcs:referenceFileWithRelayMode'] = req.body.gcsReferenceFileWithRelayMode;
+    if (fileUploadType === 'aws') {
+      try {
+        try {
+          toNonBlankString(req.body.s3Bucket);
+        }
+        catch (err) {
+          throw new Error('S3 Bucket name is required');
+        }
+        try {
+          toNonBlankString(req.body.s3Region);
+        }
+        catch (err) {
+          throw new Error('S3 Region is required');
+        }
+        await configManager.updateConfigs({
+          'app:fileUploadType': fileUploadType,
+          'aws:s3Region': toNonBlankString(req.body.s3Region),
+          'aws:s3Bucket': toNonBlankString(req.body.s3Bucket),
+          'aws:referenceFileWithRelayMode': req.body.s3ReferenceFileWithRelayMode,
+        },
+        { skipPubsub: true });
+        await configManager.updateConfigs({
+          'aws:s3CustomEndpoint': toNonBlankStringOrUndefined(req.body.s3CustomEndpoint),
+          'aws:s3AccessKeyId': toNonBlankStringOrUndefined(req.body.s3AccessKeyId),
+          'aws:s3SecretAccessKey': toNonBlankStringOrUndefined(req.body.s3SecretAccessKey),
+        },
+        {
+          skipPubsub: true,
+          removeIfUndefined: true,
+        });
+      }
+      catch (err) {
+        const msg = `Error occurred in updating AWS S3 settings: ${err.message}`;
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-fileUploadType-failed'));
+      }
     }
 
-    if (fileUploadType === 'aws') {
-      requestParams['aws:s3Region'] = req.body.s3Region;
-      requestParams['aws:s3CustomEndpoint'] = req.body.s3CustomEndpoint;
-      requestParams['aws:s3Bucket'] = req.body.s3Bucket;
-      requestParams['aws:s3AccessKeyId'] = req.body.s3AccessKeyId;
-      requestParams['aws:referenceFileWithRelayMode'] = req.body.s3ReferenceFileWithRelayMode;
+    if (fileUploadType === 'gcs') {
+      try {
+        await configManager.updateConfigs({
+          'app:fileUploadType': fileUploadType,
+          'gcs:referenceFileWithRelayMode': req.body.gcsReferenceFileWithRelayMode,
+        },
+        { skipPubsub: true });
+        await configManager.updateConfigs({
+          'gcs:apiKeyJsonPath': toNonBlankStringOrUndefined(req.body.gcsApiKeyJsonPath),
+          'gcs:bucket': toNonBlankStringOrUndefined(req.body.gcsBucket),
+          'gcs:uploadNamespace': toNonBlankStringOrUndefined(req.body.gcsUploadNamespace),
+        },
+        { skipPubsub: true, removeIfUndefined: true });
+      }
+      catch (err) {
+        const msg = `Error occurred in updating GCS settings: ${err.message}`;
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-fileUploadType-failed'));
+      }
     }
 
     if (fileUploadType === 'azure') {
-      requestParams['azure:tenantId'] = req.body.azureTenantId;
-      requestParams['azure:clientId'] = req.body.azureClientId;
-      requestParams['azure:clientSecret'] = req.body.azureClientSecret;
-      requestParams['azure:storageAccountName'] = req.body.azureStorageAccountName;
-      requestParams['azure:storageContainerName'] = req.body.azureStorageContainerName;
-      requestParams['azure:referenceFileWithRelayMode'] = req.body.azureReferenceFileWithRelayMode;
+      try {
+        await configManager.updateConfigs({
+          'app:fileUploadType': fileUploadType,
+          'azure:referenceFileWithRelayMode': req.body.azureReferenceFileWithRelayMode,
+        },
+        { skipPubsub: true });
+        await configManager.updateConfigs({
+          'azure:tenantId': toNonBlankStringOrUndefined(req.body.azureTenantId),
+          'azure:clientId': toNonBlankStringOrUndefined(req.body.azureClientId),
+          'azure:clientSecret': toNonBlankStringOrUndefined(req.body.azureClientSecret),
+          'azure:storageAccountName': toNonBlankStringOrUndefined(req.body.azureStorageAccountName),
+          'azure:storageContainerName': toNonBlankStringOrUndefined(req.body.azureStorageContainerName),
+        }, { skipPubsub: true, removeIfUndefined: true });
+      }
+      catch (err) {
+        const msg = `Error occurred in updating Azure settings: ${err.message}`;
+        logger.error('Error', err);
+        return res.apiv3Err(new ErrorV3(msg, 'update-fileUploadType-failed'));
+      }
     }
 
     try {
-      await configManager.updateConfigs(requestParams, { skipPubsub: true });
-
-      const s3SecretAccessKey = req.body.s3SecretAccessKey;
-      if (fileUploadType === 'aws' && s3SecretAccessKey != null && s3SecretAccessKey.trim() !== '') {
-        await configManager.updateConfigs({ 'aws:s3SecretAccessKey': s3SecretAccessKey }, { skipPubsub: true });
-      }
-
       await crowi.setUpFileUpload(true);
       crowi.fileUploaderSwitchService.publishUpdatedMessage();
 
@@ -959,7 +1007,7 @@ module.exports = (crowi) => {
       return res.apiv3({ responseParams });
     }
     catch (err) {
-      const msg = 'Error occurred in updating fileUploadType';
+      const msg = 'Error occurred in retrieving file upload configurations';
       logger.error('Error', err);
       return res.apiv3Err(new ErrorV3(msg, 'update-fileUploadType-failed'));
     }

+ 4 - 4
apps/app/src/server/routes/apiv3/attachment.js

@@ -267,9 +267,9 @@ module.exports = (crowi) => {
    *                      description: uploadable
    *                      example: true
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   router.get('/limit', accessTokenParser, loginRequiredStrictly, validator.retrieveFileLimit, apiV3FormValidator, async(req, res) => {
     const { fileUploadService } = crowi;
@@ -333,9 +333,9 @@ module.exports = (crowi) => {
    *                    revision:
    *                      type: string
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   router.post('/', accessTokenParser, loginRequiredStrictly, excludeReadOnlyUser, uploads.single('file'),
     validator.retrieveAddAttachment, apiV3FormValidator, addActivity,

+ 2 - 2
apps/app/src/server/routes/apiv3/page/index.ts

@@ -409,9 +409,9 @@ module.exports = (crowi) => {
    *                        revision:
    *                          $ref: '#/components/schemas/Revision'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   router.put('/', updatePageHandlersFactory(crowi));
 

+ 10 - 10
apps/app/src/server/routes/apiv3/security-settings/index.js

@@ -1,4 +1,4 @@
-import { ConfigSource } from '@growi/core/dist/interfaces';
+import { ConfigSource, toNonBlankStringOrUndefined } from '@growi/core/dist/interfaces';
 import { ErrorV3 } from '@growi/core/dist/models';
 import xss from 'xss';
 
@@ -407,11 +407,11 @@ module.exports = (crowi) => {
 
   const activityEvent = crowi.event('activity');
 
-  async function updateAndReloadStrategySettings(authId, params) {
+  async function updateAndReloadStrategySettings(authId, params, opts = { removeIfUndefined: false }) {
     const { passportService } = crowi;
 
     // update config without publishing S2sMessage
-    await configManager.updateConfigs(params, { skipPubsub: true });
+    await configManager.updateConfigs(params, { skipPubsub: true, removeIfUndefined: opts.removeIfUndefined });
 
     await passportService.setupStrategyById(authId);
     passportService.publishUpdatedMessage(authId);
@@ -1262,15 +1262,15 @@ module.exports = (crowi) => {
    *                      $ref: '#/components/schemas/GoogleOAuthSetting'
    */
   router.put('/google-oauth', loginRequiredStrictly, adminRequired, addActivity, validator.googleOAuth, apiV3FormValidator, async(req, res) => {
-    const requestParams = {
-      'security:passport-google:clientId': req.body.googleClientId,
-      'security:passport-google:clientSecret': req.body.googleClientSecret,
-      'security:passport-google:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
-    };
-
 
     try {
-      await updateAndReloadStrategySettings('google', requestParams);
+      await updateAndReloadStrategySettings('google', {
+        'security:passport-google:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
+      });
+      await updateAndReloadStrategySettings('google', {
+        'security:passport-google:clientId': toNonBlankStringOrUndefined(req.body.googleClientId),
+        'security:passport-google:clientSecret': toNonBlankStringOrUndefined(req.body.googleClientSecret),
+      }, { removeIfUndefined: true });
 
       const securitySettingParams = {
         googleClientId: await configManager.getConfig('security:passport-google:clientId'),

+ 2 - 2
apps/app/src/server/routes/apiv3/users.js

@@ -1212,9 +1212,9 @@ module.exports = (crowi) => {
    *                          $ref: '#/components/schemas/User'
    *                        description: user list
    *            403:
-   *              $ref: '#/components/responses/403'
+   *              $ref: '#/components/responses/Forbidden'
    *            500:
-   *              $ref: '#/components/responses/500'
+   *              $ref: '#/components/responses/InternalServerError'
    */
   router.get('/list', accessTokenParser, loginRequired, async(req, res) => {
     const userIds = req.query.userIds ?? null;

+ 15 - 18
apps/app/src/server/routes/attachment/api.js

@@ -1,4 +1,3 @@
-
 import { SupportedAction } from '~/interfaces/activity';
 import { AttachmentType } from '~/server/interfaces/attachment';
 import loggerFactory from '~/utils/logger';
@@ -220,15 +219,17 @@ export const routesFactory = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    attachment:
-   *                      $ref: '#/components/schemas/AttachmentProfile'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        attachment:
+   *                          $ref: '#/components/schemas/AttachmentProfile'
+   *                          description: The uploaded profile image attachment
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /attachments.uploadProfileImage Add attachment for profile image
@@ -298,13 +299,11 @@ export const routesFactory = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
+   *                  $ref: '#/components/schemas/ApiResponseSuccess'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /attachments.remove Remove attachments
@@ -363,13 +362,11 @@ export const routesFactory = (crowi) => {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
+   *                  $ref: '#/components/schemas/ApiResponseSuccess'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /attachments.removeProfileImage Remove profile image attachments

+ 32 - 31
apps/app/src/server/routes/comment.js

@@ -1,4 +1,3 @@
-
 import { getIdStringForRef } from '@growi/core';
 import { serializeUserSecurely } from '@growi/core/dist/models/serializers';
 
@@ -101,17 +100,19 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comments:
-   *                      type: array
-   *                      items:
-   *                        $ref: '#/components/schemas/Comment'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        comments:
+   *                          type: array
+   *                          items:
+   *                            $ref: '#/components/schemas/Comment'
+   *                          description: List of comments for the page revision
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /comments.get Get comments of the page of the revision
@@ -207,15 +208,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comment:
-   *                      $ref: '#/components/schemas/Comment'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        comment:
+   *                          $ref: '#/components/schemas/Comment'
+   *                          description: The newly created comment
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /comments.add Post comment for the page
@@ -353,15 +356,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comment:
-   *                      $ref: '#/components/schemas/Comment'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        comment:
+   *                          $ref: '#/components/schemas/Comment'
+   *                          description: The updated comment
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /comments.update Update comment dody
@@ -444,15 +449,11 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    comment:
-   *                      $ref: '#/components/schemas/Comment'
+   *                  $ref: '#/components/schemas/ApiResponseSuccess'
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /comments.remove Remove specified comment

+ 332 - 117
apps/app/src/server/routes/page.js

@@ -14,54 +14,6 @@ import UpdatePost from '../models/update-post';
  *    name: Pages
  */
 
-/**
- * @swagger
- *
- *  components:
- *    schemas:
- *
- *      UpdatePost:
- *        description: UpdatePost
- *        type: object
- *        properties:
- *          _id:
- *            type: string
- *            description: update post ID
- *            example: 5e0734e472560e001761fa68
- *          __v:
- *            type: number
- *            description: DB record version
- *            example: 0
- *          pathPattern:
- *            type: string
- *            description: path pattern
- *            example: /test
- *          patternPrefix:
- *            type: string
- *            description: patternPrefix prefix
- *            example: /
- *          patternPrefix2:
- *            type: string
- *            description: path
- *            example: test
- *          channel:
- *            type: string
- *            description: channel
- *            example: general
- *          provider:
- *            type: string
- *            description: provider
- *            enum:
- *              - slack
- *            example: slack
- *          creator:
- *            $ref: '#/components/schemas/User'
- *          createdAt:
- *            type: string
- *            description: date created at
- *            example: 2010-01-01T00:00:00.000Z
- */
-
 /* eslint-disable no-use-before-define */
 /** @param {import('~/server/crowi').default} crowi Crowi instance */
 module.exports = function(crowi, app) {
@@ -134,37 +86,57 @@ module.exports = function(crowi, app) {
   const validator = {};
 
   actions.api = api;
-  actions.validator = validator;
-
-  /**
+  actions.validator = validator; /**
    * @swagger
    *
-   *    /pages.getPageTag:
-   *      get:
-   *        tags: [Pages]
-   *        operationId: getPageTag
-   *        summary: /pages.getPageTag
-   *        description: Get page tag
-   *        parameters:
-   *          - in: query
-   *            name: pageId
-   *            schema:
-   *              $ref: '#/components/schemas/ObjectId'
-   *        responses:
-   *          200:
-   *            description: Succeeded to get page tags.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    tags:
-   *                      $ref: '#/components/schemas/Tags'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
+   * components:
+   *   schemas:
+   *     PageTagsData:
+   *       type: object
+   *       properties:
+   *         tags:
+   *           type: array
+   *           items:
+   *             type: string
+   *           description: Array of tag names associated with the page
+   *           example: ["javascript", "tutorial", "backend"]
+   *
+   *   responses:
+   *     PageTagsSuccess:
+   *       description: Successfully retrieved page tags
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageTagsData'
+   *
+   * /pages.getPageTag:
+   *   get:
+   *     tags: [Pages]
+   *     operationId: getPageTag
+   *     summary: Get page tags
+   *     description: Retrieve all tags associated with a specific page
+   *     parameters:
+   *       - in: query
+   *         name: pageId
+   *         required: true
+   *         description: Unique identifier of the page
+   *         schema:
+   *           type: string
+   *           format: ObjectId
+   *           example: "507f1f77bcf86cd799439011"
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageTagsSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       404:
+   *         $ref: '#/components/responses/NotFound'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /pages.getPageTag get page tags
@@ -187,32 +159,58 @@ module.exports = function(crowi, app) {
   /**
    * @swagger
    *
-   *    /pages.updatePost:
-   *      get:
-   *        tags: [Pages]
-   *        operationId: getUpdatePostPage
-   *        summary: /pages.updatePost
-   *        description: Get UpdatePost setting list
-   *        parameters:
-   *          - in: query
-   *            name: path
-   *            schema:
-   *              $ref: '#/components/schemas/PagePath'
-   *        responses:
-   *          200:
-   *            description: Succeeded to get UpdatePost setting list.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    updatePost:
-   *                      $ref: '#/components/schemas/UpdatePost'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
+   * components:
+   *   schemas:
+   *     UpdatePostData:
+   *       type: object
+   *       properties:
+   *         updatePost:
+   *           type: array
+   *           items:
+   *             type: string
+   *           description: Array of channel names for notifications
+   *           example: ["general", "development", "notifications"]
+   *
+   *   responses:
+   *     UpdatePostSuccess:
+   *       description: Successfully retrieved UpdatePost settings
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/UpdatePostData'
+   *
+   * /pages.updatePost:
+   *   get:
+   *     tags: [Pages]
+   *     operationId: getUpdatePost
+   *     summary: Get UpdatePost settings
+   *     description: Retrieve UpdatePost notification settings for a specific path
+   *     parameters:
+   *       - in: query
+   *         name: path
+   *         required: true
+   *         description: Page path to get UpdatePost settings for
+   *         schema:
+   *           type: string
+   *           example: "/user/example"
+   *         examples:
+   *           userPage:
+   *             value: "/user/john"
+   *             description: User page path
+   *           projectPage:
+   *             value: "/project/myproject"
+   *             description: Project page path
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/UpdatePostSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /pages.updatePost
@@ -254,12 +252,102 @@ module.exports = function(crowi, app) {
   ];
 
   /**
-   * @api {post} /pages.remove Remove page
-   * @apiName RemovePage
-   * @apiGroup Page
+   * @swagger
+   *
+   * components:
+   *   schemas:
+   *     PageRemoveData:
+   *       type: object
+   *       required:
+   *         - path
+   *       properties:
+   *         path:
+   *           type: string
+   *           description: Path of the deleted page
+   *           example: "/user/example"
+   *         isRecursively:
+   *           type: boolean
+   *           description: Whether deletion was recursive
+   *           example: true
+   *         isCompletely:
+   *           type: boolean
+   *           description: Whether deletion was complete
+   *           example: false
+   *
+   *   responses:
+   *     PageRemoveSuccess:
+   *       description: Page successfully deleted
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageRemoveData'
    *
-   * @apiParam {String} page_id Page Id.
-   * @apiParam {String} revision_id
+   * /pages.remove:
+   *   post:
+   *     tags: [Pages]
+   *     operationId: removePage
+   *     summary: Remove page
+   *     description: Delete a page either softly or completely, with optional recursive deletion
+   *     requestBody:
+   *       required: true
+   *       content:
+   *         application/json:
+   *           schema:
+   *             type: object
+   *             required:
+   *               - page_id
+   *             properties:
+   *               page_id:
+   *                 type: string
+   *                 format: ObjectId
+   *                 description: Unique identifier of the page to delete
+   *                 example: "507f1f77bcf86cd799439011"
+   *               revision_id:
+   *                 type: string
+   *                 format: ObjectId
+   *                 description: Revision ID for conflict detection
+   *                 example: "507f1f77bcf86cd799439012"
+   *               completely:
+   *                 type: boolean
+   *                 description: Whether to delete the page completely (true) or soft delete (false)
+   *                 default: false
+   *                 example: false
+   *               recursively:
+   *                 type: boolean
+   *                 description: Whether to delete child pages recursively
+   *                 default: false
+   *                 example: true
+   *           examples:
+   *             softDelete:
+   *               summary: Soft delete single page
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 revision_id: "507f1f77bcf86cd799439012"
+   *             recursiveDelete:
+   *               summary: Recursive soft delete
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 recursively: true
+   *             completeDelete:
+   *               summary: Complete deletion
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 completely: true
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageRemoveSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       404:
+   *         $ref: '#/components/responses/NotFound'
+   *       409:
+   *         $ref: '#/components/responses/Conflict'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   api.remove = async function(req, res) {
     const pageId = req.body.page_id;
@@ -365,11 +453,89 @@ module.exports = function(crowi, app) {
   ];
 
   /**
-   * @api {post} /pages.revertRemove Revert removed page
-   * @apiName RevertRemovePage
-   * @apiGroup Page
+   * @swagger
+   *
+   * components:
+   *   schemas:
+   *     PageRevertData:
+   *       type: object
+   *       properties:
+   *         page:
+   *           type: object
+   *           description: Restored page object
+   *           properties:
+   *             _id:
+   *               type: string
+   *               format: ObjectId
+   *               example: "507f1f77bcf86cd799439011"
+   *             path:
+   *               type: string
+   *               example: "/user/example"
+   *             title:
+   *               type: string
+   *               example: "Example Page"
+   *             status:
+   *               type: string
+   *               example: "published"
    *
-   * @apiParam {String} page_id Page Id.
+   *   responses:
+   *     PageRevertSuccess:
+   *       description: Page successfully restored
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageRevertData'
+   *
+   * /pages.revertRemove:
+   *   post:
+   *     tags: [Pages]
+   *     operationId: revertRemovePage
+   *     summary: Revert removed page
+   *     description: Restore a previously deleted (soft-deleted) page
+   *     requestBody:
+   *       required: true
+   *       content:
+   *         application/json:
+   *           schema:
+   *             type: object
+   *             required:
+   *               - page_id
+   *             properties:
+   *               page_id:
+   *                 type: string
+   *                 format: ObjectId
+   *                 description: Unique identifier of the page to restore
+   *                 example: "507f1f77bcf86cd799439011"
+   *               recursively:
+   *                 type: boolean
+   *                 description: Whether to restore child pages recursively
+   *                 default: false
+   *                 example: true
+   *           examples:
+   *             singleRevert:
+   *               summary: Revert single page
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *             recursiveRevert:
+   *               summary: Revert page and children
+   *               value:
+   *                 page_id: "507f1f77bcf86cd799439011"
+   *                 recursively: true
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageRevertSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       404:
+   *         $ref: '#/components/responses/NotFound'
+   *       409:
+   *         $ref: '#/components/responses/Conflict'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   api.revertRemove = async function(req, res, options) {
     const pageId = req.body.page_id;
@@ -406,12 +572,61 @@ module.exports = function(crowi, app) {
   };
 
   /**
-   * @api {post} /pages.unlink Remove the redirecting page
-   * @apiName UnlinkPage
-   * @apiGroup Page
+   * @swagger
+   *
+   * components:
+   *   schemas:
+   *     PageUnlinkData:
+   *       type: object
+   *       properties:
+   *         path:
+   *           type: string
+   *           description: Path for which redirects were removed
+   *           example: "/user/example"
+   *
+   *   responses:
+   *     PageUnlinkSuccess:
+   *       description: Successfully removed page redirects
+   *       content:
+   *         application/json:
+   *           schema:
+   *             allOf:
+   *               - $ref: '#/components/schemas/ApiResponseSuccess'
+   *               - $ref: '#/components/schemas/PageUnlinkData'
    *
-   * @apiParam {String} page_id Page Id.
-   * @apiParam {String} revision_id
+   * /pages.unlink:
+   *   post:
+   *     tags: [Pages]
+   *     operationId: unlinkPage
+   *     summary: Remove page redirects
+   *     description: Remove all redirect entries that point to the specified page path
+   *     requestBody:
+   *       required: true
+   *       content:
+   *         application/json:
+   *           schema:
+   *             type: object
+   *             required:
+   *               - path
+   *             properties:
+   *               path:
+   *                 type: string
+   *                 description: Target path to remove redirects for
+   *                 example: "/user/example"
+   *           examples:
+   *             unlinkPage:
+   *               summary: Remove redirects to a page
+   *               value:
+   *                 path: "/user/example"
+   *     responses:
+   *       200:
+   *         $ref: '#/components/responses/PageUnlinkSuccess'
+   *       400:
+   *         $ref: '#/components/responses/BadRequest'
+   *       403:
+   *         $ref: '#/components/responses/Forbidden'
+   *       500:
+   *         $ref: '#/components/responses/InternalServerError'
    */
   api.unlink = async function(req, res) {
     const path = req.body.path;

+ 18 - 16
apps/app/src/server/routes/search.ts

@@ -79,24 +79,26 @@ module.exports = function(crowi: Crowi, app) {
    *           content:
    *             application/json:
    *               schema:
-   *                 properties:
-   *                   ok:
-   *                     $ref: '#/components/schemas/V1ResponseOK'
-   *                   meta:
-   *                     $ref: '#/components/schemas/ElasticsearchResultMeta'
-   *                   totalCount:
-   *                     type: integer
-   *                     description: total count of pages
-   *                     example: 35
-   *                   data:
-   *                     type: array
-   *                     items:
-   *                       $ref: '#/components/schemas/Page'
-   *                     description: page list
+   *                 allOf:
+   *                   - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                   - type: object
+   *                     properties:
+   *                       meta:
+   *                         $ref: '#/components/schemas/ElasticsearchResultMeta'
+   *                         description: Elasticsearch metadata
+   *                       totalCount:
+   *                         type: integer
+   *                         description: total count of pages
+   *                         example: 35
+   *                       data:
+   *                         type: array
+   *                         items:
+   *                           $ref: '#/components/schemas/Page'
+   *                         description: page list
    *         403:
-   *           $ref: '#/components/responses/403'
+   *           $ref: '#/components/responses/Forbidden'
    *         500:
-   *           $ref: '#/components/responses/500'
+   *           $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /search search page

+ 29 - 23
apps/app/src/server/routes/tag.js

@@ -37,15 +37,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    tags:
-   *                      $ref: '#/components/schemas/Tags'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        tags:
+   *                          $ref: '#/components/schemas/Tags'
+   *                          description: List of matching tags
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /tags.search search tags
@@ -91,15 +93,17 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    tags:
-   *                      $ref: '#/components/schemas/Tags'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        tags:
+   *                          $ref: '#/components/schemas/Tags'
+   *                          description: Updated tags for the page
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {post} /tags.update update tags on view-mode (not edit-mode)
@@ -168,17 +172,19 @@ module.exports = function(crowi, app) {
    *            content:
    *              application/json:
    *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1ResponseOK'
-   *                    data:
-   *                      type: array
-   *                      items:
-   *                        $ref: '#/components/schemas/Tag'
+   *                  allOf:
+   *                    - $ref: '#/components/schemas/ApiResponseSuccess'
+   *                    - type: object
+   *                      properties:
+   *                        data:
+   *                          type: array
+   *                          items:
+   *                            $ref: '#/components/schemas/Tag'
+   *                          description: List of tags with count information
    *          403:
-   *            $ref: '#/components/responses/403'
+   *            $ref: '#/components/responses/Forbidden'
    *          500:
-   *            $ref: '#/components/responses/500'
+   *            $ref: '#/components/responses/InternalServerError'
    */
   /**
    * @api {get} /tags.list get tagnames and count pages relate each tag

+ 20 - 16
apps/app/src/server/service/config-manager/config-definition.ts

@@ -1,6 +1,9 @@
 import { GrowiDeploymentType, GrowiServiceType } from '@growi/core/dist/consts';
-import type { ConfigDefinition, Lang } from '@growi/core/dist/interfaces';
-import { defineConfig } from '@growi/core/dist/interfaces';
+import type { ConfigDefinition, Lang, NonBlankString } from '@growi/core/dist/interfaces';
+import {
+  toNonBlankString,
+  defineConfig,
+} from '@growi/core/dist/interfaces';
 import type OpenAI from 'openai';
 
 import { ActionGroupSize } from '~/interfaces/activity';
@@ -726,10 +729,10 @@ export const CONFIG_DEFINITIONS = {
   'security:passport-google:isEnabled': defineConfig<boolean>({
     defaultValue: false,
   }),
-  'security:passport-google:clientId': defineConfig<string | undefined>({
+  'security:passport-google:clientId': defineConfig<NonBlankString | undefined>({
     defaultValue: undefined,
   }),
-  'security:passport-google:clientSecret': defineConfig<string | undefined>({
+  'security:passport-google:clientSecret': defineConfig<NonBlankString | undefined>({
     defaultValue: undefined,
   }),
   'security:passport-google:isSameUsernameTreatedAsIdenticalUser': defineConfig<boolean>({
@@ -819,28 +822,29 @@ export const CONFIG_DEFINITIONS = {
     envVarName: 'S3_OBJECT_ACL',
     defaultValue: undefined,
   }),
-  'aws:s3Bucket': defineConfig<string>({
-    defaultValue: 'growi',
+  'aws:s3Bucket': defineConfig<NonBlankString>({
+    defaultValue: toNonBlankString('growi'),
   }),
-  'aws:s3Region': defineConfig<string>({
-    defaultValue: 'ap-northeast-1',
+  'aws:s3Region': defineConfig<NonBlankString>({
+    defaultValue: toNonBlankString('ap-northeast-1'),
   }),
-  'aws:s3AccessKeyId': defineConfig<string | undefined>({
+  'aws:s3AccessKeyId': defineConfig<NonBlankString | undefined>({
     defaultValue: undefined,
   }),
-  'aws:s3SecretAccessKey': defineConfig<string | undefined>({
+  'aws:s3SecretAccessKey': defineConfig<NonBlankString | undefined>({
     defaultValue: undefined,
+    isSecret: true,
   }),
-  'aws:s3CustomEndpoint': defineConfig<string | undefined>({
+  'aws:s3CustomEndpoint': defineConfig<NonBlankString | undefined>({
     defaultValue: undefined,
   }),
 
   // GCS Settings
-  'gcs:apiKeyJsonPath': defineConfig<string | undefined>({
+  'gcs:apiKeyJsonPath': defineConfig<NonBlankString | undefined>({
     envVarName: 'GCS_API_KEY_JSON_PATH',
     defaultValue: undefined,
   }),
-  'gcs:bucket': defineConfig<string | undefined>({
+  'gcs:bucket': defineConfig<NonBlankString | undefined>({
     envVarName: 'GCS_BUCKET',
     defaultValue: undefined,
   }),
@@ -866,15 +870,15 @@ export const CONFIG_DEFINITIONS = {
     envVarName: 'AZURE_REFERENCE_FILE_WITH_RELAY_MODE',
     defaultValue: false,
   }),
-  'azure:tenantId': defineConfig<string | undefined>({
+  'azure:tenantId': defineConfig<NonBlankString | undefined>({
     envVarName: 'AZURE_TENANT_ID',
     defaultValue: undefined,
   }),
-  'azure:clientId': defineConfig<string | undefined>({
+  'azure:clientId': defineConfig<NonBlankString | undefined>({
     envVarName: 'AZURE_CLIENT_ID',
     defaultValue: undefined,
   }),
-  'azure:clientSecret': defineConfig<string | undefined>({
+  'azure:clientSecret': defineConfig<NonBlankString | undefined>({
     envVarName: 'AZURE_CLIENT_SECRET',
     defaultValue: undefined,
     isSecret: true,

+ 8 - 5
apps/app/src/server/service/file-uploader/aws/index.ts

@@ -13,6 +13,8 @@ import {
   AbortMultipartUploadCommand,
 } from '@aws-sdk/client-s3';
 import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
+import type { NonBlankString } from '@growi/core/dist/interfaces';
+import { toNonBlankStringOrUndefined } from '@growi/core/dist/interfaces';
 import urljoin from 'url-join';
 
 import type Crowi from '~/server/crowi';
@@ -79,14 +81,15 @@ const getS3PutObjectCannedAcl = (): ObjectCannedACL | undefined => {
   return undefined;
 };
 
-const getS3Bucket = (): string | undefined => {
-  return configManager.getConfig('aws:s3Bucket') ?? undefined; // return undefined when getConfig() returns null
+const getS3Bucket = (): NonBlankString | undefined => {
+  return toNonBlankStringOrUndefined(configManager.getConfig('aws:s3Bucket')); // Blank strings may remain in the DB, so convert with toNonBlankStringOrUndefined for safety
 };
 
 const S3Factory = (): S3Client => {
   const accessKeyId = configManager.getConfig('aws:s3AccessKeyId');
   const secretAccessKey = configManager.getConfig('aws:s3SecretAccessKey');
-  const s3CustomEndpoint = configManager.getConfig('aws:s3CustomEndpoint') || undefined;
+  const s3Region = toNonBlankStringOrUndefined(configManager.getConfig('aws:s3Region')); // Blank strings may remain in the DB, so convert with toNonBlankStringOrUndefined for safety
+  const s3CustomEndpoint = toNonBlankStringOrUndefined(configManager.getConfig('aws:s3CustomEndpoint'));
 
   return new S3Client({
     credentials: accessKeyId != null && secretAccessKey != null
@@ -95,9 +98,9 @@ const S3Factory = (): S3Client => {
         secretAccessKey,
       }
       : undefined,
-    region: configManager.getConfig('aws:s3Region'),
+    region: s3Region,
     endpoint: s3CustomEndpoint,
-    forcePathStyle: !!s3CustomEndpoint, // s3ForcePathStyle renamed to forcePathStyle in v3
+    forcePathStyle: s3CustomEndpoint != null, // s3ForcePathStyle renamed to forcePathStyle in v3
   });
 };
 

+ 4 - 3
apps/app/src/server/service/file-uploader/azure.ts

@@ -17,6 +17,7 @@ import {
   type BlockBlobUploadResponse,
   type BlockBlobParallelUploadOptions,
 } from '@azure/storage-blob';
+import { toNonBlankStringOrUndefined } from '@growi/core/dist/interfaces';
 
 import type Crowi from '~/server/crowi';
 import { FilePathOnStoragePrefix, ResponseMode, type RespondOptions } from '~/server/interfaces/attachment';
@@ -60,9 +61,9 @@ function getAzureConfig(): AzureConfig {
 }
 
 function getCredential(): TokenCredential {
-  const tenantId = configManager.getConfig('azure:tenantId');
-  const clientId = configManager.getConfig('azure:clientId');
-  const clientSecret = configManager.getConfig('azure:clientSecret');
+  const tenantId = toNonBlankStringOrUndefined(configManager.getConfig('azure:tenantId'));
+  const clientId = toNonBlankStringOrUndefined(configManager.getConfig('azure:clientId'));
+  const clientSecret = toNonBlankStringOrUndefined(configManager.getConfig('azure:clientSecret'));
 
   if (tenantId == null || clientId == null || clientSecret == null) {
     throw new Error(`Azure Blob Storage missing required configuration: tenantId=${tenantId}, clientId=${clientId}, clientSecret=${clientSecret}`);

+ 3 - 2
apps/app/src/server/service/file-uploader/gcs/index.ts

@@ -2,6 +2,7 @@ import type { Readable } from 'stream';
 import { pipeline } from 'stream/promises';
 
 import { Storage } from '@google-cloud/storage';
+import { toNonBlankStringOrUndefined } from '@growi/core/dist/interfaces';
 import axios from 'axios';
 import urljoin from 'url-join';
 
@@ -24,7 +25,7 @@ const logger = loggerFactory('growi:service:fileUploaderGcs');
 
 
 function getGcsBucket(): string {
-  const gcsBucket = configManager.getConfig('gcs:bucket');
+  const gcsBucket = toNonBlankStringOrUndefined(configManager.getConfig('gcs:bucket')); // Blank strings may remain in the DB, so convert with toNonBlankStringOrUndefined for safety
   if (gcsBucket == null) {
     throw new Error('GCS bucket is not configured.');
   }
@@ -34,7 +35,7 @@ function getGcsBucket(): string {
 let storage: Storage;
 function getGcsInstance() {
   if (storage == null) {
-    const keyFilename = configManager.getConfig('gcs:apiKeyJsonPath');
+    const keyFilename = toNonBlankStringOrUndefined(configManager.getConfig('gcs:apiKeyJsonPath')); // Blank strings may remain in the DB, so convert with toNonBlankStringOrUndefined for safety
     // see https://googleapis.dev/nodejs/storage/latest/Storage.html
     storage = keyFilename != null
       ? new Storage({ keyFilename }) // Create a client with explicit credentials

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

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

+ 1 - 3
biome.json

@@ -25,9 +25,7 @@
       "./packages/editor/**",
       "./packages/pdf-converter-client/**",
       "./packages/pluginkit/**",
-      "./packages/presentation/**",
-      "./packages/preset-templates/**",
-      "./packages/preset-themes/**"
+      "./packages/presentation/**"
     ]
   },
   "formatter": {

+ 1 - 1
package.json

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

+ 1 - 0
packages/preset-templates/.eslintignore

@@ -0,0 +1 @@
+*

+ 0 - 5
packages/preset-templates/.eslintrc.js

@@ -1,5 +0,0 @@
-module.exports = {
-  extends: [
-    'plugin:vitest/recommended',
-  ],
-};

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

@@ -5,7 +5,8 @@
   "license": "MIT",
   "private": "true",
   "scripts": {
-    "test": "vitest run"
+    "test": "vitest run",
+    "lint": "biome check"
   },
   "dependencies": {},
   "devDependencies": {

+ 7 - 8
packages/preset-templates/test/index.test.ts

@@ -1,13 +1,14 @@
 import path from 'node:path';
 
-import { scanAllTemplates, validateTemplatePluginGrowiDirective, validateAllTemplateLocales } from '@growi/pluginkit/dist/v4/server';
-
+import {
+  scanAllTemplates,
+  validateAllTemplateLocales,
+  validateTemplatePluginGrowiDirective,
+} from '@growi/pluginkit/dist/v4/server';
 
 const projectDirRoot = path.resolve(__dirname, '../');
 
-
 it('Validation for package.json should be passed', () => {
-
   // when
   const caller = () => validateTemplatePluginGrowiDirective(projectDirRoot);
 
@@ -16,7 +17,6 @@ it('Validation for package.json should be passed', () => {
 });
 
 it('Validation for package.json should be return data', () => {
-
   // when
   const data = validateTemplatePluginGrowiDirective(projectDirRoot);
 
@@ -24,7 +24,7 @@ it('Validation for package.json should be return data', () => {
   expect(data).not.toBeNull();
 });
 
-it('Scanning the templates ends up with no errors', async() => {
+it('Scanning the templates ends up with no errors', async () => {
   // when
   const results = await scanAllTemplates(projectDirRoot);
 
@@ -32,8 +32,7 @@ it('Scanning the templates ends up with no errors', async() => {
   expect(results).not.toBeNull();
 });
 
-it('Scanning the templates ends up with no errors with opts.data', async() => {
-
+it('Scanning the templates ends up with no errors with opts.data', async () => {
   // setup
   const data = validateTemplatePluginGrowiDirective(projectDirRoot);
 

+ 1 - 3
packages/preset-templates/tsconfig.json

@@ -3,8 +3,6 @@
   "compilerOptions": {
     "esModuleInterop": true,
     "resolveJsonModule": true,
-    "types": [
-      "vitest/globals"
-    ]
+    "types": ["vitest/globals"]
   }
 }

+ 1 - 1
packages/preset-themes/.eslintignore

@@ -1 +1 @@
-/dist/**
+*

+ 0 - 2
packages/preset-themes/.eslintrc.js

@@ -1,2 +0,0 @@
-module.exports = {
-};

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

@@ -21,7 +21,7 @@
     "watch": "run-p watch:*",
     "watch:libs": "pnpm run dev:libs -w --emptyOutDir=false",
     "watch:themes": "pnpm run dev:themes -w --emptyOutDir=false",
-    "lint:eslint": "eslint \"**/*.{js,jsx,ts,tsx}\"",
+    "lint:biome": "biome check",
     "lint:styles": "stylelint \"src/**/*.scss\"",
     "lint:typecheck": "vue-tsc --noEmit",
     "lint": "run-p lint:*",

+ 21 - 11
packages/preset-themes/src/consts/preset-themes.ts

@@ -21,7 +21,7 @@ export const PresetThemes = {
   WOOD: 'wood',
   CLASSIC: 'classic',
 } as const;
-export type PresetThemes = typeof PresetThemes[keyof typeof PresetThemes];
+export type PresetThemes = (typeof PresetThemes)[keyof typeof PresetThemes];
 
 /* eslint-disable no-multi-spaces, */
 
@@ -74,7 +74,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#94351E',
     darkIcon: '#EE775B',
     createBtn: '#EA5532',
-  }, {
+  },
+  {
     name: PresetThemes.JADE_GREEN,
     schemeType: BOTH,
     lightBg: '#FFFFFF',
@@ -84,7 +85,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#3A8F6F',
     darkIcon: '#5FC2A2',
     createBtn: '#49B38A',
-  }, {
+  },
+  {
     name: PresetThemes.CLASSIC,
     schemeType: BOTH,
     lightBg: '#FFFFFF',
@@ -94,7 +96,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#53687E',
     darkIcon: '#869BB1',
     createBtn: '#3491CB',
-  }, {
+  },
+  {
     name: PresetThemes.CHRISTMAS,
     schemeType: BOTH,
     lightBg: '#FFFFFF',
@@ -116,7 +119,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#3F8421',
     darkIcon: '#1F4210',
     createBtn: '#4FA529',
-  }, {
+  },
+  {
     name: PresetThemes.WOOD,
     schemeType: LIGHT,
     lightBg: '#FFFFF5',
@@ -126,7 +130,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#86651A',
     darkIcon: '#43320D',
     createBtn: '#A77E21',
-  }, {
+  },
+  {
     name: PresetThemes.ISLAND,
     schemeType: LIGHT,
     lightBg: '#FFFFFF',
@@ -136,7 +141,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#51C2D3',
     darkIcon: '#204D54',
     createBtn: '#51C2D3',
-  }, {
+  },
+  {
     name: PresetThemes.ANTARCTIC,
     schemeType: LIGHT,
     lightBg: '#FAFEFF',
@@ -146,7 +152,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#2631AF',
     darkIcon: '#131857',
     createBtn: '#303DDB',
-  }, {
+  },
+  {
     name: PresetThemes.SPRING,
     schemeType: LIGHT,
     lightBg: '#FFFFFF',
@@ -156,7 +163,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#D76F7D',
     darkIcon: '#8A423F',
     createBtn: '#6ABA55',
-  }, {
+  },
+  {
     name: PresetThemes.KIBELA,
     schemeType: LIGHT,
     lightBg: '#FFFFFF',
@@ -178,7 +186,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#3F999B',
     darkIcon: '#99E5E6',
     createBtn: '#03A2A8',
-  }, {
+  },
+  {
     name: PresetThemes.HALLOWEEN,
     schemeType: DARK,
     lightBg: '#240E3E',
@@ -188,7 +197,8 @@ export const PresetThemesMetadatas: GrowiThemeMetadata[] = [
     lightIcon: '#8C3C03',
     darkIcon: '#DDB69B',
     createBtn: '#AA4A04',
-  }, {
+  },
+  {
     name: PresetThemes.BLACKBOARD,
     schemeType: DARK,
     lightBg: '#223323',

+ 2 - 5
packages/preset-themes/tsconfig.json

@@ -1,9 +1,6 @@
 {
   "$schema": "http://json.schemastore.org/tsconfig",
   "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-  },
-  "include": [
-    "src"
-  ]
+  "compilerOptions": {},
+  "include": ["src"]
 }

+ 2 - 5
packages/preset-themes/vite.libs.config.ts

@@ -3,9 +3,7 @@ import dts from 'vite-plugin-dts';
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [
-    dts({ copyDtsFiles: true }),
-  ],
+  plugins: [dts({ copyDtsFiles: true })],
   build: {
     outDir: 'dist/libs',
     sourcemap: true,
@@ -16,8 +14,7 @@ export default defineConfig({
       formats: ['es', 'umd'],
     },
     rollupOptions: {
-      external: [
-      ],
+      external: [],
     },
   },
 });

+ 1 - 3
packages/preset-themes/vite.themes.config.ts

@@ -28,9 +28,7 @@ export default defineConfig(({ mode }) => {
           '/src/styles/classic.scss',
         ],
         output: {
-          assetFileNames: isProd
-            ? undefined
-            : 'assets/[name].[ext]', // not attach hash
+          assetFileNames: isProd ? undefined : 'assets/[name].[ext]', // not attach hash
         },
       },
     },

+ 257 - 86
pnpm-lock.yaml

@@ -60,7 +60,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.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2))
+        version: 4.3.1(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0))
       '@vitest/coverage-v8':
         specifier: ^2.1.1
         version: 2.1.1(vitest@2.1.1)
@@ -78,7 +78,7 @@ importers:
         version: 8.41.0
       eslint-config-next:
         specifier: ^12.1.6
-        version: 12.1.6(eslint@8.41.0)(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(typescript@5.0.4)
+        version: 12.1.6(eslint@8.41.0)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(typescript@5.0.4)
       eslint-config-weseek:
         specifier: ^2.1.1
         version: 2.1.1(@babel/core@7.24.6)(@babel/eslint-parser@7.24.7(@babel/core@7.24.6)(eslint@8.41.0))(@typescript-eslint/eslint-plugin@5.59.7(@typescript-eslint/parser@5.59.7(eslint@8.41.0)(typescript@5.0.4))(eslint@8.41.0)(typescript@5.0.4))(@typescript-eslint/parser@5.59.7(eslint@8.41.0)(typescript@5.0.4))(eslint-import-resolver-typescript@3.2.5)(eslint-plugin-import@2.26.0)(eslint-plugin-jsx-a11y@6.5.1(eslint@8.41.0))(eslint-plugin-react-hooks@4.6.0(eslint@8.41.0))(eslint-plugin-react@7.30.1(eslint@8.41.0))(eslint-plugin-vue@7.20.0(eslint@8.41.0))(eslint@8.41.0)
@@ -150,7 +150,7 @@ importers:
         version: 5.0.1(stylelint@16.5.0(typescript@5.0.4))
       stylelint-config-recommended-scss:
         specifier: ^14.0.0
-        version: 14.0.0(postcss@8.5.3)(stylelint@16.5.0(typescript@5.0.4))
+        version: 14.0.0(postcss@8.5.5)(stylelint@16.5.0(typescript@5.0.4))
       ts-deepmerge:
         specifier: ^6.2.0
         version: 6.2.0
@@ -177,16 +177,16 @@ importers:
         version: 3.4.7(typescript@5.0.4)
       vite:
         specifier: ^5.4.19
-        version: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
+        version: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
       vite-plugin-dts:
         specifier: ^3.9.1
-        version: 3.9.1(@types/node@20.14.0)(rollup@4.41.0)(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2))
+        version: 3.9.1(@types/node@20.14.0)(rollup@4.41.0)(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0))
       vite-tsconfig-paths:
         specifier: ^5.0.1
-        version: 5.0.1(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2))
+        version: 5.0.1(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0))
       vitest:
         specifier: ^2.1.1
-        version: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.39.2)
+        version: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.42.0)
       vitest-mock-extended:
         specifier: ^2.0.2
         version: 2.0.2(typescript@5.0.4)(vitest@2.1.1)
@@ -327,7 +327,7 @@ importers:
         version: 3.9.1
       babel-plugin-superjson-next:
         specifier: ^0.4.2
-        version: 0.4.5(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)
+        version: 0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)
       body-parser:
         specifier: ^1.20.3
         version: 1.20.3
@@ -524,20 +524,20 @@ importers:
         specifier: ^4.2.0
         version: 4.2.0
       next:
-        specifier: ^14.2.26
-        version: 14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+        specifier: ^14.2.30
+        version: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       next-dynamic-loading-props:
         specifier: ^0.1.1
         version: 0.1.1(react@18.2.0)
       next-i18next:
         specifier: ^15.3.1
-        version: 15.3.1(i18next@23.16.5)(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
+        version: 15.3.1(i18next@23.16.5)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
       next-superjson:
         specifier: ^0.0.4
-        version: 0.0.4(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15)))
+        version: 0.0.4(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15)))
       next-themes:
         specifier: ^0.2.1
-        version: 0.2.1(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+        version: 0.2.1(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       nocache:
         specifier: ^4.0.0
         version: 4.0.0
@@ -2677,6 +2677,9 @@ packages:
   '@codemirror/language@6.11.0':
     resolution: {integrity: sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==}
 
+  '@codemirror/language@6.11.1':
+    resolution: {integrity: sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==}
+
   '@codemirror/legacy-modes@6.4.1':
     resolution: {integrity: sha512-vdg3XY7OAs5uLDx2Iw+cGfnwtd7kM+Et/eMsqAGTfT/JKiVBQZXosTzjEbWAi/FrY6DcQIz8mQjBozFHZEUWQA==}
 
@@ -2698,8 +2701,8 @@ packages:
   '@codemirror/view@6.36.4':
     resolution: {integrity: sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==}
 
-  '@codemirror/view@6.36.8':
-    resolution: {integrity: sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==}
+  '@codemirror/view@6.37.2':
+    resolution: {integrity: sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==}
 
   '@colors/colors@1.5.0':
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
@@ -3449,6 +3452,9 @@ packages:
   '@next/env@14.2.26':
     resolution: {integrity: sha512-vO//GJ/YBco+H7xdQhzJxF7ub3SUwft76jwaeOyVVQFHCi5DCnkP16WHB+JBylo4vOKPoZBlR94Z8xBxNBdNJA==}
 
+  '@next/env@14.2.30':
+    resolution: {integrity: sha512-KBiBKrDY6kxTQWGzKjQB7QirL3PiiOkV7KW98leHFjtVRKtft76Ra5qSA/SL75xT44dp6hOcqiiJ6iievLOYug==}
+
   '@next/eslint-plugin-next@12.1.6':
     resolution: {integrity: sha512-yNUtJ90NEiYFT6TJnNyofKMPYqirKDwpahcbxBgSIuABwYOdkGwzos1ZkYD51Qf0diYwpQZBeVqElTk7Q2WNqw==}
 
@@ -3458,54 +3464,108 @@ packages:
     cpu: [arm64]
     os: [darwin]
 
+  '@next/swc-darwin-arm64@14.2.30':
+    resolution: {integrity: sha512-EAqfOTb3bTGh9+ewpO/jC59uACadRHM6TSA9DdxJB/6gxOpyV+zrbqeXiFTDy9uV6bmipFDkfpAskeaDcO+7/g==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+
   '@next/swc-darwin-x64@14.2.26':
     resolution: {integrity: sha512-U0adH5ryLfmTDkahLwG9sUQG2L0a9rYux8crQeC92rPhi3jGQEY47nByQHrVrt3prZigadwj/2HZ1LUUimuSbg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
 
+  '@next/swc-darwin-x64@14.2.30':
+    resolution: {integrity: sha512-TyO7Wz1IKE2kGv8dwQ0bmPL3s44EKVencOqwIY69myoS3rdpO1NPg5xPM5ymKu7nfX4oYJrpMxv8G9iqLsnL4A==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+
   '@next/swc-linux-arm64-gnu@14.2.26':
     resolution: {integrity: sha512-SINMl1I7UhfHGM7SoRiw0AbwnLEMUnJ/3XXVmhyptzriHbWvPPbbm0OEVG24uUKhuS1t0nvN/DBvm5kz6ZIqpg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
+  '@next/swc-linux-arm64-gnu@14.2.30':
+    resolution: {integrity: sha512-I5lg1fgPJ7I5dk6mr3qCH1hJYKJu1FsfKSiTKoYwcuUf53HWTrEkwmMI0t5ojFKeA6Vu+SfT2zVy5NS0QLXV4Q==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+
   '@next/swc-linux-arm64-musl@14.2.26':
     resolution: {integrity: sha512-s6JaezoyJK2DxrwHWxLWtJKlqKqTdi/zaYigDXUJ/gmx/72CrzdVZfMvUc6VqnZ7YEvRijvYo+0o4Z9DencduA==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
+  '@next/swc-linux-arm64-musl@14.2.30':
+    resolution: {integrity: sha512-8GkNA+sLclQyxgzCDs2/2GSwBc92QLMrmYAmoP2xehe5MUKBLB2cgo34Yu242L1siSkwQkiV4YLdCnjwc/Micw==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+
   '@next/swc-linux-x64-gnu@14.2.26':
     resolution: {integrity: sha512-FEXeUQi8/pLr/XI0hKbe0tgbLmHFRhgXOUiPScz2hk0hSmbGiU8aUqVslj/6C6KA38RzXnWoJXo4FMo6aBxjzg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
+  '@next/swc-linux-x64-gnu@14.2.30':
+    resolution: {integrity: sha512-8Ly7okjssLuBoe8qaRCcjGtcMsv79hwzn/63wNeIkzJVFVX06h5S737XNr7DZwlsbTBDOyI6qbL2BJB5n6TV/w==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
   '@next/swc-linux-x64-musl@14.2.26':
     resolution: {integrity: sha512-BUsomaO4d2DuXhXhgQCVt2jjX4B4/Thts8nDoIruEJkhE5ifeQFtvW5c9JkdOtYvE5p2G0hcwQ0UbRaQmQwaVg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
+  '@next/swc-linux-x64-musl@14.2.30':
+    resolution: {integrity: sha512-dBmV1lLNeX4mR7uI7KNVHsGQU+OgTG5RGFPi3tBJpsKPvOPtg9poyav/BYWrB3GPQL4dW5YGGgalwZ79WukbKQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+
   '@next/swc-win32-arm64-msvc@14.2.26':
     resolution: {integrity: sha512-5auwsMVzT7wbB2CZXQxDctpWbdEnEW/e66DyXO1DcgHxIyhP06awu+rHKshZE+lPLIGiwtjo7bsyeuubewwxMw==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [win32]
 
+  '@next/swc-win32-arm64-msvc@14.2.30':
+    resolution: {integrity: sha512-6MMHi2Qc1Gkq+4YLXAgbYslE1f9zMGBikKMdmQRHXjkGPot1JY3n5/Qrbg40Uvbi8//wYnydPnyvNhI1DMUW1g==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [win32]
+
   '@next/swc-win32-ia32-msvc@14.2.26':
     resolution: {integrity: sha512-GQWg/Vbz9zUGi9X80lOeGsz1rMH/MtFO/XqigDznhhhTfDlDoynCM6982mPCbSlxJ/aveZcKtTlwfAjwhyxDpg==}
     engines: {node: '>= 10'}
     cpu: [ia32]
     os: [win32]
 
+  '@next/swc-win32-ia32-msvc@14.2.30':
+    resolution: {integrity: sha512-pVZMnFok5qEX4RT59mK2hEVtJX+XFfak+/rjHpyFh7juiT52r177bfFKhnlafm0UOSldhXjj32b+LZIOdswGTg==}
+    engines: {node: '>= 10'}
+    cpu: [ia32]
+    os: [win32]
+
   '@next/swc-win32-x64-msvc@14.2.26':
     resolution: {integrity: sha512-2rdB3T1/Gp7bv1eQTTm9d1Y1sv9UuJ2LAwOE0Pe2prHKe32UNscj7YS13fRB37d0GAiGNR+Y7ZcW8YjDI8Ns0w==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
 
+  '@next/swc-win32-x64-msvc@14.2.30':
+    resolution: {integrity: sha512-4KCo8hMZXMjpTzs3HOqOGYYwAXymXIy7PEPAXNEcEOyKqkjiDlECumrWziy+JEF0Oi4ILHGxzgQ3YiMGG2t/Lg==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+
   '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
     resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
 
@@ -5387,6 +5447,9 @@ packages:
   '@types/estree@1.0.7':
     resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
 
+  '@types/estree@1.0.8':
+    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
   '@types/express-serve-static-core@4.19.5':
     resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==}
 
@@ -5951,6 +6014,11 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
+  acorn@8.15.0:
+    resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
   agent-base@6.0.2:
     resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
     engines: {node: '>= 6.0.0'}
@@ -6456,8 +6524,8 @@ packages:
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
 
-  browserslist@4.24.5:
-    resolution: {integrity: sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==}
+  browserslist@4.25.0:
+    resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
 
@@ -6600,8 +6668,8 @@ packages:
   caniuse-lite@1.0.30001707:
     resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==}
 
-  caniuse-lite@1.0.30001718:
-    resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
+  caniuse-lite@1.0.30001723:
+    resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
 
   capital-case@1.0.4:
     resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
@@ -8091,8 +8159,8 @@ packages:
     engines: {node: '>=0.10.0'}
     hasBin: true
 
-  electron-to-chromium@1.5.155:
-    resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==}
+  electron-to-chromium@1.5.167:
+    resolution: {integrity: sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==}
 
   electron-to-chromium@1.5.91:
     resolution: {integrity: sha512-sNSHHyq048PFmZY4S90ax61q+gLCs0X0YmcOII9wG9S2XwbVr+h4VW2wWhnbp/Eys3cCwTxVF292W3qPaxIapQ==}
@@ -11271,6 +11339,24 @@ packages:
       sass:
         optional: true
 
+  next@14.2.30:
+    resolution: {integrity: sha512-+COdu6HQrHHFQ1S/8BBsCag61jZacmvbuL2avHvQFbWa2Ox7bE+d8FyNgxRLjXQ5wtPyQwEmk85js/AuaG2Sbg==}
+    engines: {node: '>=18.17.0'}
+    hasBin: true
+    peerDependencies:
+      '@opentelemetry/api': ^1.1.0
+      '@playwright/test': ^1.41.2
+      react: ^18.2.0
+      react-dom: ^18.2.0
+      sass: ^1.3.0
+    peerDependenciesMeta:
+      '@opentelemetry/api':
+        optional: true
+      '@playwright/test':
+        optional: true
+      sass:
+        optional: true
+
   nice-try@1.0.4:
     resolution: {integrity: sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==}
 
@@ -11997,6 +12083,10 @@ packages:
     resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
     engines: {node: ^10 || ^12 || >=14}
 
+  postcss@8.5.5:
+    resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==}
+    engines: {node: ^10 || ^12 || >=14}
+
   postgres-array@2.0.0:
     resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
     engines: {node: '>=4'}
@@ -13605,8 +13695,8 @@ packages:
       uglify-js:
         optional: true
 
-  terser@5.39.2:
-    resolution: {integrity: sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==}
+  terser@5.42.0:
+    resolution: {integrity: sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -14531,8 +14621,8 @@ packages:
     engines: {node: '>= 10.13.0'}
     hasBin: true
 
-  webpack-sources@3.2.3:
-    resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
+  webpack-sources@3.3.2:
+    resolution: {integrity: sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==}
     engines: {node: '>=10.13.0'}
 
   webpack-virtual-modules@0.6.2:
@@ -16687,6 +16777,15 @@ snapshots:
       '@lezer/lr': 1.4.2
       style-mod: 4.1.2
 
+  '@codemirror/language@6.11.1':
+    dependencies:
+      '@codemirror/state': 6.5.2
+      '@codemirror/view': 6.37.2
+      '@lezer/common': 1.2.3
+      '@lezer/highlight': 1.2.1
+      '@lezer/lr': 1.4.2
+      style-mod: 4.1.2
+
   '@codemirror/legacy-modes@6.4.1':
     dependencies:
       '@codemirror/language': 6.11.0
@@ -16717,9 +16816,9 @@ snapshots:
 
   '@codemirror/theme-one-dark@6.1.2':
     dependencies:
-      '@codemirror/language': 6.11.0
+      '@codemirror/language': 6.11.1
       '@codemirror/state': 6.5.2
-      '@codemirror/view': 6.36.8
+      '@codemirror/view': 6.37.2
       '@lezer/highlight': 1.2.1
 
   '@codemirror/view@6.36.4':
@@ -16728,9 +16827,10 @@ snapshots:
       style-mod: 4.1.2
       w3c-keyname: 2.2.8
 
-  '@codemirror/view@6.36.8':
+  '@codemirror/view@6.37.2':
     dependencies:
       '@codemirror/state': 6.5.2
+      crelt: 1.0.6
       style-mod: 4.1.2
       w3c-keyname: 2.2.8
 
@@ -17608,6 +17708,8 @@ snapshots:
 
   '@next/env@14.2.26': {}
 
+  '@next/env@14.2.30': {}
+
   '@next/eslint-plugin-next@12.1.6':
     dependencies:
       glob: 7.1.7
@@ -17615,30 +17717,57 @@ snapshots:
   '@next/swc-darwin-arm64@14.2.26':
     optional: true
 
+  '@next/swc-darwin-arm64@14.2.30':
+    optional: true
+
   '@next/swc-darwin-x64@14.2.26':
     optional: true
 
+  '@next/swc-darwin-x64@14.2.30':
+    optional: true
+
   '@next/swc-linux-arm64-gnu@14.2.26':
     optional: true
 
+  '@next/swc-linux-arm64-gnu@14.2.30':
+    optional: true
+
   '@next/swc-linux-arm64-musl@14.2.26':
     optional: true
 
+  '@next/swc-linux-arm64-musl@14.2.30':
+    optional: true
+
   '@next/swc-linux-x64-gnu@14.2.26':
     optional: true
 
+  '@next/swc-linux-x64-gnu@14.2.30':
+    optional: true
+
   '@next/swc-linux-x64-musl@14.2.26':
     optional: true
 
+  '@next/swc-linux-x64-musl@14.2.30':
+    optional: true
+
   '@next/swc-win32-arm64-msvc@14.2.26':
     optional: true
 
+  '@next/swc-win32-arm64-msvc@14.2.30':
+    optional: true
+
   '@next/swc-win32-ia32-msvc@14.2.26':
     optional: true
 
+  '@next/swc-win32-ia32-msvc@14.2.30':
+    optional: true
+
   '@next/swc-win32-x64-msvc@14.2.26':
     optional: true
 
+  '@next/swc-win32-x64-msvc@14.2.30':
+    optional: true
+
   '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
     dependencies:
       eslint-scope: 5.1.1
@@ -20399,7 +20528,7 @@ snapshots:
   '@types/eslint-scope@3.7.7':
     dependencies:
       '@types/eslint': 8.37.0
-      '@types/estree': 1.0.7
+      '@types/estree': 1.0.8
 
   '@types/eslint@8.37.0':
     dependencies:
@@ -20412,6 +20541,8 @@ snapshots:
 
   '@types/estree@1.0.7': {}
 
+  '@types/estree@1.0.8': {}
+
   '@types/express-serve-static-core@4.19.5':
     dependencies:
       '@types/node': 22.13.14
@@ -20917,14 +21048,14 @@ snapshots:
 
   '@unts/get-tsconfig@4.1.1': {}
 
-  '@vitejs/plugin-react@4.3.1(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2))':
+  '@vitejs/plugin-react@4.3.1(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0))':
     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.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
+      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -20942,7 +21073,7 @@ snapshots:
       std-env: 3.7.0
       test-exclude: 7.0.1
       tinyrainbow: 1.2.0
-      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.39.2)
+      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.42.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -20953,13 +21084,13 @@ snapshots:
       chai: 5.1.1
       tinyrainbow: 1.2.0
 
-  '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2))':
+  '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0))':
     dependencies:
       '@vitest/spy': 2.1.1
       estree-walker: 3.0.3
       magic-string: 0.30.11
     optionalDependencies:
-      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
+      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
 
   '@vitest/pretty-format@2.1.1':
     dependencies:
@@ -20989,7 +21120,7 @@ snapshots:
       sirv: 2.0.4
       tinyglobby: 0.2.6
       tinyrainbow: 1.2.0
-      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.39.2)
+      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.42.0)
 
   '@vitest/utils@2.1.1':
     dependencies:
@@ -21191,6 +21322,10 @@ snapshots:
     dependencies:
       acorn: 8.14.1
 
+  acorn-import-attributes@1.9.5(acorn@8.15.0):
+    dependencies:
+      acorn: 8.15.0
+
   acorn-jsx@5.3.2(acorn@7.4.1):
     dependencies:
       acorn: 7.4.1
@@ -21205,6 +21340,8 @@ snapshots:
 
   acorn@8.14.1: {}
 
+  acorn@8.15.0: {}
+
   agent-base@6.0.2:
     dependencies:
       debug: 4.4.1(supports-color@10.0.0)
@@ -21606,12 +21743,12 @@ snapshots:
       '@types/babel__core': 7.20.5
       '@types/babel__traverse': 7.0.7
 
-  babel-plugin-superjson-next@0.4.5(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3):
+  babel-plugin-superjson-next@0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3):
     dependencies:
       '@babel/helper-module-imports': 7.24.6
       '@babel/types': 7.25.6
       hoist-non-react-statics: 3.3.2
-      next: 14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+      next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       superjson: 1.13.3
 
   babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.6):
@@ -21840,17 +21977,17 @@ snapshots:
 
   browserslist@4.24.4:
     dependencies:
-      caniuse-lite: 1.0.30001707
+      caniuse-lite: 1.0.30001723
       electron-to-chromium: 1.5.91
       node-releases: 2.0.19
       update-browserslist-db: 1.1.2(browserslist@4.24.4)
 
-  browserslist@4.24.5:
+  browserslist@4.25.0:
     dependencies:
-      caniuse-lite: 1.0.30001718
-      electron-to-chromium: 1.5.155
+      caniuse-lite: 1.0.30001723
+      electron-to-chromium: 1.5.167
       node-releases: 2.0.19
-      update-browserslist-db: 1.1.3(browserslist@4.24.5)
+      update-browserslist-db: 1.1.3(browserslist@4.25.0)
 
   bs-recipes@1.3.4: {}
 
@@ -22025,7 +22162,7 @@ snapshots:
 
   caniuse-lite@1.0.30001707: {}
 
-  caniuse-lite@1.0.30001718: {}
+  caniuse-lite@1.0.30001723: {}
 
   capital-case@1.0.4:
     dependencies:
@@ -23252,7 +23389,7 @@ snapshots:
     dependencies:
       jake: 10.9.1
 
-  electron-to-chromium@1.5.155: {}
+  electron-to-chromium@1.5.167: {}
 
   electron-to-chromium@1.5.91: {}
 
@@ -23543,7 +23680,7 @@ snapshots:
       object.assign: 4.1.5
       object.entries: 1.1.5
 
-  eslint-config-next@12.1.6(eslint@8.41.0)(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(typescript@5.0.4):
+  eslint-config-next@12.1.6(eslint@8.41.0)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(typescript@5.0.4):
     dependencies:
       '@next/eslint-plugin-next': 12.1.6
       '@rushstack/eslint-patch': 1.1.3
@@ -23555,7 +23692,7 @@ snapshots:
       eslint-plugin-jsx-a11y: 6.5.1(eslint@8.41.0)
       eslint-plugin-react: 7.30.1(eslint@8.41.0)
       eslint-plugin-react-hooks: 4.6.0(eslint@8.41.0)
-      next: 14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+      next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
     optionalDependencies:
       typescript: 5.0.4
     transitivePeerDependencies:
@@ -27328,7 +27465,7 @@ snapshots:
     dependencies:
       react: 18.2.0
 
-  next-i18next@15.3.1(i18next@23.16.5)(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
+  next-i18next@15.3.1(i18next@23.16.5)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
     dependencies:
       '@babel/runtime': 7.25.4
       '@types/hoist-non-react-statics': 3.3.5
@@ -27336,26 +27473,26 @@ snapshots:
       hoist-non-react-statics: 3.3.2
       i18next: 23.16.5
       i18next-fs-backend: 2.3.2
-      next: 14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+      next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       react: 18.2.0
       react-i18next: 15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
 
-  next-superjson@0.0.4(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15))):
+  next-superjson@0.0.4(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15))):
     dependencies:
       '@babel/core': 7.24.6
       '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.6)
       '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.6)
       babel-loader: 8.3.0(@babel/core@7.24.6)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15)))
-      babel-plugin-superjson-next: 0.4.5(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)
-      next: 14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+      babel-plugin-superjson-next: 0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)
+      next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
     transitivePeerDependencies:
       - superjson
       - supports-color
       - webpack
 
-  next-themes@0.2.1(next@14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+  next-themes@0.2.1(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
     dependencies:
-      next: 14.2.26(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+      next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
 
@@ -27387,6 +27524,34 @@ snapshots:
       - '@babel/core'
       - babel-plugin-macros
 
+  next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6):
+    dependencies:
+      '@next/env': 14.2.30
+      '@swc/helpers': 0.5.5
+      busboy: 1.6.0
+      caniuse-lite: 1.0.30001723
+      graceful-fs: 4.2.11
+      postcss: 8.4.31
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      styled-jsx: 5.1.1(@babel/core@7.24.6)(react@18.2.0)
+    optionalDependencies:
+      '@next/swc-darwin-arm64': 14.2.30
+      '@next/swc-darwin-x64': 14.2.30
+      '@next/swc-linux-arm64-gnu': 14.2.30
+      '@next/swc-linux-arm64-musl': 14.2.30
+      '@next/swc-linux-x64-gnu': 14.2.30
+      '@next/swc-linux-x64-musl': 14.2.30
+      '@next/swc-win32-arm64-msvc': 14.2.30
+      '@next/swc-win32-ia32-msvc': 14.2.30
+      '@next/swc-win32-x64-msvc': 14.2.30
+      '@opentelemetry/api': 1.9.0
+      '@playwright/test': 1.49.1
+      sass: 1.77.6
+    transitivePeerDependencies:
+      - '@babel/core'
+      - babel-plugin-macros
+
   nice-try@1.0.4: {}
 
   nimma@0.2.2:
@@ -28184,18 +28349,18 @@ snapshots:
     dependencies:
       postcss: 8.5.3
 
-  postcss-scss@4.0.9(postcss@8.5.3):
+  postcss-scss@4.0.9(postcss@8.5.5):
     dependencies:
-      postcss: 8.5.3
+      postcss: 8.5.5
 
   postcss-selector-parser@6.1.0:
     dependencies:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
 
-  postcss-sorting@8.0.2(postcss@8.5.3):
+  postcss-sorting@8.0.2(postcss@8.5.5):
     dependencies:
-      postcss: 8.5.3
+      postcss: 8.5.5
 
   postcss-value-parser@4.2.0: {}
 
@@ -28211,6 +28376,12 @@ snapshots:
       picocolors: 1.1.1
       source-map-js: 1.2.1
 
+  postcss@8.5.5:
+    dependencies:
+      nanoid: 3.3.11
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
   postgres-array@2.0.0: {}
 
   postgres-bytea@1.0.0: {}
@@ -29971,14 +30142,14 @@ snapshots:
       stylelint: 16.5.0(typescript@5.0.4)
       stylelint-order: 6.0.4(stylelint@16.5.0(typescript@5.0.4))
 
-  stylelint-config-recommended-scss@14.0.0(postcss@8.5.3)(stylelint@16.5.0(typescript@5.0.4)):
+  stylelint-config-recommended-scss@14.0.0(postcss@8.5.5)(stylelint@16.5.0(typescript@5.0.4)):
     dependencies:
-      postcss-scss: 4.0.9(postcss@8.5.3)
+      postcss-scss: 4.0.9(postcss@8.5.5)
       stylelint: 16.5.0(typescript@5.0.4)
       stylelint-config-recommended: 14.0.0(stylelint@16.5.0(typescript@5.0.4))
       stylelint-scss: 6.3.0(stylelint@16.5.0(typescript@5.0.4))
     optionalDependencies:
-      postcss: 8.5.3
+      postcss: 8.5.5
 
   stylelint-config-recommended@14.0.0(stylelint@16.5.0(typescript@5.0.4)):
     dependencies:
@@ -29986,8 +30157,8 @@ snapshots:
 
   stylelint-order@6.0.4(stylelint@16.5.0(typescript@5.0.4)):
     dependencies:
-      postcss: 8.5.3
-      postcss-sorting: 8.0.2(postcss@8.5.3)
+      postcss: 8.5.5
+      postcss-sorting: 8.0.2(postcss@8.5.5)
       stylelint: 16.5.0(typescript@5.0.4)
 
   stylelint-scss@6.3.0(stylelint@16.5.0(typescript@5.0.4)):
@@ -30274,15 +30445,15 @@ snapshots:
       jest-worker: 27.5.1
       schema-utils: 4.3.2
       serialize-javascript: 6.0.2
-      terser: 5.39.2
+      terser: 5.42.0
       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.39.2:
+  terser@5.42.0:
     dependencies:
       '@jridgewell/source-map': 0.3.6
-      acorn: 8.14.1
+      acorn: 8.15.0
       commander: 2.20.3
       source-map-support: 0.5.21
 
@@ -30888,9 +31059,9 @@ snapshots:
       escalade: 3.2.0
       picocolors: 1.1.1
 
-  update-browserslist-db@1.1.3(browserslist@4.24.5):
+  update-browserslist-db@1.1.3(browserslist@4.25.0):
     dependencies:
-      browserslist: 4.24.5
+      browserslist: 4.25.0
       escalade: 3.2.0
       picocolors: 1.1.1
 
@@ -31053,12 +31224,12 @@ snapshots:
       '@types/unist': 3.0.3
       vfile-message: 4.0.2
 
-  vite-node@2.1.1(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2):
+  vite-node@2.1.1(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0):
     dependencies:
       cac: 6.7.14
       debug: 4.4.0(supports-color@5.5.0)
       pathe: 1.1.2
-      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
+      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -31070,7 +31241,7 @@ snapshots:
       - supports-color
       - terser
 
-  vite-plugin-dts@3.9.1(@types/node@20.14.0)(rollup@4.41.0)(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)):
+  vite-plugin-dts@3.9.1(@types/node@20.14.0)(rollup@4.41.0)(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)):
     dependencies:
       '@microsoft/api-extractor': 7.43.0(@types/node@20.14.0)
       '@rollup/pluginutils': 5.1.0(rollup@4.41.0)
@@ -31081,24 +31252,24 @@ snapshots:
       typescript: 5.0.4
       vue-tsc: 1.8.27(typescript@5.0.4)
     optionalDependencies:
-      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
+      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
     transitivePeerDependencies:
       - '@types/node'
       - rollup
       - supports-color
 
-  vite-tsconfig-paths@5.0.1(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)):
+  vite-tsconfig-paths@5.0.1(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)):
     dependencies:
       debug: 4.4.0(supports-color@5.5.0)
       globrex: 0.1.2
       tsconfck: 3.0.3(typescript@5.0.4)
     optionalDependencies:
-      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
+      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
     transitivePeerDependencies:
       - supports-color
       - typescript
 
-  vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2):
+  vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.5.3
@@ -31107,18 +31278,18 @@ snapshots:
       '@types/node': 20.14.0
       fsevents: 2.3.3
       sass: 1.77.6
-      terser: 5.39.2
+      terser: 5.42.0
 
   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.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.39.2)
+      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.42.0)
 
-  vitest@2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.39.2):
+  vitest@2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.42.0):
     dependencies:
       '@vitest/expect': 2.1.1
-      '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2))
+      '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0))
       '@vitest/pretty-format': 2.1.1
       '@vitest/runner': 2.1.1
       '@vitest/snapshot': 2.1.1
@@ -31133,8 +31304,8 @@ snapshots:
       tinyexec: 0.3.0
       tinypool: 1.0.1
       tinyrainbow: 1.2.0
-      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
-      vite-node: 2.1.1(@types/node@20.14.0)(sass@1.77.6)(terser@5.39.2)
+      vite: 5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
+      vite-node: 2.1.1(@types/node@20.14.0)(sass@1.77.6)(terser@5.42.0)
       why-is-node-running: 2.3.0
     optionalDependencies:
       '@types/node': 20.14.0
@@ -31248,20 +31419,20 @@ snapshots:
       - bufferutil
       - utf-8-validate
 
-  webpack-sources@3.2.3: {}
+  webpack-sources@3.3.2: {}
 
   webpack-virtual-modules@0.6.2: {}
 
   webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15)):
     dependencies:
       '@types/eslint-scope': 3.7.7
-      '@types/estree': 1.0.7
+      '@types/estree': 1.0.8
       '@webassemblyjs/ast': 1.14.1
       '@webassemblyjs/wasm-edit': 1.14.1
       '@webassemblyjs/wasm-parser': 1.14.1
-      acorn: 8.14.1
-      acorn-import-attributes: 1.9.5(acorn@8.14.1)
-      browserslist: 4.24.5
+      acorn: 8.15.0
+      acorn-import-attributes: 1.9.5(acorn@8.15.0)
+      browserslist: 4.25.0
       chrome-trace-event: 1.0.4
       enhanced-resolve: 5.18.1
       es-module-lexer: 1.7.0
@@ -31277,7 +31448,7 @@ snapshots:
       tapable: 2.2.2
       terser-webpack-plugin: 5.3.14(@swc/core@1.10.7(@swc/helpers@0.5.15))(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15)))
       watchpack: 2.4.4
-      webpack-sources: 3.2.3
+      webpack-sources: 3.3.2
     transitivePeerDependencies:
       - '@swc/core'
       - esbuild