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

Merge pull request #8418 from weseek/fix/tree-item-click-handler

fix: Tree item click handler
Yuki Takei 2 лет назад
Родитель
Сommit
1a24e175cd

+ 3 - 3
apps/app/src/components/PageSelectModal/TreeItemForModal.tsx

@@ -1,7 +1,7 @@
 import React, { type FC } from 'react';
 
 import {
-  SimpleItem, SimpleItemTool, useNewPageInput, type TreeItemProps,
+  SimpleItem, useNewPageInput, type TreeItemProps,
 } from '../TreeItem';
 
 type PageTreeItemProps = TreeItemProps & {
@@ -21,12 +21,12 @@ export const TreeItemForModal: FC<PageTreeItemProps> = (props) => {
       isOpen={isOpen}
       isEnableActions={props.isEnableActions}
       isReadOnlyUser={props.isReadOnlyUser}
-      onRenamed={props.onRenamed}
       onClickDuplicateMenuItem={props.onClickDuplicateMenuItem}
       onClickDeleteMenuItem={props.onClickDeleteMenuItem}
+      onRenamed={props.onRenamed}
       customNextComponents={[NewPageInput]}
       itemClass={TreeItemForModal}
-      customEndComponents={[SimpleItemTool, NewPageCreateButton]}
+      customEndComponents={[NewPageCreateButton]}
     />
   );
 };

+ 18 - 2
apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx

@@ -6,12 +6,14 @@ import React, {
 import nodePath from 'path';
 
 import type { IPageHasId } from '@growi/core';
-import { pagePathUtils } from '@growi/core/dist/utils';
+import { pagePathUtils, pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
 import { useDrag, useDrop } from 'react-dnd';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastWarning, toastError } from '~/client/util/toastr';
+import type { IPageForItem } from '~/interfaces/page';
 import { mutatePageTree, useSWRxPageChildren } from '~/stores/page-listing';
 import loggerFactory from '~/utils/logger';
 
@@ -21,9 +23,12 @@ import {
 
 import { Ellipsis } from './Ellipsis';
 
+
 const logger = loggerFactory('growi:cli:Item');
 
 export const PageTreeItem: FC<TreeItemProps> = (props) => {
+  const router = useRouter();
+
   const getNewPathAfterMoved = (droppedPagePath: string, newParentPagePath: string): string => {
     const pageTitle = nodePath.basename(droppedPagePath);
     return nodePath.join(newParentPagePath, pageTitle);
@@ -53,6 +58,16 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
 
   const { mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
 
+  const itemSelectedHandler = useCallback((page: IPageForItem) => {
+    if (page.path == null || page._id == null) {
+      return;
+    }
+
+    const link = pathUtils.returnPathForURL(page.path, page._id);
+
+    router.push(link);
+  }, [router]);
+
   const displayDroppedItemByPageId = useCallback((pageId) => {
     const target = document.getElementById(`pagetree-item-${pageId}`);
     if (target == null) {
@@ -161,9 +176,10 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
       isOpen={isOpen}
       isEnableActions={props.isEnableActions}
       isReadOnlyUser={props.isReadOnlyUser}
-      onRenamed={props.onRenamed}
+      onClick={itemSelectedHandler}
       onClickDuplicateMenuItem={props.onClickDuplicateMenuItem}
       onClickDeleteMenuItem={props.onClickDeleteMenuItem}
+      onRenamed={props.onRenamed}
       itemRef={itemRef}
       itemClass={PageTreeItem}
       mainClassName={mainClassName}

+ 43 - 33
apps/app/src/components/TreeItem/SimpleItem.tsx

@@ -1,16 +1,15 @@
 import React, {
   useCallback, useState, useEffect,
-  type FC, type RefObject, type RefCallback,
+  type FC, type RefObject, type RefCallback, type MouseEvent,
 } from 'react';
 
 import nodePath from 'path';
 
 import type { Nullable } from '@growi/core';
-import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
-import { useRouter } from 'next/router';
 import { UncontrolledTooltip } from 'reactstrap';
 
+import type { IPageForItem } from '~/interfaces/page';
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { usePageTreeDescCountMap } from '~/stores/ui';
 import { shouldRecoverPagePaths } from '~/utils/page-operation';
@@ -40,34 +39,15 @@ const markTarget = (children: ItemNode[], targetPathOrId?: Nullable<string>): vo
 };
 
 
-export const SimpleItemTool: FC<TreeItemToolProps> = (props) => {
+const SimpleItemContent = ({ page }: { page: IPageForItem }) => {
   const { t } = useTranslation();
-  const router = useRouter();
-
-  const { getDescCount } = usePageTreeDescCountMap();
-
-  const { page } = props.itemNode;
 
   const pageName = nodePath.basename(page.path ?? '') || '/';
 
   const shouldShowAttentionIcon = page.processData != null ? shouldRecoverPagePaths(page.processData) : false;
 
-  const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
-
-  const pageTreeItemClickHandler = (e) => {
-    e.preventDefault();
-
-    if (page.path == null || page._id == null) {
-      return;
-    }
-
-    const link = pathUtils.returnPathForURL(page.path, page._id);
-
-    router.push(link);
-  };
-
   return (
-    <>
+    <div className="flex-grow-1 d-flex align-items-center pe-none">
       {shouldShowAttentionIcon && (
         <>
           <i id="path-recovery" className="fa fa-warning mr-2 text-warning"></i>
@@ -78,9 +58,22 @@ export const SimpleItemTool: FC<TreeItemToolProps> = (props) => {
       )}
       {page != null && page.path != null && page._id != null && (
         <div className="grw-pagetree-title-anchor flex-grow-1">
-          <p onClick={pageTreeItemClickHandler} className={`text-truncate m-auto ${page.isEmpty && 'grw-sidebar-text-muted'}`}>{pageName}</p>
+          <p className={`text-truncate m-auto ${page.isEmpty && 'grw-sidebar-text-muted'}`}>{pageName}</p>
         </div>
       )}
+    </div>
+  );
+};
+
+export const SimpleItemTool: FC<TreeItemToolProps> = (props) => {
+  const { getDescCount } = usePageTreeDescCountMap();
+
+  const { page } = props.itemNode;
+
+  const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
+
+  return (
+    <>
       {descendantCount > 0 && (
         <div className="grw-pagetree-count-wrapper">
           <CountBadge count={descendantCount} />
@@ -97,7 +90,7 @@ type SimpleItemProps = TreeItemProps & {
 export const SimpleItem: FC<SimpleItemProps> = (props) => {
   const {
     itemNode, targetPathOrId, isOpen: _isOpen = false,
-    onRenamed, onClickDuplicateMenuItem, onClickDeleteMenuItem, isEnableActions, isReadOnlyUser,
+    onRenamed, onClick, onClickDuplicateMenuItem, onClickDeleteMenuItem, isEnableActions, isReadOnlyUser,
     itemRef, itemClass, mainClassName,
   } = props;
 
@@ -110,11 +103,22 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
 
   const { data } = useSWRxPageChildren(isOpen ? page._id : null);
 
+
+  const itemClickHandler = useCallback((e: MouseEvent) => {
+    // DO NOT handle the event when e.currentTarget and e.target is different
+    if (e.target !== e.currentTarget) {
+      return;
+    }
+
+    onClick?.(page);
+
+  }, [onClick, page]);
+
+
   // descendantCount
   const { getDescCount } = usePageTreeDescCountMap();
   const descendantCount = getDescCount(page._id) || page.descendantCount || 0;
 
-
   // hasDescendants flag
   const isChildrenLoaded = currentChildren?.length > 0;
   const hasDescendants = descendantCount > 0 || isChildrenLoaded;
@@ -123,7 +127,7 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
     return currentChildren != null && currentChildren.length > 0;
   }, [currentChildren]);
 
-  const onClickLoadChildren = useCallback(async() => {
+  const onClickLoadChildren = useCallback(() => {
     setIsOpen(!isOpen);
   }, [isOpen]);
 
@@ -155,9 +159,7 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
 
   const ItemClassFixed = itemClass ?? SimpleItem;
 
-  const CustomEndComponents = props.customEndComponents;
-
-  const SimpleItemContent = CustomEndComponents ?? [SimpleItemTool];
+  const EndComponents = props.customEndComponents ?? [SimpleItemTool];
 
   const baseProps: Omit<TreeItemProps, 'itemNode'> = {
     isEnableActions,
@@ -185,10 +187,13 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
     >
       <li
         ref={itemRef}
+        role="button"
         className={`list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center
         ${page.isTarget ? 'grw-pagetree-current-page-item' : ''}`}
         id={page.isTarget ? 'grw-pagetree-current-page-item' : `grw-pagetree-list-${page._id}`}
+        onClick={itemClickHandler}
       >
+
         <div className="grw-triangle-container d-flex justify-content-center">
           {hasDescendants && (
             <button
@@ -202,10 +207,14 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
             </button>
           )}
         </div>
-        {SimpleItemContent.map((ItemContent, index) => (
+
+        <SimpleItemContent page={page} />
+
+        {EndComponents.map((EndComponent, index) => (
           // eslint-disable-next-line react/no-array-index-key
-          <ItemContent key={index} {...toolProps} />
+          <EndComponent key={index} {...toolProps} />
         ))}
+
       </li>
 
       {CustomNextComponents?.map((UnderItemContent, index) => (
@@ -220,6 +229,7 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
             itemNode: node,
             itemClass,
             mainClassName,
+            onClick,
           };
 
           return (

+ 3 - 1
apps/app/src/components/TreeItem/interfaces/index.ts

@@ -1,6 +1,7 @@
 import type { IPageToDeleteWithMeta } from '@growi/core';
 import type { Nullable } from 'vitest';
 
+import type { IPageForItem } from '~/interfaces/page';
 import type { IPageForPageDuplicateModal } from '~/stores/modal';
 
 import type { ItemNode } from '../ItemNode';
@@ -9,9 +10,9 @@ type TreeItemBaseProps = {
   itemNode: ItemNode,
   isEnableActions: boolean,
   isReadOnlyUser: boolean,
-  onRenamed?(fromPath: string | undefined, toPath: string): void,
   onClickDuplicateMenuItem?(pageToDuplicate: IPageForPageDuplicateModal): void,
   onClickDeleteMenuItem?(pageToDelete: IPageToDeleteWithMeta): void,
+  onRenamed?(fromPath: string | undefined, toPath: string): void,
   stateHandlers?: {
     isOpen: boolean,
     setIsOpen: React.Dispatch<React.SetStateAction<boolean>>,
@@ -27,4 +28,5 @@ export type TreeItemProps = TreeItemBaseProps & {
   mainClassName?: string,
   customEndComponents?: Array<React.FunctionComponent<TreeItemToolProps>>,
   customNextComponents?: Array<React.FunctionComponent<TreeItemToolProps>>,
+  onClick?(page: IPageForItem): void,
 };