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

Merge pull request #7622 from weseek/feat/115447-119728-refactor-navbar-bookmark-menu

feat: Update BookmarkFolderMenu
Yuki Takei 2 лет назад
Родитель
Сommit
c2b19d3b5b

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

@@ -791,7 +791,8 @@
     "delete": "Delete Folder",
     "drop_item_here": "Drag and drop item here",
     "cancel_bookmark": "Un-bookmark this page",
-    "Move to the root": "Move to the root"
+    "move_to_root": "Move to the root",
+    "do_not_include_folder": "Do not include in folder"
   },
   "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

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

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

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

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

@@ -1,30 +1,18 @@
-import React, {
-  useCallback, useState,
-} from 'react';
-
+import React, { useCallback, useMemo, useState } from 'react';
+import { getCustomModifiers } from '@growi/ui/dist/utils';
 import { useTranslation } from 'next-i18next';
-import {
-  DropdownItem, DropdownMenu, UncontrolledDropdown,
-} from 'reactstrap';
+import { DropdownItem, DropdownMenu, UncontrolledDropdown } from 'reactstrap';
 
-import { addBookmarkToFolder, addNewFolder, toggleBookmark } from '~/client/util/bookmark-utils';
+import { addBookmarkToFolder, 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 { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
-import { useBookmarkFolderDeleteModal } from '~/stores/modal';
 import { useSWRxCurrentPage, useSWRxPageInfo } from '~/stores/page';
-
-import { FolderIcon } from '../Icons/FolderIcon';
-
 import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
-import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 
 export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ children }): JSX.Element => {
   const { t } = useTranslation();
 
-  const [isCreateAction, setIsCreateAction] = useState(false);
   const [selectedItem, setSelectedItem] = useState<string | null>(null);
   const [isOpen, setIsOpen] = useState(false);
 
@@ -33,10 +21,13 @@ export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ chi
   const { data: bookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
   const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
   const { mutate: mutatePageInfo } = useSWRxPageInfo(currentPage?._id);
-  const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
 
   const isBookmarked = bookmarkInfo?.isBookmarked ?? false;
 
+  const isBookmarkFolderExists = useMemo((): boolean => {
+    return bookmarkFolders != null && bookmarkFolders.length > 0;
+  }, [bookmarkFolders]);
+
   const toggleBookmarkHandler = useCallback(async() => {
     try {
       if (currentPage != null) {
@@ -48,22 +39,19 @@ export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ chi
     }
   }, [currentPage, isBookmarked]);
 
-  const onClickNewBookmarkFolder = useCallback(() => {
-    setIsCreateAction(true);
-  }, []);
-
   const onUnbookmarkHandler = useCallback(async() => {
     await toggleBookmarkHandler();
+    setIsOpen(false);
+    setSelectedItem(null);
     mutateUserBookmarks();
     mutateBookmarkInfo();
     mutateBookmarkFolders();
     mutatePageInfo();
-    setSelectedItem(null);
   }, [mutateBookmarkFolders, mutateBookmarkInfo, mutatePageInfo, mutateUserBookmarks, toggleBookmarkHandler]);
 
   const toggleHandler = useCallback(async() => {
     setIsOpen(!isOpen);
-    mutateBookmarkFolders();
+
     if (isOpen && bookmarkFolders != null) {
       bookmarkFolders.forEach((bookmarkFolder) => {
         bookmarkFolder.bookmarks.forEach((bookmark) => {
@@ -73,13 +61,13 @@ export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ chi
         });
       });
     }
+
     if (!isOpen && !isBookmarked) {
       try {
-        toggleBookmarkHandler();
+        await toggleBookmarkHandler();
         mutateUserBookmarks();
         mutateBookmarkInfo();
         mutatePageInfo();
-        setSelectedItem(null);
       }
       catch (err) {
         toastError(err);
@@ -98,22 +86,11 @@ export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ chi
     mutatePageInfo,
   ]);
 
-  const isBookmarkFolderExists = useCallback((): boolean => {
-    return bookmarkFolders != null && bookmarkFolders.length > 0;
-  }, [bookmarkFolders]);
+  const onMenuItemClickHandler = useCallback(async(e, itemId: string | null) => {
+    e.stopPropagation();
 
-  const onPressEnterHandlerForCreate = useCallback(async(folderName: string, item?: BookmarkFolderItems) => {
-    try {
-      await addNewFolder(folderName, item ? item._id : null);
-      await mutateBookmarkFolders();
-      setIsCreateAction(false);
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }, [mutateBookmarkFolders]);
+    setSelectedItem(itemId);
 
-  const onMenuItemClickHandler = useCallback(async(itemId: string) => {
     try {
       if (isBookmarked) {
         await toggleBookmarkHandler();
@@ -121,147 +98,72 @@ export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ chi
       if (currentPage != null) {
         await addBookmarkToFolder(currentPage._id, itemId);
       }
-      mutateBookmarkInfo();
-      mutateUserBookmarks();
-    }
-    catch (err) {
-      toastError(err);
-    }
-
-    mutateBookmarkFolders();
-    setSelectedItem(itemId);
-  }, [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 || currentPage == null || bookmarkInfo == null) {
-        return <></>;
-      }
-      return (
-        <div className="bookmark-folder-submenu">
-          {child.map(folder => (
-            <div key={folder._id}>
-              <div
-                className="dropdown-item grw-bookmark-folder-menu-item"
-                tabIndex={0}
-                role="menuitem"
-                onClick={() => onMenuItemClickHandler(folder._id)}
-              >
-                <BookmarkFolderMenuItem
-                  item={folder}
-                  isSelected={selectedItem === folder._id}
-                  currentPage={currentPage}
-                  onClickDeleteHandler={onClickDeleteHandler}
-                  onClickChildMenuItemHandler={onClickChildMenuItemHandler}
-                  onPressEnterHandlerForCreate={onPressEnterHandlerForCreate}
-                />
-                {isOpen && renderSubmenu()}
-              </div>
-            </div>
-          ))}
-        </div>
-      );
-    };
+  }, [mutateBookmarkFolders, isBookmarked, currentPage, mutateBookmarkInfo, mutateUserBookmarks, toggleBookmarkHandler]);
 
+  const renderBookmarkMenuItem = () => {
     return (
       <>
-        {isBookmarked && (
+        <DropdownItem
+          toggle={false}
+          onClick={onUnbookmarkHandler}
+          className={'grw-bookmark-folder-menu-item text-danger'}
+        >
+          <i className="fa fa-bookmark"></i>{' '}
+          <span className="mx-2 ">
+            {t('bookmark_folder.cancel_bookmark')}
+          </span>
+        </DropdownItem>
+
+        {isBookmarkFolderExists && (
           <>
+            <DropdownItem divider />
             <DropdownItem
               toggle={false}
-              onClick={onUnbookmarkHandler}
-              className={'grw-bookmark-folder-menu-item text-danger'}
+              onClick={e => onMenuItemClickHandler(e, null)}
             >
-              <i className="fa fa-bookmark"></i>{' '}
-              <span className="mx-2 ">
-                {t('bookmark_folder.cancel_bookmark')}
-              </span>
+              {t('bookmark_folder.do_not_include_folder')}
             </DropdownItem>
             <DropdownItem divider />
-          </>
-        )}
-
-        {isCreateAction ? (
-          <div className="mx-2">
-            <BookmarkFolderNameInput
-              onClickOutside={() => setIsCreateAction(false)}
-              onPressEnter={onPressEnterHandlerForCreate}
-            />
-          </div>
-        ) : (
-          <DropdownItem
-            toggle={false}
-            onClick={onClickNewBookmarkFolder}
-            className="grw-bookmark-folder-menu-item"
-          >
-            <FolderIcon isOpen={false} />
-            <span className="mx-2 ">{t('bookmark_folder.new_folder')}</span>
-          </DropdownItem>
-        )}
-
-        {isBookmarkFolderExists() && currentPage != null && bookmarkInfo != null && (
-          <>
-            <DropdownItem divider />
             {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
-                    item={folder}
-                    isSelected={selectedItem === folder._id}
-                    currentPage={currentPage}
-                    onClickDeleteHandler={onClickDeleteHandler}
-                    onClickChildMenuItemHandler={onClickChildMenuItemHandler}
-                    onPressEnterHandlerForCreate={onPressEnterHandlerForCreate}
-                  />
-                  {isOpen && renderSubmenu()}
+              <>
+                <div key={folder._id}>
+                  <div
+                    className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
+                    tabIndex={0}
+                    role="menuitem"
+                    onClick={e => onMenuItemClickHandler(e, folder._id)}
+                  >
+                    <BookmarkFolderMenuItem
+                      item={folder}
+                      isSelected={selectedItem === folder._id}
+                    />
+                  </div>
                 </div>
-              </div>
+                <>
+                  {folder.children?.map(child => (
+                    <div key={child._id}>
+                      <div
+                        className='dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0'
+                        style={{ paddingLeft: '40px' }}
+                        tabIndex={0}
+                        role="menuitem"
+                        onClick={e => onMenuItemClickHandler(e, child._id)}>
+                        <BookmarkFolderMenuItem
+                          item={child}
+                          isSelected={selectedItem === child._id}
+                        />
+                      </div>
+                    </div>
+                  ))}
+                </>
+              </>
             ))}
           </>
         )}
@@ -271,14 +173,18 @@ export const BookmarkFolderMenu: React.FC<{children?: React.ReactNode}> = ({ chi
 
   return (
     <UncontrolledDropdown
+      isOpen={isOpen}
       onToggle={toggleHandler}
-      direction={ isBookmarkFolderExists() ? 'up' : 'down' }
-      className='grw-bookmark-folder-dropdown'>
+      direction={isBookmarkFolderExists ? 'up' : 'down'}
+      className='grw-bookmark-folder-dropdown'
+    >
       {children}
       <DropdownMenu
         right
-        className='grw-bookmark-folder-menu'
+        persist
         positionFixed
+        className='grw-bookmark-folder-menu'
+        modifiers={getCustomModifiers(true)}
       >
         { renderBookmarkMenuItem() }
       </DropdownMenu>

+ 14 - 160
apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx

@@ -1,173 +1,27 @@
-import React, {
-  useCallback, useEffect, useState,
-} from 'react';
+import React from 'react';
 
-import type { IPagePopulatedToShowRevision } from '@growi/core';
-import { useTranslation } from 'next-i18next';
-import {
-  DropdownItem,
-  DropdownMenu, DropdownToggle, UncontrolledDropdown,
-} from 'reactstrap';
-
-import { hasChildren } from '~/client/util/bookmark-utils';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 
-import { FolderIcon } from '../Icons/FolderIcon';
-import { TriangleIcon } from '../Icons/TriangleIcon';
-
-import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
-
 export const BookmarkFolderMenuItem: React.FC<{
   item: BookmarkFolderItems
   isSelected: boolean
-  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 [selectedItem, setSelectedItem] = useState<string | null>(null);
-  const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
-
-  const childrenExists = hasChildren(item);
-
-  useEffect(() => {
-    if (isOpen) {
-      item.children?.forEach((bookmarkFolder) => {
-        bookmarkFolder.bookmarks.forEach((bookmark) => {
-          if (bookmark.page._id === currentPage?._id) {
-            setSelectedItem(bookmarkFolder._id);
-          }
-        });
-      });
-    }
-  }, [currentPage?._id, isOpen, item.children]);
-
-  const onClickNewBookmarkFolder = useCallback((e) => {
-    e.stopPropagation();
-    setIsCreateAction(true);
-  }, []);
-
-  const onMouseLeaveHandler = useCallback(() => {
-    setIsOpen(false);
-    setIsCreateAction(false);
-  }, []);
-
-  const onMouseEnterHandler = useCallback(() => {
-    setIsOpen(true);
-  }, []);
-
-  const renderBookmarkSubMenuItem = useCallback(() => {
-    if (!isOpen) {
-      return <></>;
-    }
-    return (
-      <DropdownMenu className='m-0'>
-        {isCreateAction ? (
-          <div className='mx-2' onClick={e => e.stopPropagation()}>
-            <BookmarkFolderNameInput
-              onClickOutside={() => setIsCreateAction(false)}
-              onPressEnter={onPressEnterHandlerForCreate}
-            />
-          </div>
-        ) : (
-          <DropdownItem toggle={false} onClick={e => onClickNewBookmarkFolder(e)} className='grw-bookmark-folder-menu-item'>
-            <FolderIcon isOpen={false} />
-            <span className="mx-2 ">{t('bookmark_folder.new_folder')}</span>
-          </DropdownItem>
-        )}
-
-        {childrenExists && (<DropdownItem divider />)}
-
-        {item.children?.map(child => (
-          <div key={child._id} >
-            <div
-              className='dropdown-item grw-bookmark-folder-menu-item'
-              tabIndex={0} role="menuitem"
-              onClick={e => onClickChildMenuItemHandler(e, child)}>
-              <BookmarkFolderMenuItem
-                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,
-    currentPage,
-    onClickDeleteHandler,
-    onClickChildMenuItemHandler,
-  ]);
-
   return (
-    <>
-      <UncontrolledDropdown
-        direction="right"
-        className='d-flex justify-content-between '
-        isOpen={isOpen}
-        // toggle={toggleHandler}
-        onMouseLeave={onMouseLeaveHandler}
-      >
-        <div className='d-flex justify-content-start grw-bookmark-folder-menu-item-title'>
-          <input
-            type="radio"
-            checked={isSelected}
-            name="bookmark-folder-menu-item"
-            id={`bookmark-folder-menu-item-${item._id}`}
-            onChange={e => e.stopPropagation()}
-            onClick={e => e.stopPropagation()}
-          />
-          <label htmlFor={`bookmark-folder-menu-item-${item._id}`} className='p-2 m-0'>
-            {item.name}
-          </label>
-        </div>
-
-        <DropdownToggle
-          id={`bookmark-delete-button-${item._id}`}
-          className="text-danger ml-auto"
-          color="transparent"
-          onClick={e => onClickDeleteHandler(e, item)}
-        >
-          <i className="icon-fw icon-trash grw-page-control-dropdown-icon"></i>
-        </DropdownToggle>
-        {/* Maximum folder hierarchy of 2 levels */}
-        {item.parent == null && (
-          <DropdownToggle
-            color="transparent"
-            onClick={e => e.stopPropagation()}
-            onMouseEnter={onMouseEnterHandler}
-          >
-            {childrenExists
-              ? <TriangleIcon />
-              : <i className="icon-plus d-block pl-0" />
-            }
-          </DropdownToggle>
-        )}
-        {renderBookmarkSubMenuItem()}
-
-      </UncontrolledDropdown >
-    </>
+    <div className='d-flex justify-content-start grw-bookmark-folder-menu-item-title'>
+      <input
+        type="radio"
+        checked={isSelected}
+        name="bookmark-folder-menu-item"
+        id={`bookmark-folder-menu-item-${item._id}`}
+        onChange={e => e.stopPropagation()}
+        onClick={e => e.stopPropagation()}
+      />
+      <label htmlFor={`bookmark-folder-menu-item-${item._id}`} className='p-2 m-0'>
+        {item.name}
+      </label>
+    </div>
   );
 };