BookmarkList.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import React, { useCallback, useState } from 'react';
  2. import nodePath from 'path';
  3. import {
  4. IPageInfoAll, IPageToDeleteWithMeta, pathUtils,
  5. } from '@growi/core';
  6. import { useTranslation } from 'next-i18next';
  7. import { DropdownToggle } from 'reactstrap';
  8. import { unbookmark } from '~/client/services/page-operation';
  9. import { toastError, toastSuccess } from '~/client/util/apiNotification';
  10. import { apiv3Put } from '~/client/util/apiv3-client';
  11. import { IPageHasId } from '~/interfaces/page';
  12. import loggerFactory from '~/utils/logger';
  13. import ClosableTextInput, { AlertInfo, AlertType } from '../Common/ClosableTextInput';
  14. import { MenuItemType, PageItemControl } from '../Common/Dropdown/PageItemControl';
  15. import { PageListItemS } from './PageListItemS';
  16. const logger = loggerFactory('growi:BookmarkList');
  17. type Props = {
  18. page: IPageHasId
  19. onRenamed: () => void
  20. onUnbookmarked: () => void
  21. onClickDeleteMenuItem: (pageToDelete: IPageToDeleteWithMeta) => void
  22. }
  23. export const BookmarkList = (props:Props): JSX.Element => {
  24. const {
  25. page, onRenamed, onUnbookmarked, onClickDeleteMenuItem,
  26. } = props;
  27. const { t } = useTranslation();
  28. const [isRenameInputShown, setIsRenameInputShown] = useState(false);
  29. const inputValidator = (title: string | null): AlertInfo | null => {
  30. if (title == null || title === '' || title.trim() === '') {
  31. return {
  32. type: AlertType.WARNING,
  33. message: t('form_validation.title_required'),
  34. };
  35. }
  36. return null;
  37. };
  38. const bookmarkMenuItemClickHandler = useCallback(async() => {
  39. await unbookmark(page._id);
  40. onUnbookmarked();
  41. }, [page._id, onUnbookmarked]);
  42. const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise<void> => {
  43. if (page._id == null || page.path == null) {
  44. throw Error('_id and path must not be null.');
  45. }
  46. const pageToDelete: IPageToDeleteWithMeta = {
  47. data: {
  48. _id: page._id,
  49. revision: page.revision as string,
  50. path: page.path,
  51. },
  52. meta: pageInfo,
  53. };
  54. onClickDeleteMenuItem(pageToDelete);
  55. }, [onClickDeleteMenuItem, page]);
  56. const pressEnterForRenameHandler = useCallback(async(inputText: string) => {
  57. const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? ''));
  58. const newPagePath = nodePath.resolve(parentPath, inputText);
  59. if (newPagePath === page.path) {
  60. setIsRenameInputShown(false);
  61. return;
  62. }
  63. try {
  64. setIsRenameInputShown(false);
  65. await apiv3Put('/pages/rename', {
  66. pageId: page._id,
  67. revisionId: page.revision,
  68. newPagePath,
  69. });
  70. onRenamed();
  71. toastSuccess(t('renamed_pages', { path: page.path }));
  72. }
  73. catch (err) {
  74. setIsRenameInputShown(true);
  75. toastError(err);
  76. }
  77. }, [onRenamed, page, t]);
  78. return (
  79. <li key={`my-bookmarks:${page?._id}`} className="list-group-item list-group-item-action border-0 py-0 pl-3 d-flex align-items-center">
  80. { isRenameInputShown ? (
  81. <ClosableTextInput
  82. value={nodePath.basename(page.path ?? '')}
  83. placeholder={t('Input page name')}
  84. onClickOutside={() => { setIsRenameInputShown(false) }}
  85. onPressEnter={pressEnterForRenameHandler}
  86. inputValidator={inputValidator}
  87. />
  88. ) : (
  89. <PageListItemS page={page} />
  90. )}
  91. <PageItemControl
  92. pageId={page._id}
  93. isEnableActions
  94. forceHideMenuItems={[MenuItemType.DUPLICATE]}
  95. onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
  96. onClickRenameMenuItem={() => setIsRenameInputShown(true)}
  97. onClickDeleteMenuItem={deleteMenuItemClickHandler}
  98. >
  99. <DropdownToggle color="transparent" className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover mr-1">
  100. <i className="icon-options fa fa-rotate-90 p-1"></i>
  101. </DropdownToggle>
  102. </PageItemControl>
  103. </li>
  104. );
  105. };