GlobalSearch.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import React, { useState, useCallback, useRef } from 'react';
  2. import assert from 'assert';
  3. import { useTranslation } from 'next-i18next';
  4. import { IFocusable } from '~/client/interfaces/focusable';
  5. import { IPageWithSearchMeta } from '~/interfaces/search';
  6. import {
  7. useCurrentPagePath, useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
  8. } from '~/stores/context';
  9. import { useGlobalSearchFormRef } from '~/stores/ui';
  10. import SearchForm from '../SearchForm';
  11. import styles from './GlobalSearch.module.scss';
  12. export type GlobalSearchProps = {
  13. dropup?: boolean,
  14. }
  15. export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
  16. const { t } = useTranslation();
  17. const { dropup } = props;
  18. const globalSearchFormRef = useRef<IFocusable>(null);
  19. useGlobalSearchFormRef(globalSearchFormRef);
  20. const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
  21. const { data: isSearchScopeChildrenAsDefault } = useIsSearchScopeChildrenAsDefault();
  22. const { data: currentPagePath } = useCurrentPagePath();
  23. const [text, setText] = useState('');
  24. const [isScopeChildren, setScopeChildren] = useState<boolean|undefined>(isSearchScopeChildrenAsDefault);
  25. const [isFocused, setFocused] = useState<boolean>(false);
  26. const gotoPage = useCallback((data: IPageWithSearchMeta[]) => {
  27. assert(data.length > 0);
  28. const page = data[0].data; // should be single page selected
  29. // navigate to page
  30. if (page != null) {
  31. window.location.href = `/${page._id}`;
  32. }
  33. }, []);
  34. const search = useCallback(() => {
  35. const url = new URL(window.location.href);
  36. url.pathname = '/_search';
  37. // construct search query
  38. let q = text;
  39. if (isScopeChildren) {
  40. q += ` prefix:${currentPagePath ?? window.location.pathname}`;
  41. }
  42. url.searchParams.append('q', q);
  43. window.location.href = url.href;
  44. }, [currentPagePath, isScopeChildren, text]);
  45. const scopeLabel = isScopeChildren
  46. ? t('header_search_box.label.This tree')
  47. : t('header_search_box.label.All pages');
  48. const isIndicatorShown = !isFocused && (text.length === 0);
  49. if (isScopeChildren == null || isSearchServiceReachable == null) {
  50. return <></>;
  51. }
  52. return (
  53. <div className={`grw-global-search ${styles['grw-global-search']} form-group mb-0 d-print-none ${isSearchServiceReachable ? '' : 'has-error'}`}>
  54. <div className="input-group flex-nowrap">
  55. <div className={`input-group-prepend ${dropup ? 'dropup' : ''}`}>
  56. <button
  57. className="btn btn-secondary dropdown-toggle py-0"
  58. type="button"
  59. data-toggle="dropdown"
  60. aria-haspopup="true"
  61. data-testid="select-search-scope"
  62. >
  63. {scopeLabel}
  64. </button>
  65. <div className="dropdown-menu">
  66. <button
  67. className="dropdown-item"
  68. type="button"
  69. onClick={() => {
  70. setScopeChildren(false);
  71. globalSearchFormRef.current?.focus();
  72. }}
  73. >
  74. { t('header_search_box.item_label.All pages') }
  75. </button>
  76. <button
  77. data-tesid="search-current-tree"
  78. className="dropdown-item"
  79. type="button"
  80. onClick={() => {
  81. setScopeChildren(true);
  82. globalSearchFormRef.current?.focus();
  83. }}
  84. >
  85. { t('header_search_box.item_label.This tree') }
  86. </button>
  87. </div>
  88. </div>
  89. <SearchForm
  90. ref={globalSearchFormRef}
  91. isSearchServiceReachable={isSearchServiceReachable}
  92. dropup={dropup}
  93. onChange={gotoPage}
  94. onBlur={() => setFocused(false)}
  95. onFocus={() => setFocused(true)}
  96. onInputChange={text => setText(text)}
  97. onSubmit={search}
  98. />
  99. { isIndicatorShown && (
  100. <span className="grw-shortcut-key-indicator">
  101. <code className="bg-transparent text-muted">/</code>
  102. </span>
  103. ) }
  104. </div>
  105. </div>
  106. );
  107. };