@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.
Files changed (381) hide show
  1. package/BookReader/474.js +2 -0
  2. package/BookReader/474.js.map +1 -0
  3. package/BookReader/BookReader.css +649 -1225
  4. package/BookReader/BookReader.js +1 -1
  5. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  6. package/BookReader/BookReader.js.map +1 -1
  7. package/BookReader/bergamot-translator-worker.js +2966 -0
  8. package/BookReader/bergamot-translator-worker.wasm +0 -0
  9. package/BookReader/hypothesis/LICENSE +50 -0
  10. package/BookReader/hypothesis/README.md +55 -0
  11. package/BookReader/hypothesis/build/boot.js +1 -0
  12. package/BookReader/hypothesis/build/manifest.json +20 -0
  13. package/BookReader/hypothesis/build/scripts/annotator.bundle.js +184 -0
  14. package/BookReader/hypothesis/build/scripts/annotator.bundle.js.map +1 -0
  15. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js +798 -0
  16. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js.map +1 -0
  17. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js +711 -0
  18. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js.map +1 -0
  19. package/BookReader/hypothesis/build/styles/annotator.css +2235 -0
  20. package/BookReader/hypothesis/build/styles/annotator.css.map +1 -0
  21. package/BookReader/hypothesis/build/styles/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  22. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  23. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  24. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  25. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  26. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Bold.woff2 +0 -0
  27. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  28. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Italic.woff2 +0 -0
  29. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Regular.woff2 +0 -0
  30. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  31. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-Italic.woff2 +0 -0
  32. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  33. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  34. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  35. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Script-Regular.woff2 +0 -0
  36. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  37. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  38. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  39. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  40. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  41. package/BookReader/hypothesis/build/styles/highlights.css +2 -0
  42. package/BookReader/hypothesis/build/styles/highlights.css.map +1 -0
  43. package/BookReader/hypothesis/build/styles/katex.min.css +2 -0
  44. package/BookReader/hypothesis/build/styles/katex.min.css.map +1 -0
  45. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css +2 -0
  46. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css.map +1 -0
  47. package/BookReader/hypothesis/build/styles/sidebar.css +2731 -0
  48. package/BookReader/hypothesis/build/styles/sidebar.css.map +1 -0
  49. package/BookReader/hypothesis/build/styles/ui-playground.css +2659 -0
  50. package/BookReader/hypothesis/build/styles/ui-playground.css.map +1 -0
  51. package/BookReader/hypothesis/package.json +126 -0
  52. package/BookReader/ia-bookreader-bundle.js +1904 -0
  53. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +19 -0
  54. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  55. package/BookReader/icons/1up.svg +1 -1
  56. package/BookReader/icons/2up.svg +1 -1
  57. package/BookReader/icons/advance.svg +1 -1
  58. package/BookReader/icons/chevron-right.svg +1 -1
  59. package/BookReader/icons/close-circle-dark.svg +1 -1
  60. package/BookReader/icons/close-circle.svg +1 -1
  61. package/BookReader/icons/fullscreen.svg +1 -1
  62. package/BookReader/icons/fullscreen_exit.svg +1 -1
  63. package/BookReader/icons/hamburger.svg +1 -1
  64. package/BookReader/icons/left-arrow.svg +1 -1
  65. package/BookReader/icons/magnify-minus.svg +1 -1
  66. package/BookReader/icons/magnify-plus.svg +1 -1
  67. package/BookReader/icons/magnify.svg +1 -1
  68. package/BookReader/icons/pause.svg +1 -1
  69. package/BookReader/icons/play.svg +1 -1
  70. package/BookReader/icons/playback-speed.svg +1 -1
  71. package/BookReader/icons/read-aloud.svg +1 -1
  72. package/BookReader/icons/review.svg +1 -1
  73. package/BookReader/icons/slider-toggle.svg +1 -0
  74. package/BookReader/icons/thumbnails.svg +1 -1
  75. package/BookReader/icons/voice.svg +1 -0
  76. package/BookReader/icons/volume-full.svg +1 -1
  77. package/BookReader/images/BRicons.svg +3 -3
  78. package/BookReader/images/books_graphic.svg +1 -1
  79. package/BookReader/images/hypothesis.ico +0 -0
  80. package/BookReader/images/icon_book.svg +1 -1
  81. package/BookReader/images/icon_bookmark.svg +1 -1
  82. package/BookReader/images/icon_experiment.svg +1 -0
  83. package/BookReader/images/icon_gear.svg +1 -1
  84. package/BookReader/images/icon_hamburger.svg +1 -1
  85. package/BookReader/images/icon_home.svg +1 -1
  86. package/BookReader/images/icon_info.svg +1 -1
  87. package/BookReader/images/icon_one_page.svg +1 -1
  88. package/BookReader/images/icon_pause.svg +1 -1
  89. package/BookReader/images/icon_play.svg +1 -1
  90. package/BookReader/images/icon_playback-rate.svg +1 -1
  91. package/BookReader/images/icon_search_button.svg +1 -1
  92. package/BookReader/images/icon_share.svg +1 -1
  93. package/BookReader/images/icon_skip-ahead.svg +1 -1
  94. package/BookReader/images/icon_skip-back.svg +1 -1
  95. package/BookReader/images/icon_speaker.svg +1 -1
  96. package/BookReader/images/icon_speaker_open.svg +1 -1
  97. package/BookReader/images/icon_thumbnails.svg +1 -1
  98. package/BookReader/images/icon_toc.svg +1 -1
  99. package/BookReader/images/icon_two_pages.svg +1 -1
  100. package/BookReader/images/marker_chap-off.svg +1 -1
  101. package/BookReader/images/marker_chap-on.svg +1 -1
  102. package/BookReader/images/marker_srch-on.svg +1 -1
  103. package/BookReader/images/translate.svg +1 -0
  104. package/BookReader/images/unviewable_page.png +0 -0
  105. package/BookReader/jquery-3.js +2 -0
  106. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  107. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  108. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  109. package/BookReader/plugins/plugin.autoplay.js +1 -1
  110. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  111. package/BookReader/plugins/plugin.chapters.js +22 -1
  112. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  113. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  114. package/BookReader/plugins/plugin.experiments.js +3 -0
  115. package/BookReader/plugins/plugin.experiments.js.LICENSE.txt +1 -0
  116. package/BookReader/plugins/plugin.experiments.js.map +1 -0
  117. package/BookReader/plugins/plugin.iframe.js +1 -1
  118. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  119. package/BookReader/plugins/plugin.iiif.js +2 -0
  120. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  121. package/BookReader/plugins/plugin.resume.js +1 -1
  122. package/BookReader/plugins/plugin.resume.js.map +1 -1
  123. package/BookReader/plugins/plugin.search.js +2 -1
  124. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  125. package/BookReader/plugins/plugin.search.js.map +1 -1
  126. package/BookReader/plugins/plugin.text_selection.js +24 -1
  127. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  128. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  129. package/BookReader/plugins/plugin.translate.js +159 -0
  130. package/BookReader/plugins/plugin.translate.js.LICENSE.txt +1 -0
  131. package/BookReader/plugins/plugin.translate.js.map +1 -0
  132. package/BookReader/plugins/plugin.tts.js +1 -1
  133. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  134. package/BookReader/plugins/plugin.tts.js.map +1 -1
  135. package/BookReader/plugins/plugin.url.js +2 -1
  136. package/BookReader/plugins/plugin.url.js.LICENSE.txt +1 -0
  137. package/BookReader/plugins/plugin.url.js.map +1 -1
  138. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  139. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  140. package/BookReader/plugins/translator-worker.js +2 -0
  141. package/BookReader/plugins/translator-worker.js.map +1 -0
  142. package/BookReader/silence.mp3 +0 -0
  143. package/BookReader/translator-worker.js +475 -0
  144. package/BookReader/webcomponents-bundle.js +3 -0
  145. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  146. package/BookReader/webcomponents-bundle.js.map +1 -0
  147. package/README.md +14 -3
  148. package/jsconfig.json +19 -0
  149. package/package.json +93 -70
  150. package/src/BookReader/BookModel.js +92 -46
  151. package/src/BookReader/DragScrollable.js +233 -0
  152. package/src/BookReader/ImageCache.js +49 -16
  153. package/src/BookReader/Mode1Up.js +66 -365
  154. package/src/BookReader/Mode1UpLit.js +392 -0
  155. package/src/BookReader/Mode2Up.js +87 -1315
  156. package/src/BookReader/Mode2UpLit.js +786 -0
  157. package/src/BookReader/ModeAbstract.js +43 -0
  158. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  159. package/src/BookReader/ModeSmoothZoom.js +312 -0
  160. package/src/BookReader/ModeThumb.js +29 -15
  161. package/src/BookReader/Navbar/Navbar.js +201 -76
  162. package/src/BookReader/PageContainer.js +120 -23
  163. package/src/BookReader/ReduceSet.js +2 -2
  164. package/src/BookReader/Toolbar/Toolbar.js +18 -40
  165. package/src/BookReader/events.js +3 -3
  166. package/src/BookReader/options.js +94 -17
  167. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  168. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  169. package/src/BookReader/utils/SelectionObserver.js +53 -0
  170. package/src/BookReader/utils/classes.js +1 -1
  171. package/src/BookReader/utils.js +136 -12
  172. package/src/BookReader.js +695 -1228
  173. package/src/BookReaderPlugin.js +52 -0
  174. package/src/assets/icons/magnify-minus.svg +3 -7
  175. package/src/assets/icons/magnify-plus.svg +3 -7
  176. package/src/assets/icons/slider-toggle.svg +1 -0
  177. package/src/assets/icons/voice.svg +1 -0
  178. package/src/assets/images/hypothesis.ico +0 -0
  179. package/src/assets/images/icon_experiment.svg +1 -0
  180. package/src/assets/images/translate.svg +1 -0
  181. package/src/assets/images/unviewable_page.png +0 -0
  182. package/src/assets/silence.mp3 +0 -0
  183. package/src/css/BookReader.scss +1 -5
  184. package/src/css/_BRBookmarks.scss +1 -1
  185. package/src/css/_BRComponent.scss +1 -1
  186. package/src/css/_BRicon.scss +8 -2
  187. package/src/css/_BRmain.scss +16 -3
  188. package/src/css/_BRnav.scss +74 -70
  189. package/src/css/_BRpages.scss +171 -42
  190. package/src/css/_BRsearch.scss +69 -30
  191. package/src/css/_BRtoolbar.scss +5 -5
  192. package/src/css/_TextSelection.scss +188 -24
  193. package/src/css/_colorbox.scss +2 -2
  194. package/src/css/_controls.scss +24 -7
  195. package/src/css/_icons.scss +8 -1
  196. package/src/{BookNavigator/assets → css}/button-base.js +2 -2
  197. package/src/css/icon_checkmark.js +9 -0
  198. package/src/css/sharedStyles.js +15 -0
  199. package/src/ia-bookreader/downloads/downloads-provider.js +81 -0
  200. package/src/{BookNavigator → ia-bookreader}/downloads/downloads.js +25 -5
  201. package/src/ia-bookreader/ia-bookreader.js +666 -0
  202. package/src/ia-bookreader/sharing.js +27 -0
  203. package/src/ia-bookreader/viewable-files.js +98 -0
  204. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments-provider.js +17 -17
  205. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments.js +75 -67
  206. package/src/{BookNavigator → plugins}/bookmarks/bookmark-button.js +4 -3
  207. package/src/{BookNavigator/assets → plugins/bookmarks}/bookmark-colors.js +1 -1
  208. package/src/{BookNavigator → plugins}/bookmarks/bookmark-edit.js +43 -31
  209. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-list.js +48 -49
  210. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-loginCTA.js +3 -3
  211. package/src/plugins/bookmarks/bookmarks-provider.js +63 -0
  212. package/src/{BookNavigator → plugins/bookmarks}/delete-modal-actions.js +1 -1
  213. package/src/{BookNavigator → plugins}/bookmarks/ia-bookmarks.js +117 -68
  214. package/src/plugins/plugin.archive_analytics.js +84 -78
  215. package/src/plugins/plugin.autoplay.js +99 -104
  216. package/src/plugins/plugin.chapters.js +319 -205
  217. package/src/plugins/plugin.experiments.js +339 -0
  218. package/src/plugins/plugin.iframe.js +1 -1
  219. package/src/plugins/plugin.iiif.js +141 -0
  220. package/src/plugins/plugin.resume.js +54 -51
  221. package/src/plugins/plugin.text_selection.js +539 -219
  222. package/src/plugins/plugin.vendor-fullscreen.js +5 -5
  223. package/src/plugins/search/plugin.search.js +374 -392
  224. package/src/{BookNavigator → plugins}/search/search-provider.js +59 -27
  225. package/src/{BookNavigator → plugins}/search/search-results.js +105 -76
  226. package/src/plugins/search/utils.js +50 -0
  227. package/src/plugins/search/view.js +50 -68
  228. package/src/plugins/translate/TranslationManager.js +164 -0
  229. package/src/plugins/translate/plugin.translate.js +512 -0
  230. package/src/plugins/tts/AbstractTTSEngine.js +78 -49
  231. package/src/plugins/tts/FestivalTTSEngine.js +20 -30
  232. package/src/plugins/tts/PageChunk.js +33 -21
  233. package/src/plugins/tts/PageChunkIterator.js +11 -17
  234. package/src/plugins/tts/WebTTSEngine.js +131 -91
  235. package/src/plugins/tts/plugin.tts.js +345 -350
  236. package/src/plugins/tts/utils.js +77 -49
  237. package/src/plugins/url/UrlPlugin.js +211 -0
  238. package/src/plugins/{plugin.url.js → url/plugin.url.js} +105 -20
  239. package/src/util/TextSelectionManager.js +532 -0
  240. package/src/util/browserSniffing.js +33 -1
  241. package/src/util/cache.js +20 -0
  242. package/src/util/docCookies.js +21 -2
  243. package/src/util/lit.js +15 -0
  244. package/src/util/strings.js +1 -0
  245. package/.babelrc +0 -12
  246. package/.dependabot/config.yml +0 -6
  247. package/.eslintrc.js +0 -50
  248. package/.gitattributes +0 -2
  249. package/.github/ISSUE_TEMPLATE/bug.md +0 -32
  250. package/.github/ISSUE_TEMPLATE/feature-request.md +0 -30
  251. package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -15
  252. package/.github/workflows/node.js.yml +0 -37
  253. package/.github/workflows/npm-publish.yml +0 -47
  254. package/.testcaferc.json +0 -5
  255. package/BookReader/bookreader-component-bundle.js +0 -1450
  256. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  257. package/BookReader/bookreader-component-bundle.js.map +0 -1
  258. package/BookReader/jquery-1.10.1.js +0 -2
  259. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  260. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  261. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  262. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  263. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  264. package/BookReaderDemo/BookReaderDemo.css +0 -41
  265. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -115
  266. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  267. package/BookReaderDemo/BookReaderJSSimple.js +0 -55
  268. package/BookReaderDemo/IIIFBookReader.js +0 -207
  269. package/BookReaderDemo/assets/v5/Bookreader-logo-cool-grad.svg +0 -1
  270. package/BookReaderDemo/assets/v5/Bookreader-logo-flat.svg +0 -1
  271. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-cool-grad.png +0 -0
  272. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-flat.png +0 -0
  273. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.png +0 -0
  274. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.svg +0 -1
  275. package/BookReaderDemo/assets/v5/Bookreader-logo-warm.svg +0 -1
  276. package/BookReaderDemo/assets/v5/bookreader-logo-renders@1x.png +0 -0
  277. package/BookReaderDemo/assets/v5/bookreader-logo-renders@2x.png +0 -0
  278. package/BookReaderDemo/assets/v5/bookreader-v5-screenshot.png +0 -0
  279. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  280. package/BookReaderDemo/demo-advanced.html +0 -33
  281. package/BookReaderDemo/demo-autoplay.html +0 -38
  282. package/BookReaderDemo/demo-embed-iframe-src.html +0 -84
  283. package/BookReaderDemo/demo-embed.html +0 -26
  284. package/BookReaderDemo/demo-fullscreen-mobile.html +0 -36
  285. package/BookReaderDemo/demo-fullscreen.html +0 -33
  286. package/BookReaderDemo/demo-iiif.html +0 -34
  287. package/BookReaderDemo/demo-iiif.js +0 -26
  288. package/BookReaderDemo/demo-internetarchive.html +0 -74
  289. package/BookReaderDemo/demo-multiple.html +0 -43
  290. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  291. package/BookReaderDemo/demo-preview-pages.html +0 -1092
  292. package/BookReaderDemo/demo-simple.html +0 -34
  293. package/BookReaderDemo/demo-vendor-fullscreen.html +0 -36
  294. package/BookReaderDemo/immersion-1up.html +0 -64
  295. package/BookReaderDemo/immersion-mode.html +0 -35
  296. package/BookReaderDemo/toggle_controls.html +0 -53
  297. package/BookReaderDemo/view_mode.html +0 -39
  298. package/BookReaderDemo/viewmode-cycle.html +0 -41
  299. package/CHANGELOG.md +0 -495
  300. package/CONTRIBUTING.md +0 -7
  301. package/codecov.yml +0 -17
  302. package/index.html +0 -31
  303. package/karma.conf.js +0 -23
  304. package/screenshot.png +0 -0
  305. package/scripts/postversion.js +0 -10
  306. package/scripts/preversion.js +0 -14
  307. package/scripts/version.js +0 -26
  308. package/src/BookNavigator/BookModel.js +0 -14
  309. package/src/BookNavigator/BookNavigator.js +0 -446
  310. package/src/BookNavigator/assets/book-loader.js +0 -27
  311. package/src/BookNavigator/assets/icon_checkmark.js +0 -6
  312. package/src/BookNavigator/assets/icon_close.js +0 -3
  313. package/src/BookNavigator/bookmarks/bookmarks-provider.js +0 -53
  314. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  315. package/src/BookNavigator/downloads/downloads-provider.js +0 -66
  316. package/src/BookNavigator/search/a-search-result.js +0 -55
  317. package/src/BookReader/DebugConsole.js +0 -54
  318. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  319. package/src/ItemNavigator/ItemNavigator.js +0 -376
  320. package/src/ItemNavigator/providers/sharing.js +0 -29
  321. package/src/css/_MobileNav.scss +0 -194
  322. package/src/dragscrollable-br.js +0 -261
  323. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  324. package/src/plugins/plugin.mobile_nav.js +0 -287
  325. package/tests/BookReader/BookModel.test.js +0 -312
  326. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  327. package/tests/BookReader/DebugConsole.test.js +0 -25
  328. package/tests/BookReader/ImageCache.test.js +0 -150
  329. package/tests/BookReader/Mode1Up.test.js +0 -164
  330. package/tests/BookReader/Mode2Up.test.js +0 -247
  331. package/tests/BookReader/Navbar/Navbar.test.js +0 -169
  332. package/tests/BookReader/PageContainer.test.js +0 -115
  333. package/tests/BookReader/ReduceSet.test.js +0 -38
  334. package/tests/BookReader/Toolbar/Toolbar.test.js +0 -26
  335. package/tests/BookReader/utils/classes.test.js +0 -88
  336. package/tests/BookReader/utils.test.js +0 -109
  337. package/tests/BookReader.options.test.js +0 -39
  338. package/tests/BookReader.test.js +0 -301
  339. package/tests/e2e/README.md +0 -75
  340. package/tests/e2e/autoplay.test.js +0 -13
  341. package/tests/e2e/base.test.js +0 -35
  342. package/tests/e2e/helpers/base.js +0 -263
  343. package/tests/e2e/helpers/debug.js +0 -13
  344. package/tests/e2e/helpers/desktopSearch.js +0 -72
  345. package/tests/e2e/helpers/mobileSearch.js +0 -85
  346. package/tests/e2e/helpers/mockSearch.js +0 -93
  347. package/tests/e2e/helpers/rightToLeft.js +0 -29
  348. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  349. package/tests/e2e/models/BookReader.js +0 -11
  350. package/tests/e2e/models/Navigation.js +0 -56
  351. package/tests/e2e/rightToLeft.test.js +0 -20
  352. package/tests/e2e/viewmode.test.js +0 -37
  353. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  354. package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +0 -133
  355. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +0 -222
  356. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  357. package/tests/karma/BookNavigator/search/search-results.test.js +0 -240
  358. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  359. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  360. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  361. package/tests/plugins/plugin.autoplay.test.js +0 -52
  362. package/tests/plugins/plugin.chapters.test.js +0 -130
  363. package/tests/plugins/plugin.iframe.test.js +0 -42
  364. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  365. package/tests/plugins/plugin.resume.test.js +0 -98
  366. package/tests/plugins/plugin.text_selection.test.js +0 -203
  367. package/tests/plugins/plugin.url.test.js +0 -129
  368. package/tests/plugins/plugin.vendor-fullscreen.test.js +0 -65
  369. package/tests/plugins/search/plugin.search.test.js +0 -173
  370. package/tests/plugins/search/plugin.search.view.test.js +0 -106
  371. package/tests/plugins/tts/AbstractTTSEngine.test.js +0 -153
  372. package/tests/plugins/tts/FestivalTTSEngine.test.js +0 -59
  373. package/tests/plugins/tts/PageChunk.test.js +0 -57
  374. package/tests/plugins/tts/PageChunkIterator.test.js +0 -179
  375. package/tests/plugins/tts/WebTTSEngine.test.js +0 -126
  376. package/tests/plugins/tts/utils.test.js +0 -133
  377. package/tests/util/browserSniffing.test.js +0 -56
  378. package/tests/util/docCookies.test.js +0 -15
  379. package/tests/util/strings.test.js +0 -63
  380. package/tests/utils.js +0 -42
  381. 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
+ }
@@ -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 {true}
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
 
@@ -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
+ }
@@ -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
@@ -1,12 +0,0 @@
1
- {
2
- "presets": [
3
- [
4
- "@babel/preset-env",
5
- {
6
- "targets": "> 2%, ie 11, edge 14, samsung > 9, OperaMini all, UCAndroid > 12",
7
- "useBuiltIns": "usage",
8
- "corejs": 3
9
- }
10
- ]
11
- ]
12
- }
@@ -1,6 +0,0 @@
1
- version: 1
2
-
3
- update_configs:
4
- - package_manager: "javascript"
5
- directory: "/"
6
- update_schedule: "monthly"