@internetarchive/bookreader 5.0.0-11 → 5.0.0-111
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/474.js +2 -0
- package/BookReader/474.js.map +1 -0
- package/BookReader/BookReader.css +649 -1225
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.LICENSE.txt +20 -20
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/bergamot-translator-worker.js +2966 -0
- package/BookReader/bergamot-translator-worker.wasm +0 -0
- package/BookReader/hypothesis/LICENSE +50 -0
- package/BookReader/hypothesis/README.md +55 -0
- package/BookReader/hypothesis/build/boot.js +1 -0
- package/BookReader/hypothesis/build/manifest.json +20 -0
- package/BookReader/hypothesis/build/scripts/annotator.bundle.js +184 -0
- package/BookReader/hypothesis/build/scripts/annotator.bundle.js.map +1 -0
- package/BookReader/hypothesis/build/scripts/sidebar.bundle.js +798 -0
- package/BookReader/hypothesis/build/scripts/sidebar.bundle.js.map +1 -0
- package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js +711 -0
- package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js.map +1 -0
- package/BookReader/hypothesis/build/styles/annotator.css +2235 -0
- package/BookReader/hypothesis/build/styles/annotator.css.map +1 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/BookReader/hypothesis/build/styles/highlights.css +2 -0
- package/BookReader/hypothesis/build/styles/highlights.css.map +1 -0
- package/BookReader/hypothesis/build/styles/katex.min.css +2 -0
- package/BookReader/hypothesis/build/styles/katex.min.css.map +1 -0
- package/BookReader/hypothesis/build/styles/pdfjs-overrides.css +2 -0
- package/BookReader/hypothesis/build/styles/pdfjs-overrides.css.map +1 -0
- package/BookReader/hypothesis/build/styles/sidebar.css +2731 -0
- package/BookReader/hypothesis/build/styles/sidebar.css.map +1 -0
- package/BookReader/hypothesis/build/styles/ui-playground.css +2659 -0
- package/BookReader/hypothesis/build/styles/ui-playground.css.map +1 -0
- package/BookReader/hypothesis/package.json +126 -0
- package/BookReader/ia-bookreader-bundle.js +1904 -0
- package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +19 -0
- package/BookReader/ia-bookreader-bundle.js.map +1 -0
- package/BookReader/icons/1up.svg +1 -1
- package/BookReader/icons/2up.svg +1 -1
- package/BookReader/icons/advance.svg +1 -1
- package/BookReader/icons/chevron-right.svg +1 -1
- package/BookReader/icons/close-circle-dark.svg +1 -1
- package/BookReader/icons/close-circle.svg +1 -1
- package/BookReader/icons/fullscreen.svg +1 -1
- package/BookReader/icons/fullscreen_exit.svg +1 -1
- package/BookReader/icons/hamburger.svg +1 -1
- package/BookReader/icons/left-arrow.svg +1 -1
- package/BookReader/icons/magnify-minus.svg +1 -1
- package/BookReader/icons/magnify-plus.svg +1 -1
- package/BookReader/icons/magnify.svg +1 -1
- package/BookReader/icons/pause.svg +1 -1
- package/BookReader/icons/play.svg +1 -1
- package/BookReader/icons/playback-speed.svg +1 -1
- package/BookReader/icons/read-aloud.svg +1 -1
- package/BookReader/icons/review.svg +1 -1
- package/BookReader/icons/slider-toggle.svg +1 -0
- package/BookReader/icons/thumbnails.svg +1 -1
- package/BookReader/icons/voice.svg +1 -0
- package/BookReader/icons/volume-full.svg +1 -1
- package/BookReader/images/BRicons.svg +3 -3
- package/BookReader/images/books_graphic.svg +1 -1
- package/BookReader/images/hypothesis.ico +0 -0
- package/BookReader/images/icon_book.svg +1 -1
- package/BookReader/images/icon_bookmark.svg +1 -1
- package/BookReader/images/icon_experiment.svg +1 -0
- package/BookReader/images/icon_gear.svg +1 -1
- package/BookReader/images/icon_hamburger.svg +1 -1
- package/BookReader/images/icon_home.svg +1 -1
- package/BookReader/images/icon_info.svg +1 -1
- package/BookReader/images/icon_one_page.svg +1 -1
- package/BookReader/images/icon_pause.svg +1 -1
- package/BookReader/images/icon_play.svg +1 -1
- package/BookReader/images/icon_playback-rate.svg +1 -1
- package/BookReader/images/icon_search_button.svg +1 -1
- package/BookReader/images/icon_share.svg +1 -1
- package/BookReader/images/icon_skip-ahead.svg +1 -1
- package/BookReader/images/icon_skip-back.svg +1 -1
- package/BookReader/images/icon_speaker.svg +1 -1
- package/BookReader/images/icon_speaker_open.svg +1 -1
- package/BookReader/images/icon_thumbnails.svg +1 -1
- package/BookReader/images/icon_toc.svg +1 -1
- package/BookReader/images/icon_two_pages.svg +1 -1
- package/BookReader/images/marker_chap-off.svg +1 -1
- package/BookReader/images/marker_chap-on.svg +1 -1
- package/BookReader/images/marker_srch-on.svg +1 -1
- package/BookReader/images/translate.svg +1 -0
- package/BookReader/images/unviewable_page.png +0 -0
- package/BookReader/jquery-3.js +2 -0
- package/BookReader/jquery-3.js.LICENSE.txt +24 -0
- package/BookReader/plugins/plugin.archive_analytics.js +1 -1
- package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
- package/BookReader/plugins/plugin.autoplay.js +1 -1
- package/BookReader/plugins/plugin.autoplay.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +22 -1
- package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.experiments.js +3 -0
- package/BookReader/plugins/plugin.experiments.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.experiments.js.map +1 -0
- package/BookReader/plugins/plugin.iframe.js +1 -1
- package/BookReader/plugins/plugin.iframe.js.map +1 -1
- package/BookReader/plugins/plugin.iiif.js +2 -0
- package/BookReader/plugins/plugin.iiif.js.map +1 -0
- package/BookReader/plugins/plugin.resume.js +1 -1
- package/BookReader/plugins/plugin.resume.js.map +1 -1
- package/BookReader/plugins/plugin.search.js +2 -1
- package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +24 -1
- package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.translate.js +159 -0
- package/BookReader/plugins/plugin.translate.js.LICENSE.txt +1 -0
- package/BookReader/plugins/plugin.translate.js.map +1 -0
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +2 -1
- package/BookReader/plugins/plugin.url.js.LICENSE.txt +1 -0
- 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/BookReader/plugins/translator-worker.js +2 -0
- package/BookReader/plugins/translator-worker.js.map +1 -0
- package/BookReader/silence.mp3 +0 -0
- package/BookReader/translator-worker.js +475 -0
- package/BookReader/webcomponents-bundle.js +3 -0
- package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
- package/BookReader/webcomponents-bundle.js.map +1 -0
- package/README.md +14 -3
- package/jsconfig.json +19 -0
- package/package.json +93 -70
- package/src/BookReader/BookModel.js +92 -46
- package/src/BookReader/DragScrollable.js +233 -0
- package/src/BookReader/ImageCache.js +49 -16
- package/src/BookReader/Mode1Up.js +66 -365
- package/src/BookReader/Mode1UpLit.js +392 -0
- package/src/BookReader/Mode2Up.js +87 -1315
- package/src/BookReader/Mode2UpLit.js +786 -0
- package/src/BookReader/ModeAbstract.js +43 -0
- package/src/BookReader/ModeCoordinateSpace.js +29 -0
- package/src/BookReader/ModeSmoothZoom.js +312 -0
- package/src/BookReader/ModeThumb.js +29 -15
- package/src/BookReader/Navbar/Navbar.js +201 -76
- package/src/BookReader/PageContainer.js +120 -23
- package/src/BookReader/ReduceSet.js +2 -2
- package/src/BookReader/Toolbar/Toolbar.js +18 -40
- package/src/BookReader/events.js +3 -3
- package/src/BookReader/options.js +94 -17
- package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
- package/src/BookReader/utils/ScrollClassAdder.js +31 -0
- package/src/BookReader/utils/SelectionObserver.js +53 -0
- package/src/BookReader/utils/classes.js +1 -1
- package/src/BookReader/utils.js +136 -12
- package/src/BookReader.js +695 -1228
- package/src/BookReaderPlugin.js +52 -0
- package/src/assets/icons/magnify-minus.svg +3 -7
- package/src/assets/icons/magnify-plus.svg +3 -7
- package/src/assets/icons/slider-toggle.svg +1 -0
- package/src/assets/icons/voice.svg +1 -0
- package/src/assets/images/hypothesis.ico +0 -0
- package/src/assets/images/icon_experiment.svg +1 -0
- package/src/assets/images/translate.svg +1 -0
- package/src/assets/images/unviewable_page.png +0 -0
- package/src/assets/silence.mp3 +0 -0
- package/src/css/BookReader.scss +1 -5
- package/src/css/_BRBookmarks.scss +1 -1
- package/src/css/_BRComponent.scss +1 -1
- package/src/css/_BRicon.scss +8 -2
- package/src/css/_BRmain.scss +16 -3
- package/src/css/_BRnav.scss +74 -70
- package/src/css/_BRpages.scss +171 -42
- package/src/css/_BRsearch.scss +69 -30
- package/src/css/_BRtoolbar.scss +5 -5
- package/src/css/_TextSelection.scss +188 -24
- package/src/css/_colorbox.scss +2 -2
- package/src/css/_controls.scss +24 -7
- package/src/css/_icons.scss +8 -1
- package/src/{BookNavigator/assets → css}/button-base.js +2 -2
- package/src/css/icon_checkmark.js +9 -0
- package/src/css/sharedStyles.js +15 -0
- package/src/ia-bookreader/downloads/downloads-provider.js +81 -0
- package/src/{BookNavigator → ia-bookreader}/downloads/downloads.js +25 -5
- package/src/ia-bookreader/ia-bookreader.js +666 -0
- package/src/ia-bookreader/sharing.js +27 -0
- package/src/ia-bookreader/viewable-files.js +98 -0
- package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments-provider.js +17 -17
- package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments.js +75 -67
- package/src/{BookNavigator → plugins}/bookmarks/bookmark-button.js +4 -3
- package/src/{BookNavigator/assets → plugins/bookmarks}/bookmark-colors.js +1 -1
- package/src/{BookNavigator → plugins}/bookmarks/bookmark-edit.js +43 -31
- package/src/{BookNavigator → plugins}/bookmarks/bookmarks-list.js +48 -49
- package/src/{BookNavigator → plugins}/bookmarks/bookmarks-loginCTA.js +3 -3
- package/src/plugins/bookmarks/bookmarks-provider.js +63 -0
- package/src/{BookNavigator → plugins/bookmarks}/delete-modal-actions.js +1 -1
- package/src/{BookNavigator → plugins}/bookmarks/ia-bookmarks.js +117 -68
- package/src/plugins/plugin.archive_analytics.js +84 -78
- package/src/plugins/plugin.autoplay.js +99 -104
- package/src/plugins/plugin.chapters.js +319 -205
- package/src/plugins/plugin.experiments.js +339 -0
- package/src/plugins/plugin.iframe.js +1 -1
- package/src/plugins/plugin.iiif.js +141 -0
- package/src/plugins/plugin.resume.js +54 -51
- package/src/plugins/plugin.text_selection.js +539 -219
- package/src/plugins/plugin.vendor-fullscreen.js +5 -5
- package/src/plugins/search/plugin.search.js +374 -392
- package/src/{BookNavigator → plugins}/search/search-provider.js +59 -27
- package/src/{BookNavigator → plugins}/search/search-results.js +105 -76
- package/src/plugins/search/utils.js +50 -0
- package/src/plugins/search/view.js +50 -68
- package/src/plugins/translate/TranslationManager.js +164 -0
- package/src/plugins/translate/plugin.translate.js +512 -0
- package/src/plugins/tts/AbstractTTSEngine.js +78 -49
- package/src/plugins/tts/FestivalTTSEngine.js +20 -30
- package/src/plugins/tts/PageChunk.js +33 -21
- package/src/plugins/tts/PageChunkIterator.js +11 -17
- package/src/plugins/tts/WebTTSEngine.js +131 -91
- package/src/plugins/tts/plugin.tts.js +345 -350
- package/src/plugins/tts/utils.js +77 -49
- package/src/plugins/url/UrlPlugin.js +211 -0
- package/src/plugins/{plugin.url.js → url/plugin.url.js} +105 -20
- package/src/util/TextSelectionManager.js +532 -0
- package/src/util/browserSniffing.js +33 -1
- package/src/util/cache.js +20 -0
- package/src/util/docCookies.js +21 -2
- package/src/util/lit.js +15 -0
- package/src/util/strings.js +1 -0
- package/.babelrc +0 -12
- package/.dependabot/config.yml +0 -6
- package/.eslintrc.js +0 -50
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/bug.md +0 -32
- package/.github/ISSUE_TEMPLATE/feature-request.md +0 -30
- package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -15
- package/.github/workflows/node.js.yml +0 -37
- package/.github/workflows/npm-publish.yml +0 -47
- package/.testcaferc.json +0 -5
- package/BookReader/bookreader-component-bundle.js +0 -1450
- package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
- package/BookReader/bookreader-component-bundle.js.map +0 -1
- package/BookReader/jquery-1.10.1.js +0 -2
- package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
- package/BookReader/plugins/plugin.menu_toggle.js +0 -2
- package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
- package/BookReader/plugins/plugin.mobile_nav.js +0 -2
- package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
- package/BookReaderDemo/BookReaderDemo.css +0 -41
- package/BookReaderDemo/BookReaderJSAdvanced.js +0 -115
- package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
- package/BookReaderDemo/BookReaderJSSimple.js +0 -55
- package/BookReaderDemo/IIIFBookReader.js +0 -207
- package/BookReaderDemo/assets/v5/Bookreader-logo-cool-grad.svg +0 -1
- package/BookReaderDemo/assets/v5/Bookreader-logo-flat.svg +0 -1
- package/BookReaderDemo/assets/v5/Bookreader-logo-hex-cool-grad.png +0 -0
- package/BookReaderDemo/assets/v5/Bookreader-logo-hex-flat.png +0 -0
- package/BookReaderDemo/assets/v5/Bookreader-logo-lines.png +0 -0
- package/BookReaderDemo/assets/v5/Bookreader-logo-lines.svg +0 -1
- package/BookReaderDemo/assets/v5/Bookreader-logo-warm.svg +0 -1
- package/BookReaderDemo/assets/v5/bookreader-logo-renders@1x.png +0 -0
- package/BookReaderDemo/assets/v5/bookreader-logo-renders@2x.png +0 -0
- package/BookReaderDemo/assets/v5/bookreader-v5-screenshot.png +0 -0
- package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
- package/BookReaderDemo/demo-advanced.html +0 -33
- package/BookReaderDemo/demo-autoplay.html +0 -38
- package/BookReaderDemo/demo-embed-iframe-src.html +0 -84
- package/BookReaderDemo/demo-embed.html +0 -26
- package/BookReaderDemo/demo-fullscreen-mobile.html +0 -36
- package/BookReaderDemo/demo-fullscreen.html +0 -33
- package/BookReaderDemo/demo-iiif.html +0 -34
- package/BookReaderDemo/demo-iiif.js +0 -26
- package/BookReaderDemo/demo-internetarchive.html +0 -74
- package/BookReaderDemo/demo-multiple.html +0 -43
- package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
- package/BookReaderDemo/demo-preview-pages.html +0 -1092
- package/BookReaderDemo/demo-simple.html +0 -34
- package/BookReaderDemo/demo-vendor-fullscreen.html +0 -36
- package/BookReaderDemo/immersion-1up.html +0 -64
- package/BookReaderDemo/immersion-mode.html +0 -35
- package/BookReaderDemo/toggle_controls.html +0 -53
- package/BookReaderDemo/view_mode.html +0 -39
- package/BookReaderDemo/viewmode-cycle.html +0 -41
- package/CHANGELOG.md +0 -495
- package/CONTRIBUTING.md +0 -7
- package/codecov.yml +0 -17
- package/index.html +0 -31
- package/karma.conf.js +0 -23
- package/screenshot.png +0 -0
- package/scripts/postversion.js +0 -10
- package/scripts/preversion.js +0 -14
- package/scripts/version.js +0 -26
- package/src/BookNavigator/BookModel.js +0 -14
- package/src/BookNavigator/BookNavigator.js +0 -446
- package/src/BookNavigator/assets/book-loader.js +0 -27
- package/src/BookNavigator/assets/icon_checkmark.js +0 -6
- package/src/BookNavigator/assets/icon_close.js +0 -3
- package/src/BookNavigator/bookmarks/bookmarks-provider.js +0 -53
- package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
- package/src/BookNavigator/downloads/downloads-provider.js +0 -66
- package/src/BookNavigator/search/a-search-result.js +0 -55
- package/src/BookReader/DebugConsole.js +0 -54
- package/src/BookReaderComponent/BookReaderComponent.js +0 -112
- package/src/ItemNavigator/ItemNavigator.js +0 -376
- package/src/ItemNavigator/providers/sharing.js +0 -29
- package/src/css/_MobileNav.scss +0 -194
- package/src/dragscrollable-br.js +0 -261
- package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
- package/src/plugins/plugin.mobile_nav.js +0 -287
- package/tests/BookReader/BookModel.test.js +0 -312
- package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
- package/tests/BookReader/DebugConsole.test.js +0 -25
- package/tests/BookReader/ImageCache.test.js +0 -150
- package/tests/BookReader/Mode1Up.test.js +0 -164
- package/tests/BookReader/Mode2Up.test.js +0 -247
- package/tests/BookReader/Navbar/Navbar.test.js +0 -169
- package/tests/BookReader/PageContainer.test.js +0 -115
- package/tests/BookReader/ReduceSet.test.js +0 -38
- package/tests/BookReader/Toolbar/Toolbar.test.js +0 -26
- package/tests/BookReader/utils/classes.test.js +0 -88
- package/tests/BookReader/utils.test.js +0 -109
- package/tests/BookReader.options.test.js +0 -39
- package/tests/BookReader.test.js +0 -301
- package/tests/e2e/README.md +0 -75
- package/tests/e2e/autoplay.test.js +0 -13
- package/tests/e2e/base.test.js +0 -35
- package/tests/e2e/helpers/base.js +0 -263
- package/tests/e2e/helpers/debug.js +0 -13
- package/tests/e2e/helpers/desktopSearch.js +0 -72
- package/tests/e2e/helpers/mobileSearch.js +0 -85
- package/tests/e2e/helpers/mockSearch.js +0 -93
- package/tests/e2e/helpers/rightToLeft.js +0 -29
- package/tests/e2e/ia-production/ia-prod-base.js +0 -17
- package/tests/e2e/models/BookReader.js +0 -11
- package/tests/e2e/models/Navigation.js +0 -56
- package/tests/e2e/rightToLeft.test.js +0 -20
- package/tests/e2e/viewmode.test.js +0 -37
- package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
- package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +0 -133
- package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +0 -222
- package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
- package/tests/karma/BookNavigator/search/search-results.test.js +0 -240
- package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
- package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
- package/tests/plugins/plugin.archive_analytics.test.js +0 -23
- package/tests/plugins/plugin.autoplay.test.js +0 -52
- package/tests/plugins/plugin.chapters.test.js +0 -130
- package/tests/plugins/plugin.iframe.test.js +0 -42
- package/tests/plugins/plugin.mobile_nav.test.js +0 -66
- package/tests/plugins/plugin.resume.test.js +0 -98
- package/tests/plugins/plugin.text_selection.test.js +0 -203
- package/tests/plugins/plugin.url.test.js +0 -129
- package/tests/plugins/plugin.vendor-fullscreen.test.js +0 -65
- package/tests/plugins/search/plugin.search.test.js +0 -173
- package/tests/plugins/search/plugin.search.view.test.js +0 -106
- package/tests/plugins/tts/AbstractTTSEngine.test.js +0 -153
- package/tests/plugins/tts/FestivalTTSEngine.test.js +0 -59
- package/tests/plugins/tts/PageChunk.test.js +0 -57
- package/tests/plugins/tts/PageChunkIterator.test.js +0 -179
- package/tests/plugins/tts/WebTTSEngine.test.js +0 -126
- package/tests/plugins/tts/utils.test.js +0 -133
- package/tests/util/browserSniffing.test.js +0 -56
- package/tests/util/docCookies.test.js +0 -15
- package/tests/util/strings.test.js +0 -63
- package/tests/utils.js +0 -42
- package/webpack.config.js +0 -86
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { SelectionObserver } from "../BookReader/utils/SelectionObserver.js";
|
|
3
|
+
import { html, LitElement } from 'lit';
|
|
4
|
+
import { customElement } from 'lit/decorators.js';
|
|
5
|
+
import '@internetarchive/icon-share';
|
|
6
|
+
|
|
7
|
+
export class TextSelectionManager {
|
|
8
|
+
options = {
|
|
9
|
+
// Current Translation plugin implementation does not have words, will limit to one BRlineElement for now
|
|
10
|
+
maxProtectedWords: 200,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** @type {BRSelectMenu} */
|
|
14
|
+
selectMenu;
|
|
15
|
+
/** @type {boolean} */
|
|
16
|
+
selectionMenuEnabled = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} layer Selector for the text layer to manage
|
|
20
|
+
* @param {import('../BookReader.js').default} br
|
|
21
|
+
* @param {Object} options
|
|
22
|
+
* @param {string[]} options.selectionElement CSS selector for elements that count as "words" for selection limiting
|
|
23
|
+
* @param {number} [maxWords] Maximum number of words allowed to be selected
|
|
24
|
+
*/
|
|
25
|
+
constructor (layer, br, { selectionElement }, maxWords) {
|
|
26
|
+
/** @type {string} */
|
|
27
|
+
this.layer = layer;
|
|
28
|
+
/** @type {import('../BookReader.js').default} */
|
|
29
|
+
this.br = br;
|
|
30
|
+
/** @type {string[]} */
|
|
31
|
+
this.selectionElement = selectionElement;
|
|
32
|
+
this.selectionObserver = new SelectionObserver(this.layer, this._onSelectionChange);
|
|
33
|
+
this.options.maxProtectedWords = maxWords ? maxWords : 200;
|
|
34
|
+
|
|
35
|
+
this.selectMenu = new BRSelectMenu(br);
|
|
36
|
+
this.selectMenu.className = "br-select-menu__root";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
init() {
|
|
40
|
+
this.attach();
|
|
41
|
+
new SelectionObserver(this.layer, (selectEvent) => {
|
|
42
|
+
// Track how often selection is used
|
|
43
|
+
if (selectEvent == 'started') {
|
|
44
|
+
this.br.plugins.archiveAnalytics?.sendEvent('BookReader', 'SelectStart');
|
|
45
|
+
|
|
46
|
+
// Set a class on the page to avoid hiding it when zooming/etc
|
|
47
|
+
this.br.refs.$br.find('.BRpagecontainer--hasSelection').removeClass('BRpagecontainer--hasSelection');
|
|
48
|
+
$(window.getSelection().anchorNode).closest('.BRpagecontainer').addClass('BRpagecontainer--hasSelection');
|
|
49
|
+
this.selectMenu.showMenu();
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (selectEvent == 'focusChanged') {
|
|
54
|
+
// hide the button as user changes their selection
|
|
55
|
+
if (this.mouseIsDown) {
|
|
56
|
+
this.selectMenu.hideMenu();
|
|
57
|
+
} else if (window.getSelection().toString()) {
|
|
58
|
+
this.selectMenu.showMenu();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (selectEvent == 'cleared') {
|
|
63
|
+
this.selectMenu.hideMenu();
|
|
64
|
+
}
|
|
65
|
+
}).attach();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Need attach + detach methods to toggle w/ Translation plugin
|
|
69
|
+
attach() {
|
|
70
|
+
this.selectionObserver.attach();
|
|
71
|
+
if (this.selectionMenuEnabled) {
|
|
72
|
+
this.renderSelectionMenu();
|
|
73
|
+
}
|
|
74
|
+
if (this.br.protected) {
|
|
75
|
+
document.addEventListener('selectionchange', this._limitSelection);
|
|
76
|
+
// Prevent right clicking when selected text
|
|
77
|
+
$(document.body).on('contextmenu dragstart copy', (e) => {
|
|
78
|
+
const selection = document.getSelection();
|
|
79
|
+
if (selection?.toString()) {
|
|
80
|
+
const intersectsTextLayer = $(this.layer)
|
|
81
|
+
.toArray()
|
|
82
|
+
.some(el => selection.containsNode(el, true));
|
|
83
|
+
if (intersectsTextLayer) {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
detach() {
|
|
93
|
+
this.selectionObserver.detach();
|
|
94
|
+
if (this.br.protected) {
|
|
95
|
+
document.removeEventListener('selectionchange', this._limitSelection);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
renderSelectionMenu() {
|
|
100
|
+
if (document.querySelector('.br-select-menu__option')) return;
|
|
101
|
+
document.body.append(this.selectMenu);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @param {'started' | 'cleared' | 'focusChanged'} type
|
|
105
|
+
* @param {HTMLElement} target
|
|
106
|
+
*/
|
|
107
|
+
_onSelectionChange = (type, target) => {
|
|
108
|
+
if (type === 'started') {
|
|
109
|
+
this.textSelectingMode(target);
|
|
110
|
+
} else if (type === 'cleared') {
|
|
111
|
+
this.defaultMode(target);
|
|
112
|
+
} else if (type === 'focusChanged') {
|
|
113
|
+
// do nothing, just wait for the mouseup to trigger the styling change
|
|
114
|
+
} else {
|
|
115
|
+
throw new Error(`Unknown type ${type}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Intercept copied text to remove any styling applied to it
|
|
121
|
+
* @param {JQuery} $container
|
|
122
|
+
*/
|
|
123
|
+
interceptCopy ($container) {
|
|
124
|
+
$container[0].addEventListener('copy', (event) => {
|
|
125
|
+
const selection = document.getSelection();
|
|
126
|
+
event.clipboardData.setData('text/plain', selection.toString());
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Initializes text selection modes if there is a text layer on the page
|
|
133
|
+
* @param {JQuery} $container
|
|
134
|
+
*/
|
|
135
|
+
stopPageFlip($container) {
|
|
136
|
+
/** @type {JQuery<HTMLElement>} */
|
|
137
|
+
const $textLayer = $container.find(this.layer);
|
|
138
|
+
if (!$textLayer.length) return;
|
|
139
|
+
$textLayer.each((i, s) => this.defaultMode(s));
|
|
140
|
+
if (!this.br.protected) {
|
|
141
|
+
this.interceptCopy($container);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Applies mouse events when in default mode
|
|
147
|
+
* @param {HTMLElement} textLayer
|
|
148
|
+
*/
|
|
149
|
+
defaultMode (textLayer) {
|
|
150
|
+
const $pageContainer = $(textLayer).closest('.BRpagecontainer');
|
|
151
|
+
textLayer.style.pointerEvents = "none";
|
|
152
|
+
$pageContainer.find("img").css("pointer-events", "auto");
|
|
153
|
+
|
|
154
|
+
$(textLayer).off(".textSelectPluginHandler");
|
|
155
|
+
const startedMouseDown = this.mouseIsDown;
|
|
156
|
+
let skipNextMouseup = this.mouseIsDown;
|
|
157
|
+
if (startedMouseDown) {
|
|
158
|
+
textLayer.style.pointerEvents = "auto";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Need to stop propagation to prevent DragScrollable from
|
|
162
|
+
// blocking selection
|
|
163
|
+
$(textLayer).on("mousedown.textSelectPluginHandler", (event) => {
|
|
164
|
+
this.mouseIsDown = true;
|
|
165
|
+
this.selectMenu.hideMenu();
|
|
166
|
+
if ($(event.target).is(this.selectionElement.join(", "))) {
|
|
167
|
+
event.stopPropagation();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
$(textLayer).on("mouseup.textSelectPluginHandler", (event) => {
|
|
172
|
+
this.mouseIsDown = false;
|
|
173
|
+
this.selectMenu.hideMenu();
|
|
174
|
+
textLayer.style.pointerEvents = "none";
|
|
175
|
+
if (skipNextMouseup) {
|
|
176
|
+
skipNextMouseup = false;
|
|
177
|
+
event.stopPropagation();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* This mode is active while there is a selection on the given textLayer
|
|
184
|
+
* @param {HTMLElement} textLayer
|
|
185
|
+
*/
|
|
186
|
+
textSelectingMode(textLayer) {
|
|
187
|
+
const $pageContainer = $(textLayer).closest('.BRpagecontainer');
|
|
188
|
+
// Make text layer consume all events
|
|
189
|
+
textLayer.style.pointerEvents = "all";
|
|
190
|
+
// Block img from getting long-press to save while selecting
|
|
191
|
+
$pageContainer.find("img").css("pointer-events", "none");
|
|
192
|
+
|
|
193
|
+
$(textLayer).off(".textSelectPluginHandler");
|
|
194
|
+
|
|
195
|
+
$(textLayer).on("mousedown.textSelectPluginHandler", (event) => {
|
|
196
|
+
if (event.which != 1) return;
|
|
197
|
+
this.mouseIsDown = true;
|
|
198
|
+
event.stopPropagation();
|
|
199
|
+
this.selectMenu.hideMenu();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Prevent page flip on click
|
|
203
|
+
$(textLayer).on('mouseup.textSelectPluginHandler', (event) => {
|
|
204
|
+
this.mouseIsDown = false;
|
|
205
|
+
if (event.which != 1) return;
|
|
206
|
+
event.stopPropagation();
|
|
207
|
+
this.selectMenu.showMenu();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_limitSelection = () => {
|
|
212
|
+
const selection = window.getSelection();
|
|
213
|
+
if (!selection.rangeCount) return;
|
|
214
|
+
|
|
215
|
+
const range = selection.getRangeAt(0);
|
|
216
|
+
|
|
217
|
+
// Check if range.startContainer is inside the sub-tree of .BRContainer
|
|
218
|
+
const startInBr = !!range.startContainer.parentElement.closest('.BRcontainer');
|
|
219
|
+
const endInBr = !!range.endContainer.parentElement.closest('.BRcontainer');
|
|
220
|
+
if (!startInBr && !endInBr) return;
|
|
221
|
+
if (!startInBr || !endInBr) {
|
|
222
|
+
// weird case, just clear the selection
|
|
223
|
+
selection.removeAllRanges();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Find the last allowed word in the selection
|
|
228
|
+
const lastAllowedWord = genAt(
|
|
229
|
+
genFilter(
|
|
230
|
+
walkBetweenNodes(range.startContainer, range.endContainer),
|
|
231
|
+
(node) => node.classList?.contains(
|
|
232
|
+
this.selectionElement[0].replace(".", ""),
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
this.options.maxProtectedWords - 1,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (!lastAllowedWord || range.endContainer.parentNode == lastAllowedWord) return;
|
|
239
|
+
|
|
240
|
+
const newRange = document.createRange();
|
|
241
|
+
newRange.setStart(range.startContainer, range.startOffset);
|
|
242
|
+
newRange.setEnd(lastAllowedWord.firstChild, lastAllowedWord.textContent.length);
|
|
243
|
+
|
|
244
|
+
selection.removeAllRanges();
|
|
245
|
+
selection.addRange(newRange);
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Builds a TextFragment string from a given text selection.
|
|
251
|
+
* Note does not include the fragment directive `:~:` or # symbol
|
|
252
|
+
* See https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment/Text_fragments
|
|
253
|
+
* @param {Selection} selection currently selected text, eg `document.getSelection()`
|
|
254
|
+
* @param {HTMLElement[]} contextElements elements providing context for the selection
|
|
255
|
+
* @returns {string}
|
|
256
|
+
*/
|
|
257
|
+
export function createTextFragmentUrlParam(selection, contextElements) {
|
|
258
|
+
// TODO: Can import something that handles this more gracefully? see -
|
|
259
|
+
// https://web.dev/articles/text-fragments#:~:text=In%20its%20simplest%20form%2C%20the%20syntax%20of,percent%2Dencoded%20text%20I%20want%20to%20link%20to.
|
|
260
|
+
|
|
261
|
+
// :~:text=[prefix-,]textStart[,textEnd][,-suffix]
|
|
262
|
+
const highlightedText = selection.toString().replace(/[\s]+/g, " ").trim().split(" ");
|
|
263
|
+
const direction = selection.direction;
|
|
264
|
+
const startNode = direction == 'backward' ? selection.focusNode : selection.anchorNode;
|
|
265
|
+
const endNode = direction == 'backward' ? selection.anchorNode : selection.focusNode;
|
|
266
|
+
// If text selection begins or ends with a space, we look for the next eligible word to serve as the start or end word
|
|
267
|
+
const startWord = startNode.textContent.replace(/[\s]+/g, "") ? startNode.textContent : highlightedText[0];
|
|
268
|
+
const endWord = endNode.textContent.replace(/[\s]+/g, "") ? endNode.textContent : highlightedText[highlightedText.length - 1];
|
|
269
|
+
|
|
270
|
+
const textStartRe = RegExp.escape(startWord);
|
|
271
|
+
const textEndRe = RegExp.escape(endWord);
|
|
272
|
+
|
|
273
|
+
// 's' regex modifier ensures the `.` also captures newline characters
|
|
274
|
+
// Need to use lookahead/lookbehind assertions to allow for overlapping quotes (i.e. multiple "Holmes" on the same page)
|
|
275
|
+
const startPhraseMatchRe = new RegExp(String.raw`(?<=(${textStartRe}).*?)(${textEndRe})`, "gis");
|
|
276
|
+
const endPhraseMatchRe = new RegExp(String.raw`(${textStartRe})(?=.*?(${textEndRe}))`, "gis");
|
|
277
|
+
|
|
278
|
+
// Duplicated spaces in pageLayer.textContent for some reason
|
|
279
|
+
const selectionContext = contextElements
|
|
280
|
+
.map((el) => el.textContent)
|
|
281
|
+
.join(' ')
|
|
282
|
+
.replace(/\s+/g, " ");
|
|
283
|
+
const startPhraseFoundMatches = selectionContext.matchAll(startPhraseMatchRe).toArray();
|
|
284
|
+
const endPhraseFoundMatches = selectionContext.matchAll(endPhraseMatchRe).toArray();
|
|
285
|
+
if (startPhraseFoundMatches.length == 1 && endPhraseFoundMatches.length == 1) {
|
|
286
|
+
// If `startWord...endWord` quote is unambiguous and only occurs once, no prefix-/-suffix is needed for the URL param
|
|
287
|
+
return `text=${encodeURIComponent(startWord)},${encodeURIComponent(endWord)}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Need to add some additional context to `startWord...endWord` by including surrounding words before and after the keywords
|
|
291
|
+
const preStartRange = document.createRange();
|
|
292
|
+
preStartRange.setStart(contextElements[0].firstElementChild, 0);
|
|
293
|
+
preStartRange.setEnd(startNode, 0);
|
|
294
|
+
|
|
295
|
+
const postEndRange = document.createRange();
|
|
296
|
+
postEndRange.setStart(endNode, endNode.textContent.length);
|
|
297
|
+
const lastWordOfPageEl = getLastMostElement(contextElements[contextElements.length - 1]);
|
|
298
|
+
postEndRange.setEnd(lastWordOfPageEl, Math.max(0, lastWordOfPageEl.textContent.length - 1));
|
|
299
|
+
|
|
300
|
+
// prefixes/suffixes cannot contain paragraph breaks, words that are from more than one line break away should not be included
|
|
301
|
+
const prefix = getLastWords(3, preStartRange.toString())
|
|
302
|
+
.replace(/[ ]+/g, " ")
|
|
303
|
+
.trim()
|
|
304
|
+
.replace(/^[^\n]*\n/gm, "");
|
|
305
|
+
const suffix = getFirstWords(3, postEndRange.toString())
|
|
306
|
+
.replace(/[ ]+/g, " ")
|
|
307
|
+
.trim()
|
|
308
|
+
.replace(/\n[^\n]*$/gm, "");
|
|
309
|
+
|
|
310
|
+
// Partially selected words need to be captured completely
|
|
311
|
+
// Guarantee that all whitespace is replaced with just one space and that the first/last word of the highlight is not a space
|
|
312
|
+
const fullHighlight = selection.toString().replace(/\s+/g, " ").trim().split(/\s/g);
|
|
313
|
+
// Capture start/end words that may be partially highlighted
|
|
314
|
+
if (startNode.textContent.trim().length != 0) {
|
|
315
|
+
if (!startNode.textContent.includes(fullHighlight[0])) {
|
|
316
|
+
fullHighlight.unshift(startNode.textContent);
|
|
317
|
+
} else {
|
|
318
|
+
fullHighlight[0] = startNode.textContent;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (endNode.textContent.trim().length != 0) {
|
|
322
|
+
if (!endNode.textContent.includes(fullHighlight[fullHighlight.length - 1])) {
|
|
323
|
+
fullHighlight.push(endNode.textContent);
|
|
324
|
+
}
|
|
325
|
+
fullHighlight[fullHighlight.length - 1] = endNode.textContent;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let quote = [fullHighlight.join(" ")];
|
|
329
|
+
if (fullHighlight.length > 6) {
|
|
330
|
+
quote = [fullHighlight.slice(0, 3).join(" "), fullHighlight.slice(-3).join(" ")];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const textFragmentArr = [];
|
|
334
|
+
if (prefix) textFragmentArr.push(`${prefix}-`);
|
|
335
|
+
textFragmentArr.push(...quote);
|
|
336
|
+
if (suffix) textFragmentArr.push(`-${suffix}`);
|
|
337
|
+
|
|
338
|
+
return `text=${textFragmentArr.map(encodeURIComponent).join(',')}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @template T
|
|
343
|
+
* Get the i-th element of an iterable
|
|
344
|
+
* @param {Iterable<T>} iterable
|
|
345
|
+
* @param {number} index
|
|
346
|
+
*/
|
|
347
|
+
export function genAt(iterable, index) {
|
|
348
|
+
let i = 0;
|
|
349
|
+
for (const x of iterable) {
|
|
350
|
+
if (i == index) {
|
|
351
|
+
return x;
|
|
352
|
+
}
|
|
353
|
+
i++;
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @template T
|
|
360
|
+
* Generator version of filter
|
|
361
|
+
* @param {Iterable<T>} iterable
|
|
362
|
+
* @param {function(T): boolean} fn
|
|
363
|
+
*/
|
|
364
|
+
export function* genFilter(iterable, fn) {
|
|
365
|
+
for (const x of iterable) {
|
|
366
|
+
if (fn(x)) yield x;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Depth traverse the DOM tree starting at `start`, and ending at `end`.
|
|
372
|
+
* @param {Node} start
|
|
373
|
+
* @param {Node} end
|
|
374
|
+
* @returns {Generator<Node>}
|
|
375
|
+
*/
|
|
376
|
+
export function* walkBetweenNodes(start, end) {
|
|
377
|
+
let done = false;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* @param {Node} node
|
|
381
|
+
*/
|
|
382
|
+
function* walk(node, {children = true, parents = true, siblings = true} = {}) {
|
|
383
|
+
if (node === end) {
|
|
384
|
+
done = true;
|
|
385
|
+
yield node;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// yield self
|
|
390
|
+
yield node;
|
|
391
|
+
|
|
392
|
+
// First iterate children (depth-first traversal)
|
|
393
|
+
if (children && node.firstChild) {
|
|
394
|
+
yield* walk(node.firstChild, {children: true, parents: false, siblings: true});
|
|
395
|
+
if (done) return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Then iterate siblings
|
|
399
|
+
if (siblings) {
|
|
400
|
+
for (let sib = node.nextSibling; sib; sib = sib.nextSibling) {
|
|
401
|
+
yield* walk(sib, {children: true, parents: false, siblings: false});
|
|
402
|
+
if (done) return;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Finally, move up the tree
|
|
407
|
+
if (parents && node.parentNode) {
|
|
408
|
+
yield* walk(node.parentNode, {children: false, parents: true, siblings: true});
|
|
409
|
+
if (done) return;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
yield* walk(start);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@customElement('br-select-menu')
|
|
417
|
+
class BRSelectMenu extends LitElement {
|
|
418
|
+
/** @type {import('../BookReader.js').default} */
|
|
419
|
+
br;
|
|
420
|
+
|
|
421
|
+
constructor(br) {
|
|
422
|
+
super();
|
|
423
|
+
this.br = br;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** @override */
|
|
427
|
+
createRenderRoot() {
|
|
428
|
+
// Disable shadow DOM; that would require a huge rejiggering of CSS
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
render() {
|
|
433
|
+
return html`
|
|
434
|
+
<button @click=${this.handleCopyLinkToHighlight} class="br-select-menu__option">
|
|
435
|
+
<ia-icon-share class="br-select-menu__icon" aria-hidden="true"></ia-icon-share>
|
|
436
|
+
<span class="br-select-menu__label">Copy Link to Highlight</span>
|
|
437
|
+
</button>
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* @param {MouseEvent} e
|
|
443
|
+
*/
|
|
444
|
+
handleCopyLinkToHighlight(e) {
|
|
445
|
+
e.preventDefault();
|
|
446
|
+
|
|
447
|
+
const currentParams = this.br.readQueryString();
|
|
448
|
+
const currentSelection = window.getSelection();
|
|
449
|
+
/** @type {HTMLElement} */
|
|
450
|
+
const textLayer = currentSelection.anchorNode.parentElement.closest('.BRtextLayer');
|
|
451
|
+
const textFragmentUrlParam = createTextFragmentUrlParam(currentSelection, Array.from(document.querySelectorAll('.BRpage-visible')));
|
|
452
|
+
|
|
453
|
+
// Note: Have to do a param construction to avoid url-encoding of commas in the text fragment param
|
|
454
|
+
let linkToHighlightParams = currentParams;
|
|
455
|
+
if (currentParams.includes('text=')) {
|
|
456
|
+
linkToHighlightParams = currentParams.replace(/(text=)[\w\W\d%]+/, textFragmentUrlParam);
|
|
457
|
+
} else {
|
|
458
|
+
const sep = linkToHighlightParams ? '&' : '?';
|
|
459
|
+
linkToHighlightParams += `${sep}${textFragmentUrlParam}`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const currentUrl = window.location;
|
|
463
|
+
// TODO - updateResumeValue + getCookiePath in plugin.resume.js overrides the adjustedUrlPageNumPath, check how to workaround this
|
|
464
|
+
// TODO - won't work with hash mode
|
|
465
|
+
const adjustedUrlPageNumPath = currentUrl.pathname.toString().replace(/(?<=\/page\/)\d+(?=\/)/, textLayer.parentElement.getAttribute('data-page-num'));
|
|
466
|
+
|
|
467
|
+
const linkToHighlight = `${currentUrl.origin}${adjustedUrlPageNumPath}${linkToHighlightParams}${currentUrl?.hash || ''}`;
|
|
468
|
+
navigator.clipboard.writeText(linkToHighlight);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
showMenu() {
|
|
472
|
+
if (this.br.plugins.translate?.userToggleTranslate) return;
|
|
473
|
+
const currentSelection = window.getSelection();
|
|
474
|
+
const start = currentSelection.anchorNode.parentElement;
|
|
475
|
+
const end = currentSelection.focusNode.parentElement; // will always be a text node
|
|
476
|
+
const height = 30;
|
|
477
|
+
const width = 60;
|
|
478
|
+
const startBoundingRect = start.getBoundingClientRect();
|
|
479
|
+
const endBoundingRect = end.getBoundingClientRect();
|
|
480
|
+
let hlButtonTop = startBoundingRect.top - height;
|
|
481
|
+
let hlButtonLeft = startBoundingRect.left - width;
|
|
482
|
+
if (currentSelection.direction == 'backward') {
|
|
483
|
+
hlButtonTop = endBoundingRect.top - height;
|
|
484
|
+
hlButtonLeft = endBoundingRect.left - width;
|
|
485
|
+
}
|
|
486
|
+
this.style.top = `${hlButtonTop}px`;
|
|
487
|
+
this.style.left = `${hlButtonLeft}px`;
|
|
488
|
+
this.style.zIndex = '1';
|
|
489
|
+
this.style.position = 'absolute';
|
|
490
|
+
this.style.display = 'block';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
hideMenu = () => {
|
|
494
|
+
this.style.display = 'none';
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* @param {number} numWords
|
|
501
|
+
* @param {string} text
|
|
502
|
+
* @return {string}
|
|
503
|
+
*/
|
|
504
|
+
export function getFirstWords(numWords, text) {
|
|
505
|
+
text = text.trim();
|
|
506
|
+
const re = new RegExp(String.raw`^(\S+(\s+|$)){1,${numWords}}`);
|
|
507
|
+
const m = text.match(re);
|
|
508
|
+
return m ? m[0].trim() : "";
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @param {number} numWords
|
|
513
|
+
* @param {string} text
|
|
514
|
+
* @return {string}
|
|
515
|
+
*/
|
|
516
|
+
export function getLastWords(numWords, text) {
|
|
517
|
+
text = text.trim();
|
|
518
|
+
const re = new RegExp(String.raw`((^|\s+)\S+){1,${numWords}}\s*?$`);
|
|
519
|
+
const m = text.match(re);
|
|
520
|
+
return m ? m[0].trim() : "";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* @param {HTMLElement | Element} parent
|
|
525
|
+
* @returns {Node}
|
|
526
|
+
*/
|
|
527
|
+
export function getLastMostElement(parent) {
|
|
528
|
+
while (parent.lastElementChild) {
|
|
529
|
+
parent = parent.lastElementChild;
|
|
530
|
+
}
|
|
531
|
+
return parent;
|
|
532
|
+
}
|
|
@@ -7,7 +7,17 @@
|
|
|
7
7
|
* @return {boolean}
|
|
8
8
|
*/
|
|
9
9
|
export function isChrome(userAgent = navigator.userAgent, vendor = navigator.vendor) {
|
|
10
|
-
return /chrome/i.test(userAgent) && /google inc/i.test(vendor);
|
|
10
|
+
return /chrome/i.test(userAgent) && /google inc/i.test(vendor) && !isEdge(userAgent);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether the current browser is a Edge browser
|
|
15
|
+
* See https://learn.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-guidance
|
|
16
|
+
* @param {string} [userAgent]
|
|
17
|
+
* @return {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function isEdge(userAgent = navigator.userAgent) {
|
|
20
|
+
return /chrome/i.test(userAgent) && /\bEdg(e|A|iOS)?\b/i.test(userAgent);
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
/**
|
|
@@ -28,3 +38,25 @@ export function isFirefox(userAgent = navigator.userAgent) {
|
|
|
28
38
|
export function isSafari(userAgent = navigator.userAgent) {
|
|
29
39
|
return /safari/i.test(userAgent) && !/chrome|chromium/i.test(userAgent);
|
|
30
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks whether the current browser is iOS (and hence iOS webkit)
|
|
44
|
+
* @return {boolean}
|
|
45
|
+
*/
|
|
46
|
+
export function isIOS() {
|
|
47
|
+
// We can't just check the userAgent because as of iOS 13,
|
|
48
|
+
// the userAgent is the same as desktop Safari because
|
|
49
|
+
// they wanted iPad's to be served the same version of websites
|
|
50
|
+
// as desktops.
|
|
51
|
+
return 'ongesturestart' in window && navigator.maxTouchPoints > 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Checks whether the current browser is Samsung Internet
|
|
56
|
+
* https://stackoverflow.com/a/40684162/2317712
|
|
57
|
+
* @param {string} [userAgent]
|
|
58
|
+
* @return {boolean}
|
|
59
|
+
*/
|
|
60
|
+
export function isSamsungInternet(userAgent = navigator.userAgent) {
|
|
61
|
+
return /SamsungBrowser/i.test(userAgent);
|
|
62
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template T
|
|
3
|
+
*/
|
|
4
|
+
export class Cache {
|
|
5
|
+
constructor(maxSize = 10) {
|
|
6
|
+
this.maxSize = maxSize;
|
|
7
|
+
/** @type {T[]} */
|
|
8
|
+
this.entries = [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {T} entry
|
|
13
|
+
*/
|
|
14
|
+
add(entry) {
|
|
15
|
+
if (this.entries.length >= this.maxSize) {
|
|
16
|
+
this.entries.shift();
|
|
17
|
+
}
|
|
18
|
+
this.entries.push(entry);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/util/docCookies.js
CHANGED
|
@@ -9,6 +9,22 @@
|
|
|
9
9
|
* http://www.gnu.org/licenses/gpl-3.0-standalone.html
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Check to see if the browser has cookies enabled.
|
|
14
|
+
* Accessing document.cookies errors if eg iframe with sandbox enabled.
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
export function areCookiesBlocked(doc = document) {
|
|
18
|
+
try {
|
|
19
|
+
doc.cookie;
|
|
20
|
+
return false;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const COOKIES_BLOCKED = areCookiesBlocked();
|
|
27
|
+
|
|
12
28
|
/**
|
|
13
29
|
* Get specific key's value stored in cookie
|
|
14
30
|
*
|
|
@@ -17,7 +33,7 @@
|
|
|
17
33
|
* @returns {string|null}
|
|
18
34
|
*/
|
|
19
35
|
export function getItem(sKey) {
|
|
20
|
-
if (!sKey) return null;
|
|
36
|
+
if (COOKIES_BLOCKED || !sKey) return null;
|
|
21
37
|
|
|
22
38
|
return decodeURIComponent(
|
|
23
39
|
// eslint-disable-next-line no-useless-escape
|
|
@@ -34,9 +50,11 @@ export function getItem(sKey) {
|
|
|
34
50
|
* @param {string} [sDomain] domain name
|
|
35
51
|
* @param {boolean} [bSecure]
|
|
36
52
|
*
|
|
37
|
-
* @returns {
|
|
53
|
+
* @returns {boolean}
|
|
38
54
|
*/
|
|
39
55
|
export function setItem(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
|
|
56
|
+
if (COOKIES_BLOCKED) return false;
|
|
57
|
+
|
|
40
58
|
document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue)
|
|
41
59
|
+ (vEnd ? `; expires=${vEnd.toUTCString()}` : '')
|
|
42
60
|
+ (sDomain ? `; domain=${sDomain}` : '')
|
|
@@ -56,6 +74,7 @@ export function setItem(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
|
|
|
56
74
|
* @returns {boolean}
|
|
57
75
|
*/
|
|
58
76
|
export function removeItem(sKey, sPath, sDomain) {
|
|
77
|
+
if (COOKIES_BLOCKED) return false;
|
|
59
78
|
// eslint-disable-next-line
|
|
60
79
|
if (!hasItem(sKey)) return false;
|
|
61
80
|
|
package/src/util/lit.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert an SVG string to a data URL usable in CSS
|
|
5
|
+
* @param {import('lit').TemplateResult} svgTemplate - The SVG template to convert
|
|
6
|
+
* @returns {import('lit').CSSResult}
|
|
7
|
+
*/
|
|
8
|
+
export function svgToDataUrl(svgTemplate) {
|
|
9
|
+
// No clue why, on prod the template from the close icon comes in as a class instead of a TemplateResult.
|
|
10
|
+
// Might be a discrepancy with lit versions.
|
|
11
|
+
if (svgTemplate.prototype) svgTemplate = svgTemplate.prototype.render();
|
|
12
|
+
|
|
13
|
+
const svgString = svgTemplate.strings[0];
|
|
14
|
+
return css([`data:image/svg+xml;base64,${btoa(svgString.replace(/\n/g, ' '))}`]);
|
|
15
|
+
}
|
package/src/util/strings.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* @param {StringWithVars|String} template
|
|
10
10
|
* @param { {[varName: string]: { toString: () => string} } } vars
|
|
11
11
|
* @param { {[varName: string]: { toString: () => string} } } [overrides]
|
|
12
|
+
* @returns {StringWithVars|string}
|
|
12
13
|
*/
|
|
13
14
|
export function applyVariables(template, vars, overrides = {}, possibleFilters = APPLY_FILTERS) {
|
|
14
15
|
return template?.replace(/\{\{([^}]*?)\}\}/g, ($0, $1) => {
|
package/.babelrc
DELETED