@internetarchive/bookreader 5.0.0-22 → 5.0.0-24-sortingstate-1
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/.nvmrc +1 -0
- package/BookReader/BookReader.js +32148 -2
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/bookreader-component-bundle.js +10917 -941
- package/BookReader/bookreader-component-bundle.js.map +1 -1
- package/BookReader/icons/1up.svg +12 -1
- package/BookReader/icons/2up.svg +15 -1
- package/BookReader/icons/advance.svg +26 -3
- package/BookReader/icons/chevron-right.svg +1 -1
- package/BookReader/icons/close-circle-dark.svg +1 -1
- package/BookReader/icons/close-circle.svg +1 -1
- package/BookReader/icons/fullscreen.svg +17 -1
- package/BookReader/icons/fullscreen_exit.svg +17 -1
- package/BookReader/icons/hamburger.svg +15 -1
- package/BookReader/icons/left-arrow.svg +12 -1
- package/BookReader/icons/magnify-minus.svg +16 -1
- package/BookReader/icons/magnify-plus.svg +17 -1
- package/BookReader/icons/magnify.svg +15 -1
- package/BookReader/icons/pause.svg +23 -1
- package/BookReader/icons/play.svg +22 -1
- package/BookReader/icons/playback-speed.svg +34 -1
- package/BookReader/icons/read-aloud.svg +22 -1
- package/BookReader/icons/review.svg +22 -3
- package/BookReader/icons/thumbnails.svg +17 -1
- package/BookReader/icons/voice.svg +1 -1
- package/BookReader/icons/volume-full.svg +22 -1
- package/BookReader/images/BRicons.svg +94 -5
- package/BookReader/images/books_graphic.svg +177 -1
- package/BookReader/images/icon_book.svg +12 -1
- package/BookReader/images/icon_bookmark.svg +12 -1
- package/BookReader/images/icon_gear.svg +14 -1
- package/BookReader/images/icon_hamburger.svg +20 -1
- package/BookReader/images/icon_home.svg +21 -1
- package/BookReader/images/icon_info.svg +11 -1
- package/BookReader/images/icon_one_page.svg +8 -1
- package/BookReader/images/icon_pause.svg +1 -1
- package/BookReader/images/icon_play.svg +1 -1
- package/BookReader/images/icon_playback-rate.svg +15 -1
- package/BookReader/images/icon_search_button.svg +8 -1
- package/BookReader/images/icon_share.svg +9 -1
- package/BookReader/images/icon_skip-ahead.svg +6 -1
- package/BookReader/images/icon_skip-back.svg +13 -2
- package/BookReader/images/icon_speaker.svg +18 -1
- package/BookReader/images/icon_speaker_open.svg +10 -1
- package/BookReader/images/icon_thumbnails.svg +12 -1
- package/BookReader/images/icon_toc.svg +5 -1
- package/BookReader/images/icon_two_pages.svg +9 -1
- package/BookReader/images/marker_chap-off.svg +11 -1
- package/BookReader/images/marker_chap-on.svg +11 -1
- package/BookReader/images/marker_srch-on.svg +11 -1
- package/BookReader/jquery-1.10.1.js +108 -2
- package/BookReader/plugins/plugin.archive_analytics.js +170 -1
- package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
- package/BookReader/plugins/plugin.autoplay.js +163 -1
- package/BookReader/plugins/plugin.autoplay.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +333 -1
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.iframe.js +72 -1
- package/BookReader/plugins/plugin.iframe.js.map +1 -1
- package/BookReader/plugins/plugin.mobile_nav.js +332 -1
- package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
- package/BookReader/plugins/plugin.resume.js +241 -1
- package/BookReader/plugins/plugin.resume.js.map +1 -1
- package/BookReader/plugins/plugin.search.js +1263 -1
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +839 -1
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +9114 -2
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +750 -1
- package/BookReader/plugins/plugin.url.js.map +1 -1
- package/BookReader/plugins/plugin.vendor-fullscreen.js +326 -1
- package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
- package/BookReader/webcomponents-bundle.js +411 -2
- package/BookReader/webcomponents-bundle.js.map +1 -1
- package/CHANGELOG.md +9 -0
- package/package.json +2 -6
- package/src/BookNavigator/bookmarks/bookmarks-provider.js +1 -0
- package/src/BookNavigator/bookmarks/ia-bookmarks.js +3 -0
- package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +1 -1
- package/src/BookNavigator/volumes/volumes-provider.js +40 -9
- package/src/BookReader/Mode1Up.js +10 -2
- package/src/BookReader/ModeThumb.js +13 -6
- package/src/BookReader/options.js +4 -0
- package/src/BookReader.js +8 -22
- package/src/ItemNavigator/ItemNavigator.js +1 -0
- package/src/plugins/plugin.url.js +209 -2
- package/tests/{BookReader → jest/BookReader}/BookModel.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/BookReaderPublicFunctions.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/Mode1UpLit.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/ModeSmoothZoom.test.js +0 -0
- package/tests/jest/BookReader/ModeThumb.test.js +71 -0
- package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/utils/HTMLDimensionsCacher.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +0 -0
- package/tests/{BookReader → jest/BookReader}/utils.test.js +0 -0
- package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +0 -0
- package/tests/{BookReader.test.js → jest/BookReader.test.js} +0 -0
- package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +0 -0
- package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +0 -0
- package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +0 -0
- package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +0 -0
- package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +0 -0
- package/tests/{plugins → jest/plugins}/plugin.resume.test.js +0 -0
- package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +0 -0
- package/tests/jest/plugins/plugin.url.test.js +306 -0
- package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +0 -0
- package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +0 -0
- package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +0 -0
- package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +0 -0
- package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +0 -0
- package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +0 -0
- package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +0 -0
- package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +0 -0
- package/tests/{plugins → jest/plugins}/tts/utils.test.js +0 -0
- package/tests/{util → jest/util}/browserSniffing.test.js +0 -0
- package/tests/{util → jest/util}/docCookies.test.js +0 -0
- package/tests/{util → jest/util}/strings.test.js +0 -0
- package/tests/{utils.js → jest/utils.js} +0 -0
- package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +6 -6
- package/tests/plugins/plugin.url.test.js +0 -147
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# 5.0.0-24
|
2
|
+
Fix: book-nav side panel zoom out @mc2
|
3
|
+
Dev: refactor zoom code @mc2
|
4
|
+
|
5
|
+
# 5.0.0-23
|
6
|
+
Fix: Darken scrollbars in Safari @pezvi
|
7
|
+
Fix: Bookmarks service calls when reader is logged in @mc2
|
8
|
+
Dev: Move jest tests into separate directory @cdrini
|
9
|
+
|
1
10
|
# 5.0.0-22
|
2
11
|
- Dev: remove deprecated embed nav view, use standard default @iisa
|
3
12
|
|
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-24-sortingstate-1",
|
4
4
|
"description": "The Internet Archive BookReader.",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
@@ -94,11 +94,7 @@
|
|
94
94
|
],
|
95
95
|
"roots": [
|
96
96
|
"<rootDir>/src/",
|
97
|
-
"<rootDir>/tests/"
|
98
|
-
],
|
99
|
-
"testPathIgnorePatterns": [
|
100
|
-
"<rootDir>/tests/e2e/",
|
101
|
-
"<rootDir>/tests/karma/"
|
97
|
+
"<rootDir>/tests/jest/"
|
102
98
|
],
|
103
99
|
"coverageDirectory": "<rootDir>/coverage-jest"
|
104
100
|
},
|
@@ -15,6 +15,7 @@ export default class BookmarksProvider {
|
|
15
15
|
this.component = document.createElement('ia-bookmarks');
|
16
16
|
this.component.bookreader = bookreader;
|
17
17
|
this.component.options = boundOptions;
|
18
|
+
this.component.displayMode = this.component.options.displayMode;
|
18
19
|
|
19
20
|
this.bindEvents();
|
20
21
|
|
@@ -121,6 +121,9 @@ class IABookmarks extends LitElement {
|
|
121
121
|
|
122
122
|
setup() {
|
123
123
|
this.api.identifier = this.bookreader.bookId;
|
124
|
+
if (this.displayMode === 'login') {
|
125
|
+
return;
|
126
|
+
}
|
124
127
|
this.fetchBookmarks()
|
125
128
|
.then(() => this.initializeBookmarks())
|
126
129
|
.catch((err) => this.displayMode = 'login');
|
@@ -7,8 +7,16 @@ import volumesIcon from '../assets/icon_volumes.js';
|
|
7
7
|
|
8
8
|
import './volumes.js';
|
9
9
|
|
10
|
+
const sortType = {
|
11
|
+
title_asc: 'title_asc',
|
12
|
+
title_desc: 'title_desc',
|
13
|
+
default: 'default'
|
14
|
+
};
|
10
15
|
export default class VolumesProvider {
|
11
16
|
|
17
|
+
/**
|
18
|
+
* @param {import('../../BookReader').default} bookreader
|
19
|
+
*/
|
12
20
|
constructor(baseHost, bookreader, optionChange) {
|
13
21
|
this.optionChange = optionChange;
|
14
22
|
this.component = document.createElement("viewable-files");
|
@@ -17,6 +25,9 @@ export default class VolumesProvider {
|
|
17
25
|
this.viewableFiles = Object.keys(files).map(item => files[item]);
|
18
26
|
this.volumeCount = Object.keys(files).length;
|
19
27
|
|
28
|
+
/** @type {import('../../BookReader').default} */
|
29
|
+
this.bookreader = bookreader;
|
30
|
+
|
20
31
|
this.component.subPrefix = bookreader.options.subPrefix || "";
|
21
32
|
this.component.hostUrl = baseHost;
|
22
33
|
this.component.viewableFiles = this.viewableFiles;
|
@@ -25,20 +36,28 @@ export default class VolumesProvider {
|
|
25
36
|
this.label = `Viewable files (${this.volumeCount})`;
|
26
37
|
this.icon = html`${volumesIcon}`;
|
27
38
|
|
28
|
-
|
29
|
-
this.
|
39
|
+
// get sort state from query param
|
40
|
+
this.bookreader.urlPlugin.pullFromAddressBar(location.pathname + location.search);
|
41
|
+
const urlSortValue = this.bookreader.urlPlugin.getUrlParam('sort');
|
42
|
+
console.log('urlSortValue: ', urlSortValue);
|
43
|
+
if (urlSortValue === sortType.title_asc || urlSortValue === sortType.title_desc) {
|
44
|
+
this.sortOrderBy = urlSortValue;
|
45
|
+
} else {
|
46
|
+
this.sortOrderBy = sortType.default;
|
47
|
+
}
|
48
|
+
this.sortVolumes(this.sortOrderBy);
|
30
49
|
}
|
31
50
|
|
32
51
|
get sortButton() {
|
33
52
|
const sortIcons = {
|
34
|
-
|
53
|
+
default: html`
|
35
54
|
<button class="sort-by neutral-icon" aria-label="Sort volumes in initial order" @click=${() => this.sortVolumes("title_asc")}>${sortNeutralIcon}</button>
|
36
55
|
`,
|
37
56
|
title_asc: html`
|
38
57
|
<button class="sort-by asc-icon" aria-label="Sort volumes in ascending order" @click=${() => this.sortVolumes("title_desc")}>${sortAscIcon}</button>
|
39
58
|
`,
|
40
59
|
title_desc: html`
|
41
|
-
<button class="sort-by desc-icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes("
|
60
|
+
<button class="sort-by desc-icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes("default")}>${sortDescIcon}</button>
|
42
61
|
`,
|
43
62
|
};
|
44
63
|
|
@@ -46,28 +65,40 @@ export default class VolumesProvider {
|
|
46
65
|
}
|
47
66
|
|
48
67
|
/**
|
49
|
-
* @param {'
|
68
|
+
* @param {'default' | 'title_asc' | 'title_desc'} sortByType
|
50
69
|
*/
|
51
70
|
sortVolumes(sortByType) {
|
52
71
|
let sortedFiles = [];
|
53
72
|
|
54
73
|
const files = this.viewableFiles;
|
55
74
|
sortedFiles = files.sort((a, b) => {
|
56
|
-
if (sortByType ===
|
57
|
-
else if (sortByType ===
|
58
|
-
else return b.
|
75
|
+
if (sortByType === sortType.title_asc) return a.title.localeCompare(b.title);
|
76
|
+
else if (sortByType === sortType.title_desc) return b.title.localeCompare(a.title);
|
77
|
+
else return a.orig_sort - b.orig_sort;
|
59
78
|
});
|
60
79
|
|
61
80
|
this.sortOrderBy = sortByType;
|
62
81
|
this.component.viewableFiles = [...sortedFiles];
|
63
82
|
this.actionButton = this.sortButton;
|
83
|
+
|
84
|
+
if (this.sortOrderBy !== sortType.default) {
|
85
|
+
this.bookreader.urlPlugin.setUrlParam('sort', sortByType);
|
86
|
+
} else {
|
87
|
+
this.bookreader.urlPlugin.removeUrlParam('sort');
|
88
|
+
}
|
89
|
+
|
90
|
+
const urlSchema = this.bookreader.urlPlugin.urlSchema;
|
91
|
+
const urlState = this.bookreader.urlPlugin.urlState;
|
92
|
+
this.bookreader.urlPlugin.urlStateToUrlString(urlSchema, urlState);
|
93
|
+
this.bookreader.urlPlugin.pushToAddressBar();
|
94
|
+
|
64
95
|
this.optionChange(this.bookreader);
|
65
96
|
|
66
97
|
this.multipleFilesClicked(sortByType);
|
67
98
|
}
|
68
99
|
|
69
100
|
/**
|
70
|
-
* @param {'
|
101
|
+
* @param {'default' | 'title_asc' | 'title_desc'} orderBy
|
71
102
|
*/
|
72
103
|
multipleFilesClicked(orderBy) {
|
73
104
|
if (!window.archive_analytics) {
|
@@ -79,8 +79,16 @@ export class Mode1Up {
|
|
79
79
|
* @param {'in' | 'out'} direction
|
80
80
|
*/
|
81
81
|
zoom(direction) {
|
82
|
-
|
83
|
-
|
82
|
+
switch (direction) {
|
83
|
+
case 'in':
|
84
|
+
this.mode1UpLit.zoomIn();
|
85
|
+
break;
|
86
|
+
case 'out':
|
87
|
+
this.mode1UpLit.zoomOut();
|
88
|
+
break;
|
89
|
+
default:
|
90
|
+
console.error(`Unsupported direction: ${direction}`);
|
91
|
+
}
|
84
92
|
}
|
85
93
|
|
86
94
|
/**
|
@@ -235,20 +235,27 @@ export class ModeThumb {
|
|
235
235
|
}
|
236
236
|
|
237
237
|
/**
|
238
|
-
* @param {
|
238
|
+
* @param {'in' | 'out'} direction
|
239
239
|
*/
|
240
240
|
zoom(direction) {
|
241
241
|
const oldColumns = this.br.thumbColumns;
|
242
242
|
switch (direction) {
|
243
|
-
case
|
244
|
-
this.br.thumbColumns += 1;
|
245
|
-
break;
|
246
|
-
case 1:
|
243
|
+
case 'in':
|
247
244
|
this.br.thumbColumns -= 1;
|
248
245
|
break;
|
246
|
+
case 'out':
|
247
|
+
this.br.thumbColumns += 1;
|
248
|
+
break;
|
249
|
+
default:
|
250
|
+
console.error(`Unsupported direction: ${direction}`);
|
249
251
|
}
|
250
252
|
|
251
|
-
|
253
|
+
// Limit zoom in/out columns
|
254
|
+
this.br.thumbColumns = clamp(
|
255
|
+
this.br.thumbColumns,
|
256
|
+
this.br.options.thumbMinZoomColumns,
|
257
|
+
this.br.options.thumbMaxZoomColumns
|
258
|
+
);
|
252
259
|
|
253
260
|
if (this.br.thumbColumns != oldColumns) {
|
254
261
|
this.br.displayedRows = []; /* force a gallery redraw */
|
@@ -25,6 +25,10 @@ export const DEFAULT_OPTIONS = {
|
|
25
25
|
thumbMaxLoading: 4,
|
26
26
|
/** spacing between thumbnails */
|
27
27
|
thumbPadding: 10,
|
28
|
+
/** min zoom in columns */
|
29
|
+
thumbMinZoomColumns: 2,
|
30
|
+
/** max zoom out columns */
|
31
|
+
thumbMaxZoomColumns: 8,
|
28
32
|
|
29
33
|
/** @type {number | 'fast' | 'slow'} speed for flip animation */
|
30
34
|
flipSpeed: 'fast',
|
package/src/BookReader.js
CHANGED
@@ -63,6 +63,10 @@ if (location.toString().indexOf('_debugShowConsole=true') != -1) {
|
|
63
63
|
*/
|
64
64
|
export default function BookReader(overrides = {}) {
|
65
65
|
const options = jQuery.extend(true, {}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
|
66
|
+
|
67
|
+
/** @type {import('./plugins/plugin.url').UrlPlugin | null} */
|
68
|
+
this.urlPlugin = null;
|
69
|
+
|
66
70
|
this.setup(options);
|
67
71
|
}
|
68
72
|
|
@@ -829,29 +833,11 @@ BookReader.prototype.drawLeafsThrottled = utils.throttle(
|
|
829
833
|
* @param {number} direction Pass 1 to zoom in, anything else to zoom out
|
830
834
|
*/
|
831
835
|
BookReader.prototype.zoom = function(direction) {
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
this.zoom1up('in');
|
837
|
-
} else {
|
838
|
-
this.zoom1up('out');
|
839
|
-
}
|
840
|
-
break;
|
841
|
-
case this.constMode2up:
|
842
|
-
if (direction == 1) {
|
843
|
-
// XXX other cases
|
844
|
-
this.zoom2up('in');
|
845
|
-
} else {
|
846
|
-
this.zoom2up('out');
|
847
|
-
}
|
848
|
-
break;
|
849
|
-
case this.constModeThumb:
|
850
|
-
// XXX update zoomThumb for named directions
|
851
|
-
this.zoomThumb(direction);
|
852
|
-
break;
|
836
|
+
if (direction == 1) {
|
837
|
+
this.activeMode.zoom('in');
|
838
|
+
} else {
|
839
|
+
this.activeMode.zoom('out');
|
853
840
|
}
|
854
|
-
|
855
841
|
this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
|
856
842
|
return;
|
857
843
|
};
|
@@ -50,7 +50,7 @@ BookReader.prototype.init = (function(super_) {
|
|
50
50
|
this.bind(BookReader.eventNames.PostInit, () => {
|
51
51
|
const { updateWindowTitle, urlMode } = this.options;
|
52
52
|
if (updateWindowTitle) {
|
53
|
-
document.title = this.shortTitle(50);
|
53
|
+
document.title = this.shortTitle(this.bookTitle, 50);
|
54
54
|
}
|
55
55
|
if (urlMode === 'hash') {
|
56
56
|
this.urlStartLocationPolling();
|
@@ -86,7 +86,7 @@ BookReader.prototype.urlStartLocationPolling = function() {
|
|
86
86
|
this.oldLocationHash = this.urlReadFragment();
|
87
87
|
|
88
88
|
if (this.locationPollId) {
|
89
|
-
clearInterval(this.
|
89
|
+
clearInterval(this.locationPollId);
|
90
90
|
this.locationPollId = null;
|
91
91
|
}
|
92
92
|
|
@@ -196,3 +196,210 @@ BookReader.prototype.urlReadFragment = function() {
|
|
196
196
|
BookReader.prototype.urlReadHashFragment = function() {
|
197
197
|
return window.location.hash.substr(1);
|
198
198
|
};
|
199
|
+
export class UrlPlugin {
|
200
|
+
constructor(options = {}) {
|
201
|
+
this.bookReaderOptions = options;
|
202
|
+
|
203
|
+
this.urlSchema = [
|
204
|
+
{ name: 'page', position: 'path', default: 'n0' },
|
205
|
+
{ name: 'mode', position: 'path', default: '2up' },
|
206
|
+
{ name: 'search', position: 'path', deprecated_for: 'q' },
|
207
|
+
{ name: 'q', position: 'query_param' },
|
208
|
+
{ name: 'sort', position: 'query_param' },
|
209
|
+
{ name: 'view', position: 'query_param' },
|
210
|
+
{ name: 'admin', position: 'query_param' },
|
211
|
+
];
|
212
|
+
|
213
|
+
this.urlState = {};
|
214
|
+
this.urlMode = 'hash';
|
215
|
+
this.urlHistoryBasePath = '/';
|
216
|
+
this.urlLocationPollId = null;
|
217
|
+
this.oldLocationHash = null;
|
218
|
+
this.oldUserHash = null;
|
219
|
+
}
|
220
|
+
|
221
|
+
/**
|
222
|
+
* Parse JSON object URL state to string format
|
223
|
+
* Arrange path names in an order that it is positioned on the urlSchema
|
224
|
+
* @param {object} urlState
|
225
|
+
* @returns {string}
|
226
|
+
*/
|
227
|
+
urlStateToUrlString(urlSchema, urlState) {
|
228
|
+
const searchParams = new URLSearchParams();
|
229
|
+
const pathParams = {};
|
230
|
+
|
231
|
+
Object.keys(urlState).forEach(key => {
|
232
|
+
let schema = urlSchema.find(schema => schema.name === key);
|
233
|
+
if (schema?.deprecated_for) {
|
234
|
+
schema = urlSchema.find(schemaKey => schemaKey.name === schema.deprecated_for);
|
235
|
+
}
|
236
|
+
if (schema?.position == 'path') {
|
237
|
+
pathParams[schema?.name] = urlState[key];
|
238
|
+
} else {
|
239
|
+
searchParams.append(schema?.name || key, urlState[key]);
|
240
|
+
}
|
241
|
+
});
|
242
|
+
|
243
|
+
const strPathParams = urlSchema
|
244
|
+
.filter(s => s.position == 'path')
|
245
|
+
.map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
|
246
|
+
.join('/');
|
247
|
+
|
248
|
+
const strStrippedTrailingSlash = `${strPathParams.replace(/\/$/, '')}`;
|
249
|
+
const concatenatedPath = `/${strStrippedTrailingSlash}?${searchParams.toString()}`;
|
250
|
+
return searchParams.toString() ? concatenatedPath : `/${strStrippedTrailingSlash}`;
|
251
|
+
}
|
252
|
+
|
253
|
+
/**
|
254
|
+
* Parse string URL and add it in the current urlState
|
255
|
+
* Example:
|
256
|
+
* /page/n7/mode/2up => {page: 'n7', mode: '2up'}
|
257
|
+
* /page/n7/mode/2up/search/hello => {page: 'n7', mode: '2up', q: 'hello'}
|
258
|
+
* @param {array} urlSchema
|
259
|
+
* @param {string} str
|
260
|
+
* @returns {object}
|
261
|
+
*/
|
262
|
+
urlStringToUrlState(urlSchema, str) {
|
263
|
+
const urlState = {};
|
264
|
+
|
265
|
+
// Fetch searchParams from given {str}
|
266
|
+
// Note: whole URL path is needed for URLSearchParams
|
267
|
+
const urlPath = new URL(str, 'http://example.com');
|
268
|
+
const urlSearchParamsObj = Object.fromEntries(urlPath.searchParams.entries());
|
269
|
+
const urlStrSplitSlashObj = Object.fromEntries(urlPath.pathname
|
270
|
+
.match(/[^\\/]+\/[^\\/]+/g)
|
271
|
+
.map(x => x.split('/'))
|
272
|
+
);
|
273
|
+
const doesKeyExists = (_object, _key) => {
|
274
|
+
return Object.keys(_object).some(value => value == _key);
|
275
|
+
};
|
276
|
+
|
277
|
+
urlSchema
|
278
|
+
.filter(schema => schema.position == 'path')
|
279
|
+
.forEach(schema => {
|
280
|
+
if (!urlStrSplitSlashObj[schema.name] && schema.default) {
|
281
|
+
return urlState[schema.name] = schema.default;
|
282
|
+
}
|
283
|
+
const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
|
284
|
+
const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
|
285
|
+
|
286
|
+
if (hasDeprecatedKey)
|
287
|
+
return urlState[schema.deprecated_for] = urlStrSplitSlashObj[schema.name];
|
288
|
+
|
289
|
+
if (hasPropertyKey)
|
290
|
+
return urlState[schema.name] = urlStrSplitSlashObj[schema.name];
|
291
|
+
});
|
292
|
+
|
293
|
+
// Add searchParams to urlState
|
294
|
+
// Check if Object value is a Boolean and convert value to Boolean
|
295
|
+
// Otherwise, return Object value
|
296
|
+
const isBoolean = value => value === 'true' || (value === 'false' ? false : value);
|
297
|
+
Object.entries(urlSearchParamsObj).forEach(([key, value]) => {
|
298
|
+
urlState[key] = isBoolean(value);
|
299
|
+
});
|
300
|
+
|
301
|
+
return urlState;
|
302
|
+
}
|
303
|
+
|
304
|
+
/**
|
305
|
+
* Add or update key-value to the urlState
|
306
|
+
* @param {string} key
|
307
|
+
* @param {string} val
|
308
|
+
*/
|
309
|
+
setUrlParam(key, value) {
|
310
|
+
this.urlState[key] = value;
|
311
|
+
|
312
|
+
this.pushToAddressBar();
|
313
|
+
}
|
314
|
+
|
315
|
+
/**
|
316
|
+
* Delete key-value to the urlState
|
317
|
+
* @param {string} key
|
318
|
+
*/
|
319
|
+
removeUrlParam(key) {
|
320
|
+
delete this.urlState[key];
|
321
|
+
|
322
|
+
this.pushToAddressBar();
|
323
|
+
}
|
324
|
+
|
325
|
+
/**
|
326
|
+
* Get key-value from the urlState
|
327
|
+
* @param {string} key
|
328
|
+
* @return {string}
|
329
|
+
*/
|
330
|
+
getUrlParam(key) {
|
331
|
+
return this.urlState[key];
|
332
|
+
}
|
333
|
+
|
334
|
+
/**
|
335
|
+
* Push URL params to addressbar
|
336
|
+
*/
|
337
|
+
pushToAddressBar() {
|
338
|
+
const urlStrPath = this.urlStateToUrlString(this.urlSchema, this.urlState);
|
339
|
+
if (this.urlMode == 'history') {
|
340
|
+
if (window.history && window.history.replaceState) {
|
341
|
+
const newUrlPath = `${this.urlHistoryBasePath}${urlStrPath}`;
|
342
|
+
window.history.replaceState({}, null, newUrlPath);
|
343
|
+
}
|
344
|
+
} else {
|
345
|
+
window.location.replace('#' + urlStrPath);
|
346
|
+
}
|
347
|
+
this.oldLocationHash = urlStrPath;
|
348
|
+
}
|
349
|
+
|
350
|
+
/**
|
351
|
+
* Get the url and check if it has changed
|
352
|
+
* If it was changeed, update the urlState
|
353
|
+
*/
|
354
|
+
listenForHashChanges() {
|
355
|
+
this.oldLocationHash = window.location.hash.substr(1);
|
356
|
+
if (this.urlLocationPollId) {
|
357
|
+
clearInterval(this.urlLocationPollId);
|
358
|
+
this.urlLocationPollId = null;
|
359
|
+
}
|
360
|
+
|
361
|
+
// check if the URL changes
|
362
|
+
const updateHash = () => {
|
363
|
+
const newFragment = window.location.hash.substr(1);
|
364
|
+
const hasFragmentChange = newFragment != this.oldLocationHash;
|
365
|
+
|
366
|
+
if (!hasFragmentChange) { return; }
|
367
|
+
|
368
|
+
this.urlState = this.urlStringToUrlState(newFragment);
|
369
|
+
};
|
370
|
+
this.urlLocationPollId = setInterval(updateHash, 500);
|
371
|
+
}
|
372
|
+
|
373
|
+
/**
|
374
|
+
* Will read either the hash or URL and return the bookreader fragment
|
375
|
+
* @param {string} location
|
376
|
+
* @return {string}
|
377
|
+
*/
|
378
|
+
pullFromAddressBar (location) {
|
379
|
+
const path = this.urlMode === 'history'
|
380
|
+
? location.substr(this.urlHistoryBasePath.length)
|
381
|
+
: location.substr(1);
|
382
|
+
this.urlState = this.urlStringToUrlState(this.urlSchema, path);
|
383
|
+
}
|
384
|
+
}
|
385
|
+
export class BookreaderUrlPlugin extends BookReader {
|
386
|
+
|
387
|
+
init() {
|
388
|
+
if (this.options.enableUrlPlugin) {
|
389
|
+
this.urlPlugin = new UrlPlugin(this.options);
|
390
|
+
this.bind(BookReader.eventNames.PostInit, () => {
|
391
|
+
const { urlMode } = this.options;
|
392
|
+
|
393
|
+
if (urlMode === 'hash') {
|
394
|
+
this.urlPlugin.listenForHashChanges();
|
395
|
+
}
|
396
|
+
});
|
397
|
+
}
|
398
|
+
|
399
|
+
super.init();
|
400
|
+
}
|
401
|
+
|
402
|
+
}
|
403
|
+
|
404
|
+
window.BookReader = BookreaderUrlPlugin;
|
405
|
+
export default BookreaderUrlPlugin;
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
import sinon from 'sinon';
|
3
|
+
import BookReader from '@/src/BookReader.js';
|
4
|
+
/** @typedef {import('@/src/BookReader/options.js').BookReaderOptions} BookReaderOptions */
|
5
|
+
|
6
|
+
beforeAll(() => {
|
7
|
+
global.alert = jest.fn();
|
8
|
+
});
|
9
|
+
afterEach(() => {
|
10
|
+
jest.restoreAllMocks();
|
11
|
+
sinon.restore();
|
12
|
+
});
|
13
|
+
|
14
|
+
/** @type {BookReaderOptions['data']} */
|
15
|
+
const SAMPLE_DATA = [
|
16
|
+
[
|
17
|
+
{ width: 123, height: 123, uri: 'https://archive.org/image0.jpg', pageNum: '1' },
|
18
|
+
],
|
19
|
+
[
|
20
|
+
{ width: 123, height: 123, uri: 'https://archive.org/image1.jpg', pageNum: '2' },
|
21
|
+
{ width: 123, height: 123, uri: 'https://archive.org/image2.jpg', pageNum: '3' },
|
22
|
+
],
|
23
|
+
[
|
24
|
+
{ width: 123, height: 123, uri: 'https://archive.org/image3.jpg', pageNum: '4' },
|
25
|
+
{ width: 123, height: 123, uri: 'https://archive.org/image4.jpg', pageNum: '5' },
|
26
|
+
],
|
27
|
+
[
|
28
|
+
{ width: 123, height: 123, uri: 'https://archive.org/image5.jpg', pageNum: '6' },
|
29
|
+
],
|
30
|
+
];
|
31
|
+
|
32
|
+
describe('zoom', () => {
|
33
|
+
const br = new BookReader({ data: SAMPLE_DATA });
|
34
|
+
br.init();
|
35
|
+
|
36
|
+
test('initializes with default columns', () => {
|
37
|
+
expect(br.thumbColumns).toBe(br.options.thumbColumns);
|
38
|
+
});
|
39
|
+
|
40
|
+
test('removes column and redraws zooming in', () => {
|
41
|
+
const prepare = sinon.spy(br._modes.modeThumb, 'prepare');
|
42
|
+
const startColumns = br.thumbColumns;
|
43
|
+
br._modes.modeThumb.zoom('in');
|
44
|
+
expect(br.thumbColumns).toBe(startColumns - 1);
|
45
|
+
expect(prepare.callCount).toBe(1);
|
46
|
+
});
|
47
|
+
|
48
|
+
test('adds column and redraws zooming out', () => {
|
49
|
+
const prepare = sinon.spy(br._modes.modeThumb, 'prepare');
|
50
|
+
const startColumns = br.thumbColumns;
|
51
|
+
br._modes.modeThumb.zoom('out');
|
52
|
+
expect(br.thumbColumns).toBe(startColumns + 1);
|
53
|
+
expect(prepare.callCount).toBe(1);
|
54
|
+
});
|
55
|
+
|
56
|
+
test('keeps columns and no redraw at zooming in limit', () => {
|
57
|
+
const prepare = sinon.spy(br._modes.modeThumb, 'prepare');
|
58
|
+
br.thumbColumns = br.options.thumbMinZoomColumns;
|
59
|
+
br._modes.modeThumb.zoom('in');
|
60
|
+
expect(br.thumbColumns).toBe(br.options.thumbMinZoomColumns);
|
61
|
+
expect(prepare.callCount).toBe(0);
|
62
|
+
});
|
63
|
+
|
64
|
+
test('keeps columns and no redraw at zooming out limit', () => {
|
65
|
+
const prepare = sinon.spy(br._modes.modeThumb, 'prepare');
|
66
|
+
br.thumbColumns = br.options.thumbMaxZoomColumns;
|
67
|
+
br._modes.modeThumb.zoom('out');
|
68
|
+
expect(br.thumbColumns).toBe(br.options.thumbMaxZoomColumns);
|
69
|
+
expect(prepare.callCount).toBe(0);
|
70
|
+
});
|
71
|
+
});
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|