SearchTop.jsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import { createSubscribedElement } from '../UnstatedUtils';
  5. import AppContainer from '../../services/AppContainer';
  6. import SearchForm from '../SearchForm';
  7. class SearchTop extends React.Component {
  8. constructor(props) {
  9. super(props);
  10. this.state = {
  11. text: '',
  12. isScopeChildren: false,
  13. isCollapsed: true,
  14. };
  15. this.onInputChange = this.onInputChange.bind(this);
  16. this.onClickAllPages = this.onClickAllPages.bind(this);
  17. this.onClickChildren = this.onClickChildren.bind(this);
  18. this.search = this.search.bind(this);
  19. }
  20. componentWillMount() {
  21. this.initBreakpointEvents();
  22. }
  23. initBreakpointEvents() {
  24. this.props.appContainer.addBreakpointListener('md', (mql) => {
  25. this.setState({ isCollapsed: !mql.matches });
  26. }, true);
  27. }
  28. onInputChange(text) {
  29. this.setState({ text });
  30. }
  31. onClickAllPages() {
  32. this.setState({ isScopeChildren: false });
  33. }
  34. onClickChildren() {
  35. this.setState({ isScopeChildren: true });
  36. }
  37. search() {
  38. const url = new URL(window.location.href);
  39. url.pathname = '/_search';
  40. // construct search query
  41. let q = this.state.text;
  42. if (this.state.isScopeChildren) {
  43. q += ` prefix:${window.location.pathname}`;
  44. }
  45. url.searchParams.append('q', q);
  46. window.location.href = url.href;
  47. }
  48. Root = ({ children }) => {
  49. const { isCollapsed } = this.state;
  50. return isCollapsed
  51. ? (
  52. <div id="grw-search-top-collapse" className="collapse bg-dark p-3">
  53. {children}
  54. </div>
  55. )
  56. : (
  57. <div className="grw-search-top-fixed position-fixed">
  58. {children}
  59. </div>
  60. );
  61. };
  62. SearchTopForm = () => {
  63. const { t, appContainer } = this.props;
  64. const scopeLabel = this.state.isScopeChildren
  65. ? t('header_search_box.label.This tree')
  66. : t('header_search_box.label.All pages');
  67. const config = appContainer.getConfig();
  68. const isReachable = config.isSearchServiceReachable;
  69. return (
  70. <div className={`form-group mb-0 ${isReachable ? '' : 'has-error'}`}>
  71. <div className="input-group flex-nowrap">
  72. <div className="input-group-prepend">
  73. <button className="btn btn-secondary dropdown-toggle py-0" type="button" data-toggle="dropdown" aria-haspopup="true">
  74. {scopeLabel}
  75. </button>
  76. <div className="dropdown-menu">
  77. <button className="dropdown-item" type="button" onClick={this.onClickAllPages}>{ t('header_search_box.item_label.All pages') }</button>
  78. <button className="dropdown-item" type="button" onClick={this.onClickChildren}>{ t('header_search_box.item_label.This tree') }</button>
  79. </div>
  80. </div>
  81. <SearchForm
  82. t={this.props.t}
  83. crowi={this.props.appContainer}
  84. onInputChange={this.onInputChange}
  85. onSubmit={this.search}
  86. placeholder="Search ..."
  87. />
  88. <div className="btn-group-submit-search">
  89. <span className="btn-link text-decoration-none" onClick={this.search}>
  90. <i className="icon-magnifier"></i>
  91. </span>
  92. </div>
  93. </div>
  94. </div>
  95. );
  96. }
  97. render() {
  98. const { Root, SearchTopForm } = this;
  99. return (
  100. <Root><SearchTopForm /></Root>
  101. );
  102. }
  103. }
  104. SearchTop.propTypes = {
  105. t: PropTypes.func.isRequired, // i18next
  106. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  107. };
  108. /**
  109. * Wrapper component for using unstated
  110. */
  111. const SearchTopWrapper = (props) => {
  112. return createSubscribedElement(SearchTop, props, [AppContainer]);
  113. };
  114. export default withTranslation()(SearchTopWrapper);