SearchResult.js 9.1 KB

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