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

Merge pull request #8802 from weseek/imprv/140987-145787-display-selected-TreeItem-in-PageSelectModal-as-active

imprv: Display selected tree item in page select modal as active
Yuki Takei 1 год назад
Родитель
Сommit
2847e109a9

+ 12 - 10
apps/app/src/components/PageSelectModal/PageSelectModal.tsx

@@ -1,8 +1,11 @@
 import type { FC } from 'react';
 import type { FC } from 'react';
-import { Suspense, useState, useCallback } from 'react';
+import {
+  Suspense, useState, useCallback, useEffect,
+} from 'react';
 
 
 import nodePath from 'path';
 import nodePath from 'path';
 
 
+import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
   Modal, ModalHeader, ModalBody, ModalFooter, Button,
   Modal, ModalHeader, ModalBody, ModalFooter, Button,
@@ -11,7 +14,7 @@ import {
 import type { IPageForItem } from '~/interfaces/page';
 import type { IPageForItem } from '~/interfaces/page';
 import { useTargetAndAncestors, useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 import { useTargetAndAncestors, useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 import { usePageSelectModal } from '~/stores/modal';
 import { usePageSelectModal } from '~/stores/modal';
-import { useCurrentPagePath, useCurrentPageId, useSWRxCurrentPage } from '~/stores/page';
+import { useSWRxCurrentPage } from '~/stores/page';
 
 
 import { ItemsTree } from '../ItemsTree';
 import { ItemsTree } from '../ItemsTree';
 import ItemsTreeContentSkeleton from '../ItemsTree/ItemsTreeContentSkeleton';
 import ItemsTreeContentSkeleton from '../ItemsTree/ItemsTreeContentSkeleton';
@@ -19,7 +22,6 @@ import { usePagePathRenameHandler } from '../PageEditor/page-path-rename-utils';
 
 
 import { TreeItemForModal } from './TreeItemForModal';
 import { TreeItemForModal } from './TreeItemForModal';
 
 
-
 export const PageSelectModal: FC = () => {
 export const PageSelectModal: FC = () => {
   const {
   const {
     data: PageSelectModalData,
     data: PageSelectModalData,
@@ -34,8 +36,6 @@ export const PageSelectModal: FC = () => {
 
 
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
-  const { data: currentPath } = useCurrentPagePath();
-  const { data: targetId } = useCurrentPageId();
   const { data: targetAndAncestorsData } = useTargetAndAncestors();
   const { data: targetAndAncestorsData } = useTargetAndAncestors();
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: currentPage } = useSWRxCurrentPage();
 
 
@@ -45,7 +45,7 @@ export const PageSelectModal: FC = () => {
     const parentPagePath = page.path;
     const parentPagePath = page.path;
 
 
     if (parentPagePath == null) {
     if (parentPagePath == null) {
-      return;
+      return <></>;
     }
     }
 
 
     setClickedParentPagePath(parentPagePath);
     setClickedParentPagePath(parentPagePath);
@@ -67,12 +67,14 @@ export const PageSelectModal: FC = () => {
     closeModal();
     closeModal();
   }, [clickedParentPagePath, closeModal, currentPage?.path, pagePathRenameHandler]);
   }, [clickedParentPagePath, closeModal, currentPage?.path, pagePathRenameHandler]);
 
 
-  const targetPathOrId = targetId || currentPath;
+  const parentPagePath = pathUtils.addTrailingSlash(nodePath.dirname(currentPage?.path ?? ''));
+
+  const targetPathOrId = clickedParentPagePath || parentPagePath;
 
 
-  const path = currentPath || '/';
+  const targetPath = clickedParentPagePath || parentPagePath;
 
 
   if (isGuestUser == null) {
   if (isGuestUser == null) {
-    return null;
+    return <></>;
   }
   }
 
 
   return (
   return (
@@ -89,7 +91,7 @@ export const PageSelectModal: FC = () => {
             CustomTreeItem={TreeItemForModal}
             CustomTreeItem={TreeItemForModal}
             isEnableActions={!isGuestUser}
             isEnableActions={!isGuestUser}
             isReadOnlyUser={!!isReadOnlyUser}
             isReadOnlyUser={!!isReadOnlyUser}
-            targetPath={path}
+            targetPath={targetPath}
             targetPathOrId={targetPathOrId}
             targetPathOrId={targetPathOrId}
             targetAndAncestorsData={targetAndAncestorsData}
             targetAndAncestorsData={targetAndAncestorsData}
             onClickTreeItem={onClickTreeItem}
             onClickTreeItem={onClickTreeItem}

+ 13 - 15
apps/app/src/components/PageSelectModal/TreeItemForModal.tsx

@@ -10,33 +10,31 @@ import styles from './TreeItemForModal.module.scss';
 const moduleClass = styles['tree-item-for-modal'];
 const moduleClass = styles['tree-item-for-modal'];
 
 
 
 
-type PageTreeItemProps = TreeItemProps & {
+type TreeItemForModalProps = TreeItemProps & {
   key?: React.Key | null,
   key?: React.Key | null,
 };
 };
 
 
-export const TreeItemForModal: FC<PageTreeItemProps> = (props) => {
+export const TreeItemForModal: FC<TreeItemForModalProps> = (props) => {
 
 
-  const { isOpen, onClick } = props;
+  const { itemNode, targetPathOrId } = props;
+  const { page } = itemNode;
 
 
   const { Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
   const { Input: NewPageInput, CreateButton: NewPageCreateButton } = useNewPageInput();
 
 
+  const isSelected = page._id === targetPathOrId || page.path === targetPathOrId;
+
+  const itemClassNames = [
+    isSelected ? 'active' : '',
+  ];
+
   return (
   return (
     <TreeItemLayout
     <TreeItemLayout
-      key={props.key}
+      {...props}
       className={moduleClass}
       className={moduleClass}
-      targetPathOrId={props.targetPathOrId}
-      itemLevel={props.itemLevel}
-      itemNode={props.itemNode}
-      isOpen={isOpen}
-      isEnableActions={props.isEnableActions}
-      isReadOnlyUser={props.isReadOnlyUser}
-      onClickDuplicateMenuItem={props.onClickDuplicateMenuItem}
-      onClickDeleteMenuItem={props.onClickDeleteMenuItem}
-      onRenamed={props.onRenamed}
-      customHeadOfChildrenComponents={[NewPageInput]}
       itemClass={TreeItemForModal}
       itemClass={TreeItemForModal}
+      itemClassName={itemClassNames.join(' ')}
+      customHeadOfChildrenComponents={[NewPageInput]}
       customHoveredEndComponents={[NewPageCreateButton]}
       customHoveredEndComponents={[NewPageCreateButton]}
-      onClick={onClick}
     />
     />
   );
   );
 };
 };

+ 0 - 1
apps/app/src/components/Sidebar/PageTree/PageTreeSubstance.tsx

@@ -175,7 +175,6 @@ export const PageTreeContent = memo(({ isWipPageShown }: PageTreeContentProps) =
     return null;
     return null;
   }
   }
 
 
-
   return (
   return (
     <div ref={rootElemRef} className="pt-4">
     <div ref={rootElemRef} className="pt-4">
       <ItemsTree
       <ItemsTree

+ 8 - 3
apps/app/src/components/Sidebar/PageTreeItem/PageTreeItem.tsx

@@ -26,6 +26,7 @@ import { CountBadgeForPageTreeItem } from './CountBadgeForPageTreeItem';
 import { CreatingNewPageSpinner } from './CreatingNewPageSpinner';
 import { CreatingNewPageSpinner } from './CreatingNewPageSpinner';
 import { usePageItemControl } from './use-page-item-control';
 import { usePageItemControl } from './use-page-item-control';
 
 
+
 import styles from './PageTreeItem.module.scss';
 import styles from './PageTreeItem.module.scss';
 
 
 const moduleClass = styles['page-tree-item'] ?? '';
 const moduleClass = styles['page-tree-item'] ?? '';
@@ -56,7 +57,7 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const {
   const {
-    itemNode, isOpen: _isOpen = false, onRenamed,
+    itemNode, targetPathOrId, isOpen: _isOpen = false, onRenamed,
   } = props;
   } = props;
 
 
   const { page } = itemNode;
   const { page } = itemNode;
@@ -166,7 +167,11 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
     drop(c);
     drop(c);
   };
   };
 
 
-  const itemClassName = `${isOver ? 'drag-over' : ''}`;
+  const isSelected = page._id === targetPathOrId || page.path === targetPathOrId;
+  const itemClassNames = [
+    isOver ? 'drag-over' : '',
+    page.path !== '/' && isSelected ? 'active' : '', // set 'active' except the root page
+  ];
 
 
   return (
   return (
     <TreeItemLayout
     <TreeItemLayout
@@ -184,7 +189,7 @@ export const PageTreeItem: FC<TreeItemProps> = (props) => {
       onRenamed={props.onRenamed}
       onRenamed={props.onRenamed}
       itemRef={itemRef}
       itemRef={itemRef}
       itemClass={PageTreeItem}
       itemClass={PageTreeItem}
-      itemClassName={itemClassName}
+      itemClassName={itemClassNames.join(' ')}
       customEndComponents={[CountBadgeForPageTreeItem]}
       customEndComponents={[CountBadgeForPageTreeItem]}
       customHoveredEndComponents={[Control, NewPageCreateButton]}
       customHoveredEndComponents={[Control, NewPageCreateButton]}
       customHeadOfChildrenComponents={[NewPageInput, () => <CreatingNewPageSpinner show={isProcessingSubmission} />]}
       customHeadOfChildrenComponents={[NewPageInput, () => <CreatingNewPageSpinner show={isProcessingSubmission} />]}

+ 8 - 27
apps/app/src/components/TreeItem/TreeItemLayout.tsx

@@ -1,10 +1,8 @@
 import React, {
 import React, {
-  useCallback, useState, useEffect,
+  useCallback, useState, useEffect, useMemo,
   type FC, type RefObject, type RefCallback, type MouseEvent,
   type FC, type RefObject, type RefCallback, type MouseEvent,
 } from 'react';
 } from 'react';
 
 
-import type { Nullable } from '@growi/core';
-
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { usePageTreeDescCountMap } from '~/stores/ui';
 import { usePageTreeDescCountMap } from '~/stores/ui';
 
 
@@ -18,24 +16,6 @@ import styles from './TreeItemLayout.module.scss';
 const moduleClass = styles['tree-item-layout'] ?? '';
 const moduleClass = styles['tree-item-layout'] ?? '';
 
 
 
 
-// Utility to mark target
-const markTarget = (children: ItemNode[], targetPathOrId?: Nullable<string>): void => {
-  if (targetPathOrId == null) {
-    return;
-  }
-
-  children.forEach((node) => {
-    if (node.page._id === targetPathOrId || node.page.path === targetPathOrId) {
-      node.page.isTarget = true;
-    }
-    else {
-      node.page.isTarget = false;
-    }
-    return node;
-  });
-};
-
-
 type TreeItemLayoutProps = TreeItemProps & {
 type TreeItemLayoutProps = TreeItemProps & {
   className?: string,
   className?: string,
   itemRef?: RefObject<any> | RefCallback<any>,
   itemRef?: RefObject<any> | RefCallback<any>,
@@ -98,7 +78,6 @@ export const TreeItemLayout: FC<TreeItemLayoutProps> = (props) => {
    */
    */
   useEffect(() => {
   useEffect(() => {
     if (children.length > currentChildren.length) {
     if (children.length > currentChildren.length) {
-      markTarget(children, targetPathOrId);
       setCurrentChildren(children);
       setCurrentChildren(children);
     }
     }
   }, [children, currentChildren.length, targetPathOrId]);
   }, [children, currentChildren.length, targetPathOrId]);
@@ -109,11 +88,14 @@ export const TreeItemLayout: FC<TreeItemLayoutProps> = (props) => {
   useEffect(() => {
   useEffect(() => {
     if (isOpen && data != null) {
     if (isOpen && data != null) {
       const newChildren = ItemNode.generateNodesFromPages(data.children);
       const newChildren = ItemNode.generateNodesFromPages(data.children);
-      markTarget(newChildren, targetPathOrId);
       setCurrentChildren(newChildren);
       setCurrentChildren(newChildren);
     }
     }
   }, [data, isOpen, targetPathOrId]);
   }, [data, isOpen, targetPathOrId]);
 
 
+  const isSelected = useMemo(() => {
+    return page._id === targetPathOrId || page.path === targetPathOrId;
+  }, [page, targetPathOrId]);
+
   const ItemClassFixed = itemClass ?? TreeItemLayout;
   const ItemClassFixed = itemClass ?? TreeItemLayout;
 
 
   const baseProps: Omit<TreeItemProps, 'itemLevel' | 'itemNode'> = {
   const baseProps: Omit<TreeItemProps, 'itemLevel' | 'itemNode'> = {
@@ -155,10 +137,9 @@ export const TreeItemLayout: FC<TreeItemLayoutProps> = (props) => {
       <li
       <li
         ref={itemRef}
         ref={itemRef}
         role="button"
         role="button"
-        className={`list-group-item ${itemClassName}
-          border-0 py-0 ps-0 d-flex align-items-center rounded-1
-          ${page.isTarget ? 'active' : 'list-group-item-action'}`}
-        id={page.isTarget ? 'grw-pagetree-current-page-item' : `grw-pagetree-list-${page._id}`}
+        className={`list-group-item list-group-item-action ${itemClassName}
+          border-0 py-0 ps-0 d-flex align-items-center rounded-1`}
+        id={isSelected ? 'grw-pagetree-current-page-item' : `grw-pagetree-list-${page._id}`}
         onClick={itemClickHandler}
         onClick={itemClickHandler}
       >
       >
 
 

+ 1 - 1
apps/app/src/interfaces/page.ts

@@ -10,7 +10,7 @@ export {
   isIPageInfoForEntity, isIPageInfoForOperation, isIPageInfoForListing,
   isIPageInfoForEntity, isIPageInfoForOperation, isIPageInfoForListing,
 } from '@growi/core';
 } from '@growi/core';
 
 
-export type IPageForItem = Partial<IPageHasId & {isTarget?: boolean, processData?: IPageOperationProcessData}>;
+export type IPageForItem = Partial<IPageHasId & {processData?: IPageOperationProcessData}>;
 
 
 export const UserGroupPageGrantStatus = {
 export const UserGroupPageGrantStatus = {
   isGranted: 'isGranted',
   isGranted: 'isGranted',

+ 0 - 9
apps/app/src/server/service/page/index.ts

@@ -4396,7 +4396,6 @@ class PageService implements IPageService {
       .lean()
       .lean()
       .exec();
       .exec();
 
 
-    this.injectIsTargetIntoPages(pages, path);
     await this.injectProcessDataIntoPagesByActionTypes(pages, [PageActionType.Rename]);
     await this.injectProcessDataIntoPagesByActionTypes(pages, [PageActionType.Rename]);
 
 
     /*
     /*
@@ -4416,14 +4415,6 @@ class PageService implements IPageService {
     return pathToChildren;
     return pathToChildren;
   }
   }
 
 
-  private injectIsTargetIntoPages(pages: (PageDocument & {isTarget?: boolean})[], path): void {
-    pages.forEach((page) => {
-      if (page.path === path) {
-        page.isTarget = true;
-      }
-    });
-  }
-
   /**
   /**
    * Inject processData into page docuements
    * Inject processData into page docuements
    * The processData is a combination of actionType as a key and information on whether the action is processable as a value.
    * The processData is a combination of actionType as a key and information on whether the action is processable as a value.