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

Merge pull request #7011 from weseek/imprv/gw7849-imprv-bookmarks-like-sidebar

Imprv: gw7849 imprv bookmarks like sidebar
Kaori Tokashiki 3 лет назад
Родитель
Сommit
6d6f6b9705

+ 13 - 11
packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx → packages/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -1,5 +1,5 @@
 import {
-  FC, useCallback, useEffect, useState,
+  FC, useCallback, useEffect, useState, useMemo,
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
@@ -38,7 +38,7 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
   const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
   const [isDeleteFolderModalShown, setIsDeleteFolderModalShown] = useState<boolean>(false);
 
-  const childCount = useCallback((): number => {
+  const childCount = useMemo((): number => {
     if (currentChildren != null && currentChildren.length > children.length) {
       return currentChildren.length;
     }
@@ -120,7 +120,8 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
     }
   }, [folderId, loadParent, t]);
 
-  const onClickPlusButton = useCallback(async() => {
+  const onClickPlusButton = useCallback(async(e) => {
+    e.stopPropagation();
     if (!isOpen && hasChildren()) {
       setIsOpen(true);
     }
@@ -154,9 +155,8 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
 
 
   return (
-    <div id={`bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container"
-    >
-      <li className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center">
+    <div id={`grw-bookmark-folder-item-${folderId}`} className="grw-foldertree-item-container">
+      <li className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center" onClick={loadChildFolder}>
         <div className="grw-triangle-container d-flex justify-content-center">
           {hasChildren() && (
             <button
@@ -183,12 +183,12 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
           />
         ) : (
           <>
-            <div className='grw-foldertree-title-anchor flex-grow-1 pl-2' onClick={loadChildFolder}>
+            <div className='grw-foldertree-title-anchor pl-2' >
               <p className={'text-truncate m-auto '}>{name}</p>
             </div>
             {hasChildren() && (
               <div className="grw-foldertree-count-wrapper">
-                <CountBadge count={ childCount() } />
+                <CountBadge count={ childCount } />
               </div>
             )}
           </>
@@ -200,9 +200,11 @@ const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderIt
             onClickRename={onClickRenameHandler}
             onClickDelete={onClickDeleteHandler}
           >
-            <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>
+            <div onClick={e => e.stopPropagation()}>
+              <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>
+            </div>
           </BookmarkFolderItemControl>
           <button
             type="button"

+ 0 - 0
packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItemControl.tsx → packages/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx


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

@@ -27,7 +27,7 @@ const BookmarkFolderNameInput = (props: Props): JSX.Element => {
   };
 
   return (
-    <div className="flex-fill">
+    <div className="flex-fill folder-name-input">
       <ClosableTextInput
         value={ value }
         placeholder={t('bookmark_folder.input_placeholder')}

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

@@ -0,0 +1 @@
+@use '~/styles/molecules/bookmark-folder-tree';

+ 34 - 0
packages/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -0,0 +1,34 @@
+
+import { useTranslation } from 'next-i18next';
+
+import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
+
+import BookmarkFolderItem from './BookmarkFolderItem';
+
+import styles from './BookmarkFolderTree.module.scss';
+
+
+const BookmarkFolderTree = (): JSX.Element => {
+  const { t } = useTranslation();
+  const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild();
+
+  return (
+    <>
+      <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-3 pt-3`}>
+        {bookmarkFolderData?.map((item) => {
+          return (
+            <BookmarkFolderItem
+              key={item._id}
+              bookmarkFolder={item}
+              isOpen={false}
+            />
+          );
+        })}
+      </ul>
+    </>
+  );
+
+
+};
+
+export default BookmarkFolderTree;

+ 2 - 3
packages/app/src/components/Sidebar/Bookmarks/BookmarkItem.tsx → packages/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -1,4 +1,3 @@
-
 import React, { useCallback, useState } from 'react';
 
 import nodePath from 'path';
@@ -13,8 +12,8 @@ import { apiv3Put } from '~/client/util/apiv3-client';
 import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
 
 
-import ClosableTextInput, { AlertInfo, AlertType } from '../../Common/ClosableTextInput';
-import { MenuItemType, PageItemControl } from '../../Common/Dropdown/PageItemControl';
+import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
+import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 
 
 type Props = {

+ 11 - 27
packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.tsx → packages/app/src/components/Bookmarks/BookmarkItemList.tsx

@@ -1,5 +1,4 @@
-
-import { useCallback } from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
@@ -7,19 +6,16 @@ import { toastSuccess } from '~/client/util/apiNotification';
 import { IPageToDeleteWithMeta } from '~/interfaces/page';
 import { OnDeletedFunction } from '~/interfaces/ui';
 import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
-import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 import { usePageDeleteModal } from '~/stores/modal';
 
-import BookmarkFolderItem from './BookmarkFolderItem';
 import BookmarkItem from './BookmarkItem';
 
 import styles from './BookmarkFolderTree.module.scss';
 
 
-const BookmarkFolderTree = (): JSX.Element => {
+const BookmarkItemList = (): JSX.Element => {
   const { t } = useTranslation();
   const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
-  const { data: bookmarkFolderData } = useSWRxBookamrkFolderAndChild();
   const { open: openDeleteModal } = usePageDeleteModal();
 
   const deleteMenuItemClickHandler = useCallback((pageToDelete: IPageToDeleteWithMeta) => {
@@ -40,26 +36,16 @@ const BookmarkFolderTree = (): JSX.Element => {
     openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
   }, [mutateCurrentUserBookmarks, openDeleteModal, t]);
 
-
   return (
     <>
-      <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group p-3`}>
-        {bookmarkFolderData?.map((item) => {
-          return (
-            <BookmarkFolderItem
-              key={item._id}
-              bookmarkFolder={item}
-              isOpen={false}
-            />
-          );
-        })}
-        {currentUserBookmarksData?.length === 0 && (
-          <div className="pt-3">
-            <h5 className="pl-3">
-              { t('No bookmarks yet') }
-            </h5>
-          </div>
-        )}
+      {currentUserBookmarksData?.length === 0 && (
+        <div className="pt-3">
+          <h5 className="pl-3">
+            { t('No bookmarks yet') }
+          </h5>
+        </div>
+      )}
+      <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-3 pt-2 pb-3`}>
         { currentUserBookmarksData?.map((currentUserBookmark) => {
           return (
             <BookmarkItem
@@ -74,8 +60,6 @@ const BookmarkFolderTree = (): JSX.Element => {
       </ul>
     </>
   );
-
-
 };
 
-export default BookmarkFolderTree;
+export default BookmarkItemList;

+ 0 - 0
packages/app/src/components/Sidebar/Bookmarks/DeleteBookmarkFolderModal.tsx → packages/app/src/components/Bookmarks/DeleteBookmarkFolderModal.tsx


+ 19 - 0
packages/app/src/components/Icons/CompressIcon.tsx

@@ -0,0 +1,19 @@
+import React from 'react';
+
+const CompressIcon = ():JSX.Element => {
+  return (
+    <svg xmlns="http://www.w3.org/2000/svg"
+      width="18"
+      height="18"
+      viewBox="0 0 45 45"
+    >
+      <path
+        fill="currentColor"
+        d="M22.45 44v-7.9l-3.85 3.8-2.1-2.1 7.45-7.4 7.35 7.4-2.1
+            2.1-3.75-3.8V44ZM8.05 27.5v-3H40v3Zm0-6.05v-3H40v3Zm15.9-5.85-7.4-7.4 2.1-2.1
+            3.75 3.8V2h3v7.9l3.85-3.8 2.1 2.1Z"/>
+    </svg>
+  );
+};
+
+export default CompressIcon;

+ 20 - 0
packages/app/src/components/Icons/ExpandIcon.tsx

@@ -0,0 +1,20 @@
+import React from 'react';
+
+const ExpandIcon = (): JSX.Element => {
+  return (
+    <svg xmlns="http://www.w3.org/2000/svg"
+      width="18"
+      height="18"
+      viewBox="0 0 45 45"
+    >
+      <path
+        fill="currentColor"
+        d="M8.1 44v-3h31.8v3Zm16-4.5-7.6-7.6 2.15-2.15
+            3.95 3.95V14.3l-3.95 3.95-2.15-2.15 7.6-7.6 7.6 7.6-2.15
+            2.15-3.95-3.95v19.4l3.95-3.95 2.15 2.15ZM8.1 7V4h31.8v3Z"
+      />
+    </svg>
+  );
+};
+
+export default ExpandIcon;

+ 99 - 55
packages/app/src/components/PageList/BookmarkList.tsx

@@ -1,79 +1,123 @@
-import React, { useState, useCallback, useEffect } from 'react';
+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 { toastError } from '~/client/util/apiNotification';
-import { apiv3Get } from '~/client/util/apiv3-client';
-import { MyBookmarkList } from '~/interfaces/bookmark-info';
+import { unbookmark } from '~/client/services/page-operation';
+import { toastError, toastSuccess } from '~/client/util/apiNotification';
+import { apiv3Put } from '~/client/util/apiv3-client';
+import { IPageHasId } from '~/interfaces/page';
 import loggerFactory from '~/utils/logger';
 
-import PaginationWrapper from '../PaginationWrapper';
+import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
+import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
 
 import { PageListItemS } from './PageListItemS';
 
 const logger = loggerFactory('growi:BookmarkList');
 
-type BookmarkListProps = {
-  userId: string
+type Props = {
+  page: IPageHasId
+  onRenamed: () => void
+  onUnbookmarked: () => void
+  onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void
 }
-
-export const BookmarkList = (props: BookmarkListProps): JSX.Element => {
-  const { userId } = props;
-
+export const BookmarkList = (props:Props): JSX.Element => {
+  const {
+    page, onRenamed, onUnbookmarked, onClickDeleteMenuItem,
+  } = props;
   const { t } = useTranslation();
-  const [pages, setPages] = useState<MyBookmarkList>([]);
-  const [activePage, setActivePage] = useState(1);
-  const [totalItemsCount, setTotalItemsCount] = useState(0);
-  const [pagingLimit, setPagingLimit] = useState(10);
+  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'),
+      };
+    }
 
-  const setPageNumber = (selectedPageNumber) => {
-    setActivePage(selectedPageNumber);
+    return null;
   };
 
-  const getMyBookmarkList = useCallback(async() => {
-    const page = activePage;
-
-    try {
-      const res = await apiv3Get(`/bookmarks/${userId}`, { page });
-      const { paginationResult } = res.data;
+  const bookmarkMenuItemClickHandler = useCallback(async() => {
+    await unbookmark(page._id);
+    onUnbookmarked();
+  }, [page._id, onUnbookmarked]);
 
-      setPages(paginationResult.docs);
-      setTotalItemsCount(paginationResult.totalDocs);
-      setPagingLimit(paginationResult.limit);
+  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.');
     }
-    catch (error) {
-      logger.error('failed to fetch data', error);
-      toastError(error, 'Error occurred in bookmark page list');
+
+    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;
     }
-  }, [activePage, userId]);
 
-  useEffect(() => {
-    getMyBookmarkList();
-  }, [getMyBookmarkList]);
+    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);
+      toastError(err);
+    }
+  }, [onRenamed, page, t]);
 
   return (
-    <div className="bookmarks-list-container">
-      {pages.length === 0 ? t('No bookmarks yet') : (
-        <>
-          <ul className="page-list-ul page-list-ul-flat mb-3">
-
-            {pages.map(page => (
-              <li key={`my-bookmarks:${page._id}`} className="mt-4">
-                <PageListItemS page={page.page} />
-              </li>
-            ))}
-
-          </ul>
-          <PaginationWrapper
-            activePage={activePage}
-            changePage={setPageNumber}
-            totalItemsCount={totalItemsCount}
-            pagingLimit={pagingLimit}
-            align="center"
-            size="sm"
-          />
-        </>
+    <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}
+          inputValidator={inputValidator}
+        />
+      ) : (
+        <PageListItemS page={page} />
       )}
-    </div>
+
+      <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>
+
   );
 };

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

@@ -4,12 +4,12 @@ import { useTranslation } from 'next-i18next';
 
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
+import BookmarkFolderNameInput from '~/components/Bookmarks/BookmarkFolderNameInput';
+import BookmarkFolderTree from '~/components/Bookmarks/BookmarkFolderTree';
+import BookmarkItemList from '~/components/Bookmarks/BookmarkItemList';
 import FolderPlusIcon from '~/components/Icons/FolderPlusIcon';
 import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
 
-import BookmarkFolderNameInput from './BookmarkFolderNameInput';
-import BookmarkFolderTree from './BookmarkFolderTree';
-
 
 const BookmarkContents = (): JSX.Element => {
 
@@ -66,6 +66,7 @@ const BookmarkContents = (): JSX.Element => {
         renderAddNewBookmarkFolder()
       }
       <BookmarkFolderTree />
+      <BookmarkItemList />
     </>
   );
 };

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

@@ -1,11 +1,74 @@
 @use '~/styles/molecules/page_list';
+@use '~/styles/variables' as var;
+$grw-sidebar-content-header-height: 58px;
+$grw-sidebar-content-footer-height: 50px;
 
 .user-page-footer :global {
   .grw-user-page-list-m {
+    .list-group{
+      .list-group-item {
+        .grw-visible-on-hover {
+          display: none;
+        }
+
+        &:hover {
+          .grw-visible-on-hover {
+            display: block;
+          }
+        }
+        .grw-triangle-container{
+          svg {
+            width: 12px;
+            height: 12px;
+          }
+        }
+        svg{
+          width: 20px;
+          height: 20px;
+        }
+        min-height: 40px;
+        border-radius: 0px;
+      }
+    }
+
+    .grw-foldertree-item-container {
+      input {
+        max-width: 25%;
+      }
+    }
+    .grw-foldertree-title-anchor{
+      width: fit-content !important;
+      margin-right: 20px;
+    }
     svg {
       width: 35px;
       height: 35px;
       margin-bottom: 6px;
     }
+    .new-bookmark-folder{
+      max-height: 30px;
+      svg {
+        width: 18px;
+        height: 18px;
+      }
+    }
+    .grw-expand-compress-btn {
+      max-height: 40px;
+      svg {
+        width: 18px;
+        height: 18px;
+        margin-bottom: 3px;
+      }
+    }
   }
 }
+
+.grw-bookarks-contents-compressed {
+  max-height: calc(70vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
+  overflow-y: scroll;
+}
+
+.grw-bookarks-contents-expanded {
+  max-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
+  overflow-y: scroll;
+}

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

@@ -1,11 +1,25 @@
-import React from 'react';
+import React, { useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
+import { toastError, toastSuccess } from '~/client/util/apiNotification';
+import { apiv3Post } from '~/client/util/apiv3-client';
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
-import { BookmarkList } from '~/components/PageList/BookmarkList';
 import { RecentCreated } from '~/components/RecentCreated/RecentCreated';
 import styles from '~/components/UsersHomePageFooter.module.scss';
+import { IPageToDeleteWithMeta } from '~/interfaces/page';
+import { OnDeletedFunction } from '~/interfaces/ui';
+import { useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
+import { useSWRxBookamrkFolderAndChild } from '~/stores/bookmark-folder';
+import { usePageDeleteModal } from '~/stores/modal';
+
+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 { BookmarkList } from './PageList/BookmarkList';
+
 
 export type UsersHomePageFooterProps = {
   creatorId: string,
@@ -14,16 +28,105 @@ export type UsersHomePageFooterProps = {
 export const UsersHomePageFooter = (props: UsersHomePageFooterProps): JSX.Element => {
   const { t } = useTranslation();
   const { creatorId } = props;
+  const [isCreateAction, setIsCreateAction] = useState<boolean>(false);
+  const [isExpanded, setIsExpanded] = useState<boolean>(false);
+  const { mutate: mutateChildBookmarkData } = useSWRxBookamrkFolderAndChild(null);
+  const { data: currentUserBookmarksData, mutate: mutateCurrentUserBookmarks } = useSWRxCurrentUserBookmarks();
+  const { open: openDeleteModal } = usePageDeleteModal();
+
+
+  const deleteMenuItemClickHandler = 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 }));
+      }
+      mutateCurrentUserBookmarks();
+    };
+    openDeleteModal([pageToDelete], { onDeleted: pageDeletedHandler });
+  }, [mutateCurrentUserBookmarks, openDeleteModal, t]);
+
+
+  const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
+    try {
+      await apiv3Post('/bookmark-folder', { name: folderName, parent: null });
+      await mutateChildBookmarkData();
+      setIsCreateAction(false);
+      toastSuccess(t('toaster.create_succeeded', { target: t('bookmark_folder.bookmark_folder') }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [mutateChildBookmarkData, t]);
 
   return (
     <div className={`container-lg user-page-footer py-5 ${styles['user-page-footer']}`}>
       <div className="grw-user-page-list-m d-edit-none">
-        <h2 id="bookmarks-list" className="grw-user-page-header border-bottom pb-2 mb-3">
+        <h2 id="bookmarks-list" className="grw-user-page-header border-bottom pb-2 mb-3 d-flex">
           <i style={{ fontSize: '1.3em' }} className="fa fa-fw fa-bookmark-o"></i>
           {t('footer.bookmarks')}
+          <span className="pl-2">
+            <button
+              className="btn btn-outline-secondary btn-sm new-bookmark-folder"
+              onClick={() => setIsCreateAction(true)}
+            >
+              <FolderPlusIcon />
+              <span className="mx-2 ">{t('bookmark_folder.new_folder')}</span>
+            </button>
+          </span>
+          <span className="ml-auto pl-2 ">
+            <button
+              className={`btn btn-sm grw-expand-compress-btn ${isExpanded ? 'active' : ''}`}
+              onClick={() => setIsExpanded(!isExpanded)}
+            >
+              { isExpanded
+                ? <ExpandIcon/>
+                : <CompressIcon />
+              }
+            </button>
+          </span>
         </h2>
-        <div id="user-bookmark-list" className={`page-list ${styles['page-list']}`}>
-          <BookmarkList userId={creatorId} />
+        { isCreateAction && (
+          <div className="row">
+            <div className="col-sm-12 col-md-12 col-lg-4 mb-2">
+              <BookmarkFolderNameInput
+                onClickOutside={() => setIsCreateAction(false)}
+                onPressEnter={onPressEnterHandlerForCreate}
+              />
+            </div>
+
+          </div>
+        )}
+        <div className={`${isExpanded ? `${styles['grw-bookarks-contents-expanded']}` : `${styles['grw-bookarks-contents-compressed']}`}`}>
+          {
+            <BookmarkFolderTree />
+
+          }
+          <div id="user-bookmark-list" className={`page-list p-3 ${styles['page-list']}`}>
+            <div className="grw-bookmarks-list-container">
+              {currentUserBookmarksData?.length === 0
+                ? t('No bookmarks yet')
+                : <ul className="list-group page-list-ul page-list-ul-flat mb-3">
+                  {currentUserBookmarksData?.map(page => (
+                    <BookmarkList
+                      key={page._id}
+                      page={page}
+                      onRenamed={mutateCurrentUserBookmarks}
+                      onUnbookmarked={mutateCurrentUserBookmarks}
+                      onClickDeleteMenuItem={deleteMenuItemClickHandler}
+                    />
+                  ))}
+                </ul>
+              }
+            </div>
+          </div>
         </div>
       </div>
       <div className="grw-user-page-list-m mt-5 d-edit-none">

+ 0 - 3
packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderTree.module.scss → packages/app/src/styles/molecules/_bookmark-folder-tree.scss

@@ -1,11 +1,8 @@
 @use '~/styles/variables' as var;
-$grw-sidebar-content-header-height: 58px;
-$grw-sidebar-content-footer-height: 50px;
 $grw-foldertree-item-padding-left: 10px;
 
 .grw-foldertree {
   :global {
-    min-height: calc(100vh - (var.$grw-navbar-height + var.$grw-navbar-border-width + $grw-sidebar-content-header-height + $grw-sidebar-content-footer-height));
 
     .btn-page-item-control {
       .icon-plus::before {

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

@@ -362,6 +362,35 @@ ul.pagination {
   }
 }
 
+.grw-user-page-list-m {
+  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
+  .grw-foldertree {
+    @include override-list-group-item-for-pagetree(
+      $body-color,
+      lighten($body-bg, 8%),
+      lighten($body-bg, 15%),
+      darken($body-color, 15%),
+      darken($body-color, 10%),
+      lighten($body-bg, 18%),
+      lighten($body-bg, 24%)
+    );
+    .grw-foldertree-triangle-btn {
+      @include mixins.button-outline-svg-icon-variant($secondary, $gray-200);
+    }
+  }
+  .page-list > .grw-bookmarks-list-container > .list-group {
+    @include override-list-group-item-for-pagetree(
+      $body-color,
+      lighten($body-bg, 8%),
+      lighten($body-bg, 15%),
+      darken($body-color, 15%),
+      darken($body-color, 10%),
+      lighten($body-bg, 18%),
+      lighten($body-bg, 24%)
+    );
+  }
+}
+
 .btn.btn-page-item-control {
   @include button-outline-variant($gray-500, $gray-500, $secondary, transparent);
   @include hover() {

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

@@ -240,6 +240,35 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
   }
 }
 
+.grw-user-page-list-m {
+  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
+  .grw-foldertree {
+    @include override-list-group-item-for-pagetree(
+      $body-color,
+      darken($body-bg, 5%),
+      darken($body-bg, 12%),
+      lighten($body-color, 10%),
+      lighten($body-color, 8%),
+      darken($body-bg, 15%),
+      darken($body-bg, 24%)
+    );
+    .grw-foldertree-triangle-btn {
+      @include mixins.button-outline-svg-icon-variant($gray-400, $primary);
+    }
+  }
+  .page-list > .grw-bookmarks-list-container > .list-group {
+    @include override-list-group-item-for-pagetree(
+      $body-color,
+      darken($body-bg, 5%),
+      darken($body-bg, 12%),
+      lighten($body-color, 10%),
+      lighten($body-color, 8%),
+      darken($body-bg, 15%),
+      darken($body-bg, 24%)
+    );
+  }
+}
+
 .btn.btn-page-item-control {
   @include button-outline-variant($gray-500, $primary, lighten($primary, 52%), transparent);
   @include hover() {

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

@@ -711,3 +711,17 @@ Emoji picker modal
 .emoji-picker-modal {
   background-color: transparent !important;
 }
+
+/*
+Expand / compress button bookmark list on users page
+*/
+.grw-user-page-list-m {
+  .grw-expand-compress-btn {
+    color: $body-color;
+    background-color: $body-bg;
+    &.active {
+      background-color: darken($body-bg, 12%),
+    }
+  }
+
+}