diff --git a/API/API.csproj b/API/API.csproj index 01bfaa2a9..137a3a985 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -38,7 +38,7 @@ - + diff --git a/API/Services/ImageService.cs b/API/Services/ImageService.cs index d17fea327..c2b3d4126 100644 --- a/API/Services/ImageService.cs +++ b/API/Services/ImageService.cs @@ -46,9 +46,7 @@ namespace API.Services var firstImage = _directoryService.GetFilesWithExtension(directory, Parser.Parser.ImageFileExtensions) .OrderBy(f => f, new NaturalSortComparer()).FirstOrDefault(); - - - + return firstImage; } diff --git a/UI/Web/package-lock.json b/UI/Web/package-lock.json index e47a8be17..c69cf5812 100644 --- a/UI/Web/package-lock.json +++ b/UI/Web/package-lock.json @@ -7392,7 +7392,8 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true }, "import-fresh": { "version": "2.0.0", @@ -9903,24 +9904,6 @@ "json5": "^2.1.2" } }, - "localforage": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz", - "integrity": "sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==", - "requires": { - "lie": "3.1.1" - }, - "dependencies": { - "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", - "requires": { - "immediate": "~3.0.5" - } - } - } - }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.html b/UI/Web/src/app/book-reader/book-reader/book-reader.component.html index 93f458d35..f3c7ae4e0 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.html +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.html @@ -58,17 +58,16 @@
- - +
-
{{pageNum}}
-
+
{{pageNum}}
+
-
{{maxPages - 1}}
+
{{maxPages - 1}}
@@ -100,15 +99,6 @@
- -
@@ -138,7 +128,7 @@ [disabled]="IsNextDisabled" (click)="nextPage()" title="{{readingDirection === ReadingDirection.LeftToRight ? 'Next' : 'Previous'}} Page"> {{readingDirection === ReadingDirection.LeftToRight ? 'Next' : 'Previous'}}  - +
diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss b/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss index 042274399..5672139f5 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss @@ -28,6 +28,7 @@ src: url(../../../assets/fonts/RocknRoll_One/RocknRollOne-Regular.ttf) format("truetype"); } +@import '../../../assets/themes/dark.scss'; $primary-color: #0062cc; .control-container { @@ -42,6 +43,11 @@ $primary-color: #0062cc; } } +.page-stub { + margin-top: 6px; + padding-left: 2px; + padding-right: 2px; +} .dark-mode { @@ -73,60 +79,12 @@ $primary-color: #0062cc; color: #8db2e5 !important; } - // Coppied - // html, body { - // color: #dcdcdc !important; - // background-image: none !important; - // background-color: #292929 !important; - // } - - // html::before, body::before { - // background-image: none !important; - // } - - // html *:not(input) {color: #dcdcdc !important} - // html * {background-color: rgb(41, 41, 41, 0.90) !important} - - // html *, html *[id], html *[class] { - // box-shadow: none !important; - // text-shadow: none !important; - // border-radius: unset !important; - // border-color: #555555 !important; - // outline-color: #555555 !important; - // } - - img, img[src] { + img, img[src] { z-index: 1; filter: brightness(0.85) !important; background-color: initial !important; - } + } - // video, video[src] { - // z-index: 1; - // background-color: transparent !important; - // } - - // input:not([type='button']):not([type='submit']) { - // color: #dcdcdc !important; - // background-image: none !important; - // background-color: #333333 !important; - // } - - // textarea, textarea[class], input[type='text'], input[type='text'][class] { - // color: #dcdcdc !important; - // background-color: #555555 !important; - // } - - // svg:not([fill]) {fill: #7d7d7d !important} - // li, select {background-image: none !important} - // input[type='text'], input[type='search'] {text-indent: 10px} - // a {background-color: rgba(255, 255, 255, 0.01) !important} - // html cite, html cite *, html cite *[class] {color: #029833 !important} - // svg[fill], button, input[type='button'], input[type='submit'] {opacity: 0.85 !important} - - // :before {color: #dcdcdc !important} - // :link:not(cite), :link *:not(cite) {color: #8db2e5 !important} - // :visited, :visited *, :visited *[class] {color: rgb(211, 138, 138) !important} :visited, :visited *, :visited *[class] {color: rgb(211, 138, 138) !important} :link:not(cite), :link *:not(cite) {color: #8db2e5 !important} } @@ -136,6 +94,21 @@ $primary-color: #0062cc; overflow: hidden; } +.dark-mode { + .reading-bar, .book-title, .drawer-body, .drawer-container { + background-color: $dark-form-background-no-opacity; + } + button { + background-color: $dark-form-background-no-opacity; + } +} + +::ng-deep .dark-mode .drawer-container { + .header, body, *:not(.progress-bar) { + background-color: $dark-form-background-no-opacity !important; + } +} + @media(max-width: 875px) { .book-title { display: none; diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts index 494bf6907..33aff1719 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts @@ -14,11 +14,10 @@ import { SeriesService } from 'src/app/_services/series.service'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { BookService } from '../book.service'; -import { KEY_CODES } from 'src/app/shared/_services/utility.service'; +import { KEY_CODES, UtilityService } from 'src/app/shared/_services/utility.service'; import { BookChapterItem } from '../_models/book-chapter-item'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { Stack } from 'src/app/shared/data-structures/stack'; -import { Preferences } from 'src/app/_models/preferences/preferences'; import { MemberService } from 'src/app/_services/member.service'; import { ReadingDirection } from 'src/app/_models/preferences/reading-direction'; import { ScrollService } from 'src/app/scroll.service'; @@ -166,7 +165,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { pageAnchors: {[n: string]: number } = {}; currentPageAnchor: string = ''; - intersectionObserver: IntersectionObserver = new IntersectionObserver((entries) => this.handleIntersection(entries), { threshold: [1] }); /** * Last seen progress part path */ @@ -186,10 +184,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { color: #e83e8c !important; } - // .btn-icon { - // background-color: transparent; - // } - :link, a { color: #8db2e5 !important; } @@ -205,25 +199,31 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { return ReadingDirection; } - get IsPrevDisabled() { + get IsPrevDisabled(): boolean { if (this.readingDirection === ReadingDirection.LeftToRight) { + // Acting as Previous button return this.prevPageDisabled && this.pageNum === 0; - } - return this.nextPageDisabled && this.pageNum + 1 >= this.maxPages - 1; + } else { + // Acting as a Next button + return this.nextPageDisabled && this.pageNum + 1 > this.maxPages - 1; + } } - get IsNextDisabled() { + get IsNextDisabled(): boolean { if (this.readingDirection === ReadingDirection.LeftToRight) { - this.nextPageDisabled && this.pageNum + 1 >= this.maxPages - 1; + // Acting as Next button + return this.nextPageDisabled && this.pageNum + 1 > this.maxPages - 1; + } else { + // Acting as Previous button + return this.prevPageDisabled && this.pageNum === 0; } - return this.prevPageDisabled && this.pageNum === 0; } constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, 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 scrollService: ScrollService, private utilityService: UtilityService) { this.navService.hideNavBar(); this.darkModeStyleElem = this.renderer.createElement('style'); @@ -296,6 +296,36 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } + + // Find the element that is on screen to bookmark against + const intersectingEntries = Array.from(this.readingSectionElemRef.nativeElement.querySelectorAll('div,o,p,ul,li,a,img,h1,h2,h3,h4,h5,h6,span')) + .filter(element => !element.classList.contains('no-observe')) + .filter(entry => { + return this.utilityService.isInViewport(entry, this.topOffset); + }); + + intersectingEntries.sort((a: Element, b: Element) => { + const aTop = a.getBoundingClientRect().top; + const bTop = b.getBoundingClientRect().top; + if (aTop < bTop) { + return -1; + } + if (aTop > bTop) { + return 1; + } + + return 0; + }); + + if (intersectingEntries.length > 0) { + let path = this.getXPathTo(intersectingEntries[0]); + if (path === '') { return; } + if (!path.startsWith('id')) { + path = '//html[1]/' + path; + } + this.lastSeenScrollPartPath = path; + } + if (this.lastSeenScrollPartPath !== '' && !this.incognitoMode) { this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.pageNum, this.lastSeenScrollPartPath).pipe(take(1)).subscribe(() => {/* No operation */}); } @@ -326,7 +356,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.onDestroy.next(); this.onDestroy.complete(); - this.intersectionObserver.disconnect(); } ngOnInit(): void { @@ -443,12 +472,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } - handleIntersection(entries: IntersectionObserverEntry[]) { - let intersectingEntries = Array.from(entries) - .filter(entry => entry.isIntersecting) - .map(entry => entry.target) - intersectingEntries.sort((a: Element, b: Element) => { - const aTop = a.getBoundingClientRect().top; + sortElements(a: Element, b: Element) { + const aTop = a.getBoundingClientRect().top; const bTop = b.getBoundingClientRect().top; if (aTop < bTop) { return -1; @@ -458,17 +483,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } return 0; - }); - - - if (intersectingEntries.length > 0) { - let path = this.getXPathTo(intersectingEntries[0]); - if (path === '') { return; } - if (!path.startsWith('id')) { - path = '//html[1]/' + path; - } - this.lastSeenScrollPartPath = path; - } } loadNextChapter() { @@ -541,16 +555,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } - resetSettings(afterSave: boolean = false) { + resetSettings() { const windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; let margin = '15%'; if (windowWidth <= 700) { - if (afterSave && this.user.preferences.bookReaderMargin !== 0) { - this.toastr.info('Margin will be reset to 0% on mobile. You do not have to save for settings to take effect.'); - } margin = '0%'; } if (this.user) { @@ -558,11 +569,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { margin = this.user.preferences.bookReaderMargin + '%'; } this.pageStyles = {'font-family': this.user.preferences.bookReaderFontFamily, 'font-size': this.user.preferences.bookReaderFontSize + '%', 'margin-left': margin, 'margin-right': margin, 'line-height': this.user.preferences.bookReaderLineSpacing + '%'}; - if (!afterSave) { - if (this.user.preferences.siteDarkMode && !this.user.preferences.bookReaderDarkMode) { - this.user.preferences.bookReaderDarkMode = true; - } - } this.toggleDarkMode(this.user.preferences.bookReaderDarkMode); } else { @@ -657,12 +663,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } setupPageAnchors() { - this.readingSectionElemRef.nativeElement.querySelectorAll('div,o,p,ul,li,a,img,h1,h2,h3,h4,h5,h6,span').forEach(elem => { - if (!elem.classList.contains('no-observe')) { - this.intersectionObserver.observe(elem); - } - }); - this.pageAnchors = {}; this.currentPageAnchor = ''; const ids = this.chapters.map(item => item.children).flat().filter(item => item.page === this.pageNum).map(item => item.part).filter(item => item.length > 0); @@ -875,7 +875,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } getDarkModeBackgroundColor() { - return this.darkMode ? '#292929' : '#fff'; + return this.darkMode ? '#010409' : '#fff'; } setOverrideStyles() { @@ -896,33 +896,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } - saveSettings() { - if (this.user === undefined) return; - const modelSettings = this.settingsForm.value; - const data: Preferences = { - readingDirection: this.user.preferences.readingDirection, - scalingOption: this.user.preferences.scalingOption, - pageSplitOption: this.user.preferences.pageSplitOption, - autoCloseMenu: this.user.preferences.autoCloseMenu, - readerMode: this.user.preferences.readerMode, - bookReaderDarkMode: this.darkMode, - bookReaderFontFamily: modelSettings.bookReaderFontFamily, - bookReaderFontSize: parseInt(this.pageStyles['font-size'].substr(0, this.pageStyles['font-size'].length - 1), 10), - bookReaderLineSpacing: parseInt(this.pageStyles['line-height'].replace('!important', '').trim(), 10), - bookReaderMargin: parseInt(this.pageStyles['margin-left'].replace('%', '').replace('!important', '').trim(), 10), - bookReaderTapToPaginate: this.clickToPaginate, - bookReaderReadingDirection: this.readingDirection, - siteDarkMode: this.user.preferences.siteDarkMode, - }; - this.accountService.updatePreferences(data).pipe(take(1)).subscribe((updatedPrefs) => { - this.toastr.success('User settings updated'); - if (this.user) { - this.user.preferences = updatedPrefs; - } - this.resetSettings(true); - }); - } - toggleDrawer() { this.topOffset = this.stickyTopElemRef.nativeElement?.offsetHeight; this.drawerOpen = !this.drawerOpen; diff --git a/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts b/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts index d40eec83d..d3978b4bf 100644 --- a/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts +++ b/UI/Web/src/app/manga-reader/infinite-scroller/infinite-scroller.component.ts @@ -205,7 +205,7 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy { return document.documentElement.offsetHeight + document.documentElement.scrollTop; } getScrollTop() { - return document.documentElement.scrollTop + return document.documentElement.scrollTop; } checkIfShouldTriggerContinuousReader() { diff --git a/UI/Web/src/app/shared/_services/utility.service.ts b/UI/Web/src/app/shared/_services/utility.service.ts index b66f67cb9..23fb459f8 100644 --- a/UI/Web/src/app/shared/_services/utility.service.ts +++ b/UI/Web/src/app/shared/_services/utility.service.ts @@ -127,4 +127,14 @@ export class UtilityService { return Breakpoint.Desktop; } + isInViewport(element: Element, additionalTopOffset: number = 0) { + const rect = element.getBoundingClientRect(); + return ( + rect.top >= additionalTopOffset && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + } + }