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

Merge branch 'master' into support/new-config-manager

Yuki Takei 1 год назад
Родитель
Сommit
5a20a5dff4

+ 24 - 1
CHANGELOG.md

@@ -1,9 +1,32 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.1.2...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.1.4...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v7.1.4](https://github.com/weseek/growi/compare/v7.1.3...v7.1.4) - 2024-11-26
+
+### 🐛 Bug Fixes
+
+* fix: Failed to export the page markdown (#9444) @miya
+
+## [v7.1.3](https://github.com/weseek/growi/compare/v7.1.2...v7.1.3) - 2024-11-26
+
+### 💎 Features
+
+* feat(ai): Set a rate limit for vector store rebuild (#9404) @miya
+
+### 🚀 Improvement
+
+* imprv: Fonts preload settings (#9432) @yuki-takei
+* imprv: Use stream.pipeline (#9361) @reiji-h
+
+### 🐛 Bug Fixes
+
+* fix: Retrieving runtime versions (#9438) @yuki-takei
+* fix: Notification for new user creation (#9434) @yuki-takei
+* fix:  Deleted pages appear in the page tree (#9337) @reiji-h
+
 ## [v7.1.2](https://github.com/weseek/growi/compare/v7.1.1...v7.1.2) - 2024-11-18
 
 ### 🚀 Improvement

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

@@ -36,6 +36,7 @@ module.exports = {
       tags: [
         'Attachment',
         'Bookmarks',
+        'BookmarkFolders',
         'Page',
         'Pages',
         'Revisions',

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.1.3-RC.0",
+  "version": "7.1.5-RC.0",
   "license": "MIT",
   "private": "true",
   "scripts": {

+ 12 - 0
apps/app/src/client/components/Sidebar/Sidebar.module.scss

@@ -44,6 +44,9 @@
         min-height: 50vh;
         max-height: calc(100vh - var.$grw-sidebar-nav-width * 2);
         border-radius: 0 4px 4px 0 ;
+        .simple-scrollbar {
+          max-height: inherit;
+        }
       }
     }
   }
@@ -74,6 +77,15 @@
 }
 
 
+.grw-sidebar :global {
+
+  // overwrite simplebar-react css
+  .simplebar-scrollbar::before {
+    background-color:var(--bs-gray-500);
+  }
+
+}
+
 @include bs.color-mode(light) {
   .grw-sidebar :global {
     --bs-border-color: var(--grw-highlight-200);

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

@@ -6,6 +6,7 @@ import {
 
 import withLoadingProps from 'next-dynamic-loading-props';
 import dynamic from 'next/dynamic';
+import SimpleBar from 'simplebar-react';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 import { SidebarMode } from '~/interfaces/ui';
@@ -27,6 +28,7 @@ import type { ResizableAreaProps } from './ResizableArea/props';
 import { SidebarHead } from './SidebarHead';
 import { SidebarNav, type SidebarNavProps } from './SidebarNav';
 
+import 'simplebar-react/dist/simplebar.min.css';
 import styles from './Sidebar.module.scss';
 
 
@@ -173,11 +175,16 @@ const CollapsibleContainer = memo((props: CollapsibleContainerProps): JSX.Elemen
     <div className={`flex-expand-horiz ${className}`} onMouseLeave={mouseLeaveHandler}>
       <Nav onPrimaryItemHover={primaryItemHoverHandler} />
       <div
-        ref={sidebarScrollerRef}
-        className={`sidebar-contents-container flex-grow-1 overflow-y-auto overflow-x-hidden ${closedClass} ${openedClass}`}
-        style={{ width: collapsibleContentsWidth }}
+        className={`sidebar-contents-container flex-grow-1 overflow-hidden ${closedClass} ${openedClass}`}
       >
-        {children}
+        <SimpleBar
+          scrollableNodeProps={{ ref: sidebarScrollerRef }}
+          className="simple-scrollbar h-100"
+          style={{ width: collapsibleContentsWidth }}
+          autoHide
+        >
+          {children}
+        </SimpleBar>
       </div>
     </div>
   );

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

@@ -30,10 +30,52 @@ const {
  *
  *  components:
  *    schemas:
+ *      AttachmentPaginateResult:
+ *        description: AttachmentPaginateResult
+ *        type: object
+ *        properties:
+ *          docs:
+ *            type: array
+ *            items:
+ *              $ref: '#/components/schemas/Attachment'
+ *          totalDocs:
+ *            type: number
+ *            example: 1
+ *          limit:
+ *            type: number
+ *            example: 20
+ *          totalPages:
+ *            type: number
+ *            example: 1
+ *          page:
+ *            type: number
+ *            example: 1
+ *          offset:
+ *            type: number
+ *            example: 0
+ *          prevPage:
+ *            type: number
+ *            example: null
+ *          nextPage:
+ *            type: number
+ *            example: null
+ *          hasNextPage:
+ *            type: boolean
+ *            example: false
+ *          hasPrevPage:
+ *            type: boolean
+ *            example: false
+ *          pagingCounter:
+ *            type: number
+ *            example: 1
  *      Attachment:
  *        description: Attachment
  *        type: object
  *        properties:
+ *          id:
+ *            type: string
+ *            description: attachment ID
+ *            example: 5e0734e072560e001761fa67
  *          _id:
  *            type: string
  *            description: attachment ID
@@ -42,6 +84,10 @@ const {
  *            type: number
  *            description: attachment version
  *            example: 0
+ *          attachmentType:
+ *            type: string
+ *            description: attachment type
+ *            example: WIKI_PAGE
  *          fileFormat:
  *            type: string
  *            description: file format in MIME
@@ -55,6 +101,7 @@ const {
  *            description: original file name
  *            example: file.txt
  *          creator:
+ *            type: object
  *            $ref: '#/components/schemas/User'
  *          page:
  *            type: string
@@ -64,14 +111,14 @@ const {
  *            type: string
  *            description: date created at
  *            example: 2010-01-01T00:00:00.000Z
+ *          temporaryUrlExpiredAt:
+ *            type: string
+ *            description: temporary URL expired at
+ *            example: 2024-11-27T00:59:59.962Z
  *          fileSize:
  *            type: number
  *            description: file size
  *            example: 3494332
- *          url:
- *            type: string
- *            description: attachment URL
- *            example: http://localhost/files/5e0734e072560e001761fa67
  *          filePathProxied:
  *            type: string
  *            description: file path proxied
@@ -80,8 +127,11 @@ const {
  *            type: string
  *            description: download path proxied
  *            example: "/download/5e0734e072560e001761fa67"
+ *          temporaryUrlCached:
+ *            type: string
+ *            description: temporary URL cached
+ *            example: "https://example.com/attachment/5e0734e072560e001761fa67"
  */
-
 module.exports = (crowi) => {
   const loginRequired = require('../../middlewares/login-required')(crowi, true);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
@@ -117,16 +167,35 @@ module.exports = (crowi) => {
    *      get:
    *        tags: [Attachment]
    *        description: Get attachment list
-   *        responses:
-   *          200:
-   *            description: Return attachment list
    *        parameters:
-   *          - name: page_id
+   *          - name: pageId
    *            in: query
    *            required: true
    *            description: page id
    *            schema:
    *              type: string
+   *          - name: pageNumber
+   *            in: query
+   *            required: false
+   *            description: page number
+   *            schema:
+   *              type: number
+   *              example: 1
+   *          - name: limit
+   *            in: query
+   *            required: false
+   *            description: limit
+   *            schema:
+   *              type: number
+   *              example: 10
+   *        responses:
+   *          200:
+   *            description: Return attachment list
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  $ref: '#/components/schemas/AttachmentPaginateResult'
    */
   router.get('/list', accessTokenParser, loginRequired, validator.retrieveAttachments, apiV3FormValidator, async(req, res) => {
 
@@ -202,11 +271,6 @@ module.exports = (crowi) => {
    *          500:
    *            $ref: '#/components/responses/500'
    */
-  /**
-   * @api {get} /attachment/limit get available capacity of uploaded file with GridFS
-   * @apiName AddAttachment
-   * @apiGroup Attachment
-   */
   router.get('/limit', accessTokenParser, loginRequiredStrictly, validator.retrieveFileLimit, apiV3FormValidator, async(req, res) => {
     const { fileUploadService } = crowi;
     const fileSize = Number(req.query.fileSize);
@@ -234,10 +298,7 @@ module.exports = (crowi) => {
    *              schema:
    *                properties:
    *                  page_id:
-   *                    nullable: true
-   *                    type: string
-   *                  path:
-   *                    nullable: true
+   *                    nullable: false
    *                    type: string
    *                  file:
    *                    type: string
@@ -250,10 +311,7 @@ module.exports = (crowi) => {
    *              schema:
    *                properties:
    *                  page_id:
-   *                    nullable: true
-   *                    type: string
-   *                  path:
-   *                    nullable: true
+   *                    nullable: false
    *                    type: string
    *                  file:
    *                    type: string
@@ -273,26 +331,13 @@ module.exports = (crowi) => {
    *                      $ref: '#/components/schemas/Page'
    *                    attachment:
    *                      $ref: '#/components/schemas/Attachment'
-   *                    url:
-   *                      $ref: '#/components/schemas/Attachment/properties/url'
-   *                    pageCreated:
-   *                      type: boolean
-   *                      description: whether the page was created
-   *                      example: false
+   *                    revision:
+   *                      type: string
    *          403:
    *            $ref: '#/components/responses/403'
    *          500:
    *            $ref: '#/components/responses/500'
    */
-  /**
-   * @api {post} /attachment Add attachment to the page
-   * @apiName AddAttachment
-   * @apiGroup Attachment
-   *
-   * @apiParam {String} page_id
-   * @apiParam {String} path
-   * @apiParam {File} file
-   */
   router.post('/', uploads.single('file'), autoReap, accessTokenParser, loginRequiredStrictly, excludeReadOnlyUser,
     validator.retrieveAddAttachment, apiV3FormValidator, addActivity,
     async(req, res) => {
@@ -342,6 +387,13 @@ module.exports = (crowi) => {
    *        responses:
    *          200:
    *            description: Return attachment
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    attachment:
+   *                      $ref: '#/components/schemas/Attachment'
    *        parameters:
    *          - name: id
    *            in: path

+ 286 - 3
apps/app/src/server/routes/apiv3/bookmark-folder.ts

@@ -16,6 +16,85 @@ const express = require('express');
 
 const router = express.Router();
 
+/**
+ * @swagger
+ *
+ *  components:
+ *    schemas:
+ *      BookmarkFolder:
+ *        description: Bookmark Folder
+ *        type: object
+ *        properties:
+ *          _id:
+ *            type: string
+ *            description: Bookmark Folder ID
+ *          __v:
+ *            type: number
+ *            description: Version of the bookmark folder
+ *          name:
+ *            type: string
+ *            description: Name of the bookmark folder
+ *          owner:
+ *            type: string
+ *            description: Owner user ID of the bookmark folder
+ *          bookmarks:
+ *            type: array
+ *            items:
+ *              type: object
+ *              properties:
+ *                _id:
+ *                  type: string
+ *                  description: Bookmark ID
+ *                user:
+ *                  type: string
+ *                  description: User ID of the bookmarker
+ *                createdAt:
+ *                  type: string
+ *                  description: Date and time when the bookmark was created
+ *                __v:
+ *                  type: number
+ *                  description: Version of the bookmark
+ *                page:
+ *                  description: Pages that are bookmarked in the folder
+ *                  allOf:
+ *                    - $ref: '#/components/schemas/Page'
+ *                    - type: object
+ *                      properties:
+ *                        id:
+ *                          type: string
+ *                          description: Page ID
+ *                          example: "671b5cd38d45e62b52217ff8"
+ *                        parent:
+ *                          type: string
+ *                          description: Parent page ID
+ *                          example: 669a5aa48d45e62b521d00da
+ *                        descendantCount:
+ *                          type: number
+ *                          description: Number of descendants
+ *                          example: 0
+ *                        isEmpty:
+ *                          type: boolean
+ *                          description: Whether the page is empty
+ *                          example: false
+ *                        grantedGroups:
+ *                          type: array
+ *                          description: List of granted groups
+ *                          items:
+ *                            type: string
+ *                        creator:
+ *                          type: string
+ *                          description: Creator user ID
+ *                          example: "669a5aa48d45e62b521d00e4"
+ *                        latestRevisionBodyLength:
+ *                          type: number
+ *                          description: Length of the latest revision body
+ *                          example: 241
+ *          childFolder:
+ *            type: array
+ *            items:
+ *              type: object
+ *              $ref: '#/components/schemas/BookmarkFolder'
+ */
 const validator = {
   bookmarkFolder: [
     body('name').isString().withMessage('name must be a string'),
@@ -42,7 +121,40 @@ const validator = {
 module.exports = (crowi) => {
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
 
-  // Create new bookmark folder
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder:
+   *      post:
+   *        tags: [BookmarkFolders]
+   *        operationId: createBookmarkFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Create bookmark folder
+   *        description: Create a new bookmark folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  name:
+   *                    type: string
+   *                    description: Name of the bookmark folder
+   *                    nullable: false
+   *                  parent:
+   *                    type: string
+   *                    description: Parent folder ID
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.post('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, apiV3FormValidator, async(req, res) => {
     const owner = req.user?._id;
     const { name, parent } = req.body;
@@ -64,7 +176,37 @@ module.exports = (crowi) => {
     }
   });
 
-  // List bookmark folders and child
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/list/{userId}:
+   *      get:
+   *        tags: [BookmarkFolders]
+   *        operationId: listBookmarkFolders
+   *        security:
+   *          - api_key: []
+   *        summary: List bookmark folders of a user
+   *        description: List bookmark folders of a user
+   *        parameters:
+   *         - name: userId
+   *           in: path
+   *           required: true
+   *           description: User ID
+   *           schema:
+   *             type: string
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolderItems:
+   *                      type: array
+   *                      items:
+   *                        type: object
+   *                        $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.get('/list/:userId', accessTokenParser, loginRequiredStrictly, async(req, res) => {
     const { userId } = req.params;
 
@@ -123,7 +265,36 @@ module.exports = (crowi) => {
     }
   });
 
-  // Delete bookmark folder and children
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/{id}:
+   *      delete:
+   *        tags: [BookmarkFolders]
+   *        operationId: deleteBookmarkFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Delete bookmark folder
+   *        description: Delete a bookmark folder and its children
+   *        parameters:
+   *         - name: id
+   *           in: path
+   *           required: true
+   *           description: Bookmark Folder ID
+   *           schema:
+   *             type: string
+   *        responses:
+   *          200:
+   *            description: Deleted successfully
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    deletedCount:
+   *                      type: number
+   *                      description: Number of deleted folders
+   *                      example: 1
+   */
   router.delete('/:id', accessTokenParser, loginRequiredStrictly, async(req, res) => {
     const { id } = req.params;
     try {
@@ -137,6 +308,49 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder:
+   *      put:
+   *        tags: [BookmarkFolders]
+   *        operationId: updateBookmarkFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Update bookmark folder
+   *        description: Update a bookmark folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  bookmarkFolderId:
+   *                    type: string
+   *                    description: Bookmark Folder ID
+   *                  name:
+   *                    type: string
+   *                    description: Name of the bookmark folder
+   *                    nullable: false
+   *                  parent:
+   *                    type: string
+   *                    description: Parent folder ID
+   *                  childFolder:
+   *                    type: array
+   *                    description: Child folders
+   *                    items:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.put('/', accessTokenParser, loginRequiredStrictly, validator.bookmarkFolder, async(req, res) => {
     const {
       bookmarkFolderId, name, parent, childFolder,
@@ -151,6 +365,41 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/add-boookmark-to-folder:
+   *      post:
+   *        tags: [BookmarkFolders]
+   *        operationId: addBookmarkToFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Update bookmark folder
+   *        description: Update a bookmark folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  pageId:
+   *                    type: string
+   *                    description: Page ID
+   *                    nullable: false
+   *                  folderId:
+   *                    type: string
+   *                    description: Folder ID
+   *                    nullable: true
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.post('/add-boookmark-to-folder', accessTokenParser, loginRequiredStrictly, validator.bookmarkPage, apiV3FormValidator, async(req, res) => {
     const userId = req.user?._id;
     const { pageId, folderId } = req.body;
@@ -166,6 +415,40 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /bookmark-folder/update-bookmark:
+   *      put:
+   *        tags: [BookmarkFolders]
+   *        operationId: updateBookmarkInFolder
+   *        security:
+   *          - api_key: []
+   *        summary: Update bookmark in folder
+   *        description: Update a bookmark in a folder
+   *        requestBody:
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  pageId:
+   *                    type: string
+   *                    description: Page ID
+   *                    nullable: false
+   *                  status:
+   *                    type: string
+   *                    description: Bookmark status
+   *        responses:
+   *          200:
+   *            description: Resources are available
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    bookmarkFolder:
+   *                      type: object
+   *                      $ref: '#/components/schemas/BookmarkFolder'
+   */
   router.put('/update-bookmark', accessTokenParser, loginRequiredStrictly, validator.bookmark, async(req, res) => {
     const { pageId, status } = req.body;
     const userId = req.user?._id;

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

@@ -1,5 +1,6 @@
 import path from 'path';
-import { pipeline, type Readable } from 'stream';
+import { type Readable } from 'stream';
+import { pipeline } from 'stream/promises';
 
 import type { IPage } from '@growi/core';
 import {
@@ -761,7 +762,7 @@ module.exports = (crowi) => {
     };
     await crowi.activityService.createActivity(parameters);
 
-    return pipeline(stream, res);
+    await pipeline(stream, res);
   });
 
   /**

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

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

+ 1 - 1
package.json

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