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

Merge pull request #7586 from weseek/feat/115447-119721-update-dnd-root-move

feat: Update bookmarks tree move to root features
Ryoji Shimizu 2 лет назад
Родитель
Сommit
afe9941586

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

@@ -790,7 +790,8 @@
     "new_folder": "New Folder",
     "delete": "Delete Folder",
     "drop_item_here": "Drag and drop item here",
-    "cancel_bookmark": "Un-bookmark this page"
+    "cancel_bookmark": "Un-bookmark this page",
+    "Move to the root": "Move to the root"
   },
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page tree feature is not available yet.",

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

@@ -824,7 +824,8 @@
     "new_folder": "新しいフォルダ",
     "delete": "フォルダを削除",
     "drop_item_here": "ルートに配置する",
-    "cancel_bookmark": "このページのブックマークを解除"
+    "cancel_bookmark": "このページのブックマークを解除",
+    "move_to_root": "ルートに配置する"
   },
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",

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

@@ -794,7 +794,8 @@
     "new_folder": "新建文件夹",
     "delete": "删除文件夹",
     "drop_item_here": "将项目拖放到此处",
-    "cancel_bookmark": "取消收藏此页面"
+    "cancel_bookmark": "取消收藏此页面",
+    "move_to_root": "移动到根部"
   },
   "v5_page_migration": {
     "page_tree_not_avaliable": "Page Tree 功能不可用",

+ 37 - 58
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -2,37 +2,34 @@ import {
   FC, useCallback, useState,
 } from 'react';
 
-import { useTranslation } from 'next-i18next';
 import { DropdownToggle } from 'reactstrap';
 
 import {
   addBookmarkToFolder, addNewFolder, hasChildren, updateBookmarkFolder,
 } from '~/client/util/bookmark-utils';
-import { toastError, toastSuccess } from '~/client/util/toastr';
+import { toastError } from '~/client/util/toastr';
 import { FolderIcon } from '~/components/Icons/FolderIcon';
 import { TriangleIcon } from '~/components/Icons/TriangleIcon';
 import {
   BookmarkFolderItems, DragItemDataType, DragItemType, DRAG_ITEM_TYPE,
 } from '~/interfaces/bookmark-info';
 import { IPageToDeleteWithMeta } from '~/interfaces/page';
-import { onDeletedBookmarkFolderFunction, OnDeletedFunction } from '~/interfaces/ui';
-import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
-import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
-import { useBookmarkFolderDeleteModal, usePageDeleteModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
+import { onDeletedBookmarkFolderFunction } from '~/interfaces/ui';
+import { useBookmarkFolderDeleteModal } from '~/stores/modal';
 
 import { BookmarkFolderItemControl } from './BookmarkFolderItemControl';
 import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 import { BookmarkItem } from './BookmarkItem';
 import { DragAndDropWrapper } from './DragAndDropWrapper';
 
-
 type BookmarkFolderItemProps = {
   bookmarkFolder: BookmarkFolderItems
   isOpen?: boolean
   level: number
   root: string
   isUserHomePage?: boolean
+  onClickDeleteBookmarkHandler: (pageToDelete: IPageToDeleteWithMeta) => void
+  bookmarkFolderTreeMutation: () => void
 }
 
 export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
@@ -40,22 +37,18 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
   const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const {
     bookmarkFolder, isOpen: _isOpen = false, level, root, isUserHomePage,
+    onClickDeleteBookmarkHandler, bookmarkFolderTreeMutation,
   } = props;
 
-  const { t } = useTranslation();
   const {
     name, _id: folderId, children, parent, bookmarks,
   } = bookmarkFolder;
 
   const [targetFolder, setTargetFolder] = useState<string | null>(folderId);
   const [isOpen, setIsOpen] = useState(_isOpen);
-  const { mutate: mutateBookmarkData } = useSWRxBookamrkFolderAndChild();
-  const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const [isRenameAction, setIsRenameAction] = 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: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
 
   const childrenExists = hasChildren(children);
@@ -67,17 +60,17 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
     setTargetFolder(folderId);
   }, [folderId, isOpen]);
 
-  // Rename  for bookmark folder handler
+  // Rename for bookmark folder handler
   const onPressEnterHandlerForRename = useCallback(async(folderName: string) => {
     try {
       await updateBookmarkFolder(folderId, folderName, parent);
-      mutateBookmarkData();
+      bookmarkFolderTreeMutation();
       setIsRenameAction(false);
     }
     catch (err) {
       toastError(err);
     }
-  }, [folderId, mutateBookmarkData, parent]);
+  }, [bookmarkFolderTreeMutation, folderId, parent]);
 
   // Create new folder / subfolder handler
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
@@ -85,13 +78,12 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
       await addNewFolder(folderName, targetFolder);
       setIsOpen(true);
       setIsCreateAction(false);
-      mutateBookmarkData();
+      bookmarkFolderTreeMutation();
     }
     catch (err) {
       toastError(err);
     }
-  }, [mutateBookmarkData, targetFolder]);
-
+  }, [bookmarkFolderTreeMutation, targetFolder]);
 
   const onClickPlusButton = useCallback(async(e) => {
     e.stopPropagation();
@@ -101,37 +93,12 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
     setIsCreateAction(true);
   }, [childrenExists, isOpen]);
 
-  const onClickDeleteBookmarkHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
-    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 }));
-      }
-      mutateBookmarkData();
-      mutateBookmarkInfo();
-    };
-    openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
-  }, [mutateBookmarkInfo, mutateBookmarkData, openDeleteModal, t]);
-
-  const onUnbookmarkHandler = useCallback(() => {
-    mutateBookmarkData();
-    mutateBookmarkInfo();
-  }, [mutateBookmarkInfo, mutateBookmarkData]);
-
-
   const itemDropHandler = async(item: DragItemDataType, dragItemType: string | symbol | null) => {
     if (dragItemType === DRAG_ITEM_TYPE.FOLDER) {
       try {
         if (item.bookmarkFolder != null) {
           await updateBookmarkFolder(item.bookmarkFolder._id, item.bookmarkFolder.name, bookmarkFolder._id);
-          mutateBookmarkData();
+          bookmarkFolderTreeMutation();
         }
       }
       catch (err) {
@@ -142,8 +109,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
       try {
         if (item != null) {
           await addBookmarkToFolder(item._id, bookmarkFolder._id);
-          mutateBookmarkData();
-          await mutateUserBookmarks();
+          bookmarkFolderTreeMutation();
         }
       }
       catch (err) {
@@ -174,7 +140,6 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
     return true;
   };
 
-
   const renderChildFolder = () => {
     return isOpen && children?.map((childFolder) => {
       return (
@@ -184,7 +149,9 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
             bookmarkFolder={childFolder}
             level={level + 1}
             root={root}
-            isUserHomePage ={isUserHomePage}
+            isUserHomePage={isUserHomePage}
+            onClickDeleteBookmarkHandler={onClickDeleteBookmarkHandler}
+            bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
           />
         </div>
       );
@@ -195,13 +162,13 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
     return isOpen && bookmarks?.map((bookmark) => {
       return (
         <BookmarkItem
-          bookmarkedPage={bookmark.page}
           key={bookmark._id}
-          onUnbookmarked={onUnbookmarkHandler}
-          onRenamed={mutateBookmarkData}
-          onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
-          parentFolder={bookmarkFolder}
+          bookmarkedPage={bookmark.page}
           level={level + 1}
+          parentFolder={bookmarkFolder}
+          canMoveToRoot={true}
+          onClickDeleteBookmarkHandler={onClickDeleteBookmarkHandler}
+          bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
         />
       );
     });
@@ -216,16 +183,24 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
       if (typeof folderId !== 'string') {
         return;
       }
-      mutateBookmarkInfo();
-      mutateBookmarkData();
+      bookmarkFolderTreeMutation();
     };
 
     if (bookmarkFolder == null) {
       return;
     }
     openDeleteBookmarkFolderModal(bookmarkFolder, { onDeleted: bookmarkFolderDeleteHandler });
-  }, [bookmarkFolder, mutateBookmarkData, mutateBookmarkInfo, openDeleteBookmarkFolderModal]);
+  }, [bookmarkFolder, bookmarkFolderTreeMutation, openDeleteBookmarkFolderModal]);
 
+  const onClickMoveToRootHandlerForBookmarkFolderItemControl = useCallback(async() => {
+    try {
+      await updateBookmarkFolder(bookmarkFolder._id, bookmarkFolder.name, null);
+      bookmarkFolderTreeMutation();
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [bookmarkFolder._id, bookmarkFolder.name, bookmarkFolderTreeMutation]);
 
   return (
     <div id={`grw-bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container">
@@ -278,6 +253,10 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
             <BookmarkFolderItemControl
               onClickRename={onClickRenameHandler}
               onClickDelete={onClickDeleteHandler}
+              onClickMoveToRoot={bookmarkFolder.parent != null
+                ? onClickMoveToRootHandlerForBookmarkFolderItemControl
+                : undefined
+              }
             >
               <div onClick={e => e.stopPropagation()}>
                 <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">

+ 23 - 8
apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx

@@ -5,16 +5,20 @@ import {
   Dropdown, DropdownItem, DropdownMenu, DropdownToggle,
 } from 'reactstrap';
 
-
-type BookmarkFolderItemControlProps = {
+export const BookmarkFolderItemControl: React.FC<{
   children?: React.ReactNode
+  onClickMoveToRoot?: () => Promise<void>
   onClickRename: () => void
   onClickDelete: () => void
-}
-export const BookmarkFolderItemControl = (props: BookmarkFolderItemControlProps): JSX.Element => {
+}> = ({
+  children,
+  onClickMoveToRoot,
+  onClickRename,
+  onClickDelete,
+}): JSX.Element => {
   const { t } = useTranslation();
-  const { children, onClickRename, onClickDelete } = props;
   const [isOpen, setIsOpen] = useState(false);
+
   return (
     <Dropdown isOpen={isOpen} toggle={() => setIsOpen(!isOpen)}>
       { children ?? (
@@ -23,18 +27,29 @@ export const BookmarkFolderItemControl = (props: BookmarkFolderItemControlProps)
         </DropdownToggle>
       ) }
       <DropdownMenu
-        positionFixed
-        modifiers={{ preventOverflow: { boundariesElement: undefined } }}
-        right={true}
+        modifiers={{ preventOverflow: { boundariesElement: 'viewport' } }}
+        container="body"
+        style={{ zIndex: 1055 }} /* make it larger than $zindex-modal of bootstrap */
       >
+        {onClickMoveToRoot && (
+          <DropdownItem
+            onClick={onClickMoveToRoot}
+            className="grw-page-control-dropdown-item"
+          >
+            <i className="fa fa-fw fa-bookmark-o grw-page-control-dropdown-icon"></i>
+            {t('bookmark_folder.move_to_root')}
+          </DropdownItem>
+        )}
         <DropdownItem
           onClick={onClickRename}
+          className="grw-page-control-dropdown-item"
         >
           <i className="icon-fw icon-action-redo grw-page-control-dropdown-icon"></i>
           {t('Rename')}
         </DropdownItem>
 
         <DropdownItem divider/>
+
         <DropdownItem
           className='pt-2 grw-page-control-dropdown-item text-danger'
           onClick={onClickDelete}

+ 71 - 27
apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -10,8 +10,10 @@ import {
 import { addBookmarkToFolder, addNewFolder, toggleBookmark } from '~/client/util/bookmark-utils';
 import { toastError } from '~/client/util/toastr';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+import { onDeletedBookmarkFolderFunction } from '~/interfaces/ui';
 import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
-import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
+import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
+import { useBookmarkFolderDeleteModal } from '~/stores/modal';
 import { useSWRxCurrentPage, useSWRxPageInfo } from '~/stores/page';
 
 import { FolderIcon } from '../Icons/FolderIcon';
@@ -19,24 +21,21 @@ import { FolderIcon } from '../Icons/FolderIcon';
 import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
 import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 
-
-type Props = {
-  children?: React.ReactNode
-}
-
-export const BookmarkFolderMenu = (props: Props): JSX.Element => {
+export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ children }): JSX.Element => {
   const { t } = useTranslation();
-  const { children } = props;
+
   const [isCreateAction, setIsCreateAction] = useState(false);
-  const { data: bookmarkFolders, mutate: mutateBookmarkFolderData } = useSWRxBookamrkFolderAndChild();
   const [selectedItem, setSelectedItem] = useState<string | null>(null);
+  const [isOpen, setIsOpen] = useState(false);
+
+  const { data: bookmarkFolders, mutate: mutateBookmarkFolders } = useSWRxBookmarkFolderAndChild();
   const { data: currentPage } = useSWRxCurrentPage();
-  const { data: userBookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
+  const { data: bookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
   const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { mutate: mutatePageInfo } = useSWRxPageInfo(currentPage?._id);
-  const isBookmarked = userBookmarkInfo?.isBookmarked ?? false;
-  const [isOpen, setIsOpen] = useState(false);
+  const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
 
+  const isBookmarked = bookmarkInfo?.isBookmarked ?? false;
 
   const toggleBookmarkHandler = useCallback(async() => {
     try {
@@ -57,14 +56,14 @@ export const BookmarkFolderMenu = (props: Props): JSX.Element => {
     await toggleBookmarkHandler();
     mutateUserBookmarks();
     mutateBookmarkInfo();
-    mutateBookmarkFolderData();
+    mutateBookmarkFolders();
     mutatePageInfo();
     setSelectedItem(null);
-  }, [mutateBookmarkFolderData, mutateBookmarkInfo, mutatePageInfo, mutateUserBookmarks, toggleBookmarkHandler]);
+  }, [mutateBookmarkFolders, mutateBookmarkInfo, mutatePageInfo, mutateUserBookmarks, toggleBookmarkHandler]);
 
   const toggleHandler = useCallback(async() => {
     setIsOpen(!isOpen);
-    mutateBookmarkFolderData();
+    mutateBookmarkFolders();
     if (isOpen && bookmarkFolders != null) {
       bookmarkFolders.forEach((bookmarkFolder) => {
         bookmarkFolder.bookmarks.forEach((bookmark) => {
@@ -89,7 +88,7 @@ export const BookmarkFolderMenu = (props: Props): JSX.Element => {
   },
   [
     isOpen,
-    mutateBookmarkFolderData,
+    mutateBookmarkFolders,
     bookmarkFolders,
     isBookmarked,
     currentPage?._id,
@@ -99,21 +98,20 @@ export const BookmarkFolderMenu = (props: Props): JSX.Element => {
     mutatePageInfo,
   ]);
 
-
   const isBookmarkFolderExists = useCallback((): boolean => {
     return bookmarkFolders != null && bookmarkFolders.length > 0;
   }, [bookmarkFolders]);
 
-  const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
+  const onPressEnterHandlerForCreate = useCallback(async(folderName: string, item?: BookmarkFolderItems) => {
     try {
-      await addNewFolder(folderName, null);
-      await mutateBookmarkFolderData();
+      await addNewFolder(folderName, item ? item._id : null);
+      await mutateBookmarkFolders();
       setIsCreateAction(false);
     }
     catch (err) {
       toastError(err);
     }
-  }, [mutateBookmarkFolderData]);
+  }, [mutateBookmarkFolders]);
 
   const onMenuItemClickHandler = useCallback(async(itemId: string) => {
     try {
@@ -130,14 +128,54 @@ export const BookmarkFolderMenu = (props: Props): JSX.Element => {
       toastError(err);
     }
 
-    mutateBookmarkFolderData();
+    mutateBookmarkFolders();
     setSelectedItem(itemId);
-  }, [mutateBookmarkFolderData, isBookmarked, currentPage, mutateBookmarkInfo, mutateUserBookmarks, toggleBookmarkHandler]);
+  }, [mutateBookmarkFolders, isBookmarked, currentPage, mutateBookmarkInfo, mutateUserBookmarks, toggleBookmarkHandler]);
+
+  // Delete folder handler
+  const onClickDeleteHandler = useCallback(async(e, item) => {
+    e.stopPropagation();
+
+    const bookmarkFolderDeleteHandler: onDeletedBookmarkFolderFunction = (folderId) => {
+      if (typeof folderId !== 'string') {
+        return;
+      }
+      mutateBookmarkInfo();
+      mutateBookmarkFolders();
+    };
+
+    if (item == null) {
+      return;
+    }
+    openDeleteBookmarkFolderModal(item, { onDeleted: bookmarkFolderDeleteHandler });
+  }, [mutateBookmarkFolders, mutateBookmarkInfo, openDeleteBookmarkFolderModal]);
+
+  const onClickChildMenuItemHandler = useCallback(async(e, item) => {
+    e.stopPropagation();
+
+    setSelectedItem(null);
+
+    try {
+      if (isBookmarked && currentPage != null) {
+        await toggleBookmark(currentPage._id, isBookmarked);
+      }
+      if (currentPage != null) {
+        await addBookmarkToFolder(currentPage._id, item._id);
+      }
+      mutateUserBookmarks();
+      mutateBookmarkFolders();
+      setSelectedItem(item._id);
+      mutateBookmarkInfo();
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [isBookmarked, currentPage, mutateUserBookmarks, mutateBookmarkFolders, mutateBookmarkInfo]);
 
 
   const renderBookmarkMenuItem = (child?: BookmarkFolderItems[]) => {
     const renderSubmenu = () => {
-      if (child == null) {
+      if (child == null || currentPage == null || bookmarkInfo == null) {
         return <></>;
       }
       return (
@@ -153,7 +191,10 @@ export const BookmarkFolderMenu = (props: Props): JSX.Element => {
                 <BookmarkFolderMenuItem
                   item={folder}
                   isSelected={selectedItem === folder._id}
-                  onSelectedChild={() => setSelectedItem(null)}
+                  currentPage={currentPage}
+                  onClickDeleteHandler={onClickDeleteHandler}
+                  onClickChildMenuItemHandler={onClickChildMenuItemHandler}
+                  onPressEnterHandlerForCreate={onPressEnterHandlerForCreate}
                 />
                 {isOpen && renderSubmenu()}
               </div>
@@ -199,7 +240,7 @@ export const BookmarkFolderMenu = (props: Props): JSX.Element => {
           </DropdownItem>
         )}
 
-        {isBookmarkFolderExists() && (
+        {isBookmarkFolderExists() && currentPage != null && bookmarkInfo != null && (
           <>
             <DropdownItem divider />
             {bookmarkFolders?.map(folder => (
@@ -213,7 +254,10 @@ export const BookmarkFolderMenu = (props: Props): JSX.Element => {
                   <BookmarkFolderMenuItem
                     item={folder}
                     isSelected={selectedItem === folder._id}
-                    onSelectedChild={() => setSelectedItem(null)}
+                    currentPage={currentPage}
+                    onClickDeleteHandler={onClickDeleteHandler}
+                    onClickChildMenuItemHandler={onClickChildMenuItemHandler}
+                    onPressEnterHandlerForCreate={onPressEnterHandlerForCreate}
                   />
                   {isOpen && renderSubmenu()}
                 </div>

+ 33 - 82
apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx

@@ -2,62 +2,44 @@ import React, {
   useCallback, useEffect, useState,
 } from 'react';
 
+import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
   DropdownItem,
   DropdownMenu, DropdownToggle, UncontrolledDropdown,
 } from 'reactstrap';
 
-import {
-  addBookmarkToFolder, addNewFolder, hasChildren, toggleBookmark,
-} from '~/client/util/bookmark-utils';
-import { toastError } from '~/client/util/toastr';
+import { hasChildren } from '~/client/util/bookmark-utils';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
-import { onDeletedBookmarkFolderFunction } from '~/interfaces/ui';
-import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
-import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
-import { useBookmarkFolderDeleteModal } from '~/stores/modal';
-import { useSWRxCurrentPage } from '~/stores/page';
 
 import { FolderIcon } from '../Icons/FolderIcon';
 import { TriangleIcon } from '../Icons/TriangleIcon';
 
 import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 
-type Props = {
+export const BookmarkFolderMenuItem: React.FC<{
   item: BookmarkFolderItems
-  onSelectedChild: () => void
   isSelected: boolean
-}
-export const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
-  const {
-    item, isSelected, onSelectedChild,
-  } = props;
+  currentPage: IPagePopulatedToShowRevision
+  onClickDeleteHandler: (e: any, item: any) => Promise<void>
+  onClickChildMenuItemHandler: (e: any, item: any) => Promise<void>
+  onPressEnterHandlerForCreate: (folderName: string, item?: BookmarkFolderItems) => Promise<void>
+}> = ({
+  item,
+  isSelected,
+  currentPage,
+  onClickDeleteHandler,
+  onClickChildMenuItemHandler,
+  onPressEnterHandlerForCreate,
+}) => {
   const { t } = useTranslation();
+
   const [isOpen, setIsOpen] = useState(false);
-  const { mutate: mutateBookamrkData } = useSWRxBookamrkFolderAndChild();
   const [selectedItem, setSelectedItem] = useState<string | null>(null);
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { data: userBookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
-  const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
-  const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
-
-  const isBookmarked = userBookmarkInfo?.isBookmarked ?? false;
 
   const childrenExists = hasChildren(item);
 
-  const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
-    try {
-      await addNewFolder(folderName, item._id);
-      await mutateBookamrkData();
-      setIsCreateAction(false);
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }, [item._id, mutateBookamrkData]);
-
   useEffect(() => {
     if (isOpen) {
       item.children?.forEach((bookmarkFolder) => {
@@ -84,43 +66,6 @@ export const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
     setIsOpen(true);
   }, []);
 
-  // Delete folder handler
-  const onClickDeleteHandler = useCallback(async(e) => {
-    e.stopPropagation();
-    const bookmarkFolderDeleteHandler: onDeletedBookmarkFolderFunction = (folderId) => {
-      if (typeof folderId !== 'string') {
-        return;
-      }
-      mutateBookmarkInfo();
-      mutateBookamrkData();
-    };
-
-    if (item == null) {
-      return;
-    }
-    openDeleteBookmarkFolderModal(item, { onDeleted: bookmarkFolderDeleteHandler });
-  }, [item, mutateBookamrkData, mutateBookmarkInfo, openDeleteBookmarkFolderModal]);
-
-  const onClickChildMenuItemHandler = useCallback(async(e, item) => {
-    e.stopPropagation();
-    onSelectedChild();
-    try {
-      if (isBookmarked && currentPage != null) {
-        await toggleBookmark(currentPage._id, isBookmarked);
-      }
-      if (currentPage != null) {
-        await addBookmarkToFolder(currentPage._id, item._id);
-      }
-      mutateUserBookmarks();
-      mutateBookamrkData();
-      setSelectedItem(item._id);
-      mutateBookmarkInfo();
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }, [onSelectedChild, isBookmarked, currentPage, mutateUserBookmarks, mutateBookamrkData, mutateBookmarkInfo]);
-
   const renderBookmarkSubMenuItem = useCallback(() => {
     if (!isOpen) {
       return <></>;
@@ -150,24 +95,30 @@ export const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
               tabIndex={0} role="menuitem"
               onClick={e => onClickChildMenuItemHandler(e, child)}>
               <BookmarkFolderMenuItem
-                onSelectedChild={() => setSelectedItem(null)}
                 item={child}
                 isSelected={selectedItem === child._id}
+                currentPage={currentPage}
+                onClickDeleteHandler={onClickDeleteHandler}
+                onClickChildMenuItemHandler={onClickChildMenuItemHandler}
+                onPressEnterHandlerForCreate={onPressEnterHandlerForCreate}
               />
             </div>
           </div>
         ))}
       </DropdownMenu>
     );
-  }, [isOpen,
-      isCreateAction,
-      onPressEnterHandlerForCreate,
-      t,
-      childrenExists,
-      item.children,
-      onClickNewBookmarkFolder,
-      selectedItem,
-      onClickChildMenuItemHandler,
+  }, [
+    isOpen,
+    isCreateAction,
+    onPressEnterHandlerForCreate,
+    t,
+    childrenExists,
+    item.children,
+    onClickNewBookmarkFolder,
+    selectedItem,
+    currentPage,
+    onClickDeleteHandler,
+    onClickChildMenuItemHandler,
   ]);
 
   return (
@@ -197,7 +148,7 @@ export const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
           id={`bookmark-delete-button-${item._id}`}
           className="text-danger ml-auto"
           color="transparent"
-          onClick={e => onClickDeleteHandler(e)}
+          onClick={e => onClickDeleteHandler(e, item)}
         >
           <i className="icon-fw icon-trash grw-page-control-dropdown-icon"></i>
         </DropdownToggle>

+ 3 - 9
apps/app/src/components/Bookmarks/BookmarkFolderTree.module.scss

@@ -31,17 +31,11 @@ $grw-bookmark-item-padding-left: 35px;
   .list-group-item {
     .grw-visible-on-hover {
       display: none;
-
-      &:hover {
-        display: block;
-      }
     }
 
-    .grw-count-badge {
-      display: block;
-
-      &:hover {
-        display: none;
+    &:hover {
+      .grw-visible-on-hover {
+        display: block;
       }
     }
 

+ 75 - 86
apps/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -1,132 +1,121 @@
 
-import { useCallback } from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import { addBookmarkToFolder, updateBookmarkFolder } from '~/client/util/bookmark-utils';
-import { toastError, toastSuccess } from '~/client/util/toastr';
-import { BookmarkFolderItems, DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
-import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
+import { toastSuccess } from '~/client/util/toastr';
+import { IPageToDeleteWithMeta } from '~/interfaces/page';
 import { OnDeletedFunction } from '~/interfaces/ui';
-import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
-import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
+import { useSWRxCurrentUserBookmarks, useSWRBookmarkInfo } from '~/stores/bookmark';
+import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 import { usePageDeleteModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 
 import { BookmarkFolderItem } from './BookmarkFolderItem';
 import { BookmarkItem } from './BookmarkItem';
-import { DragAndDropWrapper } from './DragAndDropWrapper';
 
 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 { isUserHomePage } = props;
+
   const { data: currentPage } = useSWRxCurrentPage();
-  const { data: bookmarkFolderData, mutate: mutateBookamrkData } = useSWRxBookamrkFolderAndChild();
-  const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
-
+  const { data: bookmarkFolders, mutate: mutateBookmarkFolders } = useSWRxBookmarkFolderAndChild();
+  const { data: userBookmarks, mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { open: openDeleteModal } = usePageDeleteModal();
 
-  const onUnbookmarkHandler = useCallback(() => {
+  const bookmarkFolderTreeMutation = useCallback(() => {
     mutateUserBookmarks();
     mutateBookmarkInfo();
-  }, [mutateBookmarkInfo, mutateUserBookmarks]);
+    mutateBookmarkFolders();
+  }, [mutateBookmarkFolders, mutateBookmarkInfo, mutateUserBookmarks]);
 
   const onClickDeleteBookmarkHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
     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 }));
-      }
-      mutateUserBookmarks();
-      mutateBookmarkInfo();
-      mutateBookamrkData();
+      if (typeof pathOrPathsToDelete !== 'string') return;
+
+      toastSuccess(isCompletely ? t('deleted_pages_completely', { pathOrPathsToDelete }) : t('deleted_pages', { pathOrPathsToDelete }));
+
+      bookmarkFolderTreeMutation();
     };
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
-  }, [mutateBookmarkInfo, mutateBookamrkData, mutateUserBookmarks, openDeleteModal, t]);
-
-  const itemDropHandler = async(item: DragItemDataType, dragType: string | null | symbol) => {
-    if (dragType === DRAG_ITEM_TYPE.FOLDER) {
-      try {
-        await updateBookmarkFolder(item.bookmarkFolder._id, item.bookmarkFolder.name, null);
-        await mutateBookamrkData();
-      }
-      catch (err) {
-        toastError(err);
-      }
-    }
-    else {
-      try {
-        await addBookmarkToFolder(item._id, null);
-        await mutateUserBookmarks();
-      }
-      catch (err) {
-        toastError(err);
-      }
-    }
-
-  };
-  const isDroppable = (item: DragItemDataType, dragType: string | null | symbol) => {
-    if (dragType === DRAG_ITEM_TYPE.FOLDER) {
-      const isRootFolder = item.level === 0;
-      return !isRootFolder;
-    }
-    const isRootBookmark = item.parentFolder == null;
-    return !isRootBookmark;
-
-  };
+  }, [openDeleteModal, t, bookmarkFolderTreeMutation]);
+
+  /* TODO: update in bookmarks folder v2. */
+  // const itemDropHandler = async(item: DragItemDataType, dragType: string | null | symbol) => {
+  //   if (dragType === DRAG_ITEM_TYPE.FOLDER) {
+  //     try {
+  //       await updateBookmarkFolder(item.bookmarkFolder._id, item.bookmarkFolder.name, null);
+  //       await mutateBookmarkData();
+  //       toastSuccess(t('toaster.update_successed', { target: t('bookmark_folder.bookmark_folder'), ns: 'commons' }));
+  //     }
+  //     catch (err) {
+  //       toastError(err);
+  //     }
+  //   }
+  //   else {
+  //     try {
+  //       await addBookmarkToFolder(item._id, null);
+  //       await mutateUserBookmarks();
+  //       toastSuccess(t('toaster.add_succeeded', { target: t('bookmark_folder.bookmark'), ns: 'commons' }));
+  //     }
+  //     catch (err) {
+  //       toastError(err);
+  //     }
+  //   }
+
+  // };
+  // const isDroppable = (item: DragItemDataType, dragType: string | null | symbol) => {
+  //   if (dragType === DRAG_ITEM_TYPE.FOLDER) {
+  //     const isRootFolder = item.level === 0;
+  //     return !isRootFolder;
+  //   }
+  //   const isRootBookmark = item.parentFolder == null;
+  //   return !isRootBookmark;
+
+  // };
 
   return (
     <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) => {
+        {bookmarkFolders?.map((bookmarkFolder) => {
           return (
             <BookmarkFolderItem
-              key={item._id}
-              bookmarkFolder={item}
+              key={bookmarkFolder._id}
+              bookmarkFolder={bookmarkFolder}
               isOpen={false}
               level={0}
-              root={item._id}
+              root={bookmarkFolder._id}
               isUserHomePage={isUserHomePage}
+              onClickDeleteBookmarkHandler={onClickDeleteBookmarkHandler}
+              bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
             />
           );
         })}
-        {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
-              bookmarkedPage={page}
-              key={page._id}
-              onUnbookmarked={onUnbookmarkHandler}
-              onRenamed={mutateUserBookmarks}
-              onClickDeleteMenuItem={onClickDeleteBookmarkHandler}
-              parentFolder={null}
+              key={userBookmark._id}
+              bookmarkedPage={userBookmark}
               level={0}
+              parentFolder={null}
+              canMoveToRoot={false}
+              onClickDeleteBookmarkHandler={onClickDeleteBookmarkHandler}
+              bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
             />
           </div>
         ))}
       </ul>
-      {bookmarkFolderData != null && bookmarkFolderData.length > 0 && (
+      {/* TODO: update in bookmarks folder v2. Also delete drop_item_here in translation.json, if don't need it. */}
+      {/* {bookmarkFolderData != null && bookmarkFolderData.length > 0 && (
         <DragAndDropWrapper
           useDropMode={true}
           type={acceptedTypes}
@@ -139,7 +128,7 @@ export const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element
             </div>
           </div>
         </DragAndDropWrapper>
-      )}
+      )} */}
     </div>
   );
 };

+ 34 - 23
apps/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useState } from 'react';
 
 import nodePath from 'path';
 
@@ -7,63 +7,71 @@ import { useTranslation } from 'react-i18next';
 import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
 
 import { unbookmark } from '~/client/services/page-operation';
-import { renamePage } from '~/client/util/bookmark-utils';
+import { addBookmarkToFolder, renamePage } from '~/client/util/bookmark-utils';
 import { ValidationTarget } from '~/client/util/input-validator';
 import { toastError } from '~/client/util/toastr';
 import { BookmarkFolderItems, DragItemDataType, 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';
 
 import ClosableTextInput from '../Common/ClosableTextInput';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { PageListItemS } from '../PageList/PageListItemS';
 
+import { BookmarkMoveToRootBtn } from './BookmarkMoveToRootBtn';
 import { DragAndDropWrapper } from './DragAndDropWrapper';
 
 type Props = {
   bookmarkedPage: IPageHasId,
-  onUnbookmarked: () => void,
-  onRenamed: () => void,
-  onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void,
-  parentFolder: BookmarkFolderItems | null
-  level: number
+  level: number,
+  parentFolder: BookmarkFolderItems | null,
+  canMoveToRoot: boolean,
+  onClickDeleteBookmarkHandler: (pageToDelete: IPageToDeleteWithMeta) => void,
+  bookmarkFolderTreeMutation: () => void
 }
 
 export const BookmarkItem = (props: Props): JSX.Element => {
   const BASE_FOLDER_PADDING = 15;
   const BASE_BOOKMARK_PADDING = 20;
+
   const { t } = useTranslation();
+
   const {
-    bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, parentFolder, level,
+    bookmarkedPage, onClickDeleteBookmarkHandler,
+    parentFolder, level, canMoveToRoot, bookmarkFolderTreeMutation,
   } = props;
+
   const [isRenameInputShown, setRenameInputShown] = useState(false);
+
+  const { data: fetchedPageInfo } = useSWRxPageInfo(bookmarkedPage._id);
+
   const dPagePath = new DevidedPagePath(bookmarkedPage.path, false, true);
   const { latter: pageTitle, former: formerPagePath } = dPagePath;
   const bookmarkItemId = `bookmark-item-${bookmarkedPage._id}`;
-  const { mutate: mutateBookamrkData } = useSWRxBookamrkFolderAndChild();
-  const { data: fetchedPageInfo } = useSWRxPageInfo(bookmarkedPage._id);
-
   const paddingLeft = BASE_BOOKMARK_PADDING + (BASE_FOLDER_PADDING * (level + 1));
-
   const dragItem: Partial<DragItemDataType> = {
     ...bookmarkedPage, parentFolder,
   };
 
-  useEffect(() => {
-    mutateBookamrkData();
-  }, [mutateBookamrkData]);
+  const onClickMoveToRootHandler = useCallback(async() => {
+    try {
+      await addBookmarkToFolder(bookmarkedPage._id, null);
+      bookmarkFolderTreeMutation();
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [bookmarkFolderTreeMutation, bookmarkedPage._id]);
 
   const bookmarkMenuItemClickHandler = useCallback(async() => {
     await unbookmark(bookmarkedPage._id);
-    onUnbookmarked();
-  }, [onUnbookmarked, bookmarkedPage]);
+    bookmarkFolderTreeMutation();
+  }, [bookmarkedPage._id, bookmarkFolderTreeMutation]);
 
   const renameMenuItemClickHandler = useCallback(() => {
     setRenameInputShown(true);
   }, []);
 
-
   const pressEnterForRenameHandler = useCallback(async(inputText: string) => {
     const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(bookmarkedPage.path ?? ''));
     const newPagePath = nodePath.resolve(parentPath, inputText);
@@ -75,13 +83,13 @@ export const BookmarkItem = (props: Props): JSX.Element => {
     try {
       setRenameInputShown(false);
       await renamePage(bookmarkedPage._id, bookmarkedPage.revision, newPagePath);
-      onRenamed();
+      bookmarkFolderTreeMutation();
     }
     catch (err) {
       setRenameInputShown(true);
       toastError(err);
     }
-  }, [bookmarkedPage, onRenamed]);
+  }, [bookmarkedPage, bookmarkFolderTreeMutation]);
 
   const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise<void> => {
     if (bookmarkedPage._id == null || bookmarkedPage.path == null) {
@@ -97,8 +105,8 @@ export const BookmarkItem = (props: Props): JSX.Element => {
       meta: pageInfo,
     };
 
-    onClickDeleteMenuItem(pageToDelete);
-  }, [bookmarkedPage, onClickDeleteMenuItem]);
+    onClickDeleteBookmarkHandler(pageToDelete);
+  }, [bookmarkedPage._id, bookmarkedPage.path, bookmarkedPage.revision, onClickDeleteBookmarkHandler]);
 
   return (
     <DragAndDropWrapper
@@ -130,6 +138,9 @@ export const BookmarkItem = (props: Props): JSX.Element => {
             onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
             onClickRenameMenuItem={renameMenuItemClickHandler}
             onClickDeleteMenuItem={deleteMenuItemClickHandler}
+            additionalMenuItemOnTopRenderer={canMoveToRoot
+              ? () => <BookmarkMoveToRootBtn pageId={bookmarkedPage._id} onClickMoveToRootHandler={onClickMoveToRootHandler}/>
+              : undefined}
           >
             <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>

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

@@ -0,0 +1,23 @@
+import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+import { DropdownItem } from 'reactstrap';
+
+export const BookmarkMoveToRootBtn: React.FC<{
+  pageId: string
+  onClickMoveToRootHandler: (pageId: string) => Promise<void>
+}> = React.memo(({ pageId, onClickMoveToRootHandler }) => {
+  const { t } = useTranslation();
+
+  return (
+    <DropdownItem
+      onClick={() => onClickMoveToRootHandler(pageId)}
+      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';

+ 3 - 1
apps/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -1,4 +1,6 @@
-import React, { useState, useCallback, useEffect } from 'react';
+import React, {
+  useState, useCallback, useEffect,
+} from 'react';
 
 import { modifiersForRightAlign } from '@growi/ui/dist/utils';
 import { useTranslation } from 'next-i18next';

+ 1 - 2
apps/app/src/components/NotFoundPage.tsx

@@ -8,7 +8,6 @@ import PageListIcon from './Icons/PageListIcon';
 import TimeLineIcon from './Icons/TimeLineIcon';
 import { PageTimeline } from './PageTimeline';
 
-
 type NotFoundPageProps = {
   path: string,
 }
@@ -22,7 +21,7 @@ const NotFoundPage = (props: NotFoundPageProps): JSX.Element => {
     return {
       pagelist: {
         Icon: PageListIcon,
-        Content: () => <DescendantsPageList path={path} />,
+        Content: () => <DescendantsPageList path={path}/>,
         i18n: t('page_list'),
       },
       timeLine: {

+ 0 - 114
apps/app/src/components/PageList/BookmarkList.tsx

@@ -1,114 +0,0 @@
-import React, { useCallback, useState } from 'react';
-
-import nodePath from 'path';
-
-import {
-  IPageInfoAll, IPageToDeleteWithMeta, pathUtils,
-} from '@growi/core';
-import { useTranslation } from 'next-i18next';
-import { DropdownToggle } from 'reactstrap';
-
-import { unbookmark } from '~/client/services/page-operation';
-import { apiv3Put } from '~/client/util/apiv3-client';
-import { ValidationTarget } from '~/client/util/input-validator';
-import { toastError, toastSuccess } from '~/client/util/toastr';
-import { IPageHasId } from '~/interfaces/page';
-import loggerFactory from '~/utils/logger';
-
-import ClosableTextInput from '../Common/ClosableTextInput';
-import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
-
-import { PageListItemS } from './PageListItemS';
-
-const logger = loggerFactory('growi:BookmarkList');
-
-type Props = {
-  page: IPageHasId
-  onRenamed: () => void
-  onUnbookmarked: () => void
-  onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void
-}
-export const BookmarkList = (props:Props): JSX.Element => {
-  const {
-    page, onRenamed, onUnbookmarked, onClickDeleteMenuItem,
-  } = props;
-  const { t } = useTranslation();
-  const [isRenameInputShown, setIsRenameInputShown] = useState(false);
-
-  const bookmarkMenuItemClickHandler = useCallback(async() => {
-    await unbookmark(page._id);
-    onUnbookmarked();
-  }, [page._id, onUnbookmarked]);
-
-  const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise<void> => {
-    if (page._id == null || page.path == null) {
-      throw Error('_id and path must not be null.');
-    }
-
-    const pageToDelete: IPageToDeleteWithMeta = {
-      data: {
-        _id: page._id,
-        revision: page.revision as string,
-        path: page.path,
-      },
-      meta: pageInfo,
-    };
-
-    onClickDeleteMenuItem(pageToDelete);
-  }, [onClickDeleteMenuItem, page]);
-
-  const pressEnterForRenameHandler = useCallback(async(inputText: string) => {
-    const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? ''));
-    const newPagePath = nodePath.resolve(parentPath, inputText);
-    if (newPagePath === page.path) {
-      setIsRenameInputShown(false);
-      return;
-    }
-
-    try {
-      setIsRenameInputShown(false);
-      await apiv3Put('/pages/rename', {
-        pageId: page._id,
-        revisionId: page.revision,
-        newPagePath,
-      });
-      onRenamed();
-      toastSuccess(t('renamed_pages', { path: page.path }));
-    }
-    catch (err) {
-      setIsRenameInputShown(true);
-      logger.error('failed to fetch data', err);
-      toastError(err);
-    }
-  }, [onRenamed, page, t]);
-
-  return (
-    <li key={`my-bookmarks:${page?._id}`} className="list-group-item list-group-item-action border-0 py-0 pl-3 d-flex align-items-center">
-      { isRenameInputShown ? (
-        <ClosableTextInput
-          value={nodePath.basename(page.path ?? '')}
-          placeholder={t('Input page name')}
-          onClickOutside={() => { setIsRenameInputShown(false) }}
-          onPressEnter={pressEnterForRenameHandler}
-          validationTarget={ValidationTarget.PAGE}
-        />
-      ) : (
-        <PageListItemS page={page} />
-      )}
-
-      <PageItemControl
-        pageId={page._id}
-        isEnableActions
-        forceHideMenuItems={[MenuItemType.DUPLICATE]}
-        onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
-        onClickRenameMenuItem={() => setIsRenameInputShown(true)}
-        onClickDeleteMenuItem={deleteMenuItemClickHandler}
-      >
-        <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
-          <i className="icon-options fa fa-rotate-90 p-1"></i>
-        </DropdownToggle>
-      </PageItemControl>
-    </li>
-
-  );
-};

+ 7 - 1
apps/app/src/components/PrivateLegacyPages.tsx

@@ -436,7 +436,13 @@ const PrivateLegacyPages = (): JSX.Element => {
         ref={searchPageBaseRef}
         pages={data?.data}
         onSelectedPagesByCheckboxesChanged={selectedPagesByCheckboxesChangedHandler}
-        forceHideMenuItems={[MenuItemType.BOOKMARK, MenuItemType.RENAME, MenuItemType.DUPLICATE, MenuItemType.REVERT, MenuItemType.PATH_RECOVERY]}
+        forceHideMenuItems={[
+          MenuItemType.BOOKMARK,
+          MenuItemType.RENAME,
+          MenuItemType.DUPLICATE,
+          MenuItemType.REVERT,
+          MenuItemType.PATH_RECOVERY,
+        ]}
         // Components
         searchControl={searchControl}
         searchResultListHead={searchResultListHead}

+ 18 - 27
apps/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx

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

+ 5 - 7
apps/app/src/components/UsersHomePageFooter.tsx

@@ -1,15 +1,13 @@
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-
-import { apiv3Post } from '~/client/util/apiv3-client';
 import { addNewFolder } from '~/client/util/bookmark-utils';
-import { toastError, toastSuccess } from '~/client/util/toastr';
+import { toastError } from '~/client/util/toastr';
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 import { RecentCreated } from '~/components/RecentCreated/RecentCreated';
 import styles from '~/components/UsersHomePageFooter.module.scss';
-import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
+import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
 
 import { BookmarkFolderNameInput } from './Bookmarks/BookmarkFolderNameInput';
 import { BookmarkFolderTree } from './Bookmarks/BookmarkFolderTree';
@@ -27,7 +25,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();
+  const { mutate: mutateChildBookmarkData } = useSWRxBookmarkFolderAndChild();
 
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
     try {
@@ -38,7 +36,7 @@ export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Elemen
     catch (err) {
       toastError(err);
     }
-  }, [mutateChildBookmarkData, t]);
+  }, [mutateChildBookmarkData]);
 
   return (
     <div className={`container-lg user-page-footer py-5 ${styles['user-page-footer']}`}>

+ 1 - 1
apps/app/src/stores/bookmark-folder.ts

@@ -4,7 +4,7 @@ import useSWRImmutable from 'swr/immutable';
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 
-export const useSWRxBookamrkFolderAndChild = (): SWRResponse<BookmarkFolderItems[], Error> => {
+export const useSWRxBookmarkFolderAndChild = (): SWRResponse<BookmarkFolderItems[], Error> => {
 
   return useSWRImmutable(
     '/bookmark-folder/list',