SearchResultListItem.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import React, { FC } from 'react';
  2. import Clamp from 'react-multiline-clamp';
  3. import { useTranslation } from 'react-i18next';
  4. import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
  5. import { DevidedPagePath } from '@growi/core';
  6. import { IPageSearchResultData } from '../../interfaces/search';
  7. import loggerFactory from '~/utils/logger';
  8. import { IPageHasId } from '~/interfaces/page';
  9. const logger = loggerFactory('growi:searchResultList');
  10. type PageItemControlProps = {
  11. page: IPageHasId,
  12. }
  13. const PageItemControl: FC<PageItemControlProps> = (props: {page: IPageHasId}) => {
  14. const { page } = props;
  15. const { t } = useTranslation('');
  16. return (
  17. <>
  18. <button
  19. type="button"
  20. className="btn-link nav-link dropdown-toggle dropdown-toggle-no-caret border-0 rounded grw-btn-page-management py-0"
  21. data-toggle="dropdown"
  22. >
  23. <i className="fa fa-ellipsis-v text-muted"></i>
  24. </button>
  25. <div className="dropdown-menu dropdown-menu-right">
  26. {/* TODO: if there is the following button in XD add it here
  27. <button
  28. type="button"
  29. className="btn btn-link p-0"
  30. value={page.path}
  31. onClick={(e) => {
  32. window.location.href = e.currentTarget.value;
  33. }}
  34. >
  35. <i className="icon-login" />
  36. </button>
  37. */}
  38. {/*
  39. TODO: add function to the following buttons like using modal or others
  40. ref: https://estoc.weseek.co.jp/redmine/issues/79026
  41. */}
  42. <button className="dropdown-item text-danger" type="button" onClick={() => console.log('delete modal show')}>
  43. <i className="icon-fw icon-fire"></i>{t('Delete')}
  44. </button>
  45. <button className="dropdown-item" type="button" onClick={() => console.log('duplicate modal show')}>
  46. <i className="icon-fw icon-star"></i>{t('Add to bookmark')}
  47. </button>
  48. <button className="dropdown-item" type="button" onClick={() => console.log('duplicate modal show')}>
  49. <i className="icon-fw icon-docs"></i>{t('Duplicate')}
  50. </button>
  51. <button className="dropdown-item" type="button" onClick={() => console.log('rename function will be added')}>
  52. <i className="icon-fw icon-action-redo"></i>{t('Move/Rename')}
  53. </button>
  54. </div>
  55. </>
  56. );
  57. };
  58. type Props = {
  59. page: IPageSearchResultData,
  60. isSelected: boolean,
  61. onClickInvoked?: (pageId: string) => void,
  62. }
  63. const SearchResultListItem: FC<Props> = (props:Props) => {
  64. const { page: { pageData, pageMeta }, isSelected } = props;
  65. // Add prefix 'id_' in pageId, because scrollspy of bootstrap doesn't work when the first letter of id attr of target component is numeral.
  66. const pageId = `#${pageData._id}`;
  67. const dPagePath = new DevidedPagePath(pageData.path, false, true);
  68. const pagePathElem = <PagePathLabel page={pageData} isFormerOnly />;
  69. const onClickInvoked = (pageId) => {
  70. if (props.onClickInvoked != null) {
  71. props.onClickInvoked(pageId);
  72. }
  73. };
  74. return (
  75. <li key={pageData._id} className={`page-list-li search-page-item w-100 border-bottom px-4 list-group-item-action ${isSelected ? 'active' : ''}`}>
  76. <a
  77. className="d-block pt-3"
  78. href={pageId}
  79. onClick={() => onClickInvoked(pageData._id)}
  80. >
  81. <div className="d-flex">
  82. {/* checkbox */}
  83. <div className="form-check my-auto mr-3">
  84. <input className="form-check-input my-auto" type="checkbox" value="" id="flexCheckDefault" />
  85. </div>
  86. <div className="w-100">
  87. {/* page path */}
  88. <small className="mb-1">
  89. <i className="icon-fw icon-home"></i>
  90. {pagePathElem}
  91. </small>
  92. <div className="d-flex my-1 align-items-center">
  93. {/* page title */}
  94. <h3 className="mb-0">
  95. <UserPicture user={pageData.lastUpdateUser} />
  96. <span className="mx-2">{dPagePath.latter}</span>
  97. </h3>
  98. {/* page meta */}
  99. <div className="d-flex mx-2">
  100. <PageListMeta page={pageData} bookmarkCount={pageMeta.bookmarkCount} />
  101. </div>
  102. {/* doropdown icon includes page control buttons */}
  103. <div className="ml-auto">
  104. <PageItemControl page={pageData} />
  105. </div>
  106. </div>
  107. <div className="my-2">
  108. <Clamp
  109. lines={2}
  110. >
  111. {pageMeta.elasticSearchResult != null
  112. && <div className="mt-1" dangerouslySetInnerHTML={{ __html: pageMeta.elasticSearchResult.snippet }}></div>}
  113. </Clamp>
  114. </div>
  115. </div>
  116. </div>
  117. {/* TODO: adjust snippet position */}
  118. </a>
  119. </li>
  120. );
  121. };
  122. export default SearchResultListItem;