-
+!
Kavita is a fast, feature rich, cross platform reading server. Built with a focus for manga,
and the goal of being a full solution for all your reading needs. Setup your own server and share
@@ -122,3 +122,4 @@ Thank you to [

](https://sentry.io/
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
* Copyright 2020-2021
+
diff --git a/UI/Web/src/app/_models/chapter.ts b/UI/Web/src/app/_models/chapter.ts
index 5b6956e74..94dd1b2d1 100644
--- a/UI/Web/src/app/_models/chapter.ts
+++ b/UI/Web/src/app/_models/chapter.ts
@@ -5,7 +5,10 @@ export interface Chapter {
range: string;
number: string;
files: Array
;
- //coverImage: string;
+ /**
+ * This is used in the UI, it is not updated or sent to Backend
+ */
+ coverImage: string;
coverImageLocked: boolean;
pages: number;
volumeId: number;
diff --git a/UI/Web/src/app/_services/action-factory.service.ts b/UI/Web/src/app/_services/action-factory.service.ts
index 28e5480f7..8ed3385cd 100644
--- a/UI/Web/src/app/_services/action-factory.service.ts
+++ b/UI/Web/src/app/_services/action-factory.service.ts
@@ -104,13 +104,6 @@ export class ActionFactoryService {
callback: this.dummyCallback,
requiresAdmin: true
});
-
- this.volumeActions.push({
- action: Action.Edit,
- title: 'Edit',
- callback: this.dummyCallback,
- requiresAdmin: false
- });
this.chapterActions.push({
action: Action.Edit,
@@ -203,6 +196,12 @@ export class ActionFactoryService {
title: 'Mark as Unread',
callback: this.dummyCallback,
requiresAdmin: false
+ },
+ {
+ action: Action.Edit,
+ title: 'Info',
+ callback: this.dummyCallback,
+ requiresAdmin: false
}
];
diff --git a/UI/Web/src/app/admin/manage-users/manage-users.component.html b/UI/Web/src/app/admin/manage-users/manage-users.component.html
index df1881ea8..232b9b2da 100644
--- a/UI/Web/src/app/admin/manage-users/manage-users.component.html
+++ b/UI/Web/src/app/admin/manage-users/manage-users.component.html
@@ -9,7 +9,7 @@
- {{member.username | titlecase}} Admin
+ {{member.username | titlecase}} Admin
diff --git a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html
index 1766c2701..4542f7938 100644
--- a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html
+++ b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html
@@ -30,11 +30,20 @@
Chapters
-
-
+
+
+
- Chapter {{formatChapterNumber(chapter)}}
+
+
+ Chapter {{formatChapterNumber(chapter)}}
+
+ 0 && chapter.pagesRead < chapter.pages">{{chapter.pagesRead}} / {{chapter.pages}}
+ UNREAD
+ READ
+
File(s)
@@ -57,7 +66,7 @@
diff --git a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts
index c50702b11..6cd02f5c8 100644
--- a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts
+++ b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.ts
@@ -1,11 +1,15 @@
import { Component, Input, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
+import { take } from 'rxjs/operators';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { MangaFile } from 'src/app/_models/manga-file';
import { MangaFormat } from 'src/app/_models/manga-format';
-import { Volume } from 'src/app/_models/volume';
+import { AccountService } from 'src/app/_services/account.service';
+import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
+import { ActionService } from 'src/app/_services/action.service';
import { ImageService } from 'src/app/_services/image.service';
import { UploadService } from 'src/app/_services/upload.service';
import { ChangeCoverImageModalComponent } from '../change-cover-image/change-cover-image-modal.component';
@@ -21,6 +25,7 @@ export class CardDetailsModalComponent implements OnInit {
@Input() parentName = '';
@Input() seriesId: number = 0;
+ @Input() libraryId: number = 0;
@Input() data!: any; // Volume | Chapter
isChapter = false;
chapters: Chapter[] = [];
@@ -31,20 +36,34 @@ export class CardDetailsModalComponent implements OnInit {
* If a cover image update occured.
*/
coverImageUpdate: boolean = false;
+ isAdmin: boolean = false;
+ actions: ActionItem[] = [];
+ chapterActions: ActionItem[] = [];
constructor(private modalService: NgbModal, public modal: NgbActiveModal, public utilityService: UtilityService,
- public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService) { }
+ public imageService: ImageService, private uploadService: UploadService, private toastr: ToastrService,
+ private accountService: AccountService, private actionFactoryService: ActionFactoryService,
+ private actionService: ActionService, private router: Router) { }
ngOnInit(): void {
this.isChapter = this.utilityService.isChapter(this.data);
+ this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
+ if (user) {
+ this.isAdmin = this.accountService.hasAdminRole(user);
+ }
+ });
+
+ this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this)).filter(item => item.action !== Action.Edit);
+
if (this.isChapter) {
this.chapters.push(this.data);
} else if (!this.isChapter) {
this.chapters.push(...this.data?.chapters);
}
this.chapters.sort(this.utilityService.sortChapters);
+ this.chapters.forEach(c => c.coverImage = this.imageService.getChapterCoverImage(c.id));
// Try to show an approximation of the reading order for files
var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
this.chapters.forEach((c: Chapter) => {
@@ -63,10 +82,17 @@ export class CardDetailsModalComponent implements OnInit {
return chapter.number;
}
+ performAction(action: ActionItem, chapter: Chapter) {
+ if (typeof action.callback === 'function') {
+ action.callback(action.action, chapter);
+ }
+ }
+
updateCover() {
const modalRef = this.modalService.open(ChangeCoverImageModalComponent, { size: 'lg' }); // scrollable: true, size: 'lg', windowClass: 'scrollable-modal' (these don't work well on mobile)
if (this.utilityService.isChapter(this.data)) {
const chapter = this.utilityService.asChapter(this.data)
+ chapter.coverImage = this.imageService.getChapterCoverImage(chapter.id);
modalRef.componentInstance.chapter = chapter;
modalRef.componentInstance.title = 'Select ' + (chapter.isSpecial ? '' : 'Chapter ') + chapter.range + '\'s Cover';
} else {
@@ -85,8 +111,52 @@ export class CardDetailsModalComponent implements OnInit {
this.uploadService.resetChapterCoverLock(closeResult.chapter.id).subscribe(() => {
this.toastr.info('Please refresh in a bit for the cover image to be reflected.');
});
+ } else {
+ closeResult.chapter.coverImage = this.imageService.randomize(this.imageService.getChapterCoverImage(closeResult.chapter.id));
}
}
});
}
+
+ markChapterAsRead(chapter: Chapter) {
+ if (this.seriesId === 0) {
+ return;
+ }
+
+ this.actionService.markChapterAsRead(this.seriesId, chapter, () => { /* No Action */ });
+ }
+
+ markChapterAsUnread(chapter: Chapter) {
+ if (this.seriesId === 0) {
+ return;
+ }
+
+ this.actionService.markChapterAsUnread(this.seriesId, chapter, () => { /* No Action */ });
+ }
+
+ handleChapterActionCallback(action: Action, chapter: Chapter) {
+ switch (action) {
+ case(Action.MarkAsRead):
+ this.markChapterAsRead(chapter);
+ break;
+ case(Action.MarkAsUnread):
+ this.markChapterAsUnread(chapter);
+ break;
+ default:
+ break;
+ }
+ }
+
+ readChapter(chapter: Chapter) {
+ if (chapter.pages === 0) {
+ this.toastr.error('There are no pages. Kavita was not able to read this archive.');
+ return;
+ }
+
+ if (chapter.files.length > 0 && chapter.files[0].format === MangaFormat.EPUB) {
+ this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'book', chapter.id]);
+ } else {
+ this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id]);
+ }
+ }
}
diff --git a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts
index 61e02c4ae..2ed296572 100644
--- a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts
+++ b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts
@@ -53,10 +53,6 @@ export class EditSeriesModalComponent implements OnInit, OnDestroy {
private uploadService: UploadService) { }
ngOnInit(): void {
- // this.imageUrls.push({
- // imageUrl: this.imageService.getSeriesCoverImage(this.series.id),
- // source: 'Url'
- // });
this.imageUrls.push(this.imageService.getSeriesCoverImage(this.series.id));
this.libraryService.getLibraryNames().pipe(takeUntil(this.onDestroy)).subscribe(names => {
diff --git a/UI/Web/src/app/cards/card-item/card-item.component.html b/UI/Web/src/app/cards/card-item/card-item.component.html
index 32b18b9e2..478764aaf 100644
--- a/UI/Web/src/app/cards/card-item/card-item.component.html
+++ b/UI/Web/src/app/cards/card-item/card-item.component.html
@@ -1,8 +1,8 @@
-
![]()
0 || supressArchiveWarning" class="card-img-top lazyload" [src]="imageService.placeholderImage" [attr.data-src]="imageUrl"
+
![]()
0 || supressArchiveWarning" class="img-top lazyload" [src]="imageService.placeholderImage" [attr.data-src]="imageUrl"
(error)="imageService.updateErroredImage($event)" aria-hidden="true" height="230px" width="158px">
-
0 && read !== (total -1)">
diff --git a/UI/Web/src/app/cards/card-item/card-item.component.scss b/UI/Web/src/app/cards/card-item/card-item.component.scss
index 722c71c4e..e1b5ceecc 100644
--- a/UI/Web/src/app/cards/card-item/card-item.component.scss
+++ b/UI/Web/src/app/cards/card-item/card-item.component.scss
@@ -38,7 +38,7 @@ $image-width: 160px;
margin-bottom: 0px;
}
-.card-img-top {
+.img-top {
height: $image-height;
}
diff --git a/UI/Web/src/app/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/series-detail.component.ts
index f094d4b54..f2fc17ee9 100644
--- a/UI/Web/src/app/series-detail/series-detail.component.ts
+++ b/UI/Web/src/app/series-detail/series-detail.component.ts
@@ -390,6 +390,7 @@ export class SeriesDetailComponent implements OnInit {
modalRef.componentInstance.data = data;
modalRef.componentInstance.parentName = this.series?.name;
modalRef.componentInstance.seriesId = this.series?.id;
+ modalRef.componentInstance.libraryId = this.series?.libraryId;
modalRef.closed.subscribe((result: {coverImageUpdate: boolean}) => {
if (result.coverImageUpdate) {
this.coverImageOffset += 1;