@internetarchive/bookreader 5.0.0-65 → 5.0.0-67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/BookReader/BookReader.css +16 -28
  2. package/BookReader/BookReader.js +1 -1
  3. package/BookReader/BookReader.js.LICENSE.txt +0 -6
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/ia-bookreader-bundle.js +102 -114
  6. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  7. package/BookReader/plugins/plugin.chapters.js +24 -1
  8. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  9. package/BookReader/plugins/plugin.search.js +1 -1
  10. package/BookReader/plugins/plugin.search.js.map +1 -1
  11. package/BookReader/plugins/plugin.text_selection.js +1 -1
  12. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  13. package/BookReader/plugins/plugin.tts.js +1 -1
  14. package/BookReader/plugins/plugin.tts.js.map +1 -1
  15. package/BookReaderDemo/demo-internetarchive.html +41 -3
  16. package/BookReaderDemo/toggle_controls.html +1 -1
  17. package/CHANGELOG.md +8 -0
  18. package/index.html +1 -1
  19. package/package.json +5 -4
  20. package/src/BookNavigator/book-navigator.js +4 -3
  21. package/src/BookReader/BookModel.js +12 -0
  22. package/src/BookReader/Mode1Up.js +1 -1
  23. package/src/BookReader/Mode1UpLit.js +0 -3
  24. package/src/BookReader/Mode2UpLit.js +0 -4
  25. package/src/BookReader/ModeSmoothZoom.js +153 -52
  26. package/src/BookReader/Navbar/Navbar.js +2 -2
  27. package/src/css/_BRnav.scss +5 -2
  28. package/src/css/_BRsearch.scss +6 -2
  29. package/src/css/_MobileNav.scss +0 -26
  30. package/src/css/_controls.scss +3 -2
  31. package/src/plugins/plugin.chapters.js +201 -169
  32. package/src/plugins/tts/plugin.tts.js +1 -1
  33. package/src/util/browserSniffing.js +22 -0
  34. package/tests/jest/BookReader/ModeSmoothZoom.test.js +75 -32
  35. package/tests/jest/plugins/plugin.chapters.test.js +93 -76
@@ -161,6 +161,44 @@
161
161
  </li>
162
162
  </ul>
163
163
  </li>
164
+ <li>
165
+ Chapters
166
+ <ul>
167
+ <li>
168
+ Normal book with chapters
169
+ <ul>
170
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=adventureofsherl0000unse">
171
+ <i>The Adventures of Sherlock Holmes</i> by Arthur Conan Doyle
172
+ </a></li>
173
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=jalna00dela">
174
+ <i>Jalna</i> by Mazo de la Roche
175
+ </a></li>
176
+ </ul>
177
+ </li>
178
+ <li>
179
+ Book preview
180
+ <ul>
181
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=peoplewareproduc00dema_0">
182
+ <i>Peopleware</i> by Tom DeMarco and Timothy Lister
183
+ </a></li>
184
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=passionforbooksb00rabi">
185
+ <i>A Passion for Books</i>
186
+ </a></li>
187
+ </ul>
188
+ </li>
189
+ <li>
190
+ Book with levelled chapters
191
+ <ul>
192
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=notredamedepar01hugo">
193
+ <i>Notre-Dame de Paris: Tome Premier</i> par Victor Hugo
194
+ </a></li>
195
+ <li><a href="/BookReaderDemo/demo-internetarchive.html?ocaid=alanturingenigma0000hodg">
196
+ <i>Alan Turing: The Enigma</i> by Andrew Hodges
197
+ </a></li>
198
+ </ul>
199
+ </li>
200
+ </ul>
201
+ </li>
164
202
  </ul>
165
203
  </details>
166
204
  <div class="demo">
@@ -220,9 +258,9 @@
220
258
 
221
259
  // analytics stub
222
260
  window.archive_analytics = {
223
- send_event_no_sampling: (category, action, label) => console.log('~~~ NO SAMPLE EVENT CALLED: ', { category, action, label }),
224
- send_event: (category, action, label) => console.log('~~~ send_event SAMPLE EVENT CALLED: ', { category, action, label }),
225
- send_ping: (category, action, label) => console.log('~~~ send_ping SAMPLE EVENT CALLED: ', { category, action, label }),
261
+ send_event_no_sampling: (category, action, label) => {}, //console.log('~~~ NO SAMPLE EVENT CALLED: ', { category, action, label }),
262
+ send_event: (category, action, label) => {}, //console.log('~~~ send_event SAMPLE EVENT CALLED: ', { category, action, label }),
263
+ send_ping: (category, action, label) => {}, //console.log('~~~ send_ping SAMPLE EVENT CALLED: ', { category, action, label }),
226
264
  }
227
265
  </script>
228
266
 
@@ -40,7 +40,7 @@
40
40
  template: function(br) {
41
41
  return '<button class="BRicon ' + this.className + ' desktop-only js-tooltip" data-param="foo">' +
42
42
  '<div class="icon icon-onepg"></div>' +
43
- '<span class="tooltip">Overridden control</span>' +
43
+ '<span class="BRtooltip">Overridden control</span>' +
44
44
  '</button>';
45
45
  }
46
46
  },
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 5.0.0-67
2
+ - Feature: Re-enable chapters plugin + migrate off mmenu @cdrini
3
+ - Fix: Disable tooltips on touchscreens + fix on IA @cdrini
4
+ - Dev: Remove core-js update block; issue resolved in core-js @cdrini
5
+
6
+ # 5.0.0-66
7
+ - Fix: Pinch zooming on iPad/iPhone, Samsung Internet @cdrini
8
+
1
9
  # 5.0.0-65
2
10
  - Dev: Remove Debug console dev helper @cdrini
3
11
  - Dev: Fix deno esm.sh esbuild erroring @cdrini
package/index.html CHANGED
@@ -21,7 +21,7 @@
21
21
  <!-- plugin.search.js -->
22
22
  <li><a href="BookReaderDemo/demo-internetarchive.html?ocaid=theworksofplato01platiala">From Internet Archive</a></li>
23
23
  <!-- plugin.search.js -->
24
- <li><a href="BookReaderDemo/demo-internetarchive.html?ocaid=adventuresofoli00dick">From Internet Archive - a book with CHAPTERS</a></li>
24
+ <li><a href="BookReaderDemo/demo-internetarchive.html?ocaid=adventureofsherl0000unse">From Internet Archive - a book with CHAPTERS</a></li>
25
25
  <li><a href="BookReaderDemo/demo-iiif.html">IIIF</a></li>
26
26
  <li><a href="BookReaderDemo/demo-autoplay.html">Autoplay (kiosk mode)</a></li>
27
27
  <li><a href="BookReaderDemo/demo-plugin-menu-toggle.html">Plugin: Full screen menu toggle</a></li>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-65",
3
+ "version": "5.0.0-67",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "private": false,
27
27
  "dependencies": {
28
28
  "@internetarchive/ia-activity-indicator": "^0.0.4",
29
- "@internetarchive/ia-item-navigator": "^1.0.3",
29
+ "@internetarchive/ia-item-navigator": "^1.0.4",
30
30
  "@internetarchive/ia-sharing-options": "^1.0.2",
31
31
  "@internetarchive/icon-bookmark": "^1.3.4",
32
32
  "@internetarchive/icon-dl": "^1.3.4",
@@ -35,6 +35,7 @@
35
35
  "@internetarchive/icon-magnify-plus": "^1.3.4",
36
36
  "@internetarchive/icon-search": "^1.3.4",
37
37
  "@internetarchive/icon-share": "^1.3.4",
38
+ "@internetarchive/icon-toc": "^1.3.4",
38
39
  "@internetarchive/icon-visual-adjustment": "^1.3.4",
39
40
  "@internetarchive/modal-manager": "^0.2.12",
40
41
  "@internetarchive/shared-resize-observer": "^0.2.0",
@@ -57,8 +58,8 @@
57
58
  "eslint": "^7.32.0",
58
59
  "eslint-plugin-no-jquery": "^2.7.0",
59
60
  "eslint-plugin-testcafe": "^0.2.1",
60
- "hammerjs": "^2.0.8",
61
61
  "http-server": "14.1.1",
62
+ "interactjs": "^1.10.18",
62
63
  "iso-language-codes": "1.1.0",
63
64
  "jest": "29.6.2",
64
65
  "jest-environment-jsdom": "^29.4.3",
@@ -121,7 +122,7 @@
121
122
  "DOCS:update:test-deps": "If CI succeeds, these should be good to update",
122
123
  "update:test-deps": "npm i @babel/eslint-parser@latest @open-wc/testing-helpers@latest @types/jest@latest codecov@latest eslint@7 eslint-plugin-testcafe@latest jest@latest sinon@latest testcafe@latest",
123
124
  "DOCS:update:build-deps": "These can cause strange changes, so do an npm run build + check file size (git diff --stat), and check the site is as expected",
124
- "update:build-deps": "npm i @babel/core@latest @babel/preset-env@latest @babel/plugin-proposal-class-properties@latest @babel/plugin-proposal-decorators@latest babel-loader@latest core-js@3.27.1 regenerator-runtime@latest sass@latest svgo@latest webpack@latest webpack-cli@latest",
125
+ "update:build-deps": "npm i @babel/core@latest @babel/preset-env@latest @babel/plugin-proposal-class-properties@latest @babel/plugin-proposal-decorators@latest babel-loader@latest core-js@latest regenerator-runtime@latest sass@latest svgo@latest webpack@latest webpack-cli@latest",
125
126
  "codecov": "npx codecov"
126
127
  }
127
128
  }
@@ -239,7 +239,7 @@ export class BookNavigator extends LitElement {
239
239
  });
240
240
  }
241
241
 
242
- this.menuProviders = providers;
242
+ Object.assign(this.menuProviders, providers);
243
243
  this.addMenuShortcut('search');
244
244
  this.addMenuShortcut('volumes');
245
245
  this.updateMenuContents();
@@ -306,9 +306,9 @@ export class BookNavigator extends LitElement {
306
306
  */
307
307
  updateMenuContents() {
308
308
  const {
309
- search, downloads, visualAdjustments, share, bookmarks, volumes
309
+ search, downloads, visualAdjustments, share, bookmarks, volumes, chapters
310
310
  } = this.menuProviders;
311
- const availableMenus = [volumes, search, bookmarks, visualAdjustments, share].filter((menu) => !!menu);
311
+ const availableMenus = [volumes, chapters, search, bookmarks, visualAdjustments, share].filter((menu) => !!menu);
312
312
 
313
313
  if (this.shouldShowDownloadsMenu()) {
314
314
  downloads?.update(this.downloadableTypes);
@@ -410,6 +410,7 @@ export class BookNavigator extends LitElement {
410
410
  bindEventListeners() {
411
411
  window.addEventListener('BookReader:PostInit', (e) => {
412
412
  this.bookreader = e.detail.props;
413
+ this.bookreader.shell = this;
413
414
  this.bookReaderLoaded = true;
414
415
  this.bookReaderCannotLoad = false;
415
416
  this.emitLoadingStatusUpdate(true);
@@ -30,6 +30,18 @@ export class BookModel {
30
30
  this._medianPageSize = null;
31
31
  /** @type {[PageData[], number]} */
32
32
  this._getDataFlattenedCached = null;
33
+
34
+ // Heal missing first page number assertion
35
+ const pages = this._getDataFlattened();
36
+ const firstNumberedPageIndex = pages.findIndex(page => page.pageNum != undefined && !isNaN(parseFloat(page.pageNum)));
37
+ if (firstNumberedPageIndex != -1 && firstNumberedPageIndex > 0) {
38
+ const pageNum = parseFloat(pages[firstNumberedPageIndex].pageNum);
39
+ if (!isNaN(pageNum)) {
40
+ // Note: Since the pages are always sorted in increasing pageNum/index
41
+ // order, this will work for both left-to-right and right-to-left books
42
+ pages[firstNumberedPageIndex - 1].pageNum = pageNum - 1;
43
+ }
44
+ }
33
45
  }
34
46
 
35
47
  /** Get median width/height of page in inches. Memoized for performance. */
@@ -51,7 +51,7 @@ export class Mode1Up {
51
51
  new DragScrollable(this.mode1UpLit, {
52
52
  preventDefault: true,
53
53
  dragSelector: '.br-mode-1up__visible-world',
54
- // Only handle mouse events; let browser/HammerJS handle touch
54
+ // Only handle mouse events; let browser/interact.js handle touch
55
55
  dragstart: 'mousedown',
56
56
  dragcontinue: 'mousemove',
57
57
  dragend: 'mouseup',
@@ -47,9 +47,6 @@ export class Mode1UpLit extends LitElement {
47
47
 
48
48
  @property({ type: Number })
49
49
  scale = 1;
50
- /** Position (in unit-less, [0, 1] coordinates) in client to scale around */
51
- @property({ type: Object })
52
- scaleCenter = { x: 0.5, y: 0.5 };
53
50
 
54
51
  /************** VIRTUAL-SCROLLING PROPERTIES **************/
55
52
 
@@ -41,10 +41,6 @@ export class Mode2UpLit extends LitElement {
41
41
 
42
42
  initialScale = 1;
43
43
 
44
- /** Position (in unit-less, [0, 1] coordinates) in client to scale around */
45
- @property({ type: Object })
46
- scaleCenter = { x: 0.5, y: 0.5 };
47
-
48
44
  /** @type {import('./options').AutoFitValues} */
49
45
  @property({ type: String })
50
46
  autoFit = 'auto';
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
- import Hammer from "hammerjs";
2
+ import interact from 'interactjs';
3
+ import { isIOS, isSamsungInternet } from '../util/browserSniffing.js';
4
+ import { sleep } from './utils.js';
3
5
  /** @typedef {import('./utils/HTMLDimensionsCacher.js').HTMLDimensionsCacher} HTMLDimensionsCacher */
4
6
 
5
7
  /**
@@ -8,7 +10,6 @@ import Hammer from "hammerjs";
8
10
  * @property {HTMLElement} $visibleWorld
9
11
  * @property {import("./options.js").AutoFitValues} autoFit
10
12
  * @property {number} scale
11
- * @property {{ x: number, y: number }} scaleCenter
12
13
  * @property {HTMLDimensionsCacher} htmlDimensionsCacher
13
14
  * @property {function(): void} [attachScrollListeners]
14
15
  * @property {function(): void} [detachScrollListeners]
@@ -16,31 +17,27 @@ import Hammer from "hammerjs";
16
17
 
17
18
  /** Manages pinch-zoom, ctrl-wheel, and trackpad pinch smooth zooming. */
18
19
  export class ModeSmoothZoom {
20
+ /** Position (in unit-less, [0, 1] coordinates) in client to scale around */
21
+ scaleCenter = { x: 0.5, y: 0.5 };
22
+
19
23
  /** @param {SmoothZoomable} mode */
20
24
  constructor(mode) {
21
25
  /** @type {SmoothZoomable} */
22
26
  this.mode = mode;
23
27
 
28
+ /** Whether a pinch is currently happening */
29
+ this.pinching = false;
24
30
  /** Non-null when a scale has been enqueued/is being processed by the buffer function */
25
31
  this.pinchMoveFrame = null;
26
32
  /** Promise for the current/enqueued pinch move frame. Resolves when it is complete. */
27
33
  this.pinchMoveFramePromise = Promise.resolve();
28
34
  this.oldScale = 1;
29
- /** @type {{ scale: number, center: { x: number, y: number }}} */
35
+ /** @type {{ scale: number, clientX: number, clientY: number }}} */
30
36
  this.lastEvent = null;
31
37
  this.attached = false;
32
38
 
33
39
  /** @type {function(function(): void): any} */
34
40
  this.bufferFn = window.requestAnimationFrame.bind(window);
35
-
36
- // Hammer.js by default set userSelect to None; we don't want that!
37
- // TODO: Is there any way to do this not globally on Hammer?
38
- delete Hammer.defaults.cssProps.userSelect;
39
- this.hammer = new Hammer.Manager(this.mode.$container, {
40
- touchAction: "pan-x pan-y",
41
- });
42
-
43
- this.hammer.add(new Hammer.Pinch());
44
41
  }
45
42
 
46
43
  attach() {
@@ -48,17 +45,44 @@ export class ModeSmoothZoom {
48
45
 
49
46
  this.attachCtrlZoom();
50
47
 
51
- // GestureEvents work only on Safari; they interfere with Hammer,
52
- // so block them.
53
- this.mode.$container.addEventListener('gesturestart', this._preventEvent);
48
+ // GestureEvents work only on Safari; they're too glitchy to use
49
+ // fully, but they can sometimes help error correct when interact
50
+ // misses an end/start event on Safari due to Safari bugs.
51
+ this.mode.$container.addEventListener('gesturestart', this._pinchStart);
54
52
  this.mode.$container.addEventListener('gesturechange', this._preventEvent);
55
- this.mode.$container.addEventListener('gestureend', this._preventEvent);
53
+ this.mode.$container.addEventListener('gestureend', this._pinchEnd);
54
+
55
+ if (isIOS()) {
56
+ this.touchesMonitor = new TouchesMonitor(this.mode.$container);
57
+ this.touchesMonitor.attach();
58
+ }
59
+
60
+ this.mode.$container.style.touchAction = "pan-x pan-y";
56
61
 
57
62
  // The pinch listeners
58
- this.hammer.on("pinchstart", this._pinchStart);
59
- this.hammer.on("pinchmove", this._pinchMove);
60
- this.hammer.on("pinchend", this._pinchEnd);
61
- this.hammer.on("pinchcancel", this._pinchCancel);
63
+ this.interact = interact(this.mode.$container);
64
+ this.interact.gesturable({
65
+ listeners: {
66
+ start: this._pinchStart,
67
+ end: this._pinchEnd,
68
+ }
69
+ });
70
+ if (isSamsungInternet()) {
71
+ // Samsung internet pinch-zoom will not work unless we disable
72
+ // all touch actions. So use interact.js' built-in drag support
73
+ // to handle moving on that browser.
74
+ this.mode.$container.style.touchAction = "none";
75
+ this.interact
76
+ .draggable({
77
+ inertia: {
78
+ resistance: 2,
79
+ minSpeed: 100,
80
+ allowResume: true,
81
+ },
82
+ listeners: { move: this._dragMove }
83
+ });
84
+ }
85
+
62
86
 
63
87
  this.attached = true;
64
88
  }
@@ -68,15 +92,15 @@ export class ModeSmoothZoom {
68
92
 
69
93
  // GestureEvents work only on Safari; they interfere with Hammer,
70
94
  // so block them.
71
- this.mode.$container.removeEventListener('gesturestart', this._preventEvent);
95
+ this.mode.$container.removeEventListener('gesturestart', this._pinchStart);
72
96
  this.mode.$container.removeEventListener('gesturechange', this._preventEvent);
73
- this.mode.$container.removeEventListener('gestureend', this._preventEvent);
97
+ this.mode.$container.removeEventListener('gestureend', this._pinchEnd);
98
+
99
+ this.touchesMonitor?.detach?.();
74
100
 
75
101
  // The pinch listeners
76
- this.hammer.off("pinchstart", this._pinchStart);
77
- this.hammer.off("pinchmove", this._pinchMove);
78
- this.hammer.off("pinchend", this._pinchEnd);
79
- this.hammer.off("pinchcancel", this._pinchCancel);
102
+ this.interact.unset();
103
+ interact.removeDocument(document);
80
104
 
81
105
  this.attached = false;
82
106
  }
@@ -87,7 +111,16 @@ export class ModeSmoothZoom {
87
111
  return false;
88
112
  }
89
113
 
90
- _pinchStart = () => {
114
+ _pinchStart = async () => {
115
+ // Safari calls gesturestart twice!
116
+ if (this.pinching) return;
117
+ if (isIOS()) {
118
+ // Safari sometimes causes a pinch to trigger when there's only one touch!
119
+ await sleep(0); // touches monitor can receive the touch event late
120
+ if (this.touchesMonitor.touches < 2) return;
121
+ }
122
+ this.pinching = true;
123
+
91
124
  // Do this in case the pinchend hasn't fired yet.
92
125
  this.oldScale = 1;
93
126
  this.mode.$visibleWorld.classList.add("BRsmooth-zooming");
@@ -95,37 +128,44 @@ export class ModeSmoothZoom {
95
128
  this.mode.autoFit = "none";
96
129
  this.detachCtrlZoom();
97
130
  this.mode.detachScrollListeners?.();
131
+
132
+ this.interact.gesturable({
133
+ listeners: {
134
+ start: this._pinchStart,
135
+ move: this._pinchMove,
136
+ end: this._pinchEnd,
137
+ }
138
+ });
98
139
  }
99
140
 
100
- /** @param {{ scale: number, center: { x: number, y: number }}} e */
141
+ /** @param {{ scale: number, clientX: number, clientY: number }}} e */
101
142
  _pinchMove = async (e) => {
102
- this.lastEvent = e;
143
+ if (!this.pinching) return;
144
+ this.lastEvent = {
145
+ scale: e.scale,
146
+ clientX: e.clientX,
147
+ clientY: e.clientY,
148
+ };
103
149
  if (!this.pinchMoveFrame) {
104
- let pinchMoveFramePromiseRes = null;
105
- this.pinchMoveFramePromise = new Promise(
106
- (res) => (pinchMoveFramePromiseRes = res)
107
- );
108
-
109
150
  // Buffer these events; only update the scale when request animation fires
110
- this.pinchMoveFrame = this.bufferFn(() => {
111
- this.updateScaleCenter({
112
- clientX: this.lastEvent.center.x,
113
- clientY: this.lastEvent.center.y,
114
- });
115
- this.mode.scale *= this.lastEvent.scale / this.oldScale;
116
- this.oldScale = this.lastEvent.scale;
117
- this.pinchMoveFrame = null;
118
- pinchMoveFramePromiseRes();
119
- });
151
+ this.pinchMoveFrame = this.bufferFn(this._drawPinchZoomFrame);
120
152
  }
121
153
  }
122
154
 
123
155
  _pinchEnd = async () => {
156
+ if (!this.pinching) return;
157
+ this.pinching = false;
158
+ this.interact.gesturable({
159
+ listeners: {
160
+ start: this._pinchStart,
161
+ end: this._pinchEnd,
162
+ }
163
+ });
124
164
  // Want this to happen after the pinchMoveFrame,
125
165
  // if one is in progress; otherwise setting oldScale
126
166
  // messes up the transform.
127
167
  await this.pinchMoveFramePromise;
128
- this.mode.scaleCenter = { x: 0.5, y: 0.5 };
168
+ this.scaleCenter = { x: 0.5, y: 0.5 };
129
169
  this.oldScale = 1;
130
170
  this.mode.$visibleWorld.classList.remove("BRsmooth-zooming");
131
171
  this.mode.$visibleWorld.style.willChange = "auto";
@@ -133,10 +173,42 @@ export class ModeSmoothZoom {
133
173
  this.mode.attachScrollListeners?.();
134
174
  }
135
175
 
136
- _pinchCancel = async () => {
137
- // iOS fires pinchcancel ~randomly; it looks like it sometimes
138
- // thinks the pinch becomes a pan, at which point it cancels?
139
- await this._pinchEnd();
176
+ _drawPinchZoomFrame = async () => {
177
+ // Because of the buffering/various timing locks,
178
+ // this can be called after the pinch has ended, which
179
+ // results in a janky zoom after the pinch.
180
+ if (!this.pinching) {
181
+ this.pinchMoveFrame = null;
182
+ return;
183
+ }
184
+
185
+ this.mode.$container.style.overflow = "hidden";
186
+ this.pinchMoveFramePromiseRes = null;
187
+ this.pinchMoveFramePromise = new Promise(
188
+ (res) => (this.pinchMoveFramePromiseRes = res)
189
+ );
190
+ this.updateScaleCenter({
191
+ clientX: this.lastEvent.clientX,
192
+ clientY: this.lastEvent.clientY,
193
+ });
194
+ const curScale = this.mode.scale;
195
+ const newScale = curScale * this.lastEvent.scale / this.oldScale;
196
+
197
+ if (curScale != newScale) {
198
+ this.mode.scale = newScale;
199
+ await this.pinchMoveFramePromise;
200
+ }
201
+ this.mode.$container.style.overflow = "auto";
202
+ this.oldScale = this.lastEvent.scale;
203
+ this.pinchMoveFrame = null;
204
+ }
205
+
206
+ _dragMove = async (e) => {
207
+ if (this.pinching) {
208
+ await this._pinchEnd();
209
+ }
210
+ this.mode.$container.scrollTop -= e.dy;
211
+ this.mode.$container.scrollLeft -= e.dx;
140
212
  }
141
213
 
142
214
  /** @private */
@@ -174,7 +246,7 @@ export class ModeSmoothZoom {
174
246
  */
175
247
  updateScaleCenter({ clientX, clientY }) {
176
248
  const bc = this.mode.htmlDimensionsCacher.boundingClientRect;
177
- this.mode.scaleCenter = {
249
+ this.scaleCenter = {
178
250
  x: (clientX - bc.left) / this.mode.htmlDimensionsCacher.clientWidth,
179
251
  y: (clientY - bc.top) / this.mode.htmlDimensionsCacher.clientHeight,
180
252
  };
@@ -194,8 +266,8 @@ export class ModeSmoothZoom {
194
266
  const F = newScale / oldScale;
195
267
 
196
268
  // Where in the viewport the zoom is centered on
197
- const XPOS = this.mode.scaleCenter.x;
198
- const YPOS = this.mode.scaleCenter.y;
269
+ const XPOS = this.scaleCenter.x;
270
+ const YPOS = this.scaleCenter.y;
199
271
  const oldCenter = {
200
272
  x: L + XPOS * W,
201
273
  y: T + YPOS * H,
@@ -207,5 +279,34 @@ export class ModeSmoothZoom {
207
279
 
208
280
  container.scrollTop = newCenter.y - YPOS * H;
209
281
  container.scrollLeft = newCenter.x - XPOS * W;
282
+ this.pinchMoveFramePromiseRes?.();
283
+ }
284
+ }
285
+
286
+ export class TouchesMonitor {
287
+ /**
288
+ * @param {HTMLElement} container
289
+ */
290
+ constructor(container) {
291
+ /** @type {HTMLElement} */
292
+ this.container = container;
293
+ this.touches = 0;
294
+ }
295
+
296
+ attach() {
297
+ this.container.addEventListener("touchstart", this._updateTouchCount);
298
+ this.container.addEventListener("touchend", this._updateTouchCount);
299
+ }
300
+
301
+ detach() {
302
+ this.container.removeEventListener("touchstart", this._updateTouchCount);
303
+ this.container.removeEventListener("touchend", this._updateTouchCount);
304
+ }
305
+
306
+ /**
307
+ * @param {TouchEvent} ev
308
+ */
309
+ _updateTouchCount = (ev) => {
310
+ this.touches = ev.touches.length;
210
311
  }
211
312
  }
@@ -41,7 +41,7 @@ export class Navbar {
41
41
  return `<li>
42
42
  <button class="BRicon ${option.className}" title="${option.label}">
43
43
  <div class="icon icon-${option.iconClassName}"></div>
44
- <span class="tooltip">${option.label}</span>
44
+ <span class="BRtooltip">${option.label}</span>
45
45
  </button>
46
46
  </li>`;
47
47
  }
@@ -130,7 +130,7 @@ export class Navbar {
130
130
  .removeClass()
131
131
  .addClass(`icon icon-${iconClass}`)
132
132
  .end()
133
- .find('.tooltip')
133
+ .find('.BRtooltip')
134
134
  .text(tooltipText);
135
135
  }
136
136
 
@@ -104,8 +104,11 @@
104
104
  border-radius: 2px;
105
105
  background: transparent;
106
106
  outline: none;
107
- &:hover .tooltip {
108
- display: block;
107
+ @media (hover: hover) {
108
+ /* styles to apply on devices that support hover */
109
+ &:hover .BRtooltip {
110
+ display: block;
111
+ }
109
112
  }
110
113
  &.hide {
111
114
  display: none;
@@ -98,6 +98,10 @@
98
98
  // background-image: url(images/marker_chap-on.svg);
99
99
  background-color: blue;
100
100
  }
101
+ .BRchapterPage {
102
+ font-size: 0.85em;
103
+ opacity: .8;
104
+ }
101
105
  }
102
106
  .BRsearch {
103
107
  width: 9px;
@@ -130,8 +134,8 @@
130
134
  }
131
135
  footer {
132
136
  text-align: center;
133
- font-weight: bold;
134
- font-size: 0.9em;
137
+ font-size: 0.85em;
138
+ opacity: .8;
135
139
  }
136
140
  mark {
137
141
  color: $searchResultText;
@@ -166,29 +166,3 @@ html.mm-background .BookReader {
166
166
  @media (min-width: ($brBreakPointMobile + 1)) {
167
167
  .BRtoolbar.responsive { display: block; }
168
168
  }
169
-
170
- li.BRtable-contents-el {
171
- line-height: 150%;
172
- padding: 10px 10px 10px 20px;
173
- overflow: hidden;
174
- color: $controlsText;
175
-
176
- &.chapter-clickable {
177
- font-weight: normal;
178
- }
179
-
180
- &.current-chapter {
181
- background-color: lightblue;
182
- }
183
- //resetting mmenu styling for span inside .table-contents-el
184
- > span {
185
- display: inline;
186
- padding: 0;
187
- white-space: normal;
188
- }
189
-
190
- .BRTOCElementPage {
191
- font-size: 0.85em;
192
- opacity: .8;
193
- }
194
- }
@@ -1,7 +1,7 @@
1
1
  .BRcontrols {
2
2
  width: 100%;
3
3
 
4
- .tooltip {
4
+ .BRtooltip {
5
5
  display: none;
6
6
  position: absolute;
7
7
  width: auto;
@@ -14,9 +14,10 @@
14
14
  color: $controlsText;
15
15
  border-radius: 3px;
16
16
  background: $tooltipBG;
17
+ pointer-events: none;
17
18
  }
18
19
 
19
- .full .tooltip {
20
+ .full .BRtooltip {
20
21
  left: auto;
21
22
  right: 0;
22
23
  transform: translateX(0);