@internetarchive/bookreader 5.0.0-90 → 5.0.0-91
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.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +2 -2
- package/BookReader/plugins/plugin.chapters.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.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/src/BookReader/BookModel.js +5 -4
- package/src/BookReader/options.js +2 -0
- package/src/BookReader.js +18 -8
- package/src/plugins/plugin.chapters.js +220 -157
- package/src/plugins/plugin.text_selection.js +16 -0
- package/src/plugins/tts/WebTTSEngine.js +67 -41
- package/src/plugins/tts/plugin.tts.js +1 -3
- package/src/plugins/tts/utils.js +13 -0
- package/src/util/browserSniffing.js +11 -1
- package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +70 -0
- package/tests/jest/plugins/plugin.chapters.test.js +56 -58
- package/tests/jest/plugins/tts/WebTTSEngine.test.js +18 -12
- package/tests/jest/util/browserSniffing.test.js +9 -3
- package/tests/jest/utils.js +4 -1
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
# 5.0.0-91
|
2
|
+
- Refactor: Migrate ChaptersPlugin to BookReaderPlugin system @cdrini
|
3
|
+
- Breaking changes:
|
4
|
+
- Options moved:
|
5
|
+
- `options.olHost` → `options.plugins.chapters.olHost`
|
6
|
+
- `options.enableChaptersPlugin` → `options.plugins.chapters.enabled`
|
7
|
+
- Note `table_of_contents` _did not_ move; that is still root-level
|
8
|
+
- All chapters state/methods moved from `BookReader` to `BookReader.plugins.chapters`. Full list:
|
9
|
+
- eg `BookReader._chapters*` has been moved to `BookReader.plugins.chapters._*`
|
10
|
+
- See https://github.com/internetarchive/bookreader/pull/1381
|
11
|
+
- Feature: TextSelectionPlugin now supports `protected` option @cdrini
|
12
|
+
- Feature: New option for ChaptersPlugin for explicit `openLibraryId` @cdrini
|
13
|
+
- Fix: No longer error when book ends on unviewable page @cdrini
|
14
|
+
- Fix: ReadAloud no longer jumps to top of page every time it scrolls @cdrini
|
15
|
+
- Fix: Fix various ReadAloud issues -- ReadAloud stuttering, not stopping, getting stuck, etc. @cdrini
|
16
|
+
|
1
17
|
# 5.0.0-90
|
2
18
|
- Fix: Festival TTS not working @cdrini
|
3
19
|
|
package/package.json
CHANGED
@@ -274,6 +274,7 @@ export class BookModel {
|
|
274
274
|
* @param {number} [arg0.end] exclusive
|
275
275
|
* @param {boolean} [arg0.combineConsecutiveUnviewables] Yield only first unviewable
|
276
276
|
* of a chunk of unviewable pages instead of each page
|
277
|
+
* @return {Generator<PageModel>}
|
277
278
|
*/
|
278
279
|
* pagesIterator({ start = 0, end = Infinity, combineConsecutiveUnviewables = false } = {}) {
|
279
280
|
start = Math.max(0, start);
|
@@ -483,7 +484,7 @@ export class PageModel {
|
|
483
484
|
* @param {object} [arg0]
|
484
485
|
* @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
|
485
486
|
* of a series of unviewable pages instead of each page
|
486
|
-
* @return {PageModel|
|
487
|
+
* @return {PageModel|undefined}
|
487
488
|
*/
|
488
489
|
findNext({ combineConsecutiveUnviewables = false } = {}) {
|
489
490
|
return this.book
|
@@ -495,7 +496,7 @@ export class PageModel {
|
|
495
496
|
* @param {object} [arg0]
|
496
497
|
* @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
|
497
498
|
* of a series of unviewable pages instead of each page
|
498
|
-
* @return {PageModel|
|
499
|
+
* @return {PageModel|undefined}
|
499
500
|
*/
|
500
501
|
findPrev({ combineConsecutiveUnviewables = false } = {}) {
|
501
502
|
if (this.index == 0) return undefined;
|
@@ -518,7 +519,7 @@ export class PageModel {
|
|
518
519
|
* @param {object} [arg0]
|
519
520
|
* @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
|
520
521
|
* of a series of unviewable pages instead of each page
|
521
|
-
* @return {PageModel|
|
522
|
+
* @return {PageModel|undefined}
|
522
523
|
*/
|
523
524
|
findLeft({ combineConsecutiveUnviewables = false } = {}) {
|
524
525
|
return this.book.pageProgression === 'lr' ? this.findPrev({ combineConsecutiveUnviewables }) : this.findNext({ combineConsecutiveUnviewables });
|
@@ -528,7 +529,7 @@ export class PageModel {
|
|
528
529
|
* @param {object} [arg0]
|
529
530
|
* @param {boolean} [arg0.combineConsecutiveUnviewables] Whether to only yield the first page
|
530
531
|
* of a series of unviewable pages instead of each page
|
531
|
-
* @return {PageModel|
|
532
|
+
* @return {PageModel|undefined}
|
532
533
|
*/
|
533
534
|
findRight({ combineConsecutiveUnviewables = false } = {}) {
|
534
535
|
return this.book.pageProgression === 'lr' ? this.findNext({ combineConsecutiveUnviewables }) : this.findPrev({ combineConsecutiveUnviewables });
|
@@ -145,6 +145,8 @@ export const DEFAULT_OPTIONS = {
|
|
145
145
|
archiveAnalytics: null,
|
146
146
|
/** @type {import('../plugins/plugin.autoplay.js').AutoplayPlugin['options']}*/
|
147
147
|
autoplay: null,
|
148
|
+
/** @type {import('../plugins/plugin.chapters.js').ChaptersPlugin['options']} */
|
149
|
+
chapters: null,
|
148
150
|
/** @type {import('../plugins/plugin.iiif.js').IiifPlugin['options']} */
|
149
151
|
iiif: null,
|
150
152
|
/** @type {import('../plugins/plugin.resume.js').ResumePlugin['options']} */
|
package/src/BookReader.js
CHANGED
@@ -44,7 +44,7 @@ import { NAMED_REDUCE_SETS } from './BookReader/ReduceSet';
|
|
44
44
|
|
45
45
|
/**
|
46
46
|
* BookReader
|
47
|
-
* @param {BookReaderOptions}
|
47
|
+
* @param {Partial<BookReaderOptions>} overrides
|
48
48
|
* TODO document all options properties
|
49
49
|
* @constructor
|
50
50
|
*/
|
@@ -70,6 +70,8 @@ BookReader.PLUGINS = {
|
|
70
70
|
archiveAnalytics: null,
|
71
71
|
/** @type {typeof import('./plugins/plugin.autoplay.js').AutoplayPlugin | null}*/
|
72
72
|
autoplay: null,
|
73
|
+
/** @type {typeof import('./plugins/plugin.chapters.js').ChaptersPlugin | null}*/
|
74
|
+
chapters: null,
|
73
75
|
/** @type {typeof import('./plugins/plugin.resume.js').ResumePlugin | null}*/
|
74
76
|
resume: null,
|
75
77
|
/** @type {typeof import('./plugins/plugin.text_selection.js').TextSelectionPlugin | null}*/
|
@@ -80,7 +82,7 @@ BookReader.PLUGINS = {
|
|
80
82
|
|
81
83
|
/**
|
82
84
|
* @param {string} pluginName
|
83
|
-
* @param {
|
85
|
+
* @param {new (...args: any[]) => import('./BookReaderPlugin.js').BookReaderPlugin} plugin
|
84
86
|
*/
|
85
87
|
BookReader.registerPlugin = function(pluginName, plugin) {
|
86
88
|
if (BookReader.PLUGINS[pluginName]) {
|
@@ -116,10 +118,14 @@ BookReader.prototype.setup = function(options) {
|
|
116
118
|
// Store the options used to setup bookreader
|
117
119
|
this.options = options;
|
118
120
|
|
121
|
+
/** @type {import('@/src/BookNavigator/book-navigator.js').BookNavigator} */
|
122
|
+
this.shell;
|
123
|
+
|
119
124
|
// Construct the usual plugins first to get type hints
|
120
125
|
this._plugins = {
|
121
126
|
archiveAnalytics: BookReader.PLUGINS.archiveAnalytics ? new BookReader.PLUGINS.archiveAnalytics(this) : null,
|
122
127
|
autoplay: BookReader.PLUGINS.autoplay ? new BookReader.PLUGINS.autoplay(this) : null,
|
128
|
+
chapters: BookReader.PLUGINS.chapters ? new BookReader.PLUGINS.chapters(this) : null,
|
123
129
|
resume: BookReader.PLUGINS.resume ? new BookReader.PLUGINS.resume(this) : null,
|
124
130
|
textSelection: BookReader.PLUGINS.textSelection ? new BookReader.PLUGINS.textSelection(this) : null,
|
125
131
|
tts: BookReader.PLUGINS.tts ? new BookReader.PLUGINS.tts(this) : null,
|
@@ -1052,16 +1058,20 @@ BookReader.prototype._isIndexDisplayed = function(index) {
|
|
1052
1058
|
BookReader.prototype.jumpToIndex = function(index, pageX, pageY, noAnimate) {
|
1053
1059
|
// Don't jump into specific unviewable page
|
1054
1060
|
const page = this.book.getPage(index);
|
1061
|
+
|
1055
1062
|
if (!page.isViewable && page.unviewablesStart != page.index) {
|
1056
1063
|
// If already in unviewable range, jump to end of that range
|
1057
1064
|
const alreadyInPreview = this._isIndexDisplayed(page.unviewablesStart);
|
1058
|
-
const newIndex = alreadyInPreview ? page.findNext({ combineConsecutiveUnviewables: true })
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1065
|
+
const newIndex = alreadyInPreview ? page.findNext({ combineConsecutiveUnviewables: true })?.index : page.unviewablesStart;
|
1066
|
+
// Rare, but a book can end on an unviewable page, so this could be undefined
|
1067
|
+
if (typeof newIndex !== 'undefined') {
|
1068
|
+
this.jumpToIndex(newIndex, pageX, pageY, noAnimate);
|
1069
|
+
}
|
1070
|
+
} else {
|
1071
|
+
this.trigger(BookReader.eventNames.stop);
|
1063
1072
|
|
1064
|
-
|
1073
|
+
this.activeMode.jumpToIndex(index, pageX, pageY, noAnimate);
|
1074
|
+
}
|
1065
1075
|
};
|
1066
1076
|
|
1067
1077
|
/**
|
@@ -1,85 +1,240 @@
|
|
1
|
-
|
1
|
+
// @ts-check
|
2
2
|
import { css, html, LitElement, nothing } from "lit";
|
3
3
|
import { customElement, property } from 'lit/decorators.js';
|
4
4
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
5
5
|
import { styleMap } from 'lit/directives/style-map.js';
|
6
6
|
import '@internetarchive/icon-toc/icon-toc';
|
7
|
-
|
7
|
+
import { BookReaderPlugin } from "../BookReaderPlugin";
|
8
|
+
import { applyVariables } from "../util/strings.js";
|
9
|
+
/** @typedef {import('@/src/BookReader/BookModel.js').PageIndex} PageIndex */
|
10
|
+
/** @typedef {import('@/src/BookReader/BookModel.js').PageString} PageString */
|
11
|
+
/** @typedef {import('@/src/BookReader/BookModel.js').LeafNum} LeafNum */
|
12
|
+
|
13
|
+
// @ts-ignore
|
14
|
+
const BookReader = /** @type {typeof import('@/src/BookReader.js').default} */(window.BookReader);
|
8
15
|
|
9
16
|
/**
|
10
17
|
* Plugin for chapter markers in BookReader. Fetches from openlibrary.org
|
11
18
|
* Could be forked, or extended to alter behavior
|
12
19
|
*/
|
20
|
+
export class ChaptersPlugin extends BookReaderPlugin {
|
21
|
+
options = {
|
22
|
+
enabled: true,
|
23
|
+
|
24
|
+
/**
|
25
|
+
* The Open Library host to fetch/query Open Library.
|
26
|
+
* @type {import('@/src/util/strings.js').StringWithVars}
|
27
|
+
*/
|
28
|
+
openLibraryHost: 'https://openlibrary.org',
|
29
|
+
|
30
|
+
/**
|
31
|
+
* The Open Library edition to fetch the table of contents from.
|
32
|
+
* E.g. 'OL12345M'
|
33
|
+
*
|
34
|
+
* @type {import('@/src/util/strings.js').StringWithVars}
|
35
|
+
*/
|
36
|
+
openLibraryId: '',
|
37
|
+
|
38
|
+
/**
|
39
|
+
* The Internet Archive identifier to fetch the Open Library table
|
40
|
+
* of contents from.
|
41
|
+
* E.g. 'goody'
|
42
|
+
*
|
43
|
+
* @type {import('@/src/util/strings.js').StringWithVars}
|
44
|
+
*/
|
45
|
+
internetArchiveId: '',
|
46
|
+
|
47
|
+
/**
|
48
|
+
* @deprecated
|
49
|
+
* Old name for openLibraryHost
|
50
|
+
* @type {import('@/src/util/strings.js').StringWithVars}
|
51
|
+
*/
|
52
|
+
olHost: 'https://openlibrary.org',
|
53
|
+
|
54
|
+
/**
|
55
|
+
* @deprecated
|
56
|
+
* Old name for internetArchiveId
|
57
|
+
* @type {import('@/src/util/strings.js').StringWithVars}
|
58
|
+
*/
|
59
|
+
bookId: '{{bookId}}',
|
60
|
+
}
|
61
|
+
|
62
|
+
/** @type {TocEntry[]} */
|
63
|
+
_tocEntries;
|
13
64
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
65
|
+
/** @type {BRChaptersPanel} */
|
66
|
+
_chaptersPanel;
|
67
|
+
|
68
|
+
/** @override */
|
69
|
+
setup(options) {
|
70
|
+
super.setup(options);
|
71
|
+
this.options.internetArchiveId = this.options.internetArchiveId || this.options.bookId;
|
72
|
+
this.options.openLibraryHost = this.options.openLibraryHost || this.options.olHost;
|
73
|
+
}
|
74
|
+
|
75
|
+
/** @override Extend to call Open Library for TOC */
|
76
|
+
async init() {
|
77
|
+
if (!this.options.enabled || this.br.ui === 'embed') {
|
78
|
+
return;
|
26
79
|
}
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
80
|
+
|
81
|
+
let rawTableOfContents = null;
|
82
|
+
// Prefer explicit TOC if specified
|
83
|
+
if (this.br.options.table_of_contents?.length) {
|
84
|
+
rawTableOfContents = this.br.options.table_of_contents;
|
85
|
+
} else {
|
86
|
+
// Otherwise fetch from OL
|
87
|
+
const olEdition = await this.getOpenLibraryRecord();
|
88
|
+
if (olEdition?.table_of_contents?.length) {
|
89
|
+
rawTableOfContents = olEdition.table_of_contents;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
if (rawTableOfContents) {
|
94
|
+
this._tocEntries = rawTableOfContents
|
95
|
+
.map(rawTOCEntry => (Object.assign({}, rawTOCEntry, {
|
96
|
+
pageIndex: (
|
97
|
+
typeof(rawTOCEntry.leaf) == 'number' ? this.br.book.leafNumToIndex(rawTOCEntry.leaf) :
|
98
|
+
rawTOCEntry.pagenum ? this.br.book.getPageIndex(rawTOCEntry.pagenum) :
|
99
|
+
undefined
|
100
|
+
),
|
101
|
+
})));
|
102
|
+
this._render();
|
103
|
+
this.br.bind(BookReader.eventNames.pageChanged, () => this._updateCurrent());
|
41
104
|
}
|
42
105
|
}
|
43
106
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
107
|
+
/**
|
108
|
+
* Update the table of contents based on array of TOC entries.
|
109
|
+
*/
|
110
|
+
_render() {
|
111
|
+
this.br.shell.menuProviders['chapters'] = {
|
112
|
+
id: 'chapters',
|
113
|
+
icon: html`<ia-icon-toc style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-toc>`,
|
114
|
+
label: 'Table of Contents',
|
115
|
+
component: html`<br-chapters-panel
|
116
|
+
.contents="${this._tocEntries}"
|
117
|
+
.jumpToPage="${(pageIndex) => {
|
118
|
+
this._updateCurrent(pageIndex);
|
119
|
+
this.br.jumpToIndex(pageIndex);
|
120
|
+
}}"
|
121
|
+
@connected="${(e) => {
|
122
|
+
this._chaptersPanel = e.target;
|
123
|
+
this._updateCurrent();
|
124
|
+
}}"
|
125
|
+
/>`,
|
126
|
+
};
|
127
|
+
this.br.shell.addMenuShortcut('chapters');
|
128
|
+
this.br.shell.updateMenuContents();
|
129
|
+
this._tocEntries.forEach((tocEntry, i) => this._renderMarker(tocEntry, i));
|
55
130
|
}
|
56
|
-
};
|
57
131
|
|
58
|
-
/**
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
.
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
132
|
+
/**
|
133
|
+
* @param {TocEntry} tocEntry
|
134
|
+
* @param {number} entryIndex
|
135
|
+
*/
|
136
|
+
_renderMarker(tocEntry, entryIndex) {
|
137
|
+
if (tocEntry.pageIndex == undefined) return;
|
138
|
+
|
139
|
+
//creates a string with non-void tocEntry.label and tocEntry.title
|
140
|
+
const chapterStr = [tocEntry.label, tocEntry.title]
|
141
|
+
.filter(x => x)
|
142
|
+
.join(' ') || `Chapter ${entryIndex + 1}`;
|
143
|
+
|
144
|
+
const percentThrough = BookReader.util.cssPercentage(tocEntry.pageIndex, this.br.book.getNumLeafs() - 1);
|
145
|
+
$(`<div></div>`)
|
146
|
+
.append(
|
147
|
+
$('<div />')
|
148
|
+
.text(chapterStr)
|
149
|
+
.append(
|
150
|
+
$('<div class="BRchapterPage" />')
|
151
|
+
.text(this.br.book.getPageName(tocEntry.pageIndex)),
|
152
|
+
),
|
153
|
+
)
|
154
|
+
.addClass('BRchapter')
|
155
|
+
.css({ left: percentThrough })
|
156
|
+
.appendTo(this.br.$('.BRnavline'))
|
157
|
+
.on("mouseenter", event => {
|
158
|
+
// remove hover effect from other markers then turn on just for this
|
159
|
+
const marker = event.currentTarget;
|
160
|
+
const tooltip = marker.querySelector('div');
|
161
|
+
const tooltipOffset = tooltip.getBoundingClientRect();
|
162
|
+
const targetOffset = marker.getBoundingClientRect();
|
163
|
+
const boxSizeAdjust = parseInt(getComputedStyle(tooltip).paddingLeft) * 2;
|
164
|
+
if (tooltipOffset.x - boxSizeAdjust < 0) {
|
165
|
+
tooltip.style.setProperty('transform', `translateX(-${targetOffset.left - boxSizeAdjust}px)`);
|
166
|
+
}
|
167
|
+
this.br.$('.BRsearch,.BRchapter').removeClass('front');
|
168
|
+
$(event.target).addClass('front');
|
169
|
+
})
|
170
|
+
.on("mouseleave", event => $(event.target).removeClass('front'))
|
171
|
+
.on('click', () => {
|
172
|
+
this._updateCurrent(tocEntry.pageIndex);
|
173
|
+
this.br.jumpToIndex(tocEntry.pageIndex);
|
174
|
+
});
|
175
|
+
|
176
|
+
this.br.$('.BRchapter, .BRsearch').each((i, el) => {
|
177
|
+
const $el = $(el);
|
178
|
+
$el
|
179
|
+
.on("mouseenter", () => $el.addClass('front'))
|
180
|
+
.on("mouseleave", () => $el.removeClass('front'));
|
181
|
+
});
|
182
|
+
}
|
183
|
+
|
184
|
+
/**
|
185
|
+
* This makes a call to OL API and calls the given callback function with the
|
186
|
+
* response from the API.
|
187
|
+
*/
|
188
|
+
async getOpenLibraryRecord() {
|
189
|
+
const olHost = applyVariables(this.options.openLibraryHost, this.br.options.vars);
|
190
|
+
|
191
|
+
if (this.options.openLibraryId) {
|
192
|
+
const openLibraryId = applyVariables(this.options.openLibraryId, this.br.options.vars);
|
193
|
+
return await $.ajax({ url: `${olHost}/books/${openLibraryId}.json` });
|
194
|
+
}
|
195
|
+
|
196
|
+
if (this.options.internetArchiveId) {
|
197
|
+
const ocaid = applyVariables(this.options.internetArchiveId, this.br.options.vars);
|
198
|
+
|
199
|
+
// Try looking up by ocaid first, then by source_record
|
200
|
+
const baseQueryUrl = `${olHost}/query.json?type=/type/edition&*=&`;
|
201
|
+
|
202
|
+
let data = await $.ajax({
|
203
|
+
url: baseQueryUrl + new URLSearchParams({ ocaid, limit: '1' }),
|
204
|
+
});
|
205
|
+
|
206
|
+
if (!data || !data.length) {
|
207
|
+
// try source_records
|
208
|
+
data = await $.ajax({
|
209
|
+
url: baseQueryUrl + new URLSearchParams({ source_records: `ia:${ocaid}`, limit: '1' }),
|
210
|
+
});
|
211
|
+
}
|
212
|
+
|
213
|
+
return data?.[0];
|
214
|
+
}
|
215
|
+
|
216
|
+
return null;
|
217
|
+
}
|
218
|
+
|
219
|
+
/**
|
220
|
+
* @private
|
221
|
+
* Highlights the current chapter based on current page
|
222
|
+
* @param {PageIndex} curIndex
|
223
|
+
*/
|
224
|
+
_updateCurrent(
|
225
|
+
curIndex = (this.br.mode == 2 ? Math.max(...this.br.displayedIndices) : this.br.firstIndex),
|
226
|
+
) {
|
227
|
+
const tocEntriesIndexed = this._tocEntries.filter((el) => el.pageIndex != undefined).reverse();
|
228
|
+
const currChapter = tocEntriesIndexed[
|
229
|
+
// subtract one so that 2up shows the right label
|
230
|
+
tocEntriesIndexed.findIndex((chapter) => chapter.pageIndex <= curIndex)
|
231
|
+
];
|
232
|
+
if (this._chaptersPanel) {
|
233
|
+
this._chaptersPanel.currentChapter = currChapter;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
}
|
237
|
+
BookReader?.registerPlugin('chapters', ChaptersPlugin);
|
83
238
|
|
84
239
|
/**
|
85
240
|
* @typedef {Object} TocEntry
|
@@ -99,98 +254,6 @@ BookReader.prototype._chaptersRender = function() {
|
|
99
254
|
* }
|
100
255
|
*/
|
101
256
|
|
102
|
-
/**
|
103
|
-
* @param {TocEntry} tocEntry
|
104
|
-
* @param {number} entryIndex
|
105
|
-
*/
|
106
|
-
BookReader.prototype._chaptersRenderMarker = function(tocEntry, entryIndex) {
|
107
|
-
if (tocEntry.pageIndex == undefined) return;
|
108
|
-
|
109
|
-
//creates a string with non-void tocEntry.label and tocEntry.title
|
110
|
-
const chapterStr = [tocEntry.label, tocEntry.title]
|
111
|
-
.filter(x => x)
|
112
|
-
.join(' ') || `Chapter ${entryIndex + 1}`;
|
113
|
-
|
114
|
-
const percentThrough = BookReader.util.cssPercentage(tocEntry.pageIndex, this.book.getNumLeafs() - 1);
|
115
|
-
$(`<div></div>`)
|
116
|
-
.append(
|
117
|
-
$('<div />')
|
118
|
-
.text(chapterStr)
|
119
|
-
.append(
|
120
|
-
$('<div class="BRchapterPage" />')
|
121
|
-
.text(this.book.getPageName(tocEntry.pageIndex)),
|
122
|
-
),
|
123
|
-
)
|
124
|
-
.addClass('BRchapter')
|
125
|
-
.css({ left: percentThrough })
|
126
|
-
.appendTo(this.$('.BRnavline'))
|
127
|
-
.on("mouseenter", event => {
|
128
|
-
// remove hover effect from other markers then turn on just for this
|
129
|
-
const marker = event.currentTarget;
|
130
|
-
const tooltip = marker.querySelector('div');
|
131
|
-
const tooltipOffset = tooltip.getBoundingClientRect();
|
132
|
-
const targetOffset = marker.getBoundingClientRect();
|
133
|
-
const boxSizeAdjust = parseInt(getComputedStyle(tooltip).paddingLeft) * 2;
|
134
|
-
if (tooltipOffset.x - boxSizeAdjust < 0) {
|
135
|
-
tooltip.style.setProperty('transform', `translateX(-${targetOffset.left - boxSizeAdjust}px)`);
|
136
|
-
}
|
137
|
-
this.$('.BRsearch,.BRchapter').removeClass('front');
|
138
|
-
$(event.target).addClass('front');
|
139
|
-
})
|
140
|
-
.on("mouseleave", event => $(event.target).removeClass('front'))
|
141
|
-
.on('click', () => {
|
142
|
-
this._chaptersUpdateCurrent(tocEntry.pageIndex);
|
143
|
-
this.jumpToIndex(tocEntry.pageIndex);
|
144
|
-
});
|
145
|
-
|
146
|
-
this.$('.BRchapter, .BRsearch').each((i, el) => {
|
147
|
-
const $el = $(el);
|
148
|
-
$el
|
149
|
-
.on("mouseenter", () => $el.addClass('front'))
|
150
|
-
.on("mouseleave", () => $el.removeClass('front'));
|
151
|
-
});
|
152
|
-
};
|
153
|
-
|
154
|
-
/**
|
155
|
-
* This makes a call to OL API and calls the given callback function with the
|
156
|
-
* response from the API.
|
157
|
-
*
|
158
|
-
* @param {string} olHost
|
159
|
-
* @param {string} ocaid
|
160
|
-
*/
|
161
|
-
BookReader.prototype.getOpenLibraryRecord = async function (olHost, ocaid) {
|
162
|
-
// Try looking up by ocaid first, then by source_record
|
163
|
-
const baseURL = `${olHost}/query.json?type=/type/edition&*=`;
|
164
|
-
const fetchUrlByBookId = `${baseURL}&ocaid=${ocaid}`;
|
165
|
-
|
166
|
-
let data = await $.ajax({ url: fetchUrlByBookId });
|
167
|
-
|
168
|
-
if (!data || !data.length) {
|
169
|
-
// try sourceid
|
170
|
-
data = await $.ajax({ url: `${baseURL}&source_records=ia:${ocaid}` });
|
171
|
-
}
|
172
|
-
|
173
|
-
return data?.[0];
|
174
|
-
};
|
175
|
-
|
176
|
-
/**
|
177
|
-
* @private
|
178
|
-
* Highlights the current chapter based on current page
|
179
|
-
* @param {PageIndex} curIndex
|
180
|
-
*/
|
181
|
-
BookReader.prototype._chaptersUpdateCurrent = function(
|
182
|
-
curIndex = (this.mode == 2 ? Math.max(...this.displayedIndices) : this.firstIndex),
|
183
|
-
) {
|
184
|
-
const tocEntriesIndexed = this._tocEntries.filter((el) => el.pageIndex != undefined).reverse();
|
185
|
-
const currChapter = tocEntriesIndexed[
|
186
|
-
// subtract one so that 2up shows the right label
|
187
|
-
tocEntriesIndexed.findIndex((chapter) => chapter.pageIndex <= curIndex)
|
188
|
-
];
|
189
|
-
if (this._chaptersPanel) {
|
190
|
-
this._chaptersPanel.currentChapter = currChapter;
|
191
|
-
}
|
192
|
-
};
|
193
|
-
|
194
257
|
@customElement('br-chapters-panel')
|
195
258
|
export class BRChaptersPanel extends LitElement {
|
196
259
|
/** @type {TocEntry[]} */
|
@@ -82,6 +82,22 @@ export class TextSelectionPlugin extends BookReaderPlugin {
|
|
82
82
|
$(window.getSelection().anchorNode).closest('.BRpagecontainer').addClass('BRpagecontainer--hasSelection');
|
83
83
|
}
|
84
84
|
}).attach();
|
85
|
+
|
86
|
+
if (this.br.protected) {
|
87
|
+
// Prevent right clicking when selected text
|
88
|
+
$(document.body).on('contextmenu dragstart copy', (e) => {
|
89
|
+
const selection = document.getSelection();
|
90
|
+
if (selection?.toString()) {
|
91
|
+
const intersectsTextLayer = $('.BRtextLayer')
|
92
|
+
.toArray()
|
93
|
+
.some(el => selection.containsNode(el, true));
|
94
|
+
if (intersectsTextLayer) {
|
95
|
+
e.preventDefault();
|
96
|
+
return false;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
});
|
100
|
+
}
|
85
101
|
}
|
86
102
|
|
87
103
|
/**
|