SearchResult.js 6.7 KB

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