@internetarchive/bookreader 5.0.0-21 → 5.0.0-24-sortingstate

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 (130) hide show
  1. package/.nvmrc +1 -0
  2. package/BookReader/BookReader.css +0 -55
  3. package/BookReader/BookReader.js +32148 -2
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/bookreader-component-bundle.js +10916 -941
  6. package/BookReader/bookreader-component-bundle.js.map +1 -1
  7. package/BookReader/icons/1up.svg +12 -1
  8. package/BookReader/icons/2up.svg +15 -1
  9. package/BookReader/icons/advance.svg +26 -3
  10. package/BookReader/icons/chevron-right.svg +1 -1
  11. package/BookReader/icons/close-circle-dark.svg +1 -1
  12. package/BookReader/icons/close-circle.svg +1 -1
  13. package/BookReader/icons/fullscreen.svg +17 -1
  14. package/BookReader/icons/fullscreen_exit.svg +17 -1
  15. package/BookReader/icons/hamburger.svg +15 -1
  16. package/BookReader/icons/left-arrow.svg +12 -1
  17. package/BookReader/icons/magnify-minus.svg +16 -1
  18. package/BookReader/icons/magnify-plus.svg +17 -1
  19. package/BookReader/icons/magnify.svg +15 -1
  20. package/BookReader/icons/pause.svg +23 -1
  21. package/BookReader/icons/play.svg +22 -1
  22. package/BookReader/icons/playback-speed.svg +34 -1
  23. package/BookReader/icons/read-aloud.svg +22 -1
  24. package/BookReader/icons/review.svg +22 -3
  25. package/BookReader/icons/thumbnails.svg +17 -1
  26. package/BookReader/icons/voice.svg +1 -1
  27. package/BookReader/icons/volume-full.svg +22 -1
  28. package/BookReader/images/BRicons.svg +94 -5
  29. package/BookReader/images/books_graphic.svg +177 -1
  30. package/BookReader/images/icon_book.svg +12 -1
  31. package/BookReader/images/icon_bookmark.svg +12 -1
  32. package/BookReader/images/icon_gear.svg +14 -1
  33. package/BookReader/images/icon_hamburger.svg +20 -1
  34. package/BookReader/images/icon_home.svg +21 -1
  35. package/BookReader/images/icon_info.svg +11 -1
  36. package/BookReader/images/icon_one_page.svg +8 -1
  37. package/BookReader/images/icon_pause.svg +1 -1
  38. package/BookReader/images/icon_play.svg +1 -1
  39. package/BookReader/images/icon_playback-rate.svg +15 -1
  40. package/BookReader/images/icon_search_button.svg +8 -1
  41. package/BookReader/images/icon_share.svg +9 -1
  42. package/BookReader/images/icon_skip-ahead.svg +6 -1
  43. package/BookReader/images/icon_skip-back.svg +13 -2
  44. package/BookReader/images/icon_speaker.svg +18 -1
  45. package/BookReader/images/icon_speaker_open.svg +10 -1
  46. package/BookReader/images/icon_thumbnails.svg +12 -1
  47. package/BookReader/images/icon_toc.svg +5 -1
  48. package/BookReader/images/icon_two_pages.svg +9 -1
  49. package/BookReader/images/marker_chap-off.svg +11 -1
  50. package/BookReader/images/marker_chap-on.svg +11 -1
  51. package/BookReader/images/marker_srch-on.svg +11 -1
  52. package/BookReader/jquery-1.10.1.js +108 -2
  53. package/BookReader/plugins/plugin.archive_analytics.js +170 -1
  54. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  55. package/BookReader/plugins/plugin.autoplay.js +163 -1
  56. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  57. package/BookReader/plugins/plugin.chapters.js +333 -1
  58. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  59. package/BookReader/plugins/plugin.iframe.js +72 -1
  60. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  61. package/BookReader/plugins/plugin.mobile_nav.js +332 -1
  62. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  63. package/BookReader/plugins/plugin.resume.js +241 -1
  64. package/BookReader/plugins/plugin.resume.js.map +1 -1
  65. package/BookReader/plugins/plugin.search.js +1263 -1
  66. package/BookReader/plugins/plugin.search.js.map +1 -1
  67. package/BookReader/plugins/plugin.text_selection.js +839 -1
  68. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  69. package/BookReader/plugins/plugin.tts.js +9114 -2
  70. package/BookReader/plugins/plugin.tts.js.map +1 -1
  71. package/BookReader/plugins/plugin.url.js +750 -1
  72. package/BookReader/plugins/plugin.url.js.map +1 -1
  73. package/BookReader/plugins/plugin.vendor-fullscreen.js +326 -1
  74. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  75. package/BookReader/webcomponents-bundle.js +411 -2
  76. package/BookReader/webcomponents-bundle.js.map +1 -1
  77. package/CHANGELOG.md +12 -0
  78. package/package.json +2 -6
  79. package/src/BookNavigator/bookmarks/bookmarks-provider.js +1 -0
  80. package/src/BookNavigator/bookmarks/ia-bookmarks.js +3 -0
  81. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +1 -1
  82. package/src/BookNavigator/volumes/volumes-provider.js +39 -9
  83. package/src/BookReader/Mode1Up.js +10 -2
  84. package/src/BookReader/ModeThumb.js +13 -6
  85. package/src/BookReader/Navbar/Navbar.js +0 -29
  86. package/src/BookReader/options.js +4 -0
  87. package/src/BookReader.js +14 -34
  88. package/src/ItemNavigator/ItemNavigator.js +1 -0
  89. package/src/css/_BRnav.scss +0 -23
  90. package/src/plugins/plugin.url.js +209 -2
  91. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +0 -0
  92. package/tests/{BookReader → jest/BookReader}/BookReaderPublicFunctions.test.js +0 -0
  93. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +0 -0
  94. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +0 -0
  95. package/tests/{BookReader → jest/BookReader}/Mode1UpLit.test.js +0 -0
  96. package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +0 -0
  97. package/tests/{BookReader → jest/BookReader}/ModeSmoothZoom.test.js +0 -0
  98. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  99. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +0 -0
  100. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +0 -0
  101. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +0 -0
  102. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +0 -0
  103. package/tests/{BookReader → jest/BookReader}/utils/HTMLDimensionsCacher.test.js +0 -0
  104. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +0 -0
  105. package/tests/{BookReader → jest/BookReader}/utils.test.js +0 -0
  106. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +8 -0
  107. package/tests/{BookReader.test.js → jest/BookReader.test.js} +16 -0
  108. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +0 -0
  109. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +0 -0
  110. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +0 -0
  111. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +0 -0
  112. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +0 -0
  113. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +0 -0
  114. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +0 -0
  115. package/tests/jest/plugins/plugin.url.test.js +306 -0
  116. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +0 -0
  117. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +0 -0
  118. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +0 -0
  119. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +0 -0
  120. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +0 -0
  121. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +0 -0
  122. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +0 -0
  123. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +0 -0
  124. package/tests/{plugins → jest/plugins}/tts/utils.test.js +0 -0
  125. package/tests/{util → jest/util}/browserSniffing.test.js +0 -0
  126. package/tests/{util → jest/util}/docCookies.test.js +0 -0
  127. package/tests/{util → jest/util}/strings.test.js +0 -0
  128. package/tests/{utils.js → jest/utils.js} +0 -0
  129. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +6 -6
  130. package/tests/plugins/plugin.url.test.js +0 -147
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
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
+
10
+ # 5.0.0-22
11
+ - Dev: remove deprecated embed nav view, use standard default @iisa
12
+
1
13
  # 5.0.0-21
2
14
  - Dev: Toggle view=theater in fullscreen @mc2
3
15
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-21",
3
+ "version": "5.0.0-24-sortingstate",
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');
@@ -60,7 +60,7 @@ export default class {
60
60
  }
61
61
 
62
62
  onZoomOut() {
63
- this.bookreader.zoom();
63
+ this.bookreader.zoom(-1);
64
64
  }
65
65
 
66
66
  onAdjustmentChange(event) {
@@ -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,27 @@ export default class VolumesProvider {
25
36
  this.label = `Viewable files (${this.volumeCount})`;
26
37
  this.icon = html`${volumesIcon}`;
27
38
 
28
- this.sortOrderBy = "orig_sort";
29
- this.sortVolumes("orig_sort");
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
+ if (urlSortValue === sortType.title_asc || urlSortValue === sortType.title_desc) {
43
+ this.sortOrderBy = urlSortValue;
44
+ } else {
45
+ this.sortOrderBy = sortType.default;
46
+ }
47
+ this.sortVolumes(this.sortOrderBy);
30
48
  }
31
49
 
32
50
  get sortButton() {
33
51
  const sortIcons = {
34
- orig_sort: html`
52
+ default: html`
35
53
  <button class="sort-by neutral-icon" aria-label="Sort volumes in initial order" @click=${() => this.sortVolumes("title_asc")}>${sortNeutralIcon}</button>
36
54
  `,
37
55
  title_asc: html`
38
56
  <button class="sort-by asc-icon" aria-label="Sort volumes in ascending order" @click=${() => this.sortVolumes("title_desc")}>${sortAscIcon}</button>
39
57
  `,
40
58
  title_desc: html`
41
- <button class="sort-by desc-icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes("orig_sort")}>${sortDescIcon}</button>
59
+ <button class="sort-by desc-icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes("default")}>${sortDescIcon}</button>
42
60
  `,
43
61
  };
44
62
 
@@ -46,28 +64,40 @@ export default class VolumesProvider {
46
64
  }
47
65
 
48
66
  /**
49
- * @param {'orig_sort' | 'title_asc' | 'title_desc'} sortByType
67
+ * @param {'default' | 'title_asc' | 'title_desc'} sortByType
50
68
  */
51
69
  sortVolumes(sortByType) {
52
70
  let sortedFiles = [];
53
71
 
54
72
  const files = this.viewableFiles;
55
73
  sortedFiles = files.sort((a, b) => {
56
- if (sortByType === 'orig_sort') return a.orig_sort - b.orig_sort;
57
- else if (sortByType === 'title_asc') return a.title.localeCompare(b.title);
58
- else return b.title.localeCompare(a.title);
74
+ if (sortByType === sortType.title_asc) return a.title.localeCompare(b.title);
75
+ else if (sortByType === sortType.title_desc) return b.title.localeCompare(a.title);
76
+ else return a.orig_sort - b.orig_sort;
59
77
  });
60
78
 
61
79
  this.sortOrderBy = sortByType;
62
80
  this.component.viewableFiles = [...sortedFiles];
63
81
  this.actionButton = this.sortButton;
82
+
83
+ if (this.sortOrderBy !== sortType.default) {
84
+ this.bookreader.urlPlugin.setUrlParam('sort', sortByType);
85
+ } else {
86
+ this.bookreader.urlPlugin.removeUrlParam('sort');
87
+ }
88
+
89
+ const urlSchema = this.bookreader.urlPlugin.urlSchema;
90
+ const urlState = this.bookreader.urlPlugin.urlState;
91
+ this.bookreader.urlPlugin.urlStateToUrlString(urlSchema, urlState);
92
+ this.bookreader.urlPlugin.pushToAddressBar();
93
+
64
94
  this.optionChange(this.bookreader);
65
95
 
66
96
  this.multipleFilesClicked(sortByType);
67
97
  }
68
98
 
69
99
  /**
70
- * @param {'orig_sort' | 'title_asc' | 'title_desc'} orderBy
100
+ * @param {'default' | 'title_asc' | 'title_desc'} orderBy
71
101
  */
72
102
  multipleFilesClicked(orderBy) {
73
103
  if (!window.archive_analytics) {
@@ -79,8 +79,16 @@ export class Mode1Up {
79
79
  * @param {'in' | 'out'} direction
80
80
  */
81
81
  zoom(direction) {
82
- if (direction == 'in') this.mode1UpLit.zoomIn();
83
- else this.mode1UpLit.zoomOut();
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 {1 | -1} direction
238
+ * @param {'in' | 'out'} direction
239
239
  */
240
240
  zoom(direction) {
241
241
  const oldColumns = this.br.thumbColumns;
242
242
  switch (direction) {
243
- case -1:
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
- this.br.thumbColumns = clamp(this.br.thumbColumns, 2, 8);
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 */
@@ -240,35 +240,6 @@ export class Navbar {
240
240
  return this.$nav;
241
241
  }
242
242
 
243
- /**
244
- * Initialize the navigation bar when embedded
245
- */
246
- initEmbed() {
247
- const { br } = this;
248
- // IA-specific
249
- const thisLink = (window.location + '')
250
- .replace('?ui=embed','')
251
- .replace('/stream/', '/details/')
252
- .replace('#', '/');
253
- const logoHtml = br.showLogo ? `<a class="logo" href="${br.logoURL}" target="_blank"></a>` : '';
254
-
255
- br.refs.$BRfooter = this.$root = $('<div class="BRfooter"></div>');
256
- br.refs.$BRnav = this.$nav = $(
257
- `<div class="BRnav BRnavEmbed">
258
- ${logoHtml}
259
- <span class="BRembedreturn">
260
- <a href="${thisLink}" target="_blank">${br.bookTitle}</a>
261
- </span>
262
- <span class="BRtoolbarbuttons">
263
- <button class="BRicon book_left"></button>
264
- <button class="BRicon book_right"></button>
265
- <button class="BRicon full"></button>
266
- </span>
267
- </div>`);
268
- this.$root.append(this.$nav);
269
- br.refs.$br.append(this.$root);
270
- }
271
-
272
243
  /**
273
244
  * Returns the textual representation of the current page for the navbar
274
245
  * @param {number} index
@@ -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
 
@@ -531,15 +535,12 @@ BookReader.prototype.init = function() {
531
535
  // Explicitly ensure this.mode exists for initNavbar() below
532
536
  this.mode = params.mode;
533
537
 
534
- if (this.ui == "embed" && this.options.showNavbar) {
535
- this.initEmbedNavbar();
536
- } else {
537
- if (this.options.showToolbar) {
538
- this.initToolbar(this.mode, this.ui); // Build inside of toolbar div
539
- }
540
- if (this.options.showNavbar) {
541
- this.initNavbar();
542
- }
538
+ // Display Navigation
539
+ if (this.options.showToolbar) {
540
+ this.initToolbar(this.mode, this.ui); // Build inside of toolbar div
541
+ }
542
+ if (this.options.showNavbar) { // default navigation
543
+ this.initNavbar();
543
544
  }
544
545
 
545
546
  // Switch navbar controls on mobile/desktop
@@ -832,29 +833,11 @@ BookReader.prototype.drawLeafsThrottled = utils.throttle(
832
833
  * @param {number} direction Pass 1 to zoom in, anything else to zoom out
833
834
  */
834
835
  BookReader.prototype.zoom = function(direction) {
835
- switch (this.mode) {
836
- case this.constMode1up:
837
- if (direction == 1) {
838
- // XXX other cases
839
- this.zoom1up('in');
840
- } else {
841
- this.zoom1up('out');
842
- }
843
- break;
844
- case this.constMode2up:
845
- if (direction == 1) {
846
- // XXX other cases
847
- this.zoom2up('in');
848
- } else {
849
- this.zoom2up('out');
850
- }
851
- break;
852
- case this.constModeThumb:
853
- // XXX update zoomThumb for named directions
854
- this.zoomThumb(direction);
855
- break;
836
+ if (direction == 1) {
837
+ this.activeMode.zoom('in');
838
+ } else {
839
+ this.activeMode.zoom('out');
856
840
  }
857
-
858
841
  this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
859
842
  return;
860
843
  };
@@ -1583,9 +1566,6 @@ BookReader.prototype.updateViewModeButton = Navbar.prototype.updateViewModeButto
1583
1566
  exposeOverrideableMethod(Navbar, '_components.navbar', 'updateViewModeButton');
1584
1567
  BookReader.prototype.getNavPageNumString = Navbar.prototype.getNavPageNumString;
1585
1568
  exposeOverrideableMethod(Navbar, '_components.navbar', 'getNavPageNumString');
1586
- /** @deprecated */
1587
- BookReader.prototype.initEmbedNavbar = Navbar.prototype.initEmbed;
1588
- exposeOverrideableMethod(Navbar, '_components.navbar', 'initEmbed', 'initEmbedNavbar');
1589
1569
  /** @deprecated unused */
1590
1570
  BookReader.prototype.getNavPageNumHtml = getNavPageNumHtml;
1591
1571
  /** @deprecated unused outside this file */
@@ -267,6 +267,7 @@ export default class ItemNavigator extends LitElement {
267
267
  #frame {
268
268
  position: relative;
269
269
  overflow: hidden;
270
+ color-scheme: dark;
270
271
  }
271
272
 
272
273
  #frame.fullscreen,
@@ -127,10 +127,6 @@
127
127
  // Default
128
128
  @include brNavDark;
129
129
 
130
- &.BRnavEmbed {
131
- @include brNavLight;
132
- }
133
-
134
130
  /* Full mobile styles */
135
131
  @media (max-width: $brBreakPointMobile) {
136
132
  .BRbodyMobileNavEnabled &,
@@ -223,25 +219,6 @@
223
219
  flex-direction: row;
224
220
  border-top: 1px solid $controlsBorder;
225
221
 
226
- &.BRnavEmbed {
227
- padding-top: 0;
228
- height: auto;
229
- align-items: center;
230
-
231
- .BRembedreturn {
232
- flex: 1 auto;
233
- overflow-x: hidden;
234
- }
235
- .logo {
236
- display: inline-block;
237
- width: 25px;
238
- height: 25px;
239
- margin: 0 10px;
240
- background: transparent url(images/icon_home.svg) no-repeat center center;
241
- background-size: contain;
242
- }
243
- }
244
-
245
222
  /* Theming */
246
223
 
247
224
  // Default
@@ -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.locationPollID);
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;