@internetarchive/bookreader 5.0.0-111 → 5.0.0-113

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 (35) hide show
  1. package/BookReader/BookReader.css +13 -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/Navbar/Navbar.js +9 -10
  24. package/src/BookReader/utils/SelectionObserver.js +2 -2
  25. package/src/BookReader.js +5 -4
  26. package/src/css/_BRpages.scss +5 -2
  27. package/src/css/_TextSelection.scss +15 -9
  28. package/src/plugins/plugin.experiments.js +31 -5
  29. package/src/plugins/plugin.text_selection.js +19 -56
  30. package/src/plugins/translate/plugin.translate.js +68 -22
  31. package/src/plugins/tts/utils.js +1 -1
  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 +834 -137
  35. 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-111",
3
+ "version": "5.0.0-113",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "type": "module",
6
6
  "files": [
@@ -314,7 +314,7 @@ export class Navbar {
314
314
  const $sliders = this.$root.find('.BRpager').slider({
315
315
  animate: true,
316
316
  min: 0,
317
- max: br.book.getNumLeafs() - 1,
317
+ max: Math.max(0, br.book.getNumLeafs() - 1),
318
318
  value: br.currentIndex(),
319
319
  range: "min",
320
320
  });
@@ -324,8 +324,8 @@ export class Navbar {
324
324
  $sliders.find('.ui-slider-handle').attr({
325
325
  'role': 'slider',
326
326
  'aria-label': 'Page navigation slider',
327
- 'aria-valuemin': 1,
328
- 'aria-valuemax': br.book.getNumLeafs(),
327
+ 'aria-valuemin': 0,
328
+ 'aria-valuemax': Math.max(0, br.book.getNumLeafs() - 1),
329
329
  });
330
330
 
331
331
  // Ignore up/down arrow keys and page up/down keys, since they're confusingly different
@@ -396,7 +396,6 @@ export class Navbar {
396
396
  */
397
397
  getNavPageNumString(index) {
398
398
  const { br } = this;
399
- // Accessible index starts at 0 (alas) so we add 1 to make human
400
399
  const pageNum = br.book.getPageNum(index);
401
400
  const pageType = br.book.getPageProp(index, 'pageType');
402
401
  const numLeafs = br.book.getNumLeafs();
@@ -425,7 +424,7 @@ export class Navbar {
425
424
  updateNavPageNum(index) {
426
425
  this.$root.find('.BRcurrentpage').html(this.getNavPageNumString(index));
427
426
  this.$root.find('.ui-slider-handle').attr({
428
- 'aria-valuenow': index + 1,
427
+ 'aria-valuenow': index,
429
428
  'aria-valuetext': this.getNavPageNumString(index),
430
429
  });
431
430
  }
@@ -446,19 +445,19 @@ export class Navbar {
446
445
 
447
446
  /**
448
447
  * Renders the html for the page string
449
- * @param {number} index
450
- * @param {number} numLeafs
448
+ * @param {PageIndex} index 0-indexed page index (0..numLeafs-1)
449
+ * @param {number} numLeafs total number of leaves
451
450
  * @param {number|string} pageNum
452
451
  * @param {*} pageType - Deprecated
453
452
  * @param {number} maxPageNum
454
- * @return {string}
453
+ * @return {string} e.g. "Page 14 (3/39)"
455
454
  */
456
455
  export function getNavPageNumHtml(index, numLeafs, pageNum, pageType, maxPageNum) {
457
456
  const pageIsAsserted = pageNum[0] != 'n';
458
- const pageIndex = index + 1;
459
457
 
460
458
  if (!pageIsAsserted) {
461
459
  pageNum = '—';
462
460
  }
463
- return `Page ${pageNum} (${pageIndex}/${numLeafs})`;
461
+ const lastIndex = Math.max(0, numLeafs - 1);
462
+ return `Page ${pageNum} (${index}/${lastIndex})`;
464
463
  }
@@ -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
  }
@@ -209,4 +202,17 @@
209
202
  width: auto;
210
203
  margin-left: 4px;
211
204
  opacity: 1;
212
- }
205
+ }
206
+
207
+ .BRtextLayer .BRhighlight {
208
+ background-color: yellow;
209
+ pointer-events: all;
210
+ color: transparent;
211
+ }
212
+
213
+ // Style URI TextFragments, eg #:~:text=example
214
+ .BRtextLayer .BRhighlight--target-text, .BRtextLayer ::target-text {
215
+ // Similar colour to the default one used in Safari, Firefox. Note Chrome uses a purple colour
216
+ background-color: hsla(45, 80%, 66%, 0.6);
217
+ color: transparent;
218
+ }
@@ -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;
@@ -58,13 +60,29 @@ export class ExperimentsPlugin extends BookReaderPlugin {
58
60
  allExperiments = [
59
61
  new class extends ExperimentModel {
60
62
  name = 'copyLinkToHighlight';
61
- title = 'Copy to Selection URL';
62
- description = 'Share text selection via URL';
63
- learnMore = 'none';
63
+ title = 'Copy Link to Highlight';
64
+ description = 'Shareable link to a text selection';
64
65
  icon = null;
65
66
  enabled = false;
66
67
  async enable ({ manual = false }) {
67
- this.br.plugins.textSelection.enableSelectionMenu();
68
+ if (manual) {
69
+ this.br.plugins.textSelection.textSelectionManager.selectMenu.copyLinkToHighlightEnabled = true;
70
+ }
71
+ }
72
+ async disable() {
73
+ this.br.plugins.textSelection.textSelectionManager.selectMenu.copyLinkToHighlightEnabled = false;
74
+ }
75
+ }(),
76
+ new class extends ExperimentModel {
77
+ name = 'annotateHighlight';
78
+ title = 'Highlight and annotate';
79
+ description = 'Create private highlights and annotations for this book';
80
+ icon = null;
81
+ enabled = false;
82
+ async enable ({ manual = false }) {
83
+ if (manual) {
84
+ this.br.plugins.textSelection.textSelectionManager.selectMenu.highlightAnnotationEnabled = true;
85
+ }
68
86
  }
69
87
  async disable() {
70
88
  sleep(0).then(() => {
@@ -150,6 +168,14 @@ export class ExperimentsPlugin extends BookReaderPlugin {
150
168
  this._render();
151
169
  }
152
170
 
171
+ /**
172
+ * @param {ExperimentName} experimentName
173
+ */
174
+ isEnabled(experimentName) {
175
+ const experiment = this.allExperiments.find(exp => exp.name === experimentName);
176
+ return experiment?.enabled;
177
+ }
178
+
153
179
  _loadExperimentStates() {
154
180
  const savedStates = JSON.parse(localStorage.getItem(this.options.localStorageKey) || '{}');
155
181
  this.allExperiments.forEach(experiment => {
@@ -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
 
@@ -46,7 +46,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
46
46
  // now we do make that assumption.
47
47
  /** Whether the book is right-to-left */
48
48
  this.rtl = this.br.pageProgression === 'rl';
49
- this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace']}, this.options.maxProtectedWords);
49
+ this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace', 'mark']}, this.options.maxProtectedWords);
50
50
  }
51
51
 
52
52
  /** @override */
@@ -54,18 +54,27 @@ export class TextSelectionPlugin extends BookReaderPlugin {
54
54
  if (!this.options.enabled) return;
55
55
 
56
56
  this.br.on('pageVisible', (_, {pageContainerEl}) => {
57
- if (pageContainerEl.querySelector('.BRtextLayer')) {
58
- this.br.trigger('textLayerVisible', {pageContainerEl});
57
+ const textLayer = pageContainerEl.querySelector('.BRtextLayer');
58
+ if (textLayer) {
59
+ this.br.trigger('textLayerVisible', {pageContainerEl, textLayer});
59
60
  }
60
61
  });
61
62
 
62
63
  this.loadData();
63
64
  this.textSelectionManager.init();
64
- }
65
65
 
66
- enableSelectionMenu() {
67
- this.textSelectionManager.selectionMenuEnabled = true;
68
- this.textSelectionManager.renderSelectionMenu();
66
+ // Init text fragment
67
+ this.targetTextFragment = BookReaderTextFragment.fromUrl(location.search, this.br.book, this.br.firstIndex);
68
+ if (this.targetTextFragment) {
69
+ const targetTextFragment = this.targetTextFragment;
70
+ this.br.on('textLayerVisible', async (_, {pageContainerEl, textLayer}) => {
71
+ const pageIndex = targetTextFragment.pageIndex;
72
+ const hasTargetText = pageIndex === parseFloat(pageContainerEl.getAttribute('data-index'));
73
+ if (hasTargetText) {
74
+ renderHighlight(textLayer, targetTextFragment, 'BRhighlight--target-text');
75
+ }
76
+ });
77
+ }
69
78
  }
70
79
 
71
80
  /**
@@ -76,7 +85,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
76
85
  _configurePageContainer(pageContainer) {
77
86
  // Disable if thumb mode; it's too janky
78
87
  // .page can be null for "pre-cover" region
79
- if (this.br.mode !== this.br.constModeThumb && pageContainer.page) {
88
+ if (this.br.mode !== this.br.constModeThumb && pageContainer.page?.isViewable) {
80
89
  this.createTextLayer(pageContainer);
81
90
  }
82
91
  return pageContainer;
@@ -210,7 +219,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
210
219
 
211
220
  // Check if page is visible
212
221
  if ($container.hasClass('BRpage-visible')) {
213
- this.br.trigger('textLayerVisible', {pageContainerEl: $container[0]});
222
+ this.br.trigger('textLayerVisible', {pageContainerEl: $container[0], textLayer});
214
223
  }
215
224
  }
216
225
 
@@ -575,49 +584,3 @@ class Rect {
575
584
  get top() { return this.y; }
576
585
  get left() { return this.x; }
577
586
  }
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
- }
@@ -1,9 +1,9 @@
1
1
  // @ts-check
2
- import { html, LitElement } from 'lit';
2
+ import { css, html, LitElement } from 'lit';
3
3
  import { BookReaderPlugin } from '../../BookReaderPlugin.js';
4
- import { customElement, property } from 'lit/decorators.js';
4
+ import { customElement, property, query } from 'lit/decorators.js';
5
5
  import { TranslationManager } from "./TranslationManager.js";
6
- import { toISO6391 } from '../tts/utils.js';
6
+ import { toISO6391, toNativeName } from '../tts/utils.js';
7
7
  import { sortBy } from '../../../src/BookReader/utils.js';
8
8
  import { TextSelectionManager } from '../../../src/util/TextSelectionManager.js';
9
9
  import '@internetarchive/ia-activity-indicator';
@@ -63,14 +63,12 @@ export class TranslatePlugin extends BookReaderPlugin {
63
63
  textSelectionManager = new TextSelectionManager('.BRtranslateLayer', this.br, {selectionElement: [".BRlineElement"]}, 1);
64
64
 
65
65
  async init() {
66
- const currentLanguage = toISO6391(this.br.options.bookLanguage.replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, ""));
66
+ if (!this.options.enabled) return;
67
+
68
+ const bookLanguage = this.br.options.bookLanguage || '';
69
+ const currentLanguage = toISO6391(bookLanguage);
67
70
  this.langFromCode = currentLanguage ?? "en";
68
71
  this.textSelectionManager.init();
69
-
70
- if (!this.options.enabled) {
71
- return;
72
- }
73
-
74
72
  this.translationManager.publicPath = this.br.options.imagesBaseURL.replace(/\/+$/, '') + '/..';
75
73
 
76
74
  /**
@@ -307,6 +305,14 @@ export class TranslatePlugin extends BookReaderPlugin {
307
305
  }
308
306
 
309
307
  handleToggleTranslation = async () => {
308
+ const shouldEnableTranslation = !this.userToggleTranslate;
309
+ if (shouldEnableTranslation && !this.isSourceLanguageSupported()) {
310
+ this.userToggleTranslate = false;
311
+ this.translationManager.active = false;
312
+ this._render();
313
+ return;
314
+ }
315
+
310
316
  this.userToggleTranslate = !this.userToggleTranslate;
311
317
  this.translationManager.active = this.userToggleTranslate;
312
318
 
@@ -325,6 +331,10 @@ export class TranslatePlugin extends BookReaderPlugin {
325
331
  /**
326
332
  * Update translation side menu
327
333
  */
334
+ isSourceLanguageSupported() {
335
+ return this.translationManager.fromLanguages.some((lang) => lang.code == this.langFromCode);
336
+ }
337
+
328
338
  _render() {
329
339
  this.br.shell.menuProviders['translate'] = {
330
340
  id: 'translate',
@@ -339,6 +349,7 @@ export class TranslatePlugin extends BookReaderPlugin {
339
349
  this._panel.detectedToLang = this.langToCode;
340
350
  this._panel.detectedFromLang = this.langFromCode;
341
351
  this._panel.loadingModel = this.loadingModel;
352
+ this._panel.sourceLanguageSupported = this.isSourceLanguageSupported();
342
353
  }
343
354
  }"
344
355
  @langFromChanged="${this.handleFromLangChange}"
@@ -351,6 +362,7 @@ export class TranslatePlugin extends BookReaderPlugin {
351
362
  .detectedFromLang=${this.langFromCode}
352
363
  .detectedToLang=${this.langToCode}
353
364
  .loadingModel=${this.loadingModel}
365
+ .sourceLanguageSupported=${this.isSourceLanguageSupported()}
354
366
  class="translate-panel"
355
367
  />`,
356
368
  };
@@ -369,12 +381,27 @@ export class BrTranslatePanel extends LitElement {
369
381
  @property({ type: String }) detectedFromLang = '';
370
382
  @property({ type: String }) detectedToLang = '';
371
383
  @property({ type: Boolean }) loadingModel;
384
+ @property({ type: Boolean }) sourceLanguageSupported = true;
385
+
386
+ @query('#lang-from') langFromDropdown;
387
+ @query('#lang-to') langToDropdown;
388
+
389
+ static styles = css`
390
+ .disclaimer {
391
+ padding: 10px;
392
+ border-radius: 8px;
393
+ font-size: 12px;
394
+ margin: 0 0 10px;
395
+ background-color: rgba(255, 255, 255, 0.1);
396
+ color: rgba(255, 255, 255, 0.9);
397
+ }
372
398
 
373
- /** @override */
374
- createRenderRoot() {
375
- // Disable shadow DOM; that would require a huge rejiggering of CSS
376
- return this;
377
- }
399
+ .disclaimer--warning {
400
+ margin: 10px 0;
401
+ background-color: rgba(255, 186, 8, 0.18);
402
+ color: #ffe7a3;
403
+ }
404
+ `;
378
405
 
379
406
  connectedCallback() {
380
407
  super.connectedCallback();
@@ -382,11 +409,12 @@ export class BrTranslatePanel extends LitElement {
382
409
  }
383
410
 
384
411
  render() {
412
+ const statusWarning = this._statusWarning();
413
+
385
414
  return html`<div class="app" style="margin-top: 5%;padding-right: 5px;">
386
415
  <div
387
416
  class="disclaimer"
388
417
  id="disclaimerMessage"
389
- style="background-color: rgba(255,255,255,0.1);padding: 10px;border-radius: 8px;font-size: 12px;margin-bottom: 10px;color: rgba(255,255,255, 0.9);"
390
418
  >${this.disclaimerMessage}</div>
391
419
 
392
420
  <div class="panel panel--to" style="padding: 0 10px;">
@@ -405,7 +433,7 @@ export class BrTranslatePanel extends LitElement {
405
433
  </div>
406
434
 
407
435
  <div class="panel panel--start" style="text-align: right;padding: 0 10px;/*! font-size: 18px; */margin-top: 10px;">
408
- <button class="start-translation-brn" @click="${this._toggleTranslation}">
436
+ <button class="start-translation-brn" @click="${this._toggleTranslation}" ?disabled=${!this.sourceLanguageSupported}>
409
437
  ${this.userTranslationActive ? "Stop Translating" : "Translate"}
410
438
  </button>
411
439
  </div>
@@ -426,9 +454,14 @@ export class BrTranslatePanel extends LitElement {
426
454
  })
427
455
  }
428
456
  </select>
429
- </details>
457
+ </details>
458
+ </div>
430
459
  <div class="footer" id="status" style="margin-top:5%">
431
- ${this._statusWarning()}
460
+ ${
461
+ statusWarning ? html`
462
+ <div class="disclaimer disclaimer--warning">${statusWarning}</div>
463
+ ` : ''
464
+ }
432
465
  </div>
433
466
 
434
467
  <div class="lang-models-loading">
@@ -469,17 +502,27 @@ export class BrTranslatePanel extends LitElement {
469
502
  }
470
503
 
471
504
  _getSelectedLang(type) {
472
- /** @type {HTMLSelectElement} */
473
- const dropdown = this.querySelector(`#lang-${type}`);
505
+ const dropdown = type === 'from' ? this.langFromDropdown : this.langToDropdown;
474
506
  return dropdown ? dropdown.value : '';
475
507
  }
476
508
 
509
+ /**
510
+ * Resolves a human-readable language name for a language code.
511
+ * Prefers names from loaded translation language lists, then falls back to
512
+ * ISO language metadata via toNativeName.
513
+ * @param {string} code
514
+ * @returns {string}
515
+ */
477
516
  _getLangName(code) {
478
- const lang = [...this.fromLanguages, ...this.toLanguages].find(lang => lang.code === code);
479
- return lang ? lang.name : '';
517
+ const lang = [...this.fromLanguages, ...this.toLanguages]
518
+ .find(lang => lang.code === code);
519
+ return lang?.name || toNativeName(code) || code;
480
520
  }
481
521
 
482
522
  _toggleTranslation(event) {
523
+ if (!this.sourceLanguageSupported) {
524
+ return;
525
+ }
483
526
  const toggleTranslateEvent = new CustomEvent('toggleTranslation', {
484
527
  detail: {value: event.target.value},
485
528
  bubbles: true,
@@ -491,6 +534,9 @@ export class BrTranslatePanel extends LitElement {
491
534
 
492
535
  // TODO: Hardcoded warning message for now but should add more statuses
493
536
  _statusWarning() {
537
+ if (!this.sourceLanguageSupported) {
538
+ return `Translating from ${this._getLangName(this.detectedFromLang)} is not supported`;
539
+ }
494
540
  if (this.detectedFromLang == this.detectedToLang) {
495
541
  return "Translate To language is the same as the Source language";
496
542
  }
@@ -53,7 +53,7 @@ export function toISO6391(language) {
53
53
  /**
54
54
  *
55
55
  * @param {string} language
56
- * @returns {string}
56
+ * @returns {string | null}
57
57
  */
58
58
  export function toNativeName(language) {
59
59
  if (!language) return null;
@@ -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
  }