Yuki Takei 3 лет назад
Родитель
Сommit
149e91d3ae

+ 6 - 4
packages/app/src/components/Sidebar/InfiniteScroll.tsx

@@ -1,11 +1,12 @@
 import React, {
   Ref, useEffect, useState,
 } from 'react';
+
 import type { SWRInfiniteResponse } from 'swr/infinite';
 
 type Props<T> = {
   swrInifiniteResponse : SWRInfiniteResponse<T>
-  children: React.ReactChild | ((item: T) => React.ReactNode),
+  children: React.ReactNode,
   loadingIndicator?: React.ReactNode
   endingIndicator?: React.ReactNode
   isReachingEnd?: boolean,
@@ -39,7 +40,7 @@ const LoadingIndicator = (): React.ReactElement => {
 const InfiniteScroll = <E, >(props: Props<E>): React.ReactElement<Props<E>> => {
   const {
     swrInifiniteResponse: {
-      setSize, data, isValidating,
+      setSize, isValidating,
     },
     children,
     loadingIndicator,
@@ -54,11 +55,12 @@ const InfiniteScroll = <E, >(props: Props<E>): React.ReactElement<Props<E>> => {
     if (intersecting && !isValidating && !isReachingEnd) {
       setSize(size => size + 1);
     }
-  }, [setSize, intersecting]);
+  }, [setSize, intersecting, isValidating, isReachingEnd]);
 
   return (
     <>
-      {typeof children === 'function' ? data?.map(item => children(item)) : children}
+      { children }
+
       <div style={{ position: 'relative' }}>
         <div ref={ref} style={{ position: 'absolute', top: offset }}></div>
         {isReachingEnd

+ 12 - 11
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -10,7 +10,7 @@ import Link from 'next/link';
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
 import { IPageHasId } from '~/interfaces/page';
 import LinkedPagePath from '~/models/linked-page-path';
-import { useSWRInifinitexRecentlyUpdated } from '~/stores/page-listing';
+import { useSWRINFxRecentlyUpdated } from '~/stores/page-listing';
 import loggerFactory from '~/utils/logger';
 
 import FormattedDistanceDate from '../FormattedDistanceDate';
@@ -19,7 +19,6 @@ import InfiniteScroll from './InfiniteScroll';
 import { SidebarHeaderReloadButton } from './SidebarHeaderReloadButton';
 import RecentChangesContentSkeleton from './Skeleton/RecentChangesContentSkeleton';
 
-import TagLabelsStyles from '../Page/TagLabels.module.scss';
 import styles from './RecentChanges.module.scss';
 
 
@@ -104,13 +103,14 @@ const RecentChanges = (): JSX.Element => {
 
   const PER_PAGE = 20;
   const { t } = useTranslation();
-  const swrInifinitexRecentlyUpdated = useSWRInifinitexRecentlyUpdated();
-  const { data: dataRecentlyUpdated, error, mutate: mutateRecentlyUpdated } = swrInifinitexRecentlyUpdated;
+  const swrInifinitexRecentlyUpdated = useSWRINFxRecentlyUpdated(PER_PAGE);
+  const {
+    data, mutate, isLoading,
+  } = swrInifinitexRecentlyUpdated;
 
   const [isRecentChangesSidebarSmall, setIsRecentChangesSidebarSmall] = useState(false);
-  const isEmpty = dataRecentlyUpdated?.[0].length === 0;
-  const isLoading = error == null && dataRecentlyUpdated === undefined;
-  const isReachingEnd = isEmpty || (dataRecentlyUpdated && dataRecentlyUpdated[dataRecentlyUpdated.length - 1]?.length < PER_PAGE);
+  const isEmpty = data?.[0]?.pages.length === 0;
+  const isReachingEnd = isEmpty || (data != null && data[data.length - 1]?.pages.length < PER_PAGE);
 
   const retrieveSizePreferenceFromLocalStorage = useCallback(() => {
     if (window.localStorage.isRecentChangesSidebarSmall === 'true') {
@@ -132,7 +132,7 @@ const RecentChanges = (): JSX.Element => {
     <div className="px-3" data-testid="grw-recent-changes">
       <div className="grw-sidebar-content-header py-3 d-flex">
         <h3 className="mb-0 text-nowrap">{t('Recent Changes')}</h3>
-        <SidebarHeaderReloadButton onClick={() => mutateRecentlyUpdated()}/>
+        <SidebarHeaderReloadButton onClick={() => mutate()}/>
         <div className="d-flex align-items-center">
           <div className={`grw-recent-changes-resize-button ${styles['grw-recent-changes-resize-button']} custom-control custom-switch ml-1`}>
             <input
@@ -155,9 +155,10 @@ const RecentChanges = (): JSX.Element => {
                 swrInifiniteResponse={swrInifinitexRecentlyUpdated}
                 isReachingEnd={isReachingEnd}
               >
-                {pages => pages.map(
-                  page => <PageItem key={page._id} page={page} isSmall={isRecentChangesSidebarSmall} />,
-                )
+                { data != null && data.map(apiResult => apiResult.pages).flat()
+                  .map(page => (
+                    <PageItem key={page._id} page={page} isSmall={isRecentChangesSidebarSmall} />
+                  ))
                 }
               </InfiniteScroll>
             </ul>

+ 2 - 3
packages/app/src/server/routes/apiv3/pages.js

@@ -379,11 +379,10 @@ module.exports = (crowi) => {
    *
    */
   router.get('/recent', accessTokenParser, loginRequired, async(req, res) => {
-    const limit = 20;
+    const limit = parseInt(req.query.limit) || 20;
     const offset = parseInt(req.query.offset) || 0;
-    const skip = offset > 0 ? (offset - 1) * limit : offset;
     const queryOptions = {
-      offset: skip,
+      offset,
       limit,
       includeTrashed: false,
       isRegExpEscapedFromPath: true,

+ 18 - 12
packages/app/src/stores/page-listing.tsx

@@ -24,19 +24,25 @@ export const useSWRxPagesByPath = (path?: Nullable<string>): SWRResponse<IPageHa
   );
 };
 
-export const useSWRxRecentlyUpdated = (): SWRResponse<(IPageHasId)[], Error> => {
-  return useSWR(
-    '/pages/recent',
-    endpoint => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
-  );
-};
-export const useSWRInifinitexRecentlyUpdated = () : SWRInfiniteResponse<(IPageHasId)[], Error> => {
-  const getKey = (page: number): string => {
-    return `/pages/recent?offset=${page + 1}`;
-  };
+
+type RecentApiResult = {
+  pages: IPageHasId[],
+  totalCount: number,
+  offset: number,
+}
+export const useSWRINFxRecentlyUpdated = (limit: number) : SWRInfiniteResponse<RecentApiResult, Error> => {
   return useSWRInfinite(
-    getKey,
-    endpoint => apiv3Get<{ pages:(IPageHasId)[] }>(endpoint).then(response => response.data?.pages),
+    (pageIndex, previousPageData) => {
+      if (previousPageData != null && previousPageData.pages.length === 0) return null;
+
+      if (pageIndex === 0 || previousPageData == null) {
+        return ['/pages/recent', undefined, limit];
+      }
+
+      const offset = previousPageData.offset + limit;
+      return ['/pages/recent', offset, limit];
+    },
+    ([endpoint, offset, limit]) => apiv3Get<RecentApiResult>(endpoint, { offset, limit }).then(response => response.data),
     {
       revalidateFirstPage: false,
       revalidateAll: false,