|
@@ -3,30 +3,35 @@ import {
|
|
|
Suspense, useState, useCallback, useMemo,
|
|
Suspense, useState, useCallback, useMemo,
|
|
|
} from 'react';
|
|
} from 'react';
|
|
|
|
|
|
|
|
-import nodePath from 'path';
|
|
|
|
|
-
|
|
|
|
|
-import { pathUtils } from '@growi/core/dist/utils';
|
|
|
|
|
import { useTranslation } from 'next-i18next';
|
|
import { useTranslation } from 'next-i18next';
|
|
|
|
|
+import { dirname } from 'pathe';
|
|
|
import {
|
|
import {
|
|
|
Modal, ModalHeader, ModalBody, ModalFooter, Button,
|
|
Modal, ModalHeader, ModalBody, ModalFooter, Button,
|
|
|
} from 'reactstrap';
|
|
} from 'reactstrap';
|
|
|
-import SimpleBar from 'simplebar-react';
|
|
|
|
|
|
|
|
|
|
-import type { IPageForItem } from '~/interfaces/page';
|
|
|
|
|
|
|
+import { SimplifiedItemsTree } from '~/features/page-tree/components';
|
|
|
import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
|
|
import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
|
|
|
import { useCurrentPageData } from '~/states/page';
|
|
import { useCurrentPageData } from '~/states/page';
|
|
|
-import { usePageSelectModalStatus, usePageSelectModalActions } from '~/states/ui/modal/page-select';
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ usePageSelectModalStatus,
|
|
|
|
|
+ usePageSelectModalActions,
|
|
|
|
|
+ useSelectedPageInModal,
|
|
|
|
|
+} from '~/states/ui/modal/page-select';
|
|
|
|
|
|
|
|
-import { ItemsTree } from '../ItemsTree';
|
|
|
|
|
import ItemsTreeContentSkeleton from '../ItemsTree/ItemsTreeContentSkeleton';
|
|
import ItemsTreeContentSkeleton from '../ItemsTree/ItemsTreeContentSkeleton';
|
|
|
|
|
|
|
|
-import { TreeItemForModal } from './TreeItemForModal';
|
|
|
|
|
|
|
+import { SimplifiedTreeItemForModal, simplifiedTreeItemForModalSize } from './SimplifiedTreeItemForModal';
|
|
|
|
|
|
|
|
const PageSelectModalSubstance: FC = () => {
|
|
const PageSelectModalSubstance: FC = () => {
|
|
|
const { close: closeModal } = usePageSelectModalActions();
|
|
const { close: closeModal } = usePageSelectModalActions();
|
|
|
|
|
|
|
|
- const [clickedParentPage, setClickedParentPage] = useState<IPageForItem | null>(null);
|
|
|
|
|
const [isIncludeSubPage, setIsIncludeSubPage] = useState(true);
|
|
const [isIncludeSubPage, setIsIncludeSubPage] = useState(true);
|
|
|
|
|
+ const [scrollerElem, setScrollerElem] = useState<HTMLDivElement | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ // Callback ref to capture the scroller element and trigger re-render
|
|
|
|
|
+ const scrollerRefCallback = useCallback((node: HTMLDivElement | null) => {
|
|
|
|
|
+ setScrollerElem(node);
|
|
|
|
|
+ }, []);
|
|
|
|
|
|
|
|
const { t } = useTranslation();
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
@@ -35,41 +40,35 @@ const PageSelectModalSubstance: FC = () => {
|
|
|
const currentPage = useCurrentPageData();
|
|
const currentPage = useCurrentPageData();
|
|
|
const { opts } = usePageSelectModalStatus();
|
|
const { opts } = usePageSelectModalStatus();
|
|
|
|
|
|
|
|
- const isHierarchicalSelectionMode = opts?.isHierarchicalSelectionMode ?? false;
|
|
|
|
|
-
|
|
|
|
|
- const onClickTreeItem = useCallback((page: IPageForItem) => {
|
|
|
|
|
- const parentPagePath = page.path;
|
|
|
|
|
|
|
+ // Get selected page from atom
|
|
|
|
|
+ const selectedPage = useSelectedPageInModal();
|
|
|
|
|
|
|
|
- if (parentPagePath == null) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- setClickedParentPage(page);
|
|
|
|
|
- }, []);
|
|
|
|
|
|
|
+ const isHierarchicalSelectionMode = opts?.isHierarchicalSelectionMode ?? false;
|
|
|
|
|
|
|
|
const onClickCancel = useCallback(() => {
|
|
const onClickCancel = useCallback(() => {
|
|
|
- setClickedParentPage(null);
|
|
|
|
|
closeModal();
|
|
closeModal();
|
|
|
}, [closeModal]);
|
|
}, [closeModal]);
|
|
|
|
|
|
|
|
const { onSelected } = opts ?? {};
|
|
const { onSelected } = opts ?? {};
|
|
|
const onClickDone = useCallback(() => {
|
|
const onClickDone = useCallback(() => {
|
|
|
- if (clickedParentPage != null) {
|
|
|
|
|
- onSelected?.(clickedParentPage, isIncludeSubPage);
|
|
|
|
|
|
|
+ if (selectedPage != null) {
|
|
|
|
|
+ onSelected?.(selectedPage, isIncludeSubPage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
closeModal();
|
|
closeModal();
|
|
|
- }, [clickedParentPage, closeModal, isIncludeSubPage, onSelected]);
|
|
|
|
|
|
|
+ }, [selectedPage, closeModal, isIncludeSubPage, onSelected]);
|
|
|
|
|
|
|
|
- // Memoize heavy calculation
|
|
|
|
|
- const parentPagePath = useMemo(() => (
|
|
|
|
|
- pathUtils.addTrailingSlash(nodePath.dirname(currentPage?.path ?? ''))
|
|
|
|
|
- ), [currentPage?.path]);
|
|
|
|
|
|
|
+ // Memoize heavy calculation - parent page path without trailing slash for matching
|
|
|
|
|
+ const parentPagePath = useMemo(() => {
|
|
|
|
|
+ const dn = dirname(currentPage?.path ?? '');
|
|
|
|
|
+ // Ensure root path is '/' not ''
|
|
|
|
|
+ return dn === '' ? '/' : dn;
|
|
|
|
|
+ }, [currentPage?.path]);
|
|
|
|
|
|
|
|
- // Memoize target path calculation (avoid duplication)
|
|
|
|
|
|
|
+ // Memoize target path calculation
|
|
|
const targetPath = useMemo(() => (
|
|
const targetPath = useMemo(() => (
|
|
|
- clickedParentPage?.path || parentPagePath
|
|
|
|
|
- ), [clickedParentPage?.path, parentPagePath]);
|
|
|
|
|
|
|
+ selectedPage?.path || parentPagePath
|
|
|
|
|
+ ), [selectedPage?.path, parentPagePath]);
|
|
|
|
|
|
|
|
// Memoize checkbox handler
|
|
// Memoize checkbox handler
|
|
|
const handleIncludeSubPageChange = useCallback(() => {
|
|
const handleIncludeSubPageChange = useCallback(() => {
|
|
@@ -85,18 +84,24 @@ const PageSelectModalSubstance: FC = () => {
|
|
|
<ModalHeader toggle={closeModal}>{t('page_select_modal.select_page_location')}</ModalHeader>
|
|
<ModalHeader toggle={closeModal}>{t('page_select_modal.select_page_location')}</ModalHeader>
|
|
|
<ModalBody className="p-0">
|
|
<ModalBody className="p-0">
|
|
|
<Suspense fallback={<ItemsTreeContentSkeleton />}>
|
|
<Suspense fallback={<ItemsTreeContentSkeleton />}>
|
|
|
- <SimpleBar style={{ maxHeight: 'calc(85vh - 133px)' }}> {/* 133px = 63px(ModalHeader) + 70px(ModalFooter) */}
|
|
|
|
|
- <div className="p-3">
|
|
|
|
|
- <ItemsTree
|
|
|
|
|
- CustomTreeItem={TreeItemForModal}
|
|
|
|
|
|
|
+ {/* 133px = 63px(ModalHeader) + 70px(ModalFooter) */}
|
|
|
|
|
+ <div
|
|
|
|
|
+ ref={scrollerRefCallback}
|
|
|
|
|
+ className="p-3"
|
|
|
|
|
+ style={{ maxHeight: 'calc(85vh - 133px)', overflowY: 'auto' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {scrollerElem && (
|
|
|
|
|
+ <SimplifiedItemsTree
|
|
|
|
|
+ CustomTreeItem={SimplifiedTreeItemForModal}
|
|
|
isEnableActions={!isGuestUser}
|
|
isEnableActions={!isGuestUser}
|
|
|
isReadOnlyUser={!!isReadOnlyUser}
|
|
isReadOnlyUser={!!isReadOnlyUser}
|
|
|
targetPath={targetPath}
|
|
targetPath={targetPath}
|
|
|
targetPathOrId={targetPath}
|
|
targetPathOrId={targetPath}
|
|
|
- onClickTreeItem={onClickTreeItem}
|
|
|
|
|
|
|
+ estimateTreeItemSize={() => simplifiedTreeItemForModalSize}
|
|
|
|
|
+ scrollerElem={scrollerElem}
|
|
|
/>
|
|
/>
|
|
|
- </div>
|
|
|
|
|
- </SimpleBar>
|
|
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
</Suspense>
|
|
</Suspense>
|
|
|
</ModalBody>
|
|
</ModalBody>
|
|
|
<ModalFooter className="border-top d-flex flex-column">
|
|
<ModalFooter className="border-top d-flex flex-column">
|