BookmarkFolderMenu.tsx 6.4 KB

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