Răsfoiți Sursa

Merge pull request #8242 from weseek/feat/135248-update-create-edit-template

feat: Update CreateTemplateModal
Yuki Takei 2 ani în urmă
părinte
comite
4d0dc99ca1

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

@@ -464,7 +464,7 @@
       "label": "Template for children",
       "desc": "Applies only to the same level pages which the template exists"
     },
-    "decendants": {
+    "descendants": {
       "label": "Template for descendants",
       "desc": "Applies to all decendant pages"
     }

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

@@ -79,7 +79,7 @@
     "template": {
       "desc": "テンプレートページの作成/編集",
       "children": "同一階層テンプレート",
-      "decendants": "下位層テンプレート"
+      "descendants": "下位層テンプレート"
     }
   },
 

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

@@ -497,7 +497,7 @@
       "label": "同一階層テンプレート",
       "desc": "テンプレートページが存在する階層にのみ適用されます"
     },
-    "decendants": {
+    "descendants": {
       "label": "下位層テンプレート",
       "desc": "テンプレートページが存在する下位層のすべてのページに適用されます"
     }

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

@@ -451,7 +451,7 @@
 			"label": "子模板",
 			"desc": "仅应用于模板存在的同一级别页"
 		},
-		"decendants": {
+		"descendants": {
 			"label": "子代模板",
 			"desc": "适用于所有分散页"
 		}

+ 0 - 76
apps/app/src/components/CreateTemplateModal.jsx

@@ -1,76 +0,0 @@
-import React from 'react';
-
-import { pathUtils } from '@growi/core/dist/utils';
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-import { Modal, ModalHeader, ModalBody } from 'reactstrap';
-import urljoin from 'url-join';
-
-const CreateTemplateModal = (props) => {
-  const { t } = useTranslation();
-  const { path } = props;
-
-  const parentPath = pathUtils.addTrailingSlash(path);
-
-  function generateUrl(label) {
-    return encodeURI(urljoin(parentPath, label, '#edit'));
-  }
-
-  /**
-   * @param {string} target Which hierarchy to create [children, decendants]
-   */
-  function renderTemplateCard(target, label) {
-    return (
-      <div className="card card-select-template">
-        <div className="card-header">{ t(`template.${target}.label`) }</div>
-        <div className="card-body">
-          <p className="text-center"><code>{label}</code></p>
-          <p className="form-text text-muted text-center"><small>{t(`template.${target}.desc`) }</small></p>
-        </div>
-        <div className="card-footer text-center">
-          <a
-            data-testid={`template-button-${target}`}
-            href={generateUrl(label)}
-            className="btn btn-sm btn-primary"
-            id={`template-button-${target}`}
-          >
-            { t('Edit') }
-          </a>
-        </div>
-      </div>
-    );
-  }
-
-  return (
-    <Modal isOpen={props.isOpen} toggle={props.onClose} data-testid="page-template-modal">
-      <ModalHeader tag="h4" toggle={props.onClose} className="bg-primary text-light">
-        {t('template.modal_label.Create/Edit Template Page')}
-      </ModalHeader>
-      <ModalBody>
-        <div>
-          <label className="form-label mb-4">
-            <code>{parentPath}</code><br />
-            { t('template.modal_label.Create template under') }
-          </label>
-          <div className="row row-cols-2">
-            <div className="col">
-              {renderTemplateCard('children', '_template')}
-            </div>
-            <div className="col">
-              {renderTemplateCard('decendants', '__template')}
-            </div>
-          </div>
-        </div>
-      </ModalBody>
-    </Modal>
-
-  );
-};
-
-CreateTemplateModal.propTypes = {
-  path: PropTypes.string.isRequired,
-  isOpen: PropTypes.bool.isRequired,
-  onClose: PropTypes.func.isRequired,
-};
-
-export default CreateTemplateModal;

+ 91 - 0
apps/app/src/components/CreateTemplateModal.tsx

@@ -0,0 +1,91 @@
+import React from 'react';
+
+import { pathUtils } from '@growi/core/dist/utils';
+import { useTranslation } from 'next-i18next';
+import { Modal, ModalHeader, ModalBody } from 'reactstrap';
+
+import { TargetType, LabelType } from '~/interfaces/template';
+
+import { useOnTemplateButtonClicked } from './Navbar/hooks';
+
+type TemplateCardProps = {
+  target: TargetType;
+  label: LabelType;
+  isPageCreating: boolean;
+  onClickHandler: () => void;
+};
+
+const TemplateCard: React.FC<TemplateCardProps> = ({
+  target, label, isPageCreating, onClickHandler,
+}) => {
+  const { t } = useTranslation();
+
+  return (
+    <div className="card card-select-template">
+      <div className="card-header">{t(`template.${target}.label`)}</div>
+      <div className="card-body">
+        <p className="text-center"><code>{label}</code></p>
+        <p className="form-text text-muted text-center"><small>{t(`template.${target}.desc`)}</small></p>
+      </div>
+      <div className="card-footer text-center">
+        <button
+          disabled={isPageCreating}
+          data-testid={`template-button-${target}`}
+          className="btn btn-sm btn-primary"
+          id={`template-button-${target}`}
+          onClick={onClickHandler}
+          type="button"
+        >
+          {t('Edit')}
+        </button>
+      </div>
+    </div>
+  );
+};
+
+type CreateTemplateModalProps = {
+  path: string;
+  isOpen: boolean;
+  onClose: () => void;
+};
+
+export const CreateTemplateModal: React.FC<CreateTemplateModalProps> = ({
+  path, isOpen, onClose,
+}) => {
+  const { t } = useTranslation();
+
+  const { onClickHandler: onClickTemplateButton, isPageCreating } = useOnTemplateButtonClicked(path);
+
+  const parentPath = pathUtils.addTrailingSlash(path);
+
+  const renderTemplateCard = (target: TargetType, label: LabelType) => (
+    <div className="col">
+      <TemplateCard
+        target={target}
+        label={label}
+        isPageCreating={isPageCreating}
+        onClickHandler={() => onClickTemplateButton(label)}
+      />
+    </div>
+  );
+
+  return (
+    <Modal isOpen={isOpen} toggle={onClose} data-testid="page-template-modal">
+      <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light">
+        {t('template.modal_label.Create/Edit Template Page')}
+      </ModalHeader>
+      <ModalBody>
+        <div>
+          <label className="form-label mb-4">
+            <code>{parentPath}</code><br />
+            {t('template.modal_label.Create template under')}
+          </label>
+          <div className="row row-cols-2">
+            {renderTemplateCard('children', '_template')}
+            {renderTemplateCard('descendants', '__template')}
+          </div>
+        </div>
+      </ModalBody>
+    </Modal>
+  );
+};

+ 1 - 1
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -31,7 +31,7 @@ import {
   useSelectedGrant,
 } from '~/stores/ui';
 
-import CreateTemplateModal from '../CreateTemplateModal';
+import { CreateTemplateModal } from '../CreateTemplateModal';
 import AttachmentIcon from '../Icons/AttachmentIcon';
 import HistoryIcon from '../Icons/HistoryIcon';
 import PresentationIcon from '../Icons/PresentationIcon';

+ 47 - 2
apps/app/src/components/Navbar/hooks.tsx

@@ -1,10 +1,11 @@
-import { useCallback } from 'react';
+import { useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 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 { LabelType } from '~/interfaces/template';
 import { useIsNotFound } from '~/stores/page';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
@@ -56,3 +57,47 @@ export const useOnPageEditorModeButtonClicked = (
     mutateEditorMode(editorMode);
   }, [isNotFound, mutateEditorMode, path, router, setIsCreating, t]);
 };
+
+export const useOnTemplateButtonClicked = (
+    currentPagePath: string,
+): {
+  onClickHandler: (label: LabelType) => Promise<void>,
+  isPageCreating: boolean
+} => {
+  const router = useRouter();
+  const [isPageCreating, setIsPageCreating] = useState(false);
+
+  const onClickHandler = useCallback(async(label: LabelType) => {
+    try {
+      setIsPageCreating(true);
+
+      const path = currentPagePath == null || currentPagePath === '/'
+        ? `/${label}`
+        : `${currentPagePath}/${label}`;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: 4,
+        // grant: currentPage?.grant || 1,
+        // 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 {
+      setIsPageCreating(false);
+    }
+  }, [currentPagePath, router]);
+
+  return { onClickHandler, isPageCreating };
+};

+ 4 - 4
apps/app/src/components/PageCreateModal.jsx

@@ -281,16 +281,16 @@ const PageCreateModal = () => {
               <DropdownToggle id="template-type" caret>
                 {template == null && t('template.option_label.select')}
                 {template === 'children' && t('template.children.label')}
-                {template === 'decendants' && t('template.decendants.label')}
+                {template === 'descendants' && t('template.descendants.label')}
               </DropdownToggle>
               <DropdownMenu>
                 <DropdownItem onClick={() => onChangeTemplateHandler('children')}>
                   {t('template.children.label')} (_template)<br className="d-block d-md-none" />
                   <small className="text-muted text-wrap">- {t('template.children.desc')}</small>
                 </DropdownItem>
-                <DropdownItem onClick={() => onChangeTemplateHandler('decendants')}>
-                  {t('template.decendants.label')} (__template) <br className="d-block d-md-none" />
-                  <small className="text-muted">- {t('template.decendants.desc')}</small>
+                <DropdownItem onClick={() => onChangeTemplateHandler('descendants')}>
+                  {t('template.descendants.label')} (__template) <br className="d-block d-md-none" />
+                  <small className="text-muted">- {t('template.descendants.desc')}</small>
                 </DropdownItem>
               </DropdownMenu>
             </UncontrolledButtonDropdown>

+ 1 - 1
apps/app/src/components/Sidebar/PageCreateButton/DropendMenu.tsx

@@ -60,7 +60,7 @@ export const DropendMenu = React.memo((props: DropendMenuProps): JSX.Element =>
           onClick={onClickTemplateForDescendantsButtonHandler}
           type="button"
         >
-          {t('create_page_dropdown.template.decendants')}
+          {t('create_page_dropdown.template.descendants')}
         </button>
       </li>
     </ul>

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

@@ -168,8 +168,6 @@ export const PageCreateButton = React.memo((): JSX.Element => {
     }
   }, [currentPage, isLoading, router]);
 
-  // TODO: update button design
-  // https://redmine.weseek.co.jp/issues/132683
   return (
     <div
       className="d-flex flex-row"

+ 2 - 0
apps/app/src/interfaces/template.ts

@@ -0,0 +1,2 @@
+export type TargetType = 'children' | 'descendants';
+export type LabelType = '_template' | '__template';

+ 1 - 1
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--access-to-page.cy.ts

@@ -303,7 +303,7 @@ context('Access to Template Editing Mode', () => {
     cy.getByTestid('open-page-template-modal-btn').filter(':visible').click({force: true});
     cy.getByTestid('page-template-modal').should('be.visible');
 
-    cy.getByTestid('template-button-decendants').click(({force: true}))
+    cy.getByTestid('template-button-descendants').click(({force: true}))
     cy.waitUntilSkeletonDisappear();
 
     cy.getByTestid('navbar-editor').should('be.visible').then(()=>{