PageListItem.tsx 5.6 KB

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