BookmarkFolderMenu.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import React, {
  2. useCallback, useMemo, useState, type JSX,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import { DropdownItem, DropdownMenu, UncontrolledDropdown } from 'reactstrap';
  6. import { addBookmarkToFolder, toggleBookmark } from '~/client/util/bookmark-utils';
  7. import { toastError } from '~/client/util/toastr';
  8. import { useCurrentUser } from '~/states/global';
  9. import { useSWRMUTxCurrentUserBookmarks } from '~/stores/bookmark';
  10. import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
  11. import { useSWRMUTxPageInfo } from '~/stores/page';
  12. import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
  13. import styles from './BookmarkFolderMenu.module.scss';
  14. type BookmarkFolderMenuProps = {
  15. isOpen: boolean,
  16. pageId: string,
  17. isBookmarked: boolean,
  18. onToggle?: () => void,
  19. onUnbookmark?: () => void,
  20. children?: React.ReactNode,
  21. }
  22. export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element => {
  23. const {
  24. isOpen, pageId, isBookmarked, onToggle, onUnbookmark, children,
  25. } = props;
  26. const { t } = useTranslation();
  27. const [selectedItem, setSelectedItem] = useState<string | null>(null);
  28. const currentUser = useCurrentUser();
  29. const { data: bookmarkFolders, mutate: mutateBookmarkFolders } = useSWRxBookmarkFolderAndChild(currentUser?._id);
  30. const { trigger: mutateCurrentUserBookmarks } = useSWRMUTxCurrentUserBookmarks();
  31. const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(pageId);
  32. const isBookmarkFolderExists = useMemo((): boolean => {
  33. return bookmarkFolders != null && bookmarkFolders.length > 0;
  34. }, [bookmarkFolders]);
  35. const toggleBookmarkHandler = useCallback(async() => {
  36. try {
  37. await toggleBookmark(pageId, isBookmarked);
  38. }
  39. catch (err) {
  40. toastError(err);
  41. }
  42. }, [isBookmarked, pageId]);
  43. const onUnbookmarkHandler = useCallback(async() => {
  44. if (onUnbookmark != null) {
  45. onUnbookmark();
  46. }
  47. await toggleBookmarkHandler();
  48. setSelectedItem(null);
  49. mutateCurrentUserBookmarks();
  50. mutateBookmarkFolders();
  51. mutatePageInfo();
  52. }, [onUnbookmark, toggleBookmarkHandler, mutateCurrentUserBookmarks, mutateBookmarkFolders, mutatePageInfo]);
  53. const toggleHandler = useCallback(async() => {
  54. // on close
  55. if (isOpen && bookmarkFolders != null) {
  56. bookmarkFolders.forEach((bookmarkFolder) => {
  57. bookmarkFolder.bookmarks.forEach((bookmark) => {
  58. if (bookmark.page?._id === pageId) {
  59. setSelectedItem(bookmarkFolder._id);
  60. }
  61. });
  62. });
  63. }
  64. if (onToggle != null) {
  65. onToggle();
  66. }
  67. if (selectedItem == null) {
  68. setSelectedItem('root');
  69. }
  70. if (!isOpen && !isBookmarked) {
  71. try {
  72. await toggleBookmarkHandler();
  73. mutateCurrentUserBookmarks();
  74. mutatePageInfo();
  75. }
  76. catch (err) {
  77. toastError(err);
  78. }
  79. }
  80. },
  81. [isOpen, bookmarkFolders, onToggle, selectedItem, isBookmarked, pageId, toggleBookmarkHandler, mutateCurrentUserBookmarks, mutatePageInfo]);
  82. const onMenuItemClickHandler = useCallback(async(e, itemId: string) => {
  83. e.stopPropagation();
  84. setSelectedItem(itemId);
  85. try {
  86. await addBookmarkToFolder(pageId, itemId === 'root' ? null : itemId);
  87. mutateCurrentUserBookmarks();
  88. mutateBookmarkFolders();
  89. mutatePageInfo();
  90. }
  91. catch (err) {
  92. toastError(err);
  93. }
  94. }, [pageId, mutateCurrentUserBookmarks, mutateBookmarkFolders, mutatePageInfo]);
  95. const renderBookmarkMenuItem = () => {
  96. return (
  97. <>
  98. <DropdownItem
  99. toggle={false}
  100. onClick={onUnbookmarkHandler}
  101. className="grw-bookmark-folder-menu-item text-danger text-truncate"
  102. >
  103. <span className="material-symbols-outlined">bookmark</span>{' '}
  104. <span className="mx-2">
  105. {t('bookmark_folder.cancel_bookmark')}
  106. </span>
  107. </DropdownItem>
  108. {isBookmarkFolderExists && (
  109. <>
  110. <DropdownItem divider />
  111. <div key="root">
  112. <div
  113. className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action px-4"
  114. tabIndex={0}
  115. role="menuitem"
  116. onClick={e => onMenuItemClickHandler(e, 'root')}
  117. >
  118. <BookmarkFolderMenuItem
  119. itemId="root"
  120. itemName={t('bookmark_folder.root')}
  121. isSelected={selectedItem === 'root'}
  122. />
  123. </div>
  124. </div>
  125. {bookmarkFolders?.map(folder => (
  126. <React.Fragment key={`bookmark-folders-${folder._id}`}>
  127. <div
  128. className="dropdown-item grw-bookmark-folder-menu-item grw-bookmark-folder-menu-item-folder-first list-group-item list-group-item-action"
  129. tabIndex={0}
  130. role="menuitem"
  131. onClick={e => onMenuItemClickHandler(e, folder._id)}
  132. >
  133. <BookmarkFolderMenuItem
  134. itemId={folder._id}
  135. itemName={folder.name}
  136. isSelected={selectedItem === folder._id}
  137. />
  138. </div>
  139. {folder.childFolder?.map(child => (
  140. <div key={child._id}>
  141. <div
  142. className="dropdown-item grw-bookmark-folder-menu-item grw-bookmark-folder-menu-item-folder-second list-group-item list-group-item-action"
  143. tabIndex={0}
  144. role="menuitem"
  145. onClick={e => onMenuItemClickHandler(e, child._id)}
  146. >
  147. <BookmarkFolderMenuItem
  148. itemId={child._id}
  149. itemName={child.name}
  150. isSelected={selectedItem === child._id}
  151. />
  152. </div>
  153. </div>
  154. ))}
  155. </React.Fragment>
  156. ))}
  157. </>
  158. )}
  159. </>
  160. );
  161. };
  162. return (
  163. <UncontrolledDropdown
  164. isOpen={isOpen}
  165. onToggle={toggleHandler}
  166. >
  167. {children}
  168. { isOpen && (
  169. <DropdownMenu
  170. end
  171. persist
  172. strategy="fixed"
  173. container="body"
  174. className={`grw-bookmark-folder-menu ${styles['grw-bookmark-folder-menu']}`}
  175. >
  176. { renderBookmarkMenuItem() }
  177. </DropdownMenu>
  178. ) }
  179. </UncontrolledDropdown>
  180. );
  181. };