SearchResult.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import Page from '../PageList/Page';
  4. import SearchResultList from './SearchResultList';
  5. import DeletePageListModal from './DeletePageListModal';
  6. // Search.SearchResult
  7. export default class SearchResult extends React.Component {
  8. constructor(props) {
  9. super(props);
  10. this.state = {
  11. deletionMode : false,
  12. selectedPages : new Set(),
  13. isDeleteConfirmModalShown: false,
  14. errorMessageForDeleting: undefined,
  15. }
  16. this.deleteSelectedPages = this.deleteSelectedPages.bind(this);
  17. this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this);
  18. }
  19. isNotSearchedYet() {
  20. return !this.props.searchResultMeta.took;
  21. }
  22. isNotFound() {
  23. return this.props.searchingKeyword !== '' && this.props.pages.length === 0;
  24. }
  25. isError() {
  26. if (this.props.searchError !== null) {
  27. return true;
  28. }
  29. return false;
  30. }
  31. /**
  32. * toggle checkbox and add (or delete from) selected pages list
  33. *
  34. * @param {any} page
  35. * @memberof SearchResult
  36. */
  37. toggleCheckbox(page) {
  38. if (this.state.selectedPages.has(page)) {
  39. this.state.selectedPages.delete(page);
  40. } else {
  41. this.state.selectedPages.add(page);
  42. }
  43. this.setState({isDeleteConfirmModalShown: false});
  44. this.setState({selectedPages: this.state.selectedPages});
  45. }
  46. /**
  47. * change deletion mode
  48. *
  49. * @memberof SearchResult
  50. */
  51. handleDeletionModeChange() {
  52. this.state.selectedPages.clear();
  53. this.setState({deletionMode: !this.state.deletionMode});
  54. }
  55. /**
  56. * delete selected pages
  57. *
  58. * @memberof SearchResult
  59. */
  60. deleteSelectedPages() {
  61. let isDeleteComplete = true;
  62. Array.from(this.state.selectedPages).map((page) => {
  63. const pageId = page._id;
  64. const revisionId = page.revision._id;
  65. this.props.crowi.apiPost('/pages.remove',
  66. {page_id: pageId, revision_id: revisionId})
  67. .then(res => {
  68. if (res.ok) {
  69. this.state.selectedPages.delete(page);
  70. }
  71. else {
  72. isDeleteComplete = false;
  73. }
  74. }).catch(err => {
  75. console.log(err.message);
  76. isDeleteComplete = false;
  77. this.setState({errorMessageForDeleting: err.message});
  78. });
  79. });
  80. if ( isDeleteComplete ) {
  81. window.location.reload();
  82. }
  83. }
  84. /**
  85. * open confirm modal for page selection delete
  86. *
  87. * @memberof SearchResult
  88. */
  89. showDeleteConfirmModal() {
  90. this.setState({isDeleteConfirmModalShown: true});
  91. }
  92. /**
  93. * close confirm modal for page selection delete
  94. *
  95. * @memberof SearchResult
  96. */
  97. closeDeleteConfirmModal() {
  98. this.setState({
  99. isDeleteConfirmModalShown: false,
  100. errorMessageForDeleting: undefined,
  101. });
  102. }
  103. render() {
  104. const excludePathString = this.props.tree;
  105. //console.log(this.props.searchError);
  106. //console.log(this.isError());
  107. if (this.isError()) {
  108. return (
  109. <div className="content-main">
  110. <i className="searcing fa fa-warning"></i> Error on searching.
  111. </div>
  112. );
  113. }
  114. if (this.isNotSearchedYet()) {
  115. return <div />;
  116. }
  117. if (this.isNotFound()) {
  118. let under = '';
  119. if (this.props.tree !== '') {
  120. under = ` under "${this.props.tree}"`;
  121. }
  122. return (
  123. <div className="content-main">
  124. <i className="fa fa-meh-o" /> No page found with "{this.props.searchingKeyword}"{under}
  125. </div>
  126. );
  127. }
  128. let deletionModeButtons = '';
  129. if (this.state.deletionMode) {
  130. deletionModeButtons =
  131. <div className="btn-group">
  132. <button type="button" className="btn btn-danger btn-xs" onClick={() => this.showDeleteConfirmModal()} disabled={this.state.selectedPages.size == 0}><i className="fa fa-trash-o"/> Delete</button>
  133. <button type="button" className="btn btn-default btn-xs" onClick={() => this.handleDeletionModeChange()}><i className="fa fa-undo"/> Cancel</button>
  134. </div>
  135. }
  136. else {
  137. deletionModeButtons =
  138. <div className="btn-group">
  139. <button type="button" className="btn btn-default btn-xs" onClick={() => this.handleDeletionModeChange()}>
  140. <i className="fa fa-toggle-off"/> DeletionMode
  141. </button>
  142. </div>
  143. }
  144. const listView = this.props.pages.map((page) => {
  145. const pageId = "#" + page._id;
  146. return (
  147. <Page page={page}
  148. linkTo={pageId}
  149. key={page._id}
  150. excludePathString={excludePathString}
  151. >
  152. { this.state.deletionMode &&
  153. <input type="checkbox" className="search-result-list-delete-checkbox"
  154. value={pageId}
  155. checked={this.state.selectedPages.has(page)}
  156. onClick={() => this.toggleCheckbox(page)} />
  157. }
  158. <div className="page-list-option">
  159. <a href={page.path}><i className="fa fa-sign-in" /></a>
  160. </div>
  161. </Page>
  162. );
  163. });
  164. // TODO あとでなんとかする
  165. setTimeout(() => {
  166. $('#search-result-list > nav').affix({ offset: { top: 120 }});
  167. }, 1200);
  168. /*
  169. UI あとで考える
  170. <span className="search-result-meta">Found: {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"</span>
  171. */
  172. return (
  173. <div className="content-main">
  174. <div className="search-result row" id="search-result">
  175. <div className="col-md-4 hidden-xs hidden-sm page-list search-result-list" id="search-result-list">
  176. <nav data-spy="affix" data-offset-top="120">
  177. <div className="pull-right">{deletionModeButtons}</div>
  178. <div className="search-result-meta">
  179. <i className="fa fa-lightbulb-o" /> Found {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"
  180. </div>
  181. <div className="clearfix"></div>
  182. <ul className="page-list-ul page-list-ul-flat nav">
  183. {listView}
  184. </ul>
  185. </nav>
  186. </div>
  187. <div className="col-md-8 search-result-content" id="search-result-content">
  188. <SearchResultList
  189. pages={this.props.pages}
  190. searchingKeyword={this.props.searchingKeyword}
  191. />
  192. </div>
  193. </div>
  194. <DeletePageListModal
  195. isShown={this.state.isDeleteConfirmModalShown}
  196. pages={Array.from(this.state.selectedPages)}
  197. errorMessage={this.state.errorMessageForDeleting}
  198. cancel={this.closeDeleteConfirmModal}
  199. confirmedToDelete={this.deleteSelectedPages}
  200. />
  201. </div>//content-main
  202. );
  203. }
  204. }
  205. SearchResult.propTypes = {
  206. tree: PropTypes.string.isRequired,
  207. pages: PropTypes.array.isRequired,
  208. searchingKeyword: PropTypes.string.isRequired,
  209. searchResultMeta: PropTypes.object.isRequired,
  210. crowi: PropTypes.object.isRequired,
  211. };
  212. SearchResult.defaultProps = {
  213. tree: '',
  214. pages: [],
  215. searchingKeyword: '',
  216. searchResultMeta: {},
  217. searchError: null,
  218. };