Taichi Masuyama 4 лет назад
Родитель
Сommit
f1b758c91f

+ 69 - 0
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -0,0 +1,69 @@
+import React, { FC } from 'react';
+
+import toastr from 'toastr';
+import { useTranslation } from 'react-i18next';
+
+import { IPageHasId } from '~/interfaces/page';
+
+type PageItemControlProps = {
+  page: IPageHasId,
+  onClickDeleteButton?: (pageId: string)=>void,
+}
+
+const PageItemControl: FC<PageItemControlProps> = (props: PageItemControlProps) => {
+
+  const { page, onClickDeleteButton } = props;
+  const { t } = useTranslation('');
+
+  const deleteButtonHandler = () => {
+    if (onClickDeleteButton != null) {
+      onClickDeleteButton(page._id);
+    }
+  };
+  return (
+    <>
+      <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"
+      >
+        <i className="fa fa-ellipsis-v text-muted"></i>
+      </button>
+      <div className="dropdown-menu dropdown-menu-right">
+
+        {/* TODO: if there is the following button in XD add it here
+        <button
+          type="button"
+          className="btn btn-link p-0"
+          value={page.path}
+          onClick={(e) => {
+            window.location.href = e.currentTarget.value;
+          }}
+        >
+          <i className="icon-login" />
+        </button>
+        */}
+
+        {/*
+          TODO: add function to the following buttons like using modal or others
+          ref: https://estoc.weseek.co.jp/redmine/issues/79026
+        */}
+        <button className="dropdown-item text-danger" type="button" onClick={deleteButtonHandler}>
+          <i className="icon-fw icon-fire"></i>{t('Delete')}
+        </button>
+        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
+          <i className="icon-fw icon-star"></i>{t('Add to bookmark')}
+        </button>
+        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
+          <i className="icon-fw icon-docs"></i>{t('Duplicate')}
+        </button>
+        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
+          <i className="icon-fw  icon-action-redo"></i>{t('Move/Rename')}
+        </button>
+      </div>
+    </>
+  );
+
+};
+
+export default PageItemControl;

+ 2 - 64
packages/app/src/components/SearchPage/SearchResultListItem.tsx

@@ -1,75 +1,13 @@
 import React, { FC } from 'react';
 
 import Clamp from 'react-multiline-clamp';
-import toastr from 'toastr';
 
-import { useTranslation } from 'react-i18next';
 import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
-import { IPageSearchResultData } from '../../interfaces/search';
-
-import { IPageHasId } from '~/interfaces/page';
-
-type PageItemControlProps = {
-  page: IPageHasId,
-  onClickDeleteButton?: (pageId: string)=>void,
-}
 
-const PageItemControl: FC<PageItemControlProps> = (props: PageItemControlProps) => {
-
-  const { page, onClickDeleteButton } = props;
-  const { t } = useTranslation('');
-
-  const deleteButtonHandler = () => {
-    if (onClickDeleteButton != null) {
-      onClickDeleteButton(page._id);
-    }
-  };
-  return (
-    <>
-      <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"
-      >
-        <i className="fa fa-ellipsis-v text-muted"></i>
-      </button>
-      <div className="dropdown-menu dropdown-menu-right">
-
-        {/* TODO: if there is the following button in XD add it here
-        <button
-          type="button"
-          className="btn btn-link p-0"
-          value={page.path}
-          onClick={(e) => {
-            window.location.href = e.currentTarget.value;
-          }}
-        >
-          <i className="icon-login" />
-        </button>
-        */}
-
-        {/*
-          TODO: add function to the following buttons like using modal or others
-          ref: https://estoc.weseek.co.jp/redmine/issues/79026
-        */}
-        <button className="dropdown-item text-danger" type="button" onClick={deleteButtonHandler}>
-          <i className="icon-fw icon-fire"></i>{t('Delete')}
-        </button>
-        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
-          <i className="icon-fw icon-star"></i>{t('Add to bookmark')}
-        </button>
-        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
-          <i className="icon-fw icon-docs"></i>{t('Duplicate')}
-        </button>
-        <button className="dropdown-item" type="button" onClick={() => toastr.warning(t('search_result.currently_not_implemented'))}>
-          <i className="icon-fw  icon-action-redo"></i>{t('Move/Rename')}
-        </button>
-      </div>
-    </>
-  );
+import { IPageSearchResultData } from '../../interfaces/search';
+import PageItemControl from '../Common/Dropdown/PageItemControl';
 
-};
 
 type Props = {
   page: IPageSearchResultData,

+ 31 - 14
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -5,9 +5,11 @@ import nodePath from 'path';
 import { useTranslation } from 'react-i18next';
 
 import { ItemNode } from './ItemNode';
+import { IPageHasId } from '~/interfaces/page';
 import { useSWRxPageChildren } from '../../../stores/page-listing';
 import { usePageId } from '../../../stores/context';
 import ClosableTextInput, { AlertInfo, AlertType } from '../../Common/ClosableTextInput';
+import PageItemControl from '../../Common/Dropdown/PageItemControl';
 
 
 interface ItemProps {
@@ -28,32 +30,39 @@ const markTarget = (children: ItemNode[], targetId: string): void => {
 };
 
 type ItemControlProps = {
+  page: Partial<IPageHasId>
+  onClickDeleteButtonHandler?(): void
   onClickPlusButtonHandler?(): void
 }
 
 const ItemControl: FC<ItemControlProps> = memo((props: ItemControlProps) => {
-  const onClickHandler = () => {
-    const { onClickPlusButtonHandler: handler } = props;
-    if (handler == null) {
+  const onClickPlusButton = () => {
+    if (props.onClickPlusButtonHandler == null) {
       return;
     }
 
-    handler();
+    props.onClickPlusButtonHandler();
   };
 
+  const onClickDeleteButton = () => {
+    if (props.onClickDeleteButtonHandler == null) {
+      return;
+    }
+
+    props.onClickDeleteButtonHandler();
+  };
+
+  if (props.page == null) {
+    return <></>;
+  }
+
   return (
     <>
-      <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"
-      >
-        <i className="icon-options-vertical text-muted"></i>
-      </button>
+      <PageItemControl page={props.page} onClickDeleteButton={onClickDeleteButton} />
       <button
         type="button"
         className="btn-link nav-link border-0 rounded grw-btn-page-management py-0"
-        onClick={onClickHandler}
+        onClick={onClickPlusButton}
       >
         <i className="icon-plus text-muted"></i>
       </button>
@@ -65,7 +74,7 @@ const ItemCount: FC = () => {
   return (
     <>
       <span className="grw-pagetree-count badge badge-pill badge-light">
-        10
+        {/* TODO: consider to show the number of children pages */}
       </span>
     </>
   );
@@ -93,6 +102,10 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     setIsOpen(!isOpen);
   }, [isOpen]);
 
+  const onClickDeleteButtonHandler = useCallback(() => {
+    console.log('Show delete modal');
+  }, []);
+
   const inputValidator = (title: string | null): AlertInfo | null => {
     if (title == null || title === '') {
       return {
@@ -158,7 +171,11 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
           <ItemCount />
         </div>
         <div className="grw-pagetree-control d-none">
-          <ItemControl onClickPlusButtonHandler={() => { setNewPageInputShown(true) }} />
+          <ItemControl
+            page={page}
+            onClickDeleteButtonHandler={onClickDeleteButtonHandler}
+            onClickPlusButtonHandler={() => { setNewPageInputShown(true) }}
+          />
         </div>
       </div>
 

+ 3 - 4
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -1,17 +1,16 @@
 import React, { FC } from 'react';
 
-import { IPage } from '../../../interfaces/page';
+import { IPageHasId } from '../../../interfaces/page';
 import { ItemNode } from './ItemNode';
 import Item from './Item';
 import { useSWRxPageAncestorsChildren } from '../../../stores/page-listing';
 import { useTargetAndAncestors, useCurrentPagePath } from '../../../stores/context';
-import { HasObjectId } from '../../../interfaces/has-object-id';
 
 
 /*
  * Utility to generate initial node
  */
-const generateInitialNodeBeforeResponse = (targetAndAncestors: Partial<IPage>[]): ItemNode => {
+const generateInitialNodeBeforeResponse = (targetAndAncestors: Partial<IPageHasId>[]): ItemNode => {
   const nodes = targetAndAncestors.map((page): ItemNode => {
     return new ItemNode(page, []);
   });
@@ -25,7 +24,7 @@ const generateInitialNodeBeforeResponse = (targetAndAncestors: Partial<IPage>[])
   return rootNode;
 };
 
-const generateInitialNodeAfterResponse = (ancestorsChildren: Record<string, Partial<IPage & HasObjectId>[]>, rootNode: ItemNode): ItemNode => {
+const generateInitialNodeAfterResponse = (ancestorsChildren: Record<string, Partial<IPageHasId>[]>, rootNode: ItemNode): ItemNode => {
   const paths = Object.keys(ancestorsChildren);
 
   let currentNode = rootNode;

+ 2 - 2
packages/app/src/interfaces/page.ts

@@ -31,6 +31,6 @@ export type IPage = {
   deletedAt: Date,
 }
 
-export type IPageForItem = Partial<IPage & {isTarget?: boolean} & HasObjectId>;
-
 export type IPageHasId = IPage & HasObjectId;
+
+export type IPageForItem = Partial<IPageHasId & {isTarget?: boolean}>;

+ 6 - 7
packages/app/src/stores/page.tsx

@@ -1,16 +1,15 @@
 import useSWR, { SWRResponse } from 'swr';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
-import { HasObjectId } from '~/interfaces/has-object-id';
 
-import { IPage } from '~/interfaces/page';
+import { IPageHasId } from '~/interfaces/page';
 import { IPagingResult } from '~/interfaces/paging-result';
 import { apiGet } from '../client/util/apiv1-client';
 
 import { IPageTagsInfo } from '../interfaces/pageTagsInfo';
 import { IPageInfo } from '../interfaces/page-info';
 
-export const useSWRxPageByPath = (path: string, initialData?: IPage): SWRResponse<IPage & HasObjectId, Error> => {
+export const useSWRxPageByPath = (path: string, initialData?: IPageHasId): SWRResponse<IPageHasId, Error> => {
   return useSWR(
     ['/page', path],
     (endpoint, path) => apiv3Get(endpoint, { path }).then(result => result.data.page),
@@ -22,10 +21,10 @@ export const useSWRxPageByPath = (path: string, initialData?: IPage): SWRRespons
 
 
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const useSWRxRecentlyUpdated = (): SWRResponse<(IPage & HasObjectId)[], Error> => {
+export const useSWRxRecentlyUpdated = (): SWRResponse<(IPageHasId)[], Error> => {
   return useSWR(
     '/pages/recent',
-    endpoint => apiv3Get<{ pages:(IPage & HasObjectId)[] }>(endpoint).then(response => response.data?.pages),
+    endpoint => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
   );
 };
 
@@ -33,11 +32,11 @@ export const useSWRxRecentlyUpdated = (): SWRResponse<(IPage & HasObjectId)[], E
 export const useSWRxPageList = (
     path: string,
     pageNumber?: number,
-): SWRResponse<IPagingResult<IPage>, Error> => {
+): SWRResponse<IPagingResult<IPageHasId>, Error> => {
   const page = pageNumber || 1;
   return useSWR(
     `/pages/list?path=${path}&page=${page}`,
-    endpoint => apiv3Get<{pages: IPage[], totalCount: number, limit: number}>(endpoint).then((response) => {
+    endpoint => apiv3Get<{pages: IPageHasId[], totalCount: number, limit: number}>(endpoint).then((response) => {
       return {
         items: response.data.pages,
         totalCount: response.data.totalCount,