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

Merge pull request #8173 from weseek/feat/132680-create-button-handler

feat: Create New Page button handler
Ryoji Shimizu 2 лет назад
Родитель
Сommit
4b4a531358

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

@@ -88,7 +88,7 @@ export const resumeRenameOperation = async(pageId: string): Promise<void> => {
 };
 
 // TODO: define return type
-const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
+export const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
   // clone
   const params = Object.assign(tmpParams, {
     path: pagePath,

+ 3 - 4
apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx

@@ -16,10 +16,8 @@ type Props = {
 
 const CustomizePresentationSetting = (props: Props): JSX.Element => {
   const { adminCustomizeContainer } = props;
-
-  console.log(adminCustomizeContainer);
-
   const { t } = useTranslation();
+
   const onClickSubmit = useCallback(async() => {
     try {
       await adminCustomizeContainer.updateCustomizePresentation();
@@ -28,7 +26,8 @@ const CustomizePresentationSetting = (props: Props): JSX.Element => {
     catch (err) {
       toastError(err);
     }
-  }, [adminCustomizeContainer]);
+  }, [adminCustomizeContainer, t]);
+
   return (
     <React.Fragment>
       <h2 className="admin-setting-header">{t('admin:customize_settings.custom_presentation')}</h2>

+ 43 - 7
apps/app/src/components/Sidebar/PageCreateButton.tsx

@@ -2,10 +2,19 @@ import React, { useCallback, useState } from 'react';
 
 import { useRouter } from 'next/router';
 
+import { createPage } from '~/client/services/page-operation';
+import { toastError } from '~/client/util/toastr';
+import { useSWRxCurrentPage } from '~/stores/page';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:cli:PageCreateButton');
+
 export const PageCreateButton = React.memo((): JSX.Element => {
   const router = useRouter();
+  const { data: currentPage, isLoading } = useSWRxCurrentPage();
 
   const [isHovered, setIsHovered] = useState(false);
+  const [isCreating, setIsCreating] = useState(false);
 
   const onMouseEnterHandler = () => {
     setIsHovered(true);
@@ -15,12 +24,37 @@ export const PageCreateButton = React.memo((): JSX.Element => {
     setIsHovered(false);
   };
 
-  const isSelected = true;
-  // TODO: create page directly
-  // TODO: https://redmine.weseek.co.jp/issues/132680s
-  const onCreateNewPageButtonHandler = useCallback(() => {
-    // router.push(`${router.pathname}#edit`);
-  }, [router]);
+  const onCreateNewPageButtonHandler = useCallback(async() => {
+    if (isLoading) return;
+
+    try {
+      setIsCreating(true);
+
+      const parentPath = currentPage == null
+        ? '/'
+        : currentPage.path;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: currentPage?.grant || 1,
+        pageTags: [],
+        grantUserGroupId: currentPage?.grantedGroup?._id,
+        shouldGeneratePath: true,
+      };
+
+      const response = await createPage(parentPath, '', params);
+
+      router.push(`${response.page.id}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentPage, isLoading, router]);
   const onCreateTodaysButtonHandler = useCallback(() => {
     // router.push(`${router.pathname}#edit`);
   }, [router]);
@@ -43,10 +77,11 @@ export const PageCreateButton = React.memo((): JSX.Element => {
     >
       <div className="btn-group">
         <button
-          className={`d-block btn btn-primary ${isSelected ? 'active' : ''}`}
+          className="d-block btn btn-primary"
           onClick={onCreateNewPageButtonHandler}
           type="button"
           data-testid="grw-sidebar-nav-page-create-button"
+          disabled={isCreating}
         >
           <i className="material-symbols-outlined">edit</i>
         </button>
@@ -65,6 +100,7 @@ export const PageCreateButton = React.memo((): JSX.Element => {
                 className="dropdown-item"
                 onClick={onCreateNewPageButtonHandler}
                 type="button"
+                disabled={isCreating}
               >
                 Create New Page
               </button>

+ 1 - 0
apps/app/src/interfaces/page-operation.ts

@@ -34,4 +34,5 @@ export type OptionsToSave = {
   pageTags: string[] | null;
   grantUserGroupId?: string | null;
   grantUserGroupName?: string | null;
+  shouldGeneratePath?: boolean | null;
 };

+ 31 - 3
apps/app/src/server/routes/apiv3/pages.js

@@ -175,6 +175,7 @@ module.exports = (crowi) => {
       body('isSlackEnabled').if(value => value != null).isBoolean().withMessage('isSlackEnabled must be boolean'),
       body('slackChannels').if(value => value != null).isString().withMessage('slackChannels must be string'),
       body('pageTags').if(value => value != null).isArray().withMessage('pageTags must be array'),
+      body('shouldGeneratePath').optional().isBoolean().withMessage('shouldGeneratePath is must be boolean or undefined'),
     ],
     renamePage: [
       body('pageId').isMongoId().withMessage('pageId is required'),
@@ -238,6 +239,17 @@ module.exports = (crowi) => {
     return [];
   }
 
+  async function generateUniquePath(basePath, index = 1) {
+    const Page = mongoose.model('Page');
+    const path = basePath + index;
+    const response = await Page.findByPath(path);
+    const isPathExists = response != null;
+    if (isPathExists) {
+      return generateUniquePath(basePath, index + 1);
+    }
+    return path;
+  }
+
   /**
    * @swagger
    *
@@ -266,9 +278,9 @@ module.exports = (crowi) => {
    *                    type: array
    *                    items:
    *                      $ref: '#/components/schemas/Tag'
-   *                  createFromPageTree:
+   *                  shouldGeneratePath:
    *                    type: boolean
-   *                    description: Whether the page was created from the page tree or not
+   *                    description: Determine whether a new path should be generated
    *                required:
    *                  - body
    *                  - path
@@ -295,7 +307,7 @@ module.exports = (crowi) => {
    */
   router.post('/', accessTokenParser, loginRequiredStrictly, excludeReadOnlyUser, addActivity, validator.createPage, apiV3FormValidator, async(req, res) => {
     const {
-      body, grant, grantUserGroupId, overwriteScopesOfDescendants, isSlackEnabled, slackChannels, pageTags,
+      body, grant, grantUserGroupId, overwriteScopesOfDescendants, isSlackEnabled, slackChannels, pageTags, shouldGeneratePath,
     } = req.body;
 
     let { path } = req.body;
@@ -303,6 +315,22 @@ module.exports = (crowi) => {
     // check whether path starts slash
     path = addHeadingSlash(path);
 
+    if (shouldGeneratePath) {
+      try {
+        const rootPath = '/';
+        const defaultTitle = '/Untitled';
+        const basePath = path === rootPath ? defaultTitle : path + defaultTitle;
+        path = await generateUniquePath(basePath);
+
+        if (!isCreatablePage(path)) {
+          path = await generateUniquePath(defaultTitle);
+        }
+      }
+      catch (err) {
+        return res.apiv3Err(new ErrorV3('Failed to generate unique path'));
+      }
+    }
+
     if (!isCreatablePage(path)) {
       return res.apiv3Err(`Could not use the path '${path}'`);
     }