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

Merge pull request #4780 from weseek/feat/pt-item-open-create-page-modal

feat: Pt item open create page modal
Haku Mizuki 4 лет назад
Родитель
Сommit
e844e9505b

+ 3 - 3
packages/app/src/components/Fab.jsx

@@ -6,7 +6,7 @@ import loggerFactory from '~/utils/logger';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import NavigationContainer from '~/client/services/NavigationContainer';
 import NavigationContainer from '~/client/services/NavigationContainer';
-import { usePageCreateModalOpened } from '~/stores/ui';
+import { useCreateModalStatus } from '~/stores/ui';
 
 
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
 import CreatePageIcon from './Icons/CreatePageIcon';
 import CreatePageIcon from './Icons/CreatePageIcon';
@@ -18,7 +18,7 @@ const Fab = (props) => {
   const { navigationContainer, appContainer } = props;
   const { navigationContainer, appContainer } = props;
   const { currentUser } = appContainer;
   const { currentUser } = appContainer;
 
 
-  const { mutate: mutatePageCreateModalOpened } = usePageCreateModalOpened();
+  const { open: openCreateModal } = useCreateModalStatus();
 
 
   const [animateClasses, setAnimateClasses] = useState('invisible');
   const [animateClasses, setAnimateClasses] = useState('invisible');
   const [buttonClasses, setButtonClasses] = useState('');
   const [buttonClasses, setButtonClasses] = useState('');
@@ -56,7 +56,7 @@ const Fab = (props) => {
           <button
           <button
             type="button"
             type="button"
             className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 waves-effect waves-light ${buttonClasses}`}
             className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 waves-effect waves-light ${buttonClasses}`}
-            onClick={() => mutatePageCreateModalOpened(true)}
+            onClick={() => openCreateModal()}
           >
           >
             <CreatePageIcon />
             <CreatePageIcon />
           </button>
           </button>

+ 4 - 4
packages/app/src/components/Hotkeys/Subscribers/CreatePage.jsx

@@ -1,19 +1,19 @@
 import React, { useEffect } from 'react';
 import React, { useEffect } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-import { usePageCreateModalOpened } from '~/stores/ui';
+import { useCreateModalStatus } from '~/stores/ui';
 
 
 const CreatePage = React.memo((props) => {
 const CreatePage = React.memo((props) => {
 
 
-  const { mutate } = usePageCreateModalOpened();
+  const { open: openCreateModal } = useCreateModalStatus();
 
 
   // setup effect
   // setup effect
   useEffect(() => {
   useEffect(() => {
-    mutate(true);
+    openCreateModal();
 
 
     // remove this
     // remove this
     props.onDeleteRender(this);
     props.onDeleteRender(this);
-  }, [mutate, props]);
+  }, [openCreateModal, props]);
 
 
   return <></>;
   return <></>;
 });
 });

+ 3 - 3
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -7,7 +7,7 @@ import { UncontrolledTooltip } from 'reactstrap';
 
 
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { IUser } from '~/interfaces/user';
 import { IUser } from '~/interfaces/user';
-import { useIsDeviceSmallerThanMd, usePageCreateModalOpened } from '~/stores/ui';
+import { useIsDeviceSmallerThanMd, useCreateModalStatus } from '~/stores/ui';
 
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import GrowiLogo from '../Icons/GrowiLogo';
 import GrowiLogo from '../Icons/GrowiLogo';
@@ -20,7 +20,7 @@ type NavbarRightProps = {
 }
 }
 const NavbarRight: FC<NavbarRightProps> = memo((props: NavbarRightProps) => {
 const NavbarRight: FC<NavbarRightProps> = memo((props: NavbarRightProps) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { mutate: mutatePageCreateModalOpened } = usePageCreateModalOpened();
+  const { open: openCreateModal } = useCreateModalStatus();
 
 
   const { currentUser } = props;
   const { currentUser } = props;
 
 
@@ -35,7 +35,7 @@ const NavbarRight: FC<NavbarRightProps> = memo((props: NavbarRightProps) => {
         <button
         <button
           className="px-md-2 nav-link btn-create-page border-0 bg-transparent"
           className="px-md-2 nav-link btn-create-page border-0 bg-transparent"
           type="button"
           type="button"
-          onClick={() => mutatePageCreateModalOpened(true)}
+          onClick={() => openCreateModal()}
         >
         >
           <i className="icon-pencil mr-2"></i>
           <i className="icon-pencil mr-2"></i>
           <span className="d-none d-lg-block">{ t('New') }</span>
           <span className="d-none d-lg-block">{ t('New') }</span>

+ 4 - 4
packages/app/src/components/Navbar/GrowiNavbarBottom.jsx

@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
 import NavigationContainer from '~/client/services/NavigationContainer';
 import NavigationContainer from '~/client/services/NavigationContainer';
-import { usePageCreateModalOpened, useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
+import { useCreateModalStatus, useIsDeviceSmallerThanMd, useDrawerOpened } from '~/stores/ui';
 
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import GlobalSearch from './GlobalSearch';
 import GlobalSearch from './GlobalSearch';
@@ -15,7 +15,7 @@ const GrowiNavbarBottom = (props) => {
 
 
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
-  const { mutate: mutatePageCreateModalOpened } = usePageCreateModalOpened();
+  const { open: openCreateModal } = useCreateModalStatus();
 
 
   const additionalClasses = ['grw-navbar-bottom'];
   const additionalClasses = ['grw-navbar-bottom'];
   if (isDrawerOpened) {
   if (isDrawerOpened) {
@@ -40,7 +40,7 @@ const GrowiNavbarBottom = (props) => {
             <a
             <a
               role="button"
               role="button"
               className="nav-link btn-lg"
               className="nav-link btn-lg"
-              onClick={() => mutateDrawerOpened(true)}
+              onClick={() => openCreateModal()}
             >
             >
               <i className="icon-menu"></i>
               <i className="icon-menu"></i>
             </a>
             </a>
@@ -59,7 +59,7 @@ const GrowiNavbarBottom = (props) => {
             <a
             <a
               role="button"
               role="button"
               className="nav-link btn-lg"
               className="nav-link btn-lg"
-              onClick={() => mutatePageCreateModalOpened(true)}
+              onClick={() => openCreateModal(true)}
             >
             >
               <i className="icon-pencil"></i>
               <i className="icon-pencil"></i>
             </a>
             </a>

+ 15 - 7
packages/app/src/components/PageCreateModal.jsx

@@ -1,5 +1,5 @@
 
 
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
@@ -13,7 +13,7 @@ import { pagePathUtils, pathUtils } from '@growi/core';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { withUnstatedContainers } from './UnstatedUtils';
 import { toastError } from '~/client/util/apiNotification';
 import { toastError } from '~/client/util/apiNotification';
-import { usePageCreateModalOpened } from '~/stores/ui';
+import { useCreateModalStatus, useCreateModalOpened, useCreateModalPath } from '~/stores/ui';
 
 
 import PagePathAutoComplete from './PagePathAutoComplete';
 import PagePathAutoComplete from './PagePathAutoComplete';
 
 
@@ -24,11 +24,14 @@ const {
 const PageCreateModal = (props) => {
 const PageCreateModal = (props) => {
   const { t, appContainer } = props;
   const { t, appContainer } = props;
 
 
-  const { data: isPageCreateModalOpened, mutate: mutatePageCreateModalOpened } = usePageCreateModalOpened();
+  const { close: closeCreateModal } = useCreateModalStatus();
+  const { data: isOpened } = useCreateModalOpened();
+  const { data: path } = useCreateModalPath();
+
 
 
   const config = appContainer.getConfig();
   const config = appContainer.getConfig();
   const isReachable = config.isSearchServiceReachable;
   const isReachable = config.isSearchServiceReachable;
-  const pathname = decodeURI(window.location.pathname);
+  const pathname = path || '';
   const userPageRootPath = userPageRoot(appContainer.currentUser);
   const userPageRootPath = userPageRoot(appContainer.currentUser);
   const pageNameInputInitialValue = isCreatablePage(pathname) ? pathUtils.addTrailingSlash(pathname) : '/';
   const pageNameInputInitialValue = isCreatablePage(pathname) ? pathUtils.addTrailingSlash(pathname) : '/';
   const now = format(new Date(), 'yyyy/MM/dd');
   const now = format(new Date(), 'yyyy/MM/dd');
@@ -38,6 +41,11 @@ const PageCreateModal = (props) => {
   const [pageNameInput, setPageNameInput] = useState(pageNameInputInitialValue);
   const [pageNameInput, setPageNameInput] = useState(pageNameInputInitialValue);
   const [template, setTemplate] = useState(null);
   const [template, setTemplate] = useState(null);
 
 
+  // ensure pageNameInput is synced with selectedPagePath || currentPagePath
+  useEffect(() => {
+    setPageNameInput(isCreatablePage(pathname) ? pathUtils.addTrailingSlash(pathname) : '/');
+  }, [pathname]);
+
   function transitBySubmitEvent(e, transitHandler) {
   function transitBySubmitEvent(e, transitHandler) {
     // prevent page transition by submit
     // prevent page transition by submit
     e.preventDefault();
     e.preventDefault();
@@ -266,12 +274,12 @@ const PageCreateModal = (props) => {
   return (
   return (
     <Modal
     <Modal
       size="lg"
       size="lg"
-      isOpen={isPageCreateModalOpened}
-      toggle={() => mutatePageCreateModalOpened(false)}
+      isOpen={isOpened}
+      toggle={() => closeCreateModal()}
       className="grw-create-page"
       className="grw-create-page"
       autoFocus={false}
       autoFocus={false}
     >
     >
-      <ModalHeader tag="h4" toggle={() => mutatePageCreateModalOpened(false)} className="bg-primary text-light">
+      <ModalHeader tag="h4" toggle={() => closeCreateModal()} className="bg-primary text-light">
         {t('New Page')}
         {t('New Page')}
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>

+ 26 - 6
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -1,11 +1,12 @@
 import React, {
 import React, {
-  useCallback, useState, FC, useEffect,
+  useCallback, useState, FC, useEffect, memo,
 } from 'react';
 } from 'react';
 import nodePath from 'path';
 import nodePath from 'path';
 
 
 import { ItemNode } from './ItemNode';
 import { ItemNode } from './ItemNode';
 import { useSWRxPageChildren } from '../../../stores/page-listing';
 import { useSWRxPageChildren } from '../../../stores/page-listing';
 import { usePageId } from '../../../stores/context';
 import { usePageId } from '../../../stores/context';
+import { useCreateModalStatus } from '../../../stores/ui';
 
 
 
 
 interface ItemProps {
 interface ItemProps {
@@ -25,7 +26,20 @@ const markTarget = (children: ItemNode[], targetId: string): void => {
   return;
   return;
 };
 };
 
 
-const ItemContol: FC = () => {
+type ItemControlProps = {
+  onClickOpenModalButtonHandler?(): void
+}
+
+const ItemControl: FC<ItemControlProps> = memo((props: ItemControlProps) => {
+  const onClickHandler = () => {
+    const { onClickOpenModalButtonHandler: handler } = props;
+    if (handler == null) {
+      return;
+    }
+
+    handler();
+  };
+
   return (
   return (
     <>
     <>
       <button
       <button
@@ -37,14 +51,14 @@ const ItemContol: FC = () => {
       </button>
       </button>
       <button
       <button
         type="button"
         type="button"
-        className="btn-link nav-link dropdown-toggle dropdown-toggle-no-caret border-0 rounded grw-btn-page-management py-0"
-        data-toggle="dropdown"
+        className="btn-link nav-link border-0 rounded grw-btn-page-management py-0"
+        onClick={onClickHandler}
       >
       >
         <i className="icon-plus text-muted"></i>
         <i className="icon-plus text-muted"></i>
       </button>
       </button>
     </>
     </>
   );
   );
-};
+});
 
 
 const ItemCount: FC = () => {
 const ItemCount: FC = () => {
   return (
   return (
@@ -67,6 +81,8 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
   const { data: targetId } = usePageId();
   const { data: targetId } = usePageId();
   const { data, error } = useSWRxPageChildren(isOpen ? page._id : null);
   const { data, error } = useSWRxPageChildren(isOpen ? page._id : null);
 
 
+  const { open: openCreateModal } = useCreateModalStatus();
+
   const hasChildren = useCallback((): boolean => {
   const hasChildren = useCallback((): boolean => {
     return currentChildren != null && currentChildren.length > 0;
     return currentChildren != null && currentChildren.length > 0;
   }, [currentChildren]);
   }, [currentChildren]);
@@ -75,6 +91,10 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     setIsOpen(!isOpen);
     setIsOpen(!isOpen);
   }, [isOpen]);
   }, [isOpen]);
 
 
+  const onClickOpenModalButtonHandler = useCallback(() => {
+    openCreateModal(page.path);
+  }, [openCreateModal, page]);
+
   // didMount
   // didMount
   useEffect(() => {
   useEffect(() => {
     if (hasChildren()) setIsOpen(true);
     if (hasChildren()) setIsOpen(true);
@@ -124,7 +144,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
           <ItemCount />
           <ItemCount />
         </div>
         </div>
         <div className="grw-pagetree-control d-none">
         <div className="grw-pagetree-control d-none">
-          <ItemContol />
+          <ItemControl onClickOpenModalButtonHandler={onClickOpenModalButtonHandler} />
         </div>
         </div>
       </div>
       </div>
       {
       {

+ 41 - 3
packages/app/src/stores/ui.tsx

@@ -12,6 +12,7 @@ import loggerFactory from '~/utils/logger';
 import { sessionStorageMiddleware } from './middlewares/sync-to-storage';
 import { sessionStorageMiddleware } from './middlewares/sync-to-storage';
 import { useStaticSWR } from './use-static-swr';
 import { useStaticSWR } from './use-static-swr';
 import { IUserUISettings } from '~/interfaces/user-ui-settings';
 import { IUserUISettings } from '~/interfaces/user-ui-settings';
+import { useCurrentPagePath } from './context';
 
 
 const logger = loggerFactory('growi:stores:ui');
 const logger = loggerFactory('growi:stores:ui');
 
 
@@ -234,7 +235,44 @@ export const useSidebarResizeDisabled = (isDisabled?: boolean): SWRResponse<bool
   return useStaticSWR('isSidebarResizeDisabled', isDisabled || null, { fallbackData: initialData });
   return useStaticSWR('isSidebarResizeDisabled', isDisabled || null, { fallbackData: initialData });
 };
 };
 
 
-export const usePageCreateModalOpened = (isOpened?: boolean): SWRResponse<boolean, Error> => {
-  const initialData = false;
-  return useStaticSWR('isPageCreateModalOpened', isOpened || null, { fallbackData: initialData });
+type CreateModalStatus = {
+  isOpened: boolean,
+  path?: string,
+}
+
+type CreateModalStatusUtils = {
+  open(path?: string): Promise<CreateModalStatus | undefined>
+  close(): Promise<CreateModalStatus | undefined>
+}
+
+export const useCreateModalStatus = (status?: CreateModalStatus): SWRResponse<CreateModalStatus, Error> & CreateModalStatusUtils => {
+  const swrResponse = useStaticSWR<CreateModalStatus, Error>('modalStatus', status || null);
+
+  return {
+    ...swrResponse,
+    open: (path?: string) => swrResponse.mutate({ isOpened: true, path }),
+    close: () => swrResponse.mutate({ isOpened: false }),
+  };
+};
+
+export const useCreateModalOpened = (): SWRResponse<boolean, Error> => {
+  const { data } = useCreateModalStatus();
+  return useSWR(
+    data != null ? ['isModalOpened', data] : null,
+    () => {
+      return data != null ? data.isOpened : false;
+    },
+  );
+};
+
+export const useCreateModalPath = (): SWRResponse<string, Error> => {
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { data: status } = useCreateModalStatus();
+
+  return useSWR(
+    [currentPagePath, status],
+    (currentPagePath, status) => {
+      return status.path || currentPagePath;
+    },
+  );
 };
 };