SearchResult.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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. isDeleteCompletely: undefined,
  14. isDeleteConfirmModalShown: false,
  15. errorMessageForDeleting: undefined,
  16. }
  17. this.toggleDeleteCompletely = this.toggleDeleteCompletely.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({isDeleteConfirmModalShown: false});
  46. this.setState({selectedPages: this.state.selectedPages});
  47. }
  48. /**
  49. * check and return is all pages selected for delete?
  50. *
  51. * @returns all pages selected (or not)
  52. * @memberof SearchResult
  53. */
  54. isAllSelected() {
  55. return this.state.selectedPages.size == this.props.pages.length;
  56. }
  57. /**
  58. * handle checkbox clicking that all pages select for delete
  59. *
  60. * @memberof SearchResult
  61. */
  62. handleAllSelect() {
  63. if (this.isAllSelected()) {
  64. this.state.selectedPages.clear();
  65. }
  66. else {
  67. this.state.selectedPages.clear();
  68. this.props.pages.map((page) => {
  69. this.state.selectedPages.add(page);
  70. });
  71. }
  72. this.setState({selectedPages: this.state.selectedPages});
  73. }
  74. /**
  75. * change deletion mode
  76. *
  77. * @memberof SearchResult
  78. */
  79. handleDeletionModeChange() {
  80. this.state.selectedPages.clear();
  81. this.setState({deletionMode: !this.state.deletionMode});
  82. }
  83. /**
  84. * toggle check delete completely
  85. *
  86. * @memberof SearchResult
  87. */
  88. toggleDeleteCompletely() {
  89. // request で completely が undefined でないと指定アリと見なされるため
  90. this.setState({isDeleteCompletely: this.state.isDeleteCompletely? undefined : true});
  91. }
  92. /**
  93. * delete selected pages
  94. *
  95. * @memberof SearchResult
  96. */
  97. deleteSelectedPages() {
  98. let isDeleted = true;
  99. let deleteCompletely = this.state.isDeleteCompletely;
  100. Array.from(this.state.selectedPages).map((page) => {
  101. const pageId = page._id;
  102. const revisionId = page.revision._id;
  103. this.props.crowi.apiPost('/pages.remove',
  104. {page_id: pageId, revision_id: revisionId, completely: deleteCompletely})
  105. .then(res => {
  106. if (res.ok) {
  107. this.state.selectedPages.delete(page);
  108. }
  109. else {
  110. isDeleted = false;
  111. }
  112. }).catch(err => {
  113. console.log(err.message);
  114. isDeleted = false;
  115. this.setState({errorMessageForDeleting: err.message});
  116. });
  117. });
  118. if ( isDeleted ) {
  119. window.location.reload();
  120. }
  121. }
  122. /**
  123. * open confirm modal for page selection delete
  124. *
  125. * @memberof SearchResult
  126. */
  127. showDeleteConfirmModal() {
  128. this.setState({isDeleteConfirmModalShown: true});
  129. }
  130. /**
  131. * close confirm modal for page selection delete
  132. *
  133. * @memberof SearchResult
  134. */
  135. closeDeleteConfirmModal() {
  136. this.setState({
  137. isDeleteConfirmModalShown: false,
  138. errorMessageForDeleting: undefined,
  139. });
  140. }
  141. render() {
  142. const excludePathString = this.props.tree;
  143. //console.log(this.props.searchError);
  144. //console.log(this.isError());
  145. if (this.isError()) {
  146. return (
  147. <div className="content-main">
  148. <i className="searcing fa fa-warning"></i> Error on searching.
  149. </div>
  150. );
  151. }
  152. if (this.isNotSearchedYet()) {
  153. return <div />;
  154. }
  155. if (this.isNotFound()) {
  156. let under = '';
  157. if (this.props.tree !== '') {
  158. under = ` under "${this.props.tree}"`;
  159. }
  160. return (
  161. <div className="content-main">
  162. <i className="fa fa-meh-o" /> No page found with "{this.props.searchingKeyword}"{under}
  163. </div>
  164. );
  165. }
  166. let deletionModeButtons = '';
  167. let allSelectCheck = '';
  168. if (this.state.deletionMode) {
  169. deletionModeButtons =
  170. <div className="btn-group">
  171. <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>
  172. <button type="button" className="btn btn-default btn-xs" onClick={() => this.handleDeletionModeChange()}><i className="fa fa-undo"/> Cancel</button>
  173. </div>
  174. allSelectCheck =
  175. <div>
  176. <label>
  177. <input
  178. type="checkbox"
  179. onClick={() => this.handleAllSelect()}
  180. checked={this.isAllSelected()} />
  181. &nbsp;Check All
  182. </label>
  183. </div>
  184. }
  185. else {
  186. deletionModeButtons =
  187. <div className="btn-group">
  188. <button type="button" className="btn btn-default btn-xs" onClick={() => this.handleDeletionModeChange()}>
  189. <i className="fa fa-toggle-off"/> DeletionMode
  190. </button>
  191. </div>
  192. }
  193. const listView = this.props.pages.map((page) => {
  194. const pageId = "#" + page._id;
  195. return (
  196. <Page page={page}
  197. linkTo={pageId}
  198. key={page._id}
  199. excludePathString={excludePathString}
  200. >
  201. { this.state.deletionMode &&
  202. <input type="checkbox" className="search-result-list-delete-checkbox"
  203. value={pageId}
  204. checked={this.state.selectedPages.has(page)}
  205. onClick={() => this.toggleCheckbox(page)} />
  206. }
  207. <div className="page-list-option">
  208. <a href={page.path}><i className="fa fa-sign-in" /></a>
  209. </div>
  210. </Page>
  211. );
  212. });
  213. // TODO あとでなんとかする
  214. setTimeout(() => {
  215. $('#search-result-list > nav').affix({ offset: { top: 120 }});
  216. }, 1200);
  217. /*
  218. UI あとで考える
  219. <span className="search-result-meta">Found: {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"</span>
  220. */
  221. return (
  222. <div className="content-main">
  223. <div className="search-result row" id="search-result">
  224. <div className="col-md-4 hidden-xs hidden-sm page-list search-result-list" id="search-result-list">
  225. <nav data-spy="affix" data-offset-top="120">
  226. <div className="pull-right">
  227. {deletionModeButtons}
  228. {allSelectCheck}
  229. </div>
  230. <div className="search-result-meta">
  231. <i className="fa fa-lightbulb-o" /> Found {this.props.searchResultMeta.total} pages with "{this.props.searchingKeyword}"
  232. </div>
  233. <div className="clearfix"></div>
  234. <ul className="page-list-ul page-list-ul-flat nav">
  235. {listView}
  236. </ul>
  237. </nav>
  238. </div>
  239. <div className="col-md-8 search-result-content" id="search-result-content">
  240. <SearchResultList
  241. pages={this.props.pages}
  242. searchingKeyword={this.props.searchingKeyword}
  243. />
  244. </div>
  245. </div>
  246. <DeletePageListModal
  247. isShown={this.state.isDeleteConfirmModalShown}
  248. pages={Array.from(this.state.selectedPages)}
  249. errorMessage={this.state.errorMessageForDeleting}
  250. cancel={this.closeDeleteConfirmModal}
  251. confirmedToDelete={this.deleteSelectedPages}
  252. toggleDeleteCompletely={this.toggleDeleteCompletely}
  253. />
  254. </div>//content-main
  255. );
  256. }
  257. }
  258. SearchResult.propTypes = {
  259. tree: PropTypes.string.isRequired,
  260. pages: PropTypes.array.isRequired,
  261. searchingKeyword: PropTypes.string.isRequired,
  262. searchResultMeta: PropTypes.object.isRequired,
  263. crowi: PropTypes.object.isRequired,
  264. };
  265. SearchResult.defaultProps = {
  266. tree: '',
  267. pages: [],
  268. searchingKeyword: '',
  269. searchResultMeta: {},
  270. searchError: null,
  271. };