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

Merge remote-tracking branch 'origin/master' into support/new-config-manager

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

+ 14 - 1
CHANGELOG.md

@@ -1,9 +1,22 @@
 # Changelog
 # Changelog
 
 
-## [Unreleased](https://github.com/weseek/growi/compare/v7.1.4...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.1.5...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.1.5](https://github.com/weseek/growi/compare/v7.1.4...v7.1.5) - 2024-12-13
+
+### 🚀 Improvement
+
+* imprv: Slim down the sidebar scrollbar (#9430) @reiji-h
+
+### 🐛 Bug Fixes
+
+* fix: Usage for stream pipeline of callback version (#9455) @reiji-h
+* fix: TypeError occurs during export (#9481) @miya
+* fix: Put `/` before the page name (#9471) @Ryosei-Fukushima
+* fix: Cannot comment when comments from rom user are allowed (#9472) @miya
+
 ## [v7.1.4](https://github.com/weseek/growi/compare/v7.1.3...v7.1.4) - 2024-11-26
 ## [v7.1.4](https://github.com/weseek/growi/compare/v7.1.3...v7.1.4) - 2024-11-26
 
 
 ### 🐛 Bug Fixes
 ### 🐛 Bug Fixes

+ 1 - 1
apps/app/package.json

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

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

@@ -157,7 +157,7 @@
   "duplicated_path": "Duplicated path",
   "duplicated_path": "Duplicated path",
   "Link sharing is disabled": "Link sharing is disabled",
   "Link sharing is disabled": "Link sharing is disabled",
   "successfully_saved_the_page": "Successfully saved the page",
   "successfully_saved_the_page": "Successfully saved the page",
-  "you_can_not_create_page_with_this_name": "You can not create page with this name",
+  "you_can_not_create_page_with_this_name_or_hierarchy": "You can not create page with this name or page hierarchy",
   "not_allowed_to_see_this_page": "You cannot see this page",
   "not_allowed_to_see_this_page": "You cannot see this page",
   "Confirm": "Confirm",
   "Confirm": "Confirm",
   "Successfully requested": "Successfully requested.",
   "Successfully requested": "Successfully requested.",

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

@@ -157,7 +157,7 @@
   "duplicated_path": "Chemin dupliqué",
   "duplicated_path": "Chemin dupliqué",
   "Link sharing is disabled": "Le partage est désactivé",
   "Link sharing is disabled": "Le partage est désactivé",
   "successfully_saved_the_page": "Page sauvegardée",
   "successfully_saved_the_page": "Page sauvegardée",
-  "you_can_not_create_page_with_this_name": "Vous ne pouvez pas créer cette page",
+  "you_can_not_create_page_with_this_name_or_hierarchy": "Vous ne pouvez pas créer de page avec ce nom ou cette hiérarchie de pages",
   "not_allowed_to_see_this_page": "Vous ne pouvez pas voir cette page",
   "not_allowed_to_see_this_page": "Vous ne pouvez pas voir cette page",
   "Confirm": "Confirmer",
   "Confirm": "Confirmer",
   "Successfully requested": "Demande envoyée.",
   "Successfully requested": "Demande envoyée.",

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

@@ -158,7 +158,7 @@
   "duplicated_path": "重複したパス",
   "duplicated_path": "重複したパス",
   "Link sharing is disabled": "リンクのシェアは無効化されています",
   "Link sharing is disabled": "リンクのシェアは無効化されています",
   "successfully_saved_the_page": "ページが正常に保存されました",
   "successfully_saved_the_page": "ページが正常に保存されました",
-  "you_can_not_create_page_with_this_name": "この名前でページを作成することはできません",
+  "you_can_not_create_page_with_this_name_or_hierarchy": "この名前、または階層でページを作成することはできません",
   "not_allowed_to_see_this_page": "このページは閲覧できません",
   "not_allowed_to_see_this_page": "このページは閲覧できません",
   "Confirm": "確認",
   "Confirm": "確認",
   "Successfully requested": "正常に処理を受け付けました",
   "Successfully requested": "正常に処理を受け付けました",

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

@@ -163,7 +163,7 @@
   "duplicated_path": "Duplicated path",
   "duplicated_path": "Duplicated path",
   "Link sharing is disabled": "你不允许分享该链接",
   "Link sharing is disabled": "你不允许分享该链接",
   "successfully_saved_the_page": "成功地保存了该页面",
   "successfully_saved_the_page": "成功地保存了该页面",
-  "you_can_not_create_page_with_this_name": "您无法使用此名称创建页面",
+  "you_can_not_create_page_with_this_name_or_hierarchy": "您無法使用此名稱或頁面層級建立頁面",
   "not_allowed_to_see_this_page": "你不能看到这个页面",
   "not_allowed_to_see_this_page": "你不能看到这个页面",
   "Confirm": "确定",
   "Confirm": "确定",
   "Successfully requested": "进程成功接受",
   "Successfully requested": "进程成功接受",

+ 1 - 2
apps/app/src/client/components/Common/CopyDropdown/CopyDropdown.jsx

@@ -195,8 +195,7 @@ export const CopyDropdown = (props) => {
                 <DropdownItemContents
                 <DropdownItemContents
                   title={t('copy_to_clipboard.Page path and permanent link')}
                   title={t('copy_to_clipboard.Page path and permanent link')}
                   contents={<>{pagePathWithParams}<br />{permalink}</>}
                   contents={<>{pagePathWithParams}<br />{permalink}</>}
-                  className="text-truncate"
-                  style={{ direction: 'rtl' }}
+                  className="text-truncate d-block"
                 />
                 />
               </DropdownItem>
               </DropdownItem>
             </CopyToClipboard>
             </CopyToClipboard>

+ 1 - 1
apps/app/src/client/components/TreeItem/NewPageInput/use-new-page-input.tsx

@@ -99,7 +99,7 @@ export const useNewPageInput = (): UseNewPageInput => {
       const isCreatable = pagePathUtils.isCreatablePage(newPagePath);
       const isCreatable = pagePathUtils.isCreatablePage(newPagePath);
 
 
       if (!isCreatable) {
       if (!isCreatable) {
-        toastWarning(t('you_can_not_create_page_with_this_name'));
+        toastWarning(t('you_can_not_create_page_with_this_name_or_hierarchy'));
         return;
         return;
       }
       }
 
 

+ 2 - 2
apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss

@@ -4,7 +4,7 @@
 
 
 .grw-page-path-nav-layout :global {
 .grw-page-path-nav-layout :global {
   .grw-page-path-nav-copydropdown {
   .grw-page-path-nav-copydropdown {
-    display: none;
+    visibility: hidden;
     @include bs.media-breakpoint-down(md) {
     @include bs.media-breakpoint-down(md) {
       display: block;
       display: block;
     }
     }
@@ -15,7 +15,7 @@
   &:global {
   &:global {
     &:hover {
     &:hover {
       .grw-page-path-nav-copydropdown {
       .grw-page-path-nav-copydropdown {
-        display: block;
+        visibility: visible;
       }
       }
     }
     }
   }
   }

+ 1 - 0
apps/app/src/pages/[[...path]].page.tsx

@@ -244,6 +244,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useIsUploadEnabled(props.isUploadEnabled);
   useIsUploadEnabled(props.isUploadEnabled);
 
 
   useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled);
   useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled);
+  useIsRomUserAllowedToComment(props.isRomUserAllowedToComment);
 
 
   useIsAiEnabled(props.aiEnabled);
   useIsAiEnabled(props.aiEnabled);
 
 

+ 352 - 19
apps/app/src/server/routes/apiv3/customize-setting.js

@@ -37,7 +37,7 @@ const router = express.Router();
  *        description: CustomizeTheme
  *        description: CustomizeTheme
  *        type: object
  *        type: object
  *        properties:
  *        properties:
- *          themeType:
+ *          theme:
  *            type: string
  *            type: string
  *      CustomizeFunction:
  *      CustomizeFunction:
  *        description: CustomizeFunction
  *        description: CustomizeFunction
@@ -61,6 +61,14 @@ const router = express.Router();
  *        description: CustomizeHighlight
  *        description: CustomizeHighlight
  *        type: object
  *        type: object
  *        properties:
  *        properties:
+ *          highlightJsStyle:
+ *            type: string
+ *          highlightJsStyleBorder:
+ *            type: boolean
+ *      CustomizeHighlightResponse:
+ *        description: CustomizeHighlight Response
+ *        type: object
+ *        properties:
  *          styleName:
  *          styleName:
  *            type: string
  *            type: string
  *          styleBorder:
  *          styleBorder:
@@ -89,6 +97,99 @@ const router = express.Router();
  *        properties:
  *        properties:
  *          customizeScript:
  *          customizeScript:
  *            type: string
  *            type: string
+ *      CustomizeSetting:
+ *        description: Customize Setting
+ *        type: object
+ *        properties:
+ *          isEnabledTimeline:
+ *            type: boolean
+ *          isEnabledAttachTitleHeader:
+ *            type: boolean
+ *          pageLimitationS:
+ *            type: number
+ *          pageLimitationM:
+ *            type: number
+ *          pageLimitationL:
+ *            type: number
+ *          pageLimitationXL:
+ *            type: number
+ *          isEnabledStaleNotification:
+ *            type: boolean
+ *          isAllReplyShown:
+ *            type: boolean
+ *          isSearchScopeChildrenAsDefault:
+ *            type: boolean
+ *          isEnabledMarp:
+ *            type: boolean
+ *          styleName:
+ *            type: string
+ *          styleBorder:
+ *            type: string
+ *          customizeTitle:
+ *            type: string
+ *          customizeScript:
+ *            type: string
+ *          customizeCss:
+ *            type: string
+ *          customizeNoscript:
+ *            type: string
+ *      ThemesMetadata:
+ *        type: object
+ *        properties:
+ *          name:
+ *            type: string
+ *            description: The name of the plugin theme.
+ *          manifestKey:
+ *            type: string
+ *            description: Path to the theme manifest file.
+ *          schemeType:
+ *            type: string
+ *            description: The color scheme type (e.g., light or dark).
+ *          lightBg:
+ *            type: string
+ *            description: Light mode background color (hex).
+ *          darkBg:
+ *            type: string
+ *            description: Dark mode background color (hex).
+ *          lightSidebar:
+ *            type: string
+ *            description: Light mode sidebar color (hex).
+ *          darkSidebar:
+ *            type: string
+ *            description: Dark mode sidebar color (hex).
+ *          lightIcon:
+ *            type: string
+ *            description: Light mode icon color (hex).
+ *          darkIcon:
+ *            type: string
+ *            description: Dark mode icon color (hex).
+ *          createBtn:
+ *            type: string
+ *            description: Color of the create button (hex).
+ *      CustomizeSidebar:
+ *        description: Customize Sidebar
+ *        type: object
+ *        properties:
+ *          isSidebarCollapsedMode:
+ *            type: boolean
+ *            description: The flag whether sidebar is collapsed mode or not.
+ *          isSidebarClosedAtDockMode:
+ *            type: boolean
+ *            description: The flag whether sidebar is closed at dock mode or not.
+ *      CustomizePresentation:
+ *        description: Customize Presentation
+ *        type: object
+ *        properties:
+ *          isEnabledMarp:
+ *            type: boolean
+ *            description: The flag whether Marp is enabled or not.
+ *      CustomizeLogo:
+ *        description: Customize Logo
+ *        type: object
+ *        properties:
+ *          isDefaultLogo:
+ *            type: boolean
+ *            description: The flag whether the logo is default or not.
  */
  */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
   const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
@@ -154,6 +255,8 @@ module.exports = (crowi) => {
    *    /customize-setting:
    *    /customize-setting:
    *      get:
    *      get:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: getCustomizeSetting
    *        operationId: getCustomizeSetting
    *        summary: /customize-setting
    *        summary: /customize-setting
    *        description: Get customize parameters
    *        description: Get customize parameters
@@ -167,6 +270,7 @@ module.exports = (crowi) => {
    *                    customizeParams:
    *                    customizeParams:
    *                      type: object
    *                      type: object
    *                      description: customize params
    *                      description: customize params
+   *                      $ref: '#/components/schemas/CustomizeSetting'
    */
    */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
     const customizeParams = {
     const customizeParams = {
@@ -197,6 +301,8 @@ module.exports = (crowi) => {
    *    /customize-setting/layout:
    *    /customize-setting/layout:
    *      get:
    *      get:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: getLayoutCustomizeSetting
    *        operationId: getLayoutCustomizeSetting
    *        summary: /customize-setting/layout
    *        summary: /customize-setting/layout
    *        description: Get layout
    *        description: Get layout
@@ -209,7 +315,6 @@ module.exports = (crowi) => {
    *                  $ref: '#/components/schemas/CustomizeLayout'
    *                  $ref: '#/components/schemas/CustomizeLayout'
    */
    */
   router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/layout', loginRequiredStrictly, adminRequired, async(req, res) => {
-
     try {
     try {
       const isContainerFluid = await configManager.getConfig('customize:isContainerFluid');
       const isContainerFluid = await configManager.getConfig('customize:isContainerFluid');
       return res.apiv3({ isContainerFluid });
       return res.apiv3({ isContainerFluid });
@@ -242,7 +347,12 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeLayout'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      type: object
+   *                      description: customized params
+   *                      $ref: '#/components/schemas/CustomizeLayout'
    */
    */
   router.put('/layout', loginRequiredStrictly, adminRequired, addActivity, validator.layout, apiV3FormValidator, async(req, res) => {
   router.put('/layout', loginRequiredStrictly, adminRequired, addActivity, validator.layout, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -267,6 +377,34 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/theme:
+   *      get:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: getThemeCustomizeSetting
+   *        summary: /customize-setting/theme
+   *        description: Get theme
+   *        responses:
+   *          200:
+   *            description: Succeeded to get layout
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    currentTheme:
+   *                      type: string
+   *                      description: The current theme name.
+   *                    pluginThemesMetadatas:
+   *                      type: array
+   *                      description: Metadata for available plugin themes.
+   *                      items:
+   *                        $ref: '#/components/schemas/ThemesMetadata'
+   */
   router.get('/theme', loginRequiredStrictly, async(req, res) => {
   router.get('/theme', loginRequiredStrictly, async(req, res) => {
 
 
     try {
     try {
@@ -294,6 +432,8 @@ module.exports = (crowi) => {
    *    /customize-setting/theme:
    *    /customize-setting/theme:
    *      put:
    *      put:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateThemeCustomizeSetting
    *        operationId: updateThemeCustomizeSetting
    *        summary: /customize-setting/theme
    *        summary: /customize-setting/theme
    *        description: Update theme
    *        description: Update theme
@@ -309,7 +449,10 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeTheme'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeTheme'
    */
    */
   router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
   router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -333,7 +476,25 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
-  // sidebar
+  /**
+   * @swagger
+   *
+   *    /customize-setting/sidebar:
+   *      get:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: getCustomeSettingSidebar
+   *        summary: /customize-setting/sidebar
+   *        description: Get sidebar
+   *        responses:
+   *          200:
+   *            description: Succeeded to get sidebar
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/CustomizeSidebar'
+   */
   router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/sidebar', loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     try {
     try {
@@ -348,6 +509,34 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/sidebar:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: updateCustomizeSettingSidebar
+   *        summary: /customize-setting/sidebar
+   *        description: Update sidebar
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/CustomizeSidebar'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update sidebar
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeSidebar'
+   */
   router.put('/sidebar', loginRequiredStrictly, adminRequired, validator.sidebar, apiV3FormValidator, addActivity, async(req, res) => {
   router.put('/sidebar', loginRequiredStrictly, adminRequired, validator.sidebar, apiV3FormValidator, addActivity, async(req, res) => {
     const requestParams = {
     const requestParams = {
       'customize:isSidebarCollapsedMode': req.body.isSidebarCollapsedMode,
       'customize:isSidebarCollapsedMode': req.body.isSidebarCollapsedMode,
@@ -378,6 +567,8 @@ module.exports = (crowi) => {
    *    /customize-setting/function:
    *    /customize-setting/function:
    *      put:
    *      put:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *         - cookieAuth: []
    *        operationId: updateFunctionCustomizeSetting
    *        operationId: updateFunctionCustomizeSetting
    *        summary: /customize-setting/function
    *        summary: /customize-setting/function
    *        description: Update function
    *        description: Update function
@@ -393,7 +584,10 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeFunction'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeFunction'
    */
    */
   router.put('/function', loginRequiredStrictly, adminRequired, addActivity, validator.function, apiV3FormValidator, async(req, res) => {
   router.put('/function', loginRequiredStrictly, adminRequired, addActivity, validator.function, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -433,6 +627,34 @@ module.exports = (crowi) => {
   });
   });
 
 
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/presentation:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *         - cookieAuth: []
+   *        operationId: updatePresentationCustomizeSetting
+   *        summary: /customize-setting/presentation
+   *        description: Update presentation
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/CustomizePresentation'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update presentation
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizePresentation'
+   */
   router.put('/presentation', loginRequiredStrictly, adminRequired, addActivity, validator.CustomizePresentation, apiV3FormValidator, async(req, res) => {
   router.put('/presentation', loginRequiredStrictly, adminRequired, addActivity, validator.CustomizePresentation, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
       'customize:isEnabledMarp': req.body.isEnabledMarp,
       'customize:isEnabledMarp': req.body.isEnabledMarp,
@@ -460,6 +682,8 @@ module.exports = (crowi) => {
    *    /customize-setting/highlight:
    *    /customize-setting/highlight:
    *      put:
    *      put:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateHighlightCustomizeSetting
    *        operationId: updateHighlightCustomizeSetting
    *        summary: /customize-setting/highlight
    *        summary: /customize-setting/highlight
    *        description: Update highlight
    *        description: Update highlight
@@ -475,7 +699,10 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeHighlight'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeHighlightResponse'
    */
    */
   router.put('/highlight', loginRequiredStrictly, adminRequired, addActivity, validator.highlight, apiV3FormValidator, async(req, res) => {
   router.put('/highlight', loginRequiredStrictly, adminRequired, addActivity, validator.highlight, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -506,9 +733,11 @@ module.exports = (crowi) => {
    *    /customize-setting/customizeTitle:
    *    /customize-setting/customizeTitle:
    *      put:
    *      put:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeTitleCustomizeSetting
    *        operationId: updateCustomizeTitleCustomizeSetting
    *        summary: /customize-setting/customizeTitle
    *        summary: /customize-setting/customizeTitle
-   *        description: Update customizeTitle
+   *        description: Update title
    *        requestBody:
    *        requestBody:
    *          required: true
    *          required: true
    *          content:
    *          content:
@@ -521,7 +750,10 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeTitle'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeTitle'
    */
    */
   router.put('/customize-title', loginRequiredStrictly, adminRequired, addActivity, validator.customizeTitle, apiV3FormValidator, async(req, res) => {
   router.put('/customize-title', loginRequiredStrictly, adminRequired, addActivity, validator.customizeTitle, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -553,9 +785,11 @@ module.exports = (crowi) => {
    *    /customize-setting/customize-noscript:
    *    /customize-setting/customize-noscript:
    *      put:
    *      put:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeNoscriptCustomizeSetting
    *        operationId: updateCustomizeNoscriptCustomizeSetting
    *        summary: /customize-setting/customize-noscript
    *        summary: /customize-setting/customize-noscript
-   *        description: Update customizeNoscript
+   *        description: Update noscript
    *        requestBody:
    *        requestBody:
    *          required: true
    *          required: true
    *          content:
    *          content:
@@ -568,7 +802,10 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeNoscript'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeNoscript'
    */
    */
   router.put('/customize-noscript', loginRequiredStrictly, adminRequired, addActivity, validator.customizeNoscript, apiV3FormValidator, async(req, res) => {
   router.put('/customize-noscript', loginRequiredStrictly, adminRequired, addActivity, validator.customizeNoscript, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -593,12 +830,14 @@ module.exports = (crowi) => {
   /**
   /**
    * @swagger
    * @swagger
    *
    *
-   *    /customize-setting/customizeCss:
+   *    /customize-setting/customize-css:
    *      put:
    *      put:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeCssCustomizeSetting
    *        operationId: updateCustomizeCssCustomizeSetting
-   *        summary: /customize-setting/customizeCss
-   *        description: Update customizeCss
+   *        summary: /customize-setting/customize-css
+   *        description: Update customize css
    *        requestBody:
    *        requestBody:
    *          required: true
    *          required: true
    *          content:
    *          content:
@@ -611,7 +850,10 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeCss'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeCss'
    */
    */
   router.put('/customize-css', loginRequiredStrictly, adminRequired, addActivity, validator.customizeCss, apiV3FormValidator, async(req, res) => {
   router.put('/customize-css', loginRequiredStrictly, adminRequired, addActivity, validator.customizeCss, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -639,12 +881,14 @@ module.exports = (crowi) => {
   /**
   /**
    * @swagger
    * @swagger
    *
    *
-   *    /customize-setting/customizeScript:
+   *    /customize-setting/customize-script:
    *      put:
    *      put:
    *        tags: [CustomizeSetting]
    *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
    *        operationId: updateCustomizeScriptCustomizeSetting
    *        operationId: updateCustomizeScriptCustomizeSetting
-   *        summary: /customize-setting/customizeScript
-   *        description: Update customizeScript
+   *        summary: /customize-setting/customize-script
+   *        description: Update customize script
    *        requestBody:
    *        requestBody:
    *          required: true
    *          required: true
    *          content:
    *          content:
@@ -657,7 +901,10 @@ module.exports = (crowi) => {
    *            content:
    *            content:
    *              application/json:
    *              application/json:
    *                schema:
    *                schema:
-   *                  $ref: '#/components/schemas/CustomizeScript'
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeScript'
    */
    */
   router.put('/customize-script', loginRequiredStrictly, adminRequired, addActivity, validator.customizeScript, apiV3FormValidator, async(req, res) => {
   router.put('/customize-script', loginRequiredStrictly, adminRequired, addActivity, validator.customizeScript, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -679,6 +926,34 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/customize-logo:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: updateCustomizeLogoCustomizeSetting
+   *        summary: /customize-setting/customize-logo
+   *        description: Update customize logo
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/CustomizeLogo'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update customize logo
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    customizedParams:
+   *                      $ref: '#/components/schemas/CustomizeLogo'
+   */
   router.put('/customize-logo', loginRequiredStrictly, adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
   router.put('/customize-logo', loginRequiredStrictly, adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
 
 
     const {
     const {
@@ -702,6 +977,45 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/upload-brand-logo:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: uploadBrandLogoCustomizeSetting
+   *        summary: /customize-setting/upload-brand-logo
+   *        description: Upload brand logo
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            multipart/form-data:
+   *              schema:
+   *               type: object
+   *               properties:
+   *                 file:
+   *                   format: binary
+   *        responses:
+   *          200:
+   *            description: Succeeded to upload brand logo
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  type: object
+   *                  properties:
+   *                    attachment:
+   *                      allOf:
+   *                        - $ref: '#/components/schemas/Attachment'
+   *                        - type: object
+   *                          properties:
+   *                            creator:
+   *                              type: string
+   *                            page: {}
+   *                            temporaryUrlExpiredAt: {}
+   *                            temporaryUrlCached: {}
+   */
   router.post('/upload-brand-logo', uploads.single('file'), loginRequiredStrictly,
   router.post('/upload-brand-logo', uploads.single('file'), loginRequiredStrictly,
     adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
     adminRequired, validator.logo, apiV3FormValidator, async(req, res) => {
 
 
@@ -739,6 +1053,25 @@ module.exports = (crowi) => {
       return res.apiv3({ attachment });
       return res.apiv3({ attachment });
     });
     });
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/delete-brand-logo:
+   *      delete:
+   *        tags: [CustomizeSetting]
+   *        security:
+   *          - cookieAuth: []
+   *        operationId: deleteBrandLogoCustomizeSetting
+   *        summary: /customize-setting/delete-brand-logo
+   *        description: Delete brand logo
+   *        responses:
+   *          200:
+   *            description: Succeeded to delete brand logo
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  additionalProperties: false
+   */
   router.delete('/delete-brand-logo', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.delete('/delete-brand-logo', loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });
     const attachments = await Attachment.find({ attachmentType: AttachmentType.BRAND_LOGO });

+ 107 - 10
apps/app/src/server/routes/apiv3/export.js

@@ -20,22 +20,100 @@ const router = express.Router();
  *  components:
  *  components:
  *    schemas:
  *    schemas:
  *      ExportStatus:
  *      ExportStatus:
- *        description: ExportStatus
  *        type: object
  *        type: object
  *        properties:
  *        properties:
  *          zipFileStats:
  *          zipFileStats:
  *            type: array
  *            type: array
  *            items:
  *            items:
- *              type: object
- *              description: the property of each file
+ *              $ref: '#/components/schemas/ExportZipFileStat'
+ *          isExporting:
+ *            type: boolean
  *          progressList:
  *          progressList:
+ *            type: [array, null]
+ *            items:
+ *              type: string
+ *      ExportZipFileStat:
+ *        type: object
+ *        properties:
+ *          meta:
+ *            $ref: '#/components/schemas/ExportMeta'
+ *          fileName:
+ *            type: string
+ *          zipFilePath:
+ *            type: string
+ *          fileStat:
+ *            $ref: '#/components/schemas/ExportFileStat'
+ *          innerFileStats:
  *            type: array
  *            type: array
  *            items:
  *            items:
- *              type: object
- *              description: progress data for each exporting collections
- *          isExporting:
- *            type: boolean
- *            description: whether the current exporting job exists or not
+ *              $ref: '#/components/schemas/ExportInnerFileStat'
+ *      ExportMeta:
+ *        type: object
+ *        properties:
+ *          version:
+ *            type: string
+ *          url:
+ *            type: string
+ *          passwordSeed:
+ *            type: string
+ *          exportedAt:
+ *            type: string
+ *            format: date-time
+ *          envVars:
+ *            type: object
+ *            additionalProperties:
+ *              type: string
+ *      ExportFileStat:
+ *        type: object
+ *        properties:
+ *          dev:
+ *            type: integer
+ *          mode:
+ *            type: integer
+ *          nlink:
+ *            type: integer
+ *          uid:
+ *            type: integer
+ *          gid:
+ *            type: integer
+ *          rdev:
+ *            type: integer
+ *          blksize:
+ *            type: integer
+ *          ino:
+ *            type: integer
+ *          size:
+ *            type: integer
+ *          blocks:
+ *            type: integer
+ *          atime:
+ *            type: string
+ *            format: date-time
+ *          mtime:
+ *            type: string
+ *            format: date-time
+ *          ctime:
+ *            type: string
+ *            format: date-time
+ *          birthtime:
+ *            type: string
+ *            format: date-time
+ *      ExportInnerFileStat:
+ *        type: object
+ *        properties:
+ *          fileName:
+ *            type: string
+ *          collectionName:
+ *            type: string
+ *          meta:
+ *           progressList:
+ *             type: array
+ *             items:
+ *               type: object
+ *               description: progress data for each exporting collections
+ *           isExporting:
+ *             type: boolean
+ *             description: whether the current exporting job exists or not
  */
  */
 
 
 module.exports = (crowi) => {
 module.exports = (crowi) => {
@@ -84,6 +162,9 @@ module.exports = (crowi) => {
    *            application/json:
    *            application/json:
    *              schema:
    *              schema:
    *                properties:
    *                properties:
+   *                  ok:
+   *                    type: boolean
+   *                    description: whether the request is succeeded or not
    *                  status:
    *                  status:
    *                    $ref: '#/components/schemas/ExportStatus'
    *                    $ref: '#/components/schemas/ExportStatus'
    */
    */
@@ -106,6 +187,17 @@ module.exports = (crowi) => {
    *      operationId: createExport
    *      operationId: createExport
    *      summary: /export
    *      summary: /export
    *      description: generate zipped jsons for collections
    *      description: generate zipped jsons for collections
+   *      requestBody:
+   *        content:
+   *          application/json:
+   *            schema:
+   *              properties:
+   *                collections:
+   *                  type: array
+   *                  items:
+   *                    type: string
+   *                    description: the collections to export
+   *                    example: ["pages", "tags"]
    *      responses:
    *      responses:
    *        200:
    *        200:
    *          description: a zip file is generated
    *          description: a zip file is generated
@@ -113,8 +205,9 @@ module.exports = (crowi) => {
    *            application/json:
    *            application/json:
    *              schema:
    *              schema:
    *                properties:
    *                properties:
-   *                  status:
-   *                    $ref: '#/components/schemas/ExportStatus'
+   *                  ok:
+   *                    type: boolean
+   *                    description: whether the request is succeeded
    */
    */
   router.post('/', accessTokenParser, loginRequired, adminRequired, addActivity, async(req, res) => {
   router.post('/', accessTokenParser, loginRequired, adminRequired, addActivity, async(req, res) => {
     // TODO: add express validator
     // TODO: add express validator
@@ -161,6 +254,10 @@ module.exports = (crowi) => {
    *            application/json:
    *            application/json:
    *              schema:
    *              schema:
    *                type: object
    *                type: object
+   *                properties:
+   *                  ok:
+   *                    type: boolean
+   *                    description: whether the request is succeeded
    */
    */
   router.delete('/:fileName', accessTokenParser, loginRequired, adminRequired, validator.deleteFile, apiV3FormValidator, addActivity, async(req, res) => {
   router.delete('/:fileName', accessTokenParser, loginRequired, adminRequired, validator.deleteFile, apiV3FormValidator, addActivity, async(req, res) => {
     // TODO: add express validator
     // TODO: add express validator

+ 78 - 0
apps/app/src/server/routes/apiv3/forgot-password.js

@@ -18,6 +18,25 @@ const logger = loggerFactory('growi:routes:apiv3:forgotPassword'); // eslint-dis
 const express = require('express');
 const express = require('express');
 const { body } = require('express-validator');
 const { body } = require('express-validator');
 
 
+/**
+ * @swagger
+ *
+ * components:
+ *   schemas:
+ *     PasswordResetRequest:
+ *       type: object
+ *       properties:
+ *         email:
+ *           type: string
+ *           format: email
+ *     PasswordResetResponse:
+ *       type: object
+ *       properties:
+ *         message:
+ *           type: string
+ *         error:
+ *           type: string
+*/
 
 
 const router = express.Router();
 const router = express.Router();
 
 
@@ -69,6 +88,34 @@ module.exports = (crowi) => {
     });
     });
   }
   }
 
 
+  /**
+   * @swagger
+   *
+   *  /forgot-password:
+   *    post:
+   *      summary: Request password reset
+   *      tags: [Users]
+   *      security:
+   *        -
+   *      requestBody:
+   *        required: true
+   *        content:
+   *          application/json:
+   *            schema:
+   *              type: object
+   *              properties:
+   *                email:
+   *                  type: string
+   *                  format: email
+   *                  description: Email address of the user requesting password reset
+   *      responses:
+   *        '200':
+   *          description: Password reset request processed
+   *          content:
+   *            application/json:
+   *              schema:
+   *                type: object
+   */
   router.post('/', checkPassportStrategyMiddleware, validator.email, apiV3FormValidator, addActivity, async(req, res) => {
   router.post('/', checkPassportStrategyMiddleware, validator.email, apiV3FormValidator, addActivity, async(req, res) => {
     const { email } = req.body;
     const { email } = req.body;
     const locale = configManager.getConfig('app:globalLang');
     const locale = configManager.getConfig('app:globalLang');
@@ -103,6 +150,37 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  /**
+   * @swagger
+   *
+   *  /forgot-password:
+   *    put:
+   *      summary: Reset password
+   *      tags: [Users]
+   *      security:
+   *        -
+   *      requestBody:
+   *        required: true
+   *        content:
+   *          application/json:
+   *            schema:
+   *              type: object
+   *              properties:
+   *                newPassword:
+   *                  type: string
+   *                  format: password
+   *                  description: New password
+   *      responses:
+   *        '200':
+   *          description: Password reset successful
+   *          content:
+   *            application/json:
+   *              schema:
+   *                type: object
+   *                properties:
+   *                  userData:
+   *                    $ref: '#/components/schemas/User'
+   */
   // eslint-disable-next-line max-len
   // eslint-disable-next-line max-len
   router.put('/', checkPassportStrategyMiddleware, injectResetOrderByTokenMiddleware, validator.password, apiV3FormValidator, addActivity, async(req, res) => {
   router.put('/', checkPassportStrategyMiddleware, injectResetOrderByTokenMiddleware, validator.password, apiV3FormValidator, addActivity, async(req, res) => {
     const { passwordResetOrder } = req;
     const { passwordResetOrder } = req;

+ 3 - 4
apps/app/src/server/service/export.js

@@ -349,13 +349,12 @@ class ExportService {
 
 
     const output = fs.createWriteStream(zipFile);
     const output = fs.createWriteStream(zipFile);
 
 
-    // pipe archive data to the file
-    const stream = pipeline(archive, output);
-
     // finalize the archive (ie we are done appending files but streams have to finish yet)
     // finalize the archive (ie we are done appending files but streams have to finish yet)
     // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
     // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
     archive.finalize();
     archive.finalize();
-    await finished(stream);
+
+    // pipe archive data to the file
+    await pipeline(archive, output);
 
 
     logger.info(`zipped GROWI data into ${zipFile} (${archive.pointer()} bytes)`);
     logger.info(`zipped GROWI data into ${zipFile} (${archive.pointer()} bytes)`);
 
 

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

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

+ 1 - 1
package.json

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

+ 2 - 1
packages/core/src/utils/page-path-utils/index.ts

@@ -1,6 +1,6 @@
 import escapeStringRegexp from 'escape-string-regexp';
 import escapeStringRegexp from 'escape-string-regexp';
 
 
-import { IUser } from '~/interfaces';
+import type { IUser } from '~/interfaces';
 
 
 import { isValidObjectId } from '../objectid-utils';
 import { isValidObjectId } from '../objectid-utils';
 import { addTrailingSlash } from '../path-utils';
 import { addTrailingSlash } from '../path-utils';
@@ -117,6 +117,7 @@ const restrictedPatternsToCreate: Array<RegExp> = [
   /^\/(_search|_private-legacy-pages)(\/.*|$)/,
   /^\/(_search|_private-legacy-pages)(\/.*|$)/,
   /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments|tags|share|attachment)(\/.*|$)/,
   /^\/(installer|register|login|logout|admin|me|files|trash|paste|comments|tags|share|attachment)(\/.*|$)/,
   /^\/user(?:\/[^/]+)?$/, // https://regex101.com/r/9Eh2S1/1
   /^\/user(?:\/[^/]+)?$/, // https://regex101.com/r/9Eh2S1/1
+  /^(\/.+){130,}$/, // avoid deep layer path. see: https://regex101.com/r/L0kzOD/1
 ];
 ];
 export const isCreatablePage = (path: string): boolean => {
 export const isCreatablePage = (path: string): boolean => {
   return !restrictedPatternsToCreate.some(pattern => path.match(pattern));
   return !restrictedPatternsToCreate.some(pattern => path.match(pattern));