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

Merge branch 'master' into fix/88635-show-presentation-modal-in-top-page

Shun Miyazawa пре 4 година
родитељ
комит
f5d55725c7

+ 20 - 18
packages/app/src/components/PrivateLegacyPages.tsx

@@ -12,7 +12,7 @@ import AppContainer from '~/client/services/AppContainer';
 import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
 import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
 import { toastSuccess } from '~/client/util/apiNotification';
 import { toastSuccess } from '~/client/util/apiNotification';
 import {
 import {
-  ISearchConfigurations, useSWRxNamedQuerySearch,
+  useSWRxNamedQuerySearch,
 } from '~/stores/search';
 } from '~/stores/search';
 import {
 import {
   ILegacyPrivatePage, useLegacyPrivatePagesMigrationModal,
   ILegacyPrivatePage, useLegacyPrivatePagesMigrationModal,
@@ -125,17 +125,17 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
   } = props;
   } = props;
 
 
 
 
-  const [configurationsByPagination, setConfigurationsByPagination] = useState<Partial<ISearchConfigurations>>({
-    limit: INITIAL_PAGIONG_SIZE,
-  });
+  const [offset, setOffset] = useState<number>(0);
+  const [limit, setLimit] = useState<number>(INITIAL_PAGIONG_SIZE);
+
   const [isControlEnabled, setControlEnabled] = useState(false);
   const [isControlEnabled, setControlEnabled] = useState(false);
 
 
   const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
   const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
   const searchPageBaseRef = useRef<ISelectableAll & IReturnSelectedPageIds|null>(null);
   const searchPageBaseRef = useRef<ISelectableAll & IReturnSelectedPageIds|null>(null);
 
 
   const { data, conditions, mutate } = useSWRxNamedQuerySearch('PrivateLegacyPages', {
   const { data, conditions, mutate } = useSWRxNamedQuerySearch('PrivateLegacyPages', {
-    limit: INITIAL_PAGIONG_SIZE,
-    ...configurationsByPagination,
+    offset,
+    limit,
   });
   });
 
 
   const { open: openModal, close: closeModal } = useLegacyPrivatePagesMigrationModal();
   const { open: openModal, close: closeModal } = useLegacyPrivatePagesMigrationModal();
@@ -179,7 +179,7 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
   }, []);
   }, []);
 
 
   // for bulk deletion
   // for bulk deletion
-  const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate);
+  const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate());
 
 
   const convertMenuItemClickedHandler = useCallback(() => {
   const convertMenuItemClickedHandler = useCallback(() => {
     if (data == null) {
     if (data == null) {
@@ -211,16 +211,18 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
     );
     );
   }, [data, mutate, openModal, closeModal]);
   }, [data, mutate, openModal, closeModal]);
 
 
+  const pagingSizeChangedHandler = useCallback((pagingSize: number) => {
+    setOffset(0);
+    setLimit(pagingSize);
+    mutate();
+  }, [mutate]);
+
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
-    const currentLimit = configurationsByPagination.limit ?? INITIAL_PAGIONG_SIZE;
-    setConfigurationsByPagination({
-      ...configurationsByPagination,
-      offset: (activePage - 1) * currentLimit,
-    });
-  }, [configurationsByPagination]);
+    setOffset((activePage - 1) * limit);
+    mutate();
+  }, [limit, mutate]);
 
 
   const hitsCount = data?.meta.hitsCount;
   const hitsCount = data?.meta.hitsCount;
-  const { offset, limit } = conditions;
 
 
   const searchControl = useMemo(() => {
   const searchControl = useMemo(() => {
     const isCheckboxDisabled = hitsCount === 0;
     const isCheckboxDisabled = hitsCount === 0;
@@ -267,10 +269,10 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
         searchResult={data}
         searchResult={data}
         offset={offset}
         offset={offset}
         pagingSize={limit}
         pagingSize={limit}
-        onPagingSizeChanged={() => {}}
+        onPagingSizeChanged={pagingSizeChangedHandler}
       />
       />
     );
     );
-  }, [data, limit, offset]);
+  }, [data, limit, offset, pagingSizeChangedHandler]);
 
 
   const searchPager = useMemo(() => {
   const searchPager = useMemo(() => {
     // when pager is not needed
     // when pager is not needed
@@ -285,11 +287,11 @@ export const PrivateLegacyPages = (props: Props): JSX.Element => {
       <PaginationWrapper
       <PaginationWrapper
         activePage={Math.floor(offset / limit) + 1}
         activePage={Math.floor(offset / limit) + 1}
         totalItemsCount={total}
         totalItemsCount={total}
-        pagingLimit={configurationsByPagination?.limit}
+        pagingLimit={limit}
         changePage={pagingNumberChangedHandler}
         changePage={pagingNumberChangedHandler}
       />
       />
     );
     );
-  }, [conditions, configurationsByPagination?.limit, data, pagingNumberChangedHandler]);
+  }, [conditions, data, pagingNumberChangedHandler]);
 
 
   return (
   return (
     <>
     <>

+ 13 - 21
packages/app/src/components/SearchPage.tsx

@@ -109,10 +109,9 @@ export const SearchPage = (props: Props): JSX.Element => {
   const initQ = (Array.isArray(parsedQueries) ? parsedQueries.join(' ') : parsedQueries) ?? '';
   const initQ = (Array.isArray(parsedQueries) ? parsedQueries.join(' ') : parsedQueries) ?? '';
 
 
   const [keyword, setKeyword] = useState<string>(initQ);
   const [keyword, setKeyword] = useState<string>(initQ);
+  const [offset, setOffset] = useState<number>(0);
+  const [limit, setLimit] = useState<number>(INITIAL_PAGIONG_SIZE);
   const [configurationsByControl, setConfigurationsByControl] = useState<Partial<ISearchConfigurations>>({});
   const [configurationsByControl, setConfigurationsByControl] = useState<Partial<ISearchConfigurations>>({});
-  const [configurationsByPagination, setConfigurationsByPagination] = useState<Partial<ISearchConfigurations>>({
-    limit: INITIAL_PAGIONG_SIZE,
-  });
 
 
   const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
   const selectAllControlRef = useRef<ISelectableAndIndeterminatable|null>(null);
   const searchPageBaseRef = useRef<ISelectableAll & IReturnSelectedPageIds|null>(null);
   const searchPageBaseRef = useRef<ISelectableAll & IReturnSelectedPageIds|null>(null);
@@ -120,13 +119,14 @@ export const SearchPage = (props: Props): JSX.Element => {
   const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
   const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
 
 
   const { data, conditions, mutate } = useSWRxFullTextSearch(keyword, {
   const { data, conditions, mutate } = useSWRxFullTextSearch(keyword, {
-    limit: INITIAL_PAGIONG_SIZE,
     ...configurationsByControl,
     ...configurationsByControl,
-    ...configurationsByPagination,
+    offset,
+    limit,
   });
   });
 
 
   const searchInvokedHandler = useCallback((_keyword: string, newConfigurations: Partial<ISearchConfigurations>) => {
   const searchInvokedHandler = useCallback((_keyword: string, newConfigurations: Partial<ISearchConfigurations>) => {
     setKeyword(_keyword);
     setKeyword(_keyword);
+    setOffset(0);
     setConfigurationsByControl(newConfigurations);
     setConfigurationsByControl(newConfigurations);
   }, []);
   }, []);
 
 
@@ -164,21 +164,15 @@ export const SearchPage = (props: Props): JSX.Element => {
   }, []);
   }, []);
 
 
   const pagingSizeChangedHandler = useCallback((pagingSize: number) => {
   const pagingSizeChangedHandler = useCallback((pagingSize: number) => {
-    setConfigurationsByPagination({
-      ...configurationsByPagination,
-      limit: pagingSize,
-    });
+    setOffset(0);
+    setLimit(pagingSize);
     mutate();
     mutate();
-  }, [configurationsByPagination, mutate]);
+  }, [mutate]);
 
 
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
   const pagingNumberChangedHandler = useCallback((activePage: number) => {
-    const currentLimit = configurationsByPagination.limit ?? INITIAL_PAGIONG_SIZE;
-    setConfigurationsByPagination({
-      ...configurationsByPagination,
-      offset: (activePage - 1) * currentLimit,
-    });
+    setOffset((activePage - 1) * limit);
     mutate();
     mutate();
-  }, [configurationsByPagination, mutate]);
+  }, [limit, mutate]);
 
 
   const initialSearchConditions: Partial<ISearchConditions> = useMemo(() => {
   const initialSearchConditions: Partial<ISearchConditions> = useMemo(() => {
     return {
     return {
@@ -188,7 +182,7 @@ export const SearchPage = (props: Props): JSX.Element => {
   }, [initQ]);
   }, [initQ]);
 
 
   // for bulk deletion
   // for bulk deletion
-  const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate);
+  const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate());
 
 
   // push state
   // push state
   useEffect(() => {
   useEffect(() => {
@@ -198,8 +192,6 @@ export const SearchPage = (props: Props): JSX.Element => {
   }, [keyword]);
   }, [keyword]);
   const hitsCount = data?.meta.hitsCount;
   const hitsCount = data?.meta.hitsCount;
 
 
-  const { offset, limit } = conditions;
-
   const deleteAllControl = useMemo(() => {
   const deleteAllControl = useMemo(() => {
     const isDisabled = hitsCount === 0;
     const isDisabled = hitsCount === 0;
 
 
@@ -265,11 +257,11 @@ export const SearchPage = (props: Props): JSX.Element => {
       <PaginationWrapper
       <PaginationWrapper
         activePage={Math.floor(offset / limit) + 1}
         activePage={Math.floor(offset / limit) + 1}
         totalItemsCount={total}
         totalItemsCount={total}
-        pagingLimit={configurationsByPagination?.limit}
+        pagingLimit={limit}
         changePage={pagingNumberChangedHandler}
         changePage={pagingNumberChangedHandler}
       />
       />
     );
     );
-  }, [conditions, configurationsByPagination?.limit, data, pagingNumberChangedHandler]);
+  }, [conditions, data, pagingNumberChangedHandler]);
 
 
   return (
   return (
     <SearchPageBase
     <SearchPageBase

+ 12 - 3
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -26,6 +26,7 @@ interface ItemProps {
   isEnableActions: boolean
   isEnableActions: boolean
   itemNode: ItemNode
   itemNode: ItemNode
   targetPathOrId?: string
   targetPathOrId?: string
+  isScrolled: boolean,
   isOpen?: boolean
   isOpen?: boolean
   isEnabledAttachTitleHeader?: boolean
   isEnabledAttachTitleHeader?: boolean
   onClickDuplicateMenuItem?(pageToDuplicate: IPageForPageDuplicateModal): void
   onClickDuplicateMenuItem?(pageToDuplicate: IPageForPageDuplicateModal): void
@@ -317,6 +318,12 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     return null;
     return null;
   };
   };
 
 
+  useEffect(() => {
+    if (!props.isScrolled && page.isTarget) {
+      document.dispatchEvent(new CustomEvent('targetItemRendered'));
+    }
+  }, [props.isScrolled, page.isTarget]);
+
   // didMount
   // didMount
   useEffect(() => {
   useEffect(() => {
     if (hasChildren()) setIsOpen(true);
     if (hasChildren()) setIsOpen(true);
@@ -348,6 +355,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
       <li
       <li
         ref={(c) => { drag(c); drop(c) }}
         ref={(c) => { drag(c); drop(c) }}
         className={`list-group-item list-group-item-action border-0 py-1 d-flex align-items-center ${page.isTarget ? 'grw-pagetree-is-target' : ''}`}
         className={`list-group-item list-group-item-action border-0 py-1 d-flex align-items-center ${page.isTarget ? 'grw-pagetree-is-target' : ''}`}
+        id={page.isTarget ? 'grw-pagetree-is-target' : `grw-pagetree-list-${page._id}`}
       >
       >
         <div className="grw-triangle-container d-flex justify-content-center">
         <div className="grw-triangle-container d-flex justify-content-center">
           {hasDescendants && (
           {hasDescendants && (
@@ -383,7 +391,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
             <ItemCount descendantCount={page.descendantCount} />
             <ItemCount descendantCount={page.descendantCount} />
           </div>
           </div>
         )}
         )}
-        <div className="grw-pagetree-control d-none">
+        <div className="grw-pagetree-control d-flex">
           <PageItemControl
           <PageItemControl
             pageId={page._id}
             pageId={page._id}
             isEnableActions={isEnableActions}
             isEnableActions={isEnableActions}
@@ -392,13 +400,13 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
             onClickRenameMenuItem={renameMenuItemClickHandler}
             onClickRenameMenuItem={renameMenuItemClickHandler}
             onClickDeleteMenuItem={deleteMenuItemClickHandler}
             onClickDeleteMenuItem={deleteMenuItemClickHandler}
           >
           >
-            <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0">
+            <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover">
               <i className="icon-options fa fa-rotate-90 text-muted p-1"></i>
               <i className="icon-options fa fa-rotate-90 text-muted p-1"></i>
             </DropdownToggle>
             </DropdownToggle>
           </PageItemControl>
           </PageItemControl>
           <button
           <button
             type="button"
             type="button"
-            className="border-0 rounded btn-page-item-control p-0"
+            className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover"
             onClick={onClickPlusButton}
             onClick={onClickPlusButton}
           >
           >
             <i className="icon-plus text-muted d-block p-1" />
             <i className="icon-plus text-muted d-block p-1" />
@@ -422,6 +430,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
               isEnableActions={isEnableActions}
               isEnableActions={isEnableActions}
               itemNode={node}
               itemNode={node}
               isOpen={false}
               isOpen={false}
+              isScrolled={props.isScrolled}
               targetPathOrId={targetPathOrId}
               targetPathOrId={targetPathOrId}
               isEnabledAttachTitleHeader={isEnabledAttachTitleHeader}
               isEnabledAttachTitleHeader={isEnabledAttachTitleHeader}
               onClickDuplicateMenuItem={onClickDuplicateMenuItem}
               onClickDuplicateMenuItem={onClickDuplicateMenuItem}

+ 32 - 10
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -1,4 +1,4 @@
-import React, { FC, useEffect } from 'react';
+import React, { FC, useEffect, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
 import { IPageHasId } from '../../../interfaces/page';
 import { IPageHasId } from '../../../interfaces/page';
@@ -66,6 +66,7 @@ type ItemsTreeProps = {
 const renderByInitialNode = (
 const renderByInitialNode = (
     initialNode: ItemNode,
     initialNode: ItemNode,
     isEnableActions: boolean,
     isEnableActions: boolean,
+    isScrolled: boolean,
     targetPathOrId?: string,
     targetPathOrId?: string,
     isEnabledAttachTitleHeader?: boolean,
     isEnabledAttachTitleHeader?: boolean,
     onClickDuplicateMenuItem?: (pageToDuplicate: IPageForPageDuplicateModal) => void,
     onClickDuplicateMenuItem?: (pageToDuplicate: IPageForPageDuplicateModal) => void,
@@ -85,11 +86,25 @@ const renderByInitialNode = (
         onClickDuplicateMenuItem={onClickDuplicateMenuItem}
         onClickDuplicateMenuItem={onClickDuplicateMenuItem}
         onClickRenameMenuItem={onClickRenameMenuItem}
         onClickRenameMenuItem={onClickRenameMenuItem}
         onClickDeleteMenuItem={onClickDeleteMenuItem}
         onClickDeleteMenuItem={onClickDeleteMenuItem}
+        isScrolled={isScrolled}
       />
       />
     </ul>
     </ul>
   );
   );
 };
 };
 
 
+// --- Auto scroll related vars and util ---
+
+const SCROLL_OFFSET_TOP = window.innerHeight / 2;
+
+const scrollTargetItem = () => {
+  const scrollElement = document.getElementById('grw-sidebar-contents-scroll-target');
+  const target = document.getElementById('grw-pagetree-is-target');
+  if (scrollElement != null && target != null) {
+    smoothScrollIntoView(target, SCROLL_OFFSET_TOP, scrollElement);
+  }
+};
+// --- end ---
+
 
 
 /*
 /*
  * ItemsTree
  * ItemsTree
@@ -107,20 +122,25 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
+  const [isScrolled, setIsScrolled] = useState(false);
+
 
 
   // for mutation
   // for mutation
   const { advance: advancePt } = usePageTreeTermManager();
   const { advance: advancePt } = usePageTreeTermManager();
   const { advance: advanceFts } = useFullTextSearchTermManager();
   const { advance: advanceFts } = useFullTextSearchTermManager();
   const { advance: advanceDpl } = useDescendantsPageListForCurrentPathTermManager();
   const { advance: advanceDpl } = useDescendantsPageListForCurrentPathTermManager();
 
 
+  const scrollItem = () => {
+    scrollTargetItem();
+    setIsScrolled(true);
+  };
+
   useEffect(() => {
   useEffect(() => {
-    const startFrom = document.getElementById('grw-sidebar-contents-scroll-target');
-    const targetElem = document.getElementsByClassName('grw-pagetree-is-target');
-    //  targetElem is HTML collection but only one HTML element in it all the time
-    if (targetElem[0] != null && startFrom != null) {
-      smoothScrollIntoView(targetElem[0] as HTMLElement, 0, startFrom);
-    }
-  }, [ancestorsChildrenData]);
+    document.addEventListener('targetItemRendered', scrollItem);
+    return () => {
+      document.removeEventListener('targetItemRendered', scrollItem);
+    };
+  }, []);
 
 
   const onClickDuplicateMenuItem = (pageToDuplicate: IPageForPageDuplicateModal) => {
   const onClickDuplicateMenuItem = (pageToDuplicate: IPageForPageDuplicateModal) => {
     openDuplicateModal(pageToDuplicate);
     openDuplicateModal(pageToDuplicate);
@@ -165,7 +185,8 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
   if (ancestorsChildrenData != null && rootPageData != null) {
   if (ancestorsChildrenData != null && rootPageData != null) {
     const initialNode = generateInitialNodeAfterResponse(ancestorsChildrenData.ancestorsChildren, new ItemNode(rootPageData.rootPage));
     const initialNode = generateInitialNodeAfterResponse(ancestorsChildrenData.ancestorsChildren, new ItemNode(rootPageData.rootPage));
     return renderByInitialNode(
     return renderByInitialNode(
-      initialNode, isEnableActions, targetPathOrId, isEnabledAttachTitleHeader, onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem,
+      // eslint-disable-next-line max-len
+      initialNode, isEnableActions, isScrolled, targetPathOrId, isEnabledAttachTitleHeader, onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem,
     );
     );
   }
   }
 
 
@@ -175,7 +196,8 @@ const ItemsTree: FC<ItemsTreeProps> = (props: ItemsTreeProps) => {
   if (targetAndAncestorsData != null) {
   if (targetAndAncestorsData != null) {
     const initialNode = generateInitialNodeBeforeResponse(targetAndAncestorsData.targetAndAncestors);
     const initialNode = generateInitialNodeBeforeResponse(targetAndAncestorsData.targetAndAncestors);
     return renderByInitialNode(
     return renderByInitialNode(
-      initialNode, isEnableActions, targetPathOrId, isEnabledAttachTitleHeader, onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem,
+      // eslint-disable-next-line max-len
+      initialNode, isEnableActions, isScrolled, targetPathOrId, isEnabledAttachTitleHeader, onClickDuplicateMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem,
     );
     );
   }
   }
 
 

+ 8 - 4
packages/app/src/server/service/search-delegator/private-legacy-pages.ts

@@ -32,10 +32,14 @@ class PrivateLegacyPagesDelegator implements SearchDelegator<IPage> {
     const Page = mongoose.model('Page') as unknown as PageModel;
     const Page = mongoose.model('Page') as unknown as PageModel;
     const { PageQueryBuilder } = Page;
     const { PageQueryBuilder } = Page;
 
 
-    const queryBuilder = new PageQueryBuilder(Page.find());
-    await queryBuilder.addConditionAsMigratablePages(user);
+    const countQueryBuilder = new PageQueryBuilder(Page.find());
+    await countQueryBuilder.addConditionAsMigratablePages(user);
+    const findQueryBuilder = new PageQueryBuilder(Page.find());
+    await findQueryBuilder.addConditionAsMigratablePages(user);
 
 
-    const _pages: PageDocument[] = await queryBuilder
+    const total = await countQueryBuilder.query.count();
+
+    const _pages: PageDocument[] = await findQueryBuilder
       .addConditionToPagenate(offset, limit)
       .addConditionToPagenate(offset, limit)
       .query
       .query
       .populate('lastUpdateUser')
       .populate('lastUpdateUser')
@@ -49,7 +53,7 @@ class PrivateLegacyPagesDelegator implements SearchDelegator<IPage> {
     return {
     return {
       data: pages,
       data: pages,
       meta: {
       meta: {
-        total: pages.length,
+        total,
         hitsCount: pages.length,
         hitsCount: pages.length,
       },
       },
     };
     };

+ 6 - 2
packages/app/src/styles/_page-tree.scss

@@ -6,9 +6,13 @@ $grw-pagetree-item-padding-left: 10px;
   min-height: calc(100vh - ($grw-navbar-height + $grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
   min-height: calc(100vh - ($grw-navbar-height + $grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
 
 
   .list-group-item {
   .list-group-item {
+    .grw-visible-on-hover {
+      display: none;
+    }
+
     &:hover {
     &:hover {
-      .grw-pagetree-control {
-        display: flex !important;
+      .grw-visible-on-hover {
+        display: block;
       }
       }
 
 
       .grw-pagetree-count {
       .grw-pagetree-count {