@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.
Files changed (36) hide show
  1. package/BookReader/BookReader.css +20 -7
  2. package/BookReader/BookReader.js +43 -1
  3. package/BookReader/BookReader.js.map +1 -1
  4. package/BookReader/ia-bookreader-bundle.js +3 -45
  5. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  6. package/BookReader/plugins/plugin.chapters.js +2 -2
  7. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  8. package/BookReader/plugins/plugin.experiments.js +1 -1
  9. package/BookReader/plugins/plugin.experiments.js.map +1 -1
  10. package/BookReader/plugins/plugin.search.js +1 -1
  11. package/BookReader/plugins/plugin.search.js.map +1 -1
  12. package/BookReader/plugins/plugin.text_selection.js +1 -23
  13. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  14. package/BookReader/plugins/plugin.translate.js +3 -25
  15. package/BookReader/plugins/plugin.translate.js.map +1 -1
  16. package/BookReader/plugins/plugin.tts.js +1 -1
  17. package/BookReader/plugins/plugin.tts.js.map +1 -1
  18. package/BookReader/plugins/plugin.url.js +1 -2
  19. package/BookReader/plugins/plugin.url.js.map +1 -1
  20. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  21. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/BookReader/utils/SelectionObserver.js +2 -2
  24. package/src/BookReader.js +5 -4
  25. package/src/css/_BRpages.scss +5 -2
  26. package/src/css/_TextSelection.scss +21 -9
  27. package/src/ia-bookreader/ia-bookreader.js +13 -2
  28. package/src/plugins/plugin.experiments.js +44 -8
  29. package/src/plugins/plugin.text_selection.js +26 -56
  30. package/src/plugins/tts/plugin.tts.js +3 -3
  31. package/src/plugins/tts/utils.js +0 -10
  32. package/src/plugins/url/UrlPlugin.js +2 -15
  33. package/src/plugins/url/plugin.url.js +7 -59
  34. package/src/util/TextSelectionManager.js +950 -138
  35. package/src/util/strings.js +10 -0
  36. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-112",
3
+ "version": "5.0.0-114",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "type": "module",
6
6
  "files": [
@@ -9,7 +9,7 @@ export class SelectionObserver {
9
9
 
10
10
  /**
11
11
  * @param {string} selector
12
- * @param {function('started' | 'cleared' | 'focusChanged', HTMLElement): any} handler
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('focusChanged', this.target);
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
- let textFragmentParam = '';
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 = `text=${this.urlPlugin.retrieveTextFragment(currQueryString)}`;
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
 
@@ -101,8 +101,11 @@ svg.BRPageLayer {
101
101
  }
102
102
 
103
103
  // Hides page layers during page flip animation
104
- .BRpageFlipping .BRtextLayer {
105
- display: none;
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
- const method = Object.keys(bookmarks).length ? 'add' : 'remove';
306
- this[`${method}MenuShortcut`]('bookmarks');
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 {string} test */
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
- /** The experiments that should be shown in the experiments panel */
54
- enabledExperiments: ['translate', 'copyLinkToHighlight'],
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 Selection URL';
62
- description = 'Share text selection via URL';
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
- this.br.plugins.textSelection.enableSelectionMenu();
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.enabledExperiments.includes(experiment.name))}"
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
- if (pageContainerEl.querySelector('.BRtextLayer')) {
58
- this.br.trigger('textLayerVisible', {pageContainerEl});
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
- enableSelectionMenu() {
67
- this.textSelectionManager.selectionMenuEnabled = true;
68
- this.textSelectionManager.renderSelectionMenu();
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, approximateWordCount } from './utils.js';
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', approximateWordCount(chunk.text));
294
+ this.sendAnalyticsEvent('ChunkFinished-Words', countWords(chunk.text));
295
295
  }
296
296
 
297
297
  /**
@@ -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. Also augments it with the text
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
- const text = this.retrieveTextFragment(window.location.search);
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
- // NOTE: If ?text is in the URL, we will fire fragment change events on every render; which is
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
- const extractedPage = this.urlPlugin.urlStringToUrlState(newFragmentWithSlash)?.page;
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
- let textFragment = this.urlPlugin.retrieveTextFragment(this.readQueryString());
196
- const extractedPage = this.urlPlugin.urlStringToUrlState(newFragmentWithSlash)?.page;
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
- if (urlMode === 'hash') {
219
+ this.on(BookReader.eventNames.PostInit, () => {
220
+ if (this.options.urlMode === 'hash') {
273
221
  this.urlPlugin.listenForHashChanges();
274
222
  }
275
223
  });