SearchResult.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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. // 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. }
  14. this.toggleCheckbox = this.toggleCheckbox.bind(this);
  15. }
  16. isNotSearchedYet() {
  17. return !this.props.searchResultMeta.took;
  18. }
  19. isNotFound() {
  20. return this.props.searchingKeyword !== '' && this.props.pages.length === 0;
  21. }
  22. isError() {
  23. if (this.props.searchError !== null) {
  24. return true;
  25. }
  26. return false;
  27. }
  28. toggleCheckbox(page) {
  29. if (this.state.selectedPages.has(page)) {
  30. this.state.selectedPages.delete(page);
  31. } else {
  32. this.state.selectedPages.add(page);
  33. }
  34. this.setState({selectedPages: this.state.selectedPages});
  35. }
  36. handleDeletionModeChange() {
  37. this.setState({deletionMode: !this.state.deletionMode});
  38. }
  39. handleFormSubmit() {
  40. // delete
  41. $('#delete-pages-form').submit(function(e) {
  42. $.ajax({
  43. type: 'POST',
  44. url: '/_api/pages.remove',
  45. data: $('#delete-pages-form').serialize(),
  46. dataType: 'json'
  47. }).done(function(res) {
  48. if (!res.ok) {
  49. $('#delete-errors').html('<i class="fa fa-times-circle"></i> ' + res.error);
  50. $('#delete-errors').addClass('alert-danger');
  51. } else {
  52. var page = res.page;
  53. top.location.href = page.path;
  54. }
  55. });
  56. return false;
  57. });
  58. }
  59. render() {
  60. const excludePathString = this.props.tree;
  61. //console.log(this.props.searchError);
  62. //console.log(this.isError());
  63. if (this.isError()) {
  64. return (
  65. <div className="content-main">
  66. <i className="searcing fa fa-warning"></i> Error on searching.
  67. </div>
  68. );
  69. }
  70. if (this.isNotSearchedYet()) {
  71. return <div />;
  72. }
  73. if (this.isNotFound()) {
  74. let under = '';
  75. if (this.props.tree !== '') {
  76. under = ` under "${this.props.tree}"`;
  77. }
  78. return (
  79. <div className="content-main">
  80. <i className="fa fa-meh-o" /> No page found with "{this.props.searchingKeyword}"{under}
  81. </div>
  82. );
  83. }
  84. let deletionModeButtons = '';
  85. if (this.state.deletionMode) {
  86. deletionModeButtons =
  87. <div className="btn-group">
  88. <button type="button" className="btn btn-danger" data-target="#deletePages" data-toggle="modal"><i className="fa fa-trash-o"/> Delete</button>
  89. <button type="button" className="btn btn-default" onClick={() => this.handleDeletionModeChange()}><i className="fa fa-undo"/> Cancel</button>
  90. </div>
  91. }
  92. else {
  93. deletionModeButtons =
  94. <div className="btn-group">
  95. <button type="button" className="btn btn-default" onClick={() => this.handleDeletionModeChange()}><i className="fa fa-trash-o"/> DeletionMode</button>
  96. </div>
  97. }
  98. const listView = this.props.pages.map((page) => {
  99. const pageId = "#" + page._id;
  100. return (
  101. <Page page={page}
  102. linkTo={pageId}
  103. key={page._id}
  104. excludePathString={excludePathString}
  105. >
  106. <SearchResultInput
  107. page={page}
  108. deletionMode={this.state.deletionMode}
  109. handleCheckboxChange={this.toggleCheckbox}/>
  110. <div className="page-list-option">
  111. <a href={page.path}><i className="fa fa-arrow-circle-right" /></a>
  112. </div>
  113. </Page>
  114. );
  115. });
  116. const selectedListView = Array.from(this.state.selectedPages).map((page) => {
  117. return (
  118. <li key={page._id}>{page.path}</li>
  119. );
  120. });
  121. // TODO あとでなんとかする
  122. setTimeout(() => {
  123. $('#search-result-list > nav').affix({ offset: { top: 120 }});
  124. }, 1200);
  125. /*
  126. UI あとで考える
  127. <span className="search-result-meta">Found: {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"</span>
  128. */
  129. return (
  130. <div className="content-main">
  131. <div className="search-result row" id="search-result">
  132. <div className="col-md-4 hidden-xs hidden-sm page-list search-result-list" id="search-result-list">
  133. <nav data-spy="affix" data-offset-top="120">
  134. {deletionModeButtons}
  135. <ul className="page-list-ul page-list-ul-flat nav">
  136. {listView}
  137. </ul>
  138. </nav>
  139. </div>
  140. <div className="col-md-8 search-result-content" id="search-result-content">
  141. <div className="search-result-meta"><i className="fa fa-lightbulb-o" /> Found {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"</div>
  142. <SearchResultList
  143. pages={this.props.pages}
  144. searchingKeyword={this.props.searchingKeyword}
  145. />
  146. </div>
  147. </div>
  148. <div id="crowi-modals">
  149. <div className="modal" id="deletePages">
  150. <div className="modal-dialog">
  151. <div className="modal-content">
  152. <form role="form" id="delete-pages-form" onSubmit="return false;">
  153. <div className="modal-header">
  154. <button type="button" className="close" data-dismiss="modal" aria-hidden="true">&times;</button>
  155. <h4 className="modal-title"><i className="fa fa-trash-o"></i> Delete Pages</h4>
  156. </div>
  157. <div className="modal-body">
  158. <div className="form-group">
  159. <label htmlFor="">Deleting pages:</label>
  160. <ul>
  161. {selectedListView}
  162. </ul>
  163. </div>
  164. </div>
  165. <div className="modal-footer">
  166. <p><small className="pull-left" id="delete-errors"></small></p>
  167. <input type="hidden" name="_csrf" value="{{ csrf() }}"/>
  168. <input type="hidden" name="path" value="{{ page.path }}"/>
  169. <input type="hidden" name="page_id" value="{{ page._id.toString() }}"/>
  170. <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}"/>
  171. <button type="submit" className="btn btn-danger delete-button" onClick={this.handleFormSubmit}>Delete</button>
  172. </div>
  173. </form>
  174. </div>
  175. </div>
  176. </div>
  177. </div>
  178. </div>//content-main
  179. );
  180. }
  181. }
  182. SearchResult.propTypes = {
  183. tree: PropTypes.string.isRequired,
  184. pages: PropTypes.array.isRequired,
  185. searchingKeyword: PropTypes.string.isRequired,
  186. searchResultMeta: PropTypes.object.isRequired,
  187. };
  188. SearchResult.defaultProps = {
  189. tree: '',
  190. pages: [],
  191. searchingKeyword: '',
  192. searchResultMeta: {},
  193. searchError: null,
  194. };