ryoji-s 2 лет назад
Родитель
Сommit
9b2b9a7411

+ 39 - 26
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -2,8 +2,10 @@ import {
   FC, useCallback, useState,
   FC, useCallback, useState,
 } from 'react';
 } from 'react';
 
 
+import type { IPageHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { DropdownToggle } from 'reactstrap';
 import { DropdownToggle } from 'reactstrap';
+import type { KeyedMutator } from 'swr';
 
 
 import {
 import {
   addBookmarkToFolder, addNewFolder, hasChildren, updateBookmarkFolder,
   addBookmarkToFolder, addNewFolder, hasChildren, updateBookmarkFolder,
@@ -12,34 +14,37 @@ import { toastError, toastSuccess } from '~/client/util/toastr';
 import { FolderIcon } from '~/components/Icons/FolderIcon';
 import { FolderIcon } from '~/components/Icons/FolderIcon';
 import { TriangleIcon } from '~/components/Icons/TriangleIcon';
 import { TriangleIcon } from '~/components/Icons/TriangleIcon';
 import {
 import {
-  BookmarkFolderItems, DragItemDataType, DragItemType, DRAG_ITEM_TYPE,
+  BookmarkFolderItems, DragItemDataType, DragItemType, DRAG_ITEM_TYPE, IBookmarkInfo,
 } from '~/interfaces/bookmark-info';
 } from '~/interfaces/bookmark-info';
 import { IPageToDeleteWithMeta } from '~/interfaces/page';
 import { IPageToDeleteWithMeta } from '~/interfaces/page';
 import { onDeletedBookmarkFolderFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { onDeletedBookmarkFolderFunction, OnDeletedFunction } from '~/interfaces/ui';
-import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
-import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { useBookmarkFolderDeleteModal, usePageDeleteModal } from '~/stores/modal';
 import { useBookmarkFolderDeleteModal, usePageDeleteModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
 
 
 import { BookmarkFolderItemControl } from './BookmarkFolderItemControl';
 import { BookmarkFolderItemControl } from './BookmarkFolderItemControl';
 import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 import { BookmarkItem } from './BookmarkItem';
 import { BookmarkItem } from './BookmarkItem';
 import { DragAndDropWrapper } from './DragAndDropWrapper';
 import { DragAndDropWrapper } from './DragAndDropWrapper';
 
 
-
 type BookmarkFolderItemProps = {
 type BookmarkFolderItemProps = {
   bookmarkFolder: BookmarkFolderItems
   bookmarkFolder: BookmarkFolderItems
   isOpen?: boolean
   isOpen?: boolean
   level: number
   level: number
   root: string
   root: string
   isUserHomePage?: boolean
   isUserHomePage?: boolean
+  bookmarkFolders: BookmarkFolderItems[]
+  mutateBookmarkFolders: KeyedMutator<BookmarkFolderItems[]>
+  userBookmarks: IPageHasId[]
+  mutateUserBookmarks: KeyedMutator<IPageHasId[]>
+  bookmarkInfo: IBookmarkInfo
+  mutateBookmarkInfo: KeyedMutator<IBookmarkInfo>
 }
 }
 
 
 export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
 export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
   const BASE_FOLDER_PADDING = 15;
   const BASE_FOLDER_PADDING = 15;
   const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const {
   const {
-    bookmarkFolder, isOpen: _isOpen = false, level, root, isUserHomePage,
+    bookmarkFolder, isOpen: _isOpen = false, level, root, isUserHomePage, bookmarkFolders,
+    mutateBookmarkFolders, userBookmarks, mutateUserBookmarks, bookmarkInfo, mutateBookmarkInfo,
   } = props;
   } = props;
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
@@ -49,12 +54,10 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
 
 
   const [targetFolder, setTargetFolder] = useState<string | null>(folderId);
   const [targetFolder, setTargetFolder] = useState<string | null>(folderId);
   const [isOpen, setIsOpen] = useState(_isOpen);
   const [isOpen, setIsOpen] = useState(_isOpen);
-  const { mutate: mutateBookmarkData } = useSWRxBookmarkFolderAndChild();
-  const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const [isRenameAction, setIsRenameAction] = useState<boolean>(false);
   const [isRenameAction, setIsRenameAction] = useState<boolean>(false);
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
+
+
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
   const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
 
 
@@ -71,13 +74,13 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
   const onPressEnterHandlerForRename = useCallback(async(folderName: string) => {
   const onPressEnterHandlerForRename = useCallback(async(folderName: string) => {
     try {
     try {
       await updateBookmarkFolder(folderId, folderName, parent);
       await updateBookmarkFolder(folderId, folderName, parent);
-      mutateBookmarkData();
+      mutateBookmarkFolders();
       setIsRenameAction(false);
       setIsRenameAction(false);
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }, [folderId, mutateBookmarkData, parent]);
+  }, [folderId, mutateBookmarkFolders, parent]);
 
 
   // Create new folder / subfolder handler
   // Create new folder / subfolder handler
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
@@ -85,12 +88,12 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
       await addNewFolder(folderName, targetFolder);
       await addNewFolder(folderName, targetFolder);
       setIsOpen(true);
       setIsOpen(true);
       setIsCreateAction(false);
       setIsCreateAction(false);
-      mutateBookmarkData();
+      mutateBookmarkFolders();
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }, [mutateBookmarkData, targetFolder]);
+  }, [mutateBookmarkFolders, targetFolder]);
 
 
 
 
   const onClickPlusButton = useCallback(async(e) => {
   const onClickPlusButton = useCallback(async(e) => {
@@ -114,16 +117,16 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
       else {
       else {
         toastSuccess(t('deleted_pages', { path }));
         toastSuccess(t('deleted_pages', { path }));
       }
       }
-      mutateBookmarkData();
+      mutateBookmarkFolders();
       mutateBookmarkInfo();
       mutateBookmarkInfo();
     };
     };
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
-  }, [mutateBookmarkInfo, mutateBookmarkData, openDeleteModal, t]);
+  }, [mutateBookmarkInfo, mutateBookmarkFolders, openDeleteModal, t]);
 
 
   const onUnbookmarkHandler = useCallback(() => {
   const onUnbookmarkHandler = useCallback(() => {
-    mutateBookmarkData();
+    mutateBookmarkFolders();
     mutateBookmarkInfo();
     mutateBookmarkInfo();
-  }, [mutateBookmarkInfo, mutateBookmarkData]);
+  }, [mutateBookmarkInfo, mutateBookmarkFolders]);
 
 
 
 
   const itemDropHandler = async(item: DragItemDataType, dragItemType: string | symbol | null) => {
   const itemDropHandler = async(item: DragItemDataType, dragItemType: string | symbol | null) => {
@@ -131,7 +134,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
       try {
       try {
         if (item.bookmarkFolder != null) {
         if (item.bookmarkFolder != null) {
           await updateBookmarkFolder(item.bookmarkFolder._id, item.bookmarkFolder.name, bookmarkFolder._id);
           await updateBookmarkFolder(item.bookmarkFolder._id, item.bookmarkFolder.name, bookmarkFolder._id);
-          mutateBookmarkData();
+          mutateBookmarkFolders();
         }
         }
       }
       }
       catch (err) {
       catch (err) {
@@ -142,7 +145,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
       try {
       try {
         if (item != null) {
         if (item != null) {
           await addBookmarkToFolder(item._id, bookmarkFolder._id);
           await addBookmarkToFolder(item._id, bookmarkFolder._id);
-          mutateBookmarkData();
+          mutateBookmarkFolders();
           await mutateUserBookmarks();
           await mutateUserBookmarks();
         }
         }
       }
       }
@@ -184,7 +187,13 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
             bookmarkFolder={childFolder}
             bookmarkFolder={childFolder}
             level={level + 1}
             level={level + 1}
             root={root}
             root={root}
-            isUserHomePage ={isUserHomePage}
+            isUserHomePage={isUserHomePage}
+            bookmarkFolders={bookmarkFolders}
+            mutateBookmarkFolders={mutateBookmarkFolders}
+            userBookmarks={userBookmarks}
+            mutateUserBookmarks={mutateUserBookmarks}
+            bookmarkInfo={bookmarkInfo}
+            mutateBookmarkInfo={mutateBookmarkInfo}
           />
           />
         </div>
         </div>
       );
       );
@@ -198,10 +207,14 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
           bookmarkedPage={bookmark.page}
           bookmarkedPage={bookmark.page}
           key={bookmark._id}
           key={bookmark._id}
           onUnbookmarked={onUnbookmarkHandler}
           onUnbookmarked={onUnbookmarkHandler}
-          onRenamed={mutateBookmarkData}
+          onRenamed={mutateBookmarkFolders}
           onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
           onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
           parentFolder={bookmarkFolder}
           parentFolder={bookmarkFolder}
           level={level + 1}
           level={level + 1}
+          bookmarkFolders={bookmarkFolders}
+          mutateBookmarkFolders={mutateBookmarkFolders}
+          userBookmarks={userBookmarks}
+          mutateUserBookmarks={mutateUserBookmarks}
         />
         />
       );
       );
     });
     });
@@ -217,24 +230,24 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
         return;
         return;
       }
       }
       mutateBookmarkInfo();
       mutateBookmarkInfo();
-      mutateBookmarkData();
+      mutateBookmarkFolders();
     };
     };
 
 
     if (bookmarkFolder == null) {
     if (bookmarkFolder == null) {
       return;
       return;
     }
     }
     openDeleteBookmarkFolderModal(bookmarkFolder, { onDeleted: bookmarkFolderDeleteHandler });
     openDeleteBookmarkFolderModal(bookmarkFolder, { onDeleted: bookmarkFolderDeleteHandler });
-  }, [bookmarkFolder, mutateBookmarkData, mutateBookmarkInfo, openDeleteBookmarkFolderModal]);
+  }, [bookmarkFolder, mutateBookmarkFolders, mutateBookmarkInfo, openDeleteBookmarkFolderModal]);
 
 
   const onClickMoveToRootHandler = useCallback(async() => {
   const onClickMoveToRootHandler = useCallback(async() => {
     try {
     try {
       await updateBookmarkFolder(bookmarkFolder._id, bookmarkFolder.name, null);
       await updateBookmarkFolder(bookmarkFolder._id, bookmarkFolder.name, null);
-      await mutateBookmarkData();
+      await mutateBookmarkFolders();
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }, [bookmarkFolder._id, bookmarkFolder.name, mutateBookmarkData]);
+  }, [bookmarkFolder._id, bookmarkFolder.name, mutateBookmarkFolders]);
 
 
   return (
   return (
     <div id={`grw-bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container">
     <div id={`grw-bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container">

+ 40 - 41
apps/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -1,44 +1,36 @@
 
 
-import { useCallback } from 'react';
+import React, { useCallback } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
-import { addBookmarkToFolder, updateBookmarkFolder } from '~/client/util/bookmark-utils';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { BookmarkFolderItems, DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
 import { BookmarkFolderItems, DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { OnDeletedFunction } from '~/interfaces/ui';
 import { OnDeletedFunction } from '~/interfaces/ui';
-import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
+import { useSWRxCurrentUserBookmarks, useSWRBookmarkInfo } from '~/stores/bookmark';
 import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { usePageDeleteModal } from '~/stores/modal';
 import { usePageDeleteModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxCurrentPage } from '~/stores/page';
 
 
 import { BookmarkFolderItem } from './BookmarkFolderItem';
 import { BookmarkFolderItem } from './BookmarkFolderItem';
 import { BookmarkItem } from './BookmarkItem';
 import { BookmarkItem } from './BookmarkItem';
-import { DragAndDropWrapper } from './DragAndDropWrapper';
 
 
 import styles from './BookmarkFolderTree.module.scss';
 import styles from './BookmarkFolderTree.module.scss';
 
 
+// type DragItemDataType = {
+//   bookmarkFolder: BookmarkFolderItems
+//   level: number
+//   parentFolder: BookmarkFolderItems | null
+//  } & IPageHasId
 
 
-type BookmarkFolderTreeProps = {
-  isUserHomePage?: boolean
-}
-
-type DragItemDataType = {
-  bookmarkFolder: BookmarkFolderItems
-  level: number
-  parentFolder: BookmarkFolderItems | null
- } & IPageHasId
-
-export const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
-  const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
+export const BookmarkFolderTree: React.FC<{isUserHomePage?: boolean}> = ({ isUserHomePage }) => {
+  // const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { isUserHomePage } = props;
+
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: currentPage } = useSWRxCurrentPage();
-  const { data: bookmarkFolderData, mutate: mutateBookmarkData } = useSWRxBookmarkFolderAndChild();
+  const { data: bookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
+  const { data: bookmarkFolders, mutate: mutateBookmarkFolders } = useSWRxBookmarkFolderAndChild();
   const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
-  const { mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
-
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
 
   const onUnbookmarkHandler = useCallback(() => {
   const onUnbookmarkHandler = useCallback(() => {
@@ -48,23 +40,16 @@ export const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element
 
 
   const onClickDeleteBookmarkHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
   const onClickDeleteBookmarkHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
     const pageDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => {
     const pageDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => {
-      if (typeof pathOrPathsToDelete !== 'string') {
-        return;
-      }
-      const path = pathOrPathsToDelete;
-
-      if (isCompletely) {
-        toastSuccess(t('deleted_pages_completely', { path }));
-      }
-      else {
-        toastSuccess(t('deleted_pages', { path }));
-      }
+      if (typeof pathOrPathsToDelete !== 'string') return;
+
+      toastSuccess(isCompletely ? t('deleted_pages_completely', { pathOrPathsToDelete }) : t('deleted_pages', { pathOrPathsToDelete }));
+
       mutateUserBookmarks();
       mutateUserBookmarks();
       mutateBookmarkInfo();
       mutateBookmarkInfo();
-      mutateBookmarkData();
+      mutateBookmarkFolders();
     };
     };
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
-  }, [mutateBookmarkInfo, mutateBookmarkData, mutateUserBookmarks, openDeleteModal, t]);
+  }, [openDeleteModal, mutateUserBookmarks, mutateBookmarkInfo, mutateBookmarkFolders, t]);
 
 
   /* TODO: update in bookmarks folder v2. */
   /* TODO: update in bookmarks folder v2. */
   // const itemDropHandler = async(item: DragItemDataType, dragType: string | null | symbol) => {
   // const itemDropHandler = async(item: DragItemDataType, dragType: string | null | symbol) => {
@@ -100,31 +85,45 @@ export const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element
 
 
   // };
   // };
 
 
+  if (bookmarkFolders == null || userBookmarks == null || currentPage == null || bookmarkInfo == null) {
+    return <></>;
+  }
+
   return (
   return (
     <div className={`grw-folder-tree-container ${styles['grw-folder-tree-container']}` } >
     <div className={`grw-folder-tree-container ${styles['grw-folder-tree-container']}` } >
       <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-2 py-2`}>
       <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-2 py-2`}>
-        {bookmarkFolderData?.map((item) => {
+        {bookmarkFolders?.map((bookmarkFolder) => {
           return (
           return (
             <BookmarkFolderItem
             <BookmarkFolderItem
-              key={item._id}
-              bookmarkFolder={item}
+              key={bookmarkFolder._id}
+              bookmarkFolder={bookmarkFolder}
               isOpen={false}
               isOpen={false}
               level={0}
               level={0}
-              root={item._id}
+              root={bookmarkFolder._id}
               isUserHomePage={isUserHomePage}
               isUserHomePage={isUserHomePage}
+              bookmarkFolders={bookmarkFolders}
+              mutateBookmarkFolders={mutateBookmarkFolders}
+              userBookmarks={userBookmarks}
+              mutateUserBookmarks={mutateUserBookmarks}
+              bookmarkInfo={bookmarkInfo}
+              mutateBookmarkInfo={mutateBookmarkInfo}
             />
             />
           );
           );
         })}
         })}
-        {userBookmarks?.map(page => (
-          <div key={page._id} className="grw-foldertree-item-container grw-root-bookmarks">
+        {userBookmarks?.map(userBookmark => (
+          <div key={userBookmark._id} className="grw-foldertree-item-container grw-root-bookmarks">
             <BookmarkItem
             <BookmarkItem
-              bookmarkedPage={page}
-              key={page._id}
+              bookmarkedPage={userBookmark}
+              key={userBookmark._id}
               onUnbookmarked={onUnbookmarkHandler}
               onUnbookmarked={onUnbookmarkHandler}
               onRenamed={mutateUserBookmarks}
               onRenamed={mutateUserBookmarks}
               onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
               onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
               parentFolder={null}
               parentFolder={null}
               level={0}
               level={0}
+              bookmarkFolders={bookmarkFolders}
+              mutateBookmarkFolders={mutateBookmarkFolders}
+              userBookmarks={userBookmarks}
+              mutateUserBookmarks={mutateUserBookmarks}
             />
             />
           </div>
           </div>
         ))}
         ))}

+ 20 - 29
apps/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -6,9 +6,8 @@ import nodePath from 'path';
 
 
 import { DevidedPagePath, pathUtils } from '@growi/core';
 import { DevidedPagePath, pathUtils } from '@growi/core';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
-import {
-  UncontrolledTooltip, DropdownItem, DropdownToggle,
-} from 'reactstrap';
+import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
+import type { KeyedMutator } from 'swr';
 
 
 import { unbookmark } from '~/client/services/page-operation';
 import { unbookmark } from '~/client/services/page-operation';
 import { addBookmarkToFolder, renamePage } from '~/client/util/bookmark-utils';
 import { addBookmarkToFolder, renamePage } from '~/client/util/bookmark-utils';
@@ -16,14 +15,13 @@ import { ValidationTarget } from '~/client/util/input-validator';
 import { toastError } from '~/client/util/toastr';
 import { toastError } from '~/client/util/toastr';
 import { BookmarkFolderItems, DragItemDataType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
 import { BookmarkFolderItems, DragItemDataType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
 import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
-import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
-import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { useSWRxPageInfo } from '~/stores/page';
 import { useSWRxPageInfo } from '~/stores/page';
 
 
 import ClosableTextInput from '../Common/ClosableTextInput';
 import ClosableTextInput from '../Common/ClosableTextInput';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { PageListItemS } from '../PageList/PageListItemS';
 import { PageListItemS } from '../PageList/PageListItemS';
 
 
+import { BookmarkMoveToRootBtn } from './BookmarkMoveToRootBtn';
 import { DragAndDropWrapper } from './DragAndDropWrapper';
 import { DragAndDropWrapper } from './DragAndDropWrapper';
 
 
 type Props = {
 type Props = {
@@ -31,8 +29,12 @@ type Props = {
   onUnbookmarked: () => void,
   onUnbookmarked: () => void,
   onRenamed: () => void,
   onRenamed: () => void,
   onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void,
   onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void,
-  parentFolder: BookmarkFolderItems | null
-  level: number
+  parentFolder: BookmarkFolderItems | null,
+  level: number,
+  bookmarkFolders: BookmarkFolderItems[],
+  mutateBookmarkFolders: KeyedMutator<BookmarkFolderItems[]>,
+  userBookmarks: IPageHasId[],
+  mutateUserBookmarks: KeyedMutator<IPageHasId[]>,
 }
 }
 
 
 export const BookmarkItem = (props: Props): JSX.Element => {
 export const BookmarkItem = (props: Props): JSX.Element => {
@@ -40,13 +42,16 @@ export const BookmarkItem = (props: Props): JSX.Element => {
   const BASE_BOOKMARK_PADDING = 20;
   const BASE_BOOKMARK_PADDING = 20;
   const { t } = useTranslation();
   const { t } = useTranslation();
   const {
   const {
-    bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, parentFolder, level,
+    bookmarkedPage,
+    onUnbookmarked,
+    onRenamed,
+    onClickDeleteMenuItem, parentFolder, level,
+    mutateBookmarkFolders, userBookmarks, mutateUserBookmarks,
   } = props;
   } = props;
   const [isRenameInputShown, setRenameInputShown] = useState(false);
   const [isRenameInputShown, setRenameInputShown] = useState(false);
   const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
   const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
   const { latter: pageTitle, former: formerPagePath } = dPagePath;
   const { latter: pageTitle, former: formerPagePath } = dPagePath;
   const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
   const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
-  const { mutate: mutateBookmarkData } = useSWRxBookmarkFolderAndChild();
   const { data: fetchedPageInfo } = useSWRxPageInfo(bookmarkedPage._id);
   const { data: fetchedPageInfo } = useSWRxPageInfo(bookmarkedPage._id);
 
 
   const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level + 1));
   const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level + 1));
@@ -56,16 +61,11 @@ export const BookmarkItem = (props: Props): JSX.Element => {
   };
   };
 
 
   useEffect(() => {
   useEffect(() => {
-    mutateBookmarkData();
-  }, [mutateBookmarkData]);
+    mutateBookmarkFolders();
+  }, [mutateBookmarkFolders]);
 
 
   const pageId = bookmarkedPage._id;
   const pageId = bookmarkedPage._id;
 
 
-  const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
-  const isMoveToRoot = useMemo(() => {
-    return !userBookmarks?.map(userBookmark => userBookmark._id).includes(pageId);
-  }, [pageId, userBookmarks]);
-
   const moveToRootClickedHandler = useCallback(async() => {
   const moveToRootClickedHandler = useCallback(async() => {
     try {
     try {
       await addBookmarkToFolder(pageId, null);
       await addBookmarkToFolder(pageId, null);
@@ -76,18 +76,9 @@ export const BookmarkItem = (props: Props): JSX.Element => {
     }
     }
   }, [mutateUserBookmarks, pageId]);
   }, [mutateUserBookmarks, pageId]);
 
 
-  const additionalMenuItemOnTopRenderer = useMemo(() => {
-    return (
-      <DropdownItem
-        onClick={moveToRootClickedHandler}
-        className="grw-page-control-dropdown-item"
-        data-testid="add-remove-bookmark-btn"
-      >
-        <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
-        {t('bookmark_folder.move_to_root')}
-      </DropdownItem>
-    );
-  }, [moveToRootClickedHandler, t]);
+  const isMoveToRoot = useMemo(() => {
+    return !userBookmarks?.map(userBookmark => userBookmark._id).includes(pageId);
+  }, [pageId, userBookmarks]);
 
 
   const bookmarkMenuItemClickHandler = useCallback(async() => {
   const bookmarkMenuItemClickHandler = useCallback(async() => {
     await unbookmark(bookmarkedPage._id);
     await unbookmark(bookmarkedPage._id);
@@ -165,7 +156,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
             onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
             onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
             onClickRenameMenuItem={renameMenuItemClickHandler}
             onClickRenameMenuItem={renameMenuItemClickHandler}
             onClickDeleteMenuItem={deleteMenuItemClickHandler}
             onClickDeleteMenuItem={deleteMenuItemClickHandler}
-            additionalMenuItemOnTopRenderer={isMoveToRoot ? (() => additionalMenuItemOnTopRenderer) : undefined}
+            additionalMenuItemOnTopRenderer={isMoveToRoot ? (() => <BookmarkMoveToRootBtn moveToRootClickedHandler={moveToRootClickedHandler}/>) : undefined}
           >
           >
             <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
             <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>
               <i className="icon-options fa fa-rotate-90 p-1"></i>

+ 22 - 0
apps/app/src/components/Bookmarks/BookmarkMoveToRootBtn.tsx

@@ -0,0 +1,22 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+import { DropdownItem } from 'reactstrap';
+
+export const BookmarkMoveToRootBtn: React.FC<{
+  moveToRootClickedHandler: () => Promise<void>
+}> = React.memo(({ moveToRootClickedHandler }) => {
+  const { t } = useTranslation();
+
+  return (
+    <DropdownItem
+      onClick={moveToRootClickedHandler}
+      className="grw-page-control-dropdown-item"
+      data-testid="add-remove-bookmark-btn"
+    >
+      <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
+      {t('bookmark_folder.move_to_root')}
+    </DropdownItem>
+  );
+});
+BookmarkMoveToRootBtn.displayName = 'BookmarkMoveToRootBtn';

+ 4 - 15
apps/app/src/components/PageList/BookmarkList.tsx

@@ -6,7 +6,7 @@ import {
   IPageInfoAll, IPageToDeleteWithMeta, pathUtils,
   IPageInfoAll, IPageToDeleteWithMeta, pathUtils,
 } from '@growi/core';
 } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
-import { DropdownItem, DropdownToggle } from 'reactstrap';
+import { DropdownToggle } from 'reactstrap';
 
 
 import { unbookmark } from '~/client/services/page-operation';
 import { unbookmark } from '~/client/services/page-operation';
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { apiv3Put } from '~/client/util/apiv3-client';
@@ -17,6 +17,7 @@ import { IPageHasId } from '~/interfaces/page';
 import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import { BookmarkMoveToRootBtn } from '../Bookmarks/BookmarkMoveToRootBtn';
 import ClosableTextInput from '../Common/ClosableTextInput';
 import ClosableTextInput from '../Common/ClosableTextInput';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 
 
@@ -40,6 +41,7 @@ export const BookmarkList = (props:Props): JSX.Element => {
   const pageId = page._id;
   const pageId = page._id;
 
 
   const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
+
   const isMoveToRoot = useMemo(() => {
   const isMoveToRoot = useMemo(() => {
     return !userBookmarks?.map(userBookmark => userBookmark._id).includes(pageId);
     return !userBookmarks?.map(userBookmark => userBookmark._id).includes(pageId);
   }, [pageId, userBookmarks]);
   }, [pageId, userBookmarks]);
@@ -54,19 +56,6 @@ export const BookmarkList = (props:Props): JSX.Element => {
     }
     }
   }, [mutateUserBookmarks, pageId]);
   }, [mutateUserBookmarks, pageId]);
 
 
-  const additionalMenuItemOnTopRenderer = useMemo(() => {
-    return (
-      <DropdownItem
-        onClick={moveToRootClickedHandler}
-        className="grw-page-control-dropdown-item"
-        data-testid="add-remove-bookmark-btn"
-      >
-        <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
-        {t('bookmark_folder.move_to_root')}
-      </DropdownItem>
-    );
-  }, [moveToRootClickedHandler, t]);
-
   const bookmarkMenuItemClickHandler = useCallback(async() => {
   const bookmarkMenuItemClickHandler = useCallback(async() => {
     await unbookmark(page._id);
     await unbookmark(page._id);
     onUnbookmarked();
     onUnbookmarked();
@@ -135,7 +124,7 @@ export const BookmarkList = (props:Props): JSX.Element => {
         onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
         onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
         onClickRenameMenuItem={() => setIsRenameInputShown(true)}
         onClickRenameMenuItem={() => setIsRenameInputShown(true)}
         onClickDeleteMenuItem={deleteMenuItemClickHandler}
         onClickDeleteMenuItem={deleteMenuItemClickHandler}
-        additionalMenuItemOnTopRenderer={isMoveToRoot ? (() => additionalMenuItemOnTopRenderer) : undefined}
+        additionalMenuItemOnTopRenderer={isMoveToRoot ? (() => <BookmarkMoveToRootBtn moveToRootClickedHandler={moveToRootClickedHandler}/>) : undefined}
       >
       >
         <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
         <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>
           <i className="icon-options fa fa-rotate-90 p-1"></i>

+ 17 - 26
apps/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx

@@ -3,37 +3,38 @@ import React, { useCallback, useState } from 'react';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
-import { toastError, toastSuccess } from '~/client/util/toastr';
+import { toastError } from '~/client/util/toastr';
 import { BookmarkFolderNameInput } from '~/components/Bookmarks/BookmarkFolderNameInput';
 import { BookmarkFolderNameInput } from '~/components/Bookmarks/BookmarkFolderNameInput';
 import { BookmarkFolderTree } from '~/components/Bookmarks/BookmarkFolderTree';
 import { BookmarkFolderTree } from '~/components/Bookmarks/BookmarkFolderTree';
 import { FolderPlusIcon } from '~/components/Icons/FolderPlusIcon';
 import { FolderPlusIcon } from '~/components/Icons/FolderPlusIcon';
 import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 
 
-
 export const BookmarkContents = (): JSX.Element => {
 export const BookmarkContents = (): JSX.Element => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
-  const { mutate: mutateChildBookmarkData } = useSWRxBookmarkFolderAndChild();
-
+  const { mutate: mutateBookmarkFolders } = useSWRxBookmarkFolderAndChild();
 
 
   const onClickNewBookmarkFolder = useCallback(() => {
   const onClickNewBookmarkFolder = useCallback(() => {
     setIsCreateAction(true);
     setIsCreateAction(true);
   }, []);
   }, []);
 
 
-  const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
+  const onClickonClickOutsideHandler = useCallback(() => {
+    setIsCreateAction(false);
+  }, []);
 
 
+  const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
     try {
     try {
       await apiv3Post('/bookmark-folder', { name: folderName, parent: null });
       await apiv3Post('/bookmark-folder', { name: folderName, parent: null });
-      await mutateChildBookmarkData();
+      await mutateBookmarkFolders();
       setIsCreateAction(false);
       setIsCreateAction(false);
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }, [mutateChildBookmarkData]);
+  }, [mutateBookmarkFolders]);
 
 
-  const renderAddNewBookmarkFolder = useCallback(() => (
+  return (
     <>
     <>
       <div className="col-8 mb-2 ">
       <div className="col-8 mb-2 ">
         <button
         <button
@@ -44,24 +45,14 @@ export const BookmarkContents = (): JSX.Element => {
           <span className="mx-2 ">{t('bookmark_folder.new_folder')}</span>
           <span className="mx-2 ">{t('bookmark_folder.new_folder')}</span>
         </button>
         </button>
       </div>
       </div>
-      {
-        isCreateAction && (
-          <div className="col-12 mb-2 ">
-            <BookmarkFolderNameInput
-              onClickOutside={() => setIsCreateAction(false)}
-              onPressEnter={onPressEnterHandlerForCreate}
-            />
-          </div>
-        )
-      }
-    </>
-  ), [isCreateAction, onClickNewBookmarkFolder, onPressEnterHandlerForCreate, t]);
-
-  return (
-    <>
-      {
-        renderAddNewBookmarkFolder()
-      }
+      {isCreateAction && (
+        <div className="col-12 mb-2 ">
+          <BookmarkFolderNameInput
+            onClickOutside={onClickonClickOutsideHandler}
+            onPressEnter={onPressEnterHandlerForCreate}
+          />
+        </div>
+      )}
       <BookmarkFolderTree />
       <BookmarkFolderTree />
     </>
     </>
   );
   );