@internetarchive/bookreader 5.0.0-97 → 5.0.0-99
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.
- package/BookReader/BookReader.css +23 -20
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/bergamot-translator-worker.js +1 -1
- package/BookReader/ia-bookreader-bundle.js +156 -156
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +1 -1
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.translate.js +135 -1
- package/BookReader/plugins/plugin.translate.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/silence.mp3 +0 -0
- package/package.json +2 -2
- package/src/BookReader/options.js +12 -8
- package/src/BookReader.js +53 -5
- package/src/assets/silence.mp3 +0 -0
- package/src/css/_BRpages.scss +1 -9
- package/src/css/_BRsearch.scss +1 -0
- package/src/css/_TextSelection.scss +23 -11
- package/src/plugins/plugin.text_selection.js +14 -168
- package/src/plugins/translate/TranslationManager.js +19 -24
- package/src/plugins/translate/plugin.translate.js +189 -78
- package/src/plugins/tts/AbstractTTSEngine.js +3 -4
- package/src/plugins/tts/PageChunk.js +28 -9
- package/src/plugins/tts/WebTTSEngine.js +5 -7
- package/src/plugins/tts/plugin.tts.js +40 -4
- package/src/plugins/tts/utils.js +15 -5
- package/src/util/TextSelectionManager.js +282 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@internetarchive/bookreader",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-99",
|
|
4
4
|
"description": "The Internet Archive BookReader.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"homepage": "https://github.com/internetarchive/bookreader#readme",
|
|
32
32
|
"private": false,
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@internetarchive/bergamot-translator": "0.4.9-ia.
|
|
34
|
+
"@internetarchive/bergamot-translator": "^0.4.9-ia.1",
|
|
35
35
|
"@internetarchive/ia-activity-indicator": "^0.0.4",
|
|
36
36
|
"@internetarchive/ia-item-navigator": "^2.1.2",
|
|
37
37
|
"@internetarchive/icon-bookmark": "^1.3.4",
|
|
@@ -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:
|
|
150
|
+
archiveAnalytics: {},
|
|
151
151
|
/** @type {Partial<import('../plugins/plugin.autoplay.js').AutoplayPlugin['options'>]}*/
|
|
152
|
-
autoplay:
|
|
152
|
+
autoplay: {},
|
|
153
153
|
/** @type {Partial<import('../plugins/plugin.chapters.js').ChaptersPlugin['options']>} */
|
|
154
|
-
chapters:
|
|
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:
|
|
158
|
+
iiif: {},
|
|
157
159
|
/** @type {Partial<import('../plugins/plugin.resume.js').ResumePlugin['options']>} */
|
|
158
|
-
resume:
|
|
160
|
+
resume: {},
|
|
159
161
|
/** @type {Partial<import('../plugins/search/plugin.search.js').SearchPlugin['options']>} */
|
|
160
|
-
search:
|
|
162
|
+
search: {},
|
|
161
163
|
/** @type {Partial<import('../plugins/plugin.text_selection.js').TextSelectionPlugin['options']>} */
|
|
162
|
-
textSelection:
|
|
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:
|
|
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 =
|
|
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
|
|
|
@@ -365,6 +408,7 @@ BookReader.prototype.getActivePageContainerElements = function() {
|
|
|
365
408
|
* Get the HTML Elements for the rendered page. Note there can be more than one, since
|
|
366
409
|
* (at least as of writing) different modes can maintain different caches.
|
|
367
410
|
* @param {PageIndex} pageIndex
|
|
411
|
+
* @returns {HTMLElement[]}
|
|
368
412
|
*/
|
|
369
413
|
BookReader.prototype.getActivePageContainerElementsForIndex = function(pageIndex) {
|
|
370
414
|
return [
|
|
@@ -961,7 +1005,8 @@ BookReader.prototype.zoom = function(direction) {
|
|
|
961
1005
|
} else {
|
|
962
1006
|
this.activeMode.zoom('out');
|
|
963
1007
|
}
|
|
964
|
-
this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
|
|
1008
|
+
this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
1009
|
+
this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
965
1010
|
return;
|
|
966
1011
|
};
|
|
967
1012
|
|
|
@@ -1208,7 +1253,8 @@ BookReader.prototype.switchMode = function(
|
|
|
1208
1253
|
const eventName = mode + 'PageViewSelected';
|
|
1209
1254
|
this.trigger(BookReader.eventNames[eventName]);
|
|
1210
1255
|
|
|
1211
|
-
this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
|
|
1256
|
+
this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
1257
|
+
this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
1212
1258
|
};
|
|
1213
1259
|
|
|
1214
1260
|
BookReader.prototype.updateBrClasses = function() {
|
|
@@ -1280,7 +1326,8 @@ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = tru
|
|
|
1280
1326
|
}
|
|
1281
1327
|
this.jumpToIndex(currentIndex);
|
|
1282
1328
|
|
|
1283
|
-
this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
|
|
1329
|
+
this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
1330
|
+
this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
1284
1331
|
// Add "?view=theater"
|
|
1285
1332
|
this.trigger(BookReader.eventNames.fragmentChange);
|
|
1286
1333
|
// trigger event here, so that animations,
|
|
@@ -1326,7 +1373,8 @@ BookReader.prototype.exitFullScreen = async function () {
|
|
|
1326
1373
|
await this.activeMode.mode1UpLit.updateComplete;
|
|
1327
1374
|
}
|
|
1328
1375
|
|
|
1329
|
-
this.plugins.textSelection?.stopPageFlip(this.refs.$brContainer);
|
|
1376
|
+
this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
1377
|
+
this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
|
|
1330
1378
|
// Remove "?view=theater"
|
|
1331
1379
|
this.trigger(BookReader.eventNames.fragmentChange);
|
|
1332
1380
|
this.refs.$br.removeClass('BRfullscreenAnimation');
|
|
Binary file
|
package/src/css/_BRpages.scss
CHANGED
|
@@ -91,16 +91,8 @@
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
svg.BRPageLayer {
|
|
95
|
-
position: absolute;
|
|
96
|
-
top: 0;
|
|
97
|
-
left: 0;
|
|
98
|
-
right: 0;
|
|
99
|
-
bottom: 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
94
|
// Hides page layers during page flip animation
|
|
103
|
-
.BRpageFlipping .
|
|
95
|
+
.BRpageFlipping .BRtextLayer {
|
|
104
96
|
display: none;
|
|
105
97
|
}
|
|
106
98
|
|
package/src/css/_BRsearch.scss
CHANGED
|
@@ -54,6 +54,21 @@
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Style URI TextFragments, eg #:~:text=example
|
|
58
|
+
.BRtextLayer ::target-text {
|
|
59
|
+
// Similar colour to the default one used in Safari, Firefox. Note Chrome uses a purple colour
|
|
60
|
+
background-color: hsla(45, 80%, 66%, 0.6);
|
|
61
|
+
color: transparent;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.BRtranslateLayer ::selection {
|
|
65
|
+
background: hsla(210, 74%, 62%, 0.4);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.BRtranslateLayer ::-moz-selection {
|
|
69
|
+
background: hsla(210, 74%, 62%, 0.4);
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
.BRparagraphElement br {
|
|
58
73
|
visibility: hidden;
|
|
59
74
|
}
|
|
@@ -88,21 +103,20 @@
|
|
|
88
103
|
// These are Microsoft Edge specific fixed to make some of the
|
|
89
104
|
// browsers features work well. These are for the in-place
|
|
90
105
|
// translation.
|
|
106
|
+
.BRtextLayer:has([_istranslated="1"], msreadoutspan) {
|
|
107
|
+
mix-blend-mode: normal;
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
.BRwordElement, .BRspace {
|
|
92
111
|
&[_istranslated="1"], &[_msttexthash] {
|
|
93
|
-
background-color: #e4dccd;
|
|
94
112
|
color: black;
|
|
95
113
|
letter-spacing: unset !important;
|
|
96
|
-
background: #ccbfa7;
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
116
|
|
|
100
|
-
.BRlineElement font[_mstmutation="1"] {
|
|
101
|
-
background: #ccbfa7;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
117
|
.BRlineElement:has([_istranslated="1"], [_msttexthash]) {
|
|
105
|
-
background
|
|
118
|
+
background: rgba(248, 237, 192, 0.8);
|
|
119
|
+
backdrop-filter: blur(8px);
|
|
106
120
|
color: black;
|
|
107
121
|
text-align: justify;
|
|
108
122
|
width: inherit;
|
|
@@ -112,14 +126,13 @@
|
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
.BRlineElement[_msttexthash] {
|
|
115
|
-
background: #ccbfa7;
|
|
116
129
|
word-spacing: unset !important;
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
.BRtranslateLayer .BRparagraphElement {
|
|
120
133
|
pointer-events: auto;
|
|
121
134
|
overflow-y: auto;
|
|
122
|
-
background: rgba(248, 237, 192, 0.
|
|
135
|
+
background: rgba(248, 237, 192, 0.8);
|
|
123
136
|
backdrop-filter: blur(8px);
|
|
124
137
|
color:black;
|
|
125
138
|
line-height: 1em;
|
|
@@ -132,8 +145,7 @@
|
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
.BRtextLayer.showingTranslation {
|
|
135
|
-
|
|
136
|
-
pointer-events: none;
|
|
148
|
+
display: none;
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
.BRtranslateLayer .BRparagraphElement.BRtranslateHidden {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
import { createDIVPageLayer } from '../BookReader/PageContainer.js';
|
|
3
|
-
import { SelectionObserver } from '../BookReader/utils/SelectionObserver.js';
|
|
4
3
|
import { BookReaderPlugin } from '../BookReaderPlugin.js';
|
|
5
4
|
import { applyVariables } from '../util/strings.js';
|
|
6
5
|
import { Cache } from '../util/cache.js';
|
|
6
|
+
import { toISO6391 } from './tts/utils.js';
|
|
7
|
+
import { TextSelectionManager } from '../util/TextSelectionManager.js';
|
|
7
8
|
/** @typedef {import('../util/strings.js').StringWithVars} StringWithVars */
|
|
8
9
|
/** @typedef {import('../BookReader/PageContainer.js').PageContainer} PageContainer */
|
|
9
10
|
|
|
@@ -19,7 +20,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
19
20
|
singlePageDjvuXmlUrl: null,
|
|
20
21
|
/** Whether to fetch the XML as a jsonp */
|
|
21
22
|
jsonp: false,
|
|
22
|
-
/** Mox words
|
|
23
|
+
/** Mox words that can be selected when the text layer is protected */
|
|
23
24
|
maxProtectedWords: 200,
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -45,7 +46,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
45
46
|
// now we do make that assumption.
|
|
46
47
|
/** Whether the book is right-to-left */
|
|
47
48
|
this.rtl = this.br.pageProgression === 'rl';
|
|
48
|
-
this.
|
|
49
|
+
this.textSelectionManager = new TextSelectionManager('.BRtextLayer', this.br, {selectionElement: ['.BRwordElement', '.BRspace']}, this.options.maxProtectedWords);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/** @override */
|
|
@@ -53,72 +54,9 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
53
54
|
if (!this.options.enabled) return;
|
|
54
55
|
|
|
55
56
|
this.loadData();
|
|
56
|
-
|
|
57
|
-
this.selectionObserver.attach();
|
|
58
|
-
new SelectionObserver('.BRtextLayer', (selectEvent) => {
|
|
59
|
-
// Track how often selection is used
|
|
60
|
-
if (selectEvent == 'started') {
|
|
61
|
-
this.br.plugins.archiveAnalytics?.sendEvent('BookReader', 'SelectStart');
|
|
62
|
-
|
|
63
|
-
// Set a class on the page to avoid hiding it when zooming/etc
|
|
64
|
-
this.br.refs.$br.find('.BRpagecontainer--hasSelection').removeClass('BRpagecontainer--hasSelection');
|
|
65
|
-
$(window.getSelection().anchorNode).closest('.BRpagecontainer').addClass('BRpagecontainer--hasSelection');
|
|
66
|
-
}
|
|
67
|
-
}).attach();
|
|
68
|
-
|
|
69
|
-
if (this.br.protected) {
|
|
70
|
-
document.addEventListener('selectionchange', this._limitSelection);
|
|
71
|
-
// Prevent right clicking when selected text
|
|
72
|
-
$(document.body).on('contextmenu dragstart copy', (e) => {
|
|
73
|
-
const selection = document.getSelection();
|
|
74
|
-
if (selection?.toString()) {
|
|
75
|
-
const intersectsTextLayer = $('.BRtextLayer')
|
|
76
|
-
.toArray()
|
|
77
|
-
.some(el => selection.containsNode(el, true));
|
|
78
|
-
if (intersectsTextLayer) {
|
|
79
|
-
e.preventDefault();
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
57
|
+
this.textSelectionManager.init();
|
|
85
58
|
}
|
|
86
59
|
|
|
87
|
-
_limitSelection = () => {
|
|
88
|
-
const selection = window.getSelection();
|
|
89
|
-
if (!selection.rangeCount) return;
|
|
90
|
-
|
|
91
|
-
const range = selection.getRangeAt(0);
|
|
92
|
-
|
|
93
|
-
// Check if range.startContainer is inside the sub-tree of .BRContainer
|
|
94
|
-
const startInBr = !!range.startContainer.parentElement.closest('.BRcontainer');
|
|
95
|
-
const endInBr = !!range.endContainer.parentElement.closest('.BRcontainer');
|
|
96
|
-
if (!startInBr && !endInBr) return;
|
|
97
|
-
if (!startInBr || !endInBr) {
|
|
98
|
-
// weird case, just clear the selection
|
|
99
|
-
selection.removeAllRanges();
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Find the last allowed word in the selection
|
|
104
|
-
const lastAllowedWord = genAt(
|
|
105
|
-
genFilter(
|
|
106
|
-
walkBetweenNodes(range.startContainer, range.endContainer),
|
|
107
|
-
(node) => node.classList?.contains('BRwordElement'),
|
|
108
|
-
),
|
|
109
|
-
this.options.maxProtectedWords - 1,
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
if (!lastAllowedWord || range.endContainer.parentNode == lastAllowedWord) return;
|
|
113
|
-
|
|
114
|
-
const newRange = document.createRange();
|
|
115
|
-
newRange.setStart(range.startContainer, range.startOffset);
|
|
116
|
-
newRange.setEnd(lastAllowedWord.firstChild, lastAllowedWord.textContent.length);
|
|
117
|
-
|
|
118
|
-
selection.removeAllRanges();
|
|
119
|
-
selection.addRange(newRange);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
60
|
/**
|
|
123
61
|
* @override
|
|
124
62
|
* @param {PageContainer} pageContainer
|
|
@@ -133,20 +71,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
133
71
|
return pageContainer;
|
|
134
72
|
}
|
|
135
73
|
|
|
136
|
-
/**
|
|
137
|
-
* @param {'started' | 'cleared'} type
|
|
138
|
-
* @param {HTMLElement} target
|
|
139
|
-
*/
|
|
140
|
-
_onSelectionChange = (type, target) => {
|
|
141
|
-
if (type === 'started') {
|
|
142
|
-
this.textSelectingMode(target);
|
|
143
|
-
} else if (type === 'cleared') {
|
|
144
|
-
this.defaultMode(target);
|
|
145
|
-
} else {
|
|
146
|
-
throw new Error(`Unknown type ${type}`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
74
|
loadData() {
|
|
151
75
|
// Only fetch the full djvu xml if the single page url isn't there
|
|
152
76
|
if (this.options.singlePageDjvuXmlUrl) return;
|
|
@@ -203,92 +127,6 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
203
127
|
}
|
|
204
128
|
}
|
|
205
129
|
|
|
206
|
-
/**
|
|
207
|
-
* Intercept copied text to remove any styling applied to it
|
|
208
|
-
* @param {JQuery} $container
|
|
209
|
-
*/
|
|
210
|
-
interceptCopy($container) {
|
|
211
|
-
$container[0].addEventListener('copy', (event) => {
|
|
212
|
-
const selection = document.getSelection();
|
|
213
|
-
event.clipboardData.setData('text/plain', selection.toString());
|
|
214
|
-
event.preventDefault();
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Applies mouse events when in default mode
|
|
220
|
-
* @param {HTMLElement} textLayer
|
|
221
|
-
*/
|
|
222
|
-
defaultMode(textLayer) {
|
|
223
|
-
const $pageContainer = $(textLayer).closest('.BRpagecontainer');
|
|
224
|
-
textLayer.style.pointerEvents = "none";
|
|
225
|
-
$pageContainer.find("img").css("pointer-events", "auto");
|
|
226
|
-
|
|
227
|
-
$(textLayer).off(".textSelectPluginHandler");
|
|
228
|
-
const startedMouseDown = this.mouseIsDown;
|
|
229
|
-
let skipNextMouseup = this.mouseIsDown;
|
|
230
|
-
if (startedMouseDown) {
|
|
231
|
-
textLayer.style.pointerEvents = "auto";
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Need to stop propagation to prevent DragScrollable from
|
|
235
|
-
// blocking selection
|
|
236
|
-
$(textLayer).on("mousedown.textSelectPluginHandler", (event) => {
|
|
237
|
-
this.mouseIsDown = true;
|
|
238
|
-
if ($(event.target).is(".BRwordElement, .BRspace")) {
|
|
239
|
-
event.stopPropagation();
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
$(textLayer).on("mouseup.textSelectPluginHandler", (event) => {
|
|
244
|
-
this.mouseIsDown = false;
|
|
245
|
-
textLayer.style.pointerEvents = "none";
|
|
246
|
-
if (skipNextMouseup) {
|
|
247
|
-
skipNextMouseup = false;
|
|
248
|
-
event.stopPropagation();
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* This mode is active while there is a selection on the given textLayer
|
|
255
|
-
* @param {HTMLElement} textLayer
|
|
256
|
-
*/
|
|
257
|
-
textSelectingMode(textLayer) {
|
|
258
|
-
const $pageContainer = $(textLayer).closest('.BRpagecontainer');
|
|
259
|
-
// Make text layer consume all events
|
|
260
|
-
textLayer.style.pointerEvents = "all";
|
|
261
|
-
// Block img from getting long-press to save while selecting
|
|
262
|
-
$pageContainer.find("img").css("pointer-events", "none");
|
|
263
|
-
|
|
264
|
-
$(textLayer).off(".textSelectPluginHandler");
|
|
265
|
-
|
|
266
|
-
$(textLayer).on('mousedown.textSelectPluginHandler', (event) => {
|
|
267
|
-
this.mouseIsDown = true;
|
|
268
|
-
event.stopPropagation();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Prevent page flip on click
|
|
272
|
-
$(textLayer).on('mouseup.textSelectPluginHandler', (event) => {
|
|
273
|
-
this.mouseIsDown = false;
|
|
274
|
-
event.stopPropagation();
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Initializes text selection modes if there is a text layer on the page
|
|
280
|
-
* @param {JQuery} $container
|
|
281
|
-
*/
|
|
282
|
-
stopPageFlip($container) {
|
|
283
|
-
/** @type {JQuery<HTMLElement>} */
|
|
284
|
-
const $textLayer = $container.find('.BRtextLayer');
|
|
285
|
-
if (!$textLayer.length) return;
|
|
286
|
-
$textLayer.each((i, s) => this.defaultMode(s));
|
|
287
|
-
if (!this.br.protected) {
|
|
288
|
-
this.interceptCopy($container);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
130
|
/**
|
|
293
131
|
* @param {PageContainer} pageContainer
|
|
294
132
|
*/
|
|
@@ -314,6 +152,10 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
314
152
|
const ratioW = parseFloat(pageContainer.$container[0].style.width) / pageContainer.page.width;
|
|
315
153
|
const ratioH = parseFloat(pageContainer.$container[0].style.height) / pageContainer.page.height;
|
|
316
154
|
textLayer.style.transform = `scale(${ratioW}, ${ratioH})`;
|
|
155
|
+
const bookLangCode = toISO6391(this.br.options.bookLanguage);
|
|
156
|
+
if (bookLangCode) {
|
|
157
|
+
textLayer.setAttribute("lang", bookLangCode);
|
|
158
|
+
}
|
|
317
159
|
textLayer.setAttribute("dir", this.rtl ? "rtl" : "ltr");
|
|
318
160
|
|
|
319
161
|
const ocrParagraphs = $(XMLpage).find("PARAGRAPH[coords]").toArray();
|
|
@@ -339,7 +181,7 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
339
181
|
textLayer.appendChild(paragEl);
|
|
340
182
|
}
|
|
341
183
|
$container.append(textLayer);
|
|
342
|
-
this.stopPageFlip($container);
|
|
184
|
+
this.textSelectionManager.stopPageFlip($container);
|
|
343
185
|
this.br.trigger('textLayerRendered', {
|
|
344
186
|
pageIndex,
|
|
345
187
|
pageContainer,
|
|
@@ -353,6 +195,10 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
|
353
195
|
renderParagraph(ocrParagraph) {
|
|
354
196
|
const paragEl = document.createElement('p');
|
|
355
197
|
paragEl.classList.add('BRparagraphElement');
|
|
198
|
+
if (ocrParagraph.getAttribute("x-role")) {
|
|
199
|
+
paragEl.classList.add('ocr-role-header-footer');
|
|
200
|
+
paragEl.ariaHidden = "true";
|
|
201
|
+
}
|
|
356
202
|
const [paragLeft, paragBottom, paragRight, paragTop] = $(ocrParagraph).attr("coords").split(",").map(parseFloat);
|
|
357
203
|
const wordHeightArr = [];
|
|
358
204
|
const lines = $(ocrParagraph).find("LINE[coords]").toArray();
|
|
@@ -1,27 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { Cache } from '../../util/cache.js';
|
|
3
3
|
import { BatchTranslator } from '@internetarchive/bergamot-translator/translator.js';
|
|
4
|
-
|
|
5
|
-
export const langs = /** @type {{[lang: string]: string}} */ {
|
|
6
|
-
"bg": "Bulgarian",
|
|
7
|
-
"ca": "Catalan",
|
|
8
|
-
"cs": "Czech",
|
|
9
|
-
"nl": "Dutch",
|
|
10
|
-
"en": "English",
|
|
11
|
-
"et": "Estonian",
|
|
12
|
-
"de": "German",
|
|
13
|
-
"fr": "French",
|
|
14
|
-
"is": "Icelandic",
|
|
15
|
-
"it": "Italian",
|
|
16
|
-
"nb": "Norwegian Bokmål",
|
|
17
|
-
"nn": "Norwegian Nynorsk",
|
|
18
|
-
"fa": "Persian",
|
|
19
|
-
"pl": "Polish",
|
|
20
|
-
"pt": "Portuguese",
|
|
21
|
-
"ru": "Russian",
|
|
22
|
-
"es": "Spanish",
|
|
23
|
-
"uk": "Ukrainian",
|
|
24
|
-
};
|
|
4
|
+
import { toISO6391 } from '../tts/utils.js';
|
|
25
5
|
|
|
26
6
|
export class TranslationManager {
|
|
27
7
|
/** @type {Cache<{index: string, response: string}>} */
|
|
@@ -68,7 +48,7 @@ export class TranslationManager {
|
|
|
68
48
|
this._initResolve = resolve;
|
|
69
49
|
this._initReject = reject;
|
|
70
50
|
});
|
|
71
|
-
const registryUrl = "https://cors.archive.org/cors/mozilla-translate-models/";
|
|
51
|
+
const registryUrl = "https://cors.archive.org/cors/mozilla-translate-models/firefox_models/";
|
|
72
52
|
const registryJson = await fetch(registryUrl + "registry.json").then(r => r.json());
|
|
73
53
|
for (const language of Object.values(registryJson)) {
|
|
74
54
|
for (const file of Object.values(language)) {
|
|
@@ -97,10 +77,10 @@ export class TranslationManager {
|
|
|
97
77
|
// List of dev models found here https://github.com/mozilla/firefox-translations-models/tree/main/models/base
|
|
98
78
|
// There are also differences between the model types in the repo above here: https://github.com/mozilla/firefox-translations-models?tab=readme-ov-file#firefox-translations-models
|
|
99
79
|
if (firstLang !== "en") {
|
|
100
|
-
this.fromLanguages.push({code: firstLang, name:
|
|
80
|
+
this.fromLanguages.push({code: firstLang, name: toISO6391(firstLang, true), type: "prod"});
|
|
101
81
|
}
|
|
102
82
|
if (secondLang !== "en") {
|
|
103
|
-
this.toLanguages.push({code: secondLang, name:
|
|
83
|
+
this.toLanguages.push({code: secondLang, name: toISO6391(secondLang, true), type: "prod"});
|
|
104
84
|
}
|
|
105
85
|
}
|
|
106
86
|
this._initResolve([this.modelRegistry]);
|
|
@@ -160,8 +140,23 @@ export class TranslationManager {
|
|
|
160
140
|
}).then((resp) => {
|
|
161
141
|
const response = resp;
|
|
162
142
|
this.currentlyTranslating[key].resolve(response.target.text);
|
|
143
|
+
this.alreadyTranslated.add({index: key, response: response.target.text});
|
|
144
|
+
delete this.currentlyTranslating[key];
|
|
163
145
|
});
|
|
164
146
|
|
|
165
147
|
return promise;
|
|
166
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Checks and updates the status of a model that is currently being retreived from IA
|
|
152
|
+
* @param {string} fromLanguage
|
|
153
|
+
* @param {string} toLanguage
|
|
154
|
+
* @returns {Promise<boolean>}
|
|
155
|
+
*/
|
|
156
|
+
getTranslationModel = async(fromLanguage, toLanguage) => {
|
|
157
|
+
return this.translator.backing.getTranslationModel({from: fromLanguage, to: toLanguage})
|
|
158
|
+
.catch((err) => {
|
|
159
|
+
console.error(`An error occurred while trying to retreive the ${fromLanguage}-${toLanguage} model`);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
167
162
|
}
|