From 97642ff95a8b8154e243924d8b54b54f09370525 Mon Sep 17 00:00:00 2001 From: Drini Cami Date: Tue, 7 Jul 2026 16:26:15 +0200 Subject: [PATCH] Fix copy link to highlight for preview pages --- src/BookReader.js | 29 ++++++++++++++++++-- src/plugins/plugin.text_selection.js | 7 +++++ src/plugins/search/plugin.search.js | 40 +++------------------------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/BookReader.js b/src/BookReader.js index 5fabcce6e..ea755cec3 100644 --- a/src/BookReader.js +++ b/src/BookReader.js @@ -32,6 +32,7 @@ import { DEFAULT_OPTIONS, OptionsParseError } from './BookReader/options.js'; /** @typedef {import('./BookReader/options.js').BookReaderOptions} BookReaderOptions */ /** @typedef {import('./BookReader/options.js').ReductionFactor} ReductionFactor */ /** @typedef {import('./BookReader/BookModel.js').PageIndex} PageIndex */ +/** @typedef {import('./BookReader/BookModel.js').PageModel} PageModel */ import { EVENTS } from './BookReader/events.js'; import { Toolbar } from './BookReader/Toolbar/Toolbar.js'; import { BookModel } from './BookReader/BookModel.js'; @@ -1147,6 +1148,7 @@ BookReader.prototype._isIndexDisplayed = function(index) { * Changes the current page * @param {PageIndex | 'left' | 'right' | 'next' | 'prev'} indexOrDirection * @param {object} options + * @param {boolean} [options.allowUnviewable] - whether to allow jumping to unviewable pages; default false * @param {number} [options.pageX] Position on page ; not implemented * @param {number} [options.pageY] Position on page ; not implemented * @param {boolean} [options.noAnimate] @@ -1154,7 +1156,7 @@ BookReader.prototype._isIndexDisplayed = function(index) { * @param {boolean} [options.ariaLive] * @param {boolean} [options.triggerStop] - whether to trigger the stop event; default true; maybe deprecated? */ -BookReader.prototype.jumpToIndex = function(indexOrDirection, {pageX = 0, pageY = 0, noAnimate = false, flipSpeed = null, ariaLive = false, triggerStop = true} = {}) { +BookReader.prototype.jumpToIndex = function(indexOrDirection, {pageX = 0, pageY = 0, noAnimate = false, flipSpeed = null, ariaLive = false, triggerStop = true, allowUnviewable = false} = {}) { const page = this.activeMode.parsePageSpecifier(indexOrDirection); flipSpeed = utils.parseAnimationSpeed(flipSpeed) || this.flipSpeed; if (!page || page.index == this.currentIndex()) { @@ -1162,7 +1164,7 @@ BookReader.prototype.jumpToIndex = function(indexOrDirection, {pageX = 0, pageY } // Don't jump into specific unviewable page - if (!page.isViewable && page.unviewablesStart != page.index) { + if (!allowUnviewable && !page.isViewable && page.unviewablesStart != page.index) { // If already in unviewable range, jump to end of that range const alreadyInPreview = this._isIndexDisplayed(page.unviewablesStart); const newIndex = alreadyInPreview ? page.findNext({ combineConsecutiveUnviewables: true })?.index : page.unviewablesStart; @@ -1981,6 +1983,29 @@ BookReader.prototype.queryStringFromParams = function( return result ? `?${result}` : ''; }; +/** + * Tries to open the slot for the given page by requesting the server to make it viewable. + * @param {PageModel} page + */ +BookReader.prototype.tryOpenSlotForPage = async function(page) { + if (page.isViewable) return; + const resp = await fetch('/services/bookreader/request_page?' + new URLSearchParams({ + id: this.options.bookId, + subprefix: this.options.subPrefix, + leafNum: page.leafNum, + })).then(r => r.json()); + + for (const leafNum of resp.value) { + this.book.getPage(this.book.leafNumToIndex(leafNum)).makeViewable(); + } + + // Trigger an update of book + this._modes.mode1Up.mode1UpLit.updatePages(); + if (this.activeMode == this._modes.mode1Up) { + await this._modes.mode1Up.mode1UpLit.updateComplete; + } +}; + /** * Helper to select within instance's elements */ diff --git a/src/plugins/plugin.text_selection.js b/src/plugins/plugin.text_selection.js index df0b0301e..19621ca7f 100644 --- a/src/plugins/plugin.text_selection.js +++ b/src/plugins/plugin.text_selection.js @@ -83,6 +83,13 @@ export class TextSelectionPlugin extends BookReaderPlugin { } } }); + + // Now jump to the page and open a slot if necessary + const page = this.br.book.getPage(this.targetTextFragment.pageIndex); + if (!page.isViewable) { + this.br.tryOpenSlotForPage(page) + .then(() => this.br.jumpToIndex(page.index, { ariaLive: true, allowUnviewable: true })); + } } } diff --git a/src/plugins/search/plugin.search.js b/src/plugins/search/plugin.search.js index c6ba05757..f1f298f2e 100644 --- a/src/plugins/search/plugin.search.js +++ b/src/plugins/search/plugin.search.js @@ -29,6 +29,7 @@ import { applyVariables } from '../../util/strings.js'; import { toISO6391 } from '../tts/utils.js'; /** @typedef {import('../../BookReader/PageContainer').PageContainer} PageContainer */ /** @typedef {import('../../BookReader/BookModel').PageIndex} PageIndex */ +/** @typedef {import('../../BookReader/BookModel').PageModel} PageModel */ /** @typedef {import('../../BookReader/BookModel').LeafNum} LeafNum */ /** @typedef {import('../../BookReader/BookModel').PageNumString} PageNumString */ @@ -341,43 +342,10 @@ export class SearchPlugin extends BookReaderPlugin { const match = this.searchResults?.matches[matchIndex]; const book = this.br.book; const pageIndex = book.leafNumToIndex(match.par[0].page); - const page = book.getPage(pageIndex); + const page = /** @type {PageModel} */(book.getPage(pageIndex)); const onNearbyPage = Math.abs(this.br.currentIndex() - pageIndex) < 3; - let makeUnviewableAtEnd = false; - if (!page.isViewable) { - const resp = await fetch('/services/bookreader/request_page?' + new URLSearchParams({ - id: this.br.options.bookId, - subprefix: this.br.options.subPrefix, - leafNum: page.leafNum, - })).then(r => r.json()); - - for (const leafNum of resp.value) { - book.getPage(book.leafNumToIndex(leafNum)).makeViewable(); - } - - // not able to show page; make the page viewable anyways so that it can - // actually open. On IA, it has a fallback to a special error page. - if (!resp.value.length) { - book.getPage(pageIndex).makeViewable(); - makeUnviewableAtEnd = true; - } - - // Trigger an update of book - this.br._modes.mode1Up.mode1UpLit.updatePages(); - if (this.br.activeMode == this.br._modes.mode1Up) { - await this.br._modes.mode1Up.mode1UpLit.updateComplete; - } - } - /* this updates the URL */ - if (!this.br._isIndexDisplayed(pageIndex)) { - this.suppressFragmentChange = false; - this.br.jumpToIndex(pageIndex, { ariaLive: true }); - } - - // Reset it to unviewable if it wasn't resolved - if (makeUnviewableAtEnd) { - book.getPage(pageIndex).makeViewable(false); - } + await this.br.tryOpenSlotForPage(page); + this.br.jumpToIndex(page.index, { ariaLive: true, allowUnviewable: true }); // Scroll/flash in the ui const $boxes = await poll(() => $(`rect.match-index-${match.matchIndex}`), { until: result => result.length > 0 });