Explorar o código

Merge pull request #2668 from weseek/imprv/duplicate-child-page-when-duplicateRecursively-is-true

Imprv/duplicate child page when duplicate recursively is true
Yuki Takei %!s(int64=5) %!d(string=hai) anos
pai
achega
466f4dddce

+ 2 - 3
src/client/js/components/PageDuplicateModal.jsx

@@ -69,9 +69,8 @@ const PageDuplicateModal = (props) => {
     setErrs(null);
     setErrs(null);
 
 
     try {
     try {
-      const res = await appContainer.apiv3Post('/pages/duplicate', { pageId, pageNameInput });
-      const { page } = res.data;
-      window.location.href = encodeURI(`${page.path}?duplicated=${path}`);
+      await appContainer.apiv3Post('/pages/duplicate', { pageId, pageNameInput, isDuplicateRecursively });
+      window.location.href = encodeURI(`${pageNameInput}?duplicated=${path}`);
     }
     }
     catch (err) {
     catch (err) {
       setErrs(err);
       setErrs(err);

+ 59 - 26
src/server/routes/apiv3/pages.js

@@ -4,6 +4,7 @@ const logger = loggerFactory('growi:routes:apiv3:pages'); // eslint-disable-line
 
 
 const express = require('express');
 const express = require('express');
 const pathUtils = require('growi-commons').pathUtils;
 const pathUtils = require('growi-commons').pathUtils;
+const escapeStringRegexp = require('escape-string-regexp');
 
 
 const { body } = require('express-validator/check');
 const { body } = require('express-validator/check');
 const ErrorV3 = require('../../models/vo/error-apiv3');
 const ErrorV3 = require('../../models/vo/error-apiv3');
@@ -144,6 +145,7 @@ module.exports = (crowi) => {
     duplicatePage: [
     duplicatePage: [
       body('pageId').isMongoId().withMessage('pageId is required'),
       body('pageId').isMongoId().withMessage('pageId is required'),
       body('pageNameInput').trim().isLength({ min: 1 }).withMessage('pageNameInput is required'),
       body('pageNameInput').trim().isLength({ min: 1 }).withMessage('pageNameInput is required'),
+      body('isRecursively').if(value => value != null).isBoolean().withMessage('isRecursively must be boolean'),
     ],
     ],
   };
   };
 
 
@@ -438,6 +440,51 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  async function duplicatePage(page, newPagePath, user) {
+    // populate
+    await page.populate({ path: 'revision', model: 'Revision', select: 'body' }).execPopulate();
+
+    // create option
+    const options = { page };
+    options.grant = page.grant;
+    options.grantUserGroupId = page.grantedGroup;
+    options.grantedUsers = page.grantedUsers;
+
+    const createdPage = await createPageAction({
+      path: newPagePath, user, body: page.revision.body, options,
+    });
+
+    const originTags = await page.findRelatedTagsById();
+    const savedTags = await saveTagsAction({ page, createdPage, pageTags: originTags });
+
+    // global notification
+    if (globalNotificationService != null) {
+      try {
+        await globalNotificationService.fire(GlobalNotificationSetting.EVENT.PAGE_CREATE, createdPage, user);
+      }
+      catch (err) {
+        logger.error('Create grobal notification failed', err);
+      }
+    }
+
+    return { page: pageService.serializeToObj(createdPage), tags: savedTags };
+  }
+
+  async function duplicatePageRecursively(page, newPagePath, user) {
+    const newPagePathPrefix = newPagePath;
+    const pathRegExp = new RegExp(`^${escapeStringRegexp(page.path)}`, 'i');
+
+    const pages = await Page.findManageableListWithDescendants(page, user);
+
+    const promise = pages.map(async(page) => {
+      const newPagePath = page.path.replace(pathRegExp, newPagePathPrefix);
+      return duplicatePage(page, newPagePath, user);
+    });
+
+    return Promise.allSettled(promise);
+  }
+
+
   /**
   /**
    * @swagger
    * @swagger
    *
    *
@@ -456,6 +503,9 @@ module.exports = (crowi) => {
    *                    $ref: '#/components/schemas/Page/properties/_id'
    *                    $ref: '#/components/schemas/Page/properties/_id'
    *                  pageNameInput:
    *                  pageNameInput:
    *                    $ref: '#/components/schemas/Page/properties/path'
    *                    $ref: '#/components/schemas/Page/properties/path'
+   *                  isRecursively:
+   *                    type: boolean
+   *                    description: whether duplicate page with descendants
    *                required:
    *                required:
    *                  - pageId
    *                  - pageId
    *        responses:
    *        responses:
@@ -474,7 +524,7 @@ module.exports = (crowi) => {
    *            description: Internal server error.
    *            description: Internal server error.
    */
    */
   router.post('/duplicate', accessTokenParser, loginRequiredStrictly, csrf, validator.duplicatePage, apiV3FormValidator, async(req, res) => {
   router.post('/duplicate', accessTokenParser, loginRequiredStrictly, csrf, validator.duplicatePage, apiV3FormValidator, async(req, res) => {
-    const { pageId } = req.body;
+    const { pageId, isRecursively } = req.body;
 
 
     const newPagePath = pathUtils.normalizePath(req.body.pageNameInput);
     const newPagePath = pathUtils.normalizePath(req.body.pageNameInput);
 
 
@@ -493,36 +543,19 @@ module.exports = (crowi) => {
       return res.apiv3Err(new ErrorV3('Not Founded the page', 'notfound_or_forbidden'), 404);
       return res.apiv3Err(new ErrorV3('Not Founded the page', 'notfound_or_forbidden'), 404);
     }
     }
 
 
-    // populate
-    await page.populate({ path: 'revision', model: 'Revision', select: 'body' }).execPopulate();
-
-    // create option
-    const options = { page };
-    options.grant = page.grant;
-    options.grantUserGroupId = page.grantedGroup;
-    options.grantedUsers = page.grantedUsers;
-
+    let result;
 
 
-    const createdPage = await createPageAction({
-      path: newPagePath, user: req.user, body: page.revision.body, options,
-    });
-    const originTags = await page.findRelatedTagsById();
-    const savedTags = await saveTagsAction({ page, createdPage, pageTags: originTags });
-    const result = { page: pageService.serializeToObj(createdPage), tags: savedTags };
-
-    // global notification
-    if (globalNotificationService != null) {
-      try {
-        await globalNotificationService.fire(GlobalNotificationSetting.EVENT.PAGE_CREATE, createdPage, req.user);
-      }
-      catch (err) {
-        logger.error('Create grobal notification failed', err);
-      }
+    if (isRecursively) {
+      result = await duplicatePageRecursively(page, newPagePath, req.user);
+    }
+    else {
+      result = await duplicatePage(page, newPagePath, req.user);
     }
     }
 
 
-    return res.apiv3(result);
+    return res.apiv3({ result });
   });
   });
 
 
+
   router.get('/subordinated-list', accessTokenParser, loginRequired, async(req, res) => {
   router.get('/subordinated-list', accessTokenParser, loginRequired, async(req, res) => {
     const { path } = req.query;
     const { path } = req.query;