Merge pull request #12493 from Snuffleupagus/PDFHistory.pushPage

Support adding pages, in addition to regular destinations, to the browser history and use it with thumbnails (issue 12440)
This commit is contained in:
Tim van der Meij 2020-10-20 22:20:11 +02:00 committed by GitHub
commit e389ed6201
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 92 deletions

View File

@ -344,7 +344,7 @@ class LinkAnnotationElement extends AnnotationElement {
link.href = this.linkService.getDestinationHash(destination); link.href = this.linkService.getDestinationHash(destination);
link.onclick = () => { link.onclick = () => {
if (destination) { if (destination) {
this.linkService.navigateTo(destination); this.linkService.goToDestination(destination);
} }
return false; return false;
}; };

View File

@ -54,9 +54,14 @@ class IPDFLinkService {
set externalLinkEnabled(value) {} set externalLinkEnabled(value) {}
/** /**
* @param dest - The PDF destination object. * @param {string|Array} dest - The named, or explicit, PDF destination.
*/ */
navigateTo(dest) {} async goToDestination(dest) {}
/**
* @param {number} pageNumber - The page number.
*/
goToPage(pageNumber) {}
/** /**
* @param dest - The PDF destination object. * @param dest - The PDF destination object.
@ -108,6 +113,11 @@ class IPDFHistory {
*/ */
push({ namedDest = null, explicitDest, pageNumber }) {} push({ namedDest = null, explicitDest, pageNumber }) {}
/**
* @param {number} pageNumber
*/
pushPage(pageNumber) {}
pushCurrentPosition() {} pushCurrentPosition() {}
back() {} back() {}

View File

@ -143,9 +143,6 @@ class PDFHistory {
state.uid, state.uid,
/* removeTemporary = */ true /* removeTemporary = */ true
); );
if (this._uid > this._maxUid) {
this._maxUid = this._uid;
}
if (destination.rotation !== undefined) { if (destination.rotation !== undefined) {
this._initialRotation = destination.rotation; this._initialRotation = destination.rotation;
@ -271,6 +268,55 @@ class PDFHistory {
} }
} }
/**
* Push a page to the browser history; generally the `push` method should be
* used instead.
* @param {number} pageNumber
*/
pushPage(pageNumber) {
if (!this._initialized) {
return;
}
if (
!(
Number.isInteger(pageNumber) &&
pageNumber > 0 &&
pageNumber <= this.linkService.pagesCount
)
) {
console.error(
`PDFHistory.pushPage: "${pageNumber}" is not a valid page number.`
);
return;
}
if (this._destination && this._destination.page === pageNumber) {
// When the new page is identical to the one in `this._destination`, we
// don't want to add a potential duplicate entry in the browser history.
return;
}
if (this._popStateInProgress) {
return;
}
this._pushOrReplaceState({
hash: `page=${pageNumber}`,
page: pageNumber,
rotation: this.linkService.rotation,
});
if (!this._popStateInProgress) {
// Prevent the browser history from updating while the new page is
// being scrolled into view, to avoid potentially inconsistent state.
this._popStateInProgress = true;
// We defer the resetting of `this._popStateInProgress`, to account for
// e.g. zooming occuring when the new page is being navigated to.
Promise.resolve().then(() => {
this._popStateInProgress = false;
});
}
}
/** /**
* Push the current position to the browser history. * Push the current position to the browser history.
*/ */
@ -361,7 +407,6 @@ class PDFHistory {
if (shouldReplace) { if (shouldReplace) {
window.history.replaceState(newState, "", newUrl); window.history.replaceState(newState, "", newUrl);
} else { } else {
this._maxUid = this._uid;
window.history.pushState(newState, "", newUrl); window.history.pushState(newState, "", newUrl);
} }
@ -485,6 +530,7 @@ class PDFHistory {
} }
this._destination = destination; this._destination = destination;
this._uid = uid; this._uid = uid;
this._maxUid = Math.max(this._maxUid, uid);
// This should always be reset when `this._destination` is updated. // This should always be reset when `this._destination` is updated.
this._numPositionUpdates = 0; this._numPositionUpdates = 0;
} }
@ -639,15 +685,12 @@ class PDFHistory {
state.uid, state.uid,
/* removeTemporary = */ true /* removeTemporary = */ true
); );
if (this._uid > this._maxUid) {
this._maxUid = this._uid;
}
if (isValidRotation(destination.rotation)) { if (isValidRotation(destination.rotation)) {
this.linkService.rotation = destination.rotation; this.linkService.rotation = destination.rotation;
} }
if (destination.dest) { if (destination.dest) {
this.linkService.navigateTo(destination.dest); this.linkService.goToDestination(destination.dest);
} else if (destination.hash) { } else if (destination.hash) {
this.linkService.setHash(destination.hash); this.linkService.setHash(destination.hash);
} else if (destination.page) { } else if (destination.page) {
@ -655,7 +698,7 @@ class PDFHistory {
this.linkService.page = destination.page; this.linkService.page = destination.page;
} }
// Since `PDFLinkService.navigateTo` is asynchronous, we thus defer the // Since `PDFLinkService.goToDestination` is asynchronous, we thus defer the
// resetting of `this._popStateInProgress` slightly. // resetting of `this._popStateInProgress` slightly.
Promise.resolve().then(() => { Promise.resolve().then(() => {
this._popStateInProgress = false; this._popStateInProgress = false;

View File

@ -108,91 +108,127 @@ class PDFLinkService {
} }
/** /**
* @param {string|Array} dest - The named, or explicit, PDF destination. * @deprecated
*/ */
navigateTo(dest) { navigateTo(dest) {
const goToDestination = ({ namedDest, explicitDest }) => { console.error(
// Dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..> "Deprecated method: `navigateTo`, use `goToDestination` instead."
const destRef = explicitDest[0]; );
let pageNumber; this.goToDestination(dest);
}
if (destRef instanceof Object) { /**
pageNumber = this._cachedPageNumber(destRef); * @private
*/
_goToDestinationHelper(rawDest, namedDest = null, explicitDest) {
// Dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
const destRef = explicitDest[0];
let pageNumber;
if (pageNumber === null) { if (destRef instanceof Object) {
// Fetch the page reference if it's not yet available. This could pageNumber = this._cachedPageNumber(destRef);
// only occur during loading, before all pages have been resolved.
this.pdfDocument
.getPageIndex(destRef)
.then(pageIndex => {
this.cachePageRef(pageIndex + 1, destRef);
goToDestination({ namedDest, explicitDest });
})
.catch(() => {
console.error(
`PDFLinkService.navigateTo: "${destRef}" is not ` +
`a valid page reference, for dest="${dest}".`
);
});
return;
}
} else if (Number.isInteger(destRef)) {
pageNumber = destRef + 1;
} else {
console.error(
`PDFLinkService.navigateTo: "${destRef}" is not ` +
`a valid destination reference, for dest="${dest}".`
);
return;
}
if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
console.error(
`PDFLinkService.navigateTo: "${pageNumber}" is not ` +
`a valid page number, for dest="${dest}".`
);
return;
}
if (this.pdfHistory) { if (pageNumber === null) {
// Update the browser history before scrolling the new destination into // Fetch the page reference if it's not yet available. This could
// view, to be able to accurately capture the current document position. // only occur during loading, before all pages have been resolved.
this.pdfHistory.pushCurrentPosition(); this.pdfDocument
this.pdfHistory.push({ namedDest, explicitDest, pageNumber }); .getPageIndex(destRef)
} .then(pageIndex => {
this.cachePageRef(pageIndex + 1, destRef);
this.pdfViewer.scrollPageIntoView({ this._goToDestinationHelper(rawDest, namedDest, explicitDest);
pageNumber, })
destArray: explicitDest, .catch(() => {
ignoreDestinationZoom: this._ignoreDestinationZoom, console.error(
}); `PDFLinkService._goToDestinationHelper: "${destRef}" is not ` +
}; `a valid page reference, for dest="${rawDest}".`
);
new Promise((resolve, reject) => {
if (typeof dest === "string") {
this.pdfDocument.getDestination(dest).then(destArray => {
resolve({
namedDest: dest,
explicitDest: destArray,
}); });
});
return; return;
} }
resolve({ } else if (Number.isInteger(destRef)) {
namedDest: "", pageNumber = destRef + 1;
explicitDest: dest, } else {
}); console.error(
}).then(data => { `PDFLinkService._goToDestinationHelper: "${destRef}" is not ` +
if (!Array.isArray(data.explicitDest)) { `a valid destination reference, for dest="${rawDest}".`
console.error( );
`PDFLinkService.navigateTo: "${data.explicitDest}" is` + return;
` not a valid destination array, for dest="${dest}".` }
); if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
return; console.error(
} `PDFLinkService._goToDestinationHelper: "${pageNumber}" is not ` +
goToDestination(data); `a valid page number, for dest="${rawDest}".`
);
return;
}
if (this.pdfHistory) {
// Update the browser history before scrolling the new destination into
// view, to be able to accurately capture the current document position.
this.pdfHistory.pushCurrentPosition();
this.pdfHistory.push({ namedDest, explicitDest, pageNumber });
}
this.pdfViewer.scrollPageIntoView({
pageNumber,
destArray: explicitDest,
ignoreDestinationZoom: this._ignoreDestinationZoom,
}); });
} }
/**
* This method will, when available, also update the browser history.
*
* @param {string|Array} dest - The named, or explicit, PDF destination.
*/
async goToDestination(dest) {
let namedDest, explicitDest;
if (typeof dest === "string") {
namedDest = dest;
explicitDest = await this.pdfDocument.getDestination(dest);
} else {
namedDest = null;
explicitDest = await dest;
}
if (!Array.isArray(explicitDest)) {
console.error(
`PDFLinkService.goToDestination: "${explicitDest}" is not ` +
`a valid destination array, for dest="${dest}".`
);
return;
}
this._goToDestinationHelper(dest, namedDest, explicitDest);
}
/**
* This method will, when available, also update the browser history.
*
* @param {number} pageNumber - The page number.
*/
goToPage(pageNumber) {
if (
!(
Number.isInteger(pageNumber) &&
pageNumber > 0 &&
pageNumber <= this.pagesCount
)
) {
console.error(
`PDFLinkService.goToPage: "${pageNumber}" is not a valid page number.`
);
return;
}
if (this.pdfHistory) {
// Update the browser history before scrolling the new page into view,
// to be able to accurately capture the current document position.
this.pdfHistory.pushCurrentPosition();
this.pdfHistory.pushPage(pageNumber);
}
this.pdfViewer.scrollPageIntoView({ pageNumber });
}
/** /**
* @param {string|Array} dest - The PDF destination object. * @param {string|Array} dest - The PDF destination object.
* @returns {string} The hyperlink to the PDF object. * @returns {string} The hyperlink to the PDF object.
@ -307,7 +343,7 @@ class PDFLinkService {
// Ensure that this parameter is *always* handled last, in order to // Ensure that this parameter is *always* handled last, in order to
// guarantee that it won't be overridden (e.g. by the "page" parameter). // guarantee that it won't be overridden (e.g. by the "page" parameter).
if ("nameddest" in params) { if ("nameddest" in params) {
this.navigateTo(params.nameddest); this.goToDestination(params.nameddest);
} }
} else { } else {
// Named (or explicit) destination. // Named (or explicit) destination.
@ -323,7 +359,7 @@ class PDFLinkService {
} catch (ex) {} } catch (ex) {}
if (typeof dest === "string" || isValidExplicitDestination(dest)) { if (typeof dest === "string" || isValidExplicitDestination(dest)) {
this.navigateTo(dest); this.goToDestination(dest);
return; return;
} }
console.error( console.error(
@ -394,6 +430,9 @@ class PDFLinkService {
this._pagesRefCache[refStr] = pageNum; this._pagesRefCache[refStr] = pageNum;
} }
/**
* @private
*/
_cachedPageNumber(pageRef) { _cachedPageNumber(pageRef) {
const refStr = const refStr =
pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`; pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
@ -510,9 +549,14 @@ class SimpleLinkService {
set rotation(value) {} set rotation(value) {}
/** /**
* @param dest - The PDF destination object. * @param {string|Array} dest - The named, or explicit, PDF destination.
*/ */
navigateTo(dest) {} async goToDestination(dest) {}
/**
* @param {number} pageNumber - The page number.
*/
goToPage(pageNumber) {}
/** /**
* @param dest - The PDF destination object. * @param dest - The PDF destination object.

View File

@ -73,7 +73,7 @@ class PDFOutlineViewer extends BaseTreeViewer {
element.href = linkService.getDestinationHash(dest); element.href = linkService.getDestinationHash(dest);
element.onclick = () => { element.onclick = () => {
if (dest) { if (dest) {
linkService.navigateTo(dest); linkService.goToDestination(dest);
} }
return false; return false;
}; };

View File

@ -138,7 +138,7 @@ class PDFThumbnailView {
anchor.title = msg; anchor.title = msg;
}); });
anchor.onclick = function () { anchor.onclick = function () {
linkService.page = id; linkService.goToPage(id);
return false; return false;
}; };
this.anchor = anchor; this.anchor = anchor;