SearchPage.jsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. // This is the root component for #search-page
  2. import React from 'react';
  3. import PropTypes from 'prop-types';
  4. import { withTranslation } from 'react-i18next';
  5. import { withUnstatedContainers } from './UnstatedUtils';
  6. import AppContainer from '~/client/services/AppContainer';
  7. import { toastError } from '~/client/util/apiNotification';
  8. import SearchPageLayout from './SearchPage/SearchPageLayout';
  9. import SearchResultContent from './SearchPage/SearchResultContent';
  10. import SearchResultList from './SearchPage/SearchResultList';
  11. import SearchControl from './SearchPage/SearchControl';
  12. export const specificPathNames = {
  13. user: '/user',
  14. trash: '/trash',
  15. };
  16. class SearchPage extends React.Component {
  17. constructor(props) {
  18. super(props);
  19. // NOTE : selectedPages is deletion related state, will be used later in story 77535, 77565.
  20. // deletionModal, deletion related functions are all removed, add them back when necessary.
  21. // i.e ) in story 77525 or any tasks implementing deletion functionalities
  22. this.state = {
  23. searchingKeyword: decodeURI(this.props.query.q) || '',
  24. searchedKeyword: '',
  25. searchedPages: [],
  26. searchResultMeta: {},
  27. selectedPage: {},
  28. selectedPages: new Set(),
  29. searchResultCount: 0,
  30. activePage: 1,
  31. pagingLimit: 10, // change to an appropriate limit number
  32. excludeUsersHome: true,
  33. excludeTrash: true,
  34. };
  35. this.changeURL = this.changeURL.bind(this);
  36. this.search = this.search.bind(this);
  37. this.selectPage = this.selectPage.bind(this);
  38. this.toggleCheckBox = this.toggleCheckBox.bind(this);
  39. this.onExcludeUsersHome = this.onExcludeUsersHome.bind(this);
  40. this.onExcludeTrash = this.onExcludeTrash.bind(this);
  41. this.onPagingNumberChanged = this.onPagingNumberChanged.bind(this);
  42. }
  43. componentDidMount() {
  44. const keyword = this.state.searchingKeyword;
  45. if (keyword !== '') {
  46. this.search({ keyword });
  47. }
  48. }
  49. componentDidUpdate(prevProps, prevState) {
  50. if (this.state.activePage !== prevState.activePage) {
  51. this.search({ keyword: this.state.searchedKeyword });
  52. }
  53. }
  54. static getQueryByLocation(location) {
  55. const search = location.search || '';
  56. const query = {};
  57. search.replace(/^\?/, '').split('&').forEach((element) => {
  58. const queryParts = element.split('=');
  59. query[queryParts[0]] = decodeURIComponent(queryParts[1]).replace(/\+/g, ' ');
  60. });
  61. return query;
  62. }
  63. onExcludeUsersHome() {
  64. this.setState({ excludeUsersHome: !this.state.excludeUsersHome });
  65. }
  66. onExcludeTrash() {
  67. this.setState({ excludeTrash: !this.state.excludeTrash });
  68. }
  69. changeURL(keyword, refreshHash) {
  70. let hash = window.location.hash || '';
  71. // TODO 整理する
  72. if (refreshHash || this.state.searchedKeyword !== '') {
  73. hash = '';
  74. }
  75. if (window.history && window.history.pushState) {
  76. window.history.pushState('', `Search - ${keyword}`, `/_search?q=${keyword}${hash}`);
  77. }
  78. }
  79. createSearchQuery(keyword) {
  80. let query = keyword;
  81. // pages included in specific path are not retrived when prefix is added
  82. if (this.state.excludeTrash) {
  83. query = `${query} -prefix:${specificPathNames.trash}`;
  84. }
  85. if (this.state.excludeUsersHome) {
  86. query = `${query} -prefix:${specificPathNames.user}`;
  87. }
  88. return query;
  89. }
  90. async onPagingNumberChanged(activePage) {
  91. this.setState({ activePage });
  92. }
  93. async search(data) {
  94. const keyword = data.keyword;
  95. if (keyword === '') {
  96. this.setState({
  97. searchingKeyword: '',
  98. searchedKeyword: '',
  99. searchedPages: [],
  100. searchResultMeta: {},
  101. searchResultCount: 0,
  102. activePage: 1,
  103. });
  104. return true;
  105. }
  106. this.setState({
  107. searchingKeyword: keyword,
  108. });
  109. const pagingLimit = this.state.pagingLimit;
  110. const offset = (this.state.activePage * pagingLimit) - pagingLimit;
  111. try {
  112. const res = await this.props.appContainer.apiGet('/search', {
  113. q: this.createSearchQuery(keyword),
  114. limit: pagingLimit,
  115. offset,
  116. });
  117. this.changeURL(keyword);
  118. if (res.data.length > 0) {
  119. this.setState({
  120. searchedKeyword: keyword,
  121. searchedPages: res.data,
  122. searchResultMeta: res.meta,
  123. searchResultCount: res.meta.total,
  124. selectedPage: res.data[0],
  125. });
  126. }
  127. else {
  128. this.setState({
  129. searchedKeyword: keyword,
  130. searchedPages: [],
  131. searchResultMeta: {},
  132. searchResultCount: 0,
  133. selectedPage: {},
  134. activePage: 1,
  135. });
  136. }
  137. }
  138. catch (err) {
  139. toastError(err);
  140. }
  141. }
  142. selectPage= (pageId) => {
  143. const index = this.state.searchedPages.findIndex((page) => {
  144. return page._id === pageId;
  145. });
  146. this.setState({
  147. selectedPage: this.state.searchedPages[index],
  148. });
  149. }
  150. toggleCheckBox = (page) => {
  151. if (this.state.selectedPages.has(page)) {
  152. this.state.selectedPages.delete(page);
  153. }
  154. else {
  155. this.state.selectedPages.add(page);
  156. }
  157. }
  158. renderSearchResultContent = () => {
  159. return (
  160. <SearchResultContent
  161. appContainer={this.props.appContainer}
  162. searchingKeyword={this.state.searchingKeyword}
  163. selectedPage={this.state.selectedPage}
  164. >
  165. </SearchResultContent>
  166. );
  167. }
  168. renderSearchResultList = () => {
  169. return (
  170. <SearchResultList
  171. pages={this.state.searchedPages}
  172. deletionMode={false}
  173. selectedPage={this.state.selectedPage}
  174. selectedPages={this.state.selectedPages}
  175. onClickInvoked={this.selectPage}
  176. onChangedInvoked={this.toggleCheckBox}
  177. activePage={this.state.activePage}
  178. onPagingNumberChanged={this.onPagingNumberChanged}
  179. searchResultCount={this.state.searchResultCount}
  180. pagingLimit={this.state.pagingLimit}
  181. />
  182. );
  183. }
  184. renderSearchControl = () => {
  185. return (
  186. <SearchControl
  187. searchingKeyword={this.state.searchingKeyword}
  188. appContainer={this.props.appContainer}
  189. onSearchInvoked={this.search}
  190. onExcludeUsersHome={this.onExcludeUsersHome}
  191. onExcludeTrash={this.onExcludeTrash}
  192. >
  193. </SearchControl>
  194. );
  195. }
  196. render() {
  197. return (
  198. <div>
  199. <SearchPageLayout
  200. SearchControl={this.renderSearchControl}
  201. SearchResultList={this.renderSearchResultList}
  202. SearchResultContent={this.renderSearchResultContent}
  203. searchResultMeta={this.state.searchResultMeta}
  204. searchingKeyword={this.state.searchedKeyword}
  205. >
  206. </SearchPageLayout>
  207. </div>
  208. );
  209. }
  210. }
  211. /**
  212. * Wrapper component for using unstated
  213. */
  214. const SearchPageWrapper = withUnstatedContainers(SearchPage, [AppContainer]);
  215. SearchPage.propTypes = {
  216. t: PropTypes.func.isRequired, // i18next
  217. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  218. query: PropTypes.object,
  219. };
  220. SearchPage.defaultProps = {
  221. // pollInterval: 1000,
  222. query: SearchPage.getQueryByLocation(window.location || {}),
  223. };
  224. export default withTranslation()(SearchPageWrapper);