Skip to content

Commit 84b4f7e

Browse files
authored
Merge pull request #1279 from cdrini/feature/ia-toc
Make chapters plugin support table_of_contents in options
2 parents 2c562d7 + a1fe11d commit 84b4f7e

4 files changed

Lines changed: 94 additions & 25 deletions

File tree

src/BookReader/options.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ export const DEFAULT_OPTIONS = {
180180
*/
181181
data: [],
182182

183+
/** @type {import('../plugins/plugin.chapters.js').TocEntry[]} */
184+
table_of_contents: null,
185+
183186
/** Advanced methods for page rendering */
184187
/** @type {() => number} */
185188
getNumLeafs: null,

src/css/_BRsearch.scss

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
display: none;
1010
position: absolute;
1111
bottom: calc(100% + 5px);
12-
left: 50%;
13-
transform: translateX(-50%);
12+
left: -14px;
1413
width: 350px;
1514
max-width: 100vw;
1615
padding: 12px 14px;
@@ -34,9 +33,7 @@
3433
position: absolute;
3534
content: "";
3635
bottom: -9px;
37-
left: 50%;
38-
margin-left: -1px;
39-
transform: translateX(-50%);
36+
left: 0;
4037
width: 30px;
4138
height: 10px;
4239
clip-path: polygon(0 0, 100% 0, 50% 100%);
@@ -131,9 +128,10 @@
131128
main {
132129
@include ellipsis-lines(4);
133130
margin-bottom: 6px;
131+
&:before { content: ""; }
132+
&:after { content: ""; }
134133
}
135134
footer {
136-
text-align: center;
137135
font-size: 0.85em;
138136
opacity: .8;
139137
}

src/plugins/plugin.chapters.js

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,28 @@ BookReader.prototype.init = (function(super_) {
2828
})(BookReader.prototype.init);
2929

3030
BookReader.prototype._chapterInit = async function() {
31-
const olEdition = await this.getOpenLibraryRecord(this.options.olHost, this.options.bookId);
32-
if (olEdition?.table_of_contents?.length) {
33-
this._tocEntries = olEdition.table_of_contents.map(rawTOCEntry => (
34-
Object.assign({}, rawTOCEntry, {pageIndex: this.book.getPageIndex(rawTOCEntry.pagenum)})
35-
));
31+
let rawTableOfContents = null;
32+
// Prefer IA TOC for now, until we update the second half to check for
33+
// `openlibrary_edition` on the IA metadata instead of making a bunch of
34+
// requests to OL.
35+
if (this.options.table_of_contents?.length) {
36+
rawTableOfContents = this.options.table_of_contents;
37+
} else {
38+
const olEdition = await this.getOpenLibraryRecord(this.options.olHost, this.options.bookId);
39+
if (olEdition?.table_of_contents?.length) {
40+
rawTableOfContents = olEdition.table_of_contents;
41+
}
42+
}
43+
44+
if (rawTableOfContents) {
45+
this._tocEntries = rawTableOfContents
46+
.map(rawTOCEntry => (Object.assign({}, rawTOCEntry, {
47+
pageIndex: (
48+
typeof(rawTOCEntry.leaf) == 'number' ? this.book.leafNumToIndex(rawTOCEntry.leaf) :
49+
rawTOCEntry.pagenum ? this.book.getPageIndex(rawTOCEntry.pagenum) :
50+
undefined
51+
),
52+
})));
3653
this._chaptersRender(this._tocEntries);
3754
this.bind(BookReader.eventNames.pageChanged, () => this._chaptersUpdateCurrent());
3855
}
@@ -60,19 +77,18 @@ BookReader.prototype._chaptersRender = function() {
6077
/>`,
6178
};
6279
shell.updateMenuContents();
63-
for (const tocEntry of this._tocEntries) {
64-
this._chaptersRenderMarker(tocEntry);
65-
}
80+
this._tocEntries.forEach((tocEntry, i) => this._chaptersRenderMarker(tocEntry, i));
6681
};
6782

6883
/**
6984
* @typedef {Object} TocEntry
7085
* Table of contents entry as defined by Open Library, with some extra values for internal use
71-
* @property {string} pagenum
72-
* @property {number} level
73-
* @property {string} label
74-
* @property {string} title
75-
* @property {number} pageIndex - Added
86+
* @property {number} [level]
87+
* @property {string} [label]
88+
* @property {string} [title]
89+
* @property {PageString} [pagenum]
90+
* @property {LeafNum} [leaf]
91+
* @property {number} [pageIndex] - Added
7692
*
7793
* @example {
7894
* "pagenum": "17",
@@ -84,21 +100,25 @@ BookReader.prototype._chaptersRender = function() {
84100

85101
/**
86102
* @param {TocEntry} tocEntry
103+
* @param {number} entryIndex
87104
*/
88-
BookReader.prototype._chaptersRenderMarker = function(tocEntry) {
105+
BookReader.prototype._chaptersRenderMarker = function(tocEntry, entryIndex) {
89106
if (tocEntry.pageIndex == undefined) return;
90107

91108
//creates a string with non-void tocEntry.label and tocEntry.title
92109
const chapterStr = [tocEntry.label, tocEntry.title]
93110
.filter(x => x)
94-
.join(' ');
111+
.join(' ') || `Chapter ${entryIndex + 1}`;
95112

96113
const percentThrough = BookReader.util.cssPercentage(tocEntry.pageIndex, this.book.getNumLeafs() - 1);
97114
$(`<div></div>`)
98115
.append(
99116
$('<div />')
100117
.text(chapterStr)
101-
.append($('<div class="BRchapterPage" />').text(`Page ${tocEntry.pagenum}`))
118+
.append(
119+
$('<div class="BRchapterPage" />')
120+
.text(this.book.getPageName(tocEntry.pageIndex))
121+
)
102122
)
103123
.addClass('BRchapter')
104124
.css({ left: percentThrough })

tests/jest/plugins/plugin.chapters.test.js

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import BookReader from "@/src/BookReader.js";
44
import "@/src/plugins/plugin.chapters.js";
55
import { BookModel } from "@/src/BookReader/BookModel";
66
import { deepCopy } from "../utils";
7+
/** @typedef {import('@/src/plugins/plugin.chapters').TocEntry} TocEntry */
78

9+
/** @type {TocEntry[]} */
810
const SAMPLE_TOC = [{
911
"pagenum": "3",
1012
"level": 1,
@@ -34,23 +36,34 @@ const SAMPLE_TOC = [{
3436
"pageIndex": 40,
3537
}];
3638

39+
/** @type {TocEntry[]} */
3740
const SAMPLE_TOC_UNDEF = [
3841
{
39-
"pagenum": "undefined",
4042
"level": 1,
4143
"label": "CHAPTER I",
4244
"type": { "key": "/type/toc_item" },
4345
"title": "THE COUNTRY AND THE MISSION 1",
4446
},
4547
{
46-
"pagenum": "undefined",
4748
"level": 1,
4849
"label": "CHAPTER II",
4950
"type": { "key": "/type/toc_item" },
5051
"title": "THE COUNTRY AND THE MISSION 2",
5152
},
5253
];
5354

55+
/** @type {TocEntry[]} */
56+
const SAMPLE_TOC_OPTION = [
57+
{
58+
"level": 1,
59+
"title": "THE COUNTRY AND THE MISSION 1",
60+
},
61+
{
62+
"level": 1,
63+
"title": "THE COUNTRY AND THE MISSION 2",
64+
},
65+
];
66+
5467
afterEach(() => {
5568
sinon.restore();
5669
});
@@ -92,14 +105,49 @@ describe("BRChaptersPlugin", () => {
92105
},
93106
getOpenLibraryRecord: async () => ({
94107
"title": "The Adventures of Sherlock Holmes",
95-
"table_of_contents": deepCopy(SAMPLE_TOC_UNDEF),
108+
"table_of_contents": deepCopy(SAMPLE_TOC_OPTION),
96109
"ocaid": "adventureofsherl0000unse",
97110
}),
98111
_chaptersRender: sinon.stub(),
99112
};
100113
await BookReader.prototype._chapterInit.call(fakeBR);
101114
expect(fakeBR._chaptersRender.callCount).toBe(1);
102115
});
116+
117+
test("does not fetch open library record if table of contents in options", async () => {
118+
const fakeBR = {
119+
options: {
120+
table_of_contents: deepCopy(SAMPLE_TOC_UNDEF),
121+
},
122+
bind: sinon.stub(),
123+
getOpenLibraryRecord: sinon.stub(),
124+
_chaptersRender: sinon.stub(),
125+
};
126+
await BookReader.prototype._chapterInit.call(fakeBR);
127+
expect(fakeBR.getOpenLibraryRecord.callCount).toBe(0);
128+
expect(fakeBR._chaptersRender.callCount).toBe(1);
129+
});
130+
131+
test("converts leafs and pagenums to page index", async () => {
132+
const table_of_contents = deepCopy(SAMPLE_TOC_UNDEF);
133+
table_of_contents[0].leaf = 0;
134+
table_of_contents[1].pagenum = '17';
135+
const fakeBR = {
136+
options: {
137+
table_of_contents,
138+
},
139+
bind: sinon.stub(),
140+
book: {
141+
leafNumToIndex: (leaf) => leaf + 1,
142+
getPageIndex: (str) => parseFloat(str),
143+
},
144+
_chaptersRender: sinon.stub(),
145+
};
146+
await BookReader.prototype._chapterInit.call(fakeBR);
147+
expect(fakeBR._chaptersRender.callCount).toBe(1);
148+
expect(fakeBR._tocEntries[0].pageIndex).toBe(1);
149+
expect(fakeBR._tocEntries[1].pageIndex).toBe(17);
150+
});
103151
});
104152

105153
describe('_chaptersRender', () => {

0 commit comments

Comments
 (0)