Explorar el Código

Merge pull request #7455 from weseek/feat/gw7926-bookmark-refactor

feat: refactor and clean code
Ryoji Shimizu hace 3 años
padre
commit
a398889409
Se han modificado 28 ficheros con 272 adiciones y 296 borrados
  1. 5 5
      packages/app/public/static/locales/en_US/commons.json
  2. 5 5
      packages/app/public/static/locales/ja_JP/commons.json
  3. 2 2
      packages/app/public/static/locales/ja_JP/translation.json
  4. 5 5
      packages/app/public/static/locales/zh_CN/commons.json
  5. 12 0
      packages/app/src/client/util/bookmark-utils.ts
  6. 15 0
      packages/app/src/client/util/input-validator-utils.ts
  7. 2 4
      packages/app/src/components/BookmarkButtons.tsx
  8. 13 20
      packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  9. 1 3
      packages/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx
  10. 91 63
      packages/app/src/components/Bookmarks/BookmarkFolderMenu.tsx
  11. 46 49
      packages/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx
  12. 3 16
      packages/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx
  13. 38 45
      packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx
  14. 3 14
      packages/app/src/components/Bookmarks/BookmarkItem.tsx
  15. 2 2
      packages/app/src/components/DeleteBookmarkFolderModal.tsx
  16. 1 3
      packages/app/src/components/Icons/CompressIcon.tsx
  17. 1 3
      packages/app/src/components/Icons/ExpandIcon.tsx
  18. 1 3
      packages/app/src/components/Icons/FolderIcon.tsx
  19. 1 3
      packages/app/src/components/Icons/FolderPlusIcon.tsx
  20. 1 3
      packages/app/src/components/Icons/TriangleIcon.tsx
  21. 1 1
      packages/app/src/components/Layout/BasicLayout.tsx
  22. 5 6
      packages/app/src/components/Navbar/SubNavButtons.tsx
  23. 2 12
      packages/app/src/components/PageList/BookmarkList.tsx
  24. 3 5
      packages/app/src/components/Sidebar/Bookmarks.tsx
  25. 4 6
      packages/app/src/components/Sidebar/Bookmarks/BookmarkContents.tsx
  26. 3 12
      packages/app/src/components/Sidebar/PageTree/Item.tsx
  27. 1 1
      packages/app/src/components/Sidebar/SidebarContents.tsx
  28. 5 5
      packages/app/src/components/UsersHomePageFooter.tsx

+ 5 - 5
packages/app/public/static/locales/en_US/commons.json

@@ -10,14 +10,14 @@
     "display_name": "English"
   },
   "toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
+    "add_succeeded": "Succeeded to add {{target}}",
     "create_failed": "Failed to create {{target}}",
-    "update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
+    "create_succeeded": "Succeeded to create {{target}}",
     "delete_succeeded": "Succeeded to delete {{target}}",
-    "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
     "remove_share_link": "Succeeded to remove {{count}} share links",
-    "add_succeeded": "Succeeded to add {{target}}"
+    "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
+    "update_failed": "Failed to update {{target}}",
+    "update_successed": "Succeeded to update {{target}}"
   },
   "alert": {
     "siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",

+ 5 - 5
packages/app/public/static/locales/ja_JP/commons.json

@@ -10,14 +10,14 @@
     "display_name": "日本語"
   },
   "toaster": {
-    "create_succeeded": "新しい{{target}}が作成されました",
+    "add_succeeded": "新しい{{target}}が追加されました",
+    "delete_succeeded": "{{target}} の削除に成功しました",
     "create_failed": "{{target}}の作成に失敗しました",
-    "update_successed": "{{target}}を更新しました",
+    "create_succeeded": "新しい{{target}}が作成されました",
     "update_failed": "{{target}}の更新に失敗しました",
-    "delete_succeeded": "{{target}} の削除に成功しました",
-    "remove_share_link_success": "{{shareLinkId}}を削除しました",
+    "update_successed": "{{target}}を更新しました",
     "remove_share_link": "共有リンクを{{count}}件削除しました",
-    "add_succeeded": "新しい{{target}}が追加されました"
+    "remove_share_link_success": "{{shareLinkId}}を削除しました"
   },
   "alert": {
     "siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",

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

@@ -817,11 +817,11 @@
     "bookmark": "ブックマーク",
     "delete_modal": {
       "modal_header_label": "ブックマークフォルダを削除",
-      "modal_body_description": "このブックマーク フォルダとその内容を削除する",
+      "modal_body_description": "このブックマークフォルダと配下のブックマークを削除する",
       "modal_body_alert": "削除されたフォルダとその内容は復元できません",
       "modal_footer_button": "フォルダを削除"
     },
-    "input_placeholder": "フォルダ名を入力してください`",
+    "input_placeholder": "フォルダ名を入力してください",
     "new_folder": "新しいフォルダ",
     "delete": "フォルダを削除",
     "drop_item_here": "ブックマークを解除",

+ 5 - 5
packages/app/public/static/locales/zh_CN/commons.json

@@ -10,14 +10,14 @@
     "display_name": "简体中文"
   },
   "toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
+    "add_succeeded": "Succeeded to add {{target}}",
     "create_failed": "Failed to create {{target}}",
-    "update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
+    "create_succeeded": "Succeeded to create {{target}}",
     "delete_succeeded": "Succeeded to delete {{target}}",
-    "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
     "remove_share_link": "Succeeded to remove {{count}} share links",
-    "add_succeeded": "Succeeded to add {{target}}"
+    "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
+    "update_failed": "Failed to update {{target}}",
+    "update_successed": "Succeeded to update {{target}}"
   },
   "alert": {
     "siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",

+ 12 - 0
packages/app/src/client/util/bookmark-utils.ts

@@ -0,0 +1,12 @@
+import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+
+
+export const hasChildren = (item: BookmarkFolderItems | BookmarkFolderItems[]): boolean => {
+  if (item === null) {
+    return false;
+  }
+  if (Array.isArray(item)) {
+    return item.length > 0;
+  }
+  return item.children && item.children.length > 0;
+};

+ 15 - 0
packages/app/src/client/util/input-validator-utils.ts

@@ -0,0 +1,15 @@
+import i18n from 'i18next';
+
+import { AlertInfo, AlertType } from '~/components/Common/ClosableTextInput';
+
+// Validator for closeable text input
+export const inputValidator = (title: string | null): AlertInfo | null => {
+
+  if (title == null || title === '' || title.trim() === '') {
+    return {
+      type: AlertType.WARNING,
+      message: i18n.t('form_validation.title_required'),
+    };
+  }
+  return null;
+};

+ 2 - 4
packages/app/src/components/BookmarkButtons.tsx

@@ -12,7 +12,7 @@ import { useIsGuestUser } from '~/stores/context';
 
 import { IUser } from '../interfaces/user';
 
-import BookmarkFolderMenu from './Bookmarks/BookmarkFolderMenu';
+import { BookmarkFolderMenu } from './Bookmarks/BookmarkFolderMenu';
 import UserPictureList from './User/UserPictureList';
 
 import styles from './BookmarkButtons.module.scss';
@@ -23,7 +23,7 @@ interface Props {
   bookmarkInfo? : IBookmarkInfo
 }
 
-const BookmarkButtons: FC<Props> = (props: Props) => {
+export const BookmarkButtons: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
   const {
     bookmarkedUsers, hideTotalNumber, bookmarkInfo,
@@ -83,5 +83,3 @@ const BookmarkButtons: FC<Props> = (props: Props) => {
     </div>
   );
 };
-
-export default BookmarkButtons;

+ 13 - 20
packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -7,9 +7,10 @@ import { useDrag, useDrop } from 'react-dnd';
 import { DropdownToggle } from 'reactstrap';
 
 import { apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
+import { hasChildren } from '~/client/util/bookmark-utils';
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import FolderIcon from '~/components/Icons/FolderIcon';
-import TriangleIcon from '~/components/Icons/TriangleIcon';
+import { FolderIcon } from '~/components/Icons/FolderIcon';
+import { TriangleIcon } from '~/components/Icons/TriangleIcon';
 import { BookmarkFolderItems, DragItemType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { onDeletedBookmarkFolderFunction, OnDeletedFunction } from '~/interfaces/ui';
@@ -18,9 +19,9 @@ import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { useBookmarkFolderDeleteModal, usePageDeleteModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 
-import BookmarkFolderItemControl from './BookmarkFolderItemControl';
-import BookmarkFolderNameInput from './BookmarkFolderNameInput';
-import BookmarkItem from './BookmarkItem';
+import { BookmarkFolderItemControl } from './BookmarkFolderItemControl';
+import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
+import { BookmarkItem } from './BookmarkItem';
 
 
 type BookmarkFolderItemProps = {
@@ -35,7 +36,7 @@ type DragItemDataType = {
   parentFolder: BookmarkFolderItems
 } & BookmarkFolderItemProps & IPageHasId
 
-const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
+export 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,
@@ -57,9 +58,8 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteBookmarkFolderModal } = useBookmarkFolderDeleteModal();
 
-  const hasChildren = useCallback((): boolean => {
-    return children != null && children.length > 0;
-  }, [children]);
+  const childrenExists = hasChildren(children);
+
 
   const loadChildFolder = useCallback(async() => {
     setIsOpen(!isOpen);
@@ -98,11 +98,11 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
 
   const onClickPlusButton = useCallback(async(e) => {
     e.stopPropagation();
-    if (!isOpen && hasChildren()) {
+    if (!isOpen && childrenExists) {
       setIsOpen(true);
     }
     setIsCreateAction(true);
-  }, [hasChildren, isOpen]);
+  }, [childrenExists, isOpen]);
 
   const onClickDeleteBookmarkHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
     const pageDeletedHandler: OnDeletedFunction = (pathOrPathsToDelete, _isRecursively, isCompletely) => {
@@ -214,7 +214,6 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
     });
   };
 
-
   const renderBookmarkItem = () => {
     return isOpen && bookmarks?.map((bookmark) => {
       return (
@@ -258,7 +257,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
         onClick={loadChildFolder}
       >
         <div className="grw-triangle-container d-flex justify-content-center">
-          {hasChildren() && (
+          {childrenExists && (
             <button
               type="button"
               className={`grw-foldertree-triangle-btn btn ${isOpen ? 'grw-foldertree-open' : ''}`}
@@ -287,9 +286,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
               <p className={'text-truncate m-auto '}>{name}</p>
             </div>
           </>
-        )
-
-        }
+        )}
         <div className="grw-foldertree-control d-flex">
           <BookmarkFolderItemControl
             onClickRename={onClickRenameHandler}
@@ -308,9 +305,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
           >
             <i className="icon-plus d-block p-0" />
           </button>
-
         </div>
-
       </li>
       {isCreateAction && (
         <div className="flex-fill">
@@ -329,5 +324,3 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
     </div>
   );
 };
-
-export default BookmarkFolderItem;

+ 1 - 3
packages/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx

@@ -11,7 +11,7 @@ type BookmarkFolderItemControlProps = {
   onClickRename: () => void
   onClickDelete: () => void
 }
-const BookmarkFolderItemControl = (props: BookmarkFolderItemControlProps): JSX.Element => {
+export const BookmarkFolderItemControl = (props: BookmarkFolderItemControlProps): JSX.Element => {
   const { t } = useTranslation();
   const { children, onClickRename, onClickDelete } = props;
   const [isOpen, setIsOpen] = useState(false);
@@ -46,5 +46,3 @@ const BookmarkFolderItemControl = (props: BookmarkFolderItemControlProps): JSX.E
     </Dropdown>
   );
 };
-
-export default BookmarkFolderItemControl;

+ 91 - 63
packages/app/src/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -14,17 +14,17 @@ import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookma
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { useSWRxCurrentPage } from '~/stores/page';
 
-import FolderIcon from '../Icons/FolderIcon';
+import { FolderIcon } from '../Icons/FolderIcon';
 
-import BookmarkFolderMenuItem from './BookmarkFolderMenuItem';
-import BookmarkFolderNameInput from './BookmarkFolderNameInput';
+import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
+import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 
 
 type Props = {
   children?: React.ReactNode
 }
 
-const BookmarkFolderMenu = (props: Props): JSX.Element => {
+export const BookmarkFolderMenu = (props: Props): JSX.Element => {
   const { t } = useTranslation();
   const { children } = props;
   const [isCreateAction, setIsCreateAction] = useState(false);
@@ -66,12 +66,10 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
     setIsOpen(!isOpen);
     mutateBookmarkFolderData();
     if (isOpen && bookmarkFolders != null) {
-      bookmarkFolders?.forEach((bookmarkFolder) => {
+      bookmarkFolders.forEach((bookmarkFolder) => {
         bookmarkFolder.bookmarks.forEach((bookmark) => {
           if (bookmark.page._id === currentPage?._id) {
-            if (bookmark.page._id === currentPage?._id) {
-              setSelectedItem(bookmarkFolder._id);
-            }
+            setSelectedItem(bookmarkFolder._id);
           }
         });
       });
@@ -92,10 +90,7 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
 
 
   const isBookmarkFolderExists = useCallback((): boolean => {
-    if (bookmarkFolders && bookmarkFolders.length > 0) {
-      return true;
-    }
-    return false;
+    return bookmarkFolders != null && bookmarkFolders.length > 0;
   }, [bookmarkFolders]);
 
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
@@ -131,59 +126,94 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
   }, [currentPage?._id, isBookmarked, mutateBookmarkFolderData, mutateBookmarkInfo, mutateUserBookmarks, toggleBookmarkHandler, t]);
 
 
-  const renderBookmarkMenuItem = (child ?:BookmarkFolderItems[]) => {
-    if (!child) {
+  const renderBookmarkMenuItem = (child?: BookmarkFolderItems[]) => {
+    const renderSubmenu = () => {
+      if (child == null) {
+        return <></>;
+      }
       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>
-              <DropdownItem divider />
-            </>)
-
-          }
-
-          { isCreateAction ? (
-            <div className='mx-2'>
-              <BookmarkFolderNameInput
-                onClickOutside={() => setIsCreateAction(false)}
-                onPressEnter={onPressEnterHandlerForCreate}
-              />
+        <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}
+                  onSelectedChild={() => setSelectedItem(null)}
+                />
+                {isOpen && renderSubmenu()}
+              </div>
             </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>
+          ))}
+        </div>
+      );
+    };
+
+    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 />
-              {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}
-                      onSelectedChild={() => setSelectedItem(null)}
-                    />
-                    {isOpen && (
-                      <div className="bookmark-folder-submenu">
-                        {renderBookmarkMenuItem(folder.children)}
-                      </div>
-                    )}
-                  </div>
+            <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() && (
+          <>
+            <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}
+                    onSelectedChild={() => setSelectedItem(null)}
+                  />
+                  {isOpen && renderSubmenu()}
                 </div>
-              ))}
-            </>
-          )}
-        </>
-      );
-    }
+              </div>
+            ))}
+          </>
+        )}
+      </>
+    );
   };
 
   return (
@@ -202,5 +232,3 @@ const BookmarkFolderMenu = (props: Props): JSX.Element => {
     </UncontrolledDropdown>
   );
 };
-
-export default BookmarkFolderMenu;

+ 46 - 49
packages/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx

@@ -7,6 +7,7 @@ import {
 } from 'reactstrap';
 
 import { apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
+import { hasChildren } from '~/client/util/bookmark-utils';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 import { onDeletedBookmarkFolderFunction } from '~/interfaces/ui';
@@ -15,10 +16,10 @@ 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 { FolderIcon } from '../Icons/FolderIcon';
+import { TriangleIcon } from '../Icons/TriangleIcon';
 
-import BookmarkFolderNameInput from './BookmarkFolderNameInput';
+import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
 
 
 type Props = {
@@ -26,7 +27,7 @@ type Props = {
   onSelectedChild: () => void
   isSelected: boolean
 }
-const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
+export const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
   const {
     item, isSelected, onSelectedChild,
   } = props;
@@ -42,9 +43,7 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
 
   const isBookmarked = userBookmarkInfo?.isBookmarked;
 
-  const hasChildren = useCallback((): boolean => {
-    return item.children.length > 0;
-  }, [item.children.length]);
+  const childrenExists = hasChildren(item);
 
   const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
     try {
@@ -124,52 +123,52 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
   }, [onSelectedChild, isBookmarked, mutateBookamrkData, mutateBookmarkInfo, currentPage?._id, mutateUserBookmarks, t]);
 
   const renderBookmarkSubMenuItem = useCallback(() => {
+    if (!isOpen) {
+      return <></>;
+    }
     return (
-      <>
-        {isOpen && (
-          <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>
-            )}
-
-            {hasChildren() && (<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
-                    onSelectedChild={() => setSelectedItem(null)}
-                    item={child}
-                    isSelected={selectedItem === child._id}
-                  />
-                </div>
-              </div>
-            ))}
-          </DropdownMenu>
+      <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
+                onSelectedChild={() => setSelectedItem(null)}
+                item={child}
+                isSelected={selectedItem === child._id}
+              />
+            </div>
+          </div>
+        ))}
+      </DropdownMenu>
     );
-  }, [hasChildren,
+  }, [isOpen,
       isCreateAction,
-      isOpen, item.children,
-      onClickChildMenuItemHandler,
-      onClickNewBookmarkFolder,
       onPressEnterHandlerForCreate,
       t,
+      childrenExists,
+      item.children,
+      onClickNewBookmarkFolder,
       selectedItem,
+      onClickChildMenuItemHandler,
   ]);
 
   return (
@@ -208,7 +207,7 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
           onClick={e => e.stopPropagation()}
           onMouseEnter={onMouseEnterHandler}
         >
-          {hasChildren()
+          {childrenExists
             ? <TriangleIcon />
             : (
               <i className="icon-plus d-block pl-0" />
@@ -229,5 +228,3 @@ const BookmarkFolderMenuItem = (props: Props): JSX.Element => {
     </>
   );
 };
-
-export default BookmarkFolderMenuItem;

+ 3 - 16
packages/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx

@@ -1,6 +1,7 @@
 import { useTranslation } from 'next-i18next';
 
-import ClosableTextInput, { AlertInfo, AlertType } from '~/components/Common/ClosableTextInput';
+import { inputValidator } from '~/client/util/input-validator-utils';
+import ClosableTextInput from '~/components/Common/ClosableTextInput';
 
 
 type Props = {
@@ -9,23 +10,12 @@ type Props = {
   value?: string
 }
 
-const BookmarkFolderNameInput = (props: Props): JSX.Element => {
+export const BookmarkFolderNameInput = (props: Props): JSX.Element => {
   const {
     onClickOutside, onPressEnter, value,
   } = props;
   const { t } = useTranslation();
 
-
-  const inputValidator = (title: string | null): AlertInfo | null => {
-    if (title == null || title === '' || title.trim() === '') {
-      return {
-        type: AlertType.WARNING,
-        message: t('form_validation.title_required'),
-      };
-    }
-    return null;
-  };
-
   return (
     <div className="flex-fill folder-name-input">
       <ClosableTextInput
@@ -38,6 +28,3 @@ const BookmarkFolderNameInput = (props: Props): JSX.Element => {
     </div>
   );
 };
-
-
-export default BookmarkFolderNameInput;

+ 38 - 45
packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -14,8 +14,8 @@ import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { usePageDeleteModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 
-import BookmarkFolderItem from './BookmarkFolderItem';
-import BookmarkItem from './BookmarkItem';
+import { BookmarkFolderItem } from './BookmarkFolderItem';
+import { BookmarkItem } from './BookmarkItem';
 
 import styles from './BookmarkFolderTree.module.scss';
 
@@ -30,7 +30,7 @@ type DragItemDataType = {
   parentFolder: BookmarkFolderItems | null
  } & IPageHasId
 
-const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
+export const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
   const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const { t } = useTranslation();
   const { isUserHomePage } = props;
@@ -117,49 +117,42 @@ const BookmarkFolderTree = (props: BookmarkFolderTreeProps): JSX.Element => {
 
 
   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) => {
-            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 != null && bookmarkFolderData.length > 0 && (
-          <div ref={(c) => { dropRef(c) }} className="grw-drop-item-area">
-            <div className="grw-accept-drop-item">
-              <div className="d-flex flex-column align-items-center">
-                {t('bookmark_folder.drop_item_here')}
-              </div>
+    <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 != null && bookmarkFolderData.length > 0 && (
+        <div ref={(c) => { dropRef(c) }} className="grw-drop-item-area">
+          <div className="grw-accept-drop-item">
+            <div className="d-flex flex-column align-items-center">
+              {t('bookmark_folder.drop_item_here')}
             </div>
-
           </div>
-        )}
-      </div>
-    </>
+        </div>
+      )}
+    </div>
   );
-
-
 };
-
-export default BookmarkFolderTree;

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

@@ -9,13 +9,14 @@ import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
 
 import { unbookmark } from '~/client/services/page-operation';
 import { apiv3Put } from '~/client/util/apiv3-client';
+import { inputValidator } from '~/client/util/input-validator-utils';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { BookmarkFolderItems, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
 import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { useSWRxPageInfo } from '~/stores/page';
 
-import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
+import ClosableTextInput from '../Common/ClosableTextInput';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 import { PageListItemS } from '../PageList/PageListItemS';
 
@@ -28,7 +29,7 @@ type Props = {
   parentFolder: BookmarkFolderItems | null
 }
 
-const BookmarkItem = (props: Props): JSX.Element => {
+export const BookmarkItem = (props: Props): JSX.Element => {
   const { t } = useTranslation();
   const {
     bookmarkedPage, onUnbookmarked, onRenamed, onClickDeleteMenuItem, parentFolder,
@@ -53,16 +54,6 @@ const BookmarkItem = (props: Props): JSX.Element => {
     setRenameInputShown(true);
   }, []);
 
-  const inputValidator = (title: string | null): AlertInfo | null => {
-    if (title == null || title === '' || title.trim() === '') {
-      return {
-        type: AlertType.WARNING,
-        message: t('form_validation.title_required'),
-      };
-    }
-
-    return null;
-  };
 
   const pressEnterForRenameHandler = useCallback(async(inputText: string) => {
     const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(bookmarkedPage.path ?? ''));
@@ -161,5 +152,3 @@ const BookmarkItem = (props: Props): JSX.Element => {
     </li>
   );
 };
-
-export default BookmarkItem;

+ 2 - 2
packages/app/src/components/DeleteBookmarkFolderModal.tsx

@@ -8,7 +8,7 @@ import {
 
 import { apiv3Delete } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
-import FolderIcon from '~/components/Icons/FolderIcon';
+import { FolderIcon } from '~/components/Icons/FolderIcon';
 import { useBookmarkFolderDeleteModal } from '~/stores/modal';
 
 
@@ -67,4 +67,4 @@ const DeleteBookmarkFolderModal: FC = () => {
   );
 };
 
-export default DeleteBookmarkFolderModal;
+export { DeleteBookmarkFolderModal };

+ 1 - 3
packages/app/src/components/Icons/CompressIcon.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-const CompressIcon = ():JSX.Element => {
+export const CompressIcon = ():JSX.Element => {
   return (
     <svg xmlns="http://www.w3.org/2000/svg"
       width="18"
@@ -15,5 +15,3 @@ const CompressIcon = ():JSX.Element => {
     </svg>
   );
 };
-
-export default CompressIcon;

+ 1 - 3
packages/app/src/components/Icons/ExpandIcon.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-const ExpandIcon = (): JSX.Element => {
+export const ExpandIcon = (): JSX.Element => {
   return (
     <svg xmlns="http://www.w3.org/2000/svg"
       width="18"
@@ -16,5 +16,3 @@ const ExpandIcon = (): JSX.Element => {
     </svg>
   );
 };
-
-export default ExpandIcon;

+ 1 - 3
packages/app/src/components/Icons/FolderIcon.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 type Props = {
   isOpen: boolean
 }
-const FolderIcon = (props: Props): JSX.Element => {
+export const FolderIcon = (props: Props): JSX.Element => {
   const { isOpen } = props;
 
   return (
@@ -35,5 +35,3 @@ const FolderIcon = (props: Props): JSX.Element => {
   );
 
 };
-
-export default FolderIcon;

+ 1 - 3
packages/app/src/components/Icons/FolderPlusIcon.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-const FolderPlusIcon = (): JSX.Element => (
+export const FolderPlusIcon = (): JSX.Element => (
   <svg
     width="18"
     height="18"
@@ -14,5 +14,3 @@ const FolderPlusIcon = (): JSX.Element => (
 
   </svg>
 );
-
-export default FolderPlusIcon;

+ 1 - 3
packages/app/src/components/Icons/TriangleIcon.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-const TriangleIcon = (): JSX.Element => (
+export const TriangleIcon = (): JSX.Element => (
   <svg
     xmlns="http://www.w3.org/2000/svg"
     width="12"
@@ -13,5 +13,3 @@ const TriangleIcon = (): JSX.Element => (
     </g>
   </svg>
 );
-
-export default TriangleIcon;

+ 1 - 1
packages/app/src/components/Layout/BasicLayout.tsx

@@ -22,7 +22,7 @@ const PageDeleteModal = dynamic(() => import('../PageDeleteModal'), { ssr: false
 const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
 const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false });
 const PageAccessoriesModal = dynamic(() => import('../PageAccessoriesModal'), { ssr: false });
-const DeleteBookmarkFolderModal = dynamic(() => import('../DeleteBookmarkFolderModal'), { ssr: false });
+const DeleteBookmarkFolderModal = dynamic(() => import('../DeleteBookmarkFolderModal').then(mod => mod.DeleteBookmarkFolderModal), { ssr: false });
 // Fab
 const Fab = dynamic(() => import('../Fab').then(mod => mod.Fab), { ssr: false });
 

+ 5 - 6
packages/app/src/components/Navbar/SubNavButtons.tsx

@@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next';
 import { DropdownItem } from 'reactstrap';
 
 import {
-  toggleBookmark, toggleLike, toggleSubscribe,
+  toggleLike, toggleSubscribe,
 } from '~/client/services/page-operation';
 import { toastError } from '~/client/util/apiNotification';
 import {
@@ -13,10 +13,10 @@ import {
 import { useIsGuestUser } from '~/stores/context';
 import { IPageForPageDuplicateModal } from '~/stores/modal';
 
-import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '../../stores/bookmark';
+import { useSWRBookmarkInfo } from '../../stores/bookmark';
 import { useSWRxPageInfo } from '../../stores/page';
 import { useSWRxUsersList } from '../../stores/user';
-import BookmarkButtons from '../BookmarkButtons';
+import { BookmarkButtons } from '../BookmarkButtons';
 import {
   AdditionalMenuItemsRendererProps, ForceHideMenuItems, MenuItemType,
   PageItemControl,
@@ -93,8 +93,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
 
   const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId, shareLinkId);
 
-  const { data: bookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(pageId);
-  const { mutate: mutateCurrentUserBookmark } = useSWRxCurrentUserBookmarks();
+  const { data: bookmarkInfo } = useSWRBookmarkInfo(pageId);
 
   const likerIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.likerIds ?? []).slice(0, 15) : [];
   const seenUserIds = isIPageInfoForEntity(pageInfo) ? (pageInfo.seenUserIds ?? []).slice(0, 15) : [];
@@ -203,7 +202,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
   }
 
   const {
-    sumOfLikers, sumOfSeenUsers, isLiked, bookmarkCount, isBookmarked,
+    sumOfLikers, sumOfSeenUsers, isLiked,
   } = pageInfo;
 
   const forceHideMenuItemsWithBookmark = forceHideMenuItems ?? [];

+ 2 - 12
packages/app/src/components/PageList/BookmarkList.tsx

@@ -11,10 +11,11 @@ import { DropdownToggle } from 'reactstrap';
 import { unbookmark } from '~/client/services/page-operation';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiv3Put } from '~/client/util/apiv3-client';
+import { inputValidator } from '~/client/util/input-validator-utils';
 import { IPageHasId } from '~/interfaces/page';
 import loggerFactory from '~/utils/logger';
 
-import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
+import ClosableTextInput from '../Common/ClosableTextInput';
 import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 
 import { PageListItemS } from './PageListItemS';
@@ -34,17 +35,6 @@ export const BookmarkList = (props:Props): JSX.Element => {
   const { t } = useTranslation();
   const [isRenameInputShown, setIsRenameInputShown] = useState(false);
 
-  const inputValidator = (title: string | null): AlertInfo | null => {
-    if (title == null || title === '' || title.trim() === '') {
-      return {
-        type: AlertType.WARNING,
-        message: t('form_validation.title_required'),
-      };
-    }
-
-    return null;
-  };
-
   const bookmarkMenuItemClickHandler = useCallback(async() => {
     await unbookmark(page._id);
     onUnbookmarked();

+ 3 - 5
packages/app/src/components/Sidebar/Bookmarks.tsx

@@ -5,9 +5,9 @@ import { useTranslation } from 'react-i18next';
 
 import { useIsGuestUser } from '~/stores/context';
 
-import BookamrkContents from './Bookmarks/BookmarkContents';
+import { BookmarkContents } from './Bookmarks/BookmarkContents';
 
-const Bookmarks = () : JSX.Element => {
+export const Bookmarks = () : JSX.Element => {
   const { t } = useTranslation();
   const { data: isGuestUser } = useIsGuestUser();
 
@@ -21,10 +21,8 @@ const Bookmarks = () : JSX.Element => {
           { t('Not available for guest') }
         </h4>
       ) : (
-        <BookamrkContents />
+        <BookmarkContents />
       )}
     </>
   );
 };
-
-export default Bookmarks;

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

@@ -4,13 +4,13 @@ import { useTranslation } from 'next-i18next';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
-import BookmarkFolderNameInput from '~/components/Bookmarks/BookmarkFolderNameInput';
-import BookmarkFolderTree from '~/components/Bookmarks/BookmarkFolderTree';
-import FolderPlusIcon from '~/components/Icons/FolderPlusIcon';
+import { BookmarkFolderNameInput } from '~/components/Bookmarks/BookmarkFolderNameInput';
+import { BookmarkFolderTree } from '~/components/Bookmarks/BookmarkFolderTree';
+import { FolderPlusIcon } from '~/components/Icons/FolderPlusIcon';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 
 
-const BookmarkContents = (): JSX.Element => {
+export const BookmarkContents = (): JSX.Element => {
 
   const { t } = useTranslation();
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
@@ -68,5 +68,3 @@ const BookmarkContents = (): JSX.Element => {
     </>
   );
 };
-
-export default BookmarkContents;

+ 3 - 12
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -13,7 +13,8 @@ import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
 import { bookmark, unbookmark, resumeRenameOperation } from '~/client/services/page-operation';
 import { toastWarning, toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
-import TriangleIcon from '~/components/Icons/TriangleIcon';
+import { inputValidator } from '~/client/util/input-validator-utils';
+import { TriangleIcon } from '~/components/Icons/TriangleIcon';
 import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
 import {
   IPageHasId, IPageInfoAll, IPageToDeleteWithMeta,
@@ -25,7 +26,7 @@ import { usePageTreeDescCountMap } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import { shouldRecoverPagePaths } from '~/utils/page-operation';
 
-import ClosableTextInput, { AlertInfo, AlertType } from '../../Common/ClosableTextInput';
+import ClosableTextInput from '../../Common/ClosableTextInput';
 import CountBadge from '../../Common/CountBadge';
 import { PageItemControl } from '../../Common/Dropdown/PageItemControl';
 
@@ -370,16 +371,6 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     }
   };
 
-  const inputValidator = (title: string | null): AlertInfo | null => {
-    if (title == null || title === '' || title.trim() === '') {
-      return {
-        type: AlertType.WARNING,
-        message: t('form_validation.title_required'),
-      };
-    }
-
-    return null;
-  };
 
   /**
    * Users do not need to know if all pages have been renamed.

+ 1 - 1
packages/app/src/components/Sidebar/SidebarContents.tsx

@@ -3,7 +3,7 @@ import React, { memo } from 'react';
 import { SidebarContentsType } from '~/interfaces/ui';
 import { useCurrentSidebarContents } from '~/stores/ui';
 
-import Bookmarks from './Bookmarks';
+import { Bookmarks } from './Bookmarks';
 import CustomSidebar from './CustomSidebar';
 import PageTree from './PageTree';
 import RecentChanges from './RecentChanges';

+ 5 - 5
packages/app/src/components/UsersHomePageFooter.tsx

@@ -10,11 +10,11 @@ import { RecentCreated } from '~/components/RecentCreated/RecentCreated';
 import styles from '~/components/UsersHomePageFooter.module.scss';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 
-import BookmarkFolderNameInput from './Bookmarks/BookmarkFolderNameInput';
-import BookmarkFolderTree from './Bookmarks/BookmarkFolderTree';
-import CompressIcon from './Icons/CompressIcon';
-import ExpandIcon from './Icons/ExpandIcon';
-import FolderPlusIcon from './Icons/FolderPlusIcon';
+import { BookmarkFolderNameInput } from './Bookmarks/BookmarkFolderNameInput';
+import { BookmarkFolderTree } from './Bookmarks/BookmarkFolderTree';
+import { CompressIcon } from './Icons/CompressIcon';
+import { ExpandIcon } from './Icons/ExpandIcon';
+import { FolderPlusIcon } from './Icons/FolderPlusIcon';
 
 
 export type UsersHomePageFooterProps = {