Search.tsx 3.1 KB

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