Convert the find controller to ES6 syntax

Moreover, rename `FindStates` to `FindState` since enumeration names are
usually not in plural, for readability and consistency with the ones in
`src/shared/util.js`.
This commit is contained in:
Tim van der Meij 2017-06-04 20:35:21 +02:00
parent 8654635b0b
commit 9a95d91b92
No known key found for this signature in database
GPG Key ID: 8C3FD2925A5F2762
2 changed files with 400 additions and 404 deletions

View File

@ -13,7 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { FindStates } from './pdf_find_controller'; import { FindState } from './pdf_find_controller';
import { NullL10n } from './ui_utils'; import { NullL10n } from './ui_utils';
/** /**
@ -108,19 +108,19 @@ class PDFFindBar {
var status = ''; var status = '';
switch (state) { switch (state) {
case FindStates.FIND_FOUND: case FindState.FOUND:
break; break;
case FindStates.FIND_PENDING: case FindState.PENDING:
status = 'pending'; status = 'pending';
break; break;
case FindStates.FIND_NOTFOUND: case FindState.NOT_FOUND:
findMsg = this.l10n.get('find_not_found', null, 'Phrase not found'); findMsg = this.l10n.get('find_not_found', null, 'Phrase not found');
notFound = true; notFound = true;
break; break;
case FindStates.FIND_WRAPPED: case FindState.WRAPPED:
if (previous) { if (previous) {
findMsg = this.l10n.get('find_reached_top', null, findMsg = this.l10n.get('find_reached_top', null,
'Reached top of document, continued from bottom'); 'Reached top of document, continued from bottom');

View File

@ -16,17 +16,18 @@
import { createPromiseCapability } from 'pdfjs-lib'; import { createPromiseCapability } from 'pdfjs-lib';
import { scrollIntoView } from './ui_utils'; import { scrollIntoView } from './ui_utils';
var FindStates = { const FindState = {
FIND_FOUND: 0, FOUND: 0,
FIND_NOTFOUND: 1, NOT_FOUND: 1,
FIND_WRAPPED: 2, WRAPPED: 2,
FIND_PENDING: 3, PENDING: 3,
}; };
var FIND_SCROLL_OFFSET_TOP = -50; const FIND_SCROLL_OFFSET_TOP = -50;
var FIND_SCROLL_OFFSET_LEFT = -400; const FIND_SCROLL_OFFSET_LEFT = -400;
const FIND_TIMEOUT = 250; // ms
var CHARACTERS_TO_NORMALIZE = { const CHARACTERS_TO_NORMALIZE = {
'\u2018': '\'', // Left single quotation mark '\u2018': '\'', // Left single quotation mark
'\u2019': '\'', // Right single quotation mark '\u2019': '\'', // Right single quotation mark
'\u201A': '\'', // Single low-9 quotation mark '\u201A': '\'', // Single low-9 quotation mark
@ -41,12 +42,11 @@ var CHARACTERS_TO_NORMALIZE = {
}; };
/** /**
* Provides "search" or "find" functionality for the PDF. * Provides search functionality to find a given string in a PDF document.
* This object actually performs the search for a given string.
*/ */
var PDFFindController = (function PDFFindControllerClosure() { class PDFFindController {
function PDFFindController(options) { constructor({ pdfViewer, }) {
this.pdfViewer = options.pdfViewer || null; this.pdfViewer = pdfViewer;
this.onUpdateResultsCount = null; this.onUpdateResultsCount = null;
this.onUpdateState = null; this.onUpdateState = null;
@ -54,12 +54,11 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.reset(); this.reset();
// Compile the regular expression for text normalization once. // Compile the regular expression for text normalization once.
var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(''); let replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
this.normalizationRegex = new RegExp('[' + replace + ']', 'g'); this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
} }
PDFFindController.prototype = { reset() {
reset: function PDFFindController_reset() {
this.startedTextExtraction = false; this.startedTextExtraction = false;
this.extractTextPromises = []; this.extractTextPromises = [];
this.pendingFindMatches = Object.create(null); this.pendingFindMatches = Object.create(null);
@ -85,36 +84,35 @@ var PDFFindController = (function PDFFindControllerClosure() {
this._firstPagePromise = new Promise((resolve) => { this._firstPagePromise = new Promise((resolve) => {
this.resolveFirstPage = resolve; this.resolveFirstPage = resolve;
}); });
}, }
normalize: function PDFFindController_normalize(text) { normalize(text) {
return text.replace(this.normalizationRegex, function (ch) { return text.replace(this.normalizationRegex, function (ch) {
return CHARACTERS_TO_NORMALIZE[ch]; return CHARACTERS_TO_NORMALIZE[ch];
}); });
}, }
// Helper for multiple search - fills matchesWithLength array
// and takes into account cases when one search term
// include another search term (for example, "tamed tame" or "this is").
// Looking for intersecting terms in the 'matches' and
// leave elements with a longer match-length.
_prepareMatches: function PDFFindController_prepareMatches(
matchesWithLength, matches, matchesLength) {
/**
* Helper for multi-term search that fills the `matchesWithLength` array
* and handles cases where one search term includes another search term (for
* example, "tamed tame" or "this is"). It looks for intersecting terms in
* the `matches` and keeps elements with a longer match length.
*/
_prepareMatches(matchesWithLength, matches, matchesLength) {
function isSubTerm(matchesWithLength, currentIndex) { function isSubTerm(matchesWithLength, currentIndex) {
var currentElem, prevElem, nextElem; let currentElem = matchesWithLength[currentIndex];
currentElem = matchesWithLength[currentIndex]; let nextElem = matchesWithLength[currentIndex + 1];
nextElem = matchesWithLength[currentIndex + 1];
// checking for cases like "TAMEd TAME" // Check for cases like "TAMEd TAME".
if (currentIndex < matchesWithLength.length - 1 && if (currentIndex < matchesWithLength.length - 1 &&
currentElem.match === nextElem.match) { currentElem.match === nextElem.match) {
currentElem.skipped = true; currentElem.skipped = true;
return true; return true;
} }
// checking for cases like "thIS IS"
for (var i = currentIndex - 1; i >= 0; i--) { // Check for cases like "thIS IS".
prevElem = matchesWithLength[i]; for (let i = currentIndex - 1; i >= 0; i--) {
let prevElem = matchesWithLength[i];
if (prevElem.skipped) { if (prevElem.skipped) {
continue; continue;
} }
@ -130,27 +128,25 @@ var PDFFindController = (function PDFFindControllerClosure() {
return false; return false;
} }
var i, len; // Sort the array of `{ match: <match>, matchLength: <matchLength> }`
// Sorting array of objects { match: <match>, matchLength: <matchLength> } // objects on increasing index first and on the length otherwise.
// in increasing index first and then the lengths.
matchesWithLength.sort(function(a, b) { matchesWithLength.sort(function(a, b) {
return a.match === b.match ? return a.match === b.match ? a.matchLength - b.matchLength :
a.matchLength - b.matchLength : a.match - b.match; a.match - b.match;
}); });
for (i = 0, len = matchesWithLength.length; i < len; i++) { for (let i = 0, len = matchesWithLength.length; i < len; i++) {
if (isSubTerm(matchesWithLength, i)) { if (isSubTerm(matchesWithLength, i)) {
continue; continue;
} }
matches.push(matchesWithLength[i].match); matches.push(matchesWithLength[i].match);
matchesLength.push(matchesWithLength[i].matchLength); matchesLength.push(matchesWithLength[i].matchLength);
} }
}, }
calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch( calcFindPhraseMatch(query, pageIndex, pageContent) {
query, pageIndex, pageContent) { let matches = [];
var matches = []; let queryLen = query.length;
var queryLen = query.length; let matchIdx = -queryLen;
var matchIdx = -queryLen;
while (true) { while (true) {
matchIdx = pageContent.indexOf(query, matchIdx + queryLen); matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
if (matchIdx === -1) { if (matchIdx === -1) {
@ -159,18 +155,16 @@ var PDFFindController = (function PDFFindControllerClosure() {
matches.push(matchIdx); matches.push(matchIdx);
} }
this.pageMatches[pageIndex] = matches; this.pageMatches[pageIndex] = matches;
}, }
calcFindWordMatch: function PDFFindController_calcFindWordMatch( calcFindWordMatch(query, pageIndex, pageContent) {
query, pageIndex, pageContent) { let matchesWithLength = [];
var matchesWithLength = []; // Divide the query into pieces and search for text in each piece.
// Divide the query into pieces and search for text on each piece. let queryArray = query.match(/\S+/g);
var queryArray = query.match(/\S+/g); for (let i = 0, len = queryArray.length; i < len; i++) {
var subquery, subqueryLen, matchIdx; let subquery = queryArray[i];
for (var i = 0, len = queryArray.length; i < len; i++) { let subqueryLen = subquery.length;
subquery = queryArray[i]; let matchIdx = -subqueryLen;
subqueryLen = subquery.length;
matchIdx = -subqueryLen;
while (true) { while (true) {
matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen); matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen);
if (matchIdx === -1) { if (matchIdx === -1) {
@ -184,24 +178,26 @@ var PDFFindController = (function PDFFindControllerClosure() {
}); });
} }
} }
// Prepare arrays for store the matches.
// Prepare arrays for storing the matches.
if (!this.pageMatchesLength) { if (!this.pageMatchesLength) {
this.pageMatchesLength = []; this.pageMatchesLength = [];
} }
this.pageMatchesLength[pageIndex] = []; this.pageMatchesLength[pageIndex] = [];
this.pageMatches[pageIndex] = []; this.pageMatches[pageIndex] = [];
// Sort matchesWithLength, clean up intersecting terms
// and put the result into the two arrays. // Sort `matchesWithLength`, remove intersecting terms and put the result
// into the two arrays.
this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex],
this.pageMatchesLength[pageIndex]); this.pageMatchesLength[pageIndex]);
}, }
calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) { calcFindMatch(pageIndex) {
var pageContent = this.normalize(this.pageContents[pageIndex]); let pageContent = this.normalize(this.pageContents[pageIndex]);
var query = this.normalize(this.state.query); let query = this.normalize(this.state.query);
var caseSensitive = this.state.caseSensitive; let caseSensitive = this.state.caseSensitive;
var phraseSearch = this.state.phraseSearch; let phraseSearch = this.state.phraseSearch;
var queryLen = query.length; let queryLen = query.length;
if (queryLen === 0) { if (queryLen === 0) {
// Do nothing: the matches should be wiped out already. // Do nothing: the matches should be wiped out already.
@ -225,12 +221,12 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.nextPageMatch(); this.nextPageMatch();
} }
// Update the matches count // Update the match count.
if (this.pageMatches[pageIndex].length > 0) { if (this.pageMatches[pageIndex].length > 0) {
this.matchCount += this.pageMatches[pageIndex].length; this.matchCount += this.pageMatches[pageIndex].length;
this.updateUIResultsCount(); this.updateUIResultsCount();
} }
}, }
extractText() { extractText() {
if (this.startedTextExtraction) { if (this.startedTextExtraction) {
@ -258,29 +254,30 @@ var PDFFindController = (function PDFFindControllerClosure() {
}); });
}); });
} }
}, }
executeCommand: function PDFFindController_executeCommand(cmd, state) { executeCommand(cmd, state) {
if (this.state === null || cmd !== 'findagain') { if (this.state === null || cmd !== 'findagain') {
this.dirtyMatch = true; this.dirtyMatch = true;
} }
this.state = state; this.state = state;
this.updateUIState(FindStates.FIND_PENDING); this.updateUIState(FindState.PENDING);
this._firstPagePromise.then(() => { this._firstPagePromise.then(() => {
this.extractText(); this.extractText();
clearTimeout(this.findTimeout); clearTimeout(this.findTimeout);
if (cmd === 'find') { if (cmd === 'find') {
// Only trigger the find action after 250ms of silence. // Trigger the find action with a small delay to avoid starting the
this.findTimeout = setTimeout(this.nextMatch.bind(this), 250); // search when the user is still typing (saving resources).
this.findTimeout = setTimeout(this.nextMatch.bind(this), FIND_TIMEOUT);
} else { } else {
this.nextMatch(); this.nextMatch();
} }
}); });
}, }
updatePage: function PDFFindController_updatePage(index) { updatePage(index) {
if (this.selected.pageIdx === index) { if (this.selected.pageIdx === index) {
// If the page is selected, scroll the page into view, which triggers // If the page is selected, scroll the page into view, which triggers
// rendering the page, which adds the textLayer. Once the textLayer is // rendering the page, which adds the textLayer. Once the textLayer is
@ -288,16 +285,16 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.pdfViewer.currentPageNumber = index + 1; this.pdfViewer.currentPageNumber = index + 1;
} }
var page = this.pdfViewer.getPageView(index); let page = this.pdfViewer.getPageView(index);
if (page.textLayer) { if (page.textLayer) {
page.textLayer.updateMatches(); page.textLayer.updateMatches();
} }
}, }
nextMatch: function PDFFindController_nextMatch() { nextMatch() {
var previous = this.state.findPrevious; let previous = this.state.findPrevious;
var currentPageIndex = this.pdfViewer.currentPageNumber - 1; let currentPageIndex = this.pdfViewer.currentPageNumber - 1;
var numPages = this.pdfViewer.pagesCount; let numPages = this.pdfViewer.pagesCount;
this.active = true; this.active = true;
@ -314,10 +311,10 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.pageMatchesLength = null; this.pageMatchesLength = null;
for (let i = 0; i < numPages; i++) { for (let i = 0; i < numPages; i++) {
// Wipe out any previous highlighted matches. // Wipe out any previously highlighted matches.
this.updatePage(i); this.updatePage(i);
// As soon as the text is extracted start finding the matches. // Start finding the matches as soon as the text is extracted.
if (!(i in this.pendingFindMatches)) { if (!(i in this.pendingFindMatches)) {
this.pendingFindMatches[i] = true; this.pendingFindMatches[i] = true;
this.extractTextPromises[i].then((pageIdx) => { this.extractTextPromises[i].then((pageIdx) => {
@ -330,7 +327,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
// If there's no query there's no point in searching. // If there's no query there's no point in searching.
if (this.state.query === '') { if (this.state.query === '') {
this.updateUIState(FindStates.FIND_FOUND); this.updateUIState(FindState.FOUND);
return; return;
} }
@ -339,13 +336,13 @@ var PDFFindController = (function PDFFindControllerClosure() {
return; return;
} }
var offset = this.offset; let offset = this.offset;
// Keep track of how many pages we should maximally iterate through. // Keep track of how many pages we should maximally iterate through.
this.pagesToSearch = numPages; this.pagesToSearch = numPages;
// If there's already a matchIdx that means we are iterating through a // If there's already a `matchIdx` that means we are iterating through a
// page's matches. // page's matches.
if (offset.matchIdx !== null) { if (offset.matchIdx !== null) {
var numPageMatches = this.pageMatches[offset.pageIdx].length; let numPageMatches = this.pageMatches[offset.pageIdx].length;
if ((!previous && offset.matchIdx + 1 < numPageMatches) || if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
(previous && offset.matchIdx > 0)) { (previous && offset.matchIdx > 0)) {
// The simple case; we just have advance the matchIdx to select // The simple case; we just have advance the matchIdx to select
@ -362,15 +359,15 @@ var PDFFindController = (function PDFFindControllerClosure() {
} }
// Start searching through the page. // Start searching through the page.
this.nextPageMatch(); this.nextPageMatch();
}, }
matchesReady: function PDFFindController_matchesReady(matches) { matchesReady(matches) {
var offset = this.offset; let offset = this.offset;
var numMatches = matches.length; let numMatches = matches.length;
var previous = this.state.findPrevious; let previous = this.state.findPrevious;
if (numMatches) { if (numMatches) {
// There were matches for the page, so initialize the matchIdx. // There were matches for the page, so initialize `matchIdx`.
this.hadMatch = true; this.hadMatch = true;
offset.matchIdx = (previous ? numMatches - 1 : 0); offset.matchIdx = (previous ? numMatches - 1 : 0);
this.updateMatch(true); this.updateMatch(true);
@ -383,55 +380,56 @@ var PDFFindController = (function PDFFindControllerClosure() {
if (this.pagesToSearch < 0) { if (this.pagesToSearch < 0) {
// No point in wrapping again, there were no matches. // No point in wrapping again, there were no matches.
this.updateMatch(false); this.updateMatch(false);
// while matches were not found, searching for a page // While matches were not found, searching for a page
// with matches should nevertheless halt. // with matches should nevertheless halt.
return true; return true;
} }
} }
// Matches were not found (and searching is not done). // Matches were not found (and searching is not done).
return false; return false;
}, }
/** /**
* The method is called back from the text layer when match presentation * Called from the text layer when match presentation is updated.
* is updated. *
* @param {number} pageIndex - page index. * @param {number} pageIndex - The index of the page.
* @param {number} index - match index. * @param {number} matchIndex - The index of the match.
* @param {Array} elements - text layer div elements array. * @param {Array} elements - Text layer `div` elements.
* @param {number} beginIdx - start index of the div array for the match. * @param {number} beginIdx - Start index of the `div` array for the match.
*/ */
updateMatchPosition: function PDFFindController_updateMatchPosition( updateMatchPosition(pageIndex, matchIndex, elements, beginIdx) {
pageIndex, index, elements, beginIdx) { if (this.selected.matchIdx === matchIndex &&
if (this.selected.matchIdx === index &&
this.selected.pageIdx === pageIndex) { this.selected.pageIdx === pageIndex) {
var spot = { let spot = {
top: FIND_SCROLL_OFFSET_TOP, top: FIND_SCROLL_OFFSET_TOP,
left: FIND_SCROLL_OFFSET_LEFT, left: FIND_SCROLL_OFFSET_LEFT,
}; };
scrollIntoView(elements[beginIdx], spot, scrollIntoView(elements[beginIdx], spot,
/* skipOverflowHiddenElements = */ true); /* skipOverflowHiddenElements = */ true);
} }
}, }
nextPageMatch: function PDFFindController_nextPageMatch() { nextPageMatch() {
if (this.resumePageIdx !== null) { if (this.resumePageIdx !== null) {
console.error('There can only be one pending page.'); console.error('There can only be one pending page.');
} }
let matches = null;
do { do {
var pageIdx = this.offset.pageIdx; let pageIdx = this.offset.pageIdx;
var matches = this.pageMatches[pageIdx]; matches = this.pageMatches[pageIdx];
if (!matches) { if (!matches) {
// The matches don't exist yet for processing by "matchesReady", // The matches don't exist yet for processing by `matchesReady`,
// so set a resume point for when they do exist. // so set a resume point for when they do exist.
this.resumePageIdx = pageIdx; this.resumePageIdx = pageIdx;
break; break;
} }
} while (!this.matchesReady(matches)); } while (!this.matchesReady(matches));
}, }
advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) { advanceOffsetPage(previous) {
var offset = this.offset; let offset = this.offset;
var numPages = this.extractTextPromises.length; let numPages = this.extractTextPromises.length;
offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1); offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1);
offset.matchIdx = null; offset.matchIdx = null;
@ -441,18 +439,19 @@ var PDFFindController = (function PDFFindControllerClosure() {
offset.pageIdx = (previous ? numPages - 1 : 0); offset.pageIdx = (previous ? numPages - 1 : 0);
offset.wrapped = true; offset.wrapped = true;
} }
}, }
updateMatch: function PDFFindController_updateMatch(found) { updateMatch(found = false) {
var state = FindStates.FIND_NOTFOUND; let state = FindState.NOT_FOUND;
var wrapped = this.offset.wrapped; let wrapped = this.offset.wrapped;
this.offset.wrapped = false; this.offset.wrapped = false;
if (found) { if (found) {
var previousPage = this.selected.pageIdx; let previousPage = this.selected.pageIdx;
this.selected.pageIdx = this.offset.pageIdx; this.selected.pageIdx = this.offset.pageIdx;
this.selected.matchIdx = this.offset.matchIdx; this.selected.matchIdx = this.offset.matchIdx;
state = (wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND); state = (wrapped ? FindState.WRAPPED : FindState.FOUND);
// Update the currently selected page to wipe out any selected matches. // Update the currently selected page to wipe out any selected matches.
if (previousPage !== -1 && previousPage !== this.selected.pageIdx) { if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
this.updatePage(previousPage); this.updatePage(previousPage);
@ -463,25 +462,22 @@ var PDFFindController = (function PDFFindControllerClosure() {
if (this.selected.pageIdx !== -1) { if (this.selected.pageIdx !== -1) {
this.updatePage(this.selected.pageIdx); this.updatePage(this.selected.pageIdx);
} }
}, }
updateUIResultsCount: updateUIResultsCount() {
function PDFFindController_updateUIResultsCount() {
if (this.onUpdateResultsCount) { if (this.onUpdateResultsCount) {
this.onUpdateResultsCount(this.matchCount); this.onUpdateResultsCount(this.matchCount);
} }
}, }
updateUIState: function PDFFindController_updateUIState(state, previous) { updateUIState(state, previous) {
if (this.onUpdateState) { if (this.onUpdateState) {
this.onUpdateState(state, previous, this.matchCount); this.onUpdateState(state, previous, this.matchCount);
} }
}, }
}; }
return PDFFindController;
})();
export { export {
FindStates, FindState,
PDFFindController, PDFFindController,
}; };