@internetarchive/bookreader 5.0.0-113 → 5.0.0-115

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 (42) hide show
  1. package/BookReader/474.js.map +1 -1
  2. package/BookReader/BookReader.css +34 -10
  3. package/BookReader/BookReader.js +1 -1
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/ia-bookreader-bundle.js +20 -38
  6. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  7. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  8. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  9. package/BookReader/plugins/plugin.chapters.js +1 -1
  10. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  11. package/BookReader/plugins/plugin.experiments.js +1 -1
  12. package/BookReader/plugins/plugin.experiments.js.map +1 -1
  13. package/BookReader/plugins/plugin.iframe.js +1 -1
  14. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  15. package/BookReader/plugins/plugin.iiif.js.map +1 -1
  16. package/BookReader/plugins/plugin.resume.js.map +1 -1
  17. package/BookReader/plugins/plugin.search.js +1 -1
  18. package/BookReader/plugins/plugin.search.js.map +1 -1
  19. package/BookReader/plugins/plugin.text_selection.js +1 -1
  20. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  21. package/BookReader/plugins/plugin.translate.js +1 -1
  22. package/BookReader/plugins/plugin.translate.js.map +1 -1
  23. package/BookReader/plugins/plugin.tts.js +1 -1
  24. package/BookReader/plugins/plugin.tts.js.map +1 -1
  25. package/BookReader/plugins/plugin.url.js.map +1 -1
  26. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  27. package/BookReader/plugins/translator-worker.js.map +1 -1
  28. package/BookReader/webcomponents-bundle.js.map +1 -1
  29. package/package.json +37 -31
  30. package/src/BookReader/utils/SelectionObserver.js +4 -2
  31. package/src/BookReader.js +2 -1
  32. package/src/css/_TextSelection.scss +32 -11
  33. package/src/ia-bookreader/ia-bookreader.js +13 -2
  34. package/src/plugins/plugin.experiments.js +13 -3
  35. package/src/plugins/plugin.iiif.js +7 -1
  36. package/src/plugins/plugin.text_selection.js +8 -1
  37. package/src/plugins/tts/WebTTSEngine.js +2 -2
  38. package/src/plugins/tts/plugin.tts.js +3 -3
  39. package/src/plugins/tts/utils.js +0 -19
  40. package/src/util/TextSelectionManager.js +276 -110
  41. package/src/util/browserSniffing.js +9 -0
  42. package/src/util/strings.js +10 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-113",
3
+ "version": "5.0.0-115",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "type": "module",
6
6
  "files": [
@@ -32,21 +32,21 @@
32
32
  "private": false,
33
33
  "dependencies": {
34
34
  "@internetarchive/bergamot-translator": "^0.4.9-ia.1",
35
- "@internetarchive/ia-activity-indicator": "0.0.6",
36
- "@internetarchive/ia-item-navigator": "2.2.2",
37
- "@internetarchive/icon-bookmark": "^1.4.0",
38
- "@internetarchive/icon-close": "1.4.0",
39
- "@internetarchive/icon-dl": "^1.4.0",
40
- "@internetarchive/icon-edit-pencil": "^1.4.1",
41
- "@internetarchive/icon-ia-logo": "1.4.0",
42
- "@internetarchive/icon-magnify-minus": "^1.4.0",
43
- "@internetarchive/icon-magnify-plus": "^1.4.0",
44
- "@internetarchive/icon-search": "^1.4.0",
45
- "@internetarchive/icon-share": "1.4.0",
46
- "@internetarchive/icon-toc": "^1.4.0",
47
- "@internetarchive/icon-visual-adjustment": "^1.4.0",
48
- "@internetarchive/modal-manager": "2.0.5",
49
- "@internetarchive/shared-resize-observer": "^0.2.0",
35
+ "@internetarchive/ia-activity-indicator": "1.0.0",
36
+ "@internetarchive/ia-item-navigator": "2.2.3",
37
+ "@internetarchive/icon-bookmark": "1.4.1",
38
+ "@internetarchive/icon-close": "1.4.1",
39
+ "@internetarchive/icon-dl": "1.4.1",
40
+ "@internetarchive/icon-edit-pencil": "1.4.1",
41
+ "@internetarchive/icon-ia-logo": "1.4.1",
42
+ "@internetarchive/icon-magnify-minus": "1.4.1",
43
+ "@internetarchive/icon-magnify-plus": "1.4.1",
44
+ "@internetarchive/icon-search": "1.4.1",
45
+ "@internetarchive/icon-share": "1.4.1",
46
+ "@internetarchive/icon-toc": "1.4.1",
47
+ "@internetarchive/icon-visual-adjustment": "1.4.1",
48
+ "@internetarchive/modal-manager": "2.0.6",
49
+ "@internetarchive/shared-resize-observer": "0.2.0",
50
50
  "interactjs": "^1.10.18",
51
51
  "iso-language-codes": "2.0.0",
52
52
  "jquery": "3.6.1",
@@ -58,36 +58,36 @@
58
58
  "soundmanager2": "2.97.20170602"
59
59
  },
60
60
  "devDependencies": {
61
- "@babel/core": "7.29.0",
62
- "@babel/eslint-parser": "7.28.6",
61
+ "@babel/core": "7.29.7",
62
+ "@babel/eslint-parser": "7.29.7",
63
63
  "@babel/plugin-proposal-class-properties": "7.18.6",
64
- "@babel/plugin-proposal-decorators": "7.29.0",
65
- "@babel/preset-env": "7.29.2",
66
- "@iiif/presentation-2": "^1.0.4",
67
- "@iiif/presentation-3": "^2.1.3",
64
+ "@babel/plugin-proposal-decorators": "7.29.7",
65
+ "@babel/preset-env": "7.29.7",
66
+ "@iiif/presentation-2": "1.0.4",
67
+ "@iiif/presentation-3": "2.2.3",
68
68
  "@open-wc/testing-helpers": "3.0.1",
69
69
  "@types/jest": "30.0.0",
70
70
  "@webcomponents/webcomponentsjs": "^2.6.0",
71
71
  "babel-loader": "10.1.1",
72
- "concurrently": "9.0.1",
72
+ "concurrently": "10.0.3",
73
73
  "core-js": "3.49.0",
74
- "cpx2": "8.0.0",
74
+ "cpx2": "9.0.0",
75
75
  "eslint": "7.32.0",
76
76
  "eslint-plugin-no-jquery": "^2.7.0",
77
77
  "eslint-plugin-testcafe": "0.2.1",
78
78
  "http-server": "14.1.1",
79
79
  "hypothesis": "^1.1627.0",
80
- "jest": "30.3.0",
81
- "jest-environment-jsdom": "30.3.0",
80
+ "jest": "30.4.2",
81
+ "jest-environment-jsdom": "30.4.1",
82
82
  "live-server": "1.2.2",
83
83
  "regenerator-runtime": "0.14.1",
84
- "sass": "1.98.0",
85
- "sinon": "21.0.3",
84
+ "sass": "1.100.0",
85
+ "sinon": "22.0.0",
86
86
  "svgo": "4.0.1",
87
87
  "testcafe": "3.7.4",
88
88
  "testcafe-browser-provider-browserstack": "1.15.2",
89
- "webpack": "5.105.4",
90
- "webpack-cli": "7.0.2"
89
+ "webpack": "5.107.2",
90
+ "webpack-cli": "7.0.3"
91
91
  },
92
92
  "jest": {
93
93
  "testEnvironment": "jsdom",
@@ -133,7 +133,13 @@
133
133
  "test:e2e:dev": "npx testcafe --live --dev",
134
134
  "DOCS:update:test-deps": "If CI succeeds, these should be good to update",
135
135
  "update:test-deps": "npm i @babel/eslint-parser@latest @open-wc/testing-helpers@latest @types/jest@latest eslint@7 eslint-plugin-testcafe@latest jest@latest jest-environment-jsdom@latest sinon@latest testcafe@latest",
136
+ "update:test-deps:test": "npm run lint && npm run test && npm run test:e2e",
136
137
  "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",
137
- "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"
138
+ "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",
139
+ "update:build-deps:test": "npm run build",
140
+ "DOCS:update:dev-deps": "Packages used for development",
141
+ "update:dev-deps": "npm i @iiif/presentation-2@latest @iiif/presentation-3@latest concurrently@latest cpx2@latest http-server@latest live-server@latest",
142
+ "DOCS:update:ia-deps": "Packages by @internetarchive",
143
+ "update:ia-deps": "npm i @internetarchive/ia-activity-indicator@latest @internetarchive/ia-item-navigator@latest @internetarchive/icon-bookmark@latest @internetarchive/icon-close@latest @internetarchive/icon-dl@latest @internetarchive/icon-edit-pencil@latest @internetarchive/icon-ia-logo@latest @internetarchive/icon-magnify-minus@latest @internetarchive/icon-magnify-plus@latest @internetarchive/icon-search@latest @internetarchive/icon-share@latest @internetarchive/icon-toc@latest @internetarchive/icon-visual-adjustment@latest @internetarchive/modal-manager@latest @internetarchive/shared-resize-observer@latest"
138
144
  }
139
145
  }
@@ -4,7 +4,7 @@ export class SelectionObserver {
4
4
  startedInSelector = false;
5
5
  /** @type {HTMLElement} */
6
6
  target = null;
7
- /** @type {Node} */
7
+ /** @type {Node | null} */
8
8
  lastKnownFocusNode = null;
9
9
 
10
10
  /**
@@ -30,6 +30,7 @@ export class SelectionObserver {
30
30
 
31
31
  _onSelectionChange = () => {
32
32
  const sel = window.getSelection();
33
+ if (!sel) return;
33
34
 
34
35
  if (!this.selecting && sel.toString()) {
35
36
  const target = $(sel.anchorNode).closest(this.selector)[0];
@@ -40,13 +41,14 @@ export class SelectionObserver {
40
41
  this.handler('started', this.target);
41
42
  }
42
43
 
43
- if (this.selecting && (this.lastKnownFocusNode != sel.focusNode || sel.toString() && !sel.isCollapsed)) {
44
+ if (this.selecting && this.lastKnownFocusNode != sel.focusNode && sel.toString() && !sel.isCollapsed) {
44
45
  this.lastKnownFocusNode = sel.focusNode;
45
46
  this.handler('changed', this.target);
46
47
  }
47
48
 
48
49
  if (this.selecting && (sel.isCollapsed || !sel.toString() || !$(sel.anchorNode).closest(this.selector)[0])) {
49
50
  this.selecting = false;
51
+ this.lastKnownFocusNode = null;
50
52
  this.handler('cleared', this.target);
51
53
  }
52
54
  };
package/src/BookReader.js CHANGED
@@ -151,7 +151,7 @@ BookReader.defaultOptions = DEFAULT_OPTIONS;
151
151
  * @type {BookReaderOptions}
152
152
  * This is here, just in case you need to absolutely override an option.
153
153
  */
154
- BookReader.optionOverrides = {};
154
+ BookReader.optionOverrides = BookReader.optionOverrides || {};
155
155
 
156
156
  /**
157
157
  * Setup
@@ -852,6 +852,7 @@ BookReader.prototype.setupKeyListeners = function () {
852
852
 
853
853
  // Ignore if modifiers are active.
854
854
  if (e.getModifierState('Control') ||
855
+ e.getModifierState('Shift') ||
855
856
  e.getModifierState('Alt') ||
856
857
  e.getModifierState('Meta') ||
857
858
  e.getModifierState('Win') /* hack for IE */) {
@@ -149,10 +149,20 @@
149
149
  display: none;
150
150
  border-radius: 20px;
151
151
  background-color: #333;
152
+ color: white;
152
153
  color-scheme: dark;
153
154
  padding: 2px;
154
155
  overflow: hidden;
155
- transition: border-radius 0.2s;
156
+ opacity: 1;
157
+ transition: border-radius 0.2s, opacity 0.2s;
158
+
159
+ @starting-style {
160
+ opacity: 0;
161
+ }
162
+ }
163
+ .br-select-menu__root--scrolling {
164
+ opacity: 0;
165
+ pointer-events: none;
156
166
  }
157
167
  .br-select-menu__root:hover {
158
168
  border-radius: 8px;
@@ -171,6 +181,7 @@
171
181
  align-items: center;
172
182
  border-radius: 20px;
173
183
  font-family: inherit;
184
+ color: inherit;
174
185
  border: 0;
175
186
  transition: background-color 0.2s, border-radius 0.2s;
176
187
  cursor: pointer;
@@ -190,18 +201,23 @@
190
201
  }
191
202
 
192
203
  .br-select-menu__label {
193
- width: 0px;
194
- opacity: 0;
195
- interpolate-size: allow-keywords;
196
- transition: width 0.2s;
204
+ margin-left: 4px;
197
205
  font-size: 12px;
198
206
  }
199
207
 
200
-
201
- .br-select-menu__root:hover .br-select-menu__label {
202
- width: auto;
203
- margin-left: 4px;
204
- opacity: 1;
208
+ @media (hover: hover) {
209
+ .br-select-menu__label {
210
+ width: 0px;
211
+ margin-left: 0;
212
+ opacity: 0;
213
+ interpolate-size: allow-keywords;
214
+ transition: width 0.2s;
215
+ }
216
+ .br-select-menu__root:hover .br-select-menu__label {
217
+ width: auto;
218
+ margin-left: 4px;
219
+ opacity: 1;
220
+ }
205
221
  }
206
222
 
207
223
  .BRtextLayer .BRhighlight {
@@ -210,9 +226,14 @@
210
226
  color: transparent;
211
227
  }
212
228
 
213
- // Style URI TextFragments, eg #:~:text=example
229
+ @keyframes highlightTargetFlash {
230
+ to { background-color: hsla(45, 80%, 66%, 1); }
231
+ }
232
+
233
+ // Style URI TextFragments, eg ?text=example
214
234
  .BRtextLayer .BRhighlight--target-text, .BRtextLayer ::target-text {
215
235
  // Similar colour to the default one used in Safari, Firefox. Note Chrome uses a purple colour
216
236
  background-color: hsla(45, 80%, 66%, 0.6);
217
237
  color: transparent;
238
+ animation: highlightTargetFlash 600ms 1 reverse;
218
239
  }
@@ -302,8 +302,11 @@ export class IaBookReader extends LitElement {
302
302
  providers.bookmarks = new BookmarksProvider({
303
303
  ...this.baseProviderConfig,
304
304
  onProviderChange: (bookmarks) => {
305
- const method = Object.keys(bookmarks).length ? 'add' : 'remove';
306
- this[`${method}MenuShortcut`]('bookmarks');
305
+ if (Object.keys(bookmarks).length) {
306
+ this.addMenuShortcut('bookmarks');
307
+ } else {
308
+ this.removeMenuShortcut('bookmarks');
309
+ }
307
310
  this.updateMenuContents();
308
311
  },
309
312
  });
@@ -463,6 +466,14 @@ export class IaBookReader extends LitElement {
463
466
  this._sortMenuShortcuts();
464
467
  }
465
468
 
469
+ /**
470
+ * @param {string} menuId
471
+ */
472
+ removeMenuShortcut(menuId) {
473
+ this.menuShortcuts = this.menuShortcuts.filter((m) => m.id !== menuId);
474
+ this._sortMenuShortcuts();
475
+ }
476
+
466
477
  /**
467
478
  * Sorts the menuShortcuts property by comparing each provider's id to
468
479
  * the id in each iteration over the shortcutOrder array.
@@ -52,8 +52,11 @@ export class ExperimentsPlugin extends BookReaderPlugin {
52
52
  /** Where the state of this plugin is saved in localStorage */
53
53
  localStorageKey: 'BrExperiments',
54
54
 
55
- /** The experiments that should be shown in the experiments panel */
56
- enabledExperiments: ['translate', 'copyLinkToHighlight'],
55
+ /** @type {ExperimentName[]} Experiments shown in the experiments panel */
56
+ availableExperiments: ['translate', 'copyLinkToHighlight'],
57
+
58
+ /** @type {ExperimentName[]} Experiments enabled by default */
59
+ autoEnabledExperiments: [],
57
60
  }
58
61
 
59
62
  /** @type {ExperimentModel[]} */
@@ -160,7 +163,14 @@ export class ExperimentsPlugin extends BookReaderPlugin {
160
163
  if (experiment.icon) {
161
164
  experiment.icon = experiment.buildAssetPath(experiment.icon);
162
165
  }
166
+
163
167
  experiment.br = this.br;
168
+
169
+ // Enable any experiments that should be automatically enabled
170
+ if (!experiment.enabled && this.options.autoEnabledExperiments.includes(experiment.name)) {
171
+ experiment.enabled = true;
172
+ await experiment.enable({ manual: false });
173
+ }
164
174
  }
165
175
 
166
176
  this._loadExperimentStates();
@@ -222,7 +232,7 @@ export class ExperimentsPlugin extends BookReaderPlugin {
222
232
  `,
223
233
  label: 'Experiments',
224
234
  component: html`<br-experiments-panel
225
- .experiments="${this.allExperiments.filter(experiment => this.options.enabledExperiments.includes(experiment.name))}"
235
+ .experiments="${this.allExperiments.filter(experiment => this.options.availableExperiments.includes(experiment.name))}"
226
236
  @connected="${e => this._panel = e.target}"
227
237
  @toggle="${async e => {
228
238
  await this._toggleExperiment(e.detail.experiment, e.detail.enabled);
@@ -3,13 +3,19 @@ import { BookReaderPlugin } from '../BookReaderPlugin.js';
3
3
 
4
4
  const BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);
5
5
 
6
+ /**
7
+ * @extends BookReaderPlugin<IiifPlugin['options']>
8
+ */
6
9
  export class IiifPlugin extends BookReaderPlugin {
7
10
  options = {
8
11
  enabled: true,
9
- /** @type {import('@iiif/presentation-3').Manifest | import('@iiif/presentation-2').Manifest} */
12
+ /** @type {import('@iiif/presentation-3').Manifest | import('@iiif/presentation-2').Manifest | null} */
10
13
  manifest: null,
11
14
  }
12
15
 
16
+ /**
17
+ * @param {IiifPlugin['options']} options
18
+ */
13
19
  setup(options) {
14
20
  super.setup(options);
15
21
  this.manifest = this.options.manifest;
@@ -36,6 +36,8 @@ export class TextSelectionPlugin extends BookReaderPlugin {
36
36
  */
37
37
  maxWordRendered = 2500;
38
38
 
39
+ _jumpedToHighlight = false;
40
+
39
41
  /**
40
42
  * @param {import('../BookReader.js').default} br
41
43
  */
@@ -71,7 +73,12 @@ export class TextSelectionPlugin extends BookReaderPlugin {
71
73
  const pageIndex = targetTextFragment.pageIndex;
72
74
  const hasTargetText = pageIndex === parseFloat(pageContainerEl.getAttribute('data-index'));
73
75
  if (hasTargetText) {
74
- renderHighlight(textLayer, targetTextFragment, 'BRhighlight--target-text');
76
+ const markEls = renderHighlight(textLayer, targetTextFragment, 'BRhighlight--target-text');
77
+ // Only jump once; presumably on first page load.
78
+ if (!this._jumpedToHighlight) {
79
+ markEls[0].scrollIntoView({behavior: 'smooth', block: 'center'});
80
+ this._jumpedToHighlight = true;
81
+ }
75
82
  }
76
83
  });
77
84
  }
@@ -1,6 +1,6 @@
1
1
  /* global br */
2
- import { isChrome, isFirefox } from '../../util/browserSniffing.js';
3
- import { isAndroid, DEBUG_READ_ALOUD } from './utils.js';
2
+ import { isChrome, isFirefox, isAndroid } from '../../util/browserSniffing.js';
3
+ import { DEBUG_READ_ALOUD } from './utils.js';
4
4
  import { promisifyEvent, sleep } from '../../BookReader/utils.js';
5
5
  import AbstractTTSEngine from './AbstractTTSEngine.js';
6
6
  /** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
@@ -2,11 +2,11 @@
2
2
 
3
3
  import FestivalTTSEngine from './FestivalTTSEngine.js';
4
4
  import WebTTSEngine from './WebTTSEngine.js';
5
- import { toISO6391, approximateWordCount } from './utils.js';
5
+ import { toISO6391 } from './utils.js';
6
6
  import { en as tooltips } from './tooltip_dict.js';
7
7
  import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
8
8
  import { BookReaderPlugin } from '../../BookReaderPlugin.js';
9
- import { applyVariables } from '../../util/strings.js';
9
+ import { applyVariables, countWords } from '../../util/strings.js';
10
10
  /** @typedef {import('./PageChunk.js').default} PageChunk */
11
11
  /** @typedef {import("./AbstractTTSEngine.js").default} AbstractTTSEngine */
12
12
 
@@ -291,7 +291,7 @@ export class TtsPlugin extends BookReaderPlugin {
291
291
  * @param {PageChunk} chunk
292
292
  */
293
293
  sendChunkFinishedAnalyticsEvent(chunk) {
294
- this.sendAnalyticsEvent('ChunkFinished-Words', approximateWordCount(chunk.text));
294
+ this.sendAnalyticsEvent('ChunkFinished-Words', countWords(chunk.text));
295
295
  }
296
296
 
297
297
  /**
@@ -1,25 +1,6 @@
1
1
  // @ts-check
2
2
  import langs from 'iso-language-codes';
3
3
 
4
- /**
5
- * Use regex to approximate word count in a string
6
- * @param {string} text
7
- * @return {number}
8
- */
9
- export function approximateWordCount(text) {
10
- const m = text.match(/\S+/g);
11
- return m ? m.length : 0;
12
- }
13
-
14
- /**
15
- * Checks whether the current browser is on android
16
- * @param {string} [userAgent]
17
- * @return {boolean}
18
- */
19
- export function isAndroid(userAgent = navigator.userAgent) {
20
- return /android/i.test(userAgent);
21
- }
22
-
23
4
  /** @type {{[lang: string]: string}} */
24
5
  // Handle odd one-off language pairs that might show up over time
25
6
  const specialLangs = {