123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- import React, { useState, useEffect } from 'react';
- import useActions from '../actions';
- import useStore from '../store';
- import SearchComponent from '../components/Search';
- import {
- normalize,
- calculatePhraseMatch,
- convertMatches,
- } from '../helpers/pdf';
- import { scrollIntoView } from '../helpers/utility';
- type MatchType = {
- page: number;
- index: number;
- };
- const Search: React.FC = () => {
- const [queryString, setQueryString] = useState('');
- const [matchesMap, setMatchesMap] = useState<MatchType[]>([]);
- const [matchTotal, setMatchTotal] = useState(0);
- const [prevIndex, setPrevIndex] = useState(-1);
- const [currentIndex, setCurrentIndex] = useState(-1);
- const [{ navbarState, pdf, totalPage, textDivs }, dispatch] = useStore();
- const { setNavbar } = useActions(dispatch);
- const getTextContent = async (pageNum: number): Promise<any[]> => {
- const page = await pdf.getPage(pageNum);
- const textContent = await page.getTextContent({
- normalizeWhitespace: true,
- });
- return textContent.items;
- };
- const extractTextItems = async (pageNum: number): Promise<string[]> => {
- const textContent = await getTextContent(pageNum);
- const strBuf = [];
- for (let j = 0, len = textContent.length; j < len; j += 1) {
- if (textContent[j].str) {
- strBuf.push(textContent[j].str);
- }
- }
- return strBuf;
- };
- const getMatchTextIndex = async (
- pageNum: number,
- queryStr: string
- ): Promise<void> => {
- const contentItems = await extractTextItems(pageNum);
- const content = normalize(contentItems.join('').toLowerCase());
- const matches = calculatePhraseMatch(content, queryStr);
- if (matches.length) {
- matches.forEach(ele => {
- matchesMap.push({
- page: pageNum,
- index: ele,
- });
- });
- setMatchesMap(matchesMap);
- setMatchTotal(cur => cur + matches.length);
- }
- if (pageNum < totalPage) {
- await getMatchTextIndex(pageNum + 1, queryStr);
- }
- };
- const startSearchPdf = async (queryStr: string): Promise<void> => {
- getMatchTextIndex(1, queryStr);
- };
- const appendTextToDiv = async (
- pageNum: number,
- divIdx: number,
- fromOffset: number,
- toOffset: number | undefined,
- highlight: boolean
- ): Promise<any> => {
- const textContentItem = await extractTextItems(pageNum);
- if (textDivs[pageNum]) {
- const domElements = textDivs[pageNum][divIdx];
- const content = textContentItem[divIdx].substring(fromOffset, toOffset);
- const node = document.createTextNode(content);
- const span = document.createElement('span');
- if (highlight) {
- span.style.backgroundColor = 'rgba(255, 211, 0, 0.7)';
- span.appendChild(node);
- domElements.appendChild(span);
- scrollIntoView(domElements, { top: -120 });
- } else {
- domElements.textContent = '';
- domElements.appendChild(node);
- }
- }
- };
- const beginText = async (
- pageNum: number,
- begin: Record<string, any>
- ): Promise<any> => {
- const { divIdx } = begin;
- const domElements = textDivs[pageNum][divIdx];
- if (domElements) {
- domElements.textContent = '';
- appendTextToDiv(pageNum, divIdx, 0, begin.offset, false);
- }
- };
- const cleanMatches = async (
- pageNum: number,
- matchIndex: number,
- queryStr: string
- ): Promise<any> => {
- const textContentItem = await extractTextItems(pageNum);
- const { begin, end } = convertMatches(
- queryStr,
- matchIndex,
- textContentItem
- );
- for (let i = begin.divIdx; i <= end.divIdx; i += 1) {
- appendTextToDiv(pageNum, i, 0, undefined, false);
- }
- };
- const reset = (): void => {
- setMatchTotal(0);
- setCurrentIndex(-1);
- setPrevIndex(-1);
- setMatchesMap([]);
- };
- const renderMatches = async (
- pageNum: number,
- matchIndex: number,
- queryStr: string
- ): Promise<any> => {
- const textContentItem = await extractTextItems(pageNum);
- const { begin, end } = convertMatches(
- queryStr,
- matchIndex,
- textContentItem
- );
- beginText(pageNum, begin);
- if (begin.divIdx === end.divIdx) {
- appendTextToDiv(pageNum, begin.divIdx, begin.offset, end.offset, true);
- } else {
- for (let i = begin.divIdx; i <= end.divIdx; i += 1) {
- switch (i) {
- case begin.divIdx:
- appendTextToDiv(
- pageNum,
- begin.divIdx,
- begin.offset,
- undefined,
- true
- );
- break;
- case end.divIdx:
- beginText(pageNum, { divIdx: end.divIdx, offset: 0 });
- appendTextToDiv(pageNum, end.divIdx, 0, end.offset, true);
- break;
- default: {
- beginText(pageNum, { divIdx: i, offset: 0 });
- appendTextToDiv(pageNum, i, 0, undefined, true);
- break;
- }
- }
- }
- }
- };
- const handleSearch = (val: string): void => {
- if (!val) return;
- const queryStr = normalize(val.toLowerCase());
- if (queryStr !== queryString) {
- reset();
- setQueryString(queryStr);
- startSearchPdf(queryStr);
- }
- };
- const clickPrev = (): void => {
- setCurrentIndex(cur => {
- setPrevIndex(cur);
- if (cur > 0) {
- return cur - 1;
- }
- setPrevIndex(cur - 1);
- return cur;
- });
- };
- const clickNext = (): void => {
- setCurrentIndex(cur => {
- setPrevIndex(cur);
- if (cur + 1 < matchTotal) {
- return cur + 1;
- }
- return cur;
- });
- };
- const handleClose = (): void => {
- setNavbar('');
- reset();
- const currentMatches = matchesMap[currentIndex];
- if (currentMatches) {
- cleanMatches(currentMatches.page, currentMatches.index, queryString);
- }
- };
- useEffect(() => {
- if (matchTotal >= 1 && currentIndex === -1) {
- clickNext();
- }
- }, [matchTotal, currentIndex]);
- useEffect(() => {
- if (currentIndex >= 0) {
- const indexObj = matchesMap[currentIndex];
- const pageDiv: HTMLDivElement = document.getElementById(
- `page_${indexObj.page}`
- ) as HTMLDivElement;
- scrollIntoView(pageDiv);
- }
- }, [currentIndex, matchesMap]);
- useEffect(() => {
- if (currentIndex >= 0) {
- const currentMatches = matchesMap[currentIndex];
- if (textDivs[currentMatches.page]) {
- renderMatches(currentMatches.page, currentMatches.index, queryString);
- }
- if (currentIndex !== prevIndex && prevIndex >= 0) {
- const prevMatches = matchesMap[prevIndex];
- if (prevMatches) {
- cleanMatches(prevMatches.page, prevMatches.index, queryString);
- }
- }
- }
- }, [currentIndex, prevIndex, matchesMap, queryString, textDivs]);
- return (
- <SearchComponent
- matchesTotal={matchTotal}
- current={currentIndex + 1}
- onPrev={clickPrev}
- onNext={clickNext}
- onEnter={handleSearch}
- isActive={navbarState === 'search'}
- close={handleClose}
- />
- );
- };
- export default Search;
|