BookmarkFolderMenu.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import React, {
  2. useCallback, useState,
  3. } from 'react';
  4. import { useTranslation } from 'next-i18next';
  5. import {
  6. DropdownItem, DropdownMenu, UncontrolledDropdown,
  7. } from 'reactstrap';
  8. import { addBookmarkToFolder, addNewFolder, toggleBookmark } from '~/client/util/bookmark-utils';
  9. import { toastError } from '~/client/util/toastr';
  10. import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
  11. import { useSWRBookmarkInfo, useSWRxCurrentUserBookmarks } from '~/stores/bookmark';
  12. import { useSWRxBookmarkFolderAndChild } from '~/stores/bookmark-folder';
  13. import { useSWRxCurrentPage } from '~/stores/page';
  14. import { FolderIcon } from '../Icons/FolderIcon';
  15. import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
  16. import { BookmarkFolderNameInput } from './BookmarkFolderNameInput';
  17. type Props = {
  18. children?: React.ReactNode
  19. }
  20. export const BookmarkFolderMenu = (props: Props): JSX.Element => {
  21. const { t } = useTranslation();
  22. const { children } = props;
  23. const [isCreateAction, setIsCreateAction] = useState(false);
  24. const { data: bookmarkFolders, mutate: mutateBookmarkFolderData } = useSWRxBookmarkFolderAndChild();
  25. const [selectedItem, setSelectedItem] = useState<string | null>(null);
  26. const { data: currentPage } = useSWRxCurrentPage();
  27. const { data: userBookmarkInfo, mutate: mutateBookmarkInfo } = useSWRBookmarkInfo(currentPage?._id);
  28. const { mutate: mutateUserBookmarks } = useSWRxCurrentUserBookmarks();
  29. const isBookmarked = userBookmarkInfo?.isBookmarked ?? false;
  30. const [isOpen, setIsOpen] = useState(false);
  31. const toggleBookmarkHandler = useCallback(async() => {
  32. try {
  33. if (currentPage != null) {
  34. await toggleBookmark(currentPage._id, isBookmarked);
  35. }
  36. }
  37. catch (err) {
  38. toastError(err);
  39. }
  40. }, [currentPage, isBookmarked]);
  41. const onClickNewBookmarkFolder = useCallback(() => {
  42. setIsCreateAction(true);
  43. }, []);
  44. const onUnbookmarkHandler = useCallback(async() => {
  45. await toggleBookmarkHandler();
  46. mutateUserBookmarks();
  47. mutateBookmarkInfo();
  48. mutateBookmarkFolderData();
  49. setSelectedItem(null);
  50. }, [mutateBookmarkFolderData, mutateBookmarkInfo, mutateUserBookmarks, toggleBookmarkHandler]);
  51. const toggleHandler = useCallback(async() => {
  52. setIsOpen(!isOpen);
  53. mutateBookmarkFolderData();
  54. if (isOpen && bookmarkFolders != null) {
  55. bookmarkFolders.forEach((bookmarkFolder) => {
  56. bookmarkFolder.bookmarks.forEach((bookmark) => {
  57. if (bookmark.page._id === currentPage?._id) {
  58. setSelectedItem(bookmarkFolder._id);
  59. }
  60. });
  61. });
  62. }
  63. if (!isOpen && !isBookmarked) {
  64. try {
  65. toggleBookmarkHandler();
  66. mutateUserBookmarks();
  67. mutateBookmarkInfo();
  68. setSelectedItem(null);
  69. }
  70. catch (err) {
  71. toastError(err);
  72. }
  73. }
  74. }, [isOpen, mutateBookmarkFolderData, bookmarkFolders, isBookmarked, currentPage?._id, toggleBookmarkHandler, mutateUserBookmarks, mutateBookmarkInfo]);
  75. const isBookmarkFolderExists = useCallback((): boolean => {
  76. return bookmarkFolders != null && bookmarkFolders.length > 0;
  77. }, [bookmarkFolders]);
  78. const onPressEnterHandlerForCreate = useCallback(async(folderName: string) => {
  79. try {
  80. await addNewFolder(folderName, null);
  81. await mutateBookmarkFolderData();
  82. setIsCreateAction(false);
  83. }
  84. catch (err) {
  85. toastError(err);
  86. }
  87. }, [mutateBookmarkFolderData]);
  88. const onMenuItemClickHandler = useCallback(async(itemId: string) => {
  89. try {
  90. if (isBookmarked) {
  91. await toggleBookmarkHandler();
  92. }
  93. if (currentPage != null) {
  94. await addBookmarkToFolder(currentPage._id, itemId);
  95. }
  96. mutateBookmarkInfo();
  97. mutateUserBookmarks();
  98. }
  99. catch (err) {
  100. toastError(err);
  101. }
  102. mutateBookmarkFolderData();
  103. setSelectedItem(itemId);
  104. }, [mutateBookmarkFolderData, isBookmarked, currentPage, mutateBookmarkInfo, mutateUserBookmarks, toggleBookmarkHandler]);
  105. const renderBookmarkMenuItem = (child?: BookmarkFolderItems[]) => {
  106. const renderSubmenu = () => {
  107. if (child == null) {
  108. return <></>;
  109. }
  110. return (
  111. <div className="bookmark-folder-submenu">
  112. {child.map(folder => (
  113. <div key={folder._id}>
  114. <div
  115. className="dropdown-item grw-bookmark-folder-menu-item"
  116. tabIndex={0}
  117. role="menuitem"
  118. onClick={() => onMenuItemClickHandler(folder._id)}
  119. >
  120. <BookmarkFolderMenuItem
  121. item={folder}
  122. isSelected={selectedItem === folder._id}
  123. onSelectedChild={() => setSelectedItem(null)}
  124. />
  125. {isOpen && renderSubmenu()}
  126. </div>
  127. </div>
  128. ))}
  129. </div>
  130. );
  131. };
  132. return (
  133. <>
  134. {isBookmarked && (
  135. <>
  136. <DropdownItem
  137. toggle={false}
  138. onClick={onUnbookmarkHandler}
  139. className={'grw-bookmark-folder-menu-item text-danger'}
  140. >
  141. <i className="fa fa-bookmark"></i>{' '}
  142. <span className="mx-2 ">
  143. {t('bookmark_folder.cancel_bookmark')}
  144. </span>
  145. </DropdownItem>
  146. <DropdownItem divider />
  147. </>
  148. )}
  149. {isCreateAction ? (
  150. <div className="mx-2">
  151. <BookmarkFolderNameInput
  152. onClickOutside={() => setIsCreateAction(false)}
  153. onPressEnter={onPressEnterHandlerForCreate}
  154. />
  155. </div>
  156. ) : (
  157. <DropdownItem
  158. toggle={false}
  159. onClick={onClickNewBookmarkFolder}
  160. className="grw-bookmark-folder-menu-item"
  161. >
  162. <FolderIcon isOpen={false} />
  163. <span className="mx-2 ">{t('bookmark_folder.new_folder')}</span>
  164. </DropdownItem>
  165. )}
  166. {isBookmarkFolderExists() && (
  167. <>
  168. <DropdownItem divider />
  169. {bookmarkFolders?.map(folder => (
  170. <div key={folder._id}>
  171. <div
  172. className="dropdown-item grw-bookmark-folder-menu-item"
  173. tabIndex={0}
  174. role="menuitem"
  175. onClick={() => onMenuItemClickHandler(folder._id)}
  176. >
  177. <BookmarkFolderMenuItem
  178. item={folder}
  179. isSelected={selectedItem === folder._id}
  180. onSelectedChild={() => setSelectedItem(null)}
  181. />
  182. {isOpen && renderSubmenu()}
  183. </div>
  184. </div>
  185. ))}
  186. </>
  187. )}
  188. </>
  189. );
  190. };
  191. return (
  192. <UncontrolledDropdown
  193. onToggle={toggleHandler}
  194. direction={ isBookmarkFolderExists() ? 'up' : 'down' }
  195. className='grw-bookmark-folder-dropdown'>
  196. {children}
  197. <DropdownMenu
  198. right
  199. className='grw-bookmark-folder-menu'
  200. positionFixed
  201. >
  202. { renderBookmarkMenuItem() }
  203. </DropdownMenu>
  204. </UncontrolledDropdown>
  205. );
  206. };