BookmarkFolderMenu.tsx 6.3 KB

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