Forráskód Böngészése

Drag and drop handler update

https://youtrack.weseek.co.jp/issue/GW-7920
- Create itemDropHandler to handle dropped item based on type
- Unite bookmarkItemDropRef and bookmarkFolderDropRef to single dropRef
- Implement itemDropHandler and isDroppable to BookmarkFolderTree componnet
- Add droppable area in BookmarkFolderTree component
- Update drag item in BookamrkItem component
- Remove mutateUserBookmarks from useDrag end
- Define new type of Drag item type
- Modify updateBookmarkFolder and insertOrUpdateBookmarkedPage in bookmark-folder model
- Adjust folderId express-validation
- Add and adjust custom styling from droppable area
- Update translation for drop area text
Mudana-Grune 3 éve
szülő
commit
13da199de4

+ 2 - 1
packages/app/public/static/locales/en_US/translation.json

@@ -823,7 +823,8 @@
     },
     "input_placeholder": "Input folder name",
     "new_folder": "New Folder",
-    "delete": "Delete Folder"
+    "delete": "Delete Folder",
+    "drop_item_here": "Drop item here"
   },
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page tree feature is not available yet.",

+ 2 - 1
packages/app/public/static/locales/ja_JP/translation.json

@@ -823,7 +823,8 @@
     },
     "input_placeholder": "フォルダ名を入力してください`",
     "new_folder": "新しいフォルダ",
-    "delete": "フォルダを削除"
+    "delete": "フォルダを削除",
+    "drop_item_here": "ここにアイテムをドロップ"
   },
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",

+ 2 - 1
packages/app/public/static/locales/zh_CN/translation.json

@@ -826,7 +826,8 @@
     },
     "input_placeholder": "输入文件夹名称",
     "new_folder": "新建文件夹",
-    "delete": "删除文件夹"
+    "delete": "删除文件夹",
+    "drop_item_here": "将项目放在这里"
   },
   "v5_page_migration": {
     "page_tree_not_avaliable": "Page Tree 功能不可用",

+ 48 - 40
packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -10,10 +10,10 @@ import { apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import FolderIcon from '~/components/Icons/FolderIcon';
 import TriangleIcon from '~/components/Icons/TriangleIcon';
-import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
-import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
+import { BookmarkFolderItems, DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
+import { IPageToDeleteWithMeta } from '~/interfaces/page';
 import { onDeletedBookmarkFolderFunction, OnDeletedFunction } from '~/interfaces/ui';
-import { useSWRBookmarkInfo } from '~/stores/bookmark';
+import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { useBookmarkFolderDeleteModal, usePageDeleteModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
@@ -30,7 +30,9 @@ type BookmarkFolderItemProps = {
   root: string
   isUserHomePage?: boolean
 }
+
 const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
+  const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const {
     bookmarkFolder, isOpen: _isOpen = false, level, root, isUserHomePage,
   } = props;
@@ -44,6 +46,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   const [isOpen, setIsOpen] = useState(_isOpen);
   const { data: childBookmarkFolderData, mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(targetFolder);
   const { mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild(parent);
+  const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const [isRenameAction, setIsRenameAction] = useState<boolean>(false);
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const { data: currentPage } = useSWRxCurrentPage();
@@ -162,56 +165,61 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   });
 
 
-  const folderItemDropHandler = async(item: BookmarkFolderItemProps) => {
-    try {
-      await apiv3Put('/bookmark-folder', { bookmarkFolderId: item.bookmarkFolder._id, name: item.bookmarkFolder.name, parent: bookmarkFolder._id });
-      await mutateChildBookmarkData();
-      toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
+  const itemDropHandler = async(item: any, dragItemType: string | null| symbol) => {
+    if (dragItemType === DRAG_ITEM_TYPE.FOLDER) {
+      try {
+        await apiv3Put('/bookmark-folder', { bookmarkFolderId: item.bookmarkFolder._id, name: item.bookmarkFolder.name, parent: bookmarkFolder._id });
+        await mutateChildBookmarkData();
+        toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
+      }
+      catch (err) {
+        toastError(err);
+      }
     }
-    catch (err) {
-      toastError(err);
+    else {
+      try {
+        await apiv3Post('/bookmark-folder/add-boookmark-to-folder', { pageId: item._id, folderId: bookmarkFolder._id });
+        await mutateParentBookmarkFolder();
+        await mutateUserBookmarks();
+        toastSuccess(t('toaster.add_succeeded', { target: t('bookmark_folder.bookmark'), ns: 'commons' }));
+      }
+      catch (err) {
+        toastError(err);
+      }
     }
   };
 
-  const bookmarkItemDropHandler = useCallback(async(item: IPageHasId) => {
-    try {
-      await apiv3Post('/bookmark-folder/add-boookmark-to-folder', { pageId: item._id, folderId: bookmarkFolder._id });
-      mutateParentBookmarkFolder();
-      toastSuccess(t('toaster.add_succeeded', { target: t('bookmark_folder.bookmark'), ns: 'commons' }));
-    }
-    catch (err) {
-      toastError(err);
+  const isDroppable = (item: any, targetRoot: string, targetLevel: number, type: any): boolean => {
+    if (type === DRAG_ITEM_TYPE.FOLDER) {
+      if (item.bookmarkFolder.parent === bookmarkFolder._id || item.bookmarkFolder._id === bookmarkFolder._id) {
+        return false;
+      }
+      return item.root !== targetRoot || item.level >= targetLevel;
     }
-
-  }, [bookmarkFolder._id, mutateParentBookmarkFolder, t]);
-
-
-  const isDroppable = (item: BookmarkFolderItemProps, targetRoot: string, targetLevel: number): boolean => {
-    if (item.bookmarkFolder.parent === bookmarkFolder._id || item.bookmarkFolder._id === bookmarkFolder._id) {
+    const bookmarks = bookmarkFolder.bookmarks;
+    const isBookmarkExists = bookmarks.filter(bookmark => bookmark.page._id === item._id).length > 0;
+    if (isBookmarkExists) {
       return false;
     }
-    return item.root !== targetRoot || item.level >= targetLevel;
+    return true;
   };
 
-  const [, bookmarkFolderDropRef] = useDrop(() => ({
-    accept: 'FOLDER',
-    drop: folderItemDropHandler,
-    canDrop: (item) => {
-      // Implement isDropable function & improve
-      return isDroppable(item, root, level);
+  const [, dropRef] = useDrop(() => ({
+    accept: acceptedTypes,
+    drop: (item: any, monitor) => {
+      const itemType = monitor.getItemType();
+      itemDropHandler(item, itemType);
+    },
+    canDrop: (item: any, monitor) => {
+      const itemType = monitor.getItemType();
+      return isDroppable(item, root, level, itemType);
     },
     collect: monitor => ({
-      isOver: monitor.isOver(),
+      isFolderOver: monitor.isOver({ shallow: true }) && monitor.canDrop(),
+      isBookmarkOver: monitor.isOver() && monitor.canDrop(),
     }),
   }));
 
-  const [, bookmarkItemDropRef] = useDrop(() => ({
-    accept: 'BOOKMARK',
-    drop: bookmarkItemDropHandler,
-    collect: monitor => ({
-      isOver: monitor.isOver(),
-    }),
-  }));
 
   const renderChildFolder = () => {
     return isOpen && currentChildren?.map((childFolder) => {
@@ -268,7 +276,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
 
   return (
     <div id={`grw-bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container">
-      <li ref={(c) => { bookmarkFolderDragRef(c); bookmarkFolderDropRef(c); bookmarkItemDropRef(c) }}
+      <li ref={(c) => { bookmarkFolderDragRef(c); dropRef(c) }}
         className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center"
         onClick={loadChildFolder}
       >

+ 12 - 0
packages/app/src/components/Bookmarks/BookmarkFolderTree.module.scss

@@ -1 +1,13 @@
 @use '~/styles/molecules/bookmark-folder-tree';
+
+.grw-folder-tree-container :global {
+  .grw-drop-item-area {
+    min-height: 90vh;
+    padding: 1rem;
+    .grw-accept-drop-item {
+      padding: 0.5rem;
+      border-style: dashed;
+      border-width: 0.15rem;
+    }
+  }
+}

+ 90 - 28
packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -2,8 +2,11 @@
 import { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
+import { useDrop } from 'react-dnd';
 
-import { toastSuccess } from '~/client/util/toastr';
+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 { OnDeletedFunction } from '~/interfaces/ui';
 import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
@@ -22,10 +25,11 @@ type BookmarkFolderTreeProps = {
 }
 
 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 } = useSWRxBookamrkFolderAndChild();
+  const { data: bookmarkFolderData, mutate: mutateParentBookmarkFolder } = useSWRxBookamrkFolderAndChild();
   const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
 
@@ -55,35 +59,93 @@ const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
   }, [mutateBookmarkInfo, mutateUserBookmarks, openDeleteModal, t]);
 
+  const itemDropHandler = async(item: any, 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();
+        toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
+      }
+      catch (err) {
+        toastError(err);
+      }
+    }
+    else {
+      try {
+        await apiv3Post('/bookmark-folder/add-boookmark-to-folder', { pageId: item._id, folderId: null });
+        await mutateUserBookmarks();
+        toastSuccess(t('toaster.add_succeeded', { target: t('bookmark_folder.bookmark'), ns: 'commons' }));
+      }
+      catch (err) {
+        toastError(err);
+      }
+    }
+
+  };
+  const isDroppable = (item: any, dragType: string | null | symbol) => {
+    if (dragType === DRAG_ITEM_TYPE.FOLDER) {
+      const isRootFolder = item.level === 0;
+      return !isRootFolder;
+    }
+    const isRootBookmark = item.parentFolder == null;
+    return !isRootBookmark;
+
+  };
+
+  const [{ isOver, canDrop }, dropRef] = useDrop(() => ({
+    accept: acceptedTypes,
+    drop: (item: any, monitor) => {
+      const dragType = monitor.getItemType();
+      itemDropHandler(item, dragType);
+    },
+    canDrop: (item: any, monitor) => {
+      const dragType = monitor.getItemType();
+      return isDroppable(item, dragType);
+    },
+    collect: monitor => ({
+      isOver: monitor.isOver({ shallow: true }) && monitor.canDrop(),
+      canDrop: monitor.canDrop(),
+    }),
+  }));
+
+
   return (
     <>
-      <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-3 pt-3`}>
-        {bookmarkFolderData?.map((item) => {
-          return (
-            <BookmarkFolderItem
-              key={item._id}
-              bookmarkFolder={item}
-              isOpen={false}
-              level={0}
-              root={item._id}
-              isUserHomePage={isUserHomePage}
-            />
-          );
-        })}
-        {userBookmarks?.map(page => (
-          <div key={page._id} className="grw-foldertree-item-container grw-root-bookmarks">
-            <BookmarkItem
-              bookmarkedPage={page}
-              key={page._id}
-              onUnbookmarked={onUnbookmarkHandler}
-              onRenamed={mutateUserBookmarks}
-              onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
-              parentFolder={null}
-            />
+      <div className={`grw-folder-tree-container ${styles['grw-folder-tree-container']}` } >
+        <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-2 py-2`}>
+          {bookmarkFolderData?.map((item) => {
+            return (
+              <BookmarkFolderItem
+                key={item._id}
+                bookmarkFolder={item}
+                isOpen={false}
+                level={0}
+                root={item._id}
+                isUserHomePage={isUserHomePage}
+              />
+            );
+          })}
+          {userBookmarks?.map(page => (
+            <div key={page._id} className="grw-foldertree-item-container grw-root-bookmarks">
+              <BookmarkItem
+                bookmarkedPage={page}
+                key={page._id}
+                onUnbookmarked={onUnbookmarkHandler}
+                onRenamed={mutateUserBookmarks}
+                onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
+                parentFolder={null}
+              />
+            </div>
+          ))}
+        </ul>
+        { bookmarkFolderData && bookmarkFolderData.length > 0 && (
+          <div ref={(c) => { dropRef(c) }} className= 'grw-drop-item-area' >
+            { canDrop && isOver && (
+              <div className='grw-accept-drop-item' >{t('bookmark_folder.drop_item_here')}</div>
+            )}
           </div>
-        ))}
-      </ul>
-
+        ) }
+      </div>
     </>
   );
 

+ 1 - 4
packages/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -12,7 +12,6 @@ import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
-import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { useSWRxPageInfo } from '~/stores/page';
 
@@ -42,7 +41,6 @@ const BookmarkItem = (props: Props): JSX.Element => {
   const { mutate: mutateParentBookmarkData } = useSWRxBookamrkFolderAndChild();
   const { mutate: mutateChildFolderData } = useSWRxBookamrkFolderAndChild(parentId);
   const { data: fetchedPageInfo, mutate: mutatePageInfo } = useSWRxPageInfo(bookmarkedPage._id);
-  const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
 
   useEffect(() => {
     mutatePageInfo();
@@ -115,10 +113,9 @@ const BookmarkItem = (props: Props): JSX.Element => {
 
   const [, bookmarkItemDragRef] = useDrag({
     type: 'BOOKMARK',
-    item: bookmarkedPage,
+    item: { ...bookmarkedPage, parentFolder },
     end: () => {
       setParentId(parentFolder?.parent);
-      mutateUserBookmarks();
     },
     collect: monitor => ({
       isDragging: monitor.isDragging(),

+ 11 - 0
packages/app/src/components/UsersHomePageFooter.module.scss

@@ -79,6 +79,17 @@ $grw-sidebar-content-footer-height: 50px;
         margin-bottom: 3px;
       }
     }
+    .grw-folder-tree-container {
+      .grw-drop-item-area {
+        min-height: 25vh;
+        padding: 1rem;
+        .grw-accept-drop-item {
+          padding: 0.5rem;
+          border-style: dashed;
+          border-width: 0.15rem;
+        }
+      }
+    }
   }
 }
 

+ 7 - 0
packages/app/src/interfaces/bookmark-info.ts

@@ -31,3 +31,10 @@ export interface BookmarkFolderItems {
   children: this[]
   bookmarks: BookmarkedPage[]
 }
+
+export const DRAG_ITEM_TYPE = {
+  FOLDER: 'FOLDER',
+  BOOKMARK: 'BOOKMARK',
+} as const;
+
+export type DragItemType = typeof DRAG_ITEM_TYPE[keyof typeof DRAG_ITEM_TYPE];

+ 19 - 10
packages/app/src/server/models/bookmark-folder.ts

@@ -29,8 +29,8 @@ export interface BookmarkFolderModel extends Model<BookmarkFolderDocument>{
   createByParameters(params: IBookmarkFolder): Promise<BookmarkFolderDocument>
   findFolderAndChildren(user: Types.ObjectId | string, parentId?: Types.ObjectId | string): Promise<BookmarkFolderItems[]>
   deleteFolderAndChildren(bookmarkFolderId: Types.ObjectId | string): Promise<{deletedCount: number}>
-  updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string): Promise<BookmarkFolderDocument>
-  insertOrUpdateBookmarkedPage(pageId: IPageHasId, userId: Types.ObjectId | string, folderId: string): Promise<BookmarkFolderDocument>
+  updateBookmarkFolder(bookmarkFolderId: string, name: string, parent: string | null): Promise<BookmarkFolderDocument>
+  insertOrUpdateBookmarkedPage(pageId: IPageHasId, userId: Types.ObjectId | string, folderId: string | null): Promise<BookmarkFolderDocument | null>
   findUserRootBookmarksItem(userId: Types.ObjectId| string): Promise<MyBookmarkList>
 }
 
@@ -136,12 +136,17 @@ bookmarkFolderSchema.statics.deleteFolderAndChildren = async function(bookmarkFo
   return { deletedCount };
 };
 
-bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolderId: string, name: string, parentId: string):
+bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolderId: string, name: string, parentId: string | null):
  Promise<BookmarkFolderDocument> {
-  const parentFolder = await this.findById(parentId);
-  const updateFields = {
-    name, parent: parentFolder?._id || null,
+  const updateFields: {name: string, parent: Types.ObjectId | null} = {
+    name: '',
+    parent: null,
   };
+
+  updateFields.name = name;
+  const parentFolder = parentId ? await this.findById(parentId) : null;
+  updateFields.parent = parentFolder?._id ?? null;
+
   const bookmarkFolder = await this.findByIdAndUpdate(bookmarkFolderId, { $set: updateFields }, { new: true });
   if (bookmarkFolder == null) {
     throw new Error('Update bookmark folder failed');
@@ -150,8 +155,8 @@ bookmarkFolderSchema.statics.updateBookmarkFolder = async function(bookmarkFolde
 
 };
 
-bookmarkFolderSchema.statics.insertOrUpdateBookmarkedPage = async function(pageId: IPageHasId, userId: Types.ObjectId | string, folderId: string):
-Promise<BookmarkFolderDocument> {
+bookmarkFolderSchema.statics.insertOrUpdateBookmarkedPage = async function(pageId: IPageHasId, userId: Types.ObjectId | string, folderId: string | null):
+Promise<BookmarkFolderDocument | null> {
 
   // Create bookmark or update existing
   const bookmarkedPage = await Bookmark.findOneAndUpdate({ page: pageId, user: userId }, { page: pageId, user: userId }, { new: true, upsert: true });
@@ -160,8 +165,12 @@ Promise<BookmarkFolderDocument> {
   await this.updateMany({}, { $pull: { bookmarks:  bookmarkedPage._id } });
 
   // Insert bookmark into bookmark folder
-  const bookmarkFolder = await this.findByIdAndUpdate(folderId, { $addToSet: { bookmarks: bookmarkedPage } }, { new: true, upsert: true });
-  return bookmarkFolder;
+  if (folderId != null) {
+    const bookmarkFolder = await this.findByIdAndUpdate(folderId, { $addToSet: { bookmarks: bookmarkedPage } }, { new: true, upsert: true });
+    return bookmarkFolder;
+  }
+
+  return null;
 };
 
 bookmarkFolderSchema.statics.findUserRootBookmarksItem = async function(userId: Types.ObjectId | string): Promise<MyBookmarkList> {

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

@@ -20,7 +20,7 @@ const validator = {
   ],
   bookmarkPage: [
     body('pageId').isMongoId().withMessage('Page ID must be a valid mongo ID'),
-    body('folderId').isMongoId().withMessage('Folder ID must be a valid mongo ID'),
+    body('folderId').optional({ nullable: true }).isMongoId().withMessage('Folder ID must be a valid mongo ID'),
   ],
 };
 

+ 1 - 1
packages/app/src/stores/bookmark.ts

@@ -1,4 +1,4 @@
-import { IUserHasId, Nullable } from '@growi/core';
+import { IUserHasId } from '@growi/core';
 import { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 

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

@@ -370,6 +370,14 @@
     }
 
     // bookmarks
+    .grw-folder-tree-container {
+      .grw-drop-item-area {
+        .grw-accept-drop-item {
+          background-color: hsl.lighten(var(--bgcolor-sidebar-context), 10%);
+          border-color: hsl.lighten(var(--bgcolor-sidebar-context), 30%);
+        }
+      }
+    }
     .grw-bookmarks-list {
       @include override-list-group-item-for-pagetree(
         var(--color-sidebar-context),
@@ -429,6 +437,14 @@
         hsl.lighten($body-bg, 24%)
       );
     }
+    .grw-folder-tree-container {
+      .grw-drop-item-area {
+        .grw-accept-drop-item {
+          background-color: hsl.lighten(var($body-bg), 10%);
+          border-color: hsl.lighten(var($body-bg), 30%);
+        }
+      }
+    }
   }
 
   // Bookmark dropdown menu

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

@@ -238,6 +238,15 @@
     }
 
     // bookmark
+    .grw-folder-tree-container {
+      .grw-drop-item-area {
+        .grw-accept-drop-item {
+          background-color: hsl.darken(var(--bgcolor-sidebar-context), 10%);
+          border-color: hsl.darken(var(--bgcolor-sidebar-context), 30%);
+        }
+      }
+    }
+
     .grw-bookmarks-list {
       @include override-list-group-item-for-pagetree(
         var(--color-sidebar-context),
@@ -300,6 +309,14 @@
         hsl.darken($body-bg, 24%)
       );
     }
+    .grw-folder-tree-container {
+      .grw-drop-item-area {
+        .grw-accept-drop-item {
+          background-color: hsl.darken(var($body-bg), 10%);
+          border-color: hsl.darken(var($body-bg), 30%);
+        }
+      }
+    }
   }
 
   // Bookmark dropdown menu