index.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. // jshint ignore:start
  2. import React from 'react';
  3. import ajax from 'misago/services/ajax';
  4. import snackbar from 'misago/services/snackbar';
  5. import misago from 'misago';
  6. import cleanResults from './clean-results';
  7. import Dropdown from './dropdown';
  8. export default class extends React.Component {
  9. constructor() {
  10. super();
  11. this.state = {
  12. isLoading: false,
  13. isOpen: false,
  14. query: '',
  15. results: []
  16. };
  17. this.intervalId = null;
  18. }
  19. componentDidMount() {
  20. document.addEventListener('mousedown', this.onDocumentMouseDown);
  21. document.addEventListener('keydown', this.onEscape);
  22. }
  23. componentWillUnmount() {
  24. document.removeEventListener('mousedown', this.onDocumentMouseDown);
  25. document.removeEventListener('keydown', this.onEscape);
  26. }
  27. onToggle = (ev) => {
  28. this.setState((prevState, props) => {
  29. return { isOpen: !prevState.isOpen };
  30. });
  31. };
  32. onDocumentMouseDown = (ev) => {
  33. let closeResults = true;
  34. let node = ev.target;
  35. while (node !== null && node !== document) {
  36. if (node === this.container) {
  37. closeResults = false;
  38. return;
  39. }
  40. node = node.parentNode;
  41. }
  42. if (closeResults) {
  43. this.setState({ isOpen: false });
  44. }
  45. };
  46. onEscape = (ev) => {
  47. if (ev.key === 'Escape') {
  48. this.setState({ isOpen: false });
  49. }
  50. };
  51. onChange = (ev) => {
  52. const query = ev.target.value;
  53. this.setState({ query });
  54. this.loadResults(query.trim());
  55. };
  56. loadResults(query) {
  57. if (!query.length) return;
  58. const delay = 300 + (Math.random() * 300);
  59. if (this.intervalId) {
  60. window.clearTimeout(this.intervalId);
  61. }
  62. this.setState({ isLoading: true });
  63. this.intervalId = window.setTimeout(
  64. () => {
  65. ajax.get(misago.get('SEARCH_API'), {q: query}).then(
  66. (data) => {
  67. this.setState({
  68. intervalId: null,
  69. isLoading: false,
  70. results: cleanResults(data)
  71. });
  72. },
  73. (rejection) => {
  74. snackbar.apiError(rejection);
  75. this.setState({
  76. intervalId: null,
  77. isLoading: false,
  78. results: []
  79. });
  80. }
  81. );
  82. },
  83. delay
  84. );
  85. }
  86. render() {
  87. let className = "navbar-right navbar-search dropdown";
  88. if (this.state.isOpen) className += " open";
  89. return (
  90. <div className="navbar-form" ref={(container) => this.container = container}>
  91. <div className={className}>
  92. <a
  93. aria-haspopup="true"
  94. aria-expanded="false"
  95. className="btn navbar-btn dropdown-toggle"
  96. data-toggle="dropdown"
  97. href={misago.get('SEARCH_URL')}
  98. onClick={this.onToggle}
  99. >
  100. <i className="material-icon">
  101. search
  102. </i>
  103. </a>
  104. <Dropdown
  105. isLoading={this.state.isLoading}
  106. onChange={this.onChange}
  107. results={this.state.results}
  108. query={this.state.query}
  109. />
  110. </div>
  111. </div>
  112. );
  113. }
  114. }