@internetarchive/bookreader 5.0.0-65 → 5.0.0-67

Sign up to get free protection for your applications and to get access to all the features.
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);