@internetarchive/bookreader 5.0.0-112 → 5.0.0-114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BookReader/BookReader.css +20 -7
- package/BookReader/BookReader.js +43 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +3 -45
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +2 -2
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.experiments.js +1 -1
- package/BookReader/plugins/plugin.experiments.js.map +1 -1
- package/BookReader/plugins/plugin.search.js +1 -1
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +1 -23
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.translate.js +3 -25
- package/BookReader/plugins/plugin.translate.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +1 -2
- package/BookReader/plugins/plugin.url.js.map +1 -1
- package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
- package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
- package/package.json +1 -1
- package/src/BookReader/utils/SelectionObserver.js +2 -2
- package/src/BookReader.js +5 -4
- package/src/css/_BRpages.scss +5 -2
- package/src/css/_TextSelection.scss +21 -9
- package/src/ia-bookreader/ia-bookreader.js +13 -2
- package/src/plugins/plugin.experiments.js +44 -8
- package/src/plugins/plugin.text_selection.js +26 -56
- package/src/plugins/tts/plugin.tts.js +3 -3
- package/src/plugins/tts/utils.js +0 -10
- package/src/plugins/url/UrlPlugin.js +2 -15
- package/src/plugins/url/plugin.url.js +7 -59
- package/src/util/TextSelectionManager.js +950 -138
- package/src/util/strings.js +10 -0
- package/BookReader/plugins/plugin.url.js.LICENSE.txt +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugins/plugin.vendor-fullscreen.js","mappings":"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","sources":["webpack://@internetarchive/bookreader/./node_modules/core-js/internals/string-trim-forced.js","webpack://@internetarchive/bookreader/./node_modules/core-js/modules/es.string.trim.js","webpack://@internetarchive/bookreader/./src/plugins/plugin.vendor-fullscreen.js"],"sourcesContent":["'use strict';\nvar PROPER_FUNCTION_NAME = require('../internals/function-name').PROPER;\nvar fails = require('../internals/fails');\nvar whitespaces = require('../internals/whitespaces');\n\nvar non = '\\u200B\\u0085\\u180E';\n\n// check that a method works with the correct list\n// of whitespaces and has a correct name\nmodule.exports = function (METHOD_NAME) {\n return fails(function () {\n return !!whitespaces[METHOD_NAME]()\n || non[METHOD_NAME]() !== non\n || (PROPER_FUNCTION_NAME && whitespaces[METHOD_NAME].name !== METHOD_NAME);\n });\n};\n","'use strict';\nvar $ = require('../internals/export');\nvar $trim = require('../internals/string-trim').trim;\nvar forcedStringTrimMethod = require('../internals/string-trim-forced');\n\n// `String.prototype.trim` method\n// https://tc39.es/ecma262/#sec-string.prototype.trim\n$({ target: 'String', proto: true, forced: forcedStringTrimMethod('trim') }, {\n trim: function trim() {\n return $trim(this);\n }\n});\n","/* global BookReader */\n\n/**\n * Toggles browser's native fullscreen mode if available device is not mobile\n */\nif (!isMobile()) {\n const EVENT_NAMESPACE = '.bookreader_vendor-fullscreen';\n\n jQuery.extend(BookReader.defaultOptions, {\n /** @type {boolean} */\n enableVendorFullscreenPlugin: true,\n });\n\n /** @override */\n BookReader.prototype.setup = (function(super_) {\n return function(options) {\n super_.call(this, options);\n\n this.isVendorFullscreenActive = false;\n };\n })(BookReader.prototype.setup);\n\n /** @override */\n BookReader.prototype.getInitialMode = (function(super_) {\n return function(params) {\n let nextMode = super_.call(this, params);\n if (this.isVendorFullscreenActive) {\n nextMode = this.constMode1up;\n }\n return nextMode;\n };\n })(BookReader.prototype.getInitialMode);\n\n /** @override */\n BookReader.prototype.init = (function(super_) {\n return function() {\n super_.call(this);\n\n if (!fullscreenAllowed()) {\n return;\n }\n // In fullscreen mode the colorbox and overlay need to be inside the fullscreen element to display properly.\n bindFullscreenChangeListener(this, (e) => {\n e.data.resize();\n e.data.updateBrClasses();\n const cboxOverlay = $('#cboxOverlay');\n const cbox = $('#colorbox');\n if (isFullscreenActive()) {\n // In full screen mode, the colorbox and overlay need\n // to be children of the fullscreen element to display properly.\n const $fullscreen = $(getFullscreenElement());\n $fullscreen.append(cboxOverlay).append(cbox);\n } else {\n // In non-fullscreen mode, the colorbox and overlay need\n // to be children of the main document body.\n $(document.body).append(cboxOverlay).append(cbox);\n }\n });\n };\n })(BookReader.prototype.init);\n\n /**\n * Start fullscreen mode\n */\n BookReader.prototype.enterFullWindow = function() {\n this.refs.$brContainer.css('opacity', 0);\n\n const windowWidth = $(window).width();\n if (windowWidth <= this.onePageMinBreakpoint) {\n this.switchMode(this.constMode1up);\n }\n\n this.isVendorFullscreenActive = true;\n this.updateBrClasses();\n\n this.resize();\n this.jumpToIndex(this.currentIndex());\n\n this.refs.$brContainer.animate({ opacity: 1 }, 400, 'linear');\n\n $(document).on(`keyup.${EVENT_NAMESPACE}`, e => {\n if (e.keyCode === 27) this.exitFullScreen();\n });\n };\n\n /**\n * Exit from fullscreen mode\n */\n BookReader.prototype.exitFullWindow = function() {\n this.refs.$brContainer.css('opacity', 0);\n\n $(document).off('keyup' + EVENT_NAMESPACE);\n\n this.isFullscreenActive = false;\n this.updateBrClasses();\n\n this.resize();\n this.refs.$brContainer.animate({ opacity: 1 }, 400, 'linear');\n };\n\n /**\n * Returns true if fullscreen mode is enabled\n *\n * @returns {boolean}\n */\n BookReader.prototype.isFullscreen = function() {\n return isFullscreenActive() || this.isVendorFullscreenActive;\n };\n\n /**\n * Toggle screen\n */\n BookReader.prototype.toggleFullscreen = function() {\n if (this.isFullscreen()) {\n if (fullscreenAllowed()) {\n exitFullscreen();\n } else {\n this.exitFullWindow();\n }\n } else {\n if (fullscreenAllowed()) {\n requestFullscreen(this.refs.$br[0]);\n } else {\n this.enterFullWindow();\n }\n }\n };\n\n /** @deprecated */\n BookReader.util.isMobile = isMobile;\n\n /** @deprecated */\n BookReader.util.getFullscreenElement = getFullscreenElement;\n\n /** @deprecated */\n BookReader.util.bindFullscreenChangeListener = bindFullscreenChangeListener;\n\n /** @deprecated */\n BookReader.util.fullscreenAllowed = fullscreenAllowed;\n\n /** @deprecated */\n BookReader.util.requestFullscreen = requestFullscreen;\n\n /** @deprecated */\n BookReader.util.exitFullscreen = exitFullscreen;\n\n /** @deprecated */\n BookReader.util.isFullscreenActive = isFullscreenActive;\n}\n\n\n/**\n * Returns the DOM element being used for fullscreen.\n *\n * @returns {HTMLElement}\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/fullscreenElement\n */\nexport function getFullscreenElement() {\n return document.fullscreenElement ||\n document.webkitFullscreenElement ||\n document.mozFullScreenElement ||\n document.msFullscreenElement;\n}\n\n/**\n * Returns true if the document is in fullscreen mode.\n *\n * @returns {boolean}\n */\nexport function isFullscreenActive() {\n const fullscreenElement = getFullscreenElement();\n return fullscreenElement !== null && fullscreenElement !== undefined;\n}\n\n/**\n * Exits fullscreen mode.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/exitFullscreen\n */\nexport function exitFullscreen() {\n if (document.exitFullscreen) {\n document.exitFullscreen();\n } else if (document.webkitExitFullscreen) {\n document.webkitExitFullscreen();\n } else if (document.mozCancelFullScreen) {\n document.mozCancelFullScreen();\n } else if (document.msExitFullscreen) {\n document.msExitFullscreen();\n }\n}\n\n/**\n * Requests fullscreen mode for the given element\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen\n */\nexport function requestFullscreen(element) {\n if (element.requestFullscreen) {\n element.requestFullscreen();\n } else if (element.webkitRequestFullscreen) {\n element.webkitRequestFullscreen();\n } else if (element.mozRequestFullScreen) {\n element.mozRequestFullScreen();\n } else if (element.msRequestFullscreen) {\n element.msRequestFullscreen();\n }\n}\n\n/**\n * Returns true if fullscreen mode is allowed on this device and document.\n *\n * @returns {boolean}\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenEnabled\n */\nexport function fullscreenAllowed() {\n return (document.fullscreenEnabled ||\n document.webkitFullscreenEnabled ||\n document.mozFullScreenEnabled ||\n document.msFullScreenEnabled);\n}\n\n/**\n * jQuery-style binding to a fullscreenchange event.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenchange_event\n */\nexport function bindFullscreenChangeListener(\n data, fullscreenchangeListener,\n) {\n const event = 'fullscreenchange ';\n const vendor_prefixes = [\n 'webkit',\n 'moz',\n 'ms',\n ];\n const all_events = (event + vendor_prefixes.join(event) + event).trim();\n $(document).on(all_events, data, fullscreenchangeListener);\n}\n\n/**\n * Returns true if current device is mobile\n *\n * @returns {boolean}\n */\nexport function isMobile() {\n return (typeof window.orientation !== 'undefined') || (navigator.userAgent.indexOf('IEMobile') !== -1);\n}\n"],"names":["PROPER_FUNCTION_NAME","fails","whitespaces","module","exports","METHOD_NAME","name","$","$trim","target","proto","forced","forcedStringTrimMethod","trim","this","super_","isMobile","EVENT_NAMESPACE","jQuery","extend","BookReader","defaultOptions","enableVendorFullscreenPlugin","prototype","setup","options","call","isVendorFullscreenActive","getInitialMode","params","nextMode","constMode1up","init","fullscreenAllowed","bindFullscreenChangeListener","e","data","resize","updateBrClasses","cboxOverlay","cbox","isFullscreenActive","getFullscreenElement","append","document","body","enterFullWindow","_this","refs","$brContainer","css","window","width","onePageMinBreakpoint","switchMode","jumpToIndex","currentIndex","animate","opacity","on","concat","keyCode","exitFullScreen","exitFullWindow","off","isFullscreen","toggleFullscreen","exitFullscreen","requestFullscreen","$br","util","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullScreenEnabled","fullscreenchangeListener","event","all_events","join","orientation","navigator","userAgent","indexOf"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"file":"plugins/plugin.vendor-fullscreen.js","mappings":"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","sources":["webpack://@internetarchive/bookreader/./src/plugins/plugin.vendor-fullscreen.js"],"sourcesContent":["/* global BookReader */\n\n/**\n * Toggles browser's native fullscreen mode if available device is not mobile\n */\nif (!isMobile()) {\n const EVENT_NAMESPACE = '.bookreader_vendor-fullscreen';\n\n jQuery.extend(BookReader.defaultOptions, {\n /** @type {boolean} */\n enableVendorFullscreenPlugin: true,\n });\n\n /** @override */\n BookReader.prototype.setup = (function(super_) {\n return function(options) {\n super_.call(this, options);\n\n this.isVendorFullscreenActive = false;\n };\n })(BookReader.prototype.setup);\n\n /** @override */\n BookReader.prototype.getInitialMode = (function(super_) {\n return function(params) {\n let nextMode = super_.call(this, params);\n if (this.isVendorFullscreenActive) {\n nextMode = this.constMode1up;\n }\n return nextMode;\n };\n })(BookReader.prototype.getInitialMode);\n\n /** @override */\n BookReader.prototype.init = (function(super_) {\n return function() {\n super_.call(this);\n\n if (!fullscreenAllowed()) {\n return;\n }\n // In fullscreen mode the colorbox and overlay need to be inside the fullscreen element to display properly.\n bindFullscreenChangeListener(this, (e) => {\n e.data.resize();\n e.data.updateBrClasses();\n const cboxOverlay = $('#cboxOverlay');\n const cbox = $('#colorbox');\n if (isFullscreenActive()) {\n // In full screen mode, the colorbox and overlay need\n // to be children of the fullscreen element to display properly.\n const $fullscreen = $(getFullscreenElement());\n $fullscreen.append(cboxOverlay).append(cbox);\n } else {\n // In non-fullscreen mode, the colorbox and overlay need\n // to be children of the main document body.\n $(document.body).append(cboxOverlay).append(cbox);\n }\n });\n };\n })(BookReader.prototype.init);\n\n /**\n * Start fullscreen mode\n */\n BookReader.prototype.enterFullWindow = function() {\n this.refs.$brContainer.css('opacity', 0);\n\n const windowWidth = $(window).width();\n if (windowWidth <= this.onePageMinBreakpoint) {\n this.switchMode(this.constMode1up);\n }\n\n this.isVendorFullscreenActive = true;\n this.updateBrClasses();\n\n this.resize();\n this.jumpToIndex(this.currentIndex());\n\n this.refs.$brContainer.animate({ opacity: 1 }, 400, 'linear');\n\n $(document).on(`keyup.${EVENT_NAMESPACE}`, e => {\n if (e.keyCode === 27) this.exitFullScreen();\n });\n };\n\n /**\n * Exit from fullscreen mode\n */\n BookReader.prototype.exitFullWindow = function() {\n this.refs.$brContainer.css('opacity', 0);\n\n $(document).off('keyup' + EVENT_NAMESPACE);\n\n this.isFullscreenActive = false;\n this.updateBrClasses();\n\n this.resize();\n this.refs.$brContainer.animate({ opacity: 1 }, 400, 'linear');\n };\n\n /**\n * Returns true if fullscreen mode is enabled\n *\n * @returns {boolean}\n */\n BookReader.prototype.isFullscreen = function() {\n return isFullscreenActive() || this.isVendorFullscreenActive;\n };\n\n /**\n * Toggle screen\n */\n BookReader.prototype.toggleFullscreen = function() {\n if (this.isFullscreen()) {\n if (fullscreenAllowed()) {\n exitFullscreen();\n } else {\n this.exitFullWindow();\n }\n } else {\n if (fullscreenAllowed()) {\n requestFullscreen(this.refs.$br[0]);\n } else {\n this.enterFullWindow();\n }\n }\n };\n\n /** @deprecated */\n BookReader.util.isMobile = isMobile;\n\n /** @deprecated */\n BookReader.util.getFullscreenElement = getFullscreenElement;\n\n /** @deprecated */\n BookReader.util.bindFullscreenChangeListener = bindFullscreenChangeListener;\n\n /** @deprecated */\n BookReader.util.fullscreenAllowed = fullscreenAllowed;\n\n /** @deprecated */\n BookReader.util.requestFullscreen = requestFullscreen;\n\n /** @deprecated */\n BookReader.util.exitFullscreen = exitFullscreen;\n\n /** @deprecated */\n BookReader.util.isFullscreenActive = isFullscreenActive;\n}\n\n\n/**\n * Returns the DOM element being used for fullscreen.\n *\n * @returns {HTMLElement}\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/fullscreenElement\n */\nexport function getFullscreenElement() {\n return document.fullscreenElement ||\n document.webkitFullscreenElement ||\n document.mozFullScreenElement ||\n document.msFullscreenElement;\n}\n\n/**\n * Returns true if the document is in fullscreen mode.\n *\n * @returns {boolean}\n */\nexport function isFullscreenActive() {\n const fullscreenElement = getFullscreenElement();\n return fullscreenElement !== null && fullscreenElement !== undefined;\n}\n\n/**\n * Exits fullscreen mode.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/exitFullscreen\n */\nexport function exitFullscreen() {\n if (document.exitFullscreen) {\n document.exitFullscreen();\n } else if (document.webkitExitFullscreen) {\n document.webkitExitFullscreen();\n } else if (document.mozCancelFullScreen) {\n document.mozCancelFullScreen();\n } else if (document.msExitFullscreen) {\n document.msExitFullscreen();\n }\n}\n\n/**\n * Requests fullscreen mode for the given element\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen\n */\nexport function requestFullscreen(element) {\n if (element.requestFullscreen) {\n element.requestFullscreen();\n } else if (element.webkitRequestFullscreen) {\n element.webkitRequestFullscreen();\n } else if (element.mozRequestFullScreen) {\n element.mozRequestFullScreen();\n } else if (element.msRequestFullscreen) {\n element.msRequestFullscreen();\n }\n}\n\n/**\n * Returns true if fullscreen mode is allowed on this device and document.\n *\n * @returns {boolean}\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenEnabled\n */\nexport function fullscreenAllowed() {\n return (document.fullscreenEnabled ||\n document.webkitFullscreenEnabled ||\n document.mozFullScreenEnabled ||\n document.msFullScreenEnabled);\n}\n\n/**\n * jQuery-style binding to a fullscreenchange event.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenchange_event\n */\nexport function bindFullscreenChangeListener(\n data, fullscreenchangeListener,\n) {\n const event = 'fullscreenchange ';\n const vendor_prefixes = [\n 'webkit',\n 'moz',\n 'ms',\n ];\n const all_events = (event + vendor_prefixes.join(event) + event).trim();\n $(document).on(all_events, data, fullscreenchangeListener);\n}\n\n/**\n * Returns true if current device is mobile\n *\n * @returns {boolean}\n */\nexport function isMobile() {\n return (typeof window.orientation !== 'undefined') || (navigator.userAgent.indexOf('IEMobile') !== -1);\n}\n"],"names":["super_","isMobile","EVENT_NAMESPACE","jQuery","extend","BookReader","defaultOptions","enableVendorFullscreenPlugin","prototype","setup","options","call","this","isVendorFullscreenActive","getInitialMode","params","nextMode","constMode1up","init","fullscreenAllowed","bindFullscreenChangeListener","e","data","resize","updateBrClasses","cboxOverlay","$","cbox","isFullscreenActive","getFullscreenElement","append","document","body","enterFullWindow","_this","refs","$brContainer","css","window","width","onePageMinBreakpoint","switchMode","jumpToIndex","currentIndex","animate","opacity","on","concat","keyCode","exitFullScreen","exitFullWindow","off","isFullscreen","toggleFullscreen","exitFullscreen","requestFullscreen","$br","util","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullScreenEnabled","fullscreenchangeListener","event","all_events","join","trim","orientation","navigator","userAgent","indexOf"],"sourceRoot":""}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ export class SelectionObserver {
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @param {string} selector
|
|
12
|
-
* @param {function('started' | 'cleared' | '
|
|
12
|
+
* @param {function('started' | 'cleared' | 'changed', HTMLElement): any} handler
|
|
13
13
|
*/
|
|
14
14
|
constructor(selector, handler) {
|
|
15
15
|
this.selector = selector;
|
|
@@ -42,7 +42,7 @@ export class SelectionObserver {
|
|
|
42
42
|
|
|
43
43
|
if (this.selecting && (this.lastKnownFocusNode != sel.focusNode || sel.toString() && !sel.isCollapsed)) {
|
|
44
44
|
this.lastKnownFocusNode = sel.focusNode;
|
|
45
|
-
this.handler('
|
|
45
|
+
this.handler('changed', this.target);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (this.selecting && (sel.isCollapsed || !sel.toString() || !$(sel.anchorNode).closest(this.selector)[0])) {
|
package/src/BookReader.js
CHANGED
|
@@ -41,6 +41,7 @@ import { ModeThumb } from './BookReader/ModeThumb.js';
|
|
|
41
41
|
import { ImageCache } from './BookReader/ImageCache.js';
|
|
42
42
|
import { PageContainer } from './BookReader/PageContainer.js';
|
|
43
43
|
import { NAMED_REDUCE_SETS } from './BookReader/ReduceSet.js';
|
|
44
|
+
import {BookReaderTextFragment} from './util/TextSelectionManager.js';
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* BookReader
|
|
@@ -644,7 +645,6 @@ BookReader.prototype.init = function() {
|
|
|
644
645
|
this.pageScale = this.reduce; // preserve current reduce
|
|
645
646
|
|
|
646
647
|
const params = this.initParams();
|
|
647
|
-
|
|
648
648
|
this.firstIndex = params.index ? params.index : 0;
|
|
649
649
|
|
|
650
650
|
// Setup Navbars and other UI
|
|
@@ -1971,12 +1971,13 @@ BookReader.prototype.queryStringFromParams = function(
|
|
|
1971
1971
|
newParams.set('q', params.search);
|
|
1972
1972
|
}
|
|
1973
1973
|
|
|
1974
|
-
|
|
1974
|
+
/** @type {BookReaderTextFragment | null} */
|
|
1975
|
+
let textFragmentParam = null;
|
|
1975
1976
|
// Need to pull out text separately to avoid the spaces becoming encoded as +, which
|
|
1976
1977
|
// the browser seems not to handle with the text fragment
|
|
1977
1978
|
if (newParams.get('text')) {
|
|
1978
1979
|
newParams.delete('text');
|
|
1979
|
-
textFragmentParam =
|
|
1980
|
+
textFragmentParam = BookReaderTextFragment.fromUrl(currQueryString, this.book, this.firstIndex);
|
|
1980
1981
|
}
|
|
1981
1982
|
|
|
1982
1983
|
// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/toString
|
|
@@ -1984,7 +1985,7 @@ BookReader.prototype.queryStringFromParams = function(
|
|
|
1984
1985
|
let result = newParams.toString();
|
|
1985
1986
|
if (textFragmentParam) {
|
|
1986
1987
|
if (result) result += '&';
|
|
1987
|
-
result += textFragmentParam
|
|
1988
|
+
result += `text=${textFragmentParam.toUrlString()}`;
|
|
1988
1989
|
}
|
|
1989
1990
|
if (result) result = '?' + result;
|
|
1990
1991
|
|
package/src/css/_BRpages.scss
CHANGED
|
@@ -101,8 +101,11 @@ svg.BRPageLayer {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// Hides page layers during page flip animation
|
|
104
|
-
.BRpageFlipping
|
|
105
|
-
|
|
104
|
+
.BRpageFlipping {
|
|
105
|
+
// If the text layer has an annotation, don't hide it
|
|
106
|
+
.BRpagecontainer:not(:has(hypothesis-highlight)):not(:has(.BRhighlight)) .BRtextLayer {
|
|
107
|
+
display: none;
|
|
108
|
+
}
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
.br-mode-2up__root {
|
|
@@ -54,13 +54,6 @@
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Style URI TextFragments, eg #:~:text=example
|
|
58
|
-
.BRtextLayer ::target-text {
|
|
59
|
-
// Similar colour to the default one used in Safari, Firefox. Note Chrome uses a purple colour
|
|
60
|
-
background-color: hsla(45, 80%, 66%, 0.6);
|
|
61
|
-
color: transparent;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
57
|
.BRtranslateLayer ::selection {
|
|
65
58
|
background: hsla(210, 74%, 62%, 0.4);
|
|
66
59
|
}
|
|
@@ -82,7 +75,7 @@
|
|
|
82
75
|
// Hide text layer for performance during zooming & scrolling
|
|
83
76
|
.BRsmooth-zooming, .BRscrolling-active {
|
|
84
77
|
// If the text layer has an annotation, don't hide it
|
|
85
|
-
.BRpagecontainer:not(.BRpagecontainer--hasSelection):not(:has(hypothesis-highlight)) .BRtextLayer {
|
|
78
|
+
.BRpagecontainer:not(.BRpagecontainer--hasSelection):not(:has(hypothesis-highlight)):not(:has(.BRhighlight)) .BRtextLayer {
|
|
86
79
|
display: none;
|
|
87
80
|
}
|
|
88
81
|
}
|
|
@@ -156,6 +149,7 @@
|
|
|
156
149
|
display: none;
|
|
157
150
|
border-radius: 20px;
|
|
158
151
|
background-color: #333;
|
|
152
|
+
color: white;
|
|
159
153
|
color-scheme: dark;
|
|
160
154
|
padding: 2px;
|
|
161
155
|
overflow: hidden;
|
|
@@ -209,4 +203,22 @@
|
|
|
209
203
|
width: auto;
|
|
210
204
|
margin-left: 4px;
|
|
211
205
|
opacity: 1;
|
|
212
|
-
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.BRtextLayer .BRhighlight {
|
|
209
|
+
background-color: yellow;
|
|
210
|
+
pointer-events: all;
|
|
211
|
+
color: transparent;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@keyframes highlightTargetFlash {
|
|
215
|
+
to { background-color: hsla(45, 80%, 66%, 1); }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Style URI TextFragments, eg ?text=example
|
|
219
|
+
.BRtextLayer .BRhighlight--target-text, .BRtextLayer ::target-text {
|
|
220
|
+
// Similar colour to the default one used in Safari, Firefox. Note Chrome uses a purple colour
|
|
221
|
+
background-color: hsla(45, 80%, 66%, 0.6);
|
|
222
|
+
color: transparent;
|
|
223
|
+
animation: highlightTargetFlash 600ms 1 reverse;
|
|
224
|
+
}
|
|
@@ -302,8 +302,11 @@ export class IaBookReader extends LitElement {
|
|
|
302
302
|
providers.bookmarks = new BookmarksProvider({
|
|
303
303
|
...this.baseProviderConfig,
|
|
304
304
|
onProviderChange: (bookmarks) => {
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
if (Object.keys(bookmarks).length) {
|
|
306
|
+
this.addMenuShortcut('bookmarks');
|
|
307
|
+
} else {
|
|
308
|
+
this.removeMenuShortcut('bookmarks');
|
|
309
|
+
}
|
|
307
310
|
this.updateMenuContents();
|
|
308
311
|
},
|
|
309
312
|
});
|
|
@@ -463,6 +466,14 @@ export class IaBookReader extends LitElement {
|
|
|
463
466
|
this._sortMenuShortcuts();
|
|
464
467
|
}
|
|
465
468
|
|
|
469
|
+
/**
|
|
470
|
+
* @param {string} menuId
|
|
471
|
+
*/
|
|
472
|
+
removeMenuShortcut(menuId) {
|
|
473
|
+
this.menuShortcuts = this.menuShortcuts.filter((m) => m.id !== menuId);
|
|
474
|
+
this._sortMenuShortcuts();
|
|
475
|
+
}
|
|
476
|
+
|
|
466
477
|
/**
|
|
467
478
|
* Sorts the menuShortcuts property by comparing each provider's id to
|
|
468
479
|
* the id in each iteration over the shortcutOrder array.
|
|
@@ -7,8 +7,10 @@ import { sleep } from '../BookReader/utils.js';
|
|
|
7
7
|
// @ts-ignore
|
|
8
8
|
const BookReader = /** @type {typeof import('@/src/BookReader.js').default} */(window.BookReader);
|
|
9
9
|
|
|
10
|
+
/** @typedef {'copyLinkToHighlight' | 'annotateHighlight' | 'translate' | 'hypothesis'} ExperimentName */
|
|
11
|
+
|
|
10
12
|
class ExperimentModel {
|
|
11
|
-
/** @type {
|
|
13
|
+
/** @type {ExperimentName} */
|
|
12
14
|
name;
|
|
13
15
|
/** @type {string} */
|
|
14
16
|
title;
|
|
@@ -50,21 +52,40 @@ export class ExperimentsPlugin extends BookReaderPlugin {
|
|
|
50
52
|
/** Where the state of this plugin is saved in localStorage */
|
|
51
53
|
localStorageKey: 'BrExperiments',
|
|
52
54
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
+
/** @type {ExperimentName[]} Experiments shown in the experiments panel */
|
|
56
|
+
availableExperiments: ['translate', 'copyLinkToHighlight'],
|
|
57
|
+
|
|
58
|
+
/** @type {ExperimentName[]} Experiments enabled by default */
|
|
59
|
+
autoEnabledExperiments: [],
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
/** @type {ExperimentModel[]} */
|
|
58
63
|
allExperiments = [
|
|
59
64
|
new class extends ExperimentModel {
|
|
60
65
|
name = 'copyLinkToHighlight';
|
|
61
|
-
title = 'Copy to
|
|
62
|
-
description = '
|
|
63
|
-
learnMore = 'none';
|
|
66
|
+
title = 'Copy Link to Highlight';
|
|
67
|
+
description = 'Shareable link to a text selection';
|
|
64
68
|
icon = null;
|
|
65
69
|
enabled = false;
|
|
66
70
|
async enable ({ manual = false }) {
|
|
67
|
-
|
|
71
|
+
if (manual) {
|
|
72
|
+
this.br.plugins.textSelection.textSelectionManager.selectMenu.copyLinkToHighlightEnabled = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async disable() {
|
|
76
|
+
this.br.plugins.textSelection.textSelectionManager.selectMenu.copyLinkToHighlightEnabled = false;
|
|
77
|
+
}
|
|
78
|
+
}(),
|
|
79
|
+
new class extends ExperimentModel {
|
|
80
|
+
name = 'annotateHighlight';
|
|
81
|
+
title = 'Highlight and annotate';
|
|
82
|
+
description = 'Create private highlights and annotations for this book';
|
|
83
|
+
icon = null;
|
|
84
|
+
enabled = false;
|
|
85
|
+
async enable ({ manual = false }) {
|
|
86
|
+
if (manual) {
|
|
87
|
+
this.br.plugins.textSelection.textSelectionManager.selectMenu.highlightAnnotationEnabled = true;
|
|
88
|
+
}
|
|
68
89
|
}
|
|
69
90
|
async disable() {
|
|
70
91
|
sleep(0).then(() => {
|
|
@@ -142,7 +163,14 @@ export class ExperimentsPlugin extends BookReaderPlugin {
|
|
|
142
163
|
if (experiment.icon) {
|
|
143
164
|
experiment.icon = experiment.buildAssetPath(experiment.icon);
|
|
144
165
|
}
|
|
166
|
+
|
|
145
167
|
experiment.br = this.br;
|
|
168
|
+
|
|
169
|
+
// Enable any experiments that should be automatically enabled
|
|
170
|
+
if (!experiment.enabled && this.options.autoEnabledExperiments.includes(experiment.name)) {
|
|
171
|
+
experiment.enabled = true;
|
|
172
|
+
await experiment.enable({ manual: false });
|
|
173
|
+
}
|
|
146
174
|
}
|
|
147
175
|
|
|
148
176
|
this._loadExperimentStates();
|
|
@@ -150,6 +178,14 @@ export class ExperimentsPlugin extends BookReaderPlugin {
|
|
|
150
178
|
this._render();
|
|
151
179
|
}
|
|
152
180
|
|
|
181
|
+
/**
|
|
182
|
+
* @param {ExperimentName} experimentName
|
|
183
|
+
*/
|
|
184
|
+
isEnabled(experimentName) {
|
|
185
|
+
const experiment = this.allExperiments.find(exp => exp.name === experimentName);
|
|
186
|
+
return experiment?.enabled;
|
|
187
|
+
}
|
|
188
|
+
|
|
153
189
|
_loadExperimentStates() {
|
|
154
190
|
const savedStates = JSON.parse(localStorage.getItem(this.options.localStorageKey) || '{}');
|
|
155
191
|
this.allExperiments.forEach(experiment => {
|
|
@@ -196,7 +232,7 @@ export class ExperimentsPlugin extends BookReaderPlugin {
|
|
|
196
232
|
`,
|
|
197
233
|
label: 'Experiments',
|
|
198
234
|
component: html`<br-experiments-panel
|
|
199
|
-
.experiments="${this.allExperiments.filter(experiment => this.options.
|
|
235
|
+
.experiments="${this.allExperiments.filter(experiment => this.options.availableExperiments.includes(experiment.name))}"
|
|
200
236
|
@connected="${e => this._panel = e.target}"
|
|
201
237
|
@toggle="${async e => {
|
|
202
238
|
await this._toggleExperiment(e.detail.experiment, e.detail.enabled);
|
|
@@ -4,7 +4,7 @@ import { BookReaderPlugin } from '../BookReaderPlugin.js';
|
|
|
4
4
|
import { applyVariables } from '../util/strings.js';
|
|
5
5
|
import { Cache } from '../util/cache.js';
|
|
6
6
|
import { toISO6391 } from './tts/utils.js';
|
|
7
|
-
import { TextSelectionManager } from '../util/TextSelectionManager.js';
|
|
7
|
+
import { BookReaderTextFragment, renderHighlight, TextSelectionManager } from '../util/TextSelectionManager.js';
|
|
8
8
|
/** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
|
|
9
9
|
/** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
|
|
10
10
|
|
|
@@ -36,6 +36,8 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
36
36
|
*/
|
|
37
37
|
maxWordRendered = 2500;
|
|
38
38
|
|
|
39
|
+
_jumpedToHighlight = false;
|
|
40
|
+
|
|
39
41
|
/**
|
|
40
42
|
* @param {import('../BookReader.js').default} br
|
|
41
43
|
*/
|
|
@@ -46,7 +48,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
46
48
|
// now we do make that assumption.
|
|
47
49
|
/** Whether the book is right-to-left */
|
|
48
50
|
this.rtl = this.br.pageProgression === 'rl';
|
|
49
|
-
this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace']}, this.options.maxProtectedWords);
|
|
51
|
+
this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace', 'mark']}, this.options.maxProtectedWords);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/** @override */
|
|
@@ -54,18 +56,32 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
54
56
|
if (!this.options.enabled) return;
|
|
55
57
|
|
|
56
58
|
this.br.on('pageVisible', (_, {pageContainerEl}) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const textLayer = pageContainerEl.querySelector('.BRtextLayer');
|
|
60
|
+
if (textLayer) {
|
|
61
|
+
this.br.trigger('textLayerVisible', {pageContainerEl, textLayer});
|
|
59
62
|
}
|
|
60
63
|
});
|
|
61
64
|
|
|
62
65
|
this.loadData();
|
|
63
66
|
this.textSelectionManager.init();
|
|
64
|
-
}
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
68
|
+
// Init text fragment
|
|
69
|
+
this.targetTextFragment = BookReaderTextFragment.fromUrl(location.search, this.br.book, this.br.firstIndex);
|
|
70
|
+
if (this.targetTextFragment) {
|
|
71
|
+
const targetTextFragment = this.targetTextFragment;
|
|
72
|
+
this.br.on('textLayerVisible', async (_, {pageContainerEl, textLayer}) => {
|
|
73
|
+
const pageIndex = targetTextFragment.pageIndex;
|
|
74
|
+
const hasTargetText = pageIndex === parseFloat(pageContainerEl.getAttribute('data-index'));
|
|
75
|
+
if (hasTargetText) {
|
|
76
|
+
const markEls = renderHighlight(textLayer, targetTextFragment, 'BRhighlight--target-text');
|
|
77
|
+
// Only jump once; presumably on first page load.
|
|
78
|
+
if (!this._jumpedToHighlight) {
|
|
79
|
+
markEls[0].scrollIntoView({behavior: 'smooth', block: 'center'});
|
|
80
|
+
this._jumpedToHighlight = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
/**
|
|
@@ -76,7 +92,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
76
92
|
_configurePageContainer(pageContainer) {
|
|
77
93
|
// Disable if thumb mode; it's too janky
|
|
78
94
|
// .page can be null for "pre-cover" region
|
|
79
|
-
if (this.br.mode !== this.br.constModeThumb && pageContainer.page) {
|
|
95
|
+
if (this.br.mode !== this.br.constModeThumb && pageContainer.page?.isViewable) {
|
|
80
96
|
this.createTextLayer(pageContainer);
|
|
81
97
|
}
|
|
82
98
|
return pageContainer;
|
|
@@ -210,7 +226,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
210
226
|
|
|
211
227
|
// Check if page is visible
|
|
212
228
|
if ($container.hasClass('BRpage-visible')) {
|
|
213
|
-
this.br.trigger('textLayerVisible', {pageContainerEl: $container[0]});
|
|
229
|
+
this.br.trigger('textLayerVisible', {pageContainerEl: $container[0], textLayer});
|
|
214
230
|
}
|
|
215
231
|
}
|
|
216
232
|
|
|
@@ -575,49 +591,3 @@ class Rect {
|
|
|
575
591
|
get top() { return this.y; }
|
|
576
592
|
get left() { return this.x; }
|
|
577
593
|
}
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Depth traverse the DOM tree starting at `start`, and ending at `end`.
|
|
581
|
-
* @param {Node} start
|
|
582
|
-
* @param {Node} end
|
|
583
|
-
* @returns {Generator<Node>}
|
|
584
|
-
*/
|
|
585
|
-
export function* walkBetweenNodes(start, end) {
|
|
586
|
-
let done = false;
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* @param {Node} node
|
|
590
|
-
*/
|
|
591
|
-
function* walk(node, {children = true, parents = true, siblings = true} = {}) {
|
|
592
|
-
if (node === end) {
|
|
593
|
-
done = true;
|
|
594
|
-
yield node;
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// yield self
|
|
599
|
-
yield node;
|
|
600
|
-
|
|
601
|
-
// First iterate children (depth-first traversal)
|
|
602
|
-
if (children && node.firstChild) {
|
|
603
|
-
yield* walk(node.firstChild, {children: true, parents: false, siblings: true});
|
|
604
|
-
if (done) return;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Then iterate siblings
|
|
608
|
-
if (siblings) {
|
|
609
|
-
for (let sib = node.nextSibling; sib; sib = sib.nextSibling) {
|
|
610
|
-
yield* walk(sib, {children: true, parents: false, siblings: false});
|
|
611
|
-
if (done) return;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Finally, move up the tree
|
|
616
|
-
if (parents && node.parentNode) {
|
|
617
|
-
yield* walk(node.parentNode, {children: false, parents: true, siblings: true});
|
|
618
|
-
if (done) return;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
yield* walk(start);
|
|
623
|
-
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import FestivalTTSEngine from './FestivalTTSEngine.js';
|
|
4
4
|
import WebTTSEngine from './WebTTSEngine.js';
|
|
5
|
-
import { toISO6391
|
|
5
|
+
import { toISO6391 } from './utils.js';
|
|
6
6
|
import { en as tooltips } from './tooltip_dict.js';
|
|
7
7
|
import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
|
|
8
8
|
import { BookReaderPlugin } from '../../BookReaderPlugin.js';
|
|
9
|
-
import { applyVariables } from '../../util/strings.js';
|
|
9
|
+
import { applyVariables, countWords } from '../../util/strings.js';
|
|
10
10
|
/** @typedef {import('./PageChunk.js').default} PageChunk */
|
|
11
11
|
/** @typedef {import("./AbstractTTSEngine.js").default} AbstractTTSEngine */
|
|
12
12
|
|
|
@@ -291,7 +291,7 @@ export class TtsPlugin extends BookReaderPlugin {
|
|
|
291
291
|
* @param {PageChunk} chunk
|
|
292
292
|
*/
|
|
293
293
|
sendChunkFinishedAnalyticsEvent(chunk) {
|
|
294
|
-
this.sendAnalyticsEvent('ChunkFinished-Words',
|
|
294
|
+
this.sendAnalyticsEvent('ChunkFinished-Words', countWords(chunk.text));
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
/**
|
package/src/plugins/tts/utils.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import langs from 'iso-language-codes';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Use regex to approximate word count in a string
|
|
6
|
-
* @param {string} text
|
|
7
|
-
* @return {number}
|
|
8
|
-
*/
|
|
9
|
-
export function approximateWordCount(text) {
|
|
10
|
-
const m = text.match(/\S+/g);
|
|
11
|
-
return m ? m.length : 0;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
4
|
/**
|
|
15
5
|
* Checks whether the current browser is on android
|
|
16
6
|
* @param {string} [userAgent]
|
|
@@ -190,22 +190,9 @@ export class UrlPlugin {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
|
-
* Get the hash out of the current URL
|
|
194
|
-
* from the main part of the URL, since that is not readable by JS
|
|
195
|
-
* from the actual hash
|
|
196
|
-
* @returns
|
|
193
|
+
* Get the hash out of the current URL
|
|
197
194
|
*/
|
|
198
195
|
getHash() {
|
|
199
|
-
|
|
200
|
-
const textFragment = text ? `:~:text=${text[0]}` : '';
|
|
201
|
-
return `${window.location.hash.slice(1)}${textFragment}`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* @param {string} urlString
|
|
206
|
-
* @returns {string}
|
|
207
|
-
*/
|
|
208
|
-
retrieveTextFragment(urlString) {
|
|
209
|
-
return urlString.match(/(?<=[&?]?text=)[^&]*/);
|
|
196
|
+
return window.location.hash.slice(1);
|
|
210
197
|
}
|
|
211
198
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* global BookReader */
|
|
2
2
|
|
|
3
3
|
import { UrlPlugin } from "./UrlPlugin.js";
|
|
4
|
-
import { sleep } from "../../BookReader/utils.js";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Plugin for URL management in BookReader
|
|
@@ -43,10 +42,6 @@ BookReader.prototype.setup = (function(super_) {
|
|
|
43
42
|
this.locationPollId = null;
|
|
44
43
|
this.oldLocationHash = null;
|
|
45
44
|
this.oldUserHash = null;
|
|
46
|
-
// Should include the :~:text= prefix
|
|
47
|
-
this.textFragment = null;
|
|
48
|
-
// Tracks the original textFragment page num when first loaded
|
|
49
|
-
this.textFragmentPage = null;
|
|
50
45
|
};
|
|
51
46
|
})(BookReader.prototype.setup);
|
|
52
47
|
|
|
@@ -146,22 +141,16 @@ BookReader.prototype.urlUpdateFragment = function() {
|
|
|
146
141
|
}, {});
|
|
147
142
|
|
|
148
143
|
// eg 'page/3/mode/2up'; no query params (in hash mode, it might have /search/term)
|
|
149
|
-
// Does NOT have the :~:text fragment
|
|
150
144
|
const newFragment = this.fragmentFromParams(params, this.options.urlMode);
|
|
151
145
|
const newFragmentWithSlash = newFragment === '' ? '' : `/${newFragment}`;
|
|
152
146
|
// eg 'page/3/mode/2up'; no query params
|
|
153
|
-
// WILL CONTAIN the :~:text fragment in hash mode (!)
|
|
154
147
|
const currFragment = this.urlReadFragment();
|
|
155
148
|
// This should have both ?q=foo&text=bar (and any other params) as an encoded string
|
|
156
149
|
const currQueryString = this.getLocationSearch();
|
|
157
150
|
// Eg ?q=foo&text=bar; only query params, no fragment
|
|
158
151
|
const newQueryString = this.queryStringFromParams(params, currQueryString, this.options.urlMode);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// not desireable, but currently don't have a way to handle re-writing ?text to the hash text
|
|
162
|
-
// fragment form, :~:text=foo.
|
|
163
|
-
const hasTextParam = this.urlPlugin.retrieveTextFragment(currQueryString);
|
|
164
|
-
if (currFragment === newFragment && currQueryString === newQueryString && !hasTextParam) {
|
|
152
|
+
// Avoid infinite loop if there are no changes
|
|
153
|
+
if (currFragment === newFragment && currQueryString === newQueryString) {
|
|
165
154
|
return;
|
|
166
155
|
}
|
|
167
156
|
|
|
@@ -170,19 +159,11 @@ BookReader.prototype.urlUpdateFragment = function() {
|
|
|
170
159
|
this.options.urlMode = 'hash';
|
|
171
160
|
} else {
|
|
172
161
|
const baseWithoutSlash = this.options.urlHistoryBasePath.replace(/\/+$/, '');
|
|
173
|
-
const textFragment = this.urlPlugin.retrieveTextFragment(newQueryString);
|
|
174
162
|
const newUrlPath = `${baseWithoutSlash}${newFragmentWithSlash}${newQueryString}`;
|
|
175
|
-
|
|
176
|
-
if (!this.textFragmentPage && textFragment) {
|
|
177
|
-
this.textFragmentPage = extractedPage ? extractedPage : null;
|
|
178
|
-
this.textFragment = `:~:text=${textFragment}`;
|
|
179
|
-
}
|
|
163
|
+
|
|
180
164
|
try {
|
|
181
165
|
window.history.replaceState({}, null, newUrlPath);
|
|
182
166
|
this.oldLocationHash = newFragment + newQueryString;
|
|
183
|
-
if (textFragment) {
|
|
184
|
-
this.oldLocationHash += `:~:text=${textFragment[0]}`;
|
|
185
|
-
}
|
|
186
167
|
} catch (e) {
|
|
187
168
|
// DOMException on Chrome when in sandboxed iframe
|
|
188
169
|
this.options.urlMode = 'hash';
|
|
@@ -192,22 +173,8 @@ BookReader.prototype.urlUpdateFragment = function() {
|
|
|
192
173
|
|
|
193
174
|
if (this.options.urlMode === 'hash') {
|
|
194
175
|
const newQueryStringSearch = this.urlParamsFiltersOnlySearch(this.readQueryString());
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (textFragment) {
|
|
199
|
-
textFragment = `:~:text=${textFragment[0]}`;
|
|
200
|
-
} else {
|
|
201
|
-
textFragment = '';
|
|
202
|
-
}
|
|
203
|
-
if (!this.textFragmentPage && textFragment) {
|
|
204
|
-
this.textFragmentPage = extractedPage ? extractedPage : null;
|
|
205
|
-
this.textFragment = textFragment;
|
|
206
|
-
} else if (this.textFragmentPage && extractedPage != this.textFragmentPage) {
|
|
207
|
-
textFragment = '';
|
|
208
|
-
}
|
|
209
|
-
window.location.replace('#' + newFragment + newQueryStringSearch + textFragment);
|
|
210
|
-
this.oldLocationHash = newFragment + newQueryStringSearch + textFragment;
|
|
176
|
+
window.location.replace('#' + newFragment + newQueryStringSearch);
|
|
177
|
+
this.oldLocationHash = newFragment + newQueryStringSearch;
|
|
211
178
|
}
|
|
212
179
|
};
|
|
213
180
|
|
|
@@ -248,28 +215,9 @@ export class BookreaderUrlPlugin extends BookReader {
|
|
|
248
215
|
init() {
|
|
249
216
|
if (this.options.enableUrlPlugin) {
|
|
250
217
|
this.urlPlugin = new UrlPlugin(this.options);
|
|
251
|
-
const location = this.getLocationSearch();
|
|
252
|
-
if (location.includes("text=")) {
|
|
253
|
-
this.on('textLayerVisible', async (_, {pageContainerEl}) => {
|
|
254
|
-
const visiblePageNum = pageContainerEl.getAttribute('data-page-num');
|
|
255
|
-
|
|
256
|
-
// Hack: More time mode 1up page "settle down" from user scrolling
|
|
257
|
-
await sleep(this.mode === 1 ? 900 : 100);
|
|
258
|
-
|
|
259
|
-
// No textFragment found or the textFragment stored doesn't match current visible page loaded
|
|
260
|
-
if (!this.textFragment || this.textFragmentPage !== visiblePageNum) return;
|
|
261
|
-
if (this.options.urlMode === 'history') {
|
|
262
|
-
window.location.replace(`#${this.textFragment}`);
|
|
263
|
-
} else {
|
|
264
|
-
// for urlMode hash, textFragment is stored in oldLocationHash already
|
|
265
|
-
window.location.replace(`#${this.oldLocationHash}`);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
this.bind(BookReader.eventNames.PostInit, () => {
|
|
270
|
-
const { urlMode } = this.options;
|
|
271
218
|
|
|
272
|
-
|
|
219
|
+
this.on(BookReader.eventNames.PostInit, () => {
|
|
220
|
+
if (this.options.urlMode === 'hash') {
|
|
273
221
|
this.urlPlugin.listenForHashChanges();
|
|
274
222
|
}
|
|
275
223
|
});
|