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

Create bookmark folder list component

https://youtrack.weseek.co.jp/issue/GW-7895
- Update  current user bookmark folder return type
- Add BookmarkFolderItems type
- Create bookmark folder tree and item component
- Create temporary style for bookmark item
- Implement BookmarkFolderTree component on Bookmark sidebar
Mudana-Grune 3 лет назад
Родитель
Сommit
3bea49d9f2

+ 2 - 7
packages/app/src/components/Sidebar/Bookmarks.tsx

@@ -13,6 +13,7 @@ import { usePageDeleteModal } from '~/stores/modal';
 
 
 import BookmarkFolder from './Bookmarks/BookmarkFolder';
+import BookmarkFolderTree from './Bookmarks/BookmarkFolderTree';
 import BookmarkItem from './Bookmarks/BookmarkItem';
 
 const Bookmarks = () : JSX.Element => {
@@ -105,13 +106,7 @@ const Bookmarks = () : JSX.Element => {
             folderName={folderName}
           />
           {/* TODO: List Bookmark Folder */}
-          <ul>
-            {currentUserBookmarkFolder?.map((bookmarkFolder, idx) => {
-              return (
-                <li key={idx}>{bookmarkFolder.name}</li>
-              );
-            })}
-          </ul>
+          <BookmarkFolderTree />
         </>
       )
       }

+ 49 - 0
packages/app/src/components/Sidebar/Bookmarks/BookmarkFolderItem.tsx

@@ -0,0 +1,49 @@
+import { FC } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import CountBadge from '~/components/Common/CountBadge';
+import TriangleIcon from '~/components/Icons/TriangleIcon';
+import { BookmarkFolderItems } from '~/server/models/bookmark-folder';
+
+
+type BookmarkFolderItemProps = {
+  bookmarkFolders: BookmarkFolderItems
+}
+const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkFolderItemProps) => {
+  const { bookmarkFolders } = props;
+  const { t } = useTranslation();
+  const hasChildren = bookmarkFolders.childCount > 0;
+  return (
+    <div className="grw-pagetree-item-container" >
+      <li className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center">
+        <div className="grw-triangle-container d-flex justify-content-center">
+          {hasChildren && (
+            <button
+              type="button"
+              className={'grw-pagetree-triangle-btn btn '}
+              onClick={() => {}}
+            >
+              <div className="d-flex justify-content-center">
+                <TriangleIcon />
+              </div>
+            </button>
+          )}
+
+          {
+            <div className='grw-pagetree-title-anchor flex-grow-1'>
+              <p className={'text-truncate m-auto '}>{bookmarkFolders.bookmarkFolder.name}</p>
+            </div>
+          }
+          {hasChildren && (
+            <div className="grw-pagetree-count-wrapper">
+              <CountBadge count={bookmarkFolders.childCount } />
+            </div>
+          )}
+        </div>
+      </li>
+    </div>
+  );
+};
+
+export default BookmarkFolderItem;

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

@@ -0,0 +1,141 @@
+@use '~/styles/variables' as var;
+$grw-sidebar-content-header-height: 58px;
+$grw-sidebar-content-footer-height: 50px;
+$grw-pagetree-item-padding-left: 10px;
+
+.grw-pagetree {
+  :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 {
+        font-size: 18px;
+      }
+    }
+
+    .list-group-item {
+      .grw-visible-on-hover {
+        display: none;
+      }
+
+      &:hover {
+        .grw-visible-on-hover {
+          display: block;
+        }
+
+        .grw-count-badge {
+          display: none;
+        }
+      }
+
+      .grw-pagetree-triangle-btn {
+        background-color: transparent;
+        transition: all 0.2s ease-out;
+        transform: rotate(0deg);
+
+        &.grw-pagetree-open {
+          transform: rotate(90deg);
+        }
+      }
+
+      .grw-pagetree-title-anchor {
+        width: 100%;
+        overflow: hidden;
+        text-decoration: none;
+      }
+
+      .grw-pagetree-count-wrapper {
+        display: inline-block;
+
+        &:hover {
+          display: none;
+        }
+      }
+    }
+
+    .grw-pagetree-item-container {
+      .grw-triangle-container {
+        min-width: 35px;
+        height: 40px;
+      }
+    }
+  }
+  &:global{
+    // To realize a hierarchical structure, set multiplied padding-left to each pagetree-item
+    > .grw-pagetree-item-container {
+      > .list-group-item {
+        padding-left: 0;
+      }
+      > .grw-pagetree-item-children {
+        > .grw-pagetree-item-container {
+          > .list-group-item {
+            padding-left: $grw-pagetree-item-padding-left;
+          }
+          > .grw-pagetree-item-children {
+            > .grw-pagetree-item-container {
+              > .list-group-item {
+                padding-left: $grw-pagetree-item-padding-left * 2;
+              }
+              > .grw-pagetree-item-children {
+                > .grw-pagetree-item-container {
+                  > .list-group-item {
+                    padding-left: $grw-pagetree-item-padding-left * 3;
+                  }
+                  > .grw-pagetree-item-children {
+                    > .grw-pagetree-item-container {
+                      > .list-group-item {
+                        padding-left: $grw-pagetree-item-padding-left * 4;
+                      }
+                      > .grw-pagetree-item-children {
+                        > .grw-pagetree-item-container {
+                          > .list-group-item {
+                            padding-left: $grw-pagetree-item-padding-left * 5;
+                          }
+                          > .grw-pagetree-item-children {
+                            > .grw-pagetree-item-container {
+                              > .list-group-item {
+                                padding-left: $grw-pagetree-item-padding-left * 6;
+                              }
+                              > .grw-pagetree-item-children {
+                                > .grw-pagetree-item-container {
+                                  > .list-group-item {
+                                    padding-left: $grw-pagetree-item-padding-left * 7;
+                                  }
+                                  > .grw-pagetree-item-children {
+                                    > .grw-pagetree-item-container {
+                                      > .list-group-item {
+                                        padding-left: $grw-pagetree-item-padding-left * 8;
+                                      }
+                                      > .grw-pagetree-item-children {
+                                        > .grw-pagetree-item-container {
+                                          > .list-group-item {
+                                            padding-left: $grw-pagetree-item-padding-left * 9;
+                                          }
+                                          .grw-pagetree-item-children {
+                                            > .grw-pagetree-item-container {
+                                              > .list-group-item {
+                                                padding-left: $grw-pagetree-item-padding-left * 10;
+                                              }
+                                            }
+                                          }
+                                        }
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}

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

@@ -0,0 +1,32 @@
+import React, { useRef } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { useSWRxCurrentUserBookmarkFolders } from '~/stores/bookmark';
+
+import BookmarkFolderItem from './BookmarkFolderItem';
+
+import styles from './BookmarkFolderTree.module.scss';
+
+const BookmarkFolderTree = (): JSX.Element => {
+  const { t } = useTranslation();
+  const rootFolderRef = useRef(null);
+  const { data: bookmarkFolderData, mutate: mutateBookmarkFolderData } = useSWRxCurrentUserBookmarkFolders();
+
+  if (bookmarkFolderData != null) {
+    return (
+
+      <ul className={`grw-pagetree ${styles['grw-pagetree']} list-group p-3`} ref={rootFolderRef}>
+        {bookmarkFolderData.map((item, index) => {
+          return (
+            <BookmarkFolderItem key={index} bookmarkFolders={item} />
+          );
+        })}
+      </ul>
+    );
+  }
+  return <></>;
+
+};
+
+export default BookmarkFolderTree;

+ 4 - 0
packages/app/src/server/models/bookmark-folder.ts

@@ -13,6 +13,10 @@ const logger = loggerFactory('growi:models:bookmark-folder');
 
 export class InvalidParentBookmarkFolder extends ExtensibleCustomError {}
 
+export type BookmarkFolderItems = {
+  bookmarkFolder: IBookmarkFolderDocument
+  childCount: number
+}
 
 export type IBookmarkFolderDocument = {
   name: string

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

@@ -3,7 +3,7 @@ import { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { IPageHasId } from '~/interfaces/page';
-import { IBookmarkFolderDocument } from '~/server/models/bookmark-folder';
+import { BookmarkFolderItems } from '~/server/models/bookmark-folder';
 
 import { apiv3Get } from '../client/util/apiv3-client';
 import { IBookmarkInfo } from '../interfaces/bookmark-info';
@@ -40,7 +40,7 @@ export const useSWRxCurrentUserBookmarks = (pageNum?: Nullable<number>): SWRResp
   );
 };
 
-export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse<IBookmarkFolderDocument[], Error> => {
+export const useSWRxCurrentUserBookmarkFolders = () : SWRResponse<BookmarkFolderItems[], Error> => {
   return useSWRImmutable(
     '/bookmark-folder/list',
     endpoint => apiv3Get(endpoint).then((response) => {