Browse Source

Merge branch 'master' into feat/97800-159429-add-shortcuts-when-editing

WNomunomu 1 year ago
parent
commit
559d4a3096
100 changed files with 1528 additions and 253 deletions
  1. 9 5
      .devcontainer/app/devcontainer.json
  2. 9 0
      .devcontainer/app/initializeCommand.sh
  3. 5 0
      .devcontainer/app/postCreateCommand.sh
  4. 12 0
      .devcontainer/compose.extend.template.yml
  5. 3 1
      .devcontainer/compose.yml
  6. 30 0
      .devcontainer/pdf-converter/devcontainer.json
  7. 9 0
      .devcontainer/pdf-converter/initializeCommand.sh
  8. 22 0
      .devcontainer/pdf-converter/postCreateCommand.sh
  9. 1 1
      .github/workflows/auto-approve.yml
  10. 1 1
      .github/workflows/auto-labeling.yml
  11. 168 0
      .github/workflows/ci-pdf-converter.yml
  12. 1 1
      .github/workflows/draft-release.yml
  13. 120 0
      .github/workflows/release-pdf-converter.yml
  14. 1 6
      .github/workflows/release-slackbot-proxy.yml
  15. 1 1
      .github/workflows/reusable-app-create-manifests.yml
  16. 23 5
      .github/workflows/reusable-app-prod.yml
  17. 1 1
      .github/workflows/reusable-app-reg-suit.yml
  18. 2 0
      .gitignore
  19. 2 0
      .npmrc
  20. 79 1
      CHANGELOG.md
  21. 1 1
      apps/app/.env.production
  22. 16 1
      apps/app/bin/swagger-jsdoc/definition-apiv3.js
  23. 5 0
      apps/app/bin/swagger-jsdoc/generate-spec-apiv3.sh
  24. 29 19
      apps/app/docker/Dockerfile
  25. 5 1
      apps/app/docker/docker-entrypoint.sh
  26. 12 5
      apps/app/package.json
  27. 2 0
      apps/app/playwright.config.ts
  28. 39 5
      apps/app/playwright/20-basic-features/use-tools.spec.ts
  29. 13 10
      apps/app/public/static/locales/en_US/admin.json
  30. 142 16
      apps/app/public/static/locales/en_US/translation.json
  31. 13 10
      apps/app/public/static/locales/fr_FR/admin.json
  32. 142 16
      apps/app/public/static/locales/fr_FR/translation.json
  33. 13 11
      apps/app/public/static/locales/ja_JP/admin.json
  34. 143 17
      apps/app/public/static/locales/ja_JP/translation.json
  35. 13 10
      apps/app/public/static/locales/zh_CN/admin.json
  36. 145 18
      apps/app/public/static/locales/zh_CN/translation.json
  37. 1 1
      apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx
  38. 11 0
      apps/app/src/client/components/Admin/App/AppSettingsPageContents.tsx
  39. 3 0
      apps/app/src/client/components/Admin/App/AwsSetting.tsx
  40. 3 0
      apps/app/src/client/components/Admin/App/AzureSetting.tsx
  41. 10 6
      apps/app/src/client/components/Admin/App/FileUploadSetting.tsx
  42. 3 0
      apps/app/src/client/components/Admin/App/GcsSetting.tsx
  43. 1 1
      apps/app/src/client/components/Admin/App/MaskedInput.tsx
  44. 136 0
      apps/app/src/client/components/Admin/App/PageBulkExportSettings.tsx
  45. 43 29
      apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx
  46. 1 1
      apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx
  47. 1 1
      apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx
  48. 1 1
      apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx
  49. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeCssSetting.tsx
  50. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeFunctionOption.tsx
  51. 15 1
      apps/app/src/client/components/Admin/Customize/CustomizeFunctionSetting.tsx
  52. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeLayoutSetting.tsx
  53. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeLogoSetting.tsx
  54. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  55. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizePresentationSetting.tsx
  56. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeScriptSetting.tsx
  57. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeSidebarSetting.tsx
  58. 1 1
      apps/app/src/client/components/Admin/Customize/CustomizeThemeOptions.tsx
  59. 3 1
      apps/app/src/client/components/Admin/Customize/CustomizeThemeSetting.tsx
  60. 1 1
      apps/app/src/client/components/Admin/Customize/ThemeColorBox.tsx
  61. 1 1
      apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx
  62. 1 1
      apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx
  63. 1 1
      apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx
  64. 1 1
      apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx
  65. 3 1
      apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx
  66. 3 1
      apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx
  67. 1 1
      apps/app/src/client/components/Admin/ForbiddenPage.tsx
  68. 1 1
      apps/app/src/client/components/Admin/FullTextSearchManagement.tsx
  69. 1 1
      apps/app/src/client/components/Admin/G2GDataTransfer.tsx
  70. 1 1
      apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx
  71. 1 1
      apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx
  72. 1 1
      apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx
  73. 1 1
      apps/app/src/client/components/Admin/ManageExternalAccount.tsx
  74. 1 1
      apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx
  75. 1 1
      apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx
  76. 1 1
      apps/app/src/client/components/Admin/NotFoundPage.tsx
  77. 1 1
      apps/app/src/client/components/Admin/Notification/ManageGlobalNotification.tsx
  78. 1 1
      apps/app/src/client/components/Admin/Notification/NotificationTypeIcon.tsx
  79. 1 1
      apps/app/src/client/components/Admin/Security/LdapAuthTest.tsx
  80. 1 1
      apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx
  81. 3 0
      apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx
  82. 1 1
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx
  83. 1 1
      apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx
  84. 2 1
      apps/app/src/client/components/Admin/SlackIntegration/MessageBasedOnConnection.jsx
  85. 3 0
      apps/app/src/client/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx
  86. 3 1
      apps/app/src/client/components/Admin/SlackIntegration/SlackIntegration.tsx
  87. 2 1
      apps/app/src/client/components/Admin/UserGroup/UserGroupDropdown.tsx
  88. 1 1
      apps/app/src/client/components/Admin/UserGroup/UserGroupTable.tsx
  89. 1 1
      apps/app/src/client/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  90. 3 1
      apps/app/src/client/components/Admin/UserGroupDetail/UserGroupPageList.tsx
  91. 1 1
      apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserModal.tsx
  92. 1 1
      apps/app/src/client/components/Admin/UserGroupDetail/UserGroupUserTable.tsx
  93. 1 1
      apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx
  94. 1 1
      apps/app/src/client/components/Admin/Users/GrantAdminButton.tsx
  95. 1 1
      apps/app/src/client/components/Admin/Users/GrantReadOnlyButton.tsx
  96. 1 1
      apps/app/src/client/components/Admin/Users/RevokeAdminButton.tsx
  97. 1 1
      apps/app/src/client/components/Admin/Users/RevokeAdminMenuItem.tsx
  98. 1 1
      apps/app/src/client/components/Admin/Users/RevokeReadOnlyMenuItem.tsx
  99. 1 1
      apps/app/src/client/components/Admin/Users/SortIcons.tsx
  100. 1 1
      apps/app/src/client/components/Admin/Users/StatusSuspendMenuItem.tsx

+ 9 - 5
.devcontainer/devcontainer.json → .devcontainer/app/devcontainer.json

@@ -2,21 +2,22 @@
 // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
 // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
 {
 {
   "name": "GROWI-Dev",
   "name": "GROWI-Dev",
-  "dockerComposeFile": "compose.yml",
-  "service": "node",
+  "dockerComposeFile": ["../compose.yml", "../compose.extend.yml"],
+  "service": "app",
   "workspaceFolder": "/workspace/growi",
   "workspaceFolder": "/workspace/growi",
 
 
   "features": {
   "features": {
     "ghcr.io/devcontainers/features/node:1": {
     "ghcr.io/devcontainers/features/node:1": {
-      "version": "20.18.0"
+      "version": "20.18.3"
     }
     }
   },
   },
 
 
   // Use 'forwardPorts' to make a list of ports inside the container available locally.
   // Use 'forwardPorts' to make a list of ports inside the container available locally.
   // "forwardPorts": [],
   // "forwardPorts": [],
 
 
+  "initializeCommand": "/bin/bash .devcontainer/pdf-converter/initializeCommand.sh",
   // Use 'postCreateCommand' to run commands after the container is created.
   // Use 'postCreateCommand' to run commands after the container is created.
-  "postCreateCommand": "/bin/bash ./.devcontainer/postCreateCommand.sh",
+  "postCreateCommand": "/bin/bash ./.devcontainer/app/postCreateCommand.sh",
 
 
   // Configure tool-specific properties.
   // Configure tool-specific properties.
   "customizations": {
   "customizations": {
@@ -37,7 +38,10 @@
         "vitest.explorer",
         "vitest.explorer",
         "ms-playwright.playwright"
         "ms-playwright.playwright"
       ],
       ],
-    }
+      "settings": {
+        "terminal.integrated.defaultProfile.linux": "bash"
+      }
+    },
   },
   },
 
 
   // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
   // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.

+ 9 - 0
.devcontainer/app/initializeCommand.sh

@@ -0,0 +1,9 @@
+# prevent file not found error on docker compose up
+if [ ! -f ".devcontainer/compose.extend.yml" ]; then
+
+cat > ".devcontainer/compose.extend.yml" <<EOF
+services:
+  {}
+EOF
+
+fi

+ 5 - 0
.devcontainer/postCreateCommand.sh → .devcontainer/app/postCreateCommand.sh

@@ -6,6 +6,11 @@ sudo apt-get install -y --no-install-recommends \
   iputils-ping net-tools dnsutils
   iputils-ping net-tools dnsutils
 sudo apt-get clean -y
 sudo apt-get clean -y
 
 
+# Set permissions for shared directory for bulk export
+mkdir -p /tmp/page-bulk-export
+sudo chown -R vscode:vscode /tmp/page-bulk-export
+sudo chmod 700 /tmp/page-bulk-export
+
 # Setup pnpm
 # Setup pnpm
 SHELL=bash pnpm setup
 SHELL=bash pnpm setup
 eval "$(cat /home/vscode/.bashrc)"
 eval "$(cat /home/vscode/.bashrc)"

+ 12 - 0
.devcontainer/compose.extend.template.yml

@@ -0,0 +1,12 @@
+# A template of the file for extending the primary docker compose configuration.
+# To actually use this file, create a `compose.extend.yml` file and copy the contents of this file into it.
+services:
+  pdf-converter:
+    # enabling devcontainer 'features' was not working for secondary devcontainer (https://github.com/devcontainers/features/issues/1175)
+    image: mcr.microsoft.com/vscode/devcontainers/javascript-node:1-20
+    volumes:
+      - ..:/workspace/growi:delegated
+      - pnpm-store:/workspace/growi/.pnpm-store
+      - node_modules:/workspace/growi/node_modules
+      - page_bulk_export_tmp:/tmp/page-bulk-export
+    tty: true

+ 3 - 1
.devcontainer/compose.yml

@@ -1,5 +1,5 @@
 services:
 services:
-  node:
+  app:
     image: mcr.microsoft.com/devcontainers/base:ubuntu
     image: mcr.microsoft.com/devcontainers/base:ubuntu
     volumes:
     volumes:
       - ..:/workspace/growi:delegated
       - ..:/workspace/growi:delegated
@@ -8,6 +8,7 @@ services:
       - buildcache_app:/workspace/growi/apps/app/.next
       - buildcache_app:/workspace/growi/apps/app/.next
       - ../../growi-docker-compose:/workspace/growi-docker-compose:delegated
       - ../../growi-docker-compose:/workspace/growi-docker-compose:delegated
       - ../../share:/workspace/share:delegated
       - ../../share:/workspace/share:delegated
+      - page_bulk_export_tmp:/tmp/page-bulk-export
     tty: true
     tty: true
     networks:
     networks:
     - default
     - default
@@ -48,6 +49,7 @@ volumes:
   pnpm-store:
   pnpm-store:
   node_modules:
   node_modules:
   buildcache_app:
   buildcache_app:
+  page_bulk_export_tmp:
 
 
 networks:
 networks:
   default:
   default:

+ 30 - 0
.devcontainer/pdf-converter/devcontainer.json

@@ -0,0 +1,30 @@
+{
+  "name": "GROWI-PDF-Converter",
+  "dockerComposeFile": ["../compose.yml", "../compose.extend.yml"],
+  "service": "pdf-converter",
+  "workspaceFolder": "/workspace/growi",
+
+  // Use 'forwardPorts' to make a list of ports inside the container available locally.
+  // "forwardPorts": [],
+
+  "initializeCommand": "/bin/bash .devcontainer/pdf-converter/initializeCommand.sh",
+  // Use 'postCreateCommand' to run commands after the container is created.
+  "postCreateCommand": "/bin/bash ./.devcontainer/pdf-converter/postCreateCommand.sh",
+
+  // Configure tool-specific properties.
+  "customizations": {
+    "vscode": {
+      "extensions": [
+        "dbaeumer.vscode-eslint",
+        "mhutchie.git-graph",
+        "eamodio.gitlens"
+      ],
+      "settings": {
+        "terminal.integrated.defaultProfile.linux": "bash"
+      }
+    }
+  }
+
+  // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+  // "remoteUser": "root"
+}

+ 9 - 0
.devcontainer/pdf-converter/initializeCommand.sh

@@ -0,0 +1,9 @@
+# prevent file not found error on docker compose up
+if [ ! -f ".devcontainer/compose.extend.yml" ]; then
+
+cat > ".devcontainer/compose.extend.yml" <<EOF
+services:
+  {}
+EOF
+
+fi

+ 22 - 0
.devcontainer/pdf-converter/postCreateCommand.sh

@@ -0,0 +1,22 @@
+# Instal additional packages
+sudo apt update
+sudo apt-get install -y --no-install-recommends \
+  chromium fonts-lato fonts-ipafont-gothic fonts-noto-cjk
+sudo apt-get clean -y
+
+# Set permissions for shared directory for bulk export
+mkdir -p /tmp/page-bulk-export
+sudo chown -R node:node /tmp/page-bulk-export
+sudo chmod 700 /tmp/page-bulk-export
+
+# Setup pnpm
+SHELL=bash pnpm setup
+eval "$(cat /home/node/.bashrc)"
+# Update pnpm
+pnpm i -g pnpm
+
+# Install turbo
+pnpm install turbo --global
+
+# Install dependencies
+turbo run bootstrap

+ 1 - 1
.github/workflows/auto-approve.yml

@@ -16,7 +16,7 @@ jobs:
     steps:
     steps:
       - name: Dependabot metadata
       - name: Dependabot metadata
         id: dependabot-metadata
         id: dependabot-metadata
-        uses: dependabot/fetch-metadata@v1
+        uses: dependabot/fetch-metadata@v2
         with:
         with:
           github-token: '${{ secrets.GITHUB_TOKEN }}'
           github-token: '${{ secrets.GITHUB_TOKEN }}'
       - name: Approve a PR
       - name: Approve a PR

+ 1 - 1
.github/workflows/auto-labeling.yml

@@ -25,7 +25,7 @@ jobs:
         && !startsWith( github.head_ref, 'mergify/merge-queue/' ))
         && !startsWith( github.head_ref, 'mergify/merge-queue/' ))
 
 
     steps:
     steps:
-      - uses: release-drafter/release-drafter@v5
+      - uses: release-drafter/release-drafter@v6
         with:
         with:
           disable-releaser: true
           disable-releaser: true
         env:
         env:

+ 168 - 0
.github/workflows/ci-pdf-converter.yml

@@ -0,0 +1,168 @@
+name: Node CI for pdf-converter
+
+on:
+  push:
+    branches-ignore:
+      - release/**
+      - rc/**
+      - support/prepare-v**
+    paths:
+      - .github/mergify.yml
+      - .github/workflows/ci-pdf-converter.yml
+      - .eslint*
+      - tsconfig.base.json
+      - turbo.json
+      - pnpm-lock.yaml
+      - package.json
+      - apps/pdf-converter/**
+      - '!apps/pdf-converter/docker/**'
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+jobs:
+
+  ci-pdf-converter-lint:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        node-version: [20.x]
+
+    steps:
+    - uses: actions/checkout@v4
+
+    - uses: pnpm/action-setup@v4
+
+    - uses: actions/setup-node@v4
+      with:
+        node-version: ${{ matrix.node-version }}
+        cache: 'pnpm'
+
+    - name: Install dependencies
+      run: |
+        pnpm add turbo --global
+        pnpm install --frozen-lockfile
+
+    - name: Lint
+      run: |
+        turbo run lint --filter=@growi/pdf-converter
+
+    - name: Slack Notification
+      uses: weseek/ghaction-slack-notification@master
+      if: failure()
+      with:
+        type: ${{ job.status }}
+        job_name: '*Node CI for growi-pdf-converter - test (${{ matrix.node-version }})*'
+        channel: '#ci'
+        isCompactMode: true
+        url: ${{ secrets.SLACK_WEBHOOK_URL }}
+
+  ci-pdf-converter-launch-dev:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        node-version: [20.x]
+
+    steps:
+    - uses: actions/checkout@v4
+
+    - uses: pnpm/action-setup@v4
+
+    - uses: actions/setup-node@v4
+      with:
+        node-version: ${{ matrix.node-version }}
+        cache: 'pnpm'
+
+    - name: Install dependencies
+      run: |
+        pnpm add turbo --global
+        pnpm install --frozen-lockfile
+
+    - name: turbo run dev:pdf-converter:ci
+      working-directory: ./apps/pdf-converter
+      run: turbo run dev:pdf-converter:ci
+
+    - name: Slack Notification
+      uses: weseek/ghaction-slack-notification@master
+      if: failure()
+      with:
+        type: ${{ job.status }}
+        job_name: '*Node CI for growi-pdf-converter - launch-dev (${{ matrix.node-version }})*'
+        channel: '#ci'
+        isCompactMode: true
+        url: ${{ secrets.SLACK_WEBHOOK_URL }}
+
+  ci-pdf-converter-launch-prod:
+
+    if: startsWith(github.head_ref, 'mergify/merge-queue/')
+
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        node-version: [20.x]
+
+    steps:
+    - uses: actions/checkout@v4
+
+    - uses: pnpm/action-setup@v4
+
+    - uses: actions/setup-node@v4
+      with:
+        node-version: ${{ matrix.node-version }}
+        cache: 'pnpm'
+
+    - name: Install turbo
+      run: |
+        pnpm add turbo --global
+
+    - name: Install dependencies
+      run: |
+        pnpm install --frozen-lockfile
+
+    - name: Restore dist
+      uses: actions/cache/restore@v4
+      with:
+        path: |
+          **/.turbo
+          **/dist
+        key: dist-pdf-converter-prod-${{ runner.OS }}-node${{ matrix.node-version }}-${{ github.sha }}
+        restore-keys: |
+          dist-pdf-converter-prod-${{ runner.OS }}-node${{ matrix.node-version }}-
+
+    - name: Build
+      working-directory: ./apps/pdf-converter
+      run: |
+        turbo run build
+
+    - name: Assembling all dependencies
+      run: |
+        rm -rf out
+        pnpm deploy out --prod --filter @growi/pdf-converter
+        rm -rf apps/pdf-converter/node_modules && mv out/node_modules apps/pdf-converter/node_modules
+
+    - name: pnpm run start:prod:ci
+      working-directory: ./apps/pdf-converter
+      run: pnpm run start:prod:ci
+
+    - name: Slack Notification
+      uses: weseek/ghaction-slack-notification@master
+      if: failure()
+      with:
+        type: ${{ job.status }}
+        job_name: '*Node CI for growi-pdf-converter - launch-prod (${{ matrix.node-version }})*'
+        channel: '#ci'
+        isCompactMode: true
+        url: ${{ secrets.SLACK_WEBHOOK_URL }}
+
+    - name: Cache dist
+      uses: actions/cache/save@v4
+      with:
+        path: |
+          **/.turbo
+          **/dist
+        key: dist-pdf-converter-prod-${{ runner.OS }}-node${{ matrix.node-version }}-${{ github.sha }}

+ 1 - 1
.github/workflows/draft-release.yml

@@ -29,7 +29,7 @@ jobs:
         uses: myrotvorets/info-from-package-json-action@v2.0.2
         uses: myrotvorets/info-from-package-json-action@v2.0.2
         id: package-json
         id: package-json
 
 
-      - uses: release-drafter/release-drafter@v5
+      - uses: release-drafter/release-drafter@v6
         id: release-drafter
         id: release-drafter
         with:
         with:
           config-name: release-drafter.yml
           config-name: release-drafter.yml

+ 120 - 0
.github/workflows/release-pdf-converter.yml

@@ -0,0 +1,120 @@
+name: Release Docker Image for @growi/pdf-converter
+
+on:
+  pull_request:
+    branches:
+      - release/pdf-converter/**
+    types: [closed]
+
+jobs:
+  build-and-push-image:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        ref: ${{ github.event.pull_request.base.ref }}
+
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@2.0.1
+      id: package-json
+      with:
+        workingDir: apps/pdf-converter
+
+    - name: Docker meta
+      id: meta
+      uses: docker/metadata-action@v4
+      with:
+        images: growilabs/pdf-converter
+        tags: |
+          type=raw,value=latest
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}
+
+    - name: Login to docker.io registry
+      run: |
+        echo ${{ secrets.DOCKER_REGISTRY_PASSWORD_GROWIMOOGLE }} | docker login --username growimoogle --password-stdin
+
+    - name: Set up Docker Buildx
+      uses: docker/setup-buildx-action@v3
+
+    - name: Build and push
+      uses: docker/build-push-action@v6
+      with:
+        context: .
+        file: ./apps/pdf-converter/docker/Dockerfile
+        platforms: linux/amd64,linux/arm64
+        push: true
+        builder: ${{ steps.buildx.outputs.name }}
+        cache-from: type=gha
+        cache-to: type=gha,mode=max
+        tags: ${{ steps.meta.outputs.tags }}
+
+    - name: Add tag
+      uses: anothrNick/github-tag-action@v1
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        CUSTOM_TAG: pdf-converter/v${{ steps.package-json.outputs.packageVersion }}
+        VERBOSE : true
+
+    - name: Update Docker Hub Description
+      uses: peter-evans/dockerhub-description@v3
+      with:
+        username: growimoogle
+        password: ${{ secrets.DOCKER_REGISTRY_PASSWORD_GROWIMOOGLE }}
+        repository: growilabs/pdf-converter
+        readme-filepath: ./apps/pdf-converter/docker/README.md
+
+
+  create-pr-for-next-rc:
+    needs: build-and-push-image
+
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        node-version: [20.x]
+
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        ref: ${{ github.event.pull_request.base.ref }}
+
+    - uses: pnpm/action-setup@v4
+
+    - uses: actions/setup-node@v4
+      with:
+        node-version: ${{ matrix.node-version }}
+        cache: 'pnpm'
+
+    - name: Install dependencies
+      run: |
+        pnpm add turbo --global
+        pnpm install --frozen-lockfile
+
+    - name: Bump versions for next RC
+      run: |
+        turbo run version:prerelease --filter=@growi/pdf-converter
+
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@2.0.1
+      id: package-json
+      with:
+        workingDir: apps/pdf-converter
+
+    - name: Commit
+      uses: github-actions-x/commit@v2.9
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        push-branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
+        commit-message: 'Bump version'
+        name: GitHub Action
+
+    - name: Create PR
+      uses: repo-sync/pull-request@v2
+      with:
+        source_branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
+        destination_branch: master
+        pr_title: Prepare pdf-converter v${{ steps.package-json.outputs.packageVersion }}
+        pr_label: flag/exclude-from-changelog,type/prepare-next-version
+        pr_body: "An automated PR generated by ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+        github_token: ${{ secrets.GITHUB_TOKEN }}

+ 1 - 6
.github/workflows/release-slackbot-proxy.yml

@@ -51,7 +51,7 @@ jobs:
       uses: docker/setup-buildx-action@v3
       uses: docker/setup-buildx-action@v3
 
 
     - name: Build and push
     - name: Build and push
-      uses: docker/build-push-action@v4
+      uses: docker/build-push-action@v6
       with:
       with:
         context: .
         context: .
         file: ./apps/slackbot-proxy/docker/Dockerfile
         file: ./apps/slackbot-proxy/docker/Dockerfile
@@ -62,11 +62,6 @@ jobs:
         cache-to: type=gha,mode=max
         cache-to: type=gha,mode=max
         tags: ${{ steps.meta.outputs.tags }}
         tags: ${{ steps.meta.outputs.tags }}
 
 
-    - name: Move cache
-      run: |
-        rm -rf /tmp/.buildx-cache
-        mv /tmp/.buildx-cache-new /tmp/.buildx-cache
-
     - name: Add tag
     - name: Add tag
       uses: anothrNick/github-tag-action@v1
       uses: anothrNick/github-tag-action@v1
       env:
       env:

+ 1 - 1
.github/workflows/reusable-app-create-manifests.yml

@@ -45,7 +45,7 @@ jobs:
         password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
         password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 
 
     - name: Create and push manifest images
     - name: Create and push manifest images
-      uses: Noelware/docker-manifest-action@master
+      uses: Noelware/docker-manifest-action@0.4.3
       with:
       with:
         base-image: ${{ inputs.tags }}
         base-image: ${{ inputs.tags }}
         extra-images: ${{ steps.meta-extra-images.outputs.tags }}
         extra-images: ${{ steps.meta-extra-images.outputs.tags }}

+ 23 - 5
.github/workflows/reusable-app-prod.yml

@@ -262,12 +262,19 @@ jobs:
         MONGO_URI: mongodb://mongodb:27017/growi-playwright-guest-mode
         MONGO_URI: mongodb://mongodb:27017/growi-playwright-guest-mode
         ELASTICSEARCH_URI: http://localhost:${{ job.services.elasticsearch.ports['9200'] }}/growi
         ELASTICSEARCH_URI: http://localhost:${{ job.services.elasticsearch.ports['9200'] }}/growi
 
 
-    - name: Upload test results
+    - name: Generate shard ID
+      id: shard-id
       if: always()
       if: always()
+      run: |
+        SHARD_ID=$(echo "${{ matrix.shard }}" | tr '/' '-')
+        echo "shard_id=${SHARD_ID}" >> $GITHUB_OUTPUT
+
+    - name: Upload test results
       uses: actions/upload-artifact@v4
       uses: actions/upload-artifact@v4
+      if: always()
       with:
       with:
-        name: blob-report-${{ matrix.shard }}
-        path: blob-report
+        name: blob-report-${{ matrix.browser }}-${{ steps.shard-id.outputs.shard_id }}
+        path: ./apps/app/blob-report
         retention-days: 30
         retention-days: 30
 
 
     - name: Slack Notification
     - name: Slack Notification
@@ -302,10 +309,21 @@ jobs:
       run: |
       run: |
         pnpm install --frozen-lockfile
         pnpm install --frozen-lockfile
 
 
+    - name: Download blob reports
+      uses: actions/download-artifact@v4
+      with:
+        pattern: blob-report-*
+        path: all-blob-reports
+        merge-multiple: true
+
     - name: Merge into HTML Report
     - name: Merge into HTML Report
       run: |
       run: |
-        mkdir -p all-blob-reports
-        pnpm playwright merge-reports --reporter html ./all-blob-reports
+        mkdir -p playwright-report
+        if [ -z "$(ls all-blob-reports/*.zip all-blob-reports/*.blob 2>/dev/null || true)" ]; then
+          echo "<html><body><h1>No test results available</h1><p>This could be because tests were skipped or all artifacts were not available.</p></body></html>" > playwright-report/index.html
+        else
+          pnpm playwright merge-reports --reporter html all-blob-reports
+        fi
 
 
     - name: Upload HTML report
     - name: Upload HTML report
       uses: actions/upload-artifact@v4
       uses: actions/upload-artifact@v4

+ 1 - 1
.github/workflows/reusable-app-reg-suit.yml

@@ -1,4 +1,4 @@
-name: Reusable build app workflow for production
+name: Reusable VRT reporting workflow for production
 
 
 on:
 on:
   workflow_call:
   workflow_call:

+ 2 - 0
.gitignore

@@ -44,3 +44,5 @@ yarn-error.log*
 
 
 # pnpm deploy target dir
 # pnpm deploy target dir
 out
 out
+
+.devcontainer/compose.extend.yml

+ 2 - 0
.npmrc

@@ -0,0 +1,2 @@
+# see: https://pnpm.io/next/npmrc#force-legacy-deploy
+force-legacy-deploy=true

+ 79 - 1
CHANGELOG.md

@@ -1,9 +1,87 @@
 # Changelog
 # Changelog
 
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.1.9...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.2.2...HEAD)
 
 
 *Please do not manually update this file. We've automated the process.*
 *Please do not manually update this file. We've automated the process.*
 
 
+## [v7.2.2](https://github.com/weseek/growi/compare/v7.2.1...v7.2.2) - 2025-04-17
+
+### 🐛 Bug Fixes
+
+* fix: Export page API is broken (#9870) @yuki-takei
+
+## [v7.2.1](https://github.com/weseek/growi/compare/v7.2.0...v7.2.1) - 2025-04-16
+
+### 💎 Features
+
+* feat: Page bulk export (pdf export included) (#9678) @arafubeatbox
+
+### 🚀 Improvement
+
+* imprv: Add util method to get react instance of growi via growifacade (#9775) @NaokiHigashi28
+* imprv: Adjust comment editor clickable area (#9840) @yuki-takei
+* imprv: Make user activation middleware securely (#9818) @yuki-takei
+* imprv: Prepare to upgrade React and Next.js (#9816) @yuki-takei
+
+### 🐛 Bug Fixes
+
+* fix: The order of multer middleware (#9772) @NaokiHigashi28
+* fix: growi pluginkit growifacade typo (#9812) @NaokiHigashi28
+* fix: Migration script to remove index for 'ns' from config collection (#9770) @yuki-takei
+* fix: Assistant remains default when made unpublished (#9763) @miya
+* fix: Fix layout when safari browse (#9744) @satof3
+* fix:  Prevent GrowiPlugin from being downloaded outside the plugin directory (#9712) @NaokiHigashi28
+* fix: The link for Telemetry of GROWI Docs (#9737) @yuki-takei
+
+### 🧰 Maintenance
+
+* ci(deps): bump next from 14.2.22 to 14.2.25 (#9761) @[dependabot[bot]](https://github.com/apps/dependabot)
+* ci(deps-dev): bump vite from 5.4.16 to 5.4.17 (#9837) @[dependabot[bot]](https://github.com/apps/dependabot)
+* ci(deps-dev): bump vite from 5.4.15 to 5.4.16 (#9830) @[dependabot[bot]](https://github.com/apps/dependabot)
+* support: Upgrade Next.js (#9825) @yuki-takei
+* support: Upgrade SWR (#9814) @yuki-takei
+* ci(deps-dev): bump vite from 5.4.14 to 5.4.15 (#9781) @[dependabot[bot]](https://github.com/apps/dependabot)
+* ci(deps): bump dependabot/fetch-metadata from 1 to 2 (#9636) @[dependabot[bot]](https://github.com/apps/dependabot)
+* ci(deps): bump docker/build-push-action from 4 to 6 (#9638) @[dependabot[bot]](https://github.com/apps/dependabot)
+* ci(deps): bump release-drafter/release-drafter from 5 to 6 (#9637) @[dependabot[bot]](https://github.com/apps/dependabot)
+
+## [v7.2.0](https://github.com/weseek/growi/compare/v7.1.9...v7.2.0) - 2025-03-11
+
+### 💎 Features
+
+* feat: GROWI AI Next Gen (#9492) @miya
+* feat: Support OpenTelemetry (#8810) @yuki-takei
+* feat: Add AuthorInfo display setting to PageSideContents (#9689) @satof3
+* feat: Expose React Insance to window via GrowiFacade (#9729) @NaokiHigashi28
+* feat: Normalize remark growi directives for v6.0.x or above (#9690) @yuki-takei
+
+### 🚀 Improvement
+
+* imprv: Fix RecentChanges dropdown label (#9711) @satof3
+* imprv: Border color for dark mode (#9695) @satof3
+* imprv: Update shortcut key modal (#9651) @satof3
+* imprv: Suppresses unnecessary re-rendering within PageEditor (#9629) @reiji-h
+
+### 🐛 Bug Fixes
+
+* fix: Redirection after login does not work on systems with guest mode enabled (#9653) @reiji-h
+* fix: Data migration script for CSV and TSV (#9641) @miya
+* fix: Authenticate before uploading at /_api/v3/import/upload endpoint (#9647) @NaokiHigashi28
+* fix: Add XSS filter to remark-attachment-refs /refs endpoint (#9631) @NaokiHigashi28
+* fix: PageTree auto-scrolling sometimges not woking (#9544) @reiji-h
+* fix: Middlewares about installation (#9616) @yuki-takei
+* fix: Typo for bookmark API (#9613) @yuki-takei
+
+### 🧰 Maintenance
+
+* support: Upgrade runtime versions (#9655) @yuki-takei
+* support: Display brand logo when editor mode (#9632) @satof3
+* support: Upgrade CodeMirror (#9633) @yuki-takei
+* ci(deps): bump docker/login-action from 2 to 3 (#8208) @dependabot
+* ci(deps): bump google-github-actions/auth from 1 to 2 (#9557) @dependabot
+* ci(deps): bump myrotvorets/info-from-package-json-action from 2.0.1 to 2.0.2 (#9558) @dependabot
+* support: Remove legacy ConfigManager (#9624) @yuki-takei
+
 ## [v7.1.9](https://github.com/weseek/growi/compare/v7.1.8...v7.1.9) - 2025-02-03
 ## [v7.1.9](https://github.com/weseek/growi/compare/v7.1.8...v7.1.9) - 2025-02-03
 
 
 ### 💎 Features
 ### 💎 Features

+ 1 - 1
apps/app/.env.production

@@ -7,4 +7,4 @@ MIGRATIONS_DIR=dist/migrations/
 
 
 # OpenTelemetry Configuration
 # OpenTelemetry Configuration
 OTEL_TRACES_SAMPLER_ARG=0.1
 OTEL_TRACES_SAMPLER_ARG=0.1
-
+OTEL_EXPORTER_OTLP_ENDPOINT="https://telemetry.growi.org"

+ 16 - 1
apps/app/bin/swagger-jsdoc/definition-apiv3.js

@@ -28,6 +28,11 @@ module.exports = {
         in: 'cookie',
         in: 'cookie',
         name: 'connect.sid',
         name: 'connect.sid',
       },
       },
+      transferHeaderAuth: {
+        type: 'apiKey',
+        in: 'header',
+        name: 'x-growi-transfer-key',
+      },
     },
     },
   },
   },
   'x-tagGroups': [
   'x-tagGroups': [
@@ -39,10 +44,11 @@ module.exports = {
         'BookmarkFolders',
         'BookmarkFolders',
         'Page',
         'Page',
         'Pages',
         'Pages',
+        'PageListing',
         'Revisions',
         'Revisions',
         'ShareLinks',
         'ShareLinks',
         'Users',
         'Users',
-        '',
+        'UserUISettings',
         '',
         '',
       ],
       ],
     },
     },
@@ -63,20 +69,29 @@ module.exports = {
       name: 'System Management API',
       name: 'System Management API',
       tags: [
       tags: [
         'Home',
         'Home',
+        'Activity',
         'AdminHome',
         'AdminHome',
         'AppSettings',
         'AppSettings',
+        'ExternalUserGroups',
         'SecuritySetting',
         'SecuritySetting',
         'MarkDownSetting',
         'MarkDownSetting',
         'CustomizeSetting',
         'CustomizeSetting',
         'Import',
         'Import',
         'Export',
         'Export',
+        'GROWI to GROWI Transfer',
         'MongoDB',
         'MongoDB',
         'NotificationSetting',
         'NotificationSetting',
+        'Plugins',
+        'Questionnaire',
+        'QuestionnaireSetting',
+        'SlackIntegration',
         'SlackIntegrationSettings',
         'SlackIntegrationSettings',
         'SlackIntegrationSettings (with proxy)',
         'SlackIntegrationSettings (with proxy)',
         'SlackIntegrationSettings (without proxy)',
         'SlackIntegrationSettings (without proxy)',
         'SlackIntegrationLegacySetting',
         'SlackIntegrationLegacySetting',
         'ShareLink Management',
         'ShareLink Management',
+        'Templates',
+        'Staff',
         'UserGroupRelations',
         'UserGroupRelations',
         'UserGroups',
         'UserGroups',
         'Users Management',
         'Users Management',

+ 5 - 0
apps/app/bin/swagger-jsdoc/generate-spec-apiv3.sh

@@ -10,5 +10,10 @@ OUT=${OUT:-"${APP_PATH}/tmp/openapi-spec-apiv3.json"}
 swagger-jsdoc \
 swagger-jsdoc \
   -o "${OUT}" \
   -o "${OUT}" \
   -d "${APP_PATH}/bin/swagger-jsdoc/definition-apiv3.js" \
   -d "${APP_PATH}/bin/swagger-jsdoc/definition-apiv3.js" \
+  "${APP_PATH}/src/features/external-user-group/server/routes/apiv3/*.ts" \
+  "${APP_PATH}/src/features/questionnaire/server/routes/apiv3/*.ts" \
+  "${APP_PATH}/src/features/templates/server/routes/apiv3/*.ts" \
+  "${APP_PATH}/src/features/growi-plugin/server/routes/apiv3/**/*.ts" \
   "${APP_PATH}/src/server/routes/apiv3/**/*.{js,ts}" \
   "${APP_PATH}/src/server/routes/apiv3/**/*.{js,ts}" \
+  "${APP_PATH}/src/server/routes/login.js" \
   "${APP_PATH}/src/server/models/openapi/**/*.{js,ts}"
   "${APP_PATH}/src/server/models/openapi/**/*.{js,ts}"

+ 29 - 19
apps/app/docker/Dockerfile

@@ -1,25 +1,31 @@
-# syntax = docker/dockerfile:1
+# syntax = docker/dockerfile:1.4
 
 
+ARG OPT_DIR="/opt"
+ARG PNPM_HOME="/root/.local/share/pnpm"
 
 
 ##
 ##
 ## base
 ## base
 ##
 ##
 FROM node:20-slim AS base
 FROM node:20-slim AS base
 
 
-ENV optDir=/opt
+ARG OPT_DIR
+ARG PNPM_HOME
 
 
-WORKDIR ${optDir}
+WORKDIR $OPT_DIR
 
 
 # install tools
 # install tools
-RUN apt-get update && apt-get install -y ca-certificates wget curl --no-install-recommends
+RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
+    --mount=type=cache,target=/var/cache/apt,sharing=locked \
+  apt-get update && apt-get install -y ca-certificates wget curl --no-install-recommends
 
 
 # install pnpm
 # install pnpm
-RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -
-ENV PNPM_HOME="/root/.local/share/pnpm"
+RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" PNPM_VERSION="10.4.1" sh -
+ENV PNPM_HOME=$PNPM_HOME
 ENV PATH="$PNPM_HOME:$PATH"
 ENV PATH="$PNPM_HOME:$PATH"
 
 
 # install turbo
 # install turbo
-RUN pnpm add turbo --global
+RUN --mount=type=cache,target=$PNPM_HOME/store,sharing=locked \
+  pnpm add turbo --global
 
 
 
 
 
 
@@ -28,14 +34,17 @@ RUN pnpm add turbo --global
 ##
 ##
 FROM base AS builder
 FROM base AS builder
 
 
-ENV optDir=/opt
+ENV PNPM_HOME=$PNPM_HOME
+ENV PATH="$PNPM_HOME:$PATH"
 
 
-WORKDIR ${optDir}
+WORKDIR $OPT_DIR
 
 
 COPY . .
 COPY . .
 
 
-RUN pnpm add node-gyp --global
-RUN pnpm install ---frozen-lockfile
+RUN --mount=type=cache,target=$PNPM_HOME/store,sharing=locked \
+  pnpm add node-gyp --global
+RUN --mount=type=cache,target=$PNPM_HOME/store,sharing=locked \
+  pnpm install ---frozen-lockfile
 
 
 # build
 # build
 RUN turbo run clean
 RUN turbo run clean
@@ -45,7 +54,7 @@ RUN turbo run build --filter @growi/app
 RUN pnpm deploy out --prod --filter @growi/app
 RUN pnpm deploy out --prod --filter @growi/app
 RUN rm -rf apps/app/node_modules && mv out/node_modules apps/app/node_modules
 RUN rm -rf apps/app/node_modules && mv out/node_modules apps/app/node_modules
 RUN rm -rf apps/app/.next/cache
 RUN rm -rf apps/app/.next/cache
-RUN tar -zcf packages.tar.gz \
+RUN tar -zcf /tmp/packages.tar.gz \
   package.json \
   package.json \
   apps/app/.next \
   apps/app/.next \
   apps/app/config \
   apps/app/config \
@@ -66,27 +75,28 @@ RUN tar -zcf packages.tar.gz \
 FROM node:20-slim
 FROM node:20-slim
 LABEL maintainer="Yuki Takei <yuki@weseek.co.jp>"
 LABEL maintainer="Yuki Takei <yuki@weseek.co.jp>"
 
 
+ARG OPT_DIR
+
 ENV NODE_ENV="production"
 ENV NODE_ENV="production"
 
 
-ENV optDir=/opt
-ENV appDir=${optDir}/growi
+ENV appDir="$OPT_DIR/growi"
 
 
 # Add gosu
 # Add gosu
 # see: https://github.com/tianon/gosu/blob/1.13/INSTALL.md
 # see: https://github.com/tianon/gosu/blob/1.13/INSTALL.md
-RUN set -eux; \
+RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
+    --mount=type=cache,target=/var/cache/apt,sharing=locked \
+  set -eux; \
 	apt-get update; \
 	apt-get update; \
 	apt-get install -y gosu; \
 	apt-get install -y gosu; \
 	rm -rf /var/lib/apt/lists/*; \
 	rm -rf /var/lib/apt/lists/*; \
 # verify that the binary works
 # verify that the binary works
 	gosu nobody true
 	gosu nobody true
 
 
-COPY --from=builder --chown=node:node \
-  ${optDir}/packages.tar.gz ${appDir}/
-
 # extract artifacts as 'node' user
 # extract artifacts as 'node' user
 USER node
 USER node
 WORKDIR ${appDir}
 WORKDIR ${appDir}
-RUN tar -zxf packages.tar.gz && rm packages.tar.gz
+RUN --mount=type=bind,from=builder,source=/tmp/packages.tar.gz,target=/tmp/packages.tar.gz \
+  tar -zxf /tmp/packages.tar.gz -C ${appDir}/
 
 
 COPY --chown=node:node --chmod=700 apps/app/docker/docker-entrypoint.sh /
 COPY --chown=node:node --chmod=700 apps/app/docker/docker-entrypoint.sh /
 
 

+ 5 - 1
apps/app/docker/docker-entrypoint.sh

@@ -7,8 +7,12 @@ mkdir -p /data/uploads
 if [ ! -e "./public/uploads" ]; then
 if [ ! -e "./public/uploads" ]; then
   ln -s /data/uploads ./public/uploads
   ln -s /data/uploads ./public/uploads
 fi
 fi
-
 chown -R node:node /data/uploads
 chown -R node:node /data/uploads
 chown -h node:node ./public/uploads
 chown -h node:node ./public/uploads
 
 
+# Set permissions for shared directory for bulk export
+mkdir -p /tmp/page-bulk-export
+chown -R node:node /tmp/page-bulk-export
+chmod 700 /tmp/page-bulk-export
+
 exec gosu node /bin/bash -c "$@"
 exec gosu node /bin/bash -c "$@"

+ 12 - 5
apps/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "7.2.0-RC.0",
+  "version": "7.2.3-RC.0",
   "license": "MIT",
   "license": "MIT",
   "private": "true",
   "private": "true",
   "scripts": {
   "scripts": {
@@ -31,7 +31,7 @@
     "lint:styles": "stylelint \"src/**/*.scss\"",
     "lint:styles": "stylelint \"src/**/*.scss\"",
     "lint:swagger2openapi:apiv3": "node node_modules/swagger2openapi/oas-validate tmp/openapi-spec-apiv3.json",
     "lint:swagger2openapi:apiv3": "node node_modules/swagger2openapi/oas-validate tmp/openapi-spec-apiv3.json",
     "lint:swagger2openapi:apiv1": "node node_modules/swagger2openapi/oas-validate tmp/openapi-spec-apiv1.json",
     "lint:swagger2openapi:apiv1": "node node_modules/swagger2openapi/oas-validate tmp/openapi-spec-apiv1.json",
-    "lint": "run-p lint:*",
+    "lint": "run-p lint:**",
     "prelint:swagger2openapi:apiv3": "pnpm run swagger2openapi:apiv3",
     "prelint:swagger2openapi:apiv3": "pnpm run swagger2openapi:apiv3",
     "prelint:swagger2openapi:apiv1": "pnpm run swagger2openapi:apiv1",
     "prelint:swagger2openapi:apiv1": "pnpm run swagger2openapi:apiv1",
     "test": "run-p test:*",
     "test": "run-p test:*",
@@ -82,6 +82,7 @@
     "@growi/remark-growi-directive": "workspace:^",
     "@growi/remark-growi-directive": "workspace:^",
     "@growi/remark-lsx": "workspace:^",
     "@growi/remark-lsx": "workspace:^",
     "@growi/slack": "workspace:^",
     "@growi/slack": "workspace:^",
+    "@growi/pdf-converter-client": "workspace:^",
     "@keycloak/keycloak-admin-client": "^18.0.0",
     "@keycloak/keycloak-admin-client": "^18.0.0",
     "@opentelemetry/api": "^1.9.0",
     "@opentelemetry/api": "^1.9.0",
     "@opentelemetry/auto-instrumentations-node": "^0.55.1",
     "@opentelemetry/auto-instrumentations-node": "^0.55.1",
@@ -94,12 +95,14 @@
     "@opentelemetry/sdk-trace-node": "^1.28.0",
     "@opentelemetry/sdk-trace-node": "^1.28.0",
     "@slack/web-api": "^6.2.4",
     "@slack/web-api": "^6.2.4",
     "@slack/webhook": "^6.0.0",
     "@slack/webhook": "^6.0.0",
+    "@types/async": "^3.2.24",
     "JSONStream": "^1.3.5",
     "JSONStream": "^1.3.5",
     "archiver": "^5.3.0",
     "archiver": "^5.3.0",
     "array.prototype.flatmap": "^1.2.2",
     "array.prototype.flatmap": "^1.2.2",
     "async-canvas-to-blob": "^1.0.3",
     "async-canvas-to-blob": "^1.0.3",
     "axios": "^0.24.0",
     "axios": "^0.24.0",
     "axios-retry": "^3.2.4",
     "axios-retry": "^3.2.4",
+    "babel-plugin-superjson-next": "^0.4.2",
     "body-parser": "^1.20.3",
     "body-parser": "^1.20.3",
     "browser-bunyan": "^1.8.0",
     "browser-bunyan": "^1.8.0",
     "bson-objectid": "^2.0.4",
     "bson-objectid": "^2.0.4",
@@ -142,6 +145,7 @@
     "is-iso-date": "^0.0.1",
     "is-iso-date": "^0.0.1",
     "js-tiktoken": "^1.0.15",
     "js-tiktoken": "^1.0.15",
     "js-yaml": "^4.1.0",
     "js-yaml": "^4.1.0",
+    "jsonrepair": "^3.12.0",
     "katex": "^0.16.21",
     "katex": "^0.16.21",
     "ldapjs": "^3.0.2",
     "ldapjs": "^3.0.2",
     "lucene-query-parser": "^1.2.0",
     "lucene-query-parser": "^1.2.0",
@@ -164,7 +168,7 @@
     "multer": "~1.4.0",
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
     "multer-autoreap": "^1.0.3",
     "mustache": "^4.2.0",
     "mustache": "^4.2.0",
-    "next": "^14.2.21",
+    "next": "^14.2.26",
     "next-dynamic-loading-props": "^0.1.1",
     "next-dynamic-loading-props": "^0.1.1",
     "next-i18next": "^15.3.1",
     "next-i18next": "^15.3.1",
     "next-superjson": "^0.0.4",
     "next-superjson": "^0.0.4",
@@ -214,6 +218,7 @@
     "remark-directive": "^3.0.0",
     "remark-directive": "^3.0.0",
     "remark-frontmatter": "^5.0.0",
     "remark-frontmatter": "^5.0.0",
     "remark-gfm": "^4.0.0",
     "remark-gfm": "^4.0.0",
+    "remark-html": "^16.0.1",
     "remark-math": "^6.0.0",
     "remark-math": "^6.0.0",
     "remark-parse": "^11.0.0",
     "remark-parse": "^11.0.0",
     "remark-rehype": "^11.1.1",
     "remark-rehype": "^11.1.1",
@@ -223,7 +228,7 @@
     "string-width": "=4.2.2",
     "string-width": "=4.2.2",
     "superjson": "^1.9.1",
     "superjson": "^1.9.1",
     "swagger-jsdoc": "^6.2.8",
     "swagger-jsdoc": "^6.2.8",
-    "swr": "^2.2.2",
+    "swr": "^2.3.2",
     "throttle-debounce": "^5.0.0",
     "throttle-debounce": "^5.0.0",
     "ts-deepmerge": "^6.2.0",
     "ts-deepmerge": "^6.2.0",
     "tslib": "^2.8.0",
     "tslib": "^2.8.0",
@@ -242,7 +247,8 @@
     "xss": "^1.0.15",
     "xss": "^1.0.15",
     "y-mongodb-provider": "^0.2.0",
     "y-mongodb-provider": "^0.2.0",
     "y-socket.io": "^1.1.3",
     "y-socket.io": "^1.1.3",
-    "yjs": "^13.6.18"
+    "yjs": "^13.6.18",
+    "zod": "^3.24.2"
   },
   },
   "// comments for defDependencies": {
   "// comments for defDependencies": {
     "bootstrap": "v5.3.3 has a bug. refs: https://github.com/twbs/bootstrap/issues/39798",
     "bootstrap": "v5.3.3 has a bug. refs: https://github.com/twbs/bootstrap/issues/39798",
@@ -265,6 +271,7 @@
     "@testing-library/jest-dom": "^6.5.0",
     "@testing-library/jest-dom": "^6.5.0",
     "@testing-library/react": "^16.0.1",
     "@testing-library/react": "^16.0.1",
     "@testing-library/user-event": "^14.5.2",
     "@testing-library/user-event": "^14.5.2",
+    "@types/archiver": "^6.0.2",
     "@types/bunyan": "^1.8.11",
     "@types/bunyan": "^1.8.11",
     "@types/express": "^4.17.21",
     "@types/express": "^4.17.21",
     "@types/hast": "^3.0.4",
     "@types/hast": "^3.0.4",

+ 2 - 0
apps/app/playwright.config.ts

@@ -72,6 +72,8 @@ export default defineConfig({
     trace: 'on-first-retry',
     trace: 'on-first-retry',
 
 
     viewport: { width: 1400, height: 1024 },
     viewport: { width: 1400, height: 1024 },
+
+    screenshot: 'only-on-failure',
   },
   },
 
 
   /* Configure projects for major browsers */
   /* Configure projects for major browsers */

+ 39 - 5
apps/app/playwright/20-basic-features/use-tools.spec.ts

@@ -1,8 +1,44 @@
 import { test, expect, type Page } from '@playwright/test';
 import { test, expect, type Page } from '@playwright/test';
 
 
 const openPageItemControl = async(page: Page): Promise<void> => {
 const openPageItemControl = async(page: Page): Promise<void> => {
-  await expect(page.getByTestId('grw-contextual-sub-nav')).toBeVisible();
-  await page.getByTestId('grw-contextual-sub-nav').getByTestId('open-page-item-control-btn').click();
+  const nav = page.getByTestId('grw-contextual-sub-nav');
+  const button = nav.getByTestId('open-page-item-control-btn');
+
+  // Wait for navigation element to be visible and attached
+  await expect(nav).toBeVisible();
+  await nav.waitFor({ state: 'visible' });
+
+  // Wait for button to be visible, enabled and attached
+  await expect(button).toBeVisible();
+  await expect(button).toBeEnabled();
+  await button.waitFor({ state: 'visible' });
+
+  // Add a small delay to ensure the button is fully interactive
+  await page.waitForTimeout(100);
+
+  await button.click();
+};
+
+const openPutBackPageModal = async(page: Page): Promise<void> => {
+  const alert = page.getByTestId('trash-page-alert');
+  const button = alert.getByTestId('put-back-button');
+
+  // Wait for alert element to be visible and attached
+  await expect(alert).toBeVisible();
+  await alert.waitFor({ state: 'visible' });
+
+  // Wait for button to be visible, enabled and attached
+  await expect(button).toBeVisible();
+  await expect(button).toBeEnabled();
+  await button.waitFor({ state: 'visible' });
+
+  // Scroll to the top of the page to prevent the subnav hide the button
+  await page.evaluate(() => {
+    window.scrollTo(0, 0);
+  });
+
+  await button.click();
+  await expect(page.getByTestId('put-back-page-modal')).toBeVisible();
 };
 };
 
 
 test('Page Deletion and PutBack is executed successfully', async({ page }) => {
 test('Page Deletion and PutBack is executed successfully', async({ page }) => {
@@ -15,9 +51,7 @@ test('Page Deletion and PutBack is executed successfully', async({ page }) => {
   await page.getByTestId('delete-page-button').click();
   await page.getByTestId('delete-page-button').click();
 
 
   // PutBack
   // PutBack
-  await expect(page.getByTestId('trash-page-alert')).toBeVisible();
-  await page.getByTestId('put-back-button').click();
-  await expect(page.getByTestId('put-back-page-modal')).toBeVisible();
+  await openPutBackPageModal(page);
   await page.getByTestId('put-back-execution-button').click();
   await page.getByTestId('put-back-execution-button').click();
   await expect(page.getByTestId('trash-page-alert')).not.toBeVisible();
   await expect(page.getByTestId('trash-page-alert')).not.toBeVisible();
 });
 });

+ 13 - 10
apps/app/public/static/locales/en_US/admin.json

@@ -10,6 +10,7 @@
   "only_me": "Only me",
   "only_me": "Only me",
   "only_inside_the_group": "Only inside the group",
   "only_inside_the_group": "Only inside the group",
   "optional": "Optional",
   "optional": "Optional",
+  "days": "days",
   "security_settings": {
   "security_settings": {
     "security_settings": "Security Settings",
     "security_settings": "Security Settings",
     "scope_of_page_disclosure": "Scope of page disclosure",
     "scope_of_page_disclosure": "Scope of page disclosure",
@@ -359,6 +360,11 @@
     "file_uploading": "File uploading",
     "file_uploading": "File uploading",
     "enable_files_except_image": "Enabling this option will allow upload of any file type. Without this option, only image file upload is supported.",
     "enable_files_except_image": "Enabling this option will allow upload of any file type. Without this option, only image file upload is supported.",
     "attach_enable": "You can attach files other than image files if you enable this option.",
     "attach_enable": "You can attach files other than image files if you enable this option.",
+    "page_bulk_export_settings": "Page Bulk Export Settings",
+    "enable_page_bulk_export": "Enable bulk export",
+    "page_bulk_export_explanation": "Enables a feature that allows all users to export a page and all it's child pages at once from the menu. Exported data will be automatically deleted after the storage period has passed.",
+    "page_bulk_export_warning": "The bulk page export feature is available to all users. In order to maintain system resources, we ask for your cooperation in using the minimum amount necessary. If you are an administrator, please inform all users of this.",
+    "page_bulk_export_storage_period": "Storage period",
     "update": "Update",
     "update": "Update",
     "mail_settings": "E-mail Settings",
     "mail_settings": "E-mail Settings",
     "mailer_is_not_set_up": "E-mail setting is not set up.",
     "mailer_is_not_set_up": "E-mail setting is not set up.",
@@ -382,7 +388,7 @@
     "file_delivery_method_relay":"Internal System Relay",
     "file_delivery_method_relay":"Internal System Relay",
     "file_delivery_method_redirect_info":"Redirect: It redirects to a signed URL without GROWI server, it gives excellent performance.",
     "file_delivery_method_redirect_info":"Redirect: It redirects to a signed URL without GROWI server, it gives excellent performance.",
     "file_delivery_method_relay_info":"Internal System Relay: The GROWI server delivers to clients, it provides complete security.",
     "file_delivery_method_relay_info":"Internal System Relay: The GROWI server delivers to clients, it provides complete security.",
-    "fixed_by_env_var": "This is fixed by the env var <code>FILE_UPLOAD={{fileUploadType}}</code>.",
+    "fixed_by_env_var": "This is fixed by the env var <code>{{envKey}}={{envVar}}</code>.",
     "gcs_label": "GCP(GCS)",
     "gcs_label": "GCP(GCS)",
     "aws_label": "AWS(S3)",
     "aws_label": "AWS(S3)",
     "local_label": "Local",
     "local_label": "Local",
@@ -493,7 +499,9 @@
       "show_all_reply_comments": "Show all reply comments",
       "show_all_reply_comments": "Show all reply comments",
       "show_all_reply_comments_desc": "When the setting value is off, comments other than the latest two are omitted.",
       "show_all_reply_comments_desc": "When the setting value is off, comments other than the latest two are omitted.",
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
-      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
+      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range.",
+      "show_page_side_authors": "Always display creators and updaters above the table of contents",
+      "show_page_side_authors_desc": "Displays information about the creator and the last updater above the table of contents in the page sidebar."
     },
     },
       "presentation": "Presentation",
       "presentation": "Presentation",
     "presentation_options": {
     "presentation_options": {
@@ -1060,7 +1068,8 @@
     "ADMIN_USER_GROUP_ADD_USER": "Add User to User Group",
     "ADMIN_USER_GROUP_ADD_USER": "Add User to User Group",
     "ADMIN_SEARCH_CONNECTION": "Attempting to reconnect to Elasticsearch",
     "ADMIN_SEARCH_CONNECTION": "Attempting to reconnect to Elasticsearch",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes",
-    "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes"
+    "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes",
+    "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "Update Page Bulk Export Settings"
   },
   },
   "g2g": {
   "g2g": {
     "transfer_success": "Completed GROWI to GROWI transfer successfully",
     "transfer_success": "Completed GROWI to GROWI transfer successfully",
@@ -1139,12 +1148,6 @@
   "ai_integration": {
   "ai_integration": {
     "ai_integration": "AI Integration",
     "ai_integration": "AI Integration",
     "disable_mode_explanation": "Currently, AI integration is disabled. To enable it, configure the <code>AI_ENABLED</code> environment variable along with the required additional variables.<br><br>For details, please refer to the <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
     "disable_mode_explanation": "Currently, AI integration is disabled. To enable it, configure the <code>AI_ENABLED</code> environment variable along with the required additional variables.<br><br>For details, please refer to the <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
-    "ai_search_management": "AI search management",
-    "rebuild_vector_store": "Rebuild Vector Store",
-    "rebuild_vector_store_label": "Rebuild",
-    "rebuild_vector_store_explanation1": "Delete the existing Vector Store and recreate the Vector Store on the public page.",
-    "rebuild_vector_store_explanation2": "This process may take several minutes.",
-    "rebuild_vector_store_requested": "Vector Store rebuild has been requested",
-    "rebuild_vector_store_failed": "Vector Store rebuild failed"
+    "ai_search_management": "AI search management"
   }
   }
 }
 }

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

@@ -152,6 +152,9 @@
   "Page Tree": "Page Tree",
   "Page Tree": "Page Tree",
   "Bookmarks": "Bookmarks",
   "Bookmarks": "Bookmarks",
   "In-App Notification": "Notifications",
   "In-App Notification": "Notifications",
+  "AI Assistant": "AI Assistant",
+  "Knowledge Assistant": "Knowledge Assistant (Beta)",
+  "Editor Assistant": "Editor Assistant (Beta)",
   "original_path": "Original path",
   "original_path": "Original path",
   "new_path": "New path",
   "new_path": "New path",
   "duplicated_path": "Duplicated path",
   "duplicated_path": "Duplicated path",
@@ -184,7 +187,9 @@
   },
   },
   "author_info": {
   "author_info": {
     "created_at": "Created at",
     "created_at": "Created at",
-    "last_revision_posted_at": "Last revision posted at"
+    "created_by": "Created by",
+    "last_revision_posted_at": "Last revision posted at",
+    "updated_by": "Updated by"
   },
   },
   "installer": {
   "installer": {
     "tab": "Create account",
     "tab": "Create account",
@@ -340,6 +345,7 @@
       "file": "File only"
       "file": "File only"
     },
     },
     "editor_config": "Editor Config",
     "editor_config": "Editor Config",
+    "editor_assistant": "Editor Assistant",
     "Show active line": "Show active line",
     "Show active line": "Show active line",
     "auto_format_table": "Auto format table",
     "auto_format_table": "Auto format table",
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",
@@ -454,7 +460,7 @@
   "modal_shortcuts": {
   "modal_shortcuts": {
     "global": {
     "global": {
       "title": "Global shortcuts",
       "title": "Global shortcuts",
-      "Open/Close shortcut help": "Open/Close<br>shortcut help",
+      "Open/Close shortcut help": "Open/Close Shortcut Help",
       "Edit Page": "Edit Page",
       "Edit Page": "Edit Page",
       "Create Page": "Create Page",
       "Create Page": "Create Page",
       "Search": "Search",
       "Search": "Search",
@@ -468,11 +474,14 @@
       "Indent": "Indent",
       "Indent": "Indent",
       "Outdent": "Outdent",
       "Outdent": "Outdent",
       "Save Page": "Save Page",
       "Save Page": "Save Page",
-      "Delete Line": "Delete Line"
-    },
-    "commentform": {
-      "title": "Comment Form shortcuts",
-      "Post": "Post"
+      "Only Editor": "(Editor Only)",
+      "Delete Line": "Delete Line",
+      "Search in Editor": "Search in Editor",
+      "Move Line": "Move Line",
+      "Copy Line": "Copy Line",
+      "Toggle Line": "Toggle Line Comment",
+      "Insert Line": "Insert Line",
+      "Post Comment": "(Post Comment)"
     }
     }
   },
   },
   "modal_resolve_conflict": {
   "modal_resolve_conflict": {
@@ -486,10 +495,12 @@
     "latest_revision": "theirs",
     "latest_revision": "theirs",
     "selected_editable_revision": "Selected Page Body (Editable)"
     "selected_editable_revision": "Selected Page Body (Editable)"
   },
   },
-  "modal_aichat": {
-    "title": "Knowledge Assistant",
-    "title_beta_label": "(Beta)",
+  "sidebar_ai_assistant": {
+    "instruction_label": "Assistant instructions",
+    "reference_pages_label": "Reference pages",
     "placeholder": "Ask me anything.",
     "placeholder": "Ask me anything.",
+    "knowledge_assistant_placeholder": "Ask me anything.",
+    "editor_assistant_placeholder": "Can I help you with anything?",
     "summary_mode_label": "Summary mode",
     "summary_mode_label": "Summary mode",
     "summary_mode_help": "Concise answer within 2-3 sentences",
     "summary_mode_help": "Concise answer within 2-3 sentences",
     "caution_against_hallucination": "Please verify the information and check the sources.",
     "caution_against_hallucination": "Please verify the information and check the sources.",
@@ -498,8 +509,109 @@
     "budget_exceeded": "You have reached your usage limit for OpenAI's API. To use the Knowledge Assistant again, please add credits from the OpenAI billing page.",
     "budget_exceeded": "You have reached your usage limit for OpenAI's API. To use the Knowledge Assistant again, please add credits from the OpenAI billing page.",
     "budget_exceeded_for_growi_cloud": "You have reached your OpenAI API usage limit. To use the Knowledge Assistant again, please add credits from the GROWI.cloud admin page for Hosted users or from the OpenAI billing page for Owned users.",
     "budget_exceeded_for_growi_cloud": "You have reached your OpenAI API usage limit. To use the Knowledge Assistant again, please add credits from the GROWI.cloud admin page for Hosted users or from the OpenAI billing page for Owned users.",
     "error_message": "An error has occurred",
     "error_message": "An error has occurred",
-    "show_error_detail": "Show error details"
-
+    "show_error_detail": "Show error details",
+    "discard": "Discard",
+    "accept": "Accept",
+    "use_assistant": "Use Assistant",
+    "remove_assistant": "Deselect the selected assistant",
+    "preset_menu": {
+      "summarize": {
+        "title": "Summarize this article",
+        "prompt": "Please summarize the markdown content"
+      },
+      "correct": {
+        "title": "Correct errors in the text",
+        "prompt": "Please correct the errors in the markdown text"
+      }
+    }
+  },
+  "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "Update Assistant",
+      "add_new_assistant": "Add New Assistant"
+    },
+    "assistant_name_placeholder": "Enter assistant name",
+    "page_count": "{{count}} pages",
+    "memo": {
+      "title": "Assistant memo",
+      "optional": "Optional",
+      "placeholder": "You can display notes about content and usage",
+      "description": "The contents of the memo do not affect the assistant's processing."
+    },
+    "submit_button": {
+      "update_assistant": "Update Assistant",
+      "create_assistant": "Create Assistant"
+    },
+    "toaster": {
+      "create_success": "Assistant has been created",
+      "update_success": "Assistant has been updated",
+      "create_failed": "Failed to create assistant",
+      "update_failed": "Failed to update assistant"
+    },
+    "edit_page_description": "Edit pages that the assistant can reference.<br> The assistant can reference up to {{limitLearnablePageCountPerAssistant}} pages including child pages.",
+    "default_instruction": "You are the knowledge assistant for this Wiki. Please provide support according to the following guidelines:\n\n- Analyze document relevance and connect information\n- Suggest new perspectives\n- Provide accurate information based on understanding the intent of questions\nI will provide information in a structured format when necessary.",
+    "add_page_button": "Add page",
+    "page_mode_title": {
+      "share": "Assistant Sharing",
+      "pages": "Reference Pages",
+      "instruction": "Assistant Instructions"
+    },
+    "share_assistant": "Share assistant",
+    "page_access_permission": "Page access permission",
+    "access_scope": {
+      "owner": "All pages accessible by {{username}}",
+      "groups": "Specify groups",
+      "publicOnly": "Public pages only"
+    },
+    "share_scope": {
+      "title": "Assistant sharing scope",
+      "owner": {
+        "label": "{{username}} only"
+      },
+      "publicOnly": {
+        "label": "Public",
+        "desc": "Shared with all users"
+      },
+      "groups": {
+        "label": "Specify groups",
+        "desc": "Shared only with members of selected groups"
+      },
+      "sameAsAccessScope": {
+        "label": "Same as page access scope",
+        "desc": "Shared with the same scope as page access"
+      }
+    },
+    "instructions": {
+      "description": "You can set instructions that determine how the assistant behaves.<br>The assistant will answer and analyze based on these instructions.",
+      "reset_to_default": "Reset to default"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "Confirm Sharing Scope",
+    "warning_message": "This assistant includes pages with limited access.<br>With the current settings, information from these pages may be shared beyond their original access permissions through the assistant.",
+    "selected_pages_label": "Selected page paths",
+    "confirmation_message": "Please confirm that you understand the content of these pages may be shared within the assistant's public scope if you proceed.",
+    "button": {
+      "review": "Review settings",
+      "proceed": "Understand and proceed"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "Default assistant is not set"
+  },
+  "ai_assistant_tree": {
+    "add_assistant": "Add Assistant",
+    "my_assistants": "My Assistants",
+    "team_assistants": "Team Assistants",
+    "thread_does_not_exist": "No threads exist",
+    "toaster": {
+      "ai_assistant_deleted_success": "Assistant deleted",
+      "ai_assistant_deleted_failed": "Failed to delete assistant",
+      "thread_deleted_success": "Thread deleted",
+      "thread_deleted_failed": "Failed to delete thread",
+      "ai_assistant_set_default_success": "Default assistant set successfully",
+      "ai_assistant_set_default_failed": "Failed to set default assistant"
+    }
   },
   },
   "link_edit": {
   "link_edit": {
     "edit_link": "Edit Link",
     "edit_link": "Edit Link",
@@ -654,11 +766,26 @@
     "discription_heading": "Create Account",
     "discription_heading": "Create Account",
     "discription": "Create an your account with the invited email address"
     "discription": "Create an your account with the invited email address"
   },
   },
-  "export_bulk": {
+  "page_export": {
     "failed_to_export": "Failed to export",
     "failed_to_export": "Failed to export",
     "failed_to_count_pages": "Failed to count pages",
     "failed_to_count_pages": "Failed to count pages",
     "export_page_markdown": "Export page as Markdown",
     "export_page_markdown": "Export page as Markdown",
-    "export_page_pdf": "Export page as PDF"
+    "export_page_pdf": "Export page as PDF",
+    "bulk_export": "Export page and all child pages",
+    "bulk_export_download_explanation": "A notification will be sent when the export is complete. To download the exported file, click the notification.",
+    "bulk_export_exec_time_warning": "If the number of pages is large, it may take a while to export",
+    "large_bulk_export_warning": "To conserve system resources, please refrain from exporting a large number of pages consecutively",
+    "markdown": "Markdown",
+    "choose_export_format": "Select export format",
+    "bulk_export_started": "Please wait a moment...",
+    "bulk_export_download_expired": "Download period has expired",
+    "bulk_export_job_expired": "Export process was canceled because it took too long",
+    "export_in_progress": "Export in progress",
+    "export_in_progress_explanation": "Export with the same format is already in progress. Would you like to restart to export the latest page contents?",
+    "export_cancel_warning": "The following export in progress will be canceled",
+    "restart": "Restart",
+    "format": "Format",
+    "started_on": "Started on"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "Successfully Connected!",
     "successfully_connected": "Successfully Connected!",
@@ -870,8 +997,7 @@
   },
   },
   "sidebar_header": {
   "sidebar_header": {
     "show_wip_page": "Show WIP",
     "show_wip_page": "Show WIP",
-    "size_s": "Size: S",
-    "size_l": "Size: L"
+    "compact_view": "Compact View"
   },
   },
   "create_page": {
   "create_page": {
     "untitled": "Untitled"
     "untitled": "Untitled"

+ 13 - 10
apps/app/public/static/locales/fr_FR/admin.json

@@ -10,6 +10,7 @@
   "only_me": "Seulement moi",
   "only_me": "Seulement moi",
   "only_inside_the_group": "Utilisateurs du groupe",
   "only_inside_the_group": "Utilisateurs du groupe",
   "optional": "Optionnel",
   "optional": "Optionnel",
+  "days": "jours",
   "security_settings": {
   "security_settings": {
     "security_settings": "Sécurité",
     "security_settings": "Sécurité",
     "scope_of_page_disclosure": "Confidentialité de la page",
     "scope_of_page_disclosure": "Confidentialité de la page",
@@ -359,6 +360,11 @@
     "file_uploading": "Téléversement de fichiers",
     "file_uploading": "Téléversement de fichiers",
     "enable_files_except_image": "Autoriser tout les types de fichiers",
     "enable_files_except_image": "Autoriser tout les types de fichiers",
     "attach_enable": "Autorise le téléversement de tout les types de fichiers.",
     "attach_enable": "Autorise le téléversement de tout les types de fichiers.",
+    "page_bulk_export_settings": "Paramètres d'exportation de pages par lots",
+    "enable_page_bulk_export": "Activer l'exportation groupée",
+    "page_bulk_export_explanation": "Active une fonctionnalité qui permet à tous les utilisateurs d'exporter simultanément toutes les pages sélectionnées dans le menu des pages et leurs pages subordonnées. Les données exportées seront automatiquement supprimées une fois la période de conservation écoulée.",
+    "page_bulk_export_warning": "La fonctionnalité d’exportation de pages en masse est disponible pour tous les utilisateurs. Afin de maintenir les ressources du système, nous demandons votre coopération pour utiliser le montant minimum nécessaire. Si vous êtes administrateur, veuillez en informer tous les utilisateurs.",
+    "page_bulk_export_storage_period": "Date limite de téléchargement",
     "update": "Sauvegarder",
     "update": "Sauvegarder",
     "mail_settings": "SMTP",
     "mail_settings": "SMTP",
     "mailer_is_not_set_up": "Paramètres d'envoi de courriels non configurés.",
     "mailer_is_not_set_up": "Paramètres d'envoi de courriels non configurés.",
@@ -382,7 +388,7 @@
     "file_delivery_method_relay": "Relai interne du système",
     "file_delivery_method_relay": "Relai interne du système",
     "file_delivery_method_redirect_info": "Rediriger: Redirige vers une URL signé, performance excellente.",
     "file_delivery_method_redirect_info": "Rediriger: Redirige vers une URL signé, performance excellente.",
     "file_delivery_method_relay_info": "Relai interne du système: Le serveur GROWI sert les fichiers directement au client, sécurité complète.",
     "file_delivery_method_relay_info": "Relai interne du système: Le serveur GROWI sert les fichiers directement au client, sécurité complète.",
-    "fixed_by_env_var": "Défini par une variable d'environnement <code>FILE_UPLOAD={{fileUploadType}}</code>.",
+    "fixed_by_env_var": "Défini par une variable d'environnement <code>{{envKey}}={{envVar}}</code>.",
     "gcs_label": "GCP(GCS)",
     "gcs_label": "GCP(GCS)",
     "aws_label": "AWS(S3)",
     "aws_label": "AWS(S3)",
     "local_label": "Local",
     "local_label": "Local",
@@ -493,7 +499,9 @@
       "show_all_reply_comments": "Afficher tout les commentaires",
       "show_all_reply_comments": "Afficher tout les commentaires",
       "show_all_reply_comments_desc": "Lorsque désactivé, seul les deux commentaires les plus récents sont affichés",
       "show_all_reply_comments_desc": "Lorsque désactivé, seul les deux commentaires les plus récents sont affichés",
       "select_search_scope_children_as_default": "'Seulement enfant de ce chemin' lors de la recherche",
       "select_search_scope_children_as_default": "'Seulement enfant de ce chemin' lors de la recherche",
-      "select_search_scope_children_as_default_desc": "Lorsque désactivé, utilise 'Toutes les pages' en portée de recherche."
+      "select_search_scope_children_as_default_desc": "Lorsque désactivé, utilise 'Toutes les pages' en portée de recherche.",
+      "show_page_side_authors": "Toujours afficher les créateurs et les modificateurs au-dessus de la table des matières",
+      "show_page_side_authors_desc": "Affiche les informations sur le créateur et le dernier modificateur au-dessus de la table des matières dans la barre latérale de la page."
     },
     },
     "presentation": "Présentation",
     "presentation": "Présentation",
     "presentation_options": {
     "presentation_options": {
@@ -1059,7 +1067,8 @@
     "ADMIN_USER_GROUP_ADD_USER": "Ajouter l'utilisateur au groupe",
     "ADMIN_USER_GROUP_ADD_USER": "Ajouter l'utilisateur au groupe",
     "ADMIN_SEARCH_CONNECTION": "Essai de reconnexion Elasticsearch",
     "ADMIN_SEARCH_CONNECTION": "Essai de reconnexion Elasticsearch",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Nomarliser l'index Elasticsearch",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Nomarliser l'index Elasticsearch",
-    "ADMIN_SEARCH_INDICES_REBUILD": "Reconstruire l'index Elasticsearch"
+    "ADMIN_SEARCH_INDICES_REBUILD": "Reconstruire l'index Elasticsearch",
+    "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "Mettre à jour les paramètres d'exportation groupée de la page"
   },
   },
   "g2g": {
   "g2g": {
     "transfer_success": "Transfert de GROWI vers GROWI complété!",
     "transfer_success": "Transfert de GROWI vers GROWI complété!",
@@ -1138,12 +1147,6 @@
   "ai_integration": {
   "ai_integration": {
     "ai_integration": "Intégration de l'IA",
     "ai_integration": "Intégration de l'IA",
     "disable_mode_explanation": "Actuellement, l'intégration AI est désactivée. Pour l'activer, configurez la variable d'environnement <code>AI_ENABLED</code> ainsi que les autres variables nécessaires.<br><br>Pour plus de détails, veuillez consulter la <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
     "disable_mode_explanation": "Actuellement, l'intégration AI est désactivée. Pour l'activer, configurez la variable d'environnement <code>AI_ENABLED</code> ainsi que les autres variables nécessaires.<br><br>Pour plus de détails, veuillez consulter la <a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>documentation</a>.",
-    "ai_search_management": "Gestion de la recherche par l'IA",
-    "rebuild_vector_store": "Reconstruire le magasin Vector",
-    "rebuild_vector_store_label": "Reconstruire",
-    "rebuild_vector_store_explanation1": "Supprimez le Vector Store existant et recréez le Vector Store sur la page publique.",
-    "rebuild_vector_store_explanation2": "Ce processus peut prendre plusieurs minutes.",
-    "rebuild_vector_store_requested": "La reconstruction du magasin Vector a été demandée",
-    "rebuild_vector_store_failed": "Échec de la reconstruction du magasin de vecteurs"
+    "ai_search_management": "Gestion de la recherche par l'IA"
   }
   }
 }
 }

+ 142 - 16
apps/app/public/static/locales/fr_FR/translation.json

@@ -153,6 +153,9 @@
   "Page Tree": "Arborescence",
   "Page Tree": "Arborescence",
   "Bookmarks": "Favoris",
   "Bookmarks": "Favoris",
   "In-App Notification": "Notifications",
   "In-App Notification": "Notifications",
+  "AI Assistant": "Assistant IA",
+  "Knowledge Assistant": "Assistant de Connaissances (Bêta)",
+  "Editor Assistant": "Assistante de rédaction (Bêta)",
   "original_path": "Chemin originel",
   "original_path": "Chemin originel",
   "new_path": "Nouveau chemin",
   "new_path": "Nouveau chemin",
   "duplicated_path": "Chemin dupliqué",
   "duplicated_path": "Chemin dupliqué",
@@ -185,7 +188,9 @@
   },
   },
   "author_info": {
   "author_info": {
     "created_at": "Crée le",
     "created_at": "Crée le",
-    "last_revision_posted_at": "Dernière révision le"
+    "created_by": "Créé par",
+    "last_revision_posted_at": "Dernière révision le",
+    "updated_by": "Mis à jour par"
   },
   },
   "installer": {
   "installer": {
     "tab": "Créer compte",
     "tab": "Créer compte",
@@ -341,6 +346,7 @@
       "file": "Fichier seulement"
       "file": "Fichier seulement"
     },
     },
     "editor_config": "Préférences de l'éditeur",
     "editor_config": "Préférences de l'éditeur",
+    "editor_assistant": "Assistant d'édition",
     "Show active line": "Surligner la ligne active",
     "Show active line": "Surligner la ligne active",
     "auto_format_table": "Formatter les tableaux",
     "auto_format_table": "Formatter les tableaux",
     "overwrite_scopes": "{{operation}} et écraser les scopes des pages enfants",
     "overwrite_scopes": "{{operation}} et écraser les scopes des pages enfants",
@@ -456,18 +462,21 @@
       "Show Contributors": "Voir contributeurs",
       "Show Contributors": "Voir contributeurs",
       "MirrorMode": "Mode mirroir",
       "MirrorMode": "Mode mirroir",
       "Konami Code": "Code Konami",
       "Konami Code": "Code Konami",
-      "konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
+      "konami_code_url": "https://fr.wikipedia.org/wiki/Code_Konami"
     },
     },
     "editor": {
     "editor": {
       "title": "Raccourcis d'édition",
       "title": "Raccourcis d'édition",
       "Indent": "Indentation",
       "Indent": "Indentation",
       "Outdent": "Retrait",
       "Outdent": "Retrait",
       "Save Page": "Sauvegarder la page",
       "Save Page": "Sauvegarder la page",
-      "Delete Line": "Supprimer la ligne"
-    },
-    "commentform": {
-      "title": "Raccourcis de commentaires",
-      "Post": "Poster"
+      "Only Editor": "(Éditeur uniquement)",
+      "Delete Line": "Supprimer la ligne",
+      "Search in Editor": "Rechercher dans l'éditeur",
+      "Move Line": "Déplacer la ligne",
+      "Copy Line": "Copier la ligne",
+      "Toggle Line": "Commenter/Décommenter la ligne",
+      "Insert Line": "Insérer une ligne",
+      "Post Comment": "(Publier le commentaire)"
     }
     }
   },
   },
   "modal_resolve_conflict": {
   "modal_resolve_conflict": {
@@ -481,10 +490,11 @@
     "latest_revision": "les autres",
     "latest_revision": "les autres",
     "selected_editable_revision": "Corps de page sélectionné (Modifiable)"
     "selected_editable_revision": "Corps de page sélectionné (Modifiable)"
   },
   },
-  "modal_aichat": {
-    "title": "Assistant de Connaissance",
-    "title_beta_label": "(Bêta)",
-    "placeholder": "Demandez-moi n'importe quoi.",
+  "sidebar_ai_assistant": {
+    "instruction_label": "Instructions pour l'assistant",
+    "reference_pages_label": "Pages de référence",
+    "knowledge_assistant_placeholder": "Demandez-moi n'importe quoi.",
+    "editor_assistant_placeholder": "Puis-je vous aider ?",
     "summary_mode_label": "Mode résumé",
     "summary_mode_label": "Mode résumé",
     "summary_mode_help": "Réponse concise en 2-3 phrases",
     "summary_mode_help": "Réponse concise en 2-3 phrases",
     "caution_against_hallucination": "Veuillez vérifier les informations et consulter les sources.",
     "caution_against_hallucination": "Veuillez vérifier les informations et consulter les sources.",
@@ -493,7 +503,109 @@
     "budget_exceeded": "Vous avez atteint votre limite d'utilisation de l'API de l'OpenAI. Pour utiliser à nouveau l'assistant de connaissance, veuillez ajouter des crédits à partir de la page de facturation d'OpenAI.",
     "budget_exceeded": "Vous avez atteint votre limite d'utilisation de l'API de l'OpenAI. Pour utiliser à nouveau l'assistant de connaissance, veuillez ajouter des crédits à partir de la page de facturation d'OpenAI.",
     "budget_exceeded_for_growi_cloud": "Vous avez atteint votre limite d'utilisation de l'API de l'OpenAI. Pour utiliser à nouveau l'assistant de connaissance, veuillez ajouter des crédits à partir de la page d'administration de GROWI.cloud pour les utilisateurs hébergés ou à partir de la page de facturation de l'OpenAI pour les utilisateurs propriétaires.",
     "budget_exceeded_for_growi_cloud": "Vous avez atteint votre limite d'utilisation de l'API de l'OpenAI. Pour utiliser à nouveau l'assistant de connaissance, veuillez ajouter des crédits à partir de la page d'administration de GROWI.cloud pour les utilisateurs hébergés ou à partir de la page de facturation de l'OpenAI pour les utilisateurs propriétaires.",
     "error_message": "Erreur",
     "error_message": "Erreur",
-    "show_error_detail": "Détails de l'exposition"
+    "show_error_detail": "Détails de l'exposition",
+    "discard": "Annuler",
+    "accept": "Accepter",
+    "use_assistant": "Utiliser l'assistant",
+    "remove_assistant": "Désélectionner l'assistant sélectionné",
+    "preset_menu": {
+      "summarize": {
+        "title": "Résumer cet article'",
+        "prompt": "Veuillez résumer le contenu markdown"
+      },
+      "correct": {
+        "title": "Corriger les erreurs du texte",
+        "prompt": "Veuillez corriger les erreurs dans le texte markdown"
+      }
+    }
+  },
+  "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "Mettre à jour l'assistant",
+      "add_new_assistant": "Ajouter un nouvel assistant"
+    },
+    "assistant_name_placeholder": "Entrer le nom de l'assistant",
+    "page_count": "{{count}} pages",
+    "memo": {
+      "title": "Note sur l'assistant",
+      "optional": "Optionnel",
+      "placeholder": "Vous pouvez afficher des notes sur le contenu et l'utilisation",
+      "description": "Le contenu de la note n'affecte pas le traitement de l'assistant."
+    },
+    "submit_button": {
+      "update_assistant": "Mettre à jour l'assistant",
+      "create_assistant": "Créer l'assistant"
+    },
+    "toaster": {
+      "create_success": "L'assistant a été créé",
+      "update_success": "L'assistant a été mis à jour",
+      "create_failed": "Échec de la création de l'assistant",
+      "update_failed": "Échec de la mise à jour de l'assistant"
+    },
+    "edit_page_description": "Modifier les pages que l'assistant peut référencer.<br> L'assistant peut référencer jusqu'à {{limitLearnablePageCountPerAssistant}} pages, y compris les pages enfants.",
+    "default_instruction": "Vous êtes l'assistant de connaissances pour ce Wiki. Veuillez fournir un support selon les directives suivantes :\n\n- Analyser la pertinence des documents et relier les informations\n- Proposer de nouvelles perspectives\n- Fournir des informations précises en comprenant l'intention des questions\nJe fournirai les informations sous forme structurée si nécessaire.",
+    "add_page_button": "Ajouter une page",
+    "page_mode_title": {
+      "share": "Partage de l'assistant",
+      "pages": "Pages de référence",
+      "instruction": "Instructions de l'assistant"
+    },
+    "share_assistant": "Partager l'assistant",
+    "page_access_permission": "Autorisation d'accès à la page",
+    "access_scope": {
+      "owner": "Toutes les pages accessibles par {{username}}",
+      "groups": "Spécifier les groupes",
+      "publicOnly": "Pages publiques uniquement"
+    },
+    "share_scope": {
+      "title": "Portée de partage de l'assistant",
+      "owner": {
+        "label": "Seulement {{username}}"
+      },
+      "publicOnly": {
+        "label": "Public",
+        "desc": "Partagé avec tous les utilisateurs"
+      },
+      "groups": {
+        "label": "Spécifier les groupes",
+        "desc": "Partagé uniquement avec les membres des groupes sélectionnés"
+      },
+      "sameAsAccessScope": {
+        "label": "Même portée que l'accès à la page",
+        "desc": "Partagé avec la même portée que l'accès à la page"
+      }
+    },
+    "instructions": {
+      "description": "Vous pouvez définir des instructions qui déterminent le comportement de l'assistant.<br>L'assistant répondra et analysera en fonction de ces instructions.",
+      "reset_to_default": "Réinitialiser par défaut"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "Confirmation de la portée de partage",
+    "warning_message": "Cet assistant comprend des pages à accès limité.<br>Avec les paramètres actuels, les informations de ces pages peuvent être partagées au-delà de leurs autorisations d'accès d'origine via l'assistant.",
+    "selected_pages_label": "Chemins de pages sélectionnés",
+    "confirmation_message": "Veuillez confirmer que vous comprenez que le contenu de ces pages peut être partagé dans la portée publique de l'assistant si vous continuez.",
+    "button": {
+      "review": "Réviser les paramètres",
+      "proceed": "Comprendre et continuer"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "L'assistant par défaut n'est pas configuré"
+  },
+ "ai_assistant_tree": {
+    "add_assistant": "Ajouter un assistant",
+    "my_assistants": "Mes assistants",
+    "team_assistants": "Assistants d'équipe",
+    "thread_does_not_exist": "Aucune discussion",
+    "toaster": {
+      "ai_assistant_deleted_success": "Assistant supprimé",
+      "ai_assistant_deleted_failed": "Échec de la suppression de l'assistant",
+      "thread_deleted_success": "Discussion supprimée",
+      "thread_deleted_failed": "Échec de la suppression de la discussion",
+      "ai_assistant_set_default_success": "Assistant par défaut défini avec succès",
+      "ai_assistant_set_default_failed": "Échec de la définition de l'assistant par défaut"
+    }
   },
   },
   "link_edit": {
   "link_edit": {
     "edit_link": "Modifier lien",
     "edit_link": "Modifier lien",
@@ -648,11 +760,26 @@
     "discription_heading": "Créer un compte",
     "discription_heading": "Créer un compte",
     "discription": "Créer un compte avec votre adresse courriel invitée"
     "discription": "Créer un compte avec votre adresse courriel invitée"
   },
   },
-  "export_bulk": {
+  "page_export": {
     "failed_to_export": "Échec de l'export",
     "failed_to_export": "Échec de l'export",
     "failed_to_count_pages": "Échec du compte des pages",
     "failed_to_count_pages": "Échec du compte des pages",
     "export_page_markdown": "Exporter la page en Markdown",
     "export_page_markdown": "Exporter la page en Markdown",
-    "export_page_pdf": "Exporter la page en PDF"
+    "export_page_pdf": "Exporter la page en PDF",
+    "bulk_export": "Exporter la page et toutes les pages enfants",
+    "bulk_export_download_explanation": "Une notification sera envoyée lorsque l’exportation sera terminée. Pour télécharger le fichier exporté, cliquez sur la notification.",
+    "bulk_export_exec_time_warning": "Si le nombre de pages est important, l'exportation peut prendre un certain temps.",
+    "large_bulk_export_warning": "Pour préserver les ressources du système, veuillez éviter d'exporter un grand nombre de pages consécutivement",
+    "markdown": "Markdown",
+    "choose_export_format": "Sélectionnez le format d'exportation",
+    "bulk_export_started": "Patientez s'il-vous-plait...",
+    "bulk_export_download_expired": "La période de téléchargement a expiré",
+    "bulk_export_job_expired": "Le traitement a été interrompu car le temps d'exportation était trop long",
+    "export_in_progress": "Exportation en cours",
+    "export_in_progress_explanation": "L'exportation avec le même format est déjà en cours. Souhaitez-vous redémarrer pour exporter le dernier contenu de la page ?",
+    "export_cancel_warning": "Les exportations suivantes en cours seront annulées",
+    "restart": "Redémarrage",
+    "format": "Format",
+    "started_on": "Commencé le"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "Connecté!",
     "successfully_connected": "Connecté!",
@@ -864,8 +991,7 @@
   },
   },
   "sidebar_header": {
   "sidebar_header": {
     "show_wip_page": "Voir brouillon",
     "show_wip_page": "Voir brouillon",
-    "size_s": "Taille: P",
-    "size_l": "Taille: G"
+    "compact_view": "Vue compacte"
   },
   },
   "sync-latest-revision-body": {
   "sync-latest-revision-body": {
     "menuitem": "Synchroniser avec la dernière révision",
     "menuitem": "Synchroniser avec la dernière révision",

+ 13 - 11
apps/app/public/static/locales/ja_JP/admin.json

@@ -19,6 +19,7 @@
   "only_me": "自分のみ",
   "only_me": "自分のみ",
   "only_inside_the_group": "特定グループのみ",
   "only_inside_the_group": "特定グループのみ",
   "optional": "オプション",
   "optional": "オプション",
+  "days": "日",
   "security_settings": {
   "security_settings": {
     "security_settings": "セキュリティ設定",
     "security_settings": "セキュリティ設定",
     "scope_of_page_disclosure": "ページの公開範囲",
     "scope_of_page_disclosure": "ページの公開範囲",
@@ -368,6 +369,11 @@
     "file_uploading": "ファイルアップロード",
     "file_uploading": "ファイルアップロード",
     "enable_files_except_image": "画像以外のファイルアップロードを許可",
     "enable_files_except_image": "画像以外のファイルアップロードを許可",
     "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。",
     "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。",
+    "page_bulk_export_settings": "ページ一括エクスポート設定",
+    "enable_page_bulk_export": "一括エクスポートを有効にする",
+    "page_bulk_export_explanation": "すべてのユーザーが、ページメニューから選択したページとその配下ページをまとめてエクスポートできる機能を有効化します。エクスポートされたデータは保存期間経過後に自動的に削除されます。",
+    "page_bulk_export_warning": "ページ一括エクスポート機能は全ユーザーが利用可能です。システムリソースの維持のため、必要最小限の利用にご協力をお願いいたします。管理者の方は、この旨をユーザーの皆様にご周知ください。",
+    "page_bulk_export_storage_period": "保存期間",
     "update": "更新",
     "update": "更新",
     "mail_settings": "メールの設定",
     "mail_settings": "メールの設定",
     "mailer_is_not_set_up": "メール設定がセットアップされていません。",
     "mailer_is_not_set_up": "メール設定がセットアップされていません。",
@@ -402,7 +408,7 @@
     "azure_storage_account_name": "ストレージアカウント名",
     "azure_storage_account_name": "ストレージアカウント名",
     "azure_storage_container_name": "コンテナ名",
     "azure_storage_container_name": "コンテナ名",
     "azure_note_for_the_only_env_option": "現在Azure設定は環境変数の値によって制限されています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
     "azure_note_for_the_only_env_option": "現在Azure設定は環境変数の値によって制限されています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
-    "fixed_by_env_var": "環境変数 <code>FILE_UPLOAD={{fileUploadType}}</code> により固定されています。",
+    "fixed_by_env_var": "環境変数 <code>{{envKey}}={{envVar}}</code> により固定されています。",
     "file_upload": "ファイルをアップロードするための設定を行います。ファイルアップロードの設定を完了させると、ファイルアップロード機能、プロフィール写真機能などが有効になります。",
     "file_upload": "ファイルをアップロードするための設定を行います。ファイルアップロードの設定を完了させると、ファイルアップロード機能、プロフィール写真機能などが有効になります。",
     "test_connection": "接続テスト",
     "test_connection": "接続テスト",
     "change_setting": "この設定を途中で変更すると、これまでにアップロードしたファイル等へのアクセスができなくなりますのでご注意下さい。",
     "change_setting": "この設定を途中で変更すると、これまでにアップロードしたファイル等へのアクセスができなくなりますのでご注意下さい。",
@@ -502,8 +508,9 @@
       "show_all_reply_comments": "返信コメントを全て表示する",
       "show_all_reply_comments": "返信コメントを全て表示する",
       "show_all_reply_comments_desc": "OFFの場合、最新2件のコメント以外が省略されます。",
       "show_all_reply_comments_desc": "OFFの場合、最新2件のコメント以外が省略されます。",
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
-      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
-
+      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。",
+      "show_page_side_authors": "作成者・更新者を目次上部に常時表示する",
+      "show_page_side_authors_desc": "ページサイドバーの目次上部に作成者と最終更新者の情報を表示します。"
     },
     },
     "presentation":"プレゼンテーション",
     "presentation":"プレゼンテーション",
     "presentation_options":{
     "presentation_options":{
@@ -1070,7 +1077,8 @@
     "ADMIN_USER_GROUP_ADD_USER": "ユーザーグループにユーザーを追加",
     "ADMIN_USER_GROUP_ADD_USER": "ユーザーグループにユーザーを追加",
     "ADMIN_SEARCH_CONNECTION": "Elasticsearch の再接続の試行",
     "ADMIN_SEARCH_CONNECTION": "Elasticsearch の再接続の試行",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化",
-    "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド"
+    "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド",
+    "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "ページ一括エクスポート設定の更新"
   },
   },
   "g2g": {
   "g2g": {
     "transfer_success": "G2G移行が完了しました",
     "transfer_success": "G2G移行が完了しました",
@@ -1149,12 +1157,6 @@
   "ai_integration": {
   "ai_integration": {
     "ai_integration": "AI 連携",
     "ai_integration": "AI 連携",
     "disable_mode_explanation": "現在、AI 連携は無効になっています。有効にする場合は環境変数 <code>AI_ENABLED</code> の他、必要な環境変数を設定してください。<br><br>詳細は<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}ja/guide/features/ai-knowledge-assistant.html>ドキュメント</a>を参照してください。",
     "disable_mode_explanation": "現在、AI 連携は無効になっています。有効にする場合は環境変数 <code>AI_ENABLED</code> の他、必要な環境変数を設定してください。<br><br>詳細は<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}ja/guide/features/ai-knowledge-assistant.html>ドキュメント</a>を参照してください。",
-    "ai_search_management": "AI 検索管理",
-    "rebuild_vector_store": "Vector Store のリビルド",
-    "rebuild_vector_store_label": "リビルド",
-    "rebuild_vector_store_explanation1": "既存の Vector Store を削除し、公開ページの Vector Store を再作成します。",
-    "rebuild_vector_store_explanation2": "この作業には数分かかる可能性があります。",
-    "rebuild_vector_store_requested": "Vector Store のリビルドを受け付けました",
-    "rebuild_vector_store_failed": "Vector Store のリビルドに失敗しました"
+    "ai_search_management": "AI 検索管理"
   }
   }
 }
 }

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

@@ -153,6 +153,9 @@
   "Page Tree": "ページツリー",
   "Page Tree": "ページツリー",
   "Bookmarks": "ブックマーク",
   "Bookmarks": "ブックマーク",
   "In-App Notification": "通知",
   "In-App Notification": "通知",
+  "AI Assistant": "AI アシスタント",
+  "Knowledge Assistant": "ナレッジアシスタント (ベータ版)",
+  "Editor Assistant": "エディターアシスタント (ベータ版)",
   "original_path": "元のパス",
   "original_path": "元のパス",
   "new_path": "新しいパス",
   "new_path": "新しいパス",
   "duplicated_path": "重複したパス",
   "duplicated_path": "重複したパス",
@@ -185,7 +188,9 @@
   },
   },
   "author_info": {
   "author_info": {
     "created_at": "作成日",
     "created_at": "作成日",
-    "last_revision_posted_at": "最終更新日"
+    "created_by": "作成者:",
+    "last_revision_posted_at": "最終更新日",
+    "updated_by": "最終更新者:"
   },
   },
   "installer": {
   "installer": {
     "tab": "アカウント作成",
     "tab": "アカウント作成",
@@ -372,7 +377,8 @@
       "text": "テキストのみ",
       "text": "テキストのみ",
       "file": "ファイルのみ"
       "file": "ファイルのみ"
     },
     },
-    "editor_config": "エディタ設定",
+    "editor_config": "エディター設定",
+    "editor_assistant": "エディターアシスタント",
     "Show active line": "アクティブ行をハイライト",
     "Show active line": "アクティブ行をハイライト",
     "auto_format_table": "表の自動整形",
     "auto_format_table": "表の自動整形",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
@@ -487,7 +493,7 @@
   "modal_shortcuts": {
   "modal_shortcuts": {
     "global": {
     "global": {
       "title": "グローバルショートカット",
       "title": "グローバルショートカット",
-      "Open/Close shortcut help": "ショートカットヘルプ<br>表示/非表示",
+      "Open/Close shortcut help": "ショートカットヘルプ<br>表示/非表示",
       "Edit Page": "ページ編集",
       "Edit Page": "ページ編集",
       "Create Page": "ページ作成",
       "Create Page": "ページ作成",
       "Search": "検索",
       "Search": "検索",
@@ -501,11 +507,14 @@
       "Indent": "インデント",
       "Indent": "インデント",
       "Outdent": "左インデント",
       "Outdent": "左インデント",
       "Save Page": "保存",
       "Save Page": "保存",
-      "Delete Line": "行削除"
-    },
-    "commentform": {
-      "title": "コメントフォームショートカット",
-      "Post": "投稿"
+      "Only Editor": "(エディターのみ)",
+      "Delete Line": "行削除",
+      "Search in Editor": "エディター内検索",
+      "Move Line": "行の移動",
+      "Copy Line": "行のコピー",
+      "Toggle Line": "行の非表示化",
+      "Insert Line": "行を挿入",
+      "Post Comment": "(コメント投稿)"
     }
     }
   },
   },
   "modal_resolve_conflict": {
   "modal_resolve_conflict": {
@@ -519,10 +528,11 @@
     "latest_revision": "最新の本文",
     "latest_revision": "最新の本文",
     "selected_editable_revision": "保存するページ本文(編集可能)"
     "selected_editable_revision": "保存するページ本文(編集可能)"
   },
   },
-  "modal_aichat": {
-    "title": "ナレッジアシスタント",
-    "title_beta_label": "(ベータ)",
-    "placeholder": "ききたいことを入力してください",
+  "sidebar_ai_assistant": {
+    "instruction_label": "アシスタントへの指示",
+    "reference_pages_label": "参照するページ",
+    "knowledge_assistant_placeholder": "ききたいことを入力してください",
+    "editor_assistant_placeholder": "お手伝いできることはありますか?",
     "summary_mode_label": "要約モード",
     "summary_mode_label": "要約モード",
     "summary_mode_help": "2~3文以内の簡潔な回答",
     "summary_mode_help": "2~3文以内の簡潔な回答",
     "caution_against_hallucination": "情報が正しいか出典を確認しましょう",
     "caution_against_hallucination": "情報が正しいか出典を確認しましょう",
@@ -531,7 +541,109 @@
     "budget_exceeded": "OpenAI の API の利用上限に達しました。ナレッジアシスタントを再度利用するには OpenAI の請求ページからクレジットを追加してください。",
     "budget_exceeded": "OpenAI の API の利用上限に達しました。ナレッジアシスタントを再度利用するには OpenAI の請求ページからクレジットを追加してください。",
     "budget_exceeded_for_growi_cloud": "OpenAI の API の利用上限に達しました。ナレッジアシスタントを再度利用するには Hosted の場合は GROWI.cloud の管理画面から Owned の場合は OpenAI の請求ページからクレジットを追加してください。",
     "budget_exceeded_for_growi_cloud": "OpenAI の API の利用上限に達しました。ナレッジアシスタントを再度利用するには Hosted の場合は GROWI.cloud の管理画面から Owned の場合は OpenAI の請求ページからクレジットを追加してください。",
     "error_message": "エラーが発生しました",
     "error_message": "エラーが発生しました",
-    "show_error_detail": "詳細を表示"
+    "show_error_detail": "詳細を表示",
+    "discard": "破棄",
+    "accept": "採用",
+    "use_assistant": "アシスタントを使用する",
+    "remove_assistant": "選択されているアシスタントの解除",
+    "preset_menu": {
+      "summarize": {
+        "title": "この記事の要約をつくる",
+        "prompt": "マークダウンの内容を要約してください"
+      },
+      "correct": {
+        "title": "文章の誤りを修正する",
+        "prompt": "マークダウンの内の文章の誤りを修正してください"
+      }
+    }
+  },
+  "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "アシスタントの更新",
+      "add_new_assistant": "新規アシスタントの追加"
+    },
+    "assistant_name_placeholder": "アシスタント名を入力",
+    "page_count": "{{count}} ページ",
+    "memo": {
+      "title": "アシスタントのメモ",
+      "optional": "任意",
+      "placeholder": "内容や用途のメモを表示させることができます",
+      "description": "メモの内容はアシスタントの処理に影響しません。"
+    },
+    "submit_button": {
+      "update_assistant": "アシスタントを更新する",
+      "create_assistant": "アシスタントを作成する"
+    },
+    "toaster": {
+      "create_success": "アシスタントが作成されました",
+      "update_success": "アシスタントが更新されました",
+      "create_failed": "アシスタントの作成に失敗しました",
+      "update_failed": "アシスタントの更新に失敗しました"
+    },
+    "default_instruction": "あなたはこのWikiの知識アシスタントです。以下の方針で支援を行ってください:\n\n- 文書の関連性分析と情報の関連付け\n- 新しい視点の提案\n- 質問の意図を理解した的確な情報提供 必要に応じて構造化された形式で情報を提供します。",
+    "edit_page_description": " アシスタントが参照するページを編集します。<br> 参照できるページは配下ページも含めて {{limitLearnablePageCountPerAssistant}} ページまでです。",
+    "add_page_button": "ページを追加する",
+    "page_mode_title": {
+      "share": "アシスタントの共有",
+      "pages": "参照ページ",
+      "instruction": "アシスタントへの指示"
+    },
+    "share_assistant": "アシスタントを共有する",
+    "page_access_permission": "ページのアクセス権限",
+    "access_scope": {
+      "owner": "{{username}} がアクセス可能な全てのページ",
+      "groups": "グループを指定",
+      "publicOnly": "公開ページのみ"
+    },
+    "share_scope": {
+      "title": "アシスタントの共有範囲",
+      "owner": {
+        "label": "{{username}} のみ"
+      },
+      "publicOnly": {
+        "label": "全体公開",
+        "desc": "すべてのユーザーに共有されます"
+      },
+      "groups": {
+        "label": "グループを指定",
+        "desc": "選択したグループのメンバーにのみ共有されます"
+      },
+      "sameAsAccessScope": {
+        "label": "ページのアクセス権限と同じ範囲",
+        "desc": "ページのアクセス権限と同じ範囲で共有されます"
+      }
+    },
+    "instructions": {
+      "description": "アシスタントの振る舞いを決める指示文を設定できます。<br>この指示に従ってにアシスタントの回答や分析を行います。",
+      "reset_to_default": "デフォルトに戻す"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "共有範囲の確認",
+    "warning_message": "このアシスタントには限定公開されているページが含まれています。<br />現在の設定では、アシスタントを通じてこれらのページの情報が、本来のアクセス権限を超えて共有される可能性があります。",
+    "selected_pages_label": "選択されているページパス",
+    "confirmation_message": "続行する場合、これらのページの内容がアシスタントの公開範囲内で共有される可能性があることを確認してください。",
+    "button": {
+      "review": "設定を見直す",
+      "proceed": "理解して続行する"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "デフォルトアシスタントが設定されていません"
+  },
+  "ai_assistant_tree": {
+    "add_assistant": "アシスタントを追加する",
+    "my_assistants": "マイアシスタント",
+    "team_assistants": "チームアシスタント",
+    "thread_does_not_exist": "スレッドが存在しません",
+    "toaster": {
+      "ai_assistant_deleted_success": "アシスタントを削除しました",
+      "ai_assistant_deleted_failed": "アシスタントの削除に失敗しました",
+      "thread_deleted_success": "スレッドを削除しました",
+      "thread_deleted_failed": "スレッドの削除に失敗しました",
+      "ai_assistant_set_default_success": "デフォルトアシスタントを設定しました",
+      "ai_assistant_set_default_failed": "デフォルトアシスタントの設定に失敗しました"
+    }
   },
   },
   "link_edit": {
   "link_edit": {
     "edit_link": "リンク編集",
     "edit_link": "リンク編集",
@@ -686,11 +798,26 @@
     "discription_heading": "アカウント作成",
     "discription_heading": "アカウント作成",
     "discription": "招待を受け取ったメールアドレスでアカウントを作成します"
     "discription": "招待を受け取ったメールアドレスでアカウントを作成します"
   },
   },
-  "export_bulk": {
+  "page_export": {
     "failed_to_export": "ページのエクスポートに失敗しました",
     "failed_to_export": "ページのエクスポートに失敗しました",
     "failed_to_count_pages": "ページ数の取得に失敗しました",
     "failed_to_count_pages": "ページ数の取得に失敗しました",
     "export_page_markdown": "マークダウン形式でページをエクスポート",
     "export_page_markdown": "マークダウン形式でページをエクスポート",
-    "export_page_pdf": "PDF形式でページをエクスポート"
+    "export_page_pdf": "PDF形式でページをエクスポート",
+    "bulk_export": "ページとその配下のページを全てエクスポート",
+    "bulk_export_download_explanation": "エクスポート完了後に通知が届きます。通知をクリックし、ファイルをダウンロードしてください。",
+    "bulk_export_exec_time_warning": "ページ数が多いと、エクスポートに時間がかかる場合があります",
+    "large_bulk_export_warning": "システムリソースの維持のため、ページ数の多いエクスポートを連続して実行することはご遠慮ください",
+    "markdown": "マークダウン",
+    "choose_export_format": "エクスポート形式を選択してください",
+    "bulk_export_started": "ただいま準備中です...",
+    "bulk_export_download_expired": "ダウンロード期限が切れました",
+    "bulk_export_job_expired": "エクスポート時間が長すぎるため、処理が中断されました",
+    "export_in_progress": "エクスポート進行中",
+    "export_in_progress_explanation": "既に同じ形式でのエクスポートが進行中です。最新のページ内容でエクスポートを最初からやり直しますか?",
+    "export_cancel_warning": "進行中の以下のエクスポートはキャンセルされます",
+    "restart": "やり直す",
+    "format": "形式",
+    "started_on": "開始日時"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "接続に成功しました!",
     "successfully_connected": "接続に成功しました!",
@@ -902,8 +1029,7 @@
   },
   },
   "sidebar_header": {
   "sidebar_header": {
     "show_wip_page": "WIP を表示",
     "show_wip_page": "WIP を表示",
-    "size_s": "サイズ: S",
-    "size_l": "サイズ: L"
+    "compact_view": "コンパクト表示"
   },
   },
   "create_page": {
   "create_page": {
     "untitled": "無題のページ"
     "untitled": "無題のページ"

+ 13 - 10
apps/app/public/static/locales/zh_CN/admin.json

@@ -19,6 +19,7 @@
   "only_me": "只有我",
   "only_me": "只有我",
   "only_inside_the_group": "仅组内",
   "only_inside_the_group": "仅组内",
   "optional": "可选的",
   "optional": "可选的",
+  "days": "天",
   "security_settings": {
   "security_settings": {
     "security_settings": "安全设置",
     "security_settings": "安全设置",
     "scope_of_page_disclosure": "页面公开范围",
     "scope_of_page_disclosure": "页面公开范围",
@@ -368,6 +369,11 @@
     "file_uploading": "文件上传",
     "file_uploading": "文件上传",
     "enable_files_except_image": "启用此选项将允许上传任何文件类型。如果没有此选项,则仅支持图像文件上载。",
     "enable_files_except_image": "启用此选项将允许上传任何文件类型。如果没有此选项,则仅支持图像文件上载。",
     "attach_enable": "如果启用此选项,则可以附加图像文件以外的文件。",
     "attach_enable": "如果启用此选项,则可以附加图像文件以外的文件。",
+    "page_bulk_export_settings": "页面批量导出设置",
+    "enable_page_bulk_export": "启用批量导出",
+    "page_bulk_export_explanation": "启用一项功能,允许所有用户一次性导出从页面菜单中选择的所有页面及其下级页面。保留期限过后,导出的数据将自动删除。",
+    "page_bulk_export_warning": "批量页面导出功能可供所有用户使用。为了维护系统资源,请您配合使用最低限度的资源。如果您是管理员,请将此事实告知所有用户。",
+    "page_bulk_export_storage_period": "储存期限",
     "update": "更新",
     "update": "更新",
     "mail_settings": "邮件设置",
     "mail_settings": "邮件设置",
     "mailer_is_not_set_up": "邮件设置尚未完成。",
     "mailer_is_not_set_up": "邮件设置尚未完成。",
@@ -402,7 +408,7 @@
     "azure_storage_account_name": "Storage Account Name",
     "azure_storage_account_name": "Storage Account Name",
     "azure_storage_container_name": "Container Name",
     "azure_storage_container_name": "Container Name",
     "azure_note_for_the_only_env_option": "The Azure Settings is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
     "azure_note_for_the_only_env_option": "The Azure Settings is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
-    "fixed_by_env_var": "这是由env var 修复的 <code>{{key}}={{value}}</code>.",
+    "fixed_by_env_var": "这是由env var 修复的 <code>{{envKey}}={{envVar}}</code>.",
     "file_upload": "This is for uploading file settings. If you complete file upload settings, file upload function, profile picture function etc will be enabled.",
     "file_upload": "This is for uploading file settings. If you complete file upload settings, file upload function, profile picture function etc will be enabled.",
     "test_connection": "测试邮件服务器连接",
     "test_connection": "测试邮件服务器连接",
     "change_setting": "注意:如果你更改此设置未完成,您将无法访问迄今为止上传的文件。",
     "change_setting": "注意:如果你更改此设置未完成,您将无法访问迄今为止上传的文件。",
@@ -502,7 +508,9 @@
       "show_all_reply_comments": "显示所有回复评论",
       "show_all_reply_comments": "显示所有回复评论",
       "show_all_reply_comments_desc": "当设置值为“关”时,将忽略最近两个之外的注释。",
       "show_all_reply_comments_desc": "当设置值为“关”时,将忽略最近两个之外的注释。",
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
-      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
+      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。",
+      "show_page_side_authors": "在目录上方始终显示创建者和更新者",
+      "show_page_side_authors_desc": "在页面侧边栏的目录上方显示创建者和最后更新者的信息。"
     },
     },
       "presentation": "表达",
       "presentation": "表达",
       "presentation_options": {
       "presentation_options": {
@@ -1069,7 +1077,8 @@
     "ADMIN_USER_GROUP_ADD_USER": "添加用户到用户组",
     "ADMIN_USER_GROUP_ADD_USER": "添加用户到用户组",
     "ADMIN_SEARCH_CONNECTION": "重试Elasticsearch连接",
     "ADMIN_SEARCH_CONNECTION": "重试Elasticsearch连接",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "试图重新连接Elasticsearch",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "试图重新连接Elasticsearch",
-    "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引"
+    "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引",
+    "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "更新页面批量导出设置"
   },
   },
   "g2g": {
   "g2g": {
     "transfer_success": "Completed GROWI to GROWI transfer successfully",
     "transfer_success": "Completed GROWI to GROWI transfer successfully",
@@ -1148,12 +1157,6 @@
   "ai_integration": {
   "ai_integration": {
     "ai_integration": "AI 集成",
     "ai_integration": "AI 集成",
     "disable_mode_explanation": "目前,AI 集成已被禁用。若要启用,请配置 <code>AI_ENABLED</code> 环境变量以及其他必要的变量。<br><br>详细信息请参考<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>文档</a>。",
     "disable_mode_explanation": "目前,AI 集成已被禁用。若要启用,请配置 <code>AI_ENABLED</code> 环境变量以及其他必要的变量。<br><br>详细信息请参考<a target='blank' rel='noopener noreferrer' href={{documentationUrl}}en/guide/features/ai-knowledge-assistant.html>文档</a>。",
-    "ai_search_management": "AI 搜索管理",
-    "rebuild_vector_store": "重建矢量商店",
-    "rebuild_vector_store_label": "重建",
-    "rebuild_vector_store_explanation1": "删除现有的矢量存储,在公共页面上重新创建矢量存储。",
-    "rebuild_vector_store_explanation2": "这个过程可能需要几分钟。",
-    "rebuild_vector_store_requested": "已要求重建矢量存储库",
-    "rebuild_vector_store_failed": "向量存储区重建失败"
+    "ai_search_management": "AI 搜索管理"
   }
   }
 }
 }

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

@@ -158,6 +158,9 @@
   "Page Tree": "页面树",
   "Page Tree": "页面树",
   "Bookmarks": "书签",
   "Bookmarks": "书签",
   "In-App Notification": "通知",
   "In-App Notification": "通知",
+  "AI Assistant": "AI助手",
+  "Knowledge Assistant": "知识助手 (测试版)",
+  "Editor Assistant": "编辑助理 (测试版)",
   "original_path": "Original path",
   "original_path": "Original path",
   "new_path": "New path",
   "new_path": "New path",
   "duplicated_path": "Duplicated path",
   "duplicated_path": "Duplicated path",
@@ -189,10 +192,12 @@
   "custom_navigation": {
   "custom_navigation": {
     "no_pages_under_this_page": "There are no pages under this page."
     "no_pages_under_this_page": "There are no pages under this page."
   },
   },
-  "author_info": {
-    "created_at": "Created at",
-    "last_revision_posted_at": "Last revision posted at"
-  },
+"author_info": {
+  "created_at": "创建日期",
+  "created_by": "创建者:",
+  "last_revision_posted_at": "最后更新日期",
+  "updated_by": "更新者:"
+},
   "installer": {
   "installer": {
     "tab": "创建账户",
     "tab": "创建账户",
     "title": "安装",
     "title": "安装",
@@ -330,6 +335,7 @@
       "file": "仅文件"
       "file": "仅文件"
     },
     },
     "editor_config": "编辑器配置",
     "editor_config": "编辑器配置",
+    "editor_assistant": "编辑助手",
 		"Show active line": "显示活动行",
 		"Show active line": "显示活动行",
 		"auto_format_table": "自动格式化表格",
 		"auto_format_table": "自动格式化表格",
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
@@ -449,6 +455,7 @@
       "Create Page": "创建页面",
       "Create Page": "创建页面",
       "Search": "搜索",
       "Search": "搜索",
       "Show Contributors": "显示参与者",
       "Show Contributors": "显示参与者",
+      "MirrorMode": "镜像模式",
       "Konami Code": "Konami Code",
       "Konami Code": "Konami Code",
       "konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
       "konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
     },
     },
@@ -457,11 +464,14 @@
       "Indent": "缩进",
       "Indent": "缩进",
       "Outdent": "回退缩进",
       "Outdent": "回退缩进",
       "Save Page": "保存页面",
       "Save Page": "保存页面",
-      "Delete Line": "删除行"
-    },
-    "commentform": {
-      "title": "注释窗体快捷方式",
-      "Post": "提交"
+      "Only Editor": "(仅编辑器)",
+      "Delete Line": "删除行",
+      "Search in Editor": "编辑器内搜索",
+      "Move Line": "移动行",
+      "Copy Line": "复制行",
+      "Toggle Line": "注释/取消注释行",
+      "Insert Line": "插入行",
+      "Post Comment": "(发表评论)"
     }
     }
   },
   },
   "modal_resolve_conflict": {
   "modal_resolve_conflict": {
@@ -475,10 +485,11 @@
     "latest_revision": "最新页面正文",
     "latest_revision": "最新页面正文",
     "selected_editable_revision": "选定的可编辑页面正文"
     "selected_editable_revision": "选定的可编辑页面正文"
   },
   },
-  "modal_aichat": {
-    "title": "知识助手",
-    "title_beta_label": "(测试版)",
-    "placeholder": "问我任何问题。",
+  "sidebar_ai_assistant": {
+    "instruction_label": "助手指令",
+    "reference_pages_label": "参考页面",
+    "knowledge_assistant_placeholder": "问我任何问题。",
+    "editor_assistant_placeholder": "有什么需要帮忙的吗?",
     "summary_mode_label": "摘要模式",
     "summary_mode_label": "摘要模式",
     "summary_mode_help": "简洁回答在2-3句话内",
     "summary_mode_help": "简洁回答在2-3句话内",
     "caution_against_hallucination": "请核实信息并检查来源。",
     "caution_against_hallucination": "请核实信息并检查来源。",
@@ -487,7 +498,109 @@
     "budget_exceeded": "您已达到 OpenAI API 的使用上限。要再次使用知识助手,请从 OpenAI 账单页面添加点数。",
     "budget_exceeded": "您已达到 OpenAI API 的使用上限。要再次使用知识助手,请从 OpenAI 账单页面添加点数。",
     "budget_exceeded_for_growi_cloud": "您已达到 OpenAI API 使用上限。如需再次使用知识助手,请从GROWI.cloud管理页面为托管用户添加点数,或从OpenAI计费页面为自有用户添加点数。",
     "budget_exceeded_for_growi_cloud": "您已达到 OpenAI API 使用上限。如需再次使用知识助手,请从GROWI.cloud管理页面为托管用户添加点数,或从OpenAI计费页面为自有用户添加点数。",
     "error_message": "错误",
     "error_message": "错误",
-    "show_error_detail": "显示详情"
+    "show_error_detail": "显示详情",
+    "discard": "丢弃",
+    "accept": "接受",
+    "use_assistant": "使用助手",
+    "remove_assistant": "取消选定的助手",
+    "preset_menu": {
+      "summarize": {
+        "title": "为此文章创建摘要",
+        "prompt": "请总结这个 markdown 内容"
+      },
+      "correct": {
+        "title": "修正文本中的错误",
+        "prompt": "请修正 markdown 中的文本错误"
+      }
+    }
+  },
+  "modal_ai_assistant": {
+    "header": {
+      "update_assistant": "更新助手",
+      "add_new_assistant": "添加新助手"
+    },
+    "assistant_name_placeholder": "输入助手名称",
+    "page_count": "{{count}} 页",
+    "memo": {
+      "title": "助手备忘录",
+      "optional": "可选",
+      "placeholder": "您可以显示关于内容和用途的备注",
+      "description": "备忘录的内容不会影响助手的处理。"
+    },
+    "submit_button": {
+      "update_assistant": "更新助手",
+      "create_assistant": "创建助手"
+    },
+    "toaster": {
+      "create_success": "助手已创建",
+      "update_success": "助手已更新",
+      "create_failed": "创建助手失败",
+      "update_failed": "更新助手失败"
+    },
+    "edit_page_description": "编辑助手可以参考的页面。<br> 助手可以参考最多 {{limitLearnablePageCountPerAssistant}} 个页面,包括子页面。",
+    "default_instruction": "您是这个Wiki的知识助手。请按照以下方针提供支持:\n\n- 分析文档相关性并连接信息\n- 提出新的观点\n- 理解问题意图并提供准确信息\n必要时我会以结构化的形式提供信息。",
+    "add_page_button": "添加页面",
+    "page_mode_title": {
+      "share": "助理共享",
+      "pages": "参考页面",
+      "instruction": "助理指示"
+    },
+    "share_assistant": "共享助手",
+    "page_access_permission": "页面访问权限",
+    "access_scope": {
+      "owner": "{{username}} 可访问的所有页面",
+      "groups": "指定群组",
+      "publicOnly": "仅公开页面"
+    },
+    "share_scope": {
+      "title": "助手共享范围",
+      "owner": {
+        "label": "仅 {{ username }}"
+      },
+      "publicOnly": {
+        "label": "公开",
+        "desc": "与所有用户共享"
+      },
+      "groups": {
+        "label": "指定群组",
+        "desc": "仅与选定组的成员共享"
+      },
+      "sameAsAccessScope": {
+        "label": "与页面访问范围相同",
+        "desc": "与页面访问范围相同的范围共享"
+      }
+    },
+    "instructions": {
+      "description": "您可以设置决定助手行为的指令。<br>助手将根据这些指令进行回答和分析。",
+      "reset_to_default": "恢复默认设置"
+    }
+  },
+  "share_scope_warning_modal": {
+    "header_title": "确认共享范围",
+    "warning_message": "此助手包含访问受限的页面。<br>使用当前设置,这些页面的信息可能通过助手超出其原始访问权限范围进行共享。",
+    "selected_pages_label": "已选择的页面路径",
+    "confirmation_message": "如果继续,请确认您了解这些页面的内容可能会在助手的公开范围内共享。",
+    "button": {
+      "review": "重新检查设置",
+      "proceed": "了解并继续"
+    }
+  },
+  "default_ai_assistant": {
+    "not_set": "未设置默认助手"
+  },
+  "ai_assistant_tree": {
+    "add_assistant": "添加助手",
+    "my_assistants": "我的助手",
+    "team_assistants": "团队助手",
+    "thread_does_not_exist": "暂无会话",
+    "toaster": {
+      "ai_assistant_deleted_success": "已删除助手",
+      "ai_assistant_deleted_failed": "删除助手失败",
+      "thread_deleted_success": "已删除会话",
+      "thread_deleted_failed": "删除会话失败",
+      "ai_assistant_set_default_success": "已成功设置默认助手",
+      "ai_assistant_set_default_failed": "设置默认助手失败"
+    }
   },
   },
   "link_edit": {
   "link_edit": {
     "edit_link": "Edit Link",
     "edit_link": "Edit Link",
@@ -656,11 +769,26 @@
     "discription_heading": "创建账户",
     "discription_heading": "创建账户",
     "discription": "用被邀请的电子邮件地址创建一个你的账户"
     "discription": "用被邀请的电子邮件地址创建一个你的账户"
   },
   },
-  "export_bulk": {
+  "page_export": {
     "failed_to_export": "导出失败",
     "failed_to_export": "导出失败",
     "failed_to_count_pages": "页面计数失败",
     "failed_to_count_pages": "页面计数失败",
     "export_page_markdown": "以Markdown格式导出页面",
     "export_page_markdown": "以Markdown格式导出页面",
-    "export_page_pdf": "以PDF格式导出页面"
+    "export_page_pdf": "以PDF格式导出页面",
+    "bulk_export": "导出页面及其下的所有页面",
+    "bulk_export_download_explanation": "导出完成后将发送通知。要下载导出的文件,请单击通知。",
+    "bulk_export_exec_time_warning": "如果页数较多,导出可能需要一段时间",
+    "large_bulk_export_warning": "为了节省系统资源,请避免连续导出大量页面",
+    "markdown": "Markdown",
+    "choose_export_format": "选择导出格式",
+    "bulk_export_started": "目前我们正在准备...",
+    "bulk_export_download_expired": "下载期限已过",
+    "bulk_export_job_expired": "由于导出时间太长,处理被中断",
+    "export_in_progress": "导出正在进行中",
+    "export_in_progress_explanation": "已在进行相同格式的导出。您要重新启动以导出最新的页面内容吗?",
+    "export_cancel_warning": "以下正在进行的导出将被取消",
+    "restart": "重新开始",
+    "format": "格式",
+    "started_on": "开始于"
   },
   },
   "message": {
   "message": {
     "successfully_connected": "连接成功!",
     "successfully_connected": "连接成功!",
@@ -872,8 +1000,7 @@
   },
   },
   "sidebar_header": {
   "sidebar_header": {
     "show_wip_page": "显示 WIP",
     "show_wip_page": "显示 WIP",
-    "size_s": "尺寸: S",
-    "size_l": "尺寸: L"
+    "compact_view": "紧凑视图"
   },
   },
   "create_page": {
   "create_page": {
     "untitled": "Untitled"
     "untitled": "Untitled"

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 type EnvVarsTableProps = {
 type EnvVarsTableProps = {
   envVars: Record<string, string | number | boolean>,
   envVars: Record<string, string | number | boolean>,

+ 11 - 0
apps/app/src/client/components/Admin/App/AppSettingsPageContents.tsx

@@ -14,6 +14,7 @@ import AppSetting from './AppSetting';
 import FileUploadSetting from './FileUploadSetting';
 import FileUploadSetting from './FileUploadSetting';
 import MailSetting from './MailSetting';
 import MailSetting from './MailSetting';
 import { MaintenanceMode } from './MaintenanceMode';
 import { MaintenanceMode } from './MaintenanceMode';
+import PageBulkExportSettings from './PageBulkExportSettings';
 import QuestionnaireSettings from './QuestionnaireSettings';
 import QuestionnaireSettings from './QuestionnaireSettings';
 import SiteUrlSetting from './SiteUrlSetting';
 import SiteUrlSetting from './SiteUrlSetting';
 import V5PageMigration from './V5PageMigration';
 import V5PageMigration from './V5PageMigration';
@@ -108,6 +109,16 @@ const AppSettingsPageContents = (props: Props) => {
         </div>
         </div>
       </div>
       </div>
 
 
+      {/* TODO: Enable configuring bulk export for GROWI.cloud when it can be relased for cloud (https://redmine.weseek.co.jp/issues/163220) */}
+      {!adminAppContainer.state.isBulkExportDisabledForCloud && (
+        <div className="row mt-5">
+          <div className="col-lg-12">
+            <h2 className="admin-setting-header">{t('admin:app_setting.page_bulk_export_settings')}</h2>
+            <PageBulkExportSettings />
+          </div>
+        </div>
+      )}
+
       <div className="row mt-5">
       <div className="row mt-5">
         <div className="col-lg-12">
         <div className="col-lg-12">
           <h2 className="admin-setting-header">{t('admin:app_setting.questionnaire_settings')}</h2>
           <h2 className="admin-setting-header">{t('admin:app_setting.questionnaire_settings')}</h2>

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

@@ -1,5 +1,8 @@
+import type { JSX } from 'react';
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
+
 export type AwsSettingMoleculeProps = {
 export type AwsSettingMoleculeProps = {
   s3ReferenceFileWithRelayMode
   s3ReferenceFileWithRelayMode
   s3Region
   s3Region

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

@@ -1,7 +1,10 @@
+import type { JSX } from 'react';
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
 import MaskedInput from './MaskedInput';
 import MaskedInput from './MaskedInput';
 
 
+
 export type AzureSettingMoleculeProps = {
 export type AzureSettingMoleculeProps = {
   azureReferenceFileWithRelayMode
   azureReferenceFileWithRelayMode
   azureUseOnlyEnvVars
   azureUseOnlyEnvVars

+ 10 - 6
apps/app/src/client/components/Admin/App/FileUploadSetting.tsx

@@ -1,10 +1,11 @@
-import type { ChangeEvent } from 'react';
+import type { ChangeEvent, JSX } from 'react';
 import React, { useCallback } from 'react';
 import React, { useCallback } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { toastSuccess, toastError } from '~/client/util/toastr';
+import { FileUploadType } from '~/interfaces/file-uploader';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
@@ -16,9 +17,6 @@ import type { AzureSettingMoleculeProps } from './AzureSetting';
 import { GcsSettingMolecule } from './GcsSetting';
 import { GcsSettingMolecule } from './GcsSetting';
 import type { GcsSettingMoleculeProps } from './GcsSetting';
 import type { GcsSettingMoleculeProps } from './GcsSetting';
 
 
-
-const fileUploadTypes = ['aws', 'gcs', 'azure', 'gridfs', 'local'] as const;
-
 type FileUploadSettingMoleculeProps = {
 type FileUploadSettingMoleculeProps = {
   fileUploadType: string
   fileUploadType: string
   isFixedFileUploadByEnvVar: boolean
   isFixedFileUploadByEnvVar: boolean
@@ -45,7 +43,7 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
         </label>
         </label>
 
 
         <div className="col-md-6 py-2">
         <div className="col-md-6 py-2">
-          {fileUploadTypes.map((type) => {
+          {Object.values(FileUploadType).map((type) => {
             return (
             return (
               <div key={type} className="form-check form-check-inline">
               <div key={type} className="form-check form-check-inline">
                 <input
                 <input
@@ -67,7 +65,13 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
             <span className="material-symbols-outlined">help</span>
             <span className="material-symbols-outlined">help</span>
             <b>FIXED</b><br />
             <b>FIXED</b><br />
             {/* eslint-disable-next-line react/no-danger */}
             {/* eslint-disable-next-line react/no-danger */}
-            <b dangerouslySetInnerHTML={{ __html: t('admin:app_setting.fixed_by_env_var', { fileUploadType: props.envFileUploadType }) }} />
+            <b dangerouslySetInnerHTML={{
+              __html: t('admin:app_setting.fixed_by_env_var', {
+                envKey: 'FILE_UPLOAD',
+                envVar: props.envFileUploadType,
+              }),
+            }}
+            />
           </p>
           </p>
         )}
         )}
       </div>
       </div>

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

@@ -1,5 +1,8 @@
+import type { JSX } from 'react';
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
+
 export type GcsSettingMoleculeProps = {
 export type GcsSettingMoleculeProps = {
   gcsReferenceFileWithRelayMode
   gcsReferenceFileWithRelayMode
   gcsUseOnlyEnvVars
   gcsUseOnlyEnvVars

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

@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, type JSX } from 'react';
 
 
 import styles from './MaskedInput.module.scss';
 import styles from './MaskedInput.module.scss';
 
 

+ 136 - 0
apps/app/src/client/components/Admin/App/PageBulkExportSettings.tsx

@@ -0,0 +1,136 @@
+import {
+  useState, useCallback, useEffect, type JSX,
+} from 'react';
+
+import { LoadingSpinner } from '@growi/ui/dist/components';
+import { useTranslation } from 'next-i18next';
+
+import { apiv3Put } from '~/client/util/apiv3-client';
+import { toastSuccess, toastError } from '~/client/util/toastr';
+import { useSWRxAppSettings } from '~/stores/admin/app-settings';
+
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+
+const PageBulkExportSettings = (): JSX.Element => {
+  const { t } = useTranslation(['admin', 'commons']);
+
+  const { data, error, mutate } = useSWRxAppSettings();
+
+  const [isBulkExportPagesEnabled, setIsBulkExportPagesEnabled] = useState(data?.isBulkExportPagesEnabled);
+  const [bulkExportDownloadExpirationSeconds, setBulkExportDownloadExpirationSeconds] = useState(data?.bulkExportDownloadExpirationSeconds);
+
+  const changeBulkExportDownloadExpirationSeconds = (bulkExportDownloadExpirationDays: number) => {
+    const bulkExportDownloadExpirationSeconds = bulkExportDownloadExpirationDays * 24 * 60 * 60;
+    setBulkExportDownloadExpirationSeconds(bulkExportDownloadExpirationSeconds);
+  };
+
+  const onSubmitHandler = useCallback(async() => {
+    try {
+      await apiv3Put('/app-settings/page-bulk-export-settings', {
+        isBulkExportPagesEnabled,
+        bulkExportDownloadExpirationSeconds,
+      });
+      toastSuccess(t('commons:toaster.update_successed', { target: t('app_setting.questionnaire_settings') }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+    mutate();
+  }, [isBulkExportPagesEnabled, bulkExportDownloadExpirationSeconds, mutate, t]);
+
+  useEffect(() => {
+    if (data?.useOnlyEnvVarForFileUploadType) {
+      setIsBulkExportPagesEnabled(data?.envIsBulkExportPagesEnabled);
+    }
+    else {
+      setIsBulkExportPagesEnabled(data?.isBulkExportPagesEnabled);
+    }
+    setBulkExportDownloadExpirationSeconds(data?.bulkExportDownloadExpirationSeconds);
+  }, [data]);
+
+  const isLoading = data === undefined && error === undefined;
+
+  return (
+    <>
+      {isLoading && (
+        <div className="text-muted text-center mb-5">
+          <LoadingSpinner className="me-1 fs-3" />
+        </div>
+      )}
+
+      {!isLoading && (
+        <>
+          <p className="card custom-card bg-warning-subtle my-3">
+            {t('admin:app_setting.page_bulk_export_explanation')} <br />
+            <span className="text-danger mt-1">
+              {t('admin:app_setting.page_bulk_export_warning')}
+            </span>
+          </p>
+
+          <div className="my-4 row">
+            <label
+              className="text-start text-md-end col-md-3 col-form-label"
+            >
+            </label>
+
+            <div className="col-md-6">
+              <div className="form-check form-switch form-check-info">
+                <input
+                  type="checkbox"
+                  className="form-check-input"
+                  id="cbIsPageBulkExportEnabled"
+                  checked={isBulkExportPagesEnabled}
+                  disabled={data?.useOnlyEnvVarsForIsBulkExportPagesEnabled}
+                  onChange={e => setIsBulkExportPagesEnabled(e.target.checked)}
+                />
+                <label className="form-label form-check-label" htmlFor="cbIsPageBulkExportEnabled">
+                  {t('app_setting.enable_page_bulk_export')}
+                </label>
+              </div>
+              {data?.useOnlyEnvVarsForIsBulkExportPagesEnabled && (
+                <p className="form-text text-muted">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <b dangerouslySetInnerHTML={{
+                    __html: t('admin:app_setting.fixed_by_env_var', {
+                      envKey: 'BULK_EXPORT_PAGES_ENABLED',
+                      envVar: isBulkExportPagesEnabled,
+                    }),
+                  }}
+                  />
+                </p>
+              )}
+            </div>
+          </div>
+
+          <div className="mb-4">
+            <div className="row">
+              <label
+                className="text-start text-md-end col-md-3 col-form-label"
+              >
+                {t('app_setting.page_bulk_export_storage_period')}
+              </label>
+
+              <div className="col-md-2">
+                <select
+                  className="form-select"
+                  value={(bulkExportDownloadExpirationSeconds ?? 0) / (24 * 60 * 60)}
+                  onChange={(e) => { changeBulkExportDownloadExpirationSeconds(Number(e.target.value)) }}
+                >
+                  {Array.from({ length: 7 }, (_, i) => i + 1).map(number => (
+                    <option key={`be-download-expiration-option-${number}`} value={number}>
+                      {number} {t('admin:days')}
+                    </option>
+                  ))}
+                </select>
+              </div>
+            </div>
+          </div>
+
+          <AdminUpdateButtonRow onClick={onSubmitHandler} />
+        </>
+      )}
+    </>
+  );
+};
+
+export default PageBulkExportSettings;

+ 43 - 29
apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx

@@ -1,5 +1,5 @@
 import {
 import {
-  useState, useCallback, useEffect,
+  useState, useCallback, useEffect, type JSX,
 } from 'react';
 } from 'react';
 
 
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { LoadingSpinner } from '@growi/ui/dist/components';
@@ -72,37 +72,51 @@ const QuestionnaireSettings = (): JSX.Element => {
 
 
       {!isLoading && (
       {!isLoading && (
         <>
         <>
-          <div className="my-4">
-            <div className="form-check form-switch form-check-info">
-              <input
-                type="checkbox"
-                className="form-check-input"
-                id="isQuestionnaireEnabled"
-                checked={isQuestionnaireEnabled}
-                onChange={onChangeIsQuestionnaireEnabledHandler}
-              />
-              <label className="form-label form-check-label" htmlFor="isQuestionnaireEnabled">
-                {t('app_setting.enable_questionnaire')}
-              </label>
+          <div className="my-4 row">
+            <label
+              className="text-start text-md-end col-md-3 col-form-label"
+            >
+            </label>
+
+            <div className="col-md-6">
+              <div className="form-check form-switch form-check-info">
+                <input
+                  type="checkbox"
+                  className="form-check-input"
+                  id="isQuestionnaireEnabled"
+                  checked={isQuestionnaireEnabled}
+                  onChange={onChangeIsQuestionnaireEnabledHandler}
+                />
+                <label className="form-label form-check-label" htmlFor="isQuestionnaireEnabled">
+                  {t('app_setting.enable_questionnaire')}
+                </label>
+              </div>
             </div>
             </div>
           </div>
           </div>
 
 
-          <div className="my-4">
-            <div className="form-check form-check-info">
-              <input
-                type="checkbox"
-                className="form-check-input"
-                id="isAppSiteUrlHashed"
-                checked={isAppSiteUrlHashed}
-                onChange={onChangeisAppSiteUrlHashedHandler}
-                disabled={!isQuestionnaireEnabled}
-              />
-              <label className="form-label form-check-label" htmlFor="isAppSiteUrlHashed">
-                {t('app_setting.anonymize_app_site_url')}
-              </label>
-              <p className="form-text text-muted small">
-                {t('app_setting.url_anonymization_explanation')}
-              </p>
+          <div className="my-4 row">
+            <label
+              className="text-start text-md-end col-md-3 col-form-label"
+            >
+            </label>
+
+            <div className="col-md-6">
+              <div className="form-check form-check-info">
+                <input
+                  type="checkbox"
+                  className="form-check-input"
+                  id="isAppSiteUrlHashed"
+                  checked={isAppSiteUrlHashed}
+                  onChange={onChangeisAppSiteUrlHashedHandler}
+                  disabled={!isQuestionnaireEnabled}
+                />
+                <label className="form-label form-check-label" htmlFor="isAppSiteUrlHashed">
+                  {t('app_setting.anonymize_app_site_url')}
+                </label>
+                <p className="form-text text-muted small">
+                  {t('app_setting.url_anonymization_explanation')}
+                </p>
+              </div>
             </div>
             </div>
           </div>
           </div>
 
 

+ 1 - 1
apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 type Props = {
 type Props = {
   onClick: () => void,
   onClick: () => void,

+ 1 - 1
apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

+ 1 - 1
apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { Progress } from 'reactstrap';
 import { Progress } from 'reactstrap';
 
 

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

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
 import { Card, CardBody } from 'reactstrap';

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 type Props = {
 type Props = {
   optionId: string
   optionId: string

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

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
 import { Card, CardBody } from 'reactstrap';
@@ -133,6 +133,20 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
             </div>
             </div>
           </div>
           </div>
 
 
+          <div className="row">
+            <div className="offset-md-2 col-md-7 text-start">
+              <CustomizeFunctionOption
+                optionId="showPageSideAuthors"
+                label={t('admin:customize_settings.function_options.show_page_side_authors')}
+                isChecked={adminCustomizeContainer.state.showPageSideAuthors}
+                onChecked={() => { adminCustomizeContainer.switchShowPageSideAuthors() }}
+              >
+                <p className="form-text text-muted">
+                  {t('admin:customize_settings.function_options.show_page_side_authors_desc')}
+                </p>
+              </CustomizeFunctionOption>
+            </div>
+          </div>
 
 
           <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
         </div>
         </div>

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

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useCallback, useEffect, useState,
+  useCallback, useEffect, useState, type JSX,
 } from 'react';
 } from 'react';
 
 
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { LoadingSpinner } from '@growi/ui/dist/components';

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

@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useState, type JSX } from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 

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

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { PrismAsyncLight } from 'react-syntax-highlighter';
 import { PrismAsyncLight } from 'react-syntax-highlighter';

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

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

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

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { PrismAsyncLight } from 'react-syntax-highlighter';
 import { PrismAsyncLight } from 'react-syntax-highlighter';

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

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React, { useMemo, type JSX } from 'react';
 
 
 import { type GrowiThemeMetadata, GrowiThemeSchemeType } from '@growi/core';
 import { type GrowiThemeMetadata, GrowiThemeSchemeType } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,4 +1,6 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import React, {
+  useCallback, useEffect, useState, type JSX,
+} from 'react';
 
 
 import { PresetThemes, PresetThemesMetadatas } from '@growi/preset-themes';
 import { PresetThemes, PresetThemesMetadatas } from '@growi/preset-themes';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import type { GrowiThemeMetadata } from '@growi/core';
 import type { GrowiThemeMetadata } from '@growi/core';
 
 

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { format } from 'date-fns/format';
 import { format } from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTableMenu.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

+ 3 - 1
apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx

@@ -1,4 +1,6 @@
-import React, { useCallback, useState, useEffect } from 'react';
+import React, {
+  useCallback, useState, useEffect, type JSX,
+} from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {

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

@@ -1,4 +1,6 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import React, {
+  useCallback, useEffect, useState, type JSX,
+} from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import DefaultErrorPage from 'next/error';
 import DefaultErrorPage from 'next/error';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

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

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useCallback, useEffect, useState,
+  useCallback, useEffect, useState, type JSX,
 } from 'react';
 } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useState, useEffect, useCallback, useMemo,
+  useState, useEffect, useCallback, useMemo, type JSX,
 } from 'react';
 } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,4 +1,4 @@
-import React, { type ComponentPropsWithoutRef } from 'react';
+import React, { type ComponentPropsWithoutRef, type JSX } from 'react';
 
 
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 
 

+ 1 - 1
apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 
 

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

@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect } from 'react';
+import React, { useCallback, useEffect, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import Link from 'next/link';

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

@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
 import { Card, CardBody } from 'reactstrap';

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

@@ -1,4 +1,4 @@
-import { useCallback, useRef } from 'react';
+import { useCallback, useRef, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

+ 1 - 1
apps/app/src/client/components/Admin/Notification/ManageGlobalNotification.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useCallback, useMemo, useEffect, useState,
+  useCallback, useMemo, useEffect, useState, type JSX,
 } from 'react';
 } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/Notification/NotificationTypeIcon.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { UncontrolledTooltip } from 'reactstrap';
 import { UncontrolledTooltip } from 'reactstrap';
 
 

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

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, type JSX } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 

+ 1 - 1
apps/app/src/client/components/Admin/SlackIntegration/BotTypeCard.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { SlackbotType } from '@growi/slack';
 import { SlackbotType } from '@growi/slack';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 3 - 0
apps/app/src/client/components/Admin/SlackIntegration/Bridge.tsx

@@ -1,7 +1,10 @@
 
 
+import type { JSX } from 'react';
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip } from 'reactstrap';
 import { UncontrolledTooltip } from 'reactstrap';
 
 
+
 const ProxyCircle = () => (
 const ProxyCircle = () => (
   <div className="grw-bridge-proxy-circle position-relative">
   <div className="grw-bridge-proxy-circle position-relative">
     <div className="circle position-absolute m-auto z-1 bg-primary border-light rounded-circle">
     <div className="circle position-absolute m-auto z-1 bg-primary border-light rounded-circle">

+ 1 - 1
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import type { ConnectionStatus } from '@growi/slack';
 import type { ConnectionStatus } from '@growi/slack';
 import Image from 'next/image';
 import Image from 'next/image';

+ 1 - 1
apps/app/src/client/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import type { ConnectionStatus } from '@growi/slack';
 import type { ConnectionStatus } from '@growi/slack';
 import Image from 'next/image';
 import Image from 'next/image';

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

@@ -1,6 +1,7 @@
 import React from 'react';
 import React from 'react';
-import PropTypes from 'prop-types';
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 
 
 
 const MessageBasedOnConnection = (props) => {
 const MessageBasedOnConnection = (props) => {

+ 3 - 0
apps/app/src/client/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx

@@ -1,6 +1,9 @@
 
 
+import type { JSX } from 'react';
+
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
+
 type Props = {
 type Props = {
   slackAppIntegration: {
   slackAppIntegration: {
     _id: string,
     _id: string,

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

@@ -1,4 +1,6 @@
-import React, { useState, useEffect, useCallback } from 'react';
+import React, {
+  useState, useEffect, useCallback, type JSX,
+} from 'react';
 
 
 import { SlackbotType } from '@growi/slack';
 import { SlackbotType } from '@growi/slack';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { LoadingSpinner } from '@growi/ui/dist/components';

+ 2 - 1
apps/app/src/client/components/Admin/UserGroup/UserGroupDropdown.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useCallback } from 'react';
 
 
 import type { IUserGroupHasId } from '@growi/core';
 import type { IUserGroupHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,4 +1,4 @@
-import type { FC } from 'react';
+import type { FC, JSX } from 'react';
 import React, { useState, useEffect } from 'react';
 import React, { useState, useEffect } from 'react';
 
 
 import type { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '@growi/core';
 import type { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '@growi/core';

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

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useState, useCallback, useEffect,
+  useState, useCallback, useEffect, type JSX,
 } from 'react';
 } from 'react';
 
 
 import {
 import {

+ 3 - 1
apps/app/src/client/components/Admin/UserGroupDetail/UserGroupPageList.tsx

@@ -1,4 +1,6 @@
-import React, { useEffect, useState, useCallback } from 'react';
+import React, {
+  useEffect, useState, useCallback, type JSX,
+} from 'react';
 
 
 import type { IPageHasId } from '@growi/core';
 import type { IPageHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import type { IUserGroupHasId, IUserHasId } from '@growi/core';
 import type { IUserGroupHasId, IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

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

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 import { UserPicture } from '@growi/ui/dist/components';
 import { UserPicture } from '@growi/ui/dist/components';
 import { format as dateFnsFormat } from 'date-fns/format';
 import { format as dateFnsFormat } from 'date-fns/format';

+ 1 - 1
apps/app/src/client/components/Admin/Users/ExternalAccountTable.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import type { IAdminExternalAccount } from '@growi/core';
 import type { IAdminExternalAccount } from '@growi/core';
 import { format as dateFnsFormat } from 'date-fns/format';
 import { format as dateFnsFormat } from 'date-fns/format';

+ 1 - 1
apps/app/src/client/components/Admin/Users/GrantAdminButton.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import type { IUserHasId } from '@growi/core';
 import type { IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/Users/GrantReadOnlyButton.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import type { IUserHasId } from '@growi/core';
 import type { IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/Users/RevokeAdminButton.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import type { IUserHasId } from '@growi/core';
 import type { IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/Users/RevokeAdminMenuItem.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import type { IUserHasId } from '@growi/core';
 import type { IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/Users/RevokeReadOnlyMenuItem.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import type { IUserHasId } from '@growi/core';
 import type { IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

+ 1 - 1
apps/app/src/client/components/Admin/Users/SortIcons.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { type JSX } from 'react';
 
 
 type SortIconsProps = {
 type SortIconsProps = {
   onClick: (sortOrder: string) => void,
   onClick: (sortOrder: string) => void,

+ 1 - 1
apps/app/src/client/components/Admin/Users/StatusSuspendMenuItem.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, type JSX } from 'react';
 
 
 import type { IUserHasId } from '@growi/core';
 import type { IUserHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';

Some files were not shown because too many files changed in this diff