@internetarchive/bookreader 5.0.0-28 → 5.0.0-30-a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. package/.husky/_/husky.sh +30 -0
  2. package/BookReader/BookReader.css +1 -1
  3. package/BookReader/BookReader.js +1 -1
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/ia-bookreader-bundle.js +1458 -0
  6. package/BookReader/{bookreader-component-bundle.js.LICENSE.txt → ia-bookreader-bundle.js.LICENSE.txt} +12 -0
  7. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  8. package/BookReader/plugins/plugin.search.js +1 -1
  9. package/BookReader/plugins/plugin.search.js.map +1 -1
  10. package/BookReader/plugins/plugin.url.js +1 -1
  11. package/BookReader/plugins/plugin.url.js.map +1 -1
  12. package/BookReaderDemo/BookReaderDemo.css +14 -1
  13. package/BookReaderDemo/IADemoBr.js +107 -0
  14. package/BookReaderDemo/demo-internetarchive.html +64 -99
  15. package/CHANGELOG.md +4 -0
  16. package/package.json +9 -6
  17. package/src/BookNavigator/assets/ia-logo.js +17 -0
  18. package/src/BookNavigator/book-navigator.js +528 -0
  19. package/src/BookNavigator/bookmarks/bookmark-button.js +2 -1
  20. package/src/BookNavigator/bookmarks/bookmarks-provider.js +20 -8
  21. package/src/BookNavigator/bookmarks/ia-bookmarks.js +84 -51
  22. package/src/BookNavigator/downloads/downloads-provider.js +5 -9
  23. package/src/BookNavigator/downloads/downloads.js +1 -0
  24. package/src/BookNavigator/search/search-provider.js +15 -8
  25. package/src/BookNavigator/sharing.js +27 -0
  26. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +9 -8
  27. package/src/BookNavigator/volumes/volumes-provider.js +3 -4
  28. package/src/BookReader/options.js +6 -0
  29. package/src/BookReader.js +20 -8
  30. package/src/css/_BRComponent.scss +1 -1
  31. package/src/ia-bookreader/ia-bookreader.js +205 -0
  32. package/src/plugins/search/plugin.search.js +9 -9
  33. package/src/plugins/url/UrlPlugin.js +5 -6
  34. package/{src → stat}/BookNavigator/BookModel.js +0 -0
  35. package/{src → stat}/BookNavigator/BookNavigator.js +109 -102
  36. package/stat/BookNavigator/assets/bookmark-colors.js +15 -0
  37. package/stat/BookNavigator/assets/button-base.js +61 -0
  38. package/stat/BookNavigator/assets/ia-logo.js +17 -0
  39. package/stat/BookNavigator/assets/icon_checkmark.js +6 -0
  40. package/stat/BookNavigator/assets/icon_close.js +3 -0
  41. package/stat/BookNavigator/assets/icon_sort_asc.js +5 -0
  42. package/stat/BookNavigator/assets/icon_sort_desc.js +5 -0
  43. package/stat/BookNavigator/assets/icon_sort_neutral.js +5 -0
  44. package/stat/BookNavigator/assets/icon_volumes.js +11 -0
  45. package/stat/BookNavigator/bookmarks/bookmark-button.js +64 -0
  46. package/stat/BookNavigator/bookmarks/bookmark-edit.js +215 -0
  47. package/stat/BookNavigator/bookmarks/bookmarks-list.js +285 -0
  48. package/stat/BookNavigator/bookmarks/bookmarks-loginCTA.js +28 -0
  49. package/stat/BookNavigator/bookmarks/bookmarks-provider.js +56 -0
  50. package/stat/BookNavigator/bookmarks/ia-bookmarks.js +523 -0
  51. package/{src → stat}/BookNavigator/br-fullscreen-mgr.js +1 -2
  52. package/stat/BookNavigator/delete-modal-actions.js +49 -0
  53. package/stat/BookNavigator/downloads/downloads-provider.js +72 -0
  54. package/stat/BookNavigator/downloads/downloads.js +139 -0
  55. package/stat/BookNavigator/provider-config.js +0 -0
  56. package/stat/BookNavigator/search/a-search-result.js +55 -0
  57. package/stat/BookNavigator/search/search-provider.js +180 -0
  58. package/stat/BookNavigator/search/search-results.js +360 -0
  59. package/{src/ItemNavigator/providers → stat/BookNavigator}/sharing.js +3 -5
  60. package/stat/BookNavigator/visual-adjustments/visual-adjustments-provider.js +94 -0
  61. package/stat/BookNavigator/visual-adjustments/visual-adjustments.js +280 -0
  62. package/stat/BookNavigator/volumes/volumes-provider.js +83 -0
  63. package/stat/BookNavigator/volumes/volumes.js +178 -0
  64. package/stat/BookReader/BookModel.js +518 -0
  65. package/stat/BookReader/DebugConsole.js +54 -0
  66. package/stat/BookReader/DragScrollable.js +233 -0
  67. package/stat/BookReader/ImageCache.js +116 -0
  68. package/stat/BookReader/Mode1Up.js +102 -0
  69. package/stat/BookReader/Mode1UpLit.js +434 -0
  70. package/stat/BookReader/Mode2Up.js +1372 -0
  71. package/stat/BookReader/ModeSmoothZoom.js +177 -0
  72. package/stat/BookReader/ModeThumb.js +344 -0
  73. package/stat/BookReader/Navbar/Navbar.js +310 -0
  74. package/stat/BookReader/PageContainer.js +120 -0
  75. package/stat/BookReader/ReduceSet.js +26 -0
  76. package/stat/BookReader/Toolbar/Toolbar.js +384 -0
  77. package/stat/BookReader/events.js +20 -0
  78. package/stat/BookReader/options.js +324 -0
  79. package/stat/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  80. package/stat/BookReader/utils/classes.js +36 -0
  81. package/stat/BookReader/utils.js +240 -0
  82. package/stat/BookReader.js +2550 -0
  83. package/{src → stat}/BookReaderComponent/BookReaderComponent.js +15 -10
  84. package/stat/assets/icons/1up.svg +12 -0
  85. package/stat/assets/icons/2up.svg +15 -0
  86. package/stat/assets/icons/advance.svg +26 -0
  87. package/stat/assets/icons/chevron-right.svg +1 -0
  88. package/stat/assets/icons/close-circle-dark.svg +1 -0
  89. package/stat/assets/icons/close-circle.svg +1 -0
  90. package/stat/assets/icons/fullscreen.svg +17 -0
  91. package/stat/assets/icons/fullscreen_exit.svg +17 -0
  92. package/stat/assets/icons/hamburger.svg +15 -0
  93. package/stat/assets/icons/left-arrow.svg +12 -0
  94. package/stat/assets/icons/magnify-minus.svg +16 -0
  95. package/stat/assets/icons/magnify-plus.svg +17 -0
  96. package/stat/assets/icons/magnify.svg +15 -0
  97. package/stat/assets/icons/pause.svg +23 -0
  98. package/stat/assets/icons/play.svg +22 -0
  99. package/stat/assets/icons/playback-speed.svg +34 -0
  100. package/stat/assets/icons/read-aloud.svg +22 -0
  101. package/stat/assets/icons/review.svg +22 -0
  102. package/stat/assets/icons/thumbnails.svg +17 -0
  103. package/stat/assets/icons/voice.svg +1 -0
  104. package/stat/assets/icons/volume-full.svg +22 -0
  105. package/stat/assets/images/BRicons.png +0 -0
  106. package/stat/assets/images/BRicons.svg +94 -0
  107. package/stat/assets/images/BRicons_ia.png +0 -0
  108. package/stat/assets/images/back_pages.png +0 -0
  109. package/stat/assets/images/book_bottom_icon.png +0 -0
  110. package/stat/assets/images/book_down_icon.png +0 -0
  111. package/stat/assets/images/book_left_icon.png +0 -0
  112. package/stat/assets/images/book_leftmost_icon.png +0 -0
  113. package/stat/assets/images/book_right_icon.png +0 -0
  114. package/stat/assets/images/book_rightmost_icon.png +0 -0
  115. package/stat/assets/images/book_top_icon.png +0 -0
  116. package/stat/assets/images/book_up_icon.png +0 -0
  117. package/stat/assets/images/books_graphic.svg +177 -0
  118. package/stat/assets/images/booksplit.png +0 -0
  119. package/stat/assets/images/control_pause_icon.png +0 -0
  120. package/stat/assets/images/control_play_icon.png +0 -0
  121. package/stat/assets/images/embed_icon.png +0 -0
  122. package/stat/assets/images/icon-home-ia.png +0 -0
  123. package/stat/assets/images/icon_OL-logo-xs.png +0 -0
  124. package/stat/assets/images/icon_alert-xs.png +0 -0
  125. package/stat/assets/images/icon_book.svg +12 -0
  126. package/stat/assets/images/icon_bookmark.svg +12 -0
  127. package/stat/assets/images/icon_close-pop.png +0 -0
  128. package/stat/assets/images/icon_download.png +0 -0
  129. package/stat/assets/images/icon_gear.svg +14 -0
  130. package/stat/assets/images/icon_hamburger.svg +20 -0
  131. package/stat/assets/images/icon_home.png +0 -0
  132. package/stat/assets/images/icon_home.svg +21 -0
  133. package/stat/assets/images/icon_home_ia.png +0 -0
  134. package/stat/assets/images/icon_indicator.png +0 -0
  135. package/stat/assets/images/icon_info.svg +11 -0
  136. package/stat/assets/images/icon_one_page.svg +8 -0
  137. package/stat/assets/images/icon_pause.svg +1 -0
  138. package/stat/assets/images/icon_play.svg +1 -0
  139. package/stat/assets/images/icon_playback-rate.svg +15 -0
  140. package/stat/assets/images/icon_return.png +0 -0
  141. package/stat/assets/images/icon_search_button.svg +8 -0
  142. package/stat/assets/images/icon_share.svg +9 -0
  143. package/stat/assets/images/icon_skip-ahead.svg +6 -0
  144. package/stat/assets/images/icon_skip-back.svg +13 -0
  145. package/stat/assets/images/icon_speaker.svg +18 -0
  146. package/stat/assets/images/icon_speaker_open.svg +10 -0
  147. package/stat/assets/images/icon_thumbnails.svg +12 -0
  148. package/stat/assets/images/icon_toc.svg +5 -0
  149. package/stat/assets/images/icon_two_pages.svg +9 -0
  150. package/stat/assets/images/icon_zoomer.png +0 -0
  151. package/stat/assets/images/loading.gif +0 -0
  152. package/stat/assets/images/logo_icon.png +0 -0
  153. package/stat/assets/images/marker_chap-off.png +0 -0
  154. package/stat/assets/images/marker_chap-off.svg +11 -0
  155. package/stat/assets/images/marker_chap-off_ia.png +0 -0
  156. package/stat/assets/images/marker_chap-on.png +0 -0
  157. package/stat/assets/images/marker_chap-on.svg +11 -0
  158. package/stat/assets/images/marker_srch-on.svg +11 -0
  159. package/stat/assets/images/marker_srchchap-off.png +0 -0
  160. package/stat/assets/images/marker_srchchap-on.png +0 -0
  161. package/stat/assets/images/nav_control-dn.png +0 -0
  162. package/stat/assets/images/nav_control-dn_ia.png +0 -0
  163. package/stat/assets/images/nav_control-up.png +0 -0
  164. package/stat/assets/images/nav_control-up_ia.png +0 -0
  165. package/stat/assets/images/nav_control.png +0 -0
  166. package/stat/assets/images/one_page_mode_icon.png +0 -0
  167. package/stat/assets/images/paper-badge.png +0 -0
  168. package/stat/assets/images/print_icon.png +0 -0
  169. package/stat/assets/images/progressbar.gif +0 -0
  170. package/stat/assets/images/right_edges.png +0 -0
  171. package/stat/assets/images/slider.png +0 -0
  172. package/stat/assets/images/slider_ia.png +0 -0
  173. package/stat/assets/images/thumbnail_mode_icon.png +0 -0
  174. package/stat/assets/images/transparent.png +0 -0
  175. package/stat/assets/images/two_page_mode_icon.png +0 -0
  176. package/stat/assets/images/zoom_in_icon.png +0 -0
  177. package/stat/assets/images/zoom_out_icon.png +0 -0
  178. package/stat/css/BookReader.scss +89 -0
  179. package/stat/css/_BRBookmarks.scss +29 -0
  180. package/stat/css/_BRComponent.scss +13 -0
  181. package/stat/css/_BRfloat.scss +197 -0
  182. package/stat/css/_BRicon.scss +48 -0
  183. package/stat/css/_BRmain.scss +251 -0
  184. package/stat/css/_BRnav.scss +359 -0
  185. package/stat/css/_BRpages.scss +139 -0
  186. package/stat/css/_BRsearch.scss +226 -0
  187. package/stat/css/_BRtoolbar.scss +84 -0
  188. package/stat/css/_BRvendor.scss +5 -0
  189. package/stat/css/_MobileNav.scss +194 -0
  190. package/stat/css/_TextSelection.scss +32 -0
  191. package/stat/css/_colorbox.scss +52 -0
  192. package/stat/css/_controls.scss +253 -0
  193. package/stat/css/_icons.scss +121 -0
  194. package/stat/jquery-wrapper.js +4 -0
  195. package/stat/plugins/plugin.archive_analytics.js +86 -0
  196. package/stat/plugins/plugin.autoplay.js +129 -0
  197. package/stat/plugins/plugin.chapters.js +248 -0
  198. package/stat/plugins/plugin.iframe.js +48 -0
  199. package/stat/plugins/plugin.mobile_nav.js +288 -0
  200. package/stat/plugins/plugin.resume.js +68 -0
  201. package/stat/plugins/plugin.text_selection.js +291 -0
  202. package/stat/plugins/plugin.url.js +198 -0
  203. package/stat/plugins/plugin.vendor-fullscreen.js +247 -0
  204. package/stat/plugins/search/plugin.search.js +439 -0
  205. package/stat/plugins/search/view.js +439 -0
  206. package/stat/plugins/tts/AbstractTTSEngine.js +249 -0
  207. package/stat/plugins/tts/FestivalTTSEngine.js +169 -0
  208. package/stat/plugins/tts/PageChunk.js +107 -0
  209. package/stat/plugins/tts/PageChunkIterator.js +163 -0
  210. package/stat/plugins/tts/WebTTSEngine.js +357 -0
  211. package/stat/plugins/tts/plugin.tts.js +357 -0
  212. package/stat/plugins/tts/tooltip_dict.js +15 -0
  213. package/stat/plugins/tts/utils.js +91 -0
  214. package/stat/util/browserSniffing.js +30 -0
  215. package/stat/util/debouncer.js +26 -0
  216. package/stat/util/docCookies.js +67 -0
  217. package/stat/util/strings.js +34 -0
  218. package/tests/e2e/viewmode.test.js +30 -30
  219. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +64 -52
  220. package/tests/jest/plugins/url/UrlPlugin.test.js +33 -10
  221. package/tests/karma/BookNavigator/book-navigator.test.js +413 -108
  222. package/tests/karma/BookNavigator/bookmarks/bookmark-button.test.js +44 -0
  223. package/tests/karma/BookNavigator/downloads/downloads-provider.test.js +6 -3
  224. package/tests/karma/BookNavigator/search/search-provider.test.js +106 -6
  225. package/tests/karma/BookNavigator/search/search-results.test.js +0 -2
  226. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +29 -20
  227. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +41 -17
  228. package/webpack.config.js +1 -1
  229. package/.nvmrc +0 -1
  230. package/BookReader/bookreader-component-bundle.js +0 -1436
  231. package/BookReader/bookreader-component-bundle.js.map +0 -1
  232. package/src/BookNavigator/assets/book-loader.js +0 -27
  233. package/src/ItemNavigator/ItemNavigator.js +0 -377
@@ -0,0 +1,291 @@
1
+ //@ts-check
2
+ import { createSVGPageLayer } from '../BookReader/PageContainer.js';
3
+ import { isFirefox, isSafari } from '../util/browserSniffing.js';
4
+ import { applyVariables } from '../util/strings.js';
5
+ /** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
6
+ /** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
7
+
8
+ const BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);
9
+
10
+ export const DEFAULT_OPTIONS = {
11
+ enabled: true,
12
+ /** @type {StringWithVars} The URL to fetch the entire DJVU xml. Supports options.vars */
13
+ fullDjvuXmlUrl: null,
14
+ /** @type {StringWithVars} The URL to fetch a single page of the DJVU xml. Supports options.vars. Also has {{pageIndex}} */
15
+ singlePageDjvuXmlUrl: null,
16
+ };
17
+ /** @typedef {typeof DEFAULT_OPTIONS} TextSelectionPluginOptions */
18
+
19
+ /**
20
+ * @template T
21
+ */
22
+ export class Cache {
23
+ constructor(maxSize = 10) {
24
+ this.maxSize = maxSize;
25
+ /** @type {T[]} */
26
+ this.entries = [];
27
+ }
28
+
29
+ /**
30
+ * @param {T} entry
31
+ */
32
+ add(entry) {
33
+ if (this.entries.length >= this.maxSize) {
34
+ this.entries.shift();
35
+ }
36
+ this.entries.push(entry);
37
+ }
38
+ }
39
+
40
+ export class TextSelectionPlugin {
41
+
42
+ constructor(options = DEFAULT_OPTIONS, optionVariables, avoidTspans = isFirefox(), pointerEventsOnParagraph = isSafari()) {
43
+ this.options = options;
44
+ this.optionVariables = optionVariables;
45
+ /**@type {PromiseLike<JQuery<HTMLElement>|undefined>} */
46
+ this.djvuPagesPromise = null;
47
+ // Using text elements instead of tspans for words because Firefox does not allow svg tspan stretch.
48
+ // Tspans are necessary on Chrome because they prevent newline character after every word when copying
49
+ this.svgParagraphElement = "text";
50
+ this.svgWordElement = "tspan";
51
+ this.insertNewlines = avoidTspans;
52
+ // Safari has a bug where `pointer-events` doesn't work on `<tspans>`. So
53
+ // there we will set `pointer-events: all` on the paragraph element. We don't
54
+ // do this everywhere, because it's a worse experience. Thanks Safari :/
55
+ this.pointerEventsOnParagraph = pointerEventsOnParagraph;
56
+ if (avoidTspans) {
57
+ this.svgParagraphElement = "g";
58
+ this.svgWordElement = "text";
59
+ }
60
+
61
+ /** @type {Cache<{index: number, response: any}>} */
62
+ this.pageTextCache = new Cache();
63
+
64
+ /**
65
+ * Sometimes there are too many words on a page, and the browser becomes near
66
+ * unusable. For now don't render text layer for pages with too many words.
67
+ */
68
+ this.maxWordRendered = 2500;
69
+ }
70
+
71
+ init() {
72
+ // Only fetch the full djvu xml if the single page url isn't there
73
+ if (this.options.singlePageDjvuXmlUrl) return;
74
+ this.djvuPagesPromise = $.ajax({
75
+ type: "GET",
76
+ url: applyVariables(this.options.fullDjvuXmlUrl, this.optionVariables),
77
+ dataType: "html",
78
+ error: (e) => undefined
79
+ }).then((res) => {
80
+ try {
81
+ const xmlMap = $.parseXML(res);
82
+ return xmlMap && $(xmlMap).find("OBJECT");
83
+ } catch (e) {
84
+ return undefined;
85
+ }
86
+ });
87
+ }
88
+
89
+ /**
90
+ * @param {number} index
91
+ * @returns {Promise<HTMLElement|undefined>}
92
+ */
93
+ async getPageText(index) {
94
+ if (this.options.singlePageDjvuXmlUrl) {
95
+ const cachedEntry = this.pageTextCache.entries.find(x => x.index == index);
96
+ if (cachedEntry) {
97
+ return cachedEntry.response;
98
+ }
99
+ return $.ajax({
100
+ type: "GET",
101
+ url: applyVariables(this.options.singlePageDjvuXmlUrl, this.optionVariables, { pageIndex: index }),
102
+ dataType: "html",
103
+ error: (e) => undefined,
104
+ }).then((res) => {
105
+ try {
106
+ const xmlDoc = $.parseXML(res);
107
+ const result = xmlDoc && $(xmlDoc).find("OBJECT")[0];
108
+ this.pageTextCache.add({ index, response: result });
109
+ return result;
110
+ } catch (e) {
111
+ return undefined;
112
+ }
113
+ });
114
+ } else {
115
+ const XMLpagesArr = await this.djvuPagesPromise;
116
+ if (XMLpagesArr) return XMLpagesArr[index];
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Intercept copied text to remove any styling applied to it
122
+ * @param {JQuery} $container
123
+ */
124
+ interceptCopy($container) {
125
+ $container[0].addEventListener('copy', (event) => {
126
+ const selection = document.getSelection();
127
+ event.clipboardData.setData('text/plain', selection.toString());
128
+ event.preventDefault();
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Applies mouse events when in default mode
134
+ * @param {SVGElement} svg
135
+ */
136
+ defaultMode(svg) {
137
+ svg.classList.remove("selectingSVG");
138
+ $(svg).on("mousedown.textSelectPluginHandler", (event) => {
139
+ if (!$(event.target).is(".BRwordElement")) return;
140
+ event.stopPropagation();
141
+ svg.classList.add("selectingSVG");
142
+ $(svg).one("mouseup.textSelectPluginHandler", (event) => {
143
+ if (window.getSelection().toString() != "") {
144
+ event.stopPropagation();
145
+ $(svg).off(".textSelectPluginHandler");
146
+ this.textSelectingMode(svg);
147
+ }
148
+ else svg.classList.remove("selectingSVG");
149
+ });
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Applies mouse events when in textSelecting mode
155
+ * @param {SVGElement} svg
156
+ */
157
+ textSelectingMode(svg) {
158
+ $(svg).on('mousedown.textSelectPluginHandler', (event) => {
159
+ if (!$(event.target).is(".BRwordElement")) {
160
+ if (window.getSelection().toString() != "") window.getSelection().removeAllRanges();
161
+ }
162
+ event.stopPropagation();
163
+ });
164
+ $(svg).on('mouseup.textSelectPluginHandler', (event) => {
165
+ event.stopPropagation();
166
+ if (window.getSelection().toString() == "") {
167
+ $(svg).off(".textSelectPluginHandler");
168
+ this.defaultMode(svg); }
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Initializes text selection modes if there is an svg on the page
174
+ * @param {JQuery} $container
175
+ */
176
+ stopPageFlip($container) {
177
+ /** @type {JQuery<SVGElement>} */
178
+ const $svg = $container.find('svg.textSelectionSVG');
179
+ if (!$svg.length) return;
180
+ $svg.each((i, s) => this.defaultMode(s));
181
+ this.interceptCopy($container);
182
+ }
183
+
184
+ /**
185
+ * @param {PageContainer} pageContainer
186
+ */
187
+ async createTextLayer(pageContainer) {
188
+ const pageIndex = pageContainer.page.index;
189
+ const $container = pageContainer.$container;
190
+ const $svgLayers = $container.find('.textSelectionSVG');
191
+ if ($svgLayers.length) return;
192
+ const XMLpage = await this.getPageText(pageIndex);
193
+ if (!XMLpage) return;
194
+
195
+ const totalWords = $(XMLpage).find("WORD").length;
196
+ if (totalWords > this.maxWordRendered) {
197
+ console.log(`Page ${pageIndex} has too many words (${totalWords} > ${this.maxWordRendered}). Not rendering text layer.`);
198
+ return;
199
+ }
200
+
201
+ const svg = createSVGPageLayer(pageContainer.page, 'textSelectionSVG');
202
+ $container.append(svg);
203
+
204
+ $(XMLpage).find("PARAGRAPH").each((i, paragraph) => {
205
+ // Adding text element for each paragraph in the page
206
+ const words = $(paragraph).find("WORD");
207
+ if (!words.length) return;
208
+ const paragSvg = document.createElementNS("http://www.w3.org/2000/svg", this.svgParagraphElement);
209
+ paragSvg.setAttribute("class", "BRparagElement");
210
+ if (this.pointerEventsOnParagraph) {
211
+ paragSvg.style.pointerEvents = "all";
212
+ }
213
+
214
+ const wordHeightArr = [];
215
+
216
+ for (let i = 0; i < words.length; i++) {
217
+ // Adding tspan for each word in paragraph
218
+ const currWord = words[i];
219
+ // eslint-disable-next-line no-unused-vars
220
+ const [left, bottom, right, top] = $(currWord).attr("coords").split(',').map(parseFloat);
221
+ const wordHeight = bottom - top;
222
+ wordHeightArr.push(wordHeight);
223
+
224
+ const wordTspan = document.createElementNS("http://www.w3.org/2000/svg", this.svgWordElement);
225
+ wordTspan.setAttribute("class", "BRwordElement");
226
+ wordTspan.setAttribute("x", left.toString());
227
+ wordTspan.setAttribute("y", bottom.toString());
228
+ wordTspan.setAttribute("textLength", (right - left).toString());
229
+ wordTspan.setAttribute("lengthAdjust", "spacingAndGlyphs");
230
+ wordTspan.textContent = currWord.textContent;
231
+ paragSvg.appendChild(wordTspan);
232
+
233
+ // Adding spaces after words except at the end of the paragraph
234
+ // TODO: assumes left-to-right text
235
+ if (i < words.length - 1) {
236
+ const nextWord = words[i + 1];
237
+ // eslint-disable-next-line no-unused-vars
238
+ const [leftNext, bottomNext, rightNext, topNext] = $(nextWord).attr("coords").split(',').map(parseFloat);
239
+ const spaceTspan = document.createElementNS("http://www.w3.org/2000/svg", this.svgWordElement);
240
+ spaceTspan.setAttribute("class", "BRwordElement");
241
+ spaceTspan.setAttribute("x", right.toString());
242
+ spaceTspan.setAttribute("y", bottom.toString());
243
+ if ((leftNext - right) > 0) spaceTspan.setAttribute("textLength", (leftNext - right).toString());
244
+ spaceTspan.setAttribute("lengthAdjust", "spacingAndGlyphs");
245
+ spaceTspan.textContent = " ";
246
+ paragSvg.appendChild(spaceTspan);
247
+ }
248
+
249
+ // Adds newline at the end of paragraph on Firefox
250
+ if ((i == words.length - 1 && (this.insertNewlines))) {
251
+ paragSvg.appendChild(document.createTextNode("\n"));
252
+ }
253
+ }
254
+
255
+ wordHeightArr.sort();
256
+ const paragWordHeight = wordHeightArr[Math.floor(wordHeightArr.length * 0.85)];
257
+ paragSvg.setAttribute("font-size", paragWordHeight.toString());
258
+ svg.appendChild(paragSvg);
259
+ });
260
+ this.stopPageFlip($container);
261
+ }
262
+ }
263
+
264
+ export class BookreaderWithTextSelection extends BookReader {
265
+ init() {
266
+ const options = Object.assign({}, DEFAULT_OPTIONS, this.options.plugins.textSelection);
267
+ if (options.enabled) {
268
+ this.textSelectionPlugin = new TextSelectionPlugin(options, this.options.vars);
269
+ // Write this back; this way the plugin is the source of truth, and BR just
270
+ // contains a reference to it.
271
+ this.options.plugins.textSelection = options;
272
+ this.textSelectionPlugin.init();
273
+ }
274
+ super.init();
275
+ }
276
+
277
+ /**
278
+ * @param {number} index
279
+ */
280
+ _createPageContainer(index) {
281
+ const pageContainer = super._createPageContainer(index);
282
+ // Disable if thumb mode; it's too janky
283
+ // .page can be null for "pre-cover" region
284
+ if (this.mode !== this.constModeThumb && pageContainer.page) {
285
+ this.textSelectionPlugin?.createTextLayer(pageContainer);
286
+ }
287
+ return pageContainer;
288
+ }
289
+ }
290
+ window.BookReader = BookreaderWithTextSelection;
291
+ export default BookreaderWithTextSelection;
@@ -0,0 +1,198 @@
1
+ /* global BookReader */
2
+ /**
3
+ * Plugin for URL management in BookReader
4
+ * Note read more about the url "fragment" here:
5
+ * https://openlibrary.org/dev/docs/bookurls
6
+ */
7
+
8
+ jQuery.extend(BookReader.defaultOptions, {
9
+ enableUrlPlugin: true,
10
+ bookId: '',
11
+ /** @type {string} Defaults can be a urlFragment string */
12
+ defaults: null,
13
+ updateWindowTitle: false,
14
+
15
+ /** @type {'history' | 'hash'} */
16
+ urlMode: 'hash',
17
+
18
+ /**
19
+ * When using 'history' mode, this part of the URL is kept constant
20
+ * @example /details/plato/
21
+ */
22
+ urlHistoryBasePath: '/',
23
+
24
+ /** Only these params will be reflected onto the URL */
25
+ urlTrackedParams: ['page', 'search', 'mode', 'region', 'highlight', 'view'],
26
+
27
+ /** If true, don't update the URL when `page == n0 (eg "/page/n0")` */
28
+ urlTrackIndex0: false,
29
+ });
30
+
31
+ /** @override */
32
+ BookReader.prototype.setup = (function(super_) {
33
+ return function(options) {
34
+ super_.call(this, options);
35
+
36
+ this.bookId = options.bookId;
37
+ this.defaults = options.defaults;
38
+
39
+ this.locationPollId = null;
40
+ this.oldLocationHash = null;
41
+ this.oldUserHash = null;
42
+ };
43
+ })(BookReader.prototype.setup);
44
+
45
+ /** @override */
46
+ BookReader.prototype.init = (function(super_) {
47
+ return function() {
48
+
49
+ if (this.options.enableUrlPlugin) {
50
+ this.bind(BookReader.eventNames.PostInit, () => {
51
+ const { updateWindowTitle, urlMode } = this.options;
52
+ if (updateWindowTitle) {
53
+ document.title = this.shortTitle(50);
54
+ }
55
+ if (urlMode === 'hash') {
56
+ this.urlStartLocationPolling();
57
+ }
58
+ });
59
+
60
+ this.bind(BookReader.eventNames.fragmentChange,
61
+ this.urlUpdateFragment.bind(this)
62
+ );
63
+ }
64
+ super_.call(this);
65
+ };
66
+ })(BookReader.prototype.init);
67
+
68
+ /**
69
+ * Returns a shortened version of the title with the maximum number of characters
70
+ * @param {number} maximumCharacters
71
+ * @return {string}
72
+ */
73
+ BookReader.prototype.shortTitle = function(maximumCharacters) {
74
+ if (this.bookTitle.length < maximumCharacters) {
75
+ return this.bookTitle;
76
+ }
77
+
78
+ const title = `${this.bookTitle.substr(0, maximumCharacters - 3)}...`;
79
+ return title;
80
+ };
81
+
82
+ /**
83
+ * Starts polling of window.location to see hash fragment changes
84
+ */
85
+ BookReader.prototype.urlStartLocationPolling = function() {
86
+ this.oldLocationHash = this.urlReadFragment();
87
+
88
+ if (this.locationPollId) {
89
+ clearInterval(this.locationPollID);
90
+ this.locationPollId = null;
91
+ }
92
+
93
+ const updateHash = () => {
94
+ const newFragment = this.urlReadFragment();
95
+ const hasFragmentChange = (newFragment != this.oldLocationHash) && (newFragment != this.oldUserHash);
96
+
97
+ if (!hasFragmentChange) { return; }
98
+
99
+ const params = this.paramsFromFragment(newFragment);
100
+
101
+ const updateParams = () => this.updateFromParams(params);
102
+
103
+ this.trigger(BookReader.eventNames.stop);
104
+ if (this.animating) {
105
+ // Queue change if animating
106
+ if (this.autoStop) this.autoStop();
107
+ this.animationFinishedCallback = updateParams;
108
+ } else {
109
+ // update immediately
110
+ updateParams();
111
+ }
112
+ this.oldUserHash = newFragment;
113
+ };
114
+
115
+ this.locationPollId = setInterval(updateHash, 500);
116
+ };
117
+
118
+ /**
119
+ * Update URL from the current parameters.
120
+ * Call this instead of manually using window.location.replace
121
+ */
122
+ BookReader.prototype.urlUpdateFragment = function() {
123
+ const allParams = this.paramsFromCurrent();
124
+ const { urlMode, urlTrackIndex0, urlTrackedParams } = this.options;
125
+
126
+ if (!urlTrackIndex0
127
+ && (typeof(allParams.index) !== 'undefined')
128
+ && allParams.index === 0) {
129
+ delete allParams.index;
130
+ delete allParams.page;
131
+ }
132
+
133
+ const params = urlTrackedParams.reduce((validParams, paramName) => {
134
+ if (paramName in allParams) {
135
+ validParams[paramName] = allParams[paramName];
136
+ }
137
+ return validParams;
138
+ }, {});
139
+
140
+ const newFragment = this.fragmentFromParams(params, urlMode);
141
+ const currFragment = this.urlReadFragment();
142
+ const currQueryString = this.getLocationSearch();
143
+ const newQueryString = this.queryStringFromParams(params, currQueryString, urlMode);
144
+ if (currFragment === newFragment && currQueryString === newQueryString) {
145
+ return;
146
+ }
147
+
148
+ if (urlMode === 'history') {
149
+ if (window.history && window.history.replaceState) {
150
+ const baseWithoutSlash = this.options.urlHistoryBasePath.replace(/\/+$/, '');
151
+ const newFragmentWithSlash = newFragment === '' ? '' : `/${newFragment}`;
152
+
153
+ const newUrlPath = `${baseWithoutSlash}${newFragmentWithSlash}${newQueryString}`;
154
+ window.history.replaceState({}, null, newUrlPath);
155
+ this.oldLocationHash = newFragment + newQueryString;
156
+
157
+ }
158
+ } else {
159
+ const newQueryStringSearch = this.urlParamsFiltersOnlySearch(this.readQueryString());
160
+ window.location.replace('#' + newFragment + newQueryStringSearch);
161
+ this.oldLocationHash = newFragment + newQueryStringSearch;
162
+
163
+ }
164
+ };
165
+
166
+ /**
167
+ * @private
168
+ * Filtering query parameters to select only book search param (?q=foo)
169
+ This needs to be updated/URL system modified if future query params are to be added
170
+ * @param {string} url
171
+ * @return {string}
172
+ * */
173
+ BookReader.prototype.urlParamsFiltersOnlySearch = function(url) {
174
+ const params = new URLSearchParams(url);
175
+ return params.has('q') ? `?${new URLSearchParams({ q: params.get('q') })}` : '';
176
+ };
177
+
178
+
179
+ /**
180
+ * Will read either the hash or URL and return the bookreader fragment
181
+ * @return {string}
182
+ */
183
+ BookReader.prototype.urlReadFragment = function() {
184
+ const { urlMode, urlHistoryBasePath } = this.options;
185
+ if (urlMode === 'history') {
186
+ return window.location.pathname.substr(urlHistoryBasePath.length);
187
+ } else {
188
+ return window.location.hash.substr(1);
189
+ }
190
+ };
191
+
192
+ /**
193
+ * Will read the hash return the bookreader fragment
194
+ * @return {string}
195
+ */
196
+ BookReader.prototype.urlReadHashFragment = function() {
197
+ return window.location.hash.substr(1);
198
+ };