SearchPage.jsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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. excludeUsersHome: true,
  31. excludeTrash: true,
  32. };
  33. this.changeURL = this.changeURL.bind(this);
  34. this.search = this.search.bind(this);
  35. this.selectPage = this.selectPage.bind(this);
  36. this.toggleCheckBox = this.toggleCheckBox.bind(this);
  37. this.onExcludeUsersHome = this.onExcludeUsersHome.bind(this);
  38. this.onExcludeTrash = this.onExcludeTrash.bind(this);
  39. }
  40. componentDidMount() {
  41. const keyword = this.state.searchingKeyword;
  42. if (keyword !== '') {
  43. this.search({ keyword });
  44. }
  45. }
  46. static getQueryByLocation(location) {
  47. const search = location.search || '';
  48. const query = {};
  49. search.replace(/^\?/, '').split('&').forEach((element) => {
  50. const queryParts = element.split('=');
  51. query[queryParts[0]] = decodeURIComponent(queryParts[1]).replace(/\+/g, ' ');
  52. });
  53. return query;
  54. }
  55. onExcludeUsersHome() {
  56. this.setState({ excludeUsersHome: !this.state.excludeUsersHome });
  57. }
  58. onExcludeTrash() {
  59. this.setState({ excludeTrash: !this.state.excludeTrash });
  60. }
  61. changeURL(keyword, refreshHash) {
  62. let hash = window.location.hash || '';
  63. // TODO 整理する
  64. if (refreshHash || this.state.searchedKeyword !== '') {
  65. hash = '';
  66. }
  67. if (window.history && window.history.pushState) {
  68. window.history.pushState('', `Search - ${keyword}`, `/_search?q=${keyword}${hash}`);
  69. }
  70. }
  71. createSearchQuery(keyword) {
  72. let query = keyword;
  73. // pages included in specific path are not retrived when prefix is added
  74. if (this.state.excludeTrash) {
  75. query = `${query} -prefix:${specificPathNames.trash}`;
  76. }
  77. if (this.state.excludeUsersHome) {
  78. query = `${query} -prefix:${specificPathNames.user}`;
  79. }
  80. return query;
  81. }
  82. search(data) {
  83. const keyword = data.keyword;
  84. if (keyword === '') {
  85. this.setState({
  86. searchingKeyword: '',
  87. searchedPages: [],
  88. searchResultMeta: {},
  89. });
  90. return true;
  91. }
  92. this.setState({
  93. searchingKeyword: keyword,
  94. });
  95. this.props.appContainer.apiGet('/search', { q: this.createSearchQuery(keyword) })
  96. .then((res) => {
  97. this.changeURL(keyword);
  98. if (res.data.length > 0) {
  99. // TODO: remove creating dummy snippet lines when the data with snippet is abole to be retrieved
  100. res.data.forEach((page) => {
  101. page.snippet = `dummy snippet dummpy snippet dummpy snippet dummpy snippet dummpy snippet
  102. dummpy snippet dummpy snippet dummpy snippet dummpy snippet`;
  103. });
  104. this.setState({
  105. searchedKeyword: keyword,
  106. searchedPages: res.data,
  107. searchResultMeta: res.meta,
  108. searchResultCount: res.totalCount,
  109. selectedPage: res.data[0],
  110. });
  111. }
  112. else {
  113. this.setState({
  114. searchedKeyword: keyword,
  115. searchedPages: [],
  116. searchResultMeta: {},
  117. selectedPage: {},
  118. });
  119. }
  120. })
  121. .catch((err) => {
  122. toastError(err);
  123. });
  124. }
  125. selectPage= (pageId) => {
  126. const index = this.state.searchedPages.findIndex((page) => {
  127. return page._id === pageId;
  128. });
  129. this.setState({
  130. selectedPage: this.state.searchedPages[index],
  131. });
  132. }
  133. toggleCheckBox = (page) => {
  134. if (this.state.selectedPages.has(page)) {
  135. this.state.selectedPages.delete(page);
  136. }
  137. else {
  138. this.state.selectedPages.add(page);
  139. }
  140. }
  141. renderSearchResultContent = () => {
  142. return (
  143. <SearchResultContent
  144. appContainer={this.props.appContainer}
  145. searchingKeyword={this.state.searchingKeyword}
  146. selectedPage={this.state.selectedPage}
  147. >
  148. </SearchResultContent>
  149. );
  150. }
  151. renderSearchResultList = () => {
  152. return (
  153. <SearchResultList
  154. pages={this.state.searchedPages}
  155. deletionMode={false}
  156. selectedPage={this.state.selectedPage}
  157. selectedPages={this.state.selectedPages}
  158. searchResultCount={this.state.searchResultCount}
  159. onClickInvoked={this.selectPage}
  160. onChangedInvoked={this.toggleCheckBox}
  161. >
  162. </SearchResultList>
  163. );
  164. }
  165. renderSearchControl = () => {
  166. return (
  167. <SearchControl
  168. searchingKeyword={this.state.searchingKeyword}
  169. appContainer={this.props.appContainer}
  170. onSearchInvoked={this.search}
  171. onExcludeUsersHome={this.onExcludeUsersHome}
  172. onExcludeTrash={this.onExcludeTrash}
  173. >
  174. </SearchControl>
  175. );
  176. }
  177. render() {
  178. return (
  179. <div>
  180. <SearchPageLayout
  181. SearchControl={this.renderSearchControl}
  182. SearchResultList={this.renderSearchResultList}
  183. SearchResultContent={this.renderSearchResultContent}
  184. searchResultMeta={this.state.searchResultMeta}
  185. searchingKeyword={this.state.searchedKeyword}
  186. >
  187. </SearchPageLayout>
  188. </div>
  189. );
  190. }
  191. }
  192. /**
  193. * Wrapper component for using unstated
  194. */
  195. const SearchPageWrapper = withUnstatedContainers(SearchPage, [AppContainer]);
  196. SearchPage.propTypes = {
  197. t: PropTypes.func.isRequired, // i18next
  198. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  199. query: PropTypes.object,
  200. };
  201. SearchPage.defaultProps = {
  202. // pollInterval: 1000,
  203. query: SearchPage.getQueryByLocation(window.location || {}),
  204. };
  205. export default withTranslation()(SearchPageWrapper);