PageListItem.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import React, { FC, memo, useCallback } from 'react';
  2. import Clamp from 'react-multiline-clamp';
  3. import { UserPicture, PageListMeta, PagePathLabel } from '@growi/ui';
  4. import { pagePathUtils, DevidedPagePath } from '@growi/core';
  5. import { useIsDeviceSmallerThanLg } from '~/stores/ui';
  6. import { IPageSearchResultData } from '../../interfaces/search';
  7. import PageItemControl from '../Common/Dropdown/PageItemControl';
  8. const { isTopPage } = pagePathUtils;
  9. type Props = {
  10. page: IPageSearchResultData,
  11. isSelected: boolean,
  12. isChecked: boolean,
  13. isEnableActions: boolean,
  14. shortBody?: string
  15. showCheckbox: boolean, // whether you show checkbox on the left side of an item
  16. changeBgColorOnSelected: boolean, // whether you change background color of an item on selected
  17. showPageUpdatedTime: boolean, // whether you show page's updatedAt at the top-right corner of an item
  18. onClickCheckbox?: (pageId: string) => void,
  19. onClickSearchResultItem?: (pageId: string) => void,
  20. onClickDeleteButton?: (pageId: string) => void,
  21. }
  22. const PageListItem: FC<Props> = memo((props:Props) => {
  23. const {
  24. // todo: refactoring variable name to clear what changed
  25. page: { pageData, pageMeta }, isSelected, onClickSearchResultItem, onClickCheckbox,
  26. isChecked, isEnableActions, shortBody, showCheckbox, changeBgColorOnSelected, showPageUpdatedTime,
  27. } = props;
  28. const { data: isDeviceSmallerThanLg } = useIsDeviceSmallerThanLg();
  29. const pagePath: DevidedPagePath = new DevidedPagePath(pageData.path, true);
  30. const pageTitle = (
  31. <PagePathLabel
  32. path={pageMeta.elasticSearchResult?.highlightedPath || pageData.path}
  33. isLatterOnly
  34. isPathIncludedHtml={pageMeta.elasticSearchResult?.isHtmlInPath}
  35. >
  36. </PagePathLabel>
  37. );
  38. const pagePathElem = (
  39. <PagePathLabel
  40. path={pageMeta.elasticSearchResult?.highlightedPath || pageData.path}
  41. isFormerOnly
  42. isPathIncludedHtml={pageMeta.elasticSearchResult?.isHtmlInPath}
  43. />
  44. );
  45. // click event handler
  46. const clickHandler = useCallback(() => {
  47. // do nothing if mobile
  48. if (isDeviceSmallerThanLg) {
  49. return;
  50. }
  51. if (onClickSearchResultItem != null) {
  52. onClickSearchResultItem(pageData._id);
  53. }
  54. }, [isDeviceSmallerThanLg, onClickSearchResultItem, pageData._id]);
  55. const responsiveListStyleClass = `${isDeviceSmallerThanLg ? '' : `list-group-item-action ${changeBgColorOnSelected && isSelected ? 'active' : ''}`}`;
  56. return (
  57. <li
  58. key={pageData._id}
  59. className={`w-100 grw-search-result-item border-bottom ${responsiveListStyleClass}`}
  60. >
  61. <div
  62. className="h-100 text-break"
  63. onClick={clickHandler}
  64. >
  65. <div className="d-flex h-100">
  66. {showCheckbox && (
  67. <div className="form-check d-flex align-items-center justify-content-center px-md-2 pl-3 pr-2 search-item-checkbox">
  68. <input
  69. className="form-check-input position-relative m-0"
  70. type="checkbox"
  71. id="flexCheckDefault"
  72. onChange={() => {
  73. if (onClickCheckbox != null) {
  74. onClickCheckbox(pageData._id);
  75. }
  76. }}
  77. checked={isChecked}
  78. />
  79. </div>
  80. )}
  81. <div className="search-item-text p-md-3 pl-2 py-3 pr-3 flex-grow-1">
  82. {/* page path */}
  83. <h6 className="mb-1 py-1 d-flex justify-content-between">
  84. <a className="d-block" href={pagePath.isRoot ? pagePath.latter : pagePath.former}>
  85. <i className="icon-fw icon-home"></i>
  86. {pagePathElem}
  87. </a>
  88. {/* Todo: show actual time of page updated and apply color */}
  89. {showPageUpdatedTime && (
  90. <p className="mb-0 mr-4 text-muted">Updated: 2056/01/25 03:52:24</p>
  91. )}
  92. </h6>
  93. <div className="d-flex align-items-center mb-2">
  94. {/* Picture */}
  95. <span className="mr-2 d-none d-md-block">
  96. <UserPicture user={pageData.lastUpdateUser} size="sm" />
  97. </span>
  98. {/* page title */}
  99. <Clamp lines={1}>
  100. <span className="py-1 h5 mr-2 mb-0">
  101. <a href={`/${pageData._id}`}>{pageTitle}</a>
  102. </span>
  103. </Clamp>
  104. {/* page meta */}
  105. <div className="d-none d-md-flex item-meta py-0 px-1">
  106. <PageListMeta page={pageData} bookmarkCount={pageMeta.bookmarkCount} />
  107. </div>
  108. {/* doropdown icon includes page control buttons */}
  109. <div className="item-control ml-auto">
  110. <PageItemControl
  111. page={pageData}
  112. onClickDeleteButton={props.onClickDeleteButton}
  113. isEnableActions={isEnableActions}
  114. isDeletable={!isTopPage(pageData.path)}
  115. />
  116. </div>
  117. </div>
  118. <div className="grw-search-result-list-snippet py-1">
  119. <Clamp lines={2}>
  120. {
  121. pageMeta.elasticSearchResult != null && pageMeta.elasticSearchResult?.snippet.length !== 0 ? (
  122. <div dangerouslySetInnerHTML={{ __html: pageMeta.elasticSearchResult.snippet }}></div>
  123. ) : (
  124. <div>{ shortBody != null ? shortBody : 'Loading ...' }</div> // TODO: improve indicator
  125. )
  126. }
  127. </Clamp>
  128. </div>
  129. </div>
  130. </div>
  131. {/* TODO: adjust snippet position */}
  132. </div>
  133. </li>
  134. );
  135. });
  136. export default PageListItem;