Преглед изворни кода

Merge pull request #4665 from weseek/feat/77524-80623-tagLable-refactoring

Feat/80623 TagLabel refactor
Haku Mizuki пре 4 година
родитељ
комит
5aa17fc60b

+ 32 - 5
packages/app/src/components/Navbar/GrowiSubNavigation.jsx

@@ -1,10 +1,11 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 import PropTypes from 'prop-types';
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 import AppContainer from '~/client/services/AppContainer';
 import NavigationContainer from '~/client/services/NavigationContainer';
 import PageContainer from '~/client/services/PageContainer';
+import EditorContainer from '~/client/services/EditorContainer';
 
 import TagLabels from '../Page/TagLabels';
 import SubNavButtons from './SubNavButtons';
@@ -15,13 +16,16 @@ import DrawerToggler from './DrawerToggler';
 
 import PagePathNav from '../PagePathNav';
 
+import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import { apiPost } from '~/client/util/apiv1-client';
+
 const GrowiSubNavigation = (props) => {
   const {
-    appContainer, navigationContainer, pageContainer, isCompactMode,
+    appContainer, navigationContainer, pageContainer, editorContainer, isCompactMode,
   } = props;
   const { isDrawerMode, editorMode, isDeviceSmallerThanMd } = navigationContainer.state;
   const {
-    pageId, path, createdAt, creator, updatedAt, revisionAuthor, isPageExist,
+    pageId, path, createdAt, creator, updatedAt, revisionAuthor, isPageExist, tags,
   } = pageContainer.state;
 
   const { isGuestUser } = appContainer;
@@ -33,6 +37,28 @@ const GrowiSubNavigation = (props) => {
     navigationContainer.setEditorMode(viewType);
   }
 
+  const tagsUpdatedHandler = useCallback(async(newTags) => {
+    // It will not be reflected in the DB until the page is refreshed
+    if (editorMode === 'edit') {
+      return editorContainer.setState({ tags: newTags });
+    }
+
+    try {
+      const { tags } = await apiPost('/tags.update', { pageId, tags: newTags });
+
+      // update pageContainer.state
+      pageContainer.setState({ tags });
+      // update editorContainer.state
+      editorContainer.setState({ tags });
+
+      toastSuccess('updated tags successfully');
+    }
+    catch (err) {
+      toastError(err, 'fail to update tags');
+    }
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [pageId]);
+
   return (
     <div className={`grw-subnav container-fluid d-flex align-items-center justify-content-between ${isCompactMode ? 'grw-subnav-compact d-print-none' : ''}`}>
 
@@ -47,7 +73,7 @@ const GrowiSubNavigation = (props) => {
         <div className="grw-path-nav-container">
           { pageContainer.isAbleToShowTagLabel && !isCompactMode && !isTagLabelHidden && (
             <div className="grw-taglabels-container">
-              <TagLabels editorMode={editorMode} />
+              <TagLabels tags={tags} tagsUpdateInvoked={tagsUpdatedHandler} />
             </div>
           ) }
           <PagePathNav pageId={pageId} pagePath={path} isSingleLineMode={isEditorMode} isCompactMode={isCompactMode} />
@@ -92,13 +118,14 @@ const GrowiSubNavigation = (props) => {
 /**
  * Wrapper component for using unstated
  */
-const GrowiSubNavigationWrapper = withUnstatedContainers(GrowiSubNavigation, [AppContainer, NavigationContainer, PageContainer]);
+const GrowiSubNavigationWrapper = withUnstatedContainers(GrowiSubNavigation, [AppContainer, NavigationContainer, PageContainer, EditorContainer]);
 
 
 GrowiSubNavigation.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+  editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
 
   isCompactMode: PropTypes.bool,
 };

+ 9 - 48
packages/app/src/components/Page/TagLabels.jsx

@@ -2,12 +2,9 @@ import React, { Suspense } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 import AppContainer from '~/client/services/AppContainer';
-import PageContainer from '~/client/services/PageContainer';
-import EditorContainer from '~/client/services/EditorContainer';
 
 import RenderTagLabels from './RenderTagLabels';
 import TagEditModal from './TagEditModal';
@@ -23,18 +20,8 @@ class TagLabels extends React.Component {
 
     this.openEditorModal = this.openEditorModal.bind(this);
     this.closeEditorModal = this.closeEditorModal.bind(this);
-    this.tagsUpdatedHandler = this.tagsUpdatedHandler.bind(this);
   }
 
-  /**
-   * @return tags data
-   *   1. pageContainer.state.tags if editorMode is view
-   *   2. editorContainer.state.tags if editorMode is edit
-   */
-  getTagData() {
-    const { editorContainer, pageContainer, editorMode } = this.props;
-    return (editorMode === 'edit') ? editorContainer.state.tags : pageContainer.state.tags;
-  }
 
   openEditorModal() {
     this.setState({ isTagEditModalShown: true });
@@ -44,37 +31,9 @@ class TagLabels extends React.Component {
     this.setState({ isTagEditModalShown: false });
   }
 
-  async tagsUpdatedHandler(newTags) {
-    const {
-      appContainer, editorContainer, pageContainer, editorMode,
-    } = this.props;
-
-    const { pageId } = pageContainer.state;
-
-    // It will not be reflected in the DB until the page is refreshed
-    if (editorMode === 'edit') {
-      return editorContainer.setState({ tags: newTags });
-    }
-
-    try {
-      const { tags } = await appContainer.apiPost('/tags.update', { pageId, tags: newTags });
-
-      // update pageContainer.state
-      pageContainer.setState({ tags });
-      // update editorContainer.state
-      editorContainer.setState({ tags });
-
-      toastSuccess('updated tags successfully');
-    }
-    catch (err) {
-      toastError(err, 'fail to update tags');
-    }
-  }
-
 
   render() {
-    const tags = this.getTagData();
-    const { appContainer } = this.props;
+    const { appContainer, tagsUpdateInvoked, tags } = this.props;
 
     return (
       <>
@@ -95,7 +54,7 @@ class TagLabels extends React.Component {
           isOpen={this.state.isTagEditModalShown}
           onClose={this.closeEditorModal}
           appContainer={this.props.appContainer}
-          onTagsUpdated={this.tagsUpdatedHandler}
+          onTagsUpdated={tagsUpdateInvoked}
         />
 
       </>
@@ -107,16 +66,18 @@ class TagLabels extends React.Component {
 /**
  * Wrapper component for using unstated
  */
-const TagLabelsWrapper = withUnstatedContainers(TagLabels, [AppContainer, PageContainer, EditorContainer]);
+const TagLabelsUnstatedWrapper = withUnstatedContainers(TagLabels, [AppContainer]);
 
 TagLabels.propTypes = {
   t: PropTypes.func.isRequired, // i18next
 
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-  editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
-
-  editorMode: PropTypes.string.isRequired,
+  tags: PropTypes.arrayOf(PropTypes.object).isRequired,
+  tagsUpdateInvoked: PropTypes.func,
 };
 
+// wrapping tsx component returned by withUnstatedContainers to avoid type error when this component used in other tsx components.
+const TagLabelsWrapper = (props) => {
+  return <TagLabelsUnstatedWrapper {...props}></TagLabelsUnstatedWrapper>;
+};
 export default withTranslation()(TagLabelsWrapper);

+ 25 - 9
packages/app/src/components/SearchPage/SearchResultContentSubNavigation.tsx

@@ -1,7 +1,11 @@
-import React, { FC } from 'react';
+import React, { FC, useCallback } from 'react';
 import PagePathNav from '../PagePathNav';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import AppContainer from '../../client/services/AppContainer';
+import TagLabels from '../Page/TagLabels';
+import { toastSuccess, toastError } from '../../client/util/apiNotification';
+import { apiPost } from '../../client/util/apiv1-client';
+import { useSWRTagsInfo } from '../../stores/page';
 import SubNavButtons from '../Navbar/SubNavButtons';
 
 type Props = {
@@ -17,21 +21,33 @@ const SearchResultContentSubNavigation: FC<Props> = (props : Props) => {
   const {
     appContainer, pageId, path, isCompactMode, isSignleLineMode,
   } = props;
+
+  const { data: tagInfoData, error: tagInfoError, mutate: mutateTagInfo } = useSWRTagsInfo(pageId);
+
+  const tagsUpdatedHandler = useCallback(async(newTags) => {
+    try {
+      await apiPost('/tags.update', { pageId, tags: newTags });
+      toastSuccess('updated tags successfully');
+      mutateTagInfo();
+    }
+    catch (err) {
+      toastError(err, 'fail to update tags');
+    }
+  }, [pageId, mutateTagInfo]);
+
+  if (tagInfoError != null || tagInfoData == null) {
+    return <></>;
+  }
   const { isSharedUser } = appContainer;
   return (
     <div className={`grw-subnav container-fluid d-flex align-items-center justify-content-between ${isCompactMode ? 'grw-subnav-compact d-print-none' : ''}`}>
       {/* Left side */}
       <div className="grw-path-nav-container">
-        {/* TODO : refactor TagLabels in a way that it can be used independently from pageContainenr
-              TASK: #80623 https://estoc.weseek.co.jp/redmine/issues/80623
-              CONDITION reference : https://dev.growi.org/5fabddf8bbeb1a0048bcb9e9
-              userPage is not included in search so chekcing only isSharedUser or not.
-          */}
-        {/* { !isSharedUser &&  !isCompactMode &&  (
+        {!isSharedUser && !isCompactMode && (
           <div className="grw-taglabels-container">
-            <TagLabels editorMode={editorMode} />
+            <TagLabels tags={tagInfoData.tags} tagsUpdateInvoked={tagsUpdatedHandler} />
           </div>
-        )} */}
+        )}
         <PagePathNav pageId={pageId} pagePath={path} isCompactMode={isCompactMode} isSingleLineMode={isSignleLineMode} />
       </div>
       {/* Right side */}

+ 3 - 0
packages/app/src/interfaces/pageTagsInfo.ts

@@ -0,0 +1,3 @@
+export type IPageTagsInfo = {
+  tags : string[],
+}

+ 10 - 1
packages/app/src/stores/page.tsx

@@ -1,9 +1,11 @@
 import useSWR, { SWRResponse } from 'swr';
 
 import { apiv3Get } from '../client/util/apiv3-client';
+import { apiGet } from '../client/util/apiv1-client';
 
 import { IPage } from '../interfaces/page';
 import { IPagingResult } from '../interfaces/paging-result';
+import { IPageTagsInfo } from '../interfaces/pageTagsInfo';
 import { IPageInfo } from '../interfaces/page-info';
 
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -32,7 +34,6 @@ export const useSWRxPageList = (
   );
 };
 
-
 export const useSWRPageInfo = (pageId: string): SWRResponse<IPageInfo, Error> => {
   return useSWR(`/page/info?pageId=${pageId}`, endpoint => apiv3Get(endpoint).then((response) => {
     return {
@@ -45,3 +46,11 @@ export const useSWRPageInfo = (pageId: string): SWRResponse<IPageInfo, Error> =>
     };
   }));
 };
+
+export const useSWRTagsInfo = (pageId: string): SWRResponse<IPageTagsInfo, Error> => {
+  return useSWR(`/pages.getPageTag?pageId=${pageId}`, endpoint => apiGet(endpoint).then((response: IPageTagsInfo) => {
+    return {
+      tags: response.tags,
+    };
+  }));
+};