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

Merge remote-tracking branch 'origin/dev/7.0.x' into imprv/pae-create-button

Yuki Takei 2 лет назад
Родитель
Сommit
ddee4a35a4

+ 13 - 0
apps/app/public/static/locales/en_US/commons.json

@@ -68,6 +68,19 @@
     "feedback": "Feedback"
     "feedback": "Feedback"
   },
   },
 
 
+  "create_page_dropdown": {
+    "new_page": "Create New Page",
+    "todays": {
+      "desc": "Create today's ...",
+      "memo": "memo"
+    },
+    "template": {
+      "desc": "Create/Edit template page",
+      "children": "Template for children",
+      "descendants": "Template for descendants"
+    }
+  },
+
   "copy_to_clipboard": {
   "copy_to_clipboard": {
     "Copy to clipboard": "Copy to clipboard",
     "Copy to clipboard": "Copy to clipboard",
     "Page path": "Page path",
     "Page path": "Page path",

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

@@ -106,8 +106,6 @@
   "Disclose E-mail": "Disclose E-mail",
   "Disclose E-mail": "Disclose E-mail",
   "page exists": "this page already exists",
   "page exists": "this page already exists",
   "Error occurred": "Error occurred",
   "Error occurred": "Error occurred",
-  "Create today's": "Create today's ...",
-  "Memo": "memo",
   "Input page name": "Input page name",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
   "Input page name (optional)": "Input page name (optional)",
   "New Page": "New page",
   "New Page": "New page",

+ 13 - 0
apps/app/public/static/locales/ja_JP/commons.json

@@ -70,6 +70,19 @@
     "feedback": "ご意見・ご要望"
     "feedback": "ご意見・ご要望"
   },
   },
 
 
+  "create_page_dropdown": {
+    "new_page": "新規ページ作成",
+    "todays": {
+      "desc": "今日の◯◯を作成",
+      "memo": "メモ"
+    },
+    "template": {
+      "desc": "テンプレートページの作成/編集",
+      "children": "同一階層テンプレート",
+      "decendants": "下位層テンプレート"
+    }
+  },
+
   "copy_to_clipboard": {
   "copy_to_clipboard": {
     "Copy to clipboard": "クリップボードにコピー",
     "Copy to clipboard": "クリップボードにコピー",
     "Page path": "ページ名",
     "Page path": "ページ名",

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

@@ -105,8 +105,6 @@
   "Disclose E-mail": "メールアドレスの公開",
   "Disclose E-mail": "メールアドレスの公開",
   "page exists": "このページはすでに存在しています",
   "page exists": "このページはすでに存在しています",
   "Error occurred": "エラーが発生しました",
   "Error occurred": "エラーが発生しました",
-  "Create today's": "今日の◯◯を作成",
-  "Memo": "メモ",
   "Input page name": "ページ名を入力",
   "Input page name": "ページ名を入力",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "New Page": "新規ページ",
   "New Page": "新規ページ",

+ 13 - 0
apps/app/public/static/locales/zh_CN/commons.json

@@ -71,6 +71,19 @@
     "feedback": "意见和要求"
     "feedback": "意见和要求"
   },
   },
 
 
+  "create_page_dropdown": {
+    "new_page": "新页面",
+    "todays": {
+      "desc": "Create today's ...",
+      "memo": "memo"
+    },
+    "template": {
+      "desc": "创建/编辑模板页",
+      "children": "子模板",
+      "descendants": "子代模板"
+    }
+  },
+
 	"copy_to_clipboard": {
 	"copy_to_clipboard": {
 		"Copy to clipboard": "复制到剪贴板",
 		"Copy to clipboard": "复制到剪贴板",
 		"Page path": "页面路径",
 		"Page path": "页面路径",

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

@@ -111,8 +111,6 @@
 	"Disclose E-mail": "显示邮箱",
 	"Disclose E-mail": "显示邮箱",
 	"page exists": "页面已存在",
 	"page exists": "页面已存在",
 	"Error occurred": "Error occurred",
 	"Error occurred": "Error occurred",
-	"Create today's": "Create today's ...",
-	"Memo": "memo",
 	"Input page name": "Input page name",
 	"Input page name": "Input page name",
 	"Input page name (optional)": "Input page name (optional)",
 	"Input page name (optional)": "Input page name (optional)",
 	"New Page": "新页面",
 	"New Page": "新页面",

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

@@ -9,7 +9,7 @@ import { useCurrentPageId, useSWRMUTxCurrentPage, useSWRxTagsInfo } from '~/stor
 import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import { apiPost } from '../util/apiv1-client';
+import { apiGet, apiPost } from '../util/apiv1-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { toastError } from '../util/toastr';
 import { toastError } from '../util/toastr';
 
 
@@ -203,3 +203,19 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
 export const unlink = async(path: string): Promise<void> => {
 export const unlink = async(path: string): Promise<void> => {
   await apiPost('/pages.unlink', { path });
   await apiPost('/pages.unlink', { path });
 };
 };
+
+
+interface PageExistRequest {
+  pagePaths: string;
+}
+
+interface PageExistResponse {
+  pages: Record<string, boolean>;
+  ok: boolean
+}
+
+export const exist = async(pagePaths: string): Promise<PageExistResponse> => {
+  const request: PageExistRequest = { pagePaths };
+  const res = await apiGet('/pages.exist', request);
+  return res as PageExistResponse;
+};

+ 125 - 68
apps/app/src/components/Sidebar/PageCreateButton/PageCreateButton.tsx

@@ -1,29 +1,33 @@
 import React, { useCallback, useState } from 'react';
 import React, { useCallback, useState } from 'react';
 
 
+import { pagePathUtils } from '@growi/core/dist/utils';
+import { format } from 'date-fns';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
 
 
-import { createPage } from '~/client/services/page-operation';
+import { createPage, exist } from '~/client/services/page-operation';
 import { toastError } from '~/client/util/toastr';
 import { toastError } from '~/client/util/toastr';
+import { useCurrentUser } from '~/stores/context';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxCurrentPage } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import { PageCreateButtonDropdownMenu } from './PageCreateButtonDropdownMenu';
 import { CreateButton } from './CreateButton';
 import { CreateButton } from './CreateButton';
 import { DropendToggle } from './DropendToggle';
 import { DropendToggle } from './DropendToggle';
 
 
-import styles from './PageCreateButton.module.scss';
-
-const moduleClass = styles['grw-page-create-button'];
-
-
 const logger = loggerFactory('growi:cli:PageCreateButton');
 const logger = loggerFactory('growi:cli:PageCreateButton');
 
 
 export const PageCreateButton = React.memo((): JSX.Element => {
 export const PageCreateButton = React.memo((): JSX.Element => {
   const router = useRouter();
   const router = useRouter();
   const { data: currentPage, isLoading } = useSWRxCurrentPage();
   const { data: currentPage, isLoading } = useSWRxCurrentPage();
+  const { data: currentUser } = useCurrentUser();
 
 
   const [isHovered, setIsHovered] = useState(false);
   const [isHovered, setIsHovered] = useState(false);
   const [isCreating, setIsCreating] = useState(false);
   const [isCreating, setIsCreating] = useState(false);
 
 
+  const now = format(new Date(), 'yyyy/MM/dd');
+  const userHomepagePath = pagePathUtils.userHomepagePath(currentUser);
+  const todaysPath = `${userHomepagePath}/memo/${now}`;
+
   const onMouseEnterHandler = () => {
   const onMouseEnterHandler = () => {
     setIsHovered(true);
     setIsHovered(true);
   };
   };
@@ -32,7 +36,7 @@ export const PageCreateButton = React.memo((): JSX.Element => {
     setIsHovered(false);
     setIsHovered(false);
   };
   };
 
 
-  const onCreateNewPageButtonHandler = useCallback(async() => {
+  const onClickCreateNewPageButtonHandler = useCallback(async() => {
     if (isLoading) return;
     if (isLoading) return;
 
 
     try {
     try {
@@ -63,86 +67,139 @@ export const PageCreateButton = React.memo((): JSX.Element => {
       setIsCreating(false);
       setIsCreating(false);
     }
     }
   }, [currentPage, isLoading, router]);
   }, [currentPage, isLoading, router]);
-  const onCreateTodaysButtonHandler = useCallback(() => {
-    // router.push(`${router.pathname}#edit`);
-  }, [router]);
-  const onTemplateForChildrenButtonHandler = useCallback(() => {
-    // router.push(`${router.pathname}/_template#edit`);
-  }, [router]);
-  const onTemplateForDescendantsButtonHandler = useCallback(() => {
-    // router.push(`${router.pathname}/__template#edit`);
-  }, [router]);
-
-  // TODO: i18n
-  // https://redmine.weseek.co.jp/issues/132681
+
+  const onClickCreateTodaysButtonHandler = useCallback(async() => {
+    if (currentUser == null) {
+      return;
+    }
+
+    try {
+      setIsCreating(true);
+
+      // TODO: get grant, grantUserGroupId data from parent page
+      // https://redmine.weseek.co.jp/issues/133892
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: 1,
+        pageTags: [],
+      };
+
+      const res = await exist(JSON.stringify([todaysPath]));
+      if (!res.pages[todaysPath]) {
+        await createPage(todaysPath, '', params);
+      }
+
+      router.push(`${todaysPath}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentUser, router, todaysPath]);
+
+  const onClickTemplateForChildrenButtonHandler = useCallback(async() => {
+    if (isLoading) return;
+
+    try {
+      setIsCreating(true);
+
+      const path = currentPage == null || currentPage.path === '/'
+        ? '/_template'
+        : `${currentPage.path}/_template`;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: currentPage?.grant || 1,
+        pageTags: [],
+        grantUserGroupId: currentPage?.grantedGroup?._id,
+      };
+
+      const res = await exist(JSON.stringify([path]));
+      if (!res.pages[path]) {
+        await createPage(path, '', params);
+      }
+
+      router.push(`${path}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentPage, isLoading, router]);
+
+  const onClickTemplateForDescendantsButtonHandler = useCallback(async() => {
+    if (isLoading) return;
+
+    try {
+      setIsCreating(true);
+
+      const path = currentPage == null || currentPage.path === '/'
+        ? '/__template'
+        : `${currentPage.path}/__template`;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: currentPage?.grant || 1,
+        pageTags: [],
+        grantUserGroupId: currentPage?.grantedGroup?._id,
+      };
+
+      const res = await exist(JSON.stringify([path]));
+      if (!res.pages[path]) {
+        await createPage(path, '', params);
+      }
+
+      router.push(`${path}#edit`);
+    }
+    catch (err) {
+      logger.warn(err);
+      toastError(err);
+    }
+    finally {
+      setIsCreating(false);
+    }
+  }, [currentPage, isLoading, router]);
+
+  // TODO: update button design
+  // https://redmine.weseek.co.jp/issues/132683
   return (
   return (
     <div
     <div
-      className={`${moduleClass} d-flex flex-row`}
+      className="d-flex flex-row"
       onMouseEnter={onMouseEnterHandler}
       onMouseEnter={onMouseEnterHandler}
       onMouseLeave={onMouseLeaveHandler}
       onMouseLeave={onMouseLeaveHandler}
     >
     >
       <div className="btn-group flex-grow-1">
       <div className="btn-group flex-grow-1">
         <CreateButton
         <CreateButton
           className="z-2"
           className="z-2"
-          onClick={onCreateNewPageButtonHandler}
+          onClick={onClickCreateNewPageButtonHandler}
           disabled={isCreating}
           disabled={isCreating}
         />
         />
       </div>
       </div>
-      { isHovered && (
+      {isHovered && (
         <div className="btn-group dropend position-absolute">
         <div className="btn-group dropend position-absolute">
           <DropendToggle
           <DropendToggle
             className="dropdown-toggle dropdown-toggle-split"
             className="dropdown-toggle dropdown-toggle-split"
             data-bs-toggle="dropdown"
             data-bs-toggle="dropdown"
             aria-expanded="false"
             aria-expanded="false"
           />
           />
-          <ul className="dropdown-menu">
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onCreateNewPageButtonHandler}
-                type="button"
-                disabled={isCreating}
-              >
-                Create New Page
-              </button>
-            </li>
-            <li><hr className="dropdown-divider" /></li>
-            <li><span className="text-muted px-3">Create today&apos;s ...</span></li>
-            {/* TODO: show correct create today's page path */}
-            {/* https://redmine.weseek.co.jp/issues/132682 */}
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onCreateTodaysButtonHandler}
-                type="button"
-              >
-                Create today&apos;s
-              </button>
-            </li>
-            <li><hr className="dropdown-divider" /></li>
-            <li><span className="text-muted px-3">Child page template</span></li>
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onTemplateForChildrenButtonHandler}
-                type="button"
-              >
-                Template for children
-              </button>
-            </li>
-            <li>
-              <button
-                className="dropdown-item"
-                onClick={onTemplateForDescendantsButtonHandler}
-                type="button"
-              >
-                Template for descendants
-              </button>
-            </li>
-          </ul>
+          <PageCreateButtonDropdownMenu
+            todaysPath={todaysPath}
+            onClickCreateNewPageButtonHandler={onClickCreateNewPageButtonHandler}
+            onClickCreateTodaysButtonHandler={onClickCreateTodaysButtonHandler}
+            onClickTemplateForChildrenButtonHandler={onClickTemplateForChildrenButtonHandler}
+            onClickTemplateForDescendantsButtonHandler={onClickTemplateForDescendantsButtonHandler}
+          />
         </div>
         </div>
       )}
       )}
     </div>
     </div>
   );
   );
 });
 });
-PageCreateButton.displayName = 'PageCreateButton';

+ 69 - 0
apps/app/src/components/Sidebar/PageCreateButton/PageCreateButtonDropdownMenu.tsx

@@ -0,0 +1,69 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+type PageCreateButtonDropdownMenuProps = {
+  todaysPath: string,
+  onClickCreateNewPageButtonHandler: () => Promise<void>
+  onClickCreateTodaysButtonHandler: () => Promise<void>
+  onClickTemplateForChildrenButtonHandler: () => Promise<void>
+  onClickTemplateForDescendantsButtonHandler: () => Promise<void>
+}
+
+export const PageCreateButtonDropdownMenu = React.memo((props: PageCreateButtonDropdownMenuProps): JSX.Element => {
+  const {
+    todaysPath,
+    onClickCreateNewPageButtonHandler,
+    onClickCreateTodaysButtonHandler,
+    onClickTemplateForChildrenButtonHandler,
+    onClickTemplateForDescendantsButtonHandler,
+  } = props;
+
+  const { t } = useTranslation('commons');
+
+  return (
+    <ul className="dropdown-menu">
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickCreateNewPageButtonHandler}
+          type="button"
+        >
+          {t('create_page_dropdown.new_page')}
+        </button>
+      </li>
+      <li><hr className="dropdown-divider" /></li>
+      <li><span className="text-muted px-3">{t('create_page_dropdown.todays.desc')}</span></li>
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickCreateTodaysButtonHandler}
+          type="button"
+        >
+          {todaysPath}
+        </button>
+      </li>
+      <li><hr className="dropdown-divider" /></li>
+      <li><span className="text-muted text-nowrap px-3">{t('create_page_dropdown.template.desc')}</span></li>
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickTemplateForChildrenButtonHandler}
+          type="button"
+        >
+          {t('create_page_dropdown.template.children')}
+        </button>
+      </li>
+      <li>
+        <button
+          className="dropdown-item"
+          onClick={onClickTemplateForDescendantsButtonHandler}
+          type="button"
+        >
+          {t('create_page_dropdown.template.decendants')}
+        </button>
+      </li>
+    </ul>
+  );
+});
+PageCreateButtonDropdownMenu.displayName = 'PageCreateButtonDropdownMenu';

+ 2 - 4
apps/app/src/server/routes/apiv3/pages.js

@@ -240,11 +240,9 @@ module.exports = (crowi) => {
   }
   }
 
 
   async function generateUniquePath(basePath, index = 1) {
   async function generateUniquePath(basePath, index = 1) {
-    const Page = mongoose.model('Page');
     const path = basePath + index;
     const path = basePath + index;
-    const response = await Page.findByPath(path);
-    const isPathExists = response != null;
-    if (isPathExists) {
+    const existingPageId = await Page.exists({ path, isEmpty: false });
+    if (existingPageId != null) {
       return generateUniquePath(basePath, index + 1);
       return generateUniquePath(basePath, index + 1);
     }
     }
     return path;
     return path;