Merge pull request #16247 from Snuffleupagus/issue-7442
[api-minor] Add support, in `PDFFindController`, for mixing phrase/word searches (issue 7442)
This commit is contained in:
commit
f46ed43b81
@ -95,7 +95,6 @@ function testSearch({
|
||||
query: null,
|
||||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
phraseSearch: true,
|
||||
findPrevious: false,
|
||||
matchDiacritics: false,
|
||||
},
|
||||
@ -182,7 +181,6 @@ function testEmptySearch({ eventBus, pdfFindController, state }) {
|
||||
query: null,
|
||||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
phraseSearch: true,
|
||||
findPrevious: false,
|
||||
matchDiacritics: false,
|
||||
},
|
||||
@ -321,8 +319,7 @@ describe("pdf_find_controller", function () {
|
||||
eventBus,
|
||||
pdfFindController,
|
||||
state: {
|
||||
query: "alternate solution",
|
||||
phraseSearch: false,
|
||||
query: ["alternate", "solution"],
|
||||
},
|
||||
matchesPerPage: [0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0],
|
||||
selectedMatch: {
|
||||
@ -332,6 +329,25 @@ describe("pdf_find_controller", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("performs a multiple term (phrase) search", async function () {
|
||||
// Page 9 contains 'alternate solution' and pages 6 and 9 contain
|
||||
// 'solution'. Both should be found for multiple term (phrase) search.
|
||||
const { eventBus, pdfFindController } = await initPdfFindController();
|
||||
|
||||
await testSearch({
|
||||
eventBus,
|
||||
pdfFindController,
|
||||
state: {
|
||||
query: ["alternate solution", "solution"],
|
||||
},
|
||||
matchesPerPage: [0, 0, 0, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0],
|
||||
selectedMatch: {
|
||||
pageIndex: 5,
|
||||
matchIndex: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("performs a normal search, where the text is normalized", async function () {
|
||||
const { eventBus, pdfFindController } = await initPdfFindController(
|
||||
"fraction-highlight.pdf"
|
||||
|
@ -2575,7 +2575,6 @@ function webViewerFindFromUrlHash(evt) {
|
||||
source: evt.source,
|
||||
type: "",
|
||||
query: evt.query,
|
||||
phraseSearch: evt.phraseSearch,
|
||||
caseSensitive: false,
|
||||
entireWord: false,
|
||||
highlightAll: true,
|
||||
|
@ -222,7 +222,6 @@ class MozL10n {
|
||||
source: window,
|
||||
type: type.substring(findLen),
|
||||
query: detail.query,
|
||||
phraseSearch: true,
|
||||
caseSensitive: !!detail.caseSensitive,
|
||||
entireWord: !!detail.entireWord,
|
||||
highlightAll: !!detail.highlightAll,
|
||||
|
@ -99,7 +99,6 @@ class PDFFindBar {
|
||||
source: this,
|
||||
type,
|
||||
query: this.findField.value,
|
||||
phraseSearch: true,
|
||||
caseSensitive: this.caseSensitive.checked,
|
||||
entireWord: this.entireWord.checked,
|
||||
highlightAll: this.highlightAll.checked,
|
||||
|
@ -387,6 +387,8 @@ function getOriginalIndex(diffs, pos, len) {
|
||||
* Provides search functionality to find a given string in a PDF document.
|
||||
*/
|
||||
class PDFFindController {
|
||||
#state = null;
|
||||
|
||||
#updateMatchesCountOnProgress = true;
|
||||
|
||||
#visitedPagesCount = 0;
|
||||
@ -421,7 +423,7 @@ class PDFFindController {
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this._state;
|
||||
return this.#state;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -445,13 +447,25 @@ class PDFFindController {
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
|
||||
state.phraseSearch === false
|
||||
) {
|
||||
console.error(
|
||||
"The `phraseSearch`-parameter was removed, please provide " +
|
||||
"an Array of strings in the `query`-parameter instead."
|
||||
);
|
||||
if (typeof state.query === "string") {
|
||||
state.query = state.query.match(/\S+/g);
|
||||
}
|
||||
}
|
||||
const pdfDocument = this._pdfDocument;
|
||||
const { type } = state;
|
||||
|
||||
if (this._state === null || this.#shouldDirtyMatch(state)) {
|
||||
if (this.#state === null || this.#shouldDirtyMatch(state)) {
|
||||
this._dirtyMatch = true;
|
||||
}
|
||||
this._state = state;
|
||||
this.#state = state;
|
||||
if (type !== "highlightallchange") {
|
||||
this.#updateUIState(FindState.PENDING);
|
||||
}
|
||||
@ -490,7 +504,7 @@ class PDFFindController {
|
||||
|
||||
// When the findbar was previously closed, and `highlightAll` is set,
|
||||
// ensure that the matches on all active pages are highlighted again.
|
||||
if (findbarClosed && this._state.highlightAll) {
|
||||
if (findbarClosed && this.#state.highlightAll) {
|
||||
this.#updateAllPages();
|
||||
}
|
||||
} else if (type === "highlightallchange") {
|
||||
@ -537,7 +551,7 @@ class PDFFindController {
|
||||
this._pageMatches = [];
|
||||
this._pageMatchesLength = [];
|
||||
this.#visitedPagesCount = 0;
|
||||
this._state = null;
|
||||
this.#state = null;
|
||||
// Currently selected match.
|
||||
this._selected = {
|
||||
pageIdx: -1,
|
||||
@ -565,22 +579,44 @@ class PDFFindController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string} The (current) normalized search query.
|
||||
* @type {string|Array} The (current) normalized search query.
|
||||
*/
|
||||
get #query() {
|
||||
if (this._state.query !== this._rawQuery) {
|
||||
this._rawQuery = this._state.query;
|
||||
[this._normalizedQuery] = normalize(this._state.query);
|
||||
const { query } = this.#state;
|
||||
if (typeof query === "string") {
|
||||
if (query !== this._rawQuery) {
|
||||
this._rawQuery = query;
|
||||
[this._normalizedQuery] = normalize(query);
|
||||
}
|
||||
return this._normalizedQuery;
|
||||
}
|
||||
return this._normalizedQuery;
|
||||
// We don't bother caching the normalized search query in the Array-case,
|
||||
// since this code-path is *essentially* unused in the default viewer.
|
||||
return (query || []).filter(q => !!q).map(q => normalize(q)[0]);
|
||||
}
|
||||
|
||||
#shouldDirtyMatch(state) {
|
||||
// When the search query changes, regardless of the actual search command
|
||||
// used, always re-calculate matches to avoid errors (fixes bug 1030622).
|
||||
if (state.query !== this._state.query) {
|
||||
const newQuery = state.query,
|
||||
prevQuery = this.#state.query;
|
||||
const newType = typeof newQuery,
|
||||
prevType = typeof prevQuery;
|
||||
|
||||
if (newType !== prevType) {
|
||||
return true;
|
||||
}
|
||||
if (newType === "string") {
|
||||
if (newQuery !== prevQuery) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Array
|
||||
if (JSON.stringify(newQuery) !== JSON.stringify(prevQuery)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state.type) {
|
||||
case "again":
|
||||
const pageNumber = this._selected.pageIdx + 1;
|
||||
@ -670,7 +706,7 @@ class PDFFindController {
|
||||
}
|
||||
|
||||
#convertToRegExpString(query, hasDiacritics) {
|
||||
const { matchDiacritics } = this._state;
|
||||
const { matchDiacritics } = this.#state;
|
||||
let isUnicode = false;
|
||||
query = query.replaceAll(
|
||||
SPECIAL_CHARS_REG_EXP,
|
||||
@ -741,36 +777,31 @@ class PDFFindController {
|
||||
|
||||
#calculateMatch(pageIndex) {
|
||||
let query = this.#query;
|
||||
if (!query) {
|
||||
// Do nothing: the matches should be wiped out already.
|
||||
return;
|
||||
if (query.length === 0) {
|
||||
return; // Do nothing: the matches should be wiped out already.
|
||||
}
|
||||
|
||||
const { caseSensitive, entireWord, phraseSearch } = this._state;
|
||||
const { caseSensitive, entireWord } = this.#state;
|
||||
const pageContent = this._pageContents[pageIndex];
|
||||
const hasDiacritics = this._hasDiacritics[pageIndex];
|
||||
|
||||
let isUnicode = false;
|
||||
if (phraseSearch) {
|
||||
if (typeof query === "string") {
|
||||
[isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics);
|
||||
} else {
|
||||
// Words are sorted in reverse order to be sure that "foobar" is matched
|
||||
// before "foo" in case the query is "foobar foo".
|
||||
const match = query.match(/\S+/g);
|
||||
if (match) {
|
||||
query = match
|
||||
.sort()
|
||||
.reverse()
|
||||
.map(q => {
|
||||
const [isUnicodePart, queryPart] = this.#convertToRegExpString(
|
||||
q,
|
||||
hasDiacritics
|
||||
);
|
||||
isUnicode ||= isUnicodePart;
|
||||
return `(${queryPart})`;
|
||||
})
|
||||
.join("|");
|
||||
}
|
||||
query = query
|
||||
.sort()
|
||||
.reverse()
|
||||
.map(q => {
|
||||
const [isUnicodePart, queryPart] = this.#convertToRegExpString(
|
||||
q,
|
||||
hasDiacritics
|
||||
);
|
||||
isUnicode ||= isUnicodePart;
|
||||
return `(${queryPart})`;
|
||||
})
|
||||
.join("|");
|
||||
}
|
||||
|
||||
const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`;
|
||||
@ -780,7 +811,7 @@ class PDFFindController {
|
||||
|
||||
// When `highlightAll` is set, ensure that the matches on previously
|
||||
// rendered (and still active) pages are correctly highlighted.
|
||||
if (this._state.highlightAll) {
|
||||
if (this.#state.highlightAll) {
|
||||
this.#updatePage(pageIndex);
|
||||
}
|
||||
if (this._resumePageIdx === pageIndex) {
|
||||
@ -876,7 +907,7 @@ class PDFFindController {
|
||||
}
|
||||
|
||||
#nextMatch() {
|
||||
const previous = this._state.findPrevious;
|
||||
const previous = this.#state.findPrevious;
|
||||
const currentPageIndex = this._linkService.page - 1;
|
||||
const numPages = this._linkService.pagesCount;
|
||||
|
||||
@ -911,7 +942,8 @@ class PDFFindController {
|
||||
}
|
||||
|
||||
// If there's no query there's no point in searching.
|
||||
if (!this.#query) {
|
||||
const query = this.#query;
|
||||
if (query.length === 0) {
|
||||
this.#updateUIState(FindState.FOUND);
|
||||
return;
|
||||
}
|
||||
@ -948,7 +980,7 @@ class PDFFindController {
|
||||
#matchesReady(matches) {
|
||||
const offset = this._offset;
|
||||
const numMatches = matches.length;
|
||||
const previous = this._state.findPrevious;
|
||||
const previous = this.#state.findPrevious;
|
||||
|
||||
if (numMatches) {
|
||||
// There were matches for the page, so initialize `matchIdx`.
|
||||
@ -1021,7 +1053,7 @@ class PDFFindController {
|
||||
}
|
||||
}
|
||||
|
||||
this.#updateUIState(state, this._state.findPrevious);
|
||||
this.#updateUIState(state, this.#state.findPrevious);
|
||||
if (this._selected.pageIdx !== -1) {
|
||||
// Ensure that the match will be scrolled into view.
|
||||
this._scrollMatches = true;
|
||||
@ -1106,7 +1138,7 @@ class PDFFindController {
|
||||
state,
|
||||
previous,
|
||||
matchesCount: this.#requestMatchesCount(),
|
||||
rawQuery: this._state?.query ?? null,
|
||||
rawQuery: this.#state?.query ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -350,10 +350,12 @@ class PDFLinkService {
|
||||
if (hash.includes("=")) {
|
||||
const params = parseQueryString(hash);
|
||||
if (params.has("search")) {
|
||||
const query = params.get("search").replaceAll('"', ""),
|
||||
phrase = params.get("phrase") === "true";
|
||||
|
||||
this.eventBus.dispatch("findfromurlhash", {
|
||||
source: this,
|
||||
query: params.get("search").replaceAll('"', ""),
|
||||
phraseSearch: params.get("phrase") === "true",
|
||||
query: phrase ? query : query.match(/\S+/g),
|
||||
});
|
||||
}
|
||||
// borrowing syntax from "Parameters for Opening PDF Files"
|
||||
|
Loading…
Reference in New Issue
Block a user