Explorar el Código

Merge branch 'feat/gw7830-bookmarks-on-sidebar' into feat/gw7840-implement-rename-from-bookmark-sidebar

Mudana-Grune hace 3 años
padre
commit
ed5d15e451

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

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

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

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

+ 70 - 105
packages/app/src/components/Sidebar/Bookmarks.tsx

@@ -1,5 +1,5 @@
 
 
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback } from 'react';
 
 
 import nodePath from 'path';
 import nodePath from 'path';
 
 
@@ -9,36 +9,33 @@ import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
 
 
 import { unbookmark } from '~/client/services/page-operation';
 import { unbookmark } from '~/client/services/page-operation';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
-import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
+import { apiv3Put } from '~/client/util/apiv3-client';
 import { IPageHasId } from '~/interfaces/page';
 import { IPageHasId } from '~/interfaces/page';
-import { useCurrentUser, useIsGuestUser } from '~/stores/context';
-import loggerFactory from '~/utils/logger';
+import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
+import { useIsGuestUser } from '~/stores/context';
 
 
 import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
 import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 
 
-const logger = loggerFactory('growi:BookmarkList');
-// TODO: Remove pagination and apply  scrolling (not infinity)
-const ACTIVE_PAGE = 1;
 
 
 type Props = {
 type Props = {
-  page: IPageHasId,
-  refreshBookmarkList: () => void
+  bookmarkedPage: IPageHasId,
+  onPageOperationSuccess: () => void
 }
 }
 
 
-const BookmarksItem = (props: Props) => {
+const BookmarkItem = (props: Props) => {
+  const { bookmarkedPage, onPageOperationSuccess } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { page, refreshBookmarkList } = props;
   const [isRenameInputShown, setRenameInputShown] = useState(false);
   const [isRenameInputShown, setRenameInputShown] = useState(false);
-  const dPagePath = new DevidedPagePath(page.path, false, true);
-  const { latter: pageTitle, former: formerPagePath } = dPagePath;
-  const bookmarkItemId = `bookmark-item-${page._id}`;
-
+  const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
+  const { latter: pageTitle, former, isRoot } = dPagePath;
+  const formerPagePath = isRoot ? pageTitle : pathUtils.addTrailingSlash(former);
+  const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
 
 
   const bookmarkMenuItemClickHandler = useCallback(async() => {
   const bookmarkMenuItemClickHandler = useCallback(async() => {
-    await unbookmark(page._id);
-    refreshBookmarkList();
-  }, [page, refreshBookmarkList]);
+    await unbookmark(bookmarkedPage._id);
+    onPageOperationSuccess();
+  }, [onPageOperationSuccess, bookmarkedPage]);
 
 
   const renameMenuItemClickHandler = useCallback(() => {
   const renameMenuItemClickHandler = useCallback(() => {
     setRenameInputShown(true);
     setRenameInputShown(true);
@@ -56,9 +53,9 @@ const BookmarksItem = (props: Props) => {
   };
   };
 
 
   const pressEnterForRenameHandler = (async(inputText: string) => {
   const pressEnterForRenameHandler = (async(inputText: string) => {
-    const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? ''));
+    const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(bookmarkedPage.path ?? ''));
     const newPagePath = nodePath.resolve(parentPath, inputText);
     const newPagePath = nodePath.resolve(parentPath, inputText);
-    if (newPagePath === page.path) {
+    if (newPagePath === bookmarkedPage.path) {
       setRenameInputShown(false);
       setRenameInputShown(false);
       return;
       return;
     }
     }
@@ -66,12 +63,12 @@ const BookmarksItem = (props: Props) => {
     try {
     try {
       setRenameInputShown(false);
       setRenameInputShown(false);
       await apiv3Put('/pages/rename', {
       await apiv3Put('/pages/rename', {
-        pageId: page._id,
-        revisionId: page.revision,
+        pageId: bookmarkedPage._id,
+        revisionId: bookmarkedPage.revision,
         newPagePath,
         newPagePath,
       });
       });
-      refreshBookmarkList();
-      toastSuccess(t('renamed_pages', { path: page.path }));
+      onPageOperationSuccess();
+      toastSuccess(t('renamed_pages', { path: bookmarkedPage.path }));
     }
     }
     catch (err) {
     catch (err) {
       setRenameInputShown(true);
       setRenameInputShown(true);
@@ -80,82 +77,64 @@ const BookmarksItem = (props: Props) => {
   });
   });
 
 
   return (
   return (
-    <>
-      <div className="d-flex justify-content-between" key={page._id}>
-        <li className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center" id={bookmarkItemId}>
-          { isRenameInputShown ? (
-            <ClosableTextInput
-              value={nodePath.basename(page.path ?? '')}
-              placeholder={t('Input page name')}
-              onClickOutside={() => { setRenameInputShown(false) }}
-              onPressEnter={pressEnterForRenameHandler}
-              inputValidator={inputValidator}
-            />
-          ) : (
-            <a href={`/${page._id}`} className="grw-bookmarks-title-anchor flex-grow-1">
-              <p className={`text-truncate m-auto ${page.isEmpty && 'grw-sidebar-text-muted'}`}>{pageTitle}</p>
-            </a>
-          )}
-          <PageItemControl
-            pageId={page._id}
-            isEnableActions
-            forceHideMenuItems={[MenuItemType.DUPLICATE]}
-            onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
-            onClickRenameMenuItem={renameMenuItemClickHandler}
-          >
-            <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
-              <i className="icon-options fa fa-rotate-90 p-1"></i>
-            </DropdownToggle>
-          </PageItemControl>
-          <UncontrolledTooltip
-            modifiers={{ preventOverflow: { boundariesElement: 'window' } }}
-            autohide={false}
-            placement="right"
-            target={bookmarkItemId}
-          >
-            { formerPagePath || '/' }
-          </UncontrolledTooltip>
-        </li>
-      </div>
-    </>
+    <div className="d-flex justify-content-between" key={bookmarkedPage._id}>
+      <li className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center" id={bookmarkItemId}>
+        { isRenameInputShown ? (
+          <ClosableTextInput
+            value={nodePath.basename(bookmarkedPage.path ?? '')}
+            placeholder={t('Input page name')}
+            onClickOutside={() => { setRenameInputShown(false) }}
+            onPressEnter={pressEnterForRenameHandler}
+            inputValidator={inputValidator}
+          />
+        ) : (
+          <a href={`/${bookmarkedPage._id}`} className="grw-bookmarks-title-anchor flex-grow-1">
+            <p className={`text-truncate m-auto ${bookmarkedPage.isEmpty && 'grw-sidebar-text-muted'}`}>{pageTitle}</p>
+          </a>
+        )}
+        <PageItemControl
+          pageId={bookmarkedPage._id}
+          isEnableActions
+          forceHideMenuItems={[MenuItemType.DUPLICATE]}
+          onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
+          onClickRenameMenuItem={renameMenuItemClickHandler}
+        >
+          <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
+            <i className="icon-options fa fa-rotate-90 p-1"></i>
+          </DropdownToggle>
+        </PageItemControl>
+        <UncontrolledTooltip
+          modifiers={{ preventOverflow: { boundariesElement: 'window' } }}
+          autohide={false}
+          placement="right"
+          target={bookmarkItemId}
+        >
+          { formerPagePath }
+        </UncontrolledTooltip>
+      </li>
+    </div>
   );
   );
 };
 };
 
 
-
 const Bookmarks = () : JSX.Element => {
 const Bookmarks = () : JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
-  const [pages, setPages] = useState<IPageHasId[]>([]);
-  const page = ACTIVE_PAGE;
+  const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
 
 
-  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');
+  const renderBookmarkList = () => {
+    if (currentUserBookmarksData?.length === 0) {
+      return (
+        <h4 className="pl-3">
+          { t('No bookmarks yet') }
+        </h4>
+      );
     }
     }
-  }, [currentUser, page]);
-
-  useEffect(() => {
-    getMyBookmarkList();
-  }, [getMyBookmarkList]);
-
-  const generateBookmarkList = () => {
     return (
     return (
       <ul className="grw-bookmarks-list list-group p-3">
       <ul className="grw-bookmarks-list list-group p-3">
         <div className="grw-bookmarks-item-container">
         <div className="grw-bookmarks-item-container">
-          { pages.map((page) => {
+          { currentUserBookmarksData?.map((currentUserBookmark) => {
             return (
             return (
-              <BookmarksItem key={page._id} page={page} refreshBookmarkList={getMyBookmarkList} />
+              <BookmarkItem key={currentUserBookmark._id} bookmarkedPage={currentUserBookmark} onPageOperationSuccess={mutateCurrentUserBookmarks} />
             );
             );
           })}
           })}
         </div>
         </div>
@@ -163,34 +142,20 @@ const Bookmarks = () : JSX.Element => {
     );
     );
   };
   };
 
 
-  const renderBookmarksItem = () => {
-    if (pages?.length === 0) {
-      return (
-        <h3 className="pl-3">
-          { t('No bookmarks yet') }
-        </h3>
-      );
-    }
-    return generateBookmarkList();
-  };
-
   return (
   return (
     <>
     <>
       <div className="grw-sidebar-content-header p-3">
       <div className="grw-sidebar-content-header p-3">
         <h3 className="mb-0">{t('Bookmarks')}</h3>
         <h3 className="mb-0">{t('Bookmarks')}</h3>
       </div>
       </div>
-
       { isGuestUser
       { isGuestUser
         ? (
         ? (
-          <h3 className="pl-3">
+          <h4 className="pl-3">
             { t('Not available for guest') }
             { t('Not available for guest') }
-          </h3>
-        ) : renderBookmarksItem()
+          </h4>
+        ) : renderBookmarkList()
       }
       }
-
     </>
     </>
   );
   );
-
 };
 };
 
 
 export default Bookmarks;
 export default Bookmarks;

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

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

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

@@ -1,9 +1,13 @@
 import { SWRResponse } from 'swr';
 import { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
+import { Nullable } from '~/interfaces/common';
+import { IPageHasId } from '~/interfaces/page';
+
 import { apiv3Get } from '../client/util/apiv3-client';
 import { apiv3Get } from '../client/util/apiv3-client';
 import { IBookmarkInfo } from '../interfaces/bookmark-info';
 import { IBookmarkInfo } from '../interfaces/bookmark-info';
 
 
+import { useCurrentUser } from './context';
 
 
 export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRResponse<IBookmarkInfo, Error> => {
 export const useSWRBookmarkInfo = (pageId: string | null | undefined): SWRResponse<IBookmarkInfo, Error> => {
   return useSWRImmutable(
   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,
+        };
+      });
+    }),
+  );
+};