Book Reader Issues (#906)

* Refactored the Font Escaping Regex with new unit tests.

* Fonts are now properly escaped, somehow a regression was introduced.

* Refactored most of the book page loading for the reader into the service.

* Fixed a bug where going into fullscreen in non dark mode will cause the background of the reader to go black. Fixed a rendering issue with margin left/right screwing html up. Fixed an issue where line-height: 100% would break book's css, now we remove the styles if they are non-valuable.

* Changed how I fixed the black mode in fullscreen

* Fixed an issue where anchors wouldn't be colored blue in white mode

* Fixed a bug in the code that checks if a filename is a cover where it would choose "backcover" as a cover, despite it not being a valid case.

* Validate if ReleaseYear is a valid year and if not, set it to 0 to disable it.

* Fixed an issue where some large images could blow out the screen when reading on mobile. Now images will force to be max of width of browser

* Put my hack back in for fullscreen putting background color to black

* Change forwarded headers from All to explicit names

* Fixed an issue where Scheme was not https when it should have been. Now the browser will handle which scheme to request.

* Cleaned up the user preferences to stack multiple controls onto one row

* Fixed fullscreen scroll issue with progress, but now sticky top is missing.

* Corrected the element on which we fullscreen
This commit is contained in:
Joseph Milazzo 2022-01-07 06:56:28 -08:00 committed by GitHub
parent 32bfe46187
commit 2b57449a63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 426 additions and 315 deletions

View file

@ -1,4 +1,4 @@
<div class="container-flex {{darkMode ? 'dark-mode' : ''}}" #reader>
<div class="container-flex {{darkMode ? 'dark-mode' : ''}}" style="overflow: auto;" #reader>
<div class="fixed-top" #stickyTop>
<a class="sr-only sr-only-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
@ -94,7 +94,7 @@
</ul>
</div>
<ng-template #nestedChildren>
<ul *ngFor="let chapterGroup of chapters" style="padding-inline-start: 0px">
<ul *ngFor="let chapterGroup of chapters" class="chapter-title">
<li class="{{chapterGroup.page == pageNum ? 'active': ''}}" (click)="loadChapterPage(chapterGroup.page, '')">
{{chapterGroup.title}}
</li>
@ -110,8 +110,11 @@
</app-drawer>
</div>
<div #readingSection class="reading-section" [ngStyle]="{'padding-top': topOffset + 20 + 'px'}" [@isLoading]="isLoading ? true : false" (click)="handleReaderClick($event)">
<div #readingHtml class="book-content" [ngStyle]="{'padding-bottom': topOffset + 20 + 'px', 'margin': '0px 0px'}" [innerHtml]="page" *ngIf="page !== undefined"></div>
<div #readingSection class="reading-section" [ngStyle]="{'padding-top': topOffset + 20 + 'px'}"
[@isLoading]="isLoading ? true : false" (click)="handleReaderClick($event)">
<div #readingHtml class="book-content" [ngStyle]="{'padding-bottom': topOffset + 20 + 'px', 'margin': '0px 0px'}"
[innerHtml]="page" *ngIf="page !== undefined"></div>
<div class="left {{clickOverlayClass('left')}} no-observe" (click)="prevPage()" *ngIf="clickToPaginate">
</div>

View file

@ -154,19 +154,33 @@ $primary-color: #0062cc;
}
.reading-section {
height: 100vh;
max-height: 100vh;
width: 100%;
overflow: auto;
//overflow: auto; // This will break progress reporting
}
.book-content {
position: relative;
}
// A bunch of resets so books render correctly
::ng-deep .book-content {
& a, & :link {
color: blue;
}
}
.drawer-body {
padding-bottom: 20px;
}
.chapter-title {
padding-inline-start: 0px
}
::ng-deep .scale-width {
max-width: 100%;
}
// Click to Paginate styles

View file

@ -1,5 +1,5 @@
import { AfterViewInit, Component, ElementRef, HostListener, OnDestroy, OnInit, Renderer2, RendererStyleFlags2, ViewChild } from '@angular/core';
import {Location} from '@angular/common';
import { AfterViewInit, Component, ElementRef, HostListener, Inject, OnDestroy, OnInit, Renderer2, RendererStyleFlags2, ViewChild } from '@angular/core';
import {DOCUMENT, Location} from '@angular/common';
import { FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
@ -243,7 +243,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
private seriesService: SeriesService, private readerService: ReaderService, private location: Location,
private renderer: Renderer2, private navService: NavService, private toastr: ToastrService,
private domSanitizer: DomSanitizer, private bookService: BookService, private memberService: MemberService,
private scrollService: ScrollService, private utilityService: UtilityService, private libraryService: LibraryService) {
private scrollService: ScrollService, private utilityService: UtilityService, private libraryService: LibraryService,
@Inject(DOCUMENT) private document: Document) {
this.navService.hideNavBar();
this.darkModeStyleElem = this.renderer.createElement('style');
@ -281,7 +282,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
});
}
const bodyNode = document.querySelector('body');
const bodyNode = this.document.querySelector('body');
if (bodyNode !== undefined && bodyNode !== null) {
this.originalBodyColor = bodyNode.style.background;
}
@ -296,14 +297,16 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/
ngAfterViewInit() {
// check scroll offset and if offset is after any of the "id" markers, save progress
fromEvent(window, 'scroll')
fromEvent(this.reader.nativeElement, 'scroll')
.pipe(debounceTime(200), takeUntil(this.onDestroy)).subscribe((event) => {
if (this.isLoading) return;
console.log('Scroll');
// Highlight the current chapter we are on
if (Object.keys(this.pageAnchors).length !== 0) {
// get the height of the document so we can capture markers that are halfway on the document viewport
const verticalOffset = this.scrollService.scrollPosition + (document.body.offsetHeight / 2);
const verticalOffset = this.scrollService.scrollPosition + (this.document.body.offsetHeight / 2);
const alreadyReached = Object.values(this.pageAnchors).filter((i: number) => i <= verticalOffset);
if (alreadyReached.length > 0) {
@ -350,7 +353,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
ngOnDestroy(): void {
const bodyNode = document.querySelector('body');
const bodyNode = this.document.querySelector('body');
if (bodyNode !== undefined && bodyNode !== null && this.originalBodyColor !== undefined) {
bodyNode.style.background = this.originalBodyColor;
if (this.user.preferences.siteDarkMode) {
@ -359,7 +362,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
this.navService.showNavBar();
const head = document.querySelector('head');
const head = this.document.querySelector('head');
this.renderer.removeChild(head, this.darkModeStyleElem);
if (this.clickToPaginateVisualOverlayTimeout !== undefined) {
@ -581,8 +584,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
resetSettings() {
const windowWidth = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;
|| this.document.documentElement.clientWidth
|| this.document.body.clientWidth;
let margin = '15%';
if (windowWidth <= 700) {
@ -631,7 +634,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
moveFocus() {
const elems = document.getElementsByClassName('reading-section');
const elems = this.document.getElementsByClassName('reading-section');
if (elems.length > 0) {
(elems[0] as HTMLDivElement).focus();
}
@ -679,10 +682,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
getPageMarkers(ids: Array<string>) {
try {
return document.querySelectorAll(ids.map(id => '#' + this.cleanIdSelector(id)).join(', '));
return this.document.querySelectorAll(ids.map(id => '#' + this.cleanIdSelector(id)).join(', '));
} catch (Exception) {
// Fallback to anchors instead. Some books have ids that are not valid for querySelectors, so anchors should be used instead
return document.querySelectorAll(ids.map(id => '[href="#' + id + '"]').join(', '));
return this.document.querySelectorAll(ids.map(id => '[href="#' + id + '"]').join(', '));
}
}
@ -717,6 +720,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.setupPage(part, scrollTop);
return;
}
// Apply scaling class to all images to ensure they scale down to max width to not blow out the reader
Array.from(imgs).forEach(img => this.renderer.addClass(img, 'scale-width'));
Promise.all(Array.from(imgs)
.filter(img => !img.complete)
.map(img => new Promise(resolve => { img.onload = img.onerror = resolve; })))
@ -868,14 +875,25 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
updateReaderStyles() {
if (this.readingHtml != undefined && this.readingHtml.nativeElement) {
for(let i = 0; i < this.readingHtml.nativeElement.children.length; i++) {
const elem = this.readingHtml.nativeElement.children.item(i);
if (elem?.tagName != 'STYLE') {
Object.entries(this.pageStyles).forEach(item => {
this.renderer.setStyle(elem, item[0], item[1], RendererStyleFlags2.Important);
});
// for(let i = 0; i < this.readingHtml.nativeElement.children.length; i++) {
// const elem = this.readingHtml.nativeElement.children.item(i);
// if (elem?.tagName != 'STYLE') {
// Object.entries(this.pageStyles).forEach(item => {
// if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') return;
// this.renderer.setStyle(elem, item[0], item[1], RendererStyleFlags2.Important);
// });
// }
// }
console.log('pageStyles: ', this.pageStyles);
console.log('readingHtml: ', this.readingHtml.nativeElement);
Object.entries(this.pageStyles).forEach(item => {
if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') {
// Remove the style or skip
this.renderer.removeStyle(this.readingHtml.nativeElement, item[0]);
return;
}
}
this.renderer.setStyle(this.readingHtml.nativeElement, item[0], item[1], RendererStyleFlags2.Important);
});
}
}
@ -903,7 +921,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
setOverrideStyles() {
const bodyNode = document.querySelector('body');
const bodyNode = this.document.querySelector('body');
if (bodyNode !== undefined && bodyNode !== null) {
if (this.user.preferences.siteDarkMode) {
bodyNode.classList.remove('bg-dark');
@ -912,7 +930,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
bodyNode.style.background = this.getDarkModeBackgroundColor();
}
this.backgroundColor = this.getDarkModeBackgroundColor();
const head = document.querySelector('head');
const head = this.document.querySelector('head');
if (this.darkMode) {
this.renderer.appendChild(head, this.darkModeStyleElem)
} else {
@ -948,7 +966,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// Part selector is a XPATH
element = this.getElementFromXPath(partSelector);
} else {
element = document.querySelector('*[id="' + partSelector + '"]');
element = this.document.querySelector('*[id="' + partSelector + '"]');
}
if (element === null) return;
@ -984,7 +1002,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}
getElementFromXPath(path: string) {
const node = document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
const node = this.document.evaluate(path, this.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (node?.nodeType === Node.ELEMENT_NODE) {
return node as Element;
}
@ -994,7 +1012,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
getXPathTo(element: any): string {
if (element === null) return '';
if (element.id !== '') { return 'id("' + element.id + '")'; }
if (element === document.body) { return element.tagName; }
if (element === this.document.body) { return element.tagName; }
let ix = 0;
@ -1027,12 +1045,16 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.isFullscreen) {
this.readerService.exitFullscreen(() => {
this.isFullscreen = false;
this.renderer.removeStyle(this.reader.nativeElement, 'background');
});
} else {
this.readerService.enterFullscreen(this.reader.nativeElement, () => {
this.isFullscreen = true;
// HACK: This is a bug with how browsers change the background color for fullscreen mode
if (!this.darkMode) {
this.renderer.setStyle(this.reader.nativeElement, 'background', 'white');
}
});
}
}
}