mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Webtoon Polish Part 2 (#739)
* Change css scoping to be on the book content itself only to prevent any leaking on the reading section code. Apply a margin so that book margins on the whole content doesn't show black lines. * Take out debug outline on webtoon reader * Removed some imports * Fixed an issue where when restoring current page in webtoon mode, the page number would jump forward * Last page on webtoon reader now properly counts. This was due to a - 1 issue fixing previous issues. * Fixed an issue where scrollToPage (from progress bar or go to page) wouldn't work if the page somehow was visible. * Ready for testing on beta users
This commit is contained in:
parent
336283be6e
commit
094c12d43d
@ -30,6 +30,7 @@ namespace API.Services
|
|||||||
private readonly ILogger<BookService> _logger;
|
private readonly ILogger<BookService> _logger;
|
||||||
private readonly StylesheetParser _cssParser = new ();
|
private readonly StylesheetParser _cssParser = new ();
|
||||||
private static readonly RecyclableMemoryStreamManager StreamManager = new ();
|
private static readonly RecyclableMemoryStreamManager StreamManager = new ();
|
||||||
|
private const string CssScopeClass = ".book-content";
|
||||||
|
|
||||||
public BookService(ILogger<BookService> logger)
|
public BookService(ILogger<BookService> logger)
|
||||||
{
|
{
|
||||||
@ -152,22 +153,23 @@ namespace API.Services
|
|||||||
EscapeCssImageReferences(ref stylesheetHtml, apiBase, book);
|
EscapeCssImageReferences(ref stylesheetHtml, apiBase, book);
|
||||||
|
|
||||||
var styleContent = RemoveWhiteSpaceFromStylesheets(stylesheetHtml);
|
var styleContent = RemoveWhiteSpaceFromStylesheets(stylesheetHtml);
|
||||||
styleContent = styleContent.Replace("body", ".reading-section");
|
|
||||||
|
styleContent = styleContent.Replace("body", CssScopeClass);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(styleContent)) return string.Empty;
|
if (string.IsNullOrEmpty(styleContent)) return string.Empty;
|
||||||
|
|
||||||
var stylesheet = await _cssParser.ParseAsync(styleContent);
|
var stylesheet = await _cssParser.ParseAsync(styleContent);
|
||||||
foreach (var styleRule in stylesheet.StyleRules)
|
foreach (var styleRule in stylesheet.StyleRules)
|
||||||
{
|
{
|
||||||
if (styleRule.Selector.Text == ".reading-section") continue;
|
if (styleRule.Selector.Text == CssScopeClass) continue;
|
||||||
if (styleRule.Selector.Text.Contains(","))
|
if (styleRule.Selector.Text.Contains(","))
|
||||||
{
|
{
|
||||||
styleRule.Text = styleRule.Text.Replace(styleRule.SelectorText,
|
styleRule.Text = styleRule.Text.Replace(styleRule.SelectorText,
|
||||||
string.Join(", ",
|
string.Join(", ",
|
||||||
styleRule.Selector.Text.Split(",").Select(s => ".reading-section " + s)));
|
styleRule.Selector.Text.Split(",").Select(s => $"{CssScopeClass} " + s)));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
styleRule.Text = ".reading-section " + styleRule.Text;
|
styleRule.Text = $"{CssScopeClass} " + styleRule.Text;
|
||||||
}
|
}
|
||||||
return RemoveWhiteSpaceFromStylesheets(stylesheet.ToCss());
|
return RemoveWhiteSpaceFromStylesheets(stylesheet.ToCss());
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div #readingSection class="reading-section" [ngStyle]="{'padding-top': topOffset + 20 + 'px'}" [@isLoading]="isLoading ? true : false" (click)="handleReaderClick($event)">
|
<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'}" [innerHtml]="page" *ngIf="page !== undefined"></div>
|
<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 class="left {{clickOverlayClass('left')}} no-observe" (click)="prevPage()" *ngIf="clickToPaginate">
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges } from '@angular/core';
|
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges } from '@angular/core';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
|
||||||
import { BehaviorSubject, fromEvent, ReplaySubject, Subject } from 'rxjs';
|
import { BehaviorSubject, fromEvent, ReplaySubject, Subject } from 'rxjs';
|
||||||
import { debounceTime, takeUntil } from 'rxjs/operators';
|
import { debounceTime, takeUntil } from 'rxjs/operators';
|
||||||
import { UtilityService } from 'src/app/shared/_services/utility.service';
|
|
||||||
import { ReaderService } from '../../_services/reader.service';
|
import { ReaderService } from '../../_services/reader.service';
|
||||||
import { PAGING_DIRECTION } from '../_models/reader-enums';
|
import { PAGING_DIRECTION } from '../_models/reader-enums';
|
||||||
import { WebtoonImage } from '../_models/webtoon-image';
|
import { WebtoonImage } from '../_models/webtoon-image';
|
||||||
@ -119,7 +117,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
|
* Debug mode. Will show extra information. Use bitwise (|) operators between different modes to enable different output
|
||||||
*/
|
*/
|
||||||
debugMode: DEBUG_MODES = DEBUG_MODES.Outline;
|
debugMode: DEBUG_MODES = DEBUG_MODES.None;
|
||||||
|
|
||||||
get minPageLoaded() {
|
get minPageLoaded() {
|
||||||
return Math.min(...Object.values(this.imagesLoaded));
|
return Math.min(...Object.values(this.imagesLoaded));
|
||||||
@ -138,7 +136,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
private readonly onDestroy = new Subject<void>();
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
|
||||||
constructor(private readerService: ReaderService, private renderer: Renderer2, private utilityService: UtilityService) {}
|
constructor(private readerService: ReaderService, private renderer: Renderer2) {}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (changes.hasOwnProperty('totalPages') && changes['totalPages'].previousValue != changes['totalPages'].currentValue) {
|
if (changes.hasOwnProperty('totalPages') && changes['totalPages'].previousValue != changes['totalPages'].currentValue) {
|
||||||
@ -197,11 +195,6 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|| document.documentElement.scrollTop
|
|| document.documentElement.scrollTop
|
||||||
|| document.body.scrollTop || 0);
|
|| document.body.scrollTop || 0);
|
||||||
|
|
||||||
if (this.isScrolling && this.currentPageElem != null && this.isElementVisible(this.currentPageElem)) {
|
|
||||||
this.debugLog('[Scroll] Image is visible from scroll, isScrolling is now false');
|
|
||||||
this.isScrolling = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verticalOffset > this.prevScrollPosition) {
|
if (verticalOffset > this.prevScrollPosition) {
|
||||||
this.scrollingDirection = PAGING_DIRECTION.FORWARD;
|
this.scrollingDirection = PAGING_DIRECTION.FORWARD;
|
||||||
} else {
|
} else {
|
||||||
@ -209,15 +202,23 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.prevScrollPosition = verticalOffset;
|
this.prevScrollPosition = verticalOffset;
|
||||||
|
|
||||||
// Use offset of the image against the scroll container to test if the most of the image is visible on the screen. We can use this
|
if (this.isScrolling && this.currentPageElem != null && this.isElementVisible(this.currentPageElem)) {
|
||||||
// to mark the current page and separate the prefetching code.
|
this.debugLog('[Scroll] Image is visible from scroll, isScrolling is now false');
|
||||||
const midlineImages = Array.from(document.querySelectorAll('img[id^="page-"]'))
|
this.isScrolling = false;
|
||||||
.filter(entry => this.shouldElementCountAsCurrentPage(entry));
|
|
||||||
|
|
||||||
if (midlineImages.length > 0) {
|
|
||||||
this.setPageNum(parseInt(midlineImages[0].getAttribute('page') || this.pageNum + '', 10));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isScrolling) {
|
||||||
|
// Use offset of the image against the scroll container to test if the most of the image is visible on the screen. We can use this
|
||||||
|
// to mark the current page and separate the prefetching code.
|
||||||
|
const midlineImages = Array.from(document.querySelectorAll('img[id^="page-"]'))
|
||||||
|
.filter(entry => this.shouldElementCountAsCurrentPage(entry));
|
||||||
|
|
||||||
|
if (midlineImages.length > 0) {
|
||||||
|
this.setPageNum(parseInt(midlineImages[0].getAttribute('page') || this.pageNum + '', 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Check if we hit the last page
|
// Check if we hit the last page
|
||||||
this.checkIfShouldTriggerContinuousReader();
|
this.checkIfShouldTriggerContinuousReader();
|
||||||
|
|
||||||
@ -389,8 +390,6 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.debugLog('[Intersection] Page ' + imagePage + ' is visible: ', entry.isIntersecting);
|
this.debugLog('[Intersection] Page ' + imagePage + ' is visible: ', entry.isIntersecting);
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
this.debugLog('[Intersection] ! Page ' + imagePage + ' just entered screen');
|
this.debugLog('[Intersection] ! Page ' + imagePage + ' just entered screen');
|
||||||
//this.setPageNum(imagePage);
|
|
||||||
// ?! Changing this so that this just triggers a prefetch
|
|
||||||
this.prefetchWebtoonImages(imagePage);
|
this.prefetchWebtoonImages(imagePage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -414,10 +413,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
if (scrollToPage) {
|
if (scrollToPage) {
|
||||||
const currentImage = document.querySelector('img#page-' + this.pageNum);
|
const currentImage = document.querySelector('img#page-' + this.pageNum);
|
||||||
if (currentImage !== null && !this.isElementVisible(currentImage)) {
|
if (currentImage === null) return;
|
||||||
this.debugLog('[GoToPage] Scrolling to page', this.pageNum);
|
this.debugLog('[GoToPage] Scrolling to page', this.pageNum);
|
||||||
this.scrollToCurrentPage();
|
this.scrollToCurrentPage();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,6 +476,12 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the ranges of indecies to load from backend. totalPages - 1 is due to backend will automatically return last page for any page number
|
||||||
|
* above totalPages. Webtoon reader might ask for that which results in duplicate last pages.
|
||||||
|
* @param pageNum
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
calculatePrefetchIndecies(pageNum: number = -1) {
|
calculatePrefetchIndecies(pageNum: number = -1) {
|
||||||
if (pageNum == -1) {
|
if (pageNum == -1) {
|
||||||
pageNum = this.pageNum;
|
pageNum = this.pageNum;
|
||||||
@ -486,15 +490,15 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
let startingIndex = 0;
|
let startingIndex = 0;
|
||||||
let endingIndex = 0;
|
let endingIndex = 0;
|
||||||
if (this.isScrollingForwards()) {
|
if (this.isScrollingForwards()) {
|
||||||
startingIndex = Math.min(Math.max(pageNum - this.bufferPages, 0), this.totalPages);
|
startingIndex = Math.min(Math.max(pageNum - this.bufferPages, 0), this.totalPages - 1);
|
||||||
endingIndex = Math.min(Math.max(pageNum + this.bufferPages, 0), this.totalPages);
|
endingIndex = Math.min(Math.max(pageNum + this.bufferPages, 0), this.totalPages - 1);
|
||||||
|
|
||||||
if (startingIndex === this.totalPages) {
|
if (startingIndex === this.totalPages) {
|
||||||
return [0, 0];
|
return [0, 0];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startingIndex = Math.min(Math.max(pageNum - this.bufferPages, 0), this.totalPages);
|
startingIndex = Math.min(Math.max(pageNum - this.bufferPages, 0), this.totalPages - 1);
|
||||||
endingIndex = Math.min(Math.max(pageNum + this.bufferPages, 0), this.totalPages);
|
endingIndex = Math.min(Math.max(pageNum + this.bufferPages, 0), this.totalPages - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
[bufferPages]="5"
|
[bufferPages]="5"
|
||||||
[goToPage]="goToPageEvent"
|
[goToPage]="goToPageEvent"
|
||||||
(pageNumberChange)="handleWebtoonPageChange($event)"
|
(pageNumberChange)="handleWebtoonPageChange($event)"
|
||||||
[totalPages]="maxPages - 1"
|
[totalPages]="maxPages"
|
||||||
[urlProvider]="getPageUrl"
|
[urlProvider]="getPageUrl"
|
||||||
(loadNextChapter)="loadNextChapter()"
|
(loadNextChapter)="loadNextChapter()"
|
||||||
(loadPrevChapter)="loadPrevChapter()"
|
(loadPrevChapter)="loadPrevChapter()"
|
||||||
|
@ -20,7 +20,7 @@ import { ChangeContext, LabelType, Options } from '@angular-slider/ngx-slider';
|
|||||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
import { ChapterInfo } from './_models/chapter-info';
|
import { ChapterInfo } from './_models/chapter-info';
|
||||||
import { COLOR_FILTER, FITTING_OPTION, PAGING_DIRECTION, SPLIT_PAGE_PART } from './_models/reader-enums';
|
import { COLOR_FILTER, FITTING_OPTION, PAGING_DIRECTION, SPLIT_PAGE_PART } from './_models/reader-enums';
|
||||||
import { Preferences, scalingOptions } from '../_models/preferences/preferences';
|
import { scalingOptions } from '../_models/preferences/preferences';
|
||||||
import { READER_MODE } from '../_models/preferences/reader-mode';
|
import { READER_MODE } from '../_models/preferences/reader-mode';
|
||||||
import { MangaFormat } from '../_models/manga-format';
|
import { MangaFormat } from '../_models/manga-format';
|
||||||
import { LibraryService } from '../_services/library.service';
|
import { LibraryService } from '../_services/library.service';
|
||||||
@ -950,7 +950,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
sliderDragUpdate(context: ChangeContext) {
|
sliderDragUpdate(context: ChangeContext) {
|
||||||
// This will update the value for value except when in webtoon due to how the webtoon reader
|
// This will update the value for value except when in webtoon due to how the webtoon reader
|
||||||
// responds to page changes
|
// responds to page changes
|
||||||
if (this.readerMode != READER_MODE.WEBTOON) {
|
if (this.readerMode !== READER_MODE.WEBTOON) {
|
||||||
this.setPageNum(context.value);
|
this.setPageNum(context.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user