Parcourir la source

Update type and query

https://youtrack.weseek.co.jp/issue/GW-7920
- Update BookmarkFolderDocument type
- Change findFolderAndChildren method to create bookmark folder data and ref populated recursively
- Update bookmark list route
- Adjust bookmark mutation implementation
- Update types of useDrop and useDrag variables
- Update Drag item type with constant value
- Remove unused method since data fetching handled by single mutation
Mudana-Grune il y a 3 ans
Parent
commit
f599797de3

+ 29 - 52
packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -1,5 +1,5 @@
 import {
-  FC, useCallback, useEffect, useState,
+  FC, useCallback, useState,
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
@@ -11,7 +11,7 @@ import { toastError, toastSuccess } from '~/client/util/toastr';
 import FolderIcon from '~/components/Icons/FolderIcon';
 import TriangleIcon from '~/components/Icons/TriangleIcon';
 import { BookmarkFolderItems, DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
-import { IPageToDeleteWithMeta } from '~/interfaces/page';
+import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { onDeletedBookmarkFolderFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
@@ -31,6 +31,8 @@ type BookmarkFolderItemProps = {
   isUserHomePage?: boolean
 }
 
+type DragItemDataType = BookmarkFolderItemProps & IPageHasId;
+
 const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
   const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const {
@@ -41,11 +43,10 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   const {
     name, _id: folderId, children, parent, bookmarks,
   } = bookmarkFolder;
-  const [currentChildren, setCurrentChildren] = useState<BookmarkFolderItems[]>();
+
   const [targetFolder, setTargetFolder] = useState<string | null>(folderId);
   const [isOpen, setIsOpen] = useState(_isOpen);
-  const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(targetFolder);
-  const { mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild(parent);
+  const { mutate: mutateBookmarkData } = useSWRxBookamrkFolderAndChild();
   const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const [isRenameAction, setIsRenameAction] = useState<boolean>(false);
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
@@ -54,51 +55,27 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
 
-  useEffect(() => {
-    if (childBookmarkFolderData != null) {
-      mutateChildBookmarkData();
-      setCurrentChildren(childBookmarkFolderData);
-    }
-  }, [childBookmarkFolderData, mutateChildBookmarkData]);
-
   const hasChildren = useCallback((): boolean => {
-    if (currentChildren != null && currentChildren.length > children.length) {
-      return currentChildren.length > 0;
-    }
-    return children.length > 0;
-  }, [children.length, currentChildren]);
+    return children != null && children.length > 0;
+  }, [children]);
 
   const loadChildFolder = useCallback(async() => {
     setIsOpen(!isOpen);
     setTargetFolder(folderId);
   }, [folderId, isOpen]);
 
-  const loadParent = useCallback(async() => {
-    if (!isRenameAction) {
-      if (parent != null) {
-        await mutateParentBookmarkFolder();
-      }
-      // Reload root folder structure
-      setTargetFolder(null);
-    }
-    else {
-      await mutateParentBookmarkFolder();
-    }
-
-  }, [isRenameAction, mutateParentBookmarkFolder, parent]);
-
   // Rename  for bookmark folder handler
   const onPressEnterHandlerForRename = useCallback(async(folderName: string) => {
     try {
       await apiv3Put('/bookmark-folder', { bookmarkFolderId: folderId, name: folderName, parent });
-      loadParent();
+      mutateBookmarkData();
       setIsRenameAction(false);
       toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
     }
-  }, [folderId, loadParent, parent, t]);
+  }, [folderId, mutateBookmarkData, parent, t]);
 
   // Create new folder / subfolder handler
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
@@ -106,7 +83,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
       await apiv3Post('/bookmark-folder', { name: folderName, parent: targetFolder });
       setIsOpen(true);
       setIsCreateAction(false);
-      mutateChildBookmarkData();
+      mutateBookmarkData();
       toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
 
     }
@@ -114,7 +91,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
       toastError(err);
     }
 
-  }, [mutateChildBookmarkData, t, targetFolder]);
+  }, [mutateBookmarkData, t, targetFolder]);
 
 
   const onClickPlusButton = useCallback(async(e) => {
@@ -138,24 +115,24 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
       else {
         toastSuccess(t('deleted_pages', { path }));
       }
-      mutateParentBookmarkFolder();
+      mutateBookmarkData();
       mutateBookmarkInfo();
     };
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
-  }, [mutateBookmarkInfo, mutateParentBookmarkFolder, openDeleteModal, t]);
+  }, [mutateBookmarkInfo, mutateBookmarkData, openDeleteModal, t]);
 
   const onUnbookmarkHandler = useCallback(() => {
-    mutateParentBookmarkFolder();
+    mutateBookmarkData();
     mutateBookmarkInfo();
-  }, [mutateBookmarkInfo, mutateParentBookmarkFolder]);
+  }, [mutateBookmarkInfo, mutateBookmarkData]);
 
   const [, bookmarkFolderDragRef] = useDrag({
-    type: 'FOLDER',
+    type: DRAG_ITEM_TYPE.FOLDER,
     item: props,
-    end: (item, monitor) => {
+    end: (_item, monitor) => {
       const dropResult = monitor.getDropResult();
       if (dropResult != null) {
-        mutateParentBookmarkFolder();
+        mutateBookmarkData();
       }
     },
     collect: monitor => ({
@@ -165,11 +142,11 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   });
 
 
-  const itemDropHandler = async(item: any, dragItemType: string | null| symbol) => {
+  const itemDropHandler = async(item: DragItemDataType, dragItemType: string | symbol | null) => {
     if (dragItemType === DRAG_ITEM_TYPE.FOLDER) {
       try {
         await apiv3Put('/bookmark-folder', { bookmarkFolderId: item.bookmarkFolder._id, name: item.bookmarkFolder.name, parent: bookmarkFolder._id });
-        await mutateChildBookmarkData();
+        mutateBookmarkData();
         toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
       }
       catch (err) {
@@ -179,7 +156,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
     else {
       try {
         await apiv3Post('/bookmark-folder/add-boookmark-to-folder', { pageId: item._id, folderId: bookmarkFolder._id });
-        await mutateParentBookmarkFolder();
+        mutateBookmarkData();
         await mutateUserBookmarks();
         toastSuccess(t('toaster.add_succeeded', { target: t('bookmark_folder.bookmark'), ns: 'commons' }));
       }
@@ -189,7 +166,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
     }
   };
 
-  const isDroppable = (item: any, targetRoot: string, targetLevel: number, type: any): boolean => {
+  const isDroppable = (item: DragItemDataType, targetRoot: string, targetLevel: number, type: string | null| symbol): boolean => {
     if (type === DRAG_ITEM_TYPE.FOLDER) {
       if (item.bookmarkFolder.parent === bookmarkFolder._id || item.bookmarkFolder._id === bookmarkFolder._id) {
         return false;
@@ -206,11 +183,11 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
 
   const [, dropRef] = useDrop(() => ({
     accept: acceptedTypes,
-    drop: (item: any, monitor) => {
+    drop: (item: DragItemDataType, monitor) => {
       const itemType = monitor.getItemType();
       itemDropHandler(item, itemType);
     },
-    canDrop: (item: any, monitor) => {
+    canDrop: (item: DragItemDataType, monitor) => {
       const itemType = monitor.getItemType();
       return isDroppable(item, root, level, itemType);
     },
@@ -222,7 +199,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
 
 
   const renderChildFolder = () => {
-    return isOpen && currentChildren?.map((childFolder) => {
+    return isOpen && children?.map((childFolder) => {
       return (
         <div key={childFolder._id} className="grw-foldertree-item-children">
           <BookmarkFolderItem
@@ -245,7 +222,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
           bookmarkedPage={bookmark.page}
           key={bookmark._id}
           onUnbookmarked={onUnbookmarkHandler}
-          onRenamed={mutateParentBookmarkFolder}
+          onRenamed={mutateBookmarkData}
           onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
           parentFolder={bookmarkFolder}
         />
@@ -262,8 +239,8 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
       if (typeof folderId !== 'string') {
         return;
       }
-      loadParent();
       mutateBookmarkInfo();
+      mutateBookmarkData();
       toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
     };
 
@@ -271,7 +248,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
       return;
     }
     openDeleteBookmarkFolderModal(bookmarkFolder, { onDeleted: bookmarkFolderDeleteHandler });
-  }, [bookmarkFolder, loadParent, mutateBookmarkInfo, openDeleteBookmarkFolderModal, t]);
+  }, [bookmarkFolder, mutateBookmarkData, mutateBookmarkInfo, openDeleteBookmarkFolderModal, t]);
 
 
   return (

+ 8 - 32
packages/app/src/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -1,5 +1,5 @@
 import React, {
-  useCallback, useEffect, useState,
+  useCallback, useState,
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
@@ -29,41 +29,17 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
   const { children } = props;
   const [isCreateAction, setIsCreateAction] = useState(false);
   const { data: bookmarkFolders, mutate: mutateBookmarkFolderData } = useSWRxBookamrkFolderAndChild();
-  const [updateParent, setUpdateParent] = useState<string|null>(null);
-  const { mutate: mutateChildBookmarkFolderData } = useSWRxBookamrkFolderAndChild(updateParent);
   const [selectedItem, setSelectedItem] = useState<string | null>(null);
   const { data: currentPage } = useSWRxCurrentPage();
   const { data: userBookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
   const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const isBookmarked = userBookmarkInfo?.isBookmarked;
   const [isOpen, setIsOpen] = useState(false);
-  const [bookmarkData, setBookmarkData] = useState<BookmarkFolderItems[]>([]);
-
-
-  useEffect(() => {
-    if (updateParent) {
-      mutateBookmarkFolderData();
-      mutateChildBookmarkFolderData();
-    }
-  }, [mutateBookmarkFolderData, mutateChildBookmarkFolderData, updateParent]);
-
-  useEffect(() => {
-    if (!isOpen) {
-      setBookmarkData([]);
-    }
-    else if (bookmarkFolders != null) {
-      setBookmarkData(bookmarkFolders);
-    }
-  }, [bookmarkFolders, isOpen]);
 
   const toggleBookmarkHandler = useCallback(async() => {
 
     try {
-      const res = await apiv3Put('/bookmark-folder/update-bookmark', { pageId: currentPage?._id, status: isBookmarked });
-      const { bookmarkFolder } = res.data;
-      if (bookmarkFolder != null) {
-        setUpdateParent(bookmarkFolder.parent);
-      }
+      await apiv3Put('/bookmark-folder/update-bookmark', { pageId: currentPage?._id, status: isBookmarked });
       toastSuccess(t('toaster.add_succeeded', { target: t('bookmark_folder.bookmark'), ns: 'commons' }));
     }
     catch (err) {
@@ -85,8 +61,8 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
   const toggleHandler = useCallback(() => {
     setIsOpen(!isOpen);
     mutateBookmarkFolderData();
-    if (isOpen && bookmarkData != null) {
-      bookmarkData?.forEach((bookmarkFolder) => {
+    if (isOpen && bookmarkFolders != null) {
+      bookmarkFolders?.forEach((bookmarkFolder) => {
         bookmarkFolder.bookmarks.forEach((bookmark) => {
           if (bookmark.page._id === currentPage?._id) {
             if (bookmark.page._id === currentPage?._id) {
@@ -96,15 +72,15 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
         });
       });
     }
-  }, [bookmarkData, currentPage?._id, isOpen, mutateBookmarkFolderData]);
+  }, [bookmarkFolders, currentPage?._id, isOpen, mutateBookmarkFolderData]);
 
 
   const isBookmarkFolderExists = useCallback((): boolean => {
-    if (bookmarkData && bookmarkData.length > 0) {
+    if (bookmarkFolders && bookmarkFolders.length > 0) {
       return true;
     }
     return false;
-  }, [bookmarkData]);
+  }, [bookmarkFolders]);
 
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
     try {
@@ -168,7 +144,7 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
           {isBookmarkFolderExists() && (
             <>
               <DropdownItem divider />
-              {bookmarkData?.map(folder => (
+              {bookmarkFolders?.map(folder => (
                 <div key={folder._id}>
                   <div className='dropdown-item grw-bookmark-folder-menu-item' tabIndex={0} role="menuitem" onClick={() => onMenuItemClickHandler(folder._id)}>
                     <BookmarkFolderMenuItem

+ 33 - 30
packages/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx

@@ -6,7 +6,6 @@ import {
   DropdownMenu, DropdownToggle, UncontrolledDropdown, UncontrolledTooltip,
 } from 'reactstrap';
 
-import { unbookmark } from '~/client/services/page-operation';
 import { apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
@@ -33,9 +32,7 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
   } = props;
   const { t } = useTranslation();
   const [isOpen, setIsOpen] = useState(false);
-  const [currentChildFolders, setCurrentChildFolders] = useState<BookmarkFolderItems[]>();
-  const { data: childFolders, mutate: mutateChildFolders } = useSWRxBookamrkFolderAndChild(item._id);
-  const { mutate: mutateParentFolders } = useSWRxBookamrkFolderAndChild(item.parent);
+  const { mutate: mutateBookamrkData } = useSWRxBookamrkFolderAndChild();
   const [selectedItem, setSelectedItem] = useState<string | null>(null);
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const { data: currentPage } = useSWRxCurrentPage();
@@ -45,10 +42,14 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
 
   const isBookmarked = userBookmarkInfo?.isBookmarked;
 
+  const hasChildren = useCallback((): boolean => {
+    return item.children.length > 0;
+  }, [item.children.length]);
+
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
     try {
       await apiv3Post('/bookmark-folder', { name: folderName, parent: item._id });
-      await mutateChildFolders();
+      await mutateBookamrkData();
       setIsCreateAction(false);
       toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
     }
@@ -56,23 +57,19 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
       toastError(err);
     }
 
-  }, [item, mutateChildFolders, t]);
-
+  }, [item, mutateBookamrkData, t]);
 
   useEffect(() => {
-    if (isOpen && childFolders != null) {
-      mutateChildFolders();
-      setCurrentChildFolders(childFolders);
-    }
-    currentChildFolders?.forEach((bookmarkFolder) => {
-      bookmarkFolder.bookmarks.forEach((bookmark) => {
-        if (bookmark.page._id === currentPage?._id) {
-          setSelectedItem(bookmarkFolder._id);
-        }
+    if (isOpen) {
+      item.children?.forEach((bookmarkFolder) => {
+        bookmarkFolder.bookmarks.forEach((bookmark) => {
+          if (bookmark.page._id === currentPage?._id) {
+            setSelectedItem(bookmarkFolder._id);
+          }
+        });
       });
-    });
-
-  }, [childFolders, currentChildFolders, currentPage?._id, isOpen, item, mutateChildFolders, mutateParentFolders]);
+    }
+  }, [currentPage?._id, isOpen, item.children]);
 
   const onClickNewBookmarkFolder = useCallback((e) => {
     e.stopPropagation();
@@ -96,7 +93,7 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
         return;
       }
       mutateBookmarkInfo();
-      mutateParentFolders();
+      mutateBookamrkData();
       toastSuccess(t('toaster.delete_succeeded', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
     };
 
@@ -104,7 +101,7 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
       return;
     }
     openDeleteBookmarkFolderModal(item, { onDeleted: bookmarkFolderDeleteHandler });
-  }, [item, mutateBookmarkInfo, mutateParentFolders, openDeleteBookmarkFolderModal, t]);
+  }, [item, mutateBookamrkData, mutateBookmarkInfo, openDeleteBookmarkFolderModal, t]);
 
   const onClickChildMenuItemHandler = useCallback(async(e, item) => {
     e.stopPropagation();
@@ -121,20 +118,19 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
         mutateUserBookmarks();
       }
 
-      mutateParentFolders();
-      mutateChildFolders();
+      mutateBookamrkData();
       setSelectedItem(item._id);
       mutateBookmarkInfo();
     }
     catch (err) {
       toastError(err);
     }
-  }, [mutateBookmarkInfo, onSelectedChild, isBookmarked, mutateChildFolders, currentPage?._id, mutateUserBookmarks, t, mutateParentFolders]);
+  }, [onSelectedChild, isBookmarked, mutateBookamrkData, mutateBookmarkInfo, currentPage?._id, mutateUserBookmarks, t]);
 
   const renderBookmarkSubMenuItem = useCallback(() => {
     return (
       <>
-        {childFolders != null && (
+        {isOpen && (
           <DropdownMenu className='m-0'>
             {isCreateAction ? (
               <div className='mx-2' onClick={e => e.stopPropagation()}>
@@ -150,11 +146,10 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
               </DropdownItem>
             )}
 
-            {currentChildFolders && currentChildFolders?.length > 0 && (<DropdownItem divider />)}
+            {hasChildren() && (<DropdownItem divider />)}
 
-            {currentChildFolders?.map(child => (
+            {item.children?.map(child => (
               <div key={child._id} >
-
                 <div
                   className='dropdown-item grw-bookmark-folder-menu-item'
                   tabIndex={0} role="menuitem"
@@ -171,7 +166,15 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
         )}
       </>
     );
-  }, [childFolders, currentChildFolders, isCreateAction, onClickChildMenuItemHandler, onClickNewBookmarkFolder, onPressEnterHandlerForCreate, selectedItem, t]);
+  }, [hasChildren,
+      isCreateAction,
+      isOpen, item.children,
+      onClickChildMenuItemHandler,
+      onClickNewBookmarkFolder,
+      onPressEnterHandlerForCreate,
+      t,
+      selectedItem,
+  ]);
 
   return (
     <>
@@ -209,7 +212,7 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
           onClick={e => e.stopPropagation()}
           onMouseEnter={onMouseEnterHandler}
         >
-          {childFolders && childFolders?.length > 0
+          {hasChildren()
             ? <TriangleIcon />
             : (
               <i className="icon-plus d-block pl-0" />

+ 16 - 9
packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -6,8 +6,8 @@ import { useDrop } from 'react-dnd';
 
 import { apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import { DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
-import { IPageToDeleteWithMeta } from '~/interfaces/page';
+import { BookmarkFolderItems, DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
+import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { OnDeletedFunction } from '~/interfaces/ui';
 import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
@@ -24,12 +24,18 @@ type BookmarkFolderTreeProps = {
   isUserHomePage?: boolean
 }
 
+type DragItemDataType = {
+  bookmarkFolder: BookmarkFolderItems
+  level: number
+  parentFolder: BookmarkFolderItems | null
+ } & IPageHasId
+
 const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
   const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const { t } = useTranslation();
   const { isUserHomePage } = props;
   const { data: currentPage } = useSWRxCurrentPage();
-  const { data: bookmarkFolderData, mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild();
+  const { data: bookmarkFolderData, mutate: mutateBookamrkData } = useSWRxBookamrkFolderAndChild();
   const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
 
@@ -55,15 +61,16 @@ const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
       }
       mutateUserBookmarks();
       mutateBookmarkInfo();
+      mutateBookamrkData();
     };
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
-  }, [mutateBookmarkInfo, mutateUserBookmarks, openDeleteModal, t]);
+  }, [mutateBookmarkInfo, mutateBookamrkData, mutateUserBookmarks, openDeleteModal, t]);
 
-  const itemDropHandler = async(item: any, dragType: string | null | symbol) => {
+  const itemDropHandler = async(item: DragItemDataType, dragType: string | null | symbol) => {
     if (dragType === DRAG_ITEM_TYPE.FOLDER) {
       try {
         await apiv3Put('/bookmark-folder', { bookmarkFolderId: item.bookmarkFolder._id, name: item.bookmarkFolder.name, parent: null });
-        await mutateParentBookmarkFolder();
+        await mutateBookamrkData();
         toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
       }
       catch (err) {
@@ -82,7 +89,7 @@ const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
     }
 
   };
-  const isDroppable = (item: any, dragType: string | null | symbol) => {
+  const isDroppable = (item: DragItemDataType, dragType: string | null | symbol) => {
     if (dragType === DRAG_ITEM_TYPE.FOLDER) {
       const isRootFolder = item.level === 0;
       return !isRootFolder;
@@ -94,11 +101,11 @@ const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
 
   const [{ isOver, canDrop }, dropRef] = useDrop(() => ({
     accept: acceptedTypes,
-    drop: (item: any, monitor) => {
+    drop: (item: DragItemDataType, monitor) => {
       const dragType = monitor.getItemType();
       itemDropHandler(item, dragType);
     },
-    canDrop: (item: any, monitor) => {
+    canDrop: (item: DragItemDataType, monitor) => {
       const dragType = monitor.getItemType();
       return isDroppable(item, dragType);
     },

+ 10 - 14
packages/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -10,7 +10,7 @@ import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
 import { unbookmark } from '~/client/services/page-operation';
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+import { BookmarkFolderItems, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
 import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { useSWRxPageInfo } from '~/stores/page';
@@ -37,18 +37,12 @@ const BookmarkItem = (props: Props): JSX.Element => {
   const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
   const { latter: pageTitle, former: formerPagePath } = dPagePath;
   const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
-  const [parentId, setParentId] = useState(parentFolder?._id);
-  const { mutate: mutateParentBookmarkData } = useSWRxBookamrkFolderAndChild();
-  const { mutate: mutateChildFolderData } = useSWRxBookamrkFolderAndChild(parentId);
-  const { data: fetchedPageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(bookmarkedPage._id);
+  const { mutate: mutateBookamrkData } = useSWRxBookamrkFolderAndChild();
+  const { data: fetchedPageInfo } = useSWRxPageInfo(bookmarkedPage._id);
 
   useEffect(() => {
-    mutatePageInfo();
-    if (parentId != null) {
-      mutateChildFolderData();
-    }
-    mutateParentBookmarkData();
-  }, [parentId, mutateChildFolderData, mutatePageInfo, mutateParentBookmarkData]);
+    mutateBookamrkData();
+  }, [mutateBookamrkData]);
 
   const bookmarkMenuItemClickHandler = useCallback(async() => {
     await unbookmark(bookmarkedPage._id);
@@ -112,10 +106,12 @@ const BookmarkItem = (props: Props): JSX.Element => {
   }, [bookmarkedPage, onClickDeleteMenuItem]);
 
   const [, bookmarkItemDragRef] = useDrag({
-    type: 'BOOKMARK',
+    type: DRAG_ITEM_TYPE.BOOKMARK,
     item: { ...bookmarkedPage, parentFolder },
-    end: () => {
-      setParentId(parentFolder?.parent);
+    end: (_, monitor) => {
+      if (monitor.getDropResult() != null) {
+        mutateBookamrkData();
+      }
     },
     collect: monitor => ({
       isDragging: monitor.isDragging(),

+ 1 - 1
packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx

@@ -14,7 +14,7 @@ const BookmarkContents = (): JSX.Element => {
 
   const { t } = useTranslation();
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
-  const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null);
+  const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild();
 
 
   const onClickNewBookmarkFolder = useCallback(() => {

+ 3 - 2
packages/app/src/components/UsersHomePageFooter.tsx

@@ -2,8 +2,9 @@ import React, { useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import { toastError, toastSuccess } from '~/client/util/apiNotification';
+
 import { apiv3Post } from '~/client/util/apiv3-client';
+import { toastError, toastSuccess } from '~/client/util/toastr';
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 import { RecentCreated } from '~/components/RecentCreated/RecentCreated';
 import styles from '~/components/UsersHomePageFooter.module.scss';
@@ -25,7 +26,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
   const { creatorId } = props;
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const [isExpanded, setIsExpanded] = useState<boolean>(false);
-  const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null);
+  const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild();
 
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
     try {

+ 29 - 23
packages/app/src/server/models/bookmark-folder.ts

@@ -21,8 +21,9 @@ export interface BookmarkFolderDocument extends Document {
   _id: Types.ObjectId
   name: string
   owner: Types.ObjectId
-  parent?: this[]
-  bookmarks?: Types.ObjectId[]
+  parent?: Types.ObjectId | undefined
+  bookmarks?: Types.ObjectId[],
+  children?: BookmarkFolderDocument[]
 }
 
 export interface BookmarkFolderModel extends Model<BookmarkFolderDocument>{
@@ -84,25 +85,10 @@ bookmarkFolderSchema.statics.findFolderAndChildren = async function(
     userId: Types.ObjectId | string,
     parentId?: Types.ObjectId | string,
 ): Promise<BookmarkFolderItems[]> {
+  const folderItems: BookmarkFolderItems[] = [];
 
-  let parentFolder: BookmarkFolderDocument | null;
-  let query = {};
-  // Load child bookmark folders
-  if (parentId != null) {
-    parentFolder = await this.findById(parentId);
-    if (parentFolder != null) {
-      query = { owner: userId, parent: parentFolder };
-    }
-    else {
-      throw new InvalidParentBookmarkFolderError('Parent folder not found');
-    }
-  }
-  // Load initial / root bookmark folders
-  else {
-    query = { owner: userId, parent: null };
-  }
-  const bookmarkFolders: BookmarkFolderItems[] = await this.find(query)
-    .populate({ path: 'children' })
+  const folders = await this.find({ owner: userId, parent: parentId })
+    .populate('children')
     .populate({
       path: 'bookmarks',
       model: 'Bookmark',
@@ -111,7 +97,27 @@ bookmarkFolderSchema.statics.findFolderAndChildren = async function(
         model: 'Page',
       },
     });
-  return bookmarkFolders;
+
+  const promises = folders.map(async(folder) => {
+    const children = await this.findFolderAndChildren(userId, folder._id);
+    const {
+      _id, name, owner, bookmarks, parent,
+    } = folder;
+
+    const res = {
+      _id: _id.toString(),
+      name,
+      owner,
+      bookmarks,
+      children,
+      parent,
+    };
+    return res;
+  });
+
+  const results = await Promise.all(promises) as unknown as BookmarkFolderItems[];
+  folderItems.push(...results);
+  return folderItems;
 };
 
 bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(bookmarkFolderId: Types.ObjectId | string): Promise<{deletedCount: number}> {
@@ -125,12 +131,12 @@ bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(bookmarkFo
       await Bookmark.deleteMany({ _id: { $in: bookmarks } });
     }
     // Delete all child recursively and update deleted count
-    const childFolders = await this.find({ parent: bookmarkFolder });
+    const childFolders = await this.find({ parent: bookmarkFolder._id });
     await Promise.all(childFolders.map(async(child) => {
       const deletedChildFolder = await this.deleteFolderAndChildren(child._id);
       deletedCount += deletedChildFolder.deletedCount;
     }));
-    const deletedChild = await this.deleteMany({ parent: bookmarkFolder });
+    const deletedChild = await this.deleteMany({ parent: bookmarkFolder._id });
     deletedCount += deletedChild.deletedCount + 1;
     bookmarkFolder.delete();
   }

+ 5 - 11
packages/app/src/server/routes/apiv3/bookmark-folder.ts

@@ -1,6 +1,7 @@
 import { ErrorV3 } from '@growi/core';
 import { body } from 'express-validator';
 
+import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { InvalidParentBookmarkFolderError } from '~/server/models/errors';
 import loggerFactory from '~/utils/logger';
@@ -54,18 +55,11 @@ module.exports = (crowi) => {
   });
 
   // List bookmark folders and child
-  router.get('/list/:parentId?', accessTokenParser, loginRequiredStrictly, async(req, res) => {
-    const { parentId } = req.params;
-    const _parentId = parentId ?? null;
+  router.get('/list', accessTokenParser, loginRequiredStrictly, async(req, res) => {
+
     try {
-      const bookmarkFolders = await BookmarkFolder.findFolderAndChildren(req.user?._id, _parentId);
-      const bookmarkFolderItems = bookmarkFolders.map(bookmarkFolder => ({
-        _id: bookmarkFolder._id,
-        name: bookmarkFolder.name,
-        parent: bookmarkFolder.parent,
-        children: bookmarkFolder.children,
-        bookmarks: bookmarkFolder.bookmarks,
-      }));
+      const bookmarkFolderItems = await BookmarkFolder.findFolderAndChildren(req.user?._id);
+
       return res.apiv3({ bookmarkFolderItems });
     }
     catch (err) {

+ 3 - 4
packages/app/src/stores/bookmark-folder.ts

@@ -1,14 +1,13 @@
-import { Nullable } from '@growi/core';
 import { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 
-export const useSWRxBookamrkFolderAndChild = (parentId?: Nullable<string>): SWRResponse<BookmarkFolderItems[], Error> => {
-  const _parentId = parentId == null ? '' : parentId;
+export const useSWRxBookamrkFolderAndChild = (): SWRResponse<BookmarkFolderItems[], Error> => {
+
   return useSWRImmutable(
-    `/bookmark-folder/list/${_parentId}`,
+    '/bookmark-folder/list',
     endpoint => apiv3Get(endpoint).then((response) => {
       return response.data.bookmarkFolderItems;
     }),