@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.
- package/BookReader/BookReader.css +13 -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/Navbar/Navbar.js +9 -10
- 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 +15 -9
- package/src/plugins/plugin.experiments.js +31 -5
- package/src/plugins/plugin.text_selection.js +19 -56
- package/src/plugins/translate/plugin.translate.js +68 -22
- package/src/plugins/tts/utils.js +1 -1
- package/src/plugins/url/UrlPlugin.js +2 -15
- package/src/plugins/url/plugin.url.js +7 -59
- package/src/util/TextSelectionManager.js +834 -137
- 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":"mKAcyCA,E,oBATzC,IAAKC,IAAY,CACf,IAAMC,EAAkB,gCAExBC,EAAOC,OAAOC,WAAWC,eAAgB,CAEvCC,8BAA8B,IAIhCF,WAAWG,UAAUC,OAAkBT,EAMpCK,WAAWG,UAAUC,MALf,SAASC,GACdV,EAAOW,KAAKC,KAAMF,GAElBE,KAAKC,0BAA2B,CAClC,GAIFR,WAAWG,UAAUM,eAAkB,SAASd,GAC9C,OAAO,SAASe,GACd,IAAIC,EAAWhB,EAAOW,KAAKC,KAAMG,GAIjC,OAHIH,KAAKC,2BACPG,EAAWJ,KAAKK,cAEXD,CACT,CACF,CARuC,CAQpCX,WAAWG,UAAUM,gBAGxBT,WAAWG,UAAUU,KAAQ,SAASlB,GACpC,OAAO,WACLA,EAAOW,KAAKC,MAEPO,KAILC,EAA6BR,KAAM,SAACS,GAClCA,EAAEC,KAAKC,SACPF,EAAEC,KAAKE,kBACP,IAAMC,EAAcC,EAAE,gBAChBC,EAAOD,EAAE,aACXE,IAGkBF,EAAEG,KACVC,OAAOL,GAAaK,OAAOH,GAIvCD,EAAEK,SAASC,MAAMF,OAAOL,GAAaK,OAAOH,EAEhD,EACF,CACF,CAzB6B,CAyB1BtB,WAAWG,UAAUU,MAKxBb,WAAWG,UAAUyB,gBAAkB,WAAW,IAAAC,EAAA,KAChDtB,KAAKuB,KAAKC,aAAaC,IAAI,UAAW,GAElBX,EAAEY,QAAQC,SACX3B,KAAK4B,sBACtB5B,KAAK6B,WAAW7B,KAAKK,cAGvBL,KAAKC,0BAA2B,EAChCD,KAAKY,kBAELZ,KAAKW,SACLX,KAAK8B,YAAY9B,KAAK+B,gBAEtB/B,KAAKuB,KAAKC,aAAaQ,QAAQ,CAAEC,QAAS,GAAK,IAAK,UAEpDnB,EAAEK,UAAUe,GAAG,SAADC,OAAU7C,GAAmB,SAAAmB,GACvB,KAAdA,EAAE2B,SAAgBd,EAAKe,gBAC7B,EACF,EAKA5C,WAAWG,UAAU0C,eAAiB,WACpCtC,KAAKuB,KAAKC,aAAaC,IAAI,UAAW,GAEtCX,EAAEK,UAAUoB,IAAI,QAAUjD,GAE1BU,KAAKgB,oBAAqB,EAC1BhB,KAAKY,kBAELZ,KAAKW,SACLX,KAAKuB,KAAKC,aAAaQ,QAAQ,CAAEC,QAAS,GAAK,IAAK,SACtD,EAOAxC,WAAWG,UAAU4C,aAAe,WAClC,OAAOxB,KAAwBhB,KAAKC,wBACtC,EAKAR,WAAWG,UAAU6C,iBAAmB,WAClCzC,KAAKwC,eACHjC,IACFmC,IAEA1C,KAAKsC,iBAGH/B,IACFoC,EAAkB3C,KAAKuB,KAAKqB,IAAI,IAEhC5C,KAAKqB,iBAGX,EAGA5B,WAAWoD,KAAKxD,SAAWA,EAG3BI,WAAWoD,KAAK5B,qBAAuBA,EAGvCxB,WAAWoD,KAAKrC,6BAA+BA,EAG/Cf,WAAWoD,KAAKtC,kBAAoBA,EAGpCd,WAAWoD,KAAKF,kBAAoBA,EAGpClD,WAAWoD,KAAKH,eAAiBA,EAGjCjD,WAAWoD,KAAK7B,mBAAqBA,CACvC,CASO,SAASC,IACd,OAAOE,SAAS2B,mBACd3B,SAAS4B,yBACT5B,SAAS6B,sBACT7B,SAAS8B,mBACb,CAOO,SAASjC,IAEd,OAAO8B,MADmB7B,GAE5B,CAOO,SAASyB,IACVvB,SAASuB,eACXvB,SAASuB,iBACAvB,SAAS+B,qBAClB/B,SAAS+B,uBACA/B,SAASgC,oBAClBhC,SAASgC,sBACAhC,SAASiC,kBAClBjC,SAASiC,kBAEb,CAOO,SAAST,EAAkBU,GAC5BA,EAAQV,kBACVU,EAAQV,oBACCU,EAAQC,wBACjBD,EAAQC,0BACCD,EAAQE,qBACjBF,EAAQE,uBACCF,EAAQG,qBACjBH,EAAQG,qBAEZ,CAQO,SAASjD,IACd,OAAQY,SAASsC,mBACftC,SAASuC,yBACTvC,SAASwC,sBACTxC,SAASyC,mBACb,CAOO,SAASpD,EACdE,EAAMmD,GAEN,IAAMC,EAAQ,oBAMRC,GAAcD,EALI,CACtB,SACA,MACA,MAE0CE,KAAKF,GAASA,GAAOG,OACjEnD,EAAEK,UAAUe,GAAG6B,EAAYrD,EAAMmD,EACnC,CAOO,SAASxE,IACd,YAAsC,IAAvBqC,OAAOwC,cAA8E,IAA7CC,UAAUC,UAAUC,QAAQ,WACrF,C","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
|
@@ -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':
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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' | '
|
|
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
|
}
|
|
@@ -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 {
|
|
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
|
|
62
|
-
description = '
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
457
|
+
</details>
|
|
458
|
+
</div>
|
|
430
459
|
<div class="footer" id="status" style="margin-top:5%">
|
|
431
|
-
|
|
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
|
-
|
|
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]
|
|
479
|
-
|
|
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
|
}
|
package/src/plugins/tts/utils.js
CHANGED
|
@@ -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
|
}
|