@internetarchive/bookreader 5.0.0-96 → 5.0.0-98

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 (47) hide show
  1. package/BookReader/474.js +2 -0
  2. package/BookReader/474.js.map +1 -0
  3. package/BookReader/BookReader.css +39 -34
  4. package/BookReader/BookReader.js +1 -1
  5. package/BookReader/BookReader.js.map +1 -1
  6. package/BookReader/bergamot-translator-worker.js +2966 -0
  7. package/BookReader/bergamot-translator-worker.wasm +0 -0
  8. package/BookReader/ia-bookreader-bundle.js +1 -1
  9. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  10. package/BookReader/images/icon_experiment.svg +1 -0
  11. package/BookReader/images/translate.svg +1 -0
  12. package/BookReader/plugins/plugin.experiments.js +1 -1
  13. package/BookReader/plugins/plugin.experiments.js.map +1 -1
  14. package/BookReader/plugins/plugin.text_selection.js +1 -1
  15. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  16. package/BookReader/plugins/plugin.translate.js +3 -0
  17. package/BookReader/plugins/plugin.translate.js.LICENSE.txt +1 -0
  18. package/BookReader/plugins/plugin.translate.js.map +1 -0
  19. package/BookReader/plugins/plugin.tts.js +1 -1
  20. package/BookReader/plugins/plugin.tts.js.map +1 -1
  21. package/BookReader/plugins/translator-worker.js +2 -0
  22. package/BookReader/plugins/translator-worker.js.map +1 -0
  23. package/BookReader/silence.mp3 +0 -0
  24. package/BookReader/translator-worker.js +475 -0
  25. package/package.json +6 -3
  26. package/src/BookNavigator/book-navigator.js +1 -0
  27. package/src/BookReader/Mode1UpLit.js +6 -1
  28. package/src/BookReader/Mode2UpLit.js +11 -1
  29. package/src/BookReader/Navbar/Navbar.js +61 -0
  30. package/src/BookReader/options.js +12 -8
  31. package/src/BookReader.js +67 -140
  32. package/src/assets/images/icon_experiment.svg +1 -0
  33. package/src/assets/images/translate.svg +1 -0
  34. package/src/assets/silence.mp3 +0 -0
  35. package/src/css/_BRnav.scss +0 -24
  36. package/src/css/_BRsearch.scss +1 -5
  37. package/src/css/_TextSelection.scss +38 -9
  38. package/src/plugins/plugin.experiments.js +34 -9
  39. package/src/plugins/plugin.text_selection.js +17 -20
  40. package/src/plugins/translate/TranslationManager.js +170 -0
  41. package/src/plugins/translate/plugin.translate.js +489 -0
  42. package/src/plugins/tts/AbstractTTSEngine.js +3 -4
  43. package/src/plugins/tts/PageChunk.js +28 -9
  44. package/src/plugins/tts/WebTTSEngine.js +5 -7
  45. package/src/plugins/tts/plugin.tts.js +40 -4
  46. package/src/plugins/tts/utils.js +21 -22
  47. package/src/util/cache.js +20 -0
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /** @typedef {import("../../BookReader.js").default} BookReader */
2
3
 
3
4
  import 'jquery-ui/ui/widget.js';
@@ -120,6 +121,66 @@ export class Navbar {
120
121
  });
121
122
  }
122
123
 
124
+ bindControlClickHandlers() {
125
+ const jIcons = this.$nav.find('.BRicon');
126
+
127
+ // Map of jIcon class -> click handler
128
+ const navigationControls = {
129
+ book_left: () => {
130
+ this.br.trigger(EVENTS.stop);
131
+ this.br.left();
132
+ },
133
+ book_right: () => {
134
+ this.br.trigger(EVENTS.stop);
135
+ this.br.right();
136
+ },
137
+ book_top: this.br.first.bind(this.br),
138
+ book_bottom: this.br.last.bind(this.br),
139
+ book_leftmost: this.br.leftmost.bind(this.br),
140
+ book_rightmost: this.br.rightmost.bind(this.br),
141
+ onepg: () => {
142
+ this.br.switchMode('1up');
143
+ },
144
+ thumb: () => {
145
+ this.br.switchMode('thumb');
146
+ },
147
+ twopg: () => {
148
+ this.br.switchMode('2up');
149
+ },
150
+ zoom_in: () => {
151
+ this.br.trigger(EVENTS.stop);
152
+ this.br.zoom(1);
153
+ this.br.trigger(EVENTS.zoomIn);
154
+ },
155
+ zoom_out: () => {
156
+ this.br.trigger(EVENTS.stop);
157
+ this.br.zoom(-1);
158
+ this.br.trigger(EVENTS.zoomOut);
159
+ },
160
+ full: () => {
161
+ if (this.br.ui == 'embed') {
162
+ const url = this.br.$('.BRembedreturn a').attr('href');
163
+ window.open(url);
164
+ } else {
165
+ this.br.toggleFullscreen();
166
+ }
167
+ },
168
+ };
169
+
170
+ // custom event for auto-loan-renew in ia-book-actions
171
+ // - to know if user is actively reading
172
+ this.$nav.find('nav.BRcontrols li button').on('click', () => {
173
+ this.br.trigger(EVENTS.userAction);
174
+ });
175
+
176
+ for (const control in navigationControls) {
177
+ jIcons.filter(`.${control}`).on('click.bindNavigationHandlers', () => {
178
+ navigationControls[control]();
179
+ return false;
180
+ });
181
+ }
182
+ }
183
+
123
184
  /**
124
185
  * Toggle viewmode button to change page view
125
186
  */
@@ -147,21 +147,25 @@ export const DEFAULT_OPTIONS = {
147
147
  **/
148
148
  plugins: {
149
149
  /** @type {Partial<import('../plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin['options'>]}*/
150
- archiveAnalytics: null,
150
+ archiveAnalytics: {},
151
151
  /** @type {Partial<import('../plugins/plugin.autoplay.js').AutoplayPlugin['options'>]}*/
152
- autoplay: null,
152
+ autoplay: {},
153
153
  /** @type {Partial<import('../plugins/plugin.chapters.js').ChaptersPlugin['options']>} */
154
- chapters: null,
154
+ chapters: {},
155
+ /** @type {Partial<import('../plugins/plugin.experiments.js').ExperimentsPlugin['options']>} */
156
+ experiments: {},
155
157
  /** @type {Partial<import('../plugins/plugin.iiif.js').IiifPlugin['options']>} */
156
- iiif: null,
158
+ iiif: {},
157
159
  /** @type {Partial<import('../plugins/plugin.resume.js').ResumePlugin['options']>} */
158
- resume: null,
160
+ resume: {},
159
161
  /** @type {Partial<import('../plugins/search/plugin.search.js').SearchPlugin['options']>} */
160
- search: null,
162
+ search: {},
161
163
  /** @type {Partial<import('../plugins/plugin.text_selection.js').TextSelectionPlugin['options']>} */
162
- textSelection: null,
164
+ textSelection: {},
165
+ /** @type {Partial<import('../plugins/translate/plugin.translate.js').TranslatePlugin['options']>} */
166
+ translate: {},
163
167
  /** @type {Partial<import('../plugins/tts/plugin.tts.js').TtsPlugin['options']>} */
164
- tts: null,
168
+ tts: {},
165
169
  },
166
170
 
167
171
  /**
package/src/BookReader.js CHANGED
@@ -49,10 +49,47 @@ import { NAMED_REDUCE_SETS } from './BookReader/ReduceSet.js';
49
49
  * @constructor
50
50
  */
51
51
  export default function BookReader(overrides = {}) {
52
- const options = jQuery.extend(true, {}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
52
+ const options = BookReader.extendOptions({}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
53
53
  this.setup(options);
54
54
  }
55
55
 
56
+ /**
57
+ * Extend the default options for BookReader
58
+ * Accepts any number of option objects and merges them in order.
59
+ *
60
+ * Does a shallow merge of everything except plugin options. These are individually merged.
61
+ * @param {...Partial<BookReaderOptions>} newOptions
62
+ */
63
+ BookReader.extendOptions = function(...newOptions) {
64
+ if (newOptions.length <= 1) {
65
+ return newOptions[0];
66
+ }
67
+ /** @type {Array<keyof BookReaderOptions>} */
68
+ const shallowOverrides = ['onePage', 'twoPage', 'vars'];
69
+ /** @type {Array<keyof BookReaderOptions>} */
70
+ const mapOverrides = ['plugins', 'controls'];
71
+
72
+ const result = newOptions.shift();
73
+ for (const opts of newOptions) {
74
+ for (const [key, value] of Object.entries(opts)) {
75
+ if (shallowOverrides.includes(key)) {
76
+ result[key] ||= {};
77
+ result[key] = Object.assign(result[key], value);
78
+ } else if (mapOverrides.includes(key)) {
79
+ result[key] ||= {};
80
+ for (const [name, mapValue] of Object.entries(value)) {
81
+ result[key][name] ||= {};
82
+ Object.assign(result[key][name], mapValue);
83
+ }
84
+ } else {
85
+ result[key] = value;
86
+ }
87
+ }
88
+ }
89
+
90
+ return result;
91
+ };
92
+
56
93
  BookReader.version = PACKAGE_JSON.version;
57
94
 
58
95
  // Mode constants
@@ -72,12 +109,16 @@ BookReader.PLUGINS = {
72
109
  autoplay: null,
73
110
  /** @type {typeof import('./plugins/plugin.chapters.js').ChaptersPlugin | null}*/
74
111
  chapters: null,
112
+ /** @type {typeof import('./plugins/plugin.experiments.js').ExperimentsPlugin | null}*/
113
+ experiments: null,
75
114
  /** @type {typeof import('./plugins/plugin.resume.js').ResumePlugin | null}*/
76
115
  resume: null,
77
116
  /** @type {typeof import('./plugins/search/plugin.search.js').SearchPlugin | null}*/
78
117
  search: null,
79
118
  /** @type {typeof import('./plugins/plugin.text_selection.js').TextSelectionPlugin | null}*/
80
119
  textSelection: null,
120
+ /** @type {typeof import('./plugins/translate/plugin.translate.js').TranslatePlugin | null}*/
121
+ translate: null,
81
122
  /** @type {typeof import('./plugins/tts/plugin.tts.js').TtsPlugin | null}*/
82
123
  tts: null,
83
124
  };
@@ -138,9 +179,11 @@ BookReader.prototype.setup = function(options) {
138
179
  archiveAnalytics: BookReader.PLUGINS.archiveAnalytics ? new BookReader.PLUGINS.archiveAnalytics(this) : null,
139
180
  autoplay: BookReader.PLUGINS.autoplay ? new BookReader.PLUGINS.autoplay(this) : null,
140
181
  chapters: BookReader.PLUGINS.chapters ? new BookReader.PLUGINS.chapters(this) : null,
182
+ experiments: BookReader.PLUGINS.experiments ? new BookReader.PLUGINS.experiments(this) : null,
141
183
  search: BookReader.PLUGINS.search ? new BookReader.PLUGINS.search(this) : null,
142
184
  resume: BookReader.PLUGINS.resume ? new BookReader.PLUGINS.resume(this) : null,
143
185
  textSelection: BookReader.PLUGINS.textSelection ? new BookReader.PLUGINS.textSelection(this) : null,
186
+ translate: BookReader.PLUGINS.translate ? new BookReader.PLUGINS.translate(this) : null,
144
187
  tts: BookReader.PLUGINS.tts ? new BookReader.PLUGINS.tts(this) : null,
145
188
  };
146
189
 
@@ -330,6 +373,24 @@ BookReader.prototype.setup = function(options) {
330
373
  this.hasKeyFocus = true;
331
374
  };
332
375
 
376
+ BookReader.prototype.initializePlugin = function(pluginName) {
377
+ const plugin = new BookReader.PLUGINS[pluginName](this);
378
+ this.plugins[pluginName] = plugin;
379
+ try {
380
+ plugin.setup(this.options.plugins?.[pluginName] ?? {});
381
+ this.options.plugins[pluginName] = plugin.options;
382
+ } catch (e) {
383
+ console.error(`Error setting up plugin ${pluginName} outside of regular cycle`, e);
384
+ throw e;
385
+ }
386
+
387
+ try {
388
+ plugin.init();
389
+ } catch (e) {
390
+ console.error(`Error initializing plugin ${pluginName} outside of regular cycle`, e);
391
+ throw e;
392
+ }
393
+ };
333
394
  /**
334
395
  * Get all the HTML Elements that are being/can be rendered.
335
396
  * Includes cached elements which might be rendered again.
@@ -347,6 +408,7 @@ BookReader.prototype.getActivePageContainerElements = function() {
347
408
  * Get the HTML Elements for the rendered page. Note there can be more than one, since
348
409
  * (at least as of writing) different modes can maintain different caches.
349
410
  * @param {PageIndex} pageIndex
411
+ * @returns {HTMLElement[]}
350
412
  */
351
413
  BookReader.prototype.getActivePageContainerElementsForIndex = function(pageIndex) {
352
414
  return [
@@ -632,8 +694,10 @@ BookReader.prototype.init = function() {
632
694
  this.initUIStrings();
633
695
 
634
696
  // Bind to events
635
-
636
- this.bindNavigationHandlers();
697
+ this._components.navbar.bindControlClickHandlers();
698
+ for (const plugin of Object.values(this.plugins)) {
699
+ plugin._bindNavigationHandlers();
700
+ }
637
701
  this.setupKeyListeners();
638
702
 
639
703
  this.lastScroll = (new Date().getTime());
@@ -1497,141 +1561,6 @@ exposeOverrideableMethod(Toolbar, '_components.toolbar', 'buildInfoDiv');
1497
1561
  BookReader.prototype.getToolBarHeight = Toolbar.prototype.getToolBarHeight;
1498
1562
  exposeOverrideableMethod(Toolbar, '_components.toolbar', 'getToolBarHeight');
1499
1563
 
1500
- /**
1501
- * Bind navigation handlers
1502
- */
1503
- BookReader.prototype.bindNavigationHandlers = function() {
1504
- const self = this;
1505
- const jIcons = this.$('.BRicon');
1506
-
1507
- // Map of jIcon class -> click handler
1508
- const navigationControls = {
1509
- book_left: () => {
1510
- this.trigger(BookReader.eventNames.stop);
1511
- this.left();
1512
- },
1513
- book_right: () => {
1514
- this.trigger(BookReader.eventNames.stop);
1515
- this.right();
1516
- },
1517
- book_top: this.first.bind(this),
1518
- book_bottom: this.last.bind(this),
1519
- book_leftmost: this.leftmost.bind(this),
1520
- book_rightmost: this.rightmost.bind(this),
1521
- onepg: () => {
1522
- this.switchMode(self.constMode1up);
1523
- },
1524
- thumb: () => {
1525
- this.switchMode(self.constModeThumb);
1526
- },
1527
- twopg: () => {
1528
- this.switchMode(self.constMode2up);
1529
- },
1530
- zoom_in: () => {
1531
- this.trigger(BookReader.eventNames.stop);
1532
- this.zoom(1);
1533
- this.trigger(BookReader.eventNames.zoomIn);
1534
- },
1535
- zoom_out: () => {
1536
- this.trigger(BookReader.eventNames.stop);
1537
- this.zoom(-1);
1538
- this.trigger(BookReader.eventNames.zoomOut);
1539
- },
1540
- full: () => {
1541
- if (this.ui == 'embed') {
1542
- const url = this.$('.BRembedreturn a').attr('href');
1543
- window.open(url);
1544
- } else {
1545
- this.toggleFullscreen();
1546
- }
1547
- },
1548
- };
1549
-
1550
- // custom event for auto-loan-renew in ia-book-actions
1551
- // - to know if user is actively reading
1552
- this.$('nav.BRcontrols li button').on('click', () => {
1553
- this.trigger(BookReader.eventNames.userAction);
1554
- });
1555
-
1556
- for (const control in navigationControls) {
1557
- jIcons.filter(`.${control}`).on('click.bindNavigationHandlers', () => {
1558
- navigationControls[control]();
1559
- return false;
1560
- });
1561
- }
1562
-
1563
- const $brNavCntlBtmEl = this.$('.BRnavCntlBtm');
1564
- const $brNavCntlTopEl = this.$('.BRnavCntlTop');
1565
-
1566
- this.$('.BRnavCntl').click(
1567
- function() {
1568
- const promises = [];
1569
- // TODO don't use magic constants
1570
- // TODO move this to a function
1571
- if ($brNavCntlBtmEl.hasClass('BRdn')) {
1572
- if (self.refs.$BRtoolbar)
1573
- promises.push(self.refs.$BRtoolbar.animate(
1574
- {top: self.getToolBarHeight() * -1},
1575
- ).promise());
1576
- promises.push(self.$('.BRfooter').animate({bottom: self.getFooterHeight() * -1}).promise());
1577
- $brNavCntlBtmEl.addClass('BRup').removeClass('BRdn');
1578
- $brNavCntlTopEl.addClass('BRdn').removeClass('BRup');
1579
- self.$('.BRnavCntlBtm.BRnavCntl').animate({height:'45px'});
1580
- self.$('.BRnavCntl').delay(1000).animate({opacity:.75}, 1000);
1581
- } else {
1582
- if (self.refs.$BRtoolbar)
1583
- promises.push(self.refs.$BRtoolbar.animate({top:0}).promise());
1584
- promises.push(self.$('.BRfooter').animate({bottom:0}).promise());
1585
- $brNavCntlBtmEl.addClass('BRdn').removeClass('BRup');
1586
- $brNavCntlTopEl.addClass('BRup').removeClass('BRdn');
1587
- self.$('.BRnavCntlBtm.BRnavCntl').animate({height:'30px'});
1588
- self.$('.BRvavCntl').animate({opacity:1});
1589
- }
1590
- $.when.apply($, promises).done(function() {
1591
- // Only do full resize in auto mode and need to recalc. size
1592
- if (self.mode == self.constMode2up && self.twoPage.autofit != null
1593
- && self.twoPage.autofit != 'none'
1594
- ) {
1595
- self.resize();
1596
- } else if (self.mode == self.constMode1up && self.onePage.autofit != null
1597
- && self.onePage.autofit != 'none') {
1598
- self.resize();
1599
- } else {
1600
- // Don't do a full resize to avoid redrawing images
1601
- self.resizeBRcontainer();
1602
- }
1603
- });
1604
- },
1605
- );
1606
- $brNavCntlBtmEl
1607
- .on("mouseover", function() {
1608
- if ($(this).hasClass('BRup')) {
1609
- self.$('.BRnavCntl').animate({opacity:1},250);
1610
- }
1611
- })
1612
- .on("mouseleave", function() {
1613
- if ($(this).hasClass('BRup')) {
1614
- self.$('.BRnavCntl').animate({opacity:.75},250);
1615
- }
1616
- });
1617
- $brNavCntlTopEl
1618
- .on("mouseover", function() {
1619
- if ($(this).hasClass('BRdn')) {
1620
- self.$('.BRnavCntl').animate({opacity:1},250);
1621
- }
1622
- })
1623
- .on("mouseleave", function() {
1624
- if ($(this).hasClass('BRdn')) {
1625
- self.$('.BRnavCntl').animate({opacity:.75},250);
1626
- }
1627
- });
1628
-
1629
- // Call _bindNavigationHandlers on the plugins
1630
- for (const plugin of Object.values(this.plugins)) {
1631
- plugin._bindNavigationHandlers();
1632
- }
1633
- };
1634
-
1635
1564
  /**************************/
1636
1565
  /** BookModel extensions **/
1637
1566
  /**************************/
@@ -1805,8 +1734,6 @@ BookReader.prototype.initUIStrings = function() {
1805
1734
  '.book_right': 'Flip right',
1806
1735
  '.play': 'Play',
1807
1736
  '.pause': 'Pause',
1808
- '.BRdn': 'Show/hide nav bar', // Would have to keep updating on state change to have just "Hide nav bar"
1809
- '.BRup': 'Show/hide nav bar',
1810
1737
  '.book_top': 'First page',
1811
1738
  '.book_bottom': 'Last page',
1812
1739
  '.book_leftmost': 'First page',
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g fill="#fff" fill-rule="evenodd"><path d="m20.5312017 91.3043478h59.513497c1.2006191 0 2.1739131-.973294 2.1739131-2.173913 0-.3822983-.1008154-.7578365-.2922774-1.0887357l-25.7859388-44.5652174c-.3886001-.6716082-1.1057054-1.0851774-1.8816356-1.0851774h-9.4292698c-.7956659 0-1.5277636.4346832-1.9086489 1.1332607l-24.2982884 44.5652174c-.5747365 1.0541176-.1861212 2.3745648.8679964 2.9493012.3192413.1740599.677043.2652642 1.0406524.2652642z"/><path d="m58.2338112 0c3.9857698 0 7.0754101 2.92605669 7.0754101 6.52173913 0 1.94814675-1.1751392 3.49748367-3.1329548 4.78763887-.7622726.5029309-1.2209368 1.3542647-1.2211436 2.2659686l-.0000001 17.2659447c0 1.4033895.3628136 2.7830137 1.0533233 4.0053358l29.9382305 52.9958632c.6905098 1.2223221 1.0533234 2.6019463 1.0533234 4.0053358 0 4.5023213-3.655118 8.1521739-8.1639346 8.1521739h-69.6705953c-1.3692898 0-2.7165851-.3439149-3.9178929-1.0000939-3.95568315-2.1606754-5.40829754-7.1143442-3.24450505-11.0643289l29.09149695-53.106279c.6571256-1.1995773 1.0015367-2.5449317 1.0015367-3.9122489l-.0000001-17.3410875c-.000193-.8514978-.3997534-1.6498707-1.0727831-2.1614374l-.1478555-.104365c-1.8735444-1.2352818-3.0298253-2.70631266-3.1268298-4.53659758l-.00663-.25182269c0-3.59709019 3.085096-6.52173913 7.07541-6.52173913zm0 5.43478261h-17.4163939c-.3341979 0-.655354.0973303-.9634683.29199092l-.1529759.10543374c-.2276736.17021467-.2742532.49276685-.1040386.72044045l.0538074.06119811.0627915.05193913.1617643.10919499c2.2021208 1.45291154 3.5596356 3.87504265 3.6577257 6.49699115l.0057053.3033755.0000002 17.3417027c0 2.2788621-.5740185 4.5211194-1.6692279 6.5204148l-29.0914969 53.106279c-.7212642 1.3166616-.2370594 2.9678845 1.0815017 3.6881097.4004359.2187263.8495344.3333646 1.3059643.3333646h69.6705953c1.5029388 0 2.7213115-1.2166175 2.7213115-2.7173913 0-.4677965-.1209379-.9276713-.3511078-1.3351119l-29.9382305-52.9958633c-1.1508495-2.0372034-1.7555389-4.336577-1.7555389-6.6755596l.0000002-17.2665599c.0005984-2.6392088 1.2800823-5.10829666 3.4230589-6.63488495l.2416409-.16570308c.2977633-.19621921.380081-.59667116.1838618-.89443446-.0526648-.07991901-.1224381-.14713032-.2042705-.1967699-.2372346-.14393755-.4815228-.22451299-.7328507-.24174888z" fill-rule="nonzero"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g fill="#FFF"><path d="m55.6227664 40v-10.9816972c0-1.2005631.4447386-2.2302572 1.3342157-3.0890823.8894772-.858825 1.9338187-1.2888775 3.1330245-1.2901574h7.702184v6.5391015l14.2078094-10.6188404-14.2078094-10.5593242v6.6005376h-7.8252813c-3.5579086 0-6.6082065 1.2300012-9.1508934 3.6900038-2.542687 2.4600026-3.8146923 5.4095738-3.8160159 8.8487137v10.8607449z" transform="matrix(-1 0 0 1 129 0)"/><path d="m26.6227664 90v-10.9816972c0-1.2005631.4447386-2.2302572 1.3342157-3.0890823.8894772-.858825 1.9338187-1.2888775 3.1330245-1.2901574h7.702184v6.5391015l14.2078094-10.6188404-14.2078094-10.5593242v6.6005376h-7.8252813c-3.5579086 0-6.6082065 1.2300012-9.1508934 3.6900038-2.542687 2.4600026-3.8146923 5.4095738-3.8160159 8.8487137v10.8607449z" transform="matrix(1 0 0 -1 0 150)"/><g fill-rule="evenodd"><path d="m36.1711896 0c.7952259 0 1.5825239.11678603 2.361894.35035809.7793701.23357205 1.3385895.44226661 1.6776582.62608367.3390687.18381707.9354881.50929765 1.7892582.97644177v53.04711647h-41.11634795c-.26466873-.2667421-.47079323-.6253926-.61837349-1.0759518-.14758025-.4505591-.23600644-.8423796-.26527856-1.1754617v-52.7485865zm-15.8773502 11c-2.6260005.9445399-3.9390007 1.4168099-3.9390007 1.4168099.8613947 1.0405262 1.656859 2.1393299 2.3863927 3.2964109.2700118.4282536.5298575.8568906.7795373 1.2859111l-14.5207687.0008681v3l6.4238969.0000134c.8804258 3.5657294 2.3239844 6.8097976 4.3306759 9.7322048 1.0398193 1.5143213 2.176787 2.9231182 3.410903 4.2263908-3.5068695 3.0329696-7.8850877 5.246602-13.13321774 6.6415991 1.30042972 2.2665279 1.95064458 3.3997919 1.95064458 3.3997919 5.83922866-1.8768332 10.47451876-4.3674587 13.90587026-7.4718765.7278161.6172884 1.4851619 1.2034299 2.2700924 1.7596083 3.2815631 2.3252177 6.8996284 4.2293071 10.8541961 5.7122682 1.3246257-2.1759469 1.9869386-3.2639204 1.9869386-3.2639204-3.7993227-1.4042832-7.2042979-3.1143224-10.2149257-5.1301175-.8119265-.5436333-1.5953934-1.1239157-2.3504008-1.7408472 3.3115726-3.9242615 5.6922469-8.5457291 7.1440766-13.8651104l4.3889918-.0000045v-3l-12.2148765-.0011479c-.3495756-.7520023-.7243601-1.4925223-1.1243535-2.2215599-.7129337-1.2994097-1.4911578-2.5585071-2.3346725-3.7772922zm7.4469262 9.0006565c-1.3612535 4.5640835-3.3583061 8.3614158-5.9911579 11.3919968-1.0548712-1.0913671-2.041101-2.2689312-2.9598238-3.5334282-1.7358278-2.3891311-2.9991645-5.0087964-3.79001-7.858996z"/><path d="m94.1711896 45c.7952259 0 1.5825239.116786 2.361894.3503581.7793701.233572 1.3385895.4422666 1.6776582.6260837.3390687.183817.9354881.5092976 1.7892582.9764417v53.0471165h-41.116348c-.2646687-.2667421-.4707932-.6253926-.6183734-1.0759518-.1475803-.4505591-.2360065-.8423796-.2652786-1.1754617v-52.7485865zm-11.1769203 11h-7.8424068l-13.1518625 33h7.6962751l2.7277937-7.3487395h13.0057306l2.6303725 7.3487395h7.9398281zm-3.8794044 8.25 4.1351351 12.375h-8.5l4.272973-12.375z"/></g></g></svg>
Binary file
@@ -35,14 +35,6 @@
35
35
  border: 3px solid rgba(255,255,255,0.3);
36
36
  }
37
37
  }
38
-
39
- .BRnavCntl {
40
- z-index: $brZindexBase + 5;
41
- background-color: $brColorDarkBorder;
42
- }
43
- .BRnavCntlBtm:hover {
44
- background-color: $brColorThemeblue;
45
- }
46
38
  }
47
39
 
48
40
  @mixin brNavLight {
@@ -66,14 +58,6 @@
66
58
  border: 3px solid rgba($brColorThemeblue,0.3);
67
59
  }
68
60
  }
69
-
70
- .BRnavCntl {
71
- background-color: $brColorLightBorder;
72
- }
73
-
74
- .BRnavCntlBtm:hover {
75
- background-color: $brColorThemeblue;
76
- }
77
61
  }
78
62
 
79
63
  @keyframes fadeUp {
@@ -319,20 +303,12 @@
319
303
  font-size: .8em;
320
304
  vertical-align: top;
321
305
  }
322
- .BRup {
323
- background-image: url("images/nav_control-up.png");
324
- background-repeat: no-repeat;
325
- }
326
-
327
306
 
328
307
  /* Mobile Only */
329
308
  @media (max-width: $brBreakPointMobile) {
330
309
  /* hide navline chapters and search in mobile */
331
310
  .BRnavline .BRchapter { display: none; }
332
311
 
333
- .BRnavCntlBtm {
334
- bottom: $brNavHeightMobile;
335
- }
336
312
  .BRpager.ui-slider {
337
313
  height: 10px;
338
314
  top: math.div($brNavHeightMobile, 2) - 5px;
@@ -45,6 +45,7 @@
45
45
  // but they appear the same in the UI.
46
46
  .searchHiliteLayer, .ttsHiliteLayer {
47
47
  pointer-events: none;
48
+ z-index: 4;
48
49
 
49
50
  rect {
50
51
  // Note: Can't use fill-opacity ; safari inexplicably applies that to
@@ -241,11 +242,6 @@
241
242
  height: 18px;
242
243
  }
243
244
  }
244
- + .BRnav {
245
- .BRnavCntl {
246
- display: none;
247
- }
248
- }
249
245
  }
250
246
  }
251
247
 
@@ -1,4 +1,4 @@
1
- .BRtextLayer {
1
+ .BRtextLayer, .BRtranslateLayer {
2
2
  z-index: 2;
3
3
  position: absolute;
4
4
  top: 0;
@@ -8,9 +8,16 @@
8
8
  // Make it so right-clicking on "blank" part of text layer sends events to the image (for saving)
9
9
  pointer-events: none;
10
10
  cursor: text;
11
+ }
12
+
13
+ .BRtextLayer {
11
14
  mix-blend-mode: multiply;
12
15
  }
13
16
 
17
+ .BRtranslateLayer {
18
+ hyphens: auto;
19
+ }
20
+
14
21
  .BRparagraphElement {
15
22
  margin: 0;
16
23
  cursor: text;
@@ -81,21 +88,20 @@
81
88
  // These are Microsoft Edge specific fixed to make some of the
82
89
  // browsers features work well. These are for the in-place
83
90
  // translation.
91
+ .BRtextLayer:has([_istranslated="1"], msreadoutspan) {
92
+ mix-blend-mode: normal;
93
+ }
94
+
84
95
  .BRwordElement, .BRspace {
85
96
  &[_istranslated="1"], &[_msttexthash] {
86
- background-color: #e4dccd;
87
97
  color: black;
88
98
  letter-spacing: unset !important;
89
- background: #ccbfa7;
90
99
  }
91
100
  }
92
101
 
93
- .BRlineElement font[_mstmutation="1"] {
94
- background: #ccbfa7;
95
- }
96
-
97
102
  .BRlineElement:has([_istranslated="1"], [_msttexthash]) {
98
- background-color: #e4dccd;
103
+ background: rgba(248, 237, 192, 0.8);
104
+ backdrop-filter: blur(8px);
99
105
  color: black;
100
106
  text-align: justify;
101
107
  width: inherit;
@@ -105,6 +111,29 @@
105
111
  }
106
112
 
107
113
  .BRlineElement[_msttexthash] {
108
- background: #ccbfa7;
109
114
  word-spacing: unset !important;
110
115
  }
116
+
117
+ .BRtranslateLayer .BRparagraphElement {
118
+ pointer-events: auto;
119
+ overflow-y: auto;
120
+ background: rgba(248, 237, 192, 0.8);
121
+ backdrop-filter: blur(8px);
122
+ color:black;
123
+ line-height: 1em;
124
+ text-align: justify;
125
+ }
126
+
127
+ .BRtranslateLayer .BRparagraphElement .BRlineElement {
128
+ white-space: break-spaces;
129
+ display: inline-block;
130
+ }
131
+
132
+ .BRtextLayer.showingTranslation {
133
+ visibility: hidden;
134
+ pointer-events: none;
135
+ }
136
+
137
+ .BRtranslateLayer .BRparagraphElement.BRtranslateHidden {
138
+ display: none;
139
+ }
@@ -21,6 +21,9 @@ class ExperimentModel {
21
21
  /** @type {string} */
22
22
  learnMore;
23
23
 
24
+ /** @type {import("@/src/BookReader.js").default} */
25
+ br;
26
+
24
27
  assetRoot = '/BookReader/';
25
28
 
26
29
  enabledLoading = false;
@@ -46,10 +49,32 @@ export class ExperimentsPlugin extends BookReaderPlugin {
46
49
 
47
50
  /** Where the state of this plugin is saved in localStorage */
48
51
  localStorageKey: 'BrExperiments',
52
+
53
+ /** The experiments that should be shown in the experiments panel */
54
+ enabledExperiments: ['translate'],
49
55
  }
50
56
 
51
57
  /** @type {ExperimentModel[]} */
52
- experiments = [
58
+ allExperiments = [
59
+ new class extends ExperimentModel {
60
+ name = 'translate';
61
+ title = 'Translate Plugin';
62
+ description = "Translate books directly in your browser.";
63
+ learnMore = 'https://mozilla.github.io/translations/';
64
+ icon = 'images/translate.svg';
65
+ enabled = false;
66
+ async enable({ manual = false}) {
67
+ await importAsScript(this.buildAssetPath('plugins/plugin.translate.js'));
68
+ this.br.initializePlugin('translate');
69
+ }
70
+ async disable() {
71
+ // need to reload to remove translate plugin script
72
+ // Sleep so that the event loop can finish processing before the reload
73
+ sleep(0).then(() => {
74
+ window.location.reload();
75
+ });
76
+ }
77
+ }(),
53
78
  new class extends ExperimentModel {
54
79
  name = 'hypothesis';
55
80
  title = 'Hypothes.is';
@@ -93,10 +118,11 @@ export class ExperimentsPlugin extends BookReaderPlugin {
93
118
  return;
94
119
  }
95
120
 
96
- for (const experiment of this.experiments) {
121
+ for (const experiment of this.allExperiments) {
97
122
  // TODO: imagesBaseURL should be replaced with assetRoot everywhere
98
123
  experiment.assetRoot = this.br.options.imagesBaseURL.replace(/images\/$/, '');
99
124
  experiment.icon = experiment.buildAssetPath(experiment.icon);
125
+ experiment.br = this.br;
100
126
  }
101
127
 
102
128
  this._loadExperimentStates();
@@ -106,7 +132,7 @@ export class ExperimentsPlugin extends BookReaderPlugin {
106
132
 
107
133
  _loadExperimentStates() {
108
134
  const savedStates = JSON.parse(localStorage.getItem(this.options.localStorageKey) || '{}');
109
- this.experiments.forEach(experiment => {
135
+ this.allExperiments.forEach(experiment => {
110
136
  if (savedStates[experiment.name] !== undefined) {
111
137
  experiment.enabled = savedStates[experiment.name];
112
138
  if (experiment.enabled) {
@@ -118,7 +144,7 @@ export class ExperimentsPlugin extends BookReaderPlugin {
118
144
 
119
145
  _saveExperimentStates() {
120
146
  const states = Object.fromEntries(
121
- this.experiments.map(experiment => [experiment.name, experiment.enabled]),
147
+ this.allExperiments.map(experiment => [experiment.name, experiment.enabled]),
122
148
  );
123
149
  localStorage.setItem(this.options.localStorageKey, JSON.stringify(states));
124
150
  }
@@ -145,13 +171,12 @@ export class ExperimentsPlugin extends BookReaderPlugin {
145
171
  _render() {
146
172
  this.br.shell.menuProviders['experiments'] = {
147
173
  id: 'experiments',
148
- // https://icon-sets.iconify.design/hugeicons/?icon-filter=eco-lab-02&query=lab
149
174
  icon: html`
150
- <svg xmlns="http://www.w3.org/2000/svg" width="34" viewBox="0 0 24 24"><path fill="currentColor" d="M10 5.75h1.25v2.5H9.5c-.41 0-.75.34-.75.75s.34.75.75.75h.25v1.96A5.72 5.72 0 0 0 6.25 17c0 3.17 2.58 5.75 5.75 5.75s5.75-2.58 5.75-5.75c0-2.33-1.39-4.4-3.5-5.29V9.75h.25c.41 0 .75-.34.75-.75s-.34-.75-.75-.75h-1.75v-1.5H14c1.52 0 2.75-1.23 2.75-2.75V3c0-.41-.34-.75-.75-.75h-2c-.579 0-1.115.178-1.558.483A2.75 2.75 0 0 0 10 1.25H8c-.41 0-.75.34-.75.75v1c0 1.52 1.23 2.75 2.75 2.75m2.75-.5V5c0-.69.56-1.25 1.25-1.25h1.25V4c0 .69-.56 1.25-1.25 1.25zm-1.5 6.98V9.75h1.5v2.48c0 .33.22.62.53.72c1.77.56 2.97 2.19 2.97 4.06A4.26 4.26 0 0 1 12 21.26a4.26 4.26 0 0 1-4.25-4.25c0-1.87 1.19-3.5 2.97-4.06c.32-.1.53-.39.53-.72m-2.5-9.48H10c.69 0 1.25.56 1.25 1.25v.25H10c-.69 0-1.25-.56-1.25-1.25z" color="currentColor"/></svg>
175
+ <img src="${this.br.options.imagesBaseURL}/icon_experiment.svg" width="26"/>
151
176
  `,
152
177
  label: 'Experiments',
153
178
  component: html`<br-experiments-panel
154
- .experiments="${this.experiments}"
179
+ .experiments="${this.allExperiments.filter(experiment => this.options.enabledExperiments.includes(experiment.name))}"
155
180
  @connected="${e => this._panel = e.target}"
156
181
  @toggle="${async e => {
157
182
  await this._toggleExperiment(e.detail.experiment, e.detail.enabled);
@@ -240,14 +265,14 @@ export class BrExperimentToggle extends LitElement {
240
265
 
241
266
  render() {
242
267
  return html`
243
- <div class="experiment-card">
268
+ <div class="experiment-card" style="margin-bottom: 10px;">
244
269
  <div style="display: flex; align-items: center; gap: 10px;">
245
270
  <img src="${this.icon}" style="width: 20px; height: 20px;" alt="" />
246
271
  <div style="flex-grow: 1; font-weight: bold;">${this.title}</div>
247
272
  </div>
248
273
  <p style="opacity: 0.9">
249
274
  ${this.description}
250
- <a href="${this.learnMore}" target="_blank">Learn more</a>.
275
+ ${this.learnMore ? html`<a href="${this.learnMore}" target="_blank">Learn more</a>.` : ''}
251
276
  </p>
252
277
  <div style="display: flex">
253
278
  <div style="flex-grow: 1;"></div>