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

Merge branch 'feat/gw7841-implement-delete-from-bookmark-sidebar' into feat/gw7846-separate-bookmarkitem-from-bookmarks

Mudana-Grune 3 лет назад
Родитель
Сommit
80911da8ed

+ 4 - 2
packages/app/src/components/Navbar/SubNavButtons.tsx

@@ -7,7 +7,7 @@ import {
 import { useIsGuestUser } from '~/stores/context';
 import { IPageForPageDuplicateModal } from '~/stores/modal';
 
-import { useSWRBookmarkInfo } from '../../stores/bookmark';
+import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '../../stores/bookmark';
 import { useSWRxPageInfo } from '../../stores/page';
 import { useSWRxUsersList } from '../../stores/user';
 import BookmarkButtons from '../BookmarkButtons';
@@ -51,6 +51,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
   const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId, shareLinkId);
 
   const { data: bookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(pageId);
+  const { mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks();
 
   const likerIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.likerIds ?? []).slice(0, 15) : [];
   const seenUserIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.seenUserIds ?? []).slice(0, 15) : [];
@@ -95,7 +96,8 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
     await toggleBookmark(pageId, pageInfo.isBookmarked);
     mutatePageInfo();
     mutateBookmarkInfo();
-  }, [isGuestUser, mutateBookmarkInfo, mutatePageInfo, pageId, pageInfo]);
+    mutateCurrentUserBookmark();
+  }, [isGuestUser, mutateBookmarkInfo, mutatePageInfo, mutateCurrentUserBookmark, pageId, pageInfo]);
 
   const duplicateMenuItemClickHandler = useCallback(async(_pageId: string): Promise<void> => {
     if (onClickDuplicateMenuItem == null || path == null) {

+ 3 - 1
packages/app/src/components/PageList/PageListItemL.tsx

@@ -23,6 +23,7 @@ import {
   OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
 } from '~/interfaces/ui';
 import LinkedPagePath from '~/models/linked-page-path';
+import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import {
   usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageModal,
 } from '~/stores/modal';
@@ -85,7 +86,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
 
   const shouldFetch = isSelected && (pageData != null || pageMeta != null);
   const { data: pageInfo } = useSWRxPageInfo(shouldFetch ? pageData?._id : null);
-
+  const { mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks();
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
 
@@ -122,6 +123,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
   const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise<void> => {
     const bookmarkOperation = _newValue ? bookmark : unbookmark;
     await bookmarkOperation(_pageId);
+    mutateCurrentUserBookmark();
   };
 
   const duplicateMenuItemClickHandler = useCallback(() => {

+ 44 - 49
packages/app/src/components/Sidebar/Bookmarks.tsx

@@ -1,54 +1,62 @@
 
-import React, { useCallback, useEffect, useState } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'react-i18next';
+import { toastSuccess } from '~/client/util/apiNotification';
+import { IPageToDeleteWithMeta } from '~/interfaces/page';
+import { useIsGuestUser } from '~/stores/context';
+import { usePageDeleteModal } from '~/stores/modal';
+import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
+import { OnDeletedFunction } from '~/interfaces/ui';
 
-import { toastError } from '~/client/util/apiNotification';
-import { apiv3Get } from '~/client/util/apiv3-client';
-import { IPageHasId } from '~/interfaces/page';
-import { useCurrentUser, useIsGuestUser } from '~/stores/context';
-import loggerFactory from '~/utils/logger';
 
 import BookmarkItem from './Bookmarks/BookmarkItem';
 
-const logger = loggerFactory('growi:BookmarkList');
-// TODO: Remove pagination and apply  scrolling (not infinity)
-const ACTIVE_PAGE = 1;
 
 const Bookmarks = () : JSX.Element => {
   const { t } = useTranslation();
-  const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
-  const [pages, setPages] = useState<IPageHasId[]>([]);
-  const page = ACTIVE_PAGE;
+  const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
+  const { open: openDeleteModal } = usePageDeleteModal();
 
-  const getMyBookmarkList = useCallback(async() => {
-    try {
-      const res = await apiv3Get(`/bookmarks/${currentUser?._id}`, { page });
-      const { paginationResult } = res.data;
-      setPages(paginationResult.docs.map((page) => {
-        return {
-          ...page.page,
-        };
-      }));
-    }
-    catch (error) {
-      logger.error('failed to fetch data', error);
-      toastError(error, 'Error occurred in bookmark page list');
-    }
-  }, [currentUser, page]);
+  const onBookmarkItemDeleted = (pageToDelete: IPageToDeleteWithMeta):void => {
+    const onDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => {
+      if (typeof pathOrPathsToDelete !== 'string') {
+        return;
+      }
+      const path = pathOrPathsToDelete;
 
-  useEffect(() => {
-    getMyBookmarkList();
-  }, [getMyBookmarkList]);
+      if (isCompletely) {
+        toastSuccess(t('deleted_pages_completely', { path }));
+      }
+      else {
+        toastSuccess(t('deleted_pages', { path }));
+      }
+      mutateCurrentUserBookmarks();
+    };
+    openDeleteModal([pageToDelete], { onDeleted: onDeletedHandler });
+  };
 
-  const generateBookmarkList = () => {
+  const renderBookmarkList = () => {
+    if (currentUserBookmarksData?.length === 0) {
+      return (
+        <h4 className="pl-3">
+          { t('No bookmarks yet') }
+        </h4>
+      );
+    }
     return (
       <ul className="grw-bookmarks-list list-group p-3">
         <div className="grw-bookmarks-item-container">
-          { pages.map((page) => {
+          { currentUserBookmarksData?.map((currentUserBookmark) => {
             return (
-              <BookmarkItem key={page._id} page={page} refreshBookmarkList={getMyBookmarkList} />
+              <BookmarkItem
+                key={currentUserBookmark._id}
+                bookmarkedPage={currentUserBookmark}
+                onUnbookmarked={mutateCurrentUserBookmarks}
+                onRenamed={mutateCurrentUserBookmarks}
+                onDeleted={onBookmarkItemDeleted}
+              />
             );
           })}
         </div>
@@ -56,17 +64,6 @@ const Bookmarks = () : JSX.Element => {
     );
   };
 
-  const renderBookmarksItem = () => {
-    if (pages?.length === 0) {
-      return (
-        <h3 className="pl-3">
-          { t('No bookmarks yet') }
-        </h3>
-      );
-    }
-    return generateBookmarkList();
-  };
-
   return (
     <>
       <div className="grw-sidebar-content-header p-3">
@@ -74,15 +71,13 @@ const Bookmarks = () : JSX.Element => {
       </div>
       { isGuestUser
         ? (
-          <h3 className="pl-3">
+          <h4 className="pl-3">
             { t('Not available for guest') }
-          </h3>
-        ) : renderBookmarksItem()
+          </h4>
+        ) : renderBookmarkList()
       }
-
     </>
   );
-
 };
 
 export default Bookmarks;

+ 8 - 6
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -16,6 +16,7 @@ import TriangleIcon from '~/components/Icons/TriangleIcon';
 import {
   IPageHasId, IPageInfoAll, IPageToDeleteWithMeta,
 } from '~/interfaces/page';
+import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { IPageForPageDuplicateModal } from '~/stores/modal';
 import { useSWRxPageChildren } from '~/stores/page-listing';
 import { usePageTreeDescCountMap } from '~/stores/ui';
@@ -57,12 +58,6 @@ const markTarget = (children: ItemNode[], targetPathOrId?: string): void => {
   });
 };
 
-
-const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise<void> => {
-  const bookmarkOperation = _newValue ? bookmark : unbookmark;
-  await bookmarkOperation(_pageId);
-};
-
 /**
  * Return new page path after the droppedPagePath is moved under the newParentPagePath
  * @param droppedPagePath
@@ -111,6 +106,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
   const [isCreating, setCreating] = useState(false);
 
   const { data, mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
+  const { mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
 
   // descendantCount
   const { getDescCount } = usePageTreeDescCountMap();
@@ -238,6 +234,12 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     }
   }, [hasDescendants]);
 
+  const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise<void> => {
+    const bookmarkOperation = _newValue ? bookmark : unbookmark;
+    await bookmarkOperation(_pageId);
+    mutateCurrentUserBookmarks();
+  };
+
   const duplicateMenuItemClickHandler = useCallback((): void => {
     if (onClickDuplicateMenuItem == null) {
       return;

+ 0 - 2
packages/app/src/server/routes/apiv3/bookmarks.js

@@ -201,7 +201,6 @@ module.exports = (crowi) => {
     const { userId } = req.params;
     const page = req.query.page;
     const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30;
-    const offset = page > 0 ? (page - 1) * limit : page;
 
     if (userId == null) {
       return res.apiv3Err('User id is not found or forbidden', 400);
@@ -223,7 +222,6 @@ module.exports = (crowi) => {
               model: 'User',
             },
           },
-          offset,
           page,
           limit,
         },

+ 20 - 0
packages/app/src/stores/bookmark.ts

@@ -1,9 +1,13 @@
 import { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
+import { Nullable } from '~/interfaces/common';
+import { IPageHasId } from '~/interfaces/page';
+
 import { apiv3Get } from '../client/util/apiv3-client';
 import { IBookmarkInfo } from '../interfaces/bookmark-info';
 
+import { useCurrentUser } from './context';
 
 export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRResponse<IBookmarkInfo, Error> => {
   return useSWRImmutable(
@@ -17,3 +21,19 @@ export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRRespon
     }),
   );
 };
+
+export const useSWRxCurrentUserBookmarks = (pageNum?: Nullable<number>): SWRResponse<IPageHasId[], Error> => {
+  const { data: currentUser } = useCurrentUser();
+  const currentPage = pageNum ?? 1;
+  return useSWRImmutable(
+    currentUser != null ? `/bookmarks/${currentUser._id}` : null,
+    endpoint => apiv3Get(endpoint, { page: currentPage }).then((response) => {
+      const { paginationResult } = response.data;
+      return paginationResult.docs.map((item) => {
+        return {
+          ...item.page,
+        };
+      });
+    }),
+  );
+};

+ 0 - 1
packages/app/src/styles/theme/_apply-colors.scss

@@ -20,7 +20,6 @@ $bgcolor-page-list-group-item-active: lighten($primary, 76%) !default;
 $color-page-list-group-item-meta: $gray-500 !default;
 $color-search-page-list-title: $color-global !default;
 $bgcolor-subnav: darken($bgcolor-global, 3%) !default;
-$bgcolor-list-hover: darken($primary, 8%) !default;
 
 // override bootstrap variables
 $body-bg: $bgcolor-global;