GlobalSearch.tsx 4.1 KB

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