Swagger, Tachiyomi, and some new settings (#1331)

* Fixed up swagger generation

* Updated Tachiyomi's latest-chapter to hopefully solve some sync issues.

* Fixed #1279 with table of contents due to new EPubReader

* When errors occur, show the event widget icon in red

* Lots of documentation added and tweaked some wording around backups and swagger

* For promidius

* Return proper ChapterDTO

* Hacks for Promidius

* Cleanup code

* No loose leaf, send max chapter

* One more encode change

* Implemented code per promiduius' requirements

* Fixed a bug in the epub parsing where even if you had a series index and series group, but didn't have the series in the title, Kavita wouldn't group them properly.

* Removed some extra comment

* Implemented the ability to change a library's type after it's been setup. This displays a warning explaining the dangers of it.

* Removed some whitespace

* Blur descriptions based on read status for list item view to avoid spoilers

* Tweaked placement of a tooltip due to new series detail styles

* Hooked up a user preference for bluring unread summaries. Fixed a bug in refresh token where we would cause re-authentication when it shouldn't be needed.
This commit is contained in:
Joseph Milazzo 2022-06-25 17:52:21 -05:00 committed by GitHub
parent f2c08092b8
commit 2ab0aedd22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1851 additions and 72 deletions

View file

@ -33,6 +33,7 @@ export interface Preferences {
// Global
theme: SiteTheme;
globalPageLayoutMode: PageLayoutMode;
blurUnreadSummaries: boolean;
}
export const readingDirections = [{text: 'Left to Right', value: ReadingDirection.LeftToRight}, {text: 'Right to Left', value: ReadingDirection.RightToLeft}];

View file

@ -92,8 +92,9 @@ export class AccountService implements OnDestroy {
this.themeService.setTheme(this.themeService.defaultTheme);
}
this.currentUserSource.next(user);
this.currentUser = user;
this.currentUserSource.next(user);
if (this.currentUser !== undefined) {
this.startRefreshTokenTimer();
} else {

View file

@ -203,7 +203,6 @@ export class MessageHubService {
});
this.hubConnection.on(EVENTS.UserUpdate, resp => {
console.log('got UserUpdate', resp);
this.messagesSource.next({
event: EVENTS.UserUpdate,
payload: resp.body as UserUpdateEvent

View file

@ -19,7 +19,7 @@
<label for="library-type" class="form-label">Type</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="typeTooltip" role="button" tabindex="0"></i>
<ng-template #typeTooltip>Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but fall back to embedded data.</ng-template>
<span class="visually-hidden" id="library-type-help">Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but fall back to embedded data.</span>
<select class="form-select" id="library-type" formControlName="type" [attr.disabled]="this.library" aria-describedby="library-type-help">
<select class="form-select" id="library-type" formControlName="type" aria-describedby="library-type-help"> <!-- [attr.disabled]="this.library" -->
<option [value]="i" *ngFor="let opt of libraryTypes; let i = index">{{opt}}</option>
</select>
</div>

View file

@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { ConfirmService } from 'src/app/shared/confirm.service';
import { Library } from 'src/app/_models/library';
import { LibraryService } from 'src/app/_services/library.service';
import { SettingsService } from '../../settings.service';
@ -28,7 +29,7 @@ export class LibraryEditorModalComponent implements OnInit {
constructor(private modalService: NgbModal, private libraryService: LibraryService, public modal: NgbActiveModal, private settingService: SettingsService,
private toastr: ToastrService) { }
private toastr: ToastrService, private confirmService: ConfirmService) { }
ngOnInit(): void {
@ -45,7 +46,7 @@ export class LibraryEditorModalComponent implements OnInit {
this.madeChanges = true;
}
submitLibrary() {
async submitLibrary() {
const model = this.libraryForm.value;
model.folders = this.selectedFolders;
@ -57,6 +58,12 @@ export class LibraryEditorModalComponent implements OnInit {
model.id = this.library.id;
model.folders = model.folders.map((item: string) => item.startsWith('\\') ? item.substr(1, item.length) : item);
model.type = parseInt(model.type, 10);
if (model.type !== this.library.type) {
if (!await this.confirmService.confirm(`Changing library type will trigger a new scan with different parsing rules and may lead to
series being re-created and hence you may loose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?`)) return;
}
this.libraryService.update(model).subscribe(() => {
this.close(true);
}, err => {

View file

@ -29,7 +29,7 @@
</div>
<div class="col-md-4 col-sm-12 pe-2">
<label for="backup-tasks" class="form-label">Backup Tasks</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="backupTasksTooltip" role="button" tabindex="0"></i>
<label for="backup-tasks" class="form-label">Days of Backups</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="backupTasksTooltip" role="button" tabindex="0"></i>
<ng-template #backupTasksTooltip>The number of backups to maintain. Default is 30, minumum is 1, maximum is 30.</ng-template>
<span class="visually-hidden" id="backup-tasks-help">The number of backups to maintain. Default is 30, minumum is 1, maximum is 30.</span>
<input id="backup-tasks" aria-describedby="backup-tasks-help" class="form-control" formControlName="totalBackups" type="number" step="1" min="1" max="30" onkeypress="return event.charCode >= 48 && event.charCode <= 57">
@ -67,7 +67,7 @@
<div class="mb-3">
<label for="swagger-ui" class="form-label" aria-describedby="swaggerui-info">Expose Swagger UI</label>
<p class="accent" id="swaggerui-info">Allows Swagger UI to be exposed via swagger/ on your server. Authentication is not required, but a valid JWT token is. Requires a restart to take effect.</p>
<p class="accent" id="swaggerui-info">Allows Swagger UI to be exposed via swagger/ on your server. Authentication is not required, but a valid JWT token is. Requires a restart to take effect. Swagger is hosted on yourip:5000/swagger</p>
<div class="form-check form-switch">
<input id="swagger-ui" type="checkbox" class="form-check-input" formControlName="enableSwaggerUi" role="switch">
<label for="swagger-ui" class="form-check-label">Enable Swagger UI</label>

View file

@ -28,7 +28,7 @@
<h6 class="text-muted" [ngClass]="{'subtitle-with-actionables' : actions.length > 0}" style="font-size: 0.75rem" *ngIf="Title != '' && showTitle">{{Title}}</h6>
<ng-container *ngIf="summary.length > 0">
<div class="mt-2 ps-2">
<app-read-more [text]="summary" [maxLength]="250"></app-read-more>
<app-read-more [text]="summary" [blur]="pagesRead === 0 && blur" [maxLength]="250"></app-read-more>
</div>
</ng-container>
<div class="ps-2 d-none d-md-inline-block">

View file

@ -62,6 +62,10 @@ export class ListItemComponent implements OnInit {
* Show's the title if avaible on entity
*/
@Input() showTitle: boolean = true;
/**
* Blur the summary for the list item
*/
@Input() blur: boolean = false;
@Output() read: EventEmitter<void> = new EventEmitter<void>();

View file

@ -1,9 +1,12 @@
<ng-container *ngIf="isAdmin">
<button type="button" class="btn btn-icon {{activeEvents > 0 ? 'colored' : ''}}"
[ngbPopover]="popContent" title="Activity" placement="bottom" [popoverClass]="'nav-events'" [autoClose]="'outside'">
<i aria-hidden="true" class="fa fa-wave-square nav"></i>
</button>
<ng-container *ngIf="errors$ | async as errors">
<button type="button" class="btn btn-icon" [ngClass]="{'colored': activeEvents > 0, 'colored-error': errors.length > 0}"
[ngbPopover]="popContent" title="Activity" placement="bottom" [popoverClass]="'nav-events'" [autoClose]="'outside'">
<i aria-hidden="true" class="fa fa-wave-square nav"></i>
</button>
</ng-container>
<ng-template #popContent>
<ul class="list-group list-group-flush dark-menu">

View file

@ -66,6 +66,11 @@
border-radius: 60px;
}
.colored-error {
background-color: var(--error-color) !important;
border-radius: 60px;
}
.update-available {
cursor: pointer;

View file

@ -96,7 +96,7 @@
</div>
<div class="col-auto ms-2">
<ngb-rating class="rating-star" [(rate)]="series!.userRating" (rateChange)="updateRating($event)" (click)="promptToReview()"></ngb-rating>
<button *ngIf="series?.userRating || series.userRating" class="btn btn-sm btn-icon" (click)="openReviewModal(true)" placement="top" ngbTooltip="Edit Review" attr.aria-label="Edit Review"><i class="fa fa-pen" aria-hidden="true"></i></button>
<button *ngIf="series?.userRating || series.userRating" class="btn btn-sm btn-icon" (click)="openReviewModal(true)" placement="bottom" ngbTooltip="Edit Review" attr.aria-label="Edit Review"><i class="fa fa-pen" aria-hidden="true"></i></button>
</div>
</div>
<div class="row g-0">
@ -142,7 +142,8 @@
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(item.volume.id)"
[seriesName]="series.name" [entity]="item.volume" *ngIf="item.volume.number != 0"
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
[pagesRead]="item.volume.pagesRead" [totalPages]="item.volume.pages" (read)="openVolume(item.volume)">
[pagesRead]="item.volume.pagesRead" [totalPages]="item.volume.pages" (read)="openVolume(item.volume)"
[blur]="user?.preferences?.blurUnreadSummaries || false">
<ng-container title>
<app-entity-title [libraryType]="libraryType" [entity]="item.volume" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
</ng-container>
@ -152,7 +153,8 @@
<app-list-item [imageUrl]="imageService.getChapterCoverImage(item.chapter.id)"
[seriesName]="series.name" [entity]="item.chapter" *ngIf="!item.chapter.isSpecial"
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
[pagesRead]="item.chapter.pagesRead" [totalPages]="item.chapter.pages" (read)="openChapter(item.chapter)">
[pagesRead]="item.chapter.pagesRead" [totalPages]="item.chapter.pages" (read)="openChapter(item.chapter)"
[blur]="user?.preferences?.blurUnreadSummaries || false">
<ng-container title>
<app-entity-title [libraryType]="libraryType" [entity]="item.chapter" [seriesName]="series.name" [prioritizeTitleName]="false"></app-entity-title>
</ng-container>
@ -185,7 +187,8 @@
<app-list-item [imageUrl]="imageService.getVolumeCoverImage(volume.id)"
[seriesName]="series.name" [entity]="volume" *ngIf="volume.number != 0"
[actions]="volumeActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
[pagesRead]="volume.pagesRead" [totalPages]="volume.pages" (read)="openVolume(volume)">
[pagesRead]="volume.pagesRead" [totalPages]="volume.pages" (read)="openVolume(volume)"
[blur]="user?.preferences?.blurUnreadSummaries || false">
<ng-container title>
<app-entity-title [libraryType]="libraryType" [entity]="volume" [seriesName]="series.name"></app-entity-title>
</ng-container>
@ -222,7 +225,7 @@
[seriesName]="series.name" [entity]="chapter" *ngIf="!chapter.isSpecial"
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
[includeVolume]="true">
[includeVolume]="true" [blur]="user?.preferences?.blurUnreadSummaries || false">
<ng-container title>
<app-entity-title [libraryType]="libraryType" [entity]="chapter" [seriesName]="series.name" [includeVolume]="true" [prioritizeTitleName]="false"></app-entity-title>
</ng-container>
@ -254,7 +257,8 @@
<app-list-item [imageUrl]="imageService.getChapterCoverImage(chapter.id)"
[seriesName]="series.name" [entity]="chapter"
[actions]="chapterActions" [libraryType]="libraryType" imageWidth="130px" imageHeight=""
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)">
[pagesRead]="chapter.pagesRead" [totalPages]="chapter.pages" (read)="openChapter(chapter)"
[blur]="user?.preferences?.blurUnreadSummaries || false">
<ng-container title>
{{chapter.title || chapter.range}}
</ng-container>
@ -287,4 +291,3 @@
</div>
</div>
</div>

View file

@ -38,6 +38,7 @@ import { CardDetailDrawerComponent } from '../cards/card-detail-drawer/card-deta
import { FormControl, FormGroup } from '@angular/forms';
import { PageLayoutMode } from '../_models/page-layout-mode';
import { DOCUMENT } from '@angular/common';
import { User } from '../_models/user';
interface RelatedSeris {
series: Series;
@ -154,6 +155,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterViewInit {
});
isAscendingSort: boolean = false; // TODO: Get this from User preferences
user: User | undefined;
bulkActionCallback = (action: Action, data: any) => {
if (this.series === undefined) {
@ -252,6 +254,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterViewInit {
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.user = user;
this.isAdmin = this.accountService.hasAdminRole(user);
this.hasDownloadingRole = this.accountService.hasDownloadRole(user);
this.renderMode = user.preferences.globalPageLayoutMode;

View file

@ -1,5 +1,5 @@
<div>
<span [innerHTML]="currentText | safeHtml"></span>
<span [innerHTML]="currentText | safeHtml" [ngClass]="{'blur-text': blur && isCollapsed}"></span>
<a [class.hidden]="hideToggle" *ngIf="text && text.length > maxLength" class="read-more-link" (click)="toggleView()">
&nbsp;<i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}}"></i>&nbsp;Read {{isCollapsed ? 'More' : 'Less'}}
</a>

View file

@ -1,6 +1,4 @@
// .read-more-link {
// font-weight: bold;
// text-decoration: none;
// cursor: pointer;
// color: var(--body-text-color) !important;
// }
.blur-text {
color: transparent;
text-shadow: 0 0 5px var(--body-text-color);
}

View file

@ -9,6 +9,10 @@ export class ReadMoreComponent implements OnChanges {
@Input() text!: string;
@Input() maxLength: number = 250;
/**
* If the field is collapsed and blur true, text will not be readable
*/
@Input() blur: boolean = false;
currentText!: string;
hideToggle: boolean = true;

View file

@ -34,6 +34,16 @@
<option *ngFor="let opt of pageLayoutModes" [value]="opt.value">{{opt.text | titlecase}}</option>
</select>
</div>
<div class="col-md-6 col-sm-12 pe-2 mb-2">
<div class="form-check form-switch">
<input type="checkbox" id="auto-close" role="switch" formControlName="blurUnreadSummaries" class="form-check-input" aria-describedby="blurUnreadSummaries" [value]="true" aria-labelledby="auto-close-label">
<label class="form-check-label" for="auto-close">Blur Unread Summaries</label><i class="fa fa-info-circle ms-1" aria-hidden="true" placement="right" [ngbTooltip]="blurUnreadSummariesTooltip" role="button" tabindex="0"></i>
</div>
<ng-template #blurUnreadSummariesTooltip>Blurs summary text on volumes or chapters that have no read progress (to avoid spoilers)</ng-template>
<span class="visually-hidden" id="settings-global-blurUnreadSummaries-help">Blurs summary text on volumes or chapters that have no read progress (to avoid spoilers)</span>
</div>
</div>
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">

View file

@ -123,6 +123,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.settingsForm.addControl('theme', new FormControl(this.user.preferences.theme, []));
this.settingsForm.addControl('globalPageLayoutMode', new FormControl(this.user.preferences.globalPageLayoutMode, []));
this.settingsForm.addControl('blurUnreadSummaries', new FormControl(this.user.preferences.blurUnreadSummaries, []));
});
this.passwordChangeForm.addControl('password', new FormControl('', [Validators.required]));
@ -169,6 +170,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
this.settingsForm.get('theme')?.setValue(this.user.preferences.theme);
this.settingsForm.get('bookReaderImmersiveMode')?.setValue(this.user.preferences.bookReaderImmersiveMode);
this.settingsForm.get('globalPageLayoutMode')?.setValue(this.user.preferences.globalPageLayoutMode);
this.settingsForm.get('blurUnreadSummaries')?.setValue(this.user.preferences.blurUnreadSummaries);
}
resetPasswordForm() {
@ -200,6 +202,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
theme: modelSettings.theme,
bookReaderImmersiveMode: modelSettings.bookReaderImmersiveMode,
globalPageLayoutMode: parseInt(modelSettings.globalPageLayoutMode, 10),
blurUnreadSummaries: modelSettings.blurUnreadSummaries,
};
this.obserableHandles.push(this.accountService.updatePreferences(data).subscribe((updatedPrefs) => {