Search.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import React, { useState, useEffect } from 'react';
  2. import useActions from '../actions';
  3. import useStore from '../store';
  4. import SearchComponent from '../components/Search';
  5. import { normalize, calcFindPhraseMatch } from '../helpers/pdf';
  6. import { scrollIntoView } from '../helpers/utility';
  7. type MatchType = {
  8. page: number;
  9. index: number;
  10. }
  11. const Search: React.FC = () => {
  12. let queryString = '';
  13. const [matchesMap, setMatchesMap] = useState<MatchType[]>([]);
  14. const [matchTotal, setMatchTotal] = useState(0);
  15. const [currentIndex, setCurrentIndex] = useState(-1);
  16. const [{
  17. navbarState,
  18. pdf,
  19. totalPage,
  20. }, dispatch] = useStore();
  21. const { setNavbar } = useActions(dispatch);
  22. const extractTextItems = async (pageNum: number): Promise<string[]> => {
  23. const page = await pdf.getPage(pageNum);
  24. const textContent = await page.getTextContent({
  25. normalizeWhitespace: true,
  26. });
  27. const { items } = textContent;
  28. const strBuf = [];
  29. for (let j = 0, len = items.length; j < len; j += 1) {
  30. if (items[j].str.match(/[^\s]/)) {
  31. strBuf.push(items[j].str);
  32. }
  33. }
  34. return strBuf;
  35. };
  36. const getMatchTextIndex = async (pageNum: number): Promise<void> => {
  37. const contentItems = await extractTextItems(pageNum);
  38. const content = normalize(contentItems.join('').toLowerCase());
  39. const matches = calcFindPhraseMatch(content, queryString);
  40. if (matches.length) {
  41. matches.forEach((ele) => {
  42. matchesMap.push({
  43. page: pageNum,
  44. index: ele,
  45. });
  46. });
  47. setMatchesMap(matchesMap);
  48. setMatchTotal(cur => cur + matches.length);
  49. }
  50. if (pageNum < totalPage) {
  51. getMatchTextIndex(pageNum + 1);
  52. }
  53. };
  54. const searchPdfPages = async (): Promise<void> => {
  55. getMatchTextIndex(1);
  56. };
  57. const cleanMatch = (): void => {
  58. setMatchTotal(0);
  59. setCurrentIndex(-1);
  60. setMatchesMap([]);
  61. };
  62. const handleSearch = (val: string): void => {
  63. cleanMatch();
  64. if (val) {
  65. queryString = val.toLowerCase();
  66. searchPdfPages();
  67. }
  68. };
  69. const clickPrev = (): void => {
  70. setCurrentIndex((cur) => {
  71. if (cur > 0) {
  72. return cur - 1;
  73. }
  74. return cur;
  75. });
  76. };
  77. const clickNext = (): void => {
  78. setCurrentIndex((cur) => {
  79. if (cur + 1 < matchTotal) {
  80. return cur + 1;
  81. }
  82. return cur;
  83. });
  84. };
  85. const handleClose = (): void => {
  86. setNavbar('');
  87. cleanMatch();
  88. };
  89. useEffect(() => {
  90. if (matchTotal === 1) {
  91. clickNext();
  92. }
  93. }, [matchTotal]);
  94. useEffect(() => {
  95. if (currentIndex >= 0) {
  96. const indexObj = matchesMap[currentIndex];
  97. const pageDiv: HTMLDivElement = document.getElementById(`page_${indexObj.page}`) as HTMLDivElement;
  98. scrollIntoView(pageDiv);
  99. }
  100. }, [currentIndex, matchesMap]);
  101. return (
  102. <SearchComponent
  103. matchesTotal={matchTotal}
  104. current={currentIndex + 1}
  105. onPrev={clickPrev}
  106. onNext={clickNext}
  107. onEnter={handleSearch}
  108. isActive={navbarState === 'search'}
  109. close={handleClose}
  110. />
  111. );
  112. };
  113. export default Search;